/* ==========================================================================
   Vidash Music Ministry
   Vida Atanga, Kukore, Ghana

   Design concept: an issue of a printed worship journal.
   Signature device: call and response, drawn from Ghanaian gospel worship.
   Brand colours: red, white, blue-black (from her logo).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Brand, sampled directly from her logo file.

     Her exact red is 4.26:1 on the paper ground, which fails AA for body
     text, so the red is a three-step ramp. --red is her true logo colour and
     is used for fills, borders, focus rings and display type, where the bar
     is 3:1. Small text uses --red-text on light grounds and --red-lift on
     navy. Never set small text to --red. */
  --paper:      #FAFAF8;   /* page ground, a whisper off true white  */
  --white:      #FFFFFF;   /* cards and inset panels                 */
  --ink:        #1E2460;   /* her logo navy. 13.61:1 on paper        */
  --ink-2:      #3E4778;   /* secondary text on light. 8.45:1        */
  --red:        #EA1F25;   /* her exact logo red. Fills and large    */
  --red-text:   #D81920;   /* small text on light grounds. 4.93:1    */
  --red-deep:   #B01117;   /* hover and pressed. 6.84:1              */
  --red-lift:   #F86E72;   /* small text on the navy ground. 5.06:1  */

  --rule:       #E3E3E0;   /* hairlines on light grounds             */
  --dot:        #C7C5BE;   /* leader dots, needs to read at 1px      */
  --rule-dark:  #39417F;   /* hairlines on the navy ground           */

  /* The photographic grade is an SVG tritone, defined per page in
     <svg id="grade-def">. Its ramp, for reference:
       0.00  #1E2460  logo navy    shadows
       0.25  #613D3D               deep transition
       0.62  #C72933  toward red   midtones
       0.88  #F29994               bright red
       1.00  #FFFFFF  white        highlights                              */

  /* Type */
  --display: "Fraunces", Georgia, "Times New Roman", serif;
  --sans:    "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:    "JetBrains Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;

  /* Fluid scale. Ratio ~1.5 at the top end, tightening toward body size. */
  --t-masthead: clamp(3.2rem, 15.5vw, 15rem);
  --t-call:     clamp(2rem, 5.4vw, 4.4rem);
  --t-call-sm:  clamp(1.5rem, 3.2vw, 2.5rem);
  --t-lead:     clamp(1.0625rem, 1.35vw, 1.25rem);
  --t-body:     1rem;
  --t-small:    0.875rem;
  --t-data:     0.6875rem;

  /* Rhythm */
  --gut:   clamp(1.25rem, 4vw, 3.5rem);
  --band:  clamp(4rem, 9vw, 8.5rem);
  --measure: 62ch;

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; height: auto; }

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; }

button { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute;
  left: 0.5rem;
  top: -3rem;
  z-index: 200;
  background: var(--ink);
  color: var(--paper);
  padding: 0.6rem 1rem;
  font-size: var(--t-small);
  transition: top 0.2s var(--ease);
}
.skip:focus { top: 0.5rem; }

.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   3. Type roles

   One family, two opposite voices. The masthead runs the variable axes hard
   and flat; the call lines run them soft and wonky. Same face, different
   personality, which does more work than adding a second family would.
   -------------------------------------------------------------------------- */

.masthead,
.call,
.numeral,
.big-figure {
  font-family: var(--display);
}

/* Data only: dates, counts, volume numbers, the phone number. Never used as
   decoration above a heading. */
.data {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.call {
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 32;
  font-style: italic;
  font-weight: 400;
  font-size: var(--t-call);
  line-height: 1.02;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.call--sm { font-size: var(--t-call-sm); line-height: 1.08; }

/* The red rule that separates a call from its response. */
.call + .response,
.answer {
  position: relative;
  padding-top: 1.75rem;
  margin-top: 1.5rem;
  max-width: var(--measure);
  font-size: var(--t-lead);
  color: var(--ink-2);
}
.call + .response::before,
.answer::before {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 3.5rem;
  height: 2px;
  background: var(--red);
}

.response p + p, .answer p + p { margin-top: 1em; }

.lead { font-size: var(--t-lead); color: var(--ink-2); max-width: var(--measure); }

/* A standalone "continue reading" link. Not inside a sentence, so it does not
   get the WCAG inline-target exception and needs a real tap height. */
.more {
  display: inline-block;
  padding-block: 0.7rem;
  text-decoration: underline;
  text-underline-offset: 4px;
  font-size: var(--t-lead);
}
.more:hover { color: var(--red-text); }
.band--ink .more:hover { color: var(--red-lift); }

/* --------------------------------------------------------------------------
   4. Layout
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: 84rem;
  margin-inline: auto;
  padding-inline: var(--gut);
}

.band { padding-block: var(--band); }
.band--tight { padding-block: calc(var(--band) * 0.6); }

.band--ink {
  background: var(--ink);
  color: var(--paper);
}
.band--ink .data { color: rgba(250, 250, 248, 0.62); }
.band--ink .response,
.band--ink .answer,
.band--ink .lead { color: rgba(250, 250, 248, 0.78); }
.band--ink .rule { background: var(--rule-dark); }

/* Red text landing on the blue-black ground switches to the lifted red: the
   base red is 3.10:1 here, which fails AA at label sizes. Rules and borders
   keep the base red, since a 2px bar is not text. Text already set in paper
   stays paper, which measures ~14:1 and needs no help. */
.band--ink .numeral,
.band--ink .timeline__when { color: var(--red-lift); }

.band--white { background: var(--white); }

.rule { height: 1px; background: var(--rule); border: 0; margin: 0; }

/* --------------------------------------------------------------------------
   5. The grade

   A tritone, not a duotone, and that is the point: shadows resolve to
   blue-black, midtones to red, highlights to white, so all three of her logo
   colours live inside the photograph itself rather than only around it.

   Implemented as an SVG filter (see the <svg id="grade-def"> block at the top
   of every page) rather than with blend-mode layers. Layered `multiply` plus
   `screen` was tried first and could not hold the red: screening a mostly
   dark photograph with navy raises every channel's blue and washes the red
   out. feComponentTransfer maps each channel independently against a lookup
   table, so the tonal ramp is exact and does not depend on how bright the
   source happens to be.

   The grade lives in CSS, not baked into the files, so photographs added
   later inherit it automatically by being dropped into assets/img/.
   -------------------------------------------------------------------------- */

.duo {
  position: relative;
  overflow: hidden;
  background: var(--ink);
}
.duo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The grade is reserved for the page-top banners, where type sits on the
   photograph and the tritone carries the brand. Everywhere else she is shown
   in true colour: on a phone there is no hover to reveal it, and the grade
   flattened her skin tone in every portrait. */
.cover__media .duo img { filter: url(#grade); }

/* Scrim for type sitting over a photograph.

   Deliberately a real element, not a pseudo-element. `.duo` already owns both
   ::before and ::after for the grade, and a pseudo-element is a single slot:
   stacking `.duo .scrim` on one element would silently replace the grade's
   shadow layer and leak its blend mode onto the gradient. A class that owns a
   pseudo-element is not composable. */
.scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(14,21,36,0.78) 0%, rgba(14,21,36,0.15) 38%, rgba(14,21,36,0) 52%),
    linear-gradient(to top,    rgba(14,21,36,0.82) 0%, rgba(14,21,36,0.2) 34%, rgba(14,21,36,0) 56%);
}

/* --------------------------------------------------------------------------
   6. Ticker

   Content motion, not decoration: it carries her three real event names.
   -------------------------------------------------------------------------- */

.ticker {
  background: var(--ink);
  color: var(--paper);
  overflow: hidden;
  border-bottom: 1px solid var(--rule-dark);
}
.ticker__track {
  display: flex;
  width: max-content;
  animation: slide 42s linear infinite;
}
.ticker:hover .ticker__track,
.ticker:focus-within .ticker__track { animation-play-state: paused; }

.ticker__group {
  display: flex;
  flex-shrink: 0;
  padding-block: 0.6rem;
}
.ticker__item {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  padding-inline: 1.6rem;
  white-space: nowrap;
  color: rgba(250, 250, 248, 0.82);
}
.ticker__item b { color: var(--red-lift); font-weight: 400; }

@keyframes slide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* --------------------------------------------------------------------------
   7. Header
   -------------------------------------------------------------------------- */

/* Above the drawer (z-index 90), not below it. The burger is the only way a
   phone user closes the menu by tapping, so it has to stay reachable while
   the drawer is open. */
.site-head {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--paper);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s var(--ease);
}
.site-head.is-stuck { border-bottom-color: var(--rule); }

.site-head__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 4.25rem;
}

/* Wordmark. Swap the inner markup for the real logo when the file arrives;
   nothing else in the stylesheet depends on its internals. */
.wordmark {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  flex-shrink: 0;
  padding-block: 0.5rem;
}
/* Her supplied logo.

   The lockup is stacked, mark above script wordmark, at 1.62:1. Dropped into
   a slim header it shrinks its own wordmark to about 67px wide, where
   "Vidash Music" is unreadable. So the header shows her mark alone, extracted
   from the same file, with the name set in type beside it; the footer, which
   has vertical room, shows the whole lockup.

   Two variants of each: the plain files keep her navy, and the -reverse files
   lift the navy to paper for the navy grounds. */
.wordmark__mark {
  height: 2.5rem;
  width: auto;
  display: block;
  flex: 0 0 auto;
}
.wordmark__text {
  display: flex;
  flex-direction: column;
  line-height: 1;
}
.wordmark__logo {
  height: 4.5rem;
  width: auto;
  display: block;
}
.wordmark--foot .wordmark__logo { height: 5rem; }

.wordmark__name {
  font-family: var(--display);
  font-variation-settings: "SOFT" 0, "WONK" 0, "opsz" 40;
  font-weight: 800;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  line-height: 1.05;
}
.wordmark__dot { color: var(--red-text); }
.wordmark__sub {
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-top: 0.2rem;
}

.nav { display: flex; gap: clamp(1rem, 2vw, 2rem); }
.nav a {
  text-decoration: none;
  font-size: var(--t-small);
  letter-spacing: 0.01em;
  padding-block: 0.8rem;
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.nav a:hover { border-bottom-color: var(--red); }
.nav a[aria-current="page"] { border-bottom-color: var(--ink); }

.head-actions { display: flex; align-items: center; gap: 0.75rem; }

/* Actions are pills; everything editorial is squared. The two shapes tell
   "do this" apart from "read this". */
/* gap is 0 because every pill is text only: a flex gap between two words
   reads as a double space. The responsive label supplies its own word space
   below. */
/* The fill, not just the shape, has to clear 4.5:1 because the label sits on
   it in white. Her exact red is 4.45 against white, which misses by 0.05, so
   filled buttons use the deepened red. Her true red stays on every rule,
   border, focus ring and the logo itself. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 0;
  background: var(--red-text);
  color: var(--white);
  text-decoration: none;
  font-size: var(--t-small);
  font-weight: 500;
  padding: 0.6rem 1.25rem;
  border: 1px solid var(--red-text);
  border-radius: 999px;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.pill:hover { background: var(--red-deep); border-color: var(--red-deep); }

.pill--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.pill--ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.band--ink .pill--ghost { color: var(--paper); border-color: rgba(250,250,248,0.5); }
.band--ink .pill--ghost:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }

.pill--lg { padding: 0.85rem 1.75rem; font-size: 1rem; }

/* One word space, since the pill itself contributes none. */
.pill__long { margin-left: 0.3em; }

/* Burger */
.burger {
  display: none;
  background: none;
  border: 0;
  /* Explicit box rather than padding maths: the three bars plus padding kept
     landing a few pixels under the 44px guidance. */
  width: 44px;
  height: 44px;
  padding: 0;
  place-items: center;
  cursor: pointer;
}
.burger span {
  display: block;
  width: 22px;
  height: 1.5px;
  background: var(--ink);
  transition: transform 0.25s var(--ease), opacity 0.2s var(--ease);
}
.burger span + span { margin-top: 5px; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* Mobile drawer */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--paper);
  /* --head-h is measured and set by site.js: the ticker plus header height
     varies with the ticker's wrapping, so a fixed value clips the first
     link under the header on some widths. */
  padding: calc(var(--head-h, 6.75rem) + 1rem) var(--gut) var(--gut);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  transform: translateY(-100%);
  transition: transform 0.4s var(--ease);
}
.drawer[data-open="true"] { transform: translateY(0); }

/* Scoped to the nav links only. An unscoped `.drawer a` also caught the
   phone number in the footer and set it in 3rem italic serif. */
.drawer > a {
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 32;
  font-style: italic;
  font-size: clamp(2rem, 9vw, 3rem);
  line-height: 1.25;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-block: 0.5rem;
}
.drawer > a[aria-current="page"] { color: var(--red-text); }

.drawer__foot { margin-top: auto; padding-top: 2rem; }
.drawer__foot a {
  text-decoration: none;
  display: inline-block;
  padding-block: 0.8rem;   /* was a 14px-tall phone link */
}
.drawer__foot a:hover { color: var(--red-text); }

/* --------------------------------------------------------------------------
   8. Cover

   The one bold element on the site. The masthead spaces its letters edge to
   edge, which is how a magazine cover actually works, and it puts brand
   recognition first.
   -------------------------------------------------------------------------- */

.cover {
  position: relative;
  min-height: min(86svh, 52rem);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ink);
  color: var(--paper);
}
.cover__media { position: absolute; inset: 0; }
.cover__media .duo { position: absolute; inset: 0; }
/* Her face sits low and right of centre so the masthead crosses the frame
   above it rather than through it. */
.cover__media img { width: 100%; height: 100%; object-fit: cover; object-position: 54% 42%; }

.cover__in {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  flex: 1;
  padding-block: clamp(1.5rem, 3vw, 2.75rem) clamp(2rem, 4vw, 3.25rem);
  gap: 2rem;
}

.masthead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-variation-settings: "SOFT" 0, "WONK" 0, "opsz" 144;
  font-weight: 900;
  font-size: var(--t-masthead);
  line-height: 0.82;
  margin: 0;
  color: var(--paper);
}
.masthead span { display: block; }

/* A word space inside the per-letter masthead. A span containing a literal
   space collapses to nothing, which is how "Invite Vida" rendered as
   "INVITEVIDA". This gives the gap an explicit width instead. */
.masthead__gap { width: 0.3em; }

.cover__rule {
  height: 3px;
  background: var(--red);
  margin-top: clamp(0.5rem, 1.2vw, 1rem);
  transform-origin: left;
}

.cover__meta {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.75rem;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(250, 250, 248, 0.8);
}

.cover__foot {
  margin-top: auto;
  display: grid;
  gap: clamp(1.5rem, 4vw, 3rem);
  grid-template-columns: 1fr;
  align-items: end;
}
.cover .call { color: var(--paper); }
.cover .response { color: rgba(250, 250, 248, 0.86); }

.cover__cue {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding-block: 0.8rem;   /* was an 18px-tall target */
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(250, 250, 248, 0.8);
}
.cover__cue:hover { color: var(--paper); }
/* The only arrow on the site. It survives because here it literally means
   "further down the page". */
.cover__cue i { font-style: normal; animation: nudge 2.6s var(--ease) infinite; }

@keyframes nudge {
  0%, 72%, 100% { transform: translateY(0); }
  82%           { transform: translateY(4px); }
}

/* Orchestrated load. One moment, on the cover only. */
.cover [data-anim] { opacity: 0; }
.cover.is-ready .masthead span {
  animation: rise 0.85s var(--ease) both;
  animation-delay: calc(var(--i) * 55ms);
}
.cover.is-ready .cover__rule {
  animation: draw 0.7s var(--ease) 0.45s both;
}
.cover.is-ready [data-anim="meta"] { animation: fade 0.6s var(--ease) 0.72s both; }
.cover.is-ready [data-anim="call"] { animation: rise 0.9s var(--ease) 0.86s both; }
.cover.is-ready [data-anim="cue"]  { animation: fade 0.6s var(--ease) 1.15s both; }

@keyframes rise {
  from { opacity: 0; transform: translateY(0.5em); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Interior page headers reuse the cover shell at reduced height. The gap
   closes to 1rem: on the home cover the space is the point, here it would
   just be a hole. */
.cover--page { min-height: min(48svh, 28rem); }
.cover--page .cover__in { gap: 1rem; }
.cover--page .masthead {
  font-size: clamp(2.4rem, 8.5vw, 6.5rem);
  justify-content: flex-start;
  letter-spacing: -0.01em;
}
.cover--page .masthead span { display: inline; }
.cover--page .cover__foot { margin-top: auto; }

/* Interior covers are half the height of the home cover, so the same crop
   pulled her face up behind the masthead. Sitting the frame lower keeps the
   type over background rather than over her. */
.cover--page .cover__media img { object-position: 50% 30%; }

/* --------------------------------------------------------------------------
   9. Contents

   Numbered because a table of contents genuinely is an ordered sequence.
   This is the only numbered list on the site.
   -------------------------------------------------------------------------- */

.contents { border-top: 1px solid var(--rule); }
.contents li { border-bottom: 1px solid var(--rule); }

.contents a {
  display: grid;
  grid-template-columns: 3rem auto minmax(1.5rem, 1fr) auto;
  align-items: baseline;
  gap: clamp(0.5rem, 1.5vw, 1.25rem);
  padding-block: clamp(1rem, 2vw, 1.5rem);
  text-decoration: none;
  transition: background-color 0.2s var(--ease);
}
.contents a:hover { background: var(--white); }

/* The leader rule. It fills the gap between a title and its note with
   something that means "these two belong together", which is the whole
   reason a printed contents page uses one. */
.contents a::after {
  content: "";
  order: 2;
  align-self: center;
  border-bottom: 1px dotted var(--dot);
  transform: translateY(0.15em);
}
.contents__note { order: 3; }


.numeral {
  font-variation-settings: "SOFT" 0, "WONK" 0, "opsz" 24;
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--red-text);
  letter-spacing: 0.06em;
}

.contents__name {
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 40;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2.6rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.contents a:hover .contents__name { color: var(--red-text); }

.contents__note {
  font-size: var(--t-small);
  color: var(--ink-2);
  text-align: right;
  max-width: 26ch;
}
/* On a phone the four-column row collapsed: the leader rule shrank to a
   meaningless 20px stub and the note wrapped onto three lines. Below 44rem
   the note sits under the title and the leader is dropped, which is what a
   printed contents page does in a narrow measure. */
@media (max-width: 43.99rem) {
  .contents a {
    grid-template-columns: 2.25rem minmax(0, 1fr);
    row-gap: 0.3rem;
  }
  .contents a::after { display: none; }
  .contents__note {
    grid-column: 2;
    text-align: left;
    max-width: none;
  }
}


/* --------------------------------------------------------------------------
   10. Spreads: two-column editorial blocks
   -------------------------------------------------------------------------- */

/* Centre, not start.

   With align-items:start the shorter column hugged the top and left a void
   as tall as the difference: about 320px of nothing under the release text
   on the home page. Centring splits any remaining difference above and
   below, which reads as intentional breathing room rather than a gap. */
.spread {
  display: grid;
  gap: clamp(2rem, 5vw, 4.5rem);
  grid-template-columns: 1fr;
  align-items: center;
}

/* Stacked, a 3:4 portrait at full width is around 930px tall and swamps the
   section. Cap it and centre it until the two columns sit side by side. */
@media (max-width: 55.99rem) {
  .spread__figure { max-width: 30rem; margin-inline: auto; width: 100%; }
}
.spread--figure-right { grid-template-columns: 1fr; }

.spread__figure { position: relative; }
.spread__figure .duo { aspect-ratio: 3 / 4; }
.spread__figure figcaption {
  margin-top: 0.75rem;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* Metadata strip. Vertical hairlines, real data only. */
.strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.strip > div {
  padding: 1.1rem clamp(0.75rem, 2vw, 1.5rem);
  border-left: 1px solid var(--rule);
}
.strip > div:first-child { border-left: 0; padding-left: 0; }
.strip dt {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 0.35rem;
}
.strip dd { margin: 0; font-size: var(--t-small); }
.band--ink .strip { border-color: var(--rule-dark); }
.band--ink .strip > div { border-left-color: var(--rule-dark); }
.band--ink .strip dt { color: rgba(250,250,248,0.62); }

/* --------------------------------------------------------------------------
   11. On stage

   Squared photo frames on the blue-black ground, separated by hairlines.
   No shadows, no matching radii, nothing that reads as a card kit.
   -------------------------------------------------------------------------- */

.stage {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  grid-template-columns: 1fr;
  margin-top: clamp(2rem, 5vw, 3.5rem);
}
.stage__item { display: flex; flex-direction: column; }
.stage__item .duo { aspect-ratio: 4 / 5; }
.stage__body {
  padding-top: 1rem;
  margin-top: 1rem;
  border-top: 1px solid var(--rule-dark);
}
.stage__name {
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 32;
  font-style: italic;
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1.15;
}
.stage__where { margin-top: 0.4rem; font-size: var(--t-small); color: rgba(250,250,248,0.72); }
/* Not red. Red on blue-black is 2.20:1, which is legal for display type and
   borders but not for an 11px label. Paper at 62% keeps it above 7:1 and
   still reads as secondary. */
.stage__year {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(250, 250, 248, 0.62);
  margin-top: 0.6rem;
  display: block;
}

/* --------------------------------------------------------------------------
   12. Releases
   -------------------------------------------------------------------------- */

.tracks { border-top: 1px solid var(--rule); }
.tracks li { border-bottom: 1px solid var(--rule); }
/* Stacked below 40rem: side by side, the platform pills squeezed the gloss
   until it clipped at around 414px. */
.tracks__row {
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
  gap: 0.85rem;
  padding-block: clamp(0.9rem, 1.8vw, 1.35rem);
}
@media (min-width: 40rem) {
  .tracks__row {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 1rem;
  }
}
.tracks__title {
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 40;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.35rem, 2.8vw, 2rem);
  line-height: 1.15;
}
.tracks__gloss { font-size: var(--t-small); color: var(--ink-2); margin-top: 0.15rem; }
.tracks__meta { display: flex; align-items: center; gap: clamp(0.75rem, 2vw, 1.75rem); }

.plat { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.plat a {
  font-size: 0.8125rem;
  text-decoration: none;
  padding: 0.6rem 0.95rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-2);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
  white-space: nowrap;
}
.plat a:hover { color: var(--red-text); border-color: var(--red); }

/* The releases carousel put these pills on the blue-black band, where the
   light-ground colours measured 1.45:1 and the pale border read as a bug. */
.band--ink .plat a {
  color: rgba(250, 250, 248, 0.85);
  border-color: var(--rule-dark);
}
.band--ink .plat a:hover {
  color: var(--ink);
  background: var(--paper);
  border-color: var(--paper);
}

/* --------------------------------------------------------------------------
   13. Video

   Poster frames from her own photography. A real YouTube id dropped into
   data-youtube turns any card live; no invented ids that would render
   "video unavailable".
   -------------------------------------------------------------------------- */

.videos {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem);
  grid-template-columns: 1fr;
  margin-top: clamp(2rem, 5vw, 3rem);
}
.video {
  position: relative;
  display: block;
  text-decoration: none;
  background: none;
  border: 0;
  padding: 0;
  width: 100%;
  text-align: left;
  cursor: pointer;
}
/* The media area is the positioning context, not the whole card. Anchored to
   .video the play button drifted down over the title, because the card also
   contains the text block beneath the image. */
.video .duo { aspect-ratio: 16 / 9; position: relative; }
.video__play {
  position: absolute;
  left: 1rem; bottom: 1rem;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 3.25rem; height: 3.25rem;
  border-radius: 999px;
  background: var(--red-text);
  color: var(--white);
  transition: transform 0.25s var(--ease), background-color 0.2s var(--ease);
}
.video:hover .video__play { transform: scale(1.08); background: var(--red-deep); }
.video__play svg { width: 14px; height: 14px; margin-left: 2px; }
/* The card is a <button>, so its inner labels are spans. They need explicit
   block display or the title and note run together on one line. */
.video__body { display: block; padding-top: 0.9rem; }
.video__title {
  display: block;
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 32;
  font-style: italic;
  font-weight: 400;
  font-size: 1.35rem;
  line-height: 1.2;
}
.video__note {
  display: block;
  font-size: var(--t-small);
  color: var(--ink-2);
  margin-top: 0.25rem;
}
.band--ink .video__note { color: rgba(250,250,248,0.72); }

.video--lead .duo { aspect-ratio: 16 / 9; }
.video--lead .video__title { font-size: clamp(1.6rem, 3.2vw, 2.4rem); }

/* --------------------------------------------------------------------------
   14. Photo rail

   True colour, like every photograph outside the page-top banners.
   -------------------------------------------------------------------------- */

.rail {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 1rem;
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
  scrollbar-width: thin;
}
.rail__item {
  flex: 0 0 clamp(13rem, 34vw, 20rem);
  scroll-snap-align: start;
  text-decoration: none;
}
.rail__item .duo { aspect-ratio: 3 / 4; }
.rail__cap {
  margin-top: 0.6rem;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   15. Gallery

   True colour. This is where a visitor came to actually look at her, so the
   grade steps out of the way.
   -------------------------------------------------------------------------- */

/* Columns, not grid. Her photographs are a mix of 3:4 portrait and 3:2
   landscape, and a row-based grid leaves a hole beside every landscape frame.
   CSS columns let each image keep its true proportions and pack tightly, so
   nothing is cropped and nothing is left hanging. */
/* .shot sets display:block, and an author rule beats the browser's built-in
   [hidden] { display: none }. The filter set .hidden on every tile it wanted
   out and none of them ever disappeared. */
.shot[hidden] { display: none; }

.grid-photos {
  column-count: 1;
  column-gap: clamp(0.75rem, 2vw, 1.25rem);
}
.grid-photos .shot {
  break-inside: avoid;
  margin-bottom: clamp(0.75rem, 2vw, 1.25rem);
}
.shot {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--ink);
  cursor: zoom-in;
  overflow: hidden;
}
.shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.shot:hover img { transform: scale(1.03); }
/* No aspect-ratio here: in a column layout each frame keeps its own. */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 120;
  background: rgba(14, 21, 36, 0.96);
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
}
.lightbox[hidden] { display: none; }
.lightbox img {
  max-width: 100%;
  max-height: 82svh;
  width: auto;
  object-fit: contain;
}
.lightbox__cap {
  margin-top: 1rem;
  text-align: center;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(250, 250, 248, 0.72);
}
.lightbox__btn {
  position: absolute;
  background: none;
  border: 1px solid rgba(250, 250, 248, 0.35);
  color: var(--paper);
  width: 2.75rem; height: 2.75rem;
  border-radius: 999px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background-color 0.2s var(--ease);
}
.lightbox__btn:hover { background: rgba(250, 250, 248, 0.14); }
.lightbox__close { top: 1rem; right: 1rem; }
.lightbox__prev { left: 1rem; top: 50%; transform: translateY(-50%); }
.lightbox__next { right: 1rem; top: 50%; transform: translateY(-50%); }

/* --------------------------------------------------------------------------
   16. Invite and form
   -------------------------------------------------------------------------- */

.contact-list { display: grid; gap: 1.25rem; margin-top: 2rem; }
.contact-list a {
  font-family: var(--mono);
  font-size: clamp(1rem, 2.4vw, 1.35rem);
  letter-spacing: 0.04em;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  /* 29px was too small to tap reliably; this brings the row to ~44px. */
  padding-block: 0.55rem 0.35rem;
  justify-self: start;
}
.contact-list a:hover { color: var(--red-text); }
.band--ink .contact-list a:hover { color: var(--paper); }

.form { display: grid; gap: 1.25rem; margin-top: 2.5rem; max-width: 34rem; }
.field { display: grid; gap: 0.4rem; }
.field label { font-size: var(--t-small); font-weight: 500; }
.field input,
.field select,
.field textarea {
  font: inherit;
  font-size: var(--t-body);
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 0.7rem 0.85rem;
  width: 100%;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: 2px solid var(--red);
  outline-offset: 1px;
  border-color: var(--red);
}
.field textarea { resize: vertical; min-height: 7rem; }
.field__hint { font-size: 0.8125rem; color: var(--ink-2); }

.field__error {
  font-size: 0.8125rem;
  color: var(--red-deep);
  font-weight: 500;
}
.field__error[hidden] { display: none; }
.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea { border-color: var(--red); }

.form__note {
  font-size: var(--t-small);
  color: var(--ink-2);
  border-left: 2px solid var(--red);
  padding-left: 1rem;
}
.form__status { font-size: var(--t-small); font-weight: 500; }

.or {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-block: 2.5rem;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.or::before, .or::after { content: ""; height: 1px; background: var(--rule); flex: 1; }

/* --------------------------------------------------------------------------
   17. Timeline

   A genuine chronology, so the years carry the structure. Not decorative
   numbering: these are the actual dates.
   -------------------------------------------------------------------------- */

.timeline { border-top: 1px solid var(--rule); margin-top: clamp(2rem, 5vw, 3rem); }
.timeline li {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem 2rem;
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  border-bottom: 1px solid var(--rule);
}
.timeline__when {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--red-text);
  padding-top: 0.4rem;
}
.timeline__what {
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 40;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.35rem, 2.8vw, 2rem);
  line-height: 1.15;
}
.timeline__body { font-size: var(--t-body); color: var(--ink-2); margin-top: 0.5rem; max-width: 54ch; }

/* --------------------------------------------------------------------------
   18. Footer
   -------------------------------------------------------------------------- */

/* Every page ends with an ink invite band immediately above this ink footer,
   and with no edge between them the two merged into one undifferentiated
   dark block. The hairline restores the boundary. */
.site-foot {
  background: var(--ink);
  color: var(--paper);
  border-top: 1px solid var(--rule-dark);
  padding-block: clamp(3rem, 6vw, 5rem) 2rem;
}
.site-foot .wordmark__name { color: var(--paper); font-size: 2rem; }
.site-foot .wordmark__sub { color: rgba(250, 250, 248, 0.6); }

.foot-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 4rem);
}
.foot-head {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(250, 250, 248, 0.55);
  margin-bottom: 1rem;
}
.foot-list { display: grid; gap: 0.15rem; }
.foot-list a {
  text-decoration: none;
  font-size: var(--t-small);
  color: rgba(250, 250, 248, 0.85);
  display: inline-block;
  padding-block: 0.6rem;   /* rows were 22px tall */
}
.foot-list span { padding-block: 0.6rem; display: inline-block; }
.foot-list a:hover { color: var(--paper); text-decoration: underline; text-underline-offset: 3px; }

.foot-base {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule-dark);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  justify-content: space-between;
  font-size: 0.8125rem;
  color: rgba(250, 250, 248, 0.55);
}
.foot-base a { color: inherit; }

/* Footer, single column.

   Below 56rem the four columns stack, and a stack of left-aligned blocks in
   a full-width dark band reads as unfinished: everything hugs the left edge
   with a wide empty gutter to its right. Centring the stack gives the band a
   centre of gravity. It reverts to left alignment the moment the columns sit
   side by side, where left alignment is correct again.

   The wordmark is a flex row and the lists are grids, so each needs its own
   centring instruction; text-align alone does not move them. */
@media (max-width: 55.99rem) {
  /* The floating back-to-top button sits over the bottom-right corner, so
     the footer reserves room for it. Without this it covers the photography
     credit on a phone. */
  .site-foot { text-align: center; padding-bottom: 5.5rem; }
  .foot-grid { justify-items: center; }
  .foot-grid > div { width: 100%; }
  .site-foot .wordmark { justify-content: center; }
  .wordmark--foot .wordmark__logo { height: 4.25rem; margin-inline: auto; }
  .site-foot .foot-grid > div:first-child p { margin-inline: auto; }
  .foot-list { justify-items: center; }
  .foot-base { justify-content: center; text-align: center; }
  .foot-base span { flex: 0 0 100%; }
}

/* --------------------------------------------------------------------------
   19. Scroll reveal

   Same attribute API as the AOS library, so the markup reads familiarly and
   the real library could replace this without touching any HTML.

   The hidden starting state is gated behind html[data-aos-ready], which the
   script sets once it is running. Without JS that attribute never appears
   and nothing is ever hidden: the page simply renders.
   -------------------------------------------------------------------------- */

html[data-aos-ready] [data-aos] {
  opacity: 0;
  transition-property: opacity, transform;
  transition-duration: 700ms;
  transition-timing-function: var(--ease);
}

html[data-aos-ready] [data-aos="fade-up"]    { transform: translateY(26px); }
html[data-aos-ready] [data-aos="fade-down"]  { transform: translateY(-26px); }
html[data-aos-ready] [data-aos="fade-right"] { transform: translateX(-32px); }
html[data-aos-ready] [data-aos="fade-left"]  { transform: translateX(32px); }
html[data-aos-ready] [data-aos="zoom-in"]    { transform: scale(0.94); }
html[data-aos-ready] [data-aos="zoom-out"]   { transform: scale(1.06); }
html[data-aos-ready] [data-aos="reveal-up"]  { transform: translateY(40px) scale(0.985); }

html[data-aos-ready] [data-aos].aos-in {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   20. Scroll progress, back to top
   -------------------------------------------------------------------------- */

.progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 110;
  pointer-events: none;
}
.progress__bar {
  height: 100%;
  width: 100%;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left;
}

/* Red, not blue-black. The button floats over whatever band happens to be
   under it, and an ink button sitting on an ink band disappears entirely.
   Red holds ~3.1:1 against ink and ~5.9:1 against paper, which clears the
   3:1 that WCAG asks of a non-text control on both, and the white ring
   guarantees an edge on the darkest frames. */
.to-top {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 95;
  width: 3rem; height: 3rem;
  display: grid;
  place-items: center;
  border: 1px solid rgba(250, 250, 248, 0.28);
  border-radius: 999px;
  background: var(--red-text);
  color: var(--white);
  cursor: pointer;
  opacity: 0;
  transform: translateY(12px) scale(0.9);
  pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease),
              background-color 0.2s var(--ease);
}
.to-top.is-on { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { background: var(--red-deep); }
.to-top svg { width: 14px; height: 14px; }

/* Set by the drawer and by the lightbox. */
body.is-locked { overflow: hidden; }

/* --------------------------------------------------------------------------
   21. Carousel

   Progressive enhancement: the viewport is a plain scrollable row until the
   script adds .is-live, at which point transform takes over. With no JS it
   stays swipeable and every slide is still reachable.
   -------------------------------------------------------------------------- */

/* The carousel replaced .videos and .stage, both of which carried a top
   margin, so without this the response paragraph above sits directly on the
   first image. */
.carousel {
  position: relative;
  margin-top: clamp(2rem, 5vw, 3rem);
}

.carousel__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.carousel.is-live .carousel__viewport {
  overflow: hidden;
  scroll-snap-type: none;
  cursor: grab;
  outline-offset: 4px;
}
.carousel.is-live.is-dragging .carousel__viewport { cursor: grabbing; }

.carousel__track {
  display: flex;
  gap: clamp(1rem, 2.4vw, 1.75rem);
  padding-bottom: 0.25rem;
}
.carousel.is-live .carousel__track {
  transition: transform 620ms var(--ease);
  touch-action: pan-y;
}
.carousel.is-dragging .carousel__track { transition: none; }

.carousel__slide {
  flex: 0 0 var(--slide, 86%);
  scroll-snap-align: start;
  min-width: 0;
}

/* Slides off the current page dim, so the viewport edge reads as a fold
   rather than as a crop. */
.carousel.is-live .carousel__slide {
  opacity: 0.42;
  transition: opacity 500ms var(--ease);
}
.carousel.is-live .carousel__slide.is-current { opacity: 1; }

.carousel__nav {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.5rem;
}
.carousel__prev,
.carousel__next {
  width: 2.75rem; height: 2.75rem;
  flex: 0 0 auto;          /* stops them squeezing to 27px at 320px */
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease),
              color 0.2s var(--ease), opacity 0.2s var(--ease);
}
.carousel__prev:hover:not(:disabled),
.carousel__next:hover:not(:disabled) {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
.carousel__prev:disabled,
.carousel__next:disabled { opacity: 0.3; cursor: default; }
.carousel__prev svg, .carousel__next svg { width: 13px; height: 13px; }

.band--ink .carousel__prev,
.band--ink .carousel__next { border-color: var(--rule-dark); color: var(--paper); }
.band--ink .carousel__prev:hover:not(:disabled),
.band--ink .carousel__next:hover:not(:disabled) {
  background: var(--paper); color: var(--ink); border-color: var(--paper);
}

.carousel__dots {
  display: flex;
  gap: 0.4rem;
  margin-left: auto;
  align-items: center;
}

/* On a phone the back-to-top button is pinned bottom-right and lands exactly
   on top of these. Tuck them in beside the arrows instead. */
@media (max-width: 47.99rem) {
  .carousel__dots { margin-left: 0.5rem; }
  /* Reserve the floating button's footprint so a long dot row can never run
     underneath it, at any scroll position. */
  .carousel__nav { flex-wrap: wrap; padding-right: 4rem; }
}

/* Every slide already fits, so there is nothing to page through. Hiding the
   controls beats showing an inert dot beside two disabled arrows. */
.carousel.is-static .carousel__nav { display: none; }
.carousel.is-static .carousel__slide { opacity: 1; }
.carousel.is-static .carousel__viewport { cursor: default; }
/* The visible bar stays 3px, but the button is 44px tall.
   background-clip: content-box paints only inside the padding, so the hit
   area grows without the mark growing. A 3px-tall tap target is unusable on
   a phone; 44px is the platform guidance. */
.carousel__dot {
  width: 1.75rem;
  height: 3px;
  box-sizing: content-box;
  padding: 20px 0;
  border: 0;
  background-color: var(--rule);
  background-clip: content-box;
  cursor: pointer;
  transition: background-color 0.25s var(--ease), transform 0.25s var(--ease);
}
.carousel__dot:hover { background-color: var(--ink-2); }
.carousel__dot.is-on {
  background-color: var(--red-text);
  transform: scaleY(2);
}
.band--ink .carousel__dot { background-color: var(--rule-dark); }
.band--ink .carousel__dot.is-on { background-color: var(--red-lift); }

/* --------------------------------------------------------------------------
   22. Hero slider

   Crossfade, not slide: the masthead sits above the photograph, and sliding
   an image beneath fixed type reads as a fault rather than a transition.
   -------------------------------------------------------------------------- */

.hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1200ms var(--ease);
}
.hero__slide.is-on { opacity: 1; }

/* A slow drift on the active frame. These are stills, so the only way the
   cover moves at all is if the frame does. */
.hero__slide img {
  transform: scale(1.07);
  transition: transform 10s linear;
}
.hero__slide.is-on img { transform: scale(1); }

.hero__dots {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.25rem;
  margin-left: -0.25rem;
}
.hero__dot {
  width: 2rem;
  height: 3px;
  box-sizing: content-box;
  padding: 21px 0;
  border: 0;
  background-color: rgba(250, 250, 248, 0.32);
  background-clip: content-box;
  cursor: pointer;
  transition: background-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.hero__dot:hover { background-color: rgba(250, 250, 248, 0.7); }
.hero__dot.is-on {
  background-color: var(--red-lift);
  transform: scaleY(2);
}

/* --------------------------------------------------------------------------
   23. Tally

   Counts only what is countable from the site's own contents: releases,
   photographs, documented events. No reach or attendance figures, which
   would be claims about her ministry rather than descriptions of this site.
   -------------------------------------------------------------------------- */

.tally {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
  margin-top: clamp(2rem, 5vw, 3rem);
}
.tally__n {
  display: block;
  font-family: var(--display);
  font-variation-settings: "SOFT" 0, "WONK" 0, "opsz" 96;
  font-weight: 800;
  font-size: clamp(2.75rem, 7vw, 4.5rem);
  line-height: 0.9;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
/* Letterspaced uppercase mono overflowed its column at 320-360px. Allowing
   a wrap and easing the tracking keeps the label inside its box. */
.tally__l {
  display: block;
  margin-top: 0.6rem;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
  overflow-wrap: break-word;
}
.band--ink .tally__l { color: rgba(250, 250, 248, 0.62); }
.band--ink .tally__n { color: var(--paper); }

/* --------------------------------------------------------------------------
   24. Accordion
   -------------------------------------------------------------------------- */

.acc { border-top: 1px solid var(--rule); margin-top: clamp(2rem, 5vw, 3rem); }
.acc__item { border-bottom: 1px solid var(--rule); }

.acc__head {
  width: 100%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: clamp(1.1rem, 2.2vw, 1.6rem) 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font-family: var(--display);
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 32;
  font-style: italic;
  font-size: clamp(1.2rem, 2.4vw, 1.6rem);
  line-height: 1.2;
  color: inherit;
  transition: color 0.2s var(--ease);
}
.acc__head:hover { color: var(--red-text); }

.acc__sign {
  flex: 0 0 auto;
  position: relative;
  width: 1rem; height: 1rem;
  margin-top: 0.3rem;
}
.acc__sign::before,
.acc__sign::after {
  content: "";
  position: absolute;
  background: var(--red);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.acc__sign::before { inset: 45% 0 45% 0; }
.acc__sign::after  { inset: 0 45% 0 45%; }
.acc__item.is-open .acc__sign::after { transform: scaleY(0); opacity: 0; }

.acc__panel { padding-bottom: clamp(1.1rem, 2.2vw, 1.6rem); }
.acc__panel p { max-width: 60ch; color: var(--ink-2); }
.acc__panel p + p { margin-top: 0.75em; }

.band--ink .acc { border-top-color: var(--rule-dark); }
.band--ink .acc__item { border-bottom-color: var(--rule-dark); }
.band--ink .acc__panel p { color: rgba(250, 250, 248, 0.78); }
.band--ink .acc__head:hover { color: var(--red-lift); }

/* --------------------------------------------------------------------------
   25. Gallery filters
   -------------------------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
}
.filters__chip {
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font-size: var(--t-small);
  padding: 0.6rem 1rem;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
              border-color 0.2s var(--ease);
}
.filters__chip:hover { border-color: var(--ink); color: var(--ink); }
.filters__chip.is-on {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
.filters__count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   26. Lightbox additions
   -------------------------------------------------------------------------- */

.lightbox__stage {
  display: grid;
  place-items: center;
  width: 100%;
}
.lightbox__num {
  font-family: var(--mono);
  font-size: var(--t-data);
  letter-spacing: 0.18em;
  color: var(--red-lift);
  margin-top: 0.75rem;
  text-align: center;
  display: block;
}

/* --------------------------------------------------------------------------
   27. Breakpoints
   -------------------------------------------------------------------------- */

@media (min-width: 40rem) {
  /* 47% left a 46px sliver of the next slide showing half a letter, which
     read as a clipping bug rather than as a peek. These values fit their
     slides plus gaps to the full width. */
  .carousel--2 { --slide: 48.6%; }
  .carousel--3 { --slide: 48.6%; }
  .carousel--4 { --slide: 31.5%; }
  .grid-photos { column-count: 2; }
  .timeline li { grid-template-columns: 7rem minmax(0, 1fr); }
  .cover__foot { grid-template-columns: minmax(0, 1.6fr) auto; }
  .videos { grid-template-columns: repeat(2, 1fr); }
  .video--lead { grid-column: 1 / -1; }
}

@media (min-width: 56rem) {
  .carousel--3 { --slide: 31.7%; }
  .carousel--4 { --slide: 23.5%; }
  .stage { grid-template-columns: repeat(3, 1fr); }
  .spread { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
  .spread--figure-left { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
  .spread--figure-left .spread__figure { order: -1; }
  .grid-photos { column-count: 3; }
  .foot-grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; }
}

/* 48rem, not 62rem: from about 768px there is ample room for four nav items,
   and hiding them behind a burger on a tablet wasted the space. */
@media (max-width: 47.99rem) {
  .nav { display: none; }
  .burger { display: grid; }
}

/* On a narrow phone the header holds a wordmark, a pill and a burger. The
   pill sheds its second word rather than wrapping to two lines: the label
   still says what happens, and the drawer carries the full wording. */
/* "Invite Minister Vida" is a longer label than this pill used to carry, and
   it pushed the header past the right edge of the viewport between 26rem and
   30rem. The long half now collapses to a plain "Invite" a little earlier. */
@media (max-width: 30rem) {
  .head-actions .pill .pill__long { display: none; }
}

@media (max-width: 26rem) {
  .head-actions .pill { padding-inline: 1rem; }
  .head-actions .pill .pill__long { display: none; }
  .site-head__in { gap: 0.75rem; }
  .wordmark__mark { height: 2.1rem; }
  .wordmark__sub { display: none; }
  .wordmark__name { font-size: 1rem; }
}

@media (min-width: 48rem) {
  .drawer { display: none; }
}

/* --------------------------------------------------------------------------
   28. Reduced motion

   The ticker stops entirely rather than slowing: a moving line of text is
   the exact thing this setting exists to suppress.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .ticker__track { animation: none; transform: none; }
  .cover [data-anim] { opacity: 1; }
  .cover .masthead span { opacity: 1; }
  .shot:hover img { transform: none; }

  /* Reveals resolve to their finished state rather than animating, and the
     hero's slow zoom stops entirely. Autoplay is stopped in JS, which reads
     the same media query live. */
  html[data-aos-ready] [data-aos] { opacity: 1; transform: none; }
  .hero__slide img { transform: none; transition: none; }
  .carousel.is-live .carousel__slide { opacity: 1; }
  .to-top { transition: none; }
}

@media print {
  .ticker, .site-head, .drawer, .lightbox, .cover__cue { display: none; }
  body { background: #fff; color: #000; }
  .duo img { filter: none; }
}
