@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.
- package/package.json +7 -7
- package/src/ow.js +83 -4
- package/template/README.md +34 -28
- package/template/app/main.ts +273 -4
- package/template/index.html +117 -28
- package/template/package.json +1 -1
- package/template/public/favicon.svg +4 -0
- package/template/src/assets/dancing-script.woff2 +0 -0
- package/template/src/assets/jetbrains-mono.woff2 +0 -0
- package/template/src/ow.d.ts +9 -1
- package/template/src/renderer.ts +489 -1
- package/template/src/style.css +280 -56
- package/template-installer/index.html +36 -26
- package/template-installer/src/assets/dancing-script.woff2 +0 -0
- package/template-installer/src/assets/jetbrains-mono.woff2 +0 -0
- package/template-installer/src/installer.ts +44 -25
- package/template-installer/src/style.css +429 -53
- package/template-installer/uninstaller/index.html +22 -16
- package/template-installer/uninstaller/src/uninstaller.ts +53 -28
- package/tools/owear-installer.mjs +5 -0
- package/tools/owear-sign.mjs +204 -0
- package/tools/owear-update.mjs +150 -0
- package/LICENSE +0 -201
|
@@ -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
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
$('#win-close').addEventListener('click', () => void invoke('ow-window', 'close',
|
|
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
|
-
|
|
81
|
+
`Se instalará ${name}${version ? ` ${version}` : ''} · modo ${mode} · ${platform}`
|
|
74
82
|
|
|
75
|
-
|
|
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:
|
|
99
|
+
order: bridge?.order,
|
|
87
100
|
})
|
|
88
101
|
const total = plan.reduce((n, e) => n + e.size, 0)
|
|
89
|
-
$('#count').textContent = `${plan.length}
|
|
102
|
+
$('#count').textContent = `${plan.length} · ${fmtSize(total)}`
|
|
90
103
|
$('#plan').textContent = plan
|
|
91
104
|
.slice(0, 300)
|
|
92
|
-
.map((e) => `${e.dir ? '
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
await invoke('
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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(--
|
|
18
|
-
font: 14px/1.
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
.
|
|
131
|
+
|
|
132
|
+
.titlebar__controls {
|
|
133
|
+
display: flex;
|
|
134
|
+
}
|
|
135
|
+
|
|
32
136
|
.winbtn {
|
|
33
|
-
width:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
}
|