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