/* ============================================================
   03 — GRAPHICS
   The visual layer: atmosphere, glass, gradient edges, glow,
   beams, rings, tickers. Everything here is decoration that
   earns its place — remove this file and the site still works.
   Every effect is transform/opacity only, and every one of them
   is switched off under prefers-reduced-motion.
   ============================================================ */

/* ---------- 1. AURORA  (retired) -----------------------------
   Superseded by .mesh in 08-showcase.css, which does the same job
   with four sources instead of three and blends them on screen.
   The class is kept as an alias so any page still asking for
   .aurora gets the newer field rather than nothing.
   ------------------------------------------------------------ */
.aurora{position:absolute;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.aurora i{
  position:absolute;display:block;width:52vmax;height:52vmax;border-radius:50%;
  filter:blur(62px);opacity:.45;mix-blend-mode:screen;
}
.aurora i:nth-child(1){background:radial-gradient(circle,rgba(34,224,255,.8),transparent 64%);top:-20vmax;left:-12vmax}
.aurora i:nth-child(2){background:radial-gradient(circle,rgba(46,107,255,.85),transparent 62%);top:-26vmax;left:24vmax}
.aurora i:nth-child(3){background:radial-gradient(circle,rgba(155,107,255,.75),transparent 64%);top:-14vmax;right:-14vmax}
html[data-theme="day"] .aurora i{opacity:.24;mix-blend-mode:multiply}

/* ---------- 2. GRAIN : stops gradients banding -------------- */
.grain{
  /*  mix-blend-mode on a fixed, full-viewport layer forces the compositor to
      read the whole frame back. Plain alpha at a slightly higher opacity is
      visually the same at this strength and costs nothing. */
  position:fixed;inset:0;pointer-events:none;z-index:2;opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- 3. GRID : perspective floor / blueprint --------- */
.gridfield{
  position:absolute;inset:0;pointer-events:none;z-index:0;overflow:hidden;
}
.gridfield::before{
  content:"";position:absolute;inset:-2px;
  background-image:
    linear-gradient(to right,var(--line-2) 1px,transparent 1px),
    linear-gradient(to bottom,var(--line-2) 1px,transparent 1px);
  background-size:56px 56px;
  -webkit-mask-image:radial-gradient(ellipse 80% 60% at 50% 0%,#000 20%,transparent 78%);
  mask-image:radial-gradient(ellipse 80% 60% at 50% 0%,#000 20%,transparent 78%);
}
.gridfield.floor::before{
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 70%);
  mask-image:linear-gradient(180deg,transparent,#000 70%);
}

/* ---------- 4. GLASS : the panel material ------------------- */
/*  NOTE ON backdrop-filter
    It is beautiful and it is expensive: the compositor has to read back the
    backdrop for every element that uses it, on every frame it is composited.
    With dozens of panels over a drifting gradient that is what makes a long
    page stutter.

    So it lives on the CHROME that floats over moving content — the header, the
    sidebar, the sticky page header, the hero console, modals and drawers — and
    the ordinary panel gets a slightly richer background instead. Over a mostly
    flat dark backdrop the two are nearly indistinguishable; the frame rate is
    not. Opt back in per element with .frosted. */
.glass,.panel{
  position:relative;
  background:linear-gradient(160deg,rgba(255,255,255,.055),rgba(255,255,255,.028));
  border:1px solid var(--line);border-radius:var(--r);
  contain:layout;   /* layout only: paint containment would clip tooltips and popovers */
}
html[data-theme="day"] .glass,html[data-theme="day"] .panel{
  background:linear-gradient(160deg,rgba(255,255,255,.96),rgba(255,255,255,.86));
}
.frosted{
  backdrop-filter:blur(16px) saturate(150%);
  -webkit-backdrop-filter:blur(16px) saturate(150%);
}

/* ============================================================
   FRAME HOVER
   Every framed surface answers the pointer: a light that follows
   the cursor across it, a brighter edge, and a small lift.

   The sheen is a background LAYER, not a pseudo-element. Half of
   these components already use ::before or ::after for something
   else (the stat's top rule, the account row's hairline, the glass
   card's top glow), and a universal pseudo would have quietly
   overwritten them.

   --sheen is registered, so its 0 -> 1 is transitionable; --mx and
   --my come from one delegated pointermove listener in showcase.js.
   With no JavaScript at all the light simply sits in the middle of
   the card and the hover still works.
   ============================================================ */
@property --sheen{syntax:"<number>";initial-value:0;inherits:false}

.panel,.glasscard,.step,.plan,.fx,.acct,.rc,.stat{
  --sheen:0;
  transition:
    --sheen .32s ease,
    transform .3s cubic-bezier(.2,.7,.3,1),
    border-color .3s ease,
    box-shadow .3s ease;
}
.panel:hover,.glasscard:hover,.step:hover,.plan:hover,.fx:hover,
.acct:hover,.rc:hover,.stat:hover{
  --sheen:1;
  transform:translateY(-3px);
  border-color:rgba(46,107,255,.42);
  box-shadow:0 22px 54px -32px rgba(46,107,255,.85);
}
/*  the light itself, layered under whatever background the component has */
.panel:hover,.glasscard:hover,.step:hover,.plan:hover,.fx:hover,
.acct:hover,.rc:hover,.stat:hover{
  background-image:
    radial-gradient(320px circle at var(--mx,50%) var(--my,50%),
      rgba(46,107,255,calc(.20 * var(--sheen))),
      rgba(34,224,255,calc(.07 * var(--sheen))) 38%,
      transparent 66%),
    linear-gradient(160deg,rgba(255,255,255,.055),rgba(255,255,255,.028));
}
html[data-theme="day"] .panel:hover,html[data-theme="day"] .glasscard:hover,
html[data-theme="day"] .step:hover,html[data-theme="day"] .plan:hover,
html[data-theme="day"] .fx:hover,html[data-theme="day"] .stat:hover{
  background-image:
    radial-gradient(320px circle at var(--mx,50%) var(--my,50%),
      rgba(35,82,224,calc(.13 * var(--sheen))), transparent 64%),
    linear-gradient(160deg,rgba(255,255,255,.96),rgba(255,255,255,.86));
  border-color:rgba(35,82,224,.3);
  box-shadow:0 20px 44px -30px rgba(35,82,224,.5);
}

/*  A row in a table is a frame too, but it must not lift — a moving row in a
    list of executions is disorienting. It gets the light and the edge only. */
.tbl tbody tr{transition:background .22s ease,box-shadow .22s ease}
.tbl tbody tr:hover{box-shadow:inset 2px 0 0 0 var(--blue)}

@media(prefers-reduced-motion:reduce){
  .panel,.glasscard,.step,.plan,.fx,.acct,.rc,.stat{transition:none}
  .panel:hover,.glasscard:hover,.step:hover,.plan:hover,.fx:hover,
  .acct:hover,.rc:hover,.stat:hover{transform:none}
}
@media(hover:none){
  /*  no hover on a touch screen: the :hover state would stick after a tap */
  .panel:hover,.glasscard:hover,.step:hover,.plan:hover,.fx:hover,
  .acct:hover,.rc:hover,.stat:hover{
    transform:none;box-shadow:none;background-image:none;
  }
}
/* opt-in edge light. NOT applied to every panel — ::after is needed by
   .beam and .scan, and three rules fighting over one pseudo-element is how
   the hero card broke. One class, used deliberately. */
.acct,.tbl-wrap{position:relative}
.acct::after,.tbl-wrap::after{
  content:"";position:absolute;left:16px;right:16px;top:0;height:1px;border-radius:2px;z-index:1;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.26),transparent);
  pointer-events:none;
}
html[data-theme="day"] .acct::after,html[data-theme="day"] .tbl-wrap::after{display:none}
.glass.flat,.panel.flat{background:var(--deep)}

/* ---------- 5. EDGE : 1px gradient border ------------------- */
.edge{position:relative}
.edge::before{
  content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;
  background:var(--spectrum);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
  pointer-events:none;opacity:.85;
}

/* ---------- 6. BEAM : rotating conic edge on the hero card -- */
@property --beam-a{syntax:"<angle>";initial-value:0deg;inherits:false}
.beam{position:relative}
.beam::before{
  content:"";position:absolute;inset:-1px;border-radius:inherit;padding:1.5px;pointer-events:none;
  background:conic-gradient(from var(--beam-a),transparent 0deg,var(--cyan) 40deg,var(--blue) 92deg,var(--violet) 145deg,transparent 205deg,transparent 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
  animation:beamspin 7s linear infinite;
}
@keyframes beamspin{to{--beam-a:360deg}}

/* ---------- 7. GLOW ---------------------------------------- */
.glow{box-shadow:var(--glow-blue)}
.glow-soft{box-shadow:var(--glow-soft)}
.halo{position:relative}
.halo::before{
  content:"";position:absolute;inset:-40% -12%;z-index:-1;pointer-events:none;
  background:radial-gradient(ellipse at 50% 50%,rgba(46,107,255,.30),transparent 66%);
  filter:blur(30px);
}

/* ---------- 8. GRADIENT TEXT ------------------------------- */
.grad{
  background:var(--spectrum);-webkit-background-clip:text;background-clip:text;
  color:transparent;
}
.grad-sweep{
  background:linear-gradient(100deg,var(--text) 0%,var(--text) 34%,var(--cyan) 47%,var(--blue) 55%,var(--text) 70%,var(--text) 100%);
  background-size:280% 100%;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  animation:sweeptext 7s ease-in-out infinite;
}
@keyframes sweeptext{0%,100%{background-position:130% 0}50%{background-position:-30% 0}}

/* ---------- 9. SHINE : cursor-following highlight on cards -- */
.shine{position:relative;overflow:hidden}
.shine::before{
  content:"";position:absolute;width:280px;height:280px;border-radius:50%;pointer-events:none;
  left:var(--mx,50%);top:var(--my,50%);transform:translate(-50%,-50%);
  background:radial-gradient(circle,rgba(46,107,255,.20),transparent 62%);
  opacity:0;transition:opacity .28s;
}
.shine:hover::before{opacity:1}

/* ---------- 10. TILT : 3D on pointer ----------------------- */
/*  no will-change: .tilt only moves while the pointer is over it, and a
    permanently promoted layer per card costs memory for nothing */
.tilt{transform-style:preserve-3d;transition:transform .18s ease-out}
.tilt > *{transform:translateZ(24px)}

/* ---------- 11. REVEAL : scroll entrance ------------------- */
html.js .reveal{opacity:0;transform:translateY(20px);transition:opacity .6s cubic-bezier(.2,.7,.3,1),transform .6s cubic-bezier(.2,.7,.3,1)}
html.js .reveal.in{opacity:1;transform:none}
.reveal{transition:opacity .6s cubic-bezier(.2,.7,.3,1),transform .6s cubic-bezier(.2,.7,.3,1)}
.reveal[data-d="1"]{transition-delay:.07s}
.reveal[data-d="2"]{transition-delay:.14s}
.reveal[data-d="3"]{transition-delay:.21s}
.reveal[data-d="4"]{transition-delay:.28s}

/* ---------- 12. MARQUEE : execution ticker ----------------- */
.marquee{
  overflow:hidden;position:relative;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.marquee-track{display:flex;gap:0;width:max-content;animation:slide 46s linear infinite}
.marquee:hover .marquee-track{animation-play-state:paused}
@keyframes slide{to{transform:translateX(-50%)}}
.mq{
  display:flex;align-items:center;gap:9px;padding:0 20px;height:40px;white-space:nowrap;
  font:400 12px/1 var(--mono);color:var(--dim);border-right:1px solid var(--line-2);
}
.mq b{color:var(--text);font-weight:500}

/* ---------- 13. RING : conic progress gauge ---------------- */
.ring{
  --p:0;--size:74px;
  width:var(--size);height:var(--size);border-radius:50%;display:grid;place-items:center;
  background:conic-gradient(var(--blue) calc(var(--p)*1%),rgba(255,255,255,.07) 0);
  position:relative;flex:0 0 auto;
}
.ring::before{content:"";position:absolute;inset:7px;border-radius:50%;background:var(--deep)}
.ring span{position:relative;font:500 15px/1 var(--mono)}
.ring.mint{background:conic-gradient(var(--mint) calc(var(--p)*1%),rgba(255,255,255,.07) 0)}
.ring.rose{background:conic-gradient(var(--rose) calc(var(--p)*1%),rgba(255,255,255,.07) 0)}

/* ---------- 14. SCANLINE : terminal texture ---------------- */
.scan{
  background-image:repeating-linear-gradient(180deg,rgba(255,255,255,.03) 0 1px,transparent 1px 3px);
}

/* ---------- 15. ORB : small animated status light ---------- */
.orb{
  width:9px;height:9px;border-radius:50%;flex:0 0 auto;position:relative;background:var(--faint);
}
.orb.live{background:var(--mint)}
.orb.live::after{
  content:"";position:absolute;inset:-4px;border-radius:50%;border:1px solid var(--mint);
  animation:ping 2s ease-out infinite;
}
.orb.warn{background:var(--gold)}
.orb.down{background:var(--rose)}
.orb.off{background:var(--faint);opacity:.5}
@keyframes ping{0%{transform:scale(.7);opacity:.9}100%{transform:scale(1.7);opacity:0}}

/* ---------- 16. NOISE-FREE DIVIDER ------------------------- */
.rule-grad{height:1px;border:0;background:linear-gradient(90deg,transparent,var(--line),transparent);margin:0}

/* ---------- 17. COUNTER ------------------------------------ */
.count{font-variant-numeric:tabular-nums}

/* ---------- 18. LOADER ------------------------------------- */
.boot{
  position:fixed;inset:0;z-index:200;background:var(--void);display:grid;place-items:center;
  transition:opacity .45s ease,visibility .45s;
  /*  CSS-only failsafe. JS normally removes this the moment the page is ready,
      but if the script never runs the overlay must still get out of the way.
      A loader that can trap the page is worse than no loader. */
  animation:boot-give-up .4s ease 2.4s forwards;
}
@keyframes boot-give-up{to{opacity:0;visibility:hidden;pointer-events:none}}
.boot.done{opacity:0;visibility:hidden}
.boot .bar{width:180px;height:2px;background:var(--line);overflow:hidden;border-radius:2px}
.boot .bar i{display:block;height:100%;width:40%;background:var(--spectrum);animation:boot 1s ease-in-out infinite}
@keyframes boot{0%{transform:translateX(-110%)}100%{transform:translateX(280%)}}

/* ============================================================
   REDUCED MOTION — the whole graphics layer goes still
   ============================================================ */
@media(max-width:700px){
  .gridfield{opacity:.5}
}

@media(prefers-reduced-motion:reduce){
  .aurora i,.beam::before,.marquee-track,.grad-sweep,.orb.live::after,.boot .bar i{animation:none !important}
  .split-bar .mask{transition:none !important}
  .reveal{opacity:1 !important;transform:none !important;transition:none !important}
  .tilt{transform:none !important;transition:none !important}
  *{scroll-behavior:auto}
}

/* low-power fallback: no backdrop-filter support */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .glass,.panel{background:var(--deep)}
}

/* ---------- phone budget: two blobs, lighter blur ----------- */
@media(max-width:700px){
  .aurora{height:min(620px,70vh)}
  .aurora i{filter:blur(60px);opacity:.42}
  .aurora i:nth-child(3){display:none}
  .gridfield::before{background-size:40px 40px}
}
