/* ============================================================================
   Chassidish Choir — pages.css
   Everything the inner pages need that base.css does not already give:
     · media bar + section filter chips        (videos.html)
     · mixed-orientation grid + masonry wall   (videos.html / gallery.html)
     · the booking CTA band                    (videos.html / gallery.html)
     · contact layout, details rail, form      (contact.html)
     · the 404 panel                           (404.html)

   Rules followed here:
     - No new colors. Every value below resolves to a token in tokens.css.
     - Nothing that base.css already defines is redeclared. Where a base.css
       rule is deliberately overridden, the reason is in a comment right above
       the override — see .gallery-masonry (display) and .grid-videos
       (.card--portrait's frame ratio).
     - .loadmore and .card__frame--noimg are media.js class names that home.css
       also styles; home.css is not loaded on these pages, so they are repeated
       here on purpose and kept identical.
   ========================================================================== */

/* ============================================================================
   1. MEDIA BAR — the row above a grid: filter chips on the left, count right
   ========================================================================== */

.media-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-6);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid var(--rule-soft);
}

/* Before media.js runs, the chips are empty and the count is blank — an empty
   bar is just a stray hairline, so it stays out of the way until it has
   something to say. (Browsers without :has simply show the empty bar.) */
.media-bar:not(:has(.filters > *)):has([data-media-count]:empty) { display: none; }

.media-count {
  margin: 0;
  max-width: none;
  font-size: var(--t-label);
  font-weight: 400;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--fg-faint);
}
.media-count [data-media-count] { color: var(--gold-300); }

/* ---- Section filter chips -------------------------------------------------
   media.js owns the markup inside [data-media-filters]; we do not know whether
   it emits <button> or <a>, so every plausible child is styled the same and the
   active state is matched on all three conventions it might use. */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  min-width: 0;
}
.filters:empty { display: none; }

.filters > button,
.filters > a,
.filters .chip {
  display: inline-flex;
  align-items: center;
  min-height: 38px;
  padding: 0.35em 1.15em;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--fg-dim);
  font-family: var(--font-ui);
  font-size: var(--t-small);
  font-weight: 400;
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              background var(--dur) var(--ease-out);
}

.filters > button:hover,
.filters > a:hover,
.filters .chip:hover {
  color: var(--gold-100);
  border-color: var(--rule);
}

.filters > button[aria-pressed="true"],
.filters > a[aria-current],
.filters .chip.is-active,
.filters .is-active,
.filters [data-active] {
  color: var(--plum-950);
  background: var(--gold-300);
  border-color: var(--gold-300);
  font-weight: 500;
}

/* ============================================================================
   2. GRID PLUMBING shared by both media pages
   ========================================================================== */

/* The <button> UA reset that used to live here (padding / text-align / font) has
   moved onto `.card` itself in base.css. Scoping it to these two grids is what
   left every card on the HOME page with Chrome's 1px 6px button padding showing
   as a band between the border and the picture. Don't re-scope it. */

/* media.js sets .is-empty and drops a single .state box in. In a multi-column
   layout that box would sit in column one; flatten the container while empty. */
.grid-videos.is-empty,
.gallery-masonry.is-empty { display: block; }

/* .loadmore and .card__frame--noimg are media.js's own class names. home.css
   already styles them for index.html, but home.css is not loaded here — these
   are the same rules so the two pages match. */
.loadmore { margin: var(--sp-6) auto 0; display: flex; }

.card__frame--noimg {
  background:
    radial-gradient(circle at 50% 45%, rgba(184, 137, 47, 0.22), transparent 62%),
    var(--plum-800);
}
.card__frame--noimg::after {
  content: "";
  position: absolute;
  inset: 22%;
  background: url("../img/seal.webp") center / contain no-repeat;
  opacity: 0.28;
  filter: grayscale(1) brightness(1.5);
}

/* ============================================================================
   3. VIDEOS GRID — mixed orientation without a hole in the row
   ========================================================================== */

/* The vertical clip is real — one of the six is 1080x1920 — and a 9/16 card
   dropped into a row of 16/9 cards leaves a crater under its neighbours.
   Fix without reordering anything (the client's rule is newest-first, so a
   masonry column flow that reads top-to-bottom is not available here):

     · the portrait card spans TWO grid rows, so it is genuinely tall;
     · every card is a flex column whose frame grows to fill its row, so the
       row always closes flush instead of leaving a hole;
     · grid-auto-flow: dense backfills the cell the tall card would strand.

   The 9/16 frame from base.css is dropped for a fill-the-span frame; the
   lightbox still opens the clip at its true ratio. */
.grid-videos {
  grid-auto-flow: dense;
  align-items: stretch;
}

.grid-videos > .card {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.grid-videos > .card .card__frame { flex: 1 1 auto; }

.grid-videos > .card--portrait {
  grid-row: span 2;
}
/* 3/4 is the FLOOR, not the shape: flex-grow above lets the frame fill the two
   rows it spans. Without a floor the card collapses to just its caption bar the
   moment the grid drops to one column and no sibling is left to size the rows. */
.grid-videos > .card--portrait .card__frame {
  aspect-ratio: 3 / 4;
  min-height: 0;
}
/* One column: there is nothing to span, and spanning is what collapsed it. */
@media (max-width: 700px) {
  .grid-videos > .card--portrait { grid-row: auto; }
}

/* Videos are 16/9 or vertical in practice (PLAN §7); a "square" clip is treated
   as landscape so it does not set an odd height for its whole row. */
.grid-videos > .card--square .card__frame { aspect-ratio: 16 / 9; }

/* ============================================================================
   4. GALLERY — a masonry wall, not the dead-black grid
   ========================================================================== */

.gallery-masonry {
  /* OVERRIDE of base.css .grid-media { display: grid }.
     A photo set of mixed orientation in an equal-row grid is exactly the
     uneven dead-space look on yedidimchoir.com (reference/yed-2.png). CSS
     columns let every photo keep its own height and close the gaps. Aspect
     ratios stay declared below so nothing shifts while images load. */
  --gal-gap: clamp(1rem, 1.6vw, 1.75rem);
  display: block;
  /* Explicit counts rather than a column-width, so the phone gets 2-up instead
     of one full-width photo per screen and a 5,000px scroll. */
  columns: 2;
  column-gap: var(--gal-gap);
}
/* Capped at 3. Four columns balance badly — break-inside:avoid leaves the last
   column ending several hundred px short — and 3 keeps the faces big enough to
   be worth looking at, which is the whole point of the wall. */
@media (min-width: 620px) { .gallery-masonry { columns: 3; } }

.gallery-masonry > * {
  break-inside: avoid;
  margin-bottom: var(--gal-gap);
}

.gallery-masonry .card { width: 100%; }

/* Three declared shapes so the wall staggers on purpose instead of by accident,
   and so a portrait photo is a portrait photo. */
.gallery-masonry .card__frame { aspect-ratio: 3 / 2; }
.gallery-masonry .card--portrait .card__frame { aspect-ratio: 3 / 4; }
.gallery-masonry .card--square .card__frame { aspect-ratio: 1 / 1; }

/* If a batch happens to be all-landscape, this keeps the column tops from
   lining up into a grid again. */
.gallery-masonry .card:nth-child(4n + 1):not(.card--portrait):not(.card--square) .card__frame {
  aspect-ratio: 5 / 4;
}

/* No caption bar on the wall. media.js takes a photo's title from its filename,
   and the client's photos are camera dumps — LASH0851, DSC00602, YST_4471. A
   row of serial numbers under the pictures is the watermark problem all over
   again. The card keeps its aria-label, and the lightbox still names the file. */
.gallery-masonry .card__meta { display: none; }

/* Anything media.js drops in that is NOT a card — empty state, an error note, a
   stray button — spans the full wall instead of hiding in column one.
   (:not(.card) matters: the cards themselves are <button> elements.) */
.gallery-masonry > .state,
.gallery-masonry > p,
.gallery-masonry > button:not(.card) { column-span: all; }
.gallery-masonry > button:not(.card) { display: block; margin-inline: auto; }

/* ============================================================================
   5. BOOKING CTA BAND — slim, closes Videos and Gallery
   ========================================================================== */

.cta-band {
  position: relative;
  padding-block: clamp(2.75rem, 2rem + 3.4vw, 5rem);
  background: var(--plum-900);
  border-top: 1px solid var(--rule);
  overflow: hidden;
}
.cta-band::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -70%;
  width: min(880px, 130vw);
  aspect-ratio: 1;
  translate: -50% 0;
  background: radial-gradient(circle, rgba(184, 137, 47, 0.14), transparent 62%);
  pointer-events: none;
}

.cta-band__inner {
  position: relative;
  z-index: var(--z-content);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5) var(--sp-7);
}
.cta-band__copy { min-width: 0; }
.cta-band__copy .label { margin-bottom: var(--sp-3); }
.cta-band__copy .lead { margin-top: var(--sp-3); }
.cta-band .btn { flex: 0 0 auto; }

@media (max-width: 640px) {
  .cta-band__inner { flex-direction: column; align-items: flex-start; }
  .cta-band .btn { width: 100%; }
}

/* ============================================================================
   6. CONTACT — two columns: the form, and the details rail
   ========================================================================== */

.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: clamp(2.25rem, 4vw, 4.5rem);
  align-items: start;
}
@media (max-width: 940px) {
  .contact__grid { grid-template-columns: minmax(0, 1fr); }
}

.contact__h { margin-bottom: var(--sp-4); }
.contact__intro { color: var(--fg-dim); }

/* ---- Details rail --------------------------------------------------------- */

.contact__rail {
  position: relative;
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--plum-900);
  overflow: hidden;
}
.rail__seal {
  position: absolute;
  right: -20%;
  top: -14%;
  width: min(260px, 60%);
  opacity: 0.06;
  filter: grayscale(1) brightness(1.6);
  pointer-events: none;
}
.contact__rail > *:not(.rail__seal) { position: relative; z-index: var(--z-content); }

.rail__list {
  list-style: none;
  margin: var(--sp-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--sp-5);
}
.rail__item {
  display: grid;
  gap: var(--sp-2);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid var(--rule-soft);
}
.rail__item:last-child { padding-bottom: 0; border-bottom: 0; }

.rail__k {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-400);
}
.rail__v {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 1.1rem + 0.85vw, 1.9rem);
  line-height: 1.15;
  color: var(--cream);
  overflow-wrap: anywhere;
}
a.rail__v { transition: color var(--dur) var(--ease-out); }
a.rail__v:hover { color: var(--gold-200); }

/* Deliberately does NOT look like a real value — nothing here is invented. */
.rail__v.is-pending {
  justify-self: start;
  padding: 0.35em 1em;
  border: 1px dashed var(--rule);
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: var(--t-small);
  font-weight: 400;
  letter-spacing: var(--track-nav);
  color: var(--fg-faint);
}

.rail__rule { margin-top: var(--sp-6); }
.rail__note {
  margin-top: var(--sp-5);
  font-size: var(--t-small);
  color: var(--fg-dim);
}

/* ============================================================================
   7. FORM CONTROLS on the dark plum surface
   ========================================================================== */

.form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5);
  margin-top: var(--sp-6);
}
@media (max-width: 560px) {
  .form { grid-template-columns: minmax(0, 1fr); }
}

.field { display: grid; gap: var(--sp-2); min-width: 0; }
.field--full { grid-column: 1 / -1; }

.field > label {
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-300);
}
.req { color: var(--blush-400); }

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 48px; /* thumb target */
  padding: 0.7rem 1rem;
  font-family: var(--font-ui);
  font-size: 1rem; /* 16px — anything smaller makes iOS zoom on focus */
  font-weight: 300;
  line-height: 1.5;
  color: var(--cream);
  background: var(--plum-800);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  transition: border-color var(--dur) var(--ease-out),
              background var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}
.field input::placeholder,
.field textarea::placeholder { color: var(--fg-faint); }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--rule); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  background: var(--plum-700);
  border-color: var(--gold-500);
  outline: none; /* replaced by the ring below, which is more visible on plum */
}
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--gold-300);
  outline-offset: 2px;
}

.field textarea {
  min-height: 148px;
  line-height: 1.6;
  resize: vertical;
}

.field [aria-invalid="true"] { border-color: var(--blush-500); }

/* ---- Select: the OS arrow is replaced with our own ------------------------ */

/* ⭐ ROUND 6, R6-19 — AND THE ONE FAULT THIS WOULD HAVE CAUSED IF IT WERE LEFT
   PHYSICAL. The ten options are now Yiddish, so the select carries dir="rtl"
   and its text starts at the RIGHT edge — which is exactly where these two
   rules used to reserve the space for our own chevron. Written physically
   (`padding-right`, `right`) the arrow would have been drawn straight on top of
   the first letter of חתונות. Written logically it follows the direction of the
   field it belongs to and lands on the left, where the text now ends.

   ⚠️ The wrap carries dir="rtl" too, not just the <select>. ::after is a child
   of the WRAP, and inset-inline-end resolves against the wrap's own direction —
   set it only on the select and the arrow would not have moved at all. */
.select-wrap { position: relative; display: block; }
.select-wrap select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: 2.9rem;
  cursor: pointer;
}
.select-wrap::after {
  content: "";
  position: absolute;
  inset-inline-end: 1.15rem;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--gold-300);
  border-bottom: 1.5px solid var(--gold-300);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}
/* The dropdown list itself is OS-drawn; this is as far as it can be pushed. */
.select-wrap select option {
  background: var(--plum-900);
  color: var(--cream);
}
/* Frank Ruhl Libre for the Yiddish, and a size that does not shrink it next to
   the Latin fields either side of it. Hebrew has no lower case, so the same
   16px reads smaller than the same 16px of Jost — but 16px is also the floor
   that stops iOS zooming the page on focus, so this goes UP, never down. */
.select-wrap select[lang="yi"] {
  font-family: var(--font-yi);
  font-size: 1.06rem;
  font-weight: 400;
}
.select-wrap select[lang="yi"] option[lang="en"] {
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 300;
}
/* ⚠️ A CLOSED SELECT DRAWS THE CHOSEN OPTION IN THE SELECT'S OWN FONT, NOT THE
   OPTION'S. So the rule above styles the placeholder in the open list and does
   nothing at all to the field the visitor is actually looking at — which, until
   they pick something, is showing the English "Choose one" in the Hebrew face.
   This is the rule that reaches it, and it also dims it, so an untouched field
   reads as empty rather than as a choice already made. That was true of the
   English version too and it is fixed here.

   :has() is doing the work and it fails safe: in a browser without it the
   placeholder simply stays in the Yiddish face — legible, just not matched. */
.select-wrap select[lang="yi"]:has(option[value=""]:checked) {
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 300;
  color: var(--fg-faint);
}

/* ---- Date: the native picker glyph is black-on-plum by default ------------ */

.field input[type="date"] { cursor: text; }
.field input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(0.68);
  cursor: pointer;
}
.field input[type="date"]::-webkit-calendar-picker-indicator:hover { filter: invert(1) opacity(1); }

/* ---- Honeypot -------------------------------------------------------------
   Off-screen, NOT display:none — bots skip hidden-by-display fields. Paired
   with tabindex="-1" in the markup so it is unreachable by keyboard. */
.hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---- Errors, actions, status --------------------------------------------- */

.field__err {
  margin: 0;
  max-width: none;
  font-size: var(--t-small);
  line-height: 1.4;
  color: var(--blush-400);
}
.field__err[hidden] { display: none; }

.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-5);
  margin-top: var(--sp-2);
}
.form__note {
  margin: 0;
  max-width: 34ch;
  font-size: var(--t-small);
  color: var(--fg-faint);
}
.form__actions .btn[disabled] { opacity: 0.6; cursor: progress; }

.form__status {
  grid-column: 1 / -1;
  margin: 0;
  max-width: none;
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--rule-soft);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--plum-800);
  font-size: var(--t-small);
  line-height: 1.55;
}
.form__status:empty { display: none; }
.form__status.is-ok { border-color: var(--gold-500); color: var(--gold-100); }
.form__status.is-err { border-color: var(--blush-500); color: var(--blush-200); }

/* Turnstile renders nothing until a site key is pasted in — no empty gap. */
.cf-turnstile:empty { display: none; }

/* ############################################################################
   ROUND 4 — the new pages. Music (items 8-10), Services (16), Shabbos (13/19),
   Media (15). Everything below this line is new; nothing above it was moved.
   ########################################################################## */

/* ============================================================================
   A PAGE HEADER WHOSE HEADING IS YIDDISH
   ============================================================================
   The booking page's h1 became `א שמחה מיט חסיד'יש: איז חסיד'יש` (item 11), and
   that broke two assumptions the header was built on. Both were only findable
   by looking at the page:

   1. THE MUSICAL MARK COLLIDED WITH THE HEADING. The mark is placed on the
      right because on an English page the right two-thirds of the header are
      empty — that is the whole reason it exists (item 8, "it shouldn't look
      that empty"). An RTL heading fills exactly that space, so the quaver was
      printed straight through the words. It moves to the LEFT, which is the
      side that is now empty. Same rule, mirrored, because the language is.

   2. THE BLOCK READ FROM BOTH EDGES. Label and sub-line hard left, heading hard
      right, 700px of nothing between them. The English furniture stays English
      — only its alignment follows the heading.

   3. And --t-h1 tops out at 6rem, which is right for `Watch` and far too big
      for 31 Hebrew characters: it wrapped to two lines and swamped the page.

   `:has()` rather than a class on the markup, so this applies itself to any
   future page whose h1 is set RTL and cannot be forgotten. pages.css already
   depends on `:has()` (see `.media-bar`), so this adds no new requirement.

   ============================================================================
   ⭐⭐ ROUND 6 — R6-15a / R6-17b. POINT 2 ABOVE WAS WRONG AND HE REJECTED IT
   IN THREE PLACES. HIS WORDS:
   ============================================================================
     "You made the words on the right side. I don't know. You gotta bring it
      back to the left side."
     "The writing is on the right side, which the entire website everything's
      on the left side. So let's get it moved to the left side."

   THE RULE, SETTLED: Yiddish keeps its reading direction. A block is NEVER
   pushed to the right-hand side of the page because its text happens to be
   Yiddish. So the eyebrow, the heading and the sub-line all sit on the same
   left edge as Music, Gallery, Videos and Services.

   ⚠️ `text-align: left` has to be stated, not merely removed. The initial
   value is `start`, and `start` resolves against the ELEMENT'S OWN direction
   — so an h1 carrying `direction: rtl` would still throw itself to the right
   the moment nothing says otherwise. Removing the rule would have looked like
   a fix and changed nothing.

   ⚠️ And the musical mark goes BACK to the right. It was moved left because
   an RTL heading filled the right two-thirds of the header; with the block on
   the left, the empty side is the right again — which is the side the mark
   exists to fill. That override is deleted rather than rewritten.

   What does NOT change: `direction: rtl` on the heading itself, and the size
   below. 31 Hebrew characters at --t-h1 wrapped to two lines and swamped the
   page; that was never the complaint. */
.page-hero:has(h1[dir="rtl"]) .wrap { text-align: left; }
.page-hero:has(h1[dir="rtl"]) .lead { margin-inline-start: 0; }
.page-hero:has(h1[dir="rtl"]) h1 { font-size: clamp(1.6rem, 1.1rem + 2.5vw, 2.9rem); }

/* A tile at eyebrow scale, for a section label that carries an icon. He asked
   for these on the Music page by name. Sized off the label's own font so it can
   never grow out of proportion with the four words beside it. */
.tile--sm {
  width: 2.25em;
  height: 2.25em;
  border-radius: 0.7em;
  font-size: 1em;
}
.tile--sm .ic { width: 1.15em; height: 1.15em; }
/* .label is inline-flex with a leading rule; the tile becomes the second item
   and needs a little more air than the 0.7em word gap. */
.label .tile--sm { margin-inline-end: 0.15em; }

/* ============================================================================
   9. MUSIC — Live Recordings (item 9)
   ============================================================================
   "One place it should be Live Recordings, and one place it should be Albums…
   everything should be in the music page with two sections."

   The two sections deliberately do NOT share a component. A live recording is
   a thing that PLAYS, in place, and its card is a row: play button, title,
   scrub bar, time. An album is a PRODUCT whose card is a picture that leaves
   the site. Making them look the same would be tidier and would lie about what
   tapping them does.
   ========================================================================== */

.tracks {
  display: grid;
  gap: var(--sp-3);
  max-width: 860px;
}

/* ⚠️ ROUND 8 — R8-3: FOUR COLUMNS NOW, and the row is a <div> rather than a
   <button>. A slider and a download link cannot live inside a button, so the
   play circle became the real button and the row became a container. Everything
   below that used to style a <button> — the reset of font/colour/text-align —
   is kept, because .track__btn now needs it. */
.track {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--sp-4);
  width: 100%;
  padding: clamp(0.7rem, 0.55rem + 0.6vw, 1rem) clamp(0.85rem, 0.6rem + 0.8vw, 1.35rem);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-lg);
  background: linear-gradient(168deg, rgba(74, 49, 89, 0.3), rgba(21, 12, 27, 0.46));
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease-out),
              background var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.track:hover { border-color: var(--rule); transform: translateY(-2px); }
.track.is-playing { border-color: var(--gold-500); background: linear-gradient(168deg, rgba(184, 137, 47, 0.16), rgba(21, 12, 27, 0.5)); }

/* ⚠️ ROUND 8 — R8-3: THIS IS A REAL <button> NOW, so it arrives carrying the
   user-agent's own padding and font. Without the two resets the 46px box has a
   content area of about 30px and the icon sits off-centre inside its circle —
   which is the exact complaint the play triangle was already fixed for once. */
.track__btn {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  padding: 0;
  font: inherit;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: rgba(21, 12, 27, 0.6);
  color: var(--gold-200);
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.track__btn .ic { width: 18px; height: 18px; }
.track:hover .track__btn,
.track.is-playing .track__btn { background: var(--gold-300); color: var(--plum-950); border-color: var(--gold-300); }

.track__main { min-width: 0; }
.track__title {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.4rem);
  line-height: 1.2;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.track__sub {
  display: block;
  margin-top: 0.15em;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-400);
}

/* ---- ROUND 8 — R8-3: THE SCRUBBER ---------------------------------------- *
   "I'm not able to forward, like, the skip the beginning, and I'm not able to
    go back once it's starting to play."

   It was a `.track__bar` / `.track__fill` pair — two spans that displayed the
   position and could not be touched. It is a real <input type="range"> now, so
   dragging, tapping, touch and the arrow keys all work without a line of
   pointer code.

   It still only appears on the row that is playing — an idle list of empty
   progress bars reads as a broken page, and that was a deliberate call.

   ⚠️ THE BOX IS 18px TALL AND THE LINE IS STILL 3px. A 3px hit target is not
   usable with a finger, and it is barely usable with a mouse. The extra 15px is
   pulled back out with negative margins, so the row's LAYOUT height is the same
   3px it always was and the list does not jump any harder than before — the
   grab area simply reaches up and down into the row's own padding, which is
   empty. 18 - 7 - 8 = 3.
   ⚠️ 8px BELOW, NOT MORE. On the desktop two-column panel the row's bottom
   padding bottoms out near 10px, and a box that reached past it would be
   catching clicks meant for the NEXT row's title.

   ⚠️ ONE CUSTOM PROPERTY DRIVES BOTH ENGINES. WebKit and Gecko name the track
   pseudo-element differently and neither can be reached from JS, so the played
   gold is a background sized by --p, which audio.js sets. Do not try to animate
   the width of a pseudo-element from script. */
.track__seek {
  grid-column: 1 / -1;
  display: none;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 18px;
  margin: -7px 0 -8px;
  padding: 0;
  background: transparent;
  cursor: pointer;
  /* ⚠️ pan-y, NOT none. `none` would be the usual advice for a custom slider,
     but this one is a full-width strip across a scrolling page: a thumb landing
     on it mid-swipe would find the page frozen. pan-y hands vertical gestures
     back to the page and keeps horizontal ones for the scrub. */
  touch-action: pan-y;
}
.track.is-playing .track__seek { display: block; }

/* The two engines will not accept these selectors in one list — if either is
   unknown the whole rule is dropped — so the track and the thumb are each
   written out twice on purpose. Do not "tidy" them together. */
.track__seek::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: var(--radius-pill);
  background:
    linear-gradient(90deg, var(--gold-500), var(--gold-200)) 0 / calc(var(--p, 0) * 1%) 100% no-repeat,
    var(--rule-soft);
}
.track__seek::-moz-range-track {
  height: 3px;
  border-radius: var(--radius-pill);
  background:
    linear-gradient(90deg, var(--gold-500), var(--gold-200)) 0 / calc(var(--p, 0) * 1%) 100% no-repeat,
    var(--rule-soft);
}
.track__seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 13px;
  margin-top: -5px; /* (3px track - 13px thumb) / 2 — WebKit does not centre it */
  border-radius: 50%;
  border: 1px solid var(--plum-950);
  background: var(--gold-100);
  box-shadow: 0 0 0 3px rgba(21, 12, 27, 0.55);
}
.track__seek::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 1px solid var(--plum-950);
  background: var(--gold-100);
  box-shadow: 0 0 0 3px rgba(21, 12, 27, 0.55);
}
.track__seek:focus-visible {
  outline: 2px solid var(--gold-300);
  outline-offset: 3px;
  border-radius: var(--radius-pill);
}

/* ---- ROUND 8 — R8-3: DOWNLOAD -------------------------------------------- *
   "how about we put a download so we should be able to download it?"

   ⚠️ QUIET UNTIL IT IS WANTED. Three rows each shouting a second gold circle
   would turn a list of songs into a list of buttons, and the play control is
   the one that matters. So it sits at the dim body colour and only comes up to
   gold on hover, on focus, or on the row that is playing.

   ⚠️ 44px SQUARE. It is the smallest thing on the row and it is next to the
   scrubber; below the accepted touch minimum this becomes the control people
   hit when they meant to seek. The icon inside stays 17px. */
.track__dl {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-right: calc(var(--sp-2) * -1);
  border-radius: 50%;
  color: var(--fg-dim);
  transition: color var(--dur) var(--ease-out), background var(--dur) var(--ease-out);
}
.track__dl .ic { width: 17px; height: 17px; }
.track.is-playing .track__dl { color: var(--gold-300); }
.track__dl:hover,
.track__dl:focus-visible {
  color: var(--gold-100);
  background: rgba(21, 12, 27, 0.55);
}

.track__time {
  font-size: var(--t-mini);
  letter-spacing: var(--track-nav);
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.track.is-playing .track__time { color: var(--gold-200); }

@media (max-width: 520px) {
  .track { gap: var(--sp-3); }
  .track__btn { width: 40px; height: 40px; }
  .track__dl { width: 40px; height: 40px; }
  /* ⚠️ THE TIME IS BACK ON THE PLAYING ROW, and only on it. It used to be hidden
     outright at this width because it said nothing a bar could not — but now
     that he can JUMP, the number is the thing that tells him where he landed.
     Idle rows still hide it, which is what keeps the row from crowding. */
  .track__time { display: none; }
  .track.is-playing .track__time { display: block; font-size: var(--t-label); }
}

/* ============================================================================
   10. MUSIC — Albums (items 9 + 10)
   ============================================================================
   "In the album it needs to have an image from the album, and then when they
   click on the image it's gonna take them to the link where they can purchase
   the album."

   ⚠️ THE TEASER IS OPTIONAL AND THE LAYOUT MUST NOT NEED IT.
   "He should have the ability, but he doesn't need to." So the album is a
   two-column pair ONLY when a teaser exists; with a cover alone it is a single
   centred card at the same size, not a two-column grid with a hole in it. That
   is why the second column is a real grid item rather than a fixed track.
   ========================================================================== */

.albums { display: grid; gap: clamp(1.5rem, 3vw, 3rem); }

/* ⭐ R7-2 — THE THREE PARTS ARE PLACED BY NAME, NOT BY SOURCE ORDER.
   audio.js now emits cover, teaser and words as three SIBLINGS (the
   `.album__col` wrapper is gone — see the note in audio.js). Naming the areas
   is what lets the words run underneath BOTH of them as one row, which is his
   tweak: "the words on the bottom should be in one row."
   Stacked, it is simply one column in the order they are written. */
.album {
  display: grid;
  gap: clamp(1.25rem, 2.4vw, 2.5rem);
  align-items: start;
  grid-template-areas:
    "cover"
    "teaser"
    "meta";
}
.album__buy { grid-area: cover; }
.album__teaser { grid-area: teaser; }
.album__meta { grid-area: meta; }

/* Only pairs up when there is genuinely a second thing to show.
   ⚠️ NOT 1fr 1fr. At 1280 that gives the teaser a 570px column for a 290px-wide
   vertical clip, and centring it inside leaves a 180px hole between the cover
   and the teaser — measured on screen, they read as two unrelated things
   rather than as a pair. 0.62 sizes the second column to what actually goes in
   it.
   (Inside the two-column MUSIC layout this is overridden again — R7-2 makes
   them equal there, because there the teaser is squared off to match.) */
/* ⭐ R7-2 — EQUAL, NOT 0.62. His tweak: "I want the teaser with the album
   should be the same size."
   ⚠️ THIS REPLACES A ROUND-6 DECISION ON PURPOSE. 0.62 existed because the
   teaser was a tall 9/16 clip and a wide column left a hole beside it. The
   teaser is squared off now (see `.album__teaser` below), so the reason for the
   uneven split is gone with it. Two squares, side by side, same size. */
@media (min-width: 780px) {
  .album:has(.album__teaser) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "cover teaser"
      "meta  meta";
  }
}
.album:not(:has(.album__teaser)) { max-width: 520px; margin-inline: auto; }

/* The cover IS the buy button. His instruction, and it is also the honest
   design: the picture is the product. */
.album__buy {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* The artwork is dark aubergine and rose-gold on a dark page — without a rim
     it dissolves into the background. */
  box-shadow: 0 0 0 1px var(--rule), var(--shadow);
  transition: transform var(--dur-slow) var(--ease-out), box-shadow var(--dur-slow) var(--ease-out);
}
.album__buy:hover {
  transform: translateY(-6px);
  box-shadow: 0 0 0 1px var(--gold-500), var(--shadow-lg);
}
.album__buy img { width: 100%; height: auto; display: block; }

/* A visitor must be able to tell the picture is a link out to a shop BEFORE
   tapping it, not after. */
.album__tag {
  position: absolute;
  left: var(--sp-4);
  bottom: var(--sp-4);
  right: var(--sp-4);
  display: flex;
  align-items: center;
  gap: 0.6em;
  padding: 0.7em 1.15em;
  border-radius: var(--radius-pill);
  background: rgba(21, 12, 27, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--rule);
  color: var(--gold-200);
  font-size: var(--t-mini);
  font-weight: 500;
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.album__tag .ic { width: 1.15em; height: 1.15em; }
.album__tag .ic:last-child { margin-left: auto; }
.album__buy:hover .album__tag { background: var(--gold-300); color: var(--plum-950); border-color: var(--gold-300); }

/* ⚠️ WAS `margin-top: var(--sp-4)`. It sat directly under the cover inside the
   old `.album__col` wrapper, so it had to space itself. It is a grid AREA now
   and the grid's own gap does that job — leaving the margin in would have
   double-spaced it. */
.album__meta { margin-top: 0; }
.album__title { font-family: var(--font-display); font-size: var(--t-h3); color: var(--cream); }
.album__sub { margin-top: 0.2em; font-size: var(--t-mini); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--gold-400); }

/* ---- the teaser ----------------------------------------------------------
   His file is 1080x1920 — natively vertical. A vertical clip in a square-ish
   column is the letterbox problem from the videos grid all over again, so it
   keeps its real shape.

   ⚠️ IT IS A POSTER CARD, NOT AN INLINE <video controls>. The first build used
   the native player, and on screen Chrome's own control bar — grey scrubber,
   volume, the three-dot menu — sat across the bottom of a gold-and-plum album
   card and looked like a browser had leaked into the design. Every other video
   on this site opens in the site's own lightbox; this one now does too, so the
   teaser behaves like the rest of the site instead of being the one exception. */
/* ⚠️ FOUND WHILE BUILDING R6-9a, AND IT WAS THERE BEFORE: this is a <figure>,
   and a <figure> carries a UA margin of 40px on each side. Nothing reset it. It
   went unnoticed while the teaser column was 450px wide — the card is capped at
   300px and centred, so 80px of margin inside a 450px track changed nothing you
   could see. The moment the column narrowed to 189px the same 80px ate it, and
   the teaser rendered 112px wide: a postage stamp beside a 416px album cover.
   Measured on the page, not guessed at. */
/* ⭐⭐ R7-2 — THE TEASER IS NOW SQUARE, AND THAT REVERSES THE PARAGRAPH ABOVE.
   His tweak, in his words: "I want the teaser with the album should be the same
   size." The album cover is genuinely 1400x1400, so 1/1 is the COVER'S shape —
   not a number chosen to make a row look tidy. The poster is 1080x1920 and is
   cropped to fill rather than letterboxed inside it.

   ⚠️ `aspect-ratio` DOES NOT CLAMP A BOX. With the image left in the flow, a
   1080x1920 poster pushed this frame to 252x448 while `getComputedStyle`
   reported `1 / 1` and agreed with me. Only `getBoundingClientRect` caught it.
   The image has to LEAVE the flow — that is what `position: absolute` is for.
   ⚠️ And `.card__frame` is a <span>. The moment the image left the flow, an
   inline box with nothing in it collapsed to 0x0 — that is what `display:
   block` is for. Both lines are load-bearing; neither is tidiness. */
.album__teaser { display: grid; gap: var(--sp-3); justify-items: stretch; margin: 0; }
.album__teaser .card {
  width: 100%;
  max-width: none;
  border-radius: var(--radius-lg);
}
.album__teaser .card__frame {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
}
.album__teaser .card__frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.album__teaser figcaption {
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--fg-faint);
}

/* ============================================================================
   ⭐⭐ 10b. MUSIC — THE TWO COLUMNS SIDE BY SIDE  (ROUND 6 — R6-9a/b/c)
   ============================================================================
   HIS WORDS, all three complaints in one breath:

     "I don't like that it's recording first and then the album. I want it to
      be, like, sideways. The left side, let's say the recordings and the right
      side of the album. So there's no much scrolling. Everything sits on one
      page. Obviously, on the phone view, I'm gonna wanna have it one under the
      other... Now the album, it's too big. It's too big... And I don't know.
      Everything looks to the same color."

   ⚠️ (a) AND (b) PULL AGAINST EACH OTHER AND WERE DESIGNED AS ONE PROBLEM.
   A tall album beside a short list balances; a small album beside a short list
   leaves a hole on the right. So the album is not shrunk by a number picked out
   of the air — it is sized BY ITS COLUMN, and the column split is what sets it.
   Measured on the page: the cover went from about 890px, the full height of his
   screen and the largest object anywhere on the site, to about 410px.

   THE SPLIT IS 0.88 / 1.12, not 1 / 1. The recordings are text rows and stop
   being easier to read past roughly 500px; the album is a photograph and every
   pixel it gets, it uses. So the picture takes the larger share.

   ⚠️ WHY THE SIDEWAYS LAYOUT STOPS AT 1060px AND NOT LOWER. He only ruled on
   the two ends — desktop sideways, phone stacked — and left the middle to me.
   Below 1060 the album column falls under ~430px, and the album is itself a
   two-column thing (cover + teaser), so the cover lands near 280px and the
   teaser near 130px: two stamps in a row. A tablet gets the stacked layout,
   which is the same one his phone gets.

   ⭐ R6-9c — THE COLOUR, and it is the reason the left column is a PANEL.
   Putting the two blocks side by side makes "everything looks to the same
   color" WORSE on its own: two blocks of one plum touching read as one block
   with no seam. So the two columns are given two different KINDS of surface —
   the recordings sit on a raised, bordered panel, and the album sits directly
   on the page as bare artwork. Surface against photograph is what separates
   them. Plum and gold only; not one new colour was added.
   ========================================================================== */

.music-cols {
  display: grid;
  gap: clamp(2rem, 1.4rem + 2.6vw, 3.25rem);
  align-items: start;
}

@media (min-width: 1060px) {
  .music-cols { grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr); }
}

/* Each column keeps its OWN eyebrow, heading and lead — it still reads as two
   things, which was the point of the original two sections. Only the heading
   size comes down, because a 26px h2 that was sitting across a 1168px page is
   now sitting across a 490px column. */
.music-col .section-head { margin-bottom: var(--sp-5); }
.music-col .section-head h2 { font-size: clamp(1.3rem, 1.15rem + 0.55vw, 1.65rem); }
.music-col .section-head .lead { margin-top: var(--sp-2); font-size: var(--t-small); }

/* ---- the left column: a real panel, not more plum ------------------------ */
.music-col--live {
  --panel-pad: clamp(1.05rem, 0.8rem + 1.1vw, 1.85rem);
  padding: var(--panel-pad);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 90% at 10% 0%, rgba(184, 137, 47, 0.11), transparent 62%),
    linear-gradient(168deg, rgba(74, 49, 89, 0.5), rgba(21, 12, 27, 0.74));
  box-shadow: inset 0 1px 0 rgba(242, 227, 179, 0.11), var(--shadow);
}

/* ---- and the rows inside it stop being seven identical plum bars ---------
   The rows were individually bordered, individually filled pills — the same
   flatness he keeps pointing at, one size down. On the panel they become a
   plain list: hairline between rows, no box of their own, and they BLEED to the
   panel's edges (that is the negative margin) so a highlighted row is a full
   band across the panel rather than a floating lozenge.

   ⚠️ The lift-on-hover has to be cancelled explicitly. `transform:
   translateY(-2px)` on a bordered pill reads as the pill rising; on a row in a
   ruled list it reads as the list coming apart. */
.music-col--live .tracks {
  gap: 0;
  max-width: none;
  margin-inline: calc(var(--panel-pad) * -1);
}
.music-col--live .track {
  gap: var(--sp-3);
  padding: clamp(0.62rem, 0.5rem + 0.4vw, 0.9rem) var(--panel-pad);
  border: 0;
  border-radius: 0;
  background: transparent;
  transition: background var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.music-col--live .track + .track { border-top: 1px solid var(--rule-soft); }
.music-col--live .track:hover {
  transform: none;
  background: rgba(74, 49, 89, 0.42);
}

/* THE PLAYING ROW HAS TO BE OBVIOUS. Four signals at once, because one is not
   enough on a dark screen: a gold rail down its left edge, a gold wash that
   fades out across the row, the title in the brightest gold on the page, and
   the play button already inverts to solid gold from the rule above. */
.music-col--live .track.is-playing {
  background: linear-gradient(90deg, rgba(184, 137, 47, 0.26), rgba(184, 137, 47, 0.07) 58%, transparent);
  box-shadow: inset 3px 0 0 var(--gold-400);
}
.music-col--live .track.is-playing .track__title { color: var(--gold-100); }
.music-col--live .track.is-playing + .track { border-top-color: transparent; }

/* ---- the right column: the album, sized by its column -------------------- */
/* Stacked (phone and tablet) the album would otherwise run the whole width of
   the page again, which is the complaint. 560 is the cap.
   ⚠️ NOT centred. Centring it was the first attempt and it looked wrong on the
   tablet shot: the ALBUMS eyebrow and heading sit on the page's left edge like
   every other heading on the site, and a centred album underneath them read as
   a picture floating free of its own title. It stays on the same left edge. */
.music-col--album .albums { max-width: 560px; }
/* ⚠️ AND THE SAME FOR THE TEASER-LESS CASE, WHICH IS A REAL CASE — "he should
   have the ability, but he doesn't need to." Tested by stripping the teaser out
   of the manifest, not by reading the CSS: without this the album fell back to
   the old 520px centred card, so removing the teaser made the cover BIGGER
   (520px at 820, against 333px with the teaser beside it) and floated it away
   from its own heading. Left edge, and near the size it is when the teaser is
   there. */
.music-col--album .album:not(:has(.album__teaser)) { max-width: 380px; margin-inline: 0; }

@media (min-width: 1060px) {
  /* The left column's eyebrow starts one panel-padding down from the top of its
     box. Without the same offset here the two eyebrows sit 29px apart and the
     row reads as slightly fallen over. Measured, then matched. */
  .music-col--album { padding-top: clamp(1.05rem, 0.8rem + 1.1vw, 1.85rem); }
  .music-col--album .albums { max-width: none; }
  /* ⚠️ This overrides the 0.62 second track from the rule above, which was
     sized for the album sitting across the FULL page. In a 620px column 0.62
     would give the teaser 235px and the cover 360px — the teaser nearly as wide
     as the artwork. 0.46 keeps the cover the subject and the teaser the note
     beside it. */
  .music-col--album .album:has(.album__teaser) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.46fr);
    gap: clamp(1rem, 0.7rem + 1.1vw, 1.75rem);
  }
  /* ⚠️ AND THE TEASER IS OPTIONAL — "he should have the ability, but he doesn't
     need to." With no teaser the album must not stretch to the full 635px
     column or it is straight back to being the biggest thing on the page. 430
     is what the cover measures WITH the teaser beside it, so taking the teaser
     away changes the teaser, not the album. */
  .music-col--album .album:not(:has(.album__teaser)) { max-width: 430px; margin-inline: 0; }
}

/* ============================================================================
   ⭐⭐⭐ 10c. MUSIC — R7-2. ONE CARD WITH A SEAM, NOT TWO UNEQUAL BLOCKS.
   ============================================================================
   HIS WORDS: "I don't know what's wrong on this one and the music page, but
   doesn't look even. It's a complete disaster."

   HE WAS DESCRIBING SOMETHING MEASURABLE. Before this block, at 1440:

     left column   499px wide, 468px tall, sitting on a bordered panel
     right column  635px wide, 698px tall, sitting on bare page
     the two bottoms ended 230px apart

   Two columns of different widths, different heights, and different KINDS of
   surface. Nothing about them lined up, which is exactly "not even".

   He was shown three options and picked this one:
     "Perfect. I like this option with a few tweaks. the first thing is I want
      the teaser with the album should be the same size. And then the words on
      the bottom should be in one row. Now please take in consideration that if
      I'm gonna upload more recordings, it's gonna go on the bottom and the same
      thing if I upload more albums, I can get it uploaded on the bottom."

   SO THERE ARE FOUR REQUIREMENTS, AND THE LAST ONE IS THE ARCHITECTURE:

     1. even            — one card, split down the middle by a hairline, 1fr/1fr
     2. teaser = cover  — the teaser is squared off to the cover's 1:1
     3. words in one row— the album's title/sub span BOTH halves underneath
     4. GROWS DOWNWARD  — more recordings, or more albums, extend their own
                          side downward and the card grows. Nothing reflows
                          sideways and nothing has to be re-tuned.

   ⚠️ (4) IS WHY THE PANEL STRETCHES BUT THE CONTENT DOES NOT. The first two
   mockups stretched the three track ROWS to fill the column height and left a
   hole above them — worse than the defect. `align-items: stretch` is on the
   GRID so the two halves are equal height; the content inside each half stays
   top-aligned in a flex column. An empty half shows empty panel, not stretched
   furniture.

   ⚠️ THE PANEL MOVED FROM THE COLUMN TO THE GRID. It used to be
   `.music-col--live` that carried the border, radius and gradient (R6-9c, to
   stop the two blocks reading as one flat plum field). Now `.music-cols`
   carries them and both halves sit inside, with a single `border-left` on the
   album half as the seam. The R6-9c problem is still solved — surface against
   photograph — it is just one surface now instead of one-and-a-half.
   ========================================================================== */

@media (min-width: 1060px) {
  .music-cols {
    /* EQUAL. The 0.88 / 1.12 split above was tuned when the two halves were
       free-standing blocks; inside one card an off-centre seam is the thing
       that reads as "not even". */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
    gap: 0;
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    overflow: hidden;
    background:
      radial-gradient(120% 90% at 10% 0%, rgba(184, 137, 47, 0.11), transparent 62%),
      linear-gradient(168deg, rgba(74, 49, 89, 0.5), rgba(21, 12, 27, 0.74));
    box-shadow: inset 0 1px 0 rgba(242, 227, 179, 0.11), var(--shadow);
  }

  /* ⚠️ DECLARED ON `.music-col`, NOT ON `.music-cols`. `--panel-pad` is already
     declared on `.music-col--live` further up this file; a variable an element
     declares on ITSELF beats one it inherits, so setting it on the parent would
     have left the left half on the old 1.05rem padding and the two halves would
     not have matched. Same specificity, later in the file, so this wins. */
  .music-col {
    --panel-pad: clamp(1.15rem, 0.85rem + 1.2vw, 2rem);
    display: flex;
    flex-direction: column;
    padding: var(--panel-pad);
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
  }

  /* The seam. One hairline, on the inside edge of the album half. */
  .music-col--album { border-left: 1px solid var(--rule); padding-top: var(--panel-pad); }

  /* Both eyebrow/heading/lead blocks reserve the same height, so the track list
     and the album cover start on the same line. Without it the two headings
     differ by a line and everything below inherits the offset. */
  .music-col .section-head { margin-bottom: var(--sp-4); }

  /* The rows still bleed to the card's edge — the padding they cancel is now
     the shared one. */
  .music-col--live .tracks { margin-inline: calc(var(--panel-pad) * -1); gap: 0; }
  .music-col--live .track { padding: clamp(0.8rem, 0.65rem + 0.55vw, 1.15rem) var(--panel-pad); }

  /* ---- tweak 1 + 2: equal halves, and the words underneath both ---------- */
  /* ⚠️ WRITTEN AT `.music-col--album .album`, NOT `.opt-b .album`.
     `:has()` takes the specificity of its most specific argument, so
     `.music-col--album .album:has(.album__teaser)` above scores (0,3,0). A
     two-class override loses to it SILENTLY — the rule applies, the teaser just
     stays narrow, and it reads as "the change didn't work". Match the shape. */
  .music-col--album .album,
  .music-col--album .album:has(.album__teaser) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "cover teaser"
      "meta  meta";
    gap: var(--sp-4);
    align-items: start;
  }

  /* THE TEASER IS SQUARED OFF TO MATCH THE COVER — his tweak, and the cover is
     genuinely 1400x1400, so 1/1 is the cover's own shape, not a number picked
     to look tidy. The poster is 1080x1920 and is cropped to fit rather than
     letterboxed.
     ⚠️ `aspect-ratio` DOES NOT CLAMP A BOX. Left in the flow, a 1080x1920 image
     pushed this frame to 252x448 while `getComputedStyle` cheerfully reported
     `1 / 1`. The image has to leave the flow — hence `position:absolute`. And
     `.card__frame` is a <span>, so once the image left, an inline box collapsed
     to 0x0 — hence `display:block`. Both were measured, not reasoned. */
  .music-col--album .album__teaser { justify-items: stretch; gap: var(--sp-2); }
  .music-col--album .album__teaser .card { width: 100%; max-width: none; }
  .music-col--album .album__teaser .card__frame {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
  }
  .music-col--album .album__teaser .card__frame img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* The title used to wrap to two lines in a half-width column and drag the
     whole right side down. Across the full card it fits on one. */
  .music-col--album .album__title {
    font-size: clamp(1.25rem, 1.05rem + 0.8vw, 1.7rem);
    line-height: 1.15;
  }

  /* ---- tweak 3: more albums go DOWNWARD, and are separated ---------------- */
  .music-col--album .album + .album {
    padding-top: clamp(1.75rem, 3vw, 2.75rem);
    border-top: 1px solid var(--rule-soft);
  }
}

/* ============================================================================
   11. SERVICES (item 16) — ROUND 8. THE HEAD, THEN THE BOXES.
   ============================================================================
   "Don't make like a whole text. Everything should be like individual in a box
   with a nice icon… It should be divided in boxes and it should have all the
   details."

   ⭐ ROUND 8 — WHAT CHANGED, AND WHY. He reviewed this page five times in one
   round. Three things came out of it, and all three are settled:

     1. THE WRITING IS ALWAYS OUT. These were <details> that you clicked open.
        They are plain <article> now — nothing to click, everything readable.
        The chevron, the [open] rules and the grow-to-full-row rule went with
        them, because there is no open state left to style.
     2. TWO ACROSS, NOT FIVE. Ten over two is FIVE CLEAN ROWS, both boxes in a
        row the same height. Five across was only ever possible because the
        boxes were shut; with the text out it would be five ribbons.
        ⚠️ NEVER THREE OR FOUR. Ten divides by five, by two, and by nothing
        else that fits a page. A short last row is what made him say "not
        organized" — and an eleventh service is NEVER invented to square it.
        He has caught that twice.
     3. THE SECTION IS PAPER. "I like this color" — the same cream surface
        About and Shabbos already use.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   11a. THE HEAD OF THE PAGE — centred, and his line under the heading.
   ---------------------------------------------------------------------------
   HE CHOSE CENTRED AND HIS REASON IS THE RIGHT ONE: "because it's English and
   Yiddish, it doesn't look good together. The only way it's when it's in
   center." Latin runs left-to-right and Yiddish runs right-to-left, so a centre
   line is the only spine the two of them can actually share. Ranged left, the
   heading started at one edge and his line ended at the other and the two
   agreed on nothing — measured at 1440: the heading ran 127->738 and the
   Yiddish 383->863.

   ⚠️ SPECIFICITY, NOT ORDER — AND IT HAS ALREADY BITTEN ONCE ON THIS BLOCK.
   base.css carries .yi { text-align: right }, which is 0-1-0, exactly what a
   bare .hero-yi would be. In the preview — where the <style> sat ABOVE the
   <link> tags — that tie went to base.css and the "centred" line was really
   RIGHT-ALIGNED inside a centred box: crooked on every line but the longest,
   and a check that only read the CSS would have called it centred. Every rule
   here is two classes deep for that reason. Prove the COMPUTED value in a
   browser; never assume the cascade.

   ⚠️ AND THE EYEBROW RULE IS SCOPED TO THE HERO ON PURPOSE. .label is used
   again further down this same page for "Explore" and for "Book Us". A bare
   .label::after put a second flourish on both of them. */
.services-hero .wrap {
  display: grid;
  justify-items: center;
  text-align: center;
}
.services-hero .label { order: 1; margin-bottom: 0.65rem; }
.services-hero h1 { order: 2; margin-bottom: 0; }

/* base.css gives .label a rule on its leading side only; centred, it needs one
   on both or it sits lopsided */
.services-hero .label::after {
  content: "";
  width: clamp(20px, 4vw, 48px);
  height: 1px;
  background: currentColor;
  opacity: 0.7;
}

/* HIS INTRO LINE, moved up out of the section and under the heading.
   ⚠️ CREAM, NOT GOLD. It was --gold-200 against a --cream heading, which made
   the one line a Yiddish reader actually reads the DIMMEST thing in the block
   (12.77:1 against the heading's 14.72:1). That was most of "it doesn't come in
   clear". */
.services-hero .hero-yi {
  order: 3;
  max-width: 46rem;
  margin: 0;
  margin-top: clamp(0.85rem, 1.5vw, 1.25rem);
  text-align: center;
  font-size: clamp(1.04rem, 0.9rem + 0.72vw, 1.45rem);
  line-height: 1.62;
  color: var(--cream);
  text-wrap: balance;
}
/* a short flourish between the English heading and his line */
.services-hero .hero-yi::before {
  content: "";
  display: block;
  width: clamp(70px, 10vw, 130px);
  height: 1px;
  margin: 0 auto clamp(0.85rem, 1.5vw, 1.25rem);
  background: linear-gradient(90deg, transparent, var(--gold-300), transparent);
}

/* ---- the three pieces of his one sentence ---------------------------------
   ⚠️⚠️ THE BREAKS ARE CUT AT PUNCTUATION HE HIMSELF TYPED, AND NOT ONE
   CHARACTER OF HIS TEXT IS TOUCHED. "organize it better… it should look good"
   was never a punctuation problem — his sentence carries ONE comma and the
   browser was breaking about eight words past it, so every line ended
   mid-thought. tools/build-yiddish.mjs cuts at his comma and at the space
   before his last word, wraps the three pieces in spans, keeps his own spaces
   between them, and then reads the line back off the page and compares it to
   content/services-yi.txt character by character. No <br>, no rewording.

   ⚠️ THE LAST WORD STANDS ALONE, IN GOLD. "I like this one, but maybe we
   remove the dot and make it a little bit bigger." Read together with the two
   edits before it, all three of his content changes were the same thing: he was
   clearing the punctuation off the end of that sentence so the last word could
   stand by itself. The line break and the gold do that job now. */
.services-hero .yl { display: block; }
.services-hero .yl--c {
  color: var(--gold-300);
  font-size: 1.55em;
  margin-top: 0.1em;
  line-height: 1.35; /* 1.62 is set for body-sized text and leaves a hole under
                        a word this size */
}
/* A phone is too narrow to hold the first piece on one line, and forcing it
   there gives two ragged half-lines. Below 560 the sentence flows and only the
   last word keeps its own line. */
@media (max-width: 560px) {
  .services-hero .yl--a,
  .services-hero .yl--b { display: inline; }
}

/* ---------------------------------------------------------------------------
   11b. THE SECTION — paper. He picked this colour.
   --------------------------------------------------------------------------- */
.services-scope {
  --bg: #fbf6ea;
  --bg-raised: #f2e9d6;
  --fg: #241528;
  --fg-dim: rgba(36, 21, 40, 0.72);
  --accent: var(--gold-700);
  --rule: rgba(140, 101, 32, 0.3);
  --rule-soft: rgba(36, 21, 40, 0.12);
  background: var(--bg);
  color: var(--fg);
}

/* ---- the grid: five clean rows -------------------------------------------- */
.svc-grid {
  direction: rtl; /* the boxes fill from the right, like the words in them */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1rem, 1.6vw, 1.6rem);
  align-items: stretch; /* both boxes in a row match — no ragged edge */
  max-width: 76rem;
  margin-inline: auto;
}
@media (max-width: 760px) {
  .svc-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the box he liked ------------------------------------------------------
   ⭐ THE BOX WAS NEVER THE PROBLEM. It was thrown away twice before that was
   understood: he liked it all along, and the only fault was having to CLICK it
   open. */
.services-scope .svc {
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(140, 101, 32, 0.26);
  border-radius: var(--radius-lg);
  background: linear-gradient(168deg, #fffcf4, #f4ebda);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 10px 24px rgba(36, 21, 40, 0.09);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.services-scope .svc:hover {
  border-color: rgba(140, 101, 32, 0.55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 14px 30px rgba(36, 21, 40, 0.16);
}

/* ⚠️ direction: rtl ON THE ROW is what keeps the icon and its own label
   together. It laid its children out from the LEFT while the Yiddish name
   inside aligned RIGHT, so icon and label sat at opposite ends of the card with
   dead air between them — ten well-made components, each one torn in half.
   "It looks good, but it looked a disaster as well." */
.services-scope .svc__head {
  direction: rtl;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: clamp(0.9rem, 1.3vw, 1.2rem);
  cursor: default;
  background: linear-gradient(180deg, rgba(184, 137, 47, 0.14), transparent);
  border-bottom: 1px solid rgba(140, 101, 32, 0.22);
}
.services-scope .svc__name {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right; /* in an RTL row, right IS the start edge */
  font-size: clamp(1.14rem, 1.02rem + 0.38vw, 1.4rem);
  line-height: 1.25;
  color: #3a2545;
}
.services-scope .svc__body {
  flex: 1 1 auto; /* fills the taller row-mate's height, so rows stay level */
  padding: clamp(1rem, 1.4vw, 1.25rem) clamp(0.9rem, 1.3vw, 1.2rem) clamp(1.1rem, 1.5vw, 1.35rem);
  font-size: 0.99rem;
  line-height: 1.9;
  color: rgba(36, 21, 40, 0.82);
}
.services-scope .svc__body p { max-width: none; }
.services-scope .svc__body p + p { margin-top: 1em; }

/* ---- ⚠️ THE HOVER DEFECT HE FOUND HIMSELF ---------------------------------
   "when I hover over my mouse on it, the icon is getting, like, white. I cannot
   even see it."

   This rule used to read .svc:hover .tile { color: var(--gold-100) }, which
   is #F2E3B3 — near-white. On the old DARK box that is a lift. On a CREAM box
   it is the icon disappearing into its own tile.

   ⭐ THE LESSON IS BIGGER THAN THE LINE: EVERY :hover WRITTEN FOR THE DARK
   THEME IS SUSPECT THE MOMENT ITS COMPONENT LANDS ON THE PAPER SECTION. The
   icon now goes DARKER on hover, not lighter. */
.services-scope .svc .tile {
  font-size: 0.95rem;
  background:
    radial-gradient(120% 110% at 32% 12%, rgba(255, 255, 255, 0.92), transparent 60%),
    linear-gradient(168deg, #fbf6ea, #f2e9d6 72%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 0 0 1px rgba(140, 101, 32, 0.28),
    0 8px 20px rgba(36, 21, 40, 0.14);
  color: var(--gold-700);
  transition: color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.services-scope .svc:hover .tile {
  color: #6e4e14;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 0 0 1px rgba(140, 101, 32, 0.55),
    0 10px 22px rgba(36, 21, 40, 0.2);
}

/* ============================================================================
   12. SHABBOS CHOIR (items 13 + 19)
   ========================================================================== */

.shabbos-hero {
  position: relative;
  min-height: min(62svh, 560px);
  display: grid;
  align-items: end;
  padding-top: calc(var(--header-h) + var(--sp-7));
  padding-bottom: clamp(2rem, 1.6rem + 2.4vw, 3.5rem);
  overflow: hidden;
  isolation: isolate;
}
.shabbos-hero__media { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.shabbos-hero__media video,
.shabbos-hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Graded like the home hero: his clip is a bright hall under white lighting,
     and left alone the footage is brighter than the type so the gold reads as
     grey.
     ⚠️ Raised from 0.56 after looking at it. At 0.56 plus the two overlays
     below the six singers were barely findable, and the ONLY reason this video
     is on the page is to show that there is a Shabbos choir. The vignette was
     doing most of the darkening anyway, so the brightness came up and the
     vignette's outer stop came down — the type is still safe and you can now
     see who is on the stage. */
  filter: brightness(0.68) contrast(1.05) saturate(0.9);
}
.shabbos-hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(112% 84% at 50% 32%, rgba(21, 12, 27, 0.14), rgba(21, 12, 27, 0.76) 80%),
    linear-gradient(180deg, rgba(21, 12, 27, 0.72) 0%, rgba(21, 12, 27, 0.3) 34%, rgba(21, 12, 27, 0.92) 100%);
}
.shabbos-hero__veil { position: absolute; inset: 0; z-index: 1; background: var(--plum-950); opacity: 0.2; }
/* ⭐ ROUND 6, R6-17b — THE THIRD TIME HE ASKED FOR THIS. "The writing is on the
   right side, which the entire website everything's on the left side. So let's
   get it moved to the left side."
   This was `text-align: right`, because the heading is Yiddish. Same rule as
   the Book Us hero: the language keeps its reading direction, the BLOCK stops
   being pushed to the far side of the page. `left` is stated rather than the
   rule deleted — the heading carries `direction: rtl`, so the initial value
   `start` would resolve to the right and nothing would change. */
.shabbos-hero__inner { position: relative; z-index: 2; text-align: left; }
.shabbos-hero__inner .label { justify-content: flex-start; }

.shabbos-hero__h {
  font-size: clamp(2.4rem, 1.4rem + 4.6vw, 5rem);
  color: var(--gold-100);
  text-shadow: 0 12px 40px rgba(0, 0, 0, 0.7);
}

.shabbos-body { padding-block: clamp(3rem, 2.4rem + 3.4vw, 5.5rem); }
.shabbos-copy { font-size: clamp(1rem, 0.95rem + 0.4vw, 1.16rem); }
.shabbos-copy p + p { margin-top: 1.1em; }
.shabbos-copy p { max-width: none; }

/* ⭐⭐ ROUND 6, R6-17c — THE ARTICLE GETS A WAY IN, AND NOT ONE NEW WORD.
   ============================================================================
   "By the writing, it doesn't start with some header… We gotta come up with
   something." Six paragraphs began cold, straight off the video.

   THE LIMIT: I do not write Yiddish. So the opening is HIS OWN LINE —
   `א שבת מיט חסידיש איז פשוט א צווייטער שבת`, which he sent himself — re-set as
   a standfirst, plus two things that are not words at all: a hairline, and a
   drop cap on a letter that was already there.

   ⭐ IT BOOKENDS THE PAGE. His sign-off at the foot of this article is the same
   sentence with a lead-in and a full stop, and it already had a hairline and a
   gold face. So this is built as its mirror — hairline BELOW instead of above —
   and it is deliberately INK, not gold. Same shape, different colour: the page
   opens and closes on the same words without the closing line looking like an
   accidental repeat of the opening one.

   ⚠️ Alignment. `.yi` sets text-align:right and this block keeps it, because
   the whole article — six paragraphs and the sign-off — is set that way inside
   a centred reading column. R6-17b was about a block being pushed to the far
   RIGHT EDGE of the page; nothing here is. Left-aligning this one line while the
   six paragraphs under it stay right would not read as "moved left", it would
   read as a mistake. */
.shabbos-standfirst {
  max-width: none;
  margin: 0 0 clamp(1.5rem, 1.2rem + 1.1vw, 2.3rem);
  padding-bottom: clamp(1rem, 0.8rem + 0.8vw, 1.5rem);
  border-bottom: 1px solid var(--rule);
  font-size: clamp(1.35rem, 1.12rem + 1.15vw, 2.1rem);
  font-weight: 500;
  line-height: 1.4;
  color: var(--fg);
  text-align: right;
}

/* ---- the drop cap --------------------------------------------------------
   ⚠️ A DROP CAP ON HEBREW IS NOT A DROP CAP ON LATIN, and the mechanics that
   matter here are the ones that have no Latin equivalent:

   · `float: left` would drop the letter on the WRONG SIDE of a right-to-left
     paragraph. `float: inline-start` resolves against the paragraph's own
     direction, so on this rtl block it floats right — where the text begins.
   · Hebrew has no ascenders and almost no descenders, so a cap sized to "three
     lines" the way a Latin one is sized comes out visually taller than three
     lines. 2.9em against this line-height is two lines and a shade, measured on
     screen rather than calculated.
   · The letter is א — a whole word on its own ("a"), not the first letter of a
     longer one. That is normal in Yiddish and it is why the padding on the
     inline-end side is small: it must not open a gap wide enough to read as a
     missing word.
   · `::first-letter` inherits, so the face is already --font-yi. It is stated
     anyway, because this is the one place on the site where a fallback face
     would be four times the size of the body and impossible to miss.

   Verified by screenshot at 390, 820 and 1440 — not by reading the numbers
   back off getComputedStyle, which on this project has lied before. */
.shabbos-copy > p:first-child::first-letter {
  float: inline-start;
  font-family: var(--font-yi);
  font-size: 3.3em;
  line-height: 0.88;
  font-weight: 400;
  padding-inline-end: 0.1em;
  padding-block-start: 0.02em;
  color: var(--gold-700);
}

.shabbos-signoff {
  margin-top: var(--sp-7);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--rule);
  font-size: clamp(1.15rem, 1rem + 0.85vw, 1.7rem);
  color: var(--gold-700);
  text-align: right;
}

/* ============================================================================
   13. MEDIA — the article cards and the reader (item 15)
   ========================================================================== */

/* ⭐⭐ R8-13 — THREE ACROSS, AND THE FULL WIDTH OF THE PAGE.
   "let's make it instead of a two section, it should be a three section. And
   then the article should also be three section... let's make the four
   pictures should be on the entire length."

   WAS `auto-fill minmax(300px)` under a 760px cap, which gave TWO tracks of
   366px on a 1186px page and left the right third of both sections empty.
   The cap is gone and the count is explicit.

   ⚠️ THE COVERS DO NOT GET SMALLER. Three tracks across the full 1186px is
   ~370px each — the same size they were at two-up under the cap. The page
   gained a column; nothing shrank.

   ⚠️ AN INCOMPLETE LAST ROW IS EXPECTED AND IT IS HIS CALL. Two articles fill
   two of three tracks; four pictures are 3 + 1. His words: "if he's gonna put
   a more article, it's gonna be three section, and then the fourth one will go
   on the bottom of it." **An eleventh item is NEVER invented to square a grid**
   — he has caught that twice on this site.

   ⚠️ EXPLICIT COUNTS, NOT `auto-fill`. auto-fill derives the column count from
   whatever width the container happens to have, so the page silently restyles
   itself when a container changes elsewhere. That is exactly how these two
   sections ended up at two different widths in the first place.

   Both sections share one set of tracks, so a cover and the photograph under
   it start and end on the same lines. */
.articles,
.press-wall {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
}
/* ⚠️ 900 AND 560 ARE THE COVER'S NUMBERS, NOT ROUND ONES. A cover is 3:4, so
   a 3-across track below ~900px puts the masthead under 240px wide and the
   magazine name stops being readable at a glance — which is the whole job of
   a cover. Two across down to 560, then one. */
@media (max-width: 900px) {
  .articles,
  .press-wall { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .articles,
  .press-wall { grid-template-columns: minmax(0, 1fr); }
}

/* ⭐⭐⭐ R8-13 — THE DESCENDER HE SPOTTED, AND WHY IT IS A REAL BUG.
   "on the picture side, where it says On the night. the g is getting cut into
   the picture."

   He is exactly right, and measured it is worse than it looks: the top of the
   first photograph sat at 463.7px and the ink of that heading ended at
   465.8px. **The picture started 2.1px ABOVE where the word finished.**

   THE CAUSE IS NOT SPACING TASTE — IT IS A MISSING STYLESHEET. `.section-head`
   is a shared component whose rules live in home.css, and media.html does not
   load home.css. So on this page the head had NO rules at all: no margin under
   it, nothing. On Home the same block carries 48px.

   ⚠️⚠️ AND THE ARTICLES SECTION WAS ONLY SAVED BY THE LINE HE JUST ASKED ME
   TO DELETE. Its heading was followed by a `.lead`, whose own 1em bottom
   margin was standing in for the missing rule. Removing that line — his edit
   2 — would have put the exact fault he reported onto the section he did NOT
   report it on. **Two of his five edits were the same bug, and one was hiding
   the other.**

   ⚠️ FIXED HERE, SCOPED TO THIS PAGE — NOT by moving the rule into base.css.
   Music uses `.section-head` too and also does not load home.css; measured, it
   is `display: block` with a 16px margin from its own `.music-col` rules. A
   global rule would have made every Music head `display: flex` as a side
   effect of fixing Media. That page was not in the complaint.

   The line-height is the belt to the margin's braces: headings are set at 1.03
   site-wide, which is tighter than the font's own descenders, so the ink hangs
   past its box. 1.25 puts the box back around the ink. */
#articles .section-head,
#press-photos .section-head {
  margin-bottom: clamp(1.5rem, 1.2rem + 1.2vw, 2.5rem);
}
#articles .section-head h2,
#press-photos .section-head h2 { line-height: 1.25; }
/* press.js swaps the grid's children for a single `.state` block when a folder
   is empty. As a grid item it would sit in a 366px track; it needs the row. */
.press-wall.is-empty { display: block; }

/* ---- the pictures -------------------------------------------------------
   Uniform. Every frame the same 3:2, so the wall is a grid of identical shapes
   rather than a ragged masonry one. R8-13: three across, so four pictures are
   a row of three and one under it.
   ⚠️ SCOPED TO `.press-wall`, WHICH ONLY MEDIA USES. `.card__frame` itself is
   shared by gallery, videos, index and the music teaser — editing it would have
   moved four other pages. This is the mistake the note at the top of this file
   already records someone making once.
   ⚠️ One of the four is a 2:3 PORTRAIT and is cropped to fit. It is a STAND-IN
   (`placeholder: true` in the manifest, and it is the same file the Gallery
   page already shows). When he sends real press photographs this is the line to
   look at again. */
/* ⚠️⚠️ `display: block` IS THE LOAD-BEARING LINE HERE, NOT `aspect-ratio`.
   `.card__frame` is a <span>, and press.js renders it inside a <button>. An
   INLINE box ignores `aspect-ratio`, `width` and `height` outright. With the
   ratio alone the four pictures measured 243, 243, 546, 546 — the portrait
   stand-in kept its own 2:3 shape and dragged the landscape beside it up to
   match the row.
   ⚠️ AND `getComputedStyle` REPORTED `aspect-ratio: 3 / 2` THE WHOLE TIME.
   The rule had won; the renderer had thrown it away. Only
   `getBoundingClientRect` showed it. This is the second time in this one round
   — the album teaser was the first. If a box will not take a shape, check
   whether it is inline BEFORE touching the ratio. */
.press-wall .card__frame {
  display: block;
  aspect-ratio: 3 / 2;
}
.press-wall .card__meta { display: none; }

.article {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.article__frame {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  /* A magazine cover is a document, not a photograph. A hard edge and a real
     shadow make it read as a printed thing sitting on the page. */
  box-shadow: 0 0 0 1px var(--rule-soft), var(--shadow);
  transition: transform var(--dur-slow) var(--ease-out), box-shadow var(--dur-slow) var(--ease-out);
}
.article:hover .article__frame,
.article:focus-visible .article__frame {
  transform: translateY(-6px);
  box-shadow: 0 0 0 1px var(--gold-500), var(--shadow-lg);
}
/* ⭐⭐ R7-3c — THE TWO COVERS NOW LINE UP.
   WAS `height: auto` and no `aspect-ratio`, so each card was as tall as its own
   scan: der-shrift is 800x1024 (ratio 0.781) and toameu is 800x1068 (0.749).
   They rendered 468px and 489px tall — 21px apart — and because the title sits
   UNDER the cover, the two titles sat at two different heights too. That is a
   large part of what he was calling messy.

   3/4 is 0.75, which is toameu's own shape almost exactly; der-shrift loses
   about 2% off each side. That is the whole cost, it falls in the margin of a
   magazine cover, and it is the only way two different scans line up without
   letterbox bands round one of them.
   ⚠️ If a future article is a very different shape, CHECK ITS MASTHEAD IS STILL
   IN FRAME before assuming this still holds. */
.article__frame { aspect-ratio: 3 / 4; }
.article__frame img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center;
}
.article__frame::after {
  content: "Read";
  position: absolute;
  inset: auto 0 0 0;
  padding: 2.4rem 1rem 0.9rem;
  background: linear-gradient(180deg, transparent, rgba(21, 12, 27, 0.9));
  color: var(--gold-200);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  text-align: center;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}
.article:hover .article__frame::after,
.article:focus-visible .article__frame::after { opacity: 1; }

/* ⭐⭐⭐ R7-3c — THIS IS THE "WRITINGS LOOK MESSY", AND IT WAS NOT A MATTER OF
   TASTE. IT WAS BROKEN.

   The title and the subtitle are both <span>, so both were INLINE and they ran
   together on ONE LINE:

       שריפט THE COVER STORY — 13
       PAGES

   the Yiddish title butted straight against the English subtitle with no break
   and no gap, then wrapped mid-phrase onto a second line. And `margin-top:
   0.25em` on `.article__sub` did NOTHING, because vertical margins have no
   effect on an inline box — so the one rule that was supposed to separate them
   had never once applied.

   ⚠️ Nothing in any console said a word about this, and every geometric check
   passed: the two covers lined up, the grid was even, no overflow. It is only
   visible by LOOKING at the page. */
.article__text { display: block; }
/* ⚠️⚠️ THESE TWO COLOURS FOLLOW THE SURFACE, AND THEY HAVE TO.
   R8-13 put both Media sections on the cream paper. `color: var(--cream)` is
   #F5E6C0 — it was the right colour for six rounds on a near-black page, and
   on cream it is cream on cream. `--fg` is the token the surface itself sets:
   it resolves to --cream on the dark pages and to #241528 inside .on-paper,
   so the title is correct on both without either page knowing about the other.

   ⭐ THIS IS THE SAME SHAPE AS THE WHITE ICON HE FOUND ON THE SERVICES BOXES
   (`.svc:hover .tile { color: var(--gold-100) }`). **Every colour written for
   the dark theme is suspect the moment its component lands on paper.** Two
   for two now — so it is a rule, not a coincidence. */
.article__title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-h3);
  line-height: 1.15;
  color: var(--fg, var(--cream));
}
.article__sub {
  display: block;
  margin-top: 0.25em;
  font-size: var(--t-mini);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  /* same reason as the title above: --accent is --gold-300 on the dark pages
     and --gold-700 inside .on-paper, so this reads on either surface. */
  color: var(--accent, var(--gold-400));
}

/* ---- the reader ----------------------------------------------------------
   "It's not gonna open up like a printing view… maybe if they click on it they
   can start scrolling, like, from the website."

   So: full screen, the site's own chrome, and the pages simply stack and
   scroll. No PDF plugin, no toolbar, no download-instead-of-open on a phone.
   The original file is still offered — as the second option, in the bar. */
.reader {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  /* ⭐⭐ R7-3b — "When I open it up, I just opened up in the background."
     WAS `rgba(11, 6, 15, 0.97)`. 97% SOUNDS OPAQUE AND IS NOT. The Media page
     behind this panel is cream text at about V240 on a near-black ground: 3% of
     240 is 7 levels lifted over an 11-level background, and 7 levels of cream
     on black is READABLE TEXT. Both his article covers and three headings ghost
     straight through it.

     ⚠️ EVERY INSTRUMENT SAID THIS WAS FINE. Opened in a real browser it
     reported opacity 1, visibility visible, z-index 200, position fixed,
     rect 0,0 1440x900, body locked, 13 pages, no console errors — and the page
     behind it was still legible. Only the SCREENSHOT showed it.
     It is fully opaque now. The fade still works: `opacity` on the panel is
     what animates, and that is a separate property from this one. */
  background: #0b060f;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur) var(--ease-out), visibility var(--dur);
}
.reader.is-open { opacity: 1; visibility: visible; }

.reader__bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: 0.7rem clamp(0.75rem, 2vw, 1.5rem);
  border-bottom: 1px solid var(--rule);
  background: rgba(21, 12, 27, 0.9);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.reader__title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reader__count {
  flex: 0 0 auto;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
  /* ⭐ The spacer lives HERE, on an element that is always present.
     It used to be `margin-left: auto` on `.reader__dl` — which worked only
     while the download button was rendered. The moment that button was
     correctly hidden (his articles ship without the original PDF), nothing was
     left pushing the close X to the right and it collapsed to sit against
     "13 PAGES" at the far left of the bar. One fix uncovered by another. */
  margin-right: auto;
}
.reader__dl {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  flex: 0 0 auto;
  padding: 0.5em 1em;
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  color: var(--gold-300);
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.reader__dl .ic { width: 1.1em; height: 1.1em; }
.reader__dl:hover { background: var(--gold-300); color: var(--plum-950); }
/* ⭐ THIS ONE LINE IS A REAL BUG FIX, NOT TIDINESS.
   press.js sets `dl.hidden = true` when an article has no original PDF — and
   neither of his does, because the originals are 7.8 MB and 15.4 MB and
   offering those first is the "printing view" he did not want. But `hidden` is
   only a UA-stylesheet `display: none`, and the rule above sets
   `display: inline-flex`, which is an author rule and therefore WINS.

   The symptom was a fully styled "ORIGINAL PDF" button sitting in the reader
   bar with href="#" — a button that looks live, reads live, and does nothing.
   Found by opening the reader and LOOKING at it; nothing in the console said a
   word. Any `[hidden]` on an element with an author `display` needs this. */
.reader__dl[hidden] { display: none; }
.reader__close {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--cream);
  cursor: pointer;
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.reader__close:hover { background: var(--gold-300); color: var(--plum-950); }

.reader__pages {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: clamp(0.75rem, 2vw, 2rem);
  display: grid;
  gap: clamp(0.75rem, 1.6vw, 1.5rem);
  justify-items: center;
}
/* ⭐⭐ R7-3b, THE SECOND HALF — AND IT MAY BE THE BIGGER HALF OF "DISASTER".
   A page used to be sized by WIDTH ALONE: `width:100%; max-width:1100px`.
   Nothing ever asked whether it fitted the SCREEN. At 1440x900 a magazine page
   rendered 1100x1408 inside a 900px-tall window, so opening an article landed
   him on the top two-thirds of a cover, cut off, with no way to tell what he
   was looking at until he scrolled.

   ⚠️⚠️ `max-height` ALONE DISTORTS THE PAGE, AND IT DOES IT SILENTLY. The first
   attempt kept `width: 100%` and added `max-height`. CSS only re-derives the
   width from a clamped height when the width is AUTO; with a definite width it
   clamps the height and leaves the width alone. The magazine cover rendered
   1100x764 — a 0.781 page squashed to 1.440, flat as a letterbox — and every
   other check still passed. It was caught by comparing the RENDERED ratio to
   `naturalWidth/naturalHeight`, which is now part of the verification.
   Both axes are `auto` with two caps, so the constraint algorithm keeps the
   proportion. The 8.5rem is the reader bar plus the grid's padding, top and
   bottom.
   ⚠️ `dvh`, not `vh` — on a phone `vh` is the tallest the viewport ever gets,
   so with the address bar showing, `100vh` is BIGGER than the screen and the
   cap would do nothing on the one device it matters most on. */
.reader__pages img {
  width: auto;
  height: auto;
  max-width: min(100%, 1100px);
  max-height: calc(100dvh - 8.5rem);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  background: var(--plum-800);
}
/* ⚠️ Toameu's inner pages are 2-page LANDSCAPE spreads. At 390px a spread is
   two columns of Yiddish inside 390 CSS px — unreadable. They get the full
   width of the reader and the visitor pinch-zooms, which is the one gesture
   everybody already knows on a phone. `touch-action` must allow it. */
.reader__pages { touch-action: pinch-zoom pan-y; }

@media (max-width: 620px) {
  .reader__dl span { display: none; }
  .reader__dl { padding: 0.5em 0.7em; }
  .reader__title { font-size: 1rem; }
}

/* ============================================================================
   8. 404
   ========================================================================== */

.page-404 {
  position: relative;
  min-height: 76vh;
  display: grid;
  place-items: center;
  padding-block: calc(var(--header-h) + var(--sp-8)) var(--sp-9);
  overflow: hidden;
}
.page-404::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(820px, 130vw);
  aspect-ratio: 1;
  translate: -50% -50%;
  background: radial-gradient(circle, rgba(184, 137, 47, 0.15), transparent 62%);
  pointer-events: none;
}
.page-404__inner { position: relative; z-index: var(--z-content); }
.page-404__seal {
  width: clamp(88px, 12vw, 138px);
  margin: 0 auto var(--sp-6);
  border-radius: 50%;
}
.page-404__lead { margin-top: var(--sp-4); }
.page-404__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-7);
}
@media (max-width: 420px) {
  .page-404__actions .btn { width: 100%; }
}


/* ============================================================================
   15. PRIVACY — the one page of plain reading on the site. Round 6, R6-8.
   ============================================================================
   A privacy link in the bottom bar needs a page to land on. It is deliberately
   the least decorated page here: one narrow column, short headings, and
   nothing to look at, because the only reason anybody opens it is to find out
   what happened to their phone number. */

.legal h2 {
  margin-top: var(--sp-7);
  margin-bottom: var(--sp-4);
  color: var(--gold-100);
}
.legal h2:first-child { margin-top: 0; }
/* Not --fg-dim. That token is for a one-line sub-heading under a big display
   heading; a whole page of reading at 62% opacity is hard work, and this is
   the one page somebody opens because they actually want to READ it. */
.legal p { color: rgba(245, 230, 192, 0.8); }
.legal p + p { margin-top: -0.35em; }

.legal__contacts {
  list-style: none;
  margin: var(--sp-5) 0 0;
  padding: 0;
  display: grid;
  gap: 0.75rem;
  color: var(--cream);
}
.legal__contacts a { transition: color var(--dur) var(--ease-out); }
.legal__contacts a:hover { color: var(--gold-200); }

.legal__stamp {
  margin-top: var(--sp-8);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--rule-soft);
  font-size: var(--t-mini);
  color: var(--fg-faint);
}
