/* co:carte motion
   ===============
   The rules, after Apple's "Designing Fluid Interfaces", and the reason for
   each:

   - Respond on the press, not the release. A control gives the instant a
     finger lands on it; waiting for the click to show anything feels dead.
   - Springs, not easing curves, and critically damped by default: things
     arrive with weight and stop without wobbling. Overshoot is only for
     motion the visitor threw (the feature carousel, in home.render.js).
   - Anything a visitor can touch is driven by a live spring in
     public/scripts/motion.js (window.coMotion), which starts from where the
     thing is on screen and keeps its velocity, so it can be grabbed and
     reversed mid-flight. CSS transitions are only for things nobody can
     interrupt: entrances and hovers.
   - Things leave the way they came. The phone menu grows out of its button
     and shrinks back into it; the FAQ answer folds up into its question.
   - Nothing simply fades in. Type rises out of a cut, a kicker is scored
     left to right, cards are set down, ticks are stamped. Body copy does not
     move: a paragraph that waits to appear is a paragraph that is late.
   - No idle loops and no glow. The menu in the story moves only when the
     visitor scrolls, and backwards when they scroll back.
   - Reduced motion is gentler, not absent: no entrances, no slides, but the
     menu and the FAQ still cross-fade so a change is never a jump cut. Every
     hidden starting state below is scoped to html[data-motion="on"], which
     lib/motion-arm.js only sets for visitors who have not asked for less. */

:root {
  /* Fallbacks for engines without linear(): close, never overshooting. */
  --spring-ui: cubic-bezier(.2, .8, .25, 1);   --spring-ui-dur: 520ms;
  --spring-set: cubic-bezier(.2, .8, .25, 1);  --spring-set-dur: 660ms;
  --spring-type: cubic-bezier(.2, .8, .25, 1); --spring-type-dur: 810ms;
}
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    /* Generated by `node scripts/springs.mjs`; regenerate rather than edit. */
    /* ui: damping 1, response 0.35s, settles in 517ms */
    --spring-ui: linear(0, 0.0231, 0.0794, 0.1543, 0.2376, 0.3227, 0.4052, 0.4825, 0.5533, 0.617, 0.6735, 0.7229, 0.7659, 0.803, 0.8347, 0.8618, 0.8847, 0.9041, 0.9204, 0.934, 0.9454, 0.9549, 0.9629, 0.9694, 0.9749, 0.9794, 0.9831, 0.9861, 0.9887, 0.9907, 0.9924, 0.9938, 0.995, 0.9959, 0.9967, 0.9973, 0.9978, 0.9982, 0.9985, 0.9988, 1);
    --spring-ui-dur: 517ms;
    /* set: damping 1, response 0.45s, settles in 662ms */
    --spring-set: linear(0, 0.023, 0.0791, 0.1536, 0.2367, 0.3215, 0.4039, 0.4811, 0.5519, 0.6156, 0.672, 0.7216, 0.7646, 0.8018, 0.8336, 0.8608, 0.8838, 0.9033, 0.9196, 0.9334, 0.9449, 0.9544, 0.9624, 0.969, 0.9745, 0.9791, 0.9828, 0.9859, 0.9885, 0.9906, 0.9923, 0.9937, 0.9949, 0.9958, 0.9966, 0.9972, 0.9977, 0.9982, 0.9985, 0.9988, 1);
    --spring-set-dur: 662ms;
    /* type: damping 1, response 0.55s, settles in 808ms */
    --spring-type: linear(0, 0.0229, 0.0788, 0.1532, 0.2361, 0.3208, 0.403, 0.4802, 0.551, 0.6146, 0.6711, 0.7207, 0.7638, 0.801, 0.8329, 0.8601, 0.8832, 0.9027, 0.9192, 0.933, 0.9445, 0.9541, 0.9621, 0.9688, 0.9743, 0.9789, 0.9827, 0.9858, 0.9884, 0.9905, 0.9922, 0.9936, 0.9948, 0.9958, 0.9965, 0.9972, 0.9977, 0.9981, 0.9985, 0.9988, 1);
    --spring-type-dur: 808ms;
  }
}

/* ---- Press: instant in, spring out ----
   The press is 100ms and plain ease-out because it must be immediate; the
   release is the UI spring, from wherever the press had got to. Double-tap
   zoom is off on controls, which takes away the 300ms some browsers still
   hold a tap for. motion.js adds an empty touchstart listener, without
   which iOS Safari never applies :active at all. */
a, button, summary, [role="button"] { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
.tap, .carousel-arrow, .demo-card, [data-hero-cta] a, #cta a, .client-access {
  transition: transform var(--spring-ui-dur) var(--spring-ui), background-color .15s linear, color .15s linear, box-shadow .2s linear;
}
.tap:active, .carousel-arrow:active, [data-hero-cta] a:active, #cta a:active {
  transform: scale(.96); transition: transform 100ms ease-out;
}
.demo-card:active { transform: scale(.985); transition: transform 100ms ease-out; }

@media (hover: hover) and (pointer: fine) {
  [data-hero-cta] a svg, #cta a svg, .demo-card .text-cocarte-flameDark {
    transition: transform var(--spring-ui-dur) var(--spring-ui);
  }
  [data-hero-cta] a:hover svg, #cta a:hover svg { transform: translateX(3px); }
  .demo-card:hover .text-cocarte-flameDark { transform: translateX(3px); }
}

/* ---- Type rising out of a cut ----
   motion.js wraps every word of a heading in a mask (.mo-w) holding the word
   itself (.mo-wi), and numbers the lines in --l. The words of one line rise
   together and each line follows the one above it by 80ms. Until the split
   has happened a heading is held back, so it never flashes up in full and is
   then pulled down to animate. */
.mo-w {
  display: inline-block; overflow: hidden; vertical-align: top;
  padding: .06em .04em .16em; margin: -.06em -.04em -.16em;
}
.mo-wi { display: inline-block; }
html[data-motion="on"] :is(h1, h2):not(.mo-split):not(.ap-cap-title) { visibility: hidden; }
/* Far enough that no ascender peeks into the mask's bottom padding. */
html[data-motion="on"] .mo-type .mo-wi { transform: translateY(calc(100% + .35em)); }
html[data-motion="on"] .mo-type.is-in .mo-wi {
  transform: none;
  transition: transform var(--spring-type-dur) var(--spring-type);
  transition-delay: calc(var(--d0, 0ms) + var(--l, 0) * 80ms);
}

/* ---- Kicker: scored left to right, like the laser ---- */
html[data-motion="on"] .mo-kicker { clip-path: inset(0 100% 0 0); }
html[data-motion="on"] .mo-kicker.is-in {
  clip-path: inset(0 0 0 0);
  transition: clip-path var(--spring-type-dur) var(--spring-type);
}

/* ---- Set down ----
   Cards arrive from a little below, a fraction off square, and settle
   without overshoot. --i staggers what came into view together. */
html[data-motion="on"] .mo-set {
  opacity: 0;
  transform: translateY(24px) rotate(var(--mo-r, .6deg));
}
html[data-motion="on"] .mo-set.is-in {
  opacity: 1; transform: none;
  transition:
    transform var(--spring-set-dur) var(--spring-set),
    opacity 200ms linear;
  transition-delay: calc(var(--d0, 0ms) + var(--i, 0) * 70ms);
}

/* ---- The ledger: crosses first, then the ticks stamped one by one ---- */
html[data-motion="on"] .rev-ledger:not(.is-in) :is(.rev-no, .rev-ok, .rev-some) { opacity: 0; }
html[data-motion="on"] .rev-ledger:not(.is-in) .rev-yes { opacity: 0; transform: scale(.4); }
html[data-motion="on"] .rev-ledger.is-in :is(.rev-no, .rev-ok, .rev-some) {
  transition: opacity 200ms linear;
  transition-delay: calc(250ms + var(--i, 0) * 40ms);
}
html[data-motion="on"] .rev-ledger.is-in .rev-yes {
  transition: transform var(--spring-ui-dur) var(--spring-ui), opacity 120ms linear;
  transition-delay: calc(600ms + var(--i, 0) * 100ms);
}
/* The flame rule under the three verbs is burnt in, left to right. */
html[data-motion="on"] .rev-ledger:not(.is-in) .rev-verb { background-size: 0% 100%; }
html[data-motion="on"] .rev-ledger.is-in .rev-verb {
  transition: background-size var(--spring-type-dur) var(--spring-type);
  transition-delay: calc(420ms + var(--i, 0) * 260ms);
}

/* ---- Stand swatches: the drawn stand is set onto its tilt ---- */
[data-stand] { transform: rotate(var(--r, 0deg)); }
html[data-motion="on"] .mo-set:not(.is-in) [data-stand] {
  transform: translateY(-14px) rotate(calc(var(--r, 0deg) + 6deg));
}
html[data-motion="on"] .mo-set.is-in [data-stand] {
  transition: transform var(--spring-set-dur) var(--spring-set);
  transition-delay: calc(var(--i, 0) * 70ms + 220ms);
}

/* ---- "Probeer": one knock when the card first comes fully into view ----
   It used to wiggle every five seconds forever. A slow loop like that is
   the kind of motion that makes some people unwell, and a cue repeated
   until ignored stops being a cue. */
.try-badge.is-knock { animation: tryKnock 900ms linear; }
@keyframes tryKnock {
  0%, 100% { transform: none; }
  12% { transform: rotate(-7deg) scale(1.06); }
  30% { transform: rotate(5deg) scale(1.04); }
  48% { transform: rotate(-2.5deg); }
  66% { transform: rotate(1deg); }
  82% { transform: rotate(-.3deg); }
}

/* ---- FAQ: motion.js drives the height and the chevron from one spring ---- */
details.faq > div { overflow: hidden; }
details.faq .chev { transition: none !important; }

/* ---- Phone menu: a panel of material, grown out of its button ----
   motion.js runs one spring (0 closed, 1 open) and paints the panel, the
   scrim and the links from it; it scales from the button's corner, and
   closes back into it along the same path. */
#mobileMenu {
  position: absolute; top: calc(100% + 8px); right: calc(12px + env(safe-area-inset-right)); left: auto;
  width: min(20rem, calc(100vw - 24px));
  transform-origin: calc(100% - 22px) -8px;
  will-change: transform, opacity;
}
.mo-scrim {
  position: fixed; inset: 0; z-index: 40; background: rgba(15, 6, 2, .32);
  opacity: 0; pointer-events: none;
}
.mo-scrim.is-on { pointer-events: auto; }

/* ---- The story phone ----
   Its menu is styled in public/styles/story-menu.css; motion.js plays it. */

/* Hero entrance, before motion.js has painted its first frame. */
html[data-motion="on"] [data-hero-phone]:not(.is-live) { opacity: 0; }
html[data-motion="on"] [data-hero-plaque]:not(.is-live) { opacity: 0; }
html[data-motion="on"] [data-hero-cta] { opacity: 0; transform: translateY(14px); }
html[data-motion="on"] [data-hero-cta].is-in {
  opacity: 1; transform: none;
  transition: transform var(--spring-set-dur) var(--spring-set), opacity 200ms linear;
  transition-delay: 360ms;
}
[data-hero-plaque] { transform: rotate(-2deg); }
