/* AssumeCheck — the motion vocabulary. One file, shared by every page that
   opts in.

   The concept mockup (design/hero-concept.html) sets the revealed elements to
   opacity:0 and paints them in with a class. Shipped as written, that is a
   blank page for anyone whose JavaScript did not run — a slow network, a
   blocked script, a text browser, a crawler. The content here is public
   records; it has no business depending on a script to be legible.

   So the polarity is inverted. Nothing is hidden by a stylesheet alone.
   Hiding requires all three of:

     html.js        an inline head script set this, so JavaScript is running
     body.reveal    this page asked for the choreography
     (not) .on      the reveal has not been triggered yet

   Miss any one and every element sits in its final state. That is also why
   `.js` is set in <head> rather than on load: the class is on the element
   before first paint, so there is no flash of finished content.

   Two opt-in levels, because a receipt should not perform:
     body.reveal    entrance choreography. Homepage and town pages.
     body.hero      micro-interactions and depth. Homepage only.

   Everything animated here is opacity or transform. Neither reflows, so none
   of it can move the page under a thumb that is already reaching for a link.
*/

:root {
  --ease: cubic-bezier(.2, .8, .2, 1);
  --rise-dur: 620ms;
  --rise-shift: 14px;
}

/* ---------- the reveal ----------------------------------------------------
   [data-rise] on any element; [data-rise="1".."8"] to place it in the queue.
   80ms apart, so the eye reads the page in the order it is meant to. */

.js body.reveal [data-rise] {
  opacity: 0;
  transform: translateY(var(--rise-shift));
}

/* The dead man's switch.
   `html.js` covers JavaScript being switched off. It does not cover
   JavaScript that ran, set the class, and then died — a throwing extension, a
   `load` event that never arrives because an image hangs, a browser that
   handles requestAnimationFrame differently than expected. In every one of
   those the reveal never fires and the page is blank prose on paper.
   An animation is not a transition and needs nothing to trigger it: three
   seconds after parse, this lands on the finished state whatever happened to
   the script. CSS animations outrank normal declarations, so it also cannot
   be undone by anything above. Nobody should ever see it fire. */
.js body.reveal [data-rise],
.js body.reveal [data-fade] {
  animation: reveal-failsafe 1ms linear 3s forwards;
}

@keyframes reveal-failsafe {
  to { opacity: 1; transform: none; }
}

.js body.reveal [data-rise],
.js body.reveal [data-fade] {
  transition:
    opacity var(--rise-dur) var(--ease) var(--rise-delay, 0ms),
    transform var(--rise-dur) var(--ease) var(--rise-delay, 0ms);
  will-change: opacity, transform;
}

.js body.reveal [data-fade] { opacity: 0; }

.js body.reveal.on [data-rise],
.js body.reveal.on [data-fade] {
  opacity: 1;
  transform: none;
}

/* Once it has played, hand everything back.
   Not just `will-change`: an `animation-fill-mode: forwards` never finishes
   in the sense the compositor cares about — the failsafe below would hold a
   layer on every revealed element for the rest of the visit, and text on a
   composited layer rasterises differently (grey antialiasing, borders landing
   a pixel off) than the same text at rest. Measurably: 1.5% of the homepage's
   pixels, permanently, for an animation that lasted half a second.
   `.done` lands before the failsafe's delay elapses, so in the normal path
   that animation never arms at all. */
.js body.reveal.done [data-rise],
.js body.reveal.done [data-fade] {
  will-change: auto;
  transition: none;
  animation: none;
}

[data-rise="1"] { --rise-delay:  60ms; }
[data-rise="2"] { --rise-delay: 140ms; }
[data-rise="3"] { --rise-delay: 220ms; }
[data-rise="4"] { --rise-delay: 320ms; }
[data-rise="5"] { --rise-delay: 420ms; }
[data-rise="6"] { --rise-delay: 520ms; }
[data-rise="7"] { --rise-delay: 620ms; }
[data-rise="8"] { --rise-delay: 720ms; }

/* ---------- micro-interactions (homepage only) --------------------------- */

body.hero .btn {
  transition: transform .25s var(--ease), box-shadow .25s var(--ease),
              background .25s var(--ease);
}
body.hero .btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 22px -12px rgba(22, 35, 59, .6);
}
body.hero .btn:active { transform: translateY(0); }

body.hero input[type=text],
body.hero input[type=email],
body.hero input[type=number] {
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
body.hero input:focus {
  outline: none;
  border-color: var(--brass);
  box-shadow: 0 0 0 3px rgba(140, 109, 47, .18);
}

/* The underline grows from the left rather than appearing, which reads as the
   nav responding to the pointer instead of blinking at it. */
body.hero .masthead nav a { position: relative; padding-bottom: 3px; }
body.hero .masthead nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  height: 1px; width: 100%;
  background: var(--brass);
  /* scaleX rather than animating width: the concept animates the width, and
     on an absolutely-positioned pseudo-element that happens to be safe, but
     it asks the browser for layout on every frame of a hover. A scale is
     composited, and it is the same underline. */
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .3s var(--ease);
}
body.hero .masthead nav a:hover::after { transform: scaleX(1); }

/* ---------- the one accessibility rule that outranks all of the above -----
   Not "smaller animations": none. Everything sits in its final state, exactly
   as it does with JavaScript switched off. The ticker stops too — a strip of
   text sliding forever is the thing this setting is usually asked for. */

@media (prefers-reduced-motion: reduce) {
  .js body.reveal [data-rise],
  .js body.reveal [data-fade],
  .js body.reveal.on [data-rise],
  .js body.reveal.on [data-fade] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
    will-change: auto !important;
  }

  body.hero .btn,
  body.hero .btn:hover,
  body.hero .btn:active,
  body.hero input {
    transition: none !important;
    transform: none !important;
    box-shadow: none !important;
  }
  /* The underline still appears on hover — it is feedback, not motion — it
     just stops sliding into place. */
  body.hero .masthead nav a::after {
    transition: none !important;
    transform: scaleX(0) !important;
  }
  body.hero .masthead nav a:hover::after { transform: scaleX(1) !important; }

  .ticker .track {
    animation: none !important;
    transform: none !important;
  }

  .timeline .band { transform: scaleX(1) !important; }
  .timeline .dot  { transform: translate(-50%, 0) scale(1) !important; }
  .timeline .band, .timeline .dot, .timeline .bandlabel {
    transition: none !important;
    opacity: 1 !important;
  }
}
