/* skin.css — sanagustin. Tokens + every visual decision for Andrea & Paolo.
   Scoped under [data-skin="sanagustin"] so the Phase 4 tour can load eight of these at once.
   No behaviour here, and no engine internals touched. Fonts are system stacks: nothing to
   download, nothing for the CSP to allow. */

/* Self-hosted type — LIVE since 2026-08-13 (evening). Files are in assets/type/ (see the README
   there). The families are first in --display / --body below, so this block is the whole switch.
   Note: these are Fontsource latin subsets and their GSUB tables carry no `smcp` or `onum` —
   `.a-sc` / `.a-onum` therefore still do nothing. See CHANGELOG 2026-08-13 (evening).
*/
@font-face {
  font-family: "Cormorant Garamond";
  src: url("../assets/type/cormorant-garamond-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Cormorant Garamond";
  src: url("../assets/type/cormorant-garamond-400i.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "EB Garamond";
  src: url("../assets/type/eb-garamond-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "EB Garamond";
  src: url("../assets/type/eb-garamond-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}

[data-skin="sanagustin"] {
  /* raw palette */
  --ivory: #f6f1e8;
  --ivory-lit: #fffcf6;
  --stone: #cec5b5;
  --stone-deep: #a89c88;   /* borders and rules only — too pale for text */
  --sand-ink: #6b5b47;     /* the quiet text colour: 5.8:1 on flat ivory, ~4.9 on the laid paper (2026-10-01, C1; was #7a6a55) */
  --ink-raw: #221a15;
  --burgundy: #6d1f2b;
  --burgundy-lit: #8c2c3a;
  --blush: #f2e6e4;

  /* semantic */
  --bg: var(--ivory);
  --bg-raised: var(--ivory-lit);
  --ink: var(--ink-raw);
  --muted: #5f5349;         /* 6.6:1 on flat ivory (2026-10-01, C1; #6d6055 measured 4.0 on the laid paper) */
  --rule: #ded4c5;
  --accent: var(--burgundy);
  --accent-soft: var(--blush);
  --field: var(--ivory-lit);

  /* type */
  /* The two Garamonds are named first and simply do not resolve until the woff2 files exist —
     a missing @font-face family is skipped, so the system stack behind it renders today. */
  --display: "Cormorant Garamond", "Palatino Linotype", "Book Antiqua", Palatino, "Iowan Old Style", Georgia, serif;
  --body: "EB Garamond", Georgia, "Times New Roman", Times, serif;
  --ui: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --step--1: clamp(.8rem, .77rem + .12vw, .875rem);
  --step-0: clamp(1rem, .97rem + .18vw, 1.0625rem);
  --step-1: clamp(1.18rem, 1.1rem + .4vw, 1.375rem);
  --step-2: clamp(1.45rem, 1.28rem + .9vw, 2rem);
  --step-3: clamp(1.85rem, 1.5rem + 1.9vw, 2.9rem);
  --step-4: clamp(2.3rem, 1.7rem + 3.4vw, 4.1rem);
  --step-5: clamp(2.9rem, 1.7rem + 6vw, 6.4rem);

  /* form */
  --radius: 2px;
  --rule-w: 1px;
  /* The height of the two fixed docks, in ONE place. Everything that has to keep clear of them —
     the hero panel, the footer — reserves this, so the reserve cannot drift from the furniture
     again. Measured: 60px at 390 rising to 76px at 1440, so 5rem with the safe area on top. */
  --dock-h: calc(5rem + env(safe-area-inset-bottom, 0px));
  /* Since 2026-08-21 the docks sit COLLAPSED at rest (a mark, not a pill), so anything above
     them reserves the resting height, not the expanded one. The footer still reserves --dock-h:
     it is the one place the pair may be expanded over. */
  --dock-h-rest: calc(3.6rem + env(safe-area-inset-bottom, 0px));
  /* The sheet. Above this the window is a desk and the site is a page laid on it. */
  --page: 100rem;
  --gap: clamp(1rem, .6rem + 1.6vw, 1.75rem);
  --pad-band: clamp(3rem, 2rem + 5vw, 7rem);
  --wrap: 68rem;
  --motion-fast: 160ms;
  --motion-slow: 520ms;
  --ease: cubic-bezier(.2, .6, .2, 1);
  --focus-w: 2px;
  --focus-off: 3px;
}

[data-skin="sanagustin"] body {
  /* background-COLOR, never the shorthand: the laid ground lives in atelier.css's
     [data-atelier~="paper"] body rule, same specificity, and this file loads after it — a
     `background:` here resets background-image to none and silently kills the paper. */
  background-color: var(--bg);
  color: var(--ink);
  font: var(--step-0)/1.65 var(--body);
  text-wrap: pretty;
}

/* ---------- type ---------- */
[data-skin="sanagustin"] :is(h1, h2, h3, h4) {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -.01em;
  text-wrap: balance;
}
/* :where() keeps these at (0,1,0) so any utility class below can override the size on a
   heading tag without an arms race. Semantics stay in the markup, looks stay in the class. */
[data-skin="sanagustin"] :where(h1) { font-size: var(--step-4); }
[data-skin="sanagustin"] :where(h2) { font-size: var(--step-3); }
[data-skin="sanagustin"] :where(h3) { font-size: var(--step-1); }
/* Scoped away from components: a bare <a> is a text link, anything with a component class
   owns its own colour. Without :not() these two rules outrank .btn (0,1,1 beats 0,1,0). */
[data-skin="sanagustin"] a:not([class]) { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: .18em; }
[data-skin="sanagustin"] a:not([class]):hover { color: var(--burgundy-lit); text-decoration-color: var(--stone-deep); }
[data-skin="sanagustin"] a.btn { color: var(--ivory-lit); text-decoration: none; }
[data-skin="sanagustin"] a.btn:hover { color: var(--ivory-lit); }
[data-skin="sanagustin"] a.btn--quiet, [data-skin="sanagustin"] a.btn--quiet:hover { color: var(--accent); }
[data-skin="sanagustin"] a.link { color: var(--accent); text-decoration: none; }
[data-skin="sanagustin"] a.link:hover { color: var(--burgundy-lit); }
[data-skin="sanagustin"] a.brand, [data-skin="sanagustin"] a.brand:hover { color: var(--ink); text-decoration: none; }
[data-skin="sanagustin"] strong { font-weight: 700; }

[data-skin="sanagustin"] .eyebrow {
  font: 600 var(--step--1)/1 var(--ui);
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 .9rem;
}
.eyebrow--quiet { color: var(--sand-ink); }
[data-skin="sanagustin"] .lede { font-size: var(--step-1); line-height: 1.5; color: var(--muted); max-width: 34em; }
[data-skin="sanagustin"] .meta { font: var(--step--1)/1.5 var(--ui); letter-spacing: .06em; color: var(--muted); }
.prose p + p { margin-top: 1.1em; }
.prose { max-width: 36em; }

/* ---------- layout ---------- */
.wrap { width: min(100% - 2 * var(--gap), var(--wrap)); margin-inline: auto; }
.wrap--narrow { width: min(100% - 2 * var(--gap), 46rem); margin-inline: auto; }
.band { padding-block: var(--pad-band); }
.band--raised { background: var(--bg-raised); border-block: var(--rule-w) solid var(--rule); }
.band--edge { border-top: var(--rule-w) solid var(--rule); }
.stack > * + * { margin-top: var(--gap); }
.grid-2 { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
.grid-3 { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.hr { border: 0; border-top: var(--rule-w) solid var(--rule); margin-block: var(--gap); }
.center { text-align: center; }
.center .lede, .center .prose { margin-inline: auto; }

/* ---------- buttons + fields ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  min-height: 48px; padding: .7em 1.5em;
  background: var(--accent); color: var(--ivory-lit);
  border: var(--rule-w) solid var(--accent); border-radius: var(--radius);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  text-decoration: none;
  transition: background var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease);
}
.btn:hover { background: var(--burgundy-lit); color: var(--ivory-lit); }
.btn:active { transform: translateY(1px); }
.btn--quiet { background: transparent; color: var(--accent); border-color: var(--stone-deep); }
.btn--quiet:hover { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.btn--small { min-height: 44px; padding: .5em 1em; letter-spacing: .1em; }

.field { display: grid; gap: .4rem; }
.field > label, .label {
  font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
.field input, .field select, .field textarea {
  width: 100%; min-height: 48px; padding: .6em .8em;
  background: var(--field); color: var(--ink);
  border: var(--rule-w) solid var(--stone); border-radius: var(--radius);
  font: var(--step-0)/1.4 var(--body);
}
.field textarea { min-height: 7rem; resize: vertical; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--stone-deep); }
.field--code input {
  font: 600 var(--step-1)/1.2 var(--ui); letter-spacing: .28em; text-transform: uppercase;
}
[data-skin="sanagustin"] .hint { font: var(--step--1)/1.5 var(--ui); color: var(--muted); }
.error {
  margin-top: .8rem; padding: .7em .9em;
  background: var(--accent-soft); border-left: 3px solid var(--accent);
  font: var(--step--1)/1.5 var(--ui); color: var(--ink);
}

/* ---------- chrome: the demo bar, then the site bar ---------- */
.demo-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.25rem;
  padding: .55rem var(--gap);
  background: var(--ink); color: var(--ivory);
  font: var(--step--1)/1.4 var(--ui);
}
.demo-mark { display: flex; align-items: center; gap: .55rem; letter-spacing: .04em; }
.demo-mark::before {
  content: ""; width: .5rem; height: .5rem; flex: none;
  background: var(--accent-soft); transform: rotate(45deg);
}
.demo-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .9rem; margin-left: auto; }
.demo-ctl span { color: var(--stone); letter-spacing: .04em; }
.demo-btn {
  min-height: 44px; padding: .35em .9em;
  background: transparent; color: var(--ivory-lit);
  border: 1px solid rgba(246, 241, 232, .38); border-radius: var(--radius);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  transition: background var(--motion-fast) var(--ease);
}
.demo-btn:hover { background: rgba(246, 241, 232, .14); }
.demo-btn[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); }
.demo-note { flex-basis: 100%; color: var(--accent-soft); }

.site-bar {
  display: flex; align-items: center; gap: var(--gap);
  padding: 1rem var(--gap);
  background: var(--bg);
  border-bottom: var(--rule-w) solid var(--rule);
  position: sticky; top: 0; z-index: 20;
}
.brand { display: grid; gap: .2rem; text-decoration: none; color: var(--ink); }
[data-skin="sanagustin"] .brand b { font: 400 var(--step-1)/1 var(--display); letter-spacing: .16em; }
.brand span { font: var(--step--1)/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.nav-toggle {
  margin-left: auto; padding: .5em .9em;
  background: transparent; border: var(--rule-w) solid var(--stone-deep); border-radius: var(--radius);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
}
.nav { margin-left: auto; }
.nav ul { display: flex; flex-wrap: wrap; gap: 1.5rem; margin: 0; padding: 0; list-style: none; }
.nav a {
  display: block; padding: .5rem 0;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--accent); border-bottom-color: var(--stone); }
.nav a[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }

@media (max-width: 61.99em) {
  .nav {
    position: absolute; inset: 100% 0 auto; z-index: 19;
    display: none; padding: var(--gap);
    background: var(--bg-raised); border-bottom: var(--rule-w) solid var(--rule);
  }
  .nav[data-open] { display: block; }
  .nav ul { flex-direction: column; gap: .25rem; }
  .nav a { padding: .85rem 0; border-bottom: var(--rule-w) solid var(--rule); }
  .site-bar { position: relative; }
}
@media (min-width: 62em) {
  .nav-toggle { display: none; }
  .nav { display: block !important; }
}

/* ---------- hero ---------- */
.hero { position: relative; padding-block: clamp(2rem, 1rem + 4vw, 4rem) 0; }
.hero-figure { position: relative; margin: 0; }
.hero-figure img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; filter: saturate(.94); }
.hero-plate {
  position: relative; z-index: 2;
  width: min(100% - 2rem, 44rem); margin: -3.5rem auto 0;
  padding: clamp(1.75rem, 1rem + 4vw, 3.5rem);
  background: var(--bg-raised);
  border: var(--rule-w) solid var(--rule);
  text-align: center;
}
[data-skin="sanagustin"] .hero-names { font-size: var(--step-5); line-height: .98; letter-spacing: -.015em; }
.hero-amp { display: block; font-size: .42em; color: var(--accent); font-style: italic; letter-spacing: 0; margin-block: .18em; }
.hero-plate .meta { margin-top: 1.4rem; letter-spacing: .2em; text-transform: uppercase; }
.hero-rule { width: 4rem; height: 1px; background: var(--stone-deep); margin: 1.6rem auto 0; }

/* ---------- countdown ---------- */
.count { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(1rem, .5rem + 2.5vw, 3rem); }
.count-cell { min-width: 4.5rem; text-align: center; }
[data-skin="sanagustin"] .count-num { display: block; font: 400 var(--step-3)/1 var(--display); font-variant-numeric: tabular-nums; }
.count-label { display: block; margin-top: .5rem; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .2em; text-transform: uppercase; color: var(--muted); }

/* ---------- cards ---------- */
.card {
  display: flex; flex-direction: column;
  background: var(--bg-raised); border: var(--rule-w) solid var(--rule);
}
.card img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.card-body { display: grid; gap: .6rem; padding: clamp(1.1rem, .8rem + 1vw, 1.6rem); }
.card-body p { color: var(--muted); }
.card-body .btn, .card-body .link { margin-top: .4rem; justify-self: start; }
[data-skin="sanagustin"] .link {
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent); text-decoration: none; border-bottom: 1px solid var(--stone);
  padding-bottom: .25em;
}
.link:hover { border-bottom-color: var(--accent); }

/* ---------- venue blocks ---------- */
.venue { display: grid; gap: .5rem; padding-top: 1.2rem; border-top: 2px solid var(--ink); }
.venue h3 { font-size: var(--step-2); }
.venue .meta { letter-spacing: .14em; text-transform: uppercase; }
.venue p { color: var(--muted); }

/* ---------- story ---------- */
.chapter {
  display: grid; gap: var(--gap);
  padding-block: clamp(2rem, 1.2rem + 3vw, 4rem);
  border-top: var(--rule-w) solid var(--rule);
  grid-template-columns: 1fr;
}
.chapter:first-child { border-top: 0; }
[data-skin="sanagustin"] .chapter-no {
  font: 400 var(--step-2)/1 var(--display); color: var(--sand-ink);
  letter-spacing: .1em;
}
.chapter-head { display: grid; gap: .5rem; }
.chapter-head h2 { font-size: var(--step-2); }
.chapter-date { font: var(--step--1)/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--accent); }
[data-skin="sanagustin"] .chapter-blurb { font-family: var(--display); font-size: var(--step-1); font-style: italic; color: var(--muted); }
.chapter-shots { display: grid; gap: .8rem; }
.chapter-shots img { width: 100%; object-fit: cover; cursor: zoom-in; }
/* ⚠ SCOPED TO THE CHAPTER, AND IT MUST STAY SCOPED. `engine/story.js` sets a BARE `data-reveal`
   on each `[data-chapter]`; `engine/reveal.js` sets `data-reveal="on"` on <html>. Two mechanisms,
   one attribute name (see NEXT.md's traps). Written unscoped, this rule matched <html> the moment
   site 1 got the `reveal` token — the whole document at `opacity: 0` with a transform on the root,
   which also made every `position: fixed` dock measure against the document instead of the
   viewport. Nothing but the chapters may ever be keyed on the bare attribute. */
[data-chapter][data-reveal] { opacity: 0; transform: translateY(14px); transition: opacity var(--motion-slow) var(--ease), transform var(--motion-slow) var(--ease); }
[data-chapter][data-reveal][data-in] { opacity: 1; transform: none; }
@media (min-width: 48em) {
  .chapter { grid-template-columns: 4rem minmax(0, 22rem) minmax(0, 1fr); align-items: start; }
  /* Explicit from the moment the grid exists: without this the blurb auto-places into the
     4rem folio column and renders one character per line. The 62em rule below moves it into
     the margin column; it never falls back to auto placement. */
  [data-skin="sanagustin"] .chapter-blurb { grid-column: 2 / -1; }
  /* Same trap, found by the gate 2026-08-22: move 09's `.chapter-body` wrapper changed what
     auto-places here, and `.chapter-shots` fell into the 4rem folio column — five story
     photographs rendered 38.4px wide between 48em and 62em, where the sticky margin column that
     places them explicitly does not yet apply. Nothing on this grid may rely on auto placement. */
  [data-skin="sanagustin"] .chapter .chapter-body,
  [data-skin="sanagustin"] .chapter .chapter-shots { grid-column: 2 / -1; }
}

.gallery { display: grid; gap: .8rem; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.gallery figure { margin: 0; }
.gallery img { width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.gallery figure:nth-child(3n + 1) { grid-row: span 2; }
.gallery figure:nth-child(3n + 1) img { aspect-ratio: 3 / 4; }

/* ---------- schedule ---------- */
.sched-group { padding-block: clamp(1.5rem, 1rem + 2vw, 3rem); border-top: var(--rule-w) solid var(--rule); }
.sched-group:first-child { border-top: 0; }
.sched-venue { display: grid; gap: .45rem; margin-bottom: var(--gap); }
.sched-venue h2 { font-size: var(--step-2); }
.sched-venue .meta { letter-spacing: .14em; text-transform: uppercase; }
.sched-venue p { color: var(--muted); max-width: 40em; }
.sched-list { display: grid; }
.sched-item {
  display: grid; gap: .15rem .9rem; padding-block: 1rem;
  border-top: var(--rule-w) solid var(--rule);
  grid-template-columns: 6.5rem 1fr;
}
.sched-time { font: 600 var(--step--1)/1.6 var(--ui); letter-spacing: .1em; color: var(--accent); }
[data-skin="sanagustin"] .sched-title { font-family: var(--display); font-size: var(--step-1); }
.sched-note { grid-column: 2; color: var(--muted); font-size: var(--step--1); line-height: 1.6; }

/* ---------- rsvp ---------- */
.rsvp { display: grid; gap: var(--gap); }
.lookup { display: grid; gap: var(--gap); padding: clamp(1.25rem, 1rem + 2vw, 2.25rem); background: var(--bg-raised); border: var(--rule-w) solid var(--rule); }
.lookup-row { display: flex; flex-wrap: wrap; gap: .8rem; align-items: end; }
.lookup-row .field { flex: 1 1 14rem; }
.chips { display: grid; gap: .7rem; }
.chip-list { display: flex; flex-wrap: wrap; gap: .6rem; }
.chip {
  padding: .55em 1em; background: var(--bg-raised);
  border: var(--rule-w) solid var(--stone); border-radius: 999px;
  font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .06em; color: var(--ink);
  text-align: left;
}
.chip:hover { border-color: var(--accent); background: var(--accent-soft); }
.chip b { display: block; font-weight: 700; }
.chip span { color: var(--muted); letter-spacing: .12em; text-transform: uppercase; font-size: .82em; }

.party-head { display: grid; gap: .4rem; padding-bottom: 1rem; border-bottom: 2px solid var(--ink); }
.party-head h2 { font-size: var(--step-2); }
.party-head .meta { letter-spacing: .1em; }
.guest {
  display: grid; gap: .9rem; padding-block: 1.4rem;
  border-bottom: var(--rule-w) solid var(--rule);
}
.guest-top { display: flex; flex-wrap: wrap; gap: .6rem 1rem; align-items: baseline; }
[data-skin="sanagustin"] .guest-name { font: 400 var(--step-1)/1.2 var(--display); }
[data-skin="sanagustin"] .guest-role { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--sand-ink); }
.choices { display: flex; gap: .6rem; margin-left: auto; }
.choice {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 44px; padding: .45em 1.1em;
  background: var(--bg-raised); border: var(--rule-w) solid var(--stone); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer;
}
.choice input { accent-color: var(--accent); width: 1.05rem; height: 1.05rem; }
.choice:hover { border-color: var(--stone-deep); }
.choice:has(input:checked) { background: var(--accent); border-color: var(--accent); color: var(--ivory-lit); }
.choice:has(input:focus-visible) { outline: var(--focus-w) solid var(--accent); outline-offset: 3px; }
.guest[data-attending="no"] .guest-name { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--stone); }
.diet { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }

.confirm { display: grid; gap: var(--gap); padding: clamp(1.5rem, 1rem + 2.5vw, 2.5rem); background: var(--bg-raised); border: var(--rule-w) solid var(--accent); }
.confirm h2 { font-size: var(--step-3); }
.summary { display: grid; }
.summary-row {
  display: grid; gap: .1rem .9rem; padding-block: .85rem;
  grid-template-columns: 1fr auto; align-items: baseline;
  border-top: var(--rule-w) solid var(--rule);
}
[data-skin="sanagustin"] .summary-row b { font: 400 var(--step-1)/1.2 var(--display); }
.summary-row .answer { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent); }
.summary-row[data-attending="no"] .answer { color: var(--muted); }
.summary-row .detail { grid-column: 1 / -1; color: var(--muted); font-size: var(--step--1); }
.stats { display: flex; flex-wrap: wrap; gap: clamp(1rem, .5rem + 2vw, 2.5rem); padding-top: var(--gap); border-top: var(--rule-w) solid var(--rule); }
.stat { display: grid; gap: .3rem; }
[data-skin="sanagustin"] .stat b { font: 400 var(--step-2)/1 var(--display); font-variant-numeric: tabular-nums; }
.stat span { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }

/* ---------- the song requests' composer and votes (the wall's own styles are at the end) ---------- */
.filters { display: flex; flex-wrap: wrap; gap: .5rem; margin-left: auto; }
.filter {
  min-height: 44px; padding: .45em 1em;
  background: transparent; border: var(--rule-w) solid var(--stone); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
}
.filter[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--ivory-lit); }

.composer { display: grid; gap: var(--gap); padding: clamp(1.25rem, 1rem + 2vw, 2rem); background: var(--bg-raised); border: var(--rule-w) solid var(--rule); }
.composer-row { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }

@keyframes land { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: none; } }
[data-skin="sanagustin"] .tag {
  font: 600 calc(var(--step--1) * .92)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  padding: .35em .6em; border: 1px solid var(--stone); color: var(--muted);
}
.react {
  display: inline-flex; align-items: center; gap: .4rem;
  min-height: 44px; padding: .4em .85em;
  background: transparent; border: var(--rule-w) solid var(--stone); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .08em; color: var(--ink);
}
.react:hover { border-color: var(--accent); }
.react[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.react-mark { font-family: var(--display); font-size: 1.05em; }

/* ---------- forever ---------- */
.locked { display: grid; gap: var(--gap); padding: clamp(1.75rem, 1.2rem + 3vw, 3.5rem); background: var(--bg-raised); border: var(--rule-w) solid var(--rule); text-align: center; justify-items: center; }
.counter { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(1rem, .5rem + 3vw, 3.5rem); }
.fchapter { display: grid; gap: var(--gap); padding-block: clamp(2rem, 1.2rem + 3vw, 4rem); border-top: var(--rule-w) solid var(--rule); }
.fchapter:first-child { border-top: 0; }
.fchapter-shots img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; cursor: zoom-in; }
@media (min-width: 48em) {
  .fchapter { grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); align-items: start; }
}

/* ---------- entourage ---------- */
.ent-grid { display: grid; gap: var(--gap) clamp(1.5rem, 1rem + 3vw, 4rem); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.ent-col { display: grid; gap: .8rem; align-content: start; }
[data-skin="sanagustin"] .ent-col :is(h2, h3) { font-size: var(--step-1); }
.ent-col .meta { letter-spacing: .14em; text-transform: uppercase; }
.ent-list { display: grid; gap: .35rem; }
.ent-list li { line-height: 1.5; }
.ent-pair { display: grid; gap: .1rem; padding-block: .45rem; border-top: var(--rule-w) solid var(--rule); }
.ent-pair span { font: var(--step--1)/1.4 var(--ui); color: var(--muted); }

/* ---------- lightbox ---------- */
.lb { background: rgba(34, 26, 21, .93); }
.lb img { border: 1px solid rgba(246, 241, 232, .25); }
.lb-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .8rem 1.2rem; color: var(--ivory); font: var(--step--1)/1.5 var(--ui); }
.lb-btn {
  min-height: 44px; min-width: 44px; padding: .4em 1em;
  background: transparent; color: var(--ivory-lit);
  border: 1px solid rgba(246, 241, 232, .4); border-radius: var(--radius);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
}
.lb-btn:hover { background: rgba(246, 241, 232, .15); }
[data-skin="sanagustin"] .lb a, [data-skin="sanagustin"] .demo-bar a { color: var(--ivory-lit); }
.lb-caption { max-width: 40em; text-align: center; color: var(--stone); }

/* ---------- footer ---------- */
.foot { padding-block: var(--pad-band) clamp(2rem, 1rem + 3vw, 3rem); background: var(--ink); color: var(--ivory); }
[data-skin="sanagustin"] .foot a { color: var(--ivory-lit); }
[data-skin="sanagustin"] .foot a:hover { color: var(--accent-soft); }
.foot-grid { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.foot h2 { font-size: var(--step-2); }
.foot .meta, .foot .hint, .foot p { color: var(--stone); }   /* --muted is 2.8:1 on ink */
.foot h2, .foot strong { color: var(--ivory-lit); }
[data-skin="sanagustin"] .foot .eyebrow { color: var(--stone); }
.foot-links { display: grid; gap: .5rem; }
.foot-links a { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; text-decoration: none; }
.foot-mark { margin-top: var(--pad-band); padding-top: 1.5rem; border-top: 1px solid rgba(246, 241, 232, .22); color: var(--stone); font: var(--step--1)/1.7 var(--ui); }
.foot-mark strong { color: var(--ivory-lit); }
/* The one element allowed to repeat verbatim across all eight sites — see DECISIONS.md → the
   differentiation charter, "where coherence comes from instead". Tiny, and never louder. Site 1
   was the last site without it. The footer is inked here, so the diamond takes --accent-soft:
   --accent is burgundy and disappears on ink. The text inherits .foot p's --stone, already
   computed against this ground. */
.wsd-mark { display: flex; align-items: center; gap: .55rem; margin-top: 1.6rem; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .26em; text-transform: uppercase; }
.wsd-mark i { display: block; width: .4rem; height: .4rem; background: var(--accent-soft); transform: rotate(45deg); }

/* ---------- utilities the pages lean on ---------- */
.figure-note { margin-top: .5rem; font: var(--step--1)/1.5 var(--ui); color: var(--muted); }
.plate { padding: clamp(1.25rem, 1rem + 2vw, 2.25rem); background: var(--bg-raised); border: var(--rule-w) solid var(--rule); }
.plate--accent { border-color: var(--accent); }
.list-checks { display: grid; gap: .7rem; }
.list-checks li { display: grid; grid-template-columns: 1.2rem 1fr; gap: .7rem; line-height: 1.6; }
.list-checks li::before { content: "\2014"; color: var(--accent); }
.faq { display: grid; gap: 0; }
.faq details { padding-block: 1.1rem; border-top: var(--rule-w) solid var(--rule); }
[data-skin="sanagustin"] .faq summary { font: 400 var(--step-1)/1.3 var(--display); cursor: pointer; }
.faq summary::marker { color: var(--accent); }
.faq p { margin-top: .6rem; color: var(--muted); max-width: 44em; }

/* ---------- additions the pages lean on ---------- */
.actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: var(--gap); }
.chapter-shots:not(:empty) { margin-top: var(--gap); }
.fchapter-shots:not(:empty) { display: grid; gap: .8rem; }
.party > form { margin-top: var(--gap); }
.rsvp > .lookup + .chips { margin-top: calc(var(--gap) * -.4); }
.guest:last-of-type { border-bottom: 2px solid var(--ink); }
.count-cell .count-num { font-size: var(--step-3); }
.counter .count-num { font-size: var(--step-4); color: var(--accent); }
@media (max-width: 40em) {
  .choices { margin-left: 0; width: 100%; }
  .choice { flex: 1 1 auto; justify-content: center; }
  .demo-ctl { margin-left: 0; width: 100%; }
}

.demo-btn { white-space: nowrap; }
.counter .hint { flex-basis: 100%; text-align: center; color: var(--muted); }

/* verifier pass 2026-08-11: touch-target floor in the footer + focus rings on dark bands */
.foot-links a { display: flex; align-items: center; min-height: 44px; padding-block: .6rem; }
.brand { min-height: 44px; align-content: center; }
.foot :focus-visible, .demo-bar :focus-visible { outline-color: var(--accent-soft); }

/* ---------- a11y pass 2026-08-11: every interactive box clears 44px ---------- */
.link {
  display: inline-flex; align-items: center;
  min-height: 44px; padding-block: .7em;
}
.nav a { padding: .85rem 0; }
@media (min-width: 62em) {
  .nav a {
    display: flex; align-items: center; justify-content: center;
    min-height: 44px; min-width: 44px; padding-block: .5rem;
  }
  .nav ul { align-items: center; }
}
.faq summary { min-height: 44px; display: flex; align-items: center; gap: .5rem; }

/* ---------- the demo bar, compacted on small screens ---------- */
@media (max-width: 61.99em) {
  .demo-bar { padding: .5rem var(--gap); gap: .35rem .8rem; }
  .demo-ctl { width: 100%; margin-left: 0; gap: .4rem; align-items: center; }
  .demo-ctl > span { flex: 1 1 12rem; margin: 0; }
  .demo-btn { padding: .3em .75em; letter-spacing: .06em; }
}
@media (max-width: 40em) {
  .demo-tail { display: none; }          /* "Design study" carries it on a phone */
  .demo-ctl > span { display: none; }    /* the button label states the phase */
  .demo-btn { flex: 1 1 auto; }
}


/* ============================================================================
   THE CRAFT LAYER — atelier.css, composed onto this skin (added 2026-08-12)

   Nothing below styles an atelier utility from scratch; it tunes the tokens the
   utilities read, and says WHERE on this site each one applies. Any other skin
   makes different choices from the same four bundles. See ENGINE-SPEC.md → The
   craft layer.
   ========================================================================== */

[data-skin="sanagustin"] {
  --accent-lit: var(--burgundy-lit);
  --on-accent: var(--ivory-lit);      /* ink that sits ON burgundy — 9.6:1 */

  --a-grain-opacity: .42;             /* Intramuros stone, not sandpaper */
  --a-grain-size: 140px;
  --a-press-high: rgba(255, 253, 248, .9);
  --a-press-low: rgba(34, 26, 21, .12);
  --a-deckle: 11px;
  --a-tilt: .9deg;                    /* a whisper — the feed is already columned */
  --a-corner: 16px;
  --a-lift: 26px;
  --a-seal-size: clamp(74px, 8vw, 96px);
  --a-shadow: 0 14px 28px -22px rgba(34, 26, 21, .6);
  --a-shadow-deep: 0 16px 30px -22px rgba(34, 26, 21, .75);
}

/* ---- A · paper ------------------------------------------------------------ */
/* The invitation plate is the one torn sheet on the site. Rarity is the point. */
.hero-plate.a-deckle { border: 0; box-shadow: 0 1px 0 var(--a-press-high) inset, var(--a-shadow); }
.seal-line { display: flex; justify-content: flex-start; margin: 0 0 1.15rem; }
[data-skin="sanagustin"] .a-seal { font-size: calc(var(--a-seal-size) * .26); letter-spacing: .04em; }
.locked.a-deckle { border: 0; box-shadow: 0 1px 0 var(--a-press-high) inset, var(--a-shadow); }

/* ---- B · editorial -------------------------------------------------------- */
/* One drop cap per chapter, in the story's left-set bodies — never in a centred block. */
[data-skin="sanagustin"] .chapter .a-dropcap > p:first-of-type::first-letter { font-size: 3.4em; padding-right: .1em; }
[data-skin="sanagustin"] .meta, [data-skin="sanagustin"] .chapter-date { font-variant-numeric: oldstyle-nums; }
[data-skin="sanagustin"] .count-num, [data-skin="sanagustin"] .a-odo { font-variant-numeric: lining-nums tabular-nums; }
/* Matting: the story shots sit on a mat, so the placeholder ratio reads as a decision. */
.chapter-shots .a-mat { padding: .8rem .8rem .6rem; }
.chapter-shots .a-mat > img { aspect-ratio: 3 / 2; object-fit: cover; cursor: zoom-in; }
.fchapter-shots .a-corners > img { cursor: zoom-in; }

/* ---- C · choreography ----------------------------------------------------- */
/* The odometer is a sibling of the (now hidden) cell, so it takes the cell's type here. */
[data-skin="sanagustin"] .count-cell .a-odo { font: 400 var(--step-3)/1 var(--display); }
[data-skin="sanagustin"] .counter .a-odo { font: 400 var(--step-4)/1 var(--display); color: var(--accent); }
.count-cell .a-odo-win { min-width: .58ch; }
/* Chapters and schedule groups arrive; the two hero images unveil. Nothing else moves. */
.gallery figure.a-unveil { display: block; }

/* ---- D · keepsakes -------------------------------------------------------- */
/* Pinboard: tipped in, but inside a column layout, so the tilt is small and the
   gutter is widened to keep two neighbours from touching at 1024px. */
.feed .post.a-pin { box-shadow: var(--a-shadow-deep); }
.feed .post.a-pin:hover { z-index: 2; position: relative; }
.post .a-corners { position: relative; line-height: 0; }
/* The reply card is cancelled when the confirmation appears. */
.confirm { position: relative; }
/* One line, so it is set a little wider and tighter than the atelier default. */
[data-skin="sanagustin"] .confirm .a-stamp { top: 1.1rem; right: 1.1rem; padding: .4rem .8rem; letter-spacing: .18em; }
@media (max-width: 34em) {
  [data-skin="sanagustin"] .confirm .a-stamp { position: static; align-self: start; transform: rotate(-6deg); margin-bottom: .4rem; }
}


/* ============================================================================
   THE EXPERIENCE LAYER — added 2026-08-12
   E · the site knows you   F · the arrival   G · scale   H · the guest card
   ========================================================================== */

/* ---- E · the welcome ribbon, and everything it personalises ---------------- */
.guest-ribbon {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem;
  padding: .6rem var(--gap);
  background: var(--blush); color: var(--ink);
  border-bottom: var(--rule-w) solid var(--stone);
  font: var(--step--1)/1.5 var(--ui);
}
.guest-hello b { font-weight: 700; }
.guest-facts { color: var(--muted); }
.guest-dot { padding-inline: .5em; color: var(--stone-deep); }
.guest-forget {
  margin-left: auto; min-height: 44px; padding: .4em .9em;
  background: transparent; color: var(--accent);
  border: 1px solid var(--stone-deep); border-radius: var(--radius);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
}
.guest-forget:hover { border-color: var(--accent); background: var(--ivory-lit); }
.guest-forget, .actions .btn { white-space: nowrap; }
.guest-said:not(:empty) { flex-basis: 100%; color: var(--accent); }
@media (max-width: 40em) { .guest-forget { margin-left: 0; } }

/* Their table and shuttle, on The Day */
.yours {
  display: grid; gap: .8rem; margin-bottom: var(--gap);
  padding: clamp(1.1rem, .8rem + 1.6vw, 1.8rem);
  background: var(--bg-raised); border: var(--rule-w) solid var(--accent);
}
.yours-grid { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); margin: 0; }
[data-skin="sanagustin"] .yours-grid dt { font: 600 var(--step--1)/1.2 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--sand-ink); margin-bottom: .25rem; }
[data-skin="sanagustin"] .yours-grid dd { margin: 0; font: 400 var(--step-1)/1.3 var(--display); }
.posting-as { font: var(--step--1)/1.5 var(--ui); color: var(--muted); margin-bottom: calc(var(--gap) * -.35); }
.posting-as b { color: var(--ink); }

/* ---- F · the arrival ------------------------------------------------------
   The stage's colour, the seal's size and the type's colour now live in the arrival token
   block at the foot of this file. Nothing here may set `background` on .a-arrival: the
   shorthand wipes the engine's gradient layers and takes the lattice with them. */
.a-arrival .eyebrow { margin: 0; }
[data-skin="sanagustin"] .a-arrival .hint { letter-spacing: .06em; }

/* ---- G · scale: the invitation is a photograph first ----------------------
   Rewritten 2026-08-19 (the overture). The photograph takes the whole first viewport and is
   never quite still: it settles out of a slight over-scale on load, then breathes. The names sit
   on the empty left of the frame. The invitation is a CARD laid across the photograph's
   bottom-right corner — half on the picture, half on the page — carrying the date lockup, the
   live counter and the one reply. Nothing in the hero is centred: a centred stack of uppercase
   meta is what a template does when it does not know the content. */
.hero--bleed { padding-block: 0; }
.hero--bleed .hero-figure { position: relative; margin: 0; }
.hero--bleed .hero-figure img {
  width: 100%; height: clamp(26rem, 86vh, 52rem); object-fit: cover; object-position: 50% 8%;
  aspect-ratio: auto; filter: saturate(.9) contrast(1.03);
}
/* Two scrims, not one. The bottom one holds the names' baseline; the wash from the left keeps
   them legible whatever the crop puts behind them once the frame is this tall. */
.hero--bleed .hero-figure::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(102deg, rgba(34, 26, 21, .56) 0%, rgba(34, 26, 21, .18) 40%, transparent 68%),
    linear-gradient(
      rgba(34, 26, 21, 0) 0%,
      rgba(34, 26, 21, .12) 26%,
      rgba(34, 26, 21, .56) 62%,
      rgba(34, 26, 21, .8) 100%
    );
}
.hero-over {
  position: absolute; left: 0; right: 0; bottom: clamp(1.6rem, 5vw, 3.6rem); z-index: 2;
  width: min(100% - 2 * var(--gap), var(--wrap)); margin-inline: auto;
  color: var(--ivory-lit);
}
[data-skin="sanagustin"] .hero-over .eyebrow {
  color: var(--blush); margin-bottom: .6rem;
  text-shadow: 0 1px 14px rgba(34, 26, 21, .75);
}
[data-skin="sanagustin"] .hero-over .hero-names {
  font-size: clamp(2.6rem, 1.2rem + 7.5vw, 7rem); line-height: .92; letter-spacing: -.02em;
  text-shadow: 0 2px 30px rgba(34, 26, 21, .5);
}
.hero-over .hero-amp { display: block; font-size: .3em; color: var(--blush); margin-block: .12em; }
/* The names stop short of the card's column rather than running under it. Constrain the
   children, never .hero-over itself — it is auto-centred inside the wrap, so a max-width on it
   would pull the names back towards the middle. */
@media (min-width: 62em) {
  .hero-over > * { max-width: 34rem; }
}

/* the invitation card.
   The overlap is a decision about ROOM, and only 62em and up has it: below that the names are
   still at their fluid size and uncapped, so a card pulled up over the frame lands on "Paolo".
   Default is therefore the stacked case — card under the photograph — and the corner placement is
   authored inside the query, not walked back out of it at three breakpoints. */
.hero--bleed .hero-plate {
  position: relative; text-align: left;
  width: min(100% - 1.6rem, 30rem);
  margin: 1.15rem auto 0;
  padding: clamp(1.5rem, 1rem + 1.6vw, 2.2rem);
}
@media (min-width: 62em) {
  .hero--bleed .hero-plate {
    width: min(100% - 2rem, 24.5rem);
    margin: -11rem 0 0 auto;
  }
}

/* The date lockup: the month over a ruled line carrying the day, the numeral and the year.
   One paragraph of centred uppercase meta is what this replaced. */
.plate-month {
  margin: 0 0 .55rem;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .36em; text-transform: uppercase;
  color: var(--sand-ink);
}
.plate-when {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .8rem;
  margin: 0; padding-block: .3rem;
  border-block: var(--rule-w) solid var(--rule);
}
.plate-when span { font: 600 var(--step--1)/1.2 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.plate-when span:last-child { text-align: right; }
[data-skin="sanagustin"] .plate-when b {
  font: 400 var(--step-3)/1 var(--display); letter-spacing: -.02em;
  font-variant-numeric: lining-nums;
}
.plate-time { margin: .7rem 0 0; font-style: italic; color: var(--sand-ink); }
[data-skin="sanagustin"] .hero-plate .meta {
  margin-top: .2rem; font-size: var(--step--1); letter-spacing: .18em; text-transform: uppercase;
}

/* Retired 2026-08-20 with the panel's simplification: .plate-count, .plate-count-cap and the
   four-cell .count--panel grid. The invitation now carries ONE line of counter (.plate-tick) and
   the digits' display treatment stays here only for .count--plate, which the interior pages use. */
.count--plate { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.count--plate .count-cell {
  min-width: 0; text-align: left; padding: .5rem .5rem .55rem 0;
  border-top: var(--rule-w) solid var(--rule);
}
.count--plate .count-cell:nth-child(2n) {
  padding-inline: .9rem 0; border-left: var(--rule-w) solid var(--rule);
}
[data-skin="sanagustin"] .count--plate .count-num,
[data-skin="sanagustin"] .count--plate .a-odo { font: 400 var(--step-2)/1 var(--display); }
[data-skin="sanagustin"] .count--plate .count-label {
  margin-top: .28rem; letter-spacing: .16em; color: var(--sand-ink);
}
.plate-act { margin: 1.25rem 0 0; }

/* The dark passage keeps its place in the scroll and its job — the breath between the parish
   and the reception — but the digits have gone up into the invitation, so it is a short band
   now rather than a section. */
.band--breath { padding-block: clamp(2.2rem, 1.4rem + 3vw, 4.25rem); }

/* Story: the blurb leaves the head and becomes an outer-column note. */
[data-skin="sanagustin"] .chapter-no { font-size: var(--step-4); color: #827663; /* a display numeral: 3:1 on the paper, not ornament-pale (C1) */ line-height: .8; }
@media (min-width: 62em) {
  .chapter { grid-template-columns: 5.5rem minmax(0, 1fr) 13rem; }
  .chapter .chapter-head { grid-column: 2; }
  .chapter > div:not(.chapter-head) { grid-column: 2; }
  [data-skin="sanagustin"] .chapter-blurb {
    grid-column: 3; grid-row: 1 / span 2; align-self: start;
    border-top: 2px solid var(--accent); padding-top: .5rem; font-size: var(--step-0);
  }
}

/* ---- H · the guest card --------------------------------------------------- */
.gcard {
  display: grid; gap: .7rem; max-width: 26rem; margin-top: var(--gap);
  padding: clamp(1.1rem, .9rem + 1.2vw, 1.5rem);
  background: var(--bg-raised); box-shadow: 0 14px 28px -22px rgba(34, 26, 21, .7);
}
.gcard-top {
  display: flex; justify-content: space-between; gap: .8rem;
  border-bottom: var(--rule-w) solid var(--rule); padding-bottom: .5rem;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--sand-ink);
}
[data-skin="sanagustin"] .gcard h3 { font-size: var(--step-2); }
.gcard-list { display: grid; grid-template-columns: 7rem 1fr; gap: .4rem 1rem; margin: 0; }
[data-skin="sanagustin"] .gcard-list dt { font: 600 var(--step--1)/1.4 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--sand-ink); }
[data-skin="sanagustin"] .gcard-list dd { margin: 0; font: 400 var(--step-0)/1.4 var(--display); }
.gcard-note { border-top: var(--rule-w) solid var(--rule); padding-top: .6rem; font: var(--step--1)/1.5 var(--ui); color: var(--muted); margin: 0; }

/* ============================================================================
   THE QUEUED FEATURES — added 2026-08-13
   The venue map (engine/venuemap.js) · song requests (engine/songs.js) ·
   the wall's "your table" mark. Skin only: geometry comes from the engine.
   ========================================================================== */

/* ---- the venue map ------------------------------------------------------- */
.vmap { display: grid; gap: var(--gap); }
.vmap-head { display: flex; flex-wrap: wrap; align-items: end; gap: .8rem var(--gap); }
.vmap-head .eyebrow { margin-bottom: .5rem; }
.vmap-extents { display: flex; flex-wrap: wrap; gap: .5rem; margin-left: auto; }
.vmap-body { display: grid; gap: var(--gap); }
@media (min-width: 62em) {
  .vmap-body { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: start; }
  .vmap-plot { grid-column: 1; }
  .vmap-side { grid-column: 2; }
}

.vmap-plot {
  position: relative;
  /* The engine sets the projected box's aspect ratio; --vmap-a is the same number, and
     capping the WIDTH by it is the only way to bound the height without distorting scale. */
  width: min(100%, calc(66vh * var(--vmap-a, 1)));
  margin-inline: auto;
  background: var(--bg-raised);
  border: var(--rule-w) solid var(--stone-deep);
  aspect-ratio: 1;            /* the engine overwrites this with the projected box */
}
.vmap-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.vmap-lines .vmap-grid { stroke: var(--rule); stroke-width: 1; }
.vmap-lines .vmap-line { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 4; }
.vmap-marks { position: absolute; inset: 0; }

.map-mark {
  position: absolute; display: grid; place-items: center;
  width: 44px; height: 44px; margin: -22px 0 0 -22px; padding: 0;
  background: transparent; border: 0; cursor: pointer;
}
.map-dot {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 3px var(--bg-raised);
  transition: transform var(--motion-fast) var(--ease);
}
.map-mark[data-kind="landmark"] .map-dot {
  width: 9px; height: 9px; background: var(--bg-raised);
  border: 1.5px solid var(--stone-deep); box-shadow: none;
}
.map-mark[data-invented] .map-dot { background: var(--bg-raised); border: 2px dashed var(--accent); width: 15px; height: 15px; }
[data-skin="sanagustin"] .map-label {
  position: absolute; top: 30px; left: 50%; transform: translateX(-50%);
  padding: .12em .4em; white-space: nowrap; pointer-events: none;
  background: color-mix(in oklab, var(--bg-raised) 88%, transparent);
  font: 600 12px/1.2 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink);
}
.map-mark[data-kind="landmark"] .map-label { opacity: 0; color: var(--sand-ink); transition: opacity var(--motion-fast) var(--ease); }
.map-mark[data-kind="landmark"]:hover .map-label,
.map-mark[data-kind="landmark"]:focus-visible .map-label,
.map-mark[data-kind="landmark"][data-selected] .map-label { opacity: 1; }
.map-mark:hover .map-dot { transform: scale(1.25); }
.map-mark[data-selected] .map-dot { transform: scale(1.45); background: var(--accent); border-color: var(--accent); }
.map-mark[data-selected] .map-label { color: var(--accent); }

.vmap-dist {
  position: absolute; transform: translate(-50%, -50%); margin: 0;
  padding: .2em .5em; background: var(--bg-raised);
  font: 600 12px/1.2 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent);
}
[data-skin="sanagustin"] .vmap-north {
  position: absolute; top: .6rem; right: .8rem; margin: 0; text-align: center;
  font: 600 12px/1.1 var(--ui); letter-spacing: .1em; color: var(--sand-ink);
}
.vmap-north::before { content: "\2191"; display: block; font-size: 1.15em; }
.vmap-scale {
  position: absolute; bottom: .7rem; left: .8rem; margin: 0;
  display: flex; align-items: center; gap: .5rem;
}
.vmap-bar {
  display: block; height: 6px; min-width: 12px;
  border: 1px solid var(--sand-ink); border-top: 0;
}
[data-skin="sanagustin"] .vmap-bar-label { font: 600 12px/1 var(--ui); letter-spacing: .1em; color: var(--sand-ink); }

.vmap-side { display: grid; gap: var(--gap); align-content: start; }
.vmap-detail { display: grid; gap: .4rem; padding-top: 1rem; border-top: 2px solid var(--ink); }
.vmap-detail h3 { font-size: var(--step-2); }
.vmap-detail .meta { letter-spacing: .14em; text-transform: uppercase; }
.vmap-detail p { color: var(--muted); }
.vmap-detail[data-invented] { border-top-color: var(--accent); }
[data-skin="sanagustin"] .vmap-legend { font: var(--step--1)/1.7 var(--ui); color: var(--muted); max-width: 38em; }
.vmap-legend strong { color: var(--ink); }

/* ---- song requests ------------------------------------------------------- */
.songs { display: grid; gap: var(--gap); }
.songs > .eyebrow, .songs > h2 { margin-bottom: 0; }
.songs > .lede { margin: 0; }
.songs .composer-row { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.songs .hint[role="status"]:empty { display: none; }
.song-list { display: grid; margin: 0; padding: 0; list-style: none; counter-reset: song; }
.song {
  display: grid; gap: .2rem .9rem; align-items: center; padding-block: .9rem;
  grid-template-columns: 2.6rem minmax(0, 1fr) auto;
  border-top: var(--rule-w) solid var(--rule);
}
.song:last-child { border-bottom: var(--rule-w) solid var(--rule); }
.song::before {
  counter-increment: song; content: counter(song, decimal-leading-zero);
  grid-row: 1 / span 2; align-self: start;
  font: 400 var(--step-1)/1.2 var(--display); color: var(--stone-deep);
  font-variant-numeric: oldstyle-nums;
}
.song-main { grid-column: 2; display: grid; gap: .15rem; }
[data-skin="sanagustin"] .song-title { font: 400 var(--step-1)/1.25 var(--display); }
[data-skin="sanagustin"] .song-meta { font: var(--step--1)/1.4 var(--ui); letter-spacing: .06em; color: var(--ink); }
[data-skin="sanagustin"] .song-by { font: var(--step--1)/1.4 var(--ui); color: var(--muted); }
.song-meta:empty { display: none; }
.song .react { grid-column: 3; grid-row: 1 / span 2; min-width: 44px; }
.song .tag--mine { grid-column: 3; grid-row: 1; justify-self: end; align-self: center; border-color: var(--accent); color: var(--accent); }
.song[data-mine] { background: linear-gradient(90deg, var(--accent-soft), transparent 60%); }
.song[data-new] { animation: land var(--motion-slow) var(--ease); }
@media (max-width: 34em) {
  .song { grid-template-columns: 2.2rem minmax(0, 1fr); }
  .song .react, .song .tag--mine { grid-column: 2; grid-row: auto; justify-self: start; }
}

/* ---- the wall: a post from your own table ------------------------------- */
.post .tag--table { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ---- the room: tables, chairs, and the couple's arranging sandbox ---------- */
/* engine/seating.js writes geometry only: a centre position in %, and --tw/--th/--sx/--sy as
   container-query lengths off the plot's width. Sizing both axes in cqw is what keeps a round
   table round and every table to scale while its button still clears 44px around it. */
.seating { display: grid; gap: var(--gap); }
.seat-head { display: flex; flex-wrap: wrap; align-items: end; gap: .8rem var(--gap); }
.seat-head .eyebrow { margin-bottom: .5rem; }
.seat-mode {
  margin-left: auto; display: inline-flex; align-items: center; gap: .55rem;
  min-height: 44px; min-width: 44px; padding: .55em 1.1em;
  background: transparent; color: var(--accent);
  border: var(--rule-w) dashed var(--stone-deep); border-radius: var(--radius);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
}
.seat-mode::before {
  content: ""; width: .5rem; height: .5rem; flex: none;
  background: var(--accent); transform: rotate(45deg);
}
.seat-mode:hover { background: var(--accent-soft); border-color: var(--accent); }
.seat-mode[aria-pressed="true"] { border-style: solid; border-color: var(--accent); background: var(--accent-soft); }

.seat-mine {
  display: grid; gap: .8rem var(--gap); align-items: start;
  padding-top: 1rem; border-top: 2px solid var(--accent);
}
.seat-mine-head { display: grid; gap: .3rem; }
[data-skin="sanagustin"] .seat-mine-name { font: 400 var(--step-2)/1.15 var(--display); margin: 0; }
.seat-mine-name span { display: block; font-size: var(--step-0); color: var(--muted); font-style: italic; }
.seat-mine .meta { margin: 0; }
@media (min-width: 48em) {
  .seat-mine { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
}

.seat-body { display: grid; gap: var(--gap); }
@media (min-width: 62em) {
  .seat-body { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); align-items: start; }
  .seat-plot { grid-column: 1; }
  .seat-side { grid-column: 2; }
}

.seat-plot {
  container-type: inline-size;
  position: relative;
  /* the same trick the venue map uses: cap the WIDTH by the room's ratio, so the height is
     bounded without the plan ever being stretched off scale */
  width: min(100%, calc(64vh * var(--seat-a, 1.3)));
  margin-inline: auto;
  background: var(--bg-raised);
  border: var(--rule-w) solid var(--rule);
  aspect-ratio: 1.3;          /* the engine overwrites this with the room's own ratio */
  /* a revealed label on an edge table would otherwise hang past the sheet and count as page
     overflow at 390px — the room's own border is the frame */
  overflow: clip;
}
.seat-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.seat-lines .seat-grid { stroke: var(--rule); stroke-width: 1; }
.seat-lines .seat-room { fill: none; stroke: var(--stone-deep); stroke-width: 1.5; }
.seat-lines .seat-walk { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 4; }
.seat-anchors, .seat-marks { position: absolute; inset: 0; }

.seat-anchor {
  position: absolute; transform: translate(-50%, -50%);
  width: var(--tw); height: var(--th);
  display: grid; place-items: center; text-align: center;
  border: 1px dashed var(--stone); background: color-mix(in oklab, var(--stone) 16%, transparent);
}
.seat-anchor span {
  font: 600 12px/1.15 var(--ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--sand-ink); padding: .1em .2em;
}
.seat-anchor[data-kind="floor"] { border-style: solid; background: color-mix(in oklab, var(--accent-soft) 70%, transparent); }
.seat-anchor[data-kind="windows"] { background: var(--bg); border-color: var(--stone-deep); }
.seat-anchor[data-kind="windows"] span { display: none; }
.seat-anchor[data-point] {
  width: 12px; height: 12px; border: 0; background: var(--ink); border-radius: 50%;
}
.seat-anchor[data-point] span {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  white-space: nowrap; color: var(--ink);
}
/* a zone too narrow to hold its own label carries it underneath, on one line */
.seat-anchor[data-small] { display: block; }
.seat-anchor[data-small] span {
  position: absolute; left: 50%; top: 100%; transform: translateX(-50%);
  margin-top: 3px; white-space: nowrap;
}
.seat-anchor[data-kind="stair"] { background: var(--stone-deep); }
/* silenced by a table label showing next to it — the mark stays, the words step aside */
.seat-anchor[data-quiet] span { opacity: 0; }

.seat-table {
  position: absolute; transform: translate(-50%, -50%);
  width: var(--tw); height: var(--th); min-width: 44px; min-height: 44px;
  padding: 0; background: transparent; border: 0; cursor: pointer;
}
.seat-disc {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: var(--tw); height: var(--th); border-radius: 50%;
  display: grid; place-items: center;
  background: var(--bg-raised); border: 1.5px solid var(--stone-deep);
  transition: border-color var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}
.seat-table[data-shape="long"] .seat-disc { border-radius: calc(var(--radius) / 2); }
[data-skin="sanagustin"] .seat-num {
  font: 600 12px/1 var(--ui); letter-spacing: .06em; color: var(--sand-ink);
}
.seat-table[data-kind="head"] .seat-disc { background: var(--ink); border-color: var(--ink); }
.seat-table[data-kind="head"] .seat-num { color: var(--ivory); }
.seat-table[data-mine] .seat-disc { background: var(--accent); border-color: var(--accent); }
.seat-table[data-mine] .seat-num { color: var(--on-accent); }
.seat-table[data-flagged="note"] .seat-disc { border-style: dashed; border-color: var(--accent); }
.seat-table[data-flagged="error"] .seat-disc,
.seat-table[data-over] .seat-disc { border-width: 3px; border-color: var(--accent); }
.seat-table:hover .seat-disc { border-color: var(--accent); }
.seat-table[data-selected] .seat-disc { border-color: var(--accent); border-width: 3px; }
.seat-table[data-drop] .seat-disc { background: var(--accent-soft); border-color: var(--accent); }
.seating[data-mode="arrange"] .seat-table[data-fixed] { cursor: default; }
.seating[data-mode="arrange"] .seat-table[data-fixed] .seat-disc { border-style: dotted; }

.seat-dots { position: absolute; left: 50%; top: 50%; }
.seat-chair {
  position: absolute; left: 0; top: 0; width: 7px; height: 7px; border-radius: 50%;
  transform: translate(calc(var(--sx) - 50%), calc(var(--sy) - 50%));
  background: var(--bg); border: 1px solid var(--stone-deep);
}
.seat-chair[data-seat="in"] { background: var(--accent); border-color: var(--accent); }
.seat-chair[data-seat="pending"] { background: var(--stone); border-color: var(--stone-deep); }
.seat-chair[data-seat="fixed"] { background: var(--ink); border-color: var(--ink); }
.seat-chair[data-mine] { box-shadow: 0 0 0 2px var(--bg-raised), 0 0 0 3.5px var(--accent); }

[data-skin="sanagustin"] .seat-label {
  position: absolute; left: 50%; top: calc(50% + max(var(--th) / 2, 18px));
  transform: translateX(-50%); margin-top: .2rem;
  display: grid; gap: .1rem; white-space: nowrap; pointer-events: none;
  padding: .15em .4em; background: color-mix(in oklab, var(--bg-raised) 90%, transparent);
  font: 600 12px/1.2 var(--ui); letter-spacing: .08em; text-transform: uppercase; color: var(--ink);
  opacity: 0; transition: opacity var(--motion-fast) var(--ease);
}
.seat-label-count { font-weight: 400; letter-spacing: .04em; color: var(--sand-ink); }
.seat-table[data-mine] .seat-label,
.seat-table[data-selected] .seat-label,
.seat-table:hover .seat-label,
.seat-table:focus-visible .seat-label { opacity: 1; }
.seat-table[data-mine] .seat-label b { color: var(--accent); }

.seat-scale {
  position: absolute; bottom: .7rem; left: .8rem; margin: 0;
  display: flex; align-items: center; gap: .5rem;
}

.seat-side { display: grid; gap: var(--gap); align-content: start; }
.seat-detail { display: grid; gap: .5rem; padding-top: 1rem; border-top: 2px solid var(--ink); }
.seat-detail h3 { font-size: var(--step-2); }
.seat-detail .meta { letter-spacing: .14em; text-transform: uppercase; margin: 0; }
[data-skin="sanagustin"] .seat-detail-count { font: var(--step--1)/1.5 var(--ui); color: var(--muted); margin: 0; }
.seat-detail-count b { color: var(--accent); }
.seat-detail .prose { color: var(--muted); font-size: var(--step--1); margin: 0; }
[data-skin="sanagustin"] .seat-legend { font: var(--step--1)/1.7 var(--ui); color: var(--muted); max-width: 38em; }
.seat-legend strong { color: var(--ink); }

.seat-names { display: grid; gap: .35rem; margin: 0; padding: 0; list-style: none; }
.seat-name { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .6rem; }
[data-skin="sanagustin"] .seat-name b { font: 400 var(--step-0)/1.3 var(--display); }
.seat-name span, .seat-name em { font: var(--step--1)/1.3 var(--ui); color: var(--muted); }
.seat-name em { font-style: normal; letter-spacing: .08em; text-transform: uppercase; font-size: 12px; }
.seat-name[data-state="pending"] b { color: var(--muted); }
.seat-name[data-mine] b { color: var(--accent); }

.seat-plan {
  display: grid; gap: var(--gap);
  padding: clamp(1.1rem, .8rem + 1.2vw, 1.6rem);
  background: var(--bg-raised); border: var(--rule-w) solid var(--stone-deep);
}
.seat-plan-head { display: flex; flex-wrap: wrap; align-items: end; gap: .8rem var(--gap); }
.seat-plan-head > div:first-child { flex: 1 1 18rem; }
.seat-plan-head .eyebrow { margin-bottom: .4rem; }
.seat-plan-head .prose { margin: 0; font-size: var(--step--1); color: var(--muted); max-width: 34em; }
[data-skin="sanagustin"] .seat-sandbox {
  margin: 0; padding: .7em .9em;
  background: var(--bg); border: 1px dashed var(--stone-deep);
  font: var(--step--1)/1.6 var(--ui); color: var(--muted);
}
.seat-sandbox strong { color: var(--ink); }
.seat-plan-cols { display: grid; gap: var(--gap); }
@media (min-width: 48em) {
  .seat-plan-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  .seat-tray-wrap { grid-column: 1; }
  .seat-issues-wrap { grid-column: 2; }
}
.seat-tray-wrap, .seat-issues-wrap { display: grid; gap: .6rem; align-content: start; }
.seat-tray { display: flex; flex-wrap: wrap; gap: .5rem; }
.seat-tray:empty::after {
  content: "Everyone has a table.";
  font: var(--step--1)/1.5 var(--ui); color: var(--muted);
}
.seat-chip {
  display: grid; gap: .15rem; min-height: 44px; min-width: 44px; padding: .5em .9em;
  text-align: left; cursor: pointer;
  background: var(--bg); color: var(--ink);
  border: var(--rule-w) solid var(--stone-deep); border-radius: var(--radius);
}
[data-skin="sanagustin"] .seat-chip b { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .06em; }
.seat-chip span { font: 400 12px/1.2 var(--ui); color: var(--muted); }
.seat-chip:hover { border-color: var(--accent); background: var(--accent-soft); }
.seat-chip[data-picked] { border-color: var(--accent); border-width: 2px; background: var(--accent-soft); }
.seat-chip[data-loose] { border-style: dashed; border-color: var(--accent); }
.seat-issues { display: grid; gap: .45rem; margin: 0; padding: 0; list-style: none; }
[data-skin="sanagustin"] .seat-issue {
  display: grid; grid-template-columns: 4.4rem 1fr; gap: .2rem .7rem; align-items: baseline;
  font: var(--step--1)/1.5 var(--ui); color: var(--ink);
}
.seat-issue::before {
  content: "Note"; font-weight: 600; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--sand-ink);
}
.seat-issue[data-level="error"]::before { content: "To fix"; color: var(--accent); }

/* place cards: never on screen; in print, eight to a Letter sheet */
.pcards { display: none; }

/* Print, the couple's sheet: place cards on their own named page. html[data-print="cards"] is
   set by seating.js for the duration of that one print. A browser without named-page support
   falls back to the 5x7 below with the cards reflowing onto it — degraded, not broken. */
@media print {
  @page cards { size: letter; margin: .5in; }
  html[data-print="cards"] header,
  html[data-print="cards"] .foot,
  html[data-print="cards"] .band { display: none !important; }
  html[data-print="cards"] .band:has(.pcards) { display: block !important; }
  html[data-print="cards"] .seating > *:not(.pcards) { display: none !important; }
  html[data-print="cards"] .pcards {
    page: cards; display: grid; grid-template-columns: 1fr 1fr; gap: .18in;
  }
  html[data-print="cards"] .pcard {
    display: grid; align-content: center; justify-items: center; gap: .06in;
    height: 1.9in; padding: .12in; text-align: center;
    border: 1px dashed #b9ac97; break-inside: avoid;
  }
  html[data-print="cards"] .pcard-couple {
    margin: 0; font: 600 8pt/1.2 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: #7a6a55;
  }
  html[data-print="cards"] .pcard-name { margin: 0; font: 400 20pt/1.1 var(--display); color: #221a15; }
  html[data-print="cards"] .pcard-table { margin: 0; display: grid; gap: .02in; font: 400 11pt/1.3 var(--display); color: #221a15; }
  html[data-print="cards"] .pcard-table span { font: 400 9pt/1.3 var(--ui); color: #7a6a55; }
}

/* Print: the guest card alone, on paper, at card size. */
@media print {
  @page { size: 5in 7in; margin: .45in; }
  body { background: #fff; }
  .demo-bar, .guest-ribbon, .site-bar, .foot, .band, .actions, .lookup, .chips,
  .party, .stats, .summary, .a-arrival, .a-stamp, .wsd-skip { display: none !important; }
  .confirm { border: 0; padding: 0; display: block; }
  .confirm > *:not(.gcard) { display: none !important; }
  .gcard { max-width: none; box-shadow: none; padding: 0; break-inside: avoid; }
}

/* ============================================================================
   THE PHOTOGRAPHY PASS — added 2026-08-18
   J · the full-bleed interlude   K · pacing   L · the cold open
   M · scale contrast            N · asymmetry  O · the photograph as a surface
   Brief: tools/Worth The Price.html. Format and chrome only — no feature moved.
   ========================================================================== */

[data-skin="sanagustin"] {
  /* K · what the dark passage means in this skin. atelier.css only re-points tokens. */
  --band-ink-bg: #1c1511;
  --band-ink-raised: #241c16;
  --band-ink-fg: var(--ivory-lit);
  --band-ink-muted: #c3b6a4;          /* 7.4:1 on #1c1511 — computed, not assumed */
  --band-ink-rule: rgba(246, 241, 232, .22);
  --band-ink-accent: #e8b9c0;         /* burgundy is unreadable on ink; this is its light end */
  /* J · the scrim, in this skin's own browns rather than neutral black */
  --a-scrim: rgba(28, 21, 17, .5);
  --a-scrim-strong: rgba(28, 21, 17, .88);
  --a-scrim-ink: var(--ivory-lit);
}

/* ---- L · the cold open ---------------------------------------------------- */
/* The demo controls left the top of the page for a corner dock. The marker is the TAB, so it
   is on screen at every scroll position, on every page, undismissable — only the controls
   fold away. z-index sits above the arrival overlay (80) and below the lightbox (90). */
.demo-dock {
  position: fixed; right: 0; bottom: 0; z-index: 88;
  display: grid; justify-items: end; gap: .4rem;
  padding: clamp(.6rem, 2vw, 1rem);
  font: var(--step--1)/1.4 var(--ui);
  pointer-events: none;
}
.demo-dock > * { pointer-events: auto; }
.dock-tab {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 44px; padding: .5em .9em;
  background: #1c1511; color: var(--ivory-lit);
  border: 1px solid rgba(246, 241, 232, .3); border-radius: var(--radius);
  box-shadow: 0 10px 24px -18px rgba(0, 0, 0, .9);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  transition: background-color 200ms var(--ease), border-color 200ms var(--ease), transform 200ms var(--ease);
}
.dock-tab:hover { background: #241c16; border-color: rgba(246, 241, 232, .55); transform: translateY(-1px); }
.dock-label { white-space: nowrap; }
.dock-diamond { width: .5rem; height: .5rem; flex: none; background: var(--band-ink-accent); transform: rotate(45deg); }
.dock-tab[aria-expanded="true"] .dock-diamond { background: var(--ivory-lit); }
.dock-panel {
  width: min(21rem, calc(100vw - 2 * var(--gap)));
  display: grid; gap: .7rem;
  padding: 1rem 1.1rem;
  background: #1c1511; color: var(--ivory-lit);
  border: 1px solid rgba(246, 241, 232, .3); border-radius: var(--radius);
  box-shadow: 0 24px 48px -30px rgba(0, 0, 0, .95);
  transform-origin: 100% 100%;
}
@media (prefers-reduced-motion: no-preference) {
  .dock-panel { animation: dock-in 240ms var(--ease) both; }
  @keyframes dock-in { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
}
.dock-note { color: #c3b6a4; }
.dock-note b { color: var(--ivory-lit); font-weight: 700; }
.dock-phase { color: var(--band-ink-accent); letter-spacing: .04em; }
.dock-ctl { display: flex; flex-wrap: wrap; gap: .5rem; }
.dock-panel .demo-btn { color: var(--ivory-lit); border-color: rgba(246, 241, 232, .38); }
.dock-panel .demo-note { color: var(--band-ink-accent); }
@media print { .demo-dock { display: none; } }

/* The site bar floats on the photograph and only takes a ground once the page has moved.
   Colour comes from re-pointing the same tokens the bar's children already read. */
.site-head[data-float] .site-bar {
  position: fixed; inset: 0 0 auto 0;
  background: transparent; border-bottom-color: transparent;
  --ink: var(--ivory-lit);
  --muted: rgba(255, 252, 246, .94);   /* .82 measured 3.5:1 under the shade (C1) */
  --stone: rgba(255, 252, 246, .5);
  --stone-deep: rgba(255, 252, 246, .55);
  --accent: var(--blush);
  transition: background-color 380ms var(--ease), border-color 380ms var(--ease), box-shadow 380ms var(--ease);
}
.site-head[data-float] .site-bar .brand b,
.site-head[data-float] .site-bar .nav a { text-shadow: 0 1px 16px rgba(28, 21, 17, .6); }
/* C1 (2026-10-01): over the hero's grey sky the bar's type measured ~1.7:1. A shade falls from the
   top edge, the way a photograph darkens under a frame, and every word in the bar takes the shadow.
   The scrolled bar below replaces the image with its own ground. */
html:not([data-scrolled]) .site-head[data-float] .site-bar {
  background-image: linear-gradient(to bottom, rgba(28, 21, 17, .72), rgba(28, 21, 17, .42) 70%, rgba(28, 21, 17, 0));
}
html:not([data-scrolled]) .site-head[data-float] .site-bar :is(.brand span, .bar-rsvp, .nav-toggle, .bar-reply) {
  text-shadow: 0 1px 12px rgba(28, 21, 17, .7);
}
html[data-scrolled] .site-head[data-float] .site-bar {
  background: rgba(246, 241, 232, .95);
  border-bottom-color: var(--rule);
  box-shadow: 0 10px 30px -26px rgba(34, 26, 21, .8);
  /* Literals, not var(--x) of themselves: a self-referential custom property is a cycle and
     resolves to nothing, which silently broke the nav hover underline in this scope. */
  --ink: var(--ink-raw); --muted: #5f5349; --stone: #cec5b5;
  --stone-deep: #a89c88; --accent: var(--burgundy);
}
html[data-scrolled] .site-head[data-float] .site-bar .brand b,
html[data-scrolled] .site-head[data-float] .site-bar .nav a { text-shadow: none; }
@supports (backdrop-filter: blur(8px)) {
  html[data-scrolled] .site-head[data-float] .site-bar { background: rgba(246, 241, 232, .82); backdrop-filter: blur(10px) saturate(1.2); }
}
/* An open menu needs its own ground whatever the bar is doing. */
@media (max-width: 61.99em) {
  .site-head[data-float] .nav[data-open] {
    --ink: var(--ink-raw); --muted: #5f5349; --accent: var(--burgundy); --stone-deep: #a89c88;
    background: var(--ivory-lit); box-shadow: 0 20px 40px -30px rgba(34, 26, 21, .9);
  }
  .site-head[data-float] .nav[data-open] a { text-shadow: none; }
}
@media (prefers-reduced-motion: no-preference) {
  .nav[data-open] { animation: nav-in 220ms var(--ease) both; }
  @keyframes nav-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
}

/* The welcome ribbon now sits under the hero rather than above it: "the site knows you"
   lands after the couple's names, not before them. */
.guest-ribbon {
  border-block: var(--rule-w) solid var(--stone);
  padding-inline: max(var(--gap), calc((100% - var(--wrap)) / 2));
}
@media (prefers-reduced-motion: no-preference) {
  .guest-ribbon:not([hidden]) { animation: ribbon-in 520ms var(--ease) both; }
  @keyframes ribbon-in { from { opacity: 0; clip-path: inset(0 0 100% 0); } to { opacity: 1; clip-path: inset(0 0 0 0); } }
}

/* ---- J · the interludes --------------------------------------------------- */
.band--bleed { padding-block: 0; }
[data-skin="sanagustin"] .a-bleed-over .eyebrow { color: var(--ivory-lit); margin-bottom: .55rem; }   /* was --blush: the small caps need the light end (C1) */
[data-skin="sanagustin"] .a-bleed-over h2,
[data-skin="sanagustin"] .a-bleed-over h3 {
  font-size: clamp(1.9rem, 1rem + 3.6vw, 3.4rem); line-height: 1.02; letter-spacing: -.01em;
  color: var(--ivory-lit);
}
[data-skin="sanagustin"] .a-bleed-over p { color: rgba(255, 252, 246, .9); }
[data-skin="sanagustin"] .a-bleed-over p + p { margin-top: .7rem; }
[data-skin="sanagustin"] .a-bleed-over .meta { color: var(--blush); letter-spacing: .2em; text-transform: uppercase; }
[data-skin="sanagustin"] .a-bleed-over a.link {
  color: var(--ivory-lit); border-bottom-color: rgba(255, 252, 246, .5);
}
[data-skin="sanagustin"] .a-bleed-over a.link:hover { color: var(--blush); border-bottom-color: var(--blush); }
/* A caption sits in the frame's other corner, small, like a plate on a print. */
.bleed-credit {
  position: absolute; right: 0; bottom: 0; z-index: 1;
  margin: clamp(.9rem, 2.4vw, 1.6rem);
  font: var(--step--1)/1.4 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255, 252, 246, .92); text-shadow: 0 1px 12px rgba(28, 21, 17, .9);
}
/* C1 (2026-10-01): the frame's scrim runs one way, and a bright passage of photograph (the candles,
   the lit church, the sky) can sit where the type does, or in the feathered edge where the scrim
   thins out. So the type carries its own shade too: a feathered pool, not a panel. It has no edge
   and no fill you can see, only a darkening that follows the words. */
/* And the frame's own scrim reaches further up than the engine's default (transparent at 72%):
   on a wide frame the type's first line sits at about 60% of the height, where the default had
   already faded out over the sky. The top of every photograph stays clear. */
[data-skin="sanagustin"] .a-bleed::after {
  background: linear-gradient(var(--a-scrim-dir, to top),
    var(--a-scrim-strong) 0%, var(--a-scrim) 40%, rgba(28, 21, 17, .26) 66%, transparent 88%);
}
/* Wide frames are short for their width, so the type's first line sits higher still. */
@media (min-width: 60em) {
  [data-skin="sanagustin"] .a-bleed::after {
    background: linear-gradient(var(--a-scrim-dir, to top),
      var(--a-scrim-strong) 0%, rgba(28, 21, 17, .56) 40%, rgba(28, 21, 17, .4) 64%, rgba(28, 21, 17, .14) 84%, transparent 97%);
  }
}
[data-skin="sanagustin"] :is(.a-bleed-over, .bleed-credit) { isolation: isolate; }
[data-skin="sanagustin"] :is(.a-bleed-over, .bleed-credit)::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  inset: -3.2rem -3rem;
  background: radial-gradient(closest-side, rgba(28, 21, 17, .6), rgba(28, 21, 17, .52) 40%, rgba(28, 21, 17, .36) 62%, rgba(28, 21, 17, .2) 78%, rgba(28, 21, 17, .08) 90%, rgba(28, 21, 17, 0));
}
[data-skin="sanagustin"] .bleed-credit::before { inset: -1.4rem -2rem; }
/* The small caps are the thinnest strokes in the frame and sit over the brightest passages (the
   festoon lights, the sky over the parish): they take a close halo as well as the pool. */
[data-skin="sanagustin"] :is(.a-bleed-over .eyebrow, .a-bleed-over .meta, .bleed-credit) {
  text-shadow: 0 0 6px rgba(28, 21, 17, .9), 0 0 14px rgba(28, 21, 17, .7), 0 1px 2px rgba(28, 21, 17, .8);
}
/* 2026-08-21 · the credits come back on a phone (the user asked for them). They move to the
   frame's TOP corner there: the bottom corner of an interlude passes under the two fixed docks
   as it scrolls, which is exactly how the Entourage caption came to be read as "ILIPINIANA". */
@media (max-width: 46em) {
  .bleed-credit {
    top: 0; bottom: auto; right: 0;
    font-size: calc(var(--step--1) * .92);
  }
}

/* ---- K · the dark passage ------------------------------------------------- */
.band--ink { border-block: 0; }
[data-skin="sanagustin"] .band--ink .count-num,
[data-skin="sanagustin"] .band--ink .a-odo { color: var(--ivory-lit); }
[data-skin="sanagustin"] .band--ink .count-label { color: #c3b6a4; }
[data-skin="sanagustin"] .band--ink .eyebrow { color: var(--band-ink-accent); }
[data-skin="sanagustin"] .band--ink .hint { color: #a99b89; }
[data-skin="sanagustin"] .band--ink .hint strong { color: var(--ivory-lit); }
[data-skin="sanagustin"] .band--ink .a-press { text-shadow: none; }

/* ---- M · scale contrast: ONE oversized figure per page -------------------- */
.figure-xl { display: grid; gap: .2rem; }
[data-skin="sanagustin"] .figure-xl b {
  font: 400 var(--step-5)/.82 var(--display);
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: -.025em; color: var(--accent);
}
[data-skin="sanagustin"] .figure-xl span {
  font: 600 var(--step--1)/1.4 var(--ui); letter-spacing: .22em; text-transform: uppercase;
  color: var(--sand-ink); margin-top: .8rem;
}
.band--ink .figure-xl b { color: var(--ivory-lit); }
.band--ink .figure-xl span { color: #c3b6a4; }

/* ---- N · asymmetry: the seven-and-five spread ----------------------------- */
/* One photograph at 7 columns, its text at 5, its caption out in the margin under the frame.
   Placement is explicit from 46em — the breakpoint .a-grid12 itself appears at. */
.spread-shot { margin: 0; }
.spread-shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.spread-text { display: grid; gap: .7rem; align-content: end; }
.spread-note {
  font: italic var(--step--1)/1.6 var(--body); color: var(--muted);
  border-top: 2px solid var(--accent); padding-top: .5rem;
}
.spread-note span {
  display: block; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em;
  text-transform: uppercase; font-style: normal; color: var(--sand-ink); margin-bottom: .3rem;
}
@media (min-width: 46em) {
  .spread-shot { grid-column: 1 / 8; }
  .spread-text { grid-column: 8 / 13; padding-bottom: clamp(.5rem, 2vw, 1.5rem); }
  .spread-note { grid-column: 1 / 5; }
  .spread--flip .spread-shot { grid-column: 6 / 13; }
  .spread--flip .spread-text { grid-column: 1 / 6; }
  .spread--flip .spread-note { grid-column: 9 / 13; }
}

/* ---- O · the photograph as a surface, at a size that shows it ------------- */
/* The tone, the drift and the unmask were already written; this is where they are applied. */
.a-bleed > img { filter: saturate(.9) contrast(1.03); }

/* ---- F · interaction: everything answers back ----------------------------- */
/* `translate`, not `transform`: these cards also carry `.a-reveal`, whose keyframes fill
   `transform` forever once they have run, and a filled animation beats any normal declaration —
   the hover lift computed to nothing. Same rule as the engine's `.a-lift`. */
[data-skin="sanagustin"] .card {
  transition: translate 320ms var(--ease), box-shadow 320ms var(--ease), border-color 320ms var(--ease);
}
[data-skin="sanagustin"] .card:hover {
  translate: 0 -4px;
  border-color: var(--stone-deep);
  box-shadow: 0 22px 40px -30px rgba(34, 26, 21, .85);
}
.card > img, .card picture > img { transition: transform 620ms var(--ease); }
.card:hover > img { transform: scale(1.035); }
.card { overflow: clip; }
[data-skin="sanagustin"] .link { transition: color 200ms var(--ease), border-color 200ms var(--ease); }
[data-skin="sanagustin"] .btn {
  transition: background-color 200ms var(--ease), color 200ms var(--ease),
              border-color 200ms var(--ease), transform 120ms var(--ease);
}
[data-skin="sanagustin"] .btn:active { transform: translateY(1px) scale(.996); }
.demo-btn, .guest-forget, .nav-toggle, .lb-btn, .chip {
  transition: background-color 200ms var(--ease), color 200ms var(--ease),
              border-color 200ms var(--ease), transform 120ms var(--ease);
}
.demo-btn:active, .guest-forget:active, .nav-toggle:active, .lb-btn:active { transform: translateY(1px); }
.gallery figure, .chapter-shots .a-mat, .fchapter-shots .a-corners { overflow: clip; }
.gallery img, .chapter-shots img, .fchapter-shots img { transition: transform 700ms var(--ease), filter 700ms var(--ease); }
.gallery figure:hover img, .chapter-shots .a-mat:hover img, .fchapter-shots .a-corners:hover img { transform: scale(1.04); }
/* The seal is the one thing on the page asking to be touched. It breathes only while sealed —
   once the wax cracks the halves own the transform. */
@media (prefers-reduced-motion: no-preference) {
  [data-skin="sanagustin"] .a-env[data-stage="sealed"] .a-seal-half { animation: seal-breathe 3.6s ease-in-out 1.2s infinite; }
  /* Depth is animated as a drop-shadow, not a box-shadow: an animated declaration outranks the
     rule that turns box-shadow off for the photographed wax, and the halo came back as a grey
     rectangle-ish blob around a transparent seal. drop-shadow follows the alpha. */
  @keyframes seal-breathe {
    0%, 100% { transform: none; filter: drop-shadow(0 5px 8px rgba(0, 0, 0, .55)); }
    50% { transform: translateY(-3px) scale(1.02); filter: drop-shadow(0 9px 14px rgba(0, 0, 0, .62)); }
  }
  [data-skin="sanagustin"] .a-seal:hover .a-seal-half { animation-play-state: paused; }
}

/* ---- the rule-underline call to action (the filled block is now the RSVP's alone) ---- */
[data-skin="sanagustin"] a.btn--rule,
[data-skin="sanagustin"] button.btn--rule {
  display: inline-flex; align-items: center; gap: .6em;
  min-height: 48px; padding: .2em 0;
  background: transparent; border: 0;
  border-bottom: 1px solid var(--stone-deep); border-radius: 0;
  font: 600 var(--step-0)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent); text-decoration: none; white-space: nowrap;
  transition: border-color 240ms var(--ease), color 240ms var(--ease), gap 240ms var(--ease);
}
[data-skin="sanagustin"] a.btn--rule:hover,
[data-skin="sanagustin"] button.btn--rule:hover {
  background: transparent; color: var(--burgundy-lit); border-bottom-color: var(--accent); gap: .95em;
}
.btn--rule .btn-arrow { font-size: 1.1em; line-height: 1; }
[data-skin="sanagustin"] .a-bleed-over a.btn--rule,
[data-skin="sanagustin"] .a-bleed-over button.btn--rule {
  color: var(--ivory-lit); border-bottom-color: rgba(255, 252, 246, .55);
}
[data-skin="sanagustin"] .a-bleed-over a.btn--rule:hover { color: var(--blush); border-bottom-color: var(--blush); }


/* ---------- the arrival: envelope, wax and lining (2026-08-18) ----------
   Tokens only. The geometry and the four beats belong to engine/atelier.css.
   The photography landed 2026-08-18 (night); the *-img tokens below are the whole of the
   change — no markup, no geometry, and the CSS-drawn fallbacks stay in place beneath them. */
[data-skin="sanagustin"] .a-arrival {
  background-color: #1b1410;                /* stated here too: an ancestor-scoped token alone
                                               loses to any later shorthand in this file */
  --a-arr-bg: #1b1410;
  --a-arr-ink: var(--ivory-lit);
  --a-arr-lift: rgba(246, 241, 232, .14);
  --a-arr-floor: rgba(0, 0, 0, .6);
  --a-arr-tile-step: 30px;
  --a-arr-tile-opacity: .1;   /* .06 was set for a 1px drawn lattice; a mostly-ivory photographed
                                 tile needs more before the motif reads at all */
  /* The stage's wall is now the azulejo itself rather than a drawn lattice. It is a mostly-ivory
     tile, so it lifts the whole plane as well as patterning it: opacity is set for the ground,
     not for the motif. */
  --a-arr-tile: url(../assets/sanagustin/azulejo-tile.webp);
  --a-arr-tile-size: 240px 240px;
  --a-env-paper: #e9e0cf;
  --a-env-lining: #2a3346;               /* azulejo ink blue, seen when the flap is thrown back */
  --a-card-paper: var(--ivory-lit);
  --a-card-ink: var(--ink);
  --a-wax-deep: #3c0a15;
  /* Sized off the envelope, never the viewport: --a-env-w is capped in vh as well as vw, so a
     vw-based seal diverges from it on a short screen. 44px floor keeps the button a legal target. */
  --a-seal-size: clamp(48px, calc(var(--a-env-w) * .27), 124px);   /* .22 was set for a drawn disc;
     the photographed wax carries a debossed monogram, which needs the size to be legible */
  /* ⚠ These paths are relative to engine/atelier.css, NOT to this file. A url() inside a custom
     property is resolved against the stylesheet that *uses* the property, and every *-img token
     below is consumed by the engine. Written ../../ (correct from here) they resolved one level
     above the project root and 404'd — the wax, the flap and the stage's wall all rendered empty.
     A skin that consumes one of these tokens in its own rule needs the other path; don't.
     WebP wherever alpha meets paper fibre; JPEG for everything opaque. */
  /* envelope-paper.jpg, not the delivered envelope-front photograph: that one carries its own
     printed V seams, and the engine draws the pocket's V itself with clip-path — the two doubled
     up into four diagonals. The faces take a seam-free sheet instead; the flap keeps its
     photograph, which is where the deckle edge actually shows. */
  --a-env-paper-img: url(../assets/sanagustin/envelope-paper.jpg);
  --a-env-flap-img: url(../assets/sanagustin/envelope-flap.webp);
  /* The delivered flap is a triangle cut-out whose apexes sit in the image's own corners, so it
     maps exactly onto the flap box and its deckle edge lands on the clip-path. `cover` cropped
     the corners off and left two edges a few pixels apart. */
  --a-env-flap-fit: 100% 100%;
  /* A flat seamless sheet, not the delivered cut-out: the lining has to fill the flap's whole
     triangle at any crop, and the cut-out's transparent margins left the navy ground showing in a
     band across the top of the V. */
  --a-env-lining-img: url(../assets/sanagustin/envelope-lining-quiet.jpg);
  /* The second sheet is what the flap wanted: a dark indigo ground with a large tone-on-tone
     repeat, so the veil is now a whisper (6→30%) instead of the 50% blackout the bright first
     sheet needed. The motif reads as a lining, not as bunting. */
  --a-env-lining-veil: linear-gradient(0deg, rgba(11, 16, 30, .16), rgba(11, 16, 30, .32) 46%, rgba(11, 16, 30, .48));
  /* photographed inside-back: real paper in shadow, so the throat's own gradient can lift
     right off and let the fibre show. */
  --a-env-inner-img: url(../assets/sanagustin/envelope-inner.jpg);
  --a-env-throat-veil: radial-gradient(150% 100% at 50% -10%, rgba(0, 0, 0, .34), rgba(0, 0, 0, .18) 44%, rgba(0, 0, 0, .06) 76%, transparent 96%);
  /* the closing image: a deckle-edged cut-out, no radius, no straight hairline of light */
  --a-card-paper-img: url(../assets/sanagustin/card-deckle.webp);
  --a-card-paper-bg: transparent;
  --a-card-paper-fit: 100% 100%;
  --a-card-radius: 0;
  --a-card-edge: none;
}
/* The wax is the photograph everywhere, not only in the arrival: beside the photographed seal the
   drawn disc read as a flat maroon ball on the hero plate. Declared at the skin root so both the
   arrival's two halves and the hero's single seal pour from it — same engine-relative path rule as
   the tokens above. */
[data-skin="sanagustin"] { --a-seal-img: url(../assets/sanagustin/seal-wax.webp); }
/* The photographed wax brings its own monogram, its own irregular silhouette and its own contact
   shadow, so it takes over everything the drawn disc drew — in the arrival and on the hero plate
   alike. The accent background-colour comes off (it showed through the photograph's transparent
   corners as a circle), the border-radius comes off (the wax has its own outline), the pressed rim
   comes off, and the box shadows come off: a box shadow is drawn on the element's rectangle, not on
   the wax's outline, and read as a grey blob. Depth is drop-shadow, which follows alpha. */
[data-skin="sanagustin"] .a-seal {
  background-color: transparent;
  border-radius: 0;
  box-shadow: none;
  filter: drop-shadow(0 6px 10px rgba(34, 26, 21, .42));
}
[data-skin="sanagustin"] .a-seal::after { content: none; }
[data-skin="sanagustin"] .a-seal:hover {
  box-shadow: none;
  filter: drop-shadow(0 9px 14px rgba(34, 26, 21, .5));
}
/* The intact photograph still owns the sealed state — monogram debossed into it, no seam — so the
   live-type monogram stays off; it would double the pressed one. The blank reshot pair is only
   ever seen in motion, parting. */
[data-skin="sanagustin"] .a-arrival .a-seal-mono { display: none; }
[data-skin="sanagustin"] .a-arrival .a-seal-half {
  /* the two fragments of one photographed seal, cut apart along its own break */
  box-shadow: none;
  filter: drop-shadow(0 5px 8px rgba(0, 0, 0, .55));
}
[data-skin="sanagustin"] {
  --a-seal-intact-img: url(../assets/sanagustin/seal-wax.webp);
  --a-seal-half-l-img: url(../assets/sanagustin/seal-half-l.webp);
  --a-seal-half-r-img: url(../assets/sanagustin/seal-half-r.webp);
  /* the photograph carries the fracture; the engine's zigzags would cut it again */
  --a-seal-clip-l: none;
  --a-seal-clip-r: none;
  /* the shared export box is 21% wider than the disc is tall; 112% brings the fragments back
     close to the intact photograph's size without trimming their squeezed-out rim */
  --a-seal-half-fit: 112%;
  /* half the gap the seal was photographed with (65px of 1100), so the first frame of the parted
     pair is still one disc */
  --a-seal-mend: 2.95%;
}
[data-skin="sanagustin"] .a-arrival .a-seal:hover .a-seal-half {
  box-shadow: none;
  filter: drop-shadow(0 8px 12px rgba(0, 0, 0, .6));
}

[data-skin="sanagustin"] .env-card-names {
  font: 400 1.9em/1.02 var(--display);
  letter-spacing: -.012em;
}
[data-skin="sanagustin"] .env-card-names i { display: block; font-size: .36em; color: var(--accent); letter-spacing: 0; }
[data-skin="sanagustin"] .env-card-date {
  font: 600 .66em/1 var(--ui); letter-spacing: .3em; text-transform: uppercase; color: var(--accent);
  margin-top: .3em;
}
[data-skin="sanagustin"] .env-card-place {
  font: .6em/1.5 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
[data-skin="sanagustin"] .a-arrival-eyebrow { color: var(--accent-soft); }
[data-skin="sanagustin"] .a-arrival .eyebrow { color: var(--accent-soft); }
[data-skin="sanagustin"] .a-arrival-hint { color: rgba(246, 241, 232, .6); letter-spacing: .1em; }
[data-skin="sanagustin"] .a-arrival-note {
  /* 4.5:1 on #1b1410 at 12px is the floor, and the honesty marker is never the least legible
     thing on the stage — .46 came in with this pass and put it under both. .72 is ~6.4:1, and
     above the hint's .6, which is the ordering the rule actually asks for. */
  color: rgba(246, 241, 232, .72);
  max-width: 30rem;
  margin-inline: auto;
  font-size: 12px;
  letter-spacing: .05em;
}
/* Skip is the way out of a moment, not an action anyone came for: a filled box gave it the
   same weight as the seal. A ruled word keeps the 44px target and none of the emphasis. */
[data-skin="sanagustin"] button.a-arrival-skip {
  /* min-WIDTH as well as min-height: the gate measures both, and a ruled word is narrow. */
  min-width: 56px; min-height: 44px; padding: .35em 1em;
  background: transparent; border: 0;
  border-bottom: 1px solid rgba(246, 241, 232, .28); border-radius: 0;
  font: 600 12px/1 var(--ui); letter-spacing: .18em; text-transform: uppercase;
  color: rgba(246, 241, 232, .66);
  transition: color 240ms var(--ease), border-color 240ms var(--ease);
}
[data-skin="sanagustin"] button.a-arrival-skip:hover {
  background: transparent; color: var(--ivory-lit); border-bottom-color: var(--ivory-lit);
}

/* ---------- the hero plate stops eating the names on a phone (2026-08-18) ----------
   The overlap now lives in a min-width query of its own (section G), so there is nothing to walk
   back here. What is left is the caption's own breathing room at phone widths. */
/* The hero panel's dock reserve is NOT here: it belongs to the base `.hero-over` rule below, at
   every width. Two earlier attempts at it lived in phone-only queries and were both overridden by
   that later rule, so from 48em up the pills sat on the couple's names and on the one action. */

/* ============================================================================
   THE FIRST SCREEN, REBUILT — 2026-08-19 (later, second pass)
   Two user reviews in one day landed here. The first: nothing lined up and the invitation card
   was cut in half by the section seam. The second, on the fix: "it looks cut off … the card
   looks like a cut-out receipt. Not elegant at all." The card is gone. What replaces it is an
   ENGRAVED PANEL — the date lockup, the live counter and the one reply set directly on the
   photograph inside a fine double rule with azulejo corner marks. There is no paper on the
   first screen, so there is nothing to cut out; and because the panel lives INSIDE the frame,
   the whole invitation resolves before the first scroll.

   The four defects and their fixes:
   1 · NOTHING SHARED A LEFT EDGE. `.site-bar` padded from the viewport while the page sat in
       the 68rem wrap — at 1440 the brand stood ~230px outboard of the names. The bar now takes
       the wrap gutter, the same expression `.guest-ribbon` uses.
   2 · THE FAÇADE WAS CROPPED TIGHT. 86vh crops a 16:10 photograph by whatever the window is
       tall. The frame is now ratio-led — min(90svh, 100vw / 1.62) — so the crop is the
       viewport's own shape and the church reads whole on any landscape window. A phone still
       crops hard: the portrait plate is item 1 on the texture prompt sheet.
   3 · THE NAMES SAT TOO LOW. They are a grid cell now, bottom-aligned against the panel rather
       than pushed at the frame's bottom edge, and one step smaller so the pair can share the
       baseline.
   4 · NOTHING RESOLVED. The reply link was below the fold on every window. It is in the panel.
   ============================================================================ */

/* 1 · the bar joins the wrap */
.site-bar { padding-inline: max(var(--gap), calc((100% - var(--wrap)) / 2)); }

/* 2 · the frame. Rewritten 2026-08-20 (later): ONE grid area, and the invitation sits in it.
   The photograph and the words are the same object now, so the overlay is a bottom-aligned grid
   item rather than an absolute box hung off the frame. The consequence is the whole reason for
   it: the frame can never be shorter than the words printed on it — at any width, at any viewport
   height — so the panel cannot be cut off, and cannot push the page into overflow. */
.hero--bleed { padding-block: 0; }
.hero--bleed .hero-figure {
  display: grid;
  grid-template-areas: "stage";
  /* 2026-08-21 · WHY THE INVITATION WAS BELOW THE FOLD AT EVERY DESKTOP SIZE.
     `1fr` carries an auto minimum, so the photograph's own aspect height won over the
     min-height above it — a 1.6:1 plate in a 1920 window is 1200px tall, and the frame grew to
     hold it whatever `92svh` said. The names and the reply block went off the bottom of the
     screen as a direct consequence. minmax(0, 1fr) caps the row; the frame is now exactly ONE
     SCREEN, which is what the bar floating over it is for. */
  /* minmax(min-content, auto), not auto: a single auto row is STRETCHED to the container's
     min-height, and an item taller than that hangs out of the bottom instead of growing it.
     That is how the phone's reply block came to sit in the docks' band. */
  grid-template-rows: minmax(min-content, auto);
  /* ONE SCREEN, or as much more as the invitation needs — never less, never a pixel more than
     its own words. The photograph is taken out of flow (below) so it can no longer vote on the
     frame's height; the only thing that can grow it is the type printed on it. */
  min-height: 100svh;
}
.hero--bleed .hero-figure > picture { grid-area: stage; display: block; position: absolute; inset: 0; }
.hero--bleed .hero-figure img {
  width: 100%;
  height: 100%;
  object-fit: cover; object-position: 50% 16%;
  aspect-ratio: auto; filter: saturate(.9) contrast(1.03);
}
/* The scrim carries the panel now as well as the names, so it is deeper and starts higher: type
   on a photograph is only elegant while it is legible. Still two gradients on the FRAME, never a
   plate on the panel. */
.hero--bleed .hero-figure::after {
  background:
    linear-gradient(102deg, rgba(34, 26, 21, .5) 0%, rgba(34, 26, 21, .16) 44%, transparent 72%),
    linear-gradient(
      rgba(34, 26, 21, 0) 0%,
      rgba(34, 26, 21, .1) 20%,
      rgba(34, 26, 21, .5) 54%,
      rgba(34, 26, 21, .88) 100%
    );
}
@media (max-width: 47.99em) {
  /* A phone gets the portrait plate. The height is the base rule's one screen — a phone had its
     own 168vw expression, which is how the invitation came to be 1.7 screens tall on a 390. */
  .hero--bleed .hero-figure img { object-position: 50% 24%; }
  /* NOTHING about the lockup is trimmed here, deliberately — and nothing may be added here that
     the unconditional .hero-over / .plate-* rules below this block also set. A media query adds no
     specificity, so a later unconditional rule wins: that is exactly how the hero's dock reserve
     came to be phone-only-and-dead once before (CHANGELOG 2026-08-20, item 04). The phone's own
     overrides live in section 13, after those rules. */
  /* The two fixed docks sit on the bottom of the window, so the invitation's last line — which
     is now its ONE action — keeps their height clear of itself. A first screen that ends in a
     link the music control is sitting on top of is not a resolved first screen. */
  .hero--bleed .hero-figure::after {
    background:
      linear-gradient(
        rgba(34, 26, 21, 0) 0%,
        rgba(34, 26, 21, .14) 18%,
        rgba(34, 26, 21, .58) 48%,
        rgba(34, 26, 21, .9) 100%
      );
  }
}

/* 3 · the names, on the photograph's empty left, bottom-aligned inside the frame */
/* 3 · the overlay. One bottom-aligned column on a phone — eyebrow, names, invitation — and two
   columns from 52em with the names left and the invitation right, sharing a baseline. Never
   absolute: the frame above grows to hold this, which is what stops anything being cut off. */
.hero-over {
  position: relative; inset: auto;
  grid-area: stage; align-self: end; z-index: 2;
  display: grid; gap: clamp(1.1rem, 3vw, 1.9rem);
  width: min(100% - 2 * var(--gap), var(--wrap)); margin-inline: auto;
  /* The bottom padding carries the docks' height at EVERY width: the last line of the first
     screen is its one action, and a link the music control is sitting on is not a resolved
     first screen. */
  padding-block: clamp(1.3rem, 3.2vw, 2.6rem) calc(clamp(1.3rem, 3.2vw, 2.6rem) + var(--dock-h-rest));
  color: var(--ivory-lit);
}
/* The top keeps the floating bar's height clear (71px on a phone, 77 from 48em). It only acts where
   the words are taller than the screen and the frame grows to hold them, which is a short phone: on
   an iPhone SE (375 × 559) the eyebrow sat at 19px, under "A & P" (the iPhone stand-in,
   2026-10-01). Where the words fit, the overlay is bottom-aligned and this is unseen.
   ⚠ Two selectors deep ON PURPOSE: the short-window block and section 13's phone block both reset
   `padding-block` on `.hero-over` later in this file, and every top they set is under the bar. */
.hero--bleed .hero-over { padding-top: 5.75rem; }
.hero-over > * { max-width: none; }
.hero-say { align-self: end; display: grid; }
.hero-say > * { max-width: 30rem; }
[data-skin="sanagustin"] .hero-over .hero-names {
  font-size: clamp(2.2rem, .9rem + 5.6vw, 5.6rem); line-height: .94; letter-spacing: -.02em;
}

/* 4 · the invitation.
   REDESIGNED AGAIN 2026-08-20 (later, second review): it is not a panel laid on the hero, it is
   part of the hero. One treatment at every width instead of two — ivory type printed on the
   photograph, carried by the frame's own scrim and by the lockup's hairline rules, exactly the
   way every interlude on this site sets type. What changed, in order of what it is worth:
     · it lives INSIDE the frame at every width, so a phone gets the same invitation a desktop
       does rather than a paragraph of ivory type on paper underneath the picture;
     · the date lockup is the largest thing in it — the numeral at --step-4, the month
       letterspaced above it in blush, the rules in warm white;
     · the counter is one hairline row of four rather than a 2 × 2 block, which is a footer to the
       lockup instead of a second scoreboard competing with it;
     · no box: no border, no radius, no tinted plate, no backdrop-filter. The only ground it has
       is the wash, and the wash has no edge.
   The contents are the ones the panel has always carried — nothing added, nothing cut. */
.hero-panel {
  position: relative; margin: 0; padding: 0;
  display: grid; max-width: 30rem;
  text-shadow: 0 1px 16px rgba(20, 15, 11, .55), 0 1px 3px rgba(20, 15, 11, .45);
}
.plate-month {
  margin: 0 0 .5rem;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .42em; text-transform: uppercase;
  color: var(--blush);
}
.plate-when {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .8rem;
  margin: 0; padding-block: .34rem;
  border-block: var(--rule-w) solid rgba(255, 252, 246, .38);
}
.plate-when span {
  font: 600 var(--step--1)/1.2 var(--ui); letter-spacing: .2em; text-transform: uppercase;
  color: rgba(255, 252, 246, .92);
}
.plate-when span:last-child { text-align: right; }
[data-skin="sanagustin"] .plate-when b {
  font: 400 var(--step-4)/1 var(--display); letter-spacing: -.03em;
  font-variant-numeric: lining-nums;
  padding-inline: .12em; color: var(--ivory-lit);
}
[data-skin="sanagustin"] .hero-panel .plate-time {
  margin: .8rem 0 0; font-size: var(--step-1); font-style: italic;
  color: rgba(255, 252, 246, .95);
}
[data-skin="sanagustin"] .hero-panel .meta {
  margin-top: .3rem; font-size: var(--step--1); letter-spacing: .2em; text-transform: uppercase;
  color: rgba(255, 252, 246, .88);
}

/* 4b · the counter as the lockup's footer: one hairline row of four at every width. Four cells
   fit a 30rem column at 390px without a label being abbreviated, and the rules ARE the design —
   the same vocabulary as the essentials rail below the fold. */
.plate-count { margin-top: 1.05rem; }
.plate-count-cap {
  margin: 0 0 .5rem;
  font: 600 var(--step--1)/1.4 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: var(--blush);
}
.count--panel { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }
.count--panel .count-cell {
  min-width: 0; text-align: left; padding: .45rem .5rem .1rem 0;
  border-top: var(--rule-w) solid rgba(255, 252, 246, .34);
}
.count--panel .count-cell + .count-cell {
  padding-inline: .55rem 0; border-left: var(--rule-w) solid rgba(255, 252, 246, .24);
}
[data-skin="sanagustin"] .count--panel .count-num,
[data-skin="sanagustin"] .count--panel .a-odo {
  font: 400 var(--step-2)/1 var(--display); color: var(--ivory-lit);
}
[data-skin="sanagustin"] .count--panel .count-label {
  display: block; margin-top: .3rem;
  font-size: 11px; letter-spacing: .11em; color: rgba(255, 252, 246, .88);
}

/* 4c · the panel's one action. Ivory small caps on a hairline put the site's only real ask at
   exactly the weight of the four counter labels above it, and `nowrap` ran it off the panel's
   right edge. It takes the filled burgundy block this skin reserves for the RSVP — the single
   saturated thing on the whole first screen, which is the entire point of it. */
.hero-panel .plate-act {
  margin: 1.15rem 0 0;
  padding-top: 1.1rem;
  border-top: var(--rule-w) solid rgba(255, 252, 246, .34);
}
/* 2026-08-28 · the user asked for this to read as glass, like the panel it sits in. The
   saturation the filled block carried is kept as a TINT rather than a fill — burgundy at 74%,
   a specular down the top third, a rim light and a hairline — so it is still the one warm thing
   on the first screen without being an opaque slab.
   backdrop-filter is permitted HERE and nowhere else on the arrival: this button is a deliberate
   rectangle with its own edge, so a blur clipped to its box draws nothing the design was not
   already drawing. Section 13's refusal is about .hero-panel, which has no box on purpose, and
   stands. Browsers without blur keep the heavier tint, so the ivory never loses its contrast. */
[data-skin="sanagustin"] .hero-panel a.btn--rule {
  display: flex; justify-content: space-between; align-items: center; gap: 1em;
  min-height: 56px; padding: .8em 1.05em;
  background-color: color-mix(in srgb, var(--burgundy) 74%, transparent);
  background-image: linear-gradient(158deg, rgba(255, 255, 255, .3) 0, rgba(255, 255, 255, .09) 32%, rgba(255, 255, 255, 0) 60%);
  border: 0; border-radius: 0;
  color: var(--ivory-lit);
  letter-spacing: .13em; white-space: normal; text-wrap: balance;
  text-shadow: 0 1px 10px rgba(20, 15, 11, .5);
  box-shadow:
    inset 0 1px 0 rgba(255, 252, 246, .34),
    inset 0 0 0 1px rgba(255, 252, 246, .18),
    inset 0 -14px 24px -22px rgba(12, 8, 6, .55),
    0 14px 26px -16px rgba(20, 15, 11, .85);
  transition: background-color 240ms var(--ease), box-shadow 240ms var(--ease), transform 160ms var(--ease);
}
@supports (backdrop-filter: blur(6px)) {
  [data-skin="sanagustin"] .hero-panel a.btn--rule {
    background-color: color-mix(in srgb, var(--burgundy) 52%, transparent);
    backdrop-filter: blur(13px) saturate(1.3);
    -webkit-backdrop-filter: blur(13px) saturate(1.3);
  }
  [data-skin="sanagustin"] .hero-panel a.btn--rule:hover {
    background-color: color-mix(in srgb, var(--burgundy-lit) 66%, transparent);
  }
}
[data-skin="sanagustin"] .hero-panel a.btn--rule:hover {
  background-color: color-mix(in srgb, var(--burgundy-lit) 86%, transparent);
  color: var(--ivory-lit); gap: 1em;
  box-shadow:
    inset 0 1px 0 rgba(255, 252, 246, .44),
    inset 0 0 0 1px rgba(255, 252, 246, .26),
    inset 0 -14px 24px -22px rgba(12, 8, 6, .5),
    0 18px 30px -16px rgba(20, 15, 11, .95);
}
[data-skin="sanagustin"] .hero-panel a.btn--rule:active { transform: translateY(1px); }
[data-skin="sanagustin"] .hero-panel .btn-arrow {
  font-size: 1.25em; transition: transform 240ms var(--ease);
}
[data-skin="sanagustin"] .hero-panel a.btn--rule:hover .btn-arrow { transform: translateX(.24em); }

@media (min-width: 52em) {
  /* two columns: the names take the empty left of the frame, the invitation the right. 52em, not
     62em — the names are capped at 30rem now, so the pair fits a tablet, and a short landscape
     window needs the two of them side by side to stay inside the frame. */
  .hero-over {
    grid-template-columns: minmax(0, 1fr) minmax(16.5rem, 23rem);
    align-items: end; gap: clamp(1.5rem, 3.4vw, 3.4rem);
  }
  .hero-say > * { max-width: 34rem; }
  .hero-panel { max-width: none; margin-left: auto; }
  /* The wash, over the picture's bright right side only — a phone already has the frame's own
     deepened bottom and does not need a second ground. Measured from the plate this replaced:
     ivory type needs about .56 of ink under it on the 90th-percentile crop, so the wash is
     DARKER than the plate was where the type sits and pays for it by having no edge at all — a
     radial that reaches transparent well outside the type, read as a shadow on the photograph
     rather than as a panel on top of it. No backdrop-filter, ever: a blur is clipped to the
     element's box, so it draws the exact rectangle the wash exists to avoid. */
  .hero-panel::before {
    content: "";
    position: absolute;
    inset: -2.2rem -2rem -1.6rem -2.4rem;
    z-index: -1;
    background: radial-gradient(118% 114% at 48% 54%,
      rgba(20, 15, 11, .82) 0 44%, rgba(20, 15, 11, .6) 68%, rgba(20, 15, 11, .26) 86%, transparent 100%);
    pointer-events: none;
  }
}

@media (min-width: 48em) and (max-height: 44em) {
  /* A short landscape window has to hold the photograph, the invitation AND the docks' height in
     about 540px, and a frame sized to the picture cannot. So the picture stops claiming a floor,
     the lockup comes down one step and the panel's rows tighten — the reserve below it is
     untouched, which is the point: the last line of the first screen still lands clear of the
     pills at scroll 0. */
  /* The frame is the PHOTOGRAPH's own aspect height here — `height: 100%` on the picture resolves
     to auto inside a content-sized grid row, so a 1.6:1 plate is 891px tall in a 700px window
     whatever the min-height says. In a short window it takes an explicit height instead and the
     crop gives way, which is the only thing on this screen that can. */
  /* The frame's height and its capped row are the base rule's job since 2026-08-21. All this
     block does now is step the type down so one screen is enough to hold the words. */
  .hero-over {
    gap: clamp(.9rem, 1.8vw, 1.4rem);
    padding-block: clamp(.8rem, 1.8vw, 1.3rem) calc(clamp(.8rem, 1.8vw, 1.3rem) + var(--dock-h-rest));
  }
  [data-skin="sanagustin"] .hero-over .hero-names {
    font-size: clamp(1.9rem, .8rem + 3.4vw, 3.4rem);
  }
}



/* 5 · the ornament and the ambient surfaces — the skin's values for engine section R.
   Ivory ground: the rules are the page's own hairline, the mark is burgundy. Over a photograph
   both go to warm white, and the motes are lit rather than inked. */
[data-skin="sanagustin"] {
  --a-orn-ink: var(--rule);
  --a-orn-mark-ink: var(--accent);
  --a-dust-ink: rgba(255, 244, 224, .82);
  --a-glow-ink: rgba(255, 198, 132, .2);
}
[data-skin="sanagustin"] .foot .a-orn,
[data-skin="sanagustin"] .a-bleed .a-orn,
[data-skin="sanagustin"] .a-ink .a-orn {
  --a-orn-ink: rgba(246, 241, 232, .34);
  --a-orn-mark-ink: var(--band-ink-accent);
}
[data-skin="sanagustin"] .foot .a-orn { margin-block: clamp(1.4rem, 3vw, 2.4rem); }

/* 6 · the music control. Bottom LEFT, because the design-study dock owns bottom right and two
   fixed controls in one corner is a toolbar. Same ink pill as the dock tab so the pair reads as
   one set of demo furniture rather than as a widget the couple bought. */
.music-dock {
  position: fixed; left: 0; bottom: 0; z-index: 88;
  display: flex; align-items: center; gap: .55rem;
  padding: calc(var(--gap) * .5) calc(var(--gap) * .6);
  padding-bottom: calc(var(--gap) * .5 + env(safe-area-inset-bottom, 0px));
  max-width: min(30rem, 64vw);
  pointer-events: none;
}
.music-dock > * { pointer-events: auto; }
.music-btn {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: .5em .9em;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ivory-lit); background: #1c1511;
  border: var(--rule-w) solid rgba(246, 241, 232, .32);
  border-radius: var(--radius);
  box-shadow: 0 10px 24px -18px rgba(0, 0, 0, .9);
  white-space: nowrap; cursor: pointer;
  transition: background-color 200ms var(--ease), border-color 200ms var(--ease), transform 200ms var(--ease);
}
.music-btn:hover { background: #241c16; border-color: rgba(246, 241, 232, .55); transform: translateY(-1px); }
/* Three bars that only move while something is actually playing — the one animation on the page
   that is a status rather than a decoration, so it is exempt from the ornament count. */
.music-wave { display: none; align-items: flex-end; gap: 2px; height: 14px; }
.music-wave i { display: block; width: 2px; height: 40%; background: var(--accent); }
[data-music="on"] .music-wave { display: flex; }
/* The credit used to be bare grey type fixed to the window: a caption belonging to no page,
   crossing whatever scrolled behind it — a photograph one moment, a heading the next — and
   illegible over both. It takes the pair's own ink ground now, and it is only on screen while
   something is actually playing. At rest the foot of the window holds two pills and nothing else. */
.music-title {
  display: none;
  /* One line, so the pair keeps the pill's own height and nothing below the fold has to be
     re-reserved for it. The credit itself is kept short in site.js for the same reason. */
  margin: 0; max-width: 21rem;
  padding: .45em .75em;
  font: var(--step--1)/1.35 var(--ui); color: var(--ivory-lit);
  background: #1c1511;
  border: var(--rule-w) solid rgba(246, 241, 232, .32);
  border-radius: var(--radius);
  box-shadow: 0 10px 24px -18px rgba(0, 0, 0, .9);
  text-wrap: pretty;
}
[data-music="on"] .music-title { display: block; }
@media (max-width: 61.99em) {
  /* At 390 the two docks share one row: the credit would push the pill under the design-study
     tab, and the pill is the only part that has a job. The dock's note carries it instead. */
  [data-music="on"] .music-title { display: none; }
}
@media (max-width: 47.99em) {
  /* Two full-size pills on a 390px row are a bar across the foot of the page. Same words, less
     furniture, so the pair reads as two marks in the corners rather than as a toolbar. */
  .music-dock, .demo-dock { padding-inline: calc(var(--gap) * .42); }
  .music-btn, .dock-tab { font-size: calc(var(--step--1) * .92); letter-spacing: .08em; padding: .5em .7em; }
  .dock-tab { gap: .4rem; }
}
/* Both docks are fixed to the window, so the last thing on the page may not end underneath them:
   the footer reserves their height. Mid-page a fixed control passing over a photograph is what a
   fixed control does; the end of the document is where it becomes a defect. */
.foot { padding-bottom: calc(clamp(2rem, 1rem + 3vw, 3rem) + var(--dock-h)); }
@media (prefers-reduced-motion: no-preference) {
  [data-music="on"] .music-wave i { animation: music-bar 1.6s ease-in-out infinite; }
  .music-wave i:nth-child(2) { animation-delay: -.53s; }
  .music-wave i:nth-child(3) { animation-delay: -1.06s; }
  @keyframes music-bar {
    0%, 100% { height: 30%; }
    50% { height: 100%; }
  }
}
@media print { .music-dock { display: none; } }

/* 7 · the dock's one line of instruction, which the invitation panel used to carry. */
.dock-note--tip { margin-top: .7rem; color: var(--band-ink-accent); }

/* ============================================================================
   THE TEXTURE PASS — 2026-08-19 (later, second pass)
   Rich, engine-wide, all five kinds the user picked: material, spatial, photographic,
   atmospheric, ornament. Everything here is a token or a class the engine's section Q owns —
   the skin decides the values and where the classes land, and nothing depends on an asset that
   has not arrived: the image tokens stay `none` until the plates are delivered, and the
   commented line beside each is the one edit to make when they are.
   ============================================================================ */
[data-skin="sanagustin"] {
  /* material · the ground. The laid rulings are warm, not black, so the ivory stays ivory. */
  /* The CSS rulings were the stand-in for a scan. Now that paper-laid.jpg is here they are turned
     OFF on this skin: 1px lines at a 3px pitch alias into a moiré lattice on top of a real sheet,
     which is how the footer came to look like a table. Later skins without a scan still get them
     from the engine default. */
  --a-fibre: transparent;
  /* Landed 2026-08-19 (night): a scan of warm ivory laid paper, made seamless here by 2 × 2
     mirroring, 900px, 49 KB. The path is ENGINE-relative — atelier.css is the stylesheet that
     consumes this token. */
  --a-paper-img: url("../assets/sanagustin/paper-laid.jpg");
  --a-paper-size: 440px;
  /* Two paths to the same folder, and both are correct: a url() in a custom property resolves
     against the stylesheet that USES it. --a-paper-img and --a-mark-* are consumed by
     atelier.css (../assets/…); --sk-* are consumed by rules IN THIS FILE (../assets/…).
     Getting this backwards 404s silently, and with --a-mark-rule transparent there is not even a
     drawn fallback left to notice. */
  --sk-ink-img: url("../assets/sanagustin/paper-ink.jpg");
  --sk-mark-tl: url("../assets/sanagustin/azulejo-corner-tl.webp");
  --sk-mark-tr: url("../assets/sanagustin/azulejo-corner-tr.webp");
  --sk-mark-bl: url("../assets/sanagustin/azulejo-corner-bl.webp");
  --sk-mark-br: url("../assets/sanagustin/azulejo-corner-br.webp");

  /* ornament · the azulejo corner. Landed 2026-08-19 (night): cobalt on white glaze, real alpha,
     four orientations because a corner ornament is a TURN — CSS cannot flip a background layer, so
     the rotations ship as files. The drawn bracket steps aside: --a-mark-rule is transparent.
     Size is 30px, not the 15px the brackets used — an ornate motif at 15px is mush. */
  --a-mark-rule: transparent;
  --a-mark-tl: url("../assets/sanagustin/azulejo-corner-tl.webp");
  --a-mark-tr: url("../assets/sanagustin/azulejo-corner-tr.webp");
  --a-mark-bl: url("../assets/sanagustin/azulejo-corner-bl.webp");
  --a-mark-br: url("../assets/sanagustin/azulejo-corner-br.webp");
  --a-mark-size: 30px;
  --a-mark-len: 30px;
  --a-mark-inset: 7px;
  --a-mark-opacity: .85;

  /* spatial · seams and shadow */
  --a-seam-rule: var(--rule);
  --a-seam-h: 12px;
  --a-shadow: 0 22px 40px -30px rgba(34, 26, 21, .7), 0 2px 4px -1px rgba(34, 26, 21, .14);
  --a-shadow-deep: 0 34px 54px -32px rgba(34, 26, 21, .78), 0 3px 6px -2px rgba(34, 26, 21, .18);

  /* photographic + atmospheric */
  --a-vig: rgba(34, 26, 21, .38);
  --a-parallax: 11%;
  --a-grain-opacity: .62;
}

/* material · paper edges and letterpress bite on the plates the site already has. A plate is a
   sheet: it catches light at the top, sinks at the bottom, and casts. */
.plate, .confirm, .gcard, .composer {
  box-shadow: 0 1px 0 var(--a-press-high) inset, 0 -1px 0 rgba(34, 26, 21, .05) inset, var(--a-shadow);
}
/* ornament · the azulejo corner marks, on the sheets that carry the site's own facts. Rich
   means every plate, not one specimen — but never on a photograph and never on a form field. */
.plate, .confirm, .yours { position: relative; }
.plate::after, .confirm::after, .yours::after {
  --a-mask: linear-gradient(#000, #000);
  --a-mark-tl: var(--sk-mark-tl); --a-mark-tr: var(--sk-mark-tr);
  --a-mark-bl: var(--sk-mark-bl); --a-mark-br: var(--sk-mark-br);
  content: "";
  position: absolute;
  inset: var(--a-mark-inset);
  pointer-events: none;
  opacity: var(--a-mark-opacity);
  border: 1px solid var(--a-mark-rule);
  background-image:
    var(--a-mark-tl, var(--a-mark-img)), var(--a-mark-tr, var(--a-mark-img)),
    var(--a-mark-bl, var(--a-mark-img)), var(--a-mark-br, var(--a-mark-img));
  background-size: var(--a-mark-size) var(--a-mark-size);
  background-repeat: no-repeat;
  background-position: left top, right top, left bottom, right bottom;
  -webkit-mask-image: var(--a-mask), var(--a-mask), var(--a-mask), var(--a-mask);
  mask-image: var(--a-mask), var(--a-mask), var(--a-mask), var(--a-mask);
  -webkit-mask-size: var(--a-mark-len) var(--a-mark-len);
  mask-size: var(--a-mark-len) var(--a-mark-len);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: left top, right top, left bottom, right bottom;
  mask-position: left top, right top, left bottom, right bottom;
}

/* The azulejo is cobalt on white glaze — a tile, and it belongs on the ivory paper. On a dark
   passage the same 30px mark is four electric-blue brackets with a white halo, louder than the
   heading beside them, in a palette that is ivory / stone / sand / burgundy and nothing else. On
   ink the marks are warmed to stone and dropped to a third of their opacity. Ivory grounds
   (`.confirm`, `.yours`, every plate on --bg-raised) keep the cobalt. */
:is(.a-ink, .band--ink) :is(.plate, .confirm, .yours)::after {
  opacity: .32;
  filter: grayscale(1) sepia(.5) brightness(1.08);
}

/* photographic · the interludes take a vignette, and the hero takes one too. .a-bleed spends
   ::after on its scrim, so the vignette is ::before — which is free on both. */
.a-bleed, .hero-figure { isolation: isolate; }
.a-bleed::before, .hero-figure::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  box-shadow: inset 0 0 var(--a-vig-blur) var(--a-vig);
}
/* atmospheric · the dark passages get the inked sheet itself: ink on paper is never flat. */
.band--ink, .foot {
  background-image: var(--sk-ink-img, none);
  /* SOFT-LIGHT, not screen, and a large repeat: screened at 440px the inked sheet read as a
     rectangular lattice — a table grid, not paper — because the tile's chain lines and its own
     mirror seam both landed inside one viewport. */
  background-size: 620px auto;
  background-blend-mode: soft-light;
}

/* spatial · the seam between the page and the footer, and above every raised band. */
.foot, .band--raised { position: relative; }
.foot::before, .band--raised::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--a-seam-h);
  pointer-events: none;
  background-image: linear-gradient(var(--a-seam-rule) 0 1px, transparent 1px);
  background-repeat: repeat-x;
  background-position: top left;
}
.foot { --a-seam-rule: rgba(255, 252, 246, .22); }


/* ===========================================================================
   R · three placements, 2026-08-19 (fourth pass)
   Not new mechanisms — three existing ones used once more each, deliberately.
   =========================================================================== */

/* The invitation, photographed. The home page spends two full-bleeds already (the façade and
   the doorway), so the printed invitation is NOT a third one: it is a frontispiece under the
   band's own eyebrow, matted like an object rather than bled like a scene. 30rem inside a
   narrow wrap keeps it an object on the page instead of a scene behind it. */
.invite-plate { width: min(100%, 30rem); margin-inline: auto; }
.invite-plate > figcaption { color: var(--muted); text-align: left; }
.invite-plate > figcaption span:last-child { text-align: right; }
@media (max-width: 30em) {
  /* Two captions side by side become two words per line at this width. */
  .invite-plate > figcaption { display: grid; gap: .15rem; }
  .invite-plate > figcaption span:last-child { text-align: left; }
}

/* The engraved head. Q2 existed on exactly one element (the hero panel); the entourage is the
   page that reads most like the printed invitation, so its column heads take the double rule —
   and the 2px ink slab they used to wear comes off, because two rules on one edge fight.
   Ink on ivory, no plate and no corner marks: the marks belong to the plates and the hero. */
.ent-head {
  --a-engrave-rule: var(--stone-deep);
  --a-engrave-inner: var(--rule);
  --a-engrave-plate: none;
  --a-engrave-inset: 4px;
  display: grid;
  gap: .3rem;
  padding: .9rem 1.05rem 1rem;
  background-color: var(--bg-raised);
}
[data-skin="sanagustin"] .ent-head h2 { margin: 0; }

/* The story page's dark passage. The page had none; its closing beat used gallery-05, which the
   gallery underneath also shows — the same frame twice on one page. Chapter six has no
   photograph because it has not happened, so it is set on the inked sheet instead: type where a
   picture cannot be. Light chapters above, light gallery below — no dark passage touches
   another. */
.band--ink.band--breath .lede { color: var(--band-ink-muted); }

/* The footer's one outward line. Quiet: the same size as the design-study mark, above it. */
.foot-features {
  margin-top: 1.7rem;
  color: var(--stone);
  font: var(--step--1)/1.7 var(--ui);
}
[data-skin="sanagustin"] .foot-features a {
  border-bottom: 1px solid rgba(246, 241, 232, .38);
  text-decoration: none;
}
[data-skin="sanagustin"] .foot-features a:hover { border-bottom-color: var(--accent-soft); }


/* ============================================================================
   PRE-SITE-2 PASS (2026-08-20) — the eleven structure-and-design moves.
   Markup lives in the page files; everything visual is here. No engine rules.
   ========================================================================= */

/* ---- 01 · the bar carries the deadline and marks where you are ---- */
.bar-end { display: flex; align-items: center; gap: clamp(.7rem, .4rem + 1vw, 1.2rem); }
.bar-reply { display: grid; gap: .18rem; text-align: right; margin: 0; }
.bar-reply span { font: var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }
.bar-reply b { font: 400 var(--step-0)/1 var(--display); letter-spacing: .06em; color: var(--accent); }
.bar-rsvp {
  /* min-width as well as min-height: the word is short enough to fall under the 44px floor on
     its own, which the acceptance gate caught at every width. */
  display: flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px; padding: .5rem .55rem;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent); text-decoration: none;
  border-bottom: 2px solid var(--accent);
  transition: border-bottom-width .18s ease, color .18s ease;
}
.bar-rsvp:hover { color: var(--ink); border-bottom-color: var(--ink); border-bottom-width: 4px; }
.bar-rsvp[aria-current="page"] { color: var(--ink); border-bottom-color: var(--ink); }
@media (max-width: 61.99em) {
  .nav { margin-left: 0; }
  .bar-end { order: 2; margin-left: auto; }
  .nav-toggle { order: 3; margin-left: clamp(.6rem, .3rem + 1.2vw, 1rem); }
}
@media (max-width: 32em) { .bar-reply { display: none; } }
/* the floating bar re-points --accent to blush; the deadline and the RSVP rule must follow it.
   Over the photograph both go ivory with the same text-shadow the brand and nav carry; once the
   page has scrolled and the bar takes its ivory ground, both return to burgundy. */
.site-head[data-float] .site-bar .bar-rsvp { color: var(--ink); border-bottom-color: var(--accent); }
.site-head[data-float] .site-bar :is(.bar-reply b, .bar-rsvp) { text-shadow: 0 1px 16px rgba(28, 21, 17, .6); }
.site-head[data-float] .site-bar .bar-reply b { color: var(--ivory-lit); }
html[data-scrolled] .site-head[data-float] .site-bar :is(.bar-reply b, .bar-rsvp) { color: var(--accent); text-shadow: none; }
html[data-scrolled] .site-head[data-float] .site-bar .bar-rsvp { border-bottom-color: var(--accent); }

/* ---- 02 · the essentials rail ---- */
.band--flush { padding-block: 0; margin-top: var(--pad-band); }
.essentials {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0;
  border-block: var(--rule-w) solid var(--rule);
}
.essentials > div { padding: clamp(1.1rem, .8rem + 1.4vw, 1.9rem) clamp(1rem, .7rem + 1.4vw, 1.8rem); border-left: var(--rule-w) solid var(--rule); }
.essentials > div:first-child { border-left: 0; padding-left: 0; }
.essentials > div:last-child { padding-right: 0; }
.essentials dt { margin: 0 0 .5rem; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .22em; text-transform: uppercase; color: var(--sand-ink); }
[data-skin="sanagustin"] .essentials dd { margin: 0; font: 400 var(--step-2)/1.15 var(--display); }
.essentials-sub { margin: .45rem 0 0; font: var(--step--1)/1.5 var(--ui); color: var(--muted); }
@media (max-width: 44em) {
  .essentials { grid-template-columns: 1fr; }
  .essentials > div { border-left: 0; border-top: var(--rule-w) solid var(--rule); padding-inline: 0; }
  .essentials > div:first-child { border-top: 0; }
}

/* ---- 03 · retired 2026-08-20: .plate-hint, the demo instruction the invitation panel carried.
   The dock says it now (.dock-note--tip), where the controls it refers to actually are. ---- */

/* ---- 04 · four openings, one per interior page ---- */
.open { max-width: 42rem; }
.open h1 { margin: 0; }
.open-sub { margin: 1.1rem 0 0; max-width: 34em; font-size: var(--step-1); line-height: 1.5; color: var(--muted); }
.open-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* A · Our Story — a drop cap into the first line of prose */
.open--drop .open-drop {
  margin: .4rem 0 0; font-family: var(--display); font-size: var(--step-3);
  line-height: 1.22; letter-spacing: -.005em; text-wrap: pretty;
}
[data-skin="sanagustin"] .open--drop .open-drop::first-letter {
  float: left; font-size: 3.4em; line-height: .78; padding: .06em .1em 0 0; color: var(--accent);
}
.open--drop .open-after { margin: 1.1rem 0 0; max-width: 34em; color: var(--muted); font-size: var(--step-0); line-height: 1.65; }

/* B · The Day — a ruled timetable head */
.open--rule { border-top: 2px solid var(--ink); padding-top: 1.1rem; }
.open--rule .eyebrow { margin-bottom: .8rem; }
.open-times { display: flex; flex-wrap: wrap; gap: 0; margin: 1.6rem 0 0; border-top: var(--rule-w) solid var(--rule); }
.open-times > div { padding: .85rem clamp(1rem, .6rem + 1.6vw, 2rem) .85rem 0; }
.open-times > div + div { border-left: var(--rule-w) solid var(--rule); padding-left: clamp(1rem, .6rem + 1.6vw, 2rem); }
.open-times dt { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--sand-ink); }
[data-skin="sanagustin"] .open-times dd { margin: .35rem 0 0; font: 400 var(--step-2)/1 var(--display); font-variant-numeric: lining-nums; }

/* C · Entourage — a printed programme head */
.open--prog { max-width: 34rem; margin-inline: auto; text-align: center; }
.open-rule { width: 3rem; height: 1px; background: var(--stone-deep); margin: 1.1rem auto 0; }
.open--prog .open-sub { margin-inline: auto; font-size: var(--step-0); }

/* D · RSVP — the same paper plate as the invitation panel */
.open--plate {
  max-width: 38rem; margin-inline: auto; text-align: center;
  padding: clamp(1.75rem, 1.2rem + 3vw, 3.25rem);
  background: var(--bg-raised); border: var(--rule-w) solid var(--rule);
}
.open--plate .open-sub { margin-inline: auto; font-size: var(--step-0); }
.open-by {
  margin: 1.4rem 0 0; padding-top: 1.1rem; border-top: var(--rule-w) solid var(--rule);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .28em; text-transform: uppercase; color: var(--accent);
}

/* E · Questions — quiet, wide measure */
.open--ask h1 { max-width: 14em; }

/* ---- 05 · the attire spread ---- */
.attire { display: grid; gap: clamp(1.5rem, 1rem + 2.5vw, 3rem); }
[data-skin="sanagustin"] .attire-say h2 { font-size: var(--step-3); margin: 0; }
.attire-say .lede { margin-top: .7rem; max-width: 30em; }
.swatches {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  margin: 0; padding: 0; list-style: none;
  border: var(--rule-w) solid var(--rule);
}
.swatch { display: grid; }
.swatch + .swatch { border-left: var(--rule-w) solid var(--rule); }
.swatch i { display: block; height: clamp(3rem, 2rem + 4vw, 5rem); background: var(--sw); }
.swatch span {
  padding: .6rem .4rem; text-align: center;
  font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--sand-ink); border-top: var(--rule-w) solid var(--rule);
}
.swatch--no i { position: relative; background: #fff; }
.swatch--no i::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top right, transparent calc(50% - .75px), var(--accent) 50%, transparent calc(50% + .75px));
}
.swatch--no span { color: var(--accent); }
.attire-shots { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
.attire-shot { margin: 0; }
.attire-shot img { width: 100%; aspect-ratio: 8 / 5; object-fit: cover; }
.attire-shot:first-child img { object-position: 50% 30%; }
.attire-shot figcaption { margin-top: .7rem; font: var(--step--1)/1.6 var(--ui); color: var(--muted); }
.attire-shot figcaption b { display: block; margin-bottom: .2rem; color: var(--ink); font-weight: 600; letter-spacing: .04em; }
.attire-cols { display: grid; gap: var(--gap) clamp(1.5rem, 1rem + 3vw, 4rem); grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.attire-col .list-checks { margin-top: .8rem; }
.attire-warn {
  display: grid; grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: clamp(1.2rem, .8rem + 2vw, 2.5rem); align-items: center;
  padding: clamp(1.1rem, .8rem + 1.6vw, 1.9rem);
  background: var(--accent-soft); border: var(--rule-w) solid var(--rule);
}
.attire-warn-shot { margin: 0; }
.attire-warn-shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
[data-skin="sanagustin"] .attire-warn h3 { font-size: var(--step-2); margin: .3rem 0 .5rem; }
.attire-warn p { max-width: 40em; margin: 0; }
@media (max-width: 40em) { .attire-warn { grid-template-columns: 1fr; } }
@media (max-width: 30em) {
  .swatches { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .swatch:nth-child(n+4) { border-top: var(--rule-w) solid var(--rule); }
  .swatch:nth-child(4) { border-left: 0; }
}

/* ---- 06 · travel and stay ---- */
[data-skin="sanagustin"] .travel-say h2 { font-size: var(--step-3); margin: 0 0 var(--gap); }
.travel { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: var(--rule-w) solid var(--rule); }
.travel-col { padding: clamp(1.1rem, .8rem + 1.4vw, 1.8rem) clamp(1rem, .7rem + 1.4vw, 1.8rem); border-left: var(--rule-w) solid var(--rule); }
.travel-col:first-child { border-left: 0; padding-left: 0; }
.travel-col:last-child { padding-right: 0; }
[data-skin="sanagustin"] .travel-col h3 { font-size: var(--step-2); margin: 0 0 .6rem; }
.travel-col p { color: var(--muted); }
.travel-col .hint { margin-top: .8rem; }
.stay { display: grid; margin: 0; padding: 0; list-style: none; }
.stay li { padding-block: .65rem; border-top: var(--rule-w) solid var(--rule); }
.stay li:first-child { border-top: 0; padding-top: 0; }
[data-skin="sanagustin"] .stay b { font: 400 var(--step-1)/1.25 var(--display); }
.stay span { display: block; margin-top: .15rem; font: var(--step--1)/1.4 var(--ui); color: var(--muted); }
.stay-shot { margin: 0 0 var(--gap); }
.stay-shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.invented {
  margin-top: 1rem; padding-top: .8rem; border-top: var(--rule-w) solid var(--rule);
  font: var(--step--1)/1.6 var(--ui); color: var(--sand-ink);
}
.invented strong { color: var(--accent); font-weight: 600; }
.travel-shot { margin: var(--gap) 0 0; }
.travel-shot img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
@media (max-width: 52em) {
  .travel { grid-template-columns: 1fr; }
  .travel-col { border-left: 0; border-top: var(--rule-w) solid var(--rule); padding-inline: 0; }
  .travel-col:first-child { border-top: 0; }
}

/* ---- 07 · the questions page ---- */
.faq-group + .faq-group { margin-top: clamp(2rem, 1.4rem + 2.5vw, 3.5rem); }
.faq-cap {
  margin: 0 0 .2rem; padding-bottom: .7rem; border-bottom: 2px solid var(--ink);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .22em; text-transform: uppercase; color: var(--ink);
}
.faq-row { padding-block: .9rem; border-bottom: var(--rule-w) solid var(--rule); }
[data-skin="sanagustin"] .faq-row summary {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 44px; cursor: pointer; list-style: none;
  font: 400 var(--step-1)/1.35 var(--display);
}
.faq-row summary::-webkit-details-marker { display: none; }
.faq-row summary::after {
  content: "+"; flex: none; font: 400 var(--step-1)/1 var(--ui); color: var(--accent);
}
.faq-row[open] summary::after { content: "\2013"; }
.faq-row p { margin: .6rem 0 .3rem; max-width: 44em; color: var(--muted); }
[data-skin="sanagustin"] .ask-more { font-size: var(--step-3); margin: .3rem 0 1rem; }

/* ---- 08 · the quieter footer ---- */
.foot-new { display: grid; gap: var(--gap) clamp(2rem, 1rem + 4vw, 5rem); grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); align-items: end; }
[data-skin="sanagustin"] .foot-new h2 { font-size: var(--step-4); line-height: 1.02; margin: .3rem 0 .5rem; }
.foot-tag { margin: 0 0 .4rem; }
@media (max-width: 44em) { .foot-new { grid-template-columns: 1fr; align-items: start; } }

/* ---- 11a · the schedule as a ruled timetable (skin only — same fields) ---- */
.sched-venue {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .3rem 1.5rem;
  align-items: baseline; padding-bottom: .7rem; margin-bottom: 0;
  border-bottom: 2px solid var(--ink);
}
[data-skin="sanagustin"] .sched-venue h2 { grid-row: 1; grid-column: 1; font-size: var(--step-3); margin: 0; }
.sched-venue .meta { grid-row: 1; grid-column: 2; text-align: right; }
.sched-venue p:not(.meta) { grid-column: 1 / -1; color: var(--muted); font-size: var(--step--1); line-height: 1.6; }
/* D1 (2026-10-01): a long label claimed the whole row and crushed the heading's column to 0px at
   360. On phones the row stacks: heading, then the label on its own line. */
@media (max-width: 44em) {
  .sched-venue { grid-template-columns: minmax(0, 1fr); }
  .sched-venue .meta { grid-row: 2; grid-column: 1; text-align: left; }
}
.sched-item { grid-template-columns: 7rem minmax(0, 1fr); padding-block: .85rem; align-items: baseline; }
[data-skin="sanagustin"] .sched-time {
  font: 400 var(--step-1)/1.35 var(--display); letter-spacing: .01em;
  font-variant-numeric: oldstyle-nums lining-nums; color: var(--ink); white-space: nowrap;
}
@media (min-width: 48em) {
  .sched-item { grid-template-columns: 7.5rem minmax(0, 1fr) minmax(0, 1.15fr); gap: .15rem 1.5rem; }
  .sched-note { grid-column: 3; grid-row: 1; margin: 0; }
}
/* the two times a guest must not miss are keyed in burgundy by the data's own note */
.sched-item:nth-child(2) .sched-time, .sched-item:nth-child(2) .sched-title { color: var(--accent); }

/* ---- 11b · the entourage as a printed programme ---- */
.prog { display: grid; gap: clamp(1.6rem, 1.2rem + 2vw, 2.8rem); }
.prog-sec { display: grid; gap: .9rem; }
.prog-cap {
  margin: 0; text-align: center;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .26em; text-transform: uppercase; color: var(--sand-ink);
}
/* Two-up ONLY where two columns actually fit. Fixed 2026-09-02: the pair was hard-coded to two
   columns, and beside a plate that leaves ~209px a column — a leader row whose role and name were
   both `flex: none` then overflowed its track and printed on top of the other column
   ("Elena ReyesParents of the groom"). Two changes, both guards rather than layout opinions:
   auto-fit drops to one column below 17rem of room, and nothing in the row is unshrinkable. */
.prog-pair {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 0 clamp(1.5rem, 1rem + 3vw, 3.5rem);
}
.led { display: flex; align-items: baseline; gap: .5rem; margin: 0; padding-block: .42rem; min-width: 0; }
[data-skin="sanagustin"] .led-role {
  flex: 0 1 auto; flex-shrink: .2; min-width: 0;
  font: 400 var(--step-0)/1.5 var(--display); letter-spacing: .02em;
}
.led-fill { flex: 1 1 1.5rem; min-width: 1rem; border-bottom: 1px dotted var(--stone-deep); transform: translateY(-.3em); }
.led-who { flex: 0 1 auto; min-width: 0; font: var(--step--1)/1.5 var(--ui); color: var(--muted); text-align: right; text-wrap: pretty; }
@media (max-width: 40em) {
  .prog-pair { grid-template-columns: 1fr; }
  .led { display: grid; gap: .1rem; padding-block: .5rem; border-top: var(--rule-w) solid var(--rule); }
  .led-fill { display: none; }
  .led-who { text-align: left; }
}

/* ---- 11c · the entourage's marginal plates -------------------------------
   Added 2026-08-31. The page's middle was five blocks and ~35 names with nothing
   between them — the longest uninterrupted run of type on site 1. Five plates sit
   beside their own block the way a plate sits in the outer margin of a printed
   programme, alternating sides down the page; the sixth frame is a full-bleed band
   between the two sponsor blocks and needs no rule here (it reuses .a-bleed).

   The plate is a MARGIN, not a column: it never competes with the names, and the
   names keep their two-up pairing. Below 60em it stacks under the cap instead —
   deliberately generous, because the .prog-pair is itself two-up until 40em and a
   plate plus two name columns at 834px leaves neither enough room. */
.prog-sec--plate {
  grid-template-columns: minmax(0, 1fr) clamp(9rem, 27%, 13.5rem);
  grid-template-areas: "cap plate" "names plate";
  grid-template-rows: auto 1fr;
  column-gap: clamp(1.4rem, 1rem + 2vw, 2.6rem);
  align-items: start;
}
.prog-sec--plate.prog-sec--flip {
  grid-template-columns: clamp(9rem, 27%, 13.5rem) minmax(0, 1fr);
  grid-template-areas: "plate cap" "plate names";
}
.prog-sec--plate > .prog-cap { grid-area: cap; }
/* A four-name block beside a 4:5 plate left ~200px of void hanging under the names. The plate
   sets the row height, so the names are centred against it instead of pinned to its top edge —
   the leftover space becomes margin above and below rather than a hole (fixed 2026-09-02). */
.prog-sec--plate > .prog-pair { grid-area: names; align-self: center; }
.prog-plate { grid-area: plate; margin: 0; }
.prog-plate img {
  display: block; width: 100%; height: auto;
  aspect-ratio: var(--plate-ar, 4 / 5);
  object-fit: cover; object-position: var(--plate-crop, 50% 50%);
  background: var(--bg-raised);
  border: var(--rule-w) solid var(--rule);
}
.prog-plate .figure-note { margin-top: .45rem; text-align: center; }
@media (max-width: 60em) {
  .prog-sec--plate,
  .prog-sec--plate.prog-sec--flip {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "cap" "plate" "names" "strip";
    grid-template-rows: auto auto auto auto;
  }
  .prog-plate { max-width: 22rem; margin-inline: auto; }
}

/* ---- 11d · the entourage's opening frame ---------------------------------
   Added 2026-09-01 (user's call: "open the page with it"). The whole-party frame moved out
   of the page's middle and became its hero, which means it now carries the h1 rather than an
   h2 — the two had to swap or the sweep's heading check would read a jump. The section-01
   open2 that used to hold the h1 keeps the photograph and takes an h2.
   Nothing here changes .a-bleed itself; it only sets the hero's own type scale and closes
   the gap under the bar so the frame reads as the top of the page. */
.band--hero-bleed { padding-top: 0; }
.band--hero-bleed .a-bleed-over { max-width: min(38rem, 100% - 2 * var(--gap, 1rem)); }
[data-skin="sanagustin"] .band--hero-bleed .a-bleed-over h1 {
  margin: .1rem 0 .5rem;
  font-size: clamp(2.4rem, 1.6rem + 6vw, 5rem);
  line-height: .98;
}
[data-skin="sanagustin"] .band--hero-bleed .a-bleed-over p { margin: 0; text-wrap: pretty; }

/* ---- 11e · the party gallery: one dataset, two views ----------------------
   Added 2026-08-31. Narrow viewports get a filmstrip pinned under each block
   (.pstrip, grid-area "strip"); roomy ones get the whole party as one filterable
   grid in its own band (.party-band) and the strips are dropped. Neither view is a
   subset of the other's content — all 27 people are reachable in both.
   Behaviour lives in engine/party.js; this file only dresses it. */
.pstrip { grid-area: strip; margin-top: .3rem; min-width: 0; }
.pstrip-rail {
  display: flex; gap: .8rem;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding: .2rem .2rem .7rem;
  scrollbar-width: thin;
}
.pstrip .pcard { flex: 0 0 clamp(9rem, 42vw, 11.5rem); scroll-snap-align: start; }
.pstrip .pcard-side { display: none; }

/* ---- 11f · the filmstrip as a component, not a bare scroller -------------
   Added 2026-09-01, on the user's note that the mobile swipe read as too plain. It is
   worth being precise about why this one matters more than it looks: the two party views
   are mutually exclusive (see the 60em seam at the end of this section), so below 60em
   the filmstrip is the ONLY way a visitor meets the twenty-seven people. It was also,
   until today, a rail of <button>s with no click handler at all — inert.

   Three additions, all cheap: a position readout, a progress thumb, and tap-to-open.
   Deliberately NO edge-fade gradient: a translucent overlay across a half-visible name is
   exactly the shape `r.veiled` exists to catch, and the readout does the same job honestly. */
.pstrip-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: .6rem;
  margin-bottom: .5rem;
}
.pstrip-count,
.pstrip-hint {
  margin: 0;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted);
}
.pstrip-count { font-variant-numeric: lining-nums tabular-nums; }
.pstrip-count:empty { display: none; }
.pstrip-hint { display: flex; align-items: center; gap: .4rem; opacity: .72; }
.pstrip-hint::after {
  content: ""; width: 1.15rem; height: var(--rule-w); background: currentColor;
  animation: pstrip-nudge 2.6s ease-in-out infinite;
}
@keyframes pstrip-nudge {
  0%, 100% { transform: translateX(0); opacity: .35; }
  50%      { transform: translateX(.4rem); opacity: 1; }
}
/* Nothing to swipe, or already at the end: the invitation stops being true. */
.pstrip[data-at="all"] .pstrip-hint,
.pstrip[data-at="end"] .pstrip-hint { opacity: 0; }

.pstrip-bar { position: relative; height: var(--rule-w); margin-top: .7rem; background: var(--rule); }
.pstrip-bar > span {
  position: absolute; top: 0; bottom: 0;
  left: var(--pstrip-l, 0%); width: var(--pstrip-w, 100%);
  background: var(--accent);
}
.pstrip[data-at="all"] .pstrip-bar { display: none; }

/* Tap-to-open, mirroring the grid: name and role always, the sentence on demand. At 42vw a
   card cannot hold a sentence and stay legible, so the open card takes the width it needs.
   The image column keeps the CLOSED card's width, so its height never changes and opening a
   card does not shove the rest of the page down — the reveal costs width, never height. */
.pstrip .pcard { transition: flex-basis .3s ease; }
.pstrip .pcard[data-open] {
  flex-basis: clamp(15rem, 88vw, 20rem);
  grid-template-columns: clamp(9rem, 42vw, 11.5rem) minmax(0, 1fr);
  align-items: start; gap: .85rem;
}
.pstrip .pcard-line {
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .3s ease, opacity .3s ease;
}
.pstrip .pcard[data-open] .pcard-line { max-height: 12em; opacity: 1; }
.pstrip .pcard[data-open] .pcard-shot img { object-position: 50% 34%; }
/* The custom bar IS the scroll indicator now. Leaving the native one on gave every strip two
   of them, 15px apart, and cost 10px of dead gutter inside the rail. */
.pstrip-rail { scrollbar-width: none; }
.pstrip-rail::-webkit-scrollbar { display: none; }
.pstrip .pcard-name { display: flex; align-items: baseline; gap: .4rem; }
.pstrip .pcard-name::after {
  content: "+"; margin-left: auto;
  font: 400 var(--step--1)/1 var(--ui); color: var(--muted); opacity: .6;
}
.pstrip .pcard[data-open] .pcard-name::after { content: "\2212"; opacity: .9; }
@media (prefers-reduced-motion: reduce) {
  .pstrip-hint::after { animation: none; }
  .pstrip .pcard,
  .pstrip .pcard-line { transition: none; }
}

.pchips { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: var(--gap); }
.pchip {
  display: flex; align-items: center; gap: .45rem;
  min-height: 2.75rem; padding: .5rem .85rem;
  background: transparent; border: var(--rule-w) solid var(--rule); color: var(--muted);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  cursor: pointer;
}
.pchip i { font-style: normal; opacity: .55; font-variant-numeric: lining-nums tabular-nums; }
.pchip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--ivory-lit); }
.pchips-said { margin-top: .8rem; }
.pchips-said:empty { display: none; }

.pgrid {
  display: grid; gap: clamp(1rem, .8rem + 1vw, 1.7rem);
  grid-template-columns: repeat(auto-fill, minmax(11.5rem, 1fr));
  margin-top: 1.1rem;
}
.pcard {
  display: grid; gap: .5rem; align-content: start; text-align: left;
  margin: 0; padding: 0; background: none; border: 0; color: inherit; font: inherit; cursor: pointer;
}
.pcard-shot { display: block; }
.pcard-shot img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 4 / 5; object-fit: cover; object-position: 50% 42%;
  background: var(--bg-raised); border: var(--rule-w) solid var(--rule);
}
.pcard-say { display: grid; gap: .18rem; min-width: 0; }
[data-skin="sanagustin"] .pcard-name { font: 400 var(--step-0)/1.25 var(--display); }
[data-skin="sanagustin"] .pcard-role {
  font: 400 var(--step--1)/1.35 var(--display); letter-spacing: .03em; color: var(--sand-ink);
}
.pcard-line { font: var(--step--1)/1.5 var(--ui); color: var(--muted); text-wrap: pretty; }
.pcard-side {
  font: 600 var(--step--2, var(--step--1))/1.3 var(--ui); letter-spacing: .18em;
  text-transform: uppercase; color: var(--muted); opacity: .75; margin-top: .25rem;
}
.pcard-line:empty, .pcard-side:empty { display: none; }
.pcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* In the grid the line is a peek, not a wall of copy: 27 cards each carrying a sentence
   reads as a directory. Hover, focus or hold a card open to get it. */
.pgrid .pcard-line {
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .28s ease, opacity .28s ease;
}
.pgrid .pcard:hover .pcard-line,
.pgrid .pcard:focus-visible .pcard-line,
.pgrid .pcard[data-open] .pcard-line { max-height: 8em; opacity: 1; }
.pgrid .pcard-side { display: none; }
.pgrid .pcard[data-open] {
  grid-column: span 2;
  grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
  align-items: start; gap: .95rem;
}
.pgrid .pcard[data-open] .pcard-side { display: block; }
@media (prefers-reduced-motion: reduce) {
  .pgrid .pcard-line { transition: none; }
}

/* The two views never both appear. 60em is the same seam the plates stack on. */
@media (min-width: 60.01em) { .pstrip { display: none; } }
@media (max-width: 60em) { .party-band { display: none; } }

/* ---- 11g · softened photo edges -----------------------------------------
   Added 2026-09-02 on the user's note. §17.3 already dissolves the crop line on the site's
   feathered photographs, but the plates and the party cards are FRAMED — they carry a hairline
   rule, and a mask leaves a hard border floating over a soft picture. They get a radius instead:
   the rule and the photograph round together because both live on the <img>.
   Tune or revert with the one token; 0 restores the hard corner. */
[data-skin="sanagustin"] :is(.prog-plate img, .pcard-shot img) {
  border-radius: var(--photo-r, 9px);
}

/* ---- 11h · the filmstrip as cinematic marquee cards ---------------------
   Added 2026-09-02 on the user's nine answers: film strip + slow pan, depth · focus pull ·
   reveal-on-centre, NO tap-to-open (centring alone reveals), 4:5, one-and-a-half cards,
   counter only, this page only, loudness 10 of 10.

   How it is switched, because the user asked for a way back:
   · REVERT  — delete data-wsd-marquee from the five .pstrip divs in entourage/index.html.
               Everything below dies with it, engine/party.js falls back to tap-to-open, and
               §11f's plain strip returns. One attribute per strip, no CSS edit.
   · DOWNSCALE — put data-marquee="soft" on <html>. Every amplitude below is --mq multiplied,
               so one token halves the whole pass. --mq: 0 leaves the film base and the
               sprockets with no motion at all.
   The engine draws none of this: it writes --pc (signed) and --pcd (distance) per card and
   data-centre on the middle one. Everything visible here is skin. */
[data-skin="sanagustin"] .pstrip[data-wsd-marquee] {
  --mq: 1;
  /* One and a half cards in view. The upper bound is generous on purpose: the rail pads itself
     by (50% - card / 2) so the FIRST card can reach the middle — a narrow card at 834px would
     leave a third of the strip as dark leader before anything arrives. */
  --mq-cw: clamp(12rem, 58vw, 20rem);
  --mq-perf: .62rem;                        /* the sprocket band, top and bottom */
  --mq-scale: calc(.15 * var(--mq));        /* depth */
  --mq-blur: calc(3.4px * var(--mq));       /* focus pull */
  --mq-dim: calc(.42 * var(--mq));
  /* The slow pan inside the frame. Halved to 5.5% on the user's note (2026-09-02) — the pan is
     scroll-linked, so its SPEED is only its amplitude: less travel per swipe is a slower drift. */
  --mq-pan: calc(5.5% * var(--mq));
  --mq-base: #17110d;
  --mq-lit: #f6f1e8;
}
html[data-marquee="soft"] .pstrip[data-wsd-marquee] { --mq: .45; }

/* Counter only: the readout stays on the paper above the film, the bar and the hint come off. */
.pstrip[data-wsd-marquee] .pstrip-bar,
.pstrip[data-wsd-marquee] .pstrip-hint { display: none; }
.pstrip[data-wsd-marquee] .pstrip-head { margin-bottom: .55rem; }

/* The film base. `background-attachment: local` is the whole trick: the perforations belong to
   the scrollable content, so the sprocket edge travels under the thumb instead of sitting still
   behind it. The two rows live in the rail's own vertical padding, so no perforation ever
   crosses a card, a name or a numeral. */
.pstrip[data-wsd-marquee] .pstrip-rail {
  gap: .45rem;
  padding: var(--mq-perf) max(.4rem, calc(50% - var(--mq-cw) / 2));
  scroll-snap-type: x mandatory;
  counter-reset: mqframe;
  background-color: var(--mq-base);
  background-image:
    repeating-linear-gradient(90deg, transparent 0 7px, rgba(246, 241, 232, .82) 7px 16px, transparent 16px 25px),
    repeating-linear-gradient(90deg, transparent 0 7px, rgba(246, 241, 232, .82) 7px 16px, transparent 16px 25px);
  background-size: 25px calc(var(--mq-perf) - .18rem);
  background-position: 0 .09rem, 0 calc(100% - .09rem);
  background-repeat: repeat-x;
  background-attachment: local;
}

.pstrip[data-wsd-marquee] .pcard {
  flex: 0 0 var(--mq-cw);
  scroll-snap-align: center;
  gap: 0;
  /* Scroll-linked, so deliberately NOT transitioned — a transition on a per-frame value
     trails the thumb by a third of a second and reads as lag, not as motion. */
  transition: none;
  transform: scale(calc(1 - var(--mq-scale) * var(--pcd, 1)));
  transform-origin: 50% 46%;
  counter-increment: mqframe;
}
.pstrip[data-wsd-marquee] .pcard-shot { position: relative; }
.pstrip[data-wsd-marquee] .pcard-shot img {
  border-radius: 0;
  border-color: transparent;
  object-position: calc(50% + var(--pc, 0) * var(--mq-pan)) 42%;
  filter:
    blur(calc(var(--pcd, 1) * var(--mq-blur)))
    brightness(calc(1 - var(--pcd, 1) * var(--mq-dim)))
    saturate(calc(1 - var(--pcd, 1) * .4));
}
/* The frame numeral, in the corner of the frame the way a contact sheet prints it. Type on a
   photograph gets the same shadow the bleed credits use — never a plate. */
.pstrip[data-wsd-marquee] .pcard-shot::after {
  content: counter(mqframe, decimal-leading-zero);
  position: absolute; left: .4rem; top: .3rem;
  font: 600 var(--step--2, var(--step--1))/1 var(--ui);
  letter-spacing: .14em; font-variant-numeric: lining-nums tabular-nums;
  color: rgba(246, 241, 232, .9);
  text-shadow: 0 1px 3px rgba(15, 11, 8, .85);
  opacity: calc(1 - var(--pcd, 1) * .55);
}

/* The caption block reserves its full height at every state, so revealing a sentence costs
   nothing vertically — the rail's height must not change as you swipe. */
.pstrip[data-wsd-marquee] .pcard-say {
  min-height: 6.4rem; align-content: start; gap: .12rem;
  padding: .5rem .6rem .2rem;
}
[data-skin="sanagustin"] .pstrip[data-wsd-marquee] .pcard-name {
  display: block; color: var(--mq-lit);
  opacity: calc(.62 + (1 - var(--pcd, 1)) * .38);
  transform: translateY(calc(var(--pcd, 1) * .3rem));
}
.pstrip[data-wsd-marquee] .pcard-name::after { content: none; }
[data-skin="sanagustin"] .pstrip[data-wsd-marquee] .pcard-role {
  color: rgba(246, 241, 232, .74);
  opacity: calc(1 - var(--pcd, 1));
  transform: translateY(calc(var(--pcd, 1) * .5rem));
}
.pstrip[data-wsd-marquee] .pcard-line {
  color: rgba(246, 241, 232, .68);
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .34s var(--ease), opacity .34s var(--ease);
}
.pstrip[data-wsd-marquee] .pcard[data-centre] .pcard-line { max-height: 4.8em; opacity: 1; }
.pstrip[data-wsd-marquee] .pcard:focus-visible { outline-offset: -3px; }

@media (prefers-reduced-motion: reduce) {
  [data-skin="sanagustin"] .pstrip[data-wsd-marquee] { --mq: .3; }
  .pstrip[data-wsd-marquee] .pcard-line { transition: none; }
}

/* ---- 10 · the short path on the RSVP ---- */
.rsvp-simple {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--gap);
  align-items: center; margin-bottom: var(--gap);
  padding: clamp(1.1rem, .8rem + 1.6vw, 1.9rem);
  background: var(--accent-soft); border: var(--rule-w) solid var(--rule);
}
[data-skin="sanagustin"] .rsvp-simple-q { margin: .35rem 0 .5rem; font: 400 var(--step-2)/1.2 var(--display); }
.rsvp-simple .hint { max-width: 34em; }
.btn--big { min-height: 56px; padding-inline: clamp(1.2rem, .8rem + 2vw, 2.2rem); font-size: var(--step-0); white-space: nowrap; }
.rsvp-or {
  display: flex; align-items: center; gap: 1rem; margin: 0 0 var(--gap);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .2em; text-transform: uppercase; color: var(--sand-ink);
}
.rsvp-or::before, .rsvp-or::after { content: ""; flex: 1; height: 1px; background: var(--rule); }
@media (max-width: 44em) {
  .rsvp-simple { grid-template-columns: 1fr; }
  .rsvp-simple .btn--big { width: 100%; }
}

/* ---- 12 · the motion register (2026-08-20, later) ----
   The skin's amplitudes for engine sections C, P and S. Nothing here is a new mechanism and
   nothing here is continuous — section R's budget (three ornament rules, two ambient surfaces
   per page) is untouched by any of it. Raised on review: 26px of travel is the amount a page
   moves when it settles, not when something arrives, and this site is meant to feel handled.
   Sites 2–8 inherit the utilities and set their own numbers; the spare counterweight should
   probably halve these. */
[data-skin="sanagustin"] {
  --a-lift: 40px;          /* .a-reveal, was 26px */
  --a-rise-y: 58px;        /* .a-rise — the loud one, for a section's own subject */
  --a-cue-y: 34px;         /* the hero's load-time sequence */
  --a-cue-step: 180ms;
  --a-settle-from: 1.12;   /* the photograph arrives out of a deeper over-scale */
  --a-mask-from: 1.08;
}
/* A wipe reads on a short line and turns into a curtain on a long one, so it is spent on the
   eyebrows and the ornament rules only — never on a paragraph. */

/* ============================================================================
   13 · THE FITTING PASS — 2026-08-21
   The user sent eight screenshots, desktop (1920) and phone, with one sentence: the layouts do
   not fit properly. They did not. Four findings and one preference, in the order they were
   agreed:
     A · THE SITE IS A PAGE, NOT A SCREEN. The measure was 68rem whatever the window, so at 1920
         the design was a 1088px column with 400px of dead ground either side of it. The whole
         site is now capped at --page and centred, and the window outside it is a darker desk.
     B · THE INVITATION FITS ONE SCREEN. See the frame's own comment above: `1fr` let the
         photograph's aspect height decide the frame, and the names fell off the bottom.
     C · THE HEADS GO TWO-UP. Three interior pages opened with a full screen of type on paper and
         then a photograph starting exactly at the fold, which read as a sliced image. Head left,
         its photograph right, bleeding to the page's edge; stacked on a phone.
     D · THE DOCKS REST AS MARKS. Two ink pills fixed over the last band of every page, and the
         left one was sitting on the Entourage caption — the screenshot reads "ILIPINIANA". They
         collapse to a mark at rest and open on hover or focus.
     E · THE PHONE PASS. Air around the invitation, the three times in one row, a smaller drop
         cap, the credits back, less band padding, a tidier menu.
   Site 1 only, by decision: engine.css and atelier.css are untouched, so sites 2–8 inherit
   nothing from here until one of them asks for it.
   ============================================================================ */

/* ---- A · the sheet ------------------------------------------------------- */
/* The three top-level blocks are capped as one column. .foot already paints itself; the header
   and main take the sheet's own ivory so the desk outside can be darker than the paper. */
[data-skin="sanagustin"] .site-head,
[data-skin="sanagustin"] #main,
[data-skin="sanagustin"] .foot {
  width: min(100%, var(--page));
  margin-inline: auto;
}
@media (min-width: 100rem) {
  /* The desk. background-COLOR only: the laid ground and its grain live on <body> in
     atelier.css and multiply over whatever colour is underneath, so one property darkens the
     surround and leaves the paper alone. (The same reason the file's opening comment gives for
     never using the `background` shorthand here.) */
  [data-skin="sanagustin"] body { background-color: #ded4c2; }
  [data-skin="sanagustin"] .site-head,
  [data-skin="sanagustin"] #main { background-color: var(--bg); }
  /* Two hairlines, no shadow: a shadow on three stacked blocks draws a line at each seam. */
  [data-skin="sanagustin"] #main,
  [data-skin="sanagustin"] .foot {
    border-inline: var(--rule-w) solid rgba(34, 26, 21, .16);
  }
}
/* Everything fixed to the window is anchored to the SHEET's edges instead. Percentages, not vw:
   a fixed element resolves them against the initial containing block, which excludes the
   scrollbar — 100vw does not, and the pair would sit 15px outboard of the page they belong to. */
[data-skin="sanagustin"] .site-head[data-float] .site-bar {
  inset-inline: max(0px, calc((100% - var(--page)) / 2));
}
.music-dock { left: max(0px, calc((100% - var(--page)) / 2)); }
.demo-dock { right: max(0px, calc((100% - var(--page)) / 2)); }

/* ---- D · the two docks, 2026-08-21 (later) -------------------------------- */
/* The pair are not the same kind of thing, so they no longer behave the same way. The music is a
   MARK — one note, one click, play or pause, quiet enough to ignore. The design-study tab keeps
   its WORDS at every width: it is the one piece of furniture on this site that a visitor is meant
   to read, so hiding it behind a hover was the wrong economy. Both sit a little back from the
   page and come fully forward on hover or focus. */
.music-btn, .dock-tab {
  overflow: hidden;
  transition:
    background-color 200ms var(--ease), border-color 200ms var(--ease),
    transform 200ms var(--ease), opacity 200ms var(--ease);
}
.dock-label { white-space: nowrap; }
/* The music control has no label element of its own — its text is written straight onto the button
   by music.js — so the words are folded by type size and the note carries the meaning. The
   accessible name is untouched: the text is still in the DOM, and still what a screen reader
   reads. */
.music-btn {
  font-size: 0; letter-spacing: 0;
  min-width: 44px; padding: 0; justify-content: center;
  opacity: .72;
}
.music-btn::before {
  content: "\266A";
  font: 400 1.15rem/1 var(--display);
  color: var(--band-ink-accent);
  transform: translateY(-.02em);
}
/* Three bars beside the note while something is actually playing — the state is shown, not
   swapped into the glyph, so the mark never changes shape under the cursor. */
[data-music="on"] .music-btn { opacity: .9; }
.dock-tab[aria-expanded="false"] { opacity: .82; }
.music-btn:hover, .music-btn:focus-visible,
.dock-tab:hover, .dock-tab:focus-visible,
.dock-tab[aria-expanded="true"] { opacity: 1; }
/* The credit is a caption on a control that is only ever a mark, so it waits for the pointer. */
.music-dock:not(:hover):not(:focus-within):not(:has(:hover)) .music-title { display: none; }

/* ---- C · the two-up head ------------------------------------------------- */
/* One section holds both halves, so the pair can share a row. The photograph keeps its own
   frame, its own scrim and its own caption — it is the same interlude, moved. */
.band--open2 { padding-block: 0; }
.open2-say {
  width: min(100% - 2 * var(--gap), 46rem); margin-inline: auto;
  padding-block: var(--pad-band) calc(var(--pad-band) * .58);
}
.open2-shot .a-bleed { --a-bleed-h: min(62svh, 30rem); }
@media (min-width: 62em) {
  .open2 {
    display: grid;
    /* column 1 is the wrap's own left gutter, so the head starts on the page's left edge like
       every other band; column 3 runs to the sheet's right edge, so the photograph still
       bleeds — just sideways instead of across. */
    grid-template-columns:
      max(var(--gap), calc((100% - var(--wrap)) / 2))
      minmax(0, calc(var(--wrap) * .5))
      minmax(0, 1fr);
    align-items: stretch;
  }
  .open2-say {
    grid-column: 2; width: auto; margin: 0; align-self: center;
    padding-block: var(--pad-band); padding-right: clamp(1.5rem, 3vw, 3rem);
  }
  .open2-shot { grid-column: 3; }
  .open2-shot .a-bleed { --a-bleed-h: min(74svh, 42rem); height: 100%; }
  /* Two headings in one row need a hierarchy: the page's own h1 is the loud one, so the type on
     the photograph comes down two steps and stops competing with it. */
  [data-skin="sanagustin"] .open2-shot .a-bleed-over h2 { font-size: var(--step-2); }
  .open2-shot .a-bleed-over { max-width: 26rem; }
  /* Entourage's programme head is centred on its own; in a column beside a picture it is not. */
  .open2-say .open--prog { margin-inline: 0; text-align: left; }
  .open2-say .open--prog .open-rule { margin-inline: 0; }
  .open2-say .open--prog .open-sub { margin-inline: 0; }
}

/* ---- E · the phone pass -------------------------------------------------- */
@media (max-width: 47.99em) {
  /* Bands were a desktop measure of air on a 390px screen — and then, 2026-09-03, a 390px screen
     turned out to have no air left for anything else to live in. The float layer places a piece
     only where NO text column crosses its vertical span, so the only quiet height on a phone is
     a band's own padding; at 2.1–3.2rem a junction between two bands ran ~67px and a piece needs
     its own height plus its parallax travel either side. The census measured the consequence:
     one to three pieces of twelve, and the Details page placed NONE at 390.
     So this is now deliberately more generous than the desktop-relative reduction it replaces —
     the user asked for the narrow layouts to be given air (2026-09-03) and this is where the air
     goes. It is paired with a hard cut to the layer's narrow-width motion budget in § 19.2, and
     the two only work together: air with the desktop parallax still on it buys nothing.
     ⚠️ RE-RUN THE CENSUS IF YOU CHANGE THIS NUMBER. It is the phone's whole ornament budget. */
  [data-skin="sanagustin"] { --pad-band: clamp(3.5rem, 2.6rem + 3vw, 5rem); }

  /* The invitation had its type on the frame's own edge. The bottom reserve is what keeps the
     reply block off the two resting dock marks, and the clearance it buys is INDEPENDENT of the
     window's height: the overlay is bottom-aligned in a frame that is at least one screen, so
     reply-to-dock = reserve (76px) − the mark's own height and padding (~56px) ≈ 20px at every
     phone height. Measured: 22px at 390 × 844, 22px at 360 × 740. Both docks are anchored to the
     window, so this is the only place on the site where that sum has to be done by hand. */
  .hero-over {
    width: min(100% - 2.6 * var(--gap), var(--wrap));
    gap: 1rem;
    padding-block: 1rem calc(1.15rem + var(--dock-h-rest));
  }

  /* Three times, one row. The flex row wrapped, so "Between them" dropped into a box of its
     own under the other two and read as a fourth thing. */
  .open-times { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .open-times > div { padding: .7rem .5rem .7rem 0; }
  .open-times > div + div { padding-left: .5rem; }
  .open-times dt { font-size: calc(var(--step--1) * .84); letter-spacing: .09em; }
  [data-skin="sanagustin"] .open-times dd { font-size: var(--step-1); }

  /* A 3.4em drop cap on a 34em measure is a hole in the paragraph: four lines indented around
     one letter. Smaller cap, smaller opening line, and the wrap closes up. */
  .open--drop .open-drop { font-size: var(--step-2); line-height: 1.3; }
  [data-skin="sanagustin"] .open--drop .open-drop::first-letter { font-size: 2.7em; line-height: .82; }

  /* The bar and the menu. */
  .site-bar { gap: calc(var(--gap) * .7); padding-block: .8rem; }
  .nav[data-open] ul { gap: 0; }
  .nav[data-open] a { padding-block: 1rem; }
  .nav[data-open] li:last-child a { border-bottom: 0; }
}


/* ============================================================================
   14 · THE VISUAL PASS — 2026-08-21 (fourth pass)
   Features were signed off, so this pass is layout, type, copy and motion only. The findings and
   the reasoning are in `tools/Visual Pass.html`; what follows is the eleven that were built.
   Move 01 (the names' connector) was DROPPED: the review claimed it set at 13px in the UI sans
   and it does not — measured at the user's own 1622px it is 26.88px Cormorant Garamond italic in
   blush, which is already the move. Nothing to do, so nothing was done.

     06 · two voices, not three — the editorial smalls leave system-ui for Garamond small caps
     03 · the home page gains a mid-scale head; h2.eyebrow was doing a section head's work
     04 · the essentials rail: uneven columns, and the one line with a deadline set large
     02 · "Everything else" becomes a ruled index with one plate leading
     12 · the footer closes on the date
     09 · the story's chapter photographs in the margin, level with their headings
     10 · the page transition travels sideways, so a click reads as turning a page
     05 · 11 · a photograph on Questions, and a curtain on the three pages that had none

   Site 1 only. engine.css and atelier.css are untouched, so sites 2–8 inherit nothing until one
   of them asks for it. Section R's ornament budget is spent and no rule here adds to it.
   ============================================================================ */

/* ---- 06 · the editorial smalls -------------------------------------------
   The site paid 101 KB for four Garamond faces WITH `smcp` (see assets/type/README.md — the
   standard subsets ship without it and these were rebuilt to keep it). It was being spent on one
   element. Everything EDITORIAL that was set in system-ui now sets in Garamond small caps: the
   site goes from three families to two and the letterpress reads as letterpress.

   What stays in the sans, deliberately, because it is the distinction that makes this a system
   rather than a swap: genuine UI. The nav, the buttons, `.link`, form labels and hints, the two
   docks, the demo controls, the filters and choices, the bar's reply block, the lightbox. A
   visitor OPERATES those; they are not part of the invitation. */
[data-skin="sanagustin"] .eyebrow {
  font: 400 var(--step-1)/1.15 var(--display);
  font-variant-caps: all-small-caps;
  letter-spacing: .13em;
  text-transform: none;
}
[data-skin="sanagustin"] .meta {
  font: 400 var(--step-0)/1.5 var(--display);
  font-variant-caps: all-small-caps;
  letter-spacing: .085em;
}
/* Three places added `text-transform: uppercase` to .meta when it was a sans caps label. Caps
   plus all-small-caps is just caps again, so the small caps would never show. */
[data-skin="sanagustin"] .hero-plate .meta,
[data-skin="sanagustin"] .venue .meta,
[data-skin="sanagustin"] .sched-venue .meta,
[data-skin="sanagustin"] .party-head .meta { text-transform: none; }
[data-skin="sanagustin"] :is(.essentials dt, .count-label, .plate-count-cap, .faq-cap,
  .chapter-date, .bleed-credit, .figure-note, .guest-role, .stat span, .prog-cap, .rsvp-or) {
  font-family: var(--display);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  text-transform: none;
  letter-spacing: .11em;
}
/* Small caps sit at cap height, so a face set for 14px sans reads two sizes smaller in Garamond.
   Everything that was at --step--1 comes up one step — INCLUDING the two group heads, which the
   first draft of this rule left behind on the theory that they carried their own size. They do,
   and their own size is --step--1: chosen for 600-weight system-ui caps, so in Garamond small caps
   they landed at ~9px of visible cap height and were the smallest type on the two pages that use
   them. They take --step-1, which is the eyebrow they sit among. */
[data-skin="sanagustin"] :is(.essentials dt, .count-label, .plate-count-cap, .chapter-date,
  .bleed-credit, .figure-note, .guest-role, .stat span, .rsvp-or) { font-size: var(--step-0); }
[data-skin="sanagustin"] .faq-cap,
[data-skin="sanagustin"] .prog-cap { font-size: var(--step-1); }

/* ---- 03 · the home page's middle voice ------------------------------------
   Both home-page section heads were `h2.eyebrow` — the eyebrow WAS the head, so the page's type
   ran 14 → 17 → 83px with nothing between, while every interior page had a real Garamond head in
   the same slot. Same two-part lockup as the interior pages now: eyebrow, then head. */
.sec-head-block { display: grid; gap: .15rem; margin-bottom: var(--gap); }
.sec-head-block .eyebrow { margin-bottom: 0; }
[data-skin="sanagustin"] .sec-head { font-size: var(--step-2); line-height: 1.06; margin: 0; max-width: 24ch; }

/* ---- 04 · the essentials rail is a plate, not a table --------------------
   Three equal columns of hairline-ruled data directly under a full-screen photograph. The three
   facts are right; their weighting was not. "Reply by" is the only line on the page with a
   deadline in it, so it is the one set large, and the columns stop pretending the three are the
   same size of thing. No height gained, no content added. */
@media (min-width: 62em) {
  .essentials { grid-template-columns: minmax(0, 1.25fr) minmax(0, .95fr) minmax(0, 1.3fr); }
}
.essentials-big { display: flex; flex-wrap: wrap; align-items: baseline; gap: .35em; }
[data-skin="sanagustin"] .essentials-big b {
  font: 400 var(--step-3)/.95 var(--display); letter-spacing: -.005em;
  font-variant-numeric: lining-nums;
}
[data-skin="sanagustin"] .essentials-big span {
  font: 400 var(--step-1)/1 var(--display); color: var(--muted);
  font-variant-numeric: oldstyle-nums;
}

/* ---- 02 · the ruled index ------------------------------------------------
   Three equal cards with an image, a heading, a one-line blurb and a ruled link is the shape
   every wedding template ships, and it sat two screens above the footer where a visitor decides
   whether this was designed or configured. The three destinations are not equal — a five-chapter
   read, a list, a reference — so the index says so: one plate leading, the other two as ruled
   rows. Same three links, same three photographs, same section. */
.idx { display: grid; }
.idx-row {
  display: grid; gap: var(--gap);
  padding-block: clamp(1.4rem, 1rem + 2vw, 2.6rem);
  border-top: var(--rule-w) solid var(--rule);
}
.idx-row:first-child { border-top: 0; padding-top: 0; }
.idx-say { display: grid; gap: .45rem; align-content: end; }
[data-skin="sanagustin"] .idx-no {
  margin: 0; font: 400 var(--step-0)/1 var(--display);
  font-variant-caps: all-small-caps; font-variant-numeric: lining-nums;
  letter-spacing: .2em; color: var(--sand-ink);   /* text at body size: --stone-deep was 2.4:1 (C1) */
}
[data-skin="sanagustin"] .idx-say h3 { font-size: var(--step-2); line-height: 1.04; margin: 0; }
.idx-say p { margin: 0; color: var(--muted); max-width: 34em; }
.idx-say .link { justify-self: start; margin-top: .35rem; }
.idx-shot { margin: 0; overflow: clip; }
.idx-shot img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
@media (min-width: 52em) {
  .idx-row {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: clamp(1.5rem, 3vw, 3.5rem);
    align-items: end;
  }
  /* The leading row is the only one that gets a tall plate, and the only one whose head is
     allowed to be bigger than the section's own. */
  .idx-row--lead .idx-shot img { aspect-ratio: 4 / 5; }
  [data-skin="sanagustin"] .idx-row--lead .idx-say h3 { font-size: var(--step-3); }
}

/* ---- 12 · the footer closes on the date ---------------------------------
   The oversized date is the site's best typographic object and it was spent once, on the first
   screen. Every page then ended on the same five things — couple, date, venues, hashtag,
   disclosure — with the couple's names as the third-largest type on a page that opened at 110px.
   The date is now the footer's own object, in the same `13 · 02 · 2027` setting the envelope
   card uses, and the rest goes quiet beneath it. A device reused, not a device invented. */
.foot-date { display: grid; gap: .45rem; }
[data-skin="sanagustin"] .foot-day {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3em;
  margin: .1rem 0 .2rem;
  font: 400 clamp(2.6rem, 1.3rem + 4.4vw, 5.2rem)/.92 var(--display);
  color: var(--ivory-lit); letter-spacing: .005em;
  font-variant-numeric: lining-nums tabular-nums;
}
.foot-day i { font-style: normal; font-size: .42em; color: var(--stone-deep); transform: translateY(-.25em); }
.foot-who { display: grid; gap: .35rem; align-content: end; }
[data-skin="sanagustin"] .foot-new h2 { font-size: var(--step-2); line-height: 1.06; margin: 0; }

/* ---- 09 · the chapter photograph in the margin -------------------------
   At 62em and up each chapter's photograph sits in the margin column, its frame level with the
   top of the date line's letters (the user, 2026-10-01), and scrolls with its chapter. The chapter
   numeral sits on the heading's baseline (from 48em, where the two first stand side by side). It was held (`position: sticky`) from 2026-08-21 until the user
   saw it slide down past its own text and out of line with it (2026-10-01): removed. */
@media (min-width: 62em) {
  /* the prose row (1fr) takes any height the photograph adds; were the head row to grow, the
     baseline-aligned numeral and head would slide down it, off the photograph's top line */
  .chapter { grid-template-columns: 5.5rem minmax(0, 1fr) minmax(14rem, 19rem); grid-template-rows: auto 1fr auto; }
  [data-skin="sanagustin"] .chapter .chapter-body { grid-column: 2; }
  /* Higher specificity than section G's `.chapter > div:not(.chapter-head)`, which would
     otherwise pull the shots back into the prose column. */
  /* Rows 1–2 only (the head and the prose): when the photograph is the taller, the prose row grows
     and the blurb's rule starts a full gap below the frame, never a few px off its edge. */
  [data-skin="sanagustin"] .chapter .chapter-shots {
    grid-column: 3; grid-row: 1 / span 2;
    align-self: start;
    /* the frame's top edge on the top of the date's letters, not its line box: the date is
       small capitals with old-style figures at --step-0, line-height 1, and its ink starts .33em
       below the box (measured in pixels at 1024, 1280 and 1607, within 0.5px; 2026-10-01) */
    margin-top: calc(var(--step-0) * .33);
  }
  /* The blurb gives up the margin column it used to hold and sits under its own prose. */
  [data-skin="sanagustin"] .chapter-blurb { grid-column: 2; grid-row: auto; align-self: start; }
}
@media (min-width: 48em) {
  /* last baseline: the numeral shares the heading's baseline (the head's last line is its h2), not
     the date's. It was top-aligned, which put it between the two and on neither (2026-10-01). */
  [data-skin="sanagustin"] .chapter .chapter-no { grid-column: 1; grid-row: 1; }
  [data-skin="sanagustin"] .chapter .chapter-no,
  [data-skin="sanagustin"] .chapter .chapter-head { align-self: last baseline; }
}

/* ---- 10 · the page turn -------------------------------------------------
   The departure and the arrival were the same 260ms fade-and-rise in both directions, so a click
   told you nothing except that something had happened. They now travel SIDEWAYS: the page leaves
   to the left and the next one arrives from the right, which is what turning a page does.

   Per-TARGET direction (leftwards to go deeper, rightwards to come back to the invitation) needs
   the clicked link's href, and that is `engine/motion.js` — frozen. So this is the consistent
   version, which is skin-only and costs four rules. Reduced motion is untouched: motion.js
   navigates immediately and neither keyframe is reached.

   `overflow-x: clip` on the body, not `hidden`: clip does not create a scroll container, so
   anything sticky on a page still sticks to the viewport.

   And on the ROOT as well, found by the gate 2026-08-22. `body`'s clip contains the travel
   visually, but with `html` at `overflow-x: visible` the root's scrollWidth still grew by the
   26px from-state, so every page but the invitation (whose arrival sets its own root overflow)
   carried ~11px of scroll range for the length of a page turn. `clip` here for the same reason it
   is clip below: `hidden` on the root would make the root a scroll container and the sticky
   photographs would stop sticking. */
html[data-skin="sanagustin"],
[data-skin="sanagustin"] body { overflow-x: clip; }
@media (prefers-reduced-motion: no-preference) {
  [data-skin="sanagustin"][data-atelier~="motion"] #main {
    animation: sa-page-in 440ms var(--ease) both;
  }
  html[data-leaving][data-skin="sanagustin"] #main {
    animation: sa-page-out 260ms var(--ease) both;
  }
}
@keyframes sa-page-in { from { opacity: 0; transform: translateX(26px); } to { opacity: 1; transform: none; } }
@keyframes sa-page-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(-22px); } }

/* Since 2026-08-24 the turn has a SECOND direction. motion.js reads the target's place in the
   site's own chrome order and hands the page a direction on the way out (data-leaving="back")
   and on the way in (data-arriving="back", carried across the navigation in sessionStorage).
   Going back up the nav, the page turns the other way. Forward is the default and is the rule
   above untouched, so a fresh load never swaps animation-name mid-flight. */
@media (prefers-reduced-motion: no-preference) {
  html[data-arriving="back"][data-skin="sanagustin"] #main { animation-name: sa-page-in-back; }
  html[data-leaving="back"][data-skin="sanagustin"] #main { animation-name: sa-page-out-back; }
}
@keyframes sa-page-in-back { from { opacity: 0; transform: translateX(-26px); } to { opacity: 1; transform: none; } }
@keyframes sa-page-out-back { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(22px); } }

/* ============================================================================
   15 · THE HERO'S LONG ZOOM OUT — 2026-08-22
   Asked for: "a zoom out on the hero photo". There already was one — section C's `.a-settle`,
   1.12 → 1 in 2400ms — and the point of the answer is that at that length nobody reads it as a
   move; it is over before the names have finished arriving. The user's picks: an even, continuous
   pull back, 18% of travel over 14 seconds, the slow breathe still taking over afterwards, and
   the lockup riding the zoom rather than landing before it.

   TOKENS ONLY. No new keyframe, no new class, no engine edit: section C already reads
   `--a-settle-from`, `--a-settle-dur` and `--a-ease-out` off the element it animates, and its
   combined `.a-settle.a-breathe` rule already delays the breathe by `--a-settle-dur`, so the
   30s breathe now starts at 14s on its own. Because they are set on the `<img>`, the phone's
   portrait `<source>` is the same element and gets the same move — the one scope asked for.

   `--a-ease-out: linear` is scoped to the IMG, never to the figure: the figure's descendants
   include the three `.a-cue` items, and a linear rise on type reads as a slide, not an entrance.

   ⤺ TO REVERT: delete this section. Section 12's `--a-settle-from: 1.12` is still there and the
     engine's own 2400ms default comes back with it. Nothing else here has a second reader.
   ⤺ TO TONE DOWN: the three numbers below, in order of effect —
       --a-settle-from  1.18 → 1.14   (less travel; 1.12 is the old value)
       --a-settle-dur   14s   → 8s    (still continuous, less ambient)
       --a-ease-out     linear → var(--a-ease-out) removed, i.e. back to the settle curve
     and for the lockup, drop the `--a-cue-*` trio to return to 480/1100 → 220/180ms.
   ============================================================================ */
.hero--bleed .hero-figure > picture > img {
  --a-settle-from: 1.14;   /* 14% of travel. 1.18 held the façade's edges at 1440 but cropped it
                              hard on the 390 portrait plate, where the panel is the first screen's
                              only action; 1.14 is the same move with the crop intact. */
  --a-settle-dur: 14000ms; /* also the breathe's start delay, via section C's combined rule */
  --a-ease-out: linear;    /* "even, cinematic" — a settle curve spends 12 of the 14s barely moving */
}
/* The lockup rides the opening instead of preceding it: eyebrow at .48s, names at 1.58s, plate at
   2.68s, the last of them landing ~4.4s into a 14s move. Still finished long before the photograph
   is, so nothing is waiting on nothing. */
.hero--bleed .hero-figure {
  --a-cue-in: 480ms;
  --a-cue-step: 1100ms;
  --a-cue-dur: 1700ms;
}


/* ============================================================================
   16 · THE ARRIVAL, SECOND REBUILD — 2026-08-22
   Scoped from the user's reference stills and written brief; the eight answers are in
   `DECISIONS.md` → *The arrival, second rebuild*. What changed and why:

     · The wax breaks into FOUR photographed fragments instead of two halves.
     · The ground is the damask photograph instead of the azulejo tile at 240px, with a real
       vignette over it.
     · The flap's lining is warm ivory lit from the fold instead of ink-blue azulejo.
     · The whole sequence runs ~6.6s press-to-page instead of ~4.5s.
     · Reduced motion sees the envelope crossfade open instead of never seeing it.

   TWO OVERRIDES OF THE USER'S OWN ANSWERS, both stated to them:
     1. They said keep the azulejo ground. The damask they generated is used instead — it is the
        single biggest step toward the reference and it was already paid for. It is NOT tiled: the
        delivered file is not seamless (mean edge delta 18/17, a visible line at any repeat), so it
        is laid `cover` the way the reference frames it.
     2. The fragments do NOT reassemble into the sealed disc. They were asked for in four
        registered canvases and came back four times centred in their own box — and frag 1 and 2
        are each a HALF carrying the full ring set, so no arrangement of the four is one disc. The
        engine's existing answer to exactly this problem is used instead: the intact disc lies over
        them and crossfades out 140ms AFTER they are already falling, so their silhouettes never
        have to agree. Same standing rule as the 2026-08-19 pair.

   REVERTING: `_archive/sanagustin-arrival-v2-fallback/` holds all five touched files. Its README
   says how. To tone this section down instead, in order of effect:
     · too slow → drop the three --a-arr-beat-* numbers back to 620 / 1720 / 920.
     · fragments too busy → set `--sa-frag-3-o: 0; --sa-frag-4-o: 0` and it is a two-piece break.
     · ground too present → --a-arr-tile-opacity .92 → .5, or drop --a-arr-tile for the old tile.
   ============================================================================ */

/* the beats. Bare numbers, read once by engine/atelier.js off the stage; the engine's own
   defaults (620 / 1720 / 920) are untouched, so no other skin moves. The clock:
   0 crack · 1500 flap · 2900 card · card lands ~4800 · 1100 hold · 780 leave ≈ 6.6s. */
[data-skin="sanagustin"] {
  --a-arr-beat-open: 1500;
  --a-arr-beat-rise: 2900;
  --a-arr-beat-hold: 1100;
  --a-arr-still-hold: 1600;
  /* per-fragment opacity, so the break can be dialled back to two pieces without touching
     markup or keyframes */
  --sa-frag-3-o: 1;
  --sa-frag-4-o: 1;
}

/* ---- the ground: one damask photograph, laid cover, with the vignette over it ---- */
[data-skin="sanagustin"] .a-arrival {
  --a-arr-tile: url(../assets/sanagustin/arrival-damask.jpg);
  --a-arr-tile-size: cover;
  --a-arr-tile-opacity: .92;
}
/* The tile layer at .92 all but covers the engine's two radial gradients, so the falloff is
   redrawn here as an inset shadow on the same pseudo-element. Two stops: a tight one that darkens
   the frame edge, a wide one that pulls the whole surround down toward the envelope. The element
   is inset -12%, so the first stop's darkest part sits off-screen and only its shoulder shows. */
[data-skin="sanagustin"] .a-arrival::before {
  background-repeat: no-repeat;
  background-position: center;
  box-shadow:
    inset 0 0 14vmax 7vmax rgba(12, 8, 6, .92),
    inset 0 0 44vmax 20vmax rgba(12, 8, 6, .58);
}

/* ---- the paper: warm lining, lit card, the re-pressed seal ---- */
[data-skin="sanagustin"] .a-arrival {
  /* The lining is the same ivory as the envelope now, not the azulejo blue. The flap's inner face
     is drawn rotateX(180deg), so the photograph's lit bottom edge renders at the thrown-back
     triangle's apex — which is the end standing furthest from the table and nearest the light.
     Reading the flip as a mistake and "correcting" it would put the glow in the fold. */
  --a-env-lining: #e6d9c0;
  --a-env-lining-img: url(../assets/sanagustin/envelope-lining-warm.jpg);
  /* A whisper, not the .16→.48 the dark indigo sheet needed: this sheet is already in shadow at
     the fold and already lit at the apex, so the veil only has to seat it under the outer face. */
  --a-env-lining-veil: linear-gradient(0deg, rgba(28, 18, 12, .30), rgba(28, 18, 12, .16) 52%, rgba(28, 18, 12, .05));
  --a-card-paper-img: url(../assets/sanagustin/card-deckle-lit.webp);
}
/* Both the arrival's sealed disc and the hero plate's seal move to the re-pressed photograph: the
   monogram is legible at the size the plate uses it, which the first press was not. */
[data-skin="sanagustin"] { --a-seal-img: url(../assets/sanagustin/seal-wax-v2.webp); }
[data-skin="sanagustin"] { --a-seal-intact-img: url(../assets/sanagustin/seal-wax-v2.webp); }

/* ---- the four fragments ----------------------------------------------------
   Each delivered cut-out is a shard sitting near the middle of its own 700px canvas, so
   `contain` renders it at very close to its true share of the disc (frag 1's alpha box is 593px
   of 700, frag 3's is 418) — the relative sizes came out right even though the registration did
   not. What is authored here is only where each piece STARTS inside the disc's footprint and
   where it goes. */
[data-skin="sanagustin"] .a-arrival .a-seal-half {
  inset: 0;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  border-radius: 0;
  box-shadow: none;
  /* the fragment's own cast shadow, following the fracture instead of a box */
  filter: drop-shadow(0 7px 10px rgba(0, 0, 0, .58));
  opacity: 0;
}
/* The engine holds a translate on --l/--r in the sealed state to keep its two-piece crack shut.
   There is no crack to keep shut here, and a live translate would compose with the keyframes. */
[data-skin="sanagustin"] .a-env[data-stage="sealed"] .a-seal-half { translate: none; }
[data-skin="sanagustin"] .a-seal-half--l { background-image: url(../assets/sanagustin/seal-frag-1.webp); }
[data-skin="sanagustin"] .a-seal-half--r { background-image: url(../assets/sanagustin/seal-frag-2.webp); }
[data-skin="sanagustin"] .a-seal-half--c { background-image: url(../assets/sanagustin/seal-frag-3.webp); }
[data-skin="sanagustin"] .a-seal-half--d { background-image: url(../assets/sanagustin/seal-frag-4.webp); }

/* The intact disc holds 140ms into the fall before it goes. That overlap is the whole trick: the
   pieces are already moving and already rotating by the time anything of them is visible, so the
   eye reads "it broke" and never gets a still frame of four shards to compare against the disc. */
[data-skin="sanagustin"] .a-arrival .a-seal::before {
  transition: opacity 300ms var(--ease, ease) 140ms, scale 260ms var(--ease, ease), filter 260ms var(--ease, ease);
}
/* The seal is the one control on this screen and its hover was doing almost nothing: the engine
   flexes .a-seal-half, which under this skin is transparent until the wax breaks. Flex the disc. */
[data-skin="sanagustin"] .a-env[data-stage="sealed"] .a-seal:hover::before {
  scale: 1.035;
  filter: drop-shadow(0 10px 15px rgba(0, 0, 0, .62)) brightness(1.04);
}

/* the fall. One :not() pair rather than the engine's three-state list — cracked, opening and
   rising all want the same thing, and still-open must not get it. */
[data-skin="sanagustin"] .a-env:not([data-stage="sealed"]):not([data-stage="still-open"]) .a-seal-half--l {
  animation: sa-frag-1 1700ms cubic-bezier(.33, .05, .71, .69) both;
}
[data-skin="sanagustin"] .a-env:not([data-stage="sealed"]):not([data-stage="still-open"]) .a-seal-half--r {
  animation: sa-frag-2 1700ms cubic-bezier(.33, .05, .71, .69) both 40ms;
}
[data-skin="sanagustin"] .a-env:not([data-stage="sealed"]):not([data-stage="still-open"]) .a-seal-half--c {
  animation: sa-frag-3 1620ms cubic-bezier(.36, .04, .74, .68) both 110ms;
}
[data-skin="sanagustin"] .a-env:not([data-stage="sealed"]):not([data-stage="still-open"]) .a-seal-half--d {
  animation: sa-frag-4 1660ms cubic-bezier(.36, .04, .74, .68) both 170ms;
}
/* Each piece recoils a little outward and UP as the crack goes through it, then gravity takes it.
   Nothing eases out: wax does not decelerate on the way down. The two big pieces tip away from
   each other; the two small ones spin faster, because a smaller mass does. */
@keyframes sa-frag-1 {
  0%   { transform: translate(-13%, -4%) rotate(-6deg) scale(.80); opacity: 1; }
  9%   { transform: translate(-19%, -10%) rotate(-14deg) scale(.80); opacity: 1; }
  76%  { opacity: 1; }
  100% { transform: translate(-48%, 208%) rotate(-66deg) scale(.80); opacity: 0; }
}
@keyframes sa-frag-2 {
  0%   { transform: translate(14%, -2%) rotate(8deg) scale(.78); opacity: 1; }
  9%   { transform: translate(21%, -9%) rotate(18deg) scale(.78); opacity: 1; }
  76%  { opacity: 1; }
  100% { transform: translate(54%, 214%) rotate(76deg) scale(.78); opacity: 0; }
}
@keyframes sa-frag-3 {
  0%   { transform: translate(-9%, 19%) rotate(-14deg) scale(.52); opacity: var(--sa-frag-3-o, 1); }
  11%  { transform: translate(-15%, 11%) rotate(-28deg) scale(.52); opacity: var(--sa-frag-3-o, 1); }
  74%  { opacity: var(--sa-frag-3-o, 1); }
  100% { transform: translate(-32%, 232%) rotate(-124deg) scale(.52); opacity: 0; }
}
@keyframes sa-frag-4 {
  0%   { transform: translate(11%, 20%) rotate(16deg) scale(.56); opacity: var(--sa-frag-4-o, 1); }
  11%  { transform: translate(18%, 12%) rotate(31deg) scale(.56); opacity: var(--sa-frag-4-o, 1); }
  74%  { opacity: var(--sa-frag-4-o, 1); }
  100% { transform: translate(38%, 238%) rotate(134deg) scale(.56); opacity: 0; }
}

/* ---- the retime ------------------------------------------------------------
   The three beat NUMBERS above move when each animation starts; these move how long each one
   takes, so the sequence stretches instead of gaining dead air between beats. Durations only —
   every easing curve is the engine's, and every one of them was already tuned.
   The flap's swing is a TOKEN now, not an animation-duration override: the engine reads the same
   number for the swing and for both faces' relight, so retiming here can no longer leave the
   lining's reveal behind (it had drifted to 29% of the swing). */
[data-skin="sanagustin"] .a-arrival { --a-flap-dur: 1560ms; }
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-card,
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-card::before { animation-duration: 1900ms; }
[data-skin="sanagustin"] .a-env[data-stage="opening"] .a-env-shade,
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-shade { animation-duration: 1120ms; }
/* The envelope's own fade begins 420ms into the card's rise, which was right when the rise was
   1320ms and is far too early at 1900: the envelope had gone before the card was out of it. */
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-back,
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-front { animation: a-env-away 1180ms cubic-bezier(.4, 0, .3, 1) both 760ms; }
[data-skin="sanagustin"] .a-env[data-stage="rising"]::after { animation-delay: 760ms; animation-duration: 900ms; }
/* The flap's two faces carry TWO animations at this stage — the relight, then the fade — so a
   single value here would have set the relight to 900ms and put it straight back out of sync with
   the swing. One value per slot. */
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-flap::before,
[data-skin="sanagustin"] .a-env[data-stage="rising"] .a-env-flap::after {
  animation-delay: 0s, 760ms;
  animation-duration: var(--a-flap-dur, 1560ms), 900ms;
}

/* ---- reduced motion: a still, not a skip ----------------------------------
   Opt-in per site via data-arrival-still on .a-arrival; engine/atelier.js drives it. Nothing here
   travels, nothing scales, nothing rotates — it is two opacity crossfades. The flap and the wax
   fade away, the card is already in its lifted position and fades up over them. z-index and
   transform are set in BOTH states so neither can jump when the stage changes. */
[data-skin="sanagustin"] .a-arrival[data-still] .a-arrival-hint,
[data-skin="sanagustin"] .a-arrival[data-still] .a-arrival-eyebrow { opacity: 0; }
[data-skin="sanagustin"] .a-arrival[data-still] .a-env-card {
  z-index: 6;
  transform: translateY(-15%);
  opacity: 0;
  transition: opacity 880ms var(--ease, ease);
}
[data-skin="sanagustin"] .a-arrival[data-still] .a-env[data-stage="still-open"] .a-env-card { opacity: 1; }
[data-skin="sanagustin"] .a-arrival[data-still] .a-seal,
[data-skin="sanagustin"] .a-arrival[data-still] .a-env-flap,
[data-skin="sanagustin"] .a-arrival[data-still] .a-env-shade { transition: opacity 720ms var(--ease, ease); }
[data-skin="sanagustin"] .a-arrival[data-still] .a-env[data-stage="still-open"] .a-seal,
[data-skin="sanagustin"] .a-arrival[data-still] .a-env[data-stage="still-open"] .a-env-flap { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  /* The leave animation lives inside the engine's no-preference query, so without this the whole
     stage was removed on a frame. A fade is not motion. */
  [data-skin="sanagustin"] .a-arrival[data-leaving] { opacity: 0; transition: opacity 520ms var(--ease, ease); }
}

/* ============================================================================
   U · THE CLIP ARRIVAL — site 1 wears it, 2026-08-26
   ============================================================================
   The arrival is now a photographed clip paused on a measured frame, not the drawn envelope.
   The drawn one is not deleted: `engine/atelier.css` still carries it, section T still carries
   the hinge primitive, and the `.a-env-*` tokens above still resolve — they are simply no longer
   reached from this page's markup. `_archive/sanagustin-arrival-drawn/` holds the markup block.

   Everything here is a TOKEN or TYPE. No geometry: the numbers are measured off the clip and live
   in the engine's defaults, which are site 1's. A second site sets its own.

   ⚠ Paths are relative to `engine/atelier.css`, not to this file — the same rule as every other
   image token in this stylesheet. */
[data-skin="sanagustin"] .a-arrival--clip {
  /* THE WALL: the clip's own damask, darkened onto its measured average, so the plate and the
     clip are one wall and the feathered edges have nothing to give away. */
  --a-clip-wall: url(../assets/sanagustin/arrival-wall-plate.jpg);
  --a-arr-bg: #14100c;
  --a-clip-vig-ink: rgba(0, 0, 0, .62);
  --a-clip-hint: rgba(255, 226, 170, .32);
  /* The lettered render ships (the user's call, 2026-08-26), so the live text goes BEHIND the
     paper: still read, no longer seen. Set this to 2 and swap the <img> for arrival-card.webp and
     the blank render is back, with the same words in the site's own Garamond. */
  --a-clip-ink-z: 0;
  --a-clip-rise: 1750ms;
  --a-clip-read: 1600ms;
  --a-clip-lift: 2400ms;
  /* Must clear the lift, or atelier.js removes the overlay mid-flight. */
  --a-arr-exit: 2600;
}
/* The card's live text, set in the site's own Garamond and sized against the CARD, not the
   viewport: the plate is deliberately wider than a phone screen, so any vw unit here would drift
   away from the paper it is printed on. */
[data-skin="sanagustin"] .a-clip-ink { color: var(--a-clip-ink-colour, #6a5233); }
[data-skin="sanagustin"] .clip-mono {
  font: 400 3.6cqw/1 var(--body);
  letter-spacing: .5em;
  text-indent: .5em;
}
[data-skin="sanagustin"] .clip-name { font: 400 13cqw/1.02 var(--display); }
[data-skin="sanagustin"] .clip-amp { font: italic 400 6.4cqw/1.5 var(--display); }
[data-skin="sanagustin"] .clip-rule {
  width: 15%;
  height: 1px;
  margin: 3.6cqw 0 4.4cqw;
  background: currentColor;
  opacity: .55;
}
[data-skin="sanagustin"] .clip-when {
  font: 400 3.9cqw/1 var(--body);
  letter-spacing: .3em;
  text-indent: .3em;
}
[data-skin="sanagustin"] .clip-where { font: 400 3.3cqw/1 var(--body); letter-spacing: .06em; margin-top: 4.6cqw; }


/* ============================================================================
   SECTION 17 · THE GLAZE PASS — 2026-08-27
   ============================================================================
   The user's first note off their own pass: glossy/glassy, shimmering, smooth edges, and more
   ornament down the scroll — at 9 of 10, with the ornament budget overturned engine-wide. Three
   materials were built as proofs on the home page and they chose **A · glaze**: the azulejo's own
   wet glaze, which is the one reading that agrees with the site's materials instead of arguing
   with them. (The rejected two are in _archive/gloss-variants-rejected/. Variant C was frosted
   glass, and section 13's own comment already refused backdrop-filter for the hero panel — the
   pick settles that the refusal stands.)

   Mechanics live in atelier.css § V, because the override was engine-wide. Everything below is
   tokens plus which of THIS skin's components wear them. Nothing here forks engine logic.

   Tone the whole pass down in one place: --sk-glaze-spec (the specular), --a-sheen-b (the
   travelling band) and --a-field-opacity (the drifting motif). Setting --a-sheen-time to 0s
   is not the off switch — remove [data-atelier~="motion"] from <html> for that, or drop the
   three .a-sheen spans.

   NOT touched, on purpose:
   · the corner marks — four to a plate at 30px since 2026-08-19; already the ornament this pass
     would otherwise have invented. Extended to .card and .gcard here, and still kept OFF
     photographs, which was a decision with a reason, not an oversight.
   · the arrival (§U). It was gated over five passes this week. Gloss on the wax and the card is
     a separate pass with its own replay check.
   ============================================================================ */

[data-skin="sanagustin"] {
  /* The glaze is cool where the paper is warm — that is what reads as glaze rather than as a
     brighter ivory. The sink at the foot of a plate is cobalt, borrowed off the tile. */
  --sk-glaze-spec: rgba(255, 255, 255, .95);
  --a-glaze-spec: var(--sk-glaze-spec);
  --a-glaze-rim: rgba(255, 255, 255, .82);
  --a-glaze-rim-soft: rgba(255, 255, 255, .4);
  --a-glaze-sink: rgba(43, 74, 134, .5);

  /* the travelling specular: narrow, high, and slow enough that it is never seen to arrive */
  --a-sheen-a: rgba(255, 255, 255, 0);
  --a-sheen-b: rgba(255, 255, 255, .62);
  --a-sheen-time: 15s;

  /* smooth edges */
  --a-feather: clamp(9px, 2.4vw, 30px);
  --a-feather-frame: clamp(6px, 1.6vw, 18px);

  /* the soft seam: the raised band's own ground, faded at both ends instead of ruled */
  --a-seam-ground: var(--ivory-lit);
  --a-seam-ground-0: rgba(255, 252, 246, 0);

  /* the margin's numerals, in the tile's cobalt */
  --a-num-ink: #2b4a86;

  /* the drifting motif. The delivered tile is a dense 13-motif grid, so it is used large and at
     a wash — at any smaller step it reads as grey lace (DECISIONS, 2026-08-19). */
  --a-field-img: url("../assets/sanagustin/azulejo-tile.webp");
  --a-field-step: clamp(240px, 32vw, 420px);
  --a-field-opacity: .07;
}

/* --- 17.1 · which sheets are glazed ------------------------------------------------ */
/* Same list as section Q's letterpress bite, plus the panels that are sheets in all but name.
   Never a form field and never a photograph: a glazed photograph is a screen reflection.
   ⚠️ .hero-panel is NOT on this list, and must not be added. Section 13 made it edgeless on
   purpose — a radial wash with no rectangle, no border and no backdrop-filter, because the ivory
   type needs ink under it and a panel edge is what the wash exists to avoid. A specular gradient
   and a rim light draw that rectangle straight back. Its gloss is the masked sheen in 17.4. */
[data-skin="sanagustin"] :is(.plate, .confirm, .yours, .card, .gcard, .composer, .lookup,
  .invite-plate, .locked, .seat-plan) {
  background-image:
    linear-gradient(158deg, var(--a-glaze-spec) 0, rgba(255, 255, 255, .3) 24%, rgba(255, 255, 255, 0) 46%),
    radial-gradient(130% 90% at 10% 2%, var(--a-glaze-spec), transparent 62%);
  box-shadow:
    inset 0 1px 0 var(--a-glaze-rim),
    inset 0 0 0 1px var(--a-glaze-rim-soft),
    inset 0 -14px 24px -22px var(--a-glaze-sink),
    0 20px 44px -32px rgba(34, 26, 21, .6);
}
/* On an inked passage the glaze is a wet dark surface, not a white one: the specular drops to a
   quarter and the sink goes out, or a plate on ink reads as a hole cut in the page. */
[data-skin="sanagustin"] :is(.a-ink, .band--ink) :is(.plate, .confirm, .yours, .card) {
  --a-glaze-spec: rgba(255, 255, 255, .22);
  --a-glaze-rim: rgba(255, 255, 255, .3);
  --a-glaze-rim-soft: rgba(255, 255, 255, .1);
  --a-glaze-sink: rgba(0, 0, 0, .35);
}
/* The glaze reaches what the hand touches, and the chrome it sits under. */
/* .bar-rsvp is excluded: it has no ground of its own, so the glaze painted a floating milky
   rectangle over the photograph. Its highlight is the rule under it instead. */
[data-skin="sanagustin"] :is(.btn, .chip, .seat-chip, .react) {
  background-image: linear-gradient(to bottom, rgba(255, 255, 255, .34), rgba(255, 255, 255, 0) 52%, rgba(0, 0, 0, .1));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), 0 8px 18px -14px rgba(34, 26, 21, .8);
}
/* Scrolled only (C1, 2026-10-01): over the photograph the white glaze milked the sky behind the
   bar's white type down to ~1.7:1. The unscrolled bar takes its dark shade from J's section. */
html[data-skin="sanagustin"][data-scrolled] .site-head[data-float] .site-bar {
  background-image: linear-gradient(to bottom, rgba(255, 255, 255, .8), rgba(255, 255, 255, 0) 70%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 12px 30px -26px rgba(34, 26, 21, .8);
}
[data-skin="sanagustin"] .wsd-mark i { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .8); }

/* --- 17.2 · the corner marks reach the cards ------------------------------------- */
/* The mark system is section Q's and unchanged; this only widens who wears it. .card and .gcard
   are sheets carrying the site's own facts, which was always the test. */
[data-skin="sanagustin"] :is(.card, .gcard) { position: relative; }
[data-skin="sanagustin"] :is(.card, .gcard)::after {
  --a-mask: linear-gradient(#000, #000);
  --a-mark-tl: var(--sk-mark-tl); --a-mark-tr: var(--sk-mark-tr);
  --a-mark-bl: var(--sk-mark-bl); --a-mark-br: var(--sk-mark-br);
  content: "";
  position: absolute;
  inset: var(--a-mark-inset);
  pointer-events: none;
  opacity: var(--a-mark-opacity);
  border: 1px solid var(--a-mark-rule);
  background-image:
    var(--a-mark-tl), var(--a-mark-tr), var(--a-mark-bl), var(--a-mark-br);
  background-size: var(--a-mark-size) var(--a-mark-size);
  background-repeat: no-repeat;
  background-position: left top, right top, left bottom, right bottom;
  -webkit-mask-image: var(--a-mask), var(--a-mask), var(--a-mask), var(--a-mask);
  mask-image: var(--a-mask), var(--a-mask), var(--a-mask), var(--a-mask);
  -webkit-mask-size: var(--a-mark-len) var(--a-mark-len);
  mask-size: var(--a-mark-len) var(--a-mark-len);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: left top, right top, left bottom, right bottom;
  mask-position: left top, right top, left bottom, right bottom;
}
/* A card whose whole top edge is a photograph keeps its marks off the picture. */
[data-skin="sanagustin"] .card:has(> img)::after { inset: auto var(--a-mark-inset) var(--a-mark-inset); height: 40%; }

/* --- 17.3 · smooth edges -------------------------------------------------------- */
/* Framed photographs lose their crop line on all four sides. */
[data-skin="sanagustin"] :is(.hero-figure picture img, .invite-plate img, .spread-shot img,
  .idx-shot img, .chapter-shots img, .fchapter-shots img, .open2-shot img, .gallery-grid img) {
  mask-image:
    linear-gradient(to right, transparent 0, #000 var(--a-feather), #000 calc(100% - var(--a-feather)), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 var(--a-feather), #000 calc(100% - var(--a-feather)), transparent 100%);
  mask-composite: intersect;
}
/* A full-bleed frame is feathered top and bottom only — its sides are the window. The whole
   frame is masked, not just the photograph, so the scrim and the vignette go with it. */
[data-skin="sanagustin"] .a-bleed {
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--a-feather-frame),
    #000 calc(100% - var(--a-feather-frame)), transparent 100%);
}
/* Type in a bleed frame is lifted over the sheen by declaration, not by source order. */
[data-skin="sanagustin"] :is(.a-bleed-over, .bleed-credit) { position: relative; z-index: 3; }

/* Soft seams: the raised band fades its ground instead of ruling it, and the loose rules go
   from a hairline to a gradient. */
[data-skin="sanagustin"] .band--raised {
  border-block-color: transparent;
  background-color: transparent;
  background-image: linear-gradient(to bottom,
    var(--a-seam-ground-0) 0, var(--a-seam-ground) 6%,
    var(--a-seam-ground) 94%, var(--a-seam-ground-0) 100%);
}
[data-skin="sanagustin"] .hr {
  border-top-color: transparent;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--rule), transparent);
}
[data-skin="sanagustin"] .plate-when { border-block-color: transparent; }

/* --- 17.4 · the hosts of the three new elements ---------------------------------- */
[data-skin="sanagustin"] :is(.invite-plate, .spread-shot, .idx-shot, .open2-shot) { position: relative; }
[data-skin="sanagustin"] .band:has(> .a-num, > .a-field) { position: relative; }
/* The hero panel's sheen carries the same radial falloff as the wash beneath it, so what travels
   is a light on a shadow rather than a lit rectangle. Same reason as 17.1's warning. */
[data-skin="sanagustin"] .hero-panel .a-sheen {
  inset: -2.2rem -2rem -1.6rem -2.4rem;
  overflow: visible;
  mask-image: radial-gradient(108% 104% at 48% 54%, #000 0 38%, rgba(0, 0, 0, .45) 66%, transparent 92%);
}
[data-skin="sanagustin"] .hero-panel .a-sheen::after { --a-sheen-b: rgba(255, 246, 232, .3); }
/* Staggered, or the page reads as one sheet of glass being tilted. */
[data-skin="sanagustin"] .invite-plate .a-sheen::after { animation-delay: -4.5s; }
[data-skin="sanagustin"] .spread-shot .a-sheen::after { animation-delay: -9s; --a-sheen-time: 18s; }
[data-skin="sanagustin"] .idx-row:nth-child(2) .a-sheen::after { animation-delay: -6s; }
[data-skin="sanagustin"] .idx-row:nth-child(3) .a-sheen::after { animation-delay: -12s; }
[data-skin="sanagustin"] .a-bleed .a-sheen::after { --a-sheen-time: 26s; }

/* ============================================================================
   SECTION 18 · THE ORNAMENT + BACKDROP PASS — 2026-08-28
   ============================================================================
   Merged into the A · glaze pass at the user's instruction, home page first, so the whole
   thing could be judged as one look before it was applied to the rest. WIRED TO THE OTHER
   FIVE PAGES 2026-08-28 (second pass) — see 18.6 for what is uniform and what is not.
   Weight asked for: 10. Four moves, all skin-side — no engine file was touched, because every utility
   this needs already exists (R1's motif slot, V6's field, V3's feather, the skin's own
   .foot::before seam).

   REVERT, in order of bluntness:
     1 · the backdrop only — drop data-ground="photo" from <html> on all six pages. The paper
         ground, the --page cap and the sheet shadows all return with it. Nothing else in
         this section depends on it.
     2 · the whole pass — _archive/sanagustin-before-ornament-pass/ holds index.html and
         skin.css as they stood before this section. Paste both back.

   TONE IT DOWN, in this order:
     --sk-ground-scrim-a/-b   the wall's darkness. Raise for a quieter wall.
     --sk-ground-sheet-lift   the sheet's separation from the wall (0 = flat on it).
     --sk-orn-motif-opacity   the tile in the drawn rules.
     --sk-foot-border-opacity the footer's printed edge.
     --sk-seam-h              the two photographic seams' height (0 does NOT remove them —
                              delete the two .seam-band sections in index.html for that).

   THE GROUND IS ground-wall.jpg, the commissioned plate (landed 2026-08-28, ask at
   _download/sanagustin-backdrop-prompt.txt). It is GRADED IN THE LAB, not as delivered — the
   render came back with a 4.2x left-to-right light falloff and a copper cast. See the note in
   18.5. arrival-damask.jpg was the stand-in and is still the arrival's own ground.
   ---------------------------------------------------------------------------------------- */

[data-skin="sanagustin"] {
  --sk-orn-motif-opacity: .88;
  --sk-foot-border-opacity: .42;
  --sk-seam-h: clamp(148px, 24vh, 300px);
  --sk-ground-scrim-a: rgba(26, 19, 14, .52);
  --sk-ground-scrim-b: rgba(26, 19, 14, .66);
  --sk-ground-sheet-lift: 40px;
}

/* --- 18.1 · the drawn rules carry the real tile ----------------------------------- */
/* R1 says a skin that owns a motif points --a-orn-img at it. The url is written into the
   rule rather than the token on purpose: a url() inside a custom property resolves against
   the sheet that DECLARES it, and this skin already carries one engine-relative and one
   skin-relative path for exactly that reason. In a rule there is no ambiguity. */
[data-skin="sanagustin"] .a-orn--motif .a-orn-mark {
  width: clamp(24px, 2.2vw, 32px);
  height: clamp(24px, 2.2vw, 32px);
  background-image: url("../assets/sanagustin/azulejo-motif.webp");
  background-position: center;
  background-size: contain;
  background-repeat: no-repeat;
  /* filter, not opacity: R1's a-orn-turn animates opacity between .9 and 1, so an opacity
     declaration here is overridden by the animation and the token reads as inert. */
  filter: opacity(var(--sk-orn-motif-opacity));
}
/* On the inked footer the tile's own white ground reads as a lit chip, so it is held back. */
[data-skin="sanagustin"] .foot .a-orn--motif .a-orn-mark { filter: opacity(.6); }

/* --- 18.2 · the field on three bands, descending --------------------------------- */
/* Section 17 gave the raised band one drifting field. Three of them, quietest first, is what
   makes the motif read as the page's own texture rather than as that band's decoration. The
   hosts are already positioned by 17.4 and isolated by the engine. */
/* Ordered quietest LAST: .band--flush and .band--raised are both .band, so at equal
   specificity the later rule is the one that lands. */
[data-skin="sanagustin"] .band > .a-field { --a-field-opacity: .052; }
[data-skin="sanagustin"] .band--raised > .a-field { --a-field-opacity: .07; }
[data-skin="sanagustin"] .band--flush > .a-field { --a-field-opacity: .042; --a-field-fade: 78%; }

/* --- 18.3 · the footer's printed edge --------------------------------------------- */
/* The skin's own .foot::before (section Q, line ~2185) was a hairline with an unused
   --a-seam-img slot. This fills it: the azulejo border repeating along the top edge of the
   inked sheet, screened back so it is a printed band and not a light. */
[data-skin="sanagustin"] .foot { --a-seam-h: clamp(20px, 1.8vw, 28px); }
[data-skin="sanagustin"] .foot::before {
  background-image:
    linear-gradient(var(--a-seam-rule) 0 1px, transparent 1px),
    url("../assets/sanagustin/azulejo-border.webp");
  background-repeat: repeat-x, repeat-x;
  background-position: top left, top left;
  background-size: auto, auto var(--a-seam-h);
  opacity: var(--sk-foot-border-opacity);
}
/* The raised band keeps the plain hairline — one printed edge per page. */
[data-skin="sanagustin"] .band--raised::before { opacity: 1; }

/* --- 18.4 · the two photographic seams ------------------------------------------- */
/* Route B. The scroll was paper · paper · photograph · paper · paper · photograph; these are
   two short photographic registers between the bands, feathered top and bottom (V3 --block:
   the sides ARE the sheet's edge), so the page breaks more often without another interlude
   competing with the two that carry copy.

   TWO RULES FOR A SEAM'S SOURCE, both learned by getting it wrong on this page:

   1 · A SEAM TAKES A TEXTURE, NOT A SUBJECT. A 148px band across a photograph of people cuts
       their heads off, and the result reads as a broken crop rather than as a register — which
       is exactly what the first cut did with venue-03.jpg (the couple walking away). Seam 1 is
       now the cobbles of shoes-cobbles.jpg, cropped to its stone. If a picture has a subject it
       wants to be an interlude, and this page already has two of those.
   2 · IT SPANS THE WHOLE SHEET, so it must be at least as wide: 1472px at 1440 with 18.5's
       --page. texture-adobe.jpg was the very first pick and is out on that ground alone — 800 x
       800, a 1.8x upscale on the flagship's home page — and stays unplaced. Read a source's real
       dimensions before authoring width/height against it; the first cut declared both seams 16:9
       off the eye and neither source was. */
[data-skin="sanagustin"] .seam-band { padding-block: 0; }
[data-skin="sanagustin"] .seam-shot {
  position: relative;
  height: var(--sk-seam-h);
  overflow: hidden;
  isolation: isolate;
  --a-feather-frame: clamp(26px, 5vh, 60px);
}
[data-skin="sanagustin"] .seam-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--sk-seam-crop, 50% 50%);
}
[data-skin="sanagustin"] .seam-shot .a-sheen::after { --a-sheen-time: 22s; animation-delay: -7s; }

/* --- 18.5 · the ground becomes a photograph --------------------------------------- */
/* Route A, at the weight asked for. Gated on [data-ground="photo"] so it is one attribute
   to try and one attribute to withdraw — per page, which is how the home page wore it alone
   while it was out for review. All six carry it now.

   The type does not move onto the photograph. #main takes the laid sheet the body used to
   carry, which is what section 13's own logic already implies: the site is a page laid on a
   desk, and the desk is now a wall.

   THE PLATE IS GRADED, and the reason is what this ground is actually used for: the sheet covers
   the middle, so what is seen is a vertical strip down each side. As delivered the plate measured
   L=68 on the left strip against L=16 on the right — a 4.2x asymmetry, i.e. one glowing margin
   and one black one — and its cast was copper (61,31,20) against the arrival wall's browner
   (57,40,28). Corrected here by flat-field division against its own low-frequency luminance,
   keeping the grade's upper-left source but compressing the range to 1.43x, and by moving the
   cast half the distance to the arrival's ratio. After: left 49.8 / right 34.8, whole (63,37,24).
   2000 x 1333, 294 KB. The delivered file is untouched in uploads/ if the grade needs redoing. */
[data-ground="photo"] body {
  background-image:
    linear-gradient(180deg, var(--sk-ground-scrim-a) 0, var(--sk-ground-scrim-a) 42%, var(--sk-ground-scrim-b) 100%),
    url("../assets/sanagustin/ground-wall.jpg");
  background-size: cover, cover;
  background-position: center top, center top;
  background-repeat: no-repeat, no-repeat;
  background-attachment: fixed, fixed;
  background-blend-mode: normal, normal;
  background-color: #1a130e;
}
[data-ground="photo"] #main {
  background-color: var(--ivory);
  background-image: url("../assets/sanagustin/paper-laid.jpg");
  background-size: 440px auto;
  background-repeat: repeat;
  background-blend-mode: multiply;
}
/* Below 64em the sheet is the whole window: 8rem of wall would cost a phone 128px of measure,
   and the wall still shows through the header, the footer and the two bleed frames. */
@media (min-width: 64em) {
  [data-ground="photo"] body { --page: min(100rem, 100vw - 8rem); }
  /* Horizontal-only shadows, on purpose: section 13 refused a box shadow on #main and .foot
     because a shadow on stacked blocks draws a line at each seam. A shadow with no vertical
     offset and no vertical spread cannot draw that line. */
  [data-ground="photo"] :is(#main, .foot) {
    box-shadow:
      calc(-1 * var(--sk-ground-sheet-lift) / 2) 0 var(--sk-ground-sheet-lift) calc(-1 * var(--sk-ground-sheet-lift) / 2.4) rgba(0, 0, 0, .55),
      calc(var(--sk-ground-sheet-lift) / 2) 0 var(--sk-ground-sheet-lift) calc(-1 * var(--sk-ground-sheet-lift) / 2.4) rgba(0, 0, 0, .55);
  }
}
@media print {
  [data-ground="photo"] body { background-image: none; background-color: #fff; }
  [data-ground="photo"] :is(#main, .foot) { box-shadow: none; }
}

/* --- 18.6 · the pass on the other five pages — 2026-08-28 -------------------------- */
/* Wiring, not a second pass. 18.1 (the motif in the drawn rules) and 18.3 (the footer's
   printed edge) already reached every page the moment they were written — both select on a
   class, not on the home page — so all the five pages needed was the two moves that are
   MARKUP: data-ground="photo" on <html>, and the .a-field spans. Both are now on all six,
   and the interior footers were given .a-orn--motif so 18.1 has something to land on.

   WHAT IS DELIBERATELY NOT UNIFORM, and why — do not "finish" these:

   1 · THE SEAMS. 18.4's own rule is that a seam takes a TEXTURE and spans the sheet, and the
       library holds three crops that qualify: shoes-cobbles, detail-flowers (the home page's
       two) and attire-pegs. A seam is also the answer to one specific problem — a long run of
       paper with nothing in it. Only Questions has that run (two text bands, one interlude),
       so only Questions takes one, and it takes the third crop. Our Story, The Day and
       Entourage break at every band already; a register there is ornament for its own sake.
       ⚠️ venue-03.jpg is NOT a candidate. It is the couple walking away — the exact picture
       18.4 records as the rejected first cut. Do not re-cut it.

   2 · THE FIELD COUNT. Three where there are three copy bands to carry them (home, Our Story,
       The Day), two where there are two (Entourage, RSVP). Always ascending down the page.
       The ink band never takes one: the tile's own ground is white, so a wash of it on ink is
       a haze rather than a texture.

   3 · THE BODY ORNAMENT. One drawn rule per interior page, at the foot of its last copy band,
       against the home page's three. The interior pages stood at one (the footer) and R's
       budget is three, so this is the middle of the range, not the top of it.

   4 · RSVP KEEPS NO NUMERALS. Section 17's decision, unchanged: on a form, marginal numerals
       read as step numbers. It wears the ground, two fields and the rules like everything else. */
[data-skin="sanagustin"] .band--open2 > .a-field { --a-field-opacity: .042; --a-field-fade: 78%; }
/* The quiet register for a first field on a plain .band. Specificity, not order: 18.2's
   .band > .a-field is (0,2,0) and would otherwise win over a bare class. */
[data-skin="sanagustin"] .band > .a-field--quiet { --a-field-opacity: .042; --a-field-fade: 78%; }



/* ============================================================================
   19 · THE FLOATING ORNAMENTS — 2026-09-02
   The third ambient surface, taken as site 1's DECLARED exception to the two-surface budget
   (DECISIONS.md -> Site 1's floating ornaments). Paper ground + drifting azulejo field stay.
   Sites 3-8 keep two.

   ⚠️ THE MOTIF, AS BUILT: all THREE motifs, eighteen real cut-outs (2026-09-03). Ten sampaguita
   strands (sampaguita-01..10, the user's own, four to the brief + six variations), four stationery
   offcuts (offcut-01..04) and four pressed calachuchi (calachuchi-01..04) — the last eight landed
   2026-09-03 and were alpha-clipped below 20/255 on import like the sampaguita set. The six pages'
   data-wsd-cutouts INTERLEAVES the three so the first three slots are one of each (sampaguita-01,
   calachuchi-01, offcut-01, …); the engine places up to twelve and cycles the eighteen, so a page
   placing few pieces still shows the flagship strand first and a full desktop page shows all three
   motifs. The list is identical on all six pages by decision (uniform, as it was with ten).

   TONE THE WHOLE PASS with three tokens, in this order:
     --sk-float-opacity   the layer's weight        (.78 — .5 until 2026-09-03, then .66, then
                          this: the user asked for louder twice, the second time after seeing
                          the photographic render. Per-tone multipliers in 19.4 sit on top,
                          and since 2026-09-04 they take weight DOWN, not up — see 19.4)
     --float-parallax     counter-scroll in px      (90)
     --float-drift-x/-y   the drift's amplitude     (26 / 18)

   TWO KNOBS, as section 11h has:
     data-float="soft" on <html>   halves every amplitude and the weight
     delete the .a-float div       reverts the page, markup only

   REVERT: remove the .a-float div from the six pages and the <script src="engine/float.js">
   line. Nothing else in this file depends on section 19.

   ⚠️ The layer is z-index 2 inside #main, ABOVE the .a-field (which is -1) and below the two
   fixed docks (88) and the arrival (90). Do not raise it: a piece that can cross a dock is a
   piece that can sit on the design-study marker, and that marker is not negotiable.
   ⚠️ It suppresses itself over the RSVP, the room, the wall and the party gallery — the engine
   does that by module name, so a new interactive surface here needs [data-float-none] and
   nothing else.
   ⚠️ THE OPENING FRAME IS EXEMPT — see 19.5. The hero carries no ornament by the user's
   decision of 2026-09-04, and it is markup, not a number in here.
   ========================================================================== */
[data-skin="sanagustin"] .a-float {
  /* 12 until 2026-09-04. The user asked for LOUDER a third time in the same note that asked for
     transparency, so loudness is bought in SIZE and COUNT and paid for in weight (19.4) — the
     pieces are bigger and there are more of them, and each one sits further into the paper.
     The list on every page is eighteen names, so 14 still repeats nothing. */
  --float-count: 14;
  --float-lane: 21;
  /* --float-size sets the TOP of the piece range, --float-min the bottom. 92px is this site's
     legibility floor and it is not a taste number: `_download/sanagustin-float-prompts.txt`
     was written around "a form that reads at 90 px", because these pieces are MASKS and a
     seven-blossom strand whose blossoms stop separating is a smudge. Before the floor existed
     the engine shrank a piece into any room over 44px, and three of seven pieces on the home
     page at 924px were being painted 48-50px — counted as placed, invisible as ornament.
     ⚠️ RAISING EITHER NUMBER LOWERS THE PLACED COUNT. Re-run the census, always. */
  --float-size: clamp(96px, 10.4vw, 168px);   /* clamp(84px, 9vw, 148px) until 2026-09-04 */
  --float-min: 92;
  --float-opacity: var(--sk-float-opacity, .78);
  --float-parallax: 90;
  --float-drift: 26;
  --float-drift-x: 26;
  --float-drift-y: 18;
}
/* --- 19.1 · the three registers, and where each one is actually reached ----------
   Recoloured, never hidden: a layer that vanishes over the photography reads as a bug in the
   layer. The ink register is much the quietest — it is the only one competing with type.

     ink    the ivory ground. Every page, most pieces. Quietest of the three, at .42 of the
            layer's weight — raised from .3 on 2026-09-03 with the base weight, because the
            user's first look at real pixels came back "too quiet". It is still the register to
            reach for FIRST when toning the pass down: it is the only one competing with type.
     white  the full-bleed photographs (.band--bleed, .hero--bleed, .band--hero-bleed).
            Every page has at least one; the home page has three.
     lit    a DARK PASSAGE inside #main — .band--ink or the .a-ink utility.
            ⚠️ Site 1 has exactly ONE, the Story page's section 03. The footer is inked too but
            sits OUTSIDE #main, so the layer can never reach it — do not add .foot to the tone
            map expecting it to work. The register is correct and it is deliberately rare; if a
            later page gains a dark passage inside #main it takes this colour with no change. */
[data-skin="sanagustin"] .a-float-p[data-tone="ink"] { color: var(--ink); opacity: calc(var(--float-w, .5) * .42); }
[data-skin="sanagustin"] .a-float-p[data-tone="lit"] { color: var(--accent-soft); }
[data-skin="sanagustin"] .a-float-p[data-tone="white"] { color: #fffdf8; }

/* --- 19.2 · WHERE THE PIECES ACTUALLY GO — measured, not assumed -----------------
   ⚠️ TWO WRONG VERSIONS OF THIS COMMENT ARE ON RECORD. The first claimed the layer lives in the
   side margins and that a phone therefore shows nothing. The second published a table of 4-to-12
   pieces per page — measured, but measured by a census that had been handed the SAME selector
   list as the engine, so it could only ever confirm what the engine had been told. Both are kept
   in the changelog as the warning; the numbers below are from a census that finds text
   independently, by the reader's definition (any element with its own text, plus every control).

   .wrap on this site is max-width: none — it is 100% minus two 24px gaps at EVERY width. A
   24px margin cannot hold a 44px piece, so beside a full-width text column there is never room
   at any viewport, phone or desktop.

   What the engine does instead: it DETECTS the page's text itself and tests each piece PER
   BAND, so a piece lands at a height where no text column sits — the bleeds, the plates, the
   dark passages, the air between sections. THE LAYER LIVES IN THE PAGE'S QUIET HEIGHTS. Where a
   narrower measure leaves real margin (.prose and .wrap--narrow at 1440), pieces sit beside the
   column, which is why the count jumps at the widest width.

   Measured with tools/Float Census.html, 2026-09-04 (after the louder/transparent pass AND the
   clean hero), 24 frames, ZERO over text, ZERO over a control, smallest painted piece 92px:

     page        390    834   1024   1440   (placed of 14, every piece >= 92px)
     Home          6      7      7      8   (hero suppressed at every width)
     Story         2      2      1      3
     Details       1      4      2      4
     Entourage     2      2      3      9
     FAQ           2      3      3     10
     RSVP          1      1      2     14   (5 suppressed over the form and the room)

   ⚠️ THE HOME PAGE PAID FOR ITS CLEAN HERO AND STILL WENT UP. The opening 100svh photograph was
   the layer's best room (19.5) and it now carries nothing, yet Home holds 6/7/7/8 against the
   previous 6/8/7/8 — because the same pass raised --float-count to 14 and --float-size by ~14%.
   That is the whole trade of 2026-09-04: the pieces the hero used to hold were re-placed down
   the scroll, bigger, at lower weight. Story at 390 went 1 -> 2 and RSVP at 1440 now places all
   fourteen. Do not read the count as loudness: weight went DOWN in the same pass (19.4).

   ⚠️ THESE NUMBERS ARE LOWER THAN THE ONES PUBLISHED EARLIER THE SAME DAY, AND THE EARLIER ONES
   WERE NOT HONEST. Until --float-min existed the engine shrank a piece into any room over 44px,
   so a strand could be painted 48px wide and still count as placed. On the home page at 924px,
   three of seven "placed" pieces were 48-50px. That is what made twelve of twelve at 1440 look
   achievable: at that width the pieces beside .prose and .wrap--narrow were being squeezed into
   a 50-90px margin. With the floor at 92px the count at 1440 is 3 to 14 depending on the page,
   and every piece in it can actually be seen. A count that includes specks is a worse number
   than a smaller count.

   THE AIR PASS OF 2026-09-03 still stands, and it is what the phone column rests on: --pad-band
   raised at 47.99em (the phone's whole ornament budget, see the block near line 3025) and the
   parallax cut hard below 80em (the block at the end of this section). Home holds 6 at 390 where
   it held 2.

   ⚠️ WHAT NOTHING BOUGHT: Details and RSVP place ONE piece at 390 (Story reached two on
   2026-09-04's larger pieces; the floor itself did not move). That floor is text
   density — those pages are almost entirely running text, and a piece needs its own height plus
   its travel in a run where NO text column crosses it. The levers left are trades, not settings:
   lower --float-min and accept smudges (the prompt sheet was written around "reads at 90px", so
   this is the one being refused), or restructure the pages into shorter chapters. DO NOT widen
   --float-lane to chase a count — the only thing on the other side of that margin is running
   text.

   r.veiled stays clean because no piece is ever placed at a height and inset where a column
   could reach it, and the drift is clamped inside that inset — not because of opacity, and not
   by luck. If you change .wrap's max-width, RE-RUN THE CENSUS: this whole table moves. */
@media (max-width: 79.99em) {
  /* ── THE NARROW-WIDTH MOTION BUDGET, AND WHY IT IS SO MUCH SMALLER ───────────────────────
     A piece is placed only at a height where no text column crosses its vertical span — and the
     span it must clear is its own height PLUS its full parallax travel and drift either side.
     So on a narrow layout the parallax is not just motion: it is the single biggest consumer of
     the little quiet height that exists, and every pixel of counter-scroll costs about two
     pixels of the air a piece needs to be allowed to exist at all.
     Cutting it here is what turns the extra band padding (see the 47.99em block near line 3025)
     into actual placed pieces. The pair was measured together; changing one without the other
     wastes it. The desktop keeps the full 90.
     ⚠️ THE BREAKPOINT IS 79.99em, NOT 63.99em, AND THAT IS MEASURED. At 63.99em the 1024 column
     of the census sat BELOW the 834 column — 1 to 3 pieces against 8 — because 1024px is exactly
     64em, so it kept the full desktop parallax while having no more side margin to show for it.
     1440 is the first width where the narrower measures leave real margin and the full 90 pays
     for itself. Everything between 834 and 1280 behaves like a tablet, so it is toned like one.
     ⚠️ RE-RUN tools/Float Census.html after touching any number in this block. */
  [data-skin="sanagustin"] .a-float {
    --float-parallax: 30;
    --float-drift-x: 16;
    --float-drift-y: 10;
  }
}
@media (max-width: 47.99em) {
  [data-skin="sanagustin"] .a-float {
    --float-parallax: 18;
    --float-drift-x: 12;
    --float-drift-y: 6;
  }
}

/* --- 19.3 · the soft knob -------------------------------------------------------- */
[data-skin="sanagustin"][data-float="soft"] .a-float {
  --sk-float-opacity: .4;
  --float-parallax: 45;
  --float-drift-x: 13;
  --float-drift-y: 9;
}

/* --- 19.4 · THE PHOTOGRAPHIC REGISTER — decided 2026-09-03 -----------------------
   The user asked whether the ornaments being silhouettes was intentional, looked at all three
   candidates on real pixels (silhouette / photographic / hybrid) and chose PHOTOGRAPHIC. The
   switch now lives in the engine — data-float-render="photo" on <html>, all six pages,
   engine/atelier.css § W — and what is left here is the only part that was ever this skin's
   job: making warm-white blossoms visible on a warm ground.
   The proof page is retired to _archive/sanagustin-ornament-render.html.

   ⚠️ WHY A SHADOW IS STRUCTURAL HERE, not decoration. The cut-outs are warm white sampaguita
   shot on a warm table; the ground is #f6f1e6. Painted as pixels on that ground the form has
   almost no separation — a ghost. The shadow IS the edge. Remove it and the layer disappears
   on the register that carries most of the pieces.

   ⚠️ WHAT CHOOSING PHOTO COST, on the record: the white register. A photograph cannot invert
   itself over a dark passage, so 19.1's three registers collapse to one appearance with three
   strengths of separation. data-tone is still stamped by the engine and still used below — it
   is what lets ivory get more help than a photograph does.

   The three tone hooks are now shadow strength, not colour:
     ink    the ivory ground, sharing space with type — most help, needs the most
     lit    a dark passage — least help, the ground does the separating
     white  a full-bleed photograph — middling; the frame is busy, the shadow keeps the edge */
/* ⚠️ THESE MULTIPLIERS WENT BELOW 1 ON 2026-09-04, AND THAT IS THE POINT. The user's note was
   "a bit of transparency so they blend with the background" in the same breath as "louder", and
   the two are only contradictory if loudness has to be opacity. It does not: 19's base now
   carries the loudness (bigger pieces, fourteen of them) and this block hands it back in weight
   so the ornament sits IN the paper instead of on top of it.
   The measured reason they read as pasted-on: --float-w is --float-opacity × (0.45 + depth×0.75),
   and the depth spread tops out at 1.4 — so at .78 the deepest pieces reached 1.17, and a 1.15
   multiplier on top of that CLIPPED AT FULLY OPAQUE. A third of the layer had no transparency at
   all. Under 1 the whole range stays translucent: ink lands at .52–.86, the base at .58–.96.
   Do not raise these past ~.9 without re-checking that arithmetic — the clip is silent. */
[data-skin="sanagustin"][data-float-render="photo"] .a-float-p {
  opacity: calc(var(--float-w, .5) * .82);
  filter: drop-shadow(0 3px 10px rgba(31, 22, 17, .26));
}
/* On ivory the photograph is the weakest of the three, so it gets the most help — and it is the
   register that carries most of the pieces on every page. Wider and softer than it was: the
   shadow is still what makes the edge, but a tight dark one is exactly what read as a sticker. */
[data-skin="sanagustin"][data-float-render="photo"] .a-float-p[data-tone="ink"] {
  opacity: calc(var(--float-w, .5) * .74);
  filter: drop-shadow(0 4px 14px rgba(31, 22, 17, .34)) saturate(1.04) contrast(1.03);
}
/* Over a dark passage the ground already separates the form; a second shadow only muddies it. */
[data-skin="sanagustin"][data-float-render="photo"] .a-float-p[data-tone="lit"] {
  opacity: calc(var(--float-w, .5) * .88);
  filter: drop-shadow(0 2px 12px rgba(0, 0, 0, .3));
}

/* --- 19.5 · THE HERO CARRIES NOTHING — the user's note, 2026-09-04 ---------------
   "Remove all ornaments on the hero section. Keep the hero clean." Taken literally and taken
   as MARKUP: [data-float-none] on the opening full-bleed frame of the two pages that have one
   (home's .hero--bleed, entourage's .band--hero-bleed). The engine already blocks that whole
   vertical range by module suppression — HANDS_OFF in engine/float.js — so this needed no
   engine change and no selector in here.

   ⚠️ IT IS DELIBERATELY NOT ON THE FOUR TEXT-LED OPENINGS (.band--open2 and the narrow opens on
   story / details / faq / rsvp). Those are not heroes, they are the first band of running text,
   and they place ONE piece at 390 as it is (19.2) — suppressing them would empty the top of
   four pages to answer a note about one. If the user means every page's first screen, the change
   is adding the attribute to those four openings, and the census will show the cost.

   ⚠️ THE HOME PAGE'S FIRST SCREEN WAS THE LAYER'S BEST ROOM — a 100svh photograph with no text
   column crossing most of it. Removing it costs real count at every width, which is why the
   count and size went up in the same pass. REVERT is deleting the one attribute. */

/* ============================================================================
   THE FIRST SCREEN ON A SHORT PHONE — 2026-10-01 (the iPhone stand-in)
   On a phone under 600px of visible screen (an iPhone SE is 375 × 559 with Safari's bars) the
   invitation is 633px of words, so the frame grew past the screen. Its eyebrow ran under the
   floating bar ("Together with our families" under "A & P"), and once the words cleared the bar
   the reply sat under the two docks at rest. Here the words fit one short screen instead.
   Nothing is trimmed (section 13's rule: the lockup keeps every line). Only the air between the
   lines and two type sizes step down. It sits at the end of the file so it follows section 13's
   phone block, which resets the same properties. Measured before and after in the changelog. */
@media (max-width: 47.99em) and (max-height: 37.5em) {
  .hero--bleed .hero-over { padding-top: 4.7rem; gap: .5rem; }   /* the bar ends at 71px */
  [data-skin="sanagustin"] .hero-over .hero-names { font-size: 1.95rem; }
  [data-skin="sanagustin"] .plate-when b { font-size: 2.4rem; }
  [data-skin="sanagustin"] .hero-panel .plate-time { margin-top: .45rem; }
  .plate-count { margin-top: .6rem; }
  .plate-count-cap { margin-bottom: .3rem; }
  .hero-panel .plate-act { margin-top: .6rem; padding-top: .6rem; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   THE LIVE GUEST WALL (2026-10-13, WALL-PLAN.md) — the feature demo's two parts, in this site's
   dress: the guest form (.wf, on a phone) and the reception screen (.ws). Engine hooks are
   wall.js's; nothing here reaches inside it.
   The screen is the site at night, for a projector: a burgundy-black field, Cormorant, one ivory
   card. It is a size container (feature.css), so every measure below is in cqmin and the same
   rules serve the scaled box, the projector view and an LED wall of any shape.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* ---------- the guest form ---------- */
[data-skin="sanagustin"] .wf { display: grid; gap: 1.4rem; padding: 0 1.25rem 2.4rem; }
[data-skin="sanagustin"] .wf-study {
  margin: 0 -1.25rem; padding: .7rem 1.25rem;
  background: var(--ink-raw); color: var(--ivory-lit);
  font: 600 .72rem/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; text-align: center;
}
[data-skin="sanagustin"] .wf-head { display: grid; gap: .3rem; justify-items: center; padding-top: .6rem; text-align: center; }
[data-skin="sanagustin"] .wf-kicker { margin: 0; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .2em; text-transform: uppercase; color: var(--muted); }
[data-skin="sanagustin"] .wf-names { margin: 0; font: 400 2.6rem/1 var(--display); color: var(--ink); }
[data-skin="sanagustin"] .wf-names i { font-style: italic; color: var(--accent); }
[data-skin="sanagustin"] .wf-form,
[data-skin="sanagustin"] .wf-sent,
[data-skin="sanagustin"] .wf-closed {
  display: grid; gap: 1.1rem; padding: 1.4rem 1.2rem 1.5rem;
  background: var(--bg-raised); border: var(--rule-w) solid var(--rule);
}
[data-skin="sanagustin"] .wf [hidden] { display: none; }
[data-skin="sanagustin"] .wf-title { margin: 0; font: 400 1.85rem/1.1 var(--display); color: var(--ink); text-wrap: balance; }
[data-skin="sanagustin"] .wf-lede { margin: 0; font: var(--step-0)/1.55 var(--body); color: var(--muted); text-wrap: pretty; }
[data-skin="sanagustin"] .wf-opt { font-weight: 400; letter-spacing: .04em; text-transform: none; color: var(--muted); }
[data-skin="sanagustin"] .wf-left { margin: 0; justify-self: end; font: var(--step--1)/1 var(--ui); color: var(--muted); font-variant-numeric: tabular-nums; }
[data-skin="sanagustin"] .wf-left[data-near] { color: var(--accent); }
[data-skin="sanagustin"] .wf-pick {
  min-height: 3.4rem; width: 100%;
  background: transparent; border: 1px dashed var(--stone-deep); color: var(--accent);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; cursor: pointer;
}
[data-skin="sanagustin"] .wf-pick:hover { background: var(--accent-soft); border-color: var(--accent); }
[data-skin="sanagustin"] .wf-preview { display: grid; gap: .5rem; }
[data-skin="sanagustin"] .wf-preview img { display: block; width: 100%; max-height: 18rem; object-fit: cover; background: var(--rule); }
[data-skin="sanagustin"] .wf-preview-ctl { display: flex; gap: 1.2rem; }
[data-skin="sanagustin"] .wf-link {
  min-height: 44px; padding: 0; background: none; border: 0; cursor: pointer;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--accent);
  text-decoration: underline; text-underline-offset: .3em;
}
[data-skin="sanagustin"] .wf-sheet { display: grid; gap: .6rem; padding: .9rem; background: var(--ivory-lit); border: var(--rule-w) solid var(--rule); }
[data-skin="sanagustin"] .wf-sheet-h { margin: 0; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
[data-skin="sanagustin"] .wf-roll { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
[data-skin="sanagustin"] .wf-shot { aspect-ratio: 1; padding: 0; background: var(--rule); border: 2px solid transparent; cursor: pointer; line-height: 0; }
[data-skin="sanagustin"] .wf-shot img { width: 100%; height: 100%; object-fit: cover; }
[data-skin="sanagustin"] .wf-shot:hover { border-color: var(--accent); }
[data-skin="sanagustin"] .wf .actions { margin: 0; }
[data-skin="sanagustin"] .wf-send { width: 100%; justify-content: center; }
[data-skin="sanagustin"] .wf-sent { justify-items: center; text-align: center; padding-block: 2.2rem; }
[data-skin="sanagustin"] .wf-sent > div { display: grid; gap: .8rem; justify-items: center; }
[data-skin="sanagustin"] .wf-sent:focus { outline: none; }
[data-skin="sanagustin"] .wf-sent-mark { margin: 0; font: 1.8rem/1 var(--display); color: var(--accent); }
[data-skin="sanagustin"] .wf-sim { margin: 0; font: italic var(--step--1)/1.5 var(--body); color: var(--muted); }
[data-skin="sanagustin"] .wf-closed a { color: var(--accent); }

/* ---------- the screen ---------- */
[data-skin="sanagustin"] .ws {
  --ws-ink: #f6efe3; --ws-dim: rgba(246, 239, 227, .7); --ws-card: #f6f0e5; --ws-card-ink: #221a15;
  --ws-wine: #8c2c3a;
  position: absolute; inset: 0; overflow: hidden; color: var(--ws-ink);
  background:
    radial-gradient(70cqmin 55cqmin at 50% 46%, rgba(140, 44, 58, .28), transparent 70%),
    radial-gradient(130% 100% at 50% 40%, #241014 0%, #160b0d 58%, #0b0607 100%);
  font-family: var(--display);
}
[data-skin="sanagustin"] .ws p { margin: 0; }
[data-skin="sanagustin"] .ws-names {
  position: absolute; left: 4cqmin; top: 3.6cqmin; z-index: 3;
  font: 400 5cqmin/1 var(--display); letter-spacing: .01em;
}
[data-skin="sanagustin"] .ws-names i { color: #d68b97; }
[data-skin="sanagustin"] .ws-live {
  position: absolute; right: 4cqmin; top: 4.6cqmin; z-index: 3;
  display: flex; align-items: center; gap: 1.1cqmin;
  font: 600 1.7cqmin/1 var(--ui); letter-spacing: .24em; text-transform: uppercase; color: var(--ws-dim);
}
[data-skin="sanagustin"] .ws-sep { opacity: .6; }
[data-skin="sanagustin"] .ws-dot {
  width: 1.2cqmin; height: 1.2cqmin; border-radius: 50%; background: #e05568;
  animation: sa-ws-pulse 2.4s ease-in-out infinite;
}
@keyframes sa-ws-pulse { 50% { opacity: .3; scale: .7; } }
[data-skin="sanagustin"] .ws-study {
  position: absolute; left: 4cqmin; bottom: 3cqmin; z-index: 3;
  font: 600 1.35cqmin/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: rgba(246, 239, 227, .62);
}

/* the cards: one in the middle, up to four drifting behind at depth */
[data-skin="sanagustin"] .ws-stage { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; pointer-events: none; }
[data-skin="sanagustin"] .ws-drift { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
[data-skin="sanagustin"] .ws-card {
  display: flex; align-items: stretch;
  background: var(--ws-card); color: var(--ws-card-ink);
  box-shadow: 0 2.4cqmin 7cqmin rgba(0, 0, 0, .55), 0 0 0 .25cqmin rgba(255, 252, 246, .5) inset;
  transition: opacity 1.2s ease, scale 1.4s cubic-bezier(.2, .7, .2, 1);
}
[data-skin="sanagustin"] .ws-card[data-state="enter"] { opacity: 0; scale: .97; }
[data-skin="sanagustin"] .ws-card[data-state="out"] { opacity: 0; scale: 1.01; }
[data-skin="sanagustin"] .ws-photo { margin: 0; flex: none; line-height: 0; background: #2a2220; }
[data-skin="sanagustin"] .ws-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
[data-skin="sanagustin"] .ws-words { display: flex; flex-direction: column; justify-content: center; gap: 2.4cqmin; min-width: 0; }
[data-skin="sanagustin"] .ws-msg { font-weight: 400; line-height: 1.22; text-wrap: pretty; overflow-wrap: anywhere; }
[data-skin="sanagustin"] .ws-sig { display: flex; flex-wrap: wrap; gap: .4em 1em; align-items: baseline; font-family: var(--ui); }
[data-skin="sanagustin"] .ws-who { font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--ws-wine); }
[data-skin="sanagustin"] .ws-when { color: #6d6055; letter-spacing: .04em; }

[data-skin="sanagustin"] .ws-card[data-kind="featured"] { grid-area: 1 / 1; max-width: 90cqw; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"][data-side="right"] { flex-direction: row-reverse; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"] .ws-photo { padding: 1.6cqmin; background: var(--ws-card); }
[data-skin="sanagustin"] .ws-card[data-kind="featured"][data-shape="tall"] .ws-photo { width: 46cqmin; height: 61cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"][data-shape="wide"] .ws-photo { width: 64cqmin; height: 48cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"] .ws-words { width: 38cqmin; padding: 4.6cqmin 4.6cqmin 4.6cqmin 3cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"][data-side="right"] .ws-words { padding: 4.6cqmin 3cqmin 4.6cqmin 4.6cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"] .ws-msg { font-size: 3.6cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"][data-len="s"] .ws-msg { font-size: 4.6cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"][data-len="l"] .ws-msg { font-size: 2.9cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"] .ws-sig { font-size: 1.6cqmin; }
/* a line with no photograph is set like a card on its own, type first */
[data-skin="sanagustin"] .ws-card[data-kind="featured"]:not([data-has-photo]) .ws-words {
  width: 64cqmin; padding: 7cqmin 8cqmin; align-items: center; text-align: center;
}
[data-skin="sanagustin"] .ws-card[data-kind="featured"]:not([data-has-photo]) .ws-msg { font-size: 5cqmin; font-style: italic; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"]:not([data-has-photo])[data-len="l"] .ws-msg { font-size: 3.8cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"]:not([data-has-photo]) .ws-sig { justify-content: center; }
[data-skin="sanagustin"] .ws-card[data-kind="featured"]:not([data-has-photo])::before {
  content: "\2726"; position: absolute; margin-top: 2.6cqmin; left: 50%; translate: -50% 0;
  font-size: 2.4cqmin; color: var(--ws-wine);
}
[data-skin="sanagustin"] .ws-card[data-kind="featured"]:not([data-has-photo]) { position: relative; }

[data-skin="sanagustin"] .ws-card[data-kind="drift"] {
  position: absolute; width: 24cqmin; flex-direction: column;
  filter: blur(.18cqmin) saturate(.7); opacity: .34;
  animation: sa-ws-drift 34s ease-in-out infinite alternate;
}
[data-skin="sanagustin"] .ws-card[data-kind="drift"][data-state="enter"],
[data-skin="sanagustin"] .ws-card[data-kind="drift"][data-state="out"] { opacity: 0; }
[data-skin="sanagustin"] .ws-card[data-kind="drift"] .ws-photo { height: 17cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="drift"] .ws-words { padding: 1.8cqmin 2cqmin 2.2cqmin; gap: 1cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="drift"] .ws-msg {
  font-size: 1.9cqmin; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
[data-skin="sanagustin"] .ws-card[data-kind="drift"] .ws-sig { font-size: 1.05cqmin; }
[data-skin="sanagustin"] .ws-card[data-kind="drift"] .ws-when { display: none; }
[data-skin="sanagustin"] .ws-card[data-slot="1"] { left: 5cqw; top: 15cqh; rotate: -3deg; animation-duration: 31s; }
[data-skin="sanagustin"] .ws-card[data-slot="2"] { right: 6cqw; top: 11cqh; rotate: 2.5deg; animation-duration: 37s; animation-delay: -9s; }
[data-skin="sanagustin"] .ws-card[data-slot="3"] { left: 11cqw; bottom: 7cqh; rotate: 2deg; animation-duration: 41s; animation-delay: -17s; }
[data-skin="sanagustin"] .ws-card[data-slot="4"] { right: 10cqw; bottom: 9cqh; rotate: -2deg; animation-duration: 35s; animation-delay: -4s; }
@keyframes sa-ws-drift {
  from { translate: 0 0; }
  to { translate: 2.6cqmin -3.4cqmin; }
}

/* the quiet cards: nothing posted yet, and the wall closed after the day */
[data-skin="sanagustin"] .ws-quiet {
  position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; gap: 2cqmin;
  text-align: center; padding: 8cqmin;
}
[data-skin="sanagustin"] .ws-quiet[hidden] { display: none; }
[data-skin="sanagustin"] .ws-quiet-h { font: italic 400 6cqmin/1.1 var(--display); }
[data-skin="sanagustin"] .ws-quiet p:not(.ws-quiet-h) { font: 2.2cqmin/1.5 var(--body); color: var(--ws-dim); }

[data-skin="sanagustin"] .ws[data-paused] .ws-card,
[data-skin="sanagustin"] .ws[data-paused] .ws-dot { animation-play-state: paused; }
[data-skin="sanagustin"] .ws[data-still] .ws-card,
[data-skin="sanagustin"] .ws[data-still] .ws-dot { animation: none; }
[data-skin="sanagustin"] .ws[data-still] .ws-card { transition-property: opacity; }
@media (prefers-reduced-motion: reduce) {
  [data-skin="sanagustin"] .ws-card, [data-skin="sanagustin"] .ws-dot { animation: none; transition-property: opacity; }
}

/* ============================================================================
   FOREVER (2026-10-13, FOREVER-PLAN.md) — the site the morning after, in this site's skin.
   The engine hides the other phase ([data-when], engine.css). Below: the guestbook (guestbook.js),
   the day hour by hour (details), the songs played, the anniversary line.
   ============================================================================ */
.anniv-note { margin-top: clamp(1.5rem, 1rem + 2vw, 2.5rem); max-width: 36em; }

/* the guestbook: every wall post as a card, photographs keeping their own shape */
.gb { display: grid; gap: var(--gap); }
.gb-head { display: flex; flex-wrap: wrap; align-items: end; gap: var(--gap); }
.gb-head .filters { margin-left: 0; }
.gb-list { columns: 2 14rem; column-gap: clamp(.8rem, .5rem + 1.5vw, 1.5rem); }
.gb--strip .gb-list { columns: 3 13rem; }
.gb-card {
  display: grid; gap: .55rem; break-inside: avoid; margin-bottom: clamp(.8rem, .5rem + 1.5vw, 1.5rem);
  padding: clamp(.7rem, .55rem + .6vw, 1rem); background: var(--bg-raised);
  border: var(--rule-w) solid var(--rule); box-shadow: 0 1px 2px rgba(34, 26, 21, .06);
}
.gb-card[data-mine] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.gb-shot { margin: 0; overflow: clip; }
.gb-shot img { width: 100%; height: auto; cursor: zoom-in; transition: transform 700ms var(--ease); }
.gb-shot:hover img { transform: scale(1.03); }
.gb-say { font: italic 400 var(--step-1)/1.3 var(--display); color: var(--ink); overflow-wrap: anywhere; }
.gb-card[data-has-photo="false"] .gb-say { font-size: var(--step-2); padding-block: .4rem; }
.gb-say:empty { display: none; }
.gb-by { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--sand-ink); }
.gb-mine { padding: .2em .6em; background: var(--accent); color: var(--ivory-lit); letter-spacing: .1em; }
.gb-empty { font: italic var(--step-0)/1.6 var(--body); color: var(--muted); }

/* the day, hour by hour: a time, what happened, and the photograph of it */
.hours { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.hour {
  display: grid; gap: .6rem 1.5rem; padding-block: clamp(1.25rem, .9rem + 1.5vw, 2.25rem);
  border-top: var(--rule-w) solid var(--rule);
}
.hour:first-child { border-top: 0; }
.hour-time { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--accent); }
.hour-say { display: grid; gap: .45rem; align-content: start; }
.hour-say h3 { font: 400 var(--step-2)/1.1 var(--display); }
.hour-say p { font: var(--step-0)/1.6 var(--body); color: var(--muted); max-width: 32em; }
.hour-shot { margin: 0; overflow: clip; }
.hour-shot img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; cursor: zoom-in; }
.hour-shot figcaption { margin-top: .4rem; font: italic var(--step--1)/1.4 var(--body); color: var(--sand-ink); }
@media (min-width: 46em) {
  .hour { grid-template-columns: 7rem minmax(0, 1fr) minmax(0, 1.1fr); align-items: start; }
  .hour-time { padding-top: .5rem; }
}

/* the songs, after: the ones the band played */
[data-skin="sanagustin"] .tag--played { background: var(--accent); color: var(--ivory-lit); border-color: var(--accent); }

/* the day's three chapters on the story page reuse the story's .chapter-blurb, whose story-grid
   placement (grid-column: 2, above) would split their one-column head in two */
[data-skin="sanagustin"] .fchapter .chapter-blurb { grid-column: auto; grid-row: auto; }
.fchapter .chapter-head { align-content: start; }

/* ============================================================================
   FOREVER, ROUND 2 (2026-10-15, FOREVER-PLAN.md §6) — the morning after, and the years after it.
   The engine: clock.js (years later), anniversary.js, yours.js, predictions.js, journal.js,
   origins.js, capsule.js, postcard.js, print.js. Everything here is this skin's dress for them.
   ============================================================================ */

/* the dock: which morning (chrome.js wires [data-wsd-era]) */
.dock-era { display: flex; flex-wrap: wrap; gap: .5rem; padding-top: .75rem; border-top: 1px solid rgba(246, 241, 232, .22); }
.dock-era-say { flex: 1 0 100%; font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--band-ink-muted); }
.dock-era-say span { letter-spacing: .04em; text-transform: none; font-weight: 400; }

/* the banner and the thank-you, at the top of home */
.yours-band { padding-block: clamp(1.5rem, 1rem + 2.5vw, 3rem); }
.yours-band .wrap { display: grid; gap: clamp(1.25rem, .9rem + 1.5vw, 2rem); }
.anniv {
  display: grid; gap: .8rem; padding: clamp(.9rem, .7rem + .8vw, 1.25rem) clamp(1rem, .7rem + 1.2vw, 1.6rem);
  background: var(--accent); color: var(--on-accent);
}
.anniv-say { font: var(--step-0)/1.5 var(--body); max-width: 46em; }
.anniv-say b { font: italic 600 var(--step-1)/1.2 var(--display); margin-right: .2em; }
.anniv-go { display: flex; flex-wrap: wrap; gap: .5rem; }
.anniv-btn {
  min-height: 44px; padding: .45em 1em; border: 1px solid rgba(255, 252, 246, .55); background: transparent;
  color: var(--on-accent); font: 600 var(--step--1)/1.2 var(--ui); letter-spacing: .1em; text-transform: uppercase;
}
.anniv-btn:hover { background: rgba(255, 252, 246, .12); }
.anniv-btn[aria-pressed="true"] { background: var(--ivory-lit); color: var(--accent); border-color: var(--ivory-lit); }
.anniv-btn:focus-visible { outline-color: var(--ivory-lit); }

.yours { display: grid; gap: .6rem; max-width: 46rem; }
.yours-head { font: 400 var(--step-4)/1.05 var(--display); }
.yours-lede { font: var(--step-0)/1.6 var(--body); color: var(--muted); max-width: 36em; }
.yours-trail { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); gap: 0 var(--gap); margin-top: .5rem; }
.yours-trail > div { display: grid; gap: .2rem; padding-block: .8rem; border-top: var(--rule-w) solid var(--rule); }
.yours-trail dt { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--sand-ink); }
.yours-trail dd { font: italic 400 var(--step-1)/1.3 var(--display); color: var(--ink); overflow-wrap: anywhere; }

/* did it come true? */
.guess-lede { font: var(--step-0)/1.6 var(--body); color: var(--muted); max-width: 40em; margin-top: .6rem; }
.guess-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr)); gap: var(--gap); }
.guess-card {
  display: grid; gap: .8rem; align-content: start; padding: clamp(1rem, .8rem + .8vw, 1.5rem);
  background: var(--bg-raised); border: var(--rule-w) solid var(--rule);
}
.guess-q { font: 400 var(--step-2)/1.15 var(--display); }
.guess-opts { display: grid; gap: .45rem; }
.guess-pick {
  position: relative; display: flex; justify-content: space-between; align-items: center; gap: 1rem; width: 100%;
  min-height: 44px; padding: .55rem .8rem; text-align: left; overflow: hidden; isolation: isolate;
  background: var(--bg); border: var(--rule-w) solid var(--rule); color: var(--ink);
  font: var(--step-0)/1.3 var(--body);
}
/* the tally as a bar, once you have guessed (and always after) */
.guess-pick::before {
  content: ""; position: absolute; inset: 0 auto 0 0; z-index: -1; width: calc(var(--share, 0) * 100%);
  background: var(--accent-soft); transition: width var(--motion-slow) var(--ease);
}
.guess-card:not([data-picked]):not([data-called]) .guess-pick::before,
.guess-card:not([data-picked]):not([data-called]) .guess-share { visibility: hidden; }
.guess-pick:hover:not(:disabled) { border-color: var(--stone-deep); }
.guess-pick[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.guess-pick:disabled { cursor: default; opacity: 1; }
.guess-share { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .06em; color: var(--sand-ink); font-variant-numeric: tabular-nums; }
.guess-opt[data-answer] .guess-pick { border-color: var(--ink); }
.guess-opt[data-answer] .guess-label::after { content: "  \2713"; color: var(--accent); font-weight: 700; }
.guess-said { font: italic var(--step-0)/1.5 var(--body); color: var(--muted); }
.guess-said:empty, .guess-verdict:empty { display: none; }
.guess-verdict { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--sand-ink); }
.guess-card[data-called="yes"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.guess-card[data-called="yes"] .guess-verdict { color: var(--accent); }

/* the first year (Story) */
.journal-lede { font: var(--step-0)/1.6 var(--body); color: var(--muted); }
.journal-list { display: grid; gap: 0; max-width: 46rem; }
.jentry { display: grid; gap: .5rem; padding-block: clamp(1.25rem, .9rem + 1.5vw, 2rem); border-top: var(--rule-w) solid var(--rule); }
.jentry-when { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent); }
.jentry-title { font: 400 var(--step-2)/1.15 var(--display); }
.jentry-body { display: grid; gap: .5rem; }
.jentry-body p { font: var(--step-0)/1.65 var(--body); color: var(--ink); max-width: 34em; }
.jentry-shot { margin: .4rem 0 0; overflow: clip; max-width: 34rem; }
.jentry-shot img { width: 100%; height: auto; cursor: zoom-in; }
.jentry[data-shape="tall"] .jentry-shot { max-width: 20rem; }
.jentry[data-latest] .jentry-when::after { content: " \00b7  newest"; color: var(--sand-ink); }
.journal-next { margin-top: var(--gap); padding-top: var(--gap); border-top: var(--rule-w) solid var(--rule); font: italic var(--step-0)/1.6 var(--body); color: var(--muted); max-width: 40em; }

/* the day in numbers (The Day) */
.numbers { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--gap); }
@media (min-width: 52em) { .numbers { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.numbers > div { display: flex; flex-direction: column-reverse; justify-content: flex-end; gap: .3rem; padding-block: clamp(1rem, .7rem + 1vw, 1.6rem); border-top: var(--rule-w) solid var(--rule); }
.numbers dd { font: 400 var(--step-5)/.95 var(--display); color: var(--accent); font-variant-numeric: lining-nums; }
.numbers dt { font: 600 var(--step--1)/1.35 var(--ui); letter-spacing: .1em; text-transform: uppercase; color: var(--sand-ink); }

/* the recipe card */
.recipe { display: grid; gap: var(--gap); background: var(--bg-raised); border: var(--rule-w) solid var(--rule); padding: clamp(1rem, .7rem + 1.5vw, 2rem); }
@media (min-width: 60em) { .recipe { grid-template-columns: minmax(0, .9fr) minmax(0, 1.4fr); align-items: start; } }
.recipe-shot { margin: 0; overflow: clip; }
.recipe-shot img { width: 100%; height: auto; cursor: zoom-in; }
.recipe-say { display: grid; gap: .7rem; }
.recipe-title { font: 400 var(--step-3)/1.05 var(--display); }
.recipe-meta { font: italic var(--step-0)/1.5 var(--body); color: var(--muted); }
.recipe-cols { display: grid; gap: var(--gap); }
@media (min-width: 40em) { .recipe-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); } }
.recipe-cols h3 { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent); margin-bottom: .5rem; }
.recipe-list, .recipe-steps { display: grid; gap: .45rem; font: var(--step-0)/1.5 var(--body); color: var(--ink); }
.recipe-list li { padding-left: 1rem; position: relative; }
.recipe-list li::before { content: ""; position: absolute; left: 0; top: .7em; width: .35rem; height: 1px; background: var(--accent); }
.recipe-steps { counter-reset: step; }
.recipe-steps li { counter-increment: step; display: grid; grid-template-columns: 1.6rem minmax(0, 1fr); }
.recipe-steps li::before { content: counter(step); font: 400 var(--step-1)/1.1 var(--display); color: var(--accent); }
.recipe-print { background: none; border: 0; padding: 0; cursor: pointer; min-height: 44px; }

/* where everyone came from (Entourage) */
.origins-lede { font: var(--step-0)/1.6 var(--body); color: var(--muted); max-width: 40em; margin-top: .6rem; }
.origins-lede b { font-weight: 600; color: var(--ink); }
.origins-body { display: grid; gap: var(--gap); }
@media (min-width: 56em) { .origins-body { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); align-items: start; } }
.origins-chart { margin: 0; display: grid; gap: .6rem; }
.origins-chart svg { width: 100%; max-width: 34rem; height: auto; margin-inline: auto; overflow: visible; }
.origins-chart figcaption { font: italic var(--step--1)/1.5 var(--body); color: var(--sand-ink); max-width: 34rem; }
.og-ring { fill: none; stroke: var(--rule); stroke-width: 1; }
.og-ring--edge { stroke: var(--stone-deep); }
.og-ring-label { font: 600 9px var(--ui); letter-spacing: .06em; fill: var(--sand-ink); }
.og-axis { stroke: var(--rule); stroke-dasharray: 2 4; }
.og-compass { font: 600 11px var(--ui); letter-spacing: .12em; fill: var(--sand-ink); }
.og-centre { fill: var(--ink); }
.og-dot circle { fill: var(--accent); stroke: var(--bg); stroke-width: 2; }
.og-no { font: 700 9.5px var(--ui); fill: var(--on-accent); }
.origins-list { display: grid; gap: 0; }
@media (min-width: 34em) and (max-width: 55.99em) { .origins-list { display: block; columns: 2 14rem; column-gap: var(--gap); } }
.origin-row { display: grid; grid-template-columns: 1.8rem minmax(0, 1fr); gap: .1rem .5rem; padding-block: .5rem; border-top: var(--rule-w) solid var(--rule); break-inside: avoid; }
.origin-no { grid-row: span 2; font: 700 var(--step--1)/1.6 var(--ui); color: var(--accent); }
.origin-place { font: 400 var(--step-1)/1.2 var(--display); }
.origin-meta { font: var(--step--1)/1.4 var(--ui); color: var(--sand-ink); }

/* your postcard (RSVP, after) */
.pcard-lede { font: var(--step-0)/1.6 var(--body); color: var(--muted); max-width: 40em; margin-top: .6rem; }
.pcard-body { display: grid; gap: var(--gap); }
@media (min-width: 56em) { .pcard-body { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); align-items: start; } }
.pcard-side { display: grid; gap: .9rem; }
.pcard-say { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--sand-ink); }
.pcard-picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr)); gap: .5rem; }
.pcard-pick-btn { display: block; width: 100%; padding: 0; border: 2px solid transparent; background: var(--bg); aspect-ratio: 1; overflow: hidden; }
.pcard-pick-btn img { width: 100%; height: 100%; object-fit: cover; }
.pcard-pick-btn[aria-pressed="true"] { border-color: var(--accent); }
.pcard-pick[data-mine] .pcard-pick-btn { box-shadow: 0 0 0 1px var(--accent); }
.pcard-stage { display: grid; gap: .8rem; justify-items: start; }
.postcard {
  display: grid; width: 100%; max-width: 40rem; background: var(--ivory-lit); color: var(--ink);
  border: var(--rule-w) solid var(--rule); box-shadow: var(--a-shadow-deep);
}
@media (min-width: 40em) { .postcard { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.postcard-shot { margin: 0; background: var(--bg); }
.postcard-shot img { width: 100%; height: 100%; max-height: 28rem; object-fit: cover; }
.postcard-back { display: grid; gap: .7rem; align-content: start; padding: clamp(1rem, .8rem + 1vw, 1.6rem); border-left: var(--rule-w) solid var(--rule); }
.postcard-to { font: italic 400 var(--step-2)/1.15 var(--display); overflow-wrap: anywhere; }
.postcard-note { font: var(--step-0)/1.6 var(--body); }
.postcard-from { font: italic var(--step-1)/1.3 var(--display); color: var(--accent); }
.postcard-where { font: 600 var(--step--1)/1.4 var(--ui); letter-spacing: .1em; text-transform: uppercase; color: var(--sand-ink); }

/* the time capsule (RSVP, both phases) */
.capsule { display: grid; gap: .9rem; }
.capsule-form { display: grid; gap: .9rem; }
.capsule-years { display: flex; flex-wrap: wrap; gap: .4rem 1.2rem; margin: 0; padding: 0; border: 0; }
.capsule-years legend { width: 100%; margin-bottom: .4rem; padding: 0; font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--sand-ink); }
.capsule-years label { display: inline-flex; align-items: center; gap: .45rem; min-height: 44px; font: var(--step-0)/1.3 var(--body); }
.capsule-years input { accent-color: var(--accent); width: 1.1rem; height: 1.1rem; }
.capsule-list { display: grid; gap: .8rem; margin-top: .5rem; }
.envelope {
  position: relative; display: grid; gap: .35rem; padding: 1rem 1.1rem 1rem 1.25rem;
  background: var(--bg-raised); border: var(--rule-w) solid var(--rule); border-left: 3px solid var(--stone-deep);
}
.envelope[data-open="true"] { border-left-color: var(--accent); }
.envelope[data-mine] { box-shadow: 0 0 0 1px var(--accent); }
.envelope:not([data-mine]) .envelope-mine { display: none; }
.envelope-mine { margin-left: .6rem; }
.envelope-when { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--sand-ink); }
.envelope-from { font: italic 400 var(--step-1)/1.25 var(--display); }
.envelope-text { font: var(--step-0)/1.6 var(--body); color: var(--ink); max-width: 34em; }
.envelope-text:empty { display: none; }
.envelope-status { display: flex; align-items: center; gap: .5rem; font: var(--step--1)/1.4 var(--ui); color: var(--muted); }
.envelope-seal { flex: none; width: .9rem; height: .9rem; border-radius: 50%; background: var(--accent); box-shadow: inset 0 0 0 2px rgba(255, 252, 246, .35); }

/* print: the recipe card and the postcard, alone on the page */
@media print {
  .wsd-print-sheet .recipe { border: 0; padding: 0; grid-template-columns: minmax(0, .8fr) minmax(0, 1.4fr); }
  .wsd-print-sheet .recipe-do { display: none; }
  .wsd-print-sheet .postcard { box-shadow: none; max-width: none; grid-template-columns: 1fr 1fr; break-inside: avoid; }
  .wsd-print-sheet .postcard-shot img { max-height: none; }
}

/* the first year, its own page (2026-10-15, the user: "a separate page for the couple's journey", the
   journal moved off Our Story) and its highlight on home: the newest entry, then the way in */
.first-go { margin-top: 1.1rem; }
.journal--page .journal-list { margin-inline: auto; }
.journal--page .journal-lede { max-width: 46rem; margin: 0 auto .5rem; font-family: var(--ui); font-size: var(--step--1); letter-spacing: .12em; text-transform: uppercase; color: var(--sand-ink); }
.journal--page .journal-next { max-width: 46rem; margin-inline: auto; }
.journal-when { max-width: 46rem; margin: 1rem auto 0; display: grid; gap: .6rem; font: italic var(--step-0)/1.5 var(--body); color: var(--muted); }
.journal-go .anniv-btn { border-color: var(--accent); color: var(--accent); }
.journal-go .anniv-btn:hover { background: var(--accent-soft); }
.journal-go .anniv-btn[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); }
.journal-go .anniv-btn:focus-visible { outline-color: var(--accent); }
.first-hi { display: grid; gap: .4rem; max-width: 46rem; }
.first-hi .jentry { border-top: 2px solid var(--accent); }
.first-hi-go { margin-top: .4rem; }
/* the bar after the wedding carries a fifth link (The First Year): from the full bar up (62em) the
   "Married" line steps aside (the hero and the brand say it already), and the gaps tighten to 100em */
@media (min-width: 62em) {
  :root[data-phase="after"] .bar-reply { display: none; }
  :root[data-phase="after"] .nav ul { gap: clamp(.75rem, -1.6rem + 3.8vw, 1.5rem); }
}
