driggsby 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/host-page.js +16 -9
- package/dist/init/init-command.js +2 -1
- package/dist/init/template-styles.js +248 -0
- package/dist/init/templates.js +49 -153
- package/package.json +3 -3
package/dist/dev/host-page.js
CHANGED
|
@@ -23,33 +23,40 @@ export function hostPageHtml(slug, appOrigin, background) {
|
|
|
23
23
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
24
24
|
<title>${slug} — driggsby dev</title>
|
|
25
25
|
<style>
|
|
26
|
+
/* The strip above the frame is the console's title row, in its own
|
|
27
|
+
measured values: 46px tall on the black ground, 14px ink, a 14%
|
|
28
|
+
white hairline under it. */
|
|
26
29
|
* { box-sizing: border-box; }
|
|
30
|
+
html { color-scheme: dark; }
|
|
27
31
|
body {
|
|
28
32
|
margin: 0;
|
|
29
33
|
display: flex;
|
|
30
34
|
flex-direction: column;
|
|
31
35
|
height: 100vh;
|
|
32
|
-
background: #
|
|
33
|
-
color: #
|
|
34
|
-
font-family:
|
|
36
|
+
background: #000000;
|
|
37
|
+
color: #fafafa;
|
|
38
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
35
39
|
font-size: 14px;
|
|
40
|
+
line-height: 21px;
|
|
41
|
+
-webkit-font-smoothing: antialiased;
|
|
36
42
|
}
|
|
37
43
|
header {
|
|
38
44
|
display: flex;
|
|
39
|
-
align-items:
|
|
45
|
+
align-items: center;
|
|
40
46
|
justify-content: space-between;
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
height: 46px;
|
|
48
|
+
padding: 0 16px;
|
|
49
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.14);
|
|
43
50
|
}
|
|
44
|
-
header .slug { font-weight:
|
|
45
|
-
header .mode { color: #
|
|
51
|
+
header .slug { font-weight: 500; }
|
|
52
|
+
header .mode { color: #a1a1a1; font-size: 13px; }
|
|
46
53
|
iframe {
|
|
47
54
|
flex: 1;
|
|
48
55
|
width: 100%;
|
|
49
56
|
border: none;
|
|
50
57
|
/* The app's declared background from driggsby.json, painted while
|
|
51
58
|
the app loads — the same surface the production host shows. */
|
|
52
|
-
background: ${background ?? "#
|
|
59
|
+
background: ${background ?? "#000000"};
|
|
53
60
|
}
|
|
54
61
|
</style>
|
|
55
62
|
</head>
|
|
@@ -9,7 +9,8 @@ import { join } from "node:path";
|
|
|
9
9
|
import { slugProblem } from "@driggsby/deploy";
|
|
10
10
|
import { CliError } from "../cli-error.js";
|
|
11
11
|
import { quotedForTerminal, wrapProse } from "../terminal-text.js";
|
|
12
|
-
import {
|
|
12
|
+
import { STYLES_CSS } from "./template-styles.js";
|
|
13
|
+
import { APP_JS, driggsbyJsonText, indexHtml } from "./templates.js";
|
|
13
14
|
// Argv and prompt answers echo back inside quotes with this cap; a slug can
|
|
14
15
|
// never be longer than 63 characters, so the cap only trims hostile input.
|
|
15
16
|
const MAX_SLUG_ECHO_CHARS = 80;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// The scaffold's styles.css. A deployed app lives inside Driggsby's dark
|
|
2
|
+
// chrome, so the page is dark by default and its values are Driggsby's
|
|
3
|
+
// own: the console's ground, card rung, hairline, ink ramp, radii, and
|
|
4
|
+
// type sizes, written out as plain colors because the app is its own
|
|
5
|
+
// origin and cannot read the console's stylesheet. --bg-app must stay
|
|
6
|
+
// equal to driggsby.json's "background" (init-command.test.ts pins the
|
|
7
|
+
// two together): Driggsby paints that color while the app loads.
|
|
8
|
+
export const STYLES_CSS = `/* Driggsby's console palette, so this page reads as part of the app it
|
|
9
|
+
opens inside. Change these together if you re-theme; keep --bg-app equal
|
|
10
|
+
to the "background" in driggsby.json. */
|
|
11
|
+
:root {
|
|
12
|
+
color-scheme: dark;
|
|
13
|
+
--bg-app: #000000;
|
|
14
|
+
--bg-card: #0a0a0a;
|
|
15
|
+
--bg-mute: #262626;
|
|
16
|
+
--hairline: rgba(255, 255, 255, 0.14);
|
|
17
|
+
--text-primary: #fafafa;
|
|
18
|
+
--text-secondary: #c4c4c4;
|
|
19
|
+
--text-tertiary: #a1a1a1;
|
|
20
|
+
--radius-card: 8px;
|
|
21
|
+
--radius-bar: 4px;
|
|
22
|
+
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
* {
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
background: var(--bg-app);
|
|
32
|
+
color: var(--text-primary);
|
|
33
|
+
font-family: var(--font-sans);
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
line-height: 21px;
|
|
36
|
+
-webkit-font-smoothing: antialiased;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
main {
|
|
40
|
+
max-width: 800px;
|
|
41
|
+
margin: 0 auto;
|
|
42
|
+
padding: 24px 24px 64px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
h1 {
|
|
46
|
+
margin: 0;
|
|
47
|
+
font-size: 24px;
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
line-height: 36px;
|
|
50
|
+
letter-spacing: -0.02em;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
h2 {
|
|
54
|
+
margin: 24px 0 12px;
|
|
55
|
+
font-size: 14px;
|
|
56
|
+
font-weight: 500;
|
|
57
|
+
line-height: 21px;
|
|
58
|
+
letter-spacing: -0.02em;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* One card, four cells; the 1px gap over the card's own hairline color
|
|
62
|
+
draws the divider between cells however the grid wraps. */
|
|
63
|
+
.stat-grid {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
66
|
+
gap: 1px;
|
|
67
|
+
margin-top: 24px;
|
|
68
|
+
border: 1px solid var(--hairline);
|
|
69
|
+
border-radius: var(--radius-card);
|
|
70
|
+
background: var(--hairline);
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@media (max-width: 640px) {
|
|
75
|
+
.stat-grid {
|
|
76
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@media (max-width: 400px) {
|
|
81
|
+
.stat-grid {
|
|
82
|
+
grid-template-columns: minmax(0, 1fr);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.stat {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
padding: 16px;
|
|
90
|
+
background: var(--bg-card);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.stat-label {
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
line-height: 20px;
|
|
96
|
+
color: var(--text-tertiary);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.stat-value {
|
|
100
|
+
margin-top: 4px;
|
|
101
|
+
font-size: 20px;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
line-height: 28px;
|
|
104
|
+
letter-spacing: -0.02em;
|
|
105
|
+
font-variant-numeric: tabular-nums;
|
|
106
|
+
/* A currency string has no natural break, and a clipped balance is a
|
|
107
|
+
wrong number. If a value ever outgrows its cell, it wraps. */
|
|
108
|
+
overflow-wrap: anywhere;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#accounts {
|
|
112
|
+
border: 1px solid var(--hairline);
|
|
113
|
+
border-radius: var(--radius-card);
|
|
114
|
+
background: var(--bg-card);
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.account-row {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: 14px;
|
|
122
|
+
padding: 14px 16px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.account-row + .account-row {
|
|
126
|
+
border-top: 1px solid var(--hairline);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* A list with nothing in it says so, in the row's own inset. */
|
|
130
|
+
.empty-row {
|
|
131
|
+
padding: 14px 16px;
|
|
132
|
+
color: var(--text-secondary);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* The institution's initial on a round pill, as the console draws it. */
|
|
136
|
+
.account-glyph {
|
|
137
|
+
flex: none;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
width: 32px;
|
|
142
|
+
height: 32px;
|
|
143
|
+
border-radius: 50%;
|
|
144
|
+
background: var(--bg-mute);
|
|
145
|
+
color: var(--text-secondary);
|
|
146
|
+
font-size: 16px;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.account-names {
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
flex: 1;
|
|
154
|
+
min-width: 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.account-name {
|
|
158
|
+
font-weight: 500;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
text-overflow: ellipsis;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.account-institution {
|
|
165
|
+
/* Holds its line even when empty, so every row measures the same as
|
|
166
|
+
the skeleton row it replaces. */
|
|
167
|
+
min-height: 21px;
|
|
168
|
+
color: var(--text-secondary);
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
text-overflow: ellipsis;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.account-balance {
|
|
175
|
+
text-align: right;
|
|
176
|
+
font-variant-numeric: tabular-nums;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* The skeleton ships in the HTML so the first paint is the final layout as
|
|
180
|
+
muted bars; the first render replaces it and fades in (see app.js). Each
|
|
181
|
+
bar's margins pad it to exactly the height of the text it stands in for,
|
|
182
|
+
and the columns holding stacked bars are flex so those margins never
|
|
183
|
+
collapse — the skeleton page and the data page measure identical, and
|
|
184
|
+
nothing shifts when data lands. */
|
|
185
|
+
.skeleton {
|
|
186
|
+
border-radius: var(--radius-bar);
|
|
187
|
+
background: var(--bg-mute);
|
|
188
|
+
animation: skeleton-pulse 1.6s ease-in-out infinite;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.skeleton-label {
|
|
192
|
+
width: 64px;
|
|
193
|
+
height: 12px;
|
|
194
|
+
margin: 4px 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.skeleton-value {
|
|
198
|
+
width: 96px;
|
|
199
|
+
height: 20px;
|
|
200
|
+
margin: 8px 0 4px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.skeleton-glyph {
|
|
204
|
+
flex: none;
|
|
205
|
+
width: 32px;
|
|
206
|
+
height: 32px;
|
|
207
|
+
border-radius: 50%;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.skeleton-name {
|
|
211
|
+
width: 150px;
|
|
212
|
+
height: 14px;
|
|
213
|
+
margin: 3.5px 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.skeleton-institution {
|
|
217
|
+
width: 110px;
|
|
218
|
+
height: 12px;
|
|
219
|
+
margin: 4.5px 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.skeleton-balance {
|
|
223
|
+
width: 72px;
|
|
224
|
+
height: 14px;
|
|
225
|
+
margin: 3.5px 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@keyframes skeleton-pulse {
|
|
229
|
+
0%, 100% { opacity: 1; }
|
|
230
|
+
50% { opacity: 0.55; }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.fade-in {
|
|
234
|
+
animation: fade-in 200ms ease-out;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@keyframes fade-in {
|
|
238
|
+
from { opacity: 0; }
|
|
239
|
+
to { opacity: 1; }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@media (prefers-reduced-motion: reduce) {
|
|
243
|
+
.skeleton,
|
|
244
|
+
.fade-in {
|
|
245
|
+
animation: none;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
`;
|
package/dist/init/templates.js
CHANGED
|
@@ -51,6 +51,7 @@ export function indexHtml(slug) {
|
|
|
51
51
|
<h2>Accounts</h2>
|
|
52
52
|
<div id="accounts" aria-busy="true">
|
|
53
53
|
<div class="account-row">
|
|
54
|
+
<div class="skeleton skeleton-glyph"></div>
|
|
54
55
|
<div class="account-names">
|
|
55
56
|
<div class="skeleton skeleton-name"></div>
|
|
56
57
|
<div class="skeleton skeleton-institution"></div>
|
|
@@ -58,6 +59,7 @@ export function indexHtml(slug) {
|
|
|
58
59
|
<div class="skeleton skeleton-balance"></div>
|
|
59
60
|
</div>
|
|
60
61
|
<div class="account-row">
|
|
62
|
+
<div class="skeleton skeleton-glyph"></div>
|
|
61
63
|
<div class="account-names">
|
|
62
64
|
<div class="skeleton skeleton-name"></div>
|
|
63
65
|
<div class="skeleton skeleton-institution"></div>
|
|
@@ -65,6 +67,7 @@ export function indexHtml(slug) {
|
|
|
65
67
|
<div class="skeleton skeleton-balance"></div>
|
|
66
68
|
</div>
|
|
67
69
|
<div class="account-row">
|
|
70
|
+
<div class="skeleton skeleton-glyph"></div>
|
|
68
71
|
<div class="account-names">
|
|
69
72
|
<div class="skeleton skeleton-name"></div>
|
|
70
73
|
<div class="skeleton skeleton-institution"></div>
|
|
@@ -162,10 +165,22 @@ function formatMoney(value) {
|
|
|
162
165
|
if (!value || typeof value.amount !== "string") return "—";
|
|
163
166
|
const number = Number(value.amount);
|
|
164
167
|
if (!Number.isFinite(number)) return "—";
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
const currency = value.currency_code === undefined ? "USD" : value.currency_code;
|
|
169
|
+
if (typeof currency !== "string") return "—";
|
|
170
|
+
try {
|
|
171
|
+
return new Intl.NumberFormat(undefined, {
|
|
172
|
+
style: "currency",
|
|
173
|
+
currency: currency
|
|
174
|
+
}).format(number);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// An unknown currency code renders as absent, never as a guess in
|
|
177
|
+
// the wrong currency — and never takes the rest of the page down.
|
|
178
|
+
return "—";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function asText(value) {
|
|
183
|
+
return typeof value === "string" ? value : "";
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
function element(tag, className, text) {
|
|
@@ -210,22 +225,41 @@ function renderOverview(result) {
|
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
function renderAccounts(result) {
|
|
213
|
-
const
|
|
228
|
+
const list = result && Array.isArray(result.linked_accounts) ? result.linked_accounts : [];
|
|
229
|
+
const accounts = list.filter((account) => account && typeof account === "object");
|
|
214
230
|
const container = document.getElementById("accounts");
|
|
215
231
|
container.replaceChildren();
|
|
232
|
+
if (accounts.length === 0) {
|
|
233
|
+
container.append(element("div", "empty-row", "No linked accounts yet."));
|
|
234
|
+
revealOnce(container);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
216
237
|
for (const account of accounts) {
|
|
217
238
|
const row = element("div", "account-row");
|
|
218
239
|
const names = element("div", "account-names");
|
|
219
|
-
|
|
240
|
+
// Only strings paint; a malformed field renders as absent, never as
|
|
241
|
+
// "[object Object]".
|
|
242
|
+
const displayName = asText(account.account_display_name);
|
|
243
|
+
const institution = asText(account.institution_name);
|
|
244
|
+
const mask = asText(account.account_mask_last4);
|
|
245
|
+
// The institution's initial on a round pill, the way Driggsby's own
|
|
246
|
+
// account rows draw it. Array.from keeps an emoji-leading name from
|
|
247
|
+
// breaking the render (it splits by whole character, not UTF-16 half).
|
|
248
|
+
const initialSource = (institution || displayName).trim();
|
|
249
|
+
const initial = (Array.from(initialSource)[0] || "?").toUpperCase();
|
|
250
|
+
const institutionLine = [institution, mask ? "····" + mask : ""].filter(Boolean).join(" ");
|
|
220
251
|
names.append(
|
|
221
|
-
element("div", "account-name",
|
|
222
|
-
element(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
252
|
+
element("div", "account-name", displayName || "Account"),
|
|
253
|
+
element("div", "account-institution", institutionLine)
|
|
254
|
+
);
|
|
255
|
+
const glyph = element("div", "account-glyph", initial);
|
|
256
|
+
// Decorative: the institution's name is read out on the next line.
|
|
257
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
258
|
+
row.append(
|
|
259
|
+
glyph,
|
|
260
|
+
names,
|
|
261
|
+
element("div", "account-balance", formatMoney(account.current_balance))
|
|
227
262
|
);
|
|
228
|
-
row.append(names, element("div", "account-balance", formatMoney(account.current_balance)));
|
|
229
263
|
container.append(row);
|
|
230
264
|
}
|
|
231
265
|
revealOnce(container);
|
|
@@ -258,148 +292,10 @@ if (window.driggsby) {
|
|
|
258
292
|
});
|
|
259
293
|
}
|
|
260
294
|
`;
|
|
261
|
-
export const STYLES_CSS = `:root {
|
|
262
|
-
--background: #ffffff;
|
|
263
|
-
--text: #1f2430;
|
|
264
|
-
--text-muted: #6d7280;
|
|
265
|
-
--hairline: #e6e8ec;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
* {
|
|
269
|
-
box-sizing: border-box;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
body {
|
|
273
|
-
margin: 0;
|
|
274
|
-
background: var(--background);
|
|
275
|
-
color: var(--text);
|
|
276
|
-
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
277
|
-
font-size: 15px;
|
|
278
|
-
line-height: 1.5;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
main {
|
|
282
|
-
max-width: 720px;
|
|
283
|
-
margin: 0 auto;
|
|
284
|
-
padding: 48px 24px 64px;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
h1 {
|
|
288
|
-
margin: 0;
|
|
289
|
-
font-size: 22px;
|
|
290
|
-
font-weight: 600;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
h2 {
|
|
294
|
-
margin: 40px 0 4px;
|
|
295
|
-
font-size: 15px;
|
|
296
|
-
font-weight: 600;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/* The skeleton ships in the HTML so the first paint is the final layout as
|
|
300
|
-
muted bars; the first render replaces it and fades in (see app.js). The
|
|
301
|
-
bar margins make each skeleton block exactly the height of the text it
|
|
302
|
-
stands in for, so nothing shifts when data lands. */
|
|
303
|
-
.skeleton {
|
|
304
|
-
border-radius: 4px;
|
|
305
|
-
background: var(--hairline);
|
|
306
|
-
animation: skeleton-pulse 1.6s ease-in-out infinite;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.skeleton-label {
|
|
310
|
-
width: 64px;
|
|
311
|
-
height: 12px;
|
|
312
|
-
margin: 3.75px 0 0;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.skeleton-value {
|
|
316
|
-
width: 96px;
|
|
317
|
-
height: 20px;
|
|
318
|
-
margin: 8.75px 0 5px;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.skeleton-name {
|
|
322
|
-
width: 150px;
|
|
323
|
-
height: 14px;
|
|
324
|
-
margin: 4px 0 8px;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.skeleton-institution {
|
|
328
|
-
width: 110px;
|
|
329
|
-
height: 12px;
|
|
330
|
-
margin: 4px 0;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.skeleton-balance {
|
|
334
|
-
width: 72px;
|
|
335
|
-
height: 14px;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
@keyframes skeleton-pulse {
|
|
339
|
-
0%, 100% { opacity: 1; }
|
|
340
|
-
50% { opacity: 0.55; }
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.fade-in {
|
|
344
|
-
animation: fade-in 200ms ease-out;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
@keyframes fade-in {
|
|
348
|
-
from { opacity: 0; }
|
|
349
|
-
to { opacity: 1; }
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
@media (prefers-reduced-motion: reduce) {
|
|
353
|
-
.skeleton,
|
|
354
|
-
.fade-in {
|
|
355
|
-
animation: none;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
.stat-grid {
|
|
360
|
-
display: grid;
|
|
361
|
-
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
362
|
-
gap: 20px;
|
|
363
|
-
margin-top: 32px;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
.stat-label {
|
|
367
|
-
font-size: 13px;
|
|
368
|
-
color: var(--text-muted);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.stat-value {
|
|
372
|
-
font-size: 20px;
|
|
373
|
-
font-weight: 600;
|
|
374
|
-
font-variant-numeric: tabular-nums;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.account-row {
|
|
378
|
-
display: flex;
|
|
379
|
-
align-items: center;
|
|
380
|
-
justify-content: space-between;
|
|
381
|
-
gap: 16px;
|
|
382
|
-
padding: 12px 0;
|
|
383
|
-
border-top: 1px solid var(--hairline);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.account-row:first-child {
|
|
387
|
-
border-top: none;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
.account-institution {
|
|
391
|
-
font-size: 13px;
|
|
392
|
-
color: var(--text-muted);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.account-balance {
|
|
396
|
-
font-variant-numeric: tabular-nums;
|
|
397
|
-
}
|
|
398
|
-
`;
|
|
399
295
|
// "background" is the template's own page background (styles.css
|
|
400
|
-
// --
|
|
296
|
+
// --bg-app): Driggsby paints it while the app loads, so the loading
|
|
401
297
|
// surface matches the app from the first frame. An agent that re-themes
|
|
402
298
|
// the app should keep this equal to the page's real background color.
|
|
403
299
|
export function driggsbyJsonText(slug) {
|
|
404
|
-
return `{\n "slug": ${JSON.stringify(slug)},\n "serve": ".",\n "background": "#
|
|
300
|
+
return `{\n "slug": ${JSON.stringify(slug)},\n "serve": ".",\n "background": "#000000"\n}\n`;
|
|
405
301
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driggsby",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Set up and build on Driggsby, the MCP server for personal finance.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=18"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@driggsby/deploy": "0.3.
|
|
32
|
-
"@driggsby/sdk": "0.3.
|
|
31
|
+
"@driggsby/deploy": "0.3.1",
|
|
32
|
+
"@driggsby/sdk": "0.3.1"
|
|
33
33
|
}
|
|
34
34
|
}
|