/* ==========================================================================
   Ventura Fitness Club. Layout, components, motion. Mobile first.
   LIGHT THEME (version1).

   Tokens live in tokens.css. Read the colour and type contract at the top of
   that file before changing anything here. Three rules carry most of it:

     - Lime is for actions and a short closed list of accents, nothing else.
     - On a light ground, lime as *text* is --accent-ink, not --lime.
     - Royal is punctuation, not wallpaper: three blocks on the home page
       (statement, membership, footer) and nothing else.

   No section invents a measurement. Width, gutter, rhythm, radius, shadow,
   card padding and control height all come from a token, which is the whole
   reason nine sections read as one page.
   ========================================================================== */

/* --- reset -------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* Set by scroll-restore.js for the few frames it takes to put a saved offset
   back. Smooth is right for an anchor the reader clicked and wrong for a
   position they never left. */
html.is-restoring { scroll-behavior: auto; }

body {
  background: var(--paper);
  color: var(--body);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; }
ul, ol { list-style: none; padding: 0; }
a { color: inherit; text-decoration: none; }
table { border-collapse: collapse; width: 100%; }

/* --- type roles ----------------------------------------------------------
   DISPLAY is every h1 to h4 plus the statement lines: Geist 700 on the big
   steps, tracked in as it grows. SANS is everything else. MONO is metadata
   and is opted into by .eyebrow, .tagchip, .label-mono and the numeric
   classes, never by a block of prose. */
h1, h2, h3, h4 {
  color: var(--ink);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: var(--track-display);
  text-wrap: balance;
}
h3, h4 { font-weight: 600; letter-spacing: var(--track-tight); }
p { text-wrap: pretty; }

/* Headings inside a solid blue block are the one place that stays white. */
.section--royal :is(h1, h2, h3, h4),
.statement :is(h1, h2, h3, h4),
.site-foot :is(h1, h2, h3, h4),
.hero :is(h1, h2, h3, h4) { color: #fff; }

:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; border-radius: var(--r-xs); }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: 50%; top: 0; transform: translate(-50%, -120%);
  background: var(--lime); color: var(--lime-ink);
  padding: 0.65rem 1.1rem; border-radius: 0 0 var(--r) var(--r);
  font-weight: 600; z-index: 100;
}
.skip:focus { transform: translate(-50%, 0); }

.head-sentinel { position: absolute; top: 0; height: 1px; width: 100%; pointer-events: none; }

/* Every in-page link would otherwise land under the sticky header. */
:target, section[id] { scroll-margin-top: calc(var(--head-h) + 1rem); }

/* Times, prices and durations sit in columns and get compared down the page. */
.hours td, .slot__time, .panel__menu dd, .sched__time, .promo__price span,
.recurring__price span { font-variant-numeric: tabular-nums; }


/* ==========================================================================
   ZONE IDENTITY
   Contained sub-system: the map, its legend, the zone cards, the class cards
   and the zone page headers. Components read --zone and --zone-ink only, so
   adding a zone touches this block alone.
   ========================================================================== */
[data-zone="train"],     [data-active="train"]     { --zone: var(--z-train);     --zone-ink: var(--z-train-ink); }
[data-zone="move"],      [data-active="move"]      { --zone: var(--z-move);      --zone-ink: var(--z-move-ink); }
[data-zone="play"],      [data-active="play"]      { --zone: var(--z-play);      --zone-ink: var(--z-play-ink); }
[data-zone="recover"],   [data-active="recover"]   { --zone: var(--z-recover);   --zone-ink: var(--z-recover-ink); }

/* ==========================================================================
   SHARED TYPE OBJECTS
   The three mono roles on the site. Nothing else is allowed to be mono.
   ========================================================================== */

/* Above a heading. Names the section. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent-ink);
}

/* Inside a card or a panel. Names a column of facts. */
.label-mono {
  font-family: var(--font-mono); font-size: var(--step--1); font-weight: 400;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink);
}

/* A small outlined chip for a fact about a row: "Virtual" on a pre-recorded
   cycling release. Drawn in currentColor, so the same chip is legible on any
   surface without a surface-specific rule, and it borrows no colour that
   means something else. */
.tagchip {
  display: inline-block;
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: 0.1em; text-transform: uppercase;
  line-height: 1.6; padding: 0.1em 0.5em;
  border: 1px solid currentColor; border-radius: var(--r-pill);
  opacity: 0.85;
}

/* A filled chip. A badge on a card: "Mejor valor", "Proxima". */
.pillchip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: 0.1em; text-transform: uppercase; font-weight: 500;
  padding: 0.3em 0.7em; border-radius: var(--r-pill);
  background: var(--lime); color: var(--lime-ink);
  white-space: nowrap;
}
.pillchip--quiet {
  background: transparent; color: var(--faint);
  box-shadow: inset 0 0 0 1px var(--rule-mid);
}

.hours { font-size: var(--step-0); border-top: 1px solid var(--rule); }
.hours th { text-align: left; font-weight: 400; color: var(--body); padding: 0.7rem 0; border-bottom: 1px solid var(--rule); }
.hours td { text-align: right; color: var(--ink); font-family: var(--font-mono); font-size: var(--step--1); padding: 0.7rem 0; border-bottom: 1px solid var(--rule); }

/* ==========================================================================
   BUTTONS
   One CTA language for the whole site. Three weights, one meaning each:

     .btn--lime      the primary action. Lime fill, dark ink, pill.
     .btn--outline   the secondary action. Hairline, takes ink from the surface.
     .btn--text      the tertiary action. A link with an arrow, no box.

   Lime is reserved for two intentions and no others: JOIN THE CLUB and COME TO
   THE CLUB. Exploring, navigating and reading a document are outline or text.
   That is why there is at most one full-weight lime button in any viewport; the
   small persistent one in the header is the same intention, at header scale.

   Buttons compose magnetic pull and press scale through custom properties
   rather than competing transform rules.
   ========================================================================== */
.btn {
  --mx: 0px; --my: 0px; --press: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  min-height: var(--btn-h); padding: 0.75rem 1.6rem;
  font-size: var(--step-0); font-weight: 600; letter-spacing: var(--track-tight);
  border: 1px solid transparent; border-radius: var(--r-pill);
  cursor: pointer; text-align: center; white-space: nowrap;
  transform: translate(var(--mx), var(--my)) scale(var(--press));
  transition:
    transform var(--t-press) var(--ease-out),
    background-color 180ms ease,
    border-color 180ms ease,
    box-shadow 180ms ease,
    color 180ms ease;
}
.btn:active { --press: 0.97; }

.btn--lime {
  background: var(--lime); color: var(--lime-ink); border-color: var(--lime);
  box-shadow: 0 1px 2px rgb(11 14 20 / 0.06);
}
.btn--outline { background: transparent; color: var(--ink); border-color: var(--rule-strong); }
.section--royal .btn--outline,
.statement .btn--outline,
.site-foot .btn--outline,
.hero .btn--outline { color: #fff; border-color: var(--rule-royal); }

/* The tertiary weight. An underline that lights up and an arrow that moves,
   and nothing else: it must never compete with the lime button beside it. */
.btn--text {
  min-height: var(--btn-h); padding: 0.5rem 0.15rem;
  border: 0; border-radius: 0; background: none;
  color: var(--ink); font-weight: 600;
  position: relative;
}
.hero .btn--text, .section--royal .btn--text, .statement .btn--text { color: #fff; }
.btn--text::before {
  content: ""; position: absolute; left: 0.15rem; right: 0.15rem; bottom: 0.55rem;
  height: 1px; background: currentColor; opacity: 0.35;
  transition: opacity 180ms ease;
}
.btn--arrow::after { content: "\2192"; transition: transform 240ms var(--ease-out); }

.btn--sm { min-height: var(--btn-h-sm); padding: 0.4rem 1.05rem; font-size: var(--step--1); }
.btn--lg { min-height: var(--btn-h-lg); padding: 0.9rem 2.1rem; font-size: var(--step-1); }
.btn--block { display: flex; width: 100%; }

@media (hover: hover) and (pointer: fine) {
  .btn--lime:hover { background: var(--lime-deep); border-color: var(--lime-deep); box-shadow: var(--shadow-md); }
  .btn--outline:hover { border-color: var(--accent-ink); color: var(--accent-ink); }
  .btn--text:hover { color: var(--accent-ink); }
  .btn--text:hover::before { opacity: 1; }
  .btn--arrow:hover::after { transform: translateX(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .btn { transform: none; transition: background-color 180ms ease, border-color 180ms ease, color 180ms ease; }
  .btn--arrow::after { transition: none; }
}

.cta-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-sm) var(--gap-md); }
.cta-row--center { justify-content: center; }

/* ==========================================================================
   PLACEHOLDERS
   Deliberately plain and clearly labelled, so nobody mistakes the prototype
   for finished art direction.
   ========================================================================== */
.ph {
  position: relative;
  background: var(--paper-3);
  border: 1px dashed var(--rule-strong);
  border-radius: var(--r);
  display: grid; place-items: center;
  min-height: 160px; padding: 1.25rem;
  overflow: hidden;
}
.ph__tag {
  position: relative; z-index: 1;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--faint);
  text-align: center; max-width: 28ch; line-height: 1.45;
}
.ph--bleed { border: 0; border-radius: 0; }
.section--royal .ph { background: rgb(0 0 0 / 0.22); border-color: var(--rule-royal); }
.section--royal .ph__tag { color: rgb(255 255 255 / 0.6); }

/* ==========================================================================
   PHOTO SLOTS
   The filled counterpart of a placeholder. `.shot` and `.ph` are sized by the
   same modifier rules further down, so a slot occupies the same space whether
   the photograph has arrived or not and filling one cannot reflow the page.
   ========================================================================== */
.shot {
  position: relative;
  display: grid;
  /* One row that is allowed to SHRINK. Without the minmax the row sizes to the
     photograph's own aspect ratio, so in any slot with a fixed height the img
     was laid out taller than its frame and overflow:hidden simply cut the
     bottom off: a top crop, not the centred cover crop object-fit is there to
     do. The map panel was losing 170px off the foot of every photograph. */
  grid-template-rows: minmax(0, 1fr);
  overflow: hidden;
  border-radius: var(--r);
  background: var(--paper-3);
}
.shot > img {
  grid-area: 1 / 1;
  width: 100%; height: 100%;
  min-width: 0; min-height: 0;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* ==========================================================================
   MOTION LAYER
   Gated behind .js so a failed module leaves a fully readable page.
   ========================================================================== */
.js .reveal {
  opacity: 0; transform: translateY(14px);
  animation: rise var(--t-reveal) var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 70ms);
}
@keyframes rise { to { opacity: 1; transform: none; } }

.js [data-reveal] {
  opacity: 0; transform: translateY(18px);
  transition: opacity var(--t-reveal) var(--ease-out), transform var(--t-reveal) var(--ease-out);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

/* Containers whose children stagger instead of the container moving. */
.js .strip[data-reveal],
.js .promocards[data-reveal],
.js .zonecards[data-reveal] { opacity: 1; transform: none; }

.js .strip[data-reveal] > *,
.js .promocards[data-reveal] > *,
.js .zonecards[data-reveal] > * {
  opacity: 0; transform: translateY(16px);
  transition: opacity var(--t-reveal) var(--ease-out), transform var(--t-reveal) var(--ease-out);
}
.js .strip.is-in > *,
.js .promocards.is-in > *,
.js .zonecards.is-in > * {
  opacity: 1; transform: none;
  transition-delay: calc(var(--i, 0) * 45ms);
}

/* Reduced motion keeps every fade, which aids comprehension, and drops every
   translation, which is the part that causes trouble. */
@media (prefers-reduced-motion: reduce) {
  .js .reveal { transform: none; animation-name: fadeOnly; }
  @keyframes fadeOnly { to { opacity: 1; } }
  .js [data-reveal],
  .js .strip[data-reveal] > *,
  .js .promocards[data-reveal] > *,
  .js .zonecards[data-reveal] > * { transform: none; }
}

/* ==========================================================================
   HEADER
   Compact by design and more compact once it sticks. It is on screen for the
   entire page, so every pixel it takes is a pixel the content does not get.
   One highlighted action only.
   ========================================================================== */
.site-head {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 0.75rem;
  min-height: var(--head-h);
  padding: calc(0.5rem + env(safe-area-inset-top)) var(--gutter) 0.5rem;
  background: rgb(255 255 255 / 0.74);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition:
    border-color 220ms ease, background-color 220ms ease,
    min-height 220ms var(--ease-out), padding 220ms var(--ease-out),
    box-shadow 220ms ease;
}
.site-head.is-stuck {
  --head-h: 54px;
  border-bottom-color: var(--rule);
  background: rgb(255 255 255 / 0.93);
  box-shadow: 0 1px 12px rgb(11 14 20 / 0.05);
  padding-top: calc(0.35rem + env(safe-area-inset-top));
  padding-bottom: 0.35rem;
}

@media (prefers-reduced-transparency: reduce) {
  .site-head { background: var(--paper); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* The mark keeps its own colours: no site token is applied here on purpose. */
.brand { margin-right: auto; display: flex; align-items: center; }
.brand__logo {
  height: 42px; width: auto; display: block;
  transition: height 220ms var(--ease-out);
}
.is-stuck .brand__logo { height: 36px; }
@media (min-width: 900px) {
  .brand__logo { height: 52px; }
  .is-stuck .brand__logo { height: 42px; }
}

.site-head__nav { display: none; gap: 1.6rem; font-size: var(--step--1); font-weight: 500; }
.site-head__nav a {
  position: relative; padding: 0.45rem 0; color: var(--body);
  transition: color 180ms ease;
}
/* The underline grows from the left rather than appearing whole. A border
   that blinks on reads as a state change; this reads as a response. */
.site-head__nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0.15rem; height: 2px;
  background: var(--accent-ink); border-radius: 1px;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-hover) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .site-head__nav a:hover { color: var(--ink); }
  .site-head__nav a:hover::after { transform: scaleX(1); }
}
.site-head__nav a:focus-visible { color: var(--ink); }
.site-head__cta { display: none; }
@media (prefers-reduced-motion: reduce) {
  .site-head, .brand__logo { transition: border-color 220ms ease, background-color 220ms ease; }
  .site-head__nav a::after { transition: none; }
}

/* Language toggle. Named for the language you would switch TO. Not a .btn:
   lime means "the main action" on this site, and changing language is not it.
   Visible at every width, unlike the nav and the CTA, because a Spanish
   speaker landing on an English page needs the way out on a phone most.
   `hidden` until JS wires it. */
.langtoggle {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: var(--btn-h-sm); padding: 0.3rem 0.7rem;
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.06em;
  color: var(--body);
  border: 1px solid var(--rule-mid); border-radius: var(--r-pill);
  cursor: pointer;
  transition: color 180ms ease, border-color 180ms ease, background-color 180ms ease;
}
.langtoggle[hidden] { display: none; }
@media (hover: hover) and (pointer: fine) {
  .langtoggle:hover { color: var(--ink); border-color: var(--rule-strong); background: var(--paper-2); }
}

@media (min-width: 800px) {
  .site-head__nav { display: flex; }
  .site-head__cta { display: inline-flex; }
}

/* ==========================================================================
   SECTIONS
   Tonal steps inside one light theme: white, one grey tint, and three royal
   blocks on the whole home page. The alternation is the rhythm:

     hero (dark) - zones (white) - statement (ROYAL) - map (tint) -
     today (white) - promos (tint) - membership (ROYAL) - visit (white) -
     final (tint) - footer (ROYAL)
   ========================================================================== */
.section { padding: var(--section-y) var(--gutter); }
.section--tint { background: var(--paper-2); }
.section--royal { background: var(--royal); color: rgb(255 255 255 / 0.84); }
.section--royal .section__lede { color: rgb(255 255 255 / 0.84); }
/* A hairline where white meets tint, so two neighbouring light sections still
   read as two sections rather than as one long scroll. */
.section--seam { border-top: 1px solid var(--rule); }

.section__head { max-width: var(--maxw); margin: 0 auto var(--head-gap); }
.section__head--row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--gap-md); flex-wrap: wrap;
}
.section__title { font-size: var(--step-3); max-width: 20ch; }
.section__title--wide { max-width: 26ch; }
.eyebrow + .section__title { margin-top: 0.85rem; }
.section__lede { margin-top: 1.1rem; max-width: 56ch; font-size: var(--step-1); }
.section__foot { max-width: 60ch; margin: 1.75rem auto 0; font-size: var(--step--1); color: var(--faint); }
.section--royal .section__foot { color: rgb(255 255 255 / 0.7); }
.section__foot--start { margin-inline: 0; }

/* The club voice. A short brand line under a section head, in sans because it
   is a sentence. Used four times on the page and never more. */
.clubline {
  font-size: var(--step-1); font-weight: 600; color: var(--ink);
  letter-spacing: var(--track-tight);
}
.section--royal .clubline, .statement .clubline, .hero .clubline { color: #fff; }

/* ==========================================================================
   1. HERO
   A single background photograph of the club floor, covering the frame. See
   the markup note in index.html for the film slot it replaced.

   The headline is the hero. The V-diagonal that used to sit beside it at full
   strength is now an oversized watermark, cropped off the right edge and held
   at low opacity: recognisable as the mark, incapable of competing with the
   type. One primary action, one text link, and the WhatsApp button that used
   to be a second full-weight CTA here is the floating one that follows the
   reader down the whole page anyway.
   ========================================================================== */
.hero {
  position: relative;

  /* EXACTLY ONE VIEWPORT, HEADER INCLUDED.

     The header is sticky, which means it is still in flow: it occupies real
     height above the hero. So a hero of 100dvh would put the next section at
     100dvh PLUS the header, and the reader would arrive at the fold with a
     sliver of the hero still to scroll past. The height therefore subtracts
     the header, and header + hero comes to exactly 100dvh: the first section
     below starts precisely at the fold.

     --head-real is published by stickyHeader() in chrome.js, measured off the
     real element, because the header is content-sized (42px logo on a phone,
     52px on a desktop) and no constant is right at both. The 60px fallback is
     the token value, used for the frames before the script runs and on the
     rare browser where it never does.

     dvh over vh, because mobile browser chrome makes vh the WRONG number for
     most of a page's life. The vh line above it is the fallback for engines
     that do not know dvh, and is overwritten wherever dvh is understood. */
  height: calc(100vh - var(--head-real, 60px));
  height: calc(100dvh - var(--head-real, 60px));
  /* A floor for absurdly short windows, where clipping would be worse than
     letting the hero run a little past the fold. Measured: the copy plus its
     padding needs about 292px at the smallest type step, so 300px is the point
     below which the exact fit has to give way. Every viewport taller than about
     360px still lands on the fold precisely. */
  min-height: 300px;

  display: flex; align-items: flex-end;
  padding: clamp(1rem, 2.5vw, 1.75rem) var(--gutter) clamp(1.75rem, 3.5vw, 3rem);
  overflow: hidden;
  isolation: isolate;
}
/* The bed under the film. Dark on purpose even though the page is light: it
   is what the poster fades in over and what shows through if the file 404s,
   and the headline on top of it is white either way. */
.hero__bg {
  position: absolute; inset: 0; z-index: -2;
  background: var(--ink);
}
/* The hero photograph. It fills the bed and crops, never letterboxes: the
   frame is whatever the viewport is and the image covers it. Wallpaper, so it
   is never a tab stop. */
.hero__img, .hero__video {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
}
/* A flat scrim plus one soft foot, so the CTA row at the bottom of the frame
   keeps its contrast over bright footage without the whole image going grey. */
.hero__bg::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to top, rgb(8 9 11 / 0.55) 0%, rgb(8 9 11 / 0) 42%),
    var(--hero-scrim);
}
.hero__accent {
  position: absolute; z-index: -1;
  right: -12%; bottom: -5%;
  width: min(78vw, 560px); height: auto; aspect-ratio: 1;
  opacity: 0.09;
  pointer-events: none;
}
.hero__arm--royal { fill: var(--royal-lift); }
.hero__arm--lime { fill: var(--lime); }

.js .hero__accent {
  clip-path: inset(100% 0 0 0);
  animation: wipeUp 1100ms var(--ease-out) 260ms forwards;
}
@keyframes wipeUp { to { clip-path: inset(0 0 0 0); } }
@media (prefers-reduced-motion: reduce) {
  .js .hero__accent { clip-path: none; animation: none; }
}

.hero__copy { max-width: var(--maxw); margin-inline: auto; width: 100%; }

/* The two supporting sizes, named once so the height ladder below can put a
   ceiling on them rather than substitute its own value. */
.hero {
  --hero-sub: clamp(1.15rem, 0.95rem + 0.85vw, 1.75rem);
  --hero-tag: clamp(1.05rem, 0.92rem + 0.55vw, 1.4rem);
}
.hero__eyebrow {
  color: var(--lime); margin-bottom: 0.9rem;
  font-size: clamp(0.78rem, 0.7rem + 0.3vw, 1rem);
}
.hero__title {
  font-size: var(--step-5); display: grid; gap: 0.02em;
  font-weight: 700; max-width: 15ch;
}
/* Three words, three lime rules. Sans, not mono: mono is for metadata on this
   site and these are the club in three words. */
.hero__sub {
  margin-top: clamp(1.1rem, 2.4vw, 1.75rem);
  display: flex; flex-wrap: wrap; gap: 0.4rem 1.75rem;
  font-size: var(--hero-sub); font-weight: 600;
  color: #fff; letter-spacing: var(--track-tight);
}
.hero__sub span { position: relative; padding-bottom: 0.35rem; }
.hero__sub span::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--lime); border-radius: 1px;
}
.hero__tagline {
  margin-top: 0.9rem;
  font-size: var(--hero-tag);
  color: rgb(255 255 255 / 0.86); max-width: 32ch;
}
.hero .cta-row { margin-top: clamp(1.35rem, 2.6vw, 1.9rem); }

@media (min-width: 900px) {
  .hero__accent { right: -5%; bottom: -4%; width: min(40vw, 600px); }
}

/* SHORT WINDOWS. The hero is now a fixed box, so on a short screen the
   question is not how tall the section is, it is whether the type still fits
   inside it. These step the display size down against the viewport HEIGHT
   rather than its width, which is the axis that is actually short: a 1440 x
   620 laptop needs a smaller headline than a 1440 x 1080 monitor, and a width
   query cannot tell them apart. */
@media (max-height: 900px) {
  .hero__title { font-size: min(var(--step-5), 9.6vh); }
}
@media (max-height: 700px) {
  .hero__title { font-size: min(var(--step-5), 9vh); }
  .hero__sub { font-size: min(var(--hero-sub), 3.6vh); }
  .hero__tagline { font-size: min(var(--hero-tag), 3vh); margin-top: 0.7rem; }
  .hero__eyebrow { margin-bottom: 0.6rem; }
  .hero .cta-row { margin-top: 1.1rem; }
}
@media (max-height: 560px) {
  .hero__title { font-size: min(var(--step-5), 7.8vh); }
  .hero__sub { margin-top: 0.7rem; font-size: min(var(--hero-sub), 3.2vh); }
  .hero__tagline { margin-top: 0.5rem; font-size: min(var(--hero-tag), 2.8vh); }
  .hero .cta-row { margin-top: 0.85rem; }
  .hero .btn--lg {
    min-height: var(--btn-h); padding: 0.7rem 1.5rem; font-size: var(--step-0);
  }
}
@media (min-width: 1440px) {
  .hero__accent { right: 2%; }
}

/* ==========================================================================
   2. THE FIVE WORLDS
   Five zones, presented as places rather than as five identical service tiles.
   Each card is a photograph with the zone number set over it, and the row
   breaks 3 + 2 on a wide screen so the set reads as an edited spread instead
   of a catalogue row. Same card, same radius, same hover: the variation is in
   the crop, not in the system.
   ========================================================================== */
.zonecards {
  max-width: var(--maxw); margin-inline: auto;
  display: grid; gap: var(--gap-md);
  grid-template-columns: 1fr;
}
.zonecard { min-width: 0; }
.zonecard__link {
  display: flex; flex-direction: column; height: 100%;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition:
    transform var(--t-hover) var(--ease-out),
    box-shadow var(--t-hover) var(--ease-out),
    border-color var(--t-hover) ease;
}
/* The media well. A fixed ratio rather than a min-height, so the cards in a
   row crop identically whatever their photograph happens to be. */
.zonecard__media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.zonecard__media .shot,
.zonecard__media .ph {
  position: absolute; inset: 0;
  border-radius: 0; min-height: 0; height: 100%;
}
.zonecard__media .shot > img { height: 100%; transition: transform 620ms var(--ease-out); }
.zonecard__body {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 0.3rem; padding: var(--card-pad);
  flex: 1;
}
.zonecard__tag {
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--zone-ink);
}
.zonecard__name { font-size: var(--step-2); color: var(--ink); }
.zonecard__go {
  margin-top: auto; padding-top: 0.9rem;
  font-size: var(--step--1); font-weight: 600; color: var(--ink);
  display: inline-flex; align-items: center; gap: 0.4rem;
}
.zonecard__go::after { content: "\2192"; transition: transform 220ms var(--ease-out); }

@media (hover: hover) and (pointer: fine) {
  .zonecard__link:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
    border-color: var(--rule-mid);
  }
  .zonecard__link:hover .zonecard__media .shot > img { transform: scale(1.05); }
  .zonecard__link:hover .zonecard__go { color: var(--accent-ink); }
  .zonecard__link:hover .zonecard__go::after { transform: translateX(4px); }
}
.zonecard__link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .zonecard__link, .zonecard__media .shot > img, .zonecard__go::after { transition: none; }
  .zonecard__link:hover { transform: none; }
}

/* Four zones, so the row is a 2 x 2 spread rather than the 3 + 2 break the
   five needed. Half the measure per card is what keeps each one reading as a
   place rather than as a tile: at the full 1280 a card is about 620px wide and
   its photograph is a proper landscape frame, not a thumbnail. */
@media (min-width: 620px) { .zonecards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) {
  .zonecards { gap: var(--gap-lg); }
  /* The club's photographs are all wide interiors. A taller crop at this size
     would cut the room in half, so the frame widens instead. */
  .zonecard__media { aspect-ratio: 16 / 9; }
}

/* ==========================================================================
   2b. VENTAJAS VENTURA
   The perks strip. It sits directly under the four zone cards and has to read
   as a different KIND of thing, or it becomes a fifth world by association.

   So every signal the zone cards use is deliberately absent: no photograph
   leading the block, no number, no zone hue, no shadow, no card at all. What
   it uses instead is a ruled list, an outline icon and one supporting photo
   strip at the foot. Cards say "go here"; ruled lines say "this is what you
   get", which is what these six facts are.
   ========================================================================== */
.perks { max-width: var(--maxw); margin-inline: auto; display: grid; gap: var(--gap-xl); }

.perks__list {
  display: grid; gap: var(--gap-lg) var(--gap-xl);
  grid-template-columns: minmax(0, 1fr);
}
.perk {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.9rem; row-gap: 0.3rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule-mid);
}
.perk__icon {
  width: var(--icon); height: var(--icon);
  grid-row: 1 / span 2; margin-top: 0.15rem;
  color: var(--accent-ink);
  flex: none;
}
.perk__title { font-size: var(--step-0); font-weight: 600; color: var(--ink); }
.perk__body { font-size: var(--step--1); color: var(--faint); line-height: 1.5; }

/* Support, not the headline. Three wide frames on one line at the foot of the
   section, all cropped to the same ratio so three very different rooms do not
   set three different heights. */
.perks__photos {
  display: grid; gap: var(--gap-sm);
  grid-template-columns: minmax(0, 1fr);
}
.perks__shot { min-width: 0; }
.shot--perk, .ph--perk {
  aspect-ratio: 16 / 9;
  min-height: 0;
  border-radius: var(--r-lg);
}

@media (min-width: 560px) {
  .perks__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .perks__photos { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .perks__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ==========================================================================
   3. STATEMENT
   The first solid block of colour on the page and the first of only three.
   It exists to change the pace between the card spread and the map.
   ========================================================================== */
.statement {
  background: var(--royal);
  padding: clamp(3.5rem, 8vw, 7rem) var(--gutter);
  position: relative; overflow: hidden;
}
/* The mark, again as a watermark, cropped hard off the right edge. Same idea
   as the hero, at a fraction of the strength: it makes the block feel owned
   rather than filled. */
.statement::after {
  content: ""; position: absolute; z-index: 0;
  right: -10%; top: 50%; translate: 0 -50%;
  width: min(60vw, 420px); aspect-ratio: 1;
  background: radial-gradient(circle at 50% 50%, rgb(255 255 255 / 0.10), transparent 62%);
  pointer-events: none;
}
.statement__inner { position: relative; z-index: 1; max-width: var(--maxw); margin-inline: auto; }
.statement .eyebrow { color: var(--lime); }
.statement__line {
  margin-top: 1.1rem;
  font-size: var(--step-3); line-height: 1.1; letter-spacing: var(--track-display);
  color: #fff; font-weight: 700; max-width: 22ch;
}
.statement__tag {
  margin-top: 1.5rem; padding-top: 1.5rem;
  border-top: 1px solid var(--rule-royal);
  font-size: var(--step-1); font-weight: 600; color: var(--lime);
  letter-spacing: var(--track-tight);
  max-width: 30ch;
}

/* ==========================================================================
   4. ISOMETRIC MAP
   The most distinctive thing on the site, so it is given the room to be it.
   The drawing leads, the legend sits directly under the heading as a filter
   row, and the detail card is a proper white card beside it rather than a
   panel stranded in white space.
   ========================================================================== */
.isomap { max-width: var(--maxw); margin-inline: auto; }

.isomap__legend {
  display: flex; flex-wrap: wrap; gap: var(--gap-xs);
  margin-bottom: var(--gap-lg);
}
.legend {
  display: inline-flex; align-items: center; gap: 0.55rem;
  min-height: 42px; padding: 0.4rem 1rem;
  background: var(--paper);
  border: 1px solid var(--rule-mid); border-radius: var(--r-pill);
  font-size: var(--step--1); font-weight: 500; color: var(--body); cursor: pointer;
  transition: border-color 180ms ease, color 180ms ease, background-color 180ms ease, box-shadow 180ms ease;
}
/* The swatch is the map key. It carries meaning, so it stays visible whether
   or not the zone is selected, and it keeps the true zone hue rather than the
   ink variant. The inset hairline stops the pale sand swatch dissolving. */
.legend__swatch {
  width: 0.7rem; height: 0.7rem; background: var(--zone); border-radius: 2px; flex: none;
  box-shadow: inset 0 0 0 1px rgb(11 14 20 / 0.2);
  transition: transform 180ms var(--ease-out);
}
.legend[aria-pressed="true"] {
  color: var(--ink); border-color: var(--zone-ink);
  background: color-mix(in srgb, var(--zone) 12%, #fff);
  box-shadow: inset 0 0 0 1px var(--zone-ink);
}
.legend[aria-pressed="true"] .legend__swatch { transform: scale(1.15); }
@media (hover: hover) and (pointer: fine) {
  .legend:hover { color: var(--ink); border-color: var(--rule-strong); }
}

.isomap__grid { display: grid; gap: var(--gap-lg); }

/* The stage centres the drawing rather than stretching it. The map is vector
   art with a fixed projection, so left to itself it takes whatever height its
   column width implies. Capping the drawing's width caps its height, because
   the ratio is constant: --iso-cap is the tallest the map may ever be, and
   1.673 is the projection ratio of the four block campus (880 / 526, computed
   from MAP_BLOCKS). Move a block in data.js and this number moves with it. */
.isomap__stage {
  min-width: 0;
  --iso-cap: min(44svh, 330px);
  display: grid; align-content: center; justify-items: center;
  gap: 0.9rem;
}
.iso {
  width: 100%; height: auto; overflow: visible;
  max-width: calc(var(--iso-cap) * 1.673);
}

.isomap__hint {
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--faint);
  text-align: center; letter-spacing: 0.02em;
}

/* --- blocks ---------------------------------------------------------------
   Three flat faces per block. The two side faces are mixed toward black so
   the solid reads as lit from above, which is the entire 3D illusion. The
   hairline on every face is what keeps the pale sand block and the light tops
   of the azure and teal blocks from losing their silhouette on a light page. */
.iso__block {
  cursor: pointer;
  /* A tap on a block used to flash a grey rectangle over it: Android and iOS
     paint a highlight on the element's bounding BOX, and the bounding box of an
     isometric cuboid is a square that bears no relation to its silhouette. The
     body sets this on itself, but an SVG <g> with a tabindex does not reliably
     inherit it, so it is repeated here where it is actually needed. */
  -webkit-tap-highlight-color: transparent;
  /* Same square, drawn by the UA focus ring when the block is focused by a
     click or a tap. Removed for EVERY state here and put back deliberately
     below for keyboard focus, which is the only case where a focus indicator
     is doing a job. */
  outline: none;
}
.iso__body { transition: transform var(--t-lift) var(--ease-out); }
.iso__face {
  transition: fill 220ms var(--ease-out);
  stroke: rgb(11 14 20 / 0.16); stroke-width: 1; stroke-linejoin: round;
}
.iso__face--top   { fill: var(--zone); }
.iso__face--left  { fill: color-mix(in srgb, var(--zone) 62%, #000); }
.iso__face--right { fill: color-mix(in srgb, var(--zone) 40%, #000); }

/* Contact shadow, so a lifted block does not look like it is floating free. */
.iso__shadow {
  fill: var(--ink); opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--t-lift) var(--ease-out);
}

.iso__ring {
  fill: none; stroke: var(--zone-ink); stroke-width: 2;
  opacity: 0; pointer-events: none;
  transform-box: fill-box; transform-origin: center;
}

.iso__label {
  font-family: var(--font-mono); font-size: 19px; font-weight: 500;
  letter-spacing: 2px; text-anchor: middle;
  fill: rgb(0 0 0 / 0.82);
  dominant-baseline: middle;
  pointer-events: none;
}

/* Selection lifts the chosen block and dims the rest, so the campus still
   reads as one place rather than one lit room. */
.isomap[data-active] .iso__block { transition: opacity 220ms var(--ease-out); }
.isomap[data-active] .iso__block:not(.is-active) { opacity: 0.58; }

.iso__block.is-active .iso__body { transform: translateY(-12px); }
.iso__block.is-active .iso__shadow { opacity: 0.2; }
.iso__block.is-active .iso__face--top {
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--zone) 75%, transparent));
}

/* One shot on select. Not a loop: a block that keeps pulsing reads as alert. */
.iso__block.is-pulsing .iso__ring { animation: isoPulse 640ms var(--ease-out); }
@keyframes isoPulse {
  from { opacity: 0.85; transform: scale(0.98); }
  to   { opacity: 0; transform: scale(1.12); }
}

/* KEYBOARD FOCUS. Not removed, moved: instead of a rectangle around the
   block's bounding box, the block's own ring lights up in the zone's colour
   with a soft glow behind it. It traces the top face, so it follows the shape
   the reader is actually pointing at, and it is the same object the selection
   pulse already uses. Two signals, one shape.

   A pointer never reaches this rule: :focus-visible only matches when the
   browser judges the focus worth showing, which for a mouse click on a
   div-like element it does not. So the ring is a keyboard affordance and the
   click stays clean. */
.iso__block:focus-visible {
  outline: none;
}
.iso__block:focus-visible .iso__ring {
  opacity: 1;
  stroke: var(--zone-ink);
  stroke-width: 3;
  stroke-dasharray: none;
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--zone) 75%, transparent));
}
/* The label sits inside the same group, so it gets the ring's treatment too
   unless it is told otherwise. */
.iso__block:focus-visible .iso__label { fill: var(--ink); }

@media (hover: hover) and (pointer: fine) {
  .iso__block:not(.is-active):hover .iso__body { transform: translateY(-5px); }
  .isomap[data-active] .iso__block:not(.is-active):hover { opacity: 0.8; }
}
@media (prefers-reduced-motion: reduce) {
  .iso__body { transition: none; }
  .iso__block.is-active .iso__body { transform: none; }
  .iso__block.is-pulsing .iso__ring { animation: none; }
  .iso__block.is-active .iso__ring { opacity: 0.9; }
}

/* --- panel --------------------------------------------------------------- */
.isomap__panel {
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: var(--paper);
  box-shadow: var(--shadow-md);
  padding: var(--card-pad-lg);
  min-width: 0;
  overflow: hidden;
  position: relative;
}
/* The zone bar. --zone is always set by the time a zone is selected; the
   fallback only covers the frame before that. */
.isomap__panel::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: var(--zone, var(--rule-strong));
  transition: background-color var(--t-enter) var(--ease-out);
}
.isomap__panel-inner {
  transition:
    opacity var(--t-enter) var(--ease-out),
    transform var(--t-enter) var(--ease-out),
    filter var(--t-enter) var(--ease-out);
}
/* The blur is what stops a zone switch reading as two panels overlapping. */
.isomap__panel-inner.is-swapping {
  opacity: 0; transform: translateY(6px); filter: blur(3px);
  transition-duration: var(--t-swap);
}
@media (prefers-reduced-motion: reduce) {
  .isomap__panel-inner.is-swapping { transform: none; filter: none; }
}

.panel__tag {
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--zone-ink);
}
.panel__title { font-size: var(--step-2); margin-top: 0.55rem; }
.panel__tagline { margin-top: 0.5rem; color: var(--ink); font-weight: 500; }
.panel__intro { margin-top: 0.85rem; color: var(--body); }
/* A fixed landscape crop with a ceiling, so the card and the map stay within
   about one text block of each other at every width. */
.ph--panel,
.shot--panel {
  height: min(24svh, 200px);
  min-height: 140px;
  margin-top: 1.25rem;
  border-radius: var(--r);
}
.isomap__panel .btn { margin-top: 1.25rem; }

@media (min-width: 860px) {
  .isomap__grid {
    grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.8fr);
    gap: var(--gap-xl);
    align-items: center;
  }
  .isomap__stage { --iso-cap: min(58svh, 470px); }
}
@media (min-width: 1180px) {
  .isomap__stage { --iso-cap: min(62svh, 540px); }
}

/* ==========================================================================
   5. TODAY AT VENTURA
   A polished rail, not a database table. The scrollbar is hidden and replaced
   by a pair of real arrow controls, the cards snap, and the rail can be
   dragged with a mouse. The class that is next on the clock is flagged, and
   the ones already run are dimmed, so a glance answers "what now" rather than
   "what exists".
   ========================================================================== */
.today__body { max-width: var(--maxw); margin-inline: auto; }

/* The rail owns the horizontal overflow, so the section keeps its gutter
   while a card can still run to the edge of a phone screen. */
.rail { position: relative; }
.strip {
  display: flex; gap: var(--gap-sm);
  overflow-x: auto; scroll-snap-type: x proximity;
  margin-inline: calc(var(--gutter) * -1);
  padding: 0.5rem var(--gutter) 1.5rem;
  scroll-padding-inline: var(--gutter);
  overscroll-behavior-x: contain;
  scroll-behavior: smooth;
  /* Hidden, not removed: the rail still scrolls with a wheel, a trackpad, a
     touch drag and the two arrow buttons, and it is still keyboard reachable
     through the cards themselves. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: grab;
}
.strip::-webkit-scrollbar { display: none; }
.strip.is-dragging { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; }
.strip.is-dragging * { pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .strip { scroll-behavior: auto; } }

/* Fades at both ends, so a rail that continues looks like it continues. They
   are painted only while there is something to scroll to. */
.rail::before, .rail::after {
  content: ""; position: absolute; top: 0; bottom: 1rem; width: clamp(1.5rem, 4vw, 3rem);
  pointer-events: none; z-index: 2; opacity: 0;
  transition: opacity 220ms ease;
}
.rail::before { left: calc(var(--gutter) * -1); background: linear-gradient(to right, var(--rail-bg, var(--paper)), transparent); }
.rail::after  { right: calc(var(--gutter) * -1); background: linear-gradient(to left, var(--rail-bg, var(--paper)), transparent); }
.rail[data-at-start="false"]::before { opacity: 1; }
.rail[data-at-end="false"]::after { opacity: 1; }

.rail__nav { display: flex; gap: 0.5rem; }
.rail__btn {
  display: inline-grid; place-items: center;
  width: var(--btn-h-sm); height: var(--btn-h-sm);
  border: 1px solid var(--rule-mid); border-radius: var(--r-pill);
  background: var(--paper); color: var(--ink); cursor: pointer;
  transition: border-color 180ms ease, color 180ms ease, background-color 180ms ease, opacity 180ms ease;
}
.rail__btn svg { width: var(--icon); height: var(--icon); }
.rail__btn[disabled] { opacity: 0.35; cursor: default; }
@media (hover: hover) and (pointer: fine) {
  .rail__btn:not([disabled]):hover { border-color: var(--ink); background: var(--paper-2); }
}
/* Nothing to scroll: the controls come off rather than sitting there dead. */
.rail__nav[hidden] { display: none; }

.slot {
  flex: 0 0 clamp(11.5rem, 46vw, 14rem);
  scroll-snap-align: start;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.1rem 1.15rem 1.35rem;
  display: grid; align-content: start; gap: 0.1rem;
  position: relative; overflow: hidden;
  transition: box-shadow var(--t-hover) ease, transform var(--t-hover) var(--ease-out), opacity 220ms ease;
}
/* The zone rule is the card's one piece of large colour and it is the one
   that carries meaning, so it stays the zone hue rather than becoming blue. */
.slot::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--zone);
}
@media (hover: hover) and (pointer: fine) {
  .slot:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
}
.slot__flag { margin-bottom: 0.6rem; }
/* The time is the thing people scan for, and it is the accent that keeps the
   brand blue in the body of the section. */
.slot__time { font-family: var(--font-mono); font-size: var(--step-2); color: var(--royal); line-height: 1; letter-spacing: -0.02em; }
.slot__name { font-size: var(--step-0); font-weight: 600; margin-top: 0.6rem; color: var(--ink); }
.slot__meta { font-size: var(--step--1); color: var(--faint); }
.slot__coach { font-size: var(--step--1); color: var(--body); margin-top: 0.5rem; }
.slot__coach .tagchip { color: var(--royal); opacity: 1; }
.slot__zone {
  margin-top: 0.65rem; font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--zone-ink);
}
/* Already run today. Still readable, clearly behind you. */
.slot.is-past { opacity: 0.5; }
.slot.is-next { border-color: var(--rule-mid); box-shadow: var(--shadow-md); }
.strip__empty { padding: 2rem 0; color: var(--faint); }

@media (min-width: 1000px) {
  .strip { padding-inline: 0; scroll-padding-inline: 0; margin-inline: 0; }
  .rail::before { left: 0; }
  .rail::after { right: 0; }
}

/* ==========================================================================
   6. PROMOCIONES
   The month's offers, in the site's own language rather than as an uploaded
   poster. One card is featured, because five cards of equal weight leave the
   reader to do the comparing.

   The price here is deliberately smaller than the membership price further
   down the page. There is one membership and there are five promotions, and
   the page has to say which of those is the product.
   ========================================================================== */
.promos { max-width: var(--maxw); margin-inline: auto; }

.promocards {
  display: grid; gap: var(--gap-md);
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
}
.promo {
  background: var(--paper); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: var(--card-pad);
  display: flex; flex-direction: column; gap: 0.15rem;
  position: relative; overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--t-hover) ease, transform var(--t-hover) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .promo:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
}
.promo__badge { margin-bottom: 0.85rem; align-self: flex-start; }
.promo__name { font-size: var(--step-1); color: var(--ink); }
.promo__price { margin-top: 0.55rem; }
.promo__price span {
  font-family: var(--font-sans); font-weight: 700; font-size: var(--step-2);
  color: var(--ink); letter-spacing: var(--track-display);
}
/* Pushed to the floor of the card so the notes align across a row of cards
   whose names and prices wrap to different heights. */
.promo__note {
  margin-top: auto; padding-top: 0.9rem;
  font-size: var(--step--1); color: var(--faint); line-height: 1.45;
}

/* The one the club leads with. A royal edge and a lime badge, which is the
   same pair of brand colours used for the membership block further down, so
   "this is the offer" reads the same way twice on one page. */
.promo--featured {
  border-color: var(--royal);
  box-shadow: var(--shadow-md);
}
.promo--featured::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: var(--royal);
}
.promo--featured .promo__price span { font-size: var(--step-3); color: var(--royal); }

.promos__note {
  margin-top: var(--gap-lg); text-align: center;
  font-size: var(--step--1); color: var(--body);
}
/* The asterisked exclusion the club prints on every poster. A step quieter
   than the disclaimer above it and tucked directly under it, because it is a
   footnote to that line rather than a second statement of equal weight. */
.promos__footnote {
  margin-top: 0.4rem; text-align: center;
  font-size: var(--step--2); color: var(--faint);
}

@media (min-width: 560px) {
  .promocards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .promocards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .promos[data-count="2"] .promocards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .promos[data-count="4"] .promocards { grid-template-columns: repeat(4, minmax(0, 1fr)); }

  /* Five offers on four tracks, in two rows: the lead card takes two of them
     and the pair underneath take two each. A single row of five would have
     given every card 190px, which is narrower than the longest note on any of
     them, and the lead card would have been distinguished by colour alone. */
  .promos[data-count="5"] .promocards { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .promos[data-count="5"] .promo { grid-column: span 1; }
  .promos[data-count="5"] .promo--featured,
  .promos[data-count="5"] .promo:nth-child(n + 4) { grid-column: span 2; }
}

/* Nothing active in the manifest, or the manifest could not be read. Says so
   rather than collapsing the section to a heading over a void. */
.promos__empty {
  border: 1px dashed var(--rule-strong); border-radius: var(--r-lg);
  padding: clamp(2.5rem, 8vw, 5rem) 1.25rem;
  text-align: center;
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--faint);
}

/* ==========================================================================
   7. THE MEMBERSHIPS
   Two recurring plans on solid royal, side by side from 900px and stacked
   below it. A different surface from the promotions above, because these are a
   different product: a commitment, not a one-off payment.

   TWO CARDS, NOT ONE BLOCK WITH TWO PRICES. Everything that belongs to a plan
   lives inside that plan's card, in this order:

     the amount
     the permanence, as a chip directly under it
     what you get
     the button
     the full conditions, labelled "Importante"

   The chip is what makes the term unmissable beside the price. The longer
   notice sits after the button because a reader who does not yet know what the
   plan includes cannot judge its terms; nothing about the commitment is hidden
   in either position. The card boundary is the point: a 12 month notice cannot
   drift under a 6 month price when the two never share a container.

   The mechanics are the same for both plans, so they are stated once beneath
   the pair rather than printed twice.
   ========================================================================== */
.recurring {
  max-width: var(--maxw); margin-inline: auto;
  display: grid; gap: var(--gap-xl);
}

.recurring__plans {
  display: grid; gap: var(--gap-lg);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

.recurring__card {
  background: rgb(0 0 0 / 0.18);
  border: 1px solid var(--rule-royal); border-radius: var(--r-lg);
  padding: var(--card-pad-lg);
  display: flex; flex-direction: column;
  min-width: 0;
}
.recurring__plan {
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--lime); margin-bottom: 1rem;
}
.recurring__price { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem; }
.recurring__price span {
  font-family: var(--font-sans); font-weight: 700; font-size: var(--step-4);
  color: #fff; letter-spacing: var(--track-display); line-height: 0.95;
}
.recurring__price em {
  font-style: normal; font-size: var(--step-0); color: rgb(255 255 255 / 0.82);
}
/* The permanence, directly under the amount. This is the one fact a reader
   must not be able to take the price without. */
.recurring__term {
  margin-top: 0.9rem; align-self: flex-start;
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--lime);
  border: 1px solid color-mix(in srgb, var(--lime) 55%, transparent);
  border-radius: var(--r-pill); padding: 0.35em 0.85em;
}
.recurring__claim {
  margin-top: 1.2rem; padding-top: 1.2rem;
  border-top: 1px solid var(--rule-royal);
  font-size: var(--step-1); font-weight: 600; color: #fff;
  letter-spacing: var(--track-tight); max-width: 24ch;
}

.recurring__subtitle {
  font-family: var(--font-mono); font-size: var(--step--1); font-weight: 400;
  letter-spacing: var(--track-label); text-transform: uppercase; color: #fff;
  padding-bottom: 0.7rem; border-bottom: 1px solid var(--rule-royal);
}
.recurring__card .recurring__subtitle { margin-top: var(--gap-lg); }
.recurring__subtitle + * { margin-top: 1.15rem; }

.recurring__benefits { display: grid; gap: 0.65rem; font-size: var(--step-0); }
.recurring__benefits li {
  padding-left: 1.15rem; position: relative; color: rgb(255 255 255 / 0.92);
}
.recurring__benefits li::before {
  content: ""; position: absolute; left: 0; top: 0.68em;
  width: 0.5rem; height: 2px; border-radius: 1px; background: var(--lime);
}

/* Pushed to the floor of the card, so two cards whose lists differ in length
   still line their buttons up with each other. */
.recurring__card .btn { margin-top: var(--gap-lg); }
.recurring__ctanote {
  margin-top: 0.85rem; text-align: center;
  font-size: var(--step--1); color: rgb(255 255 255 / 0.72);
}

.recurring__notice {
  margin-top: var(--gap-md);
  padding: var(--card-pad);
  background: rgb(255 255 255 / 0.08);
  border: 1px solid var(--rule-royal);
  border-left: 3px solid var(--lime);
  border-radius: 0 var(--r) var(--r) 0;
}
.recurring__notice-title {
  font-family: var(--font-mono); font-size: var(--step--2); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--lime); margin-bottom: 0.55rem;
}
.recurring__notice-body { font-size: var(--step--1); color: #fff; line-height: 1.6; }

/* --- shared under the pair ------------------------------------------------- */
.recurring__shared { display: grid; gap: var(--gap-lg); }
.recurring__hownote {
  margin-top: 0.7rem;
  font-size: var(--step--1); color: rgb(255 255 255 / 0.72);
}
.recurring__hownote + .recurring__steps { margin-top: 1.15rem; }
.recurring__steps { display: grid; gap: 0.9rem; counter-reset: step; font-size: var(--step-0); }
.recurring__steps li {
  counter-increment: step; padding-left: 2.4rem; position: relative;
  color: rgb(255 255 255 / 0.92);
}
.recurring__steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute; left: 0; top: 0.05em;
  font-family: var(--font-mono); font-size: var(--step--1); font-weight: 500;
  color: var(--lime);
}

@media (min-width: 900px) {
  .recurring__plans { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The steps and the contract link sit side by side under the pair, so the
     block does not run to two full-width rows of near-empty space. */
  .recurring__shared {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    align-items: start; column-gap: var(--gap-xl);
  }
  .recurring__shared .doclink { align-self: end; }
}

/* ==========================================================================
   DOCUMENT LINK
   The membership contract. A bare PDF link reads as a leak; this reads as a
   document the club is handing you, which is what it is.
   ========================================================================== */
.doclink {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--rule-royal); border-radius: var(--r);
  color: #fff;
  transition: border-color 180ms ease, background-color 180ms ease;
}
.doclink__label { font-size: var(--step--1); }
.doclink__meta {
  font-family: var(--font-mono); font-size: var(--step--2); letter-spacing: 0.12em;
  color: var(--lime); border: 1px solid var(--lime); border-radius: var(--r-pill);
  padding: 0.2rem 0.5rem; flex: none;
}
@media (hover: hover) and (pointer: fine) {
  .doclink:hover { border-color: var(--lime); background: rgb(255 255 255 / 0.06); }
}

/* ==========================================================================
   8. LOCATION
   The closing section, and it is built to close: the heading first, then the
   map at full width, then the three things a visitor actually needs, then the
   two actions. On the light ground, because the membership block above it is
   royal and two solid blue blocks in a row read as one slab.
   ========================================================================== */
.visit__map {
  max-width: var(--maxw); margin-inline: auto;
  border: 1px solid var(--rule); border-radius: var(--r-lg);
  overflow: hidden; background: var(--paper-3);
  box-shadow: var(--shadow-sm);
  height: clamp(260px, 42svh, 460px);
}
.visit__map iframe { width: 100%; height: 100%; border: 0; display: block; }

.visit__info {
  max-width: var(--maxw); margin: var(--gap-lg) auto 0;
  display: grid; gap: var(--gap-lg);
  align-items: start;
}
.visit__block { min-width: 0; }
.visit__block > * + * { margin-top: 0.9rem; }
.visit__address { font-style: normal; font-size: var(--step-1); color: var(--ink); line-height: 1.5; }
.visit__note { font-size: var(--step--1); color: var(--body); }
.visit__note + .visit__note { margin-top: 0.55rem; }
.hours__note { font-size: var(--step--1); color: var(--faint); }
/* Inside the address block, not under the grid. It used to clear the tallest
   column, which is the opening hours table, and that left about 170px of empty
   page between the address and the two actions that act on it. */
.visit__block .cta-row { margin-top: 1.4rem; }

@media (min-width: 820px) {
  .visit__info { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gap-lg) var(--gap-xl); }
}

/* ==========================================================================
   9. FINAL CTA + FOOTER
   ========================================================================== */
.final {
  background: var(--paper-2);
  padding: clamp(3.5rem, 8vw, 6.5rem) var(--gutter);
  text-align: center;
  border-top: 1px solid var(--rule);
}
.final__inner { max-width: var(--maxw); margin-inline: auto; }
.final .eyebrow { display: block; }
.final__title { font-size: var(--step-4); max-width: 18ch; margin: 1rem auto 0; }
.final__lede { margin: 1.15rem auto 0; max-width: 46ch; color: var(--body); font-size: var(--step-1); }
.final .cta-row { margin-top: clamp(2rem, 4vw, 2.75rem); }

.site-foot {
  background: var(--royal);
  padding: clamp(3rem, 7vw, 5rem) var(--gutter);
  display: grid; gap: var(--gap-lg);
  /* Clears the floating WhatsApp button, which is anchored to the same corner
     the footer ends in. */
  padding-bottom: calc(clamp(3rem, 7vw, 5rem) + 4rem);
}
.site-foot__top {
  max-width: var(--maxw); margin-inline: auto; width: 100%;
  display: grid; gap: var(--gap-lg);
}
.site-foot__brand { display: grid; gap: 1rem; justify-items: start; }
/* No plate: the white cut of the mark sits directly on the royal. */
.site-foot__logo { height: 72px; width: auto; display: block; }
.site-foot__line {
  font-size: var(--step-1); color: #fff; font-weight: 600;
  letter-spacing: var(--track-tight);
  max-width: 24ch; line-height: 1.25; text-wrap: balance;
}
.site-foot nav {
  display: grid; gap: 0.6rem 2rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  font-size: var(--step--1); color: rgb(255 255 255 / 0.82);
  align-content: start;
}
.site-foot nav a { padding: 0.15rem 0; transition: color 180ms ease; }
@media (hover: hover) and (pointer: fine) { .site-foot nav a:hover { color: var(--accent-ink); } }

/* The three things a visitor might actually be looking for down here: how to
   reach the club, where the membership is, and the contract. WhatsApp is a
   quiet link, not a second floating button: the real one is on screen. */
.site-foot__actions {
  max-width: var(--maxw); margin-inline: auto; width: 100%;
  display: grid; gap: var(--gap-md);
  padding-top: var(--gap-lg); border-top: 1px solid var(--rule-royal);
  align-items: center;
}
.site-foot__wa {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font-size: var(--step--1); color: rgb(255 255 255 / 0.82);
  transition: color 180ms ease;
}
.site-foot__waglyph { width: var(--icon); height: var(--icon); flex: none; color: var(--lime); }
.site-foot__link { font-size: var(--step--1); color: rgb(255 255 255 / 0.82); transition: color 180ms ease; }
@media (hover: hover) and (pointer: fine) {
  .site-foot__wa:hover, .site-foot__link:hover { color: var(--accent-ink); }
}

.site-foot__note {
  max-width: var(--maxw); margin-inline: auto; width: 100%;
  font-size: var(--step--1);
  /* 62% white over royal measures about 4.3:1, which is under AA for body
     copy. 72% clears 4.5:1 and still reads as the quietest line on the page. */
  color: rgb(255 255 255 / 0.72);
}

@media (min-width: 560px) {
  .site-foot nav { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .site-foot__top { grid-template-columns: auto 1fr; align-items: center; }
  .site-foot nav { justify-self: end; grid-template-columns: repeat(3, auto); gap: 0.6rem 2.5rem; }
  .site-foot__actions { grid-template-columns: auto auto 1fr; gap: var(--gap-xl); }
  .doclink--foot { justify-self: end; max-width: 22rem; width: 100%; }
  /* The button is clear of the content column on a wide screen, so the footer
     no longer has to reserve height for it. */
  .site-foot { padding-bottom: clamp(3rem, 7vw, 5rem); }
}

/* ==========================================================================
   FLOATING WHATSAPP BUTTON
   On every page, above everything, outside the header so it stays put while
   the page scrolls. WhatsApp green rather than a site token: it is a third
   party mark and people find it by its colour, which is worth more here than
   palette discipline.

   ONE SHAPE AT EVERY WIDTH: a 46px circle with the glyph and nothing else. It
   used to expand into a labelled pill on desktop, which made a persistent
   floating control the second widest object in the corner of every page and
   put a third "Escribenos por WhatsApp" on screen next to the footer link and
   the visit CTA. The accessible name still comes from aria-label, so the
   button reads the same to a screen reader as it did with the visible label.
   ========================================================================== */
.wa-fab {
  position: fixed; z-index: 60;
  right: max(1rem, env(safe-area-inset-right));
  bottom: max(1rem, env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; flex: none;
  border-radius: var(--r-pill);
  background: #25D366; color: #fff;
  box-shadow: 0 4px 14px rgb(11 14 20 / 0.24);
  transition: transform 200ms var(--ease-out), box-shadow 200ms ease;
}
.wa-fab__glyph { width: var(--icon-lg); height: var(--icon-lg); flex: none; }

@media (hover: hover) and (pointer: fine) {
  .wa-fab:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgb(11 14 20 / 0.3); }
}
/* Only the inset grows on a wide screen. The button does not. */
@media (min-width: 900px) {
  .wa-fab {
    right: max(1.5rem, env(safe-area-inset-right));
    bottom: max(1.5rem, env(safe-area-inset-bottom));
  }
}
@media (prefers-reduced-motion: reduce) {
  .wa-fab { transition: box-shadow 200ms ease; }
  .wa-fab:hover { transform: none; }
}
/* Printed pages have no use for a chat button. */
@media print { .wa-fab { display: none; } }

/* ==========================================================================
   ZONE PAGES
   Short and focused by design. This is a detail page, not a second landing
   page, so it gets a header, a hero, one description, one list block and a
   photo row. Nothing else.
   ========================================================================== */
.zonehead {
  /* Close to the navbar, the way a page header sits. This used to open with
     --section-y-sm, which is the same step the page uses between whole
     sections, and under a sticky header it read as a hole rather than as a
     margin. */
  padding: clamp(1.25rem, 2.4vw, 1.75rem) var(--gutter) 0;
  max-width: var(--maxw); margin-inline: auto;
}
.zonehead__back {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-size: var(--step--1); font-weight: 500; color: var(--faint);
  margin-bottom: clamp(0.9rem, 1.8vw, 1.25rem);
  transition: color 180ms ease;
}
.zonehead__back::before { content: "\2190"; transition: transform 220ms var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .zonehead__back:hover { color: var(--accent-ink); }
  .zonehead__back:hover::before { transform: translateX(-3px); }
}

.zonehead__tag {
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--zone-ink);
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.zonehead__title { font-size: var(--step-4); margin-top: 0.75rem; font-weight: 700; }
.zonehead__tagline { margin-top: 0.9rem; font-size: var(--step-1); color: var(--ink); max-width: 40ch; }
.zonehead__rule { height: 3px; border-radius: 2px; background: var(--zone); width: 4.5rem; margin-top: var(--gap-md); }

.zonebody { padding: clamp(1.75rem, 3.5vw, 2.75rem) var(--gutter) var(--section-y); max-width: var(--maxw); margin-inline: auto; }
.zonebody .ph--zonehero,
.zonebody .shot--zonehero {
  height: clamp(200px, 32vw, 440px);
  min-height: 0;
  margin-bottom: var(--gap-xl);
  border-radius: var(--r-lg);
}
.zonebody__desc { font-size: var(--step-1); line-height: 1.6; max-width: var(--maxw-text); color: var(--body); }

.included { margin-top: var(--gap-xl); display: grid; gap: var(--gap-lg); }
.included__block h2 {
  font-family: var(--font-mono); font-size: var(--step--1); font-weight: 400;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink);
  padding-bottom: 0.7rem; border-bottom: 1px solid var(--rule);
}
.included__list { margin-top: 1rem; display: grid; gap: 0.6rem; font-size: var(--step-0); }
.included__list li { padding-left: 1.15rem; position: relative; }
.included__list li::before { content: ""; position: absolute; left: 0; top: 0.68em; width: 0.5rem; height: 2px; border-radius: 1px; background: var(--zone-ink); }

.panel__menu, .included__menu { margin-top: 1rem; display: grid; gap: 0.55rem; }
.included__menu div { display: flex; align-items: baseline; gap: 0.75rem; }
.included__menu dt { flex: 1; min-width: 0; }
.included__menu dd { font-family: var(--font-mono); font-size: var(--step--1); color: var(--ink); font-variant-numeric: tabular-nums; }

.zonephotos { margin-top: var(--gap-xl); display: grid; gap: var(--gap-sm); grid-template-columns: repeat(2, 1fr); }
.zonephotos .ph,
.zonephotos .shot { min-height: 40vw; border-radius: var(--r); }
@media (min-width: 760px) {
  .included { grid-template-columns: repeat(3, 1fr); gap: var(--gap-xl); align-items: start; }
  .zonephotos { grid-template-columns: repeat(3, 1fr); }
  .zonephotos .ph,
  .zonephotos .shot { min-height: 14rem; }
}

.zonefoot { padding: 0 var(--gutter) var(--section-y); max-width: var(--maxw); margin-inline: auto; }
.zonefoot__inner {
  border-top: 1px solid var(--rule); padding-top: var(--gap-lg);
  display: flex; flex-wrap: wrap; gap: var(--gap-md); align-items: center; justify-content: space-between;
}

/* ==========================================================================
   SCHEDULE PAGE
   ========================================================================== */
.sched { padding: var(--section-y-sm) var(--gutter) var(--section-y); max-width: var(--maxw); margin-inline: auto; }
.sched__day { margin-top: var(--gap-xl); }
.sched__day.is-today .sched__dayname { color: var(--accent-ink); }
.sched__dayname {
  font-family: var(--font-mono); font-size: var(--step--1); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink);
  padding-bottom: 0.7rem; border-bottom: 1px solid var(--rule);
  display: flex; justify-content: space-between; gap: 1rem;
}
.sched__today-flag { color: var(--accent-ink); }
.sched__rows { display: grid; }
.sched__row {
  display: grid; gap: 0.15rem 1rem;
  grid-template-columns: 4.5rem 1fr;
  padding: 0.85rem 0; border-bottom: 1px solid var(--rule);
  align-items: baseline;
}
.sched__time { font-family: var(--font-mono); color: var(--ink); }
.sched__name { color: var(--ink); font-weight: 500; }
.sched__meta { grid-column: 2; font-size: var(--step--1); color: var(--faint); }
.sched__zone { grid-column: 2; font-family: var(--font-mono); font-size: var(--step--2); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--zone-ink); }
.sched__closed { padding: 1.25rem 0; color: var(--faint); border-bottom: 1px solid var(--rule); }
.sched__note { margin-top: var(--gap-lg); font-size: var(--step--1); color: var(--faint); }

@media (min-width: 700px) {
  /* The last column carries the room name, not the zone, so it needs room for
     the longest of them without wrapping mid word. */
  .sched__row { grid-template-columns: 5rem minmax(0, 1.4fr) minmax(0, 1fr) 11.5rem; align-items: center; gap: 1rem; }
  .sched__meta, .sched__zone { grid-column: auto; }
  .sched__zone { text-align: right; }
}
