@neo-ui-kit/react 1.2.3 → 1.3.2
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 +70 -1
- package/dist/chunks/{chunk-YKCUOZ7V.js → chunk-ICRXRGIW.js} +98 -2
- package/dist/chunks/{chunk-RHMNSDIW.js → chunk-TAQHE3C5.js} +486 -186
- package/dist/components/artwork/QuantumField/QuantumFieldExperience.d.ts +6 -0
- package/dist/components/controls/ThemePicker/ThemePicker.d.ts +2 -0
- package/dist/components/controls/ThemePicker/styles.css +45 -10
- package/dist/components/forms/FormFields/FormFields.d.ts +10 -4
- package/dist/components/forms/FormFields/styles.css +5 -1
- package/dist/components/foundation/LoaderGenerator/LoaderGenerator.d.ts +2 -2
- package/dist/core.d.ts +1 -1
- package/dist/core.js +4 -4
- package/dist/forms.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +706 -982
- package/package.json +1 -1
- package/dist/chunks/chunk-JZ4GDTH5.js +0 -87
package/dist/index.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ThemeProvider,
|
|
3
|
-
themes,
|
|
4
|
-
useThemePalette
|
|
5
|
-
} from "./chunks/chunk-JZ4GDTH5.js";
|
|
6
1
|
import {
|
|
7
2
|
PianoKeyboard,
|
|
8
3
|
PianoRoll,
|
|
@@ -29,18 +24,24 @@ import {
|
|
|
29
24
|
Grid,
|
|
30
25
|
Icon,
|
|
31
26
|
IconButton,
|
|
27
|
+
ImageShine,
|
|
32
28
|
InputBase,
|
|
33
29
|
NumberField,
|
|
34
30
|
PeoplePicker,
|
|
35
31
|
Popover,
|
|
36
32
|
RotaryKnob,
|
|
33
|
+
SegmentedControl,
|
|
37
34
|
Select,
|
|
38
35
|
Slider,
|
|
39
36
|
SvgAsset,
|
|
40
37
|
Switch,
|
|
38
|
+
Tab,
|
|
39
|
+
Tabs,
|
|
41
40
|
TagInput,
|
|
42
41
|
TextArea,
|
|
43
42
|
TextField,
|
|
43
|
+
ThemePicker,
|
|
44
|
+
ToggleButton,
|
|
44
45
|
buttonView,
|
|
45
46
|
clamp01,
|
|
46
47
|
defaultFieldRegistry,
|
|
@@ -60,19 +61,22 @@ import {
|
|
|
60
61
|
useSvgId,
|
|
61
62
|
variantMaterial,
|
|
62
63
|
vectorElement
|
|
63
|
-
} from "./chunks/chunk-
|
|
64
|
+
} from "./chunks/chunk-TAQHE3C5.js";
|
|
64
65
|
import {
|
|
66
|
+
ThemeProvider,
|
|
65
67
|
getMotionStats,
|
|
66
68
|
refreshMotion,
|
|
67
69
|
setMotionFrameRate,
|
|
68
70
|
subscribeTick,
|
|
71
|
+
themes,
|
|
69
72
|
useBorder,
|
|
70
73
|
useDecoration,
|
|
71
74
|
usePauseOffscreen,
|
|
72
75
|
useSmoothWheel,
|
|
73
76
|
useTabShape,
|
|
77
|
+
useThemePalette,
|
|
74
78
|
useTick
|
|
75
|
-
} from "./chunks/chunk-
|
|
79
|
+
} from "./chunks/chunk-ICRXRGIW.js";
|
|
76
80
|
import {
|
|
77
81
|
LocaleProvider,
|
|
78
82
|
addMessages,
|
|
@@ -436,7 +440,7 @@ function Loader({ src = loaderDefaultImage, animation = "spin", size, speed = 1,
|
|
|
436
440
|
useLoaderStyles();
|
|
437
441
|
const ref = useRef2(null);
|
|
438
442
|
usePauseOffscreen(ref);
|
|
439
|
-
const
|
|
443
|
+
const builtIn = src === loaderDefaultImage;
|
|
440
444
|
const vars = {
|
|
441
445
|
...style,
|
|
442
446
|
...size !== void 0 ? { "--ad-loader-size": typeof size === "number" ? `${size / 16}rem` : Object.hasOwn(loaderSizes, size) ? loaderSizes[size] : size } : {},
|
|
@@ -444,10 +448,10 @@ function Loader({ src = loaderDefaultImage, animation = "spin", size, speed = 1,
|
|
|
444
448
|
...color3 ? { "--ad-loader-color": color3 } : {},
|
|
445
449
|
...palette?.[1] ? { "--ad-loader-color-2": palette[1] } : {},
|
|
446
450
|
...palette?.[2] ? { "--ad-loader-color-3": palette[2] } : {},
|
|
447
|
-
...
|
|
451
|
+
...builtIn || animation === "shine" ? { "--ad-loader-src": `url("${src}")` } : {}
|
|
448
452
|
};
|
|
449
453
|
const rings = ["wave", "palette"].includes(animation) ? 2 : ["bounce", "orbit", "glow", "radar"].includes(animation) ? 1 : 0;
|
|
450
|
-
const picture =
|
|
454
|
+
const picture = builtIn ? /* @__PURE__ */ jsx5("span", { className: "ad-loader-img ad-loader-default-image", "aria-hidden": "true" }) : /* @__PURE__ */ jsx5("img", { className: "ad-loader-img", src, alt: "", draggable: false });
|
|
451
455
|
return /* @__PURE__ */ jsxs4("span", { ...mark("Loader", p), ref, role: "status", "aria-label": label ?? tr2("\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430"), "data-animation": animation, style: vars, children: [
|
|
452
456
|
Array.from({ length: rings }, (_, i) => /* @__PURE__ */ jsx5("span", { className: "ad-loader-ring", "aria-hidden": "true" }, i)),
|
|
453
457
|
picture,
|
|
@@ -457,170 +461,11 @@ function Loader({ src = loaderDefaultImage, animation = "spin", size, speed = 1,
|
|
|
457
461
|
}
|
|
458
462
|
|
|
459
463
|
// src/components/foundation/LoaderGenerator/LoaderGenerator.tsx
|
|
460
|
-
import { useEffect, useRef as
|
|
461
|
-
|
|
462
|
-
// src/components/controls/Tabs/Tabs.tsx
|
|
463
|
-
import { useLayoutEffect, useRef as useRef3, useState } from "react";
|
|
464
|
-
|
|
465
|
-
// src/components/controls/Tab/Tab.tsx
|
|
466
|
-
import { forwardRef } from "react";
|
|
467
|
-
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
468
|
-
var Tab = /* @__PURE__ */ forwardRef(function Tab2({
|
|
469
|
-
selected = false,
|
|
470
|
-
panelId,
|
|
471
|
-
icon,
|
|
472
|
-
endIcon,
|
|
473
|
-
label,
|
|
474
|
-
children,
|
|
475
|
-
tabIndex,
|
|
476
|
-
variant: _variant,
|
|
477
|
-
loading: _loading,
|
|
478
|
-
round: _round,
|
|
479
|
-
size: _size,
|
|
480
|
-
tone: _tone,
|
|
481
|
-
material: _material,
|
|
482
|
-
...p
|
|
483
|
-
}, ref) {
|
|
484
|
-
return /* @__PURE__ */ jsxs5(
|
|
485
|
-
"button",
|
|
486
|
-
{
|
|
487
|
-
...p,
|
|
488
|
-
...mark("Tab", { ...p, size: _size }),
|
|
489
|
-
ref,
|
|
490
|
-
type: "button",
|
|
491
|
-
role: "tab",
|
|
492
|
-
"aria-selected": selected,
|
|
493
|
-
"aria-controls": panelId,
|
|
494
|
-
tabIndex: tabIndex ?? (selected ? 0 : -1),
|
|
495
|
-
children: [
|
|
496
|
-
icon && /* @__PURE__ */ jsx6(Icon, { name: icon }),
|
|
497
|
-
/* @__PURE__ */ jsx6("span", { className: "ad-tab-label", children: children ?? label }),
|
|
498
|
-
endIcon && /* @__PURE__ */ jsx6(Icon, { name: endIcon })
|
|
499
|
-
]
|
|
500
|
-
}
|
|
501
|
-
);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// src/components/controls/Tabs/Tabs.tsx
|
|
505
|
-
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
506
|
-
function Tabs(p) {
|
|
507
|
-
const tr2 = useTr();
|
|
508
|
-
const items = p.items ?? [
|
|
509
|
-
{ value: "appearance", label: tr2("\u0412\u043D\u0435\u0448\u043D\u0438\u0439 \u0432\u0438\u0434"), icon: "palette" },
|
|
510
|
-
{ value: "audio", label: tr2("\u0410\u0443\u0434\u0438\u043E"), icon: "audio" },
|
|
511
|
-
{ value: "advanced", label: tr2("\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E"), icon: "wrench" }
|
|
512
|
-
];
|
|
513
|
-
const [value, setValue] = useControllable(
|
|
514
|
-
p.value,
|
|
515
|
-
p.defaultValue ?? items.find((item) => !item.disabled)?.value ?? "",
|
|
516
|
-
p.onValueChange
|
|
517
|
-
);
|
|
518
|
-
const buttons = useRef3([]);
|
|
519
|
-
const selected = items.findIndex((item) => item.value === value && !item.disabled);
|
|
520
|
-
const firstEnabled = items.findIndex((item) => !item.disabled);
|
|
521
|
-
const fallback = items[firstEnabled]?.value;
|
|
522
|
-
useLayoutEffect(() => {
|
|
523
|
-
if (p.value === void 0 && selected < 0 && fallback !== void 0) setValue(fallback);
|
|
524
|
-
}, [p.value, selected, fallback, setValue]);
|
|
525
|
-
const [place, setPlace] = useState();
|
|
526
|
-
const [moving, setMoving] = useState(false);
|
|
527
|
-
const first = useRef3(true);
|
|
528
|
-
useLayoutEffect(() => {
|
|
529
|
-
const tab = buttons.current[selected];
|
|
530
|
-
if (!tab) return setPlace(void 0);
|
|
531
|
-
const update = () => setPlace({
|
|
532
|
-
"--ad-tabs-x": `${tab.offsetLeft}px`,
|
|
533
|
-
"--ad-tabs-w": `${tab.offsetWidth}px`
|
|
534
|
-
});
|
|
535
|
-
update();
|
|
536
|
-
const observer = createResizeObserver(update);
|
|
537
|
-
observer.observe(tab);
|
|
538
|
-
if (tab.parentElement) observer.observe(tab.parentElement);
|
|
539
|
-
let timer = 0;
|
|
540
|
-
if (!first.current) {
|
|
541
|
-
setMoving(true);
|
|
542
|
-
timer = window.setTimeout(() => setMoving(false), 420);
|
|
543
|
-
}
|
|
544
|
-
first.current = false;
|
|
545
|
-
return () => {
|
|
546
|
-
observer.disconnect();
|
|
547
|
-
clearTimeout(timer);
|
|
548
|
-
};
|
|
549
|
-
}, [selected, items.length]);
|
|
550
|
-
return /* @__PURE__ */ jsxs6(
|
|
551
|
-
"nav",
|
|
552
|
-
{
|
|
553
|
-
...mark("Tabs", p),
|
|
554
|
-
role: "tablist",
|
|
555
|
-
"aria-label": p.label ?? tr2("\u0420\u0430\u0437\u0434\u0435\u043B\u044B"),
|
|
556
|
-
"data-moving": moving || void 0,
|
|
557
|
-
onPointerDown: (e) => {
|
|
558
|
-
const tab = e.target.closest(".ad-tab");
|
|
559
|
-
if (tab && !tab.matches(":disabled")) ripple(tab, e.clientX, e.clientY);
|
|
560
|
-
},
|
|
561
|
-
onKeyDown: (e) => {
|
|
562
|
-
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
|
|
563
|
-
const available = items.map((item, index) => ({ item, index })).filter((x) => !x.item.disabled);
|
|
564
|
-
if (!available.length) return;
|
|
565
|
-
e.preventDefault();
|
|
566
|
-
const current = available.findIndex(
|
|
567
|
-
(x) => buttons.current[x.index] === document.activeElement
|
|
568
|
-
);
|
|
569
|
-
const next = e.key === "Home" ? 0 : e.key === "End" ? available.length - 1 : (current + (e.key === "ArrowRight" ? 1 : -1) + available.length) % available.length;
|
|
570
|
-
setValue(available[next].item.value);
|
|
571
|
-
buttons.current[available[next].index]?.focus();
|
|
572
|
-
},
|
|
573
|
-
children: [
|
|
574
|
-
place && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
575
|
-
/* @__PURE__ */ jsx7("span", { className: "ad-tabs-trail", style: place, "aria-hidden": true }),
|
|
576
|
-
/* @__PURE__ */ jsxs6("span", { className: "ad-tabs-indicator", style: place, "aria-hidden": true, children: [
|
|
577
|
-
/* @__PURE__ */ jsx7("span", { className: "ad-tabs-halo" }),
|
|
578
|
-
/* @__PURE__ */ jsx7("span", { className: "ad-tabs-blade", children: /* @__PURE__ */ jsx7("span", { className: "ad-tabs-blade-edge" }) })
|
|
579
|
-
] }),
|
|
580
|
-
/* @__PURE__ */ jsx7("span", { className: "ad-tabs-rail", style: place, "aria-hidden": true })
|
|
581
|
-
] }),
|
|
582
|
-
items.map((item, index) => /* @__PURE__ */ jsx7(
|
|
583
|
-
Tab,
|
|
584
|
-
{
|
|
585
|
-
id: item.id,
|
|
586
|
-
ref: (n) => {
|
|
587
|
-
buttons.current[index] = n;
|
|
588
|
-
},
|
|
589
|
-
icon: item.icon,
|
|
590
|
-
panelId: item.panelId,
|
|
591
|
-
disabled: item.disabled,
|
|
592
|
-
size: p.size,
|
|
593
|
-
selected: item.value === value,
|
|
594
|
-
tabIndex: index === (selected < 0 ? firstEnabled : selected) ? 0 : -1,
|
|
595
|
-
onClick: () => setValue(item.value),
|
|
596
|
-
children: item.label
|
|
597
|
-
},
|
|
598
|
-
item.value
|
|
599
|
-
))
|
|
600
|
-
]
|
|
601
|
-
}
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// src/components/controls/SegmentedControl/SegmentedControl.tsx
|
|
606
|
-
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
607
|
-
var SegmentedControl = (p) => {
|
|
608
|
-
const tr2 = useTr();
|
|
609
|
-
return /* @__PURE__ */ jsx8("div", { ...mark("SegmentedControl", p), children: /* @__PURE__ */ jsx8(
|
|
610
|
-
Tabs,
|
|
611
|
-
{
|
|
612
|
-
...p,
|
|
613
|
-
items: p.items ?? [
|
|
614
|
-
{ value: "list", label: tr2("\u0421\u043F\u0438\u0441\u043E\u043A"), icon: "list" },
|
|
615
|
-
{ value: "grid", label: tr2("\u041F\u043B\u0438\u0442\u043A\u0430"), icon: "grid" }
|
|
616
|
-
]
|
|
617
|
-
}
|
|
618
|
-
) });
|
|
619
|
-
};
|
|
464
|
+
import { useEffect, useRef as useRef3, useState } from "react";
|
|
620
465
|
|
|
621
466
|
// src/components/layout/Stack/Stack.tsx
|
|
622
|
-
import { Children, Fragment
|
|
623
|
-
import { jsx as
|
|
467
|
+
import { Children, Fragment, isValidElement } from "react";
|
|
468
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
624
469
|
var flex = (value) => value === "start" || value === "end" ? `flex-${value}` : value === "between" || value === "around" || value === "evenly" ? `space-${value}` : value;
|
|
625
470
|
var wrapValue = (value) => value === true ? "wrap" : value === false ? "nowrap" : value;
|
|
626
471
|
function Stack({
|
|
@@ -637,11 +482,11 @@ function Stack({
|
|
|
637
482
|
style,
|
|
638
483
|
...props
|
|
639
484
|
}) {
|
|
640
|
-
const content = divider == null ? children : Children.toArray(children).map((child, index) => /* @__PURE__ */
|
|
641
|
-
index > 0 && /* @__PURE__ */
|
|
485
|
+
const content = divider == null ? children : Children.toArray(children).map((child, index) => /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
486
|
+
index > 0 && /* @__PURE__ */ jsx6("span", { className: "ad-stack__divider", "aria-hidden": "true", children: divider }),
|
|
642
487
|
child
|
|
643
488
|
] }, isValidElement(child) ? child.key : index));
|
|
644
|
-
return /* @__PURE__ */
|
|
489
|
+
return /* @__PURE__ */ jsx6(
|
|
645
490
|
Component,
|
|
646
491
|
{
|
|
647
492
|
...props,
|
|
@@ -660,7 +505,7 @@ function Stack({
|
|
|
660
505
|
}
|
|
661
506
|
|
|
662
507
|
// src/components/foundation/LoaderGenerator/LoaderGenerator.tsx
|
|
663
|
-
import { jsx as
|
|
508
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
664
509
|
var loaderGeneratorAnimations = loaderAnimations.filter(
|
|
665
510
|
(animation) => animation !== "orbit" && animation !== "radar"
|
|
666
511
|
);
|
|
@@ -1074,7 +919,7 @@ ${loaderCss.trim()}
|
|
|
1074
919
|
function LoaderGenerator({ value, defaultValue, onValueChange, ...p }) {
|
|
1075
920
|
const tr2 = useTr();
|
|
1076
921
|
const theme = useThemePalette();
|
|
1077
|
-
const [own, setOwn] =
|
|
922
|
+
const [own, setOwn] = useState(() => defaultValue ?? {});
|
|
1078
923
|
const settings = value ?? {
|
|
1079
924
|
src: loaderDefaultImage,
|
|
1080
925
|
animation: "pulse",
|
|
@@ -1084,12 +929,12 @@ function LoaderGenerator({ value, defaultValue, onValueChange, ...p }) {
|
|
|
1084
929
|
palette: [theme.primary, theme.secondary, theme.primary],
|
|
1085
930
|
...own
|
|
1086
931
|
};
|
|
1087
|
-
const [uploaded, setUploaded] =
|
|
1088
|
-
const [removeBackground, setRemoveBackground] =
|
|
1089
|
-
const [uploadState, setUploadState] =
|
|
932
|
+
const [uploaded, setUploaded] = useState();
|
|
933
|
+
const [removeBackground, setRemoveBackground] = useState(true);
|
|
934
|
+
const [uploadState, setUploadState] = useState({ status: "idle" });
|
|
1090
935
|
const processing = uploadState.status === "processing";
|
|
1091
|
-
const uploadController =
|
|
1092
|
-
const latest =
|
|
936
|
+
const uploadController = useRef3(null);
|
|
937
|
+
const latest = useRef3({ settings, removeBackground, onValueChange });
|
|
1093
938
|
latest.current = { settings, removeBackground, onValueChange };
|
|
1094
939
|
useEffect(() => () => uploadController.current?.abort(), []);
|
|
1095
940
|
const update = (patch) => {
|
|
@@ -1115,11 +960,11 @@ function LoaderGenerator({ value, defaultValue, onValueChange, ...p }) {
|
|
|
1115
960
|
setUploadState({ status: "error", error: cause instanceof Error ? cause.message : tr2("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435.") });
|
|
1116
961
|
}
|
|
1117
962
|
};
|
|
1118
|
-
return /* @__PURE__ */
|
|
1119
|
-
/* @__PURE__ */
|
|
1120
|
-
/* @__PURE__ */
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
963
|
+
return /* @__PURE__ */ jsxs6(Stack, { ...mark("LoaderGenerator", p), gap: 4, children: [
|
|
964
|
+
/* @__PURE__ */ jsxs6(Grid, { className: "ad-loader-generator-top", columns: { base: 1, sm: "minmax(12rem, 1fr) minmax(0, 1.4fr)" }, gap: 4, children: [
|
|
965
|
+
/* @__PURE__ */ jsx7(Grid, { className: "ad-loader-generator-stage", columns: 1, align: "center", justify: "center", children: /* @__PURE__ */ jsx7(Loader, { src: settings.src, animation: settings.animation, size: `${settings.size}rem`, speed: settings.speed, color: settings.color, palette: settings.palette }, loaderRenderKey(settings)) }),
|
|
966
|
+
/* @__PURE__ */ jsxs6(Stack, { className: "ad-loader-generator-controls", gap: 3, children: [
|
|
967
|
+
/* @__PURE__ */ jsx7(
|
|
1123
968
|
FilePicker,
|
|
1124
969
|
{
|
|
1125
970
|
variant: "zone",
|
|
@@ -1131,12 +976,12 @@ function LoaderGenerator({ value, defaultValue, onValueChange, ...p }) {
|
|
|
1131
976
|
onFiles: ([file]) => file && void upload(file)
|
|
1132
977
|
}
|
|
1133
978
|
),
|
|
1134
|
-
uploadState.error && /* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
1136
|
-
/* @__PURE__ */
|
|
1137
|
-
/* @__PURE__ */
|
|
1138
|
-
/* @__PURE__ */
|
|
1139
|
-
/* @__PURE__ */
|
|
979
|
+
uploadState.error && /* @__PURE__ */ jsx7(Typography, { variant: "caption", tone: "danger", children: uploadState.error }),
|
|
980
|
+
/* @__PURE__ */ jsxs6(Stack, { className: "ad-loader-generator-toolbar", direction: "row", align: "end", gap: 3, children: [
|
|
981
|
+
/* @__PURE__ */ jsx7(Stack, { className: "ad-loader-generator-color", children: /* @__PURE__ */ jsx7(ColorPicker, { label: tr2("\u0426\u0432\u0435\u0442 \u044D\u0444\u0444\u0435\u043A\u0442\u043E\u0432"), value: settings.color, onValueChange: (color3) => update({ color: color3 }) }) }),
|
|
982
|
+
/* @__PURE__ */ jsxs6(Stack, { className: "ad-loader-generator-size", gap: 1, children: [
|
|
983
|
+
/* @__PURE__ */ jsx7(Typography, { variant: "label", children: tr2("\u0420\u0430\u0437\u043C\u0435\u0440") }),
|
|
984
|
+
/* @__PURE__ */ jsx7(
|
|
1140
985
|
SegmentedControl,
|
|
1141
986
|
{
|
|
1142
987
|
label: tr2("\u0420\u0430\u0437\u043C\u0435\u0440"),
|
|
@@ -1147,25 +992,25 @@ function LoaderGenerator({ value, defaultValue, onValueChange, ...p }) {
|
|
|
1147
992
|
}
|
|
1148
993
|
)
|
|
1149
994
|
] }),
|
|
1150
|
-
/* @__PURE__ */
|
|
1151
|
-
/* @__PURE__ */
|
|
1152
|
-
/* @__PURE__ */
|
|
1153
|
-
/* @__PURE__ */
|
|
995
|
+
/* @__PURE__ */ jsxs6(Stack, { as: "label", className: "ad-loader-generator-slider", gap: 1, children: [
|
|
996
|
+
/* @__PURE__ */ jsxs6(Stack, { direction: "row", justify: "between", children: [
|
|
997
|
+
/* @__PURE__ */ jsx7(Typography, { variant: "label", children: tr2("\u0421\u043A\u043E\u0440\u043E\u0441\u0442\u044C") }),
|
|
998
|
+
/* @__PURE__ */ jsxs6(Typography, { variant: "mono", tone: "muted", children: [
|
|
1154
999
|
"\xD7",
|
|
1155
1000
|
settings.speed
|
|
1156
1001
|
] })
|
|
1157
1002
|
] }),
|
|
1158
|
-
/* @__PURE__ */
|
|
1003
|
+
/* @__PURE__ */ jsx7(Slider, { label: tr2("\u0421\u043A\u043E\u0440\u043E\u0441\u0442\u044C"), min: 0.25, max: 3, step: 0.25, value: settings.speed, onValueChange: (speed) => update({ speed }) })
|
|
1159
1004
|
] }),
|
|
1160
|
-
/* @__PURE__ */
|
|
1005
|
+
/* @__PURE__ */ jsx7(Switch, { label: tr2("\u0423\u0431\u0440\u0430\u0442\u044C \u0444\u043E\u043D"), checked: removeBackground, onValueChange: (enabled) => {
|
|
1161
1006
|
setRemoveBackground(enabled);
|
|
1162
1007
|
if (uploaded) update({ src: enabled ? uploaded.transparent : uploaded.original });
|
|
1163
1008
|
} })
|
|
1164
1009
|
] })
|
|
1165
1010
|
] })
|
|
1166
1011
|
] }),
|
|
1167
|
-
/* @__PURE__ */
|
|
1168
|
-
/* @__PURE__ */
|
|
1012
|
+
/* @__PURE__ */ jsx7(Typography, { variant: "label", children: tr2("\u0410\u043D\u0438\u043C\u0430\u0446\u0438\u044F") }),
|
|
1013
|
+
/* @__PURE__ */ jsx7(Stack, { className: "ad-loader-generator-grid", direction: "row", gap: 2, role: "radiogroup", "aria-label": tr2("\u0410\u043D\u0438\u043C\u0430\u0446\u0438\u044F"), children: loaderGeneratorAnimations.map((animation) => /* @__PURE__ */ jsxs6(
|
|
1169
1014
|
Button,
|
|
1170
1015
|
{
|
|
1171
1016
|
variant: "ghost",
|
|
@@ -1175,8 +1020,8 @@ function LoaderGenerator({ value, defaultValue, onValueChange, ...p }) {
|
|
|
1175
1020
|
className: "ad-loader-generator-option",
|
|
1176
1021
|
onClick: () => update({ animation }),
|
|
1177
1022
|
children: [
|
|
1178
|
-
/* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1023
|
+
/* @__PURE__ */ jsx7(Loader, { src: settings.src, animation, size: "3.5rem", speed: settings.speed, color: settings.color, palette: settings.palette }),
|
|
1024
|
+
/* @__PURE__ */ jsx7(Typography, { variant: "caption", children: loaderAnimationLabel(animation, tr2) })
|
|
1180
1025
|
]
|
|
1181
1026
|
},
|
|
1182
1027
|
animation
|
|
@@ -1195,17 +1040,17 @@ function MelodixText({ children, as = "span", finish = "silver", glow: glow3 = t
|
|
|
1195
1040
|
}
|
|
1196
1041
|
|
|
1197
1042
|
// src/components/foundation/ThemeEditor/ThemeEditor.tsx
|
|
1198
|
-
import { useEffect as useEffect2, useLayoutEffect
|
|
1043
|
+
import { useEffect as useEffect2, useLayoutEffect, useRef as useRef4, useState as useState2 } from "react";
|
|
1199
1044
|
|
|
1200
1045
|
// src/components/feedback/Badge/Badge.tsx
|
|
1201
|
-
import { jsx as
|
|
1202
|
-
var Badge = (p) => /* @__PURE__ */
|
|
1203
|
-
p.tone && /* @__PURE__ */
|
|
1046
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1047
|
+
var Badge = (p) => /* @__PURE__ */ jsxs7("span", { ...mark("Badge", p), children: [
|
|
1048
|
+
p.tone && /* @__PURE__ */ jsx8("i", { className: "ad-badge-dot", "aria-hidden": true }),
|
|
1204
1049
|
p.children ?? p.label ?? "GPU"
|
|
1205
1050
|
] });
|
|
1206
1051
|
|
|
1207
1052
|
// src/components/feedback/CollapsibleSection/CollapsibleSection.tsx
|
|
1208
|
-
import { jsx as
|
|
1053
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1209
1054
|
var CollapsibleSection = (p) => {
|
|
1210
1055
|
const tr2 = useTr();
|
|
1211
1056
|
const [open, setOpen] = useControllable(
|
|
@@ -1213,7 +1058,7 @@ var CollapsibleSection = (p) => {
|
|
|
1213
1058
|
p.defaultOpen ?? false,
|
|
1214
1059
|
p.onOpenChange
|
|
1215
1060
|
);
|
|
1216
|
-
return /* @__PURE__ */
|
|
1061
|
+
return /* @__PURE__ */ jsxs8(
|
|
1217
1062
|
"details",
|
|
1218
1063
|
{
|
|
1219
1064
|
...mark("CollapsibleSection", p, "card"),
|
|
@@ -1225,34 +1070,34 @@ var CollapsibleSection = (p) => {
|
|
|
1225
1070
|
setOpen(next);
|
|
1226
1071
|
},
|
|
1227
1072
|
children: [
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
p.description && /* @__PURE__ */
|
|
1073
|
+
/* @__PURE__ */ jsxs8("summary", { children: [
|
|
1074
|
+
/* @__PURE__ */ jsx9(Icon, { name: p.icon ?? "braces" }),
|
|
1075
|
+
/* @__PURE__ */ jsxs8("span", { className: "ad-collapse-heading", children: [
|
|
1076
|
+
/* @__PURE__ */ jsx9("span", { children: p.title ?? tr2("\u0422\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0439 JSON") }),
|
|
1077
|
+
p.description && /* @__PURE__ */ jsx9("small", { children: p.description })
|
|
1233
1078
|
] }),
|
|
1234
|
-
/* @__PURE__ */
|
|
1079
|
+
/* @__PURE__ */ jsx9(Icon, { name: "chevron", size: 18 })
|
|
1235
1080
|
] }),
|
|
1236
|
-
/* @__PURE__ */
|
|
1081
|
+
/* @__PURE__ */ jsx9("div", { className: "ad-collapse-content", children: p.children ?? tr2("\u0421\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u0440\u0430\u0441\u043A\u0440\u044B\u0432\u0430\u044E\u0449\u0435\u0433\u043E\u0441\u044F \u0440\u0430\u0437\u0434\u0435\u043B\u0430.") })
|
|
1237
1082
|
]
|
|
1238
1083
|
}
|
|
1239
1084
|
);
|
|
1240
1085
|
};
|
|
1241
1086
|
|
|
1242
1087
|
// src/components/feedback/MessageBar/MessageBar.tsx
|
|
1243
|
-
import { jsx as
|
|
1088
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1244
1089
|
var icons2 = { success: "check", error: "warning", warning: "warning" };
|
|
1245
1090
|
var MessageBar = ({ action, tone = "warning", ...p }) => {
|
|
1246
1091
|
const tr2 = useTr();
|
|
1247
|
-
return /* @__PURE__ */
|
|
1092
|
+
return /* @__PURE__ */ jsxs9(
|
|
1248
1093
|
"div",
|
|
1249
1094
|
{
|
|
1250
1095
|
...mark("MessageBar", { ...p, tone }),
|
|
1251
1096
|
role: tone === "error" ? "alert" : "status",
|
|
1252
1097
|
children: [
|
|
1253
|
-
/* @__PURE__ */
|
|
1254
|
-
/* @__PURE__ */
|
|
1255
|
-
action && /* @__PURE__ */
|
|
1098
|
+
/* @__PURE__ */ jsx10("span", { className: "ad-message-bar-icon", "aria-hidden": true, children: /* @__PURE__ */ jsx10(Icon, { name: icons2[tone] ?? "info" }) }),
|
|
1099
|
+
/* @__PURE__ */ jsx10("span", { className: "ad-message-bar-text", children: p.children ?? tr2("\u0414\u043B\u044F \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u043D\u0443\u0436\u043D\u043E \u0431\u043E\u043B\u044C\u0448\u0435 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u043E\u0433\u043E \u043C\u0435\u0441\u0442\u0430.") }),
|
|
1100
|
+
action && /* @__PURE__ */ jsx10("span", { className: "ad-message-bar-action", children: action })
|
|
1256
1101
|
]
|
|
1257
1102
|
}
|
|
1258
1103
|
);
|
|
@@ -1429,7 +1274,7 @@ function resolveToken(scope, name, kind = "color") {
|
|
|
1429
1274
|
}
|
|
1430
1275
|
|
|
1431
1276
|
// src/components/foundation/ThemeEditor/ThemeEditor.tsx
|
|
1432
|
-
import { jsx as
|
|
1277
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1433
1278
|
var paletteStrip = ["primary-900", "primary-800", "primary-700", "primary-600", "primary", "secondary", "secondary-200", "secondary-100", "secondary-50"];
|
|
1434
1279
|
var neutralStrip = ["neutral-950", "neutral-900", "neutral-850", "neutral-800", "neutral-700", "neutral-600", "neutral-500", "neutral-400", "neutral-300", "neutral-200"];
|
|
1435
1280
|
var tokensWord = (n, locale, translate2) => `${n} ${plural(n, translate2("\u0442\u043E\u043A\u0435\u043D"), translate2("\u0442\u043E\u043A\u0435\u043D\u0430"), translate2("\u0442\u043E\u043A\u0435\u043D\u043E\u0432"), locale)}`;
|
|
@@ -1438,18 +1283,18 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1438
1283
|
const locale = useLocale();
|
|
1439
1284
|
const [config, setConfig] = useControllable(controlled, defaultValue, onValueChange);
|
|
1440
1285
|
const update = (patch) => setConfig({ ...config, ...patch });
|
|
1441
|
-
const root =
|
|
1442
|
-
const [resolved, setResolved] =
|
|
1443
|
-
const [search, setSearch] =
|
|
1444
|
-
const [notice, setNotice] =
|
|
1445
|
-
const [pasteOpen, setPasteOpen] =
|
|
1446
|
-
const [pasted, setPasted] =
|
|
1447
|
-
const pasteAnchor =
|
|
1448
|
-
const importVersion =
|
|
1286
|
+
const root = useRef4(null);
|
|
1287
|
+
const [resolved, setResolved] = useState2({});
|
|
1288
|
+
const [search, setSearch] = useState2("");
|
|
1289
|
+
const [notice, setNotice] = useState2();
|
|
1290
|
+
const [pasteOpen, setPasteOpen] = useState2(false);
|
|
1291
|
+
const [pasted, setPasted] = useState2("");
|
|
1292
|
+
const pasteAnchor = useRef4(null);
|
|
1293
|
+
const importVersion = useRef4(0);
|
|
1449
1294
|
useEffect2(() => () => {
|
|
1450
1295
|
importVersion.current += 1;
|
|
1451
1296
|
}, []);
|
|
1452
|
-
|
|
1297
|
+
useLayoutEffect(() => {
|
|
1453
1298
|
if (config.mode !== "advanced") return;
|
|
1454
1299
|
const node = root.current;
|
|
1455
1300
|
if (!node) return;
|
|
@@ -1480,9 +1325,9 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1480
1325
|
}
|
|
1481
1326
|
};
|
|
1482
1327
|
const needle = search.trim().toLowerCase();
|
|
1483
|
-
return /* @__PURE__ */
|
|
1484
|
-
/* @__PURE__ */
|
|
1485
|
-
/* @__PURE__ */
|
|
1328
|
+
return /* @__PURE__ */ jsxs10("div", { ...mark("ThemeEditor", p), ref: root, children: [
|
|
1329
|
+
/* @__PURE__ */ jsxs10("div", { className: "ad-theme-editor-bar", children: [
|
|
1330
|
+
/* @__PURE__ */ jsx11(
|
|
1486
1331
|
SegmentedControl,
|
|
1487
1332
|
{
|
|
1488
1333
|
size: "sm",
|
|
@@ -1494,13 +1339,13 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1494
1339
|
]
|
|
1495
1340
|
}
|
|
1496
1341
|
),
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
/* @__PURE__ */
|
|
1499
|
-
/* @__PURE__ */
|
|
1342
|
+
/* @__PURE__ */ jsxs10("span", { className: "ad-theme-editor-actions", children: [
|
|
1343
|
+
/* @__PURE__ */ jsx11(Button, { size: "sm", icon: "download", onClick: () => downloadFile(fileName, exportTheme(config)), children: tr2("\u042D\u043A\u0441\u043F\u043E\u0440\u0442") }),
|
|
1344
|
+
/* @__PURE__ */ jsx11(IconButton, { size: "sm", icon: "copy", label: tr2("\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C JSON \u0442\u0435\u043C\u044B"), onClick: async () => {
|
|
1500
1345
|
const done = await copyText(exportTheme(config));
|
|
1501
1346
|
setNotice(done ? { tone: "success", text: tr2("JSON \u0442\u0435\u043C\u044B \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D.") } : { tone: "error", text: tr2("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C.") });
|
|
1502
1347
|
} }),
|
|
1503
|
-
/* @__PURE__ */
|
|
1348
|
+
/* @__PURE__ */ jsx11(
|
|
1504
1349
|
FilePicker,
|
|
1505
1350
|
{
|
|
1506
1351
|
size: "sm",
|
|
@@ -1520,28 +1365,28 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1520
1365
|
}
|
|
1521
1366
|
}
|
|
1522
1367
|
),
|
|
1523
|
-
/* @__PURE__ */
|
|
1524
|
-
/* @__PURE__ */
|
|
1368
|
+
/* @__PURE__ */ jsx11(IconButton, { ref: pasteAnchor, size: "sm", icon: "document", label: tr2("\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C JSON \u0442\u0435\u043C\u044B"), "aria-expanded": pasteOpen, onClick: () => setPasteOpen((v) => !v) }),
|
|
1369
|
+
/* @__PURE__ */ jsx11(IconButton, { size: "sm", variant: "ghost", icon: "reset", label: tr2("\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0442\u0435\u043C\u0443"), onClick: () => {
|
|
1525
1370
|
importVersion.current += 1;
|
|
1526
1371
|
setConfig(defaultThemeConfig);
|
|
1527
1372
|
setNotice(void 0);
|
|
1528
1373
|
} })
|
|
1529
1374
|
] })
|
|
1530
1375
|
] }),
|
|
1531
|
-
/* @__PURE__ */
|
|
1532
|
-
/* @__PURE__ */
|
|
1533
|
-
/* @__PURE__ */
|
|
1376
|
+
/* @__PURE__ */ jsxs10(Popover, { open: pasteOpen, onOpenChange: setPasteOpen, anchorRef: pasteAnchor, align: "end", label: tr2("\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0442\u0435\u043C\u0443"), className: "ad-theme-editor-paste", children: [
|
|
1377
|
+
/* @__PURE__ */ jsx11(TextArea, { label: tr2("JSON \u0442\u0435\u043C\u044B"), value: pasted, onValueChange: setPasted, rows: 6, placeholder: '{ "$type": "neo-ui-theme", \u2026 }' }),
|
|
1378
|
+
/* @__PURE__ */ jsx11(Button, { size: "sm", variant: "primary", icon: "check", disabled: !pasted.trim(), onClick: () => {
|
|
1534
1379
|
if (load(pasted)) {
|
|
1535
1380
|
setPasted("");
|
|
1536
1381
|
setPasteOpen(false);
|
|
1537
1382
|
}
|
|
1538
1383
|
}, children: tr2("\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C") })
|
|
1539
1384
|
] }),
|
|
1540
|
-
notice && /* @__PURE__ */
|
|
1541
|
-
config.mode === "simple" ? /* @__PURE__ */
|
|
1542
|
-
/* @__PURE__ */
|
|
1543
|
-
/* @__PURE__ */
|
|
1544
|
-
/* @__PURE__ */
|
|
1385
|
+
notice && /* @__PURE__ */ jsx11(MessageBar, { tone: notice.tone, children: notice.text }),
|
|
1386
|
+
config.mode === "simple" ? /* @__PURE__ */ jsxs10("div", { className: "ad-theme-editor-simple", children: [
|
|
1387
|
+
/* @__PURE__ */ jsxs10("div", { className: "ad-theme-editor-colors", children: [
|
|
1388
|
+
/* @__PURE__ */ jsx11(ColorPicker, { label: tr2("\u041E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0446\u0432\u0435\u0442"), value: primary, onValueChange: (hex2) => update({ primary: hex2 }) }),
|
|
1389
|
+
/* @__PURE__ */ jsx11(
|
|
1545
1390
|
IconButton,
|
|
1546
1391
|
{
|
|
1547
1392
|
round: true,
|
|
@@ -1551,7 +1396,7 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1551
1396
|
onClick: () => update({ primary: secondary, secondary: primary, autoSecondary: false })
|
|
1552
1397
|
}
|
|
1553
1398
|
),
|
|
1554
|
-
/* @__PURE__ */
|
|
1399
|
+
/* @__PURE__ */ jsx11(
|
|
1555
1400
|
ColorPicker,
|
|
1556
1401
|
{
|
|
1557
1402
|
label: tr2("\u0412\u0442\u043E\u0440\u043E\u0439 \u0446\u0432\u0435\u0442"),
|
|
@@ -1562,7 +1407,7 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1562
1407
|
}
|
|
1563
1408
|
)
|
|
1564
1409
|
] }),
|
|
1565
|
-
/* @__PURE__ */
|
|
1410
|
+
/* @__PURE__ */ jsx11(
|
|
1566
1411
|
Switch,
|
|
1567
1412
|
{
|
|
1568
1413
|
label: tr2("\u0412\u0442\u043E\u0440\u043E\u0439 \u0446\u0432\u0435\u0442 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438"),
|
|
@@ -1570,29 +1415,29 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1570
1415
|
onValueChange: (autoSecondary) => update({ autoSecondary, primary: config.primary ?? primary, secondary })
|
|
1571
1416
|
}
|
|
1572
1417
|
),
|
|
1573
|
-
/* @__PURE__ */
|
|
1574
|
-
/* @__PURE__ */
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
] }) : /* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1579
|
-
/* @__PURE__ */
|
|
1418
|
+
/* @__PURE__ */ jsx11(Typography, { variant: "caption", tone: "muted", children: tr2("\u041F\u0430\u043B\u0438\u0442\u0440\u0430, \u043F\u043E\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u0430\u044F \u0438\u0437 \u044D\u0442\u0438\u0445 \u0446\u0432\u0435\u0442\u043E\u0432:") }),
|
|
1419
|
+
/* @__PURE__ */ jsx11("div", { className: "ad-theme-editor-strip", children: paletteStrip.map((name) => /* @__PURE__ */ jsx11("i", { title: name, style: { background: `var(--ad-${name})` } }, name)) }),
|
|
1420
|
+
/* @__PURE__ */ jsx11("div", { className: "ad-theme-editor-strip", children: neutralStrip.map((name) => /* @__PURE__ */ jsx11("i", { title: name, style: { background: `var(--ad-${name})` } }, name)) })
|
|
1421
|
+
] }) : /* @__PURE__ */ jsxs10("div", { className: "ad-theme-editor-advanced", children: [
|
|
1422
|
+
/* @__PURE__ */ jsxs10("div", { className: "ad-theme-editor-search", children: [
|
|
1423
|
+
/* @__PURE__ */ jsx11(TextField, { size: "sm", placeholder: tr2("\u041D\u0430\u0439\u0442\u0438 \u0442\u043E\u043A\u0435\u043D\u2026"), startAdornment: /* @__PURE__ */ jsx11(Icon, { name: "search" }), clearable: true, value: search, onValueChange: setSearch, "aria-label": tr2("\u041D\u0430\u0439\u0442\u0438 \u0442\u043E\u043A\u0435\u043D") }),
|
|
1424
|
+
/* @__PURE__ */ jsx11(Badge, { tone: changed ? "warning" : void 0, children: changed ? tr2("\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u043E: {count}", { count: changed }) : tr2("\u0412\u0441\u0451 \u0438\u0437 \u0442\u0435\u043C\u044B") })
|
|
1580
1425
|
] }),
|
|
1581
1426
|
themeTokenGroups.map((group, index) => {
|
|
1582
1427
|
const rows = group.tokens.filter((token) => !needle || `${token.name} ${token.label} ${group.title}`.toLowerCase().includes(needle));
|
|
1583
1428
|
if (!rows.length) return null;
|
|
1584
1429
|
const own = rows.filter((token) => tokens[token.name] !== void 0).length;
|
|
1585
|
-
return /* @__PURE__ */
|
|
1430
|
+
return /* @__PURE__ */ jsx11(
|
|
1586
1431
|
CollapsibleSection,
|
|
1587
1432
|
{
|
|
1588
1433
|
title: tr2(group.title),
|
|
1589
1434
|
defaultOpen: index < 2,
|
|
1590
1435
|
open: needle ? true : void 0,
|
|
1591
1436
|
description: own ? tr2("\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u043E: {own} \u0438\u0437 {total}", { own, total: rows.length }) : tokensWord(rows.length, locale, tr2),
|
|
1592
|
-
children: /* @__PURE__ */
|
|
1437
|
+
children: /* @__PURE__ */ jsx11("div", { className: "ad-theme-editor-tokens", children: rows.map((token) => {
|
|
1593
1438
|
const current = tokens[token.name] ?? resolved[token.name] ?? "";
|
|
1594
|
-
return /* @__PURE__ */
|
|
1595
|
-
token.kind === "color" ? /* @__PURE__ */
|
|
1439
|
+
return /* @__PURE__ */ jsxs10("div", { className: "ad-theme-editor-token", "data-changed": tokens[token.name] !== void 0 || void 0, children: [
|
|
1440
|
+
token.kind === "color" ? /* @__PURE__ */ jsx11(
|
|
1596
1441
|
ColorPicker,
|
|
1597
1442
|
{
|
|
1598
1443
|
size: "sm",
|
|
@@ -1600,7 +1445,7 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1600
1445
|
value: /^#[0-9a-f]{6}$/i.test(current) ? current : "#000000",
|
|
1601
1446
|
onValueChange: (hex2) => setToken(token.name, hex2)
|
|
1602
1447
|
}
|
|
1603
|
-
) : /* @__PURE__ */
|
|
1448
|
+
) : /* @__PURE__ */ jsx11(
|
|
1604
1449
|
TextField,
|
|
1605
1450
|
{
|
|
1606
1451
|
size: "sm",
|
|
@@ -1610,11 +1455,11 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1610
1455
|
onValueChange: (value) => setToken(token.name, value)
|
|
1611
1456
|
}
|
|
1612
1457
|
),
|
|
1613
|
-
/* @__PURE__ */
|
|
1458
|
+
/* @__PURE__ */ jsxs10("code", { children: [
|
|
1614
1459
|
"--ad-",
|
|
1615
1460
|
token.name
|
|
1616
1461
|
] }),
|
|
1617
|
-
tokens[token.name] !== void 0 && /* @__PURE__ */
|
|
1462
|
+
tokens[token.name] !== void 0 && /* @__PURE__ */ jsx11(IconButton, { size: "xs", variant: "ghost", icon: "reset", label: tr2("\u0412\u0435\u0440\u043D\u0443\u0442\u044C {name}", { name: tr2(token.label) }), onClick: () => setToken(token.name, void 0) })
|
|
1618
1463
|
] }, token.name);
|
|
1619
1464
|
}) })
|
|
1620
1465
|
},
|
|
@@ -1626,7 +1471,7 @@ function ThemeEditor({ value: controlled, defaultValue = defaultThemeConfig, onV
|
|
|
1626
1471
|
}
|
|
1627
1472
|
|
|
1628
1473
|
// src/components/effects/Tilt/Tilt.tsx
|
|
1629
|
-
import { createElement as createElement5, useRef as
|
|
1474
|
+
import { createElement as createElement5, useRef as useRef5 } from "react";
|
|
1630
1475
|
function Tilt({
|
|
1631
1476
|
as = "div",
|
|
1632
1477
|
max = 14,
|
|
@@ -1634,7 +1479,7 @@ function Tilt({
|
|
|
1634
1479
|
children,
|
|
1635
1480
|
...p
|
|
1636
1481
|
}) {
|
|
1637
|
-
const pauseRef =
|
|
1482
|
+
const pauseRef = useRef5(null);
|
|
1638
1483
|
const motion = useMotion();
|
|
1639
1484
|
usePauseOffscreen(pauseRef);
|
|
1640
1485
|
const set = (el, x, y) => {
|
|
@@ -1666,28 +1511,28 @@ function Tilt({
|
|
|
1666
1511
|
}
|
|
1667
1512
|
|
|
1668
1513
|
// src/components/layout/StatTile/StatTile.tsx
|
|
1669
|
-
import { Fragment as
|
|
1514
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1670
1515
|
function StatTile({ icon = "music", value, label, onClick, tilt = true, badge, badgeLabel, ...p }) {
|
|
1671
|
-
const card = /* @__PURE__ */
|
|
1672
|
-
/* @__PURE__ */
|
|
1673
|
-
/* @__PURE__ */
|
|
1674
|
-
/* @__PURE__ */
|
|
1516
|
+
const card = /* @__PURE__ */ jsxs11(Card, { border: true, padding: "sm", className: "ad-stat-tile-card", children: [
|
|
1517
|
+
/* @__PURE__ */ jsxs11("span", { className: "ad-stat-tile-icon", "aria-hidden": "true", children: [
|
|
1518
|
+
/* @__PURE__ */ jsx12(Icon, { name: icon }),
|
|
1519
|
+
/* @__PURE__ */ jsx12(Icon, { name: "sparkle", className: "ad-stat-tile-spark" })
|
|
1675
1520
|
] }),
|
|
1676
|
-
/* @__PURE__ */
|
|
1677
|
-
/* @__PURE__ */
|
|
1678
|
-
/* @__PURE__ */
|
|
1521
|
+
/* @__PURE__ */ jsxs11("span", { className: "ad-stat-tile-text", children: [
|
|
1522
|
+
/* @__PURE__ */ jsx12(Typography, { as: "strong", variant: "h2", children: value }),
|
|
1523
|
+
/* @__PURE__ */ jsx12(Typography, { variant: "body-sm", tone: "muted", children: label })
|
|
1679
1524
|
] })
|
|
1680
1525
|
] });
|
|
1681
|
-
const body = /* @__PURE__ */
|
|
1682
|
-
tilt ? /* @__PURE__ */
|
|
1683
|
-
badge !== void 0 && badge !== null && /* @__PURE__ */
|
|
1526
|
+
const body = /* @__PURE__ */ jsxs11(Fragment2, { children: [
|
|
1527
|
+
tilt ? /* @__PURE__ */ jsx12(Tilt, { max: 8, children: card }) : card,
|
|
1528
|
+
badge !== void 0 && badge !== null && /* @__PURE__ */ jsx12("span", { className: "ad-stat-tile-badge", "aria-label": badgeLabel, children: badge })
|
|
1684
1529
|
] });
|
|
1685
|
-
return onClick ? /* @__PURE__ */
|
|
1530
|
+
return onClick ? /* @__PURE__ */ jsx12("button", { type: "button", ...mark("StatTile", p), onClick, children: body }) : /* @__PURE__ */ jsx12("div", { ...mark("StatTile", p), children: body });
|
|
1686
1531
|
}
|
|
1687
1532
|
|
|
1688
1533
|
// src/components/effects/Equalizer/Equalizer.tsx
|
|
1689
|
-
import { useRef as
|
|
1690
|
-
import { jsx as
|
|
1534
|
+
import { useRef as useRef6 } from "react";
|
|
1535
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1691
1536
|
function Equalizer({
|
|
1692
1537
|
bars = 5,
|
|
1693
1538
|
playing = true,
|
|
@@ -1697,9 +1542,9 @@ function Equalizer({
|
|
|
1697
1542
|
...p
|
|
1698
1543
|
}) {
|
|
1699
1544
|
const tr2 = useTr();
|
|
1700
|
-
const pauseRef =
|
|
1545
|
+
const pauseRef = useRef6(null);
|
|
1701
1546
|
usePauseOffscreen(pauseRef);
|
|
1702
|
-
return /* @__PURE__ */
|
|
1547
|
+
return /* @__PURE__ */ jsx13(
|
|
1703
1548
|
"span",
|
|
1704
1549
|
{
|
|
1705
1550
|
...mark("Equalizer", p),
|
|
@@ -1708,7 +1553,7 @@ function Equalizer({
|
|
|
1708
1553
|
"aria-label": label ?? (playing ? tr2("\u0418\u0433\u0440\u0430\u0435\u0442") : tr2("\u041F\u0430\u0443\u0437\u0430")),
|
|
1709
1554
|
"data-playing": playing && !levels || void 0,
|
|
1710
1555
|
"data-live": levels ? "" : void 0,
|
|
1711
|
-
children: Array.from({ length: bars }, (_, i) => /* @__PURE__ */
|
|
1556
|
+
children: Array.from({ length: bars }, (_, i) => /* @__PURE__ */ jsx13(
|
|
1712
1557
|
"i",
|
|
1713
1558
|
{
|
|
1714
1559
|
style: levels ? { scale: `1 ${Math.max(0.08, Math.min(1, levels[Math.floor(i / bars * levels.length)] ?? 0))}` } : {
|
|
@@ -1723,26 +1568,26 @@ function Equalizer({
|
|
|
1723
1568
|
}
|
|
1724
1569
|
|
|
1725
1570
|
// src/components/media/MediaCard/MediaCard.tsx
|
|
1726
|
-
import { jsx as
|
|
1571
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1727
1572
|
function MediaCard({ title, subtitle, image, levels, phase = 0, badge, actions, tilt = true, children, ...p }) {
|
|
1728
|
-
const card = /* @__PURE__ */
|
|
1729
|
-
image && /* @__PURE__ */
|
|
1730
|
-
/* @__PURE__ */
|
|
1731
|
-
/* @__PURE__ */
|
|
1732
|
-
/* @__PURE__ */
|
|
1733
|
-
/* @__PURE__ */
|
|
1734
|
-
/* @__PURE__ */
|
|
1735
|
-
subtitle && /* @__PURE__ */
|
|
1573
|
+
const card = /* @__PURE__ */ jsxs12(Card, { border: true, padding: "none", className: "ad-media-card-card", "data-image": image ? "" : void 0, children: [
|
|
1574
|
+
image && /* @__PURE__ */ jsx14("img", { className: "ad-media-card-image", src: image, alt: "", loading: "lazy" }),
|
|
1575
|
+
/* @__PURE__ */ jsx14("span", { className: "ad-media-card-art", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(Equalizer, { bars: 16, levels, phase }) }),
|
|
1576
|
+
/* @__PURE__ */ jsxs12("div", { className: "ad-media-card-content", children: [
|
|
1577
|
+
/* @__PURE__ */ jsxs12("div", { className: "ad-media-card-head", children: [
|
|
1578
|
+
/* @__PURE__ */ jsxs12("div", { className: "ad-media-card-title", children: [
|
|
1579
|
+
/* @__PURE__ */ jsx14(Typography, { as: "strong", variant: "title", truncate: true, children: title }),
|
|
1580
|
+
subtitle && /* @__PURE__ */ jsx14(Typography, { variant: "body-sm", tone: "muted", truncate: true, children: subtitle })
|
|
1736
1581
|
] }),
|
|
1737
1582
|
badge
|
|
1738
1583
|
] }),
|
|
1739
|
-
(children || actions) && /* @__PURE__ */
|
|
1584
|
+
(children || actions) && /* @__PURE__ */ jsxs12("div", { className: "ad-media-card-actions", children: [
|
|
1740
1585
|
children,
|
|
1741
1586
|
actions
|
|
1742
1587
|
] })
|
|
1743
1588
|
] })
|
|
1744
1589
|
] });
|
|
1745
|
-
return /* @__PURE__ */
|
|
1590
|
+
return /* @__PURE__ */ jsx14("div", { ...mark("MediaCard", p), children: tilt ? /* @__PURE__ */ jsx14(Tilt, { max: typeof tilt === "number" ? tilt : 10, children: card }) : card });
|
|
1746
1591
|
}
|
|
1747
1592
|
|
|
1748
1593
|
// src/components/layout/DialogActions/DialogActions.tsx
|
|
@@ -1752,8 +1597,8 @@ var DialogActions = part("DialogActions", "footer");
|
|
|
1752
1597
|
var DialogBody = part("DialogBody", "div");
|
|
1753
1598
|
|
|
1754
1599
|
// src/components/layout/Divider/Divider.tsx
|
|
1755
|
-
import { jsx as
|
|
1756
|
-
var Divider = (p) => /* @__PURE__ */
|
|
1600
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1601
|
+
var Divider = (p) => /* @__PURE__ */ jsx15(
|
|
1757
1602
|
"div",
|
|
1758
1603
|
{
|
|
1759
1604
|
...mark("Divider", p),
|
|
@@ -1763,13 +1608,13 @@ var Divider = (p) => /* @__PURE__ */ jsx18(
|
|
|
1763
1608
|
);
|
|
1764
1609
|
|
|
1765
1610
|
// src/components/layout/ScrollArea/ScrollArea.tsx
|
|
1766
|
-
import { useRef as
|
|
1767
|
-
import { jsx as
|
|
1611
|
+
import { useRef as useRef7 } from "react";
|
|
1612
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1768
1613
|
var scrollHeight = (height) => typeof height === "number" ? `calc(var(--ad-fluid-unit) * ${height})` : height ?? "clamp(10rem, 32dvh, 18rem)";
|
|
1769
1614
|
function ScrollArea(p) {
|
|
1770
|
-
const ref =
|
|
1615
|
+
const ref = useRef7(null);
|
|
1771
1616
|
useSmoothWheel(ref);
|
|
1772
|
-
return /* @__PURE__ */
|
|
1617
|
+
return /* @__PURE__ */ jsx16(
|
|
1773
1618
|
"div",
|
|
1774
1619
|
{
|
|
1775
1620
|
...mark("ScrollArea", p),
|
|
@@ -1783,8 +1628,8 @@ function ScrollArea(p) {
|
|
|
1783
1628
|
}
|
|
1784
1629
|
|
|
1785
1630
|
// src/components/layout/TabPanel/TabPanel.tsx
|
|
1786
|
-
import { jsx as
|
|
1787
|
-
var TabPanel = (p) => /* @__PURE__ */
|
|
1631
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1632
|
+
var TabPanel = (p) => /* @__PURE__ */ jsx17(
|
|
1788
1633
|
"div",
|
|
1789
1634
|
{
|
|
1790
1635
|
...mark("TabPanel", p),
|
|
@@ -1792,7 +1637,7 @@ var TabPanel = (p) => /* @__PURE__ */ jsx20(
|
|
|
1792
1637
|
"aria-labelledby": p.labelledBy,
|
|
1793
1638
|
hidden: p.hidden,
|
|
1794
1639
|
tabIndex: 0,
|
|
1795
|
-
children: /* @__PURE__ */
|
|
1640
|
+
children: /* @__PURE__ */ jsx17("div", { className: "ad-tab-panel-content", children: p.children }, p.labelledBy)
|
|
1796
1641
|
}
|
|
1797
1642
|
);
|
|
1798
1643
|
|
|
@@ -1808,7 +1653,7 @@ var Text = ({ as = "span", ...p }) => createElement6(
|
|
|
1808
1653
|
var Toolbar = part("Toolbar", "div");
|
|
1809
1654
|
|
|
1810
1655
|
// src/components/controls/Link/Link.tsx
|
|
1811
|
-
import { jsx as
|
|
1656
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1812
1657
|
function Link({
|
|
1813
1658
|
href = "#",
|
|
1814
1659
|
icon,
|
|
@@ -1818,7 +1663,7 @@ function Link({
|
|
|
1818
1663
|
children,
|
|
1819
1664
|
...props
|
|
1820
1665
|
}) {
|
|
1821
|
-
return /* @__PURE__ */
|
|
1666
|
+
return /* @__PURE__ */ jsxs13(
|
|
1822
1667
|
"a",
|
|
1823
1668
|
{
|
|
1824
1669
|
...props,
|
|
@@ -1828,23 +1673,23 @@ function Link({
|
|
|
1828
1673
|
rel: external ? "noreferrer" : props.rel,
|
|
1829
1674
|
"data-ad-underline": underline,
|
|
1830
1675
|
children: [
|
|
1831
|
-
icon && /* @__PURE__ */
|
|
1676
|
+
icon && /* @__PURE__ */ jsx18(Icon, { name: icon }),
|
|
1832
1677
|
children,
|
|
1833
|
-
endIcon && /* @__PURE__ */
|
|
1678
|
+
endIcon && /* @__PURE__ */ jsx18(Icon, { name: endIcon })
|
|
1834
1679
|
]
|
|
1835
1680
|
}
|
|
1836
1681
|
);
|
|
1837
1682
|
}
|
|
1838
1683
|
|
|
1839
1684
|
// src/components/controls/SplitButton/SplitButton.tsx
|
|
1840
|
-
import { useRef as
|
|
1685
|
+
import { useRef as useRef8, useState as useState3 } from "react";
|
|
1841
1686
|
|
|
1842
1687
|
// src/components/feedback/MenuItem/MenuItem.tsx
|
|
1843
|
-
import { jsx as
|
|
1688
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1844
1689
|
var MenuItem = (p) => {
|
|
1845
1690
|
const tr2 = useTr();
|
|
1846
1691
|
const label = p.label ?? p.children ?? tr2("\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435");
|
|
1847
|
-
return /* @__PURE__ */
|
|
1692
|
+
return /* @__PURE__ */ jsxs14(
|
|
1848
1693
|
"button",
|
|
1849
1694
|
{
|
|
1850
1695
|
...mark("MenuItem", { ...p, tone: p.danger ? "error" : p.tone }),
|
|
@@ -1853,17 +1698,17 @@ var MenuItem = (p) => {
|
|
|
1853
1698
|
disabled: p.disabled,
|
|
1854
1699
|
onClick: p.onSelect,
|
|
1855
1700
|
children: [
|
|
1856
|
-
/* @__PURE__ */
|
|
1857
|
-
/* @__PURE__ */
|
|
1858
|
-
p.endIcon && /* @__PURE__ */
|
|
1701
|
+
/* @__PURE__ */ jsx19("span", { className: "ad-menu-item-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(Icon, { name: p.icon ?? "more" }) }),
|
|
1702
|
+
/* @__PURE__ */ jsx19("span", { className: "ad-menu-item-label", children: label }),
|
|
1703
|
+
p.endIcon && /* @__PURE__ */ jsx19("span", { className: "ad-menu-item-end", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(Icon, { name: p.endIcon }) })
|
|
1859
1704
|
]
|
|
1860
1705
|
}
|
|
1861
1706
|
);
|
|
1862
1707
|
};
|
|
1863
1708
|
|
|
1864
1709
|
// src/components/feedback/Menu/Menu.tsx
|
|
1865
|
-
import { jsx as
|
|
1866
|
-
var Menu = (p) => /* @__PURE__ */
|
|
1710
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
1711
|
+
var Menu = (p) => /* @__PURE__ */ jsx20(
|
|
1867
1712
|
Popover,
|
|
1868
1713
|
{
|
|
1869
1714
|
...p,
|
|
@@ -1887,7 +1732,7 @@ var Menu = (p) => /* @__PURE__ */ jsx23(
|
|
|
1887
1732
|
buttons[next]?.focus();
|
|
1888
1733
|
},
|
|
1889
1734
|
children: (p.items ?? []).map(
|
|
1890
|
-
(item, i) => item.separator ? /* @__PURE__ */
|
|
1735
|
+
(item, i) => item.separator ? /* @__PURE__ */ jsx20(Divider, {}, item.id ?? String(i)) : /* @__PURE__ */ jsx20(
|
|
1891
1736
|
MenuItem,
|
|
1892
1737
|
{
|
|
1893
1738
|
...item,
|
|
@@ -1904,19 +1749,19 @@ var Menu = (p) => /* @__PURE__ */ jsx23(
|
|
|
1904
1749
|
);
|
|
1905
1750
|
|
|
1906
1751
|
// src/components/controls/SplitButton/SplitButton.tsx
|
|
1907
|
-
import { jsx as
|
|
1752
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1908
1753
|
var SplitButton = (p) => {
|
|
1909
1754
|
const tr2 = useTr();
|
|
1910
|
-
const [open, setOpen] =
|
|
1911
|
-
const anchor =
|
|
1755
|
+
const [open, setOpen] = useState3(false);
|
|
1756
|
+
const anchor = useRef8(null);
|
|
1912
1757
|
const variant = p.variant ?? "primary";
|
|
1913
|
-
return /* @__PURE__ */
|
|
1758
|
+
return /* @__PURE__ */ jsxs15(
|
|
1914
1759
|
"div",
|
|
1915
1760
|
{
|
|
1916
1761
|
...mark("SplitButton", p, variantMaterial[variant]),
|
|
1917
1762
|
"data-ad-variant": variant,
|
|
1918
1763
|
children: [
|
|
1919
|
-
/* @__PURE__ */
|
|
1764
|
+
/* @__PURE__ */ jsx21(
|
|
1920
1765
|
Button,
|
|
1921
1766
|
{
|
|
1922
1767
|
className: "ad-split-button-main",
|
|
@@ -1927,7 +1772,7 @@ var SplitButton = (p) => {
|
|
|
1927
1772
|
children: p.children ?? p.label ?? tr2("\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C")
|
|
1928
1773
|
}
|
|
1929
1774
|
),
|
|
1930
|
-
/* @__PURE__ */
|
|
1775
|
+
/* @__PURE__ */ jsx21(
|
|
1931
1776
|
IconButton,
|
|
1932
1777
|
{
|
|
1933
1778
|
className: "ad-split-button-trigger",
|
|
@@ -1941,7 +1786,7 @@ var SplitButton = (p) => {
|
|
|
1941
1786
|
onClick: () => setOpen((v) => !v)
|
|
1942
1787
|
}
|
|
1943
1788
|
),
|
|
1944
|
-
/* @__PURE__ */
|
|
1789
|
+
/* @__PURE__ */ jsx21(
|
|
1945
1790
|
Menu,
|
|
1946
1791
|
{
|
|
1947
1792
|
open,
|
|
@@ -1956,132 +1801,9 @@ var SplitButton = (p) => {
|
|
|
1956
1801
|
);
|
|
1957
1802
|
};
|
|
1958
1803
|
|
|
1959
|
-
// src/components/effects/ImageShine/ImageShine.tsx
|
|
1960
|
-
import { useRef as useRef10 } from "react";
|
|
1961
|
-
import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1962
|
-
function ImageShine({ src, glow: glow3 = true, label, style, ...p }) {
|
|
1963
|
-
const pauseRef = useRef10(null);
|
|
1964
|
-
usePauseOffscreen(pauseRef);
|
|
1965
|
-
return /* @__PURE__ */ jsxs18(
|
|
1966
|
-
"span",
|
|
1967
|
-
{
|
|
1968
|
-
...mark("ImageShine", p),
|
|
1969
|
-
ref: pauseRef,
|
|
1970
|
-
style: { ...style, "--ad-shine-src": `url("${src}")` },
|
|
1971
|
-
"data-glow": glow3 || void 0,
|
|
1972
|
-
role: label ? "img" : void 0,
|
|
1973
|
-
"aria-label": label,
|
|
1974
|
-
"aria-hidden": label ? void 0 : true,
|
|
1975
|
-
children: [
|
|
1976
|
-
glow3 && /* @__PURE__ */ jsx25("img", { className: "ad-image-shine-glow", src, alt: "", draggable: false }),
|
|
1977
|
-
/* @__PURE__ */ jsx25("img", { className: "ad-image-shine-picture", src, alt: "", draggable: false }),
|
|
1978
|
-
/* @__PURE__ */ jsx25("span", { className: "ad-image-shine-sweep", children: /* @__PURE__ */ jsx25("i", {}) })
|
|
1979
|
-
]
|
|
1980
|
-
}
|
|
1981
|
-
);
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
// src/components/controls/ThemePicker/ThemePicker.tsx
|
|
1985
|
-
import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1986
|
-
var builtIn = {
|
|
1987
|
-
ruby: "Ruby",
|
|
1988
|
-
light: "Light",
|
|
1989
|
-
green: "Green",
|
|
1990
|
-
violet: "Violet"
|
|
1991
|
-
};
|
|
1992
|
-
function ThemePicker(p) {
|
|
1993
|
-
const tr2 = useTr();
|
|
1994
|
-
const first = p.options?.[0]?.value ?? "ruby";
|
|
1995
|
-
const [value, setValue] = useControllable(p.value, p.defaultValue ?? first, p.onValueChange);
|
|
1996
|
-
const options = p.options ?? Object.keys(builtIn).map((key) => ({
|
|
1997
|
-
value: key,
|
|
1998
|
-
label: builtIn[key],
|
|
1999
|
-
color: themes[key][0]
|
|
2000
|
-
}));
|
|
2001
|
-
const active = Math.max(0, options.findIndex((option) => option.value === value));
|
|
2002
|
-
return /* @__PURE__ */ jsx26(
|
|
2003
|
-
"div",
|
|
2004
|
-
{
|
|
2005
|
-
...mark("ThemePicker", p),
|
|
2006
|
-
"data-gallery": p.options ? "" : void 0,
|
|
2007
|
-
role: "radiogroup",
|
|
2008
|
-
"aria-label": p.label ?? tr2("\u0422\u0435\u043C\u0430"),
|
|
2009
|
-
onKeyDown: (event) => {
|
|
2010
|
-
const buttons = [...event.currentTarget.querySelectorAll('[role="radio"]:not(:disabled)')];
|
|
2011
|
-
const index = buttons.indexOf(document.activeElement);
|
|
2012
|
-
const moves = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1, Home: -index, End: buttons.length - index - 1 };
|
|
2013
|
-
const move = moves[event.key];
|
|
2014
|
-
if (move === void 0 || !buttons.length) return;
|
|
2015
|
-
event.preventDefault();
|
|
2016
|
-
const next = buttons[(index + move + buttons.length) % buttons.length];
|
|
2017
|
-
next.focus();
|
|
2018
|
-
next.click();
|
|
2019
|
-
},
|
|
2020
|
-
children: options.map((option, index) => {
|
|
2021
|
-
const chosen = option.value === value;
|
|
2022
|
-
const radio = { role: "radio", "aria-checked": chosen, tabIndex: index === active ? 0 : -1, disabled: p.disabled, onClick: () => setValue(option.value) };
|
|
2023
|
-
if (!p.options) return /* @__PURE__ */ jsxs19(Button, { size: p.size ?? "sm", ...radio, children: [
|
|
2024
|
-
/* @__PURE__ */ jsx26("span", { className: "ad-theme-swatch", style: { background: option.color } }),
|
|
2025
|
-
option.label
|
|
2026
|
-
] }, option.value);
|
|
2027
|
-
return /* @__PURE__ */ jsxs19(
|
|
2028
|
-
"button",
|
|
2029
|
-
{
|
|
2030
|
-
type: "button",
|
|
2031
|
-
className: "ad-theme-card",
|
|
2032
|
-
"aria-label": option.label,
|
|
2033
|
-
...radio,
|
|
2034
|
-
style: option.color ? { "--ad-theme-card": option.color } : void 0,
|
|
2035
|
-
children: [
|
|
2036
|
-
/* @__PURE__ */ jsx26("span", { className: "ad-theme-card-preview", children: option.image && (chosen ? /* @__PURE__ */ jsx26(ImageShine, { src: option.image, glow: false }) : /* @__PURE__ */ jsx26("img", { src: option.image, alt: "", loading: "lazy" })) }),
|
|
2037
|
-
/* @__PURE__ */ jsxs19("span", { className: "ad-theme-card-caption", children: [
|
|
2038
|
-
/* @__PURE__ */ jsx26("strong", { children: option.label }),
|
|
2039
|
-
option.description && /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: option.description })
|
|
2040
|
-
] }),
|
|
2041
|
-
chosen && /* @__PURE__ */ jsx26("span", { className: "ad-theme-card-check", "aria-hidden": "true", children: /* @__PURE__ */ jsx26(Icon, { name: "check" }) })
|
|
2042
|
-
]
|
|
2043
|
-
},
|
|
2044
|
-
option.value
|
|
2045
|
-
);
|
|
2046
|
-
})
|
|
2047
|
-
}
|
|
2048
|
-
);
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// src/components/controls/ToggleButton/ToggleButton.tsx
|
|
2052
|
-
import { forwardRef as forwardRef2 } from "react";
|
|
2053
|
-
var ToggleButton = /* @__PURE__ */ forwardRef2(function ToggleButton2({
|
|
2054
|
-
checked,
|
|
2055
|
-
defaultChecked = false,
|
|
2056
|
-
onValueChange,
|
|
2057
|
-
onClick,
|
|
2058
|
-
...p
|
|
2059
|
-
}, ref) {
|
|
2060
|
-
const [value, setValue] = useControllable(
|
|
2061
|
-
checked,
|
|
2062
|
-
defaultChecked,
|
|
2063
|
-
onValueChange
|
|
2064
|
-
);
|
|
2065
|
-
return buttonView(
|
|
2066
|
-
{
|
|
2067
|
-
...p,
|
|
2068
|
-
ref,
|
|
2069
|
-
"aria-label": p["aria-label"] ?? (!p.children ? p.label : void 0),
|
|
2070
|
-
"aria-pressed": value,
|
|
2071
|
-
children: p.children ?? (p.icon ? null : p.label),
|
|
2072
|
-
label: void 0,
|
|
2073
|
-
onClick: (e) => {
|
|
2074
|
-
onClick?.(e);
|
|
2075
|
-
if (!e.defaultPrevented) setValue(!value);
|
|
2076
|
-
}
|
|
2077
|
-
},
|
|
2078
|
-
"ToggleButton"
|
|
2079
|
-
);
|
|
2080
|
-
});
|
|
2081
|
-
|
|
2082
1804
|
// src/components/feedback/DataTable/DataTable.tsx
|
|
2083
|
-
import { useEffect as useEffect3, useMemo as useMemo2, useRef as
|
|
2084
|
-
import { Fragment as
|
|
1805
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef9, useState as useState4 } from "react";
|
|
1806
|
+
import { Fragment as Fragment3, jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2085
1807
|
var sortStates = {
|
|
2086
1808
|
asc: { next: "desc", aria: "ascending" },
|
|
2087
1809
|
desc: { next: null, aria: "descending" }
|
|
@@ -2124,7 +1846,7 @@ function FilterEditor({
|
|
|
2124
1846
|
onChange
|
|
2125
1847
|
}) {
|
|
2126
1848
|
const tr2 = useTr();
|
|
2127
|
-
const [search, setSearch] =
|
|
1849
|
+
const [search, setSearch] = useState4("");
|
|
2128
1850
|
const text = filter?.kind === "text" ? filter.text : "";
|
|
2129
1851
|
const chosen = filter?.kind === "values" ? filter.values : [];
|
|
2130
1852
|
const query = (kind === "text" ? text : search).trim().toLocaleLowerCase();
|
|
@@ -2133,13 +1855,13 @@ function FilterEditor({
|
|
|
2133
1855
|
(value) => (!query || value.toLocaleLowerCase().includes(query)) && !excluded.has(value)
|
|
2134
1856
|
).slice(0, 100);
|
|
2135
1857
|
if (kind === "text")
|
|
2136
|
-
return /* @__PURE__ */
|
|
1858
|
+
return /* @__PURE__ */ jsx22(
|
|
2137
1859
|
Autocomplete,
|
|
2138
1860
|
{
|
|
2139
1861
|
size: "sm",
|
|
2140
1862
|
autoFocus: true,
|
|
2141
1863
|
placeholder: tr2("\u0421\u043E\u0434\u0435\u0440\u0436\u0438\u0442\u2026"),
|
|
2142
|
-
startAdornment: /* @__PURE__ */
|
|
1864
|
+
startAdornment: /* @__PURE__ */ jsx22(Icon, { name: "search" }),
|
|
2143
1865
|
clearable: true,
|
|
2144
1866
|
options: suggestions,
|
|
2145
1867
|
value: text,
|
|
@@ -2152,19 +1874,19 @@ function FilterEditor({
|
|
|
2152
1874
|
const next = { ...range, [part2]: value === "" ? void 0 : value };
|
|
2153
1875
|
onChange(next.min === void 0 && next.max === void 0 ? void 0 : next);
|
|
2154
1876
|
};
|
|
2155
|
-
return /* @__PURE__ */
|
|
2156
|
-
/* @__PURE__ */
|
|
2157
|
-
/* @__PURE__ */
|
|
1877
|
+
return /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-range", children: [
|
|
1878
|
+
/* @__PURE__ */ jsx22(NumberField, { size: "sm", label: tr2("\u041E\u0442"), controls: false, value: range.min ?? "", onValueChange: (v) => set("min", v) }),
|
|
1879
|
+
/* @__PURE__ */ jsx22(NumberField, { size: "sm", label: tr2("\u0414\u043E"), controls: false, value: range.max ?? "", onValueChange: (v) => set("max", v) })
|
|
2158
1880
|
] });
|
|
2159
1881
|
}
|
|
2160
|
-
return /* @__PURE__ */
|
|
2161
|
-
/* @__PURE__ */
|
|
1882
|
+
return /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-values", children: [
|
|
1883
|
+
/* @__PURE__ */ jsx22(
|
|
2162
1884
|
Autocomplete,
|
|
2163
1885
|
{
|
|
2164
1886
|
size: "sm",
|
|
2165
1887
|
autoFocus: true,
|
|
2166
1888
|
placeholder: tr2("\u041D\u0430\u0439\u0442\u0438 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u2026"),
|
|
2167
|
-
startAdornment: /* @__PURE__ */
|
|
1889
|
+
startAdornment: /* @__PURE__ */ jsx22(Icon, { name: "search" }),
|
|
2168
1890
|
options: suggestions,
|
|
2169
1891
|
value: search,
|
|
2170
1892
|
onValueChange: setSearch,
|
|
@@ -2174,9 +1896,9 @@ function FilterEditor({
|
|
|
2174
1896
|
}
|
|
2175
1897
|
}
|
|
2176
1898
|
),
|
|
2177
|
-
chosen.length > 0 && /* @__PURE__ */
|
|
1899
|
+
chosen.length > 0 && /* @__PURE__ */ jsx22("div", { className: "ad-data-table-values-list", children: chosen.map((value) => /* @__PURE__ */ jsxs16("span", { className: "ad-data-table-chip", children: [
|
|
2178
1900
|
value || "\u2014",
|
|
2179
|
-
/* @__PURE__ */
|
|
1901
|
+
/* @__PURE__ */ jsx22(
|
|
2180
1902
|
"button",
|
|
2181
1903
|
{
|
|
2182
1904
|
type: "button",
|
|
@@ -2185,7 +1907,7 @@ function FilterEditor({
|
|
|
2185
1907
|
const next = chosen.filter((item) => item !== value);
|
|
2186
1908
|
onChange(next.length ? { kind: "values", values: next } : void 0);
|
|
2187
1909
|
},
|
|
2188
|
-
children: /* @__PURE__ */
|
|
1910
|
+
children: /* @__PURE__ */ jsx22(Icon, { name: "close" })
|
|
2189
1911
|
}
|
|
2190
1912
|
)
|
|
2191
1913
|
] }, value)) })
|
|
@@ -2200,15 +1922,15 @@ function ColumnFilter({
|
|
|
2200
1922
|
onChange
|
|
2201
1923
|
}) {
|
|
2202
1924
|
const tr2 = useTr();
|
|
2203
|
-
const [open, setOpen] =
|
|
2204
|
-
const anchor =
|
|
1925
|
+
const [open, setOpen] = useState4(false);
|
|
1926
|
+
const anchor = useRef9(null);
|
|
2205
1927
|
const title = typeof column.title === "string" ? column.title : column.key;
|
|
2206
1928
|
const suggestions = useMemo2(
|
|
2207
1929
|
() => open && kind !== "range" && !options.length ? distinct(column, rows) : options,
|
|
2208
1930
|
[open, kind, column, rows, options]
|
|
2209
1931
|
);
|
|
2210
|
-
return /* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
1932
|
+
return /* @__PURE__ */ jsxs16(Fragment3, { children: [
|
|
1933
|
+
/* @__PURE__ */ jsx22(
|
|
2212
1934
|
IconButton,
|
|
2213
1935
|
{
|
|
2214
1936
|
ref: anchor,
|
|
@@ -2222,7 +1944,7 @@ function ColumnFilter({
|
|
|
2222
1944
|
onClick: () => setOpen((v) => !v)
|
|
2223
1945
|
}
|
|
2224
1946
|
),
|
|
2225
|
-
/* @__PURE__ */
|
|
1947
|
+
/* @__PURE__ */ jsx22(Popover, { open, onOpenChange: setOpen, anchorRef: anchor, label: tr2("\u0424\u0438\u043B\u044C\u0442\u0440: {title}", { title }), className: "ad-data-table-filter-popover", children: /* @__PURE__ */ jsx22(FilterEditor, { kind, options: suggestions, filter, onChange }) })
|
|
2226
1948
|
] });
|
|
2227
1949
|
}
|
|
2228
1950
|
var field = (row, key) => Object.hasOwn(row, key) ? row[key] : void 0;
|
|
@@ -2344,8 +2066,8 @@ function DataTable({
|
|
|
2344
2066
|
defaultSelected,
|
|
2345
2067
|
onSelectionChange
|
|
2346
2068
|
);
|
|
2347
|
-
const generatedKeys =
|
|
2348
|
-
const nextGeneratedKey =
|
|
2069
|
+
const generatedKeys = useRef9(/* @__PURE__ */ new WeakMap());
|
|
2070
|
+
const nextGeneratedKey = useRef9(0);
|
|
2349
2071
|
const keyed = useMemo2(() => {
|
|
2350
2072
|
const identify = (row, index) => {
|
|
2351
2073
|
if (rowKey) return rowKey(row, index);
|
|
@@ -2364,17 +2086,17 @@ function DataTable({
|
|
|
2364
2086
|
return rows.map((row, index) => ({ row, index, key: identify(row, index) }));
|
|
2365
2087
|
}, [rows, rowKey]);
|
|
2366
2088
|
const [query, setQuery] = useControllable(controlledQuery, defaultQuery, onQueryChange);
|
|
2367
|
-
const [hiddenColumns, setHiddenColumns] =
|
|
2089
|
+
const [hiddenColumns, setHiddenColumns] = useState4([]);
|
|
2368
2090
|
const [columnOrder, setColumnOrder] = useControllable(controlledColumnOrder, defaultColumnOrder, onColumnOrderChange);
|
|
2369
2091
|
const [columnPinning, setColumnPinning] = useControllable(controlledColumnPinning, defaultColumnPinning, onColumnPinningChange);
|
|
2370
2092
|
const [expandedRows, setExpandedRows] = useControllable(controlledExpandedRows, defaultExpandedRows, onExpandedRowsChange);
|
|
2371
2093
|
const [dense, setDense] = useControllable(controlledDense, false, onDenseChange);
|
|
2372
|
-
const [columnWidths, setColumnWidths] =
|
|
2094
|
+
const [columnWidths, setColumnWidths] = useState4({});
|
|
2373
2095
|
const customWidth = (key) => Object.hasOwn(columnWidths, key) ? columnWidths[key] : void 0;
|
|
2374
|
-
const [containerWidth, setContainerWidth] =
|
|
2375
|
-
const resizing =
|
|
2376
|
-
const [columnsOpen, setColumnsOpen] =
|
|
2377
|
-
const columnsAnchor =
|
|
2096
|
+
const [containerWidth, setContainerWidth] = useState4(0);
|
|
2097
|
+
const resizing = useRef9(null);
|
|
2098
|
+
const [columnsOpen, setColumnsOpen] = useState4(false);
|
|
2099
|
+
const columnsAnchor = useRef9(null);
|
|
2378
2100
|
const orderedCols = useMemo2(() => {
|
|
2379
2101
|
const byKey = new Map(cols.map((column) => [column.key, column]));
|
|
2380
2102
|
return [.../* @__PURE__ */ new Set([...columnOrder, ...cols.map((column) => column.key)])].flatMap((key) => byKey.has(key) ? [byKey.get(key)] : []);
|
|
@@ -2407,7 +2129,7 @@ function DataTable({
|
|
|
2407
2129
|
}, [pageSize]);
|
|
2408
2130
|
const [filters, setFilters] = useControllable(controlledFilters, defaultFilters, onFiltersChange);
|
|
2409
2131
|
const [groupBy, setGroupBy] = useControllable(controlledGroupBy, defaultGroupBy, onGroupByChange);
|
|
2410
|
-
const [collapsed, setCollapsed] =
|
|
2132
|
+
const [collapsed, setCollapsed] = useState4(/* @__PURE__ */ new Set());
|
|
2411
2133
|
const setFilter = (key, filter) => {
|
|
2412
2134
|
const next = filter ? { ...filters, [key]: filter } : { ...filters };
|
|
2413
2135
|
if (!filter) delete next[key];
|
|
@@ -2508,15 +2230,15 @@ function DataTable({
|
|
|
2508
2230
|
return side ? { [side]: `var(--ad-table-pin-${side}, ${preceding.reduce((sum, key) => sum + widthOf(key), side === "left" ? leading * 40 : renderRowActions ? actionWidth : 0)}px)` } : {};
|
|
2509
2231
|
};
|
|
2510
2232
|
const pinnedSide = (key) => pinning.left.includes(key) ? "left" : pinning.right.includes(key) ? "right" : void 0;
|
|
2511
|
-
const groupRow = (group, viewIndex) => /* @__PURE__ */
|
|
2512
|
-
/* @__PURE__ */
|
|
2513
|
-
/* @__PURE__ */
|
|
2514
|
-
/* @__PURE__ */
|
|
2515
|
-
/* @__PURE__ */
|
|
2233
|
+
const groupRow = (group, viewIndex) => /* @__PURE__ */ jsx22("tr", { "data-virtual-key": `g:${group.name}`, "aria-rowindex": virtualize ? viewIndex + 2 : void 0, className: "ad-data-table-group", "data-collapsed": collapsed.has(group.name) || void 0, children: /* @__PURE__ */ jsx22("td", { colSpan: span, children: /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-group-content", children: [
|
|
2234
|
+
/* @__PURE__ */ jsxs16("button", { type: "button", className: "ad-data-table-group-toggle", "aria-expanded": !collapsed.has(group.name), onClick: () => toggleGroup(group.name), children: [
|
|
2235
|
+
/* @__PURE__ */ jsx22(Icon, { name: "chevron" }),
|
|
2236
|
+
/* @__PURE__ */ jsx22("span", { children: group.name || "\u2014" }),
|
|
2237
|
+
/* @__PURE__ */ jsx22("span", { className: "ad-data-table-group-count", children: group.items.length })
|
|
2516
2238
|
] }),
|
|
2517
|
-
/* @__PURE__ */
|
|
2518
|
-
/* @__PURE__ */
|
|
2519
|
-
/* @__PURE__ */
|
|
2239
|
+
/* @__PURE__ */ jsx22("div", { className: "ad-data-table-group-summaries", children: visibleCols.filter((column) => column.aggregate && column.key !== visibleCols[0]?.key).map((column) => /* @__PURE__ */ jsxs16("span", { className: "ad-data-table-group-summary", children: [
|
|
2240
|
+
/* @__PURE__ */ jsx22("span", { children: column.title }),
|
|
2241
|
+
/* @__PURE__ */ jsx22("strong", { children: group.summaries.get(column.key) })
|
|
2520
2242
|
] }, column.key)) })
|
|
2521
2243
|
] }) }) }, `g:${group.name}`);
|
|
2522
2244
|
const chosen = useMemo2(() => new Set(selection), [selection]);
|
|
@@ -2574,7 +2296,7 @@ function DataTable({
|
|
|
2574
2296
|
});
|
|
2575
2297
|
}, [visible, groupColumn, groupNames, groups, collapsed, current, perPage, expanded, !!renderRowDetails]);
|
|
2576
2298
|
const viewIndexes = useMemo2(() => new Map(viewItems.map((item, index) => [item.key, index])), [viewItems]);
|
|
2577
|
-
const scrollRef =
|
|
2299
|
+
const scrollRef = useRef9(null);
|
|
2578
2300
|
useEffect3(() => {
|
|
2579
2301
|
const element = scrollRef.current;
|
|
2580
2302
|
if (!pinned || !element) return;
|
|
@@ -2585,15 +2307,15 @@ function DataTable({
|
|
|
2585
2307
|
observer.observe(element);
|
|
2586
2308
|
return () => observer.disconnect();
|
|
2587
2309
|
}, [pinned]);
|
|
2588
|
-
const bodyRef =
|
|
2589
|
-
const headerRef =
|
|
2590
|
-
const heights =
|
|
2591
|
-
const headerHeight =
|
|
2592
|
-
const frameRef =
|
|
2593
|
-
const measuredWidth =
|
|
2594
|
-
const geometryChanged =
|
|
2595
|
-
const [geometryVersion, setGeometryVersion] =
|
|
2596
|
-
const [viewport, setViewport] =
|
|
2310
|
+
const bodyRef = useRef9(null);
|
|
2311
|
+
const headerRef = useRef9(null);
|
|
2312
|
+
const heights = useRef9(/* @__PURE__ */ new Map());
|
|
2313
|
+
const headerHeight = useRef9(44);
|
|
2314
|
+
const frameRef = useRef9(0);
|
|
2315
|
+
const measuredWidth = useRef9(0);
|
|
2316
|
+
const geometryChanged = useRef9(false);
|
|
2317
|
+
const [geometryVersion, setGeometryVersion] = useState4(0);
|
|
2318
|
+
const [viewport, setViewport] = useState4({ top: 0, height: typeof maxHeight === "number" ? maxHeight : 480, width: 0 });
|
|
2597
2319
|
const scheduleViewport = () => {
|
|
2598
2320
|
if (!virtualize || frameRef.current) return;
|
|
2599
2321
|
frameRef.current = requestAnimationFrame(() => {
|
|
@@ -2654,8 +2376,8 @@ function DataTable({
|
|
|
2654
2376
|
}
|
|
2655
2377
|
}, [virtualize, viewItems, virtualHeight, viewport.height]);
|
|
2656
2378
|
const measurementIdentity = JSON.stringify([dense, estimatedRowHeight, columnWidths, visibleCols.map((column) => column.key)]);
|
|
2657
|
-
const previousMeasurementIdentity =
|
|
2658
|
-
const previousOffsets =
|
|
2379
|
+
const previousMeasurementIdentity = useRef9(measurementIdentity);
|
|
2380
|
+
const previousOffsets = useRef9(offsets);
|
|
2659
2381
|
useEffect3(() => {
|
|
2660
2382
|
if (previousMeasurementIdentity.current === measurementIdentity) return;
|
|
2661
2383
|
previousMeasurementIdentity.current = measurementIdentity;
|
|
@@ -2716,12 +2438,12 @@ function DataTable({
|
|
|
2716
2438
|
}, [virtualize]);
|
|
2717
2439
|
const toggleDetails = (key) => setExpandedRows(expanded.has(key) ? expandedRows.filter((item) => item !== key) : [...expandedRows, key]);
|
|
2718
2440
|
const specialStyle = (index) => pinning.left.length ? { position: "sticky", left: `var(--ad-table-pin-left, ${index * 40}px)`, zIndex: 2 } : {};
|
|
2719
|
-
const body = loading ? Array.from({ length: Math.min(perPage ?? 4, 6) }, (_, i) => /* @__PURE__ */
|
|
2441
|
+
const body = loading ? Array.from({ length: Math.min(perPage ?? 4, 6) }, (_, i) => /* @__PURE__ */ jsx22("tr", { className: "ad-data-table-loading", children: Array.from({ length: span }, (__, j) => /* @__PURE__ */ jsx22("td", { children: /* @__PURE__ */ jsx22("i", {}) }, j)) }, i)) : visible.length === 0 && !groups.some((group) => collapsed.has(group.name)) ? /* @__PURE__ */ jsx22("tr", { children: /* @__PURE__ */ jsx22("td", { className: "ad-data-table-empty", colSpan: span, children: empty }) }) : viewItems.slice(start, end).map((entry, indexInWindow) => {
|
|
2720
2442
|
const viewIndex = start + indexInWindow;
|
|
2721
2443
|
if (entry.kind === "group") return groupRow(entry.group, viewIndex);
|
|
2722
2444
|
const { row, index, key } = entry.item;
|
|
2723
|
-
if (entry.kind === "details") return /* @__PURE__ */
|
|
2724
|
-
return /* @__PURE__ */
|
|
2445
|
+
if (entry.kind === "details") return /* @__PURE__ */ jsx22("tr", { "data-virtual-key": entry.key, "aria-rowindex": virtualize ? viewIndex + 2 : void 0, "data-details-key": key, className: "ad-data-table-details", children: /* @__PURE__ */ jsx22("td", { colSpan: span, children: renderRowDetails?.(row, index) }) }, entry.key);
|
|
2446
|
+
return /* @__PURE__ */ jsxs16(
|
|
2725
2447
|
"tr",
|
|
2726
2448
|
{
|
|
2727
2449
|
"data-virtual-key": entry.key,
|
|
@@ -2741,7 +2463,7 @@ function DataTable({
|
|
|
2741
2463
|
}
|
|
2742
2464
|
} : void 0,
|
|
2743
2465
|
children: [
|
|
2744
|
-
renderRowDetails && /* @__PURE__ */
|
|
2466
|
+
renderRowDetails && /* @__PURE__ */ jsx22("td", { className: "ad-data-table-expand", "data-label": tr2("\u041F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438"), style: specialStyle(0), onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx22(
|
|
2745
2467
|
IconButton,
|
|
2746
2468
|
{
|
|
2747
2469
|
size: "xs",
|
|
@@ -2753,25 +2475,25 @@ function DataTable({
|
|
|
2753
2475
|
onClick: () => toggleDetails(key)
|
|
2754
2476
|
}
|
|
2755
2477
|
) }),
|
|
2756
|
-
rowNumbers && /* @__PURE__ */
|
|
2757
|
-
selectable && /* @__PURE__ */
|
|
2478
|
+
rowNumbers && /* @__PURE__ */ jsx22("td", { className: "ad-data-table-row-number", style: specialStyle(Number(!!renderRowDetails)), "data-label": tr2("\u041D\u043E\u043C\u0435\u0440 \u0441\u0442\u0440\u043E\u043A\u0438"), children: entry.position + 1 }),
|
|
2479
|
+
selectable && /* @__PURE__ */ jsx22(
|
|
2758
2480
|
"td",
|
|
2759
2481
|
{
|
|
2760
2482
|
className: "ad-data-table-check",
|
|
2761
2483
|
style: specialStyle(Number(!!renderRowDetails) + Number(rowNumbers)),
|
|
2762
2484
|
onClick: (e) => e.stopPropagation(),
|
|
2763
|
-
children: /* @__PURE__ */
|
|
2485
|
+
children: /* @__PURE__ */ jsx22(
|
|
2764
2486
|
Checkbox,
|
|
2765
2487
|
{
|
|
2766
2488
|
size: "sm",
|
|
2767
2489
|
checked: chosen.has(key),
|
|
2768
2490
|
onValueChange: () => toggle(key),
|
|
2769
|
-
label: /* @__PURE__ */
|
|
2491
|
+
label: /* @__PURE__ */ jsx22("span", { className: "ad-sr-only", children: tr2("\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443") })
|
|
2770
2492
|
}
|
|
2771
2493
|
)
|
|
2772
2494
|
}
|
|
2773
2495
|
),
|
|
2774
|
-
visibleCols.map((column) => /* @__PURE__ */
|
|
2496
|
+
visibleCols.map((column) => /* @__PURE__ */ jsx22(
|
|
2775
2497
|
"td",
|
|
2776
2498
|
{
|
|
2777
2499
|
"data-align": column.align,
|
|
@@ -2784,13 +2506,13 @@ function DataTable({
|
|
|
2784
2506
|
},
|
|
2785
2507
|
column.key
|
|
2786
2508
|
)),
|
|
2787
|
-
renderRowActions && /* @__PURE__ */
|
|
2509
|
+
renderRowActions && /* @__PURE__ */ jsx22("td", { className: "ad-data-table-actions", "data-label": tr2("\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F"), style: pinning.right.length ? { position: "sticky", right: "var(--ad-table-pin-right, 0px)", zIndex: 2 } : void 0, onClick: (event) => event.stopPropagation(), children: renderRowActions(row, index) })
|
|
2788
2510
|
]
|
|
2789
2511
|
},
|
|
2790
2512
|
entry.key
|
|
2791
2513
|
);
|
|
2792
2514
|
});
|
|
2793
|
-
return /* @__PURE__ */
|
|
2515
|
+
return /* @__PURE__ */ jsxs16(
|
|
2794
2516
|
"div",
|
|
2795
2517
|
{
|
|
2796
2518
|
...mark("DataTable", p),
|
|
@@ -2801,9 +2523,9 @@ function DataTable({
|
|
|
2801
2523
|
"data-pinned": pinned || void 0,
|
|
2802
2524
|
"aria-busy": loading || void 0,
|
|
2803
2525
|
children: [
|
|
2804
|
-
(caption || searchable || groupable || densityToggle || cols.length > 1 || viewChanged) && /* @__PURE__ */
|
|
2805
|
-
caption && /* @__PURE__ */
|
|
2806
|
-
groupable && /* @__PURE__ */
|
|
2526
|
+
(caption || searchable || groupable || densityToggle || cols.length > 1 || viewChanged) && /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-bar", children: [
|
|
2527
|
+
caption && /* @__PURE__ */ jsx22("strong", { children: caption }),
|
|
2528
|
+
groupable && /* @__PURE__ */ jsx22(
|
|
2807
2529
|
Select,
|
|
2808
2530
|
{
|
|
2809
2531
|
size: "sm",
|
|
@@ -2822,7 +2544,7 @@ function DataTable({
|
|
|
2822
2544
|
]
|
|
2823
2545
|
}
|
|
2824
2546
|
),
|
|
2825
|
-
searchable && /* @__PURE__ */
|
|
2547
|
+
searchable && /* @__PURE__ */ jsx22(
|
|
2826
2548
|
TextField,
|
|
2827
2549
|
{
|
|
2828
2550
|
size: "sm",
|
|
@@ -2832,13 +2554,13 @@ function DataTable({
|
|
|
2832
2554
|
setQuery(value);
|
|
2833
2555
|
setPage(0);
|
|
2834
2556
|
},
|
|
2835
|
-
startAdornment: /* @__PURE__ */
|
|
2557
|
+
startAdornment: /* @__PURE__ */ jsx22(Icon, { name: "search" }),
|
|
2836
2558
|
clearable: true,
|
|
2837
2559
|
"aria-label": tr2("\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0442\u0430\u0431\u043B\u0438\u0446\u0435")
|
|
2838
2560
|
}
|
|
2839
2561
|
),
|
|
2840
|
-
/* @__PURE__ */
|
|
2841
|
-
viewChanged && /* @__PURE__ */
|
|
2562
|
+
/* @__PURE__ */ jsxs16("span", { className: "ad-data-table-tools", children: [
|
|
2563
|
+
viewChanged && /* @__PURE__ */ jsx22(
|
|
2842
2564
|
IconButton,
|
|
2843
2565
|
{
|
|
2844
2566
|
size: "sm",
|
|
@@ -2848,9 +2570,9 @@ function DataTable({
|
|
|
2848
2570
|
onClick: resetView
|
|
2849
2571
|
}
|
|
2850
2572
|
),
|
|
2851
|
-
densityToggle && /* @__PURE__ */
|
|
2852
|
-
cols.length > 1 && /* @__PURE__ */
|
|
2853
|
-
/* @__PURE__ */
|
|
2573
|
+
densityToggle && /* @__PURE__ */ jsx22(IconButton, { size: "sm", variant: "ghost", icon: "list", label: tr2("\u041A\u043E\u043C\u043F\u0430\u043A\u0442\u043D\u044B\u0435 \u0441\u0442\u0440\u043E\u043A\u0438"), "aria-pressed": dense, onClick: () => setDense(!dense) }),
|
|
2574
|
+
cols.length > 1 && /* @__PURE__ */ jsxs16(Fragment3, { children: [
|
|
2575
|
+
/* @__PURE__ */ jsx22(
|
|
2854
2576
|
IconButton,
|
|
2855
2577
|
{
|
|
2856
2578
|
ref: columnsAnchor,
|
|
@@ -2862,8 +2584,8 @@ function DataTable({
|
|
|
2862
2584
|
onClick: () => setColumnsOpen((open) => !open)
|
|
2863
2585
|
}
|
|
2864
2586
|
),
|
|
2865
|
-
/* @__PURE__ */
|
|
2866
|
-
/* @__PURE__ */
|
|
2587
|
+
/* @__PURE__ */ jsx22(Popover, { open: columnsOpen, onOpenChange: setColumnsOpen, anchorRef: columnsAnchor, label: tr2("\u0421\u0442\u043E\u043B\u0431\u0446\u044B"), children: /* @__PURE__ */ jsx22("div", { className: "ad-data-table-columns", children: orderedCols.map((column, index) => /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-column-option", children: [
|
|
2588
|
+
/* @__PURE__ */ jsx22(
|
|
2867
2589
|
Checkbox,
|
|
2868
2590
|
{
|
|
2869
2591
|
size: "sm",
|
|
@@ -2872,7 +2594,7 @@ function DataTable({
|
|
|
2872
2594
|
onValueChange: (shown) => setHiddenColumns((current2) => shown ? current2.filter((key) => key !== column.key) : visibleCols.length > 1 ? [...current2, column.key] : current2)
|
|
2873
2595
|
}
|
|
2874
2596
|
),
|
|
2875
|
-
/* @__PURE__ */
|
|
2597
|
+
/* @__PURE__ */ jsx22("span", { className: "ad-data-table-column-actions", children: [
|
|
2876
2598
|
{ label: "\u041F\u0435\u0440\u0435\u043C\u0435\u0441\u0442\u0438\u0442\u044C \u0432\u043B\u0435\u0432\u043E: {title}", icon: "prev", disabled: index === 0, action: () => {
|
|
2877
2599
|
const next = orderedCols.map((column2) => column2.key);
|
|
2878
2600
|
[next[index - 1], next[index]] = [next[index], next[index - 1]];
|
|
@@ -2891,7 +2613,7 @@ function DataTable({
|
|
|
2891
2613
|
right: pinning.right.includes(column.key) ? pinning.right.filter((key) => key !== column.key) : [...pinning.right, column.key],
|
|
2892
2614
|
left: pinning.left.filter((key) => key !== column.key)
|
|
2893
2615
|
}) }
|
|
2894
|
-
].map((action) => /* @__PURE__ */
|
|
2616
|
+
].map((action) => /* @__PURE__ */ jsx22(
|
|
2895
2617
|
IconButton,
|
|
2896
2618
|
{
|
|
2897
2619
|
size: "xs",
|
|
@@ -2906,7 +2628,7 @@ function DataTable({
|
|
|
2906
2628
|
)) })
|
|
2907
2629
|
] }, column.key)) }) })
|
|
2908
2630
|
] }),
|
|
2909
|
-
/* @__PURE__ */
|
|
2631
|
+
/* @__PURE__ */ jsx22(
|
|
2910
2632
|
IconButton,
|
|
2911
2633
|
{
|
|
2912
2634
|
size: "sm",
|
|
@@ -2925,38 +2647,38 @@ function DataTable({
|
|
|
2925
2647
|
)
|
|
2926
2648
|
] })
|
|
2927
2649
|
] }),
|
|
2928
|
-
activeFilters.length > 0 && /* @__PURE__ */
|
|
2929
|
-
activeFilters.map((column) => /* @__PURE__ */
|
|
2930
|
-
/* @__PURE__ */
|
|
2650
|
+
activeFilters.length > 0 && /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-chips", children: [
|
|
2651
|
+
activeFilters.map((column) => /* @__PURE__ */ jsxs16("span", { className: "ad-data-table-chip", children: [
|
|
2652
|
+
/* @__PURE__ */ jsx22("strong", { children: column.title }),
|
|
2931
2653
|
" ",
|
|
2932
2654
|
describe(filters[column.key], tr2),
|
|
2933
|
-
/* @__PURE__ */
|
|
2655
|
+
/* @__PURE__ */ jsx22("button", { type: "button", "aria-label": tr2("\u0423\u0431\u0440\u0430\u0442\u044C \u0444\u0438\u043B\u044C\u0442\u0440"), onClick: () => setFilter(column.key, void 0), children: /* @__PURE__ */ jsx22(Icon, { name: "close" }) })
|
|
2934
2656
|
] }, column.key)),
|
|
2935
|
-
/* @__PURE__ */
|
|
2657
|
+
/* @__PURE__ */ jsx22(Button, { size: "xs", variant: "ghost", icon: "reset", onClick: () => {
|
|
2936
2658
|
setFilters({});
|
|
2937
2659
|
setPage(0);
|
|
2938
2660
|
}, children: tr2("\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0444\u0438\u043B\u044C\u0442\u0440\u044B") })
|
|
2939
2661
|
] }),
|
|
2940
|
-
/* @__PURE__ */
|
|
2662
|
+
/* @__PURE__ */ jsx22(
|
|
2941
2663
|
"div",
|
|
2942
2664
|
{
|
|
2943
2665
|
ref: scrollRef,
|
|
2944
2666
|
className: "ad-data-table-scroll",
|
|
2945
2667
|
onScroll: virtualize ? scheduleViewport : void 0,
|
|
2946
2668
|
style: { maxHeight: maxHeight ?? (virtualize ? 480 : void 0), height: virtualize ? maxHeight ?? 480 : void 0 },
|
|
2947
|
-
children: /* @__PURE__ */
|
|
2948
|
-
/* @__PURE__ */
|
|
2949
|
-
Array.from({ length: leading }, (_, index) => /* @__PURE__ */
|
|
2950
|
-
visibleCols.map((column) => /* @__PURE__ */
|
|
2951
|
-
renderRowActions && /* @__PURE__ */
|
|
2669
|
+
children: /* @__PURE__ */ jsxs16("table", { "aria-rowcount": knownTotal ? (virtualize ? viewItems.length : total + viewItems.length - visible.length) + 1 : -1, style: pinned ? { width: visibleCols.reduce((sum, column) => sum + widthOf(column.key), leading * 40 + (renderRowActions ? actionWidth : 0)) } : void 0, children: [
|
|
2670
|
+
/* @__PURE__ */ jsxs16("colgroup", { children: [
|
|
2671
|
+
Array.from({ length: leading }, (_, index) => /* @__PURE__ */ jsx22("col", { style: { width: 40 } }, `leading:${index}`)),
|
|
2672
|
+
visibleCols.map((column) => /* @__PURE__ */ jsx22("col", { style: { width: columnWidth(column) } }, column.key)),
|
|
2673
|
+
renderRowActions && /* @__PURE__ */ jsx22("col", { style: { width: actionWidth } })
|
|
2952
2674
|
] }),
|
|
2953
|
-
/* @__PURE__ */
|
|
2954
|
-
renderRowDetails && /* @__PURE__ */
|
|
2955
|
-
rowNumbers && /* @__PURE__ */
|
|
2956
|
-
/* @__PURE__ */
|
|
2675
|
+
/* @__PURE__ */ jsx22("thead", { ref: headerRef, children: /* @__PURE__ */ jsxs16("tr", { children: [
|
|
2676
|
+
renderRowDetails && /* @__PURE__ */ jsx22("th", { className: "ad-data-table-expand", style: specialStyle(0), children: /* @__PURE__ */ jsx22("span", { className: "ad-sr-only", children: tr2("\u041F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438") }) }),
|
|
2677
|
+
rowNumbers && /* @__PURE__ */ jsxs16("th", { className: "ad-data-table-row-number", style: specialStyle(Number(!!renderRowDetails)), scope: "col", children: [
|
|
2678
|
+
/* @__PURE__ */ jsx22("span", { className: "ad-sr-only", children: tr2("\u041D\u043E\u043C\u0435\u0440 \u0441\u0442\u0440\u043E\u043A\u0438") }),
|
|
2957
2679
|
"#"
|
|
2958
2680
|
] }),
|
|
2959
|
-
selectable && /* @__PURE__ */
|
|
2681
|
+
selectable && /* @__PURE__ */ jsx22("th", { className: "ad-data-table-check", style: specialStyle(Number(!!renderRowDetails) + Number(rowNumbers)), children: /* @__PURE__ */ jsx22(
|
|
2960
2682
|
Checkbox,
|
|
2961
2683
|
{
|
|
2962
2684
|
size: "sm",
|
|
@@ -2964,7 +2686,7 @@ function DataTable({
|
|
|
2964
2686
|
indeterminate: someChosen,
|
|
2965
2687
|
onValueChange: toggleAll,
|
|
2966
2688
|
"aria-label": tr2("\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435"),
|
|
2967
|
-
label: /* @__PURE__ */
|
|
2689
|
+
label: /* @__PURE__ */ jsx22("span", { className: "ad-data-table-select-all-label", children: tr2("\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435") })
|
|
2968
2690
|
}
|
|
2969
2691
|
) }),
|
|
2970
2692
|
visibleCols.map((column) => {
|
|
@@ -2974,7 +2696,7 @@ function DataTable({
|
|
|
2974
2696
|
const filter = columnFilters.get(column.key);
|
|
2975
2697
|
const width = customWidth(column.key);
|
|
2976
2698
|
const resize = (delta, base) => setColumnWidths((current2) => ({ ...current2, [column.key]: Math.max(96, Math.min(800, Math.round(base + delta))) }));
|
|
2977
|
-
return /* @__PURE__ */
|
|
2699
|
+
return /* @__PURE__ */ jsxs16(
|
|
2978
2700
|
"th",
|
|
2979
2701
|
{
|
|
2980
2702
|
scope: "col",
|
|
@@ -2984,8 +2706,8 @@ function DataTable({
|
|
|
2984
2706
|
style: columnStyle(column),
|
|
2985
2707
|
"aria-sort": direction ? sortStates[direction].aria : void 0,
|
|
2986
2708
|
children: [
|
|
2987
|
-
/* @__PURE__ */
|
|
2988
|
-
sortable ? /* @__PURE__ */
|
|
2709
|
+
/* @__PURE__ */ jsxs16("span", { className: "ad-data-table-head", children: [
|
|
2710
|
+
sortable ? /* @__PURE__ */ jsxs16(
|
|
2989
2711
|
"button",
|
|
2990
2712
|
{
|
|
2991
2713
|
type: "button",
|
|
@@ -2994,12 +2716,12 @@ function DataTable({
|
|
|
2994
2716
|
onClick: (event) => cycleSort(column.key, !!event?.shiftKey),
|
|
2995
2717
|
children: [
|
|
2996
2718
|
column.title,
|
|
2997
|
-
/* @__PURE__ */
|
|
2998
|
-
sorting.length > 1 && sortIndex >= 0 && /* @__PURE__ */
|
|
2719
|
+
/* @__PURE__ */ jsx22(Icon, { name: "chevron" }),
|
|
2720
|
+
sorting.length > 1 && sortIndex >= 0 && /* @__PURE__ */ jsx22("small", { className: "ad-data-table-sort-priority", children: sortIndex + 1 })
|
|
2999
2721
|
]
|
|
3000
2722
|
}
|
|
3001
2723
|
) : column.title,
|
|
3002
|
-
filter?.kind && /* @__PURE__ */
|
|
2724
|
+
filter?.kind && /* @__PURE__ */ jsx22(
|
|
3003
2725
|
ColumnFilter,
|
|
3004
2726
|
{
|
|
3005
2727
|
column,
|
|
@@ -3011,7 +2733,7 @@ function DataTable({
|
|
|
3011
2733
|
}
|
|
3012
2734
|
)
|
|
3013
2735
|
] }),
|
|
3014
|
-
resizableColumns && column.resizable !== false && /* @__PURE__ */
|
|
2736
|
+
resizableColumns && column.resizable !== false && /* @__PURE__ */ jsx22(
|
|
3015
2737
|
"span",
|
|
3016
2738
|
{
|
|
3017
2739
|
className: "ad-data-table-resize",
|
|
@@ -3059,21 +2781,21 @@ function DataTable({
|
|
|
3059
2781
|
column.key
|
|
3060
2782
|
);
|
|
3061
2783
|
}),
|
|
3062
|
-
renderRowActions && /* @__PURE__ */
|
|
2784
|
+
renderRowActions && /* @__PURE__ */ jsx22("th", { className: "ad-data-table-actions", scope: "col", style: pinning.right.length ? { right: "var(--ad-table-pin-right, 0px)", zIndex: 3 } : void 0, children: tr2("\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F") })
|
|
3063
2785
|
] }) }),
|
|
3064
|
-
/* @__PURE__ */
|
|
3065
|
-
virtualize && !loading && start > 0 && /* @__PURE__ */
|
|
2786
|
+
/* @__PURE__ */ jsxs16("tbody", { ref: bodyRef, children: [
|
|
2787
|
+
virtualize && !loading && start > 0 && /* @__PURE__ */ jsx22("tr", { className: "ad-data-table-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx22("td", { colSpan: span, style: { height: offsets[start] } }) }),
|
|
3066
2788
|
body,
|
|
3067
|
-
virtualize && !loading && end < viewItems.length && /* @__PURE__ */
|
|
2789
|
+
virtualize && !loading && end < viewItems.length && /* @__PURE__ */ jsx22("tr", { className: "ad-data-table-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx22("td", { colSpan: span, style: { height: virtualHeight - offsets[end] } }) })
|
|
3068
2790
|
] })
|
|
3069
2791
|
] })
|
|
3070
2792
|
}
|
|
3071
2793
|
),
|
|
3072
|
-
(perPage || activeFilters.length > 0 || selectable && selection.length > 0) && /* @__PURE__ */
|
|
3073
|
-
/* @__PURE__ */
|
|
3074
|
-
selectable && selection.length > 0 && /* @__PURE__ */
|
|
3075
|
-
perPage && /* @__PURE__ */
|
|
3076
|
-
/* @__PURE__ */
|
|
2794
|
+
(perPage || activeFilters.length > 0 || selectable && selection.length > 0) && /* @__PURE__ */ jsxs16("div", { className: "ad-data-table-foot", children: [
|
|
2795
|
+
/* @__PURE__ */ jsx22("span", { children: selectable && selection.length > 0 ? tr2("\u0412\u044B\u0431\u0440\u0430\u043D\u043E: {count}", { count: selection.length }) : perPage ? tr2(knownTotal ? "{from}\u2013{to} \u0438\u0437 {count}" : "{from}\u2013{to}", { from: visible.length ? current * perPage + 1 : 0, to: visible.length ? current * perPage + visible.length : 0, count: total }) : tr2(query || activeFilters.length ? "{count} \u043D\u0430\u0439\u0434\u0435\u043D\u043E" : "{count} \u0432\u0441\u0435\u0433\u043E", { count: sorted.length }) }),
|
|
2796
|
+
selectable && selection.length > 0 && /* @__PURE__ */ jsx22(Button, { size: "xs", variant: "ghost", icon: "close", onClick: () => setSelection([]), children: tr2("\u0421\u043D\u044F\u0442\u044C \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u0438\u0435") }),
|
|
2797
|
+
perPage && /* @__PURE__ */ jsxs16("span", { className: "ad-data-table-page-controls", children: [
|
|
2798
|
+
/* @__PURE__ */ jsx22(
|
|
3077
2799
|
Select,
|
|
3078
2800
|
{
|
|
3079
2801
|
size: "sm",
|
|
@@ -3085,8 +2807,8 @@ function DataTable({
|
|
|
3085
2807
|
}
|
|
3086
2808
|
}
|
|
3087
2809
|
),
|
|
3088
|
-
pages > 1 && /* @__PURE__ */
|
|
3089
|
-
/* @__PURE__ */
|
|
2810
|
+
pages > 1 && /* @__PURE__ */ jsxs16("span", { className: "ad-data-table-pages", children: [
|
|
2811
|
+
/* @__PURE__ */ jsx22(
|
|
3090
2812
|
IconButton,
|
|
3091
2813
|
{
|
|
3092
2814
|
size: "xs",
|
|
@@ -3097,12 +2819,12 @@ function DataTable({
|
|
|
3097
2819
|
onClick: () => setPage(current - 1)
|
|
3098
2820
|
}
|
|
3099
2821
|
),
|
|
3100
|
-
/* @__PURE__ */
|
|
2822
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
3101
2823
|
current + 1,
|
|
3102
2824
|
" / ",
|
|
3103
2825
|
knownTotal ? pages : "\u2026"
|
|
3104
2826
|
] }),
|
|
3105
|
-
/* @__PURE__ */
|
|
2827
|
+
/* @__PURE__ */ jsx22(
|
|
3106
2828
|
IconButton,
|
|
3107
2829
|
{
|
|
3108
2830
|
size: "xs",
|
|
@@ -3123,8 +2845,8 @@ function DataTable({
|
|
|
3123
2845
|
}
|
|
3124
2846
|
|
|
3125
2847
|
// src/components/feedback/Dialog/Dialog.tsx
|
|
3126
|
-
import { useId, useLayoutEffect as
|
|
3127
|
-
import { jsx as
|
|
2848
|
+
import { useId, useLayoutEffect as useLayoutEffect2, useRef as useRef10, useState as useState5 } from "react";
|
|
2849
|
+
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3128
2850
|
var outside = (dialog, x, y, target) => {
|
|
3129
2851
|
if (target !== dialog) return false;
|
|
3130
2852
|
const box = dialog.getBoundingClientRect();
|
|
@@ -3136,9 +2858,9 @@ var Dialog = (p) => {
|
|
|
3136
2858
|
p.open,
|
|
3137
2859
|
p.defaultOpen ?? false,
|
|
3138
2860
|
p.onOpenChange
|
|
3139
|
-
), [pending, setPending] =
|
|
3140
|
-
const ref =
|
|
3141
|
-
|
|
2861
|
+
), [pending, setPending] = useState5(false), [error, setError] = useState5();
|
|
2862
|
+
const ref = useRef10(null), confirmation = useRef10(0), confirming = useRef10(false), pressedOutside = useRef10(false), titleId = useId(), descId = useId();
|
|
2863
|
+
useLayoutEffect2(() => {
|
|
3142
2864
|
confirming.current = false;
|
|
3143
2865
|
setPending(false);
|
|
3144
2866
|
setError(void 0);
|
|
@@ -3147,7 +2869,7 @@ var Dialog = (p) => {
|
|
|
3147
2869
|
confirming.current = false;
|
|
3148
2870
|
};
|
|
3149
2871
|
}, [open]);
|
|
3150
|
-
|
|
2872
|
+
useLayoutEffect2(() => {
|
|
3151
2873
|
const d = ref.current;
|
|
3152
2874
|
if (!d) return;
|
|
3153
2875
|
const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
@@ -3167,7 +2889,7 @@ var Dialog = (p) => {
|
|
|
3167
2889
|
};
|
|
3168
2890
|
}, [open]);
|
|
3169
2891
|
if (!open) return null;
|
|
3170
|
-
return /* @__PURE__ */
|
|
2892
|
+
return /* @__PURE__ */ jsxs17(
|
|
3171
2893
|
"dialog",
|
|
3172
2894
|
{
|
|
3173
2895
|
...mark("Dialog", p, "dialog"),
|
|
@@ -3197,14 +2919,14 @@ var Dialog = (p) => {
|
|
|
3197
2919
|
if (!pending) setOpen(false);
|
|
3198
2920
|
},
|
|
3199
2921
|
children: [
|
|
3200
|
-
p.art && /* @__PURE__ */
|
|
3201
|
-
/* @__PURE__ */
|
|
2922
|
+
p.art && /* @__PURE__ */ jsx23("div", { className: "ad-dialog-art", "aria-hidden": "true", children: p.art }),
|
|
2923
|
+
/* @__PURE__ */ jsx23(
|
|
3202
2924
|
Header,
|
|
3203
2925
|
{
|
|
3204
|
-
title: /* @__PURE__ */
|
|
2926
|
+
title: /* @__PURE__ */ jsx23("span", { id: titleId, children: p.title ?? tr2("\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435") }),
|
|
3205
2927
|
icon: p.icon,
|
|
3206
2928
|
level: 2,
|
|
3207
|
-
actions: /* @__PURE__ */
|
|
2929
|
+
actions: /* @__PURE__ */ jsx23(
|
|
3208
2930
|
IconButton,
|
|
3209
2931
|
{
|
|
3210
2932
|
variant: "ghost",
|
|
@@ -3216,14 +2938,14 @@ var Dialog = (p) => {
|
|
|
3216
2938
|
)
|
|
3217
2939
|
}
|
|
3218
2940
|
),
|
|
3219
|
-
/* @__PURE__ */
|
|
3220
|
-
p.description && /* @__PURE__ */
|
|
2941
|
+
/* @__PURE__ */ jsxs17(DialogBody, { children: [
|
|
2942
|
+
p.description && /* @__PURE__ */ jsx23("p", { id: descId, children: p.description }),
|
|
3221
2943
|
p.children,
|
|
3222
|
-
error && /* @__PURE__ */
|
|
2944
|
+
error && /* @__PURE__ */ jsx23(MessageBar, { tone: "error", children: error })
|
|
3223
2945
|
] }),
|
|
3224
|
-
(p.cancelLabel !== false || p.confirmLabel !== false) && /* @__PURE__ */
|
|
3225
|
-
p.cancelLabel !== false && /* @__PURE__ */
|
|
3226
|
-
p.confirmLabel !== false && /* @__PURE__ */
|
|
2946
|
+
(p.cancelLabel !== false || p.confirmLabel !== false) && /* @__PURE__ */ jsxs17(DialogActions, { children: [
|
|
2947
|
+
p.cancelLabel !== false && /* @__PURE__ */ jsx23(Button, { disabled: pending, onClick: () => setOpen(false), children: p.cancelLabel ?? tr2("\u041E\u0442\u043C\u0435\u043D\u0430") }),
|
|
2948
|
+
p.confirmLabel !== false && /* @__PURE__ */ jsx23(
|
|
3227
2949
|
Button,
|
|
3228
2950
|
{
|
|
3229
2951
|
variant: p.danger ? "danger" : "primary",
|
|
@@ -3263,28 +2985,28 @@ var Dialog = (p) => {
|
|
|
3263
2985
|
};
|
|
3264
2986
|
|
|
3265
2987
|
// src/components/feedback/EmptyState/EmptyState.tsx
|
|
3266
|
-
import { jsx as
|
|
2988
|
+
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3267
2989
|
var EmptyState = (p) => {
|
|
3268
2990
|
const tr2 = useTr();
|
|
3269
|
-
return /* @__PURE__ */
|
|
3270
|
-
/* @__PURE__ */
|
|
3271
|
-
/* @__PURE__ */
|
|
3272
|
-
/* @__PURE__ */
|
|
2991
|
+
return /* @__PURE__ */ jsxs18("div", { ...mark("EmptyState", p), children: [
|
|
2992
|
+
/* @__PURE__ */ jsx24(Icon, { name: p.icon ?? "music", size: 44 }),
|
|
2993
|
+
/* @__PURE__ */ jsx24("h3", { children: p.title ?? tr2("\u041F\u043E\u043A\u0430 \u043D\u0435\u0442 \u0437\u0430\u043F\u0438\u0441\u0435\u0439") }),
|
|
2994
|
+
/* @__PURE__ */ jsx24("p", { children: p.description ?? tr2("\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0437\u0430\u043F\u0438\u0441\u044C, \u0447\u0442\u043E\u0431\u044B \u043D\u0430\u0447\u0430\u0442\u044C.") }),
|
|
3273
2995
|
p.action
|
|
3274
2996
|
] });
|
|
3275
2997
|
};
|
|
3276
2998
|
|
|
3277
2999
|
// src/components/feedback/KeyValueList/KeyValueList.tsx
|
|
3278
|
-
import { jsx as
|
|
3000
|
+
import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3279
3001
|
var KeyValueList = (p) => {
|
|
3280
3002
|
const tr2 = useTr();
|
|
3281
|
-
return /* @__PURE__ */
|
|
3003
|
+
return /* @__PURE__ */ jsx25("dl", { ...mark("KeyValueList", p), children: (p.items ?? [
|
|
3282
3004
|
["Python Backend", "Ready"],
|
|
3283
3005
|
["AudioService", "Running"],
|
|
3284
3006
|
[tr2("\u0411\u0430\u0437\u0430 \u0434\u0430\u043D\u043D\u044B\u0445"), tr2("\u0418\u0441\u043F\u0440\u0430\u0432\u043D\u043E")]
|
|
3285
|
-
]).map(([key, value], i) => /* @__PURE__ */
|
|
3286
|
-
/* @__PURE__ */
|
|
3287
|
-
/* @__PURE__ */
|
|
3007
|
+
]).map(([key, value], i) => /* @__PURE__ */ jsxs19("div", { children: [
|
|
3008
|
+
/* @__PURE__ */ jsx25("dt", { children: key }),
|
|
3009
|
+
/* @__PURE__ */ jsx25("dd", { children: value })
|
|
3288
3010
|
] }, i)) });
|
|
3289
3011
|
};
|
|
3290
3012
|
|
|
@@ -3293,11 +3015,11 @@ import {
|
|
|
3293
3015
|
cloneElement,
|
|
3294
3016
|
isValidElement as isValidElement2,
|
|
3295
3017
|
useId as useId2,
|
|
3296
|
-
useLayoutEffect as
|
|
3297
|
-
useRef as
|
|
3298
|
-
useState as
|
|
3018
|
+
useLayoutEffect as useLayoutEffect3,
|
|
3019
|
+
useRef as useRef11,
|
|
3020
|
+
useState as useState6
|
|
3299
3021
|
} from "react";
|
|
3300
|
-
import { jsx as
|
|
3022
|
+
import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3301
3023
|
function Tooltip({
|
|
3302
3024
|
content,
|
|
3303
3025
|
children,
|
|
@@ -3305,11 +3027,11 @@ function Tooltip({
|
|
|
3305
3027
|
...p
|
|
3306
3028
|
}) {
|
|
3307
3029
|
const id = useId2();
|
|
3308
|
-
const trigger =
|
|
3309
|
-
const tip =
|
|
3310
|
-
const hovered =
|
|
3311
|
-
const [open, setOpen] =
|
|
3312
|
-
|
|
3030
|
+
const trigger = useRef11(null);
|
|
3031
|
+
const tip = useRef11(null);
|
|
3032
|
+
const hovered = useRef11(false);
|
|
3033
|
+
const [open, setOpen] = useState6(false);
|
|
3034
|
+
useLayoutEffect3(() => {
|
|
3313
3035
|
const node = tip.current;
|
|
3314
3036
|
const target = trigger.current;
|
|
3315
3037
|
if (!open || !node || !target) return;
|
|
@@ -3358,7 +3080,7 @@ function Tooltip({
|
|
|
3358
3080
|
}, [open, placement]);
|
|
3359
3081
|
const show = () => setOpen(true);
|
|
3360
3082
|
const hide = () => setOpen(false);
|
|
3361
|
-
return /* @__PURE__ */
|
|
3083
|
+
return /* @__PURE__ */ jsxs20(
|
|
3362
3084
|
"span",
|
|
3363
3085
|
{
|
|
3364
3086
|
ref: trigger,
|
|
@@ -3388,7 +3110,7 @@ function Tooltip({
|
|
|
3388
3110
|
"aria-describedby": [children.props["aria-describedby"], open ? id : void 0].filter(Boolean).join(" ") || void 0
|
|
3389
3111
|
}
|
|
3390
3112
|
) : children,
|
|
3391
|
-
open && /* @__PURE__ */
|
|
3113
|
+
open && /* @__PURE__ */ jsx26(
|
|
3392
3114
|
"span",
|
|
3393
3115
|
{
|
|
3394
3116
|
...mark("Tooltip", p),
|
|
@@ -3406,7 +3128,7 @@ function Tooltip({
|
|
|
3406
3128
|
|
|
3407
3129
|
// src/components/feedback/ProgressBar/ProgressBar.tsx
|
|
3408
3130
|
import { useId as useId3 } from "react";
|
|
3409
|
-
import { jsx as
|
|
3131
|
+
import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3410
3132
|
var WAVE = Array.from(
|
|
3411
3133
|
{ length: 64 },
|
|
3412
3134
|
(_, i) => 0.25 + 0.75 * Math.abs(Math.sin(i * 0.37) * Math.cos(i * 0.11 + 1.3))
|
|
@@ -3422,7 +3144,7 @@ var ProgressBar = (p) => {
|
|
|
3422
3144
|
const value = clamp(p.value ?? 56, 0, max);
|
|
3423
3145
|
const share = p.indeterminate ? 35 : value / max * 100;
|
|
3424
3146
|
const clip = `ad-wave-${useId3().replace(/:/g, "")}`;
|
|
3425
|
-
return /* @__PURE__ */
|
|
3147
|
+
return /* @__PURE__ */ jsx27(
|
|
3426
3148
|
"div",
|
|
3427
3149
|
{
|
|
3428
3150
|
...mark("ProgressBar", p),
|
|
@@ -3433,17 +3155,17 @@ var ProgressBar = (p) => {
|
|
|
3433
3155
|
"aria-valuenow": p.indeterminate ? void 0 : value,
|
|
3434
3156
|
"data-indeterminate": p.indeterminate || void 0,
|
|
3435
3157
|
"data-variant": p.variant === "wave" ? "wave" : void 0,
|
|
3436
|
-
children: p.variant === "wave" ? /* @__PURE__ */
|
|
3437
|
-
/* @__PURE__ */
|
|
3438
|
-
/* @__PURE__ */
|
|
3439
|
-
/* @__PURE__ */
|
|
3440
|
-
] }) : /* @__PURE__ */
|
|
3158
|
+
children: p.variant === "wave" ? /* @__PURE__ */ jsxs21("svg", { viewBox: "0 0 128 24", preserveAspectRatio: "none", "aria-hidden": "true", children: [
|
|
3159
|
+
/* @__PURE__ */ jsx27("defs", { children: /* @__PURE__ */ jsx27("clipPath", { id: clip, children: /* @__PURE__ */ jsx27("rect", { width: 128 * share / 100, height: "24" }) }) }),
|
|
3160
|
+
/* @__PURE__ */ jsx27("path", { className: "ad-progress-wave-idle", d: wavePath }),
|
|
3161
|
+
/* @__PURE__ */ jsx27("path", { className: "ad-progress-wave-done", d: wavePath, clipPath: `url(#${clip})` })
|
|
3162
|
+
] }) : /* @__PURE__ */ jsx27("span", { style: { width: `${share}%` } })
|
|
3441
3163
|
}
|
|
3442
3164
|
);
|
|
3443
3165
|
};
|
|
3444
3166
|
|
|
3445
3167
|
// src/components/feedback/StatusIndicator/StatusIndicator.tsx
|
|
3446
|
-
import { jsx as
|
|
3168
|
+
import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
3447
3169
|
var StatusIndicator = ({
|
|
3448
3170
|
status = "success",
|
|
3449
3171
|
...p
|
|
@@ -3458,14 +3180,14 @@ var StatusIndicator = ({
|
|
|
3458
3180
|
offline: tr2("\u041D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043E"),
|
|
3459
3181
|
info: tr2("\u0418\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F")
|
|
3460
3182
|
};
|
|
3461
|
-
return /* @__PURE__ */
|
|
3462
|
-
/* @__PURE__ */
|
|
3463
|
-
/* @__PURE__ */
|
|
3183
|
+
return /* @__PURE__ */ jsxs22("span", { ...mark("StatusIndicator", { ...p, tone: status }), children: [
|
|
3184
|
+
/* @__PURE__ */ jsx28("span", { className: "ad-status-dot", "aria-hidden": true, children: /* @__PURE__ */ jsx28("i", {}) }),
|
|
3185
|
+
/* @__PURE__ */ jsx28("span", { children: p.label ?? labels[status] })
|
|
3464
3186
|
] });
|
|
3465
3187
|
};
|
|
3466
3188
|
|
|
3467
3189
|
// src/components/feedback/Steps/Steps.tsx
|
|
3468
|
-
import { jsx as
|
|
3190
|
+
import { jsx as jsx29, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3469
3191
|
var Steps = (p) => {
|
|
3470
3192
|
const tr2 = useTr();
|
|
3471
3193
|
const steps = p.steps ?? [
|
|
@@ -3476,17 +3198,17 @@ var Steps = (p) => {
|
|
|
3476
3198
|
tr2("\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430")
|
|
3477
3199
|
];
|
|
3478
3200
|
const current = p.current ?? 3;
|
|
3479
|
-
return /* @__PURE__ */
|
|
3201
|
+
return /* @__PURE__ */ jsx29("ol", { ...mark("Steps", p), children: steps.map((label, i) => {
|
|
3480
3202
|
const state = i < current ? "done" : i === current ? "current" : "todo";
|
|
3481
|
-
return /* @__PURE__ */
|
|
3203
|
+
return /* @__PURE__ */ jsxs23(
|
|
3482
3204
|
"li",
|
|
3483
3205
|
{
|
|
3484
3206
|
"data-state": state,
|
|
3485
3207
|
"aria-current": state === "current" ? "step" : void 0,
|
|
3486
3208
|
children: [
|
|
3487
|
-
/* @__PURE__ */
|
|
3488
|
-
/* @__PURE__ */
|
|
3489
|
-
state === "current" && i > 0 && /* @__PURE__ */
|
|
3209
|
+
/* @__PURE__ */ jsx29("span", { className: "ad-step-node", "aria-hidden": true, children: state === "done" ? /* @__PURE__ */ jsx29(Icon, { name: "check" }) : i + 1 }),
|
|
3210
|
+
/* @__PURE__ */ jsx29("span", { className: "ad-step-label", children: label }),
|
|
3211
|
+
state === "current" && i > 0 && /* @__PURE__ */ jsx29("span", { className: "ad-steps-glint", "aria-hidden": true })
|
|
3490
3212
|
]
|
|
3491
3213
|
},
|
|
3492
3214
|
`${i}-${label}`
|
|
@@ -3495,8 +3217,8 @@ var Steps = (p) => {
|
|
|
3495
3217
|
};
|
|
3496
3218
|
|
|
3497
3219
|
// src/components/feedback/Toast/Toast.tsx
|
|
3498
|
-
import { useEffect as useEffect4, useRef as
|
|
3499
|
-
import { jsx as
|
|
3220
|
+
import { useEffect as useEffect4, useRef as useRef12 } from "react";
|
|
3221
|
+
import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
3500
3222
|
var Toast = ({
|
|
3501
3223
|
open = true,
|
|
3502
3224
|
duration = 3600,
|
|
@@ -3504,7 +3226,7 @@ var Toast = ({
|
|
|
3504
3226
|
...p
|
|
3505
3227
|
}) => {
|
|
3506
3228
|
const tr2 = useTr();
|
|
3507
|
-
const close =
|
|
3229
|
+
const close = useRef12(onClose);
|
|
3508
3230
|
close.current = onClose;
|
|
3509
3231
|
useEffect4(() => {
|
|
3510
3232
|
if (!open || !onClose || duration <= 0) return;
|
|
@@ -3512,7 +3234,7 @@ var Toast = ({
|
|
|
3512
3234
|
return () => clearTimeout(id);
|
|
3513
3235
|
}, [open, duration, !!onClose]);
|
|
3514
3236
|
if (!open) return null;
|
|
3515
|
-
return /* @__PURE__ */
|
|
3237
|
+
return /* @__PURE__ */ jsxs24(
|
|
3516
3238
|
"div",
|
|
3517
3239
|
{
|
|
3518
3240
|
...mark(
|
|
@@ -3524,14 +3246,14 @@ var Toast = ({
|
|
|
3524
3246
|
role: p.tone === "error" ? "alert" : "status",
|
|
3525
3247
|
"aria-live": p.tone === "error" ? "assertive" : "polite",
|
|
3526
3248
|
children: [
|
|
3527
|
-
/* @__PURE__ */
|
|
3249
|
+
/* @__PURE__ */ jsx30("span", { className: "ad-toast-icon", "aria-hidden": true, children: /* @__PURE__ */ jsx30(
|
|
3528
3250
|
Icon,
|
|
3529
3251
|
{
|
|
3530
3252
|
name: p.tone === "error" || p.tone === "warning" ? "warning" : p.tone === "info" ? "info" : "check"
|
|
3531
3253
|
}
|
|
3532
3254
|
) }),
|
|
3533
|
-
/* @__PURE__ */
|
|
3534
|
-
onClose && duration > 0 && /* @__PURE__ */
|
|
3255
|
+
/* @__PURE__ */ jsx30("span", { children: p.message ?? p.children ?? tr2("\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B") }),
|
|
3256
|
+
onClose && duration > 0 && /* @__PURE__ */ jsx30(
|
|
3535
3257
|
"span",
|
|
3536
3258
|
{
|
|
3537
3259
|
className: "ad-toast-timer",
|
|
@@ -3545,7 +3267,7 @@ var Toast = ({
|
|
|
3545
3267
|
};
|
|
3546
3268
|
|
|
3547
3269
|
// src/components/effects/AnimatedBorder/AnimatedBorder.tsx
|
|
3548
|
-
import { createElement as createElement7, useRef as
|
|
3270
|
+
import { createElement as createElement7, useRef as useRef13 } from "react";
|
|
3549
3271
|
function AnimatedBorder({
|
|
3550
3272
|
as = "div",
|
|
3551
3273
|
shell = false,
|
|
@@ -3553,7 +3275,7 @@ function AnimatedBorder({
|
|
|
3553
3275
|
children,
|
|
3554
3276
|
...props
|
|
3555
3277
|
}) {
|
|
3556
|
-
const ref =
|
|
3278
|
+
const ref = useRef13(null);
|
|
3557
3279
|
useBorder(ref, true, shell, round);
|
|
3558
3280
|
usePauseOffscreen(ref);
|
|
3559
3281
|
return createElement7(
|
|
@@ -3604,8 +3326,8 @@ import {
|
|
|
3604
3326
|
createElement as createElement9,
|
|
3605
3327
|
isValidElement as isValidElement3,
|
|
3606
3328
|
useEffect as useEffect5,
|
|
3607
|
-
useRef as
|
|
3608
|
-
useState as
|
|
3329
|
+
useRef as useRef14,
|
|
3330
|
+
useState as useState7
|
|
3609
3331
|
} from "react";
|
|
3610
3332
|
function Reveal({
|
|
3611
3333
|
as = "div",
|
|
@@ -3616,8 +3338,8 @@ function Reveal({
|
|
|
3616
3338
|
children,
|
|
3617
3339
|
...p
|
|
3618
3340
|
}) {
|
|
3619
|
-
const ref =
|
|
3620
|
-
const [shown, setShown] =
|
|
3341
|
+
const ref = useRef14(null);
|
|
3342
|
+
const [shown, setShown] = useState7(false);
|
|
3621
3343
|
useEffect5(() => {
|
|
3622
3344
|
const node = ref.current;
|
|
3623
3345
|
if (!node) return;
|
|
@@ -3657,15 +3379,15 @@ function Reveal({
|
|
|
3657
3379
|
}
|
|
3658
3380
|
|
|
3659
3381
|
// src/components/effects/GlowText/GlowText.tsx
|
|
3660
|
-
import { createElement as createElement10, useRef as
|
|
3661
|
-
import { jsx as
|
|
3382
|
+
import { createElement as createElement10, useRef as useRef15 } from "react";
|
|
3383
|
+
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
3662
3384
|
function GlowText({
|
|
3663
3385
|
as = "span",
|
|
3664
3386
|
flicker = false,
|
|
3665
3387
|
children,
|
|
3666
3388
|
...p
|
|
3667
3389
|
}) {
|
|
3668
|
-
const pauseRef =
|
|
3390
|
+
const pauseRef = useRef15(null);
|
|
3669
3391
|
usePauseOffscreen(pauseRef);
|
|
3670
3392
|
return createElement10(
|
|
3671
3393
|
as,
|
|
@@ -3676,32 +3398,32 @@ function GlowText({
|
|
|
3676
3398
|
},
|
|
3677
3399
|
// The halo is a still copy of the text on a layer of its own, painted once: the flowing
|
|
3678
3400
|
// gradient repaints only the letters and never re-blurs the glow.
|
|
3679
|
-
/* @__PURE__ */
|
|
3401
|
+
/* @__PURE__ */ jsx31("span", { className: "ad-glow-text-halo", "aria-hidden": "true", children }),
|
|
3680
3402
|
children
|
|
3681
3403
|
);
|
|
3682
3404
|
}
|
|
3683
3405
|
|
|
3684
3406
|
// src/components/feedback/SignalBars/SignalBars.tsx
|
|
3685
|
-
import { jsx as
|
|
3407
|
+
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
3686
3408
|
function SignalBars({ level = 3, bars = 4, weak = false, label, ...p }) {
|
|
3687
3409
|
const tr2 = useTr();
|
|
3688
3410
|
const count = Math.max(1, Number.isFinite(bars) ? Math.floor(bars) : 4);
|
|
3689
3411
|
const strength = Math.max(0, Math.min(count, Number.isFinite(level) ? Math.round(level) : 0));
|
|
3690
|
-
return /* @__PURE__ */
|
|
3412
|
+
return /* @__PURE__ */ jsx32(
|
|
3691
3413
|
"span",
|
|
3692
3414
|
{
|
|
3693
3415
|
...mark("SignalBars", p),
|
|
3694
3416
|
role: "img",
|
|
3695
3417
|
"aria-label": label ?? tr2("\u0421\u0438\u0433\u043D\u0430\u043B: {level} \u0438\u0437 {bars}", { level: strength, bars: count }),
|
|
3696
3418
|
"data-weak": weak || void 0,
|
|
3697
|
-
children: Array.from({ length: count }, (_, i) => /* @__PURE__ */
|
|
3419
|
+
children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx32("i", { "data-lit": i < strength || void 0, style: { height: `${(i + 1) / count * 100}%` } }, i))
|
|
3698
3420
|
}
|
|
3699
3421
|
);
|
|
3700
3422
|
}
|
|
3701
3423
|
|
|
3702
3424
|
// src/components/effects/Shimmer/Shimmer.tsx
|
|
3703
|
-
import { useRef as
|
|
3704
|
-
import { jsx as
|
|
3425
|
+
import { useRef as useRef16 } from "react";
|
|
3426
|
+
import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
3705
3427
|
function Shimmer({
|
|
3706
3428
|
lines = 3,
|
|
3707
3429
|
circle = false,
|
|
@@ -3709,9 +3431,9 @@ function Shimmer({
|
|
|
3709
3431
|
...p
|
|
3710
3432
|
}) {
|
|
3711
3433
|
const tr2 = useTr();
|
|
3712
|
-
const ref =
|
|
3434
|
+
const ref = useRef16(null);
|
|
3713
3435
|
usePauseOffscreen(ref);
|
|
3714
|
-
return /* @__PURE__ */
|
|
3436
|
+
return /* @__PURE__ */ jsxs25(
|
|
3715
3437
|
"div",
|
|
3716
3438
|
{
|
|
3717
3439
|
...mark("Shimmer", p),
|
|
@@ -3720,8 +3442,8 @@ function Shimmer({
|
|
|
3720
3442
|
"aria-label": label ?? tr2("\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430"),
|
|
3721
3443
|
"aria-busy": "true",
|
|
3722
3444
|
children: [
|
|
3723
|
-
circle && /* @__PURE__ */
|
|
3724
|
-
/* @__PURE__ */
|
|
3445
|
+
circle && /* @__PURE__ */ jsx33("i", { className: "ad-shimmer-circle" }),
|
|
3446
|
+
/* @__PURE__ */ jsx33("span", { className: "ad-shimmer-lines", children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx33(
|
|
3725
3447
|
"i",
|
|
3726
3448
|
{
|
|
3727
3449
|
style: { width: i === lines - 1 && lines > 1 ? "60%" : "100%" }
|
|
@@ -3734,8 +3456,8 @@ function Shimmer({
|
|
|
3734
3456
|
}
|
|
3735
3457
|
|
|
3736
3458
|
// src/components/effects/Sparkles/Sparkles.tsx
|
|
3737
|
-
import { useRef as
|
|
3738
|
-
import { jsx as
|
|
3459
|
+
import { useRef as useRef17 } from "react";
|
|
3460
|
+
import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
3739
3461
|
function Sparkles({
|
|
3740
3462
|
count = 10,
|
|
3741
3463
|
color: color3,
|
|
@@ -3743,9 +3465,9 @@ function Sparkles({
|
|
|
3743
3465
|
children,
|
|
3744
3466
|
...p
|
|
3745
3467
|
}) {
|
|
3746
|
-
const pauseRef =
|
|
3468
|
+
const pauseRef = useRef17(null);
|
|
3747
3469
|
usePauseOffscreen(pauseRef);
|
|
3748
|
-
return /* @__PURE__ */
|
|
3470
|
+
return /* @__PURE__ */ jsxs26(
|
|
3749
3471
|
"span",
|
|
3750
3472
|
{
|
|
3751
3473
|
...mark("Sparkles", p),
|
|
@@ -3756,7 +3478,7 @@ function Sparkles({
|
|
|
3756
3478
|
Array.from({ length: count }, (_, i) => {
|
|
3757
3479
|
const angle = i * 137.5;
|
|
3758
3480
|
const reach = 55 + i * 29 % 40;
|
|
3759
|
-
return /* @__PURE__ */
|
|
3481
|
+
return /* @__PURE__ */ jsx34(
|
|
3760
3482
|
"svg",
|
|
3761
3483
|
{
|
|
3762
3484
|
className: "ad-sparkle",
|
|
@@ -3768,7 +3490,7 @@ function Sparkles({
|
|
|
3768
3490
|
width: `${0.45 + i * 7 % 5 / 10}em`,
|
|
3769
3491
|
animationDelay: `${(i * 0.37 % 2.4).toFixed(2)}s`
|
|
3770
3492
|
},
|
|
3771
|
-
children: /* @__PURE__ */
|
|
3493
|
+
children: /* @__PURE__ */ jsx34("path", { d: "M5 0C5.6 3.6 6.4 4.4 10 5C6.4 5.6 5.6 6.4 5 10C4.4 6.4 3.6 5.6 0 5C3.6 4.4 4.4 3.6 5 0Z" })
|
|
3772
3494
|
},
|
|
3773
3495
|
i
|
|
3774
3496
|
);
|
|
@@ -3779,8 +3501,8 @@ function Sparkles({
|
|
|
3779
3501
|
}
|
|
3780
3502
|
|
|
3781
3503
|
// src/components/effects/Beacon/Beacon.tsx
|
|
3782
|
-
import { useRef as
|
|
3783
|
-
import { jsx as
|
|
3504
|
+
import { useRef as useRef18 } from "react";
|
|
3505
|
+
import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
3784
3506
|
function Beacon({
|
|
3785
3507
|
active = true,
|
|
3786
3508
|
color: color3,
|
|
@@ -3788,9 +3510,9 @@ function Beacon({
|
|
|
3788
3510
|
children,
|
|
3789
3511
|
...p
|
|
3790
3512
|
}) {
|
|
3791
|
-
const pauseRef =
|
|
3513
|
+
const pauseRef = useRef18(null);
|
|
3792
3514
|
usePauseOffscreen(pauseRef);
|
|
3793
|
-
return /* @__PURE__ */
|
|
3515
|
+
return /* @__PURE__ */ jsxs27(
|
|
3794
3516
|
"span",
|
|
3795
3517
|
{
|
|
3796
3518
|
...mark("Beacon", p),
|
|
@@ -3798,9 +3520,9 @@ function Beacon({
|
|
|
3798
3520
|
style: { ...style, "--ad-beacon": color3 },
|
|
3799
3521
|
"data-active": active || void 0,
|
|
3800
3522
|
children: [
|
|
3801
|
-
/* @__PURE__ */
|
|
3802
|
-
/* @__PURE__ */
|
|
3803
|
-
/* @__PURE__ */
|
|
3523
|
+
/* @__PURE__ */ jsx35("i", { "aria-hidden": true }),
|
|
3524
|
+
/* @__PURE__ */ jsx35("i", { "aria-hidden": true }),
|
|
3525
|
+
/* @__PURE__ */ jsx35("i", { "aria-hidden": true }),
|
|
3804
3526
|
children
|
|
3805
3527
|
]
|
|
3806
3528
|
}
|
|
@@ -3808,8 +3530,8 @@ function Beacon({
|
|
|
3808
3530
|
}
|
|
3809
3531
|
|
|
3810
3532
|
// src/components/effects/Marquee/Marquee.tsx
|
|
3811
|
-
import { useRef as
|
|
3812
|
-
import { jsx as
|
|
3533
|
+
import { useRef as useRef19 } from "react";
|
|
3534
|
+
import { jsx as jsx36, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3813
3535
|
function Marquee({
|
|
3814
3536
|
duration = 20,
|
|
3815
3537
|
reverse = false,
|
|
@@ -3817,7 +3539,7 @@ function Marquee({
|
|
|
3817
3539
|
children,
|
|
3818
3540
|
...p
|
|
3819
3541
|
}) {
|
|
3820
|
-
const pauseRef =
|
|
3542
|
+
const pauseRef = useRef19(null);
|
|
3821
3543
|
const motion = useMotion();
|
|
3822
3544
|
usePauseOffscreen(pauseRef);
|
|
3823
3545
|
const updatePause = () => {
|
|
@@ -3826,7 +3548,7 @@ function Marquee({
|
|
|
3826
3548
|
node.toggleAttribute("data-ad-paused", node.matches(":hover, :focus-within"));
|
|
3827
3549
|
refreshMotion();
|
|
3828
3550
|
};
|
|
3829
|
-
return /* @__PURE__ */
|
|
3551
|
+
return /* @__PURE__ */ jsx36(
|
|
3830
3552
|
"div",
|
|
3831
3553
|
{
|
|
3832
3554
|
...mark("Marquee", p),
|
|
@@ -3838,9 +3560,9 @@ function Marquee({
|
|
|
3838
3560
|
onPointerLeave: updatePause,
|
|
3839
3561
|
onFocus: updatePause,
|
|
3840
3562
|
onBlur: updatePause,
|
|
3841
|
-
children: /* @__PURE__ */
|
|
3842
|
-
/* @__PURE__ */
|
|
3843
|
-
/* @__PURE__ */
|
|
3563
|
+
children: /* @__PURE__ */ jsxs28("div", { className: "ad-marquee-track", children: [
|
|
3564
|
+
/* @__PURE__ */ jsx36("div", { className: "ad-marquee-group", children }),
|
|
3565
|
+
/* @__PURE__ */ jsx36("div", { className: "ad-marquee-group", "aria-hidden": true, ref: (node) => {
|
|
3844
3566
|
if (node) node.inert = true;
|
|
3845
3567
|
}, children })
|
|
3846
3568
|
] })
|
|
@@ -3849,7 +3571,7 @@ function Marquee({
|
|
|
3849
3571
|
}
|
|
3850
3572
|
|
|
3851
3573
|
// src/components/artwork/Landscape/Landscape.tsx
|
|
3852
|
-
import { useRef as
|
|
3574
|
+
import { useRef as useRef20 } from "react";
|
|
3853
3575
|
|
|
3854
3576
|
// src/components/artwork/useArtwork.ts
|
|
3855
3577
|
import { useEffect as useEffect6 } from "react";
|
|
@@ -3897,7 +3619,7 @@ function useArtwork(ref, key, width, height, painting) {
|
|
|
3897
3619
|
}
|
|
3898
3620
|
|
|
3899
3621
|
// src/components/artwork/Landscape/Landscape.tsx
|
|
3900
|
-
import { jsx as
|
|
3622
|
+
import { jsx as jsx37, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
3901
3623
|
var W = 1220;
|
|
3902
3624
|
var H = 168;
|
|
3903
3625
|
var centerX = 1129;
|
|
@@ -4091,20 +3813,20 @@ var NEAR_RIDGE = [
|
|
|
4091
3813
|
[1220, 132]
|
|
4092
3814
|
];
|
|
4093
3815
|
function Landscape({ shade = true, children, ...p }) {
|
|
4094
|
-
const pauseRef =
|
|
3816
|
+
const pauseRef = useRef20(null);
|
|
4095
3817
|
usePauseOffscreen(pauseRef);
|
|
4096
|
-
const ref =
|
|
3818
|
+
const ref = useRef20(null);
|
|
4097
3819
|
useArtwork(ref, "landscape", W, H, landscape);
|
|
4098
|
-
return /* @__PURE__ */
|
|
4099
|
-
/* @__PURE__ */
|
|
4100
|
-
/* @__PURE__ */
|
|
4101
|
-
children && /* @__PURE__ */
|
|
3820
|
+
return /* @__PURE__ */ jsxs29("div", { ...mark("Landscape", p), ref: pauseRef, "data-shade": shade || void 0, children: [
|
|
3821
|
+
/* @__PURE__ */ jsx37("canvas", { ref, "aria-hidden": true }),
|
|
3822
|
+
/* @__PURE__ */ jsx37("span", { className: "ad-landscape-glow", "aria-hidden": true }),
|
|
3823
|
+
children && /* @__PURE__ */ jsx37("div", { className: "ad-landscape-content", children })
|
|
4102
3824
|
] });
|
|
4103
3825
|
}
|
|
4104
3826
|
|
|
4105
3827
|
// src/components/artwork/Planet/Planet.tsx
|
|
4106
|
-
import { useRef as
|
|
4107
|
-
import { jsx as
|
|
3828
|
+
import { useRef as useRef21 } from "react";
|
|
3829
|
+
import { jsx as jsx38, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
4108
3830
|
var W2 = 515;
|
|
4109
3831
|
var H2 = 114;
|
|
4110
3832
|
var planet = {
|
|
@@ -4146,19 +3868,19 @@ var planet = {
|
|
|
4146
3868
|
}
|
|
4147
3869
|
};
|
|
4148
3870
|
function Planet({ children, ...p }) {
|
|
4149
|
-
const pauseRef =
|
|
3871
|
+
const pauseRef = useRef21(null);
|
|
4150
3872
|
usePauseOffscreen(pauseRef);
|
|
4151
|
-
const ref =
|
|
3873
|
+
const ref = useRef21(null);
|
|
4152
3874
|
useArtwork(ref, "planet", W2, H2, planet);
|
|
4153
|
-
return /* @__PURE__ */
|
|
4154
|
-
/* @__PURE__ */
|
|
4155
|
-
children && /* @__PURE__ */
|
|
3875
|
+
return /* @__PURE__ */ jsxs30("div", { ...mark("Planet", p), ref: pauseRef, children: [
|
|
3876
|
+
/* @__PURE__ */ jsx38("canvas", { ref, "aria-hidden": true }),
|
|
3877
|
+
children && /* @__PURE__ */ jsx38("div", { className: "ad-planet-content", children })
|
|
4156
3878
|
] });
|
|
4157
3879
|
}
|
|
4158
3880
|
|
|
4159
3881
|
// src/components/artwork/NeonWaves/NeonWaves.tsx
|
|
4160
|
-
import { useEffect as useEffect7, useMemo as useMemo3, useRef as
|
|
4161
|
-
import { jsx as
|
|
3882
|
+
import { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef22 } from "react";
|
|
3883
|
+
import { jsx as jsx39, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
4162
3884
|
var W3 = 600;
|
|
4163
3885
|
var H3 = 120;
|
|
4164
3886
|
var SKYLINE = [[0, 0.93], [0.16, 0.86], [0.3, 0.82], [0.42, 0.72], [0.55, 0.64], [0.64, 0.7], [0.74, 0.52], [0.84, 0.1], [0.9, 0.56], [0.95, 0.46], [1, 0.08]];
|
|
@@ -4260,15 +3982,15 @@ function NeonWaves({
|
|
|
4260
3982
|
comets = 0,
|
|
4261
3983
|
...p
|
|
4262
3984
|
}) {
|
|
4263
|
-
const rootRef =
|
|
4264
|
-
const ref =
|
|
4265
|
-
const cometsRef =
|
|
4266
|
-
const lastDraw =
|
|
4267
|
-
const tracks =
|
|
4268
|
-
const paths =
|
|
4269
|
-
const cometElements =
|
|
4270
|
-
const size =
|
|
4271
|
-
const lastTime =
|
|
3985
|
+
const rootRef = useRef22(null);
|
|
3986
|
+
const ref = useRef22(null);
|
|
3987
|
+
const cometsRef = useRef22(null);
|
|
3988
|
+
const lastDraw = useRef22(-1);
|
|
3989
|
+
const tracks = useRef22(/* @__PURE__ */ new Map());
|
|
3990
|
+
const paths = useRef22(null);
|
|
3991
|
+
const cometElements = useRef22(null);
|
|
3992
|
+
const size = useRef22({ width: 0, height: 0 });
|
|
3993
|
+
const lastTime = useRef22(0);
|
|
4272
3994
|
const motion = useMotion();
|
|
4273
3995
|
const id = useSvgId();
|
|
4274
3996
|
const dots = useMemo3(() => {
|
|
@@ -4333,11 +4055,11 @@ function NeonWaves({
|
|
|
4333
4055
|
comet.style.opacity = Math.min(1, Math.max(0, (x + 20) / ((W3 + 40) * 0.35))).toFixed(3);
|
|
4334
4056
|
});
|
|
4335
4057
|
};
|
|
4336
|
-
const painter =
|
|
4058
|
+
const painter = useRef22(paint);
|
|
4337
4059
|
painter.current = paint;
|
|
4338
4060
|
useDecoration(ref, paint);
|
|
4339
|
-
return /* @__PURE__ */
|
|
4340
|
-
/* @__PURE__ */
|
|
4061
|
+
return /* @__PURE__ */ jsxs31("span", { ...mark("NeonWaves", p), ref: rootRef, "aria-hidden": "true", children: [
|
|
4062
|
+
/* @__PURE__ */ jsxs31(
|
|
4341
4063
|
"svg",
|
|
4342
4064
|
{
|
|
4343
4065
|
ref,
|
|
@@ -4345,14 +4067,14 @@ function NeonWaves({
|
|
|
4345
4067
|
viewBox: `0 0 ${W3} ${H3}`,
|
|
4346
4068
|
preserveAspectRatio: "none",
|
|
4347
4069
|
children: [
|
|
4348
|
-
/* @__PURE__ */
|
|
4349
|
-
/* @__PURE__ */
|
|
4350
|
-
/* @__PURE__ */
|
|
4351
|
-
/* @__PURE__ */
|
|
4352
|
-
/* @__PURE__ */
|
|
4353
|
-
/* @__PURE__ */
|
|
4070
|
+
/* @__PURE__ */ jsx39("defs", { children: /* @__PURE__ */ jsxs31("linearGradient", { id: `${id}-strand`, children: [
|
|
4071
|
+
/* @__PURE__ */ jsx39("stop", { stopColor: "var(--ad-primary-700)", stopOpacity: "0" }),
|
|
4072
|
+
/* @__PURE__ */ jsx39("stop", { offset: ".16", stopColor: "var(--ad-primary-600)", stopOpacity: ".35" }),
|
|
4073
|
+
/* @__PURE__ */ jsx39("stop", { offset: ".57", stopColor: "var(--ad-red)", stopOpacity: ".74" }),
|
|
4074
|
+
/* @__PURE__ */ jsx39("stop", { offset: ".78", stopColor: "var(--ad-pink)", stopOpacity: ".85" }),
|
|
4075
|
+
/* @__PURE__ */ jsx39("stop", { offset: "1", stopColor: "var(--ad-primary-600)", stopOpacity: ".44" })
|
|
4354
4076
|
] }) }),
|
|
4355
|
-
Array.from({ length: strands }, (_, i) => /* @__PURE__ */
|
|
4077
|
+
Array.from({ length: strands }, (_, i) => /* @__PURE__ */ jsx39(
|
|
4356
4078
|
"path",
|
|
4357
4079
|
{
|
|
4358
4080
|
className: "ad-neon-waves-strand",
|
|
@@ -4367,8 +4089,8 @@ function NeonWaves({
|
|
|
4367
4089
|
]
|
|
4368
4090
|
}
|
|
4369
4091
|
),
|
|
4370
|
-
comets > 0 && motion && /* @__PURE__ */
|
|
4371
|
-
dots.map((d, i) => /* @__PURE__ */
|
|
4092
|
+
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)) }),
|
|
4093
|
+
dots.map((d, i) => /* @__PURE__ */ jsx39(
|
|
4372
4094
|
"i",
|
|
4373
4095
|
{
|
|
4374
4096
|
className: "ad-neon-waves-star",
|
|
@@ -4386,7 +4108,7 @@ function NeonWaves({
|
|
|
4386
4108
|
}
|
|
4387
4109
|
|
|
4388
4110
|
// src/components/artwork/QuantumField/QuantumField.tsx
|
|
4389
|
-
import { useEffect as useEffect8, useRef as
|
|
4111
|
+
import { useEffect as useEffect8, useRef as useRef23, useState as useState8 } from "react";
|
|
4390
4112
|
|
|
4391
4113
|
// src/components/artwork/QuantumField/renderer.ts
|
|
4392
4114
|
var TAU = Math.PI * 2;
|
|
@@ -4729,7 +4451,7 @@ function createQuantumFieldRenderer(canvas, mode, count) {
|
|
|
4729
4451
|
}
|
|
4730
4452
|
|
|
4731
4453
|
// src/components/artwork/QuantumField/QuantumField.tsx
|
|
4732
|
-
import { jsx as
|
|
4454
|
+
import { jsx as jsx40, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
4733
4455
|
var quantumFieldModes = ["orbit", "vortex", "lattice", "wave", "bloom", "helix", "terrain"];
|
|
4734
4456
|
var PIXEL_BUDGET = { low: 32e4, high: 96e4 };
|
|
4735
4457
|
var POINT_BUDGET = { low: 1400, high: 2800 };
|
|
@@ -4820,18 +4542,18 @@ function QuantumField({
|
|
|
4820
4542
|
}) {
|
|
4821
4543
|
const theme = useThemePalette();
|
|
4822
4544
|
const motion = useMotion();
|
|
4823
|
-
const canvas =
|
|
4824
|
-
const root =
|
|
4825
|
-
const paint =
|
|
4826
|
-
const pointer =
|
|
4827
|
-
const camera =
|
|
4828
|
-
const drag =
|
|
4829
|
-
const visible =
|
|
4830
|
-
const [onscreen, setOnscreen] =
|
|
4831
|
-
const frame =
|
|
4832
|
-
const connectedAudio =
|
|
4833
|
-
const live =
|
|
4834
|
-
const updatePalette =
|
|
4545
|
+
const canvas = useRef23(null);
|
|
4546
|
+
const root = useRef23(null);
|
|
4547
|
+
const paint = useRef23(() => void 0);
|
|
4548
|
+
const pointer = useRef23({ x: 0, y: 0, active: false });
|
|
4549
|
+
const camera = useRef23({ zoom: 3.5, yaw: 0, pitch: 0.16 });
|
|
4550
|
+
const drag = useRef23(null);
|
|
4551
|
+
const visible = useRef23(false);
|
|
4552
|
+
const [onscreen, setOnscreen] = useState8(typeof IntersectionObserver === "undefined");
|
|
4553
|
+
const frame = useRef23({ last: 0, time: 0 });
|
|
4554
|
+
const connectedAudio = useRef23(null);
|
|
4555
|
+
const live = useRef23({ speed, audio, palette, theme });
|
|
4556
|
+
const updatePalette = useRef23(() => void 0);
|
|
4835
4557
|
const followsTheme = !palette?.[0] || !palette?.[1];
|
|
4836
4558
|
live.current = { speed, audio, palette, theme };
|
|
4837
4559
|
useEffect8(() => {
|
|
@@ -5029,7 +4751,7 @@ function QuantumField({
|
|
|
5029
4751
|
doc.addEventListener("wheel", wheel, { passive: false, capture: true });
|
|
5030
4752
|
return () => doc.removeEventListener("wheel", wheel, true);
|
|
5031
4753
|
}, [interactive, motion, paused]);
|
|
5032
|
-
return /* @__PURE__ */
|
|
4754
|
+
return /* @__PURE__ */ jsxs32(
|
|
5033
4755
|
"div",
|
|
5034
4756
|
{
|
|
5035
4757
|
...mark("QuantumField", p),
|
|
@@ -5065,7 +4787,7 @@ function QuantumField({
|
|
|
5065
4787
|
}
|
|
5066
4788
|
} : void 0,
|
|
5067
4789
|
children: [
|
|
5068
|
-
/* @__PURE__ */
|
|
4790
|
+
/* @__PURE__ */ jsx40("canvas", { ref: canvas, "aria-hidden": "true" }),
|
|
5069
4791
|
p.children
|
|
5070
4792
|
]
|
|
5071
4793
|
}
|
|
@@ -5073,13 +4795,13 @@ function QuantumField({
|
|
|
5073
4795
|
}
|
|
5074
4796
|
|
|
5075
4797
|
// src/components/artwork/QuantumField/QuantumFieldExperience.tsx
|
|
5076
|
-
import { forwardRef
|
|
4798
|
+
import { forwardRef, useCallback, useEffect as useEffect9, useImperativeHandle, useLayoutEffect as useLayoutEffect4, useRef as useRef24, useState as useState9 } from "react";
|
|
5077
4799
|
|
|
5078
4800
|
// src/components/artwork/QuantumField/reference.html?raw
|
|
5079
|
-
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 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;\nvec3 shapedPosition(vec3 seed) {\n vec2 plane = seed.xy * 2.0 - 1.0;\n return vec3(plane.x * uViewportAspect * uViewportHalfHeight * uFieldFill,\n plane.y * uViewportHalfHeight * uFieldFill,\n (seed.z - 0.5) * 12.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 t = uTime * (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) + (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 + uTime) * 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 - uTime * 0.8 + sin(aRandom.y * 7.0 + uTime * 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(0.985, 0.998, 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 * 2.1;\n gl_PointSize = clamp(max(size * uPixelRatio * (90.0 / -mvPosition.z) * layerMod, vGlint * 4.5 * uPixelRatio), 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 = clamp(-mvPosition.z / 120.0, 0.0, 1.0);\n\n float alpha = 1.0 - smoothstep(0.92, 1.05, max(abs(newPos.x) / (uViewportAspect * uViewportHalfHeight * uFieldFill * 1.05), abs(newPos.y) / (uViewportHalfHeight * uFieldFill * 1.05)));\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;\n}`;\n\n const fragmentShader = `\nvarying vec3 vColor;\nvarying float vAlpha, vEnergy, vDepth, vRimLight, vGlint;\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 = vec4(finalColor, finalAlpha);\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 const material = new THREE.ShaderMaterial({\n vertexShader, fragmentShader,\n uniforms: {\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 t = uTime * 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 = uTime * 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) + 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), 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 = 1.0 - smoothstep(0.92, 1.05, max(abs(newPos.x) / (uViewportAspect * uViewportHalfHeight * uFieldFill * 1.05), abs(newPos.y) / (uViewportHalfHeight * uFieldFill * 1.05)));\n vAlpha = edgeFade * (0.45 + uMid * 0.35 + uLowMid * 0.2) * sparkle;\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\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 = vec4(finalColor, finalAlpha);\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 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 return target.set(\n (particleSeeds.getX(index) * 2 - 1) * halfWidth,\n (particleSeeds.getY(index) * 2 - 1) * halfHeight,\n (particleSeeds.getZ(index) - 0.5) * 12\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(activeAudio, current, previous, idleElapsed) {\n return activeAudio || 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 connectionState = [\n STATE.field, STATE.connectionThreshold, geometry.drawRange.count,\n material.uniforms.uViewportAspect.value, material.uniforms.uViewportHalfHeight.value, STATE.fieldSize,\n STATE.qualityLevel, activeAudio\n ];\n connectionElapsed = Math.min(1 / 20, connectionElapsed + dt);\n idleConnectionElapsed = Math.min(2, idleConnectionElapsed + dt);\n if (connectionElapsed >= 1 / 20 && shouldRebuildConnections(activeAudio, 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 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";
|
|
4801
|
+
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";
|
|
5080
4802
|
|
|
5081
4803
|
// src/components/artwork/QuantumField/QuantumFieldExperience.tsx
|
|
5082
|
-
import { jsx as
|
|
4804
|
+
import { jsx as jsx41, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
5083
4805
|
var quantumBackgroundThreeUrl = "https://unpkg.com/three@0.182.0/";
|
|
5084
4806
|
var sceneDocument = (threeUrl) => {
|
|
5085
4807
|
const base = threeUrl.endsWith("/") ? threeUrl : `${threeUrl}/`;
|
|
@@ -5092,7 +4814,10 @@ var quantumBackgroundControls = [
|
|
|
5092
4814
|
{ key: "sensitivity", label: "\u0427\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C", value: 1.1, min: 0.1, max: 3, step: 0.01 },
|
|
5093
4815
|
{ key: "density", label: "\u041F\u043B\u043E\u0442\u043D\u043E\u0441\u0442\u044C", value: 0.043, min: 0.017, max: 0.14, step: 1e-3 },
|
|
5094
4816
|
{ key: "timeScale", label: "\u0421\u043A\u043E\u0440\u043E\u0441\u0442\u044C", value: 1, min: 0, max: 3, step: 0.01 },
|
|
5095
|
-
{ key: "fieldSize", label: "\u0420\u0430\u0437\u043C\u0435\u0440 \u043F\u043E\u043B\u044F", value: 0.78, min: 0.3, max: 1.2, step: 0.01 }
|
|
4817
|
+
{ key: "fieldSize", label: "\u0420\u0430\u0437\u043C\u0435\u0440 \u043F\u043E\u043B\u044F", value: 0.78, min: 0.3, max: 1.2, step: 0.01 },
|
|
4818
|
+
{ key: "particleDepth", label: "\u0413\u043B\u0443\u0431\u0438\u043D\u0430 \u0447\u0430\u0441\u0442\u0438\u0446", value: 0, min: 0, max: 1, step: 0.01 },
|
|
4819
|
+
{ key: "particleScale", label: "\u0420\u0430\u0437\u043C\u0435\u0440 \u0447\u0430\u0441\u0442\u0438\u0446", value: 1, min: 0.5, max: 3, step: 0.01 },
|
|
4820
|
+
{ key: "particleSharpness", label: "\u0427\u0451\u0442\u043A\u043E\u0441\u0442\u044C \u0447\u0430\u0441\u0442\u0438\u0446", value: 0, min: 0, max: 1, step: 0.01 }
|
|
5096
4821
|
] },
|
|
5097
4822
|
{ id: "environment", label: "\u0421\u0440\u0435\u0434\u0430", controls: [
|
|
5098
4823
|
{ key: "nebulaEnabled", label: "\u0422\u0443\u043C\u0430\u043D\u043D\u043E\u0441\u0442\u044C", value: false },
|
|
@@ -5149,7 +4874,7 @@ var quantumBackgroundDefaults = Object.freeze(Object.fromEntries(
|
|
|
5149
4874
|
var settingKeys = Object.keys(quantumBackgroundDefaults);
|
|
5150
4875
|
var quantumBackgroundFullscreenFieldSize = 1.1;
|
|
5151
4876
|
var quantumBackgroundMaxParticles = 85e3;
|
|
5152
|
-
var QuantumBackground = /* @__PURE__ */
|
|
4877
|
+
var QuantumBackground = /* @__PURE__ */ forwardRef(function QuantumBackground2(props, ref) {
|
|
5153
4878
|
const {
|
|
5154
4879
|
className = "",
|
|
5155
4880
|
particleCount,
|
|
@@ -5172,13 +4897,13 @@ var QuantumBackground = /* @__PURE__ */ forwardRef3(function QuantumBackground2(
|
|
|
5172
4897
|
}
|
|
5173
4898
|
if (fullscreen) settings.fieldSize ??= quantumBackgroundFullscreenFieldSize;
|
|
5174
4899
|
for (const key of settingKeys) settings[key] ??= quantumBackgroundDefaults[key];
|
|
5175
|
-
const frame =
|
|
5176
|
-
const root =
|
|
4900
|
+
const frame = useRef24(null);
|
|
4901
|
+
const root = useRef24(null);
|
|
5177
4902
|
const theme = useThemePalette();
|
|
5178
|
-
const [mounted, setMounted] =
|
|
5179
|
-
const [ready, setReady] =
|
|
5180
|
-
const sent =
|
|
5181
|
-
const callbacks =
|
|
4903
|
+
const [mounted, setMounted] = useState9(typeof IntersectionObserver === "undefined");
|
|
4904
|
+
const [ready, setReady] = useState9(false);
|
|
4905
|
+
const sent = useRef24({});
|
|
4906
|
+
const callbacks = useRef24({ onTransportChange, onWaveformChange, onFullscreenChange });
|
|
5182
4907
|
callbacks.current = { onTransportChange, onWaveformChange, onFullscreenChange };
|
|
5183
4908
|
const normalizedBudget = typeof particleBudget === "number" && Number.isFinite(particleBudget) ? Math.max(0, Math.floor(particleBudget)) : void 0;
|
|
5184
4909
|
const normalizedCount = typeof particleCount === "number" && Number.isFinite(particleCount) ? Math.min(quantumBackgroundMaxParticles, Math.max(0, Math.floor(particleCount))) : void 0;
|
|
@@ -5231,7 +4956,7 @@ var QuantumBackground = /* @__PURE__ */ forwardRef3(function QuantumBackground2(
|
|
|
5231
4956
|
useEffect9(() => {
|
|
5232
4957
|
if (ready) send("spectrum", { bands: spectrum ? [...spectrum] : null, range: [low, high] });
|
|
5233
4958
|
}, [ready, spectrum, low, high]);
|
|
5234
|
-
|
|
4959
|
+
useLayoutEffect4(() => {
|
|
5235
4960
|
const node = root.current;
|
|
5236
4961
|
if (!fullscreen || !node?.showPopover) return;
|
|
5237
4962
|
node.showPopover();
|
|
@@ -5259,7 +4984,7 @@ var QuantumBackground = /* @__PURE__ */ forwardRef3(function QuantumBackground2(
|
|
|
5259
4984
|
if (!ready) return;
|
|
5260
4985
|
send("palette", [theme.primary, theme.secondary]);
|
|
5261
4986
|
}, [ready, theme.primary, theme.secondary]);
|
|
5262
|
-
return /* @__PURE__ */
|
|
4987
|
+
return /* @__PURE__ */ jsxs33(
|
|
5263
4988
|
"div",
|
|
5264
4989
|
{
|
|
5265
4990
|
"data-ad-component": "QuantumBackground",
|
|
@@ -5269,7 +4994,7 @@ var QuantumBackground = /* @__PURE__ */ forwardRef3(function QuantumBackground2(
|
|
|
5269
4994
|
popover: fullscreen ? "manual" : void 0,
|
|
5270
4995
|
...attributes,
|
|
5271
4996
|
children: [
|
|
5272
|
-
mounted && /* @__PURE__ */
|
|
4997
|
+
mounted && /* @__PURE__ */ jsx41(
|
|
5273
4998
|
"iframe",
|
|
5274
4999
|
{
|
|
5275
5000
|
ref: frame,
|
|
@@ -5280,7 +5005,7 @@ var QuantumBackground = /* @__PURE__ */ forwardRef3(function QuantumBackground2(
|
|
|
5280
5005
|
"data-particle-count": normalizedCount
|
|
5281
5006
|
}
|
|
5282
5007
|
),
|
|
5283
|
-
children != null && /* @__PURE__ */
|
|
5008
|
+
children != null && /* @__PURE__ */ jsx41("div", { className: "ad-qb-content", children })
|
|
5284
5009
|
]
|
|
5285
5010
|
}
|
|
5286
5011
|
);
|
|
@@ -5371,8 +5096,8 @@ function createQuantumFieldAudio() {
|
|
|
5371
5096
|
}
|
|
5372
5097
|
|
|
5373
5098
|
// src/components/artwork/Spectrum/Spectrum.tsx
|
|
5374
|
-
import { useRef as
|
|
5375
|
-
import { jsx as
|
|
5099
|
+
import { useRef as useRef25 } from "react";
|
|
5100
|
+
import { jsx as jsx42, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
5376
5101
|
var COLUMNS = 27;
|
|
5377
5102
|
var ROWS = 29;
|
|
5378
5103
|
var BARS = 23;
|
|
@@ -5384,11 +5109,11 @@ var barHeight = (i, time) => {
|
|
|
5384
5109
|
return 9 + 100 * envelope * (0.53 + 0.47 * rhythm) + 15 * Math.sin(i * 0.67 + time * 0.58) ** 2;
|
|
5385
5110
|
};
|
|
5386
5111
|
function Spectrum({ variant = "segmented", ...p }) {
|
|
5387
|
-
return variant === "segmented" ? /* @__PURE__ */
|
|
5112
|
+
return variant === "segmented" ? /* @__PURE__ */ jsx42(Segmented, { ...p }) : /* @__PURE__ */ jsx42(Bars, { ...p });
|
|
5388
5113
|
}
|
|
5389
5114
|
function Segmented(p) {
|
|
5390
|
-
const ref =
|
|
5391
|
-
const columns2 =
|
|
5115
|
+
const ref = useRef25(null);
|
|
5116
|
+
const columns2 = useRef25(null);
|
|
5392
5117
|
usePauseOffscreen(ref);
|
|
5393
5118
|
useDecoration(ref, (time) => {
|
|
5394
5119
|
if (!ref.current) return;
|
|
@@ -5399,19 +5124,19 @@ function Segmented(p) {
|
|
|
5399
5124
|
column.firstElementChild?.style.setProperty("transform", `translateY(${(-hidden).toFixed(2)}%)`);
|
|
5400
5125
|
});
|
|
5401
5126
|
});
|
|
5402
|
-
return /* @__PURE__ */
|
|
5127
|
+
return /* @__PURE__ */ jsx42("div", { ...mark("Spectrum", p), ref, "data-variant": "segmented", "aria-hidden": "true", children: Array.from({ length: COLUMNS }, (_, i) => /* @__PURE__ */ jsx42(
|
|
5403
5128
|
"span",
|
|
5404
5129
|
{
|
|
5405
5130
|
className: "ad-spectrum-column",
|
|
5406
5131
|
style: { left: `${(i * 12 + 3) / 325 * 100}%`, width: `${8 / 325 * 100}%` },
|
|
5407
|
-
children: /* @__PURE__ */
|
|
5132
|
+
children: /* @__PURE__ */ jsx42("svg", { viewBox: "0 0 8 232", preserveAspectRatio: "none", children: Array.from({ length: ROWS }, (__, row) => /* @__PURE__ */ jsx42("rect", { y: 224 - row * 7, width: "8", height: "5", rx: ".35", opacity: 0.17 + row / 35 }, row)) })
|
|
5408
5133
|
},
|
|
5409
5134
|
i
|
|
5410
5135
|
)) });
|
|
5411
5136
|
}
|
|
5412
5137
|
function Bars(p) {
|
|
5413
|
-
const ref =
|
|
5414
|
-
const layers =
|
|
5138
|
+
const ref = useRef25(null);
|
|
5139
|
+
const layers = useRef25(null);
|
|
5415
5140
|
const id = useSvgId();
|
|
5416
5141
|
usePauseOffscreen(ref);
|
|
5417
5142
|
useDecoration(ref, (time) => {
|
|
@@ -5429,7 +5154,7 @@ function Bars(p) {
|
|
|
5429
5154
|
});
|
|
5430
5155
|
});
|
|
5431
5156
|
});
|
|
5432
|
-
return /* @__PURE__ */
|
|
5157
|
+
return /* @__PURE__ */ jsxs34(
|
|
5433
5158
|
"svg",
|
|
5434
5159
|
{
|
|
5435
5160
|
...mark("Spectrum", p),
|
|
@@ -5439,23 +5164,23 @@ function Bars(p) {
|
|
|
5439
5164
|
preserveAspectRatio: "none",
|
|
5440
5165
|
"aria-hidden": "true",
|
|
5441
5166
|
children: [
|
|
5442
|
-
/* @__PURE__ */
|
|
5443
|
-
/* @__PURE__ */
|
|
5444
|
-
/* @__PURE__ */
|
|
5445
|
-
/* @__PURE__ */
|
|
5446
|
-
/* @__PURE__ */
|
|
5167
|
+
/* @__PURE__ */ jsxs34("defs", { children: [
|
|
5168
|
+
/* @__PURE__ */ jsxs34("linearGradient", { id: `${id}-bar`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
5169
|
+
/* @__PURE__ */ jsx42("stop", { stopColor: "var(--ad-secondary-200)" }),
|
|
5170
|
+
/* @__PURE__ */ jsx42("stop", { offset: ".24", stopColor: "var(--ad-red)" }),
|
|
5171
|
+
/* @__PURE__ */ jsx42("stop", { offset: "1", stopColor: "var(--ad-primary-600)", stopOpacity: "0" })
|
|
5447
5172
|
] }),
|
|
5448
|
-
/* @__PURE__ */
|
|
5449
|
-
/* @__PURE__ */
|
|
5450
|
-
/* @__PURE__ */
|
|
5451
|
-
/* @__PURE__ */
|
|
5173
|
+
/* @__PURE__ */ jsxs34("linearGradient", { id: `${id}-halo`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
5174
|
+
/* @__PURE__ */ jsx42("stop", { stopColor: "var(--ad-red)", stopOpacity: ".6" }),
|
|
5175
|
+
/* @__PURE__ */ jsx42("stop", { offset: ".24", stopColor: "var(--ad-red)", stopOpacity: ".6" }),
|
|
5176
|
+
/* @__PURE__ */ jsx42("stop", { offset: "1", stopColor: "var(--ad-red)", stopOpacity: "0" })
|
|
5452
5177
|
] })
|
|
5453
5178
|
] }),
|
|
5454
5179
|
Array.from({ length: BARS }, (_, i) => {
|
|
5455
5180
|
const x = 4 + i * 6.75;
|
|
5456
5181
|
const opacity = 0.55 + i / 55;
|
|
5457
|
-
return /* @__PURE__ */
|
|
5458
|
-
HALO.map(([grow, halo]) => /* @__PURE__ */
|
|
5182
|
+
return /* @__PURE__ */ jsxs34("g", { "data-bar": true, children: [
|
|
5183
|
+
HALO.map(([grow, halo]) => /* @__PURE__ */ jsx42(
|
|
5459
5184
|
"rect",
|
|
5460
5185
|
{
|
|
5461
5186
|
"data-grow": grow,
|
|
@@ -5469,7 +5194,7 @@ function Bars(p) {
|
|
|
5469
5194
|
},
|
|
5470
5195
|
grow
|
|
5471
5196
|
)),
|
|
5472
|
-
/* @__PURE__ */
|
|
5197
|
+
/* @__PURE__ */ jsx42("rect", { x, y: "40", width: "2.8", height: "100", rx: "1.3", fill: `url(#${id}-bar)`, opacity })
|
|
5473
5198
|
] }, i);
|
|
5474
5199
|
})
|
|
5475
5200
|
]
|
|
@@ -5478,7 +5203,7 @@ function Bars(p) {
|
|
|
5478
5203
|
}
|
|
5479
5204
|
|
|
5480
5205
|
// src/components/artwork/layers.tsx
|
|
5481
|
-
import { jsx as
|
|
5206
|
+
import { jsx as jsx43 } from "react/jsx-runtime";
|
|
5482
5207
|
function ArtLayer({
|
|
5483
5208
|
viewBox,
|
|
5484
5209
|
className,
|
|
@@ -5487,7 +5212,7 @@ function ArtLayer({
|
|
|
5487
5212
|
children
|
|
5488
5213
|
}) {
|
|
5489
5214
|
const [, , width, height] = viewBox.split(" ").map(Number);
|
|
5490
|
-
return /* @__PURE__ */
|
|
5215
|
+
return /* @__PURE__ */ jsx43(
|
|
5491
5216
|
"svg",
|
|
5492
5217
|
{
|
|
5493
5218
|
className: className ? `ad-art-layer ${className}` : "ad-art-layer",
|
|
@@ -5501,7 +5226,7 @@ function ArtLayer({
|
|
|
5501
5226
|
}
|
|
5502
5227
|
|
|
5503
5228
|
// src/components/artwork/DatabaseArt/DatabaseArt.tsx
|
|
5504
|
-
import { jsx as
|
|
5229
|
+
import { jsx as jsx44, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
5505
5230
|
var SPARKS = [
|
|
5506
5231
|
[42, 84, 1, -1],
|
|
5507
5232
|
[44, 111, 1, -2.7],
|
|
@@ -5515,7 +5240,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5515
5240
|
const ref = (name) => `url(#${id}-${name})`;
|
|
5516
5241
|
const rings = "M39 83C39 106 134 106 134 83M39 108C39 132 134 132 134 108M39 133C39 155 134 155 134 133";
|
|
5517
5242
|
const box = "0 0 170 179";
|
|
5518
|
-
return /* @__PURE__ */
|
|
5243
|
+
return /* @__PURE__ */ jsxs35(
|
|
5519
5244
|
"div",
|
|
5520
5245
|
{
|
|
5521
5246
|
...mark("DatabaseArt", p),
|
|
@@ -5523,42 +5248,42 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5523
5248
|
"aria-label": label,
|
|
5524
5249
|
"aria-hidden": !label,
|
|
5525
5250
|
children: [
|
|
5526
|
-
/* @__PURE__ */
|
|
5527
|
-
/* @__PURE__ */
|
|
5528
|
-
/* @__PURE__ */
|
|
5529
|
-
/* @__PURE__ */
|
|
5530
|
-
/* @__PURE__ */
|
|
5531
|
-
/* @__PURE__ */
|
|
5532
|
-
/* @__PURE__ */
|
|
5533
|
-
/* @__PURE__ */
|
|
5251
|
+
/* @__PURE__ */ jsx44(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs35("defs", { children: [
|
|
5252
|
+
/* @__PURE__ */ jsx44("filter", { id: `${id}-bloom`, x: "-50%", y: "-50%", width: "200%", height: "200%", children: /* @__PURE__ */ jsx44("feGaussianBlur", { stdDeviation: "3" }) }),
|
|
5253
|
+
/* @__PURE__ */ jsxs35("linearGradient", { id: `${id}-body`, children: [
|
|
5254
|
+
/* @__PURE__ */ jsx44("stop", { stopColor: "var(--ad-primary-600)" }),
|
|
5255
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".12", stopColor: "var(--ad-primary-800)" }),
|
|
5256
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".3", stopColor: "var(--ad-primary-900)" }),
|
|
5257
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".72", stopColor: "var(--ad-neutral-950)" }),
|
|
5258
|
+
/* @__PURE__ */ jsx44("stop", { offset: "1", stopColor: "var(--ad-primary-800)" })
|
|
5534
5259
|
] }),
|
|
5535
|
-
/* @__PURE__ */
|
|
5536
|
-
/* @__PURE__ */
|
|
5537
|
-
/* @__PURE__ */
|
|
5538
|
-
/* @__PURE__ */
|
|
5539
|
-
/* @__PURE__ */
|
|
5260
|
+
/* @__PURE__ */ jsxs35("radialGradient", { id: `${id}-top`, cx: ".3", cy: ".27", r: ".9", children: [
|
|
5261
|
+
/* @__PURE__ */ jsx44("stop", { stopColor: "var(--ad-primary-600)" }),
|
|
5262
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".23", stopColor: "var(--ad-primary-800)" }),
|
|
5263
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".66", stopColor: "var(--ad-neutral-900)" }),
|
|
5264
|
+
/* @__PURE__ */ jsx44("stop", { offset: "1", stopColor: "var(--ad-primary-900)" })
|
|
5540
5265
|
] }),
|
|
5541
|
-
/* @__PURE__ */
|
|
5542
|
-
/* @__PURE__ */
|
|
5543
|
-
/* @__PURE__ */
|
|
5544
|
-
/* @__PURE__ */
|
|
5545
|
-
/* @__PURE__ */
|
|
5546
|
-
/* @__PURE__ */
|
|
5266
|
+
/* @__PURE__ */ jsxs35("linearGradient", { id: `${id}-edge`, children: [
|
|
5267
|
+
/* @__PURE__ */ jsx44("stop", { stopColor: "var(--ad-neutral-200)" }),
|
|
5268
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".16", stopColor: "var(--ad-secondary)" }),
|
|
5269
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".47", stopColor: "var(--ad-primary-700)" }),
|
|
5270
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".8", stopColor: "var(--ad-primary)" }),
|
|
5271
|
+
/* @__PURE__ */ jsx44("stop", { offset: "1", stopColor: "var(--ad-secondary-200)" })
|
|
5547
5272
|
] }),
|
|
5548
|
-
/* @__PURE__ */
|
|
5549
|
-
/* @__PURE__ */
|
|
5550
|
-
/* @__PURE__ */
|
|
5551
|
-
/* @__PURE__ */
|
|
5273
|
+
/* @__PURE__ */ jsxs35("radialGradient", { id: `${id}-aura`, children: [
|
|
5274
|
+
/* @__PURE__ */ jsx44("stop", { stopColor: "var(--ad-primary)", stopOpacity: ".3" }),
|
|
5275
|
+
/* @__PURE__ */ jsx44("stop", { offset: ".5", stopColor: "var(--ad-primary)", stopOpacity: ".12" }),
|
|
5276
|
+
/* @__PURE__ */ jsx44("stop", { offset: "1", stopColor: "var(--ad-primary)", stopOpacity: "0" })
|
|
5552
5277
|
] }),
|
|
5553
|
-
/* @__PURE__ */
|
|
5554
|
-
/* @__PURE__ */
|
|
5555
|
-
/* @__PURE__ */
|
|
5556
|
-
/* @__PURE__ */
|
|
5278
|
+
/* @__PURE__ */ jsxs35("g", { id: `${id}-spark`, children: [
|
|
5279
|
+
/* @__PURE__ */ jsx44("path", { d: "M-7 0H7M0-8V8", stroke: "var(--ad-secondary-200)", strokeWidth: ".7" }),
|
|
5280
|
+
/* @__PURE__ */ jsx44("circle", { r: "3.4", fill: "var(--ad-primary)", filter: ref("bloom") }),
|
|
5281
|
+
/* @__PURE__ */ jsx44("circle", { r: "1.4", fill: "var(--ad-neutral-200)" })
|
|
5557
5282
|
] })
|
|
5558
5283
|
] }) }),
|
|
5559
|
-
/* @__PURE__ */
|
|
5560
|
-
/* @__PURE__ */
|
|
5561
|
-
/* @__PURE__ */
|
|
5284
|
+
/* @__PURE__ */ jsx44(ArtLayer, { viewBox: box, className: "ad-art-aura-layer", origin: [85, 120], children: /* @__PURE__ */ jsx44("ellipse", { cx: "85", cy: "120", rx: "83", ry: "71", fill: ref("aura") }) }),
|
|
5285
|
+
/* @__PURE__ */ jsx44(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs35("g", { transform: "translate(0 2)", children: [
|
|
5286
|
+
/* @__PURE__ */ jsx44(
|
|
5562
5287
|
"path",
|
|
5563
5288
|
{
|
|
5564
5289
|
d: "M39 57V133C39 156 134 156 134 133V57Z",
|
|
@@ -5567,7 +5292,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5567
5292
|
strokeWidth: ".75"
|
|
5568
5293
|
}
|
|
5569
5294
|
),
|
|
5570
|
-
/* @__PURE__ */
|
|
5295
|
+
/* @__PURE__ */ jsx44(
|
|
5571
5296
|
"path",
|
|
5572
5297
|
{
|
|
5573
5298
|
d: rings,
|
|
@@ -5577,8 +5302,8 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5577
5302
|
filter: ref("bloom")
|
|
5578
5303
|
}
|
|
5579
5304
|
),
|
|
5580
|
-
/* @__PURE__ */
|
|
5581
|
-
/* @__PURE__ */
|
|
5305
|
+
/* @__PURE__ */ jsx44("path", { d: rings, stroke: ref("edge"), strokeWidth: "1.4" }),
|
|
5306
|
+
/* @__PURE__ */ jsx44(
|
|
5582
5307
|
"ellipse",
|
|
5583
5308
|
{
|
|
5584
5309
|
cx: "86.5",
|
|
@@ -5590,7 +5315,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5590
5315
|
strokeWidth: "1.1"
|
|
5591
5316
|
}
|
|
5592
5317
|
),
|
|
5593
|
-
/* @__PURE__ */
|
|
5318
|
+
/* @__PURE__ */ jsx44(
|
|
5594
5319
|
"ellipse",
|
|
5595
5320
|
{
|
|
5596
5321
|
cx: "86.5",
|
|
@@ -5603,7 +5328,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5603
5328
|
filter: ref("bloom")
|
|
5604
5329
|
}
|
|
5605
5330
|
),
|
|
5606
|
-
/* @__PURE__ */
|
|
5331
|
+
/* @__PURE__ */ jsx44(
|
|
5607
5332
|
"ellipse",
|
|
5608
5333
|
{
|
|
5609
5334
|
cx: "86.5",
|
|
@@ -5615,7 +5340,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5615
5340
|
opacity: ".8"
|
|
5616
5341
|
}
|
|
5617
5342
|
),
|
|
5618
|
-
[81, 106, 132].map((cy) => /* @__PURE__ */
|
|
5343
|
+
[81, 106, 132].map((cy) => /* @__PURE__ */ jsx44(
|
|
5619
5344
|
"ellipse",
|
|
5620
5345
|
{
|
|
5621
5346
|
cx: "86.5",
|
|
@@ -5629,7 +5354,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5629
5354
|
cy
|
|
5630
5355
|
))
|
|
5631
5356
|
] }) }),
|
|
5632
|
-
/* @__PURE__ */
|
|
5357
|
+
/* @__PURE__ */ jsx44(ArtLayer, { viewBox: box, className: "ad-art-orbits", children: /* @__PURE__ */ jsx44("g", { transform: "translate(0 2)", stroke: "var(--ad-neutral-200)", strokeWidth: "1.45", children: [57, 106, 132].map((cy, i) => /* @__PURE__ */ jsx44(
|
|
5633
5358
|
"ellipse",
|
|
5634
5359
|
{
|
|
5635
5360
|
className: "ad-art-orbit",
|
|
@@ -5642,8 +5367,8 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5642
5367
|
},
|
|
5643
5368
|
cy
|
|
5644
5369
|
)) }) }),
|
|
5645
|
-
/* @__PURE__ */
|
|
5646
|
-
/* @__PURE__ */
|
|
5370
|
+
/* @__PURE__ */ jsx44(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs35("g", { transform: "translate(0 2)", children: [
|
|
5371
|
+
/* @__PURE__ */ jsx44(
|
|
5647
5372
|
"ellipse",
|
|
5648
5373
|
{
|
|
5649
5374
|
cx: "86.5",
|
|
@@ -5654,16 +5379,16 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5654
5379
|
filter: ref("bloom")
|
|
5655
5380
|
}
|
|
5656
5381
|
),
|
|
5657
|
-
/* @__PURE__ */
|
|
5382
|
+
/* @__PURE__ */ jsx44("ellipse", { cx: "86.5", cy: "53.5", rx: "3.4", ry: ".8", fill: "var(--ad-secondary-200)" })
|
|
5658
5383
|
] }) }),
|
|
5659
|
-
SPARKS.map(([x, y, scale, delay]) => /* @__PURE__ */
|
|
5384
|
+
SPARKS.map(([x, y, scale, delay]) => /* @__PURE__ */ jsx44(
|
|
5660
5385
|
ArtLayer,
|
|
5661
5386
|
{
|
|
5662
5387
|
viewBox: box,
|
|
5663
5388
|
className: "ad-art-spark-layer",
|
|
5664
5389
|
origin: [x, y],
|
|
5665
5390
|
style: { "--ad-delay": `${delay}s` },
|
|
5666
|
-
children: /* @__PURE__ */
|
|
5391
|
+
children: /* @__PURE__ */ jsx44("use", { href: `#${id}-spark`, transform: `translate(${x} ${y}) scale(${scale})` })
|
|
5667
5392
|
},
|
|
5668
5393
|
`${x}-${y}`
|
|
5669
5394
|
))
|
|
@@ -5673,7 +5398,7 @@ function DatabaseArt({ label, ...p }) {
|
|
|
5673
5398
|
}
|
|
5674
5399
|
|
|
5675
5400
|
// src/components/artwork/ServerArt/ServerArt.tsx
|
|
5676
|
-
import { jsx as
|
|
5401
|
+
import { jsx as jsx45, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
5677
5402
|
var rackRows = Array.from({ length: 15 }, (_, row) => row);
|
|
5678
5403
|
var rackLines = rackRows.map((row) => `M33 ${50 + row * 3.35} 90 ${38.8 + row * 3.35}`).join(" ");
|
|
5679
5404
|
var rackHatches = rackRows.flatMap((row) => Array.from({ length: 9 }, (_, col) => `M${34 + col * 6.15} ${49.8 + row * 3.35 - col * 1.205}l2.6-.51`)).join(" ");
|
|
@@ -5683,7 +5408,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5683
5408
|
const box = "0 0 180 170";
|
|
5684
5409
|
const lift = upload ? "translate(0 15)" : void 0;
|
|
5685
5410
|
const dy = upload ? 15 : 0;
|
|
5686
|
-
return /* @__PURE__ */
|
|
5411
|
+
return /* @__PURE__ */ jsxs36(
|
|
5687
5412
|
"div",
|
|
5688
5413
|
{
|
|
5689
5414
|
...mark("ServerArt", p),
|
|
@@ -5691,40 +5416,40 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5691
5416
|
"aria-label": label,
|
|
5692
5417
|
"aria-hidden": !label,
|
|
5693
5418
|
children: [
|
|
5694
|
-
/* @__PURE__ */
|
|
5695
|
-
/* @__PURE__ */
|
|
5696
|
-
/* @__PURE__ */
|
|
5697
|
-
/* @__PURE__ */
|
|
5698
|
-
/* @__PURE__ */
|
|
5699
|
-
/* @__PURE__ */
|
|
5419
|
+
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs36("defs", { children: [
|
|
5420
|
+
/* @__PURE__ */ jsxs36("linearGradient", { id: `${id}-front`, x1: "0", y1: "0", x2: "1", y2: "1", children: [
|
|
5421
|
+
/* @__PURE__ */ jsx45("stop", { stopColor: "var(--ad-primary-700)" }),
|
|
5422
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".22", stopColor: "var(--ad-primary-900)" }),
|
|
5423
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".72", stopColor: "var(--ad-neutral-900)" }),
|
|
5424
|
+
/* @__PURE__ */ jsx45("stop", { offset: "1", stopColor: "var(--ad-primary-800)" })
|
|
5700
5425
|
] }),
|
|
5701
|
-
/* @__PURE__ */
|
|
5702
|
-
/* @__PURE__ */
|
|
5703
|
-
/* @__PURE__ */
|
|
5704
|
-
/* @__PURE__ */
|
|
5426
|
+
/* @__PURE__ */ jsxs36("linearGradient", { id: `${id}-side`, x1: "0", y1: "0", x2: ".9", y2: "1", children: [
|
|
5427
|
+
/* @__PURE__ */ jsx45("stop", { stopColor: "var(--ad-primary-800)" }),
|
|
5428
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".27", stopColor: "var(--ad-primary-900)" }),
|
|
5429
|
+
/* @__PURE__ */ jsx45("stop", { offset: "1", stopColor: "var(--ad-neutral-950)" })
|
|
5705
5430
|
] }),
|
|
5706
|
-
/* @__PURE__ */
|
|
5707
|
-
/* @__PURE__ */
|
|
5708
|
-
/* @__PURE__ */
|
|
5709
|
-
/* @__PURE__ */
|
|
5710
|
-
/* @__PURE__ */
|
|
5431
|
+
/* @__PURE__ */ jsxs36("linearGradient", { id: `${id}-top`, x1: "0", y1: "0", x2: ".7", y2: "1", children: [
|
|
5432
|
+
/* @__PURE__ */ jsx45("stop", { stopColor: "var(--ad-secondary-200)" }),
|
|
5433
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".23", stopColor: "var(--ad-primary-600)" }),
|
|
5434
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".57", stopColor: "var(--ad-primary-800)" }),
|
|
5435
|
+
/* @__PURE__ */ jsx45("stop", { offset: "1", stopColor: "var(--ad-neutral-900)" })
|
|
5711
5436
|
] }),
|
|
5712
|
-
/* @__PURE__ */
|
|
5713
|
-
/* @__PURE__ */
|
|
5714
|
-
/* @__PURE__ */
|
|
5715
|
-
/* @__PURE__ */
|
|
5716
|
-
/* @__PURE__ */
|
|
5717
|
-
/* @__PURE__ */
|
|
5437
|
+
/* @__PURE__ */ jsxs36("linearGradient", { id: `${id}-edge`, x1: "0", y1: "0", x2: "1", y2: "1", children: [
|
|
5438
|
+
/* @__PURE__ */ jsx45("stop", { stopColor: "var(--ad-secondary-200)" }),
|
|
5439
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".29", stopColor: "var(--ad-primary)" }),
|
|
5440
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".52", stopColor: "var(--ad-primary-700)" }),
|
|
5441
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".8", stopColor: "var(--ad-primary)" }),
|
|
5442
|
+
/* @__PURE__ */ jsx45("stop", { offset: "1", stopColor: "var(--ad-primary-700)" })
|
|
5718
5443
|
] }),
|
|
5719
|
-
/* @__PURE__ */
|
|
5720
|
-
/* @__PURE__ */
|
|
5721
|
-
/* @__PURE__ */
|
|
5722
|
-
/* @__PURE__ */
|
|
5444
|
+
/* @__PURE__ */ jsxs36("radialGradient", { id: `${id}-aura`, children: [
|
|
5445
|
+
/* @__PURE__ */ jsx45("stop", { stopColor: "var(--ad-primary)", stopOpacity: ".28" }),
|
|
5446
|
+
/* @__PURE__ */ jsx45("stop", { offset: ".6", stopColor: "var(--ad-primary)", stopOpacity: ".06" }),
|
|
5447
|
+
/* @__PURE__ */ jsx45("stop", { offset: "1", stopColor: "var(--ad-primary)", stopOpacity: "0" })
|
|
5723
5448
|
] }),
|
|
5724
|
-
/* @__PURE__ */
|
|
5449
|
+
/* @__PURE__ */ jsx45("filter", { id: `${id}-bloom`, x: "-35%", y: "-35%", width: "170%", height: "170%", children: /* @__PURE__ */ jsx45("feGaussianBlur", { stdDeviation: "2.4" }) })
|
|
5725
5450
|
] }) }),
|
|
5726
|
-
upload && /* @__PURE__ */
|
|
5727
|
-
/* @__PURE__ */
|
|
5451
|
+
upload && /* @__PURE__ */ jsxs36(ArtLayer, { viewBox: box, className: "ad-server-cloud", children: [
|
|
5452
|
+
/* @__PURE__ */ jsx45(
|
|
5728
5453
|
"path",
|
|
5729
5454
|
{
|
|
5730
5455
|
d: "M57 40C37 42 40 16 58 20 63-5 96-4 102 18 120 13 132 32 117 42Z",
|
|
@@ -5734,11 +5459,11 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5734
5459
|
strokeWidth: ".7"
|
|
5735
5460
|
}
|
|
5736
5461
|
),
|
|
5737
|
-
/* @__PURE__ */
|
|
5462
|
+
/* @__PURE__ */ jsx45("path", { d: "M80 37V18m-7 7 7-7 7 7", stroke: "var(--ad-secondary)", strokeWidth: "1.5" })
|
|
5738
5463
|
] }),
|
|
5739
|
-
/* @__PURE__ */
|
|
5740
|
-
/* @__PURE__ */
|
|
5741
|
-
/* @__PURE__ */
|
|
5464
|
+
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, className: "ad-art-aura-layer", origin: [89, 124 + dy], children: /* @__PURE__ */ jsx45("ellipse", { cx: "89", cy: 124 + dy, rx: "76", ry: "22", fill: url("aura") }) }),
|
|
5465
|
+
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs36("g", { transform: lift, children: [
|
|
5466
|
+
/* @__PURE__ */ jsx45(
|
|
5742
5467
|
"path",
|
|
5743
5468
|
{
|
|
5744
5469
|
d: "M26 43 98 29 150 45 75 61Z",
|
|
@@ -5747,7 +5472,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5747
5472
|
strokeWidth: ".75"
|
|
5748
5473
|
}
|
|
5749
5474
|
),
|
|
5750
|
-
/* @__PURE__ */
|
|
5475
|
+
/* @__PURE__ */ jsx45(
|
|
5751
5476
|
"path",
|
|
5752
5477
|
{
|
|
5753
5478
|
d: "M98 29 150 45 150 119 98 107Z",
|
|
@@ -5756,7 +5481,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5756
5481
|
strokeWidth: ".7"
|
|
5757
5482
|
}
|
|
5758
5483
|
),
|
|
5759
|
-
/* @__PURE__ */
|
|
5484
|
+
/* @__PURE__ */ jsx45(
|
|
5760
5485
|
"path",
|
|
5761
5486
|
{
|
|
5762
5487
|
d: "M26 43 98 29 98 107 26 121Z",
|
|
@@ -5765,7 +5490,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5765
5490
|
strokeWidth: "1"
|
|
5766
5491
|
}
|
|
5767
5492
|
),
|
|
5768
|
-
/* @__PURE__ */
|
|
5493
|
+
/* @__PURE__ */ jsx45(
|
|
5769
5494
|
"path",
|
|
5770
5495
|
{
|
|
5771
5496
|
d: "M29 46 94 33 94 105 29 117Z",
|
|
@@ -5775,9 +5500,9 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5775
5500
|
strokeWidth: ".65"
|
|
5776
5501
|
}
|
|
5777
5502
|
),
|
|
5778
|
-
/* @__PURE__ */
|
|
5779
|
-
/* @__PURE__ */
|
|
5780
|
-
/* @__PURE__ */
|
|
5503
|
+
/* @__PURE__ */ jsx45("path", { d: rackLines, stroke: "var(--ad-primary-700)", strokeOpacity: ".58" }),
|
|
5504
|
+
/* @__PURE__ */ jsx45("path", { d: rackHatches, stroke: "var(--ad-neutral-950)", strokeWidth: "1.65" }),
|
|
5505
|
+
/* @__PURE__ */ jsx45(
|
|
5781
5506
|
"path",
|
|
5782
5507
|
{
|
|
5783
5508
|
d: "M27 44 98 30 147 45",
|
|
@@ -5787,8 +5512,8 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5787
5512
|
filter: url("bloom")
|
|
5788
5513
|
}
|
|
5789
5514
|
),
|
|
5790
|
-
/* @__PURE__ */
|
|
5791
|
-
Array.from({ length: 8 }, (_, i) => /* @__PURE__ */
|
|
5515
|
+
/* @__PURE__ */ jsx45("path", { d: "M27 44 98 30 147 45", stroke: "var(--ad-secondary-200)", strokeWidth: ".75" }),
|
|
5516
|
+
Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsx45(
|
|
5792
5517
|
"path",
|
|
5793
5518
|
{
|
|
5794
5519
|
d: `M107 ${52 + i * 7.5}l34 9v4l-34-9Z`,
|
|
@@ -5798,7 +5523,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5798
5523
|
},
|
|
5799
5524
|
i
|
|
5800
5525
|
)),
|
|
5801
|
-
/* @__PURE__ */
|
|
5526
|
+
/* @__PURE__ */ jsx45(
|
|
5802
5527
|
"path",
|
|
5803
5528
|
{
|
|
5804
5529
|
d: "M31 108 93 96v8l-62 12Z",
|
|
@@ -5807,7 +5532,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5807
5532
|
strokeWidth: ".55"
|
|
5808
5533
|
}
|
|
5809
5534
|
),
|
|
5810
|
-
/* @__PURE__ */
|
|
5535
|
+
/* @__PURE__ */ jsx45(
|
|
5811
5536
|
"path",
|
|
5812
5537
|
{
|
|
5813
5538
|
d: "M26 125 98 112 150 126v14l-73 10-51-10Z",
|
|
@@ -5816,7 +5541,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5816
5541
|
strokeWidth: ".6"
|
|
5817
5542
|
}
|
|
5818
5543
|
),
|
|
5819
|
-
/* @__PURE__ */
|
|
5544
|
+
/* @__PURE__ */ jsx45(
|
|
5820
5545
|
"path",
|
|
5821
5546
|
{
|
|
5822
5547
|
d: "M26 125 98 112v15l-72 13Z",
|
|
@@ -5825,7 +5550,7 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5825
5550
|
strokeWidth: ".65"
|
|
5826
5551
|
}
|
|
5827
5552
|
),
|
|
5828
|
-
/* @__PURE__ */
|
|
5553
|
+
/* @__PURE__ */ jsx45(
|
|
5829
5554
|
"path",
|
|
5830
5555
|
{
|
|
5831
5556
|
d: "M33 130 83 121m-50 13 41-7",
|
|
@@ -5834,8 +5559,8 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5834
5559
|
}
|
|
5835
5560
|
)
|
|
5836
5561
|
] }) }),
|
|
5837
|
-
/* @__PURE__ */
|
|
5838
|
-
Array.from({ length: 8 }, (_, i) => /* @__PURE__ */
|
|
5562
|
+
/* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, children: /* @__PURE__ */ jsxs36("g", { transform: lift, children: [
|
|
5563
|
+
Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsx45(
|
|
5839
5564
|
"path",
|
|
5840
5565
|
{
|
|
5841
5566
|
d: `M109 ${54 + i * 7.5}l3 .8`,
|
|
@@ -5845,8 +5570,8 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5845
5570
|
},
|
|
5846
5571
|
i
|
|
5847
5572
|
)),
|
|
5848
|
-
/* @__PURE__ */
|
|
5849
|
-
/* @__PURE__ */
|
|
5573
|
+
/* @__PURE__ */ jsx45("path", { d: "M35 110l20-4", className: "ad-server-glow", strokeWidth: "1.1" }),
|
|
5574
|
+
/* @__PURE__ */ jsx45(
|
|
5850
5575
|
"path",
|
|
5851
5576
|
{
|
|
5852
5577
|
d: "M27 43 98 29 98 107 27 121Z",
|
|
@@ -5855,10 +5580,10 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5855
5580
|
}
|
|
5856
5581
|
)
|
|
5857
5582
|
] }) }),
|
|
5858
|
-
[[85, 103.5, 1.5, 0], [91, 121.5, 1.3, -1.7]].map(([cx, cy, r, delay]) => /* @__PURE__ */
|
|
5859
|
-
/* @__PURE__ */
|
|
5860
|
-
/* @__PURE__ */
|
|
5861
|
-
/* @__PURE__ */
|
|
5583
|
+
[[85, 103.5, 1.5, 0], [91, 121.5, 1.3, -1.7]].map(([cx, cy, r, delay]) => /* @__PURE__ */ jsx45(ArtLayer, { viewBox: box, className: "ad-server-dot", style: { animationDelay: `${delay}s` }, children: /* @__PURE__ */ jsx45("circle", { cx, cy: cy + dy, r }) }, cx)),
|
|
5584
|
+
/* @__PURE__ */ jsxs36(ArtLayer, { viewBox: box, className: "ad-art-spark-layer", origin: [121, 27], children: [
|
|
5585
|
+
/* @__PURE__ */ jsx45("path", { d: "M114 27h14m-7-10v20", stroke: "var(--ad-secondary-100)", strokeWidth: ".65" }),
|
|
5586
|
+
/* @__PURE__ */ jsx45("circle", { cx: "121", cy: "27", r: "2.6", fill: "var(--ad-neutral-200)" })
|
|
5862
5587
|
] })
|
|
5863
5588
|
]
|
|
5864
5589
|
}
|
|
@@ -5866,13 +5591,13 @@ function ServerArt({ upload = false, label, ...p }) {
|
|
|
5866
5591
|
}
|
|
5867
5592
|
|
|
5868
5593
|
// src/components/media/AudioPlayer/AudioPlayer.tsx
|
|
5869
|
-
import { useEffect as useEffect12, useRef as
|
|
5594
|
+
import { useEffect as useEffect12, useRef as useRef27, useState as useState12 } from "react";
|
|
5870
5595
|
|
|
5871
5596
|
// src/components/media/Waveform/Waveform.tsx
|
|
5872
|
-
import { useEffect as useEffect11, useMemo as useMemo4, useRef as
|
|
5597
|
+
import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef26, useState as useState11 } from "react";
|
|
5873
5598
|
|
|
5874
5599
|
// src/components/media/Waveform/useWaveformPeaks.ts
|
|
5875
|
-
import { useEffect as useEffect10, useState as
|
|
5600
|
+
import { useEffect as useEffect10, useState as useState10 } from "react";
|
|
5876
5601
|
async function decode(src, bins, signal) {
|
|
5877
5602
|
const data = typeof src === "string" ? await (await fetch(src, { signal })).arrayBuffer() : await src.arrayBuffer();
|
|
5878
5603
|
signal.throwIfAborted();
|
|
@@ -5915,7 +5640,7 @@ async function decode(src, bins, signal) {
|
|
|
5915
5640
|
};
|
|
5916
5641
|
}
|
|
5917
5642
|
function useWaveformPeaks(src, bins = 600) {
|
|
5918
|
-
const [data, setData] =
|
|
5643
|
+
const [data, setData] = useState10(null);
|
|
5919
5644
|
useEffect10(() => {
|
|
5920
5645
|
setData(null);
|
|
5921
5646
|
if (!src) return;
|
|
@@ -5932,7 +5657,7 @@ function useWaveformPeaks(src, bins = 600) {
|
|
|
5932
5657
|
}
|
|
5933
5658
|
|
|
5934
5659
|
// src/components/media/Waveform/Waveform.tsx
|
|
5935
|
-
import { jsx as
|
|
5660
|
+
import { jsx as jsx46, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
5936
5661
|
var W4 = 1e3;
|
|
5937
5662
|
var H4 = 100;
|
|
5938
5663
|
var MID = H4 / 2;
|
|
@@ -6000,9 +5725,9 @@ function Waveform({
|
|
|
6000
5725
|
}) {
|
|
6001
5726
|
const tr2 = useTr();
|
|
6002
5727
|
const id = useSvgId();
|
|
6003
|
-
const surface =
|
|
6004
|
-
const drag =
|
|
6005
|
-
const [hover, setHover] =
|
|
5728
|
+
const surface = useRef26(null);
|
|
5729
|
+
const drag = useRef26(null);
|
|
5730
|
+
const [hover, setHover] = useState11(null);
|
|
6006
5731
|
const decoded = useWaveformPeaks(points ? null : src, bins);
|
|
6007
5732
|
const loading = !points && !!src && decoded === null;
|
|
6008
5733
|
const { peaksPath, corePath } = useMemo4(() => {
|
|
@@ -6023,8 +5748,8 @@ function Waveform({
|
|
|
6023
5748
|
const progress = clamp(position / duration, 0, 1);
|
|
6024
5749
|
const played = progress * W4;
|
|
6025
5750
|
const ahead = Math.max(0, (hover ?? 0) * W4 - played);
|
|
6026
|
-
const [playing, setPlaying] =
|
|
6027
|
-
const previous =
|
|
5751
|
+
const [playing, setPlaying] = useState11(false);
|
|
5752
|
+
const previous = useRef26(position);
|
|
6028
5753
|
useEffect11(() => {
|
|
6029
5754
|
const step = position - previous.current;
|
|
6030
5755
|
previous.current = position;
|
|
@@ -6047,7 +5772,7 @@ function Waveform({
|
|
|
6047
5772
|
if (cancelled && !disabled) seek(clamp(current.position, 0, duration));
|
|
6048
5773
|
setHover(null);
|
|
6049
5774
|
};
|
|
6050
|
-
return /* @__PURE__ */
|
|
5775
|
+
return /* @__PURE__ */ jsxs37(
|
|
6051
5776
|
"div",
|
|
6052
5777
|
{
|
|
6053
5778
|
...mark("Waveform", p),
|
|
@@ -6064,8 +5789,6 @@ function Waveform({
|
|
|
6064
5789
|
"data-playing": playing || void 0,
|
|
6065
5790
|
style: {
|
|
6066
5791
|
...p.style,
|
|
6067
|
-
"--ad-wave-played": `${progress * 100}%`,
|
|
6068
|
-
"--ad-wave-progress": progress,
|
|
6069
5792
|
"--ad-wave-hover": hover === null ? void 0 : `${hover * 100}%`,
|
|
6070
5793
|
...color3 ? { "--ad-wave-color": color3 } : {}
|
|
6071
5794
|
},
|
|
@@ -6111,8 +5834,8 @@ function Waveform({
|
|
|
6111
5834
|
seek(clamp(next, 0, duration));
|
|
6112
5835
|
},
|
|
6113
5836
|
children: [
|
|
6114
|
-
/* @__PURE__ */
|
|
6115
|
-
/* @__PURE__ */
|
|
5837
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-floor", style: { transform: `scaleX(${progress})` }, "aria-hidden": true }),
|
|
5838
|
+
/* @__PURE__ */ jsxs37(
|
|
6116
5839
|
"svg",
|
|
6117
5840
|
{
|
|
6118
5841
|
className: "ad-waveform-base",
|
|
@@ -6120,63 +5843,63 @@ function Waveform({
|
|
|
6120
5843
|
preserveAspectRatio: "none",
|
|
6121
5844
|
"aria-hidden": "true",
|
|
6122
5845
|
children: [
|
|
6123
|
-
/* @__PURE__ */
|
|
6124
|
-
/* @__PURE__ */
|
|
6125
|
-
/* @__PURE__ */
|
|
6126
|
-
/* @__PURE__ */
|
|
6127
|
-
/* @__PURE__ */
|
|
6128
|
-
/* @__PURE__ */
|
|
5846
|
+
/* @__PURE__ */ jsxs37("defs", { children: [
|
|
5847
|
+
/* @__PURE__ */ jsx46("clipPath", { id: `${id}-track`, children: /* @__PURE__ */ jsx46("path", { d: peaksPath }) }),
|
|
5848
|
+
/* @__PURE__ */ jsxs37("linearGradient", { id: `${id}-silver`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
5849
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0", stopColor: "var(--ad-neutral-300)", stopOpacity: "0.35" }),
|
|
5850
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0.5", stopColor: "#fff", stopOpacity: "0.95" }),
|
|
5851
|
+
/* @__PURE__ */ jsx46("stop", { offset: "1", stopColor: "var(--ad-neutral-300)", stopOpacity: "0.35" })
|
|
6129
5852
|
] }),
|
|
6130
|
-
/* @__PURE__ */
|
|
6131
|
-
/* @__PURE__ */
|
|
6132
|
-
/* @__PURE__ */
|
|
6133
|
-
/* @__PURE__ */
|
|
5853
|
+
/* @__PURE__ */ jsxs37("linearGradient", { id: `${id}-solid`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
5854
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0", stopColor: "var(--ad-neutral-200)", stopOpacity: "0.8" }),
|
|
5855
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0.5", stopColor: "#fff" }),
|
|
5856
|
+
/* @__PURE__ */ jsx46("stop", { offset: "1", stopColor: "var(--ad-neutral-200)", stopOpacity: "0.8" })
|
|
6134
5857
|
] }),
|
|
6135
|
-
/* @__PURE__ */
|
|
6136
|
-
/* @__PURE__ */
|
|
6137
|
-
/* @__PURE__ */
|
|
6138
|
-
/* @__PURE__ */
|
|
6139
|
-
/* @__PURE__ */
|
|
5858
|
+
/* @__PURE__ */ jsxs37("linearGradient", { id: `${id}-depth`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
5859
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0", stopColor: "#fff", stopOpacity: "0.16" }),
|
|
5860
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0.42", stopColor: "#fff", stopOpacity: "0" }),
|
|
5861
|
+
/* @__PURE__ */ jsx46("stop", { offset: "0.56", stopColor: "#000", stopOpacity: "0" }),
|
|
5862
|
+
/* @__PURE__ */ jsx46("stop", { offset: "1", stopColor: "#000", stopOpacity: "0.6" })
|
|
6140
5863
|
] })
|
|
6141
5864
|
] }),
|
|
6142
|
-
/* @__PURE__ */
|
|
6143
|
-
/* @__PURE__ */
|
|
6144
|
-
/* @__PURE__ */
|
|
6145
|
-
/* @__PURE__ */
|
|
5865
|
+
/* @__PURE__ */ jsx46("line", { className: "ad-waveform-axis", x2: W4, y1: MID, y2: MID }),
|
|
5866
|
+
/* @__PURE__ */ jsx46("path", { className: "ad-waveform-peaks", d: peaksPath, fill: `url(#${id}-silver)` }),
|
|
5867
|
+
/* @__PURE__ */ jsx46("path", { className: "ad-waveform-core", d: corePath, fill: `url(#${id}-solid)` }),
|
|
5868
|
+
/* @__PURE__ */ jsx46("path", { className: "ad-waveform-edge", d: peaksPath })
|
|
6146
5869
|
]
|
|
6147
5870
|
},
|
|
6148
5871
|
shape
|
|
6149
5872
|
),
|
|
6150
|
-
ahead > 0 && /* @__PURE__ */
|
|
6151
|
-
/* @__PURE__ */
|
|
6152
|
-
/* @__PURE__ */
|
|
6153
|
-
/* @__PURE__ */
|
|
6154
|
-
/* @__PURE__ */
|
|
5873
|
+
ahead > 0 && /* @__PURE__ */ jsxs37("svg", { className: "ad-waveform-overlay", viewBox: `0 0 ${W4} ${H4}`, preserveAspectRatio: "none", "aria-hidden": "true", children: [
|
|
5874
|
+
/* @__PURE__ */ jsx46("clipPath", { id: `${id}-ahead`, children: /* @__PURE__ */ jsx46("rect", { x: played, width: ahead, height: H4 }) }),
|
|
5875
|
+
/* @__PURE__ */ jsxs37("g", { clipPath: `url(#${id}-ahead)`, className: "ad-waveform-ahead", children: [
|
|
5876
|
+
/* @__PURE__ */ jsx46("path", { d: peaksPath }),
|
|
5877
|
+
/* @__PURE__ */ jsx46("path", { d: corePath })
|
|
6155
5878
|
] })
|
|
6156
5879
|
] }),
|
|
6157
|
-
/* @__PURE__ */
|
|
6158
|
-
/* @__PURE__ */
|
|
6159
|
-
/* @__PURE__ */
|
|
6160
|
-
/* @__PURE__ */
|
|
6161
|
-
/* @__PURE__ */
|
|
6162
|
-
/* @__PURE__ */
|
|
6163
|
-
/* @__PURE__ */
|
|
6164
|
-
hover !== null && !disabled && /* @__PURE__ */
|
|
5880
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-layer ad-waveform-bloom", style: { maskImage: masks.bloom, WebkitMaskImage: masks.bloom }, "aria-hidden": true, children: /* @__PURE__ */ jsx46("i", { style: { transform: `scaleX(${progress})` } }) }),
|
|
5881
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-layer ad-waveform-lit", style: { maskImage: masks.peaks, WebkitMaskImage: masks.peaks }, "aria-hidden": true, children: /* @__PURE__ */ jsx46("i", { style: { transform: `scaleX(${progress})` } }) }),
|
|
5882
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-layer ad-waveform-lit ad-waveform-lit-core", style: { maskImage: masks.core, WebkitMaskImage: masks.core }, "aria-hidden": true, children: /* @__PURE__ */ jsx46("i", { style: { transform: `scaleX(${progress})` } }) }),
|
|
5883
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-layer ad-waveform-sheen-clip", "data-ad-paused": progress === 0 ? "" : void 0, style: { maskImage: masks.peaks, WebkitMaskImage: masks.peaks }, "aria-hidden": true, children: /* @__PURE__ */ jsx46("span", { className: "ad-waveform-window", style: { transform: `translateX(calc(${progress * 100}% - 100%))` }, children: /* @__PURE__ */ jsx46("span", { className: "ad-waveform-window-inner", style: { transform: `translateX(calc(100% - ${progress * 100}%))` }, children: /* @__PURE__ */ jsx46("i", { className: "ad-waveform-sheen" }) }) }) }),
|
|
5884
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-layer ad-waveform-depth", style: { maskImage: masks.peaks, WebkitMaskImage: masks.peaks }, "aria-hidden": true }),
|
|
5885
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-layer ad-waveform-shade", style: { maskImage: masks.peaks, WebkitMaskImage: masks.peaks }, "aria-hidden": true, children: /* @__PURE__ */ jsx46("span", { className: "ad-waveform-follow", style: { transform: `translateX(${progress * 100}%)` }, children: /* @__PURE__ */ jsx46("i", { className: "ad-waveform-spot" }) }) }),
|
|
5886
|
+
/* @__PURE__ */ jsx46("span", { className: "ad-waveform-follow", style: { transform: `translateX(${progress * 100}%)` }, "aria-hidden": true, children: /* @__PURE__ */ jsx46("span", { className: "ad-waveform-cursor", children: /* @__PURE__ */ jsx46("span", { className: "ad-waveform-sparks", "data-ad-paused": playing ? void 0 : "", children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsx46("i", {}, i)) }) }) }),
|
|
5887
|
+
hover !== null && !disabled && /* @__PURE__ */ jsx46("span", { className: "ad-waveform-hover", "aria-hidden": true, children: /* @__PURE__ */ jsx46("span", { className: "ad-waveform-tip", children: timeText(hover * duration) }) })
|
|
6165
5888
|
]
|
|
6166
5889
|
}
|
|
6167
5890
|
);
|
|
6168
5891
|
}
|
|
6169
5892
|
|
|
6170
5893
|
// src/components/media/AudioPlayer/AudioPlayer.tsx
|
|
6171
|
-
import { jsx as
|
|
5894
|
+
import { jsx as jsx47, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
6172
5895
|
var AudioPlayer = (p) => {
|
|
6173
5896
|
const tr2 = useTr();
|
|
6174
|
-
const audio =
|
|
6175
|
-
const [playing, setPlaying] =
|
|
5897
|
+
const audio = useRef27(null);
|
|
5898
|
+
const [playing, setPlaying] = useState12(false), [muted, setMuted] = useState12(false);
|
|
6176
5899
|
const [volume, setVolume] = useControllable(p.volume, p.defaultVolume ?? 0.7, p.onVolumeChange);
|
|
6177
|
-
const onPlayingChange =
|
|
5900
|
+
const onPlayingChange = useRef27(p.onPlayingChange);
|
|
6178
5901
|
onPlayingChange.current = p.onPlayingChange;
|
|
6179
|
-
const [fileDuration, setFileDuration] =
|
|
5902
|
+
const [fileDuration, setFileDuration] = useState12();
|
|
6180
5903
|
const duration = p.duration ?? fileDuration ?? 51;
|
|
6181
5904
|
const controlled = p.playing !== void 0;
|
|
6182
5905
|
const active = p.playing ?? playing;
|
|
@@ -6225,8 +5948,8 @@ var AudioPlayer = (p) => {
|
|
|
6225
5948
|
else audio.current.pause();
|
|
6226
5949
|
}
|
|
6227
5950
|
};
|
|
6228
|
-
return /* @__PURE__ */
|
|
6229
|
-
/* @__PURE__ */
|
|
5951
|
+
return /* @__PURE__ */ jsxs38("div", { ...mark("AudioPlayer", p), "data-playing": active || void 0, children: [
|
|
5952
|
+
/* @__PURE__ */ jsx47("span", { className: "ad-player-play", children: /* @__PURE__ */ jsx47(
|
|
6230
5953
|
IconButton,
|
|
6231
5954
|
{
|
|
6232
5955
|
variant: "primary",
|
|
@@ -6237,7 +5960,7 @@ var AudioPlayer = (p) => {
|
|
|
6237
5960
|
onClick: toggle
|
|
6238
5961
|
}
|
|
6239
5962
|
) }),
|
|
6240
|
-
/* @__PURE__ */
|
|
5963
|
+
/* @__PURE__ */ jsx47(
|
|
6241
5964
|
PlaybackTrack,
|
|
6242
5965
|
{
|
|
6243
5966
|
audio,
|
|
@@ -6255,8 +5978,8 @@ var AudioPlayer = (p) => {
|
|
|
6255
5978
|
},
|
|
6256
5979
|
p.src
|
|
6257
5980
|
),
|
|
6258
|
-
p.showVolume !== false && /* @__PURE__ */
|
|
6259
|
-
/* @__PURE__ */
|
|
5981
|
+
p.showVolume !== false && /* @__PURE__ */ jsxs38("div", { className: "ad-player-volume", children: [
|
|
5982
|
+
/* @__PURE__ */ jsx47(
|
|
6260
5983
|
IconButton,
|
|
6261
5984
|
{
|
|
6262
5985
|
variant: "ghost",
|
|
@@ -6268,7 +5991,7 @@ var AudioPlayer = (p) => {
|
|
|
6268
5991
|
onClick: () => setMuted((v) => !v)
|
|
6269
5992
|
}
|
|
6270
5993
|
),
|
|
6271
|
-
/* @__PURE__ */
|
|
5994
|
+
/* @__PURE__ */ jsx47(
|
|
6272
5995
|
Slider,
|
|
6273
5996
|
{
|
|
6274
5997
|
size: "sm",
|
|
@@ -6288,10 +6011,10 @@ var AudioPlayer = (p) => {
|
|
|
6288
6011
|
] });
|
|
6289
6012
|
};
|
|
6290
6013
|
function PlaybackTrack({ audio, playing, position: controlledPosition, disabled, duration, points, src, onTimeChange, onEnded }) {
|
|
6291
|
-
const [localPosition, setPosition] =
|
|
6014
|
+
const [localPosition, setPosition] = useState12(0);
|
|
6292
6015
|
const position = controlledPosition ?? localPosition;
|
|
6293
|
-
const current =
|
|
6294
|
-
const lastTick =
|
|
6016
|
+
const current = useRef27(0);
|
|
6017
|
+
const lastTick = useRef27(void 0);
|
|
6295
6018
|
useTick((now) => {
|
|
6296
6019
|
const media = audio.current;
|
|
6297
6020
|
const next = media?.currentTime ?? current.current + (now - (lastTick.current ?? now)) / 1e3;
|
|
@@ -6314,11 +6037,11 @@ function PlaybackTrack({ audio, playing, position: controlledPosition, disabled,
|
|
|
6314
6037
|
if (audio.current) audio.current.currentTime = next;
|
|
6315
6038
|
onTimeChange?.(next);
|
|
6316
6039
|
};
|
|
6317
|
-
return /* @__PURE__ */
|
|
6318
|
-
/* @__PURE__ */
|
|
6319
|
-
/* @__PURE__ */
|
|
6320
|
-
/* @__PURE__ */
|
|
6321
|
-
/* @__PURE__ */
|
|
6040
|
+
return /* @__PURE__ */ jsxs38("div", { className: "ad-player-track", children: [
|
|
6041
|
+
/* @__PURE__ */ jsx47(Waveform, { duration, position, onSeek: seek, points, src: points ? void 0 : src, disabled }),
|
|
6042
|
+
/* @__PURE__ */ jsxs38("div", { className: "ad-player-times", children: [
|
|
6043
|
+
/* @__PURE__ */ jsx47("span", { className: "ad-time", children: timeText(position) }),
|
|
6044
|
+
/* @__PURE__ */ jsxs38("span", { className: "ad-time", children: [
|
|
6322
6045
|
"\u2212",
|
|
6323
6046
|
timeText(duration - position)
|
|
6324
6047
|
] })
|
|
@@ -6327,8 +6050,8 @@ function PlaybackTrack({ audio, playing, position: controlledPosition, disabled,
|
|
|
6327
6050
|
}
|
|
6328
6051
|
|
|
6329
6052
|
// src/components/media/LevelMeter/LevelMeter.tsx
|
|
6330
|
-
import { useEffect as useEffect13, useRef as
|
|
6331
|
-
import { jsx as
|
|
6053
|
+
import { useEffect as useEffect13, useRef as useRef28 } from "react";
|
|
6054
|
+
import { jsx as jsx48, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
6332
6055
|
var SAMPLES2 = 64;
|
|
6333
6056
|
var WIDTH = 256;
|
|
6334
6057
|
var HEIGHT = 24;
|
|
@@ -6357,9 +6080,9 @@ function LevelMeter({
|
|
|
6357
6080
|
}) {
|
|
6358
6081
|
const tr2 = useTr();
|
|
6359
6082
|
const id = useSvgId();
|
|
6360
|
-
const path =
|
|
6361
|
-
const target =
|
|
6362
|
-
const wake =
|
|
6083
|
+
const path = useRef28(null);
|
|
6084
|
+
const target = useRef28(0);
|
|
6085
|
+
const wake = useRef28(() => void 0);
|
|
6363
6086
|
const level = clamp(value) / 100;
|
|
6364
6087
|
useEffect13(() => {
|
|
6365
6088
|
target.current = active ? level : 0;
|
|
@@ -6424,7 +6147,7 @@ function LevelMeter({
|
|
|
6424
6147
|
void context?.close();
|
|
6425
6148
|
};
|
|
6426
6149
|
}, [active, stream]);
|
|
6427
|
-
return /* @__PURE__ */
|
|
6150
|
+
return /* @__PURE__ */ jsx48(
|
|
6428
6151
|
"div",
|
|
6429
6152
|
{
|
|
6430
6153
|
...mark("LevelMeter", p),
|
|
@@ -6435,20 +6158,20 @@ function LevelMeter({
|
|
|
6435
6158
|
"aria-valuenow": stream ? void 0 : Math.round(active ? level * 100 : 0),
|
|
6436
6159
|
"data-active": active,
|
|
6437
6160
|
"data-compact": compact || void 0,
|
|
6438
|
-
children: /* @__PURE__ */
|
|
6161
|
+
children: /* @__PURE__ */ jsxs39(
|
|
6439
6162
|
"svg",
|
|
6440
6163
|
{
|
|
6441
6164
|
viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
|
|
6442
6165
|
preserveAspectRatio: "none",
|
|
6443
6166
|
"aria-hidden": "true",
|
|
6444
6167
|
children: [
|
|
6445
|
-
/* @__PURE__ */
|
|
6446
|
-
/* @__PURE__ */
|
|
6447
|
-
/* @__PURE__ */
|
|
6448
|
-
/* @__PURE__ */
|
|
6168
|
+
/* @__PURE__ */ jsx48("defs", { children: /* @__PURE__ */ jsxs39("linearGradient", { id: `${id}-wave`, children: [
|
|
6169
|
+
/* @__PURE__ */ jsx48("stop", { stopColor: "var(--ad-primary-700)" }),
|
|
6170
|
+
/* @__PURE__ */ jsx48("stop", { offset: "0.52", stopColor: "var(--ad-red)" }),
|
|
6171
|
+
/* @__PURE__ */ jsx48("stop", { offset: "1", stopColor: "var(--ad-secondary-100)" })
|
|
6449
6172
|
] }) }),
|
|
6450
|
-
/* @__PURE__ */
|
|
6451
|
-
/* @__PURE__ */
|
|
6173
|
+
/* @__PURE__ */ jsx48("line", { className: "ad-level-meter-axis", x2: WIDTH, y1: MID2, y2: MID2 }),
|
|
6174
|
+
/* @__PURE__ */ jsx48(
|
|
6452
6175
|
"path",
|
|
6453
6176
|
{
|
|
6454
6177
|
ref: path,
|
|
@@ -6464,13 +6187,13 @@ function LevelMeter({
|
|
|
6464
6187
|
}
|
|
6465
6188
|
|
|
6466
6189
|
// src/components/media/KaraokeLyrics/KaraokeLyrics.tsx
|
|
6467
|
-
import { jsx as
|
|
6190
|
+
import { jsx as jsx49, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
6468
6191
|
var DEMO = [
|
|
6469
6192
|
{ id: "1", text: "\u041D\u043E\u0447\u044C", progress: 1 },
|
|
6470
6193
|
{ id: "2", text: "\u0433\u043E\u0440\u0438\u0442", progress: 0.55 },
|
|
6471
6194
|
{ id: "3", text: "\u043E\u0433\u043D\u044F\u043C\u0438", progress: 0 }
|
|
6472
6195
|
];
|
|
6473
|
-
var line = (words, sung) => words.map((word) => /* @__PURE__ */
|
|
6196
|
+
var line = (words, sung) => words.map((word) => /* @__PURE__ */ jsxs40(
|
|
6474
6197
|
"span",
|
|
6475
6198
|
{
|
|
6476
6199
|
className: "ad-lyric-word",
|
|
@@ -6492,22 +6215,22 @@ var KaraokeLyrics = ({
|
|
|
6492
6215
|
snare = 0,
|
|
6493
6216
|
pulse = 0,
|
|
6494
6217
|
...p
|
|
6495
|
-
}) => /* @__PURE__ */
|
|
6218
|
+
}) => /* @__PURE__ */ jsxs40(
|
|
6496
6219
|
"div",
|
|
6497
6220
|
{
|
|
6498
6221
|
...mark("KaraokeLyrics", p),
|
|
6499
6222
|
"aria-live": "off",
|
|
6500
6223
|
style: { ...p.style, "--ad-lyric-kick": kick, "--ad-lyric-snare": snare, "--ad-lyric-pulse": pulse },
|
|
6501
6224
|
children: [
|
|
6502
|
-
/* @__PURE__ */
|
|
6503
|
-
/* @__PURE__ */
|
|
6225
|
+
/* @__PURE__ */ jsx49("p", { className: "ad-lyrics-current", "data-message": message !== void 0 || void 0, children: message ?? line(current, true) }, currentKey),
|
|
6226
|
+
/* @__PURE__ */ jsx49("p", { className: "ad-lyrics-next", "aria-hidden": message !== void 0 || void 0, children: message === void 0 && line(next, false) }, nextKey)
|
|
6504
6227
|
]
|
|
6505
6228
|
}
|
|
6506
6229
|
);
|
|
6507
6230
|
|
|
6508
6231
|
// src/components/media/MelodyRoll/MelodyRoll.tsx
|
|
6509
|
-
import { useRef as
|
|
6510
|
-
import { jsx as
|
|
6232
|
+
import { useRef as useRef29 } from "react";
|
|
6233
|
+
import { jsx as jsx50, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
6511
6234
|
var DEMO2 = [
|
|
6512
6235
|
{ id: "1", start: 0.4, end: 1.1, pitch: 64, lyric: "\u041D\u043E\u0447\u044C" },
|
|
6513
6236
|
{ id: "2", start: 1.2, end: 1.6, pitch: 67, lyric: "\u0433\u043E" },
|
|
@@ -6549,7 +6272,7 @@ var MelodyRoll = ({
|
|
|
6549
6272
|
const x = (seconds) => (seconds - from) / span * 100;
|
|
6550
6273
|
const y = (pitch) => (high - pitch) / rows * 100;
|
|
6551
6274
|
const visible = notes.filter((note) => note.end >= from && note.start <= from + span);
|
|
6552
|
-
const trail =
|
|
6275
|
+
const trail = useRef29([]);
|
|
6553
6276
|
if (trail.current.at(-1)?.t !== position) {
|
|
6554
6277
|
const kept = trail.current.filter((point) => point.t > position - TRAIL && point.t < position);
|
|
6555
6278
|
trail.current = [...kept, { t: position, pitch: livePitch }];
|
|
@@ -6566,7 +6289,7 @@ var MelodyRoll = ({
|
|
|
6566
6289
|
if (!event.deltaY) return;
|
|
6567
6290
|
setSpan(clamp(span * (event.deltaY > 0 ? 1.12 : 1 / 1.12), 2, 20));
|
|
6568
6291
|
};
|
|
6569
|
-
return /* @__PURE__ */
|
|
6292
|
+
return /* @__PURE__ */ jsxs41(
|
|
6570
6293
|
"div",
|
|
6571
6294
|
{
|
|
6572
6295
|
...mark("MelodyRoll", p),
|
|
@@ -6583,7 +6306,7 @@ var MelodyRoll = ({
|
|
|
6583
6306
|
"--ad-roll-shift": `${-((from % 1 + 1) % 1) / span * 100}cqw`
|
|
6584
6307
|
},
|
|
6585
6308
|
children: [
|
|
6586
|
-
/* @__PURE__ */
|
|
6309
|
+
/* @__PURE__ */ jsx50(
|
|
6587
6310
|
PianoKeyboard,
|
|
6588
6311
|
{
|
|
6589
6312
|
className: "ad-melody-roll-keys",
|
|
@@ -6595,9 +6318,9 @@ var MelodyRoll = ({
|
|
|
6595
6318
|
onKeyPress
|
|
6596
6319
|
}
|
|
6597
6320
|
),
|
|
6598
|
-
/* @__PURE__ */
|
|
6599
|
-
/* @__PURE__ */
|
|
6600
|
-
/* @__PURE__ */
|
|
6321
|
+
/* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-lane", "aria-hidden": "true", children: [
|
|
6322
|
+
/* @__PURE__ */ jsx50("span", { className: "ad-melody-roll-seconds" }),
|
|
6323
|
+
/* @__PURE__ */ jsx50("div", { className: "ad-melody-roll-strip", style: { transform: `translateX(${(-from / span * 100).toFixed(3)}%)` }, children: visible.map((note) => /* @__PURE__ */ jsx50(
|
|
6601
6324
|
"span",
|
|
6602
6325
|
{
|
|
6603
6326
|
className: "ad-melody-note",
|
|
@@ -6609,17 +6332,17 @@ var MelodyRoll = ({
|
|
|
6609
6332
|
width: `${Math.max(0.6, (note.end - note.start) / span * 100)}%`,
|
|
6610
6333
|
top: `${y(note.pitch)}%`
|
|
6611
6334
|
},
|
|
6612
|
-
children: showLyrics && note.lyric && /* @__PURE__ */
|
|
6335
|
+
children: showLyrics && note.lyric && /* @__PURE__ */ jsx50("span", { children: note.lyric })
|
|
6613
6336
|
},
|
|
6614
6337
|
note.id
|
|
6615
6338
|
)) }),
|
|
6616
|
-
/* @__PURE__ */
|
|
6617
|
-
/* @__PURE__ */
|
|
6618
|
-
/* @__PURE__ */
|
|
6619
|
-
/* @__PURE__ */
|
|
6339
|
+
/* @__PURE__ */ jsx50("svg", { className: "ad-melody-trail", viewBox: "0 0 100 100", preserveAspectRatio: "none", children: segments.flatMap((d, i) => [
|
|
6340
|
+
/* @__PURE__ */ jsx50("path", { "data-halo": "wide", d, vectorEffect: "non-scaling-stroke" }, `${i}-wide`),
|
|
6341
|
+
/* @__PURE__ */ jsx50("path", { "data-halo": "near", d, vectorEffect: "non-scaling-stroke" }, `${i}-near`),
|
|
6342
|
+
/* @__PURE__ */ jsx50("path", { d, vectorEffect: "non-scaling-stroke" }, i)
|
|
6620
6343
|
]) }),
|
|
6621
|
-
/* @__PURE__ */
|
|
6622
|
-
livePitch !== void 0 && /* @__PURE__ */
|
|
6344
|
+
/* @__PURE__ */ jsx50("span", { className: "ad-melody-playhead", style: { left: `${lead * 100}%` } }),
|
|
6345
|
+
livePitch !== void 0 && /* @__PURE__ */ jsx50(
|
|
6623
6346
|
"span",
|
|
6624
6347
|
{
|
|
6625
6348
|
className: "ad-melody-voice",
|
|
@@ -6634,26 +6357,26 @@ var MelodyRoll = ({
|
|
|
6634
6357
|
};
|
|
6635
6358
|
|
|
6636
6359
|
// src/components/layout/FloatingPanel/FloatingPanel.tsx
|
|
6637
|
-
import { useRef as
|
|
6360
|
+
import { useRef as useRef31 } from "react";
|
|
6638
6361
|
|
|
6639
6362
|
// src/components/layout/FloatingPanel/useFloatingPanel.ts
|
|
6640
|
-
import { useCallback as useCallback2, useEffect as useEffect14, useLayoutEffect as
|
|
6363
|
+
import { useCallback as useCallback2, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useRef as useRef30, useState as useState13 } from "react";
|
|
6641
6364
|
var resizeEdges = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
6642
6365
|
var threshold = 3;
|
|
6643
6366
|
var panelControls = "button, a, input, select, textarea, [role=slider], [role=button], [role=switch], [role=listbox], [contenteditable=true], .ad-rotary-knob";
|
|
6644
6367
|
var useFloatingPanel = (frameRef, options = {}) => {
|
|
6645
|
-
const [active, setActive] =
|
|
6646
|
-
const [live, setLive] =
|
|
6647
|
-
const drag =
|
|
6648
|
-
const moved =
|
|
6649
|
-
const latest =
|
|
6368
|
+
const [active, setActive] = useState13(false);
|
|
6369
|
+
const [live, setLive] = useState13(null);
|
|
6370
|
+
const drag = useRef30(null);
|
|
6371
|
+
const moved = useRef30(false);
|
|
6372
|
+
const latest = useRef30(options);
|
|
6650
6373
|
latest.current = options;
|
|
6651
6374
|
const saved = options.layout;
|
|
6652
|
-
|
|
6375
|
+
useLayoutEffect5(() => {
|
|
6653
6376
|
drag.current = null;
|
|
6654
6377
|
setLive(null);
|
|
6655
6378
|
}, [saved?.left, saved?.top, saved?.width, saved?.height]);
|
|
6656
|
-
const [, setViewport] =
|
|
6379
|
+
const [, setViewport] = useState13(0);
|
|
6657
6380
|
useEffect14(() => {
|
|
6658
6381
|
const onResize = () => setViewport((n) => n + 1);
|
|
6659
6382
|
window.addEventListener("resize", onResize);
|
|
@@ -6776,9 +6499,9 @@ var useFloatingPanel = (frameRef, options = {}) => {
|
|
|
6776
6499
|
};
|
|
6777
6500
|
|
|
6778
6501
|
// src/components/layout/FloatingPanel/FloatingPanel.tsx
|
|
6779
|
-
import { jsx as
|
|
6502
|
+
import { jsx as jsx51, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
6780
6503
|
var FloatingPanel = ({ layout: saved, onLayoutChange, defaultSize, limits, onDragOutside, ignore, resizable = false, label, children, ...p }) => {
|
|
6781
|
-
const frame =
|
|
6504
|
+
const frame = useRef31(null);
|
|
6782
6505
|
const { layout, active, beginMove, beginResize, handleMove, handleUp, handleCancel, handleKeyDown } = useFloatingPanel(frame, {
|
|
6783
6506
|
layout: saved,
|
|
6784
6507
|
onLayoutChange,
|
|
@@ -6787,7 +6510,7 @@ var FloatingPanel = ({ layout: saved, onLayoutChange, defaultSize, limits, onDra
|
|
|
6787
6510
|
onDragOutside,
|
|
6788
6511
|
ignore
|
|
6789
6512
|
});
|
|
6790
|
-
return /* @__PURE__ */
|
|
6513
|
+
return /* @__PURE__ */ jsxs42(
|
|
6791
6514
|
"div",
|
|
6792
6515
|
{
|
|
6793
6516
|
...mark("FloatingPanel", p),
|
|
@@ -6804,20 +6527,20 @@ var FloatingPanel = ({ layout: saved, onLayoutChange, defaultSize, limits, onDra
|
|
|
6804
6527
|
onKeyDown: (event) => handleKeyDown(event, resizable),
|
|
6805
6528
|
children: [
|
|
6806
6529
|
children,
|
|
6807
|
-
resizable && active && resizeEdges.map((edge) => /* @__PURE__ */
|
|
6530
|
+
resizable && active && resizeEdges.map((edge) => /* @__PURE__ */ jsx51("span", { className: "ad-floating-panel-handle", "data-edge": edge, "aria-hidden": "true", onPointerDown: beginResize(edge) }, edge))
|
|
6808
6531
|
]
|
|
6809
6532
|
}
|
|
6810
6533
|
);
|
|
6811
6534
|
};
|
|
6812
6535
|
|
|
6813
6536
|
// src/components/media/Sparkline/Sparkline.tsx
|
|
6814
|
-
import { useMemo as useMemo5, useRef as
|
|
6815
|
-
import { Fragment as
|
|
6537
|
+
import { useMemo as useMemo5, useRef as useRef32 } from "react";
|
|
6538
|
+
import { Fragment as Fragment4, jsx as jsx52, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
6816
6539
|
var DEMO3 = [12, 23, 17, 31, 43, 24, 28, 20, 41, 29, 51, 34, 38, 22, 31, 16, 23];
|
|
6817
6540
|
var Sparkline = (p) => {
|
|
6818
6541
|
const values = p.values ?? DEMO3;
|
|
6819
6542
|
const id = useSvgId();
|
|
6820
|
-
const ref =
|
|
6543
|
+
const ref = useRef32(null);
|
|
6821
6544
|
usePauseOffscreen(ref);
|
|
6822
6545
|
const { line: line2, lastX, lastY } = useMemo5(() => {
|
|
6823
6546
|
if (!values.length) return { line: "", lastX: 0, lastY: 0 };
|
|
@@ -6854,7 +6577,7 @@ var Sparkline = (p) => {
|
|
|
6854
6577
|
};
|
|
6855
6578
|
}, [values, p.fit]);
|
|
6856
6579
|
const box = { viewBox: "0 0 240 70", preserveAspectRatio: "none", "aria-hidden": true };
|
|
6857
|
-
return /* @__PURE__ */
|
|
6580
|
+
return /* @__PURE__ */ jsx52(
|
|
6858
6581
|
"span",
|
|
6859
6582
|
{
|
|
6860
6583
|
...mark("Sparkline", p),
|
|
@@ -6863,13 +6586,13 @@ var Sparkline = (p) => {
|
|
|
6863
6586
|
"aria-label": p.label,
|
|
6864
6587
|
"aria-hidden": !p.label,
|
|
6865
6588
|
style: { ...p.style, "--ad-spark": p.color ?? "var(--ad-primary)" },
|
|
6866
|
-
children: line2 && /* @__PURE__ */
|
|
6867
|
-
/* @__PURE__ */
|
|
6868
|
-
/* @__PURE__ */
|
|
6869
|
-
/* @__PURE__ */
|
|
6870
|
-
/* @__PURE__ */
|
|
6589
|
+
children: line2 && /* @__PURE__ */ jsxs43(Fragment4, { children: [
|
|
6590
|
+
/* @__PURE__ */ jsxs43("svg", { ...box, children: [
|
|
6591
|
+
/* @__PURE__ */ jsx52("defs", { children: /* @__PURE__ */ jsxs43("linearGradient", { id: `${id}-area`, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
6592
|
+
/* @__PURE__ */ jsx52("stop", { offset: "0", stopColor: "var(--ad-spark)", stopOpacity: "0.45" }),
|
|
6593
|
+
/* @__PURE__ */ jsx52("stop", { offset: "1", stopColor: "var(--ad-spark)", stopOpacity: "0" })
|
|
6871
6594
|
] }) }),
|
|
6872
|
-
/* @__PURE__ */
|
|
6595
|
+
/* @__PURE__ */ jsx52(
|
|
6873
6596
|
"path",
|
|
6874
6597
|
{
|
|
6875
6598
|
className: "ad-sparkline-area",
|
|
@@ -6877,18 +6600,18 @@ var Sparkline = (p) => {
|
|
|
6877
6600
|
fill: `url(#${id}-area)`
|
|
6878
6601
|
}
|
|
6879
6602
|
),
|
|
6880
|
-
/* @__PURE__ */
|
|
6603
|
+
/* @__PURE__ */ jsx52("path", { className: "ad-sparkline-line", d: line2, pathLength: 1 })
|
|
6881
6604
|
] }),
|
|
6882
|
-
/* @__PURE__ */
|
|
6883
|
-
/* @__PURE__ */
|
|
6605
|
+
/* @__PURE__ */ jsx52("svg", { ...box, children: /* @__PURE__ */ jsx52("circle", { className: "ad-sparkline-ping", cx: lastX, cy: lastY, r: "3" }) }),
|
|
6606
|
+
/* @__PURE__ */ jsx52("svg", { ...box, children: /* @__PURE__ */ jsx52("circle", { className: "ad-sparkline-dot", cx: lastX, cy: lastY, r: "3" }) })
|
|
6884
6607
|
] })
|
|
6885
6608
|
}
|
|
6886
6609
|
);
|
|
6887
6610
|
};
|
|
6888
6611
|
|
|
6889
6612
|
// src/components/media/WaveDecoration/WaveDecoration.tsx
|
|
6890
|
-
import { useRef as
|
|
6891
|
-
import { jsx as
|
|
6613
|
+
import { useRef as useRef33 } from "react";
|
|
6614
|
+
import { jsx as jsx53, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
6892
6615
|
var POINTS = 23;
|
|
6893
6616
|
var STEP2 = 65 / (POINTS - 1);
|
|
6894
6617
|
var X = Array.from({ length: POINTS }, (_, k) => k * STEP2 / 65 * 600);
|
|
@@ -6906,8 +6629,8 @@ var strand = (j, t) => {
|
|
|
6906
6629
|
return d;
|
|
6907
6630
|
};
|
|
6908
6631
|
var WaveDecoration = (p) => {
|
|
6909
|
-
const ref =
|
|
6910
|
-
const paths =
|
|
6632
|
+
const ref = useRef33(null);
|
|
6633
|
+
const paths = useRef33(null);
|
|
6911
6634
|
const uid = useSvgId();
|
|
6912
6635
|
const paint = (t) => {
|
|
6913
6636
|
if (!ref.current) return;
|
|
@@ -6916,7 +6639,7 @@ var WaveDecoration = (p) => {
|
|
|
6916
6639
|
};
|
|
6917
6640
|
usePauseOffscreen(ref);
|
|
6918
6641
|
useDecoration(ref, paint);
|
|
6919
|
-
return /* @__PURE__ */
|
|
6642
|
+
return /* @__PURE__ */ jsxs44(
|
|
6920
6643
|
"svg",
|
|
6921
6644
|
{
|
|
6922
6645
|
...mark("WaveDecoration", p),
|
|
@@ -6924,12 +6647,12 @@ var WaveDecoration = (p) => {
|
|
|
6924
6647
|
viewBox: "0 0 600 130",
|
|
6925
6648
|
"aria-hidden": "true",
|
|
6926
6649
|
children: [
|
|
6927
|
-
/* @__PURE__ */
|
|
6650
|
+
/* @__PURE__ */ jsx53("defs", { children: /* @__PURE__ */ jsx53("linearGradient", { id: `wave-${uid}`, children: [
|
|
6928
6651
|
[0, 0],
|
|
6929
6652
|
[0.2, 0.3],
|
|
6930
6653
|
[0.7, 1],
|
|
6931
6654
|
[1, 0.35]
|
|
6932
|
-
].map(([offset, opacity]) => /* @__PURE__ */
|
|
6655
|
+
].map(([offset, opacity]) => /* @__PURE__ */ jsx53(
|
|
6933
6656
|
"stop",
|
|
6934
6657
|
{
|
|
6935
6658
|
offset,
|
|
@@ -6938,7 +6661,7 @@ var WaveDecoration = (p) => {
|
|
|
6938
6661
|
},
|
|
6939
6662
|
offset
|
|
6940
6663
|
)) }) }),
|
|
6941
|
-
Array.from({ length: 22 }, (_, j) => /* @__PURE__ */
|
|
6664
|
+
Array.from({ length: 22 }, (_, j) => /* @__PURE__ */ jsx53(
|
|
6942
6665
|
"path",
|
|
6943
6666
|
{
|
|
6944
6667
|
d: "M0 65H600",
|
|
@@ -7100,6 +6823,7 @@ export {
|
|
|
7100
6823
|
quantumFieldExperienceControls,
|
|
7101
6824
|
quantumFieldModes,
|
|
7102
6825
|
quantumFieldResolution,
|
|
6826
|
+
refreshMotion,
|
|
7103
6827
|
resizeEdges,
|
|
7104
6828
|
resolveToken,
|
|
7105
6829
|
resolveTokens,
|