@hyperframes/studio 0.8.70 → 0.8.72

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.
Files changed (29) hide show
  1. package/dist/assets/{hyperframes-player-CYdwxTwz.js → hyperframes-player-jTC1EDAI.js} +1 -1
  2. package/dist/assets/{index-BBOktYKC.css → index-0w6iIlwZ.css} +1 -1
  3. package/dist/assets/{index-Egnc_kWw.js → index-BqUGSWqN.js} +176 -176
  4. package/dist/assets/{index-D7hgjzBq.js → index-DpVD1igA.js} +1 -1
  5. package/dist/assets/{index-BPafHV3q.js → index-HuYqM2Fi.js} +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +834 -780
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles.css +1 -1
  11. package/package.json +8 -7
  12. package/src/App.tsx +5 -0
  13. package/src/components/CompositionMissingBanner.test.tsx +35 -0
  14. package/src/components/CompositionMissingBanner.tsx +32 -0
  15. package/src/components/nle/NLEPreview.test.ts +19 -1
  16. package/src/components/nle/NLEPreview.tsx +22 -7
  17. package/src/hooks/useExternalFileChangeCoordinator.test.tsx +36 -1
  18. package/src/hooks/useExternalFileChangeCoordinator.ts +19 -1
  19. package/src/hooks/useSdkSession.lifecycle.test.tsx +146 -0
  20. package/src/hooks/useSdkSession.ts +56 -8
  21. package/src/hooks/useStudioExternalFileChanges.ts +2 -0
  22. package/src/hooks/useStudioSdkSessions.ts +2 -0
  23. package/src/player/components/VolumeControl.styles.test.ts +1 -1
  24. package/src/player/components/timelineMotionStyles.test.ts +1 -1
  25. package/src/styles/componentStyles.test.ts +35 -0
  26. package/src/styles/components.css +843 -0
  27. package/src/styles/hex-baseline.json +1 -1
  28. package/src/styles/studio.css +1 -841
  29. package/src/styles/tokenGate.test.ts +1 -1
@@ -0,0 +1,843 @@
1
+ /* Rules for Studio's components, for any page that renders them. Reads the tokens in
2
+ * theme.css. The app shell (body, #root, preflight) stays in studio.css. */
3
+
4
+ .hf-color-grading-number::-webkit-outer-spin-button,
5
+ .hf-color-grading-number::-webkit-inner-spin-button {
6
+ margin: 0;
7
+ appearance: none;
8
+ }
9
+
10
+ .hf-color-grading-number {
11
+ -moz-appearance: textfield;
12
+ appearance: textfield;
13
+ }
14
+
15
+ .hf-color-grading-range {
16
+ height: 1.5rem;
17
+ cursor: default;
18
+ -webkit-appearance: none;
19
+ appearance: none;
20
+ background: transparent;
21
+ }
22
+
23
+ .hf-color-grading-range:disabled {
24
+ cursor: not-allowed;
25
+ opacity: 0.5;
26
+ }
27
+
28
+ .hf-color-grading-range::-webkit-slider-runnable-track {
29
+ height: 1.25rem;
30
+ border: 0;
31
+ background: transparent;
32
+ }
33
+
34
+ .hf-color-grading-range::-webkit-slider-thumb {
35
+ width: 0.625rem;
36
+ height: 1rem;
37
+ margin-top: 0.125rem;
38
+ border: 0;
39
+ border-radius: 999px;
40
+ -webkit-appearance: none;
41
+ appearance: none;
42
+ background: #ffffff;
43
+ box-shadow:
44
+ 0 0 0 2px #0c0c0e,
45
+ 0 1px 4px rgba(0, 0, 0, 0.55);
46
+ cursor: default;
47
+ }
48
+
49
+ .hf-color-grading-range::-moz-range-track {
50
+ height: 1.25rem;
51
+ border: 0;
52
+ background: transparent;
53
+ }
54
+
55
+ .hf-color-grading-range::-moz-range-thumb {
56
+ width: 0.625rem;
57
+ height: 1rem;
58
+ border: 0;
59
+ border-radius: 999px;
60
+ background: #ffffff;
61
+ box-shadow:
62
+ 0 0 0 2px #0c0c0e,
63
+ 0 1px 4px rgba(0, 0, 0, 0.55);
64
+ cursor: default;
65
+ }
66
+
67
+ .hf-color-grading-range:focus-visible {
68
+ outline: none;
69
+ }
70
+
71
+ .hf-color-grading-range:focus-visible::-webkit-slider-thumb {
72
+ box-shadow:
73
+ 0 0 0 2px #0c0c0e,
74
+ 0 0 0 4px rgba(60, 230, 172, 0.22),
75
+ 0 1px 4px rgba(0, 0, 0, 0.55);
76
+ }
77
+
78
+ .hf-color-grading-range:focus-visible::-moz-range-thumb {
79
+ box-shadow:
80
+ 0 0 0 2px #0c0c0e,
81
+ 0 0 0 4px rgba(60, 230, 172, 0.22),
82
+ 0 1px 4px rgba(0, 0, 0, 0.55);
83
+ }
84
+
85
+ .hf-preview-volume-range {
86
+ height: 1.5rem;
87
+ cursor: pointer;
88
+ appearance: none;
89
+ background: transparent;
90
+ }
91
+
92
+ .hf-preview-volume-range:disabled {
93
+ opacity: 0.3;
94
+ }
95
+
96
+ .hf-preview-volume-range::-webkit-slider-runnable-track {
97
+ height: 2px;
98
+ background: transparent;
99
+ }
100
+
101
+ .hf-preview-volume-range::-webkit-slider-thumb {
102
+ width: 0.5rem;
103
+ height: 0.5rem;
104
+ margin-top: -3px;
105
+ border: 0;
106
+ border-radius: 999px;
107
+ appearance: none;
108
+ background: #ffffff;
109
+ box-shadow:
110
+ 0 0 0 2px #0a0a0a,
111
+ 0 1px 3px rgba(0, 0, 0, 0.5);
112
+ cursor: grab;
113
+ }
114
+
115
+ .hf-preview-volume-range::-webkit-slider-thumb:active {
116
+ cursor: grabbing;
117
+ }
118
+
119
+ .hf-preview-volume-range::-moz-range-track,
120
+ .hf-preview-volume-range::-moz-range-progress {
121
+ height: 2px;
122
+ border: 0;
123
+ background: transparent;
124
+ }
125
+
126
+ .hf-preview-volume-range::-moz-range-thumb {
127
+ width: 0.5rem;
128
+ height: 0.5rem;
129
+ border: 0;
130
+ border-radius: 999px;
131
+ background: #ffffff;
132
+ box-shadow:
133
+ 0 0 0 2px #0a0a0a,
134
+ 0 1px 3px rgba(0, 0, 0, 0.5);
135
+ cursor: grab;
136
+ }
137
+
138
+ .hf-preview-volume-range::-moz-range-thumb:active {
139
+ cursor: grabbing;
140
+ }
141
+
142
+ .hf-preview-volume-range:focus-visible {
143
+ outline: none;
144
+ }
145
+
146
+ .hf-preview-volume-range:focus-visible::-webkit-slider-thumb,
147
+ .hf-preview-volume-range:focus-visible::-moz-range-thumb {
148
+ box-shadow:
149
+ 0 0 0 2px #0a0a0a,
150
+ 0 0 0 4px rgba(60, 230, 172, 0.4),
151
+ 0 1px 3px rgba(0, 0, 0, 0.5);
152
+ }
153
+
154
+ .timeline-clip {
155
+ background-color: var(--clip-bg);
156
+ border: 1px solid var(--clip-border);
157
+ border-radius: var(--timeline-clip-radius);
158
+ box-shadow:
159
+ inset 0 1px 0 var(--timeline-clip-highlight),
160
+ var(--timeline-clip-shadow);
161
+ }
162
+
163
+ .timeline-transition {
164
+ position: absolute;
165
+ width: 0;
166
+ height: 0;
167
+ pointer-events: none;
168
+ z-index: 30;
169
+ }
170
+
171
+ .timeline-transition__zone {
172
+ position: absolute;
173
+ left: calc(var(--timeline-transition-zone-width) / -2);
174
+ top: -21px;
175
+ width: var(--timeline-transition-zone-width);
176
+ height: 42px;
177
+ border-radius: 8px;
178
+ overflow: hidden;
179
+ background: var(--timeline-transition-zone);
180
+ filter: blur(2.5px);
181
+ opacity: 0.95;
182
+ }
183
+
184
+ .timeline-transition__image {
185
+ position: absolute;
186
+ inset: -4px;
187
+ background-position: center;
188
+ background-size: cover;
189
+ }
190
+
191
+ .timeline-transition__image--out {
192
+ mask-image: linear-gradient(90deg, black 0%, transparent 100%);
193
+ }
194
+
195
+ .timeline-transition__image--in {
196
+ mask-image: linear-gradient(90deg, transparent 0%, black 100%);
197
+ }
198
+
199
+ .timeline-transition__badge {
200
+ position: absolute;
201
+ left: -14px;
202
+ top: -14px;
203
+ display: grid;
204
+ width: 28px;
205
+ height: 28px;
206
+ place-items: center;
207
+ border: 1px solid color-mix(in srgb, var(--timeline-transition-badge-ink) 22%, transparent);
208
+ border-radius: 999px;
209
+ background: var(--timeline-transition-badge);
210
+ box-shadow: 0 2px 5px var(--timeline-playhead-shadow);
211
+ }
212
+
213
+ .timeline-transition__badge svg {
214
+ width: 17px;
215
+ height: 17px;
216
+ }
217
+ .timeline-transition__frame,
218
+ .timeline-transition__frame--out,
219
+ .timeline-transition__frame--in {
220
+ fill: none;
221
+ stroke: var(--timeline-transition-badge-ink);
222
+ stroke-width: 1.5;
223
+ }
224
+ .timeline-transition__intersection {
225
+ fill: var(--timeline-transition-badge-ink);
226
+ opacity: 0.7;
227
+ }
228
+
229
+ .timeline-clip::before {
230
+ content: "";
231
+ position: absolute;
232
+ inset: 0;
233
+ border-radius: inherit;
234
+ background: var(--clip-bg-active);
235
+ opacity: 0;
236
+ pointer-events: none;
237
+ }
238
+
239
+ .timeline-clip[data-active] {
240
+ border-color: var(--clip-border-active);
241
+ }
242
+
243
+ .timeline-clip[data-active]::before {
244
+ opacity: 1;
245
+ }
246
+
247
+ /* Audio clips read as a distinct kind — a persistent violet tint vs the teal-
248
+ accented visual clips — so the audio zone at the bottom is visually separate
249
+ at a glance (the timeline's kinds: visual on top, audio below). */
250
+ .timeline-clip.is-audio {
251
+ background-color: var(--timeline-clip-audio-bg);
252
+ border-color: var(--timeline-clip-audio-border);
253
+ border-radius: var(--timeline-clip-audio-radius);
254
+ }
255
+
256
+ .timeline-clip.is-audio.is-hovered {
257
+ background-color: var(--timeline-clip-audio-bg-hover);
258
+ }
259
+
260
+ .timeline-clip.is-audio.is-dragging {
261
+ box-shadow:
262
+ inset 0 0 0 999px var(--timeline-clip-audio-bg-dragging),
263
+ 0 8px 24px var(--timeline-clip-shadow-dragging);
264
+ }
265
+
266
+ /* Mute greys the pill in place. Hot path: no transition. */
267
+ .timeline-clip.is-audio[data-clip-hidden="true"],
268
+ .timeline-clip.is-audio[data-audio-muted="true"] {
269
+ background-color: var(--timeline-clip-audio-muted-bg);
270
+ transition: none;
271
+ }
272
+
273
+ .timeline-audio-link {
274
+ position: absolute;
275
+ z-index: 4;
276
+ top: -9px;
277
+ left: 50%;
278
+ width: 1px;
279
+ height: 10px;
280
+ background: var(--timeline-audio-link);
281
+ pointer-events: none;
282
+ }
283
+
284
+ .timeline-clip.is-hovered {
285
+ background-color: var(--clip-bg-hover);
286
+ }
287
+
288
+ .timeline-clip[data-active].is-hovered {
289
+ border-color: var(--clip-border-hover);
290
+ }
291
+
292
+ .timeline-clip.is-selected {
293
+ outline: 1.5px solid var(--timeline-clip-selection);
294
+ outline-offset: -1.5px;
295
+ }
296
+
297
+ .timeline-clip.is-selected::after {
298
+ content: "";
299
+ position: absolute;
300
+ inset: 0;
301
+ z-index: 4;
302
+ border-radius: inherit;
303
+ box-shadow: inset 0 0 0 1px var(--timeline-clip-selection-highlight);
304
+ pointer-events: none;
305
+ }
306
+
307
+ .timeline-clip[data-active].is-selected {
308
+ outline: 1.5px solid var(--timeline-clip-selection);
309
+ outline-offset: -1.5px;
310
+ }
311
+
312
+ .timeline-clip.is-dragging {
313
+ /* Veil is a shadow, not background-color, so drop does not fade the hover fill. */
314
+ box-shadow:
315
+ inset 0 0 0 999px var(--clip-bg-dragging),
316
+ 0 8px 24px var(--timeline-clip-shadow-dragging);
317
+ }
318
+
319
+ .timeline-clip__label {
320
+ position: absolute;
321
+ top: 5px;
322
+ left: 6px;
323
+ z-index: 6;
324
+ width: fit-content;
325
+ max-width: calc(100% - 22px);
326
+ overflow: hidden;
327
+ padding: 1px 5px;
328
+ border-radius: 4px;
329
+ background: var(--timeline-clip-chip-bg);
330
+ color: var(--timeline-clip-chip-text);
331
+ font-size: 10px;
332
+ font-weight: 500;
333
+ line-height: 16px;
334
+ pointer-events: none;
335
+ text-overflow: ellipsis;
336
+ white-space: nowrap;
337
+ }
338
+
339
+ .timeline-clip.is-audio .timeline-clip__label {
340
+ top: 11px;
341
+ left: 9px;
342
+ background: var(--timeline-clip-chip-audio-bg);
343
+ }
344
+
345
+ .timeline-clip__timecode {
346
+ position: absolute;
347
+ bottom: 6px;
348
+ left: 12px;
349
+ overflow: hidden;
350
+ color: var(--timeline-tick-text);
351
+ font-family: var(--font-mono);
352
+ font-size: 9px;
353
+ font-variant-numeric: tabular-nums;
354
+ line-height: 1;
355
+ text-overflow: ellipsis;
356
+ white-space: nowrap;
357
+ }
358
+
359
+ @media (prefers-reduced-motion: no-preference) {
360
+ .timeline-clip {
361
+ transition: background-color 120ms ease-out;
362
+ }
363
+
364
+ .timeline-clip__handle-bar {
365
+ transition: opacity 120ms ease-out;
366
+ }
367
+ }
368
+
369
+ /* CodeMirror overrides */
370
+ .cm-editor {
371
+ height: 100%;
372
+ font-size: 13px;
373
+ }
374
+
375
+ .cm-editor .cm-scroller {
376
+ font-family: var(--font-mono);
377
+ }
378
+
379
+ .cm-editor.cm-focused {
380
+ outline: none;
381
+ }
382
+
383
+ /*
384
+ * HyperFrames brand loader. Shared by preview overlays that need a calm,
385
+ * branded loading state instead of a generic spinner.
386
+ */
387
+ .hf-loader {
388
+ display: flex;
389
+ flex-direction: column;
390
+ align-items: center;
391
+ justify-content: center;
392
+ gap: 0.75rem;
393
+ width: min(34rem, 100%);
394
+ padding: 1.5rem;
395
+ box-sizing: border-box;
396
+ text-align: center;
397
+ cursor: default;
398
+ user-select: none;
399
+ -webkit-user-select: none;
400
+ -webkit-user-drag: none;
401
+ }
402
+
403
+ .hf-frame {
404
+ display: grid;
405
+ place-items: center;
406
+ width: 100%;
407
+ height: 100%;
408
+ min-height: 12rem;
409
+ border: 1px solid rgba(255, 255, 255, 0.08);
410
+ background: rgba(0, 0, 0, 0.52);
411
+ }
412
+
413
+ .hf-loader-mark-frame {
414
+ display: grid;
415
+ place-items: center;
416
+ overflow: visible;
417
+ transform-origin: 50% 50%;
418
+ user-select: none;
419
+ -webkit-user-select: none;
420
+ -webkit-user-drag: none;
421
+ }
422
+
423
+ .hf-loader-mark {
424
+ display: block;
425
+ overflow: visible;
426
+ filter: drop-shadow(0 0 7px rgba(79, 219, 94, 0.2));
427
+ user-select: none;
428
+ -webkit-user-select: none;
429
+ -webkit-user-drag: none;
430
+ }
431
+
432
+ .hf-loader-title {
433
+ font-family: var(--font-sans);
434
+ font-size: 1rem;
435
+ font-weight: 600;
436
+ letter-spacing: 0;
437
+ color: var(--hf-heading, #f4f4f5);
438
+ max-width: 100%;
439
+ overflow: hidden;
440
+ text-overflow: ellipsis;
441
+ white-space: nowrap;
442
+ }
443
+
444
+ .hf-loader-detail {
445
+ max-width: 32rem;
446
+ min-height: 2.5rem;
447
+ overflow: hidden;
448
+ color: var(--hf-text-secondary, rgba(244, 244, 245, 0.68));
449
+ font-family: var(--font-sans);
450
+ font-size: 0.82rem;
451
+ line-height: 1.6;
452
+ }
453
+
454
+ .hf-loader-mono {
455
+ width: min(36rem, 100%);
456
+ min-height: 1.5rem;
457
+ overflow: hidden;
458
+ text-overflow: ellipsis;
459
+ white-space: nowrap;
460
+ color: var(--hf-text-tertiary, rgba(244, 244, 245, 0.46));
461
+ font-family: var(--font-mono);
462
+ font-size: 0.75rem;
463
+ letter-spacing: 0;
464
+ font-variant-numeric: tabular-nums;
465
+ }
466
+
467
+ .hf-loader-progress {
468
+ width: min(18rem, 72vw);
469
+ height: 0.375rem;
470
+ overflow: hidden;
471
+ border-radius: 999px;
472
+ background: rgba(255, 255, 255, 0.1);
473
+ }
474
+
475
+ @keyframes hf-thumb-fade {
476
+ from {
477
+ opacity: 0;
478
+ }
479
+ to {
480
+ opacity: 1;
481
+ }
482
+ }
483
+
484
+ .hf-loader-progress__fill {
485
+ width: 100%;
486
+ height: 100%;
487
+ transform: scaleX(0);
488
+ transform-origin: left center;
489
+ border-radius: inherit;
490
+ background: linear-gradient(90deg, #06e3fa, #4fdb5e);
491
+ transition: transform 160ms ease;
492
+ }
493
+
494
+ /* Toast enter/exit (StudioToast) */
495
+ @keyframes hf-toast-in {
496
+ from {
497
+ opacity: 0;
498
+ transform: translateY(8px);
499
+ }
500
+ to {
501
+ opacity: 1;
502
+ transform: translateY(0);
503
+ }
504
+ }
505
+
506
+ @keyframes hf-toast-out {
507
+ from {
508
+ opacity: 1;
509
+ transform: translateY(0);
510
+ }
511
+ to {
512
+ opacity: 0;
513
+ transform: translateY(8px);
514
+ }
515
+ }
516
+
517
+ .hf-toast-enter {
518
+ animation: hf-toast-in 160ms ease-out;
519
+ }
520
+
521
+ .hf-toast-exit {
522
+ animation: hf-toast-out 160ms ease-in forwards;
523
+ }
524
+
525
+ @media (prefers-reduced-motion: reduce) {
526
+ .hf-toast-enter,
527
+ .hf-toast-exit {
528
+ animation: none;
529
+ }
530
+ .hf-toast-exit {
531
+ opacity: 0;
532
+ }
533
+ }
534
+
535
+ /* Overlay/backdrop entrance — the heaviest visual changes shouldn't pop in */
536
+ @keyframes hf-backdrop-in {
537
+ from {
538
+ opacity: 0;
539
+ }
540
+ to {
541
+ opacity: 1;
542
+ }
543
+ }
544
+
545
+ .hf-backdrop-in {
546
+ animation: hf-backdrop-in 150ms ease-out;
547
+ }
548
+
549
+ @media (prefers-reduced-motion: reduce) {
550
+ .hf-backdrop-in {
551
+ animation: none;
552
+ }
553
+ }
554
+
555
+ /* Flat inspector group header/body entrance (FlatGroupHeader) */
556
+ @keyframes hf-flat-group-in {
557
+ from {
558
+ opacity: 0;
559
+ transform: translateY(-4px);
560
+ }
561
+ to {
562
+ opacity: 1;
563
+ transform: translateY(0);
564
+ }
565
+ }
566
+
567
+ .hf-flat-group-enter {
568
+ animation: hf-flat-group-in 120ms ease-out;
569
+ }
570
+
571
+ @media (prefers-reduced-motion: reduce) {
572
+ .hf-flat-group-enter {
573
+ animation: none;
574
+ }
575
+ }
576
+
577
+ /* Preset shelf: the hovered row, and the wave that says it is being heard.
578
+ *
579
+ * Hovering a preset auditions it on the running audio, which is invisible — the
580
+ * panel looks identical whether the audition is playing or the pointer just
581
+ * happens to be resting there. The bars are that feedback: they run only while a
582
+ * preset is actually sounding, so they are a readout of the audio rather than a
583
+ * hover decoration. */
584
+ .hf-fx-preset-item {
585
+ position: relative;
586
+ }
587
+
588
+ @media (prefers-reduced-motion: no-preference) {
589
+ .hf-fx-preset-item {
590
+ transition:
591
+ background-color 120ms ease,
592
+ color 120ms ease;
593
+ }
594
+ }
595
+
596
+ .hf-fx-preset-item:hover,
597
+ .hf-fx-preset-item:focus-visible {
598
+ /* The accent at low alpha rather than a grey: the row is lit by the thing that
599
+ is playing, and it should read as the same event as the bars. */
600
+ background-color: rgba(60, 230, 172, 0.12);
601
+ }
602
+
603
+ .hf-fx-preset-wave {
604
+ position: absolute;
605
+ top: 50%;
606
+ right: 0.5rem;
607
+ display: flex;
608
+ align-items: center;
609
+ gap: 2px;
610
+ height: 0.75rem;
611
+ transform: translateY(-50%);
612
+ opacity: 0;
613
+ transition: opacity 120ms ease;
614
+ pointer-events: none;
615
+ }
616
+
617
+ .hf-fx-preset-item:hover .hf-fx-preset-wave,
618
+ .hf-fx-preset-item:focus-visible .hf-fx-preset-wave {
619
+ opacity: 1;
620
+ }
621
+
622
+ .hf-fx-preset-wave span {
623
+ width: 2px;
624
+ height: 100%;
625
+ border-radius: 1px;
626
+ background: #3ce6ac;
627
+ transform: scaleY(0.25);
628
+ transform-origin: center;
629
+ }
630
+
631
+ @media (prefers-reduced-motion: no-preference) {
632
+ .hf-fx-preset-wave span {
633
+ animation: hf-fx-preset-wave 900ms ease-in-out infinite;
634
+ }
635
+ /* Staggered so it reads as a waveform travelling rather than four bars
636
+ pumping in unison, which reads as a loading spinner. */
637
+ .hf-fx-preset-wave span:nth-child(2) {
638
+ animation-delay: 150ms;
639
+ }
640
+ .hf-fx-preset-wave span:nth-child(3) {
641
+ animation-delay: 300ms;
642
+ }
643
+ .hf-fx-preset-wave span:nth-child(4) {
644
+ animation-delay: 450ms;
645
+ }
646
+ }
647
+
648
+ /* Still visible without motion — it is telling the author something is
649
+ playing, and that fact does not go away because the bars hold still. */
650
+ @media (prefers-reduced-motion: reduce) {
651
+ .hf-fx-preset-wave span {
652
+ transform: scaleY(0.7);
653
+ }
654
+ }
655
+
656
+ /* Receipt-driven WebMCP Topology Lens. Every animation is one-shot Studio
657
+ chrome; none of these nodes enter the composition iframe or rendered output. */
658
+ .hf-topology-contour {
659
+ border: 1px solid rgba(60, 230, 172, 0.62);
660
+ box-shadow:
661
+ 0 0 0 1px rgba(6, 227, 250, 0.08),
662
+ inset 0 0 18px rgba(60, 230, 172, 0.035);
663
+ }
664
+
665
+ .hf-topology-field::before,
666
+ .hf-topology-contour::before,
667
+ .hf-topology-target::before {
668
+ content: attr(data-topology-node);
669
+ position: absolute;
670
+ top: 2px;
671
+ left: 3px;
672
+ z-index: 2;
673
+ padding: 1px 3px;
674
+ border-radius: 2px;
675
+ background: rgba(5, 15, 12, 0.86);
676
+ color: rgb(90, 247, 193);
677
+ font:
678
+ 600 7px/1.3 ui-monospace,
679
+ SFMono-Regular,
680
+ Menlo,
681
+ Monaco,
682
+ Consolas,
683
+ monospace;
684
+ letter-spacing: 0.04em;
685
+ text-transform: lowercase;
686
+ }
687
+
688
+ .hf-topology-field {
689
+ z-index: 0;
690
+ border: 1px solid rgba(60, 230, 172, 0.28);
691
+ background: linear-gradient(
692
+ 90deg,
693
+ rgba(60, 230, 172, 0.025),
694
+ rgba(60, 230, 172, 0.1) 68%,
695
+ rgba(6, 227, 250, 0.06)
696
+ );
697
+ box-shadow: inset 0 0 28px rgba(60, 230, 172, 0.05);
698
+ }
699
+
700
+ .hf-topology-target {
701
+ border: 1px solid rgba(60, 230, 172, 0.82);
702
+ box-shadow:
703
+ 0 0 0 1px rgba(60, 230, 172, 0.14),
704
+ 0 0 18px rgba(6, 227, 250, 0.09);
705
+ }
706
+
707
+ .hf-topology-scan {
708
+ background: linear-gradient(
709
+ 90deg,
710
+ transparent,
711
+ rgba(60, 230, 172, 0.16) 55%,
712
+ rgba(230, 255, 247, 0.9)
713
+ );
714
+ clip-path: polygon(0 0, 100% 0, 72% 100%, 0 100%);
715
+ }
716
+
717
+ .hf-topology-seal {
718
+ filter: drop-shadow(0 0 6px rgba(60, 230, 172, 0.36));
719
+ }
720
+
721
+ @media (prefers-reduced-motion: no-preference) {
722
+ .hf-topology-contour {
723
+ animation: hf-topology-reveal 176ms cubic-bezier(0.77, 0, 0.175, 1) both;
724
+ }
725
+
726
+ .hf-topology-field[data-topology-phase="acquiring"] {
727
+ animation: hf-topology-reveal 240ms cubic-bezier(0.77, 0, 0.175, 1) both;
728
+ }
729
+
730
+ .hf-topology-scan {
731
+ animation: hf-topology-scan 224ms cubic-bezier(0.77, 0, 0.175, 1) both;
732
+ }
733
+
734
+ .hf-topology-target[data-topology-phase="acquiring"] {
735
+ animation: hf-topology-localize 224ms cubic-bezier(0.23, 1, 0.32, 1) 72ms both;
736
+ }
737
+
738
+ .hf-topology-target[data-topology-phase="localizing"] {
739
+ animation: hf-topology-localize 176ms cubic-bezier(0.23, 1, 0.32, 1) both;
740
+ }
741
+
742
+ .hf-topology-target[data-topology-phase="sealing"] {
743
+ animation: hf-topology-localize 144ms cubic-bezier(0.23, 1, 0.32, 1) both;
744
+ }
745
+
746
+ .hf-topology-seal {
747
+ animation: hf-topology-seal 224ms cubic-bezier(0.23, 1, 0.32, 1) both;
748
+ }
749
+
750
+ .hf-topology-seal path:first-of-type {
751
+ animation: hf-topology-seal-left 224ms cubic-bezier(0.23, 1, 0.32, 1) both;
752
+ }
753
+
754
+ .hf-topology-seal path:nth-of-type(2) {
755
+ animation: hf-topology-seal-right 224ms cubic-bezier(0.23, 1, 0.32, 1) both;
756
+ }
757
+ }
758
+
759
+ @keyframes hf-topology-reveal {
760
+ from {
761
+ opacity: 0;
762
+ clip-path: inset(0 100% 0 0);
763
+ }
764
+ to {
765
+ opacity: 1;
766
+ clip-path: inset(0);
767
+ }
768
+ }
769
+
770
+ @keyframes hf-topology-scan {
771
+ from {
772
+ opacity: 0;
773
+ transform: translate3d(-100%, 0, 0);
774
+ }
775
+ 16% {
776
+ opacity: 1;
777
+ }
778
+ to {
779
+ opacity: 0;
780
+ transform: translate3d(400%, 0, 0);
781
+ }
782
+ }
783
+
784
+ @keyframes hf-topology-localize {
785
+ from {
786
+ opacity: 0;
787
+ transform: scaleX(0.76);
788
+ }
789
+ to {
790
+ opacity: 1;
791
+ transform: scaleX(1);
792
+ }
793
+ }
794
+
795
+ @keyframes hf-topology-seal {
796
+ from {
797
+ opacity: 0;
798
+ transform: scale(0.88);
799
+ }
800
+ to {
801
+ opacity: 1;
802
+ transform: scale(1);
803
+ }
804
+ }
805
+
806
+ @keyframes hf-topology-seal-left {
807
+ from {
808
+ transform: translate3d(-9px, 0, 0);
809
+ }
810
+ to {
811
+ transform: translate3d(0, 0, 0);
812
+ }
813
+ }
814
+
815
+ @keyframes hf-topology-seal-right {
816
+ from {
817
+ transform: translate3d(9px, 0, 0);
818
+ }
819
+ to {
820
+ transform: translate3d(0, 0, 0);
821
+ }
822
+ }
823
+
824
+ @media (prefers-reduced-motion: reduce) {
825
+ .hf-topology-contour,
826
+ .hf-topology-field,
827
+ .hf-topology-target,
828
+ .hf-topology-seal,
829
+ .hf-topology-seal path {
830
+ animation: none;
831
+ transform: none;
832
+ }
833
+ }
834
+
835
+ @keyframes hf-fx-preset-wave {
836
+ 0%,
837
+ 100% {
838
+ transform: scaleY(0.25);
839
+ }
840
+ 50% {
841
+ transform: scaleY(1);
842
+ }
843
+ }