/* StrayBirds motion layer. Load after styles.css. Every hiding/moving rule is gated on
   prefers-reduced-motion: no-preference; scroll reveal additionally needs .motion-ready (set by app.js). */

/* ---------- Always-on state styles (no movement) ---------- */
.scroll-progress{position:fixed;inset:0 0 auto;height:3px;z-index:90;pointer-events:none;transition:opacity .2s}
.scroll-progress>span{display:block;height:100%;background:var(--accent,#ef4926);transform:scaleX(0);transform-origin:0 50%}
.scroll-progress.is-inactive{opacity:0}

[data-motion-arrow],.menu-toggle span{display:inline-block}
.motion-anchor-target:focus{outline:none}

@media(min-width:761px){
  #site-nav a:not(.nav-cta){position:relative}
  #site-nav a:not(.nav-cta)::after{content:"";position:absolute;left:0;right:0;bottom:9px;height:2px;background:currentColor;transform:scaleX(0);transform-origin:100% 50%}
  #site-nav a:not(.nav-cta):is(:hover,:focus-visible,.is-active)::after{transform:scaleX(1);transform-origin:0 50%}
}
@media(max-width:760px){#site-nav a.is-active:not(.nav-cta){color:#bc3518}}
#site-nav .nav-cta.is-active,#site-nav .nav-cta.is-active:hover{background:var(--ink,#20221f);color:var(--paper,#f4f2e9)}

/* Race-safe crossfade: app.js overlays a decoded copy, fades it in, then commits the real <img>. */
.motion-swap-host{position:relative}
.motion-swap-layer{position:absolute;margin:0;opacity:0;pointer-events:none}
.motion-swap-host.is-swapping>.motion-swap-layer{opacity:1;transition:opacity .34s ease}
.motion-swap-host.is-swapping>img:not(.motion-swap-layer){opacity:0;transition:opacity .34s ease}

/* Headline line masks (app.js wraps each <br>-separated line only for below-the-fold headings, then unwraps after the reveal).
   clip-path instead of overflow keeps the inline-block baseline identical to plain text, so nothing shifts. */
.motion-line{display:inline-block;clip-path:inset(-.4em -.6em -.06em -.6em)}
.motion-line__inner{display:inline-block}
.motion-logo path{transform-box:fill-box;transform-origin:50% 100%}
.motion-ride{display:none}

@media print{
  .scroll-progress,.motion-ride,.motion-wash{display:none}
  .motion-reveal,.motion-reveal>*{opacity:1!important;transform:none!important;clip-path:none!important}
  .motion-line__inner{transform:none!important}
}

/* ---------- Motion (only when the visitor has not asked for less) ---------- */
@media(prefers-reduced-motion:no-preference){
  @keyframes motion-rise{from{opacity:0;transform:translate3d(0,18px,0)}}
  @keyframes motion-fade{from{opacity:0}}
  @keyframes motion-settle{from{transform:scale(1.04)}}
  @keyframes motion-nudge{from{opacity:.15;transform:translate3d(0,6px,0)}}
  @keyframes motion-drop{from{opacity:0;transform:translate3d(0,-6px,0)}}
  @keyframes motion-dialog{from{opacity:0;transform:translate3d(0,10px,0) scale(.98)}}

  /* Hero entry: CSS-only so it also plays without JS; ends fully visible, total ≤ .8s. */
  .hero-copy>*,[data-hero]>*{animation:motion-rise .6s cubic-bezier(.2,.7,.2,1) backwards}
  .hero-copy>:nth-child(2),[data-hero]>:nth-child(2){animation-delay:.05s}
  .hero-copy>:nth-child(3),[data-hero]>:nth-child(3){animation-delay:.1s}
  .hero-copy>:nth-child(4),[data-hero]>:nth-child(4){animation-delay:.15s}
  .hero-copy>:nth-child(n+5),[data-hero]>:nth-child(n+5){animation-delay:.2s}
  .hero-image{animation:motion-fade .6s ease .08s backwards}
  .hero-image img{animation:motion-settle .6s cubic-bezier(.2,.7,.2,1) .08s backwards}

  /* Scroll reveal: only below-the-fold targets receive .motion-reveal. */
  .motion-ready .motion-reveal{opacity:0;transform:translate3d(0,24px,0);transition:opacity .6s cubic-bezier(.2,.7,.2,1) var(--motion-delay,0s),transform .6s cubic-bezier(.2,.7,.2,1) var(--motion-delay,0s)}
  .motion-ready .motion-reveal.is-revealed{opacity:1;transform:none}

  /* Navigation underline, menu icon and panel. */
  #site-nav a::after{transition:transform .32s cubic-bezier(.2,.7,.2,1)}
  .menu-toggle span{transition:transform .28s cubic-bezier(.2,.7,.2,1)}
  .menu-toggle[aria-expanded=true] span{transform:rotate(180deg)}
  @media(max-width:760px){#site-nav.is-open{animation:motion-drop .24s cubic-bezier(.2,.7,.2,1)}}

  /* Arrow glyphs lean toward where they point. */
  [data-motion-arrow]{transition:translate .28s cubic-bezier(.2,.7,.2,1)}
  :is(a,button):focus-visible [data-motion-arrow=ne]{translate:3px -3px}
  :is(a,button):focus-visible [data-motion-arrow=se]{translate:3px 3px}
  :is(a,button):focus-visible [data-motion-arrow=sw]{translate:-3px 3px}
  :is(a,button):focus-visible [data-motion-arrow=nw]{translate:-3px -3px}
  :is(a,button):focus-visible [data-motion-arrow=right]{translate:4px 0}
  :is(a,button):focus-visible [data-motion-arrow=left]{translate:-4px 0}
  :is(a,button):focus-visible [data-motion-arrow=up]{translate:0 -3px}
  :is(a,button):focus-visible [data-motion-arrow=down]{translate:0 3px}

  /* Swapped copy and loading hint. */
  .is-updating :is(#color-name,#color-caption,#color-index),#fold-state.is-updating{animation:motion-nudge .42s cubic-bezier(.2,.7,.2,1)}
  .motion-swap-host.is-loading>img:not(.motion-swap-layer){opacity:.5;transition:opacity .2s ease .18s}

  /* Video dialog. */
  #fold-dialog[open]{animation:motion-dialog .28s cubic-bezier(.2,.7,.2,1)}
  #fold-dialog[open]::backdrop{animation:motion-fade .28s ease}

  /* Native cross-document fade between pages; navigation itself is never delayed or intercepted. */
  @view-transition{navigation:auto}
  ::view-transition-old(root),::view-transition-new(root){animation-duration:.22s;animation-timing-function:ease}
  .site-header{view-transition-name:site-header}
}

/* Hover-capable pointers only: arrows and gentle photo zoom inside the existing frame.
   scale + matching clip-path inset keep the visible box identical (1 - 1/1.035)/2 ≈ 1.691%, so no overflow or layout shift.
   Product cut-outs (#color-image, #fold-image) are never zoomed. */
@media(hover:hover) and (prefers-reduced-motion:no-preference){
  :is(a,button,figure):hover [data-motion-arrow=ne]{translate:3px -3px}
  :is(a,button,figure):hover [data-motion-arrow=se]{translate:3px 3px}
  :is(a,button,figure):hover [data-motion-arrow=sw]{translate:-3px 3px}
  :is(a,button,figure):hover [data-motion-arrow=nw]{translate:-3px -3px}
  :is(a,button,figure):hover [data-motion-arrow=right]{translate:4px 0}
  :is(a,button,figure):hover [data-motion-arrow=left]{translate:-4px 0}
  :is(a,button,figure):hover [data-motion-arrow=up]{translate:0 -3px}
  :is(a,button,figure):hover [data-motion-arrow=down]{translate:0 3px}

  :is(.hero-image,.story-photo,.gallery figure,[data-zoom]) img:not(#color-image,#fold-image,.motion-swap-layer),img[data-zoom]{scale:1;clip-path:inset(0);transition:scale .7s cubic-bezier(.2,.7,.2,1),clip-path .7s cubic-bezier(.2,.7,.2,1)}
  :is(.hero-image,.story-photo,.gallery figure,[data-zoom]):hover img:not(#color-image,#fold-image,.motion-swap-layer),img[data-zoom]:hover{scale:1.035;clip-path:inset(1.691%)}
}

/* ================= Brand motion, round two =================
   Everything below moves only transform / opacity / clip-path, and only without a reduced-motion preference. */
@media(prefers-reduced-motion:no-preference){
  /* --- Logo: letters (SVG paths, in order) slide + unskew in once per session; app.js sets --i and .is-entering. --- */
  @keyframes logo-letter{from{opacity:0;transform:translate3d(0,70%,0) skewX(-18deg)}60%{opacity:1}}
  .motion-logo.is-entering path{animation:logo-letter .62s cubic-bezier(.2,.9,.25,1.15) calc(.12s + var(--i,0) * 38ms) backwards}

  /* --- Headline line masks: each line rises out of its own slot. The heading itself stays put. --- */
  .motion-ready .motion-reveal.motion-lines{opacity:1;transform:none}
  .motion-ready .motion-reveal .motion-line__inner{transform:translate3d(0,112%,0);transition:transform .8s cubic-bezier(.2,.8,.2,1) calc(var(--motion-delay,0s) + .06s + var(--line,0) * 90ms)}
  .motion-ready .motion-reveal.is-revealed .motion-line__inner{transform:none}

  /* --- Photos wipe up from the bottom instead of fading (gallery, editorial and zoomable figures).
         The clip sits on the figure's children: a fully clipped target would never intersect for IntersectionObserver.
         :not(#_) lifts specificity above the hover-zoom clip-path rule for the duration of the reveal only. --- */
  .motion-ready :is(.gallery figure,figure.ed-figure,figure[data-zoom]).motion-reveal{opacity:1;transform:none}
  .motion-ready :is(.gallery figure,figure.ed-figure,figure[data-zoom]).motion-reveal>:not(#_){clip-path:inset(100% 0 0 0);transition:clip-path 1s cubic-bezier(.7,0,.2,1) var(--motion-delay,0s)}
  .motion-ready :is(.gallery figure,figure.ed-figure,figure[data-zoom]).motion-reveal>:nth-child(n+2):not(#_){transition-delay:calc(var(--motion-delay,0s) + .12s)}
  .motion-ready :is(.gallery figure,figure.ed-figure,figure[data-zoom]).motion-reveal.is-revealed>:not(#_){clip-path:inset(0)}

  /* --- Sticker: stamps on, then drifts like it's caught a breeze. rotate/translate/scale compose with its own rotate(12deg). --- */
  @keyframes sticker-pop{from{scale:0;rotate:-50deg}}
  @keyframes sticker-float{0%,100%{translate:0 0;rotate:0deg}50%{translate:0 -6px;rotate:-6deg}}
  .photo-sticker{animation:sticker-pop .7s cubic-bezier(.3,1.55,.5,1) .5s backwards,sticker-float 7s ease-in-out 1.2s infinite}

  /* --- Eyebrow dot: a slow radar ping. --- */
  @keyframes dot-ping{0%{transform:scale(1);opacity:.55}70%,100%{transform:scale(3.2);opacity:0}}
  .tiny-dot{position:relative}
  .tiny-dot::after{content:"";position:absolute;inset:0;border-radius:50%;background:inherit;animation:dot-ping 2.8s cubic-bezier(0,0,.2,1) 1s infinite;pointer-events:none}

  /* --- Color picker: a wash in the chosen swatch colour blooms behind the bike (the cut-out multiplies over it),
         and the new photo slides in from the side it came from (--swap-from set by app.js). --- */
  @keyframes color-wash{from{opacity:.9;transform:scale(.25)}50%{opacity:.6}to{opacity:0;transform:scale(1.15)}}
  @keyframes swap-slide{from{translate:var(--swap-from,16px) 0}}
  .color-photo>img:not(.motion-swap-layer){position:relative;z-index:1}
  .color-photo>.motion-swap-layer{z-index:2}
  .color-photo>.motion-wash{position:absolute;inset:0 6%;z-index:0;background:radial-gradient(closest-side,var(--wash,#ef4926),color-mix(in srgb,var(--wash,#ef4926) 45%,transparent) 55%,transparent);pointer-events:none;animation:color-wash .95s cubic-bezier(.2,.7,.2,1) forwards}
  .color-photo.is-swapping>.motion-swap-layer{animation:swap-slide .5s cubic-bezier(.2,.7,.2,1) backwards}
  .color-photo.is-swapping>img:not(.motion-swap-layer){translate:calc(var(--swap-from,16px) * -1) 0;transition:opacity .34s ease,translate .34s ease}

  /* --- Fold toggle: the bike squashes as it folds, stretches as it opens, standing on its wheels. --- */
  @keyframes fold-squash{0%,100%{scale:1}30%{scale:.9 1.04}62%{scale:1.025 .985}}
  @keyframes fold-stretch{0%,100%{scale:1}30%{scale:1.05 .96}62%{scale:.985 1.015}}
  .motion-swap-host>#fold-image,.motion-swap-host>#fold-image~.motion-swap-layer{transform-origin:50% 92%}
  .motion-swap-host.is-folding>img{animation:fold-squash .6s cubic-bezier(.3,.7,.3,1)}
  .motion-swap-host.is-unfolding>img{animation:fold-stretch .6s cubic-bezier(.3,.7,.3,1)}

  /* --- Footer: a tiny bike (and its bird) rides along the rule once the footer comes into view. --- */
  @keyframes ride-across{from{transform:translate3d(-100%,0,0)}to{transform:translate3d(calc(0% + 80px),0,0)}}
  @keyframes ride-bob{to{transform:translate3d(0,-1px,0)}}
  @keyframes ride-spin{to{transform:rotate(360deg)}}
  @keyframes ride-flap{50%{transform:scaleY(-.6)}}
  .motion-ride-host{position:relative}
  .motion-ride{display:block;position:absolute;left:0;right:0;bottom:calc(100% + 1px);height:34px;overflow:hidden;pointer-events:none;color:var(--ink,#20221f)}
  .motion-ride__run{position:absolute;inset:0;transform:translate3d(-100%,0,0)}
  .motion-ride svg{position:absolute;right:0;bottom:0;width:58px;height:34px;overflow:visible}
  .motion-ride.is-riding .motion-ride__run{animation:ride-across 5.2s cubic-bezier(.42,0,.58,1) forwards}
  .motion-ride.is-riding .motion-ride__bike{animation:ride-bob .18s ease-in-out infinite alternate}
  .motion-ride__wheel{transform-box:fill-box;transform-origin:center}
  .motion-ride.is-riding .motion-ride__wheel{animation:ride-spin .55s linear infinite}
  .motion-ride__bird{transform-box:fill-box;transform-origin:50% 100%;color:var(--accent,#ef4926)}
  .motion-ride.is-riding .motion-ride__bird{animation:ride-flap .32s ease-in-out infinite}
}

/* Scroll-linked motion where the browser supports scroll-driven animations; elsewhere these simply stay still. */
@supports (animation-timeline:view()){
  @media(prefers-reduced-motion:no-preference){
    /* Drift is paired with a matching scale-down ((1 - .96) / 2 = 2%), so a full-width wordmark never crosses the band's clip. */
    @keyframes wordmark-drift{from{transform:translate3d(2%,0,0) scale(.96)}50%{transform:none}to{transform:translate3d(-2%,0,0) scale(.96)}}
    @keyframes hero-parallax{to{transform:translate3d(0,5%,0) scale(1.12)}}
    /* Frames with overflow:hidden are scroll containers themselves, so an anonymous view() inside them would never move:
       each frame publishes a named view timeline that its image follows instead. */
    .wordmark-band>span{animation:wordmark-drift linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .ed-bigword{view-timeline:--sb-bigword block}
    .ed-bigword>.motion-drift{display:inline-block;animation:wordmark-drift linear both;animation-timeline:--sb-bigword;animation-range:cover 0% cover 100%}
    /* The hero image drifts and zooms as it leaves the viewport; the timed settle-in (listed second) wins while it plays. */
    :is(.hero-image,.pp-hero-lifestyle,.ed-hero__media .ed-frame,.ed-rhero__media .ed-frame){view-timeline:--sb-hero block}
    .hero-image img{animation:hero-parallax linear both,motion-settle .6s cubic-bezier(.2,.7,.2,1) .08s backwards;animation-timeline:--sb-hero,auto;animation-range:exit 0% exit 100%,normal}
    :is(.pp-hero-lifestyle,.ed-hero__media .ed-frame,.ed-rhero__media .ed-frame)>img{animation:hero-parallax linear both;animation-timeline:--sb-hero;animation-range:exit 0% exit 100%}
  }
}

/* Fine pointers only: logo hop / wing flap, and a light magnetic pull on buttons. */
@media(hover:hover) and (pointer:fine) and (prefers-reduced-motion:no-preference){
  @keyframes logo-hop{40%{transform:translate3d(0,-16%,0) rotate(-4deg)}70%{transform:translate3d(0,3%,0)}}
  @keyframes logo-flap{0%,100%{transform:none}18%{transform:rotate(-14deg) scaleY(1.1)}40%{transform:rotate(9deg) scaleY(.9)}62%{transform:rotate(-6deg) scaleY(1.05)}82%{transform:rotate(2deg)}}
  .brand:hover .motion-logo:not(.is-entering) path{animation:logo-hop .5s cubic-bezier(.3,.7,.3,1) calc(var(--i,0) * 22ms)}
  .brand:hover .motion-logo:not(.is-entering) .motion-logo-bird,.brand:hover .motion-logo:not(.is-entering) .motion-logo-bird path{transform-origin:92% 92%;animation:logo-flap .75s cubic-bezier(.3,.7,.3,1)}
  .brand:hover>span:not(:has(svg)){animation:logo-flap .75s cubic-bezier(.3,.7,.3,1);transform-origin:30% 80%}

  .is-magnetic{translate:var(--magnet-x,0px) var(--magnet-y,0px);transition:background-color .2s,color .2s,border-color .2s,translate .4s cubic-bezier(.2,.7,.2,1)}
}
