flawed-avatar 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/assets/animations/idle/Breathing Idle.fbx +0 -0
  2. package/assets/animations/idle/look away gesture.fbx +0 -0
  3. package/assets/animations/idle/weight shift.fbx +0 -0
  4. package/assets/animations/speaking/Agreeing.fbx +0 -0
  5. package/assets/animations/speaking/Talking (1).fbx +0 -0
  6. package/assets/animations/speaking/Talking (2).fbx +0 -0
  7. package/assets/animations/speaking/Talking (3).fbx +0 -0
  8. package/assets/animations/speaking/Talking.fbx +0 -0
  9. package/assets/animations/speaking/head nod yes.fbx +0 -0
  10. package/assets/animations/thinking/Thinking.fbx +0 -0
  11. package/assets/animations/thinking/thoughtful head shake.fbx +0 -0
  12. package/assets/animations/working/acknowledging.fbx +0 -0
  13. package/assets/animations/working/lengthy head nod.fbx +0 -0
  14. package/assets/icon.png +0 -0
  15. package/assets/models/CaptainLobster.vrm +0 -0
  16. package/assets/models/default-avatar.vrm +0 -0
  17. package/dist/chat-preload.cjs +87 -0
  18. package/dist/chat-renderer-bundle/chat-index.html +16 -0
  19. package/dist/chat-renderer-bundle/chat-renderer.js +355 -0
  20. package/dist/chat-renderer-bundle/styles/base.css +106 -0
  21. package/dist/chat-renderer-bundle/styles/chat.css +516 -0
  22. package/dist/chat-renderer-bundle/styles/components/button.css +221 -0
  23. package/dist/chat-renderer-bundle/styles/components/indicator.css +216 -0
  24. package/dist/chat-renderer-bundle/styles/components/input.css +139 -0
  25. package/dist/chat-renderer-bundle/styles/components/toast.css +204 -0
  26. package/dist/chat-renderer-bundle/styles/controls.css +279 -0
  27. package/dist/chat-renderer-bundle/styles/settings.css +310 -0
  28. package/dist/chat-renderer-bundle/styles/tokens.css +220 -0
  29. package/dist/chat-renderer-bundle/styles/utilities.css +349 -0
  30. package/dist/main/main/display-utils.d.ts +12 -0
  31. package/dist/main/main/display-utils.js +29 -0
  32. package/dist/main/main/gateway-client.d.ts +13 -0
  33. package/dist/main/main/gateway-client.js +265 -0
  34. package/dist/main/main/main.d.ts +1 -0
  35. package/dist/main/main/main.js +157 -0
  36. package/dist/main/main/persistence/chat-store.d.ts +8 -0
  37. package/dist/main/main/persistence/chat-store.js +110 -0
  38. package/dist/main/main/persistence/file-store.d.ts +17 -0
  39. package/dist/main/main/persistence/file-store.js +183 -0
  40. package/dist/main/main/persistence/index.d.ts +8 -0
  41. package/dist/main/main/persistence/index.js +8 -0
  42. package/dist/main/main/persistence/migrations.d.ts +23 -0
  43. package/dist/main/main/persistence/migrations.js +191 -0
  44. package/dist/main/main/persistence/settings-store.d.ts +32 -0
  45. package/dist/main/main/persistence/settings-store.js +174 -0
  46. package/dist/main/main/persistence/types.d.ts +72 -0
  47. package/dist/main/main/persistence/types.js +69 -0
  48. package/dist/main/main/settings-broadcast.d.ts +3 -0
  49. package/dist/main/main/settings-broadcast.js +9 -0
  50. package/dist/main/main/stdin-listener.d.ts +15 -0
  51. package/dist/main/main/stdin-listener.js +27 -0
  52. package/dist/main/main/tray.d.ts +3 -0
  53. package/dist/main/main/tray.js +59 -0
  54. package/dist/main/main/window-manager.d.ts +23 -0
  55. package/dist/main/main/window-manager.js +232 -0
  56. package/dist/main/main/window.d.ts +3 -0
  57. package/dist/main/main/window.js +528 -0
  58. package/dist/main/shared/config.d.ts +91 -0
  59. package/dist/main/shared/config.js +111 -0
  60. package/dist/main/shared/ipc-channels.d.ts +54 -0
  61. package/dist/main/shared/ipc-channels.js +68 -0
  62. package/dist/main/shared/types.d.ts +6 -0
  63. package/dist/main/shared/types.js +1 -0
  64. package/dist/preload.cjs +256 -0
  65. package/dist/renderer-bundle/index.html +63 -0
  66. package/dist/renderer-bundle/renderer.js +100734 -0
  67. package/dist/renderer-bundle/styles/base.css +106 -0
  68. package/dist/renderer-bundle/styles/chat.css +516 -0
  69. package/dist/renderer-bundle/styles/components/button.css +221 -0
  70. package/dist/renderer-bundle/styles/components/indicator.css +216 -0
  71. package/dist/renderer-bundle/styles/components/input.css +139 -0
  72. package/dist/renderer-bundle/styles/components/toast.css +204 -0
  73. package/dist/renderer-bundle/styles/controls.css +279 -0
  74. package/dist/renderer-bundle/styles/settings.css +310 -0
  75. package/dist/renderer-bundle/styles/tokens.css +220 -0
  76. package/dist/renderer-bundle/styles/utilities.css +349 -0
  77. package/index.ts +32 -0
  78. package/openclaw.plugin.json +22 -0
  79. package/package.json +45 -0
  80. package/src/electron-launcher.ts +63 -0
  81. package/src/main/chat-preload.cjs +87 -0
  82. package/src/main/display-utils.ts +39 -0
  83. package/src/main/gateway-client.ts +312 -0
  84. package/src/main/main.ts +169 -0
  85. package/src/main/persistence/chat-store.ts +143 -0
  86. package/src/main/persistence/file-store.ts +221 -0
  87. package/src/main/persistence/index.ts +69 -0
  88. package/src/main/persistence/migrations.ts +232 -0
  89. package/src/main/persistence/settings-store.ts +219 -0
  90. package/src/main/persistence/types.ts +107 -0
  91. package/src/main/preload.cjs +256 -0
  92. package/src/main/settings-broadcast.ts +13 -0
  93. package/src/main/settings-preload.cjs +153 -0
  94. package/src/main/stdin-listener.ts +34 -0
  95. package/src/main/tray.ts +65 -0
  96. package/src/main/window-manager.ts +298 -0
  97. package/src/main/window.ts +614 -0
  98. package/src/renderer/audio/audio-player.ts +161 -0
  99. package/src/renderer/audio/frequency-analyzer.ts +104 -0
  100. package/src/renderer/audio/index.ts +36 -0
  101. package/src/renderer/audio/kokoro-model-loader.ts +128 -0
  102. package/src/renderer/audio/kokoro-tts-service.ts +370 -0
  103. package/src/renderer/audio/lip-sync-profile.json +1 -0
  104. package/src/renderer/audio/phoneme-mapper.ts +120 -0
  105. package/src/renderer/audio/tts-controller.ts +344 -0
  106. package/src/renderer/audio/tts-service-factory.ts +75 -0
  107. package/src/renderer/audio/tts-service.ts +16 -0
  108. package/src/renderer/audio/types.ts +120 -0
  109. package/src/renderer/audio/web-speech-tts.ts +177 -0
  110. package/src/renderer/audio/wlipsync-analyzer.ts +145 -0
  111. package/src/renderer/avatar/animation-loader.ts +114 -0
  112. package/src/renderer/avatar/animator.ts +322 -0
  113. package/src/renderer/avatar/expressions.ts +165 -0
  114. package/src/renderer/avatar/eye-gaze.ts +255 -0
  115. package/src/renderer/avatar/eye-saccades.ts +133 -0
  116. package/src/renderer/avatar/hover-awareness.ts +125 -0
  117. package/src/renderer/avatar/ibl-enhancer.ts +163 -0
  118. package/src/renderer/avatar/lip-sync.ts +258 -0
  119. package/src/renderer/avatar/mixamo-retarget.ts +169 -0
  120. package/src/renderer/avatar/pixel-transparency.ts +65 -0
  121. package/src/renderer/avatar/scene.ts +70 -0
  122. package/src/renderer/avatar/spring-bones.ts +27 -0
  123. package/src/renderer/avatar/state-machine.ts +117 -0
  124. package/src/renderer/avatar/vrm-loader.ts +71 -0
  125. package/src/renderer/chat-window/chat-index.html +16 -0
  126. package/src/renderer/chat-window/chat-renderer.ts +28 -0
  127. package/src/renderer/index.html +63 -0
  128. package/src/renderer/renderer.ts +329 -0
  129. package/src/renderer/settings-window/settings-controls.ts +223 -0
  130. package/src/renderer/settings-window/settings-index.html +16 -0
  131. package/src/renderer/settings-window/settings-panel.ts +346 -0
  132. package/src/renderer/settings-window/settings-renderer.ts +5 -0
  133. package/src/renderer/styles/base.css +106 -0
  134. package/src/renderer/styles/chat.css +516 -0
  135. package/src/renderer/styles/components/button.css +221 -0
  136. package/src/renderer/styles/components/indicator.css +216 -0
  137. package/src/renderer/styles/components/input.css +139 -0
  138. package/src/renderer/styles/components/toast.css +204 -0
  139. package/src/renderer/styles/controls.css +279 -0
  140. package/src/renderer/styles/settings.css +310 -0
  141. package/src/renderer/styles/tokens.css +220 -0
  142. package/src/renderer/styles/utilities.css +349 -0
  143. package/src/renderer/types/avatar-bridge.d.ts +86 -0
  144. package/src/renderer/types/chat-bridge.d.ts +37 -0
  145. package/src/renderer/types/settings-bridge.d.ts +54 -0
  146. package/src/renderer/ui/chat-bubble.ts +435 -0
  147. package/src/renderer/ui/icons.ts +47 -0
  148. package/src/renderer/ui/typing-indicator.ts +41 -0
  149. package/src/service.ts +163 -0
  150. package/src/shared/config.ts +135 -0
  151. package/src/shared/ipc-channels.ts +81 -0
  152. package/src/shared/types.ts +7 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Indicator Component
3
+ * Loading states, typing indicators, and skeletons
4
+ */
5
+
6
+ /* === Typing Indicator === */
7
+ .typing-indicator {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: var(--space-1);
11
+ padding: var(--space-3) var(--space-4);
12
+ background: var(--surface-message-assistant);
13
+ border-radius: var(--radius-lg);
14
+ margin-bottom: var(--space-2);
15
+ animation: indicator-appear var(--duration-normal) var(--ease-out-expo);
16
+ }
17
+
18
+ @keyframes indicator-appear {
19
+ from {
20
+ opacity: 0;
21
+ transform: scale(0.9);
22
+ }
23
+ to {
24
+ opacity: 1;
25
+ transform: scale(1);
26
+ }
27
+ }
28
+
29
+ /* Typing dots */
30
+ .typing-dot {
31
+ width: 6px;
32
+ height: 6px;
33
+ border-radius: 50%;
34
+ background: var(--accent-blue);
35
+ animation: typing-bounce 1.4s infinite ease-in-out;
36
+ }
37
+
38
+ .typing-dot:nth-child(1) {
39
+ animation-delay: 0ms;
40
+ }
41
+
42
+ .typing-dot:nth-child(2) {
43
+ animation-delay: 160ms;
44
+ }
45
+
46
+ .typing-dot:nth-child(3) {
47
+ animation-delay: 320ms;
48
+ }
49
+
50
+ @keyframes typing-bounce {
51
+ 0%,
52
+ 60%,
53
+ 100% {
54
+ transform: translateY(0) scale(1);
55
+ opacity: 0.5;
56
+ }
57
+ 30% {
58
+ transform: translateY(-6px) scale(1.1);
59
+ opacity: 1;
60
+ }
61
+ }
62
+
63
+ /* Working phase - amber pulsing */
64
+ .typing-indicator[data-phase="working"] .typing-dot {
65
+ background: var(--accent-amber);
66
+ animation: working-pulse 1.8s infinite ease-in-out;
67
+ }
68
+
69
+ .typing-indicator[data-phase="working"] .typing-dot:nth-child(1) {
70
+ animation-delay: 0ms;
71
+ }
72
+
73
+ .typing-indicator[data-phase="working"] .typing-dot:nth-child(2) {
74
+ animation-delay: 200ms;
75
+ }
76
+
77
+ .typing-indicator[data-phase="working"] .typing-dot:nth-child(3) {
78
+ animation-delay: 400ms;
79
+ }
80
+
81
+ @keyframes working-pulse {
82
+ 0%,
83
+ 100% {
84
+ transform: scale(0.8);
85
+ opacity: 0.4;
86
+ }
87
+ 50% {
88
+ transform: scale(1.2);
89
+ opacity: 1;
90
+ }
91
+ }
92
+
93
+ /* === Skeleton Loader === */
94
+ .skeleton {
95
+ background: linear-gradient(
96
+ 90deg,
97
+ var(--surface-elevated) 25%,
98
+ var(--surface-hover) 50%,
99
+ var(--surface-elevated) 75%
100
+ );
101
+ background-size: 200% 100%;
102
+ animation: skeleton-shimmer 1.5s infinite;
103
+ border-radius: var(--radius-md);
104
+ }
105
+
106
+ @keyframes skeleton-shimmer {
107
+ 0% {
108
+ background-position: 200% 0;
109
+ }
110
+ 100% {
111
+ background-position: -200% 0;
112
+ }
113
+ }
114
+
115
+ /* Message skeleton */
116
+ .message-skeleton {
117
+ height: 48px;
118
+ margin-bottom: var(--space-2);
119
+ }
120
+
121
+ .message-skeleton:nth-child(odd) {
122
+ width: 70%;
123
+ }
124
+
125
+ .message-skeleton:nth-child(even) {
126
+ width: 55%;
127
+ margin-inline-start: auto;
128
+ }
129
+
130
+ /* Text skeleton */
131
+ .skeleton--text {
132
+ height: 1em;
133
+ width: 100%;
134
+ }
135
+
136
+ .skeleton--text-short {
137
+ width: 60%;
138
+ }
139
+
140
+ .skeleton--text-medium {
141
+ width: 80%;
142
+ }
143
+
144
+ /* Circle skeleton (avatar) */
145
+ .skeleton--circle {
146
+ border-radius: 50%;
147
+ }
148
+
149
+ /* === Spinner === */
150
+ .spinner {
151
+ width: 20px;
152
+ height: 20px;
153
+ border: 2px solid var(--surface-hover);
154
+ border-top-color: var(--accent-blue);
155
+ border-radius: 50%;
156
+ animation: spinner-rotate 0.8s linear infinite;
157
+ }
158
+
159
+ @keyframes spinner-rotate {
160
+ to {
161
+ transform: rotate(360deg);
162
+ }
163
+ }
164
+
165
+ .spinner--sm {
166
+ width: 14px;
167
+ height: 14px;
168
+ border-width: 2px;
169
+ }
170
+
171
+ .spinner--lg {
172
+ width: 32px;
173
+ height: 32px;
174
+ border-width: 3px;
175
+ }
176
+
177
+ /* === Progress Bar === */
178
+ .progress {
179
+ height: 4px;
180
+ background: var(--surface-sunken);
181
+ border-radius: var(--radius-full);
182
+ overflow: hidden;
183
+ }
184
+
185
+ .progress__bar {
186
+ height: 100%;
187
+ background: var(--accent-blue);
188
+ border-radius: var(--radius-full);
189
+ transition: width var(--duration-normal) var(--ease-out-expo);
190
+ }
191
+
192
+ .progress--indeterminate .progress__bar {
193
+ width: 30%;
194
+ animation: progress-indeterminate 1.5s infinite ease-in-out;
195
+ }
196
+
197
+ @keyframes progress-indeterminate {
198
+ 0% {
199
+ transform: translateX(-100%);
200
+ }
201
+ 100% {
202
+ transform: translateX(400%);
203
+ }
204
+ }
205
+
206
+ /* === Loading overlay === */
207
+ .loading-overlay {
208
+ position: absolute;
209
+ inset: 0;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ background: rgba(0, 0, 0, 0.5);
214
+ backdrop-filter: blur(4px);
215
+ z-index: var(--z-overlay);
216
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Input Component
3
+ * Reusable input styles with states
4
+ */
5
+
6
+ /* === Base Input === */
7
+ .input {
8
+ background: var(--surface-input);
9
+ border: 1px solid var(--glass-border);
10
+ border-radius: var(--radius-full);
11
+ padding: var(--space-2) var(--space-4);
12
+ color: var(--text-primary);
13
+ font-family: var(--font-sans);
14
+ font-size: var(--font-size-md);
15
+ transition:
16
+ border-color var(--duration-fast),
17
+ box-shadow var(--duration-fast),
18
+ background var(--duration-fast);
19
+ }
20
+
21
+ .input::placeholder {
22
+ color: var(--text-secondary);
23
+ opacity: 0.6;
24
+ }
25
+
26
+ /* Focus */
27
+ .input:focus {
28
+ outline: none;
29
+ }
30
+
31
+ .input:focus-visible {
32
+ background: var(--surface-focus);
33
+ border-color: var(--accent-blue);
34
+ box-shadow:
35
+ 0 0 0 3px var(--surface-focus),
36
+ inset 0 0 0 1px rgba(100, 180, 255, 0.3);
37
+ }
38
+
39
+ /* Hover */
40
+ .input:hover:not(:focus):not(:disabled) {
41
+ background: var(--surface-hover);
42
+ border-color: var(--glass-border-hover);
43
+ }
44
+
45
+ /* Disabled */
46
+ .input:disabled {
47
+ opacity: 0.4;
48
+ cursor: not-allowed;
49
+ }
50
+
51
+ /* === Error State === */
52
+ .input--error,
53
+ .input.is-error {
54
+ border-color: var(--color-error);
55
+ box-shadow: 0 0 0 2px var(--color-error-soft);
56
+ }
57
+
58
+ .input--error:focus-visible,
59
+ .input.is-error:focus-visible {
60
+ border-color: var(--color-error);
61
+ box-shadow:
62
+ 0 0 0 3px var(--color-error-soft),
63
+ inset 0 0 0 1px var(--color-error);
64
+ }
65
+
66
+ /* === Success State === */
67
+ .input--success,
68
+ .input.is-success {
69
+ border-color: var(--color-success);
70
+ box-shadow: 0 0 0 2px var(--color-success-soft);
71
+ }
72
+
73
+ /* === Size Variants === */
74
+ .input--sm {
75
+ padding: var(--space-1) var(--space-3);
76
+ font-size: var(--font-size-sm);
77
+ }
78
+
79
+ .input--lg {
80
+ padding: var(--space-3) var(--space-5);
81
+ font-size: var(--font-size-lg);
82
+ }
83
+
84
+ /* === Textarea variant === */
85
+ textarea.input {
86
+ border-radius: var(--radius-lg);
87
+ resize: vertical;
88
+ min-height: 80px;
89
+ line-height: var(--line-height-normal);
90
+ }
91
+
92
+ /* === Input with icon === */
93
+ .input-wrapper {
94
+ position: relative;
95
+ display: flex;
96
+ align-items: center;
97
+ }
98
+
99
+ .input-wrapper .input {
100
+ width: 100%;
101
+ }
102
+
103
+ .input-wrapper--icon-start .input {
104
+ padding-inline-start: calc(var(--space-4) + 20px);
105
+ }
106
+
107
+ .input-wrapper--icon-end .input {
108
+ padding-inline-end: calc(var(--space-4) + 20px);
109
+ }
110
+
111
+ .input-icon {
112
+ position: absolute;
113
+ width: 16px;
114
+ height: 16px;
115
+ color: var(--text-secondary);
116
+ pointer-events: none;
117
+ }
118
+
119
+ .input-icon--start {
120
+ inset-inline-start: var(--space-3);
121
+ }
122
+
123
+ .input-icon--end {
124
+ inset-inline-end: var(--space-3);
125
+ }
126
+
127
+ /* === Responsive === */
128
+ @media (max-width: 480px) {
129
+ .input {
130
+ font-size: 16px; /* Prevent iOS zoom */
131
+ }
132
+ }
133
+
134
+ /* === Forced colors mode === */
135
+ @media (forced-colors: active) {
136
+ .input {
137
+ border: 2px solid currentColor;
138
+ }
139
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Toast Component
3
+ * Notification messages that appear temporarily
4
+ */
5
+
6
+ /* === Toast Container === */
7
+ .toast-container {
8
+ position: fixed;
9
+ bottom: var(--space-4);
10
+ left: 50%;
11
+ transform: translateX(-50%);
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: var(--space-2);
15
+ z-index: var(--z-toast);
16
+ pointer-events: none;
17
+ }
18
+
19
+ /* === Base Toast === */
20
+ .toast {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: var(--space-2);
24
+ padding: var(--space-3) var(--space-4);
25
+ background: var(--glass-bg);
26
+ backdrop-filter: blur(var(--glass-blur));
27
+ -webkit-backdrop-filter: blur(var(--glass-blur));
28
+ border: 1px solid var(--glass-border);
29
+ border-radius: var(--radius-lg);
30
+ box-shadow: var(--shadow-lg);
31
+ color: var(--text-primary);
32
+ font-size: var(--font-size-sm);
33
+ pointer-events: auto;
34
+ transform: translateY(100%);
35
+ opacity: 0;
36
+ transition:
37
+ transform var(--duration-slower) var(--ease-out-back),
38
+ opacity var(--duration-normal);
39
+ }
40
+
41
+ /* Visible state */
42
+ .toast.is-visible {
43
+ transform: translateY(0);
44
+ opacity: 1;
45
+ }
46
+
47
+ /* Exiting state */
48
+ .toast.is-exiting {
49
+ transform: translateY(100%);
50
+ opacity: 0;
51
+ transition:
52
+ transform var(--duration-normal) var(--ease-out-expo),
53
+ opacity var(--duration-fast);
54
+ }
55
+
56
+ /* === Toast Variants === */
57
+ .toast--error {
58
+ border-color: var(--color-error);
59
+ background: linear-gradient(
60
+ 135deg,
61
+ rgba(255, 107, 107, 0.1) 0%,
62
+ var(--glass-bg) 100%
63
+ );
64
+ }
65
+
66
+ .toast--error .toast__icon {
67
+ color: var(--color-error);
68
+ }
69
+
70
+ .toast--success {
71
+ border-color: var(--color-success);
72
+ background: linear-gradient(
73
+ 135deg,
74
+ rgba(107, 255, 140, 0.1) 0%,
75
+ var(--glass-bg) 100%
76
+ );
77
+ }
78
+
79
+ .toast--success .toast__icon {
80
+ color: var(--color-success);
81
+ }
82
+
83
+ .toast--warning {
84
+ border-color: var(--color-warning);
85
+ background: linear-gradient(
86
+ 135deg,
87
+ rgba(255, 180, 100, 0.1) 0%,
88
+ var(--glass-bg) 100%
89
+ );
90
+ }
91
+
92
+ .toast--warning .toast__icon {
93
+ color: var(--color-warning);
94
+ }
95
+
96
+ .toast--info {
97
+ border-color: var(--accent-blue);
98
+ background: linear-gradient(
99
+ 135deg,
100
+ rgba(100, 180, 255, 0.1) 0%,
101
+ var(--glass-bg) 100%
102
+ );
103
+ }
104
+
105
+ .toast--info .toast__icon {
106
+ color: var(--accent-blue);
107
+ }
108
+
109
+ /* === Toast Elements === */
110
+ .toast__icon {
111
+ width: 16px;
112
+ height: 16px;
113
+ flex-shrink: 0;
114
+ }
115
+
116
+ .toast__message {
117
+ flex: 1;
118
+ line-height: var(--line-height-tight);
119
+ }
120
+
121
+ .toast__close {
122
+ width: 20px;
123
+ height: 20px;
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ flex-shrink: 0;
128
+ border-radius: var(--radius-sm);
129
+ color: var(--text-secondary);
130
+ cursor: pointer;
131
+ transition:
132
+ background var(--duration-fast),
133
+ color var(--duration-fast);
134
+ }
135
+
136
+ .toast__close:hover {
137
+ background: var(--surface-hover);
138
+ color: var(--text-primary);
139
+ }
140
+
141
+ .toast__close svg {
142
+ width: 12px;
143
+ height: 12px;
144
+ }
145
+
146
+ /* === Toast with action === */
147
+ .toast__action {
148
+ padding: var(--space-1) var(--space-2);
149
+ border-radius: var(--radius-sm);
150
+ background: var(--surface-button);
151
+ color: var(--accent-blue);
152
+ font-size: var(--font-size-xs);
153
+ font-weight: var(--font-weight-medium);
154
+ cursor: pointer;
155
+ transition:
156
+ background var(--duration-fast),
157
+ color var(--duration-fast);
158
+ }
159
+
160
+ .toast__action:hover {
161
+ background: var(--accent-blue-soft);
162
+ color: var(--text-primary);
163
+ }
164
+
165
+ /* === Progress bar for auto-dismiss === */
166
+ .toast__progress {
167
+ position: absolute;
168
+ bottom: 0;
169
+ left: 0;
170
+ right: 0;
171
+ height: 2px;
172
+ background: var(--surface-sunken);
173
+ border-radius: 0 0 var(--radius-lg) var(--radius-lg);
174
+ overflow: hidden;
175
+ }
176
+
177
+ .toast__progress-bar {
178
+ height: 100%;
179
+ background: var(--accent-blue);
180
+ transform-origin: left;
181
+ animation: toast-progress linear forwards;
182
+ }
183
+
184
+ @keyframes toast-progress {
185
+ from {
186
+ transform: scaleX(1);
187
+ }
188
+ to {
189
+ transform: scaleX(0);
190
+ }
191
+ }
192
+
193
+ /* Variant progress colors */
194
+ .toast--error .toast__progress-bar {
195
+ background: var(--color-error);
196
+ }
197
+
198
+ .toast--success .toast__progress-bar {
199
+ background: var(--color-success);
200
+ }
201
+
202
+ .toast--warning .toast__progress-bar {
203
+ background: var(--color-warning);
204
+ }