/* ──────────────────────────────────────────────────────────────────
   magnet-cursor.css
   Custom cursor with magnet-snap behaviour.
   Two layers:
     .magnet-cursor__blob — the soft, lagging "jelly" that morphs to
       wrap whatever interactive element is under the pointer.
     .magnet-cursor__dot  — a tiny dot that tracks the pointer 1:1
       so the user never loses their real position.
   All animation is JS-driven (js/magnet-cursor.js, rAF lerp). No CSS
   transitions on width / height / transform — they would fight the
   spring loop and visibly wobble.
   Disabled automatically on coarse pointers and prefers-reduced-motion
   by the JS; this stylesheet just sets the visual.
   ────────────────────────────────────────────────────────────────── */

:root {
  /* Iridescent blue→magenta→red gradient. The two stops below are
     also exposed as RGB twins for rgba() halos / borders.
       --cursor-blue   ≈ vivid electric blue
       --cursor-red    ≈ saturated coral red
     The free-state dot is white so it reads on any backdrop. */
  --cursor-blue: #2b6bff;
  --cursor-blue-rgb: 43, 107, 255;
  --cursor-red: #ff2d55;
  --cursor-red-rgb: 255, 45, 85;
  --cursor-gradient: linear-gradient(
    135deg,
    #2b6bff 0%,
    #8a3dff 45%,
    #ff2d55 100%
  );
  --cursor-base-size: 20px;
}

/* Hide the OS cursor only on fine-pointer devices (mouse, trackpad).
   Touch and pen users keep their native cursor / lack thereof. We
   gate this by the .has-magnet-cursor class which the JS adds to
   <html> after it confirms it's safe to take over. */
html.has-magnet-cursor,
html.has-magnet-cursor body,
html.has-magnet-cursor a,
html.has-magnet-cursor button,
html.has-magnet-cursor input,
html.has-magnet-cursor textarea,
html.has-magnet-cursor select,
html.has-magnet-cursor [role="button"],
html.has-magnet-cursor [role="switch"] {
  cursor: none;
}

/* The wrapper holds both layers. position: fixed so it ignores page
   scroll; pointer-events: none so the cursor itself never blocks
   clicks; isolation: isolate so its blend modes don't bleed into the
   document. */
.magnet-cursor {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9999;             /* above dock (100) and lets-talk (200) */
  isolation: isolate;
  contain: layout style paint;
}

/* Both layers start at 0,0 and are positioned by transform every
   frame in JS. Using top: 0 / left: 0 plus translate3d gives the
   compositor a stable transform-only animation. */
.magnet-cursor__blob,
.magnet-cursor__dot {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  will-change: transform, width, height, border-radius, opacity;
}

/* Outer blob — the morphing one.
   Iridescent blue→magenta→red gradient fill. opacity is JS-driven
   via the spring (1 free / 0.8 pressed / 0.35 snapped).
   No box-shadow, no backdrop-blur — the user explicitly asked for
   shadow-free hover. The gradient itself reads against most
   backgrounds without needing a halo.
   transform-origin: center center so the press-scale (0.8 / 0.95)
   contracts symmetrically. */
.magnet-cursor__blob {
  width: var(--cursor-base-size);
  height: var(--cursor-base-size);
  border-radius: 50%;
  border: 0;
  background: var(--cursor-gradient);
  /* Free-state halo only — a tight 6px rim that hints at the dot
     when free. Snapped state turns this off entirely. */
  box-shadow: 0 0 6px rgba(var(--cursor-blue-rgb), 0.45);
  opacity: 0;
  transform-origin: center center;
  /* width / height / border-radius / opacity / transform are all
     animated frame-by-frame by js/magnet-cursor.js — do NOT add CSS
     transitions on those, or the spring and transitions fight. */
  transition: box-shadow 0.18s ease;
}

.magnet-cursor__blob.is-snapped {
  /* No halo, no blur, no drop pad. Just the gradient hugging the
     icon edge — minimum visual noise so the directional wrap reads
     clearly. */
  box-shadow: none;
}

/* The press feedback (scale 0.8/0.95) is fully JS-driven — no rule
   needed here, but we keep the class as a hook for future custom
   pressed visuals. */

/* Inner dot — the 1:1 tracker. White with a white glow so it's
   readable on any background regardless of the blob colour. Hidden
   when the blob is snapped — JS sets opacity directly. */
.magnet-cursor__dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.9);
  opacity: 0;
}
.magnet-cursor.is-snapped .magnet-cursor__dot {
  opacity: 0;
}

/* Click ripple — same iridescent gradient on the rim, expands and
   fades. The JS appends one of these on every mousedown and removes
   it 800ms later. */
.magnet-cursor__ripple {
  position: absolute;
  top: 0;
  left: 0;
  width: 24px;
  height: 24px;
  margin: -12px 0 0 -12px;   /* centre on its own anchor */
  border-radius: 50%;
  border: 1.5px solid rgba(var(--cursor-red-rgb), 0.7);
  pointer-events: none;
  animation: magnet-cursor-ripple 700ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
  will-change: transform, opacity;
}
@keyframes magnet-cursor-ripple {
  from { transform: translate3d(var(--x, 0), var(--y, 0), 0) scale(0.4); opacity: 0.8; }
  to   { transform: translate3d(var(--x, 0), var(--y, 0), 0) scale(2.6); opacity: 0; }
}

/* Reduced-motion: keep a static blue ring at the pointer, no glow,
   no morph. JS sets opacity directly; we just freeze the shadow. */
@media (prefers-reduced-motion: reduce) {
  .magnet-cursor__blob,
  .magnet-cursor__blob.is-snapped {
    box-shadow: 0 0 0 1px var(--cursor-blue);
  }
  .magnet-cursor__ripple { display: none; }
}
