@celestia-island/hikari 0.55.194 → 0.55.196
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/package.json +1 -1
- package/src/components/HkAlert.scss +1 -1
- package/src/components/HkCheckbox.scss +2 -2
- package/src/components/HkDrawer.scss +1 -1
- package/src/components/HkEmptyState.scss +1 -1
- package/src/components/HkFab.scss +4 -4
- package/src/components/HkIconButtonGroup.scss +2 -2
- package/src/components/HkKeywordSearchModal.scss +2 -2
- package/src/components/HkLoadingVeil.scss +2 -2
- package/src/components/HkMarkdownRenderer.scss +1 -1
- package/src/components/HkModal.scss +4 -4
- package/src/components/HkModalBreadcrumb.scss +1 -1
- package/src/components/HkPasswordSurface.scss +1 -1
- package/src/components/HkRadio.scss +1 -1
- package/src/components/HkScrollContainer.scss +1 -1
- package/src/components/HkScrollPin.scss +1 -1
- package/src/components/HkSidebar.scss +1 -1
- package/src/components/HkStepFlow.scss +1 -1
- package/src/components/HkStepGuide.scss +1 -1
- package/src/components/HkSwitch.scss +1 -1
- package/src/components/HkTag.scss +1 -1
- package/src/components/HkTitleBar.scss +1 -1
- package/src/components/HkTransitionContainer.scss +13 -0
- package/src/components/HkTransitionContainer.test.tsx +127 -0
- package/src/components/HkTransitionContainer.tsx +154 -0
- package/src/components/HkTree.scss +1 -1
- package/src/components/HkUserPicker.scss +1 -1
- package/src/composables/useAnimationLoop.ts +95 -0
- package/src/index.ts +1 -0
- package/src/styles/admin-tokens.scss +10 -10
- package/src/styles/theme/channels.scss +8 -2
- package/src/tokens.scss +8 -2
package/package.json
CHANGED
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
transition: background var(--duration-fast, 0.15s) ease, color var(--duration-fast, 0.15s) ease;
|
|
117
117
|
|
|
118
118
|
&:hover {
|
|
119
|
-
background: var(--hi-color-surface, #
|
|
119
|
+
background: var(--hi-color-surface, #ffffff);
|
|
120
120
|
color: var(--hi-color-text-primary, #1e1e1e);
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* --hi-color-border is the faint divider channel (15% alpha) and melts
|
|
34
34
|
* into white/dark surfaces. */
|
|
35
35
|
border: 1px solid rgb(var(--color-border, 100 100 100));
|
|
36
|
-
background: var(--hi-color-surface, rgba(
|
|
36
|
+
background: var(--hi-color-surface, rgba(255,255,255,0.95));
|
|
37
37
|
transition:
|
|
38
38
|
border-color var(--duration-normal, 0.3s) ease,
|
|
39
39
|
background var(--duration-normal, 0.3s) ease,
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
:focus-visible keeps mouse toggles ring-free. */
|
|
96
96
|
.hk-checkbox-box:has(.hk-checkbox-input:focus-visible) {
|
|
97
97
|
box-shadow:
|
|
98
|
-
0 0 0 2px var(--hi-color-surface, rgba(
|
|
98
|
+
0 0 0 2px var(--hi-color-surface, rgba(255,255,255,0.95)),
|
|
99
99
|
0 0 0 4px var(--hi-color-primary, #7aa2f7);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
position: fixed;
|
|
27
27
|
display: flex;
|
|
28
28
|
flex-direction: column;
|
|
29
|
-
background: var(--hk-drawer-bg, var(--hi-color-surface, rgba(
|
|
29
|
+
background: var(--hk-drawer-bg, var(--hi-color-surface, rgba(255,255,255,0.95)));
|
|
30
30
|
// Token-gated (was a hard blur(12px)): the drawer resizes through
|
|
31
31
|
// animated width/height, and a backdrop-filter on a resizing fixed
|
|
32
32
|
// layer re-rasters the blurred region every frame — on phones that
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
panel — usually already provides a surface, and an always-on box turned
|
|
13
13
|
every in-card empty state into a nested placeholder layer. */
|
|
14
14
|
.hk-empty-state--boxed {
|
|
15
|
-
background: var(--hk-empty-bg, var(--hi-color-surface, rgba(
|
|
15
|
+
background: var(--hk-empty-bg, var(--hi-color-surface, rgba(255,255,255,0.95)));
|
|
16
16
|
backdrop-filter: blur(var(--blur-sm, 8px));
|
|
17
17
|
border: 1px solid var(--hi-color-border, rgba(100,100,100,0.06));
|
|
18
18
|
border-radius: var(--radius-md, 0.5rem);
|
|
@@ -85,12 +85,12 @@ $sizes: (sm: 34px, md: 42px, lg: 50px);
|
|
|
85
85
|
|
|
86
86
|
.hk-fab[data-variant="glass"] .hk-fab-button {
|
|
87
87
|
color: var(--hi-color-text-secondary, #666666);
|
|
88
|
-
background: color-mix(in srgb, var(--hi-color-surface, #
|
|
88
|
+
background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 78%, transparent);
|
|
89
89
|
border: 1px solid color-mix(in srgb, var(--hi-color-border, rgba(100,100,100,0.12)) 25%, transparent);
|
|
90
90
|
backdrop-filter: blur(var(--blur-xs, 4px)); // chip language shared with autotag pills
|
|
91
91
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
|
|
92
92
|
|
|
93
|
-
&:hover:not(:disabled) { background: color-mix(in srgb, var(--hi-color-surface, #
|
|
93
|
+
&:hover:not(:disabled) { background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 92%, transparent); }
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.hk-fab[data-variant="primary"] .hk-fab-button {
|
|
@@ -130,7 +130,7 @@ $sizes: (sm: 34px, md: 42px, lg: 50px);
|
|
|
130
130
|
letter-spacing: 0.02em;
|
|
131
131
|
white-space: nowrap;
|
|
132
132
|
color: var(--hi-color-text-secondary, #666666);
|
|
133
|
-
background: color-mix(in srgb, var(--hi-color-surface, #
|
|
133
|
+
background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 82%, transparent);
|
|
134
134
|
border: 1px solid color-mix(in srgb, var(--hi-color-border, rgba(100,100,100,0.12)) 25%, transparent);
|
|
135
135
|
border-radius: var(--radius-full, 9999px);
|
|
136
136
|
cursor: pointer;
|
|
@@ -149,7 +149,7 @@ $sizes: (sm: 34px, md: 42px, lg: 50px);
|
|
|
149
149
|
&:focus-visible { outline: 2px solid var(--hi-color-primary, #7aa2f7); outline-offset: 1px; }
|
|
150
150
|
&:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
151
151
|
// 82%→94% mirrors the main glass hover ladder (78%→92%).
|
|
152
|
-
&:hover:not(:disabled) { background: color-mix(in srgb, var(--hi-color-surface, #
|
|
152
|
+
&:hover:not(:disabled) { background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 94%, transparent); }
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// Staggered reveal while expanded (CSS only, via nth-child delay).
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
padding: var(--space-2, 0.125rem);
|
|
23
23
|
border: 1px solid var(--border-faint);
|
|
24
24
|
border-radius: var(--radius-md, 8px);
|
|
25
|
-
background: rgb(var(--color-surface,
|
|
25
|
+
background: rgb(var(--color-surface, 255 255 255) / 60%);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
.hk-icon-group-sm {
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
|
|
197
197
|
.hk-icon-group-item-marker-success {
|
|
198
198
|
background: rgb(var(--color-success, 14 184 64));
|
|
199
|
-
box-shadow: 0 0 0 0.125rem rgb(var(--color-surface,
|
|
199
|
+
box-shadow: 0 0 0 0.125rem rgb(var(--color-surface, 255 255 255));
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
/* Slider items already establish position:relative for the thumb stack —
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
align-items: center;
|
|
14
14
|
gap: var(--space-8, 0.5rem);
|
|
15
15
|
padding: var(--space-8, 0.5rem) var(--space-12, 0.75rem);
|
|
16
|
-
background: var(--hi-color-surface, rgba(
|
|
16
|
+
background: var(--hi-color-surface, rgba(255,255,255,0.95));
|
|
17
17
|
border: 1px solid var(--hk-kw-border, rgb(var(--color-border) / 8%));
|
|
18
18
|
border-radius: var(--radius-lg, 0.75rem);
|
|
19
19
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
transition: background 0.15s ease, border-color 0.15s ease;
|
|
85
85
|
|
|
86
86
|
&:hover {
|
|
87
|
-
background: var(--hi-color-surface, rgba(
|
|
87
|
+
background: var(--hi-color-surface, rgba(255,255,255,0.95));
|
|
88
88
|
border-color: rgb(var(--color-border) / 6%);
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
justify-content: center;
|
|
12
12
|
// Semi-transparent backdrop with a slight blur: the page beneath
|
|
13
13
|
// stays readable (orientation) but clearly inactive.
|
|
14
|
-
background: rgb(var(--color-background,
|
|
14
|
+
background: rgb(var(--color-background, 255 255 255) / 0.55);
|
|
15
15
|
backdrop-filter: blur(6px);
|
|
16
16
|
-webkit-backdrop-filter: blur(6px);
|
|
17
17
|
pointer-events: all;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
gap: 12px;
|
|
28
28
|
padding: 24px 32px;
|
|
29
29
|
border-radius: 12px;
|
|
30
|
-
background: rgb(var(--color-surface,
|
|
30
|
+
background: rgb(var(--color-surface, 255 255 255) / 0.85);
|
|
31
31
|
box-shadow: 0 8px 32px rgb(0 0 0 / 0.12);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
display: flex;
|
|
162
162
|
align-items: center;
|
|
163
163
|
justify-content: center;
|
|
164
|
-
background: color-mix(in srgb, var(--hi-color-surface, #
|
|
164
|
+
background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 70%, transparent);
|
|
165
165
|
backdrop-filter: blur(4px);
|
|
166
166
|
z-index: 10;
|
|
167
167
|
}
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
height var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1)),
|
|
80
80
|
max-height var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1)),
|
|
81
81
|
clip-path var(--duration-fast, 0.15s) var(--ease-standard, cubic-bezier(0.4, 0, 0.2, 1));
|
|
82
|
-
background: var(--hk-modal-bg, var(--hi-color-surface, rgba(
|
|
82
|
+
background: var(--hk-modal-bg, var(--hi-color-surface, rgba(255,255,255,0.95)));
|
|
83
83
|
backdrop-filter: var(--hk-modal-blur, none);
|
|
84
84
|
border: 1px solid var(--hk-modal-border-color, var(--hi-color-border, rgba(100,100,100,0.08)));
|
|
85
85
|
border-radius: var(--hk-modal-radius, var(--radius-lg, 12px));
|
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
// covers never surface as foreign bands over the padding.
|
|
324
324
|
background: var(
|
|
325
325
|
--hk-scroll-pin-cover-bg,
|
|
326
|
-
var(--hk-modal-bg, var(--hi-color-surface, rgba(
|
|
326
|
+
var(--hk-modal-bg, var(--hi-color-surface, rgba(255, 255, 255, 0.95)))
|
|
327
327
|
);
|
|
328
328
|
}
|
|
329
329
|
|
|
@@ -391,7 +391,7 @@
|
|
|
391
391
|
font-size: var(--text-2xs);
|
|
392
392
|
letter-spacing: 0.04em;
|
|
393
393
|
color: var(--hi-color-muted, #6c6c6c);
|
|
394
|
-
background: color-mix(in srgb, var(--hi-color-surface, rgba(
|
|
394
|
+
background: color-mix(in srgb, var(--hi-color-surface, rgba(255,255,255,0.95)) 78%, transparent);
|
|
395
395
|
border: 1px solid color-mix(in srgb, var(--hi-color-border, rgba(100,100,100,0.08)) 12%, transparent);
|
|
396
396
|
border-radius: var(--radius-full, 9999px);
|
|
397
397
|
backdrop-filter: blur(4px);
|
|
@@ -547,7 +547,7 @@
|
|
|
547
547
|
backdrop-filter: var(--hk-modal-blur-mobile, none);
|
|
548
548
|
background: var(
|
|
549
549
|
--hk-modal-bg-mobile,
|
|
550
|
-
var(--hk-modal-bg, var(--hi-color-surface, rgba(
|
|
550
|
+
var(--hk-modal-bg, var(--hi-color-surface, rgba(255, 255, 255, 0.95)))
|
|
551
551
|
);
|
|
552
552
|
border-radius: var(--hk-modal-radius, var(--radius-lg, 12px))
|
|
553
553
|
var(--hk-modal-radius, var(--radius-lg, 12px))
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// family don't collapse this strip (8px→4px gap, 32px→16px padding).
|
|
14
14
|
gap: var(--space-8, 0.5rem);
|
|
15
15
|
padding: var(--space-12, 0.75rem) var(--space-32, 2rem);
|
|
16
|
-
background: var(--hi-color-surface, rgba(
|
|
16
|
+
background: var(--hi-color-surface, rgba(255,255,255,0.95));
|
|
17
17
|
border: 1px solid var(--hk-breadcrumb-border, rgba(0, 0, 0, 0.06));
|
|
18
18
|
border-radius: var(--radius-md, 0.5rem);
|
|
19
19
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
|
@@ -342,6 +342,6 @@
|
|
|
342
342
|
.hk-pwd-box input:-webkit-autofill:active {
|
|
343
343
|
-webkit-text-fill-color: var(--hi-color-text-primary, #1e1e1e) !important;
|
|
344
344
|
caret-color: var(--hi-color-text-primary, #1e1e1e);
|
|
345
|
-
box-shadow: 0 0 0 1000px var(--hi-color-surface, #
|
|
345
|
+
box-shadow: 0 0 0 1000px var(--hi-color-surface, #ffffff) inset !important;
|
|
346
346
|
transition: background-color calc(infinity * 1s) step-end, color calc(infinity * 1s) step-end;
|
|
347
347
|
}
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
:focus-visible keeps mouse clicks ring-free. */
|
|
102
102
|
.hk-radio-box:has(.hk-radio-input:focus-visible) {
|
|
103
103
|
box-shadow:
|
|
104
|
-
0 0 0 2px var(--hi-color-surface, rgba(
|
|
104
|
+
0 0 0 2px var(--hi-color-surface, rgba(255,255,255,0.95)),
|
|
105
105
|
0 0 0 4px var(--hi-color-primary, #7aa2f7);
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
font-size: var(--text-2xs);
|
|
195
195
|
letter-spacing: 0.04em;
|
|
196
196
|
color: var(--hi-color-muted, #6c6c6c);
|
|
197
|
-
background: color-mix(in srgb, var(--hi-color-surface, rgba(
|
|
197
|
+
background: color-mix(in srgb, var(--hi-color-surface, rgba(255,255,255,0.95)) 78%, transparent);
|
|
198
198
|
border: 1px solid color-mix(in srgb, var(--hi-color-border, rgba(100,100,100,0.08)) 12%, transparent);
|
|
199
199
|
border-radius: var(--radius-full, 9999px);
|
|
200
200
|
backdrop-filter: blur(4px);
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// sticky header introduced; hosts retune through --hk-scroll-pin-bg).
|
|
24
24
|
background: var(
|
|
25
25
|
--hk-scroll-pin-bg,
|
|
26
|
-
color-mix(in srgb, var(--hi-color-surface, #
|
|
26
|
+
color-mix(in srgb, var(--hi-color-surface, #ffffff) 95%, transparent)
|
|
27
27
|
);
|
|
28
28
|
backdrop-filter: var(--hk-scroll-pin-blur, blur(6px));
|
|
29
29
|
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
display: flex;
|
|
25
25
|
flex-direction: column;
|
|
26
26
|
height: 100%;
|
|
27
|
-
background: var(--hk-sidebar-bg, var(--hi-color-surface, rgba(
|
|
27
|
+
background: var(--hk-sidebar-bg, var(--hi-color-surface, rgba(255,255,255,0.95)));
|
|
28
28
|
backdrop-filter: blur(var(--blur-sm, 8px));
|
|
29
29
|
/* Logical edge: the border rides the content-facing side in either
|
|
30
30
|
* direction (LTR sidebar docks left, RTL docks right). */
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
--hk-scroll-pin-z: var(--hk-stepflow-sticky-z, 10);
|
|
31
31
|
--hk-scroll-pin-bg: var(
|
|
32
32
|
--hk-stepflow-sticky-bg,
|
|
33
|
-
color-mix(in srgb, var(--hi-color-surface, #
|
|
33
|
+
color-mix(in srgb, var(--hi-color-surface, #ffffff) 95%, transparent)
|
|
34
34
|
);
|
|
35
35
|
// The HOST's declared gutter owns the resting offset, not this rule. The
|
|
36
36
|
// pin contract's offset rule is (0,3,0) and parks a top pin at
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
padding: var(--space-20, 1.25rem);
|
|
50
50
|
padding-block-start: calc(var(--space-20, 1.25rem) + var(--space-2, 0.125rem));
|
|
51
51
|
border-radius: var(--radius-lg, 1rem);
|
|
52
|
-
background: var(--hi-color-surface, #
|
|
52
|
+
background: var(--hi-color-surface, #ffffff);
|
|
53
53
|
|
|
54
54
|
&[data-done] {
|
|
55
55
|
box-shadow: inset 0 0 0 1px rgb(from var(--hi-color-success, #0eb840) r g b / 0.55);
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
the label; house pattern: HkButton double ring). */
|
|
30
30
|
.hk-switch-input:focus-visible ~ .hk-switch-track {
|
|
31
31
|
box-shadow:
|
|
32
|
-
0 0 0 2px var(--hi-color-surface, rgba(
|
|
32
|
+
0 0 0 2px var(--hi-color-surface, rgba(255, 255, 255, 0.95)),
|
|
33
33
|
0 0 0 4px var(--hi-color-primary, #7aa2f7);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
.hk-tag-default {
|
|
61
|
-
background: color-mix(in srgb, var(--hi-color-surface, #
|
|
61
|
+
background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 60%, transparent);
|
|
62
62
|
color: var(--hi-color-text-primary, #1e1e1e);
|
|
63
63
|
border-color: var(--hi-color-border, #646464);
|
|
64
64
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
.hk-titlebar {
|
|
9
9
|
--hk-tb-height: 32px;
|
|
10
|
-
--hk-tb-bg: color-mix(in srgb, rgb(var(--color-surface,
|
|
10
|
+
--hk-tb-bg: color-mix(in srgb, rgb(var(--color-surface, 255 255 255)) 80%, transparent);
|
|
11
11
|
--hk-tb-border: rgb(var(--color-border, 100 100 100) / 0.25);
|
|
12
12
|
--hk-tb-fg: rgb(var(--color-text-secondary, 102 102 102));
|
|
13
13
|
--hk-tb-fg-strong: rgb(var(--color-text, 30 30 30));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// HkTransitionContainer — the state-keyed morph container. The host
|
|
2
|
+
// carries the inline size (transitioned by the component); the stage
|
|
3
|
+
// is the measured content. Overflow hides DURING the morph only (the
|
|
4
|
+
// host is `overflow: clip` while a pinned size is active — applied via
|
|
5
|
+
// the inline width/height, the clip rides along) so shortened content
|
|
6
|
+
// never spills past the animating box.
|
|
7
|
+
.hk-tc {
|
|
8
|
+
overflow: clip;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.hk-tc-stage {
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createApp, defineComponent, h, ref, type App } from "vue";
|
|
3
|
+
|
|
4
|
+
import HkTransitionContainer from "./HkTransitionContainer";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_ANIMATION_DURATION_MS,
|
|
7
|
+
animationLoopArmedCount,
|
|
8
|
+
onAnimationFrame,
|
|
9
|
+
} from "../composables/useAnimationLoop";
|
|
10
|
+
|
|
11
|
+
/** Shared-container discipline (HkCrossfade.test precedent): every
|
|
12
|
+
* case appends its own container; afterEach tears them all down. */
|
|
13
|
+
const containers: HTMLElement[] = [];
|
|
14
|
+
|
|
15
|
+
interface Harness {
|
|
16
|
+
app: App;
|
|
17
|
+
container: HTMLElement;
|
|
18
|
+
key: { value: string };
|
|
19
|
+
swaps: Array<{ key: string | number; previous: string | number; durationMs: number }>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mountContainer(initial = "a"): Harness {
|
|
23
|
+
const container = document.createElement("div");
|
|
24
|
+
document.body.appendChild(container);
|
|
25
|
+
containers.push(container);
|
|
26
|
+
const key = ref(initial);
|
|
27
|
+
const swaps: Harness["swaps"] = [];
|
|
28
|
+
const Wrapper = defineComponent({
|
|
29
|
+
setup() {
|
|
30
|
+
return () => (
|
|
31
|
+
<HkTransitionContainer
|
|
32
|
+
swapKey={key.value}
|
|
33
|
+
onSwap={(e: { key: string | number; previous: string | number; durationMs: number }) => {
|
|
34
|
+
swaps.push(e);
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<p>{`panel-${key.value}`}</p>
|
|
38
|
+
</HkTransitionContainer>
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const app = createApp(Wrapper);
|
|
43
|
+
app.mount(container);
|
|
44
|
+
return { app, container, key, swaps };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
for (const c of containers.splice(0)) c.remove();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("HkTransitionContainer", () => {
|
|
52
|
+
it("renders the slot inside the morph host", () => {
|
|
53
|
+
const t = mountContainer("a");
|
|
54
|
+
const host = t.container.querySelector<HTMLElement>(".hk-tc");
|
|
55
|
+
const stage = t.container.querySelector<HTMLElement>(".hk-tc-stage");
|
|
56
|
+
expect(host).not.toBeNull();
|
|
57
|
+
expect(stage).not.toBeNull();
|
|
58
|
+
expect(t.container.textContent).toContain("panel-a");
|
|
59
|
+
t.app.unmount();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("defaults to the 300ms family duration on the host transition", () => {
|
|
63
|
+
const t = mountContainer("a");
|
|
64
|
+
const host = t.container.querySelector<HTMLElement>(".hk-tc");
|
|
65
|
+
expect(host?.style.transitionDuration).toBe(`${DEFAULT_ANIMATION_DURATION_MS}ms`);
|
|
66
|
+
expect(DEFAULT_ANIMATION_DURATION_MS).toBe(300);
|
|
67
|
+
t.app.unmount();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("axis=width transitions only the width property", () => {
|
|
71
|
+
const container = document.createElement("div");
|
|
72
|
+
document.body.appendChild(container);
|
|
73
|
+
containers.push(container);
|
|
74
|
+
const app = createApp({
|
|
75
|
+
render: () => (
|
|
76
|
+
<HkTransitionContainer swapKey="a" axis="width">
|
|
77
|
+
<p>x</p>
|
|
78
|
+
</HkTransitionContainer>
|
|
79
|
+
),
|
|
80
|
+
});
|
|
81
|
+
app.mount(container);
|
|
82
|
+
const host = container.querySelector<HTMLElement>(".hk-tc");
|
|
83
|
+
expect(host?.style.transitionProperty).toBe("width");
|
|
84
|
+
app.unmount();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("survives a swapKey change without ResizeObserver (degraded path, no NaN)", async () => {
|
|
88
|
+
const t = mountContainer("login");
|
|
89
|
+
t.key.value = "org";
|
|
90
|
+
await new Promise((r) => setTimeout(r, DEFAULT_ANIMATION_DURATION_MS + 120));
|
|
91
|
+
// jsdom has no ResizeObserver and zero offsets: no morph, no crash,
|
|
92
|
+
// and any pinned inline size is well-formed (never NaNpx).
|
|
93
|
+
const host = t.container.querySelector<HTMLElement>(".hk-tc");
|
|
94
|
+
expect(host?.style.width).not.toContain("NaN");
|
|
95
|
+
expect(host?.style.height).not.toContain("NaN");
|
|
96
|
+
t.app.unmount();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("useAnimationLoop (shared rAF context)", () => {
|
|
101
|
+
it("registers and unregisters; the loop idles at zero", () => {
|
|
102
|
+
const cb = vi.fn();
|
|
103
|
+
const off = onAnimationFrame(cb);
|
|
104
|
+
expect(animationLoopArmedCount()).toBe(1);
|
|
105
|
+
off();
|
|
106
|
+
expect(animationLoopArmedCount()).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("dedupes the same callback registration", () => {
|
|
110
|
+
const cb = vi.fn();
|
|
111
|
+
const off1 = onAnimationFrame(cb);
|
|
112
|
+
const off2 = onAnimationFrame(cb);
|
|
113
|
+
expect(animationLoopArmedCount()).toBe(1);
|
|
114
|
+
off1();
|
|
115
|
+
expect(animationLoopArmedCount()).toBe(0);
|
|
116
|
+
off2();
|
|
117
|
+
expect(animationLoopArmedCount()).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("unregister is idempotent", () => {
|
|
121
|
+
const cb = vi.fn();
|
|
122
|
+
const off = onAnimationFrame(cb);
|
|
123
|
+
off();
|
|
124
|
+
off();
|
|
125
|
+
expect(animationLoopArmedCount()).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { defineComponent, nextTick, onBeforeUnmount, ref, watch, type PropType } from "vue";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_ANIMATION_DURATION_MS } from "../composables/useAnimationLoop";
|
|
4
|
+
import HkCrossfade from "./HkCrossfade";
|
|
5
|
+
|
|
6
|
+
import "./HkTransitionContainer.scss";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HkTransitionContainer — the state-keyed morph container.
|
|
10
|
+
*
|
|
11
|
+
* HkCrossfade's height-safe dissolve, PLUS the size choreography the
|
|
12
|
+
* user direction (2026-10-22) asks of every swap surface: when the
|
|
13
|
+
* bound state key changes, the OLD content fades out, the container's
|
|
14
|
+
* width/height smoothly animate to the incoming content's natural
|
|
15
|
+
* size, and the NEW content fades in — one 300ms default, CSS-driven
|
|
16
|
+
* (transition on inline size), with the animation-loop registration
|
|
17
|
+
* kept for the choreography CSS cannot express.
|
|
18
|
+
*
|
|
19
|
+
* Binding: `swapKey` is the identity of the content on stage — the same
|
|
20
|
+
* contract HkCrossfade owns (same value ⇒ no swap). Tabs, segmented
|
|
21
|
+
* controls, editor mode switches, chat-node state labels all reduce to
|
|
22
|
+
* "a key changed; morph to the new panel".
|
|
23
|
+
*
|
|
24
|
+
* Scroll handoff (loose coupling, per the user's spec): the container
|
|
25
|
+
* never touches any scroller itself. Each swap emits a `swap` event
|
|
26
|
+
* carrying `{ key, previous, durationMs }`; the HOST forwards that to
|
|
27
|
+
* its scroll container's PUBLIC interface — the same scrollTo API used
|
|
28
|
+
* for back-to-top and jump-to-line — so the scrollbar adapts to the
|
|
29
|
+
* size change and the caret/anchor coordinate glides instead of
|
|
30
|
+
* teleporting. The scroll container needs zero knowledge of this
|
|
31
|
+
* component.
|
|
32
|
+
*
|
|
33
|
+
* Sizing: a ResizeObserver on the inner stage feeds the container's
|
|
34
|
+
* inline width/height; the CSS transition on those properties does the
|
|
35
|
+
* actual morph (off-thread, cancellable, respects the global animation
|
|
36
|
+
* switch via the duration override to 0s). When the observer is
|
|
37
|
+
* unavailable (older webview, SSR, jsdom) the container degrades to
|
|
38
|
+
* plain auto sizing — a dissolve without the morph, never a broken
|
|
39
|
+
* layout.
|
|
40
|
+
*/
|
|
41
|
+
export default defineComponent({
|
|
42
|
+
name: "HkTransitionContainer",
|
|
43
|
+
props: {
|
|
44
|
+
/** Identity of the content on stage; changing it starts the morph. */
|
|
45
|
+
swapKey: { type: [String, Number] as PropType<string | number>, required: true },
|
|
46
|
+
/** Which axes morph. `height` is the default (most swap surfaces);
|
|
47
|
+
* `width` for horizontal morphs (segmented panels); `both` for
|
|
48
|
+
* free-floating cards. */
|
|
49
|
+
axis: {
|
|
50
|
+
type: String as PropType<"height" | "width" | "both">,
|
|
51
|
+
default: "height",
|
|
52
|
+
},
|
|
53
|
+
/** Morph + dissolve duration, ms. Family default 300. */
|
|
54
|
+
duration: { type: Number, default: DEFAULT_ANIMATION_DURATION_MS },
|
|
55
|
+
tag: { type: String, default: "div" },
|
|
56
|
+
},
|
|
57
|
+
emits: {
|
|
58
|
+
/** Fired when a swap settles: the host forwards this to its scroll
|
|
59
|
+
* container's public scrollTo interface. */
|
|
60
|
+
swap: (payload: { key: string | number; previous: string | number; durationMs: number }) => true,
|
|
61
|
+
},
|
|
62
|
+
setup(props, { slots, emit }) {
|
|
63
|
+
const hostRef = ref<HTMLElement | null>(null);
|
|
64
|
+
const stageRef = ref<HTMLElement | null>(null);
|
|
65
|
+
// Pinned inline size: `{ w, h }` while morphing, `null` = auto.
|
|
66
|
+
const pinned = ref<{ w: number; h: number } | null>(null);
|
|
67
|
+
|
|
68
|
+
let observer: ResizeObserver | null = null;
|
|
69
|
+
let pendingKey: string | number | null = null;
|
|
70
|
+
let settleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
71
|
+
|
|
72
|
+
function readStage(): { w: number; h: number } {
|
|
73
|
+
const el = stageRef.value;
|
|
74
|
+
if (!el) return { w: 0, h: 0 };
|
|
75
|
+
return { w: el.offsetWidth, h: el.offsetHeight };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function applyPinned(): void {
|
|
79
|
+
const el = hostRef.value;
|
|
80
|
+
if (!el) return;
|
|
81
|
+
const p = pinned.value;
|
|
82
|
+
el.style.width = p && props.axis !== "height" ? `${p.w}px` : "";
|
|
83
|
+
el.style.height = p && props.axis !== "width" ? `${p.h}px` : "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function observe(): void {
|
|
87
|
+
if (typeof ResizeObserver === "undefined" || !stageRef.value) return;
|
|
88
|
+
observer?.disconnect();
|
|
89
|
+
observer = new ResizeObserver((entries) => {
|
|
90
|
+
const rect = entries[0]?.contentRect;
|
|
91
|
+
if (!rect || pendingKey === null) return;
|
|
92
|
+
// Stage settled at the incoming content's natural size: pin to
|
|
93
|
+
// it (from the outgoing size) → next frame the CSS transition
|
|
94
|
+
// morphs the container; after the duration, release to auto.
|
|
95
|
+
pinned.value = { w: Math.ceil(rect.width), h: Math.ceil(rect.height) };
|
|
96
|
+
applyPinned();
|
|
97
|
+
if (settleTimer !== null) clearTimeout(settleTimer);
|
|
98
|
+
settleTimer = setTimeout(() => {
|
|
99
|
+
pinned.value = null;
|
|
100
|
+
applyPinned();
|
|
101
|
+
const key = pendingKey;
|
|
102
|
+
pendingKey = null;
|
|
103
|
+
if (key !== null) {
|
|
104
|
+
emit("swap", { key, previous: previousKey, durationMs: props.duration });
|
|
105
|
+
}
|
|
106
|
+
}, props.duration + 40);
|
|
107
|
+
});
|
|
108
|
+
observer.observe(stageRef.value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let previousKey: string | number = props.swapKey;
|
|
112
|
+
|
|
113
|
+
watch(() => props.swapKey, (next) => {
|
|
114
|
+
// Pin the OUTGOING size first so the container never collapses
|
|
115
|
+
// through zero while the content swaps underneath.
|
|
116
|
+
pendingKey = next;
|
|
117
|
+
pinned.value = readStage();
|
|
118
|
+
applyPinned();
|
|
119
|
+
void nextTick(() => observe());
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
onBeforeUnmount(() => {
|
|
123
|
+
observer?.disconnect();
|
|
124
|
+
observer = null;
|
|
125
|
+
if (settleTimer !== null) clearTimeout(settleTimer);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
const Tag = props.tag as "div" | "section" | "main" | "aside";
|
|
130
|
+
const d = `${props.duration}ms`;
|
|
131
|
+
return (
|
|
132
|
+
<Tag
|
|
133
|
+
ref={hostRef}
|
|
134
|
+
class="hk-tc"
|
|
135
|
+
style={{
|
|
136
|
+
transitionProperty:
|
|
137
|
+
props.axis === "both" ? "width, height" : props.axis === "width" ? "width" : "height",
|
|
138
|
+
transitionDuration: d,
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
{/* The stage is HkCrossfade: it owns the opacity dissolve
|
|
142
|
+
(both generations stacked in one grid cell — no exit blank,
|
|
143
|
+
no collapse flash) and the swap-stall watchdog; THIS
|
|
144
|
+
component owns the measured size morph around it. */}
|
|
145
|
+
<div ref={stageRef} class="hk-tc-stage">
|
|
146
|
+
<HkCrossfade swapKey={props.swapKey}>
|
|
147
|
+
{slots.default?.()}
|
|
148
|
+
</HkCrossfade>
|
|
149
|
+
</div>
|
|
150
|
+
</Tag>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
});
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
cursor: pointer;
|
|
115
115
|
|
|
116
116
|
&:hover {
|
|
117
|
-
background: color-mix(in srgb, var(--hi-color-surface, #
|
|
117
|
+
background: color-mix(in srgb, var(--hi-color-surface, #ffffff) 40%, transparent);
|
|
118
118
|
color: var(--hi-color-text-primary, #1e1e1e);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
gap: var(--space-8, 0.5rem);
|
|
26
26
|
min-height: 2.5rem;
|
|
27
27
|
padding: var(--space-8, 0.5rem) var(--space-12, 0.75rem);
|
|
28
|
-
background: var(--hi-color-surface, rgba(
|
|
28
|
+
background: var(--hi-color-surface, rgba(255, 255, 255, 0.95));
|
|
29
29
|
border: 1px solid var(--hk-kw-border, rgb(var(--color-border) / 8%));
|
|
30
30
|
border-radius: var(--radius-lg, 0.75rem);
|
|
31
31
|
cursor: pointer;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAnimationLoop — the family's ONE shared requestAnimationFrame loop.
|
|
3
|
+
*
|
|
4
|
+
* Every JS-driven animation in the library registers a callback here
|
|
5
|
+
* instead of opening its own rAF chain: the context owns a single loop
|
|
6
|
+
* that runs ONLY while at least one callback is registered, and each
|
|
7
|
+
* frame dispatches `(now, deltaMs)` to every callback that was armed at
|
|
8
|
+
* frame start (a callback that registers/unregisters mid-frame takes
|
|
9
|
+
* effect next frame — the iteration walks a snapshot). CSS transitions
|
|
10
|
+
* remain the preferred vehicle (the browser composites them
|
|
11
|
+
* off-thread); this loop is for the choreography CSS cannot express —
|
|
12
|
+
* measured size morphs, staged state sequences, scroll handshakes
|
|
13
|
+
* (user direction 2026-10-22).
|
|
14
|
+
*
|
|
15
|
+
* The family-wide default duration for BOTH CSS and loop-driven
|
|
16
|
+
* animation is 300ms (`DEFAULT_ANIMATION_DURATION_MS`), matching the
|
|
17
|
+
* design system's `--duration-normal` token — one number, everywhere.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** The family-wide animation default: 300ms (== `--duration-normal`). */
|
|
21
|
+
export const DEFAULT_ANIMATION_DURATION_MS = 300;
|
|
22
|
+
|
|
23
|
+
/** First-frame delta stand-in: one 60Hz frame. */
|
|
24
|
+
const DEFAULT_FRAME_DELTA_MS = 1000 / 60;
|
|
25
|
+
|
|
26
|
+
/** A per-frame callback: absolute rAF timestamp + ms since last frame. */
|
|
27
|
+
export type AnimationFrameCallback = (nowMs: number, deltaMs: number) => void;
|
|
28
|
+
|
|
29
|
+
interface LoopState {
|
|
30
|
+
callbacks: Set<AnimationFrameCallback>;
|
|
31
|
+
handle: number | null;
|
|
32
|
+
last: number | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Module-level singleton: one loop per document, however many consumers.
|
|
36
|
+
const state: LoopState = { callbacks: new Set(), handle: null, last: null };
|
|
37
|
+
|
|
38
|
+
function engine(nowMs: number): void {
|
|
39
|
+
// Snapshot BEFORE mutating state: mid-frame (un)registrations land
|
|
40
|
+
// next frame, and a callback never runs twice inside one frame.
|
|
41
|
+
const armed = Array.from(state.callbacks);
|
|
42
|
+
const prev = state.last;
|
|
43
|
+
if (armed.length > 0) {
|
|
44
|
+
state.handle = requestAnimationFrame(engine);
|
|
45
|
+
state.last = nowMs;
|
|
46
|
+
} else {
|
|
47
|
+
state.handle = null;
|
|
48
|
+
state.last = null;
|
|
49
|
+
}
|
|
50
|
+
const delta = prev === null ? DEFAULT_FRAME_DELTA_MS : Math.max(0, nowMs - prev);
|
|
51
|
+
for (const cb of armed) cb(nowMs, delta);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register a per-frame callback. Returns the unregister function; call
|
|
56
|
+
* it when the animation settles (or the component unmounts). Registering
|
|
57
|
+
* the SAME function twice is idempotent (Set semantics). The loop is
|
|
58
|
+
* lazily started by the first registration and stops itself the moment
|
|
59
|
+
* the last callback unregisters — idle pages pay zero rAF churn.
|
|
60
|
+
*/
|
|
61
|
+
export function onAnimationFrame(cb: AnimationFrameCallback): () => void {
|
|
62
|
+
state.callbacks.add(cb);
|
|
63
|
+
if (state.handle === null) {
|
|
64
|
+
state.last = null;
|
|
65
|
+
state.handle = requestAnimationFrame(engine);
|
|
66
|
+
}
|
|
67
|
+
return () => {
|
|
68
|
+
state.callbacks.delete(cb);
|
|
69
|
+
if (state.callbacks.size === 0 && state.handle !== null) {
|
|
70
|
+
cancelAnimationFrame(state.handle);
|
|
71
|
+
state.handle = null;
|
|
72
|
+
state.last = null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Test/introspection hook: how many callbacks are armed right now. */
|
|
78
|
+
export function animationLoopArmedCount(): number {
|
|
79
|
+
return state.callbacks.size;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Component-facing wrapper: one registration bound to one callback
|
|
84
|
+
* (register on creation, `dispose()` when the owner unmounts). The
|
|
85
|
+
* callback receives `(nowMs, deltaMs)` every frame while armed.
|
|
86
|
+
*/
|
|
87
|
+
export function useAnimationLoop(cb: AnimationFrameCallback): { dispose: () => void } {
|
|
88
|
+
let off: (() => void) | null = onAnimationFrame(cb);
|
|
89
|
+
return {
|
|
90
|
+
dispose(): void {
|
|
91
|
+
off?.();
|
|
92
|
+
off = null;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -613,6 +613,7 @@ export { default as HkColorPicker } from "./components/HkColorPicker";
|
|
|
613
613
|
export { default as HkCopyBadge } from "./components/HkCopyBadge";
|
|
614
614
|
export { default as HkConfirmDialog } from "./components/HkConfirmDialog";
|
|
615
615
|
export { default as HkCrossfade } from "./components/HkCrossfade";
|
|
616
|
+
export { default as HkTransitionContainer } from "./components/HkTransitionContainer";
|
|
616
617
|
export { default as HkDivider } from "./components/HkDivider";
|
|
617
618
|
export { default as HkDiffViewer } from "./components/HkDiffViewer";
|
|
618
619
|
export { parseUnifiedDiff, computeGaps } from "./components/diffParse";
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
/* ── Hikari color bridge ─ */
|
|
24
24
|
--hi-color-primary: rgb(var(--color-primary, 122 162 247));
|
|
25
25
|
--hi-color-primary-rgb: var(--color-primary, 122 162 247);
|
|
26
|
-
--hi-color-surface: rgb(var(--color-surface,
|
|
26
|
+
--hi-color-surface: rgb(var(--color-surface, 255 255 255));
|
|
27
27
|
--hi-color-border: rgb(var(--color-border, 100 100 100) / 15%);
|
|
28
28
|
--hi-color-text-primary: rgb(var(--color-text, 30 30 30));
|
|
29
29
|
--hi-color-text-on-primary: rgb(var(--color-on-primary, 255 255 255)); /* deprecated in-tree: prefer the on-solid slots */
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
--hi-color-icon-on-solid: rgb(var(--color-on-solid-icon, 255 255 255));
|
|
35
35
|
--hi-color-text-secondary: color-mix(in srgb, rgb(var(--color-text, 30 30 30)) 60%, transparent);
|
|
36
36
|
--hi-color-muted: rgb(var(--color-muted, 108 108 108));
|
|
37
|
-
--hi-color-background: rgb(var(--color-background,
|
|
38
|
-
--hi-color-bg-canvas: rgb(var(--color-background,
|
|
39
|
-
--hi-color-bg-elevated: rgb(var(--color-surface,
|
|
40
|
-
--hi-color-bg-subtle: rgb(var(--color-surface,
|
|
41
|
-
--hi-secondary-bg: rgb(var(--color-surface,
|
|
37
|
+
--hi-color-background: rgb(var(--color-background, 255 255 255));
|
|
38
|
+
--hi-color-bg-canvas: rgb(var(--color-background, 255 255 255));
|
|
39
|
+
--hi-color-bg-elevated: rgb(var(--color-surface, 255 255 255));
|
|
40
|
+
--hi-color-bg-subtle: rgb(var(--color-surface, 255 255 255) / 0.5);
|
|
41
|
+
--hi-secondary-bg: rgb(var(--color-surface, 255 255 255) / 0.5);
|
|
42
42
|
--hi-color-danger: rgb(var(--color-error, 247 118 142));
|
|
43
43
|
--hi-color-success: rgb(var(--color-success, 14 184 64));
|
|
44
44
|
--hi-color-warning: rgb(var(--color-warning, 238 214 119));
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
* by load order. (--hk-surface's only in-tree consumer — the retired
|
|
59
59
|
* HTabs tail overlay — is gone; the token stays published for
|
|
60
60
|
* external plana-ui readers.) */
|
|
61
|
-
--hk-surface: var(--color-surface,
|
|
61
|
+
--hk-surface: var(--color-surface, 255 255 255);
|
|
62
62
|
--hk-primary: var(--color-primary, 122 162 247);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -948,7 +948,7 @@
|
|
|
948
948
|
depend on the host having loaded the reset sheet (without it a 1px
|
|
949
949
|
border pushes the box 2px past its max-width and off-centre). */
|
|
950
950
|
box-sizing: border-box;
|
|
951
|
-
background: color-mix(in srgb, rgb(var(--color-surface,
|
|
951
|
+
background: color-mix(in srgb, rgb(var(--color-surface, 255 255 255)) 70%, transparent);
|
|
952
952
|
border: 1px solid color-mix(in srgb, rgb(var(--color-text, 30 30 30)) 12%, transparent);
|
|
953
953
|
border-radius: var(--hi-radius-sm, 4px);
|
|
954
954
|
box-shadow: 0 2px 16px rgb(0 0 0 / 8%);
|
|
@@ -1207,11 +1207,11 @@
|
|
|
1207
1207
|
}
|
|
1208
1208
|
|
|
1209
1209
|
.a-bg-secondary {
|
|
1210
|
-
background: rgb(var(--color-background,
|
|
1210
|
+
background: rgb(var(--color-background, 255 255 255));
|
|
1211
1211
|
}
|
|
1212
1212
|
|
|
1213
1213
|
.a-bg-tertiary {
|
|
1214
|
-
background: rgb(var(--color-surface,
|
|
1214
|
+
background: rgb(var(--color-surface, 255 255 255));
|
|
1215
1215
|
}
|
|
1216
1216
|
|
|
1217
1217
|
.a-latency-text {
|
|
@@ -93,8 +93,14 @@
|
|
|
93
93
|
--color-muted: 108 108 108;
|
|
94
94
|
--color-border: 100 100 100;
|
|
95
95
|
--color-focused-border: 122 162 247;
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
/* Static seed floor (2026-10-22 user direction): pure white — the
|
|
97
|
+
* pale-cyan paper default retired. These seeds are the PRE-ENGINE
|
|
98
|
+
* paint (and the engine-failure floor); the runtime theme overrides
|
|
99
|
+
* them on :root once JS applies a preset (dark mode included — the
|
|
100
|
+
* engine owns scheme switching, the seeds are the light default). */
|
|
101
|
+
--color-background: 255 255 255;
|
|
102
|
+
--color-surface: 255 255 255;
|
|
103
|
+
|
|
98
104
|
/* Derived seed defaults for the runtime-selected channels: a primary
|
|
99
105
|
* wash over the background (selected) and the surface tint (status
|
|
100
106
|
* bar), matching what presets derive at runtime. */
|
package/src/tokens.scss
CHANGED
|
@@ -86,8 +86,14 @@
|
|
|
86
86
|
--color-muted: 108 108 108;
|
|
87
87
|
--color-border: 100 100 100;
|
|
88
88
|
--color-focused-border: 122 162 247;
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
/* Static seed floor (2026-10-22 user direction): pure white — the
|
|
90
|
+
* pale-cyan paper default retired. These seeds are the PRE-ENGINE
|
|
91
|
+
* paint (and the engine-failure floor); the runtime theme overrides
|
|
92
|
+
* them on :root once JS applies a preset (dark mode included — the
|
|
93
|
+
* engine owns scheme switching, the seeds are the light default). */
|
|
94
|
+
--color-background: 255 255 255;
|
|
95
|
+
--color-surface: 255 255 255;
|
|
96
|
+
|
|
91
97
|
/* Derived seed defaults for the runtime-selected channels: a primary
|
|
92
98
|
* wash over the background (selected) and the surface tint (status
|
|
93
99
|
* bar), matching what presets derive at runtime. */
|