/* ============================================================================
   hetki — idealism.co/hetki
   ----------------------------------------------------------------------------
   A single long page: full-bleed photographic hero, near-white ground, a giant
   display headline over a narrow label column and two body columns, an
   annotated interface with pressable marks, moment cards with inline audio,
   spec accordions, and a persistent pill.

   Two typefaces, one weight each. One accent — ochre — and it only ever marks
   something active, live or hovered. Never pure white, never pure black.

   CONTRAST: every text colour here clears WCAG AA (4.5:1 normal, 3:1 large)
   against the ground it sits on. The ratios are noted where they are tight.
   ========================================================================== */

@font-face {
  font-family: "Canela";
  src: url("../../assets/fonts/Canela-Regular-Web.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url("../../assets/fonts/IBMPlexSans-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --paper:      #f4f2ee;
  --paper-deep: #eae6df;
  --dark:       #16120e;

  --ink:        #1d1712;                    /* body ink. 14.9:1 on paper */
  --ink-60:     rgba(29, 23, 18, 0.66);     /* secondary type. 5.44:1 — was 0.60,
                                               which measured 4.478:1 and missed
                                               AA by 0.02 */
  --ink-mute:   #6b6660;                    /* the SMALL type colour. 5.08:1.
                                               --ink-40 measured 2.49:1 and was
                                               being used for real words. */
  --ink-40:     rgba(29, 23, 18, 0.40);     /* DECORATIVE ONLY — rules, tracks,
                                               fills. Never set type in it. */
  --ink-14:     rgba(29, 23, 18, 0.14);     /* hairlines */

  --cream:      #e6d9c8;                    /* type on dark. 11.8:1 */
  --cream-60:   rgba(230, 217, 200, 0.60);  /* secondary on dark. 5.45:1 */
  --cream-16:   rgba(230, 217, 200, 0.16);  /* hairlines on dark */

  --ochre:      #c99e63;                    /* the accent. Blocks, rules, live
                                               states, and type ON DARK (7.6:1) */
  --ochre-ink:  #8a6532;                    /* the accent as TYPE ON PAPER.
                                               Raw ochre on paper is 1.9:1 and
                                               unreadable; this is 4.7:1 */
  --on-ochre:   #16120e;                    /* type on an ochre block. 7.6:1 */
  --on-ochre-2: #382b1c;                    /* the recessive step there. 5.6:1 */

  --display: "Canela", Georgia, serif;
  --text: "IBM Plex Sans", system-ui, sans-serif;

  /* The type ladder. These clamps are checked against their COMPUTED value at
     1280 / 1440 / 1920 — an unreachable ceiling is a bug, not a scale. */
  --t-hero:    clamp(2.75rem, 6.4vw, 5.5rem);
  --t-display: clamp(2.25rem, 5vw, 4.5rem);
  --t-label:   clamp(1.05rem, 0.9rem + 0.45vw, 1.3rem);      /* ~19-20.8px */
  --t-body:    clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);    /* ~17-19px */
  --t-small:   0.9375rem;

  --gutter: clamp(1.5rem, 4.5vw, 4rem);
  --band:   clamp(4.5rem, 9vw, 7.5rem);
  --measure: 34rem;

  --header-h: 7rem;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; scroll-padding-top: var(--header-h); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* height:auto matters: the width/height ATTRIBUTES on every img are
   presentational hints that map to CSS width/height. Author CSS overriding
   only the width leaves the attribute height standing, and the picture
   squeezes to fit both — which is exactly what happened to the interface
   render (measured 809x959 against a 1.67 native aspect). */
img { max-width: 100%; height: auto; display: block; }

.wrap { max-width: 90rem; margin-inline: auto; padding-inline: var(--gutter); }

/* Focus is an active state, so it spends the accent. The ring has to change
   colour with the ground under it or it disappears on the ochre block. */
:focus-visible { outline: 2px solid var(--ochre-ink); outline-offset: 3px; }
.section--dark :focus-visible,
.foot :focus-visible,
.hero :focus-visible,
.header :focus-visible { outline-color: var(--ochre); }        /* 7.6:1 on dark */
.band__block :focus-visible,
.pill:focus-visible { outline-color: var(--on-ochre); }        /* 7.6:1 on ochre */

::selection { background: rgba(201, 158, 99, 0.35); }

/* the skip link: invisible until a keyboard asks for it */
.skip {
  position: fixed;
  top: 1rem; left: 50%;
  translate: -50% -400%;
  z-index: 90;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink-14);
  border-radius: 999px;
  padding: 0.7rem 1.3rem;
  font-size: var(--t-small);
  letter-spacing: 0.08em;
  text-decoration: none;
  transition: translate 200ms ease-out;
}
.skip:focus-visible { translate: -50% 0; }

.sr { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }


/* ── type ────────────────────────────────────────────────────────────────── */

.display {
  font-family: var(--display);
  font-weight: 400;
  font-size: var(--t-display);
  line-height: 1.08;
  letter-spacing: -0.005em;
  margin: 0;
  max-width: 18ch;
}

.label {
  font-size: var(--t-label);
  letter-spacing: 0.14em;
  color: var(--ink-60);
  margin: 0;
}

/* every label carries a short hairline that draws itself in when its section
   arrives — the ink redraws itself, which is the plugin's signature reveal */
.label::before {
  content: "";
  display: block;
  width: 2.75rem;
  height: 1px;
  background: currentColor;
  opacity: 0.45;
  margin-bottom: 0.8rem;
  transform-origin: left;
}
.js [data-reveal].reveal .label::before {
  transform: scaleX(0);
  transition: transform 700ms cubic-bezier(0.22, 0.61, 0.36, 1) 120ms;
}
.js [data-reveal].reveal.is-in .label::before { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal].reveal .label::before { transform: none; transition: none; }
}

.lede { max-width: var(--measure); margin: 0; }
.muted { color: var(--ink-60); }
.tiny { font-size: var(--t-small); color: var(--ink-mute); }

.ulink {
  --line: currentColor;
  position: relative;
  display: inline-block;
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  color: inherit;
  text-decoration: none;
  padding-bottom: 4px;
  transition: color 140ms ease-out;
}

/* the underline is drawn, not set: on hover it runs a few pixels past the
   word, the way a pen crosses a corner. A line that stops exactly on the
   word was drawn by a machine — the plugin's own hover vocabulary. */
.ulink::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--line);
  transition: left 140ms ease-out, right 140ms ease-out;
}
.ulink:hover { color: var(--ochre-ink); --line: var(--ochre); }
.ulink:hover::after { left: -1px; right: -4px; }
.section--dark .ulink:hover,
.hero .ulink:hover { color: var(--ochre); --line: var(--ochre); }

/* an arrow in a link leans the way it points */
.ulink .arr { display: inline-block; transition: translate 140ms ease-out; }
.ulink:hover .arr { translate: 3px 0; }
.ulink:hover .arr--down { translate: 0 3px; }


/* ── header ──────────────────────────────────────────────────────────────── */

.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.5rem var(--gutter);
  color: var(--cream);
  transition: background-color 200ms ease-out, color 200ms ease-out;
}

/* The header's own scrim. It cannot depend on the hero photograph being dark
   in the top-right corner — that is a photograph nobody has taken yet. */
.header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(rgba(12, 9, 7, 0.72), rgba(12, 9, 7, 0));
  transition: opacity 200ms ease-out;
}
.header.is-stuck {
  background: var(--paper);
  color: var(--ink);
  border-bottom: 1px solid var(--ink-14);
}
.header.is-stuck::before { opacity: 0; }

/* reading down, the header steps aside; the first upward gesture brings it
   back. It never hides while anything inside it holds focus. */
.header { transition: background-color 200ms ease-out, color 200ms ease-out,
                      transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1); }
.header.is-away:not(:focus-within) { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) {
  .header { transition: background-color 200ms ease-out, color 200ms ease-out; }
  .header.is-away:not(:focus-within) { transform: none; }
}

/* the mark is tinted with currentColor — the <a> around it must not fall back
   to the browser's default link blue, or the wordmark paints blue */
.header a { color: inherit; }

.header__mark {
  display: block;                          /* an inline span has no size */
  width: 92px;
  aspect-ratio: 842 / 454;
  background-color: currentColor;
  -webkit-mask: url("../../assets/ink/wordmark.png") no-repeat center / contain;
  mask: url("../../assets/ink/wordmark.png") no-repeat center / contain;
}

.header__nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.4vw, 2.25rem);
  font-size: var(--t-small);
  letter-spacing: 0.06em;
}
.header__nav a { color: var(--cream-60); text-decoration: none; transition: color 140ms ease-out; }
.header__nav a:hover { color: var(--cream); }
.header.is-stuck .header__nav a { color: var(--ink-60); }
.header.is-stuck .header__nav a:hover { color: var(--ink); }

@media (max-width: 52rem) { .header__nav a { display: none; } }

.sound {
  display: inline-flex;
  align-items: center;
  background: none;
  border: 0;
  border-radius: 999px;
  padding: 0.5rem 0.4rem;
  color: inherit;
  font: inherit;
  cursor: pointer;
  opacity: 0.7;
  transition: color 140ms ease-out, opacity 140ms ease-out;
}
.sound:hover { opacity: 1; }
.sound[aria-pressed="true"] { color: var(--ochre); opacity: 1; }
.header.is-stuck .sound[aria-pressed="true"] { color: var(--ochre-ink); }
.sound[aria-disabled="true"] { opacity: 0.45; cursor: default; }

/* three bars. They move only while the sound is ON — a live state, not decor. */
.sound__bars { display: flex; align-items: flex-end; gap: 2px; height: 11px; }
.sound__bars i { width: 2px; height: 3px; background: currentColor; display: block; }
.sound[aria-pressed="true"] .sound__bars i { animation: eq 900ms ease-in-out infinite alternate; }
.sound[aria-pressed="true"] .sound__bars i:nth-child(2) { animation-delay: 150ms; }
.sound[aria-pressed="true"] .sound__bars i:nth-child(3) { animation-delay: 300ms; }
@keyframes eq { from { height: 3px; } to { height: 11px; } }
@media (prefers-reduced-motion: reduce) {
  .sound[aria-pressed="true"] .sound__bars i { animation: none; height: 7px; }
}


/* ── hero ────────────────────────────────────────────────────────────────── */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;              /* the headline sits low-left */
  background: var(--dark);
  color: var(--cream);
  overflow: hidden;
}

.hero__media { position: absolute; inset: 0; z-index: 0; }
.hero__media img,
.hero__media video { width: 100%; height: 100%; object-fit: cover; }

/* the photograph does not exist yet, so the scrim must guarantee legibility
   over WHATEVER gets shot — including a bright highlight top-right, where the
   nav sits. The right-hand stop never goes below 0.45. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(12, 9, 7, 0.80) 0%,
    rgba(12, 9, 7, 0.58) 45%,
    rgba(12, 9, 7, 0.45) 100%);
}

/* flush to the gutter at every width — a centred max-width would detach the
   headline from the left edge on a wide screen while the photo stays full-bleed */
.hero__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  padding: 10rem var(--gutter) clamp(3.5rem, 8vh, 6rem);
}

.hero__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: var(--t-hero);
  line-height: 1.06;
  margin: 0 0 1.5rem;
  max-width: 16ch;
}
.hero__sub { max-width: 40ch; margin: 0 0 2rem; color: var(--cream-60); }
.hero__scroll { color: var(--cream); --line: var(--cream-60); }


/* ── photographs ─────────────────────────────────────────────────────────── */

/* the band's picture fills its half of the grid, whatever its native ratio */
.band > img { width: 100%; height: 100%; object-fit: cover; }

/* story photographs sit between the paragraphs, full column width */
.story__plate { width: 100%; height: auto; margin-block: 2.5rem; }

/* the full-bleed breaths between sections. Each is a <figure> holding one
   photograph. Where the browser supports scroll-driven animations, the image
   drifts slowly inside its frame as the page moves past it — depth, scrubbed
   by the visitor's own scrolling, so it never moves on its own. Everywhere
   else (and under prefers-reduced-motion) it is simply a picture. */
.breath { display: block; width: 100%; margin: 0; overflow: hidden; }
.breath img { display: block; width: 100%; height: auto; }

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .breath img {
      scale: 1.12;                       /* head-room for the drift */
      animation: breath-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes breath-drift {
      from { translate: 0 -4.5%; }
      to   { translate: 0  4.5%; }
    }
  }
}


/* ── photo placeholders — delete the .shot rules once the photographs land ── */

.shot {
  position: relative;
  display: grid;
  place-items: center;
  background: var(--paper-deep);
  border: 1px dashed var(--ink-14);
  color: var(--ink-mute);
  text-align: center;
  padding: 2rem;
  min-height: 12rem;
}
.shot--dark { background: #1b1712; border-color: var(--cream-16); color: var(--cream-60); }
.shot__id { font-size: var(--t-small); letter-spacing: 0.14em; display: block; margin-bottom: 0.5rem; }
.shot__what { font-size: var(--t-small); max-width: 30ch; line-height: 1.5; margin: 0; }

.hero__media .shot { align-items: end; justify-items: end; text-align: right; padding: 3rem; }
.hero__media .shot__what { margin-left: auto; }


/* ── sections ────────────────────────────────────────────────────────────── */

.section { padding-block: var(--band); }
.section--tight { padding-block: calc(var(--band) * 0.55); }

/* The dark sections carry the plugin's own ground: a whisper of warm grain,
   and a candle wash you cannot quite see. Both are background paint — they sit
   under every word. The grain plate is the one tuned against the plugin's
   audit target (sigma 2.1–2.9 measured luminance; flat was rejected as "dead").

   ⚠️ CONTRAST BUDGET, measured: on the lightest possible pixel (a full speck
   under the candle's peak) cream-60 text clears AA at 4.59:1 — a 2% margin.
   Do not raise the speck alpha (the 0.10 in the feColorMatrix), the feFuncA
   slope, or the candle's 0.055 without re-computing that ratio. */
.section--dark,
.foot {
  background-color: var(--dark);
  background-image:
    /* the candle — one soft warm pool, off-centre, like the plugin face */
    radial-gradient(75% 90% at 30% 18%,
      rgba(169, 131, 92, 0.055) 0%,
      rgba(169, 131, 92, 0.030) 40%,
      rgba(169, 131, 92, 0) 72%),
    /* the grain — sparse cream specks, tiled */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='6' intercept='-4'/%3E%3C/feComponentTransfer%3E%3CfeColorMatrix values='0 0 0 0 0.89 0 0 0 0 0.81 0 0 0 0 0.66 0 0 0 0.10 0'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23g)'/%3E%3C/svg%3E");
  background-repeat: no-repeat, repeat;
  background-size: 100% 100%, 300px 300px;
}

.section--dark { color: var(--cream); }
.section--dark .label,
.section--dark .muted,
.section--dark .tiny { color: var(--cream-60); }

.statement__head { margin-bottom: clamp(2.5rem, 5vw, 4.5rem); max-width: 20ch; }

/* a narrow label column beside two columns of body copy */
.overview {
  display: grid;
  grid-template-columns: minmax(0, 10rem) repeat(2, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: start;
}
@media (max-width: 60rem) {
  .overview { grid-template-columns: 1fr; }
  .overview .label { margin-bottom: 0.5rem; }
}
.overview p { margin: 0; }


/* the band — a picture beside a solid block of the accent */
.band {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  min-height: 26rem;
}
@media (max-width: 60rem) { .band { grid-template-columns: 1fr; } }

.band__block {
  background: var(--ochre);
  color: var(--on-ochre);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1.25rem;
  padding: clamp(2rem, 5vw, 4rem);
}
.band__block .ulink { color: var(--on-ochre); --line: rgba(22, 18, 14, 0.5); }
.band__block .ulink:hover { color: var(--on-ochre); --line: var(--on-ochre); }


/* ── the story ───────────────────────────────────────────────────────────── */

.story { display: grid; grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); gap: clamp(1.5rem, 3vw, 3rem); }
@media (max-width: 60rem) { .story { grid-template-columns: 1fr; } }
.story__body { max-width: 40rem; }
.story__body p { margin: 0 0 1.35em; }
.story__body > .shot { margin-block: 2.5rem; }


/* ── the interface, annotated ────────────────────────────────────────────── */

.iface__stage { position: relative; line-height: 0; }
.iface__stage img { width: 100%; height: auto; border: 1px solid var(--cream-16); }

.hot {
  position: absolute;
  width: 34px; height: 34px;
  margin: -17px 0 0 -17px;
  display: grid; place-items: center;
  border-radius: 999px;
  border: 1px solid var(--cream-60);
  background: rgba(22, 18, 14, 0.55);
  backdrop-filter: blur(2px);
  color: var(--cream);
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  transition: background-color 140ms ease-out, border-color 140ms ease-out, color 140ms ease-out;
}
.js #interface.reveal .hot { opacity: 0; transition: opacity 420ms ease-out; }
.js #interface.reveal.is-in .hot { opacity: 1; }
.js #interface.reveal.is-in .hot:nth-of-type(2) { transition-delay: 70ms; }
.js #interface.reveal.is-in .hot:nth-of-type(3) { transition-delay: 140ms; }
.js #interface.reveal.is-in .hot:nth-of-type(4) { transition-delay: 210ms; }
.js #interface.reveal.is-in .hot:nth-of-type(5) { transition-delay: 280ms; }
.js #interface.reveal.is-in .hot:nth-of-type(6) { transition-delay: 350ms; }
.js #interface.reveal.is-in .hot:nth-of-type(7) { transition-delay: 420ms; }
.js #interface.reveal.is-in .hot:nth-of-type(8) { transition-delay: 490ms; }
@media (prefers-reduced-motion: reduce) {
  .js #interface.reveal .hot { opacity: 1; transition: none; }
}

.hot:hover { border-color: var(--ochre); color: var(--ochre); }
.hot[aria-pressed="true"] { background: var(--ochre); border-color: var(--ochre); color: var(--on-ochre); }

/* the caption. Height is reserved for the longest one, so pressing a mark
   swaps the words without shoving the rest of the page down. */
.iface__readout {
  margin-top: 1.5rem;
  min-height: 5.5em;
  max-width: 40rem;
}
.iface__readout .k {
  display: block;
  font-size: var(--t-small);
  letter-spacing: 0.14em;
  color: var(--ochre);
  margin-bottom: 0.35rem;
}

/* BASE FIRST, then the media query. A media query adds no specificity — put
   the base rule after it and it wins at every width, which is exactly how the
   phone captions went missing. */
.iface__list { display: none; margin-top: 1.5rem; padding-left: 0; list-style: none; }
.iface__list li { margin-bottom: 0.9rem; }
.iface__list .k { color: var(--ochre); letter-spacing: 0.1em; font-size: var(--t-small); display: block; }

@media (max-width: 46rem) {
  /* 34px dots on a 1600px image are unusable — the caption list replaces them */
  .hot { display: none; }
  .iface__list { display: block; }
  .iface__prompt { display: none; }   /* "press a mark" — there are no marks */
}


/* ── the moments ─────────────────────────────────────────────────────────── */

.moments__head { display: grid; grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); gap: clamp(1.5rem, 3vw, 3rem); margin-bottom: clamp(2.5rem, 5vw, 4rem); }
@media (max-width: 60rem) { .moments__head { grid-template-columns: 1fr; } }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: clamp(2rem, 3vw, 3rem);
}
/* four cards must not land 3 + 1 with an orphan on its own row */
.cards--four { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
@media (min-width: 60rem) and (max-width: 82rem) {
  .cards--four { grid-template-columns: repeat(2, 1fr); }
}

.card__name {
  font-family: var(--display);
  font-size: clamp(1.75rem, 2.4vw, 2.25rem);
  line-height: 1.15;
  margin: 0 0 0.35rem;
}
/* the recipe whispers in italic — the plugin's own convention, and the one
   synthesised slant the two-typeface system permits (Plex ships one weight,
   one style; the browser obliques it) */
.card__recipe { font-size: var(--t-small); font-style: italic; color: var(--ochre-ink); margin: 0 0 0.9rem; }
.card__body { margin: 0 0 1.25rem; color: var(--ink-60); }
.section--dark .card__body { color: var(--cream-60); }
.section--dark .card__recipe { color: var(--ochre); }

.card__sigil {
  display: block;                       /* an inline span has no size */
  width: 34px; height: 34px;
  background-color: var(--ink-60);      /* was --ink-40: measured too faint
                                           against the paper ground */
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  margin-bottom: 1rem;
  transition: background-color 140ms ease-out;
}
.section--dark .card__sigil { background-color: var(--cream-60); }

/* hover is an active state — the sigil may take the accent while a hand is on
   the card, and only then */
.card:hover .card__sigil { background-color: var(--ochre-ink); }
.section--dark .card:hover .card__sigil { background-color: var(--ochre); }
.card__sigil--none { visibility: hidden; }   /* jouset has no drawn plate */


/* ── the audio player ────────────────────────────────────────────────────── */
/* Each .player wraps a REAL <audio controls>. With JS off that is what you
   get: the browser's own player. JS replaces it with the one below. */

.player {
  display: flex;
  align-items: center;
  gap: 1rem;
  border-top: 1px solid var(--ink-14);
  padding-top: 1rem;
}
.section--dark .player { border-color: var(--cream-16); }
.player > audio { width: 100%; }

.player__btn {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border-radius: 999px;
  border: 1px solid currentColor;
  background: none;
  color: inherit;
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
  transition: background-color 140ms ease-out, color 140ms ease-out, border-color 140ms ease-out;
}
.player__btn:hover:not(:disabled) { color: var(--ochre-ink); border-color: var(--ochre); }
.section--dark .player__btn:hover:not(:disabled) { color: var(--ochre); }
.player__btn[aria-pressed="true"] { background: var(--ochre); border-color: var(--ochre); color: var(--on-ochre); }
.player__btn svg { width: 13px; height: 15px; fill: currentColor; }
.player__btn .pause { display: none; }
.player__btn[aria-pressed="true"] .play { display: none; }
.player__btn[aria-pressed="true"] .pause { display: block; }

/* a real range input, so the scrubber is keyboard-operable for free */
.player__track {
  flex: 1 1 auto;
  -webkit-appearance: none;
  appearance: none;
  background: none;
  height: 18px;
  margin: 0;
  cursor: pointer;
}
.player__track::-webkit-slider-runnable-track { height: 2px; background: var(--ink-40); }
.player__track::-moz-range-track { height: 2px; background: var(--ink-40); }
.section--dark .player__track::-webkit-slider-runnable-track { background: var(--cream-16); }
.section--dark .player__track::-moz-range-track { background: var(--cream-16); }
.player__track::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 10px; height: 10px; border-radius: 999px;
  background: var(--ochre-ink); border: 0; margin-top: -4px;
}
.player__track::-moz-range-thumb { width: 10px; height: 10px; border-radius: 999px; background: var(--ochre-ink); border: 0; }
.section--dark .player__track::-webkit-slider-thumb { background: var(--ochre); }
.section--dark .player__track::-moz-range-thumb { background: var(--ochre); }
.player__track::-moz-range-progress { height: 2px; background: var(--ochre-ink); }

.player__time { flex: 0 0 auto; font-size: var(--t-small); color: var(--ink-mute); font-variant-numeric: tabular-nums; }
.section--dark .player__time { color: var(--cream-60); }

/* the dead state — there is no recording for this moment yet. It is conveyed by
   a disabled control and the word "soon", NOT by dimming, which drove the label
   to 1.6:1 and made the honest message the least readable text on the page. */
.player__btn:disabled { cursor: not-allowed; border-color: var(--ink-40); color: var(--ink-mute); }
.section--dark .player__btn:disabled { border-color: var(--cream-16); color: var(--cream-60); }
.player.is-empty .player__track { visibility: hidden; }


/* ── the buy ─────────────────────────────────────────────────────────────── */

.buy { display: grid; grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); gap: clamp(1.5rem, 3vw, 3rem); }
@media (max-width: 60rem) { .buy { grid-template-columns: 1fr; } }
.buy__price { font-family: var(--display); font-size: clamp(2.5rem, 5vw, 4rem); margin: 0 0 1rem; }

.form { display: flex; gap: 0.75rem; max-width: 30rem; margin-top: 1.75rem; flex-wrap: wrap; }
.form input {
  flex: 1 1 14rem;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--ink-mute);     /* 5.1:1 — a visible boundary */
  padding: 0.7rem 0.15rem;
  font: inherit;
  color: inherit;
}
.form input::placeholder { color: var(--ink-mute); }
.form input:focus { border-color: var(--ochre-ink); }   /* NO outline:none — the
                                                           focus ring is the only
                                                           thing a keyboard user
                                                           has */
.foot .form input { border-color: var(--cream-60); color: var(--cream); }
.foot .form input::placeholder { color: var(--cream-60); }
.foot .form input:focus { border-color: var(--ochre); }

.btn {
  border: 1px solid currentColor;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: 0.08em;
  padding: 0.7rem 1.5rem;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 140ms ease-out, color 140ms ease-out, border-color 140ms ease-out;
}
.btn:hover { background: var(--ochre); border-color: var(--ochre); color: var(--on-ochre); }


/* ── specs — a row of small accordions ───────────────────────────────────── */

.specs {
  border-top: 1px solid var(--ink-14);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  column-gap: clamp(1.5rem, 3vw, 3rem);
}
.spec { border-bottom: 1px solid var(--ink-14); align-self: start; }
.spec > summary {
  list-style: none;
  cursor: pointer;
  padding: 1.35rem 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--t-small);
  letter-spacing: 0.12em;
  transition: color 140ms ease-out;
}
.spec > summary::-webkit-details-marker { display: none; }
.spec > summary:hover { color: var(--ochre-ink); }
.spec > summary::after { content: "+"; font-size: 1.1rem; color: var(--ink-mute); }
.spec[open] > summary::after { content: "–"; color: var(--ochre-ink); }
.spec__body { padding-bottom: 1.75rem; color: var(--ink-60); font-size: var(--t-small); }
.spec__body p { margin: 0 0 0.85em; }
.spec__body p:last-child { margin-bottom: 0; }
.spec__body ul { margin: 0; padding-left: 1.1rem; }
.spec__body li { margin-bottom: 0.4em; }


/* ── footer ──────────────────────────────────────────────────────────────── */

.foot { background: var(--dark); color: var(--cream); padding-block: var(--band); }
.foot__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: clamp(2rem, 4vw, 4rem); }

/* the ladder inverts on the dark ground */
.foot .muted, .foot .label, .foot .tiny { color: var(--cream-60); }

.foot__mark {
  display: block;
  width: 110px;
  aspect-ratio: 842 / 454;
  background: var(--cream);
  -webkit-mask: url("../../assets/ink/wordmark.png") no-repeat center / contain;
  mask: url("../../assets/ink/wordmark.png") no-repeat center / contain;
  margin-bottom: 1.5rem;
}
.foot a { color: var(--cream); }
/* ── elsewhere: the social row ────────────────────────────────────────────
   Text links in the site's own underline voice. A link with an empty href is
   not wired yet and stays hidden, so nothing dead ever ships; the "coming"
   note shows only while every link is still empty. */
.social { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--cream-16); }
.social__row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem 1.75rem;
}
.social__row .ulink { font-size: var(--t-small); letter-spacing: 0.08em; color: var(--cream-60); }
.social__row .ulink:hover { color: var(--ochre); }
.social__row a[href=""] { display: none; }
.social__row li:has(> a[href=""]) { display: none; }
.social__row:not(:has(a:not([href=""]))) { display: none; }
.social:has(.social__row a:not([href=""])) .social__empty { display: none; }

.foot__credit { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--cream-16); font-size: var(--t-small); color: var(--cream-60); }
.foot__credit p { margin: 0 0 0.35em; }

.totop { display: block; text-align: center; padding: 1.5rem; font-size: var(--t-small); letter-spacing: 0.14em; color: var(--ink-60); text-decoration: none; }
.totop:hover { color: var(--ochre-ink); }


/* ── the reveal ──────────────────────────────────────────────────────────── */
/* Sections arrive as you reach them: a short fade and a few pixels of rise,
   once, and then they stop. Nothing loops, nothing bounces, nothing re-plays
   on the way back up.

   The class is added by JS. If JS never runs, `.reveal` is never applied and
   everything is simply visible — the content is not hidden by default, which
   is the mistake that leaves no-JS visitors staring at a blank page. */

.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 640ms cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 640ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: opacity, transform;
}
.js .reveal.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
}

/* staggered arrival — inside a marked section, the cards and accordions land
   one after another, ~70ms apart, capped so a long row does not dawdle.
   Scoped to `.js` like everything else that hides: no script, no hiding. */
.js [data-stagger].reveal .card,
.js [data-stagger].reveal .spec {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 560ms cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 560ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.js [data-stagger].reveal.is-in .card,
.js [data-stagger].reveal.is-in .spec {
  opacity: 1;
  transform: none;
}
.js [data-stagger].reveal.is-in :is(.card, .spec):nth-child(2) { transition-delay: 70ms; }
.js [data-stagger].reveal.is-in :is(.card, .spec):nth-child(3) { transition-delay: 140ms; }
.js [data-stagger].reveal.is-in :is(.card, .spec):nth-child(4) { transition-delay: 210ms; }
.js [data-stagger].reveal.is-in :is(.card, .spec):nth-child(5) { transition-delay: 280ms; }
.js [data-stagger].reveal.is-in :is(.card, .spec):nth-child(n+6) { transition-delay: 350ms; }

@media (prefers-reduced-motion: reduce) {
  .js [data-stagger].reveal .card,
  .js [data-stagger].reveal .spec { opacity: 1; transform: none; transition: none; }
}


/* ── the scrubbed statements ─────────────────────────────────────────────── */
/* The two giant claims ink in word by word as the visitor scrolls through
   them — from the ladder's faintest step up to full ink, like a line being
   written. Driven by scroll position only: it moves when the reader moves,
   both directions, and holds still when they do. With no JS (or reduced
   motion) the words are simply ink. */

.js [data-scrub] .w {
  opacity: 0;                           /* the line does not exist until the
                                           reader moves — then it writes itself
                                           in, word by word. Deliberate (the
                                           owner's call): unrevealed words are
                                           ABSENT, not low-contrast. Covered
                                           for everyone else: aria-label holds
                                           the full sentence, reduced-motion
                                           shows full ink, no-JS never hides,
                                           and the failsafe lights everything
                                           if scroll events never arrive. */
  transition: opacity 160ms ease-out;
}
.js [data-scrub] .w.on { opacity: 1; }

/* "…in a room with a piano" — the letters of the last word are keys. When the
   word arrives, each dips down and comes back up in turn, once: five notes
   played left to right. Then stillness, like everything else here. */
[data-scrub] .kb { display: inline-block; }
.js [data-scrub] .w.on .kb {
  animation: key-press 520ms cubic-bezier(0.36, 0, 0.34, 1) both;
  animation-delay: calc(var(--k, 0) * 110ms + 120ms);
}
@keyframes key-press {
  0%   { translate: 0 0; }
  35%  { translate: 0 0.085em; }   /* the key goes down          */
  55%  { translate: 0 0.085em; }   /* it is held, just a breath  */
  100% { translate: 0 0; }         /* and released               */
}
@media (prefers-reduced-motion: reduce) {
  .js [data-scrub] .w.on .kb { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .js [data-scrub] .w { opacity: 1; transition: none; }
}


/* ── hero depth ──────────────────────────────────────────────────────────── */
/* As the hero scrolls away, the headline drifts up a touch faster than the
   photograph and quietly hands itself over. Scroll-scrubbed; set via custom
   properties from the one scroll handler. */

@media (prefers-reduced-motion: no-preference) {
  .js .hero__inner {
    transform: translateY(calc(var(--hero-p, 0) * -6vh));
    opacity: calc(1 - var(--hero-p, 0) * 1.1);
    will-change: transform, opacity;
  }
  /* the photograph settles on arrival: one slow ease from 1.10 to 1.04, then
     it stops. The 1.04 stays as head-room for the scroll parallax, which uses
     the separate `scale` channel so the two never fight. */
  .js .hero__media img {
    transform: translateY(calc(var(--hero-p, 0) * 2vh));
    animation: hero-settle 1600ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
    will-change: transform, scale;
  }
  @keyframes hero-settle {
    from { scale: 1.06; }
    to   { scale: 1.02; }
  }
  /* the words arrive a breath apart, once, and stop */
  .js .hero__title  { animation: hero-in 800ms cubic-bezier(0.22, 0.61, 0.36, 1) 120ms both; }
  .js .hero__sub    { animation: hero-in 800ms cubic-bezier(0.22, 0.61, 0.36, 1) 280ms both; }
  .js .hero__scroll { animation: hero-in 800ms cubic-bezier(0.22, 0.61, 0.36, 1) 440ms both; }
  @keyframes hero-in {
    from { opacity: 0; translate: 0 16px; }
  }
}


/* ── the lightbox — for the trailer, when there is one ───────────────────── */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 5vw, 4rem);
  background: rgba(12, 9, 7, 0.92);
  border: 0;
}
.lightbox::backdrop { background: rgba(12, 9, 7, 0.92); }
.lightbox[hidden] { display: none; }

.lightbox__frame { width: min(100%, 68rem); aspect-ratio: 16 / 9; background: #000; }
.lightbox__frame video,
.lightbox__frame iframe { width: 100%; height: 100%; border: 0; display: block; }

.lightbox__close {
  position: absolute;
  top: clamp(1rem, 3vw, 2rem);
  right: clamp(1rem, 3vw, 2rem);
  background: none;
  border: 1px solid var(--cream-60);
  border-radius: 999px;
  color: var(--cream);
  font: inherit;
  font-size: var(--t-small);
  letter-spacing: 0.08em;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.lightbox__close:hover { border-color: var(--ochre); color: var(--ochre); }


/* ── the persistent pill ─────────────────────────────────────────────────── */

.pill {
  position: fixed;
  right: clamp(1rem, 2.5vw, 2rem);
  bottom: clamp(1rem, 2.5vw, 2rem);
  z-index: 70;
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  background: var(--ochre);
  color: var(--on-ochre);
  text-decoration: none;
  border-radius: 999px;
  padding: 0.9rem 1.5rem;
  letter-spacing: 0.06em;
  box-shadow: 0 8px 30px rgba(22, 18, 14, 0.22);
  transition: transform 140ms ease-out;
}
.pill:hover { transform: translateY(-2px); }
.pill small { font-size: 0.8em; color: var(--on-ochre-2); }   /* 5.6:1, not an
                                                                 opacity, which
                                                                 measured 4.3:1 */
@media (prefers-reduced-motion: reduce) { .pill:hover { transform: none; } }
