/* Apex Growth Corp — effects library styles (agc-fx.css).
   Pairs with /assets/agc-fx.js. Loading this file on its own changes nothing:
   every rule below is keyed to a class the script adds, or to a wrapper element
   the script creates. Nothing here can hide content if the JS never runs.

   Colours reuse the site tokens (--gold, --border-gold, --ease-spring …) with
   literal fallbacks, so the file also works on a page that has not declared
   them. It never restyles anything global — no element selectors, no resets. */

/* ==================================================================== *
 * 1. data-fx="shiny" — Shiny Pill
 *
 * Two stacked copies of the text. The top copy is painted in the shine
 * colour and revealed through a narrow mask gradient; sliding the mask
 * from 200% to -100% sweeps the sheen across the word. The base copy
 * underneath is always fully opaque, so the text is never dependent on
 * the animation for legibility.
 * ==================================================================== */
[data-fx="shiny"] {
  --agc-shine-color: var(--gold-bright, #F4D78A);
  --agc-shine-speed: 5s;
  --agc-shine-width: 14%;
  --agc-shine-angle: 100deg;
  /* Deliberately NOT position/display — the host keeps whatever the page gave
     it. See .agc-shiny below. */
}

/* The wrapper the script builds inside the host owns the positioning context.
   Putting it on the host instead would (a) override an author's
   position:absolute — the proof pill is absolutely placed over its image, and
   forcing relative dropped it back into the flow — and (b) resolve the sheen
   against the host's PADDING box, so on any padded host the sheen landed
   offset from the text and read as doubled. A shrink-to-fit wrapper sitting in
   the content box has neither problem. */
.agc-shiny {
  position: relative;
  display: inline-block;
}

.agc-shiny__base {
  display: inline-block;
}

.agc-shiny__shine {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  display: inline-block;
  color: var(--agc-shine-color);
  white-space: inherit;
  pointer-events: none;             /* never intercept a click on the text below */
  -webkit-user-select: none;
  user-select: none;                /* and never turn one word into two on copy */

  -webkit-mask-image: linear-gradient(
    var(--agc-shine-angle),
    transparent calc(50% - var(--agc-shine-width)),
    #000 50%,
    transparent calc(50% + var(--agc-shine-width))
  );
  mask-image: linear-gradient(
    var(--agc-shine-angle),
    transparent calc(50% - var(--agc-shine-width)),
    #000 50%,
    transparent calc(50% + var(--agc-shine-width))
  );
  -webkit-mask-size: 220% 100%;
  mask-size: 220% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;

  animation: agc-shine var(--agc-shine-speed) linear infinite;
}

@keyframes agc-shine {
  from { -webkit-mask-position: 200% 0;  mask-position: 200% 0; }
  to   { -webkit-mask-position: -100% 0; mask-position: -100% 0; }
}

/* Off-screen: park the animation rather than let the style engine keep
   ticking it. The class is toggled by a shared IntersectionObserver. */
[data-fx="shiny"].agc-fx-paused .agc-shiny__shine {
  animation-play-state: paused;
}

/* ==================================================================== *
 * 2. data-fx="curved-marquee" — Curved Marquee
 * ==================================================================== */
.agc-fx-marquee {
  display: block;
  width: 100%;
  /* Horizontal drag belongs to the marquee, vertical scroll belongs to the
     page — without this a touch drag would fight the scroller. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}

.agc-fx-marquee svg {
  display: block;
  width: 100%;
  height: auto;   /* the viewBox carries the aspect ratio; no JS resize needed */
}

.agc-fx-marquee__text {
  font-family: var(--serif, Georgia, serif);
  font-size: 120px;               /* user units inside the viewBox, not CSS px */
  font-weight: 600;
  font-style: italic;
  letter-spacing: 0.005em;
  fill: var(--gold, #C9A84C);
}

/* data-variant="outline" — hairline lettering, for use over imagery */
.agc-fx-marquee--outline .agc-fx-marquee__text {
  fill: none;
  stroke: var(--gold, #C9A84C);
  stroke-width: 1.1;
  stroke-opacity: 0.85;
}

/* ==================================================================== *
 * 3. data-fx="glitter" — Glitter Wrap
 *
 * Default is "under": the canvas sits behind the host's own content but in
 * front of its background, and `isolation` keeps that negative z-index from
 * escaping into the parent stacking context. data-layer="over" puts it on
 * top in screen blend instead, for laying glitter across imagery.
 * ==================================================================== */
.agc-fx-glitter {
  position: relative;
  isolation: isolate;
}

.agc-fx-glitter__canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  z-index: -1;
}

.agc-fx-glitter--over > .agc-fx-glitter__canvas {
  z-index: 2;
  mix-blend-mode: screen;
}

/* ==================================================================== *
 * 4. data-fx="link-preview" — Link Preview
 *
 * The card is a child of the anchor so it tracks the link through reflow,
 * text wrapping and responsive layout without any scroll bookkeeping.
 * pointer-events:none is load-bearing: the card overlaps the anchor's own
 * hit area and must never swallow the click it is advertising.
 * ==================================================================== */
.agc-fx-lp {
  position: relative;
}

.agc-fx-lp__card {
  --agc-lp-w: 224px;
  --agc-lp-x: 0px;                /* viewport-edge correction, set by JS */
  position: absolute;
  left: 50%;
  bottom: calc(100% + 14px);
  width: var(--agc-lp-w);
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  perspective: 640px;             /* gives the inner lean real depth */
  transform-origin: 50% 100%;
  transform: translate(calc(-50% + var(--agc-lp-x)), 12px) scale(0.86);
  transition:
    opacity 0.16s ease,
    transform 0.44s var(--ease-spring, cubic-bezier(0.34, 1.56, 0.64, 1));
  will-change: transform, opacity;
}

.agc-fx-lp__card.is-on {
  opacity: 1;
  transform: translate(calc(-50% + var(--agc-lp-x)), 0) scale(1);
}

/* Not enough headroom above the link — flip beneath it. */
.agc-fx-lp__card--below {
  bottom: auto;
  top: calc(100% + 14px);
  transform-origin: 50% 0;
  transform: translate(calc(-50% + var(--agc-lp-x)), -12px) scale(0.86);
}

.agc-fx-lp__card--below.is-on {
  transform: translate(calc(-50% + var(--agc-lp-x)), 0) scale(1);
}

.agc-fx-lp__inner {
  display: block;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--border-gold, rgba(201, 168, 76, 0.22));
  background: var(--black-2, #0f0f0f);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);
  transform-origin: 50% 100%;
  will-change: transform;
}

.agc-fx-lp__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

/* ==================================================================== *
 * 5. data-fx="liquid" — Liquid Distortion
 *
 * The <img> stays in the flow, visible, at full opacity, forever. The canvas
 * is an overlay sized in JS to the image's rendered box and cross-faded in
 * only after every WebGL probe has passed. If the context is refused or lost,
 * the canvas is removed and what remains is the untouched photograph.
 * ==================================================================== */
.agc-fx-liquid,
.agc-fx-liquid-wrap {
  position: relative;
}

.agc-fx-liquid-wrap {
  display: inline-block;
  max-width: 100%;
}

.agc-fx-liquid__canvas {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  pointer-events: none;           /* the host element handles the pointer */
  opacity: 0;
  transition: opacity 0.4s ease;
  border-radius: inherit;
}

.agc-fx-liquid--on > .agc-fx-liquid__canvas {
  opacity: 1;
}

/* ==================================================================== *
 * 6. Reduced motion
 *
 * Each effect degrades to a legible static state — never to a blank one.
 * The JS honours the same query independently; these rules are the belt to
 * its braces, and cover the case where the preference flips after load.
 * ==================================================================== */
@media (prefers-reduced-motion: reduce) {
  /* Shiny: drop the sheen copy entirely, leaving the plain base text. */
  .agc-shiny__shine {
    animation: none;
    -webkit-mask-image: none;
    mask-image: none;
    opacity: 0;
  }

  /* Marquee: the ribbon still renders, curved and legible, it just holds
     still (the JS never starts a loop or binds a drag). */

  /* Link preview: appears and disappears, without spring, scale or lean. */
  .agc-fx-lp__card,
  .agc-fx-lp__card--below {
    transition: opacity 0.12s linear;
    transform: translate(calc(-50% + var(--agc-lp-x)), 0);
  }
  .agc-fx-lp__card.is-on,
  .agc-fx-lp__card--below.is-on {
    transform: translate(calc(-50% + var(--agc-lp-x)), 0);
  }

  /* Liquid: the JS never initialises, so no canvas exists. This is purely a
     guard against one being left behind by a preference change mid-session. */
  .agc-fx-liquid__canvas {
    display: none;
  }
}

/* ==================================================================== *
 * 7. Forced colours
 *
 * In a high-contrast mode the decorative layers carry no information and
 * their colours are not ours to choose. Hide them and keep the content.
 * ==================================================================== */
@media (forced-colors: active) {
  .agc-shiny__shine,
  .agc-fx-glitter__canvas,
  .agc-fx-liquid__canvas {
    display: none;
  }
  .agc-fx-lp__inner {
    border-color: CanvasText;
  }
}

/* ═══ 6. Spotlight ═══════════════════════════════════════════════════════
   Dim base + bright clone revealed through a cursor-tracked radial mask. */
.agcfx-spot{position:relative;display:block}
.agcfx-spot .agcfx-spot-base{color:var(--fx-dim,rgba(245,245,240,.5))}
/* .22 was the dramatic setting, and it is the wrong trade: a headline nobody has
   hovered yet sits at ~1.7:1 against this black, under even the 3:1 large-text
   floor, so the copy is barely readable until the pointer happens to cross it —
   and never readable at all on a phone. .5 measures ~4.9:1, which the static
   branch below already relies on. The spotlight now ADDS emphasis instead of
   being required to make the words legible. */
.agcfx-spot .agcfx-spot-bright{
  position:absolute;inset:0;color:var(--fx-bright,#F5F5F0);pointer-events:none;
  -webkit-mask-image:radial-gradient(circle var(--fx-r,0px) at var(--fx-x,50%) var(--fx-y,50%),#000,#000 var(--fx-core,18%),transparent 100%);
          mask-image:radial-gradient(circle var(--fx-r,0px) at var(--fx-x,50%) var(--fx-y,50%),#000,#000 var(--fx-core,18%),transparent 100%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
/* Static fallback — the class the script adds when it takes the reduced-motion
   or coarse-pointer branch. Nothing will ever move the light again, so the dim
   base has to be legible on its own: .5 of #F5F5F0 on the black canvas is
   ≈4.9:1, where the animated default (.22) is ≈1.7:1 — below even the 3:1
   large-text floor. Override per page with --fx-dim-static if a lighter or
   darker canvas needs a different floor. */
.agcfx-spot--static .agcfx-spot-base{color:var(--fx-dim-static,rgba(245,245,240,.5))}
/* Belt for a preference flipped AFTER init, when the script already took the
   live pointer branch and no class was added. */
@media (prefers-reduced-motion:reduce){
  .agcfx-spot .agcfx-spot-base{color:var(--fx-dim-static,rgba(245,245,240,.5))}
}
/* Forced-colors + no-mask fallbacks: never leave the text unreadable. */
@supports not (mask-image:radial-gradient(circle,#000,transparent)){
  .agcfx-spot .agcfx-spot-base{color:var(--fx-bright,#F5F5F0)}
  .agcfx-spot .agcfx-spot-bright{display:none}
}
@media (forced-colors:active){
  .agcfx-spot .agcfx-spot-base{color:CanvasText}
  .agcfx-spot .agcfx-spot-bright{display:none}
}

/* ═══ 7. Magnetic ════════════════════════════════════════════════════════
   Element leans toward the cursor; a disc wipes from the entry point.

   The lean is COMPOSED here from custom properties the script writes
   (--fx-mgx/--fx-mgy) — it is never written as an inline `transform`, because
   an inline transform beats every stylesheet rule and would silently delete the
   host's own hover/press state. --fx-lift and --fx-press are free slots for the
   page to drive from its own rules so the two add up instead of fighting:
       .btn:hover{--fx-lift:-2px}   .btn:active{--fx-press:1px}
   A page rule that still writes `transform` directly (e.g.
   `.btn-primary:hover{transform:translateY(-2px)}`, specificity 0-2-0)
   out-specifies the rule below and drops the lean while it applies — port it to
   --fx-lift BEFORE attaching this effect.
   NB: overflow:hidden is required to contain the wipe disc (which is sized to
   2× the host's diagonal), so this effect does not belong on an element whose
   hover paints a glow outside its own box — the glow would be clipped. */
.agcfx-mag{position:relative;overflow:hidden;isolation:isolate;
  will-change:transform;transition:box-shadow .45s cubic-bezier(.16,1,.3,1);
  transform:translate(var(--fx-mgx,0px),
    calc(var(--fx-mgy,0px) + var(--fx-lift,0px) + var(--fx-press,0px)))}
.agcfx-mag>*{position:relative;z-index:1}
.agcfx-mag-disc{
  position:absolute;z-index:0;border-radius:50%;pointer-events:none;
  background:var(--fx-sweep,var(--gold,#C9A84C));
  transform:scale(0);transform-origin:center;
  transition:transform .55s cubic-bezier(.16,1,.3,1)}
.agcfx-mag.is-lit .agcfx-mag-disc{transform:scale(1)}
/* Through the slot, not through `transform` — otherwise the press would cancel
   the lean it is supposed to sit on top of. */
.agcfx-mag:active{--fx-press:1px}
@media (prefers-reduced-motion:reduce){
  /* Zero the magnet's OWN inputs only. `transform:none` here would also wipe a
     lift/press the page composed into the same declaration, i.e. the effect
     would break the host's states in the one mode where it is meant to vanish
     quietly. */
  .agcfx-mag{--fx-mgx:0px;--fx-mgy:0px}
  .agcfx-mag,.agcfx-mag-disc{transition:none!important}
  .agcfx-mag-disc{transform:scale(0)!important}
}
/* The disc carries no information and its colour is not ours to choose in a
   high-contrast mode — the backdrop would be forced to Canvas and the wipe
   would read as a grey blob behind the label. */
@media (forced-colors:active){
  .agcfx-mag-disc{display:none}
}

/* ═══ 8. Scroll highlight ════════════════════════════════════════════════
   Words dim until the block crosses the viewport, then light in sequence.

   The .18 dim is a TRANSIENT state — every word lights as the block crosses the
   viewport, and the script force-completes the sweep at the end of the scroll
   range so nothing can be stranded. It is ≈1.6:1 on black, so only attach this
   to a display line the reader will scroll through; for anything that can sit
   at rest in the dim state, set --fx-dim:rgba(245,245,240,.38) (≈3.2:1). */
.agcfx-sh{color:var(--fx-dim,rgba(245,245,240,.18))}
.agcfx-sh .agcfx-sh-w{
  display:inline-block;color:inherit;
  transition:color .5s cubic-bezier(.16,1,.3,1),opacity .5s cubic-bezier(.16,1,.3,1)}
.agcfx-sh .agcfx-sh-w.is-lit{color:var(--fx-bright,#F5F5F0)}
@media (prefers-reduced-motion:reduce){
  .agcfx-sh,.agcfx-sh .agcfx-sh-w{color:var(--fx-bright,#F5F5F0)!important;transition:none}
}
@media (forced-colors:active){
  .agcfx-sh,.agcfx-sh .agcfx-sh-w{color:CanvasText!important}
}
