/* Construction frame: four L-shaped corner ticks that sit just outside the
   bounding box of whatever interactive element the pointer is over, the way a
   layout inspector shows the geometry underneath.
 *
 * This file is the visual style only. One fixed overlay element carries it,
 * and js/cursor.js owns that element — creating it as a sibling of the
 * crosshair and the coordinate readout, and moving and sizing it onto the
 * hovered element. An element opts in by carrying `data-frame`:
 *
 *   <a href="/research/" data-frame>research</a>
 *
 * A single shared overlay means exactly one element is ever annotated, and
 * that annotation costs nothing per element: no pseudo-element, no stacking
 * context, no positioning context, nothing that could touch layout.
 *
 * Applied so far to the nav tabs only. Nothing else opts in yet. */

.cursor-frame {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  /* Just under the crosshair, so a tick never paints over the pointer itself. */
  z-index: 2147483646;
  opacity: 0;
  transition: opacity 80ms linear;

  /* Faint enough to read as a construction guide rather than a border. */
  --frame-color: #c8c8c8;
  --frame-arm: 7px;
  --frame-stroke: 1px;

  /* Eight flat fills: a horizontal and a vertical arm at each of the four
     corners. Drawing the arms separately, rather than masking a full border
     down to its corners, means there is no state in which a complete box can
     appear — not on an older engine, not mid-transition. */
  background-image: linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color)),
    linear-gradient(var(--frame-color), var(--frame-color));
  background-position: left top, left top, right top, right top, left bottom,
    left bottom, right bottom, right bottom;
  background-size: var(--frame-arm) var(--frame-stroke),
    var(--frame-stroke) var(--frame-arm), var(--frame-arm) var(--frame-stroke),
    var(--frame-stroke) var(--frame-arm), var(--frame-arm) var(--frame-stroke),
    var(--frame-stroke) var(--frame-arm), var(--frame-arm) var(--frame-stroke),
    var(--frame-stroke) var(--frame-arm);
  background-repeat: no-repeat;
}

.cursor-frame.is-visible {
  opacity: 1;
}

/* Opacity is the only thing that ever animates on the frame itself — the ticks
   do not scale, pulse, or ease into position. */
@media (prefers-reduced-motion: reduce) {
  .cursor-frame {
    transition: none;
  }
}

/* ---- registration marks --------------------------------------------------
   A few tiny marks set just outside the frame's corners: the one soft note
   against an otherwise instrument-like annotation. They are children of the
   overlay, so they inherit its placement and need no positioning of their own
   beyond an offset from a corner.

   Opt-in is separate from the frame: `data-frame-marks` alongside `data-frame`.
   An element can carry the precise frame without the whimsy. js/cursor.js
   builds these, and picks a fixed arrangement per element so a given tab always
   wears the same composition. */

.cursor-frame-mark {
  position: absolute;
  /* The site's foreground colour, inherited down from <body>, rather than the
     frame's own faint grey — the marks are the thing that should read. */
  color: inherit;
  /* Also the state they return to when the hover ends, which is what gives the
     scale-down on the way out. */
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 120ms linear, transform 120ms ease-in;
}

/* Carried on the inner <svg> rather than the wrapper, so it multiplies with the
   pop-in's own 0 -> 1 fade instead of being overwritten by it. Foreground
   colour at a fraction of its strength: the marks stay the site's ink, drawn
   light enough to sit beside the frame's hairlines rather than shout over them.
   This is the knob to turn if they want to be fainter still. */
.cursor-frame-mark svg {
  display: block;
  width: 100%;
  height: 100%;
  opacity: var(--mark-ink, 0.35);
  /* Finer than the frame's own 1px ticks, and finer than a border can go: the
     paths carry vector-effect: non-scaling-stroke, so this is read in CSS
     pixels and holds at the same weight whether a mark is drawn at 2px or 8px.
     Inherits from the <svg> down to the path it is actually drawn on. */
  stroke-width: var(--mark-stroke, 0.65);
}

/* Overshoot slightly and settle. The stagger is per-mark, set inline by the
   script, and starts after the corner ticks have finished arriving. */
.cursor-frame.is-visible .cursor-frame-mark {
  animation: frame-mark-in 200ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: var(--mark-delay, 0ms);
}

@keyframes frame-mark-in {
  0% {
    opacity: 0;
    transform: scale(0);
  }
  65% {
    opacity: 1;
    transform: scale(1.1);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* At most one mark per arrangement drifts, and only once it has arrived. The
   rotation is on the inner <svg> so it cannot fight the wrapper's scale — one
   element animating two different transforms would drop the pop-in entirely. */
.cursor-frame.is-visible .cursor-frame-mark.is-drifting svg {
  animation: frame-mark-drift 5s ease-in-out 400ms infinite;
}

@keyframes frame-mark-drift {
  0%,
  100% {
    transform: rotate(0deg);
  }
  50% {
    transform: rotate(25deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cursor-frame-mark {
    transition: none;
  }
  .cursor-frame.is-visible .cursor-frame-mark {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .cursor-frame.is-visible .cursor-frame-mark.is-drifting svg {
    animation: none;
  }
}
