/* ------------------------------------------------------------------
   base: page frame, reset, type helpers, component tokens, motion
   ------------------------------------------------------------------ */
:root {
  --page: 640px;
  --gutter: 16px;
  --top-bar-h: 64px;

  --sa-t: env(safe-area-inset-top, 0px);
  --sa-b: env(safe-area-inset-bottom, 0px);
  --sa-l: env(safe-area-inset-left, 0px);
  --sa-r: env(safe-area-inset-right, 0px);
  --gx-l: max(var(--gutter), var(--sa-l));
  --gx-r: max(var(--gutter), var(--sa-r));
}

*, *::before, *::after { box-sizing: border-box; }
/* Reset only what the UA actually gives us, and only on elements we author.
   A blanket `* { margin: 0; padding: 0 }` is not safe here: a declaration in the
   document tree beats a component's `:host` and `::slotted()` rules whatever the
   specificity (tree context is sorted first), so it silently strips Material
   Web's own spacing - md-dialog centres itself with `margin: auto` from :host
   and pads its headline/content/actions through ::slotted. */
h1, h2, h3, h4, p, ul, ol, dl, dt, dd, figure, figcaption, form, fieldset, blockquote, pre, hr, table { margin: 0; }
ul, ol { padding-left: 0; }
button { padding: 0; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
  min-height: 100dvh;
  background: var(--md-sys-color-surface);
  color: var(--md-sys-color-on-surface);
  font: var(--md-sys-typescale-body-medium-weight) var(--md-sys-typescale-body-medium-size) /
        var(--md-sys-typescale-body-medium-line-height) var(--md-ref-typeface-plain);
  letter-spacing: var(--md-sys-typescale-body-medium-tracking);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  overscroll-behavior-y: none;
}
a { color: inherit; text-decoration: none; -webkit-tap-highlight-color: transparent; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
img { display: block; max-width: 100%; }
ul, ol { list-style: none; }
h1, h2, h3 { font-size: inherit; font-weight: inherit; }
svg { display: block; }
:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }
::selection { background: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }
/* hidden must beat a component's own :host display */
[hidden] { display: none !important; }

.shell { width: 100%; max-width: var(--page); margin: 0 auto; padding-left: var(--gx-l); padding-right: var(--gx-r); }

/* Page content gets its own stacking context. Measured, not guessed: without it
   the tag chips in the release header paint *over* the sticky app bar while the
   page scrolls (elementsFromPoint at y=56 inside the 64px bar returned the chip,
   not the header), so the tags showed through the bar. Isolating #app confines
   everything to it and the bar's z-index resolves against it. The bar stays at
   z-index 1 because md-dialog's scrim is position:fixed z-index:1 in the root
   context and wins on tree order - raising it would put the bar over the scrim. */
#app { isolation: isolate; }

/* type-scale helpers for the text Material Web does not own */
.t-display-small { font: var(--md-sys-typescale-display-small-weight) var(--md-sys-typescale-display-small-size) / var(--md-sys-typescale-display-small-line-height) var(--md-ref-typeface-brand); letter-spacing: var(--md-sys-typescale-display-small-tracking); }
.t-headline-large { font: var(--md-sys-typescale-headline-large-weight) var(--md-sys-typescale-headline-large-size) / var(--md-sys-typescale-headline-large-line-height) var(--md-ref-typeface-brand); letter-spacing: var(--md-sys-typescale-headline-large-tracking); }
.t-title-large { font: var(--md-sys-typescale-title-large-weight) var(--md-sys-typescale-title-large-size) / var(--md-sys-typescale-title-large-line-height) var(--md-ref-typeface-brand); letter-spacing: var(--md-sys-typescale-title-large-tracking); }
.t-title-medium { font: var(--md-sys-typescale-title-medium-weight) var(--md-sys-typescale-title-medium-size) / var(--md-sys-typescale-title-medium-line-height) var(--md-ref-typeface-brand); letter-spacing: var(--md-sys-typescale-title-medium-tracking); }
.t-title-small { font: var(--md-sys-typescale-title-small-weight) var(--md-sys-typescale-title-small-size) / var(--md-sys-typescale-title-small-line-height) var(--md-ref-typeface-brand); letter-spacing: var(--md-sys-typescale-title-small-tracking); }
.t-body-large { font: var(--md-sys-typescale-body-large-weight) var(--md-sys-typescale-body-large-size) / var(--md-sys-typescale-body-large-line-height) var(--md-ref-typeface-plain); letter-spacing: var(--md-sys-typescale-body-large-tracking); }
.t-body-medium { font: var(--md-sys-typescale-body-medium-weight) var(--md-sys-typescale-body-medium-size) / var(--md-sys-typescale-body-medium-line-height) var(--md-ref-typeface-plain); letter-spacing: var(--md-sys-typescale-body-medium-tracking); }
.t-body-small { font: var(--md-sys-typescale-body-small-weight) var(--md-sys-typescale-body-small-size) / var(--md-sys-typescale-body-small-line-height) var(--md-ref-typeface-plain); letter-spacing: var(--md-sys-typescale-body-small-tracking); }
.t-label-large { font: var(--md-sys-typescale-label-large-weight) var(--md-sys-typescale-label-large-size) / var(--md-sys-typescale-label-large-line-height) var(--md-ref-typeface-plain); letter-spacing: var(--md-sys-typescale-label-large-tracking); }
.t-label-medium { font: var(--md-sys-typescale-label-medium-weight) var(--md-sys-typescale-label-medium-size) / var(--md-sys-typescale-label-medium-line-height) var(--md-ref-typeface-plain); letter-spacing: var(--md-sys-typescale-label-medium-tracking); }

.muted { color: var(--md-sys-color-on-surface-variant); }
.truncate { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* section label above a card: M3 "list subheader", neutral not accent */
.subhead {
  display: flex; align-items: baseline; gap: var(--md-sys-space-200);
  padding: 0 var(--md-sys-space-400) var(--md-sys-space-200);
  color: var(--md-sys-color-on-surface-variant);
}

.empty { padding: var(--md-sys-space-600) var(--md-sys-space-400); text-align: center; }
.empty h3 { margin-bottom: var(--md-sys-space-100); color: var(--md-sys-color-on-surface); }
.empty p { color: var(--md-sys-color-on-surface-variant); }
.empty md-filled-button { margin-top: var(--md-sys-space-500); }

/* ------------------------------------------------------------------
   Material Web: component-level token overrides
   One accent (primary) for anything interactive or a status; everything
   else stays on surface / on-surface-variant / outline.
   ------------------------------------------------------------------ */
/* <md-icon> still owns the sizing; the glyph is an inline Feather svg
   (assets/js/icons.js), so there is no icon font and nothing to wait for.
   The document tree also carries the box, so an md-icon that has not upgraded
   yet (slow CDN) still reserves its M3 square instead of collapsing to the
   svg's intrinsic size. Same values the component sets on :host. */
md-icon { --md-icon-size: 24px; display: inline-flex; width: var(--md-icon-size); height: var(--md-icon-size); }
md-icon-button { display: inline-flex; }
md-icon > svg { width: 100%; height: 100%; flex: none; }

/* nothing scrolls or takes taps while the splash is up */
body:has(.boot:not([hidden])) { overflow: hidden; }

/* Feather's contract, restated in CSS. md-icon carries ::slotted(svg){fill:currentColor}
   for icon *fonts*; that is a style rule, so it beats the fill="none" presentation
   attribute on our svgs and filled every shape solid. Author CSS wins here. */
.ico {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* lists inside a card must not paint their own surface */
md-outlined-card md-list { --md-list-container-color: transparent; }

/* Chips come in two roles. `tag-beta`/`status` is the one status tag the site
   shows (Beta; experimental reads Beta) and must look the same everywhere it
   appears. `meta` is the quiet metadata beside it - otherwise a status tag just
   reads as another category. The chip component has no fill token (outline
   only), so the tag is carried by its label and outline colour. */
md-assist-chip.tag-beta,
md-assist-chip.status {
  --md-assist-chip-container-height: 24px;
  --md-assist-chip-label-text-size: var(--md-sys-typescale-label-small-size);
  --md-assist-chip-label-text-line-height: var(--md-sys-typescale-label-small-line-height);
  --md-assist-chip-label-text-weight: var(--md-ref-typeface-weight-semibold);
  --md-assist-chip-label-text-color: var(--md-sys-color-primary);
  --md-assist-chip-outline-color: var(--md-sys-color-primary);
  --md-assist-chip-leading-space: var(--md-sys-space-200);
  --md-assist-chip-trailing-space: var(--md-sys-space-200);
}
md-assist-chip.meta {
  --md-assist-chip-container-height: 24px;
  --md-assist-chip-label-text-size: var(--md-sys-typescale-label-small-size);
  --md-assist-chip-label-text-line-height: var(--md-sys-typescale-label-small-line-height);
  --md-assist-chip-label-text-weight: var(--md-ref-typeface-weight-medium);
  --md-assist-chip-label-text-color: var(--md-sys-color-on-surface-variant);
  --md-assist-chip-outline-color: var(--md-sys-color-outline);
  --md-assist-chip-leading-space: var(--md-sys-space-200);
  --md-assist-chip-trailing-space: var(--md-sys-space-200);
}
/* "New" is a dot, not a chip, so it can never change the row height. */
.dot {
  flex: none; width: 8px; height: 8px; border-radius: var(--md-sys-shape-corner-full);
  background: var(--md-sys-color-primary);
}
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* Device filter: selected chip uses the one accent, unselected stays neutral. */
md-filter-chip {
  --md-filter-chip-selected-container-color: var(--md-sys-color-primary-container);
  --md-filter-chip-selected-label-text-color: var(--md-sys-color-on-primary-container);
  --md-filter-chip-selected-hover-state-layer-color: var(--md-sys-color-on-primary-container);
  --md-filter-chip-selected-pressed-state-layer-color: var(--md-sys-color-on-primary-container);
  --md-filter-chip-selected-outline-width: 0px;
  --md-filter-chip-outline-color: var(--md-sys-color-outline);
  --md-filter-chip-label-text-color: var(--md-sys-color-on-surface-variant);
}

/* the outbound-link alert is a plain M3 dialog on the surface container role.
   md-dialog centres itself with `margin: auto` inherited from its own :host, so
   nothing in the document tree may set a margin on it. */
md-dialog { margin: auto; }
md-dialog.alert { --md-dialog-container-color: var(--md-sys-color-surface-container-high); }
#alert-title { overflow-wrap: anywhere; word-break: break-word; }
.alert__host {
  margin-top: var(--md-sys-space-200);
  color: var(--md-sys-color-on-surface-variant);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.alert__progress {
  width: 100%;
  margin-top: var(--md-sys-space-300);
}

/* ------------------------------------------------------------------
   text motion — M3 easing tokens only: one move in, no blur
   ------------------------------------------------------------------ */
.w { display: inline-block; white-space: nowrap; }
.ch, .dg, .rl { display: inline-block; }
.ch {
  animation: rise-t var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate) both;
  animation-delay: var(--d, 0ms);
}
@keyframes rise-t { from { opacity: 0; transform: translateY(0.45em) scale(0.9); } }

.num { display: inline-flex; overflow: hidden; vertical-align: bottom; line-height: 1.2; }
.dg { animation: dg-in var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate) both; animation-delay: var(--d, 0ms); }
@keyframes dg-in { from { transform: translateY(110%); opacity: 0; } }
.rl--in { animation: rl-in var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate) both; }
@keyframes rl-in { from { transform: translateY(calc(var(--dir, 1) * 90%)); opacity: 0; } }

.in { animation: in var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate) both; animation-delay: calc(min(var(--i, 0), 6) * 45ms); }
@keyframes in { from { opacity: 0; transform: translateY(14px) scale(0.99); } }

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