/* Island Reply tokens, hand-compiled from ds/project/tokens.json in the shape the page compiles.
   For local render checks and as the model for the site's production tokens.css.
   Light first, dark second. Values from ds-ref/direction.md sections 3, 4 and 5. */

:root, [data-theme="light"] {
  --ground: #f2f5f7;
  --ground-raised: #ffffff;
  --ground-sunken: #e3e9ed;
  --ground-band: #0f2433;
  --ground-ticket: #f4efe4;
  --line: #c9d4db;
  --line-strong: #74899a;
  --ink: #122230;
  --ink-muted: #4a5c69;
  --ink-on-band: #eef4f7;
  --ink-on-band-muted: #a9bbc7;
  --deep: #0e4f78;
  --reef: #2a8db4;
  --dirt: #9e4a2b;
  --ohia: #c1322e;
  --on-deep: #ffffff;
  --on-dirt: #ffffff;
  --signal: #f5b700;
  --on-signal: #122230;
  --urgent: #b3261e;
  --on-urgent: #ffffff;
  --urgent-soft: #fbe4e1;
  --booked: #6b4700;
  --booked-soft: #ffefb8;
  --bubble-in: #e3e9ed;
  --bubble-in-ink: #122230;
  --bubble-out: #0e4f78;
  --bubble-out-ink: #ffffff;
  --link: #0f5e8c;
  --focus: #1e7fa4;
  --ridge: #1b3a52;
  --reef-soft: #dceef4;
  --dirt-soft: #f6e4da;
  --deep-strong: #0a3d5e;
  --shadow-card: 0 1px 0 rgba(18,34,48,0.06), 0 10px 24px -14px rgba(18,34,48,0.28);
  --shadow-float: 0 2px 0 rgba(18,34,48,0.06), 0 24px 48px -20px rgba(18,34,48,0.35);
}

[data-theme="dark"] {
  --ground: #0b151d;
  --ground-raised: #13222d;
  --ground-sunken: #070f15;
  --ground-band: #0e3a57;
  --ground-ticket: #221f19;
  --line: #27394a;
  --line-strong: #55707f;
  --ink: #e8eff3;
  --ink-muted: #9fb3c1;
  --ink-on-band: #eaf2f6;
  --ink-on-band-muted: #a7c1d1;
  --deep: #2272a6;
  --reef: #5fb6da;
  --dirt: #d8794f;
  --ohia: #e0564f;
  --on-deep: #ffffff;
  --on-dirt: #0b151d;
  --signal: #f5b700;
  --on-signal: #0b151d;
  --urgent: #f0655c;
  --on-urgent: #0b151d;
  --urgent-soft: #3a1715;
  --booked: #f2ce5a;
  --booked-soft: #33290a;
  --bubble-in: #1c2e3b;
  --bubble-in-ink: #e8eff3;
  --bubble-out: #2272a6;
  --bubble-out-ink: #ffffff;
  --link: #7cc4e8;
  --focus: #7cc4e8;
  --ridge: #3d7597;
  --reef-soft: #143240;
  --dirt-soft: #3a2219;
  --deep-strong: #1a5f8c;
  --shadow-card: 0 1px 0 rgba(0,0,0,0.5), 0 12px 28px -16px rgba(0,0,0,0.75);
  --shadow-float: 0 2px 0 rgba(0,0,0,0.6), 0 28px 56px -22px rgba(0,0,0,0.85);
}

/* Follow the OS when no data-theme is set. An explicit data-theme="light" wins. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0b151d;
    --ground-raised: #13222d;
    --ground-sunken: #070f15;
    --ground-band: #0e3a57;
    --ground-ticket: #221f19;
    --line: #27394a;
    --line-strong: #55707f;
    --ink: #e8eff3;
    --ink-muted: #9fb3c1;
    --ink-on-band: #eaf2f6;
    --ink-on-band-muted: #a7c1d1;
    --deep: #2272a6;
    --reef: #5fb6da;
    --dirt: #d8794f;
    --ohia: #e0564f;
    --on-deep: #ffffff;
    --on-dirt: #0b151d;
    --signal: #f5b700;
    --on-signal: #0b151d;
    --urgent: #f0655c;
    --on-urgent: #0b151d;
    --urgent-soft: #3a1715;
    --booked: #f2ce5a;
    --booked-soft: #33290a;
    --bubble-in: #1c2e3b;
    --bubble-in-ink: #e8eff3;
    --bubble-out: #2272a6;
    --bubble-out-ink: #ffffff;
    --link: #7cc4e8;
    --focus: #7cc4e8;
    --ridge: #3d7597;
    --reef-soft: #143240;
    --dirt-soft: #3a2219;
    --deep-strong: #1a5f8c;
    --shadow-card: 0 1px 0 rgba(0,0,0,0.5), 0 12px 28px -16px rgba(0,0,0,0.75);
    --shadow-float: 0 2px 0 rgba(0,0,0,0.6), 0 28px 56px -22px rgba(0,0,0,0.85);
  }
}

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;
  --radius-xs: 2px;
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;
  --radius-pill: 999px;
  --font-display: "Barlow Semi Condensed", "Arial Narrow", "Helvetica Neue Condensed", system-ui, sans-serif;
  --font-body: "Public Sans", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
}

/* Type styles. Sizes are the 48rem-and-up column of direction.md section 4;
   00-base.css sets the mobile sizes (display-xl 40, display-lg 32, heading-2 26, heading-3 20, lead 18, body 17). */

.display-xl {
  font-family: var(--font-display);
  font-size: 64px;
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.015em;
}

.display-lg {
  font-family: var(--font-display);
  font-size: 48px;
  line-height: 1.04;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.heading-2 {
  font-family: var(--font-display);
  font-size: 32px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.005em;
}

.heading-3 {
  font-family: var(--font-display);
  font-size: 22px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
}

.lead {
  font-family: var(--font-body);
  font-size: 20px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
}

.body {
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.55;
  font-weight: 400;
  letter-spacing: 0;
}

.body-sm {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
}

.caption {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
}

.eyebrow {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.label {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.meta-mono {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Island Reply: components/bundle.css. Assembled from partials; load after tokens.css. No build step. */

/* ===== 00-base ===== */
/* ==========================================================================
   Island Reply design system — 00-base.css
   Global layer. Loads after tokens.css on every page and in every preview.
   Owns: motion properties, reset, headings, links, focus, a11y helpers,
   page rhythm (.ir-wrap / .ir-section / .ir-band), figure captions,
   the tabular-figures utility, scene pausing, reduced motion, print.
   Colors come only from tokens (var(--…)). Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Motion custom properties (direction.md §8)
   Three durations, one easing family. --ease-stamp is the only overshoot.
   -------------------------------------------------------------------------- */
:root {
  --dur-1: 160ms;
  --dur-2: 240ms;
  --dur-3: 480ms;
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-stamp: cubic-bezier(0.3, 1.3, 0.5, 1);
  /* Page gutter: 16px below 48rem, 32px above (direction.md §5). */
  --gutter: var(--space-4);
}

@media (min-width: 48rem) {
  :root {
    --gutter: var(--space-8);
  }
}

/* --------------------------------------------------------------------------
   2. Reset
   body = the `body` type style: 17px / 1.55 on phones, 18px at 48rem.
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

@media (min-width: 48rem) {
  body {
    font-size: 18px;
  }
}

p {
  text-wrap: pretty;
}

/* No italics anywhere; emphasis is weight 600 (direction.md §4). */
strong,
b,
em,
i {
  font-weight: 600;
  font-style: normal;
}

figure {
  margin: 0;
}

svg {
  display: block;
}

img {
  max-width: 100%;
  height: auto;
}

/* --------------------------------------------------------------------------
   3. Headings (direction.md §4 type table)
   h1 = display-xl, h2 = display-lg, h3 = heading-3. Each size is a clamp()
   that reads the mobile size at 360px and the ≥48rem size at 768px.
   .ir-h--2 = heading-2 for an h2 that needs the smaller style.
   -------------------------------------------------------------------------- */
h1,
h2,
h3,
.ir-h--2 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  color: inherit;
  text-wrap: balance;
}

/* display-xl: 40px → 64px, line-height 1.00, -0.015em */
h1 {
  font-size: clamp(2.5rem, 1.1765rem + 5.8824vw, 4rem);
  line-height: 1;
  letter-spacing: -0.015em;
}

/* display-lg: 32px → 48px, line-height 1.04, -0.01em */
h2 {
  font-size: clamp(2rem, 1.1176rem + 3.9216vw, 3rem);
  line-height: 1.04;
  letter-spacing: -0.01em;
}

/* heading-3: 20px → 22px, line-height 1.20, weight 600 */
h3 {
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
}

/* heading-2: 26px → 32px, line-height 1.10, -0.005em */
.ir-h--2 {
  font-size: clamp(1.625rem, 1.2941rem + 1.4706vw, 2rem);
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.005em;
}

/* Type-style classes from tokens.css carry the ≥48rem sizes only; the same
   mobile-to-desktop scaling applies when a page uses the class instead of
   the element. Display styles scale with the viewport, text styles step
   at 48rem like the body element. */
.display-xl {
  font-size: clamp(2.5rem, 1.1765rem + 5.8824vw, 4rem);
  text-wrap: balance;
}

.display-lg {
  font-size: clamp(2rem, 1.1176rem + 3.9216vw, 3rem);
  text-wrap: balance;
}

.heading-2 {
  font-size: clamp(1.625rem, 1.2941rem + 1.4706vw, 2rem);
  text-wrap: balance;
}

.heading-3 {
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  text-wrap: balance;
}

/* lead 18px → 20px; body 17px → 18px (text-wrap: pretty) */
.lead {
  font-size: 18px;
  text-wrap: pretty;
}

.body {
  font-size: 17px;
  text-wrap: pretty;
}

@media (min-width: 48rem) {
  .lead {
    font-size: 20px;
  }

  .body {
    font-size: 18px;
  }
}

/* Every meta-mono element lines its figures up and slashes its zeros. */
.meta-mono {
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
}

/* --------------------------------------------------------------------------
   4. Links
   `link` text with a 2px underline of the same ink. On hover the underline
   takes currentColor (visible change on the band, where the rest underline
   is `reef`). No transition: hover transitions are background-color only.
   -------------------------------------------------------------------------- */
a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
}

a:hover {
  text-decoration-color: currentColor;
}

/* --------------------------------------------------------------------------
   5. Focus (direction.md §3 focus ring rule, verbatim)
   Same ring on every ground, both themes. Appears instantly.
   -------------------------------------------------------------------------- */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
  transition: none;
}

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

/* Skip link: off-screen until focused, then a deep primary button top-left
   (label style at 16px / 600, min-height 48px, pressed-key bottom border). */
.ir-skip-link {
  position: absolute;
  top: -100px;
  left: var(--gutter);
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: var(--space-3) var(--space-5);
  background: var(--deep);
  color: var(--on-deep);
  border-bottom: 2px solid var(--deep-strong);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
}

.ir-skip-link:focus {
  top: var(--space-2);
}

/* Anchored sections stop a little below the viewport top. */
[id] {
  scroll-margin-top: var(--space-4);
}

/* --------------------------------------------------------------------------
   7. Page rhythm (direction.md §5)
   -------------------------------------------------------------------------- */

/* Wrap: 72rem of content plus the gutters. */
.ir-wrap {
  width: 100%;
  max-width: calc(72rem + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Section: space-16 padding-block on phones, space-24 at 48rem; hairline top. */
.ir-section {
  padding-block: var(--space-16);
  border-top: 1px solid var(--line);
}

@media (min-width: 48rem) {
  .ir-section {
    padding-block: var(--space-24);
  }
}

/* Band: the deep section ("Who it's for", footer). Always sits under a
   `ridge` strip, so it carries no border of its own. Text tokens re-map to
   their on-band twins; links are ink-on-band with a `reef` underline; the
   focus token is unchanged by rule (same ring on every ground). */
.ir-band {
  --ink: var(--ink-on-band);
  --ink-muted: var(--ink-on-band-muted);
  --link: var(--ink-on-band);
  background: var(--ground-band);
  color: var(--ink-on-band);
  border: 0;
}

.ir-band a {
  text-decoration-color: var(--reef);
}

.ir-band a:hover {
  text-decoration-color: currentColor;
}

/* Visible, selectable text (the contact email): one tap selects it all. */
.ir-select-all {
  -webkit-user-select: all;
  user-select: all;
}

/* --------------------------------------------------------------------------
   8. Figures
   .ir-figcaption = `caption` style in ink-muted ("Example only…" lines).
   -------------------------------------------------------------------------- */
.ir-figcaption {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   9. Tabular figures utility
   Tickets, the report, the dial, stubs and every meta-mono element reuse
   this so figures line up and zeros are slashed (direction.md §4).
   -------------------------------------------------------------------------- */
.ir-tnum {
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
}

/* --------------------------------------------------------------------------
   10. Scenes
   A self-hosted script sets data-paused when a scene is under 40% visible.
   Without the script, scenes simply play.
   -------------------------------------------------------------------------- */
.ir-scene[data-paused] * {
  animation-play-state: paused;
}

/* --------------------------------------------------------------------------
   11. Motion preference
   Reduced motion is the finished scene: every keyframe lives inside a
   no-preference block in its partial, and this kills anything that slipped.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none;
    transition: none;
    scroll-behavior: auto;
  }
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* --------------------------------------------------------------------------
   12. Print
   Paper has no theme: white ground, black ink are literal by intent
   (the one place a literal color is allowed in this system).
   -------------------------------------------------------------------------- */
@media print {
  body {
    background: #fff; /* print: paper, not a theme token */
    color: #000; /* print: ink on paper, not a theme token */
  }

  .ir-skip-link {
    display: none;
  }

  a {
    color: inherit;
  }
}


/* ===== Labels ===== */
/* ==========================================================================
   Island Reply design system — Labels.css
   The small labels every scene reuses: eyebrow, Example tag, URGENT pill,
   BOOKED stamp, quick-fact chip, dirt emphasis, mono meta.
   Shop-paper vocabulary (direction.md §4, §9): paper things are radius-xs,
   pills and chips are radius-pill, stamps rest at -4deg, every mono element
   lines its figures up and slashes its zeros. Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Eyebrow — `eyebrow` style, ink-muted.
   Inside .ir-band the base layer remaps --ink-muted to ink-on-band-muted,
   so the same rule reads right on the band with no override.
   -------------------------------------------------------------------------- */
.ir-eyebrow {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   2. The Example tag — eyebrow style in a 1.5px line-strong box, radius-xs
   (paper corners), padding 2px 6px. Top-right of every scene.
   --paper: the ticket-stub version on a dirt-soft field.
   -------------------------------------------------------------------------- */
.ir-tag {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: 2px 6px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-xs);
  background: transparent;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
}

.ir-tag--paper {
  background: var(--dirt-soft);
}

/* On the band the box takes the band's muted ink (line-strong is below 3:1
   on dark ground-band); the letters already remap through --ink-muted. */
.ir-band .ir-tag {
  border-color: var(--ink-on-band-muted);
}

/* --------------------------------------------------------------------------
   3. The URGENT pill — the only place `signal` appears. Safety-yellow field,
   on-signal meta-mono letters at 600, radius-pill, padding 2px 8px.
   Rule: one per component, three per page, never without `urgent` in the
   same component (and `urgent` never without it).
   -------------------------------------------------------------------------- */
.ir-pill,
.ir-pill--urgent {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  white-space: nowrap;
}

.ir-pill--urgent {
  background: var(--signal);
  color: var(--on-signal);
}

/* --------------------------------------------------------------------------
   4. The BOOKED stamp — booked-soft field, 2px booked outline, booked
   meta-mono letters at 600 with 0.08em tracking, radius-xs, resting skew
   rotate(-4deg). Flat at rest; --float adds shadow-float for the stamp
   that sits over a ticket corner (one of the three shadows in the system).
   -------------------------------------------------------------------------- */
.ir-stamp {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border: 2px solid var(--booked);
  border-radius: var(--radius-xs);
  background: var(--booked-soft);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--booked);
  white-space: nowrap;
  transform: rotate(-4deg);
  transform-origin: 50% 50%;
}

.ir-stamp--float {
  box-shadow: var(--shadow-float);
}

/* --------------------------------------------------------------------------
   5. The quick-fact chip — reef-soft field, `label` style, radius-pill,
   padding space-1 space-3, a 16px check drawn in CSS before the words.
   The check is the system's check icon at 16px: 1.5px stroke, currentColor,
   an L rotated 45deg inside a 16px cell (5 + 1.5 wide, 11 margin = 16).
   -------------------------------------------------------------------------- */
.ir-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--reef-soft);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-align: start;
}

.ir-chip::before {
  content: "";
  flex: none;
  box-sizing: content-box;
  width: 4.5px;
  height: 9px;
  margin: 0 5.5px 3px 5px;
  border: solid currentColor;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

/* On the band the chip inverts like the primary button: ink-on-band field,
   ground-band letters (reef-soft under ink-on-band would be unreadable). */
.ir-band .ir-chip {
  background: var(--ink-on-band);
  color: var(--ground-band);
}

/* --------------------------------------------------------------------------
   6. Inline emphasis — weight 600 in `dirt`, the only colored emphasis.
   `dirt` is never text on the band, so there it falls back to the band ink.
   -------------------------------------------------------------------------- */
.ir-em {
  font-weight: 600;
  font-style: normal;
  color: var(--dirt);
}

.ir-band .ir-em {
  color: var(--ink-on-band);
}

/* --------------------------------------------------------------------------
   7. Meta — `meta-mono`, ink-muted, tabular figures and slashed zero.
   Timestamps, "L1001", stub text, map labels.
   -------------------------------------------------------------------------- */
.ir-meta {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   8. Motion — the stamp landing (direction.md §8): 180ms on --ease-stamp,
   the only overshoot, from scale(1.12) rotate(-6deg) to the resting skew.
   Transform and opacity only. The resting rules above are the end state,
   so without the modifier, or with reduced motion, the stamp is simply down.
   The pill's second beat (the buzz) belongs to the owner alert, not here.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-stamp-land {
    from {
      transform: scale(1.12) rotate(-6deg);
      opacity: 0;
    }
    to {
      transform: scale(1) rotate(-4deg);
      opacity: 1;
    }
  }

  @keyframes ir-pill-land {
    from {
      transform: scale(1.12);
      opacity: 0;
    }
    to {
      transform: scale(1);
      opacity: 1;
    }
  }

  .ir-stamp--land {
    animation: ir-stamp-land 180ms var(--ease-stamp) both;
  }

  .ir-pill--land {
    animation: ir-pill-land 180ms var(--ease-stamp) both;
  }
}


/* ===== Button ===== */
/* ==========================================================================
   Island Reply design system — Button.css
   .ir-button: the pressed-key button (direction.md §4). Deep water fill,
   a 2px `deep-strong` bottom edge instead of a shadow, `label` type at
   16px / 600. One primary per view. Never a `dirt` or `signal` button.
   Variants: --primary (default), --secondary, --small, --large.
   Inside .ir-band the primary inverts (direction.md §3).
   .is-hover / .is-active mirror :hover / :active for previews only.
   ========================================================================== */

/* Base = primary. Border-box, so the 2px key edge sits inside min-height. */
.ir-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 48px;
  margin: 0;
  padding: var(--space-3) var(--space-5);
  border: 0;
  border-bottom: 2px solid var(--deep-strong);
  border-radius: var(--radius-sm);
  background: var(--deep);
  color: var(--on-deep);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  /* Hover is the only transition, and it is background-color only (direction.md §8). */
  transition: background-color 120ms var(--ease-out);
}

.ir-button:hover,
.ir-button.is-hover {
  background: var(--deep-strong);
  color: var(--on-deep);
}

/* Optional 16px icon beside the label (direction.md §9: 1.5px stroke at 16px,
   round caps, miter joins, currentColor). Never alone. */
.ir-button__icon {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: miter;
}

/* --------------------------------------------------------------------------
   Secondary: transparent, 2px `deep` outline, `ink` text, hover `reef-soft`.
   The outline keeps the same total height; no key edge.
   -------------------------------------------------------------------------- */
.ir-button--secondary {
  border: 2px solid var(--deep);
  background: transparent;
  color: var(--ink);
}

.ir-button--secondary:hover,
.ir-button--secondary.is-hover {
  background: var(--reef-soft);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   Sizes
   -------------------------------------------------------------------------- */
.ir-button--small {
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  font-size: 15px;
}

.ir-button--large {
  min-height: 54px;
  padding: var(--space-3) var(--space-6);
  font-size: 17px;
}

/* --------------------------------------------------------------------------
   On the band (`deep` on `ground-band` is 1.82, so the primary inverts):
   fill `ink-on-band`, text `ground-band`, key edge `ink-on-band-muted`.
   Hover fills with the muted tone (ground-band on it: 8.04 light, 6.35 dark).
   The secondary takes the band ink for its outline and text.
   -------------------------------------------------------------------------- */
.ir-band .ir-button,
.ir-band .ir-button--primary {
  border-bottom-color: var(--ink-on-band-muted);
  background: var(--ink-on-band);
  color: var(--ground-band);
}

.ir-band .ir-button:hover,
.ir-band .ir-button.is-hover,
.ir-band .ir-button--primary:hover,
.ir-band .ir-button--primary.is-hover {
  background: var(--ink-on-band-muted);
  color: var(--ground-band);
}

.ir-band .ir-button--secondary {
  border-color: var(--ink-on-band);
  background: transparent;
  color: var(--ink-on-band);
}

.ir-band .ir-button--secondary:hover,
.ir-band .ir-button--secondary.is-hover {
  /* translucent wash of the band ink; derived from the token, not a literal */
  background: color-mix(in srgb, var(--ink-on-band) 12%, transparent);
  color: var(--ink-on-band);
}

/* --------------------------------------------------------------------------
   Press: the key goes down 1px for as long as it is held. Transform only,
   no transition, and only when motion is welcome. With reduced motion the
   hover fill is the whole press feedback.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .ir-button:active,
  .ir-button.is-active {
    transform: translateY(1px);
  }
}


/* ===== Ridge ===== */
/* ==========================================================================
   Island Reply design system — Ridge.css
   The Koʻolau ridge divider (direction.md §7). One path, pitch 96px,
   peaks 24 / 15 / 20 / 12px above a valley that sits 6px over the
   baseline, each period rising over 64px and falling over 32px, steep
   side right. The markup tiles that path with an SVG <pattern> so the
   ridge never stretches; this file only sizes the strip and colors the
   land. The land never moves: no keyframes live here.
   ========================================================================== */

/* The strip. An inline <svg width="100%" height="30"> carrying the
   pattern and a full-width rect. Block so it stacks like a section edge. */
.ir-ridge {
  display: block;
  width: 100%;
  height: 30px;
  overflow: hidden;
}

/* The land: the one silhouette treatment. No stroke, no gradient, no
   shadow. The water is simply whatever ground sits above the strip. */
.ir-ridge__land {
  fill: var(--ridge);
  stroke: none;
}

/* Band top: the strip sits directly on a following .ir-band. The ridge's
   own valley floor (the 6px under the valleys) is `ridge`, and the band
   starts the next pixel down; the -1px pulls the band under the strip so
   no hairline of page ground can show at fractional zoom levels. The
   background stays transparent so the page ground is the sky above the
   land. In light the land and the band read as one dark mass rising out
   of the page; in dark the land is the lit rim that gives the band an
   edge (ridge on ground 3.68:1). */
.ir-ridge--band-top {
  position: relative;
  z-index: 1;
  margin-bottom: -1px;
  background: transparent;
}

/* Forced colors: SVG fills are not swapped by the OS, so the land would
   vanish against a flattened band. CanvasText is the system ink, not a
   literal color. */
@media (forced-colors: active) {
  .ir-ridge__land {
    fill: CanvasText;
  }
}


/* ===== Logo ===== */
/* ==========================================================================
   Island Reply design system — Logo.css
   .ir-brand: the lockup. An inline SVG mark (a text bubble whose top edge
   is the Koʻolau ridge, on a `deep` square) beside the wordmark "Island
   Reply" in the display face. The wordmark is 0.75 × the mark height and
   its baseline sits on the bubble's bottom edge (y = 24 of 32), so the
   words rest on the same line the bubble does (direction.md §6).
   Sizes: 32px (default), 24px (--sm), 16px mark alone (--xs).
   Static by rule: the land never moves, and the mark is land.
   ========================================================================== */

.ir-brand {
  /* Mark height. The wordmark, the gap and the baseline offset derive from it. */
  --ir-brand-size: 32px;
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-3);
  /* Clear space `space-2` on all sides, kept inside the box so a header
     cannot crowd the mark. It also contains the bubble's tail below the
     baseline. */
  padding: var(--space-2);
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  vertical-align: middle;
}

/* As a link the lockup stays `ink`, never `link`, and never underlines.
   Nothing changes on hover: the ring on focus is the only state. */
.ir-brand:hover,
.ir-brand:active {
  color: var(--ink);
  text-decoration: none;
}

/* The mark. A block the size of the mark whose one line box is the SVG's
   margin box: the negative bottom margin lifts the SVG's baseline to the
   bubble's bottom edge (0.25 × the size above the square's foot), which is
   what the flex container aligns the wordmark to. The tail paints in the
   remaining quarter. */
.ir-brand__mark {
  flex: none;
  width: var(--ir-brand-size);
  height: var(--ir-brand-size);
  font-size: 0;
  line-height: 0;
}

.ir-brand__mark svg {
  display: inline-block;
  width: var(--ir-brand-size);
  height: var(--ir-brand-size);
  margin-bottom: calc(var(--ir-brand-size) * -0.25);
  vertical-align: baseline;
  overflow: visible;
}

/* Two fills, one path. The square is `deep` in both themes (dark `deep` is
   the mark-dark drawing); the bubble is `on-deep`. */
.ir-brand__square {
  fill: var(--deep);
}

.ir-brand__bubble {
  fill: var(--on-deep);
}

/* The wordmark: display face, 700, -0.01em, 0.75 × the mark. Line-height 1
   so the text box is the em box and the gap below the baseline stays tight. */
.ir-brand__word {
  font-family: var(--font-display);
  font-size: calc(var(--ir-brand-size) * 0.75);
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: inherit;
}

/* --sm: 24px mark, 18px wordmark, gap `space-2`. The smallest lockup. */
.ir-brand--sm {
  --ir-brand-size: 24px;
  gap: var(--space-2);
}

/* --xs: 16px, the favicon size. Mark alone by rule ("below a 24px mark,
   use the mark alone"); a wordmark in this markup is hidden as a guard.
   rx="8" in the 32-unit viewBox lands at 4px here (`radius-sm`). */
.ir-brand--xs {
  --ir-brand-size: 16px;
  gap: 0;
}

.ir-brand--xs .ir-brand__word {
  display: none;
}

/* --on-band: the lockup on `ground-band`. The band carries `ink-on-band`,
   not `deep` (1.82 against the band in light), so the mark inverts the way
   the primary button does there: square `ink-on-band`, bubble `ground-band`.
   One ink for square and words; the bubble is a window onto the water. */
.ir-brand--on-band,
.ir-brand--on-band:hover,
.ir-brand--on-band:active {
  color: var(--ink-on-band);
}

.ir-brand--on-band .ir-brand__square {
  fill: var(--ink-on-band);
}

.ir-brand--on-band .ir-brand__bubble {
  fill: var(--ground-band);
}


/* ===== CallGreeting ===== */
/* ==========================================================================
   Island Reply design system — CallGreeting.css
   .ir-callgreeting: the opening beats of a missed call on one phone screen
   (direction.md §8 hero beats 0 to 3800ms, looped on their own at 8000ms).
   The call rings, "MISSED CALL · 2:14 PM" stamps in, the greeting plays as
   the ridge (.ir-wave: seven reef bars at 24 / 15 / 20 / 12 / 24 / 15 / 20,
   the peak sequence), and the 1 key on the keypad tray (.ir-keypad) goes
   down like a pressed key. Under the phone a manila call slip carries the
   mono stub and the dirt hem, so the call is already becoming shop paper.
   Four anchors: ground-ticket slip, 2px dirt hem, meta-mono stub and label,
   the ridge as the waveform. Tokens only. Transform and opacity only.
   The resting rules are the finished scene; every keyframe sits inside
   prefers-reduced-motion: no-preference. Mobile first.
   .ir-wave and .ir-keypad are the brief's block names and are defined here.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The scene
   -------------------------------------------------------------------------- */
.ir-callgreeting {
  position: relative;
  margin: 0;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   2. The phone screen: ground-raised, radius-lg, shadow-card (one of the
   three shadows in the system), a 1px line border. Narrower than the
   column so the slip beneath can show past its right edge.
   -------------------------------------------------------------------------- */
.ir-callgreeting__phone {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--space-3);
  width: 100%;
  max-width: 336px;
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--ground-raised);
  box-shadow: var(--shadow-card);
}

/* Header: initials avatar, the business line, the Example tag. */
.ir-callgreeting__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Avatars in scenes are initials in meta-mono on ground-sunken, never portraits. */
.ir-callgreeting__avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  background: var(--ground-sunken);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink);
}

.ir-callgreeting__who {
  display: grid;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}

/* `label` style: the business name on the screen. */
.ir-callgreeting__name {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ir-callgreeting__from {
  white-space: nowrap;
}

.ir-callgreeting__head .ir-tag {
  flex: none;
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   3. The status row: the 24px missed-call icon (handset with a short
   diagonal, 2px ink stroke, round caps, miter joins) beside the stamp
   "MISSED CALL · 2:14 PM" in meta-mono. The handset is the thing that
   rings; the diagonal is what makes it a missed call.
   -------------------------------------------------------------------------- */
.ir-callgreeting__status {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 24px;
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
}

.ir-callgreeting__icon {
  flex: none;
  width: 24px;
  height: 24px;
  overflow: visible;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: miter;
  transform: scale(1);
  transform-origin: 50% 50%;
}

.ir-callgreeting__slash {
  opacity: 1;
}

.ir-callgreeting__label {
  color: var(--ink);
  opacity: 1;
  transform: translateY(0);
}

/* --------------------------------------------------------------------------
   4. The greeting waveform (.ir-wave): a ground-sunken tray holding seven
   `reef` bars, 4px wide with a 4px gap, heights 24 / 15 / 20 / 12 / 24 /
   15 / 20px. This is the ridge sequence, so it is this scene's one ridge.
   Bars rest at full height; transform-origin bottom for the grow.
   -------------------------------------------------------------------------- */
.ir-wave {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--ground-sunken);
}

.ir-wave__bars {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 24px;
}

.ir-wave__bar {
  display: block;
  width: 4px;
  border-radius: 0;
  background: var(--reef);
  transform: scaleY(1);
  transform-origin: 50% 100%;
  opacity: 1;
}

.ir-wave__bar:nth-child(1) { height: 24px; }
.ir-wave__bar:nth-child(2) { height: 15px; }
.ir-wave__bar:nth-child(3) { height: 20px; }
.ir-wave__bar:nth-child(4) { height: 12px; }
.ir-wave__bar:nth-child(5) { height: 24px; }
.ir-wave__bar:nth-child(6) { height: 15px; }
.ir-wave__bar:nth-child(7) { height: 20px; }

.ir-wave__label {
  min-width: 0;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   5. The keypad tray (.ir-keypad): ground-sunken, 3 × 4 radius-pill keys in
   meta-mono with a 2px line-strong key edge (the pressed-key look), the
   1 key outlined in `deep` because it is the one to press, and the prompt
   "Press 1 to get a text" beside the keys.
   -------------------------------------------------------------------------- */
.ir-keypad {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--ground-sunken);
}

.ir-keypad__keys {
  flex: none;
  display: grid;
  grid-template-columns: repeat(3, 48px);
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ir-keypad__key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  margin: 0;
  border-bottom: 2px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--ground-raised);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink);
  transform: translateY(0) scale(1);
  transform-origin: 50% 100%;
}

.ir-keypad__key--one {
  border: 2px solid var(--deep);
  border-bottom-color: var(--deep-strong);
  font-weight: 600;
}

.ir-keypad__prompt {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

/* `label` style for the prompt, `caption` style for the alternative. */
.ir-keypad__label {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-wrap: balance;
}

.ir-keypad__alt {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   6. The call slip: manila ground-ticket paper tucked under the phone and
   offset right, radius-xs (paper corners), 1px line border, 2px dirt hem
   at its foot. It carries the mono stub "L1001 · 2:14 PM" and the brief's
   meta line. No perforation: the perf belongs to the ticket and report.
   -------------------------------------------------------------------------- */
.ir-callgreeting__slip {
  position: relative;
  z-index: 0;
  display: grid;
  gap: var(--space-1);
  width: calc(100% - var(--space-6));
  max-width: 336px;
  margin: calc(-1 * var(--space-3)) 0 0 var(--space-6);
  padding: var(--space-6) var(--space-4) var(--space-3);
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-xs);
  background: var(--ground-ticket);
}

/* `body-sm` for the log line. */
.ir-callgreeting__log {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   7. The caption: `caption` style through .ir-figcaption from the base
   layer; this only spaces it.
   -------------------------------------------------------------------------- */
.ir-callgreeting__caption {
  max-width: 36rem;
  margin-top: var(--space-4);
}

/* --------------------------------------------------------------------------
   8. Motion (direction.md §8). One 8000ms loop shared by every moving part,
   so nothing drifts out of step: 1% = 80ms.
     0        the handset rings: two pulses, 600ms each, scale 1.08
     1200     the diagonal and "MISSED CALL · 2:14 PM" enter (240ms)
     2200     the seven bars grow bottom-up in sequence over 1600ms
     3800     the 1 key goes down 120ms and up 120ms
     4040     rest on the finished scene
     7700     300ms fade of the moving parts to the empty start
   The land never moves: the phone, trays, keys 2 to #, the slip and all
   text hold still. Only the handset, the stamp, the bars and the 1 key move.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-callgreeting-ring {
    0%     { transform: scale(1); }
    3.75%  { transform: scale(1.08); }
    7.5%   { transform: scale(1); }
    11.25% { transform: scale(1.08); }
    15%    { transform: scale(1); }
    100%   { transform: scale(1); }
  }

  @keyframes ir-callgreeting-stamp {
    0%, 15%  { opacity: 0; transform: translateY(8px); }
    18%      { opacity: 1; transform: translateY(0); }
    96.25%   { opacity: 1; transform: translateY(0); }
    100%     { opacity: 0; transform: translateY(0); }
  }

  @keyframes ir-callgreeting-slash {
    0%, 15%  { opacity: 0; }
    18%      { opacity: 1; }
    96.25%   { opacity: 1; }
    100%     { opacity: 0; }
  }

  /* Each bar grows over 460ms, 190ms after the one before it. */
  @keyframes ir-wave-bar-1 {
    0%, 27.5%  { transform: scaleY(0); opacity: 1; }
    33.25%     { transform: scaleY(1); opacity: 1; }
    96.25%     { transform: scaleY(1); opacity: 1; }
    100%       { transform: scaleY(1); opacity: 0; }
  }
  @keyframes ir-wave-bar-2 {
    0%, 29.875% { transform: scaleY(0); opacity: 1; }
    35.625%     { transform: scaleY(1); opacity: 1; }
    96.25%      { transform: scaleY(1); opacity: 1; }
    100%        { transform: scaleY(1); opacity: 0; }
  }
  @keyframes ir-wave-bar-3 {
    0%, 32.25% { transform: scaleY(0); opacity: 1; }
    38%        { transform: scaleY(1); opacity: 1; }
    96.25%     { transform: scaleY(1); opacity: 1; }
    100%       { transform: scaleY(1); opacity: 0; }
  }
  @keyframes ir-wave-bar-4 {
    0%, 34.625% { transform: scaleY(0); opacity: 1; }
    40.375%     { transform: scaleY(1); opacity: 1; }
    96.25%      { transform: scaleY(1); opacity: 1; }
    100%        { transform: scaleY(1); opacity: 0; }
  }
  @keyframes ir-wave-bar-5 {
    0%, 37%  { transform: scaleY(0); opacity: 1; }
    42.75%   { transform: scaleY(1); opacity: 1; }
    96.25%   { transform: scaleY(1); opacity: 1; }
    100%     { transform: scaleY(1); opacity: 0; }
  }
  @keyframes ir-wave-bar-6 {
    0%, 39.375% { transform: scaleY(0); opacity: 1; }
    45.125%     { transform: scaleY(1); opacity: 1; }
    96.25%      { transform: scaleY(1); opacity: 1; }
    100%        { transform: scaleY(1); opacity: 0; }
  }
  @keyframes ir-wave-bar-7 {
    0%, 41.75% { transform: scaleY(0); opacity: 1; }
    47.5%      { transform: scaleY(1); opacity: 1; }
    96.25%     { transform: scaleY(1); opacity: 1; }
    100%       { transform: scaleY(1); opacity: 0; }
  }

  @keyframes ir-keypad-press {
    0%, 47.5% { transform: translateY(0) scale(1); animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
    49%       { transform: translateY(1px) scale(0.94); animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1); }
    50.5%     { transform: translateY(0) scale(1); }
    100%      { transform: translateY(0) scale(1); }
  }

  .ir-callgreeting__icon {
    animation: ir-callgreeting-ring 8000ms var(--ease-out) infinite;
  }

  .ir-callgreeting__slash {
    animation: ir-callgreeting-slash 8000ms var(--ease-out) infinite;
  }

  .ir-callgreeting__label {
    animation: ir-callgreeting-stamp 8000ms var(--ease-out) infinite;
  }

  .ir-wave__bar:nth-child(1) { animation: ir-wave-bar-1 8000ms var(--ease-out) infinite; }
  .ir-wave__bar:nth-child(2) { animation: ir-wave-bar-2 8000ms var(--ease-out) infinite; }
  .ir-wave__bar:nth-child(3) { animation: ir-wave-bar-3 8000ms var(--ease-out) infinite; }
  .ir-wave__bar:nth-child(4) { animation: ir-wave-bar-4 8000ms var(--ease-out) infinite; }
  .ir-wave__bar:nth-child(5) { animation: ir-wave-bar-5 8000ms var(--ease-out) infinite; }
  .ir-wave__bar:nth-child(6) { animation: ir-wave-bar-6 8000ms var(--ease-out) infinite; }
  .ir-wave__bar:nth-child(7) { animation: ir-wave-bar-7 8000ms var(--ease-out) infinite; }

  .ir-keypad__key--one {
    animation: ir-keypad-press 8000ms linear infinite;
  }
}

/* --------------------------------------------------------------------------
   9. Forced colors: the bars and the dirt hem are fills, which the OS would
   flatten away. CanvasText is the system ink, not a literal color.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-wave__bar {
    background: CanvasText;
  }

  .ir-callgreeting__slip {
    border-bottom-color: CanvasText;
  }
}


/* ===== Checklist ===== */
/* ==========================================================================
   Island Reply design system — Checklist.css
   .ir-checklist: the "Who it's for" qualifiers drawn as a ticked intake
   sheet, not a feature list. Ruled rows in `line`, and at the head of each
   row a 22px paper cell (1.5px `line-strong`, radius-xs, `ground-raised`)
   holding the system's 16px check: an L rotated 45deg, 1.5px stroke, in
   `deep` on light grounds and `reef` on .ir-band (deep is 1.82 against the
   band). The foot line ("Starting on Oʻahu…") closes the sheet.
   .ir-facts: the hero quick facts as one wrapping row of .ir-chip
   (Labels.css draws the chip and its check).
   Tokens only. Mobile first. Transform and opacity only; the ticked sheet
   is the default and the landing is opt-in.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The block. A measure of 40rem so three qualifiers read as a sheet, not
   a banner; the page may widen it.
   -------------------------------------------------------------------------- */
.ir-checklist {
  max-width: 40rem;
  margin: 0;
  color: var(--ink);
}

/* The sheet: a ruled list, hairline above the first row and under each row. */
.ir-checklist__list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   2. The row. Body size (inherited from the base layer: 17px, 18px at
   48rem), space-3 above and below, the cell on the left with a space-3 gap
   (22 + 12 = 34px of padding). Position: relative anchors the two pseudos.
   -------------------------------------------------------------------------- */
.ir-checklist__item {
  position: relative;
  margin: 0;
  padding: var(--space-3) 0 var(--space-3) 34px;
  border-bottom: 1px solid var(--line);
  line-height: 1.55;
  text-wrap: pretty;
}

/* The cell: 22px of paper with square-ish corners, sitting on the first
   line. 0.14em lifts it to the line box's center at both body sizes. */
.ir-checklist__item::before {
  content: "";
  position: absolute;
  top: calc(var(--space-3) + 0.14em);
  left: 0;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-xs);
  background: var(--ground-raised);
}

/* The check: the 16px icon rule at 1.5px, currentColor replaced by `deep`.
   The L is 4.5 x 9 inside its stroke, rotated about its center and placed
   so the rotated mark sits on the cell's center, lifted 1px like the chip's. */
.ir-checklist__item::after {
  content: "";
  position: absolute;
  top: calc(var(--space-3) + 0.14em + 4px);
  left: 8px;
  box-sizing: content-box;
  width: 4.5px;
  height: 9px;
  border: solid var(--deep);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
  transform-origin: 50% 50%;
}

/* --------------------------------------------------------------------------
   3. The foot. "Starting on Oʻahu, town to the North Shore, Kapolei to
   Hawaiʻi Kai. On a neighbor island? Email anyway." Body size, `ink`; the
   base layer styles the mailto link. On the band it reads in ink-on-band.
   -------------------------------------------------------------------------- */
.ir-checklist__foot {
  margin: var(--space-4) 0 0;
  line-height: 1.55;
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   4. On the band. The rules become a wash of the band ink (`line` would be
   a bright hairline on ground-band), the cell empties and takes the band's
   muted ink for its edge, and the check turns `reef` (4.0 light, 5.0 dark
   against ground-band), the band's lighter water.
   -------------------------------------------------------------------------- */
.ir-band .ir-checklist__list,
.ir-band .ir-checklist__item {
  /* translucent wash of the band ink; derived from the token, not a literal */
  border-color: color-mix(in srgb, var(--ink-on-band) 22%, transparent);
}

.ir-band .ir-checklist__item::before {
  border-color: var(--ink-on-band-muted);
  background: transparent;
}

.ir-band .ir-checklist__item::after {
  border-color: var(--reef);
}

/* --------------------------------------------------------------------------
   5. Quick facts. One row of .ir-chip that wraps at 360px. The chip, its
   check and its band inversion live in Labels.css.
   -------------------------------------------------------------------------- */
.ir-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* --------------------------------------------------------------------------
   6. Motion (direction.md §8). Opt-in: .ir-checklist--land ticks the boxes
   once, --dur-1 each on --ease-out, 250ms apart like ticket fields, scale
   within 12%, transform and opacity only. The resting rules above are the
   end state, so with reduced motion (or without the modifier) the sheet is
   simply ticked. The base layer's scene pause selects elements only, so the
   pseudo-element pause is restated here.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-check-land {
    from {
      transform: rotate(45deg) scale(0.88);
      opacity: 0;
    }
    to {
      transform: rotate(45deg) scale(1);
      opacity: 1;
    }
  }

  .ir-checklist--land .ir-checklist__item::after {
    animation: ir-check-land var(--dur-1) var(--ease-out) both;
  }

  .ir-checklist--land .ir-checklist__item:nth-child(2)::after {
    animation-delay: 250ms;
  }

  .ir-checklist--land .ir-checklist__item:nth-child(3)::after {
    animation-delay: 500ms;
  }

  .ir-checklist--land .ir-checklist__item:nth-child(4)::after {
    animation-delay: 750ms;
  }

  .ir-checklist--land .ir-checklist__item:nth-child(5)::after {
    animation-delay: 1000ms;
  }

  .ir-checklist--land .ir-checklist__item:nth-child(6)::after {
    animation-delay: 1250ms;
  }

  .ir-scene[data-paused] .ir-checklist__item::after {
    animation-play-state: paused;
  }
}

/* Forced colors: the cell edge and the check keep their system ink. */
@media (forced-colors: active) {
  .ir-checklist__item::before {
    border-color: CanvasText;
    background: Canvas;
  }

  .ir-checklist__item::after {
    border-color: CanvasText;
  }
}


/* ===== DemoBand ===== */
/* ==========================================================================
   Island Reply design system — DemoBand.css
   .ir-demo: "Book a 15-minute demo", the one action on the site. Markup:
   .ir-ridge--band-top, then <section class="ir-section ir-band ir-demo">.
   The band is deep water, the ridge is its top edge. Left: a mono stub
   line (15 minutes · email only · Oʻahu), the h2 and the brief's demo
   text. Right: the contact slip, drawn like the service tag a tech hangs
   on a unit: a 1.5px box with paper corners, an eyelet and a mono stub
   row ("Subject · Demo request") over a hairline, then the eyebrow, the
   address in the display face as big selectable text, the founder line,
   and the inverted pressed-key button. No forms: email only.
   Land: nothing here moves, so no keyframes live in this file.
   Reuses .ir-band (00-base), .ir-ridge (Ridge), .ir-meta and .ir-eyebrow
   (Labels), .ir-button + .ir-button__icon (Button). Tokens only.
   `dirt` and `ohia` never sit on the band, so the slip has no hem; the
   stub, the eyelet and the mono fields are the paper here. Mobile first.
   ========================================================================== */

/* The grid: one column on phones, two from 60rem (copy 7 : slip 6). */
.ir-demo__grid {
  display: grid;
  gap: var(--space-8);
}

@media (min-width: 60rem) {
  .ir-demo__grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 6fr);
    column-gap: var(--space-16);
    align-items: start;
  }
}

/* --------------------------------------------------------------------------
   Copy column: stub line, h2, the demo text.
   -------------------------------------------------------------------------- */
.ir-demo__copy {
  display: grid;
  gap: var(--space-4);
  justify-items: start;
}

/* The stub line above the h2: .ir-meta from Labels (meta-mono, muted band
   ink through the band's remap). Three facts, middle dots between them. */
.ir-demo__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
}

.ir-demo__meta span + span::before {
  content: "\00B7";
  margin-right: var(--space-3);
}

/* h2 = display-lg from the base layer. The measure makes it two lines;
   "15-minute" never breaks at its hyphen, so the lines fall "Book a" /
   "15-minute demo". */
.ir-demo__title {
  max-width: 16ch;
}

.ir-demo__nobreak {
  white-space: nowrap;
}

/* The demo text: `lead` (18px, 20px from 48rem), measure 34rem. */
.ir-demo__text {
  margin: 0;
  max-width: 34rem;
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-on-band);
}

@media (min-width: 48rem) {
  .ir-demo__text {
    font-size: 20px;
  }
}

/* --------------------------------------------------------------------------
   The contact slip: a service tag on the band. 1.5px box in the band's
   muted ink (line-strong is below 3:1 on the dark band), paper corners
   (radius-xs), flat: no shadow, no fill. Padding space-5 on phones,
   space-6 from 48rem. Position relative so the PHONE block, once filled
   in, stacks inside the same tag.
   -------------------------------------------------------------------------- */
.ir-demo__contact {
  position: relative;
  display: grid;
  gap: 0;
  justify-items: start;
  padding: var(--space-5);
  border: 1.5px solid var(--ink-on-band-muted);
  border-radius: var(--radius-xs);
}

@media (min-width: 48rem) {
  .ir-demo__contact {
    padding: var(--space-6);
  }
}

/* The stub row: eyelet, field name, value; a hairline under it. The row
   spans the slip (justify-self: stretch) so the hairline runs edge to edge
   of the padding box. The hairline is the band ink at 24%, derived from the
   token (a literal would not follow the theme). */
.ir-demo__stub {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  justify-self: stretch;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid color-mix(in srgb, var(--ink-on-band) 24%, transparent); /* translucent band ink */
}

/* The eyelet: a 12px ring in the muted band ink, the band showing through
   as the hole. Decorative; the words carry the meaning. */
.ir-demo__eyelet {
  flex: none;
  width: 12px;
  height: 12px;
  border: 2px solid var(--ink-on-band-muted);
  border-radius: var(--radius-pill);
  background: transparent;
}

/* The field value takes the band ink; the field name keeps .ir-meta's muted ink. */
.ir-demo__stub-val {
  color: var(--ink-on-band);
}

/* Eyebrow "Email": .ir-eyebrow from Labels, space-5 below the stub. */
.ir-demo__label {
  margin-top: var(--space-5);
}

/* The address: the display face at 28px (32px from 48rem, 36px from 72rem),
   700, -0.01em, band ink. One tap selects the whole thing (.ir-select-all,
   00-base). overflow-wrap: anywhere so a narrow slip can never push the
   page sideways; at every breakpoint here it fits on one line. */
.ir-demo__email {
  margin: var(--space-2) 0 0;
  max-width: 100%;
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink-on-band);
  overflow-wrap: anywhere;
  text-wrap: balance;
}

@media (min-width: 48rem) {
  .ir-demo__email {
    font-size: 32px;
  }
}

@media (min-width: 72rem) {
  .ir-demo__email {
    font-size: 36px;
  }
}

/* The founder line: `caption` in the band's muted ink, under the address. */
.ir-demo__note {
  margin: var(--space-2) 0 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--ink-on-band-muted);
}

/* The button: .ir-button inverts on the band by itself (Button.css). Full
   width on phones so the tap target is the slip's width; its own width
   from 40rem. space-5 above. */
.ir-demo__button {
  margin-top: var(--space-5);
  width: 100%;
}

@media (min-width: 40rem) {
  .ir-demo__button {
    width: auto;
  }
}

/* --------------------------------------------------------------------------
   Forced colors: the slip's box, the eyelet and the hairline would flatten;
   give them the system ink so the tag keeps its edge.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-demo__contact,
  .ir-demo__stub,
  .ir-demo__eyelet {
    border-color: CanvasText;
  }
}


/* ===== Faq ===== */
/* ==========================================================================
   Island Reply design system — Faq.css
   .ir-faq: the seven questions as one sheet of shop paper. A 2px `ink`
   head rule, 1px `line` rules between rows, a 2px `dirt` hem at the foot
   (a bottom edge, like every hem in the system). Each question is a
   numbered line item: a `meta-mono` number ("01", tabular, slashed zero)
   beside a heading-3 summary, a CSS plus at the right. The open row lifts
   to `ground-raised` (the FAQ panel) and its whole head turns `dirt`: the
   number, the question and the minus. No left stripe, no chevron, no
   shadow, no ridge (the ridge keeps to its five placements).
   Native <details>/<summary>; no JavaScript. Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The sheet
   -------------------------------------------------------------------------- */
.ir-faq {
  /* Row metrics. The number column and gap set the answer's indent too. */
  --ir-faq-pad-y: var(--space-4);
  --ir-faq-pad-x: var(--space-3);
  --ir-faq-num: 24px;
  --ir-faq-gap: var(--space-3);
  margin: 0;
  padding: 0;
  border-top: 2px solid var(--ink);
  border-bottom: 2px solid var(--dirt);
  color: var(--ink);
}

@media (min-width: 40rem) {
  .ir-faq {
    --ir-faq-pad-y: var(--space-5);
    --ir-faq-pad-x: var(--space-4);
    --ir-faq-num: 32px;
    --ir-faq-gap: var(--space-4);
  }
}

/* --------------------------------------------------------------------------
   2. Rows
   Each <details> is one line of the sheet. The last row has no rule of
   its own so the dirt hem sits directly under its text.
   -------------------------------------------------------------------------- */
.ir-faq__item {
  margin: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
}

.ir-faq__item:last-child {
  border-bottom: 0;
}

/* The open row is the FAQ panel: it lifts to ground-raised. */
.ir-faq__item[open] {
  background: var(--ground-raised);
}

/* --------------------------------------------------------------------------
   3. Summary: number · question · plus
   A grid so the number, the question and the icon cell sit in fixed
   columns and share the question's first baseline. heading-3 set from the
   type table (20px → 22px, 600, 1.2). The native marker is removed; the
   plus is drawn below.
   -------------------------------------------------------------------------- */
.ir-faq__summary {
  position: relative;
  display: grid;
  grid-template-columns: var(--ir-faq-num) minmax(0, 1fr) 24px;
  column-gap: var(--ir-faq-gap);
  align-items: baseline;
  margin: 0;
  padding: var(--ir-faq-pad-y) var(--ir-faq-pad-x);
  list-style: none;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
  text-wrap: balance;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  /* Hover is the only transition, and it is background-color only (direction.md §8). */
  transition: background-color 120ms var(--ease-out);
}

.ir-faq__summary::-webkit-details-marker {
  display: none;
}

.ir-faq__summary::marker {
  content: "";
}

.ir-faq__summary:hover {
  background: var(--ground-raised);
}

/* The line number: meta-mono, ink-muted, figures tabular and the zero
   slashed, so "01" through "07" read as a stamped index. */
.ir-faq__num {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
  white-space: nowrap;
}

.ir-faq__q {
  min-width: 0;
}

/* The plus: two 2px bars of currentColor in a 24px cell at the right,
   centred on the question's first line (0.6em = half of line-height 1.2).
   The 16px glyph on a 24px grid follows the icon rule (direction.md §9). */
.ir-faq__summary::before,
.ir-faq__summary::after {
  content: "";
  position: absolute;
  background: currentColor;
}

/* Horizontal bar. */
.ir-faq__summary::before {
  top: calc(var(--ir-faq-pad-y) + 0.6em - 1px);
  right: calc(var(--ir-faq-pad-x) + 4px);
  width: 16px;
  height: 2px;
}

/* Vertical bar. When the row opens it lies down onto the horizontal bar
   and the plus becomes a minus. A state swap, not a transition. */
.ir-faq__summary::after {
  top: calc(var(--ir-faq-pad-y) + 0.6em - 8px);
  right: calc(var(--ir-faq-pad-x) + 11px);
  width: 2px;
  height: 16px;
}

/* --------------------------------------------------------------------------
   4. Open state: the whole head turns dirt (the active FAQ marker).
   dirt on ground 5.53 / 5.94, on ground-raised 6.05 / 5.23. The bars take
   currentColor, so the minus is dirt too. No left stripe.
   -------------------------------------------------------------------------- */
.ir-faq__item[open] > .ir-faq__summary {
  color: var(--dirt);
}

.ir-faq__item[open] > .ir-faq__summary .ir-faq__num {
  color: var(--dirt);
}

.ir-faq__item[open] > .ir-faq__summary::after {
  transform: rotate(90deg);
}

/* --------------------------------------------------------------------------
   5. Answer
   body-sm (15px / 1.5) in ink, indented to the question's column, measure
   40rem. Paragraphs stack with space-3. Static at rest: no transform, no
   opacity, so the rendered answer is the end state.
   -------------------------------------------------------------------------- */
.ir-faq__answer {
  display: grid;
  gap: var(--space-3);
  max-width: 40rem;
  margin: 0;
  padding: 0 var(--ir-faq-pad-x) var(--space-5)
    calc(var(--ir-faq-pad-x) + var(--ir-faq-num) + var(--ir-faq-gap));
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink);
}

/* Scoped to the answer block; base.css sets text-wrap on p and nothing else. */
.ir-faq__answer > p {
  margin: 0;
}

/* --------------------------------------------------------------------------
   6. Motion: the answer enters (direction.md §8 enter rule), --dur-2 on
   --ease-out from translateY(8px) / opacity 0 to rest. Keyframes live only
   here, so with reduced motion the answer simply appears. Closing is the
   native instant hide; nothing exits.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-faq-enter {
    from {
      transform: translateY(8px);
      opacity: 0;
    }
    to {
      transform: none;
      opacity: 1;
    }
  }

  .ir-faq__item[open] > .ir-faq__answer {
    animation: ir-faq-enter var(--dur-2) var(--ease-out) both;
  }
}

/* --------------------------------------------------------------------------
   7. Forced colors: backgrounds are flattened, so the drawn bars vanish.
   The plus and minus become mono text at the same spot. The hem and the
   head rule survive as borders.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-faq__summary::before {
    content: none;
  }

  .ir-faq__summary::after {
    content: "+";
    top: var(--ir-faq-pad-y);
    right: var(--ir-faq-pad-x);
    width: auto;
    height: auto;
    background: none;
    font-family: var(--font-mono);
    font-size: 1em;
    line-height: 1.2;
    font-weight: 600;
  }

  .ir-faq__item[open] > .ir-faq__summary::after {
    content: "\2212";
    transform: none;
  }
}


/* ===== FeatureList ===== */
/* ==========================================================================
   Island Reply design system — FeatureList.css
   "What lands on your phone": four flat cards that read like lines on a
   dispatcher's board, not a SaaS feature grid. .ir-featurelist is the grid
   (one column, two from 48rem, cards hugging their own content so the hems
   fall where each card ends, like four separate tickets). .ir-feature is
   one card: ground-raised, 1px `line` border, radius-md, a 2px `dirt` hem
   on the foot, no shadow (direction.md §5). Each card opens with a
   meta-mono stub ("01 · RIGHT AWAY") that trails a dotted `line` leader
   the way a ticket field name does, then a heading-3 title beside a 24px
   outline icon (2px stroke, round caps, miter joins, currentColor), then
   body-sm copy in ink-muted. The flag is the one colored icon (`urgent`),
   so that card also carries the URGENT pill from Labels.css at the right
   of its title row (the pairing rule, direction.md §9) and a ticket-style
   field listing what counts as an emergency. Static: the land never moves.
   Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The list — a grid of cards. Cards align to the top of their row so
   each hem sits where that card's own copy ends.
   -------------------------------------------------------------------------- */
.ir-featurelist {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 48rem) {
  .ir-featurelist {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   2. The card — flat paper on the page ground. The 2px `dirt` hem replaces
   the bottom hairline; it is the only warm mark on the card.
   -------------------------------------------------------------------------- */
.ir-feature {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  margin: 0;
  padding: var(--space-5);
  background: var(--ground-raised);
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-md);
  box-shadow: none;
  color: var(--ink);
}

@media (min-width: 48rem) {
  .ir-feature {
    padding: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   3. The stub — meta-mono (12px / 500 / 0.04em, uppercase, tabular figures,
   slashed zero) in ink-muted, followed by a dotted leader in `line` that
   runs to the card's edge. "01 · RIGHT AWAY": the number and the timing
   word the brief uses for that item.
   -------------------------------------------------------------------------- */
.ir-feature__stub {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
  white-space: nowrap;
}

.ir-feature__stub::after {
  content: "";
  flex: 1 1 auto;
  min-width: var(--space-4);
  height: 0;
  border-top: 1px dotted var(--line);
}

/* --------------------------------------------------------------------------
   4. The title row — icon, title, and (on the emergency card) the pill.
   The title is heading-3 (display 600, 20px on phones, 22px from 48rem).
   The icon's 24px box sits on the title's first line.
   -------------------------------------------------------------------------- */
.ir-feature__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.ir-feature__icon {
  flex: none;
  width: 24px;
  height: 24px;
  margin-top: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: miter;
  color: var(--ink);
}

/* The flag: the only icon that takes a color (direction.md §9). Its card
   must carry the URGENT pill, and the pill never appears without it. */
.ir-feature__icon--urgent {
  color: var(--urgent);
}

.ir-feature__title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}

/* The pill (Labels.css .ir-pill--urgent) sits at the right edge of the
   title row, on the first line, like the pill in the owner-alert header. */
.ir-feature__pill {
  flex: none;
  margin-left: auto;
  margin-top: 2px;
}

@media (min-width: 48rem) {
  .ir-feature__icon {
    margin-top: 1px;
  }

  .ir-feature__title {
    font-size: 22px;
  }

  .ir-feature__pill {
    margin-top: 3px;
  }
}

/* --------------------------------------------------------------------------
   5. The copy — body-sm (15px / 1.5) in ink-muted. A reply keyword inside
   the copy ("BOOKED") is set in mono and `ink`: it is the word the owner
   actually types, so it reads like the stamp it becomes.
   -------------------------------------------------------------------------- */
.ir-feature__body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-muted);
  text-wrap: pretty;
}

.ir-feature__kw {
  font-family: var(--font-mono);
  font-size: 0.9em;
  font-weight: 500;
  letter-spacing: 0.04em;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   6. The field — a ticket row inside the card: a hairline in `line`, a
   meta-mono field name in ink-muted, then the value in body-sm `ink`
   (ticket values are ink, not muted). The list marks each item with a
   5px square of ink-muted: a tick box on a work order, not a bullet.
   -------------------------------------------------------------------------- */
.ir-feature__field {
  margin: var(--space-1) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
}

.ir-feature__field-name {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
}

.ir-feature__list {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--ink);
}

.ir-feature__item {
  position: relative;
  margin: 0;
  padding-left: var(--space-4);
  text-wrap: pretty;
}

.ir-feature__item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 0;
  background: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   7. Forced colors — the hem and the dotted leader are borders, so the OS
   swaps them itself. The square marker is a background, which would
   vanish; a border keeps it visible. CanvasText is the system ink.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-feature__item::before {
    background: transparent;
    border: 1px solid CanvasText;
  }
}


/* ===== Icons ===== */
/* ==========================================================================
   Island Reply design system — Icons.css
   The 16-icon set (direction.md §9): 24px grid, 2px stroke, round caps,
   miter joins, outline only. In page markup the SVG is inlined with
   stroke="currentColor" and sits beside a text label; this file sizes it,
   drops it to 16px / 1.5px, and colors the one icon that may be colored
   (the flag, `urgent`). Icons never move except the chevron's turn.
   Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The icon — an inline <svg class="ir-icon" viewBox="0 0 24 24">.
   Inline-block so it sits in a line of text; vertical-align middle so it
   centers on the label's x-height. Stroke and fill are set here as well
   as in the markup, so a path pasted without its attributes still reads.
   flex: none keeps a flex label from squeezing it.
   -------------------------------------------------------------------------- */
.ir-icon {
  display: inline-block;
  flex: none;
  width: 24px;
  height: 24px;
  vertical-align: middle;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: miter;
  color: inherit;
}

/* --------------------------------------------------------------------------
   2. 16px — chips, list rows, nav. Strokes thin to 1.5px (direction.md §9:
   2px at 24px, 1.5px at 16px). The class outranks the stroke-width
   attribute on the root, and the paths inherit it.
   -------------------------------------------------------------------------- */
.ir-icon--16 {
  width: 16px;
  height: 16px;
  stroke-width: 1.5;
}

/* --------------------------------------------------------------------------
   3. The flag — the only colored icon. `urgent`, and by the signal rule it
   lives in a component that also carries the URGENT pill (the owner alert,
   the ticket's Urgency row). On the band `urgent` fails 3:1 against light
   ground-band (2.43), so there the flag takes the band ink like every
   other icon.
   -------------------------------------------------------------------------- */
.ir-icon--flag {
  color: var(--urgent);
}

.ir-band .ir-icon--flag {
  color: inherit;
}

/* --------------------------------------------------------------------------
   4. The chevron's turn — the FAQ summary's chevron points down closed and
   up open. Static rotation; the turn itself is in the motion block below.
   -------------------------------------------------------------------------- */
.ir-icon--up {
  transform: rotate(180deg);
}

/* --------------------------------------------------------------------------
   5. Pair — an icon beside its label. Inline-flex, space-2 apart, centered
   on the text. The label is whatever text the page puts after the svg.
   -------------------------------------------------------------------------- */
.ir-icon-pair {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* --------------------------------------------------------------------------
   6. Motion — the chevron's turn, --dur-1 on --ease-out, transform only.
   The rotated and unrotated rules above are the end states, so with
   reduced motion the chevron is simply up or down.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .ir-icon--turn {
    transition: transform var(--dur-1) var(--ease-out);
  }
}


/* ===== JobTicket ===== */
/* ==========================================================================
   Island Reply design system — JobTicket.css
   .ir-ticket: the example job ticket as shop paper (direction.md §5, §7, §9).
   Manila `ground-ticket`, square `radius-xs` corners, a 1px `line` border,
   a 2px `dirt` hem on the foot and no shadow. The paper's top is a
   perforated stub: mono ID and time on the left, the Example tag on the
   right, then a tear line of 4px holes at 12px pitch that show the page
   ground through the paper. Below the tear, the fields the AI pulls out
   as a dl: meta-mono names, body-sm values, tabular figures, slashed
   zeros; the Urgency row is the pink carbon copy with the one URGENT pill.
   The BOOKED stamp lands on the bottom-right corner over the hem.
   Reuses Labels.css: .ir-meta, .ir-tag--paper, .ir-pill--urgent, .ir-stamp.
   The paper never moves (land); only the three fields, the pill and the
   stamp do, and only on transform and opacity. Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The paper
   The container paints no background of its own: the stub, the tear line
   and the body each paint `ground-ticket`, so the tear's holes open onto
   whatever ground the ticket sits on. position: relative anchors the stamp.
   -------------------------------------------------------------------------- */
.ir-ticket {
  position: relative;
  display: block;
  width: 100%;
  max-width: 100%;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt); /* the hem: foot only, never a frame */
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-body);
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
}

/* --------------------------------------------------------------------------
   2. The stub
   A strip of the same paper above the tear line. Left: `.ir-meta`
   "L1001 · 2:14 PM". Right: `.ir-tag--paper` "Example" on dirt-soft.
   -------------------------------------------------------------------------- */
.ir-ticket__stub {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: var(--ground-ticket);
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
}

.ir-ticket__stub .ir-meta {
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   3. The tear line (direction.md §7, verbatim technique)
   A 4px strip of paper whose mask punches 4px holes at 12px pitch. The
   first mask layer is the row of holes, centered (50% 0) so the two end
   insets match; the second layer keeps 8px of solid paper at each end so
   the row never runs into the corner. The default composite is the union
   of the two layers. `#000` here is mask alpha (opaque), not a color.
   -------------------------------------------------------------------------- */
.ir-ticket__perf {
  display: block;
  height: 4px;
  margin: 0;
  background: var(--ground-ticket);
  -webkit-mask-image:
    radial-gradient(circle 2px, transparent 98%, #000 100%),
    linear-gradient(to right, #000 8px, transparent 8px, transparent calc(100% - 8px), #000 calc(100% - 8px));
  -webkit-mask-position: 50% 0, 0 0;
  -webkit-mask-size: 12px 4px, 100% 100%;
  -webkit-mask-repeat: repeat-x, no-repeat;
  mask-image:
    radial-gradient(circle 2px, transparent 98%, #000 100%),
    linear-gradient(to right, #000 8px, transparent 8px, transparent calc(100% - 8px), #000 calc(100% - 8px));
  mask-position: 50% 0, 0 0;
  mask-size: 12px 4px, 100% 100%;
  mask-repeat: repeat-x, no-repeat;
}

/* Fallbacks: a 1px dashed `line-strong` rule where masks are not painted.
   Forced colors drops backgrounds and masks alike, so the rule is the only
   thing that survives there; dash length is the user agent's. */
@supports not ((mask-image: none) or (-webkit-mask-image: none)) {
  .ir-ticket__perf {
    height: 0;
    background: transparent;
    border-top: 1px dashed var(--line-strong);
  }
}

@media (forced-colors: active) {
  .ir-ticket__perf {
    height: 0;
    background: transparent;
    -webkit-mask-image: none;
    mask-image: none;
    border-top: 1px dashed var(--line-strong);
  }
}

/* --------------------------------------------------------------------------
   4. The body: heading-3 head, then the fields
   -------------------------------------------------------------------------- */
.ir-ticket__body {
  padding: var(--space-4) 0 var(--space-5);
  background: var(--ground-ticket);
  border-radius: 0 0 var(--radius-xs) var(--radius-xs);
}

/* heading-3: 20px → 22px, 1.20, weight 600 (the ticket head) */
.ir-ticket__head {
  margin: 0 var(--space-4) var(--space-3);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}

.ir-ticket__fields {
  margin: 0;
  padding: 0;
}

/* One field per row: name column, value column, a hairline between rows.
   Rows run edge to edge so the Urgency row's carbon tint fills the paper. */
.ir-ticket__row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: baseline;
  margin: 0;
  padding: var(--space-2) var(--space-4);
}

.ir-ticket__row + .ir-ticket__row {
  border-top: 1px solid var(--line);
}

/* meta-mono field names in ink-muted */
.ir-ticket__name {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* body-sm values in ink */
.ir-ticket__value {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: pretty;
  overflow-wrap: anywhere;
}

/* The Urgency row: pink carbon copy, URGENT text in `urgent` at 600, and
   the one URGENT pill before the words. signal and urgent travel together. */
.ir-ticket__row--urgent {
  background: var(--urgent-soft);
}

.ir-ticket__row--urgent .ir-ticket__value {
  color: var(--urgent);
  font-weight: 600;
}

.ir-ticket__pill {
  margin-right: var(--space-2);
  vertical-align: 1px;
}

/* --------------------------------------------------------------------------
   5. The stamp
   The BOOKED stamp from Labels.css, landed on the bottom-right corner so it
   straddles the hem. It floats (shadow-float: one of the system's three
   shadows). Resting skew -4deg comes from .ir-stamp.
   -------------------------------------------------------------------------- */
.ir-ticket__stamp {
  position: absolute;
  right: var(--space-5);
  bottom: calc(-1 * var(--space-2));
  z-index: 1;
  box-shadow: var(--shadow-float);
}

/* --------------------------------------------------------------------------
   6. Motion (direction.md §8). The rules above are the finished ticket:
   every field filled, the pill on, the stamp down. The two modifiers only
   add keyframes, and only when motion is welcome.

   .ir-ticket--loop: Issue, Address and Urgency enter 250ms apart, each
   over --dur-1 (160ms) on --ease-out, translateY(8px) and opacity to rest;
   the pill lands 180ms on --ease-stamp right after its row. Hold on the
   filled ticket, then a 300ms fade on --ease-in at 11,700ms and the loop
   restarts at 12,000ms: the reset is a fade, not a rewind. Percentages are
   those times over the 12,000ms period (160ms = 1.3333%, 250ms = 2.0833%).

   .ir-ticket--stamped: the stamp lands once, 180ms on --ease-stamp from
   scale(1.12) rotate(-6deg) to scale(1) rotate(-4deg), 600ms after load,
   and stays. Scenes pause through .ir-scene[data-paused] in the base layer.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-ticket-beat-1 {
    0% { opacity: 0; transform: translateY(8px); animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1); }
    1.3333% { opacity: 1; transform: none; }
    97.5% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
    100% { opacity: 0; transform: none; }
  }

  @keyframes ir-ticket-beat-2 {
    0%, 2.0833% { opacity: 0; transform: translateY(8px); animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1); }
    3.4167% { opacity: 1; transform: none; }
    97.5% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
    100% { opacity: 0; transform: none; }
  }

  @keyframes ir-ticket-beat-3 {
    0%, 4.1667% { opacity: 0; transform: translateY(8px); animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1); }
    5.5% { opacity: 1; transform: none; }
    97.5% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
    100% { opacity: 0; transform: none; }
  }

  @keyframes ir-ticket-pill-land {
    0%, 5.8333% { opacity: 0; transform: scale(1.12); animation-timing-function: cubic-bezier(0.3, 1.3, 0.5, 1); }
    7.3333% { opacity: 1; transform: scale(1); }
    97.5% { opacity: 1; transform: scale(1); animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
    100% { opacity: 0; transform: scale(1); }
  }

  @keyframes ir-ticket-stamp-land {
    from { opacity: 0; transform: scale(1.12) rotate(-6deg); }
    to { opacity: 1; transform: scale(1) rotate(-4deg); }
  }

  .ir-ticket--loop .ir-ticket__row--beat-1 {
    animation: ir-ticket-beat-1 12000ms linear infinite both;
  }

  .ir-ticket--loop .ir-ticket__row--beat-2 {
    animation: ir-ticket-beat-2 12000ms linear infinite both;
  }

  .ir-ticket--loop .ir-ticket__row--beat-3 {
    animation: ir-ticket-beat-3 12000ms linear infinite both;
  }

  .ir-ticket--loop .ir-ticket__pill {
    animation: ir-ticket-pill-land 12000ms linear infinite both;
  }

  .ir-ticket--stamped .ir-ticket__stamp {
    animation: ir-ticket-stamp-land 180ms var(--ease-stamp) 600ms both;
  }
}


/* ===== MessageThread ===== */
/* ==========================================================================
   Island Reply design system — MessageThread.css
   .ir-thread: the example conversation on a phone screen. The business
   speaks in deep water (`bubble-out` = `deep`), the customer in the
   sunken grey of the keypad tray, the meta lines are a job-sheet row in
   meta-mono with dotted leaders, and the avatar is a square of initials,
   like the mark, not a portrait circle. The screen is one of the three
   things in the system that carries a shadow (direction.md §5).
   Motion follows the hero beat sheet (direction.md §8) and lives only
   inside prefers-reduced-motion: no-preference; the resting rules are
   the finished thread with no typing dots. Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The scene figure: the screen plus its "Example only" caption.
   Phone-sized by nature; the page decides where it sits.
   -------------------------------------------------------------------------- */
.ir-thread-scene {
  display: grid;
  gap: var(--space-3);
  width: 100%;
  max-width: 26rem;
  margin: 0;
}

/* --------------------------------------------------------------------------
   2. The screen: ground-raised, radius-lg, 1px line, shadow-card.
   Body-sm text throughout.
   -------------------------------------------------------------------------- */
.ir-thread {
  --ir-thread-phase: 0ms;
  display: grid;
  gap: var(--space-3);
  width: 100%;
  padding: 0 var(--space-4) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--ground-raised);
  color: var(--ink);
  box-shadow: var(--shadow-card);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   3. Header: square mono avatar on ground-sunken, name + channel, Example tag.
   Hairline under it, bleeding to the screen's edges.
   -------------------------------------------------------------------------- */
.ir-thread__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 calc(-1 * var(--space-4));
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
}

/* Initials in meta-mono on ground-sunken (direction.md §9), never a portrait.
   A radius-sm square, the favicon-size shape, not a circle. */
.ir-thread__avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  background: var(--ground-sunken);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink);
}

.ir-thread__who {
  display: grid;
  gap: 0;
  min-width: 0;
  flex: 1 1 auto;
}

/* `label` style: 14px / 600 / 0.01em. */
.ir-thread__name {
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* `caption` style in ink-muted. */
.ir-thread__channel {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--ink-muted);
}

.ir-thread__head .ir-tag {
  flex: none;
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   4. Meta: the brief's "Call at 2:14 PM. Caller pressed 1 for a text." as
   centered meta-mono lines between dotted leaders in `line`, the way a
   field name sits in a ticket row. The .ir-meta class (Labels.css) sets
   the face, size, uppercase, tabular figures and the slashed zero.
   -------------------------------------------------------------------------- */
.ir-thread__meta {
  display: grid;
  grid-template-columns: minmax(var(--space-4), 1fr) auto minmax(var(--space-4), 1fr);
  align-items: center;
  column-gap: var(--space-3);
  margin: 0;
  text-align: center;
}

.ir-thread__meta::before,
.ir-thread__meta::after {
  content: "";
  height: 0;
  border-top: 1px dotted var(--line);
}

.ir-thread__meta-line {
  display: block;
}

/* --------------------------------------------------------------------------
   5. The list of bubbles. Always full height: bubbles that have not
   arrived yet are transparent, not absent, so nothing shifts.
   -------------------------------------------------------------------------- */
.ir-thread__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* --------------------------------------------------------------------------
   6. Bubbles: radius-lg with the tail corner at radius-sm (customer
   bottom-left, business bottom-right), max-width 86%, body-sm.
   The customer bubble is the keypad-tray grey; the business bubble is
   deep water, so the business always speaks in `deep`.
   -------------------------------------------------------------------------- */
.ir-bubble {
  position: relative;
  max-width: 86%;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-lg);
  overflow-wrap: break-word;
  text-wrap: pretty;
}

.ir-bubble--in {
  align-self: flex-start;
  border-bottom-left-radius: var(--radius-sm);
  background: var(--bubble-in);
  color: var(--bubble-in-ink);
}

.ir-bubble--out {
  align-self: flex-end;
  border-bottom-right-radius: var(--radius-sm);
  background: var(--bubble-out);
  color: var(--bubble-out-ink);
}

/* The sender ID that opens every business text ("Kaimana Cooling:"),
   weight 600, the system's only emphasis. */
.ir-bubble__from {
  font-weight: 600;
  font-style: normal;
}

/* --------------------------------------------------------------------------
   7. Typing dots: three radius-pill dots in the customer's ink inside a
   customer bubble. At rest (and with reduced motion) they do not exist:
   the finished thread has no dots. In motion they sit over the spot
   where the customer's first reply will land, out of the flow.
   -------------------------------------------------------------------------- */
.ir-typing {
  display: none;
}

.ir-typing__dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--bubble-in-ink);
}

/* --------------------------------------------------------------------------
   8. Motion (direction.md §8). Opt in with .ir-thread--play. One 18,000ms
   loop shared by every message; each one enters on the hero beat sheet
   through animation-delay (business 4400, typing 6800, customer 8000,
   customer 9400, business 10800), holds, and all of them crossfade out
   together over the 300ms at 17700, so the reset is a fade, not a
   rewind. The hold-to-fade point is written into each keyframe set as
   (17700 - delay) / 18000. --ir-thread-phase shifts the whole schedule
   earlier (the --solo modifier sets 3200ms, so a thread shown on its own
   starts its first text 1200ms in); the loop length and the fade do not
   change. Transform and opacity only.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .ir-thread--solo {
    --ir-thread-phase: 3200ms;
  }

  /* Enter: 240ms translateY(8px) + opacity on --ease-out. The fade segment
     is linear (a crossfade), then hidden until the next cycle. */
  @keyframes ir-thread-beat-1 {
    0% { opacity: 0; transform: translateY(8px); }
    1.334% { opacity: 1; transform: none; }
    73.889% { opacity: 1; transform: none; animation-timing-function: linear; }
    75.556%, 100% { opacity: 0; transform: none; }
  }

  @keyframes ir-thread-beat-2 {
    0% { opacity: 0; transform: translateY(8px); }
    1.334% { opacity: 1; transform: none; }
    53.889% { opacity: 1; transform: none; animation-timing-function: linear; }
    55.556%, 100% { opacity: 0; transform: none; }
  }

  @keyframes ir-thread-beat-3 {
    0% { opacity: 0; transform: translateY(8px); }
    1.334% { opacity: 1; transform: none; }
    46.111% { opacity: 1; transform: none; animation-timing-function: linear; }
    47.778%, 100% { opacity: 0; transform: none; }
  }

  @keyframes ir-thread-beat-4 {
    0% { opacity: 0; transform: translateY(8px); }
    1.334% { opacity: 1; transform: none; }
    38.333% { opacity: 1; transform: none; animation-timing-function: linear; }
    40%, 100% { opacity: 0; transform: none; }
  }

  /* Typing: enters at 6800, shows for 1200ms, leaves over --dur-1 on
     --ease-in just as the customer's reply lands at 8000. */
  @keyframes ir-thread-typing {
    0% { opacity: 0; transform: translateY(8px); }
    1.334% { opacity: 1; transform: none; }
    5.778% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
    6.667%, 100% { opacity: 0; transform: none; }
  }

  /* The dots: 900ms loop, opacity 0.35 to 1, offset 150ms each. */
  @keyframes ir-typing-dot {
    0%, 100% { opacity: 0.35; }
    50% { opacity: 1; }
  }

  .ir-thread--play .ir-thread__list > .ir-bubble {
    animation-duration: 18000ms;
    animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    animation-iteration-count: infinite;
    animation-fill-mode: backwards;
  }

  .ir-thread--play .ir-thread__list > .ir-bubble:nth-child(1) {
    animation-name: ir-thread-beat-1;
    animation-delay: calc(4400ms - var(--ir-thread-phase));
  }

  .ir-thread--play .ir-thread__list > .ir-bubble:nth-child(2) {
    animation-name: ir-thread-beat-2;
    animation-delay: calc(8000ms - var(--ir-thread-phase));
  }

  .ir-thread--play .ir-thread__list > .ir-bubble:nth-child(3) {
    animation-name: ir-thread-beat-3;
    animation-delay: calc(9400ms - var(--ir-thread-phase));
  }

  .ir-thread--play .ir-thread__list > .ir-bubble:nth-child(4) {
    animation-name: ir-thread-beat-4;
    animation-delay: calc(10800ms - var(--ir-thread-phase));
  }

  /* The typing bubble exists only while the scene plays. It is a customer
     bubble with no text, placed over the top-left of the bubble it precedes
     (the list item is position: relative), so it takes no room. */
  .ir-thread--play .ir-typing {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-lg);
    border-bottom-left-radius: var(--radius-sm);
    background: var(--bubble-in);
    animation: ir-thread-typing 18000ms var(--ease-out) calc(6800ms - var(--ir-thread-phase)) infinite backwards;
  }

  .ir-thread--play .ir-typing__dot {
    animation: ir-typing-dot 900ms var(--ease-out) infinite;
  }

  .ir-thread--play .ir-typing__dot:nth-child(2) {
    animation-delay: 150ms;
  }

  .ir-thread--play .ir-typing__dot:nth-child(3) {
    animation-delay: 300ms;
  }
}


/* ===== OwnerAlert ===== */
/* ==========================================================================
   Island Reply design system — OwnerAlert.css
   The text that lands on the owner's own phone (direction.md §9). Two
   kinds. .ir-alert is the emergency: a pink carbon-copy field in
   `urgent-soft` under a 1px `urgent` border, a header strip in `urgent`
   carrying the on-urgent meta-mono line "OWNER'S PHONE · 2:16 PM" and the
   URGENT pill from Labels.css at its right, then the brief's alert text in
   body-sm `ink`. It is the one card in the system that floats
   (`shadow-float`): the thing that just buzzed in the owner's pocket.
   .ir-alert--regular is everything else: flat `ground-raised`, 1px `line`
   border, no shadow, no pill, no red; the head is a muted mono line over a
   hairline.
   .ir-alert-scene stages them like a job folder: a manila stub strip
   (`ground-ticket`, radius-xs, a 2px `dirt` hem) with the 16px mark and
   the ticket's mono ID and time, the alerts stacked under it, the
   figcaption at the foot. The stub is paper, not a perforated ticket: the
   tear line belongs to the ticket and the report only.
   Motion: the alert is one of the two elements allowed a second beat
   (direction.md §8): slide in over --dur-2, then three translateX(2px)
   buzz pulses of 60ms, once. The rest rules below are the end state, so
   without a modifier, or with reduced motion, the alert simply sits.
   Tokens only. Transform and opacity only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The scene — a figure: stub, alerts, caption, in a tight column.
   `.ir-scene` on the same element gives the page script its pause hook.
   -------------------------------------------------------------------------- */
.ir-alert-scene {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  min-width: 0;
}

/* The stub: the ticket this alert came off. Manila, square corners, and the
   `dirt` hem on its foot (the hem is always a bottom edge, never a stripe). */
.ir-alert-scene__stub {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--ground-ticket);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-xs);
}

.ir-alert-scene__from {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* The 16px mark: the bubble whose top edge is the ridge, so the scene's one
   ridge is the sender's mark. rx="8" in the 32-unit viewBox lands at 4px
   here (radius-sm). The fills come from Logo.css (.ir-brand__square,
   .ir-brand__bubble). Clear space is the stub's own padding and gap. */
.ir-alert-scene__mark {
  flex: none;
  width: 16px;
  height: 16px;
}

/* Stub text: meta-mono (12px / 500 / 0.04em, uppercase, tabular figures,
   slashed zero) in ink-muted. The ID and time on the left, the business on
   the right; the right one may truncate on the narrowest phones. */
.ir-alert-scene__meta {
  margin: 0;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
  white-space: nowrap;
}

.ir-alert-scene__meta--end {
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: end;
}

/* The caption: `caption` style in ink-muted (same as .ir-figcaption). */
.ir-alert-scene__caption {
  margin: var(--space-1) 0 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   2. The alert — base is the emergency. The border wraps the header strip
   too, so overflow hidden clips the strip to the card's radius-md corners.
   shadow-float: one of the three shadows in the system.
   -------------------------------------------------------------------------- */
.ir-alert {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  background: var(--urgent-soft);
  border: 1px solid var(--urgent);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
  color: var(--ink);
}

/* The header strip: `urgent` field, on-urgent letters, the pill at the end.
   The strip is where the signal sits, so `urgent` and `signal` always
   travel together (the pairing rule, direction.md §9). */
.ir-alert__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--urgent);
  color: var(--on-urgent);
}

/* The head's line: meta-mono, inheriting the strip's ink. */
.ir-alert__meta {
  margin: 0;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: inherit;
}

/* The pill (Labels.css .ir-pill--urgent) keeps to the end of the strip. */
.ir-alert__pill {
  flex: none;
  margin-left: auto;
}

/* The body: the brief's alert text, body-sm (15px / 1.5) in `ink`.
   A phone number is one long token, so it may break anywhere rather than
   push the card wider than the phone. URGENT is <strong> (600 by base). */
.ir-alert__body {
  margin: 0;
  padding: var(--space-3);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink);
  overflow-wrap: anywhere;
  text-wrap: pretty;
}

/* The reply the owner types ("BOOKED L1001 <amount>") is set in mono and
   `ink`, the way FeatureList sets the BOOKED keyword: it is the line that
   becomes the stamp. */
.ir-alert__kw {
  font-family: var(--font-mono);
  font-size: 0.9em;
  font-weight: 500;
  letter-spacing: 0.04em;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink);
  white-space: nowrap;
}

@media (min-width: 40rem) {
  .ir-alert__head {
    padding: var(--space-2) var(--space-4);
  }

  .ir-alert__body {
    padding: var(--space-3) var(--space-4) var(--space-4);
  }
}

/* --------------------------------------------------------------------------
   3. The regular text — flat paper, no signal. The head is the same mono
   line in ink-muted over a `line` hairline; the body is ink-muted.
   -------------------------------------------------------------------------- */
.ir-alert--regular {
  background: var(--ground-raised);
  border-color: var(--line);
  box-shadow: none;
}

.ir-alert--regular .ir-alert__head {
  background: transparent;
  color: var(--ink-muted);
  border-bottom: 1px solid var(--line);
}

.ir-alert--regular .ir-alert__body {
  color: var(--ink-muted);
}

/* The Example tag (Labels.css .ir-tag) takes the pill's place on the regular
   text. */
.ir-alert__tag {
  flex: none;
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   4. Motion — transform and opacity only, keyframes inside no-preference.
   --enter: the hero beat, once: slide in (--dur-2, translateY(8px) and
     opacity 0 to rest on --ease-out), then at 240ms three translateX(2px)
     pulses of 60ms (the buzz, linear). 420ms in all, holds the end state.
   --loop: the standing demo, 10000ms: the same enter and buzz at the top,
     rest on the finished alert, then a 300ms fade at the end; the next
     cycle starts from the faded state, so the reset is a fade, not a
     rewind. 1% of the loop is 100ms.
   The pill does not land separately here: it rides in with its strip.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-alert-enter {
    0% {
      opacity: 0;
      transform: translateY(8px);
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    57.14% {
      opacity: 1;
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    64.29% {
      transform: translate(2px, 0);
      animation-timing-function: linear;
    }
    71.43% {
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    78.57% {
      transform: translate(2px, 0);
      animation-timing-function: linear;
    }
    85.71% {
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    92.86% {
      transform: translate(2px, 0);
      animation-timing-function: linear;
    }
    100% {
      opacity: 1;
      transform: translate(0, 0);
    }
  }

  @keyframes ir-alert-loop {
    0% {
      opacity: 0;
      transform: translateY(8px);
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    2.4% {
      opacity: 1;
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    2.7% {
      transform: translate(2px, 0);
      animation-timing-function: linear;
    }
    3% {
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    3.3% {
      transform: translate(2px, 0);
      animation-timing-function: linear;
    }
    3.6% {
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    3.9% {
      transform: translate(2px, 0);
      animation-timing-function: linear;
    }
    4.2% {
      opacity: 1;
      transform: translate(0, 0);
      animation-timing-function: linear;
    }
    97% {
      opacity: 1;
      transform: translate(0, 0);
      animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
    }
    100% {
      opacity: 0;
      transform: translate(0, 0);
    }
  }

  .ir-alert--enter {
    animation: ir-alert-enter 420ms both;
  }

  .ir-alert--loop {
    animation: ir-alert-loop 10000ms infinite;
  }
}

/* --------------------------------------------------------------------------
   5. Forced colors — the strip's fill and the pink field flatten, so the
   strip keeps a hairline under it to stay a strip. Borders survive.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-alert__head {
    border-bottom: 1px solid CanvasText;
  }
}


/* ===== QuietHoursDial ===== */
/* ==========================================================================
   Island Reply design system — QuietHoursDial.css
   .ir-dial: the quiet-hours day clock, drawn as a 24-hour time switch
   (the kind screwed to a wall beside an air handler: a dial, two trippers,
   a pointer). A service tag of shop paper: a manila `ground-ticket` stub
   on top with a `meta-mono` field and the Example tag, a `ground-raised`
   body holding the SVG dial on a `ground-sunken` face (24 `line-strong`
   ticks, the 8 am to 8 pm window a 6px `reef` arc on a `reef-soft` track,
   two `ink` trippers at its ends, a 2px `ink` hand) beside a
   `ground-sunken` queue tray ("TOMORROW · 8:00 AM") that holds the
   business bubble, a 2px `dirt` hem at the paper's foot, and under the
   paper the one ridge in view with the caption on `ground-band` water.
   Four anchors, one scene (direction.md §1, §9, §10).
   The land never moves: face, ticks, arc, trippers, labels, tray and
   caption are static. Only the hand, the bubble and the SENT label move,
   and only in transform and opacity, inside the no-preference block.
   The resting rules are the finished scene: hand at 8 am, the queued
   text in the morning slot, SENT · 8:00 AM beside it. Tokens only.
   Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The paper. A flat figure: 1px `line` border, `radius-xs` corners
   (paper, not a card), no shadow, the hem on its foot. Tabular figures
   and the slashed zero on everything inside (direction.md §4).
   -------------------------------------------------------------------------- */
.ir-dial {
  position: relative;
  width: 100%;
  max-width: 40rem;
  margin: 0;
  color: var(--ink);
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
}

.ir-dial__paper {
  overflow: hidden;
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-xs);
  background: var(--ground-raised);
}

/* --------------------------------------------------------------------------
   2. The stub: manila, the field name on the left, the Example tag on the
   right. The one place this scene shows `ground-ticket`. No perforation:
   the tear line belongs to the ticket and the report only.
   -------------------------------------------------------------------------- */
.ir-dial__stub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line);
  background: var(--ground-ticket);
}

.ir-dial__stub .ir-meta {
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   3. The body: the dial, then the tray. Stacked on phones; a row from
   40rem with the dial at a fixed 232px and the tray taking the rest.
   -------------------------------------------------------------------------- */
.ir-dial__body {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-4);
}

@media (min-width: 40rem) {
  .ir-dial__body {
    grid-template-columns: 232px minmax(0, 1fr);
    align-items: center;
    gap: var(--space-5);
    padding: var(--space-5);
  }
}

/* --------------------------------------------------------------------------
   4. The dial. viewBox 0 0 200 200, 12 am at the top, 8 am at 120deg,
   12 pm at the bottom, 8 pm at 300deg. Strokes are non-scaling so the arc
   is 6px, the hand 2px and the ticks 1.5px at every render size. 232px
   wide on phones (the labels then render at 12.2px, the floor), up to
   272px where there is room.
   -------------------------------------------------------------------------- */
.ir-dial__face {
  display: block;
  width: 100%;
  max-width: 272px;
  height: auto;
  margin-inline: auto;
  overflow: visible;
}

@media (min-width: 40rem) {
  .ir-dial__face {
    max-width: 232px;
    margin-inline: 0;
  }
}

/* The sunken face with a hairline edge. */
.ir-dial__plate {
  fill: var(--ground-sunken);
  stroke: var(--line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The track: the whole day in pale water. */
.ir-dial__track {
  fill: none;
  stroke: var(--reef-soft);
  stroke-width: 6;
  vector-effect: non-scaling-stroke;
}

/* The window: 8 am to 8 pm, the 6px `reef` arc (direction.md §9).
   Butt caps so the arc ends square under the trippers. */
.ir-dial__window {
  fill: none;
  stroke: var(--reef);
  stroke-width: 6;
  stroke-linecap: butt;
  vector-effect: non-scaling-stroke;
}

/* The trippers: two `ink` tabs pinned at 8 am and 8 pm, the way a time
   switch holds its on and off times. Fill, 4 units wide: a mark. */
.ir-dial__tripper {
  fill: var(--ink);
}

/* 24 ticks in `line-strong`, the four cardinal ones longer. */
.ir-dial__ticks {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1.5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.ir-dial__ticks--cardinal {
  stroke-width: 2;
}

/* The labels: `meta-mono` in `ink`, outside the ring. 10.5 user units,
   so 12px at a 232px dial and 14px at 272px. */
.ir-dial__label {
  fill: var(--ink);
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
}

/* The hand: 2px `ink`, round caps, a short tail through the hub. Rests
   at 8 am (120deg). Rotates about the dial's centre in user units. */
.ir-dial__hand {
  transform-box: view-box;
  transform-origin: 100px 100px;
  transform: rotate(120deg);
}

.ir-dial__hand-line {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.ir-dial__hub {
  fill: var(--ink);
}

/* --------------------------------------------------------------------------
   5. The tray: the morning slot. `ground-sunken`, `radius-sm`, a
   `meta-mono` label at the top, the business bubble, then the SENT mark
   under the bubble's right edge, where a phone prints its delivery line.
   The bubble is always in the layout (only its opacity moves), so the
   tray never changes height.
   -------------------------------------------------------------------------- */
.ir-dial__tray {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--ground-sunken);
}

.ir-dial__tray-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  min-height: 24px;
  margin: 0;
}

/* SENT · 8:00 AM: a stamp in `meta-mono` at 600, `ink` letters, a 1.5px
   `line-strong` box with paper corners, the 16px check beside the words
   (1.5px stroke, currentColor, round caps). Straight, not skewed: a
   receipt mark, not the BOOKED stamp. Sits at the tray's right edge. */
.ir-dial__sent {
  display: inline-flex;
  align-items: center;
  justify-self: end;
  gap: var(--space-1);
  margin: 0;
  padding: 2px 6px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-xs);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink);
  white-space: nowrap;
}

.ir-dial__sent-icon {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: miter;
}

/* The business bubble: `bubble-out` field, `bubble-out-ink` text at
   body-sm, `radius-lg` with the tail corner (bottom-right) at `radius-sm`,
   set to the right of the tray the way the business speaks in a thread.
   The thread's own bubble, once built, carries the same rules. */
.ir-dial__bubble {
  justify-self: end;
  max-width: calc(100% - var(--space-8));
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-lg);
  border-bottom-right-radius: var(--radius-sm);
  background: var(--bubble-out);
  color: var(--bubble-out-ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  text-wrap: pretty;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   6. The foot: the one ridge in view, then the caption on `ground-band`
   water. The figcaption is the foot; its first child is the ridge strip
   (Ridge.css sizes and colors it), its second the band. `space-4` of
   page ground between the hem and the peaks so the paper floats over
   the land. The band remaps `ink-muted` to `ink-on-band-muted`, which is
   what `.ir-figcaption` reads.
   -------------------------------------------------------------------------- */
.ir-dial__foot {
  margin: var(--space-4) 0 0;
  padding: 0;
}

.ir-dial__land {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-radius: 0 0 var(--radius-xs) var(--radius-xs);
}

.ir-dial__caption {
  max-width: 34rem;
  margin: 0;
}

/* --------------------------------------------------------------------------
   7. Motion (direction.md §8). One 12,000ms loop:
      0        hand at 8 pm, tray empty
      0–2400   hand sweeps 8 pm to 8 am (300deg to 480deg) on --ease-out
      2400     bubble enters the tray: translateY(8px) and opacity, --dur-2
      4800     bubble goes out, --dur-1 on --ease-in, while SENT · 8:00 AM
               crossfades in over --dur-2
      5040     hold on the finished morning
      11700    300ms fade reset; the hand's return happens at opacity 0
   Transform and opacity only. The resting rules above are the default,
   so with reduced motion, or the page's data-paused, the scene is the
   finished morning. .ir-scene[data-paused] * pauses it off screen.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-dial-hand {
    0% {
      transform: rotate(300deg);
      opacity: 0;
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    2.5% {
      opacity: 1;
    }
    20% {
      transform: rotate(480deg);
      opacity: 1;
      animation-timing-function: linear;
    }
    97.5% {
      transform: rotate(480deg);
      opacity: 1;
      animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
    }
    99.9% {
      transform: rotate(480deg);
      opacity: 0;
    }
    100% {
      transform: rotate(300deg);
      opacity: 0;
    }
  }

  @keyframes ir-dial-bubble {
    0%,
    20% {
      transform: translateY(8px);
      opacity: 0;
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    22% {
      transform: translateY(0);
      opacity: 1;
      animation-timing-function: linear;
    }
    40% {
      transform: translateY(0);
      opacity: 1;
      animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
    }
    41.4%,
    100% {
      transform: translateY(0);
      opacity: 0;
    }
  }

  @keyframes ir-dial-sent {
    0%,
    40% {
      opacity: 0;
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    42% {
      opacity: 1;
      animation-timing-function: linear;
    }
    97.5% {
      opacity: 1;
      animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
    }
    100% {
      opacity: 0;
    }
  }

  .ir-dial__hand {
    animation: ir-dial-hand 12000ms linear infinite;
  }

  .ir-dial__bubble {
    animation: ir-dial-bubble 12000ms linear infinite;
  }

  .ir-dial__sent {
    animation: ir-dial-sent 12000ms linear infinite;
  }
}

/* --------------------------------------------------------------------------
   8. Forced colors: SVG fills and strokes are not swapped by the OS, so
   the marks take the system ink (CanvasText, not a literal color) and
   the face keeps its edge.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-dial__plate {
    fill: Canvas;
    stroke: CanvasText;
  }

  .ir-dial__window,
  .ir-dial__ticks,
  .ir-dial__hand-line {
    stroke: CanvasText;
  }

  .ir-dial__track {
    stroke: GrayText;
  }

  .ir-dial__tripper,
  .ir-dial__hub,
  .ir-dial__label {
    fill: CanvasText;
  }
}


/* ===== SectionHeader ===== */
/* ==========================================================================
   Island Reply design system — SectionHeader.css
   The section opener and its grid. A work-order block header on shop
   paper: the field label in typewriter mono with a dotted leader running
   to the cell's edge (the way "JOB NAME ........" reads on a carbon form),
   the headline in the condensed display face, the intro in muted ink at
   a 30rem measure. Asymmetric 4/8 grid from 60rem with the opener sticky
   beside the content, like the stub that stays put while the ticket
   fills. Headlines and text are land: nothing here moves (direction.md §8).
   Tokens only. Mobile first. No ridge in here (one per viewport, never
   behind text); the band example carries its own ridge strip above it.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The grid — one column on phones; 4fr / 8fr from 60rem with the head
   sticky at space-8 so the opener keeps the section's name in view while
   the steps, cards or rules scroll past it.
   -------------------------------------------------------------------------- */
.ir-section-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  align-items: start;
}

@media (min-width: 60rem) {
  .ir-section-grid {
    grid-template-columns: 4fr 8fr;
    column-gap: var(--space-12);
  }

  .ir-section-grid > .ir-section-head {
    position: sticky;
    top: var(--space-8);
    align-self: start;
  }

  /* A wide opener spans both columns and does not stick. */
  .ir-section-grid > .ir-section-head--wide {
    grid-column: 1 / -1;
    position: static;
  }
}

/* --------------------------------------------------------------------------
   2. The head
   -------------------------------------------------------------------------- */
.ir-section-head {
  min-width: 0;
  margin: 0;
}

/* The eyebrow: a `meta-mono` field label (pair it with .ir-meta from
   Labels.css for the type and the tabular figures; this rule only lays the
   row out). The ::after is the leader: 2px dots in `line`, the dotted
   leader ink from direction.md §3, running to the cell's right edge. On
   phones the text may wrap; the leader always keeps at least space-12. */
.ir-section-head__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  min-width: 0;
}

.ir-section-head__eyebrow::after {
  content: "";
  flex: 1 1 var(--space-12);
  min-width: var(--space-12);
  height: 0;
  /* Dots sit on the x-height line of the label, not its baseline. */
  margin-top: 1px;
  border-bottom: 2px dotted var(--line);
}

/* On the band the leader takes the band's muted ink (`line` is a page
   token and sits below 1.3:1 on ground-band). The label's own color
   already remaps through --ink-muted in 00-base.css. */
.ir-band .ir-section-head__eyebrow::after {
  border-bottom-color: var(--ink-on-band-muted);
}

/* The headline: an h2 is `display-lg` from 00-base.css (32px → 48px,
   1.04, 700, -0.01em, balanced). The class only sets rhythm so a page may
   use another level with .display-lg when the outline needs it. */
.ir-section-head__title {
  margin: var(--space-3) 0 0;
  color: var(--ink);
}

/* The intro: `lead` (18px → 20px, 1.5) in ink-muted, 30rem measure. */
.ir-section-head__intro {
  max-width: 30rem;
  margin: var(--space-4) 0 0;
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-muted);
  text-wrap: pretty;
}

@media (min-width: 48rem) {
  .ir-section-head__intro {
    font-size: 20px;
  }
}

/* --------------------------------------------------------------------------
   3. --wide: an opener that stands over full-width content (the rules
   list, the FAQ, the demo). The intro takes the full lead measure (34rem).
   -------------------------------------------------------------------------- */
.ir-section-head--wide .ir-section-head__intro {
  max-width: 34rem;
}


/* ===== SiteFooter ===== */
/* ==========================================================================
   Island Reply design system — SiteFooter.css
   .ir-footer: the site footer. Markup: .ir-ridge--band-top, then
   <footer class="ir-footer ir-band">. The band is deep water, the ridge is
   its top edge, and the footer reads like the foot of a work order: the
   on-band lockup, the brief's footer line with the 8px `ohia` "Based on
   Oʻahu" dot, the three links set as ticket fields (mono field name, then
   the value), a boxed quiet-hours plate in meta-mono, and the © line under
   a hairline. Land: nothing in this file moves, so it holds no keyframes.
   Reuses .ir-band (00-base), .ir-brand--on-band (Logo), .ir-meta (Labels)
   and .ir-ridge (Ridge). Tokens only. Mobile first.
   ========================================================================== */

/* The band. The ridge above it sets margin-bottom: -1px, so the footer needs
   no top edge of its own. space-10 / space-8 on phones, space-12 / space-10
   from 48rem: a footer, not a section. */
.ir-footer {
  padding-block: var(--space-10) var(--space-8);
}

@media (min-width: 48rem) {
  .ir-footer {
    padding-block: var(--space-12) var(--space-10);
  }
}

/* --------------------------------------------------------------------------
   Layout: one column on phones; from 48rem the brand column takes the room
   and the fields column sits at the right, like the stub beside a ticket.
   -------------------------------------------------------------------------- */
.ir-footer__grid {
  display: grid;
  gap: var(--space-8);
}

@media (min-width: 48rem) {
  .ir-footer__grid {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-16);
    align-items: start;
  }
}

/* --------------------------------------------------------------------------
   Brand column: the lockup, then the footer line.
   The lockup keeps space-2 clear space inside its own box (Logo.css); the
   negative inline-start margin puts the mark's edge on the column edge so the
   mark and the line below it share one left line.
   -------------------------------------------------------------------------- */
.ir-footer__brand {
  display: grid;
  gap: var(--space-4);
  justify-items: start;
}

.ir-footer__brand .ir-brand {
  margin-inline-start: calc(-1 * var(--space-2));
}

/* The brief's footer line: `body-sm` on the band, measure 34rem. */
.ir-footer__line {
  margin: 0;
  max-width: 34rem;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-on-band);
}

/* "Based on Oʻahu." stays on one line, so the dot never ends a line alone. */
.ir-footer__based {
  white-space: nowrap;
}

/* The 8px `ohia` dot (direction.md §3: once per composition). A dot, never an
   area; decorative, hidden from assistive tech, the words carry the meaning. */
.ir-footer__dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-inline-end: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--ohia);
  vertical-align: 0.05em;
}

/* --------------------------------------------------------------------------
   Fields: the three links as ticket rows. A mono field name in the band's
   muted ink (via .ir-meta, which remaps inside .ir-band), then the link.
   The link is `label` type (nav links), ink-on-band with a `reef` underline
   from the base layer, on a 44px tap target.
   -------------------------------------------------------------------------- */
.ir-footer__fields {
  --ir-footer-key: 6.5rem;
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ir-footer__field {
  display: grid;
  grid-template-columns: var(--ir-footer-key) minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: baseline;
}

/* Field names (field name, not a heading): .ir-meta sets the type and the
   muted ink. The 1px nudge seats the 12px mono on the link's baseline. */
.ir-footer__key {
  padding-top: 1px;
}

.ir-footer__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: var(--space-2);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   The hours plate: the quiet-hours rule from the brief, set like the hours
   sign on a shop door. A 1.5px box in the band's muted ink (line-strong is
   below 3:1 on the dark band), paper corners, meta-mono rows: a field name
   in the muted ink, the value in the band ink.
   -------------------------------------------------------------------------- */
.ir-footer__hours {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  justify-self: start;
  margin: var(--space-4) 0 0;
  padding: var(--space-2) var(--space-3);
  border: 1.5px solid var(--ink-on-band-muted);
  border-radius: var(--radius-xs);
}

/* dt and dd are direct children, so both values share one column and the
   widest field name sets it. */
.ir-footer__hours dt {
  margin: 0;
}

.ir-footer__hours dd {
  margin: 0;
  color: var(--ink-on-band);
}

/* --------------------------------------------------------------------------
   Foot: a hairline, then the © line in `caption` type, muted band ink.
   The hairline is the band ink at 24%, derived from the token (a literal
   would not follow the theme).
   -------------------------------------------------------------------------- */
.ir-footer__foot {
  margin-top: var(--space-8);
  padding-top: var(--space-4);
  border-top: 1px solid color-mix(in srgb, var(--ink-on-band) 24%, transparent); /* translucent band ink */
}

.ir-footer__legal {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--ink-on-band-muted);
}

/* --------------------------------------------------------------------------
   Forced colors: the dot and the plate's box would flatten; give them the
   system ink so the plate keeps its edge and the dot stays a dot.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-footer__dot {
    background: CanvasText;
  }

  .ir-footer__hours,
  .ir-footer__foot {
    border-color: CanvasText;
  }
}


/* ===== SiteHeader ===== */
/* ==========================================================================
   Island Reply design system — SiteHeader.css
   .ir-header: the one-page site's header. Plain by rule: `ground-raised`,
   a 1px `line` foot, 4.25rem min-height, and the lockup carries the
   identity (the ridge-topped bubble beside the wordmark). No dirt hem
   (hems belong to paper), no ridge (one per viewport, and the hero keeps
   it), no shadow, no hamburger (the anchors repeat in the footer).
   Order: skip link first, lockup left, a mono "Based on Oʻahu" stub beside
   it, the three anchors from 56rem, the small primary button right.
   The bar inside is a size container, so the breakpoints read the header's
   own width: the same header lays out right inside a 375px frame and at
   full width. The land never moves: nothing here animates. Tokens only.
   Mobile first.
   ========================================================================== */

/* The header. Static (not sticky): on a phone the page is the product, and
   a 4.25rem bar would cost a third of the hero. The page may make it
   sticky. Not positioned and not a container, so the skip link inside it
   positions against the page and stays off-screen until focused. */
.ir-header {
  min-height: 4.25rem;
  background: var(--ground-raised);
  color: var(--ink);
  border-bottom: 1px solid var(--line);
}

/* The bar: the container the breakpoints below measure. */
.ir-header__bar {
  container-type: inline-size;
  container-name: ir-header;
}

/* The row: an .ir-wrap (gutter + 72rem) laid out as one flex line.
   Border-box, so the 1px foot sits inside the 4.25rem. */
.ir-header__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: calc(4.25rem - 1px);
  padding-block: var(--space-2);
}

/* --------------------------------------------------------------------------
   The lockup. Logo.css owns .ir-brand; this class only sets the size the
   header wants through the lockup's own custom property: the compact 24px
   lockup on phones, the 32px header lockup from 40rem. The negative left
   margin lines the mark's edge up with the gutter while the lockup's
   built-in clear space (space-2) stays inside its box.
   -------------------------------------------------------------------------- */
.ir-header .ir-header__brand {
  --ir-brand-size: 24px;
  flex: none;
  gap: var(--space-2);
  margin-left: calc(-1 * var(--space-2));
}

@container ir-header (min-width: 40rem) {
  .ir-header .ir-header__brand {
    --ir-brand-size: 32px;
    gap: var(--space-3);
  }
}

/* --------------------------------------------------------------------------
   The stub: `meta-mono` in ink-muted behind a 1px `line` rule, the way a
   ticket stub carries its ID beside the title. Reads "Based on Oʻahu" (the
   brief's footer fact, nothing invented). Hidden until the nav has room.
   -------------------------------------------------------------------------- */
.ir-header__stub {
  display: none;
  flex: none;
  margin: 0;
  padding-left: var(--space-3);
  border-left: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   The nav: three in-page anchors in the `label` style, `ink`, no underline
   at rest. Hover underlines in the same ink (instant, no transition: hover
   transitions are background-color only). The anchor whose section is in
   view carries aria-current and a 2px `dirt` underline: red is a mark, a
   line under a word, the same ink as the open FAQ title. Hidden below
   56rem; there is no hamburger, the footer repeats the anchors.
   .is-hover mirrors :hover for previews only.
   -------------------------------------------------------------------------- */
.ir-header__nav {
  display: none;
  margin-left: auto;
}

.ir-header__list {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ir-header__link {
  display: inline-block;
  padding: var(--space-3) 0;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.ir-header__link:hover,
.ir-header__link.is-hover {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-decoration-color: currentColor;
  text-underline-offset: 0.35em;
}

.ir-header__link[aria-current] {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-decoration-color: var(--dirt);
  text-underline-offset: 0.35em;
}

/* The button: Button.css owns it (.ir-button--small, 44px). Right-aligned
   on phones by the auto margin; from 56rem the nav takes that margin and
   the button sits a step past it. */
.ir-header__cta {
  flex: none;
  margin-left: auto;
}

@container ir-header (min-width: 56rem) {
  .ir-header__stub {
    display: block;
  }

  .ir-header__nav {
    display: block;
  }

  .ir-header__cta {
    margin-left: var(--space-5);
  }
}

/* Forced colors: the nav's rest state has no underline, so the current
   marker and hover keep their underline (decoration color follows the
   system ink); the foot rule stays a border. Nothing else to swap. */
@media (forced-colors: active) {
  .ir-header__link[aria-current] {
    text-decoration-color: currentColor;
  }
}


/* ===== SpecList ===== */
/* ==========================================================================
   Island Reply design system — SpecList.css
   .ir-spec: "Rules every customer text follows". A definition list read
   like the spec block on a work order: a 2px `ink` rule across the top,
   one hairline per rule, a mono rule number in the term gutter (01, 02 …
   set by a CSS counter, tabular, slashed zero), terms at 600, values in
   `ink-muted`, and the shop-paper foot: a 2px `dirt` hem. Static by rule:
   it is content, so nothing here moves and no keyframes live here.
   Markup: dl.ir-spec > div.ir-spec__row > dt.ir-spec__term + dd.ir-spec__desc
   Tokens only. Mobile first; the two-column row starts at 40rem.
   ========================================================================== */

/* The sheet head (optional): one `meta-mono` line in `ink-muted` directly
   above the ink rule, like the stub line on a work order. Two short facts
   separated by a middle dot; the page supplies the words. */
.ir-spec__head {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
}

/* The list. Top rule in `ink` (the sheet's header line), hem in `dirt`
   (the paper's foot: the only place red appears here, 2px, never a frame). */
.ir-spec {
  margin: 0;
  padding: 0;
  border-top: 2px solid var(--ink);
  border-bottom: 2px solid var(--dirt);
  counter-reset: ir-spec;
}

/* One rule. Stacked on phones: term, then value. A hairline below each
   row except the last, which hands off to the hem. */
.ir-spec__row {
  display: grid;
  row-gap: var(--space-1);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--line);
  counter-increment: ir-spec;
}

.ir-spec__row:last-child {
  border-bottom: 0;
}

/* The term: `body` size at 600 in `ink`, with the rule number before it.
   Baseline alignment keeps the 12px mono number on the term's first line. */
.ir-spec__term {
  display: flex;
  align-items: baseline;
  margin: 0;
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--ink);
}

/* The rule number: `meta-mono` in `ink-muted`, two digits with a slashed
   zero, like line items on a ticket. Generated here so the page supplies
   only the eight rows; the order of the brief is the numbering. */
.ir-spec__term::before {
  content: counter(ir-spec, decimal-leading-zero);
  flex: none;
  min-width: 2ch;
  margin-right: var(--space-2);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
}

/* The value: `ink-muted`, measure capped so a wide row stays readable.
   Links inside keep the base `link` style and ring. */
.ir-spec__desc {
  margin: 0;
  max-width: 60ch;
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   From 40rem the row is two columns: 12rem for the number and term, the
   rest for the value (the current site's proportion, kept). The value's
   first line sits level with the term's.
   -------------------------------------------------------------------------- */
@media (min-width: 40rem) {
  .ir-spec__row {
    grid-template-columns: 12rem minmax(0, 1fr);
    column-gap: var(--space-6);
    row-gap: 0;
    align-items: start;
    padding-block: var(--space-5);
  }

  /* The leader: a dotted `line` from the end of the term toward the value,
     the way a parts list runs a field name out to its figure. It has no
     basis of its own and its margins net to -8px, so it never makes a term
     wrap; it fills whatever the term leaves, starts `space-2` after the
     words and runs on into the column gap, stopping `space-2` short of the
     value. Decorative: generated, no text. On phones the term stacks, so
     there is no leader. */
  .ir-spec__term::after {
    content: "";
    flex: 1 1 0;
    align-self: baseline;
    min-width: 0;
    height: 0;
    margin: 0 calc(var(--space-2) - var(--space-6)) 0.3em var(--space-2);
    border-top: 1px dotted var(--line);
  }
}

/* --------------------------------------------------------------------------
   On the band: `dirt` is never a mark on `ground-band`, so the hem takes
   the band's muted ink; the top rule already follows the remapped --ink.
   -------------------------------------------------------------------------- */
.ir-band .ir-spec {
  border-bottom-color: var(--ink-on-band-muted);
}

/* Print: the rules are the fine print; keep every row on one sheet. */
@media print {
  .ir-spec__row {
    break-inside: avoid;
  }
}


/* ===== SplitPanel ===== */
/* ==========================================================================
   Island Reply design system — SplitPanel.css
   .ir-split: "Your part / Our part". One job sheet torn down the middle by
   a 1px `line` grid. The owner's half sits on pale dirt (`dirt-soft`) and
   carries the 2px `dirt` hem at its foot, the way work pants pick up red
   dirt by the end of a day; our half sits on pale water (`reef-soft`).
   Each half opens with a meta-mono stub ("01 · YOU", "02 · US"), a
   heading-3 title and a body-sm list with the system's 16px check beside
   every item (1.5px stroke, currentColor, round caps, miter joins).
   Flat by rule: 1px `line` borders, paper corners (`radius-xs`), no shadow.
   The sheet is land: nothing in it moves, so no keyframes live here.
   Tokens only. Mobile first: stacked at 360px, two columns from 40rem.
   Reuses .ir-meta from Labels.css for the stub; nothing is redefined.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The sheet. A one-column grid on phones; the halves carry their own
   1px `line` borders so the shared edge is always exactly one line.
   -------------------------------------------------------------------------- */
.ir-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  border-radius: var(--radius-xs);
  overflow: hidden;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   2. The halves. Each is a bordered field with its own ground; the title
   and list below inherit `ink`, which holds 13.1 on dirt-soft and 13.6 on
   reef-soft in light, 12.7 and 11.6 in dark.
   -------------------------------------------------------------------------- */
.ir-split__col {
  display: grid;
  grid-template-rows: auto auto 1fr;
  gap: 0;
  min-width: 0;
  padding: var(--space-5) var(--space-4) var(--space-6);
  border: 1px solid var(--line);
}

/* Your part: pale dirt, and the hem. The 2px `dirt` foot is the only
   place red appears here; it is a line, never an area. */
.ir-split__col--yours {
  background: var(--dirt-soft);
  border-bottom: 2px solid var(--dirt);
}

/* Our part: pale water. Stacked under the hem on phones, so it drops its
   own top line; the hem is the divider between the two halves. */
.ir-split__col--ours {
  background: var(--reef-soft);
  border-top: 0;
}

/* --------------------------------------------------------------------------
   3. The head: the mono stub, then the heading-3 title.
   The stub is .ir-meta (meta-mono, ink-muted, tabular figures and slashed
   zero, so "01" and "02" line up and the zero reads as a zero).
   -------------------------------------------------------------------------- */
.ir-split__meta {
  margin: 0 0 var(--space-2);
}

.ir-split__title {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}

/* --------------------------------------------------------------------------
   4. The list: body-sm items, each with the check at its left edge.
   -------------------------------------------------------------------------- */
.ir-split__list {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  align-content: start;
}

.ir-split__item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  text-wrap: pretty;
}

/* The check: the system's 16px icon on the 24px grid. Stroke 2.25 in
   viewBox units renders as 1.5px at 16px (direction.md §9). It sits on
   the first line's vertical centre: (1.5em line box - 16px) / 2. */
.ir-split__check {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: calc((1.5em - 16px) / 2);
  fill: none;
  stroke: currentColor;
  stroke-width: 2.25;
  stroke-linecap: round;
  stroke-linejoin: miter;
}

/* --------------------------------------------------------------------------
   5. Two columns from 40rem. The halves sit side by side and stretch to one
   height; the shared vertical edge is Your part's right line, so Our part
   drops its left line and takes its top line back.
   -------------------------------------------------------------------------- */
@media (min-width: 40rem) {
  .ir-split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .ir-split__col {
    padding: var(--space-6);
  }

  .ir-split__col--ours {
    border-top: 1px solid var(--line);
    border-left: 0;
  }
}

@media (min-width: 60rem) {
  .ir-split__col {
    padding: var(--space-8);
  }

  .ir-split__list {
    gap: var(--space-4);
  }
}

/* --------------------------------------------------------------------------
   6. Forced colors: the soft grounds flatten, so the borders carry the
   grid and the hem keeps its weight. CanvasText is the system ink.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-split__col {
    border-color: CanvasText;
  }

  .ir-split__col--yours {
    border-bottom-color: CanvasText;
  }
}


/* ===== Steps ===== */
/* ==========================================================================
   Island Reply design system — Steps.css
   .ir-steps: the five "How it works" steps, set as a job-ticket timeline.
   An <ol> of .ir-step rows. Each row is a 3rem `deep` square carrying the
   step number in the display face (the mark's square, with a number where
   the bubble is), a 2px `line` spine running square to square, a
   `meta-mono` stub above the title (the field name and the example's
   time or ID, the way a work order logs each stage), a heading-3 title
   and `ink-muted` text at a 38rem measure. The list closes on a 2px
   `dirt` hem, the way paper does in this system. Step 5 is the emergency
   step: the URGENT pill after its title and a 2px `urgent` ring on its
   square, so `signal` and `urgent` arrive together (direction.md §3).
   The land never moves: titles and text are visible from first paint.
   Only the square, the stub and the pill enter when a page script adds
   .is-in to a step; with no class, or reduced motion, every step is at
   rest (direction.md §8). Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The list. Numbering comes from a CSS counter so the page never types
   a digit. The hem is the list's foot: straight, full width, bottom only.
   -------------------------------------------------------------------------- */
.ir-steps {
  counter-reset: ir-step;
  margin: 0;
  padding: 0 0 var(--space-6);
  list-style: none;
  border-bottom: 2px solid var(--dirt);
}

/* --------------------------------------------------------------------------
   2. A step: a two-column grid, the square in column 1 and the body in
   column 2. The space below each step is where the spine runs.
   -------------------------------------------------------------------------- */
.ir-step {
  position: relative;
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  column-gap: var(--space-4);
  align-items: start;
  margin: 0;
  padding: 0 0 var(--space-8);
}

.ir-step:last-child {
  padding-bottom: 0;
}

/* The square: 3rem, `deep`, radius-md (the mark's square at 32px has the
   same corner), the number in the display face at 700 with tabular
   figures. A grid item, so it sits in column 1 and the body cannot wrap
   under it. */
.ir-step::before {
  counter-increment: ir-step;
  content: counter(ir-step);
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-md);
  background: var(--deep);
  color: var(--on-deep);
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.01em;
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

/* The spine: a 2px `line` leader from the foot of this square to the top
   of the next. Drawn as a border so it survives forced colors. The last
   step has nothing below it: the hem is the end of the paper. */
.ir-step::after {
  content: "";
  position: absolute;
  top: 3rem;
  bottom: 0;
  left: calc(1.5rem - 1px);
  width: 0;
  border-left: 2px solid var(--line);
}

.ir-step:last-child::after {
  content: none;
}

.ir-step__body {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
}

/* --------------------------------------------------------------------------
   3. The stub: a `meta-mono` field name with the example's time or ID
   (.ir-meta from Labels.css), and on the first step the Example tag
   (.ir-tag) pushed to the row's right end, which is the top-right of the
   list. 24px tall so the title below lands its line box on the square's
   foot (24 + 24 = 3rem).
   -------------------------------------------------------------------------- */
.ir-step__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  min-height: var(--space-6);
  margin: 0;
}

.ir-step__meta .ir-tag {
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   4. Title and text. heading-3 (direction.md §4) in `ink`; the text in
   `ink-muted` at a 38rem measure. The URGENT pill rides the title line
   of the emergency step, `space-2` after the last word.
   -------------------------------------------------------------------------- */
.ir-step__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}

.ir-step__title .ir-pill {
  margin-left: var(--space-2);
  vertical-align: middle;
}

.ir-step__text {
  max-width: 38rem;
  margin: var(--space-2) 0 0;
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   5. The emergency step. A 2px `urgent` ring on the square with a 2px gap
   of ground inside it, so the red reads against the page (urgent on
   ground 5.97 light, 5.90 dark), not against `deep`. The square keeps its
   3rem footprint; the deep field inside is 40px. `urgent` is here only
   because the pill is in the same row (direction.md §3, §10 rule 3).
   -------------------------------------------------------------------------- */
.ir-step--urgent::before {
  padding: 2px;
  border: 2px solid var(--urgent);
  background-clip: content-box;
}

/* --------------------------------------------------------------------------
   6. Wider screens: a little more air between square and body, and
   between steps. The square stays 3rem at every width.
   -------------------------------------------------------------------------- */
@media (min-width: 48rem) {
  .ir-step {
    column-gap: var(--space-5);
    padding-bottom: var(--space-10);
  }

  .ir-step:last-child {
    padding-bottom: 0;
  }
}

/* --------------------------------------------------------------------------
   7. Motion (direction.md §8). The resting rules above are the finished
   state, so with no .is-in, with JavaScript off, or with reduced motion,
   every step is simply there. When a page script adds .is-in to a step
   (at 40% visibility, the scene rule), the square enters on the system's
   enter beat (translateY(8px) and opacity over --dur-2 on --ease-out),
   the stub follows one --dur-1 later, and on the emergency step the pill
   lands on the stamp beat (ir-pill-land from Labels.css, 180ms on
   --ease-stamp) after the square has settled. Titles and text never move.
   Transform and opacity only.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-step-enter {
    from {
      transform: translateY(8px);
      opacity: 0;
    }
    to {
      transform: translateY(0);
      opacity: 1;
    }
  }

  .ir-step.is-in::before {
    animation: ir-step-enter var(--dur-2) var(--ease-out) both;
  }

  .ir-step.is-in .ir-step__meta {
    animation: ir-step-enter var(--dur-2) var(--ease-out) var(--dur-1) both;
  }

  .ir-step.is-in .ir-pill--urgent {
    animation: ir-pill-land 180ms var(--ease-stamp) var(--dur-2) both;
  }
}

/* --------------------------------------------------------------------------
   8. Forced colors: fills are flattened by the OS, so the square keeps an
   edge from a CanvasText border (the system ink, not a literal color).
   The spine and the hem are borders already and survive on their own.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-step::before {
    border: 2px solid CanvasText;
  }
}


/* ===== StopLock ===== */
/* ==========================================================================
   Island Reply design system — StopLock.css
   .ir-stoplock: the STOP moment (direction.md §8, §9). A short thread in
   its own frame: the brief's first business text, the customer's "STOP",
   then the lock bar across the foot where a reply field would be. STOP is
   compliance, not an emergency: no red anywhere in this block.

   Shop paper and land, the four anchors: a manila `ground-ticket` job
   strip clipped to the top of the thread (the job's stub, L1001), a 2px
   `dirt` hem on the frame's foot, `meta-mono` stamps (stub, "Pressed 1",
   the lock bar), and the frame standing on the Koʻolau ridge over a
   `ground-band` foot that carries the caption. The ridge is the system's
   one path (Ridge.css); the frame, the land and the water never move.
   Only the STOP bubble and the lock bar move, and only on --play.

   Reuses .ir-meta, .ir-tag, .ir-tag--paper (Labels), .ir-ridge,
   .ir-ridge__land, .ir-ridge--band-top (Ridge), .ir-band and
   .ir-figcaption (00-base). Does not depend on MessageThread.css.
   Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The block — a <figure class="ir-stoplock ir-scene">. Block so the foot
   (ridge + water) can run full width under the frame.
   -------------------------------------------------------------------------- */
.ir-stoplock {
  display: block;
  margin: 0;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   2. The frame — the thread's screen. `ground-raised`, `radius-lg` (the
   phone/thread frame), `shadow-card` (one of the system's three shadows),
   a 1px `line` edge so it still has a rim in dark, and the 2px `dirt` hem
   on its foot: straight, full width, bottom edge only. overflow hidden
   clips the stub and the lock bar to the corners.
   -------------------------------------------------------------------------- */
.ir-stoplock__frame {
  overflow: hidden;
  margin: 0;
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-lg);
  background: var(--ground-raised);
  box-shadow: var(--shadow-card);
}

/* --------------------------------------------------------------------------
   3. The stub — a manila job strip clipped to the top of the thread. The
   only `ground-ticket` in the scene. Left: `.ir-meta` "L1001 · 2:14 PM";
   right: the Example tag on `dirt-soft` (`.ir-tag--paper`), exactly as it
   sits on a ticket stub. No perforation: the perf belongs to the ticket
   and the report only.
   -------------------------------------------------------------------------- */
.ir-stoplock__stub {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--line);
  background: var(--ground-ticket);
}

.ir-stoplock__stub .ir-meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   4. The head — who is texting. A 28px initials avatar (`meta-mono` on
   `ground-sunken`, `radius-sm`; never a portrait), the business name in
   `label`, and a `meta-mono` note at the right ("Pressed 1").
   -------------------------------------------------------------------------- */
.ir-stoplock__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
}

.ir-stoplock__avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  background: var(--ground-sunken);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink);
}

.ir-stoplock__name {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ir-stoplock__head .ir-meta {
  flex: none;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   5. The thread — an <ol role="list"> of messages. Business bubbles sit
   right (`bubble-out`, tail corner bottom-right); the customer's sits left
   (`bubble-in`, tail corner bottom-left). `body-sm` text, `radius-lg`
   with the tail-side corner at `radius-sm` (direction.md §9).
   -------------------------------------------------------------------------- */
.ir-stoplock__thread {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: var(--space-4);
  list-style: none;
}

.ir-stoplock__msg {
  display: flex;
  margin: 0;
  min-width: 0;
}

.ir-stoplock__msg--out {
  justify-content: flex-end;
}

.ir-stoplock__msg--in {
  justify-content: flex-start;
}

.ir-stoplock__bubble {
  max-width: 86%;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-lg);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: 0;
  overflow-wrap: anywhere;
  text-wrap: pretty;
}

.ir-stoplock__msg--out .ir-stoplock__bubble {
  border-bottom-right-radius: var(--radius-sm);
  background: var(--bubble-out);
  color: var(--bubble-out-ink);
}

.ir-stoplock__msg--in .ir-stoplock__bubble {
  border-bottom-left-radius: var(--radius-sm);
  background: var(--bubble-in);
  color: var(--bubble-in-ink);
}

/* "STOP" is the one word the whole scene turns on: weight 600, the
   system's only emphasis, with the tracking a typed keyword gets. */
.ir-stoplock__msg--stop .ir-stoplock__bubble {
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   6. The lock bar — `ground-sunken` across the foot of the frame, where a
   reply field would be. A 16px octagon (2px `ink` stroke, round caps,
   miter joins) beside `meta-mono` "STOPPED · NO MORE TEXTS" in
   `ink-muted`. No red: STOP is compliance.
   -------------------------------------------------------------------------- */
.ir-stoplock__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--ground-sunken);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
}

.ir-stoplock__octagon {
  display: block;
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: miter;
}

/* --------------------------------------------------------------------------
   7. The foot — the land and the water under the frame. A <figcaption>
   holding the ridge strip (Ridge.css draws it; `.ir-ridge--band-top` pulls
   the band under it) and a `ground-band` water block with the caption in
   `.ir-figcaption`, which reads `ink-on-band-muted` through the band's
   remap. One ridge per viewport: see --flat for scenes that share a
   viewport with a band.
   -------------------------------------------------------------------------- */
.ir-stoplock__foot {
  display: block;
  margin: var(--space-6) 0 0;
  padding: 0;
}

.ir-stoplock__water {
  margin: 0;
  padding: 0;
}

.ir-stoplock__water.ir-band {
  padding: var(--space-4) var(--space-5) var(--space-5);
}

.ir-stoplock__caption {
  display: block;
  margin: 0;
  max-width: 34rem;
}

/* --flat: the page omits the ridge svg and the `ir-band` class, and the
   caption sits on the page ground a little under the frame. Use it where
   a band is already in the viewport (never two ridges visible at once). */
.ir-stoplock--flat .ir-stoplock__foot {
  margin-top: var(--space-3);
}

/* --------------------------------------------------------------------------
   8. Wider frames — from 40rem the thread gets a little more air and the
   bubbles a tighter measure. Nothing else changes; the scene is designed
   phone-sized and stays that way.
   -------------------------------------------------------------------------- */
@media (min-width: 40rem) {
  .ir-stoplock__thread {
    padding: var(--space-5) var(--space-4);
  }

  .ir-stoplock__bubble {
    max-width: 80%;
  }
}

/* --------------------------------------------------------------------------
   9. Motion — opt-in with .ir-stoplock--play. The resting rules above are
   the locked thread, so without the modifier, with reduced motion, or with
   JavaScript off, the scene is simply finished: STOP sent, bar down.

   9000ms loop. 0 to 1200: the business text alone. 1200: the STOP bubble
   enters (--dur-2 on --ease-out, 8px up and in). 1440, 240ms later: the
   lock bar slides up the same way. Then rest until 8700. 8700 to 9000: a
   300ms opacity fade on --ease-in resets to the start, never a rewind.
   Transform and opacity only; layout never changes. The pause hook is the
   base layer's .ir-scene[data-paused] *.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-stoplock-stop {
    0% {
      opacity: 0;
      transform: translateY(8px);
    }
    13.333% {
      opacity: 0;
      transform: translateY(8px);
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    16% {
      opacity: 1;
      transform: translateY(0);
    }
    96.667% {
      opacity: 1;
      transform: translateY(0);
      animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
    }
    100% {
      opacity: 0;
      transform: translateY(0);
    }
  }

  @keyframes ir-stoplock-bar {
    0% {
      opacity: 0;
      transform: translateY(8px);
    }
    16% {
      opacity: 0;
      transform: translateY(8px);
      animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
    }
    18.667% {
      opacity: 1;
      transform: translateY(0);
    }
    96.667% {
      opacity: 1;
      transform: translateY(0);
      animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
    }
    100% {
      opacity: 0;
      transform: translateY(0);
    }
  }

  .ir-stoplock--play .ir-stoplock__msg--stop {
    animation: ir-stoplock-stop 9000ms linear infinite;
  }

  .ir-stoplock--play .ir-stoplock__bar {
    animation: ir-stoplock-bar 9000ms linear infinite;
  }
}


/* ===== WeeklyReport ===== */
/* ==========================================================================
   Island Reply design system — WeeklyReport.css
   .ir-report: the Monday report as shop paper. A manila sheet
   (ground-ticket, radius-xs, 1px `line` border, 2px `dirt` hem) that
   opens with a perforated stub: the 16px mark as a letterhead beside
   "WEEK OF · EXAMPLE" in meta-mono, the Example tag on dirt-soft at the
   right, then the tear line of 4px holes at 12px pitch (direction.md §7).
   Under the perf: heading-3 "Weekly report" and three ledger rows (Calls
   caught, Jobs booked, Revenue recovered), each a 600 label, a dotted
   leader and a 3.5rem blank to write the owner's own number on, with a
   ground-sunken track beneath whose `reef` fill shows the layout only.
   The Jobs booked row is the booked row: booked-soft tint, `booked` ink
   (direction.md §3). No figures, no $ sign, no counters anywhere. The
   figcaption sits on the paper as fine print above the hem.
   Motion: the fills scaleX in from 0 over --dur-3, once, when the page
   adds .ir-report--play; the rest state (full fills) is the default and
   the reduced-motion scene. The paper, the perf and the words never move.
   Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The paper — a figure. Transparent itself so the perf's holes show
   the page ground; the stub, tear and body paint the manila. Tabular
   figures and slashed zeros by rule (direction.md §4), even though the
   only figures here are the stub's.
   -------------------------------------------------------------------------- */
.ir-report {
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  margin: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-xs);
  box-shadow: none;
  overflow: hidden;
  color: var(--ink);
  font-family: var(--font-body);
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
}

/* --------------------------------------------------------------------------
   2. The stub — manila strip above the tear line. Left: the 16px mark
   (Logo.css .ir-brand--xs, mark alone, with its own space-2 clear space)
   and the meta-mono "WEEK OF · EXAMPLE". Right: the Example tag on
   dirt-soft (Labels.css .ir-tag--paper). The left padding is space-2 so
   the mark's visual edge lands on the same 16px line as the title below.
   -------------------------------------------------------------------------- */
.ir-report__stub {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-1) var(--space-4) var(--space-1) var(--space-2);
  background: var(--ground-ticket);
}

.ir-report__stub .ir-brand {
  flex: none;
}

/* The stub text: Labels' .ir-meta carries the type; this only places it. */
.ir-report__id {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ir-report__stub .ir-tag {
  flex: none;
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   3. The tear line — 4px of manila with 4px holes at 12px pitch punched
   through to the page ground (the figure is transparent beneath). The
   radial gradient is a mask, so #000 is mask alpha ("keep"), not a
   color. `space` lays whole tiles edge to edge with no clipped hole, so
   the end insets are equal; with the space-1 side margins the first and
   last holes sit 8px from the paper's edge. Perf at the top only; the
   hem is at the foot; the two never touch (direction.md §7).
   -------------------------------------------------------------------------- */
.ir-report__tear {
  flex: none;
  height: 4px;
  margin: 0 var(--space-1);
  background: var(--ground-ticket);
  -webkit-mask-image: radial-gradient(circle 2px, transparent 98%, #000 100%); /* mask alpha, not a color */
  mask-image: radial-gradient(circle 2px, transparent 98%, #000 100%); /* mask alpha, not a color */
  -webkit-mask-size: 12px 4px;
  mask-size: 12px 4px;
  -webkit-mask-position: 50% 0;
  mask-position: 50% 0;
  -webkit-mask-repeat: space no-repeat;
  mask-repeat: space no-repeat;
}

/* --------------------------------------------------------------------------
   4. The body — manila below the perf. The title is heading-3 (display
   600, 20px, 22px from 48rem). Body padding is space-4; rows bleed back
   to the paper's inner edges so the booked tint runs full width.
   -------------------------------------------------------------------------- */
.ir-report__body {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-4) var(--space-4) var(--space-2);
  background: var(--ground-ticket);
}

.ir-report__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: 20px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: balance;
}

/* --------------------------------------------------------------------------
   5. The rows — a <dl>, one <div> per row: dt label, dd blank, dd track.
   Hairlines in `line` between rows, like the ruled lines on a ledger.
   -------------------------------------------------------------------------- */
.ir-report__rows {
  display: grid;
  gap: 0;
  margin: 0 calc(-1 * var(--space-4));
  padding: 0;
  border-top: 1px solid var(--line);
}

.ir-report__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 3.5rem;
  grid-template-areas:
    "label blank"
    "track track";
  align-items: baseline;
  column-gap: var(--space-3);
  row-gap: var(--space-2);
  min-width: 0;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
}

/* The booked row: booked-soft tint, `booked` ink (7.24 light, 9.40 dark).
   The one place the old yellow still shows, as a stamp tint, not hi-vis. */
.ir-report__row--booked {
  background: var(--booked-soft);
  color: var(--booked);
}

/* The label: body-sm at 600, then a dotted leader in `line` that runs to
   the blank. The leader is a flex item with no text, so its baseline is
   synthesized from its bottom edge: the dots sit on the label's baseline. */
.ir-report__label {
  grid-area: label;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 600;
  letter-spacing: 0;
  color: inherit;
}

.ir-report__label::after {
  content: "";
  flex: 1 1 auto;
  min-width: var(--space-4);
  height: 0;
  border-bottom: 1px dotted var(--line);
}

/* The blank: 3.5rem of rule to write on. The dd has no in-flow text (the
   sr-only span is out of flow), so its baseline is its bottom edge; the
   1px nudge drops the rule a hair under the label's baseline, where a
   form's blank sits. line-strong: 3:1 on manila in both themes. */
.ir-report__blank {
  grid-area: blank;
  position: relative;
  top: 1px;
  width: 3.5rem;
  height: 0;
  margin: 0;
  border-bottom: 1.5px solid var(--line-strong);
}

.ir-report__row--booked .ir-report__blank {
  border-bottom-color: currentColor;
}

/* The track: 8px of ground-sunken, radius-xs. The fill is `reef`, a
   layout-only width per row set by its modifier; it carries no value.
   transform-origin left so the scale reads as a bar filling in. */
.ir-report__track {
  grid-area: track;
  position: relative;
  height: var(--space-2);
  margin: 0;
  background: var(--ground-sunken);
  border-radius: var(--radius-xs);
  overflow: hidden;
}

.ir-report__fill {
  display: block;
  height: 100%;
  width: 60%;
  background: var(--reef);
  border-radius: var(--radius-xs);
  transform: scaleX(1);
  transform-origin: 0 50%;
}

.ir-report__fill--a {
  width: 68%;
}

.ir-report__fill--b {
  width: 44%;
}

.ir-report__fill--c {
  width: 80%;
}

/* --------------------------------------------------------------------------
   6. The caption — fine print on the paper above the hem. Base's
   .ir-figcaption carries the type (caption, ink-muted); this places it.
   -------------------------------------------------------------------------- */
.ir-report__caption {
  margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--ground-ticket);
}

/* --------------------------------------------------------------------------
   7. Wider paper — from 48rem the title steps to 22px and the body gets
   space-5 of side padding; rows and the stub follow.
   -------------------------------------------------------------------------- */
@media (min-width: 48rem) {
  .ir-report__title {
    font-size: 22px;
  }

  .ir-report__stub {
    padding-right: var(--space-5);
    padding-left: var(--space-3);
  }

  .ir-report__body {
    padding-right: var(--space-5);
    padding-left: var(--space-5);
  }

  .ir-report__rows {
    margin-inline: calc(-1 * var(--space-5));
  }

  .ir-report__row {
    padding-right: var(--space-5);
    padding-left: var(--space-5);
  }

  .ir-report__caption {
    padding-right: var(--space-5);
    padding-left: var(--space-5);
  }
}

/* --------------------------------------------------------------------------
   8. Motion — the report plays once (direction.md §8): each fill scales
   in from 0 over --dur-3 on --ease-out, rows one --dur-1 apart, when the
   page adds .ir-report--play (a scene script at 40% visibility, or the
   markup itself to play on load). Transform only. The resting rules above
   are the end state, so without the modifier, with JavaScript off, or
   with reduced motion, the bars are simply full. A page script can pause
   the scene off screen with the base layer's .ir-scene[data-paused] hook.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-report-fill {
    from {
      transform: scaleX(0);
    }
    to {
      transform: scaleX(1);
    }
  }

  .ir-report--play .ir-report__fill {
    animation: ir-report-fill var(--dur-3) var(--ease-out) both;
  }

  .ir-report--play .ir-report__row:nth-child(2) .ir-report__fill {
    animation-delay: var(--dur-1);
  }

  .ir-report--play .ir-report__row:nth-child(3) .ir-report__fill {
    animation-delay: calc(2 * var(--dur-1));
  }
}

/* --------------------------------------------------------------------------
   9. Forced colors — the mask would hide the whole strip, so the tear
   line becomes a 1px dashed `line-strong` rule (the OS swaps the color).
   The fill's background would vanish; a border keeps the bar's extent.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-report__tear {
    height: 0;
    margin: 0 var(--space-2);
    background: transparent;
    -webkit-mask-image: none;
    mask-image: none;
    border-top: 1px dashed var(--line-strong);
  }

  .ir-report__fill {
    border: 1px solid CanvasText;
  }

  .ir-report__track {
    border: 1px solid CanvasText;
  }
}


/* ===== 00-site.css ===== */
/* ==========================================================================
   Island Reply site — 00-site.css
   Page-level systems every section shares: reveal, depth, 3D, scene hold,
   view transitions, the band seam, print. Loads after bundle.css and before
   the section files. Tokens only. Nothing here moves unless the visitor has
   not asked for reduced motion, and every rule starts from the visible,
   finished state (html.js is added by 00-core.js only after it has marked
   what is already on screen).
   ========================================================================== */

/* A class selector on a flex or grid element (.ir-button) beats the UA
   [hidden] rule; the pause buttons rely on hidden meaning hidden. */
[hidden] {
  display: none !important;
}

/* --------------------------------------------------------------------------
   1. Reveal. Markup: class="rv", optional data-rv="up|fade|mask|scale" and
   data-rv-delay="1".."6". Visible by default; only html.js .rv:not(.is-in)
   is offset. The individual transform properties (translate, scale) are used
   so a revealed element's own transform is never replaced. 480ms, 12px,
   --ease-out, 80ms stagger.
   -------------------------------------------------------------------------- */
:root {
  --rv-step: 80ms;
  --rv-travel: 12px;
}

.rv {
  transition:
    opacity var(--dur-3) var(--ease-out),
    translate var(--dur-3) var(--ease-out),
    scale var(--dur-3) var(--ease-out),
    clip-path var(--dur-3) var(--ease-out);
  transition-delay: var(--rv-delay, 0ms);
}

.rv[data-rv-delay="1"] { --rv-delay: calc(var(--rv-step) * 1); }
.rv[data-rv-delay="2"] { --rv-delay: calc(var(--rv-step) * 2); }
.rv[data-rv-delay="3"] { --rv-delay: calc(var(--rv-step) * 3); }
.rv[data-rv-delay="4"] { --rv-delay: calc(var(--rv-step) * 4); }
.rv[data-rv-delay="5"] { --rv-delay: calc(var(--rv-step) * 5); }
.rv[data-rv-delay="6"] { --rv-delay: calc(var(--rv-step) * 6); }

.rv[data-rv="mask"] {
  clip-path: inset(0 0 0 0);
}

html.js .rv:not(.is-in) {
  opacity: 0;
  translate: 0 var(--rv-travel);
}

html.js .rv[data-rv="fade"]:not(.is-in) {
  translate: none;
}

html.js .rv[data-rv="scale"]:not(.is-in) {
  translate: none;
  scale: 0.96;
}

/* The ridge wipe: the land is revealed left to right, never faded. */
html.js .rv[data-rv="mask"]:not(.is-in) {
  opacity: 1;
  translate: none;
  clip-path: inset(0 100% 0 0);
}

/* Reduced motion is the finished page. The doubled attribute selector
   matches the specificity of the variant rules above. */
@media (prefers-reduced-motion: reduce) {
  html.js .rv:not(.is-in),
  html.js .rv[data-rv]:not(.is-in) {
    opacity: 1;
    translate: none;
    scale: none;
    clip-path: none;
  }
}

/* --------------------------------------------------------------------------
   2. Scene hold. The visitor's own Pause example button sets data-held,
   separate from data-paused (off screen). Pseudo-elements and the scene
   root are covered too, which the base layer's descendant rule is not.
   -------------------------------------------------------------------------- */
.ir-scene[data-held],
.ir-scene[data-held] *,
.ir-scene[data-held] *::before,
.ir-scene[data-held] *::after,
.ir-scene[data-paused],
.ir-scene[data-paused] *::before,
.ir-scene[data-paused] *::after {
  animation-play-state: paused;
}

/* --------------------------------------------------------------------------
   3. Depth. A layer lags the scroll by IR.parallax(el, { speed }). Where
   scroll timelines exist the compositor does the work (.is-px); otherwise
   core writes --py (px) from one rAF. Either way the element moves with
   `translate` (compositor-only, and it composes with the element's own
   transform). Never on an element that carries text.
   -------------------------------------------------------------------------- */
.ir-depth {
  translate: 0 var(--py, 0px);
}

.ir-depth-stage {
  position: relative;
  isolation: isolate;
}

.ir-depth-layer {
  position: absolute;
  inset: 0;
  z-index: var(--layer, 0);
  pointer-events: none;
  translate: 0 var(--py, 0px);
}

@supports (animation-timeline: scroll()) {
  @keyframes ir-px {
    from { translate: 0 0; }
    to { translate: 0 var(--py-max, 0px); }
  }

  .is-px {
    animation: ir-px linear both;
    animation-timeline: scroll(root);
    animation-range: 0px var(--px-range, 600px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .is-px {
    animation: none;
    translate: none;
  }
}

/* --------------------------------------------------------------------------
   4. 3D. CSS only: a perspective wrapper, preserve-3d on every ancestor
   between the wrapper and the faces, and IR.tilt writing --rx / --ry.
   An ancestor with opacity < 1, filter, clip-path, mask or overflow other
   than visible flattens the object, so none of those belong on the path.
   -------------------------------------------------------------------------- */
.ir-3d {
  perspective: var(--ir-persp, 1200px);
  perspective-origin: var(--ir-persp-x, 50%) var(--ir-persp-y, 40%);
}

.ir-3d-obj {
  transform-style: preserve-3d;
}

.ir-face {
  backface-visibility: hidden;
}

.ir-tilt {
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transform-style: preserve-3d;
}

.ir-tilt.is-tilting {
  will-change: transform;
}

/* --------------------------------------------------------------------------
   5. View transitions between pages (progressive enhancement). The root
   crossfades in 240ms; the header is its own named group and holds still.
   Same-page #fragment links do not transition.
   -------------------------------------------------------------------------- */
@view-transition {
  navigation: auto;
}

@keyframes ir-vt-out {
  to { opacity: 0; }
}

@keyframes ir-vt-in {
  from { opacity: 0; transform: translateY(8px); }
}

::view-transition-old(root) {
  animation: ir-vt-out var(--dur-1) var(--ease-in) both;
}

::view-transition-new(root) {
  animation: ir-vt-in var(--dur-2) var(--ease-out) both;
}

.ir-header {
  view-transition-name: ir-header;
}

::view-transition-old(ir-header),
::view-transition-new(ir-header) {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   6. The band seam. A band is always entered through a ridge (the ridge sets
   its own -1px overlap in bundle.css). On the home page the demo band and
   the footer are one body of water: the footer's ridge is hidden and a
   hairline of on-band ink marks the join. The footer ridge sits after
   </main>, so the sibling is matched through main. The bare .ir-demo
   selectors cover a page that closes main elsewhere.
   -------------------------------------------------------------------------- */
main:has(> .ir-demo) + .ir-ridge--band-top,
.ir-demo + .ir-ridge--band-top {
  display: none;
}

main:has(> .ir-demo) + .ir-ridge--band-top + .ir-footer,
.ir-demo + .ir-ridge--band-top + .ir-footer {
  border-top: 1px solid color-mix(in srgb, var(--ink-on-band) 24%, transparent);
}

/* --------------------------------------------------------------------------
   7. Print. Paper has no theme and no motion: everything visible, bands
   flattened to system ink on system paper, pinned and 3D layers released,
   decorative land and the device stage dropped, controls hidden.
   -------------------------------------------------------------------------- */
@media print {
  @page {
    margin: 16mm;
  }

  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  html.js .rv:not(.is-in),
  html.js .rv[data-rv]:not(.is-in) {
    opacity: 1 !important;
    translate: none !important;
    scale: none !important;
    clip-path: none !important;
  }

  .ir-band {
    --ink: CanvasText;
    --ink-muted: CanvasText;
    --link: CanvasText;
    background: Canvas;
    color: CanvasText;
    border-top: 1px solid CanvasText;
  }

  .ir-ridge,
  .ir-hero__land,
  .ir-how__stage,
  .ir-header__nav,
  .ir-header__cta,
  [data-ir-pause] {
    display: none !important;
  }

  .ir-section-head,
  .ir-how__stage {
    position: static !important;
  }

  .ir-section,
  .ir-ticket,
  .ir-report,
  .ir-faq__item,
  .ir-step {
    break-inside: avoid;
  }
}

/* ===== 10-hero.css ===== */
/* 10-hero.css: seven-face phone, paper planes, three ridges, one clock */

/* dark: sunken bezel with a rim, deep-strong mid ridge */
:root { --ir-device-face: var(--ink); --ir-device-rim: 0px; --ir-hero-mid: var(--reef); }
:root[data-theme="dark"] { --ir-device-face: var(--ground-sunken); --ir-device-rim: 1px; --ir-hero-mid: var(--deep-strong); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --ir-device-face: var(--ground-sunken); --ir-device-rim: 1px; --ir-hero-mid: var(--deep-strong); }
}

/* two columns once the copy holds the 64px headline unbroken (75rem) */
.ir-hero { overflow-x: clip; padding-block: var(--space-8) var(--space-16); }
.ir-hero__grid { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: var(--space-12); }
.ir-hero__copy { min-width: 0; }
.ir-hero__eyebrow, .ir-hero__lead, .ir-hero__facts, .ir-hero__caption { max-width: 34rem; }
.ir-hero__title { max-width: 46rem; margin-top: var(--space-5); color: var(--ink); }
.ir-hero__lead { margin: var(--space-6) 0 0; }
.ir-hero__cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-5); margin: var(--space-6) 0 0; }
.ir-hero__or { font-size: 15px; line-height: 1.5; color: var(--ink-muted); }
.ir-hero__email { font-weight: 600; color: var(--ink); }
.ir-hero__facts { margin-top: var(--space-6); }

@media (min-width: 48rem) {
  .ir-hero { padding-block: var(--space-10) var(--space-24); }
}

@media (min-width: 75rem) {
  .ir-hero__grid { grid-template-columns: minmax(0, 1fr) 640px; column-gap: var(--space-12); align-items: start; }
  /* the alert projects above the stage: clear the header */
  .ir-hero__grid > .ir-hero__stage { margin-top: var(--space-6); }
}

/* fixed 3D box; origin in px */
.ir-hero__stage {
  --ir-hero-h: 1380px;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: var(--ir-hero-h);
  row-gap: var(--space-4);
  min-width: 0;
  margin: 0;
  perspective: 1000px;
  perspective-origin: 50% calc(var(--ir-hero-h) * 0.3);
}

/* land: cut end always hidden; strips only translate, at most 24px */
.ir-hero__land { position: absolute; z-index: 0; top: 0; left: calc(-1 * var(--gutter)); width: 100vw; height: var(--ir-hero-h); pointer-events: none; }
.ir-hero__ridge { position: absolute; left: 0; width: 100%; height: 66px; overflow: hidden; translate: 0 var(--py, 0px); }
.ir-hero__ridge--far { top: 473px; }
.ir-hero__ridge--mid { top: 491px; }
.ir-hero__ridge--near { top: 509px; }
.ir-hero__ridge--far .ir-ridge__land { fill: var(--reef-soft); }
.ir-hero__ridge--mid .ir-ridge__land { fill: var(--ir-hero-mid); }

/* straighten > lean > clock, preserve-3d all the way, nothing flattening */
.ir-hero__scroll {
  --ir-hero-pose-0: rotateY(-6deg) rotateX(2deg);
  --ir-hero-pose-1: rotateY(0deg) rotateX(0deg);
  --ir-hero-range: 400px;
  position: relative;
  z-index: 1;
  grid-area: 1 / 1 / 2 / -1;
  height: var(--ir-hero-h);
  transform: var(--ir-hero-pose-0);
  transform-origin: 150px 50%;
  transform-style: preserve-3d;
}

.ir-hero__tilt { height: 100%; transform: none; transform-origin: 150px 50%; transform-style: preserve-3d; }
.ir-hero__tilt.is-tilting { will-change: transform; }

/* ticket and stamp planes share a cell: same box at any text size */
.ir-hero__scene {
  --hero-phase: 16200ms;
  --ir-hero-tx: 0px;
  --ir-hero-tw: minmax(0, 1fr);
  --ir-hero-ty: 868px;
  position: relative;
  display: grid;
  grid-template: var(--ir-hero-ty) auto / var(--ir-hero-tx) var(--ir-hero-tw);
  align-content: start;
  height: 100%;
  transform-style: preserve-3d;
}

.ir-hero .ir-device { --dw: 296px; --dh: 700px; --ir-device-pad: var(--space-2); top: 0; left: 32px; }
.ir-hero__plane { position: relative; min-width: 0; transform: translateZ(var(--z, 0px)); transform-style: preserve-3d; }
.ir-hero__plane--ticket, .ir-hero__plane--stamp { grid-area: 2 / 2; }
.ir-hero__plane--alert { position: absolute; top: 716px; left: 0; width: 100%; }
.ir-hero__stamp { position: absolute; right: var(--space-5); bottom: calc(-1 * var(--space-2)); }
.ir-hero__alert .ir-alert__head, .ir-hero__controls, .ir-hero__caption { margin: 0; }
.ir-hero__controls:has(> [hidden]) { display: none; }

/* origin 55%: paper within stage + 16px */
@media (min-width: 40rem) {
  .ir-hero__stage { --ir-hero-h: 760px; width: 600px; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--space-4); perspective: 1200px; perspective-origin: 55% calc(var(--ir-hero-h) * 0.4); }
  .ir-hero__land { left: var(--space-6); }
  .ir-hero__ridge--far { top: 513px; }
  .ir-hero__ridge--mid { top: 531px; }
  .ir-hero__ridge--near { top: 549px; }
  .ir-hero__scroll { --ir-hero-pose-0: rotateY(-12deg) rotateX(5deg); --ir-hero-pose-1: rotateY(-4deg) rotateX(1deg); --ir-hero-range: 600px; }
  .ir-hero__tilt { transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); }
  .ir-hero__scene { --ir-hero-tx: 300px; --ir-hero-tw: 300px; --ir-hero-ty: 184px; }
  .ir-hero .ir-device { --dw: 300px; --ir-device-pad: var(--space-3); top: 40px; left: 0; }
  .ir-hero__plane--alert { --z: 56px; top: 0; left: 288px; width: 300px; }
  .ir-hero__plane--ticket { --z: 32px; }
  .ir-hero__plane--stamp { --z: 72px; }
  .ir-hero__controls { grid-area: 2 / 1; }
  .ir-hero__caption { grid-area: 2 / 2; align-self: center; }
  .ir-hero__stage:has(.ir-hero__controls > [hidden]) .ir-hero__caption { grid-column: 1 / -1; }
}

/* 10px slab; edge faces point OUT so culling shows the near sides */
.ir-device { position: absolute; width: var(--dw, 300px); height: var(--dh, 700px); transform-style: preserve-3d; }
.ir-device__front, .ir-device__back, .ir-device__shadow { position: absolute; inset: 0; border-radius: var(--radius-lg); }
.ir-device__front { border: var(--ir-device-rim) solid var(--line-strong); background: var(--ir-device-face); transform: translateZ(5px); }
.ir-device__back { background: var(--ir-device-face); transform: rotateY(180deg) translateZ(5px); backface-visibility: hidden; }
.ir-device__shadow { box-shadow: var(--shadow-card); transform: translateZ(-6px) translateY(10px); }
.ir-device__edge { position: absolute; backface-visibility: hidden; }
.ir-device__edge--l, .ir-device__edge--r { top: 14px; bottom: 14px; width: 10px; background: var(--ridge); }
.ir-device__edge--t, .ir-device__edge--b { left: 14px; right: 14px; height: 10px; background: var(--deep-strong); }
.ir-device__edge--l { left: 0; transform-origin: left center; transform: translateZ(-5px) rotateY(-90deg); }
.ir-device__edge--r { right: 0; transform-origin: right center; transform: translateZ(-5px) rotateY(90deg); }
.ir-device__edge--t { top: 0; transform-origin: center top; transform: translateZ(-5px) rotateX(90deg); }
.ir-device__edge--b { bottom: 0; transform-origin: center bottom; transform: translateZ(-5px) rotateX(-90deg); }

/* the screen lays out all text; home indicator as on the How phone */
.ir-device__screen { position: absolute; inset: calc(12px - var(--ir-device-rim)); display: grid; grid-template-rows: 24px minmax(0, 1fr); border-radius: var(--radius-md); background: var(--ground-raised); overflow: clip; contain: layout paint; }
.ir-device__screen::after { content: ""; position: absolute; z-index: 2; left: 50%; bottom: var(--space-2); width: 88px; height: 4px; margin-left: -44px; border-radius: var(--radius-pill); background: var(--line-strong); }

/* block keeps " PM"'s space; digits share one cell */
.ir-device__bar { display: block; height: 24px; margin: 0; padding: 0 var(--space-4); color: var(--ink); font-weight: 600; line-height: 24px; white-space: nowrap; }
.ir-device__digits { display: inline-grid; }
.ir-device__digits > span { grid-area: 1 / 1; }
.ir-device__digit--4, .ir-device__digit--5 { opacity: 0; }
.ir-device__cell { display: grid; grid-template-rows: minmax(0, 1fr); min-width: 0; min-height: 0; }
.ir-device__cell > * { grid-area: 1 / 1; min-width: 0; min-height: 0; }
/* tight sides: the name sets in full */
.ir-device .ir-thread { align-content: start; padding-inline: var(--ir-device-pad, var(--space-3)); border: 0; border-radius: 0; box-shadow: none; }
.ir-device .ir-thread__head { gap: var(--space-2); margin-inline: calc(-1 * var(--ir-device-pad, var(--space-3))); padding-inline: var(--ir-device-pad, var(--space-3)); }
.ir-device .ir-thread .ir-bubble { max-width: 90%; }

/* call view: over the thread, keypad at the foot; absent at rest */
.ir-device__call { display: none; z-index: 1; grid-template-rows: auto auto minmax(0, 1fr); gap: var(--space-3); padding: var(--space-3) var(--space-3) var(--space-6); background: var(--ground-raised); }
.ir-device__call .ir-callgreeting__status { padding: var(--space-1) 0 var(--space-3); border-top: 0; border-bottom: 1px solid var(--line); }
.ir-device__call .ir-keypad { display: grid; align-self: end; gap: var(--space-3); }
.ir-device__call .ir-keypad__keys { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ir-device__call .ir-keypad__key { height: 36px; }
.ir-device__call .ir-keypad__prompt > p { margin: 0; }

@media (forced-colors: active) {
  .ir-device__edge, .ir-device__back, .ir-device__shadow, .ir-device__screen::after, .ir-hero__ridge--far, .ir-hero__ridge--mid { display: none; }
  .ir-device__front { border: 1px solid CanvasText; }
}

/* hold and off-screen pause beat every shorthand */
.ir-hero .ir-hero__scene[data-paused] *, .ir-hero .ir-hero__scene[data-held] * { animation-play-state: paused; }

/* 18000ms, 1% = 180ms, delay -16200ms: finished frame first. Call-view parts reset
   while hidden; the rest fade at 17700. Keyframe easings are the --ease-in and
   --ease-stamp values written out: var() does not resolve inside keyframes. */
@media (prefers-reduced-motion: no-preference) {
  .ir-hero__scene { --k: 18000ms var(--ease-out) calc(-1 * var(--hero-phase)) infinite both; }
  .ir-hero .ir-hero__thread { --ir-thread-phase: var(--hero-phase); }
  .is-looping .ir-device__call { display: grid; animation: ir-hero-call var(--k); }
  .is-looping .ir-callgreeting__icon { animation: ir-hero-ring var(--k); }
  .is-looping .ir-callgreeting__slash { animation: ir-hero-slash var(--k); }
  .is-looping .ir-callgreeting__label { animation: ir-hero-label var(--k); }
  .is-looping .ir-wave__bar { animation: ir-hero-bar var(--k); }
  .is-looping .ir-keypad__key--one { animation: ir-hero-key var(--k); }
  .is-looping .ir-device__digit--4 { animation: ir-hero-d4 var(--k); }
  .is-looping .ir-device__digit--5 { animation: ir-hero-d5 var(--k); }
  .is-looping .ir-device__digit--6 { animation: ir-hero-d6 var(--k); }
  .is-looping .ir-ticket__row--beat-1 { animation: ir-hero-row-1 var(--k); }
  .is-looping .ir-ticket__row--beat-2 { animation: ir-hero-row-2 var(--k); }
  .is-looping .ir-ticket__row--beat-3 { animation: ir-hero-row-3 var(--k); }
  .is-looping .ir-hero__beat-4 { animation: ir-hero-row-4 var(--k); }
  .is-looping .ir-hero__beat-5 { animation: ir-hero-row-5 var(--k); }
  .is-looping .ir-ticket__pill { animation: ir-hero-pill var(--k); }
  .is-looping .ir-hero__alert { animation: ir-hero-alert var(--k); }
  .is-looping .ir-hero__stamp { animation: ir-hero-stamp var(--k); }

  /* bars 190ms apart */
  .is-looping .ir-wave__bar:nth-child(2) { animation-delay: calc(190ms - var(--hero-phase)); }
  .is-looping .ir-wave__bar:nth-child(3) { animation-delay: calc(380ms - var(--hero-phase)); }
  .is-looping .ir-wave__bar:nth-child(4) { animation-delay: calc(570ms - var(--hero-phase)); }
  .is-looping .ir-wave__bar:nth-child(5) { animation-delay: calc(760ms - var(--hero-phase)); }
  .is-looping .ir-wave__bar:nth-child(6) { animation-delay: calc(950ms - var(--hero-phase)); }
  .is-looping .ir-wave__bar:nth-child(7) { animation-delay: calc(1140ms - var(--hero-phase)); }

  /* 4160 call view out, 17700 back */
  @keyframes ir-hero-call { 0%, 23.1111% { opacity: 1; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); } 24%, 98.3333% { opacity: 0; animation-timing-function: linear; } 100% { opacity: 1; } }
  /* 0 ring */
  @keyframes ir-hero-ring { 0%, 3.3333%, 6.6667%, 100% { transform: scale(1); } 1.6667%, 5% { transform: scale(1.08); } }
  /* 1200 label, reset 4400 */
  @keyframes ir-hero-label { 0%, 6.6667%, 24.5%, 100% { opacity: 0; translate: 0 8px; } 8%, 24.4444% { opacity: 1; translate: 0; } }
  @keyframes ir-hero-slash { 0%, 6.6667%, 24.5%, 100% { opacity: 0; } 8%, 24.4444% { opacity: 1; } }
  /* 2200 greeting, 460ms */
  @keyframes ir-hero-bar { 0%, 12.2222%, 24.5%, 100% { transform: scaleY(0); } 14.7778%, 24.4444% { transform: scaleY(1); } }
  /* 3800 key 1 */
  @keyframes ir-hero-key { 0%, 21.1111% { transform: none; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); } 21.7778% { transform: translateY(1px) scale(0.94); } 22.4444%, 100% { transform: none; } }
  /* clock 2:15 at 8000, 2:16 at 13200 */
  @keyframes ir-hero-d4 { 0%, 44.4444% { opacity: 1; translate: 0; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); } 45.3333% { opacity: 0; translate: 0 -4px; } 45.5%, 98.3333% { opacity: 0; translate: 0; animation-timing-function: linear; } 100% { opacity: 1; translate: 0; } }
  @keyframes ir-hero-d5 { 0%, 44.4444%, 74.4%, 100% { opacity: 0; translate: 0 4px; } 45.3333%, 73.3333% { opacity: 1; translate: 0; animation-timing-function: cubic-bezier(0.4, 0, 1, 1); } 74.2222% { opacity: 0; translate: 0 -4px; } }
  @keyframes ir-hero-d6 { 0%, 73.3333% { opacity: 0; translate: 0 4px; } 74.2222%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; } 100% { opacity: 0; translate: 0; } }
  /* rows 12000, 12250, 12500, 12750, 13400 */
  @keyframes ir-hero-row-1 { 0%, 66.6667% { opacity: 0; translate: 0 8px; } 67.5556%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; } 100% { opacity: 0; translate: 0; } }
  @keyframes ir-hero-row-2 { 0%, 68.0556% { opacity: 0; translate: 0 8px; } 68.9444%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; } 100% { opacity: 0; translate: 0; } }
  @keyframes ir-hero-row-3 { 0%, 69.4444% { opacity: 0; translate: 0 8px; } 70.3333%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; } 100% { opacity: 0; translate: 0; } }
  @keyframes ir-hero-row-4 { 0%, 70.8333% { opacity: 0; translate: 0 8px; } 71.7222%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; } 100% { opacity: 0; translate: 0; } }
  @keyframes ir-hero-row-5 { 0%, 74.4444% { opacity: 0; translate: 0 8px; } 75.3333%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; } 100% { opacity: 0; translate: 0; } }
  /* 13200 pill */
  @keyframes ir-hero-pill { 0%, 73.3333% { opacity: 0; scale: 1.12; animation-timing-function: cubic-bezier(0.3, 1.3, 0.5, 1); } 74.3333%, 98.3333% { opacity: 1; scale: 1; animation-timing-function: linear; } 100% { opacity: 0; scale: 1; } }
  /* 13400 alert, 13640 buzz */
  @keyframes ir-hero-alert {
    0%, 74.4444% { opacity: 0; translate: 0 8px; }
    75.7778%, 76.1111%, 76.4444%, 76.7778%, 98.3333% { opacity: 1; translate: 0; animation-timing-function: linear; }
    75.9444%, 76.2778%, 76.6111% { opacity: 1; translate: 2px 0; animation-timing-function: linear; }
    100% { opacity: 0; translate: 0; }
  }
  /* 14600 stamp drops along Z */
  @keyframes ir-hero-stamp {
    0%, 81.1111% { opacity: 0; transform: translateZ(110px) rotate(-6deg); animation-timing-function: cubic-bezier(0.3, 1.3, 0.5, 1); }
    81.4444% { opacity: 1; }
    82.1111%, 98.3333% { opacity: 1; transform: translateZ(0) rotate(-4deg); animation-timing-function: linear; }
    100% { opacity: 0; transform: translateZ(0) rotate(-4deg); }
  }

  /* straighten on scroll */
  @supports (animation-timeline: scroll()) {
    @keyframes ir-hero-straighten { from { transform: var(--ir-hero-pose-0); } to { transform: var(--ir-hero-pose-1); } }
    .ir-hero__scroll { animation: ir-hero-straighten linear both; animation-timeline: scroll(root); animation-range: 0px var(--ir-hero-range); }
  }
}

@media print {
  .ir-hero__scroll { transform: none; }
}

/* ===== 20-how.css ===== */
/* Island Reply site: 20-how.css. How it works: steps beside a pinned 3D phone whose screen follows data-step (20-how.js). Composition and choreography only; finished state by default. */

/* 1. Composition */
.ir-how .ir-section-head {
  margin-bottom: var(--space-12);
}

.ir-how__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: var(--space-12);
}

.ir-how .ir-split {
  margin-top: var(--space-12);
}

.ir-how__tagrow {
  display: flex;
  justify-content: flex-end;
  margin: 0;
}

@media (min-width: 64rem) {
  .ir-how .ir-section-head {
    margin-bottom: var(--space-16);
  }

  .ir-how__grid {
    grid-template-columns: minmax(0, 1fr) 360px;
    column-gap: var(--space-16);
    row-gap: 0;
    align-items: start;
  }

  .ir-how .ir-steps {
    grid-column: 1;
    grid-row: 1;
  }

  .ir-how .ir-step {
    min-height: 50svh;
  }

}

/* 2. Spine wires fill as steps are reached; the active square steps up 8% */
.ir-how__wire {
  position: absolute;
  z-index: 1;
  top: 3rem;
  bottom: 0;
  left: calc(1.5rem - 1px);
  width: 2px;
  background: var(--deep);
  transform: scaleY(0);
  transform-origin: 50% 0;
  pointer-events: none;
}

.ir-how__wire.is-lit {
  transform: scaleY(1);
}

@media (forced-colors: active) {
  .ir-how__wire {
    background: Highlight;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .ir-how__wire {
    transition: transform var(--dur-3) var(--ease-out);
  }

  .ir-how .ir-step::before {
    transition: scale var(--dur-2) var(--ease-out);
  }

  .ir-how .ir-step.is-active::before {
    scale: 1.08;
  }
}

/* 3. The pinned phone (64rem up): hero construction, preserve-3d all the way down */
.ir-how__stage {
  display: none;
}

@media (min-width: 64rem) {
  .ir-how__stage {
    display: block;
    grid-column: 2;
    grid-row: 1;
    position: sticky;
    top: max(var(--space-6), calc(50svh - 320px));
    height: 640px;
    perspective: 1200px;
    perspective-origin: 50% 40%;
  }
}

@media (min-width: 64rem) and (max-height: 44rem) {
  .ir-how__stage {
    position: static;
  }
}

.ir-how__pose {
  position: relative;
  height: 100%;
  transform-style: preserve-3d;
  transform: rotateY(calc(-4deg + var(--ry, 0deg))) rotateX(calc(1deg + var(--rx, 0deg)));
}

.ir-how__device {
  --dw: 300px;
  --dh: 640px;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  width: var(--dw);
  height: var(--dh);
  margin-inline: auto;
  transform-style: preserve-3d;
}

.ir-how__stage {
  --how-bezel: var(--ink);
  --how-rim: var(--ink);
}

/* Dark: sunken bezel with a rim */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ir-how__stage {
    --how-bezel: var(--ground-sunken);
    --how-rim: var(--line-strong);
  }
}

:root[data-theme="dark"] .ir-how__stage {
  --how-bezel: var(--ground-sunken);
  --how-rim: var(--line-strong);
}

.ir-how__front,
.ir-how__back,
.ir-how__shadow {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
}

.ir-how__front,
.ir-how__back {
  border: 1px solid var(--how-rim);
  background: var(--how-bezel);
}

.ir-how__front { transform: translateZ(5px); }
.ir-how__back { transform: rotateY(180deg) translateZ(5px); backface-visibility: hidden; }
.ir-how__shadow { box-shadow: var(--shadow-card); transform: translateZ(-6px) translateY(10px); }

.ir-how__edge { position: absolute; backface-visibility: hidden; }
.ir-how__edge--l, .ir-how__edge--r { top: 14px; bottom: 14px; width: 10px; background: var(--ridge); }
.ir-how__edge--t, .ir-how__edge--b { left: 14px; right: 14px; height: 10px; background: var(--deep-strong); }
.ir-how__edge--l { left: 0; transform-origin: left center; transform: translateZ(5px) rotateY(90deg); }
.ir-how__edge--r { right: 0; transform-origin: right center; transform: translateZ(5px) rotateY(-90deg); }
.ir-how__edge--t { top: 0; transform-origin: center top; transform: translateZ(5px) rotateX(-90deg); }
.ir-how__edge--b { bottom: 0; transform-origin: center bottom; transform: translateZ(5px) rotateX(90deg); }

@media (forced-colors: active) {
  .ir-how__edge, .ir-how__back, .ir-how__shadow { display: none; }
  .ir-how__front { border-color: CanvasText; }
}

/* Screen, status bar, home indicator */
.ir-how__screen {
  position: absolute;
  inset: 11px;
  display: grid;
  grid-template-rows: 24px minmax(0, 1fr);
  border-radius: var(--radius-md);
  background: var(--ground-raised);
  overflow: clip;
  contain: layout paint;
}

.ir-how__screen::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: var(--space-2);
  width: 88px;
  height: 4px;
  margin-left: -44px;
  border-radius: var(--radius-pill);
  background: var(--line-strong);
}
.ir-how__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin: 0;
  padding: 0 var(--space-4);
  background: var(--ground-raised);
}

.ir-how__panes {
  display: grid;
  min-width: 0;
}

/* 4. Five screens in one cell; opacity picks one (out 160ms, in 240ms + 80ms) */
.ir-how__pane {
  grid-area: 1 / 1;
  align-self: start;
  display: grid;
  align-content: start;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-4);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--dur-1) var(--ease-in), transform 0s linear var(--dur-1);
}

.ir-how__stage[data-step="1"] .ir-how__pane--1,
.ir-how__stage[data-step="2"] .ir-how__pane--2,
.ir-how__stage[data-step="3"] .ir-how__pane--3,
.ir-how__stage[data-step="4"] .ir-how__pane--4,
.ir-how__stage[data-step="5"] .ir-how__pane--5 {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-2) var(--ease-out) 80ms, transform var(--dur-2) var(--ease-out) 80ms;
}

/* Below 64rem each step carries a compact pane: the phone's content, flat. */
.ir-how .ir-step__pane {
  --how-d: 480ms;
  grid-column: 2;
  grid-row: 2;
  display: grid;
  gap: var(--space-3);
  width: 100%;
  min-width: 0;
  max-width: 328px;
  margin: var(--space-4) 0 0;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--ground-raised);
  box-shadow: var(--shadow-card);
}

.ir-how .ir-step__pane--4,
.ir-how .ir-step__pane--5 {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.ir-how .ir-step__pane--5 {
  gap: var(--space-2);
}

.ir-how .ir-step__pane--2 .ir-keypad__key:nth-child(n+4) {
  display: none;
}

@media (min-width: 64rem) {
  .ir-how .ir-step__pane {
    display: none;
  }
}

/* 5. Contents fitted to a 244px pane */
.ir-how .ir-keypad {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

.ir-how .ir-ticket__stub,
.ir-how .ir-ticket__row {
  padding-inline: var(--space-3);
}

.ir-how .ir-ticket__row {
  grid-template-columns: 64px minmax(0, 1fr);
  column-gap: var(--space-2);
}

.ir-how .ir-ticket__head {
  margin-inline: var(--space-3);
}

.ir-how .ir-alert__head {
  flex-wrap: wrap;
  row-gap: var(--space-1);
}

/* Ticket screen sits closer to the bezel */
.ir-how__pane--4 {
  padding-inline: var(--space-2);
}

/* Screen 1: a large handset */
.ir-how__pane--1 .ir-callgreeting__status {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  padding-top: var(--space-16);
  border-top: 0;
}

.ir-how__pane--1 .ir-callgreeting__icon {
  width: 48px;
  height: 48px;
}

/* 6. Choreography: bundle loops off, each part plays once on arrival */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-how-ring {
    0%, 50%, 100% { transform: scale(1); }
    25%, 75% { transform: scale(1.08); }
  }

  @keyframes ir-how-fade {
    from { opacity: 0; }
  }

  @keyframes ir-how-grow {
    from { transform: scaleY(0); }
  }

  @keyframes ir-how-press {
    0%, 100% { transform: none; }
    50% { transform: translateY(1px) scale(0.94); }
  }

  .ir-how__stage {
    --how-d: 320ms;
  }

  .ir-how .ir-callgreeting__icon,
  .ir-how .ir-callgreeting__slash,
  .ir-how .ir-callgreeting__label,
  .ir-how .ir-wave__bar:nth-child(n),
  .ir-how .ir-keypad__key--one {
    animation: none;
  }

  .ir-how .ir-wave__bar:nth-child(1) { --i: 0; }
  .ir-how .ir-wave__bar:nth-child(2) { --i: 1; }
  .ir-how .ir-wave__bar:nth-child(3) { --i: 2; }
  .ir-how .ir-wave__bar:nth-child(4) { --i: 3; }
  .ir-how .ir-wave__bar:nth-child(5) { --i: 4; }
  .ir-how .ir-wave__bar:nth-child(6) { --i: 5; }
  .ir-how .ir-wave__bar:nth-child(7) { --i: 6; }
  .ir-how .ir-ticket__row:nth-child(1) { --i: 0; }
  .ir-how .ir-ticket__row:nth-child(2) { --i: 1; }
  .ir-how .ir-ticket__row:nth-child(3) { --i: 2; }

  /* 1: ring, then stamp and slash */
  :is(.ir-how__stage.is-live[data-step="1"] .ir-how__pane--1, .ir-step.is-in .ir-step__pane--1) .ir-callgreeting__icon {
    animation: ir-how-ring 800ms var(--ease-out) var(--how-d) both;
  }

  :is(.ir-how__stage.is-live[data-step="1"] .ir-how__pane--1, .ir-step.is-in .ir-step__pane--1) .ir-callgreeting__slash {
    animation: ir-how-fade var(--dur-2) var(--ease-out) calc(var(--how-d) + 800ms) both;
  }

  :is(.ir-how__stage.is-live[data-step="1"] .ir-how__pane--1, .ir-step.is-in .ir-step__pane--1) .ir-callgreeting__label {
    animation: ir-step-enter var(--dur-2) var(--ease-out) calc(var(--how-d) + 800ms) both;
  }

  /* 2: bars in ridge order, then the 1 key */
  :is(.ir-how__stage.is-live[data-step="2"] .ir-how__pane--2, .ir-step.is-in .ir-step__pane--2) .ir-wave__bar {
    animation: ir-how-grow 460ms var(--ease-out) calc(var(--how-d) + var(--i) * 90ms) both;
  }

  :is(.ir-how__stage.is-live[data-step="2"] .ir-how__pane--2, .ir-step.is-in .ir-step__pane--2) .ir-keypad__key--one {
    animation: ir-how-press 240ms linear calc(var(--how-d) + 1100ms) both;
  }

  /* 3: the business text */
  :is(.ir-how__stage.is-live[data-step="3"] .ir-how__pane--3, .ir-step.is-in .ir-step__pane--3) .ir-bubble {
    animation: ir-step-enter var(--dur-2) var(--ease-out) calc(var(--how-d) + 120ms) both;
  }

  /* 4: Issue, Address, Urgency 250ms apart */
  :is(.ir-how__stage.is-live[data-step="4"] .ir-how__pane--4, .ir-step.is-in .ir-step__pane--4) .ir-ticket__row {
    animation: ir-step-enter var(--dur-1) var(--ease-out) calc(var(--how-d) + var(--i) * 250ms) both;
  }

  /* 5: alert enter-and-buzz (class from 20-how.js) as its pane lands */
  .ir-how__pane .ir-alert--enter {
    animation-delay: 80ms;
  }

  .ir-how .ir-step__pane .ir-alert--enter {
    animation-delay: var(--dur-2);
  }

  /* Flat panes arrive with their step */
  html.js .ir-step:not(.is-in) .ir-step__pane {
    opacity: 0;
  }

  .ir-step.is-in .ir-step__pane {
    animation: ir-step-enter var(--dur-2) var(--ease-out) var(--dur-2) both;
  }
}

/* 7. Scroll-driven yaw (-5 to +3deg on the -4deg rest pose) where view timelines exist */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    @keyframes ir-how-yaw {
      from { rotate: y -5deg; }
      to { rotate: y 3deg; }
    }

    .ir-how__grid {
      view-timeline: --ir-how block;
    }

    .ir-how__pose {
      animation: ir-how-yaw linear both;
      animation-timeline: --ir-how;
      animation-range: cover 10% cover 90%;
    }
  }
}

/* ===== 30-what.css ===== */
/* ==========================================================================
   Island Reply site: 30-what.css
   "What lands on your phone" (#what-you-get). Composition and choreography
   only: the four FeatureList cards, and beside them a shop desk, a sunken
   slab seen in perspective with the two papers (the job ticket and the
   weekly report) lying on it. The component CSS (bundle.css) owns every
   paper, card and label; this file places them and adds depth.

   Depth model (CSS only, no libraries):
   .ir-what__desk   perspective wrapper (.ir-3d)
   .ir-what__tilt   IR.tilt target, pointer leans the desk +-3deg (.ir-tilt)
   .ir-what__slab   preserve-3d plane, resting lean, thickness underside
   .ir-what__cell   preserve-3d pass-through, the stable hover target
   .ir-ticket / .ir-report   the papers: individual transform properties
     (rotate = resting skew, translate = height above the slab) so the
     component's own transform and the keyframes never fight the pose.

   Nothing here puts opacity, filter, clip-path or overflow on the path
   between the perspective wrapper and the papers. Only transform-type
   properties and opacity animate, and only under no-preference. The rest
   state (every row filled, stamp down, bars full) is the CSS default, so
   no JavaScript, reduced motion and print all get the finished desk.
   Tokens only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Grid: cards first, then the desk (360px reading order). A phone shows
   one column; 48rem keeps the cards two-up with the desk full width below;
   64rem sets the cards in one column against the desk, 7fr to 5fr.
   -------------------------------------------------------------------------- */
.ir-what__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: var(--space-8);
  margin-top: var(--space-10);
}

@media (min-width: 48rem) {
  .ir-what__grid {
    gap: var(--space-10);
    margin-top: var(--space-12);
  }
}

@media (min-width: 64rem) {
  .ir-what__grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--space-12);
  }

  .ir-what__features {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-5);
  }
}

/* --------------------------------------------------------------------------
   2. The desk: perspective wrapper, tilt layer, slab.
   The slab is `ground-sunken` with a 1px `line` edge. Its underside is a
   second plane 10px behind and 8px lower, so perspective shows the slab's
   thickness along the bottom edge. The lean tips the far (top) edge away.
   -------------------------------------------------------------------------- */
.ir-what__desk {
  --ir-persp: 1400px;
  --ir-persp-y: 30%;
  min-width: 0;
}

.ir-what__slab {
  --what-lean: 3deg;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  padding: var(--space-4);
  background: var(--ground-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  transform: rotateX(var(--what-lean));
  transform-origin: 50% 100%;
  transform-style: preserve-3d;
}

.ir-what__slab::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--line);
  transform: translate3d(0, 8px, -10px);
  pointer-events: none;
}

@media (min-width: 48rem) {
  .ir-what__slab {
    --what-lean: 5deg;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    padding: var(--space-6);
  }

  /* the report sits a step lower than the ticket, like paper tossed down */
  .ir-what__cell--report {
    margin-top: var(--space-10);
  }
}

@media (min-width: 64rem) {
  .ir-what__slab {
    grid-template-columns: minmax(0, 1fr);
  }

  /* staggered across the slab: ticket flush left, report flush right */
  .ir-what__cell--ticket {
    justify-self: start;
    width: 94%;
  }

  .ir-what__cell--report {
    justify-self: end;
    width: 94%;
    margin-top: 0;
  }
}

/* --------------------------------------------------------------------------
   3. The papers. A cell is unrotated and stays put (so hover never
   flickers at a moving corner); the paper inside carries the pose.
   The contact shadow is the system's own shadow-card token, doubled so the
   edge reads on the sunken slab in light and dark.
   -------------------------------------------------------------------------- */
.ir-what__cell {
  min-width: 0;
  margin: 0;
  transform-style: preserve-3d;
}

.ir-what__ticket {
  rotate: -2.5deg;
  translate: 0 0 6px;
  transform-style: preserve-3d;
  box-shadow: var(--shadow-card), var(--shadow-card);
}

.ir-what__report {
  rotate: 2deg;
  translate: 0 0 3px;
  box-shadow: var(--shadow-card), var(--shadow-card);
}

/* the BOOKED stamp floats 14px above the paper */
.ir-what__ticket .ir-ticket__stamp {
  translate: 0 0 14px;
}

.ir-what__cap {
  margin: var(--space-5) 0 0;
  padding-inline: var(--space-1);
}

@media (max-width: 47.99rem) {
  .ir-what__ticket {
    rotate: -2deg;
  }
}

/* --------------------------------------------------------------------------
   4. Choreography (no-preference only)
   Hover (fine pointers): the paper straightens and lifts off the slab.
   Once, when a paper is seen: it settles onto the slab (480ms, 36px along
   Z); the ticket then assembles row by row, 250ms apart, 160ms each, and
   the BOOKED stamp drops along Z last (180ms, --ease-stamp). The report's
   bars fill in the component's own keyframes. Backward fill only, so the
   resting pose (and the hover pose) is never held by a finished animation.
   Pre-states live under html.js and :not(.is-playing): without JS or with
   reduced motion the papers are simply finished.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-what-settle {
    from { translate: 0 -10px 36px; }
  }

  @keyframes ir-what-row {
    from { opacity: 0; transform: translateY(8px); }
  }

  @keyframes ir-what-stamp {
    from { opacity: 0; translate: 0 0 76px; transform: rotate(-6deg) scale(1.12); }
    to { opacity: 1; translate: 0 0 14px; transform: rotate(-4deg) scale(1); }
  }

  html.js .ir-what__scene:not(.is-playing) .ir-what__ticket,
  html.js .ir-what__report:not(.is-playing) {
    translate: 0 -10px 36px;
  }

  html.js .ir-what__scene:not(.is-playing) .ir-ticket__row--beat-1,
  html.js .ir-what__scene:not(.is-playing) .ir-ticket__row--beat-2,
  html.js .ir-what__scene:not(.is-playing) .ir-ticket__row--beat-3,
  html.js .ir-what__scene:not(.is-playing) .ir-what__beat-4,
  html.js .ir-what__scene:not(.is-playing) .ir-what__beat-5,
  html.js .ir-what__scene:not(.is-playing) .ir-ticket__stamp {
    opacity: 0;
  }

  html.js .ir-report[data-scene="once"]:not(.is-playing) .ir-report__fill {
    transform: scaleX(0);
  }

  .ir-what__scene.is-playing .ir-what__ticket {
    animation: ir-what-settle var(--dur-3) var(--ease-out) backwards;
  }

  .ir-what__report.is-playing {
    animation: ir-what-settle var(--dur-3) var(--ease-out) 120ms backwards;
  }

  .ir-what__scene.is-playing .ir-ticket__row--beat-1 {
    animation: ir-what-row var(--dur-1) var(--ease-out) 320ms backwards;
  }

  .ir-what__scene.is-playing .ir-ticket__row--beat-2 {
    animation: ir-what-row var(--dur-1) var(--ease-out) 570ms backwards;
  }

  .ir-what__scene.is-playing .ir-ticket__row--beat-3 {
    animation: ir-what-row var(--dur-1) var(--ease-out) 820ms backwards;
  }

  .ir-what__scene.is-playing .ir-what__beat-4 {
    animation: ir-what-row var(--dur-1) var(--ease-out) 1070ms backwards;
  }

  .ir-what__scene.is-playing .ir-what__beat-5 {
    animation: ir-what-row var(--dur-1) var(--ease-out) 1320ms backwards;
  }

  .ir-what__scene.is-playing .ir-ticket__stamp {
    animation: ir-what-stamp 180ms var(--ease-stamp) 1640ms backwards;
  }

  @media (hover: hover) and (pointer: fine) {
    .ir-what__ticket,
    .ir-what__report {
      transition:
        rotate var(--dur-2) var(--ease-out),
        translate var(--dur-2) var(--ease-out);
    }

    .ir-what__cell:hover .ir-what__ticket,
    .ir-what__cell:hover .ir-what__report {
      rotate: 0deg;
      translate: 0 -4px 22px;
    }
  }
}

/* The desk lays down as it scrolls into view: the slab's lean eases from
   16deg to its resting lean over the first 70% of its entry. Scroll-driven,
   compositor only, and the end keyframe is the resting pose, so browsers
   without view timelines simply show the rest pose. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes ir-what-lay {
      from { transform: rotateX(16deg); }
      to { transform: rotateX(var(--what-lean)); }
    }

    .ir-what__slab {
      animation: ir-what-lay linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 70%;
    }
  }
}

/* --------------------------------------------------------------------------
   5. Forced colors and print: flat, bordered, upright.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-what__slab {
    border-color: CanvasText;
  }

  .ir-what__slab::before {
    display: none;
  }
}

@media print {
  .ir-what__slab {
    transform: none;
  }

  .ir-what__slab::before {
    display: none;
  }

  .ir-what__ticket,
  .ir-what__report {
    rotate: none;
    translate: none;
    box-shadow: none;
  }
}

/* ===== 40-who.css ===== */
/* ==========================================================================
   Island Reply site — 40-who.css
   "Who it's for": the deep-water band. Composition and choreography only;
   the checklist, section head, ridge and band are the component library's.
   The band is entered through the ridge strip (revealed left to right by the
   shared mask wipe), the intake sheet ticks itself once when it is seen, and
   the island line under it marks the six islands west to east, one tick each,
   with Oʻahu set a little taller and heavier. Every rule starts from the
   finished state: nothing is hidden without JS, or under reduced motion.
   Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Composition. The right-hand column holds the sheet, then the island
   line, with room above the line so it reads as the sheet's footnote.
   -------------------------------------------------------------------------- */
.ir-who__col {
  display: grid;
  gap: var(--space-8);
  min-width: 0;
}

/* The sheet runs the column's full measure on wide screens so the rows read
   as a form, not a banner; 40rem is still the component's cap. */
.ir-who .ir-checklist {
  max-width: 40rem;
}

.ir-who .ir-checklist__foot {
  max-width: 34rem;
  margin-top: var(--space-5);
}

/* --------------------------------------------------------------------------
   2. The island line. One meta-mono sentence on a hairline of band ink. Each
   name is a nowrap field with a 2px tick standing on the hairline above it;
   the home island is `ink-on-band` at 600 with a taller tick. No fill, no
   dot: `ohia` is not a band ink, and the footer holds the page's one.
   -------------------------------------------------------------------------- */
.ir-who__islands {
  margin: 0;
  border-top: 1px solid color-mix(in srgb, var(--ink-on-band) 22%, transparent);
  color: var(--ink-on-band-muted);
}

.ir-who__isle {
  position: relative;
  display: inline-block;
  margin-bottom: var(--space-1);
  padding-top: var(--space-5);
  white-space: nowrap;
}

.ir-who__isle::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  height: 8px;
  background: var(--ink-on-band-muted);
  transform-origin: 50% 0;
  pointer-events: none;
}

.ir-who__isle--home {
  font-weight: 600;
  color: var(--ink-on-band);
}

.ir-who__isle--home::before {
  height: 14px;
  background: var(--ink-on-band);
}

/* --------------------------------------------------------------------------
   3. Wide screens. Slightly larger sheet rows (the cell and tick follow the
   row's own padding, so only the type steps up) and a longer line.
   -------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .ir-who__col {
    gap: var(--space-10);
    padding-top: var(--space-2);
  }
}

/* --------------------------------------------------------------------------
   4. Choreography (no-preference only). The sheet's own check-landing is the
   component's `ir-checklist--land`, added by core when the sheet is 40%
   visible; here are its pre-state and the island ticks that follow it.
   The ticks grow down from the hairline (scaleY, 240ms, --ease-out) west to
   east, 110ms apart, after the third check has landed. `:has` is the hook
   because the scene root is the sheet, not the column.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-who-tick {
    from { transform: scaleY(0); }
    to { transform: scaleY(1); }
  }

  html.js .ir-checklist[data-scene="once"]:not(.is-playing) .ir-checklist__item::after {
    opacity: 0;
  }

  html.js .ir-who__col:not(:has(> .ir-checklist.is-playing)) .ir-who__isle::before {
    transform: scaleY(0);
  }

  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle::before {
    animation: ir-who-tick var(--dur-2) var(--ease-out) both;
  }

  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle:nth-of-type(1)::before { animation-delay: 900ms; }
  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle:nth-of-type(2)::before { animation-delay: 1010ms; }
  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle:nth-of-type(3)::before { animation-delay: 1120ms; }
  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle:nth-of-type(4)::before { animation-delay: 1230ms; }
  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle:nth-of-type(5)::before { animation-delay: 1340ms; }
  .ir-who__col:has(> .ir-checklist.is-playing) .ir-who__isle:nth-of-type(6)::before { animation-delay: 1450ms; }
}

/* Forced colors: the ticks keep the system ink. */
@media (forced-colors: active) {
  .ir-who__isle::before {
    background: CanvasText;
  }
}

/* ===== 50-rules.css ===== */
/* ==========================================================================
   Island Reply site — 50-rules.css
   "Rules every customer text follows": the eight-line spec sheet beside the
   two scenes that prove two of its lines, the quiet-hours dial and the STOP
   lock. Composition and choreography only; the spec list, the dial, the
   lock and the buttons are the component library's.
   One ridge in view: the dial carries it, so the lock runs flat (--flat).
   Every rule starts from the finished state: the dial at 8 am with the text
   sent, the thread locked. Nothing is hidden without JS or under reduced
   motion. Tokens only. Mobile first.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Composition. One column on phones: head, the sheet, then the scenes.
   From 48rem the two scenes sit side by side under the sheet, the lock
   dropped by one space-8 so the two papers do not align like cards. From
   60rem the sheet and the scenes take half each (the sheet's rows run long
   at that measure, so the two columns end near each other), the lock set in
   from the right by space-10 so the pair reads as two papers on a desk.
   -------------------------------------------------------------------------- */
.ir-rules__head {
  margin-bottom: var(--space-10);
}

.ir-rules__grid {
  display: grid;
  gap: var(--space-10);
  align-items: start;
}

.ir-rules__grid > * {
  min-width: 0;
}

.ir-rules__scenes {
  display: grid;
  gap: var(--space-8);
  align-items: start;
}

.ir-rules__controls {
  margin: calc(-1 * var(--space-4)) 0 0;
}

/* The pause button is hidden under reduced motion and without JS; its row
   leaves no gap behind it then. */
.ir-rules__controls:has(> [hidden]) {
  display: none;
}

@media (min-width: 48rem) {
  .ir-rules__head {
    margin-bottom: var(--space-12);
  }

  .ir-rules__scenes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto 1fr;
    gap: var(--space-4) var(--space-6);
  }

  .ir-rules__scenes > .ir-dial {
    grid-column: 1;
    grid-row: 1;
  }

  .ir-rules__controls {
    grid-column: 1;
    grid-row: 2;
    margin: 0;
  }

  .ir-rules__scenes > .ir-stoplock {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-top: var(--space-8);
  }
}

@media (min-width: 60rem) {
  .ir-rules__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--space-12);
  }

  .ir-rules__scenes {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    gap: var(--space-4);
  }

  .ir-rules__scenes > .ir-dial,
  .ir-rules__controls,
  .ir-rules__scenes > .ir-stoplock {
    grid-column: 1;
    grid-row: auto;
  }

  .ir-rules__scenes > .ir-stoplock {
    justify-self: end;
    width: calc(100% - var(--space-10));
    margin-top: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   2. The dial in a narrow paper. The component stacks the face over the tray
   below 40rem of viewport, but the scene can be narrow at wider viewports
   (two columns, or the 5-part column), so the stack is decided by the
   figure's own width. Under 31rem: one column, the face centered at 232px
   (272px from 26rem), nothing wider than its paper.
   -------------------------------------------------------------------------- */
.ir-rules__scenes > .ir-dial {
  container-type: inline-size;
}

@container (max-width: 31rem) {
  .ir-rules .ir-dial__body {
    grid-template-columns: minmax(0, 1fr);
    align-items: stretch;
    gap: var(--space-4);
    padding: var(--space-4);
  }

  .ir-rules .ir-dial__face {
    max-width: 232px;
    margin-inline: auto;
  }
}

@container (min-width: 26rem) and (max-width: 31rem) {
  .ir-rules .ir-dial__face {
    max-width: 272px;
  }
}

/* The pause button sits under the dial's paper, left-aligned with it. */
.ir-rules__controls .ir-button {
  min-height: 44px;
}

/* --------------------------------------------------------------------------
   3. The lock, once. The component's loop is opt-in (--play) and is not
   used; the scene root is `data-scene="once"`, so core adds .is-playing when
   it is 40% visible and these rules play the thread a single time: the STOP
   bubble at 1200ms, the lock bar 240ms later, ending locked at 8700ms (a
   9000ms run cut at 0.9667 of one iteration, forwards, so the last frame is
   the held, locked one). Before it plays, under JS and no-preference, STOP
   and the bar are not yet drawn; without JS or with reduced motion the
   thread is simply locked.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  html.js .ir-stoplock[data-scene="once"]:not(.is-playing) .ir-stoplock__msg--stop,
  html.js .ir-stoplock[data-scene="once"]:not(.is-playing) .ir-stoplock__bar {
    opacity: 0;
  }

  .ir-stoplock.is-playing .ir-stoplock__msg--stop {
    animation: ir-stoplock-stop 9000ms linear 0.9667 forwards;
  }

  .ir-stoplock.is-playing .ir-stoplock__bar {
    animation: ir-stoplock-bar 9000ms linear 0.9667 forwards;
  }
}

/* ===== 60-faq.css ===== */
/* ==========================================================================
   Island Reply site — 60-faq.css
   Composition for #faq. The sheet itself is the Faq component (bundle.css):
   mono numbers, ink head rule, dirt hem, ground-raised open row, the
   component's own answer-enter. This file only adds what the page needs:
   the section grid's rhythm, the plus lying down into a minus (transform
   only, 160ms), and the printed page, where every answer is shown.
   Nothing here hides anything: no row is ever at opacity 0, and no JS runs.
   ========================================================================== */

/* The head column and the sheet share the section grid (4fr / 8fr from
   60rem, head sticky). Keep the sheet's rows from stretching the head. */
.ir-faq-section .ir-section-grid {
  row-gap: var(--space-8);
}

.ir-faq-section .ir-faq {
  min-width: 0;
}

/* The question text may be long at 360px; it wraps inside its column and
   never pushes the plus or the page sideways. */
.ir-faq-section .ir-faq__q {
  overflow-wrap: anywhere;
}

/* The vertical bar of the plus lies down onto the horizontal one when a row
   opens (the component rotates it with no transition). Compositor-only, and
   only when motion is welcome. */
@media (prefers-reduced-motion: no-preference) {
  .ir-faq-section .ir-faq__summary::after {
    transition: transform var(--dur-1) var(--ease-out);
  }
}

/* Paper shows every answer: a closed <details> hides its content in print,
   so ask the engine to render the content slot regardless (progressive:
   engines without ::details-content ignore this rule). */
@media print {
  .ir-faq-section .ir-faq__item::details-content {
    content-visibility: visible;
    display: block;
  }
}

/* ===== 70-demo.css ===== */
/* ==========================================================================
   Island Reply site — 70-demo.css
   Composition and depth for #demo, on top of DemoBand.css (bundle.css).
   The contact slip is a service tag hung on the deep-water band, and it
   gets the band's one piece of depth, in three layers:
     1. a second tag behind it, showing only its right and bottom edge, so
        the slip reads as the top sheet of a pad;
     2. the address set as a stamped work-order field: a faint wash and two
        registration brackets at opposite corners;
     3. as the slip scrolls in, the back sheet settles and the brackets snap
        onto the field (transform and opacity only, once).
   Text is never hidden: every pre-state below is decoration (a bracket, a
   sheet edge), only under html.js and only with motion welcome, so no-JS,
   reduced motion and print all show the finished slip. Tokens only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Composition. From 60rem the slip hangs a little below the h2's top line
   so the two columns do not share one edge.
   -------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .ir-demo__contact {
    margin-top: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   2. The tag behind. A same-size sheet offset 8px right and down, clipped
   to the 6px of its edge that clears the slip (right and bottom, with the
   short returns at each end), so no line ever crosses the slip's inside.
   Hairline ink: the band ink at 24%, derived from the token like the stub's.
   -------------------------------------------------------------------------- */
.ir-demo__contact::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  translate: 8px 8px;
  border: 1.5px solid color-mix(in srgb, var(--ink-on-band) 24%, transparent); /* translucent band ink */
  border-radius: var(--radius-xs);
  clip-path: polygon(
    calc(100% - 6px) 0,
    100% 0,
    100% 100%,
    0 100%,
    0 calc(100% - 6px),
    calc(100% - 6px) calc(100% - 6px)
  );
  pointer-events: none;
}

/* The slip makes its own stacking context so the sheet sits behind the
   slip's contents but never behind the band. */
.ir-demo__contact {
  isolation: isolate;
}

/* --------------------------------------------------------------------------
   3. The stamped field. The address spans the slip, on a wash of band ink at
   7% (text keeps ink-on-band at far above 4.5:1 on it), with a 2px bracket
   at the top-left and bottom-right corners, 4px outside the field.
   -------------------------------------------------------------------------- */
.ir-demo__email {
  position: relative;
  justify-self: stretch;
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-2);
  background: color-mix(in srgb, var(--ink-on-band) 7%, transparent); /* translucent band ink */
  border-radius: var(--radius-xs);
}

@media (min-width: 40rem) {
  .ir-demo__email {
    padding-inline: var(--space-3);
  }
}

.ir-demo__email::before,
.ir-demo__email::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  border: 0 solid var(--ink-on-band-muted);
  pointer-events: none;
}

.ir-demo__email::before {
  top: -4px;
  left: -4px;
  border-top-width: 2px;
  border-left-width: 2px;
}

.ir-demo__email::after {
  right: -4px;
  bottom: -4px;
  border-right-width: 2px;
  border-bottom-width: 2px;
}

/* --------------------------------------------------------------------------
   4. Motion. Once, when the slip is 40% in view (core adds .is-playing to
   the once-scene). The brackets start 6px out and transparent and ease in
   to their corners, 120ms apart, 240ms on --ease-out. The back sheet is
   driven by the page's scroll (view timeline): it starts 12px lower and
   settles to 8px as the slip enters, a parallax of two layers; where view
   timelines do not exist it simply rests at 8px.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .ir-demo__email::before,
  .ir-demo__email::after {
    transition:
      opacity var(--dur-2) var(--ease-out),
      translate var(--dur-2) var(--ease-out);
  }

  .ir-demo__email::after {
    transition-delay: 120ms;
  }

  html.js .ir-demo__contact[data-scene="once"]:not(.is-playing) .ir-demo__email::before {
    opacity: 0;
    translate: -6px -6px;
  }

  html.js .ir-demo__contact[data-scene="once"]:not(.is-playing) .ir-demo__email::after {
    opacity: 0;
    translate: 6px 6px;
  }

  @supports (animation-timeline: view()) {
    @keyframes ir-demo-sheet {
      from { translate: 8px 20px; }
      to { translate: 8px 8px; }
    }

    .ir-demo__contact::before {
      animation: ir-demo-sheet linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 100%;
    }
  }
}

/* --------------------------------------------------------------------------
   5. Forced colors and print. Translucent inks flatten, so the sheet, the
   wash and the brackets take the system ink; paper has no theme, so the
   slip's own band-ink tokens become CanvasText (00-site.css flattens the
   band itself).
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .ir-demo__contact::before {
    border-color: CanvasText;
  }

  .ir-demo__email {
    background: none;
    border: 1px solid CanvasText;
  }

  .ir-demo__email::before,
  .ir-demo__email::after {
    border-color: CanvasText;
  }
}

@media print {
  .ir-demo__contact::before {
    display: none;
  }

  .ir-demo__text,
  .ir-demo__email,
  .ir-demo__note,
  .ir-demo__stub-val {
    color: CanvasText;
  }

  .ir-demo__email {
    background: none;
    border-bottom: 2px solid CanvasText;
  }

  .ir-demo__email::before,
  .ir-demo__email::after {
    display: none;
  }

  .ir-demo__contact,
  .ir-demo__stub,
  .ir-demo__eyelet {
    border-color: CanvasText;
  }
}

/* ===== 95-legal.css ===== */
/* ==========================================================================
   Island Reply site, 95-legal.css
   /privacy, /sms-terms and the 404. Composition only: tokens, the old
   class names the fixed legal text already carries (legal, legal__grid,
   legal__head, toc, prose, glance, callout, contact-block, checklist,
   updated, lead, select-all), all scoped under .legal. No ridge below the
   header here: the shared footer's ridge is the page's one.

   Phone: head, then the short version, then "On this page", then the
   text (the grid reorders the visual flow only; DOM and focus order stay
   head, contents, text). 60rem and up: a 15rem sticky contents column
   beside a 44rem measure.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Page and grid
   -------------------------------------------------------------------------- */
.legal {
  padding-block: var(--space-10) var(--space-16);
}

@media (min-width: 48rem) {
  .legal {
    padding-block: var(--space-12) var(--space-24);
  }
}

.legal__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

/* 1.1 The head: eyebrow, h1 (display-lg), updated stamp, lead. */
.legal__head {
  grid-column: 1 / -1;
  order: 0;
  margin-bottom: var(--space-8);
  padding-bottom: var(--space-8);
  border-bottom: 1px solid var(--line);
}

.legal__head h1 {
  margin-top: var(--space-3);
  font-size: clamp(2rem, 1.1176rem + 3.9216vw, 3rem);
  line-height: 1.04;
  letter-spacing: -0.01em;
}

.legal .updated {
  margin-top: var(--space-4);
}

.legal .lead {
  max-width: 34rem;
  margin: var(--space-5) 0 0;
  color: var(--ink);
}

/* 1.2 Phone flow. The prose steps out of the grid so its children can be
   ordered around the contents: short version (1), contents (2), the rest (3). */
.legal .prose {
  display: contents;
  overflow-wrap: break-word;
}

.legal .prose > * {
  order: 3;
  margin: 0;
}

.legal .prose > * + * {
  margin-top: var(--space-4);
}

.legal .prose > .glance {
  order: 1;
}

.legal .toc {
  order: 2;
  margin-top: var(--space-8);
}

@media (min-width: 60rem) {
  .legal__grid {
    grid-template-columns: 15rem minmax(0, 44rem);
    column-gap: var(--space-12);
    justify-content: start;
    align-items: start;
  }

  .legal__head {
    margin-bottom: var(--space-12);
    padding-bottom: var(--space-10);
  }

  .legal .prose {
    display: block;
    grid-column: 2;
    grid-row: 2;
    min-width: 0;
    max-width: 44rem;
  }

  .legal .toc {
    grid-column: 1;
    grid-row: 2;
    order: 0;
    margin-top: 0;
  }
}

/* Sticky only when the whole list fits the window, so nothing is clipped
   and the focus ring is never cut off by a scroll container. */
@media (min-width: 60rem) and (min-height: 42rem) {
  .legal .toc {
    position: sticky;
    top: var(--space-8);
  }
}

/* --------------------------------------------------------------------------
   2. "On this page": 2px ink rule, heading-3 title, a ruled list of 44px
   targets numbered with a meta-mono counter. Where the browser supports
   scroll-target-group, the section being read gets a 2px dirt hem.
   -------------------------------------------------------------------------- */
.legal .toc {
  padding-top: var(--space-4);
  border-top: 2px solid var(--ink);
}

.legal .toc h2 {
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
}

.legal .toc ol {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: legal-toc;
  border-top: 1px solid var(--line);
}

.legal .toc li {
  margin: 0;
  counter-increment: legal-toc;
  border-bottom: 1px solid var(--line);
}

.legal .toc a {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  min-height: 44px;
  padding: 12px var(--space-2);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  transition: background-color 120ms linear;
}

.legal .toc a::before {
  content: counter(legal-toc, decimal-leading-zero);
  flex: none;
  min-width: 2ch;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  font-feature-settings: "tnum", "zero";
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--ink-muted);
}

.legal .toc a:hover {
  background-color: var(--ground-sunken);
}

@media (min-width: 60rem) and (pointer: fine) {
  .legal .toc a {
    min-height: 38px;
    padding-block: 9px;
  }
}

@supports selector(:target-current) {
  .legal .toc ol {
    scroll-target-group: auto;
  }

  .legal .toc a:target-current {
    box-shadow: inset 0 -2px 0 var(--dirt);
  }

  .legal .toc a:target-current::before {
    color: var(--ink);
  }
}

/* --------------------------------------------------------------------------
   3. Prose rhythm. h2 = heading-2 with a hairline rule above, h3 =
   heading-3, 16px between blocks, 8px between list items.
   -------------------------------------------------------------------------- */
.legal .prose h2 {
  margin-top: var(--space-10);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
  font-size: clamp(1.625rem, 1.2941rem + 1.4706vw, 2rem);
  line-height: 1.1;
  letter-spacing: -0.005em;
}

.legal .prose h3 {
  margin-top: var(--space-6);
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
}

.legal .prose h2 + *,
.legal .prose h3 + * {
  margin-top: var(--space-3);
}

.legal .prose ul:not(.checklist) {
  padding-left: var(--space-5);
  list-style: disc;
}

.legal .prose ul:not(.checklist) li::marker {
  color: var(--ink-muted);
}

.legal .prose li + li {
  margin-top: var(--space-2);
}

.legal .select-all {
  font-weight: 600;
  overflow-wrap: anywhere;
  -webkit-user-select: all;
  user-select: all;
}

/* --------------------------------------------------------------------------
   4. The short version: ground-raised, 1px line, radius-xs, one 2px dirt
   hem on the foot. Rows are the Checklist row: 22px cell, 16px deep check.
   -------------------------------------------------------------------------- */
.legal .glance {
  padding: var(--space-5);
  background: var(--ground-raised);
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--dirt);
  border-radius: var(--radius-xs);
}

.legal .prose .glance h2 {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: clamp(1.25rem, 1.1397rem + 0.4902vw, 1.375rem);
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0;
}

.legal .prose .glance .checklist {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

.legal .checklist li {
  position: relative;
  margin: 0;
  padding: var(--space-3) 0 var(--space-3) 34px;
  border-bottom: 1px solid var(--line);
  line-height: 1.55;
  text-wrap: pretty;
}

.legal .prose .checklist li + li {
  margin-top: 0;
}

.legal .checklist li:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.legal .checklist li::before {
  content: "";
  position: absolute;
  top: calc(var(--space-3) + 0.14em);
  left: 0;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-xs);
  background: var(--ground-raised);
}

.legal .checklist li::after {
  content: "";
  position: absolute;
  top: calc(var(--space-3) + 0.14em + 4px);
  left: 8px;
  box-sizing: content-box;
  width: 4.5px;
  height: 9px;
  border: solid var(--deep);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
  transform-origin: 50% 50%;
}

@media (min-width: 48rem) {
  .legal .glance {
    padding: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   5. Callout and blockquote: ground-raised, 1px line, no stripe. The
   greeting quote takes a 2px dirt hem when a link lands on #greeting.
   -------------------------------------------------------------------------- */
.legal .callout {
  padding: var(--space-3) var(--space-4);
  background: var(--ground-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-weight: 600;
}

.legal .callout p {
  margin: 0;
}

.legal .prose blockquote {
  margin: var(--space-4) 0;
  padding: var(--space-4);
  background: var(--ground-raised);
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: var(--radius-md);
}

.legal .prose blockquote p {
  margin: 0;
}

.legal .prose #greeting:target + p + blockquote {
  border-bottom-color: var(--dirt);
}

/* --------------------------------------------------------------------------
   6. Contact block: a service tag on manila, 1.5px line-strong box.
   -------------------------------------------------------------------------- */
.legal .contact-block {
  max-width: 28rem;
  padding: var(--space-4);
  background: var(--ground-ticket);
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-xs);
}

.legal .contact-block p {
  margin: 0;
}

.legal .contact-block p + p {
  margin-top: var(--space-1);
}

/* --------------------------------------------------------------------------
   7. The 404. Left-aligned on the grid, one line of weight, the 8px ohia
   dot as the heading marker, a primary button and a ruled link row.
   -------------------------------------------------------------------------- */
.legal--404 {
  display: flex;
  align-items: center;
  min-height: min(58svh, 36rem);
}

.legal__lost {
  max-width: 34rem;
}

.legal__lost h1 {
  margin-top: var(--space-4);
}

.legal__dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--ohia);
  vertical-align: 0.2em;
}

.legal__lost .lead {
  margin-top: var(--space-4);
}

.legal__actions {
  margin: var(--space-8) 0 0;
}

.legal__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-6);
  margin: var(--space-10) 0 0;
  padding: var(--space-2) 0 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

.legal__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* --------------------------------------------------------------------------
   8. Motion (no-preference only; every state above is the finished one).
   The head, or the 404 copy, rises in one beat at a time: translateY(8px)
   and opacity, 240ms, --ease-out, 80ms apart. The dot lands on
   --ease-stamp. Where scroll timelines exist, a 3px deep bar grows along
   the top edge as the page is read (transform only).
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ir-legal-in {
    from {
      opacity: 0;
      transform: translateY(8px);
    }
  }

  @keyframes ir-legal-dot {
    from {
      transform: scale(0);
    }
  }

  .legal__head > *,
  .legal__lost > * {
    animation: ir-legal-in var(--dur-2) var(--ease-out) both;
  }

  .legal__head > :nth-child(2),
  .legal__lost > :nth-child(2) { animation-delay: 80ms; }
  .legal__head > :nth-child(3),
  .legal__lost > :nth-child(3) { animation-delay: 160ms; }
  .legal__head > :nth-child(4),
  .legal__lost > :nth-child(4) { animation-delay: 240ms; }
  .legal__lost > :nth-child(5) { animation-delay: 320ms; }

  .legal__dot {
    animation: ir-legal-dot 180ms var(--ease-stamp) 240ms both;
  }

  @supports (animation-timeline: scroll()) {
    @keyframes ir-legal-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    .legal:not(.legal--404)::before {
      content: "";
      position: fixed;
      inset: 0 0 auto 0;
      z-index: 50;
      height: 3px;
      background: var(--deep);
      transform: scaleX(0);
      transform-origin: 0 50%;
      pointer-events: none;
      animation: ir-legal-progress linear both;
      animation-timeline: scroll(root);
    }
  }
}

@media (forced-colors: active) {
  .legal .checklist li::before,
  .legal .checklist li::after {
    border-color: CanvasText;
  }

  .legal .contact-block {
    background: Canvas;
  }
}

@media print {
  .legal .toc {
    position: static;
  }

  .legal .glance,
  .legal .callout,
  .legal .prose blockquote,
  .legal .contact-block {
    break-inside: avoid;
  }
}
