/* ============================================================
   motion.css
   Arrival (round 10). Every block of a page rises into place on
   load, in order.

   The whole thing is inside a no-preference query rather than
   being switched off by a reduce query. That way the starting
   state is visible content: if the stylesheet is late, or the
   reader has asked for less movement, or a browser does not know
   the property, the page is simply there. Motion is added to a
   working page, never something the page waits for.

   Needs tokens.css.
   ============================================================ */

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

  /* The toast arrives from above and leaves upward. If motion is
     off, both happen instantly; the message is still shown and
     still leaves.

     The fill mode is `backwards`, NOT `both`. The `forwards` part
     of `both` LOCKS the last keyframe's value in place after the
     animation ends, and it outranks normal declarations in the
     cascade; meaning the opacity:0 that `.is-going` sets could
     never take effect. The toast was not fading: it stayed fully
     visible, then vanished instantly the moment the script removed
     it from the DOM. As long as entry and exit use the same
     property, the entry animation must NOT LOCK things in place. */
  .js .toast{animation:toast-in var(--dur-fade) var(--ease) backwards}
  .toast{transition:opacity var(--dur-fade) var(--ease),
                    transform var(--dur-fade) var(--ease)}
  @keyframes toast-in{from{opacity:0;transform:translateY(var(--off-toast))}
                      to{opacity:1;transform:none}}


  /* So the card's height does not jump on the monthly/yearly
     switch. The motion itself is here, the state is in layout.css:
     when motion is turned off the transition is instant but the
     page stays correct. */
  .price-note{transition:grid-template-rows var(--dur) var(--ease),
                         opacity var(--dur) var(--ease)}


  /* Fade and movement are TWO SEPARATE animations. When they were
     one animation, both shared --ease-out; that expo curve is right
     for movement but leaves a long tail for opacity, and the card
     settles into place looking like its colour is wobbling.
     Separated, the fade finishes early and the settling movement
     stays the same.
     The reasoning is in tokens.css, next to --dur-fade. */
  .rise{
    animation:rise-fade var(--dur-fade) var(--ease) both,
              rise-move var(--dur-in) var(--ease-out) both;
    animation-delay:calc(var(--i,0) * var(--stagger));
  }
  @keyframes rise-fade{from{opacity:0}to{opacity:1}}
  @keyframes rise-move{from{transform:translateY(var(--off-rise))}to{transform:none}}

  /* The bar is chrome, not content. It fades without moving, so
     the page does not appear to slide out from under its own
     navigation. */
  .rise-bar{animation:rise-fade var(--dur-fade) var(--ease) both}


  /* Between pages (2026-10-06). In-site links no longer unload the
     document: js/navigate.js fetches the next page and swaps it in
     place, because in Firefox the gap between two documents is
     painted with the browser's own ground and no CSS or script of
     ours can reach it (see the note further down).

     The change: the content being left fades out while the next page
     is fetched (`data-leaving`, --dur-leave), then the incoming
     content rises in with the same staggered .rise a full load has.
     The bar, the panel tabs and the public pages' navigation
     (`.wrap-top`) hold still; they are the same on both pages, and
     taking them away to bring them straight back would only flicker.
     Chosen over an arrival alone and a cross fade, both tried on a
     real product. */
  html[data-leaving] main > :not(.wrap-top){
    opacity:0;
    transition:opacity var(--dur-leave) var(--ease);
  }

  /* Only when a page is slow to arrive: after --dur-swap with no answer
     a hairline sweeps under the bar. On a normal connection it never
     appears. Muted, not the accent: the accent marks where you are,
     and this is where you are going. */
  html[data-navigating] main::before{
    content:"";position:fixed;left:0;right:0;top:0;z-index:20;
    height:var(--ring-w);background:linear-gradient(90deg,transparent,var(--muted),transparent);
    animation:nav-progress var(--dur-progress) var(--ease) infinite;
  }
  @keyframes nav-progress{from{transform:translateX(-100%)}to{transform:translateX(100%)}}

  /* When arriving FROM WITHIN the site, the top bar GETS NO
     animation. It stays in place on the way out; if it faded in
     from zero on the way in, something that was staying in place
     the whole time would flash. On first load the arrival
     choreography stays exactly as it is -- there, the bar arriving
     is correct.

     A real load reached from inside the site gets the class from the
     script under head BEFORE the first paint (the navigator sets a
     flag when it falls back to one); a swapped page gets it from the
     navigator. Set any later, the bar would flash and disappear.

     The name is `is-nav`, NOT `nav`. `nav` was tried first, and the
     moment the page changed the whole layout pushed to the left:
     `.nav` is already the site navigation's component class
     (parts.css) and carries `display:flex`, so <html> turned into a
     flex box and laid <head> and <body> out side by side. A class
     put on the ROOT ELEMENT cannot share a name with a component;
     state classes start with `is-`, and this is one of them.

     An arrival animation for the CONTENT was TRIED and reverted:
     turning `.rise` off on in-site navigation did not remove the
     flash, and the transitions looked worse.

     STOP CHASING IT. The brief flash seen in Firefox when changing
     pages is NOT OURS: Firefox has no "paint holding", meaning it
     does not keep the old document on screen until the new one is
     painted, and shows its own default ground in between. It never
     happens in Chrome on the same transitions; tried side by side
     on 2026-08-28.

       bugzilla 1714769  the record requesting Chrome's paint holding
       bugzilla 1488384  white flash in dark theme
       bugzilla 1538414  "hold painting until the ground is known"

     In this window there is no page of ours in the middle at all --
     no CSS, no script. Three separate attempts (an inline
     background, scrollbar-gutter, turning off the arrival
     animation) therefore had no effect. The first two stay anyway
     because each closed a real gap of its own; the third was
     reverted. */
  html.is-nav .rise-bar{animation:none}


  /* ---------- conversation ----------
     Bubbles do NOT get animated ONE BY ONE on page load: thirty
     messages arriving in sequence means waiting for the thing you
     came to read. The conversation gets `.rise` as a single block,
     just like the rest of the page's regions.

     The only thing that gets animated is a message that arrives
     LATER. There, motion is not decoration but information: it
     says something changed while the page was sitting still. */
  .msg--new{animation:rise-fade var(--dur-fade) var(--ease) both,
                      msg-move var(--dur-in) var(--ease-out) both}
  @keyframes msg-move{from{transform:translateY(var(--off-msg))}to{transform:none}}


  /* ---------- modal ----------
     The backdrop fades, the card comes up from below and settles
     into place. Same language as the page's arrival: short fade,
     long settle.

     This could also be written CSS-only, with `@starting-style` and
     `allow-discrete`, but the page-transition experiment in Firefox
     taught us the lesson: a factory template has to behave the same
     in every browser. The script sets the class.

     The hidden state sits under `.js`: without a script these rules
     never apply at all. Otherwise, if something went wrong, the
     modal would open at opacity:0, meaning an invisible window that
     still locks the page. */
  .js .modal{opacity:0;transform:translateY(var(--off-modal)) scale(.98);
    transition:opacity var(--dur-fade) var(--ease),
               transform var(--dur-fade) var(--ease-out)}
  .js .modal.is-open{opacity:1;transform:none}

  .js .modal::backdrop{background:transparent;
    transition:background var(--dur-fade) var(--ease)}
  .js .modal.is-open::backdrop{background:var(--veil)}
}

/* Six is the practical end of a stagger. Past that the last block
   waits half a second for nothing, so pages stop counting at six
   and let the rest arrive together. */
.i1{--i:1} .i2{--i:2} .i3{--i:3} .i4{--i:4} .i5{--i:5} .i6{--i:6}
