/* ============================================================================
   Cursor — the blend-mode dot
   ---------------------------------------------------------------------------
   One element, white, inverting whatever it sits on. `difference` rather than
   `exclusion` because the site's two extremes — cream text on ink, and ink text
   on paper — both need a hard flip to stay visible; exclusion greys out in the
   midtones and the dot vanishes over the blue-500 buttons.

   ---------------------------------------------------------------------------
   The stacking-context trap, which this file exists to avoid

   `mix-blend-mode` blends an element with its backdrop only as far as the
   nearest ancestor that forms a stacking context. Put the blend on a CHILD of
   the moving element and it blends against nothing, because the parent is
   transformed by the spring and a transform forms a stacking context — the dot
   renders as a plain white circle and the effect silently disappears.

   So the blend lives on the SAME element that carries the transform. An
   element creating a stacking context isolates its descendants' blending, not
   its own: this one still blends with the page behind it. For the same reason
   the fade is on this element too, and never on a wrapper above it — an
   opacity below 1 on an ancestor would isolate the blend for exactly as long
   as the fade lasts, which is a white flash on every entry.
   ========================================================================= */

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;          /* above --z-overlay (200); nothing sits on the dot */
  pointer-events: none;   /* it must never eat a click */

  /* The spring positions this element's top-left corner, so the box is pulled
     back by half its own size to centre the disc on the pointer. Kept here
     rather than in the motion value so the component holds no layout maths.

     Sized to the LARGEST state the dot ever reaches — the hover ring — because
     the box does not resize with it. A box smaller than the ring clips it, and
     the clip is invisible until you hover a button near the edge of the
     viewport. Keep these four numbers in step with the ring below. */
  margin-left: -24px;
  margin-top: -24px;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;

  mix-blend-mode: difference;
  opacity: 0;
  transition: opacity 0.22s var(--ease-out-soft);
}
.cursor.is-visible { opacity: 1; }

/* The shape only. No blend mode of its own — it inherits the effect by being
   painted inside the element that carries it. */
.cursor__dot {
  display: block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  transition:
    width 0.32s var(--ease-out-expo),
    height 0.32s var(--ease-out-expo),
    background-color 0.32s var(--ease-out-expo),
    box-shadow 0.32s var(--ease-out-expo),
    transform 0.18s var(--ease-out-expo);
}

/* Interactive affordance.
   Hiding the system cursor also hides the hand that said "this is clickable",
   so the dot has to say it instead. It opens into a ring — the growth is the
   signal, and the hollow centre keeps the label underneath readable, which a
   solid 44px disc would not. */
.cursor.is-live .cursor__dot {
  width: 44px;
  height: 44px;
  background: transparent;
  /* Hairline, whatever the ring's diameter. Under `difference` a heavy stroke
     inverts enough of the label underneath to make it hard to read — which is
     the one thing this ring exists not to do. */
  box-shadow: inset 0 0 0 1.5px #fff;
}

/* Press. A small, fast contraction — the one piece of feedback the system
   cursor never gave. */
.cursor.is-pressed .cursor__dot { transform: scale(0.78); }

/* --- Hiding the system cursor ------------------------------------------------
   Applied by the component, and ONLY while the dot is genuinely on screen. If
   the pointer leaves the window, the capability test fails, or the component
   unmounts, the class comes off and the visitor has their own cursor back.  */

html.cursor-hidden,
html.cursor-hidden * { cursor: none; }

/* Two exceptions the effect does not get to override.

   Text fields keep their I-beam, because a caret with no I-beam is a real
   usability loss rather than a stylistic one — and the component hides the dot
   over these anyway, so nothing is drawn twice.

   `[data-native-cursor]` is the escape hatch for anything added later that
   needs a true system cursor: a resize handle, a map, an embedded tool. */
html.cursor-hidden input:not([type="button"]):not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
html.cursor-hidden textarea,
html.cursor-hidden [contenteditable="true"] { cursor: text; }

html.cursor-hidden [data-native-cursor],
html.cursor-hidden [data-native-cursor] * { cursor: auto; }

/* --- Never on a touch screen, never under reduced motion ---------------------
   The component already refuses to render in both cases; these are the belt to
   that braces, so a stale class on <html> can never leave somebody without a
   pointer. */
@media (pointer: coarse), (hover: none), (prefers-reduced-motion: reduce) {
  .cursor { display: none; }
  html.cursor-hidden,
  html.cursor-hidden * { cursor: auto; }
}
