/* ══════════════════════════════════════════════════════════════════
   LAYMON GAMES — motion.css

   Deliberately small. The site is a reading experience, so only three
   things move: the cursor, the reveal system, and the process rail.
   Every ambient loop that used to run forever has been removed, which
   also removes the constant full-screen repaints.
   ══════════════════════════════════════════════════════════════════ */

/* ──────────────── CURSOR ──────────────── */
/* Physical top-left origin on purpose: the ring and dot are absolutely
   positioned inside this zero-size box and placed with translate3d()
   from clientX/clientY. An inline-start origin sits on the right edge in
   Arabic and throws the whole cursor off-screen. */
.cursor{
  position:fixed; top:0; left:0; width:0; height:0;
  z-index:1500; pointer-events:none; mix-blend-mode:difference;
  transition:opacity .3s;
}
html.has-cursor, html.has-cursor *{ cursor:none!important }

.cursor__dot{
  position:absolute; width:6px; height:6px; margin:-3px; border-radius:50%;
  background:var(--gold-hi);
}
.cursor__ring{
  position:absolute; width:34px; height:34px; margin:-17px; border-radius:50%;
  border:1px solid rgba(243,199,100,.65);
  transition:width .35s cubic-bezier(.16,1,.3,1),
             height .35s cubic-bezier(.16,1,.3,1),
             margin .35s cubic-bezier(.16,1,.3,1),
             border-color .3s, background .3s;
}
.cursor.is-link .cursor__ring{ width:52px; height:52px; margin:-26px; border-color:var(--gold-hi) }
.cursor.is-hidden{ opacity:0 }

@media (hover:none), (pointer:coarse){ .cursor{ display:none } }

/* ──────────────── REVEAL SYSTEM ──────────────── */
.js [data-reveal]{ opacity:0 }
.js [data-reveal="up"]{ transform:translate3d(0,32px,0) }
.js [data-reveal="scale"]{ transform:scale(.965) }
.js [data-reveal].is-in{
  opacity:1; transform:none;
  transition:opacity .8s cubic-bezier(.16,1,.3,1) var(--d,0s),
             transform .95s cubic-bezier(.16,1,.3,1) var(--d,0s);
}

/* ──────────────── PROCESS RAIL ──────────────── */
@keyframes flowDrawX{ from{ transform:scaleX(0) } to{ transform:scaleX(1) } }
@keyframes flowDrawY{ from{ transform:scaleY(0) } to{ transform:scaleY(1) } }

/* desktop: one horizontal rail spanning the first three gaps */
.js [data-reveal="flow"].is-in::before{
  animation:flowDrawX 1.15s cubic-bezier(.16,1,.3,1) .12s both;
}
/* the dots arrive in step with the rail */
.js [data-reveal="flow"] .flow__dot{ opacity:0; transform:scale(.4) }
.js [data-reveal="flow"].is-in .flow__dot{
  opacity:1; transform:none;
  transition:opacity .4s ease calc(.18s + var(--i,0) * 160ms),
             transform .5s cubic-bezier(.16,1,.3,1) calc(.18s + var(--i,0) * 160ms);
}

@media (max-width:900px){
  /* narrow: one short line per step, drawn top to bottom */
  .js [data-reveal="flow"] .flow__step::after{ transform:scaleY(0) }
  .js [data-reveal="flow"].is-in .flow__step::after{
    animation:flowDrawY .9s cubic-bezier(.16,1,.3,1) calc(var(--i,0) * 110ms) both;
  }
}

/* ──────────────── SMALL MOTION ──────────────── */
@keyframes cueDrop{ 0%{ transform:translateY(-120%) } 100%{ transform:translateY(140%) } }
.hero__cue i{ position:relative; overflow:hidden }
.hero__cue i::after{
  content:''; position:absolute; inset-inline:0; inset-block-start:0;
  block-size:100%; background:var(--gold);
  animation:cueDrop 2.6s cubic-bezier(.6,0,.4,1) infinite;
}

@keyframes caretBlink{ 0%,49%{ opacity:1 } 50%,100%{ opacity:0 } }
.term__out .caret{
  display:inline-block; inline-size:.6em; block-size:1.05em;
  vertical-align:-.18em;
  background:var(--gold); opacity:.85;
}
.term__out.is-typing .caret{ animation:caretBlink 1s steps(1) infinite }

/* ──────────────── ART DIRECTION ────────────────
   One idea runs through this block: gold behaves like a current. It
   sweeps across the wordmark once on load, it travels along the top
   progress bar for as long as the reader keeps reading, and it runs down
   the process rail as that rail draws. Everything else is local and
   quiet — a lift, a nudge, a sheen — so that those three moments read.

   Depth uses the independent translate/scale properties rather than
   transform, because the reveal system already owns transform on several
   of the same elements. The two compose instead of overwriting.
   ─────────────────────────────────────────────── */

/* -- entrance: the hero assembles once, in reading order ------------ */
@keyframes fadeIn{ from{ opacity:0 } to{ opacity:1 } }
@keyframes riseIn{ from{ opacity:0; transform:translate3d(0,18px,0) } to{ opacity:1; transform:none } }
@keyframes mascotIn{
  from{ opacity:0; transform:translate3d(0,16px,0) scale(.88) }
  to  { opacity:1; transform:none }
}
@keyframes titleSheen{
  from{ background-position:171.4% 0, 0 0 }
  to  { background-position:-70% 0, 0 0 }
}

.mascot{ animation:mascotIn 1.15s cubic-bezier(.16,1,.3,1) .05s both }
.hero__grid{ animation:fadeIn 1.4s ease .15s both }
.hero__halo{ animation:fadeIn 1.9s ease .25s both }
.hero__kicker{ animation:riseIn .85s cubic-bezier(.16,1,.3,1) .3s both }
.hero__title{ animation:riseIn 1s cubic-bezier(.16,1,.3,1) .38s both,
                         titleSheen 1.6s cubic-bezier(.4,0,.2,1) .4s both }
.hero__lede{ animation:riseIn .9s cubic-bezier(.16,1,.3,1) .5s both }
.hero__btns{ animation:riseIn .9s cubic-bezier(.16,1,.3,1) .6s both }

/* -- depth: the hero recedes at four speeds ------------------------- */
@media (min-width:901px) and (hover:hover) and (pointer:fine){
  /* foreground leaves upward and shrinks, background sinks downward */
  html.has-depth .hero__in{ translate:0 calc(var(--hp,0) * -70px) }
  html.has-depth .mascot{ translate:0 calc(var(--hp,0) * -46px);
                         scale:calc(1 - var(--hp,0) * .09) }
  html.has-depth .hero__halo{ translate:0 calc(var(--hp,0) * 76px) }
  html.has-depth .hero__field{ translate:0 calc(var(--hp,0) * 112px) }
  html.has-depth .hero__grid{ translate:0 calc(var(--hp,0) * 152px) }

  html.has-depth .hero__in{ opacity:calc(1 - var(--hp,0)) }
  html.has-depth .hero__halo{ opacity:calc(1 - var(--hp,0) * .78) }
  html.has-depth .hero__field{ opacity:calc(.42 * (1 - var(--hp,0))) }
  html.has-depth .hero__grid{ opacity:calc(1 - var(--hp,0)) }

  /* three depth pairs, each read against the section's own travel */
  html.has-depth #about .about__frame{ translate:0 calc(var(--sp,0) * -15px) }
  html.has-depth #projects .feature__art{ translate:0 calc(var(--sp,0) * 20px) }
  html.has-depth #projects .feature__body{ translate:0 calc(var(--sp,0) * -13px) }
  html.has-depth #skills .skill--lead .term{ translate:0 calc(var(--sp,0) * 12px) }
}

/* -- WOW 1 · the current in the progress bar ------------------------ */
.progress i{ box-shadow:0 0 10px -1px rgba(216,164,60,.5) }
/* the fill grows from the inline-start edge in both directions, so the
   head always belongs on the inline-end edge */
.progress i::after{
  content:''; position:absolute;
  inset-block:0; inset-inline-end:-1px; inline-size:2px;
  background:var(--gold-hi);
  box-shadow:0 0 8px 1px rgba(243,199,100,.85);
  opacity:0; transition:opacity .45s ease;
}
html.has-scrolled .progress i::after{ opacity:1 }
html[dir="rtl"] .progress i{
  background:linear-gradient(270deg, var(--gold-dk), var(--gold-hi));
}

/* -- WOW 2 · the current running down the process rail ------------- */
/* It rides inset-inline-start, so it runs right-to-left in Arabic with no
   second keyframe and no RTL override. */
.flow::after{
  content:''; position:absolute;
  inset-block-start:0; inset-inline-start:0;
  inline-size:7px; block-size:7px; margin-inline-start:-3px;
  border-radius:50%;
  background:var(--gold-hi);
  box-shadow:0 0 10px 1px rgba(243,199,100,.7);
  opacity:0;
}
.js [data-reveal="flow"].is-in::after{
  animation:flowCurrent 1.15s cubic-bezier(.45,0,.25,1) .12s both;
}
@keyframes flowCurrent{
  0%  { inset-inline-start:var(--flow-dot,0px); opacity:0 }
  8%  { opacity:1 }
  92% { opacity:1 }
  100%{ inset-inline-start:calc(var(--flow-dot,0px) + var(--flow-col) * 3 + var(--flow-gap) * 3);
        opacity:0 }
}
/* the dots now land as the current reaches them */
.js [data-reveal="flow"].is-in .flow__dot{
  transition:opacity .4s ease calc(.24s + var(--i,0) * 290ms),
             transform .5s cubic-bezier(.16,1,.3,1) calc(.24s + var(--i,0) * 290ms);
}
@media (max-width:900px){ .flow::after{ content:none } }

/* -- WOW 3 · section titles wiping in from the reading edge --------- */
/* Driven by the parent .shead's is-in, so no extra observer and no
   markup change; clip-path is reset wholesale under reduced motion. */
.js .shead__title{ clip-path:inset(0 100% 0 0) }
.js .shead.is-in .shead__title{
  clip-path:inset(0 -10% 0 0);
  transition:clip-path 1s cubic-bezier(.16,1,.3,1) .1s;
}
html[dir="rtl"] .js .shead__title{ clip-path:inset(0 0 0 100%) }
html[dir="rtl"] .js .shead.is-in .shead__title{ clip-path:inset(0 0 0 -10%) }

/* -- reveal: content rises from the reading edge, not the middle ---- */
.js [data-reveal="up"]{ transform:translate3d(-14px,32px,0) }
html[dir="rtl"] .js [data-reveal="up"]{ transform:translate3d(14px,32px,0) }

/* -- the about frame lights its top edge once ----------------------- */
.about__frame::after{
  content:''; position:absolute; inset-block-start:-1px; inset-inline:0;
  block-size:1px; background:var(--gold);
  transform:scaleX(0); transform-origin:left center;
}
html[dir="rtl"] .about__frame::after{ transform-origin:right center }
.js .about__figure.is-in .about__frame::after{
  transform:none;
  transition:transform 1.1s cubic-bezier(.16,1,.3,1) .38s;
}

/* -- buttons: a single specular pass, then press ------------------- */
.btn::after{
  content:''; position:absolute; inset-block:0;
  inset-inline-start:-42%; inline-size:42%;
  background:linear-gradient(90deg, transparent, rgba(255,252,240,.62), transparent);
  transform:translateX(0) skewX(-18deg);
  opacity:0; pointer-events:none;
  --bx:1;
}
html[dir="rtl"] .btn::after{ --bx:-1 }
.btn--gold:hover::after{ animation:btnSheen .8s cubic-bezier(.35,0,.2,1) both }
@keyframes btnSheen{
  from{ transform:translateX(0) skewX(-18deg) }
  to  { transform:translateX(calc(var(--bx) * 350%)) skewX(-18deg) }
}
.btn:active{ transform:scale(.975) }

/* -- projects: thumb breathes, arrow leads -------------------------- */
.row__art img{ transition:transform .55s cubic-bezier(.16,1,.3,1) }
.row:hover .row__art img{ transform:scale(1.07) }
.row__go{ --nudge:5px; transition:transform .3s cubic-bezier(.16,1,.3,1), color .3s ease }
html[dir="rtl"] .row__go{ --nudge:-5px }
.row:hover .row__go{ transform:translateX(var(--nudge)) }

.feature__art img{ transition:transform .85s cubic-bezier(.16,1,.3,1) }
.feature:hover .feature__art img{ transform:scale(1.035) }

/* -- contact: the confirmation is felt as well as read -------------- */
.reach__mail{ --bx:1 }
html[dir="rtl"] .reach__mail{ --bx:-1 }
.reach__mail::after{
  content:''; position:absolute; inset-block:0;
  inset-inline-start:-32%; inline-size:32%;
  background:linear-gradient(90deg, transparent, rgba(243,199,100,.24), transparent);
  transform:translateX(0); opacity:0; pointer-events:none;
}
.reach__mail.is-copied::after{ animation:reachSweep .8s cubic-bezier(.35,0,.2,1) both }
@keyframes reachSweep{
  0%  { opacity:0; transform:translateX(0) }
  18% { opacity:1 }
  100%{ opacity:0; transform:translateX(calc(var(--bx) * 430%)) }
}
.reach__addr{ transition:color .45s ease }
.reach__mail.is-copied .reach__addr{ color:var(--gold-hi) }
.reach__mail.is-copied{ border-color:var(--line) }

/* -- nav: the mark acknowledges, the bar firms up ------------------- */
.nav__mark img{ transition:transform .55s cubic-bezier(.16,1,.3,1) }
.nav__brand:hover .nav__mark img{ transform:rotate(-13deg) scale(1.07) }
html.has-scrolled .nav{ background:rgba(9,8,6,.88) }

@media (prefers-reduced-motion:reduce){
  .js [data-reveal]{ opacity:1!important; transform:none!important }
  .js [data-reveal="flow"] .flow__dot{ opacity:1!important; transform:none!important }
  .js [data-reveal="flow"].is-in::before,
  .js [data-reveal="flow"].is-in .flow__step::after{ animation:none }
  .hero__cue i::after{ animation:none; opacity:.45 }
  .term__out.is-typing .caret{ animation:none }
  .cursor{ display:none }

  /* entrance would be the first thing a motion-sensitive reader sees */
  .mascot,.hero__grid,.hero__halo,.hero__kicker,
  .hero__title,.hero__lede,.hero__btns{ animation:none!important }
  /* and the resting wordmark must match the sheen'd one exactly */
  .hero__title{ background-position:-70% 0, 0 0 }

  .js .shead__title,
  html[dir="rtl"] .js .shead__title,
  .js .shead.is-in .shead__title,
  html[dir="rtl"] .js .shead.is-in .shead__title{ clip-path:none }
  .flow::after{ content:none }
  .progress i::after{ opacity:0 }
  .about__frame::after,
  html[dir="rtl"] .about__frame::after{ transform:none }
  .btn::after,.reach__mail::after{ content:none }

  /* decorative motion is the first thing to go, not the only thing */
  .btn:active{ transform:none }
  .row:hover .row__art img,
  .feature:hover .feature__art img,
  .nav__brand:hover .nav__mark img{ transform:none }
  .row:hover .row__go{ transform:none }
  .reach__mail.is-copied .reach__addr{ color:var(--cream) }
}

@media (hover:none){
  /* every decorative hover below duplicates a state that a phone has no
     way to leave, so the highlights simply do not arm */
  .btn--gold:hover::after{ animation:none }
  .row:hover .row__art img,
  .feature:hover .feature__art img,
  .nav__brand:hover .nav__mark img{ transform:none }
  .row:hover .row__go{ transform:none }
  .reach__mail.is-copied::after{ animation:none }
}
