/* Full-screen surfaces clear the status bar and home indicator through one shared rule. */
.app-fullscreen {
  box-sizing: border-box;
  padding: var(--fullscreen-pad);
}

/* ===================================================================================
   THE WALLPAPER: one fixed layer behind every pane, in both themes -- see index.html,
   which places it as body's own first child, never inside <main class="chat">. It belongs
   to the viewport, not to content: html, body, .chat and .chat-upper all read var(--page)
   or transparent rather than painting their own opaque ground (below and near .chat), so
   the wallpaper shows through everywhere a real surface (.app-card, .app-surface, an
   .app-scroller's own field/page ground) does not cover it.

   Ported from the reference's own phone-width composition -- a 390x854 frame, each wave an
   absolutely-positioned circle (width/height/left/top, then rotated) -- by keeping its exact
   pixel geometry as a ratio of that frame rather than copying the pixels themselves: width,
   height, left and top are each the reference's own literal divided by the frame dimension
   it is measured against (390 for anything horizontal, 854 for anything vertical) and
   multiplied back out over 100vw/100dvh, so the composition holds -- gold entering upper-
   right, a wave lower-left, a wave anchoring the bottom, at the same crossing points the
   reference itself renders -- from a 320px phone to a 430px one, and from 640px tall to
   932px, rather than stretching a fixed shape or drifting off a hand-picked oversized guess.
   dvh, not vh, for every vertical figure: the dynamic viewport height, so an iOS address bar
   showing or hiding never reflows the composition the way vh's larger, static value would.
   The desktop block below (48rem) is a deliberately calmer, wider composition -- gold to the
   right third, a wave to the left third, one along the bottom -- rather than the phone shape
   stretched wide.

   No animation, no blur filter, no background-attachment trick: three flat gradients and one
   overlay, all colour literals living in the token blocks above (visual-system.test.ts's
   colour scan is the enforcement; the position/size figures below are geometry, which the
   same test does not restrict). The wall route keeps its own dedicated look (see
   docs/architecture/calendar.md, "The wall route") and may show this same layer underneath it. */
.app-wallpaper {
  position: relative;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(ellipse at 8% 0, var(--wall-base-radial) 0, transparent 62%),
    linear-gradient(155deg, var(--wall-grad-1), var(--wall-grad-2) 48%, var(--wall-grad-3) 100%);
}

.app-wallpaper::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--wall-overlay-top), transparent 29%, transparent 72%, var(--wall-overlay-bottom) 100%);
}

.app-wallpaper-wave {
  position: absolute;
  border-radius: 50%;
}

.app-wallpaper-wave-gold {
  width: calc(610 / 390 * 100vw);
  height: calc(950 / 854 * 100dvh);
  left: calc(105 / 390 * 100vw);
  top: calc(100 / 854 * 100dvh);
  transform: rotate(28deg);
  background: linear-gradient(145deg, var(--wave-gold-1), var(--wave-gold-2) 45%, var(--wave-gold-3));
  box-shadow: inset 20px 8px 48px var(--wave-gold-shadow);
}

.app-wallpaper-wave-teal {
  width: calc(550 / 390 * 100vw);
  height: calc(810 / 854 * 100dvh);
  left: calc(-320 / 390 * 100vw);
  top: calc(280 / 854 * 100dvh);
  transform: rotate(-26deg);
  background: linear-gradient(140deg, var(--wave-teal-1), var(--wave-teal-2) 49%, var(--wave-teal-3));
  box-shadow: inset -16px 0 42px var(--wave-teal-shadow);
}

.app-wallpaper-wave-blue {
  width: calc(670 / 390 * 100vw);
  height: calc(500 / 854 * 100dvh);
  left: calc(25 / 390 * 100vw);
  bottom: calc(-200 / 854 * 100dvh);
  transform: rotate(-26deg);
  background: linear-gradient(30deg, var(--wave-blue-1), var(--wave-blue-2));
}

/* Tablet/desktop: a calmer, wider composition rather than the phone shape stretched across
   a much wider frame -- one wave anchored to the right third, another to the left third, one
   along the bottom, each far shallower than its phone counterpart since a wide viewport needs
   reach, not height. */
@media (min-width: 48rem) {
  .app-wallpaper-wave-gold {
    width: 55vw;
    height: 140vh;
    top: -25vh;
    right: -8vw;
  }

  .app-wallpaper-wave-teal {
    width: 50vw;
    height: 130vh;
    top: 5vh;
    left: -8vw;
  }

  .app-wallpaper-wave-blue {
    width: 65vw;
    height: 55vh;
    left: 18vw;
    bottom: -22vh;
  }
}

/* ===================================================================================
   THE SHARED PRIMITIVE VOCABULARY (tier 2).

   Every display, every dialog, the command bar and the pages view.js composes are built
   from these and nothing else. The measures began as the Calendar agenda's, which is why
   several of them used to be declared as a joined selector with their agenda originals --
   .app-row and .agenda-event-toggle on one rule, and so on. That join is gone: agenda.js
   puts the shared class on the node instead, so Calendar now READS this vocabulary rather
   than being named inside it, exactly like every other pane. Its computed style is
   unchanged either way; what changed is which direction the dependency runs.

   A pane rule below may position one of these, add a modifier, or zero one of its
   declarations. It may not restate one. See the token block at the top of this file for
   the extension rule.
   =================================================================================== */

/* --panel-subtle, not --panel: several panes hang a hoverable control off this bar
   (Podcasts' back and Now Playing buttons, .app-quiet-button generally), and that
   control's own hover is --panel. A toolbar sitting on --panel would make that hover
   invisible against its own bar -- the retired --header-tint used to dodge this by being
   its own colour; the plain card surface dodges it by being visibly lighter than --panel. */
.app-toolbar {
  display: flex;
  align-items: center;
  flex: none;
  gap: var(--space-r2);
  padding: 0.6rem var(--space-r3) 0.3rem;
  background: var(--panel-subtle);
  border-bottom: 1px solid var(--line);
}

/* A toolbar inside a pane that already carries its own horizontal padding, so the bar
   keeps its row shape instead of stacking a second inset on top of it. Five panes --
   Meals, Today, Settings, Groceries and the Screen -- had written this same override
   out, with five near-identical comments explaining it. */
.app-toolbar-flush {
  padding-inline: 0;
}

/* --text-detail-body, not --text-detail-title, and it is the same 16px this row has always
   rendered at: every pane that uses this toolbar (Meals, Groceries, Today) is a list whose
   own row titles are --text-detail-body, so the heading sits at their step and is told apart
   by weight. Raising it a step clipped `Dinner plan` against the count beside it, and bought
   nothing: the rule the request set was a 16px floor, and this was never below it. */
.app-toolbar-title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  color: var(--ink);
  font-size: var(--text-detail-body);
  font-weight: var(--w-title);
  line-height: 1.3;
  overflow-wrap: break-word;
  white-space: normal;
}

/* The count beside a toolbar title is the part that gives way, the same rule .calendar-note
   follows in the bar above: a shrink factor of 100 makes it absorb the whole deficit before
   the pane's own heading loses a character. Without it `Dinner plan` ellipsised against
   `2 meals in the current plan (up to 7).` at 390px. */
.app-toolbar > .app-row-meta {
  flex: 0 100 auto;
  min-width: 0;
}

/* THE SUB-VIEW BAR. Every view one level below a pane (a show, a queue, a playlist, a
   library entry, a photo, a settings page) opens with a back control and its own title, set
   directly on the wallpaper rather than in the shared toolbar's card-ground band: a 44px
   back button in the on-wall heading ink (chevron glyph + the parent's name), then the title
   on its own line in the section step, which may wrap rather than lose its tail. Composed
   onto .app-toolbar so an element that needs the flex row keeps it; this zeroes the band's
   ground, rule and inset. .app-toolbar-flush (a pane whose own scroller already pads the
   sides) is declared after and keeps winning the inline padding. */
.app-back-bar {
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-r2);
  row-gap: 0;
  padding: var(--space-xs) var(--list-gutter) var(--space-sm);
  background: transparent;
  border-bottom: 0;
}

.app-back-bar.app-toolbar-flush {
  padding-inline: 0;
}

.app-back-bar > button:first-child {
  flex: none;
  min-height: var(--touch-min);
  padding: 0 var(--space-r3) 0 0;
  color: var(--ink-heading);
  background: transparent;
  border: 0;
  font-size: var(--text-section);
  text-shadow: var(--text-shadow-on-wall);
}

.app-back-bar > button:first-child:hover,
.app-back-bar > button:first-child:focus-visible {
  background: var(--top-button-ground);
}

.app-back-bar > .app-toolbar-title {
  flex: 1 1 100%;
  overflow: visible;
  color: var(--ink-heading);
  font-size: var(--text-section);
  text-shadow: var(--text-shadow-on-wall);
  white-space: normal;
  overflow-wrap: anywhere;
}

/* An empty title (a view whose heading has not loaded yet) must not leave a blank line. */
.app-back-bar > .app-toolbar-title:empty {
  display: none;
}

/* A settings page's one title is the same size as a top-level page title. */
.settings-detail .app-back-bar > .app-toolbar-title {
  font: var(--w-body) var(--text-page-title)/1.15 var(--font-display);
  letter-spacing: var(--title-tracking);
}

/* The count beside a title and the trailing text of a bar read as on-wall captions too. */
.app-back-bar > .app-row-meta {
  color: var(--ink-on-wall-muted);
  text-shadow: var(--text-shadow-on-wall);
}

/* A card with nothing in it is a dark empty bar on the wallpaper: a list container that has
   no rows (a failed feed, a queue not yet loaded) hides the card around it. Only a list or
   text container with nothing in it (ul, ol, p, div) or a [hidden] child counts as empty; a
   bare :empty would also match an img, canvas or input, which are content. */
.app-card:empty,
.app-card:has(> :empty:only-child),
.app-card:has(> *):not(:has(> :not([hidden], ul:empty, ol:empty, p:empty, div:empty))) {
  display: none;
}

/* A label above a card or list stays quiet and aligned to the page gutter. */
.app-section-label {
  margin: 0;
  color: var(--ink-on-wall-muted);
  font-size: var(--text-detail-meta);
  font-weight: var(--w-title);
  letter-spacing: 0.05em;
  text-shadow: var(--text-shadow-on-wall);
}

/* A card's own heading is the larger, plain title step and can share a row with a count or
   trailing action. */
.app-card-title {
  margin: 0;
  color: var(--ink);
  font-size: var(--text-detail-title);
  font-weight: var(--w-title);
  letter-spacing: normal;
  text-transform: none;
}

.app-disclosure {
  display: flex;
  width: 100%;
  min-height: var(--touch-min);
  align-items: center;
  gap: 0;
  padding: 0;
  color: var(--ink);
  text-align: left;
  background: transparent;
  border: 0;
  font-size: var(--text-detail-body);
  font-weight: var(--w-title);
}

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

.app-disclosure-label {
  flex: 0 1 auto;
  min-width: 0;
}

.app-disclosure-count {
  flex: 0 0 auto;
  min-width: 0;
  white-space: pre;
  font-variant-numeric: tabular-nums;
}

.app-disclosure-chevron {
  flex: 0 0 auto;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: auto;
  margin-right: var(--space-r1);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--motion-press, 0.12s) ease;
}

.app-disclosure[aria-expanded="true"] .app-disclosure-chevron {
  transform: rotate(225deg);
}

.app-disclosure:hover,
.app-disclosure:focus-visible {
  background: var(--panel);
}

.app-disclosure-section {
  color: var(--ink-on-wall-muted);
  font-size: var(--text-detail-meta);
  font-weight: var(--w-title);
  letter-spacing: 0.05em;
  text-shadow: var(--text-shadow-on-wall);
}

@media (prefers-reduced-motion: reduce) {
  .app-disclosure-chevron {
    transition: none;
  }
}

/* A list row that is also a control: no chrome of its own beyond a hover/focus wash and the
   same brief press feedback every other pressable control gives (.app-quiet-button and
   .app-primary-button's own scale(0.98), --motion-press). One rule here, rather than one per
   caller, covers every row this primitive composes onto -- Today's own openers, a podcast
   episode or group, a meal's opener, an agenda event or task's own toggle. */
.app-row {
  display: flex;
  width: 100%;
  min-height: 0;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.35rem 0.3rem;
  color: var(--ink);
  text-align: left;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
  font: inherit;
  transition: transform var(--motion-press) ease, background var(--motion-press) ease;
}

.app-row-link {
  min-height: var(--touch-min);
  align-items: center;
}

.app-row-link-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
}

.app-row-link-title[hidden] {
  display: none;
}

.app-row-link-icon {
  flex: none;
  width: var(--text-control);
  height: var(--text-control);
  margin-top: var(--space-2xs);
  color: var(--muted);
}

.app-row-link .app-row-title {
  font-size: var(--text-support);
  font-weight: var(--w-title);
}

.app-row-link .app-row-meta {
  font-size: var(--text-caption);
}

.app-row:hover,
.app-row:focus-visible {
  background: var(--panel);
}

.app-row:active {
  transform: scale(0.98);
}

/* Rows inside a divider list start at the card's own padding, so text, dividers and the trailing slot share one inset. */
.app-divider-list > .app-row {
  padding-inline: 0;
}

@media (prefers-reduced-motion: reduce) {
  .app-row:active {
    transform: none;
  }
}

/* Row text keeps its type here; only actual row/list entries below receive the bounded shape. */
.app-row-title {
  margin: 0;
  font-size: var(--text-detail-body);
  font-weight: var(--w-title);
  line-height: var(--leading-detail-tight);
  min-width: 0;
  overflow-wrap: anywhere;
}

.app-row-meta {
  margin: 0.1rem 0 0;
  color: var(--muted);
  font-size: var(--text-detail-meta);
  line-height: var(--leading-detail-tight);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Identifiers (email, URL, device name, token) show every line without the row-title line clamp; only an unbreakable segment wider than the line is cut with one ellipsis. */
.app-identifier {
  min-width: 0;
  overflow: hidden;
  overflow-wrap: normal;
  word-break: normal;
  text-overflow: ellipsis;
}

.app-row .app-row-title.app-identifier,
.app-divider-list > * .app-row-title.app-identifier,
.app-card-list > * .app-row-title.app-identifier {
  -webkit-line-clamp: none;
  line-clamp: none;
}

.app-on-wallpaper.app-row-meta,
.app-on-wallpaper.app-status {
  color: var(--muted-on-wallpaper);
  text-shadow: var(--text-shadow-on-wall);
}

.app-row .app-row-title,
.app-divider-list > * .app-row-title,
.app-card-list > * .app-row-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--row-title-lines);
  line-clamp: var(--row-title-lines);
  overflow: hidden;
}

.app-row .app-row-meta,
.app-divider-list > * .app-row-meta,
.app-card-list > * .app-row-meta {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--row-meta-lines);
  line-clamp: var(--row-meta-lines);
  overflow: hidden;
}

#household-person-email .app-row-meta {
  overflow-wrap: break-word;
  min-width: 0;
}

/* A date, time or count at the trailing edge that never shrinks. */
.app-row-trailing {
  flex: none;
  margin-left: auto;
  white-space: nowrap;
  text-align: right;
  color: var(--muted);
  font-size: var(--text-detail-meta);
  line-height: var(--leading-detail-tight);
}

/* A card source gives way after two lines while the lead and the trailing column are vertically centred on each other. */
.app-meta-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  min-width: 0;
}

.app-meta-lead {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--muted);
  font-size: var(--text-caption);
  line-height: var(--leading-detail-tight);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.app-meta-trail {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  white-space: nowrap;
  color: var(--muted);
  font-size: var(--text-caption);
  line-height: var(--leading-detail-tight);
}

.app-meta-bar[hidden],
.app-meta-lead[hidden],
.app-meta-trail[hidden] { display: none; }

/* Short dates and durations stay together when a row wraps. */
.app-nowrap {
  white-space: nowrap;
}

.app-row-actions {
  flex: none;
  display: flex;
  gap: 0.4rem;
  align-items: center;
  align-self: center;
}

/* Only an actions group with two or more worded buttons needs a line of its own at Large;
   a single worded button stays on the row, and icon-only groups stay aligned to the row. */
:root[data-text-size="large"] .app-row:has(> .app-row-actions > button:not(.app-icon-button) ~ button:not(.app-icon-button)) {
  flex-wrap: wrap;
}

:root[data-text-size="large"] .app-row-actions:has(> button:not(.app-icon-button) ~ button:not(.app-icon-button)) {
  flex-basis: 100%;
}

/* Calendar's day heading, event title and task title read as one restrained hierarchy
   instead of three same-looking titles: the day heading alone still steps up to
   --text-detail-title (see .agenda-day-heading, unchanged, 18px/700), and an event title
   reads at the shared row rule above (16px/600) with no escalation of its own and no rule of
   its own at all: the wrap it used to carry is stated once in that shared rule now, and an
   override left standing with nothing in it is only somewhere for a declaration to creep back
   in. A task is a plan, not a claim on the day the way an event is, so it is the one title
   here that steps down from the shared 600 -- to 500 rather than 400, the same weight Today's
   task row gives it (see the .today-row :has() rule), so a task reads alike on both
   displays it appears on. */
/* One step below its row title, because the row title does the naming. A task's own title
   is the second thing read in that row, not the first. */
.agenda-task-title {
  font-weight: var(--w-meta);
}

.app-event-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.app-setup-prompt {
  box-sizing: border-box;
  width: 100%;
  margin-bottom: var(--setup-prompt-gap);
}

.app-setup-prompt[hidden] { display: none; }

/* In a card's divider row the row's own padding is the rhythm; the page-level prompt gap would
   read as a second, doubled band under the button. */
.app-divider-list .app-setup-prompt {
  margin-bottom: 0;
}

/* A setup card's list resets the browser <ul> defaults; the button is the row. */
.app-setup-card > .app-divider-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.app-event-meta-when,
.app-event-meta-where {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--row-meta-lines);
  line-clamp: var(--row-meta-lines);
  overflow: hidden;
  overflow-wrap: anywhere;
  min-width: 0;
}

.app-event-meta-where {
  color: var(--muted);
}

.app-event-meta[hidden],
.app-event-meta-when[hidden],
.app-event-meta-where[hidden] { display: none; }

.agenda-event-meta,
.agenda-task-planned,
.agenda-task-repeats,
.agenda-task-time-meta {
  margin-top: var(--space-xs);
  font-size: var(--text-detail-body);
  line-height: var(--leading-detail-tight);
}

/* A planned-for-another-day task's own date label reads one step quieter than the row
   around it, through ink alone: the muted caption colour, applied evenly to every such
   label and never a state colour. A plan that has passed is still a plan -- the words
   stay exactly what agendaTaskPlannedLabel builds, and nothing here says otherwise. A timed
   task's own time meta (.agenda-task-time-meta, the reference's own plain "5:00 PM") reads
   the same quiet way -- a time is a datum, not a state, the same rule .agenda-event-time
   gives the event rail beside it. */
.agenda-task-planned,
.agenda-task-time-meta {
  color: var(--muted);
}

.app-tag {
  display: inline-flex;
  align-items: center;
  padding: 0.02rem 0.35rem;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-nested);
  font-size: var(--text-detail-meta);
  font-weight: var(--w-meta);
  line-height: var(--leading-detail-tight);
  vertical-align: middle;
}

.app-tag-positive {
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 45%, var(--line));
}

.app-tag-attention {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
}

.app-tag-dot::before {
  content: '';
  width: 6px;
  height: 6px;
  flex: none;
  margin-right: 0.3rem;
  border-radius: 50%;
  background: currentColor;
}

/* The same marker with a ground under it, for a mark that has to be found in a dense
   list rather than read beside a title -- a calendar cell's chips and its "+N more",
   a library type badge. Written four times before this pass at three different radii,
   which is the difference between a marker you recognise everywhere and four things
   that happen to be small. */
.app-tag-solid {
  background: var(--panel);
  border-color: transparent;
}

/* The agenda's tags sit directly after a title with no gap of their own, so they carry
   the gap; a tag that follows its own row's gap (Email Review's kinds) does not. */
.agenda-event-overlap-tag,
.agenda-event-repeats-tag {
  margin-left: 0.4rem;
}

/* A quiet nested block: panel surface at the small reading size. */
.app-inset {
  margin: 0 0 0.4rem;
  padding: 0.45rem 0.55rem;
  color: var(--muted);
  background: var(--panel);
  border-radius: var(--radius-control);
  font-size: var(--text-detail-meta);
}

.app-quiet-button,
#calendar-reminder-enable,
.calendar-notifications {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: var(--control-pad-y) var(--control-pad-x);
  color: var(--ink);
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
  font: inherit;
  font-size: var(--text-detail-body);
  font-weight: var(--w-meta);
  transition: transform var(--motion-press) ease, background var(--motion-press) ease;
}

/* The one range slider: the podcast, weather radar and recipe servings sliders share it. */
.app-range {
  flex: 1 1 8rem;
  min-width: 0;
  accent-color: var(--pane-accent, var(--accent));
}

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

.app-quiet-button {
  color: var(--accent-ink);
  text-decoration: none;
}

.app-quiet-button:hover,
.app-quiet-button:focus-visible,
#calendar-reminder-enable:hover,
#calendar-reminder-enable:focus-visible,
.calendar-notifications:hover,
.calendar-notifications:focus-visible {
  background: var(--panel);
}

.app-quiet-button:active,
#calendar-reminder-enable:active,
.calendar-notifications:active {
  transform: scale(0.98);
}

/* The group wraps together at every text size. */
.app-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-r2);
}

/* The gap after a list or empty state belongs to this shared rule; pages must not add their own '<list> + .app-actions' margin. */
.app-empty + .app-actions,
.app-divider-list + .app-actions,
.app-card-list + .app-actions {
  margin-top: var(--space-sm);
}

.app-actions:has(> .app-link) {
  row-gap: var(--space-xs);
}

/* A worded action inside a group is a filled control, never a bare caption. --panel-inset is
   a step off the card in both themes, and the hairline is what carries the edge where that
   step all but disappears: in the light theme the card and the inset sit a few RGB points
   apart, and a settings-dialog card's own ground IS --panel-inset. A quiet button set loose
   -- a back bar, a toolbar, a row's own trailing slot -- keeps the transparent box above, so
   a bar still reads as a bar and not as a row of chips. Radius comes from the shared box. */
.app-actions > .app-quiet-button,
.settings-row-actions > .app-quiet-button {
  min-height: var(--touch-min);
  background: var(--panel-inset);
  border: 1px solid var(--line-strong);
}

.app-actions > .app-quiet-button:hover,
.app-actions > .app-quiet-button:focus-visible,
.settings-row-actions > .app-quiet-button:hover,
.settings-row-actions > .app-quiet-button:focus-visible {
  /* --surface-high is the shared hover wash's own colour in the light theme and one step
     brighter than the inset in the dark one, so a filled chip brightens on hover exactly as
     the transparent quiet button always has. */
  background: var(--surface-high);
}

.app-link {
  color: var(--accent-ink);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 0.15em;
}

.app-link:hover,
.app-link:focus-visible {
  color: var(--accent-ink);
  background: var(--panel);
}

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

/* The filled treatment the brief's control vocabulary asks for beside the quiet button
   above and the icon/overflow button below: the pane's ONE clearly primary action --
   never a row's routine action (Dismiss, Later, Save), which stays quiet, and never
   every button on a pane, which is exactly the "outlined text button everywhere" the
   brief's audit found. Same box as .app-quiet-button (min-height, radius, padding) so
   the two read as one family at two weights, not two different controls. */
.app-primary-button {
  /* --touch-min (44px), not --control-min: this is the one control the brief calls out
     for an "intentional action" floor of its own, matching the reference's own gold
     button exactly rather than a control-min literal near it. */
  min-height: var(--touch-min);
  padding: var(--control-pad-y) var(--control-pad-x);
  color: var(--on-accent);
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-control);
  font: inherit;
  font-size: var(--text-detail-body);
  font-weight: var(--w-title);
  text-decoration: none;
  transition: transform var(--motion-press) ease, background var(--motion-press) ease;
}

.app-primary-button:hover,
.app-primary-button:focus-visible {
  background: var(--accent-hover);
}

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

.app-primary-button:disabled {
  color: var(--muted);
  background: var(--panel);
}

/* An icon plus one word, in the same pill as any other quiet button. Selected is the
   shared .app-selected and not a colour of its own -- the bell inside stays in the
   button's own ink (see .calendar-notifications svg below), never a colour it owns. */
.calendar-notifications {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  flex: none;
  /* Sized with the Calendar bar it lives in rather than with the detail scale, for the reason
     spelled out on .email-review-toggle strong: this row is the app's tightest at 390px, and
     the width this control takes comes straight out of the note beside it. */
  font-size: var(--text-detail-meta);
  margin-right: var(--space-r2);
}

.calendar-notifications svg {
  width: 16px;
  height: 16px;
  flex: none;
  stroke: currentColor;
  fill: none;
}

/* The pill: the quiet button's box, round. Its 500 weight is deliberate -- this is a
   navigation action rather than a quiet secondary one, so it keeps the heading weight the
   quiet button steps down from. A selector chip is the one caller now: Calendar's
   Today control, this primitive's original reason for existing, moved onto the wallpaper
   itself when the header did (see .app-top-button below) and no longer sits on a card
   ground a bordered pill would read against. */
.app-pill {
  flex: none;
  min-height: var(--touch-min);
  padding: var(--control-pad-y) var(--control-pad-x);
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  font: inherit;
  font-size: var(--text-detail-body);
  font-weight: var(--w-title);
}

.app-pill:hover,
.app-pill:focus-visible {
  background: var(--panel);
}

/* The reference's own gp-top-button: a secondary action set directly on the wallpaper,
   beside a page's own title, on a translucent ground rather than a card. Calendar's Today
   control is the first caller. */
.app-top-button {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: var(--space-lg) 13px;
  color: var(--ink-heading);
  background: var(--top-button-ground);
  border: 0;
  border-radius: var(--radius-control);
  font: inherit;
  font-size: var(--text-caption);
  font-weight: var(--w-title);
  box-shadow: var(--shadow-inset-highlight);
}

.app-top-button:hover,
.app-top-button:focus-visible {
  background: var(--top-button-ground-hover);
}

.app-top-button:disabled {
  opacity: 0.6;
}

.app-top-button[hidden] {
  display: none;
}

/* A square icon-only header action shares the top button's wallpaper surface and focus
   treatment while keeping its icon target consistent across page headers. */
.app-icon-button {
  flex: none;
  width: var(--touch-min);
  min-height: var(--touch-min);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-heading);
  background: var(--top-button-ground);
  border: 0;
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-inset-highlight);
}

.app-icon-button:hover,
.app-icon-button:focus-visible {
  background: var(--top-button-ground-hover);
}

.app-icon-button:disabled {
  opacity: 0.6;
}

.app-icon-button[hidden] {
  display: none;
}

.app-icon-button svg {
  width: 20px;
  height: 20px;
  stroke-width: 2;
}

.app-page-header-title + .app-icon-button {
  margin-left: auto;
}
