/* ============================================================
   Colores de Esperanza — parent portal
   Built by LeeGStudios.com

   Loaded after site.css on /portal/ pages only. Every token, the
   type, the pills and the sheets come from the public design
   system (docs/DESIGN.md); this file adds only what the portal has
   and the public site does not: the session states, the tab bar,
   the feed, the photo viewer, the menu week, the admin rows and
   the printed setup card.

   Rules carried over: rows separated by hairlines, not card grids;
   pills for what you press; sheets inset from the edge; 44px
   targets; nothing that relies on hover; Spanish-length labels.
   ============================================================ */

/* ---------- 1. Session states (body[data-auth]) ----------
   Presentation only. The pages hold nothing private. */

.portal-loading { display: none; padding-block: var(--space-2xl); color: var(--ink-soft); }
body[data-auth="pending"] .portal-loading { display: block; }
body[data-auth="pending"] main,
body[data-auth="denied"] main,
body[data-auth="offline"] main,
body[data-auth="pending"] .portalnav,
body[data-auth="denied"] .portalnav,
body[data-auth="offline"] .portalnav { display: none; }

.portal-noscript {
  padding-block: var(--space-lg);
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--magenta-strong);
}

[hidden] { display: none !important; }

/* The public site keeps room at the bottom for its phone call bar; the
   portal has none (its footer carries the call), so the ink reaches
   the bottom of the screen. */
body.portal { padding-bottom: 0; }

/* Browser surfaces in the house palette: the marigold ribbon for a
   selection, wordmark green for the caret and every native control. */
body.portal { accent-color: var(--green-strong); caret-color: var(--green-strong); scrollbar-color: var(--field-edge) transparent; }
body.portal ::selection { background: color-mix(in oklch, var(--yellow) 55%, transparent); color: var(--ink); }

/* ---------- 2. Masthead extras and the tab bar ---------- */

.portal-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
}
.portal-signout { min-height: 44px; }

/* Same grammar as the public nav: a grid of pills on phones, one row
   of transparent pills from 48rem. The admin group sits after a
   hairline, in the lilac wash of her "official" purple. */
.portalnav {
  border-top: 1px solid var(--paper-edge);
  border-bottom: 1px solid var(--paper-edge);
  background: var(--paper);
}
.portalnav__bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  padding-block: var(--space-sm);
}
.portalnav__list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: var(--space-xs);
  flex: 1 1 100%;
}
.portalnav__link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 var(--space-sm);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1.1;
  text-align: center;
  color: var(--ink);
  text-decoration: none;
  background: var(--paper-deep);
  border-radius: var(--radius-pill);
  transition: color var(--dur-fast) var(--ease-out-quart), background var(--dur-fast) var(--ease-out-quart);
}
.portalnav__link[aria-current="page"] { color: var(--paper); background: var(--green-strong); }
.portalnav__list--admin .portalnav__link { background: var(--wash-lilac); color: var(--purple); }

/* Phones: a parent's four tabs take one row, each as wide as its label
   plus an even share of what is left, so "Documentos" keeps its padding
   beside a short "Menú"; under pressure (large text) they shrink back to
   equal shares and wrap, never wider than the screen. Lety's six tabs sit two to a row, so "Editar
   documentos" gets the width of a Spanish label instead of wrapping
   inside a quarter of the screen; a teacher's two make one row. */
@media (max-width: 47.99rem) {
  .portalnav__list { grid-template-columns: repeat(4, minmax(0, auto)); gap: var(--space-2xs); }
  .portalnav__list--admin { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* On the Overview its own four big buttons, just below, go where these
     three tabs go; showing both would fill her first screen twice over.
     Overview then takes the row and Families and Staff share the next. */
  body[data-page="admin-overview"] .portalnav__list--admin > li:first-child { grid-column: 1 / -1; }
  body[data-page="admin-overview"] :is([data-tab="admin-post"], [data-tab="admin-menu"], [data-tab="admin-documents"]) { display: none; }
  .portalnav__link { font-size: 0.8rem; padding: 0 var(--space-xs); }
}
/* Up to 368px (small phones, zoomed text) "Documentos" needs half the row. */
@media (max-width: 23rem) {
  .portalnav__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.portalnav__list--admin .portalnav__link[aria-current="page"] { background: var(--purple); color: var(--paper); }

/* Wide: one row of transparent pills. Lety's group rides in its own
   lilac track, so when a narrow window pushes it onto a second row it
   arrives as a group, not after a divider left hanging at the start. */
@media (min-width: 48rem) {
  .portalnav__bar { padding-block: var(--space-xs); align-items: center; }
  .portalnav__list {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--space-2xs);
    flex: 0 1 auto;
  }
  .portalnav__list--admin {
    padding: 2px;
    border-radius: var(--radius-pill);
    background: var(--wash-lilac);
  }
  .portalnav__link { padding: 0 var(--space-md); background: transparent; color: var(--ink-soft); }
  .portalnav__link[aria-current="page"] { color: var(--green-strong); background: var(--wash-leaf); }
  .portalnav__list--admin .portalnav__link { background: transparent; color: var(--purple); }
  .portalnav__list--admin .portalnav__link[aria-current="page"] { color: var(--paper); background: var(--purple); }
}
@media (hover: hover) {
  .portalnav__link:not([aria-current]):hover { color: var(--ink); background: var(--paper-deep); }
  .portalnav__list--admin .portalnav__link:not([aria-current]):hover { color: var(--purple); background: var(--paper); }
}

/* ---------- 3. Page furniture ---------- */

.portal-narrow { max-width: 34rem; }
.nowrap { white-space: nowrap; }

/* Real addresses, file names and family labels can be one long word
   ("maria.guadalupe.hernandez.villalobos@gmail.com"). These may break
   anywhere rather than push a phone screen sideways. */
.portal .facts__val,
.portal .downloads__name,
[data-greeting],
.menuday__note,
.menutable td,
.docrow__names,
.guardian__who,
.family__head h3,
.dishrow__names,
.chip,
.draftrow,
[data-doc-picked],
[data-invite-email],
[data-account-email] { overflow-wrap: anywhere; }

/* Form beside a short aside on wide screens; stacked on phones. */
.portal-split { display: grid; gap: var(--space-2xl); }
@media (min-width: 54rem) {
  .portal-split { grid-template-columns: minmax(0, 34rem) minmax(0, 24rem); justify-content: space-between; align-items: start; }
}
.portal-aside {
  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  background: var(--paper-deep);
}

/* Title on the left, the page's one action on the right. */
.portal-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-md) var(--space-lg);
}

/* A sheet for a working area: a form, a result. Inset like DESIGN.md's
   tinted bands, but inside the shell. */
.panel {
  padding: clamp(var(--space-lg), 4vw, var(--space-2xl));
  border-radius: var(--radius-lg);
  background: var(--paper-deep);
}
.panel--ok { background: var(--wash-leaf); }
.panel:focus { outline: none; }

.form--wide { max-width: 42rem; }

/* A field's detail in sentence case, between its short uppercase label
   and the input: uppercase stays for labels a glance can take in. */
.field__hint {
  margin: calc(-1 * var(--space-2xs)) 0 var(--space-xs);
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--ink-soft);
}

/* Form rhythm. In a stacked form the stack spaces the fields, so the
   field's own bottom margin would double every gap. In a plain .form
   the checkbox, the rules link and the submit each get their own air. */
.stack > .field, .stack-lg > .field, .stack > .choice, .stack-lg > .choice { margin-bottom: 0; }
.form > .check { margin-bottom: var(--space-xs); }
.form > p.fine { margin-bottom: var(--space-lg); }
.form:not(.stack, .stack-lg) > .btn-row, .form:not(.stack, .stack-lg) > .btn { margin-top: var(--space-sm); }
.field input[type="date"] { max-width: 18rem; }

/* Titles that are not headings still speak in the display face. */
.portal p.h3, .portal p.h4 { font-family: var(--font-display); font-weight: 650; line-height: 1.12; }

/* Lists that are rows of content, not bullet points: site.css dots
   every ul.stack, which these must not inherit. */
.plainlist { list-style: none; display: grid; gap: var(--space-sm); }

/* A row of quiet actions: its first label lines up with the text above,
   not with the pill's invisible padding. */
.actions { margin-inline-start: calc(-1 * var(--space-sm)); gap: 0 var(--space-xs); }

/* A quiet link beside a real button keeps its 48px height but drops its
   side padding, so when a phone wraps it under the button it lines up
   with the button's edge instead of hanging 12px in. */
.btn-row:not(.actions) > .btn--quiet { padding-inline: 0; }

/* A link standing alone in its paragraph is a target in its own right:
   44px tall, like every other thing a thumb presses here. */
.portal main p > a:only-child:not(.btn) { display: inline-flex; align-items: center; min-height: 44px; }

.portal-empty {
  padding: var(--space-lg);
  border: 1px dashed var(--field-edge);
  border-radius: var(--radius-md);
  color: var(--ink-soft);
}

/* ---------- 4. Messages ---------- */

/* The block stays in the accessibility tree (it is a live region); it
   only gives up its spacing while every message in it is hidden. */
/* A message moved under its field (messages.js) is visually hidden here,
   and counts as hidden for the spacing too. */
.stack > .portal-msgs:not(:has(> .portal-msg:not([hidden], .visually-hidden))),
.stack-lg > .portal-msgs:not(:has(> .portal-msg:not([hidden], .visually-hidden))) { margin-top: 0; }
.form > .portal-msgs:has(> .portal-msg:not([hidden], .visually-hidden)) { margin-block: var(--space-md); }
.portal-msg {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step--1);
  line-height: 1.45;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  border: 2px solid currentColor;
}
.portal-msg--error { color: var(--magenta-strong); background: var(--wash-blush); }
.portal-msg--ok    { color: var(--green-strong); background: var(--wash-leaf); }
.portal-msgs .portal-msg + .portal-msg { margin-top: var(--space-xs); }

/* On the long admin editors a "Saved." at the top of the page would
   be off screen; there the message block rides along at the top. */
body[data-page="admin-menu"] main .shell > .portal-msgs,
body[data-page="admin-documents"] main .shell > .portal-msgs {
  position: sticky;
  top: var(--space-sm);
  z-index: 5;
}

/* A standing reminder: a marigold wash, never the dashed purple that
   DESIGN.md keeps for the license seal and the TODO marker. */
.notice {
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-md);
  background: var(--wash-sun);
  font-size: var(--step--1);
}
.notice strong { font-family: var(--font-display); font-weight: 700; }

/* ---------- 5. Inputs the public site does not have ---------- */

/* Checkboxes and radios: the whole row is the 44px target. */
.check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  min-height: 44px;
  padding-block: var(--space-xs);
  cursor: pointer;
  line-height: 1.45;
}
.check input {
  flex: none;
  width: 1.35rem;
  height: 1.35rem;
  margin: 0.1rem 0 0;
  accent-color: var(--green-strong);
}

.choice { border: 0; margin-inline: 0; padding: 0; min-inline-size: 0; }
.choice legend { padding: 0; margin-bottom: var(--space-2xs); }
legend.h4 { font-family: var(--font-display); font-weight: 650; }
.choice .check { display: inline-flex; margin-right: var(--space-lg); }

.field select {
  width: 100%;
  min-height: 48px;
  font-family: var(--font-body);
  font-size: var(--step-0);
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--field-edge);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
}

/* A file input dressed as a button; the input covers the label so
   the whole pill opens the picker, by touch or keyboard. */
.filebtn { position: relative; overflow: hidden; }
.filebtn input[type="file"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  font-size: 0;
}
.filebtn:focus-within { outline: 3px solid var(--magenta-strong); outline-offset: 3px; }

/* The field a message is about (messages.js showMsg): the same magenta as
   the message, on the select and the checkbox too. */
.field[data-invalid="true"] select { border-color: var(--magenta-strong); }
.check[data-invalid="true"] { color: var(--magenta-strong); }
.check[data-invalid="true"] input { outline: 2px solid var(--magenta-strong); outline-offset: 2px; }

/* A Spanish label that has to wrap stays centred in its pill. */
.portal .btn { text-align: center; }
@media (max-width: 47.99rem) {
  .portal-aside .btn { width: 100%; padding-inline: var(--space-md); }
}

.btn[disabled], .btn[aria-busy="true"] { opacity: 0.6; cursor: progress; transform: none; box-shadow: none; }
.btn--danger { color: var(--magenta-strong); }
.btn[data-armed="true"] {
  color: var(--paper);
  background: var(--magenta-strong);
  text-decoration: none;
  padding-inline: var(--space-md);
}

/* ---------- 6. Pills (tags, not buttons: no shadow, no lift) ---------- */

.pill {
  display: inline-flex;
  align-items: center;
  min-height: 1.6rem;
  padding: 0 var(--space-sm);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--ink-soft);
  background: var(--paper-deep);
  border-radius: var(--radius-pill);
  white-space: nowrap;
  vertical-align: middle;
}
.pill--urgent { color: var(--paper); background: var(--magenta-strong); }
.pill--today  { color: var(--paper); background: var(--green-strong); }
.pill--ok     { color: var(--green-strong); background: var(--wash-leaf); }
.pill--wait   { color: var(--sky-text); background: var(--wash-sky); }
.pill--off    { color: var(--ink-soft); background: var(--paper); box-shadow: inset 0 0 0 1px var(--field-edge); }

/* ---------- 7. Updates feed ---------- */

.feed { border-top: 1px solid var(--paper-edge); }
.post {
  padding-block: var(--space-xl);
  border-bottom: 1px solid var(--paper-edge);
  display: grid;
  gap: var(--space-md);
}
.post__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
}
.post__head { max-width: var(--measure); }
.post__date {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-right: auto;
}
/* A teacher's first name before the date (PORTAL.md Q43); Lety's posts have none. */
.post__author {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step--1);
  color: var(--ink-soft);
}
.post__author::after { content: " ·"; }
.post__body {
  max-width: var(--measure);
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.post__body:empty { display: none; }
/* Photos share the text's right edge, and the grid follows the count
   (updates.js sets data-count): one photo in its own shape, two or four
   in pairs, three and more in threes. */
.post__photos {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-xs);
  max-width: var(--measure);
}
.post__photos:empty { display: none; }
.post__photos[data-count="2"],
.post__photos[data-count="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.post__photos[data-count="1"] { grid-template-columns: minmax(0, 1fr); max-width: min(var(--measure), 30rem); }
.post__photos[data-count="1"] .post__photo { aspect-ratio: var(--ratio, 4 / 3); }
@media (min-width: 40rem) { .post__photos { gap: var(--space-sm); } }
.post__photo {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--paper-deep);
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 1 / 1;
  cursor: zoom-in;
}
.post__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  -webkit-touch-callout: none;
  user-select: none;
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-out-quart);
}
/* Loading: the tile breathes between deep paper and the rule while its
   thumbnail is fetched, then the photo fades in over it. */
.post__photo img[data-loaded] { opacity: 1; }
.post__photo:has(img:not([data-loaded], [data-failed])) { animation: tile-wait 1.2s ease-in-out infinite alternate; }
@keyframes tile-wait { to { background-color: var(--paper-edge); } }
.post__admin { margin-top: var(--space-xs); }

/* Pinned urgent posts: a blush sheet with the magenta rule, above the feed. */
.pinned .post {
  padding: var(--space-lg);
  border: 2px solid var(--magenta-strong);
  border-radius: var(--radius-lg);
  background: var(--wash-blush);
}
.pinned__title { color: var(--magenta-strong); }

/* ---------- 8. Photo viewer ---------- */

.viewer {
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  border: 0;
  background: var(--ink);
  color: var(--paper);
}
.viewer[open] { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
.viewer::backdrop { background: oklch(24% 0.01 67 / 0.9); }
.viewer__bar, .viewer__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
}
.viewer__count { font-family: var(--font-display); font-weight: 600; font-variant-numeric: tabular-nums; }
/* The one cell is sized by the stage, not by the photo in it, so a
   1200px photo is contained instead of pushing under the buttons. */
.viewer__stage {
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  min-height: 0;
  padding: 0 var(--space-xs);
  touch-action: pan-y pinch-zoom;
}
.viewer__stage img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-sm);
  -webkit-touch-callout: none;
  user-select: none;
}
.viewer__btn {
  min-height: 48px;
  min-width: 6.5rem;
  padding: 0 var(--space-lg);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1rem;
  color: var(--ink);
  background: var(--gold);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
}
/* Disabled on the ink: an outline that has stepped back, not a dimmed
   marigold that reads as broken. */
.viewer__btn[disabled] { color: oklch(72% 0.012 75); background: transparent; box-shadow: inset 0 0 0 2px oklch(40% 0.012 70); cursor: not-allowed; }
.viewer :focus-visible { outline-color: var(--gold); }

/* ---------- 9. Menu week ---------- */

.weeknav { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.weeknav__btn {
  min-height: 44px;
  padding: 0 var(--space-md);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper-deep);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out-quart), color var(--dur-fast) var(--ease-out-quart);
}
.weeknav__btn--now { color: var(--green-strong); background: var(--wash-leaf); }
.weeknav__btn[disabled] { opacity: 0.6; cursor: progress; }
a.weeknav__btn { display: inline-flex; align-items: center; gap: 0.3em; text-decoration: none; }
@media (hover: hover) {
  .weeknav__btn:hover { background: var(--paper-edge); }
}
.menuweek { list-style: none; display: grid; gap: var(--space-lg); }
.menuday {
  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  background: var(--paper-deep);
  display: grid;
  gap: var(--space-sm);
  align-content: start;
}
.menuday--today { background: var(--wash-leaf); box-shadow: inset 0 0 0 2px var(--green-strong); scroll-margin-top: var(--space-md); }
.menuday--closed { background: var(--paper); border: 1px dashed var(--field-edge); }
.menuday__title { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.menuday__closed { font-family: var(--font-display); font-weight: 700; color: var(--magenta-strong); }
.menuday__note { font-size: var(--step--1); }
.menuday__meals .facts__row { grid-template-columns: minmax(6.5rem, 8rem) 1fr; }
.menuday__time {
  display: block;
  margin-top: 2px;
  color: var(--green-strong);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}
.menuday__notes:not(:has(> :not([hidden]))) { display: none; }

/* From 62rem: the week as one table in the public menu page's table
   style (site.css: leaf-wash header band, zebra rows, green times),
   meals down the side and the five days across, today in green. The
   per-day blocks above stay for narrower screens. */
.menutable { display: none; }
@media (min-width: 62rem) {
  .menuweek { display: none; }
  .menutable { display: block; }
  .weeknav [data-jump-today] { display: none; }
}
.menutable table { table-layout: fixed; }
.menutable th:first-child { width: 10.5rem; }
.menutable__weekday, .menutable__date, .menutable__meal, .menutable__time { display: block; }
.menutable__date {
  margin-top: 2px;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--ink-soft);
}
.menutable__today { margin-top: var(--space-2xs); color: var(--green-strong); background: var(--paper); }
.menutable thead th.is-today { color: var(--paper); background: var(--green-strong); border-radius: var(--radius-sm); }
.menutable thead th.is-today .menutable__date { color: var(--paper); }
.menutable td.is-today { background: var(--wash-leaf); }
.menutable tbody th { white-space: normal; }
.menutable__time { margin-top: 2px; font-size: 0.8rem; font-weight: 600; }
.menutable__notes td { font-size: var(--step--1); }
/* One dish per line, in the table and in the phone cards alike. */
.menutable .dish, .menuday__meals .dish { display: block; }
.menutable .dish__sep + .dish, .menuday__meals .dish__sep + .dish { margin-top: var(--space-2xs); }
/* Hidden from the eye only: a screen reader still hears "Oatmeal, Milk". */
.menutable .dish__sep, .menuday__meals .dish__sep {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.menutable .t-closed {
  vertical-align: middle;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--magenta-strong);
  background: var(--paper);
  border: 1px dashed var(--field-edge);
  border-radius: var(--radius-sm);
}

/* ---------- 10. Admin: overview, storage, families, results ---------- */

/* Her four jobs. Phones: one full-width pill each, "New post" (the
   daily one) first; two to a row would wrap "Agregar una familia". */
@media (max-width: 47.99rem) {
  .overview__actions { display: grid; grid-template-columns: minmax(0, 1fr); }
}

/* The numbers beside what parents see now; stacked on phones. Plain
   hairline rows, never the big-number tiles PRODUCT.md rules out. On
   wide screens the shorter numbers column rides along while the longer
   one scrolls, so it never leaves a blank field under the meter. */
.overview { display: grid; gap: var(--space-2xl); }
/* No grid child may grow wider than the phone: a pasted link wraps. */
.overview > *, .recent__item > * { min-width: 0; }
@media (min-width: 54rem) {
  .overview { grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr); align-items: start; }
  .overview > :first-child { position: sticky; top: var(--space-lg); }
}
.overview__facts .facts__row { grid-template-columns: minmax(8rem, 11rem) 1fr; align-items: center; }
.overview__facts .facts__val { font-family: var(--font-display); font-weight: 700; }
.overview__facts .facts__val a { margin-left: var(--space-xs); font-family: var(--font-body); }

/* Links a thumb presses inside a line of text keep the 44px target. */
.overview__facts .facts__val a,
.overview .portal-empty a,
.overview .portal-msg a { display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; }
.overview .portal-msg a { color: inherit; }
.overview .portal-empty { padding-block: var(--space-sm); }

/* The reload button belongs to the message right above it. */
.stack-xl > .overview__reload { margin-top: var(--space-sm); }

/* A closed day: the menu page's dashed "closed" box (DESIGN.md). */
.overview__closed {
  padding: var(--space-md) var(--space-lg);
  border: 1px dashed var(--field-edge);
  border-radius: var(--radius-lg);
  background: var(--paper);
}

/* A block's title with its "see all" link on the right. */
.overview__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 var(--space-md);
}
.overview__head a { display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; }

.recent { list-style: none; border-top: 1px solid var(--paper-edge); }
.recent:empty { display: none; }
.recent__item {
  display: grid;
  gap: var(--space-2xs);
  padding-block: var(--space-md);
  border-bottom: 1px solid var(--paper-edge);
}
.recent__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2xs) var(--space-sm); }
.recent__head .post__date { margin-right: 0; }
/* The post's opening words (admin-overview.js cuts them at a word). */
.recent__body { max-width: var(--measure); overflow-wrap: anywhere; }
.recent__body:empty { display: none; }

.storage__meter {
  display: block;
  width: 100%;
  height: 0.75rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--paper-deep);
  box-shadow: inset 0 0 0 1px var(--paper-edge);
}
.storage__meter::-webkit-meter-bar { background: transparent; border: 0; height: 0.75rem; border-radius: var(--radius-pill); }
.storage__meter::-webkit-meter-optimum-value { background: var(--green-strong); border-radius: var(--radius-pill); }
.storage__meter::-webkit-meter-suboptimum-value { background: var(--gold); border-radius: var(--radius-pill); }
.storage__meter::-webkit-meter-even-less-good-value { background: var(--magenta-strong); border-radius: var(--radius-pill); }
.storage__meter::-moz-meter-bar { background: var(--green-strong); border-radius: var(--radius-pill); }

/* "Add a family" is a disclosure, not a page section: closed, it is one
   pill; open, it becomes the secondary pill above the form. */
.adder > summary { list-style: none; width: max-content; }
.adder > summary::-webkit-details-marker { display: none; }
.adder[open] > summary {
  background: var(--paper);
  color: var(--magenta-strong);
  border-color: var(--magenta-strong);
  box-shadow: none;
}
.adder[open] > .panel { margin-top: var(--space-md); }

.families { list-style: none; border-top: 1px solid var(--paper-edge); }
.family {
  padding-block: var(--space-lg);
  border-bottom: 1px solid var(--paper-edge);
  display: grid;
  gap: var(--space-sm);
}
.family--ended .family__head h3 { color: var(--ink-soft); }
.family__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-sm); }
.family__number {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step--1);
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.family__guardians { list-style: none; display: grid; gap: var(--space-sm); }
.guardian {
  display: grid;
  gap: var(--space-2xs) var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  background: var(--paper-deep);
}
/* Wide: one set of columns for the whole list. Every family and every
   adult row is a subgrid of .families, so the status pills stand in one
   line down the page and the action column is as wide as the longest
   row of actions, which then never wraps. */
@media (min-width: 54rem) {
  .families { display: grid; grid-template-columns: minmax(0, 1fr) max-content max-content; column-gap: var(--space-lg); }
  .family, .family__guardians { grid-column: 1 / -1; grid-template-columns: subgrid; }
  .family > * { grid-column: 1 / -1; }
  .family > .family__guardians { grid-column: 1 / -1; }
  .guardian { grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
  .guardian__actions { justify-content: flex-end; flex-wrap: nowrap; }
}
.guardian__who { display: grid; }
.guardian__who .fine { overflow-wrap: anywhere; }
.guardian__status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.guardian__actions { gap: 0; }

/* Each adult in the new-family form: a hairline and a heading inside
   the panel, not a box inside the box. */
.guardianrow {
  margin: 0;
  padding: var(--space-md) 0 0;
  border: 0;
  border-top: 1px solid var(--field-edge);
  min-inline-size: 0;
}
.guardianrow > legend { float: left; width: 100%; padding: 0; margin-bottom: var(--space-sm); }
.guardianrow > legend + * { clear: both; }

.resultrow { display: grid; gap: var(--space-xs); }
.codeface {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--step-1);
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
}

/* ---------- 11. Admin: new post ---------- */

.previews {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: var(--space-xs);
}
.preview { position: relative; aspect-ratio: 1 / 1; border-radius: var(--radius-sm); overflow: hidden; background: var(--paper-deep); }
.preview img { width: 100%; height: 100%; object-fit: cover; }
.preview__remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 44px;
  height: 44px;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--paper);
  background: oklch(24% 0.01 67 / 0.72);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}
.preview__state {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 2px var(--space-xs);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.7rem;
  color: var(--paper);
  background: oklch(24% 0.01 67 / 0.72);
}
.preview__state:empty { display: none; }
.preview[data-state="done"] .preview__state { background: var(--green-strong); }
.preview[data-state="failed"] .preview__state { background: var(--magenta-strong); }

.progress progress { width: 100%; height: 0.9rem; accent-color: var(--green-strong); }

.draftrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  background: var(--paper-deep);
}

/* ---------- 12. Admin: menu editor ---------- */

.dishform { display: grid; gap: 0 var(--space-md); align-items: end; }
@media (min-width: 48rem) { .dishform { grid-template-columns: 1fr 1fr auto; } .dishform .btn { margin-bottom: var(--space-lg); } }
.cycleform { display: grid; gap: 0 var(--space-md); align-items: end; max-width: 42rem; }
@media (min-width: 40rem) { .cycleform { grid-template-columns: 1fr 1fr auto; } .cycleform .btn { margin-bottom: var(--space-lg); } }

.dishlist { list-style: none; border-top: 1px solid var(--paper-edge); max-height: 24rem; overflow-y: auto; overscroll-behavior: contain; }
/* A long library scrolls inside the panel. Soft shadows at the top and
   bottom edges appear only while there is more to scroll to: the paper
   covers ride with the list ("local"), the shadows stay put. */
.dishlist {
  background:
    linear-gradient(var(--paper-deep) 40%, transparent) top / 100% 2.5rem no-repeat local,
    linear-gradient(transparent, var(--paper-deep) 60%) bottom / 100% 2.5rem no-repeat local,
    radial-gradient(farthest-side at 50% 0, oklch(35% 0.03 70 / 0.22), transparent) top / 100% 0.8rem no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, oklch(35% 0.03 70 / 0.22), transparent) bottom / 100% 0.8rem no-repeat scroll;
}
.dishrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  padding: var(--space-2xs) 0;
  border-bottom: 1px solid var(--paper-edge);
}
.dishrow__names { display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: baseline; }

.weektabs { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.weektab {
  min-height: 44px;
  padding: 0 var(--space-md);
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--field-edge);
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.weektab[aria-selected="true"] { color: var(--paper); background: var(--green-strong); border-color: var(--green-strong); }

.gridweek { list-style: none; display: grid; gap: var(--space-md); }
@media (min-width: 70rem) { .gridweek { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.gridday { padding: var(--space-xs) var(--space-md); border-radius: var(--radius-md); background: var(--paper); }
.gridday__head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 48px;
  list-style: none;
  cursor: pointer;
}
.gridday__head::-webkit-details-marker { display: none; }
.gridday__count {
  margin-left: auto;
  font-size: var(--step--1);
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
/* A chevron drawn from two borders, turning as the day opens. */
.gridday__head::after {
  content: '';
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  margin: 0 0.3rem 0.2rem 0;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(45deg);
  transition: transform var(--dur-fast) var(--ease-out-quart);
}
.gridday__fold[open] > .gridday__head::after { transform: translateY(0.3rem) rotate(-135deg); }
.gridday__fold[open] > .daymeals { padding-bottom: var(--space-sm); }
@media (min-width: 70rem) {
  .gridday { padding: var(--space-md); }
  .gridday__head { min-height: 0; margin-bottom: var(--space-sm); cursor: default; }
  .gridday__head::after, .gridday__count { display: none; }
}

.daymeals { display: grid; gap: var(--space-sm); }
.mealcell { display: grid; gap: var(--space-2xs); }
.mealcell__name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.mealcell__body { margin: 0; display: grid; gap: var(--space-2xs); }
.mealcell__add {
  width: 100%;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 2px dashed var(--field-edge);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-sm);
}

.chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.chips:empty { display: none; }
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-left: var(--space-md);
  font-size: 0.92rem;
  line-height: 1.25;
  background: var(--wash-sun);
  border-radius: var(--radius-pill);
}
/* Wide: five days side by side leave each column narrow, so a meal's
   dishes stack as full-width rows rather than pills wrapping mid-name. */
@media (min-width: 70rem) {
  .gridday .chips { flex-direction: column; }
  .gridday .chip { justify-content: space-between; border-radius: var(--radius-sm); }
}
.chip__remove {
  flex: none;
  width: 44px;
  height: 44px;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--ink-soft);
  background: transparent;
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
}

/* ---------- 13. Admin: documents ---------- */

.doclist { list-style: none; border-top: 1px solid var(--paper-edge); }
.docrow {
  display: grid;
  gap: var(--space-xs);
  padding-block: var(--space-md);
  border-bottom: 1px solid var(--paper-edge);
}
@media (min-width: 54rem) { .docrow { grid-template-columns: 1fr auto; align-items: center; } }
.docrow__names { display: grid; }
/* Phones: "Delete" always takes its own line, whichever actions a row
   has, so the destructive one is never where "Move up" was a row ago. */
@media (max-width: 53.99rem) { .docrow .actions > .btn--danger { flex-basis: 100%; justify-content: flex-start; } }

/* The PDF tag travels with its date and size, in either language. */
.downloads__tail { display: inline-flex; align-items: baseline; gap: var(--space-md); min-width: 0; }
.downloads__tail .downloads__pdf { flex: none; }

/* ---------- 14. Footer ---------- */

/* The house footer (site.css .foot), compact: her ribbon across the top
   of the ink ground, so the portal is plainly still at Lety's. */
.portalfoot {
  margin-top: var(--space-3xl);
  padding-top: var(--space-xl);
  padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom));
  font-size: var(--step--1);
  color: oklch(80% 0.016 80);
}
.portalfoot__inner { display: grid; gap: var(--space-sm); padding-top: var(--space-xl); }
.portalfoot .fine { color: inherit; }
.portalfoot .fine a { display: inline-flex; align-items: center; min-height: 44px; }
/* One tap to call, from every portal page: the marigold on-dark pill. */
.portalfoot__call {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm) var(--space-lg);
  padding-bottom: var(--space-sm);
  font-size: var(--step-0);
  color: var(--paper);
}
.portalfoot__phone { font-variant-numeric: tabular-nums; white-space: nowrap; }
.portalfoot__links { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2xs) var(--space-lg); }
.portalfoot__links a {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--paper);
}

/* ---------- 15. Motion (DESIGN.md, Motion) ----------
   The portal is a tool: motion here only says that something arrived
   or changed, fast, and never makes anyone wait. Each keyframe sits on
   an element that was display:none, so it plays as the element is
   shown; nothing leaves with an exit animation. Script plays the rest
   (assets/js/portal/motion.js). site.css stills all of it under
   prefers-reduced-motion. */

@keyframes arrive { from { opacity: 0; } }
@keyframes settle { from { opacity: 0; transform: translateY(6px); } }
@keyframes drop-in { from { opacity: 0; transform: translateY(-4px); } }
@keyframes viewer-open { from { opacity: 0; transform: scale(0.96); } }

/* The page, once the session has said who this is, instead of
   "Loading…" snapping to a full page. Opacity only: a transform on
   <main> would give its sticky blocks a new containing block. */
body[data-auth="ok"] main,
body[data-auth="ok"] .portalnav,
.portal-denied:not([hidden]) { animation: arrive var(--dur-mid) var(--ease-out-quart); }

/* A message, and its copy under the field it is about. */
.portal-msg,
.field__error[data-inline-error] { animation: drop-in var(--dur-mid) var(--ease-out-quart); }

/* A result: the family added, the post published, the adder opened,
   a photo picked for a post. */
.panel--ok,
.adder[open] > .panel,
.preview { animation: settle var(--dur-mid) var(--ease-out-quart); }

/* The photo viewer opens around the photo; stepping is motion.js. */
.viewer[open],
.viewer[open]::backdrop { animation: arrive var(--dur-mid) var(--ease-out-quart); }
.viewer[open] .viewer__stage img { animation: viewer-open var(--dur-mid) var(--ease-out-quart); }

/* ---------- 16. The printed setup card ----------
   On screen it is never shown; body.is-printing-card prints it
   alone, on a quarter of a letter page, both languages at once. */

.printarea { display: none; }

@media print {
  body.is-printing-card > *:not(main) { display: none !important; }
  body.is-printing-card main > *:not(.printarea) { display: none !important; }
  body.is-printing-card .printarea { display: block; }

  .setupcard {
    width: 5.5in;
    padding: 0.35in;
    border: 2px dashed #3b50a1;
    border-radius: 18px;
    font-family: var(--font-body);
    font-size: 11pt;
    color: #231f1b;
    background: #fff;
    break-inside: avoid;
  }
  .setupcard__head { display: flex; align-items: center; gap: 10px; }
  .setupcard__head img { width: 40px; height: 40px; }
  .setupcard__brand { font-family: var(--font-display); font-weight: 700; font-size: 14pt; color: #0a622b; }
  .setupcard__title { margin-top: 10px; font-size: 13pt; display: grid; }
  .setupcard__title span[lang="en"] { font-weight: 600; font-size: 11pt; color: #4a443c; }
  .setupcard__for { margin-top: 6px; font-weight: 600; }
  .setupcard__codes { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .setupcard__codes dt { font-size: 8pt; text-transform: uppercase; letter-spacing: 0.08em; color: #4a443c; }
  .setupcard__codes dd { margin: 2px 0 0; font-family: var(--font-display); font-weight: 800; font-size: 18pt; letter-spacing: 0.1em; }
  .setupcard__steps { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 9.5pt; }
  .setupcard__steps ol { padding-left: 1.2em; }
  .setupcard__steps ol[lang="en"] { color: #4a443c; }
  .setupcard__fine { margin-top: 10px; font-size: 8.5pt; }
}
