/* ==========================================================================
 * PYNQ motion system
 * --------------------------------------------------------------------------
 * BOOT -> REVEAL -> INTERACT -> SCROLL -> SETTLE
 *
 * Built on the vocabulary the site already has: the stepped scanline mask that
 * app.js uses for [data-reveal], the pixel-notched frames, and the 4px grid
 * everything else is drawn on. Nothing here introduces a new visual language.
 *
 * Every hidden-before-reveal state is scoped to `html.motion`, a class motion.js
 * adds at runtime. Without JavaScript, or under prefers-reduced-motion, that
 * class is never added and the page renders exactly as it does today, fully
 * visible. Nothing in this file can hide content permanently.
 *
 * Only transform and opacity are animated, so nothing here can cause a layout
 * shift or horizontal overflow.
 * ========================================================================== */

:root {
  /* One curve and three durations for the whole system. */
  --m-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --m-ease-step: steps(6, end);
  --m-fast: 140ms;
  --m-mid: 320ms;
  --m-slow: 560ms;
  --m-stagger: 70ms;
}

/* ==========================================================================
   INTERACT. Hover, press and focus. CSS only: these must never wait on a
   script, and they must never delay the action they decorate.
   ========================================================================== */

/* Buttons depress 2px into their own block shadow, the way an arcade cabinet
   key does. The shadow is drawn on .btn-face so it rides the existing frame. */
.btn > .btn-face {
  transition:
    transform var(--m-fast) var(--m-ease),
    box-shadow var(--m-fast) var(--m-ease),
    background-color var(--m-fast) var(--m-ease);
}
.btn:hover > .btn-face {
  transform: translateY(-1px);
}
.btn:active > .btn-face {
  transform: translateY(2px);
  box-shadow: none;
}
.btn-copper > .btn-face,
.btn-sand > .btn-face,
.btn-white > .btn-face {
  box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.55);
}
.btn-copper:active > .btn-face,
.btn-sand:active > .btn-face,
.btn-white:active > .btn-face {
  box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.55);
}

/* Keyboard focus has to be at least as loud as hover. */
.btn:focus-visible > .btn-face,
.card:focus-within,
.nav-panel a:focus-visible {
  outline: 2px solid var(--copper-500);
  outline-offset: 2px;
}

/* Cards: a border that wakes up, and one pixel of lift. No scaling, because
   scaling a pixel frame makes its notches blurry. */
.card,
.spec,
.live {
  transition:
    transform var(--m-mid) var(--m-ease),
    color var(--m-mid) var(--m-ease);
}
.card:hover {
  transform: translateY(-2px);
  /* .card colours its own pixel frame through currentColor */
  color: var(--copper-600);
}

/* Table rows light a left edge rather than moving. */
.data-table tbody tr {
  transition: background-color var(--m-fast) var(--m-ease), box-shadow var(--m-fast) var(--m-ease);
}
.data-table tbody tr:hover {
  box-shadow: inset 3px 0 0 0 var(--copper-500);
}

/* Navigation: an indicator that grows from the left on hover and stays put on
   the current page. Drawn on a pseudo-element so no layout is involved. */
.site-nav .nav-list > li > a,
.site-nav .nav-list > li > button {
  position: relative;
}
.site-nav .nav-list > li > a::after,
.site-nav .nav-list > li > button::after {
  content: '';
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 2px;
  height: 2px;
  background: var(--copper-500);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--m-fast) var(--m-ease);
  pointer-events: none;
}
.site-nav .nav-list > li:hover > a::after,
.site-nav .nav-list > li:hover > button::after,
.site-nav .nav-list > li > a[aria-current]::after {
  transform: scaleX(1);
}

/* The CRT desktop icons already invert on selection; give the press a feel. */
.crt-icon {
  transition: transform var(--m-fast) var(--m-ease);
}
.crt-icon:active {
  transform: translateY(2px);
}

/* ==========================================================================
   REVEAL. Everything below is gated on html.motion.
   ========================================================================== */

/* The shared resting state: 18px down and transparent, revealed through the
   same stepped scanline mask app.js uses, so a card resolves the way a
   heading does. */
html.motion [data-m] {
  opacity: 0;
  transform: translate3d(0, var(--m-rise, 18px), 0);
  transition:
    opacity var(--m-slow) var(--m-ease) var(--m-delay, 0ms),
    transform var(--m-slow) var(--m-ease) var(--m-delay, 0ms);
  will-change: transform, opacity;
}
html.motion [data-m].m-in {
  opacity: 1;
  transform: none;
}
/* Once settled, drop the hint so long pages do not hold hundreds of layers. */
html.motion [data-m].m-done {
  will-change: auto;
}

/* The scanline resolve, run by motion.js in four steps on the way in. */
html.motion [data-m-mask] {
  -webkit-mask-image: var(--m-mask);
  mask-image: var(--m-mask);
}

/* Cards get a one-shot border flash as they land, which is the pixel-frame
   equivalent of a highlight. */
@keyframes m-edge {
  0% { color: var(--copper-500); }
  100% { color: var(--stone-600); }
}
html.motion .card.m-in {
  animation: m-edge 620ms var(--m-ease) 120ms both;
}

/* Flow steps resolve along their rail: the number first, then the rule. */
html.motion .flow-step[data-m] .flow-n {
  transition: color var(--m-mid) var(--m-ease) calc(var(--m-delay, 0ms) + 120ms);
  color: var(--stone-600);
}
html.motion .flow-step[data-m].m-in .flow-n {
  color: var(--copper-500);
}

/* ==========================================================================
   BOOT. Homepage only, under two seconds, once per session.
   The page is interactive throughout: nothing covers it and nothing blocks
   input. This only sequences things that were going to appear anyway.
   ========================================================================== */

/* A small status indicator in the corner of the hero, in the site's own
   caption type. It reports the boot and then removes itself. */
/* Held back for the boot sequence. The class is added and removed by
   motion.js, so these are only ever hidden while the sequence is running. */
html.motion.booting .hero-sub,
html.motion.booting .hero-actions {
  opacity: 0;
}
html.motion.booting .nav-logo img {
  opacity: 0;
}

.boot-status {
  position: absolute;
  top: calc(5rem + 0.75rem);
  left: 0;
  right: 0;
  margin-inline: auto;
  width: fit-content;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--copper-600);
  background: var(--stone-900);
  color: var(--copper-400);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--m-mid) var(--m-ease);
}
.boot-status[data-on] {
  opacity: 1;
}
.boot-status i {
  width: 7px;
  height: 7px;
  background: currentColor;
  animation: m-blink 420ms steps(1, end) infinite;
}
@keyframes m-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0.15; }
}

/* ==========================================================================
   SCROLL. A background that reacts without competing.
   ========================================================================== */

/* The graticule on inner pages drifts one grid cell over a long cycle, and
   brightens very slightly as the section is scrolled through. Both are driven
   by custom properties motion.js writes, so there is no extra paint work.  */
.graticule {
  background-position: calc(50% + var(--m-drift, 0px)) calc(50% + var(--m-drift, 0px));
}

/* Discrete grid illumination: a single cell lights behind the content at the
   top of a band, on the same 4px-derived rhythm as everything else. */
.m-grid-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(
      320px 180px at var(--m-glow-x, 50%) var(--m-glow-y, 0%),
      color-mix(in srgb, var(--copper-500) 7%, transparent),
      transparent 70%
    );
  opacity: var(--m-glow, 0);
  transition: opacity var(--m-slow) var(--m-ease);
}

/* ==========================================================================
   SETTLE. One reusable pixelated wipe, used between a small number of major
   sections. It reveals what is behind it through a shrinking grid of blocks.
   ========================================================================== */
.m-wipe {
  position: relative;
  isolation: isolate;
}
.m-wipe::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: var(--page-primary);
  /* The mask is a grid of squares whose size motion.js shrinks from 32px to 0,
     so the section behind appears block by block rather than by fading. */
  -webkit-mask-image:
    repeating-linear-gradient(to right, #000 0 var(--m-block, 0px), transparent var(--m-block, 0px) 32px),
    repeating-linear-gradient(to bottom, #000 0 var(--m-block, 0px), transparent var(--m-block, 0px) 32px);
  mask-image:
    repeating-linear-gradient(to right, #000 0 var(--m-block, 0px), transparent var(--m-block, 0px) 32px),
    repeating-linear-gradient(to bottom, #000 0 var(--m-block, 0px), transparent var(--m-block, 0px) 32px);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
  opacity: var(--m-wipe-on, 0);
}

/* ==========================================================================
   COUNTERS. The element reserves its final width before counting, so a number
   ticking from 0 to 1,284 cannot reflow the line it sits on.
   ========================================================================== */
.m-count {
  display: inline-block;
  font-variant-numeric: tabular-nums;
  min-width: var(--m-count-w, auto);
}

/* A progress indicator that fills in discrete blocks rather than smoothly. */
.m-blocks {
  display: flex;
  gap: 2px;
}
.m-blocks i {
  width: 10px;
  height: 10px;
  background: var(--stone-700);
  transition: background-color var(--m-fast) var(--m-ease);
}
.m-blocks i[data-on] {
  background: var(--copper-500);
}

/* ==========================================================================
   The whole system is opt-out, in one place.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html.motion [data-m],
  html.motion [data-m].m-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
  html.motion .card.m-in {
    animation: none !important;
  }
  .boot-status,
  .m-wipe::after,
  .m-grid-glow {
    display: none !important;
  }
  .btn > .btn-face,
  .card,
  .data-table tbody tr,
  .crt-icon {
    transition: none !important;
  }
  .site-nav .nav-list > li > a::after,
  .site-nav .nav-list > li > button::after {
    transition: none !important;
  }
}

/* ==========================================================================
   SCROLL-DRIVEN SEQUENCE
   The verification pipeline is the one genuine sequence on the site, so it is
   driven by the scrollbar rather than by a timer. Each gate lights as the
   section passes, and unlights when you scroll back up.
   ========================================================================== */
html.motion [data-m-scrub] .flow-step .flow-n,
html.motion [data-m-scrub] .flow-step h3 {
  transition: color var(--m-mid) var(--m-ease);
}
html.motion [data-m-scrub] .flow-step .flow-n {
  color: var(--stone-600);
}
html.motion [data-m-scrub] .flow-step h3 {
  color: var(--stone-400);
}
html.motion [data-m-scrub] .flow-step.m-lit .flow-n {
  color: var(--copper-500);
}
html.motion [data-m-scrub] .flow-step.m-lit h3 {
  color: var(--text-primary);
}
/* A marker travelling the rail, showing where the sequence has reached. */
html.motion [data-m-scrub] .flow-step {
  position: relative;
}
html.motion [data-m-scrub] .flow-step::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--copper-500);
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform var(--m-mid) var(--m-ease);
}
html.motion [data-m-scrub] .flow-step.m-lit::before {
  transform: scaleY(1);
}

@media (prefers-reduced-motion: reduce) {
  html.motion [data-m-scrub] .flow-step .flow-n {
    color: var(--copper-500) !important;
  }
  html.motion [data-m-scrub] .flow-step h3 {
    color: var(--text-primary) !important;
  }
  html.motion [data-m-scrub] .flow-step::before {
    display: none !important;
  }
}
