:root {
  color-scheme: light;
  --app-ink: #18322c;
  --app-muted: #52645f;
  --app-surface: #f5f8f4;
  --app-card: #ffffff;
  --app-accent: #325c53;
  --app-focus: #f2a900;

  /* The palette, named by role.
     Every colour in this file is one of these. A design language whose
     palette lives as scattered literals cannot be changed safely: the first
     tier-wide or theme-wide decision would be a find-and-replace across
     eighteen sites, and one missed site is a page that looks broken. The
     names say what a colour is for rather than what it looks like, because
     the next person changing it needs to know which things move together.
     static/app_test.go fails if a literal reappears below. */
  --app-body-size: 1rem;
  --app-safety-size: 1rem;
  --app-border: #cddbd6;
  --app-border-soft: #dfe7e2;
  --app-rule: #dbe5df;
  --app-rule-faint: #e6ece8;
  --app-rule-header: #d9e4e1;
  --app-rule-hairline: #e4ece8;
  --app-tint: #edf4f0;
  --app-tint-strong: #dceee7;
  --app-tint-ink: #204c42;
  --app-tint-quiet: #e3ebe6;
  --app-card-raised: #f7faf8;
  --app-card-alt: #fafcf9;
  --app-accent-soft: #4f756b;
  --app-white: #ffffff;
  --app-notice: #fff5d8;
  --app-notice-ink: #8a5a00;
  --app-notice-deep: #5e3d00;
  --app-notice-soft: #fff8dd;
  --app-link: #245a76;
  --bulma-scheme-main: var(--app-card);
  --bulma-scheme-main-bis: var(--app-card-alt);
  --bulma-scheme-main-ter: var(--app-surface);
  --bulma-background: var(--app-surface);
  --bulma-text: var(--app-ink);
  --bulma-text-strong: var(--app-ink);
  --bulma-text-title: var(--app-ink);
  --bulma-text-weak: var(--app-muted);
  --bulma-link-text: var(--app-link);

  /* Bulma's own component colours, mapped to ours.
     Bulma 1.x derives every shade of a colour — the light variant, the invert,
     the hover — from three channels, so setting those three re-themes the
     whole family. Without this, .button.is-primary renders Bulma's stock
     turquoise (rgb(0, 209, 178)) and .button.is-link its stock indigo, which
     is what the app actually shipped: a documented palette that appeared on
     the wordmark and almost nowhere else. Setting the composed colours instead
     of the channels does not work; the derived shades ignore it. */
  --bulma-primary-h: 167deg;
  --bulma-primary-s: 30%;
  --bulma-primary-l: 28%;
  --bulma-link-h: 200deg;
  --bulma-link-s: 53%;
  --bulma-link-l: 30%;

  /* Bulma picks a button's text colour by deriving an "invert" lightness from
     the same channels. On an accent this dark it chose near-black on dark
     green — 2.52:1, which fails AA and is unreadable outdoors. Pin both
     inverts to white: 7.42:1 on the accent, 8.5:1 on the link. */
  --bulma-primary-invert-l: 100%;
  --bulma-primary-invert-s: 0%;
  --bulma-link-invert-l: 100%;
  --bulma-link-invert-s: 0%;
}

* {
  box-sizing: border-box;
}

html,
body {
  min-width: 0;
  max-width: 100%;
  overflow-x: hidden;
}

.button,
.input,
.select select {
  min-height: 44px;
}

.profile-list li {
  border-bottom: 1px solid var(--app-rule-header);
  padding: 0.75rem 0;
}

.quest-grid {
  display: grid;
  gap: var(--app-body-size);
}

.weekday-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
}

.weekday-grid .choice-card {
  margin: 0;
}

.chore-photo-preview {
  display: block;
  width: 100%;
  max-height: 24rem;
  object-fit: contain;
}

.quest-card {
  border: 1px solid var(--app-rule-header);
}

.tag.is-foundation {
  background: var(--app-tint-strong);
  color: var(--app-tint-ink);
  font-weight: 700;
}

.review-due {
  padding: 0.75rem;
  border-left: 0.25rem solid var(--app-notice-ink);
  background: var(--app-notice);
  color: var(--app-notice-deep);
}

.choice-card {
  display: flex;
  min-height: 52px;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
  padding: 0.85rem;
  border: 1px solid var(--app-border);
  border-radius: 0.65rem;
  background: var(--app-card);
}

.choice-card input {
  width: 1.25rem;
  height: 1.25rem;
  flex: 0 0 auto;
}

.onboarding-actions {
  align-items: stretch;
}

.onboarding-actions .button {
  margin-right: 0;
}

.child-mode-entry {
  padding-top: 1.25rem;
  border-top: 1px solid var(--app-rule);
}

/* The sentence that used to be the second half of a button label. It explains
   the path without competing with the two account buttons above it, so it is
   quiet text rather than the accented callout .support-note draws. */
.entry-note {
  margin-bottom: 0.75rem;
  color: var(--app-muted);
  line-height: 1.5;
}

.auth-lane-grid {
  display: grid;
  gap: var(--app-body-size);
}

.auth-lane-card {
  display: flex;
  min-width: 0;
  flex-direction: column;
  padding: 1.25rem;
  border: 1px solid var(--app-border);
  border-radius: 0.75rem;
  background: var(--app-card);
}

.auth-lane-card .button {
  margin-top: auto;
}

.lane-preview {
  margin-left: 1.25rem;
}

.lane-preview li {
  margin-bottom: 0.35rem;
}

.onboarding-progress {
  padding: var(--app-body-size);
  overflow-wrap: anywhere;
}

.onboarding-steps {
  display: grid;
  margin: 0;
  gap: 0.45rem;
  list-style: none;
}

.onboarding-steps li {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  align-items: baseline;
  padding: 0.45rem 0.65rem;
  border-left: 0.25rem solid var(--app-border);
}

.onboarding-steps li.is-complete {
  border-left-color: var(--app-accent-soft);
  color: var(--app-muted);
}

.onboarding-steps li.is-current {
  border-left-color: var(--app-focus);
  background: var(--app-notice-soft);
  font-weight: 700;
}

.onboarding-step-status {
  color: var(--app-muted);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.verification-next[hidden] {
  display: none;
}

@media (min-width: 769px) {
  .auth-lane-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.history-list li {
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--app-rule-hairline);
}

.box,
.choice-card {
  background: var(--app-card);
  color: var(--app-ink);
}

.label,
.title,
strong {
  color: var(--app-ink);
}

body {
  min-height: 100vh;
  background: var(--app-surface);
  color: var(--app-ink);
  /* Body text is a token so that anything required to be at least body size —
     the safety line, today — can say so rather than hope. */
  font-size: var(--app-body-size);
}

.connection-status {
  position: sticky;
  z-index: 20;
  top: 0;
  width: 100%;
  margin: 0;
  border-radius: 0;
  text-align: center;
  font-weight: 700;
}

.connection-status[hidden] {
  display: none;
}

.skip-link {
  position: fixed;
  z-index: 10;
  top: 0.5rem;
  left: 0.5rem;
  padding: 0.75rem var(--app-body-size);
  transform: translateY(-160%);
  border-radius: 0.5rem;
  background: var(--app-card);
  color: var(--app-ink);
}

.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--app-focus);
  outline-offset: 2px;
}

.app-header {
  border-bottom: 1px solid var(--app-rule);
  background: var(--app-card);
}

.app-wordmark {
  color: var(--app-accent);
  font-size: 1.125rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.welcome-card {
  margin: 0 auto;
  padding: clamp(1.5rem, 5vw, 2.5rem);
  border: 1px solid var(--app-rule);
  border-radius: var(--app-body-size);
  background: var(--app-card);
  box-shadow: 0 0.75rem 2rem rgb(35 68 59 / 8%);
  overflow-wrap: anywhere;
}

.welcome-card .title {
  max-width: 14ch;
  margin-bottom: var(--app-body-size);
  color: var(--app-ink);
  line-height: 1.08;
}

.welcome-card .subtitle {
  max-width: 34rem;
  color: var(--app-muted);
  line-height: 1.5;
}

.eyebrow {
  margin-bottom: 0.75rem;
  color: var(--app-accent);
  font-size: 0.85rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.support-note {
  padding: var(--app-body-size);
  border-left: 0.3rem solid var(--app-accent);
  border-radius: 0.25rem;
  background: var(--app-tint);
  color: var(--app-ink);
  line-height: 1.5;
}

a:focus-visible,
.button:focus-visible,
.input:focus-visible,
.select select:focus-visible,
.textarea:focus-visible {
  outline: 3px solid var(--app-focus);
  outline-offset: 3px;
}

@media (max-width: 480px) {
  .section {
    padding-top: 1.5rem;
  }

  .welcome-card .title {
    font-size: 2rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ---------------------------------------------------------------------------
   The AI partner conversation.

   Anchored on Open WebUI's chat layout, which is the pattern almost every
   other chat interface descends from and the one a child who has used any of
   them already knows: a thread that reads downwards, the person's turn as a
   compact bubble on the right, the partner's reply full width on the left
   behind a small mark, and the composer docked at the bottom.

   What is deliberately not taken from it: the conversation sidebar, the model
   picker, regenerate and edit controls, file upload and web search. Those are
   adult power-user surfaces, and several are things this product has ruled
   out. The safety line sits where Open WebUI puts its model disclaimer, under
   the composer, because that is the one place on the page that never scrolls
   away.
   ------------------------------------------------------------------------ */

.partner-body {
  display: flex;
  min-height: 100dvh;
  flex-direction: column;
  background: var(--app-card);
}

.partner-bar {
  position: sticky;
  top: 0;
  z-index: 2;
  border-bottom: 1px solid var(--app-rule-faint);
  background: var(--app-card);
}

.partner-bar-inner {
  display: flex;
  max-width: 46rem;
  align-items: center;
  margin: 0 auto;
  padding: 0.6rem 0.9rem;
  gap: 0.75rem;
}

.partner-back {
  flex: 0 0 auto;
  padding: 0.25rem 0.5rem;
  border-radius: 0.5rem;
  color: var(--app-accent);
  font-size: 1.25rem;
  line-height: 1;
  text-decoration: none;
}

.partner-title {
  flex: 1 1 auto;
  margin: 0;
  color: var(--app-ink);
  font-size: 1.05rem;
  font-weight: 700;
}

.partner-privacy {
  flex: 0 0 auto;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: var(--app-tint);
  color: var(--app-accent);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.partner-shell {
  display: flex;
  width: 100%;
  max-width: 46rem;
  flex: 1 1 auto;
  flex-direction: column;
  margin: 0 auto;
}

.partner-thread {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: 1.25rem 0.9rem 0.5rem;
}

/* A short conversation sits just above the composer rather than stranded at
   the top of an empty column. Pushing the first turn down does this without
   the clipping that justify-content causes once the thread overflows. */
.partner-thread .turn:first-child {
  margin-top: auto;
}

/* One turn ------------------------------------------------------------- */

.turn {
  margin-bottom: 1.5rem;
}

.turn-body {
  margin: 0;
  color: var(--app-ink);
  line-height: 1.6;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.turn-meta {
  margin: 0.4rem 0 0;
  color: var(--app-muted);
  font-size: 0.78rem;
  line-height: 1.45;
}

/* The person's own turn: compact, tinted, to the right. It is short and it is
   theirs, so it does not need the width. */
.turn-person {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.turn-person .turn-bubble {
  max-width: 85%;
  padding: 0.7rem 0.95rem;
  border-radius: 1.var(--app-body-size) 1.var(--app-body-size) 0.25rem 1.var(--app-body-size);
  background: var(--app-tint);
}

/* The partner's turn: full width behind a small mark, never in a bubble. A
   long explanation has to be readable, and a bubble fights that. */
.turn-partner {
  display: grid;
  grid-template-columns: 1.75rem 1fr;
  gap: 0.7rem;
}

.turn-mark {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--app-accent);
}

.turn-mark svg {
  display: block;
  width: 100%;
  height: 100%;
}

.turn-quiet .turn-mark {
  color: var(--app-muted);
}

.turn-label {
  margin: 0 0 0.3rem;
  color: var(--app-muted);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Waiting: three dots where the reply will appear, rather than a card
   announcing that something is happening elsewhere. */
.turn-thinking {
  display: flex;
  height: 1.6rem;
  align-items: center;
  margin: 0;
  gap: 0.3rem;
}

.turn-thinking span {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--app-accent);
  opacity: 0.35;
  animation: turn-thinking 1.2s ease-in-out infinite;
}

.turn-thinking span:nth-child(2) {
  animation-delay: 0.15s;
}

.turn-thinking span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes turn-thinking {
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
  40% { opacity: 1; transform: translateY(-0.2rem); }
}

@media (prefers-reduced-motion: reduce) {
  .turn-thinking span {
    animation: none;
    opacity: 0.55;
  }
}

/* Nothing asked yet ----------------------------------------------------- */

.partner-empty {
  padding: 2.5rem 0 var(--app-body-size);
  text-align: center;
}

.partner-empty-title {
  margin: 0 0 0.5rem;
  color: var(--app-ink);
  font-size: 1.6rem;
  font-weight: 800;
}

.partner-empty-note {
  margin: 0 auto 1.75rem;
  max-width: 26rem;
  color: var(--app-muted);
  line-height: 1.55;
}

.partner-starters {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  gap: 0.6rem;
  list-style: none;
  text-align: left;
}

/* A starter is its own form, so choosing one is an ordinary submit and works
   with no JavaScript at all. */
.starter {
  width: 100%;
  padding: 0.85rem var(--app-body-size);
  border: 1px solid var(--app-border-soft);
  border-radius: 0.9rem;
  background: var(--app-card);
  color: var(--app-ink);
  font: inherit;
  line-height: 1.4;
  text-align: left;
  cursor: pointer;
}

.starter:hover {
  border-color: var(--app-accent);
  background: var(--app-card-raised);
}

/* Composer and the line that never scrolls away ------------------------- */

.partner-foot {
  position: sticky;
  bottom: 0;
  padding: 0.75rem 0.9rem var(--app-body-size);
  border-top: 1px solid var(--app-rule-faint);
  background: var(--app-card);
}

.partner-composer {
  display: flex;
  align-items: flex-end;
  padding: 0.4rem 0.4rem 0.4rem 0.9rem;
  border: 1px solid var(--app-border-soft);
  border-radius: 1.4rem;
  background: var(--app-card);
  gap: 0.5rem;
}

.partner-composer:focus-within {
  border-color: var(--app-accent);
}

.partner-composer textarea {
  flex: 1 1 auto;
  max-height: 9rem;
  padding: 0.55rem 0;
  border: 0;
  background: transparent;
  color: var(--app-ink);
  font: inherit;
  line-height: 1.5;
  resize: none;
}

.partner-composer textarea:focus {
  outline: none;
}

.composer-send {
  display: flex;
  width: 2.4rem;
  height: 2.4rem;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--app-accent);
  color: var(--app-white);
  cursor: pointer;
}

.composer-send svg {
  width: 1.3rem;
  height: 1.3rem;
}

.partner-off {
  margin: 0;
  padding: 0.9rem var(--app-body-size);
  border: 1px dashed var(--app-border-soft);
  border-radius: 0.9rem;
  color: var(--app-muted);
  line-height: 1.5;
}

/* Where Open WebUI puts its model disclaimer. Here it is the safety line, and
   it is the reason this place was worth copying: it is the only part of the
   page that is always on screen. */
.partner-safety {
  margin: 0.7rem 0 0;
  color: var(--app-muted);
  /* Never smaller than body text. This line is the only safety surface the
     module has until escalation detection exists, and it was set at 0.8rem —
     two thirds the size of a child's body text — which made the most
     important sentence on the page the smallest one. It scales with the
     reader, so a tier that enlarges body text enlarges this too. */
  font-size: var(--app-safety-size);
  line-height: 1.5;
  text-align: center;
}

/* The rail: the wide-screen half of Open WebUI's shell. Hidden on a phone,
   where the top bar carries the same links and the composer carries the
   safety line. */

/* On a phone the rail is a drawer. A checkbox opens it, so it works with no
   JavaScript; the label in the bar is the handle and the scrim closes it. */

.rail-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  clip-path: inset(50%);
}

.rail-scrim,
.rail-close,
.rail-open {
  cursor: pointer;
}

.rail-open {
  flex: 0 0 auto;
  width: 1.9rem;
  height: 1.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--app-border-soft);
  border-radius: 50%;
  color: var(--app-accent);
  font-weight: 800;
}

.rail-toggle:focus-visible ~ .partner-main .rail-open {
  outline: 3px solid var(--app-focus);
  outline-offset: 2px;
}

.rail-scrim {
  position: fixed;
  inset: 0;
  z-index: 3;
  background: rgba(24, 50, 44, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.rail-toggle:checked ~ .rail-scrim {
  opacity: 1;
  pointer-events: auto;
}

.partner-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 4;
  display: flex;
  width: min(19rem, 85vw);
  flex-direction: column;
  padding: 1.5rem 1.25rem;
  overflow-y: auto;
  background: var(--app-surface);
  transform: translateX(-100%);
  transition: transform 0.2s ease;
}

.rail-toggle:checked ~ .partner-rail {
  transform: translateX(0);
}

@media (prefers-reduced-motion: reduce) {
  .partner-rail,
  .rail-scrim {
    transition: none;
  }
}

.rail-close {
  align-self: flex-end;
  margin-bottom: 0.5rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  background: var(--app-tint-quiet);
  color: var(--app-accent);
  font-size: 0.8rem;
  font-weight: 700;
}

.rail-home {
  margin-bottom: 1.75rem;
  color: var(--app-accent);
  font-weight: 600;
  text-decoration: none;
}

.rail-title {
  margin: 0 0 0.5rem;
  color: var(--app-ink);
  font-size: 1.35rem;
  font-weight: 800;
}

.rail-note {
  margin: 0 0 2rem;
  color: var(--app-muted);
  font-size: 0.88rem;
  line-height: 1.55;
}

.rail-heading {
  margin: 0 0 0.6rem;
  color: var(--app-muted);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.rail-safety {
  margin: auto 0 0;
  padding-top: 1.5rem;
  color: var(--app-muted);
  /* The rail carries the safety line on a wide screen, where the composer
     copy is hidden. Same rule: never below body size. */
  font-size: var(--app-safety-size);
  line-height: 1.5;
}

.partner-main {
  display: flex;
  min-width: 0;
  flex: 1 1 auto;
  flex-direction: column;
}

@media (min-width: 60rem) {
  .partner-body {
    flex-direction: row;
  }

  /* On a wide screen the drawer becomes a column that is simply always
     there: no handle, no scrim, nothing to open. */
  .partner-rail {
    position: static;
    width: 19rem;
    flex: 0 0 19rem;
    border-right: 1px solid var(--app-rule-faint);
    transform: none;
  }

  .rail-close,
  .rail-open,
  .rail-scrim {
    display: none;
  }

  /* The rail already carries both, so the page does not say either twice. */
  .partner-bar,
  .partner-safety,
  .partner-starters-empty {
    display: none;
  }

  .partner-thread {
    padding-top: 2.5rem;
  }

  .partner-empty {
    padding-top: 6rem;
  }

  .partner-foot {
    border-top: 0;
    padding-bottom: 1.5rem;
  }
}

/* Beyond a comfortable reading measure the thread stops widening and centres.
   A whole tab is worth having for the shell; a line of text 1400px wide is
   not worth reading. */
.partner-shell {
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
}

/* The child's own colour choice.
   ---------------------------------------------------------------------------
   Children asked for this, and it is the one decision in the app that is
   entirely theirs. The chooser is a list of ordinary form buttons: one tap is
   one decision, and the page that comes back is already wearing the colour, so
   there is nothing to confirm and nothing to undo.

   The swatch carries an inline background because the colour is the content —
   it is the only thing on the page that cannot be expressed as a class. It
   comes from a closed set of literals in models/appearance.go, never from
   anything a child typed. */
.colour-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.colour-choice {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 60px;
  padding: 0.75rem;
  border: 2px solid var(--app-border);
  border-radius: 0.9rem;
  background: var(--app-card);
  color: var(--app-ink);
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.colour-choice.is-chosen {
  border-color: var(--app-accent);
  border-width: 3px;
}
.colour-swatch {
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--app-border);
  border-radius: 0.6rem;
}
.colour-label {
  flex: 1 1 auto;
  /* Without this a long label refuses to shrink and pushes "Chosen" out of
     the button entirely, which is what it did. */
  min-width: 0;
}
/* "Chosen" is a word, not only a thicker border: the current choice must not
   be carried by colour or weight alone. */
.colour-chosen {
  flex: none;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--app-accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* The navigation.
   ---------------------------------------------------------------------------
   Before this there was no navigation: every page carried one back link and
   the only way to know where you could go was to return to a home screen 2632
   pixels long and read it again. That asks a reader to hold the map in their
   head, which is the thing this product exists to stop asking.

   It sits at the top, sticky, in the markup above the content it navigates.
   At the bottom it was read last by a screen reader, and on the home screen it
   lit the tab furthest from where a child starts looking — the page announced
   Today at the top and Me at the bottom, about itself.

   Words, not icons. The repository ships no icon set, and five invented glyphs
   would be five more things to decode. Five labels cost one row and are never
   ambiguous.

   The partner has a route of its own because it is a place a child goes, not
   an errand run from the home screen; it was the fourth card down a page that
   is still nine cards long. Its own page keeps its full-screen shell and this
   bar stays off it, so no tab there is ever current.

   The active tab is marked twice — colour and a rule under it — plus
   aria-current, because colour alone is not a state.

   It scrolls with the page rather than sticking. position: sticky does nothing
   for a child of body here: html and body carry overflow-x: hidden, so they
   are the scroll container and a sticky child of theirs has nothing to stick
   to. (The partner page's bars do stick, because that shell scrolls inside
   itself.) The clamp is the guarantee that no page ever scrolls sideways, and
   it is worth more than a nav that follows you down. The fix is one combined
   app bar — wordmark, nav, profile — fixed as a unit, which is the navigation
   work still to come. */
.tabbar {
  background: var(--app-card);
  border-bottom: 1px solid var(--app-rule);
}
.tabbar-inner {
  display: flex;
}
.tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0 0.25rem;
  border-bottom: 0.2rem solid transparent;
  color: var(--app-muted);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}
.tab.is-current {
  border-bottom-color: var(--app-accent);
  color: var(--app-accent);
}
.tab:focus-visible {
  outline: 3px solid var(--app-focus);
  outline-offset: -3px;
}

/* Asking for help -------------------------------------------------------- */

/* The child's door. It sits above everything on the home screen and is the
   only card there with a border, because a child looking for it is not
   scanning calmly and must not have to read nine headings to find it. */
.help-entry {
  border: 2px solid var(--app-accent);
}

/* Each choice is a whole sentence in a child's words, so it wraps rather than
   truncating. A safeguarding option cut off mid-line is a safeguarding option
   a child cannot read. */
.help-choice {
  height: auto;
  min-height: 44px;
  /* Bulma centres button content. A one-line choice would then sit centred
     between two that wrap and start at the left, so the list reads as ragged
     at the moment a child is scanning it fastest. */
  justify-content: flex-start;
  padding: 0.75rem 1rem;
  white-space: normal;
  text-align: left;
  line-height: 1.4;
}

/* The caregiver's notice. Marked by a border rather than by a colour that
   means danger: a child asking for help is the product working, and a red
   alarm on a parent's screen frames it as an emergency to be managed. */
.help-asked {
  border-left: 0.35rem solid var(--app-accent);
}

.rail-help {
  display: inline-block;
  margin-top: 0.75rem;
  font-weight: 700;
  color: var(--app-link);
}

/* The hours, and being outside them -------------------------------------- */

.hours-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 0.75rem;
}
.hours-table th,
.hours-table td {
  padding: 0.35rem 0.4rem 0.35rem 0;
  text-align: left;
  vertical-align: middle;
}
.hours-table thead th {
  font-size: 0.85rem;
  color: var(--app-muted);
}
.hours-table .input {
  min-width: 0;
}

/* Resting, not off. The wording and the colour both have to carry that: a
   child who reads this as "taken away" stops coming back, and the whole point
   of naming the time is that it is a wait rather than a loss. Same tint as
   every other quiet note, not a warning colour. */
.partner-closed {
  padding: 0.85rem 1rem;
  border-radius: 0.5rem;
  background: var(--app-tint);
  color: var(--app-tint-ink);
}

/* The tab, greyed while the hours are closed.
   ---------------------------------------------------------------------------
   This is a courtesy to the person looking at it and nothing more. It is
   delivered in the per-child stylesheet that already carries their colour, so
   it needs no field threaded through nine page structs — and it is honest
   about what it is, because CSS cannot restrict anything. The restriction is
   in AIPartnerService.Ask, where developer tools, a replayed form and curl all
   meet the same refusal. The tab stays clickable on purpose: a child who taps
   it reaches a page that tells them when it comes back, and a control that is
   dead and silent teaches nothing. */

/* The home screen ---------------------------------------------------------
   It was eleven cards and 2972 pixels, which is three and a half screens of
   identical white rectangles a child has to read to find anything. Four of
   them -- chores, routines, the partner, notes -- were the four places the tab
   bar already goes, so the page was largely a second copy of the navigation
   rendered as a list. Those are gone from here; the nav is the nav.

   What is left is what only this page can say: who you are, the one thing to
   do today, the map, and a quiet list of the once-in-a-while things. */

/* Help is a bar rather than a card. It must be unmissable and must not be the
   loudest thing on a child's home screen every single day -- a permanent
   emergency-sized panel teaches a child that this is an emergency app. */
.help-bar {
  display: block;
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  border: 2px solid var(--app-accent);
  border-radius: 0.6rem;
  background: var(--app-card);
  color: var(--app-ink);
  text-decoration: none;
}
.help-bar strong {
  display: block;
  color: var(--app-accent);
}
.help-bar span {
  font-size: 0.9rem;
  color: var(--app-muted);
}
.help-bar:focus-visible {
  outline: 3px solid var(--app-focus);
  outline-offset: 2px;
}

/* The once-in-a-while things, as a list rather than four more cards. Each is
   a row a child reads in one glance instead of a heading, a paragraph and a
   full-width button repeated four times. */
.more-list {
  background: var(--app-card);
  border-radius: 0.6rem;
  padding: 1rem 1.25rem 0.25rem;
}
.more-list a {
  display: block;
  padding: 0.75rem 0;
  border-top: 1px solid var(--app-rule-faint);
  color: var(--app-link);
  text-decoration: none;
  font-weight: 600;
}
.more-list a:focus-visible {
  outline: 3px solid var(--app-focus);
  outline-offset: -3px;
}
.more-note {
  display: block;
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--app-muted);
}

/* The caregiver shell ------------------------------------------------------
   A top bar, a side nav, an app drawer and a profile menu, with no
   JavaScript. Each panel is a checkbox and a label: this app renders on the
   server and works with scripting off (ADR-001), so navigation that needs a
   script is navigation that is sometimes missing. A label is focusable and
   announced without hand-written aria, and a checkbox cannot disagree with
   itself, which is most of what breaks in a hand-rolled menu. */

.shell-toggle {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-height: 56px;
  padding: 0 0.5rem;
  background: var(--app-card);
  border-bottom: 1px solid var(--app-rule-header);
}
.topbar-mark {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0 0.5rem;
  font-weight: 700;
  color: var(--app-accent);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
  color: var(--app-accent);
}
.topbar-button:hover {
  background: var(--app-tint);
}
.shell-toggle:focus-visible + .shell-toggle + .shell-toggle + .topbar .nav-button,
.topbar-button:focus-within {
  outline: 3px solid var(--app-focus);
}

/* The two marks are drawn in CSS rather than shipped as an icon set, because
   this repository has none and two glyphs is not a reason to start one. */
.bars,
.bars::before,
.bars::after {
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}
.bars::before,
.bars::after {
  content: "";
  position: relative;
  top: -6px;
}
.bars::after {
  top: 4px;
}
.topbar-avatar {
  background: var(--app-accent);
  color: var(--app-white);
  font-weight: 700;
  text-transform: uppercase;
}

/* Panels ------------------------------------------------------------------ */

.shell-scrim {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 25;
  background: rgba(24, 50, 44, 0.35);
}

.sidenav {
  position: fixed;
  top: 56px;
  bottom: 0;
  left: 0;
  z-index: 26;
  width: min(17rem, 80vw);
  padding: 1rem 0.75rem;
  overflow-y: auto;
  background: var(--app-card);
  border-right: 1px solid var(--app-rule);
  transform: translateX(-100%);
  transition: transform 0.15s ease-out;
}
#nav-toggle:checked ~ .sidenav {
  transform: none;
}
.sidenav-link {
  display: block;
  padding: 0.7rem 0.9rem;
  border-radius: 0.5rem;
  color: var(--app-ink);
  text-decoration: none;
  font-weight: 600;
}
.sidenav-link:hover {
  background: var(--app-tint);
}
.sidenav-link.is-current {
  background: var(--app-tint-strong);
  color: var(--app-tint-ink);
}
.sidenav-heading {
  margin: 1rem 0 0.25rem;
  padding: 0 0.9rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--app-muted);
}
.profilemenu-who {
  padding: 0.25rem 0.6rem 0.75rem;
  border-bottom: 1px solid var(--app-rule-faint);
  margin-bottom: 0.4rem;
  word-break: break-word;
}
.profilemenu-who span {
  display: block;
  font-size: 0.85rem;
  color: var(--app-muted);
}
.profilemenu-link {
  display: block;
  width: 100%;
  padding: 0.6rem;
  border: 0;
  border-radius: 0.5rem;
  background: none;
  color: var(--app-link);
  font: inherit;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.profilemenu-link:hover {
  background: var(--app-tint);
}
.profilemenu-signout {
  color: var(--app-muted);
}

/* On a wide screen the side nav is furniture rather than a panel: always
   there, never over the content, and the hamburger that opens it disappears
   because there is nothing left for it to do. */
@media (min-width: 60rem) {
  .nav-button {
    display: none;
  }
  .sidenav {
    transform: none;
    transition: none;
  }
  /* Margin rather than padding: Bulma's px-4 utility sets padding with
     !important, so a padding rule here loses to a class on the element. */
  body:has(.sidenav) main {
    margin-left: min(17rem, 80vw);
  }
  #nav-toggle:checked ~ .shell-scrim:not(.apps-scrim):not(.profile-scrim) {
    display: none;
  }
}

/* A definition list used as a record of facts: label above value on a phone,
   two columns once there is room. */
.detail-list > div {
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--app-rule-faint);
}
.detail-list dt {
  font-size: 0.85rem;
  color: var(--app-muted);
}
.detail-list dd {
  margin: 0;
  font-weight: 600;
}
@media (min-width: 30rem) {
  .detail-list > div {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
  }
  .detail-list dt {
    font-size: 1rem;
  }
}

.saved-note {
  background: var(--app-tint-strong);
  color: var(--app-tint-ink);
  font-weight: 600;
}

/* The caregiver home.

   The page was a phone-width column beside a side nav that is furniture on a
   wide screen, so half the window was empty. This one is left-aligned against
   the nav rather than centred in what is left: a column that drifts right as
   the window widens is harder to read than one that stays put. */
.caregiver-main {
  max-width: 62rem;
  margin-left: 0;
}

/* What needs a parent now. Rows in one list, never cards: a permanent
   card-sized panel for a thing that is usually empty teaches a parent to stop
   reading it. */
.needs-you-item {
  display: block;
  margin-bottom: 0.5rem;
  padding: 0.75rem 1rem;
  border-left: 0.3rem solid var(--app-accent);
  border-radius: 0.25rem;
  background: var(--app-tint);
  color: var(--app-ink);
  text-decoration: none;
}
.needs-you-item:hover {
  background: var(--app-tint-strong);
}
/* Asking for help is not one of several equal things, so it does not look
   like one. It is the only place on this page with this weight. */
.needs-you-item.is-help {
  border-left-color: var(--app-notice-ink);
  background: var(--app-notice);
  color: var(--app-notice-deep);
}
.needs-you-item.is-help:hover {
  background: var(--app-notice-soft);
}
.needs-you-item.is-help .needs-you-why {
  color: var(--app-notice-ink);
}
.needs-you-what {
  display: block;
  font-weight: 600;
}
.needs-you-why {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.9rem;
  color: var(--app-muted);
}
.needs-you-quiet {
  color: var(--app-muted);
}

/* One card per child, and every action belonging to a child is inside it.
   The page used to emit two full-width buttons per child, so it grew with the
   family it was describing. */
.child-grid {
  display: grid;
  gap: var(--app-body-size);
}
.child-card {
  min-width: 0;
  margin-bottom: 0;
}
.child-card-meta {
  margin-bottom: 0.75rem;
  color: var(--app-muted);
  font-size: 0.9rem;
}
.child-card-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.9rem;
  margin-bottom: 0.9rem;
}
.child-card-links li {
  min-width: 0;
}
.child-card-settings {
  display: inline-block;
  padding-top: 0.6rem;
  border-top: 1px solid var(--app-rule-faint);
  width: 100%;
  font-size: 0.9rem;
}

@media (min-width: 48rem) {
  .child-grid {
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  }
}

/* The chore step screen's quiet control.

   The screen asks for one decision: Done, or I need help. Try again, Skip
   and Pause are real things a child needs -- take them away and a child who
   cannot do a step learns to tap Done on things they did not do -- but as
   three more buttons of equal weight they turned the one-decision screen into
   a five-decision one.

   A checkbox and a label, not a script. This is the screen a child reaches
   when a chore is going wrong, and it must work with scripting off. */
.step-more {
  display: none;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--app-rule-faint);
}
.step-more-toggle:checked ~ .step-more {
  display: block;
}
.step-more-open {
  display: inline-block;
  margin-top: 1rem;
  padding: 0.4rem 0;
  color: var(--app-muted);
  font-size: 0.95rem;
  text-decoration: underline;
  cursor: pointer;
}
.step-more-open:hover {
  color: var(--app-ink);
}
.step-more-toggle:focus-visible ~ .step-more-open {
  outline: 3px solid var(--app-focus);
  outline-offset: 2px;
}
.step-more-toggle:checked ~ .step-more-open {
  color: var(--app-ink);
}

/* A child answering a message a caregiver sent them. Fixed taps, so they wrap
   as a row of small buttons rather than a stack of full-width ones: this is a
   reply, not the point of the screen. */
.ack-replies {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.ack-reply-button {
  font-size: 0.9rem;
}
.ack-reply {
  margin-top: 0.75rem;
  color: var(--app-muted);
  font-size: 0.95rem;
}

/* Widening the page is not the same as widening everything on it.

   The caregiver pages now use the room beside the side nav instead of sitting
   in a phone-width column, but a single-line text field stretched to sixty
   rems is harder to scan than one at a readable measure, and a label floating
   at the far left of a very wide control is harder still to associate with
   it. So the page is wide and the controls are not. */
@media (min-width: 60rem) {
  .caregiver-main .field .input,
  .caregiver-main .field .select,
  .caregiver-main .field .select select,
  .caregiver-main .field .textarea {
    max-width: 34rem;
    /* Bulma gives a textarea min-width: 100%, and min-width beats max-width,
       so without this the box ignores the measure and fills the page. */
    min-width: 0;
  }
  /* A button that says "Save setting" does not become clearer at sixty rems
     either. Full-width is a phone idiom; beside a side nav it means the
     content's measure, not the window's. */
  .caregiver-main .button.is-fullwidth {
    max-width: 34rem;
  }
}
