/* =============================================================================
   PILATES HUNTER — detail modal & parallax
   -----------------------------------------------------------------------------
   1. Detail dialog (shared by class details and specialized programs)
   2. Parallax layer (Specialized Programs background)
   ========================================================================== */

/* 1. ------------------------------------------------------- DETAIL DIALOG */
.detail-dialog {
  width: min(92vw, 660px);
  max-height: min(88vh, 900px);
  margin: auto;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 30px 80px rgba(20, 15, 12, 0.35);
  overflow: hidden;
}
.detail-dialog::backdrop { background: rgba(20, 15, 12, 0.55); }
body:has(.detail-dialog[open]) { overflow: hidden; }

@media (prefers-reduced-motion: no-preference) {
  .detail-dialog[open] { animation: dialogIn 0.28s var(--ease); }
  .detail-dialog[open]::backdrop { animation: backdropIn 0.28s var(--ease); }
}

/* Duplicated from css/styles.css (section 9) on purpose: this file must be
   self-contained so it does not break if that dead .class-dialog block is
   ever removed. Identical duplicate @keyframes across files are harmless. */
@keyframes dialogIn {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
}
@keyframes backdropIn {
  from { opacity: 0; }
}

.detail-dialog__scroll {
  position: relative;
  max-height: inherit;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* `position: sticky` (not `absolute`) so the close button stays reachable
   while a long modal body scrolls inside .detail-dialog__scroll — an
   absolutely-positioned close button scrolls out of view with the rest of
   the content in that container. It still sits over the media image at rest:
   the negative bottom margin collapses the space it would otherwise reserve
   in the flow, and `margin-left: auto` on a flex box keeps it pinned to the
   right edge. */
.detail-dialog__close {
  position: sticky;
  top: 0.85rem;
  z-index: 2;
  display: flex;
  margin: 0.85rem 0.85rem -2.4rem auto;
  width: 2.4rem;
  height: 2.4rem;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), transform 0.2s var(--ease);
}
.detail-dialog__close:hover,
.detail-dialog__close:focus-visible { background: var(--accent-soft); transform: rotate(90deg); }

/* 16/9 suits the landscape class and program photos. The instructor profile
   shots are the opposite shape — every one is a portrait around 1707x2560 —
   and in a 16/9 frame roughly 62% of their height is cropped away, which cut
   the subject's head off. They get a portrait-leaning frame instead, set by
   js/programs.js when it opens an instructor. Framing within the frame is
   handled separately by the measured object-position focal points in
   js/data.js. */
.detail-dialog__media { aspect-ratio: 16 / 9; overflow: hidden; }
.detail-dialog__media--portrait {
  /* A fixed, shallower height instead of a ratio box. The image still fills
     the width and is still cropped by the shared rule below — what saves the
     subject's head is not this height but the measured focal point in
     js/data.js (profileFocal), which object-position applies. This height just
     keeps a 2:3 portrait from taking over the modal before the name and bio
     are reachable. */
  aspect-ratio: auto;
  height: min(46vh, 420px);
  background: var(--cream);
}
.detail-dialog__media img { width: 100%; height: 100%; object-fit: cover; }
.detail-dialog__body { padding: clamp(1.5rem, 4vw, 2.5rem); }
.detail-dialog__body .eyebrow { margin-bottom: 0.6rem; }
.detail-dialog__body .h2 { margin-bottom: 0.75rem; }
.detail-dialog__body .lead { margin-bottom: 0.5rem; }

.detail-dialog__sub {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 1.6rem 0 0.6rem;
}
.detail-dialog__list {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 0.4rem;
  font-size: 0.95rem;
  color: var(--ink-soft);
  line-height: 1.55;
}
.detail-dialog__list li::marker { color: var(--accent); }

.detail-dialog__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.75rem;
}

/* Ghost button on a light ground — the existing .btn--ghost-light is for dark */
.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
.btn--ghost:hover,
.btn--ghost:focus-visible { border-color: var(--accent); color: var(--accent); }

@media (max-width: 620px) {
  .detail-dialog__media { aspect-ratio: 3 / 2; }
  /* Restated inside the query: the rule above it would otherwise win by source
     order on a phone and put the portrait profile shots back into a landscape
     frame — the exact crop that cut heads off. */
  /* Restated inside the query: the rule above would otherwise win by source
     order on a phone and put these portraits back in a landscape ratio box. */
  .detail-dialog__media--portrait { aspect-ratio: auto; height: 52vh; }
  .detail-dialog__actions .btn { width: 100%; justify-content: center; }
}

/* 2. --------------------------------------------------------------- PARALLAX
   The layer is oversized and offset so it never reveals its edges as it moves.
   No `background-attachment: fixed` — it judders badly on iOS Safari. */
.parallax {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.parallax__layer {
  position: absolute;
  left: 0;
  top: -15%;
  width: 100%;
  height: 130%;
  object-fit: cover;
  will-change: transform;
}
.parallax__scrim {
  position: absolute;
  inset: 0;
  /* branch: must match --cream. Was rgba(250, 245, 240, …) — Kemang's warm
     off-white — which left a warm band sitting on the cool ground. */
  background: linear-gradient(180deg, rgba(246, 247, 243, 0.95), rgba(246, 247, 243, 0.88));
}

@media (prefers-reduced-motion: reduce) {
  .parallax__layer { transform: none !important; }
}

/* Specialized-program heroes: one full-width parallax band per program
   (js/programs.js renderProgramHeroes), deliberately not a card grid. Each
   reuses the .parallax/.parallax__layer/.parallax__scrim markup above, but
   the scrim here goes dark instead of the cream tone above: a program photo
   is the whole background (not a light box the copy sits inside), so white
   text needs real contrast rather than the studio's default ink-on-cream. */
.program-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  min-height: clamp(30rem, 76vh, 46rem);
  /* --pad-section, not a band-specific value: .program-hero carries the
     .section class, so it must sit on the page's one vertical rhythm like
     every other section. min-height still decides the band's height on a
     normal screen; this only sets the floor around the copy. */
  padding-block: var(--pad-section);
  color: #fff;
}
/* The horizontal ramp has to stay dark across the WHOLE text column, not just
   its first half. .program-hero__inner is max-width 34rem inside a 1200px
   container, so on a wide screen the copy still runs to roughly two-thirds of
   the viewport — where the previous ramp had already fallen to 0.05 and left
   white text sitting on whatever the photo happened to be. The program images
   are not art-directed for this (several are bright studio interiors shot
   against windows and pale walls), so the scrim, not the photograph, has to
   guarantee contrast. It holds past the text, then falls away quickly so the
   right-hand side of the image is still visible. */
.program-hero .parallax__scrim {
  background:
    linear-gradient(180deg, rgba(20, 15, 12, 0.3) 0%, rgba(20, 15, 12, 0.4) 60%, rgba(20, 15, 12, 0.55) 100%),
    linear-gradient(90deg, rgba(20, 15, 12, 0.76) 0%, rgba(20, 15, 12, 0.7) 50%, rgba(20, 15, 12, 0.46) 72%, rgba(20, 15, 12, 0.08) 92%);
}
/* Below the wide breakpoint the container is nearly the full viewport, so the
   copy spans it and a ramp that fades to the right would strand the ends of
   the longer lines. Darken evenly instead. */
@media (max-width: 860px) {
  .program-hero .parallax__scrim {
    background:
      linear-gradient(180deg, rgba(20, 15, 12, 0.42) 0%, rgba(20, 15, 12, 0.5) 60%, rgba(20, 15, 12, 0.62) 100%),
      linear-gradient(90deg, rgba(20, 15, 12, 0.5) 0%, rgba(20, 15, 12, 0.46) 100%);
  }
}

/* The band is full-bleed; its CONTENT is not. .program-hero__inner carries
   .container, but it also used to carry `max-width: 34rem` — which overrode
   .container's 1200px and then, as a centred flex item, parked the copy in the
   middle of the viewport. At 1280 the headings started at x=416 while every
   other section on the page started at x=88: the full-bleed exception had
   quietly become a full-bleed misalignment.
   .container's max-width and gutter are left alone now, so the left content
   edge is the page's; the 34rem measure that keeps these lines readable moves
   down onto the children, where it belongs. */
/* Where the dark band meets a light section, fade rather than cut. Only the
   OUTER edges of the run get this: hero 1's top (against #classes) and hero
   5's bottom (against #instructor). The joins BETWEEN heroes are dark-to-dark
   and must stay untouched — a light fade there would put a bright seam in the
   middle of the run, which is the banding this is meant to remove. Both
   neighbours end on --paper (#classes because .section--stone now fades out to
   it), so both fades go to --paper.
   These sit at z-index 0: above .parallax (-1) and below .program-hero__inner
   (1), inside .program-hero's own isolation. They do not touch the scrim in
   .parallax__scrim above, which is what actually guarantees white-on-photo
   contrast for the copy — the copy is vertically centred in a band at least
   30rem tall and these are --blend deep at each end, so they never overlap it. */
#programHeroes .program-hero:first-of-type::before,
#programHeroes .program-hero:last-of-type::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: var(--blend);
  z-index: 0;
  pointer-events: none;
}
#programHeroes .program-hero:first-of-type::before {
  top: 0;
  background: linear-gradient(180deg, var(--paper), rgba(255, 255, 255, 0));
}
#programHeroes .program-hero:last-of-type::after {
  bottom: 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0), var(--paper));
}

.program-hero__inner { position: relative; z-index: 1; }
.program-hero__inner > * { max-width: 34rem; }
.program-hero__inner .eyebrow { color: rgba(255, 255, 255, 0.92); text-shadow: 0 1px 3px rgba(12, 9, 7, 0.5); }
.program-hero__inner .h2 { text-shadow: 0 1px 2px rgba(12, 9, 7, 0.35), 0 4px 34px rgba(12, 9, 7, 0.45); }
/* Text shadows are a second line of defence behind the scrim above: they cost
   nothing on a dark ground and keep the smaller copy legible over the brighter
   patches of the studio photos. The label was rgba(...,0.7) — too faint for
   small uppercase text over a photograph — and is lifted to 0.85. */
.program-hero__inner .lead {
  margin-top: 0.75rem;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 3px rgba(12, 9, 7, 0.5);
}

.program-hero__label {
  margin-top: 1.75rem;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 1px 3px rgba(12, 9, 7, 0.5);
}
.program-hero__benefits {
  margin: 0.6rem 0 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 0.4rem;
  font-size: 0.95rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 1px 3px rgba(12, 9, 7, 0.5);
}
.program-hero__benefits li::marker { color: var(--accent); }

.program-hero .btn { margin-top: 1.75rem; }
