/* Homepage dynamic sections: a dynamic product hero slideshow (.hero-
   slideshow), a scrolling product-title marquee directly under it (class
   names still say "people-marquee" — same reused marquee CSS/markup, just
   fed product titles instead of Athlete/Creator names now), an Athletes/
   Creators people-strip, and a deal-cards product strip. Mobile-first: base
   rules below target phones, larger screens are added via min-width media
   queries further down. The strips are native horizontal-scroll +
   scroll-snap (no swiper dependency) so they work with any item count the
   backend returns, including very few or zero. */

/* ---- Hero slideshow: real admin-managed product data, driven by
   assets/js/hero-slideshow.js (GET /api/products + GET /api/taxonomy).
   Slide 0's content/background is server-rendered directly into index.html
   (server/src/index.js -> applyHeroContext) for SEO/no-JS; this file only
   lays out and animates what that markup (SSR'd or client-added) contains —
   nothing here is itself hardcoded to a specific product. ---- */
.hero-slideshow {
  position: relative;
  display: flex;
  align-items: center;
  /* Mobile: NO viewport-based height and no px floor — the hero is exactly
     as tall as its own content plus the padding below, and nothing more.

     It previously carried `min-height: max(520px, 88vh)` (with an 88svh
     line after it). `max()` means the hero can only ever get TALLER than
     its content, never shorter, and measured on the real page its content
     needs 421.5px — so the floor was injecting dead space:

         320x568  hero 520.0px  content 421.5px  ->  98.5px empty
         375x667  hero 587.0px  content 421.5px  -> 165.4px empty
         390x844  hero 742.7px  content 421.5px  -> 321.2px empty
         430x932  hero 820.2px  content 421.5px  -> 398.6px empty  (49%)

     That empty space is what pushed the product ticker directly beneath
     the hero down across the fold — at 320px wide with the browser
     toolbars showing (460px of real estate) 0% of the ticker was on
     screen, at 375x553 only 83% of it was, so it read as a strip whose
     bottom had been cut off. Nothing was clipping it; the hero was simply
     reserving up to half a screen of nothing above it.

     Deliberately no `overflow: hidden` here either. The only thing that
     needs clipping is the scaled/blurred background image, and
     .hero-slideshow__bg-layer below already clips it inside its own
     `inset: 0` box — so this section cannot crop anything, its own
     children or the section after it, under any viewport. */
  padding: 84px 0 32px;
  background: var(--color-text); /* dark fallback while the first image loads */
}

/* Background layer: fills the section, sits behind everything else, and is
   the single place the oversized (scale(1.18) + slow pan) background image
   is clipped — see the note about `overflow` on .hero-slideshow above. */
.hero-slideshow__bg-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
.hero-slideshow__swiper,
.hero-slideshow__swiper .swiper-wrapper,
.hero-slideshow__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* Crossfade between slides: assets/js/hero-slideshow.js toggles each
   slide's opacity directly (plain JS, no Swiper) — this is what animates
   that change. Duration must match the JS SPEED constant. Defaults to
   fully opaque so the server-rendered first slide is visible with no JS
   at all; slides added by JS start at opacity 0 via inline style. */
.hero-slideshow__slide {
  transition: opacity 900ms ease;
}
.hero-slideshow__bg-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* Large, slightly blurred, slightly darkened — never the focal point.
     Scaled up so the blur's soft edge never shows inside the frame. */
  filter: blur(22px) brightness(0.55) saturate(1.05);
  transform: scale(1.18);
  animation: heroSlowPan 22s ease-in-out infinite alternate;
}
/* Very subtle pan/zoom — a premium touch, never fast or distracting enough
   to compete with the foreground content. Respects reduced-motion. */
@keyframes heroSlowPan {
  0% { transform: scale(1.18) translate(0, 0); }
  100% { transform: scale(1.26) translate(-1.4%, -1%); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-slideshow__bg-img {
    animation: none;
  }
}
.hero-slideshow__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Belt-and-suspenders on top of the brightness filter above — guarantees
     readable contrast for light text regardless of how bright/busy a given
     admin-uploaded cover image is. */
  background: linear-gradient(180deg, rgba(10, 10, 10, 0.55) 0%, rgba(10, 10, 10, 0.72) 100%);
}

.hero-slideshow__content {
  position: relative;
  z-index: 2;
  max-width: 620px;
  color: #ffffff;
  transition: opacity 0.4s ease, transform 0.4s ease;
}

/* The slideshow swaps its title every few seconds and product names are not
   the same length, so the <h1> silently grew from one line to two and pushed
   the description and CTA row down with it — a 0.0475 layout shift ~7s into
   the page, long after everything had "finished" loading. That is the most
   jarring kind of shift, because the visitor is already reading by then.

   .hero-slideshow__desc solved the same problem for itself with a clamp plus
   a fixed min-height; the <h1> never got the equivalent because it has no
   rule of its own here at all — it inherits the global heading styles. This
   is that missing rule, deliberately mirroring the desc pattern below rather
   than inventing a second approach. */
.hero-slideshow__content h1 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Two lines at the inherited heading line-height, so a one-line title
     reserves the same box as a two-line one. */
  min-height: 2.2em;
}
.hero-slideshow__content--out {
  opacity: 0;
  transform: translateX(-14px);
}
.hero-slideshow__badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: var(--radius-pill, 100px);
  padding: 6px 14px;
  font-size: 12px;
  font-weight: 600;
  color: #ffffff;
  margin-bottom: 20px;
  backdrop-filter: blur(4px);
}
.hero-slideshow__badge img {
  width: 14px;
  height: 14px;
  filter: brightness(0) invert(1); /* the fire icon is dark-line art; make it read on a dark badge */
}
.hero-slideshow__meta {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
  margin: 0 0 10px;
  min-height: 1.2em; /* reserves its line even before content loads/swaps, avoiding CLS */
}
.hero-slideshow h1 {
  font-size: 26px;
  line-height: 1.2;
  font-weight: 700;
  color: #ffffff;
  margin: 0 0 14px;
}
.hero-slideshow__desc {
  font-size: 14px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.82);
  margin: 0 0 26px;
  max-width: 48ch;
  /* Fixed 2-line box regardless of a given product's description length —
     content swaps never reflow the hero's height (no CLS between slides). */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  min-height: 2.8em;
}
/* Price is the primary visual anchor, ranked above the CTA in both markup
   order and weight — the user should register the value proposition
   (headline -> description -> price) before reaching the "Shop Now" action,
   per the conversion-path spec this hero was redesigned around. */
.hero-slideshow__price {
  display: block;
  font-size: 34px;
  line-height: 1.1;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: #ffffff;
  margin: 0 0 22px;
  min-height: 1.1em; /* reserves its line before content loads/swaps, avoiding CLS */
}
.hero-slideshow__cta-row {
  display: flex;
}
.hero-slideshow__cta.btn-one {
  /* Inverted from the sitewide dark .btn-one — a near-black button would
     nearly disappear against this section's own dark, blurred backdrop. A
     light, high-contrast button is what actually needs to pop here.
     Deliberately not the visual anchor of the hero (the price above is) —
     full-width for thumb reach, but no oversized padding/shadow to compete
     with it. */
  width: 100%;
  text-align: center;
  background-color: #ffffff;
  color: var(--color-text);
  padding-left: 28px;
  padding-right: 28px;
}
.hero-slideshow__cta.btn-one:hover {
  background-color: #f0f0f0;
  color: var(--color-text);
}
.hero-slideshow__cta.btn-one span {
  color: inherit;
}

@media (min-width: 420px) {
  .hero-slideshow__cta.btn-one {
    width: auto;
  }
}
/* ---- Hero floors above mobile.
   Every one of these is `min(<px>, 88svh)`, never a bare px value and never
   `max()`. The floor is therefore mathematically incapable of exceeding 88%
   of the viewport the visitor can actually see, so the hero can never grow
   tall enough to push the ticker below it off the first screen — which is
   the exact failure the base rule above documents.

   This also covers a phone held in landscape, which is what a bare px floor
   got wrong: an iPhone 15 Pro Max is 932x430, so it is WIDER than 768px and
   picks up the desktop rules here while being only 430px tall. `min()`
   resolves that to 378px on its own, no viewport-height media query needed.

   `min-height` only ever sets a floor, so where the hero's content is
   taller than the result (421.5px of content vs 378px on that landscape
   phone) the content still wins and the section sizes to it.

   The bare px line before each `min()` is the fallback for browsers with no
   svh support (< Chrome 108 / Safari 15.4); those breakpoints start at
   576px, and the value is the same one that shipped before. ---- */
@media (min-width: 576px) {
  .hero-slideshow {
    min-height: 560px;
    min-height: min(560px, 88svh);
  }
  .hero-slideshow h1 {
    font-size: 34px;
  }
  .hero-slideshow__desc {
    font-size: 15px;
  }
  .hero-slideshow__price {
    font-size: 38px;
  }
}
@media (min-width: 768px) {
  .hero-slideshow {
    padding: 116px 0 60px;
    min-height: 600px;
    min-height: min(600px, 88svh);
  }
  .hero-slideshow h1 {
    font-size: 44px;
  }
  .hero-slideshow__price {
    font-size: 42px;
  }
}
@media (min-width: 992px) {
  .hero-slideshow {
    padding: 136px 0 70px;
    min-height: 640px;
    min-height: min(640px, 88svh);
  }
  .hero-slideshow h1 {
    font-size: 54px;
  }
  .hero-slideshow__desc {
    font-size: 16px;
    -webkit-line-clamp: 3;
    min-height: 4.6em;
  }
  .hero-slideshow__price {
    font-size: 46px;
  }
}
@media (min-width: 1300px) {
  .hero-slideshow h1 {
    font-size: 60px;
  }
}

.people-strip-area,
.deal-cards-area {
  overflow: hidden; /* clip the peeking last card's shadow/edge cleanly */
}

/* ---- People marquee: slim override of the theme's .marquee-wrapper/
   .text-slider (style.css: 105px tall, 80px font — the "oversized" ticker).
   Combined-class selectors here (.marquee-wrapper.people-marquee etc.) beat
   the base rules on specificity regardless of stylesheet load order, and
   this file is only linked from index.html, so no other page that still
   uses the full-size ticker is affected. Reuses the existing
   .marquee-inner.to-left / @keyframes marqueeLeft animation as-is.

   The band's height is derived from its own content, and this is the one
   thing that has to stay true. `.marquee-wrapper` (style.css) carries
   `overflow: hidden`, which the ticker genuinely needs — the track is
   `width: 200%` and slides horizontally, and clipping that overhang IS the
   marquee effect. But `overflow` cannot be limited to one axis (a computed
   `overflow-x: hidden` forces `overflow-y` to `auto`, never `visible`), so
   whatever that wrapper's height happens to be also crops the track
   vertically. The only safe height for it is therefore the track's own.

   Two things previously stopped that from happening, and both are undone
   right here:

     1. `.marquee-inner` (style.css) is `position: absolute`, so it
        contributes nothing to a `height: auto` parent — the wrapper
        collapsed to zero and the visible text was whatever leaked out.
        `position: relative` below puts the track back in normal flow, so
        the wrapper measures it.
     2. Because of (1) the wrapper was given a hardcoded px height, which
        then had to be manually kept >= the track. It never was: the
        touch-target pass in style.css gives `.marquee-item > a` a
        `min-height: 44px` below 992px, so on every single phone a 44px
        track sat in a 40px box and lost 2px off the top and 2px off the
        bottom. `height: auto` cannot drift out of sync that way.

   With the track in flow there is nothing to centre, so the
   top:50%/translateY(-50%) pair is gone too — and with it the
   marqueeLeftCentered/marqueeRightCentered keyframes in style.css that
   existed only to fold that -50% into the scroll transform. The plain
   marqueeLeft/marqueeRight keyframes now apply unmodified. ---- */
.marquee-wrapper.people-marquee {
  /* Content-derived. See above — never a px value. */
  height: auto;
  padding: 0;
  /* Centres the track for the one case where the box is deliberately taller
     than it: the `min-height` floor at >=768px. At every width below that
     the box is exactly the track's height and this does nothing. */
  display: flex;
  align-items: center;
  background: var(--sub-bg, #f7f7f7);
}
.people-marquee .marquee-inner {
  /* In flow, so it gives the wrapper above its height. Still positioned,
     so the horizontal slide is unchanged. */
  position: relative;
  /* The track is deliberately 200% wide (two copies of the list, so the
     -50% slide loops seamlessly). As a flex item it would otherwise be
     shrunk back to 100% and the loop would visibly jump. */
  flex: 0 0 auto;
}
.text-slider.people-marquee {
  font-size: 13px;
  line-height: 1.4;
  font-family: inherit;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
}
.people-marquee .marqee-list {
  align-items: center;
}
.people-marquee .marquee-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  white-space: nowrap;
}
.people-marquee .marquee-item a {
  color: inherit;
  text-decoration: none;
}
@media (min-width: 576px) {
  .text-slider.people-marquee {
    font-size: 15px;
  }
}
@media (min-width: 768px) {
  .marquee-wrapper.people-marquee {
    /* `min-height`, not `height`. The band wants more visual presence at
       this size than its 18px text alone gives it, and a floor is the one
       way to say that which cannot crop: if the track is ever taller than
       52px the box grows to fit it instead of shaving it. `height: 52px`
       here was the desktop half of the same mistake the base rule above
       documents. */
    min-height: 52px;
  }
  .text-slider.people-marquee {
    font-size: 18px;
  }
  .people-marquee .marquee-item {
    padding: 0 20px;
  }
}

.people-strip__head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}
.people-strip__head h3 {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.people-strip__track,
.deal-cards__track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.people-strip__track::-webkit-scrollbar,
.deal-cards__track::-webkit-scrollbar {
  display: none;
}

/* ---- People chips (Athletes / Creators) ---- */
.people-strip__track {
  gap: 10px;
  padding: 2px 2px 10px;
}
.people-chip {
  scroll-snap-align: start;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 148px;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 100px;
  padding: 7px 18px 7px 7px;
  text-decoration: none;
  touch-action: manipulation;
  transition: var(--transition);
}
.people-chip:hover,
.people-chip:active {
  border-color: var(--primary-color);
}
.people-chip__avatar {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--primary-color);
  color: #ffffff;
  font-size: 12px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.people-chip__body {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  min-width: 0;
}
.people-chip__name {
  font-size: 13px;
  font-weight: 600;
  color: #1a1a1a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.people-chip__kind {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: rgba(26, 26, 26, 0.55);
}

/* ---- Deal cards ---- */
.deal-cards__track {
  gap: 14px;
  padding: 2px 2px 12px;
}
.deal-card {
  scroll-snap-align: start;
  flex: 0 0 82%; /* ~1.2 cards visible on a phone screen, signals swipeability */
  max-width: 320px;
  position: relative;
  display: block;
  text-decoration: none;
  color: inherit;
  border-radius: 14px;
  overflow: hidden;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.06);
  box-shadow: var(--shadow-sm);
  touch-action: manipulation;
  transition: var(--transition);
}
.deal-card:active {
  transform: scale(0.98);
}
.deal-card__off {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  width: 54px;
  height: 54px;
  line-height: 15px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: var(--shadow-sm);
  color: var(--primary-color);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.deal-card__image {
  display: block;
  aspect-ratio: 4 / 3;
  background: #f2f2f2;
  overflow: hidden;
}
.deal-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.deal-card__content {
  display: block;
  padding: 14px 16px 16px;
}
.deal-card__name {
  display: block;
  font-size: 14.5px;
  font-weight: 600;
  color: #1a1a1a;
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.deal-card__price {
  display: block;
  font-size: 15px;
  font-weight: 700;
  color: var(--primary-color);
  margin-bottom: 12px;
}
.deal-card__cta.btn-two {
  display: flex;
  width: 100%;
  justify-content: center;
  text-align: center;
}

/* ---- Small-phone tightening ---- */
@media (max-width: 400px) {
  .deal-card {
    flex-basis: 88%;
  }
  .people-chip {
    min-width: 132px;
  }
}

/* ---- Progressively show more per row on larger screens ---- */
@media (min-width: 480px) {
  .deal-card {
    flex-basis: 68%;
  }
}
@media (min-width: 576px) {
  .deal-card {
    flex-basis: 46%;
  }
  .people-strip__head h3 {
    font-size: 18px;
  }
}
@media (min-width: 768px) {
  .deal-card {
    flex-basis: 31%;
  }
  .people-chip {
    min-width: 168px;
  }
  .people-strip__head h3 {
    font-size: 20px;
  }
}
@media (min-width: 992px) {
  .deal-card {
    flex-basis: 23.5%;
  }
}
@media (min-width: 1300px) {
  .deal-card {
    flex-basis: 18.5%;
  }
}
