/* =========================================================================
   MOTION LAYER — Simon Selvam
   ---------------------------------------------------------------------
   Additive only. Adds no colours, fonts, spacing or layout of its own —
   it animates what style.css already draws. Safe to delete this file and
   motion.js together and the site returns exactly to its previous state.

   Performance contract: scroll-driven work uses transform / opacity /
   clip-path only. filter:blur() appears in exactly two one-shot places
   (loader, hero portrait entrance) and never on a scroll or rAF loop.
   ========================================================================= */

/* Everything here is opt-in via .motion-on, set by motion.js only when
   the device and the user's settings allow it. No JS → no changes. */

/* =========================================================
   0 · LOADER
   ========================================================= */
#loader{
  position:fixed;inset:0;z-index:9999;
  display:flex;align-items:center;justify-content:center;flex-direction:column;
  gap:22px;
  background:var(--bg);
  transition:opacity .55s var(--ease), visibility 0s linear .55s;
}
#loader.done{opacity:0;visibility:hidden;pointer-events:none}
.ld-mark{
  font-family:var(--serif);font-size:clamp(1.6rem,4vw,2.2rem);font-weight:400;
  letter-spacing:-.02em;color:var(--ink);
  opacity:0;transform:translateY(8px) scale(.985);
  filter:blur(9px);                     /* one-shot only */
  animation:ldIn .85s var(--ease) .1s forwards;
}
.ld-mark span{color:var(--hi)}
@keyframes ldIn{to{opacity:1;transform:none;filter:blur(0)}}
.ld-bar{
  width:min(180px,42vw);height:1px;
  background:var(--line-2);overflow:hidden;
  opacity:0;animation:ldFade .4s var(--ease) .25s forwards;
}
.ld-bar i{
  display:block;height:100%;width:100%;
  background:var(--ink);
  transform:scaleX(var(--p,0));transform-origin:left;
  transition:transform .3s var(--ease);
}
@keyframes ldFade{to{opacity:1}}
.no-js #loader{display:none}

/* =========================================================
   1 · PAGE / BACKGROUND — very slow ambient drift
   ========================================================= */
.motion-on .amb{
  position:fixed;inset:0;z-index:0;pointer-events:none;
  overflow:hidden;
}
.motion-on .amb i{
  position:absolute;display:block;border-radius:50%;
  filter:blur(90px);opacity:.28;
  will-change:transform;
}
.motion-on .amb i:nth-child(1){
  width:46vw;height:46vw;left:-8vw;top:-10vw;
  background:radial-gradient(circle,var(--hi),transparent 70%);
  animation:drift1 46s ease-in-out infinite;
}
.motion-on .amb i:nth-child(2){
  width:38vw;height:38vw;right:-6vw;bottom:-8vw;
  background:radial-gradient(circle,#A9B89D,transparent 70%);
  animation:drift2 58s ease-in-out infinite;
}
html[data-theme="dark"] .motion-on .amb i{opacity:.14}
@keyframes drift1{
  0%,100%{transform:translate3d(0,0,0) scale(1)}
  50%{transform:translate3d(4vw,3vh,0) scale(1.08)}
}
@keyframes drift2{
  0%,100%{transform:translate3d(0,0,0) scale(1)}
  50%{transform:translate3d(-3vw,-4vh,0) scale(1.06)}
}

/* =========================================================
   2 · CUSTOM CURSOR — fine pointers only
   ========================================================= */
.cur{
  position:fixed;top:0;left:0;z-index:9998;
  pointer-events:none;
  border-radius:50%;
  transform:translate3d(-100px,-100px,0);
  will-change:transform;
}
.cur-dot{
  width:6px;height:6px;margin:-3px 0 0 -3px;
  background:var(--ink);
  transition:opacity .25s var(--ease), transform .12s var(--ease);
}
.cur-ring{
  width:34px;height:34px;margin:-17px 0 0 -17px;
  border:1px solid var(--ink);
  opacity:.45;
  transition:width .28s var(--ease), height .28s var(--ease),
             margin .28s var(--ease), opacity .28s var(--ease),
             border-color .28s var(--ease);
}
/* grow over interactive targets */
.cur-ring.is-lg{width:58px;height:58px;margin:-29px 0 0 -29px;opacity:.7}
.cur-dot.is-sm{transform:scale(.4)}
/* hide the native cursor only when ours is live */
.motion-on.cursor-on,
.motion-on.cursor-on a,
.motion-on.cursor-on button,
.motion-on.cursor-on [role="button"]{cursor:none}

/* =========================================================
   3 · SCROLL ENTRANCES — one variant per section
   Applied by motion.js via [data-anim] on existing sections;
   no markup changes. All compositor-only.
   ========================================================= */
.motion-on [data-anim] .reveal{transition-duration:.85s}

/* about — rise + settle */
.motion-on [data-anim="rise"] .reveal{transform:translateY(30px) scale(.985)}
.motion-on [data-anim="rise"] .reveal.in{transform:none}

/* skills / tools — soft depth */
.motion-on [data-anim="depth"] .reveal{transform:translateY(20px) scale(.96)}
.motion-on [data-anim="depth"] .reveal.in{transform:none}

/* work — clip-path curtain */
.motion-on [data-anim="curtain"] .reveal{
  clip-path:inset(0 0 14% 0);
  transform:translateY(24px);
  transition-property:opacity,transform,clip-path;
}
.motion-on [data-anim="curtain"] .reveal.in{clip-path:inset(0 0 0 0);transform:none}

/* experience — enter from the side */
.motion-on [data-anim="side"] .reveal{transform:translate3d(-22px,10px,0)}
.motion-on [data-anim="side"] .reveal.in{transform:none}

/* services — scale up */
.motion-on [data-anim="pop"] .reveal{transform:scale(.94) translateY(16px)}
.motion-on [data-anim="pop"] .reveal.in{transform:none}

/* contact — plain lift, keeps the form calm */
.motion-on [data-anim="lift"] .reveal{transform:translateY(26px)}
.motion-on [data-anim="lift"] .reveal.in{transform:none}

/* =========================================================
   4 · TEXT — character stagger (hero) + line rise
   .c spans are produced by motion.js from the existing .w words
   ========================================================= */
.motion-on .c{
  display:inline-block;
  opacity:0;
  transform:translateY(.36em) rotate(1.2deg);
  transition:opacity .5s var(--ease) calc(var(--ci,0) * 22ms),
             transform .62s cubic-bezier(.22,1,.36,1) calc(var(--ci,0) * 22ms);
}
.motion-on .in .c,
.motion-on .reveal.in .c,
.motion-on .c.done{opacity:1;transform:none}

/* =========================================================
   5 · BUTTONS — magnetic, glow, arrow, ripple, press
   ========================================================= */
.motion-on .btn,
.motion-on .pill-cta,
.motion-on .nav-text-btn{
  position:relative;overflow:hidden;
  transform:translate3d(var(--mx,0),var(--my,0),0);
  transition:transform .35s cubic-bezier(.22,1,.36,1),
             box-shadow .35s var(--ease), border-color .3s var(--ease);
}
.motion-on .btn:hover{box-shadow:0 8px 24px -10px rgba(23,21,15,.42)}
html[data-theme="dark"] .motion-on .btn:hover{box-shadow:0 8px 24px -10px rgba(0,0,0,.7)}
.motion-on .btn .ic{transition:transform .35s cubic-bezier(.22,1,.36,1)}
.motion-on .btn:hover .ic{transform:translateX(4px)}
/* icon buttons rotate rather than slide */
.motion-on .icon-btn .ic{transition:transform .4s cubic-bezier(.22,1,.36,1)}
.motion-on .icon-btn:hover .ic{transform:rotate(12deg) scale(1.12)}

/* ripple — one span injected on click, removed on animationend */
.rip{
  position:absolute;border-radius:50%;
  transform:translate(-50%,-50%) scale(0);
  background:currentColor;opacity:.18;
  pointer-events:none;
  animation:rip .55s var(--ease) forwards;
}
@keyframes rip{to{transform:translate(-50%,-50%) scale(1);opacity:0}}

/* =========================================================
   6 · CARDS — tilt, lift, shine, border glow
   ========================================================= */
.motion-on .card,
.motion-on .tool{
  transform:perspective(900px)
            rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
            translate3d(0,var(--ty,0),0);
  transition:transform .5s cubic-bezier(.22,1,.36,1),
             box-shadow .45s var(--ease), border-color .45s var(--ease);
  will-change:auto;
}
.motion-on .card.tilting,
.motion-on .tool.tilting{transition:transform .12s linear, box-shadow .45s var(--ease)}
.motion-on .card:hover,
.motion-on .tool:hover{
  --ty:-5px;
  box-shadow:0 22px 48px -22px rgba(23,21,15,.4);
  border-color:var(--line-2);
}
html[data-theme="dark"] .motion-on .card:hover,
html[data-theme="dark"] .motion-on .tool:hover{
  box-shadow:0 22px 48px -22px rgba(0,0,0,.75);
}
.motion-on .tool .logo{transition:transform .45s cubic-bezier(.22,1,.36,1)}
.motion-on .tool:hover .logo{transform:scale(1.09) rotate(-3deg)}

/* shine sweep across project cards */
.motion-on .card::after{
  content:"";position:absolute;inset:0;z-index:3;
  pointer-events:none;border-radius:inherit;
  background:linear-gradient(105deg,transparent 38%,
             color-mix(in srgb,#fff 26%,transparent) 50%,transparent 62%);
  transform:translateX(-120%);
  transition:transform .85s var(--ease);
  opacity:0;
}
.motion-on .card:hover::after{transform:translateX(120%);opacity:1}
html[data-theme="dark"] .motion-on .card::after{
  background:linear-gradient(105deg,transparent 38%,
             color-mix(in srgb,#fff 10%,transparent) 50%,transparent 62%);
}
/* image zoom inside cards */
.motion-on .card img{transition:transform .8s cubic-bezier(.22,1,.36,1)}
.motion-on .card:hover img{transform:scale(1.045)}

/* =========================================================
   7 · NAVBAR — glass, shrink, hide on scroll down
   ========================================================= */
.motion-on #nav{
  transition:transform .45s cubic-bezier(.22,1,.36,1),
             padding .35s var(--ease), top .35s var(--ease),
             box-shadow .35s var(--ease), border-color .35s var(--ease),
             backdrop-filter .35s var(--ease);
  will-change:transform;
}
.motion-on #nav.nav-hide{transform:translate3d(-50%,-140%,0)}
.motion-on #nav.scrolled{backdrop-filter:blur(18px) saturate(160%)}

/* =========================================================
   8 · FORM — focus glow, valid/invalid
   ========================================================= */
.motion-on .cf-field input,
.motion-on .cf-field textarea{
  transition:border-color .28s var(--ease), box-shadow .28s var(--ease),
             background .28s var(--ease);
}
.motion-on .cf-field input:focus,
.motion-on .cf-field textarea:focus{
  box-shadow:0 0 0 4px color-mix(in srgb,var(--hi) 22%,transparent);
}

/* =========================================================
   9 · PORTRAIT / IMAGES — one-shot blur-to-sharp on entrance
   ========================================================= */
/* Mouse parallax combined with style.css's scroll parallax (--sy).
   Both must live in ONE transform or the later rule wins and the other
   silently stops working. No transition here — the rAF lerp smooths it. */
@media (pointer:fine){
  .motion-on .hero .portrait{
    transform:translate3d(var(--px,0px),
                          calc(var(--py,0px) + var(--sy,0) * -.035px), 0);
    will-change:transform;
  }
}
.motion-on .portrait img{
  clip-path:inset(0 0 0 0);
  transition:transform 1.1s cubic-bezier(.22,1,.36,1);
}
.motion-on .hero-right.in .portrait img{animation:sharpen .9s var(--ease) both}
@keyframes sharpen{
  from{filter:blur(12px);transform:scale(1.05)}
  to{filter:blur(0);transform:none}
}

/* =========================================================
   10 · FOOTER + back-to-top
   ========================================================= */
.motion-on .soc{transition:transform .35s cubic-bezier(.22,1,.36,1), color .3s var(--ease)}
.motion-on .soc:hover{transform:translateY(-3px)}
.motion-on #toTop{transition:transform .35s cubic-bezier(.22,1,.36,1), opacity .3s var(--ease)}
.motion-on #toTop:hover{transform:translateY(-3px)}

/* =========================================================
   REDUCED MOTION / TOUCH — hard off switch
   motion.js never adds .motion-on under reduce, but belt and braces:
   ========================================================= */
@media (prefers-reduced-motion:reduce){
  #loader{display:none}
  .motion-on .amb i{animation:none}
  .motion-on .c{opacity:1;transform:none;transition:none}
  .motion-on [data-anim] .reveal{transform:none;clip-path:none;opacity:1}
  .motion-on .card,.motion-on .tool,.motion-on #nav{transition:none;transform:none}
  .motion-on .card::after{display:none}
  .motion-on .hero-right.in .portrait img{animation:none}
  .cur{display:none}
}
@media (pointer:coarse){
  .cur{display:none}
  .motion-on.cursor-on,
  .motion-on.cursor-on a,
  .motion-on.cursor-on button{cursor:auto}
  .motion-on .card::after{display:none}     /* no hover on touch — skip the paint */
}


/* =========================================================================
   LIGHT-THEME MOTION SYSTEM
   -------------------------------------------------------------------------
   WHY THIS EXISTS
   The light theme was never missing animation — it was missing CONTRAST.
   Every effect below already ran in light mode, but keyed its visibility to
   luminance that a light background cannot provide:

     · card shine swept white (#fff 26%) across white surfaces  → invisible
     · ambient blobs used gold #C7B594 / sage #A9B89D at 28%
       over #F3F0EA beige                                       → invisible
     · depth was carried by shadow colour tuned for a dark page → flat

   On dark (#0E0D0A) the same values read instantly. So light felt static
   while running identical code.

   FIX: light reads depth through SHADOW and INK, not glow. Every rule is
   scoped to html:not([data-theme="dark"]) so the dark theme is untouched
   by construction — not by discipline. Deleting this block returns light
   to its previous behaviour and cannot affect dark at all.
   ========================================================================= */

/* ---------------------------------------------------------------
   1 · AMBIENT — soft light and shade, not coloured glow
   Ink-tinted radials read as a room lit from two sides.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .amb i{opacity:.5}
html:not([data-theme="dark"]).motion-on .amb i:nth-child(1){
  background:radial-gradient(circle,
              rgba(23,21,15,.09), transparent 68%);
}
html:not([data-theme="dark"]).motion-on .amb i:nth-child(2){
  background:radial-gradient(circle,
              rgba(199,181,148,.30), transparent 70%);
}

/* ---------------------------------------------------------------
   2 · CARD SHINE — graphite sheen instead of white light
   A faint darkening sweep is the light-mode equivalent of a
   highlight sweep: it reads as a surface catching the light.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .card::after{
  background:linear-gradient(105deg,
              transparent 40%,
              rgba(23,21,15,.055) 50%,
              transparent 60%);
}

/* ---------------------------------------------------------------
   3 · ELEVATION ON ENTRANCE — depth via shadow, animated on a
   pseudo-element's OPACITY so no shadow is ever re-rasterised.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .card,
html:not([data-theme="dark"]).motion-on .tool{position:relative}
html:not([data-theme="dark"]).motion-on .card::before,
html:not([data-theme="dark"]).motion-on .tool::before{
  content:"";position:absolute;inset:0;z-index:-1;
  border-radius:inherit;pointer-events:none;
  box-shadow:0 18px 40px -24px rgba(23,21,15,.42);
  opacity:0;
  transition:opacity .7s var(--ease);
}
html:not([data-theme="dark"]).motion-on .reveal.in .card::before,
html:not([data-theme="dark"]).motion-on .card.in::before,
html:not([data-theme="dark"]).motion-on .tool.in::before{opacity:1}
html:not([data-theme="dark"]).motion-on .card:hover::before,
html:not([data-theme="dark"]).motion-on .tool:hover::before{opacity:1}

/* ---------------------------------------------------------------
   4 · SCROLL REVEAL — light gets the 0.97 → 1 settle and a softer,
   slower curve. Editorial rather than springy.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .reveal{
  transform:translateY(24px) scale(.978);
  transition-duration:.95s;
  transition-timing-function:cubic-bezier(.16,1,.3,1);
}
html:not([data-theme="dark"]).motion-on .reveal.in{transform:none}

/* ---------------------------------------------------------------
   5 · HEADINGS — blur → sharp, LIGHT ONLY and headings ONLY.
   Deliberately not applied to the 36 .reveal elements: blur is a
   raster pass, and doing it site-wide is exactly the cost removed
   earlier. Scoped to display headings (a handful of nodes), one
   shot, never on a scroll or rAF loop.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .display,
html:not([data-theme="dark"]).motion-on .hero h1{
  transition:filter .8s var(--ease), opacity .8s var(--ease);
}
html:not([data-theme="dark"]).motion-on .display:not(.in),
html:not([data-theme="dark"]).motion-on .reveal:not(.in) .display{
  filter:blur(6px);
}
html:not([data-theme="dark"]).motion-on .display.in,
html:not([data-theme="dark"]).motion-on .reveal.in .display{filter:blur(0)}

/* word / character stagger — gentler rise, no rotation in light.
   Rotation reads as playful; light theme wants editorial. */
html:not([data-theme="dark"]).motion-on .c{
  transform:translateY(.3em);
  transition:opacity .55s var(--ease) calc(var(--ci,0) * 26ms),
             transform .7s cubic-bezier(.16,1,.3,1) calc(var(--ci,0) * 26ms);
}
html:not([data-theme="dark"]).motion-on .in .c,
html:not([data-theme="dark"]).motion-on .reveal.in .c,
html:not([data-theme="dark"]).motion-on .c.done{transform:none}

/* ---------------------------------------------------------------
   6 · IMAGE MASK REVEAL — clip-path wipe + 0.96 → 1 settle.
   clip-path and transform are both compositor-friendly.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .card img,
html:not([data-theme="dark"]).motion-on .portrait img{
  clip-path:inset(0 0 0 0);
  transition:transform .9s cubic-bezier(.16,1,.3,1),
             clip-path .9s cubic-bezier(.16,1,.3,1);
}
html:not([data-theme="dark"]).motion-on .reveal:not(.in) .card img,
html:not([data-theme="dark"]).motion-on .card:not(.in) img{
  clip-path:inset(0 0 100% 0);
  transform:scale(1.04);
}
html:not([data-theme="dark"]).motion-on .card:hover img{transform:scale(1.03)}

/* ---------------------------------------------------------------
   7 · TILT — light theme stays flatter. Editorial pages do not
   tip; they lift. Halve the rotation, keep the rise.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .card,
html:not([data-theme="dark"]).motion-on .tool{
  transform:perspective(1100px)
            rotateX(calc(var(--rx,0deg) * .45))
            rotateY(calc(var(--ry,0deg) * .45))
            translate3d(0,var(--ty,0),0);
}
html:not([data-theme="dark"]).motion-on .card:hover,
html:not([data-theme="dark"]).motion-on .tool:hover{
  --ty:-4px;
  box-shadow:none;                      /* handled by ::before opacity */
  border-color:rgba(23,21,15,.22);
}

/* ---------------------------------------------------------------
   8 · BUTTONS — soft shadow, no glow. Light mode press reads
   through shadow collapse rather than brightness.
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .btn{
  transition:transform .35s cubic-bezier(.16,1,.3,1),
             box-shadow .35s var(--ease),
             background-color .35s var(--ease),
             border-color .3s var(--ease);
}
html:not([data-theme="dark"]).motion-on .btn:hover{
  box-shadow:0 10px 26px -14px rgba(23,21,15,.5);
}
html:not([data-theme="dark"]).motion-on .btn:active{
  box-shadow:0 3px 10px -8px rgba(23,21,15,.5);
}
html:not([data-theme="dark"]) .rip{opacity:.10}   /* ink ripple, subtler */

/* ---------------------------------------------------------------
   9 · NAV — attached to the page, then quietly lifts off it
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on #nav{
  transition:transform .45s cubic-bezier(.16,1,.3,1),
             background-color .45s var(--ease),
             box-shadow .45s var(--ease),
             border-color .45s var(--ease),
             backdrop-filter .45s var(--ease);
}
html:not([data-theme="dark"]).motion-on #nav.scrolled{
  box-shadow:0 10px 34px -18px rgba(23,21,15,.28);
  border-color:rgba(23,21,15,.14);
}

/* ---------------------------------------------------------------
   10 · FOOTER / SOCIAL — sequential reveal, ink-weighted
   --------------------------------------------------------------- */
html:not([data-theme="dark"]).motion-on .soc{
  transition:transform .4s cubic-bezier(.16,1,.3,1),
             color .3s var(--ease), border-color .3s var(--ease);
}
html:not([data-theme="dark"]).motion-on .soc:hover{
  transform:translateY(-3px);
  border-color:rgba(23,21,15,.28);
}

/* ---------------------------------------------------------------
   11 · MOBILE — strip everything that needs a pointer or costs
   raster time. Fades, slides and scale only.
   --------------------------------------------------------------- */
@media (pointer:coarse){
  html:not([data-theme="dark"]).motion-on .display,
  html:not([data-theme="dark"]).motion-on .hero h1,
  html:not([data-theme="dark"]).motion-on .display:not(.in),
  html:not([data-theme="dark"]).motion-on .reveal:not(.in) .display{
    filter:none;                        /* no blur raster on phones */
  }
  html:not([data-theme="dark"]).motion-on .card,
  html:not([data-theme="dark"]).motion-on .tool{transform:none}
  html:not([data-theme="dark"]).motion-on .card::before,
  html:not([data-theme="dark"]).motion-on .tool::before{display:none}
  html:not([data-theme="dark"]).motion-on .reveal{
    transform:translateY(18px);         /* drop the scale on mobile */
  }
  html:not([data-theme="dark"]).motion-on .reveal.in{transform:none}
}

/* ---------------------------------------------------------------
   12 · REDUCED MOTION — motion.js never sets .motion-on under
   reduce, but assert the end state anyway.
   --------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  html:not([data-theme="dark"]).motion-on .reveal,
  html:not([data-theme="dark"]).motion-on .display,
  html:not([data-theme="dark"]).motion-on .c,
  html:not([data-theme="dark"]).motion-on .card img,
  html:not([data-theme="dark"]).motion-on .portrait img{
    filter:none !important;
    transform:none !important;
    clip-path:none !important;
    opacity:1;
  }
  html:not([data-theme="dark"]).motion-on .card::before,
  html:not([data-theme="dark"]).motion-on .tool::before{opacity:1;transition:none}
  html:not([data-theme="dark"]).motion-on .amb i{animation:none}
}

/* ---------------------------------------------------------------
   13 · FAILSAFE — rules 5 and 6 hide content until .in arrives.
   If the observer never fires, the head-script failsafe must be
   able to undo them, or headings stay blurred and images stay
   fully clipped. Same failure mode guarded elsewhere; guard it
   here too.
   --------------------------------------------------------------- */
html.reveal-failsafe .display,
html.reveal-failsafe .hero h1,
html.reveal-failsafe .card img,
html.reveal-failsafe .portrait img{
  filter:none !important;
  clip-path:none !important;
  transform:none !important;
  opacity:1 !important;
}
html.reveal-failsafe .card::before,
html.reveal-failsafe .tool::before{opacity:1 !important}
