/* ---- tokens.css ---- */
/* ============================================================================
   TOKENS — Katie Ditschun, APEX theme.

   This is a re-skin of the katie-ditschun repo against the Sonaar "Apex" demo
   (demo.sonaar.io/apex-elementor), measured in a browser on 2026-08-26. The
   sibling repo Client Websites/katie-ditschun carries the original theme; the
   content, build, events system and tests are shared, so
   `git diff 14e5ad2..HEAD` is exactly the theme delta and nothing else.

   ---- what Apex actually is, measured, not remembered ----

   Every number below with MEASURED beside it was read out of the live demo
   with getComputedStyle, not eyeballed from a screenshot:

     hero display      125px / 700 / ls 2px    / uppercase / lh 162.5px (1.30)
     big section title  72px / 700 / ls 10px   / uppercase / lh 93.6px  (1.30)
     section title      24px / 700 / ls 3px    / uppercase / lh 36px
     album title        18px / 700 / ls 6px    / uppercase
     eyebrow label      14px / 700 / ls 7.8px  / uppercase
     body copy          18px / lh 38px (2.11)
     one family throughout: Josefin Sans. No serif anywhere on the site.

   The signature is the TRACKING, and it scales inversely with size: 0.016em at
   125px, 0.139em at 72px, 0.125em at 24px, 0.333em at 18px, 0.557em at 14px.
   Heading line-height is 1.30 at both sizes it could be checked at.

   Apex is monochrome — black, white, greyscale photography, no accent colour
   at all. The purple is the client's, not the theme's, and is threaded through
   as: the ring in the divider motif, the eyebrow labels, hover and
   aria-current, one deep-plum band, and the tint on the hero scrim.

   ---- what is NOT negotiable ----

   The four --plum values marked MEASURED were read out of her own logo art by
   decoding KD-logo-purple-circle.png and KD-logo-white-circle.png (both
   2048x2048) and counting pixels. They are her brand and must not be "tidied".
   tools/test/tokens.test.js pins them, checks 13 contrast pairs, and refuses
   two tokens that share a value.
   ========================================================================= */

:root {
  /* ---- brand: MEASURED from her logo art ------------------------------ */
  --plum-500: #a565ea;   /* core violet. AA-LARGE ONLY on white (3.72:1) —
                            display type >=24px and icons. NEVER body links.
                            On the near-black ground it is 5.33:1, so on dark
                            it is safe at any size. */
  --plum-400: #c398f1;   /* light violet — links and labels on dark grounds */
  --plum-200: #dbccea;   /* pale lilac — body copy on the dark bands */
  --plum-100: #f3eef8;   /* near-white lilac */

  /* ---- brand: derived on hue 269 --------------------------------------
     --plum-900 is the Apex ground. The original theme's #201230 is a visibly
     purple panel; Apex's ground is near-black (its own kit ships #121214), so
     this is pulled almost to black and keeps only a trace of the hue. White on
     it measures 19.80:1. */
  --plum-900: #0b0910;   /* the black band, the footer, the sticky header */
  --plum-800: #191225;   /* one step up: hairlines and inset blocks on dark */
  --plum-700: #442965;   /* headings on lilac */
  --plum-600: #613597;   /* body links — 8.52:1 on white */

  /* ---- neutrals ------------------------------------------------------- */
  --ink: #14111a;
  --ink-soft: #56506a;
  --paper: #ffffff;
  --paper-alt: #f5f3f8;
  --hairline: #ded8e6;
  --hairline-dk: #2e2440;

  /* ---- type -----------------------------------------------------------
     Apex uses ONE family for everything, including body copy, at weight 700.
     That works there because every block of text on the demo is under forty
     words. This site has a 300-word bio, a lessons description and two press
     kits, and Josefin Sans has a very low x-height — it is a display face
     wearing a text face's clothes.

     So: Josefin Sans carries every tracked uppercase run (nav, eyebrows,
     labels, buttons), and Jost — already self-hosted here, also a geometric
     sans, normal x-height — carries running copy.

     Titles are the exception, since 2026-09-26, at Katie's request: the hero
     name, page and section titles, and release and gig titles are Butler, a
     high-contrast serif, in mixed case and untracked. Her There Will Be
     Flowers cover sets her name in a serif the same way. The tracked
     Josefin Sans labels still sit above and around every title, which is what
     keeps the page reading as the site she liked. Butler ships in one weight,
     so --fw-title is not a preference: see fonts.css before changing it. */
  --font-display: "Josefin Sans", "Century Gothic", "Futura", sans-serif;
  --font: "Jost", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-title: "Butler", "Bodoni 72", "Didot", Georgia, serif;
  --fw-title: 500;

  /* Every clamp() below is clamp(MIN, intercept + slope·vw, MAX), solved so it
     hits MIN at a 360px viewport and MAX at 1440px:
         slope_vw  = (MAX − MIN) / 1080 × 100
         intercept = MIN − slope_vw × 3.6      (px; ÷16 for rem)
     Measured either side of every breakpoint after any change to these.

     The four title sizes are set for Butler in mixed case. It is 0.704em to
     the cap and 0.42em to the x-height, so at the old uppercase-Josefin sizes
     every title read a step smaller than before.

     The hero MIN is set by the surname. "Ditschun" measures 3.64em in
     Butler Medium, and a 360px phone leaves 320px inside the gutters, so
     64px sets it at 233px: as large as it can go with air either side. */
  --fs-hero:      clamp(4rem, 2.333rem + 7.41vw, 9rem);       /* 64 -> 144 */
  --fs-h1-banner: clamp(2.25rem, 1.5rem + 3.33vw, 4.5rem);    /* 36 -> 72  */
  --fs-h2:        clamp(1.875rem, 1.333rem + 2.41vw, 3.5rem); /* 30 -> 56  */
  --fs-h3:        clamp(1.25rem, 1.083rem + 0.74vw, 1.75rem); /* 20 -> 28  */
  /* The award years on /music/ are Josefin Sans numerals, not titles. They
     shared --fs-h3 until the titles grew, and keep its old values here. */
  --fs-figure:    clamp(1.0625rem, 0.958rem + 0.46vw, 1.375rem); /* 17 -> 22 */
  --fs-lead:  1.3125rem;   /* 21px */
  --fs-body:  1.125rem;    /* 18px — Apex's measured body size */
  --fs-small: 0.875rem;    /* 14px */
  --fs-label: 0.8125rem;   /* 13px, tracked to 0.34em */
  --fs-nav:   0.875rem;    /* 14px, tracked to 0.16em */

  /* The tracking ramp IS the theme. Wider as the type gets smaller.
     The two title steps, --ls-hero (0.02em) and --ls-display (0.12em), went
     with the uppercase titles on 2026-09-26: Butler is set untracked, and no
     rule was left reading either. */
  --ls-nav:     0.16em;
  --ls-label:   0.34em;    /* MEASURED 6px at 18px  */
  --ls-eyebrow: 0.5em;     /* MEASURED 7.8px at 14px */

  --lh-body: 1.9;          /* Apex measures 2.11; pulled in for long-form copy */
  --lh-heading: 1.3;       /* MEASURED, and identical at 72px and 125px */
  --lh-title: 1.12;        /* Butler titles that wrap; 1.3 left them gappy */
  --lh-wordmark: 0.95;     /* the stacked two-line name; 1.3 would gap it open */

  /* ---- spacing --------------------------------------------------------
     The air is all BETWEEN sections; the blocks themselves are tight. */
  --section-y: clamp(4.5rem, 2.5rem + 8.9vw, 11.25rem);        /* 72 -> 180 */
  --section-y-tight: clamp(3rem, 2rem + 4.4vw, 5.5rem);        /* 48 -> 88 */
  --gap: clamp(1.25rem, 1rem + 1.1vw, 2rem);                   /* 20 -> 32 */

  --container: 1140px;
  --gutter: 20px;

  /* ---- grid minimums --------------------------------------------------
     These drive auto-fit grids, so columns drop away on their own as the
     viewport shrinks and no media query is needed. The min() wrapper is what
     stops a fixed minimum from overflowing a narrow viewport — a bare
     minmax(420px, 1fr) cannot shrink below 420 and pushed 80px off-screen at
     360px in the original build. */
  --col-min-2: 420px;
  --col-min-3: 300px;
  --col-min-4: 220px;
  --col-min-cover: 200px;

  /* ---- chrome ---------------------------------------------------------
     Apex has no rounded corners and no shadows anywhere: every edge on the
     demo is a right angle and every surface is flat. Both are kept as tokens
     rather than hard zeros so the whole theme can be softened from one place. */
  --radius: 0px;
  --radius-card: 0px;
  --shadow-card: none;
  --transition: 0.25s ease;

  /* ---- the photo treatment -------------------------------------------
     Every photograph on the Apex demo is black and white. Hers are not, so
     they are desaturated in CSS rather than in the files — which means the
     originals stay in the repo at full colour and this is a ONE-LINE reversal
     if she would rather keep them. Set --photo-filter: none. */
  --photo-filter: grayscale(1) contrast(1.06);

  /* ---- the sticky header ---------------------------------------------- */
  --header-h: 68px;        /* MEASURED on the demo: 67px */
  --hero-h: 100svh;

  /* ---- the one structural breakpoint ---------------------------------
     Used only where layout genuinely changes rather than merely resizing:
     the nav collapsing to a drawer. */
  --bp-nav: 900px;
}

/* svh is the small viewport height — the one that does not jump when mobile
   browser chrome hides. Below the nav breakpoint the hero is shorter so the
   first band is visible without scrolling. */
@media (max-width: 900px) { :root { --hero-h: 78svh; --header-h: 60px; } }
@media (max-width: 599px) { :root { --hero-h: 72svh; } }

/* ---- base/fonts.css ---- */
/* ============================================================================
   FONTS — self-hosted, so the site makes zero third-party requests and the CSP
   needs no font host at all. Josefin Sans and Jost are SIL Open Font License;
   Butler is NOT — read its section below before touching its file.

   ---- Josefin Sans ----

   The Apex demo uses Josefin Sans for every word on the page. It is a true
   variable font: the fetched file carries an `fvar` table and an `HVAR` table
   (HVAR only exists in variable fonts), and the wght axis really does move —
   proven by rendering the same string at wght 100 and wght 700 and comparing
   measured advance widths, not by trusting the CSS the API returned.

   Fetched latin-only, which is enough: the entire content set — every page,
   every JSON record, both press kits — contains exactly ONE non-ASCII letter,
   `é` (U+00E9), and that lives inside the latin subset's U+0000-00FF range.
   Counted, not assumed. Adding a character outside Latin-1 means re-fetching
   with the latin-ext subset.

       fonts.googleapis.com/css2?family=Josefin+Sans:wght@100..700

   ---- Jost ----

   Kept from the original theme for running copy. Josefin Sans has a very low
   x-height and Apex only ever sets short blocks in it; this site has a 300-word
   bio. See the note in tokens.css.

   ---- Fraunces ----

   Removed. It was the original theme's display face. Deleting the @font-face
   without deleting the file would have shipped 120 KB of unreferenced font in
   dist/, so the file is gone too.

   ---- Butler (titles) ----

   Katie asked for Butler on 2026-09-26. It sets the titles (the hero name,
   page and section titles, release and gig titles) in mixed case, which is
   the look of the serif on her own There Will Be Flowers cover. Everything
   tracked and uppercase stays in Josefin Sans. See --font-title in tokens.css.

   Butler Free by Fabian De Smet, taken from the author's own zip (fetched
   2026-09-26 from the dafont mirror of www.fabiandesmet.com's download). Its
   licence is assets/fonts/Butler-EULA-v2-00.pdf, dated 2026-02-19. That is
   NOT the CC BY-SA that older write-ups of Butler quote. It allows commercial
   use. It allows redistribution only if the files stay free and UNMODIFIED,
   ship with the licence in full, and a clear reference and link to
   www.fabiandesmet.com is displayed. A webfont is handed to every visitor, so
   this site treats itself as redistributing it and meets every condition:

     unmodified   Butler-Free-Med.woff2 is byte-identical to the one in the
                  author's zip (sha256 57af575b...). Do NOT subset, convert or
                  re-encode it to save bytes: that is a modification.
     licence      Butler-EULA-v2-00.pdf ships beside it, byte-identical.
     reference    the footer says "Butler typeface by Fabian De Smet", linked
                  to www.fabiandesmet.com, with the licence linked beside it.

   tools/test/fonts.test.js checks all three.

   ONE weight ships: Medium (500), 27.5 KB. Every rule that uses
   var(--font-title) also takes var(--fw-title), which is 500. A title asking
   for 700 would not fail; the browser would synthesise a fake bold with
   smeared hairlines. The same test checks for it.

   Butler has no italic. No title on the site carries <em>; if one ever does,
   the browser will slant the roman.
   ========================================================================= */

@font-face {
  font-family: "Josefin Sans";
  src: url("/assets/fonts/josefin-sans-var.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Jost";
  src: url("/assets/fonts/jost-var.woff2") format("woff2");
  font-weight: 300 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Butler";
  src: url("/assets/fonts/Butler-Free-Med.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ---- base/reset.css ---- */
/* A small, opinionated reset. Nothing here is a preference — each rule removes
   a browser default that fights the design. */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@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;
  }
}

body {
  margin: 0;
  /* Deliberately `hidden`, not `clip`: `overflow-x: clip` on html or body kills
     position: sticky outright — body becomes the scroll container and any
     sticky element rides away up the page.
     Note this also means `scrollWidth - clientWidth` can never prove zero
     horizontal overflow here; the sweep must enumerate
     getBoundingClientRect().right > innerWidth instead. */
  overflow-x: hidden;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

img, svg, video, iframe { max-width: 100%; }
img, svg, video { height: auto; display: block; }

h1, h2, h3, h4, h5, h6 { font-weight: 600; line-height: var(--lh-heading); }

a { color: var(--plum-600); }
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--plum-500);
  outline-offset: 2px;
}

button, input, select, textarea { font: inherit; color: inherit; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--plum-900);
  color: var(--paper);
  padding: 0.75rem 1.25rem;
  text-decoration: none;
}
.skip-link:focus { left: 0; }

/* ---- base/typography.css ---- */
/* ============================================================================
   Type defaults. The scale itself lives in tokens.css.

   Apex sets nearly every heading in uppercase Josefin Sans 700 with heavy
   tracking. Here h4-h6 still do. h1-h3 are titles and, since 2026-09-26, are
   Butler in mixed case (see --font-title in tokens.css). Both are set here
   rather than in reset.css because reset.css loads first and this file is
   where the theme's opinion belongs.

   Tracking adds space AFTER the last letter too, which pulls a centred line
   visibly off-centre — at 0.5em on a 14px eyebrow that is a 7px error. Every
   centred tracked run therefore carries a compensating negative margin-right.
   Titles are untracked, so they carry none.
   ========================================================================= */

h1, h2, h3 {
  font-family: var(--font-title);
  font-weight: var(--fw-title);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  margin-top: 0;
}
h1 { font-size: var(--fs-h1-banner); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink);
  margin-top: 0;
}

p { margin-top: 0; }
strong, b { font-weight: 600; }
em, i { font-style: italic; }

blockquote { margin: 0; }

::selection { background: var(--plum-500); color: var(--paper); }

/* ---- the tracked-uppercase run ----------------------------------------
   The single most reusable piece of the theme. Anything wearing this reads as
   Apex; anything not wearing it reads as ordinary. */
.eyebrow {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--plum-600);
  margin: 0 0 1.25rem;
}
.eyebrow--center { text-align: center; margin-right: calc(var(--ls-eyebrow) * -1); }

/* On any dark ground the eyebrow lifts to the light violet: --plum-600 on
   --plum-900 is 2.28:1 and would be unreadable. */
.section--dark .eyebrow,
.page-banner .eyebrow,
.site-footer .eyebrow { color: var(--plum-400); }

/* The hero's eyebrow sits on a photograph, not a flat band, so its ground is
   whatever the scrim leaves of the photo. Over hero-floral-coat.jpg (light
   backdrop, 2026-10-05) --plum-400 measured 3.03-3.96:1 against the brightest
   pixel under it, 1920 to 390 wide; --plum-200 measured 4.62:1 (390) to
   6.03:1 (1280). Re-measure if the hero photograph changes again. */
.hero .eyebrow { color: var(--plum-200); }

/* ---- base/layout.css ---- */
/* The one layout primitive: a centred column with a gutter. Everything
   full-bleed is a section; everything readable is inside a container. */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--narrow { max-width: 780px; }

/* Kept out of the flow but readable by a screen reader. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- components/divider.css ---- */
/* ============================================================================
   THE RULE-AND-RING — Apex's one piece of ornament.

   A thin horizontal rule with a small open circle sitting on its centre,
   under every section title. On the demo it is a flat IMAGE (there is no
   markup for it: a sweep for class names matching divider|separator|deco
   returned zero elements on the page), so this is a rebuild of what it looks
   like rather than a port of how they made it. Measured off a zoomed capture:
   roughly 95px overall, a hairline rule, a ring about 13px across.

   The RING is the element and the two rule segments are its pseudo-elements —
   which is what lets the ring take the accent colour while the rules stay a
   hairline, with one span of markup and no wrapper.

   This is the main place the client's purple appears in an otherwise
   monochrome theme.
   ========================================================================= */

.section-rule {
  display: block;
  position: relative;
  width: 13px;
  height: 13px;
  /* The head below it owns the space to the next block; this only needs to
     clear the title. */
  margin: 1.5rem auto 0;
  border: 1.5px solid var(--plum-500);
  border-radius: 50%;
  /* The rule segments read the ambient colour, so one class works on both
     grounds without a --dark override. */
  color: var(--hairline);
}

.section-rule::before,
.section-rule::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 40px;
  height: 1px;
  background: currentColor;
}
.section-rule::before { right: 100%; margin-right: 5px; }
.section-rule::after  { left: 100%;  margin-left: 5px; }

.section--dark .section-rule,
.page-banner .section-rule,
.hero .section-rule,
.site-footer .section-rule { color: var(--hairline-dk); }

/* Over a photograph a 1px hairline disappears in EITHER direction — the dark
   one vanishes into the shadows, the light one into the highlights. Both bands
   whose ground is an image take the accent for the rule segments as well as
   the ring. Measured on /events/: at --hairline-dk the segments were invisible
   against the portrait. */
.hero .section-rule,
.events-stage .section-rule { color: var(--plum-500); }

/* ---- components/button.css ---- */
/* ============================================================================
   Buttons.

   Apex's buttons are square-cornered, tracked uppercase, and either a flat
   dark fill or a hairline outline — there is not one rounded corner on the
   demo. The pill radius from the original theme is gone; the radius comes from
   --radius so the whole theme can be softened from tokens.css instead of here.

   Five variants, and every one of them has a rule. A class the markup asks for
   that the stylesheet does not define ships as an unstyled element — on a
   prior site `.btn--red` was deleted from the CSS while sixteen pages still
   requested it, producing a transparent pill on near-black at roughly 1:1
   contrast, with the build clean and every check silent. verify.js now fails
   on that; this comment is why the check exists.
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  min-height: 44px;
  padding: 0.7rem 1.9rem;
  /* The tracking on the last letter would otherwise sit the label left of
     centre inside its own box. */
  padding-right: calc(1.9rem - 0.34em);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}

.btn--solid {
  background: var(--plum-600);
  color: var(--paper);
  border-color: var(--plum-600);
}
.btn--solid:hover, .btn--solid:focus-visible {
  background: var(--plum-700);
  border-color: var(--plum-700);
  color: var(--paper);
}

/* The hero and any dark band: hairline outline, fills on hover. This is the
   demo's default button. */
.btn--ghost {
  background: transparent;
  color: var(--paper);
  border-color: rgba(255, 255, 255, 0.5);
}
.btn--ghost:hover, .btn--ghost:focus-visible {
  background: var(--paper);
  color: var(--plum-900);
  border-color: var(--paper);
}

.btn--quiet {
  background: transparent;
  color: var(--plum-600);
  border-color: var(--hairline);
}
.btn--quiet:hover, .btn--quiet:focus-visible {
  border-color: var(--plum-500);
  color: var(--plum-700);
}

.btn--outline {
  background: transparent;
  color: var(--plum-600);
  border-color: var(--plum-600);
}
.btn--outline:hover, .btn--outline:focus-visible {
  background: var(--plum-600);
  color: var(--paper);
}

/* On a dark ground the solid button needs the lighter violet to stay legible;
   --plum-600 on --plum-900 is 2.28:1. */
.section--dark .btn--solid,
.section--lilac .btn--solid,
.site-footer .btn--solid {
  background: var(--plum-400);
  color: var(--plum-900);
  border-color: var(--plum-400);
}
.section--dark .btn--solid:hover,
.section--lilac .btn--solid:hover,
.site-footer .btn--solid:hover { background: var(--paper); border-color: var(--paper); }

.section--dark .btn--outline,
.section--lilac .btn--outline {
  color: var(--plum-400);
  border-color: var(--plum-400);
}
.section--dark .btn--outline:hover,
.section--lilac .btn--outline:hover { background: var(--plum-400); color: var(--plum-900); }

/* ---- components/header.css ---- */
/* ============================================================================
   HEADER — the Apex bar.

   MEASURED on the demo: 67px tall, logo left, nav centred, social icons right;
   transparent while it sits over the hero, and a solid light bar with dark
   type once the page scrolls. The demo does this with a second, cloned sticky
   element (the markup header itself computes `position: relative` and scrolls
   away); one element and a class is the same effect with half the DOM.

   `position: fixed`, not `sticky`. Sticky would work here — reset.css uses
   `overflow-x: hidden` rather than `clip` precisely so it can — but a sticky
   header occupies space in flow, and this one has to sit OVER the hero
   photograph with nothing under it.

   Because it overlays, every page's first band must be dark or the light type
   at scroll 0 lands on white. That is not a convention anyone can be expected
   to remember, so tools/test/theme.test.js asserts it for every page.
   ========================================================================= */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  background: transparent;
  color: var(--paper);
  transition: background var(--transition), color var(--transition),
              border-color var(--transition);
  border-bottom: 1px solid transparent;
}

.header-inner {
  display: flex;
  align-items: center;
  gap: var(--gap);
  max-width: var(--container);
  min-height: var(--header-h);
  margin: 0 auto;
  padding-inline: var(--gutter);
}

.brand { display: inline-flex; align-items: center; flex: 0 0 auto; }
/* ---- the K mark ---------------------------------------------------------
   Katie's new logo, 2026-09-26: she asked for a stylised K in white in place
   of the old round KD logo. It is the K from Butler Free Bold (the title face)
   inside a hairline ring. The ring echoes the old round logo and the ring in
   every section divider, and it is broken wherever the letter crosses it.

   It is inline SVG in header.html, not an <img>, so that it is drawn in
   currentColor: white over the dark first band of every page (the "white"
   she asked for), ink on the white stuck bar, where a white mark would
   vanish, and violet on hover like the nav links.

   How the paths were made, so the mark can be redrawn rather than traced:
     letter  the Butler-Free-Bd.otf 'K' outline (from the zip described in
             fonts.css), flipped to y-down with its cap top at y=0. The EULA
             allows designs made using the typeface; the font file itself
             is not shipped for this.
     ring    centred on the letter's bounding box (417.5, 352), radius 470,
             22 units wide (459 to 481), with the letter grown by 34 units
             cut out of it. That leaves four arcs, written as true SVG arcs.
   22 units is close to the letter's own hairline (its serifs are 17), so the
   ring reads as part of the same drawing.

   It is a fixed 44px at every width, because the ring is 44 x 22 / 970 =
   1.0 CSS px there. Scaling it down would put that hairline under one device
   pixel on a 1x screen, where it goes grey and soft. */
.brand { color: inherit; transition: color var(--transition); }
.brand-mark {
  display: block;
  width: 44px;
  height: 44px;
}
.brand:hover, .brand:focus-visible { color: var(--plum-400); }
.site-header.is-stuck .brand:hover,
.site-header.is-stuck .brand:focus-visible { color: var(--plum-600); }

/* The nav fills the space after the logo and centres its own list, which puts
   the list a little right of true page centre. That is what the demo measures
   as too: its list spans 366-960 at a 1280 viewport, centre 663 against a page
   centre of 640. */
.main-nav {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}

.nav-list {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 0.2rem + 2.2vw, 2.5rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-list a {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--fs-nav);
  font-weight: 700;
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  text-decoration: none;
  color: inherit;
  padding-bottom: 4px;
  border-bottom: 2px solid transparent;
  transition: color var(--transition), border-color var(--transition);
}

.nav-list a:hover,
.nav-list a:focus-visible { color: var(--plum-400); }

/* aria-current has to repaint something the link is not already showing.
   The accent and the ambient colour are far apart in the ramp precisely so
   this rule is visible: on a prior site two tokens shared a value, so hover
   and aria-current painted the colour the link already had, the markup was
   perfect, and every accessibility check passed while nothing happened. */
.nav-list a[aria-current="page"] {
  color: var(--plum-400);
  border-bottom-color: var(--plum-500);
}

.nav-social {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 0.85rem;
}
.nav-social a { color: inherit; transition: color var(--transition); }
.nav-social a:hover, .nav-social a:focus-visible { color: var(--plum-400); }

.nav-toggle { display: none; }
.nav-backdrop { display: none; }

/* ---- the stuck state --------------------------------------------------- */

.site-header.is-stuck {
  background: var(--paper);
  color: var(--ink);
  border-bottom-color: var(--hairline);
}
.site-header.is-stuck .nav-list a:hover,
.site-header.is-stuck .nav-list a:focus-visible { color: var(--plum-600); }
.site-header.is-stuck .nav-list a[aria-current="page"] {
  color: var(--plum-600);
  border-bottom-color: var(--plum-500);
}
.site-header.is-stuck .nav-social a:hover,
.site-header.is-stuck .nav-social a:focus-visible { color: var(--plum-600); }
/* The K mark needs no stuck-state rule of its own: it is drawn in
   currentColor, and .is-stuck already sets the header's colour to --ink. */

/* ---- the drawer -------------------------------------------------------- */

@media (max-width: 900px) {
  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;                 /* WCAG 2.2 SC 2.5.8 wants 24x24 minimum; */
    height: 44px;                /* 44 is the comfortable target, not the floor */
    padding: 10px;
    margin-left: auto;
    background: none;
    border: 0;
    color: inherit;
    cursor: pointer;
    z-index: 45;
  }
  .nav-toggle-bar {
    display: block;
    height: 2px;
    background: currentColor;
    border-radius: 2px;
  }

  .main-nav {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(20rem, 86vw);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: var(--gap);
    padding: var(--gap) clamp(1.5rem, 1rem + 3vw, 2.5rem);
    background: var(--plum-900);
    color: var(--paper);
    transform: translateX(100%);
    visibility: hidden;
    z-index: 44;
    /* The visibility delay is what makes the drawer focusable. A transitioned
       `visibility` is still hidden on the next frame, so focus silently fails
       to move into the panel unless the delay is zeroed on the way IN. */
    transition: transform 0.28s ease, visibility 0s linear 0.28s;
  }

  .main-nav.is-open {
    transform: none;
    visibility: visible;
    transition: transform 0.28s ease, visibility 0s linear 0s;
  }

  .nav-list {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.4rem;
  }
  .main-nav .nav-list a {
    color: var(--paper);
    font-size: 1rem;
  }
  .main-nav .nav-list a:hover,
  .main-nav .nav-list a:focus-visible { color: var(--plum-400); }
  .main-nav .nav-list a[aria-current="page"] { color: var(--plum-400); border-bottom-color: var(--plum-400); }
  .main-nav .nav-social { justify-self: start; color: var(--plum-200); }
  .main-nav .nav-social a { color: var(--plum-200); }

  /* The drawer is a fixed panel, so the grid columns it uses on desktop must
     be undone or its two children keep their 1fr/auto/1fr placement and stack
     with a phantom empty column above them. */
  .nav-list, .nav-social { grid-column: auto; }

  .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(11, 9, 16, 0.62);
    opacity: 0;
    visibility: hidden;
    z-index: 42;
    transition: opacity 0.28s ease, visibility 0s linear 0.28s;
  }
  .nav-backdrop.is-open {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.28s ease, visibility 0s linear 0s;
  }
}

/* build.js marks the nav item whose key matches the page, on the <li> as well
   as the <a>. The <a> carries the visible state; this exists so the <li> hook
   is defined rather than merely unused. */
.nav-list .is-active { position: relative; }

/* ---- components/section.css ---- */
/* ============================================================================
   SECTIONS — the rhythm.

   Apex alternates FULL-BLEED bands: near-black, then white, then near-black.
   There are no hairline separators between sections anywhere on the demo and
   no tinted-panel-inside-a-white-page; the ground colour itself is the
   separator, edge to edge. The original theme's inner-column hairline is
   therefore gone rather than kept alongside — two separation systems at once
   reads as neither.

   Section headings are CENTRED, uppercase and tracked, with the rule-and-ring
   beneath. That centring is the second-most recognisable thing about Apex
   after the tracking.
   ========================================================================= */

.section { padding-block: var(--section-y); }
.section--tight { padding-block: var(--section-y-tight); }

/* ---- the two grounds --------------------------------------------------- */

.section--alt { background: var(--paper-alt); }

.section--dark {
  background: var(--plum-900);
  color: var(--plum-200);
}
.section--dark .section-title { color: var(--paper); }
.section--dark a { color: var(--plum-400); }
.section--dark a:hover, .section--dark a:focus-visible { color: var(--paper); }

/* The one band that is purple rather than black or white. Apex has no accent
   colour at all, so this is a deliberate departure: it carries the newest
   release, which is the thing on the homepage most worth colouring. */
.section--lilac {
  background: var(--plum-800);
  color: var(--plum-200);
}
.section--lilac .section-title { color: var(--paper); }
.section--lilac .eyebrow { color: var(--plum-400); }
.section--lilac .section-rule { color: var(--hairline-dk); }
.section--lilac a { color: var(--plum-400); }
.section--lilac a:hover, .section--lilac a:focus-visible { color: var(--paper); }

/* ---- the centred head -------------------------------------------------- */

.section-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Measured: with --section-y-tight here the gap between the ring and the
     first line of content ran to 143px at 1280. The demo's equivalent gap is
     nearer 100. This plus the rule's own bottom margin lands at about 80. */
  margin-bottom: clamp(2.5rem, 1.8rem + 3vw, 5rem);
}

/* A Butler title. It is untracked, so unlike the eyebrow above it, it needs no
   negative margin to sit on true centre. */
.section-title {
  font-family: var(--font-title);
  font-weight: var(--fw-title);
  font-size: var(--fs-h2);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  margin: 0;
}

/* The old class name, kept as the eyebrow it now is, because it is set on
   eleven pages and both press kits. The theme's own name for it is .eyebrow;
   this is the alias, not a second implementation. */
.section-label {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--plum-600);
  margin: 0 0 1.1rem;
  margin-right: calc(var(--ls-eyebrow) * -1);
}
.section--dark .section-label,
.section--lilac .section-label { color: var(--plum-400); }

.section-intro {
  font-size: var(--fs-lead);
  line-height: 1.7;
  max-width: 62ch;
  margin: var(--gap) auto 0;
}

.section-foot {
  margin-top: var(--section-y-tight);
  text-align: center;
}

/* Running copy inside a centred section stays left-aligned and centred as a
   BLOCK — centred body text is unreadable past about two lines, and Apex never
   does it either: its one long passage, the About Me quote, is left-aligned
   under a centred heading. */
.section-head + .prose,
.section .prose--center { margin-inline: auto; }

/* ---- components/prose.css ---- */
/* ============================================================================
   Body copy blocks — the bio, the lessons description, the press kits.

   Running copy is the one place the theme steps out of Josefin Sans: see the
   note in tokens.css. Apex's line-height on body copy measures 2.11; --lh-body
   is 1.9 here, which keeps the air without stranding a 300-word bio.
   ========================================================================= */

.prose { max-width: 68ch; }
.prose p { margin: 0 0 1.15em; }
.prose p:last-child { margin-bottom: 0; }
.prose a {
  color: var(--plum-600);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.prose a:hover, .prose a:focus-visible { color: var(--plum-700); }
.prose--wide { max-width: 78ch; }
.prose--center { text-align: center; }

.section--dark .prose a,
.section--lilac .prose a { color: var(--plum-400); }
.section--dark .prose a:hover,
.section--lilac .prose a:hover { color: var(--paper); }

/* ---- pull quotes -------------------------------------------------------
   Apex sets its one long passage — the About Me quote — centred under the
   heading, in the body face rather than the display face, with no rule beside
   it. The original theme's left border is gone: a 2px accent bar is a second
   ornament competing with the rule-and-ring, and the theme only has room for
   one. */
.pullquote {
  margin: 0;
  text-align: center;
}
.pullquote p {
  font-family: var(--font);
  font-size: var(--fs-lead);
  line-height: 1.75;
  color: inherit;
  margin: 0 0 1rem;
}
.pullquote cite {
  display: block;
  font-family: var(--font-display);
  font-style: normal;
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin-right: calc(var(--ls-label) * -1);
}
.section--dark .pullquote cite,
.section--lilac .pullquote cite { color: var(--plum-400); }

.quote-grid {
  display: grid;
  gap: var(--section-y-tight) var(--gap);
  /* min() is what stops the fixed minimum overflowing a narrow viewport: a
     bare minmax(420px, 1fr) cannot shrink below 420 and pushed 80px off-screen
     at 360px in the original build. */
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-2), 100%), 1fr));
}

/* ---- components/page-banner.css ---- */
/* ============================================================================
   The inner-page banner. The homepage has no h1 key in its front matter, so
   nothing renders there and the hero carries the h1 instead.

   Centred, on the near-black ground, with the rule-and-ring under the title —
   the same head treatment as a section, at page scale. It has to clear the
   fixed header, which overlays rather than pushes.
   ========================================================================= */

.page-banner {
  background: var(--plum-900);
  color: var(--plum-200);
  text-align: center;
  padding-block:
    calc(var(--header-h) + clamp(3rem, 2rem + 4.4vw, 6rem))
    clamp(3rem, 2rem + 4.4vw, 6rem);
}

.page-banner h1 {
  font-family: var(--font-title);
  font-weight: var(--fw-title);
  font-size: var(--fs-h1-banner);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  color: var(--paper);
  margin: 0;
}

/* Inside a banner the head must not add a whole section's worth of air under
   itself — the banner already owns that spacing. */
.page-banner .section-head { margin-bottom: 0; }
.page-banner .section-label { color: var(--plum-400); }
.page-banner .section-rule { margin-bottom: 1.25rem; }

.breadcrumb {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-200);
}
.breadcrumb li + li::before { content: "/"; margin-right: 0.5rem; color: var(--plum-700); }
.breadcrumb a { color: var(--plum-400); text-decoration: none; }
.breadcrumb a:hover, .breadcrumb a:focus-visible { color: var(--paper); }

/* ---- components/hero.css ---- */
/* ============================================================================
   THE HERO — full-bleed photograph, centred stacked wordmark.

   This replaces the original theme's split stage, where a solid purple panel
   sat beside the portrait and the type never touched the photograph. Apex does
   the opposite: white display type directly over a full-bleed image. That
   trades a guaranteed contrast ratio for the composition, so the scrim below
   is not decoration — it is the thing that makes the type legible, and the
   ratio it produces was sampled from rendered pixels rather than assumed.

   The name STACKS. It first stacked because the uppercase, tracked name would
   not fit on one line: "KATIEDITSCHUN" at the demo's 125px needed about
   1260px. In Butler since 2026-09-26 that reason only holds on phones.
   Measured in Butler Medium: "Katie Ditschun" is 6.06em on one line, which
   is 872px at 144px and fits the desktop container; but at 64px it is 387px
   against a phone's 320px. It stacks at every width so the name is one
   composition everywhere, not a line that breaks at some arbitrary width.
   ========================================================================= */

.hero {
  position: relative;
  min-height: var(--hero-h);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--plum-900);
  color: var(--paper);
  isolation: isolate;
  overflow: hidden;
  /* The header is fixed and overlays this, so the content is nudged down by
     half its height to stay optically centred in what remains. */
  padding: calc(var(--header-h) + var(--gap)) var(--gutter) var(--section-y-tight);
}

.hero-photo {
  position: absolute;
  inset: 0;
  background-image: var(--hero-image, none);
  background-size: cover;
  /* The photograph is upright and a desktop frame is wide, so cover keeps
     only a band of it. 6% keeps her whole head below the fixed header from
     1280x720 to 1920x1080 (measured 2026-10-05, hero-floral-coat.jpg); the
     22% the previous photograph used cut off the top of her head and, at
     1920, put her eyes behind the nav. */
  background-position: center 6%;
  background-repeat: no-repeat;
  filter: var(--photo-filter);
  z-index: 0;
}

/* The scrim. Centre-weighted rather than a plain top-to-bottom fade, because
   the type is centred in the frame rather than sitting along one edge. The
   tint is --plum-900 rather than neutral black, which is the only place the
   photograph itself picks up the brand hue. */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 78% 62% at 50% 50%,
      rgba(11, 9, 16, 0.74) 0%,
      rgba(11, 9, 16, 0.52) 55%,
      rgba(11, 9, 16, 0.30) 100%),
    linear-gradient(to bottom,
      rgba(11, 9, 16, 0.62) 0%,
      rgba(11, 9, 16, 0.20) 34%,
      rgba(11, 9, 16, 0.24) 66%,
      rgba(11, 9, 16, 0.70) 100%);
}

.hero-intro {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 100%;
  max-width: var(--container);
}

/* Butler, mixed case, untracked: her name set the way her There Will Be
   Flowers cover sets it. */
.hero-wordmark {
  font-family: var(--font-title);
  font-weight: var(--fw-title);
  font-size: var(--fs-hero);
  line-height: var(--lh-wordmark);
  letter-spacing: normal;
  text-transform: none;
  color: var(--paper);
  margin: 0;
}

/* Two blocks, one heading: the h1 still reads as a single name to a screen
   reader and to Google, and the line break is presentational.

   The uppercase theme tracked "KATIE" out to the width of "DITSCHUN". That
   trick is gone with the caps: stretching a serif's lowercase that far would
   break its letterforms apart. The two lines simply centre. */
.hero-given,
.hero-surname { display: block; }

.hero-tagline {
  font-family: var(--font);
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--plum-100);
  margin: 0;
  max-width: 34ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.85rem;
  margin-top: var(--gap);
}

/* A scroll cue sits at the bottom of the frame on the demo. It is decoration,
   so it is hidden from assistive tech and from anyone who has asked for less
   motion. */
.hero-scroll {
  position: absolute;
  left: 50%;
  bottom: clamp(1.25rem, 0.5rem + 2vw, 2.5rem);
  transform: translateX(-50%);
  z-index: 1;
  width: 1px;
  height: clamp(34px, 4vh, 56px);
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--plum-400));
}
@media (prefers-reduced-motion: no-preference) {
  .hero-scroll { animation: hero-scroll-cue 2.4s ease-in-out infinite; }
}
@keyframes hero-scroll-cue {
  0%, 100% { opacity: 0.25; transform: translateX(-50%) scaleY(0.6); }
  50%      { opacity: 1;    transform: translateX(-50%) scaleY(1); }
}

@media (max-width: 900px) {
  .hero { padding-top: calc(var(--header-h) + var(--section-y-tight)); }
  .hero-tagline { max-width: 26ch; }
}

/* ---- components/music.css ---- */
/* ============================================================================
   MUSIC — discography, awards, videos, listen row.
   ========================================================================= */

.release-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-cover), 100%), 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}

.release-cover {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  margin-bottom: 0.85rem;
}

/* A record's name is a title, so it is Butler like every other title. The
   tracked label under it (.release-meta) is what keeps the Apex rhythm. */
.release-title {
  font-family: var(--font-title);
  font-size: var(--fs-h3);
  font-weight: var(--fw-title);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  margin: 0 0 0.3rem;
}

.release-meta {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin: 0 0 0.5rem;
}
.section--dark .release-meta { color: var(--plum-400); }

.release-links { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.75rem; }

/* The one tactile hover on the site: the label slides, nothing else moves.
   Modelled on the streaming-link hover that is the whole motion budget of
   kellyleeevans.com. */
.release-link,
.listen-link {
  position: relative;
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-small);
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--plum-600);
  padding-left: 0;
  transition: padding-left var(--transition), color var(--transition);
}
.release-link::before,
.listen-link::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 0;
  height: 1px;
  background: currentColor;
  transition: width var(--transition);
}
.release-link:hover, .release-link:focus-visible,
.listen-link:hover, .listen-link:focus-visible {
  padding-left: 18px;
  color: var(--plum-700);
}
.release-link:hover::before, .release-link:focus-visible::before,
.listen-link:hover::before, .listen-link:focus-visible::before { width: 12px; }

/* The platform glyph before each name (Katie's request, 2026-09-26). It is
   drawn in currentColor, so it takes the link's colour and its hover colour
   with no rule of its own. It sits a little larger than the 14px caps so the
   circular Spotify mark does not look smaller than the letters beside it. The
   anchor is already inline-flex and centred (utilities.css, tap targets), so
   the gap is all this needs. */
.release-link, .listen-link { gap: 0.55em; }
.link-icon {
  flex: 0 0 auto;
  width: 1.2em;
  height: 1.2em;
}

.section--dark .release-link,
.section--dark .listen-link { color: var(--plum-400); }
.section--dark .release-link:hover,
.section--dark .listen-link:hover { color: var(--paper); }

.listen-row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1rem, 0.5rem + 1.6vw, 2rem);
}
.listen-link { font-size: var(--fs-small); }

/* The /music/ listen strip sits under a centred head, so its row centres too. */
.listen-row--center { justify-content: center; }

/* Album art is the client's actual released artwork — a product, not editorial
   photography — so it is the one image class on the site that keeps its colour
   while --photo-filter desaturates everything else. */
.release-cover, .release-feature-cover { filter: none; }

/* ---- awards ------------------------------------------------------------ */

.award-list { list-style: none; margin: 0; padding: 0; }

.award {
  display: flex;
  gap: clamp(1rem, 0.5rem + 1.6vw, 2rem);
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--hairline);
}
.award:last-child { border-bottom: 0; }
.section--dark .award { border-bottom-color: var(--hairline-dk); }

.award-year {
  flex: 0 0 auto;
  min-width: 4.5ch;
  font-family: var(--font-display);
  font-size: var(--fs-figure);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--plum-600);
}
.section--dark .award-year { color: var(--plum-400); }

.award-body { display: flex; flex-direction: column; gap: 0.15rem; }
.award-name {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.award-detail {
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}
.section--dark .award-detail { color: var(--plum-200); }
.award-work { font-style: italic; }

/* ---- videos ------------------------------------------------------------ */

.video-wall {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-3), 100%), 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}
.video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--ink);
}
.video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.video-caption {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  margin: 0.65rem 0 0;
}
.video-note { color: var(--plum-600); text-transform: none; letter-spacing: 0; font-style: italic; }
.section--dark .video-note { color: var(--plum-400); }

/* Grid items and inline spans that carry no visual weight of their own but are
   real hooks the markup asks for. Declared so the undefined-class check stays
   meaningful — an empty rule is a statement that the element is deliberately
   unstyled, whereas a missing rule is indistinguishable from one that was
   deleted out from under the markup. */
.release { display: flex; flex-direction: column; }
.release-type { text-transform: uppercase; letter-spacing: var(--ls-label); }
.release-year::before { content: "· "; }
.video { display: flex; flex-direction: column; }
.award-cat { font-style: normal; }

/* ---- components/events.css ---- */
/* ============================================================================
   EVENTS

   The one stillness moment on the site: `background-attachment: fixed` on the
   events photograph, so the date cards travel across a still image while
   everything around them scrolls. This is the effect the client cited about
   jenngrant.com, and it is one declaration.

   Legibility comes from frosted cards, not from dimming the photo. For an
   artist with strong live photography that reads far better than a grey wash.

   `background-attachment: fixed` is the classic iOS Safari trouble spot. The
   coarse-pointer query is the fallback and must be checked on a real device.

   Class names here follow what tools/events.js emits — do not rename them
   without changing that module.
   ========================================================================= */

/* The photograph moved from the section itself onto a ::before layer, because
   --photo-filter has to desaturate the IMAGE without touching the frosted
   cards and white type stacked on top of it. `filter` on the section would
   grey out its own children. */
.events-stage { position: relative; isolation: isolate; }
.events-stage::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--events-image, none);
  background-size: cover;
  background-position: center 30%;
  background-attachment: fixed;
  background-repeat: no-repeat;
  filter: var(--photo-filter);
}

@media (hover: none) and (pointer: coarse) { .events-stage::before { background-attachment: scroll; } }
@media (prefers-reduced-motion: reduce)    { .events-stage::before { background-attachment: scroll; } }

.events { list-style: none; margin: 0; padding: 0; }

.event-row { margin-bottom: 16px; }
.event-row:last-child { margin-bottom: 0; }

.event-row__link {
  display: flex;
  align-items: center;
  gap: clamp(0.9rem, 0.5rem + 1.3vw, 1.75rem);
  padding: clamp(0.9rem, 0.7rem + 1vw, 1.4rem);
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(15px);
  -webkit-backdrop-filter: blur(15px);
  border-radius: var(--radius-card);
  text-decoration: none;
  color: var(--ink);
  transition: background var(--transition), transform var(--transition);
}
.event-row__link:hover,
.event-row__link:focus-visible {
  background: rgba(255, 255, 255, 0.94);
  transform: translateX(3px);
}

.event-date {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 4.5rem;
  line-height: 1.05;
}
.event-date__month {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
}
.event-date__day {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--plum-900);
}
.event-date__year { font-size: var(--fs-small); color: var(--ink-soft); }

.event-row__body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 0.2rem; }
.event-row__title {
  font-family: var(--font-title);
  font-weight: var(--fw-title);
  font-size: var(--fs-h3);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
}
.event-row__meta { font-size: var(--fs-small); color: var(--ink-soft); }
.event-row__go { flex: 0 0 auto; color: var(--plum-600); display: inline-flex; }

.events-empty {
  font-size: var(--fs-lead);
  padding: clamp(1.25rem, 1rem + 1.1vw, 2rem);
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(15px);
  -webkit-backdrop-filter: blur(15px);
  border-radius: var(--radius-card);
  margin: 0;
}
.events-empty a { color: var(--plum-600); }

.events-heading {
  font-family: var(--font-title);
  font-size: var(--fs-h2);
  font-weight: var(--fw-title);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  margin: 0;
}

/* Inside the fixed-photo section the heading is the ONE piece of type that is
   not on a frosted card — it sits directly on the photograph. --plum-900 there
   was near-black on a dark image and effectively invisible; a screenshot caught
   it, no measurement would have. White plus a shadow, because the photograph
   behind it can be any brightness and this section is the one place the design
   cannot guarantee its own ground. */
.events-stage .events-heading {
  color: var(--paper);
  text-shadow: 0 1px 12px rgba(17, 16, 21, 0.65), 0 1px 2px rgba(17, 16, 21, 0.5);
}

/* ---- a single event's page -------------------------------------------- */

.event-page { padding-block: var(--section-y-tight) var(--section-y); }

.event-hero {
  display: grid;
  gap: clamp(1.5rem, 1rem + 3vw, 3rem);
  align-items: start;
}
/* Side by side once there is room for it; the figure is a 3:2 landscape photo
   and stacking it above the details pushes everything below the fold. */
@media (min-width: 860px) {
  .event-hero:has(.event-hero__figure) { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
}

.event-hero__figure { margin: 0; }
.event-hero__figure img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  filter: var(--photo-filter);
}

/* This had one rule and it was the title's; the title moved to the banner and
   took the rule with it, leaving the class set in the markup with nothing
   behind it. verify.js caught it — that check exists because on a prior site
   `.btn--red` was deleted from the CSS while sixteen pages still asked for it
   and every other check stayed green. */
.event-hero__body { display: flex; flex-direction: column; }
/* The title lives in the dark banner ABOVE the detail section, not inside
   .event-hero__body. It moved there because these 29 pages were the only ones
   on the site that opened on a white band, and the site header is fixed and
   transparent with light type until the page scrolls — so their nav was white
   on white at scroll 0. See tools/events.js and tools/test/theme.test.js. */
.event-banner .event-title {
  font-family: var(--font-title);
  font-size: var(--fs-h1-banner);
  font-weight: var(--fw-title);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  color: var(--paper);
  margin: 0;
}
.event-when {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-lead);
  line-height: 1.35;
  letter-spacing: 0.06em;
  color: var(--plum-700);
  margin: 0 0 0.9rem;
}
.event-passed {
  display: inline-block;
  font-size: var(--fs-small);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 0.3rem 0.8rem;
  margin: 0 0 1rem;
}
.event-details { margin: 0 0 var(--gap); }
.event-details dt {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
}
.event-details dd { margin: 0 0 0.75rem; }
.event-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: var(--gap); }
.event-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
  flex-wrap: wrap;
  margin-top: var(--section-y-tight);
  padding-top: var(--gap);
  border-top: 1px solid var(--hairline);
}

/* The markup is `<span>« Previous</span>Title` with no separator between them.
   utilities.css makes these links inline-flex to meet the 24px tap-target
   minimum, which turns the span and the bare text node into two adjacent flex
   items — so they rendered as "« PreviousSeaway Valley Music Academy Youth
   Choir", one run with no space. Pre-existing: the other theme does the same
   thing, and only reads as a near-miss there because its titles are sentence
   case. Uppercase made it unmissable.
   Stacking them is the fix: the direction label becomes a caption over the
   title, which is what a prev/next control should look like anyway. */
.event-nav__prev,
.event-nav__next { flex-direction: column; gap: 0.3rem; max-width: 46%; }
.event-nav__next { align-items: flex-end; text-align: right; }
.event-nav__prev span,
.event-nav__next span {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-eyebrow);
  color: var(--ink-soft);
}
.event-nav__prev, .event-nav__next,
.event-back {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  color: var(--plum-600);
}
.event-back { display: inline-block; margin-bottom: var(--gap); }

/* events.js wraps a single event's page in `.section--light`. */
.section--light { background: var(--paper); padding-block: var(--section-y-tight); }

/* ---- components/press.css ---- */
/* ============================================================================
   PRESS — coverage list.

   A dead link never renders as an anchor. Six of the thirty-one items are 404
   or their domain no longer resolves; those render as citations with a note.
   Handing a visitor a link into a 404 is worse than telling them it is gone.
   ========================================================================= */

.press-list { list-style: none; margin: 0; padding: 0; }

.press-item {
  display: flex;
  gap: clamp(0.75rem, 0.4rem + 1.1vw, 1.5rem);
  align-items: baseline;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--hairline);
}
.press-item:last-child { border-bottom: 0; }

.press-year {
  flex: 0 0 auto;
  min-width: 4.5ch;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-small);
  letter-spacing: 0.06em;
  color: var(--plum-600);
}

.press-link { text-decoration: none; }
.press-link .press-headline { text-decoration: underline; text-underline-offset: 2px; }
.press-link:hover .press-headline { color: var(--plum-700); }

.press-headline { color: var(--ink); }
.press-outlet {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin-top: 0.2rem;
}

.press-cite .press-headline { color: var(--ink-soft); }
.press-gone {
  font-size: var(--fs-small);
  font-style: italic;
  color: var(--ink-soft);
  white-space: nowrap;
}

.press-photos {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-3), 100%), 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}
.press-photo img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  filter: var(--photo-filter);
}
.press-photo p {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  margin: 0.65rem 0 0;
}

/* The lead press photo is a portrait, so it is editorial and desaturates with
   the rest. Album art does not — see the note in music.css. */
.press-photo-lead { filter: var(--photo-filter); }

/* ---- components/lessons.css ---- */
/* ============================================================================
   LESSONS — Katie Smith Studio.

   A separate teaching brand from the performing one, with its own wordmark.
   Seaway Valley Music Academy is the school she co-founded and is a different
   entity again; it is cross-linked, not merged.
   ========================================================================= */

.studio-mark {
  display: block;
  width: auto;
  max-width: min(340px, 100%);
  height: auto;
  /* It sits inside a centred .section-head. */
  margin: 0 auto var(--gap);
}

/* kss-logo.png and duo-1111-logo.png are PURE BLACK artwork on transparency —
   decoded, their only opaque colour is #000000. On the white bands that is
   correct and needs nothing. On a near-black band it is invisible: black on
   --plum-900 is about 1.1:1, and the element is present, correctly sized and
   perfectly accessible while showing nothing at all.
   The 11:11 press kit opens on exactly such a band. Because the source is a
   single flat colour, invert() gives clean white with no hue to correct. */
.section--dark .studio-mark,
.section--lilac .studio-mark,
.page-banner .studio-mark { filter: invert(1); }

.service-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-4), 100%), 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}

.service {
  padding: clamp(1.25rem, 1rem + 1.1vw, 1.75rem);
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
}
.service h3 {
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin: 0 0 0.75rem;
}
.service ul { list-style: none; margin: 0; padding: 0; }
.service li { padding: 0.2rem 0; font-size: var(--fs-small); }

.testimonial-list {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-3), 100%), 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}
.testimonial-quote { margin: 0 0 0.75rem; }
.testimonial-quote p { margin: 0; font-size: var(--fs-small); line-height: 1.7; }
.testimonial-by {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin: 0;
}

.credential-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1rem, 0.5rem + 1.6vw, 2rem);
  font-size: var(--fs-small);
}

/* The CommunityVotes badge is an award mark, not a photograph — it keeps its
   colour for the same reason album art does. */
.award-badge {
  display: block;
  width: auto;
  max-width: 200px;
  height: auto;
  border-radius: var(--radius-card);
  filter: none;
}

.location-list { list-style: none; margin: 0; padding: 0; }
.location-list li { padding: 0.35rem 0; }
.location-list strong { color: var(--plum-700); }

.testimonial { display: flex; flex-direction: column; justify-content: space-between; }

.studio-photos {
  list-style: none;
  margin: 0 0 var(--gap);
  padding: 0;
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-3), 100%), 1fr));
}
.studio-photos img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-card);
  filter: var(--photo-filter);
}
.studio-photos p {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin: 0.5rem 0 0;
}

/* ---- components/form.css ---- */
/* The contact form.

   Every control has a real <label>. The Contact Form 7 form this replaces had
   none — six fields carrying placeholder text only, which is invisible to a
   screen reader and disappears the moment a sighted user starts typing. */

.form-wrap { max-width: 640px; }

.form-row { margin: 0 0 1.1rem; display: flex; flex-direction: column; gap: 0.4rem; }

.form-row label {
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink);
}
.form-row .req { color: var(--plum-600); }
.form-row .opt {
  color: var(--ink-soft);
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: none;
}

.form-row input,
.form-row select,
.form-row textarea {
  width: 100%;
  padding: 0.8rem 0.9rem;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: var(--fs-body);
}
.form-row input:focus,
.form-row select:focus,
.form-row textarea:focus { border-color: var(--plum-500); }
.form-row textarea { resize: vertical; min-height: 9rem; }

.form-actions { margin-top: var(--gap); }

/* The honeypot. Hidden with CSS rather than type="hidden" so a bot filling
   every field it can see still trips it, while a screen reader is told to skip
   it by the aria-hidden on its wrapper. */
.form-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status:empty { display: none; }
.form-status {
  margin: 1rem 0 0;
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  font-size: var(--fs-small);
  background: var(--paper-alt);
}

/* Turnstile renders its own iframe into this div, so the box is not ours to
   style — but its SIZE is ours to reserve. Without a reserved box the div is
   0px until api.js loads and then jumps to 300x65, shoving the submit button
   down the page just as someone reaches for it. 300x65 is the "normal" widget,
   the default when data-size is not set. */
.cf-turnstile {
  min-height: 65px;
  margin: 4px 0;
}

/* ---- components/contact.css ---- */
/* The four contact routes. */

.contact-routes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min-3), 100%), 1fr));
}

.contact-route {
  padding: clamp(1.25rem, 1rem + 1.1vw, 1.75rem);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--paper);
}
.section--dark .contact-route { background: transparent; border-color: var(--hairline-dk); }

.contact-route h3 {
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-600);
  margin: 0 0 0.6rem;
}
.section--dark .contact-route h3 { color: var(--plum-400); }

.contact-route p { font-size: var(--fs-small); line-height: 1.65; margin: 0 0 0.9rem; }

.contact-email,
.contact-phone {
  display: block;
  font-size: var(--fs-body);
  color: var(--plum-600);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--transition), color var(--transition);
  margin-bottom: 0.3rem;
  overflow-wrap: anywhere;
}
.contact-email:hover, .contact-email:focus-visible,
.contact-phone:hover, .contact-phone:focus-visible {
  color: var(--plum-700);
  border-bottom-color: var(--plum-500);
}
.section--dark .contact-email, .section--dark .contact-phone { color: var(--plum-400); }

/* A visible, greppable marker for copy the client still owes us. Loud on
   purpose: a placeholder that ships quietly is how the old site served the
   literal string [woocommerce_my_account] to visitors for years. */
.is-todo {
  background: #fff4d6;
  border-left: 3px solid #d19b00;
  padding: 0.75rem 1rem;
  font-size: var(--fs-small);
  color: #6b4e00;
}

.contact-form { display: block; }

/* ---- components/footer.css ---- */
/* ============================================================================
   The footer.

   Apex's is a centred stack: a title, the rule-and-ring, a spaced row of
   social icons, and one wide-tracked credit line. That is the whole thing —
   no columns, no address block, no menu.

   Everything functional the original footer carried is kept (the logo, phone,
   email, the CTA, the designer credit) and recomposed into that centred stack,
   because the old site's footer was a single line — a copyright notice and a
   link to the theme vendor, with no contact details anywhere on it.
   ========================================================================= */

.site-footer {
  background: var(--plum-900);
  color: var(--plum-200);
  padding-block: var(--section-y);
  text-align: center;
}

.footer-head {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.footer-logo { margin-bottom: var(--gap); }
.footer-logo img {
  width: auto;
  height: auto;
  max-width: 190px;
  opacity: 0.9;
}

.footer-title {
  font-family: var(--font-title);
  font-weight: var(--fw-title);
  font-size: var(--fs-h2);
  line-height: var(--lh-title);
  letter-spacing: normal;
  text-transform: none;
  color: var(--paper);
  margin: 0;
}

/* Apex spaces its social row far wider than a normal icon group — the gap is
   as much of the effect as the icons are. */
.footer-social {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(1.75rem, 1rem + 3vw, 3.5rem);
  margin-top: 0.5rem;
}
.footer-social-link { color: var(--plum-200); transition: color var(--transition); }
.footer-social-link:hover, .footer-social-link:focus-visible { color: var(--plum-400); }
.footer-social-link svg { width: 22px; height: 22px; }

.footer-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap);
  margin-top: var(--section-y-tight);
}

.footer-contact {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem clamp(1.25rem, 0.75rem + 2vw, 2.5rem);
}
.footer-contact li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.footer-contact a {
  color: var(--plum-200);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--transition), border-color var(--transition);
}
.footer-contact a:hover,
.footer-contact a:focus-visible { color: var(--paper); border-bottom-color: var(--plum-500); }

.footer-cta { margin: 0; }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.4rem var(--gap);
  margin-top: var(--section-y-tight);
  padding-top: var(--gap);
  border-top: 1px solid var(--hairline-dk);
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--plum-200);
}
.footer-bottom p { margin: 0; }
/* The Butler credit its licence requires (fonts.css). It wears the footer
   line's own treatment, so it reads as part of the colophon, not as an ad. */
.footer-type-credit { color: var(--plum-200); }
.footer-bottom a { color: var(--plum-400); text-decoration: none; }
.footer-bottom a:hover, .footer-bottom a:focus-visible { color: var(--paper); }

/* ---- utilities.css ---- */
/* ============================================================================
   UTILITIES — loaded last, so these win.

   Kept deliberately small. A hard-coded value in a component file is a bug;
   a utility that duplicates a component rule is the same bug wearing a hat.
   ========================================================================= */

.to-top {
  position: fixed;
  right: 20px;
  bottom: 20px;
  width: 44px;
  height: 44px;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--plum-900);
  color: var(--paper);
  border: 1px solid var(--hairline-dk);
  border-radius: var(--radius);
  cursor: pointer;
  z-index: 25;
}
.to-top.is-visible { display: flex; }

/* ---- body-class hooks --------------------------------------------------
   build.js emits `page-<nav key>` on <body> so a page can restyle shared
   chrome without a second layout file. In this theme none of them needs to:
   the header is one fixed bar on every page and changes state by scroll
   position rather than by which page it is on. They are declared so the
   undefined-class check can tell a deliberate hook from a class whose rule was
   deleted out from under the markup — which is the defect that check exists to
   catch. */
.page-home, .page-music, .page-events, .page-press,
.page-lessons, .page-contact, .page-plain { }

.release-feature {
  display: grid;
  gap: clamp(1.5rem, 1rem + 3vw, 3.5rem);
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  align-items: center;
  justify-content: center;
  max-width: 900px;
  margin-inline: auto;
}
@media (max-width: 599px) {
  .release-feature { grid-template-columns: 1fr; justify-items: center; text-align: center; }
  .release-feature .listen-row { justify-content: center; }
}
.release-feature-cover {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.release-feature-body p { margin-bottom: 1rem; }

.press-photo-lead { width: 100%; height: auto; border-radius: var(--radius-card); }

/* Every editorial photograph on the site is desaturated to match the demo,
   which is black and white throughout. Album art, the award badge and the
   logo images are exempt — they are artwork and identity, not photography.
   (The header's K mark is inline SVG in currentColor and was never filtered.)
   Reversing the whole treatment is one line: --photo-filter: none. */
.hero-photo, .press-photo img, .press-photo-lead,
.studio-photos img, .event-hero__figure img { filter: var(--photo-filter); }
.release-cover, .release-feature-cover, .award-badge,
.studio-mark, .footer-logo img { filter: none; }

/* 00-utils.js sets this on <html> as its first act, so a rule can target
   "JavaScript ran". The back-to-top button is hidden until then: with JS off
   it would never appear, and a control that cannot work should not be painted.
   Declared here so the undefined-class check can tell this deliberate hook
   from a class whose rule was deleted out from under the markup. */
html:not(.js) .to-top { display: none; }

/* ---- tap targets -------------------------------------------------------
   WCAG 2.2 SC 2.5.8 sets a 24x24 CSS-pixel minimum. Measured in a real
   browser at 360/768/1024/1440, three controls failed it:

     .footer-social-link   18x18  (the svg's own size, with no padding)
     .release-link         short text links standing alone in a list
     .listen-link          the same, larger
     .event-nav__prev/next

   The SC exempts links inline in a sentence. None of these is: they are all
   standalone controls in a list, so they need the box. The icons keep their
   18px glyph and gain padding rather than growing, so nothing reflows. */

.footer-social-link,
.nav-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  padding: 3px;
}

.release-link,
.listen-link,
.event-nav__prev,
.event-nav__next,
.event-back,
.press-photo p a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

/* The last two SC 2.5.8 failures the sweep found: breadcrumb links (47x19) and
   the credential links on /lessons/ (239x19). Both are standalone controls in
   a list rather than links inline in a sentence, so neither is exempt. */
.breadcrumb a,
.credential-list a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}
