/* ==========================================================================
   Hayanezu — motion.css
   Every transition, keyframe and animation on the site lives here.
   The whole file is neutralised by the block at the bottom under
   `prefers-reduced-motion: reduce`. Nothing here carries meaning on its own.

   The hero's atmosphere is defined entirely in this file and main.css: no
   canvas, no JavaScript, nothing to boot. Everything animates transform and
   opacity only.
   ========================================================================== */

/* --- Transitions ---------------------------------------------------------- */

.nav__link::after { transition: transform var(--d-fast) var(--e-out); }

.link, .nav__link, .brand__word, .brand .mark, .lang a,
.footer a, .index__meta, .check, .nav-toggle, .view-toggle button, .word,
.field input, .field select, .field textarea, .field label {
  transition: color var(--d-fast) var(--e-out),
              background-color var(--d-fast) var(--e-out),
              border-color var(--d-fast) var(--e-out),
              box-shadow var(--d-fast) var(--e-out);
}
.brand .mark { transition: transform var(--d-mid) var(--e-soft), filter var(--d-mid) var(--e-out); }
.brand:hover .mark,
.brand:focus-visible .mark {
  transform: translateY(-1px) rotate(-4deg) scale(1.06);
  filter: drop-shadow(0 0 13px rgb(var(--accent-rgb) / 0.52));
}

.btn { transition: transform var(--d-fast) var(--e-out),
                   border-color var(--d-fast) var(--e-out),
                   box-shadow var(--d-mid) var(--e-out); }

/* visibility animates DISCRETELY — it flips at the END of its duration — so
   `visibility var(--d-mid)` delayed the panel becoming visible AND focusable
   by 380ms after it opened, while nav.js moves focus to the first item at
   220ms. The focus call landed on nothing and the keyboard was stranded on
   the trigger.

   The correct idiom is asymmetric: flip visibility immediately when opening,
   and hold it until the fade finishes when closing, which is the only thing
   the transition was ever for. */
.nav {
  transition: opacity var(--d-mid) var(--e-out),
              visibility 0s linear var(--d-mid);
}
body.nav-open .nav {
  transition: opacity var(--d-mid) var(--e-out),
              visibility 0s linear 0s;
}
.navx, .navx__curtain {
  transition: opacity var(--d-mid) var(--e-out),
              visibility 0s linear var(--d-mid);
}
html.navx-open .navx,
html.navx-open .navx__curtain {
  transition: opacity var(--d-mid) var(--e-out),
              visibility 0s linear 0s;
}

/* The index row lifts towards the viewer on hover. It is a translateZ inside
   the list's own perspective, so the row nearest the vanishing point grows
   least and the geometry does the easing for us — a plain scale() would
   treat every row identically and read as a hover state rather than depth. */
.index__link { transition: transform var(--d-mid) var(--e-soft); }
.index__link:hover, .index__link:focus-visible { transform: translateZ(34px); }

/* --- Reveal on scroll ----------------------------------------------------- */
/* `.reveal` is added by main.js; without JS the content is simply visible.
   A calm rise, in keeping with the rest: nothing here should feel urgent. */

.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity var(--d-slow) var(--e-soft), transform var(--d-slow) var(--e-soft);
}
.reveal.is-in { opacity: 1; transform: none; }

/* --- Accent rule ---------------------------------------------------------- */

@keyframes rule-draw {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
.shot-rule { animation: rule-draw 900ms var(--e-soft) 200ms both; }
.hero .shot-rule,
.page-hero .shot-rule,
.page-head .shot-rule {
  display: none;
  animation: none;
}

/* --- Hero title warp ----------------------------------------------------- */
/* Two lines arrive from deep behind the page with a brief horizontal stretch,
   then settle crisply into the typography. The wake and bloom exist for less
   than a second: enough to say "warp", not enough to compete with the copy. */

.title-warp .warp-line { opacity: 1; filter: none; transform: none; }
.title-warp.is-ready .warp-line { opacity: 1; }
.title-warp.is-entered .warp-line {
  animation: title-warp-in 460ms cubic-bezier(0.16, 0.84, 0.18, 1) both;
}
.title-warp.is-entered .warp-line:nth-child(2) { animation-delay: 60ms; }

@keyframes title-warp-in {
  0% {
    opacity: 1;
    filter: none;
    transform: translate3d(-12px, 8px, 0);
  }
  100% {
    opacity: 1;
    filter: none;
    transform: none;
    text-shadow: none;
  }
}

.title-warp__wake,
.title-warp.is-entered .title-warp__wake {
  display: none;
  animation: none;
}
@keyframes title-wake {
  0%   { opacity: 0; transform: translate3d(-20%, 0, 0) scaleX(0.04); }
  28%  { opacity: 0.7; }
  100% { opacity: 0; transform: translate3d(30%, 0, 0) scaleX(1.18); }
}

.title-warp.is-entered .title-warp__flash {
  animation: title-flash 720ms var(--e-out) 70ms both;
}
@keyframes title-flash {
  0%, 100% { opacity: 0; transform: scale(0.72); }
  34%      { opacity: 1; transform: scale(1); }
}

/* Re-time the rule as the clean final beat of the homepage title. */
.title-warp.is-ready + .shot-rule { animation: none; transform: scaleX(0); }
.title-warp.is-entered + .shot-rule {
  animation: rule-draw 680ms var(--e-soft) 480ms both;
}

/* --- The aurora ----------------------------------------------------------- */
/* Five washes on long, mutually prime cycles, so the sky never repeats a
   composition you have already seen. Slow enough to be atmosphere rather
   than animation — you should notice it has changed, not watch it change. */

@keyframes drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(6%, 4%, 0) scale(1.12); }
}
@keyframes drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.06); }
  50%      { transform: translate3d(-7%, 6%, 0) scale(1); }
}
@keyframes drift-c {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(-5%, -6%, 0) scale(1.1); }
}

.aurora__blob--1 { animation: drift-a 31s var(--e-io) infinite; }
.aurora__blob--2 { animation: drift-b 43s var(--e-io) infinite; }
.aurora__blob--3 { animation: drift-c 37s var(--e-io) infinite; }
.aurora__blob--4 { animation: drift-a 53s var(--e-io) infinite reverse; }
.aurora__blob--5 { animation: drift-b 29s var(--e-io) infinite; }
.aurora--quiet .aurora__blob--3 { animation-duration: 51s; }

/* --- Motes ---------------------------------------------------------------- */
/* The one fast thing left. Each mote is still for most of its cycle, then
   crosses in about half a second — quick, but the size of a crumb. This is
   the speed cue the direction keeps, at the volume the reference would. */

@keyframes mote-dash {
  0%,  86%  { transform: translate3d(0, 0, 0); opacity: 0; }
  88%       { opacity: 0; }
  90%       { opacity: 1; }
  99%       { opacity: 0; }
  100%      { transform: translate3d(var(--dx, 320px), var(--dy, -40px), 0); opacity: 0; }
}
.mote { animation: mote-dash var(--dur, 9s) var(--e-io) infinite; }

/* --- Loader --------------------------------------------------------------- */

.loader { transition: opacity var(--d-mid) var(--e-out); }
.loader.is-out { opacity: 0; }

@keyframes loader-mark-in {
  from { opacity: 0; transform: scale(0.9); }
  to   { opacity: 1; transform: none; }
}
.loader__mark { animation: loader-mark-in 620ms var(--e-soft) both; }

@keyframes bloom-breathe {
  0%, 100% { transform: scale(0.94); opacity: 0.8; }
  50%      { transform: scale(1.06); opacity: 1; }
}
.loader__bloom { animation: bloom-breathe 2.4s var(--e-io) infinite; }

/* --- Ticker --------------------------------------------------------------- */
/* The track holds the same list twice; shifting it by exactly half its width
   lands the second copy where the first began, so the loop has no seam. */

@keyframes ticker-run {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.ticker__track { animation: ticker-run 52s linear infinite; }
.ticker:hover .ticker__track { animation-play-state: paused; }

/* --- The rabbit hole ------------------------------------------------------ */
/* /websites/ page head. The timing function on these is an EASE-IN — slow
   while far away, fast as it passes — because that is what falling feels
   like. A linear tunnel is a screensaver; the acceleration is the whole
   effect. See section 45 of main.css. */

@keyframes rabbit-fall {
  0%   { transform: translateZ(-1800px) rotate(0deg)  scale(0.9); opacity: 0; }
  14%  { opacity: 0.85; }
  72%  { transform: translateZ(-360px)  rotate(26deg) scale(1);   opacity: 0.6; }
  100% { transform: translateZ(500px)   rotate(52deg) scale(1.1); opacity: 0; }
}

/* The website rides the same tunnel, but holds near the middle long enough
   to be read as a page before it is pulled through. That pause is the
   "attracting" half — without it there is nothing to fall towards. */
@keyframes rabbit-page {
  0%   { transform: translateZ(-1500px) rotateZ(-6deg); opacity: 0; }
  20%  { opacity: 1; }
  58%  { transform: translateZ(-330px)  rotateZ(-1deg); opacity: 1; }
  76%  { transform: translateZ(-150px)  rotateZ(1deg);  opacity: 0.95; }
  100% { transform: translateZ(620px)   rotateZ(9deg);  opacity: 0; }
}

/* The hole breathes, so the vanishing point is alive rather than a dot. */
@keyframes rabbit-core {
  0%, 100% { opacity: 0.6; transform: translateZ(-560px) scale(0.92); }
  50%      { opacity: 1;   transform: translateZ(-560px) scale(1.12); }
}

/* --- The service scenes --------------------------------------------------- */
/* The three panels under "What we do". All of their idle motion is here, so
   it costs nothing per frame and the block at the bottom of this file stops
   it dead. Slow on purpose: these sit beside body copy, and anything quick
   next to a paragraph is something you have to look away from to read. */

/* The mark turns through +/-38 degrees and comes back. It does NOT go all the
   way round, and that is a branding decision rather than a technical one: past
   90 degrees you are looking at the back of the mask, which is the mark
   MIRRORED. Showing a company's mark in reverse for half of every cycle is the
   one thing a page selling identity work must not do. Thirty-eight degrees is
   enough to see the extrusion and read the object as solid, and the mark is
   the right way round at every frame. */
@keyframes scene-turn {
  0%, 100% { transform: rotateY(-38deg) rotateX(10deg); }
  50%      { transform: rotateY(38deg)  rotateX(4deg); }
}

/* The page draws apart into its layers and settles back into one flat page.
   The pause at the ends is the point: it is a page most of the time and a
   stack of layers briefly, not a permanently exploded diagram. */
@keyframes scene-assemble {
  0%, 18%   { transform: rotateY(-24deg) rotateX(13deg) scale(0.96); }
  46%, 62%  { transform: rotateY(-8deg)  rotateX(4deg)  scale(1); }
  100%      { transform: rotateY(-24deg) rotateX(13deg) scale(0.96); }
}

/* Both together: one slow orbit, so the mark passes in front of the page it
   is applied to and the two read as one arrangement. */
@keyframes scene-orbit {
  from { transform: rotateY(-18deg) rotateX(8deg); }
  50%  { transform: rotateY(18deg)  rotateX(-4deg); }
  to   { transform: rotateY(-18deg) rotateX(8deg); }
}

/* --- Nav panel items ------------------------------------------------------ */
/* The six links arriving from deep perspective on a stagger, below 760px.
   An animation rather than a transition, so the menu's resting state is
   "present" and this is decoration over it — see the note in main.css. */

@keyframes nav-item-in {
  from { transform: translate3d(0, 12px, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* --- Drift view ----------------------------------------------------------- */
/* .word.is-out is gone with the effect that used it: clicking a word no
   longer throws the other thirteen 900px off-screen and teleports them home.
   The words are simulated now, so nothing about them is a transition —
   drift.js writes their transforms every frame. See the note there. */

/* ==========================================================================
   Reduced motion — the off switch.
   Under this query the site is a clean, static, fully usable page: no loader,
   no drifting sky, no motes, no reveals, no scene or Drift pointer physics.
   The mouse-head pointer itself still tracks direct input; main.js keeps the
   motion-heavy subscribers switched off.

   The aurora itself STAYS — it is a still image of a sky, and a still image
   is not motion. Only its drift stops.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }

  .reveal { opacity: 1; transform: none; }
  .shot-rule { animation: none; transform: none; }
  .title-warp.is-ready .warp-line { opacity: 1; filter: none; transform: none; }
  .title-warp__wake, .title-warp__flash { display: none; }
  .aurora__blob { animation: none; }
  .page-hero__cue::before { animation: none; }
  .mote { display: none; }
  /* The stage keeps its depth but stops responding to the pointer — scene.js
     reads the same query and never subscribes. */
  .hero__scene { transform: none; }
  .ticker__track { animation: none; }
  .loader__mark, .loader__bloom { animation: none; }
  .desk__lid { transform: rotateX(8deg); }
  .desk__typed { width: calc(var(--ch, 10) * 0.62em); }
  .desk__caret { opacity: 0.7; }
  .desk__pointer { opacity: 0; }
  .keep .cube {
    animation: none;
    opacity: 1;
    transform: rotateX(-28deg) rotateY(36deg);
  }
  .cubes { animation: none; transform: none; }
  .page-art--emblem img { animation: none; transform: none; }

  /* Added with the interaction pass. The header still takes its ground and
     the progress line still reports position — both are information about
     where you are, not decoration — but nothing travels, rises or tilts. */
  .display.is-split .ln__i { transform: none; opacity: 1; }
  .stg { opacity: 1; transform: none; }
  .header.is-tucked { transform: none; }
  .progress { transition: none; }
  .project__media { transform: none !important; }
  .project__media::after { display: none; }
  .promise:hover::before,
  .creds li:hover,
  .bucket:hover .bucket__num { transform: none; }
}

/* Project previews. A 2px lift on hover and nothing else — the grid is the
   largest thing on the page and anything more reads as a slideshow. */
.project__media { transition: transform var(--d-mid) var(--e-out); }
.project:hover .project__media,
.project:focus-within .project__media { transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) {
  .project__media { transition: none; }
  .project:hover .project__media,
  .project:focus-within .project__media { transform: none; }
}

/* --- Header, progress, split lines, stagger -------------------------------- */

.header { transition: transform var(--d-mid) var(--e-out), border-color var(--d-mid) var(--e-out); }
.header::before, .header::after { transition: opacity var(--d-mid) var(--e-out); }
.progress { transition: transform 90ms linear; }

/* Each authored line rises from behind its own mask, one after the next.
   --l is the line index, set by main.js. */
.display.is-split .ln__i {
  transform: none;
  opacity: 1;
  transition: transform 420ms var(--e-soft), opacity 320ms var(--e-out);
  transition-delay: calc(var(--l, 0) * 40ms);
}
.display.is-split.is-lit .ln__i { transform: none; opacity: 1; }

/* Staggered children. The section fades; these travel. */
.stg {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 620ms var(--e-out), transform 620ms var(--e-soft);
  transition-delay: calc(var(--i, 0) * 62ms);
}
[data-reveal].is-in .stg { opacity: 1; transform: none; }

.reveal--group { transform: none; }
.reveal--group.is-in { transform: none; }

/* Surface hovers */
.promise::before { transition: transform var(--d-mid) var(--e-out); }
.creds li { transition: transform var(--d-fast) var(--e-out), border-color var(--d-fast) var(--e-out); }
.qs li { transition: border-left-color var(--d-mid) var(--e-out); }
.bucket__num { transition: transform var(--d-mid) var(--e-out); }
.link { transition: background-size var(--d-mid) var(--e-out), color var(--d-fast) var(--e-out); }
.project__media::after { transition: opacity var(--d-mid) var(--e-out); }
.project__media { transition: box-shadow var(--d-mid) var(--e-out); }

/* The ticker, once scroll.js has taken it over. The CSS marquee is the
   baseline and must stop, or the two would fight over the same transform. */
.ticker.is-live .ticker__track {
  animation: none;
  /* scroll.js drives --tx and --skew. The cylinder rule in main.css replaces
     the skew with a roll on the same two values. */
  transform: translate3d(var(--tx, 0px), 0, 0) skewX(var(--skew, 0deg));
}

/* --- Websites desk: lid opens, page loads, then someone types ----------- */
/* Resting form lives in main.css (lid up, headline complete). This file
   plays the sequence over it. Reduced motion at the bottom of this file
   collapses every animation to 1ms, so the still laptop is what remains. */

@keyframes desk-open {
  from { transform: rotateX(78deg) scaleY(0.18); opacity: 0.4; }
  to   { transform: rotateX(8deg) scaleY(1); opacity: 1; }
}
@keyframes desk-url {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@keyframes desk-site {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes desk-type {
  from { width: 0; }
  to   { width: calc(var(--ch, 10) * 0.62em); }
}
@keyframes desk-caret {
  0%, 46% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@keyframes desk-copy {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes desk-pointer {
  0%   { opacity: 0; transform: translate(72%, 18%); }
  12%  { opacity: 1; transform: translate(64%, 16%); }
  28%  { opacity: 1; transform: translate(14%, 34%); }
  72%  { opacity: 1; transform: translate(calc(14% + var(--ch, 10) * 3.2%), 34%); }
  86%  { opacity: 1; transform: translate(22%, 72%); }
  100% { opacity: 1; transform: translate(22%, 72%); }
}
@keyframes desk-keys {
  0%, 100% { opacity: 0.35; filter: brightness(1); }
  8%, 16%, 30%, 42%, 54%, 66% { opacity: 1; filter: brightness(1.55); }
  12%, 24%, 36%, 48%, 60%, 80% { opacity: 0.4; filter: brightness(1); }
}

.desk__lid {
  animation: desk-open 1180ms var(--e-out) 80ms both;
}
.desk__url-fill {
  transform-origin: 0 50%;
  animation: desk-url 620ms var(--e-soft) 980ms both;
}
.desk__shot {
  animation: desk-site 720ms var(--e-soft) 1180ms both;
}
.desk__site {
  animation: desk-site 520ms var(--e-soft) 1180ms both;
}
.desk__typed {
  width: 0;
  animation: desk-type 1680ms steps(12, end) 1880ms both;
}
.desk__caret {
  opacity: 0;
  animation: desk-caret 1s steps(1) 1880ms infinite;
}
.desk__copy,
.desk__btn {
  animation: desk-copy 480ms var(--e-soft) 3680ms both;
}
.desk__pointer {
  opacity: 0;
  animation: desk-pointer 4.6s var(--e-soft) 1.15s both;
}
.desk__keys {
  animation: desk-keys 1.7s var(--e-io) 1.88s 1;
}

/* --- Contact keep: cubes fall into a gatehouse ------------------------- */
/* Resting pose is the isometric cube. The drop is a translate in front
   of that rotation, so the faces stay a cube on the way down. */

@keyframes keep-drop {
  from {
    opacity: 0;
    transform:
      translateY(-200px)
      rotateX(-28deg) rotateY(36deg)
      rotateZ(var(--spin, 8deg));
  }
  72% {
    opacity: 1;
    transform:
      translateY(6px)
      rotateX(-28deg) rotateY(36deg)
      rotateZ(calc(var(--spin, 8deg) * 0.12));
  }
  86% {
    transform:
      translateY(-3px)
      rotateX(-28deg) rotateY(36deg);
  }
  to {
    opacity: 1;
    transform: rotateX(-28deg) rotateY(36deg);
  }
}

.keep .cube {
  animation: keep-drop 640ms var(--e-out) calc(160ms + var(--i, 0) * 130ms) both;
}

@keyframes cubes-idle {
  from { transform: translateY(0) rotateY(-7deg); }
  50%  { transform: translateY(-8px) rotateY(7deg); }
  to   { transform: translateY(0) rotateY(-7deg); }
}
.cubes {
  animation: cubes-idle 8.5s var(--e-io) infinite;
}

@keyframes emblem-float {
  from, to { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
.page-art--emblem img {
  animation: emblem-float 4.8s var(--e-io) infinite;
}

/* Spatial experience refinements. */
.depth-surface {
  transition: transform 560ms var(--e-soft),
              border-color var(--d-mid) var(--e-out),
              box-shadow 560ms var(--e-out);
}
.depth-surface::after { transition: opacity var(--d-mid) var(--e-out); }
.index__link::after,
.index__meta { transition: opacity var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-soft); }
.sound-control { transition: transform var(--d-fast) var(--e-out), border-color var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out); }
.sound-control:hover { transform: translateY(-3px); }
.install-control { transition: transform var(--d-fast) var(--e-out), border-color var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out); }
.install-control:hover { transform: translateY(-3px); }
.install-help { transition: opacity var(--d-mid) var(--e-out), visibility var(--d-mid), transform var(--d-mid) var(--e-soft); }
.field input, .field select, .field textarea {
  transition: border-color var(--d-fast) var(--e-out),
              box-shadow 280ms var(--e-out),
              background-color 280ms var(--e-out),
              transform 280ms var(--e-out);
}
.checks__thumb {
  transition: transform 340ms var(--e-out), width 340ms var(--e-out),
              height 340ms var(--e-out), opacity 200ms var(--e-out);
}
.lang { transition: transform 180ms var(--e-out); }
html.is-inverse .detail,
html.is-inverse .form {
  transition: transform 280ms var(--e-out), box-shadow 280ms var(--e-out);
}
.experience-ready .depth-surface.is-depth-active,
.experience-ready [data-reveal].is-in .depth-surface.is-depth-active {
  transform: perspective(1100px) rotateX(var(--depth-rx, 0deg)) rotateY(var(--depth-ry, 0deg)) translateZ(8px);
}

@keyframes cue-pulse {
  0%, 100% { transform: scaleY(1); opacity: 0.65; }
  50% { transform: scaleY(1.22); opacity: 1; }
}
.page-hero__cue::before { animation: cue-pulse 2.6s var(--e-io) infinite; }

@keyframes found-in {
  from { opacity: 0; transform: translateY(28px) rotate(calc(var(--r, 0deg) - 10deg)); }
  to   { opacity: 1; transform: rotate(var(--r, 0deg)); }
}
@keyframes deal-in {
  from { opacity: 0; transform: translate3d(-18px, 14px, 0) rotate(-8deg); }
  to   { opacity: 1; transform: none; }
}
