/* ============================================================================
   Chassidish Choir — home.css
   Home page only. Everything here works WITHOUT JavaScript: the three "pinned"
   sections are ordinary full-height sections until motion.js adds `.has-pins`
   to <html>, at which point they grow tall and their stage sticks.
   ========================================================================== */

/* ============================================================== 1. HERO ==== */

.hero {
  position: relative;
  height: 100svh;
  min-height: 640px;
}
/* ⭐ SCROLL LENGTH. This number is the whole "how many scrolls to get past the
   hero" feeling — it is not a layout value, it is a FEEL value. He counted five
   wheel-scrolls at 320vh and asked for two. The timeline in motion.js is
   scrubbed, so shortening this plays the same animation over less distance;
   nothing is cut.

   Calibrated against HIS OWN counts rather than guessed: 5 pushes to clear 320vh
   and 3 to clear 240vh both work out to ~430px of travel per push (416 and 441 —
   two independent numbers that agree, so the unit is real). Clearing a sticky
   section costs `height - 1 viewport`, so 2 pushes = 100vh + 2×430px ≈ 190vh. */
html.has-pins .hero { height: 190vh; min-height: 0; }

/* Flex column, not a stacked grid: the circle and the type block must never
   overlap on a short laptop screen, and flex makes that impossible by
   construction. GSAP only writes transforms here, so it cannot disturb it. */
.hero__stage {
  /* ONE number drives the whole lockup. The circle used to be sized from the
     viewport HEIGHT (17vh) while the words were sized from the WIDTH (560px /
     78vw) — so the two never tracked each other, and on a short wide screen the
     circle shrank while the words stayed big. That is the "the words just
     doesn't fit" he was looking at. Both now derive from this. */
  --hero-mark-w: min(560px, 78vw);

  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 560px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Was clamp(1.1rem, 3.2vh, 2.6rem) — the circle's bottom edge nearly touched
     the top of the letters, so they read as two pieces that happen to be near
     each other instead of one mark. */
  gap: clamp(1.6rem, 5.6vh, 4rem);
  isolation: isolate;
}

/* -- the video layer -- */
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  transform: scale(1.06);
  will-change: transform;
}
/* The source clips are shot under bright hall lighting against white drapes.
   Left alone the frame is brighter than the type, and the gold reads as grey.
   Grade the footage down here, then veil it — one without the other is not
   enough. */
.hero__media video,
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 34%;
  filter: brightness(0.62) contrast(1.06) saturate(0.88);
}
/* Two veils: a plum wash that keeps the video on-palette, and a bottom
   gradient so the type never sits on a busy frame. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(112% 84% at 50% 38%, rgba(21, 12, 27, 0.22), rgba(21, 12, 27, 0.88) 80%),
    linear-gradient(180deg, rgba(21, 12, 27, 0.74) 0%, rgba(21, 12, 27, 0.42) 28%, rgba(21, 12, 27, 0.88) 74%, rgba(21, 12, 27, 0.99) 100%);
}
.hero__veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--plum-950);
  opacity: 0.26;
  pointer-events: none;
}

/* -- the circle -- */
.hero__seal {
  position: relative;
  z-index: 3;
  flex: 0 0 auto;
  /* Proportional to the words, with a height cap so a short laptop still fits
     the whole lockup on screen. 0.34 is the classical mark-to-wordmark ratio —
     big enough to lead, small enough not to compete. */
  width: min(calc(var(--hero-mark-w) * 0.34), 22vh);
  min-width: 96px;
  aspect-ratio: 1;
  filter: drop-shadow(0 24px 60px rgba(0, 0, 0, 0.6));
  will-change: transform;
  /* ITEM 2 — a REAL perspective, on the parent. Without it a rotateY is just a
     horizontal squash: the far edge does not shorten, so the coin never turns,
     it only narrows. This one number is the difference between "3D" and "a
     scale animation".

     ⭐ ROUND 5 — R5-7. He rejected the 3D coin, so nothing tips any more and
     this no longer has a job — kept only because motion.js rotates .hero__seal
     on scroll and a perspective keeps that rotation from looking flat. */
  perspective: 900px;
}

/* ⭐⭐ ROUND 5 — R5-7. THE HERO ENTRANCE. Signed off after five sample rounds.
   Built from site/_sample-hero.html, which stays on disk as the record of how
   it was chosen. deploy.mjs refuses to publish anything named _sample*.

   THE SEQUENCE, in his words across five messages:

     "the entire thing should Come in from left to right… Slowly"
     "the coin should also come from the left side… later from the words"
     "the coin should not come From on top"
     "come from the left corner on top and like bounce… until it reaches the
      endpoint"
     "the two circles… bounce the same way… while it's still bouncing, the coin
      starts to come in and bounce. and they meet together"

     0.00s  the words begin arriving from the left, one after another
     1.20s  the two gold rings arc in from the TOP-left and start bouncing
     1.65s  the coin follows on the same path — while the rings still bounce
     6.25s  everything at rest

   ⚠️ WHAT IS GONE, AND WHY. The thick 3D coin and the drop-from-above that were
   built earlier this round are DELETED, not disabled: "I don't like this three
   d logo" and "the coin should not come From on top". The coin is flat again —
   the seal artwork exactly as it was before round 5 — and nothing falls.

   ⚠️ A BOUNCE NEEDS TWO ELEMENTS. The horizontal travel decelerates smoothly
   the whole way; the vertical slam-stops and rebounds four times. One
   `transform` cannot carry two easings, and merging them into shared keyframes
   would mean hand-computing every X at every bounce — brittle, and wrong the
   moment a timing changes.

       .hero__rings  → across      .hero__ring    → down
       .hero__coin   → across      .hero__coin img → down

   ⚠️ .hero__seal NEVER ANIMATES. GSAP's scroll timeline owns it. If the
   container moved and its children moved, the transforms would compose and the
   coin would travel twice as far as the rings. */

.hero__rings {
  position: absolute;
  inset: 0;
}

/* Travel is a viewport FRACTION, not a pixel count, so the distance reads the
   same on a 390px phone and a 2258px monitor. Everything starts fully off the
   left edge — nothing appears out of thin air mid-screen.
   .hero__stage has overflow:hidden, so none of this can widen the page. */
@keyframes hero-slide-in {
  0% { transform: translateX(-62vw); opacity: 0; }
  18% { opacity: 1; }
  100% { transform: translateX(0); opacity: 1; }
}
@keyframes hero-coin-x {
  0% { transform: translateX(-64vw); opacity: 0; }
  9% { opacity: 1; }
  100% { transform: translateX(0); opacity: 1; }
}

/* Gravity down, decay up. The squash is what makes it read as an object with
   weight rather than a picture being moved: wide and flat on every contact,
   tall and narrow at the top of every bounce. Four contacts, each rebound
   roughly 40% of the one before. */
@keyframes hero-coin-y {
  0% {
    transform: translateY(calc(var(--hero-lift) * -1)) scale(0.9);
    animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.42);
  }
  24% {
    transform: translateY(0) scaleX(1.07) scaleY(0.9);
    animation-timing-function: cubic-bezier(0.14, 0.7, 0.3, 1);
  }
  41% {
    transform: translateY(calc(var(--hero-lift) * -0.35)) scaleX(0.965) scaleY(1.04);
    animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.42);
  }
  57% {
    transform: translateY(0) scaleX(1.045) scaleY(0.94);
    animation-timing-function: cubic-bezier(0.14, 0.7, 0.3, 1);
  }
  70% {
    transform: translateY(calc(var(--hero-lift) * -0.14)) scaleX(0.985) scaleY(1.018);
    animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.42);
  }
  81% {
    transform: translateY(0) scaleX(1.022) scaleY(0.972);
    animation-timing-function: cubic-bezier(0.14, 0.7, 0.3, 1);
  }
  89% {
    transform: translateY(calc(var(--hero-lift) * -0.047)) scale(1);
    animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.42);
  }
  95% { transform: translateY(0) scaleX(1.008) scaleY(0.99); }
  100% { transform: translateY(0) scale(1); }
}

/* ⭐ THE TIMINGS HE CHOSE. One place, so they can be retuned without hunting.

   --hero-in is 4.6s and the whole entrance is 6.25s, which is deliberate and
   his: he asked for slower four separate times.

   ⚠️ THE EASING IS NOT DECORATION. The first three samples used a hard ease-out
   and he kept reporting, correctly, that "slower" did not feel slower — at 39%
   of the way through, the element was already 89% of the way home, so every
   extra second was an invisible creep. --hero-ease spreads the movement across
   the whole duration, which is what made the number he picks mean something. */
.hero__stage {
  --hero-in: 4600ms;
  --hero-rings-at: 1196ms;
  /* ⭐ 450ms -> 150ms. "once it hits the page, it is gonna be the coin with the
     two lines" — at 450ms they arrived as two separate events; at 150ms they
     land as one object with the rings a hair ahead. */
  --hero-coin-gap: 150ms;
  /* ⭐⭐ ONE SHARED LIFT, and the single most important number here.
     It used to be a percentage lift, and a percentage in translateY resolves
     against THE ELEMENT'S OWN HEIGHT.
     Inner ring 225px, outer ring 255px, coin 190px — so
     the three started 674 / 765 / 571px up. 194px of spread at 1440, 103px at
     390: three different arcs that merely shared a destination.
     Expressed in vw against the 64vw horizontal travel, so the ENTRY ANGLE is a
     constant ~18-20 degrees at every screen size. It was 32-40 degrees on a
     desktop and 50-58 on a phone — steeper than 45, i.e. mostly falling. That
     is precisely what he saw and called "bouncing only from on top". */
  --hero-lift: clamp(90px, 18vw, 320px);
  --hero-ease: cubic-bezier(0.36, 0.06, 0.22, 1);
}

.hero__type > *,
.hero__rings,
.hero__coin {
  will-change: transform, opacity;
}

.hero__type > * {
  animation: hero-slide-in var(--hero-in) var(--hero-ease) both;
}
.hero__type > *:nth-child(1) { animation-delay: 0ms; }
.hero__type > *:nth-child(2) { animation-delay: calc(var(--hero-in) * 0.12); }
.hero__type > *:nth-child(3) { animation-delay: calc(var(--hero-in) * 0.24); }
.hero__type > *:nth-child(4) { animation-delay: calc(var(--hero-in) * 0.36); }

.hero__rings {
  animation: hero-coin-x var(--hero-in) cubic-bezier(0.24, 0.42, 0.32, 1) both;
  animation-delay: var(--hero-rings-at);
}
.hero__ring {
  animation: hero-coin-y var(--hero-in) linear both;
  animation-delay: var(--hero-rings-at);
}
/* ⭐⭐ THESE THREE LINES ARE THE WHOLE FIX FOR "the coin is bouncing only from
   on top". .hero__coin is a <span>, and when this block was rewritten it lost
   its positioning and fell back to `display: inline` — and **transform does not
   apply to a non-replaced inline box.** The horizontal animation was parsed,
   valid, and silently discarded, so the coin sat at centre and only fell.

   ⚠️ AND IT CANNOT BE CAUGHT BY MEASURING. `getComputedStyle` still REPORTS
   `matrix(1, 0, 0, 1, -921.6, 0)` on that inline box — the browser reports the
   value and then ignores it. Every check I ran read -922px of travel off an
   element that was standing still. Only the screenshot told the truth. */
.hero__coin {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  animation: hero-coin-x var(--hero-in) cubic-bezier(0.24, 0.42, 0.32, 1) both;
  /* ⚠️ The `animation` SHORTHAND resets animation-delay to 0. Both the delay
     lines here and on the <img> below have to be stated explicitly or the coin
     sets off the instant the page loads — and the symptom reads as "the gap
     stopped working", not as a shorthand problem. */
  animation-delay: calc(var(--hero-rings-at) + var(--hero-coin-gap));
}
.hero__coin > img {
  animation: hero-coin-y var(--hero-in) linear both;
  animation-delay: calc(var(--hero-rings-at) + var(--hero-coin-gap));
}

/* A thin gold ring orbiting the seal — the scrollwork motif, reduced. */
.hero__ring {
  position: absolute;
  inset: -9%;
  border-radius: 50%;
  border: 1px solid rgba(227, 200, 119, 0.34);
  pointer-events: none;
}
.hero__ring--2 { inset: -17%; border-color: rgba(227, 200, 119, 0.16); }

/* -- the type block -- */
.hero__type {
  position: relative;
  z-index: 3;
  flex: 0 0 auto;
  width: 100%;
  max-width: 900px;
  padding-inline: var(--page-pad);
  text-align: center;
  will-change: transform, opacity;
}
.hero__h1 { margin: 0; line-height: 0; }
.hero__wordmark {
  width: var(--hero-mark-w);
  margin-inline: auto;
  filter: drop-shadow(0 10px 34px rgba(0, 0, 0, 0.55));
}
.hero__name {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 1.2rem + 6.4vw, 5.6rem);
  line-height: 0.98;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0;
}
.hero__tagline {
  margin-top: var(--sp-4);
  font-size: clamp(0.68rem, 0.56rem + 0.46vw, 0.92rem);
  letter-spacing: 0.42em;
  text-indent: 0.42em; /* balances the trailing letter-space so it reads centered */
}
/* In the client's own artwork the Yiddish line runs 52.5% of the word's width,
   tucked under its right half. Driving it off --hero-mark-w — the one variable
   the whole lockup is sized from — keeps that proportion at every screen size
   instead of letting the two drift apart the way the circle and the words did. */
/* In the client's artwork the Yiddish line is NOT centred under the word — its
   right edge is flush with the right edge of the ח (x=2082 against x=2086, on a
   word 1987px wide) and it hangs under the right half only. Centring it was
   wrong and he caught it: "it should match with the ח."
   The word is centred at width --hero-mark-w, so its right edge sits
   (50% + mark/2) from the left; putting that much minus the mark on the RIGHT
   margin lands this element's right edge in the same place, at any width. */
.hero__tagline-yi {
  width: calc(var(--hero-mark-w) * 0.525);
  margin-top: var(--sp-4);
  margin-left: auto;
  margin-right: calc(50% - var(--hero-mark-w) / 2);
}
/* On a phone the word itself is only ~304px, so the designed 52.5% lands the
   Yiddish at 160px — the letters drop to ~12px and start losing against the
   video behind them. Text has a floor that artwork proportions don't. */
@media (max-width: 640px) {
  .hero__tagline-yi { width: calc(var(--hero-mark-w) * 0.68); }
}
.hero__line {
  margin: var(--sp-5) auto 0;
  max-width: 34ch;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 0.95rem + 1.05vw, 1.85rem);
  font-style: italic;
  font-weight: 300;
  line-height: 1.35;
  color: rgba(245, 230, 192, 0.86);
}

/* ---- ITEM 6: the Yiddish line, set ON A STAVE ---------------------------- *
   "He wants it should be like a musical font. Like, come up with a creative
   way that it should be a musical font."

   The creative way is not a novelty typeface — there is no Hebrew display face
   whose letterforms are musical that would not look wrong next to his own logo.
   The music is in the SETTING: the five flowing hairlines that run behind the
   rest of the site pass under this one line, and it rides them. A lyric under a
   melody. It also means items 3, 4 and 6 are one idea rather than three
   decorations that ignore each other.

   The wrapper is what carries the stave (data-orn="soft" in the markup); it is
   also now the element motion.js's intro staggers, in place of the old <p>. */
.hero__line-wrap {
  margin-top: var(--sp-5);
  padding-block: clamp(0.5rem, 1.6vh, 1.1rem);
}
/* The stave is behind the words, so the words have to be lifted off it — see
   the painting-order note in ornament.css. */
.hero__line-wrap > .hero__line { position: relative; z-index: 1; }
/* `soft` is 0.09 for a section background. Over a graded video, at the size of
   one line of type, that is invisible. This is the one place the stave is meant
   to be READ rather than felt. */
.hero__line-wrap .orn__stave { opacity: 0.34; }
.hero__line-wrap .orn--soft .orn__stave { top: 4%; height: 92%; }

.yi-hero {
  margin: 0 auto;
  /* ⚠️ The whole idea is that the line SITS ON the stave. At 22ch it wrapped to
     two lines even on a 1440 desktop, and the stave then passed through the gap
     BETWEEN them instead of under the words — which is a different, weaker
     effect that looks accidental. 30ch keeps his 37-character line whole at
     every width down to about 700px; below that it wraps, and on a phone two
     lines with the stave between them is the right answer anyway because there
     is no room for one. */
  max-width: 30ch;
  font-family: var(--font-yi);
  font-style: normal; /* the Latin line was italic; Hebrew has no true italic */
  font-weight: 300;
  font-size: clamp(1.2rem, 0.98rem + 1.15vw, 2.05rem);
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--gold-100);
  /* It sits ON the stave, so it needs to sit on something — otherwise the
     hairlines run straight through the letters and both become mud. */
  text-shadow: 0 2px 14px rgba(21, 12, 27, 0.95), 0 0 26px rgba(21, 12, 27, 0.8);
}
.hero__actions {
  margin-top: var(--sp-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: center;
}

/* -- scroll cue -- */
.hero__cue {
  position: absolute;
  z-index: 4;
  left: 50%;
  bottom: clamp(1.25rem, 3vh, 2.5rem);
  translate: -50% 0;
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  font-size: 0.64rem;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--fg-faint);
  pointer-events: none;
}
.hero__cue i {
  display: block;
  width: 1px;
  height: 46px;
  background: linear-gradient(180deg, var(--gold-300), transparent);
  transform-origin: top;
  animation: cue 2.4s var(--ease-in-out) infinite;
}
@keyframes cue {
  0% { transform: scaleY(0); opacity: 0; }
  35% { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1) translateY(46px); opacity: 0; }
}

/* ---- ROUND 6 — R6-18: the Shabbos Choir mark ------------------------------ *
   "put something to notify regarding the Shabbos Choir… small, something in the
    left side corner… And when they press on it, it's gonna take them to that
    page."

   THE BOTTOM-LEFT IS THE ONLY QUIET CORNER ON THIS SCREEN. Top-right is the
   nav's gold BOOK US pill, bottom-centre is the scroll cue, and the hero's own
   lockup is centred. Nothing else lives down here.

   ⚠️ IT IS SIZED AS A LABEL, NOT A BUTTON, ON PURPOSE. The one job of this
   screen is BOOK US. A third pill at button weight would turn one clear ask
   into three competing ones. So: a hairline gold outline, the site's smallest
   two type sizes, and no fill until you touch it.

   ---- ROUND 8 — R8-1: THE LINE-ART TILE ------------------------------------
   "Can we get it nice with that style of picture? Like, something nicer, not
    just this word we also do."

   It is now a small CARD: the drawing on the left, the same two words stacked
   on the right. The drawing itself lives in index.html as an inline SVG — see
   the note there for why it is not a file.

   ⚠️ IT STILL IS NOT A BUTTON. Nothing about the weight changed: the same
   hairline border, the same two type sizes, the same empty background until you
   touch it. What grew is the AREA, not the loudness, and the tile is a picture
   rather than a filled shape, so it reads as a stamp in the corner and BOOK US
   is still the only solid gold thing on the screen.

   ⚠️ THE PILL RADIUS HAD TO GO. --radius-pill on a box this tall bows the sides
   into a lozenge and the square tile inside it fights the curve. 16px is the
   card radius the tile is drawn to (its own plate is rx 8 at 72 units ≈ the same
   arc once it is scaled down). */
.hero__shabbos {
  position: absolute;
  z-index: 4;
  left: var(--page-pad);
  /* level with the scroll cue, so the bottom edge of the screen reads as one
     line of furniture rather than two things at two heights */
  bottom: clamp(1.25rem, 3vh, 2.5rem);
  display: inline-flex;
  align-items: center;
  gap: clamp(0.7rem, 0.55rem + 0.5vw, 0.95rem);
  /* tighter on the LEFT: the tile carries its own dark plate, so the panel's
     padding on that side would read as a double frame */
  padding: 0.6rem 1.15rem 0.6rem 0.6rem;
  border: 1px solid rgba(214, 178, 96, 0.34);
  border-radius: 16px;
  /* The hero video runs under this. Without a ground of its own the outline
     lands on whatever frame happens to be playing and disappears for a second
     at a time. */
  background: rgba(21, 12, 27, 0.45);
  backdrop-filter: blur(5px);
  text-transform: uppercase;
  transition: border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
/* ⚠️ display:block IS LOAD-BEARING. An <svg> is inline by default, and an inline
   box is the one place where a width and a height are quietly discarded — this
   file's oldest recurring bug. It has to be a block before it is a flex item. */
.hero__shabbos-art {
  display: block;
  flex: 0 0 auto;
  width: 62px;
  height: 65px;
  color: var(--gold-300);
  transition: color var(--dur) var(--ease-out);
}
/* The two words stack now. Side by side beside a 62px tile the card measured
   309px across — a strip, not a corner mark. */
.hero__shabbos-t {
  display: grid;
  gap: 0.2em;
}
.hero__shabbos-k {
  font-size: var(--t-label);
  letter-spacing: var(--track-nav);
  color: var(--fg-dim);
}
.hero__shabbos-n {
  font-size: var(--t-mini);
  font-weight: 500;
  letter-spacing: var(--track-nav);
  color: var(--gold-200);
}
.hero__shabbos:hover,
.hero__shabbos:focus-visible {
  border-color: var(--gold-300);
  background: rgba(21, 12, 27, 0.72);
}
.hero__shabbos:hover .hero__shabbos-art,
.hero__shabbos:focus-visible .hero__shabbos-art {
  color: var(--gold-100);
}

/* ⚠️⚠️ IT MUST ARRIVE AFTER THE ENTRANCE, NOT DURING IT. The words, the two
   rings and the coin all fly across this screen for the first six and a quarter
   seconds, and that entrance is approved and signed off — anything appearing
   inside it is a distraction from something he chose over five rounds.

   The number is DERIVED, not typed: the last thing to arrive is the fourth
   child of .hero__type, which starts at 0.36 x --hero-in and runs for
   --hero-in, so the entrance is over at 1.36 x --hero-in. A quarter second
   after that, this fades up. Retune --hero-in and this follows it.

   ⚠️⚠️ `backwards`, NOT `both`, AND THAT IS A BUG FIX. `backwards` holds the
   first keyframe during the wait — which is the invisibility we want — and then
   lets go completely once the animation has finished. `both` would keep
   ASSERTING opacity 1 forever, and a filled CSS animation outranks an inline
   style, so GSAP's scroll fade in motion.js would have been written, been valid,
   and done nothing: the mark would have stayed at full strength while the whole
   hero dimmed around it. That is this file's own oldest lesson (see the note on
   .hero__coin) and it applies to opacity exactly as it applies to transform. */
@keyframes hero-mark-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
.hero__shabbos {
  animation: hero-mark-in 900ms var(--ease-out) backwards;
  animation-delay: calc(var(--hero-in) * 1.36 + 250ms);
}

/* ⚠️⚠️ ON A PHONE THIS SITS UNDER THE BUTTONS, CENTRED, WITH NO PLATE — AND THAT
   REVERSES WHAT THIS COMMENT USED TO SAY. Read the reversal before putting the
   old corner back.

   WHAT WAS HERE AND WHY IT IS GONE. The phone rule parked the mark TOP-LEFT,
   under the header. It lands ON TOP OF THE GOLD COIN. Measured on the live page,
   this element's box against `.hero__seal`'s box:

       386x666   his own screenshot          78 x 62 overlap
       320x568   iPhone SE 1                 88 x 57 overlap
       375x553   iPhone SE 2/3 in Safari     81 x 50 overlap
       390x664   iPhone 13/14 in Safari      76 x 62 overlap
       393x659   Pixel in Chrome             75 x 62 overlap
       430x745   iPhone Pro Max in Safari    63 x 46 overlap
       768x954 / 820x1110 / 1024x698         CLEAR — the tablet is right

   ⚠️⚠️ THE WIDTH IS NOT THE VARIABLE, THE HEIGHT IS — WHICH IS HOW THIS SURVIVED
   A SWEEP. At the SPEC iPhone 13 viewport, 390x844, the mark CLEARS the coin, so
   `devices['iPhone 13 Pro']` is the one preset in which this bug does not
   reproduce at all. His real Safari window is 390x664: the address bar and the
   toolbar eat ~180px, `.hero__stage` centres its column in `100svh`, and a
   shorter window lifts the coin straight up under the mark. Every phone check on
   this element has to use REAL window heights, never the device spec.

   THE PLACEMENT HE PICKED, from three that were rendered and shown to him:
   centred UNDER the two hero buttons, with no outline and no plate behind it.

   ⚠️ THE OLD OBJECTION WAS REAL, AND IT IS ANSWERED RATHER THAN IGNORED. This
   comment used to argue that centring it "under two full-width stacked buttons
   … becomes a third button-shaped thing in the same stack, which is the one
   thing this mark must not be". That is true OF A BORDERED PLATE, and it is
   exactly why the border, the background and the backdrop-filter all come off
   below 700px: under 640px `.hero__actions` is a COLUMN of `width: 100%`
   buttons, so anything down there carrying an outline reads as the third one.
   Stripped back to the drawing and two words standing on the video, it is a
   caption, not a button — the shape is what made it a button, not the position.
   Tablet and desktop keep the plate; only the phone gives it up.

   ⚠️ AND THE OLD "~150px" IN THIS COMMENT WAS SIMPLY WRONG. It claimed the
   stacked card measured about 150px across and therefore "reads as a corner mark
   again". It measures 201px at 390 — and 200.1 at 320, 202.0 at 430, because the
   two WORDS stack but the drawing does not, so the width is tile + longest word +
   padding and it barely moves with the screen. It was never 150px, and 201px
   hard against the left edge under the header is not a corner mark, it is a
   banner lying across the coin.

   ⚠️ THE SCROLL CUE GOES WITH IT, and that was part of the option he approved.
   The cue is centre-bottom and this is now centre-bottom; left on, they sit on
   each other. A phone loses nothing — the cue is `aria-hidden` decoration and a
   thumb does not need telling that a page scrolls.

   ⚠️ 699px, NOT 640px, AND THE 59px STILL MATTERS. Swept every width from 360 to
   2258 measuring this element's box against the cue's. At 641 — the first width
   where the desktop bottom-left version came back — the two cleared each other by
   SEVEN PIXELS. That is a coincidence, not a clearance. 700 is also the line this
   whole page already turns on: the pinned scenes in motion.js, the two-column
   About, and the booking section's phone block. One line, not four.

   ⚠️ IT STAYS A ROW — the drawing BESIDE the two stacked words. A column here
   puts the drawing on top of the words and builds a tower out of a caption.

   ⚠️⚠️ IT IS CENTRED WITH `left: 0; right: 0; margin-inline: auto`, AND NOT WITH
   THE OBVIOUS `left: 50%` + `translate: -50%`. The obvious one was written first
   and it silently BREAKS THE CARD ON THE TWO SMALLEST PHONES. An absolutely
   positioned shrink-to-fit box gets, as its available width, the containing
   block MINUS its own `left` offset — so `left: 50%` hands it half the screen
   and no more. Measured: 160.0 x 74.2 at 320 and 187.5 x 74.5 at 375, against
   188.9 x 53.3 at 390. SHABBOS CHOIR had wrapped onto a third line, the card
   grew 21px taller, and that extra height ran 5.2px into the BOOK US button at
   375x553. Nothing about it looked wrong in the stylesheet, and the 390 preset —
   the one everybody reaches for — cleared it.

   Both offsets at 0 give the box the whole width to shrink-wrap in, and
   `margin-inline: auto` centres it inside them; `fit-content` is what keeps it a
   shrink-wrapped card rather than a full-width strip. The `max-width` is a
   backstop for a very narrow screen, not a layout.

   ⚠️ AND THAT ALSO KEEPS `transform` FREE, which matters here for a second
   reason: this element runs `hero-mark-in`, which animates `transform`, and
   motion.js reads these elements' transforms on scroll. A centring that lives in
   `transform` would be in a three-way argument with both of them. */
@media (max-width: 699px) {
  .hero__shabbos {
    left: 0;
    right: 0;
    top: auto;
    margin-inline: auto;
    width: fit-content;
    max-width: calc(100% - 2 * var(--page-pad));
    /* Level with where the cue used to sit. `.hero__stage` has a 560px
       min-height, so on a 553px window the stage runs ~7px past the fold and
       this offset is measured from the STAGE, not the screen — 1rem leaves 9px
       of real margin there and 16-18px everywhere else. */
    bottom: clamp(1rem, 2.4vh, 1.5rem);
    gap: 0.55rem;
    /* The padding IS the tap target now that the plate is gone, and it is the
       one thing here that must not be trimmed further. Measured, not derived:
       53.0px tall at 320 and 53.4 at 430, comfortably over the 44px floor.
       ⚠️ The height is set by the TWO STACKED WORDS (43.4px), not by the 42px
       drawing — shrinking the drawing to buy height back would buy 0. */
    padding: 0.3rem 0.5rem;
    border: 0;
    border-radius: 0;
    background: none;
    backdrop-filter: none;
  }
  /* The desktop rule repaints the plate on hover and focus. There is no plate to
     repaint here — and focus is not lost, because base.css puts a gold
     `:focus-visible` outline on everything. */
  .hero__shabbos:hover,
  .hero__shabbos:focus-visible {
    border-color: transparent;
    background: none;
  }
  /* With the plate gone the words stand directly on the graded video, which is
     the exact problem the plate was written for. They get what `.yi-hero` gets
     over the same footage instead: a shadow darkens what is already behind the
     letters rather than adding a shape of its own, so it cannot read as a
     button. The DRAWING needs nothing — it carries its own #100916 tile. */
  .hero__shabbos-k,
  .hero__shabbos-n {
    text-shadow: 0 1px 10px rgba(21, 12, 27, 0.95), 0 0 20px rgba(21, 12, 27, 0.85);
  }
  .hero__shabbos-art {
    width: 40px;
    height: 42px;
  }
  /* Centre-bottom is taken. See the note above. */
  .hero__cue { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__shabbos { animation: none; }
}

/* Short laptops and phones now shrink the lockup by moving ONE number, so the
   circle and the words stay in proportion instead of drifting apart. Overriding
   the two widths separately is what broke the relationship in the first place. */
@media (max-height: 720px) {
  .hero__stage {
    --hero-mark-w: min(430px, 72vw);
    gap: clamp(1.1rem, 4vh, 2.4rem);
  }
}
/* ⚠️⚠️ A SHORT PHONE RAN THE COIN INTO THE HEADER BAR, AND IT IS A HEIGHT
   PROBLEM WITH A HEIGHT FIX. Measured with the Shabbos mark HIDDEN, so the mark
   could not be blamed for it — `.hero__seal`'s box against the header's box:

       320x568   the coin overlaps the header by 17px
       375x553   the coin overlaps the header by 34px
       390x664   clear

   NOTHING IS OVERLAPPING ANYTHING ON PURPOSE. `.hero__stage` centres its column
   in `100svh` (min 560px), so the coin's top is simply (stage - column) / 2. The
   column is 450-490px of coin, wordmark, Yiddish and two stacked buttons however
   short the window is, so on a 553px screen there are 42px above it and the 76px
   header eats all of them. Shorten the window and the top of the column is what
   pokes out.

   TWO LEVERS, IN THIS ORDER, BECAUSE THEY COST DIFFERENT THINGS:

   1. THE VERTICAL RHYTHM FIRST — it is the cheap one. On a 553px screen this
      column carries 128px of pure GAP: 22 between the coin and the words, then
      16 / 24 / 32 down the type block, 16 between the two buttons and 18 inside
      the stave wrapper. Every one of those was set on a desktop. Halving them
      below 640px of window height gives back ~59px and there is nothing on
      screen to point at afterwards.

   2. --hero-mark-w SECOND, AND IT IS STILL ONE NUMBER. The `45vh` term goes
      INSIDE the same `min()` as the width terms, so the coin and the wordmark
      stay locked to each other. Overriding the two separately is what broke that
      relationship the first time — see the note above this block.

   ⚠️ 45vh IS PICKED SO IT BITES ONLY WHERE IT HAS TO, and that was checked one
   viewport at a time. At 553 it resolves to 249px and takes over from 76vw's
   285px. At 568, 664, 666 and 745 the WIDTH term is still the smaller of the two
   and the lockup is bit-for-bit what it was; at 659 it takes 2px off a 299px
   mark, which is 0.7px of coin. A short window shrinks the mark; a normal one
   never sees this term at all.

   ⚠️ AND THE 88px FLOOR HAD TO COME DOWN WITH IT — 88 -> 78. `min-width` is a
   floor on the COIN ALONE, so the moment it binds, the coin stops tracking the
   wordmark and the one proportion this lockup is built on is gone. It was
   already binding at 320, where the ratio asks for 82.7px and the floor served
   88. 78px sits under every value any phone in the sweep produces, so the ratio
   now holds everywhere and the floor is back to doing its only real job: stopping
   the coin disappearing on something like a 280px Fold. */
@media (max-width: 640px) {
  .hero__stage { --hero-mark-w: min(360px, 76vw, 45vh); }
  .hero__seal { min-width: 78px; }
  .hero__actions { flex-direction: column; align-items: stretch; }
  .hero__actions .btn { width: 100%; }
}

/* THE RHYTHM HALF OF THE FIX — lever 1 above. Gated on the WINDOW HEIGHT,
   because that is the thing that is actually short; a 375px-wide window that is
   844px tall has no problem to solve and must not be touched.

   ⚠️ 640px OF HEIGHT IS A MEASURED THRESHOLD, NOT A ROUND NUMBER. It has to
   catch 553 and 568 and it must NOT catch 659, 664, 666 or 745 — at those
   heights the column already clears the header with 83-106px to spare, and
   tightening the rhythm there would just shrink a lockup that has a whole screen
   to sit in. Between the two, at 600-640, the untightened column clears by 25px
   at 320 wide and by 6px at 375, so the step lands where it is still true on
   both sides of the line.

   ⚠️ IT IS ALSO WIDER THAN 640 ON PURPOSE — up to 699. Between 641 and 699 the
   buttons come back onto one row, so the column is shorter, but `--hero-mark-w`
   is still the 430px value from the max-height:720 block above and the coin is
   at its 22vh ceiling: at 641x640 that column measured 506px against a 640px
   window, 67px of top clearance where 76 is needed. */
@media (max-width: 699px) and (max-height: 640px) {
  .hero__stage { gap: clamp(0.75rem, 2.2vh, 1.6rem); }
  .hero__tagline-yi { margin-top: clamp(0.5rem, 1.5vh, 1rem); }
  .hero__line-wrap {
    margin-top: clamp(0.75rem, 2.2vh, 1.5rem);
    padding-block: clamp(0.25rem, 0.8vh, 0.7rem);
  }
  .hero__actions {
    margin-top: clamp(1rem, 3vh, 2rem);
    gap: clamp(0.6rem, 1.8vh, 1rem);
  }
}

/* ⚠️ 641-699px WIDE IS ITS OWN BAND, AND THE SWEEP IS THE ONLY REASON IT IS HERE.
   Sweeping 19 widths x 15 heights turned up a strip nobody would have thought to
   open by hand: a window 641-699 wide and 553-680 tall. It is under the 700px
   phone line, so the mark is centre-bottom there — but it is OVER the 640px line,
   so it gets neither `min(360px, 76vw, 45vh)` nor the stacked buttons, and
   `--hero-mark-w` sits at a desktop-sized 430px with the coin pinned to its 22vh
   ceiling. Measured across that strip: the coin overlapped the header by 2.9 to
   10.9px, and the column pushed the buttons down far enough to run 3.3-5.6px into
   the mark. The rhythm block above reaches the short end of it and is not enough
   on its own; the coin is the thing that has to give.

   So the SAME 45vh term, and nothing else — the 430px and the 72vw stay exactly
   as the max-height:720 block set them, so this only ever takes the smaller of
   the two. Above 680px of height the band clears on its own and this does not
   apply; it is deliberately not a `max-width: 699px` rule, because at 641-699 the
   width terms differ from the phone ones and inheriting them here would shrink
   the coin at 320 by another 5%. */
@media (min-width: 641px) and (max-width: 699px) and (max-height: 680px) {
  .hero__stage { --hero-mark-w: min(430px, 72vw, 45vh); }
}

/* ============================================================= 2. ABOUT ==== */

.about {
  position: relative;
  background: var(--paper);
  color: var(--ink);
  /* The medallion is 108vw and centred, so it hangs 4vw off each edge. Its
     containing block is THIS element (`.about__stage` is static when there are
     no pins), so the stage's own `overflow: hidden` never clipped it and on a
     phone it produced real horizontal page scroll.
     `clip`, not `hidden` — hidden would make this a scroll container and move
     the sticky containing block for `.about__stage`, breaking the pin. */
  overflow-x: clip;
}
.about__stage {
  position: sticky;
  top: 0;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding-block: var(--section-y);
  overflow: hidden;
}
html:not(.has-pins) .about__stage { position: static; min-height: 0; }

/* ⭐⭐⭐ ROUND 6 — R6-2. "this about page on the button. There is too much empty
   space. It's too empty. We gotta make it shorter."

   ⚠️⚠️ THE PIN WAS DEAD. NOT SHORT — DEAD, AT EVERY WIDTH. That is the real
   cause of his empty space, and it is not what anyone thought it was.

   `ornament.css` carries this rule, which exists to lift the stage above the
   stave layer that gets injected behind it:

       [data-orn] > .about__stage { position: relative; z-index: 1; }

   `.about` carries data-orn="soft", so that selector matches — and it is an
   attribute PLUS a class PLUS a child combinator (0,2,0), while `.about__stage`
   above is a single class (0,1,0). Specificity, not order. So the stage has been
   computing `position: relative` and the sticky pin has never once run on this
   page. Measured, on the live page: getComputedStyle said `relative`, and the
   stage scrolled straight up and off the top like any ordinary block.

   WHAT THAT LOOKS LIKE ON SCREEN, and why he could see it: with no pin, every
   pixel of scroll runway is a pixel of EMPTY CREAM. The content rides up off the
   top and the leftover height plays out as a blank page. Measured at the end of
   the section, before this fix: 585px of empty cream at 1440x900, 1002px at
   820x1180, 884px at 1920x1080. The phone had none, because the phone has no
   pins at all — which is exactly the pattern he reported.

   ⭐ IT ALSO EXPLAINS AN OLDER COMPLAINT. He said of this section, twice, "it
   doesn't move at all". That was diagnosed as the 901px tablet gate in motion.js
   and the gate was lowered to 700 — a real improvement, but not this bug, so it
   could not have fixed what he was looking at.

   THE FIX IS TWO THINGS, and both are needed:

   1. WIN THE SPECIFICITY BACK. (0,3,1) beats ornament.css's (0,2,0). `sticky` is
      a positioned value just like `relative`, so the z-index lift that rule was
      written for still works and the staves stay behind the content.

   2. THE STAGE HAS TO FIT THE SCREEN. A sticky box TALLER than the viewport pins
      its top and never reveals its bottom — with the old --section-y padding the
      stage was 1126px against a 900px screen, so restoring the pin on its own
      would have hidden his sign-off line below the fold forever. The padding is
      pointless on a pinned stage anyway (the content is centred in a full screen
      of its own), so it comes down to a size that always fits.

   AND THE RUNWAY COMES DOWN — he asked for shorter, and shorter is now free,
   because with the pin working the runway is time on screen instead of blank
   page. 168vh -> 140vh. His original 168 was "one and a half wheel pushes"
   (100vh + 1.5x430px); 140vh is about one. One number to change if he wants the
   longer hold back.

   ⚠️⚠️ THE GATE IS ON BOTH DIMENSIONS, AND THE WIDTH HALF WAS FOUND BY SWEEPING.
   Pinning a box that does not fit the screen is how you clip a man's own words
   with no error anywhere, so the pin is only allowed where the block provably
   fits. Two things make it not fit, and only one of them is obvious:

     TOO SHORT a window — 748px of content against a 720px window.
     TOO NARROW a window — which is the one a height check never catches. The
     copy is a COLUMN, so the narrower it gets the TALLER it gets: 748px at
     1440, but 862px at 760 and 882px at 700. A height-only gate passed 700x900
     and the block came out 54px taller than the screen.

   So: 820 wide and 900 tall, and outside that there is no pin and no runway at
   all — the section is then exactly as tall as its content, which is the one
   shape that can never have dead space in it. Verified across a grid of 17
   window sizes from 390x844 to 2258x1440: inside the gate the tightest case is
   900x900, where the block clears the top and bottom of the screen by 38px each,
   and nothing clips anywhere. */
html.has-pins .about { height: auto; }

@media (min-width: 820px) and (min-height: 900px) {
  html.has-pins .about { height: 140vh; }
  /* (0,3,1) — this is the line that beats ornament.css:44. */
  html.has-pins .about > .about__stage {
    position: sticky;
    z-index: 1;
    /* The full --section-y (up to 152px a side) is what made the stage 1126px
       against a 900px screen in the first place. On a pinned stage the padding
       is doing no work anyway — the content is centred in a whole screen of its
       own — so it comes down to a size that always leaves the block room. */
    padding-block: clamp(0.75rem, 3vh, 2.5rem);
  }
}

/* The medallion sits behind the copy, huge and faint — the shape motif doing
   the work instead of a decorative background image. */
.about__medallion {
  position: absolute;
  z-index: 0;
  top: 50%;
  left: 50%;
  width: min(760px, 108vw);
  aspect-ratio: 1;
  translate: -50% -50%;
  opacity: 0.07;
  pointer-events: none;
  will-change: transform;
}
/* Flatten it to a single warm tint. At full colour the scrollwork competes
   with the paragraph sitting on top of it and the copy becomes hard to read. */
.about__medallion img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: grayscale(1) contrast(0.75) brightness(0.55) sepia(0.45);
}

.about__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 820px;
  margin-inline: auto;
  padding-inline: var(--page-pad);
  text-align: center;
}
.about__inner .label { color: var(--gold-700); justify-content: center; }

/* -- the owner, cut out ---------------------------------------------------- *
   A black coat on the one cream section of the site. He is gesturing to the
   viewer's right, so he is placed LEFT and points into the copy; mirroring him
   would have him facing off the edge of the page.
   `contain` and a max-height, never `cover`: cropping a person's head or feet to
   fill a box is the one thing that would make this look worse than not using the
   photo at all. */
.about__portrait {
  margin: 0 auto var(--sp-6);
  display: flex;
  justify-content: center;
  align-items: flex-end;
  /* ⭐ ROUND 5 — R5-3. The grounding, re-made in the page's own colours.
     The cutout used to carry his real shadow from the studio floor — a shadow
     cast on bright GREEN cloth, which over cream read as a grey smudge beside
     his shoe. That is now keyed out (tools/cutout.mjs, KEY.shoeShadowRatio) and
     replaced here, where it is a warm ink tone instead of a green-grey one and
     can be tuned without re-cutting the picture.
     Wider than his stance and very soft: a tight shadow under a full-length
     figure reads as a sticker on a shelf. */
  position: relative;
}
/* ⭐⭐ ROUND 6 — R6-20. "Can we please work on the foot? Right now, it's a
   disaster." Third complaint about the shoes. The first two rounds re-cut the
   picture; this round the cut was MEASURED against the master and found to be
   right — vertical scans down the rear shoe put it within a few pixels of the
   true edge, and every row below it is green cloth by ratio, not shoe.

   What was actually wrong is that a black shoe needs something under it. In
   the studio photograph the eye reads these shoes easily, because a black sole
   against brilliant green cloth is all outline. Lift the same shoes onto cream,
   take the floor away, and the sole, welt, heel and shank — which the camera
   recorded inside FIFTEEN levels of pure black — collapse into one shapeless
   mass. Two fixes, and neither is the matte: the crushed blacks are opened up
   in tools/cutout.mjs (see LIFT), and he is given a real contact shadow here.

   ⚠️ ONE centred ellipse was the bug in this rule. His two shoes are not
   side by side: the front one is down and to the left, the rear one is further
   back, higher and to the right. A single pool centred under the pair sits
   under NEITHER of them, so both shoes floated and the rear one — the one he
   keeps pointing at — floated worst. There are three layers now: a firm, tight
   contact under each shoe where it actually meets the ground, and the wide soft
   pool underneath both so he still does not read as a sticker on a shelf. */
.about__portrait {
  /* so the percentages below line up with the PICTURE and not with the column
     it happens to be sitting in — object-fit leaves the box wider than the
     figure, and the shadows drifted off his feet as the column changed width */
  width: fit-content;
}
.about__portrait::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 96%;
  height: 34px;
  translate: -50% 0;
  pointer-events: none;
  background:
    /* the front shoe — it carries his weight, so this one is tightest and darkest */
    radial-gradient(30% 42% at 27% 82%, rgba(36, 21, 40, 0.42), rgba(36, 21, 40, 0) 70%),
    /* the rear shoe — smaller, softer and higher, because it is further away */
    radial-gradient(23% 30% at 52% 46%, rgba(36, 21, 40, 0.30), rgba(36, 21, 40, 0) 72%),
    /* and the wide pool that ties the two together */
    radial-gradient(48% 52% at 39% 72%, rgba(36, 21, 40, 0.20), rgba(36, 21, 40, 0) 76%);
}
.about__portrait img {
  width: auto;
  max-width: 100%;
  max-height: min(38svh, 320px);
  object-fit: contain;

  /* There used to be a `mask-image` here dissolving him away at shin height,
     because the shoes could not be keyed and the strip below was ragged. The
     client's read of it was exactly right: "the bottom of the person, it's like
     white, it's not good blending."
     The shoes key cleanly now (tools/cutout.mjs — the fix was positional, not a
     threshold), so there is nothing left to hide and he stands on his own feet.
     The only softening left is a 19px hairline inside the asset itself, which
     stops the soles ending on a razor line. */
}

/* ⭐ ROUND 6 — R6-3. The three `.about__copy .cta-row` rules that used to live
   here, twenty lines down and again inside the two-column block are GONE with
   the "See all videos" link they centred (see the note in index.html). A rule
   left behind for an element that no longer exists is how the element quietly
   comes back. The shared `.cta-row` at the bottom of this file is untouched. */

/* ⭐ ROUND 4 — the About column is now RTL, so its FURNITURE has to follow it.
   The eyebrow and the "See all videos" link are English and stayed where the
   English copy used to put them, which left the block reading from three
   different edges at once: eyebrow centred, heading and body hard right, link
   hard left at the far opposite end of a 600px column. On screen the link
   looked orphaned rather than related to anything above it.

   The words stay English — he asked for the —— ABOUT eyebrow to stay exactly as
   it is, and it does. Only the alignment moves, so the whole block reads down
   one edge the way the language it is written in does.

   ⚠️ `display: flex`, not just `justify-content`. `.label` is `inline-flex` in
   base.css, so it shrink-wraps its own contents and is placed by its PARENT's
   text-align — `justify-content` on it only rearranges the rule and the word
   inside a box that is already exactly their width, and moves nothing. The
   first attempt at this changed nothing on screen and looked like it worked in
   the stylesheet. Making it a block-level flex gives it the full column to
   justify within. */
/* max-width: none is the part that matters. .label is a <p> and inherits the
   62ch measure cap, so at 1440 it was a 484px box sitting at the LEFT of a
   607px column — justify-content dutifully centred the text inside that box and
   the box itself stayed put. Measured 62px off centre with the rule looking
   perfectly correct in the stylesheet. */
.about__copy .label { display: flex; justify-content: center; max-width: none; }

/* ⭐ ROUND 5 — R5-2b. "the on top pattern, it should be in one line, not in two
   lines."

   The heading was sized off the VIEWPORT — clamp(1.75rem, 1.15rem + 3vw, 3.4rem)
   — but it lives in a COLUMN, and the viewport does not predict that column's
   width. At 1440 it resolved to 3.4rem inside a box narrower than the string
   needs, so it broke in two.

   Sizing it in `cqi` (percent of THIS container) instead makes the relationship
   exact: the string is a fixed number of characters, so a size expressed as a
   fraction of the width it has to fit in can never wrap — at 320px or at 2258px.

   ⚠️ `white-space: nowrap` ALONE would have been the wrong fix. It does not make
   the text smaller, it just converts the wrap into a sideways overflow off the
   edge of the screen, which is worse and is what the sweep tests for. The cap
   and the nowrap only work as a pair. */
.about__copy { container-type: inline-size; }

/* ⚠️ Scoped to :not(.about__h). ".about h2" is a class PLUS a type selector, so
   it outranks the single class ".about__h" no matter which comes later — the
   R5-2b container size was written, was valid, and never applied. The heading
   still showed as one line only because "white-space: nowrap" was doing it, and
   that is precisely the failure I warned about: nowrap without a working cap is
   not "one line", it is a sideways overflow. Caught by measuring text width
   against column width, which is why that check tests BOTH. */
.about h2:not(.about__h) {
  font-size: var(--t-h2);
  color: var(--ink);
}

/* ---- ITEM 7: his Yiddish, in place of the English --------------------------
   The heading is the first line of his own copy — חסידיש — כשמו כן הוא! — set
   "at the same weight Who We Are has now, so the block keeps its shape."
   Slightly smaller than --t-h2 on purpose: Hebrew has no lower case, so at an
   identical size it carries visibly more ink than the Latin line it replaces
   and starts to shout. */
.about__h {
  color: var(--ink);
  text-align: center;
  /* R5-2b — see the note on .about__copy. 7.4cqi is measured, not guessed: it
     is the largest fraction of the column at which חסידיש — כשמו כן הוא! still
     sits on one line at every width, with the 3.4rem ceiling kept so it cannot
     grow absurd on a very wide column. */
  /* 9.2cqi is measured against the WORST case, not the average. The string
     renders at ~10.3x its font size at small sizes and ~8.7x at large ones, so
     the coefficient has to clear the 10.3. Verified as one line AND inside its
     column at 390 / 560 / 700 / 820 / 1024 / 1440 / 1920 / 2258. */
  font-size: min(3.4rem, 9.2cqi);
  white-space: nowrap;
}

/* ⚠️ FOUR TIMES LONGER THAN WHAT IT REPLACES. Two short English paragraphs
   became five Yiddish ones, so the measure comes down and the leading goes up —
   at --t-lead across 56ch this block was a slab. */
.about__body {
  margin: var(--sp-5) auto 0;
  max-width: 60ch;
  /* R5-2a — "let's make everything that should be centered." */
  text-align: center;
  font-size: clamp(0.98rem, 0.94rem + 0.28vw, 1.1rem);
  line-height: 1.9;
  color: rgba(36, 21, 40, 0.8);
}
.about__body > span {
  display: block;
  will-change: opacity, transform;
}
.about__body > span + span { margin-top: 0.95em; }

/* The one short line — חסידיש איז מער ווי בלויז א כאר. — standing between
   paragraphs of 150-250 characters. His screenshot underlines it; picked out by
   SHAPE in build-yiddish.mjs rather than by me deciding it matters, and asserted
   there to be exactly one. Emphasis is applied to the whole line, never inside
   it: wrapping a line in a tag cannot change one of its characters, and he told
   me twice not to touch the spelling. */
.about__body > span.yi-pull {
  margin-top: 1.15em;
  font-family: var(--font-yi);
  font-size: clamp(1.15rem, 1rem + 0.85vw, 1.6rem);
  font-weight: 500;
  line-height: 1.4;
  color: var(--gold-700);
}

/* HIS OVERRIDE — see the comment in index.html. The About paste ends
   א נאכט מיט חסידיש — איז חסידיש!, and on the page it is the booking line
   instead: "Let's do it the same like the booking." */
.about__signoff {
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(140, 101, 32, 0.3);
  font-size: clamp(1.1rem, 0.95rem + 0.8vw, 1.6rem);
  color: var(--gold-700);
  text-align: center; /* R5-2a */
}

/* -- two columns: the owner on the left, the copy on the right ------------- *
   Only once there is real width for them. Below this he sits above the copy at
   a modest height — a full-length figure beside a paragraph on a phone leaves
   both of them too narrow to read.

   ⚠️ This block MUST stay below `.about__body` and `.about__inner`. It overrides
   them at equal specificity, so it only wins by coming later — and `.about__body`
   sets the `margin` SHORTHAND, which silently resets the `margin-inline` set
   here if the order is the other way round. */
/* ⭐ Lowered 900 -> 700. At 900 an 820px tablet fell to the stacked layout and
   the client's note was "he is sitting on top" — the portrait stacked ABOVE the
   copy instead of standing beside it. 700 is also the gate motion.js now uses
   for the pinned scenes, so "two-column" and "animated" start together instead
   of at two different widths. */
@media (min-width: 700px) {
  .about__inner {
    max-width: 1180px;
    display: grid;
    grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
    align-items: center;
    gap: clamp(1.5rem, 4vw, 4.5rem);
    text-align: left;
  }
  /* ⚠️ These two used to read `flex-start`, and they sit AFTER the RTL block
     above at equal specificity — so they won, and the desktop layout kept the
     eyebrow and the link pinned to the left edge of a right-aligned Yiddish
     column. Whatever changes up there has to change here too; that is what
     "only wins by coming later" costs. */
  .about__inner .label { display: flex; justify-content: center; max-width: none; }
  .about__portrait { margin: 0; align-self: end; }
  .about__portrait img {
    /* Capped against the stage, not the image: .about__stage is 100svh with
       section padding, and anything taller is clipped by its `overflow: hidden`
       with no warning at all. */
    max-height: min(68svh, 620px);
  }
  /* R5-2a — auto, not 0. Pinning the margins to zero is what pressed the
     60ch column against the right edge of its grid cell. */
  .about__body { margin-inline: auto; }
}

/* A value the client has not given us yet. Visible on purpose, so nobody ships
   a made-up number. Delete the <span class="todo"> wrapper when the real value
   lands — nothing else changes. */
.todo {
  border-bottom: 1px dashed var(--blush-500);
  color: var(--blush-500);
  font-style: normal;
  white-space: nowrap;
}

/* ============================================= 3. WATCH + 4. GALLERY ======= */

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-bottom: var(--sp-7);
}
.section-head__text { max-width: 44ch; }
.section-head h2 { font-size: var(--t-h2); }
.section-head .lead { margin-top: var(--sp-3); }

.watch { background: var(--plum-950); }

/* The home page shows the four newest. `auto-fill` gives three across at 1440
   and strands the fourth alone on its own row. Two-up is deliberate: bigger
   frames, no orphan, and it reads as a feature rather than a contact sheet. */
@media (min-width: 760px) {
  .watch .grid-media { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.gallery-strip { background: var(--plum-900); }
/* A deliberately uneven, editorial strip — not the flat even grid that makes
   the reference site read cheap. First tile spans two rows on wide screens. */
/* Column count is FIXED per breakpoint rather than auto-fit, because the first
   tile spans 2x2 and the strip must still end on a flush bottom edge. The lead
   tile eats 4 slots, so 9 items = 12 slots, which divides evenly by 2, 3 and 4.
   `auto-fit` made the count depend on the window width and left a single tile
   hanging in a half-empty final row. Keep the item count at 9 (index.html
   data-limit) if you change these numbers. */
.strip {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: clamp(140px, 24vw, 240px);
  gap: clamp(0.5rem, 0.9vw, 0.9rem);
}
@media (min-width: 700px) { .strip { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1040px) { .strip { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.strip .card { border-radius: var(--radius); }
.strip .card__frame { aspect-ratio: auto; height: 100%; }
.strip .card__meta { display: none; }
.strip > *:first-child { grid-row: span 2; grid-column: span 2; }

/* =========================================================== 5. BOOKING ==== */

.booking {
  position: relative;
  background: var(--plum-950);
}
/* ⭐ SCROLL LENGTH — see .hero. He said this one "didn't look that bad" but to
   bring it down too; left long it becomes the slowest pin on the page. */
html.has-pins .booking { height: 150vh; }

.booking__stage {
  position: sticky;
  top: 0;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding-block: var(--section-y);
}
html:not(.has-pins) .booking__stage { position: static; min-height: 0; }

/* ⚠️ ROUND 6 — R6-4. A PINNED STAGE MUST FIT THE SCREEN, and this one did not.
   With the full --section-y padding the stage came to 992px against a 900px
   window and 992 against a 720px one. A sticky box taller than the screen pins
   its TOP and never shows its bottom — so on a short window the three contact
   lines sat 105px BELOW the fold for the whole time the section was pinned, and
   on a normal window they sat low enough that the reveal observer never saw
   them. Both faults are the same fault: the block is too tall for its frame.

   The padding is not doing any work here anyway — the content is centred in a
   full screen of its own — so it comes down to a size that always fits. */
@media (min-width: 700px) {
  html.has-pins .booking__stage { padding-block: clamp(1rem, 3vh, 2.5rem); }
}

.booking__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
.booking__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.35) brightness(0.42) saturate(0.8);
  transform: scale(1.08);
  will-change: transform;
}
.booking__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(90% 70% at 50% 50%, rgba(21, 12, 27, 0.55), rgba(21, 12, 27, 0.94));
}

.booking__inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-inline: var(--page-pad);
}

/* ⭐ ROUND 5 — R5-5. The square box the ring frames.
   Everything that is THE PITCH lives in here — heading, line, button — and the
   ring is its child rather than a thing floating over the whole stage. That one
   change is what makes "the text is inside the circle" true by construction
   instead of true by luck at one screen width.

   min-height rather than a hard aspect-ratio: the box is at least a square, and
   if the content ever needs more it grows rather than spilling out of a circle
   silently. */
.booking__pitch {
  position: relative;
  /* ⚠️ ROUND 6 — the `74svh` is the third term and it is not decoration. The
     circle was sized against the WIDTH only, so on a short window a 660px ring
     plus its caption came to more than the screen and the bottom of the block
     went under the fold with no warning. A circle has to fit the shorter of the
     two dimensions. At 1440x900 this resolves to 660 exactly as before — it only
     bites where it has to. */
  width: min(660px, 88vw, 74svh);
  min-height: min(660px, 88vw, 74svh);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Inside a CIRCLE, not a box: the usable width at the vertical middle is the
     full diameter, but two lines up it is much less. 11% keeps the longest line
     clear of the curve.
     ⭐ R6-4 / D3 — 11% -> 10%, ported from `.v-d3` in _sample-booking.html.
     ⚠️ THIS NUMBER IS NOT COSMETIC. `cqi` below resolves against the CONTENT
     box, so the padding is the divisor for every size in this block: at 10% the
     content box of a 660px ring is 528px, which is what makes 39cqi land the
     seal on exactly 205px. Change the padding and every size here moves. */
  padding-inline: 10%;
  /* R5-5 — the heading is sized from THIS, so it can never outgrow the ring.
     Same technique as .about__copy in R5-2b, same reason. */
  container-type: inline-size;
}

/* The gold ring that draws itself as you scroll. Now centred on the pitch. */
.booking__ring {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 50%;
  width: 100%;
  aspect-ratio: 1;
  translate: -50% -50%;
  pointer-events: none;
}
.booking__ring circle {
  fill: none;
  stroke: var(--gold-400);
  stroke-width: 0.6;
  opacity: 0.7;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
}
/* ⭐ ROUND 5 — R5-5. The size ladder was 96 / 22 / 12.5 — the heading 4.4x the
   line under it and the contact row half that again, so the block read as three
   unrelated things stacked up rather than one. Measured, not eyeballed.

   Now the heading is sized off the ring it sits in (min(…, 9.4cqi)) and the
   steps between the three are even. It is still the loudest thing on the
   screen; it just stops shouting over its own frame.

   `white-space: nowrap` is safe here ONLY because the cqi cap guarantees it
   fits — the two work as a pair. On its own nowrap converts the wrap into a
   sideways overflow, which is worse. */
/* ⭐⭐ ROUND 6 — R6-4 = D3. THE SEAL INSIDE THE TOP OF THE RING.
   Ported from `.v-d3` in site/_sample-booking.html — the page he picked from —
   not re-invented. His two notes on picking D were "the logo… looks a little
   bit too small" and "on the bottom, outside the circle, you put the writings…
   it doesn't look right to me", and this is the answer to both: 132px -> 205px,
   and the three contact lines pulled tight under the ring instead of drifting.

   ⚠️ 39cqi IS A MEASURED NUMBER, NOT A ROUND ONE. cqi is a percent of the
   CONTENT box of .booking__pitch, which is 80% of the ring (10% padding each
   side). On the 660px desktop ring that is 528px, and 39% of 528 is 205.9 — so
   the clamp ceiling is what actually lands, and the cqi keeps it proportional
   all the way down instead of jumping at a breakpoint.

   ⚠️ WHY 205 AND NOT MORE. The seal sits about 208px ABOVE the circle's middle,
   and an element above the middle has a CHORD of width available, not the
   diameter — 512px up there. 205 clears the curve with room either side; larger
   and its top corners start touching. */
.booking__seal {
  width: clamp(112px, 39cqi, 205px);
  height: auto;
  margin-bottom: var(--sp-4);
  opacity: 1;
}

.booking__pitch h2 {
  /* ⚠️ cqi resolves against the container CONTENT box, and .booking__pitch has
     10% padding either side — so 9.4cqi was 9.4% of 80% of the ring, and the
     text still came out wider than the box it had to sit in.
     ⭐ R6-4 / D3 — 7.2 -> 7.6cqi, ported from `.v-d3`. It can grow because the
     seal above it pushes the heading DOWN toward the circle's middle, where the
     chord is wider. Both numbers were measured against the phone, where the
     ring is smallest and a one-line heading's corners are closest to the curve. */
  font-size: min(3rem, 7.6cqi);
  white-space: nowrap;
  line-height: 1.25;
}
/* ⭐ R6-4 / D3 — both tightened one step (sp-4 -> sp-3, sp-6 -> sp-5). The seal
   is 205px of new height inside a circle that has not grown, so the gaps under
   it have to give the same amount back. */
.booking__pitch .lead { margin-top: var(--sp-3); }
.booking__pitch .btn { margin-top: var(--sp-5); }

/* R5-5 — OUT of the ring. It used to cut across the circle's bottom edge, which
   read as a mistake, and at 12.5px it was the smallest thing on the block while
   being the only thing a person needs to ACT on.

   ⭐ R6-4 / D3 — AND NOW IT STOPS LOOKING LOOSE. His words on the D sample:
   "on the bottom, outside the circle, you put the writings. I don't know. It
   looks… it doesn't look right to me." It was a wide row of three separate
   items with big gaps, sitting a long way under the circle with nothing tying
   it to anything. It is now one gold caption line pulled tight under the ring,
   with hairline dividers between the three, so it reads as belonging to the
   circle instead of as a stray row that happens to be below it.

   ⚠️ 0.7rem = 11.2px, which is UNDER the 12px floor this project tests for.
   It is here because it is what he approved on the sample and locked, and
   because it is a caption OUTSIDE the ring — it is not text shrunk to make
   something fit. Raising it is one number: 0.7rem -> 0.75rem. */
.booking__contacts {
  margin-top: var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  /* ⚠️ ROW gap 6, COLUMN gap 0. The 0 is the D3 look — the three items are
     spaced by their own padding and separated by hairlines, so any gap between
     them would push the dividers off the text they belong to. But `gap: 0` flat
     means that IF the row ever wraps, the second line sits directly on the
     first. A backstop, not a layout. */
  gap: 6px 0;
  justify-content: center;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-200);
  opacity: 0.72;
}
.booking__contacts a {
  padding-inline: var(--sp-4);
  transition: color var(--dur) var(--ease-out);
}
.booking__contacts a + a { border-left: 1px solid rgba(214, 178, 96, 0.3); }
.booking__contacts a:hover { color: var(--gold-200); opacity: 1; }

/* ---- the phone re-fit, ported whole from the sample's max-width:700px block --
   The ring goes to 92vw here — roughly HALF the desktop circle — while the
   writing barely shrinks, so every size in the block has to be re-stated or the
   seal pokes out of the top and the button falls out of the bottom.

   ⚠️ THE WHOLE BLOCK COMES ACROSS OR IT BREAKS. Porting the seal and leaving
   the heading and the spacing behind is what makes a phone layout that passes a
   width check and still spills out of a circle. */
@media (max-width: 700px) {
  .booking__pitch {
    width: min(660px, 92vw);
    min-height: min(660px, 92vw);
    padding-inline: 8%;
  }
  .booking__seal { width: 116px; margin-bottom: var(--sp-3); }
  .booking__pitch h2 { font-size: 6.8cqi; }
  .booking__pitch .lead { margin-top: var(--sp-2); font-size: var(--t-small); }
  .booking__pitch .btn { margin-top: var(--sp-4); padding-block: 0.8rem; }

}

/* ⚠️ THE CONTACT ROW STACKS EARLIER THAN EVERYTHING ELSE — 760, not 700 — AND
   THAT IS A MEASURED NUMBER, NOT A ROUNDING.

   The three lines set at 11.2px come to a FIXED 662px, whatever the screen is.
   The space they have is the page width less the page padding, which only
   reaches 662px at about 719px of window. So between 701 and 718 the row wrapped
   onto two lines — with the dividers still on, leaving a stray hairline hanging
   at the start of the second line. An eighteen-pixel window, found by sweeping
   every width rather than by looking at the three usual ones.

   Stacking from 760 clears it with room to spare, and it is only this row: the
   ring, the seal and the heading keep their own 700px block above, because at
   760 the circle is still the full-size desktop circle and should look like it.

   Three long lines with dividers cannot sit on one row down here anyway, so they
   stack and the dividers come off — a divider between two STACKED items is a
   rule hanging off the side of nothing. */
@media (max-width: 760px) {
  .booking__contacts { flex-direction: column; gap: 6px; }
  .booking__contacts a + a { border-left: 0; }
}

/* ============================================================== Shared ===== */

.cta-row {
  margin-top: var(--sp-7);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: center;
}

.loadmore { margin: var(--sp-6) auto 0; display: flex; }

/* Card frame with no usable image — the seal fills the hole rather than a
   broken-image icon. */
.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);
}

@media (prefers-reduced-motion: reduce) {
  .hero__cue i { animation: none; opacity: 0.7; }
}
