/* ============================================================
   CONCERT DANCE — DESIGN SYSTEM
   All type sizes bumped +2px/pt from original spec
   ============================================================ */
:root {
  --navy:       #0b2c4d;
  --main:       #04304d;
  --highlight:  #10629e;
  --pale:       rgba(30,145,206,0.08);
  --pale-full:  #1e91ce;
  --black:      #262626;
  --white:      #FFFFFF;
  --error:      #E00241;
  --grey:       #8A9BB0;
  /* DIG-005156. Photo credits and captions. The same hue as --grey, darkened to the
     first shade that clears WCAG AA for 13px text on white: #8A9BB0 is 2.84:1,
     this is 4.53:1. --grey itself is unchanged because it is used elsewhere. */
  --credit-ink: #637892;
  --footer-top: #04304d;
  --footer-bot: #10629e;

  /* Derived tokens (colour-handoff §7). The Electric -> Serious migration had to
   * touch 136 hard-coded values because these six were written out longhand at
   * every use. Named once here so the next adjustment is one line, not a sweep.
   * Scoped to this stylesheet — the workspace/webform/login sheets can load
   * without it, so they keep literals. */
  --nav-bg-translucent: rgba(16,98,158,0.75);
  --nav-bg-solid:       rgba(16,98,158,1);
  --hairline:           rgba(255,255,255,0.9);
  --shadow-hover:       rgba(16,98,158,0.35);
  --rule:               rgba(4,48,77,0.15);
  --band:               rgba(30,145,206,0.12);
  /* Opaque 12% tint of --pale-full over white, for faces that sit on a dark
   * bar where a translucent fill would composite against the bar instead. */
  --pale-tint:          #e4f2f9;

  /* Effra CC / Omnes Pro / Brandon Grotesque load via Adobe kit dgi3gsj
   * (brand-fonts-typekit library), not self-hosted — DIG-004918, 2026-10-01.
   * 'effra' was renamed 'effra-cc' in the kit; 'omnes-pro' is unchanged;
   * 'brandon' moves off its Josefin Sans stand-in now that the kit covers it. */
  --effra:   'effra-cc', sans-serif;
  --brandon: 'brandon-grotesque', sans-serif;
  --omnes:   'omnes-pro', sans-serif;

  /* 76px, not 64px, since 2026-09-14 (DIG-004994). Tzipora, 9 and 10 September:
   * "the tip of the conductor baton has a little bit of clear space above it…
   * it is not supposed to be brushing up against the top like that", then the
   * number — "about hopefully eight to 10 pixels of clear space above it… add
   * eight to 10 pixels into the nav bar at desktop, and a little bit less at
   * mobile."
   *
   * 64px gave 2.5px of clearance. 76px gives 8.5px, inside her 8-10 band. Every
   * other number in the lockup is a ratio of this and --cd-frank-h, so the one
   * token moves all of them; see .nav-lockup-mark.
   *
   * ⚠ MOBILE IS DELIBERATELY NOT CHANGED. "A little bit less at mobile" assumes
   * mobile has the same problem; it does not. At 375px it already computes 9.2px
   * of clearance — inside the band she asked for — against an overhang of only
   * 16.9px. Taking "a little bit less" literally there would spend an overhang
   * less than half the desktop figure to fix something that is not broken. Mobile
   * stays 56px in the <=768px block. */
  --nav-height: 76px;
  --content-max: 1200px;
  /* Responsive tiers, locked in the 2026-08-18 handoff (DIG-004691). This used
   * to be a single 48px at every width, which put 96px of horizontal padding on
   * a 375px phone — a quarter of the viewport before any content. Tablet and
   * mobile overrides live in the ≤1024px and ≤768px blocks. */
  --page-pad: 48px;
  /* Two hero height tiers, one per surface (hero-credit-handoff §1, DIG-004691).
   * One clamp per surface replaces the old flat 520px + a separate mobile
   * override, so every width is handled in one declaration.
   *   grand    — the homepage hero surface
   *   interior — every other carousel, plus the choreographer static hero
   * The homepage's top band is a site setting as of DIG-004992: either
   * `.home-hero-solid` or the shared carousel, switched at
   * /admin/config/mti/concert-dance/homepage. Both carry the grand tier in that
   * position — see `.path-frontpage #sharedCarousel` below — and every OTHER
   * `.hero` in this build is an interior one. It defaults to the solid band,
   * which is what the homepage has shown since 6/26. */
  --hero-grand:    clamp(520px, 70vh, 840px);
  --hero-interior: clamp(440px, 60vh, 720px);
  /* Credit text aligns to the content margin, same line as the carousel label
   * (hero-credit-handoff §3). Its own token so the two can be pulled apart
   * later without hunting for the places that assumed they were equal. */
  --credit-pad: var(--page-pad);
  /* ---- Nav branding lockup (DIG-004688, nav-lockup-spec §2) ----
   * The WHOLE lockup derives from one Frank height per breakpoint. Every other
   * number below is a ratio of it, so nothing here is a pixel snapshot and the
   * lockup rescales correctly if --cd-frank-h ever changes.
   *   wordmark height = F x 2/9        (0.22222)
   *   wordmark width  = wm height x 14.2126   (the SVG's aspect)
   *   Frank width     = F x 0.50601           (the SVG's aspect)
   *   mark -> wordmark gap = F x 0.20
   *   wordmark top    = F x 0.18393  (Frank's blue-box top)
   *   overhang        = F x 0.70496 - bar/2   -- INTENTIONAL, see .nav-lockup-mark
   * Desktop Frank is 100px against a 64px bar (1.56x). Mobile is a clamp; see
   * the <=768px block. */
  --cd-frank-h: 100px;
  --cd-wm-h: calc(var(--cd-frank-h) * 0.22222);
  --cd-overhang: calc(var(--cd-frank-h) * 0.70496 - var(--nav-height) / 2);
  /* Amendment B (2026-09-15) §3: Frank's keyline visible width IS the nav's bottom rule
     width, carried as one variable so they cannot drift. Replaces the 2px / 1.5px split,
     which assumed the authored value reached the screen intact — it did not, see §1.2 and
     the .cd-frank-outline rule below. */
  /* §3 of the overlay spec: the photo-credit lane's height. It REPLACES the
     content region's 24px bottom padding rather than adding to it, so the net
     cost to the item stack is 4px, not 28. */
  --credit-lane: 28px;
  --nav-rule: 1px;
  --cd-keyline-w: var(--nav-rule);
  /* Rendered height of one .search-result-item row: 64px thumbnail
   * + 20px*2 vertical padding + 2px bottom divider = 106px. The quick-search
   * field is grown to this so it reads as the exact same height as each result
   * row below it (Tzipora design review / DIG-004051). */
  --search-row-h: 106px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* DIG-005213. body has its own background below, so per spec it's body's white
   that paints the canvas beyond the document's own bounds -- which is exactly
   what a mobile overscroll bounce reveals under a short page's footer. Giving
   html its own background takes that job away from body (body's white then
   only fills body's own box, unaffected), so the bounce shows the footer's
   colour instead. --highlight, not --main, because it's the footer gradient's
   BOTTOM stop (closest to the page's actual bottom edge). */
html { scroll-behavior: smooth; background: var(--highlight); }
body { font-family: var(--effra); background: var(--white); color: var(--black); overflow-x: hidden; }
* { -webkit-tap-highlight-color: transparent; }

.sr-only { position: absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); }
.page { display: none; min-height: 100vh; }
.page.active { display: block; }
.page-offset { padding-top: var(--nav-height); }
.content-wrap { max-width: var(--content-max); margin: 0 auto; padding: 0 var(--page-pad); }

/* ============================================================
   NAVIGATION
   ============================================================ */
.nav {
  position: fixed; top: 0; left: 0; right: 0;
  height: var(--nav-height);
  background: var(--nav-bg-translucent);
  border-bottom: var(--nav-rule) solid var(--hairline);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 0 0 var(--page-pad);
  z-index: 1000;
  transition: background 0.3s;
}
.nav.solid { background: var(--nav-bg-solid); }

.nav-logo { display: flex; align-items: center; gap: 12px; cursor: pointer; text-decoration: none; }
/* The lockup on the anonymous workspace pages isn't a link (2026-07-30), so it
   must not claim to be one on hover. */
.nav-logo--static { cursor: default; }

/* ============================================================
   NAV BRANDING LOCKUP (DIG-004688) — nav-lockup-spec §2-§6
   Replaces .nav-logo-icon (a generic circle-and-spokes placeholder) and
   .nav-logo-text / -concert / -dance ("Concert" "Dance" as letter-spaced text).
   All four are gone from the markup and from this file.
   ============================================================ */
/* align-items: flex-start, not center: the lockup is positioned from the TOP of
   the bar by an explicit margin, because Frank is taller than the bar and
   centring it would cancel the overhang. */
.nav-logo--brand {
  align-items: flex-start;
  gap: calc(var(--cd-frank-h) * 0.20);
  /* align-self is load-bearing, not tidiness. `.nav` is a flex row with
     align-items: center, and this lockup's box is TALLER than the bar (108.5px
     against 76px) because Frank overhangs. Centring that box in the bar would lift
     the whole lockup by (76 - 108.5) / 2 = -16.25px, silently cancelling the
     margin-top the geometry depends on.

     The incident that put this line here, measured on dev against the THEN 64px
     bar: Frank's top came out at -17.25px instead of 2.50px, the overhang
     collapsed from 38.5px to 18.8px, and the wordmark's centre landed at y=12.25
     instead of on the 32px bar centreline. The ratio maths was right the whole
     time; the cross-axis alignment was throwing it away. Those figures are the
     historical record at 64px — the failure mode is identical at 76px, only the
     numbers move. */
  align-self: flex-start;
}
/* Frank drops so the WORDMARK's vertical centre lands on the bar centreline:
   margin-top = bar/2 - F x 0.18393 - wmHeight/2. At the desktop sizes that is
   now 8.5px above the baton tip, which puts Frank's bottom 32.5px below a 76px
   bar. It was 2.5px / 38.5px against the old 64px bar (DIG-004994).

   ⚠ THE OVERHANG IS THE PRICE OF THE CLEARANCE, and that is a real trade, not a
   free win: both derive from bar/2, so every pixel gained above the baton is a
   pixel lost below the bar. Spec §2 called the 38.5px overhang "the stamp"; §8
   said "64px desktop — kept, no desktop bump… a taller bar would only reduce the
   overhang". She asked for the bump anyway on 9 and 10 September knowing it was
   her own spec's arithmetic ("apparently I hadn't"), so 32.5px is the stamp now
   and both spec sections have been reissued to say so.

   Still deliberate, and unchanged: do NOT re-centre Frank or contain it in the
   bar. Nothing may clip it either — the nav is z-index 1000 and overflow-visible,
   so it paints over the hero (spec §6). */
.nav-lockup-mark {
  display: block; flex-shrink: 0;
  margin-top: calc(var(--nav-height) / 2 - var(--cd-frank-h) * 0.18393 - var(--cd-wm-h) / 2);
}
/* The wordmark's own top = Frank's blue-box top, which by the margin above is
   exactly bar/2 - wmHeight/2 from the bar top. */
.nav-lockup-wordmark {
  display: block; flex-shrink: 0;
  margin-top: calc(var(--nav-height) / 2 - var(--cd-wm-h) / 2);
}
/* Height only — both assets ship with no width/height attributes on purpose, so
   the viewBox aspect supplies the width (spec §1, README §SVG assets).
   overflow: visible is required on Frank: the keyline is a centred stroke on a
   rect that reaches the edge of the viewBox, so half of it would be clipped. */
.cd-frank { display: block; height: var(--cd-frank-h); width: auto; overflow: visible; }
.cd-wordmark { display: block; height: var(--cd-wm-h); width: auto; }

/* Keyline: white, round the WHOLE mark — the blue box and the parts of Frank that
   overhang it (DIG-004938). non-scaling-stroke keeps it a true screen-pixel width at any
   Frank size. Visibility follows nav transparency: ON translucent, OFF solid. It
   cross-fades on the same 0.3s as the nav background and would follow a future
   scroll-to-solid transition for free.
   NOTE the README's snippet keys this to a `.nav--translucent` class. This build
   expresses the same state the other way round — `.nav` is translucent by
   default and `.nav.solid` opts out — so the selector is the negation.

   ⚠ THIS DEPARTS FROM nav-lockup-spec §5 ON HER INSTRUCTION. §5 reads "A white keyline on
   Frank's blue box only (not the figure)... the figure paints over the top edge", and that
   is what shipped: a stroke on the .cdf-4 rect alone. The consequence was the thing she
   objected to on 2026-09-08 — the keyline broke wherever the baton, hand or coat crossed
   the box edge, and there was none at all along the overhang. Her words: "we have a thick
   white border around the blue box but then the border gets smaller around the actual
   illustration... The border needs to be the same across the blue box and the illustration."
   She raised it to high herself and asked for it fixed on the call. §5 needs rewriting to
   match; until it is, this rule and that spec line disagree, and the call is the newer of
   the two. Reverting is one rule: stroke .cdf-4 instead of .cd-frank-outline. */
.nav .cd-frank .cd-frank-outline {
  /* The <use> in nav-frank.html.twig repeats the whole mark behind itself. Stroked at
     TWICE the keyline width because a stroke is centred on the path: the inner half is
     hidden by the real copy in front, so the visible outer half is exactly --cd-keyline-w.
     Filled as well as stroked, so the backing copy is opaque where the front artwork has
     a transparent path rather than showing the stroke's inner edge through it.
     miter + a low miterlimit keeps the box's corners square — round joins visibly softened
     them, which is not what her artwork does. */
  fill: #fff;
  stroke: #fff;
  stroke-width: calc(var(--cd-keyline-w) * 2);
  stroke-linejoin: miter;
  stroke-miterlimit: 2;
  opacity: 0.9;
  transition: opacity 0.3s;
}

/* ⚠ vector-effect IS NOT AN INHERITED PROPERTY, and the keyline is painted by a <use>.
   Declaring it on .cd-frank-outline (the <use> itself) therefore styles nothing that draws:
   the shapes live in the use-element's shadow tree, which the property cannot reach by
   inheritance the way stroke and stroke-width do. The stroke then resolved in viewBox units
   and scaled with Frank — Amendment B §1.2, and exactly what she measured.

   Arithmetic, confirmed in a browser on live 2026-09-15 before this change: Frank renders
   100px against a 360.03 viewBox, so 0.27775px per unit. The authored 4px became 4 USER
   UNITS = 1.111px of total stroke, of which the visible outer half is 0.556px. Her
   measurement was 0.56px desktop / 0.37px mobile. Sampling the rendered pixels at the blue
   box's left edge found ZERO white pixels before this rule and a clean 2px run after it.

   Matching the ORIGINAL shapes rather than the <use> is what makes it apply: the cloned
   content in the shadow tree takes the computed style of the element it was cloned from.
   Verified by injection on live before it was written here. */
#cd-frank-art,
#cd-frank-art * {
  vector-effect: non-scaling-stroke;
}
/* Solid nav: the whole outline goes, not just its stroke — a fill-only backing copy would
   still show hairlines round the overhang under antialiasing. */
.nav.solid .cd-frank .cd-frank-outline { opacity: 0; }

/* ---- Overhang clearance on no-hero pages (spec §7, Option A: push down) ----
   With no hero between the bar and the content, Frank's overhang dips into the
   top-left where the first content element starts. Measured on this build at the
   locked desktop sizes: the overhang reaches 38.5px below the bar and the first
   .content-wrap starts 32px below it, so Frank overlapped the breadcrumb by
   6.5px. Clearance = overhang + 20px, carried as the formula so it recomputes if
   Frank or the bar ever change.

   Keyed to a `cd-no-hero` class the template sets, NOT to `.nav.solid`. The spec
   equates "solid nav" with "no hero", and on this build they are different sets:
   the legal pages (Terms / Privacy / Accessibility) are listed in §5 and §7 as
   solid no-hero pages but actually render the shared carousel and a TRANSLUCENT
   nav, while `page-webform` has no hero and a translucent nav yet already
   carries 112px of header padding. The pages that genuinely need this are the
   two no-photo dance-work variants. Keying it to the real condition — no hero —
   means it covers any future page in that state instead of any future solid one.
   Flagged to Tzipora. */
/* The dance-work page's first wrapper. These two values used to be an inline
   style on the element, which no stylesheet rule can override — that is why the
   clearance below had no effect the first time. */
/* +50px on both, DIG-005088 — see the clearspace block further down for her
   wording. The dance work page is the surface she was actually looking at
   ("basically only the dance work pages"), and its breadcrumb is NOT inside
   .breadcrumb-row, so the rule down there does not reach it. Measured on live
   before changing: the breadcrumb sat 292px down inside .content-wrap.dw-top-wrap
   with 32px above it. 32 + 50 = 82. */
.dw-top-wrap { padding-top: 82px; padding-bottom: 0; }
.cd-no-hero .dw-top-wrap { padding-top: calc(var(--cd-overhang) + 70px); }
/* DIG-005121, the dance work half. Her note was given on a dance work page first:
   "if we go into a dance work... at mobile it's too tall... maybe 25px less space
   at mobile". The 18 Sep fix changed .breadcrumb-row (58 -> 33) and was measured on
   /news, but this page's breadcrumb lives in .dw-top-wrap, so it never reached it.
   Measured on live 23 Sep at 375px: 82px with a hero, 86.9px without. Both -25.
   Both selectors are repeated because .cd-no-hero .dw-top-wrap outranks the bare
   class, and this block has to sit AFTER the two rules above or source order wins. */
@media (max-width: 768px) {
  .dw-top-wrap { padding-top: 57px; }
  .cd-no-hero .dw-top-wrap { padding-top: calc(var(--cd-overhang) + 45px); }
}

/* Contributor workspace lockup (Tzipora 2026-07-28). Her SVG puts the vertical
   divider bar flush with the top and bottom of its own viewBox, so clearspace
   can only come from the bar: a 44px lockup in an 80px nav leaves 18px above
   and below. Raising --nav-height on <body> rather than on .nav also moves
   every offset calc()'d off it (.page-offset, the login card, the workspace and
   rights wrappers), so the content below stays clear of the taller bar. */
body.contributor-chrome { --nav-height: 80px; }

/* Lock the footer to the bottom of short contributor pages (DIG-004418, Tzipora
   2026-07-28: "I would love if on all these pages the footer could lock to the
   bottom"). /logged-out and the login card are short enough that the footer
   floated mid-viewport with dead white space under it.
   Column flex + margin-top:auto on the footer, scoped to contributor chrome so
   the public pages' layout is untouched. The nav is position:fixed and therefore
   out of flow, so it is unaffected; the flex items in between keep their natural
   `flex: 0 1 auto` sizing, so nothing stretches.
   The flex container is core's .dialog-off-canvas-main-canvas wrapper, NOT <body>:
   the footer lives inside it, so `body > .footer` never matches (and making body
   itself the flex parent turns the cookie-banner div and a run of <script> tags
   into flex items). */
body.contributor-chrome > .dialog-off-canvas-main-canvas {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
body.contributor-chrome > .dialog-off-canvas-main-canvas > .footer { margin-top: auto; }

.nav-lockup-pic { display: block; }
.nav-lockup { display: block; height: 44px; width: auto; }

/* Right-side nav cluster: logout link (any logged-in user) + the search/menu
   icons. On contributor chrome pages the icons are hidden, so the logout link
   sits alone on the right, inline with the wordmark (DIG-004384). */
.nav-right { display: flex; align-items: center; }
.nav-logout {
  display: inline-flex; align-items: center;
  height: var(--nav-height);
  padding: 0 24px;
  color: white; text-decoration: none;
  font-family: var(--brandon); font-weight: 700;
  font-size: 0.8rem; letter-spacing: 0.12em; text-transform: uppercase;
  white-space: nowrap;
  transition: opacity 0.2s;
}
.nav-logout:hover { opacity: 0.7; }
.nav-icons { display: flex; align-items: center; }
.nav-icon-btn {
  height: 60px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; border-radius: 4px;
  transition: background 0.2s; border: none; background: transparent;
  padding: 0 20px;
}
.nav-icon-btn:hover, .nav-icon-btn.active { background: var(--navy); }
#searchBtn.active { opacity: 0.5; }
/* 30px glyph (spec §3). The button geometry around it — 60px tall, padding
   0 20px, 4px radius, navy hover, .2s — was already correct, and the icons
   already hug the right edge because .nav has padding-right: 0 and .nav-icons
   has no gap (spacing comes from the button padding). */
.nav-icon-btn .material-icons { color: white; font-size: 30px; }
.no-hero-offset { margin-top: var(--nav-height); }

/* NAV OVERLAY */
.nav-overlay {
  position: fixed; top: 0; left: 0; right: 0;
  display: flex; flex-direction: column; justify-content: center;
  /* §2: keyed to the same tier as the homepage carousel so the two follow each
     other if that is ever retuned. Use the token, never the literal clamp.
     Replaces `height:auto; min-height:520px; max-height:100vh`. */
  height: var(--hero-grand);
  z-index: 999; transform: translateY(-100%);
  transition: transform 0.3s ease-out; overflow: hidden;
  /* §1 trap 1: mix-blend-mode blends with everything beneath it in the same
     stacking context. This panel gets isolation by ACCIDENT today, from the
     transform used for the slide. Declare it so it survives a reimplementation
     that animates `top` or uses clip-path instead. */
  isolation: isolate;
}
/* The closed overlay is kept out of the tab order and the accessibility tree by
   the `inert` attribute, toggled in concert-dance.js — NOT by CSS (DIG-004704).
   It was hidden by transform alone, which leaves its links focusable while the
   menu is shut; that was liveable for navigation links and is not, now that
   "Log out" lives in here.
   A `visibility: hidden` + delayed-transition version of this was tried first and
   rejected: the guarantee then depends on a 0.3s transition completing, and
   repeated open/close toggling interrupts it — measured on dev, the overlay was
   left computed `visible` after closing via resetNav(). `inert` is immediate, has
   no timing semantics, and does not affect painting, so the slide animation is
   untouched. */
.nav-overlay.open {
  transform: translateY(0);
}

@media (max-width: 768px) {
  /* §1 trap 2: dvh, NEVER vh. 100vh excludes the browser toolbars from its
     measurement, so the panel runs underneath them and bottom-anchored content
     is invisible on device while looking correct in a desktop emulator. */
  .nav-overlay { height: 100dvh; }
  .overlay-credit { bottom: max(0px, env(safe-area-inset-bottom)); }
}
/* OVERLAY LAYER STACK — concert-dance-overlay-handoff.md §3, and the rename the
   selector concordance §D calls for. The build carried three layers with the base
   colour and the photograph fused into one, and the scrim misnamed
   `.nav-overlay-photo` — a name describing the wrong thing. Five now, separated:
   base fill, photograph, scrim, content, credit. */
.overlay-bg { position: absolute; inset: 0; background: var(--navy); }

/* The blend keeps only the photograph's luminance and takes hue and saturation
   from the navy beneath, so ANY photograph resolves to a navy duotone — which is
   what makes one fixed scrim safe across a rotating set of images. Consequence
   worth knowing: `saturate()` has no effect under luminosity, so do not add one;
   brightness and contrast still work because they alter luminance. */
.overlay-photo {
  position: absolute; inset: 0;
  background-size: cover;
  background-repeat: no-repeat;
  /* DIG-005094. Was a hardcoded `center`, which is why there was nothing to
     find when Tzipora went looking for the controls: the framing was not a
     setting, it was a constant. Two theme settings now drive it, and the
     cascade is written here rather than inline because the phone value needs a
     media query and an inline style cannot carry one. Same shape as the hero's
     --cd-hero-pos / --cd-hero-pos-mobile pair, deliberately. */
  background-position: var(--nav-photo-pos, center);
  mix-blend-mode: luminosity;
  opacity: 1;
}

/* The phone panel falls back to the desktop value when it has none of its own,
   exactly as the settings-form preview shows it. Placed immediately after the
   rule above, and it must stay there: same specificity, so source order decides
   (see the note at #searchResults). The two panels really are different shapes —
   1425 x 630 on a 1440x900 desktop against 375 x 667 on a phone — so one value
   framing both is a coincidence, not a default. */
@media (max-width: 768px) {
  .overlay-photo {
    background-position: var(--nav-photo-pos-mobile, var(--nav-photo-pos, center));
  }
}

/* Dense at the RIGHT edge, where the right-aligned items live, falling off across
   the full width to a low floor so the left third still reads as photograph.
   One flat colour at varying alpha, so the result does not depend on the
   photograph's own hues. §1 trap 3: the dark stop is written FIRST and 270deg
   points away from it — reversing the stops puts the density on the empty side. */
.overlay-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(270deg,
    rgba(11,44,77,0.70) 0%,
    rgba(11,44,77,0.10) 100%);
}

.overlay-content {
  position: relative; z-index: 2; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: flex-end;
  justify-content: center;
  /* §1 trap 4: the credit lane REPLACES this 24px, it never adds to it. */
  padding: var(--nav-height) var(--page-pad) var(--credit-lane);
  gap: 0;
  overflow: hidden;
}

/* §3: a reserved lane, not an overlay on the content. Right-aligned deliberately
   — the scrim is dense at the right, so the credit sits in high-alpha navy and
   clears 4.5:1 comfortably; left-aligned it would sit at the 0.10 floor and fail
   badly. Single line, grows leftward, never wraps. */
.overlay-credit {
  position: absolute;
  z-index: 3;
  left: 0; right: 0; bottom: 0;
  height: var(--credit-lane);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 var(--page-pad);
  white-space: nowrap;
  border-top: 1px solid rgba(255,255,255,.20);
  font-family: var(--brandon);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(255,255,255,.78);
  line-height: 1.4;
  pointer-events: none;
}

/* §3: the supported no-photograph state — base plus scrim, no credit element,
   no placeholder, and the content region takes its 24px back. */
.nav-overlay.is-flat .overlay-photo,
.nav-overlay.is-flat .overlay-credit { display: none; }
.nav-overlay.is-flat .overlay-content { padding-bottom: 24px; }

/* §6: the search panel carries no photograph, no scrim and no credit, so it has
   only a base — and the content sits above it. */
.search-overlay .overlay-content { position: relative; z-index: 2; height: 100%; }
.nav-close {
  position: absolute; top: 0; right: 0; width: 60px; height: 60px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; border: none; background: var(--navy); border-radius: 4px;
}
.nav-close .material-icons { color: white; font-size: 28px; }
.nav-item {
  font-family: var(--brandon); font-weight: 300;
  font-size: 40pt; letter-spacing: 0.3em; text-transform: uppercase;
  color: white; cursor: pointer; text-align: right;
  padding: 20px 0; border: none; background: none; text-decoration: none;
  line-height: 1.2; position: relative;
  display: flex; align-items: center; justify-content: flex-end; gap: 12px;
}
.nav-item-text { position: relative; }
.nav-item-text::after {
  content: ''; position: absolute; bottom: -4px; right: 0;
  height: 1px; background: white;
  width: 0; transition: width 0.2s ease;
}
/* DIG-005113. Tzipora, 18 Sep: "when you click the touch state, the line goes too
   long. It goes all the way across frame... the line element should only go on the
   touch state to the length of the bottom line." Her example: if only "Ballets"
   wraps to the second line, the rule should stop at the end of "Ballets".

   --cd-underline-w is measured per item in JS, because CSS cannot know where a
   line breaks. `.nav-subitem-text` is a flex item and therefore blockified, so
   `element.getClientRects()` returns ONE rect for the whole box — the measurement
   uses a Range over its contents, which does return a rect per line box.

   The fallback is 100%, so the moment the JS has not run the behaviour is exactly
   what it was before. The nav is right-aligned and the rule is anchored `right: 0`,
   so the last line's right edge already coincides with the box's — no horizontal
   offset is needed.

   ⚠ She reserved the right to reverse this: "I'm going to reserve the right to
   change that if and when I see it in action to the length of the longer part."
   Deliberately not made configurable for that reason. */
.nav-item:hover .nav-item-text::after { width: 100%; }
.nav-item.expanded { font-weight: 500; }
.nav-item.expanded .nav-item-text::after { display: none; }
.nav-item-group { display: flex; flex-direction: column; overflow: hidden; }
/* Overlay "Log out" (DIG-004704). Hidden at desktop widths, where the bar link
   fits and having both would put two identically-labelled controls on the page.
   Shown below 768px, where the bar link is hidden instead — see the mobile block.
   Smaller than the navigation items on purpose: a utility action, and it keeps a
   fifth item from clipping inside the overlay's overflow:hidden. */
.nav-item--logout { display: none; font-size: 16pt; letter-spacing: 0.2em; font-weight: 700; padding-top: 28px; }
.nav-item-group .nav-subitems { margin-top: 0; }
.nav-item-group .nav-subitems.open { margin-top: 8px; }
.nav-subitems {
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
  max-height: 0; overflow: hidden; opacity: 0;
  transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
}
.nav-subitems.open { max-height: 300px; opacity: 1; }
.nav-subitem {
  font-family: var(--brandon); font-weight: 300;
  font-size: 12pt; letter-spacing: 0.1em; text-transform: uppercase;
  color: white; cursor: pointer; text-align: right;
  padding: 10px 0; border: none; background: none; text-decoration: none;
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
}
.nav-subitem-text { position: relative; }
/* DIG-005113. Tzipora, 18 Sep: "for the sub nav items on catalogue and news, the
   type be reduced by half so that they're not so big" — confirmed on the call as
   half of what renders today. Her reasoning, and why this is mobile only:
   "someone's holding a phone, it's close to their face. I'm not worried about
   size on desktop." */
.nav-subitem-text::after {
  content: ''; position: absolute; bottom: -4px; right: 0;
  height: 1px; background: white;
  width: 0; transition: width 0.2s ease;
}
.nav-subitem:hover .nav-subitem-text::after,
.nav-subitem:focus-visible .nav-subitem-text::after,
.nav-subitem.touch-active .nav-subitem-text::after { width: var(--cd-underline-w, 100%); }
.nav-item:focus-visible .nav-item-text::after,
.nav-item.touch-active .nav-item-text::after { width: 100%; }
.nav-subitem.loading .nav-spinner { display: block; }

@keyframes nav-spin { to { transform: rotate(360deg); } }
.nav-spinner {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.3); border-top-color: white;
  animation: nav-spin 0.7s linear infinite;
  flex-shrink: 0; display: none;
}
.nav-item.loading .nav-spinner { display: block; }

/* SEARCH OVERLAY */
.search-overlay {
  position: fixed; top: 0; left: 0; right: 0;
  height: var(--hero-grand);                 /* §2: same tier as the nav panel */
  z-index: 998; background: var(--navy);
  transform: translateY(-100%); transition: transform 0.3s ease-out;
  overflow: hidden;
  isolation: isolate;
}
.search-overlay.open { transform: translateY(0); }

@media (max-width: 768px) {
  .search-overlay { height: 100dvh; }   /* dvh, not vh — trap 2 */
}
/* .search-overlay-bg / .search-overlay-photo retired — §6 makes this panel flat.
   The markup now emits a single .overlay-bg and no photograph at all. */
.search-overlay > .search-bar,
.search-overlay > #searchResults { position: relative; z-index: 1; }
.search-bar {
  position: relative;
  /* +25px (DIG-005088). Tzipora, 16 Sep: "we've got the interaction with the logo,
     the drop logo, the overhang, the smaller you go... let's try 25px, but if
     that's too small we'll go up. I just need a little bit more space here, it's
     too squishy." Kept as a calc on --nav-height so it still tracks the nav
     rather than becoming a second number to maintain. */
  margin-top: calc(var(--nav-height) + 25px);
  max-width: var(--content-max);
  margin-left: auto; margin-right: auto;
  width: 100%;
  /* Grow to the FULL rendered height of a .search-result-item row (incl. its
   * 64px thumbnail) so the input reads as exactly the same height as the items
   * below it. The old 76px only matched a text-only row and looked ~30px short
   * once the result thumbnails were in place. */
  min-height: var(--search-row-h);
  padding: 0 var(--page-pad);
  display: flex; align-items: center; gap: 12px;
}
/* Underline drawn inset by --page-pad (via pseudo-element, not the box border)
 * so it matches the width of the .search-result-item dividers below it, which
 * sit inside #searchResults' own --page-pad padding. */
.search-bar::after {
  content: ""; position: absolute;
  left: var(--page-pad); right: var(--page-pad); bottom: 0;
  border-bottom: 2px solid rgba(255,255,255,0.3);
}
.search-bar-inner {
  display: contents;
}
.search-bar .material-icons { color: white; font-size: 24px; }
.search-input {
  flex: 1; background: none; border: none; outline: none;
  font-family: var(--effra); font-size: 18px; color: white; caret-color: white;
}
.search-input::placeholder { color: rgba(255,255,255,0.5); }

/* ============================================================
   HERO
   ============================================================ */
.hero { position: relative; width: 100%; height: var(--hero-interior); overflow: hidden; }
.static-hero {
  width: 100%; height: var(--hero-interior); position: relative;
  background: linear-gradient(135deg,#0b2c4d 0%,#04304d 60%,#10629e 100%);
  overflow: hidden;
}
/* No object-position here — see the hero-framing block below (DIG-004637). */
.static-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- Hero framing, desktop and mobile (DIG-004510 / DIG-004524) ----
   .static-hero reads --hero-interior, clamp(440px, 60vh, 720px) (DIG-004691),
   so the hero swings from about 2.67:1 on a 1440x900 desktop to 0.77:1 on a
   390x844 phone — landscape to strong portrait. (It was a flat 520px until
   DIG-004691; the swing is the same problem either way, slightly narrower now.)
   One object-position cannot serve both, so staff set two: the
   mobile one is optional and falls back to the desktop value, which means an
   unset mobile field leaves today's rendering exactly as it was.

   DIG-004637: this is the ONLY place hero object-position may be declared.
   `.static-hero img` and `.dw-header-photo img` are both (0,1,1) and beat
   `.cd-hero-img` (0,1,0) regardless of source order, so an object-position on
   either container silently killed this control from 2026-08-05 until
   2026-08-16 — the field saved, the custom property reached the page, and the
   render ignored it. Do not add object-position to a hero container; put the
   image in .cd-hero-img and let this rule own the framing. */
.cd-hero-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  object-position: var(--cd-hero-pos, center top);
}
@media (max-width: 768px) {
  .cd-hero-img {
    object-position: var(--cd-hero-pos-mobile, var(--cd-hero-pos, center top));
  }
}
.carousel-placeholder { width: 100%; }
.hero-slides { display: flex; height: 100%; transition: transform 0.5s ease; width: 100%; }
.hero-slide { min-width: 100%; width: 100%; height: 100%; position: relative; display: flex; align-items: flex-end; flex-shrink: 0; }
/* Colour layer only. The photo is a real <img> on top of this (DIG-004689), so
   this element carries no background-image and no background-position — it is
   what shows while the photo loads, and if the photo fails. */
.hero-slide-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }

/* ---- Carousel slide photo + framing (DIG-004689) ----
   Mirrors the node-hero pattern: ONE rule owns object-position, reading a custom
   property, with a mobile override that falls back to the desktop value. Do not
   declare object-position on .hero-slide, .hero-slide-bg or .hero-slide-pic —
   a container rule at (0,1,1) beats this one at (0,1,0) regardless of source
   order, which is exactly how the node heroes shipped broken for eleven days
   (DIG-004637).

   Below 768px the <picture> may be serving a DIFFERENT file (the composed
   mobile crop). --cd-slide-pos-mobile therefore applies to whichever file the
   browser chose, which is the behaviour the spec asks for: the mobile framing
   value belongs to the mobile photo when there is one, and to the desktop photo
   when there is not. */
.hero-slide-pic { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: block; }
.hero-slide-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* --cd-slide-pos is resolved in PHP and already carries the focal point when
     no explicit desktop position is set, so the chain here stays two-deep
     (DIG-005077). --cd-focal is the last resort for a slide with neither. */
  object-position: var(--cd-slide-pos, var(--cd-focal, center));
}
@media (max-width: 768px) {
  .hero-slide-img {
    object-position: var(--cd-slide-pos-mobile, var(--cd-slide-pos, var(--cd-focal, center)));
  }
}
.hero-label {
  display: inline-flex; align-items: center; gap: 14px;
  background: var(--main); padding: 25px;
  font-family: var(--effra); font-weight: 300; font-size: 26px; color: white;
  max-width: calc((var(--content-max) - var(--page-pad) * 2) * 0.5);
  max-height: calc(26px * 1.4 * 3 + 50px);
  overflow: hidden;
  cursor: pointer;
  border-bottom: 4px solid transparent;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.15s ease;
}
@media (hover: hover) {
  .hero-label:hover {
    border-bottom-color: white;
    box-shadow: 8px 8px 20px 2px var(--shadow-hover);
  }
}
.hero-label:active {
  background: var(--navy);
  border-bottom-color: white;
  box-shadow: 4px 4px 12px 1px var(--shadow-hover);
}
.hero-label-spinner {
  width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
  border: 2px solid rgba(255,255,255,0.3); border-top-color: white;
  animation: nav-spin 0.7s linear infinite;
  display: none;
}
.hero-label.loading .hero-label-spinner { display: block; }
/* DIG-005153. The spinner used to be a flex item beside the text, so showing it
   pushed the label onto an extra line (80 -> 104px at 375). Tzipora, 23 Sep: layer
   it on top and let the text fade under it. Now it is taken out of the flow over
   the label's right-hand padding, and the label keeps its exact size. */
.hero-label { position: relative; }
.hero-label.loading .hero-label-spinner { position: absolute; right: 12px; top: calc(50% - 10px); }
.hero-label.loading #heroLabelText { opacity: 0.5; transition: opacity 0.15s ease; }

/* DIG-005216. On touch, :active only holds for as long as the finger is down —
   it releases well before heroLabelClick()'s 600ms navigation delay is over, so
   the bar fell back to its resting background (and, on some touch browsers, a
   bright native tap-highlight flash) for that gap. Tzipora wants one continuous
   muted tone across tap-and-loading, not two states. Forcing the same :active
   background for the whole .loading duration makes that deterministic — it no
   longer depends on how long any browser's native touch-active state happens to
   last. */
.hero-label.loading {
  background: var(--navy);
  border-bottom-color: white;
}
.hero-controls {
  position: absolute; bottom: 24px; right: var(--page-pad);
  display: flex; align-items: center; gap: 12px; z-index: 2;
}
.hero-btn {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--main); border: 2px solid white;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: background 0.2s;
}
.hero-btn:hover, .hero-btn.paused { background: var(--highlight); }
.hero-btn .material-icons { color: white; font-size: 18px; }
.hero-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--grey); cursor: pointer; border: none; transition: background 0.2s;
}
.hero-dot.active { background: var(--highlight); }

/* ============================================================
   TABS
   ============================================================ */
.tabs {
  display: flex; gap: 80px;
  border-bottom: 1px solid rgba(4,48,77,0.2); margin-bottom: 32px;
  position: relative;
}
.tab-indicator {
  position: absolute; bottom: -1px; height: 3px;
  /* Start hidden so the bar doesn't flash at width:auto/left:0 before JS
   * positions it under the active button on first paint. */
  width: 0; left: 0;
  background: var(--main); transition: left 0.3s ease, width 0.3s ease;
}
.tab-btn {
  font-family: var(--brandon); text-transform: uppercase;
  font-size: 20px; letter-spacing: 0.05em;
  color: var(--main); background: none; border: none;
  padding: 12px 0; cursor: pointer; font-weight: 300;
  position: relative;
}
.tab-btn.active { font-weight: 700; }
.tab-btn.active::after { display: none; }
.tab-content { display: none; }
.tab-content.active { display: block; }

/* ============================================================
   BUTTONS
   ============================================================ */
.btn {
  font-family: var(--brandon); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 15px;
  border-radius: 4px; border: 2px solid; cursor: pointer;
  padding: 12px 20px 8px; transition: all 0.2s;
  display: inline-flex; align-items: center; gap: 8px; text-decoration: none;
  line-height: 1;
}
.btn-primary { background: var(--highlight); color: white; border-color: var(--highlight); }
.btn-primary:hover, .btn-primary:active, .btn-primary.touch-active { background: white; color: var(--highlight); }
.btn-primary-inv { background: transparent; color: white; border-color: white; }
.btn-primary-inv:hover, .btn-primary-inv:active, .btn-primary-inv.touch-active { background: white; color: var(--highlight); border-color: white; }
.btn-action, .btn-simple { background: white; color: var(--main); border-color: var(--main); }
.btn-action:hover, .btn-simple:hover, .btn-action:active, .btn-simple:active, .btn-action.touch-active, .btn-simple.touch-active { background: var(--pale); }

/* Tier 3 button on pale/light blue background: no fill inactive, white hover */
.btn-simple-pale { background: transparent; color: var(--main); border-color: var(--main); }
.btn-simple-pale:hover, .btn-simple-pale:active, .btn-simple-pale.touch-active { background: white; }

/* ============================================================
   TYPOGRAPHY
   ============================================================ */
.h1 { font-family: var(--effra); font-weight: 700; font-size: 36px; color: var(--main); line-height: 1.2; }
.h1-highlight { font-family: var(--effra); font-weight: 700; font-size: 36px; color: var(--highlight); line-height: 1.2; }
.h2 { font-family: var(--effra); font-weight: 700; font-size: 26px; color: var(--black); line-height: 1.3; }
.h3 { font-family: var(--effra); font-weight: 500; font-size: 24px; color: var(--highlight); line-height: 1.3; }
.article-title { font-family: var(--effra); font-weight: 700; font-size: 24px; color: var(--main); }
.body-a { font-family: var(--effra); font-weight: 400; font-size: 18px; color: var(--black); line-height: 1.7; }
.body-b { font-family: var(--effra); font-weight: 300; font-size: 18px; color: var(--black); line-height: 1.7; }
.date-stamp { font-family: var(--effra); font-weight: 400; font-size: 15px; color: var(--main); }
.byline { font-family: var(--effra); font-weight: 400; font-size: 15px; color: var(--black); }
.caption { font-family: var(--effra); font-weight: 300; font-size: 15px; color: var(--black); }
.faq-answer { font-family: var(--effra); font-weight: 400; font-size: 18px; color: var(--main); line-height: 1.7; max-width: 720px; }
.choreographer-name-dw { font-family: var(--effra); font-weight: 500; font-size: 20px; color: var(--highlight); }

.pullquote {
  font-family: var(--effra); font-weight: 300; font-style: italic;
  font-size: 18px; color: var(--black); line-height: 1.65;
  position: relative; padding: 40px 0 40px 40px;
  margin: 40px auto;
}
.pullquote::before {
  content: '\201C';
  font-size: 400px; font-style: normal; font-weight: 700;
  color: rgba(16,98,158,0.12);
  position: absolute; left: -20px; top: -40px;
  line-height: 1; font-family: Georgia, serif;
  z-index: 0; pointer-events: none;
}
.pullquote-attribution {
  display: flex; flex-direction: column; align-items: flex-end;
  margin-top: 24px; position: relative; z-index: 1;
}
.pullquote-attribution-name {
  font-family: var(--effra); font-weight: 700; font-style: normal;
  font-size: 18px; color: var(--black);
  padding-left: 24px; text-indent: -24px;
}
.pullquote-attribution-title {
  font-family: var(--effra); font-weight: 300; font-style: normal;
  font-size: 16px; color: var(--black); padding-left: 24px;
}

/* ============================================================
   CARDS & LISTS
   ============================================================ */
.catalogue-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; margin-bottom: 48px; }
.card {
  border: 1.5px solid var(--highlight); border-radius: 5px;
  background: white; cursor: pointer; overflow: hidden; transition: box-shadow 0.2s;
  display: flex; flex-direction: column;
  text-decoration: none; color: inherit;
}
.card:hover { box-shadow: 8px 8px 20px 2px var(--shadow-hover); }
.card.touch-active { box-shadow: 8px 8px 20px 2px var(--shadow-hover); border-color: var(--highlight); }
.news-card.touch-active, .news-card-home.touch-active, .learn-more-card.touch-active { transform: scale(0.98); }
.card-img-placeholder {
  width: 100%; aspect-ratio: 4/3;
  background: linear-gradient(135deg, var(--pale) 0%, rgba(16,98,158,0.08) 100%);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; flex-shrink: 0;
}
/* Focal point (DIG-004977): --cd-focal is set per image in the template and
   falls back to centre, so an unframed photograph crops exactly as before.
   ONE rule owns object-position on this surface — do not add another. */
.card-img-placeholder img { width: 100%; height: 100%; object-fit: cover; object-position: var(--cd-focal, center); display: block; }
.card-img-placeholder .material-icons { font-size: 64px; color: var(--highlight); opacity: 0.3; }
.card-body { padding: 12px 16px 0; }
.card-title {
  font-family: var(--effra); font-weight: 400; font-size: 18px;
  color: var(--highlight); line-height: 1.4; text-decoration: none;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.card-bottom { padding: 0 0 50px; flex: 1; border-bottom: 4px solid transparent; }
.card:hover .card-bottom { border-bottom-color: var(--highlight); }

/* ============================================================
   DANCE WORK CATALOGUE CARD — concert-dance-card-handoff.md §8
   ============================================================
   Locked 2026-09-08, built 2026-09-15 (DIG-004975). Title leads, photo sits at
   the bottom, and a work with no photograph renders nothing in its place.

   `.work-card` sits ALONGSIDE `.card`, it does not replace it (DIG-004974 §E):
   `.card` is load-bearing in concert-dance.js — the filter count, the filter
   loop, the filter reset and the grid/list toggle all select it — and it is
   shared with the choreographer card, which this spec explicitly leaves alone
   (§11). The second class is what lets #page-bio suppress the choreographer
   name on one of them and not the other.

   Everything below overrides a `.card` rule of the same name rather than
   editing it, so the choreographer card is untouched. */

.work-card {
  position: relative;
  /* display:flex, column, 1.5px border, 5px radius, overflow:hidden and the
     hover shadow all come from .card and already match §3. */
}

.work-card-head { padding: 16px 16px 0; }

.work-card-title {
  font-family: var(--effra);
  font-weight: 400;
  font-size: 18px;
  line-height: 1.4;
  color: var(--highlight);
  /* §3: free wrap, no clamp, no fixed zone — titles are NEVER truncated.
     `.card-title` clamps to 3 lines and this element carries both classes, so
     the clamp has to be switched off explicitly rather than merely not set.
     §4: "a licensor's work should not appear as The Four Temperaments: A Dance
     Piece Without Plot in Five…" */
  display: block;
  -webkit-line-clamp: none;
  overflow: visible;
}

.work-card-choreographer {
  margin-top: 6px;
  font-family: var(--brandon);
  font-weight: 700;
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--highlight);            /* §6: var(--highlight), never #10639e */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  height: 34px;                       /* 12 x 1.4 x 2 — recalc if font-size moves */
}

/* §5: suppressed on choreographer-specific views, where the name is the H1 and
   repeating it on every card is a stutter. A DISPLAY rule, not a data rule, so
   it reverses by deleting this declaration. #page-bio, not her
   #page-choreographer, which does not exist in this build (DIG-004974 §B). */
#page-bio .work-card-choreographer { display: none; }

.work-card-spacer { flex: 1; min-height: 16px; }

/* DIG-005072. The choreographer card's name, in place of .work-card-title.
   "It's all caps, not the dance work title style" — so this is the brandon
   treatment the card already uses for a work's choreographer line, sized up to
   act as the card's own title. Deliberately NOT clamped: a person's name is
   never truncated, the same rule the work title follows.
   .card-title also lands on this element for switchView(), and clamps to three
   lines, so the clamp is switched off explicitly rather than merely not set. */
.choreo-card-name {
  font-family: var(--brandon);
  font-weight: 700;
  /* DIG-005119. Tzipora, 18 Sep: "it's too big on the choreographers, just
     because it gets to look like it's shouting... let's try dropping the size of
     the choreographer card names to be two points bigger than the size that they
     are on the dance cards. It's like Jerome Robbins shouting at me."

     The name on a dance card is .work-card-choreographer at 12px, so two points
     bigger is 14. Measured before changing it, because the instruction is
     relative to another element and guessing would have given 14 or 20.

     ⚠ Worth knowing if it still shouts: this is ALREADY smaller in pixels than
     the 18px work title beside it. What makes it loud is 700 weight, all caps and
     1.92px of tracking — the treatment she asked for on 17 September. The next
     lever is tracking or weight, not another two points. */
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--highlight);
  display: block;
  -webkit-line-clamp: none;
  overflow: visible;
}

.work-card-photo {
  width: 100%;
  aspect-ratio: 4 / 3;
  flex-shrink: 0;
  overflow: hidden;
}
.work-card-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* §7 wanted a way to steer this crop, and DIG-004977 is it. The card takes a
     4:3 crop from a photograph shot for a 1.92:1 hero, so roughly 40% of the
     frame goes; --cd-focal moves what survives. Unset falls back to centre, so
     nothing anyone has already looked at moves on its own. */
  object-position: var(--cd-focal, center);
  display: block;
}

/* §4 "Hover must not move anything": absolutely positioned, so it takes no
   layout space and the card is exactly as tall hovered as not. Must never be a
   border-bottom or anything in normal flow — that grows the card on hover and
   pushes the rest of the grid down. */
.work-card-rule {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 4px;
  background: transparent;
  transition: background .18s;
  pointer-events: none;
}
.work-card:hover .work-card-rule { background: var(--highlight); }

/* §4, both accepted consequences: photo tops do not align across a row, and row
   heights vary down the page. DO NOT add grid-auto-rows or a min-height to
   "fix" either — that restores a tall empty box under every title-only card,
   which is the exact thing this design removes. The existing .catalogue-grid
   already matches §8: 4 / 3 / 2 columns at 1024px and 768px, 24px gap. */

@media (max-width: 768px) {
  .work-card-title { font-size: 16px; }
  .work-card-choreographer {
    font-size: 11px;
    height: 31px;                     /* 11 x 1.4 x 2 */
  }
}

/* ============================================================
   CATALOGUE CARD — SIDE THUMBNAIL AMENDMENT
   concert-dance-card-side-thumb-amendment.md, approved by Tzip 2026-10-01.
   ============================================================
   Supersedes the title-top/photo-bottom layout above, but ONLY on the
   catalogue page's own two grids (#choreo-grid, #dw-grid inside
   #page-catalogue). The choreographer bio page's related-works grid
   (#bio-dw-grid, node--mti-choreographer.html.twig) uses the IDENTICAL
   .work-card markup and classes and is NOT part of this amendment — her
   email only names "both catalogue tabs." Every selector below is scoped
   under #page-catalogue for exactly that reason: a bare .work-card-* rule
   here would silently reach the bio page too.

   List view is untouched by construction, not by care taken here: switchView()
   (concert-dance.js) wipes each card's innerHTML and rebuilds it with entirely
   different .list-* classes when switching to list, so none of these
   .work-card-* selectors can ever match a list row regardless of what they say. */

#page-catalogue .catalogue-grid { grid-template-columns: repeat(3, 1fr); }

/* Deliberately a SEPARATE breakpoint from the existing @media(max-width:1024px)
   gutter switch above — one pixel narrower, so 1024px (iPad landscape) stays
   3 columns with the 32px tablet gutter already set there. Do not merge these
   two queries into one. */
@media (max-width: 1023px) {
  #page-catalogue .catalogue-grid { grid-template-columns: repeat(2, 1fr); }
}

#page-catalogue .work-card {
  /* The amendment's own min-height, intentionally reintroducing what the
     comment above this block warned against — that warning was about the OLD
     design, where an uneven column layout was the point. This design wants
     equal-height rows. */
  flex-direction: row;
  min-height: 148px;
}

/* Head becomes the text column; the spacer (which only ever existed to push
   the photo to the bottom of a column) has nothing left to do in a row and is
   hidden rather than removed from the markup, per "map to existing markup." */
#page-catalogue .work-card-head { flex: 1; min-width: 0; padding: 16px; }
#page-catalogue .work-card-spacer { display: none; }

#page-catalogue .work-card-title { font-family: 'Nunito Sans', sans-serif; font-weight: 400; line-height: 1.35; }

/* Spec names "Josefin Sans" explicitly, twice, for the choreographer name —
   not var(--brandon). That token changed meaning today (DIG-004918): it used
   to resolve to the Josefin Sans stand-in and now resolves to the real
   licensed Brandon Grotesque. Following the token here would silently swap
   this typography to a face she did not ask for in this amendment, so these
   two selectors hardcode the face instead. */
#page-catalogue .work-card-choreographer,
#page-catalogue .choreo-card-name { font-family: 'Josefin Sans', sans-serif; }

#page-catalogue .work-card-photo {
  width: 45%;
  aspect-ratio: auto;   /* cancel the 4:3 ratio above; height comes from the row via flex stretch */
  background: var(--main);
}
#page-catalogue .work-card-photo img { mix-blend-mode: luminosity; }
#page-catalogue .work-card:hover .work-card-photo img,
#page-catalogue .work-card:focus-visible .work-card-photo img { mix-blend-mode: normal; }

/* mix-blend-mode cannot animate (spec: "the switch to colour is instant, do
   not fake a fade") — no transition added here, deliberately. */

/* Keyboard parity with :hover for the shadow + bottom bar, same pattern
   .touch-active already uses above for the generic .card. The colour reveal
   above already covers :focus-visible; these two are the rest of the "hover
   OR focus-visible" card state the spec's Card Shell table asks for. */
#page-catalogue .work-card:focus-visible { box-shadow: 8px 8px 20px 2px var(--shadow-hover); border-color: var(--highlight); }
#page-catalogue .work-card:focus-visible .work-card-rule { background: var(--highlight); }

@media (max-width: 768px) {
  #page-catalogue .catalogue-grid { grid-template-columns: 1fr; gap: 16px; }
  #page-catalogue .work-card { min-height: 118px; }
  #page-catalogue .work-card-head { padding: 12px; }
}

/* ============================================================
   LIST VIEW ROWS — concert-dance-list-view-handoff.md §5
   ============================================================
   Locked 2026-09-08, built 2026-09-15 (DIG-004976). Two row types, one shell:
   choreographers carry a circular portrait, works a rectangular thumbnail, and
   a work with no photograph carries NO thumbnail at all — the text moves left
   into that space (§6, and the same no-substitution rule as the grid card).

   SCOPE: her document also specced search-results rows. Her 9 September overlay
   spec §6 WITHDRAWS that section — the overlay is the only search surface —
   so .search-kind and the .search-results padding rules are deliberately not
   built here. Search rows are DIG-005000. */

.list-view { display: flex; flex-direction: column; }

.list-row {
  display: flex;
  align-items: center;
  padding: 30px 0;
  border-bottom: 1px solid var(--main);
  cursor: pointer;
  /* §3: border-color ONLY. Animating border-width reintroduces the movement
     during the transition even though the two endpoints match. */
  transition: border-color .15s;
  text-decoration: none; color: inherit;
}

/* §3 "Hover must not move the page": the divider goes 1px -> 3px, so the
   padding drops by the same 2px and the row's total height is identical in both
   states. 30 + 1 = 31; 28 + 3 = 31. Without this every row below the hovered
   one is nudged down 2px. */
.list-row:hover {
  border-bottom: 3px solid var(--highlight);
  padding-bottom: 28px;
}
/* DIG-005073. Her spec §5 asks for `font-weight: 500` on hover, and her spec §3
   asks that hover "must not move the page". Those two cannot both hold: a weight
   change alters glyph advance widths, so the text reflows inside the row. Measured
   on live, three catalogue rows: 400 -> 500 moved the title +3.81px, +3.76px and
   -0.47px — it grows on some titles and shrinks on others, which is exactly why it
   reads as "jumping" rather than as growing.

   So the weight stays 400 and the thickening is drawn by stroking the glyph outline
   instead. Same visual step up, and the advance widths are untouched: the same three
   titles measured 0.00px of movement with the stroke applied. §3 is the load-bearing
   rule here — §5 wanted the look, not the reflow.

   The stroke is painted inside and outside the outline, so 0.3px reads close to the
   ~0.35px of extra stem width Effra puts between 400 and 500 at 18px. */
.list-row:hover .list-title {
  font-weight: 400;
  -webkit-text-stroke: 0.3px currentColor;
}

/* §1: the gap belongs to the IMAGE, not the row. `gap` on .list-row would leave
   a phantom indent on a work with no thumbnail — the exact thing §6 forbids —
   and would force both row types to share one value when they need different
   ones to land text at the same place. */

/* Dance work thumbnail — 130 + 24 = text starts at 154px */
.list-thumb {
  width: 130px;
  height: 98px;                     /* 4:3 */
  margin-right: 24px;
  border-radius: 3px;
  flex-shrink: 0;
  overflow: hidden;
  transition: box-shadow .2s;
}
.list-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* The list view MOVES this <img> out of the grid card rather than rebuilding
     it, so the inline --cd-focal travels with it and this needs no plumbing of
     its own (DIG-004977). */
  object-position: var(--cd-focal, center);
  display: block;
}

/* Choreographer portrait — 120 + 34 = text starts at 154px too. The circle is
   smaller than the thumbnail precisely so its larger gap makes up the 10px and
   both row types align. §1: "a relationship, not two independent numbers" —
   change a width and you change its gap to match. Portraits are required
   (blocker B12), so there is no missing state here. */
.list-portrait {
  width: 120px;
  height: 120px;
  margin-right: 34px;
  border-radius: 50%;
  flex-shrink: 0;
  overflow: hidden;
  transition: box-shadow .2s;
}
.list-portrait img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Centred crops cut foreheads, which is why this one defaults to `center top`
     rather than `center` — the fallback is deliberately NOT the same as the
     other three. --cd-focal overrides it per photograph (DIG-004977). */
  object-position: var(--cd-focal, center top);
  display: block;
}

.list-row:hover .list-thumb,
.list-row:hover .list-portrait { box-shadow: 8px 8px 20px 1px var(--shadow-hover); }

.list-text { min-width: 0; max-width: 720px; }

.list-title {
  font-family: var(--effra); font-weight: 400; font-size: 18px;
  color: var(--highlight); line-height: 1.4;
  min-width: 0; word-wrap: break-word; text-decoration: none;
  /* No clamp. Same rule as the grid card: titles are legally significant and
     are never truncated. The 3-line clamp that used to live here is gone. */
}

.list-choreographer {
  margin-top: 6px;
  font-family: var(--brandon);
  font-weight: 700;
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--highlight);
}

/* Suppressed on choreographer-specific views only — the text stays in the DOM.
   #page-bio, not her #page-choreographer, which does not exist here
   (DIG-004974 §B). One rule covers the card and the row so the two views
   cannot drift apart, which is why her spec puts them together. */
#page-bio .list-choreographer { display: none; }

/* Homepage news cards — wider image variant */
.news-card-home {
  display: flex; overflow: hidden;
  border: 1.5px solid var(--highlight); border-radius: 5px;
  background: white; transition: box-shadow 0.2s; cursor: pointer;
  margin-bottom: 0;
}

.news-card-home .news-card-img-placeholder {
  width: 40%; align-self: stretch;
}
.news-card-home .news-card-content { }
/* Neither news-card variant is an underlined link (DIG-004995). The homepage
   anchor carried an inline text-decoration:none and the listing anchor carried
   nothing, so /news underlined every card while the homepage did not. Declared
   here for both so a third variant cannot pick the bug up again; the inline
   style on .news-card-home in featured-news.html.twig is now redundant. */
.news-card, .news-card-home { text-decoration: none; color: inherit; }
.news-card {
  display: flex; overflow: hidden;
  border: 1.5px solid var(--highlight); border-radius: 5px;
  background: white; transition: box-shadow 0.2s; cursor: pointer;
  margin-bottom: 24px;
}

.news-card-img-placeholder {
  width: 40%; flex-shrink: 0;
  background: linear-gradient(135deg, var(--pale) 0%, rgba(16,98,158,0.08) 100%);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; align-self: stretch;
}
/* The news card crops ~40% of the frame at 40% width; --cd-focal steers what
   survives, unset falls back to centre (DIG-004977). Serves the news listing and
   the homepage featured strip, which share this class. */
.news-card-img-placeholder img { width: 100%; height: 100%; object-fit: cover; object-position: var(--cd-focal, center); display: block; }
.news-card-img-placeholder .material-icons { color: var(--highlight); opacity: 0.3; font-size: 32px; }
.news-card-content { flex: 1; display: flex; flex-direction: column; gap: 8px; padding: 30px 34px; align-items: flex-start; }
.news-card-content .btn { margin-top: 10px; }
.news-card-divider { width: 44px; height: 2px; background: var(--main); flex-shrink: 0; }

/* ============================================================
   DANCE WORK PAGE
   ============================================================ */
.dw-header-card {
  border: 1.5px solid var(--highlight); border-radius: 5px;
  overflow: hidden; display: flex;
}
/* With photo: image left ~40%, content right */
.dw-header-photo {
  width: 40%; flex-shrink: 0;
  background: linear-gradient(135deg, var(--pale) 0%, rgba(16,98,158,0.1) 100%);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; align-self: stretch; min-height: 260px;
}
/* No object-position here — .cd-hero-img owns hero framing (DIG-004637). */
.dw-header-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dw-header-photo .material-icons { font-size: 80px; color: var(--highlight); opacity: 0.25; }

/* No photo: full width content only */
.dw-header-card.no-photo { display: block; }
.dw-header-card.no-photo .dw-header-content { padding: 32px; }

.dw-header-content { flex: 1; padding: 28px 32px; display: flex; flex-direction: column; gap: 16px; }
.dw-header-content .h3 { font-family: var(--effra); font-weight: 700; font-size: 28px; color: var(--highlight); line-height: 1.2; }
.dw-divider { width: 80px; height: 2px; background: var(--highlight); margin: 10px 0; }
.dw-choreographer { display: flex; align-items: center; gap: 12px; }
.dw-portrait-placeholder {
  width: 80px; height: 80px; border-radius: 50%; flex-shrink: 0;
  background: var(--pale); display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.dw-portrait-placeholder img { width: 100%; height: 100%; object-fit: cover; object-position: center; }
.dw-portrait-placeholder .material-icons { font-size: 40px; color: var(--highlight); opacity: 0.5; }
.choreographer-name-dw { font-family: var(--effra); font-weight: 500; font-size: 30px; color: var(--highlight); }

/* Metadata band — inside the unified card, below the header row */
.dw-metadata-band {
  border-top: 1.5px solid var(--highlight);
  padding: 16px 24px;
  display: flex; justify-content: space-evenly; align-items: center;
  background: white;
}
.dw-meta-item {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--effra); font-weight: 700; font-size: 22px; color: var(--highlight);
}
.dw-meta-icon {
  width: 36px; height: 36px; border-radius: 50%; border: 2.5px solid var(--highlight);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.dw-meta-icon .material-icons { font-size: 20px; color: var(--highlight); }
button.dw-meta-asterisk {
  background: transparent; border: 0; padding: 0 2px;
  font-family: var(--effra); font-weight: 700; font-size: 14px;
  color: var(--highlight); margin-left: 2px; line-height: 1;
  vertical-align: super; cursor: pointer;
  transition: opacity 0.15s ease;
}
button.dw-meta-asterisk:hover,
button.dw-meta-asterisk:focus-visible { opacity: 0.7; outline: 2px solid var(--highlight); outline-offset: 2px; border-radius: 2px; }
.dw-footnote {
  /* 2026-07-15 review §13: the asterisk note must be visible on page load, not
     hidden until the asterisk is clicked. Shown by default; the meta-band
     asterisk still scrolls to it and re-plays the highlight as an enhancement. */
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--effra); font-weight: 400; font-size: 15px;
  color: var(--main); font-style: italic;
  margin-bottom: 20px; padding: 10px 14px;
  background: var(--pale); border-left: 3px solid var(--highlight);
  border-radius: 0 4px 4px 0;
}
.dw-footnote.revealed { animation: dwFootnoteReveal 0.6s ease; }
@keyframes dwFootnoteReveal {
  0%   { background: color-mix(in srgb, var(--highlight) 35%, var(--pale)); }
  100% { background: var(--pale); }
}
.dw-footnote-marker {
  font-weight: 700; font-size: 14px; color: var(--highlight);
  flex-shrink: 0; vertical-align: super;
}

/* License band — bottom of unified card */
.dw-license-band {
  background: var(--highlight);
  padding: 20px 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  /* DIG-005157. 4px, not 5px: the card around this band has a 5px radius and a
     1px border, and the curve INSIDE a border is the radius minus the border. A
     5px fill inside it left a white sliver at both bottom corners, at every width
     (Tzipora zoomed in on it, 23 Sep). A sweep of ten pages at 375 and 1440 found
     no other box with the same mismatch. */
  border-radius: 0 0 4px 4px;
}
.license-band-text { font-family: var(--effra); font-weight: 500; font-size: 22px; color: white; }
.dw-license-band .btn-primary-inv { flex-shrink: 0; }

/* Standalone license band (used on other pages) */
.license-band {
  background: var(--highlight); padding: 20px var(--page-pad);
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 12px;
}

/* Reviews */
.review-item { padding: 40px 0; border-bottom: 1px solid var(--highlight); }
.review-item:last-child { border-bottom: none; }
.review-item:first-child { padding-top: 0; }
.review-stars { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; }
.review-stars .material-icons { font-size: 22px; color: var(--highlight); }
.review-stars .material-icons.empty { color: var(--pale-full); }
.review-stars-quote { font-family: Georgia, serif; font-size: 28px; color: var(--highlight); line-height: 1; }
.review-text-quote { font-family: Georgia, serif; font-size: 28px; color: var(--black); line-height: 1; vertical-align: middle; }
.review-pub { font-family: var(--effra); font-weight: 900; font-size: 15px; text-transform: uppercase; color: var(--black); margin-top: 20px; }
.review-date { font-family: var(--effra); font-weight: 400; font-size: 15px; color: var(--black); }
.review-link { margin-top: 8px; }
.review-link a { font-family: var(--effra); font-weight: 400; font-size: 15px; color: var(--highlight); text-decoration: underline; }

.learn-more-card {
  border: 1.5px solid var(--highlight); border-radius: 5px;
  overflow: hidden; display: flex; margin: 48px auto; background: white;
  max-width: 800px;
}
.learn-more-logo-placeholder {
  width: 40%; flex-shrink: 0; min-height: 240px;
  background: linear-gradient(135deg, var(--pale) 0%, rgba(16,98,158,0.08) 100%);
  display: flex; align-items: center; justify-content: center;
}
.learn-more-logo-placeholder .material-icons { font-size: 48px; color: var(--highlight); opacity: 0.35; }
.learn-more-content { flex: 1; padding: 24px 34px; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.learn-more-title { font-family: var(--effra); font-weight: 700; font-size: 24px; color: var(--main); }
.learn-more-divider { width: 44px; height: 2px; background: var(--main); margin: 10px 0; }
.learn-more-desc { font-family: var(--effra); font-weight: 400; font-size: 20px; color: var(--black); }
.learn-more-content .btn { margin-top: 10px; }

/* ============================================================
   FAQ
   ============================================================ */
.faq-item { border-bottom: 2px solid rgba(11,44,77,0.2); transition: border-color 0.3s ease, border-width 0.3s ease; }
/* DIG-005117. Tzipora, 18 Sep: "at mobile, the divider line after I open it is
   retaining its active state... clicking out of them should restore them to
   inactive totally."

   The cause is sticky :hover, not the open/close logic — toggleFaq() already
   removes .open from the item immediately on close. On a touch device a tap leaves
   :hover applied until you tap something else, so the 3px stays. Gating the rule
   behind `hover: hover` means it only ever applies to a real pointer.

   Same family as DIG-005102 (the carousel spinner that never cleared), which she
   spotted the resemblance to herself — a state that is entered on interaction and
   never left. Different mechanism though: that one was a class nobody removed,
   this one is a pseudo-class the browser will not drop. */
@media (hover: hover) {
  .faq-item:hover { border-bottom: 3px solid var(--main); }
}
.faq-item.open { border-bottom: 3px solid var(--main); }
.faq-question {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 38px 0; cursor: pointer; gap: 48px;
  font-family: var(--effra); font-weight: 300; font-size: 18px; color: var(--black);
  transition: color 0.25s ease;
}
.faq-question-text { max-width: 720px; }
.faq-question:hover { color: var(--main); }
.faq-question.open { font-weight: 700; color: var(--main); }
.faq-question .material-icons { color: var(--main); font-size: 22px; transition: transform 0.3s ease; flex-shrink: 0; }
.faq-question.open .material-icons { transform: rotate(180deg); }
.faq-answer-wrap { max-height: 0; overflow: hidden; transition: max-height 0.7s cubic-bezier(0.4, 0, 0.2, 1); }
.faq-answer-wrap.open { max-height: 1000px; transition: max-height 0.85s cubic-bezier(0.4, 0, 0.2, 1); }
/* DIG-005117. Tzipora, 18 Sep: "I want to add another 25px padding in between
   the bottom of the content in the drawer in the FAQ before the line element,
   before the divider." 20 + 25 = 45. */
.faq-answer-inner { padding: 16px 0 45px; }

/* ============================================================
   FILTER & SORT
   ============================================================ */
.filter-controls { display: flex; align-items: center; gap: 16px; margin-top: 40px; margin-bottom: 40px; flex-wrap: wrap; }
.filter-controls-right { display: flex; align-items: center; gap: 16px; margin-left: auto; }
/* Applied-filter summary row: chips + Clear All, shown ABOVE the toolbar so
   activating filters pushes the toolbar down (Tzipora 2026-05-27 ~13:00). */
.filter-applied { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 40px 0 0; }
.filter-applied[style*="flex"] + .filter-controls { margin-top: 16px; }
.filter-active-tags { display: flex; flex-wrap: wrap; gap: 10px; margin: 0; }
.filter-clear-all {
  font-family: var(--brandon); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 14px; color: var(--main);
  background: none; border: none; padding: 4px 2px; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.filter-clear-all:hover { color: var(--highlight); }
.filter-active-tag {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--navy); color: white;
  font-family: var(--omnes); font-weight: 300; font-size: 17px;
  padding: 7px 18px; border-radius: 20px; cursor: pointer;
  border: 1.5px solid var(--navy); transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.filter-active-tag:hover { background: rgba(30,145,206,0.08); color: var(--navy); border: 1.5px solid var(--navy); }
.filter-active-tag .material-icons { font-size: 14px; }

/* Filter panel */
.filter-btn-wrap { display: contents; }
.filter-btn-label { display: none; }
.filter-panel {
  background: white; border: 1.5px solid var(--main); border-radius: 5px;
  padding: 32px; margin-bottom: 24px; display: none;
}
.filter-panel.open { display: block; }
.filter-panel-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 24px;
}
.filter-panel-close {
  background: none; border: none; cursor: pointer; padding: 4px;
  display: flex; align-items: center; justify-content: center;
}
.filter-panel-close .material-icons { font-size: 24px; color: var(--main); }
.filter-panel-close:hover .material-icons { color: var(--highlight); }
.filter-apply-band { display: none; }

.filter-section { margin-bottom: 0; padding: 40px 0; border-top: 1px solid var(--rule); }
.filter-section:first-of-type { border-top: none; padding-top: 20px; }
.filter-section-head {
  font-family: var(--brandon); font-weight: 700; font-size: 16px;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--main); margin-bottom: 16px;
}
.filter-radios { display: flex; flex-wrap: wrap; gap: 16px 32px; margin-top: 20px; margin-bottom: 40px; }
.filter-radio {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-family: var(--effra); font-size: 18px; color: var(--black);
}
.filter-radio-circle {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--main); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: white; transition: background 0.15s;
}
.filter-radio:hover .filter-radio-circle { background: rgba(30,145,206,0.08); }
.filter-radio.selected .filter-radio-circle { border-color: var(--main); background: white; }
.filter-radio.selected .filter-radio-circle::after {
  content: ''; width: 14px; height: 14px; border-radius: 50%; background: var(--navy);
}
.filter-radio.selected { color: var(--black); font-weight: 400; }
.filter-pills { display: flex; flex-wrap: wrap; gap: 10px; }
.filter-pill {
  font-family: var(--omnes); font-weight: 300; font-size: 17px;
  color: var(--main); border: 1.5px solid var(--main); border-radius: 20px;
  padding: 7px 18px; cursor: pointer; background: white; transition: all 0.15s;
  display: inline-flex; align-items: center; gap: 6px;
}
.filter-pill:hover { background: var(--pale); }
.filter-pill.selected { background: var(--navy); color: white; border-color: var(--navy); font-weight: 500; }
.filter-pill.selected::after { content: ' ✕'; font-size: 12px; }
.filter-yesno { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.filter-yesno:last-child { margin-bottom: 0; }
.filter-yesno-label { font-family: var(--effra); font-size: 18px; color: var(--black); flex: 1; }
.filter-yesno-options { display: flex; gap: 24px; }

.sort-wrap { position: relative; display: inline-block; }
.sort-btn {
  font-family: var(--brandon); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 15px; color: var(--main);
  border: 2px solid var(--main); background: white; padding: 8px 16px;
  border-radius: 4px; cursor: pointer; display: flex; align-items: center; gap: 8px;
  line-height: 1;
}
.sort-dropdown {
  position: absolute; top: 100%; left: 0; right: 0;
  background: white; border: 2px solid var(--main); border-top: none;
  z-index: 10; display: none;
}
.sort-dropdown.open { display: block; }
.sort-option {
  font-family: var(--brandon); font-weight: 700; text-transform: uppercase;
  font-size: 15px; color: var(--main); padding: 10px 16px; cursor: pointer;
}
.sort-option:hover { background: var(--pale); }

.view-toggle { display: flex; gap: 4px; }
.toggle-btn {
  width: 36px; height: 36px; border: 2px solid var(--main);
  background: white; border-radius: 4px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; color: var(--main);
}
.toggle-btn.active { background: var(--main); color: white; }
.toggle-btn .material-icons { font-size: 18px; }

.breadcrumb {
  font-family: var(--brandon); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; font-size: 15px; color: var(--main);
  text-decoration: none; display: inline-flex; align-items: center; gap: 4px;
  margin-bottom: 24px; cursor: pointer;
}
.breadcrumb:hover { text-decoration: underline; }

/* ============================================================
   PHOTO CREDIT STRIP — "editorial light" (DIG-004692)
   Spec: hero-credit-handoff §2–§5.
   ============================================================ */
/* A white strip joined BELOW the hero, carrying a 1px highlight-blue hairline
   along its top edge. `border-top` rather than a pseudo-element so the hairline
   cannot be separated from the strip by a margin. */
.cd-credit {
  background: var(--white);
  border-top: 1px solid var(--highlight);
  /* Cross-fade on rotation: opacity only, so the strip never changes height and
     the content below it does not move while the text swaps (§2, ~300ms). */
  opacity: 1;
  transition: opacity 0.3s ease;
}
.cd-credit.is-fading { opacity: 0; }
/* `hidden` on a flex/block element needs the explicit `display: none` — a
   `display` declaration in the component would otherwise beat the UA rule and
   a credit-less carousel would show an empty white band with a hairline. */
.cd-credit[hidden] { display: none; }
/* DIG-005112. "Now this photo credit switches, and it's very distracting while I
   had the nav open, because it's basically right underneath the nav. I wonder if
   there's an if-then... where if the nav is open, the credit for the hero is
   hidden." Above the mobile breakpoint only — she was explicit that it does not
   matter on phones, where the overlay is full screen and covers it anyway.
   Visibility, not display, so the strip keeps its reserved height and nothing
   below it moves when the nav opens. */
@media (min-width: 769px) {
  body:has(.nav-overlay.open) .cd-credit { visibility: hidden; }
}
.cd-credit-inner {
  max-width: var(--content-max); margin: 0 auto;
  padding: 12px var(--credit-pad);
  /* baseline, not center: the kicker is small caps beside larger name text */
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  font-family: var(--effra); font-weight: 300; font-size: 15px;
  color: var(--black); line-height: 1.3;
}
/* 12px / --highlight, not 13px / --main. The credit spec (§2) never gives the
   kicker its own size or colour; the VIDEO spec does, in §4, by describing the
   WATCH kicker as "the same treatment as the photo-credit PHOTO kicker
   (Josefin/Brandon, 700, uppercase, ls 0.12em, 12px, --highlight)". So §4
   specifies both kickers, and PHOTO shipped at the wrong two values this
   morning. #10629e on white is 6.44:1, so this stays AA at 12px bold. */
.cd-credit-kicker {
  font-family: var(--brandon); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; font-size: 12px; color: var(--highlight);
}
.cd-credit-name { font-weight: 700; }

/* DIG-005112. Three things about this strip, from the 18 September mobile pass.

   1. THE WORD "PHOTO" GOES, both breakpoints. "Let's also start by taking away the
      word photo. That might help a lot. We don't necessarily need it. It's pretty
      clear that the credit is for the photo above it." Hidden rather than cut from
      the two templates that emit it (node--mti-choreographer and hero-carousel),
      so it is one line to put back — hero-credit-handoff §2/§4 still specify it.

   2. THE STRIP MUST NOT CHANGE HEIGHT AS THE CAROUSEL CYCLES. "It keeps adjusting
      the height of the page as it cycles, so the copy keeps moving... that's super
      annoying from a user perspective." The cross-fade was already designed not to
      move anything — see the note on .cd-credit above — but `.cd-credit-inner` is
      `flex-wrap: wrap`, so a credit long enough for two lines grows the strip and
      everything below it shifts. Reserving two lines makes a one-line and a
      two-line credit occupy the same height, which is the actual fix; her
      suggestion (smaller type) only makes two lines rarer.

   3. Her own note on this one: "that's really my fault because I never really
      actually put thought into how this would work." */
.cd-credit-kicker { display: none; }
.cd-credit-inner { min-height: 2.6em; }
.cd-credit-company { font-weight: 300; color: var(--grey); }
/* Hides when absent (§2) — the JS sets [hidden] rather than emptying the node,
   so the flex gap beside the name goes with it. */
.cd-credit-company[hidden] { display: none; }

/* ============================================================
   VIDEO — click-to-load facade (DIG-004694)
   Spec: specs/concert-dance-video-handoff.md §1, §3-§5.
   ============================================================ */
/* ONE play button, scaled by a single diameter variable. The triangle is a
   ratio of the diameter (54%, §3) so it scales with either size automatically
   and neither size needs its own triangle rule. */
.cd-play {
  --play-d: 60px;
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: var(--play-d); height: var(--play-d);
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.9);
  background: rgba(255,255,255,0.18);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.35);
  cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.18s ease;
}
.cd-play:hover { transform: translate(-50%, -50%) scale(1.06); }
.cd-play:focus-visible { outline: 3px solid var(--highlight); outline-offset: 3px; }
/* Triangle drawn with clip-path on a square sized off the diameter, so one
   declaration serves 92px, 68px and 60px. Nudged right ~6% of the diameter for
   optical centering — a centred triangle reads as sitting left. */
.cd-play::before {
  content: ''; display: block;
  width: 54%; height: 54%;
  margin-left: 6%;
  background: #fff;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
/* The glass fill depends on backdrop-filter. Where it is unsupported the 0.18
   white reads as almost nothing over a bright still, so raise the opacity and
   drop the blur rather than leaving an invisible button (spec §1). */
@supports not ((backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px))) {
  .cd-play { background: rgba(255,255,255,0.32); }
}

/* Poster frame, shared by both surfaces. 16/9 at both. */
.cd-video-facade {
  position: relative; width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden; background: var(--main);
  border: 0; padding: 0; display: block; cursor: pointer;
}
.cd-video-facade img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Scrim for legibility over bright stills (§3). Sits above the poster and
   below the button. */
.cd-video-facade::after {
  content: ''; position: absolute; inset: 0;
  background: #04121f; opacity: 0.15; pointer-events: none;
}
/* No poster uploaded: a branded ground rather than a third-party thumbnail
   fetch. See the module's facade builder for why we do not hotlink the
   provider's still. */
.cd-video-facade--noposter {
  background: linear-gradient(135deg,#0b2c4d 0%,#04304d 60%,#10629e 100%);
}

/* ---- Surface 1: description tab (§4) ---- */
/* First flow element in the tab and carries NO top margin — the tab's own
   25px padding-top is the only offset, so the start line is identical with or
   without a video and nothing shifts when one is absent. */
.dw-desc-video { margin: 0 0 32px; }
.dw-desc-video .cd-video-facade { border-radius: 4px; }
.dw-desc-video .cd-play { --play-d: 92px; }
/* The iframe replaces the facade inside the same 16:9 box, so the frame does
   not resize on play. */
.dw-desc-video .cd-video-frame {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  border-radius: 4px; overflow: hidden; background: #000;
}
.dw-desc-video .cd-video-frame iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}
/* Caption renders only when the link carries a title. video -> caption 12px;
   the 32px below is on .dw-desc-video, so it holds whether or not the caption
   is present. */
.cd-video-caption { margin-top: 12px; }
.cd-video-kicker {
  font-family: var(--brandon); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; font-size: 12px; color: var(--highlight);
  margin-right: 8px;
}

/* ---- Surface 2: gallery strip (§5) ---- */
/* The video is the first .gallery-item in the strip, so it inherits the strip's
   220px height (160px on mobile) and the item's 3px radius, hover border and
   shadow. Width follows from the aspect ratio: 391px at 220px, 284px at 160px.
   Do NOT add the desc frame's 4px radius here (§3). */
.gallery-item--video { aspect-ratio: 16 / 9; width: auto; }
.gallery-item--video .cd-video-facade { height: 100%; aspect-ratio: auto; }
.gallery-item--video .cd-play { --play-d: 60px; }

/* Lightbox video mode (§6) — a 16:9 box inside the existing stage. */
.lightbox-main-video {
  width: min(100%, 1200px); aspect-ratio: 16 / 9;
  position: relative; background: #000;
}
.lightbox-main-video iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}

/* ---- Clearspace (§4) ----
   Measured from the credit strip, which is the LAST child of
   .carousel-placeholder once concert-dance.js has moved the pair in — so the
   selectors hang off the placeholder, not off .cd-credit, which is nested
   inside it and therefore not a sibling of what follows.

   credit -> breadcrumb 8px; credit -> H1 48px with no breadcrumb, 30px with. */
.carousel-placeholder + .breadcrumb-row { padding-top: 8px; }
.carousel-placeholder + .page-header { padding-top: 48px; }
.breadcrumb-row + .page-header { padding-top: 30px; }
/* Inside a row the ROW owns the gap to whatever follows, so the standalone
   breadcrumb's own 24px bottom margin has to come off or it stacks on top of
   the 30px above and the measured clearspace is 54px. §5's 24px still applies
   to a breadcrumb that is not in a row. */
.breadcrumb-row .breadcrumb { margin-bottom: 0; }
/* DIG-005121. Tzipora, 18 Sep, said twice and with a number: "the top padding in
   between the breadcrumb and the content above it — whether it's the photo credit
   from the hero, whether it's the bottom of the nav overlay logo — it feels too
   tall. And I would like that distance at mobile only to come down by about 25px."

   Measured on live at 375x667, /news/*: .breadcrumb-row padding-top 58px, and 64px
   from the carousel placeholder's bottom edge to the breadcrumb's ink. 58 - 25 = 33.

   ⚠ The space BELOW the breadcrumb is deliberately untouched. She named it as
   already right — "the distance between the breadcrumb and the content beneath it
   is excellent" — and it was set twice on purpose (50px on 16 Sep, and again on
   17 Sep when the news article was un-nested from this row, DIG-005103). */

/* The choreographer strip is a direct sibling — no placeholder in between. */
.cd-credit--static + .breadcrumb-row { padding-top: 8px; }
.cd-credit--static + .page-header { padding-top: 48px; }
/* A conditional breadcrumb that is NOT shown (DIG-004879) still sits between the
   credit strip and the H1, so the row-to-header rule above would hold the gap at
   30px — the "with a breadcrumb" figure — on a page that has none. Restore the
   48px the spec asks for when there is nothing in the row. */
.breadcrumb-row.cd-crumb-hidden { display: none; }
.breadcrumb-row.cd-crumb-hidden + .page-header { padding-top: 48px; }

/* ---- Breadcrumb clearspace, +50px (DIG-005088) ----
   Tzipora, 16 September 2pm check-in:
     "Anywhere a breadcrumb exists we need more padding between the breadcrumb and
      the dropped SVG logo... 50px. Let's try making it a standard rule — wherever
      the breadcrumb exists is just an additional 50px on top of it. Whatever the
      top padding is, just add 50px to it."
   Worst below full width: "it's fine when the screen is really wide, but it's not
   fine at all on smaller sizes."

   Added as a separate block rather than by editing the §4 numbers above, so the
   measured clearspace from her own spec stays legible and this is one block to
   revert if 50 turns out to be too much. She expects to look again — these are her
   starting numbers, not a final value. 8 + 50 = 58. */
.carousel-placeholder + .breadcrumb-row,
.cd-credit--static + .breadcrumb-row { padding-top: 58px; }
/* DIG-005121, and it has to sit AFTER the rule above. Tzipora, 18 Sep, twice and
   with a number: "the top padding in between the breadcrumb and the content above
   it... feels too tall. I would like that distance at mobile only to come down by
   about 25px." Measured 58px, so 33.

   ⚠ An earlier attempt put this in a media query EARLIER in the file. Same
   specificity, so the 58px above won on source order and live still measured 58px.
   Equal-specificity overrides have to come later, not merely be more specific-looking.

   The space BELOW the breadcrumb is untouched on purpose — she called it "excellent". */
@media (max-width: 768px) {
  .breadcrumb-row,
  .cd-credit--static + .breadcrumb-row,
  .carousel-placeholder + .breadcrumb-row { padding-top: 33px; }
}
/* A breadcrumb with no credit strip above it has no padding-top of its own, so the
   50px is the whole rule here — this is the no-carousel case she was looking at. */
.breadcrumb-row { padding-top: 50px; }

/* ============================================================
   FORMS
   ============================================================ */
.form-group { margin-bottom: 20px; }
.form-label { font-family: var(--effra); font-weight: 400; font-size: 17px; color: var(--black); display: block; margin-bottom: 6px; }
.form-input {
  width: 100%; height: 40px; border: 2px solid var(--main); border-radius: 4px;
  padding: 0 12px; font-family: var(--omnes); font-size: 16px; color: var(--black);
  background: white; outline: none; transition: border-color 0.2s, color 0.2s;
}
.form-input::placeholder { font-style: italic; color: rgba(4,48,77,0.5); font-weight: 300; }
.form-input:focus { border-color: var(--highlight); color: var(--highlight); caret-color: var(--highlight); }
.form-input.error { border-color: var(--error); color: var(--error); }
.form-subhead { font-family: var(--effra); font-weight: 700; font-size: 18px; color: var(--black); margin-bottom: 12px; }
.checkbox-wrap { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; margin-bottom: 12px; }
.checkbox {
  width: 18px; height: 18px; border: 2px solid var(--main); border-radius: 3px;
  background: white; flex-shrink: 0; display: flex; align-items: center;
  justify-content: center; transition: background 0.15s; margin-top: 2px;
}
.checkbox.checked { background: var(--pale); }
.checkbox.checked::after { content: '✓'; font-size: 13px; color: var(--main); font-weight: 700; }
.checkbox-label { font-family: var(--effra); font-size: 18px; color: var(--black); line-height: 1.5; }
.required-note { font-family: var(--effra); font-weight: 300; font-style: italic; font-size: 14px; color: var(--black); margin-top: 4px; }

/* ============================================================
   GALLERY
   ============================================================ */
.gallery-group { margin-bottom: 0; }
.gallery-strip {
  display: flex; gap: 20px; overflow-x: auto;
  padding-bottom: 0; margin-bottom: 0;
  scrollbar-width: none;
}
.gallery-strip::-webkit-scrollbar { display: none; }
.gallery-strip .gallery-item:first-child { margin-left: 0; }
.gallery-nav { display: flex; gap: 8px; flex-shrink: 0; }
.gallery-nav.hidden { visibility: hidden; pointer-events: none; }
.gallery-item {
  height: 220px; flex-shrink: 0; cursor: pointer; border-radius: 3px;
  overflow: hidden; transition: box-shadow 0.2s;
  /* Reserve bottom border space to prevent layout jump */
  border-bottom: 3px solid transparent;
}
.gallery-item:hover {
  border-bottom: 3px solid var(--highlight);
  box-shadow: 8px 8px 20px 1px var(--shadow-hover);
}
.gallery-item img { height: 100%; width: auto; object-fit: contain; object-position: top left; display: block; }
.gallery-item-placeholder {
  height: 100%; width: 200px;
  background: linear-gradient(135deg, var(--pale) 0%, rgba(16,98,158,0.1) 100%);
  display: flex; align-items: center; justify-content: center;
}
.gallery-item-placeholder .material-icons { font-size: 48px; color: var(--highlight); opacity: 0.25; }

.gallery-meta {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 16px 0 20px;
}
.gallery-meta-text { display: flex; flex-direction: column; gap: 4px; }
.gallery-credit { font-family: var(--effra); font-weight: 700; font-size: 16px; color: var(--black); }
.gallery-credit-desc { font-family: var(--effra); font-weight: 300; font-size: 15px; color: var(--black); }
/* 2026-07-15 review §17: small "Photographer" role label in front of the name
   so it reads as the credit, not the subject. First pass — Tzipora to refine. */
.gallery-credit-role { font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: 11px; color: var(--main); margin-right: 4px; }
.gallery-nav-btn {
  width: 36px; height: 36px; border-radius: 50%;
  border: 2px solid var(--highlight); background: white;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: background 0.15s;
}
.gallery-nav-btn:hover { background: var(--pale); }
.gallery-nav-btn .material-icons { font-size: 18px; color: var(--highlight); }
.gallery-divider { height: 1px; background: var(--highlight); margin-top: 28px; margin-bottom: 40px; }
.line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.line-clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

/* SEARCH RESULTS */
/* SEARCH RESULT ROWS — concert-dance-overlay-handoff.md §6 (DIG-005000).
   Three of the eleven traps she flagged in §1 were live defects here, and all
   three are fixed below. The panel geometry — height, scrim, credit lane, the
   accordion nav — is the rest of that card and is NOT built yet. */
.search-result-item {
  display: flex;
  align-items: center;
  /* §6: row padding matches the catalogue list rows — 30px, up from a flat 20px,
     so a result row and a catalogue row share one rhythm. */
  padding: 30px 0;
  /* Trap: the divider was rgba(255,255,255,0.2), which measures about 1.8:1
     against #0b2c4d — below the 3:1 a UI component needs. 0.40 measures ~3.3:1. */
  border-bottom: 2px solid rgba(255,255,255,0.40);
  cursor: pointer;
  /* Trap: border-width was IN this transition. Animating it reintroduces the
     movement during the transition even though the endpoints match. Colour only. */
  transition: border-color 0.25s ease;
  /* NO `gap` — the gap belongs to the image, so a row without one has no indent. */
}
/* Trap: hover thickened the divider 2px -> 3px with nothing compensating, so every
   row below the hovered one moved 1px inside a scrolling box. 30 + 2 = 32;
   29 + 3 = 32. */
.search-result-item:hover {
  border-bottom: 3px solid rgba(255,255,255,0.9);
  padding-bottom: 29px;
}
/* DIG-005089. The same metric reflow the catalogue list rows had (DIG-005073):
   a weight change alters glyph advance widths, so the title moves under the
   cursor. Thickened by stroking the outline instead — identical visual step,
   zero movement. If you change one of these two rules, change the other. */
.search-result-item:hover .search-result-title {
  font-weight: 400;
  -webkit-text-stroke: 0.3px currentColor;
}
.search-result-item:hover .search-result-img { filter: drop-shadow(0 0 8px rgba(255,255,255,0.7)); }

/* §6 row values, unchanged from the prototype: 64px circle, 80x64 rectangle at
   3px radius, 24px gap. The gap is margin-right on the image, never `gap` on the
   row. No tinted background: an element only exists when a photograph does. */
.search-result-img {
  flex-shrink: 0;
  margin-right: 24px;
  overflow: hidden;
  transition: filter 0.25s ease;
}
.search-result-img.is-portrait { width: 64px; height: 64px; border-radius: 50%; }
.search-result-img.is-thumb    { width: 80px; height: 64px; border-radius: 3px; }
.search-result-img img {
  width: 100%; height: 100%; display: block;
  object-fit: cover;
  object-position: center;   /* placeholder — focal point is DIG-004977 */
}
.search-result-img.is-portrait img { object-position: center top; }

.search-result-text { flex: 1; min-width: 0; }
.search-result-title {
  font-family: var(--effra); font-weight: 300;
  font-size: 24px; color: white; line-height: 1.3;
}
/* §6: work rows carry the choreographer, in the catalogue card's treatment sized
   for a row. Choreographer rows carry none — they are the choreographer. */
.search-result-choreographer {
  margin-top: 4px;
  font-family: var(--brandon);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.78);
}

@media (max-width: 768px) {
  .search-result-item { padding: 20px 0; }
  .search-result-item:hover { padding-bottom: 19px; }   /* 19 + 3 = 22 = 20 + 2 */
  /* DIG-005218. Matches .list-title / .list-choreographer's own mobile sizes
     (catalogue list view) -- unscaled, these stayed at their desktop sizes
     (24px/12px, a 2:1 ratio) while the list view's equivalent row shrinks to
     16px/11px (~1.45:1), so the dropdown read oversized by comparison. */
  .search-result-title { font-size: 16px; }
  .search-result-choreographer { font-size: 11px; }
}
.search-result-spinner {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.3); border-top-color: white;
  animation: nav-spin 0.7s linear infinite;
  flex-shrink: 0; display: none;
}
.search-result-item.loading .search-result-spinner { display: block; }

/* Search results scroll region — its own scrollable container so the scrollbar
 * starts at the top of the first item (not above it as it did when padding
 * lived on the container). Standard-width scrollbar; only shown when content
 * actually overflows. */
#searchResults {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 var(--page-pad);
  overflow-y: auto;
  /* §6: rederived from the panel. The prototype hardcoded 50vh, and the panel
     is no longer 50vh — left as it was, the box and the panel disagree: at a
     tall window the list stops well short of the panel bottom, at a short one
     it overruns. No credit lane to subtract on this panel. */
  max-height: calc(var(--hero-grand) - var(--search-row-h) - var(--nav-height));
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,0.7) transparent;
}

/* DIG-005120. Tzipora, 18 Sep: "let's try 25px from the bottom of the frame is
   the cutoff for the search overlay on mobile." At mobile the panel is 100dvh,
   not --hero-grand, so the calc above resolves against the wrong box and the
   list runs to 122px short of the bottom at 375x667 — measured, not assumed.
   Subtract what is actually above the list (the nav, the 25px DIG-005088 added
   to the search bar, the bar itself) and then her 25px below it.

   ⚠ THIS BLOCK MUST STAY BELOW THE ONE ABOVE IT. Written first inside the
   `@media (max-width: 768px)` at the top of the file, where .search-overlay's
   100dvh lives, it shipped and did nothing: same specificity, and the desktop
   rule here comes later, so source order won. Fourth time on this stylesheet. */
@media (max-width: 768px) {
  #searchResults {
    max-height: calc(100dvh - var(--nav-height) - 25px - var(--search-row-h) - 25px);
  }
}
#searchResults::-webkit-scrollbar { width: 10px; }
#searchResults::-webkit-scrollbar-track { background: transparent; }
#searchResults::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,0.7); border-radius: 5px;
  border: 2px solid transparent; background-clip: padding-box;
}
#searchResults::-webkit-scrollbar-thumb:hover { background: white; background-clip: padding-box; }

/* HOMEPAGE */
.feature-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; padding: 68px 0; }
.feature-card { border: 1.5px solid var(--highlight); border-radius: 5px; padding: 40px; display: flex; flex-direction: column; gap: 16px; }
/* The two homepage cards differ by design, not by editorial choice, so the
   variance is here and not on the admin form: the left one is outlined with a
   highlight title and a solid button, the right one is a plain column with a
   text button. Was two `style="..."` attributes in the template. DIG-004992. */
.feature-card--outlined { max-width: 400px; }
.feature-card--outlined .feature-card-title { color: var(--highlight); }
.feature-card--plain { max-width: 400px; border: none; }
/* 25px between the copy and the button on the two homepage cards (DIG-005088).
   Tzipora, 16 Sep: "can we add 25px padding in between the bottom of the copy and
   the buttons on the top two, on just the Apply for a License and Browse the Dance
   Catalogue modals... it just looks a little squishy here." She said explicitly
   that the About band and the MTI band are fine, so this is scoped to .feature-card
   and does NOT touch .band-body, which those two use.
   The card is a flex column with gap:16px, so the button needs the difference —
   16 + 9 = 25 — rather than 25 on top of the gap. */
.feature-card-actions { margin-top: 9px; }
.feature-card-title { font-family: var(--effra); font-weight: 700; font-size: 24px; color: var(--main); }
.feature-card-text { font-family: var(--effra); font-size: 18px; color: var(--black); line-height: 1.6; }
.section-band { background: var(--band); padding: 68px 0; }
/* DIG-005076. Tzipora, 15 Sep: "it's sort of suspended from the bottom of the
   footer. It's supposed to be locked. This colour field is supposed to be locked
   to the footer. It's not a separate stripe."

   Nothing was wrong with the band — the gap is `.footer`'s own `margin-top: 100px`,
   which is the right separation when white page content runs into the footer and
   the wrong one when a full-bleed colour block already ends the page. Measured on
   live /news: band bottom 2180, footer top 2280, a dead 100px of white.

   Scoped by a modifier rather than by page id, because this is a property of "the
   page ends in a band", not of the news page. `:has()` keeps the knowledge in one
   place; where it is unsupported the rule simply does not apply and the spacing is
   today's, so there is no broken state to fall back to.

   Matched from `body` rather than as `.page + .footer`: Drupal wraps `.page` in an
   unclassed region div, so the footer is the WRAPPER's sibling, not the page's. The
   `.page > …:last-child` test stays inside the `:has()` so the rule still only fires
   when the band really is the last thing on the page. */
body:has(.page > .section-band--flush:last-child) .footer { margin-top: 0; }
/* Homepage band internals. These were inline `style` attributes on the heading,
   the paragraph and the flex row; they are classes now so the admin form can
   change the words without the markup carrying layout with it. DIG-004992. */
.band-heading { margin-bottom: 16px; }
/* The band text is a rich-text wrapper now rather than a single <p> carrying
   inline margins, so the spacing the old markup hardcoded lives here. The
   measure cap was on the licensing paragraph only — the MTI one is already
   narrowed by the photograph beside it — so it stays scoped to that band. */
.band-body { margin-bottom: 24px; }
.band-body > p { margin: 0 0 16px; }
.band-body > *:first-child { margin-top: 0; }
.band-body > *:last-child { margin-bottom: 0; }
.section-band--licensing .band-body { max-width: 720px; }
/* The news cards are pulled up over the licensing band (.news-cards-wrapper,
   -120px). An optional heading between the two would be swallowed by that, so
   it clears the overlap and the cards drop back to flow. DIG-004992. */
.news-heading-wrap { position: relative; z-index: 1; padding-top: 68px; }
/* Descendant, not sibling: whether drupal_block() wraps its output is not this
   rule's business to know. */
.home-news--titled .news-cards-wrapper { margin-top: 0; }
/* The licensing band clears the news cards, which are pulled up over it by
   `.news-cards-wrapper`'s negative margin. */
.section-band--licensing { padding-bottom: 160px; }
.mti-band { display: flex; align-items: flex-start; gap: 48px; }
.mti-band-text { flex: 1; }
/* With no photograph uploaded the text column is the whole 1104px content
   width, which is far too wide a measure to read. Capped to the same 720px the
   licensing band uses. Scoped to the no-photo case on purpose: with the
   photograph present the column is 836px, which is what the page has always
   rendered, and this refactor should not quietly restyle that too. */
.mti-band--no-photo .band-body { max-width: 720px; }
.mti-band-figure { flex-shrink: 0; width: 220px; margin: 0; }
.mti-band-photo {
  display: block; width: 220px; height: 220px;
  border-radius: 50%; object-fit: cover;
}
.mti-band-credit {
  font-family: var(--effra); font-size: 13px; color: var(--black);
  opacity: 0.7; margin-top: 8px; text-align: center;
}
.home-pullquote-wrap { padding-top: 68px; padding-bottom: 68px; }
.home-pullquote-wrap .pullquote { max-width: 800px; }
.news-cards-wrapper { margin-top: -120px; }

/* FOOTER */
.footer { background: linear-gradient(to bottom, #04304d, #10629e); padding: 48px 0; margin-top: 100px; }
.footer-inner {
  max-width: var(--content-max); margin: 0 auto; padding: 0 var(--page-pad);
  /* Two columns since the social block came out (DIG-004472, 2026-08-03).
     Left at 1fr 1fr 1fr this would reserve an empty third of the footer. */
  display: grid; grid-template-columns: 1fr 1fr; gap: 48px;
}
.footer-brand-sub { font-family: var(--effra); font-weight: 400; font-size: 15px; color: rgba(255,255,255,0.8); margin-bottom: 4px; }
/* .footer-brand-name removed with the markup it styled (DIG-004688): the footer
   set "MUSIC THEATRE INTERNATIONAL" as 24px letter-spaced caps. It is now the
   real two-line wordmark. */

/* The hairline rule is keyed to the wordmark's RENDERED width, not a fixed one:
   fit-content shrinks the wrapper to the artwork, and the rule is 100% of the
   wrapper, so the rule always ends exactly where the wordmark ends and keeps
   doing so if the asset is ever swapped. A fixed width breaks this by design
   (README §SVG assets, footer spec A12).
   The wordmark ships with no width/height, so height alone sizes it and the
   4.1478 viewBox aspect supplies the width. NOTE: no spec gives the footer
   wordmark a HEIGHT — 40px desktop / 34px mobile is our choice, set to carry
   about the same weight as the 24px caps it replaces. Flagged for Tzipora. */
.footer-brand-lockup { display: block; width: fit-content; }
.mti-wordmark { display: block; height: 40px; width: auto; }
/* ~20px clearspace above and below the rule (README §SVG assets). It was 8px
   when the rule sat under a line of text. */
.footer-rule { height: 1px; background: rgba(255,255,255,0.4); margin: 20px 0; width: 100%; }
.footer-address { font-family: var(--effra); font-weight: 400; font-size: 14px; color: rgba(255,255,255,0.8); line-height: 1.6; }
.footer-links { display: flex; flex-direction: column; gap: 8px; justify-content: center; }
.footer-link { font-family: var(--brandon); font-weight: 700; font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: rgba(255,255,255,0.8); text-decoration: none; cursor: pointer; }
.footer-link:hover { text-decoration: underline; }
/* .footer-social / .footer-follow / .footer-icons / .footer-icon removed
   2026-08-03 with the markup they styled (DIG-004472). */

/* ABOUT PAGE BODY (DIG-004749) */
/* The About copy moved out of the template and into the CMS, so these two
   blocks now arrive as CKEditor output — bare <p>/<h2>/<ul> with none of the
   inline margins the hardcoded markup carried. Styled by tag, scoped to
   .about-body, exactly as .legal-body already does for the policy pages.
   Without this the paragraphs run together with no spacing. */
.about-body p { margin-bottom: 24px; }
.about-body p:last-child { margin-bottom: 0; }
.about-body h2 { font-family: var(--effra); font-weight: 700; font-size: 26px; color: var(--black); line-height: 1.3; margin: 36px 0 16px; }
.about-body h3 { font-family: var(--effra); font-weight: 700; font-size: 20px; color: var(--black); line-height: 1.3; margin: 28px 0 12px; }
.about-body ul, .about-body ol { margin: 0 0 24px 22px; }
.about-body li { margin-bottom: 8px; }
.about-body a { color: var(--main); text-decoration: underline; text-underline-offset: 2px; }
.about-body a:hover { color: var(--highlight); }
/* A heading immediately after the intro should not push an extra 36px gap. */
.about-body > :first-child { margin-top: 0; }

/* PERSON */
/* Hero image photo credit — small muted caption sitting directly under the
   dance-work header card, attached to the image it credits (DIG-004448).
   .dw-hero-credit-wrap and its 40px top pad went with the old bottom-of-page
   placement; the caption is now a direct child of the header content-wrap, so
   it needs only enough offset to clear the card's border. */
.dw-hero-credit {
  font-family: var(--effra); font-weight: 300; font-size: 13px;
  color: var(--grey); margin: 8px 0 0;
}

.person-portrait {
  width: 160px; height: 160px; border-radius: 50%; flex-shrink: 0;
  background: var(--pale); display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.person-portrait img { width: 100%; height: 100%; object-fit: cover; object-position: center; }
.person-portrait .material-icons { font-size: 64px; color: var(--highlight); opacity: 0.4; }
.person-portrait-caption { text-align: center; margin-top: 10px; width: 160px; flex-shrink: 0; }
.person-top-row { display: flex; gap: 40px; align-items: flex-start; margin-bottom: 40px; }

/* DIG-004943. The director's copy wraps AROUND the circular portrait instead of
   sitting in a column beside it.

   Tzipora, 8 September: "the circle photo is supposed to be full width at mobile
   only. Here it's supposed to wrap. So the text is supposed to wrap around." The
   flex row above confined every paragraph to the right of a fixed column and
   left the space under the circle empty.

   float + shape-outside, not grid: only a float makes inline text reflow around
   a non-rectangular shape. `shape-outside: circle(50%)` follows the portrait's
   own border-radius, so the copy hugs the curve rather than its bounding box,
   and shape-margin keeps it off the edge.

   The modifier is on the row rather than replacing .person-top-row, because that
   class is shared and this treatment is the director block's alone. */
.person-top-row--wrap { display: block; }

.person-top-row--wrap .person-portrait-col {
  float: left;
  width: 260px;
  margin: 4px 32px 20px 0;
  shape-outside: circle(50% at 50% 130px);
  shape-margin: 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* The copy is a plain block: it must NOT establish a new formatting context or
   it stops wrapping and sits beside the float again, which is the bug. So no
   overflow, no display:flow-root, no flex here. */
.person-top-row--wrap .person-copy { padding-top: 4px; }

/* Close the float so anything after the block does not ride up beside it. */
.person-top-row--wrap::after { content: ''; display: block; clear: both; }

/* "Full width at mobile only" — her words. Under the breakpoint the circle
   stops floating, centres, and the copy runs full width beneath it. */
@media (max-width: 768px) {
  .person-top-row--wrap .person-portrait-col {
    float: none;
    width: 100%;
    margin: 0 0 24px;
    shape-outside: none;
  }
}
.person-portrait-name { font-family: var(--effra); font-weight: 700; font-size: 15px; color: var(--black); }
.person-portrait-credit { font-family: var(--effra); font-weight: 400; font-size: 13px; color: var(--grey); }

/* THANK YOU */
.thankyou-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 60vh; text-align: center; gap: 24px; padding: 48px; }

/* PAGE LAYOUT */
/* page-header is applied alongside .content-wrap on the same element. Shorthand
 * padding overrides .content-wrap's horizontal padding, so use --page-pad here
 * to keep the H1 aligned with the content gutter (was offset by ~23px). */
.page-header { padding: 48px var(--page-pad) 32px; }
/* DIG-005103. The news article's own wrapper, now that it is a sibling of the
   breadcrumb row rather than a child of it. 32px is not a new number — it is
   what a choreographer page already puts between the breadcrumb and its first
   line, and matching it is precisely what she asked for: "however it is on the
   choreographer pages is good, we should just deploy that on the news pages."
   The 64px below is the bottom padding the old inline style carried.
   Verify by measuring crumb-bottom to first-ink on both pages; they agree. */
.article-layout-wrap { padding-top: 32px; padding-bottom: 64px; }
/* DIG-005093. The credit under an article photograph. Quiet, immediately under
   the picture it belongs to, and it only exists when there is a credit to
   print — see the template. */
/* DIG-004879. Search within one choreographer's works. */
.bio-works-search {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 24px;
}
.bio-works-search__input {
  font-family: var(--effra);
  font-size: 16px;
  padding: 10px 14px;
  border: 1px solid rgba(4,48,77,0.25);
  border-radius: 3px;
  min-width: 260px;
  max-width: 100%;
  color: var(--black);
  background: #fff;
}
.bio-works-search__input:focus {
  outline: 2px solid var(--highlight);
  outline-offset: 1px;
}
.bio-works-search__count {
  font-family: var(--brandon);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey, #6b7783);
}
/* DIG-005107. The filter panel beside it reuses the /catalogue panel's classes.
   Its options are real <button>s (the catalogue's are divs with onclick, which
   a keyboard cannot reach), so the browser's button chrome comes off here and
   the catalogue rules supply the look. */
.bio-works-search { flex-wrap: wrap; }
.bio-works-applied { margin: 0 0 16px; }
.bio-works-applied[hidden] { display: none; }
.bio-works-filter .filter-pill,
.bio-works-filter .filter-radio {
  line-height: inherit;
  margin: 0;
}
.bio-works-filter .filter-radio {
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
}
.bio-works-filter .filter-pill:focus-visible,
.bio-works-filter .filter-radio:focus-visible {
  outline: 2px solid var(--highlight);
  outline-offset: 2px;
}

.news-photo-credit {
  font-family: var(--effra);
  font-weight: 400;
  font-size: 13px;
  color: var(--grey, #6b7783);
  margin: 0 0 16px;
}
.page-content { padding-bottom: 64px; }

/* ARTICLE LAYOUT */
.article-layout { display: flex; gap: 48px; align-items: flex-start; }
.article-body { flex: 0 0 70%; min-width: 0; }
.article-sidebar {
  flex: 0 0 calc(30% - 48px); min-width: 0;
  display: flex; flex-direction: column;
}
.article-sidebar-inner {
  display: flex; flex-direction: column; gap: 0;
}

/* SIDEBAR SECTIONS */
.sidebar-section {
  border-top: 2px solid var(--rule);
  padding: 20px 0;
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
}
.sidebar-section-head {
  font-family: var(--brandon); font-weight: 700; font-size: 14px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--main); margin-bottom: 10px;
}
.sidebar-link {
  font-family: var(--effra); font-weight: 400; font-size: 16px;
  color: var(--black); text-decoration: none; cursor: pointer;
  padding: 6px 0; border-bottom: 1px solid transparent;
  transition: color 0.15s, border-color 0.15s;
  display: inline-block; line-height: 1.4;
}
.sidebar-link:hover { color: var(--highlight); border-bottom-color: rgba(16,98,158,0.4); }
.sidebar-link .sidebar-link-title { font-weight: 500; color: var(--main); }
.sidebar-link:hover .sidebar-link-title { color: var(--highlight); }

/* BACK TO TOP */
.sidebar-top-btn {
  position: fixed; bottom: 32px; right: 48px;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--brandon); font-weight: 700; font-size: 13px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--main); background: white;
  border: 2px solid var(--main); border-radius: 4px;
  cursor: pointer; padding: 10px 16px 7px;
  transition: background 0.15s, border-color 0.15s;
  box-shadow: 0 2px 12px rgba(0,0,0,0.12);
  z-index: 500;
}
.sidebar-top-btn:hover { background: var(--pale); border-color: var(--main); }
.sidebar-top-btn .material-icons { font-size: 16px; opacity: 0; width: 0; overflow: hidden; transition: opacity 0.2s, width 0.2s; flex-shrink: 0; }
.sidebar-top-btn:hover .material-icons { opacity: 1; width: 16px; }
.sidebar-top-btn.above-footer { position: absolute; fixed: none; }

/* ============================================================
   GALLERY LIGHTBOX
   ============================================================ */
.lightbox {
  position: fixed; inset: 0; z-index: 2000;
  background: rgba(255,255,255,0.92);
  display: none; flex-direction: column;
  align-items: center;
  padding: var(--nav-height) 0 0;
}
.lightbox.open { display: flex; }

.lightbox-close {
  position: absolute; top: 0; right: 0;
  width: 64px; height: 64px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; border: none; background: transparent;
  z-index: 10;
}
.lightbox-close .material-icons { font-size: 32px; color: var(--navy); }
.lightbox-close:hover .material-icons { color: var(--highlight); }

/* Main image area */
.lightbox-main {
  flex: 1; display: flex; align-items: center; justify-content: center;
  width: 100%; padding: 24px 80px; min-height: 0;
  position: relative;
}
.lightbox-main-img {
  max-height: 100%; max-width: 100%;
  object-fit: contain; display: block;
  border-radius: 3px;
}
.lightbox-main-placeholder {
  max-height: 100%; height: 100%; width: auto; max-width: 600px;
  background: var(--pale);
  display: flex; align-items: center; justify-content: center;
  border-radius: 3px; flex: 0 0 auto; aspect-ratio: 4/3;
}
.lightbox-main-placeholder .material-icons { font-size: 80px; color: var(--highlight); opacity: 0.2; }

/* Prev/next arrows on main image */
.lightbox-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 48px; height: 48px; border-radius: 50%;
  border: 2px solid var(--highlight); background: white;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: background 0.15s; z-index: 5;
}
.lightbox-arrow:hover { background: var(--pale); }
.lightbox-arrow .material-icons { font-size: 22px; color: var(--highlight); }
.lightbox-arrow-prev { left: 24px; }
.lightbox-arrow-next { right: 24px; }

/* Caption */
.lightbox-caption {
  text-align: center; padding: 12px 48px 16px;
  display: flex; align-items: center; justify-content: center; gap: 24px;
  flex-shrink: 0;
}
.lightbox-caption-text { display: flex; flex-direction: column; gap: 2px; }
.lightbox-caption-company { font-family: var(--effra); font-weight: 700; font-size: 16px; color: var(--navy); }
.lightbox-caption-credit { font-family: var(--effra); font-weight: 300; font-size: 15px; color: var(--black); }

/* Thumbnail strip */
.lightbox-thumbs-wrap {
  width: 100%; padding: 0 80px 24px;
  flex-shrink: 0;
}
.lightbox-thumbs {
  display: flex; gap: 12px; overflow-x: auto;
  scrollbar-width: none;
}
.lightbox-thumbs::-webkit-scrollbar { display: none; }
.lightbox-thumb {
  height: 100px; flex-shrink: 0; cursor: pointer; border-radius: 3px;
  overflow: hidden; opacity: 0.55; transition: opacity 0.15s;
  border-bottom: 3px solid transparent;
}
.lightbox-thumb:hover { opacity: 0.85; }
.lightbox-thumb.active { opacity: 1; border-bottom: 3px solid var(--highlight); }
.lightbox-thumb-placeholder {
  height: 100%; width: 140px;
  background: var(--pale);
  display: flex; align-items: center; justify-content: center;
}
.lightbox-thumb-placeholder .material-icons { font-size: 32px; color: var(--highlight); opacity: 0.3; }


/* ============================================================
   RESPONSIVE — TABLET (≤1024px)
   ============================================================ */
@media (max-width: 1024px) {
  :root { --page-pad: 32px; }
  .nav-inner { padding: 0 32px; }
  .hero-label { font-size: 22px; padding: 18px 22px; }
  .catalogue-grid { grid-template-columns: repeat(3, 1fr); }
  .article-layout { gap: 32px; }
  .article-body { flex: 0 0 65%; }
  .article-sidebar { flex: 0 0 calc(35% - 32px); }
  .dw-metadata-band { gap: 8px; }
  .feature-cards { gap: 20px; }
  .footer-inner { gap: 32px; }
  .filter-panel { width: 100%; }
}

/* ============================================================
   RESPONSIVE — MOBILE (≤768px)
   ============================================================ */
@media (max-width: 768px) {

  /* ── GLOBAL ── */
  :root {
    --nav-height: 56px; --page-pad: 30px;
    /* Mobile lockup (spec §4): Frank becomes a clamp and EVERYTHING scales off
     * it, anchored to a 360px reference where 17vw = 61.2px. Each icon value
     * below is therefore expressed as a multiple of --cd-frank-h rather than as
     * its own clamp — value@360 / 61.2 — so all of them track F exactly and
     * stay correct at the clamp's floor and ceiling, not just in the vw range.
     *   glyph  20px@360 -> F x 0.326797
     *   gap    16px@360 -> F x 0.261438
     *   circle 35px@360 -> F x 0.571895
     *   tap    44px@360 -> F x 0.718954 */
    --cd-frank-h: clamp(56px, 17vw, 72px);
    /* Amendment B §3: no mobile override. The nav rule is 1px at both sizes, so the
       keyline is too, and --cd-keyline-w now follows --nav-rule at every width. */
  }
  /* Right inset drifts linearly from 20px at 360 to the 30px gutter, reaching it
     where Frank's clamp caps (72 / 0.17 = 424px). Slope 10px / 64px = 0.15625. */
  .nav-icons {
    gap: calc(var(--cd-frank-h) * 0.261438);
    padding-right: clamp(20px, calc(20px + (100vw - 360px) * 0.15625), 30px);
  }
  /* GLYPH-TIGHT, and this matters more than it looks. The button's LAYOUT box is
     the glyph; the 44px@360 tap target is an absolutely-positioned ::after that
     takes no space. Spec §4 says "glyph is glyph-tight; the tap area widens
     around it without shifting the glyph" and "effective hit tile =
     (glyph + gap) x tap" — i.e. the horizontal budget spends glyph + gap, not
     tap + gap.

     Sizing the box to the tap target instead overflows the bar. Measured at the
     spec's own 360px reference: lockup 236.5 + gutter 30 + inset 20 leaves 73.5px
     for the icons; glyph-width spends 2 x 20 + 16 = 56 and clears 17.5px, which
     is the "~18px (tight but passing)" §4 records. Tap-width spends 2 x 44 + 16 =
     104 and overruns the viewport by 30.5px. */
  .nav-icon-btn {
    width: calc(var(--cd-frank-h) * 0.326797);
    height: calc(var(--cd-frank-h) * 0.326797);
    padding: 0; border-radius: 50%; position: relative;
  }
  /* The tap target. Absolutely positioned so it contributes no width; clicks on
     it still land on the button because it is the button's own pseudo-element. */
  .nav-icon-btn::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: calc(var(--cd-frank-h) * 0.718954);
    height: calc(var(--cd-frank-h) * 0.718954);
  }
  .nav-icon-btn .material-icons {
    font-size: calc(var(--cd-frank-h) * 0.326797);
    position: relative; z-index: 1;
  }
  /* Mobile active state is a FROSTED CIRCLE, not the desktop navy rectangle
     (spec §4). Drawn on a pseudo-element sized independently of the tap target
     so the circle stays 35px@360 while the tap area is 44px@360. */
  .nav-icon-btn:hover, .nav-icon-btn.active { background: transparent; }
  .nav-icon-btn::before {
    content: ''; position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: calc(var(--cd-frank-h) * 0.571895);
    height: calc(var(--cd-frank-h) * 0.571895);
    border-radius: 50%;
    background: rgba(255,255,255,0.18);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    opacity: 0; transition: opacity 0.2s;
  }
  .nav-icon-btn.active::before, .nav-icon-btn:active::before { opacity: 1; }
  @supports not ((backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px))) {
    .nav-icon-btn::before { background: rgba(255,255,255,0.32); }
  }
  .nav-inner { padding: 0 20px; }
  nav { padding: 0 16px 0 16px; box-sizing: border-box; width: 100%; }
  /* .nav-logo-icon / -concert / -dance removed with the placeholder markup they
     styled (DIG-004688). The public lockup's mobile gap is F x 0.20 via
     .nav-logo--brand, so the 8px override is scoped to the contributor lockup,
     which is the only .nav-logo left without the --brand modifier. */
  .nav-logo:not(.nav-logo--brand) { gap: 8px; }

  /* The workspace lockup is a wide one-liner (641x58), so on phones it scales to
     the room left beside the Log out link instead of holding a fixed height —
     640px of artwork in a 375px viewport can't do both. A stacked variant for
     narrow screens would need new artwork from Tzipora. */
  body.contributor-chrome { --nav-height: 64px; }
  /* 16px gutter (what the mobile `nav` rule below already intends, though .nav's
     48px beats it on specificity) + the Log out link, trimmed to 14px of side
     padding here, is the room the stacked mobile lockup has to give back. The
     <picture> source swaps at this same 768px boundary; max-height keeps the
     shorter, stacked artwork from outgrowing the bar on a tablet, where the
     width budget alone would let it reach ~43px. */
  body.contributor-chrome .nav { padding-left: 16px; }
  body.contributor-chrome .nav-logout { padding: 0 14px; }
  /* 16px gutter + ~101px of Log out link + ~23px of breathing room between the
     two, so the lockup never butts against the link on a narrow phone. */
  .nav-lockup { height: auto; width: min(340px, calc(100vw - 140px)); max-height: 38px; }
  /* These two were hard-coded 40px, which silently defeated the whole point of
     giving --page-pad a mobile tier: the token said 30px and the content margin
     stayed at 40px, because these rules own the property outright (DIG-004691).
     40px is the value from the superseded May spacing guide — so the README's
     "the May style guide said 40px" was right about these two selectors even
     though --page-pad itself was 48px everywhere. Both read the token now, so
     the locked 30px actually reaches the content margin.
     .page-header keeps the shorthand because it also sets vertical padding, and
     a shorthand here would otherwise reset the horizontal value to 0. */
  .content-wrap { padding-left: var(--page-pad); padding-right: var(--page-pad); }
  .page-header { padding: 48px var(--page-pad) 32px; }
  #page-news .page-header { padding-bottom: 48px; }
  #page-faqs .page-header { padding-bottom: 48px; }

  /* ── TYPOGRAPHY ── */
  .h1, .h1-highlight { font-size: 30px; }
  .h2 { font-size: 22px; }
  .h3 { font-size: 20px; }
  .body-a, .body-b { font-size: 18px; }
  .date-stamp { font-size: 17px; }
  .feature-card-title { font-size: 26px; }
  .feature-card-text { font-size: 20px; }
  .learn-more-title { font-size: 26px; }
  .learn-more-desc { font-size: 22px; }
  .faq-question-text { font-size: 20px; }
  .filter-section-head { font-size: 18px; }
  .filter-pill { font-size: 19px; }
  .choreographer-name-dw { font-size: 22px; }
  .dw-meta-item { font-size: 17px; }
  .dw-meta-item > span { white-space: nowrap; }
  .review-pub, .review-date { font-size: 17px; }
  /* 13px on mobile, per hero-credit-handoff §5. This was 17px — LARGER than the
     15px desktop size, which is the opposite of the locked spec (DIG-004692). */
  .breadcrumb { font-size: 13px; }
  /* DIG-005112. "We need to make the type smaller, maybe by about two pixels or
     two points, and a less bold typeface. Just for mobile." 13 -> 11, and the name
     drops from 700 to 500 — the strip's own text is already 300, so the bold part
     she was reacting to is the name. */
  .cd-credit-inner { font-size: 11px; padding: 10px var(--credit-pad); }
  /* DIG-005112, corrected 23 Sep. min-height: 2.6em is two lines of type, but the
     site is border-box, so the 20px of padding came out of it and only ONE line was
     reserved: a two-line credit measured 49.6px and the one-line Petronio credit
     35.3px, so the page still jumped 14px on the homepage. Add the padding back.
     Desktop has the same arithmetic but every current credit is one line there, so
     it is left alone rather than adding 18px under every hero at launch. */
  .cd-credit-inner { min-height: calc(2.6em + 20px); }
  .cd-credit-name { font-weight: 500; }
  .cd-credit-kicker { font-size: 11px; } /* kept for when the kicker returns */
  /* Desc-tab play button steps 92 -> 68px; the gallery badge stays 60px at all
     widths (spec §3/§4). The triangle is a ratio of the diameter, so it follows
     without its own rule. 68px still clears the 48px tap target. */
  .dw-desc-video .cd-play { --play-d: 68px; }
  /* Video-handoff §4 describes the caption title as "existing .caption (Nunito
     Sans 300, 15px desktop / 13px mobile)". `.caption` is 15px at EVERY width —
     it has no mobile rule anywhere — so the 13px is stepped here rather than on
     `.caption` itself. Doing it globally would also resize the choreographer
     inline-image captions, which is another card's surface and nobody has asked
     for it. Flagged to Tzipora: if .caption is meant to be 15/13 everywhere,
     this rule should move up to the base class. */
  .cd-video-caption .caption { font-size: 13px; }
  .mti-wordmark { height: 34px; }
  .pullquote { font-size: 18px; }
  /* DIG-005159. The attribution is right-aligned for desktop, so on a phone the
     long title fills the width and the short name floats off to the right (name at
     184px, title at 70px, at 375). Tzipora, 23 Sep: "the hyphen should line up on
     the left edge of the quote text and then the name itself should start in the
     same place that the title starts." Left-aligning does exactly that, because
     the name already hangs its dash 24px outside the title's padding. */
  .pullquote-attribution { align-items: flex-start; }

  /* ── NAV OVERLAY ── */
  /* dvh, not vh — §1 trap 2. This rule sits AFTER the other mobile block and at
     equal specificity, so it was quietly overriding it; both say dvh now. */
  .nav-overlay { height: 100dvh; }
  /* `.nav-items { gap: 8px; padding-top: 24px; }` REMOVED 2026-09-15: that class
     is rendered nowhere in this theme, so the rule had never fired once. The
     element doing that job is `.overlay-content`. Deliberately NOT re-pointed at
     it — switching on spacing that has never been applied would change the mobile
     menu nine days before launch on the strength of a rule nobody has seen work.
     Her spec writes `.nav-items` rules too (§3, §5), and the 10 September selector
     concordance listed the name as matching the build. It does not. DIG-005000. */
  /* DIG-005160. Was 28pt (37.3px). Tzipora, 23 Sep, wants the large items smaller
     on a phone. Her 9 Sep handoff (§5) said "No separate mobile font-size", so no
     size was ever specified; this is our default of 80% until she gives one.
     22.5pt = 30px. Smaller only, so CATALOGUE still fits (see the §5 clipping trap). */
  .nav-item { font-size: 22.5pt; }
  /* The swap. .nav-logout is scoped away from contributor chrome, whose bar has
     no search/menu icons and therefore room for it (DIG-004384) — and whose
     overlay is not rendered at all, so hiding it there would leave no way out. */
  body:not(.contributor-chrome) .nav-logout { display: none; }
  .nav-item--logout { display: flex; font-size: 14pt; }
  /* DIG-005113. Was 18pt. Tzipora, 18 Sep: "for the sub nav items on catalogue and
     news, the type be reduced by half so that they're not so big" — and Seth
     confirmed on the call that she meant half of what was on screen. She was
     looking at MOBILE, where this rule was serving 18pt / 24px, so half is 9pt.

     ⚠ NOT half of the 12pt on the base .nav-subitem rule. The first attempt did
     exactly that, shipped 6pt, and measured 24px on live — because this rule is
     later in the same 768px query and won. Half of what she could see, not half of
     the desktop value.

     Her reasoning: "someone's holding a phone, it's close to their face. I'm not
     worried about size on desktop." */
  .nav-subitem { font-size: 9pt; padding: 14px 0; }
  .nav-logo { font-size: 16pt; }

  /* ── CAROUSEL ── */
  /* No mobile height override here any more. Both hero surfaces read
     --hero-interior, a clamp that covers every width in one declaration
     (DIG-004691, hero-credit-handoff §1).

     This SUPERSEDES the mobile caps added under DIG-004640, and it is a real
     change in the other direction: the locked clamp is TALLER on a phone than
     the cap it replaces. Measured, interior surfaces:
       390x844 (iPhone 14)  420px -> 506px  (49.8% -> 60.0% of the viewport)
       375x667 (iPhone SE)  334px -> 440px  (50.0% -> 66.0%, the 440px floor)
       768x1024 (iPad port) 420px -> 614px
     DIG-004640 existed precisely to stop the hero eating the phone screen, so
     the +86px is worth a look before launch — but the clamp is the locked value
     and 60vh is what it asks for. Re-capping mobile is one declaration here if
     Tzipora wants 004640's 420px back; flagged to her with these numbers. */
  .hero-label { font-size: 18px; padding: 14px 18px; max-width: 80%; }
  .hero-label-spinner { width: 16px; height: 16px; }
  .hero-label.loading .hero-label-spinner { right: 8px; top: calc(50% - 8px); }
  .hero-btn { width: 32px; height: 32px; }
  .hero-btn .material-icons { font-size: 15px; }
  .hero-dot { width: 8px; height: 8px; }

  /* ── TABS ── */
  /* DIG-004986. Two tab buttons need 458px on /about and 330px on /catalogue
     inside a 315px row, and they neither wrapped nor scrolled — so the LAYOUT
     VIEWPORT grew to 505px on /about and the whole page scrolled sideways at
     375px. Tzipora over Teams, 8 Sep: "the nav runs off frame and the carousel
     ends before the nav does when scrolling right."

     .tab-bar already scrolled; .tabs never got the same treatment, which is why
     only two pages were affected. Scrolling rather than wrapping, because the
     indicator underline is absolutely positioned against this row and a wrapped
     second line would leave it pointing at the wrong place. min-width:0 is what
     actually stops the growth — without it a flex item refuses to shrink below
     its content and the overflow moves to the page instead of the row. */
  .tabs {
    gap: 16px;
    overflow-x: auto;
    scrollbar-width: none;
    min-width: 0;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs .tab-btn { flex: 0 0 auto; }
  .tab-bar { gap: 0; overflow-x: auto; scrollbar-width: none; }
  .tab-bar::-webkit-scrollbar { display: none; }
  /* DIG-005114. She asked for this on three separate pages, so it is one rule:
     a tab row that does not fit truncates the INACTIVE tabs with an ellipsis and
     keeps the ACTIVE one fully readable. "Choreographers needs to shrink, it needs
     to have the cutoff with the ellipse, and Dance Works and Ballets needs to be
     fully visible as a tab selector."

     ⚠ This REPLACES the horizontal scroll added for DIG-004986 rather than sitting
     beside it: a scrolling row has unlimited width, so nothing ever shrinks and an
     ellipsis can never trigger. Both cards wanted the same underlying thing — the
     row must not widen the page — and truncation delivers it while also signalling
     that there is more, which scrolling did not.

     `min-width: 0` is what actually allows the shrink; without it a flex item
     refuses to go below its content width and the overflow returns to the page.
     Same trap as the metadata band in DIG-005115. */
  .tab-btn { font-size: 11px; letter-spacing: 0.02em; padding: 16px 8px; white-space: nowrap; }
  .tab-btn.active { font-size: 14px; letter-spacing: 0.04em; }
  .tabs { overflow-x: hidden; }
  /* DIG-005158. min-width: 0 let the inactive tab shrink to nothing: with the long
     Director tab active on /about it was 16px, a single "A". Tzipora, 23 Sep: "It
     just looks like a mistake." The inactive tab now keeps 60px (a few letters
     plus the ellipsis) and shrinks first (flex-shrink 1000); only then does the
     active tab give way, with its own ellipsis.
     60, not 72: with ABOUT MTI CONCERT DANCE active (233px) plus a 16px gap, 72px
     needed 321px of a 315px row and clipped the ACTIVE tab, breaking DIG-005114's
     rule that the active tab stays readable. 60px fits (309px). Caught by the
     automated re-check before it reached live. */
  .tabs .tab-btn:not(.active) {
    flex: 0 1000 auto;
    min-width: 60px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .tabs .tab-btn.active { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

  /* ── FILTER CONTROLS ── */
  /* Center-align all filter controls on mobile per Tzipora's 2026-05-27 feedback;
   * desktop pushes the right-side group with margin-left:auto, but on narrow
   * widths that wrapped haphazardly. Keep all buttons on the same visual row
   * (or wrapped centered) instead. */
  .filter-controls {
    justify-content: flex-start;
    gap: 8px;
    flex-wrap: wrap;
  }
  /* DIG-005118. Tzipora, 18 Sep: "The Sort By dropdown needs to grid all the way
     to the right margin... and then the two touch state box and list items need
     to be next to THAT, not next to the Filter button."

     This reverses the 2026-05-27 `margin-left: 0`, which parked the whole right
     group immediately after FILTER and left the margin empty. Restoring `auto`
     puts Sort flush right and the toggles directly to its left, because the
     toggles already precede .sort-wrap in the markup — no DOM change needed.

     `flex-wrap: wrap` stays on the parent deliberately: measured at 375px the
     row is 297px against 327px of usable width, so it does not wrap, but below
     ~340px it should drop to a second line rather than start a sideways scroll. */
  .filter-controls-right { margin-left: auto; flex-basis: auto; gap: 8px; }
  /* Same 36px as .toggle-btn, and she was explicit about which way to close the
     gap: "INSTEAD of making the box and grid buttons smaller, I would like you to
     make the Filter and the Sort dropdown TALLER so that the heights match."
     Height is fixed rather than left to padding so a font-metric difference
     between the two labels cannot re-open it; the padding is still asymmetric
     (11/8) because Brandon's caps sit high and geometric centring reads low. */
  .filter-controls .btn { height: 36px; padding: 11px 14px 8px; font-size: 13px; }
  .sort-btn { height: 36px; padding: 11px 12px 8px; font-size: 13px; line-height: 1; }

  /* ── CATALOGUE GRID ── */
  .catalogue-grid { grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .card-body { padding: 12px; }
  .card-bottom { padding: 0 0 20px; }

  /* ── LIST VIEW ── */
  /* §2/§5 mobile: values are set INDEPENDENTLY, not scaled from desktop — her
     note says deriving them produced a 62px thumbnail last time. Text starts at
     116px for both row types: 100 + 16 and 92 + 24. */
  .list-thumb { width: 100px; height: 75px; margin-right: 16px; flex-shrink: 0; }
  .list-portrait { width: 92px; height: 92px; margin-right: 24px; }
  .list-row { padding: 20px 0; }
  .list-row:hover { padding-bottom: 18px; }   /* 18 + 3 = 21 = 20 + 1 */
  .list-text { max-width: 100%; }
  .list-title { font-size: 16px; }
  .list-choreographer { font-size: 11px; }

  /* ── FEATURE CARDS ── */
  .feature-cards { grid-template-columns: 1fr; gap: 16px; padding: 60px 0 40px; }
  .feature-card { padding: 28px; }

  /* ── NEWS CARDS ── */
  .news-card, .news-card-home { flex-direction: row; }
  .news-card-img-placeholder { width: 38%; min-width: 38%; }
  .news-card-content { padding: 14px 16px; }
  .news-card-content .article-title { font-size: 19px; }
  .news-card-content .body-a { font-size: 15px; }
  .news-card-content .date-stamp { font-size: 14px; }
  .news-card-content .btn, .learn-more-content .btn { font-size: 13px; padding: 9px 16px 7px; }
  .news-card-divider, .learn-more-divider { display: none; }

  .news-card-content .line-clamp-3, .learn-more-content .line-clamp-3 { -webkit-line-clamp: 2; }
  .learn-more-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

  .news-card { margin-bottom: 40px; }

  /* ── LEARN MORE CARD ── */
  .learn-more-card { flex-direction: row; }
  .learn-more-card .learn-more-logo-placeholder { width: 38%; min-width: 38%; align-self: stretch; flex-shrink: 0; overflow: hidden; min-height: 0; }
  .learn-more-title { font-size: 17px; }
  .learn-more-desc { font-size: 15px; }

  /* ── DWP HEADER ── */
  .dw-header-card { flex-direction: column; }
  .dw-header-photo { width: 100%; height: 240px; align-self: auto; flex-shrink: 0; }
  .dw-header-content { padding: 20px 40px; }

  /* ── DWP METADATA BAND ── */
  /* DIG-005115. This was a two-column grid with the last item spanning both, and
     at 375px it ran off: measured on /work/2-3-part-inventions, the band's right
     edge is 329 and two of its three items reached 416.

     TWO causes, and the first fix missed the second entirely:
       `grid-template-columns: auto auto` sizes columns to content, so a long item
       widens the grid past its container rather than wrapping; and
       `.dw-meta-item > span { white-space: nowrap }` (in this same query, above)
       forbids the text from wrapping at all.

     ⚠ An earlier attempt added `flex-direction: column` in a separate block and
     changed nothing, because `display: grid` here is later in the file and won.
     Fixed where the rule actually lives.

     One column, per her rule on 18 Sep: "there's very little that needs to be side
     by side. If anything needs to be stacked, it should just stack." */
  .dw-metadata-band {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    justify-content: start;
    gap: 14px;
    padding: 20px 24px;
  }
  .dw-meta-item { align-items: flex-start; line-height: 1.35; }
  .dw-meta-item > span { white-space: normal; }

  /* ── DWP LICENSE BAND ── */
  .dw-license-band { flex-direction: column; align-items: flex-start; gap: 28px; }

  /* ── ARTICLE LAYOUT ── */
  .article-layout { flex-direction: column; gap: 24px; }
  .article-body { flex: none; width: 100%; }
  .article-sidebar { flex: none; width: 100%; }
  .article-sidebar-inner { flex-direction: row; flex-wrap: wrap; gap: 0; }
  .sidebar-section { flex: 0 0 50%; padding-right: 16px; }
  .sidebar-top-btn { bottom: 16px; right: 16px; }

  /* ── ABOUT PAGE ── */
  .person-top-row { flex-direction: column; align-items: center; gap: 24px; }
  .person-portrait { width: 80vw; height: 80vw; max-width: 300px; max-height: 300px; }
  .person-portrait-caption { text-align: center; }

  /* ── SECTION BAND ── */
  .section-band { padding: 60px 0; }
  #page-home .section-band { padding-bottom: 60px; }
  .news-cards-wrapper { margin-top: -80px; }
  .pullquote-wrap { padding-top: 34px !important; padding-bottom: 34px !important; }
  .inline-image-row { flex-direction: column !important; }
  .inline-image-row > div:first-child { width: 100% !important; flex-shrink: 0; }
  .inline-image-row > div:last-child { width: 100% !important; flex-shrink: 0; }
  /* Was `#page-home .section-band .content-wrap > div > div:last-child`, which
     named the photograph only by position. DIG-004992 gave both a class. */
  .mti-band { flex-direction: column; gap: 32px; }
  .mti-band-figure { display: none; }
  .news-heading-wrap { padding-top: 40px; }
  /* .home-pullquote-wrap needs nothing here — `.pullquote-wrap` above already
     wins with !important.

     The licensing band does. Its 160px was an INLINE style until DIG-004992, so
     it outranked the `#page-home .section-band { padding-bottom: 60px }` rule
     directly above and the homepage rendered 160px on phones too. Moving it to
     a class made that rule start winning, which quietly took 100px off the
     coloured band the news cards overlap on mobile. Whether 60px was the
     intent — the rule is otherwise redundant, since `.section-band` already
     sets 60px, which suggests it was written to beat this exact inline style
     and failed — is a design question, not a refactor's to answer. Restored to
     what the page has always rendered; raised with Tzipora separately. */
  #page-home .section-band--licensing { padding-bottom: 160px; }

  /* ── FOOTER ── */
  .footer { margin-top: 80px; }
  .page-content { padding-bottom: 80px; }
  .footer-inner { grid-template-columns: 1fr; gap: 32px; }

  /* ── FILTER BOTTOM SHEET ── */
  .filter-panel {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    max-height: 85vh;
    overflow-y: auto;
    border-radius: 16px 16px 0 0;
    border: none;
    z-index: 900;
    transform: translateY(100%);
    transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
    display: block !important;
    visibility: hidden;
  }
  .filter-panel.open { transform: translateY(0); visibility: visible; }
  .filter-scrim {
    display: none; position: fixed; inset: 0;
    background: rgba(0,0,0,0.3); z-index: 899;
  }
  .filter-scrim.open { display: block; }

  /* ── GALLERY ── */
  .gallery-item { height: 160px; }
  .lightbox-main { padding: 16px 48px; }
  .lightbox-thumbs-wrap { padding: 0 16px 16px; }
  .lightbox-thumb { height: 70px; }

  /* ── FAQ ── */
  .faq-question { padding: 24px 0; }

  /* ── FORMS ── */
  /* .subscribe-form-row removed 2026-08-03 (DIG-004474) — it was already dead,
     no template has referenced that class since the prototype port. */
}

/* Solid-color homepage hero — the default top band on the front page. */
.home-hero-solid { width: 100%; height: var(--hero-grand); background: #0b2c4d; }

/* ...and the alternative, when the homepage is switched to the carousel. The
   carousel is a shared block, so it renders at the interior tier everywhere
   else; only in this position does it take the grand one. Tzipora, 2026-08-18:
   "70 VH for desktop for the homepage, and then 10% less for the internal
   carousels on height, so responsive on height." DIG-004992. */
.path-frontpage #sharedCarousel { height: var(--hero-grand); }

/* Contributor "chrome" pages (workspace / form / preview): solid matte navy nav,
   full opacity, no translucency — isolated data-entry pages (2026-07-10 review §C).
   Final nav/branding colour still to come from Tzipora. */
.nav--solid { background: var(--navy) !important; opacity: 1 !important; border-bottom-color: rgba(255,255,255,0.18); }

/* ============================================================
   COOKIE CONSENT BANNER (eu_cookie_compliance)
   The module's default styling puts a link-blue anchor and an
   unstyled text button on our navy bar — both fail contrast.
   Brand it and make every control readable at AA. Partners
   shipped this banner with a11y defects (DIG-003313 /
   DIG-003465); don't repeat them here.
   ============================================================ */
#sliding-popup.sliding-popup-bottom,
#sliding-popup .eu-cookie-compliance-banner { background: var(--navy); }
#sliding-popup .eu-cookie-compliance-message,
#sliding-popup .eu-cookie-compliance-message p { color: var(--white); font-family: var(--effra); font-size: 15px; line-height: 1.5; }
/* White underlined link — 13:1 on navy, and still obviously a link. */
#sliding-popup .eu-cookie-compliance-message a { color: var(--white); text-decoration: underline; text-underline-offset: 2px; }
#sliding-popup .eu-cookie-compliance-message a:hover,
#sliding-popup .eu-cookie-compliance-message a:focus { color: var(--white); }
/* Both choices are real buttons, equally reachable. Accept is the filled
   inverse (navy on white, 13:1); decline is outlined white on navy. */
#sliding-popup .eu-cookie-compliance-buttons button,
#sliding-popup button.agree-button,
#sliding-popup button.decline-button {
  font-family: var(--brandon); font-weight: 700; letter-spacing: 0.04em;
  font-size: 14px; padding: 10px 20px; border-radius: 4px; cursor: pointer;
  background: transparent; border: 2px solid var(--white); color: var(--white);
}
#sliding-popup button.agree-button { background: var(--white); border-color: var(--white); color: var(--navy); }
#sliding-popup button.agree-button:hover { background: var(--pale-tint); color: var(--navy); }
#sliding-popup button.decline-button:hover { background: rgba(255,255,255,0.14); color: var(--white); }
/* Visible focus ring on the navy bar — the WCAG failure partners hit. */
#sliding-popup button:focus-visible,
#sliding-popup .eu-cookie-compliance-message a:focus-visible {
  outline: 3px solid var(--highlight); outline-offset: 2px;
}
@media (max-width: 768px) {
  #sliding-popup .eu-cookie-compliance-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
  #sliding-popup .eu-cookie-compliance-message { font-size: 14px; }
}

/* ============================================================
   LEGAL PAGES (/privacy-policy, /cookie-policy)
   Long-form prose + cookie tables. Readable measure, real
   table styling, and a clear draft marker while legal reviews.
   ============================================================ */
.legal-doc { max-width: 780px; padding-top: 25px; padding-bottom: 64px; }
.legal-effective { font-family: var(--brandon); font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--grey); margin-bottom: 24px; }
.legal-draft-notice { background: var(--pale); border-left: 4px solid var(--highlight); padding: 14px 18px; margin-bottom: 32px; font-size: 15px; line-height: 1.5; color: var(--navy); }
.legal-body { font-size: 16px; line-height: 1.65; }
.legal-body p { margin-bottom: 20px; }
.legal-body h2 { font-family: var(--brandon); font-weight: 700; font-size: 22px; color: var(--navy); margin: 36px 0 14px; }
.legal-body h3 { font-family: var(--brandon); font-weight: 700; font-size: 18px; color: var(--navy); margin: 28px 0 10px; }
.legal-body ul, .legal-body ol { margin: 0 0 20px 22px; }
.legal-body li { margin-bottom: 8px; }
.legal-body a { color: var(--main); text-decoration: underline; text-underline-offset: 2px; }
.legal-body a:hover { color: var(--highlight); }
/* Cookie tables — scroll rather than force the page sideways on mobile. */
.legal-body table { width: 100%; border-collapse: collapse; margin: 0 0 28px; font-size: 14px; display: block; overflow-x: auto; }
.legal-body thead th { background: var(--navy); color: var(--white); font-family: var(--brandon); font-weight: 700; text-align: left; letter-spacing: 0.03em; }
.legal-body th, .legal-body td { border: 1px solid var(--pale-full); padding: 10px 12px; vertical-align: top; }
.legal-body tbody tr:nth-child(even) { background: rgba(30,145,206,0.08); }
@media (max-width: 768px) {
  .legal-doc { padding-bottom: 40px; }
  .legal-body h2 { font-size: 20px; }
}

/* Contact page (DIG-004409). /contact is core contact module's site-wide form,
   which ships with browser-default controls — full-bleed, and wide enough to
   force horizontal scroll on a phone. Dressed here to match the site's own
   forms rather than replaced, so core keeps handling validation and flood
   control. The intro copy above it reuses .legal-doc / .legal-body. */
.cd-contact-form { max-width: 780px; padding-bottom: 64px; }
.cd-contact-form .contact-intro { padding-bottom: 8px; }
.cd-contact-form .form-item { margin-bottom: 20px; }
.cd-contact-form label {
  font-family: var(--effra); font-weight: 400; font-size: 17px;
  color: var(--black); display: block; margin-bottom: 6px;
}
/* Required marker. The class sits on the label itself, and because this theme
   has no base theme nothing supplies the marker — required fields rendered with
   no indication at all. Colour is paired with the asterisk so it is not the only
   signal, and the fields also carry the native `required` attribute. */
.cd-contact-form label.form-required::after {
  content: " *"; color: var(--error); font-weight: 700;
}
.cd-contact-form input[type="text"],
.cd-contact-form input[type="email"],
.cd-contact-form textarea {
  width: 100%; max-width: 100%; box-sizing: border-box;
  border: 2px solid var(--main); border-radius: 4px;
  font-family: var(--omnes); font-size: 16px; color: var(--black);
  background: white; outline: none; transition: border-color 0.2s, color 0.2s;
}
.cd-contact-form input[type="text"],
.cd-contact-form input[type="email"] { height: 40px; padding: 0 12px; }
.cd-contact-form textarea { min-height: 180px; padding: 10px 12px; line-height: 1.5; resize: vertical; }
.cd-contact-form input[type="text"]:focus,
.cd-contact-form input[type="email"]:focus,
.cd-contact-form textarea:focus {
  border-color: var(--highlight); color: var(--highlight); caret-color: var(--highlight);
}
.cd-contact-form .description {
  font-size: 14px; line-height: 1.45; color: var(--grey); margin-top: 6px;
}
.cd-contact-form .form-actions { margin-top: 28px; }
.cd-contact-form input[type="submit"] {
  font-family: var(--brandon); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 15px;
  border-radius: 4px; border: 2px solid var(--highlight); cursor: pointer;
  padding: 12px 20px 8px; transition: all 0.2s; line-height: 1;
  background: var(--highlight); color: white;
}
.cd-contact-form input[type="submit"]:hover,
.cd-contact-form input[type="submit"]:active {
  background: white; color: var(--highlight);
}
.cd-contact-form input[type="submit"]:focus-visible,
.cd-contact-form input:focus-visible,
.cd-contact-form textarea:focus-visible {
  outline: 3px solid var(--highlight); outline-offset: 2px;
}
/* Webform's inline confirmation otherwise reads as one more body paragraph —
   the visitor needs to see plainly that the message went. */
.webform-confirmation { max-width: 780px; padding-bottom: 64px; }
.webform-confirmation__message {
  border-left: 4px solid var(--main); background: var(--pale);
  padding: 16px 20px; font-size: 16px; line-height: 1.55;
  color: var(--navy); font-weight: 500;
}
.webform-confirmation__back { margin-top: 20px; }
.webform-confirmation__back a,
.webform-confirmation__back input[type="submit"] {
  font-family: var(--brandon); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 15px;
  border-radius: 4px; border: 2px solid var(--main); cursor: pointer;
  padding: 12px 20px 8px; line-height: 1; transition: all 0.2s;
  background: white; color: var(--main); text-decoration: none;
  display: inline-block;
}
.webform-confirmation__back a:hover,
.webform-confirmation__back input[type="submit"]:hover { background: var(--pale); }

/* DIG-005074. Tzipora on the subscribe confirmation, 15 Sep: "this should be
   just regular type — this right here, not in the box. It should just be
   regular old type."

   Scoped by a class set in the form's own `confirmation_attributes`, NOT by
   editing `.webform-confirmation__message` above: that rule is shared with the
   Licensing Inquiry and Contact confirmations, which she was not looking at and
   has not asked to change. One form's copy decision must not restyle the other
   two. */
.cd-confirmation-plain .webform-confirmation__message {
  border-left: 0;
  background: transparent;
  padding: 0;
  font-weight: 400;
  color: var(--black);
}

/* DIG-005074 item 4, the homepage CTA that replaces "Back to form".
   ⚠ The button treatment CANNOT ride on a class in the message HTML: Webform
   renders these through basic_html, whose filter allows `<a hreflang href>` and
   strips `class`. A `class="btn btn-simple"` in the confirmation copy is
   silently dropped and the CTA renders as a bare underlined link — which is
   exactly what happened on the first pass. Style it by position instead. */
.cd-confirmation-plain .webform-confirmation__message p:last-child { margin-top: 20px; }
.cd-confirmation-plain .webform-confirmation__message p:last-child a {
  font-family: var(--brandon); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 15px;
  border-radius: 4px; border: 2px solid var(--main);
  padding: 12px 20px 8px; line-height: 1; transition: all 0.2s;
  background: white; color: var(--main); text-decoration: none;
  display: inline-block;
}
.cd-confirmation-plain .webform-confirmation__message p:last-child a:hover { background: var(--pale); }

/* DIG-005070. The warning staff see when a contributor has unapproved changes
   in progress on the page they have just opened. Amber rather than the error
   red: nothing has gone wrong yet, and this fires on a perfectly normal edit —
   a red banner every time is one that stops being read. Weight -100 puts it
   above the title, because it has to be seen before the form is worked on and
   not after. */
.cd-draft-warning {
  border-left: 4px solid #b8860b;
  background: #fdf6e3;
  color: #4a3a05;
  padding: 14px 18px;
  margin: 0 0 20px;
  font-size: 15px;
  line-height: 1.55;
  max-width: 780px;
}

@media (max-width: 768px) {
  .cd-contact-form { padding-bottom: 40px; }
  .webform-confirmation { padding-bottom: 40px; }
}

/* Drupal status/warning/error messages (DIG-004409). The region was never
   printed, so these arrive unstyled; given the same treatment as the legal
   draft notice so confirmations and validation errors read as part of the
   site. Colour is not the only signal — core keeps its own visually-hidden
   "Status message"/"Error message" heading for screen readers. */
/* No spacing on the wrapper itself: the region renders an empty div on every
   page (the messages block is always present, just usually with nothing to
   say), so any padding here would shift the whole site down by that much. */
.page-messages .messages {
  border-left: 4px solid var(--main); background: var(--pale);
  padding: 14px 18px; margin: 24px 0 8px;
  font-size: 15px; line-height: 1.5; color: var(--navy);
}
/* The nav is position:fixed and the messages region is printed at the very top of
   the document, so wherever nothing precedes it in flow the confirmation renders
   UNDERNEATH the bar with only its bottom sliver showing. First seen on
   /node/N/latest (DIG-004418; Tzipora liked the callout, she just couldn't read
   it), and again on the canonical page (DIG-004886) — she saves a dance work,
   lands on /work/<alias>, and "the updated notice is like behind the nav."

   Offset the INNER .messages rather than the .page-messages wrapper: the wrapper
   is emitted on every page whether or not there is anything to say, so padding it
   would push the whole site down, while .messages only exists when a message does.

   Keyed off .page-messages--top, which page.html.twig sets from the SAME
   expression that decides whether the hero carousel renders. That is the real
   condition: the carousel is the only thing that ever precedes this region, and it
   is suppressed on the front page, the contributor chrome, webform pages, error
   pages and both detail bundles (mti_choreographer, mti_dance_work). The previous
   version of this rule keyed off body.contributor-chrome instead, which covers
   entity.node.edit_form / add / latest_version but NOT entity.node.canonical —
   so staff, who land on the canonical page when they save, got no offset at all.
   DIG-004684 learned the same lesson on the 403/404 page and fixed it the same way.

   Fifth occurrence of this fixed-nav class of bug: DIG-004418, DIG-004684,
   DIG-004846, DIG-004861, DIG-004873.

   The toolbar variant is a separate rule rather than an unconditional
   var(--drupal-displace-offset-top, 0px) in the calc: displace.js publishes an
   offset whenever the toolbar is present, but the nav is only pushed down by it
   under body.toolbar-fixed (see the DIG-004846 block below). Mirroring that
   scoping exactly is what keeps the message aligned to the nav in both
   orientations instead of over-shifting in the vertical one. */
.page-messages--top .messages { margin-top: calc(var(--nav-height, 64px) + 24px); }
body.toolbar-fixed .page-messages--top .messages {
  margin-top: calc(var(--nav-height, 64px) + var(--drupal-displace-offset-top, 39px) + 24px);
}
.page-messages .messages--status { border-left-color: var(--main); }
.page-messages .messages--warning { border-left-color: var(--highlight); }
.page-messages .messages--error { border-left-color: var(--error); background: rgba(214,40,40,0.07); }
.page-messages .messages a { color: var(--main); text-decoration: underline; text-underline-offset: 2px; }
.page-messages .messages__list { margin: 6px 0 0 20px; }
.page-messages .messages__item { margin-bottom: 4px; }

/* ============================================================
   DRUPAL ADMIN TOOLBAR — stop our fixed nav sitting on top of it
   ============================================================
   Elizabeth Parkinson, 2026-09-01 call: "in the Drupal backend with the edit,
   is there any way to design it so that the edit buttons don't wind up behind
   the nav? ... can we drop it down so that I'll have to continuously just try
   and get the mouse to hit the edge of the link?"

   Both bars are position:fixed at top:0 — core's toolbar and this theme's .nav —
   so for any signed-in staff member the toolbar's Content / Structure /
   Configuration row and the contextual Edit pencil sit UNDER the Concert Dance
   header and are only clickable along whatever slice happens to stick out.

   Core's displace.js publishes the toolbar's real height as
   --drupal-displace-offset-top, which already accounts for an open tray, so
   offset by that rather than by a hardcoded bar height. The 39px fallback is
   core's own collapsed horizontal bar, used only if displace has not run yet.
   Scoped to body.toolbar-fixed, so anonymous visitors — every public page —
   are untouched. (DIG-004846; overlay correction DIG-004861)
   ============================================================ */
body.toolbar-fixed .nav {
  top: var(--drupal-displace-offset-top, 39px);
}

/* The two overlays must NOT have their `top` moved, and this is the whole
   correction to the first attempt at this fix.
   
   Both are `top: 0` with `transform: translateY(-100%)` when closed, and both
   are min-height 520px — so "off-screen" is computed FROM top. Setting
   `top: 80px` on the closed overlay slid 80px of it back into view, at z-index
   998 against the toolbar's 502, and it covered the very toolbar this rule
   exists to reveal. Tzipora Kaplan, 2026-09-02: "I'm logged in and this is what
   I see - no drupal edit and what looks like the hidden nav in the space where
   the Drupal functionality should be." The photo she could see was
   .search-overlay-photo.
   
   So leave the closed geometry alone and push the OPEN state down instead, which
   is the only state that needs to clear the toolbar. */
body.toolbar-fixed .nav-overlay.open,
body.toolbar-fixed .search-overlay.open {
  transform: translateY(var(--drupal-displace-offset-top, 39px));
}
body.toolbar-fixed .page-offset {
  padding-top: calc(var(--nav-height) + var(--drupal-displace-offset-top, 39px));
}
body.toolbar-fixed .no-hero-offset {
  margin-top: calc(var(--nav-height) + var(--drupal-displace-offset-top, 39px));
}

/* ============================================================
   NODE PREVIEW BAR — the only way back to the form
   ============================================================
   Tzipora Kaplan, 2026-09-02, after losing an upload: "I had added a photo here,
   but I went to preview it and then there was no way to get back to save it ...
   There was no way to save or go back to edit from here unless I hit this
   button, which doesn't work." And: "That's important because they're in there
   right now working."

   Core's preview bar carries "Back to content editing" — on a preview page it is
   the ONLY route back to the unsaved form. It is position:fixed at z-index 499;
   this theme's .nav is position:fixed at z-index 1000 and, since DIG-004846,
   top:80px. They occupy the same band, so the nav paints over the bar and the
   link is unclickable. Measured on live before the fix: bar at y 79-121, link at
   y 89-111, and elementFromPoint on the link's own centre returned the nav's
   logo. That is silent data loss — you preview, you cannot get back, your
   unsaved changes are gone.

   Two changes, deliberately belt-and-braces after DIG-004861:
   1. Raise the bar above the nav. This alone makes the link clickable and does
      not depend on :has() support.
   2. Where :has() is available, drop the nav below the bar so nothing is
      covered in either direction. Cosmetic; the fix does not rely on it.

   Nothing here touches the closed-overlay geometry that DIG-004861 was about.
   (DIG-004873)
   ============================================================ */
.node-preview-container {
  z-index: 1001;
}
body:has(.node-preview-container) .nav {
  top: calc(var(--drupal-displace-offset-top, 39px) + 42px);
}

/* DIG-004946. DIG-004873 above made the "Back to content editing" link
   CLICKABLE, which was the data-loss half. What it did not do is move the page:
   the nav drops below the bar, but the content underneath stays where it was, so
   the bar still lands on the BIOGRAPHY / DANCE WORKS & BALLETS row. Tzipora,
   8 Sep: "when you go into preview mode, it puts the back to content editing in
   the full above [the nav]" — and her fix, in her words, was to "bump the site
   down so the hero sits behind the nav and make the editing bar a grey wash
   across the full width."

   Both halves, and nothing outside preview is touched — every selector is
   behind `body:has(.node-preview-container)`, which only exists on
   entity.node.preview.

   42px is the same constant the nav offset above uses; if the bar's height ever
   changes, the two move together and must be changed together. */
body:has(.node-preview-container) .page,
body:has(.node-preview-container) #sharedCarousel,
body:has(.node-preview-container) .home-hero-solid {
  margin-top: 42px;
}

/* The grey wash. Core renders the bar as a bordered strip that stops at the
   content column, which is why it reads as floating over the page rather than
   as chrome belonging to it. Full-bleed, flat, and visibly NOT the site. */
body:has(.node-preview-container) .node-preview-container {
  left: 0;
  right: 0;
  width: 100%;
  max-width: none;
  background: #e9edf1;
  border-bottom: 1px solid rgba(4,48,77,0.18);
  box-shadow: none;
  padding-left: var(--page-pad, 24px);
  padding-right: var(--page-pad, 24px);
}

/* A choreographer hero with its own PHONE photograph (DIG-004875).

   The <picture> source hands the phone a portrait file that is already the right
   shape, so the desktop framing must NOT be applied to it — --cd-hero-pos is
   tuned for a 2.64:1 band and would re-crop a picture that needs no cropping.
   Under the breakpoint, centre it and let object-fit do the rest.

   Scoped to .cd-hero-img--has-phone so every hero WITHOUT a phone file keeps the
   existing behaviour untouched: one file, reframed by --cd-hero-pos-mobile.
   768px matches both the <source> media query and the object-position rule
   above; all three have to move together. */
@media (max-width: 768px) {
  .cd-hero-img--has-phone {
    object-position: var(--cd-hero-pos-mobile, center center);
  }
}

/* ============================================================
   NON-LIVE ENVIRONMENT MARKER (DIG-005067)
   ============================================================
   Set from mti_concert_dance_preprocess_html() as data-cd-env on <body>, and
   ONLY on dev/test/multidev — live carries no attribute, so this rule cannot
   fire there and there is nothing to strip at launch.

   Why a corner pill and not a top bar: the nav is fixed at var(--nav-height)
   and both overlays are position:fixed off the top with a toolbar-displacement
   offset on the open state. A banner in that band would have to be added to
   three separate offset calculations, and getting one wrong puts a panel over
   the Drupal toolbar again. This sits in a corner nothing else occupies, in its
   own stacking context, and changes no geometry.

   pointer-events:none so it can never intercept a click, and a z-index above
   the nav overlay (999) so it stays visible with the menu open — which is
   exactly when someone is navigating and most likely to be unsure where
   they are. */
body[data-cd-env]::after {
  content: attr(data-cd-env) " — NOT LIVE";
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 10000;
  pointer-events: none;
  padding: 7px 14px 6px;
  border-radius: 0 6px 0 0;
  background: #b3261e;
  color: #fff;
  font-family: var(--brandon), "Josefin Sans", sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  line-height: 1;
  box-shadow: 0 -1px 8px rgba(0, 0, 0, .35);
}

@media print {
  body[data-cd-env]::after { display: none; }
}

/* ============================================================
   DIG-005156. ONE CREDIT STYLE, CROSS-SITE.
   Tzipora, 23 Sep: "Whatever the typeface and the weight and the color is on the
   credit for the dance work, that's the way I would like it to appear cross-site.
   So wherever there's a credit or a photo or a caption style."
   Measured on live first: .dw-hero-credit is Nunito Sans 300, 13px, at both widths.
   The colour is --credit-ink, not --grey, for contrast (see :root).

   At the END of the file on purpose. The hero strip has mobile overrides (11px,
   name 500) inside the 768px query further up, and a same-specificity rule
   earlier in the file loses to them on source order. This has bitten this
   stylesheet four times.

   NOT included: .overlay-credit (the nav menu photo). She is still deciding
   whether it keeps a heavier weight against the background texture (16:48). */
.cd-credit-inner, .cd-credit-name, .cd-credit-company,
.dw-hero-credit, .person-portrait-credit, .news-photo-credit, .caption,
.mti-band-credit, .gallery-credit, .gallery-credit-desc,
.lightbox-caption-company, .lightbox-caption-credit {
  font-family: var(--effra); font-weight: 300; font-style: normal;
  font-size: 13px; color: var(--credit-ink); opacity: 1;
}
/* PHOTO comes back on the dance work page only, where the credit sits below the
   licence box, far from the photo. "I think we should put the word photo back in
   all caps... just add the style to the dance work page only." Same kicker style
   that was hidden everywhere on 18 Sep (DIG-005112), which stays hidden elsewhere. */
.dw-hero-credit .cd-credit-kicker { display: inline; margin-right: 6px; }

/* DIG-005109. The slot above the Description holding a PHOTO instead of a video:
   same 16:9 box, radius and 32px below as .dw-desc-video, so the Description
   starts on the same line whichever one is there. After the credit block above
   on purpose — .dw-hero-credit's margin must not win over this one. */
.dw-desc-photo { margin: 0 0 32px; }
.dw-desc-photo img {
  display: block; width: 100%; aspect-ratio: 16 / 9;
  object-fit: cover; border-radius: 4px;
}
.dw-desc-credit { margin: 8px 0 0; }
