@charset "UTF-8";
/* ==========================================================================
   CoCo Fitness — motion.css
   A motion layer for a calm brand. Loads AFTER site.css and changes nothing
   in it. Zero dependencies, zero external requests.

   The rule this file obeys, above every other:
     Motion here is atmosphere, not event. Nothing loops in the reading area,
     nothing pins the scroll, nothing asks to be noticed. If you can describe
     a movement to someone after they have read the page, it is too much.

   How it is built
     · Everything below the fold rides a native CSS scroll timeline
       (animation-timeline: view()) — no scroll listeners, no JS, composited
       off the main thread. Browsers without it keep the existing
       IntersectionObserver behaviour already shipped in site.js.
     · Everything above the fold is time-based and fires on load, because a
       view() timeline is already at 100% for anything visible at first paint.
     · Every animated property is `opacity` or `transform`. Nothing animates
       a property that can move a box, so this file cannot cause layout shift.

   Contents
     01  Primitives (keyframes)
     02  Above the fold — the load sequence
     03  Below the fold — scroll-driven reveal
     04  Stagger
     05  The gold rule, drawing itself
     06  The trust hairline, drawing itself
     07  Ken Burns — the hero photograph
     08  Parallax — one landscape photograph per page
     09  Photographs fading in as they decode
     10  Hover lift — interactive surfaces only
     11  FAQ disclosure
     12  No-JavaScript safety net
     13  Reduced motion — the off switch
     14  Print
   ========================================================================== */


/* 01  PRIMITIVES =========================================================== */

@keyframes m-rise {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes m-rise-sm {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* Left-to-right draw. Used for the gold rule and the one structural hairline
   that already exists. It is scaleX only, so the box never changes size. */
@keyframes m-draw {
  from { opacity: 1; transform: scaleX(0); }
  to   { opacity: 1; transform: scaleX(1); }
}

/* The hero photograph. 32s each way — this is roughly 0.17% of scale per
   second. It is meant to be felt, not seen. */
@keyframes m-kenburns {
  from { transform: scale(1)     translate3d(0, 0, 0); }
  to   { transform: scale(1.055) translate3d(-1%, -0.7%, 0); }
}

/* Depth on one landscape image. Travel is deliberately small — the overhang
   in section 08 is 9%, so ±3.5% can never expose an edge. */
@keyframes m-parallax {
  from { transform: translate3d(0, -3.5%, 0); }
  to   { transform: translate3d(0,  3.5%, 0); }
}


/* 02  ABOVE THE FOLD — THE LOAD SEQUENCE =================================== */
/* .m-seq staggers its own direct children on load. It REPLACES .reveal on the
   hero column: .reveal cannot animate above the fold under a view() timeline,
   and stacking the two would fade the same pixels twice.

   Time-based, so this works with JavaScript disabled and in every browser. */

/* 0.5s, not 0.62s. The reveal tier this file is aiming at is 400–600ms; anything
   past ~500ms starts reading as the page being slow rather than the page arriving. */
.m-seq > * {
  animation: m-rise-sm 0.5s var(--ease) both;
}
.m-seq > *:nth-child(1) { animation-delay: 0.04s; }
.m-seq > *:nth-child(2) { animation-delay: 0.10s; }
.m-seq > *:nth-child(3) { animation-delay: 0.16s; }
.m-seq > *:nth-child(4) { animation-delay: 0.22s; }
.m-seq > *:nth-child(5) { animation-delay: 0.28s; }
.m-seq > *:nth-child(6) { animation-delay: 0.34s; }
.m-seq > *:nth-child(7) { animation-delay: 0.40s; }
.m-seq > *:nth-child(n + 8) { animation-delay: 0.46s; }

/* Single above-fold element (the hero photograph). Same curve, chosen delay. */
.m-in {
  animation: m-rise 0.55s var(--ease) both;
  animation-delay: var(--m-delay, 0s);
}
.m-delay-1 { --m-delay: 0.10s; }
.m-delay-2 { --m-delay: 0.16s; }
.m-delay-3 { --m-delay: 0.22s; }
.m-delay-4 { --m-delay: 0.28s; }
.m-delay-5 { --m-delay: 0.34s; }
.m-delay-6 { --m-delay: 0.40s; }


/* 03  BELOW THE FOLD — SCROLL-DRIVEN REVEAL ================================ */
/* site.css already defines .reveal (opacity 0 + 14px) and site.js already
   adds .is-in through an IntersectionObserver. Where the platform supports
   scroll timelines we take the same class over and drive it natively, so the
   reveal costs no JavaScript and no scroll listener at all.

   Range note: `cover` is used rather than `entry` on purpose. A cover range
   spans (element height + viewport height), so viewport height dominates and
   short headings and tall panels reveal over the same comfortable distance.
   An `entry` range would make a two-line heading snap open in ~30px of scroll. */

@supports (animation-timeline: view()) {
  .reveal {
    opacity: 0;
    transform: translate3d(0, 14px, 0);
    animation-name: m-rise;
    animation-duration: auto;
    animation-timing-function: var(--ease);
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 5% cover 32%;
  }

  /* site.js still adds .is-in. A running animation already wins the cascade
     over these declarations; killing the transition just avoids a redundant
     style recalculation on every observed element. */
  .reveal.is-in { transition: none; }
}


/* 04  STAGGER ============================================================== */
/* Applied to the CONTAINER. Under a scroll timeline `animation-delay` has no
   meaning — there is no clock — so the cascade is produced by offsetting each
   child's range instead. Two percent of a cover range is roughly 30px of
   scroll, which is about as gentle as a stagger can be and still read.

   Under the IntersectionObserver fallback, site.css's existing
   `.reveal:nth-child(n).is-in { transition-delay }` rules already stagger
   these same children, so no fallback code is needed here. */

@supports (animation-timeline: view()) {
  .m-stagger > *:nth-child(1) { animation-range: cover  5% cover 30%; }
  .m-stagger > *:nth-child(2) { animation-range: cover  7% cover 32%; }
  .m-stagger > *:nth-child(3) { animation-range: cover  9% cover 34%; }
  .m-stagger > *:nth-child(4) { animation-range: cover 11% cover 36%; }
  .m-stagger > *:nth-child(5) { animation-range: cover 13% cover 38%; }
  .m-stagger > *:nth-child(6) { animation-range: cover 15% cover 40%; }
  .m-stagger > *:nth-child(n + 7) { animation-range: cover 17% cover 42%; }
}


/* 05  THE GOLD RULE, DRAWING ITSELF ======================================== */
/* Law 1 says gold is the live element. This is the one place on the page
   where "live" is allowed to be literal: the 56px gold rule under the H1
   draws itself left to right, once, as the page settles.

   It sits inside .m-seq, so it inherits that child's delay and simply swaps
   which keyframes it runs. Width and height never change — scaleX only. */

.m-seq > .rule-gold,
.m-draw {
  animation-name: m-draw;
  animation-duration: 0.55s;
  animation-timing-function: var(--ease);
  animation-fill-mode: both;
  transform-origin: left center;
}
.m-draw { animation-delay: var(--m-delay, 0.2s); }

.m-seq > .rule-gold--center,
.rule-gold--center.m-draw { transform-origin: center; }


/* 06  THE TRUST HAIRLINE, DRAWING ITSELF ==================================== */
/* Law 2 forbids inventing rules to divide sections, so this is applied only
   to a hairline the design already owns: the border above the trust strip.
   A border cannot be scaled, so the real border is made transparent (its 1px
   of layout is kept, so nothing moves) and a hairline pseudo-element is drawn
   over exactly where it sat. */

.m-rule-draw {
  position: relative;
  border-top-color: transparent;
}
.m-rule-draw::before {
  content: "";
  position: absolute;
  top: -1px; left: 0; right: 0;
  height: 1px;
  background: var(--hairline);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}
.section--deep .m-rule-draw::before,
.site-footer .m-rule-draw::before { background: var(--hairline-d); }

/* JS fallback path — motion.js adds .is-drawn */
.m-rule-draw.is-drawn::before {
  transform: scaleX(1);
  transition: transform 0.9s var(--ease);
}

@supports (animation-timeline: view()) {
  .m-rule-draw::before {
    animation-name: m-draw;
    animation-duration: auto;
    animation-timing-function: var(--ease);
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 28% cover 56%;
  }
  .m-rule-draw.is-drawn::before { transition: none; }
}


/* 07  KEN BURNS — THE HERO PHOTOGRAPH ====================================== */
/* Targets the <img> only, so the current photo--placeholder boxes are
   untouched and this switches itself on the day the real photography lands.
   The container already has overflow:hidden and a fixed aspect-ratio, so a
   transform on the image cannot shift a single pixel of layout.

   motion.js pauses this whenever the photograph is off screen — a permanently
   running compositor animation is a battery cost we are not entitled to. */

.m-kenburns img {
  animation: m-kenburns 32s var(--ease) infinite alternate;
  transform-origin: 54% 42%;
  backface-visibility: hidden;
}
.m-kenburns.is-paused img { animation-play-state: paused; }


/* 08  PARALLAX — ONE LANDSCAPE PHOTOGRAPH PER PAGE ========================= */
/* Depth, not spectacle: the image drifts ±3.5% against a 9% overhang while
   the frame scrolls past. Deliberately confined to the @supports block, so
   browsers without scroll timelines render an ordinary, correctly-cropped
   still rather than an oversized image sitting motionless.

   Law 4 still stands — apply this to one photograph per page, and only to a
   landscape crop. It reads as noise on a portrait. */

@supports (animation-timeline: view()) {
  .m-parallax { position: relative; overflow: hidden; }
  .m-parallax img {
    position: absolute;
    top: -4.5%; left: 0;
    width: 100%; height: 109%;
    object-fit: cover;
    animation-name: m-parallax;
    animation-duration: auto;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
    backface-visibility: hidden;
  }
}


/* 09  PHOTOGRAPHS FADING IN AS THEY DECODE ================================= */
/* Large warm photographs are the only place this brand raises its voice, and
   a hard pop is the one thing that makes them feel cheap. motion.js applies
   .m-img BEFORE decode and .is-loaded after, so with JavaScript off, or on a
   cached image, nothing is ever hidden. Opacity only — no reflow. */

/* A transition rather than an animation, on purpose. A filled CSS animation
   outranks every normal declaration in the cascade, so an `animation` here
   would silently cancel the parallax transform on the same <img>. */
.m-img { opacity: 0; }
.m-img.is-loaded { opacity: 1; transition: opacity 0.65s var(--ease); }


/* 10  HOVER LIFT — INTERACTIVE SURFACES ONLY =============================== */
/* Reused from .contact-row in site.css, verbatim, so the gesture stays one
   gesture across the site: 2–3px of lift and the existing --shadow-lift.

   Do not put this on a card that is not a link. A surface that responds to
   the pointer promises it can be clicked, and for a visitor who is already
   unsure of herself, a promise the page cannot keep is worse than stillness.
   Coarse pointers are excluded so it never sticks after a tap.

   The lift uses `translate`, not `transform`. Every .m-lift target on this
   site is also a .reveal, and a filled reveal animation owns `transform`
   outright — a hover written with `transform` would simply never appear.
   `translate` is an independent property, so it composes on top instead. */

@media (hover: hover) and (pointer: fine) {
  .m-lift {
    transition: translate 0.28s var(--ease), box-shadow 0.28s var(--ease);
  }
  .m-lift:hover {
    translate: 0 -3px;
    box-shadow: var(--shadow-lift);
  }

  /* Same cascade guard for the two lifts site.css already owns, in case a
     .reveal is ever put on the element itself rather than a wrapper. */
  .reveal.contact-row:hover { translate: 0 -2px; }
  .reveal.btn:active { translate: 0 1px; }
}


/* 11  FAQ DISCLOSURE ======================================================= */
/* Native <details> snaps. This gives it a height that eases, using the modern
   interpolate-size / ::details-content pair. Gated behind @supports, so a
   browser without it keeps today's instant open. No JavaScript, no ARIA
   changes, no loss of find-in-page. */

@supports selector(::details-content) {
  :root { interpolate-size: allow-keywords; }

  .faq details::details-content {
    block-size: 0;
    overflow: clip;
    opacity: 0;
    transition:
      block-size 0.36s var(--ease),
      opacity 0.28s var(--ease),
      content-visibility 0.36s allow-discrete;
  }
  .faq details[open]::details-content {
    block-size: auto;
    opacity: 1;
  }
}


/* 12  NO-JAVASCRIPT SAFETY NET ============================================= */
/* site.css hides .reveal and relies on site.js to bring it back, so a visitor
   with JavaScript off currently sees an almost empty page. Where scroll
   timelines exist this file has already fixed that — the reveal is pure CSS
   and needs no script. Where they do not, there is nothing left to un-hide
   the content, so we simply never hide it. Costs nothing, touches nothing.

   .m-seq, .m-in and .m-draw are time-based CSS and already work without
   JavaScript. .m-img is only ever applied BY JavaScript, so it cannot strand
   an image. Neither needs a net here. */

/* The `scripting: none` gate is deliberately NOT here. It only matches when scripting
   is unavailable — so if JavaScript is ENABLED but site.js never runs (404, blocking
   extension, CSP rejection, parse error, flaky network), a browser without scroll
   timelines had nothing left to un-hide .reveal and no <noscript> to fall back on.
   Blast radius measured: disclaimer.html and privacy.html carry ONE .reveal each
   wrapping the entire legal body, and faq.html two wrapping all twelve questions.
   Those pages rendered blank. Un-hiding unconditionally costs nothing — site.js's
   .is-in is purely additive on top of a visible baseline. */
@supports not (animation-timeline: view()) {
  .reveal {
    opacity: 1;
    transform: none;
  }
  /* the JS path re-hides only what it can then reveal itself */
  .js-reveal .reveal:not(.is-in) {
    opacity: 0;
    transform: translateY(14px);
  }
  .m-rule-draw { border-top-color: var(--hairline); }
  .m-rule-draw::before { transform: scaleX(1); }
  .section--deep .m-rule-draw,
  .site-footer .m-rule-draw { border-top-color: var(--hairline-d); }
}


/* 13  REDUCED MOTION — THE OFF SWITCH ====================================== */
/* This block is the reason the file exists in this order. site.css already
   clamps animation-duration to 0.01ms under reduced motion, but a
   scroll-driven animation has NO duration — it is driven by scroll position,
   so that clamp does not touch it. Scroll timelines must be switched off by
   name, and they are, here.

   Everything lands in its finished state. Nothing is left half-drawn,
   half-faded, or permanently zoomed. */

@media (prefers-reduced-motion: reduce) {
  .m-seq > *,
  .m-in,
  .m-img,
  .m-draw,
  .m-seq > .rule-gold,
  .reveal,
  .m-kenburns img,
  .m-parallax img,
  .m-rule-draw::before {
    animation: none !important;
    animation-timeline: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  /* Give the hairline back to the real border and retire the pseudo-element. */
  .m-rule-draw { border-top-color: var(--hairline) !important; }
  .section--deep .m-rule-draw,
  .site-footer .m-rule-draw { border-top-color: var(--hairline-d) !important; }
  .m-rule-draw::before { display: none !important; }

  /* Undo the parallax overhang so the crop is the intended one. */
  .m-parallax img {
    position: static !important;
    height: 100% !important;
    top: auto !important;
  }

  /* A shadow change is not movement, so the hover affordance survives.
     The travel does not.
     .contact-row and .btn move via `transform` in site.css and neither carries
     .reveal in the markup — the .reveal sits on their wrapper — so the old
     `.reveal.contact-row` / `.reveal.btn` selectors matched nothing and the 2px lift
     and 1px press both survived. Targeted directly, on the right property. */
  .m-lift { transition: box-shadow 0.2s !important; }
  .m-lift:hover { translate: none !important; }
  .contact-row:hover,
  .btn:active { transform: none !important; }

  .faq details::details-content { transition: none !important; }
}


/* 14  PRINT ================================================================ */

@media print {
  .m-seq > *,
  .m-in,
  .m-img,
  .m-draw,
  .reveal,
  .m-kenburns img,
  .m-parallax img {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .m-rule-draw { border-top-color: #ccc !important; }
  .m-rule-draw::before { display: none !important; }
  .m-parallax img { position: static !important; height: auto !important; }
  .faq details::details-content { block-size: auto !important; opacity: 1 !important; }
}
