@owear/cli 0.1.3 → 0.1.4

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.
@@ -2,23 +2,23 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  //
4
4
  // src/installer.ts — UI del instalador (vanilla). Corre en el renderer y usa
5
- // la API nativa `installer` vía `window.ow` (inyectado por el kernel).
5
+ // SOLO la API nativa `installer` (builtin, siempre presente) vía `window.ow`.
6
+ // No depende de módulos .owm (dialog, etc.).
6
7
  //
7
8
  // Personalízalo a gusto (React, Vue, Tailwind…): esto es sólo el default.
8
9
 
9
10
  import './style.css'
10
11
 
11
- declare global {
12
- interface Window {
13
- __owWindowId: number
14
- ow: { invoke<T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T> }
15
- }
12
+ interface OwBridge {
13
+ invoke<T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T>
16
14
  }
15
+ const ow = (window as unknown as { ow: OwBridge }).ow
16
+ const wid = (window as unknown as { __owWindowId: number }).__owWindowId
17
17
 
18
18
  const $ = <T extends HTMLElement = HTMLElement>(sel: string): T =>
19
19
  document.querySelector(sel) as T
20
20
  const invoke = <T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T> =>
21
- window.ow.invoke(module, fn, ...args) as Promise<T>
21
+ ow.invoke<T>(module, fn, ...args)
22
22
 
23
23
  interface PayloadEntry {
24
24
  rel: string
@@ -52,6 +52,13 @@ let mode: 'minimal' | 'layout' = 'minimal'
52
52
  let plan: PayloadEntry[] = []
53
53
  let busy = false
54
54
 
55
+ function fmtSize(bytes: number): string {
56
+ if (!bytes) return ''
57
+ if (bytes < 1024) return `${bytes} B`
58
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`
59
+ return `${(bytes / 1024 / 1024).toFixed(1)} MiB`
60
+ }
61
+
55
62
  function setStatus(msg: string, kind: 'ok' | 'err' | '' = ''): void {
56
63
  const el = $('#status')
57
64
  el.textContent = msg
@@ -59,9 +66,9 @@ function setStatus(msg: string, kind: 'ok' | 'err' | '' = ''): void {
59
66
  }
60
67
 
61
68
  async function boot(): Promise<void> {
62
- // controles de ventana (titlebar propia)
63
- const id = window.__owWindowId
64
- $('#win-close').addEventListener('click', () => void invoke('ow-window', 'close', id))
69
+ $('#win-min').addEventListener('click', () => void invoke('ow-window', 'minimize', wid))
70
+ $('#win-max').addEventListener('click', () => void invoke('ow-window', 'maximize', wid))
71
+ $('#win-close').addEventListener('click', () => void invoke('ow-window', 'close', wid))
65
72
 
66
73
  const info = await invoke<InstallerInfo>('installer', 'info').catch(() => ({}))
67
74
  const bridge = await invoke<Bridge | null>('installer', 'bridge').catch(() => null)
@@ -69,10 +76,15 @@ async function boot(): Promise<void> {
69
76
  mode = t.layout ?? 'minimal'
70
77
 
71
78
  const name = info.appName ?? bridge?.app?.name ?? 'la app'
79
+ const version = info.version ?? bridge?.app?.version ?? ''
72
80
  $('#summary').textContent =
73
- `${name} ${info.version ?? bridge?.app?.version ?? ''} — modo ${mode} · ${platform}`
81
+ `Se instalará ${name}${version ? ` ${version}` : ''} · modo ${mode} · ${platform}`
74
82
 
75
- const value = t.dir ?? (info.appName ? `~/opt/${info.appName.toLowerCase()}` : '')
83
+ // Destino: preferimos el del bridge si es ABSOLUTO; si no, el default real.
84
+ let value = t.dir ?? ''
85
+ if (!value.startsWith('/')) {
86
+ value = await invoke<string>('installer', 'defaultDir').catch(() => '')
87
+ }
76
88
  ;($('#dir') as HTMLInputElement).value = value
77
89
 
78
90
  await refreshPlan()
@@ -80,28 +92,39 @@ async function boot(): Promise<void> {
80
92
  }
81
93
 
82
94
  async function refreshPlan(): Promise<void> {
95
+ const bridge = await invoke<Bridge | null>('installer', 'bridge').catch(() => null)
83
96
  plan = await invoke<PayloadEntry[]>('installer', 'plan', {
84
97
  mode,
85
98
  layout: mode === 'minimal' ? 'flat' : 'tree',
86
- order: (await invoke<Bridge | null>('installer', 'bridge').catch(() => null))?.order,
99
+ order: bridge?.order,
87
100
  })
88
101
  const total = plan.reduce((n, e) => n + e.size, 0)
89
- $('#count').textContent = `${plan.length} entradas · ${(total / 1024).toFixed(1)} KiB`
102
+ $('#count').textContent = `${plan.length} · ${fmtSize(total)}`
90
103
  $('#plan').textContent = plan
91
104
  .slice(0, 300)
92
- .map((e) => `${e.dir ? '📁' : '📄'} ${e.dst}${e.size ? ` (${e.size} B)` : ''}`)
105
+ .map((e) => `${e.dst}${e.dir ? '/' : ''}${e.size ? ` ${fmtSize(e.size)}` : ''}`)
93
106
  .join('\n')
94
107
  }
95
108
 
96
109
  async function pickDir(): Promise<void> {
110
+ const current = ($('#dir') as HTMLInputElement).value.trim()
97
111
  try {
98
- const dir = await invoke<string | null>('dialog', 'open', 'openDirectory', 'Elegir carpeta')
112
+ // Selector NATIVO del builtin installer (no usa el módulo `dialog`).
113
+ const dir = await invoke<string | null>('installer', 'chooseDir', {
114
+ title: 'Elegir carpeta de instalación',
115
+ defaultPath: current,
116
+ })
99
117
  if (dir) ($('#dir') as HTMLInputElement).value = dir
100
118
  } catch {
101
- /* cancelado o sin picker */
119
+ /* cancelado */
102
120
  }
103
121
  }
104
122
 
123
+ function targetExec(dir: string): string {
124
+ const top = plan.find((e) => !e.dir && !e.dst.includes('/')) ?? plan[0]
125
+ return top ? `${dir}/${top.dst}` : ''
126
+ }
127
+
105
128
  async function doInstall(): Promise<void> {
106
129
  if (busy) return
107
130
  busy = true
@@ -126,15 +149,11 @@ async function doInstall(): Promise<void> {
126
149
  setStatus(`Instalado en ${res.dir} (${res.files} ficheros)`, 'ok')
127
150
 
128
151
  if (($('#shortcut') as HTMLInputElement).checked) {
129
- const exec = plan.find((e) => !e.dir && !e.dst.includes('/'))?.dst ?? plan[0]?.dst
130
- await invoke('installer', 'shortcuts', { execPath: `${dir}/${exec ?? ''}` }).catch(() => false)
152
+ await invoke('installer', 'shortcuts', { execPath: targetExec(res.dir) }).catch(() => false)
131
153
  }
132
154
  if (($('#launch') as HTMLInputElement).checked) {
133
- const exec = plan.find((e) => !e.dir && !e.dst.includes('/'))?.dst ?? plan[0]?.dst
134
- await invoke('installer', 'launch', { path: `${dir}/${exec ?? ''}` }).catch(() => false)
135
- const v = await invoke<{ elevated: boolean }>('installer', 'elevate').catch(() => ({ elevated: false }))
136
- void v
137
- await invoke('ow-window', 'close', window.__owWindowId)
155
+ await invoke('installer', 'launch', { path: targetExec(res.dir) }).catch(() => false)
156
+ await invoke('ow-window', 'close', wid)
138
157
  }
139
158
  } catch (e) {
140
159
  setStatus(e instanceof Error ? e.message : String(e), 'err')
@@ -147,6 +166,6 @@ async function doInstall(): Promise<void> {
147
166
 
148
167
  $('#pick').addEventListener('click', () => void pickDir())
149
168
  $('#install').addEventListener('click', () => void doInstall())
150
- $('#cancel').addEventListener('click', () => void invoke('ow-window', 'close', window.__owWindowId))
169
+ $('#cancel').addEventListener('click', () => void invoke('ow-window', 'close', wid))
151
170
 
152
171
  void boot()
@@ -1,66 +1,442 @@
1
1
  /* Copyright 2026 Owear Contributors
2
- SPDX-License-Identifier: Apache-2.0 */
2
+ * SPDX-License-Identifier: Apache-2.0
3
+ *
4
+ * Estilos del instalador — mismos tokens/componentes que el Starter de Owear
5
+ * (paleta oscura + acento rosado, JetBrains Mono + Dancing Script).
6
+ */
7
+
3
8
  :root {
4
- --bg: #0f1115;
5
- --panel: #171a21;
6
- --fg: #e8eaed;
7
- --muted: #9aa0aa;
8
- --accent: #4c8bf5;
9
- --ok: #3fb950;
10
- --err: #f85149;
11
9
  color-scheme: dark;
10
+
11
+ /* colores base */
12
+ --bg: #282c34;
13
+ --bg-deep: #21252b;
14
+ --panel: #2c313a;
15
+ --panel-2: #21252b;
16
+ --line: #3b4048;
17
+ --line-2: #4b5263;
18
+ --text: #abb2bf;
19
+ --text-strong: #dcdfe4;
20
+ --muted: #5c6370;
21
+ --purple: #c678dd;
22
+ --green: #98c379;
23
+ --red: #e06c75;
24
+ --cyan: #56b6c2;
25
+
26
+ /* acento: rosado seco */
27
+ --accent: #d3869b;
28
+ --accent-hover: #c06f86;
29
+ --accent-ink: #241820;
30
+
31
+ --radius: 10px;
32
+ --btn: #2c313a;
33
+ --hover: rgba(255, 255, 255, 0.06);
34
+ --hover-strong: rgba(255, 255, 255, 0.14);
12
35
  }
13
- * { box-sizing: border-box; }
14
- html, body { height: 100%; margin: 0; }
36
+
37
+ :root[data-theme="light"] {
38
+ color-scheme: light;
39
+ --bg: #fafafa;
40
+ --bg-deep: #f0f0f1;
41
+ --panel: #ffffff;
42
+ --panel-2: #f0f0f1;
43
+ --line: #d7d7d9;
44
+ --line-2: #b9b9bc;
45
+ --text: #383a42;
46
+ --text-strong: #1f2126;
47
+ --muted: #9aa0a6;
48
+ --purple: #a626a4;
49
+ --green: #50a14f;
50
+ --red: #e45649;
51
+ --cyan: #0184bc;
52
+ --accent: #b5586e;
53
+ --accent-hover: #9d4760;
54
+ --accent-ink: #ffffff;
55
+ --btn: #e8e8ea;
56
+ --hover: rgba(0, 0, 0, 0.05);
57
+ --hover-strong: rgba(0, 0, 0, 0.1);
58
+ }
59
+
60
+ @font-face {
61
+ font-family: "Dancing Script";
62
+ font-style: normal;
63
+ font-weight: 600;
64
+ font-display: swap;
65
+ src: url("./assets/dancing-script.woff2") format("woff2");
66
+ }
67
+
68
+ @font-face {
69
+ font-family: "JetBrains Mono";
70
+ font-style: normal;
71
+ font-weight: 400 700;
72
+ font-display: swap;
73
+ src: url("./assets/jetbrains-mono.woff2") format("woff2");
74
+ }
75
+
76
+ * {
77
+ box-sizing: border-box;
78
+ }
79
+
80
+ html,
15
81
  body {
82
+ height: 100%;
83
+ }
84
+
85
+ body {
86
+ margin: 0;
16
87
  background: var(--bg);
17
- color: var(--fg);
18
- font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
88
+ color: var(--text);
89
+ font: 14px/1.55 "JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo,
90
+ Consolas, monospace;
91
+ display: flex;
92
+ flex-direction: column;
93
+ overflow: hidden;
94
+ user-select: none;
95
+ -webkit-user-select: none;
19
96
  }
20
97
 
98
+ /* ── Titlebar ─────────────────────────────────────────────────────────── */
21
99
  .titlebar {
100
+ height: 40px;
101
+ flex: 0 0 auto;
102
+ display: flex;
103
+ align-items: stretch;
104
+ background: var(--bg-deep);
105
+ }
106
+
107
+ .titlebar__drag {
108
+ flex: 1;
109
+ min-width: 0;
22
110
  display: flex;
23
111
  align-items: center;
24
- justify-content: space-between;
25
- height: 38px;
26
- padding: 0 8px 0 14px;
27
- background: #12141a;
28
- border-bottom: 1px solid #232733;
112
+ gap: 10px;
113
+ padding: 0 14px;
114
+ }
115
+
116
+ .brand__mark {
117
+ width: 12px;
118
+ height: 12px;
119
+ border-radius: 3px;
120
+ background: var(--accent);
121
+ }
122
+
123
+ .brand__name {
124
+ color: var(--text-strong);
125
+ font-weight: 600;
126
+ letter-spacing: 0.2px;
127
+ white-space: nowrap;
128
+ overflow: hidden;
129
+ text-overflow: ellipsis;
29
130
  }
30
- .titlebar__drag { flex: 1; -webkit-app-region: drag; user-select: none; }
31
- .brand__name { font-weight: 600; }
131
+
132
+ .titlebar__controls {
133
+ display: flex;
134
+ }
135
+
32
136
  .winbtn {
33
- width: 30px; height: 30px; border: 0; border-radius: 6px;
34
- background: transparent; color: var(--muted); cursor: pointer;
35
- }
36
- .winbtn:hover { background: #232733; color: var(--fg); }
37
- .winbtn--close:hover { background: #7d2c2c; color: #fff; }
38
- .winbtn svg { fill: none; stroke: currentColor; stroke-width: 1.3; }
39
-
40
- .page { max-width: 780px; margin: 0 auto; padding: 22px 18px 90px; display: grid; gap: 14px; }
41
- .hero h1 { margin: 6px 0 4px; font-size: 24px; }
42
- .eyebrow { color: var(--accent); text-transform: uppercase; letter-spacing: .08em; font-size: 11px; }
43
- .lead { color: var(--muted); margin: 0; }
44
-
45
- .card { background: var(--panel); border: 1px solid #232733; border-radius: 12px; padding: 14px; }
46
- .card--grow { min-height: 160px; }
47
- .card__head { display: flex; align-items: center; justify-content: space-between; }
48
- .card__head h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0; }
49
- .badge { background: #232733; border-radius: 999px; padding: 2px 10px; font-size: 12px; color: var(--muted); }
50
-
51
- .row { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
52
- .input { flex: 1; background: #0d0f13; border: 1px solid #2a2f3b; color: var(--fg); border-radius: 8px; padding: 8px 10px; }
53
- .opt { display: flex; align-items: center; gap: 6px; color: var(--muted); }
54
- .console { background: #0d0f13; border: 1px solid #232733; border-radius: 8px; padding: 10px; max-height: 260px; overflow: auto; font: 12px/1.5 ui-monospace, monospace; white-space: pre-wrap; color: var(--muted); }
55
-
56
- .btn { background: #232733; color: var(--fg); border: 0; border-radius: 8px; padding: 8px 14px; cursor: pointer; }
57
- .btn:hover { background: #2b3040; }
58
- .btn--primary { background: var(--accent); color: #fff; }
59
- .btn--primary:disabled { opacity: .5; cursor: default; }
60
- .btn--ghost { background: transparent; border: 1px solid #2a2f3b; color: var(--muted); }
61
-
62
- .actions { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 12px; align-items: center; justify-content: flex-end; padding: 12px 18px; background: #12141acc; backdrop-filter: blur(6px); border-top: 1px solid #232733; }
63
- .status { margin-right: auto; color: var(--muted); }
64
- .status[data-kind="ok"] { color: var(--ok); }
65
- .status[data-kind="err"] { color: var(--err); }
66
- progress { width: 160px; }
137
+ width: 46px;
138
+ border: 0;
139
+ background: transparent;
140
+ color: var(--muted);
141
+ cursor: pointer;
142
+ display: grid;
143
+ place-items: center;
144
+ }
145
+
146
+ .winbtn svg {
147
+ fill: none;
148
+ stroke: currentColor;
149
+ stroke-width: 1.4;
150
+ }
151
+
152
+ .winbtn:hover {
153
+ background: var(--btn);
154
+ color: var(--text-strong);
155
+ }
156
+
157
+ .winbtn--close:hover {
158
+ background: var(--red);
159
+ color: #fff;
160
+ }
161
+
162
+ /* ── Layout ───────────────────────────────────────────────────────────── */
163
+ .page {
164
+ flex: 1;
165
+ min-height: 0;
166
+ overflow: auto;
167
+ padding: 26px clamp(18px, 4vw, 44px) 96px;
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: 20px;
171
+ max-width: 860px;
172
+ width: 100%;
173
+ margin: 0 auto;
174
+ }
175
+
176
+ .hero {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: 10px;
180
+ }
181
+
182
+ h1 {
183
+ margin: 0;
184
+ color: var(--text-strong);
185
+ font-family: "Dancing Script", cursive;
186
+ font-weight: 600;
187
+ font-size: clamp(30px, 4vw, 44px);
188
+ line-height: 1.15;
189
+ }
190
+
191
+ .lead {
192
+ margin: 0;
193
+ max-width: 72ch;
194
+ color: var(--muted);
195
+ font-size: 15px;
196
+ }
197
+
198
+ .lead strong {
199
+ color: var(--text);
200
+ }
201
+
202
+ .board {
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: 16px;
206
+ padding: 16px;
207
+ background: var(--bg-deep);
208
+ border-radius: var(--radius);
209
+ }
210
+
211
+ .card {
212
+ background: var(--panel);
213
+ border-radius: var(--radius);
214
+ padding: 16px 18px;
215
+ }
216
+
217
+ .card--grow {
218
+ min-width: 0;
219
+ display: flex;
220
+ flex-direction: column;
221
+ }
222
+
223
+ .card__head {
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: space-between;
227
+ margin-bottom: 12px;
228
+ }
229
+
230
+ .card__head h2 {
231
+ margin: 0;
232
+ font-size: 12px;
233
+ text-transform: uppercase;
234
+ letter-spacing: 0.07em;
235
+ color: var(--muted);
236
+ }
237
+
238
+ .badge {
239
+ font-size: 12px;
240
+ color: var(--purple);
241
+ background: var(--panel-2);
242
+ border: 1px solid var(--line);
243
+ border-radius: 999px;
244
+ padding: 2px 10px;
245
+ font-variant-numeric: tabular-nums;
246
+ }
247
+
248
+ .hint {
249
+ margin: 0 0 12px;
250
+ color: var(--muted);
251
+ }
252
+
253
+ .hint code,
254
+ .lead code {
255
+ background: var(--panel-2);
256
+ border: 1px solid var(--line);
257
+ border-radius: 6px;
258
+ padding: 1px 6px;
259
+ font-size: 12.5px;
260
+ }
261
+
262
+ code {
263
+ color: var(--purple);
264
+ }
265
+
266
+ .console {
267
+ flex: 1;
268
+ min-height: 150px;
269
+ max-height: 42vh;
270
+ margin: 0;
271
+ overflow: auto;
272
+ background: var(--bg-deep);
273
+ border-radius: 8px;
274
+ padding: 12px 14px;
275
+ font: 12.5px/1.6 "JetBrains Mono", ui-monospace, monospace;
276
+ color: var(--text);
277
+ white-space: pre-wrap;
278
+ word-break: break-word;
279
+ user-select: text;
280
+ -webkit-user-select: text;
281
+ }
282
+
283
+ /* ── Formulario ───────────────────────────────────────────────────────── */
284
+ .row {
285
+ display: flex;
286
+ gap: 8px;
287
+ align-items: center;
288
+ }
289
+
290
+ .row + .row {
291
+ margin-top: 12px;
292
+ }
293
+
294
+ .field {
295
+ flex: 1;
296
+ min-width: 0;
297
+ font: inherit;
298
+ color: var(--text);
299
+ background: var(--bg-deep);
300
+ border: 1px solid var(--line);
301
+ border-radius: 8px;
302
+ padding: 8px 10px;
303
+ }
304
+
305
+ .field:focus {
306
+ outline: none;
307
+ border-color: var(--accent);
308
+ }
309
+
310
+ .opt {
311
+ display: flex;
312
+ align-items: center;
313
+ gap: 8px;
314
+ color: var(--text);
315
+ cursor: pointer;
316
+ }
317
+
318
+ .opt input {
319
+ accent-color: var(--accent);
320
+ }
321
+
322
+ /* ── Botones (igual que el Starter) ───────────────────────────────────── */
323
+ .btn {
324
+ position: relative;
325
+ isolation: isolate;
326
+ font: inherit;
327
+ color: var(--text);
328
+ background: var(--btn);
329
+ border: 0;
330
+ border-radius: 10px;
331
+ padding: 8px 14px;
332
+ cursor: pointer;
333
+ }
334
+
335
+ .btn::before {
336
+ content: "";
337
+ position: absolute;
338
+ inset: 3px;
339
+ border-radius: 7px;
340
+ background: transparent;
341
+ transition: inset 0.12s ease, border-radius 0.12s ease, background 0.12s ease;
342
+ z-index: -1;
343
+ pointer-events: none;
344
+ }
345
+
346
+ .btn:hover::before {
347
+ background: var(--hover);
348
+ }
349
+
350
+ .btn:active::before {
351
+ inset: 0;
352
+ border-radius: 10px;
353
+ background: var(--hover-strong);
354
+ }
355
+
356
+ .btn--primary {
357
+ background: var(--accent);
358
+ color: var(--accent-ink);
359
+ font-weight: 600;
360
+ }
361
+
362
+ .btn--primary:hover::before {
363
+ background: rgba(255, 255, 255, 0.16);
364
+ }
365
+
366
+ .btn--primary:active::before {
367
+ background: rgba(255, 255, 255, 0.3);
368
+ }
369
+
370
+ .btn--primary:disabled {
371
+ opacity: 0.5;
372
+ cursor: default;
373
+ }
374
+
375
+ .btn--ghost {
376
+ background: transparent;
377
+ }
378
+
379
+ .linkbtn {
380
+ font: inherit;
381
+ background: transparent;
382
+ border: 0;
383
+ color: var(--muted);
384
+ cursor: pointer;
385
+ }
386
+
387
+ .linkbtn:hover {
388
+ color: var(--accent);
389
+ }
390
+
391
+ /* ── Pie fijo con progreso ────────────────────────────────────────────── */
392
+ .foot {
393
+ position: fixed;
394
+ left: 0;
395
+ right: 0;
396
+ bottom: 0;
397
+ display: flex;
398
+ align-items: center;
399
+ gap: 14px;
400
+ padding: 14px clamp(18px, 4vw, 44px);
401
+ background: color-mix(in srgb, var(--bg-deep) 86%, transparent);
402
+ backdrop-filter: blur(6px);
403
+ }
404
+
405
+ .foot__spacer {
406
+ flex: 1;
407
+ }
408
+
409
+ .status {
410
+ color: var(--muted);
411
+ min-width: 0;
412
+ overflow: hidden;
413
+ text-overflow: ellipsis;
414
+ white-space: nowrap;
415
+ }
416
+
417
+ .status[data-kind="ok"] {
418
+ color: var(--green);
419
+ }
420
+
421
+ .status[data-kind="err"] {
422
+ color: var(--red);
423
+ }
424
+
425
+ progress {
426
+ width: 160px;
427
+ height: 6px;
428
+ border: 0;
429
+ border-radius: 999px;
430
+ background: var(--panel-2);
431
+ overflow: hidden;
432
+ }
433
+
434
+ progress::-webkit-progress-bar {
435
+ background: var(--panel-2);
436
+ border-radius: 999px;
437
+ }
438
+
439
+ progress::-webkit-progress-value {
440
+ background: var(--accent);
441
+ border-radius: 999px;
442
+ }