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