/*
Reveal on scroll. Every block below the first screen arrives as the scroll reaches it, and
the things inside it arrive one after another instead of all at once.

It is a component and not a rule inside a block for the reason every block after this one
would otherwise write its own: the timing, the travel and the step are one decision about
how this site behaves, and five copies of it drift apart. A block opts in by carrying the
class and marking what it wants staggered; it never writes an animation of its own.

    <section class="services reveal">
      <h2 class="reveal__item"> …
      <p  class="reveal__item"> …

A group nests. A `.reveal` inside a `.reveal` is a group of its own with its own trigger
and its own count, which is what a long section needs: the head arrives when the section
does, and the rows arrive later, when the list itself is on screen, rather than playing out
below the fold while the visitor is still reading the heading. reveal.js gives an item to
the nearest `.reveal` above it, so the outer group does not claim the inner one's items.

The hero does not use it. It has its own storyboard, which is accepted, and pulling it onto
this one would be a redesign of an approved scene.

--- Without the script the content is simply there ---

Nothing here hides anything on its own: every rule that hides is behind `.reveal-js`, and
that class is put on <html> by reveal.js. A 404 on the file, a blocked script, a browser
with no IntersectionObserver, motion declined — in all of them the class is absent, none of
these rules match, and the page is in its final state with no flash of anything.

That is a step stricter than the hero's arrangement, where the pending class sits in the
markup and only the `.js` flag guards it: there, a failure of hero.js alone would leave the
scene hidden. Here the file that hides and the file that reveals are the same file.
*/

/*
The travel has two axes, and the horizontal one is off unless a block asks for it.

Everything up to block 06 arrives straight up, which is why the component was written with
one value. Block 07's portrait field arrives out of the middle of the block: twenty-four
tiles gathered on one point and opening out to their places, each one along its own
diagonal. That is a distance rather than a direction — the shape of it belongs to the block,
which derives each tile's offset from the tile's own position (book.css) — and what the
component owes it is somewhere to put the other half of the vector.

The default is a bare 0, so every block written before this one animates exactly as it did.
*/
@keyframes reveal-in {
  from {
    opacity: 0;
    translate: var(--reveal-shift-x, 0) var(--reveal-shift);
  }

  to {
    opacity: 1;
    translate: 0 0;
  }
}

/*
Which group an item obeys.

A descendant selector cannot say "the nearest group above me", and that is exactly what is
needed once groups nest: written as `.reveal--in .reveal__item`, the outer group's rule
reaches straight through the inner group and plays its items on the outer group's trigger.
Caught on the page, not reasoned about — with the section revealed and the list not, all
five rows were already at full opacity, so the list's own entrance was over before the list
was on screen and the stagger the CEO asked for did not exist.

An inherited custom property does say it. Every group sets --reveal-anim on itself, so a
nested group overrides what it inherits, and the item reads the value of the nearest group
above it — which is the missing selector, expressed as a value instead.
*/
.reveal-js .reveal {
  --reveal-anim: none;
}

.reveal-js .reveal--in {
  --reveal-anim: reveal-in;
}

/*
The waiting state. `:not(.reveal--in)` rather than a pending class the script has to take
off: there is no third state to name, an item is either waiting for its group or it is not,
and one selector cannot get out of step with a second one the way an add/remove pair can.

This one may reach through a nested group — an item of a revealed list inside a section
that has not itself been reached — and it does not matter: an animation beats a plain
declaration, so the item's own group wins wherever the two disagree.
*/
.reveal-js .reveal:not(.reveal--in) .reveal__item {
  opacity: 0;
  translate: var(--reveal-shift-x, 0) var(--reveal-shift);
}

/*
The stagger is the item's ordinal times the step, and the ordinal is written by the script
onto every item it finds — so a sixth service row added by the client picks up the sequence
by itself. That is the standing rule about animating collections rather than elements: there
is no list of delays anywhere, and nothing has to be edited when the collection grows.

`both` holds the first keyframe through the delay, so an item late in the queue does not
appear at its final opacity and then start moving.

The delay is its own declaration and comes after the shorthand on purpose: the shorthand
resets every animation property it does not mention, and animation-delay is one of them.
*/
.reveal-js .reveal__item {
  animation: var(--reveal-anim, none) var(--reveal-dur) var(--reveal-ease) both;
  animation-delay: calc(var(--reveal-index, 0) * var(--reveal-step));
}

/*
No motion means the end state and nothing else. reveal.js already declines to add its class
at all in this case, so none of the above is reachable; this is stated anyway because the
preference can be changed after the page has loaded, and because the blanket duration clamp
in base.css would otherwise leave an item on its *first* keyframe — transparent and offset,
i.e. invisible content — rather than on its last.
*/
@media (prefers-reduced-motion: reduce) {
  .reveal-js .reveal:not(.reveal--in) .reveal__item,
  .reveal-js .reveal__item {
    opacity: 1;
    translate: none;
    animation: none;
  }
}
