/*
 * Motion layer
 * ------------
 * One orchestrated arrival per screen, then stillness. The rule throughout:
 * transform and opacity only, nothing that triggers layout, and every effect
 * disappears entirely under prefers-reduced-motion.
 *
 * Elements opt in with .reveal (or .reveal-lines for the hero). anim.js adds
 * .is-in when they enter the viewport; if JS never runs, the fallback at the
 * bottom of this file leaves everything visible.
 */

/* --------------------------------------------------------------- reveals */
@media (prefers-reduced-motion: no-preference) {
  .js-anim .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
      transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
    transition-delay: var(--reveal-delay, 0ms);
    will-change: opacity, transform;
  }

  .js-anim .reveal.is-in {
    opacity: 1;
    transform: none;
  }

  /* The hero headline rises line by line, masked — the one flourish on load. */
  .js-anim .reveal-line {
    display: block;
    overflow: hidden;
  }

  .js-anim .reveal-line > span {
    display: block;
    transform: translateY(105%);
    transition: transform 1s cubic-bezier(0.22, 1, 0.36, 1);
    transition-delay: var(--reveal-delay, 0ms);
  }

  .js-anim .reveal-line.is-in > span {
    transform: none;
  }

  /* Hero supporting copy follows the headline, never competes with it. */
  .js-anim .hero-stagger > * {
    opacity: 0;
    transform: translateY(14px);
    animation: hero-in 0.85s cubic-bezier(0.22, 1, 0.36, 1) both;
  }

  .js-anim .hero-stagger > *:nth-child(1) {
    animation-delay: 0.15s;
  }
  .js-anim .hero-stagger > *:nth-child(2) {
    animation-delay: 0.25s;
  }
  .js-anim .hero-stagger > *:nth-child(3) {
    animation-delay: 0.35s;
  }
  .js-anim .hero-stagger > *:nth-child(4) {
    animation-delay: 0.45s;
  }
  .js-anim .hero-stagger > *:nth-child(5) {
    animation-delay: 0.55s;
  }

  @keyframes hero-in {
    to {
      opacity: 1;
      transform: none;
    }
  }

  /* Proof numbers count up in JS; the tick keeps them from feeling static. */
  .js-anim .soul-proof-value {
    font-variant-numeric: tabular-nums;
  }

  /*
   * The genie effect.
   *
   * macOS does not simply shrink a window into the dock — it funnels it: the
   * edge nearest the dock narrows to a point while the body stretches down
   * toward it. Real genie warps along a curve, which CSS cannot do to a live
   * DOM subtree, so this composes the three parts the eye actually reads:
   *
   *   1. transform-origin pinned to the dock icon's x position, so the whole
   *      window collapses toward that exact point rather than the centre;
   *   2. scaleX shrinking faster than scaleY, which is what makes it look
   *      sucked in rather than zoomed out;
   *   3. a small skew and perspective tilt mid-flight, giving the funnel its
   *      lean toward the icon.
   *
   * --genie-x is the icon's position as a percentage across the window, and
   * --genie-y is the vertical distance down to the dock. Both written by
   * main.js from the real element positions.
   */
  .js-anim .mac-window.is-launching,
  .js-anim .mac-window.is-closing {
    transform-origin: var(--genie-x, 50%) bottom;
    will-change: transform, opacity;
    backface-visibility: hidden;
  }

  .js-anim .mac-window.is-launching {
    animation: window-genie-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  .js-anim .mac-window.is-closing {
    animation: window-genie-out 0.38s cubic-bezier(0.5, 0, 0.75, 0) both;
  }

  @keyframes window-genie-in {
    0% {
      opacity: 0;
      transform: translateY(var(--genie-y, 60vh)) scale(0.04, 0.12)
        skewX(0deg);
    }
    45% {
      opacity: 1;
      transform: translateY(calc(var(--genie-y, 60vh) * 0.32))
        scale(0.5, 0.66) skewX(-1.5deg);
    }
    100% {
      opacity: 1;
      transform: translateY(0) scale(1, 1) skewX(0deg);
    }
  }

  @keyframes window-genie-out {
    0% {
      opacity: 1;
      transform: translateY(0) scale(1, 1) skewX(0deg);
    }
    55% {
      opacity: 1;
      transform: translateY(calc(var(--genie-y, 60vh) * 0.34))
        scale(0.46, 0.62) skewX(1.5deg);
    }
    100% {
      opacity: 0;
      transform: translateY(var(--genie-y, 60vh)) scale(0.04, 0.12)
        skewX(0deg);
    }
  }

  /*
   * Dock magnification. macOS scales the hovered icon and eases its immediate
   * neighbours — the previous version jumped 10px and lifted neighbours by a
   * fixed amount, which read as a bounce rather than magnification.
   */
  .dock-item {
    transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1),
      background-color 0.2s ease, border-color 0.2s ease;
    transform-origin: bottom center;
  }

  /* Magnification: the icon grows, its neighbours ease outward so nothing
     overlaps, and the row keeps its baseline. */
  .mac-dock:hover .dock-item:hover {
    transform: translateY(-8px) scale(1.45);
  }

  .mac-dock:hover .dock-item:hover + .dock-item,
  .mac-dock:hover .dock-item:has(+ .dock-item:hover) {
    transform: translateY(-3px) scale(1.2);
  }

  .mac-dock:hover .dock-item:hover + .dock-item + .dock-item,
  .mac-dock:hover .dock-item:has(+ .dock-item + .dock-item:hover) {
    transform: translateY(-1px) scale(1.07);
  }

  /*
   * Window content stagger. When a window launches, its contents arrive just
   * behind it — the window first, then what is inside it, which is the order
   * the eye wants. Short delays only; this must never feel like a queue.
   */
  .js-anim .section.active .section-content > *,
  .js-anim .section.active .mac-profile-info > * {
    animation: content-in 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
  }

  .js-anim .section.active .section-content > *:nth-child(1) { animation-delay: 0.16s; }
  .js-anim .section.active .section-content > *:nth-child(2) { animation-delay: 0.22s; }
  .js-anim .section.active .section-content > *:nth-child(3) { animation-delay: 0.28s; }
  .js-anim .section.active .section-content > *:nth-child(n + 4) { animation-delay: 0.34s; }

  @keyframes content-in {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
  }

  /* The timeline rail draws downward as the journey comes into view. */
  .js-anim .timeline::after {
    transform: scaleY(0);
    transform-origin: top;
    transition: transform 1.1s cubic-bezier(0.22, 1, 0.36, 1) 0.2s;
  }

  .js-anim .section.active .timeline::after {
    transform: scaleY(1);
  }

  /* Timeline nodes pop as their card arrives. */
  .js-anim .timeline-item::after {
    transition: background-color 0.2s ease, border-color 0.2s ease,
      transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .js-anim .timeline-item:hover::after {
    transform: scale(1.3);
  }

  /* Skill tiles settle in rather than blinking on. */
  .js-anim .skill-item {
    animation: tile-in 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
  }

  @keyframes tile-in {
    from {
      opacity: 0;
      transform: translateY(8px) scale(0.97);
    }
  }

  /* Certificates lift their image slightly, so the card feels physical. */
  .js-anim .certificate-item {
    overflow: hidden;
  }

  .js-anim .certificate-img {
    transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .js-anim .certificate-item:hover .certificate-img {
    transform: scale(1.05);
  }

  /* Project tiles answer the pointer without moving the layout. */
  .js-anim .project-icon {
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .js-anim .project-item:hover .project-icon {
    transform: translateY(-3px) scale(1.06);
  }

  /* Reading progress for the long home page. */
  .scroll-progress {
    position: fixed;
    top: 30px; /* sits on the menu bar's lower edge */
    left: 0;
    height: 2px;
    width: 100%;
    transform: scaleX(var(--scroll, 0));
    transform-origin: left;
    background: linear-gradient(90deg, var(--peacock), var(--gold-bright));
    z-index: 899;
    pointer-events: none;
  }

}

/* ------------------------------------------------------- cursor companion */
/* A soft feather-coloured glow that trails the pointer across the desktop.
   Pointer-events none, fine pointers only, and it never appears on touch. */
@media (prefers-reduced-motion: no-preference) and (hover: hover) and (pointer: fine) {
  .soul-cursor {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 700;
    width: 320px;
    height: 320px;
    margin: -160px 0 0 -160px;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(
      circle,
      rgba(27, 156, 147, 0.1),
      rgba(192, 138, 46, 0.05) 45%,
      transparent 68%
    );
    transition: opacity 0.5s ease;
  }

  .soul-cursor.is-live {
    opacity: 1;
  }
}

/* The mobile header is taller, so the progress bar follows it down. */
@media (max-width: 900px) {
  .scroll-progress {
    top: 52px;
  }
}

/* No-JS and reduced-motion: everything is simply present and legible. */
.reveal,
.reveal-line > span,
.hero-stagger > * {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .soul-cursor {
    display: none;
  }
}
