/* Compositor-style surface treatment for interactive controls.
 *
 * The reference is a Wayland compositor's window effects (KWin, Hyprland): a
 * surface that behaves like a physical panel under a light source rather than a
 * rectangle that changes colour on hover. Four things together produce that,
 * and all four are needed — any one alone reads as a gimmick:
 *
 *   1. Specular highlight   a soft bright spot that tracks the pointer, so the
 *                           surface appears to catch light from where you are.
 *   2. Parallax tilt        a few degrees of rotateX/rotateY toward the pointer,
 *                           under perspective, so the panel has a front face.
 *   3. Ambient breathing    a slow glow and scale cycle, so an idle control is
 *                           alive rather than frozen.
 *   4. Spring press         a depress that overshoots slightly on release,
 *                           which is what makes a click feel physical.
 *
 * Served from a single file rather than duplicated into landing.css, style.css
 * and viewer.css so the three pages cannot drift apart. Every rule here is
 * inert until depth.js adds `data-depth` — the treatment degrades to the plain
 * button if scripting is off, which is why the base styles are untouched.
 *
 * Nothing here is inline: the app's CSP is `style-src 'self'`, which silently
 * drops inline blocks. That fault shipped the readme viewer unstyled once.
 */

:root {
  /* One easing vocabulary for the whole app. `--ease-spring` overshoots past 1
     and is the reason a release feels like a physical rebound rather than a
     fade. */
  --ease-out: cubic-bezier(0.16, 0.84, 0.44, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-lift: 420ms;
  --dur-press: 130ms;
  --dur-settle: 620ms;

  /* Breathing period. Slow on purpose: at 6s it registers as ambience, at 2s it
     registers as a notification badge demanding attention. */
  --breathe: 6s;
}

/* --- the surface ------------------------------------------------------- */

[data-depth] {
  position: relative;
  isolation: isolate;
  transform-style: preserve-3d;
  will-change: transform;

  /* Pointer position within the element, in per cent, written by depth.js.
     Centre is the resting value so the highlight starts centred rather than
     jumping in from a corner on first hover. */
  --px: 50%;
  --py: 50%;
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  --lift: 0px;
  --press: 1;
  --glow: 0;

  transform:
    perspective(560px)
    translateY(var(--lift))
    rotateX(var(--tilt-x))
    rotateY(var(--tilt-y))
    scale(var(--press));
  transition:
    transform var(--dur-settle) var(--ease-spring),
    box-shadow var(--dur-lift) var(--ease-out);
}

/* The label must sit above both the highlight and the bloom, or the light
   washes over the text and dims it on every pass.
 *
 * The `:not()` exclusions are load-bearing, not defensive. This selector is
 * `[data-depth] > span` — specificity (0,1,1) — while `.depth-bloom` is (0,1,0).
 * Without the exclusion this rule WINS on the effect elements and forces
 * `position: relative` onto them, putting a 12px node that animates to 14×
 * scale into the button's flow: the button visibly grows and its label wraps to
 * two lines on every click. That was a real bug, not a hypothetical one.
 *
 * `.depth-halo` escaped it only by accident of having a more specific selector.
 * Both are excluded here so neither depends on that accident. */
[data-depth] > span:not(.depth-halo):not(.depth-fx),
[data-depth] > .key,
[data-depth] > .sub {
  position: relative;
  z-index: 3;
}

/* Clipping layer for the click bloom.
 *
 * The bloom needs to be clipped to the button, or a 14× expansion spills a pale
 * disc across the page. It cannot be clipped by putting `overflow: hidden` on
 * the button itself, because the breathing halo deliberately extends 14px
 * outside the bounds and would be cut off. So the bloom lives in its own
 * inset layer that clips, and the halo stays outside it. */
[data-depth] > .depth-fx {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  border-radius: inherit;
}

/* --- 1. specular highlight -------------------------------------------- */

/* Highlight and bevel share one pseudo-element on purpose.
 *
 * `::after` is already taken on two of these controls — the Start Quiz button
 * and the About button carry the diagonal sheen sweep there. An element has
 * exactly one `::after`, so claiming it here would have silently replaced the
 * sheen with the bevel. Both effects fit on `::before`: the gradients go in
 * `background`, the bevel in `box-shadow`. */
[data-depth]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: inherit;
  /* Two stops, not one: the tight inner core is the specular reflection, the
     wide outer wash is the diffuse falloff. A single gradient looks like a
     flashlight; two looks like a lit surface. */
  background:
    radial-gradient(
      circle 42px at var(--px) var(--py),
      rgba(255, 255, 255, calc(0.42 * var(--glow))),
      rgba(255, 255, 255, 0) 100%
    ),
    radial-gradient(
      circle 130px at var(--px) var(--py),
      rgba(255, 255, 255, calc(0.16 * var(--glow))),
      rgba(255, 255, 255, 0) 100%
    );
  /* A hairline along the top edge — the geometric edge catching the same light.
     This is the detail that reads as "bevelled panel" rather than "flat div". */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + 0.30 * var(--glow))),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16);
  transition:
    background var(--dur-lift) var(--ease-out),
    box-shadow var(--dur-lift) var(--ease-out);
}

/* Motion-only variant.
 *
 * Keeps the tilt, the lift and the spring press; drops every light effect. The
 * reason is size: the specular highlight is a fixed-radius spot (42px core,
 * 130px falloff), which reads as a sheen on a 90–200px pill and as a flashlight
 * on a 700px answer row — the wider the surface, the more the spot looks like a
 * separate object moving across it rather than the surface catching light.
 *
 * The bevel stays, but static. Its brightness was tied to `--glow`, so on hover
 * the whole top edge lit up along with the spot; pinned low it just reads as an
 * edge. */
[data-depth][data-depth-motion]::before {
  background: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    inset 0 -1px 0 rgba(0, 0, 0, 0.14);
}

/* No click bloom either — it is a light flash, and on a row this wide it flares
   across the answer text at the moment of choosing. depth.js also skips creating
   the node, so this is belt-and-braces for a control tagged in markup rather
   than by script. */
[data-depth][data-depth-motion] .depth-bloom {
  display: none;
}

/* --- 3. ambient breathing --------------------------------------------- */

/* Applied only to the page's primary call to action. If every control breathed,
   nothing would draw the eye and the page would shimmer. */
[data-depth][data-depth-breathe] {
  animation: depth-breathe var(--breathe) ease-in-out infinite;
}

@keyframes depth-breathe {
  0%, 100% {
    filter: brightness(1) saturate(1);
  }
  50% {
    filter: brightness(1.07) saturate(1.06);
  }
}

/* The halo breathes on its own element so it can pulse without animating the
   button's own `transform`, which the pointer tilt is already driving. Two
   animations writing one property is the classic way this effect breaks. */
[data-depth][data-depth-breathe] > .depth-halo {
  position: absolute;
  inset: -14px;
  z-index: 0;
  pointer-events: none;
  border-radius: inherit;
  /* `--halo` is set per button by the page that owns its colour; the fallback is
     deliberately colourless so an un-themed control pulses white rather than
     borrowing an accent that may clash. On a control with `overflow: hidden`
     (the Start Quiz pill, which clips its sheen) this halo is clipped to the
     pill — the `filter` breathing above is what carries the effect there, and it
     is unaffected by clipping. */
  background: radial-gradient(closest-side,
    var(--halo, rgba(255, 255, 255, 0.22)), transparent 78%);
  animation: depth-halo var(--breathe) ease-in-out infinite;
}

@keyframes depth-halo {
  0%, 100% {
    opacity: 0.42;
    transform: scale(0.97);
  }
  50% {
    opacity: 0.9;
    transform: scale(1.04);
  }
}

/* --- 4. spring press --------------------------------------------------- */

[data-depth].is-pressed {
  --press: 0.968;
  --lift: 1px;
  /* Fast going down, slow coming back: the asymmetry is what makes it feel like
     a key rather than a rubber band. */
  transition:
    transform var(--dur-press) var(--ease-out),
    box-shadow var(--dur-press) var(--ease-out);
}

/* Click bloom — a ring expanding from the exact point of contact. Driven by an
   animation rather than a transition so a rapid second click restarts it. */
/* Selector deliberately carries the parent, so it out-specifies the label rule
   above no matter how that rule is edited later. */
[data-depth] .depth-bloom {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0) 70%);
  animation: depth-bloom 620ms var(--ease-out) forwards;
}

@keyframes depth-bloom {
  from { transform: scale(0.4); opacity: 0.85; }
  to   { transform: scale(14);  opacity: 0; }
}

/* --- reduced motion ---------------------------------------------------- */

/* Tilt, breathing, bloom and spring all removed. The specular highlight is
   kept: it responds to the pointer the user is already moving, adds no
   autonomous motion, and is the part that makes the control legible as
   interactive. Vestibular triggers are the self-driving effects, not the
   cursor-following one. */
@media (prefers-reduced-motion: reduce) {
  [data-depth] {
    --tilt-x: 0deg !important;
    --tilt-y: 0deg !important;
    transform: none;
    transition: box-shadow 160ms linear, opacity 160ms linear;
    animation: none !important;
  }

  [data-depth][data-depth-breathe],
  [data-depth][data-depth-breathe] > .depth-halo {
    animation: none !important;
  }

  [data-depth][data-depth-breathe] > .depth-halo {
    opacity: 0.5;
    transform: none;
  }

  [data-depth].is-pressed {
    --press: 1;
    --lift: 0px;
  }

  [data-depth] .depth-bloom {
    display: none;
  }
}
