/* Header actions such as Calendar's Today and Library's Publish can carry a compact icon. */
.app-top-button svg {
  width: 15px;
  height: 15px;
  stroke-width: 2;
}

.app-status {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-detail-body);
}

.app-status-failure {
  color: var(--error);
}

.app-status-offline {
  color: var(--muted);
}

.app-status-retry {
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  margin-left: var(--space-r2);
  padding: 0;
  color: var(--accent-ink);
  background: transparent;
  border: 0;
  font: inherit;
  text-decoration: underline;
}

.app-failure {
  margin: 0;
  color: var(--error);
  font-size: var(--text-detail-body);
}

.app-empty {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-detail-body);
}

/* One terse setup or status note: a single line of person-facing state, without provider instructions or raw identifiers. */
.app-note {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-support);
}

/* Empty copy on the wallpaper uses the shared on-wall ink, except on a card-like surface
   and on an empty line that is itself the card. */
.app-empty:not(.app-card, .app-card *, .app-surface *, .app-inset *, .app-dialog *, dialog *, .view-page *) {
  color: var(--ink-on-wall-muted);
  text-shadow: var(--text-shadow-on-wall);
}

/* A loading placeholder, for a surface whose fetch is already tracked in JS and would
   otherwise show nothing (or a bare "Loading…" line) while it waits. It stands on the
   wallpaper drawn in the card's own surface (--panel-subtle, --card-radius, --shadow-card),
   so it never reads brighter or more transparent than the card it stands in for. Composed
   by the caller onto whatever box shape the real content will take (a grid tile, a row).
   The reduced-motion twin follows the checkbox control's own pattern: the animation drops
   to the static tint, never a motionless flash. */
.app-skeleton {
  min-height: 1rem;
  background: var(--panel-subtle);
  border-radius: var(--card-radius);
  box-shadow: var(--shadow-card);
  animation: app-skeleton-pulse 1.6s ease-in-out infinite;
}

@keyframes app-skeleton-pulse {
  50% {
    background: color-mix(in srgb, var(--panel) 55%, var(--panel-subtle));
  }
}

/* A skeleton shaped like a list of rows: one .app-card (it supplies the surface) holding
   .app-skeleton-row entries, each an optional thumb beside two lines. A list of them takes
   .app-card-list's gap, and the card composes .app-divider-list for the hairline between
   rows. The pane's own "Loading…" line is what is announced, so the card is aria-hidden. */
.app-skeleton-card {
  display: flex;
  flex-direction: column;
}

.app-skeleton-row {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  min-height: 44px;
  padding-block: var(--space-sm);
}

.app-skeleton-thumb {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-control);
  background: var(--panel);
  animation: app-skeleton-line-pulse 1.6s ease-in-out infinite;
}

.app-skeleton-lines {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.app-skeleton-line {
  width: 85%;
  /* Follows the text step (10.8px at Medium) so a skeleton holds the rough height of the
     line it stands in for at Large too. */
  height: calc(var(--text-detail-body) * 0.6);
  border-radius: var(--radius-pill);
  background: var(--panel);
  animation: app-skeleton-line-pulse 1.6s ease-in-out infinite;
}

.app-skeleton-line-short {
  width: 45%;
}

@keyframes app-skeleton-line-pulse {
  50% {
    background: color-mix(in srgb, var(--line) 60%, var(--panel));
  }
}

@media (prefers-reduced-motion: reduce) {
  .app-skeleton,
  .app-skeleton-thumb,
  .app-skeleton-line { animation: none; }
}

/* The one accented datum in a row (a date, a time), or a row whose leading title is that datum. */
.app-key {
  color: var(--accent-ink);
  font-weight: var(--w-title);
}

/* Rhythm, not cards: one faint divider between entries and none around them. */
.app-divider-list > * + * {
  border-top: 1px solid var(--line);
}

/* The app's second list idiom. .app-divider-list above is rhythm, not cards: it suits a list
   of lines inside one document. This one suits a list whose entries are things in their own
   right -- a calendar row, an email -- where the eye has to find where one ends and the next
   begins. The two are siblings; neither replaces the other. */
.app-card-list {
  display: flex;
  flex-direction: column;
  gap: var(--rhythm-card-card);
}

.app-card {
  padding: var(--card-pad);
  background: var(--panel-subtle);
  border: 0;
  border-radius: var(--card-radius);
  box-shadow: var(--shadow-card);
}

/* No dark override here on purpose. This rule used to promote every card to --surface-high,
   reasoning that a card had to read one step above a --panel field it sat inside -- but no
   pane paints that field any more (.app-scroller-field and .app-scroller-page are both
   `background: transparent` below, and the wallpaper is the only ground behind them). A
   card is the ONLY opaque surface between its content and the wallpaper now, so the base
   rule's own --panel-subtle -- #27313e in the approved theme, the reference's own opaque
   .gp-panel -- is already the right, single step; promoting it further just made the card
   read close enough to the wallpaper's own blue to look translucent. An inset or field
   nested INSIDE a card (Email Review's own overflow menu, an .app-inset) may still take
   --panel/--surface-raised as its own lighter step above the card -- that pairing is
   unaffected, since it was never about the card's own background. */

/* A grouped surface: several rows a page reads as ONE block, rather than a list of
   bounded entries (.app-card-list) or lines in one document (.app-divider-list). It is
   the largest of the three radii because it is the largest of the three shapes -- an
   18px corner on a 40px card looks like a mistake, and a 10px corner on a whole block
   looks like a card that grew.

   This shared 18px surface was written in several places as a literal, including generated
   pages and the composer's `+` sheet. It makes a generated page read as a published
   document rather than app chrome, which is exactly why the rest of the app should have
   the word too. */
.app-surface {
  padding: 0.7rem 0.85rem;
  background: var(--panel-subtle);
  border: 1px solid var(--line);
  border-radius: var(--radius-surface);
}

/* THE SCROLLER. Every display in this app is one: the band shows exactly one pane, and
   that pane owns the vertical scroll. Thirteen rules wrote this out before this round --
   eight whole panes plus five inner scrollers -- and overscroll-behavior is part of the
   primitive rather than an afterthought, because a scroller that bubbles its overscroll
   drags the whole app on a phone.

   The mechanics are here; the GROUND is a separate decision and gets its own name,
   because it is not arbitrary -- it follows from which of the app's two list idioms the
   pane holds. See "Which list idiom a new list should use": entries that are things in
   their own right sit as bounded cards on a pale field, and lines inside one document sit
   on the plain card surface. Eight panes were making that choice correctly and writing it
   out by hand; now they name it. */
.app-scroller {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-height: 0;
  /* This scroller moves vertically only: clip keeps stray width from turning the page
     into a two-axis pan, and a child that scrolls sideways keeps its own overflow-x box. */
  overflow-y: auto;
  overflow-x: clip;
  touch-action: pan-y pinch-zoom;
  overscroll-behavior: contain;
}

/* `hidden` IS ONLY A UA `display: none`, which any author rule naming `display`
   outranks -- so every primitive that sets one has to hand the attribute back, or a
   consumer that relied on the UA rule silently stops hiding. This is stated once, over
   the whole set, rather than per primitive: the failure is invisible in a unit test
   (jsdom lays out nothing) and the next primitive to name `display` would have to
   remember. It was not hypothetical -- the Calendar's day list had no `display` of its
   own, so `hidden` worked, and it kept a full-height empty field above the four-week
   grid the moment .app-scroller gave it one. Found in a browser screenshot. */
.app-toolbar[hidden],
.app-row[hidden],
.app-row-title[hidden],
.app-row-meta[hidden],
.app-row-actions[hidden],
.app-tag[hidden],
.app-card-list[hidden],
.app-skeleton-card[hidden],
.app-skeleton-row[hidden],
.app-skeleton-lines[hidden],
.app-scroller[hidden],
.app-check[hidden],
.app-check-target[hidden],
.app-page[hidden],
.app-page-body[hidden],
.app-section[hidden],
.app-stack[hidden],
.app-search[hidden],
.app-field-row[hidden],
.app-grid[hidden],
.app-media[hidden],
.app-dialog-frame[hidden],
.app-dialog-header[hidden],
.app-dialog-x[hidden],
.app-dialog-footer[hidden] {
  display: none;
}

.app-page-body > :is(p:empty, [hidden]) {
  display: none;
}

/* Bounded cards on a pale field: Today, Podcasts, the Library, the Calendar's day list,
   the Email Review list, Meals and Groceries. The Library
   was the one that MOVED here in an earlier round -- it held cards on the plain surface,
   which is the mismatch the two names exist to make visible; Meals and Groceries moved
   here in the gold-and-blue card round, for the same reason -- each recipe and each
   category is now its own bounded card rather than a line inside one document. */
/* Transparent, both idioms: the gold-and-blue direction's own wallpaper (.app-wallpaper,
   fixed behind everything) is what a pane's own ground used to hide, one dark column per
   destination -- see index.html's boot comment and docs/architecture/shared-app-layout.md's "One presentation
   layer" for the fixed layer this reveals. A card floating on it still draws its own
   surface (.app-card, .app-surface); it is only the FIELD or PAGE around those cards that
   stopped drawing one of its own. */
.app-scroller-field {
  padding: var(--space-lg) var(--list-gutter) var(--space-r4);
  background: transparent;
}

/* Lines inside one document: Household, Settings and the All launcher. The
   roomier inset is this idiom's own -- a document wants a margin, a field of cards wants
   a gutter; vertically only -- sideways its cards start at the same --list-gutter as a
   field's, at every width. */
.app-scroller-page {
  padding: var(--space-r3) var(--list-gutter) var(--space-r4);
  background: transparent;
}

/* A page header that lives INSIDE a padded scroller (Meals, Groceries, Music, Library, Photos,
   Household, Settings, All apps) stacked its own 21px --page-gutter on the
   scroller's 17px, so those titles sat 38px from the edge while Inbox and Calendar
   Podcasts, whose headers sit outside a padded scroller, sit at 21. The header hands the
   scroller's inset back at every width, so a phone and a tablet both put every title at
   --page-gutter from the pane edge; cards stay at --list-gutter. The wide-screen
   content-column rule in the 68.75rem block still recentres headers and content together.
   Podcasts zeroes its scroller's padding and Today's header is its clock, so neither takes it. */
:where(.app-scroller-field, .app-scroller-page):not(.podcasts, .today) .app-page-header {
  margin-inline: calc(var(--list-gutter) * -1);
}

/* A page header owns its own 18px padding-top (.app-page-header above); a field or page
   container that ALSO wraps it as the pane's first content stacks a second gap on top of
   that one -- the exact ~10px Meals/Groceries/Music (both classes on the outer section, or
   one level in through a bare wrapper div like #music-overview/#settings-root) used to read
   lower than Calendar/Inbox/Podcasts, whose header sits outside any field/page
   padding. Podcasts solved this itself by zeroing its whole field padding and pushing the
   gutter into each sub-view instead (see .podcasts above); this generalises just the
   top-padding half of that fix to every pane whose header is its scroller's first child, so
   every destination's title lands at the same offset without touching the side/bottom
   gutters its card list still needs. */
.app-scroller-field:has(.app-page-header:first-child),
.app-scroller-page:has(.app-page-header:first-child) {
  padding-top: 0;
}

/* THE CHECKBOX, once. This ringed circle was written out seven times before this pass
   -- .agenda-task-check, .calendar-candidate-choice, .meals-proposal-choice's input,
   .grocery-check-input, .grocery-ingredient-check, .view-task-checkbox and
   .view-grocery-checkbox -- each with its own prefers-reduced-motion twin, and the last
   two byte-for-byte identical to each other. Seven copies is seven chances for one
   display's checkbox to drift, which is the most literal possible way a set of pages
   stops feeling like one product.

   It works on a <span> as well as an <input>: a decorative, always-open ring for a task
   list with no done-state simply never matches :checked. The ring reads --control-line rather than --line-strong, which is
   a real fix and not a restyle: a card's outline measures 1.3:1 and a control's
   boundary has to clear 3:1, so every one of those seven copies was drawing a ring a
   low-vision reader could not find. */
/* Transparent, not --panel-subtle: an unchecked box is a ring, not a filled disc -- the
   an unchecked input draws no fill at all, and a solid panel-coloured circle
   read as a dark disc rather than an open ring in a screenshot review. :checked below still
   fills it, which is what tells the two states apart beyond colour alone. */
.app-check {
  appearance: none;
  -webkit-appearance: none;
  display: grid;
  flex: none;
  place-content: center;
  box-sizing: content-box;
  width: var(--check-size);
  height: var(--check-size);
  padding: var(--touch-slack);
  margin: 0;
  background: radial-gradient(circle, transparent 0 calc((var(--check-size) / 2) - 2.1px), var(--control-line) calc((var(--check-size) / 2) - 1.4px) calc((var(--check-size) / 2) - 0.5px), transparent calc(var(--check-size) / 2));
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.app-check::before {
  content: '';
  width: calc(var(--check-size) / 2);
  height: calc(var(--check-size) / 2);
  border-radius: 50%;
  background: var(--on-accent);
  transform: scale(0);
  transition: transform 120ms ease-in-out;
}

.app-check:checked {
  background: radial-gradient(circle, var(--on-accent) 0 calc(var(--check-size) / 4 - 0.5px), var(--accent) calc(var(--check-size) / 4 + 0.5px) calc((var(--check-size) / 2) - 0.5px), transparent calc(var(--check-size) / 2));
}

.app-check.app-selected {
  background: radial-gradient(circle, var(--on-accent) 0 calc(var(--check-size) / 4 - 0.5px), var(--accent) calc(var(--check-size) / 4 + 0.5px) calc((var(--check-size) / 2) - 0.5px), transparent calc(var(--check-size) / 2));
}

.app-check:checked::before {
  transform: scale(1);
}

.app-check.app-selected::before {
  transform: scale(1);
}

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

@media (prefers-reduced-motion: reduce) {
  .app-check::before { transition: none; }
  .app-quiet-button,
  #calendar-reminder-enable,
  .calendar-notifications,
  .app-primary-button {
    transition: none;
  }
}

/* The touch target around a checkbox, and it costs no layout space: --touch-slack of
   padding grows the hit area to --touch-min, and the same amount of negative margin
   hands the space straight back, so the label occupies exactly the box's own size.
   Growing the box instead would add 20px to the height of every single-line row that
   carries one. Written three times before this (Today's card, the grocery row, the
   grocery meal row) with the same four values each time. */
.app-check-target {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: calc(var(--touch-slack) * -1);
  cursor: pointer;
}

/* THE SELECTED STATE, once: the accent's own quiet ground, the accent as ink, and a
   step up in weight -- never a solid fill, because a fill is what a primary action looks
   like and a chosen segment that looks like a Submit button is telling you the wrong
   thing. Weight carries the state as well as colour, so selection is never colour alone.

   Five controls said "chosen" before this pass and they said it three different ways:
   the notifications toggle and the two calendar option rows drew an accent ring on the
   hover ground, the person options drew an --ink ring, and the theme options changed the
   ground to --accent-soft. Three answers to one question in three settings surfaces is
   the most direct way a set of pages stops feeling like one product, and each of the
   three had an argument for itself that was really an argument about the OTHER two.

   The ground is what carries it, because the ground is the one form that degrades
   correctly in both places this appears. A standalone control also takes the accent on
   its own border; a segment inside a joined bar has no border of its own to take it
   (the bar owns the border), so that declaration simply does nothing there rather than
   drawing a ring around one segment of a group. Applied by whichever attribute the
   control already uses to say it is chosen, so no controller has to learn a class. */
.app-selected[aria-pressed="true"],
.app-selected[aria-checked="true"],
.app-selected[aria-current="true"],
.app-selected:has(> input:checked),
.app-selected.selected {
  color: var(--accent);
  font-weight: var(--w-title);
  background: var(--accent-soft);
  border-color: var(--accent);
}

/* The layout containers: section, stack, row, grid -- shared so generated and hand-authored
   pages lay out with the same words. A `section` is the only one of the four that sets
   anything apart visually; the other three are pure arrangement, because a document that
   could draw a box anywhere is a document that has been given styling. */
.app-section {
  display: flex;
  flex-direction: column;
  gap: var(--rhythm-label-card);
}

/* The page root holds its header and body; the header owns the header-to-first-block space. */
.app-page {
  display: flex;
  flex-direction: column;
}

/* Children set no vertical margin of their own; the page body owns block rhythm. */
.app-page-body {
  display: flex;
  flex-direction: column;
  gap: var(--rhythm-card-card);
  min-width: 0;
}

.app-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-r2);
}

/* Two or three columns that collapse to one on a phone -- the collapse is in the
   30rem media query, stated once there for every caller. */
.app-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem;
}

.app-grid[data-columns="3"] {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* A line of prose. Not .app-row-meta (a caption under a title) and not .app-status (a
   pane telling you what it is doing) -- this is body copy a person reads. */
.app-text {
  margin: 0;
  color: var(--ink);
  font-size: var(--text-detail-body);
  line-height: var(--leading-detail-body);
  overflow-wrap: anywhere;
}

/* One emphasised line, for the thing a person should read first on a surface: the
   shared `notice` primitive, the calendar's reminder note, the notifications note,
   the theme note. It brings only type and ink; the surface under it is whichever of
   .app-inset or .app-card the caller wants, exactly the way notice variants already
   worked. Declared after both of those so it wins the size -- a notice sits at the body
   step even inside a quiet block, because a sentence a person should read first is not
   a caption. */
.app-notice {
  margin: 0;
  color: var(--ink);
  font-size: var(--text-detail-body);
  line-height: var(--leading-detail-body);
  overflow-wrap: anywhere;
}

/* THE PAGE HEADER: the pane's own visible title band now, on the wallpaper rather than a
   card -- the gold-and-blue direction's own reversal of the compact-pane-identity round
   this replaces (see ARCHITECTURE.md's "the page header is a compact meta line, not a
   banner", which this header no longer is). The nav's own selected state is still part of
   a pane's identity, but the title is real, visible text again everywhere except Today
   (.today .app-page-header-title below): Today's own clock header is a later slice's
   subject, so its h1 stays the visually hidden focus target it already was. */
.app-page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-r2);
  row-gap: var(--rhythm-title-subtitle);
  margin: 0;
  padding: 18px var(--page-gutter);
  padding-bottom: var(--rhythm-header-body);
}

/* The slot grows to fill the header row, so a trailing action button lands at the right
   edge; min-width lets the slot shrink below its content size so long roles cannot push it. */
.household-person-role-slot { flex: 1 1 auto; min-width: 0; }

/* An empty subtitle slot is hidden and some headers have no subtitle slot at all; either
   way a visible action with its -4px shorthand margin is the last visible header child. Restore
   the shared header-to-body rhythm without changing headers whose subtitle has real
   content. */
.app-page-header:has(> :is(button, select):not([hidden])):where(:has(> .app-page-header-subtitle:empty), :not(:has(> .app-page-header-subtitle))) {
  padding-block-end: calc(var(--rhythm-header-body) + 4px);
}

.app-page-header[hidden] {
  display: none;
}

/* The pane's canonical name: announced and focusable (chat.js focuses it when the pane
   opens), and visible now -- large, light Manrope, tight tracking, white with the on-wall
   text-shadow the subtitle beside it already used. */
.app-page-header-title {
  margin: 0;
  min-width: 0;
  flex: 1 1 auto;
  font: var(--w-body) var(--text-page-title)/1.15 var(--font-display);
  letter-spacing: var(--title-tracking);
  color: var(--ink-heading);
  text-shadow: var(--text-shadow-on-wall);
}

/* Header actions are compact and never stretch; a gold primary action in a header matches
   the 42px .app-top-button beside it rather than being a bigger slab. Actions may overhang
   the title line by a few px so a page with a 42px action keeps exactly the same title
   position as a page without one. */
.app-page-header > :is(button, select) {
  flex: none;
  margin-block: -4px;
}

/* The primary action sits at the header's far right, on the title row; a gear after it keeps
   its own spacing. */
.app-page-header > .app-primary-button {
  min-height: var(--touch-min);
  padding: var(--space-lg) 13px;
  font-size: var(--text-support);
  line-height: 1.2;
  margin-left: auto;
  white-space: nowrap;
}

/* Today alone: its own clock header is its visible identity, so its h1 stays exactly the
   visually hidden heading every pane's title used to be -- the same .visually-hidden
   declarations, restated here because the h1 wears this class rather than that one (see
   visual-system.test.ts's own "except Today" case, which pins the markup this answers). */
/* Its header wrapper collapses to nothing as well: with the h1 out of flow, the wrapper's
   18px top and bottom padding would be 36px of empty band above the clock. */
.today .app-page-header {
  padding: 0;
}

.today .app-page-header-title {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.app-page-header-title:focus {
  outline: none;
}

h1[tabindex="-1"]:focus,
h2[tabindex="-1"]:focus {
  outline: none;
}

/* A programmatic focus target (chat.js focuses it on open), not a control. */
.app-back-bar > .app-toolbar-title:focus {
  outline: none;
}

/* One optional slot, wrapping onto its own line (flex-basis 100%) -- the pane's own short
   piece of glanceable context that used to repeat as a second, separate row in the pane's
   own body: Today's date, Calendar's month, a saved count, a last-checked time. A pane
   with more than one such fragment keeps each as its own
   child rather than concatenating them into one string, so each keeps writing to the id
   or class it already had.

   The breathing room is a taller line box, not padding, so it exists only where a line
   does: a header whose meta is empty or [hidden] -- most panes in the first moments after
   load, Meals and Settings always -- lays out at 0px
   positioned, so line-less) detail span. A padded slot kept 6.4px of nothing there. One
   line is 25.6px, measured in a real browser at 390x844. */
.app-page-header-subtitle {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--space-r2);
  row-gap: 0.1rem;
  min-width: 0;
  margin: 0;
  /* This line sits directly on the wallpaper, not on a card (.app-page-header draws no
     background of its own -- see the test above this rule in visual-system.test.ts) --
     never --muted, which is tuned for a card. --ink-on-wall-muted plus the shared soft
     shadow are the on-wall pair every caption between cards now reads through instead. */
  color: var(--ink-on-wall-muted);
  text-shadow: var(--text-shadow-on-wall);
  font-size: var(--text-detail-meta);
  font-weight: var(--w-meta);
  line-height: 2;
}

.app-page-header-subtitle[hidden],
.app-page-header-subtitle:empty {
  display: none;
}

/* Fragment paragraphs' default 1em block margins would double the line's
   height. */
.app-page-header-subtitle > * {
  margin: 0;
}

/* An unwritten fragment beside a written one
   would otherwise still take its column-gap and start the line 8px in from the content
   below it. */
.app-page-header-subtitle > :empty {
  display: none;
}

/* A lone fragment (Today's date, Calendar's month, a plain saved count) is the common
   case and should ellipsise rather than wrap; a container with more than one child
   leaves each child free to wrap onto its own line instead, since ellipsising
   two independent fragments in one box has no correct single-line answer. */
.app-page-header-subtitle > *:only-child {
  min-width: 0;
  overflow-wrap: break-word;
  white-space: normal;
}
