/* ==========================================================================
   FOFO COFFEE — motion, cursor, dock, marquee, gallery
   --------------------------------------------------------------------------
   Loaded after styles.css. Uses the same tokens; adds no new colours.
   Everything degrades: if GSAP never loads, nothing here hides content.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Loading screen
   --------------------------------------------------------------------------
   Shown only when the inline <head> script adds html.is-loading — so if
   JavaScript is off, or the visitor has already seen it this session, the
   overlay never exists and can never trap anyone behind it.
   -------------------------------------------------------------------------- */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 9998;
  display: none;
  place-items: center;
  background: var(--cream);
}
html.is-loading .preloader { display: grid; }
html.is-loading,
html.is-loading body { overflow: hidden; }

/* The art stays hidden through the grace period. If the page is ready before
   it elapses, the overlay lifts without the visitor ever seeing a loader. */
.preloader__art {
  width: min(62vw, 320px);
  aspect-ratio: 1;
  position: relative;
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.preloader.is-armed .preloader__art { opacity: 1; }
.preloader__art video,
.preloader__art img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  /* the clip is maroon-on-white; multiply drops the white onto our cream */
  mix-blend-mode: multiply;
}
/* the still only shows if the video can't autoplay */
.preloader__art img { opacity: 1; }
.preloader__art video { opacity: 0; transition: opacity .3s; }
.preloader.is-playing video { opacity: 1; }
.preloader.is-playing img { opacity: 0; }

.preloader__bar {
  position: absolute;
  left: 50%;
  bottom: 18%;
  transform: translateX(-50%);
  width: min(46vw, 190px);
  height: 2px;
  background: rgba(74, 14, 18, .16);
  border-radius: 2px;
  overflow: hidden;
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.preloader.is-armed .preloader__bar { opacity: 1; }

.preloader__bar::after {
  content: '';
  display: block;
  height: 100%;
  width: 100%;
  background: var(--maroon);
  transform: scaleX(0);
  transform-origin: left;
}
/* only starts once the loader has actually been shown */
.preloader.is-armed .preloader__bar::after {
  animation: preload-bar 4s cubic-bezier(.25, .8, .3, 1) forwards;
}
@keyframes preload-bar {
  0%   { transform: scaleX(0); }
  60%  { transform: scaleX(.7); }
  100% { transform: scaleX(1); }
}

/* fade out, then it's removed from the DOM entirely */
.preloader.is-done {
  opacity: 0;
  visibility: hidden;
  transition: opacity .65s var(--ease), visibility .65s;
}

@media (prefers-reduced-motion: reduce) {
  .preloader__bar::after { animation: none; transform: scaleX(1); }
}

/* --------------------------------------------------------------------------
   1. Custom cursor  (pointer devices only — see the media query at the end)
   -------------------------------------------------------------------------- */
.cursor {
  position: fixed;
  top: 0; left: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s;
}
.cursor.is-active { opacity: 1; }

/* No transform here on purpose: GSAP owns it and centres these with
   xPercent/yPercent, which it recomputes as the ring changes size. A CSS
   translate(-50%,-50%) would get baked into stale pixels on first parse. */
.cursor__dot,
.cursor__ring {
  position: absolute;
  top: 0; left: 0;
  border-radius: 50%;
  will-change: transform;
}
.cursor__dot {
  width: 7px; height: 7px;
  background: var(--maroon);
}
.cursor__ring {
  width: 38px; height: 38px;
  border: 1.5px solid rgba(74, 14, 18, .45);
  transition: width .3s var(--ease), height .3s var(--ease),
              background-color .3s, border-color .3s, opacity .3s;
}

/* over something clickable */
.cursor.is-hovering .cursor__ring {
  width: 62px; height: 62px;
  background: rgba(74, 14, 18, .12);
  border-color: transparent;
}
.cursor.is-hovering .cursor__dot { opacity: 0; }

/* over dark sections the cursor inverts */
.cursor.is-light .cursor__dot { background: var(--cream); }
.cursor.is-light .cursor__ring { border-color: rgba(244, 236, 231, .5); }
.cursor.is-light.is-hovering .cursor__ring { background: rgba(244, 236, 231, .18); }

/* a word that rides inside the ring, e.g. "drag" or "play" */
.cursor__label {
  position: absolute;
  top: 0; left: 0;
  font-family: var(--font-display);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--cream);
  opacity: 0;
  transition: opacity .25s;
  white-space: nowrap;
}
.cursor.is-labelled .cursor__label { opacity: 1; }
.cursor.is-labelled .cursor__ring {
  width: 76px; height: 76px;
  background: var(--maroon);
  border-color: transparent;
}
.cursor.is-labelled .cursor__dot { opacity: 0; }

/* --------------------------------------------------------------------------
   2. Bottom dock
   -------------------------------------------------------------------------- */
.dock {
  position: fixed;
  left: 50%;
  bottom: calc(1rem + env(safe-area-inset-bottom));
  z-index: 70;
  transform: translate(-50%, 150%);
  transition: transform .55s var(--ease);
  width: min(560px, calc(100vw - 2rem));
}
.dock.is-up { transform: translate(-50%, 0); }

/* One row, always. The three page links must never be swapped out — on a
   phone the top nav links are hidden, so this bar IS the navigation. Only the
   button on the right changes as the cart fills. */
.dock__inner {
  display: flex;
  align-items: center;
  gap: .4rem;
  height: 62px;
  padding: .5rem .5rem .5rem 1rem;
  border-radius: var(--radius-pill);
  background: rgba(74, 14, 18, .93);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  box-shadow: 0 14px 40px rgba(23, 12, 9, .3);
}

.dock__nav {
  display: flex;
  align-items: center;
  gap: .15rem;
  flex: 1 1 auto;
  min-width: 0;
}

.dock__link {
  flex: 0 0 auto;
  padding: .45rem .7rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: .88rem;
  font-weight: 500;
  color: rgba(244, 236, 231, .72);
  text-decoration: none;
  transition: color .2s, background-color .2s;
}
.dock__link:hover { color: var(--cream); background: rgba(244, 236, 231, .1); }
.dock__link[aria-current='page'] { color: var(--cream); }

.dock__cta {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--cream);
  color: var(--maroon);
  padding: .7rem 1.2rem;
  font-family: var(--font-display);
  font-size: .95rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .2s, transform .2s var(--ease);
}
.dock__cta:hover { background: #fff; }

/* cart count rides on the button so the total never crowds the nav links */
.dock__count {
  display: none;
  min-width: 21px;
  height: 21px;
  padding-inline: 5px;
  border-radius: var(--radius-pill);
  background: var(--maroon);
  color: var(--cream);
  font-size: .74rem;
  font-weight: 700;
  line-height: 21px;
  text-align: center;
}
.dock.is-cart .dock__count { display: inline-block; }

@media (max-width: 400px) {
  .dock__inner { padding-left: .7rem; }
  .dock__link  { padding: .45rem .38rem; font-size: .8rem; }
  .dock__cta   { padding: .7rem .95rem; font-size: .88rem; }
}

/* the old in-page cart bar is replaced by the dock */
.cart-bar { display: none !important; }

/* keep page content clear of the dock */
body { padding-bottom: 5.5rem; }

/* --------------------------------------------------------------------------
   3. Marquee
   -------------------------------------------------------------------------- */
.marquee {
  overflow: hidden;
  padding-block: 1.4rem;
  background: var(--maroon);
  border-block: 1px solid rgba(244, 236, 231, .12);
  user-select: none;
}
.marquee--cream { background: var(--cream-warm); border-color: var(--line); }

.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee-slide 38s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }

.marquee__group {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}
.marquee__item {
  display: inline-flex;
  align-items: center;
  gap: 1.75rem;
  padding-inline: .9rem;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 4.4vw, 2.1rem);
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--cream);
  white-space: nowrap;
}
.marquee--cream .marquee__item { color: var(--maroon); }
.marquee__item::after {
  content: '';
  width: 9px; height: 9px;
  border-radius: 50%;
  background: currentColor;
  opacity: .45;
}

@keyframes marquee-slide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* --------------------------------------------------------------------------
   4. Hero video
   -------------------------------------------------------------------------- */
.hero__media video {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
}

/* The hero clip is 9:16 portrait, so a wide screen only shows a horizontal
   band of it. Centring lands on the middle of the frame; shifting the window
   down brings the lower part of the shot — the cup and the pour — into view.
   Mobile is left alone: there the whole frame is already visible. */
@media (min-width: 880px) {
  .hero__media video { object-position: center 72%; }
}

.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  font-size: .66rem;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: rgba(244, 236, 231, .6);
  pointer-events: none;
}
.hero__scroll::after {
  content: '';
  width: 1px; height: 34px;
  background: linear-gradient(to bottom, rgba(244, 236, 231, .6), transparent);
}

/* --------------------------------------------------------------------------
   5. Reveal primitives — GSAP sets the "from" state, so no FOUC without JS
   -------------------------------------------------------------------------- */
.split-line { display: block; overflow: hidden; }
.split-line > span { display: block; will-change: transform; }

/* --------------------------------------------------------------------------
   6. Gallery wall
   -------------------------------------------------------------------------- */
.gallery { overflow: hidden; padding-block: 4rem 5rem; background: var(--espresso); }

.gallery__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .75rem 1.5rem;
  padding-inline: var(--gutter);
  max-width: 1120px;
  margin: 0 auto 2.25rem;
}
.gallery__title {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 7vw, 3rem);
  font-weight: 700;
  color: var(--cream);
  margin: 0;
}
.gallery__handle {
  font-family: var(--font-ui);
  font-size: .9rem;
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--gold);
  text-decoration: none;
  margin-left: auto;
  border-bottom: 1px solid rgba(201, 138, 75, .4);
  padding-bottom: 2px;
  transition: color .2s, border-color .2s;
}
.gallery__handle:hover { color: var(--cream); border-color: var(--cream); }

/* A real horizontal scroll container, so it can simply be swiped. It used to
   be a max-content strip translated by scroll position, which meant it could
   not be swiped at all and only crept sideways as the page scrolled. */
.gallery__track {
  display: flex;
  gap: 1rem;
  padding-inline: var(--gutter);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scroll-padding-left: var(--gutter);
  /* flex-start, not the default stretch — stretch overrides the tiles'
     aspect-ratio and pulls every one of them to the tallest item's height,
     which the staggered margin-top makes taller still. */
  align-items: flex-start;
  scrollbar-width: none;
  cursor: grab;
}
.gallery__track::-webkit-scrollbar { display: none; }
.gallery__track.is-dragging { cursor: grabbing; scroll-snap-type: none; }

/* trailing spacer so the last tile can clear the right edge */
.gallery__track::after {
  content: '';
  flex: 0 0 calc(var(--gutter) - 1rem);
}

.tile {
  position: relative;
  flex: 0 0 auto;
  width: clamp(190px, 46vw, 280px);
  aspect-ratio: 9 / 16;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--espresso-soft);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
.tile:nth-child(even) { margin-top: 2.25rem; }
.tile { scroll-snap-align: start; }

.tile img,
.tile video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .7s var(--ease);
}
.tile:hover img,
.tile:hover video { transform: scale(1.05); }

/* --- tap to play -------------------------------------------------------- */
/* Mobile blocks autoplay outright in Low Power Mode, so every clip needs a
   way to be started by hand. The button hides itself if autoplay succeeds. */
.video-play {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  transition: opacity .3s;
}
.video-play::before {
  content: '';
  width: 58px; height: 58px;
  border-radius: 50%;
  background: rgba(23, 12, 9, .55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 4px 18px rgba(0, 0, 0, .3);
  transition: transform .25s var(--ease), background-color .25s;
}
.video-play::after {
  content: '';
  position: absolute;
  width: 0; height: 0;
  margin-left: 4px;
  border-left: 17px solid var(--cream);
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
}
.video-play:hover::before { transform: scale(1.08); background: rgba(23, 12, 9, .72); }
.video-play:active::before { transform: scale(.94); }

/* once it's running, get out of the way */
.video-play.is-playing { opacity: 0; pointer-events: none; }

/* on a light ground the button needs the opposite treatment */
.story__art--square .video-play::before { background: rgba(74, 14, 18, .5); }

.tile__badge {
  position: absolute;
  top: .6rem; left: .6rem;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  background: rgba(23, 12, 9, .55);
  backdrop-filter: blur(6px);
  color: var(--cream);
  border-radius: var(--radius-pill);
  padding: .22rem .6rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   6b. Scroll-expand showcase
   --------------------------------------------------------------------------
   The tall section provides the scroll distance; the stage sticks to the
   viewport while the frame grows. No scroll hijacking — the page scrolls
   normally throughout.
   Defaults below are the FINISHED state, so with JS off or reduced motion
   the section still reads correctly.
   -------------------------------------------------------------------------- */
.expand {
  position: relative;
  background: var(--espresso);
}
.expand__bg { position: absolute; inset: 0; z-index: 0; }
/* The backdrop photo is tall portrait, so a wide desktop viewport only ever
   shows a horizontal band of it. Biased downward to land on the branded cloth
   rather than the middle of the counter — which framed the payment QR code. */
.expand__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 66%;
  opacity: .45;
}

/* The stage is pinned by ScrollTrigger, which needs it to own a full viewport
   and clip its own contents — the words travel outside it as the clip grows. */
.expand__stage {
  position: relative;
  z-index: 1;
  height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--espresso);
}

.expand__frame {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(94vw, 620px);
  aspect-ratio: 9 / 16;
  max-height: 82svh;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .45);
  background: var(--espresso-soft);
}
.expand__frame video { width: 100%; height: 100%; object-fit: cover; display: block; }
.expand__scrim { position: absolute; inset: 0; background: rgba(23, 12, 9, .28); }

.expand__title {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1em;
  margin: 0;
  font-family: var(--font-display);
  /* sized for the longest word ("COFFEE CLUB") rather than the shortest —
     11vw fitted "pop-up" and pushed the longer word off a 375px screen */
  font-size: clamp(1.75rem, 7.2vw, 4.25rem);
  font-weight: 800;
  line-height: 1;
  color: var(--cream);
  text-align: center;
  pointer-events: none;
  text-shadow: 0 4px 30px rgba(0, 0, 0, .45);
}
/* nowrap: the words are pushed sideways, so a wrap mid-phrase would break the
   split apart into ragged lines */
.expand__word { display: block; white-space: nowrap; }

.expand__hint {
  position: absolute;
  bottom: 8%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  margin: 0;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: rgba(244, 236, 231, .72);
  pointer-events: none;
}

/* Copy section that follows the pinned stage */
.expand-after {
  position: relative;
  z-index: 1;
  padding-block: 4.5rem 5.5rem;
  background: var(--espresso);
  color: var(--cream);
}
.expand-after .section__eyebrow { color: var(--gold); }
.expand__after-title {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 6vw, 2.75rem);
  color: var(--cream);
  margin: 0 0 1rem;
}
.expand-after .section__body { color: rgba(244, 236, 231, .82); }

/* --------------------------------------------------------------------------
   7. Editorial feature rows
   -------------------------------------------------------------------------- */
.feature { display: grid; gap: 1.75rem; align-items: center; }
.feature + .feature { margin-top: 4.5rem; }

.feature__media {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--white);
  aspect-ratio: 1;
}
.feature__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  mix-blend-mode: multiply;
  will-change: transform;
}
.feature__index {
  font-family: var(--font-display);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .2em;
  color: var(--maroon-soft);
  margin-bottom: .6rem;
}
.feature__name {
  font-family: var(--font-serif);
  font-size: clamp(2rem, 8vw, 3rem);
  font-weight: 600;
  color: var(--maroon);
  line-height: 1.05;
  margin: 0 0 .6rem;
}
.feature__notes {
  font-family: var(--font-serif);
  font-size: 1.15rem;
  color: var(--text-muted);
  line-height: 1.6;
  margin: 0 0 1.25rem;
}
.feature__price {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--maroon);
}

@media (min-width: 820px) {
  .feature { grid-template-columns: 1fr 1fr; gap: 4rem; }
  .feature:nth-child(even) .feature__media { order: 2; }
  .feature + .feature { margin-top: 7rem; }
}

/* --------------------------------------------------------------------------
   8. Magnetic buttons
   -------------------------------------------------------------------------- */
[data-magnetic] { will-change: transform; }

/* --------------------------------------------------------------------------
   9. Preferences & touch
   -------------------------------------------------------------------------- */
@media (hover: none), (pointer: coarse) {
  .cursor { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .cursor { display: none; }
  .tile:hover img, .tile:hover video { transform: none; }
}
