/* ============================================================================
   DISNEY × BATH & BODY WORKS — MULTI-FORMAT CAMPAIGN CASE STUDY
   A campaign exhibition: four sections, 48 pieces of real campaign media and
   almost no prose.

   Written mobile-first for real: every rule outside a media query targets a
   ~390px canvas, and larger screens only ever ADD to it through min-width
   blocks. Nothing here is a desktop layout folded back down.

   Layered on top of style.css, which already supplies the design tokens, the
   topbar/footer chrome, the reveal system, the .count count-up hook and the
   .btn set.

   SECTIONS 2-4 (chrome + editorial furniture) intentionally repeat the
   equivalents in casestudy-warnerbros.css, casestudy-delhivery.css and
   casestudy-siemens.css at IDENTICAL values, so the case studies read as one
   system. They are duplicated rather than shared for the reason those files
   already give: each also carries its own layout, which has no business
   loading here. The standing note about lifting the common ~200 lines into a
   shared casestudy.css still applies and is still deliberately not done here,
   so that adding this page cannot regress the four live ones.

   COLOUR — one ACCENT, several WASHES, and they are not the same thing.
   #b162fe (--primary-red, aliased --cs-red) carries every eyebrow, rule,
   button, arrow and active indicator, exactly as it does on the other four
   studies. It is the portfolio's mark and it does not change per client.
   What DOES come from this campaign is the ambient light behind the work:
   the royal blue of the Disney contest gingham, the amber of the storybook
   ads and the berry of the aromatherapy set, sampled from the masters and
   used only as very low-alpha radial washes under whole sections. They tint
   the room; they never touch type. Do not promote a wash to an accent.

     1  Page tokens & gutter
     2  Chrome adjustments
     3  Primitives — media button, tag, play badge, sr-only
     4  Editorial furniture — eyebrow, headings, section rhythm
     5  Hero + collage
     6  Across every feed — masonry
     7  Carousels — shared shell
     8  Carousels — the four presentations
     9  Commercial finale + closing strip
    10  CTA & next
    11  Media viewer
    12  TABLET         min-width: 768px
    13  DESKTOP        min-width: 1024px
    14  LARGE DESKTOP  min-width: 1440px
    15  Reduced motion
   ========================================================================= */

/* --------------------------------------------------------- 1 tokens & gutter */
:root {
  /* Carried straight from the other four studies so every case study shares
     one type colour and one hairline weight. --cs-red IS --primary-red. */
  --cs-red:    #b162fe;
  --cs-white:  #f7f5f2;
  --cs-grey:   #a8a5a0;
  --cs-dim:    #6e6b66;
  --cs-line:   rgba(247, 245, 242, .1);
  --cs-line-2: rgba(247, 245, 242, .18);
  --cs-panel:  #101011;
  --cs-red-lo: rgba(177, 98, 254, .42);

  /* Campaign washes. Sampled from the masters, not invented:
       navy   the Disney contest gingham + the "step into your story" ad
       gold   the storybook / castle performance set
       blush  the aromatherapy and fine-fragrance-mist ads
     Only ever used inside radial-gradient() at low alpha. */
  --bbw-navy:  22, 38, 77;
  --bbw-gold:  194, 148, 83;
  --bbw-blush: 181, 116, 116;

  --cs-space: 5.5rem;
  --cs-h1:    2.35rem;   /* the H1 is a long two-line lockup on a phone */
  --cs-h2:    1.95rem;

  /* Feed masonry. --feed-row is the grid's row UNIT, not a row height: cards
     span however many of them their true aspect ratio needs, which is what
     makes 1200x1200 ads and 1080x1920 reels pack together without either
     being cropped. Small enough to pack tightly, large enough that the span
     count stays a sane number. --feed-gap doubles as the row gap, applied as
     padding on the card rather than as row-gap, because a real row-gap would
     have to be subtracted out of every span.

     DO NOT "improve" this by shrinking the unit. It reads like the cause of
     the grid's dead space and it is not: measured at 1900px, dropping 8px to
     4px made things WORSE (10.8% -> 12.4% dead area, ragged bottom edge
     1184px -> 1352px), because a finer unit lets tall cards slot in earlier
     and strand a column that a coarser quantisation would have kept level.
     The dead space comes from the data-span="2" cards — see the note in the
     feed section of casestudy-disneyxbbw.html. */
  --feed-cols: 2;
  --feed-gap:  .55rem;
  --feed-row:  6px;

  /* 22px of horizontal breathing room on mobile — style.css drops the global
     gutter to 18.4px under 640px, which is tighter than this page wants. */
  --gutter: 1.375rem;
}

/* ------------------------------------------------------- 2 chrome adjustments */
/* style.css makes the topbar transparent under 640px because the homepage hero
   sits behind it. Here it is a plain bar over a plain canvas. */
@media (max-width: 640px) {
  .topbar { background: var(--background); }
}

.cs-brand { display: flex; align-items: center; gap: .6rem; min-height: 44px; }
.cs-brand__arrow {
  width: 1.3rem; height: .6rem; color: var(--cs-grey);
  transition: transform .3s var(--ease), color .3s var(--ease);
}
.cs-brand:hover .cs-brand__arrow,
.cs-brand:focus-visible .cs-brand__arrow { transform: translateX(-5px); color: var(--cs-red); }

.bbw-body { background: var(--background); }

/* ============================================================= 3 primitives */

/* Visually hidden but announced. Used for the carousel live regions and for
   the "×" in the H1, which is a symbol sighted readers see and screen readers
   should hear as a word boundary rather than "multiplication sign". */
.bbw-sr {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip: rect(0 0 0 0); clip-path: inset(50%);
}

/* THE media card. One button, used by every image and every clip on the page
   — feed, editorial set and all four carousels — so there is a single hover
   state, a single focus ring and a single corner radius to maintain.

   No aspect-ratio here on purpose. Every <img> carries real width/height
   attributes, so the browser derives the box's ratio from the file itself
   before it loads. That is what guarantees no crop and no layout shift on a
   page mixing 4:5, 1:1, 9:16 and 12:5 in the same grid. */
.bbw-media {
  position: relative;
  display: block;
  width: 100%;
  margin: 0; padding: 0;
  border: 1px solid var(--cs-line);
  border-radius: 4px;
  background: var(--cs-panel);
  overflow: hidden;
  cursor: pointer;
  color: inherit;
  font: inherit;
  -webkit-tap-highlight-color: transparent;
  transition: border-color .45s var(--ease), box-shadow .45s var(--ease),
              transform .5s var(--ease-out);
}
.bbw-media__img {
  display: block;
  width: 100%; height: auto;
  transition: transform .8s var(--ease-out), filter .5s var(--ease);
  filter: saturate(.94);
}

.bbw-media:hover,
.bbw-media:focus-visible {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 20px 48px -24px rgba(0, 0, 0, .95);
  transform: translateY(-3px);
}
.bbw-media:hover .bbw-media__img,
.bbw-media:focus-visible .bbw-media__img { transform: scale(1.025); filter: saturate(1); }

/* The muted preview clip. Injected by the JS on first activation and reused
   afterwards, so scrubbing back over a card never re-downloads it. Sits over
   the poster and fades in only once the clip is actually running — a black
   box appearing before the first frame decodes is worse than the poster. */
.bbw-media__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .45s var(--ease);
  pointer-events: none;
}
.bbw-media.is-playing .bbw-media__video { opacity: 1; }
.bbw-media.is-playing .bbw-media__play { opacity: 0; }

/* Format label. Hover/focus only — a permanent caption under all 26 feed
   cards would turn the section into a list with pictures. */
.bbw-media__tag {
  position: absolute; left: .5rem; top: .5rem; z-index: 2;
  padding: .3rem .55rem;
  border-radius: 2px;
  background: rgba(7, 7, 7, .82);
  color: var(--cs-white);
  font-family: var(--font-cond); font-weight: 500;
  font-size: .55rem; letter-spacing: .16em; text-transform: uppercase;
  line-height: 1;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity .3s var(--ease), transform .3s var(--ease-out);
  pointer-events: none;
}
.bbw-media:hover .bbw-media__tag,
.bbw-media:focus-visible .bbw-media__tag { opacity: 1; transform: none; }

/* Play indicator — the one thing that is ALWAYS visible on a clip, because it
   is the only cue distinguishing a video card from a still. */
.bbw-media__play {
  position: absolute; z-index: 2;
  right: .5rem; bottom: .5rem;
  display: grid; place-items: center;
  width: 2rem; height: 2rem;
  border: 1px solid rgba(247, 245, 242, .5);
  border-radius: 50%;
  background: rgba(7, 7, 7, .5);
  backdrop-filter: blur(3px);
  transition: opacity .35s var(--ease), background .35s var(--ease),
              border-color .35s var(--ease);
  pointer-events: none;
}
.bbw-media__play svg { width: .9rem; height: .9rem; fill: var(--cs-white); }
.bbw-media:hover .bbw-media__play {
  background: rgba(177, 98, 254, .85);
  border-color: rgba(177, 98, 254, .85);
}

/* ================================================== 4 editorial furniture */
.cs-sec {
  position: relative;
  padding-block: var(--cs-space);
  border-top: 1px solid var(--cs-line);
}
.cs-sec__head { margin-bottom: 2.4rem; }

.cs-eyebrow {
  display: flex; align-items: center; gap: .8rem;
  margin-bottom: 1.35rem;
  font-family: var(--font-cond); font-weight: 500;
  font-size: .68rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--cs-red);
}
.cs-eyebrow__rule { width: 1.6rem; height: 1px; background: var(--cs-red); flex: none; }

.cs-h1, .cs-h2, .cs-h3 {
  color: var(--cs-white);
  font-family: var(--font-cond); font-weight: 400;
  letter-spacing: -.012em;
  text-wrap: balance;
}
.cs-h1 { font-size: var(--cs-h1); line-height: 1.06; }
.cs-h2 { font-size: var(--cs-h2); line-height: 1.14; }

/* The washes. Each section gets ONE, keyed to the work inside it, and all of
   them sit behind the content at an alpha low enough that the black canvas is
   still the background — they read as light in the room, not as panels. */
.bbw-sec::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
}
.bbw-sec { isolation: isolate; }

.bbw-sec--feed::before {
  background:
    radial-gradient(115% 55% at 8% 0%,   rgba(var(--bbw-navy), .34), transparent 62%),
    radial-gradient(95% 45% at 100% 88%, rgba(var(--bbw-gold), .10), transparent 64%);
}
.bbw-sec--cars::before {
  background:
    radial-gradient(105% 50% at 92% 4%,  rgba(var(--bbw-blush), .12), transparent 62%),
    radial-gradient(110% 55% at 0% 80%,  rgba(var(--bbw-navy), .26), transparent 64%);
}

/* ================================================================== 5 hero */
.bbw-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: 2.5rem 3.25rem;
}
.bbw-hero__wash {
  position: absolute; inset: -20% -10% auto -10%; z-index: -1;
  height: 120%;
  background:
    radial-gradient(58% 46% at 74% 22%, rgba(var(--bbw-navy), .55), transparent 66%),
    radial-gradient(46% 40% at 14% 72%, rgba(var(--bbw-gold), .16), transparent 68%),
    radial-gradient(40% 34% at 96% 82%, rgba(var(--bbw-blush), .12), transparent 70%);
  pointer-events: none;
}

.bbw-hero__grid { display: grid; gap: 2.25rem; }

.bbw-hero__title {
  /* the lockup is the page's identity, so it gets the display face rather
     than the condensed one every other heading uses */
  font-family: var(--font-cond); font-weight: 500;
  letter-spacing: -.018em;
}
.bbw-hero__x {
  color: var(--cs-red);
  font-weight: 400;
  padding-inline: .06em;
}

.bbw-hero__sub {
  margin-top: 1.1rem;
  font-family: var(--font-cond); font-weight: 300;
  font-size: 1.05rem; letter-spacing: .01em; line-height: 1.5;
  color: var(--cs-grey);
}

/* Campaign scale. A hairline above it, middots drawn between items, and the
   numerals in the accent — an editorial credit line, not a row of stat tiles.

   ALL FIVE ITEMS SIT ON ONE LINE ON A PHONE, and that is what the fluid
   font-size below buys. The row is 425px of content against 331px of shell at
   375px of viewport, so at any fixed size the last item ("1 Commercial", the
   longest) strands on a second line by itself. Rather than shorten the labels,
   the whole row scales with the viewport: 3vw sized against the measured cost
   of one line — glyphs, letter-spacing, the two gap tracks and the middot
   margins together come to ~27x the font-size — which fits inside the shell
   from 320px up with room to spare, and clamps to the desktop 13.12px at
   416px. Everything that contributes to that width therefore has to scale WITH
   it, which is why the gaps, the middot margin and .count are all in em here
   and not in rem. flex-wrap stays as a safety valve; it should never fire.
   The separators can never strand at the start of a line in any case, because
   they are ::before marks that only render between siblings. */
.bbw-scale {
  display: flex; flex-wrap: wrap;
  gap: .3rem .45em;
  margin-top: 1.6rem;
  padding-top: 1.35rem;
  border-top: 1px solid var(--cs-line);
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.58rem, 3vw, .82rem);
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--cs-grey);
}
.bbw-scale__item { display: flex; align-items: baseline; gap: .3em; }
.bbw-scale__item + .bbw-scale__item::before {
  content: "\00b7";
  margin-right: .3em;
  color: var(--cs-dim);
}
.bbw-scale .count {
  color: var(--cs-white);
  font-weight: 500;
  font-size: 1.28em;
  font-variant-numeric: tabular-nums;
}

/* --- the collage -----------------------------------------------------------
   A square field on desktop, a shorter one on phones, with every card placed
   in percentages of that field. Because each card's HEIGHT comes from its own
   image ratio and its WIDTH is a percentage of the field, a square field is
   what makes the vertical percentages below mean what they say. Change the
   field ratio and every top/bottom value has to be re-derived.

   Mobile shows all FIVE cards. It used to show three and display:none the
   carousel slide and the widescreen frame, because five across a 346px field
   would each be under 90px wide — a speck, not a campaign asset. The fix was
   not to shrink them but to give the field room: it is 1/1.3 on a phone and
   only squares up to 1/1 from 1024px. Do not restore the square field here
   without also restoring the two display:none rules.                        */
.bbw-collage {
  position: relative;
  aspect-ratio: 1 / 1.3;
  width: 100%;
  max-width: 30rem;
  margin-inline: auto;
}

.bbw-collage__item {
  position: absolute;
  transform:
    translate3d(calc(var(--mx, 0px) * var(--depth)), calc(var(--my, 0px) * var(--depth)), 0)
    rotate(var(--rot, 0deg));
  transition: transform .55s var(--ease-out);
}
.bbw-collage__frame {
  display: block;
  overflow: hidden;
  border: 1px solid var(--cs-line-2);
  border-radius: 4px;
  background: var(--cs-panel);
  box-shadow: 0 26px 54px -26px rgba(0, 0, 0, .95);
}
.bbw-collage__frame img { display: block; width: 100%; height: auto; }

/* Entrance, then float. The two never overlap in time — the float's delay is
   longer than the entrance's duration — and the entrance's filled resting
   value is transform:none, which is exactly where the float starts. That is
   what lets both live on one element without a visible seam. */
.js .bbw-collage__frame {
  opacity: 0;
  animation:
    bbw-collage-in .95s var(--ease-out) var(--delay, 0ms) both,
    bbw-float 15s ease-in-out calc(var(--delay, 0ms) + 1.2s) infinite alternate;
}
@keyframes bbw-collage-in {
  from { opacity: 0; transform: translateY(22px) scale(.972); }
  to   { opacity: 1; transform: none; }
}
@keyframes bbw-float {
  from { transform: translateY(0); }
  to   { transform: translateY(-9px); }
}

/* mobile field — all FIVE cards. Each anchors to exactly ONE horizontal and
   ONE vertical edge; the height always comes from the image's own ratio.

   This used to show three and hide the slide and the wide frame, on the
   grounds that five at phone width would each be a speck. That holds only if
   you keep the square-ish field — five cards do not fit across 346px, but
   they fit down it, which is why the field is now 1/1.3 rather than 1/.95.
   The extra height is what buys the two returning cards a real size instead
   of a shrunken one.

   The vertical percentages below are derived from that 1/1.3 field and from
   each image's own ratio, so they only mean what they say while it holds:
     post  50% wide x 1.25 -> 48.1% of the field's height
     ad    38%      x 1    -> 29.2%
     wide  60%      x .417 -> 19.2%
     reel  28%      x 1.78 -> 38.3%
     slide 35%      x 1.25 -> 33.7%
   Change the field ratio and all ten numbers have to be re-derived. */
.bbw-collage__item--post  { left: 0;   top: 0;     width: 50%; z-index: 5; }
.bbw-collage__item--ad    { right: 0;  top: 6%;    width: 38%; z-index: 3; }
.bbw-collage__item--wide  { right: 1%; top: 40%;   width: 60%; z-index: 2; }
.bbw-collage__item--reel  { left: 3%;  bottom: 1%; width: 28%; z-index: 4; }
.bbw-collage__item--slide { right: 6%; bottom: 3%; width: 35%; z-index: 1; }

/* ================================================= 6 across every feed */
/* Two layouts, chosen by whether JavaScript ran.

   NO JS (the default rules): a plain equal-column grid. Every card is fully
   visible at its own ratio, just with ragged column bottoms. Correct, if less
   handsome.

   JS (.js, set inline in <head>): true masonry. row-gap goes to zero, the
   grid gains a 6px row unit, and casestudy-disneyxbbw.js writes each card's
   --rows from its true aspect ratio and the measured column width. dense
   packing then backfills the holes the featured 2-column cards leave. */
.bbw-feed {
  display: grid;
  grid-template-columns: repeat(var(--feed-cols), minmax(0, 1fr));
  gap: var(--feed-gap);
}
.bbw-card { grid-column: span 1; min-width: 0; }
.bbw-card[data-span="2"] { grid-column: span 2; }

.js .bbw-feed {
  row-gap: 0;
  grid-auto-rows: var(--feed-row);
  grid-auto-flow: row dense;
}
/* The fallback span keeps the grid sane for the one frame between first paint
   and the JS pass — and if the script fails outright, nothing collapses. */
.js .bbw-card {
  grid-row: span var(--rows, 40);
  padding-bottom: var(--feed-gap);
}

/* =========================================== 7 carousels — shared shell */
/* minmax(0, 1fr), never a bare implicit column. An `auto` track is sized from
   its content, and the catalogue's thumbnail rail is 5 x 2.6rem plus two
   arrows = 350px of max-content — which pushed the column 74px past the shell
   at 320px and took all four carousels with it. The sitewide
   `body { overflow-x: hidden }` swallowed it silently, so it read as a
   slightly-too-wide carousel rather than as overflow. */
.bbw-cars {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 4rem;
}
.bbw-car { min-width: 0; }
.bbw-car__stage { min-width: 0; }

.bbw-car__head { margin-bottom: 1.1rem; }
.bbw-car__title {
  color: var(--cs-white);
  font-family: var(--font-cond); font-weight: 400;
  font-size: 1.35rem; line-height: 1.2; letter-spacing: -.01em;
}
/* The only metadata any carousel carries: what it holds and how much of it. */
.bbw-car__meta {
  margin-top: .4rem;
  font-family: var(--font-cond); font-weight: 300;
  font-size: .66rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--cs-dim);
}

.bbw-car__viewport {
  position: relative;
  touch-action: pan-y;          /* let the page scroll; we take the X axis */
  outline-offset: 6px;
}
/* position:relative is what makes slide.offsetLeft in the JS measure against
   the TRACK — the film and swipe sets centre themselves from that number, and
   an unpositioned track would send it measuring from the page. */
.bbw-car__track { position: relative; margin: 0; padding: 0; list-style: none; }
.bbw-car__slide { margin: 0; }

/* The two presentations whose slides are absolutely positioned need the track
   to BE the viewport box, not a zero-height strip at the top of it. Without
   this, `inset: 0` on a catalogue slide resolves against a track whose own
   height is zero — every slide collapses and the section renders empty. */
.bbw-car--stack .bbw-car__track,
.bbw-car--catalogue .bbw-car__track { position: absolute; inset: 0; }

/* While a drag is in progress the pointer owns the gesture, so the click that
   would otherwise land on a slide is suppressed in JS and the cursor changes
   here. */
.bbw-car__viewport.is-dragging { cursor: grabbing; }
.bbw-car__viewport.is-dragging .bbw-car__track { transition: none !important; }
.bbw-car__viewport.is-dragging .bbw-media { transition: none; }

.bbw-car__controls {
  display: flex; align-items: center; gap: .8rem;
  margin-top: 1.1rem;
}
.bbw-car__arrow {
  flex: none;
  display: grid; place-items: center;
  width: 44px; height: 44px;
  padding: 0;
  border: 1px solid var(--cs-line-2);
  border-radius: 50%;
  background: transparent;
  color: var(--cs-white);
  cursor: pointer;
  transition: border-color .3s var(--ease), background .3s var(--ease),
              color .3s var(--ease);
}
.bbw-car__arrow svg {
  width: 1rem; height: 1rem;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.bbw-car__arrow:hover:not(:disabled) {
  border-color: var(--cs-red);
  background: rgba(177, 98, 254, .12);
}
.bbw-car__arrow:disabled { opacity: .3; cursor: default; }

.bbw-car__dots {
  display: flex; align-items: center; justify-content: center; gap: .4rem;
  flex: 1 1 auto; flex-wrap: wrap;
  min-width: 0;                 /* a flex item may not shrink below its
                                   content without this */
  margin: 0; padding: 0; list-style: none;
}
/* The <li> carries the width, not the button. An <ol> needs <li> children, and
   a shrink-wrapping <li> around a button whose only width came from a
   width:100% pseudo-element resolved circularly to zero — the indicators were
   in the DOM, focusable and correct, and 0px wide. Give the <li> a real
   flex-basis and let the button fill it. */
.bbw-car__dots > li { flex: 0 1 2.1rem; min-width: .85rem; display: flex; }

.bbw-car__dot {
  width: 100%; height: 30px;
  padding: 0;
  border: 0; background: transparent;
  cursor: pointer;
}
/* the visible mark is a hairline inside a 30px-tall hit area — the target
   stays finger-sized without the indicator becoming a row of buttons.

   NOT --cs-line-2 (18% white): these sit on near-black with nothing around
   them, and at 2px tall that read as no indicator at all. A hairline needs
   more contrast standing alone than it does under type. */
.bbw-car__dot::before {
  content: ""; display: block;
  height: 2px; width: 100%;
  background: rgba(247, 245, 242, .34);
  transition: background .35s var(--ease), transform .35s var(--ease-out);
}
.bbw-car__dot:hover::before { background: var(--cs-white); }
.bbw-car__dot[aria-current="true"]::before {
  background: var(--cs-red);
  transform: scaleY(2);
}

/* ==================================== 8 carousels — the four presentations */

/* --- 1 · STACK (Disney Contest, 7 image slides) -----------------------------
   A pile. The active slide sits flush left at full size; the next two are
   offset right, scaled down and dimmed so the depth of the set is legible
   without a counter. Everything past the third is parked and invisible, and
   spent slides exit left. Offsets are set by the JS as --off (0, 1, 2, …). */
.bbw-car--stack .bbw-car__viewport { aspect-ratio: 1 / .96; }
.bbw-car--stack .bbw-car__slide {
  position: absolute; left: 0; top: 0;
  width: 74%;
  transform-origin: 0 50%;
  transition: transform .62s var(--ease-out), opacity .5s var(--ease);
  will-change: transform;
}
/* The peek opacities are set for THIS artwork. Six of the seven Disney
   contest slides are jewel-tone florals on near-black grounds, so the .72/.42
   pair these started at left the stack reading as one slide with a smudge
   behind it. Raised until the depth is legible without the peeks competing
   with the slide in front. */
.bbw-car--stack .bbw-car__slide[data-off="0"] { transform: translate3d(0, 0, 0)     scale(1);   opacity: 1;   z-index: 30; }
.bbw-car--stack .bbw-car__slide[data-off="1"] { transform: translate3d(19%, 3%, 0)  scale(.93); opacity: .88; z-index: 20; }
.bbw-car--stack .bbw-car__slide[data-off="2"] { transform: translate3d(34%, 6%, 0)  scale(.87); opacity: .62; z-index: 10; }
.bbw-car--stack .bbw-car__slide[data-off="far"]  { transform: translate3d(42%, 8%, 0) scale(.84); opacity: 0; z-index: 1; }
.bbw-car--stack .bbw-car__slide[data-off="past"] { transform: translate3d(-64%, 0, 0) scale(.9); opacity: 0; z-index: 1; }
.bbw-car--stack .bbw-car__slide:not([data-off="0"]) { pointer-events: none; }

/* --- 2 · FILM (Foaming Soap, 5 video slides) --------------------------------
   Centre stage. The track is shifted so the active slide sits in the middle
   of the viewport with its neighbours cropped by the edges; only the middle
   one is at full scale and full opacity, and only the middle one is ever
   allowed to play. */
.bbw-car--film .bbw-car__viewport,
.bbw-car--swipe .bbw-car__viewport { overflow: hidden; }
.bbw-car--film .bbw-car__track,
.bbw-car--swipe .bbw-car__track {
  display: flex;
  gap: var(--slide-gap, .75rem);
  transform: translate3d(var(--shift, 0px), 0, 0);
  will-change: transform;
}
.bbw-car--film .bbw-car__track  { transition: transform .68s var(--ease-out); }
/* the trend set is the quicker one — same system, more energy */
.bbw-car--swipe .bbw-car__track { transition: transform .42s cubic-bezier(.2, .85, .25, 1); }

.bbw-car--film .bbw-car__slide,
.bbw-car--swipe .bbw-car__slide {
  flex: 0 0 var(--slide-w, 74%);
  transition: opacity .5s var(--ease), transform .55s var(--ease-out);
  opacity: .38;
  transform: scale(.93);
}
.bbw-car--film .bbw-car__slide.is-active,
.bbw-car--swipe .bbw-car__slide.is-active { opacity: 1; transform: none; }

.bbw-car--swipe { --slide-w: 78%; --slide-gap: .6rem; }
.bbw-car--swipe .bbw-car__slide { opacity: .45; transform: scale(.95); }

/* --- 3 · CATALOGUE (The Men's Shop, 5 image slides) -------------------------
   Product-catalogue behaviour: one large slide at a time, crossfaded, with a
   thumbnail rail standing in for the dot row. The rail IS the indicator —
   these five slides are near-identical in composition and differ by product,
   so a thumbnail says more than a dash. */
/* The viewport keeps the slides' own 4:5 — these five carry fine annotation
   callouts pinned close to the frame edge, so a wider stage plus object-fit:
   cover would eat the very copy the slides exist to show. On wide screens the
   plate is capped and the rail stands beside it rather than the plate being
   stretched to fill the column. */
/* The plate is CAPPED, not stretched. 4:5 across a full desktop column would
   be a 700x880 slab taller than the window; the width cap keeps it a large
   product plate that still fits on screen with its rail beside it. */
.bbw-car--catalogue .bbw-car__viewport {
  aspect-ratio: 4 / 5;
  width: min(100%, 27rem);
  margin-inline: auto;
}
.bbw-car--catalogue .bbw-car__slide {
  position: absolute; inset: 0;
  opacity: 0;
  transform: scale(1.012);
  transition: opacity .55s var(--ease), transform .7s var(--ease-out);
  pointer-events: none;
}
.bbw-car--catalogue .bbw-car__slide.is-active {
  opacity: 1; transform: none; pointer-events: auto;
}
.bbw-car--catalogue .bbw-media { height: 100%; }
.bbw-car--catalogue .bbw-media__img { height: 100%; object-fit: contain; }

.bbw-car__thumbs {
  display: flex; align-items: center; gap: .45rem;
  flex: 1 1 auto;
  min-width: 0;                 /* the rail scrolls; it must be allowed to be
                                   narrower than the five thumbs inside it */
  margin: 0; padding: 0; list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}
.bbw-car__thumbs::-webkit-scrollbar { display: none; }
.bbw-car__thumbs > li { flex: 0 0 auto; display: flex; }
.bbw-car__thumb {
  flex: 0 0 auto;
  width: 2.6rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 3px;
  background: none;
  overflow: hidden;
  cursor: pointer;
  opacity: .45;
  transition: opacity .3s var(--ease), border-color .3s var(--ease);
}
.bbw-car__thumb img { display: block; width: 100%; height: auto; }
.bbw-car__thumb:hover { opacity: .8; }
.bbw-car__thumb[aria-current="true"] { opacity: 1; border-color: var(--cs-red); }

/* --- 4 · SWIPE (This and Instagram Trend, 4 video slides) --- see FILM above,
   which it shares its transform machinery with. Only the slide width, the gap
   and the easing differ, so the two read as one system running at two
   tempos rather than as two carousels. */

/* ==================================== 9 commercial finale + closing strip */
.bbw-finale {
  position: relative;
  isolation: isolate;
  padding-block: var(--cs-space);
  border-top: 1px solid var(--cs-line);
  overflow: hidden;
}
.bbw-finale::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(80% 42% at 50% 8%,  rgba(var(--bbw-gold), .16), transparent 68%),
    radial-gradient(90% 50% at 50% 96%, rgba(var(--bbw-navy), .34), transparent 70%);
}
.bbw-finale__head { margin-bottom: 2.2rem; }
.bbw-finale__stage { margin-bottom: 2.5rem; }

/* The player. 12:5 is held by aspect-ratio on the frame and object-fit:contain
   on the video, so the film is never letterboxed INTO a 16:9 box and never
   cropped out of its own. */
.bbw-film {
  position: relative;
  aspect-ratio: 12 / 5;
  overflow: hidden;
  background: #000;
  border-block: 1px solid var(--cs-line);
}
.bbw-film__poster {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity .5s var(--ease), transform 1.4s var(--ease-out);
}
.bbw-film video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  background: #000;
}
.bbw-film.is-live .bbw-film__poster { opacity: 0; }

.bbw-film__play {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: .7rem;
  width: 100%;
  padding: 0;
  border: 0;
  background: linear-gradient(to top, rgba(7, 7, 7, .58), rgba(7, 7, 7, .12) 55%, rgba(7, 7, 7, .34));
  color: var(--cs-white);
  cursor: pointer;
  transition: background .45s var(--ease), opacity .35s var(--ease);
}
.bbw-film__play svg {
  width: 2.9rem; height: 2.9rem;
  padding: .55rem;
  border: 1px solid rgba(247, 245, 242, .55);
  border-radius: 50%;
  fill: var(--cs-white);
  background: rgba(7, 7, 7, .35);
  backdrop-filter: blur(4px);
  transition: transform .45s var(--ease-out), background .35s var(--ease),
              border-color .35s var(--ease);
}
.bbw-film__play span {
  font-family: var(--font-cond); font-weight: 400;
  font-size: .68rem; letter-spacing: .22em; text-transform: uppercase;
}
.bbw-film__play:hover svg,
.bbw-film__play:focus-visible svg {
  transform: scale(1.07);
  background: rgba(177, 98, 254, .88);
  border-color: rgba(177, 98, 254, .88);
}
.bbw-film.is-live .bbw-film__play { display: none; }

/* Closing composition — one frame per format, decorative. On a phone it is a
   simple five-across band; the overlap onto the film only happens where
   there is room for it (see the desktop block). */
.bbw-strip {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  align-items: center;
  gap: .5rem;
  margin: 0 0 2rem;
  padding: 0;
  list-style: none;
}
/* Five across a 331px canvas would be 62px each, which is not a campaign
   asset, it is a swatch. Three then two instead, so each frame is still
   readable as the format it stands for. */
.bbw-strip__item--ad,
.bbw-strip__item--post,
.bbw-strip__item--reel  { grid-column: span 2; }
.bbw-strip__item--slide,
.bbw-strip__item--wide  { grid-column: span 3; }
.bbw-strip__item {
  overflow: hidden;
  border: 1px solid var(--cs-line);
  border-radius: 3px;
  background: var(--cs-panel);
}
.bbw-strip__item img { display: block; width: 100%; height: auto; }

.bbw-finale__line {
  max-width: 34ch;
  font-family: var(--font-cond); font-weight: 300;
  font-size: .95rem; letter-spacing: .04em; line-height: 1.6;
  color: var(--cs-grey);
}

/* ========================================================== 10 CTA & next */
.cs-cta { margin-top: 3rem; }
.cs-cta__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 1.1rem 1.6rem;
}
.cs-cta__actions .btn { min-height: 44px; }

.cs-next {
  margin-top: 5rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--cs-line);
}
.cs-next__label {
  font-family: var(--font-cond); font-weight: 500;
  font-size: .62rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--cs-dim);
  margin-bottom: .6rem;
}
.cs-next__link {
  display: flex; align-items: center; gap: 1rem;
  color: var(--cs-white);
  font-family: var(--font-cond); font-weight: 400;
  font-size: 1.6rem; line-height: 1.1;
  transition: color .3s var(--ease);
}
.cs-next__link .arrow { width: 2.1rem; color: var(--cs-red); }
.cs-next__link:hover, .cs-next__link:focus-visible { color: var(--cs-red); }
.cs-next__link:hover .arrow { transform: translateX(6px); }

/* ====================================================== 11 media viewer */
/* Above .grain (z 60) and the topbar (z 20). The dialog is a column flex box
   with a definite height, which is what gives the stage a definite height,
   which is what lets max-height:100% on the media actually constrain a tall
   9:16 reel instead of letting it run off the screen. */
.bbw-lb {
  position: fixed; inset: 0; z-index: 200;
  display: grid;
}
.bbw-lb[hidden] { display: none; }

.bbw-lb__backdrop {
  position: absolute; inset: 0;
  background: rgba(4, 4, 5, .95);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
}

.bbw-lb__dialog {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center;
  gap: .7rem;
  width: 100%;
  height: 100vh;
  padding: 3.5rem .9rem 1rem;
}
@supports (height: 1dvh) { .bbw-lb__dialog { height: 100dvh; } }

.bbw-lb__stage {
  position: relative;          /* containing block for the media below */
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}

/* The media is positioned, NOT laid out in the stage, and that is load
   bearing. The stage is a flex item whose height is resolved by the flex
   pass, but a percentage max-height on an in-flow child resolves against the
   stage's INTRINSIC height instead — which is the image's own 1350px. So
   `max-height: 100%` computed to 100% and still constrained nothing: a
   1080x1350 still rendered full size and ran ~500px past the bottom of the
   viewport, taking the caption and the counter with it.

   inset:0 resolves against the stage's USED height, so the media is always
   exactly the box the flex layout actually granted and object-fit letterboxes
   inside it. No percentage is involved, so there is nothing left to resolve
   against the wrong number. */
.bbw-lb__stage img,
.bbw-lb__stage video {
  position: absolute; inset: 0;
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
}

.bbw-lb__caption {
  flex: none;
  max-width: 64ch;
  text-align: center;
  font-family: var(--font-cond); font-weight: 300;
  font-size: .74rem; letter-spacing: .04em; line-height: 1.5;
  color: var(--cs-grey);
}

.bbw-lb__bar {
  flex: none;
  display: flex; align-items: center; justify-content: center; gap: 1.1rem;
}
.bbw-lb__nav,
.bbw-lb__close {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  padding: 0;
  border: 1px solid var(--cs-line-2);
  border-radius: 50%;
  background: rgba(255, 255, 255, .04);
  color: var(--cs-white);
  cursor: pointer;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.bbw-lb__nav svg,
.bbw-lb__close svg {
  width: 1.05rem; height: 1.05rem;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.bbw-lb__nav:hover,
.bbw-lb__close:hover { border-color: var(--cs-red); background: rgba(177, 98, 254, .16); }
.bbw-lb__nav:disabled { opacity: .28; cursor: default; }

.bbw-lb__count {
  min-width: 5rem;
  text-align: center;
  font-family: var(--font-cond); font-weight: 400;
  font-size: .72rem; letter-spacing: .18em;
  color: var(--cs-grey);
  font-variant-numeric: tabular-nums;
}

.bbw-lb__close { position: absolute; top: .9rem; right: .9rem; z-index: 2; }

/* ================================================== 12 TABLET  ≥ 768px */
@media (min-width: 768px) {
  :root {
    --cs-space: 7rem;
    --cs-h1: 3.1rem;
    --cs-h2: 2.4rem;
    --feed-cols: 3;
    --feed-gap: .8rem;
  }

  .bbw-hero { padding-block: 3.5rem 4.5rem; }
  .bbw-hero__sub { font-size: 1.15rem; }
  /* Past the phone the row has width to spare, so the tighter tracking and the
     tighter gap tracks the fluid mobile size needs are all handed back here. */
  .bbw-scale { font-size: .88rem; gap: .35rem 1.1rem; letter-spacing: .1em; }
  .bbw-scale__item { gap: .35em; }
  .bbw-scale__item + .bbw-scale__item::before { margin-right: .4em; }
  .bbw-scale .count { font-size: 1.2rem; }

  .bbw-collage { max-width: 34rem; }

  .bbw-cars { gap: 5.5rem; }
  .bbw-car__title { font-size: 1.6rem; }

  .bbw-car--stack .bbw-car__viewport { aspect-ratio: 1 / .82; }
  .bbw-car--stack .bbw-car__slide { width: 62%; }

  .bbw-car--film  { --slide-w: 48%; --slide-gap: 1rem; }
  .bbw-car--swipe { --slide-w: 44%; --slide-gap: .9rem; }

  .bbw-car__thumb { width: 3.4rem; }

  .bbw-strip { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .8rem; }
  .bbw-strip__item { grid-column: auto; }
  .bbw-finale__line { font-size: 1.05rem; max-width: 40ch; }

  .bbw-lb__dialog { padding: 4rem 2.5rem 1.4rem; gap: 1rem; }
  .bbw-lb__caption { font-size: .8rem; }
  .bbw-lb__close { top: 1.4rem; right: 1.6rem; }
}

/* ================================================= 13 DESKTOP  ≥ 1024px */
@media (min-width: 1024px) {
  :root {
    --cs-space: 8.5rem;
    --cs-h1: 3.9rem;
    --cs-h2: 2.9rem;
    --feed-cols: 4;
    --feed-gap: 1rem;
    --feed-row: 8px;
  }

  .cs-sec__head { margin-bottom: 3.4rem; }

  /* --- hero: copy left, collage right, both inside the shell --- */
  .bbw-hero {
    display: flex; align-items: center;
    min-height: 88vh;
    padding-block: 4.5rem 5rem;
  }
  @supports (height: 1svh) { .bbw-hero { min-height: min(88svh, 900px); } }

  .bbw-hero__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr);
    align-items: center;
    gap: 3.5rem;
    width: 100%;
  }
  .bbw-hero__copy { max-width: 34rem; }
  .bbw-hero__sub { font-size: 1.28rem; margin-top: 1.35rem; }
  .bbw-scale { margin-top: 2.2rem; padding-top: 1.7rem; }

  /* The square field the collage percentages are derived against. All five
     cards are placed here; see the note in section 5 before moving any of
     them, because the vertical values only mean what they say while this
     stays 1/1. */
  .bbw-collage {
    aspect-ratio: 1 / 1;
    max-width: none;
    margin: 0;
  }
  /* ALL FOUR INSETS on every card, every time. Three of these are overriding
     a mobile rule that anchored to a different edge, and leaving the old edge
     in place gives the card two opposing insets — which stretches it and
     silently destroys its aspect ratio. The square ad did exactly that: it
     rendered 222x610 instead of 222x222 because `bottom: 0` survived from the
     phone layout. Auto is not the default here; it has to be written. */
  .bbw-collage__item--post {
    left: 2%; top: 8%; right: auto; bottom: auto; width: 42%; z-index: 5;
  }
  .bbw-collage__item--ad {
    left: 52%; top: 4%; right: auto; bottom: auto; width: 32%; z-index: 3;
  }
  .bbw-collage__item--wide {
    left: 36%; top: 34%; right: auto; bottom: auto; width: 60%; z-index: 2;
  }
  .bbw-collage__item--reel {
    left: 64%; bottom: 4%; right: auto; top: auto; width: 24%; z-index: 4;
  }
  .bbw-collage__item--slide {
    left: 8%; bottom: 2%; right: auto; top: auto; width: 30%; z-index: 1;
  }

  /* --- carousels: alternating left/right compositions --- */
  .bbw-cars { gap: 7rem; }
  .bbw-car {
    display: grid;
    grid-template-columns: minmax(0, 0.32fr) minmax(0, 0.68fr);
    align-items: center;
    gap: 3.5rem;
  }
  .bbw-car__head { margin-bottom: 0; }
  .bbw-car__title { font-size: 1.9rem; }

  /* every second set flips: media left, title right */
  .bbw-car:nth-child(even) { grid-template-columns: minmax(0, 0.68fr) minmax(0, 0.32fr); }
  .bbw-car:nth-child(even) .bbw-car__head { order: 2; text-align: right; }
  .bbw-car:nth-child(even) .bbw-car__stage { order: 1; }

  .bbw-car--stack { --slide-w: 56%; }
  .bbw-car--stack .bbw-car__viewport { aspect-ratio: 1 / .74; }
  .bbw-car--stack .bbw-car__slide { width: var(--slide-w); }

  .bbw-car--film  { --slide-w: 40%; --slide-gap: 1.1rem; }
  .bbw-car--swipe { --slide-w: 36%; --slide-gap: 1rem; }

  /* --- nav aligned to the ACTIVE SLIDE, not to the stage ---------------
     DESKTOP ONLY. Each presentation parks its live slide somewhere
     different inside a full-width viewport — the stack pins it flush left
     at --slide-w, film and swipe centre theirs — but the control row spanned
     the whole viewport in every case. Measured at 1900px that put the stack's
     arrows 408px past the right edge of the card they drive, and film's and
     swipe's roughly 280px outside it on both sides, so the nav read as
     belonging to the section rather than to the slide.

     Matching the row to the slide box is why --slide-w is now the single
     source for both; the stack's width used to be a second hard-coded 56%
     and would have drifted the moment either was tuned.

     Deliberately NOT applied below 1024px: there the slide already fills the
     viewport, so a full-width control row is already aligned, and narrowing
     it would only cramp the arrows and dots. The catalogue is untouched too
     — its controls are the vertical thumbnail rail standing beside the
     plate, which is its own deliberate composition. */
  .bbw-car--stack .bbw-car__controls {
    width: var(--slide-w);
    margin-right: auto;
  }
  .bbw-car--film  .bbw-car__controls,
  .bbw-car--swipe .bbw-car__controls {
    width: var(--slide-w);
    margin-inline: auto;
  }

  /* The catalogue splits its stage: the portrait plate takes the space it
     needs and the thumbnail rail stands up beside it as a vertical index,
     which is what a product catalogue looks like and what stops a 4:5 plate
     from having to stretch across a 68% column. */
  .bbw-car--catalogue .bbw-car__stage {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: center;
    align-items: center;
    gap: 1.6rem;
  }
  /* A flat width, not min(100%, …): the stage's columns are content-sized
     here, so a percentage would be resolving against a column that is itself
     waiting on this element — which collapses the plate to zero. */
  .bbw-car--catalogue .bbw-car__viewport { width: 27rem; margin-inline: 0; }
  .bbw-car--catalogue .bbw-car__controls--thumbs {
    flex-direction: column;
    margin-top: 0;
    gap: .7rem;
  }
  .bbw-car--catalogue .bbw-car__thumbs {
    flex-direction: column;
    overflow: visible;
    flex: 0 0 auto;
  }
  .bbw-car--catalogue .bbw-car__arrow { width: 38px; height: 38px; }

  .bbw-car__thumb { width: 4rem; }

  /* --- finale: the strip climbs onto the film --- */
  .bbw-finale__stage { margin-bottom: 0; }
  .bbw-finale__close { position: relative; }
  .bbw-strip {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 1.1rem;
    margin-top: -5.5rem;
    margin-bottom: 3rem;
  }
  .bbw-strip__item { box-shadow: 0 26px 54px -28px rgba(0, 0, 0, .95); }

  .bbw-finale__line { font-size: 1.15rem; max-width: 44ch; }

  .cs-cta { margin-top: 4rem; }
  .cs-next { margin-top: 7rem; }
  .cs-next__link { font-size: 2.1rem; }

  .bbw-lb__dialog { padding: 4.5rem 4.5rem 1.6rem; }
  .bbw-lb__close { top: 2rem; right: 2.4rem; }
}

/* ============================================ 14 LARGE DESKTOP  ≥ 1440px */
@media (min-width: 1440px) {
  :root {
    --cs-h1: 4.5rem;
    --feed-gap: 1.15rem;
  }
  .bbw-hero__copy { max-width: 38rem; }
  .bbw-cars { gap: 8rem; }
  .bbw-car { gap: 4.5rem; }
}

/* ================================================== 15 reduced motion */
/* Everything on this page that moves is decoration on top of media that is
   already legible standing still, so under reduce it all simply stops: the
   collage lands in place, the carousels cut instead of sliding, and the
   video previews never start at all (that last one is enforced in the JS,
   which does not attach the hover or the centre-of-screen observers). */
@media (prefers-reduced-motion: reduce) {
  .js .bbw-collage__frame { opacity: 1; animation: none; }
  .bbw-collage__item { transition: none; transform: rotate(var(--rot, 0deg)); }

  .bbw-media,
  .bbw-media__img,
  .bbw-media__tag,
  .bbw-media__play,
  .bbw-media__video,
  .bbw-film__poster,
  .bbw-film__play svg,
  .bbw-car__dot::before,
  .bbw-car__thumb,
  .bbw-car__arrow { transition: none; }

  .bbw-media:hover,
  .bbw-media:focus-visible { transform: none; }
  .bbw-media:hover .bbw-media__img,
  .bbw-media:focus-visible .bbw-media__img { transform: none; }

  .bbw-car__track,
  .bbw-car--stack .bbw-car__slide,
  .bbw-car--film .bbw-car__slide,
  .bbw-car--swipe .bbw-car__slide,
  .bbw-car--catalogue .bbw-car__slide { transition: none; }

  .bbw-car--catalogue .bbw-car__slide { transform: none; }
  .bbw-car--film .bbw-car__slide,
  .bbw-car--swipe .bbw-car__slide { transform: none; }
}
