/* uaclub.org.uk — motion
 *
 * Everything here is optional decoration on a site that must work without it, so it is built to
 * fail safe in three ways:
 *
 *   1. Nothing is hidden unless JavaScript has run. The reveal states only apply under
 *      `html.has-motion`, a class site.js adds. If the script never loads, every element is
 *      already in its final state and the page reads exactly as it does today.
 *   2. The whole file is behind `prefers-reduced-motion: no-preference`. A visitor who has asked
 *      their system for less movement — which includes people for whom motion triggers nausea or
 *      migraine — gets the static site, not a slower version of the animated one.
 *   3. Hover effects are behind `hover: hover`, so a phone never leaves a card stuck in its
 *      hover state after a tap.
 *
 * The movement itself is deliberately small. This is a charity whose members have had enough
 * upheaval; the page should feel calm, not eventful.
 */

@media (prefers-reduced-motion: no-preference) {

  /* ---- Reveal on scroll ---------------------------------------
   * A short rise and fade as a section arrives. 12px, not 40 — enough to feel like the page is
   * settling, little enough that nobody scrolling quickly sees a gap where content should be. */
  .has-motion .reveal {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity .55s cubic-bezier(.22, .61, .36, 1),
                transform .55s cubic-bezier(.22, .61, .36, 1);
  }

  .has-motion .reveal.is-in {
    opacity: 1;
    transform: none;
  }

  /* Children of a revealed group come in just behind it, so a row of cards arrives as a row
   * rather than four separate events. Five steps is as far as the stagger goes; a longer one
   * makes the last card feel late. */
  .has-motion .reveal--stagger > * {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity .5s cubic-bezier(.22, .61, .36, 1),
                transform .5s cubic-bezier(.22, .61, .36, 1);
  }
  .has-motion .reveal--stagger.is-in > * { opacity: 1; transform: none; }
  .has-motion .reveal--stagger.is-in > *:nth-child(1) { transition-delay: .00s; }
  .has-motion .reveal--stagger.is-in > *:nth-child(2) { transition-delay: .07s; }
  .has-motion .reveal--stagger.is-in > *:nth-child(3) { transition-delay: .14s; }
  .has-motion .reveal--stagger.is-in > *:nth-child(4) { transition-delay: .21s; }
  .has-motion .reveal--stagger.is-in > *:nth-child(n+5) { transition-delay: .28s; }

  /* The home page leans into the reveal a little more, so each block clearly arrives one by one as
   * it scrolls into view — a larger rise, and the block's own pieces (heading, then content) coming
   * in just behind one another. The rest of the site keeps the calmer 12px settle above.
   * `has-motion` is on <html> and `home` on <body>, hence the order; `:not(.is-in)` keeps the
   * bigger offset from overriding the settled state. */
  .has-motion .home .reveal:not(.is-in),
  .has-motion .home .reveal--stagger:not(.is-in) > * { transform: translateY(30px); }
  .has-motion .home .reveal { transition-duration: .7s; }
  .has-motion .home .band .wrap > .reveal:nth-child(2) { transition-delay: .10s; }
  .has-motion .home .band .wrap > .reveal:nth-child(3) { transition-delay: .18s; }
  .has-motion .home .band .wrap > .reveal:nth-child(n+4) { transition-delay: .26s; }

  /* ---- The hero photograph settles ----------------------------
   * One slow scale on first paint, never repeated. It reads as the picture coming to rest rather
   * than as an animation playing. */
  .has-motion .hero > img {
    animation: uac-settle 1.6s cubic-bezier(.22, .61, .36, 1) both;
  }

  @keyframes uac-settle {
    from { transform: scale(1.045); }
    to   { transform: scale(1); }
  }

  /* ---- Cards ---------------------------------------------------
   * The whole card is a link target in practice, so it should acknowledge the pointer. */
  .card,
  .event-card,
  .person,
  .partner,
  .stat {
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
  }

  @media (hover: hover) {
    .card:hover,
    .event-card:hover {
      transform: translateY(-3px);
      box-shadow: 0 10px 26px -12px rgba(7, 34, 47, .38);
      border-color: rgba(7, 34, 47, .22);
    }

    /* The photograph leans in a little with it. `overflow: hidden` on the card keeps it framed. */
    .card,
    .event-card { overflow: hidden; }

    .card > img,
    .event-card > img { transition: transform .5s cubic-bezier(.22, .61, .36, 1); }
    .card:hover > img,
    .event-card:hover > img { transform: scale(1.04); }

    /* On the deep band the lift needs light, not shadow. */
    .band--deep .stat:hover {
      transform: translateY(-2px);
      border-color: rgba(250, 246, 238, .34);
      box-shadow: none;
    }

    /* The arrow moves, not the words. `.link-action` is an inline-flex row and the bare "→" after
     * the spans is an anonymous flex item, so widening the gap slides the arrow on its own — no
     * extra markup, and the label stays put under the pointer. */
    .link-action { transition: gap .22s ease, color .2s ease; }
    .link-action:hover { gap: calc(var(--space-2) + 5px); }

    .logo-tile { transition: border-color .25s ease, background-color .25s ease; }
    .logo-tile:hover { border-color: rgba(7, 34, 47, .28); }
  }

  /* ---- Buttons -------------------------------------------------
   * Pressing should feel like pressing. */
  .btn { transition: background-color .2s ease, border-color .2s ease, color .2s ease, transform .1s ease; }
  .btn:active { transform: translateY(1px); }
}

/* ==================================================================
 * Parallax and image effects
 *
 * Built on scroll-driven animations (`animation-timeline: view()`), not a scroll listener. The
 * browser runs these off the main thread, so they stay smooth while the page is doing other work
 * — a JS scroll handler moving a 1200px photograph is the classic way to make a site feel worse,
 * not better. Where the feature is missing the effects simply do not apply and the page is the
 * one we already had.
 * ================================================================== */

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {

    /* ---- Photographs wipe open as they arrive ------------------
     * A soft inset that opens from the bottom, finished well before the image is centred so the
     * reader never scrolls to a picture that is still assembling itself. */
    /* `forwards`, never `both`. A backwards fill would hold the from-state — clipped and at 55%
     * opacity — on any browser where the timeline failed to become active, and a permanently
     * dimmed photograph is a real defect where a missing animation is not. */
    .has-motion .card > img,
    .has-motion .event-card > img {
      animation: uac-wipe linear forwards;
      animation-timeline: view();
      animation-range: entry 10% entry 90%;
    }

    @keyframes uac-wipe {
      from { clip-path: inset(0 0 22% 0); opacity: .55; }
      to   { clip-path: inset(0 0 0 0);   opacity: 1; }
    }

    /* ---- The gold rule under a section heading draws in -------- */
    .has-motion .band--deep .pullquote--inverse {
      animation: uac-rule linear forwards;
      animation-timeline: view();
      animation-range: entry 20% entry 80%;
    }

    @keyframes uac-rule {
      from { border-left-color: rgba(233, 198, 5, 0); }
      to   { border-left-color: var(--accent); }
    }
  }

  /* ---- The gold button catches the light on hover -------------- */
  @media (hover: hover) {
    .btn--primary { position: relative; overflow: hidden; }
    .btn--primary::after {
      content: ""; position: absolute; inset: 0;
      background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, .55) 50%, transparent 62%);
      transform: translateX(-120%);
      transition: transform .6s cubic-bezier(.22, .61, .36, 1);
      pointer-events: none;
    }
    .btn--primary:hover::after { transform: translateX(120%); }
  }

  /* The numbers tick up; see site.js. Tabular figures so the card does not jitter as digits
   * change width on the way. */
  .has-motion .stat__n { font-variant-numeric: tabular-nums; }
}

  /* ---- Parallax ------------------------------------------------
   * site.js sets --uac-shift on each of these as the page scrolls; the transform below turns it
   * into movement. Keeping the maths in JS and the painting in CSS means the scroll handler only
   * ever writes one custom property, and the compositor does the rest.
   *
   * The scale is the headroom: without it, shifting the photograph would drag its edge into the
   * frame. The settle animation is dropped on the hero, because two things animating one
   * element's transform fight and the drift already gives the picture life.
   */
  .has-motion .hero > img[data-parallax] {
    animation: none;
    transform: scale(1.12) translate3d(0, var(--uac-shift, 0px), 0);
  }

  .has-motion .photo:not(.photo--hero) { overflow: hidden; border-radius: var(--radius-card); }

  .has-motion .photo:not(.photo--hero) > img[data-parallax] {
    transform: scale(1.14) translate3d(0, var(--uac-shift, 0px), 0);
  }

/* ==================================================================
 * The gallery stack
 *
 * Two effects, both in the same idea: this is a pile of photographs, and a pile can be dealt out
 * and looked through. Nothing here moves on its own — the stack is beside a headline, and a thing
 * that animates unprompted next to text you are trying to read is a nuisance rather than a
 * flourish.
 * ================================================================== */

@media (prefers-reduced-motion: no-preference) {

  /* ---- Dealt out ------------------------------------------------
   * On arrival the slats come in from the left in sequence, the way a stack is laid down. The
   * edges follow the three photographs, so the eye lands on a picture first and the depth appears
   * behind it a moment later. `backwards` fill holds each slat off-stage until its turn rather
   * than flashing it in place first. */
  .has-motion .photo-strip__slat {
    animation: uac-deal .5s cubic-bezier(.22, .61, .36, 1) backwards;
  }

  /* From .35, not from 0. A `backwards` fill holds the from-state until the animation's turn, so
   * an animation that is throttled at exactly the wrong moment would otherwise leave photographs
   * at zero opacity. Dim-then-sharpen looks near enough the same and cannot hide anything. */
  @keyframes uac-deal {
    from { opacity: .35; transform: translateX(-18px); }
    to   { opacity: 1; transform: none; }
  }

  .has-motion .photo-strip__slat:nth-child(1)  { animation-delay: .05s; }
  .has-motion .photo-strip__slat:nth-child(2)  { animation-delay: .11s; }
  .has-motion .photo-strip__slat:nth-child(3)  { animation-delay: .17s; }
  .has-motion .photo-strip__slat:nth-child(n+4) { animation-delay: .23s; }
  .has-motion .photo-strip__slat:nth-child(n+6) { animation-delay: .29s; }
  .has-motion .photo-strip__slat:nth-child(n+8) { animation-delay: .35s; }

  @media (hover: hover) {
    /* ---- Look through it ----------------------------------------
     * An edge opens under the pointer so it is worth pointing at instead of being decoration — you
     * can see what is in the stack without leaving the page. The edge stops at 60px: their source is
     * the square thumbnail, and past that it would be enlarged past its own resolution. The depth
     * (the front/back widths) is left alone, so hovering reads through the stack rather than
     * flattening the perspective the layout just built. */
    .photo-strip__shots:hover .photo-strip__slat--edge { flex-basis: 16px; }
    .photo-strip__shots .photo-strip__slat--edge:hover { flex-basis: 60px; }

    /* The whole stack dips into the wash and the photograph under the pointer comes back up out of
     * it — full colour and a small step forward. */
    .photo-strip__slat img { transition: filter .35s ease, transform .5s cubic-bezier(.22, .61, .36, 1); }
    .photo-strip__shots:hover .photo-strip__slat img { filter: saturate(.7) brightness(.78); }
    .photo-strip__shots .photo-strip__slat:hover img { filter: none; transform: scale(1.05); }
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* Branch index cards — a small lift and a nudge of the arrow on hover, the icon filling in.
   * Calm by design: one 3px rise, nothing that jumps. */
  .has-motion .row-card { transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease; }
  .has-motion .row-card__arrow { transition: transform .25s ease; }
  .has-motion .row-card__ico { transition: background-color .25s ease, color .25s ease; }
  @media (hover: hover) {
    .has-motion .row-card:hover { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(26, 26, 24, .12); }
    .has-motion .row-card:hover .row-card__arrow { transform: translateX(4px); }
    .has-motion .row-card:hover .row-card__ico { background: var(--brand); color: var(--ink-inverse); }
  }
}
