/* Club Confidant theme v3 — motion design system (lane B)
   Binding contract: E:\clubconfidant-capture\MOTION-CONTRACT.md
   Owns: tokens, all keyframes, SDA blocks, View Transitions rules, and the
   CSS side of CC decoration (motion.js does the JS side).

   Rules this file obeys throughout:
   - Compositor whitelist: only transform/opacity are ever keyframed. The two
     explicit contract exceptions are used exactly where the contract names
     them: backdrop-filter (static, never animated) on the modal veil, and
     grid-template-rows on .m-collapse (the accepted FLIP-free pattern).
   - Decorative motion lives ONLY inside `@media (prefers-reduced-motion: no-preference)`.
   - Functional, JS-awaited transitions (modal, cartbar entrance, collapse,
     tab-panel crossfade) stay outside that gate so they always run, but get
     an explicit `@media (prefers-reduced-motion: reduce)` override forcing
     them to .01ms.
   - Scroll/view-driven effects are wrapped in `@supports (animation-timeline: view())`
     and are final-state-first: the un-gated base rule is always the fully
     visible, laid-out state.
   - `will-change` never appears in this file — motion.js's helper sets/clears
     it only on the fly-to-cart clone and the tab indicator, right before and
     right after their own transitions. */

/* ===== 1. Tokens ===================================================== */
:root {
  --m-fast: 120ms;      /* micro feedback: presses, checks, badge pulse */
  --m-base: 240ms;      /* standard: modal, drawer, tab panels, reveals */
  --m-slow: 400ms;      /* expressive ceiling: hero entrance, fly-to-cart is 650ms exception */
  --e-out: cubic-bezier(.22,.61,.36,1);
  --e-inout: cubic-bezier(.65,.05,.36,1);
  --e-spring: cubic-bezier(.34,1.56,.64,1);   /* overshoot: modal open, check morph */
  --e-fly: cubic-bezier(.75,-0.5,0,1.75);     /* fly-to-cart only */
}

/* Animated custom properties get their own @property per rule 5 — declared
   top-level, set on the leaf node (.m-tab-indicator) only, never inherited. */
@property --tx {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}
@property --tw {
  syntax: '<number>';
  inherits: false;
  initial-value: 1;
}

/* ===== 2. .m-reveal — view()-timeline fade+rise ======================= */
/* Final-state-first: fully visible/laid-out unless BOTH the browser
   supports view-timeline AND the user has not asked for reduced motion. */
.m-reveal {
  opacity: 1;
  transform: none;
}
@keyframes m-reveal-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .m-reveal {
      animation: m-reveal-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 30%;
    }
  }
}

/* ===== 3. .m-stagger — index-based delay ============================== */
/* Consumed by .m-hero-line (animation-delay drives the load-time stagger)
   and by .m-reveal groups (transition-delay covers any transition-based
   reveal usage; harmless no-op alongside the animation above otherwise). */
@media (prefers-reduced-motion: no-preference) {
  .m-stagger {
    animation-delay: calc(var(--i, 0) * 40ms);
    transition-delay: calc(var(--i, 0) * 40ms);
  }
}

/* ===== 4. .m-hero-line — load entrance ================================ */
.m-hero-line {
  opacity: 1;
  transform: none;
}
@keyframes m-hero-line-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  .m-hero-line {
    animation: m-hero-line-in 500ms var(--e-out) both;
    animation-delay: calc(var(--i, 0) * 40ms);
  }
}

/* ===== 4b. .m-hero-banner — the brand artwork's entrance =============== */
.m-hero-banner {
  opacity: 1;
  transform: none;
}
@keyframes m-hero-banner-in {
  from { opacity: 0; transform: translateY(10px) scale(.992); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  .m-hero-banner {
    animation: m-hero-banner-in 600ms var(--e-out) both;
  }
}

/* ===== 4c. .is-capped — blocked "+" at the purchase cap must not look broken */
@keyframes m-capped-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  75% { transform: translateX(3px); }
}
.countbox.is-capped {
  border-color: var(--cc-sale, #C0392B);
}
@media (prefers-reduced-motion: no-preference) {
  .countbox.is-capped {
    animation: m-capped-shake 200ms var(--e-out) 1;
  }
}

/* ===== 5. .m-press — active-state feedback ============================ */
.m-press {
  transform: scale(1);
}
.m-press:active {
  transform: scale(.97);
}
@media (prefers-reduced-motion: no-preference) {
  .m-press {
    transition: transform var(--m-fast) var(--e-out);
  }
}

/* ===== 6. .m-tab-indicator + .tab-panel crossfade ====================== */
.section-tabs {
  position: relative; /* positioning context for the indicator this file adds */
}
.m-tab-indicator {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 3px;
  background: var(--cc-ink-accent, #B4473C);
  transform-origin: left center;
  transform: translateX(var(--tx)) scaleX(var(--tw));
  pointer-events: none;
  z-index: 1;
}
@media (prefers-reduced-motion: no-preference) {
  .m-tab-indicator {
    transition: transform var(--m-base) var(--e-out);
  }
}

/* Panel crossfade is functional (motion.js drives it via inline opacity so
   it can fade the incoming panel in even though base.css toggles panels
   with display:none — see motion.js CC.tab decoration and the deviation
   note in the final report). The transition itself always applies; only
   the reduce override forces it instant. */
.tab-panel {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .tab-panel {
    transition-duration: .01ms !important;
  }
}

/* ===== 7. .m-modal — open/close ======================================== */
/* Final state (no .is-opening) is always the fully visible, in-place sheet —
   so if motion.js never runs, #cc-modal.m-modal still renders correctly the
   instant base cc.js clears [hidden]. motion.js toggles .is-opening to
   animate INTO that state on open, and back OUT of it before hiding on close. */
.m-modal .veil {
  opacity: 1;
  backdrop-filter: blur(6px);   /* static — never animated, per contract rule 1 */
  -webkit-backdrop-filter: blur(6px);
}
.m-modal .sheet {
  transform: scale(1);
  opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
  .m-modal .veil {
    transition: opacity var(--m-base) var(--e-out);
  }
  .m-modal .sheet {
    transition: transform var(--m-base) var(--e-spring), opacity var(--m-base) var(--e-out);
  }
  .m-modal.is-opening .veil {
    opacity: 0;
  }
  .m-modal.is-opening .sheet {
    transform: scale(.96);
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .m-modal .veil,
  .m-modal .sheet {
    transition-duration: .01ms !important;
  }
}

/* ===== 8. .m-badge-pulse ================================================ */
@keyframes m-badge-pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.25); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: no-preference) {
  .m-badge-pulse {
    animation: m-badge-pulse 300ms var(--e-out);
  }
}

/* ===== 9. .btn-add loading / success choreography ====================== */
@keyframes m-btn-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: .75; transform: scale(.98); }
}
@keyframes m-btn-pop {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: no-preference) {
  .btn-add.is-adding {
    animation: m-btn-pulse 900ms ease-in-out infinite;
  }
  .btn-add.is-added {
    animation: m-btn-pop 300ms var(--e-spring);
  }
}

/* ===== 10. .m-fly — fly-to-cart clone base ============================= */
/* JS-choreographed only: motion.js checks CC.motionOK() before ever
   creating this element, so no reduced-motion gate is needed here. */
.m-fly {
  position: fixed;
  pointer-events: none;
  border-radius: 8px;
  z-index: 999;
  transition: transform 650ms var(--e-fly), opacity 650ms var(--e-out);
}

/* ===== 11. .m-tick — subtotal update blip =============================== */
@keyframes m-tick {
  0%   { opacity: .4; transform: translateY(-2px); }
  100% { opacity: 1;  transform: translateY(0); }
}
@media (prefers-reduced-motion: no-preference) {
  .m-tick {
    animation: m-tick 220ms var(--e-out);
  }
}

/* ===== 12. View Transitions ============================================= */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 200ms;
    animation-timing-function: var(--e-out);
  }
  /* Named product-{{ product.id }} transitions (lane D) intentionally get no
     overrides here and inherit the UA's default cross-fade/morph pair. */
}

/* ===== 13. Cart bar entrance ============================================= */
@keyframes m-cartbar-in {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
@media (prefers-reduced-motion: no-preference) {
  .cartbar.m-in {
    animation: m-cartbar-in 350ms var(--e-out) both;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cartbar.m-in {
    animation-duration: .01ms !important;
  }
}

/* ===== 14. .m-collapse — cart row removal =============================== */
/* Contract-cited exception to the compositor whitelist: grid-template-rows
   1fr -> 0fr is the accepted FLIP-free collapse trick. Lane E wraps each
   removable cart row as `<div class="m-collapse"><div>...row...</div></div>` —
   the inner element needs no extra class; this rule handles it structurally. */
.m-collapse {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  overflow: hidden;
  transition: grid-template-rows var(--m-base) var(--e-inout), opacity var(--m-base) var(--e-inout);
}
.m-collapse > * {
  min-height: 0;
  overflow: hidden;
}
.m-collapse.is-collapsing {
  grid-template-rows: 0fr;
  opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
  .m-collapse {
    transition-duration: .01ms !important;
  }
}

/* ===== 15. Showpiece layer (v3.1 "eye-catching" pass, operator 2026-08-13) ===== */

/* -- top scroll progress ribbon (scroll-driven, zero JS) -- */
.scroll-progress{
  position:fixed;top:0;left:0;right:0;height:3px;z-index:90;
  background:linear-gradient(90deg,var(--cc-accent-dark),var(--cc-accent));
  transform-origin:left center;transform:scaleX(0);pointer-events:none;
}
@supports (animation-timeline: scroll()){
  @media (prefers-reduced-motion: no-preference){
    .scroll-progress{
      animation:m-progress linear both;
      animation-timeline:scroll(root block);
    }
  }
}
@keyframes m-progress{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* -- logo shine sweep (periodic, subtle spectacle) -- */
.top-header .logo{position:relative;overflow:hidden;display:inline-block}
.top-header .logo::after{
  content:"";position:absolute;top:0;bottom:0;width:38%;left:0;
  background:linear-gradient(105deg,transparent 0%,rgba(255,255,255,.85) 50%,transparent 100%);
  transform:translateX(-180%) skewX(-12deg);pointer-events:none;
}
@media (prefers-reduced-motion: no-preference){
  .top-header .logo::after{animation:m-sheen-sweep 7s ease-in-out infinite}
}
@keyframes m-sheen-sweep{
  0%,78%{transform:translateX(-180%) skewX(-12deg)}
  92%,100%{transform:translateX(420%) skewX(-12deg)}
}

/* -- CTA sheen utility (.m-sheen on a button/link) -- */
.m-sheen{position:relative;overflow:hidden}
.m-sheen::after{
  content:"";position:absolute;top:0;bottom:0;width:40%;left:0;
  background:linear-gradient(105deg,transparent 0%,rgba(255,255,255,.45) 50%,transparent 100%);
  transform:translateX(-160%) skewX(-12deg);pointer-events:none;
}
@media (prefers-reduced-motion: no-preference){
  .m-sheen::after{animation:m-sheen-sweep 6s ease-in-out infinite;animation-delay:2s}
}

/* -- self-drawing section rule + dot pop (scroll-driven per section) -- */
.cat-head{border-bottom:0}
.cat-head::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:var(--cc-line);transform-origin:left center;transform:scaleX(1);
}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .cat-head::after{
      animation:m-rule-draw 1ms linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 60%;
    }
    .cat-head::before{
      animation:m-dots-pop 1ms linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 45%;
    }
  }
}
@keyframes m-rule-draw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes m-dots-pop{from{transform:scale(0)}to{transform:scale(1)}}

/* -- row hover lift (pointer devices only) -- */
@media (hover:hover) and (prefers-reduced-motion: no-preference){
  .row{transition:transform var(--m-fast) var(--e-out),box-shadow var(--m-fast) var(--e-out)}
  .row:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(43,35,32,.08);z-index:1}
  .row .pic img{transition:transform var(--m-base) var(--e-out)}
  .row:hover .pic img{transform:scale(1.07)}
}

/* -- brand-dot confetti on add-to-cart (spawned by motion.js) -- */
.m-confetti{
  position:fixed;width:8px;height:8px;border-radius:50%;z-index:95;
  pointer-events:none;opacity:1;
  transition:transform 650ms cubic-bezier(.22,.61,.36,1),opacity 650ms ease-out;
}
.m-confetti.c-blush{background:var(--cc-accent-dark)}
.m-confetti.c-sky{background:var(--cc-sky)}
.m-confetti.c-amber{background:var(--cc-amber)}
.m-confetti.c-teal{background:var(--cc-teal)}
@media (prefers-reduced-motion: reduce){
  .m-confetti{display:none}
}
