/* ==========================================================================
   MEMBERSHIP 3D RING — Evolve Up Fitness
   ==========================================================================
   Isolated, self-contained stylesheet for the Membership section only —
   built as a reference component per MEMBERSHIP_3D_RING_DESIGN_SPEC.md.
   Nothing here touches any other section's styles. To roll back: restore
   the previous static .pricing-grid markup in index.html and remove the
   two <link>/<script> tags that load this component — nothing else on
   the site depends on these class names.

   Reuses the site's existing design tokens (--bg-primary, --font-display,
   --font-body, --radius, --shadow-*, --ease, --dur) from main.css rather
   than redefining them — this component is new, not a new visual system.
   ========================================================================== */

/* ---------------------------------------------------------- SHOWROOM THEME
   Every plan defines these four variables on its own card element. When a
   card becomes active, its values are copied onto the ring viewport's
   crossfade layer (see membership-ring.js) — everything below reads from
   these variables, nothing hardcodes a color. */
.mship-ring-viewport {
  --theme-accent: #9CA3AF;
  --theme-accent-soft: rgba(156, 163, 175, 0.18);
  --theme-glow: rgba(156, 163, 175, 0.45);
  --theme-ambient: rgba(156, 163, 175, 0.12);
  /* Foundation refactor: previously a distinct curve
     (cubic-bezier(0.22, 1, 0.36, 1)), reasoned through at the time this
     was the only section built. Six sections have since converged on
     the shared --ease, and side-by-side comparison confirmed the
     divergence was no longer justified (flagged as a Critical finding
     in GLOBAL_DESIGN_SYSTEM_AUDIT.md). Aliased to the shared curve
     rather than removed outright — every existing reference (7 in this
     file, 1 in membership-ring.js) continues to resolve correctly with
     zero risk of an orphaned reference, while the actual motion now
     genuinely matches the rest of the site. */
  --ring-ease: var(--ease);
  --ring-settle-dur: 820ms;
}

.mship-ring-viewport {
  position: relative;
  margin-top: 48px;
  padding: 40px 0 64px;
  perspective: 1150px;
  perspective-origin: 50% 40%;
  overflow: hidden;
  border-radius: var(--radius);
}

/* ------------------------------------------------------------- AMBIENT BG */
.mship-ring-ambient {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.mship-ring-bg {
  position: absolute;
  inset: 0;
  transform: scale(1.2);
  background: radial-gradient(ellipse at 50% 35%, var(--theme-ambient) 0%, transparent 60%);
  transition: background var(--dur, 0.35s) var(--ease, ease);
  filter: blur(20px);
  animation: mship-bg-pulse 7s ease-in-out infinite;
}
@keyframes mship-bg-pulse {
  0%, 100% { opacity: 0.85; }
  50% { opacity: 1; }
}

.mship-ring-floor {
  position: absolute;
  left: 50%;
  bottom: 6%;
  width: 70%;
  height: 40px;
  transform: translateX(-50%) scale(1);
  background: radial-gradient(ellipse, var(--theme-glow) 0%, transparent 70%);
  filter: blur(12px);
  opacity: 0.8;
  transition: transform 0.5s ease;
}
/* A discrete, one-shot response to card promotion — distinct from the
   continuous ambient crossfade above. JS toggles this class briefly
   when goTo() fires, so the floor visibly reacts to a new selection
   rather than only ambiently drifting toward the new theme color. */
.mship-ring-floor.is-pulsing { transform: translateX(-50%) scale(1.15); }

.mship-ring-particles {
  position: absolute;
  inset: 0;
}
.mship-particle {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--theme-accent);
  opacity: 0.5;
  animation: mship-float-up linear infinite;
}
@keyframes mship-float-up {
  from { transform: translateY(10%); opacity: 0; }
  10% { opacity: 0.5; }
  90% { opacity: 0.5; }
  to { transform: translateY(-110%); opacity: 0; }
}

.mship-beam {
  position: absolute;
  width: 1px;
  height: 140%;
  top: -20%;
  background: linear-gradient(to bottom, transparent, var(--theme-accent-soft), transparent);
  transform: rotate(12deg);
  opacity: 0.4;
  animation: mship-beam-sweep 14s linear infinite;
}
@keyframes mship-beam-sweep {
  from { transform: translateX(-20vw) rotate(12deg); }
  to { transform: translateX(120vw) rotate(12deg); }
}

/* ---------------------------------------------------------------- STAGE */
.mship-ring-stage {
  position: relative;
  z-index: 1;
  height: 620px;
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: pan-y; /* only claim horizontal gestures — vertical scroll always passes through */
  cursor: grab;
  outline: none;
}
.mship-ring-stage:active { cursor: grabbing; }
.mship-ring-stage:focus-visible {
  box-shadow: 0 0 0 3px var(--theme-accent);
  border-radius: var(--radius);
}

.mship-ring {
  position: relative;
  width: 300px;
  height: 520px;
  transform-style: preserve-3d;
  transition: filter var(--dur-fast) var(--ease);
}
/* Very subtle motion blur, only while actually rotating (drag or settle),
   removed the instant motion stops. One blur on the whole ring container
   — not per-card — keeps this cheap; six independent blurs would not be. */
.mship-ring.is-rotating { filter: blur(0.6px); }

/* ----------------------------------------------------------------- CARD */
.mship-card {
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  background: var(--glass-standard-fill);
  backdrop-filter: blur(var(--blur-strong));
  -webkit-backdrop-filter: blur(var(--blur-strong));
  border: 1px solid var(--glass-standard-border);
  padding: 28px 24px;
  display: flex;
  flex-direction: column;
  color: var(--text-primary, #fff);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  will-change: transform, filter;
  transition: transform var(--ring-settle-dur) var(--ring-ease),
              filter var(--ring-settle-dur) var(--ring-ease),
              opacity var(--ring-settle-dur) var(--ring-ease);
  /* Continuous idle float — uses the independent `translate` CSS
     property (not `transform`) specifically so it composes cleanly with
     the JS-driven rotateY/translateZ/scale transform above rather than
     fighting it for the same property. Runs on every card from the
     moment it renders, each with its own animation-delay set once in JS
     (see renderCards()) for an independent per-card phase — never
     started or stopped by activation, so it's always already mid-cycle. */
  animation: mship-float-bob 4.4s ease-in-out infinite;
}
@keyframes mship-float-bob {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -6px; }
}
/* JS sets this during active drag/touch for true 1:1 tracking with zero
   transition lag — restored immediately on release so the momentum
   settle animates smoothly through the same curve as everything else. */
.mship-card.is-dragging-live { transition: none; }

.mship-card__glow {
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  /* Static now — no longer animated itself. The breathing motion below
     comes from a separate pseudo-element animating transform/opacity
     only, which is compositor-only work (no paint), unlike animating
     box-shadow's spread/blur values directly would be. */
  box-shadow: 0 0 60px 4px var(--theme-glow), 0 30px 60px -20px rgba(0, 0, 0, 0.6);
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
}
.mship-card.is-active .mship-card__glow { opacity: 1; }
.mship-card.is-near .mship-card__glow { opacity: 0.4; }

/* The actual "breathing" — a soft radial pulse layered on top of the
   static glow above, animating only transform (scale) and opacity.
   Runs permanently on every card from render, same reasoning as before:
   never started/stopped by activation, so it's always already
   mid-cycle. This achieves the same visual result as the old box-shadow
   animation with none of its paint cost. */
.mship-card__glow::after {
  content: '';
  position: absolute;
  inset: -24px;
  border-radius: inherit;
  background: radial-gradient(circle, var(--theme-glow) 0%, transparent 70%);
  opacity: 0.4;
  will-change: transform, opacity;
  animation: mship-glow-pulse 3.6s ease-in-out infinite;
}
@keyframes mship-glow-pulse {
  0%, 100% { transform: scale(0.92); opacity: 0.3; }
  50% { transform: scale(1.06); opacity: 0.6; }
}

/* Animated conic-gradient border — spins permanently on every card from
   render; only opacity (not the animation itself) changes with position,
   so it's never mid-restart at the moment a card becomes active. */
.mship-card__border {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  background: conic-gradient(from var(--border-angle, 0deg), var(--theme-accent), transparent 30%, var(--theme-accent) 60%, transparent 90%);
  transition: opacity 0.4s ease;
}
/* Spin only while this card is actually the active one. This animation
   repaints a conic gradient every frame — genuine paint work that GPU
   compositing cannot offload — and it was previously running on all six
   cards, five of which sit at opacity:0 and are completely invisible.
   Roughly an 83% cut in per-frame repaint cost for no visible change:
   the border fades in over 0.4s on activation, so the gradient's
   starting angle isn't perceptible. */
.mship-card.is-active .mship-card__border {
  opacity: 1;
  animation: mship-border-spin var(--card-border-speed, 6s) linear infinite;
}
@keyframes mship-border-spin {
  to { --border-angle: 360deg; }
}
@property --border-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* Registering these as typed custom properties is what makes the
   showroom crossfade (§9 of the design spec) genuinely smooth — browsers
   only interpolate a custom property's value across a transition if its
   syntax is registered as an animatable type. Without this, changing
   --theme-accent etc. via JS would be a discrete jump; every downstream
   element reading these variables (background, box-shadow, particle
   fill) inherits the same real interpolation as a result, with zero DOM
   changes required. Falls back gracefully to an instant swap in browsers
   that don't support @property (Firefox < 128) — not broken, just less
   smooth there. */
@property --theme-accent { syntax: '<color>'; inherits: true; initial-value: #9CA3AF; }
@property --theme-accent-soft { syntax: '<color>'; inherits: true; initial-value: rgba(156, 163, 175, 0.18); }
@property --theme-glow { syntax: '<color>'; inherits: true; initial-value: rgba(156, 163, 175, 0.45); }
@property --theme-ambient { syntax: '<color>'; inherits: true; initial-value: rgba(156, 163, 175, 0.12); }

.mship-ring-viewport {
  transition: --theme-accent var(--ring-settle-dur) var(--ring-ease),
              --theme-accent-soft var(--ring-settle-dur) var(--ring-ease),
              --theme-glow var(--ring-settle-dur) var(--ring-ease),
              --theme-ambient var(--ring-settle-dur) var(--ring-ease);
}

.mship-card__reflection {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.mship-card.is-active .mship-card__reflection { opacity: 1; }
.mship-card__reflection::after {
  content: '';
  position: absolute;
  top: -50%;
  left: -60%;
  width: 40%;
  height: 220%;
  background: linear-gradient(120deg, transparent, rgba(255, 255, 255, 0.12), transparent);
  /* Runs permanently on every card — gated by the parent's opacity
     above, never by starting/stopping this animation, so it's always
     already sweeping mid-cycle underneath whichever card becomes active.
     Animated via transform rather than `left`: at 6 cards running
     permanently, animating `left` forced a layout recalculation every
     frame on all six, on the main thread, which no GPU can offload.
     transform is compositor-only. Behaviour is identical. */
  animation: mship-sheen-sweep 8s ease-in-out infinite;
}
@keyframes mship-sheen-sweep {
  0%, 92%, 100% { transform: translateX(0) rotate(20deg); opacity: 0; }
  4% { opacity: 0.8; }
  12% { transform: translateX(500%) rotate(20deg); opacity: 0; }
}

/* --------------------------------------------------------- CARD CONTENT */
.mship-card__plan-name { font-size: 0.78rem; color: var(--text-tertiary, #838B96); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 4px; }
.mship-card__duration { font-family: var(--font-display);
  font-weight: 700; font-size: 1.5rem; margin-bottom: 10px; }
/* Featured plan: a persistent accent border and soft glow, visible
   regardless of whether this card is currently centered in the
   rotation — the existing .is-active border animation is a separate,
   temporary state tied to carousel position, not plan popularity, so
   this needed its own distinction that doesn't depend on it.
   Deliberately no size/scale change — this is a 3D rotating carousel,
   and altering a card's dimensions risks unpredictable interaction
   with the rotation's own positioning math; border and glow carry the
   distinction without touching layout. */
/* Featured plan distinction. Derives from --theme-accent rather than a
   hardcoded colour: the featured plan is Yearly, whose theme is green,
   so a fixed blue border sat directly against a green glow and clashed.
   Reading the live theme variable means this always harmonises with
   whichever plan is featured, and never needs manual updating if that
   choice changes later. */
.mship-card--featured {
  border-color: var(--theme-accent);
  box-shadow: 0 0 32px -8px var(--theme-glow);
}
.mship-card__badge {
  position: absolute; top: 16px; right: 16px;
  background: var(--theme-accent); color: #0A0A0A;
  font-size: 0.66rem; font-weight: 800; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px; letter-spacing: 0.04em;
}
.mship-card__price-row { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.mship-card__original { text-decoration: line-through; color: var(--text-tertiary, #838B96); font-size: 0.85rem; }
.mship-card__price { font-family: var(--font-display);
  font-weight: 700; font-size: 2rem; }
.mship-card__savings { font-size: 0.78rem; color: var(--theme-accent); font-weight: 700; margin-bottom: 12px; }

.mship-card__detail {
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height 0.4s ease, opacity 0.3s ease;
}
.mship-card.is-active .mship-card__detail {
  max-height: 300px;
  opacity: 1;
  overflow-y: auto;
}

.mship-card__audience { font-size: 0.82rem; color: var(--text-secondary, #9CA3AF); margin-bottom: 10px; }
.mship-card__features { list-style: none; margin-bottom: 12px; }
.mship-card__features li { display: flex; align-items: flex-start; gap: 8px; font-size: 0.8rem; color: var(--text-secondary, #9CA3AF); margin-bottom: 6px; }
.mship-card__features svg { color: var(--theme-accent); flex-shrink: 0; margin-top: 2px; }
.mship-card__edge { display: flex; gap: 8px; font-size: 0.78rem; color: var(--text-tertiary, #838B96); margin-bottom: 14px; }
.mship-card__edge svg { color: var(--theme-accent); flex-shrink: 0; }

.mship-card__cta {
  margin-top: auto;
  text-align: center;
  padding: 12px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.9rem;
  background: var(--theme-accent);
  color: #0A0A0A;
  transition: transform var(--dur-fast) var(--ease);
}
.mship-card.is-active .mship-card__cta:hover { transform: translateY(-2px); }
.mship-card:not(.is-active) .mship-card__cta { pointer-events: none; opacity: 0; }

/* ----------------------------------------------------- CONTROLS / DOTS */
.mship-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--glass-elevated-fill);
  border: 1px solid var(--glass-elevated-border);
  color: #fff;
  font-size: 1.4rem;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.mship-arrow:hover { background: var(--theme-accent-soft); border-color: var(--theme-accent); }
.mship-arrow--prev { left: 8px; }
.mship-arrow--next { right: 8px; }

.mship-dots { position: relative; z-index: 1; display: flex; justify-content: center; gap: 16px; margin-top: 20px; }
.mship-dots button {
  position: relative;
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.2); border: none; cursor: pointer;
  transition: background var(--dur-fast) var(--ease), width var(--dur-fast) var(--ease);
}
/* The visible dot is intentionally small, but an 8px tap target is far
   below the ~44px minimum for reliable thumb tapping. This extends the
   hit area without changing the dot's appearance.
   Width is `100% + gap`, NOT a fixed 44px: a fixed 44px overlay on dots
   16px apart overhangs 18px each side and collides 10px into BOTH
   neighbours, so the wrong dot receives the tap. Playwright caught this
   directly — dot 4's overlay was intercepting clicks aimed at dot 3.
   Sizing to exactly own-width-plus-gap means adjacent hit areas meet
   precisely and never overlap. Height stays 44px since vertical
   expansion collides with nothing. */
.mship-dots button::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: calc(100% + 16px); height: 44px;
  transform: translate(-50%, -50%);
}
.mship-dots button.is-active { background: var(--theme-accent); width: 22px; border-radius: 4px; }

/* -------------------------------------------------------- MOBILE SCALE */
@media (max-width: 640px) {
  .mship-ring-stage { height: 560px; }
  .mship-ring { width: 250px; height: 460px; }
  .mship-ring-viewport { perspective: 1300px; }
  .mship-beam { display: none; } /* desktop-only ambient flourish */
  .mship-ring.is-rotating { filter: none; } /* motion blur skipped on mobile — real GPU cost on low-end Android for a barely-visible effect at this size */

  /* Real mobile cost reduction, not just a smaller version of desktop.
     Glassmorphism blur is genuinely expensive to composite — cut
     significantly rather than left at desktop strength. Still visibly
     glass, just not paying full desktop GPU cost for it. */
  .mship-card { backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }

  /* Deliberately NOT disabling float/glow animations on non-active
     cards here. That was tried and reverted — is-active is only
     assigned via setTimeout after a transition settles, so gating
     animations on that class would recreate, on mobile specifically,
     the exact "incoming card has no animation yet" bug already fixed
     in the previous pass. Since Fix 1 made glow compositor-only
     (transform/opacity, not box-shadow), the real remaining mobile
     cost was backdrop-filter and will-change memory, not the
     animations themselves — letting the browser manage layer
     promotion on mobile rather than forcing it via will-change is the
     safer reduction that doesn't risk reintroducing a continuity bug. */
  .mship-card { will-change: auto; }
}

/* --------------------------------------------------- REDUCED MOTION */
@media (prefers-reduced-motion: reduce) {
  .mship-card, .mship-card__glow, .mship-card__border, .mship-ring-bg { transition: opacity 0.25s ease !important; }
  .mship-card, .mship-card__glow, .mship-card__glow::after, .mship-card__border, .mship-ring-bg, .mship-particle, .mship-beam, .mship-card__reflection::after { animation: none !important; }
  .mship-ring-particles, .mship-beam { display: none; }
  .mship-ring.is-rotating { filter: none !important; }
  .mship-ring-floor { transition: none !important; }
}
