@kayf/ui 0.1.0

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.
@@ -0,0 +1,1296 @@
1
+ /**
2
+ * KayfElement - базовый класс для всех компонентов.
3
+ */
4
+ class KayfElement extends HTMLElement {
5
+ constructor() {
6
+ super();
7
+ Object.defineProperty(this, "root", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: void 0
12
+ });
13
+ this.root = this.attachShadow({ mode: 'open' });
14
+ }
15
+ static get observedAttributes() {
16
+ return [];
17
+ }
18
+ attributeChangedCallback(_name, oldValue, newValue) {
19
+ if (oldValue !== newValue) {
20
+ this.update();
21
+ }
22
+ }
23
+ connectedCallback() {
24
+ this.render();
25
+ this.setup();
26
+ }
27
+ disconnectedCallback() {
28
+ this.cleanup();
29
+ }
30
+ update() {
31
+ if (this.isConnected) {
32
+ this.render();
33
+ this.setup();
34
+ }
35
+ }
36
+ styles() { return ''; }
37
+ render() {
38
+ this.root.innerHTML = '<style>' + this.styles() + '</style>' + this.template();
39
+ }
40
+ setup() { }
41
+ cleanup() { }
42
+ attr(name, fallback = '') {
43
+ return this.getAttribute(name) ?? fallback;
44
+ }
45
+ numAttr(name, fallback = 0) {
46
+ const val = this.getAttribute(name);
47
+ return val !== null ? parseFloat(val) : fallback;
48
+ }
49
+ boolAttr(name) {
50
+ return this.hasAttribute(name);
51
+ }
52
+ }
53
+
54
+ const tokens = {
55
+ colors: {
56
+ cyan: '#00d4ff',
57
+ violet: '#7c3aed',
58
+ emerald: '#10b981',
59
+ amber: '#f59e0b',
60
+ red: '#ef4444',
61
+ white: '#e8e8f0',
62
+ },
63
+ glow: {
64
+ cyan: 'rgba(0, 212, 255, 0.2)',
65
+ violet: 'rgba(124, 58, 237, 0.2)',
66
+ emerald: 'rgba(16, 185, 129, 0.2)',
67
+ amber: 'rgba(245, 158, 11, 0.2)',
68
+ red: 'rgba(239, 68, 68, 0.2)',
69
+ white: 'rgba(232, 232, 240, 0.2)',
70
+ },
71
+ spotlight: {
72
+ cyan: 'rgba(0, 212, 255, 0.12)',
73
+ violet: 'rgba(124, 58, 237, 0.12)',
74
+ emerald: 'rgba(16, 185, 129, 0.12)',
75
+ amber: 'rgba(245, 158, 11, 0.12)',
76
+ red: 'rgba(239, 68, 68, 0.12)',
77
+ white: 'rgba(232, 232, 240, 0.12)',
78
+ },
79
+ };
80
+ const baseCSS = `
81
+ :host {
82
+ --kayf-cyan: #00d4ff;
83
+ --kayf-violet: #7c3aed;
84
+ --kayf-emerald: #10b981;
85
+ --kayf-amber: #f59e0b;
86
+ --kayf-red: #ef4444;
87
+ --kayf-bg: #050508;
88
+ --kayf-surface: rgba(255, 255, 255, 0.03);
89
+ --kayf-border: rgba(255, 255, 255, 0.07);
90
+ --kayf-text: #e8e8f0;
91
+ --kayf-muted: rgba(232, 232, 240, 0.4);
92
+ --kayf-font-sans: 'Syne', system-ui, sans-serif;
93
+ --kayf-font-mono: 'JetBrains Mono', monospace;
94
+ }
95
+ `;
96
+ function getColor(v) {
97
+ return tokens.colors[v] ?? v;
98
+ }
99
+ function getGlow(v) {
100
+ return tokens.glow[v] ?? 'rgba(255,255,255,0.1)';
101
+ }
102
+ function getSpotlight(v) {
103
+ return tokens.spotlight[v] ?? 'rgba(255,255,255,0.08)';
104
+ }
105
+
106
+ /**
107
+ * <kayf-spotlight-card>
108
+ *
109
+ * Атрибуты:
110
+ * color - cyan | violet | emerald | amber | red (default: cyan)
111
+ * glow - none | soft | medium | strong (default: soft)
112
+ * radius - border-radius px (default: 16)
113
+ *
114
+ * Пример:
115
+ * <kayf-spotlight-card color="violet" glow="medium">
116
+ * <h3>Title</h3>
117
+ * <p>Description</p>
118
+ * </kayf-spotlight-card>
119
+ */
120
+ const glowIntensity = {
121
+ none: 0, soft: 0.15, medium: 0.25, strong: 0.4,
122
+ };
123
+ class SpotlightCard extends KayfElement {
124
+ static get observedAttributes() {
125
+ return ['color', 'glow', 'radius'];
126
+ }
127
+ get color() { return this.attr('color', 'cyan'); }
128
+ get glowLevel() { return this.attr('glow', 'soft'); }
129
+ get radius() { return this.numAttr('radius', 16); }
130
+ styles() {
131
+ const color = getColor(this.color);
132
+ const glowColor = getGlow(this.color);
133
+ const spotColor = getSpotlight(this.color);
134
+ const intensity = glowIntensity[this.glowLevel] ?? 0.15;
135
+ const r = this.radius;
136
+ return baseCSS + `
137
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
138
+ :host { display: block; position: relative; border-radius: ${r}px; }
139
+
140
+ .card-border {
141
+ border-radius: ${r}px;
142
+ padding: 1px;
143
+ background: rgba(255,255,255,0.07);
144
+ transition: background 0.3s;
145
+ height: 100%;
146
+ }
147
+ :host(:hover) .card-border {
148
+ background: linear-gradient(135deg, ${color}44, rgba(255,255,255,0.05), ${color}22);
149
+ }
150
+ .card {
151
+ position: relative;
152
+ background: var(--kayf-surface);
153
+ border-radius: ${r - 1}px;
154
+ overflow: hidden;
155
+ height: 100%;
156
+ min-height: 160px;
157
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
158
+ transition: box-shadow 0.3s, transform 0.3s;
159
+ }
160
+ :host(:hover) .card {
161
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.08),
162
+ 0 0 40px ${glowColor.replace('0.2', String(intensity))};
163
+ transform: translateY(-2px);
164
+ }
165
+ .spotlight {
166
+ position: absolute;
167
+ width: 350px; height: 350px;
168
+ border-radius: 50%;
169
+ pointer-events: none;
170
+ transform: translate(-50%, -50%);
171
+ background: radial-gradient(circle, ${spotColor}, transparent 70%);
172
+ opacity: 0;
173
+ transition: opacity 0.3s;
174
+ z-index: 0;
175
+ }
176
+ :host(:hover) .spotlight { opacity: 1; }
177
+ .accent {
178
+ position: absolute;
179
+ top: 0; left: 20%; right: 20%; height: 1px;
180
+ background: linear-gradient(90deg, transparent, ${color}88, transparent);
181
+ opacity: 0; transition: opacity 0.3s; z-index: 2;
182
+ }
183
+ :host(:hover) .accent { opacity: 1; }
184
+ .content { position: relative; z-index: 1; padding: 24px; height: 100%; }
185
+ ::slotted(p) {
186
+ font-family: var(--kayf-font-mono);
187
+ font-size: 13px; color: var(--kayf-muted);
188
+ line-height: 1.6; font-weight: 300;
189
+ }
190
+ `;
191
+ }
192
+ template() {
193
+ return `
194
+ <div class="card-border">
195
+ <div class="card">
196
+ <div class="spotlight"></div>
197
+ <div class="accent"></div>
198
+ <div class="content"><slot></slot></div>
199
+ </div>
200
+ </div>
201
+ `;
202
+ }
203
+ setup() {
204
+ const card = this.root.querySelector('.card');
205
+ const spot = this.root.querySelector('.spotlight');
206
+ if (!card || !spot)
207
+ return;
208
+ const fn = (e) => {
209
+ const rect = card.getBoundingClientRect();
210
+ spot.style.left = (e.clientX - rect.left) + 'px';
211
+ spot.style.top = (e.clientY - rect.top) + 'px';
212
+ };
213
+ card.addEventListener('mousemove', fn);
214
+ this._fn = fn;
215
+ this._card = card;
216
+ }
217
+ cleanup() {
218
+ const card = this._card;
219
+ const fn = this._fn;
220
+ if (card && fn)
221
+ card.removeEventListener('mousemove', fn);
222
+ }
223
+ }
224
+ if (!customElements.get('kayf-spotlight-card')) {
225
+ customElements.define('kayf-spotlight-card', SpotlightCard);
226
+ }
227
+
228
+ const sizes = {
229
+ sm: { padding: '0 14px', fontSize: '12px', height: '32px', radius: '8px' },
230
+ md: { padding: '0 22px', fontSize: '14px', height: '40px', radius: '10px' },
231
+ lg: { padding: '0 32px', fontSize: '15px', height: '50px', radius: '12px' },
232
+ };
233
+ class BeamButton extends KayfElement {
234
+ static get observedAttributes() {
235
+ return ['color', 'variant', 'size', 'disabled', 'loading'];
236
+ }
237
+ get color() { return this.attr('color', 'cyan'); }
238
+ get variant() { return this.attr('variant', 'outline'); }
239
+ get size() { return this.attr('size', 'md'); }
240
+ get isDisabled() { return this.boolAttr('disabled'); }
241
+ get isLoading() { return this.boolAttr('loading'); }
242
+ styles() {
243
+ const color = getColor(this.color);
244
+ const sz = sizes[this.size] ?? sizes.md;
245
+ const vs = {
246
+ solid: `
247
+ background: ${color};
248
+ color: #050508;
249
+ border: 1px solid transparent;
250
+ font-weight: 700;
251
+ box-shadow: 0 0 20px ${color}44, inset 0 1px 0 rgba(255,255,255,0.2);
252
+ `,
253
+ outline: `
254
+ background: ${color}12;
255
+ color: ${color};
256
+ border: 1px solid ${color}44;
257
+ font-weight: 600;
258
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
259
+ `,
260
+ ghost: `
261
+ background: rgba(255,255,255,0.04);
262
+ color: rgba(232,232,240,0.7);
263
+ border: 1px solid rgba(255,255,255,0.08);
264
+ font-weight: 500;
265
+ `,
266
+ };
267
+ const vh = {
268
+ solid: `
269
+ background: ${color}dd;
270
+ box-shadow: 0 0 30px ${color}66, 0 4px 20px ${color}33, inset 0 1px 0 rgba(255,255,255,0.3);
271
+ transform: translateY(-2px);
272
+ `,
273
+ outline: `
274
+ background: ${color}1e;
275
+ border-color: ${color}88;
276
+ box-shadow: 0 0 20px ${color}22;
277
+ transform: translateY(-2px);
278
+ `,
279
+ ghost: `
280
+ background: rgba(255,255,255,0.07);
281
+ border-color: rgba(255,255,255,0.15);
282
+ color: rgba(232,232,240,0.9);
283
+ transform: translateY(-1px);
284
+ `,
285
+ };
286
+ return baseCSS + `
287
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
288
+ :host { display: inline-block; }
289
+ :host([disabled]), :host([loading]) { pointer-events: none; opacity: 0.45; }
290
+
291
+ .btn {
292
+ position: relative;
293
+ display: inline-flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ gap: 8px;
297
+ height: ${sz.height};
298
+ padding: ${sz.padding};
299
+ border-radius: ${sz.radius};
300
+ font-family: var(--kayf-font-sans);
301
+ font-size: ${sz.fontSize};
302
+ letter-spacing: 0.04em;
303
+ cursor: pointer;
304
+ overflow: hidden;
305
+ transition: background 0.25s, border-color 0.25s, box-shadow 0.25s, transform 0.2s, color 0.25s;
306
+ white-space: nowrap;
307
+ user-select: none;
308
+ outline: none;
309
+ ${vs[this.variant]}
310
+ }
311
+ .btn:hover { ${vh[this.variant]} }
312
+ .btn:active { transform: translateY(0) scale(0.98) !important; transition-duration: 0.1s; }
313
+ .btn:focus-visible { outline: 2px solid ${color}88; outline-offset: 3px; }
314
+
315
+ /* Beam sweep */
316
+ .beam {
317
+ position: absolute; inset: 0;
318
+ pointer-events: none; overflow: hidden; border-radius: inherit;
319
+ }
320
+ .beam::before {
321
+ content: '';
322
+ position: absolute;
323
+ top: 0; left: -120%; width: 60%; height: 100%;
324
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.08) 40%, rgba(255,255,255,0.18) 50%, rgba(255,255,255,0.08) 60%, transparent);
325
+ transform: skewX(-20deg);
326
+ }
327
+ .btn:hover .beam::before { animation: beam-sweep 0.55s cubic-bezier(0.4,0,0.2,1) forwards; }
328
+ @keyframes beam-sweep { to { left: 160%; } }
329
+
330
+ /* Ripple */
331
+ .ripple {
332
+ position: absolute; border-radius: 50%;
333
+ background: rgba(255,255,255,0.15);
334
+ transform: scale(0);
335
+ animation: ripple-out 0.5s ease-out forwards;
336
+ pointer-events: none;
337
+ }
338
+ @keyframes ripple-out { to { transform: scale(4); opacity: 0; } }
339
+
340
+ /* Spinner */
341
+ .spinner {
342
+ display: none; width: 14px; height: 14px;
343
+ border: 2px solid currentColor; border-top-color: transparent;
344
+ border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0;
345
+ }
346
+ :host([loading]) .spinner { display: block; }
347
+ :host([loading]) .label { opacity: 0.6; }
348
+ @keyframes spin { to { transform: rotate(360deg); } }
349
+
350
+ /* Icon slots */
351
+ .icon-wrap { display: inline-flex; align-items: center; width: 16px; height: 16px; flex-shrink: 0; }
352
+ `;
353
+ }
354
+ template() {
355
+ return `
356
+ <button class="btn" part="button" type="button">
357
+ <span class="beam"></span>
358
+ <span class="spinner"></span>
359
+ <span class="icon-wrap"><slot name="icon-left"></slot></span>
360
+ <span class="label"><slot></slot></span>
361
+ <span class="icon-wrap"><slot name="icon-right"></slot></span>
362
+ </button>
363
+ `;
364
+ }
365
+ setup() {
366
+ const btn = this.root.querySelector('.btn');
367
+ if (!btn)
368
+ return;
369
+ const onPointerDown = (e) => {
370
+ const rect = btn.getBoundingClientRect();
371
+ const size = Math.max(rect.width, rect.height);
372
+ const x = e.clientX - rect.left - size / 2;
373
+ const y = e.clientY - rect.top - size / 2;
374
+ const ripple = document.createElement('span');
375
+ ripple.className = 'ripple';
376
+ ripple.style.cssText = `width:${size}px;height:${size}px;left:${x}px;top:${y}px;`;
377
+ btn.appendChild(ripple);
378
+ ripple.addEventListener('animationend', () => ripple.remove());
379
+ };
380
+ const onClick = (e) => {
381
+ if (this.isDisabled || this.isLoading) {
382
+ e.stopPropagation();
383
+ return;
384
+ }
385
+ this.dispatchEvent(new CustomEvent('kayf-click', {
386
+ bubbles: true, composed: true, detail: { originalEvent: e },
387
+ }));
388
+ };
389
+ btn.addEventListener('pointerdown', onPointerDown);
390
+ btn.addEventListener('click', onClick);
391
+ this._btn = btn;
392
+ this._pd = onPointerDown;
393
+ this._oc = onClick;
394
+ }
395
+ cleanup() {
396
+ const btn = this._btn;
397
+ if (btn) {
398
+ btn.removeEventListener('pointerdown', this._pd);
399
+ btn.removeEventListener('click', this._oc);
400
+ }
401
+ }
402
+ }
403
+ if (!customElements.get('kayf-beam-button')) {
404
+ customElements.define('kayf-beam-button', BeamButton);
405
+ }
406
+
407
+ /**
408
+ * <kayf-aurora-card>
409
+ *
410
+ * Атрибуты:
411
+ * speed — slow | normal | fast (default: normal)
412
+ * blur — sm | md | lg (default: md)
413
+ * opacity — 0.0–1.0 (default: 0.6)
414
+ * colors — через запятую: "cyan,violet,emerald" (default: cyan,violet,emerald,amber)
415
+ * static — boolean, отключает анимацию
416
+ *
417
+ * Слоты:
418
+ * (default) — контент поверх aurora
419
+ *
420
+ * Пример:
421
+ * <kayf-aurora-card colors="cyan,violet" speed="slow">
422
+ * <h2>Title</h2>
423
+ * <p>Content</p>
424
+ * </kayf-aurora-card>
425
+ */
426
+ const COLOR_MAP = {
427
+ cyan: [0, 212, 255],
428
+ violet: [124, 58, 237],
429
+ emerald: [16, 185, 129],
430
+ amber: [245, 158, 11],
431
+ red: [239, 68, 68],
432
+ white: [232, 232, 240],
433
+ };
434
+ const SPEED_MAP$1 = {
435
+ slow: 0.00015, normal: 0.0003, fast: 0.0006,
436
+ };
437
+ const BLUR_MAP = {
438
+ sm: 80, md: 120, lg: 180,
439
+ };
440
+ class AuroraCard extends KayfElement {
441
+ constructor() {
442
+ super(...arguments);
443
+ Object.defineProperty(this, "_canvas", {
444
+ enumerable: true,
445
+ configurable: true,
446
+ writable: true,
447
+ value: void 0
448
+ });
449
+ Object.defineProperty(this, "_ctx", {
450
+ enumerable: true,
451
+ configurable: true,
452
+ writable: true,
453
+ value: void 0
454
+ });
455
+ Object.defineProperty(this, "_blobs", {
456
+ enumerable: true,
457
+ configurable: true,
458
+ writable: true,
459
+ value: []
460
+ });
461
+ Object.defineProperty(this, "_raf", {
462
+ enumerable: true,
463
+ configurable: true,
464
+ writable: true,
465
+ value: 0
466
+ });
467
+ Object.defineProperty(this, "_resizeOb", {
468
+ enumerable: true,
469
+ configurable: true,
470
+ writable: true,
471
+ value: void 0
472
+ });
473
+ }
474
+ static get observedAttributes() {
475
+ return ['speed', 'blur', 'opacity', 'colors', 'static'];
476
+ }
477
+ // Переименованы чтобы не конфликтовать с HTMLElement.blur()
478
+ get speedValue() { return SPEED_MAP$1[this.attr('speed', 'normal')] ?? 0.0003; }
479
+ get blurSize() { return BLUR_MAP[this.attr('blur', 'md')] ?? 120; }
480
+ get opacityValue() { return this.numAttr('opacity', 0.6); }
481
+ get isStatic() { return this.boolAttr('static'); }
482
+ get colorList() {
483
+ return this.attr('colors', 'cyan,violet,emerald,amber')
484
+ .split(',')
485
+ .map(s => COLOR_MAP[s.trim()] ?? COLOR_MAP.cyan);
486
+ }
487
+ styles() {
488
+ return baseCSS + `
489
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
490
+
491
+ :host {
492
+ display: block;
493
+ position: relative;
494
+ border-radius: 20px;
495
+ overflow: hidden;
496
+ }
497
+
498
+ .wrap {
499
+ position: relative;
500
+ border-radius: 20px;
501
+ overflow: hidden;
502
+ border: 1px solid rgba(255,255,255,0.07);
503
+ }
504
+
505
+ canvas {
506
+ position: absolute;
507
+ inset: 0;
508
+ width: 100%;
509
+ height: 100%;
510
+ border-radius: 20px;
511
+ }
512
+
513
+ .content {
514
+ position: relative;
515
+ z-index: 1;
516
+ background: rgba(5, 5, 8, 0.55);
517
+ backdrop-filter: blur(12px);
518
+ -webkit-backdrop-filter: blur(12px);
519
+ border-radius: 19px;
520
+ min-height: 200px;
521
+ padding: 40px;
522
+ }
523
+
524
+ ::slotted(h1), ::slotted(h2), ::slotted(h3) {
525
+ font-family: var(--kayf-font-sans);
526
+ font-weight: 800;
527
+ color: #fff;
528
+ margin-bottom: 12px;
529
+ line-height: 1.1;
530
+ }
531
+
532
+ ::slotted(p) {
533
+ font-family: var(--kayf-font-mono);
534
+ font-size: 13px;
535
+ color: rgba(232,232,240,0.5);
536
+ line-height: 1.7;
537
+ font-weight: 300;
538
+ }
539
+ `;
540
+ }
541
+ template() {
542
+ return `
543
+ <div class="wrap">
544
+ <canvas part="canvas"></canvas>
545
+ <div class="content">
546
+ <slot></slot>
547
+ </div>
548
+ </div>
549
+ `;
550
+ }
551
+ setup() {
552
+ // Останавливаем предыдущий цикл при реактивном обновлении атрибутов
553
+ cancelAnimationFrame(this._raf);
554
+ this._resizeOb?.disconnect();
555
+ const canvas = this.root.querySelector('canvas');
556
+ const ctx = canvas.getContext('2d');
557
+ if (!ctx)
558
+ return;
559
+ this._canvas = canvas;
560
+ this._ctx = ctx;
561
+ this._initBlobs();
562
+ this._resize();
563
+ this._resizeOb = new ResizeObserver(() => this._resize());
564
+ this._resizeOb.observe(this);
565
+ if (this.isStatic) {
566
+ this._drawFrame(0);
567
+ }
568
+ else {
569
+ this._startLoop();
570
+ }
571
+ }
572
+ cleanup() {
573
+ cancelAnimationFrame(this._raf);
574
+ this._resizeOb?.disconnect();
575
+ }
576
+ _initBlobs() {
577
+ const colors = this.colorList;
578
+ this._blobs = colors.map((rgb, i) => ({
579
+ x: 0.15 + (i / colors.length) * 0.7,
580
+ y: 0.2 + Math.sin(i * 1.3) * 0.35 + 0.15,
581
+ rgb,
582
+ phase: i * (Math.PI * 2 / colors.length),
583
+ speed: this.speedValue * (0.7 + Math.random() * 0.6),
584
+ }));
585
+ }
586
+ _resize() {
587
+ this._canvas.width = this.offsetWidth || 600;
588
+ this._canvas.height = this.offsetHeight || 300;
589
+ if (this.isStatic)
590
+ this._drawFrame(0);
591
+ }
592
+ _startLoop() {
593
+ const tick = (t) => {
594
+ this._drawFrame(t);
595
+ this._raf = requestAnimationFrame(tick);
596
+ };
597
+ this._raf = requestAnimationFrame(tick);
598
+ }
599
+ _drawFrame(t) {
600
+ const { _ctx: ctx, _canvas: canvas, _blobs: blobs } = this;
601
+ const { width: w, height: h } = canvas;
602
+ if (!w || !h)
603
+ return;
604
+ ctx.clearRect(0, 0, w, h);
605
+ const blurPx = this.blurSize;
606
+ const opac = this.opacityValue;
607
+ const minDim = Math.min(w, h);
608
+ blobs.forEach(b => {
609
+ const bx = this.isStatic
610
+ ? b.x
611
+ : b.x + Math.sin(t * b.speed + b.phase) * 0.18;
612
+ const by = this.isStatic
613
+ ? b.y
614
+ : b.y + Math.cos(t * b.speed * 0.7 + b.phase) * 0.12;
615
+ const px = bx * w;
616
+ const py = by * h;
617
+ const r = 0.32 * minDim + blurPx;
618
+ const grad = ctx.createRadialGradient(px, py, 0, px, py, r);
619
+ grad.addColorStop(0, `rgba(${b.rgb.join(',')},${opac})`);
620
+ grad.addColorStop(0.5, `rgba(${b.rgb.join(',')},${opac * 0.35})`);
621
+ grad.addColorStop(1, `rgba(${b.rgb.join(',')},0)`);
622
+ ctx.fillStyle = grad;
623
+ ctx.beginPath();
624
+ ctx.arc(px, py, r, 0, Math.PI * 2);
625
+ ctx.fill();
626
+ });
627
+ }
628
+ }
629
+ if (!customElements.get('kayf-aurora-card')) {
630
+ customElements.define('kayf-aurora-card', AuroraCard);
631
+ }
632
+
633
+ const INTENSITY_MAP = {
634
+ subtle: { shiftPx: 1.2, layerOpacity: 0.55, shadowAlpha: 0.28 },
635
+ medium: { shiftPx: 2.2, layerOpacity: 0.72, shadowAlpha: 0.35 },
636
+ hard: { shiftPx: 3.2, layerOpacity: 0.84, shadowAlpha: 0.45 },
637
+ };
638
+ const SPEED_MAP = {
639
+ slow: { layerMs: 3900, flickerMs: 5300, burstMs: 2400 },
640
+ normal: { layerMs: 2500, flickerMs: 3600, burstMs: 1700 },
641
+ fast: { layerMs: 1700, flickerMs: 2400, burstMs: 1100 },
642
+ };
643
+ class GlitchText extends KayfElement {
644
+ constructor() {
645
+ super(...arguments);
646
+ Object.defineProperty(this, "_wrap", {
647
+ enumerable: true,
648
+ configurable: true,
649
+ writable: true,
650
+ value: void 0
651
+ });
652
+ Object.defineProperty(this, "_slot", {
653
+ enumerable: true,
654
+ configurable: true,
655
+ writable: true,
656
+ value: void 0
657
+ });
658
+ Object.defineProperty(this, "_layerA", {
659
+ enumerable: true,
660
+ configurable: true,
661
+ writable: true,
662
+ value: void 0
663
+ });
664
+ Object.defineProperty(this, "_layerB", {
665
+ enumerable: true,
666
+ configurable: true,
667
+ writable: true,
668
+ value: void 0
669
+ });
670
+ Object.defineProperty(this, "_onSlotChange", {
671
+ enumerable: true,
672
+ configurable: true,
673
+ writable: true,
674
+ value: void 0
675
+ });
676
+ Object.defineProperty(this, "_onEnter", {
677
+ enumerable: true,
678
+ configurable: true,
679
+ writable: true,
680
+ value: void 0
681
+ });
682
+ Object.defineProperty(this, "_observer", {
683
+ enumerable: true,
684
+ configurable: true,
685
+ writable: true,
686
+ value: void 0
687
+ });
688
+ Object.defineProperty(this, "_burstInterval", {
689
+ enumerable: true,
690
+ configurable: true,
691
+ writable: true,
692
+ value: 0
693
+ });
694
+ Object.defineProperty(this, "_burstTimeout", {
695
+ enumerable: true,
696
+ configurable: true,
697
+ writable: true,
698
+ value: 0
699
+ });
700
+ }
701
+ static get observedAttributes() {
702
+ return ['color', 'intensity', 'speed', 'text', 'static', 'uppercase'];
703
+ }
704
+ get color() { return this.attr('color', 'cyan'); }
705
+ get intensity() { return this.attr('intensity', 'medium'); }
706
+ get speed() { return this.attr('speed', 'normal'); }
707
+ get isStatic() { return this.boolAttr('static'); }
708
+ get isUppercase() { return this.boolAttr('uppercase'); }
709
+ styles() {
710
+ const color = getColor(this.color);
711
+ const level = INTENSITY_MAP[this.intensity] ?? INTENSITY_MAP.medium;
712
+ const speed = SPEED_MAP[this.speed] ?? SPEED_MAP.normal;
713
+ return baseCSS + `
714
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
715
+ :host {
716
+ display: inline-block;
717
+ position: relative;
718
+ color: ${color};
719
+ }
720
+
721
+ .wrap {
722
+ position: relative;
723
+ display: inline-grid;
724
+ align-items: center;
725
+ justify-items: start;
726
+ line-height: 1.05;
727
+ letter-spacing: 0.03em;
728
+ font-family: var(--kayf-font-sans);
729
+ font-weight: 800;
730
+ isolation: isolate;
731
+ text-shadow: 0 0 16px rgba(255,255,255,${level.shadowAlpha});
732
+ ${this.isUppercase ? 'text-transform: uppercase;' : ''}
733
+ }
734
+
735
+ .content,
736
+ .layer {
737
+ grid-area: 1 / 1;
738
+ white-space: pre-wrap;
739
+ }
740
+
741
+ .content {
742
+ position: relative;
743
+ z-index: 2;
744
+ color: rgba(255,255,255,0.96);
745
+ filter: drop-shadow(0 0 6px ${color}44);
746
+ animation: main-flicker ${speed.flickerMs}ms linear infinite;
747
+ }
748
+
749
+ .layer {
750
+ position: relative;
751
+ z-index: 1;
752
+ pointer-events: none;
753
+ opacity: ${level.layerOpacity};
754
+ mix-blend-mode: screen;
755
+ }
756
+
757
+ .layer-a {
758
+ color: ${color};
759
+ transform: translateX(calc(var(--offset-x, 0px) * -1));
760
+ animation:
761
+ glitch-clip-a ${speed.layerMs}ms steps(2, end) infinite,
762
+ glitch-shift-a ${Math.round(speed.layerMs * 0.9)}ms steps(1, end) infinite;
763
+ }
764
+
765
+ .layer-b {
766
+ color: #ff4d8d;
767
+ transform: translateX(var(--offset-x, 0px));
768
+ animation:
769
+ glitch-clip-b ${Math.round(speed.layerMs * 1.1)}ms steps(2, end) infinite,
770
+ glitch-shift-b ${Math.round(speed.layerMs * 0.8)}ms steps(1, end) infinite;
771
+ }
772
+
773
+ .scanline {
774
+ position: absolute;
775
+ left: -2%;
776
+ width: 104%;
777
+ height: 2px;
778
+ top: -15%;
779
+ opacity: 0;
780
+ pointer-events: none;
781
+ background: linear-gradient(90deg, transparent, ${color}99, transparent);
782
+ filter: blur(0.5px);
783
+ z-index: 3;
784
+ animation: scan ${Math.round(speed.layerMs * 1.25)}ms linear infinite;
785
+ }
786
+
787
+ .wrap.is-burst .layer-a,
788
+ .wrap.is-burst .layer-b {
789
+ opacity: 0.98;
790
+ }
791
+
792
+ .wrap.is-burst .layer-a { transform: translateX(${level.shiftPx * -1.65}px); }
793
+ .wrap.is-burst .layer-b { transform: translateX(${level.shiftPx * 1.65}px); }
794
+ .wrap.is-burst .content { opacity: 0.88; }
795
+
796
+ :host([static]) .content,
797
+ :host([static]) .layer,
798
+ :host([static]) .scanline {
799
+ animation: none !important;
800
+ }
801
+
802
+ :host([static]) .layer { opacity: 0.25; }
803
+ :host([static]) .scanline { display: none; }
804
+
805
+ @keyframes glitch-clip-a {
806
+ 0%, 7%, 58%, 100% { clip-path: inset(0 0 0 0); }
807
+ 8% { clip-path: inset(12% 0 72% 0); }
808
+ 14% { clip-path: inset(50% 0 28% 0); }
809
+ 20% { clip-path: inset(72% 0 6% 0); }
810
+ 36% { clip-path: inset(32% 0 40% 0); }
811
+ }
812
+
813
+ @keyframes glitch-clip-b {
814
+ 0%, 9%, 62%, 100% { clip-path: inset(0 0 0 0); }
815
+ 10% { clip-path: inset(70% 0 8% 0); }
816
+ 16% { clip-path: inset(18% 0 62% 0); }
817
+ 28% { clip-path: inset(45% 0 29% 0); }
818
+ 43% { clip-path: inset(8% 0 77% 0); }
819
+ }
820
+
821
+ @keyframes glitch-shift-a {
822
+ 0%, 58%, 100% { transform: translateX(0); }
823
+ 7% { transform: translateX(${level.shiftPx * -1.2}px); }
824
+ 13% { transform: translateX(${level.shiftPx * 0.45}px); }
825
+ 19% { transform: translateX(${level.shiftPx * -0.9}px); }
826
+ }
827
+
828
+ @keyframes glitch-shift-b {
829
+ 0%, 63%, 100% { transform: translateX(0); }
830
+ 8% { transform: translateX(${level.shiftPx * 1.1}px); }
831
+ 15% { transform: translateX(${level.shiftPx * -0.35}px); }
832
+ 22% { transform: translateX(${level.shiftPx * 0.8}px); }
833
+ }
834
+
835
+ @keyframes main-flicker {
836
+ 0%, 13%, 40%, 64%, 100% { opacity: 1; }
837
+ 14% { opacity: 0.82; }
838
+ 41% { opacity: 0.9; }
839
+ 66% { opacity: 0.86; }
840
+ }
841
+
842
+ @keyframes scan {
843
+ 0% { top: -15%; opacity: 0; }
844
+ 8% { opacity: 0.4; }
845
+ 30% { opacity: 0.2; }
846
+ 52% { opacity: 0.35; }
847
+ 100% { top: 115%; opacity: 0; }
848
+ }
849
+ `;
850
+ }
851
+ template() {
852
+ return `
853
+ <span class="wrap">
854
+ <span class="scanline" aria-hidden="true"></span>
855
+ <span class="layer layer-a" aria-hidden="true"></span>
856
+ <span class="layer layer-b" aria-hidden="true"></span>
857
+ <span class="content"><slot></slot></span>
858
+ </span>
859
+ `;
860
+ }
861
+ setup() {
862
+ this.cleanup();
863
+ this._wrap = this.root.querySelector('.wrap') ?? undefined;
864
+ this._slot = this.root.querySelector('slot') ?? undefined;
865
+ this._layerA = this.root.querySelector('.layer-a') ?? undefined;
866
+ this._layerB = this.root.querySelector('.layer-b') ?? undefined;
867
+ if (!this._wrap || !this._slot || !this._layerA || !this._layerB)
868
+ return;
869
+ const syncText = () => {
870
+ const text = this._resolveText(this._slot);
871
+ this._layerA.textContent = text;
872
+ this._layerB.textContent = text;
873
+ this._wrap.style.setProperty('--offset-x', `${(INTENSITY_MAP[this.intensity] ?? INTENSITY_MAP.medium).shiftPx}px`);
874
+ };
875
+ this._onSlotChange = syncText;
876
+ this._slot.addEventListener('slotchange', this._onSlotChange);
877
+ syncText();
878
+ this._observer = new MutationObserver(syncText);
879
+ this._observer.observe(this, { subtree: true, childList: true, characterData: true });
880
+ if (this.isStatic)
881
+ return;
882
+ this._onEnter = () => this._burst();
883
+ this._wrap.addEventListener('pointerenter', this._onEnter);
884
+ const step = SPEED_MAP[this.speed] ?? SPEED_MAP.normal;
885
+ this._burstInterval = window.setInterval(() => {
886
+ if (Math.random() > 0.52)
887
+ this._burst();
888
+ }, step.burstMs);
889
+ }
890
+ cleanup() {
891
+ if (this._slot && this._onSlotChange) {
892
+ this._slot.removeEventListener('slotchange', this._onSlotChange);
893
+ }
894
+ if (this._wrap && this._onEnter) {
895
+ this._wrap.removeEventListener('pointerenter', this._onEnter);
896
+ }
897
+ this._observer?.disconnect();
898
+ if (this._burstInterval) {
899
+ window.clearInterval(this._burstInterval);
900
+ this._burstInterval = 0;
901
+ }
902
+ if (this._burstTimeout) {
903
+ window.clearTimeout(this._burstTimeout);
904
+ this._burstTimeout = 0;
905
+ }
906
+ }
907
+ _resolveText(slot) {
908
+ const assigned = slot.assignedNodes({ flatten: true });
909
+ const fromSlot = assigned.map(node => node.textContent ?? '').join(' ')
910
+ .replace(/\s+/g, ' ')
911
+ .trim();
912
+ return fromSlot || this.attr('text', '').trim();
913
+ }
914
+ _burst() {
915
+ if (!this._wrap)
916
+ return;
917
+ this._wrap.classList.add('is-burst');
918
+ if (this._burstTimeout)
919
+ window.clearTimeout(this._burstTimeout);
920
+ this._burstTimeout = window.setTimeout(() => {
921
+ this._wrap?.classList.remove('is-burst');
922
+ this._burstTimeout = 0;
923
+ }, 130);
924
+ }
925
+ }
926
+ if (!customElements.get('kayf-glitch-text')) {
927
+ customElements.define('kayf-glitch-text', GlitchText);
928
+ }
929
+
930
+ /**
931
+ * <kayf-hud-panel>
932
+ *
933
+ * Атрибуты:
934
+ * color - cyan | violet | emerald | amber | red | white (default: cyan)
935
+ * glow - none | soft | medium | strong (default: soft)
936
+ * radius - border-radius px (default: 16)
937
+ * padding - внутренний отступ контента px (default: 20)
938
+ *
939
+ * Слоты:
940
+ * title - заголовок в верхней части
941
+ * meta - правый верхний блок (статус/значение)
942
+ * footer - нижняя строка/действия
943
+ * (default) - основной контент
944
+ *
945
+ * Пример:
946
+ * <kayf-hud-panel color="emerald" glow="medium">
947
+ * <span slot="title">Telemetry</span>
948
+ * <span slot="meta">ONLINE</span>
949
+ * <p>Signal quality 98%</p>
950
+ * </kayf-hud-panel>
951
+ */
952
+ const GLOW_MAP = {
953
+ none: 0, soft: 0.16, medium: 0.26, strong: 0.4,
954
+ };
955
+ class HudPanel extends KayfElement {
956
+ constructor() {
957
+ super(...arguments);
958
+ Object.defineProperty(this, "_panel", {
959
+ enumerable: true,
960
+ configurable: true,
961
+ writable: true,
962
+ value: void 0
963
+ });
964
+ Object.defineProperty(this, "_spot", {
965
+ enumerable: true,
966
+ configurable: true,
967
+ writable: true,
968
+ value: void 0
969
+ });
970
+ Object.defineProperty(this, "_titleSlot", {
971
+ enumerable: true,
972
+ configurable: true,
973
+ writable: true,
974
+ value: void 0
975
+ });
976
+ Object.defineProperty(this, "_metaSlot", {
977
+ enumerable: true,
978
+ configurable: true,
979
+ writable: true,
980
+ value: void 0
981
+ });
982
+ Object.defineProperty(this, "_footerSlot", {
983
+ enumerable: true,
984
+ configurable: true,
985
+ writable: true,
986
+ value: void 0
987
+ });
988
+ Object.defineProperty(this, "_onMove", {
989
+ enumerable: true,
990
+ configurable: true,
991
+ writable: true,
992
+ value: void 0
993
+ });
994
+ Object.defineProperty(this, "_onLeave", {
995
+ enumerable: true,
996
+ configurable: true,
997
+ writable: true,
998
+ value: void 0
999
+ });
1000
+ Object.defineProperty(this, "_onSlotChange", {
1001
+ enumerable: true,
1002
+ configurable: true,
1003
+ writable: true,
1004
+ value: void 0
1005
+ });
1006
+ }
1007
+ static get observedAttributes() {
1008
+ return ['color', 'glow', 'radius', 'padding'];
1009
+ }
1010
+ get color() { return this.attr('color', 'cyan'); }
1011
+ get glowLevel() { return this.attr('glow', 'soft'); }
1012
+ get radius() { return this.numAttr('radius', 16); }
1013
+ get padding() { return this.numAttr('padding', 20); }
1014
+ styles() {
1015
+ const color = getColor(this.color);
1016
+ const spot = getSpotlight(this.color);
1017
+ const glow = getGlow(this.color);
1018
+ const glowAlpha = GLOW_MAP[this.glowLevel] ?? GLOW_MAP.soft;
1019
+ const glowHover = glow.replace('0.2', String(glowAlpha));
1020
+ const radius = Math.max(0, this.radius);
1021
+ const innerRadius = Math.max(0, radius - 1);
1022
+ const pad = Math.max(8, this.padding);
1023
+ return baseCSS + `
1024
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1025
+ :host { display: block; position: relative; border-radius: ${radius}px; }
1026
+
1027
+ .hud-border {
1028
+ position: relative;
1029
+ border-radius: ${radius}px;
1030
+ padding: 1px;
1031
+ background:
1032
+ linear-gradient(150deg, ${color}44, rgba(255,255,255,0.08) 38%, rgba(255,255,255,0.03) 62%, ${color}22);
1033
+ transition: box-shadow 0.3s ease, background 0.3s ease;
1034
+ }
1035
+
1036
+ .panel {
1037
+ position: relative;
1038
+ border-radius: ${innerRadius}px;
1039
+ overflow: hidden;
1040
+ min-height: 140px;
1041
+ background:
1042
+ radial-gradient(circle at 20% 15%, rgba(255,255,255,0.045), transparent 40%),
1043
+ linear-gradient(165deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015) 30%, rgba(5,5,8,0.76) 100%);
1044
+ border: 1px solid rgba(255,255,255,0.06);
1045
+ box-shadow:
1046
+ inset 0 1px 0 rgba(255,255,255,0.06),
1047
+ 0 10px 26px rgba(0,0,0,0.25);
1048
+ transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
1049
+ }
1050
+
1051
+ :host(:hover) .hud-border {
1052
+ box-shadow: 0 0 34px ${glowHover};
1053
+ }
1054
+
1055
+ :host(:hover) .panel {
1056
+ transform: translateY(-2px);
1057
+ border-color: ${color}66;
1058
+ box-shadow:
1059
+ inset 0 1px 0 rgba(255,255,255,0.08),
1060
+ 0 14px 34px rgba(0,0,0,0.32),
1061
+ 0 0 30px ${glowHover};
1062
+ }
1063
+
1064
+ .spotlight {
1065
+ position: absolute;
1066
+ width: 360px;
1067
+ height: 360px;
1068
+ border-radius: 50%;
1069
+ pointer-events: none;
1070
+ transform: translate(-50%, -50%);
1071
+ background: radial-gradient(circle, ${spot}, transparent 72%);
1072
+ opacity: 0;
1073
+ transition: opacity 0.3s ease;
1074
+ z-index: 0;
1075
+ }
1076
+
1077
+ :host(:hover) .spotlight { opacity: 1; }
1078
+
1079
+ .grid {
1080
+ position: absolute;
1081
+ inset: 0;
1082
+ pointer-events: none;
1083
+ z-index: 0;
1084
+ opacity: 0.18;
1085
+ background-image:
1086
+ linear-gradient(${color}26 1px, transparent 1px),
1087
+ linear-gradient(90deg, ${color}1f 1px, transparent 1px);
1088
+ background-size: 24px 24px, 24px 24px;
1089
+ mask-image: linear-gradient(to bottom, rgba(0,0,0,0.55), transparent 75%);
1090
+ }
1091
+
1092
+ .scanline {
1093
+ position: absolute;
1094
+ top: -20%;
1095
+ left: 0;
1096
+ width: 100%;
1097
+ height: 1px;
1098
+ background: linear-gradient(90deg, transparent, ${color}88, transparent);
1099
+ opacity: 0.24;
1100
+ pointer-events: none;
1101
+ z-index: 1;
1102
+ animation: scan 5.4s linear infinite;
1103
+ }
1104
+
1105
+ .corner {
1106
+ position: absolute;
1107
+ width: 16px;
1108
+ height: 16px;
1109
+ border-color: ${color}88;
1110
+ border-style: solid;
1111
+ z-index: 2;
1112
+ pointer-events: none;
1113
+ }
1114
+
1115
+ .corner.tl { top: 8px; left: 8px; border-width: 1px 0 0 1px; border-radius: 3px 0 0 0; }
1116
+ .corner.tr { top: 8px; right: 8px; border-width: 1px 1px 0 0; border-radius: 0 3px 0 0; }
1117
+ .corner.bl { bottom: 8px; left: 8px; border-width: 0 0 1px 1px; border-radius: 0 0 0 3px; }
1118
+ .corner.br { bottom: 8px; right: 8px; border-width: 0 1px 1px 0; border-radius: 0 0 3px 0; }
1119
+
1120
+ .frame-line {
1121
+ position: absolute;
1122
+ left: ${pad}px;
1123
+ right: ${pad}px;
1124
+ height: 1px;
1125
+ background: linear-gradient(90deg, transparent, ${color}66 20%, ${color}66 80%, transparent);
1126
+ opacity: 0.42;
1127
+ z-index: 2;
1128
+ pointer-events: none;
1129
+ }
1130
+
1131
+ .frame-line.top { top: ${pad - 1}px; }
1132
+ .frame-line.bottom { bottom: ${pad - 1}px; }
1133
+
1134
+ .content {
1135
+ position: relative;
1136
+ z-index: 3;
1137
+ padding: ${pad}px;
1138
+ display: grid;
1139
+ gap: 14px;
1140
+ }
1141
+
1142
+ .header,
1143
+ .footer {
1144
+ display: none;
1145
+ }
1146
+
1147
+ .panel.has-header .header {
1148
+ display: flex;
1149
+ align-items: flex-start;
1150
+ justify-content: space-between;
1151
+ gap: 12px;
1152
+ padding-bottom: 8px;
1153
+ border-bottom: 1px solid rgba(255,255,255,0.06);
1154
+ }
1155
+
1156
+ .panel.has-footer .footer {
1157
+ display: block;
1158
+ padding-top: 8px;
1159
+ border-top: 1px solid rgba(255,255,255,0.06);
1160
+ }
1161
+
1162
+ .title {
1163
+ font-family: var(--kayf-font-sans);
1164
+ font-size: 14px;
1165
+ font-weight: 700;
1166
+ letter-spacing: 0.08em;
1167
+ text-transform: uppercase;
1168
+ color: rgba(255,255,255,0.94);
1169
+ }
1170
+
1171
+ .meta {
1172
+ font-family: var(--kayf-font-mono);
1173
+ font-size: 12px;
1174
+ line-height: 1.3;
1175
+ letter-spacing: 0.08em;
1176
+ text-transform: uppercase;
1177
+ color: ${color};
1178
+ text-shadow: 0 0 10px ${color}55;
1179
+ }
1180
+
1181
+ .body {
1182
+ font-family: var(--kayf-font-mono);
1183
+ font-size: 13px;
1184
+ line-height: 1.65;
1185
+ color: var(--kayf-text);
1186
+ }
1187
+
1188
+ ::slotted(p) {
1189
+ margin: 0;
1190
+ color: var(--kayf-muted);
1191
+ }
1192
+
1193
+ @keyframes scan {
1194
+ 0% { top: -20%; opacity: 0; }
1195
+ 10% { opacity: 0.3; }
1196
+ 55% { opacity: 0.14; }
1197
+ 100% { top: 120%; opacity: 0; }
1198
+ }
1199
+ `;
1200
+ }
1201
+ template() {
1202
+ return `
1203
+ <div class="hud-border">
1204
+ <section class="panel" part="panel">
1205
+ <div class="spotlight"></div>
1206
+ <div class="grid"></div>
1207
+ <div class="scanline"></div>
1208
+ <span class="corner tl"></span>
1209
+ <span class="corner tr"></span>
1210
+ <span class="corner bl"></span>
1211
+ <span class="corner br"></span>
1212
+ <span class="frame-line top"></span>
1213
+ <span class="frame-line bottom"></span>
1214
+ <div class="content">
1215
+ <header class="header">
1216
+ <div class="title"><slot name="title"></slot></div>
1217
+ <div class="meta"><slot name="meta"></slot></div>
1218
+ </header>
1219
+ <div class="body"><slot></slot></div>
1220
+ <footer class="footer"><slot name="footer"></slot></footer>
1221
+ </div>
1222
+ </section>
1223
+ </div>
1224
+ `;
1225
+ }
1226
+ setup() {
1227
+ this.cleanup();
1228
+ this._panel = this.root.querySelector('.panel') ?? undefined;
1229
+ this._spot = this.root.querySelector('.spotlight') ?? undefined;
1230
+ this._titleSlot = this.root.querySelector('slot[name="title"]') ?? undefined;
1231
+ this._metaSlot = this.root.querySelector('slot[name="meta"]') ?? undefined;
1232
+ this._footerSlot = this.root.querySelector('slot[name="footer"]') ?? undefined;
1233
+ if (!this._panel || !this._spot)
1234
+ return;
1235
+ this._onMove = (e) => {
1236
+ const rect = this._panel.getBoundingClientRect();
1237
+ this._spot.style.left = (e.clientX - rect.left) + 'px';
1238
+ this._spot.style.top = (e.clientY - rect.top) + 'px';
1239
+ };
1240
+ this._onLeave = () => {
1241
+ this._spot.style.left = '50%';
1242
+ this._spot.style.top = '50%';
1243
+ };
1244
+ this._panel.addEventListener('mousemove', this._onMove);
1245
+ this._panel.addEventListener('mouseleave', this._onLeave);
1246
+ this._onLeave();
1247
+ this._onSlotChange = () => this._syncSlotState();
1248
+ if (this._titleSlot)
1249
+ this._titleSlot.addEventListener('slotchange', this._onSlotChange);
1250
+ if (this._metaSlot)
1251
+ this._metaSlot.addEventListener('slotchange', this._onSlotChange);
1252
+ if (this._footerSlot)
1253
+ this._footerSlot.addEventListener('slotchange', this._onSlotChange);
1254
+ this._syncSlotState();
1255
+ }
1256
+ cleanup() {
1257
+ if (this._panel && this._onMove)
1258
+ this._panel.removeEventListener('mousemove', this._onMove);
1259
+ if (this._panel && this._onLeave)
1260
+ this._panel.removeEventListener('mouseleave', this._onLeave);
1261
+ if (this._titleSlot && this._onSlotChange)
1262
+ this._titleSlot.removeEventListener('slotchange', this._onSlotChange);
1263
+ if (this._metaSlot && this._onSlotChange)
1264
+ this._metaSlot.removeEventListener('slotchange', this._onSlotChange);
1265
+ if (this._footerSlot && this._onSlotChange)
1266
+ this._footerSlot.removeEventListener('slotchange', this._onSlotChange);
1267
+ }
1268
+ _syncSlotState() {
1269
+ if (!this._panel)
1270
+ return;
1271
+ const hasTitle = this._slotHasContent(this._titleSlot);
1272
+ const hasMeta = this._slotHasContent(this._metaSlot);
1273
+ const hasFooter = this._slotHasContent(this._footerSlot);
1274
+ this._panel.classList.toggle('has-header', hasTitle || hasMeta);
1275
+ this._panel.classList.toggle('has-footer', hasFooter);
1276
+ }
1277
+ _slotHasContent(slot) {
1278
+ if (!slot)
1279
+ return false;
1280
+ return slot.assignedNodes({ flatten: true }).some(node => {
1281
+ if (node.nodeType === Node.ELEMENT_NODE)
1282
+ return true;
1283
+ if (node.nodeType === Node.TEXT_NODE)
1284
+ return (node.textContent ?? '').trim().length > 0;
1285
+ return false;
1286
+ });
1287
+ }
1288
+ }
1289
+ if (!customElements.get('kayf-hud-panel')) {
1290
+ customElements.define('kayf-hud-panel', HudPanel);
1291
+ }
1292
+
1293
+ const version = '0.1.0';
1294
+
1295
+ export { AuroraCard, BeamButton, GlitchText, HudPanel, KayfElement, SpotlightCard, baseCSS, getColor, getGlow, getSpotlight, tokens, version };
1296
+ //# sourceMappingURL=kayf-ui.esm.js.map