/* ==========================================================================
   BORDER BEAM — a light that travels a card's border

   The effect from the supplied BorderBeamPanel / BorderBeam / GlowingShadow
   references, rebuilt for this project's actual architecture: plain CSS and a
   little vanilla JS. There is no React, no Tailwind, no TypeScript and no
   build step in this project (no package.json, no node_modules), so the
   references are used as animation references, which is what they are for.

   The mechanism, and why it looks like cast light rather than a spinning box:

     * a conic gradient carrying two comets, rotated by one custom property
     * cut to a thin ring by a two-layer mask with `mask-composite: exclude`
       (an SVG luminance mask is deliberately avoided — it silently no-ops in
       Chromium)
     * a blurred copy behind the card, read as the light it throws

   THE ROTATION IS A CSS ANIMATION, NOT A JAVASCRIPT LOOP. That is the whole
   reason this can go on every card on the site: `--bb-a` is registered with
   @property, so the browser interpolates it on its own and the page keeps no
   requestAnimationFrame running at all. An earlier version drove one shared
   rAF loop and wrote a property per card per frame; with fifty cards on an
   admin page that is fifty style invalidations every frame, for an effect the
   browser will happily run by itself.

   Nothing here sets width, height, padding, margin, background or position on
   a card. The two layers are absolutely positioned children; the only property
   written to the card itself is `position: relative`, which moves nothing.

   Colour comes from the club's own tokens, so the beam is gold on navy in the
   dark theme and the deeper gold on the light one with no second set of rules.
   ========================================================================== */

/* Registered so it can be *animated*. An unregistered custom property is an
   opaque string to the animation engine and jumps between values instead of
   interpolating; registered as an <angle> it sweeps smoothly. Browsers without
   @property (Firefox before 128) fall back to the static lit border below,
   which is the effect without the motion rather than a broken card. */
@property --bb-a {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

:root {
  /* The two comets. Gold first — the club's colour, and the one the eye should
     land on — with royal blue behind it for depth. Read from the existing
     palette rather than restated, so a change to the club's gold carries here
     on its own. Deliberately not the references' blue/cyan/coral or the
     GlowingShadow demo's full rainbow hue-cycle. */
  --bb-lead: var(--c-gold, #D4AF37);
  --bb-trail: #3E6FB8;
  /* What the head of each comet is brightened *towards*.

     This used to be a literal #ffffff, which is right on navy and exactly
     wrong on white: the brightest point of the beam — the part that makes it
     read as a moving light — was being mixed into the card it was travelling
     over, so the head disappeared and only the dim tail showed. On a pale
     surface the peak has to go the other way, toward the ink. */
  --bb-peak: #ffffff;
  --bb-thickness: 2px;
  --bb-duration: 9s;
  --bb-glow-opacity: 0.26;
}

/* The light theme's gold arrives darker through --c-gold on its own.
   The blue is lifted a step so it does not vanish into a pale card edge. */
:root[data-theme="light"] {
  --bb-trail: #2F5DA8;
  /* Deep navy, not white — see --bb-peak above. */
  --bb-peak: #0F2748;
  /* The cast light behind the box is a blurred dark shape on a bright page,
     which at the dark theme's strength reads as a grey smudge rather than as
     glow. Halved. */
  --bb-glow-opacity: 0.13;
}

/* `position: relative` with no offsets is a no-op for layout — it only makes
   the card the containing block for the two layers. `isolation` keeps their
   z-indexes from reaching outside the card and reordering anything else. */
[data-bb] {
  position: relative;
  isolation: isolate;
}

[data-bb] > .bb-ring,
[data-bb] > .bb-glow {
  position: absolute;
  inset: 0;
  /* Inherited, never hardcoded: whatever radius the card already has is the
     radius the beam follows. This is what keeps a pill-shaped search field,
     a 12px card and a 14px admin panel all correct with one rule. */
  border-radius: inherit;
  pointer-events: none;          /* never in front of a link, field or button */

  /* Two comets 180° apart. Each is a long faint tail easing into a short
     bright head — the tail starts at a 4% mix rather than `transparent`,
     because `transparent` is rgba(0,0,0,0) and a conic ramp through it dips
     visibly grey. Literal rgba first as the fallback for browsers without
     color-mix(); the @supports block below replaces it where they have it. */
  background: conic-gradient(from var(--bb-a),
    transparent 0deg,
    rgba(212, 175, 55, 0.04) 18deg,
    rgba(212, 175, 55, 0.55) 46deg,
    rgba(212, 175, 55, 0.95) 56deg,
    rgba(245, 226, 168, 1) 60deg,
    transparent 63deg,
    transparent 198deg,
    rgba(62, 111, 184, 0.04) 216deg,
    rgba(62, 111, 184, 0.50) 244deg,
    rgba(62, 111, 184, 0.92) 254deg,
    rgba(150, 185, 240, 1) 258deg,
    transparent 261deg,
    transparent 360deg);

  /* The negative delay is what stops a grid of cards turning in lockstep:
     each is started part-way through the same lap. --bb-i is the card's index,
     written once by the script when it attaches — never per frame. */
  animation: bb-spin var(--bb-duration) linear infinite;
  animation-delay: calc(var(--bb-i, 0) * -0.73s);
}

@keyframes bb-spin {
  to { --bb-a: 360deg; }
}

@supports (background: conic-gradient(from 0deg, color-mix(in srgb, red 50%, transparent), transparent)) {
  [data-bb] > .bb-ring,
  [data-bb] > .bb-glow {
    background: conic-gradient(from var(--bb-a),
      transparent 0deg,
      color-mix(in srgb, var(--bb-lead) 4%, transparent) 18deg,
      color-mix(in srgb, var(--bb-lead) 55%, transparent) 46deg,
      var(--bb-lead) 56deg,
      color-mix(in srgb, var(--bb-lead) 78%, var(--bb-peak, #ffffff)) 60deg,
      transparent 63deg,
      transparent 198deg,
      color-mix(in srgb, var(--bb-trail) 4%, transparent) 216deg,
      color-mix(in srgb, var(--bb-trail) 50%, transparent) 244deg,
      var(--bb-trail) 254deg,
      color-mix(in srgb, var(--bb-trail) 74%, var(--bb-peak, #ffffff)) 258deg,
      transparent 261deg,
      transparent 360deg);
  }
}

/* The ring itself. The two masks cancel everywhere except the padding band,
   which leaves exactly a --bb-thickness border of the gradient showing. */
[data-bb] > .bb-ring {
  z-index: 2;                    /* over the card's own 1px border */
  padding: var(--bb-thickness);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  transition: opacity 0.35s ease;
}

/* The cast light. Behind the card, blurred, deliberately faint — this is the
   difference between "premium" and "gaming RGB". */
[data-bb] > .bb-glow {
  z-index: 0;
  filter: blur(12px);
  opacity: var(--bb-glow-opacity);
  transition: opacity 0.35s ease;
}

/* ---------- hover ----------
   The references wind the rotation up on hover. Brightness is changed here
   instead, on purpose: changing `animation-duration` mid-lap makes the comets
   visibly jump as the timeline is remapped, and across a grid of cards that
   reads as a glitch. Brighter is the same message with none of that, and it
   costs one opacity interpolation. Cards do not move, scale or resize. */
@media (hover: hover) and (pointer: fine) {
  [data-bb]:hover > .bb-glow,
  [data-bb]:focus-within > .bb-glow { opacity: calc(var(--bb-glow-opacity) * 2); }
  [data-bb]:hover > .bb-ring,
  [data-bb]:focus-within > .bb-ring { opacity: 1; }
}

/* ---------- pausing ----------
   Two switches, both toggled by a class rather than by per-frame work:
   `.bb-live` is put on a card by an IntersectionObserver while it is on
   screen, and `bb-idle` on <body> while the tab is hidden. A paused CSS
   animation costs nothing at all. */
[data-bb]:not(.bb-live) > .bb-ring,
[data-bb]:not(.bb-live) > .bb-glow,
body.bb-idle [data-bb] > .bb-ring,
body.bb-idle [data-bb] > .bb-glow { animation-play-state: paused; }

/* Turned off where the card would clip it anyway — a container with
   `overflow: hidden` cuts the blur off at its own edge, leaving a hard band
   that looks like a mistake. Detected by the script, not guessed at here. */
[data-bb-glow="off"] > .bb-glow { display: none; }

/* ---------- the search field (Task 1) ----------
   `.nav-search` is a bare flex wrapper with no radius of its own; the shape a
   person sees is the input inside it, which is a pill. `inherit` would give
   the ring square corners around a rounded field, so the one case that cannot
   inherit reads the same token the input uses. Still not a hardcoded demo
   value — change --radius-pill and both follow.

   The field keeps its own width, height, padding, icon, placeholder, border
   and colours; this adds a ring around the box it already occupies. */
.nav-search[data-bb] > .bb-ring,
.nav-search[data-bb] > .bb-glow {
  border-radius: var(--radius-pill);
  /* `.nav-search` is a flex row that also holds the magnifier button, so its
     box is taller than the field inside it — `inset: 0` drew the ring several
     pixels clear of the pill, top and bottom, which reads as a halo rather
     than as a border. The ring is pinned to the field's own height instead,
     measured once by the script; the fallback matches the height the
     stylesheet gives .nav-search-input today. */
  top: var(--bb-field-top, 6px);
  bottom: auto;
  height: var(--bb-field-h, 38px);
}
.nav-search[data-bb] { --bb-thickness: 1.5px; }

/* On a phone the field is collapsed to the magnifier button and the input has
   no width — a ring there would be a stray pill floating in the header. The
   script cannot see that (the element is still in the DOM), so it is answered
   at the same breakpoint the header itself uses. */
@media (max-width: 900px) {
  .nav-search:not(.is-open) > .bb-ring,
  .nav-search:not(.is-open) > .bb-glow { display: none; }
}

/* ---------- the member portal is two screens in one container ----------
   `.member-portal` holds the sign-in card *and*, once signed in, the whole
   member dashboard — the same element, with `dash-mode` added. A ring around
   an entire dashboard is not "an animated border on a card". */
.member-portal.dash-mode > .bb-ring,
.member-portal.dash-mode > .bb-glow { display: none; }

/* ---------- phones ----------
   A 12px blur across a full-width card, on every card of a long list, is the
   expensive half of this effect. The ring is the effect; the glow is garnish. */
@media (max-width: 700px) {
  [data-bb] > .bb-glow { display: none; }
  :root { --bb-thickness: 1.5px; }
}

/* Windows high-contrast mode replaces colours wholesale; a gradient ring there
   is noise over a border the OS is already drawing. */
@media (forced-colors: active) {
  [data-bb] > .bb-ring,
  [data-bb] > .bb-glow { display: none; }
}

/* ---------- reduced motion ----------
   Not removed — parked. The comets stop at an angle that leaves both sitting
   on visible edges, so the card keeps an elegant lit border and simply stops
   moving, which is what the setting asks for. */
@media (prefers-reduced-motion: reduce) {
  [data-bb] > .bb-ring,
  [data-bb] > .bb-glow {
    animation: none;
    --bb-a: 40deg;
  }
  [data-bb] > .bb-glow { display: none; }
}
