/* ==========================================================================
   effects.css — the detail layer. Everything here animates transform or
   opacity (or paint-only SVG strokes), and everything stops under
   prefers-reduced-motion. Without JS the page shows the final states.
   ========================================================================== */

/* ---- Blueprint grid + grain -------------------------------------------- */
/* Faint 12-column lines aligned to the real layout grid: invisible at a
   glance, there when you look for them. */
body::before {
  content: "";
  position: fixed;
  inset: 0 auto 0 50%;
  z-index: -1;
  width: min(var(--container), 100% - 2 * var(--gutter));
  transform: translateX(-50%);
  pointer-events: none;
  background:
    linear-gradient(to left, var(--grid-line) 1px, transparent 1px) right / 100% 100% no-repeat,
    repeating-linear-gradient(to right,
      var(--grid-line) 0 1px,
      transparent 1px calc((100% + var(--col-gap)) / 12));
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  opacity: var(--noise-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 .9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- Scroll progress hairline ------------------------------------------ */
.scroll-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  height: 2px;
  background: linear-gradient(90deg, var(--accent), var(--flow));
  transform-origin: 0 50%;
  transform: scaleX(var(--scroll, 0));
}
@supports (animation-timeline: scroll()) {
  .scroll-progress {
    animation: grow-x linear both;
    animation-timeline: scroll(root block);
  }
}
@keyframes grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ---- Section reveal choreography --------------------------------------- */
/* Uses the individual `translate` property so it composes with the
   `transform` that tilt and foil cards use. */
.js [data-reveal] {
  opacity: 0;
  translate: 0 16px;
  transition:
    opacity var(--dur-3) var(--ease-out) calc(var(--i, 0) * 80ms),
    translate var(--dur-3) var(--ease-out) calc(var(--i, 0) * 80ms),
    transform var(--dur-3) var(--ease-out),
    border-color var(--dur-2) var(--ease-out);
}
.js [data-reveal].is-in { opacity: 1; translate: none; }
/* The hero is above the fold: it animates in with CSS on first paint
   instead of waiting for JS, so it never delays Largest Contentful Paint. */
.js .hero [data-reveal] {
  opacity: 1;
  translate: none;
  animation: rise var(--dur-3) var(--ease-out) calc(var(--i, 0) * 80ms) both;
}
@keyframes rise { from { opacity: 0; translate: 0 16px; } }

/* ---- Kinetic underline: grows from the side the cursor entered --------- */
.k-link { position: relative; }
.k-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0.05em;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: var(--origin, left) 50%;
  transition: transform var(--dur-2) var(--ease-out);
}
.k-link:hover::after, .k-link:focus-visible::after { transform: scaleX(1); }
.email-link::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0.05em;
  height: 1px;
  background: var(--line-strong);
}
.email-link::after { background: var(--accent); }

/* ---- Icon micro-interactions: every icon redraws its own stroke --------- */
@keyframes redraw {
  from { stroke-dasharray: 1; stroke-dashoffset: 1; }
  to { stroke-dasharray: 1; stroke-dashoffset: 0; }
}
:is(a, button, .skill-group, .awards li, .cert, .edu-row, .status-chip):hover .icon,
.skill-group.is-active .icon {
  animation: redraw 0.75s var(--ease-out) both;
}
.brand:hover .brand-mark { animation: redraw 1s var(--ease-in-out) both; }
.cert:hover .cert-top .icon { animation: redraw 0.9s var(--ease-out) both, spin-in 0.9s var(--ease-out) both; }
@keyframes spin-in { from { transform: rotate(-90deg); } to { transform: none; } }

/* ---- Name scramble -------------------------------------------------------- */
.hero-name .ch { display: inline-block; text-align: center; white-space: pre; }
.hero-name .ch.is-noise { color: var(--accent); opacity: 0.55; font-style: normal; }

/* ---- Hero architecture: wires draw in, nodes settle, then packets flow --- */
.js .arch .wire {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition:
    stroke-dashoffset var(--dur-4) var(--ease-in-out) var(--d, 0ms),
    stroke var(--dur-2) var(--ease-out),
    opacity var(--dur-2) var(--ease-out);
}
.js .arch .node {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--dur-3) var(--ease-out) var(--d, 0ms), transform var(--dur-3) var(--ease-out) var(--d, 0ms);
}
.js .arch.is-drawn .wire { stroke-dashoffset: 0; }
.js .arch.is-drawn .node { opacity: 1; transform: none; }
.js .arch.is-drawn.has-hot .node:not(.is-hot, .is-linked) { opacity: 0.4; }
.arch .wire-labels { transition: opacity var(--dur-3) var(--ease-out) var(--d, 0ms); }
.js .arch:not(.is-drawn) .wire-labels { opacity: 0; }
.arch .packets { transition: opacity var(--dur-3) var(--ease-out); }

/* ---- Gauges ("governor limits") ----------------------------------------- */
.gauge .seg {
  transition: stroke-dashoffset var(--dur-3) var(--ease-out) calc(250ms + var(--i) * 70ms),
              opacity var(--dur-3) var(--ease-out) calc(250ms + var(--i) * 70ms);
}
.js .limits:not(.is-in) .gauge .seg:not(.is-retired) { stroke-dashoffset: var(--fill, 1); }
.js .limits:not(.is-in) .gauge .seg.is-retired { opacity: 0; }

/* Hover: the cell "re-runs" its meter. Arcs sweep in again left to right,
   the value lifts, a soft glow rises from below and the open "+" segment
   breathes. Transform, opacity and stroke-dashoffset only. */
.limit { isolation: isolate; }
.limit::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(70% 60% at 50% 35%, var(--spot), transparent 70%);
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-out);
  pointer-events: none;
}
.limit .gauge { transition: transform var(--dur-3) var(--ease-spring); }
.limit .limit-value { transition: transform var(--dur-3) var(--ease-spring); }
.limit .limit-suffix { display: inline-block; transition: transform var(--dur-3) var(--ease-spring); }
.limit .limit-label { transition: color var(--dur-2) var(--ease-out); }
.limit:hover::before { opacity: 1; }
.limit:hover .gauge { transform: scale(1.04); }
.limit:hover .limit-value { transform: translateY(-3px); }
.limit:hover .limit-suffix { transform: translateX(2px); }
.limit:hover .limit-label { color: var(--accent); }
.limit:hover .gauge .seg:not(.is-retired) {
  animation: seg-sweep 0.55s var(--ease-out) calc(var(--i) * 45ms) both;
}
.limit:hover .gauge .seg.is-retired { animation: seg-fade 0.55s var(--ease-out) calc(var(--i) * 45ms) both; }
.limit:hover .gauge .track-open { animation: open-breathe 1.6s var(--ease-in-out) 0.4s infinite; }
@keyframes seg-sweep { from { stroke-dashoffset: var(--fill, 1); } to { stroke-dashoffset: 0; } }
@keyframes seg-fade { from { opacity: 0; } }
@keyframes open-breathe { 50% { opacity: 1; } }

/* ---- Timeline ------------------------------------------------------------ */
/* Scroll-driven where supported; JS writes --progress everywhere else. */
.js .timeline-fill { transform: scaleY(var(--progress, 0)); }
@supports (animation-timeline: view()) {
  .js .timeline { view-timeline: --timeline block; }
  .js .timeline-fill {
    animation: grow-y linear both;
    animation-timeline: --timeline;
    animation-range: cover 40vh cover calc(100% - 60vh);
  }
}
@keyframes grow-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }

/* Collapsible bullets: height snaps; the items fade and slide in. */
.js [data-collapsible] .role-more {
  display: grid;
  grid-template-rows: 0fr;
}
.js [data-collapsible] .role-more > ul { min-height: 0; overflow: hidden; }
.js [data-collapsible] .role-more li {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.js [data-collapsible].is-open .role-more { grid-template-rows: 1fr; }
.js [data-collapsible].is-open .role-more li {
  opacity: 1;
  transform: none;
  transition-delay: calc(120ms + var(--i, 0) * 60ms);
}

/* ---- Cursor spotlight + border glow on cards ---------------------------- */
[data-spotlight]::before,
[data-spotlight]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
[data-spotlight]::before {
  z-index: -1;
  background: radial-gradient(480px circle at var(--x, 50%) var(--y, 50%), var(--spot), transparent 55%);
}
[data-spotlight]::after {
  inset: -1px;
  padding: 1px;
  background: radial-gradient(240px circle at var(--x, 50%) var(--y, 50%), var(--accent), transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
[data-spotlight].is-lit::before, [data-spotlight].is-lit::after { opacity: 1; }
[data-tilt] {
  transform: perspective(1000px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform var(--dur-3) var(--ease-out);
}
[data-tilt].is-lit { transition-duration: 120ms; }

/* ---- Holographic certification cards ------------------------------------ */
.holo {
  --mx: 50%;
  --my: 50%;
  --angle: 0deg;
  transform: perspective(800px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform var(--dur-3) var(--ease-out), border-color var(--dur-2) var(--ease-out);
}
.holo.is-lit { transition-duration: 100ms, var(--dur-2); border-color: var(--line-strong); }
.holo .foil, .holo .shine {
  position: absolute;
  pointer-events: none;
  border-radius: inherit;
}
.holo .foil {
  inset: 0;
  z-index: -1;
  opacity: 0;
  mix-blend-mode: var(--foil-blend);
  background:
    repeating-linear-gradient(115deg, rgb(255 255 255 / 0) 0 5px, rgb(255 255 255 / 0.22) 5px 6px),
    radial-gradient(circle at var(--mx) var(--my), rgb(255 255 255 / 0.55), transparent 45%),
    conic-gradient(from var(--angle) at var(--mx) var(--my),
      #ff9bd2, #ffd59b, #c8ff9b, #9bffe1, #9bc8ff, #b99bff, #ff9bd2);
  transition: opacity var(--dur-2) var(--ease-out);
}
/* A resting iridescent edge hints at the effect before you hover. */
.holo::after {
  content: "";
  position: absolute;
  inset: -1px;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0.5;
  background: conic-gradient(from var(--angle), #ff9bd2, #ffd59b, #9bffe1, #9bc8ff, #b99bff, #ff9bd2);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  transition: opacity var(--dur-2) var(--ease-out);
}
.holo.is-lit .foil { opacity: var(--foil-opacity); }
.holo.is-lit::after { opacity: 1; }
.holo .shine {
  inset: -40% -60%;
  z-index: -1;
  opacity: 0;
  background: linear-gradient(100deg, transparent 42%, rgb(255 255 255 / 0.7) 50%, transparent 58%);
  mix-blend-mode: soft-light;
  transform: translateX(-40%);
}
.holo.is-lit .shine { animation: shine 1.2s var(--ease-out) both; }
@keyframes shine {
  0% { transform: translateX(-40%); opacity: 0; }
  30% { opacity: 1; }
  100% { transform: translateX(40%); opacity: 0; }
}
.cert-retired .foil, .cert-retired::after { filter: grayscale(1); }
.cert-retired::after { opacity: 0.25; }

/* ---- Mini diagram motion (pure CSS, offset-path) ------------------------- */
.d-packet, .d-chunk { offset-rotate: 0deg; animation-timing-function: var(--ease-in-out); animation-iteration-count: infinite; }
.kip-packet { offset-path: path("M79 70H261"); animation-name: travel; animation-duration: 1.8s; }
.kip-packet-2 { animation-delay: -0.9s; }
#kip-before:checked ~ svg .kip-packet { animation-name: hop2; animation-duration: 4s; }
#kip-before:checked ~ svg .kip-packet-2 { animation-delay: -2s; }

.rest-out, .rest-in { offset-path: path("M90 35H250"); animation-name: travel; animation-duration: 2.4s; }
.rest-in { animation-direction: reverse; animation-delay: -1.2s; }
.rest-mw-out, .rest-mw-in { offset-path: path("M90 105H250"); animation-name: hop; animation-duration: 3.2s; }
.rest-mw-in { animation-direction: reverse; animation-delay: -1.6s; }

.cti-out, .cti-in { offset-path: path("M86 60H232"); animation-name: travel; animation-duration: 2.2s; }
/* Case 01: Apex sends each case down one of two routes. */
.route-skill { offset-path: path("M60 90H148C161 90 159 46 170 46H268"); animation-name: travel; animation-duration: 3s; }
.route-queue { offset-path: path("M60 90H148C161 90 159 134 170 134H268"); animation-name: travel; animation-duration: 3s; animation-delay: -1.5s; }
/* Case 03: execute() writes to records. */
.batch-write-k { offset-path: path("M199 82C199 100 140 100 140 120"); animation-name: travel; animation-duration: 3.6s; animation-delay: -1.2s; }
.batch-write-r { offset-path: path("M199 82C199 100 258 100 258 120"); animation-name: travel; animation-duration: 3.6s; animation-delay: -2.4s; }
/* Case 06: portal into Education Cloud, out to scheduling, activity flowing back in. */
.edu-portal { offset-path: path("M170 50V72"); animation-name: travel; animation-duration: 1.8s; }
.edu-sched { offset-path: path("M130 112C130 122 83 118 83 128"); animation-name: travel; animation-duration: 2.4s; animation-delay: -0.8s; }
.edu-eac { offset-path: path("M257 128C257 118 210 122 210 112"); animation-name: travel; animation-duration: 2.4s; animation-delay: -1.6s; }
.cti-in { animation-direction: reverse; animation-delay: -1.1s; }
.d-ring { fill: none !important; stroke: var(--flow) !important; transform-box: fill-box; transform-origin: center; animation: ring 2.2s var(--ease-out) infinite; }

.d-chunk { offset-path: path("M76 66H304"); animation-name: batch; animation-duration: 3.6s; }
.d-chunk-2 { animation-delay: -1.2s; }
.d-chunk-3 { animation-delay: -2.4s; }
.d-hand { transform-box: view-box; transform-origin: 56px 66px; animation: tick 3.6s steps(12) infinite; }


@keyframes travel {
  0% { offset-distance: 0%; opacity: 0; }
  12%, 88% { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
/* "hop": the packet waits inside the middle box, i.e. the extra hop. */
@keyframes hop {
  0% { offset-distance: 0%; opacity: 0; }
  8% { opacity: 1; }
  34%, 66% { offset-distance: 50%; }
  92% { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
/* Chunks pass behind each step and queue in the gaps between them. */
/* "hop2": two stops, for the two layers in Case 02's before state. */
@keyframes hop2 {
  0% { offset-distance: 0%; opacity: 0; }
  6% { opacity: 1; }
  22%, 36% { offset-distance: 26.4%; }
  58%, 72% { offset-distance: 73.6%; }
  94% { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
@keyframes batch {
  0% { offset-distance: 0%; opacity: 0; }
  8% { opacity: 1; }
  26%, 38% { offset-distance: 37.9%; }
  60%, 72% { offset-distance: 72.7%; }
  92% { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
@keyframes ring {
  0% { transform: scale(1); opacity: 0.7; }
  80%, 100% { transform: scale(1.3); opacity: 0; }
}
@keyframes tick { to { transform: rotate(360deg); } }
/* Off-screen diagrams don't animate at all. */
.js [data-diagram]:not(.is-visible) * { animation-play-state: paused !important; }

/* ---- Theme switch: circular reveal (View Transitions) -------------------- */
::view-transition-old(root),
::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
::view-transition-new(root) { z-index: 2; }

.sunmoon { width: 18px; height: 18px; overflow: visible; }
.sunmoon-core { fill: currentColor; transform-box: fill-box; transform-origin: center; transition: transform 0.6s var(--ease-spring); }
.sunmoon-cut { transition: transform 0.6s var(--ease-out); }
.sunmoon-rays { stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; transform-origin: 12px 12px; transition: transform 0.6s var(--ease-out), opacity var(--dur-2) var(--ease-out); }
:root[data-theme="dark"] .sunmoon-core { transform: scale(1.55); }
:root[data-theme="dark"] .sunmoon-cut { transform: translate(-7px, 4px); }
:root[data-theme="dark"] .sunmoon-rays { transform: rotate(-90deg) scale(0.4); opacity: 0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sunmoon-core { transform: scale(1.55); }
  :root:not([data-theme="light"]) .sunmoon-cut { transform: translate(-7px, 4px); }
  :root:not([data-theme="light"]) .sunmoon-rays { transform: rotate(-90deg) scale(0.4); opacity: 0; }
}

/* ---- Dialog entrances ---------------------------------------------------- */
.palette[open] .palette-box { animation: pop-in var(--dur-2) var(--ease-out); }
.terminal[open] { animation: drop-in var(--dur-3) var(--ease-out); }
dialog[open]::backdrop { animation: fade-in var(--dur-2) var(--ease-out); }
@keyframes pop-in { from { opacity: 0; transform: scale(0.97) translateY(-6px); } }
@keyframes drop-in { from { opacity: 0; transform: translateY(-24px); } }
@keyframes fade-in { from { opacity: 0; } }

/* ---- Copy-email confirmation -------------------------------------------- */
.copy-icons .icon { transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-spring); }
.copy-icons .icon-check { opacity: 0; transform: scale(0.5); }
.copy-btn.is-copied .icon-copy { opacity: 0; transform: scale(0.5) rotate(-30deg); }
.copy-btn.is-copied .icon-check { opacity: 1; transform: none; animation: redraw 0.5s var(--ease-out) 0.05s both; }
.copy-btn.is-copied { animation: nudge 0.5s var(--ease-spring); }
@keyframes nudge { 40% { transform: scale(1.06); } }

/* ======================= Reduced motion: final states ===================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }
  .scroll-progress { animation-duration: auto !important; }
  .d-packet, .d-chunk, .d-ring, .pulse::after, .arch .packets { display: none; }
  .holo, [data-tilt] { transform: none !important; }
  .holo .shine { display: none; }
  .js .timeline-fill { animation: none !important; transform: scaleY(1); }
}
