@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/README.md +18 -1
- package/dist/chunks/{chunk-FRALT7ER.js → chunk-L6VCC7AS.js} +72 -47
- package/dist/chunks/{chunk-YVRQTI7B.js → chunk-OCH27SNY.js} +31 -7
- package/dist/components/artwork/NeonWaves/styles.css +15 -2
- package/dist/components/controls/InputBase/styles.css +29 -47
- package/dist/components/media/MelodyRoll/styles.css +7 -0
- package/dist/core.js +1 -1
- package/dist/forms.js +2 -2
- package/dist/index.js +142 -150
- package/package.json +1 -1
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-
|
|
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-
|
|
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
|
-
() =>
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
|
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
|
|
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
|
|
3310
|
+
createElement as createElement8,
|
|
3329
3311
|
isValidElement as isValidElement3,
|
|
3330
3312
|
useEffect as useEffect5,
|
|
3331
|
-
useRef as
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
3374
|
+
const pauseRef = useRef14(null);
|
|
3393
3375
|
usePauseOffscreen(pauseRef);
|
|
3394
|
-
return
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
3800
|
+
const pauseRef = useRef19(null);
|
|
3819
3801
|
usePauseOffscreen(pauseRef);
|
|
3820
|
-
const ref =
|
|
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
|
|
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 =
|
|
3859
|
+
const pauseRef = useRef20(null);
|
|
3878
3860
|
usePauseOffscreen(pauseRef);
|
|
3879
|
-
const ref =
|
|
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
|
|
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 =
|
|
3912
|
-
const ref =
|
|
3913
|
-
const cometsRef =
|
|
3914
|
-
const lastDraw =
|
|
3915
|
-
const tracks =
|
|
3916
|
-
const paths =
|
|
3917
|
-
const cometElements =
|
|
3918
|
-
const size =
|
|
3919
|
-
const lastTime =
|
|
3920
|
-
const renderer =
|
|
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(
|
|
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 =
|
|
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.
|
|
4034
|
-
"
|
|
4035
|
-
{
|
|
4036
|
-
|
|
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
|
|
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 =
|
|
4486
|
-
const root =
|
|
4487
|
-
const paint =
|
|
4488
|
-
const pointer =
|
|
4489
|
-
const camera =
|
|
4490
|
-
const drag =
|
|
4491
|
-
const visible =
|
|
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 =
|
|
4494
|
-
const connectedAudio =
|
|
4495
|
-
const live =
|
|
4496
|
-
const updatePalette =
|
|
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
|
|
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 \xB7 scroll \xB7 zoom \xB7 drag \xB7 rotate \xB7 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 =
|
|
4841
|
-
const root =
|
|
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 =
|
|
4846
|
-
const callbacks =
|
|
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
|
|
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 =
|
|
5056
|
-
const columns2 =
|
|
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 =
|
|
5079
|
-
const layers =
|
|
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
|
|
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
|
|
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 =
|
|
5669
|
-
const drag =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5957
|
-
const lastTick =
|
|
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
|
|
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 =
|
|
6024
|
-
const target =
|
|
6025
|
-
const wake =
|
|
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
|
|
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
|
|
6208
|
-
|
|
6209
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
6310
|
-
const moved =
|
|
6311
|
-
const latest =
|
|
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 =
|
|
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
|
|
6477
|
-
import { Fragment as
|
|
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 =
|
|
6474
|
+
const ref = useRef31(null);
|
|
6483
6475
|
usePauseOffscreen(ref);
|
|
6484
|
-
const { line: line2, lastX, lastY } =
|
|
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(
|
|
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
|
|
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 =
|
|
6572
|
-
const paths =
|
|
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;
|