/* ---------- site-wide cursor (driven by cursor.js) ----------
   The wrapper is display:contents, so it has no box and no stacking context:
   every part is its own fixed element in the root stacking context, which is
   what lets mix-blend-mode reach the page underneath (a fixed wrapper would
   isolate the blend and the dot would just be a flat disc).

   The red dot is two stacked blends of the same shape:
     1. __invert  white, difference  dark page -> white, white text -> black
     2. __tint    red,   multiply    white -> XST red, black stays black
   Net result: XST red on the dark page, with text under it knocked out dark,
   and a dark maroon dot over red artwork, so it never disappears on theme.

   cursor.js writes --x/--y on the wrapper once per frame; parts read them
   through the individual translate property. The dot is always a circle. Size
   changes are transform: scale() on a fixed 60px box, transitioned in CSS.
   Nothing here triggers layout. */

.xst-cursor {
  display: contents;
}

.xst-cursor__invert,
.xst-cursor__tint,
.xst-cursor__fill {
  position: fixed;
  top: 0;
  left: 0;
  width: 60px;
  height: 60px;
  margin: -30px 0 0 -30px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 2147483646;
  will-change: transform;
  translate: var(--x, -100px) var(--y, -100px);
  opacity: 0;
  /* --s is the target size ratio, --press the pointerdown squeeze. */
  --s: 0.2;
  --press: 1;
  transform: scale(calc(var(--s) * var(--press)));
  transition: transform 0.32s var(--ease), opacity 0.22s ease;
}

.xst-cursor__invert {
  background: #fff;
  mix-blend-mode: difference;
}

.xst-cursor__tint {
  background: var(--xst-red);
  mix-blend-mode: multiply;
}

.xst-cursor.is-visible:not(.is-drag):not(.is-view) .xst-cursor__invert,
.xst-cursor.is-visible:not(.is-drag):not(.is-view) .xst-cursor__tint {
  opacity: 1;
}

/* Links: grow to 48px, full-strength knockout like voidvoidvoid.com. */
.xst-cursor.is-link .xst-cursor__invert,
.xst-cursor.is-link .xst-cursor__tint {
  --s: 0.8;
}

.xst-cursor.is-down .xst-cursor__invert,
.xst-cursor.is-down .xst-cursor__tint,
.xst-cursor.is-down .xst-cursor__fill {
  --press: 0.85;
}

/* ---------- Drag / View bubble over the sphere ---------- */

.xst-cursor__fill {
  --s: 0;
  background: var(--xst-red);
  /* The bubble scales up a touch slower than the dot shrinks, so the
     hand-off reads as one shape morphing rather than a swap. */
  transition: transform 0.42s var(--ease), opacity 0.26s ease;
}

.xst-cursor.is-visible.is-drag .xst-cursor__fill,
.xst-cursor.is-visible.is-view .xst-cursor__fill {
  --s: 1;
  opacity: 1;
}

/* Glow and the hot-red View tone are layers whose opacity crossfades, so
   Drag <-> View never snaps colour (background itself isn't animated). */
.xst-cursor__fill::after,
.xst-cursor__hot {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.26s ease;
}

.xst-cursor__fill::after {
  box-shadow: 0 0 32px 6px rgba(224, 27, 27, 0.4);
}

.xst-cursor__hot {
  background: var(--xst-red-hot);
}

.xst-cursor.is-drag .xst-cursor__fill::after,
.xst-cursor.is-view .xst-cursor__fill::after,
.xst-cursor.is-view .xst-cursor__hot {
  opacity: 1;
}

.xst-cursor__label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-stretch: 112.5%;
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.xst-cursor.is-drag .xst-cursor__label[data-mode="drag"],
.xst-cursor.is-view .xst-cursor__label[data-mode="view"] {
  opacity: 1;
}

/* Native cursor off everywhere once cursor.js has passed its gate... */
html.xst-cursor-on,
html.xst-cursor-on *,
html.xst-cursor-on *::before,
html.xst-cursor-on *::after {
  cursor: none !important;
}

/* ...except form fields, where the dot hides and the real cursor is needed. */
html.xst-cursor-on :is(input, textarea, select, [contenteditable]:not([contenteditable="false"]),
    [contenteditable]:not([contenteditable="false"]) *) {
  cursor: auto !important;
}

html.xst-cursor-on :is(textarea, input:not([type]), input[type="text"], input[type="email"],
    input[type="search"], input[type="url"], input[type="tel"], input[type="password"],
    input[type="number"], [contenteditable]:not([contenteditable="false"]),
    [contenteditable]:not([contenteditable="false"]) *) {
  cursor: text !important;
}

@media (hover: none), (pointer: coarse), (max-width: 767px) {
  .xst-cursor {
    display: none;
  }
}
