transitions-refine 0.3.22 → 0.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo.html +892 -89
- package/package.json +2 -2
package/demo.html
CHANGED
|
@@ -96,6 +96,38 @@
|
|
|
96
96
|
min-height: 100vh;
|
|
97
97
|
-webkit-font-smoothing: antialiased;
|
|
98
98
|
-moz-osx-font-smoothing: grayscale;
|
|
99
|
+
/* Smooth ease out (transitions.dev) for the demo-page bg toggle */
|
|
100
|
+
transition: background-color 250ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
101
|
+
}
|
|
102
|
+
/* demo-page-only dark background — independent of the panel's data-tl-theme */
|
|
103
|
+
body[data-demo-bg="dark"] { background: #121212; }
|
|
104
|
+
/* the only unavoidable legibility tweak: the bare H1 sits directly on the page bg */
|
|
105
|
+
body[data-demo-bg="dark"] .demo-header h1 { color: #f5f5f5; }
|
|
106
|
+
|
|
107
|
+
/* demo-chrome background toggle — sits beside the Hide-demo button */
|
|
108
|
+
.demo-bg-toggle {
|
|
109
|
+
position: fixed; top: 12px; right: 104px; z-index: 2147483000;
|
|
110
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
111
|
+
height: 28px; padding: 0 10px; border-radius: 8px; cursor: pointer;
|
|
112
|
+
border: 1px solid rgba(0,0,0,0.12); background: #fff; color: #444;
|
|
113
|
+
font: 500 12px/1 ui-sans-serif, system-ui, sans-serif;
|
|
114
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
|
115
|
+
transition: background-color 250ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
116
|
+
color 250ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
117
|
+
border-color 250ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
118
|
+
}
|
|
119
|
+
.demo-bg-toggle:hover { border-color: rgba(0,0,0,0.22); }
|
|
120
|
+
.demo-bg-toggle[aria-pressed="true"] {
|
|
121
|
+
background: #121212; color: #ededed; border-color: rgba(255,255,255,0.18);
|
|
122
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
|
123
|
+
}
|
|
124
|
+
.demo-bg-toggle[aria-pressed="true"]:hover { border-color: rgba(255,255,255,0.3); }
|
|
125
|
+
/* small swatch previewing the bg this toggle controls */
|
|
126
|
+
.demo-bg-toggle .demo-bg-dot {
|
|
127
|
+
width: 10px; height: 10px; border-radius: 50%; background: #121212;
|
|
128
|
+
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25); }
|
|
129
|
+
@media (prefers-reduced-motion: reduce) {
|
|
130
|
+
body, .demo-bg-toggle { transition: none; }
|
|
99
131
|
}
|
|
100
132
|
|
|
101
133
|
.demo-root { max-width: 900px; margin: 0 auto; padding: 24px 24px 120px; }
|
|
@@ -494,9 +526,10 @@
|
|
|
494
526
|
.tl-refine-btn:disabled::before { background: var(--c-blue-bg); background: color(display-p3 0 0.451 0.898 / 0.06); }
|
|
495
527
|
.tl-refine-btn:disabled span { color: var(--c-blue-pressed); opacity: 0.5; }
|
|
496
528
|
/* tiny 1.5px sparks flying out along each of the wand's spark rays on hover */
|
|
497
|
-
.tl-refine-sparks { position: absolute; left: 23px; top: 15px; width: 0; height: 0; z-index: 3; pointer-events: none;
|
|
529
|
+
.tl-refine-sparks { position: absolute; left: 23px; top: 15px; width: 0; height: 0; z-index: 3; pointer-events: none;
|
|
530
|
+
color: var(--c-blue-pressed); }
|
|
498
531
|
.tl-refine-sparks i { position: absolute; left: 0; top: 0; width: 1.5px; height: 1.5px; border-radius: 50%;
|
|
499
|
-
background:
|
|
532
|
+
background: currentColor; opacity: 0; }
|
|
500
533
|
@keyframes tl-refine-spark {
|
|
501
534
|
0% { opacity: 0; transform: translate(var(--ox, 0), var(--oy, 0)) scale(0.4); }
|
|
502
535
|
20% { opacity: 1; transform: translate(calc(var(--ox, 0) + var(--sx, 0) * 0.2), calc(var(--oy, 0) + var(--sy, -12px) * 0.2)) scale(1); }
|
|
@@ -513,6 +546,20 @@
|
|
|
513
546
|
.tl-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
514
547
|
.tl-inspector { flex: 0 0 280px; padding: 16px 16px; display: flex; flex-direction: column;
|
|
515
548
|
border-left: 1px solid var(--c-line); min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
|
|
549
|
+
/* thin overlay-style scrollbars — scoped to the panel scroll containers so the
|
|
550
|
+
host page's own scrollbar is never touched */
|
|
551
|
+
.tl-inspector, .tl-rows, .tl-refine-body, .tl-refine-pages .t-page {
|
|
552
|
+
scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.18) transparent; }
|
|
553
|
+
.tl-inspector::-webkit-scrollbar, .tl-rows::-webkit-scrollbar,
|
|
554
|
+
.tl-refine-body::-webkit-scrollbar, .tl-refine-pages .t-page::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
555
|
+
.tl-inspector::-webkit-scrollbar-track, .tl-rows::-webkit-scrollbar-track,
|
|
556
|
+
.tl-refine-body::-webkit-scrollbar-track, .tl-refine-pages .t-page::-webkit-scrollbar-track { background: transparent; }
|
|
557
|
+
.tl-inspector::-webkit-scrollbar-thumb, .tl-rows::-webkit-scrollbar-thumb,
|
|
558
|
+
.tl-refine-body::-webkit-scrollbar-thumb, .tl-refine-pages .t-page::-webkit-scrollbar-thumb {
|
|
559
|
+
background: rgba(0,0,0,0.18); border-radius: 8px; border: 1px solid transparent; background-clip: padding-box; }
|
|
560
|
+
.tl-inspector::-webkit-scrollbar-thumb:hover, .tl-rows::-webkit-scrollbar-thumb:hover,
|
|
561
|
+
.tl-refine-body::-webkit-scrollbar-thumb:hover, .tl-refine-pages .t-page::-webkit-scrollbar-thumb:hover {
|
|
562
|
+
background: rgba(0,0,0,0.28); background-clip: padding-box; }
|
|
516
563
|
/* title sits centered in the ~52px band between the Refine toolbar and the
|
|
517
564
|
Duration field: 16px above + 20px title + 16px below = 52px */
|
|
518
565
|
.tl-insp-title { display: flex; align-items: center; font-size: 13px; font-weight: 500; line-height: 18px; color: #171717; margin-bottom: 16px; text-transform: capitalize; }
|
|
@@ -557,26 +604,48 @@
|
|
|
557
604
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
558
605
|
/* 16px left inset of the plot area (kept in sync with the ruler) */
|
|
559
606
|
.tl-prop-track { position: relative; flex: 1; user-select: none; margin-left: 16px; }
|
|
560
|
-
/* timeline line —
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
transition:
|
|
607
|
+
/* timeline line — rubber-band drag physics. card resize tweens left/width
|
|
608
|
+
when timing changes programmatically; suppressed mid-drag (.is-dragging)
|
|
609
|
+
so the bar tracks the cursor 1:1. */
|
|
610
|
+
.tl-bar { position: absolute; top: 50%;
|
|
611
|
+
height: 24px; min-width: 24px; cursor: grab;
|
|
612
|
+
transform: translateY(-50%) skewX(var(--rb-skew,0deg)) scale(var(--rb-sx,1),var(--rb-sy,1));
|
|
613
|
+
transform-origin: var(--rb-origin,50%) 50%;
|
|
614
|
+
transition: transform 70ms linear,
|
|
568
615
|
left 220ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
569
616
|
width 220ms cubic-bezier(0.22, 1, 0.36, 1); will-change: left, width; }
|
|
570
|
-
.tl-bar.is-dragging { transition:
|
|
617
|
+
.tl-bar.is-dragging { transition: transform 70ms linear; }
|
|
618
|
+
/* one-shot guard for move-mouseup: keeps left/width transition disabled
|
|
619
|
+
until the final released position has fully flushed to React state */
|
|
620
|
+
.tl-bar.no-geom-tween { transition: transform 70ms linear !important; }
|
|
621
|
+
/* keep release geometry stable: clearing rubber vars should not nudge the
|
|
622
|
+
dragged edge after mouseup, so transform resets instantly on release */
|
|
623
|
+
.tl-bar.rb-release { transition: left 220ms cubic-bezier(0.22,1,0.36,1),
|
|
624
|
+
width 220ms cubic-bezier(0.22,1,0.36,1); }
|
|
571
625
|
@media (prefers-reduced-motion: reduce) {
|
|
572
|
-
.tl-bar { transition:
|
|
573
|
-
|
|
626
|
+
.tl-bar { transition: left 220ms cubic-bezier(0.22,1,0.36,1), width 220ms cubic-bezier(0.22,1,0.36,1) !important; } }
|
|
627
|
+
/* Bent capsule is a real SVG path: the 1px ring is a gradient stroke that
|
|
628
|
+
follows the bend exactly, the drop-shadow filter follows the painted path
|
|
629
|
+
(exact Figma shadow 580:8961), and the outline is re-synced to the bar's
|
|
630
|
+
real size by a ResizeObserver so it can never detach from the handles. */
|
|
631
|
+
.tl-bar-body { position: absolute; inset: 0; overflow: visible; pointer-events: none;
|
|
632
|
+
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.08)); }
|
|
633
|
+
.tl-bar-body path { fill: var(--c-track); stroke: url(#rb-ring); stroke-width: 1;
|
|
634
|
+
transition: fill 0.12s ease; }
|
|
635
|
+
.tl-bar.rb-release .tl-bar-body path { transition: fill 0.12s ease, d 200ms cubic-bezier(0.22,1,0.36,1); }
|
|
636
|
+
.tl-prop-row:hover .tl-bar-body path, .tl-prop-row.selected .tl-bar-body path { fill: var(--c-track-h); }
|
|
574
637
|
.tl-bar:active { cursor: grabbing; }
|
|
575
|
-
/* resize handles — Figma
|
|
576
|
-
.tl-bar-handle { position: absolute; top: 50%;
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
.tl-bar-handle
|
|
638
|
+
/* resize handles — Figma 625:1959 ")" crescent stroke on the leading edge */
|
|
639
|
+
.tl-bar-handle { position: absolute; top: 50%; width: 8px; height: 14px;
|
|
640
|
+
transform: translateY(-50%) translateX(var(--rb-hshift,0px));
|
|
641
|
+
opacity: 0; transition: opacity 0.12s ease, transform 70ms linear; pointer-events: none; z-index: 1; }
|
|
642
|
+
.tl-bar.rb-release .tl-bar-handle { transition: opacity 0.12s ease, transform 200ms cubic-bezier(0.22,1,0.36,1); }
|
|
643
|
+
.tl-bar-handle svg { display: block; width: 100%; height: 100%; overflow: visible; }
|
|
644
|
+
.tl-bar-handle path { fill: none; stroke: var(--c-thumb); stroke-width: 2; stroke-linecap: round;
|
|
645
|
+
opacity: 0.6; transition: d 70ms linear; }
|
|
646
|
+
.tl-bar.rb-release .tl-bar-handle path { transition: d 200ms cubic-bezier(0.22,1,0.36,1); }
|
|
647
|
+
.tl-bar-handle.left { left: 2px; }
|
|
648
|
+
.tl-bar-handle.right { right: 2px; }
|
|
580
649
|
.tl-prop-row:hover .tl-bar-handle { opacity: 0.6; }
|
|
581
650
|
.tl-bar:active .tl-bar-handle,
|
|
582
651
|
.tl-bar:has(.tl-bar-grip:active) .tl-bar-handle { opacity: 1; }
|
|
@@ -737,9 +806,10 @@
|
|
|
737
806
|
transition: color 0.12s ease; }
|
|
738
807
|
.tl-preview-head:hover .tl-preview-title,
|
|
739
808
|
.tl-preview-head:hover .tl-preview-chevron { color: #000; }
|
|
740
|
-
.tl-preview-title { font-size: 13px; font-weight: 500; color: #171717; letter-spacing: 0; line-height: 18px;
|
|
809
|
+
.tl-preview-title { font-size: 13px; font-weight: 500; color: #171717; letter-spacing: 0; line-height: 18px;
|
|
810
|
+
transition: color 0.12s ease; }
|
|
741
811
|
.tl-preview-chevron { display: inline-flex; color: #171717;
|
|
742
|
-
transform: scaleY(1); transform-origin: center; transition: transform var(--acc-dur) var(--acc-ease); }
|
|
812
|
+
transform: scaleY(1); transform-origin: center; transition: transform var(--acc-dur) var(--acc-ease), color 0.12s ease; }
|
|
743
813
|
.tl-preview[data-open="true"] .tl-preview-chevron { transform: scaleY(-1); }
|
|
744
814
|
.tl-preview-panel { display: grid; grid-template-rows: 0fr;
|
|
745
815
|
transition: grid-template-rows var(--acc-dur) var(--acc-ease); }
|
|
@@ -1192,7 +1262,7 @@
|
|
|
1192
1262
|
}
|
|
1193
1263
|
.tl-refine-inner {
|
|
1194
1264
|
width: var(--refine-w, 360px); height: 100%; box-sizing: border-box;
|
|
1195
|
-
display: flex; flex-direction: column;
|
|
1265
|
+
display: flex; flex-direction: column; position: relative;
|
|
1196
1266
|
background: #fff; color: var(--c-text);
|
|
1197
1267
|
font-family: "Inter", system-ui, -apple-system, sans-serif;
|
|
1198
1268
|
}
|
|
@@ -1202,8 +1272,11 @@
|
|
|
1202
1272
|
gap: 8px; box-sizing: border-box; padding: 7.5px 7.5px 7.5px 14px;
|
|
1203
1273
|
border-bottom: 1px solid #f0f0f0; }
|
|
1204
1274
|
.tl-refine-titles { min-width: 0; }
|
|
1205
|
-
|
|
1206
|
-
|
|
1275
|
+
/* Explicit margin:0 — the injected build strips the demo-only global *{margin:0}
|
|
1276
|
+
reset, so without this h3/p inherit the UA default block margins (~1em) and
|
|
1277
|
+
the header balloons to ~82px instead of matching the 52px timeline top bar. */
|
|
1278
|
+
.tl-refine-titles h3 { margin: 0; font-size: 13px; font-weight: 500; line-height: 14px; color: #17181c; }
|
|
1279
|
+
.tl-refine-titles p { margin: 2px 0 0; font-size: 12px; font-weight: 400; line-height: 14px; color: #676767;
|
|
1207
1280
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 190px; }
|
|
1208
1281
|
.tl-refine-actions { flex: none; display: flex; align-items: center; gap: 2px; }
|
|
1209
1282
|
.tl-refine-mode { display: flex; align-items: center; gap: 6px; height: 36px;
|
|
@@ -1220,7 +1293,7 @@
|
|
|
1220
1293
|
/* refine-type tabs — toolbar strip (Figma 587:9258): 48px white bar, hairline
|
|
1221
1294
|
bottom divider, segments inset 16px and packed gap:0 like a segmented control */
|
|
1222
1295
|
.tl-refine-tabs { flex: 0 0 auto; box-sizing: border-box; display: flex; align-items: center; gap: 0;
|
|
1223
|
-
height: 48px; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; }
|
|
1296
|
+
height: 48px; padding: 0 16px 0 10px; background: #fff; border-bottom: 1px solid #f0f0f0; }
|
|
1224
1297
|
.tl-refine-tab { height: 32px; padding: 6px 12px; border: none; background: transparent; cursor: pointer;
|
|
1225
1298
|
border-radius: 8px; font: inherit; font-size: 13px; font-weight: 500; line-height: 14px; color: #676767;
|
|
1226
1299
|
transition: background 0.14s ease, color 0.14s ease; }
|
|
@@ -1367,6 +1440,39 @@
|
|
|
1367
1440
|
/* scanning: cross-blur to the status face */
|
|
1368
1441
|
.tl-scan-morph.is-scanning .tl-scan-face-label { opacity: 0; filter: blur(var(--icon-swap-blur)); transform: scale(0.98); }
|
|
1369
1442
|
.tl-scan-morph.is-scanning .tl-scan-face-status { opacity: 1; filter: blur(0); transform: scale(1); }
|
|
1443
|
+
/* exit (scan done → liquid split): pill stays put on the stage while status
|
|
1444
|
+
text cross-blurs out and the chrome/beam fade so the goo blobs underneath
|
|
1445
|
+
can take over without a position jump. */
|
|
1446
|
+
/* The exiting pill lives inside the overlay and collapses along the SAME
|
|
1447
|
+
300ms curve and box values as the blobs' vertical travel — its centered
|
|
1448
|
+
label + loader ride down with the goo in perfect lockstep, and it can
|
|
1449
|
+
never disagree with the blobs about height. The stage below is just a
|
|
1450
|
+
spacer driving the foot's height tween. */
|
|
1451
|
+
.tl-scan-morph.is-exiting { position: absolute;
|
|
1452
|
+
width: auto !important; height: auto !important;
|
|
1453
|
+
margin: 0; pointer-events: none; z-index: 2;
|
|
1454
|
+
box-shadow: none !important; transition: none !important;
|
|
1455
|
+
animation: tl-scan-pill-exit 300ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
1456
|
+
@keyframes tl-scan-pill-exit {
|
|
1457
|
+
from { left: 4px; right: 4px; top: -41px; bottom: 24px; }
|
|
1458
|
+
to { left: 0; right: 0; top: 0; bottom: 0; } }
|
|
1459
|
+
/* the goo layer owns the pill's outline (fill + shadow) during exit — the
|
|
1460
|
+
exiting pill drops its own shadow and inset ring so they don't stack into
|
|
1461
|
+
a doubled outline. Only its white fill (::before) lingers, cross-fading to
|
|
1462
|
+
reveal the identical white blobs underneath, plus the fading text. */
|
|
1463
|
+
.tl-scan-morph.is-exiting::after { opacity: 0 !important; animation: none !important; }
|
|
1464
|
+
.tl-scan-morph.is-exiting .tl-scan-beam { animation: tl-scan-chrome-out var(--icon-swap-dur) var(--icon-swap-ease) forwards; }
|
|
1465
|
+
/* fill drops fast (150ms) so the blobs own the surface before they start
|
|
1466
|
+
visibly shrinking away from the pill's box; text follows at 250ms */
|
|
1467
|
+
.tl-scan-morph.is-exiting::before { animation: tl-scan-chrome-out 150ms ease-out forwards; }
|
|
1468
|
+
@keyframes tl-scan-chrome-out {
|
|
1469
|
+
from { opacity: 1; }
|
|
1470
|
+
to { opacity: 0; } }
|
|
1471
|
+
.tl-scan-morph.is-exiting .tl-scan-face-status {
|
|
1472
|
+
animation: tl-scan-status-out var(--icon-swap-dur) var(--icon-swap-ease) forwards; }
|
|
1473
|
+
@keyframes tl-scan-status-out {
|
|
1474
|
+
from { opacity: 1; filter: blur(0); transform: scale(1); }
|
|
1475
|
+
to { opacity: 0; filter: blur(var(--icon-swap-blur)); transform: scale(0.98); } }
|
|
1370
1476
|
.tl-scan-label { white-space: nowrap; }
|
|
1371
1477
|
/* The BorderBeam library injects its own stylesheet that forces
|
|
1372
1478
|
position:relative on its [data-beam] container (and it lands later in the
|
|
@@ -1409,10 +1515,10 @@
|
|
|
1409
1515
|
transition: background 150ms ease-out, border-color 150ms ease-out, opacity 150ms ease-out; }
|
|
1410
1516
|
.tl-gate-recheck:hover { background: rgba(0, 0, 0, 0.07); border-color: rgba(0, 0, 0, 0.12); }
|
|
1411
1517
|
.tl-gate-recheck:disabled { opacity: 0.55; cursor: default; }
|
|
1412
|
-
html[data-theme="dark"] .tl-gate-reason { color: #7e7e7e; }
|
|
1413
|
-
html[data-theme="dark"] .tl-gate-recheck { color: #d4d4d4; background: rgba(255, 255, 255, 0.06);
|
|
1518
|
+
html[data-tl-theme="dark"] .tl-gate-reason { color: #7e7e7e; }
|
|
1519
|
+
html[data-tl-theme="dark"] .tl-gate-recheck { color: #d4d4d4; background: rgba(255, 255, 255, 0.06);
|
|
1414
1520
|
border-color: rgba(255, 255, 255, 0.12); }
|
|
1415
|
-
html[data-theme="dark"] .tl-gate-recheck:hover { background: rgba(255, 255, 255, 0.1);
|
|
1521
|
+
html[data-tl-theme="dark"] .tl-gate-recheck:hover { background: rgba(255, 255, 255, 0.1);
|
|
1416
1522
|
border-color: rgba(255, 255, 255, 0.16); }
|
|
1417
1523
|
@media (prefers-reduced-motion: reduce) { .tl-gate-recheck { transition: none; } }
|
|
1418
1524
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1468,7 +1574,7 @@
|
|
|
1468
1574
|
animation: tl-live-in 250ms cubic-bezier(0.22,1,0.36,1) both; }
|
|
1469
1575
|
.tl-live-main { display: inline-flex; align-items: center; gap: 5px; flex: none; }
|
|
1470
1576
|
.tl-live-label { font-size: 12px; font-weight: 400; line-height: 14px; color: #18a32f; white-space: nowrap; }
|
|
1471
|
-
html[data-theme="dark"] .tl-live-label { color: #
|
|
1577
|
+
html[data-tl-theme="dark"] .tl-live-label { color: #70e984; }
|
|
1472
1578
|
.tl-live-dot { width: 8px; height: 8px; border-radius: 50%; flex: none;
|
|
1473
1579
|
background: #34CC20; box-shadow: inset 0 0 0 0.5px rgba(0,182,30,0.55);
|
|
1474
1580
|
animation: tl-live-pulse 1600ms linear infinite; }
|
|
@@ -1483,9 +1589,9 @@
|
|
|
1483
1589
|
color 150ms cubic-bezier(0.22,1,0.36,1); }
|
|
1484
1590
|
.tl-live-stop:hover,
|
|
1485
1591
|
.tl-live-stop:active { background: rgba(68,167,84,0.2); color: #057217; }
|
|
1486
|
-
html[data-theme="dark"] .tl-live-stop { color: #
|
|
1487
|
-
html[data-theme="dark"] .tl-live-stop:hover,
|
|
1488
|
-
html[data-theme="dark"] .tl-live-stop:active { color: #
|
|
1592
|
+
html[data-tl-theme="dark"] .tl-live-stop { color: #70e984; }
|
|
1593
|
+
html[data-tl-theme="dark"] .tl-live-stop:hover,
|
|
1594
|
+
html[data-tl-theme="dark"] .tl-live-stop:active { color: #8ef09e; }
|
|
1489
1595
|
.tl-live-stop:focus-visible { outline: 2px solid #118c25; outline-offset: 1px; }
|
|
1490
1596
|
/* LIVE OFF — node 594:22912 (gray, no button) */
|
|
1491
1597
|
.tl-live-chip.is-stopped { width: auto; justify-content: flex-start; padding: 0 9px;
|
|
@@ -1495,7 +1601,7 @@
|
|
|
1495
1601
|
.tl-live-chip.is-stopped .tl-live-dot { background: #A1A1A1;
|
|
1496
1602
|
box-shadow: inset 0 0 0 0.5px rgba(146,146,146,0.6); animation: none; }
|
|
1497
1603
|
.tl-live-chip.is-stopped .tl-live-label { color: #525252; }
|
|
1498
|
-
html[data-theme="dark"] .tl-live-chip.is-stopped .tl-live-label { color: #
|
|
1604
|
+
html[data-tl-theme="dark"] .tl-live-chip.is-stopped .tl-live-label { color: #848484; }
|
|
1499
1605
|
@keyframes tl-live-in { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: scale(1); } }
|
|
1500
1606
|
@keyframes tl-live-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
1501
1607
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1665,6 +1771,86 @@
|
|
|
1665
1771
|
.tl-refine-foot.is-bar { padding: 8px 16px; gap: 8px; justify-content: stretch;
|
|
1666
1772
|
background: #fff; border-top: 1px solid #f0f0f0; }
|
|
1667
1773
|
.tl-refine-foot.is-bar .tl-pill-btn { flex: 1; align-self: stretch; }
|
|
1774
|
+
/* Loading pill → two footer buttons: gooey/liquid split. During exit the
|
|
1775
|
+
scanning pill stays mounted (is-exiting) while status text cross-blurs out;
|
|
1776
|
+
goo blobs start inset-0 on the same 49px stage, then the stage shrinks to
|
|
1777
|
+
32px as the blobs split — no footer/pill position jump. */
|
|
1778
|
+
/* vertical geometry (stage height + foot padding) settles FAST (220ms) while
|
|
1779
|
+
the horizontal split runs the full 300ms — the box is already at its final
|
|
1780
|
+
32px height while the blobs finish sliding apart, so the ending is calm. */
|
|
1781
|
+
.tl-liquid-stage { position: relative; width: 100%; height: 49px; margin-bottom: 20px;
|
|
1782
|
+
animation: tl-liquid-stage-shrink 220ms cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
|
1783
|
+
@keyframes tl-liquid-stage-shrink {
|
|
1784
|
+
to { height: 32px; margin-bottom: 0; } }
|
|
1785
|
+
/* The overlay is anchored to the FOOT's bottom at the bar's exact resting
|
|
1786
|
+
geometry (bottom 8 / sides 16 / height 32) — NOT to the shrinking stage.
|
|
1787
|
+
The foot's bottom edge never moves, so blobs, chrome and labels live at
|
|
1788
|
+
the final position/size for the entire morph; only the fading pill rides
|
|
1789
|
+
the stage. Handoff to the real bar is then pixel-identical. */
|
|
1790
|
+
.tl-liquid-morph { position: absolute; left: 16px; right: 16px; bottom: 8px; height: 32px; }
|
|
1791
|
+
.tl-liquid-goo-svg { position: absolute; width: 0; height: 0; }
|
|
1792
|
+
.tl-liquid-goo { position: absolute; inset: 0; pointer-events: none; z-index: 1;
|
|
1793
|
+
filter: url(#tl-goo) drop-shadow(0 1px 3px rgba(0,0,0,0.08)) drop-shadow(0 0 1px rgba(0,0,0,0.14));
|
|
1794
|
+
/* hand-off: goo cross-fades OUT (250–430ms) in the same window the real
|
|
1795
|
+
buttons cross-fade IN — the surface swaps ownership gradually, so the
|
|
1796
|
+
overlay unmount at 470ms removes an already-invisible layer */
|
|
1797
|
+
animation: tl-liquid-goo-out 180ms ease 250ms both; }
|
|
1798
|
+
@keyframes tl-liquid-goo-out {
|
|
1799
|
+
from { opacity: 1; }
|
|
1800
|
+
to { opacity: 0; } }
|
|
1801
|
+
/* split runs at the transitions.dev "Smooth ease out" token (300ms) — kept
|
|
1802
|
+
in sync with the stage shrink above so blobs land as the box reaches 32px */
|
|
1803
|
+
.tl-liquid-blob { position: absolute; background: #fff; border-radius: 36px;
|
|
1804
|
+
animation-duration: 300ms; animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
|
1805
|
+
animation-fill-mode: both; }
|
|
1806
|
+
.tl-liquid-blob-a { animation-name: tl-liquid-a; }
|
|
1807
|
+
.tl-liquid-blob-b { animation-name: tl-liquid-b; }
|
|
1808
|
+
/* from = the scanning pill's box expressed in the overlay's (final-row)
|
|
1809
|
+
coordinates: pill top sits 41px above the row, its bottom 24px above the
|
|
1810
|
+
row's bottom, and it's 4px wider each side (foot pad 20 vs bar pad 16). */
|
|
1811
|
+
@keyframes tl-liquid-a {
|
|
1812
|
+
from { left: 4px; right: 4px; top: -41px; bottom: 24px; border-radius: 60px; }
|
|
1813
|
+
to { left: 0; right: calc(50% + 4px); top: 0; bottom: 0; border-radius: 36px; } }
|
|
1814
|
+
@keyframes tl-liquid-b {
|
|
1815
|
+
from { left: 4px; right: 4px; top: -41px; bottom: 24px; border-radius: 60px; }
|
|
1816
|
+
to { left: calc(50% + 4px); right: 0; top: 0; bottom: 0; border-radius: 36px; } }
|
|
1817
|
+
.tl-liquid-btns { position: absolute; inset: 0; display: flex; gap: 8px; z-index: 3; }
|
|
1818
|
+
/* Two independent fades on the real buttons:
|
|
1819
|
+
— LABELS cross-blur in immediately (0–250ms), mirroring the pill label
|
|
1820
|
+
cross-blurring out — the icon-swap pattern, per the pill's own faces.
|
|
1821
|
+
— CHROME (fill + ring + shadow) fades in at 250–430ms, the exact window
|
|
1822
|
+
the goo fades out, so the surface swaps ownership gradually. */
|
|
1823
|
+
.tl-liquid-btns .tl-pill-btn { flex: 1; align-self: stretch;
|
|
1824
|
+
background: transparent; box-shadow: none;
|
|
1825
|
+
animation: tl-liquid-btn-chrome 180ms ease 250ms both; }
|
|
1826
|
+
.tl-liquid-btns .tl-pill-btn > span {
|
|
1827
|
+
animation: tl-liquid-label-in 250ms ease-in-out both; }
|
|
1828
|
+
@keyframes tl-liquid-label-in {
|
|
1829
|
+
from { opacity: 0; filter: blur(var(--icon-swap-blur, 2px)); }
|
|
1830
|
+
to { opacity: 1; filter: blur(0); } }
|
|
1831
|
+
@keyframes tl-liquid-btn-chrome {
|
|
1832
|
+
from { background: transparent; box-shadow: none; }
|
|
1833
|
+
to { background: #fff;
|
|
1834
|
+
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.04),
|
|
1835
|
+
inset 0 0 0 1px rgba(0,0,0,0.06),
|
|
1836
|
+
inset 0 -1px 0 0 rgba(0,0,0,0.1),
|
|
1837
|
+
inset 0 0 0 1px rgba(196,196,196,0.1); } }
|
|
1838
|
+
/* the bar's background, hairline and geometry all converge on is-bar's
|
|
1839
|
+
exact resting values DURING the 300ms split (padding 12/20→8/16, border
|
|
1840
|
+
0→1px, colors fade in), so swapping classes at the end is a visual no-op */
|
|
1841
|
+
.tl-refine-foot.is-exiting-scan { position: relative;
|
|
1842
|
+
animation: tl-foot-to-bar 220ms cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
|
1843
|
+
@keyframes tl-foot-to-bar {
|
|
1844
|
+
from { padding: 12px 20px; border-top-width: 0; }
|
|
1845
|
+
to { padding: 8px 16px; border-top-width: 1px; justify-content: stretch; } }
|
|
1846
|
+
.tl-refine-foot.is-exiting-scan.is-morphing { --bar-bg: #fff; --bar-line: #f0f0f0;
|
|
1847
|
+
border-top: 0 solid var(--bar-line);
|
|
1848
|
+
animation: tl-liquid-bar-in 260ms ease both, tl-foot-to-bar 220ms cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
|
1849
|
+
.tl-refine-foot.is-bar.is-morphing { --bar-bg: #fff; --bar-line: #f0f0f0;
|
|
1850
|
+
animation: tl-liquid-bar-in 280ms ease both; }
|
|
1851
|
+
@keyframes tl-liquid-bar-in {
|
|
1852
|
+
from { background: transparent; border-top-color: transparent; }
|
|
1853
|
+
to { background: var(--bar-bg); border-top-color: var(--bar-line); } }
|
|
1668
1854
|
@media (prefers-reduced-motion: reduce) {
|
|
1669
1855
|
.tl-refine-panel { transition: none; }
|
|
1670
1856
|
.tl-dotm-dot { animation: none !important; }
|
|
@@ -1673,6 +1859,21 @@
|
|
|
1673
1859
|
.t-text-swap { transition: none !important; }
|
|
1674
1860
|
.tl-refine-results .t-stagger-line { transition: none !important; }
|
|
1675
1861
|
.t-resize, .tl-scan-morph { transition: none !important; }
|
|
1862
|
+
.tl-liquid-goo { display: none !important; }
|
|
1863
|
+
.tl-liquid-stage { animation: none !important; height: 32px; margin-bottom: 0; }
|
|
1864
|
+
.tl-refine-foot.is-exiting-scan { animation: none !important; padding: 8px 16px;
|
|
1865
|
+
border-top-width: 1px !important; background: var(--bar-bg, #fff) !important;
|
|
1866
|
+
border-top-color: var(--bar-line, #f0f0f0) !important; }
|
|
1867
|
+
.tl-scan-morph.is-exiting { animation: none !important; inset: 0; }
|
|
1868
|
+
.tl-scan-morph.is-exiting .tl-scan-beam,
|
|
1869
|
+
.tl-scan-morph.is-exiting::before,
|
|
1870
|
+
.tl-scan-morph.is-exiting::after,
|
|
1871
|
+
.tl-scan-morph.is-exiting .tl-scan-face-status { animation: none !important; opacity: 0; }
|
|
1872
|
+
.tl-liquid-btns .tl-pill-btn { animation: none !important; background: #fff;
|
|
1873
|
+
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.04), inset 0 0 0 1px rgba(0,0,0,0.06),
|
|
1874
|
+
inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 0 0 1px rgba(196,196,196,0.1); }
|
|
1875
|
+
.tl-liquid-btns .tl-pill-btn > span { animation: none !important; }
|
|
1876
|
+
.tl-refine-foot.is-bar.is-morphing { animation: none !important; }
|
|
1676
1877
|
.tl-pill-btn.tl-apply-btn { transition: background 0.12s ease, scale 0.12s ease, opacity 0.12s ease !important; }
|
|
1677
1878
|
.tl-apply-check { transition: none !important; }
|
|
1678
1879
|
.tl-sug-check { animation: none !important; opacity: 1; }
|
|
@@ -1681,6 +1882,305 @@
|
|
|
1681
1882
|
.tl-scan-beam { animation: none !important; }
|
|
1682
1883
|
.tl-pill { animation: none !important; }
|
|
1683
1884
|
}
|
|
1885
|
+
|
|
1886
|
+
/* ═════════════════════════════════════════════════════════════════════
|
|
1887
|
+
DARK THEME — html[data-tl-theme="dark"]
|
|
1888
|
+
System-default, overridable from the 3-dot menu Theme row. Scoped to
|
|
1889
|
+
.tl-* / .t-* so a host page (which has no such classes) is never touched.
|
|
1890
|
+
Values are the exact Figma dark tokens; missing ones are derived. Purely
|
|
1891
|
+
additive — light mode stays byte-for-byte unchanged.
|
|
1892
|
+
═════════════════════════════════════════════════════════════════════ */
|
|
1893
|
+
|
|
1894
|
+
/* — token overrides: auto-themes everything token-driven — */
|
|
1895
|
+
html[data-tl-theme="dark"] {
|
|
1896
|
+
--c-text: #fbfbfb; --c-text-strong: #fbfbfb; --c-text-mut: #979797;
|
|
1897
|
+
--c-text-mut2: #979797; --c-text-faint: #8b8b8b; --c-count: #8b8b8b;
|
|
1898
|
+
/* surfaces / overlay system (Figma node 580:652) */
|
|
1899
|
+
--c-sec: rgba(255,255,255,0.06); --c-sec-h: rgba(255,255,255,0.1); --c-sec-a: rgba(255,255,255,0.12);
|
|
1900
|
+
--c-circle: rgba(255,255,255,0.06); --c-circle-h: rgba(255,255,255,0.1); --c-circle-a: rgba(255,255,255,0.12);
|
|
1901
|
+
--c-ghost-h: rgba(255,255,255,0.04); --c-ghost-a: rgba(255,255,255,0.06);
|
|
1902
|
+
--c-field-bg: rgba(255,255,255,0.04); --c-fill: rgba(255,255,255,0.1); --c-fill-a: rgba(255,255,255,0.12);
|
|
1903
|
+
--c-track: rgba(255,255,255,0.1); --c-track-h: rgba(255,255,255,0.14); --c-thumb: #767676;
|
|
1904
|
+
--c-line: #262626; --c-hairline: rgba(255,255,255,0.04); --c-hairline2: rgba(255,255,255,0.06);
|
|
1905
|
+
/* blue accents */
|
|
1906
|
+
--c-blue: #3b9bff; --c-blue-pressed: #5cb0ff; --c-blue-bg: rgba(0,128,255,0.17);
|
|
1907
|
+
--c-blue-bg-h: rgba(0,128,255,0.24); --c-blue-bg-a: rgba(0,128,255,0.3); --c-blue-ring: rgba(85,207,255,0.06);
|
|
1908
|
+
/* tooltip surface */
|
|
1909
|
+
--tt-bg: #262626; --tt-fg: #fbfbfb;
|
|
1910
|
+
/* shadow / ring recipes — drop 0 1px1px rgba(0,0,0,.24) + white insets */
|
|
1911
|
+
--ring: inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06);
|
|
1912
|
+
--ring-fill: inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06);
|
|
1913
|
+
--ring-blue: inset 0 0 0 1px rgba(85,207,255,0.04), inset 0 1px 0 0 rgba(85,207,255,0.06);
|
|
1914
|
+
--drop: 0 1px 1px 0 rgba(0,0,0,0.24);
|
|
1915
|
+
--drop-btn: 0 1px 1px 0 rgba(0,0,0,0.24);
|
|
1916
|
+
--drop-line: 0 1px 1px 0 rgba(0,0,0,0.24);
|
|
1917
|
+
--shadow-btn: 0 1px 1px 0 rgba(0,0,0,0.24), inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06);
|
|
1918
|
+
--shadow-btn-inset: inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06);
|
|
1919
|
+
--menu-shadow: 0 0 0 1px rgba(0,0,0,0.06), 0 2px 6px rgba(0,0,0,0.05), 0 4px 42px rgba(0,0,0,0.24);
|
|
1920
|
+
--panel-shadow: 0 2px 6px 0 rgba(0,0,0,0.05), 0 4px 42px 0 rgba(0,0,0,0.24), 0 0 0 1px rgba(255,255,255,0.04) inset, 0 1px 0 0 rgba(255,255,255,0.02) inset;
|
|
1921
|
+
--card-shadow: 0 0 0 1px rgba(255,255,255,0.06), 0 1px 2px rgba(0,0,0,0.4), 0 8px 24px rgba(0,0,0,0.5);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/* — panel surfaces — */
|
|
1925
|
+
html[data-tl-theme="dark"] .tl-panel-body { background: #1b1b1b;
|
|
1926
|
+
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.05), 0 4px 42px 0 rgba(0, 0, 0, 0.24), 0 0 0 1px rgba(255, 255, 255, 0.04) inset, 0 1px 0 0 rgba(255, 255, 255, 0.02) inset; }
|
|
1927
|
+
html[data-tl-theme="dark"] .tl-refine-inner { background: #1b1b1b; }
|
|
1928
|
+
/* draw the 4% white inset ring as an overlay above all children (header,
|
|
1929
|
+
tab nav, body) so it stays continuous on the left edge where child
|
|
1930
|
+
backgrounds would otherwise paint over an element-level inset shadow. */
|
|
1931
|
+
html[data-tl-theme="dark"] .tl-refine-inner::after {
|
|
1932
|
+
content: ""; position: absolute; inset: 0; pointer-events: none;
|
|
1933
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04); }
|
|
1934
|
+
/* Refine panel: the parent .tl-panel-body has overflow:hidden, so a full
|
|
1935
|
+
surrounding shadow is clipped on every side except the internal left
|
|
1936
|
+
edge. Cast a directional LEFT shadow (negative x) so the panel reads as a
|
|
1937
|
+
clear divider over the dark timeline, plus a left-edge highlight + ring
|
|
1938
|
+
inset for the surface lift. */
|
|
1939
|
+
html[data-tl-theme="dark"] .tl-refine-panel {
|
|
1940
|
+
box-shadow:
|
|
1941
|
+
-4px 0 12px 0 rgba(0, 0, 0, 0.35),
|
|
1942
|
+
-16px 0 42px 0 rgba(0, 0, 0, 0.3); }
|
|
1943
|
+
html[data-tl-theme="dark"] .tl-empty { background: #202020; }
|
|
1944
|
+
|
|
1945
|
+
/* — timeline rows / ruler / bars / member chips — */
|
|
1946
|
+
html[data-tl-theme="dark"] .tl-prop-row:hover { background: rgba(255,255,255,0.02); }
|
|
1947
|
+
html[data-tl-theme="dark"] .tl-prop-row.selected { background: rgba(255,255,255,0.04); border-bottom-color: #2a2a2a; }
|
|
1948
|
+
/* top hairline of the selected row = bottom border of the row above (no 1px shift) */
|
|
1949
|
+
html[data-tl-theme="dark"] .tl-prop-row:has(+ .tl-prop-row.selected) { border-bottom-color: #2a2a2a; }
|
|
1950
|
+
/* first row selected: recolor the ruler-row divider that sits directly above it */
|
|
1951
|
+
html[data-tl-theme="dark"] .tl-ruler-row:has(+ .tl-rows .tl-prop-row:first-child.selected) { border-bottom-color: #2a2a2a; }
|
|
1952
|
+
html[data-tl-theme="dark"] .tl-prop-row.selected .tl-prop-label { color: #fbfbfb; }
|
|
1953
|
+
html[data-tl-theme="dark"] .tl-prop-member,
|
|
1954
|
+
html[data-tl-theme="dark"] .tl-prop-row.selected .tl-prop-member,
|
|
1955
|
+
html[data-tl-theme="dark"] .tl-insp-member { background: rgba(255,255,255,0.04); color: #bdbdbd; box-shadow: 0 0 0 1px rgba(255,255,255,0.08); }
|
|
1956
|
+
html[data-tl-theme="dark"] .tl-insp-title { color: #fbfbfb; }
|
|
1957
|
+
html[data-tl-theme="dark"] .tl-insp-label { color: #8b8b8b; }
|
|
1958
|
+
html[data-tl-theme="dark"] .tl-bar-body { filter: drop-shadow(0 1px 2px rgba(0,0,0,0.32)); }
|
|
1959
|
+
html[data-tl-theme="dark"] .tl-bar-body path { stroke: url(#rb-ring-dark); }
|
|
1960
|
+
html[data-tl-theme="dark"] #rb-ring stop:first-child { stop-opacity: 0.04; }
|
|
1961
|
+
html[data-tl-theme="dark"] #rb-ring stop:last-child { stop-opacity: 0.08; }
|
|
1962
|
+
html[data-tl-theme="dark"] .tl-ruler .tick { background: rgba(255,255,255,0.18); }
|
|
1963
|
+
/* dark thin scrollbars */
|
|
1964
|
+
html[data-tl-theme="dark"] .tl-inspector,
|
|
1965
|
+
html[data-tl-theme="dark"] .tl-rows,
|
|
1966
|
+
html[data-tl-theme="dark"] .tl-refine-body,
|
|
1967
|
+
html[data-tl-theme="dark"] .tl-refine-pages .t-page { scrollbar-color: rgba(255,255,255,0.14) transparent; }
|
|
1968
|
+
html[data-tl-theme="dark"] .tl-inspector::-webkit-scrollbar-thumb,
|
|
1969
|
+
html[data-tl-theme="dark"] .tl-rows::-webkit-scrollbar-thumb,
|
|
1970
|
+
html[data-tl-theme="dark"] .tl-refine-body::-webkit-scrollbar-thumb,
|
|
1971
|
+
html[data-tl-theme="dark"] .tl-refine-pages .t-page::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.14); background-clip: padding-box; }
|
|
1972
|
+
html[data-tl-theme="dark"] .tl-inspector::-webkit-scrollbar-thumb:hover,
|
|
1973
|
+
html[data-tl-theme="dark"] .tl-rows::-webkit-scrollbar-thumb:hover,
|
|
1974
|
+
html[data-tl-theme="dark"] .tl-refine-body::-webkit-scrollbar-thumb:hover,
|
|
1975
|
+
html[data-tl-theme="dark"] .tl-refine-pages .t-page::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.24); background-clip: padding-box; }
|
|
1976
|
+
|
|
1977
|
+
/* — refine header / tabs / titles — */
|
|
1978
|
+
html[data-tl-theme="dark"] .tl-refine-head { border-bottom-color: #262626; }
|
|
1979
|
+
html[data-tl-theme="dark"] .tl-refine-tabs { background: #1b1b1b; border-bottom-color: #262626; }
|
|
1980
|
+
html[data-tl-theme="dark"] .tl-refine-titles h3 { color: #fbfbfb; }
|
|
1981
|
+
html[data-tl-theme="dark"] .tl-refine-titles p { color: #979797; }
|
|
1982
|
+
html[data-tl-theme="dark"] .tl-refine-tab { color: #8b8b8b; }
|
|
1983
|
+
html[data-tl-theme="dark"] .tl-refine-tab:hover:not([aria-selected="true"]) { color: #fbfbfb; }
|
|
1984
|
+
html[data-tl-theme="dark"] .tl-refine-tab[aria-selected="true"] { background: rgba(255,255,255,0.06); color: #fbfbfb; }
|
|
1985
|
+
html[data-tl-theme="dark"] .tl-refine-mode { color: #fbfbfb; }
|
|
1986
|
+
html[data-tl-theme="dark"] .tl-refine-mode:hover,
|
|
1987
|
+
html[data-tl-theme="dark"] .tl-refine-mode.is-open { background: rgba(255,255,255,0.06); }
|
|
1988
|
+
html[data-tl-theme="dark"] .tl-refine-close { color: #979797; }
|
|
1989
|
+
html[data-tl-theme="dark"] .tl-refine-close:hover { background: rgba(255,255,255,0.06); color: #fbfbfb; }
|
|
1990
|
+
|
|
1991
|
+
/* — idle / unavailable / gate / inline code — */
|
|
1992
|
+
html[data-tl-theme="dark"] .tl-refine-idle-text,
|
|
1993
|
+
html[data-tl-theme="dark"] .tl-refine-unavail-text,
|
|
1994
|
+
html[data-tl-theme="dark"] .tl-gate-text { color: #979797; }
|
|
1995
|
+
html[data-tl-theme="dark"] .tl-refine-unavail-title,
|
|
1996
|
+
html[data-tl-theme="dark"] .tl-gate-title { color: #fbfbfb; }
|
|
1997
|
+
html[data-tl-theme="dark"] .tl-gate-text .tl-code { color: #cfcfcf; }
|
|
1998
|
+
html[data-tl-theme="dark"] code.tl-code { color: #fbfbfb; background: rgba(255,255,255,0.08); }
|
|
1999
|
+
html[data-tl-theme="dark"] .tl-refine-empty { color: #979797; }
|
|
2000
|
+
html[data-tl-theme="dark"] .tl-refine-error { color: #ff8a7a; background: rgba(192,57,43,0.14); }
|
|
2001
|
+
|
|
2002
|
+
/* — header buttons that hardcode white — */
|
|
2003
|
+
html[data-tl-theme="dark"] .tl-accept-btn { background: rgba(255,255,255,0.06); color: #fbfbfb; }
|
|
2004
|
+
html[data-tl-theme="dark"] .tl-accept-btn > svg { color: #fbfbfb; }
|
|
2005
|
+
html[data-tl-theme="dark"] .tl-accept-btn:hover:not(:disabled),
|
|
2006
|
+
html[data-tl-theme="dark"] .tl-accept-btn:active:not(:disabled) { background: rgba(255,255,255,0.1); }
|
|
2007
|
+
html[data-tl-theme="dark"] .tl-accept-spin { border-color: rgba(255,255,255,0.2); border-top-color: #fbfbfb; }
|
|
2008
|
+
html[data-tl-theme="dark"] .tl-header .tl-icon-btn:not(.ghost) { background: rgba(255,255,255,0.06); }
|
|
2009
|
+
html[data-tl-theme="dark"] .tl-header .tl-icon-btn:not(.ghost):hover:not(:disabled),
|
|
2010
|
+
html[data-tl-theme="dark"] .tl-header .tl-icon-btn:not(.ghost).is-active,
|
|
2011
|
+
html[data-tl-theme="dark"] .tl-header .tl-icon-btn:not(.ghost):active:not(:disabled) { background: rgba(255,255,255,0.1); }
|
|
2012
|
+
html[data-tl-theme="dark"] .tl-sec-btn { background: rgba(255,255,255,0.06); color: #fbfbfb; }
|
|
2013
|
+
html[data-tl-theme="dark"] .tl-sec-btn:hover:not(:disabled),
|
|
2014
|
+
html[data-tl-theme="dark"] .tl-sec-btn:active:not(:disabled) { background: rgba(255,255,255,0.1); }
|
|
2015
|
+
|
|
2016
|
+
/* — Refine button + scan-morph (layered blue fill) — */
|
|
2017
|
+
html[data-tl-theme="dark"] .tl-refine-btn,
|
|
2018
|
+
html[data-tl-theme="dark"] .tl-refine-btn > svg { color: #90dcff; }
|
|
2019
|
+
html[data-tl-theme="dark"] .tl-refine-sparks { color: #90dcff; }
|
|
2020
|
+
html[data-tl-theme="dark"] .tl-refine-btn::before { background: rgba(0,128,255,0.17); }
|
|
2021
|
+
/* Figma 580:758 hover variant: fill brightens 0.17 -> 0.25, text/icon #90dcff,
|
|
2022
|
+
ring + drop shadow unchanged. Override base p3 hover (0.10) which would dim it. */
|
|
2023
|
+
html[data-tl-theme="dark"] .tl-refine-btn:hover:not(:disabled)::before { background: rgba(0,128,255,0.25); }
|
|
2024
|
+
html[data-tl-theme="dark"] .tl-refine-btn.is-active { color: #b3e6ff; }
|
|
2025
|
+
html[data-tl-theme="dark"] .tl-refine-btn.is-active::before { background: rgba(0,128,255,0.3); }
|
|
2026
|
+
html[data-tl-theme="dark"] .tl-scan-morph { color: #90dcff; }
|
|
2027
|
+
html[data-tl-theme="dark"] .tl-scan-morph::before { background: rgba(0,128,255,0.17); }
|
|
2028
|
+
html[data-tl-theme="dark"] .tl-scan-morph:hover:not(:disabled):not(.is-scanning)::before,
|
|
2029
|
+
html[data-tl-theme="dark"] .tl-scan-morph:active:not(.is-scanning):not(:disabled)::before { background: rgba(0,128,255,0.24); }
|
|
2030
|
+
html[data-tl-theme="dark"] .tl-scan-morph:disabled:not(.is-scanning)::before { background: rgba(0,128,255,0.17); }
|
|
2031
|
+
html[data-tl-theme="dark"] .tl-scan-morph.is-scanning { color: #979797; }
|
|
2032
|
+
html[data-tl-theme="dark"] .tl-scan-morph.is-scanning::before { background: #202020; }
|
|
2033
|
+
|
|
2034
|
+
/* — resize handle — */
|
|
2035
|
+
html[data-tl-theme="dark"] .tl-resize-handle::after { background: rgba(255,255,255,0.14); }
|
|
2036
|
+
|
|
2037
|
+
/* — toast / minimized pill / transitions count badge — */
|
|
2038
|
+
/* toast + tooltip share the dropdown's dark shadow recipe (menu-shadow +
|
|
2039
|
+
white inset ring) so all floating surfaces read consistently. */
|
|
2040
|
+
html[data-tl-theme="dark"] .tl-toast { background: #202020; color: #fbfbfb;
|
|
2041
|
+
box-shadow: var(--menu-shadow), inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.03); }
|
|
2042
|
+
html[data-tl-theme="dark"] .t-tt {
|
|
2043
|
+
box-shadow: var(--menu-shadow), inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.03); }
|
|
2044
|
+
html[data-tl-theme="dark"] .tl-toast-ic { color: #fbfbfb; }
|
|
2045
|
+
html[data-tl-theme="dark"] .tl-pill { background: #1b1b1b; }
|
|
2046
|
+
html[data-tl-theme="dark"] .tl-pill:hover,
|
|
2047
|
+
html[data-tl-theme="dark"] .tl-pill:active { background: #262626; }
|
|
2048
|
+
html[data-tl-theme="dark"] .tl-pill-label { color: #fbfbfb; }
|
|
2049
|
+
html[data-tl-theme="dark"] .tl-pill-count { background: #fbfbfb; color: #1b1b1b;
|
|
2050
|
+
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.3), inset 0 0 0 1px rgba(0,0,0,0.06), inset 0 -1px 0 0 rgba(0,0,0,0.06); }
|
|
2051
|
+
|
|
2052
|
+
/* — dropdown menu surface + rows (Figma node 620:976) — */
|
|
2053
|
+
html[data-tl-theme="dark"] .tl-menu { background: #202020;
|
|
2054
|
+
box-shadow: var(--menu-shadow), inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.03); }
|
|
2055
|
+
html[data-tl-theme="dark"] .tl-menu-search { border-bottom-color: #262626; }
|
|
2056
|
+
html[data-tl-theme="dark"] .tl-menu-search-input { color: #fbfbfb; }
|
|
2057
|
+
html[data-tl-theme="dark"] .tl-menu-search-input::placeholder { color: #8b8b8b; }
|
|
2058
|
+
html[data-tl-theme="dark"] .tl-menu-item { color: #fbfbfb; }
|
|
2059
|
+
html[data-tl-theme="dark"] .tl-menu-item:hover { background: rgba(255,255,255,0.04); }
|
|
2060
|
+
html[data-tl-theme="dark"] .tl-menu-item:active { background: rgba(255,255,255,0.06); }
|
|
2061
|
+
html[data-tl-theme="dark"] .tl-menu-item:focus-visible { background: transparent; box-shadow: inset 0 0 0 1px rgba(85,207,255,0.5); }
|
|
2062
|
+
html[data-tl-theme="dark"] .tl-menu-dim,
|
|
2063
|
+
html[data-tl-theme="dark"] .tl-menu-value,
|
|
2064
|
+
html[data-tl-theme="dark"] .tl-menu-section,
|
|
2065
|
+
html[data-tl-theme="dark"] .tl-menu-head { color: #8b8b8b; }
|
|
2066
|
+
html[data-tl-theme="dark"] .tl-menu-head-name { color: #4c4c4c; }
|
|
2067
|
+
html[data-tl-theme="dark"] .tl-menu-check { color: #fbfbfb; }
|
|
2068
|
+
html[data-tl-theme="dark"] .tl-menu-divider { background: #262626; }
|
|
2069
|
+
html[data-tl-theme="dark"] .tl-menu-icon,
|
|
2070
|
+
html[data-tl-theme="dark"] .tl-menu-chevr,
|
|
2071
|
+
html[data-tl-theme="dark"] .tl-set-back-ic { color: #8b8b8b; }
|
|
2072
|
+
html[data-tl-theme="dark"] .tl-set-back { color: #fbfbfb; }
|
|
2073
|
+
html[data-tl-theme="dark"] .tl-set-back:hover { background: rgba(255,255,255,0.04); }
|
|
2074
|
+
html[data-tl-theme="dark"] .tl-set-back:active { background: rgba(255,255,255,0.06); }
|
|
2075
|
+
html[data-tl-theme="dark"] .tl-set-back:focus-visible { background: transparent; box-shadow: inset 0 0 0 1px rgba(85,207,255,0.5); }
|
|
2076
|
+
html[data-tl-theme="dark"] .tl-kbd-row { color: #fbfbfb; }
|
|
2077
|
+
html[data-tl-theme="dark"] .tl-kbd { background: rgba(255,255,255,0.06); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.1); color: #cfcfcf; }
|
|
2078
|
+
html[data-tl-theme="dark"] .tl-kbd-plus { color: #6b6b6b; }
|
|
2079
|
+
|
|
2080
|
+
/* — mode dropdown rows — */
|
|
2081
|
+
html[data-tl-theme="dark"] .tl-mode-row:hover { background: rgba(255,255,255,0.06); }
|
|
2082
|
+
html[data-tl-theme="dark"] .tl-mode-row-title { color: #fbfbfb; }
|
|
2083
|
+
html[data-tl-theme="dark"] .tl-mode-row-desc { color: #979797; }
|
|
2084
|
+
html[data-tl-theme="dark"] .tl-mode-row-check { color: #fbfbfb; }
|
|
2085
|
+
|
|
2086
|
+
/* — fields / selects / steppers / tabs / cubic inputs — */
|
|
2087
|
+
html[data-tl-theme="dark"] .tl-field.is-editing { box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14), inset 0 0 0 0.5px rgba(255,255,255,0.06); }
|
|
2088
|
+
html[data-tl-theme="dark"] .tl-field-fill { box-shadow: 0 1px 1px 0 rgba(0,0,0,0.24), inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06); }
|
|
2089
|
+
html[data-tl-theme="dark"] .tl-select { background: rgba(255,255,255,0.04); color: #fbfbfb; }
|
|
2090
|
+
html[data-tl-theme="dark"] .tl-select:hover,
|
|
2091
|
+
html[data-tl-theme="dark"] .tl-select.is-open,
|
|
2092
|
+
html[data-tl-theme="dark"] .tl-field:hover:not(.is-editing),
|
|
2093
|
+
html[data-tl-theme="dark"] .tl-field-chevron:hover,
|
|
2094
|
+
html[data-tl-theme="dark"] .tl-cubic-cell:hover { background: rgba(255,255,255,0.06); }
|
|
2095
|
+
html[data-tl-theme="dark"] .tl-cubic-row input { color: #fbfbfb; }
|
|
2096
|
+
html[data-tl-theme="dark"] .tl-cubic-row input::placeholder { color: #8b8b8b; }
|
|
2097
|
+
html[data-tl-theme="dark"] .tl-cubic-cell:focus-within { box-shadow: inset 0 0 0 1px rgba(255,255,255,0.1); }
|
|
2098
|
+
html[data-tl-theme="dark"] .tl-custom-input { box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06); }
|
|
2099
|
+
html[data-tl-theme="dark"] .tl-seg-btn { color: #8b8b8b; }
|
|
2100
|
+
html[data-tl-theme="dark"] .tl-seg-btn:hover:not(.is-active) { background: rgba(255,255,255,0.04); color: #fbfbfb; }
|
|
2101
|
+
html[data-tl-theme="dark"] .tl-seg-btn.is-active { background: rgba(255,255,255,0.1); color: #fbfbfb; }
|
|
2102
|
+
|
|
2103
|
+
/* — bezier preview (curve stays light; surfaces darken) — */
|
|
2104
|
+
html[data-tl-theme="dark"] .tl-curve { background: rgba(255,255,255,0.04); }
|
|
2105
|
+
html[data-tl-theme="dark"] .tl-preview-curve svg path { stroke: #fbfbfb; }
|
|
2106
|
+
html[data-tl-theme="dark"] .tl-preview-title,
|
|
2107
|
+
html[data-tl-theme="dark"] .tl-preview-sub-label { color: #fbfbfb; }
|
|
2108
|
+
html[data-tl-theme="dark"] .tl-preview-chevron { color: var(--c-ruler); }
|
|
2109
|
+
html[data-tl-theme="dark"] .tl-preview-head:hover .tl-preview-title,
|
|
2110
|
+
html[data-tl-theme="dark"] .tl-preview-head:hover .tl-preview-chevron { color: #fff; }
|
|
2111
|
+
html[data-tl-theme="dark"] .tl-preview-play:hover { color: #fbfbfb; }
|
|
2112
|
+
html[data-tl-theme="dark"] .tl-preview-track { background: rgba(255,255,255,0.04); }
|
|
2113
|
+
html[data-tl-theme="dark"] .tl-preview-rail { border-top-color: rgba(255,255,255,0.14); }
|
|
2114
|
+
|
|
2115
|
+
/* — suggestion cards — */
|
|
2116
|
+
html[data-tl-theme="dark"] .tl-sug { background: #202020; box-shadow: 0 0 0 1px rgba(255,255,255,0.06); }
|
|
2117
|
+
html[data-tl-theme="dark"] .tl-sug-member { background: rgba(255,255,255,0.04); box-shadow: 0 0 0 1px rgba(255,255,255,0.08); color: #bdbdbd; }
|
|
2118
|
+
html[data-tl-theme="dark"] .tl-sug-prop { color: #fbfbfb; }
|
|
2119
|
+
html[data-tl-theme="dark"] .tl-sug-divider { background: #262626; }
|
|
2120
|
+
html[data-tl-theme="dark"] .tl-sug-title { color: #fbfbfb; }
|
|
2121
|
+
html[data-tl-theme="dark"] .tl-sug-from { color: #8b8b8b; }
|
|
2122
|
+
html[data-tl-theme="dark"] .tl-sug-to { color: #fbfbfb; }
|
|
2123
|
+
html[data-tl-theme="dark"] .tl-sug-reason,
|
|
2124
|
+
html[data-tl-theme="dark"] .tl-sug-phases { color: #979797; }
|
|
2125
|
+
html[data-tl-theme="dark"] .tl-sug-phase { background: rgba(255,255,255,0.06); color: #cfcfcf; }
|
|
2126
|
+
html[data-tl-theme="dark"] .tl-sug-phase-dur { color: #fbfbfb; }
|
|
2127
|
+
|
|
2128
|
+
/* — colorful category pills: brighten text only (bg/ring kept) — */
|
|
2129
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-easing { color: #c471ff; }
|
|
2130
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-duration { color: #50a7ff; }
|
|
2131
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-scale { color: #64d378; }
|
|
2132
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-blur { color: #3ec9d6; }
|
|
2133
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-distance,
|
|
2134
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-delay { color: #f99032; }
|
|
2135
|
+
html[data-tl-theme="dark"] .tl-sug-cat.is-replace { color: #50a7ff; }
|
|
2136
|
+
|
|
2137
|
+
/* — white pill buttons (card Apply + footer bar) — */
|
|
2138
|
+
html[data-tl-theme="dark"] .tl-pill-btn { background: rgba(255,255,255,0.06); color: #fbfbfb;
|
|
2139
|
+
box-shadow: 0 1px 1px 0 rgba(0,0,0,0.24), inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06); }
|
|
2140
|
+
html[data-tl-theme="dark"] .tl-pill-btn .tl-dim { color: #8b8b8b; }
|
|
2141
|
+
html[data-tl-theme="dark"] .tl-pill-btn:hover:not(:disabled) { background: rgba(255,255,255,0.1); }
|
|
2142
|
+
html[data-tl-theme="dark"] .tl-pill-btn:disabled { color: #6b6b6b; background: rgba(255,255,255,0.06); box-shadow: none; }
|
|
2143
|
+
html[data-tl-theme="dark"] .tl-pill-btn.is-applied { color: #fbfbfb; background: rgba(255,255,255,0.06); box-shadow: none; }
|
|
2144
|
+
html[data-tl-theme="dark"] .tl-pill-btn.is-applied svg path { stroke: #fbfbfb; }
|
|
2145
|
+
html[data-tl-theme="dark"] .tl-refine-foot.is-bar { background: #1b1b1b; border-top-color: #262626; }
|
|
2146
|
+
/* liquid morph blobs need an OPAQUE fill (the goo filter hard-steps alpha,
|
|
2147
|
+
so a translucent fill would clip to nothing): flatten the dark pill's
|
|
2148
|
+
rgba(255,255,255,0.06)-over-#1b1b1b to its composite. */
|
|
2149
|
+
html[data-tl-theme="dark"] .tl-liquid-blob { background: #292929; }
|
|
2150
|
+
html[data-tl-theme="dark"] .tl-liquid-goo {
|
|
2151
|
+
filter: url(#tl-goo) drop-shadow(0 1px 1px rgba(0,0,0,0.24)); }
|
|
2152
|
+
/* dark chrome fade — same 250–430ms window as light */
|
|
2153
|
+
@keyframes tl-liquid-btn-chrome-dark {
|
|
2154
|
+
from { background: transparent; box-shadow: none; }
|
|
2155
|
+
to { background: rgba(255,255,255,0.06);
|
|
2156
|
+
box-shadow: 0 1px 1px 0 rgba(0,0,0,0.24),
|
|
2157
|
+
inset 0 0 0 1px rgba(255,255,255,0.04),
|
|
2158
|
+
inset 0 1px 0 0 rgba(255,255,255,0.06); } }
|
|
2159
|
+
html[data-tl-theme="dark"] .tl-liquid-btns .tl-pill-btn {
|
|
2160
|
+
animation: tl-liquid-btn-chrome-dark 180ms ease 250ms both; }
|
|
2161
|
+
html[data-tl-theme="dark"] .tl-refine-foot.is-bar.is-morphing,
|
|
2162
|
+
html[data-tl-theme="dark"] .tl-refine-foot.is-exiting-scan.is-morphing {
|
|
2163
|
+
--bar-bg: #1b1b1b; --bar-line: #262626; }
|
|
2164
|
+
|
|
2165
|
+
/* — Apply-all footer buttons (.pc-btn, shared into the build) — */
|
|
2166
|
+
html[data-tl-theme="dark"] .pc-btn { background: rgba(255,255,255,0.06); color: #fbfbfb; border-color: rgba(255,255,255,0.12); }
|
|
2167
|
+
html[data-tl-theme="dark"] .pc-btn:hover { background: rgba(255,255,255,0.1); }
|
|
2168
|
+
html[data-tl-theme="dark"] .pc-btn.primary { background: #fbfbfb; color: #1b1b1b; border-color: #fbfbfb; }
|
|
2169
|
+
html[data-tl-theme="dark"] .pc-btn.primary:hover { background: #fff; }
|
|
2170
|
+
|
|
2171
|
+
/* — shimmer / scan status highlights — */
|
|
2172
|
+
html[data-tl-theme="dark"] .tl-scan-shimmer { --shimmer-highlight: #fbfbfb; }
|
|
2173
|
+
html[data-tl-theme="dark"] .tl-refine-status-text { --shimmer-base: #6b6b6b; --shimmer-highlight: #fbfbfb; }
|
|
2174
|
+
html[data-tl-theme="dark"] .tl-scan-error { color: #ff8a7a; }
|
|
2175
|
+
|
|
2176
|
+
/* — live chip surfaces (Figma node 580:652) — */
|
|
2177
|
+
html[data-tl-theme="dark"] .tl-live-chip { background: rgba(151,251,142,0.1);
|
|
2178
|
+
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.24), inset 0 0 0 1px rgba(126,224,90,0.06), inset 0 -1px 0 0 rgba(86,218,99,0.08); }
|
|
2179
|
+
html[data-tl-theme="dark"] .tl-live-stop { background: rgba(68,167,84,0.1); box-shadow: inset 0 0 0 1px rgba(112,233,132,0.08); }
|
|
2180
|
+
html[data-tl-theme="dark"] .tl-live-stop:hover,
|
|
2181
|
+
html[data-tl-theme="dark"] .tl-live-stop:active { background: rgba(68,167,84,0.2); }
|
|
2182
|
+
html[data-tl-theme="dark"] .tl-live-chip.is-stopped { background: rgba(180,180,180,0.04);
|
|
2183
|
+
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.24), inset 0 0 0 1px rgba(255,255,255,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.2); }
|
|
1684
2184
|
</style>
|
|
1685
2185
|
</head>
|
|
1686
2186
|
<body>
|
|
@@ -1708,6 +2208,19 @@
|
|
|
1708
2208
|
// standalone demo.html.
|
|
1709
2209
|
const REFINE_VERSION = (typeof window !== "undefined" && window.__TX_REFINE_VERSION) || "0.3.5";
|
|
1710
2210
|
|
|
2211
|
+
// Apply the stored theme to <html data-tl-theme> synchronously, before the
|
|
2212
|
+
// panel mounts, so a returning dark-mode user never flashes the light panel.
|
|
2213
|
+
// Runs in both the demo and the injected build (this whole script ships via
|
|
2214
|
+
// inject.mjs). TimelinePanel re-applies + keeps it in sync afterwards.
|
|
2215
|
+
(function applyStoredTheme(){
|
|
2216
|
+
try{
|
|
2217
|
+
const pref=localStorage.getItem("tl-theme-pref")||"system";
|
|
2218
|
+
const sysDark=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2219
|
+
const r=pref==="dark"||(pref==="system"&&sysDark)?"dark":"light";
|
|
2220
|
+
document.documentElement.setAttribute("data-tl-theme",r);
|
|
2221
|
+
}catch(e){}
|
|
2222
|
+
})();
|
|
2223
|
+
|
|
1711
2224
|
// ── helpers ──
|
|
1712
2225
|
function parseCssTime(v) { const t=v.trim(); if(t.endsWith("ms")) return parseFloat(t); if(t.endsWith("s")) return parseFloat(t)*1000; const n=parseFloat(t); return isNaN(n)?0:n; }
|
|
1713
2226
|
function formatCssTime(ms) { if(ms>=1000&&ms%1000===0) return `${ms/1000}s`; if(ms>=100) return `${ms/1000}s`; return `${ms}ms`; }
|
|
@@ -2308,7 +2821,8 @@
|
|
|
2308
2821
|
// the three relay fns below short-circuit to a built-in mock so the panel is
|
|
2309
2822
|
// fully interactive with NO relay/LLM/CLI. When the flag is UNSET this whole
|
|
2310
2823
|
// block is inert and every fn falls through to the real fetch path unchanged.
|
|
2311
|
-
const TX_MOCK = typeof window!=="undefined" &&
|
|
2824
|
+
const TX_MOCK = typeof window!=="undefined" &&
|
|
2825
|
+
(window.__TX_REFINE_MOCK || /[?&]txmock\b/.test(window.location.search));
|
|
2312
2826
|
const _txMockJobs = new Map(); // id → { request, createdAt }
|
|
2313
2827
|
const _txMockId = ()=>"mock-"+Date.now()+"-"+Math.random().toString(36).slice(2,8);
|
|
2314
2828
|
const _txMs = (n)=>(n==null?"":Math.round(n)+"ms");
|
|
@@ -2417,15 +2931,45 @@
|
|
|
2417
2931
|
// motion-token tweaks: ONLY off-token values, exactly like the real engine.
|
|
2418
2932
|
const tweaks = _txTokenTweaks(t, ctx);
|
|
2419
2933
|
// related phases (open + close) → a recipe swap updates them together: one
|
|
2420
|
-
//
|
|
2934
|
+
// group of patches per phase, open slower than close (250ms / 150ms tokens).
|
|
2935
|
+
// Timing rides on a property:"all" patch; the scale value rides on dedicated
|
|
2936
|
+
// per-lane patches that carry the transform lane's BACKING VAR. A bare
|
|
2937
|
+
// property:"all" scale can't go live — the live preview writes a CSS custom
|
|
2938
|
+
// property and needs the var name, which only the scanned timings know.
|
|
2421
2939
|
const reqPhases = (request && Array.isArray(request.phases)) ? request.phases.filter(p=>p&&p.phase) : [];
|
|
2422
|
-
const
|
|
2423
|
-
|
|
2424
|
-
|
|
2940
|
+
const recipeScale = ph => /close/i.test(ph)?0.99:0.97;
|
|
2941
|
+
const recipeDur = ph => /close/i.test(ph)?150:250;
|
|
2942
|
+
// one scale patch per transform/filter lane that has a backing var, so the
|
|
2943
|
+
// applied override carries {scale, varName} and the live var gets written.
|
|
2944
|
+
const scalePatchesFor = (phase, timings) => (timings||[])
|
|
2945
|
+
.filter(tt=>tt.varName && tt.scale!=null)
|
|
2946
|
+
.map(tt=>({phase, property:tt.property, member:tt.member||null, varName:tt.varName, scale:recipeScale(phase)}));
|
|
2947
|
+
const phasePatches = (phase, timings) => [
|
|
2948
|
+
{phase, property:"all", durationMs:recipeDur(phase), easing:_TX_EASE},
|
|
2949
|
+
...scalePatchesFor(phase, timings)];
|
|
2950
|
+
let replacePatches = reqPhases.length>1
|
|
2951
|
+
? reqPhases.flatMap(p=>phasePatches(p.phase, p.timings))
|
|
2952
|
+
: phasePatches((request&&request.phase)||null, t);
|
|
2953
|
+
// If several phases resolve to the SAME backing var (a demo that reuses one
|
|
2954
|
+
// scale var for open + close), collapse to the most prominent value so the
|
|
2955
|
+
// single var lands on the recipe's open scale (0.97) instead of the subtler
|
|
2956
|
+
// close value (0.99). Real components use distinct vars → no-op here.
|
|
2957
|
+
{
|
|
2958
|
+
const seenVar=new Map(); const collapsed=[];
|
|
2959
|
+
for(const p of replacePatches){
|
|
2960
|
+
if(p.varName&&p.scale!=null){
|
|
2961
|
+
const prev=seenVar.get(p.varName);
|
|
2962
|
+
if(prev){ if(p.scale<prev.scale)prev.scale=p.scale; continue; }
|
|
2963
|
+
seenVar.set(p.varName,p);
|
|
2964
|
+
}
|
|
2965
|
+
collapsed.push(p);
|
|
2966
|
+
}
|
|
2967
|
+
replacePatches=collapsed;
|
|
2968
|
+
}
|
|
2425
2969
|
const replace = { id:"mock-replace-menu", kind:"replace", property:p0, member:m0,
|
|
2426
2970
|
title:"Menu dropdown", reference:"05-menu-dropdown.md",
|
|
2427
2971
|
patch:mp(m0,{property:"all", durationMs:250, easing:_TX_EASE, scale:0.97}),
|
|
2428
|
-
...(replacePatches?{patches:replacePatches}:{}),
|
|
2972
|
+
...(replacePatches&&replacePatches.length?{patches:replacePatches}:{}),
|
|
2429
2973
|
reason:"Best-fit transitions.dev recipe (#05 Menu dropdown): scale 0.97→1 + fade, 250ms open / 150ms close on cubic-bezier(0.22, 1, 0.36, 1)." };
|
|
2430
2974
|
const suggestions=[...tweaks, replace];
|
|
2431
2975
|
// Scope to the requested refineType so per-tab caches merge cleanly.
|
|
@@ -2719,7 +3263,7 @@
|
|
|
2719
3263
|
onReconnect&&h("button",{className:"tl-gate-recheck",disabled:rechecking,onClick:recheck},
|
|
2720
3264
|
rechecking?(relayDown?"Retrying…":"Reconnecting…"):(relayDown?"Retry connection":"Reconnect agent")));
|
|
2721
3265
|
}
|
|
2722
|
-
function RefinePanel({open,onClose,phase,label,refineType,onType,suggestions,summary,error,appliedIds,onApply,onApplyAll,mode,onMode,llmAvailable,cliInstalled,relayDown,agentReason,onReconnect,onStart,lanes}){
|
|
3266
|
+
function RefinePanel({open,onClose,phase,label,refineType,onType,suggestions,summary,error,appliedIds,onApply,onApplyAll,mode,onMode,llmAvailable,cliInstalled,relayDown,agentReason,onReconnect,onStart,lanes,resolvedTheme}){
|
|
2723
3267
|
// mount-on-open; keep mounted through the panel-reveal slide-out, then unmount
|
|
2724
3268
|
const[render,setRender]=useState(open);
|
|
2725
3269
|
const[panelOpen,setPanelOpen]=useState(false);
|
|
@@ -2762,6 +3306,26 @@
|
|
|
2762
3306
|
const id=setInterval(()=>setStatusIx(i=>(i+1)%REFINE_STATUS.length),2800);
|
|
2763
3307
|
return()=>clearInterval(id);
|
|
2764
3308
|
},[phase]);
|
|
3309
|
+
// liquid morph: when the scan finishes WITH results, the loading pill
|
|
3310
|
+
// splits into the two bar buttons via a gooey overlay. One-shot per scan;
|
|
3311
|
+
// cleared after the blob keyframes + button cross-blur settle.
|
|
3312
|
+
// useLayoutEffect (not useEffect): the flag must flip BEFORE the "done"
|
|
3313
|
+
// frame paints, otherwise the bar buttons flash for a frame, then the
|
|
3314
|
+
// exiting pill re-mounts on top of them — visible pill/buttons flicker.
|
|
3315
|
+
const[liquidMorph,setLiquidMorph]=useState(false);
|
|
3316
|
+
const prevPhaseRef=useRef(phase);
|
|
3317
|
+
useLayoutEffect(()=>{
|
|
3318
|
+
const prev=prevPhaseRef.current;
|
|
3319
|
+
prevPhaseRef.current=phase;
|
|
3320
|
+
if(prev==="scanning"&&phase==="done"){
|
|
3321
|
+
setLiquidMorph(true);
|
|
3322
|
+
// 300ms split → 300–430ms goo/chrome hand-off; unmount at 470ms once
|
|
3323
|
+
// the buttons are fully self-sufficient (small buffer past 430ms).
|
|
3324
|
+
const to=setTimeout(()=>setLiquidMorph(false),470);
|
|
3325
|
+
return()=>clearTimeout(to);
|
|
3326
|
+
}
|
|
3327
|
+
if(phase!=="done")setLiquidMorph(false);
|
|
3328
|
+
},[phase]);
|
|
2765
3329
|
if(!render)return null;
|
|
2766
3330
|
|
|
2767
3331
|
// one scan returns both kinds; each tab shows only its own:
|
|
@@ -2782,17 +3346,19 @@
|
|
|
2782
3346
|
// node morphs (card resize) into the loading rectangle with the border-beam.
|
|
2783
3347
|
const scanning = phase==="scanning";
|
|
2784
3348
|
const startBtn=(o)=>{o=o||{};
|
|
3349
|
+
const exiting = !!o.exiting;
|
|
3350
|
+
const showScanning = scanning || exiting;
|
|
2785
3351
|
// Both faces stay mounted and stacked; the morph cross-blurs (icon swap)
|
|
2786
3352
|
// between the label and the icon+status instead of swapping the subtree.
|
|
2787
|
-
return h("button",{className:cx("tl-scan-morph","t-resize",
|
|
2788
|
-
disabled:(!!o.disabled)||
|
|
2789
|
-
onClick:(o.disabled||
|
|
2790
|
-
scanning&&h(BorderBeam,{size:"md",colorVariant:"ocean",theme:"light",duration:2.6,saturation:2,brightness:1.6,className:"tl-scan-beam"},
|
|
3353
|
+
return h("button",{className:cx("tl-scan-morph","t-resize",showScanning&&"is-scanning",exiting&&"is-exiting"),
|
|
3354
|
+
disabled:(!!o.disabled)||showScanning,"aria-busy":scanning?"true":undefined,
|
|
3355
|
+
onClick:(o.disabled||showScanning)?undefined:onStart},
|
|
3356
|
+
scanning&&h(BorderBeam,{size:"md",colorVariant:"ocean",theme:resolvedTheme==="dark"?"dark":"light",duration:2.6,saturation:2,brightness:1.6,className:"tl-scan-beam"},
|
|
2791
3357
|
h("span",{className:"tl-scan-beam-fill"})),
|
|
2792
3358
|
h("span",{className:"tl-scan-content"},
|
|
2793
|
-
h("span",{className:"tl-scan-face tl-scan-face-label","aria-hidden":
|
|
3359
|
+
h("span",{className:"tl-scan-face tl-scan-face-label","aria-hidden":showScanning?"true":undefined},
|
|
2794
3360
|
h("span",{className:"tl-scan-label"},o.label||"Start scanning")),
|
|
2795
|
-
h("span",{className:"tl-scan-face tl-scan-face-status","aria-hidden":
|
|
3361
|
+
h("span",{className:"tl-scan-face tl-scan-face-status","aria-hidden":showScanning&&!exiting?undefined:"true"},
|
|
2796
3362
|
h(DotmLoader),
|
|
2797
3363
|
h(ShimmerSwapText,{text:REFINE_STATUS[statusIx]}))));
|
|
2798
3364
|
};
|
|
@@ -2840,6 +3406,10 @@
|
|
|
2840
3406
|
|
|
2841
3407
|
// Foot tracks the active type (it lives below the sliding body, so it
|
|
2842
3408
|
// just updates in place on a tab switch).
|
|
3409
|
+
// The liquid split only plays when the finished scan actually shows the
|
|
3410
|
+
// two-button bar — with zero results the foot is a plain pill and must
|
|
3411
|
+
// not pick up the morphing footer classes.
|
|
3412
|
+
const morphing = liquidMorph && phase==="done" && visible.length>0;
|
|
2843
3413
|
let foot, footBar=false;
|
|
2844
3414
|
if(phase==="scanning"){
|
|
2845
3415
|
foot = startBtn();
|
|
@@ -2849,12 +3419,38 @@
|
|
|
2849
3419
|
if(visible.length===0){
|
|
2850
3420
|
foot = startBtn({label:"Scan again"});
|
|
2851
3421
|
} else {
|
|
2852
|
-
footBar =
|
|
2853
|
-
|
|
3422
|
+
footBar = !morphing;
|
|
3423
|
+
const barBtns = h(React.Fragment,null,
|
|
2854
3424
|
h("button",{className:"tl-pill-btn",disabled:pending.length===0,
|
|
2855
3425
|
onClick:()=>pending.forEach(onApply)},
|
|
2856
3426
|
h("span",null,"Apply all ",h("span",{className:"tl-dim"},"("+pending.length+")"))),
|
|
2857
|
-
h("button",{className:"tl-pill-btn",onClick:onStart},"Scan again"));
|
|
3427
|
+
h("button",{className:"tl-pill-btn",onClick:onStart},h("span",null,"Scan again")));
|
|
3428
|
+
foot = morphing
|
|
3429
|
+
// gooey split: the exiting pill fades out on the shrinking stage
|
|
3430
|
+
// (which drives the foot's height transition), while the overlay —
|
|
3431
|
+
// blobs, chrome, labels — is a foot-anchored SIBLING pinned to the
|
|
3432
|
+
// bar's final geometry, so it never moves or resizes at handoff.
|
|
3433
|
+
? h(React.Fragment,null,
|
|
3434
|
+
h("div",{className:"tl-liquid-stage"}),
|
|
3435
|
+
h("div",{className:"tl-liquid-morph"},
|
|
3436
|
+
startBtn({exiting:true}),
|
|
3437
|
+
h("svg",{className:"tl-liquid-goo-svg","aria-hidden":"true",focusable:"false"},
|
|
3438
|
+
h("defs",null,
|
|
3439
|
+
h("filter",{id:"tl-goo"},
|
|
3440
|
+
// alpha threshold must be exactly 0.5 (20a - 10): a blurred
|
|
3441
|
+
// straight edge crosses 0.5 precisely AT the true geometry
|
|
3442
|
+
// edge, so goo pills render at the real 32px button height.
|
|
3443
|
+
// Lower thresholds (e.g. 22a - 9 = 0.41) dilate every edge
|
|
3444
|
+
// ~1.5px outward — the goo state looked ~4px taller.
|
|
3445
|
+
h("feGaussianBlur",{in:"SourceGraphic",stdDeviation:"7",result:"blur"}),
|
|
3446
|
+
h("feColorMatrix",{in:"blur",mode:"matrix",
|
|
3447
|
+
values:"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10",result:"goo"}),
|
|
3448
|
+
h("feComposite",{in:"SourceGraphic",in2:"goo",operator:"atop"})))),
|
|
3449
|
+
h("div",{className:"tl-liquid-goo"},
|
|
3450
|
+
h("div",{className:"tl-liquid-blob tl-liquid-blob-a"}),
|
|
3451
|
+
h("div",{className:"tl-liquid-blob tl-liquid-blob-b"})),
|
|
3452
|
+
h("div",{className:"tl-liquid-btns"},barBtns)))
|
|
3453
|
+
: barBtns;
|
|
2858
3454
|
}
|
|
2859
3455
|
} else { // idle
|
|
2860
3456
|
foot = startBtn(agentReady?{}:{disabled:true});
|
|
@@ -2890,7 +3486,7 @@
|
|
|
2890
3486
|
bodyFor("small",refineType==="small")),
|
|
2891
3487
|
h("div",{className:"tl-refine-page t-page","data-page-id":"2","aria-hidden":refineType!=="replace"},
|
|
2892
3488
|
bodyFor("replace",refineType==="replace"))),
|
|
2893
|
-
h("div",{className:cx("tl-refine-foot",footBar&&"is-bar")},foot)));
|
|
3489
|
+
h("div",{className:cx("tl-refine-foot",footBar&&"is-bar",morphing&&"is-exiting-scan",morphing&&"is-morphing")},foot)));
|
|
2894
3490
|
}
|
|
2895
3491
|
|
|
2896
3492
|
// Diff the active item's effective (edited/refined) lanes against their
|
|
@@ -2934,12 +3530,12 @@
|
|
|
2934
3530
|
return out;
|
|
2935
3531
|
}
|
|
2936
3532
|
|
|
2937
|
-
function Header({entries,active,onSelect,onReset,onCopy,copied,snap,setSnap,onMinimize,onRefine,refineActive,onAccept,acceptState,acceptDisabled,acceptError,scanning,scanError,onRescan,pick,setPick,chatLoop,liveStopped,onStopLive}){
|
|
3533
|
+
function Header({entries,active,onSelect,onReset,onCopy,copied,snap,setSnap,onMinimize,onRefine,refineActive,onAccept,acceptState,acceptDisabled,acceptError,scanning,scanError,onRescan,pick,setPick,chatLoop,liveStopped,onStopLive,themePref,setThemePref}){
|
|
2938
3534
|
const[setg,setSetg]=useState(false);
|
|
2939
3535
|
// settings dropdown sub-page (main menu ↔ keyboard-shortcuts cheat sheet)
|
|
2940
3536
|
const[settingsView,setSettingsView]=useState("main"); // "main" | "shortcuts"
|
|
2941
|
-
// theme preference
|
|
2942
|
-
|
|
3537
|
+
// theme preference is owned by TimelinePanel (persisted + applied to <html>);
|
|
3538
|
+
// Header just renders the picker and reports changes up.
|
|
2943
3539
|
const THEME_OPTS=[{key:"light",label:"Light"},{key:"dark",label:"Dark"},{key:"system",label:"System"}];
|
|
2944
3540
|
const isMac=typeof navigator!=="undefined"&&/mac/i.test((navigator.userAgentData&&navigator.userAgentData.platform)||navigator.platform||navigator.userAgent||"");
|
|
2945
3541
|
const modKey=isMac?"Cmd":"Ctrl";
|
|
@@ -3062,9 +3658,9 @@
|
|
|
3062
3658
|
h("span",{className:"tl-menu-chevr"},h(Ic,{name:"chevronr",size:16})))},"Theme"),
|
|
3063
3659
|
h(MenuItem,{onClick:()=>setSettingsView("shortcuts"),
|
|
3064
3660
|
right:h("span",{className:"tl-menu-chevr"},h(Ic,{name:"chevronr",size:16}))},"Keyboard shortcuts"),
|
|
3065
|
-
h(MenuItem,{onClick:()=>{setSetg(false);window.open("
|
|
3661
|
+
h(MenuItem,{onClick:()=>{setSetg(false);window.open("mailto:jakubja@gmail.com","_blank","noopener");},
|
|
3066
3662
|
right:h("span",{className:"tl-menu-chevr"},h(Ic,{name:"linkout",size:16}))},"Send feedback"),
|
|
3067
|
-
h(MenuItem,{onClick:()=>{setSetg(false);window.open("https://transitions.dev","_blank","noopener");},
|
|
3663
|
+
h(MenuItem,{onClick:()=>{setSetg(false);window.open("https://transitions.dev/refine","_blank","noopener");},
|
|
3068
3664
|
right:h("span",{className:"tl-menu-chevr"},h(Ic,{name:"linkout",size:16}))},"Learn more"),
|
|
3069
3665
|
h("div",{className:"tl-menu-divider"}),
|
|
3070
3666
|
h("div",{className:"tl-menu-head"},
|
|
@@ -3121,54 +3717,188 @@
|
|
|
3121
3717
|
h("i",{style:{"--ox":"-5px","--oy":"-2px","--sx":"-9px","--sy":"-9px","--sd":"930ms","--sdelay":"180ms"}}),
|
|
3122
3718
|
h("i",{style:{"--ox":"3px","--oy":"1px","--sx":"12px","--sy":"1px","--sd":"980ms","--sdelay":"300ms"}})),
|
|
3123
3719
|
h("span",{className:"tl-btn-label"},"Refine")),
|
|
3124
|
-
h("span",{className:"t-tt tl-tt-below",role:"tooltip"},"Refine",ttHint("
|
|
3720
|
+
h("span",{className:"t-tt tl-tt-below",role:"tooltip"},"Refine",ttHint("E"))),
|
|
3125
3721
|
h("span",{className:"t-tt-wrap"},
|
|
3126
3722
|
h("button",{className:"tl-icon-btn ghost t-tt-trigger","aria-label":"Minimize",onClick:onMinimize},h(Ic,{name:"minimize"})),
|
|
3127
3723
|
h("span",{className:"t-tt tl-tt-below tl-tt-right",role:"tooltip"},"Minimize",ttHint("."))),
|
|
3128
3724
|
);
|
|
3129
3725
|
}
|
|
3130
3726
|
|
|
3727
|
+
// capsule outline: right-drag pinches inward (neck), left-drag bows outward
|
|
3728
|
+
// (belly, 70% depth). `amt` > 0 = outward, < 0 = inward. Same command
|
|
3729
|
+
// structure at every value so the CSS `d` transition interpolates smoothly.
|
|
3730
|
+
const RB_H=24, RB_R=8;
|
|
3731
|
+
function rbPath(w, amt){
|
|
3732
|
+
const mx=(w/2).toFixed(1), t=(-2*amt).toFixed(2), b=(RB_H+2*amt).toFixed(2);
|
|
3733
|
+
return `path("M 0 ${RB_R} Q 0 0 ${RB_R} 0 Q ${mx} ${t} ${w-RB_R} 0 Q ${w} 0 ${w} ${RB_R} L ${w} ${RB_H-RB_R} Q ${w} ${RB_H} ${w-RB_R} ${RB_H} Q ${mx} ${b} ${RB_R} ${RB_H} Q 0 ${RB_H} 0 ${RB_H-RB_R} Z")`;
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3131
3736
|
function PropTrack({property, member, delayMs, durationMs, selected, onSelect, onDelayChange, onDurationChange, snap, scaleMs, lockDuration, labelW}){
|
|
3132
3737
|
const trackRef=useRef(null);
|
|
3738
|
+
const barRef=useRef(null);
|
|
3133
3739
|
const [dragging,setDragging]=useState(false);
|
|
3134
|
-
//
|
|
3135
|
-
//
|
|
3740
|
+
// Drag math stays at 1ms resolution for smooth motion. Any coarse grid
|
|
3741
|
+
// quantization is avoided here because it reads as frame snapping.
|
|
3136
3742
|
const SNAP_GRID = 25;
|
|
3137
3743
|
const pxToMs=useCallback((px,g)=>{if(!trackRef.current)return 0;const w=trackRef.current.getBoundingClientRect().width;const gg=g||1;return Math.round((px/w)*scaleMs/gg)*gg;},[scaleMs]);
|
|
3138
3744
|
|
|
3745
|
+
// keep the SVG body outline in sync with the bar's real size
|
|
3746
|
+
useLayoutEffect(()=>{
|
|
3747
|
+
const bar=barRef.current; if(!bar) return;
|
|
3748
|
+
const p=bar.querySelector(".tl-bar-body path");
|
|
3749
|
+
const sync=()=>{
|
|
3750
|
+
if(bar.classList.contains("rb-release")) return;
|
|
3751
|
+
if(bar.classList.contains("is-dragging")){
|
|
3752
|
+
p.style.d=rbPath(bar.offsetWidth,bar.__rbBulge||0);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
p.style.transition="none";
|
|
3756
|
+
p.style.d=rbPath(bar.offsetWidth,0);
|
|
3757
|
+
requestAnimationFrame(()=>p.style.removeProperty("transition"));
|
|
3758
|
+
};
|
|
3759
|
+
sync();
|
|
3760
|
+
const ro=new ResizeObserver(sync); ro.observe(bar);
|
|
3761
|
+
return ()=>ro.disconnect();
|
|
3762
|
+
},[]);
|
|
3763
|
+
|
|
3139
3764
|
const startDrag=useCallback((mode,e)=>{
|
|
3140
3765
|
e.preventDefault(); e.stopPropagation();
|
|
3141
3766
|
onSelect();
|
|
3142
|
-
// a spring controls its own duration, so its bar can be moved (delay) but not resized
|
|
3143
3767
|
if(lockDuration && mode!=="move") return;
|
|
3144
3768
|
setDragging(true);
|
|
3145
3769
|
const startX=e.clientX; const sd=delayMs; const sdur=durationMs;
|
|
3146
3770
|
let lastDel=sd, lastDur=sdur;
|
|
3771
|
+
|
|
3772
|
+
// ── rubber-band physics ──
|
|
3773
|
+
const bar=barRef.current;
|
|
3774
|
+
const bodyPath=bar?bar.querySelector(".tl-bar-body path"):null;
|
|
3775
|
+
let lastX=e.clientX, lastT=performance.now();
|
|
3776
|
+
let vel=0, dir=1, over=0, raf=0;
|
|
3777
|
+
if(bar){
|
|
3778
|
+
clearTimeout(bar.__rbCleanup);
|
|
3779
|
+
bar.classList.remove("rb-release");
|
|
3780
|
+
bar.classList.add("is-dragging");
|
|
3781
|
+
}
|
|
3782
|
+
const STRAIGHT_HANDLE='path("M 4 1 L 4 13")';
|
|
3783
|
+
const leftPath=bar?bar.querySelector(".tl-bar-handle.left path"):null;
|
|
3784
|
+
const rightPath=bar?bar.querySelector(".tl-bar-handle.right path"):null;
|
|
3785
|
+
const bentHandle=bow=>{
|
|
3786
|
+
const tip=(4+bow*0.15).toFixed(2), mid=(4+bow).toFixed(2);
|
|
3787
|
+
return `path("M ${tip} 1 Q ${mid} 7 ${tip} 13")`;
|
|
3788
|
+
};
|
|
3789
|
+
const setHandleBend=(bow,leading)=>{
|
|
3790
|
+
if(leftPath) leftPath.style.d=leading==="left"?bentHandle(bow):STRAIGHT_HANDLE;
|
|
3791
|
+
if(rightPath) rightPath.style.d=leading==="right"?bentHandle(bow):STRAIGHT_HANDLE;
|
|
3792
|
+
};
|
|
3793
|
+
let env=0;
|
|
3794
|
+
const setVars=()=>{
|
|
3795
|
+
if(!bar||!bodyPath) return;
|
|
3796
|
+
const sV=Math.abs(vel)/(Math.abs(vel)+0.15);
|
|
3797
|
+
const sO=over/(over+40);
|
|
3798
|
+
const s=Math.min(1,Math.max(sV,sO)*1.05);
|
|
3799
|
+
env=s>env?env+(s-env)*0.35:env*0.90;
|
|
3800
|
+
const w=bar.offsetWidth;
|
|
3801
|
+
if(mode==="move"){
|
|
3802
|
+
// Whole-bar drag should be pure translation only: no rubber bend.
|
|
3803
|
+
bar.__rbBulge=0;
|
|
3804
|
+
for(const v of ["--rb-origin","--rb-sx","--rb-sy","--rb-skew","--rb-hshift"]) bar.style.removeProperty(v);
|
|
3805
|
+
bodyPath.style.d=rbPath(w,0);
|
|
3806
|
+
setHandleBend(0,"none");
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
// Left-edge resize should mirror the bend direction (user expectation:
|
|
3810
|
+
// same pointer motion on the left edge bends opposite to the right edge).
|
|
3811
|
+
const bendDir = mode==="left" ? -dir : dir;
|
|
3812
|
+
const origin=mode==="move"?(dir>0?"0%":"100%"):(mode==="right"?"0%":"100%");
|
|
3813
|
+
bar.style.setProperty("--rb-origin",origin);
|
|
3814
|
+
bar.style.setProperty("--rb-sx",(1+(8*env)/Math.max(w,24)).toFixed(4));
|
|
3815
|
+
bar.style.setProperty("--rb-sy",(1-0.1*env).toFixed(4));
|
|
3816
|
+
bar.style.setProperty("--rb-skew",(bendDir*-3*env).toFixed(2)+"deg");
|
|
3817
|
+
bar.style.setProperty("--rb-hshift",(bendDir*1.5*env).toFixed(2)+"px");
|
|
3818
|
+
const bend=Math.min(4,(w-16)/8)*env;
|
|
3819
|
+
bar.__rbBulge=bendDir<0?bend*0.7:-bend;
|
|
3820
|
+
bodyPath.style.d=rbPath(w,bar.__rbBulge);
|
|
3821
|
+
const leading=mode==="move"?(dir>0?"right":"left"):mode;
|
|
3822
|
+
setHandleBend(bendDir*8*env,leading);
|
|
3823
|
+
};
|
|
3824
|
+
let lastTick=performance.now();
|
|
3825
|
+
const tick=()=>{ lastTick=performance.now(); vel*=0.86; setVars(); raf=requestAnimationFrame(tick); };
|
|
3826
|
+
raf=requestAnimationFrame(tick);
|
|
3827
|
+
const safety=setInterval(()=>{
|
|
3828
|
+
if(performance.now()-lastTick<40) return;
|
|
3829
|
+
vel*=0.86; setVars();
|
|
3830
|
+
},50);
|
|
3831
|
+
|
|
3147
3832
|
const onMove=e2=>{
|
|
3148
|
-
const
|
|
3833
|
+
const now=performance.now(); const dt=Math.max(1,now-lastT);
|
|
3834
|
+
const iv=(e2.clientX-lastX)/dt;
|
|
3835
|
+
vel+=(iv-vel)*0.3;
|
|
3836
|
+
if(Math.abs(vel)>0.05) dir=vel>0?1:-1;
|
|
3837
|
+
lastX=e2.clientX; lastT=now;
|
|
3838
|
+
const w=trackRef.current?trackRef.current.getBoundingClientRect().width:1;
|
|
3839
|
+
const msToPx=ms=>Math.abs(ms)/scaleMs*w;
|
|
3840
|
+
const grid=1;
|
|
3841
|
+
const dx=e2.clientX-startX; const dMs=pxToMs(dx,grid);
|
|
3149
3842
|
if(mode==="move"){
|
|
3150
|
-
|
|
3151
|
-
|
|
3843
|
+
const raw=sd+dMs; const next=Math.max(0,Math.min(raw,scaleMs-sdur));
|
|
3844
|
+
over=msToPx(raw-next);
|
|
3845
|
+
lastDel=next; onDelayChange(next);
|
|
3152
3846
|
} else if(mode==="left"){
|
|
3153
|
-
const rawDel=sd+dMs; const clampedDel=Math.max(0,Math.min(rawDel,sd+sdur-
|
|
3847
|
+
const rawDel=sd+dMs; const clampedDel=Math.max(0,Math.min(rawDel,sd+sdur-grid));
|
|
3848
|
+
over=msToPx(rawDel-clampedDel);
|
|
3154
3849
|
lastDel=clampedDel; lastDur=sdur-(clampedDel-sd);
|
|
3155
3850
|
onDelayChange(lastDel); onDurationChange(lastDur);
|
|
3156
3851
|
} else {
|
|
3157
|
-
|
|
3158
|
-
|
|
3852
|
+
const rawDur=sdur+dMs; const next=Math.max(grid,Math.min(rawDur,scaleMs-sd));
|
|
3853
|
+
over=msToPx(rawDur-next);
|
|
3854
|
+
lastDur=next; onDurationChange(next);
|
|
3159
3855
|
}
|
|
3856
|
+
setVars();
|
|
3160
3857
|
};
|
|
3161
3858
|
const onUp=()=>{
|
|
3162
|
-
setDragging(false);
|
|
3163
|
-
// snap to the grid once, on release (bar tweens there since the
|
|
3164
|
-
// is-dragging transition lock is now removed).
|
|
3165
|
-
if(snap){
|
|
3166
|
-
const g=SNAP_GRID, sn=v=>Math.round(v/g)*g;
|
|
3167
|
-
if(mode==="move"){ onDelayChange(Math.max(0,Math.min(sn(lastDel),scaleMs-sdur))); }
|
|
3168
|
-
else if(mode==="left"){ onDelayChange(Math.max(0,sn(lastDel))); onDurationChange(Math.max(g,sn(lastDur))); }
|
|
3169
|
-
else { onDurationChange(Math.max(g,Math.min(sn(lastDur),scaleMs-sd))); }
|
|
3170
|
-
}
|
|
3171
3859
|
window.removeEventListener("mousemove",onMove);window.removeEventListener("mouseup",onUp);
|
|
3860
|
+
cancelAnimationFrame(raf); clearInterval(safety);
|
|
3861
|
+
// Freeze the bar exactly where it is RENDERED at mouseup. On a fast
|
|
3862
|
+
// drag React's last state flush can still be in flight — the DOM lags
|
|
3863
|
+
// the pointer — and letting that flush land after release reads as
|
|
3864
|
+
// the bar drifting on its own. Committing the visible layout position
|
|
3865
|
+
// (offsetLeft/offsetWidth → ms) overrides any in-flight value with
|
|
3866
|
+
// what the user actually sees, so the bar cannot move after release.
|
|
3867
|
+
if(bar&&trackRef.current){
|
|
3868
|
+
const grid=1;
|
|
3869
|
+
const tw=trackRef.current.getBoundingClientRect().width||1;
|
|
3870
|
+
const px2ms=px=>Math.round(px/tw*scaleMs/grid)*grid;
|
|
3871
|
+
const visDel=Math.max(0,px2ms(bar.offsetLeft));
|
|
3872
|
+
if(mode==="move"){
|
|
3873
|
+
onDelayChange(Math.min(visDel,Math.max(0,scaleMs-sdur)));
|
|
3874
|
+
} else if(bar.offsetWidth>24){ // min-width clamp would corrupt px→ms
|
|
3875
|
+
const visDur=Math.max(grid,px2ms(bar.offsetWidth));
|
|
3876
|
+
if(mode==="left"){ onDelayChange(visDel); onDurationChange(visDur); }
|
|
3877
|
+
else { onDurationChange(Math.min(visDur,scaleMs-sd)); }
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if(bar&&bodyPath){
|
|
3881
|
+
bar.__rbBulge=0;
|
|
3882
|
+
for(const v of ["--rb-sx","--rb-sy","--rb-skew","--rb-hshift"]) bar.style.removeProperty(v);
|
|
3883
|
+
bodyPath.style.d=rbPath(bar.offsetWidth,0);
|
|
3884
|
+
setHandleBend(0,"none");
|
|
3885
|
+
// geometry tweens stay hard-disabled until the release commit has
|
|
3886
|
+
// rendered — position must land instantly (it equals what's already
|
|
3887
|
+
// on screen), never animate.
|
|
3888
|
+
bar.classList.add("no-geom-tween");
|
|
3889
|
+
if(mode!=="move") bar.classList.add("rb-release");
|
|
3890
|
+
requestAnimationFrame(()=>requestAnimationFrame(()=>{
|
|
3891
|
+
setDragging(false);
|
|
3892
|
+
bar.classList.remove("no-geom-tween");
|
|
3893
|
+
}));
|
|
3894
|
+
bar.__rbCleanup=setTimeout(()=>{
|
|
3895
|
+
bar.classList.remove("rb-release");
|
|
3896
|
+
bar.style.removeProperty("--rb-origin");
|
|
3897
|
+
bodyPath.style.d=rbPath(bar.offsetWidth,0);
|
|
3898
|
+
if(leftPath) leftPath.style.removeProperty("d");
|
|
3899
|
+
if(rightPath) rightPath.style.removeProperty("d");
|
|
3900
|
+
},220);
|
|
3901
|
+
}
|
|
3172
3902
|
};
|
|
3173
3903
|
window.addEventListener("mousemove",onMove);window.addEventListener("mouseup",onUp);
|
|
3174
3904
|
},[delayMs,durationMs,pxToMs,snap,scaleMs,onSelect,onDelayChange,onDurationChange,lockDuration]);
|
|
@@ -3180,10 +3910,15 @@
|
|
|
3180
3910
|
member&&h("span",{className:"tl-prop-member"},member),
|
|
3181
3911
|
h("span",{className:"tl-prop-prop"},property))),
|
|
3182
3912
|
h("div",{className:"tl-prop-track",ref:trackRef},
|
|
3183
|
-
h("div",{className:cx("tl-bar",dragging&&"is-dragging"),style:{left:delPct+"%",width:durPct+"%"},onMouseDown:e=>startDrag("move",e),
|
|
3913
|
+
h("div",{className:cx("tl-bar",dragging&&"is-dragging"),ref:barRef,style:{left:delPct+"%",width:durPct+"%"},onMouseDown:e=>startDrag("move",e),
|
|
3184
3914
|
title:lockDuration?"Spring duration is derived \u2014 drag to move, resize is locked":undefined},
|
|
3185
|
-
h("
|
|
3186
|
-
|
|
3915
|
+
h("svg",{className:"tl-bar-body","aria-hidden":true},
|
|
3916
|
+
h("defs",null,h("linearGradient",{id:"rb-ring",x1:0,y1:0,x2:0,y2:1},
|
|
3917
|
+
h("stop",{offset:"0",stopColor:"#000",stopOpacity:0.06}),
|
|
3918
|
+
h("stop",{offset:"1",stopColor:"#000",stopOpacity:0.15}))),
|
|
3919
|
+
h("path",null)),
|
|
3920
|
+
h("span",{className:"tl-bar-handle left"},h("svg",{viewBox:"0 0 8 14"},h("path",{d:"M 4 1 L 4 13"}))),
|
|
3921
|
+
h("span",{className:"tl-bar-handle right"},h("svg",{viewBox:"0 0 8 14"},h("path",{d:"M 4 1 L 4 13"}))),
|
|
3187
3922
|
!lockDuration&&h("div",{className:"tl-bar-grip left",onMouseDown:e=>startDrag("left",e)}),
|
|
3188
3923
|
!lockDuration&&h("div",{className:"tl-bar-grip right",onMouseDown:e=>startDrag("right",e)}),
|
|
3189
3924
|
),
|
|
@@ -3771,6 +4506,33 @@
|
|
|
3771
4506
|
const[panelHeight,setPanelHeight]=useState(440);
|
|
3772
4507
|
const[resizing,setResizing]=useState(false);
|
|
3773
4508
|
const[snap,setSnap]=useState(true);
|
|
4509
|
+
// ── theme (light / dark / system) ──
|
|
4510
|
+
// Preference persists in localStorage; "system" follows the OS via
|
|
4511
|
+
// matchMedia. The resolved value is mirrored onto <html data-tl-theme> so the
|
|
4512
|
+
// panel AND its body-portaled menus/toasts theme together, and is handed to
|
|
4513
|
+
// BorderBeam to pick its dark beam. A dedicated attribute (not "data-theme")
|
|
4514
|
+
// avoids clobbering a host prototype's own theming in the injected build.
|
|
4515
|
+
const[themePref,setThemePref]=useState(()=>{
|
|
4516
|
+
try{const v=localStorage.getItem("tl-theme-pref");return v==="light"||v==="dark"||v==="system"?v:"system";}catch{return "system";}
|
|
4517
|
+
});
|
|
4518
|
+
const systemDark=()=>{try{return window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches;}catch{return false;}};
|
|
4519
|
+
const[resolvedTheme,setResolvedTheme]=useState(()=>themePref==="system"?(systemDark()?"dark":"light"):themePref);
|
|
4520
|
+
useEffect(()=>{
|
|
4521
|
+
try{localStorage.setItem("tl-theme-pref",themePref);}catch{}
|
|
4522
|
+
const apply=()=>{
|
|
4523
|
+
const r=themePref==="system"?(systemDark()?"dark":"light"):themePref;
|
|
4524
|
+
setResolvedTheme(r);
|
|
4525
|
+
try{document.documentElement.setAttribute("data-tl-theme",r);}catch{}
|
|
4526
|
+
};
|
|
4527
|
+
apply();
|
|
4528
|
+
if(themePref!=="system")return;
|
|
4529
|
+
// Follow live OS changes only while on "system".
|
|
4530
|
+
let mq;try{mq=window.matchMedia("(prefers-color-scheme: dark)");}catch{return;}
|
|
4531
|
+
if(!mq)return;
|
|
4532
|
+
const onChange=()=>apply();
|
|
4533
|
+
if(mq.addEventListener)mq.addEventListener("change",onChange);else if(mq.addListener)mq.addListener(onChange);
|
|
4534
|
+
return()=>{if(mq.removeEventListener)mq.removeEventListener("change",onChange);else if(mq.removeListener)mq.removeListener(onChange);};
|
|
4535
|
+
},[themePref]);
|
|
3774
4536
|
// transition picker open state — lifted out of Header so the panel-focused
|
|
3775
4537
|
// Cmd/Ctrl+K shortcut can open it.
|
|
3776
4538
|
const[pickOpen,setPickOpen]=useState(false);
|
|
@@ -4184,7 +4946,19 @@
|
|
|
4184
4946
|
if(!specs.length) return ()=>txTeardownPhase(store);
|
|
4185
4947
|
const varsKey=v=>Object.keys(v).sort().map(k=>k+":"+v[k]).join(";");
|
|
4186
4948
|
const recompute=()=>{
|
|
4187
|
-
|
|
4949
|
+
// Two concerns, applied DIFFERENTLY:
|
|
4950
|
+
// • TIMING (the inline `transition` string) is direction-specific —
|
|
4951
|
+
// open and close share one element + one inline slot, so it must be
|
|
4952
|
+
// GATED to the active state (open's timing only while open, etc).
|
|
4953
|
+
// • VALUE VARS (scale/blur custom props, e.g. --dropdown-pre-scale)
|
|
4954
|
+
// define a STATE's resting value, not the transition itself. A
|
|
4955
|
+
// "from" var only shows in the OPPOSITE state, so gating it to the
|
|
4956
|
+
// destination state means it's never visible (this was the bug:
|
|
4957
|
+
// accepting a scale edit did nothing live). They're safe to apply
|
|
4958
|
+
// PERSISTENTLY — each var only affects whichever state references
|
|
4959
|
+
// it — which previews both directions. So: vars = always, css = gated.
|
|
4960
|
+
const desiredCss=new Map(); // el → css (state-gated)
|
|
4961
|
+
const desiredVars=new Map(); // el → {name:value} (persistent)
|
|
4188
4962
|
for(const sp of specs){
|
|
4189
4963
|
const stEls=fresh(sp.stateTarget);
|
|
4190
4964
|
// is THIS phase's destination state active on the stateTarget RIGHT NOW?
|
|
@@ -4192,19 +4966,31 @@
|
|
|
4192
4966
|
if(sp.toSuf) active=stEls.length?stEls.some(e=>safeMatches(e,sp.toSuf)):true; // open
|
|
4193
4967
|
else if(sp.fromSuf) active=stEls.length?stEls.some(e=>!safeMatches(e,sp.fromSuf)):true; // close = not in fromState
|
|
4194
4968
|
else active=true; // no state info → always reflect (degrades to old behavior)
|
|
4195
|
-
|
|
4196
|
-
|
|
4969
|
+
for(const mm of sp.members) for(const el of txMemberEls(mm.selector,sp.stateTarget)){
|
|
4970
|
+
if(inUI(el))continue;
|
|
4971
|
+
if(mm.vars&&Object.keys(mm.vars).length){ const ex=desiredVars.get(el)||{}; desiredVars.set(el,{...ex,...mm.vars}); }
|
|
4972
|
+
if(active) desiredCss.set(el,mm.css);
|
|
4973
|
+
}
|
|
4197
4974
|
}
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4975
|
+
const allEls=new Set([...desiredCss.keys(),...desiredVars.keys()]);
|
|
4976
|
+
for(const el of allEls){
|
|
4977
|
+
const css=desiredCss.has(el)?desiredCss.get(el):null;
|
|
4978
|
+
const vars=desiredVars.get(el)||{};
|
|
4979
|
+
const vk=varsKey(vars);
|
|
4980
|
+
const cur=store.applied.get(el);
|
|
4981
|
+
if(cur&&cur.css===css&&cur.vk===vk)continue;
|
|
4982
|
+
if(!cur){
|
|
4983
|
+
store.applied.set(el,{prevInline:el.style.transition||"",prevVars:Object.fromEntries(Object.keys(vars).map(n=>[n,el.style.getPropertyValue(n)])),css,vk});
|
|
4984
|
+
}else{
|
|
4985
|
+
const prevVars={...(cur.prevVars||{})};for(const n of Object.keys(vars)){if(!(n in prevVars))prevVars[n]=el.style.getPropertyValue(n);}
|
|
4986
|
+
store.applied.set(el,{prevInline:cur.prevInline,prevVars,css,vk});
|
|
4987
|
+
}
|
|
4988
|
+
const rec=store.applied.get(el);
|
|
4989
|
+
try{el.style.transition=css!=null?css:rec.prevInline;}catch{}
|
|
4204
4990
|
for(const n of Object.keys(vars)){try{el.style.setProperty(n,vars[n]);}catch{}}
|
|
4205
4991
|
}
|
|
4206
4992
|
for(const [el,rec] of Array.from(store.applied)){
|
|
4207
|
-
if(
|
|
4993
|
+
if(allEls.has(el))continue;
|
|
4208
4994
|
try{el.style.transition=rec.prevInline;}catch{}
|
|
4209
4995
|
for(const n of Object.keys(rec.prevVars||{})){try{const pv=rec.prevVars[n];if(pv)el.style.setProperty(n,pv);else el.style.removeProperty(n);}catch{}}
|
|
4210
4996
|
store.applied.delete(el);
|
|
@@ -4554,7 +5340,7 @@
|
|
|
4554
5340
|
if(sel&&!sel.isCollapsed)return;
|
|
4555
5341
|
handle(()=>{if(L.active)L.copyValues&&L.copyValues();});
|
|
4556
5342
|
break;}
|
|
4557
|
-
case"
|
|
5343
|
+
case"e":case"E": // Toggle Refine panel
|
|
4558
5344
|
handle(()=>{if(L.active)L.openRefine&&L.openRefine();});
|
|
4559
5345
|
break;
|
|
4560
5346
|
case"k":case"K": // Open transition picker
|
|
@@ -4598,7 +5384,7 @@
|
|
|
4598
5384
|
onAccept,acceptState,acceptDisabled:computeChanges(active,entries).length===0,acceptError,
|
|
4599
5385
|
scanning:groupScanState==="scanning",scanError:groupScanState==="error"?groupScanErr:null,
|
|
4600
5386
|
onRescan:rescanTransitions,pick:pickOpen,setPick:setPickOpen,
|
|
4601
|
-
chatLoop,liveStopped,onStopLive:stopLive}),
|
|
5387
|
+
chatLoop,liveStopped,onStopLive:stopLive,themePref,setThemePref}),
|
|
4602
5388
|
active
|
|
4603
5389
|
?h(Body,{entry:active,onPropChange:(prop,o)=>setPropOverride(prop,o),snap})
|
|
4604
5390
|
:h("div",{className:"tl-empty"},"Select a transition to inspect and edit it")),
|
|
@@ -4606,7 +5392,7 @@
|
|
|
4606
5392
|
refineType,onType:changeRefineType,suggestions:refineSuggestions,summary:refineSummary,error:refineError,
|
|
4607
5393
|
appliedIds,onApply:applySuggestion,onApplyAll:applyAllSuggestions,
|
|
4608
5394
|
mode:refineMode,onMode:changeRefineMode,llmAvailable,cliInstalled,relayDown,agentReason,onReconnect:reconnectAgent,onStart:startScan,
|
|
4609
|
-
lanes:active?.effectiveTimings||[]}))
|
|
5395
|
+
lanes:active?.effectiveTimings||[],resolvedTheme}))
|
|
4610
5396
|
: h("div",{className:"tl-panel-main"},
|
|
4611
5397
|
gate==="blocked" && h("div",{className:"tl-gate"},
|
|
4612
5398
|
h("div",{className:"tl-gate-col"},
|
|
@@ -4947,9 +5733,26 @@
|
|
|
4947
5733
|
// TESTING ONLY (not part of the tool): top-right toggle to hide the demo
|
|
4948
5734
|
// page content so the panel can be inspected against a blank page.
|
|
4949
5735
|
const[demoHidden,setDemoHidden]=useState(false);
|
|
5736
|
+
// Demo-page background toggle. Flips ONLY the demo page bg (#EEEEEE <-> #121212).
|
|
5737
|
+
// Deliberately separate from the panel's own theme (data-tl-theme) — it sets
|
|
5738
|
+
// data-demo-bg on <body> only and never touches the panel.
|
|
5739
|
+
const[darkBg,setDarkBg]=useState(false);
|
|
5740
|
+
useEffect(()=>{
|
|
5741
|
+
try{
|
|
5742
|
+
if(darkBg)document.body.setAttribute("data-demo-bg","dark");
|
|
5743
|
+
else document.body.removeAttribute("data-demo-bg");
|
|
5744
|
+
}catch(e){}
|
|
5745
|
+
},[darkBg]);
|
|
4950
5746
|
const active=PROTOS.find(p=>p.key===proto)||PROTOS[0];
|
|
4951
5747
|
return h(TimelineCtx.Provider,{value:ctx},
|
|
4952
5748
|
showControls&&h(PanelControls),
|
|
5749
|
+
h("button",{type:"button",className:"demo-bg-toggle",
|
|
5750
|
+
onClick:()=>setDarkBg(v=>!v),
|
|
5751
|
+
"aria-label":"Toggle demo page background",
|
|
5752
|
+
"aria-pressed":darkBg,
|
|
5753
|
+
title:"Demo page background (independent of the panel theme)"},
|
|
5754
|
+
h("span",{className:"demo-bg-dot","aria-hidden":"true"}),
|
|
5755
|
+
"Dark bg"),
|
|
4953
5756
|
h("button",{type:"button",onClick:()=>setDemoHidden(v=>!v),
|
|
4954
5757
|
title:"Testing only: hide/show demo page content",
|
|
4955
5758
|
style:{position:"fixed",top:"12px",right:"12px",zIndex:2147483000,
|