/* ==========================================================================
   Relic Collectibles - the shared rainbow holo layer
   Loaded on EVERY public page, immediately after relic.css.

   This is the page-agnostic holo: the tokens, the pointer-tracking custom
   properties, and the reusable primitives (.holo-foil, .holo-ring, .holo-art,
   .holo-fan, .holo-sparkle, .holo-tilt...) that any page can drop onto an
   element to make it read like a real holo card catching the light. It was
   extracted 2026-09-02 from the /events/ show pages (relic-events.css and
   page-tcg.js), which shipped the first rainbow holo. relic-events.css now
   depends on this file for every holo token and keeps only its own
   .tcg-* component rules (card frame, pack wrapper, binder, etc).

   Host contract: add class "holo" to any element that should track the
   pointer. It declares its own gradients on itself (--holo-ang, --holo-pos,
   --holo-bands, --holo-bands-deep, --holo-bands-vivid, --holo-ring) so
   var() substitutes at the declaring element, not at :root - children
   inherit the result. relic-holo.js writes --holo-mx/--holo-my (0-100) and
   --holo-rx/--holo-ry (tilt angles) onto that element under a fine pointer,
   and sets html[data-holo-live="1"] once at start. Touch screens get a CSS
   wander (@keyframes holo-wander) instead of pointer tracking.

   Then add whichever primitive(s) fit the element: .holo-tilt for the 3D
   press, .holo-foil / .holo-foil-photo for the body foil, .holo-ring /
   .holo-rare for the border treatment, .holo-art for the cosmos window,
   .holo-fan for a hero's fanned mini-cards, .holo-sparkle for a sparkle-only
   accent. See HOLO-CONTRACT.md for the full primitive table.

   Body-foil blend modes stay MULTIPLY on light paper and LIGHTEN on dark
   stock so the holo prints onto the ink rather than fighting it - neither
   can push text past its contrast floor. Do not loosen the floor: light
   theme pastel hues stay L >= 0.91 with alpha <= 0.60 under multiply; dark
   theme hues stay L <= 0.34 under lighten. Vivid hues (bands-vivid, ring)
   never sit under text - photo foil only ever sits over images.
   ========================================================================== */

:root {
  /* The holo, eight real hues.
     --holo-h*  body foil on card stock / paper. Light theme: pastels that
                MULTIPLY into the paper (ink can only get darker, so the
                ratio floor holds). Dark theme swaps these for deep jewel
                tones under LIGHTEN, capped at L 0.33 so ink keeps its ratio.
     --holo-d*  deep set for drenched/dark grounds in both themes.
     The art window and the ring use vivid hues written inline: no text ever
     sits on them. */
  --holo-h1: oklch(0.91 0.09 25);
  --holo-h2: oklch(0.93 0.10 65);
  --holo-h3: oklch(0.96 0.13 100);
  --holo-h4: oklch(0.94 0.12 150);
  --holo-h5: oklch(0.94 0.09 205);
  --holo-h6: oklch(0.91 0.08 265);
  --holo-h7: oklch(0.91 0.09 305);
  --holo-h8: oklch(0.91 0.09 345);
  --holo-blend: multiply;
  --holo-a:      0.42;  /* body foil at rest */
  --holo-b:      0.60;  /* body foil lit */
  --holo-spark-a: 0;    /* white sparkle over white paper is invisible: skip the paint */
  --holo-glare-a: 0;
  --holo-veil:   oklch(1 0 0 / 0.45);  /* softens the ring on a common item */

  --holo-d1: oklch(0.40 0.17 25);
  --holo-d2: oklch(0.41 0.13 60);
  --holo-d3: oklch(0.42 0.12 95);
  --holo-d4: oklch(0.40 0.14 150);
  --holo-d5: oklch(0.41 0.10 200);
  --holo-d6: oklch(0.38 0.16 265);
  --holo-d7: oklch(0.37 0.19 305);
  --holo-d8: oklch(0.40 0.18 345);

  /* Sparkle: sparse white specks from turbulence noise, tiled. No asset. */
  --holo-sparkle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='2' seed='11'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 22 -16'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23s)'/%3E%3C/svg%3E");

  /* Galaxy grounds for the art window, default identity. */
  --holo-gal-0: oklch(0.22 0.06 300);
  --holo-gal-1: oklch(0.46 0.20 340);
  --holo-gal-2: oklch(0.40 0.16 250);

  /* Ring defaults. Override --holo-surface per use if the element is not
     paper (e.g. a drenched/dark ground), so the padding-box layer matches. */
  --holo-surface: var(--paper);
  --holo-ring-w: 3px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --holo-h1: oklch(0.33 0.14 25);
    --holo-h2: oklch(0.33 0.11 60);
    --holo-h3: oklch(0.34 0.10 95);
    --holo-h4: oklch(0.33 0.12 150);
    --holo-h5: oklch(0.33 0.08 200);
    --holo-h6: oklch(0.32 0.13 265);
    --holo-h7: oklch(0.31 0.16 305);
    --holo-h8: oklch(0.33 0.15 345);
    --holo-blend: lighten;
    --holo-a:      0.60;
    --holo-b:      0.85;
    --holo-spark-a: 0.62;
    --holo-glare-a: 0.30;
    --holo-veil:   oklch(0 0 0 / 0.45);
  }
}
:root[data-theme="dark"] {
  --holo-h1: oklch(0.33 0.14 25);
  --holo-h2: oklch(0.33 0.11 60);
  --holo-h3: oklch(0.34 0.10 95);
  --holo-h4: oklch(0.33 0.12 150);
  --holo-h5: oklch(0.33 0.08 200);
  --holo-h6: oklch(0.32 0.13 265);
  --holo-h7: oklch(0.31 0.16 305);
  --holo-h8: oklch(0.33 0.15 345);
  --holo-blend: lighten;
  --holo-a:      0.60;
  --holo-b:      0.85;
  --holo-spark-a: 0.62;
  --holo-glare-a: 0.30;
  --holo-veil:   oklch(0 0 0 / 0.45);
}

/* ==========================================================================
   The pointer-tracked host. relic-holo.js writes --holo-mx/--holo-my (0-100,
   inherited, registered so they animate) and --holo-rx/--holo-ry (tilt)
   directly onto whichever .holo element the pointer is over. Declared HERE,
   not on :root: var() substitutes at the declaring element, so the written
   value has to be read on the same element it was written to. Children
   inherit the resolved gradients.
   ========================================================================== */
@property --holo-mx    { syntax: "<number>"; inherits: true; initial-value: 50; }
@property --holo-my    { syntax: "<number>"; inherits: true; initial-value: 50; }
@property --holo-rx    { syntax: "<angle>";  inherits: false; initial-value: 0deg; }
@property --holo-ry    { syntax: "<angle>";  inherits: false; initial-value: 0deg; }
@property --holo-lift  { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --holo-scale { syntax: "<number>"; inherits: false; initial-value: 1; }

.holo {
  --holo-ang: calc(112deg + (var(--holo-mx) - 50) * 0.5deg);
  --holo-pos: calc(var(--holo-mx) * 1%) calc(var(--holo-my) * 1%);
  --holo-bands: linear-gradient(var(--holo-ang),
    transparent 0 9%, var(--holo-h1) 15%, var(--holo-h2) 21%, var(--holo-h3) 27%,
    var(--holo-h4) 33%, var(--holo-h5) 39%, var(--holo-h6) 45%, var(--holo-h7) 51%,
    var(--holo-h8) 57%, transparent 65% 100%);
  --holo-bands-deep: linear-gradient(var(--holo-ang),
    transparent 0 9%, var(--holo-d1) 15%, var(--holo-d2) 21%, var(--holo-d3) 27%,
    var(--holo-d4) 33%, var(--holo-d5) 39%, var(--holo-d6) 45%, var(--holo-d7) 51%,
    var(--holo-d8) 57%, transparent 65% 100%);
  /* Vivid: the printed holo. Only ever under a set symbol or a photo, never text. */
  --holo-bands-vivid: linear-gradient(var(--holo-ang),
    transparent 0 8%, oklch(0.68 0.21 25 / 0.85) 14%, oklch(0.78 0.17 65 / 0.85) 20%,
    oklch(0.90 0.18 100 / 0.85) 26%, oklch(0.82 0.20 150 / 0.85) 32%,
    oklch(0.84 0.13 205 / 0.85) 38%, oklch(0.62 0.20 265 / 0.85) 44%,
    oklch(0.60 0.24 305 / 0.85) 50%, oklch(0.70 0.23 345 / 0.85) 56%, transparent 64% 100%);
  --holo-ring: conic-gradient(from calc(var(--holo-mx) * 3.6deg - 30deg),
    oklch(0.75 0.18 25), oklch(0.82 0.16 65), oklch(0.92 0.17 100), oklch(0.82 0.18 150),
    oklch(0.84 0.12 205), oklch(0.70 0.16 265), oklch(0.66 0.21 305), oklch(0.74 0.20 345),
    oklch(0.75 0.18 25));
}

/* ==========================================================================
   .holo-tilt: 3D press toward the pointer. Only on things that are
   card-shaped. Every .holo host (tilting or not) gets its --holo-mx/--holo-my
   transitioned so the gradient position settles instead of snapping; a
   .holo-tilt host additionally transitions and applies the tilt transform.
   ========================================================================== */
html[data-holo-live] .holo {
  transition: --holo-mx 700ms cubic-bezier(0.16, 1, 0.3, 1),
              --holo-my 700ms cubic-bezier(0.16, 1, 0.3, 1);
}
html[data-holo-live] .holo[data-holo-hover] {
  transition-duration: 120ms;
  transition-timing-function: linear;
}

html[data-holo-live] .holo.holo-tilt,
html[data-holo-live] .holo.holo-tilt:hover,
html[data-holo-live] .holo.holo-tilt:focus-within {
  transform: perspective(1100px) rotateX(var(--holo-ry)) rotateY(var(--holo-rx))
             translateY(var(--holo-lift)) scale(var(--holo-scale));
  transition: --holo-mx 700ms cubic-bezier(0.16, 1, 0.3, 1), --holo-my 700ms cubic-bezier(0.16, 1, 0.3, 1),
              --holo-rx 700ms cubic-bezier(0.16, 1, 0.3, 1), --holo-ry 700ms cubic-bezier(0.16, 1, 0.3, 1),
              --holo-lift 500ms cubic-bezier(0.16, 1, 0.3, 1), --holo-scale 500ms cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow var(--med) cubic-bezier(0.16, 1, 0.3, 1);
}
html[data-holo-live] .holo.holo-tilt[data-holo-hover] {
  --holo-lift: -7px; --holo-scale: 1.015;
  transition-duration: 80ms, 80ms, 140ms, 140ms, 300ms, 300ms, var(--med);
  transition-timing-function: linear, linear;
}
html[data-holo-live] .holo.holo-tilt:focus-within { --holo-lift: -7px; }

/* ==========================================================================
   .holo-foil: body foil for paper/dark stock surfaces WITH TEXT on them.
   Host must be position:relative (set here so it just works).
   ========================================================================== */
.holo-foil { position: relative; }
.holo-foil::before,
.holo-foil::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  transition: opacity var(--med) ease;
}
.holo-foil::before {
  background: var(--holo-bands) var(--holo-pos) / 320% 320%;
  mix-blend-mode: var(--holo-blend);
  opacity: var(--holo-a);
}
.holo-foil::after {
  background:
    radial-gradient(farthest-corner circle at var(--holo-pos), oklch(1 0 0 / var(--holo-glare-a)), transparent 55%),
    var(--holo-sparkle) 0 0 / 160px 160px;
  mix-blend-mode: screen;
  opacity: 0;
}
.holo-foil:hover::before,
.holo-foil:focus-within::before,
.holo-foil[data-holo-hover]::before,
.holo[data-holo-hover] .holo-foil::before,
.holo:hover .holo-foil::before { opacity: var(--holo-b); }
.holo-foil:hover::after,
.holo-foil:focus-within::after,
.holo-foil[data-holo-hover]::after,
.holo[data-holo-hover] .holo-foil::after,
.holo:hover .holo-foil::after { opacity: var(--holo-spark-a); }

/* ==========================================================================
   .holo-foil-photo: the same two pseudo-elements, tuned for a PHOTOGRAPH
   instead of ink on paper. Vivid hues, screen blend in both themes, and its
   own fixed alphas rather than the theme-swapped body-foil tokens. Never
   over text.
   ========================================================================== */
.holo-foil-photo { position: relative; }
.holo-foil-photo::before,
.holo-foil-photo::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  transition: opacity var(--med) ease;
}
.holo-foil-photo::before {
  background: var(--holo-bands-vivid) var(--holo-pos) / 320% 320%;
  mix-blend-mode: screen;
  opacity: 0;
}
.holo-foil-photo::after {
  background:
    radial-gradient(farthest-corner circle at var(--holo-pos), oklch(1 0 0 / 0.30), transparent 55%),
    var(--holo-sparkle) 0 0 / 160px 160px;
  mix-blend-mode: screen;
  opacity: 0;
}
.holo-foil-photo:hover::before,
.holo-foil-photo:focus-within::before,
.holo-foil-photo[data-holo-hover]::before,
.holo[data-holo-hover] .holo-foil-photo::before,
.holo:hover .holo-foil-photo::before { opacity: 0.55; }
.holo-foil-photo:hover::after,
.holo-foil-photo:focus-within::after,
.holo-foil-photo[data-holo-hover]::after,
.holo[data-holo-hover] .holo-foil-photo::after,
.holo:hover .holo-foil-photo::after { opacity: 0.7; }
/* A phone cannot hover: keep a modest resting shimmer instead of nothing. */
@media (hover: none) {
  .holo-foil-photo::before { opacity: 0.28; }
}

/* ==========================================================================
   .holo-ring: rainbow conic border. Set --holo-surface per use if the
   element is not paper. Combine with .holo-ring-hover to keep the ring at
   rest until hovered/lit, with no layout shift (a border-color swap, same
   border-width throughout).
   ========================================================================== */
.holo-ring {
  border: var(--holo-ring-w) solid transparent;
  background:
    linear-gradient(var(--holo-veil), var(--holo-veil)) padding-box,
    linear-gradient(var(--holo-surface), var(--holo-surface)) padding-box,
    var(--holo-ring) border-box;
}
.holo-ring-hover {
  border-color: var(--line);
  background: none;
  transition: border-color 200ms ease;
}
.holo-ring-hover:hover,
.holo-ring-hover:focus-within,
.holo-ring-hover[data-holo-hover],
.holo[data-holo-hover] .holo-ring-hover,
.holo:hover .holo-ring-hover {
  border-color: transparent;
  background:
    linear-gradient(var(--holo-veil), var(--holo-veil)) padding-box,
    linear-gradient(var(--holo-surface), var(--holo-surface)) padding-box,
    var(--holo-ring) border-box;
}

/* ==========================================================================
   .holo-rare: the one hero item on a page. Bare ring (no veil), foil pinned
   lit rather than hover-driven, plus a glow.
   ========================================================================== */
.holo-rare {
  background: var(--holo-ring);
  box-shadow: var(--shadow-2), 0 14px 44px oklch(0.62 0.20 305 / 0.28), 0 4px 18px oklch(0.84 0.13 205 / 0.22);
}
.holo-rare.holo-foil::before,
/* A rare PHOTO rests at a third strength: the buyer still has to see the
   card they are paying for. Hover brings the full foil up. */
.holo-rare.holo-foil-photo::before { opacity: 0.30; }
.holo-rare.holo-foil-photo:hover::before,
.holo-rare.holo-foil-photo:focus-within::before,
.holo-rare.holo-foil-photo[data-holo-hover]::before { opacity: 0.55; }
.holo-rare.holo-foil::after { opacity: var(--holo-spark-a); }
.holo-rare.holo-foil-photo::after { opacity: 0.35; }
.holo-rare.holo-foil-photo:hover::after,
.holo-rare.holo-foil-photo:focus-within::after,
.holo-rare.holo-foil-photo[data-holo-hover]::after { opacity: 0.7; }

/* ==========================================================================
   .holo-art: the cosmos window. A galaxy ground, the vivid rainbow sweeping
   across it, sparkle on top, a glare that follows the pointer. This is the
   one place the holo is loud, and nothing is ever written on it.
   ========================================================================== */
.holo-art {
  position: relative;
  aspect-ratio: 16 / 7;
  border-radius: 9px;
  border: 1px solid oklch(1 0 0 / 0.28);
  background:
    radial-gradient(farthest-corner circle at var(--holo-pos), oklch(1 0 0 / 0.42), transparent 48%),
    var(--holo-sparkle) 0 0 / 120px 120px,
    var(--holo-bands-vivid) var(--holo-pos) / 300% 300%,
    radial-gradient(80% 110% at 18% 12%, var(--holo-gal-1), transparent 62%),
    radial-gradient(70% 100% at 84% 88%, var(--holo-gal-2), transparent 60%),
    var(--holo-gal-0);
  box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.25), inset 0 0 34px oklch(0 0 0 / 0.35);
  display: grid; place-items: center;
  overflow: hidden;
}
.holo-art svg {
  width: clamp(2.4rem, 8vw, 3.1rem); height: auto;
  color: oklch(0.985 0.01 90);
  filter: drop-shadow(0 2px 10px oklch(0 0 0 / 0.55));
}

/* ==========================================================================
   .holo-fan: three fanned cosmos mini-cards for a page hero. Markup:
   <span class="holo-fan" aria-hidden="true"><i></i><i></i><i></i></span>
   inside a .holo host that is position:relative; overflow:clip.
   ========================================================================== */
.holo-fan {
  position: absolute; right: clamp(1.5rem, 3.5vw, 4rem); top: 50%;
  translate: 0 -50%;
  width: 20rem; height: 15.5rem;
  display: none;
  pointer-events: none;
}
/* Only once the host is wide enough that the fan cannot reach other content. */
@media (min-width: 1180px) { .holo-fan { display: block; } }
.holo-fan i {
  position: absolute; display: block;
  width: 9.5rem; height: 13.3rem;
  border-radius: 12px;
  background:
    var(--holo-sparkle) 0 0 / 110px 110px,
    var(--holo-bands-vivid) var(--holo-pos) / 300% 300%,
    radial-gradient(90% 110% at 20% 10%, var(--holo-gal-1), transparent 62%),
    var(--holo-gal-0);
  border: 3px solid oklch(0.96 0.12 100);
  box-shadow: 0 18px 40px oklch(0 0 0 / 0.45);
  opacity: 0.95;
}
.holo-fan i:nth-child(1) { left: 0;      top: 1.1rem; rotate: -14deg; }
.holo-fan i:nth-child(2) { left: 4.7rem; top: 0;      rotate:  -2deg; }
.holo-fan i:nth-child(3) { left: 9.5rem; top: 1.3rem; rotate:  10deg; }
@media (max-width: 30rem) { .holo-fan { display: none; } }

/* ==========================================================================
   .holo-sparkle: sparkle-only accent for photo heroes that are already
   colourful and do not need the full rainbow band.
   ========================================================================== */
.holo-sparkle { position: relative; }
.holo-sparkle::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  background: var(--holo-sparkle) 0 0 / 160px 160px;
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity var(--med) ease;
}
.holo-sparkle:hover::after,
.holo-sparkle:focus-within::after,
.holo-sparkle[data-holo-hover]::after { opacity: 0.5; }

/* ==========================================================================
   Touch: a phone cannot tilt to the light, so the light moves instead.
   ========================================================================== */
@keyframes holo-wander {
  0%   { --holo-mx: 18; --holo-my: 30; }
  50%  { --holo-mx: 82; --holo-my: 70; }
  100% { --holo-mx: 18; --holo-my: 30; }
}
@media (hover: none) {
  .holo { animation: holo-wander 11s ease-in-out infinite; }
  .holo:nth-child(2n) { animation-delay: -3.7s; }
  .holo:nth-child(3n) { animation-delay: -7.1s; }
  .holo-foil::before { opacity: var(--holo-b); }
  .holo-foil::after { opacity: var(--holo-spark-a); }
}

/* ==========================================================================
   Reduced motion: no tilt, no wander, foil static at resting alpha.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .holo { animation: none !important; }
  .holo.holo-tilt,
  .holo.holo-tilt:hover,
  .holo.holo-tilt:focus-within,
  .holo.holo-tilt[data-holo-hover] { transform: none; }
  .holo-foil::before, .holo-foil::after,
  .holo-foil-photo::before, .holo-foil-photo::after,
  .holo-sparkle::after { transition: none; }
  .holo-foil:hover::before, .holo-foil:focus-within::before, .holo-foil[data-holo-hover]::before { opacity: var(--holo-a); }
  .holo-foil:hover::after, .holo-foil:focus-within::after, .holo-foil[data-holo-hover]::after { opacity: 0; }
  .holo-foil-photo:hover::before, .holo-foil-photo:focus-within::before, .holo-foil-photo[data-holo-hover]::before { opacity: 0; }
  .holo-foil-photo:hover::after, .holo-foil-photo:focus-within::after, .holo-foil-photo[data-holo-hover]::after { opacity: 0; }
  .holo-sparkle:hover::after, .holo-sparkle:focus-within::after, .holo-sparkle[data-holo-hover]::after { opacity: 0; }
}

/* ===== storefront (Agent B) =====
   Product photos are literal trading cards, so the shop grid, the "More
   like this" rail, the item detail hero and the homepage fan all get the
   photo foil + ring treatment. .card-art and .fan-card are already
   position:relative (or become it below) with overflow:hidden in relic.css,
   so the foil pseudo-elements clip to the rounded art; --holo-surface is
   set to match each element's own background so the ring's padding-box
   layer does not show a paper-colored seam against a darker/lighter tile. */

/* .card-art already position:relative; overflow:hidden; background:
   var(--paper-2) in relic.css - just point the ring's surface at it. */
.card-art { --holo-surface: var(--paper-2); }

/* .fan-card keeps relic.css's own always-on rotate+translate transform (the
   fan spread, three breakpoints) untouched -- the holo classes go on an
   inner wrapper instead (see page-index.js paintFan), sized to fill the
   card. .fan-card already clips (overflow:hidden, border-radius) and its
   background is var(--paper), which is also the default --holo-surface, so
   the ring needs no surface override here. */
.fan-card > .holo { display: block; width: 100%; height: 100%; border-radius: inherit; }

/* .detail-art is position:sticky at >=900px (relic.css) - already a valid
   positioned host for the foil pseudo-elements, so leave that alone. Below
   900px relic.css leaves it position:static, which would let the
   absolutely-positioned foil escape the rounded box: give it position:
   relative there only, so the desktop sticky sidebar behavior is untouched. */
.detail-art { --holo-surface: var(--paper-2); }
@media (max-width: 899px) {
  .detail-art { position: relative; }
}

/* The hero becomes a .holo host so the light tracks across the whole
   section; it paints nothing by itself (no foil/ring primitive on the hero
   element), it only feeds the --holo-ang/--holo-bands custom properties to
   anything that inherits them. relic.css leaves .hero position:static. */
.hero.holo { position: relative; }

