@neo-ui-kit/react 1.3.8 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  useRouter
11
11
  } from "./chunks/chunk-IFEK5PRT.js";
12
12
  import {
13
+ AnimatedBorder,
13
14
  Autocomplete,
14
15
  Avatar,
15
16
  BooleanControl,
@@ -61,7 +62,7 @@ import {
61
62
  useSvgId,
62
63
  variantMaterial,
63
64
  vectorElement
64
- } from "./chunks/chunk-FRALT7ER.js";
65
+ } from "./chunks/chunk-L6VCC7AS.js";
65
66
  import {
66
67
  ThemeProvider,
67
68
  createNeonRenderer,
@@ -78,7 +79,7 @@ import {
78
79
  useTabShape,
79
80
  useThemePalette,
80
81
  useTick
81
- } from "./chunks/chunk-YVRQTI7B.js";
82
+ } from "./chunks/chunk-OCH27SNY.js";
82
83
  import {
83
84
  LocaleProvider,
84
85
  addMessages,
@@ -2151,15 +2152,18 @@ function DataTable({
2151
2152
  );
2152
2153
  }, [keyed, cols, query, activeFilters, filters, manualFiltering]);
2153
2154
  const sorted = useMemo2(
2154
- () => sorting.length && !manualSorting ? [...found].sort((a, b) => {
2155
- for (const sort2 of sorting) {
2156
- const column = cols.find((column2) => column2.key === sort2.key);
2157
- const x = cellValue(column, a.row), y = cellValue(column, b.row);
2158
- const order = typeof x === "number" && typeof y === "number" ? Number.isNaN(x) ? Number.isNaN(y) ? 0 : 1 : Number.isNaN(y) ? -1 : x - y : compareText(String(x), String(y));
2159
- if (order) return sort2.direction === "asc" ? order : -order;
2160
- }
2161
- return a.index - b.index;
2162
- }) : found,
2155
+ () => {
2156
+ if (!sorting.length || manualSorting) return found;
2157
+ const rules = sorting.map((sort2) => ({ column: cols.find((column) => column.key === sort2.key), direction: sort2.direction }));
2158
+ return [...found].sort((a, b) => {
2159
+ for (const { column, direction } of rules) {
2160
+ const x = cellValue(column, a.row), y = cellValue(column, b.row);
2161
+ const order = typeof x === "number" && typeof y === "number" ? Number.isNaN(x) ? Number.isNaN(y) ? 0 : 1 : Number.isNaN(y) ? -1 : x - y : compareText(String(x), String(y));
2162
+ if (order) return direction === "asc" ? order : -order;
2163
+ }
2164
+ return a.index - b.index;
2165
+ });
2166
+ },
2163
2167
  [found, sorting, cols, manualSorting]
2164
2168
  );
2165
2169
  const groupColumn = groupBy ? cols.find((column) => column.key === groupBy) : void 0;
@@ -3268,30 +3272,8 @@ var Toast = ({
3268
3272
  );
3269
3273
  };
3270
3274
 
3271
- // src/components/effects/AnimatedBorder/AnimatedBorder.tsx
3272
- import { createElement as createElement7, useRef as useRef13 } from "react";
3273
- function AnimatedBorder({
3274
- as = "div",
3275
- shell = false,
3276
- round = false,
3277
- children,
3278
- ...props
3279
- }) {
3280
- const ref = useRef13(null);
3281
- useBorder(ref, true, shell, round);
3282
- usePauseOffscreen(ref);
3283
- return createElement7(
3284
- as,
3285
- {
3286
- ...mark("AnimatedBorder", props),
3287
- ref
3288
- },
3289
- children
3290
- );
3291
- }
3292
-
3293
3275
  // src/components/effects/Spotlight/Spotlight.tsx
3294
- import { createElement as createElement8 } from "react";
3276
+ import { createElement as createElement7 } from "react";
3295
3277
  function Spotlight({
3296
3278
  as = "div",
3297
3279
  color: color3,
@@ -3300,7 +3282,7 @@ function Spotlight({
3300
3282
  children,
3301
3283
  ...p
3302
3284
  }) {
3303
- return createElement8(
3285
+ return createElement7(
3304
3286
  as,
3305
3287
  {
3306
3288
  ...mark("Spotlight", p),
@@ -3325,10 +3307,10 @@ function Spotlight({
3325
3307
  import {
3326
3308
  Children as Children2,
3327
3309
  cloneElement as cloneElement2,
3328
- createElement as createElement9,
3310
+ createElement as createElement8,
3329
3311
  isValidElement as isValidElement3,
3330
3312
  useEffect as useEffect5,
3331
- useRef as useRef14,
3313
+ useRef as useRef13,
3332
3314
  useState as useState7
3333
3315
  } from "react";
3334
3316
  function Reveal({
@@ -3340,7 +3322,7 @@ function Reveal({
3340
3322
  children,
3341
3323
  ...p
3342
3324
  }) {
3343
- const ref = useRef14(null);
3325
+ const ref = useRef13(null);
3344
3326
  const [shown, setShown] = useState7(false);
3345
3327
  useEffect5(() => {
3346
3328
  const node = ref.current;
@@ -3358,7 +3340,7 @@ function Reveal({
3358
3340
  observer.observe(node);
3359
3341
  return () => observer.disconnect();
3360
3342
  }, [repeat]);
3361
- return createElement9(
3343
+ return createElement8(
3362
3344
  as,
3363
3345
  {
3364
3346
  ...mark("Reveal", p),
@@ -3381,7 +3363,7 @@ function Reveal({
3381
3363
  }
3382
3364
 
3383
3365
  // src/components/effects/GlowText/GlowText.tsx
3384
- import { createElement as createElement10, useRef as useRef15 } from "react";
3366
+ import { createElement as createElement9, useRef as useRef14 } from "react";
3385
3367
  import { jsx as jsx31 } from "react/jsx-runtime";
3386
3368
  function GlowText({
3387
3369
  as = "span",
@@ -3389,9 +3371,9 @@ function GlowText({
3389
3371
  children,
3390
3372
  ...p
3391
3373
  }) {
3392
- const pauseRef = useRef15(null);
3374
+ const pauseRef = useRef14(null);
3393
3375
  usePauseOffscreen(pauseRef);
3394
- return createElement10(
3376
+ return createElement9(
3395
3377
  as,
3396
3378
  {
3397
3379
  ...mark("GlowText", p),
@@ -3424,7 +3406,7 @@ function SignalBars({ level = 3, bars = 4, weak = false, label, ...p }) {
3424
3406
  }
3425
3407
 
3426
3408
  // src/components/effects/Shimmer/Shimmer.tsx
3427
- import { useRef as useRef16 } from "react";
3409
+ import { useRef as useRef15 } from "react";
3428
3410
  import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
3429
3411
  function Shimmer({
3430
3412
  lines = 3,
@@ -3433,7 +3415,7 @@ function Shimmer({
3433
3415
  ...p
3434
3416
  }) {
3435
3417
  const tr2 = useTr();
3436
- const ref = useRef16(null);
3418
+ const ref = useRef15(null);
3437
3419
  usePauseOffscreen(ref);
3438
3420
  return /* @__PURE__ */ jsxs25(
3439
3421
  "div",
@@ -3458,7 +3440,7 @@ function Shimmer({
3458
3440
  }
3459
3441
 
3460
3442
  // src/components/effects/Sparkles/Sparkles.tsx
3461
- import { useRef as useRef17 } from "react";
3443
+ import { useRef as useRef16 } from "react";
3462
3444
  import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
3463
3445
  function Sparkles({
3464
3446
  count = 10,
@@ -3467,7 +3449,7 @@ function Sparkles({
3467
3449
  children,
3468
3450
  ...p
3469
3451
  }) {
3470
- const pauseRef = useRef17(null);
3452
+ const pauseRef = useRef16(null);
3471
3453
  usePauseOffscreen(pauseRef);
3472
3454
  return /* @__PURE__ */ jsxs26(
3473
3455
  "span",
@@ -3503,7 +3485,7 @@ function Sparkles({
3503
3485
  }
3504
3486
 
3505
3487
  // src/components/effects/Beacon/Beacon.tsx
3506
- import { useRef as useRef18 } from "react";
3488
+ import { useRef as useRef17 } from "react";
3507
3489
  import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
3508
3490
  function Beacon({
3509
3491
  active = true,
@@ -3512,7 +3494,7 @@ function Beacon({
3512
3494
  children,
3513
3495
  ...p
3514
3496
  }) {
3515
- const pauseRef = useRef18(null);
3497
+ const pauseRef = useRef17(null);
3516
3498
  usePauseOffscreen(pauseRef);
3517
3499
  return /* @__PURE__ */ jsxs27(
3518
3500
  "span",
@@ -3532,7 +3514,7 @@ function Beacon({
3532
3514
  }
3533
3515
 
3534
3516
  // src/components/effects/Marquee/Marquee.tsx
3535
- import { useRef as useRef19 } from "react";
3517
+ import { useRef as useRef18 } from "react";
3536
3518
  import { jsx as jsx36, jsxs as jsxs28 } from "react/jsx-runtime";
3537
3519
  function Marquee({
3538
3520
  duration = 20,
@@ -3541,7 +3523,7 @@ function Marquee({
3541
3523
  children,
3542
3524
  ...p
3543
3525
  }) {
3544
- const pauseRef = useRef19(null);
3526
+ const pauseRef = useRef18(null);
3545
3527
  const motion = useMotion();
3546
3528
  usePauseOffscreen(pauseRef);
3547
3529
  const updatePause = () => {
@@ -3573,7 +3555,7 @@ function Marquee({
3573
3555
  }
3574
3556
 
3575
3557
  // src/components/artwork/Landscape/Landscape.tsx
3576
- import { useRef as useRef20 } from "react";
3558
+ import { useRef as useRef19 } from "react";
3577
3559
 
3578
3560
  // src/components/artwork/useArtwork.ts
3579
3561
  import { useEffect as useEffect6 } from "react";
@@ -3815,9 +3797,9 @@ var NEAR_RIDGE = [
3815
3797
  [1220, 132]
3816
3798
  ];
3817
3799
  function Landscape({ shade = true, children, ...p }) {
3818
- const pauseRef = useRef20(null);
3800
+ const pauseRef = useRef19(null);
3819
3801
  usePauseOffscreen(pauseRef);
3820
- const ref = useRef20(null);
3802
+ const ref = useRef19(null);
3821
3803
  useArtwork(ref, "landscape", W, H, landscape);
3822
3804
  return /* @__PURE__ */ jsxs29("div", { ...mark("Landscape", p), ref: pauseRef, "data-shade": shade || void 0, children: [
3823
3805
  /* @__PURE__ */ jsx37("canvas", { ref, "aria-hidden": true }),
@@ -3827,7 +3809,7 @@ function Landscape({ shade = true, children, ...p }) {
3827
3809
  }
3828
3810
 
3829
3811
  // src/components/artwork/Planet/Planet.tsx
3830
- import { useRef as useRef21 } from "react";
3812
+ import { useRef as useRef20 } from "react";
3831
3813
  import { jsx as jsx38, jsxs as jsxs30 } from "react/jsx-runtime";
3832
3814
  var W2 = 515;
3833
3815
  var H2 = 114;
@@ -3874,9 +3856,9 @@ var planet = {
3874
3856
  }
3875
3857
  };
3876
3858
  function Planet({ children, ...p }) {
3877
- const pauseRef = useRef21(null);
3859
+ const pauseRef = useRef20(null);
3878
3860
  usePauseOffscreen(pauseRef);
3879
- const ref = useRef21(null);
3861
+ const ref = useRef20(null);
3880
3862
  useArtwork(ref, "planet", W2, H2, planet);
3881
3863
  return /* @__PURE__ */ jsxs30("div", { ...mark("Planet", p), ref: pauseRef, children: [
3882
3864
  /* @__PURE__ */ jsx38("canvas", { ref, "aria-hidden": true }),
@@ -3885,8 +3867,8 @@ function Planet({ children, ...p }) {
3885
3867
  }
3886
3868
 
3887
3869
  // src/components/artwork/NeonWaves/NeonWaves.tsx
3888
- import { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef22 } from "react";
3889
- import { jsx as jsx39, jsxs as jsxs31 } from "react/jsx-runtime";
3870
+ import { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef21 } from "react";
3871
+ import { Fragment as Fragment4, jsx as jsx39, jsxs as jsxs31 } from "react/jsx-runtime";
3890
3872
  var {
3891
3873
  W: W3,
3892
3874
  H: H3,
@@ -3908,16 +3890,16 @@ function NeonWaves({
3908
3890
  comets = 0,
3909
3891
  ...p
3910
3892
  }) {
3911
- const rootRef = useRef22(null);
3912
- const ref = useRef22(null);
3913
- const cometsRef = useRef22(null);
3914
- const lastDraw = useRef22(-1);
3915
- const tracks = useRef22(/* @__PURE__ */ new Map());
3916
- const paths = useRef22(null);
3917
- const cometElements = useRef22(null);
3918
- const size = useRef22({ width: 0, height: 0 });
3919
- const lastTime = useRef22(0);
3920
- const renderer = useRef22(null);
3893
+ const rootRef = useRef21(null);
3894
+ const ref = useRef21(null);
3895
+ const cometsRef = useRef21(null);
3896
+ const lastDraw = useRef21(-1);
3897
+ const tracks = useRef21(/* @__PURE__ */ new Map());
3898
+ const paths = useRef21(null);
3899
+ const cometElements = useRef21(null);
3900
+ const size = useRef21({ width: 0, height: 0 });
3901
+ const lastTime = useRef21(0);
3902
+ const renderer = useRef21(null);
3921
3903
  const motion = useMotion();
3922
3904
  const id = useSvgId();
3923
3905
  const dots = useMemo3(() => {
@@ -3945,16 +3927,16 @@ function NeonWaves({
3945
3927
  }, []);
3946
3928
  useEffect7(() => {
3947
3929
  if (!rootRef.current || !ref.current) return;
3948
- renderer.current = createNeonRenderer(rootRef.current, ref.current, { strands, phase, shape, comets, time: lastTime.current });
3930
+ renderer.current = createNeonRenderer(rootRef.current, ref.current, { strands, phase, shape, comets, dots, time: lastTime.current });
3949
3931
  return () => {
3950
3932
  renderer.current?.dispose();
3951
3933
  renderer.current = null;
3952
3934
  };
3953
3935
  }, []);
3954
3936
  useEffect7(() => {
3955
- renderer.current?.update({ strands, phase, shape, comets, time: lastTime.current });
3956
- }, [strands, phase, shape, comets, motion]);
3957
- usePauseOffscreen(ref);
3937
+ renderer.current?.update({ strands, phase, shape, comets, dots, time: lastTime.current });
3938
+ }, [strands, phase, shape, comets, dots, motion]);
3939
+ usePauseOffscreen(rootRef);
3958
3940
  const paint = (time) => {
3959
3941
  lastTime.current = time;
3960
3942
  if (renderer.current?.ready) return;
@@ -3995,9 +3977,15 @@ function NeonWaves({
3995
3977
  comet.style.opacity = Math.min(1, Math.max(0, (x + 20) / ((W3 + 40) * 0.35))).toFixed(3);
3996
3978
  });
3997
3979
  };
3998
- const painter = useRef22(paint);
3980
+ const painter = useRef21(paint);
3999
3981
  painter.current = paint;
4000
3982
  useDecoration(ref, paint);
3983
+ const star = (d, i, opacity) => /* @__PURE__ */ jsx39("i", { className: "ad-neon-waves-star", style: {
3984
+ left: `${d.x / W3 * 100}%`,
3985
+ top: `${d.y / H3 * 100}%`,
3986
+ width: `${Math.max(1, d.r * 3)}px`,
3987
+ opacity
3988
+ } }, i);
4001
3989
  return /* @__PURE__ */ jsxs31("span", { ...mark("NeonWaves", p), ref: rootRef, "aria-hidden": "true", children: [
4002
3990
  /* @__PURE__ */ jsxs31(
4003
3991
  "svg",
@@ -4030,25 +4018,15 @@ function NeonWaves({
4030
4018
  }
4031
4019
  ),
4032
4020
  comets > 0 && motion && /* @__PURE__ */ jsx39("span", { ref: cometsRef, className: "ad-neon-waves-comets", children: Array.from({ length: comets }, (_, i) => /* @__PURE__ */ jsx39("i", { className: "ad-neon-waves-comet" }, i)) }),
4033
- dots.map((d, i) => /* @__PURE__ */ jsx39(
4034
- "i",
4035
- {
4036
- className: "ad-neon-waves-star",
4037
- style: {
4038
- left: `${d.x / W3 * 100}%`,
4039
- top: `${d.y / H3 * 100}%`,
4040
- width: `${Math.max(1, d.r * 3)}px`,
4041
- opacity: d.o,
4042
- animationDelay: `${-(i % 9) * 0.45}s`
4043
- }
4044
- },
4045
- i
4046
- ))
4021
+ dots.length > 0 && /* @__PURE__ */ jsxs31(Fragment4, { children: [
4022
+ /* @__PURE__ */ jsx39("span", { className: "ad-neon-waves-stars-floor", children: dots.map((d, i) => star(d, i, 0.05)) }),
4023
+ Array.from({ length: 9 }, (_, phase2) => /* @__PURE__ */ jsx39("span", { className: "ad-neon-waves-stars", style: { animationDelay: `${-phase2 * 0.45}s` }, children: dots.map((d, i) => i % 9 === phase2 ? star(d, i, (d.o - 0.05) / 0.95) : null) }, phase2))
4024
+ ] })
4047
4025
  ] });
4048
4026
  }
4049
4027
 
4050
4028
  // src/components/artwork/QuantumField/QuantumField.tsx
4051
- import { useEffect as useEffect8, useRef as useRef23, useState as useState8 } from "react";
4029
+ import { useEffect as useEffect8, useRef as useRef22, useState as useState8 } from "react";
4052
4030
 
4053
4031
  // src/components/artwork/QuantumField/renderer.ts
4054
4032
  var TAU = Math.PI * 2;
@@ -4482,18 +4460,18 @@ function QuantumField({
4482
4460
  }) {
4483
4461
  const theme = useThemePalette();
4484
4462
  const motion = useMotion();
4485
- const canvas = useRef23(null);
4486
- const root = useRef23(null);
4487
- const paint = useRef23(() => void 0);
4488
- const pointer = useRef23({ x: 0, y: 0, active: false });
4489
- const camera = useRef23({ zoom: 3.5, yaw: 0, pitch: 0.16 });
4490
- const drag = useRef23(null);
4491
- const visible = useRef23(false);
4463
+ const canvas = useRef22(null);
4464
+ const root = useRef22(null);
4465
+ const paint = useRef22(() => void 0);
4466
+ const pointer = useRef22({ x: 0, y: 0, active: false });
4467
+ const camera = useRef22({ zoom: 3.5, yaw: 0, pitch: 0.16 });
4468
+ const drag = useRef22(null);
4469
+ const visible = useRef22(false);
4492
4470
  const [onscreen, setOnscreen] = useState8(typeof IntersectionObserver === "undefined");
4493
- const frame = useRef23({ last: 0, time: 0 });
4494
- const connectedAudio = useRef23(null);
4495
- const live = useRef23({ speed, audio, palette, theme });
4496
- const updatePalette = useRef23(() => void 0);
4471
+ const frame = useRef22({ last: 0, time: 0 });
4472
+ const connectedAudio = useRef22(null);
4473
+ const live = useRef22({ speed, audio, palette, theme });
4474
+ const updatePalette = useRef22(() => void 0);
4497
4475
  const followsTheme = !palette?.[0] || !palette?.[1];
4498
4476
  live.current = { speed, audio, palette, theme };
4499
4477
  useEffect8(() => {
@@ -4550,7 +4528,7 @@ function QuantumField({
4550
4528
  };
4551
4529
  const draw = (now) => {
4552
4530
  if (!visible.current && now !== 0) return;
4553
- if (now && now - frame.current.last < 1e3 / FPS[tier]) return;
4531
+ if (now && now - frame.current.last < (renderer ? 1e3 / 60 - 0.5 : 1e3 / FPS[tier])) return;
4554
4532
  if (now) {
4555
4533
  const elapsed = frame.current.last ? Math.min(60, Math.max(0, now - frame.current.last)) : 0;
4556
4534
  frame.current.last = now;
@@ -4735,7 +4713,7 @@ function QuantumField({
4735
4713
  }
4736
4714
 
4737
4715
  // src/components/artwork/QuantumField/QuantumFieldExperience.tsx
4738
- import { forwardRef, useCallback, useEffect as useEffect9, useImperativeHandle, useLayoutEffect as useLayoutEffect4, useRef as useRef24, useState as useState9 } from "react";
4716
+ import { forwardRef, useCallback, useEffect as useEffect9, useImperativeHandle, useLayoutEffect as useLayoutEffect4, useRef as useRef23, useState as useState9 } from "react";
4739
4717
 
4740
4718
  // src/components/artwork/QuantumField/reference.html?raw
4741
4719
  var reference_default = "<!DOCTYPE html>\n<!-- Quantum Fields Contributors, MIT License. Full notice: UPSTREAM-LICENSE.txt in this package. -->\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <title>Quantum Field: Interactive</title>\n <style>\n /* The kit hides this page's own interface, so it loads no web fonts: the scene makes no requests beyond three.js. */\n\n * {\n box-sizing: border-box;\n }\n\n :root {\n --ar: 167; --ag: 139; --ab: 250;\n }\n\n html, body {\n margin: 0;\n overflow: hidden;\n background: transparent !important;\n color-scheme: dark;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n }\n\n canvas {\n display: block;\n }\n\n #scanlines {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: radial-gradient(ellipse at center, transparent 0%, transparent 50%, rgba(0, 0, 0, 0.5) 100%);\n pointer-events: none;\n z-index: 10;\n }\n\n #overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n background: linear-gradient(180deg, rgba(15, 10, 25, 0.97) 0%, rgba(10, 10, 20, 0.98) 100%);\n z-index: 100;\n backdrop-filter: blur(20px);\n transition: opacity 1s ease;\n pointer-events: none;\n /* Allow clicks to pass through to canvas */\n }\n\n .hud-panel {\n background: linear-gradient(165deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.01) 100%);\n border: 1px solid rgba(255, 255, 255, 0.07);\n padding: 55px 75px;\n text-align: center;\n border-radius: 28px;\n box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n /* Re-enable clicks for buttons */\n }\n\n h1 {\n font-family: 'Space Grotesk', sans-serif;\n font-weight: 500;\n letter-spacing: 1px;\n font-size: 34px;\n margin: 0 0 8px 0;\n background: linear-gradient(135deg, #ffffff 0%, rgba(var(--ar),var(--ag),var(--ab),0.75) 40%, rgb(var(--ar),var(--ag),var(--ab)) 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n }\n\n .hud-panel p {\n color: rgba(255, 255, 255, 0.35);\n font-size: 13px;\n margin-bottom: 40px;\n letter-spacing: 0.5px;\n font-weight: 300;\n }\n\n .btn-group {\n display: flex;\n gap: 14px;\n justify-content: center;\n }\n\n button,\n .file-upload {\n background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.12) 0%, rgba(var(--ar),var(--ag),var(--ab),0.06) 100%);\n border: 1px solid rgba(var(--ar),var(--ag),var(--ab),0.25);\n color: rgba(255, 255, 255, 0.85);\n padding: 15px 30px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n letter-spacing: 0.3px;\n border-radius: 14px;\n font-family: 'Inter', sans-serif;\n }\n\n button:hover,\n .file-upload:hover {\n background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.25) 0%, rgba(var(--ar),var(--ag),var(--ab),0.18) 100%);\n border-color: rgba(var(--ar),var(--ag),var(--ab),0.45);\n transform: translateY(-2px);\n box-shadow: 0 12px 35px rgba(139, 92, 246, 0.2);\n }\n\n input[type=\"file\"] {\n display: none;\n }\n\n #status-bar {\n position: absolute;\n bottom: 80px;\n left: 28px;\n color: rgba(255, 255, 255, 0.3);\n font-size: 10px;\n letter-spacing: 0.5px;\n z-index: 200;\n pointer-events: none;\n }\n\n #status-bar span {\n color: rgba(196, 181, 253, 0.7);\n font-weight: 500;\n }\n\n #interaction-hint {\n position: absolute;\n top: 28px;\n width: 100%;\n text-align: center;\n color: rgba(255, 255, 255, 0.5);\n font-size: 11px;\n letter-spacing: 2px;\n pointer-events: none;\n z-index: 9;\n text-transform: lowercase;\n }\n\n #zoom-indicator {\n position: absolute;\n bottom: 80px;\n right: 28px;\n color: rgba(255, 255, 255, 0.3);\n font-size: 10px;\n z-index: 20;\n pointer-events: none;\n }\n\n #zoom-indicator span {\n color: rgba(196, 181, 253, 0.7);\n }\n\n #audio-controls {\n position: fixed;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n gap: 10px;\n align-items: center;\n padding: 12px 20px;\n background: linear-gradient(180deg, rgba(35, 28, 50, 0.88) 0%, rgba(22, 20, 38, 0.92) 100%);\n border: 1px solid rgba(255, 255, 255, 0.05);\n border-radius: 18px;\n z-index: 200;\n opacity: 0;\n pointer-events: none;\n transition: all 0.4s ease;\n backdrop-filter: blur(25px);\n }\n\n #audio-controls.visible {\n opacity: 1;\n pointer-events: auto;\n }\n\n #audio-controls button,\n #audio-controls .file-upload {\n padding: 9px 14px;\n font-size: 11px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(255, 255, 255, 0.07);\n color: rgba(255, 255, 255, 0.65);\n border-radius: 10px;\n }\n\n #play-pause-btn {\n min-width: 78px;\n background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.18) 0%, rgba(var(--ar),var(--ag),var(--ab),0.10) 100%) !important;\n border-color: rgba(var(--ar),var(--ag),var(--ab),0.28) !important;\n color: #e9d5ff !important;\n }\n\n #play-pause-btn.playing {\n background: linear-gradient(135deg, rgba(251, 113, 133, 0.18) 0%, rgba(244, 114, 182, 0.12) 100%) !important;\n border-color: rgba(251, 113, 133, 0.35) !important;\n }\n\n #progress-container {\n width: 85px;\n height: 4px;\n background: rgba(255, 255, 255, 0.06);\n border-radius: 4px;\n cursor: pointer;\n }\n\n #progress-bar {\n height: 100%;\n background: linear-gradient(90deg, rgb(var(--ar),var(--ag),var(--ab)), rgba(var(--ar),var(--ag),var(--ab),0.6), rgba(var(--ar),var(--ag),var(--ab),0.3));\n border-radius: 4px;\n width: 0%;\n }\n\n #time-display {\n color: rgba(255, 255, 255, 0.35);\n font-size: 10px;\n min-width: 62px;\n text-align: center;\n }\n\n #track-name {\n color: rgba(255, 255, 255, 0.3);\n font-size: 10px;\n max-width: 85px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* --- Center Panel Refinement --- */\n .hud-panel {\n background: rgba(15, 10, 25, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.08);\n /* Darkened container */\n padding: 24px;\n /* Reduced padding */\n border-radius: 24px;\n box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);\n backdrop-filter: blur(40px);\n min-width: 420px;\n /* Wider for buttons */\n }\n\n .hud-panel h1,\n .hud-panel p {\n display: none;\n /* Hide title as per screenshot showing only buttons */\n }\n\n .btn-group {\n display: flex;\n gap: 16px;\n justify-content: center;\n width: 100%;\n }\n\n .btn-group button {\n flex: 1;\n background: transparent;\n border: 1px solid rgba(var(--ar),var(--ag),var(--ab),0.3);\n color: rgb(var(--ar),var(--ag),var(--ab));\n padding: 14px 0;\n border-radius: 12px;\n font-size: 12px;\n letter-spacing: 1px;\n text-transform: uppercase;\n font-weight: 600;\n transition: all 0.3s ease;\n }\n\n .btn-group button:hover {\n background: rgba(var(--ar),var(--ag),var(--ab),0.1);\n border-color: rgb(var(--ar),var(--ag),var(--ab));\n box-shadow: 0 0 20px rgba(var(--ar),var(--ag),var(--ab),0.2);\n }\n #interaction-hint, #audio-controls, #status-bar, #zoom-indicator { display: none !important; }\n </style>\n</head>\n\n<body>\n <div id=\"scanlines\" style=\"display:none\"></div>\n <div id=\"interaction-hint\">space \xB7 play/pause &nbsp;\xB7&nbsp; scroll \xB7 zoom &nbsp;\xB7&nbsp; drag \xB7 rotate &nbsp;\xB7&nbsp; F \xB7 focus mode</div>\n <div id=\"overlay\" style=\"display:none\">\n <div class=\"hud-panel\">\n <h1>Quantum Fields</h1>\n <p>Audio-reactive particle simulation</p>\n <div class=\"btn-group\">\n <button id=\"enter-btn\">Enter</button>\n </div>\n </div>\n </div>\n <div id=\"audio-controls\">\n <button id=\"play-pause-btn\" class=\"playing\">\u23F8 pause</button>\n <div id=\"progress-container\">\n <div id=\"progress-bar\"></div>\n </div>\n <span id=\"time-display\">0:00 / 0:00</span>\n <span id=\"track-name\">no track</span>\n <label class=\"file-upload\" title=\"Load audio\">\u{1F4C1}<input type=\"file\" id=\"file-input-2\" accept=\"audio/*\"></label>\n <button id=\"mic-btn-2\" title=\"Microphone\">\u{1F399}</button>\n <button id=\"screenshot-btn\" title=\"Screenshot\">\u{1F4F7}</button>\n <button id=\"fullscreen-btn\" title=\"Fullscreen\">\u26F6</button>\n </div>\n <div id=\"status-bar\"><span>ready</span> \xB7 awaiting input</div>\n <div id=\"zoom-indicator\">zoom <span id=\"zoom-level\">100%</span></div>\n\n <!-- Global Error Handler (Must be before module imports) -->\n <script>\n window.onerror = function (msg, url, lineNo, columnNo, error) {\n const status = document.getElementById('status-bar');\n if (status) {\n status.innerHTML = `ERROR: <span style=\"color:#ff6b6b\">${msg}</span>`;\n status.style.opacity = 1;\n }\n console.error('Global Error:', msg, url, lineNo, error);\n return false;\n };\n // Catch unhandled promise rejections\n window.onunhandledrejection = function (event) {\n const status = document.getElementById('status-bar');\n if (status) {\n status.innerHTML = `ERROR: <span style=\"color:#ff6b6b\">${event.reason}</span>`;\n status.style.opacity = 1;\n }\n console.error('Unhandled Rejection:', event.reason);\n };\n <\/script>\n\n <script type=\"importmap\">\n{\n \"imports\": {\n \"three\": \"https://unpkg.com/three@0.182.0/build/three.module.js\",\n \"three/addons/\": \"https://unpkg.com/three@0.182.0/examples/jsm/\"\n }\n}\n<\/script>\n\n <script type=\"module\">\n import * as THREE from 'three';\n import { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';\n import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';\n import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';\n import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js';\n import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';\n // BokehPass removed - using custom DOF shader\n import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';\n\n function fitPixelRatio() {\n return Math.min(window.devicePixelRatio || 1, 1.5,\n Math.sqrt(2_100_000 / Math.max(1, window.innerWidth * window.innerHeight)));\n }\n\n const CONFIG = {\n maxParticles: 250000,\n maxDensity: 0.14,\n secondaryParticles: 50000,\n initialDensity: 0.043,\n fieldRadius: 90,\n minZoom: 1,\n maxZoom: 250,\n defaultZoom: 200\n };\n let particleBudget = Number(window.frameElement?.getAttribute('data-particle-budget') ?? Infinity);\n particleBudget = Number.isFinite(particleBudget) ? Math.max(0, Math.floor(particleBudget)) : Infinity;\n // Requested number of particles across both layers; null follows the density setting.\n let particleCount = window.frameElement?.getAttribute('data-particle-count');\n particleCount = particleCount != null && Number.isFinite(Number(particleCount)) ? Math.max(0, Math.floor(Number(particleCount))) : null;\n\n const STATE = {\n field: 'Gluon (Strong)',\n density: CONFIG.initialDensity,\n fieldSize: 0.78,\n particleDepth: 0,\n particleScale: 1,\n particleSharpness: 0,\n sensitivity: 1.1,\n trails: 0.66144,\n bloom: 0.3,\n timeScale: 1,\n pixelRatio: fitPixelRatio(),\n chromaticAberration: 0.004,\n filmGrain: 0.001,\n cameraShake: 0.65,\n vortexStrength: 0.73,\n pulseIntensity: 0.15,\n anamorphicStretch: 0.1,\n bassGateThreshold: 0.12,\n bassGateAttack: 0.3,\n bassGateRelease: 0.136,\n bassGateRatio: 2.142,\n bassGateEnabled: false,\n // NEW EFFECTS\n godRaysEnabled: false,\n godRaysIntensity: 0.078,\n godRaysDecay: 0.92,\n dofEnabled: false,\n dofFocus: 0.04,\n dofFocalLength: 0.40,\n dofBokehStrength: 0.36,\n lensFlareEnabled: true,\n lensFlareIntensity: 0.1,\n particleTrailsEnabled: true,\n particleTrailOpacity: 1,\n nebulaEnabled: false,\n nebulaIntensity: 0.3,\n connectionsEnabled: true,\n connectionThreshold: 20.0,\n connectionOpacity: 0.75,\n // === ENHANCEMENT PACK v2.0 ===\n // 1. Motion Blur\n motionBlurEnabled: true,\n motionBlurStrength: 0.1,\n // 2. Adaptive Quality\n adaptiveQualityEnabled: true,\n targetFPS: 45,\n qualityLevel: 1,\n // 3. Onset Detection\n onsetSensitivity: 1,\n onsetDecay: 0.92,\n // 4. Audio-Reactive Camera\n audioCameraEnabled: true,\n audioCameraSmoothing: 0.2,\n audioCameraIntensity: 0.3,\n // 5. Waveform Terrain\n waveformTerrainScale: 1.0,\n waveformTerrainHeight: 20.0\n };\n\n // --- AUDIO SYSTEM ---\n // Spectrum supplied by the host app (e.g. a native audio engine the iframe cannot hear):\n // band levels 0..1, log-spaced from `low` to `high` Hz. It is turned into the same frequency\n // array an analyser would give, so bands, beats, flux, onsets and the terrain all follow it.\n const EXTERNAL = {\n target: null, low: 40, high: 12000,\n sampleRate: 48000, data: new Uint8Array(2048), smoothed: new Float32Array(2048), prev: new Float32Array(2048),\n fresh() { return this.target !== null; },\n set(bands, range) {\n this.target = Array.isArray(bands) && bands.length ? bands.map((v) => Math.max(0, Math.min(1, Number(v) || 0))) : null;\n if (Array.isArray(range) && range[0] > 0 && range[1] > range[0]) [this.low, this.high] = range;\n },\n // Host frames arrive at their own pace (often 20 Hz); easing toward them every rendered\n // frame keeps the flux continuous, like an analyser's smoothing does.\n fill() {\n const bands = this.target, n = bands.length, len = this.data.length, nyquist = this.sampleRate / 2;\n const span = Math.log(this.high / this.low);\n for (let i = 0; i < len; i++) {\n const freq = Math.max(1, (i + 0.5) / len * nyquist);\n const pos = Math.log(freq / this.low) / span * (n - 1);\n let level;\n if (pos <= 0) level = bands[0];\n else if (pos >= n - 1) level = bands[n - 1] * Math.max(0, 1 - Math.log(freq / this.high) / Math.log(nyquist / this.high));\n else { const k = Math.floor(pos), t = pos - k; level = bands[k] * (1 - t) + bands[k + 1] * t; }\n this.smoothed[i] += (level - this.smoothed[i]) * 0.35;\n this.data[i] = Math.min(255, Math.round(this.smoothed[i] * 255));\n }\n }\n };\n\n const AUDIO = {\n ctx: null, analyser: null, source: null, gainNode: null, data: null,\n audioBuffer: null, isPlaying: false, isPaused: false,\n startTime: 0, pauseTime: 0, currentTrackName: 'No Track',\n mode: null, micStream: null,\n bands: { subBass: 0, bass: 0, lowMid: 0, mid: 0, highMid: 0, high: 0, ultraHigh: 0 },\n gatedBands: { subBass: 0, bass: 0, lowMid: 0, mid: 0, highMid: 0, high: 0, ultraHigh: 0 },\n beatThreshold: 0.15, beatDecay: 0.93, beatEnergy: 0,\n lastBeatTime: 0, beatDetected: false, peakHistory: [],\n spectralCentroid: 0, spectralFlux: 0, prevSpectrum: null, active: false, idleCleared: false,\n // === ENHANCEMENT: Onset Detection ===\n onsetEnergy: 0, onsetDetected: false, onsetHistory: [], onsetThreshold: 0,\n // === ENHANCEMENT: Waveform Data ===\n waveformData: new Float32Array(128), waveformSmoothed: new Float32Array(128),\n gateState: {\n isOpen: false, envelope: 0, holdTime: 0, noiseFloor: 0.05,\n calibrationSamples: [], isCalibrating: true, calibrationFrames: 30\n },\n\n init() {\n if (this.ctx) return;\n this.ctx = new (window.AudioContext || window.webkitAudioContext)();\n this.analyser = this.ctx.createAnalyser();\n this.analyser.fftSize = 4096;\n this.analyser.smoothingTimeConstant = 0.65;\n this.data = new Uint8Array(this.analyser.frequencyBinCount);\n this.prevSpectrum = new Float32Array(this.analyser.frequencyBinCount);\n this.active = true;\n },\n\n loadTrack(arrayBuffer, fileName) {\n return new Promise((resolve, reject) => {\n this.ctx.decodeAudioData(arrayBuffer, (buffer) => {\n this.stopCurrentSource();\n this.audioBuffer = buffer;\n this.currentTrackName = fileName || 'Unknown';\n this.mode = 'file';\n this.pauseTime = 0;\n this.resetCalibration();\n resolve(buffer);\n }, reject);\n });\n },\n\n play() {\n if (!this.audioBuffer || this.mode !== 'file' || this.isPlaying) return;\n this.source = this.ctx.createBufferSource();\n this.source.buffer = this.audioBuffer;\n this.source.loop = true;\n this.source.connect(this.analyser);\n this.analyser.connect(this.ctx.destination);\n this.source.start(0, this.pauseTime);\n this.startTime = this.ctx.currentTime - this.pauseTime;\n this.isPlaying = true;\n this.isPaused = false;\n },\n\n pause() {\n if (!this.isPlaying || this.mode !== 'file') return;\n this.pauseTime = this.getCurrentTime();\n this.isPaused = true;\n this.isPlaying = false;\n if (this.source) { try { this.source.stop(); } catch (e) { } this.source.disconnect(); this.source = null; }\n },\n\n togglePlayPause() {\n if (this.mode === 'mic') return false;\n if (this.isPlaying) { this.pause(); return false; }\n else { this.play(); return true; }\n },\n\n getCurrentTime() {\n if (!this.isPlaying) return this.pauseTime;\n if (!this.audioBuffer) return 0;\n return (this.ctx.currentTime - this.startTime) % this.audioBuffer.duration;\n },\n\n getDuration() { return this.audioBuffer ? this.audioBuffer.duration : 0; },\n\n seek(time) {\n if (this.mode !== 'file' || !this.audioBuffer) return;\n const wasPlaying = this.isPlaying;\n if (wasPlaying) this.pause();\n this.pauseTime = Math.max(0, Math.min(time, this.audioBuffer.duration));\n if (wasPlaying) this.play();\n },\n\n stopCurrentSource() {\n if (this.source) { try { this.source.stop(); this.source.disconnect(); } catch (e) { } this.source = null; }\n if (this.gainNode) { try { this.gainNode.disconnect(); } catch (e) { } this.gainNode = null; }\n if (this.micStream) { this.micStream.getTracks().forEach(t => t.stop()); this.micStream = null; }\n this.isPlaying = false; this.isPaused = false; this.pauseTime = 0;\n },\n\n async startMic(preferredLabel = null) {\n console.log('startMic called with preferredLabel:', preferredLabel);\n const status = document.getElementById('status-bar');\n if (status) status.innerHTML = 'SYSTEM: <span>REQUESTING ACCESS...</span>';\n\n try {\n // Ensure context is running\n if (this.ctx.state === 'suspended') {\n console.log('Resuming AudioContext...');\n await this.ctx.resume();\n }\n\n // 1. Get initial stream (activates permissions so labels are visible)\n console.log('Requesting initial getUserMedia...');\n let stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n console.log('Initial stream obtained:', stream.id);\n\n // 2. Search for preferred device (loopback)\n let target = null;\n if (preferredLabel) {\n const devices = await navigator.mediaDevices.enumerateDevices();\n console.log(\"Audio Devices Found:\", devices.length, devices.map(d => `${d.label} (${d.kind})`));\n\n target = devices.find(d => d.kind === 'audioinput' &&\n d.label.toLowerCase().includes(preferredLabel.toLowerCase()));\n }\n\n if (target) {\n console.log(`Switching audio input to: ${target.label} [${target.deviceId}]`);\n stream.getTracks().forEach(t => t.stop()); // Stop generic stream\n\n // Request specific device - being explicit with constraints\n stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n deviceId: { exact: target.deviceId },\n echoCancellation: false,\n noiseSuppression: false,\n autoGainControl: false,\n channelCount: 2\n }\n });\n this.currentTrackName = target.label;\n } else {\n if (preferredLabel) console.warn(`Preferred device '${preferredLabel}' not found. Using default.`);\n // Keep the initial stream\n this.currentTrackName = stream.getAudioTracks()[0]?.label || 'Live Microphone';\n }\n\n this.stopCurrentSource();\n this.micStream = stream;\n this.source = this.ctx.createMediaStreamSource(stream);\n this.gainNode = this.ctx.createGain();\n this.gainNode.gain.value = 3.0; // Boost loopback/mic signal before FFT \u2014 tune via sensitivity slider\n this.source.connect(this.gainNode);\n this.gainNode.connect(this.analyser);\n this.mode = 'mic';\n this.isPlaying = true;\n this.resetCalibration();\n\n if (status) status.innerHTML = 'SYSTEM: <span>LISTENING (' + (preferredLabel ? 'Loopback' : 'Mic') + ')</span>';\n updateUI();\n\n return stream;\n } catch (e) {\n console.error(\"AUDIO.startMic failed:\", e);\n throw e;\n }\n },\n\n resetCalibration() { this.gateState.isCalibrating = true; this.gateState.calibrationSamples = []; },\n\n applyBassGate(rawValue) {\n if (!STATE.bassGateEnabled) return rawValue;\n const { bassGateThreshold: threshold, bassGateAttack: attack, bassGateRelease: release, bassGateRatio: ratio } = STATE;\n if (this.gateState.isCalibrating) {\n this.gateState.calibrationSamples.push(rawValue);\n if (this.gateState.calibrationSamples.length >= this.gateState.calibrationFrames) {\n const sorted = [...this.gateState.calibrationSamples].sort((a, b) => a - b);\n this.gateState.noiseFloor = sorted.slice(0, Math.floor(sorted.length * 0.2)).reduce((a, b) => a + b, 0) / Math.floor(sorted.length * 0.2);\n this.gateState.isCalibrating = false;\n }\n }\n const effectiveThreshold = Math.max(threshold, this.gateState.noiseFloor * 1.5);\n if (rawValue > effectiveThreshold) { this.gateState.isOpen = true; this.gateState.holdTime = 10; }\n else if (rawValue < effectiveThreshold * 0.7 && this.gateState.holdTime <= 0) { this.gateState.isOpen = false; }\n if (this.gateState.holdTime > 0) this.gateState.holdTime--;\n this.gateState.envelope += ((this.gateState.isOpen ? 1.0 : 0.0) - this.gateState.envelope) * (this.gateState.isOpen ? attack : release);\n let output = rawValue;\n if (this.gateState.isOpen && rawValue > effectiveThreshold) {\n output = effectiveThreshold + (rawValue - effectiveThreshold) / ratio;\n }\n return output * this.gateState.envelope;\n },\n\n update() {\n const own = this.active && this.isPlaying && this.source;\n this.externalActive = !own && EXTERNAL.fresh();\n if (!own && !this.externalActive) {\n if (!this.idleCleared) {\n for (const band in this.bands) this.bands[band] = this.gatedBands[band] = 0;\n this.spectralCentroid = this.spectralFlux = 0;\n this.beatDetected = this.onsetDetected = false;\n this.idleCleared = true;\n }\n this.beatEnergy *= this.beatDecay;\n this.onsetEnergy *= STATE.onsetDecay;\n return;\n }\n this.idleCleared = false;\n let data = this.data, prevSpectrum = this.prevSpectrum, nyquist;\n if (this.externalActive) {\n EXTERNAL.fill();\n data = EXTERNAL.data; prevSpectrum = EXTERNAL.prev; nyquist = EXTERNAL.sampleRate / 2;\n } else {\n this.analyser.getByteFrequencyData(this.data);\n nyquist = this.ctx.sampleRate / 2;\n }\n const len = data.length;\n const getFreqIndex = (freq) => Math.round(freq / nyquist * len);\n const ranges = { subBass: [50, 120], bass: [120, 200], lowMid: [200, 450], mid: [450, 2000], highMid: [2000, 3500], high: [3500, 12000], ultraHigh: [12000, 20000] };\n let totalEnergy = 0, weightedFreqSum = 0;\n for (const [band, [low, high]] of Object.entries(ranges)) {\n const lowIdx = getFreqIndex(low), highIdx = getFreqIndex(high);\n let sum = 0;\n for (let i = lowIdx; i < highIdx && i < len; i++) {\n const v = data[i] / 255.0;\n sum += v; totalEnergy += v; weightedFreqSum += v * i;\n }\n const rawBand = sum / (highIdx - lowIdx);\n this.bands[band] = rawBand;\n this.gatedBands[band] = (band === 'subBass' || band === 'bass' || band === 'lowMid') ? this.applyBassGate(rawBand) : rawBand;\n }\n this.spectralCentroid = totalEnergy > 0 ? weightedFreqSum / totalEnergy / len : 0;\n let flux = 0;\n for (let i = 0; i < len; i++) {\n const current = data[i] / 255.0, diff = current - prevSpectrum[i];\n flux += diff > 0 ? diff * diff : 0;\n prevSpectrum[i] = current;\n }\n this.spectralFlux = Math.sqrt(flux / len);\n const currentEnergy = this.gatedBands.bass * 0.6 + this.gatedBands.subBass * 0.4;\n this.peakHistory.push(currentEnergy);\n if (this.peakHistory.length > 50) this.peakHistory.shift();\n const avgEnergy = this.peakHistory.reduce((a, b) => a + b, 0) / this.peakHistory.length;\n const variance = this.peakHistory.reduce((a, b) => a + Math.pow(b - avgEnergy, 2), 0) / this.peakHistory.length;\n const dynamicThreshold = avgEnergy + Math.sqrt(variance) * 1.8;\n const now = performance.now();\n if (currentEnergy > dynamicThreshold && currentEnergy > this.beatThreshold && now - this.lastBeatTime > 120) {\n this.beatDetected = true; this.beatEnergy = 1.0; this.lastBeatTime = now;\n } else { this.beatDetected = false; }\n this.beatEnergy *= this.beatDecay;\n\n // === ENHANCEMENT: Improved Onset Detection ===\n // Uses spectral flux derivative for sharper transient response\n const onsetSens = STATE.onsetSensitivity;\n this.onsetHistory.push(this.spectralFlux);\n if (this.onsetHistory.length > 8) this.onsetHistory.shift();\n const recentAvg = this.onsetHistory.slice(-4).reduce((a, b) => a + b, 0) / 4;\n const olderAvg = this.onsetHistory.slice(0, 4).reduce((a, b) => a + b, 0) / Math.max(1, this.onsetHistory.slice(0, 4).length);\n const onsetDelta = (recentAvg - olderAvg) * onsetSens;\n this.onsetDetected = onsetDelta > 0.015 && now - this.lastBeatTime > 50;\n this.onsetEnergy = this.onsetDetected ? 1.0 : this.onsetEnergy * STATE.onsetDecay;\n\n // === ENHANCEMENT: Waveform Data for Terrain Field ===\n const waveformBins = 128;\n const binSize = Math.floor(len / waveformBins);\n for (let i = 0; i < waveformBins; i++) {\n let sum = 0;\n for (let j = 0; j < binSize; j++) {\n sum += data[i * binSize + j] / 255.0;\n }\n const target = sum / binSize;\n this.waveformSmoothed[i] += (target - this.waveformSmoothed[i]) * 0.3;\n this.waveformData[i] = this.waveformSmoothed[i];\n }\n }\n };\n\n // --- SCENE SETUP ---\n const scene = new THREE.Scene();\n scene.fog = new THREE.FogExp2(0x000508, 0.005);\n const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 800);\n camera.position.set(0, 12, CONFIG.defaultZoom);\n const cameraShakeOffset = new THREE.Vector3();\n const baseCameraPos = camera.position.clone();\n const zoomIndicator = document.getElementById('zoom-level');\n\n const renderer = new THREE.WebGLRenderer({ powerPreference: \"high-performance\", antialias: true, alpha: true });\n renderer.setClearColor(0x000000, 0);\n renderer.setSize(window.innerWidth, window.innerHeight);\n renderer.setPixelRatio(STATE.pixelRatio);\n renderer.toneMapping = THREE.ACESFilmicToneMapping;\n renderer.toneMappingExposure = 2.1;\n document.body.appendChild(renderer.domElement);\n\n const controls = new OrbitControls(camera, renderer.domElement);\n controls.enableDamping = true;\n controls.dampingFactor = 0.06;\n controls.autoRotate = false;\n controls.autoRotateSpeed = 0.3;\n controls.minDistance = CONFIG.minZoom;\n controls.maxDistance = CONFIG.maxZoom;\n controls.maxPolarAngle = Math.PI * 0.85;\n controls.minPolarAngle = Math.PI * 0.15;\n\n controls.addEventListener('change', () => {\n const dist = camera.position.length();\n const zoomPercent = Math.round((1 - (dist - CONFIG.minZoom) / (CONFIG.maxZoom - CONFIG.minZoom)) * 100);\n zoomIndicator.textContent = Math.max(0, Math.min(100, zoomPercent)) + '%';\n baseCameraPos.copy(camera.position);\n });\n\n const raycaster = new THREE.Raycaster();\n const mouse = new THREE.Vector2(9999, 9999);\n const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);\n const mouseWorldPos = new THREE.Vector3();\n let mouseVelocity = new THREE.Vector2();\n let prevMouse = new THREE.Vector2();\n\n window.addEventListener('mousemove', (e) => {\n const newMouse = new THREE.Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);\n mouseVelocity.subVectors(newMouse, prevMouse);\n prevMouse.copy(newMouse);\n mouse.copy(newMouse);\n raycaster.setFromCamera(mouse, camera);\n const target = new THREE.Vector3();\n raycaster.ray.intersectPlane(plane, target);\n if (target) mouseWorldPos.copy(target);\n });\n\n window.addEventListener('keydown', (e) => {\n if (e.key === '+' || e.key === '=') { camera.position.multiplyScalar(0.9); controls.update(); }\n else if (e.key === '-' || e.key === '_') { camera.position.multiplyScalar(1.1); controls.update(); }\n else if (e.key === '0') { camera.position.normalize().multiplyScalar(CONFIG.defaultZoom); controls.update(); }\n else if (e.key === ' ') { e.preventDefault(); togglePlayPause(); }\n else if (e.key === 'f' || e.key === 'F') { STATE.dofEnabled = !STATE.dofEnabled; customDOFPass.enabled = STATE.dofEnabled; }\n });\n\n // Every field type shares one screen-fitted footprint; switching presets never reallocates particles.\n // uFieldFill: share of the viewport the field spans; above 1.0 it runs past the edges.\n const shapeFunction = `\nuniform float uFieldFill, uParticleDepth, uParticleScale;\nfloat particleProximity(vec3 seed) {\n // Most points stay distant; only a small share enters the foreground.\n return pow(seed.z, 4.0);\n}\nfloat particleTime(float time, float proximity) {\n return time * mix(1.0, 0.25 + proximity * 1.35, uParticleDepth);\n}\nfloat particleProjection(float proximity) {\n return 1.0 - mix(-100.0, 125.0, proximity) * uParticleDepth / ${CONFIG.defaultZoom.toFixed(1)};\n}\nvec3 shapedPosition(vec3 seed, float proximity, float time) {\n vec2 plane = seed.xy * 2.0 - 1.0;\n plane += vec2(sin(time * 0.16 + seed.x * 6.28), cos(time * 0.12 + seed.y * 6.28))\n * mix(0.006, 0.045, proximity) * uParticleDepth;\n float projection = particleProjection(proximity);\n // Compensate the footprint for perspective, so nearby points also fill the viewport.\n return vec3(plane * vec2(uViewportAspect, 1.0) * uViewportHalfHeight * uFieldFill * projection,\n (seed.z - 0.5) * 12.0 + mix(-100.0, 125.0, proximity) * uParticleDepth);\n}\nfloat particleEdge(vec3 position, float proximity) {\n vec2 extent = vec2(uViewportAspect, 1.0) * uViewportHalfHeight * uFieldFill * 1.05 * particleProjection(proximity);\n return 1.0 - smoothstep(0.92, 1.05, max(abs(position.x) / extent.x, abs(position.y) / extent.y));\n}`;\n\n const particleLight = `\nuniform float uParticleSharpness;\nvec4 crispParticle(vec3 tint, float radius, float alpha) {\n float core = 1.0 - smoothstep(0.05, 0.2, radius);\n float halo = exp(-radius * radius * 45.0) * 0.22;\n float edge = 1.0 - smoothstep(0.42, 0.5, radius);\n return vec4(mix(tint, vec3(1.0), core * 0.65), clamp(alpha * (core + halo) * edge, 0.0, 1.0));\n}`;\n\n // --- PARTICLE VERTEX SHADER ---\n const vertexShader = `\nuniform float uTime, uPixelRatio, uSizeBase, uNoiseScale, uCurlStrength;\nuniform float uViewportAspect, uViewportHalfHeight;\nuniform vec3 uColor1, uColor2, uColor3;\nuniform float uSubBass, uBass, uLowMid, uMid, uHighMid, uHigh, uUltraHigh;\nuniform float uBeatEnergy, uSpectralCentroid, uSpectralFlux;\nuniform float uOnsetEnergy, uTerrainMode, uTerrainHeight;\nuniform float uVortexStrength, uPulseIntensity, uZoomFactor;\nuniform vec3 uMousePos;\nuniform vec2 uMouseVelocity;\nattribute vec3 aRandom;\nattribute float aPhase, aLayer;\nvarying vec3 vColor;\nvarying float vAlpha, vEnergy, vDepth, vRimLight, vGlint;\n\nvec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\nvec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\nvec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }\nvec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }\n\nfloat snoise(vec3 v) {\n const vec2 C = vec2(1.0/6.0, 1.0/3.0);\n const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n vec3 i = floor(v + dot(v, C.yyy));\n vec3 x0 = v - i + dot(i, C.xxx);\n vec3 g = step(x0.yzx, x0.xyz);\n vec3 l = 1.0 - g;\n vec3 i1 = min(g.xyz, l.zxy);\n vec3 i2 = max(g.xyz, l.zxy);\n vec3 x1 = x0 - i1 + C.xxx;\n vec3 x2 = x0 - i2 + C.yyy;\n vec3 x3 = x0 - D.yyy;\n i = mod289(i);\n vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));\n float n_ = 0.142857142857;\n vec3 ns = n_ * D.wyz - D.xzx;\n vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n vec4 x_ = floor(j * ns.z);\n vec4 y_ = floor(j - 7.0 * x_);\n vec4 x = x_ * ns.x + ns.yyyy;\n vec4 y = y_ * ns.x + ns.yyyy;\n vec4 h = 1.0 - abs(x) - abs(y);\n vec4 b0 = vec4(x.xy, y.xy);\n vec4 b1 = vec4(x.zw, y.zw);\n vec4 s0 = floor(b0) * 2.0 + 1.0;\n vec4 s1 = floor(b1) * 2.0 + 1.0;\n vec4 sh = -step(h, vec4(0.0));\n vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;\n vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;\n vec3 p0 = vec3(a0.xy, h.x);\n vec3 p1 = vec3(a0.zw, h.y);\n vec3 p2 = vec3(a1.xy, h.z);\n vec3 p3 = vec3(a1.zw, h.w);\n vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));\n p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;\n vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);\n m = m * m;\n return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));\n}\n\nvec3 curl(float x, float y, float z) {\n float eps = 0.08;\n return vec3(\n snoise(vec3(x, y+eps, z)) - snoise(vec3(x, y-eps, z)),\n snoise(vec3(x, y, z+eps)) - snoise(vec3(x, y, z-eps)),\n snoise(vec3(x+eps, y, z)) - snoise(vec3(x-eps, y, z))\n );\n}\n\n${shapeFunction}\n\nvoid main() {\n float proximity = particleProximity(aRandom);\n float depthTime = particleTime(uTime, proximity);\n float t = depthTime * (1.0 + uMid * 0.5);\n vec3 noisePos = position * 0.035 + aRandom;\n float layerMod = 1.0 + aLayer * 0.5;\n\n vec3 flow1 = curl(noisePos.x * uNoiseScale + t*0.1, noisePos.y * uNoiseScale, noisePos.z * uNoiseScale);\n vec3 flow2 = curl(noisePos.x * uNoiseScale * 2.2 + t*0.07, noisePos.y * uNoiseScale * 2.2 + t*0.08, noisePos.z * uNoiseScale * 2.2) * 0.4;\n vec3 flow3 = curl(noisePos.x * uNoiseScale * 0.5 - t*0.05, noisePos.y * uNoiseScale * 0.5, noisePos.z * uNoiseScale * 0.5) * 0.6 * uLowMid;\n vec3 flow = flow1 + flow2 * uHigh + flow3;\n\n vec3 newPos = shapedPosition(aRandom, proximity, depthTime) + (flow * uCurlStrength * layerMod * (1.0 + uSpectralFlux * 2.5));\n\n float vortexAngle = uVortexStrength * uBass * 0.45 * (0.75 + aRandom.z * 0.5);\n float cosA = cos(vortexAngle), sinA = sin(vortexAngle);\n vec2 rotated = vec2(newPos.x * cosA - newPos.z * sinA, newPos.x * sinA + newPos.z * cosA);\n newPos.xz = mix(newPos.xz, rotated, uBass * uVortexStrength);\n\n float yVortex = sin(newPos.y * 0.1 + depthTime) * uMid * uVortexStrength * 2.0;\n newPos.x += cos(newPos.y * 0.15) * yVortex;\n newPos.z += sin(newPos.y * 0.15) * yVortex;\n\n float distToMouse = distance(newPos, uMousePos);\n float repulsion = smoothstep(25.0, 0.0, distToMouse);\n vec3 repelDir = normalize(newPos - uMousePos + vec3(0.001));\n newPos += repelDir * repulsion * (12.0 + length(uMouseVelocity) * 360.0);\n\n // Audio changes depth throughout the plane, without evacuating its centre.\n float beatWave = sin(aRandom.x * 12.0 + aRandom.y * 7.0 - uTime * 6.0);\n float bassDepth = (beatWave * uBeatEnergy * uPulseIntensity * 3.5\n + sin(uTime * 1.8 + aPhase) * uSubBass * 4.0\n + smoothstep(0.3, 0.8, uBass) * 6.0\n + uOnsetEnergy * 8.0) * (0.6 + aRandom.z * 0.4);\n newPos.z += bassDepth;\n float fieldWave = sin(aRandom.x * 11.0 - depthTime * 0.8 + sin(aRandom.y * 7.0 + depthTime * 0.35));\n newPos.z += fieldWave * (1.8 + uBass * 2.4 + uBeatEnergy * 1.6);\n\n // Waveform relief moves toward the camera while retaining the shared rectangular footprint.\n if (uTerrainMode > 0.5) {\n float freqBin = aRandom.x;\n float waveHeight = uBass * sin(freqBin * 6.28 * 8.0 + uTime * 3.0) * 0.5;\n waveHeight += uMid * sin(freqBin * 6.28 * 16.0 + uTime * 5.0) * 0.3;\n waveHeight += uHigh * sin(freqBin * 6.28 * 32.0 + uTime * 8.0) * 0.2;\n waveHeight *= (1.0 + uSpectralFlux * 3.0);\n newPos.z += waveHeight * uTerrainHeight;\n newPos.z += sin(aRandom.y * 6.28 * 4.0 + uTime) * uSubBass * 5.0;\n }\n\n vec4 mvPosition = modelViewMatrix * vec4(newPos, 1.0);\n gl_Position = projectionMatrix * mvPosition;\n\n float size = uSizeBase * (0.85 + uBass * 0.5 + uBeatEnergy * 0.5) * (1.0 + (uZoomFactor - 1.0) * 0.3);\n size *= 1.0 + sin(uTime * 10.0 + aPhase * 6.28) * uHigh * 0.35;\n vGlint = smoothstep(mix(0.985, 0.993, uParticleDepth), mix(0.998, 0.999, uParticleDepth), aRandom.z) * (0.7 + 0.3 * sin(uTime * 3.0 + aPhase * 6.28)) * (0.75 + uHigh * 0.15 + uOnsetEnergy * 0.1);\n size *= 0.85 + vGlint * mix(2.1, 0.7, uParticleDepth);\n size = max(size * uPixelRatio * (90.0 / -mvPosition.z) * layerMod, vGlint * 4.5 * uPixelRatio);\n gl_PointSize = clamp(size * uParticleScale * mix(1.0, 0.7 + proximity * 1.8, uParticleDepth), 0.5, 120.0);\n\n float colorMix = smoothstep(0.0, 2.8, length(flow) + uHighMid * 3.0);\n vec3 baseColor = mix(uColor1, uColor2, colorMix);\n baseColor = mix(baseColor, uColor3, uSpectralCentroid * uHigh * 2.5);\n\n float energyLevel = uBeatEnergy * 0.35 + repulsion * uMid * 2.0 + uSpectralFlux * 0.8;\n vColor = mix(baseColor, vec3(1.0), energyLevel * 0.4);\n vColor += vec3(0.08, 0.04, 0.12) * uUltraHigh * 2.5;\n\n vRimLight = pow(1.0 - max(0.0, dot(normalize(-mvPosition.xyz), normalize(newPos))), 3.0);\n vEnergy = energyLevel;\n vDepth = mix(clamp(-mvPosition.z / 120.0, 0.0, 1.0), 1.0 - proximity, uParticleDepth);\n\n float alpha = particleEdge(newPos, proximity);\n float sparkle = 1.0 + (aRandom.x > 0.8 ? sin(uTime * 20.0 + aRandom.y * 150.0) * uHigh * 2.5 : 0.0);\n vAlpha = alpha * sparkle * (0.7 + uMid * 0.35) * layerMod * mix(1.0, 0.5 + proximity * 0.5, uParticleDepth);\n}`;\n\n const fragmentShader = `\nvarying vec3 vColor;\nvarying float vAlpha, vEnergy, vDepth, vRimLight, vGlint;\n\n${particleLight}\n\nvoid main() {\n vec2 xy = gl_PointCoord.xy - vec2(0.5);\n float r = length(xy);\n if(r > 0.5) discard;\n\n float core = smoothstep(0.16 - vEnergy * 0.04, 0.12 - vEnergy * 0.04, r);\n float glow = exp(-r * r * (20.0 - vEnergy * 10.0));\n float halo = exp(-r * r * 5.0) * 0.35 + exp(-r * r * 2.5) * 0.15;\n float glint = vGlint * (1.0 - smoothstep(0.012, 0.045, min(abs(xy.x), abs(xy.y)))) * (1.0 - smoothstep(0.16, 0.48, r));\n\n vec3 finalColor = mix(vColor, vec3(1.0), core * 0.58 + glint * 0.38);\n finalColor += vColor * vRimLight * 0.1;\n finalColor = mix(finalColor, vec3(0.02, 0.03, 0.05), vDepth * 0.4);\n\n float finalAlpha = clamp(vAlpha * (glow * 0.85 + halo * 0.55 + pow(1.0 - r * 2.0, 2.5) * 0.55 + glint * 0.9), 0.0, 1.0);\n gl_FragColor = mix(vec4(finalColor, finalAlpha), crispParticle(vColor, r, vAlpha), uParticleSharpness);\n}`;\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 POST-PROCESSING SHADERS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n // GOD RAYS SHADER (Radial Light Scattering)\n const GodRaysShader = {\n uniforms: {\n tDiffuse: { value: null },\n uCenter: { value: new THREE.Vector2(0.5, 0.5) },\n uIntensity: { value: 0.35 },\n uDecay: { value: 0.94 },\n uBeatEnergy: { value: 0.0 }\n },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform vec2 uCenter;\n uniform float uIntensity, uDecay, uBeatEnergy;\n varying vec2 vUv;\n\n void main() {\n vec4 color = texture2D(tDiffuse, vUv);\n vec2 dir = vUv - uCenter;\n float dist = length(dir);\n dir = normalize(dir);\n\n vec3 rays = vec3(0.0);\n float decay = 1.0;\n\n for(int i = 0; i < 32; i++) {\n float t = float(i) / 32.0;\n vec2 samplePos = vUv - dir * t * 0.15 * (1.0 + uBeatEnergy);\n vec4 s = texture2D(tDiffuse, samplePos);\n float lum = dot(s.rgb, vec3(0.3, 0.59, 0.11));\n rays += s.rgb * decay * step(0.35, lum);\n decay *= uDecay;\n }\n rays /= 16.0;\n rays *= (1.0 - smoothstep(0.3, 0.9, dist));\n\n gl_FragColor = vec4(color.rgb + rays * uIntensity * (1.0 + uBeatEnergy * 0.8), color.a);\n }`\n };\n\n // LENS FLARE POST-PROCESS SHADER (Cinematic)\n const LensFlareShader = {\n uniforms: {\n tDiffuse: { value: null },\n uIntensity: { value: 1.0 },\n uBeatEnergy: { value: 0.0 },\n uTime: { value: 0.0 },\n uTint: { value: new THREE.Color(0.6, 0.7, 1.0) }\n },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float uIntensity, uBeatEnergy, uTime;\n uniform vec3 uTint;\n varying vec2 vUv;\n\n float sdHex(vec2 p) {\n p = abs(p);\n return max(p.x * 0.866 + p.y * 0.5, p.y) - 1.0;\n }\n\n void main() {\n vec4 color = texture2D(tDiffuse, vUv);\n vec2 center = vec2(0.5);\n vec2 toCenter = center - vUv;\n float dist = length(toCenter);\n vec2 dir = normalize(toCenter + 0.0001);\n\n vec3 flare = vec3(0.0);\n\n // Sample bright areas for flare source\n float brightness = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n float threshold = smoothstep(0.6, 0.9, brightness);\n\n // Anamorphic horizontal streak\n float streak = 0.0;\n for(float i = -8.0; i <= 8.0; i += 1.0) {\n vec2 streakUV = vUv + vec2(i * 0.012, 0.0);\n vec4 streakSample = texture2D(tDiffuse, streakUV);\n float streakBright = dot(streakSample.rgb, vec3(0.299, 0.587, 0.114));\n float falloff = 1.0 - abs(i) / 10.0;\n streak += smoothstep(0.7, 1.0, streakBright) * falloff * falloff;\n }\n flare += uTint * streak * 0.08;\n\n // Subtle ghosts (reflections through lens elements)\n for(int i = 1; i <= 3; i++) {\n float scale = 1.0 - float(i) * 0.25;\n vec2 ghostUV = center + (center - vUv) * scale * 0.8;\n if(ghostUV.x > 0.0 && ghostUV.x < 1.0 && ghostUV.y > 0.0 && ghostUV.y < 1.0) {\n vec4 ghostSample = texture2D(tDiffuse, ghostUV);\n float ghostBright = dot(ghostSample.rgb, vec3(0.299, 0.587, 0.114));\n float ghostAlpha = smoothstep(0.5, 0.8, ghostBright) * (0.25 / float(i));\n vec3 ghostTint = mix(vec3(1.0), uTint, 0.6) * (1.0 - float(i) * 0.2);\n flare += ghostSample.rgb * ghostTint * ghostAlpha;\n }\n }\n\n // Soft circular halo around bright center\n float halo = smoothstep(0.25, 0.15, abs(dist - 0.2)) * threshold * 0.25;\n flare += uTint * 0.85 * halo;\n\n // Subtle vignette-edge glow\n float edgeGlow = smoothstep(0.5, 0.7, dist) * (1.0 - smoothstep(0.7, 0.85, dist));\n flare += uTint * 0.55 * edgeGlow * brightness * 0.15;\n\n gl_FragColor = vec4(color.rgb + flare * uIntensity * (1.0 + uBeatEnergy * 0.3), color.a);\n }`\n };\n\n // ANAMORPHIC FLARE SHADER\n const AnamorphicShader = {\n uniforms: {\n tDiffuse: { value: null },\n uStretch: { value: 0.1 },\n uThreshold: { value: 0.7 },\n uIntensity: { value: 0.4 },\n uTint: { value: new THREE.Color(0.6, 0.8, 1.0) }\n },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float uStretch, uThreshold, uIntensity;\n uniform vec3 uTint;\n varying vec2 vUv;\n void main() {\n vec4 color = texture2D(tDiffuse, vUv);\n vec3 streak = vec3(0.0);\n for(float i = -15.0; i <= 15.0; i++) {\n vec4 s = texture2D(tDiffuse, vUv + vec2(i * uStretch * 0.01, 0.0));\n float bright = dot(s.rgb, vec3(0.299, 0.587, 0.114));\n if(bright > uThreshold) {\n float w = 1.0 - abs(i) / 15.0;\n streak += s.rgb * w * w;\n }\n }\n gl_FragColor = vec4(color.rgb + streak / 7.5 * uTint * uIntensity, color.a);\n }`\n };\n\n // CHROMATIC ABERRATION SHADER\n const ChromaticShader = {\n uniforms: {\n tDiffuse: { value: null },\n uIntensity: { value: 0.004 },\n uBeatEnergy: { value: 0.0 }\n },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float uIntensity, uBeatEnergy;\n varying vec2 vUv;\n void main() {\n vec2 dir = vUv - vec2(0.5);\n float dist = length(dir);\n vec2 offset = dir * dist * uIntensity * (1.0 + uBeatEnergy * 1.5);\n gl_FragColor = vec4(\n texture2D(tDiffuse, vUv + offset * 1.2).r,\n texture2D(tDiffuse, vUv).g,\n texture2D(tDiffuse, vUv - offset * 1.2).b,\n texture2D(tDiffuse, vUv).a\n );\n }`\n };\n\n // FILM GRAIN SHADER\n const GrainShader = {\n uniforms: {\n tDiffuse: { value: null },\n uTime: { value: 0.0 },\n uIntensity: { value: 0.0 }\n },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float uTime, uIntensity;\n varying vec2 vUv;\n float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453); }\n void main() {\n vec4 color = texture2D(tDiffuse, vUv);\n float grain = (rand(vUv + fract(uTime)) * 2.0 - 1.0) * uIntensity;\n gl_FragColor = vec4(color.rgb + grain * (1.0 - dot(color.rgb, vec3(0.299, 0.587, 0.114)) * 0.5), color.a);\n }`\n };\n\n // CUSTOM DEPTH OF FIELD SHADER (Radial + Edge blur)\n const CustomDOFShader = {\n uniforms: {\n tDiffuse: { value: null },\n uFocus: { value: 0.05 },\n uFocalLength: { value: 0.25 },\n uBokehStrength: { value: 0.5 },\n uResolution: { value: new THREE.Vector2() },\n uBeatEnergy: { value: 0.0 }\n },\n vertexShader: `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float uFocus;\n uniform float uFocalLength;\n uniform float uBokehStrength;\n uniform vec2 uResolution;\n uniform float uBeatEnergy;\n varying vec2 vUv;\n\n void main() {\n vec2 center = vec2(0.5);\n float dist = length(vUv - center);\n\n // Focus ring - sharp in a ring around center, blurry inside and outside\n float focusRing = abs(dist - uFocus);\n float blur = smoothstep(0.0, uFocalLength, focusRing) * uBokehStrength;\n\n vec3 color = vec3(0.0);\n float total = 0.0;\n vec2 texel = 1.0 / uResolution;\n\n if(blur < 0.01) {\n color = texture2D(tDiffuse, vUv).rgb;\n total = 1.0;\n } else {\n // Golden angle spiral sampling\n const float GOLDEN_ANGLE = 2.39996323;\n const int SAMPLES = 24;\n float radius = blur * 20.0 * (1.0 + uBeatEnergy * 0.1);\n\n for(int i = 0; i < SAMPLES; i++) {\n float angle = float(i) * GOLDEN_ANGLE;\n float r = sqrt(float(i) / float(SAMPLES)) * radius;\n vec2 offset = vec2(cos(angle), sin(angle)) * r * texel;\n vec3 s = texture2D(tDiffuse, vUv + offset).rgb;\n\n // Subtle highlight boost\n float lum = dot(s, vec3(0.299, 0.587, 0.114));\n s *= 1.0 + smoothstep(0.7, 1.0, lum) * blur * 0.3;\n\n color += s;\n total += 1.0;\n }\n }\n\n gl_FragColor = vec4(color / total, texture2D(tDiffuse, vUv).a);\n }\n `\n };\n\n // VIGNETTE + GLOW SHADER\n const VignetteShader = {\n uniforms: {\n tDiffuse: { value: null },\n uBeatEnergy: { value: 0.0 }\n },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float uBeatEnergy;\n varying vec2 vUv;\n void main() {\n vec4 color = texture2D(tDiffuse, vUv);\n vec2 uv = vUv * (1.0 - vUv.yx);\n float vig = pow(uv.x * uv.y * 15.0, 0.38 * (1.0 - uBeatEnergy * 0.15));\n float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n float glow = smoothstep(0.5, 0.9, lum) * 0.1 * (1.0 + uBeatEnergy * 0.5);\n gl_FragColor = vec4(color.rgb * vig * (1.0 + glow), color.a);\n }`\n };\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 GEOMETRY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const geometry = new THREE.BufferGeometry();\n const mainCapacity = Math.ceil(CONFIG.maxParticles * CONFIG.maxDensity);\n const pos = new Float32Array(mainCapacity * 3);\n const rnd = new Float32Array(mainCapacity * 3);\n const phase = new Float32Array(mainCapacity);\n const layer = new Float32Array(mainCapacity);\n const vec = new THREE.Vector3();\n\n for (let i = 0; i < mainCapacity; i++) {\n vec.setFromSphericalCoords(\n CONFIG.fieldRadius * Math.pow(Math.random(), 0.33),\n Math.acos(2 * Math.random() - 1),\n Math.random() * Math.PI * 2\n );\n pos[i * 3] = vec.x;\n pos[i * 3 + 1] = vec.y;\n pos[i * 3 + 2] = vec.z;\n rnd[i * 3] = Math.random();\n rnd[i * 3 + 1] = Math.random();\n rnd[i * 3 + 2] = Math.random();\n phase[i] = Math.random() * Math.PI * 2;\n }\n\n // Secondary particles now use separate instanced system below\n\n geometry.setAttribute('position', new THREE.BufferAttribute(pos, 3));\n geometry.setAttribute('aRandom', new THREE.BufferAttribute(rnd, 3));\n geometry.setAttribute('aPhase', new THREE.BufferAttribute(phase, 1));\n geometry.setAttribute('aLayer', new THREE.BufferAttribute(layer, 1));\n geometry.setDrawRange(0, Math.floor(CONFIG.maxParticles * STATE.density));\n\n const FIELD_TYPES = {\n 'Photon (EM)': { c1: '#00f2ff', c2: '#0051ff', c3: '#ffffff', s: 1.0, curl: 1.0, sz: 3.0, sc1: '#66d9ff', sc2: '#99eeff' },\n 'Gluon (Strong)': { c1: '#ff0055', c2: '#7000ff', c3: '#ff8800', s: 2.5, curl: 2.4, sz: 3.4, sc1: '#cc66ff', sc2: '#ff6699' },\n 'Higgs (Mass)': { c1: '#ffae00', c2: '#ff4800', c3: '#ffff00', s: 0.6, curl: 0.3, sz: 4.0, sc1: '#ffcc66', sc2: '#ffdd99' },\n 'Gravity': { c1: '#ffffff', c2: '#4466ff', c3: '#8844ff', s: 0.75, curl: 0.18, sz: 2.8, sc1: '#aabbff', sc2: '#ccaaff' },\n 'Dark Energy': { c1: '#1a0033', c2: '#6600ff', c3: '#ff00ff', s: 1.9, curl: 1.6, sz: 3.1, sc1: '#9933ff', sc2: '#cc66ff' },\n 'Neutrino': { c1: '#00ff88', c2: '#00ffcc', c3: '#88ffff', s: 3.0, curl: 3.0, sz: 2.6, sc1: '#66ffbb', sc2: '#99ffdd' },\n // === ENHANCEMENT: Waveform Terrain Field ===\n 'Waveform Terrain': { c1: '#ff3366', c2: '#33ff99', c3: '#6633ff', s: 0.4, curl: 0.15, sz: 2.4, sc1: '#ff6699', sc2: '#66ffcc', terrain: true }\n };\n\n // === ENHANCEMENT: Adaptive Quality System ===\n const QUALITY_SYSTEM = {\n frameCount: 0,\n frameTimes: [],\n lastAdjustTime: 0,\n currentFPS: 60,\n\n update(deltaTime) {\n this.frameCount++;\n this.frameTimes.push(deltaTime);\n if (this.frameTimes.length > 30) this.frameTimes.shift();\n\n if (this.frameCount % 30 === 0) {\n const avgDelta = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;\n this.currentFPS = 1000 / avgDelta;\n\n if (STATE.adaptiveQualityEnabled && performance.now() - this.lastAdjustTime > 1000) {\n if (this.currentFPS < STATE.targetFPS - 10 && STATE.qualityLevel > 0.3) {\n STATE.qualityLevel = Math.max(0.3, STATE.qualityLevel - (this.currentFPS < STATE.targetFPS * 0.6 ? 0.2 : 0.1));\n this.applyQuality();\n this.lastAdjustTime = performance.now();\n } else if (this.currentFPS > STATE.targetFPS + 5 && STATE.qualityLevel < 1.0) {\n STATE.qualityLevel = Math.min(1.0, STATE.qualityLevel + 0.05);\n this.applyQuality();\n this.lastAdjustTime = performance.now();\n }\n }\n }\n },\n\n applyQuality() {\n const q = STATE.qualityLevel;\n const pixelRatio = Math.min(STATE.pixelRatio, Math.max(0.35, STATE.pixelRatio * q));\n const requestedMain = Math.floor(CONFIG.maxParticles * Math.min(STATE.density, CONFIG.maxDensity));\n const densityTotal = requestedMain + CONFIG.secondaryParticles;\n const requestedTotal = particleCount === null ? densityTotal : Math.min(particleCount, mainCapacity + CONFIG.secondaryParticles);\n const visibleParticles = Math.floor(Math.min(requestedTotal, particleBudget) * q);\n // Both layers keep the density's mix; whatever one layer cannot hold goes to the other.\n let mainCount = Math.min(mainCapacity, Math.round(visibleParticles * requestedMain / densityTotal));\n mainCount = Math.min(mainCapacity, Math.max(mainCount, visibleParticles - CONFIG.secondaryParticles));\n geometry.setDrawRange(0, mainCount);\n secondaryGeometry.instanceCount = visibleParticles - mainCount;\n if (renderer.getPixelRatio() !== pixelRatio) {\n renderer.setPixelRatio(pixelRatio);\n composer.setPixelRatio(pixelRatio);\n }\n material.uniforms.uPixelRatio.value = pixelRatio;\n secondaryMaterial.uniforms.uPixelRatio.value = pixelRatio;\n }\n };\n\n // === ENHANCEMENT: Audio-Reactive Camera System ===\n const AUDIO_CAMERA = {\n targetOffset: new THREE.Vector3(),\n currentOffset: new THREE.Vector3(),\n targetFOV: 55,\n baseFOV: 55,\n breathPhase: 0,\n\n update(deltaTime) {\n if (!STATE.audioCameraEnabled || !(AUDIO.active || AUDIO.externalActive)) return;\n\n const bass = AUDIO.gatedBands.bass;\n const mid = AUDIO.gatedBands.mid;\n const high = AUDIO.gatedBands.high;\n const intensity = STATE.audioCameraIntensity;\n const smooth = STATE.audioCameraSmoothing;\n\n // Bass-driven zoom pulse\n this.targetFOV = this.baseFOV - bass * 8 * intensity + AUDIO.beatEnergy * 5 * intensity;\n camera.fov += (this.targetFOV - camera.fov) * smooth;\n camera.updateProjectionMatrix();\n\n // Subtle sway based on mid frequencies\n this.breathPhase += deltaTime * 0.001 * (1 + mid * 2);\n this.targetOffset.x = Math.sin(this.breathPhase * 0.7) * mid * 3 * intensity;\n this.targetOffset.y = Math.cos(this.breathPhase * 0.5) * high * 2 * intensity;\n this.targetOffset.z = Math.sin(this.breathPhase * 0.3) * bass * 4 * intensity;\n\n // Smooth interpolation\n this.currentOffset.lerp(this.targetOffset, smooth);\n\n // Apply onset punch\n if (AUDIO.onsetDetected) {\n this.currentOffset.z -= AUDIO.onsetEnergy * 2 * intensity;\n }\n }\n };\n\n const def = FIELD_TYPES[STATE.field];\n // Both particle layers share the same controls and light profile.\n const particleUniforms = {\n uParticleDepth: { value: STATE.particleDepth },\n uParticleScale: { value: STATE.particleScale },\n uParticleSharpness: { value: STATE.particleSharpness }\n };\n const material = new THREE.ShaderMaterial({\n vertexShader, fragmentShader,\n uniforms: {\n ...particleUniforms,\n uTime: { value: 0 },\n uPixelRatio: { value: STATE.pixelRatio },\n uSizeBase: { value: def.sz },\n uNoiseScale: { value: def.s },\n uCurlStrength: { value: def.curl },\n uViewportAspect: { value: window.innerWidth / window.innerHeight },\n uViewportHalfHeight: { value: Math.tan(camera.fov * Math.PI / 360) * CONFIG.defaultZoom },\n uFieldFill: { value: STATE.fieldSize },\n uColor1: { value: new THREE.Color(def.c1) },\n uColor2: { value: new THREE.Color(def.c2) },\n uColor3: { value: new THREE.Color(def.c3) },\n uSubBass: { value: 0 }, uBass: { value: 0 }, uLowMid: { value: 0 },\n uMid: { value: 0 }, uHighMid: { value: 0 }, uHigh: { value: 0 }, uUltraHigh: { value: 0 },\n uBeatEnergy: { value: 0 }, uSpectralCentroid: { value: 0 }, uSpectralFlux: { value: 0 },\n uOnsetEnergy: { value: 0 }, uTerrainMode: { value: 0 }, uTerrainHeight: { value: 15.0 },\n uVortexStrength: { value: STATE.vortexStrength },\n uPulseIntensity: { value: STATE.pulseIntensity },\n uZoomFactor: { value: 1.0 },\n uMousePos: { value: new THREE.Vector3(999, 999, 999) },\n uMouseVelocity: { value: new THREE.Vector2(0, 0) }\n },\n transparent: true, depthWrite: false, blending: THREE.AdditiveBlending\n });\n\n const points = new THREE.Points(geometry, material);\n scene.add(points);\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 INSTANCED SECONDARY PARTICLES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const secondaryVertexShader = `\nuniform float uTime, uPixelRatio;\nuniform float uViewportAspect, uViewportHalfHeight;\nuniform float uSubBass, uBass, uLowMid, uMid, uHighMid, uHigh, uUltraHigh;\nuniform float uBeatEnergy, uSpectralCentroid, uSpectralFlux, uOnsetEnergy;\nuniform vec3 uColor1, uColor2;\nuniform float uVortexStrength;\n\nattribute vec3 instancePosition;\nattribute vec3 instanceRandom;\nattribute float instancePhase;\n\nvarying vec3 vColor;\nvarying float vAlpha;\nvarying float vEnergy;\nvarying float vDepth;\n\nvec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\nvec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\nvec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }\nvec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }\n\nfloat snoise(vec3 v) {\n const vec2 C = vec2(1.0/6.0, 1.0/3.0);\n const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n vec3 i = floor(v + dot(v, C.yyy));\n vec3 x0 = v - i + dot(i, C.xxx);\n vec3 g = step(x0.yzx, x0.xyz);\n vec3 l = 1.0 - g;\n vec3 i1 = min(g.xyz, l.zxy);\n vec3 i2 = max(g.xyz, l.zxy);\n vec3 x1 = x0 - i1 + C.xxx;\n vec3 x2 = x0 - i2 + C.yyy;\n vec3 x3 = x0 - D.yyy;\n i = mod289(i);\n vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));\n float n_ = 0.142857142857;\n vec3 ns = n_ * D.wyz - D.xzx;\n vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n vec4 x_ = floor(j * ns.z);\n vec4 y_ = floor(j - 7.0 * x_);\n vec4 x = x_ * ns.x + ns.yyyy;\n vec4 y = y_ * ns.x + ns.yyyy;\n vec4 h = 1.0 - abs(x) - abs(y);\n vec4 b0 = vec4(x.xy, y.xy);\n vec4 b1 = vec4(x.zw, y.zw);\n vec4 s0 = floor(b0) * 2.0 + 1.0;\n vec4 s1 = floor(b1) * 2.0 + 1.0;\n vec4 sh = -step(h, vec4(0.0));\n vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;\n vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;\n vec3 p0 = vec3(a0.xy, h.x);\n vec3 p1 = vec3(a0.zw, h.y);\n vec3 p2 = vec3(a1.xy, h.z);\n vec3 p3 = vec3(a1.zw, h.w);\n vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));\n p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;\n vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\n m = m * m;\n return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));\n}\n\n${shapeFunction}\n\nvoid main() {\n // Secondary particles run at a slower timescale \u2014 they are the atmosphere, not the core\n float proximity = particleProximity(instanceRandom) * 0.22;\n float depthTime = particleTime(uTime, proximity);\n float t = depthTime * 0.45;\n\n // Multi-scale noise flow \u2014 low freqs drive large drift, high freqs add fine turbulence\n vec3 noisePos = instancePosition * 0.035 + instanceRandom;\n vec3 drift = vec3(\n snoise(noisePos + t * 0.06),\n snoise(noisePos * 0.9 - t * 0.05),\n snoise(noisePos * 1.1 + t * 0.04)\n ) * (5.0 + uSubBass * 10.0 + uLowMid * 6.0);\n\n vec3 turbulence = vec3(\n snoise(noisePos * 2.5 + t * 0.18),\n snoise(noisePos * 2.5 - t * 0.14),\n snoise(noisePos * 2.5 + t * 0.12)\n ) * (uHigh * 5.0 + uSpectralFlux * 8.0);\n\n vec3 offset = drift + turbulence;\n\n // Orbital drift \u2014 vortex-aware, modulated by bass\n float orbitSpeed = 0.12 + instanceRandom.x * 0.15 + uBass * 0.08;\n float angle = depthTime * orbitSpeed + instancePhase;\n float orbitRadius = length(instancePosition.xz) * (1.0 + uSubBass * 0.25);\n // Vortex coupling \u2014 secondary shell rotates with the main field vortex\n float vortexInfluence = uVortexStrength * uBass * 0.4;\n float vAngle = angle + vortexInfluence;\n vec3 orbit = vec3(\n cos(vAngle) * orbitRadius - instancePosition.x,\n sin(uTime * 0.5 + instancePhase) * (uMid * 4.0 + uLowMid * 2.0),\n sin(vAngle) * orbitRadius - instancePosition.z\n ) * 0.07;\n\n vec3 newPos = shapedPosition(instanceRandom, proximity, depthTime) + offset + orbit;\n\n // Transients and sub-bass move the atmosphere in depth, not away from the centre.\n newPos.z += uOnsetEnergy * 14.0 * (0.6 + instanceRandom.z * 0.4);\n newPos.z += sin(uTime * 1.2 + instancePhase) * uSubBass * 5.0;\n\n vec4 mvPosition = modelViewMatrix * vec4(newPos, 1.0);\n gl_Position = projectionMatrix * mvPosition;\n\n // Size: driven by different bands than the main system\n // Sparkle on ultraHigh, swell on subBass, punch on beatEnergy\n float sparkle = 1.0 + (instanceRandom.x > 0.85 ? sin(uTime * 15.0 + instanceRandom.y * 200.0) * uUltraHigh * 3.0 : 0.0);\n float swell = 1.0 + uSubBass * 0.6 + uLowMid * 0.3;\n float size = (1.1 + uBeatEnergy * 0.7) * swell * sparkle;\n gl_PointSize = clamp(size * uPixelRatio * (55.0 / -mvPosition.z) * uParticleScale, 0.5, 55.0);\n\n // Colour: spectral centroid shifts hue, highMid brightens toward white\n float centroidMix = clamp(uSpectralCentroid * 2.2 + uHighMid * 0.4, 0.0, 1.0);\n vColor = mix(uColor1, uColor2, centroidMix);\n // SubBass warmth \u2014 hint of deep colour saturation on heavy lows\n vColor = mix(vColor, vColor * 1.3, uSubBass * 0.4);\n // Beat flash \u2014 brief white-hot core\n vColor = mix(vColor, vec3(1.0), uBeatEnergy * 0.25);\n\n // Alpha: outer shell fades at edge, mid-frequency presence sustains visibility\n float edgeFade = particleEdge(newPos, proximity);\n vAlpha = edgeFade * (0.45 + uMid * 0.35 + uLowMid * 0.2) * sparkle * mix(1.0, 0.6, uParticleDepth);\n\n vEnergy = uBeatEnergy * 0.4 + uHighMid * 0.3 + uOnsetEnergy * 0.3;\n vDepth = clamp(-mvPosition.z / 160.0, 0.0, 1.0);\n}\n`;\n\n const secondaryFragmentShader = `\nvarying vec3 vColor;\nvarying float vAlpha;\nvarying float vEnergy;\nvarying float vDepth;\n\n${particleLight}\n\nvoid main() {\n vec2 xy = gl_PointCoord.xy - vec2(0.5);\n float r = length(xy);\n if (r > 0.5) discard;\n\n // Wider, softer glow than main particles \u2014 atmospheric haze\n float glow = exp(-r * r * 8.0);\n float softGlow = exp(-r * r * 3.0) * 0.4;\n float core = smoothstep(0.18 - vEnergy * 0.06, 0.10, r);\n\n vec3 finalColor = mix(vColor, vec3(1.0, 0.97, 0.92), core * 0.55);\n // Depth haze \u2014 far particles shift slightly cooler/dimmer\n finalColor = mix(finalColor, vec3(0.02, 0.03, 0.06), vDepth * 0.35);\n\n float finalAlpha = clamp(vAlpha * (glow * 0.65 + softGlow + core * 0.35), 0.0, 1.0);\n gl_FragColor = mix(vec4(finalColor, finalAlpha), crispParticle(vColor, r, vAlpha), uParticleSharpness);\n}\n`;\n\n // Create instanced geometry for secondary particles\n const secondaryGeometry = new THREE.InstancedBufferGeometry();\n\n // Base geometry (single point)\n const basePositions = new Float32Array([0, 0, 0]);\n secondaryGeometry.setAttribute('position', new THREE.BufferAttribute(basePositions, 3));\n\n // Instance attributes\n const instancePositions = new Float32Array(CONFIG.secondaryParticles * 3);\n const instanceRandoms = new Float32Array(CONFIG.secondaryParticles * 3);\n const instancePhases = new Float32Array(CONFIG.secondaryParticles);\n\n for (let i = 0; i < CONFIG.secondaryParticles; i++) {\n // Distribute in outer shell\n const r = CONFIG.fieldRadius * (0.65 + Math.random() * 0.45);\n const theta = Math.acos(2 * Math.random() - 1);\n const phi = Math.random() * Math.PI * 2;\n\n instancePositions[i * 3] = r * Math.sin(theta) * Math.cos(phi);\n instancePositions[i * 3 + 1] = r * Math.sin(theta) * Math.sin(phi);\n instancePositions[i * 3 + 2] = r * Math.cos(theta);\n\n instanceRandoms[i * 3] = Math.random();\n instanceRandoms[i * 3 + 1] = Math.random();\n instanceRandoms[i * 3 + 2] = Math.random();\n\n instancePhases[i] = Math.random() * Math.PI * 2;\n }\n\n secondaryGeometry.setAttribute('instancePosition', new THREE.InstancedBufferAttribute(instancePositions, 3));\n secondaryGeometry.setAttribute('instanceRandom', new THREE.InstancedBufferAttribute(instanceRandoms, 3));\n secondaryGeometry.setAttribute('instancePhase', new THREE.InstancedBufferAttribute(instancePhases, 1));\n\n const secondaryMaterial = new THREE.ShaderMaterial({\n vertexShader: secondaryVertexShader,\n fragmentShader: secondaryFragmentShader,\n uniforms: {\n ...particleUniforms,\n uTime: { value: 0 },\n uPixelRatio: { value: STATE.pixelRatio },\n uSubBass: { value: 0 },\n uBass: { value: 0 },\n uLowMid: { value: 0 },\n uMid: { value: 0 },\n uHighMid: { value: 0 },\n uHigh: { value: 0 },\n uUltraHigh: { value: 0 },\n uBeatEnergy: { value: 0 },\n uSpectralCentroid: { value: 0 },\n uSpectralFlux: { value: 0 },\n uOnsetEnergy: { value: 0 },\n uVortexStrength: { value: STATE.vortexStrength },\n uColor1: { value: new THREE.Color(def.sc1) },\n uColor2: { value: new THREE.Color(def.sc2) },\n uViewportAspect: { value: window.innerWidth / window.innerHeight },\n uViewportHalfHeight: { value: Math.tan(camera.fov * Math.PI / 360) * CONFIG.defaultZoom },\n uFieldFill: { value: STATE.fieldSize }\n },\n transparent: true,\n depthWrite: false,\n blending: THREE.AdditiveBlending\n });\n\n const secondaryPoints = new THREE.Points(secondaryGeometry, secondaryMaterial);\n scene.add(secondaryPoints);\n\n // Expose secondary-layer controls to the host component.\n STATE.secondaryEnabled = true;\n STATE.secondaryOpacity = 1.0;\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 PARTICLE TRAIL SYSTEM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const particleSeeds = geometry.getAttribute('aRandom');\n function fieldParticlePosition(index, target) {\n const halfHeight = material.uniforms.uViewportHalfHeight.value * STATE.fieldSize;\n const halfWidth = halfHeight * material.uniforms.uViewportAspect.value;\n const x = particleSeeds.getX(index), y = particleSeeds.getY(index), z = particleSeeds.getZ(index);\n const proximity = z ** 4, depth = particleUniforms.uParticleDepth.value;\n const offset = (-100 + proximity * 225) * depth;\n const projection = 1 - offset / CONFIG.defaultZoom;\n const time = material.uniforms.uTime.value * (1 + (proximity * 1.35 - 0.75) * depth);\n const drift = (0.006 + proximity * 0.039) * depth;\n return target.set(\n (x * 2 - 1 + Math.sin(time * 0.16 + x * 6.28) * drift) * halfWidth * projection,\n (y * 2 - 1 + Math.cos(time * 0.12 + y * 6.28) * drift) * halfHeight * projection,\n (z - 0.5) * 12 + offset\n );\n }\n\n class TrailSystem {\n constructor(count, length) {\n this.trails = [];\n this.meshes = [];\n this.nextPosition = new THREE.Vector3();\n\n for (let i = 0; i < count; i++) {\n const anchor = new THREE.Vector2(Math.random() * 1.5 - 0.75, Math.random() * 1.5 - 0.75);\n const start = new THREE.Vector3(\n anchor.x * material.uniforms.uViewportAspect.value * material.uniforms.uViewportHalfHeight.value * STATE.fieldSize,\n anchor.y * material.uniforms.uViewportHalfHeight.value * STATE.fieldSize,\n 0\n );\n const trail = {\n positions: Array.from({ length }, () => start.clone()),\n anchor,\n phase: Math.random() * Math.PI * 2,\n speed: 0.3 + Math.random() * 0.7,\n color: new THREE.Color(def.c1).lerp(new THREE.Color(def.c2), i / Math.max(1, count - 1))\n };\n this.trails.push(trail);\n\n const geom = new THREE.BufferGeometry();\n const positions = new Float32Array(length * 3);\n const alphas = new Float32Array(length);\n for (let j = 0; j < length; j++) alphas[j] = 1.0 - j / length;\n\n geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geom.setAttribute('alpha', new THREE.BufferAttribute(alphas, 1));\n\n const mat = new THREE.ShaderMaterial({\n vertexShader: `attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `uniform vec3 uColor; uniform float uOpacity, uBeat; varying float vAlpha; void main() { gl_FragColor = vec4(uColor * (1.0 + uBeat * 0.3), vAlpha * uOpacity * 0.6 * (1.0 + uBeat * 0.5)); }`,\n uniforms: { uColor: { value: trail.color }, uOpacity: { value: 0.4 }, uBeat: { value: 0 } },\n transparent: true, depthWrite: false, blending: THREE.AdditiveBlending\n });\n\n const line = new THREE.Line(geom, mat);\n this.meshes.push(line);\n scene.add(line);\n }\n }\n\n update(time, audio, fieldColor) {\n const halfHeight = material.uniforms.uViewportHalfHeight.value * STATE.fieldSize;\n const halfWidth = halfHeight * material.uniforms.uViewportAspect.value;\n for (let i = 0; i < this.trails.length; i++) {\n const trail = this.trails[i], mesh = this.meshes[i];\n mesh.visible = STATE.particleTrailsEnabled && i < this.trails.length * STATE.qualityLevel;\n if (!mesh.visible) continue;\n\n const angle = time * 0.5 * trail.speed + trail.phase;\n const newPos = this.nextPosition.set(\n trail.anchor.x * halfWidth + Math.cos(angle) * (4 + audio.bass * 8),\n trail.anchor.y * halfHeight + Math.sin(time * 0.3 + trail.phase) * (4 + audio.mid * 8),\n Math.sin(angle) * (4 + audio.high * 7)\n );\n newPos.x = THREE.MathUtils.clamp(newPos.x, -halfWidth * 0.94, halfWidth * 0.94);\n newPos.y = THREE.MathUtils.clamp(newPos.y, -halfHeight * 0.94, halfHeight * 0.94);\n\n for (let j = trail.positions.length - 1; j > 0; j--) trail.positions[j].copy(trail.positions[j - 1]);\n trail.positions[0].copy(newPos);\n\n const posAttr = mesh.geometry.getAttribute('position');\n for (let j = 0; j < trail.positions.length; j++) {\n posAttr.setXYZ(j, trail.positions[j].x, trail.positions[j].y, trail.positions[j].z);\n }\n posAttr.needsUpdate = true;\n\n mesh.material.uniforms.uBeat.value = audio.beat;\n mesh.material.uniforms.uOpacity.value = STATE.particleTrailOpacity;\n }\n }\n }\n\n const trailSystem = new TrailSystem(50, 12);\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 PARTICLE CONNECTIONS (Force Network) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const connectionCount = 500; // Number of particles to check for connections\n const maxConnections = 800; // Maximum line segments\n const connectionGeometry = new THREE.BufferGeometry();\n const connectionPositions = new Float32Array(maxConnections * 6); // 2 points per line, 3 coords each\n const connectionAlphas = new Float32Array(maxConnections * 2); // Alpha per vertex\n connectionGeometry.setAttribute('position', new THREE.BufferAttribute(connectionPositions, 3));\n connectionGeometry.setAttribute('alpha', new THREE.BufferAttribute(connectionAlphas, 1));\n connectionGeometry.setDrawRange(0, 0);\n\n const connectionMaterial = new THREE.ShaderMaterial({\n vertexShader: `\n attribute float alpha;\n varying float vAlpha;\n void main() {\n vAlpha = alpha;\n vec4 mvPos = modelViewMatrix * vec4(position, 1.0);\n gl_Position = projectionMatrix * mvPos;\n }\n `,\n fragmentShader: `\n uniform vec3 uColor;\n uniform vec3 uColor2;\n uniform float uOpacity;\n uniform float uBeatEnergy;\n uniform float uHighMid;\n uniform float uSpectralCentroid;\n varying float vAlpha;\n void main() {\n // Blend hue with spectral centroid (brighter/higher = more c2)\n vec3 color = mix(uColor, uColor2, clamp(uSpectralCentroid * 2.0, 0.0, 1.0));\n // Beat pulse flash\n float beatFlash = 1.0 + uBeatEnergy * 1.2;\n color *= beatFlash;\n // Per-line alpha reacts to highMid (attack/presence energy)\n float lineAlpha = vAlpha * uOpacity * (0.4 + uHighMid * 0.5 + uBeatEnergy * 0.4);\n gl_FragColor = vec4(color, clamp(lineAlpha, 0.0, 1.0));\n }\n `,\n uniforms: {\n uColor: { value: new THREE.Color(def.c2) },\n uColor2: { value: new THREE.Color(def.c1) },\n uOpacity: { value: 1.0 },\n uBeatEnergy: { value: 0 },\n uHighMid: { value: 0 },\n uSpectralCentroid: { value: 0 }\n },\n transparent: true,\n depthWrite: false,\n blending: THREE.AdditiveBlending\n });\n\n const connectionMesh = new THREE.LineSegments(connectionGeometry, connectionMaterial);\n scene.add(connectionMesh);\n\n // Particle indices to track for connections \u2014 refreshed periodically\n const trackedParticles = [];\n const positions = Array.from({ length: connectionCount }, () => new THREE.Vector3());\n let connectionRefreshCounter = 0;\n const CONNECTION_REFRESH_INTERVAL = 40; // 20 Hz updates preserve the original ~2 s selection cycle\n\n function refreshTrackedParticles() {\n trackedParticles.length = 0;\n const visibleParticles = Math.max(1, geometry.drawRange.count);\n const visibleFraction = Math.sqrt(visibleParticles / (CONFIG.maxParticles * CONFIG.initialDensity));\n const trackedCount = Math.max(2, Math.floor(connectionCount * Math.min(1, STATE.qualityLevel, visibleFraction)));\n for (let i = 0; i < trackedCount; i++) {\n trackedParticles.push(Math.floor(Math.random() * visibleParticles));\n }\n }\n refreshTrackedParticles();\n let lastConnectionState = null;\n\n function shouldRebuildConnections(moving, current, previous, idleElapsed) {\n return moving || idleElapsed >= 2 || !previous || current.some((value, index) => value !== previous[index]);\n }\n\n function updateConnections(threshold, bassBoost, highMid, mid, forceRefresh = false) {\n // Refresh particle selection every N frames \u2014 network evolves over time\n connectionRefreshCounter++;\n if (forceRefresh || connectionRefreshCounter >= CONNECTION_REFRESH_INTERVAL) {\n refreshTrackedParticles();\n connectionRefreshCounter = 0;\n }\n\n // Mirror the shader's screen-fitted seed mapping for the line systems.\n for (let i = 0; i < trackedParticles.length; i++) {\n fieldParticlePosition(trackedParticles[i], positions[i]);\n }\n\n // Threshold breathes with bass, tightens on high energy\n const effectiveThreshold = threshold * (1.0 + bassBoost * 0.6) * (1.0 - highMid * 0.15);\n const thresholdSq = effectiveThreshold * effectiveThreshold;\n\n // Dynamic connection cap \u2014 expands with mid+highMid energy\n const dynamicMax = Math.floor(maxConnections * (0.3 + (mid + highMid) * 0.7));\n\n let connIdx = 0;\n for (let i = 0; i < trackedParticles.length && connIdx < dynamicMax; i++) {\n for (let j = i + 1; j < trackedParticles.length && connIdx < dynamicMax; j++) {\n const distSq = positions[i].distanceToSquared(positions[j]);\n if (distSq < thresholdSq && distSq > 0.1) {\n const dist = Math.sqrt(distSq);\n const alpha = (1.0 - dist / effectiveThreshold) * 0.8;\n\n connectionPositions[connIdx * 6 + 0] = positions[i].x;\n connectionPositions[connIdx * 6 + 1] = positions[i].y;\n connectionPositions[connIdx * 6 + 2] = positions[i].z;\n connectionPositions[connIdx * 6 + 3] = positions[j].x;\n connectionPositions[connIdx * 6 + 4] = positions[j].y;\n connectionPositions[connIdx * 6 + 5] = positions[j].z;\n\n connectionAlphas[connIdx * 2 + 0] = alpha;\n connectionAlphas[connIdx * 2 + 1] = alpha;\n\n connIdx++;\n }\n }\n }\n\n connectionGeometry.attributes.position.needsUpdate = true;\n connectionGeometry.attributes.alpha.needsUpdate = true;\n connectionGeometry.setDrawRange(0, connIdx * 2);\n }\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 NETWORK CRAWLERS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const CRAWLER_COUNT = 24; // concurrent crawlers\n const CRAWLER_TAIL = 10; // trail positions per crawler\n const crawlerMaxSegs = CRAWLER_COUNT * (CRAWLER_TAIL - 1);\n\n const crawlerGeo = new THREE.BufferGeometry();\n const crawlerPos = new Float32Array(crawlerMaxSegs * 6);\n const crawlerAlp = new Float32Array(crawlerMaxSegs * 2);\n crawlerGeo.setAttribute('position', new THREE.BufferAttribute(crawlerPos, 3));\n crawlerGeo.setAttribute('alpha', new THREE.BufferAttribute(crawlerAlp, 1));\n crawlerGeo.setDrawRange(0, 0);\n\n // Bloom only lights pixels above 0.85 luminance. Preset greens and cyans got there on their own;\n // a theme's reds and violets carry the same energy at a fraction of the luminance, so reaction\n // colours keep the theme's hue but are lifted (past 1.0 if needed) to the brightness of the originals.\n function glowColor(color, luminance) {\n const current = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;\n return color.multiplyScalar(Math.min(4, Math.max(1, luminance / Math.max(current, 0.02))));\n }\n const crawlerLow = new THREE.Color(def.c1), crawlerHigh = new THREE.Color(def.c3);\n const crawlerTarget = new THREE.Color();\n const crawlerMaterial = new THREE.ShaderMaterial({\n vertexShader: `\n attribute float alpha;\n varying float vAlpha;\n void main() {\n vAlpha = alpha;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform vec3 uColor;\n uniform float uBeatEnergy;\n varying float vAlpha;\n void main() {\n vec3 c = uColor * (1.2 + uBeatEnergy * 1.5);\n gl_FragColor = vec4(c, vAlpha);\n }\n `,\n uniforms: {\n uColor: { value: new THREE.Color(def.c1) },\n uBeatEnergy: { value: 0 }\n },\n transparent: true,\n depthWrite: false,\n blending: THREE.AdditiveBlending\n });\n\n const crawlerMesh = new THREE.LineSegments(crawlerGeo, crawlerMaterial);\n scene.add(crawlerMesh);\n\n // Initialise crawler agents\n const crawlers = [];\n const CRAWLER_MIN_STEP = 1.8; // min world-space distance before adding a tail point\n for (let i = 0; i < CRAWLER_COUNT; i++) {\n crawlers.push({\n fromIdx: Math.floor(Math.random() * connectionCount),\n toIdx: Math.floor(Math.random() * connectionCount),\n t: Math.random(),\n speed: 0.0015 + Math.random() * 0.002,\n tail: [],\n lastTailPos: null, // last world position a tail point was recorded\n hue: Math.random()\n });\n }\n\n function updateCrawlers(dt, highMid, beatEnergy, spectralCentroid) {\n // Helper: world position of a tracked particle\n function particlePos(idx) {\n return positions[idx % trackedParticles.length];\n }\n\n // Helper: find nearest neighbour within threshold\n function nearestNeighbour(fromIdx, excludeIdx) {\n const origin = particlePos(fromIdx);\n const threshold = STATE.connectionThreshold * (1.2 + highMid * 0.4);\n const threshSq = threshold * threshold;\n let bestDist = Infinity, bestIdx = -1;\n const check = Math.min(trackedParticles.length, 80); // sample subset for perf\n const start = Math.floor(Math.random() * (trackedParticles.length - check));\n for (let k = start; k < start + check; k++) {\n const ki = k % trackedParticles.length;\n if (ki === fromIdx || ki === excludeIdx) continue;\n const d = origin.distanceToSquared(particlePos(ki));\n if (d < threshSq && d < bestDist) { bestDist = d; bestIdx = ki; }\n }\n // fallback: pick random if no neighbour in range\n return bestIdx === -1 ? Math.floor(Math.random() * trackedParticles.length) : bestIdx;\n }\n\n let segIdx = 0;\n\n for (let i = 0; i < crawlers.length; i++) {\n const c = crawlers[i];\n\n // Speed reacts to highMid (attack energy) and beat\n const spd = c.speed * (1.0 + highMid * 0.8 + beatEnergy * 0.5);\n c.t += spd;\n\n // Reached target \u2014 advance to next node\n if (c.t >= 1.0) {\n c.t -= 1.0;\n c.fromIdx = c.toIdx;\n c.toIdx = nearestNeighbour(c.fromIdx, c.fromIdx);\n // Occasionally teleport to keep crawlers spread across the field\n if (Math.random() < 0.04) {\n c.fromIdx = Math.floor(Math.random() * trackedParticles.length);\n c.toIdx = nearestNeighbour(c.fromIdx, c.fromIdx);\n c.tail = [];\n c.lastTailPos = null;\n }\n }\n\n // Current head position\n const from = particlePos(c.fromIdx);\n const to = particlePos(c.toIdx);\n const head = new THREE.Vector3().lerpVectors(from, to, c.t);\n\n // Only record a tail point when head has moved far enough in world space\n const distMoved = c.lastTailPos ? head.distanceTo(c.lastTailPos) : Infinity;\n if (distMoved >= CRAWLER_MIN_STEP) {\n c.tail.push(head.clone());\n if (c.tail.length > CRAWLER_TAIL) c.tail.shift();\n c.lastTailPos = head.clone();\n }\n\n // Write tail segments\n const tailLen = c.tail.length;\n for (let s = 0; s < tailLen - 1 && segIdx < crawlerMaxSegs; s++) {\n const p0 = c.tail[s];\n const p1 = c.tail[s + 1];\n // Alpha: head is brightest, tail fades\n const baseAlpha = ((s + 1) / (tailLen - 1));\n const alpha = baseAlpha * (0.25 + spectralCentroid * 0.3 + beatEnergy * 0.4);\n\n crawlerPos[segIdx * 6 + 0] = p0.x; crawlerPos[segIdx * 6 + 1] = p0.y; crawlerPos[segIdx * 6 + 2] = p0.z;\n crawlerPos[segIdx * 6 + 3] = p1.x; crawlerPos[segIdx * 6 + 4] = p1.y; crawlerPos[segIdx * 6 + 5] = p1.z;\n crawlerAlp[segIdx * 2 + 0] = alpha;\n crawlerAlp[segIdx * 2 + 1] = alpha;\n segIdx++;\n }\n }\n\n crawlerGeo.attributes.position.needsUpdate = true;\n crawlerGeo.attributes.alpha.needsUpdate = true;\n crawlerGeo.setDrawRange(0, segIdx * 2);\n }\n\n STATE.crawlersEnabled = true;\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 NEBULA BACKGROUND \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const nebulaVertexShader = `\nvarying vec3 vWorldPosition;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n vec4 worldPos = modelMatrix * vec4(position, 1.0);\n vWorldPosition = worldPos.xyz;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\n const nebulaFragmentShader = `\nuniform float uTime;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform float uBass;\nuniform float uMid;\nuniform float uHigh;\nuniform float uBeatEnergy;\nuniform float uIntensity;\nvarying vec3 vWorldPosition;\nvarying vec2 vUv;\n\n// Simplex 3D noise\nvec4 permute(vec4 x) { return mod(((x*34.0)+1.0)*x, 289.0); }\nvec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }\n\nfloat snoise(vec3 v) {\n const vec2 C = vec2(1.0/6.0, 1.0/3.0);\n const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n vec3 i = floor(v + dot(v, C.yyy));\n vec3 x0 = v - i + dot(i, C.xxx);\n vec3 g = step(x0.yzx, x0.xyz);\n vec3 l = 1.0 - g;\n vec3 i1 = min(g.xyz, l.zxy);\n vec3 i2 = max(g.xyz, l.zxy);\n vec3 x1 = x0 - i1 + C.xxx;\n vec3 x2 = x0 - i2 + C.yyy;\n vec3 x3 = x0 - D.yyy;\n i = mod(i, 289.0);\n vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));\n float n_ = 1.0/7.0;\n vec3 ns = n_ * D.wyz - D.xzx;\n vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n vec4 x_ = floor(j * ns.z);\n vec4 y_ = floor(j - 7.0 * x_);\n vec4 x = x_ *ns.x + ns.yyyy;\n vec4 y = y_ *ns.x + ns.yyyy;\n vec4 h = 1.0 - abs(x) - abs(y);\n vec4 b0 = vec4(x.xy, y.xy);\n vec4 b1 = vec4(x.zw, y.zw);\n vec4 s0 = floor(b0)*2.0 + 1.0;\n vec4 s1 = floor(b1)*2.0 + 1.0;\n vec4 sh = -step(h, vec4(0.0));\n vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;\n vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;\n vec3 p0 = vec3(a0.xy, h.x);\n vec3 p1 = vec3(a0.zw, h.y);\n vec3 p2 = vec3(a1.xy, h.z);\n vec3 p3 = vec3(a1.zw, h.w);\n vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));\n p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;\n vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\n return 42.0 * dot(m*m*m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));\n}\n\nfloat fbm(vec3 p) {\n float value = 0.0;\n float amplitude = 0.5;\n float frequency = 1.0;\n for(int i = 0; i < 3; i++) {\n value += amplitude * snoise(p * frequency);\n amplitude *= 0.5;\n frequency *= 2.0;\n }\n return value;\n}\n\nvoid main() {\n vec3 dir = normalize(vWorldPosition);\n float t = uTime * 0.02;\n\n // Layered nebula clouds\n vec3 p1 = dir * 2.0 + t * 0.5;\n vec3 p2 = dir * 4.0 - t * 0.3;\n vec3 p3 = dir * 1.5 + vec3(t * 0.2, -t * 0.1, t * 0.15);\n\n float n1 = fbm(p1) * 0.5 + 0.5;\n float n2 = fbm(p2) * 0.5 + 0.5;\n float n3 = fbm(p3) * 0.5 + 0.5;\n\n // Create cloud density\n float density = n1 * n2;\n density = pow(density, 1.5) * 1.5;\n\n // Wispy tendrils\n float tendrils = pow(n3, 3.0) * 0.8;\n\n // Color mixing based on field colors\n vec3 nebulaColor = mix(uColor1 * 0.3, uColor2 * 0.5, n1);\n nebulaColor = mix(nebulaColor, uColor3 * 0.4, n2 * 0.5);\n\n // Add some complementary deep space colors\n vec3 deepSpace = vec3(0.02, 0.01, 0.05);\n vec3 starGlow = vec3(0.1, 0.08, 0.15);\n\n // Audio reactivity\n float bassGlow = uBass * 0.3;\n float midShimmer = uMid * 0.2 * sin(uTime * 2.0 + n1 * 10.0);\n float beatPulse = uBeatEnergy * 0.4;\n\n // Combine layers\n vec3 color = deepSpace;\n color += nebulaColor * density * (0.4 + bassGlow + beatPulse);\n color += uColor2 * 0.15 * tendrils * (1.0 + uHigh * 0.5);\n color += starGlow * pow(n1 * n2, 4.0) * (1.0 + midShimmer);\n\n // Scattered stars\n float stars = pow(snoise(dir * 50.0) * 0.5 + 0.5, 20.0);\n stars += pow(snoise(dir * 80.0 + 100.0) * 0.5 + 0.5, 25.0) * 0.5;\n color += vec3(1.0, 0.95, 0.9) * stars * (0.5 + uHigh * 0.5);\n\n // Vignette toward horizon\n float horizonFade = 1.0 - pow(abs(dir.y), 0.5) * 0.3;\n color *= horizonFade;\n\n // Apply intensity\n color *= uIntensity;\n\n gl_FragColor = vec4(color, 1.0);\n}\n`;\n\n const nebulaMaterial = new THREE.ShaderMaterial({\n vertexShader: nebulaVertexShader,\n fragmentShader: nebulaFragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uColor1: { value: new THREE.Color(def.c1) },\n uColor2: { value: new THREE.Color(def.c2) },\n uColor3: { value: new THREE.Color(def.c3) },\n uBass: { value: 0 },\n uMid: { value: 0 },\n uHigh: { value: 0 },\n uBeatEnergy: { value: 0 },\n uIntensity: { value: STATE.nebulaIntensity }\n },\n side: THREE.BackSide,\n depthWrite: false\n });\n\n const nebulaSphere = new THREE.Mesh(\n new THREE.SphereGeometry(500, 64, 64),\n nebulaMaterial\n );\n scene.add(nebulaSphere);\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 POST-PROCESSING COMPOSER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n\n const afterimagePass = new AfterimagePass();\n afterimagePass.uniforms['damp'].value = STATE.trails;\n composer.addPass(afterimagePass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);\n bloomPass.threshold = 0.25;\n bloomPass.strength = STATE.bloom;\n bloomPass.radius = 0.45;\n composer.addPass(bloomPass);\n\n const godRaysPass = new ShaderPass(GodRaysShader);\n godRaysPass.enabled = false;\n composer.addPass(godRaysPass);\n\n const lensFlarePass = new ShaderPass(LensFlareShader);\n lensFlarePass.enabled = STATE.lensFlareEnabled && STATE.lensFlareIntensity > 0;\n composer.addPass(lensFlarePass);\n\n const anamorphicPass = new ShaderPass(AnamorphicShader);\n composer.addPass(anamorphicPass);\n\n const chromaticPass = new ShaderPass(ChromaticShader);\n chromaticPass.enabled = STATE.chromaticAberration > 0;\n composer.addPass(chromaticPass);\n\n // Custom DOF pass (radial focus)\n const customDOFPass = new ShaderPass(CustomDOFShader);\n customDOFPass.uniforms.uFocus.value = STATE.dofFocus;\n customDOFPass.uniforms.uFocalLength.value = STATE.dofFocalLength;\n customDOFPass.uniforms.uBokehStrength.value = STATE.dofBokehStrength;\n customDOFPass.uniforms.uResolution.value.set(window.innerWidth, window.innerHeight);\n customDOFPass.enabled = STATE.dofEnabled;\n composer.addPass(customDOFPass);\n\n const vignettePass = new ShaderPass(VignetteShader);\n composer.addPass(vignettePass);\n\n const grainPass = new ShaderPass(GrainShader);\n grainPass.enabled = STATE.filmGrain > 0;\n composer.addPass(grainPass);\n\n const AlphaFromLumaShader = {\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,\n fragmentShader: `uniform sampler2D tDiffuse; varying vec2 vUv;\n void main() {\n vec3 rgb = texture2D(tDiffuse, vUv).rgb;\n float luminance = max(max(rgb.r, rgb.g), rgb.b);\n float alpha = smoothstep(0.004, 0.06, luminance);\n gl_FragColor = vec4(rgb, alpha);\n }`\n };\n composer.addPass(new OutputPass());\n composer.addPass(new ShaderPass(AlphaFromLumaShader));\n\n // Keep the embedded CSS accents aligned with the kit palette.\n function applyCssTheme(accent) {\n const luminance = accent.r * 0.299 + accent.g * 0.587 + accent.b * 0.114;\n const boost = luminance < 0.35 ? 0.35 / Math.max(luminance, 0.001) : 1;\n for (const [channel, value] of Object.entries({ ar: accent.r, ag: accent.g, ab: accent.b })) {\n document.documentElement.style.setProperty(`--${channel}`, Math.min(Math.round(value * boost * 255), 255));\n }\n }\n applyCssTheme(new THREE.Color(def.c1));\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 UI & EVENTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n const overlay = document.getElementById('overlay');\n const audioControls = document.getElementById('audio-controls');\n const playPauseBtn = document.getElementById('play-pause-btn');\n const progressBar = document.getElementById('progress-bar');\n const progressContainer = document.getElementById('progress-container');\n const timeDisplay = document.getElementById('time-display');\n const trackName = document.getElementById('track-name');\n const status = document.getElementById('status-bar');\n\n const formatTime = s => `${Math.floor(s / 60)}:${Math.floor(s % 60).toString().padStart(2, '0')}`;\n\n function updateUI() {\n if (AUDIO.mode === 'mic') {\n playPauseBtn.textContent = '\u{1F399} live';\n playPauseBtn.disabled = true;\n } else {\n playPauseBtn.textContent = AUDIO.isPlaying ? '\u23F8 pause' : '\u25B6 play';\n playPauseBtn.classList.toggle('playing', AUDIO.isPlaying);\n playPauseBtn.disabled = false;\n }\n if (AUDIO.mode === 'file' && AUDIO.audioBuffer) {\n const cur = AUDIO.getCurrentTime(), dur = AUDIO.getDuration();\n progressBar.style.width = (cur / dur * 100) + '%';\n timeDisplay.textContent = `${formatTime(cur)} / ${formatTime(dur)}`;\n } else {\n progressBar.style.width = '0%';\n timeDisplay.textContent = AUDIO.mode === 'mic' ? 'LIVE' : '0:00 / 0:00';\n }\n }\n\n function togglePlayPause() {\n if (AUDIO.mode !== 'file') return;\n AUDIO.togglePlayPause();\n updateUI();\n status.innerHTML = AUDIO.isPlaying ? 'SYSTEM: <span>PLAYING</span>' : 'SYSTEM: <span>PAUSED</span>';\n }\n\n function activate() {\n AUDIO.init();\n if (AUDIO.ctx.state === 'suspended') AUDIO.ctx.resume();\n overlay.style.opacity = 0;\n setTimeout(() => overlay.style.display = 'none', 800);\n audioControls.classList.add('visible');\n }\n\n async function loadTrack(file) {\n activate();\n const buffer = await AUDIO.loadTrack(await file.arrayBuffer(), file.name);\n const samples = buffer.getChannelData(0);\n const bars = 160;\n const peaks = Array.from({ length: bars }, (_, bar) => {\n const from = Math.floor(bar * samples.length / bars);\n const to = Math.floor((bar + 1) * samples.length / bars);\n const stride = Math.max(1, Math.floor((to - from) / 64));\n let peak = 0;\n for (let i = from; i < to; i += stride) peak = Math.max(peak, Math.abs(samples[i]));\n return peak;\n });\n parent.postMessage({ type: 'ad-qf-waveform', value: peaks }, '*');\n AUDIO.play();\n trackName.textContent = file.name.length > 25 ? file.name.slice(0, 22) + '...' : file.name;\n updateUI();\n status.innerHTML = 'SYSTEM: <span>PLAYING</span>';\n }\n\n async function startMic(preferredLabel = null) {\n console.log('Global startMic requested:', preferredLabel);\n try {\n activate();\n await AUDIO.startMic(preferredLabel);\n trackName.textContent = AUDIO.currentTrackName;\n updateUI();\n // status update handled in AUDIO.startMic\n } catch (e) {\n console.error(\"Global startMic failed:\", e);\n const status = document.getElementById('status-bar');\n status.innerHTML = `ERROR: <span style=\"color:#ff6b6b\">${e.message}</span>`;\n }\n }\n\n // Using optional chaining/checks because one input was replaced by a button\n const enterBtn = document.getElementById('enter-btn');\n if (enterBtn) enterBtn.onclick = () => startMic(null);\n\n const fileInput = document.getElementById('file-input');\n if (fileInput) fileInput.onchange = e => e.target.files[0] && loadTrack(e.target.files[0]);\n\n document.getElementById('file-input-2').onchange = e => { if (e.target.files[0]) loadTrack(e.target.files[0]); e.target.value = ''; };\n document.getElementById('mic-btn-2').onclick = () => startMic(null);\n playPauseBtn.onclick = togglePlayPause;\n progressContainer.onclick = e => {\n if (AUDIO.mode !== 'file' || !AUDIO.audioBuffer) return;\n const rect = progressContainer.getBoundingClientRect();\n AUDIO.seek((e.clientX - rect.left) / rect.width * AUDIO.getDuration());\n };\n document.getElementById('screenshot-btn')?.addEventListener('click', () => {\n composer.render();\n const a = document.createElement('a');\n a.download = 'quantum-' + Date.now() + '.png';\n a.href = renderer.domElement.toDataURL('image/png');\n a.click();\n });\n document.getElementById('fullscreen-btn')?.addEventListener('click', () => {\n document.fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen();\n });\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550 ANIMATION LOOP \u2550\u2550\u2550\u2550\u2550\u2550\u2550\n let renderVisible = true;\n let connectionElapsed = 1 / 20;\n let idleConnectionElapsed = 2;\n let frameRequest = 0;\n let nextFrameAt = 0;\n let lastRenderedAt = -1;\n let elapsed = 0;\n\n function startFrames() {\n if (!frameRequest && renderVisible && !document.hidden) frameRequest = requestAnimationFrame(animate);\n }\n\n function stopFrames() {\n if (frameRequest) cancelAnimationFrame(frameRequest);\n frameRequest = 0;\n nextFrameAt = 0;\n lastRenderedAt = -1;\n }\n\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) stopFrames();\n else startFrames();\n });\n\n function animate(now) {\n frameRequest = 0;\n if (!renderVisible || document.hidden) return;\n startFrames();\n const frameInterval = 1000 / Math.max(1, STATE.targetFPS);\n if (now < nextFrameAt) return;\n nextFrameAt = Math.max(nextFrameAt + frameInterval, now);\n const dt = lastRenderedAt < 0 ? 0 : Math.min((now - lastRenderedAt) / 1000, 0.05);\n lastRenderedAt = now;\n elapsed += dt;\n\n material.uniforms.uTime.value += dt * STATE.timeScale;\n grainPass.uniforms.uTime.value = elapsed;\n lensFlarePass.uniforms.uTime.value = elapsed;\n\n material.uniforms.uZoomFactor.value = camera.position.length() / CONFIG.defaultZoom;\n material.uniforms.uMousePos.value.lerp(mouseWorldPos, 0.1);\n material.uniforms.uMouseVelocity.value.lerp(mouseVelocity, 0.08);\n\n AUDIO.update();\n\n const s = STATE.sensitivity, lf = 0.16;\n material.uniforms.uSubBass.value = THREE.MathUtils.lerp(material.uniforms.uSubBass.value, AUDIO.gatedBands.subBass * s, lf);\n material.uniforms.uBass.value = THREE.MathUtils.lerp(material.uniforms.uBass.value, AUDIO.gatedBands.bass * s, lf);\n material.uniforms.uLowMid.value = THREE.MathUtils.lerp(material.uniforms.uLowMid.value, AUDIO.gatedBands.lowMid * s, lf);\n material.uniforms.uMid.value = THREE.MathUtils.lerp(material.uniforms.uMid.value, AUDIO.gatedBands.mid * s, lf);\n material.uniforms.uHighMid.value = THREE.MathUtils.lerp(material.uniforms.uHighMid.value, AUDIO.gatedBands.highMid * s, lf);\n material.uniforms.uHigh.value = THREE.MathUtils.lerp(material.uniforms.uHigh.value, AUDIO.gatedBands.high * s, lf);\n material.uniforms.uUltraHigh.value = THREE.MathUtils.lerp(material.uniforms.uUltraHigh.value, AUDIO.gatedBands.ultraHigh * s, lf);\n material.uniforms.uBeatEnergy.value = THREE.MathUtils.lerp(material.uniforms.uBeatEnergy.value, AUDIO.beatEnergy, 0.22);\n material.uniforms.uSpectralCentroid.value = THREE.MathUtils.lerp(material.uniforms.uSpectralCentroid.value, AUDIO.spectralCentroid, lf);\n material.uniforms.uSpectralFlux.value = THREE.MathUtils.lerp(material.uniforms.uSpectralFlux.value, AUDIO.spectralFlux * s, lf);\n\n // === ENHANCEMENT: Update new uniforms ===\n material.uniforms.uOnsetEnergy.value = THREE.MathUtils.lerp(material.uniforms.uOnsetEnergy.value, AUDIO.onsetEnergy, 0.25);\n material.uniforms.uTerrainMode.value = FIELD_TYPES[STATE.field]?.terrain ? 1.0 : 0.0;\n material.uniforms.uTerrainHeight.value = STATE.waveformTerrainHeight;\n\n // Update secondary particles\n secondaryPoints.visible = STATE.secondaryEnabled;\n secondaryMaterial.uniforms.uTime.value = material.uniforms.uTime.value;\n secondaryMaterial.uniforms.uSubBass.value = material.uniforms.uSubBass.value;\n secondaryMaterial.uniforms.uBass.value = material.uniforms.uBass.value;\n secondaryMaterial.uniforms.uLowMid.value = material.uniforms.uLowMid.value;\n secondaryMaterial.uniforms.uMid.value = material.uniforms.uMid.value;\n secondaryMaterial.uniforms.uHighMid.value = material.uniforms.uHighMid.value;\n secondaryMaterial.uniforms.uHigh.value = material.uniforms.uHigh.value;\n secondaryMaterial.uniforms.uUltraHigh.value = material.uniforms.uUltraHigh.value;\n secondaryMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n secondaryMaterial.uniforms.uSpectralCentroid.value = material.uniforms.uSpectralCentroid.value;\n secondaryMaterial.uniforms.uSpectralFlux.value = material.uniforms.uSpectralFlux.value;\n secondaryMaterial.uniforms.uOnsetEnergy.value = material.uniforms.uOnsetEnergy.value;\n secondaryMaterial.uniforms.uVortexStrength.value = STATE.vortexStrength;\n\n // Update nebula background\n nebulaSphere.visible = STATE.nebulaEnabled;\n if (STATE.nebulaEnabled) {\n nebulaMaterial.uniforms.uTime.value = material.uniforms.uTime.value;\n nebulaMaterial.uniforms.uBass.value = material.uniforms.uBass.value;\n nebulaMaterial.uniforms.uMid.value = material.uniforms.uMid.value;\n nebulaMaterial.uniforms.uHigh.value = material.uniforms.uHigh.value;\n nebulaMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n }\n\n // Update particle connections\n connectionMesh.visible = STATE.connectionsEnabled;\n if (STATE.connectionsEnabled) {\n const activeAudio = AUDIO.mode === 'mic' || AUDIO.isPlaying || AUDIO.externalActive;\n const moving = activeAudio || particleUniforms.uParticleDepth.value > 0;\n const connectionState = [\n STATE.field, STATE.connectionThreshold, geometry.drawRange.count,\n material.uniforms.uViewportAspect.value, material.uniforms.uViewportHalfHeight.value, STATE.fieldSize,\n STATE.qualityLevel, activeAudio, particleUniforms.uParticleDepth.value\n ];\n connectionElapsed = Math.min(1 / 20, connectionElapsed + dt);\n idleConnectionElapsed = Math.min(2, idleConnectionElapsed + dt);\n if (connectionElapsed >= 1 / 20 && shouldRebuildConnections(moving, connectionState, lastConnectionState, idleConnectionElapsed)) {\n const refreshSelection = !lastConnectionState || connectionState[2] !== lastConnectionState[2]\n || connectionState[6] !== lastConnectionState[6] || (!activeAudio && idleConnectionElapsed >= 2);\n updateConnections(\n STATE.connectionThreshold,\n activeAudio ? material.uniforms.uBass.value : 0,\n activeAudio ? material.uniforms.uHighMid.value : 0,\n activeAudio ? material.uniforms.uMid.value : 0,\n refreshSelection\n );\n lastConnectionState = connectionState;\n connectionElapsed = 0;\n idleConnectionElapsed = 0;\n }\n connectionMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n connectionMaterial.uniforms.uHighMid.value = material.uniforms.uHighMid.value;\n connectionMaterial.uniforms.uSpectralCentroid.value = material.uniforms.uSpectralCentroid.value;\n connectionMaterial.uniforms.uOpacity.value = STATE.connectionOpacity;\n } else {\n connectionElapsed = 1 / 20;\n idleConnectionElapsed = 2;\n lastConnectionState = null;\n }\n\n // Update crawlers\n crawlerMesh.visible = STATE.crawlersEnabled && STATE.connectionsEnabled && (AUDIO.externalActive || AUDIO.active && (AUDIO.isPlaying || AUDIO.mode === 'mic'));\n if (crawlerMesh.visible) {\n updateCrawlers(\n dt,\n material.uniforms.uHighMid.value,\n material.uniforms.uBeatEnergy.value,\n material.uniforms.uSpectralCentroid.value\n );\n crawlerTarget.lerpColors(crawlerLow, crawlerHigh, material.uniforms.uSpectralCentroid.value);\n crawlerMaterial.uniforms.uColor.value.lerp(crawlerTarget, 0.05);\n crawlerMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n }\n\n // Update post-processing\n chromaticPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n godRaysPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n lensFlarePass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n vignettePass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n customDOFPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;\n\n\n\n // Update trails\n trailSystem.update(elapsed, {\n bass: material.uniforms.uBass.value,\n mid: material.uniforms.uMid.value,\n high: material.uniforms.uHigh.value,\n beat: material.uniforms.uBeatEnergy.value\n }, material.uniforms.uColor1.value);\n\n // === ENHANCEMENT: Adaptive Quality System ===\n QUALITY_SYSTEM.update(dt * 1000);\n\n // === ENHANCEMENT: Audio-Reactive Camera ===\n AUDIO_CAMERA.update(dt * 1000);\n\n // Camera shake\n if (STATE.cameraShake > 0 && AUDIO.beatEnergy > 0.1) {\n const shake = AUDIO.beatEnergy * STATE.cameraShake;\n cameraShakeOffset.set((Math.random() - 0.5) * shake * 0.6, (Math.random() - 0.5) * shake * 0.4, (Math.random() - 0.5) * shake * 0.25);\n camera.position.copy(baseCameraPos).add(cameraShakeOffset);\n }\n\n controls.autoRotateSpeed = 0.35 + material.uniforms.uMid.value * 2.0 + material.uniforms.uBeatEnergy.value * 2.0;\n bloomPass.strength = STATE.bloom + (AUDIO.gatedBands.bass + AUDIO.gatedBands.mid + AUDIO.beatEnergy) / 6;\n bloomPass.enabled = bloomPass.strength > 0;\n anamorphicPass.uniforms.uIntensity.value = STATE.anamorphicStretch + AUDIO.gatedBands.bass * 0.4;\n anamorphicPass.enabled = anamorphicPass.uniforms.uIntensity.value > 0;\n\n controls.update();\n composer.render();\n }\n\n window.addEventListener('resize', () => {\n camera.aspect = window.innerWidth / window.innerHeight;\n camera.updateProjectionMatrix();\n material.uniforms.uViewportAspect.value = window.innerWidth / window.innerHeight;\n secondaryMaterial.uniforms.uViewportAspect.value = material.uniforms.uViewportAspect.value;\n material.uniforms.uViewportHalfHeight.value = Math.tan(camera.fov * Math.PI / 360) * CONFIG.defaultZoom;\n secondaryMaterial.uniforms.uViewportHalfHeight.value = material.uniforms.uViewportHalfHeight.value;\n renderer.setSize(window.innerWidth, window.innerHeight);\n composer.setSize(window.innerWidth, window.innerHeight);\n STATE.pixelRatio = fitPixelRatio();\n QUALITY_SYSTEM.applyQuality();\n customDOFPass.uniforms.uResolution.value.set(window.innerWidth, window.innerHeight);\n baseCameraPos.copy(camera.position);\n });\n\n let pausedFromKit = false;\n let requestedTimeScale = STATE.timeScale;\n const sideEffects = {\n particleDepth: value => particleUniforms.uParticleDepth.value = THREE.MathUtils.clamp(value, 0, 1),\n particleScale: value => particleUniforms.uParticleScale.value = THREE.MathUtils.clamp(value, 0.5, 3),\n particleSharpness: value => particleUniforms.uParticleSharpness.value = THREE.MathUtils.clamp(value, 0, 1),\n trails: value => afterimagePass.uniforms['damp'].value = value,\n chromaticAberration: value => { chromaticPass.uniforms.uIntensity.value = value; chromaticPass.enabled = value > 0; },\n filmGrain: value => { grainPass.uniforms.uIntensity.value = value; grainPass.enabled = value > 0; },\n nebulaIntensity: value => nebulaMaterial.uniforms.uIntensity.value = value,\n lensFlareEnabled: value => lensFlarePass.enabled = value && STATE.lensFlareIntensity > 0,\n lensFlareIntensity: value => { lensFlarePass.uniforms.uIntensity.value = value; lensFlarePass.enabled = STATE.lensFlareEnabled && value > 0; },\n dofEnabled: value => customDOFPass.enabled = value,\n dofFocus: value => customDOFPass.uniforms.uFocus.value = value,\n dofFocalLength: value => customDOFPass.uniforms.uFocalLength.value = value,\n dofBokehStrength: value => customDOFPass.uniforms.uBokehStrength.value = value,\n godRaysEnabled: value => godRaysPass.enabled = value && STATE.godRaysIntensity > 0,\n godRaysIntensity: value => { godRaysPass.uniforms.uIntensity.value = value; godRaysPass.enabled = STATE.godRaysEnabled && value > 0; },\n vortexStrength: value => material.uniforms.uVortexStrength.value = value,\n pulseIntensity: value => material.uniforms.uPulseIntensity.value = value,\n fieldSize: value => { material.uniforms.uFieldFill.value = value; secondaryMaterial.uniforms.uFieldFill.value = value; },\n };\n window.addEventListener('message', ({ source, data }) => {\n if (source !== parent || data?.type !== 'ad-qf-control') return;\n const { key, value } = data;\n if (key === 'field' && FIELD_TYPES[value]) {\n STATE.field = value;\n const selected = FIELD_TYPES[value];\n material.uniforms.uNoiseScale.value = selected.s;\n material.uniforms.uCurlStrength.value = selected.curl;\n material.uniforms.uSizeBase.value = selected.sz;\n } else if (key === 'visible') {\n renderVisible = !!value;\n if (renderVisible) startFrames();\n else stopFrames();\n }\n else if (key === 'interactive') controls.enabled = !!value;\n else if (key === 'spectrum') EXTERNAL.set(value?.bands, value?.range);\n else if (key === 'particleCount') {\n particleCount = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : null;\n QUALITY_SYSTEM.applyQuality();\n }\n else if (key === 'particleBudget') {\n particleBudget = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : Infinity;\n QUALITY_SYSTEM.applyQuality();\n }\n else if (key === 'timeScale') { requestedTimeScale = value; if (!pausedFromKit) STATE.timeScale = value; }\n else if (key === 'paused') { pausedFromKit = !!value; STATE.timeScale = pausedFromKit ? 0 : requestedTimeScale; }\n else if (key === 'play') togglePlayPause();\n else if (key === 'file' && value) loadTrack(value);\n else if (key === 'mic') {\n if (AUDIO.mode === 'mic') { AUDIO.stopCurrentSource(); AUDIO.mode = null; }\n else startMic(null);\n }\n else if (key === 'seek') { AUDIO.seek(value); updateUI(); }\n else if (key === 'screenshot') document.getElementById('screenshot-btn').click();\n else if (key === 'palette' && Array.isArray(value)) {\n const main = new THREE.Color(value[0]), light = new THREE.Color(value[1]);\n applyCssTheme(main);\n material.uniforms.uColor1.value.copy(main);\n material.uniforms.uColor2.value.copy(light);\n material.uniforms.uColor3.value.copy(light).lerp(new THREE.Color('#ffffff'), 0.32);\n secondaryMaterial.uniforms.uColor1.value.copy(main);\n secondaryMaterial.uniforms.uColor2.value.copy(light);\n nebulaMaterial.uniforms.uColor1.value.copy(main);\n nebulaMaterial.uniforms.uColor2.value.copy(light);\n nebulaMaterial.uniforms.uColor3.value.copy(main);\n connectionMaterial.uniforms.uColor.value.copy(light);\n connectionMaterial.uniforms.uColor2.value.copy(main);\n const white = new THREE.Color('#ffffff');\n const tint = glowColor(light.clone().lerp(white, 0.35), 0.75);\n lensFlarePass.uniforms.uTint.value.copy(tint);\n anamorphicPass.uniforms.uTint.value.copy(tint);\n // Reaction lines stand out from the field like the presets' contrasting colours did:\n // a luminous tint of the theme's light colour with a near-white core on bright passages.\n glowColor(crawlerLow.copy(light).lerp(white, 0.2), 0.75);\n glowColor(crawlerHigh.copy(light).lerp(white, 0.5), 0.95);\n crawlerMaterial.uniforms.uColor.value.copy(crawlerLow);\n trailSystem.trails.forEach((trail, i) => glowColor(trail.color.copy(main).lerp(light, i / Math.max(1, trailSystem.trails.length - 1)).lerp(white, 0.15), 0.6));\n } else if (Object.prototype.hasOwnProperty.call(STATE, key)) {\n STATE[key] = value;\n if (key === 'density') QUALITY_SYSTEM.applyQuality();\n sideEffects[key]?.(value);\n }\n });\n let lastTransport = '';\n setInterval(() => {\n if (!renderVisible || document.hidden) return;\n const value = {\n mode: AUDIO.mode, playing: AUDIO.isPlaying,\n current: AUDIO.mode === 'file' ? AUDIO.getCurrentTime() : 0,\n duration: AUDIO.mode === 'file' ? AUDIO.getDuration() : 0,\n track: AUDIO.mode ? AUDIO.currentTrackName : ''\n };\n const snapshot = JSON.stringify(value);\n if (snapshot !== lastTransport) {\n lastTransport = snapshot;\n parent.postMessage({ type: 'ad-qf-transport', value }, '*');\n }\n }, 250);\n parent.postMessage({ type: 'ad-qf-ready' }, '*');\n\n QUALITY_SYSTEM.applyQuality();\n startFrames();\n <\/script>\n</body>\n\n</html>\n";
@@ -4837,13 +4815,13 @@ var QuantumBackground = /* @__PURE__ */ forwardRef(function QuantumBackground2(p
4837
4815
  }
4838
4816
  if (fullscreen) settings.fieldSize ??= quantumBackgroundFullscreenFieldSize;
4839
4817
  for (const key of settingKeys) settings[key] ??= quantumBackgroundDefaults[key];
4840
- const frame = useRef24(null);
4841
- const root = useRef24(null);
4818
+ const frame = useRef23(null);
4819
+ const root = useRef23(null);
4842
4820
  const theme = useThemePalette();
4843
4821
  const [mounted, setMounted] = useState9(typeof IntersectionObserver === "undefined");
4844
4822
  const [ready, setReady] = useState9(false);
4845
- const sent = useRef24({});
4846
- const callbacks = useRef24({ onTransportChange, onWaveformChange, onFullscreenChange });
4823
+ const sent = useRef23({});
4824
+ const callbacks = useRef23({ onTransportChange, onWaveformChange, onFullscreenChange });
4847
4825
  callbacks.current = { onTransportChange, onWaveformChange, onFullscreenChange };
4848
4826
  const normalizedBudget = typeof particleBudget === "number" && Number.isFinite(particleBudget) ? Math.max(0, Math.floor(particleBudget)) : void 0;
4849
4827
  const normalizedCount = typeof particleCount === "number" && Number.isFinite(particleCount) ? Math.min(quantumBackgroundMaxParticles, Math.max(0, Math.floor(particleCount))) : void 0;
@@ -5036,7 +5014,7 @@ function createQuantumFieldAudio() {
5036
5014
  }
5037
5015
 
5038
5016
  // src/components/artwork/Spectrum/Spectrum.tsx
5039
- import { useRef as useRef25 } from "react";
5017
+ import { useRef as useRef24 } from "react";
5040
5018
  import { jsx as jsx42, jsxs as jsxs34 } from "react/jsx-runtime";
5041
5019
  var COLUMNS = 27;
5042
5020
  var ROWS = 29;
@@ -5052,8 +5030,8 @@ function Spectrum({ variant = "segmented", ...p }) {
5052
5030
  return variant === "segmented" ? /* @__PURE__ */ jsx42(Segmented, { ...p }) : /* @__PURE__ */ jsx42(Bars, { ...p });
5053
5031
  }
5054
5032
  function Segmented(p) {
5055
- const ref = useRef25(null);
5056
- const columns2 = useRef25(null);
5033
+ const ref = useRef24(null);
5034
+ const columns2 = useRef24(null);
5057
5035
  usePauseOffscreen(ref);
5058
5036
  useDecoration(ref, (time) => {
5059
5037
  if (!ref.current) return;
@@ -5075,8 +5053,8 @@ function Segmented(p) {
5075
5053
  )) });
5076
5054
  }
5077
5055
  function Bars(p) {
5078
- const ref = useRef25(null);
5079
- const layers = useRef25(null);
5056
+ const ref = useRef24(null);
5057
+ const layers = useRef24(null);
5080
5058
  const id = useSvgId();
5081
5059
  usePauseOffscreen(ref);
5082
5060
  useDecoration(ref, (time) => {
@@ -5531,10 +5509,10 @@ function ServerArt({ upload = false, label, ...p }) {
5531
5509
  }
5532
5510
 
5533
5511
  // src/components/media/AudioPlayer/AudioPlayer.tsx
5534
- import { useEffect as useEffect12, useRef as useRef27, useState as useState12 } from "react";
5512
+ import { useEffect as useEffect12, useRef as useRef26, useState as useState12 } from "react";
5535
5513
 
5536
5514
  // src/components/media/Waveform/Waveform.tsx
5537
- import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef26, useState as useState11 } from "react";
5515
+ import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef25, useState as useState11 } from "react";
5538
5516
 
5539
5517
  // src/components/media/Waveform/useWaveformPeaks.ts
5540
5518
  import { useEffect as useEffect10, useState as useState10 } from "react";
@@ -5665,8 +5643,8 @@ function Waveform({
5665
5643
  }) {
5666
5644
  const tr2 = useTr();
5667
5645
  const id = useSvgId();
5668
- const surface = useRef26(null);
5669
- const drag = useRef26(null);
5646
+ const surface = useRef25(null);
5647
+ const drag = useRef25(null);
5670
5648
  const [hover, setHover] = useState11(null);
5671
5649
  const decoded = useWaveformPeaks(points ? null : src, bins);
5672
5650
  const loading = !points && !!src && decoded === null;
@@ -5689,7 +5667,7 @@ function Waveform({
5689
5667
  const played = progress * W4;
5690
5668
  const ahead = Math.max(0, (hover ?? 0) * W4 - played);
5691
5669
  const [playing, setPlaying] = useState11(false);
5692
- const previous = useRef26(position);
5670
+ const previous = useRef25(position);
5693
5671
  useEffect11(() => {
5694
5672
  const step = position - previous.current;
5695
5673
  previous.current = position;
@@ -5834,10 +5812,10 @@ function Waveform({
5834
5812
  import { jsx as jsx47, jsxs as jsxs38 } from "react/jsx-runtime";
5835
5813
  var AudioPlayer = (p) => {
5836
5814
  const tr2 = useTr();
5837
- const audio = useRef27(null);
5815
+ const audio = useRef26(null);
5838
5816
  const [playing, setPlaying] = useState12(false), [muted, setMuted] = useState12(false);
5839
5817
  const [volume, setVolume] = useControllable(p.volume, p.defaultVolume ?? 0.7, p.onVolumeChange);
5840
- const onPlayingChange = useRef27(p.onPlayingChange);
5818
+ const onPlayingChange = useRef26(p.onPlayingChange);
5841
5819
  onPlayingChange.current = p.onPlayingChange;
5842
5820
  const [fileDuration, setFileDuration] = useState12();
5843
5821
  const duration = p.duration ?? fileDuration ?? 51;
@@ -5953,8 +5931,8 @@ var AudioPlayer = (p) => {
5953
5931
  function PlaybackTrack({ audio, playing, position: controlledPosition, disabled, duration, points, src, onTimeChange, onEnded }) {
5954
5932
  const [localPosition, setPosition] = useState12(0);
5955
5933
  const position = controlledPosition ?? localPosition;
5956
- const current = useRef27(0);
5957
- const lastTick = useRef27(void 0);
5934
+ const current = useRef26(0);
5935
+ const lastTick = useRef26(void 0);
5958
5936
  useTick((now) => {
5959
5937
  const media = audio.current;
5960
5938
  const next = media?.currentTime ?? current.current + (now - (lastTick.current ?? now)) / 1e3;
@@ -5990,7 +5968,7 @@ function PlaybackTrack({ audio, playing, position: controlledPosition, disabled,
5990
5968
  }
5991
5969
 
5992
5970
  // src/components/media/LevelMeter/LevelMeter.tsx
5993
- import { useEffect as useEffect13, useRef as useRef28 } from "react";
5971
+ import { useEffect as useEffect13, useRef as useRef27 } from "react";
5994
5972
  import { jsx as jsx48, jsxs as jsxs39 } from "react/jsx-runtime";
5995
5973
  var SAMPLES = 64;
5996
5974
  var WIDTH = 256;
@@ -6020,9 +5998,9 @@ function LevelMeter({
6020
5998
  }) {
6021
5999
  const tr2 = useTr();
6022
6000
  const id = useSvgId();
6023
- const path = useRef28(null);
6024
- const target = useRef28(0);
6025
- const wake = useRef28(() => void 0);
6001
+ const path = useRef27(null);
6002
+ const target = useRef27(0);
6003
+ const wake = useRef27(() => void 0);
6026
6004
  const level = clamp(value) / 100;
6027
6005
  useEffect13(() => {
6028
6006
  target.current = active ? level : 0;
@@ -6169,7 +6147,7 @@ var KaraokeLyrics = ({
6169
6147
  );
6170
6148
 
6171
6149
  // src/components/media/MelodyRoll/MelodyRoll.tsx
6172
- import { useRef as useRef29 } from "react";
6150
+ import { useMemo as useMemo5, useRef as useRef28 } from "react";
6173
6151
  import { jsx as jsx50, jsxs as jsxs41 } from "react/jsx-runtime";
6174
6152
  var DEMO2 = [
6175
6153
  { id: "1", start: 0.4, end: 1.1, pitch: 64, lyric: "\u041D\u043E\u0447\u044C" },
@@ -6204,15 +6182,31 @@ var MelodyRoll = ({
6204
6182
  }) => {
6205
6183
  const tr2 = useTr();
6206
6184
  const [span, setSpan] = useControllable(windowProp, defaultWindow, onWindowChange);
6207
- const pitches = notes.map((note) => note.pitch);
6208
- const low = minPitch ?? Math.min(...pitches, 60) - 2;
6209
- const high = Math.max(low + 4, maxPitch ?? Math.max(...pitches, 64) + 2);
6185
+ const indexed = useMemo5(() => {
6186
+ let low2 = 60, high2 = 64;
6187
+ const blocks = [];
6188
+ for (let i = 0; i < notes.length; i += 128) {
6189
+ const block = notes.slice(i, i + 128);
6190
+ let start = Infinity, end = -Infinity;
6191
+ for (const note of block) {
6192
+ low2 = Math.min(low2, note.pitch);
6193
+ high2 = Math.max(high2, note.pitch);
6194
+ start = Math.min(start, note.start);
6195
+ end = Math.max(end, note.end);
6196
+ }
6197
+ blocks.push({ start, end, notes: block });
6198
+ }
6199
+ return { low: low2 - 2, high: high2 + 2, blocks };
6200
+ }, [notes]);
6201
+ const low = minPitch ?? indexed.low;
6202
+ const high = Math.max(low + 4, maxPitch ?? indexed.high);
6210
6203
  const rows = high - low + 1;
6211
6204
  const from = position - span * lead;
6205
+ const origin = Math.floor(from / span) * span;
6212
6206
  const x = (seconds) => (seconds - from) / span * 100;
6213
6207
  const y = (pitch) => (high - pitch) / rows * 100;
6214
- const visible = notes.filter((note) => note.end >= from && note.start <= from + span);
6215
- const trail = useRef29([]);
6208
+ const visible = indexed.blocks.filter((block) => block.end >= from && block.start <= from + span).flatMap((block) => block.notes.filter((note) => note.end >= from && note.start <= from + span));
6209
+ const trail = useRef28([]);
6216
6210
  if (trail.current.at(-1)?.t !== position) {
6217
6211
  const kept = trail.current.filter((point) => point.t > position - TRAIL && point.t < position);
6218
6212
  trail.current = [...kept, { t: position, pitch: livePitch }];
@@ -6240,8 +6234,6 @@ var MelodyRoll = ({
6240
6234
  ...p.style,
6241
6235
  "--ad-roll-rows": rows,
6242
6236
  "--ad-roll-beat": clamp(beat, 0, 1),
6243
- "--ad-roll-level": clamp(level, 0, 1),
6244
- "--ad-roll-accuracy": clamp(accuracy, 0, 1),
6245
6237
  "--ad-roll-second": `${100 / span}cqw`
6246
6238
  },
6247
6239
  children: [
@@ -6259,7 +6251,7 @@ var MelodyRoll = ({
6259
6251
  ),
6260
6252
  /* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-lane", "aria-hidden": "true", children: [
6261
6253
  /* @__PURE__ */ jsx50("span", { className: "ad-melody-roll-seconds", style: { "--ad-roll-shift": `${-((from % 1 + 1) % 1) / span * 100}cqw` } }),
6262
- /* @__PURE__ */ jsx50("div", { className: "ad-melody-roll-strip", style: { transform: `translateX(${(-from / span * 100).toFixed(3)}%)` }, children: visible.map((note) => /* @__PURE__ */ jsx50(
6254
+ /* @__PURE__ */ jsx50("div", { className: "ad-melody-roll-strip", style: { transform: `translateX(${((origin - from) / span * 100).toFixed(3)}%)` }, children: visible.map((note) => /* @__PURE__ */ jsx50(
6263
6255
  "span",
6264
6256
  {
6265
6257
  className: "ad-melody-note",
@@ -6267,7 +6259,7 @@ var MelodyRoll = ({
6267
6259
  "data-now": position >= note.start && position <= note.end || void 0,
6268
6260
  "data-past": position > note.end || void 0,
6269
6261
  style: {
6270
- left: `${note.start / span * 100}%`,
6262
+ left: `${(note.start - origin) / span * 100}%`,
6271
6263
  width: `${Math.max(0.6, (note.end - note.start) / span * 100)}%`,
6272
6264
  top: `${y(note.pitch)}%`
6273
6265
  },
@@ -6275,18 +6267,18 @@ var MelodyRoll = ({
6275
6267
  },
6276
6268
  note.id
6277
6269
  )) }),
6278
- /* @__PURE__ */ jsx50("svg", { className: "ad-melody-trail", viewBox: "0 0 100 100", preserveAspectRatio: "none", children: segments.flatMap((d, i) => [
6270
+ /* @__PURE__ */ jsx50("svg", { className: "ad-melody-trail", viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { "--ad-roll-accuracy": clamp(accuracy, 0, 1) }, children: segments.flatMap((d, i) => [
6279
6271
  /* @__PURE__ */ jsx50("path", { "data-halo": "wide", d, vectorEffect: "non-scaling-stroke" }, `${i}-wide`),
6280
6272
  /* @__PURE__ */ jsx50("path", { "data-halo": "near", d, vectorEffect: "non-scaling-stroke" }, `${i}-near`),
6281
6273
  /* @__PURE__ */ jsx50("path", { d, vectorEffect: "non-scaling-stroke" }, i)
6282
6274
  ]) }),
6283
- /* @__PURE__ */ jsx50("span", { className: "ad-melody-playhead", style: { left: `${lead * 100}%` } }),
6275
+ /* @__PURE__ */ jsx50("span", { className: "ad-melody-playhead", style: { left: `${lead * 100}%`, "--ad-roll-beat": clamp(beat, 0, 1) } }),
6284
6276
  livePitch !== void 0 && /* @__PURE__ */ jsx50(
6285
6277
  "span",
6286
6278
  {
6287
6279
  className: "ad-melody-voice",
6288
6280
  "data-hit": hit || void 0,
6289
- style: { left: `${lead * 100}%`, top: `${(high - livePitch + 0.5) / rows * 100}%` }
6281
+ style: { left: `${lead * 100}%`, top: `${(high - livePitch + 0.5) / rows * 100}%`, "--ad-roll-level": clamp(level, 0, 1), "--ad-roll-accuracy": clamp(accuracy, 0, 1) }
6290
6282
  }
6291
6283
  )
6292
6284
  ] })
@@ -6296,19 +6288,19 @@ var MelodyRoll = ({
6296
6288
  };
6297
6289
 
6298
6290
  // src/components/layout/FloatingPanel/FloatingPanel.tsx
6299
- import { useRef as useRef31 } from "react";
6291
+ import { useRef as useRef30 } from "react";
6300
6292
 
6301
6293
  // src/components/layout/FloatingPanel/useFloatingPanel.ts
6302
- import { useCallback as useCallback2, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useRef as useRef30, useState as useState13 } from "react";
6294
+ import { useCallback as useCallback2, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useRef as useRef29, useState as useState13 } from "react";
6303
6295
  var resizeEdges = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
6304
6296
  var threshold = 3;
6305
6297
  var panelControls = "button, a, input, select, textarea, [role=slider], [role=button], [role=switch], [role=listbox], [contenteditable=true], .ad-rotary-knob";
6306
6298
  var useFloatingPanel = (frameRef, options = {}) => {
6307
6299
  const [active, setActive] = useState13(false);
6308
6300
  const [live, setLive] = useState13(null);
6309
- const drag = useRef30(null);
6310
- const moved = useRef30(false);
6311
- const latest = useRef30(options);
6301
+ const drag = useRef29(null);
6302
+ const moved = useRef29(false);
6303
+ const latest = useRef29(options);
6312
6304
  latest.current = options;
6313
6305
  const saved = options.layout;
6314
6306
  useLayoutEffect5(() => {
@@ -6440,7 +6432,7 @@ var useFloatingPanel = (frameRef, options = {}) => {
6440
6432
  // src/components/layout/FloatingPanel/FloatingPanel.tsx
6441
6433
  import { jsx as jsx51, jsxs as jsxs42 } from "react/jsx-runtime";
6442
6434
  var FloatingPanel = ({ layout: saved, onLayoutChange, defaultSize, limits, onDragOutside, ignore, resizable = false, label, children, ...p }) => {
6443
- const frame = useRef31(null);
6435
+ const frame = useRef30(null);
6444
6436
  const { layout, active, beginMove, beginResize, handleMove, handleUp, handleCancel, handleKeyDown } = useFloatingPanel(frame, {
6445
6437
  layout: saved,
6446
6438
  onLayoutChange,
@@ -6473,15 +6465,15 @@ var FloatingPanel = ({ layout: saved, onLayoutChange, defaultSize, limits, onDra
6473
6465
  };
6474
6466
 
6475
6467
  // src/components/media/Sparkline/Sparkline.tsx
6476
- import { useMemo as useMemo5, useRef as useRef32 } from "react";
6477
- import { Fragment as Fragment4, jsx as jsx52, jsxs as jsxs43 } from "react/jsx-runtime";
6468
+ import { useMemo as useMemo6, useRef as useRef31 } from "react";
6469
+ import { Fragment as Fragment5, jsx as jsx52, jsxs as jsxs43 } from "react/jsx-runtime";
6478
6470
  var DEMO3 = [12, 23, 17, 31, 43, 24, 28, 20, 41, 29, 51, 34, 38, 22, 31, 16, 23];
6479
6471
  var Sparkline = (p) => {
6480
6472
  const values = p.values ?? DEMO3;
6481
6473
  const id = useSvgId();
6482
- const ref = useRef32(null);
6474
+ const ref = useRef31(null);
6483
6475
  usePauseOffscreen(ref);
6484
- const { line: line2, lastX, lastY } = useMemo5(() => {
6476
+ const { line: line2, lastX, lastY } = useMemo6(() => {
6485
6477
  if (!values.length) return { line: "", lastX: 0, lastY: 0 };
6486
6478
  let min = p.fit ? Infinity : 0;
6487
6479
  let max = p.fit ? -Infinity : 1;
@@ -6525,7 +6517,7 @@ var Sparkline = (p) => {
6525
6517
  "aria-label": p.label,
6526
6518
  "aria-hidden": !p.label,
6527
6519
  style: { ...p.style, "--ad-spark": p.color ?? "var(--ad-primary)" },
6528
- children: line2 && /* @__PURE__ */ jsxs43(Fragment4, { children: [
6520
+ children: line2 && /* @__PURE__ */ jsxs43(Fragment5, { children: [
6529
6521
  /* @__PURE__ */ jsxs43("svg", { ...box, children: [
6530
6522
  /* @__PURE__ */ jsx52("defs", { children: /* @__PURE__ */ jsxs43("linearGradient", { id: `${id}-area`, x1: "0", y1: "0", x2: "0", y2: "1", children: [
6531
6523
  /* @__PURE__ */ jsx52("stop", { offset: "0", stopColor: "var(--ad-spark)", stopOpacity: "0.45" }),
@@ -6549,7 +6541,7 @@ var Sparkline = (p) => {
6549
6541
  };
6550
6542
 
6551
6543
  // src/components/media/WaveDecoration/WaveDecoration.tsx
6552
- import { useRef as useRef33 } from "react";
6544
+ import { useRef as useRef32 } from "react";
6553
6545
  import { jsx as jsx53, jsxs as jsxs44 } from "react/jsx-runtime";
6554
6546
  var POINTS = 23;
6555
6547
  var STEP2 = 65 / (POINTS - 1);
@@ -6568,8 +6560,8 @@ var strand = (j, t) => {
6568
6560
  return d;
6569
6561
  };
6570
6562
  var WaveDecoration = (p) => {
6571
- const ref = useRef33(null);
6572
- const paths = useRef33(null);
6563
+ const ref = useRef32(null);
6564
+ const paths = useRef32(null);
6573
6565
  const uid = useSvgId();
6574
6566
  const paint = (t) => {
6575
6567
  if (!ref.current) return;