/* ============================================================================
   Chassidish Choir — base: reset, type, header, footer, shared components
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: auto; /* Lenis owns scrolling; never fight it with CSS smooth */
}

body {
  margin: 0;
  background: var(--plum-950);
  color: var(--cream);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  font-weight: 300;
  line-height: 1.65;
  letter-spacing: 0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

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

img { height: auto; }

a { color: inherit; text-decoration: none; }

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

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.03;
  letter-spacing: -0.012em;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0 0 1em; max-width: var(--measure); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

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

:focus-visible {
  outline: 2px solid var(--gold-300);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: var(--z-modal);
  background: var(--gold-300);
  color: var(--plum-950);
  padding: 0.75rem 1.25rem;
  font-weight: 500;
}
.skip-link:focus { left: 1rem; top: 1rem; }

/* ---- Layout ------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-pad);
}
.wrap--wide { max-width: 1600px; }
.wrap--narrow { max-width: 860px; }

.section {
  position: relative;
  padding-block: var(--section-y);
  background: var(--bg, var(--plum-950));
  color: var(--fg, var(--cream));
}

.section--tight { padding-block: clamp(3rem, 2rem + 4vw, 5.5rem); }


/* ---- Type helpers ------------------------------------------------------- */

.label {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent, var(--gold-300));
  margin: 0 0 var(--sp-5);
}
.label::before {
  content: "";
  width: clamp(20px, 4vw, 48px);
  height: 1px;
  background: currentColor;
  opacity: 0.7;
}

.h-display { font-size: var(--t-display); }
.h1 { font-size: var(--t-h1); }
.h2 { font-size: var(--t-h2); }
.h3 { font-size: var(--t-h3); }

.lead {
  font-size: var(--t-lead);
  font-weight: 300;
  line-height: 1.55;
  color: var(--fg-dim, rgba(245, 230, 192, 0.62));
}

.italic-gold {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--accent, var(--gold-300));
}

/* The wordmark's own gold sweep, applied to live text. */
.gold-text {
  background: var(--grad-gold);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.tagline {
  font-family: var(--font-ui);
  font-size: clamp(0.72rem, 0.6rem + 0.5vw, 0.95rem);
  font-weight: 400;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-300);
}

/* The Yiddish line that belongs under the word — ארגינעל. היימיש. פראפעסיאנאל.
   It is a PICTURE, not text, on purpose: --font-ui is Jost, which carries no
   Hebrew glyphs at all, so setting it as text would silently substitute some
   other face right next to the logo it came from. This is lifted out of the
   client's own artwork (tools/rebuild-wordmark.mjs) and recoloured to
   --gold-300 — the exact colour the English line it replaces was using. */
.tagline-yi {
  display: block;
  width: 200px;
  max-width: 100%;
  height: auto;
}
/* `.footer-brand img` is (0,1,1) and sets a 44px height for the wordmark, so
   this needs two classes to win. Without it the Yiddish line renders at the
   wordmark's height and blows out of the column. */
.footer-brand .tagline-yi {
  /* Here the logo is left-aligned, so "flush with the ח" means matching the
     wordmark's own width exactly — then both edges line up and the two read as
     one block. Tied to the same two numbers the wordmark is drawn from, so they
     cannot drift apart: 44px tall x the 3.241 aspect of wordmark.webp = 143px.
     The Yiddish letters stand ~11px there, the same ink height the English line
     had at 15.2px.

     ⭐ ROUND 6, R6-6. "The word should be a little bit bigger. Only a little
     bit of millimeter." The bump is a MULTIPLIER on that lock rather than a
     new number, so the tie to the wordmark survives and the size is one
     obvious thing to change if 12% is not what he meant. It does mean the
     line now overhangs the wordmark's right edge by ~17px — that is the
     price of making it bigger without making the logo bigger, and he asked
     for the writing, not the logo. */
  width: calc(var(--footer-mark-h) * var(--wordmark-aspect) * var(--footer-yi-bump));
  height: auto;
  margin-bottom: 0;
}

/* ⭐ ROUND 6, R6-6 — HIS SECOND YIDDISH LINE, UNDER THE FIRST.
   "The bottom, where it has the logo, but the writing — can we add
   מוזיקאלישע וואקאלן מיטן חסידישן קלאנג this part as well?"

   LIVE TEXT, not artwork, and the reason matters. The line ABOVE it is a
   picture only because it is lifted out of his own logo art, whose display
   face (Jost) carries no Hebrew glyph at all. This line has no such tie: it
   is the same string as the home-page hero line, which has been live text in
   --font-yi (Frank Ruhl Libre) since round 4 and renders correctly. Setting
   it the same way means the two copies are literally the same characters in
   the same face — they cannot drift, it stays sharp at any size and on any
   screen, and it is selectable and findable. Generating a second .webp would
   have bought nothing and added an artwork file to keep in step.

   ⚠️ ALIGNED LEFT, DELIBERATELY (R6-15a/R6-17b). It reads right-to-left —
   that is the language and it does not change — but the BLOCK sits on the
   same left edge as the wordmark above it, like everything else on the site.
   Without `text-align: left` the initial value `start` resolves against this
   element's own `direction: rtl` and throws the line to the right. */
.footer-yi {
  margin: var(--sp-3) 0 0;
  max-width: 34ch;
  direction: rtl;
  text-align: left;
  font-family: var(--font-yi);
  font-weight: 300;
  font-size: var(--footer-yi-size);
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--gold-300);
}

/* ---- Buttons ------------------------------------------------------------ */

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--fg, var(--cream));
  --btn-bd: var(--rule, rgba(184, 137, 47, 0.28));
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.7em;
  padding: 0.95em 2.1em;
  border: 1px solid var(--btn-bd);
  border-radius: var(--radius-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  cursor: pointer;
  overflow: hidden;
  transition: color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grad-gold);
  transform: translateY(101%);
  transition: transform var(--dur-slow) var(--ease-out);
  z-index: 0;
}
.btn > * { position: relative; z-index: 1; }
.btn:hover { color: var(--plum-950); border-color: transparent; }
.btn:hover::after { transform: translateY(0); }
.btn:active { transform: translateY(1px); }

.btn--gold {
  --btn-bg: transparent;
  --btn-fg: var(--plum-950);
  --btn-bd: transparent;
}
.btn--gold::after { transform: translateY(0); }
.btn--gold:hover { color: var(--plum-950); }
.btn--gold:hover::after { filter: brightness(1.12); }

.btn--ghost { --btn-bd: var(--rule-soft); }

.btn--lg { padding: 1.15em 2.6em; font-size: 0.92rem; }

/* Text link with a gold underline that draws in. */
.link-u {
  position: relative;
  display: inline-block;
  color: var(--accent, var(--gold-300));
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  padding-bottom: 0.3em;
}
.link-u::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0.28);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease-out);
}
.link-u:hover::after { transform: scaleX(1); }

/* ---- Ornament: the seal's scrollwork, reduced to a divider ---------------- */

.rule-orn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  color: var(--rule);
}
.rule-orn::before,
.rule-orn::after {
  content: "";
  height: 1px;
  flex: 1 1 auto;
  max-width: 220px;
  background: linear-gradient(90deg, transparent, var(--gold-500), transparent);
  opacity: 0.55;
}
.rule-orn svg { width: 26px; height: 26px; color: var(--gold-500); opacity: 0.8; }

/* ---- Header ------------------------------------------------------------- */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-header);
  height: var(--header-h);
  display: flex;
  align-items: center;
  transition: background var(--dur) var(--ease-out),
              backdrop-filter var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              height var(--dur) var(--ease-out);
  border-bottom: 1px solid transparent;
}
.site-header.is-stuck {
  background: rgba(21, 12, 27, 0.82);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: var(--rule);
}
.site-header.is-hidden { transform: translateY(-100%); }
.site-header { transition-property: background, backdrop-filter, border-color, transform; }

.header-inner {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: var(--page-pad);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
}

/* The open mobile menu is a child of the header, so it paints over its own
   siblings. Without these the overlay covers the X and the only way out is the
   Escape key — which a phone does not have. Keep the brand up there too, so the
   header still reads as a header while the menu is open. */
.brand { display: flex; align-items: center; gap: 0.75rem; flex: 0 0 auto; position: relative; z-index: 120; }
.brand img { height: 34px; width: auto; }
.brand .brand-seal { height: 38px; width: 38px; border-radius: 50%; }
@media (max-width: 640px) { .brand img { height: 28px; } .brand .brand-seal { height: 32px; width: 32px; } }

.nav { display: flex; align-items: center; gap: clamp(1rem, 2vw, 2.2rem); }

/* `:not(.btn)` is load-bearing, not tidiness. `.nav a` and `.nav a::after` are
   more specific than `.btn` and `.btn::after`, so without it the underline rule
   below wins and the gold "Book Us" pill renders as plain nav text with a
   1px line — no gold fill, unreadable dark-on-dark label. */
.nav a:not(.btn) {
  position: relative;
  font-size: var(--t-small);
  font-weight: 400;
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  color: rgba(245, 230, 192, 0.78);
  padding-block: 0.35em;
  transition: color var(--dur) var(--ease-out);
}
.nav a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--gold-300);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--dur) var(--ease-out);
}
.nav a:not(.btn):hover { color: var(--gold-100); }
.nav a:not(.btn):hover::after,
.nav a:not(.btn)[aria-current="page"]::after { transform: scaleX(1); transform-origin: left; }
.nav a:not(.btn)[aria-current="page"] { color: var(--gold-300); }

.nav .btn { padding: 0.7em 1.5em; font-size: var(--t-mini); }

.nav-toggle {
  display: none;
  position: relative;
  z-index: 120;
  width: 44px;
  height: 44px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  background: transparent;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 5px;
}
.nav-toggle span {
  display: block;
  width: 17px;
  height: 1px;
  background: var(--gold-300);
  transition: transform var(--dur) var(--ease-out), opacity var(--dur) var(--ease-out);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 900px) {
  .nav-toggle { display: flex; }
  .nav {
    position: fixed;
    inset: 0;
    z-index: var(--z-menu);
    flex-direction: column;
    justify-content: center;
    gap: var(--sp-5);
    background: rgba(21, 12, 27, 0.97);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur) var(--ease-out), visibility var(--dur);
  }
  .nav.is-open { opacity: 1; visibility: visible; }
  .nav a { font-size: 1.15rem; letter-spacing: 0.2em; }
  .nav .btn { margin-top: var(--sp-4); font-size: 0.85rem; }
}

/* ---- Footer ------------------------------------------------------------- */

.site-footer {
  position: relative;
  background: var(--plum-900);
  border-top: 1px solid var(--rule);
  padding-block: var(--sp-9) var(--sp-6);
  overflow: hidden;
}
/* The watermark bleeds off the right edge on purpose, and on a phone that bleed
   became 8px of real horizontal page scroll. `clip`, not `hidden`: hidden makes
   this a scroll container, which would change the containing block for any
   sticky element inside it. clip does not. */
.site-footer { overflow-x: clip; }
.site-footer .seal-watermark {
  position: absolute;
  right: -8%;
  bottom: -34%;
  width: min(560px, 62vw);
  opacity: 0.05;
  pointer-events: none;
  filter: grayscale(1) brightness(1.6);
}
.footer-grid {
  position: relative;
  display: grid;
  /* ⭐ ROUND 6, R6-7. Explore used to be one column of eight and this template
     was 1.4fr 1fr 1fr. Now Explore holds TWO columns of four, so its track has
     to grow or the second column squeezes Reach Us off the end — which is
     exactly the tablet reflow the spec warned about. The brand gives up the
     width, not Reach Us: it holds a 143px wordmark and one line of Yiddish and
     had the most slack of the three. */
  grid-template-columns: 1.15fr 1.5fr 1fr;
  gap: var(--sp-7);
  align-items: start;
}
/* ⭐ "In the bottom, you gotta split that into two columns."
   This used to collapse to a single column, which stacked the logo, Explore and
   Reach Us into one long ribbon — five link rows then three contact rows, all
   full width, for no reason. The brand keeps the full width; the two link
   columns sit side by side under it. */
@media (max-width: 820px) {
  .footer-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-7) var(--sp-5);
    align-items: start;
  }
  .footer-brand { grid-column: 1 / -1; }
}

/* --wordmark-aspect is the delivered width/height of wordmark.webp (1400/432).
   The Yiddish line under it is sized from these two so the pair stay locked
   together — see `.footer-brand .tagline-yi`. */
.footer-brand {
  --footer-mark-h: 44px;
  --wordmark-aspect: 3.241;
  /* R6-6 — "a little bit bigger. Only a little bit of millimeter." Both
     Yiddish lines under the logo go up one notch together: the artwork line
     by this factor, the live line by the size below. Two numbers, one place. */
  --footer-yi-bump: 1.12;
  --footer-yi-size: clamp(0.82rem, 0.76rem + 0.26vw, 0.98rem);
}
.footer-brand img { height: var(--footer-mark-h); width: auto; margin-bottom: var(--sp-4); }
.footer-col h3 {
  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);
  margin: 0 0 var(--sp-4);
}
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.footer-col a { color: rgba(245, 230, 192, 0.72); transition: color var(--dur) var(--ease-out); }
.footer-col a:hover { color: var(--gold-200); }

/* ⭐ ROUND 6, R6-7 — "Explore: though we get this divided in two sections,
   like right now it's one big line."

   Eight links running straight down next to a three-line Reach Us made the
   footer tall and lopsided, with dead space under both. Two columns of four
   halves the height and lands the block level with Reach Us.

   ONE heading over both — he said two SECTIONS of one list, not two lists —
   and the order is untouched: `grid-auto-flow: column` over four explicit
   rows fills DOWN the first column and then DOWN the second, so nothing moves
   relative to anything else.

   ⚠️ Selected by COUNTING the list, not by a class on the markup, and that is
   deliberate. tools/apply-chrome.mjs regenerates everything between
   <!-- cc:footer-cols --> from a template of its own; a class added here would
   be silently dropped the next time anyone runs it. Reach Us has three links,
   Explore has eight, so "has a fifth item" identifies it and survives.

   ⚠️ Not below 700px. In the two-column footer a phone gives Explore about
   170px, and two sub-columns of 85px cannot hold "Shabbos Choir". */
@media (min-width: 700px) {
  .footer-col:has(li:nth-child(5)) ul {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(4, auto);
    grid-auto-flow: column;
    column-gap: var(--sp-5);
  }
}

/* These stopped being decoration the moment they became real tel:, wa.me and
   mailto: links — a mis-tap now dials the wrong thing instead of doing nothing.
   At 23px tall with 13px of dead space between them they were a thumb's width
   of nothing; this fills the gap that already exists rather than adding height,
   so the footer does not grow. Touch only — on a mouse 23px is plenty.

   ⚠️⚠️ ROUND 10 — `display: inline-block` USED TO BE ON THIS RULE AND IT BROKE
   THE ICON ROWS ON EVERY TOUCH DEVICE. He photographed the footer on his phone:
   the little phone/WhatsApp/envelope icons were sitting on their own line with
   the value underneath, instead of beside it.

   `.with-ic` sets `display: inline-flex` — but `.footer-col a` is (0,1,1) and
   `.with-ic` is (0,1,0), so THE CONTAINER'S DESCENDANT SELECTOR WINS and the
   link fell back to inline-block. The icons are `display: block` svgs, so an
   inline-block box stacked them above the text. This file's own recurring
   lesson: check what ELSE matches before trusting a component's own class.

   ⚠️ IT WAS INVISIBLE ON EVERY DESKTOP. The gate is `hover: none`, so a mouse
   never loads this rule and the footer looked perfect — measured at 1366 the
   links are inline-flex and on one line. TABLETS WERE BROKEN TOO, not just
   phones; he only happened to notice on the phone.

   The tap target is the point of this rule and it is unchanged — only the
   display is corrected, and `align-items: center` keeps the icon on the text's
   optical centre now that the box is a flex container again. */
@media (hover: none) {
  .footer-col a {
    display: inline-flex;
    align-items: center;
    padding-block: 0.42rem;
    margin-block: -0.42rem;
  }
}

/* ⚠️ AND ONE LINE IS NOT FREE — THE EMAIL DOES NOT FIT AT FULL SIZE. He asked
   for these on one line, and on a phone the footer is TWO columns (his own
   round-6 instruction, "in the bottom, you gotta split that into two columns"),
   which leaves Reach Us about 171px at 390 and 170px at 320. Measured:

       (646) 989-7000            108px  + icon 18 + gap 10 = 136   fits
       WhatsApp                   70px  + icon 18 + gap 10 =  98   fits
       info@chassidishchoir.com  171px  + icon 18 + gap 10 = 199   DOES NOT

   So the address alone would have wrapped under its own icon and the row would
   still have looked broken — the exact thing being fixed. Dropping the contact
   rows one notch buys the width the icon costs, and it is applied to ALL THREE
   so they stay a set rather than one odd small line. The icon's gap tightens
   with them, because at this size 0.62em of air between a 15px glyph and the
   text is more than the row can spare.

   ⚠️ 0.82em WAS NOT ENOUGH AND IT LOOKED LIKE IT WAS. At that size the address
   came to ~72px against ~70px of room and broke across two lines, leaving a
   single "m" on its own — and a check comparing the icon's centre to the text's
   centre PASSED, because the icon obligingly centred itself against the taller
   two-line block. Only the screenshot caught it. The check now counts the
   text's own client rects, which is the thing actually being asserted.

   ⛔ Do NOT solve this by collapsing the footer to one column. He asked for two
   columns explicitly, and that instruction is recorded above. */
@media (max-width: 699px) {
  .footer-col a.with-ic {
    font-size: 0.76em;
    gap: 0.5em;
    /* a flex item will not shrink below its content unless told it may; without
       this a long address forces the column wider and the page scrolls sideways */
    max-width: 100%;
  }
  .footer-col a.with-ic span {
    min-width: 0;
    /* ⚠️ NOT `overflow-wrap: anywhere` — that lets the address break mid-word at
       the first opportunity, which is how the lone "m" happened. The row is
       sized to fit; if a future address is genuinely too long it should be
       obvious, not silently chopped. */
    white-space: nowrap;
  }
}

.footer-bottom {
  position: relative;
  margin-top: var(--sp-8);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--rule-soft);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: space-between;
  align-items: center;
  font-size: var(--t-mini);
  color: var(--fg-faint);
}
/* ⭐ ROUND 6, R6-8. "Built for chasunas, sheva brochos and simchos" came off —
   a slogan sitting where people expect information, and the same words already
   do a job on the Services page. What replaced it is a privacy link, and
   nothing else: no service-area line, no credit, no back-to-top. */
.footer-bottom a {
  color: var(--fg-faint);
  transition: color var(--dur) var(--ease-out);
}
.footer-bottom a:hover { color: var(--gold-200); }

/* ---- Cards: shared by Videos and Gallery -------------------------------- */

.grid-media {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
  gap: clamp(1rem, 1.6vw, 1.75rem);
}
.grid-media--tight {
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
}

.card {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--plum-800);
  border: 1px solid var(--rule-soft);
  cursor: pointer;
  /* media.js renders every card as a <button>, and Chrome's default button
     padding is `1px 6px`. That left a 7px band down each side and 2px top and
     bottom where this element's own plum background showed BETWEEN the border
     and the picture — which is exactly "the picture is inserted inside the
     border, you can still see behind it".
     ⚠️ This reset used to live in pages.css scoped to the videos and gallery
     grids only, so the gallery page looked right and the home page did not. It
     belongs on the component, not on two of its containers. */
  padding: 0;
  text-align: left;
  font: inherit;
  transition: transform var(--dur-slow) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out);
}
.card:hover {
  transform: translateY(-6px);
  border-color: var(--rule);
  box-shadow: var(--shadow);
}
.card__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--plum-800);
}
.card--portrait .card__frame { aspect-ratio: 9 / 16; }
.card--square .card__frame { aspect-ratio: 1 / 1; }

/* A vertical clip in a video grid: keep the 16:9 cell so the grid stays flush,
   letterbox the poster, and fill the sides with a blurred, darkened copy of
   itself. Letting it span two rows instead leaves a ~290px crater under the
   landscape card beside it; cropping it to 16:9 throws away most of the frame.
   media.js sets --thumb and picks this class over .card--portrait for videos. */
.card--letterbox .card__frame {
  aspect-ratio: 16 / 9;
  background: var(--plum-950);
}
.card--letterbox .card__frame::before {
  content: "";
  position: absolute;
  inset: -8%;
  background-image: var(--thumb);
  background-size: cover;
  background-position: center;
  filter: blur(26px) brightness(0.45) saturate(0.7);
  z-index: 0;
}
.card--letterbox .card__frame img {
  position: relative;
  z-index: 1;
  object-fit: contain;
}
/* Deliberately shallower than the normal card zoom, and excluded from the drift
   below. This poster is `contain`, not `cover` — it is already showing its whole
   frame, so every extra percent of scale crops the top and bottom off the
   picture instead of revealing more of it.
   `scale`, not `transform`, to match the rule it is overriding. */
.card--letterbox:hover .card__frame img { scale: 1.06; }
.card--letterbox .card__frame img { animation: none !important; }
/* The letterboxed poster is z-index 1, which would otherwise paint over the
   play button (it has no z-index of its own). */
.card--letterbox .card__play { z-index: 2; }
.card__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* `cover` crops around the CENTRE of the picture, and in a standing portrait
     the centre is somebody's waist — so heads and hats get sliced off the top.
     The client pointed at one: "he's out of the screen, you're gonna move a
     little bit down." Biasing the crop window upward moves the SUBJECT down in
     the frame and keeps the head in. 34% is a portrait-photography default, not
     a number picked for one picture. */
  object-position: center 34%;
  /* SLOW on the way in, quicker on the way out. 900ms/1.06 read as a twitch —
     it snapped to its end state and then sat still. At 2.6s the picture is still
     visibly creeping the whole time the mouse rests on it, which is the "really
     slowly … it should be a lot" he asked for. `linear` on purpose: an eased
     curve decelerates to a stop and kills exactly the effect we want. */
  /* SLOW on the way in, quicker on the way out. `linear` on purpose: an eased
     curve decelerates to a stop and kills exactly the effect we want.
     ⚠️ The hover zoom rides the INDEPENDENT `scale` property, not `transform`.
     The drift below is a keyframe animation on `transform`, and an animation
     beats any plain declaration for the same property — so a hover written as
     `transform: scale()` would simply never apply once the drift runs. The two
     are separate properties, and the browser multiplies them. */
  transition: scale 2600ms linear, filter var(--dur-slow) var(--ease-out);
  filter: saturate(0.94) brightness(0.94);
}
.card:hover .card__frame img,
.card:focus-visible .card__frame img {
  scale: 1.16;
  filter: saturate(1.04) brightness(1);
}
/* Leaving is a retreat, not a rewind — snapping back at 2.6s of linear looks
   broken when the pointer flicks across a grid. */
.card:not(:hover) .card__frame img { transition-duration: 700ms, var(--dur-slow); }

/* "The pictures should move by itself… not even if I hover over it."
   A long, shallow drift, staggered so the grid never pulses in unison.
   Transform-only, so it stays on the compositor and costs no layout.
   This used to be gated to `(hover: none)` — phones only — on the theory that
   it would fight the hover transition. It does not: the drift owns `transform`,
   hover owns `scale`, and they compose. Idle 1.12 x hover 1.16 = 1.30 at most. */
@media (prefers-reduced-motion: no-preference) {
  .card__frame img { animation: card-drift 22s var(--ease-in-out) infinite alternate; }
  .card:nth-child(3n + 2) .card__frame img { animation-delay: -7s; }
  .card:nth-child(3n + 3) .card__frame img { animation-delay: -14s; }
}
@keyframes card-drift {
  from { transform: scale(1); }
  to { transform: scale(1.12); }
}

.card__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: linear-gradient(180deg, rgba(21, 12, 27, 0.05), rgba(21, 12, 27, 0.5));
}
.card__play span {
  width: clamp(52px, 5vw, 68px);
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
  /* No `backdrop-filter` here. It used to blur the video showing through the
     disc, and the client's read was blunt: "the center of the play, it's blurry.
     I don't like it." A frosted disc over a moving picture never resolves — the
     eye keeps trying to focus on something that is smeared on purpose. So the
     disc is opaque enough to read as a solid object instead. */
  border: 1px solid rgba(245, 230, 192, 0.72);
  background: rgba(21, 12, 27, 0.62);
  transition: background var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
/* ⭐ ROUND 6, R6-13c. "The play button doesn't look in center from the ball…
   something looks off." He was right, and it was measured: the triangle sat
   +39% of its own width right of the circle's centre.

   THREE separate nudges had been stacked on one shape — the optical correction
   a right-pointing arrow genuinely needs (a triangle centred by its BOUNDING
   BOX always reads left of centre, so it wants +6 to +10%), a push already
   built into the drawing, and this `margin-left: 8%` on top of both.

   ⚠️ DO NOT PUT A MARGIN BACK HERE. The nudge now lives in the icon artwork
   itself (icons.js), applied ONCE, at +8%. Anything added here stacks on top
   of it and the arrow drifts right again — which is exactly how this broke. */
.card__play svg { width: 34%; color: var(--cream); }
.card:hover .card__play span {
  background: var(--gold-300);
  border-color: var(--gold-300);
  transform: scale(1.08);
}
.card:hover .card__play svg { color: var(--plum-950); }

.card__meta {
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
}
.card__title {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.55rem);
  line-height: 1.15;
  color: var(--cream);
}
.card__sub {
  font-size: var(--t-mini);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-400);
  white-space: nowrap;
}

/* ---- States: empty / loading / error ------------------------------------ */

.state {
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
  color: var(--fg-dim);
  border: 1px dashed var(--rule);
  border-radius: var(--radius-lg);
}
/* ⚠️ R8-15 — --accent, NOT a fixed gold. .state is rendered by press.js into
   BOTH media.html sections, and those are .on-paper — so #E3C877 was cream on
   cream and "Nothing here yet" was near-invisible the day a folder empties.
   Third instance of the same shape: a dark-theme colour landing on paper. */
.state strong { display: block; color: var(--accent, var(--gold-300)); font-weight: 500; margin-bottom: 0.4em; }

.skeleton {
  border-radius: var(--radius-lg);
  background: linear-gradient(100deg, var(--plum-800) 30%, var(--plum-700) 50%, var(--plum-800) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.5s linear infinite;
  aspect-ratio: 16 / 9;
}

/* ⭐ R8-16 — THE SHAPES THE OTHER TWO PAGES NEED.
   A skeleton is only worth anything if it is the same shape as the thing that
   replaces it — otherwise it reserves the wrong space and the page still jumps,
   just by less. These match their real counterparts exactly:
     --tall   .article__frame  3/4   (a magazine cover)
     --wide   .press-wall .card__frame  3/2  (a press photo)
     --square the album cover  1/1
     --row    a track row, which is not a card at all — it is a fixed-height
              line, so it takes a height rather than a ratio.
   ⚠️ On the cream sections these sit on paper, so the plum shimmer would be a
   dark slab on a light page. They take a paper-tinted shimmer there. */
.skeleton--tall { aspect-ratio: 3 / 4; }
.skeleton--wide { aspect-ratio: 3 / 2; }
.skeleton--square { aspect-ratio: 1 / 1; }
.skeleton--row { aspect-ratio: auto; height: 4.6rem; }
.on-paper .skeleton,
.services-scope .skeleton {
  background: linear-gradient(100deg, #EFE6D2 30%, #F7F1E2 50%, #EFE6D2 70%);
  background-size: 220% 100%;
}
@keyframes shimmer {
  from { background-position: 140% 0; }
  to { background-position: -40% 0; }
}

/* ---- Reveal on scroll (progressive: visible if JS never runs) ------------ */

.reveal { opacity: 1; }
html.js .reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 900ms var(--ease-out), transform 900ms var(--ease-out);
  will-change: opacity, transform;
}
html.js .reveal.is-in { opacity: 1; transform: none; }
html.js .reveal[data-delay="1"] { transition-delay: 90ms; }
html.js .reveal[data-delay="2"] { transition-delay: 180ms; }
html.js .reveal[data-delay="3"] { transition-delay: 270ms; }
html.js .reveal[data-delay="4"] { transition-delay: 360ms; }

@media (prefers-reduced-motion: reduce) {
  html.js .reveal { opacity: 1; transform: none; transition: none; }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    /* ⭐ ROUND 5. animation-DELAY has to go too, and it was missing.
       Killing only the duration leaves the delay intact, so an element with
       `animation-fill-mode: both` sits frozen at its FIRST keyframe until the
       delay elapses. The new hero entrance starts the coin off-screen at 1.6s,
       which for a reduced-motion visitor meant staring at a hero with no logo
       in it for a second and a half and then having it appear from nowhere —
       the exact jolt the setting exists to prevent. */
    animation-delay: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---- Video lightbox (shared by Home + Videos + Gallery) ------------------ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(11, 6, 15, 0.94);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur) var(--ease-out), visibility var(--dur);
}
.lightbox.is-open { opacity: 1; visibility: visible; }
.lightbox__stage {
  position: relative;
  width: min(1180px, 100%);
  max-height: 86vh;
  display: grid;
  place-items: center;
  transform: scale(0.97);
  transition: transform var(--dur-slow) var(--ease-out);
}
.lightbox.is-open .lightbox__stage { transform: scale(1); }
.lightbox video,
.lightbox img {
  width: auto;
  max-width: 100%;
  max-height: 86vh;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  background: #000;
}
.lightbox__caption {
  margin-top: var(--sp-4);
  text-align: center;
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--cream);
}
.lightbox__close,
.lightbox__nav {
  position: absolute;
  z-index: 2;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: rgba(21, 12, 27, 0.7);
  color: var(--cream);
  cursor: pointer;
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.lightbox__close:hover,
.lightbox__nav:hover { background: var(--gold-300); color: var(--plum-950); }
.lightbox__close { top: clamp(0.75rem, 2vw, 1.5rem); right: clamp(0.75rem, 2vw, 1.5rem); position: fixed; }
.lightbox__nav--prev { left: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); position: fixed; }
.lightbox__nav--next { right: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); position: fixed; }
.lightbox__nav svg { width: 18px; }

/* ---- Page hero used by the three inner pages ---------------------------- */

/* ⭐ ROUND 4, ITEM 8 — HE MEASURED THIS BY EYE AND HE WAS RIGHT.
   Of the Videos page: "it's too big of a space. Like, we gotta bring it more
   up, the entire thing, so it shouldn't look that empty." He sent it to
   illustrate the Music page but said the same fix applies to both — and since
   every interior page shares this block, it applies to all of them.

   Measured off his screenshot: VIDEOS / Watch / Live from real simchos.
   occupied about 500px of a 780px-tall viewport before a single card appeared,
   and the right-hand two thirds were empty.

   Two changes, and they are different problems:
     · the HEIGHT comes down here — 204px of top padding becomes ~140, and 80px
       of bottom becomes ~48. That is ~96px returned to the content.
     · the EMPTY RIGHT-HAND SIDE is not a padding problem and cannot be fixed
       here. That is what the musical mark in ornament.css is for; he named the
       waves as the answer to this exact complaint.

   `overflow: hidden` is deliberately NOT changed to clip: this block has no
   sticky children and the radial below must be cropped. */
.page-hero {
  position: relative;
  /* ⭐ R7-3a — "it's too big on top". Measured on media.html before the change:
     364px tall on a 900px screen (40%) for a 73px headline — the box was four
     fifths air.

     ⚠️ AND A THIRD CANNOT COME OUT OF PADDING, WHICH IS WORTH KNOWING BEFORE
     ANYONE TRIES AGAIN. 76px of the padding-top is `--header-h` — it is not
     padding, it is the reserve under the fixed header, and removing it puts the
     eyebrow behind the header. Measured floor with ALL removable padding gone:
     253px at 1440, 284px at 820, 271px at 390. So the honest ceiling on this
     lever is about -30%, -23%, -19%, and that floor is visually broken.

     These values are the most aggressive that still leave real clearance:
       1440  364 -> 292  (-20%)   eyebrow sits 28px under the header
       820   368 -> 312  (-15%)   22px
       390   336 -> 291  (-13%)   16px
     ⚠️ SEVEN PAGES SHARE THIS — media, gallery, videos, music, services,
     contact, privacy. All seven were re-measured after the change. */
  padding-top: calc(var(--header-h) + clamp(0.4rem, 0.2rem + 0.8vw, 1.1rem));
  padding-bottom: clamp(0.6rem, 0.45rem + 0.55vw, 1rem);
  overflow: hidden;
  background: var(--plum-900);
  border-bottom: 1px solid var(--rule-soft);
}
.page-hero::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -40%;
  width: min(900px, 120vw);
  aspect-ratio: 1;
  translate: -50% 0;
  background: radial-gradient(circle, rgba(184, 137, 47, 0.16), transparent 62%);
  pointer-events: none;
}
.page-hero .wrap { position: relative; z-index: 1; }

/* ⭐ ROUND 6 — R6-10 / R6-11 / R6-13a / R6-14. HE SAID THIS ON FOUR PAGES.
   "The words on top, it's too big. Gonna make a little bit smaller."

   Fixed HERE, at the component, once — not four times on four pages. Every
   interior page shares this block, so fixing the two he named and leaving the
   other four is how a site ends up with four different heading sizes.

   It was `var(--t-h1)`, which is clamp(2.5rem, 1.4rem + 5.4vw, 6rem) — a
   scale built for the home page, and 96px of it on a 1440 desktop. `A TAST OF
   CHASSIDISH` at that size ran nearly the full width of the screen and was the
   largest type anywhere on the site.

   This is one step down, not a different heading: 96 -> 70px at 1440,
   67 -> 52px at 820, 40 -> 33px at 390. Landed by putting all six pages side
   by side at those three widths, not by picking a number.

   ⚠️ NOT --t-h1 itself. That token is shared with .h1 and with the home page,
   and neither was in the complaint. */
/* ⭐ R7-4 — "It's too big, the header." SECOND PASS, and this time it is the
   HEADLINE, not the padding. The padding pass (R7-3a) took the block from 364
   to 292 and he still says it is too big — correctly, because after that trim
   THE TYPE became the biggest thing left in it. At 1440 the h1 alone was
   70.4px on a 292px block.
   ⚠️ Padding could not have solved this: the floor is 253px and the h1 was
   nearly a third of it. */
.page-hero h1 { font-size: clamp(1.75rem, 1.15rem + 2.75vw, 3.35rem); }
.page-hero .lead { margin-top: var(--sp-3); }
/* The page hero already carries generous bottom padding; the section after it
   was stacking a full --section-y on top and leaving a dead band. */
.page-hero + .section { padding-top: clamp(2.25rem, 1.6rem + 2.2vw, 4rem); }

/* ============================================================================
   YIDDISH — round 4. Items 6, 7, 11, 13, 16.
   ============================================================================
   His rule, given twice and in capitals both times: "Do not change any of the
   words spelling… It has to be exactly as it is over here as I paste it in. You
   have to verify that twice if it's identical."

   Every Yiddish string on this site comes from a file in `content/`, copied in
   byte for byte. Nothing here is retyped and nothing is "tidied". What THIS
   block does is make sure the browser renders those bytes correctly, which is a
   separate problem from getting them right in the first place — and one that
   fails silently. Hebrew set without `dir="rtl"` still shows the right letters:
   it puts the punctuation, the em dashes and the parentheses on the wrong side,
   which looks like sloppy typing rather than like a bug.
   ========================================================================== */

.yi {
  direction: rtl;
  text-align: right;
  font-family: var(--font-yi);
  /* Hebrew has no ascenders and descenders to speak of, so the same numeric
     line-height that looks airy in Latin looks cramped here. */
  line-height: 1.85;
  /* Frank Ruhl Libre's default 400 is heavier on screen than Cormorant's 400,
     and the two sit next to each other on this site. */
  font-weight: 300;
  letter-spacing: 0;
}
.yi p { max-width: 46ch; }
.center .yi,
.yi.center { text-align: center; }

/* A heading in Yiddish. Bigger and heavier than the body, but never as heavy as
   the Latin display face at the same size — Hebrew has no lower case, so it is
   already carrying more visual weight per line. */
.yi-h {
  direction: rtl;
  font-family: var(--font-yi);
  font-weight: 500;
  line-height: 1.32;
  letter-spacing: 0;
  /* base.css sets -0.012em on h1-h4 globally, which closes up Hebrew badly. */
}
h1.yi-h, h2.yi-h, h3.yi-h { letter-spacing: 0; line-height: 1.32; }

/* ⭐ THE SIGN-OFF. `א שמחה מיט חסיד'יש: איז חסיד'יש` appears in FIVE places —
   the home booking block, the booking page header, the Gallery and Videos
   booking strips, and the end of About. He was explicit that all of them must
   be character-identical, apostrophe and colon and all, so they are all set
   from the same class and none of them may be restyled locally. */
.yi-signoff {
  direction: rtl;
  font-family: var(--font-yi);
  font-weight: 500;
  line-height: 1.34;
  letter-spacing: 0;
}

/* Bold and underline inside his pasted copy. His screenshots carry both, and
   they are not decoration — they are where he put the emphasis. */
.yi b, .yi strong { font-weight: 700; }
.yi u, .yi .u {
  text-decoration: none;
  /* A plain underline collides with the descenders of ך ן ף ץ ק. An offset
     gold rule reads as emphasis without cutting the letters. */
  box-shadow: inset 0 -1px 0 0 var(--accent, var(--gold-300));
  padding-bottom: 0.08em;
}

/* An RTL block inside an otherwise LTR page still inherits the page's list and
   quote handling; these keep the bullets and the marks on the correct side. */
.yi ul, .yi ol { padding-right: 1.2em; padding-left: 0; }

/* ============================================================================
   THE BOX GRID — round 4, item 14. On EVERY page.
   ============================================================================
   "He wants in the bottom of the home page a section… boxes which all the
   services are there, meaning all the pages that we have. It should be nice
   organized. Four on top and three on the bottom row. You should make it alive,
   active and nice." And then, at the end of the same message, the part that is
   easy to miss: "whatever you're gonna create, on all pages."

   ⚠️ THIS IS LOAD-BEARING NAVIGATION, NOT DECORATION. Shabbos Choir and Media
   are deliberately kept OUT of the top menu — he said so for both — so this
   grid is the only route to either of them. If it is missing from a page, two
   pages of the site are unreachable from that page.

   The 4+3 split is his, and it is honoured exactly: a 12-column track where the
   first four cards span 3 and the last three span 4. Both rows come out flush
   at full width, which a `repeat(4, 1fr)` grid does not — that leaves a hole in
   the second row.

   The reference he sent (the Shira grid) is CONCEPT ONLY: "we don't want that
   example, something different — but that idea." He also said plainly he does
   not like their icons. Ours are drawn from scratch in icons.js.
   ========================================================================== */

.wayfind {
  position: relative;
  padding-block: clamp(3rem, 2.2rem + 3.4vw, 5.5rem);
  background: var(--plum-900);
  border-top: 1px solid var(--rule);
}

.wayfind__head { margin-bottom: var(--sp-6); }
.wayfind__head .label { margin-bottom: var(--sp-3); }

.wayfind__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.65rem, 1.1vw, 1.15rem);
}

/* 7 into 2 columns leaves one orphan; it takes the full width rather than
   sitting next to a hole.
   ⚠️ THIS RULE IS FOR THE PHONE/TABLET STACK ONLY and must never lose its
   media-query scoping below. Above 900px the grid is eight half-columns and
   the last card spans two like every other one; an unscoped `auto` here left
   Media as a half-width stump at the end of the row — and a row-width check
   PASSED on it, because it read the first card in each row. Only the picture
   caught it. */
.wayfind__grid > :last-child { grid-column: 1 / -1; }

/* ⭐ ROUND 6, R6-5 / R6-8b — THE ROWS WERE CROOKED, AND THAT WAS A DEFECT.
   Seven cards into a 12-column track meant span 3 on the top row and span 4 on
   the bottom: the bottom three came out 100px WIDER than the four above them,
   and only the left edge lined up.

   Eight HALF-columns instead. Every card spans two — so every card in the block
   is exactly the same width — and the short row of three opens one half-column
   in, which centres it under the four. No stretched cards, no hole at the end
   of the row, and no eighth card invented to make the maths come out. There
   are SEVEN and there is no eighth: he said so, and the "Book Us" box I once
   added to even up a row is exactly the mistake this arrangement exists to
   avoid. */
@media (min-width: 900px) {
  .wayfind__grid { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  .wayfind__grid > *,
  .wayfind__grid > :nth-child(n + 5),
  .wayfind__grid > :last-child { grid-column: span 2; }
  /* card 5 opens the second row one half-column in — this is what centres the
     three under the four. */
  .wayfind__grid > :nth-child(5) { grid-column: 2 / span 2; }
}

.wf {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: clamp(1rem, 0.8rem + 0.7vw, 1.5rem);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-lg);
  background: linear-gradient(168deg, rgba(74, 49, 89, 0.34), rgba(21, 12, 27, 0.5));
  overflow: hidden;
  /* "Alive, active and nice." */
  transition: transform var(--dur-slow) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out);
}
/* A gold wash that sweeps up from the bottom on hover — the same move the
   buttons make, so the grid belongs to the same site. */
.wf::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 50% 118%, rgba(184, 137, 47, 0.3), transparent 66%);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
  pointer-events: none;
}
.wf > * { position: relative; z-index: 1; }

.wf:hover,
.wf:focus-visible {
  transform: translateY(-5px);
  border-color: var(--rule);
  box-shadow: var(--shadow);
}
.wf:hover::after,
.wf:focus-visible::after { opacity: 1; }
.wf:hover .tile,
.wf:focus-visible .tile {
  transform: translateY(-2px) scale(1.05);
  color: var(--gold-100);
  box-shadow:
    inset 0 1px 0 rgba(242, 227, 179, 0.42),
    inset 0 0 0 1px rgba(184, 137, 47, 0.5),
    0 14px 30px rgba(0, 0, 0, 0.42);
}
.wf:hover .tile::before,
.wf:focus-visible .tile::before { opacity: 1; }

.wf .tile { font-size: clamp(0.78rem, 0.7rem + 0.35vw, 1rem); }

.wf__title {
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 1.05rem + 0.55vw, 1.55rem);
  line-height: 1.12;
  color: var(--cream);
}
/* The two Yiddish-facing destinations name themselves in Yiddish. */
.wf__title.yi-h { font-size: clamp(1.1rem, 0.98rem + 0.5vw, 1.4rem); text-align: right; }

.wf__sub {
  margin: 0;
  max-width: none;
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--fg-dim);
}
/* Hebrew has no lower case and no ascenders, so at the same nominal size it
   reads a good deal smaller than the Latin caption beside it — on screen the
   Shabbos card's line was noticeably harder to read than its six neighbours.
   Same optical size, different numbers. */
.wf__sub.yi { font-size: calc(var(--t-small) * 1.06); line-height: 1.75; }

/* The arrow that says this is a way out of here, not a fact. */
.wf__go {
  margin-top: auto;
  padding-top: var(--sp-2);
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-400);
  transition: gap var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.wf__go::after {
  content: "";
  width: 14px;
  height: 1px;
  background: currentColor;
  transition: width var(--dur) var(--ease-out);
}
.wf:hover .wf__go { color: var(--gold-200); gap: 0.75em; }
.wf:hover .wf__go::after { width: 22px; }

/* On a phone two cards abreast leaves ~150px of text column. The caption is the
   first thing to go — the icon and the name carry it. */
@media (max-width: 520px) {
  .wf { gap: var(--sp-2); padding: 0.85rem; }
  .wf__sub { display: none; }
  .wf__go { font-size: var(--t-label); }
}

/* ============================================================================
   ⭐ ROUND 6, R6-5 — "REAL PICTURES IN THEM". LOCKED BY HIM: "I like this one.
   Get this locked in."
   ============================================================================
   His complaint was: "everything looks to the same color… I like that when I
   hover over my mouse, it's getting like this yellow pop. But it doesn't look
   good to me. You can do much better than that." Measured, he was describing
   the CSS exactly — all seven cards carried the identical gradient, the
   identical border, the identical gold tile and the identical gold OPEN link.

   So each card now carries a photograph of the thing it goes to, dark and
   desaturated standing still, brightening and lifting under the mouse. The
   gold hover pop he already likes is kept, on top of it.

   ⚠️ EVERY LINE BELOW IS PORTED FROM site/_sample-boxes.html `.v-1`, which is
   the version he reviewed and approved. It is not re-derived here. The crops
   in particular are hand-set per picture and are not to be "tidied" to
   `center` — that is what put the interesting part of several of them off the
   top or the bottom of the card in the first place. */

.wf { min-height: 288px; justify-content: flex-end; }

/* ⚠️ .wf__go carries `margin-top: auto` above, which eats ALL the free space in
   the card — so `justify-content: flex-end` does nothing and the title stays
   pinned to the TOP, sitting on the busiest part of the photograph. Cancelling
   it is what lets the writing sit down in the dark band at the foot. */
.wf__go { margin-top: var(--sp-2); }
.wf .tile { align-self: flex-start; margin-bottom: auto; }

.wf__pic {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.55;
  filter: grayscale(0.25) saturate(0.9);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out),
              filter var(--dur-slow) var(--ease-out);
}
.wf:hover .wf__pic,
.wf:focus-visible .wf__pic { opacity: 0.82; transform: scale(1.05); filter: none; }

/* Readability floor. Without it the caption sits on whatever the photograph
   happens to be doing at that spot — and on the Media and Shabbos pictures
   that is a face and a bright stage. Nearly opaque under the writing, nearly
   clear over the top third. */
.wf::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(21, 12, 27, 0.18) 0%,
    rgba(21, 12, 27, 0.42) 32%,
    rgba(21, 12, 27, 0.88) 62%,
    rgba(21, 12, 27, 0.97) 100%
  );
  pointer-events: none;
}

/* ⚠️⚠️ THE SCRIM WAS PAINTING UNDER THE PHOTOGRAPH, AND THIS IS THE FIX.
   `::before` is generated as the element's FIRST child, so a real child like
   .wf__pic declared after it paints ON TOP — both sat at z-index 0 and DOM
   order decided it. Every readability gradient was being covered by the very
   picture it existed to darken, and the captions sat on bare photographs.
   Picture 0, scrim 1, writing 2. Written with the child combinator so it beats
   the plain `.wf > * { z-index: 1 }` above on specificity, not on order. */
.wf > .wf__pic { z-index: 0; }
.wf::before { z-index: 1; }
.wf > *:not(.wf__pic) { position: relative; z-index: 2; }

/* ---- THE CROPS, one per picture ---------------------------------------- */
.wf:nth-child(2) .wf__pic { background-position: 50% 45%; }  /* the wax seal   */
.wf:nth-child(3) .wf__pic { background-position: 50% 42%; }  /* the choir      */
.wf:nth-child(4) .wf__pic { background-position: 38% 52%; }  /* the singer     */
.wf:nth-child(5) .wf__pic { background-position: 50% 38%; }  /* the mural      */
.wf:nth-child(6) .wf__pic { background-position: 50% 62%; }  /* the set tables */
.wf:nth-child(7) .wf__pic { background-position: 50% 34%; }  /* the print page */

/* ⭐ ABOUT is the CUT-OUT of him, not the studio frame it came from — the
   studio shot showed the backdrop, its crease and the clamp holding it, which
   is a photograph of a photo shoot. The cut-out has no background at all, so
   he stands on the card's own plum, and that changes two things. */
.wf:nth-child(1) .wf__pic {
  /* `contain` made him exactly card-height, so his hat sat hard against the
     top edge and the icon tile landed on his face. 84% of the height drops him
     clear of both. */
  background-size: auto 84%;
  background-repeat: no-repeat;
  /* ⭐ R7-1 — "This picture, can you put on on the left side? he needs to be a
     little bit more on the left so that does not look good."
     WAS 88%. WHAT HE WAS LOOKING AT: at 84% height the picture renders about
     130px wide inside a 285px card, so 155px of the card is free travel and
     each 1% is 1.55px. At 88% he sat 19px from the right edge — and HIS RAISED
     HAND WAS CLIPPED BY IT. That is the "does not look good".
     74% moves him 22px left, which clears the hand with room to spare and is
     still short of the writing: below about 66% his coat starts crowding
     "About", and by 58% it is behind the sentence.
     ⚠️ Checked at four positions by SCREENSHOTTING THE CARD, not by reasoning
     about the number — the clipped hand is invisible in any measurement.
     ⚠️ Leave the Y at 100%: he stands on the card's bottom edge. */
  background-position: 74% 100%;
  opacity: 0.9;
  filter: none;
}
/* ⚠️ ON A PHONE THE 84% FIGURE DOES NOT FIT THE CARD, AND MOVING HIM LEFT MADE
   THAT WORSE BEFORE IT MADE IT BETTER. At 390 the box grid is TWO columns, so
   the card is only 170px wide while the figure is still 130 — barely 40px of
   travel, and at any position in it he is either clipped by the right edge or
   standing on the words. It is the one width where the two goals genuinely pull
   against each other, and no single percentage solves it.

   So on a narrow card he is SMALLER, not just moved: 66% of the height is about
   102px wide, which leaves 68px of travel, and hard right clears "About" and
   "OPEN" with his hat and his raised hand both inside the card.
   ⚠️ Chosen by rendering the real card at three sizes and LOOKING at each one.
   The measurement is the same either way — it cannot see a hand touching an
   edge or a word behind a coat. */
@media (max-width: 520px) {
  .wf:nth-child(1) .wf__pic {
    background-size: auto 66%;
    /* ⚠️ NOT 100%. Hard right LOOKS correct and measures correct — the picture
       box sits flush — but the cut-out carries about 21px of transparent margin
       down its right side, which scales to ~4px here. So his FINGERTIP landed
       3.6px off the card border: not clipped, but visually flush, which is a
       hair away from the complaint that started this item.
       92% pulls him back ~5px and gives a real gap at both ends of this
       query's range (170px card at 390, 231px at 519), with "About" and "OPEN"
       still clear of his coat.
       ⚠️ THE PICTURE BOX IS NOT THE FIGURE. Measuring the background box says
       nothing about where he actually ends. */
    background-position: 92% 100%;
  }
}
.wf:nth-child(1):hover .wf__pic,
.wf:nth-child(1):focus-visible .wf__pic { opacity: 1; transform: scale(1.03); }
.wf:nth-child(1)::before {
  /* He is a cut-out, so a top-to-bottom wash would grey HIM out. This one runs
     left-to-right instead: dark under the writing, clear over him. */
  background: linear-gradient(
    100deg,
    rgba(21, 12, 27, 0.94) 0%,
    rgba(21, 12, 27, 0.86) 42%,
    rgba(21, 12, 27, 0.36) 78%,
    rgba(21, 12, 27, 0.12) 100%
  );
}

/* THE SHABBOS CHOIR LINE, un-jammed. It was hard against the RIGHT edge of the
   card inside a narrow box, so that one card looked broken next to six
   left-aligned ones. Hebrew reads right-to-left, so right alignment is
   CORRECT here — the fault was the narrow box leaving a wide empty gutter.
   Full card width makes it read as deliberate rather than as a bug.
   ⚠️ This is the ONE Yiddish block on the site that stays right-aligned, and
   it is deliberate: it is a line inside its own box, not a page block pushed
   to the right-hand side of the screen. */
.wf__sub.yi { direction: rtl; text-align: right; max-width: none; }

/* ---- Utilities ---------------------------------------------------------- */

.center { text-align: center; }
.center .label { justify-content: center; }
.center p { margin-inline: auto; }
.stack-4 > * + * { margin-top: var(--sp-4); }
.stack-5 > * + * { margin-top: var(--sp-5); }
.stack-6 > * + * { margin-top: var(--sp-6); }
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}
