/* ============================================================
   FREED - school tuition ledger

   Built for one person who has run a school on Excel for years and does not
   want a computer to be clever at her. Everything is large, still, and says
   what it is in words. Colour never carries meaning on its own.
   ============================================================ */

@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('/fonts/figtree.woff2') format('woff2');
}

@font-face {
  font-family: 'Figtree Fallback';
  src: local('Arial'), local('Helvetica'), local('Liberation Sans');
  size-adjust: 97%;
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 0%;
}

/* ============================================================
   TOKENS
   ============================================================ */
:root {
  --paper:         #F6F7F9;
  --surface:       #FFFFFF;
  --surface-sunk:  #F2F4F7;
  --surface-hover: #F9FAFB;

  --ink:      #0F1521;
  --ink-2:    #3D4654;
  --graphite: #5A6472;
  --muted:    #8B94A3;

  --rule:        #E4E7EC;
  --rule-strong: #D0D5DD;

  --accent:       #0B5FFF;
  --accent-press: #0A4FD6;
  --accent-tint:  #EBF1FF;

  /* Money states. Each one always ships with a word, never just the colour. */
  --owed:      #B42318;
  --owed-tint: #FDECEA;
  --ok:        #067647;
  --ok-tint:   #E7F6EF;
  --part:      #B54708;
  --part-tint: #FEF3E7;

  --r-pill: 999px;
  --r-xl:   20px;
  --r-lg:   16px;
  --r-md:   12px;
  --r-sm:   8px;

  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
  --shadow-md: 0 2px 4px -1px rgba(16, 24, 40, 0.05), 0 8px 16px -4px rgba(16, 24, 40, 0.08);
  --shadow-lg: 0 1px 2px rgba(16, 24, 40, 0.05), 0 4px 12px -2px rgba(16, 24, 40, 0.07),
               0 24px 48px -12px rgba(16, 24, 40, 0.18);

  --font: 'Figtree', 'Figtree Fallback', sans-serif;

  /* Everything is a step larger than a normal app. 18px body, not 16. */
  --fs-display: clamp(1.75rem, 5vw, 2.5rem);
  --fs-h1:   1.5rem;
  --fs-h2:   1.25rem;
  --fs-body: 1.125rem;
  --fs-sm:   1rem;
  --fs-xs:   0.875rem;
  --fs-money:      1.375rem;
  --fs-money-big:  clamp(2.25rem, 8vw, 3rem);

  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;

  --shell: 920px;
  --tap: 56px;          /* every control clears this */
  --row: 76px;          /* list rows are deliberately tall */
  --topbar-h: 68px;     /* the desktop nav sticks directly below this */

  --t-fast: 0.13s cubic-bezier(0.4, 0, 0.2, 1);
  --t-med:  0.22s cubic-bezier(0.4, 0, 0.2, 1);
  --t-slow: 0.36s cubic-bezier(0.16, 1, 0.3, 1);
}

/* ============================================================
   RESET
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

html {
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
  /* Keep the scrollbar's space reserved at all times. Opening a sheet locks
     body scrolling, and without this the scrollbar vanishes, the page widens
     by its width, and everything slides sideways as the dialog opens. */
  scrollbar-gutter: stable;
}

body {
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
  overscroll-behavior-y: none;
}

svg { display: block; flex-shrink: 0; }
a { color: inherit; text-decoration: none; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
  font-variant-numeric: inherit;
}
button { cursor: pointer; background: none; border: none; text-align: inherit; }

button, a, .row, .tab, .chip { -webkit-tap-highlight-color: transparent; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

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

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--s-5);
}
@media (max-width: 480px) {
  .shell { padding-inline: var(--s-4); }
}

/* ============================================================
   APP FRAME
   ============================================================ */
#app { min-height: 100dvh; display: flex; flex-direction: column; }

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  padding-top: env(safe-area-inset-top);
}

.topbar-inner {
  min-height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: var(--tap);
  font-size: var(--fs-h1);
  font-weight: 800;
  letter-spacing: -0.03em;
}
.brand-dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.brand-sub {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--graphite);
  letter-spacing: 0;
}

.topbar-spacer { flex: 1; }

/* Back button replaces the brand on detail screens */
.back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding-inline: var(--s-3);
  margin-left: calc(var(--s-3) * -1);
  border-radius: var(--r-md);
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--ink);
  transition: background var(--t-fast), transform var(--t-fast);
}
.back:hover { background: var(--surface-sunk); }
.back:active { transform: scale(0.97); }

main {
  flex: 1 0 auto;
  padding-block: var(--s-5) calc(var(--s-8) + var(--s-8));
}

/* ============================================================
   BOTTOM TABS
   On a wide screen they move to the top bar, because a laptop user should
   not have to travel to the bottom of a big screen for navigation.
   ============================================================ */
.tabbar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 40;
  background: var(--surface);
  border-top: 1px solid var(--rule);
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -1px 3px rgba(16, 24, 40, 0.04);
}
.tabbar-inner {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  max-width: var(--shell);
  margin-inline: auto;
}

.tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 66px;
  padding: var(--s-2) var(--s-1);
  color: var(--graphite);
  font-size: var(--fs-xs);
  font-weight: 700;
  position: relative;
  transition: color var(--t-fast);
}
.tab svg { transition: transform var(--t-med); }
.tab[aria-current='page'] { color: var(--accent); }
.tab[aria-current='page'] svg { transform: translateY(-1px) scale(1.06); }
.tab:active svg { transform: scale(0.9); }

.tab::after {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: 0;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--accent);
  transform: translateX(-50%);
  transition: width var(--t-med);
}
.tab[aria-current='page']::after { width: 32px; }

@media (min-width: 820px) {
  /* Sticky directly under the brand bar rather than reordered above it.
     Left in normal flow it scrolls away, which leaves a wide screen with no
     visible navigation as soon as the page moves at all. */
  .tabbar {
    position: sticky;
    top: var(--topbar-h);
    bottom: auto;
    z-index: 29;
    background: var(--paper);
    border-top: none;
    box-shadow: none;
  }
  .tabbar-inner {
    grid-auto-columns: auto;
    justify-content: start;
    gap: var(--s-1);
    padding-inline: var(--s-5);
    border-bottom: 1px solid var(--rule);
  }
  .tab {
    flex-direction: row;
    gap: var(--s-2);
    min-height: 52px;
    padding-inline: var(--s-4);
    font-size: var(--fs-sm);
  }
  .tab::after { top: auto; bottom: -1px; }
  /* Still enough room that the floating button never covers the last row. */
  main { padding-bottom: calc(var(--s-8) + var(--s-7)); }
}

/* ============================================================
   SCREENS
   ============================================================ */
.screen { animation: screen-in var(--t-slow) both; }
@keyframes screen-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.screen-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
  flex-wrap: wrap;
}
.screen-title {
  font-size: var(--fs-h1);
  font-weight: 800;
  letter-spacing: -0.025em;
}
.screen-note { font-size: var(--fs-sm); color: var(--graphite); }

/* ============================================================
   THE HEADLINE NUMBER
   The one thing she wants when the page opens.
   ============================================================ */
.headline {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-xl);
  padding: var(--s-6) var(--s-5);
  text-align: center;
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--s-5);
}
.headline-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--graphite);
  letter-spacing: 0.01em;
}
.headline-value {
  margin-top: var(--s-2);
  font-size: var(--fs-money-big);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--owed);
  transition: color var(--t-med);
}
.headline-value.is-clear { color: var(--ok); }
.headline-sub {
  margin-top: var(--s-3);
  font-size: var(--fs-sm);
  color: var(--graphite);
}

/* A number that changed gets one quiet pulse, so an update is felt
   without anything moving on the page. */
.flash { animation: flash 0.9s ease; }
@keyframes flash {
  0%   { background: var(--accent-tint); }
  100% { background: transparent; }
}

/* ============================================================
   SEARCH
   ============================================================ */
.search {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  height: 64px;
  padding-inline: var(--s-4);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--s-4);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.search:focus-within {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
}
.search-icon { color: var(--muted); transition: color var(--t-fast); }
.search:focus-within .search-icon { color: var(--accent); }
.search input {
  flex: 1;
  min-width: 0;
  /* Stretch to the full bar height: the box looks 64px tall, so the thing that
     actually takes the tap has to be 64px tall too. */
  align-self: stretch;
  border: none;
  background: none;
  font-size: var(--fs-body);
  font-weight: 500;
}
.search input::-webkit-search-cancel-button { display: none; }
.search input:focus { outline: none; }
.search input::placeholder { color: var(--graphite); font-weight: 400; }

.search-clear {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  color: var(--graphite);
  transition: background var(--t-fast), color var(--t-fast);
}
.search-clear:hover { background: var(--surface-sunk); color: var(--ink); }

/* ============================================================
   LISTS
   ============================================================ */
.list {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.list + .list { margin-top: var(--s-5); }

.list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  background: var(--surface-sunk);
  border-bottom: 1px solid var(--rule);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  width: 100%;
  min-height: var(--row);
  padding: var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
  transition: background var(--t-fast), transform var(--t-fast);
}
.row:last-child { border-bottom: none; }
.row:hover { background: var(--surface-hover); }
.row:active { transform: scale(0.995); background: var(--surface-sunk); }
.row.is-static:active { transform: none; }

.row-main { flex: 1; min-width: 0; }
.row-title {
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-sub {
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--graphite);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-end {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-shrink: 0;
}
.row-chevron { color: var(--rule-strong); transition: transform var(--t-fast), color var(--t-fast); }
.row:hover .row-chevron { color: var(--graphite); transform: translateX(2px); }

/* Money in a row */
.amount {
  font-size: var(--fs-money);
  font-weight: 800;
  letter-spacing: -0.03em;
  white-space: nowrap;
}
.amount.owed  { color: var(--owed); }
.amount.clear { color: var(--ok); }
.amount.zero  { color: var(--muted); }

/* Rows stagger in so a long list resolves rather than snapping */
.list .row { animation: row-in var(--t-med) both; }
.list .row:nth-child(1) { animation-delay: 0.00s; }
.list .row:nth-child(2) { animation-delay: 0.02s; }
.list .row:nth-child(3) { animation-delay: 0.04s; }
.list .row:nth-child(4) { animation-delay: 0.06s; }
.list .row:nth-child(5) { animation-delay: 0.08s; }
.list .row:nth-child(6) { animation-delay: 0.10s; }
.list .row:nth-child(7) { animation-delay: 0.12s; }
.list .row:nth-child(n+8) { animation-delay: 0.14s; }
@keyframes row-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* A month heading inside a list, above the rows it covers */
.group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-5);
  background: var(--surface-sunk);
  border-bottom: 1px solid var(--rule);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: -0.01em;
}
.group-head-end { display: flex; align-items: center; gap: var(--s-3); }

/* Rows nested under a group heading sit in a little from the edge */
.row-sub-item { padding-left: var(--s-6); }

/* Leading icon inside an action row */
.row-icon {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  background: var(--surface-sunk);
  color: var(--ink-2);
}
.row-icon.is-danger { background: var(--owed-tint); color: var(--owed); }

/* ============================================================
   STATUS PILLS - always a word, never colour alone
   ============================================================ */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 6px var(--s-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.pill.paid      { background: var(--ok-tint);   color: var(--ok); }
.pill.part      { background: var(--part-tint); color: var(--part); }
.pill.unpaid    { background: var(--owed-tint); color: var(--owed); }
.pill.cancelled { background: var(--surface-sunk); color: var(--graphite); }
.pill.neutral   { background: var(--accent-tint); color: var(--accent-press); }

/* ============================================================
   BUTTONS
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding-inline: var(--s-5);
  border-radius: var(--r-pill);
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: -0.01em;
  transition: background var(--t-fast), border-color var(--t-fast),
              transform var(--t-fast), opacity var(--t-fast);
}
.btn:active { transform: scale(0.975); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }

.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--accent-press); }

.btn-dark { background: var(--ink); color: #fff; }
.btn-dark:hover:not(:disabled) { background: #212936; }

.btn-quiet {
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  color: var(--ink);
}
.btn-quiet:hover:not(:disabled) { background: var(--surface-sunk); }

.btn-danger { background: var(--owed-tint); color: var(--owed); }
.btn-danger:hover:not(:disabled) { background: #FBDCD8; }

.btn-ghost { color: var(--accent); }
.btn-ghost:hover:not(:disabled) { background: var(--accent-tint); }

.btn-block { width: 100%; }
.btn-lg { min-height: 64px; font-size: var(--fs-h2); }

.btn-row {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.btn-row .btn { flex: 1; min-width: 150px; }

/* The one action she does all day. Carries its own layout rather than
   extending .btn, so it needs the centring spelled out. */
.btn-pay {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  width: 100%;
  min-height: 68px;
  padding-inline: var(--s-5);
  font-size: var(--fs-h2);
  font-weight: 800;
  letter-spacing: -0.02em;
  background: var(--ok);
  color: #fff;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  transition: background var(--t-fast), transform var(--t-fast);
}
.btn-pay:hover:not(:disabled) { background: #05613A; }
.btn-pay:active { transform: scale(0.985); }

/* ============================================================
   CARDS
   ============================================================ */
.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  padding: var(--s-5);
  box-shadow: var(--shadow-sm);
}
.card + .card { margin-top: var(--s-4); }
.card-title {
  font-size: var(--fs-h2);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin-bottom: var(--s-4);
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--s-3);
}
.stat {
  padding: var(--s-4);
  background: var(--surface-sunk);
  border-radius: var(--r-md);
  text-align: center;
}
.stat-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--graphite);
}
.stat-value {
  margin-top: var(--s-1);
  font-size: var(--fs-money);
  font-weight: 800;
  letter-spacing: -0.03em;
}
.stat-value.owed  { color: var(--owed); }
.stat-value.clear { color: var(--ok); }

/* ============================================================
   MONTH STRIP
   ============================================================ */
.strip-wrap {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-4);
}
.strip-nav {
  display: grid;
  place-items: center;
  width: var(--tap); height: var(--tap);
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--ink-2);
  transition: background var(--t-fast), transform var(--t-fast);
}
.strip-nav:hover { background: var(--surface-sunk); }
.strip-nav:active { transform: scale(0.92); }

.strip {
  flex: 1;
  min-width: 0;
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding: var(--s-1);
  scrollbar-width: none;
}
.strip::-webkit-scrollbar { display: none; }

.chip {
  flex-shrink: 0;
  scroll-snap-align: center;
  min-width: 72px;
  min-height: var(--tap);
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
  text-align: center;
  transition: background var(--t-fast), color var(--t-fast),
              border-color var(--t-fast), transform var(--t-fast);
}
.chip:hover { border-color: var(--rule-strong); }
.chip:active { transform: scale(0.95); }
.chip[aria-selected='true'] {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.chip.is-future { color: var(--muted); border-style: dashed; }
.chip[aria-selected='true'].is-future { background: var(--graphite); border-color: var(--graphite); color: #fff; }
.chip-year { display: block; font-size: 0.75rem; font-weight: 600; opacity: 0.7; }

.strip-label {
  text-align: center;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--graphite);
  margin-bottom: var(--s-3);
}

/* Hold the page height steady while flicking between months. A quiet month
   has far less in it than a busy one, and without a floor the page shrinks,
   the browser clamps the scroll position to the new maximum, and the whole
   thing lurches upward - which reads as the page jumping. */
#monthHost { min-height: 62vh; }

/* ============================================================
   FORMS
   ============================================================ */
.field { margin-bottom: var(--s-4); }
.field-label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
  margin-bottom: var(--s-2);
}
.field-hint {
  margin-top: var(--s-2);
  font-size: var(--fs-xs);
  color: var(--graphite);
}

.input,
.textarea {
  width: 100%;
  min-height: 60px;
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 2px solid var(--rule);
  border-radius: var(--r-md);
  font-size: var(--fs-body);
  font-weight: 500;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.textarea { min-height: 96px; resize: vertical; line-height: 1.5; }
.input:focus,
.textarea:focus { outline: none; border-color: var(--accent); }
.input::placeholder,
.textarea::placeholder { color: var(--muted); font-weight: 400; }
.input.is-bad, .textarea.is-bad { border-color: var(--owed); }

/* The amount field is the most important input in the app */
.input-money {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding-inline: var(--s-4);
  background: var(--surface);
  border: 2px solid var(--rule);
  border-radius: var(--r-md);
  transition: border-color var(--t-fast);
}
.input-money:focus-within { border-color: var(--accent); }
.input-money-symbol {
  font-size: var(--fs-h1);
  font-weight: 800;
  color: var(--graphite);
}
.input-money input {
  flex: 1;
  min-width: 0;
  min-height: 68px;
  border: none;
  background: none;
  font-size: var(--fs-money-big);
  font-weight: 800;
  letter-spacing: -0.03em;
}
.input-money input:focus { outline: none; }
.input-money input::placeholder { color: var(--rule-strong); }

.field-error {
  margin-top: var(--s-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--owed);
}

/* ------------------------------------------------------------
   CUSTOM SELECT
   A native <select> on a phone throws up an OS wheel she cannot read.
   This is a plain listbox with rows the same size as everything else.
   ------------------------------------------------------------ */
.select {
  position: relative;
  width: 100%;
}
.select-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  min-height: 60px;
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 2px solid var(--rule);
  border-radius: var(--r-md);
  font-size: var(--fs-body);
  font-weight: 600;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.select-button:hover { background: var(--surface-hover); }
.select-button[aria-expanded='true'] { border-color: var(--accent); }
.select-button.is-placeholder { color: var(--muted); font-weight: 500; }
.select-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}
.select-caret { color: var(--graphite); transition: transform var(--t-med); }
.select-button[aria-expanded='true'] .select-caret { transform: rotate(180deg); }

.select-list {
  max-height: 320px;
  overflow-y: auto;
  padding: var(--s-2);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  animation: pop var(--t-med) both;
}

/* Popovers render into #layers and are positioned in JS. Fixed, not absolute:
   inside a sheet an absolute popup is clipped by the scrolling body, which is
   how a calendar ends up with its last week cut off. z-index sits above the
   sheet (100) so it is never trapped behind the thing that opened it. */
.popover {
  position: fixed;
  z-index: 110;
  overflow-y: auto;
}

@keyframes pop {
  from { opacity: 0; transform: translateY(-6px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

.select-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  font-size: var(--fs-body);
  font-weight: 600;
  transition: background var(--t-fast);
}
.select-option:hover,
.select-option.is-active { background: var(--surface-sunk); }
.select-option[aria-selected='true'] { color: var(--accent); }
.select-option-check { opacity: 0; transition: opacity var(--t-fast); }
.select-option[aria-selected='true'] .select-option-check { opacity: 1; }
.select-option-sub {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--graphite);
}

/* ------------------------------------------------------------
   CALENDAR
   ------------------------------------------------------------ */
.cal { padding: var(--s-3); width: 320px; max-width: calc(100vw - 48px); }

.cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.cal-title { font-size: var(--fs-body); font-weight: 800; letter-spacing: -0.02em; }
.cal-nav {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: var(--r-md);
  color: var(--ink-2);
  transition: background var(--t-fast), transform var(--t-fast);
}
.cal-nav:hover { background: var(--surface-sunk); }
.cal-nav:active { transform: scale(0.9); }

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.cal-weekday {
  display: grid;
  place-items: center;
  height: 32px;
  font-size: var(--fs-xs);
  font-weight: 800;
  color: var(--muted);
}
.cal-day {
  display: grid;
  place-items: center;
  height: 44px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  transition: background var(--t-fast), color var(--t-fast), transform var(--t-fast);
}
.cal-day:hover { background: var(--surface-sunk); }
.cal-day:active { transform: scale(0.9); }
/* Today is outlined; the chosen day is filled. Both can be true at once. */
.cal-day[data-today] { box-shadow: inset 0 0 0 2px var(--rule-strong); font-weight: 800; }
.cal-day[aria-pressed='true'] {
  background: var(--accent);
  color: #fff;
  font-weight: 800;
  box-shadow: none;
}
.cal-today { width: 100%; margin-top: var(--s-2); }

/* Repeating phone rows: who it is, the number, and a way to take it away */
.contact-rows { display: flex; flex-direction: column; gap: var(--s-2); }
.contact-row {
  display: grid;
  grid-template-columns: 130px 1fr auto;
  gap: var(--s-2);
  align-items: center;
}
.contact-remove {
  display: grid;
  place-items: center;
  width: 52px; height: 52px;
  border-radius: var(--r-md);
  color: var(--graphite);
  transition: background var(--t-fast), color var(--t-fast);
}
.contact-remove:hover { background: var(--owed-tint); color: var(--owed); }
@media (max-width: 520px) {
  .contact-row { grid-template-columns: 1fr auto; grid-template-areas: 'who who' 'phone remove'; }
  .contact-row > *:nth-child(1) { grid-area: who; }
  .contact-row > *:nth-child(2) { grid-area: phone; }
  .contact-row > *:nth-child(3) { grid-area: remove; }
}

/* Segmented control, for two or three short choices */
.segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--s-1);
  padding: var(--s-1);
  background: var(--surface-sunk);
  border-radius: var(--r-md);
}
.segmented button {
  min-height: 52px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
  transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
}
.segmented button[aria-pressed='true'] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}

/* Twelve months as a grid of toggles, for picking the ones with no charge */
.month-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-2);
}
.month-toggle {
  min-height: 52px;
  border-radius: var(--r-md);
  border: 2px solid var(--rule);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
  transition: background var(--t-fast), color var(--t-fast),
              border-color var(--t-fast), transform var(--t-fast);
}
.month-toggle:hover { border-color: var(--rule-strong); }
.month-toggle:active { transform: scale(0.95); }
.month-toggle[aria-pressed='true'] {
  background: var(--part-tint);
  border-color: var(--part);
  color: var(--part);
}

/* ============================================================
   SHEET - every dialog in the app.
   Slides up on a phone, centres on a laptop. Replaces alert/confirm/prompt.
   ============================================================ */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(15, 21, 33, 0.45);
  animation: fade var(--t-med) both;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.scrim.is-closing { animation: fade var(--t-fast) reverse both; }

.sheet {
  position: fixed;
  z-index: 100;
  inset-inline: 0;
  bottom: 0;
  max-height: 92dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--shadow-lg);
  animation: sheet-up var(--t-slow) both;
}
@keyframes sheet-up {
  from { transform: translateY(100%); }
  to   { transform: none; }
}
.sheet.is-closing { animation: sheet-up var(--t-med) reverse both; }

.sheet-grip {
  width: 44px; height: 5px;
  margin: var(--s-3) auto 0;
  border-radius: var(--r-pill);
  background: var(--rule-strong);
  flex-shrink: 0;
}

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5) var(--s-3);
  flex-shrink: 0;
}
.sheet-title {
  font-size: var(--fs-h1);
  font-weight: 800;
  letter-spacing: -0.025em;
}
.sheet-close {
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  flex-shrink: 0;
  border-radius: 50%;
  color: var(--graphite);
  transition: background var(--t-fast), color var(--t-fast);
}
.sheet-close:hover { background: var(--surface-sunk); color: var(--ink); }

.sheet-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 var(--s-5) var(--s-4);
}
.sheet-text { color: var(--ink-2); line-height: 1.55; }
.sheet-text strong { color: var(--ink); font-weight: 800; }

.sheet-foot {
  display: flex;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  padding-bottom: calc(var(--s-4) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--rule);
  background: var(--surface);
  flex-shrink: 0;
}
.sheet-foot .btn { flex: 1; }

@media (min-width: 700px) {
  .sheet {
    inset: 50% auto auto 50%;
    transform: translate(-50%, -50%);
    width: min(560px, calc(100vw - 48px));
    max-height: min(86dvh, 820px);
    border-radius: var(--r-xl);
    animation: sheet-in var(--t-slow) both;
  }
  @keyframes sheet-in {
    from { opacity: 0; transform: translate(-50%, -46%) scale(0.97); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
  .sheet.is-closing { animation: sheet-in var(--t-fast) reverse both; }
  .sheet-grip { display: none; }
  .sheet-head { padding-top: var(--s-5); }
}

/* ============================================================
   TOAST
   ============================================================ */
.toast {
  position: fixed;
  z-index: 120;
  left: 50%;
  bottom: calc(88px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  max-width: calc(100vw - 32px);
  padding: var(--s-4) var(--s-5);
  background: var(--ink);
  color: #fff;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  font-weight: 600;
  animation: toast-in var(--t-slow) both;
}
@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 16px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.toast.is-closing { animation: toast-in var(--t-fast) reverse both; }
.toast.is-bad { background: var(--owed); }
.toast.is-good { background: var(--ok); }
.toast-undo {
  flex-shrink: 0;
  padding: var(--s-2) var(--s-3);
  margin: calc(var(--s-2) * -1) calc(var(--s-2) * -1) calc(var(--s-2) * -1) 0;
  border-radius: var(--r-sm);
  color: #fff;
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 3px;
}
@media (min-width: 820px) {
  .toast { bottom: var(--s-6); }
}

/* ============================================================
   SUCCESS TICK - shown after a payment lands
   ============================================================ */
.tick-wrap { display: grid; place-items: center; padding: var(--s-5) 0 var(--s-3); }
.tick {
  width: 88px; height: 88px;
  border-radius: 50%;
  background: var(--ok-tint);
  display: grid;
  place-items: center;
  animation: tick-pop var(--t-slow) both;
}
@keyframes tick-pop {
  0%   { transform: scale(0.6); opacity: 0; }
  60%  { transform: scale(1.06); opacity: 1; }
  100% { transform: scale(1); }
}
.tick svg { color: var(--ok); }
.tick-path {
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  animation: tick-draw 0.4s 0.15s ease forwards;
}
@keyframes tick-draw { to { stroke-dashoffset: 0; } }

/* ============================================================
   EMPTY + LOADING
   ============================================================ */
.empty {
  padding: var(--s-8) var(--s-5);
  text-align: center;
  color: var(--graphite);
}
.empty-mark {
  display: grid;
  place-items: center;
  width: 72px; height: 72px;
  margin: 0 auto var(--s-4);
  border-radius: 50%;
  background: var(--surface-sunk);
  color: var(--muted);
}
.empty-title {
  font-size: var(--fs-h2);
  font-weight: 800;
  color: var(--ink);
  margin-bottom: var(--s-2);
}
.empty-text { max-width: 42ch; margin-inline: auto; }
.empty .btn { margin-top: var(--s-5); }

.skeleton {
  background: linear-gradient(90deg, var(--surface-sunk) 25%, var(--surface-hover) 37%, var(--surface-sunk) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
  border-radius: var(--r-sm);
}
@keyframes shimmer {
  from { background-position: 100% 50%; }
  to   { background-position: 0 50%; }
}
.skeleton-row { height: var(--row); border-bottom: 1px solid var(--rule); }

.spinner {
  width: 22px; height: 22px;
  border: 3px solid rgba(255, 255, 255, 0.3);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
.spinner.dark { border-color: var(--rule-strong); border-top-color: var(--accent); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================
   LOGIN
   ============================================================ */
.login {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--s-5);
}
.login-card {
  width: 100%;
  max-width: 420px;
  padding: var(--s-7) var(--s-6);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
  text-align: center;
  animation: screen-in var(--t-slow) both;
}
.login-title {
  font-size: var(--fs-display);
  font-weight: 800;
  letter-spacing: -0.04em;
}
.login-sub {
  margin-top: var(--s-2);
  margin-bottom: var(--s-6);
  color: var(--graphite);
}
.login .input { text-align: center; letter-spacing: 0.08em; }
.login .btn { margin-top: var(--s-4); }
.login-error {
  margin-top: var(--s-4);
  padding: var(--s-3) var(--s-4);
  background: var(--owed-tint);
  color: var(--owed);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.shake { animation: shake 0.4s ease; }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-7px); }
  40%, 80% { transform: translateX(7px); }
}

/* ============================================================
   MISC
   ============================================================ */
.divider { height: 1px; background: var(--rule); margin-block: var(--s-5); }

.phone-links {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s-2);
}
.phone-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding-inline: var(--s-4);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: 700;
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
.phone-link:hover { background: var(--surface-sunk); border-color: var(--rule-strong); }
.phone-link:active { transform: scale(0.97); }

.note-block {
  padding: var(--s-4);
  background: var(--accent-tint);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  color: var(--accent-press);
  font-weight: 600;
  line-height: 1.5;
  /* These blocks are written as short lines separated by \n rather than
     assembled from elements, so the breaks have to survive. */
  white-space: pre-line;
}
.note-block.warn { background: var(--part-tint); color: var(--part); }

.inline-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  width: 100%;
  min-height: var(--tap);
  color: var(--accent);
  font-size: var(--fs-sm);
  font-weight: 700;
  border-top: 1px dashed var(--rule-strong);
  transition: background var(--t-fast);
}
.inline-add:hover { background: var(--accent-tint); }

/* The add action lives in the sticky header at every width.
   There is deliberately no floating action button: fixed to the viewport, it
   sits on top of the last row whenever the list is too short to scroll, and
   no amount of page padding fixes that because padding scrolls and it does not. */
.add-top {
  flex-shrink: 0;
  gap: var(--s-1);
  padding-inline: var(--s-4);
  font-size: var(--fs-sm);
}
/* "Add" on a narrow phone, "Add someone" once there is room for it.
   The word is its own element rather than a leading space, so the flex gap
   is the only thing setting the spacing. */
.add-label-long { display: none; }
@media (min-width: 560px) {
  .add-label-long { display: inline; }
  .add-top { padding-inline: var(--s-5); font-size: var(--fs-body); }
}
/* The school name is the first thing to give up its space when things get tight. */
@media (max-width: 420px) {
  .brand-sub { display: none; }
}

/* ============================================================
   REDUCED MOTION
   Animation here is feedback, not decoration. When it is turned off the
   feedback becomes instant rather than disappearing.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   PRINT - she will want a paper list
   ============================================================ */
@media print {
  .topbar, .tabbar, .fab, .search, .strip-wrap, .btn, .row-chevron { display: none !important; }
  body { background: #fff; font-size: 11pt; }
  main { padding: 0; }
  .list, .card, .headline { box-shadow: none; border-color: #999; break-inside: auto; }
  .row { break-inside: avoid; min-height: 0; padding-block: 6pt; }
  .screen { animation: none; }
}
