/* ==========================================================================
   Collsta website — Section 2: Save anything

   Same scroll contract as the hero: site.js writes ONE custom property,
   `--p` (0 → 1), onto `.sa`; every transform below derives from it in CSS.
   Ordinary document scroll, sticky frame, no hijacking.

   The story, in four beats:
     p 0.00 → 0.26   SCATTERED    six links sitting in six different places
     p 0.26 → 0.74   MOVEMENT     they start travelling toward one point
     p 0.14 → 0.68   SAVE         "Share → Collsta" surfaces mid-flight
     p 0.68 → 1.00   TOGETHER     they land in one calm, ordered group

   The card visuals (.badge, .play, .price, .map-pin, .sat__icon, .icon--*,
   .fill--*) are REUSED from hero.css rather than redefined — the two
   sections must read as one product. When a third section needs them they
   should be lifted into a shared components.css; until then hero.css owns
   them and this file must load after it.
   ========================================================================== */

.sa{
  /* How far along the collected path each phase of the loop travels. The
     scene only HINTS at convergence — assembling the board is the next
     section's story, so it never reaches 1. */
  --focus:.22;
  --converge:.45;
  --sa-cycle:10s;
  --calm:1;

  position:relative;

  /* Transparent on purpose — `.cine` paints the page colour and the shared
     glow sits behind this section. An opaque background here would hide it
     and re-create the seam. */
  background:transparent;

  /* The gap between the hero and this section. It began life as a negative
     margin that pulled this block up into the hero's tail — useful while the
     hero ended on a receding board with dead space under it, but the hero now
     ends on the brand moment and the pull-up left this section's eyebrow only
     ~28px under the hero's last row on a notebook-height screen. It is now a
     small POSITIVE gap instead.

     Still a margin rather than padding: padding here would push this
     section's own copy down inside it, which is the block's internal spacing,
     not the junction. And margins do not affect either section's scroll
     timing — both runways are measured from offsetHeight. */
  --hero-gap:clamp(8px,1.4vh,20px);
  margin-top:var(--hero-gap);
}

/* Was a viewport-sized sticky frame; now an ordinary block. The class name
   is kept so the rest of the file and the markup stay untouched. */
.sa__pin{
  position:relative;
  overflow:hidden;
  display:flex;
  flex-direction:column;
  padding-block:clamp(44px,6vw,84px) clamp(48px,6.5vw,92px);
  /* The copy box and the scene box were flush — their edges touched at 0px and
     the only separation was whatever empty space the topmost floating card
     happened to leave. `.sa__bg` is absolutely positioned so it takes no part
     in flex layout; this gap lands between the copy and the scene only.

     Shared with section 3 via the token — same value, same breakpoints. */
  gap:var(--section-copy-gap);
  isolation:isolate;
}
@supports (overflow:clip){
  .sa__pin{overflow:clip}
}

/* ---- Background ---- */
.sa__bg{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.sa__blob{filter:blur(30px)}
.sa__blob--a{width:min(44vw,560px);aspect-ratio:1;top:-18%;right:-12%;opacity:.4}
/* Kept clear of the pin's bottom edge: this pin clips, so a blob hanging past
   it was being sliced across its brightest band exactly at the junction with
   Board customize. Sideways bleed is fine — that edge is the viewport's. */
.sa__blob--b{width:min(38vw,480px);aspect-ratio:1;bottom:4%;left:-10%;opacity:.42}

/* The focus glow that arrives with the settled group. */
.sa__glow{
  position:absolute;
  left:50%;top:50%;
  width:min(74vw,900px);aspect-ratio:1.6;
  transform:translate3d(-50%,-50%,0);
  border-radius:50%;
  background:radial-gradient(ellipse at center,rgba(184,171,242,.30) 0%,rgba(184,171,242,0) 68%);
  opacity:0;
  pointer-events:none;
  z-index:1;
  animation:sa-glow var(--sa-cycle) var(--ease-inout) infinite;
  animation-play-state:paused;
}
.sa.is-live .sa__glow{animation-play-state:running}
@keyframes sa-glow{
  0%,40%{opacity:0}
  62%,78%{opacity:1}
  100%{opacity:0}
}

/* ==========================================================================
   Copy
   ========================================================================== */
.sa__copy{
  position:relative;
  z-index:3;
  text-align:center;
}
.sa__eyebrow{margin-bottom:clamp(14px,1.8vw,20px)}

.sa__title{
  font-family:var(--font-display);
  font-weight:var(--fw-extrabold);
  font-size:var(--fs-h2);
  line-height:var(--lh-tight);
  letter-spacing:var(--ls-tight);
  color:var(--navy);
  text-wrap:balance;
}
.sa__title-line{display:block}
/* Same payoff-line treatment as the hero's "One board." — one brand device,
   used consistently. */
.sa__title-accent{
  background:var(--grad-text);
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
}

.sa__sub{
  margin:clamp(14px,1.8vw,20px) auto 0;
  max-width:46rem;
  font-size:var(--fs-body-lg);
  line-height:var(--lh-body);
  color:var(--text-body);
  text-wrap:pretty;
}
.sa__sub b{font-weight:var(--fw-semibold);color:var(--navy)}

/* ==========================================================================
   Scene
   ========================================================================== */
.sa__scene{
  position:relative;
  z-index:2;
  height:clamp(460px,58vh,600px);
}

/* The card group is sized in px while the scene grows with the viewport, so
   on tall screens it floated in the middle with a large empty band under the
   copy. Shrinking the centring box from the bottom by 2 × lift raises the
   group by exactly `lift` and lets the surplus fall below it instead.

   The cap is the point: `min()` takes whichever is smaller, so the lift can
   never eat more headroom than the scene actually has — on short viewports
   the percentage wins and the group barely moves rather than colliding with
   the copy. */
.sa{--sa-lift:min(4vh,6%)}
.sa__figure{
  position:absolute;
  inset:0 0 calc(var(--sa-lift) * 2) 0;
  margin:0;
}

/* ---- The save target the links travel toward ---- */
.sa-target{
  position:absolute;
  left:50%;top:50%;
  z-index:1;
  display:flex;flex-direction:column;align-items:center;gap:14px;
  transform:translate3d(-50%,-50%,0) scale(.82);
  opacity:0;
  pointer-events:none;
  animation:sa-target var(--sa-cycle) var(--ease-out) infinite;
  animation-play-state:paused;
}
.sa.is-live .sa-target{animation-play-state:running}
@keyframes sa-target{
  0%,18%{opacity:0;transform:translate3d(-50%,-50%,0) scale(.82)}
  32%,66%{opacity:1;transform:translate3d(-50%,-50%,0) scale(1)}
  82%,100%{opacity:0;transform:translate3d(-50%,-50%,0) scale(.9)}
}
.sa-target__mark{
  width:clamp(64px,7vw,86px);aspect-ratio:1;
  border-radius:24px;
  /* Brand purple rather than the page surface, so the save target reads as one
     Collsta object with the chip under it instead of a white app icon parked
     on the page.

     Deep end of the purple ramp on purpose. The mark's own layers include
     #735CFF and #FF7A85, so a light lavender field put those at 1.4:1 against
     it and the logo lost its shape. Mixed down toward navy every layer clears
     ~1.9:1: white 9.3, coral 3.7, the purple layer 2.1, navy 2.0. */
  background:linear-gradient(152deg,
    color-mix(in srgb,var(--purple) 64%,var(--navy)) 0%,
    color-mix(in srgb,var(--purple) 52%,var(--navy)) 100%);
  border:1px solid color-mix(in srgb,var(--lavender) 34%,transparent);
  box-shadow:var(--glow-purple),var(--shadow-lg);
  display:grid;place-items:center;
}
.sa-target__mark svg{
  width:64%;height:64%;
  /* The artwork is not centred inside its own viewBox: the card stack spans
     8 → 164 of a 0 → 180 box, so its centre sits at 86,86 rather than 90,90,
     and the -6deg rotation carries it a little further. Rasterised at 720px
     the ink's bounding-box centre and its alpha-weighted centroid agree to
     within 0.1 unit — 4.5 left, 3.75 up — so a straight nudge corrects it.

     Expressed as a percentage of the mark, not px: the mark is a clamp, and a
     fixed offset would only be right at one size. 4.5/180 = 2.5%, 3.75/180 =
     2.08%. Scoped here on purpose — the nav and hero use the same artwork,
     where it sits beside the wordmark rather than inside a tight square. */
  transform:translate(2.5%,2.08%);
}
.sa-target__label{
  display:inline-flex;align-items:center;gap:8px;
  padding:7px 15px;
  border-radius:var(--radius-pill);
  background:var(--lavender-pale);
  /* Navy, not purple: --purple on --lavender-pale measures 4.1:1 and misses
     AA at 11px. Navy on a soft lavender surface is the system's core pairing
     anyway, so nothing new is introduced. */
  color:var(--navy);
  font-family:var(--font-display);
  font-weight:var(--fw-bold);
  font-size:11px;
  letter-spacing:.16em;
  text-transform:uppercase;
  white-space:nowrap;
}

/* ==========================================================================
   Source cards
   Each card knows where it starts scattered (--x0/--y0/--r0/--s0/--o0) and
   where it lands collected (--x1/--y1/--r1/--s1). Everything between is
   interpolated in CSS from the eased convergence progress.
   ========================================================================== */
.sa-card{
  position:absolute;
  left:50%;top:50%;
  z-index:2;
  width:var(--w,190px);

  /* The two waypoints the loop visits, derived from the SAME approved
     geometry the scroll version used — `--focus` and `--converge` are
     fractions of the way toward the collected position, never all of it. */
  --xf:calc(var(--x0) + (var(--x1) - var(--x0)) * var(--focus));
  --yf:calc(var(--y0) + (var(--y1) - var(--y0)) * var(--focus));
  --rf:calc(var(--r0) + (var(--r1) - var(--r0)) * var(--focus));
  --sf:calc(var(--s0,1) + (var(--s1,.9) - var(--s0,1)) * var(--focus));
  --xc:calc(var(--x0) + (var(--x1) - var(--x0)) * var(--converge));
  --yc:calc(var(--y0) + (var(--y1) - var(--y0)) * var(--converge));
  --rc:calc(var(--r0) + (var(--r1) - var(--r0)) * var(--converge));
  --sc:calc(var(--s0,1) + (var(--s1,.9) - var(--s0,1)) * var(--converge));

  /* Resting pose — this is also exactly what reduced motion and a paused
     loop show, so the scene is never mid-animation when it is not running. */
  transform:
    translate3d(calc(-50% + var(--x0)),calc(-50% + var(--y0)),0)
    rotate(var(--r0))
    scale(var(--s0,1));
  opacity:var(--o0,1);

  animation:sa-life var(--sa-cycle) var(--ease-inout) infinite;
  animation-play-state:paused;
  will-change:transform,opacity;
}
.sa.is-live .sa-card{animation-play-state:running}

/* scattered → focus → convergence hint → back. Transform and opacity only. */
@keyframes sa-life{
  0%,15%{
    transform:translate3d(calc(-50% + var(--x0)),calc(-50% + var(--y0)),0) rotate(var(--r0)) scale(var(--s0,1));
    opacity:var(--o0,1);
  }
  30%,44%{
    transform:translate3d(calc(-50% + var(--xf)),calc(-50% + var(--yf)),0) rotate(var(--rf)) scale(var(--sf));
    opacity:calc(var(--o0,1) + (1 - var(--o0,1)) * .55);
  }
  60%,78%{
    transform:translate3d(calc(-50% + var(--xc)),calc(-50% + var(--yc)),0) rotate(var(--rc)) scale(var(--sc));
    opacity:1;
  }
  100%{
    transform:translate3d(calc(-50% + var(--x0)),calc(-50% + var(--y0)),0) rotate(var(--r0)) scale(var(--s0,1));
    opacity:var(--o0,1);
  }
}

/* Depth while scattered; the back cards pull into focus as they travel. */
.sa-card--far{filter:blur(2.6px)}
.sa-card__float{
  animation:sa-drift var(--drift,13s) var(--ease-inout) infinite;
  animation-delay:var(--drift-delay,0s);
  animation-play-state:paused;
}
.sa.is-live .sa-card__float{animation-play-state:running}
/* Two animations on one element: drift moves it, defocus only touches the
   filter, so they never fight over a property. */
.sa-card--far .sa-card__float{
  animation:
    sa-drift var(--drift,13s) var(--ease-inout) infinite,
    sa-defocus var(--sa-cycle) var(--ease-inout) infinite;
  animation-delay:var(--drift-delay,0s),0s;
  /* Re-stated because the shorthand above resets play-state to `running`,
     which outranks the base rule's `paused` — these two cards were drifting
     off screen and in background tabs. The `.sa.is-live` gate is more
     specific than this rule, so it still starts them. */
  animation-play-state:paused;
}
.sa-card--far{filter:none}
.sa-card--far .sa-card__float{filter:blur(2.6px)}
@keyframes sa-drift{
  0%,100%{transform:translate3d(0,0,0)}
  50%{transform:translate3d(0,calc(-9px * var(--calm)),0)}
}
@keyframes sa-defocus{
  0%,15%{filter:blur(2.6px)}
  34%,78%{filter:blur(0)}
  100%{filter:blur(2.6px)}
}

.sa-card__inner{
  border-radius:var(--radius-md);
  background:var(--surface);
  border:1px solid var(--border);
  box-shadow:var(--shadow-md);
  overflow:hidden;
}

/* Step 3 — one source at a time gets a beat of emphasis. Negative delays
   phase-shift a shared cycle so the four pulses land in sequence:
   TikTok → Booking → Maps → Shop. Cards 5 and 6 stay quiet. */
.sa.is-live .sa-card--1 .sa-card__inner,
.sa.is-live .sa-card--2 .sa-card__inner,
.sa.is-live .sa-card--3 .sa-card__inner,
.sa.is-live .sa-card--4 .sa-card__inner{
  animation:sa-spot var(--sa-cycle) var(--ease-inout) infinite;
  animation-delay:var(--spot,0s);
}
.sa-card--1{--spot:-2.1s}
.sa-card--2{--spot:-1.4s}
.sa-card--3{--spot:-.7s}
.sa-card--4{--spot:0s}
@keyframes sa-spot{
  0%,38%{transform:scale(1);box-shadow:var(--shadow-md)}
  44%{transform:scale(1.035);box-shadow:var(--shadow-lg)}
  52%,100%{transform:scale(1);box-shadow:var(--shadow-md)}
}
.sa-card--far .sa-card__inner{box-shadow:var(--shadow-sm)}

.sa-card__media{position:relative;height:var(--mh,96px);overflow:hidden}
.sa-card__body{padding:11px 13px}
.sa-card__title{
  font-family:var(--font-display);
  font-weight:var(--fw-bold);
  font-size:13.5px;
  line-height:1.25;
  color:var(--navy);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.sa-card__meta{
  margin-top:4px;
  font-size:11.5px;
  line-height:1.3;
  color:var(--text-meta);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* Row form: icon tile beside the text, for the link types that have no
   thumbnail worth showing. */
.sa-card__inner--row{display:flex;align-items:center;gap:11px;padding:11px 13px}
.sa-card__inner--row .sa-card__body{padding:0;min-width:0;flex:1}

/* Map form: the thumbnail sits beside the text. */
.sa-card__inner--map{display:flex;align-items:stretch}
.sa-card__inner--map .sa-card__media{flex:none;width:96px;height:auto;min-height:76px}
.sa-card__inner--map .sa-card__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}

.sa-card__fill--shop{background:linear-gradient(155deg,#FFE7E9,#FF9AA3 60%,#E16D7B)}
.sa-card__domain{
  position:absolute;bottom:8px;left:8px;
  padding:4px 9px;border-radius:var(--radius-pill);
  background:rgba(255,255,255,.94);
  font-family:var(--font-display);font-weight:var(--fw-bold);
  font-size:10px;color:var(--navy);
}

/* ---- Placement -----------------------------------------------------------
   Scattered spread is wide enough to read as "six different corners of the
   internet"; collected is a tidy 3 × 2 group, close but never overlapping.
   -------------------------------------------------------------------------- */
.sa-card--1{                          /* TikTok — video */
  --w:190px;--mh:100px;
  --x0:-395px;--y0:-140px;--r0:-8deg;--s0:1.02;
  --x1:-198px;--y1:-74px;--r1:-2deg;--s1:.9;
  --drift:13s;--drift-delay:-2s;
}
.sa-card--2{                          /* Booking — stay */
  --w:190px;--mh:100px;
  --x0:330px;--y0:-175px;--r0:7deg;--s0:.98;
  --x1:2px;--y1:-84px;--r1:1.5deg;--s1:.9;
  --drift:11s;--drift-delay:-5s;
}
.sa-card--3{                          /* Google Maps — place */
  --w:225px;
  --x0:-430px;--y0:130px;--r0:4deg;--s0:.94;--o0:.62;
  --x1:-200px;--y1:86px;--r1:-1.5deg;--s1:.9;
  --drift:14s;--drift-delay:-8s;
}
.sa-card--4{                          /* Shop — product */
  --w:165px;--mh:92px;
  --x0:170px;--y0:160px;--r0:-6deg;--s0:1;
  --x1:200px;--y1:-70px;--r1:2deg;--s1:.9;
  --drift:12s;--drift-delay:-3s;
}
.sa-card--5{                          /* Article */
  --w:215px;
  --x0:415px;--y0:120px;--r0:-5deg;--s0:.92;--o0:.6;
  --x1:14px;--y1:92px;--r1:-1deg;--s1:.9;
  --drift:15s;--drift-delay:-6s;
}
.sa-card--6{                          /* Flight */
  --w:185px;
  --x0:-140px;--y0:-215px;--r0:6deg;--s0:.96;
  --x1:210px;--y1:88px;--r1:1.5deg;--s1:.9;
  --drift:10s;--drift-delay:-4s;
}

/* The pinned hand-off veil is gone with the pin — `.cine__floor` already
   carries the colour change into the Board section. */
.sa__veil{display:none}

/* ==========================================================================
   Entrance — plays once, the first time the section is reached
   ========================================================================== */
.js .sa__copy,
.js .sa__scene{
  opacity:0;
  transform:translateY(16px);
}
.sa.is-in .sa__copy,
.sa.is-in .sa__scene{
  opacity:1;
  transform:none;
  transition:opacity .8s var(--ease-out),transform .8s var(--ease-out);
}
.sa.is-in .sa__scene{transition-delay:.1s}

/* ==========================================================================
   Breakpoints
   ========================================================================== */

@media (max-width:1180px){
  .sa-card--1{--x0:-330px;--x1:-176px}
  .sa-card--2{--x0:290px;--x1:0px}
  .sa-card--3{--x0:-360px;--x1:-178px;--w:205px}
  .sa-card--4{--x0:150px;--x1:178px}
  .sa-card--5{--x0:350px;--x1:10px;--w:195px}
  .sa-card--6{--x1:186px;--w:170px}
}

/* ---- Mobile: the same four beats, told with four links --------------------
   Six cards at 375px is a pile, not a story. Two of the six drop out and the
   rest scatter vertically — which is how the beats actually read on a phone:
   each link arrives from its own corner, then all four collect into a square.
   -------------------------------------------------------------------------- */
@media (max-width:820px){
  .sa{
    /* The phone scene is only ~470px tall and the scatter nearly fills it, so
       there is far less surplus to reclaim than on a desktop frame. */
    --sa-lift:min(2.5vh,4%);
    /* A phone has far less room to spend on a junction, and the hero's mobile
       composition already gives the board its own screen — so this keeps a
       modest pull-up rather than taking the desktop gap. */
    --hero-gap:calc(-1 * clamp(20px,3.4vh,34px));
    /* Shorter travel on a small frame: the cards nudge toward the centre
       rather than crossing it. */
    --focus:.16;
    --converge:.32;
  }
  .sa__pin{padding-block:clamp(34px,7vw,56px) clamp(36px,7vw,60px)}
  .sa__scene{height:clamp(432px,55vh,524px)}
  /* Only the two media cards drift on a phone — four moving at once is busy
     at this size. */
  /* Delay reset with the name: inherited from the drift it would leave the
     back card sitting at the sharp end of the defocus cycle at rest. */
  .sa-card--3 .sa-card__float{animation-name:sa-defocus;animation-delay:0s}
  .sa-card--4 .sa-card__float{animation-name:none}

  .sa__title{font-size:clamp(28px,7.4vw,40px)}
  .sa__sub{font-size:16px;max-width:34rem}

  .sa-card--5,.sa-card--6{display:none}

  /* Collected: two media cards on top, the two shorter cards beneath with
     their tops aligned to the media row's baseline — the scatter has to stay
     inside a 530px scene, so the spread is vertical but tight. */
  .sa-card--1{
    --w:clamp(140px,42vw,168px);--mh:84px;
    /* Topmost card of the four — pulled in by the lift amount so the group
       can rise without the scatter reaching into the copy. */
    --x0:-80px;--y0:-112px;--r0:-7deg;--s0:1;
    --x1:-86px;--y1:-72px;--r1:-2deg;--s1:.94;
  }
  .sa-card--2{
    --w:clamp(140px,42vw,168px);--mh:84px;
    --x0:92px;--y0:-80px;--r0:6deg;--s0:.96;
    --x1:86px;--y1:-72px;--r1:2deg;--s1:.94;
  }
  .sa-card--3{
    --w:clamp(150px,46vw,186px);
    --x0:-88px;--y0:118px;--r0:5deg;--s0:.94;--o0:.68;
    --x1:-84px;--y1:41px;--r1:-1.5deg;--s1:.94;
  }
  .sa-card--4{
    --w:clamp(126px,36vw,150px);--mh:76px;
    --x0:98px;--y0:150px;--r0:-6deg;--s0:1;
    --x1:88px;--y1:70px;--r1:2deg;--s1:.94;
  }
  .sa-card__inner--map .sa-card__media{width:76px;min-height:66px}
  .sa-target__mark{border-radius:20px}
}

/* Below ~840px tall the copy block eats enough of the frame that the full
   scatter no longer fits: cards clip against the copy above and run past the
   bottom of the pin. Pull the vertical spread in rather than clip it. The
   story still reads — the cards are still in six separate places. */
@media (max-height:960px) and (min-width:821px){
  .sa-card--1{--y0:-100px}
  /* Tallest card of the six, so it — not the highest --y0 — is what sets the
     clearance under the copy once the group is lifted. */
  .sa-card--2{--y0:-112px}
  .sa-card--3{--y0:95px}
  .sa-card--4{--y0:135px;--mh:82px}
  .sa-card--5{--y0:88px}
  .sa-card--6{--y0:-150px}
  .sa-card--1,.sa-card--2{--mh:86px}
}

/* Under ~780px tall the scene is only ~400px and the scatter fills it even
   with no lift at all — the lowest card was being clipped by the pin. This
   tier pulls the spread in far enough to sit inside the frame and gives up
   the lift entirely: there is no surplus here to reclaim.
   Must stay after the 960px tier — same specificity, so source order decides. */
@media (max-height:780px) and (min-width:821px){
  .sa{--sa-lift:0px}
  .sa-card--1{--y0:-80px}
  .sa-card--2{--y0:-88px}
  .sa-card--3{--y0:78px}
  .sa-card--4{--y0:112px}
  .sa-card--5{--y0:72px}
  .sa-card--6{--y0:-126px}
}

/* ==========================================================================
   Reduced motion
   The runway collapses so there is no dead scroll, the pin releases, and the
   scene renders in its COLLECTED state — the frame that actually states the
   point: six different kinds of link, one destination. The save label stays
   visible so "Share → Collsta" is still said out loud.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  /* Every loop here is `animation-play-state: paused` by default and site.js
     never adds `is-live`, so the scene already renders in its resting
     scattered pose — six links in six places. Only the pieces the loop would
     have revealed need pinning open. */
  .sa{--hero-gap:0px}
  .js .sa__copy,.js .sa__scene{opacity:1;transform:none}
  .sa-card,.sa-card__float,.sa-card__inner{animation:none}
  .sa-card--far .sa-card__float{filter:blur(2.6px)}
  /* Collsta sits in the middle of the scattered sources, which is the whole
     sentence: different sources → one destination. */
  .sa-target{
    opacity:1;
    transform:translate3d(-50%,-50%,0) scale(1);
    animation:none;
  }
  .sa__glow{opacity:.45;animation:none}
}

