@5even7/dlc-ui 0.2.18 → 0.2.20
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/CHANGELOG.md +158 -144
- package/EXTENDING.md +114 -0
- package/LICENSE +21 -21
- package/README.md +389 -338
- package/THIRD_PARTY_NOTICES.md +221 -0
- package/dist/avatar-creator/styles/humation.mjs +26 -0
- package/dist/avatar-creator/styles.mjs +76 -0
- package/dist/avatar-creator.cjs +7831 -0
- package/dist/avatar-creator.mjs +74 -0
- package/dist/capsule.cjs +185 -66
- package/dist/capsule.mjs +364 -1340
- package/dist/chunks/avatar-creator-DiAmhpRa.mjs +687 -0
- package/dist/chunks/color-DnmNceaf.mjs +350 -0
- package/dist/chunks/controls-B5_ctvvA.mjs +160 -0
- package/dist/chunks/copy-7J0XIj_0.mjs +13 -0
- package/dist/chunks/cosmic-fallback-v8G5_ibh.mjs +408 -0
- package/dist/chunks/emitter-CxLYMSbw.mjs +38 -0
- package/dist/chunks/emo-Dvahy5Qr.mjs +1140 -0
- package/dist/chunks/hash-BiO02UNb.mjs +38 -0
- package/dist/chunks/humation-B9OPUGL7.mjs +2987 -0
- package/dist/chunks/identicon-BUdMd-yf.mjs +3063 -0
- package/dist/chunks/index-BchW1tWE.mjs +478 -0
- package/dist/chunks/index-BtH8Lbra.mjs +296 -0
- package/dist/chunks/index-C02dvGLD.mjs +263 -0
- package/dist/chunks/index-CFybYBA1.mjs +278 -0
- package/dist/chunks/index-DgI872-7.mjs +241 -0
- package/dist/chunks/index-_sf_14Zu.mjs +455 -0
- package/dist/chunks/index-w5Pzdwhv.mjs +305 -0
- package/dist/chunks/motion-a8_f8_Ui.mjs +177 -0
- package/dist/chunks/number-BqWLX0iQ.mjs +231 -0
- package/dist/chunks/progress-KweS4Rx0.mjs +1836 -0
- package/dist/chunks/size-Bztk2ryJ.mjs +22 -0
- package/dist/chunks/wrapper-DXhy1E7Z.mjs +514 -0
- package/dist/color.cjs +186 -67
- package/dist/color.mjs +341 -1280
- package/dist/emo/miao/angry.mjs +4 -0
- package/dist/emo/miao/asleep.mjs +4 -0
- package/dist/emo/miao/badminton.mjs +4 -0
- package/dist/emo/miao/confident.mjs +4 -0
- package/dist/emo/miao/cry.mjs +4 -0
- package/dist/emo/miao/investigate.mjs +4 -0
- package/dist/emo/miao/laugh.mjs +4 -0
- package/dist/emo/miao/leisure.mjs +4 -0
- package/dist/emo/miao/mock.mjs +4 -0
- package/dist/emo/miao/music.mjs +4 -0
- package/dist/emo/miao/mute.mjs +4 -0
- package/dist/emo/miao/panic.mjs +4 -0
- package/dist/emo/miao/ponder.mjs +4 -0
- package/dist/emo/miao/question.mjs +4 -0
- package/dist/emo/miao/sad.mjs +4 -0
- package/dist/emo/miao/shocked.mjs +4 -0
- package/dist/emo/miao/shy.mjs +4 -0
- package/dist/emo/miao/sigh.mjs +4 -0
- package/dist/emo/miao/smile.mjs +4 -0
- package/dist/emo/miao/snigger.mjs +4 -0
- package/dist/emo/miao/static.mjs +4 -0
- package/dist/emo/miao/yawn.mjs +4 -0
- package/dist/emo/miao/yummy.mjs +4 -0
- package/dist/emo.cjs +27161 -25964
- package/dist/emo.mjs +8 -1421
- package/dist/index.cjs +32729 -24466
- package/dist/index.mjs +16 -4757
- package/dist/index.umd.js +33115 -24852
- package/dist/index.umd.min.js +3 -1
- package/dist/progress.cjs +195 -76
- package/dist/progress.mjs +7 -2448
- package/dist/vue2.cjs +33282 -25002
- package/dist/vue2.mjs +63 -5131
- package/dist/vue3.cjs +33282 -25002
- package/dist/vue3.mjs +53 -5122
- package/package.json +161 -136
- package/styles/avatar-creator.css +38 -0
- package/styles/base.css +32 -32
- package/styles/color.css +18 -18
- package/styles/emo.css +110 -89
- package/styles/progress.css +96 -96
- package/styles/theme.css +1 -0
- package/types/avatar-creator-styles.d.ts +10 -0
- package/types/avatar-creator.d.ts +143 -0
- package/types/capsule.d.ts +15 -15
- package/types/color.d.ts +15 -15
- package/types/emo-miao.d.ts +9 -0
- package/types/emo.d.ts +38 -37
- package/types/index.d.ts +593 -554
- package/types/progress.d.ts +16 -16
- package/types/vue2.d.ts +11 -10
- package/types/vue3.d.ts +169 -146
package/dist/capsule.mjs
CHANGED
|
@@ -1,1366 +1,390 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const listeners = Object.create(null);
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
on(event, fn) {
|
|
12
|
-
if (!listeners[event]) listeners[event] = [];
|
|
13
|
-
listeners[event].push(fn);
|
|
14
|
-
return () => {
|
|
15
|
-
const current = listeners[event];
|
|
16
|
-
if (current) {
|
|
17
|
-
const index = current.indexOf(fn);
|
|
18
|
-
if (index !== -1) current.splice(index, 1);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
},
|
|
22
|
-
off(event, fn) {
|
|
23
|
-
const current = listeners[event];
|
|
24
|
-
if (!current) return false;
|
|
25
|
-
const index = current.indexOf(fn);
|
|
26
|
-
if (index === -1) return false;
|
|
27
|
-
current.splice(index, 1);
|
|
28
|
-
return true;
|
|
29
|
-
},
|
|
30
|
-
emit(event, ...args) {
|
|
31
|
-
const current = listeners[event];
|
|
32
|
-
if (!current) return;
|
|
33
|
-
for (const fn of current.slice()) fn(...args);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const UNIT_PATTERN = /^[\d.]+(?:px|%|vw|vh|vmin|vmax|em|rem)$/;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Normalize a size option to a CSS length string.
|
|
42
|
-
* Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
|
|
43
|
-
*/
|
|
44
|
-
function parseSize(value) {
|
|
45
|
-
if (typeof value === 'number') {
|
|
46
|
-
if (!Number.isFinite(value) || value < 0) throw new Error(`Invalid size: ${value}`);
|
|
47
|
-
return `${value}px`;
|
|
48
|
-
}
|
|
49
|
-
if (typeof value !== 'string') throw new Error(`Invalid size: ${String(value)}`);
|
|
50
|
-
const trimmed = value.trim();
|
|
51
|
-
if (!trimmed) throw new Error('Invalid size: empty string');
|
|
52
|
-
if (/^\d+(?:\.\d+)?$/.test(trimmed)) return `${trimmed}px`;
|
|
53
|
-
if (!UNIT_PATTERN.test(trimmed)) {
|
|
54
|
-
throw new Error(`Invalid size or unsupported unit: "${value}" (use px, %, vw, vh, vmin, vmax, em, rem)`);
|
|
55
|
-
}
|
|
56
|
-
return trimmed;
|
|
57
|
-
}
|
|
1
|
+
import { c as createEmitter } from './chunks/emitter-CxLYMSbw.mjs';
|
|
2
|
+
import { p as parseSize } from './chunks/size-Bztk2ryJ.mjs';
|
|
3
|
+
import { g as getPreset, n as normalizeOptions, D as DEFAULTS, t as toFiniteNumber, e as effectiveDprCap, b as nextTick } from './chunks/number-BqWLX0iQ.mjs';
|
|
4
|
+
import { C as COPY } from './chunks/copy-7J0XIj_0.mjs';
|
|
5
|
+
import { C as CosmicRenderer, F as FallbackRenderer } from './chunks/cosmic-fallback-v8G5_ibh.mjs';
|
|
6
|
+
import { w as wakeScheduler, c as createFrameGate, a as createVisibilityGuard, b as createReducedMotionPreference, s as subscribeScheduler } from './chunks/motion-a8_f8_Ui.mjs';
|
|
7
|
+
import { n as normalizeColor } from './chunks/color-DnmNceaf.mjs';
|
|
58
8
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (quality === 'auto') return autoDprCap();
|
|
74
|
-
const tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
|
|
75
|
-
return tier.dpr;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function effectiveDprCap(quality, renderScale = 1) {
|
|
79
|
-
const scale = Number(renderScale);
|
|
80
|
-
const normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
|
|
81
|
-
return Math.max(0.5, dprCapFor(quality) * normalizedScale);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Merge defaults < preset < user. Unknown user keys are preserved so the
|
|
86
|
-
* component API can grow (new props, cssVars, callbacks) without a breaking
|
|
87
|
-
* change.
|
|
88
|
-
*/
|
|
89
|
-
function normalizeOptions(defaults, preset, user) {
|
|
90
|
-
// undefined means "not provided" (e.g. Vue $props with unset props):
|
|
91
|
-
// drop those keys before merging so defaults/preset values survive.
|
|
92
|
-
const omitUndefined = (source) => {
|
|
93
|
-
const result = {};
|
|
94
|
-
for (const key of Object.keys(source)) {
|
|
95
|
-
if (source[key] !== undefined) result[key] = source[key];
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
};
|
|
99
|
-
const presetOptions = omitUndefined(preset && typeof preset === 'object' ? preset : {});
|
|
100
|
-
const userOptions = omitUndefined(user && typeof user === 'object' ? user : {});
|
|
101
|
-
return { ...defaults, ...presetOptions, ...userOptions };
|
|
102
|
-
}
|
|
9
|
+
/**
|
|
10
|
+
* Mount a cosmic (nebula) capsule into `container`.
|
|
11
|
+
*
|
|
12
|
+
* Options: preset (literary name), width, height (number=px or string with
|
|
13
|
+
* px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
|
|
14
|
+
* width in percent), text (HTML string or DOM nodes for the text slot),
|
|
15
|
+
* colorContent (HTML string or DOM nodes for the color slot), quality,
|
|
16
|
+
* renderer, quality, renderScale, powerPreference, fps, paused/static,
|
|
17
|
+
* respectReducedMotion, mouseColor, cssVars.
|
|
18
|
+
*/
|
|
19
|
+
function createCapsule(container, options = {}) {
|
|
20
|
+
if (!container || typeof container.appendChild !== 'function') {
|
|
21
|
+
throw new Error('createCapsule: container element is required');
|
|
22
|
+
}
|
|
103
23
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
24
|
+
const preset = { ...getPreset('capsule', options.preset ?? '初光') };
|
|
25
|
+
const merged = normalizeOptions(DEFAULTS.capsule, preset, options);
|
|
26
|
+
const normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
|
|
27
|
+
if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
|
|
28
|
+
const initialSeed = toFiniteNumber(merged.seed);
|
|
29
|
+
const initialSpeed = toFiniteNumber(merged.speed);
|
|
30
|
+
if (initialSeed !== null) preset.seed = initialSeed;
|
|
31
|
+
if (initialSpeed !== null) preset.speed = initialSpeed;
|
|
32
|
+
merged.colors = [...preset.colors];
|
|
33
|
+
merged.seed = preset.seed;
|
|
34
|
+
merged.speed = preset.speed;
|
|
35
|
+
const optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
|
|
36
|
+
let colorOverride = optionColors.length === 4 && optionColors.every(Boolean)
|
|
37
|
+
? [...optionColors]
|
|
38
|
+
: null;
|
|
39
|
+
let seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
|
|
40
|
+
let speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
|
|
41
|
+
let customLabel = typeof options.label === 'string' && options.label ? options.label : null;
|
|
116
42
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{ id: 'plus', code: 'NC-06', name: '熔金', group: 'warm', seed: 51.3, speed: 0.5, colors: [...PALETTES.plus] }
|
|
128
|
-
];
|
|
43
|
+
const root = document.createElement('div');
|
|
44
|
+
root.className = 'hj-capsule-root hj-capsule-cosmic';
|
|
45
|
+
root.dataset.group = preset.group;
|
|
46
|
+
root.dataset.mode = 'nebula';
|
|
47
|
+
root.dataset.theme = preset.theme || 'light';
|
|
48
|
+
root.setAttribute('role', 'img');
|
|
49
|
+
const updateAria = () => {
|
|
50
|
+
root.setAttribute('aria-label', customLabel || COPY.capsuleAria.replace('{name}', preset.name));
|
|
51
|
+
};
|
|
52
|
+
updateAria();
|
|
129
53
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
for (let index = 0; index < list.length; index += 1) {
|
|
141
|
-
if (list[index].name.toLowerCase() === key) {
|
|
142
|
-
found = list[index];
|
|
143
|
-
break;
|
|
54
|
+
// Slot containers are always present but transparent by default: without
|
|
55
|
+
// content they are invisible, so "no slot" behaves like the old
|
|
56
|
+
// showCopy=false. They only provide geometry — no typography, padding or
|
|
57
|
+
// background is imposed on user content (see docs: 插槽 CSS 约定).
|
|
58
|
+
const fillContent = (layer, content) => {
|
|
59
|
+
layer.innerHTML = '';
|
|
60
|
+
if (content == null) return;
|
|
61
|
+
if (typeof content === 'string') {
|
|
62
|
+
layer.innerHTML = content;
|
|
63
|
+
return;
|
|
144
64
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
65
|
+
const nodes = Array.isArray(content) ? content : [content];
|
|
66
|
+
for (const node of nodes) {
|
|
67
|
+
if (node && typeof node.nodeType === 'number') layer.appendChild(node);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
149
70
|
|
|
150
|
-
const
|
|
151
|
-
capsule
|
|
152
|
-
width: '100%',
|
|
153
|
-
height: 160,
|
|
154
|
-
quality: 'auto',
|
|
155
|
-
renderer: 'auto',
|
|
156
|
-
renderScale: 1,
|
|
157
|
-
powerPreference: 'high-performance',
|
|
158
|
-
fps: 60,
|
|
159
|
-
paused: false,
|
|
160
|
-
static: false,
|
|
161
|
-
respectReducedMotion: true,
|
|
162
|
-
mouseColor: true,
|
|
163
|
-
textRatio: 39
|
|
164
|
-
}};
|
|
71
|
+
const textLayer = document.createElement('div');
|
|
72
|
+
textLayer.className = 'hj-capsule-text hj-capsule-copy';
|
|
165
73
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* removed (slots own the text area), these are no longer user-overridable.
|
|
169
|
-
* Templates use {brand} {code} {name} placeholders.
|
|
170
|
-
*/
|
|
171
|
-
const COPY = {
|
|
172
|
-
capsuleAria: '打开 {name} 沉浸预览'
|
|
173
|
-
};
|
|
74
|
+
const visualLayer = document.createElement('div');
|
|
75
|
+
visualLayer.className = 'hj-capsule-visual';
|
|
174
76
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
* (hex / named / rgb() / rgba()) into a 6-digit hex string, so renderers can
|
|
178
|
-
* keep working with #rrggbb only. rgba() alpha is intentionally dropped:
|
|
179
|
-
* the WebGL shader has no per-color alpha channel, and the component is
|
|
180
|
-
* opaque by design — use component-level `opacity` for transparency.
|
|
181
|
-
*/
|
|
77
|
+
fillContent(textLayer, options.text);
|
|
78
|
+
fillContent(visualLayer, options.colorContent);
|
|
182
79
|
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
aqua: '#00ffff',
|
|
187
|
-
aquamarine: '#7fffd4',
|
|
188
|
-
azure: '#f0ffff',
|
|
189
|
-
beige: '#f5f5dc',
|
|
190
|
-
bisque: '#ffe4c4',
|
|
191
|
-
black: '#000000',
|
|
192
|
-
blanchedalmond: '#ffebcd',
|
|
193
|
-
blue: '#0000ff',
|
|
194
|
-
blueviolet: '#8a2be2',
|
|
195
|
-
brown: '#a52a2a',
|
|
196
|
-
burlywood: '#deb887',
|
|
197
|
-
cadetblue: '#5f9ea0',
|
|
198
|
-
chartreuse: '#7fff00',
|
|
199
|
-
chocolate: '#d2691e',
|
|
200
|
-
coral: '#ff7f50',
|
|
201
|
-
cornflowerblue: '#6495ed',
|
|
202
|
-
cornsilk: '#fff8dc',
|
|
203
|
-
crimson: '#dc143c',
|
|
204
|
-
cyan: '#00ffff',
|
|
205
|
-
darkblue: '#00008b',
|
|
206
|
-
darkcyan: '#008b8b',
|
|
207
|
-
darkgoldenrod: '#b8860b',
|
|
208
|
-
darkgray: '#a9a9a9',
|
|
209
|
-
darkgreen: '#006400',
|
|
210
|
-
darkgrey: '#a9a9a9',
|
|
211
|
-
darkkhaki: '#bdb76b',
|
|
212
|
-
darkmagenta: '#8b008b',
|
|
213
|
-
darkolivegreen: '#556b2f',
|
|
214
|
-
darkorange: '#ff8c00',
|
|
215
|
-
darkorchid: '#9932cc',
|
|
216
|
-
darkred: '#8b0000',
|
|
217
|
-
darksalmon: '#e9967a',
|
|
218
|
-
darkseagreen: '#8fbc8f',
|
|
219
|
-
darkslateblue: '#483d8b',
|
|
220
|
-
darkslategray: '#2f4f4f',
|
|
221
|
-
darkslategrey: '#2f4f4f',
|
|
222
|
-
darkturquoise: '#00ced1',
|
|
223
|
-
darkviolet: '#9400d3',
|
|
224
|
-
deeppink: '#ff1493',
|
|
225
|
-
deepskyblue: '#00bfff',
|
|
226
|
-
dimgray: '#696969',
|
|
227
|
-
dimgrey: '#696969',
|
|
228
|
-
dodgerblue: '#1e90ff',
|
|
229
|
-
firebrick: '#b22222',
|
|
230
|
-
floralwhite: '#fffaf0',
|
|
231
|
-
forestgreen: '#228b22',
|
|
232
|
-
fuchsia: '#ff00ff',
|
|
233
|
-
gainsboro: '#dcdcdc',
|
|
234
|
-
ghostwhite: '#f8f8ff',
|
|
235
|
-
gold: '#ffd700',
|
|
236
|
-
goldenrod: '#daa520',
|
|
237
|
-
gray: '#808080',
|
|
238
|
-
green: '#008000',
|
|
239
|
-
greenyellow: '#adff2f',
|
|
240
|
-
grey: '#808080',
|
|
241
|
-
honeydew: '#f0fff0',
|
|
242
|
-
hotpink: '#ff69b4',
|
|
243
|
-
indianred: '#cd5c5c',
|
|
244
|
-
indigo: '#4b0082',
|
|
245
|
-
ivory: '#fffff0',
|
|
246
|
-
khaki: '#f0e68c',
|
|
247
|
-
lavender: '#e6e6fa',
|
|
248
|
-
lavenderblush: '#fff0f5',
|
|
249
|
-
lawngreen: '#7cfc00',
|
|
250
|
-
lemonchiffon: '#fffacd',
|
|
251
|
-
lightblue: '#add8e6',
|
|
252
|
-
lightcoral: '#f08080',
|
|
253
|
-
lightcyan: '#e0ffff',
|
|
254
|
-
lightgoldenrodyellow: '#fafad2',
|
|
255
|
-
lightgray: '#d3d3d3',
|
|
256
|
-
lightgreen: '#90ee90',
|
|
257
|
-
lightgrey: '#d3d3d3',
|
|
258
|
-
lightpink: '#ffb6c1',
|
|
259
|
-
lightsalmon: '#ffa07a',
|
|
260
|
-
lightseagreen: '#20b2aa',
|
|
261
|
-
lightskyblue: '#87cefa',
|
|
262
|
-
lightslategray: '#778899',
|
|
263
|
-
lightslategrey: '#778899',
|
|
264
|
-
lightsteelblue: '#b0c4de',
|
|
265
|
-
lightyellow: '#ffffe0',
|
|
266
|
-
lime: '#00ff00',
|
|
267
|
-
limegreen: '#32cd32',
|
|
268
|
-
linen: '#faf0e6',
|
|
269
|
-
magenta: '#ff00ff',
|
|
270
|
-
maroon: '#800000',
|
|
271
|
-
mediumaquamarine: '#66cdaa',
|
|
272
|
-
mediumblue: '#0000cd',
|
|
273
|
-
mediumorchid: '#ba55d3',
|
|
274
|
-
mediumpurple: '#9370db',
|
|
275
|
-
mediumseagreen: '#3cb371',
|
|
276
|
-
mediumslateblue: '#7b68ee',
|
|
277
|
-
mediumspringgreen: '#00fa9a',
|
|
278
|
-
mediumturquoise: '#48d1cc',
|
|
279
|
-
mediumvioletred: '#c71585',
|
|
280
|
-
midnightblue: '#191970',
|
|
281
|
-
mintcream: '#f5fffa',
|
|
282
|
-
mistyrose: '#ffe4e1',
|
|
283
|
-
moccasin: '#ffe4b5',
|
|
284
|
-
navajowhite: '#ffdead',
|
|
285
|
-
navy: '#000080',
|
|
286
|
-
oldlace: '#fdf5e6',
|
|
287
|
-
olive: '#808000',
|
|
288
|
-
olivedrab: '#6b8e23',
|
|
289
|
-
orange: '#ffa500',
|
|
290
|
-
orangered: '#ff4500',
|
|
291
|
-
orchid: '#da70d6',
|
|
292
|
-
palegoldenrod: '#eee8aa',
|
|
293
|
-
palegreen: '#98fb98',
|
|
294
|
-
paleturquoise: '#afeeee',
|
|
295
|
-
palevioletred: '#db7093',
|
|
296
|
-
papayawhip: '#ffefd5',
|
|
297
|
-
peachpuff: '#ffdab9',
|
|
298
|
-
peru: '#cd853f',
|
|
299
|
-
pink: '#ffc0cb',
|
|
300
|
-
plum: '#dda0dd',
|
|
301
|
-
powderblue: '#b0e0e6',
|
|
302
|
-
purple: '#800080',
|
|
303
|
-
rebeccapurple: '#663399',
|
|
304
|
-
red: '#ff0000',
|
|
305
|
-
rosybrown: '#bc8f8f',
|
|
306
|
-
royalblue: '#4169e1',
|
|
307
|
-
saddlebrown: '#8b4513',
|
|
308
|
-
salmon: '#fa8072',
|
|
309
|
-
sandybrown: '#f4a460',
|
|
310
|
-
seagreen: '#2e8b57',
|
|
311
|
-
seashell: '#fff5ee',
|
|
312
|
-
sienna: '#a0522d',
|
|
313
|
-
silver: '#c0c0c0',
|
|
314
|
-
skyblue: '#87ceeb',
|
|
315
|
-
slateblue: '#6a5acd',
|
|
316
|
-
slategray: '#708090',
|
|
317
|
-
slategrey: '#708090',
|
|
318
|
-
snow: '#fffafa',
|
|
319
|
-
springgreen: '#00ff7f',
|
|
320
|
-
steelblue: '#4682b4',
|
|
321
|
-
tan: '#d2b48c',
|
|
322
|
-
teal: '#008080',
|
|
323
|
-
thistle: '#d8bfd8',
|
|
324
|
-
tomato: '#ff6347',
|
|
325
|
-
turquoise: '#40e0d0',
|
|
326
|
-
violet: '#ee82ee',
|
|
327
|
-
wheat: '#f5deb3',
|
|
328
|
-
white: '#ffffff',
|
|
329
|
-
whitesmoke: '#f5f5f5',
|
|
330
|
-
yellow: '#ffff00',
|
|
331
|
-
yellowgreen: '#9acd32'
|
|
332
|
-
};
|
|
80
|
+
const canvas = document.createElement('canvas');
|
|
81
|
+
canvas.className = 'hj-capsule-canvas';
|
|
82
|
+
canvas.setAttribute('aria-hidden', 'true');
|
|
333
83
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
84
|
+
root.appendChild(textLayer);
|
|
85
|
+
root.appendChild(visualLayer);
|
|
86
|
+
root.appendChild(canvas);
|
|
87
|
+
container.appendChild(root);
|
|
337
88
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
89
|
+
const emitter = createEmitter();
|
|
90
|
+
let manuallyPaused = merged.paused === true;
|
|
91
|
+
let reducedPaused = false;
|
|
92
|
+
let staticMode = merged.static === true;
|
|
93
|
+
let contextLost = false;
|
|
94
|
+
let disposed = false;
|
|
95
|
+
let renderOnce = () => {};
|
|
96
|
+
const dirty = {
|
|
97
|
+
preset: false,
|
|
98
|
+
seed: false,
|
|
99
|
+
speed: false,
|
|
100
|
+
colors: false,
|
|
101
|
+
textRatio: false,
|
|
102
|
+
cssVars: false,
|
|
103
|
+
mouseColor: false,
|
|
104
|
+
quality: false,
|
|
105
|
+
renderScale: false,
|
|
106
|
+
paused: false,
|
|
107
|
+
static: false,
|
|
108
|
+
fps: false
|
|
346
109
|
};
|
|
347
|
-
const red = to255(parts[0]);
|
|
348
|
-
const green = to255(parts[1]);
|
|
349
|
-
const blue = to255(parts[2]);
|
|
350
|
-
if (red === null || green === null || blue === null) return null;
|
|
351
|
-
const hex = (n) => {
|
|
352
|
-
const text = n.toString(16);
|
|
353
|
-
return text.length === 1 ? `0${text}` : text;
|
|
354
|
-
};
|
|
355
|
-
return `#${hex(red)}${hex(green)}${hex(blue)}`;
|
|
356
|
-
}
|
|
357
110
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
111
|
+
let renderer;
|
|
112
|
+
const useWebgl = merged.renderer !== 'canvas2d';
|
|
113
|
+
if (useWebgl) {
|
|
114
|
+
try {
|
|
115
|
+
renderer = new CosmicRenderer(canvas, merged, {
|
|
116
|
+
dprCap: effectiveDprCap(merged.quality, merged.renderScale),
|
|
117
|
+
mouseColor: merged.mouseColor !== false,
|
|
118
|
+
powerPreference: merged.powerPreference,
|
|
119
|
+
onContextLost: () => {
|
|
120
|
+
contextLost = true;
|
|
121
|
+
emitter.emit('contextlost', {});
|
|
122
|
+
},
|
|
123
|
+
onContextRestored: () => {
|
|
124
|
+
contextLost = false;
|
|
125
|
+
emitter.emit('contextrestored', {});
|
|
126
|
+
renderOnce();
|
|
127
|
+
wakeScheduler();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
renderer = new FallbackRenderer(canvas, merged, {
|
|
132
|
+
dprCap: effectiveDprCap(merged.quality, merged.renderScale)
|
|
133
|
+
});
|
|
134
|
+
nextTick(() => {
|
|
135
|
+
if (disposed) return;
|
|
136
|
+
emitter.emit('error', { message: String(error && error.message ? error.message : error) });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
renderer = new FallbackRenderer(canvas, merged, {
|
|
141
|
+
dprCap: effectiveDprCap(merged.quality, merged.renderScale)
|
|
142
|
+
});
|
|
374
143
|
}
|
|
375
|
-
const match = input.match(/^rgba?\((.*)\)$/i);
|
|
376
|
-
if (match) return normalizeRgbArgs(match[1]);
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
144
|
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
const value = Number.parseInt(hex.slice(1), 16);
|
|
384
|
-
return [
|
|
385
|
-
((value >> 16) & 255) / 255,
|
|
386
|
-
((value >> 8) & 255) / 255,
|
|
387
|
-
(value & 255) / 255
|
|
388
|
-
];
|
|
389
|
-
}
|
|
145
|
+
let animationTime = 0;
|
|
146
|
+
const frameGate = createFrameGate(merged.fps);
|
|
147
|
+
renderOnce = () => renderer.draw(animationTime);
|
|
390
148
|
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
uniform vec2 u_resolution;
|
|
406
|
-
uniform float u_time;
|
|
407
|
-
uniform float u_seed;
|
|
408
|
-
uniform float u_motion;
|
|
409
|
-
uniform vec2 u_pointer;
|
|
410
|
-
uniform vec3 u_colorA;
|
|
411
|
-
uniform vec3 u_colorB;
|
|
412
|
-
uniform vec3 u_colorC;
|
|
413
|
-
uniform vec3 u_colorD;
|
|
414
|
-
|
|
415
|
-
float hash21(vec2 p) {
|
|
416
|
-
p = fract(p * vec2(123.34, 456.21));
|
|
417
|
-
p += dot(p, p + 45.32 + u_seed);
|
|
418
|
-
return fract(p.x * p.y);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
float noise(vec2 p) {
|
|
422
|
-
vec2 i = floor(p);
|
|
423
|
-
vec2 f = fract(p);
|
|
424
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
425
|
-
float a = hash21(i);
|
|
426
|
-
float b = hash21(i + vec2(1.0, 0.0));
|
|
427
|
-
float c = hash21(i + vec2(0.0, 1.0));
|
|
428
|
-
float d = hash21(i + vec2(1.0, 1.0));
|
|
429
|
-
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
float fbm(vec2 p) {
|
|
433
|
-
float value = 0.0;
|
|
434
|
-
float amplitude = 0.52;
|
|
435
|
-
mat2 rotation = mat2(0.80, 0.60, -0.60, 0.80);
|
|
436
|
-
for (int i = 0; i < 6; i++) {
|
|
437
|
-
value += amplitude * noise(p);
|
|
438
|
-
p = rotation * p * 2.03 + 17.7;
|
|
439
|
-
amplitude *= 0.5;
|
|
440
|
-
}
|
|
441
|
-
return value;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
float gaussian(float value, float center, float width) {
|
|
445
|
-
return exp(-pow(value - center, 2.0) / max(width, 0.0001));
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
vec3 palette(float t) {
|
|
449
|
-
t = clamp(t, 0.0, 1.0);
|
|
450
|
-
vec3 shadow = mix(u_colorA, u_colorB, smoothstep(0.06, 0.62, t));
|
|
451
|
-
vec3 body = mix(u_colorB, u_colorC, smoothstep(0.30, 0.82, t));
|
|
452
|
-
vec3 highlight = mix(u_colorC, u_colorD, smoothstep(0.74, 1.0, t));
|
|
453
|
-
vec3 restrained = mix(shadow, body, smoothstep(0.26, 0.72, t));
|
|
454
|
-
return mix(restrained, highlight, smoothstep(0.78, 0.97, t));
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
vec3 renderNebula(vec2 uv, vec2 p, vec2 pointer, float distanceToPointer, float t) {
|
|
458
|
-
vec2 delta = p - pointer;
|
|
459
|
-
float influence = exp(-distanceToPointer * 4.6) * u_motion;
|
|
460
|
-
float angle = influence * 1.7;
|
|
461
|
-
mat2 swirl = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
|
|
462
|
-
p = pointer + swirl * delta;
|
|
463
|
-
p += normalize(delta + 0.0001) * influence * 0.08;
|
|
464
|
-
|
|
465
|
-
vec2 drift = vec2(t * 0.22, -t * 0.13);
|
|
466
|
-
vec2 q = vec2(
|
|
467
|
-
fbm(p * 1.35 + drift + u_seed),
|
|
468
|
-
fbm(p * 1.35 + vec2(5.2, 1.3) - drift * 0.85)
|
|
469
|
-
);
|
|
470
|
-
vec2 r = vec2(
|
|
471
|
-
fbm(p * 2.0 + 3.6 * q + vec2(1.7, 9.2) + t * 0.10),
|
|
472
|
-
fbm(p * 2.0 + 3.0 * q + vec2(8.3, 2.8) - t * 0.085)
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
float cloud = fbm(p * 1.7 + 4.2 * r);
|
|
476
|
-
float veins = fbm(p * 4.0 - 2.0 * q + t * 0.065);
|
|
477
|
-
float nebula = smoothstep(0.18, 0.91, cloud * 0.9 + veins * 0.22);
|
|
478
|
-
|
|
479
|
-
vec3 color = palette(nebula);
|
|
480
|
-
color += u_colorD * pow(max(cloud - 0.63, 0.0), 2.0) * 1.05;
|
|
481
|
-
color *= 0.78 + 0.34 * smoothstep(0.15, 0.9, veins);
|
|
482
|
-
|
|
483
|
-
vec2 starGrid = floor((uv + vec2(u_seed * 0.013, 0.0)) * vec2(132.0, 58.0));
|
|
484
|
-
vec2 starCell = fract(uv * vec2(132.0, 58.0)) - 0.5;
|
|
485
|
-
float starRandom = hash21(starGrid);
|
|
486
|
-
float starShape = smoothstep(0.075, 0.0, length(starCell));
|
|
487
|
-
float starMask = step(0.989, starRandom) * starShape;
|
|
488
|
-
float twinkle = 0.35 + 0.65 * sin(t * (1.0 + starRandom * 2.4) + starRandom * 40.0) * 0.5 + 0.5;
|
|
489
|
-
color += starMask * twinkle * mix(u_colorC, u_colorD, starRandom) * 1.05;
|
|
490
|
-
|
|
491
|
-
float pointerGlow = exp(-distanceToPointer * 7.0) * u_motion;
|
|
492
|
-
color += u_colorD * pointerGlow * 0.28;
|
|
493
|
-
return color;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
void main() {
|
|
497
|
-
vec2 uv = v_uv;
|
|
498
|
-
vec2 p = uv - 0.5;
|
|
499
|
-
p.x *= u_resolution.x / max(u_resolution.y, 1.0);
|
|
500
|
-
|
|
501
|
-
vec2 pointer = u_pointer - 0.5;
|
|
502
|
-
pointer.x *= u_resolution.x / max(u_resolution.y, 1.0);
|
|
503
|
-
float distanceToPointer = length(p - pointer);
|
|
504
|
-
|
|
505
|
-
vec3 color = renderNebula(uv, p, pointer, distanceToPointer, u_time);
|
|
506
|
-
float vignette = smoothstep(0.94, 0.18, length((uv - 0.5) * vec2(1.0, 1.35)));
|
|
507
|
-
color *= 0.70 + vignette * 0.42;
|
|
508
|
-
color = pow(max(color, vec3(0.0)), vec3(0.88));
|
|
509
|
-
|
|
510
|
-
outColor = vec4(color, 1.0);
|
|
511
|
-
}`;
|
|
512
|
-
|
|
513
|
-
function compileShader(gl, type, source) {
|
|
514
|
-
const shader = gl.createShader(type);
|
|
515
|
-
gl.shaderSource(shader, source);
|
|
516
|
-
gl.compileShader(shader);
|
|
517
|
-
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
518
|
-
const message = gl.getShaderInfoLog(shader) || 'Unknown shader compile error';
|
|
519
|
-
gl.deleteShader(shader);
|
|
520
|
-
throw new Error(message);
|
|
521
|
-
}
|
|
522
|
-
return shader;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function createProgram(gl) {
|
|
526
|
-
const vertex = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
527
|
-
const fragment = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
|
|
528
|
-
const program = gl.createProgram();
|
|
529
|
-
gl.attachShader(program, vertex);
|
|
530
|
-
gl.attachShader(program, fragment);
|
|
531
|
-
gl.linkProgram(program);
|
|
532
|
-
gl.deleteShader(vertex);
|
|
533
|
-
gl.deleteShader(fragment);
|
|
534
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
535
|
-
const message = gl.getProgramInfoLog(program) || 'Unknown program link error';
|
|
536
|
-
gl.deleteProgram(program);
|
|
537
|
-
throw new Error(message);
|
|
538
|
-
}
|
|
539
|
-
return program;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
class CosmicRenderer {
|
|
543
|
-
constructor(canvas, preset, options = {}) {
|
|
544
|
-
this.canvas = canvas;
|
|
545
|
-
this.preset = { ...preset };
|
|
546
|
-
this.options = { dprCap: 1.75, mouseColor: true, ...options };
|
|
547
|
-
this.gl = canvas.getContext('webgl2', {
|
|
548
|
-
alpha: false,
|
|
549
|
-
antialias: false,
|
|
550
|
-
depth: false,
|
|
551
|
-
powerPreference: this.options.powerPreference || 'high-performance',
|
|
552
|
-
preserveDrawingBuffer: false
|
|
553
|
-
});
|
|
554
|
-
if (!this.gl) throw new Error('WebGL2 is not available');
|
|
555
|
-
|
|
556
|
-
this.program = createProgram(this.gl);
|
|
557
|
-
this.locations = this.#getLocations();
|
|
558
|
-
this.pointer = [0.72, 0.45];
|
|
559
|
-
this.pointerTarget = [...this.pointer];
|
|
560
|
-
this.motion = 0;
|
|
561
|
-
this.motionTarget = 0;
|
|
562
|
-
this.timeOffset = preset.seed * 0.73;
|
|
563
|
-
this.colors = preset.colors.map(hexToRgb01);
|
|
564
|
-
this.visible = true;
|
|
565
|
-
this.disposed = false;
|
|
566
|
-
|
|
567
|
-
this.#setupGeometry();
|
|
568
|
-
this.#bindEvents();
|
|
569
|
-
this.#bindContextEvents();
|
|
570
|
-
this.resize();
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
#getLocations() {
|
|
574
|
-
const gl = this.gl;
|
|
575
|
-
const uniform = (name) => gl.getUniformLocation(this.program, name);
|
|
576
|
-
return {
|
|
577
|
-
position: gl.getAttribLocation(this.program, 'a_position'),
|
|
578
|
-
resolution: uniform('u_resolution'),
|
|
579
|
-
time: uniform('u_time'),
|
|
580
|
-
seed: uniform('u_seed'),
|
|
581
|
-
motion: uniform('u_motion'),
|
|
582
|
-
pointer: uniform('u_pointer'),
|
|
583
|
-
colorA: uniform('u_colorA'),
|
|
584
|
-
colorB: uniform('u_colorB'),
|
|
585
|
-
colorC: uniform('u_colorC'),
|
|
586
|
-
colorD: uniform('u_colorD')
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
#setupGeometry() {
|
|
591
|
-
const gl = this.gl;
|
|
592
|
-
const vertices = new Float32Array([-1, -1, 3, -1, -1, 3]);
|
|
593
|
-
this.buffer = gl.createBuffer();
|
|
594
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
595
|
-
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
#bindEvents() {
|
|
599
|
-
if (this.options.mouseColor === false) return;
|
|
600
|
-
this.eventTarget = this.options.eventTarget || this.canvas.parentElement || this.canvas;
|
|
601
|
-
this.onPointerMove = (event) => {
|
|
602
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
603
|
-
this.pointerTarget[0] = (event.clientX - rect.left) / Math.max(rect.width, 1);
|
|
604
|
-
this.pointerTarget[1] = 1 - (event.clientY - rect.top) / Math.max(rect.height, 1);
|
|
605
|
-
this.motionTarget = 1;
|
|
606
|
-
};
|
|
607
|
-
this.onPointerLeave = () => { this.motionTarget = 0; };
|
|
608
|
-
this.eventTarget.addEventListener('pointermove', this.onPointerMove, { passive: true });
|
|
609
|
-
this.eventTarget.addEventListener('pointerdown', this.onPointerMove, { passive: true });
|
|
610
|
-
this.eventTarget.addEventListener('pointerleave', this.onPointerLeave, { passive: true });
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
#bindContextEvents() {
|
|
614
|
-
this.onContextLost = (event) => {
|
|
615
|
-
event.preventDefault();
|
|
616
|
-
if (this.disposed) return;
|
|
617
|
-
this.visible = false;
|
|
618
|
-
if (typeof this.options.onContextLost === 'function') this.options.onContextLost(event);
|
|
619
|
-
};
|
|
620
|
-
this.onContextRestored = () => {
|
|
621
|
-
if (this.disposed) return;
|
|
622
|
-
this.program = createProgram(this.gl);
|
|
623
|
-
this.locations = this.#getLocations();
|
|
624
|
-
this.#setupGeometry();
|
|
625
|
-
this.visible = true;
|
|
626
|
-
this.resize();
|
|
627
|
-
if (typeof this.options.onContextRestored === 'function') this.options.onContextRestored();
|
|
628
|
-
};
|
|
629
|
-
this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
|
|
630
|
-
this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
setPreset(preset) {
|
|
634
|
-
this.preset = { ...preset };
|
|
635
|
-
this.colors = preset.colors.map(hexToRgb01);
|
|
636
|
-
this.timeOffset = preset.seed * 0.73;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
setDprCap(cap) {
|
|
640
|
-
this.options.dprCap = cap;
|
|
641
|
-
this.resize();
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
setMouseColor(enabled) {
|
|
645
|
-
this.options.mouseColor = enabled !== false;
|
|
646
|
-
if (this.options.mouseColor) {
|
|
647
|
-
if (this.onPointerMove) return this;
|
|
648
|
-
this.#bindEvents();
|
|
649
|
-
return this;
|
|
650
|
-
}
|
|
651
|
-
if (this.onPointerMove) {
|
|
652
|
-
const target = this.eventTarget;
|
|
653
|
-
target.removeEventListener('pointermove', this.onPointerMove);
|
|
654
|
-
target.removeEventListener('pointerdown', this.onPointerMove);
|
|
655
|
-
target.removeEventListener('pointerleave', this.onPointerLeave);
|
|
656
|
-
this.onPointerMove = null;
|
|
657
|
-
this.onPointerLeave = null;
|
|
658
|
-
}
|
|
659
|
-
this.motionTarget = 0;
|
|
660
|
-
return this;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
randomize() {
|
|
664
|
-
this.preset.seed = Math.random() * 100;
|
|
665
|
-
this.timeOffset = Math.random() * 40;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
resize() {
|
|
669
|
-
const dpr = Math.min(window.devicePixelRatio || 1, this.options.dprCap);
|
|
670
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
671
|
-
const width = Math.max(2, Math.round(rect.width * dpr));
|
|
672
|
-
const height = Math.max(2, Math.round(rect.height * dpr));
|
|
673
|
-
if (this.canvas.width !== width || this.canvas.height !== height) {
|
|
674
|
-
this.canvas.width = width;
|
|
675
|
-
this.canvas.height = height;
|
|
676
|
-
}
|
|
677
|
-
this.gl.viewport(0, 0, width, height);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
draw(elapsedSeconds, paused = false) {
|
|
681
|
-
if (this.disposed || !this.visible) return;
|
|
682
|
-
const gl = this.gl;
|
|
683
|
-
this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
|
|
684
|
-
this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
|
|
685
|
-
this.motion += (this.motionTarget - this.motion) * 0.07;
|
|
686
|
-
|
|
687
|
-
gl.useProgram(this.program);
|
|
688
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
689
|
-
gl.enableVertexAttribArray(this.locations.position);
|
|
690
|
-
gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
|
|
691
|
-
|
|
692
|
-
gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
|
|
693
|
-
gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
|
|
694
|
-
gl.uniform1f(this.locations.seed, this.preset.seed);
|
|
695
|
-
gl.uniform1f(this.locations.motion, this.motion);
|
|
696
|
-
gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
|
|
697
|
-
gl.uniform3fv(this.locations.colorA, this.colors[0]);
|
|
698
|
-
gl.uniform3fv(this.locations.colorB, this.colors[1]);
|
|
699
|
-
gl.uniform3fv(this.locations.colorC, this.colors[2]);
|
|
700
|
-
gl.uniform3fv(this.locations.colorD, this.colors[3]);
|
|
701
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
dispose() {
|
|
705
|
-
this.disposed = true;
|
|
706
|
-
const target = this.eventTarget || this.canvas;
|
|
707
|
-
target.removeEventListener('pointermove', this.onPointerMove);
|
|
708
|
-
target.removeEventListener('pointerdown', this.onPointerMove);
|
|
709
|
-
target.removeEventListener('pointerleave', this.onPointerLeave);
|
|
710
|
-
this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
|
|
711
|
-
this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
|
|
712
|
-
this.gl.deleteBuffer(this.buffer);
|
|
713
|
-
this.gl.deleteProgram(this.program);
|
|
714
|
-
const lose = this.gl.getExtension('WEBGL_lose_context');
|
|
715
|
-
if (lose) lose.loseContext();
|
|
716
|
-
}
|
|
717
|
-
}
|
|
149
|
+
const applySize = () => {
|
|
150
|
+
root.style.width = parseSize(merged.width);
|
|
151
|
+
root.style.height = parseSize(merged.height);
|
|
152
|
+
const vars = merged.cssVars || {};
|
|
153
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
154
|
+
if (merged.textRatio !== undefined) {
|
|
155
|
+
root.style.setProperty('--hj-text-width', `${merged.textRatio}%`);
|
|
156
|
+
// textRatio 是文字区宽度的唯一权威值:min-width 会阻止其缩小到
|
|
157
|
+
// 164px 以下(--hj-copy-min-width 的历史默认),这里强制归零。
|
|
158
|
+
root.style.setProperty('--hj-text-min-width', '0');
|
|
159
|
+
}
|
|
160
|
+
renderer.resize();
|
|
161
|
+
};
|
|
162
|
+
applySize();
|
|
718
163
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
this.context = canvas.getContext('2d');
|
|
729
|
-
this.visible = true;
|
|
730
|
-
this.timePhase = 0;
|
|
731
|
-
this.dprCap = options.dprCap || 1.5;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
resize() {
|
|
735
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
736
|
-
const dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
|
|
737
|
-
const width = Math.max(2, Math.round(rect.width * dpr));
|
|
738
|
-
const height = Math.max(2, Math.round(rect.height * dpr));
|
|
739
|
-
if (this.canvas.width !== width || this.canvas.height !== height) {
|
|
740
|
-
this.canvas.width = width;
|
|
741
|
-
this.canvas.height = height;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
drawNebula(time) {
|
|
746
|
-
const ctx = this.context;
|
|
747
|
-
const { width, height } = this.canvas;
|
|
748
|
-
const t = (time + this.timePhase) * (this.preset.speed || 1);
|
|
749
|
-
const phase = (this.preset.seed || 0) * 0.7;
|
|
750
|
-
const gradient = ctx.createLinearGradient(0, 0, width, height);
|
|
751
|
-
gradient.addColorStop(0, this.preset.colors[0]);
|
|
752
|
-
gradient.addColorStop(0.38, this.preset.colors[1]);
|
|
753
|
-
gradient.addColorStop(0.72, this.preset.colors[2]);
|
|
754
|
-
gradient.addColorStop(1, this.preset.colors[3]);
|
|
755
|
-
ctx.fillStyle = gradient;
|
|
756
|
-
ctx.fillRect(0, 0, width, height);
|
|
757
|
-
|
|
758
|
-
ctx.globalCompositeOperation = 'screen';
|
|
759
|
-
for (let index = 0; index < 6; index += 1) {
|
|
760
|
-
const x = (0.5 + 0.45 * Math.sin(t * 0.32 + index * 1.7 + phase)) * width;
|
|
761
|
-
const y = (0.5 + 0.4 * Math.cos(t * 0.25 + index + phase)) * height;
|
|
762
|
-
const radius = Math.max(width, height) * (0.08 + (index % 3) * 0.04);
|
|
763
|
-
const glow = ctx.createRadialGradient(x, y, 0, x, y, radius);
|
|
764
|
-
glow.addColorStop(0, rgb(this.preset.colors[(index + 1) % 4], 0.24));
|
|
765
|
-
glow.addColorStop(1, rgb(this.preset.colors[index % 4], 0));
|
|
766
|
-
ctx.fillStyle = glow;
|
|
767
|
-
ctx.fillRect(x - radius, y - radius, radius * 2, radius * 2);
|
|
768
|
-
}
|
|
769
|
-
ctx.globalCompositeOperation = 'source-over';
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
draw(time) {
|
|
773
|
-
if (!this.visible) return;
|
|
774
|
-
this.drawNebula(time);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
setPreset(preset) {
|
|
778
|
-
this.preset = preset;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
setDprCap(cap) {
|
|
782
|
-
this.dprCap = cap;
|
|
783
|
-
this.resize();
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
randomize() {
|
|
787
|
-
if (this.preset) {
|
|
788
|
-
this.preset.seed = Math.random() * 100;
|
|
789
|
-
this.timePhase = Math.random() * Math.PI * 2;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
setMouseColor() {}
|
|
793
|
-
dispose() {}
|
|
794
|
-
}
|
|
164
|
+
const resize = () => {
|
|
165
|
+
renderer.resize();
|
|
166
|
+
if (manuallyPaused || reducedPaused || staticMode) renderOnce();
|
|
167
|
+
};
|
|
168
|
+
const resizeObserver = typeof ResizeObserver !== 'undefined'
|
|
169
|
+
? new ResizeObserver(resize)
|
|
170
|
+
: null;
|
|
171
|
+
if (resizeObserver) resizeObserver.observe(root);
|
|
172
|
+
else window.addEventListener('resize', resize);
|
|
795
173
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (!running) return;
|
|
808
|
-
const activeItems = [];
|
|
809
|
-
{
|
|
810
|
-
for (const item of subscribers.slice()) {
|
|
811
|
-
try {
|
|
812
|
-
if (!item.isPaused()) activeItems.push(item);
|
|
813
|
-
} catch {}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
if (activeItems.length === 0) {
|
|
817
|
-
running = false;
|
|
818
|
-
rafId = 0;
|
|
819
|
-
last = 0;
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
const delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
|
|
823
|
-
last = now;
|
|
824
|
-
// Schedule the next frame BEFORE running callbacks so one throwing
|
|
825
|
-
// subscriber can never kill the whole animation loop.
|
|
826
|
-
rafId = requestAnimationFrame(tick);
|
|
827
|
-
for (const item of activeItems) {
|
|
828
|
-
try {
|
|
829
|
-
item.onFrame(delta, now);
|
|
830
|
-
} catch (error) {
|
|
831
|
-
console.warn('[dlc-ui] frame error:', error);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
if (subscribers.length === 0) {
|
|
835
|
-
cancelAnimationFrame(rafId);
|
|
836
|
-
running = false;
|
|
837
|
-
rafId = 0;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
function start() {
|
|
842
|
-
if (running) return;
|
|
843
|
-
running = true;
|
|
844
|
-
last = 0;
|
|
845
|
-
rafId = requestAnimationFrame(tick);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
function wakeScheduler() {
|
|
849
|
-
start();
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function subscribeScheduler(onFrame, isPaused) {
|
|
853
|
-
const item = { onFrame, isPaused };
|
|
854
|
-
subscribers.push(item);
|
|
855
|
-
start();
|
|
856
|
-
return () => {
|
|
857
|
-
const index = subscribers.indexOf(item);
|
|
858
|
-
if (index !== -1) subscribers.splice(index, 1);
|
|
859
|
-
if (subscribers.length === 0 && rafId) {
|
|
860
|
-
cancelAnimationFrame(rafId);
|
|
861
|
-
running = false;
|
|
862
|
-
rafId = 0;
|
|
863
|
-
}
|
|
864
|
-
};
|
|
865
|
-
}
|
|
174
|
+
const visibility = createVisibilityGuard(root, wakeScheduler);
|
|
175
|
+
const motionPreference = createReducedMotionPreference(
|
|
176
|
+
merged.respectReducedMotion,
|
|
177
|
+
(matches) => {
|
|
178
|
+
reducedPaused = matches;
|
|
179
|
+
if (matches) renderOnce();
|
|
180
|
+
else wakeScheduler();
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
reducedPaused = motionPreference.matches();
|
|
184
|
+
const isMotionPaused = () => manuallyPaused || reducedPaused || staticMode || contextLost;
|
|
866
185
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
let observer = null;
|
|
876
|
-
|
|
877
|
-
if (typeof IntersectionObserver !== 'undefined') {
|
|
878
|
-
observer = new IntersectionObserver(
|
|
879
|
-
(entries) => {
|
|
880
|
-
intersecting = entries.some((entry) => entry.isIntersecting);
|
|
881
|
-
if (typeof onChange === 'function') onChange();
|
|
882
|
-
},
|
|
883
|
-
{ rootMargin: '180px' }
|
|
884
|
-
);
|
|
885
|
-
observer.observe(element);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
const onVisibilityChange = () => {
|
|
889
|
-
pageVisible = typeof document !== 'undefined' && !document.hidden;
|
|
890
|
-
if (typeof onChange === 'function') onChange();
|
|
891
|
-
};
|
|
892
|
-
if (typeof document !== 'undefined') {
|
|
893
|
-
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
return {
|
|
897
|
-
isVisible() {
|
|
898
|
-
return intersecting && pageVisible;
|
|
899
|
-
},
|
|
900
|
-
dispose() {
|
|
901
|
-
if (disposed) return;
|
|
902
|
-
disposed = true;
|
|
903
|
-
if (observer) observer.disconnect();
|
|
904
|
-
if (typeof document !== 'undefined') {
|
|
905
|
-
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
};
|
|
909
|
-
}
|
|
186
|
+
root.addEventListener('pointerenter', (event) => emitter.emit('pointerenter', { event, preset: { ...preset } }));
|
|
187
|
+
root.addEventListener('pointerleave', (event) => emitter.emit('pointerleave', { event, preset: { ...preset } }));
|
|
188
|
+
root.addEventListener('click', (event) => {
|
|
189
|
+
emitter.emit('click', { event, preset: { ...preset } });
|
|
190
|
+
});
|
|
191
|
+
root.addEventListener('pointerdown', (event) => emitter.emit('pointerdown', { event, preset: { ...preset } }));
|
|
192
|
+
root.addEventListener('pointerup', (event) => emitter.emit('pointerup', { event, preset: { ...preset } }));
|
|
193
|
+
root.addEventListener('dblclick', (event) => emitter.emit('dblclick', { event, preset: { ...preset } }));
|
|
910
194
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
queueMicrotask(fn);
|
|
920
|
-
} else if (typeof Promise !== 'undefined' && typeof Promise.resolve === 'function') {
|
|
921
|
-
Promise.resolve().then(fn);
|
|
922
|
-
} else {
|
|
923
|
-
setTimeout(fn, 0);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
195
|
+
renderOnce();
|
|
196
|
+
const unsubscribe = subscribeScheduler(
|
|
197
|
+
(delta) => {
|
|
198
|
+
animationTime += delta;
|
|
199
|
+
if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
|
|
200
|
+
},
|
|
201
|
+
() => isMotionPaused() || !visibility.isVisible()
|
|
202
|
+
);
|
|
926
203
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function createFrameGate(initialFps = 60) {
|
|
934
|
-
let fps = normalizeFps(initialFps);
|
|
935
|
-
let elapsed = 0;
|
|
936
|
-
return {
|
|
937
|
-
shouldDraw(delta) {
|
|
938
|
-
elapsed += delta;
|
|
939
|
-
const interval = 1 / fps;
|
|
940
|
-
if (elapsed + 0.0001 < interval) return false;
|
|
941
|
-
elapsed %= interval;
|
|
942
|
-
return true;
|
|
943
|
-
},
|
|
944
|
-
setFps(value) {
|
|
945
|
-
fps = normalizeFps(value, fps);
|
|
946
|
-
elapsed = 0;
|
|
947
|
-
return fps;
|
|
948
|
-
},
|
|
949
|
-
getFps() {
|
|
950
|
-
return fps;
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
function createReducedMotionPreference(enabled, onChange) {
|
|
956
|
-
const query = enabled && typeof matchMedia !== 'undefined'
|
|
957
|
-
? matchMedia('(prefers-reduced-motion: reduce)')
|
|
958
|
-
: null;
|
|
959
|
-
const notify = () => {
|
|
960
|
-
if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
|
|
961
|
-
};
|
|
962
|
-
if (query) {
|
|
963
|
-
if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);
|
|
964
|
-
else if (typeof query.addListener === 'function') query.addListener(notify);
|
|
965
|
-
}
|
|
966
|
-
return {
|
|
967
|
-
matches() {
|
|
968
|
-
return Boolean(query && query.matches);
|
|
969
|
-
},
|
|
970
|
-
dispose() {
|
|
971
|
-
if (!query) return;
|
|
972
|
-
if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);
|
|
973
|
-
else if (typeof query.removeListener === 'function') query.removeListener(notify);
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
}
|
|
204
|
+
nextTick(() => {
|
|
205
|
+
if (disposed) return;
|
|
206
|
+
emitter.emit('ready', { preset: { ...preset } });
|
|
207
|
+
});
|
|
977
208
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
}
|
|
209
|
+
const syncTheme = () => {
|
|
210
|
+
root.dataset.mode = 'nebula';
|
|
211
|
+
root.dataset.theme = preset.theme || 'light';
|
|
212
|
+
updateAria();
|
|
213
|
+
};
|
|
984
214
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
merged.
|
|
1153
|
-
(
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
);
|
|
1159
|
-
reducedPaused = motionPreference.matches();
|
|
1160
|
-
const isMotionPaused = () => manuallyPaused || reducedPaused || staticMode || contextLost;
|
|
1161
|
-
|
|
1162
|
-
root.addEventListener('pointerenter', (event) => emitter.emit('pointerenter', { event, preset: { ...preset } }));
|
|
1163
|
-
root.addEventListener('pointerleave', (event) => emitter.emit('pointerleave', { event, preset: { ...preset } }));
|
|
1164
|
-
root.addEventListener('click', (event) => {
|
|
1165
|
-
emitter.emit('click', { event, preset: { ...preset } });
|
|
1166
|
-
});
|
|
1167
|
-
root.addEventListener('pointerdown', (event) => emitter.emit('pointerdown', { event, preset: { ...preset } }));
|
|
1168
|
-
root.addEventListener('pointerup', (event) => emitter.emit('pointerup', { event, preset: { ...preset } }));
|
|
1169
|
-
root.addEventListener('dblclick', (event) => emitter.emit('dblclick', { event, preset: { ...preset } }));
|
|
1170
|
-
|
|
1171
|
-
renderOnce();
|
|
1172
|
-
const unsubscribe = subscribeScheduler(
|
|
1173
|
-
(delta) => {
|
|
1174
|
-
animationTime += delta;
|
|
1175
|
-
if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
|
|
1176
|
-
},
|
|
1177
|
-
() => isMotionPaused() || !visibility.isVisible()
|
|
1178
|
-
);
|
|
1179
|
-
|
|
1180
|
-
nextTick(() => {
|
|
1181
|
-
if (disposed) return;
|
|
1182
|
-
emitter.emit('ready', { preset: { ...preset } });
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
const syncTheme = () => {
|
|
1186
|
-
root.dataset.mode = 'nebula';
|
|
1187
|
-
root.dataset.theme = preset.theme || 'light';
|
|
1188
|
-
updateAria();
|
|
1189
|
-
};
|
|
1190
|
-
|
|
1191
|
-
return {
|
|
1192
|
-
element: root,
|
|
1193
|
-
canvas,
|
|
1194
|
-
preset,
|
|
1195
|
-
on: emitter.on,
|
|
1196
|
-
off: emitter.off,
|
|
1197
|
-
setPreset(ref) {
|
|
1198
|
-
const next = getPreset('capsule', ref);
|
|
1199
|
-
dirty.preset = true;
|
|
1200
|
-
Object.assign(preset, next);
|
|
1201
|
-
if (colorOverride) preset.colors = [...colorOverride];
|
|
1202
|
-
if (seedOverride !== null) preset.seed = seedOverride;
|
|
1203
|
-
if (speedOverride !== null) preset.speed = speedOverride;
|
|
1204
|
-
const nextColors = preset.colors.map(normalizeColor);
|
|
1205
|
-
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
1206
|
-
renderer.setPreset({ ...preset });
|
|
1207
|
-
syncTheme();
|
|
1208
|
-
renderer.resize();
|
|
1209
|
-
if (isMotionPaused()) renderOnce();
|
|
1210
|
-
emitter.emit('presetchange', { preset: { ...next } });
|
|
1211
|
-
return this;
|
|
1212
|
-
},
|
|
1213
|
-
setColors(colors) {
|
|
1214
|
-
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
1215
|
-
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
1216
|
-
dirty.colors = true;
|
|
1217
|
-
colorOverride = [...next];
|
|
1218
|
-
preset.colors = next;
|
|
1219
|
-
renderer.setPreset({ ...preset, colors: next });
|
|
1220
|
-
if (isMotionPaused()) renderOnce();
|
|
1221
|
-
return this;
|
|
1222
|
-
},
|
|
1223
|
-
setSeed(seed) {
|
|
1224
|
-
const value = toFiniteNumber(seed);
|
|
1225
|
-
if (value === null) return this;
|
|
1226
|
-
dirty.seed = true;
|
|
1227
|
-
seedOverride = value;
|
|
1228
|
-
preset.seed = value;
|
|
1229
|
-
renderer.setPreset({ ...preset });
|
|
1230
|
-
if (isMotionPaused()) renderOnce();
|
|
1231
|
-
return this;
|
|
1232
|
-
},
|
|
1233
|
-
setSpeed(speed) {
|
|
1234
|
-
const value = toFiniteNumber(speed);
|
|
1235
|
-
if (value === null) return this;
|
|
1236
|
-
dirty.speed = true;
|
|
1237
|
-
speedOverride = value;
|
|
1238
|
-
preset.speed = value;
|
|
1239
|
-
renderer.setPreset({ ...preset });
|
|
1240
|
-
if (isMotionPaused()) renderOnce();
|
|
1241
|
-
return this;
|
|
1242
|
-
},
|
|
1243
|
-
setText(content) {
|
|
1244
|
-
fillContent(textLayer, content);
|
|
1245
|
-
return this;
|
|
1246
|
-
},
|
|
1247
|
-
setColorContent(content) {
|
|
1248
|
-
fillContent(visualLayer, content);
|
|
1249
|
-
return this;
|
|
1250
|
-
},
|
|
1251
|
-
setTextRatio(ratio) {
|
|
1252
|
-
const value = Number(ratio);
|
|
1253
|
-
if (!Number.isFinite(value) || value < 0 || value > 100) return this;
|
|
1254
|
-
dirty.textRatio = true;
|
|
1255
|
-
merged.textRatio = value;
|
|
1256
|
-
root.style.setProperty('--hj-text-width', `${value}%`);
|
|
1257
|
-
root.style.setProperty('--hj-text-min-width', '0');
|
|
1258
|
-
return this;
|
|
1259
|
-
},
|
|
1260
|
-
setCssVars(vars) {
|
|
1261
|
-
if (!vars || typeof vars !== 'object') return this;
|
|
1262
|
-
dirty.cssVars = true;
|
|
1263
|
-
merged.cssVars = { ...(merged.cssVars || {}), ...vars };
|
|
1264
|
-
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
1265
|
-
return this;
|
|
1266
|
-
},
|
|
1267
|
-
setLabel(label) {
|
|
1268
|
-
customLabel = typeof label === 'string' && label ? label : null;
|
|
1269
|
-
updateAria();
|
|
1270
|
-
return this;
|
|
1271
|
-
},
|
|
1272
|
-
setMouseColor(value) {
|
|
1273
|
-
dirty.mouseColor = true;
|
|
1274
|
-
merged.mouseColor = value !== false;
|
|
1275
|
-
if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
|
|
1276
|
-
return this;
|
|
1277
|
-
},
|
|
1278
|
-
setSize(width, height) {
|
|
1279
|
-
if (width !== undefined) {
|
|
1280
|
-
parseSize(width); // 非法尺寸直接抛错前先校验,避免污染内部状态
|
|
1281
|
-
merged.width = width;
|
|
1282
|
-
}
|
|
1283
|
-
if (height !== undefined) {
|
|
1284
|
-
parseSize(height);
|
|
1285
|
-
merged.height = height;
|
|
1286
|
-
}
|
|
1287
|
-
applySize();
|
|
1288
|
-
if (isMotionPaused()) renderOnce();
|
|
1289
|
-
return this;
|
|
1290
|
-
},
|
|
1291
|
-
randomize() {
|
|
1292
|
-
this.setSeed(Math.random() * 100);
|
|
1293
|
-
return this;
|
|
1294
|
-
},
|
|
1295
|
-
pause() {
|
|
1296
|
-
dirty.paused = true;
|
|
1297
|
-
manuallyPaused = true;
|
|
1298
|
-
renderOnce();
|
|
1299
|
-
return this;
|
|
1300
|
-
},
|
|
1301
|
-
resume() {
|
|
1302
|
-
dirty.paused = true;
|
|
1303
|
-
manuallyPaused = false;
|
|
1304
|
-
wakeScheduler();
|
|
1305
|
-
return this;
|
|
1306
|
-
},
|
|
1307
|
-
setPaused(value) {
|
|
1308
|
-
return value ? this.pause() : this.resume();
|
|
1309
|
-
},
|
|
1310
|
-
setStatic(value) {
|
|
1311
|
-
dirty.static = true;
|
|
1312
|
-
staticMode = value === true;
|
|
1313
|
-
merged.static = staticMode;
|
|
1314
|
-
if (staticMode) renderOnce();
|
|
1315
|
-
else wakeScheduler();
|
|
1316
|
-
return this;
|
|
1317
|
-
},
|
|
1318
|
-
setFps(fps) {
|
|
1319
|
-
dirty.fps = true;
|
|
1320
|
-
merged.fps = frameGate.setFps(fps);
|
|
1321
|
-
wakeScheduler();
|
|
1322
|
-
return this;
|
|
1323
|
-
},
|
|
1324
|
-
setQuality(quality) {
|
|
1325
|
-
dirty.quality = true;
|
|
1326
|
-
merged.quality = quality;
|
|
1327
|
-
if (typeof renderer.setDprCap === 'function') {
|
|
1328
|
-
renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
|
|
1329
|
-
}
|
|
1330
|
-
if (isMotionPaused()) renderOnce();
|
|
1331
|
-
return this;
|
|
1332
|
-
},
|
|
1333
|
-
setRenderScale(renderScale) {
|
|
1334
|
-
const value = Number(renderScale);
|
|
1335
|
-
if (!Number.isFinite(value)) return this;
|
|
1336
|
-
dirty.renderScale = true;
|
|
1337
|
-
merged.renderScale = Math.min(1, Math.max(0.25, value));
|
|
1338
|
-
if (typeof renderer.setDprCap === 'function') {
|
|
1339
|
-
renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
|
|
1340
|
-
}
|
|
1341
|
-
if (isMotionPaused()) renderOnce();
|
|
1342
|
-
return this;
|
|
1343
|
-
},
|
|
1344
|
-
dispose() {
|
|
1345
|
-
disposed = true;
|
|
1346
|
-
unsubscribe();
|
|
1347
|
-
visibility.dispose();
|
|
1348
|
-
motionPreference.dispose();
|
|
1349
|
-
if (resizeObserver) resizeObserver.disconnect();
|
|
1350
|
-
else window.removeEventListener('resize', resize);
|
|
1351
|
-
renderer.dispose();
|
|
1352
|
-
root.remove();
|
|
1353
|
-
},
|
|
1354
|
-
get textRatio() { return merged.textRatio; },
|
|
1355
|
-
get cssVars() { return merged.cssVars; },
|
|
1356
|
-
get mouseColor() { return merged.mouseColor; },
|
|
1357
|
-
get quality() { return merged.quality; },
|
|
1358
|
-
get renderScale() { return merged.renderScale; },
|
|
1359
|
-
get paused() { return manuallyPaused; },
|
|
1360
|
-
get static() { return staticMode; },
|
|
1361
|
-
get fps() { return frameGate.getFps(); },
|
|
1362
|
-
dirty
|
|
1363
|
-
};
|
|
215
|
+
return {
|
|
216
|
+
element: root,
|
|
217
|
+
canvas,
|
|
218
|
+
preset,
|
|
219
|
+
on: emitter.on,
|
|
220
|
+
off: emitter.off,
|
|
221
|
+
setPreset(ref) {
|
|
222
|
+
const next = getPreset('capsule', ref);
|
|
223
|
+
dirty.preset = true;
|
|
224
|
+
Object.assign(preset, next);
|
|
225
|
+
if (colorOverride) preset.colors = [...colorOverride];
|
|
226
|
+
if (seedOverride !== null) preset.seed = seedOverride;
|
|
227
|
+
if (speedOverride !== null) preset.speed = speedOverride;
|
|
228
|
+
const nextColors = preset.colors.map(normalizeColor);
|
|
229
|
+
if (nextColors.every(Boolean)) preset.colors = nextColors;
|
|
230
|
+
renderer.setPreset({ ...preset });
|
|
231
|
+
syncTheme();
|
|
232
|
+
renderer.resize();
|
|
233
|
+
if (isMotionPaused()) renderOnce();
|
|
234
|
+
emitter.emit('presetchange', { preset: { ...next } });
|
|
235
|
+
return this;
|
|
236
|
+
},
|
|
237
|
+
setColors(colors) {
|
|
238
|
+
const next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
|
|
239
|
+
if (next.length !== 4 || next.some((color) => !color)) return this;
|
|
240
|
+
dirty.colors = true;
|
|
241
|
+
colorOverride = [...next];
|
|
242
|
+
preset.colors = next;
|
|
243
|
+
renderer.setPreset({ ...preset, colors: next });
|
|
244
|
+
if (isMotionPaused()) renderOnce();
|
|
245
|
+
return this;
|
|
246
|
+
},
|
|
247
|
+
setSeed(seed) {
|
|
248
|
+
const value = toFiniteNumber(seed);
|
|
249
|
+
if (value === null) return this;
|
|
250
|
+
dirty.seed = true;
|
|
251
|
+
seedOverride = value;
|
|
252
|
+
preset.seed = value;
|
|
253
|
+
renderer.setPreset({ ...preset });
|
|
254
|
+
if (isMotionPaused()) renderOnce();
|
|
255
|
+
return this;
|
|
256
|
+
},
|
|
257
|
+
setSpeed(speed) {
|
|
258
|
+
const value = toFiniteNumber(speed);
|
|
259
|
+
if (value === null) return this;
|
|
260
|
+
dirty.speed = true;
|
|
261
|
+
speedOverride = value;
|
|
262
|
+
preset.speed = value;
|
|
263
|
+
renderer.setPreset({ ...preset });
|
|
264
|
+
if (isMotionPaused()) renderOnce();
|
|
265
|
+
return this;
|
|
266
|
+
},
|
|
267
|
+
setText(content) {
|
|
268
|
+
fillContent(textLayer, content);
|
|
269
|
+
return this;
|
|
270
|
+
},
|
|
271
|
+
setColorContent(content) {
|
|
272
|
+
fillContent(visualLayer, content);
|
|
273
|
+
return this;
|
|
274
|
+
},
|
|
275
|
+
setTextRatio(ratio) {
|
|
276
|
+
const value = Number(ratio);
|
|
277
|
+
if (!Number.isFinite(value) || value < 0 || value > 100) return this;
|
|
278
|
+
dirty.textRatio = true;
|
|
279
|
+
merged.textRatio = value;
|
|
280
|
+
root.style.setProperty('--hj-text-width', `${value}%`);
|
|
281
|
+
root.style.setProperty('--hj-text-min-width', '0');
|
|
282
|
+
return this;
|
|
283
|
+
},
|
|
284
|
+
setCssVars(vars) {
|
|
285
|
+
if (!vars || typeof vars !== 'object') return this;
|
|
286
|
+
dirty.cssVars = true;
|
|
287
|
+
merged.cssVars = { ...(merged.cssVars || {}), ...vars };
|
|
288
|
+
for (const name of Object.keys(vars)) root.style.setProperty(name, vars[name]);
|
|
289
|
+
return this;
|
|
290
|
+
},
|
|
291
|
+
setLabel(label) {
|
|
292
|
+
customLabel = typeof label === 'string' && label ? label : null;
|
|
293
|
+
updateAria();
|
|
294
|
+
return this;
|
|
295
|
+
},
|
|
296
|
+
setMouseColor(value) {
|
|
297
|
+
dirty.mouseColor = true;
|
|
298
|
+
merged.mouseColor = value !== false;
|
|
299
|
+
if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
|
|
300
|
+
return this;
|
|
301
|
+
},
|
|
302
|
+
setSize(width, height) {
|
|
303
|
+
if (width !== undefined) {
|
|
304
|
+
parseSize(width); // 非法尺寸直接抛错前先校验,避免污染内部状态
|
|
305
|
+
merged.width = width;
|
|
306
|
+
}
|
|
307
|
+
if (height !== undefined) {
|
|
308
|
+
parseSize(height);
|
|
309
|
+
merged.height = height;
|
|
310
|
+
}
|
|
311
|
+
applySize();
|
|
312
|
+
if (isMotionPaused()) renderOnce();
|
|
313
|
+
return this;
|
|
314
|
+
},
|
|
315
|
+
randomize() {
|
|
316
|
+
this.setSeed(Math.random() * 100);
|
|
317
|
+
return this;
|
|
318
|
+
},
|
|
319
|
+
pause() {
|
|
320
|
+
dirty.paused = true;
|
|
321
|
+
manuallyPaused = true;
|
|
322
|
+
renderOnce();
|
|
323
|
+
return this;
|
|
324
|
+
},
|
|
325
|
+
resume() {
|
|
326
|
+
dirty.paused = true;
|
|
327
|
+
manuallyPaused = false;
|
|
328
|
+
wakeScheduler();
|
|
329
|
+
return this;
|
|
330
|
+
},
|
|
331
|
+
setPaused(value) {
|
|
332
|
+
return value ? this.pause() : this.resume();
|
|
333
|
+
},
|
|
334
|
+
setStatic(value) {
|
|
335
|
+
dirty.static = true;
|
|
336
|
+
staticMode = value === true;
|
|
337
|
+
merged.static = staticMode;
|
|
338
|
+
if (staticMode) renderOnce();
|
|
339
|
+
else wakeScheduler();
|
|
340
|
+
return this;
|
|
341
|
+
},
|
|
342
|
+
setFps(fps) {
|
|
343
|
+
dirty.fps = true;
|
|
344
|
+
merged.fps = frameGate.setFps(fps);
|
|
345
|
+
wakeScheduler();
|
|
346
|
+
return this;
|
|
347
|
+
},
|
|
348
|
+
setQuality(quality) {
|
|
349
|
+
dirty.quality = true;
|
|
350
|
+
merged.quality = quality;
|
|
351
|
+
if (typeof renderer.setDprCap === 'function') {
|
|
352
|
+
renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
|
|
353
|
+
}
|
|
354
|
+
if (isMotionPaused()) renderOnce();
|
|
355
|
+
return this;
|
|
356
|
+
},
|
|
357
|
+
setRenderScale(renderScale) {
|
|
358
|
+
const value = Number(renderScale);
|
|
359
|
+
if (!Number.isFinite(value)) return this;
|
|
360
|
+
dirty.renderScale = true;
|
|
361
|
+
merged.renderScale = Math.min(1, Math.max(0.25, value));
|
|
362
|
+
if (typeof renderer.setDprCap === 'function') {
|
|
363
|
+
renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
|
|
364
|
+
}
|
|
365
|
+
if (isMotionPaused()) renderOnce();
|
|
366
|
+
return this;
|
|
367
|
+
},
|
|
368
|
+
dispose() {
|
|
369
|
+
disposed = true;
|
|
370
|
+
unsubscribe();
|
|
371
|
+
visibility.dispose();
|
|
372
|
+
motionPreference.dispose();
|
|
373
|
+
if (resizeObserver) resizeObserver.disconnect();
|
|
374
|
+
else window.removeEventListener('resize', resize);
|
|
375
|
+
renderer.dispose();
|
|
376
|
+
root.remove();
|
|
377
|
+
},
|
|
378
|
+
get textRatio() { return merged.textRatio; },
|
|
379
|
+
get cssVars() { return merged.cssVars; },
|
|
380
|
+
get mouseColor() { return merged.mouseColor; },
|
|
381
|
+
get quality() { return merged.quality; },
|
|
382
|
+
get renderScale() { return merged.renderScale; },
|
|
383
|
+
get paused() { return manuallyPaused; },
|
|
384
|
+
get static() { return staticMode; },
|
|
385
|
+
get fps() { return frameGate.getFps(); },
|
|
386
|
+
dirty
|
|
387
|
+
};
|
|
1364
388
|
}
|
|
1365
389
|
|
|
1366
390
|
export { createCapsule };
|