sealkeep 0.5.0
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/ARCHITECTURE.md +201 -0
- package/CHANGELOG.md +218 -0
- package/CONTROL_PLANE.md +86 -0
- package/LICENSE +34 -0
- package/README.md +249 -0
- package/THIRD_PARTY.md +22 -0
- package/THREAT_MODEL.md +107 -0
- package/dist/packages/vaultline-crypto/src/aead.d.ts +12 -0
- package/dist/packages/vaultline-crypto/src/aead.js +24 -0
- package/dist/packages/vaultline-crypto/src/chunk-access.d.ts +39 -0
- package/dist/packages/vaultline-crypto/src/chunk-access.js +93 -0
- package/dist/packages/vaultline-crypto/src/envelope.d.ts +71 -0
- package/dist/packages/vaultline-crypto/src/envelope.js +188 -0
- package/dist/packages/vaultline-crypto/src/format.d.ts +106 -0
- package/dist/packages/vaultline-crypto/src/format.js +43 -0
- package/dist/packages/vaultline-crypto/src/index.d.ts +5 -0
- package/dist/packages/vaultline-crypto/src/index.js +5 -0
- package/dist/packages/vaultline-crypto/src/recipients.d.ts +42 -0
- package/dist/packages/vaultline-crypto/src/recipients.js +129 -0
- package/dist/packages/vaultline-crypto/src/sha256-stream.d.ts +41 -0
- package/dist/packages/vaultline-crypto/src/sha256-stream.js +206 -0
- package/dist/packages/vaultline-crypto/src/stream.d.ts +139 -0
- package/dist/packages/vaultline-crypto/src/stream.js +477 -0
- package/dist/site/index.html +1542 -0
- package/dist/site.zip +0 -0
- package/dist/src/activity.d.ts +22 -0
- package/dist/src/activity.js +52 -0
- package/dist/src/adapters.d.ts +212 -0
- package/dist/src/adapters.js +533 -0
- package/dist/src/audit.d.ts +24 -0
- package/dist/src/audit.js +41 -0
- package/dist/src/autopilot.d.ts +77 -0
- package/dist/src/autopilot.js +148 -0
- package/dist/src/bip39-wordlist.d.ts +15 -0
- package/dist/src/bip39-wordlist.js +272 -0
- package/dist/src/branding.d.ts +31 -0
- package/dist/src/branding.js +31 -0
- package/dist/src/chunk-store.d.ts +142 -0
- package/dist/src/chunk-store.js +502 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +2035 -0
- package/dist/src/cloud.d.ts +434 -0
- package/dist/src/cloud.js +851 -0
- package/dist/src/control-plane/auth.d.ts +62 -0
- package/dist/src/control-plane/auth.js +123 -0
- package/dist/src/control-plane/server.d.ts +31 -0
- package/dist/src/control-plane/server.js +263 -0
- package/dist/src/control-plane/store.d.ts +101 -0
- package/dist/src/control-plane/store.js +82 -0
- package/dist/src/control-plane-cli.d.ts +2 -0
- package/dist/src/control-plane-cli.js +37 -0
- package/dist/src/control-plane-server.d.ts +10 -0
- package/dist/src/control-plane-server.js +11 -0
- package/dist/src/control-plane.d.ts +78 -0
- package/dist/src/control-plane.js +61 -0
- package/dist/src/crypto.d.ts +56 -0
- package/dist/src/crypto.js +132 -0
- package/dist/src/daemon.d.ts +52 -0
- package/dist/src/daemon.js +142 -0
- package/dist/src/dashboard-cli.d.ts +2 -0
- package/dist/src/dashboard-cli.js +20 -0
- package/dist/src/disk.d.ts +110 -0
- package/dist/src/disk.js +169 -0
- package/dist/src/doctor.d.ts +11 -0
- package/dist/src/doctor.js +198 -0
- package/dist/src/enroll.d.ts +27 -0
- package/dist/src/enroll.js +136 -0
- package/dist/src/errors.d.ts +26 -0
- package/dist/src/errors.js +23 -0
- package/dist/src/heartbeat.d.ts +89 -0
- package/dist/src/heartbeat.js +120 -0
- package/dist/src/index-sync.d.ts +53 -0
- package/dist/src/index-sync.js +147 -0
- package/dist/src/leakscan.d.ts +48 -0
- package/dist/src/leakscan.js +222 -0
- package/dist/src/local-api.d.ts +132 -0
- package/dist/src/local-api.js +1757 -0
- package/dist/src/managed-chunks.d.ts +55 -0
- package/dist/src/managed-chunks.js +108 -0
- package/dist/src/mcp-install.d.ts +52 -0
- package/dist/src/mcp-install.js +140 -0
- package/dist/src/mcp.d.ts +1 -0
- package/dist/src/mcp.js +59 -0
- package/dist/src/migrate.d.ts +35 -0
- package/dist/src/migrate.js +88 -0
- package/dist/src/mnemonic.d.ts +60 -0
- package/dist/src/mnemonic.js +134 -0
- package/dist/src/net.d.ts +2 -0
- package/dist/src/net.js +16 -0
- package/dist/src/notify.d.ts +46 -0
- package/dist/src/notify.js +84 -0
- package/dist/src/offload.d.ts +117 -0
- package/dist/src/offload.js +331 -0
- package/dist/src/onboarding.d.ts +10 -0
- package/dist/src/onboarding.js +44 -0
- package/dist/src/packages.d.ts +126 -0
- package/dist/src/packages.js +114 -0
- package/dist/src/passkey.d.ts +26 -0
- package/dist/src/passkey.js +54 -0
- package/dist/src/password-lock.d.ts +19 -0
- package/dist/src/password-lock.js +156 -0
- package/dist/src/paths.d.ts +9 -0
- package/dist/src/paths.js +24 -0
- package/dist/src/providers/gcs.d.ts +133 -0
- package/dist/src/providers/gcs.js +235 -0
- package/dist/src/providers/gdrive.d.ts +156 -0
- package/dist/src/providers/gdrive.js +335 -0
- package/dist/src/providers/index.d.ts +45 -0
- package/dist/src/providers/index.js +74 -0
- package/dist/src/providers/s3.d.ts +174 -0
- package/dist/src/providers/s3.js +345 -0
- package/dist/src/providers/sigv4.d.ts +78 -0
- package/dist/src/providers/sigv4.js +112 -0
- package/dist/src/queue.d.ts +185 -0
- package/dist/src/queue.js +286 -0
- package/dist/src/recovery.d.ts +40 -0
- package/dist/src/recovery.js +132 -0
- package/dist/src/rehydrate.d.ts +43 -0
- package/dist/src/rehydrate.js +66 -0
- package/dist/src/restore.d.ts +34 -0
- package/dist/src/restore.js +80 -0
- package/dist/src/retention.d.ts +251 -0
- package/dist/src/retention.js +446 -0
- package/dist/src/rotate.d.ts +47 -0
- package/dist/src/rotate.js +95 -0
- package/dist/src/search.d.ts +147 -0
- package/dist/src/search.js +677 -0
- package/dist/src/secrets.d.ts +86 -0
- package/dist/src/secrets.js +220 -0
- package/dist/src/service.d.ts +73 -0
- package/dist/src/service.js +197 -0
- package/dist/src/share.d.ts +34 -0
- package/dist/src/share.js +68 -0
- package/dist/src/spool.d.ts +97 -0
- package/dist/src/spool.js +213 -0
- package/dist/src/start-tui.d.ts +17 -0
- package/dist/src/start-tui.js +113 -0
- package/dist/src/start.d.ts +75 -0
- package/dist/src/start.js +101 -0
- package/dist/src/storage-setup.d.ts +49 -0
- package/dist/src/storage-setup.js +222 -0
- package/dist/src/storage-targets.d.ts +40 -0
- package/dist/src/storage-targets.js +147 -0
- package/dist/src/stream-to-cloud.d.ts +76 -0
- package/dist/src/stream-to-cloud.js +820 -0
- package/dist/src/sync-rules.d.ts +85 -0
- package/dist/src/sync-rules.js +125 -0
- package/dist/src/trash.d.ts +15 -0
- package/dist/src/trash.js +63 -0
- package/dist/src/tui.d.ts +18 -0
- package/dist/src/tui.js +179 -0
- package/dist/src/types.d.ts +191 -0
- package/dist/src/types.js +3 -0
- package/dist/src/ui-server.d.ts +187 -0
- package/dist/src/ui-server.js +293 -0
- package/dist/src/ui.d.ts +41 -0
- package/dist/src/ui.js +102 -0
- package/dist/src/update.d.ts +30 -0
- package/dist/src/update.js +56 -0
- package/dist/src/upload.d.ts +46 -0
- package/dist/src/upload.js +80 -0
- package/dist/src/vault.d.ts +208 -0
- package/dist/src/vault.js +812 -0
- package/dist/src/watcher.d.ts +34 -0
- package/dist/src/watcher.js +121 -0
- package/dist/src/worker.d.ts +52 -0
- package/dist/src/worker.js +190 -0
- package/package.json +65 -0
- package/web/app.js +1372 -0
- package/web/index.html +476 -0
- package/web/rail.js +308 -0
- package/web/retention.html +17 -0
- package/web/rules-view.js +249 -0
- package/web/sessions-view.js +448 -0
- package/web/sessions.html +17 -0
- package/web/setup-api.js +181 -0
- package/web/setup-logic.js +394 -0
- package/web/setup.html +419 -0
- package/web/setup.js +697 -0
- package/web/style.css +990 -0
package/web/style.css
ADDED
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Vaultline dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Two worlds, one rule between them. Above the line is what you can still read:
|
|
5
|
+
* source paths, agent names, sizes. Below it is what only you can open. The
|
|
6
|
+
* boundary strip in the header is that rule, drawn with live numbers on each side.
|
|
7
|
+
*
|
|
8
|
+
* Type: monospace is the product's native voice, so it carries display and data.
|
|
9
|
+
* Prose sits in the system sans to stay readable at small sizes.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
/* Vaultline design system — the ten tokens from site/index.html, the source
|
|
14
|
+
of truth (see design-system/README.md). The older dashboard names below are
|
|
15
|
+
kept as aliases so existing rules resolve to the system's colours without a
|
|
16
|
+
full rename. The page is achromatic: the ONE saturated block is ciphertext
|
|
17
|
+
(--seal); --live marks what is interactive or currently true; --warn is
|
|
18
|
+
rationed to where inaction loses data. */
|
|
19
|
+
--paper: #f4f4f3;
|
|
20
|
+
--card: #fbfbfa;
|
|
21
|
+
--ink: #111316;
|
|
22
|
+
--soft: #5e646c;
|
|
23
|
+
--rule: #dadcde;
|
|
24
|
+
--seal: #101a3d;
|
|
25
|
+
--seal-ink: #96a6e0;
|
|
26
|
+
--live: #2a3fb0;
|
|
27
|
+
--warn: #8c3220;
|
|
28
|
+
--warn-bg: #f4e6e2;
|
|
29
|
+
|
|
30
|
+
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, "DejaVu Sans Mono", Consolas, monospace;
|
|
31
|
+
/* One typeface: monospace, including display sizes. --sans is aliased to it so
|
|
32
|
+
any rule still asking for a second face complies instead of breaking the
|
|
33
|
+
rule the whole system is built on. */
|
|
34
|
+
--sans: var(--mono);
|
|
35
|
+
|
|
36
|
+
/* Compatibility aliases: prior dashboard rules reference these names. */
|
|
37
|
+
--panel: var(--card);
|
|
38
|
+
--rule-soft: #e6e7e9;
|
|
39
|
+
--text: var(--ink);
|
|
40
|
+
--text-2: var(--soft);
|
|
41
|
+
--text-3: #8b9099;
|
|
42
|
+
--signal: var(--live);
|
|
43
|
+
--verified: var(--live);
|
|
44
|
+
--held: var(--soft);
|
|
45
|
+
--alert: var(--warn);
|
|
46
|
+
/* Were dark-navy fills for the old rail; now the solid-ink fill the system
|
|
47
|
+
uses for a filled control. Lazy substitution keeps them theme-correct. */
|
|
48
|
+
--ink-2: var(--ink);
|
|
49
|
+
--ink-3: var(--soft);
|
|
50
|
+
/* Rail is now part of the light page, not a dark slab. These name faint
|
|
51
|
+
surfaces used for nav hover/active and glyphs. */
|
|
52
|
+
--rail-active: #ecedf0;
|
|
53
|
+
--rail-glyph: #9aa0a8;
|
|
54
|
+
|
|
55
|
+
--rail: 264px;
|
|
56
|
+
--gap: 20px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ── Dark ─────────────────────────────────────────────── */
|
|
60
|
+
/*
|
|
61
|
+
* Tokens only, never components.
|
|
62
|
+
*
|
|
63
|
+
* Dark mode used to exist on exactly two pages, each with its own private
|
|
64
|
+
* palette, so a dark-mode machine flipped from warm paper to charcoal on the
|
|
65
|
+
* click into Sessions and back again on the way out. Consolidating those pages
|
|
66
|
+
* onto this sheet removed the flip by removing the dark side altogether, which
|
|
67
|
+
* is the wrong half to keep — this restores it for the whole product from one
|
|
68
|
+
* place, so the two can no longer disagree.
|
|
69
|
+
*
|
|
70
|
+
* The rail is already near-black in both themes, which is what lets the dark
|
|
71
|
+
* palette stay a change of ground rather than a redesign.
|
|
72
|
+
*/
|
|
73
|
+
@media (prefers-color-scheme: dark) {
|
|
74
|
+
:root {
|
|
75
|
+
--paper: #0d0f11;
|
|
76
|
+
--card: #14171a;
|
|
77
|
+
--ink: #e7e9ec;
|
|
78
|
+
--soft: #8d949c;
|
|
79
|
+
--rule: #24282d;
|
|
80
|
+
--seal: #111c44;
|
|
81
|
+
--seal-ink: #a2b2ea;
|
|
82
|
+
--live: #8195f2;
|
|
83
|
+
--warn: #d97a62;
|
|
84
|
+
--warn-bg: #2b1a16;
|
|
85
|
+
|
|
86
|
+
--rule-soft: #1e2226;
|
|
87
|
+
--text-3: #6f757d;
|
|
88
|
+
--rail-active: #1c2024;
|
|
89
|
+
--rail-glyph: #7d838b;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Form fields take an explicit ground so they are not blind on a dark page. */
|
|
93
|
+
input[type="text"], input[type="number"], input[type="password"],
|
|
94
|
+
input[type="search"], textarea, select {
|
|
95
|
+
background: var(--card);
|
|
96
|
+
border-color: var(--rule);
|
|
97
|
+
color: var(--ink);
|
|
98
|
+
}
|
|
99
|
+
.check-fail { background: var(--warn-bg); }
|
|
100
|
+
tbody tr:hover { background: rgba(129, 149, 242, 0.08); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
* { box-sizing: border-box; }
|
|
104
|
+
|
|
105
|
+
body {
|
|
106
|
+
margin: 0;
|
|
107
|
+
display: flex;
|
|
108
|
+
min-height: 100vh;
|
|
109
|
+
background: var(--paper);
|
|
110
|
+
color: var(--text);
|
|
111
|
+
font-family: var(--sans);
|
|
112
|
+
font-size: 14px;
|
|
113
|
+
line-height: 1.6;
|
|
114
|
+
-webkit-font-smoothing: antialiased;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ── Rail ─────────────────────────────────────────────── */
|
|
118
|
+
|
|
119
|
+
aside {
|
|
120
|
+
width: var(--rail);
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
background: var(--card);
|
|
123
|
+
color: var(--ink);
|
|
124
|
+
border-right: 1px solid var(--rule);
|
|
125
|
+
padding: 30px 16px;
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
gap: 34px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.brand {
|
|
132
|
+
font-family: var(--mono);
|
|
133
|
+
font-size: 21px;
|
|
134
|
+
letter-spacing: -0.03em;
|
|
135
|
+
padding: 0 12px;
|
|
136
|
+
color: var(--ink);
|
|
137
|
+
font-weight: 700;
|
|
138
|
+
}
|
|
139
|
+
.brand span { color: var(--live); margin-right: 6px; }
|
|
140
|
+
|
|
141
|
+
nav { display: grid; gap: 2px; }
|
|
142
|
+
/* The Sessions entry is an anchor rather than a view button, so it inherits
|
|
143
|
+
every button rule here and adds only what an anchor needs. */
|
|
144
|
+
nav a.navlink,
|
|
145
|
+
nav button {
|
|
146
|
+
appearance: none;
|
|
147
|
+
border: 0;
|
|
148
|
+
background: transparent;
|
|
149
|
+
color: var(--soft);
|
|
150
|
+
text-align: left;
|
|
151
|
+
padding: 11px 14px;
|
|
152
|
+
font: 500 13.5px var(--mono);
|
|
153
|
+
letter-spacing: -0.01em;
|
|
154
|
+
border-radius: 5px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: baseline;
|
|
158
|
+
gap: 10px;
|
|
159
|
+
transition: background 120ms ease, color 120ms ease;
|
|
160
|
+
}
|
|
161
|
+
nav a.navlink { text-decoration: none; }
|
|
162
|
+
nav a.navlink:hover { background: var(--rail-active); color: var(--ink); }
|
|
163
|
+
/* Links get the same active treatment as view buttons: on Sessions and
|
|
164
|
+
Retention the current page is a link, and without this nothing in the rail
|
|
165
|
+
looked selected at all. */
|
|
166
|
+
nav a.navlink.active { background: var(--rail-active); color: var(--ink); font-weight: 700; }
|
|
167
|
+
nav a.navlink.active .glyph { color: var(--live); }
|
|
168
|
+
nav a.navlink .glyph,
|
|
169
|
+
nav button .glyph { color: var(--rail-glyph); transition: color 120ms ease; }
|
|
170
|
+
nav button:hover { background: var(--rail-active); color: var(--ink); }
|
|
171
|
+
nav button.active { background: var(--rail-active); color: var(--ink); font-weight: 700; }
|
|
172
|
+
nav button.active .glyph { color: var(--live); }
|
|
173
|
+
nav button .count { margin-left: auto; font-size: 11.5px; color: var(--soft); }
|
|
174
|
+
|
|
175
|
+
.privacy {
|
|
176
|
+
margin-top: auto;
|
|
177
|
+
border-top: 1px solid var(--rule);
|
|
178
|
+
padding: 18px 12px 0;
|
|
179
|
+
font-family: var(--mono);
|
|
180
|
+
font-size: 12px;
|
|
181
|
+
color: var(--soft);
|
|
182
|
+
line-height: 1.65;
|
|
183
|
+
}
|
|
184
|
+
.privacy b { display: block; color: var(--ink); margin-bottom: 7px; font-weight: 600; }
|
|
185
|
+
|
|
186
|
+
/* ── Main ─────────────────────────────────────────────── */
|
|
187
|
+
|
|
188
|
+
main {
|
|
189
|
+
flex: 1;
|
|
190
|
+
min-width: 0;
|
|
191
|
+
padding: 40px 44px 56px;
|
|
192
|
+
max-width: 1440px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
header { margin-bottom: 30px; }
|
|
196
|
+
|
|
197
|
+
h1 {
|
|
198
|
+
font-family: var(--mono);
|
|
199
|
+
font-size: 32px;
|
|
200
|
+
line-height: 1.15;
|
|
201
|
+
letter-spacing: -0.035em;
|
|
202
|
+
font-weight: 700;
|
|
203
|
+
margin: 0 0 8px;
|
|
204
|
+
}
|
|
205
|
+
.lede { margin: 0; color: var(--text-2); max-width: 62ch; }
|
|
206
|
+
|
|
207
|
+
/* Section labels are "a rule plus a word": a short hairline in the accent, then
|
|
208
|
+
the word at reading size — no uppercase, no tracking games. With one mono face
|
|
209
|
+
everywhere, the old uppercase-tracked eyebrow just read as decoration. */
|
|
210
|
+
h2 {
|
|
211
|
+
font-family: var(--mono);
|
|
212
|
+
font-size: 12.5px;
|
|
213
|
+
letter-spacing: 0;
|
|
214
|
+
color: var(--live);
|
|
215
|
+
margin: 34px 0 13px;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
}
|
|
218
|
+
h3 {
|
|
219
|
+
font-family: var(--mono);
|
|
220
|
+
font-size: 12px;
|
|
221
|
+
letter-spacing: 0;
|
|
222
|
+
color: var(--live);
|
|
223
|
+
margin: 22px 0 10px;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
}
|
|
226
|
+
h2::before, h3::before {
|
|
227
|
+
content: "";
|
|
228
|
+
display: inline-block;
|
|
229
|
+
width: 1.5rem;
|
|
230
|
+
height: 1px;
|
|
231
|
+
background: currentColor;
|
|
232
|
+
vertical-align: 0.28em;
|
|
233
|
+
margin-right: 0.6rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* ── The boundary: the one device this page is built around ── */
|
|
237
|
+
|
|
238
|
+
.boundary {
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
gap: 14px;
|
|
242
|
+
margin: 26px 0 6px;
|
|
243
|
+
font-family: var(--mono);
|
|
244
|
+
font-size: 12px;
|
|
245
|
+
color: var(--text-3);
|
|
246
|
+
}
|
|
247
|
+
.boundary::before,
|
|
248
|
+
.boundary::after {
|
|
249
|
+
content: "";
|
|
250
|
+
flex: 1;
|
|
251
|
+
height: 1px;
|
|
252
|
+
background: linear-gradient(90deg, transparent, var(--rule) 18%, var(--rule) 82%, transparent);
|
|
253
|
+
}
|
|
254
|
+
.boundary b {
|
|
255
|
+
color: var(--signal);
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
letter-spacing: 0.02em;
|
|
258
|
+
white-space: nowrap;
|
|
259
|
+
}
|
|
260
|
+
.boundary .side { white-space: nowrap; }
|
|
261
|
+
.boundary .side.sealed { color: var(--ink-3); }
|
|
262
|
+
|
|
263
|
+
/* ── Metrics ──────────────────────────────────────────── */
|
|
264
|
+
|
|
265
|
+
.metrics {
|
|
266
|
+
display: grid;
|
|
267
|
+
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
268
|
+
gap: var(--gap);
|
|
269
|
+
}
|
|
270
|
+
.metric {
|
|
271
|
+
background: var(--panel);
|
|
272
|
+
border: 1px solid var(--rule);
|
|
273
|
+
border-radius: 3px;
|
|
274
|
+
padding: 18px 20px 16px;
|
|
275
|
+
}
|
|
276
|
+
.metric b {
|
|
277
|
+
display: block;
|
|
278
|
+
font-family: var(--mono);
|
|
279
|
+
font-size: 30px;
|
|
280
|
+
letter-spacing: -0.045em;
|
|
281
|
+
line-height: 1.1;
|
|
282
|
+
font-weight: 700;
|
|
283
|
+
}
|
|
284
|
+
.metric b.small { font-size: 15px; letter-spacing: -0.02em; word-break: break-all; line-height: 1.35; }
|
|
285
|
+
.metric small { display: block; margin-top: 8px; font-size: 12px; color: var(--text-3); line-height: 1.5; }
|
|
286
|
+
|
|
287
|
+
/* ── Panels, tables ───────────────────────────────────── */
|
|
288
|
+
|
|
289
|
+
.panel {
|
|
290
|
+
background: var(--panel);
|
|
291
|
+
border: 1px solid var(--rule);
|
|
292
|
+
border-radius: 3px;
|
|
293
|
+
overflow: hidden;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
297
|
+
th {
|
|
298
|
+
text-align: left;
|
|
299
|
+
font: 600 10.5px var(--mono);
|
|
300
|
+
text-transform: uppercase;
|
|
301
|
+
letter-spacing: 0.1em;
|
|
302
|
+
color: var(--text-3);
|
|
303
|
+
padding: 12px 18px;
|
|
304
|
+
border-bottom: 1px solid var(--rule);
|
|
305
|
+
white-space: nowrap;
|
|
306
|
+
}
|
|
307
|
+
td {
|
|
308
|
+
padding: 12px 18px;
|
|
309
|
+
border-bottom: 1px solid var(--rule-soft);
|
|
310
|
+
vertical-align: top;
|
|
311
|
+
word-break: break-word;
|
|
312
|
+
}
|
|
313
|
+
tbody tr:last-child td { border-bottom: 0; }
|
|
314
|
+
tbody tr:hover { background: rgba(31, 95, 255, 0.028); }
|
|
315
|
+
td.num, th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
|
|
316
|
+
td.path { font-family: var(--mono); font-size: 12.5px; color: var(--text-2); }
|
|
317
|
+
td.muted { color: var(--text-3); }
|
|
318
|
+
|
|
319
|
+
/* ── Badges ───────────────────────────────────────────── */
|
|
320
|
+
|
|
321
|
+
.badge {
|
|
322
|
+
display: inline-block;
|
|
323
|
+
font: 600 11px var(--mono);
|
|
324
|
+
letter-spacing: 0.02em;
|
|
325
|
+
padding: 3px 8px;
|
|
326
|
+
border-radius: 3px;
|
|
327
|
+
white-space: nowrap;
|
|
328
|
+
border: 1px solid transparent;
|
|
329
|
+
}
|
|
330
|
+
.badge-verified { color: var(--verified); background: #e9f4ec; border-color: #c6e3d0; }
|
|
331
|
+
.badge-local { color: var(--text-3); background: #f1efec; border-color: var(--rule); }
|
|
332
|
+
.badge-held { color: var(--held); background: #fbf3e2; border-color: #eddcb6; }
|
|
333
|
+
.badge-free { color: var(--signal); background: #eaf0ff; border-color: #c6d6ff; }
|
|
334
|
+
.badge-alert { color: var(--alert); background: #fbeeeb; border-color: #eecabf; }
|
|
335
|
+
|
|
336
|
+
/* ── Checks ───────────────────────────────────────────── */
|
|
337
|
+
|
|
338
|
+
.check {
|
|
339
|
+
display: grid;
|
|
340
|
+
grid-template-columns: 160px 1fr;
|
|
341
|
+
gap: 16px;
|
|
342
|
+
padding: 13px 18px;
|
|
343
|
+
border-bottom: 1px solid var(--rule-soft);
|
|
344
|
+
border-left: 3px solid transparent;
|
|
345
|
+
font-size: 13px;
|
|
346
|
+
}
|
|
347
|
+
.check:last-child { border-bottom: 0; }
|
|
348
|
+
.check strong { font: 600 12.5px var(--mono); }
|
|
349
|
+
.check span { color: var(--text-2); }
|
|
350
|
+
.check-pass { border-left-color: var(--verified); }
|
|
351
|
+
.check-warn { border-left-color: var(--held); }
|
|
352
|
+
.check-fail { border-left-color: var(--alert); background: #fdf4f2; }
|
|
353
|
+
|
|
354
|
+
/* ── Agents ───────────────────────────────────────────── */
|
|
355
|
+
|
|
356
|
+
.agent {
|
|
357
|
+
display: grid;
|
|
358
|
+
grid-template-columns: 150px 1fr auto;
|
|
359
|
+
align-items: center;
|
|
360
|
+
gap: 16px;
|
|
361
|
+
padding: 15px 18px;
|
|
362
|
+
border-bottom: 1px solid var(--rule-soft);
|
|
363
|
+
}
|
|
364
|
+
.agent:last-child { border-bottom: 0; }
|
|
365
|
+
.agent strong { font: 600 14px var(--mono); }
|
|
366
|
+
.agent span { font-family: var(--mono); font-size: 12px; color: var(--text-3); word-break: break-all; }
|
|
367
|
+
|
|
368
|
+
/* ── Controls ─────────────────────────────────────────── */
|
|
369
|
+
|
|
370
|
+
button.action {
|
|
371
|
+
appearance: none;
|
|
372
|
+
font: 600 13px var(--mono);
|
|
373
|
+
padding: 9px 16px;
|
|
374
|
+
border-radius: 3px;
|
|
375
|
+
border: 1px solid var(--signal);
|
|
376
|
+
background: transparent;
|
|
377
|
+
color: var(--signal);
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
transition: background 120ms ease, color 120ms ease;
|
|
380
|
+
}
|
|
381
|
+
button.action:hover { background: var(--signal); color: #fff; }
|
|
382
|
+
button.action.danger { border-color: var(--alert); color: var(--alert); }
|
|
383
|
+
button.action.danger:hover { background: var(--alert); color: #fff; }
|
|
384
|
+
button.action:disabled { border-color: var(--rule); color: var(--text-3); cursor: not-allowed; background: transparent; }
|
|
385
|
+
|
|
386
|
+
.row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
|
387
|
+
.row .field { flex: 1; min-width: 180px; }
|
|
388
|
+
.actions { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
389
|
+
.note { font-size: 12.5px; color: var(--text-3); margin: 0; line-height: 1.6; }
|
|
390
|
+
.pad { padding: 20px; display: grid; gap: 14px; }
|
|
391
|
+
|
|
392
|
+
/* ── Form fields: the settings screen is the first editable surface,
|
|
393
|
+
so text/number/select/textarea inherit the token input's own look
|
|
394
|
+
rather than the browser default. ───────────────────────────────── */
|
|
395
|
+
|
|
396
|
+
.field { display: grid; gap: 6px; }
|
|
397
|
+
/* :not(.opt) rather than letting .opt override after the cascade: a checkbox
|
|
398
|
+
row is a label too, and this keeps the two from fighting over specificity. */
|
|
399
|
+
.field > label:not(.opt) {
|
|
400
|
+
font-family: var(--mono);
|
|
401
|
+
font-size: 11px;
|
|
402
|
+
text-transform: uppercase;
|
|
403
|
+
letter-spacing: 0.1em;
|
|
404
|
+
color: var(--text-3);
|
|
405
|
+
font-weight: 600;
|
|
406
|
+
}
|
|
407
|
+
.field .note { margin-top: -2px; }
|
|
408
|
+
|
|
409
|
+
input[type="text"], input[type="number"], input[type="password"], textarea, select {
|
|
410
|
+
font: 13px var(--mono);
|
|
411
|
+
padding: 9px 11px;
|
|
412
|
+
border: 1px solid var(--rule);
|
|
413
|
+
border-radius: 3px;
|
|
414
|
+
background: #fff;
|
|
415
|
+
color: var(--text);
|
|
416
|
+
width: 100%;
|
|
417
|
+
}
|
|
418
|
+
textarea { resize: vertical; min-height: 4.4em; line-height: 1.5; font-size: 12.5px; }
|
|
419
|
+
select {
|
|
420
|
+
appearance: none;
|
|
421
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%), linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
|
|
422
|
+
background-position: calc(100% - 18px) center, calc(100% - 13px) center;
|
|
423
|
+
background-size: 5px 5px, 5px 5px;
|
|
424
|
+
background-repeat: no-repeat;
|
|
425
|
+
padding-right: 32px;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.opt { display: flex; align-items: baseline; gap: 9px; font-size: 13px; cursor: pointer; }
|
|
429
|
+
.opt input[type="checkbox"] { accent-color: var(--signal); width: 15px; height: 15px; flex-shrink: 0; }
|
|
430
|
+
.opt + .opt { margin-top: 8px; }
|
|
431
|
+
/* A radio choice whose description sits under its title, not welded to it.
|
|
432
|
+
Without this the retention page read "Keep everythingNever reclaim a source
|
|
433
|
+
file" — two sentences with no gap, which is what an unstyled label does. */
|
|
434
|
+
.opt > div b, .opt > div strong { display: block; font-weight: 600; }
|
|
435
|
+
.opt > div span { display: block; color: var(--text-2); margin-top: 3px; line-height: 1.55; }
|
|
436
|
+
.opt input[type="radio"] { accent-color: var(--signal); width: 15px; height: 15px; flex-shrink: 0; margin-top: 2px; }
|
|
437
|
+
|
|
438
|
+
form h3:first-child { margin-top: 0; }
|
|
439
|
+
|
|
440
|
+
/* ── Empty states: an invitation, not an apology ───────── */
|
|
441
|
+
|
|
442
|
+
.empty { padding: 40px 24px; text-align: center; }
|
|
443
|
+
.empty p { margin: 0 0 6px; font: 600 14px var(--mono); }
|
|
444
|
+
.empty small { color: var(--text-3); font-size: 12.5px; }
|
|
445
|
+
.empty code, code {
|
|
446
|
+
font-family: var(--mono);
|
|
447
|
+
font-size: 12px;
|
|
448
|
+
background: #efece7;
|
|
449
|
+
border: 1px solid var(--rule);
|
|
450
|
+
border-radius: 3px;
|
|
451
|
+
padding: 2px 6px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* ── Gate ─────────────────────────────────────────────── */
|
|
455
|
+
|
|
456
|
+
.gate { max-width: 640px; }
|
|
457
|
+
#token-form { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
458
|
+
#token-input {
|
|
459
|
+
flex: 1;
|
|
460
|
+
min-width: 240px;
|
|
461
|
+
font: 13px var(--mono);
|
|
462
|
+
padding: 10px 12px;
|
|
463
|
+
border: 1px solid var(--rule);
|
|
464
|
+
border-radius: 3px;
|
|
465
|
+
background: #fff;
|
|
466
|
+
color: var(--text);
|
|
467
|
+
}
|
|
468
|
+
#token-input:focus-visible, button:focus-visible, nav button:focus-visible,
|
|
469
|
+
input:focus-visible, textarea:focus-visible, select:focus-visible {
|
|
470
|
+
outline: 2px solid var(--signal);
|
|
471
|
+
outline-offset: 2px;
|
|
472
|
+
}
|
|
473
|
+
.error { color: var(--alert); font-size: 13px; margin: 0; }
|
|
474
|
+
|
|
475
|
+
/* ── Status pill ──────────────────────────────────────── */
|
|
476
|
+
|
|
477
|
+
.pill {
|
|
478
|
+
display: inline-flex;
|
|
479
|
+
align-items: center;
|
|
480
|
+
gap: 7px;
|
|
481
|
+
font: 500 12px var(--mono);
|
|
482
|
+
color: var(--text-2);
|
|
483
|
+
}
|
|
484
|
+
.pill::before {
|
|
485
|
+
content: "";
|
|
486
|
+
width: 6px;
|
|
487
|
+
height: 6px;
|
|
488
|
+
border-radius: 50%;
|
|
489
|
+
background: var(--text-3);
|
|
490
|
+
}
|
|
491
|
+
.pill.ok::before { background: var(--verified); }
|
|
492
|
+
.pill.warn::before { background: var(--held); }
|
|
493
|
+
.pill.busy::before { background: var(--signal); animation: pulse 1.1s ease-in-out infinite; }
|
|
494
|
+
|
|
495
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
|
|
496
|
+
|
|
497
|
+
footer {
|
|
498
|
+
margin-top: 40px;
|
|
499
|
+
padding-top: 18px;
|
|
500
|
+
border-top: 1px solid var(--rule);
|
|
501
|
+
display: flex;
|
|
502
|
+
justify-content: space-between;
|
|
503
|
+
gap: 20px;
|
|
504
|
+
flex-wrap: wrap;
|
|
505
|
+
font-size: 12.5px;
|
|
506
|
+
color: var(--text-3);
|
|
507
|
+
}
|
|
508
|
+
footer b { font: 600 12.5px var(--mono); color: var(--text-2); display: block; margin-bottom: 4px; }
|
|
509
|
+
|
|
510
|
+
[hidden] { display: none !important; }
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
/* ── Account card in the rail ─────────────────────────── */
|
|
514
|
+
/* The identity strip: first thing in the rail, right under the wordmark —
|
|
515
|
+
who this vault belongs to, the plan, and how much of it is used. It sits
|
|
516
|
+
above navigation because the design system's cards spec puts identity first;
|
|
517
|
+
it was previously parked at the bottom, where a paying customer's one status
|
|
518
|
+
read lived below the fold of attention. Enters once, settling downward. */
|
|
519
|
+
.acct {
|
|
520
|
+
padding: 2px 12px 14px;
|
|
521
|
+
display: grid;
|
|
522
|
+
gap: 9px;
|
|
523
|
+
border-bottom: 1px solid var(--rule);
|
|
524
|
+
animation: acct-in 480ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
|
|
525
|
+
}
|
|
526
|
+
@keyframes acct-in {
|
|
527
|
+
from { opacity: 0; transform: translateY(-5px); }
|
|
528
|
+
to { opacity: 1; transform: none; }
|
|
529
|
+
}
|
|
530
|
+
.acct-email {
|
|
531
|
+
font: 11px var(--mono);
|
|
532
|
+
color: var(--soft);
|
|
533
|
+
overflow: hidden;
|
|
534
|
+
text-overflow: ellipsis;
|
|
535
|
+
white-space: nowrap;
|
|
536
|
+
}
|
|
537
|
+
.acct-plan {
|
|
538
|
+
position: relative;
|
|
539
|
+
overflow: hidden;
|
|
540
|
+
display: inline-flex;
|
|
541
|
+
align-items: center;
|
|
542
|
+
gap: 7px;
|
|
543
|
+
align-self: start;
|
|
544
|
+
font: 600 11.5px var(--mono);
|
|
545
|
+
letter-spacing: 0;
|
|
546
|
+
padding: 5px 11px;
|
|
547
|
+
border-radius: 99px;
|
|
548
|
+
border: 1px solid var(--rule);
|
|
549
|
+
color: var(--soft);
|
|
550
|
+
}
|
|
551
|
+
.acct-plan::before {
|
|
552
|
+
content: "";
|
|
553
|
+
width: 6px; height: 6px; border-radius: 99px;
|
|
554
|
+
background: var(--soft);
|
|
555
|
+
}
|
|
556
|
+
/* Pro is the system's plan chip: the accent as border and text, weight 700.
|
|
557
|
+
Its one flourish is a single sheen that crosses the chip once on load — the
|
|
558
|
+
same "settles, then holds still" motion grammar as the boundary device — and
|
|
559
|
+
a faint accent tint on hover. Never an infinite loop: a sidebar that never
|
|
560
|
+
stops moving reads as broken, not premium. */
|
|
561
|
+
.acct-plan.pro {
|
|
562
|
+
color: var(--live);
|
|
563
|
+
border-color: var(--live);
|
|
564
|
+
font-weight: 700;
|
|
565
|
+
}
|
|
566
|
+
.acct-plan.pro::before {
|
|
567
|
+
background: var(--live);
|
|
568
|
+
}
|
|
569
|
+
.acct-plan.pro::after {
|
|
570
|
+
content: "";
|
|
571
|
+
position: absolute;
|
|
572
|
+
inset: -1px;
|
|
573
|
+
background: linear-gradient(115deg, transparent 38%, var(--live) 50%, transparent 62%);
|
|
574
|
+
opacity: 0;
|
|
575
|
+
animation: acct-sheen-once 1100ms ease-out 300ms 1 both;
|
|
576
|
+
pointer-events: none;
|
|
577
|
+
}
|
|
578
|
+
@keyframes acct-sheen-once {
|
|
579
|
+
0% { transform: translateX(-110%); opacity: 0.3; }
|
|
580
|
+
80% { opacity: 0.22; }
|
|
581
|
+
100% { transform: translateX(110%); opacity: 0; }
|
|
582
|
+
}
|
|
583
|
+
.acct-plan.pro:hover {
|
|
584
|
+
background: color-mix(in srgb, var(--live) 8%, transparent);
|
|
585
|
+
}
|
|
586
|
+
.acct-meter { height: 4px; border-radius: 99px; background: var(--rule); overflow: hidden; }
|
|
587
|
+
.acct-fill { height: 100%; border-radius: 99px; background: var(--live); transition: width 400ms ease; }
|
|
588
|
+
.acct-usage { font: 11px var(--mono); color: var(--soft); font-variant-numeric: tabular-nums; min-height: 13px; }
|
|
589
|
+
/* Skeleton discipline: every slot holds its height before data arrives, so the
|
|
590
|
+
card never grows and the nav under it never moves. The door slot reserves
|
|
591
|
+
the button's room even for a vault that will not get one — a permanently
|
|
592
|
+
quiet 34px beats a nav that jumps for everyone else. */
|
|
593
|
+
.acct-email { min-height: 13px; }
|
|
594
|
+
.acct-facts li { min-height: 15px; }
|
|
595
|
+
.acct-door-slot { min-height: 34px; }
|
|
596
|
+
.acct-door-slot .acct-door { margin-top: 0; }
|
|
597
|
+
.acct-facts { list-style: none; margin: 2px 0 0; padding: 0; display: grid; gap: 7px; }
|
|
598
|
+
.acct-facts li { display: flex; align-items: baseline; gap: 8px; font: 11.5px var(--mono); color: var(--soft); line-height: 1.4; }
|
|
599
|
+
.acct-facts .dot { width: 6px; height: 6px; border-radius: 99px; background: var(--soft); flex-shrink: 0; transform: translateY(1px); }
|
|
600
|
+
.acct-facts li.good .dot { background: var(--live); }
|
|
601
|
+
.acct-facts li.warn .dot { background: var(--warn); }
|
|
602
|
+
.acct-facts b { color: var(--ink); font-weight: 600; }
|
|
603
|
+
|
|
604
|
+
/* ══ First run ═══════════════════════════════════════════════════════
|
|
605
|
+
setup.html. Same rail, same rules, same two typefaces — a wizard that
|
|
606
|
+
looked like a different product would undo the trust it exists to build.
|
|
607
|
+
The only new idea is the trace on screen one: one session's life drawn
|
|
608
|
+
down a spine, with the boundary strip cutting across it at the moment
|
|
609
|
+
the file stops being readable. The dashboard reports that line; this
|
|
610
|
+
page is where it gets explained.
|
|
611
|
+
══════════════════════════════════════════════════════════════════ */
|
|
612
|
+
|
|
613
|
+
main.wizard { max-width: 940px; }
|
|
614
|
+
main.wizard > .pill { margin: 0 0 20px; }
|
|
615
|
+
.screen > header { margin-bottom: 26px; }
|
|
616
|
+
h1:focus-visible { outline: 2px solid var(--signal); outline-offset: 4px; }
|
|
617
|
+
|
|
618
|
+
.sr-only {
|
|
619
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
620
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/* ── The step rail ────────────────────────────────────────── */
|
|
624
|
+
|
|
625
|
+
nav.steps .step .glyph { font-variant-numeric: tabular-nums; width: 1.15em; text-align: center; }
|
|
626
|
+
nav.steps .step.done .glyph { color: var(--verified); }
|
|
627
|
+
nav.steps .step.current { background: var(--ink-2); color: #fff; }
|
|
628
|
+
nav.steps .step.current .glyph { color: var(--signal); }
|
|
629
|
+
nav.steps .step.locked { opacity: 0.42; cursor: default; }
|
|
630
|
+
nav.steps .step.locked:hover { background: transparent; color: var(--soft); }
|
|
631
|
+
nav.steps .step.locked.current:hover { background: var(--ink-2); color: #fff; }
|
|
632
|
+
|
|
633
|
+
/* ── Screen one: one file's life, across the line ─────────── */
|
|
634
|
+
|
|
635
|
+
.trace {
|
|
636
|
+
background: var(--panel);
|
|
637
|
+
border: 1px solid var(--rule);
|
|
638
|
+
border-radius: 3px;
|
|
639
|
+
padding: 20px 24px;
|
|
640
|
+
margin-bottom: var(--gap);
|
|
641
|
+
}
|
|
642
|
+
.trace-row {
|
|
643
|
+
position: relative;
|
|
644
|
+
display: grid;
|
|
645
|
+
grid-template-columns: 1fr auto;
|
|
646
|
+
gap: 3px 16px;
|
|
647
|
+
padding: 13px 0 13px 28px;
|
|
648
|
+
}
|
|
649
|
+
/* The spine, and the node that fills in the moment the file is sealed. */
|
|
650
|
+
.trace-row::before { content: ""; position: absolute; left: 5px; top: 0; bottom: 0; width: 1px; background: var(--rule); }
|
|
651
|
+
.trace-row::after {
|
|
652
|
+
content: ""; position: absolute; left: 1px; top: 18px;
|
|
653
|
+
width: 9px; height: 9px; border-radius: 50%;
|
|
654
|
+
background: var(--panel); border: 1px solid var(--text-3);
|
|
655
|
+
}
|
|
656
|
+
.trace-row:first-child::before { top: 22px; }
|
|
657
|
+
.trace-row:last-child::before { bottom: auto; height: 22px; }
|
|
658
|
+
.trace-row.sealed::after { background: var(--ink); border-color: var(--ink); }
|
|
659
|
+
/* Placed explicitly: the state is a stamp on the statement, so it belongs on
|
|
660
|
+
its line — auto-placement puts it below the paragraph instead. */
|
|
661
|
+
.trace-row b { grid-area: 1 / 1; font: 600 13.5px var(--mono); letter-spacing: -0.015em; }
|
|
662
|
+
.trace-row .state {
|
|
663
|
+
grid-area: 1 / 2; align-self: baseline;
|
|
664
|
+
font: 600 10.5px var(--mono); text-transform: uppercase; letter-spacing: 0.1em;
|
|
665
|
+
color: var(--text-3); white-space: nowrap;
|
|
666
|
+
}
|
|
667
|
+
.trace-row p { grid-area: 2 / 1 / auto / -1; margin: 0; color: var(--text-2); max-width: 68ch; }
|
|
668
|
+
.trace-row.sealed .state { color: var(--ink-3); }
|
|
669
|
+
.trace .boundary { margin: 8px 0; }
|
|
670
|
+
|
|
671
|
+
/* ── Callouts and prose blocks ────────────────────────────── */
|
|
672
|
+
|
|
673
|
+
.callout p { margin: 0; color: var(--text-2); max-width: 72ch; }
|
|
674
|
+
.callout h3 { margin: 0; }
|
|
675
|
+
.callout h3 + p { margin-top: -6px; }
|
|
676
|
+
.callout.warn { border-left: 3px solid var(--alert); }
|
|
677
|
+
.callout.warn p { color: var(--text); }
|
|
678
|
+
ul.plain { margin: 0; padding-left: 19px; display: grid; gap: 8px; color: var(--text-2); max-width: 72ch; }
|
|
679
|
+
ul.plain b { color: var(--text); }
|
|
680
|
+
.explain summary { font: 600 13px var(--mono); color: var(--signal); cursor: pointer; }
|
|
681
|
+
.explain p { margin: 0; color: var(--text-2); max-width: 72ch; }
|
|
682
|
+
.explain a { color: var(--signal); }
|
|
683
|
+
|
|
684
|
+
/* ── Footer of each screen ────────────────────────────────── */
|
|
685
|
+
|
|
686
|
+
.wizfoot {
|
|
687
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
688
|
+
gap: 16px; flex-wrap: wrap;
|
|
689
|
+
margin-top: var(--gap); padding-top: 18px;
|
|
690
|
+
border-top: 1px solid var(--rule);
|
|
691
|
+
}
|
|
692
|
+
.wizfoot .note { max-width: 46ch; }
|
|
693
|
+
|
|
694
|
+
a.action {
|
|
695
|
+
display: inline-block;
|
|
696
|
+
font: 600 13px var(--mono);
|
|
697
|
+
padding: 9px 16px;
|
|
698
|
+
border-radius: 3px;
|
|
699
|
+
border: 1px solid var(--signal);
|
|
700
|
+
background: transparent;
|
|
701
|
+
color: var(--signal);
|
|
702
|
+
text-decoration: none;
|
|
703
|
+
transition: background 120ms ease, color 120ms ease;
|
|
704
|
+
}
|
|
705
|
+
a.action:hover { background: var(--signal); color: #fff; }
|
|
706
|
+
.action.primary { background: var(--signal); color: #fff; }
|
|
707
|
+
.action.primary:hover { background: var(--ink-2); border-color: var(--ink-2); color: #fff; }
|
|
708
|
+
/* A locked "Finish setup" is on screen the whole time someone is transcribing
|
|
709
|
+
24 words, so it stays readable rather than fading to a hint of itself. */
|
|
710
|
+
button.action.primary:disabled { background: var(--rule-soft); border-color: var(--rule); color: var(--text-2); }
|
|
711
|
+
.note.error { color: var(--alert); font-size: 12.5px; }
|
|
712
|
+
|
|
713
|
+
/* ── Screen two: agents ───────────────────────────────────── */
|
|
714
|
+
|
|
715
|
+
.agent-row, .done-row {
|
|
716
|
+
display: grid; gap: 4px;
|
|
717
|
+
padding: 15px 18px;
|
|
718
|
+
border-bottom: 1px solid var(--rule-soft);
|
|
719
|
+
}
|
|
720
|
+
.agent-row:last-child, .done-row:last-child { border-bottom: 0; }
|
|
721
|
+
.agent-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
722
|
+
.agent-head b { font: 600 14px var(--mono); }
|
|
723
|
+
.agent-name { font: 600 14px var(--mono); cursor: pointer; }
|
|
724
|
+
.agent-path { margin: 0; font: 12px var(--mono); color: var(--text-3); word-break: break-all; }
|
|
725
|
+
.agent-row input[type="checkbox"] { accent-color: var(--signal); width: 15px; height: 15px; flex-shrink: 0; }
|
|
726
|
+
.agent-row input[type="checkbox"]:disabled + .agent-name { cursor: default; color: var(--text-2); }
|
|
727
|
+
.agent-row.state-missing { opacity: 0.6; }
|
|
728
|
+
.agent-row.state-unwritable { border-left: 3px solid var(--alert); padding-left: 15px; background: #fdf4f2; }
|
|
729
|
+
|
|
730
|
+
/* ── Screen three: where archives go ──────────────────────── */
|
|
731
|
+
|
|
732
|
+
.choices { border: 0; margin: 0 0 var(--gap); padding: 0; display: grid; gap: 10px; }
|
|
733
|
+
.choice {
|
|
734
|
+
display: grid; grid-template-columns: auto 1fr; gap: 12px; align-items: start;
|
|
735
|
+
background: var(--panel); border: 1px solid var(--rule); border-radius: 3px;
|
|
736
|
+
padding: 14px 16px; cursor: pointer;
|
|
737
|
+
transition: border-color 120ms ease;
|
|
738
|
+
}
|
|
739
|
+
.choice:hover { border-color: var(--signal); }
|
|
740
|
+
.choice:focus-within { outline: 2px solid var(--signal); outline-offset: 2px; }
|
|
741
|
+
.choice:has(input:checked) { border-color: var(--signal); box-shadow: inset 0 0 0 1px var(--signal); }
|
|
742
|
+
.choice input[type="radio"] { accent-color: var(--signal); width: 15px; height: 15px; margin-top: 3px; }
|
|
743
|
+
.choice-body { display: grid; gap: 4px; }
|
|
744
|
+
.choice-body b { font: 600 14px var(--mono); }
|
|
745
|
+
.choice-body .note { max-width: 68ch; }
|
|
746
|
+
#provider-fields { display: grid; gap: 14px; }
|
|
747
|
+
|
|
748
|
+
/* ── Screen two: sealed or plain ──────────────────────────── */
|
|
749
|
+
|
|
750
|
+
/* The one choice in the wizard that lowers a guarantee. It keeps the shape of
|
|
751
|
+
its neighbour — same card, same radio — and wears the warning colour so the
|
|
752
|
+
difference is visible before a word is read. */
|
|
753
|
+
.choice.warn { border-left: 3px solid var(--alert); }
|
|
754
|
+
.choice.warn:hover { border-color: var(--alert); }
|
|
755
|
+
.choice.warn:has(input:checked) { border-color: var(--alert); box-shadow: inset 0 0 0 1px var(--alert); background: var(--warn-bg); }
|
|
756
|
+
.choice.warn input[type="radio"] { accent-color: var(--alert); }
|
|
757
|
+
|
|
758
|
+
/* ── Screen three: the free/pro compare ───────────────────── */
|
|
759
|
+
|
|
760
|
+
/* auto-fit rather than a media query: the two columns stack on their own the
|
|
761
|
+
moment they would crowd, so the narrow-screen block has nothing to know. */
|
|
762
|
+
.compare { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 6px 22px; }
|
|
763
|
+
.compare-col b { font: 600 13.5px var(--mono); }
|
|
764
|
+
.compare-col .note { margin: 4px 0 0; max-width: 58ch; }
|
|
765
|
+
|
|
766
|
+
/* ── Screen four: the phrase ──────────────────────────────── */
|
|
767
|
+
|
|
768
|
+
/* Hairlines come from each word's own outline, drawn into the 1px gap it shares
|
|
769
|
+
with its neighbour, rather than from a ruled background showing through. 17 is
|
|
770
|
+
never a whole number of columns, and a ruled background paints the leftover
|
|
771
|
+
cell as a solid block. */
|
|
772
|
+
.phrase {
|
|
773
|
+
list-style: none; margin: var(--gap) 0; padding: 0;
|
|
774
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
|
|
775
|
+
gap: 1px; background: var(--panel);
|
|
776
|
+
border: 1px solid var(--rule); border-radius: 3px; overflow: hidden;
|
|
777
|
+
}
|
|
778
|
+
.word { outline: 1px solid var(--rule); padding: 12px 14px; display: flex; align-items: baseline; gap: 10px; }
|
|
779
|
+
.word .ord { font: 600 10.5px var(--mono); color: var(--text-3); font-variant-numeric: tabular-nums; min-width: 1.5em; }
|
|
780
|
+
.word b { font: 700 15px var(--mono); letter-spacing: -0.02em; }
|
|
781
|
+
.phrase-actions { align-items: center; margin-bottom: var(--gap); }
|
|
782
|
+
.phrase-actions .note { flex: 1; min-width: 220px; }
|
|
783
|
+
#verify-fields { align-items: start; }
|
|
784
|
+
#verify-fields .field { flex: 1; min-width: 120px; }
|
|
785
|
+
|
|
786
|
+
@media print {
|
|
787
|
+
aside, .wizfoot, .phrase-actions, #confirm-form, footer, main.wizard > .pill { display: none !important; }
|
|
788
|
+
body { display: block; background: #fff; }
|
|
789
|
+
main.wizard { padding: 0; max-width: none; }
|
|
790
|
+
.phrase { break-inside: avoid; }
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
@media (prefers-reduced-motion: reduce) {
|
|
794
|
+
* { animation: none !important; transition: none !important; }
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
@media (max-width: 900px) {
|
|
798
|
+
body { flex-direction: column; }
|
|
799
|
+
aside { width: 100%; flex-direction: row; align-items: center; gap: 16px; padding: 12px 16px; overflow-x: auto; }
|
|
800
|
+
aside .privacy { display: none; }
|
|
801
|
+
.brand { padding: 0; font-size: 17px; }
|
|
802
|
+
nav { display: flex; gap: 4px; }
|
|
803
|
+
nav button { padding: 8px 11px; font-size: 12.5px; }
|
|
804
|
+
nav button .count { display: none; }
|
|
805
|
+
main { padding: 26px 18px 40px; }
|
|
806
|
+
h1 { font-size: 24px; }
|
|
807
|
+
.boundary { font-size: 11px; gap: 8px; }
|
|
808
|
+
.boundary .side { display: none; }
|
|
809
|
+
.check { grid-template-columns: 1fr; gap: 4px; }
|
|
810
|
+
.agent { grid-template-columns: 1fr; gap: 6px; }
|
|
811
|
+
table { font-size: 12.5px; }
|
|
812
|
+
th, td { padding: 10px 12px; }
|
|
813
|
+
|
|
814
|
+
/* First run, narrow. The rail becomes a strip of numbered steps; the trace
|
|
815
|
+
drops its right-hand state labels above the fold and keeps them below the
|
|
816
|
+
sentence, where they still read in order. */
|
|
817
|
+
nav.steps { flex-wrap: nowrap; }
|
|
818
|
+
nav.steps .step { white-space: nowrap; }
|
|
819
|
+
.trace { padding: 14px 16px; }
|
|
820
|
+
.trace-row { grid-template-columns: 1fr; padding-left: 24px; }
|
|
821
|
+
.trace-row b { grid-area: 1 / 1; }
|
|
822
|
+
.trace-row .state { grid-area: 3 / 1; }
|
|
823
|
+
.trace-row p { grid-area: 2 / 1; }
|
|
824
|
+
.trace .boundary .side { display: none; }
|
|
825
|
+
.wizfoot { align-items: stretch; }
|
|
826
|
+
.wizfoot .actions { flex: 1; }
|
|
827
|
+
.wizfoot .actions .action { flex: 1; text-align: center; }
|
|
828
|
+
.phrase { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/* ── Sessions and retention ───────────────────────────── */
|
|
832
|
+
/*
|
|
833
|
+
* These two pages carried their own inline stylesheets with their own palette,
|
|
834
|
+
* type scale and radii, so moving between them and the dashboard changed
|
|
835
|
+
* products mid-task — and they were the only two files with a dark-mode block,
|
|
836
|
+
* which meant a dark-mode machine flipped from warm paper to charcoal on the
|
|
837
|
+
* click. What they genuinely need that the dashboard does not is below; the
|
|
838
|
+
* rest they now inherit.
|
|
839
|
+
*/
|
|
840
|
+
|
|
841
|
+
.filters {
|
|
842
|
+
display: flex;
|
|
843
|
+
flex-wrap: wrap;
|
|
844
|
+
gap: 8px;
|
|
845
|
+
align-items: center;
|
|
846
|
+
margin: 0 0 12px;
|
|
847
|
+
}
|
|
848
|
+
/* The shared form rule sets width:100% so fields fill a column layout. In a
|
|
849
|
+
filter bar that makes every control its own row, which is how five dropdowns
|
|
850
|
+
became five stacked bars. Here they size to their content. */
|
|
851
|
+
.filters input[type="search"],
|
|
852
|
+
.filters select {
|
|
853
|
+
width: auto;
|
|
854
|
+
font-size: 12.5px;
|
|
855
|
+
padding: 7px 10px;
|
|
856
|
+
}
|
|
857
|
+
.filters select { padding-right: 30px; background-position: right 10px center; }
|
|
858
|
+
.filters input[type="search"] { flex: 1 1 14rem; min-width: 10rem; font-family: var(--sans); }
|
|
859
|
+
.filters .linkish {
|
|
860
|
+
appearance: none;
|
|
861
|
+
border: 0;
|
|
862
|
+
background: none;
|
|
863
|
+
color: var(--signal);
|
|
864
|
+
font: 500 12.5px var(--sans);
|
|
865
|
+
cursor: pointer;
|
|
866
|
+
padding: 6px 4px;
|
|
867
|
+
}
|
|
868
|
+
.filters .linkish:hover { text-decoration: underline; }
|
|
869
|
+
|
|
870
|
+
/* A table of 700 rows keeps its header while you read it. Without this the
|
|
871
|
+
columns scroll away on the first wheel tick and the rest is anonymous. */
|
|
872
|
+
.scroll { max-height: calc(100vh - 26rem); min-height: 18rem; overflow: auto; }
|
|
873
|
+
.scroll thead th {
|
|
874
|
+
position: sticky;
|
|
875
|
+
top: 0;
|
|
876
|
+
background: var(--panel);
|
|
877
|
+
box-shadow: inset 0 -1px var(--rule);
|
|
878
|
+
z-index: 1;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* The plan a customer is paying for. */
|
|
882
|
+
.planbar { padding: 16px 20px; margin-bottom: var(--gap); }
|
|
883
|
+
.plan-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; flex-wrap: wrap; }
|
|
884
|
+
.plan-name { font: 600 15px var(--sans); }
|
|
885
|
+
.plan-usage { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--text-2); }
|
|
886
|
+
.meter { height: 6px; border-radius: 99px; background: var(--rule-soft); overflow: hidden; margin: 10px 0 8px; }
|
|
887
|
+
.meter-fill { height: 100%; background: var(--signal); border-radius: 99px; transition: width 220ms ease; }
|
|
888
|
+
|
|
889
|
+
.rowgrid { display: grid; gap: 16px; grid-template-columns: 1fr 1fr; }
|
|
890
|
+
@media (max-width: 720px) { .rowgrid { grid-template-columns: 1fr; } }
|
|
891
|
+
|
|
892
|
+
/* Filter chips: one click each, combining. */
|
|
893
|
+
/* A row on its way into the vault. --live is the accent for "currently true",
|
|
894
|
+
and a seal in progress is exactly that; the row is unselectable while it
|
|
895
|
+
shows this. */
|
|
896
|
+
td .queued { color: var(--live); font-weight: 600; }
|
|
897
|
+
|
|
898
|
+
.chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 10px; }
|
|
899
|
+
.chip {
|
|
900
|
+
appearance: none;
|
|
901
|
+
border: 1px solid var(--rule);
|
|
902
|
+
background: var(--panel);
|
|
903
|
+
color: var(--text-2);
|
|
904
|
+
font: 500 12.5px var(--sans);
|
|
905
|
+
padding: 5px 11px;
|
|
906
|
+
border-radius: 99px;
|
|
907
|
+
cursor: pointer;
|
|
908
|
+
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
909
|
+
}
|
|
910
|
+
.chip:hover { border-color: var(--signal); color: var(--text); }
|
|
911
|
+
.chip.on { background: var(--signal); border-color: var(--signal); color: #fff; }
|
|
912
|
+
.chip-sep { width: 1px; height: 18px; background: var(--rule); margin: 0 4px; }
|
|
913
|
+
.shown { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 4px 0 10px; }
|
|
914
|
+
|
|
915
|
+
/* The paying plan's door to its cloud panel, at the foot of the identity strip. */
|
|
916
|
+
.acct-door { display: block; margin-top: 10px; text-align: center; }
|
|
917
|
+
|
|
918
|
+
/* A backend-controlled offer, shown to vaults without a plan. The entrance
|
|
919
|
+
animation runs once — motion that ends is an arrival, motion that loops is
|
|
920
|
+
an advertisement, and this product does not blink at people. */
|
|
921
|
+
.rail-offer {
|
|
922
|
+
position: relative;
|
|
923
|
+
margin: 12px 0 4px;
|
|
924
|
+
padding: 12px;
|
|
925
|
+
border: 1px solid var(--rule);
|
|
926
|
+
border-radius: 6px;
|
|
927
|
+
background: var(--card);
|
|
928
|
+
animation: rail-offer-in 420ms ease-out 1;
|
|
929
|
+
}
|
|
930
|
+
.rail-offer b { display: block; font-size: 12px; margin-bottom: 4px; padding-right: 18px; }
|
|
931
|
+
.rail-offer p { margin: 0 0 10px; font-size: 11px; color: var(--soft); }
|
|
932
|
+
.rail-offer .action { display: block; text-align: center; }
|
|
933
|
+
.rail-offer-close {
|
|
934
|
+
position: absolute; top: 6px; right: 8px;
|
|
935
|
+
border: 0; background: none; color: var(--soft); cursor: pointer; font-size: 11px; padding: 2px;
|
|
936
|
+
}
|
|
937
|
+
@keyframes rail-offer-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
|
|
938
|
+
@media (prefers-reduced-motion: reduce) { .rail-offer, .rail-upload { animation: none; } }
|
|
939
|
+
|
|
940
|
+
/* ── Upload progress in the rail ──────────────────────── */
|
|
941
|
+
/* Injected by rail.js directly under the identity strip on every page while an
|
|
942
|
+
upload run is active — leaving the dashboard mid-send must not mean losing
|
|
943
|
+
sight of it. Negative margins tuck it inside the rail's section gap so it
|
|
944
|
+
reads as part of the identity block, closed by the same hairline. It is a
|
|
945
|
+
link back to the dashboard's Retention view, where the full progress lives.
|
|
946
|
+
The meter is determinate and the entrance runs once: motion that ends is
|
|
947
|
+
progress, motion that loops is decoration. */
|
|
948
|
+
.rail-upload {
|
|
949
|
+
display: block;
|
|
950
|
+
margin: -20px 0 -10px;
|
|
951
|
+
padding: 0 12px 14px;
|
|
952
|
+
border-bottom: 1px solid var(--rule);
|
|
953
|
+
text-decoration: none;
|
|
954
|
+
color: inherit;
|
|
955
|
+
animation: rail-upload-in 320ms ease-out 1 both;
|
|
956
|
+
}
|
|
957
|
+
/* The shared meter, at the identity strip's own 4px weight. */
|
|
958
|
+
.rail-upload .meter { height: 4px; margin: 0 0 7px; }
|
|
959
|
+
.rail-upload-line {
|
|
960
|
+
font: 11px var(--mono);
|
|
961
|
+
color: var(--soft);
|
|
962
|
+
font-variant-numeric: tabular-nums;
|
|
963
|
+
line-height: 1.5;
|
|
964
|
+
/* One line stays one line: a long failure message ellipsizes rather than
|
|
965
|
+
folding the rail; the full text lives on the strip's title. */
|
|
966
|
+
white-space: nowrap;
|
|
967
|
+
overflow: hidden;
|
|
968
|
+
text-overflow: ellipsis;
|
|
969
|
+
}
|
|
970
|
+
.rail-upload:hover .rail-upload-line { color: var(--ink); }
|
|
971
|
+
@keyframes rail-upload-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
|
|
972
|
+
/* In the narrow rail-as-row layout the tuck has no gap to sit in. */
|
|
973
|
+
@media (max-width: 900px) {
|
|
974
|
+
.rail-upload { margin: 0; border-bottom: 0; padding: 0; min-width: 180px; align-self: center; }
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/* Retention's diagnosis: each distinct blocker once, with a verb. The rows
|
|
978
|
+
below carry chips, not sentences — the 48th repetition was noise. */
|
|
979
|
+
.hold-summary { padding: 12px 14px 6px; border-bottom: 1px solid var(--rule); }
|
|
980
|
+
.hold-line b { font-weight: 600; }
|
|
981
|
+
.hold-chip {
|
|
982
|
+
display: inline-block;
|
|
983
|
+
margin: 0 .35em .15em 0;
|
|
984
|
+
padding: 0 .45em;
|
|
985
|
+
border: 1px solid var(--rule);
|
|
986
|
+
border-radius: 3px;
|
|
987
|
+
font-size: 11px;
|
|
988
|
+
color: var(--soft);
|
|
989
|
+
white-space: nowrap;
|
|
990
|
+
}
|