spendwise 1.0.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.
@@ -0,0 +1,294 @@
1
+ /*
2
+ * Built-in web server ("web access"): serves the same renderer to browsers on
3
+ * the LAN (or beyond, if the user arranges transport - Tailscale, reverse
4
+ * proxy), against the SAME live db the Electron window uses - one process
5
+ * owns db.json, so co-editing needs no file sync. Login uses the app
6
+ * password (settings.auth); sessions are HttpOnly cookies; failed logins are
7
+ * rate-limited per IP. Electron-free - the dev/scratch harnesses boot it too.
8
+ */
9
+ 'use strict';
10
+
11
+ import http from 'node:http';
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import crypto from 'node:crypto';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { verifyPassword } from './auth.js';
18
+
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const SRC_ROOT = path.join(__dirname, '..'); // serves /renderer/* below here
21
+
22
+ const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days, sliding
23
+ const MAX_FAILS = 5;
24
+ const LOCKOUT_MS = 15 * 60 * 1000;
25
+ const MAX_BODY = 64 * 1024 * 1024;
26
+
27
+ // A web client may co-edit and read - dialog-based ops and password
28
+ // management stay desktop-only.
29
+ const WEB_METHODS = new Set([
30
+ 'loadDb', 'rev', 'saveMonth', 'saveClosedMonth', 'closeMonth',
31
+ 'saveSettings', 'applyTagsEverywhere', 'listBackups', 'restoreBackup',
32
+ 'checkUpdate',
33
+ ]);
34
+
35
+ const MIME = {
36
+ '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
37
+ '.css': 'text/css; charset=utf-8', '.ico': 'image/x-icon',
38
+ '.svg': 'image/svg+xml', '.json': 'application/json',
39
+ '.png': 'image/png', '.webmanifest': 'application/manifest+json',
40
+ '.woff2': 'font/woff2', '.woff': 'font/woff', '.otf': 'font/otf',
41
+ };
42
+
43
+ const LOGIN_PAGE = `<!doctype html>
44
+ <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
45
+ <title>Spend Wise - sign in</title>
46
+ <link rel="icon" type="image/png" href="/renderer/assets/icon.ico">
47
+ <link rel="apple-touch-icon" href="/renderer/assets/icon.png">
48
+ <meta name="theme-color" content="#059669">
49
+ <style>
50
+ @font-face {
51
+ font-family: 'Roca';
52
+ src: url('/renderer/fonts/roca.woff2') format('woff2'),
53
+ url('/renderer/fonts/roca.woff') format('woff');
54
+ font-weight: 700 900;
55
+ font-style: normal;
56
+ font-display: swap;
57
+ }
58
+ body { font-family: system-ui, sans-serif; background: #f4f4f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
59
+ form { background: #fff; border: 1px solid #e4e4e7; border-radius: 12px; box-shadow: 0 10px 15px -3px rgb(0 0 0 / .07); padding: 2rem; width: 20rem; text-align: center; box-sizing: border-box; }
60
+ h1 { font-family: 'Roca', system-ui, sans-serif; font-weight: 900; color: #047857; font-size: 1.35rem; margin: 0 0 1rem; }
61
+ input { width: 100%; box-sizing: border-box; border: 1px solid #d4d4d8; border-radius: 6px; padding: .55rem .75rem; font-size: .9rem; text-align: center; outline: none; }
62
+ input:focus { border-color: #10b981; box-shadow: 0 0 0 1px #10b981; }
63
+ button { margin-top: .75rem; width: 100%; background: #059669; color: #fff; border: 0; border-radius: 6px; padding: .6rem; font-weight: 700; font-size: .9rem; cursor: pointer; }
64
+ button:hover { background: #10b981; }
65
+ p.err { color: #dc2626; font-size: .8rem; min-height: 1rem; margin: .6rem 0 0; }
66
+ </style></head><body>
67
+ <form id="f">
68
+ <h1>Spend Wise</h1>
69
+ <input id="pw" type="password" placeholder="App password" autofocus autocomplete="current-password">
70
+ <button type="submit">Sign in</button>
71
+ <p class="err" id="err"></p>
72
+ </form>
73
+ <script>
74
+ document.getElementById('f').addEventListener('submit', async (e) => {
75
+ e.preventDefault();
76
+ const res = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify({ password: document.getElementById('pw').value }) });
78
+ const out = await res.json();
79
+ if (out.ok) location.reload();
80
+ else { document.getElementById('err').textContent = out.error || 'Wrong password'; document.getElementById('pw').value = ''; }
81
+ });
82
+ </script></body></html>`;
83
+
84
+ const NO_PASSWORD_PAGE = `<!doctype html><html><head><meta charset="utf-8"><title>Spend Wise</title></head>
85
+ <body style="font-family: system-ui, sans-serif; text-align:center; padding-top:4rem; color:#3f3f46">
86
+ <h2 style="color:#047857">Spend Wise</h2>
87
+ <p>Web access needs an app password.<br>Set one in the desktop app under Settings → App password.</p>
88
+ </body></html>`;
89
+
90
+ /**
91
+ * opts.trustProxy - number of reverse proxies in front (true = 1, 0 = none).
92
+ * Required for correct per-client rate limiting behind a
93
+ * proxy; NEVER default it on: an untrusted client can
94
+ * forge X-Forwarded-For and evade the lockout.
95
+ * opts.secureCookie - force the Secure cookie flag (auto-detected from
96
+ * X-Forwarded-Proto when a proxy is trusted).
97
+ */
98
+ export function createWebServer (ctx, core, opts = {}) {
99
+ const sessions = new Map(); // token → { created, last }
100
+ const attempts = new Map(); // ip → { fails, lockedUntil }
101
+ // hops/secureCookie start from construction opts (the CLI passes them here)
102
+ // but can be re-applied at start() so the desktop app, which builds the
103
+ // server once at boot, can still configure them when web access is toggled.
104
+ const normHops = (tp) => tp === true ? 1 : Math.max(0, Number(tp) || 0);
105
+ let hops = normHops(opts.trustProxy);
106
+ let secureCookie = !!opts.secureCookie;
107
+ let server = null;
108
+ let port = null;
109
+
110
+ const hasPassword = () => !!(ctx.db.data && ctx.db.data.settings && ctx.db.data.settings.auth);
111
+
112
+ const parseCookies = (req) => Object.fromEntries((req.headers.cookie || '').split(';')
113
+ .map((c) => c.trim().split('=')).filter((kv) => kv.length === 2));
114
+
115
+ const sessionOf = (req) => {
116
+ const token = parseCookies(req).fin_sid;
117
+ const s = token && sessions.get(token);
118
+ if (!s) return null;
119
+ if (Date.now() - s.last > SESSION_TTL_MS) { sessions.delete(token); return null; }
120
+ s.last = Date.now();
121
+ return token;
122
+ };
123
+
124
+ /**
125
+ * With N trusted proxies, the (N)th entry from the RIGHT of
126
+ * X-Forwarded-For is the real client: each proxy appends the peer it
127
+ * received from, so anything further left is client-supplied and forgeable.
128
+ */
129
+ const clientIp = (req) => {
130
+ if (hops) {
131
+ const chain = String(req.headers['x-forwarded-for'] || '')
132
+ .split(',').map((s) => s.trim()).filter(Boolean);
133
+ if (chain.length) return chain[Math.max(0, chain.length - hops)];
134
+ }
135
+ return req.socket.remoteAddress || 'unknown';
136
+ };
137
+
138
+ const isHttps = (req) => secureCookie
139
+ || (hops > 0 && String(req.headers['x-forwarded-proto'] || '').split(',')[0].trim() === 'https');
140
+
141
+ const json = (res, status, obj) => {
142
+ res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
143
+ res.end(JSON.stringify(obj));
144
+ };
145
+
146
+ /** settings.auth (the scrypt hash) never leaves the process over HTTP. */
147
+ const stripAuth = (out) => {
148
+ if (out && out.data && out.data.settings) delete out.data.settings.auth;
149
+ return out;
150
+ };
151
+
152
+ async function readBody (req) {
153
+ let body = '';
154
+ for await (const chunk of req) {
155
+ body += chunk;
156
+ if (body.length > MAX_BODY) throw new Error('payload too large');
157
+ }
158
+ return body ? JSON.parse(body) : {};
159
+ }
160
+
161
+ async function handleLogin (req, res) {
162
+ const ip = clientIp(req);
163
+ const a = attempts.get(ip) || { fails: 0, lockedUntil: 0 };
164
+ if (Date.now() < a.lockedUntil) {
165
+ const mins = Math.ceil((a.lockedUntil - Date.now()) / 60000);
166
+ return json(res, 429, { ok: false, error: `Too many attempts - try again in ${mins} min` });
167
+ }
168
+ const { password } = await readBody(req);
169
+ await new Promise((r) => setTimeout(r, 250)); // flat cost per attempt
170
+ if (!verifyPassword(password, ctx.db.data.settings.auth)) {
171
+ a.fails += 1;
172
+ if (a.fails >= MAX_FAILS) { a.lockedUntil = Date.now() + LOCKOUT_MS; a.fails = 0; }
173
+ attempts.set(ip, a);
174
+ return json(res, 403, { ok: false, error: 'Wrong password' });
175
+ }
176
+ attempts.delete(ip);
177
+ const token = crypto.randomBytes(32).toString('hex');
178
+ sessions.set(token, { created: Date.now(), last: Date.now() });
179
+ res.writeHead(200, {
180
+ 'Content-Type': 'application/json',
181
+ 'Set-Cookie': `fin_sid=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${SESSION_TTL_MS / 1000}`
182
+ + (isHttps(req) ? '; Secure' : ''),
183
+ 'Cache-Control': 'no-store',
184
+ });
185
+ res.end(JSON.stringify({ ok: true }));
186
+ }
187
+
188
+ async function handleRequest (req, res) {
189
+ const u = new URL(req.url, 'http://localhost');
190
+
191
+ if (!hasPassword()) {
192
+ res.writeHead(503, { 'Content-Type': MIME['.html'], 'Cache-Control': 'no-store' });
193
+ return res.end(NO_PASSWORD_PAGE);
194
+ }
195
+
196
+ if (req.method === 'POST' && u.pathname === '/api/login') return handleLogin(req, res);
197
+
198
+ // icons and fonts are public: the login page needs its favicon and
199
+ // display face, and a browser fetching either without the cookie would
200
+ // otherwise get HTML back
201
+ const isPublicAsset = req.method === 'GET'
202
+ && (u.pathname.startsWith('/renderer/assets/') || u.pathname.startsWith('/renderer/fonts/'));
203
+
204
+ const token = sessionOf(req);
205
+ if (!token && !isPublicAsset) {
206
+ if (req.method === 'POST' && u.pathname.startsWith('/api/')) {
207
+ return json(res, 401, { ok: false, error: 'unauthorized' });
208
+ }
209
+ res.writeHead(200, { 'Content-Type': MIME['.html'], 'Cache-Control': 'no-store' });
210
+ return res.end(LOGIN_PAGE);
211
+ }
212
+
213
+ if (req.method === 'POST' && u.pathname === '/api/logout') {
214
+ sessions.delete(token);
215
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': 'fin_sid=; Path=/; Max-Age=0' });
216
+ return res.end('{"ok":true}');
217
+ }
218
+
219
+ if (req.method === 'POST' && u.pathname.startsWith('/api/')) {
220
+ const method = u.pathname.slice(5);
221
+ if (!WEB_METHODS.has(method)) return json(res, 200, { ok: false, error: 'This action is only available in the desktop app.' });
222
+ try {
223
+ const payload = await readBody(req);
224
+ const out = stripAuth(await core[method](payload));
225
+ return json(res, 200, { ok: true, ...out });
226
+ } catch (err) {
227
+ return json(res, 200, { ok: false, error: String(err && err.message || err) });
228
+ }
229
+ }
230
+
231
+ // ------------------------------------------------------- static files
232
+ const pathname = u.pathname === '/' ? '/renderer/index.html' : u.pathname;
233
+ const file = path.join(SRC_ROOT, path.normalize(pathname));
234
+ if (!file.startsWith(SRC_ROOT) || !fs.existsSync(file) || !fs.statSync(file).isFile()) {
235
+ res.writeHead(404); return res.end('not found');
236
+ }
237
+ if (pathname === '/renderer/index.html') {
238
+ let html = fs.readFileSync(file, 'utf8');
239
+ html = html.replace("connect-src 'none'", "connect-src 'self'");
240
+ html = html.replace('<link rel="stylesheet"', '<base href="/renderer/">\n <link rel="stylesheet"');
241
+ html = html.replace('<script src="js/helpers.js"></script>',
242
+ '<script src="js/web-api.js"></script>\n <script src="js/helpers.js"></script>');
243
+ res.writeHead(200, { 'Content-Type': MIME['.html'], 'Cache-Control': 'no-store' });
244
+ return res.end(html);
245
+ }
246
+ const type = pathname.endsWith('/manifest.json')
247
+ ? 'application/manifest+json'
248
+ : MIME[path.extname(file)] || 'application/octet-stream';
249
+ res.writeHead(200, { 'Content-Type': type });
250
+ res.end(fs.readFileSync(file));
251
+ }
252
+
253
+ return {
254
+ start (p, host = opts.host || '0.0.0.0', runtime = {}) {
255
+ // desktop path re-applies these each time web access is turned on
256
+ if (runtime.trustProxy !== undefined) hops = normHops(runtime.trustProxy);
257
+ if (runtime.secureCookie !== undefined) secureCookie = !!runtime.secureCookie;
258
+ if (server) return Promise.resolve(port);
259
+ return new Promise((resolve, reject) => {
260
+ server = http.createServer((req, res) => {
261
+ handleRequest(req, res).catch((err) => json(res, 500, { ok: false, error: String(err && err.message || err) }));
262
+ });
263
+ server.on('error', (err) => { server = null; reject(err); });
264
+ server.listen(p, host, () => { port = p; resolve(p); });
265
+ });
266
+ },
267
+
268
+ /** Test seam: how this server resolves the client IP for rate limiting. */
269
+ _clientIp: clientIp,
270
+ stop () {
271
+ return new Promise((resolve) => {
272
+ if (!server) return resolve();
273
+ const s = server;
274
+ server = null;
275
+ port = null;
276
+ // sever keep-alive connections or close() waits on them forever
277
+ if (s.closeAllConnections) s.closeAllConnections();
278
+ s.close(() => resolve());
279
+ });
280
+ },
281
+ status () {
282
+ const urls = [];
283
+ if (server) {
284
+ for (const list of Object.values(os.networkInterfaces())) {
285
+ for (const ni of list || []) {
286
+ if (ni.family === 'IPv4' && !ni.internal) urls.push(`http://${ni.address}:${port}`);
287
+ }
288
+ }
289
+ urls.unshift(`http://localhost:${port}`);
290
+ }
291
+ return { running: !!server, port, urls, sessions: sessions.size, trustProxy: hops, secureCookie };
292
+ },
293
+ };
294
+ }
@@ -0,0 +1,27 @@
1
+ const { contextBridge, ipcRenderer } = require('electron');
2
+
3
+ const invoke = (channel) => (payload) => ipcRenderer.invoke(channel, payload);
4
+
5
+ contextBridge.exposeInMainWorld('api', {
6
+ loadDb: invoke('db:load'),
7
+ rev: invoke('db:rev'),
8
+ authHas: invoke('auth:has'),
9
+ authVerify: invoke('auth:verify'),
10
+ authSet: invoke('auth:set'),
11
+ webStatus: invoke('web:status'),
12
+ webSet: invoke('web:set'),
13
+ saveMonth: invoke('db:saveMonth'),
14
+ saveClosedMonth: invoke('db:saveClosedMonth'),
15
+ closeMonth: invoke('db:closeMonth'),
16
+ saveSettings: invoke('db:saveSettings'),
17
+ applyTagsEverywhere: invoke('db:apply-tags'),
18
+ listBackups: invoke('backups:list'),
19
+ restoreBackup: invoke('backups:restore'),
20
+ openBackupsFolder: invoke('backups:open-folder'),
21
+ exportDb: invoke('db:export'),
22
+ changeDbLocation: invoke('db:change-location'),
23
+ migrateScan: invoke('db:migrate-scan'),
24
+ migrateLegacy: invoke('db:migrate-legacy'),
25
+ checkUpdate: invoke('update:check'),
26
+ openReleases: invoke('update:open'),
27
+ });
Binary file
Binary file
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-sky-500:oklch(68.5% .169 237.323);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--tracking-tight:-.025em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--font-brand:"Roca", ui-sans-serif, system-ui, sans-serif;--color-zinc-350:#b9b9c1}@supports (color:color-mix(in lab, red, red)){:root,:host{--color-zinc-350:color-mix(in oklab, var(--color-zinc-300), var(--color-zinc-400))}}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.-top-\[3px\]{top:-3px}.top-0{top:0}.top-0\.5{top:calc(var(--spacing) * .5)}.top-1\/2{top:50%}.top-14{top:calc(var(--spacing) * 14)}.top-\[0\.15em\]{top:.15em}.top-\[0\.125em\]{top:.125em}.right-0\.5{right:calc(var(--spacing) * .5)}.right-\[3px\]{right:3px}.bottom-7{bottom:calc(var(--spacing) * 7)}.bottom-\[3px\]{bottom:3px}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-\[3px\]{left:3px}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[60\]{z-index:60}.z-\[100\]{z-index:100}.order-1{order:1}.order-2{order:2}.order-3{order:3}.m-0{margin:0}.-mx-2{margin-inline:calc(var(--spacing) * -2)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mr-4{margin-right:calc(var(--spacing) * 4)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-6{margin-left:calc(var(--spacing) * 6)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.h-0{height:0}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-7{height:calc(var(--spacing) * 7)}.h-10{height:calc(var(--spacing) * 10)}.h-\[3px\]{height:3px}.h-full{height:100%}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[88vh\]{max-height:88vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-44{width:calc(var(--spacing) * 44)}.w-56{width:calc(var(--spacing) * 56)}.w-80{width:calc(var(--spacing) * 80)}.w-\[calc\(100\%-24px\)\]{width:calc(100% - 24px)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-screen-2xl{max-width:var(--breakpoint-2xl)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:0}.min-w-28{min-width:calc(var(--spacing) * 28)}.min-w-40{min-width:calc(var(--spacing) * 40)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.basis-full{flex-basis:100%}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:0;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3{--tw-translate-y:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-500{border-color:var(--color-amber-500)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-emerald-600{border-color:var(--color-emerald-600)}.border-red-200{border-color:var(--color-red-200)}.border-red-400{border-color:var(--color-red-400)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-sky-500{background-color:var(--color-sky-500)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-white{background-color:var(--color-white)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-900\/50{background-color:#18181b80}@supports (color:color-mix(in lab, red, red)){.bg-zinc-900\/50{background-color:color-mix(in oklab, var(--color-zinc-900) 50%, transparent)}}.p-0{padding:0}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-7{padding:calc(var(--spacing) * 7)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-px{padding-block:1px}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pr-1{padding-right:var(--spacing)}.pr-20{padding-right:calc(var(--spacing) * 20)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-1{padding-left:var(--spacing)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-9{padding-left:calc(var(--spacing) * 9)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-brand{font-family:var(--font-brand)}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-900{color:var(--color-amber-900)}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-350{color:var(--color-zinc-350)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-emerald-600{accent-color:var(--color-emerald-600)}.accent-violet-600{accent-color:var(--color-violet-600)}.opacity-0{opacity:0}.opacity-60{opacity:.6}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-amber-400{--tw-ring-color:var(--color-amber-400)}.ring-white{--tw-ring-color:var(--color-white)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale{--tw-grayscale:grayscale(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}.marker\:text-zinc-300 ::marker{color:var(--color-zinc-300)}.marker\:text-zinc-300::marker{color:var(--color-zinc-300)}.marker\:text-zinc-300 ::-webkit-details-marker{color:var(--color-zinc-300)}.marker\:text-zinc-300::-webkit-details-marker{color:var(--color-zinc-300)}@media (hover:hover){.hover\:border-amber-400:hover{border-color:var(--color-amber-400)}.hover\:border-amber-500:hover{border-color:var(--color-amber-500)}.hover\:border-emerald-400:hover{border-color:var(--color-emerald-400)}.hover\:border-emerald-500:hover{border-color:var(--color-emerald-500)}.hover\:border-zinc-500:hover{border-color:var(--color-zinc-500)}.hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.hover\:bg-amber-400:hover{background-color:var(--color-amber-400)}.hover\:bg-emerald-100:hover{background-color:var(--color-emerald-100)}.hover\:bg-emerald-500:hover{background-color:var(--color-emerald-500)}.hover\:bg-emerald-700:hover{background-color:var(--color-emerald-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-100:hover{background-color:var(--color-red-100)}.hover\:bg-red-500:hover{background-color:var(--color-red-500)}.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.hover\:bg-zinc-300:hover{background-color:var(--color-zinc-300)}.hover\:text-amber-700:hover{color:var(--color-amber-700)}.hover\:text-emerald-600:hover{color:var(--color-emerald-600)}.hover\:text-emerald-700:hover{color:var(--color-emerald-700)}.hover\:text-emerald-900:hover{color:var(--color-emerald-900)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-zinc-500:hover{color:var(--color-zinc-500)}.hover\:text-zinc-600:hover{color:var(--color-zinc-600)}.hover\:text-zinc-700:hover{color:var(--color-zinc-700)}}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-emerald-400:focus{--tw-ring-color:var(--color-emerald-400)}.focus\:ring-emerald-500:focus{--tw-ring-color:var(--color-emerald-500)}.focus\:ring-red-400:focus{--tw-ring-color:var(--color-red-400)}.focus\:ring-violet-400:focus{--tw-ring-color:var(--color-violet-400)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:bg-zinc-200:active{background-color:var(--color-zinc-200)}.disabled\:cursor-default:disabled{cursor:default}.disabled\:border-zinc-200:disabled{border-color:var(--color-zinc-200)}.disabled\:bg-zinc-200:disabled{background-color:var(--color-zinc-200)}.disabled\:text-zinc-300:disabled{color:var(--color-zinc-300)}.disabled\:text-zinc-400:disabled{color:var(--color-zinc-400)}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media not all and (min-width:40rem){.max-sm\:order-1{order:1}.max-sm\:order-2{order:2}.max-sm\:order-3{order:3}.max-sm\:order-4{order:4}.max-sm\:order-5{order:5}.max-sm\:order-6{order:6}.max-sm\:order-7{order:7}.max-sm\:mb-5{margin-bottom:calc(var(--spacing) * 5)}.max-sm\:flex{display:flex}.max-sm\:hidden{display:none}.max-sm\:w-20{width:calc(var(--spacing) * 20)}.max-sm\:w-auto{width:auto}.max-sm\:items-center{align-items:center}.max-sm\:justify-between{justify-content:space-between}.max-sm\:gap-1\.5{gap:calc(var(--spacing) * 1.5)}}@media (min-width:40rem){.sm\:block{display:block}.sm\:contents{display:contents}.sm\:hidden{display:none}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:w-\[198px\]{width:198px}.sm\:flex-nowrap{flex-wrap:nowrap}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:py-6{padding-block:calc(var(--spacing) * 6)}}@media (min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:80rem){.xl\:sticky{position:sticky}.xl\:top-20{top:calc(var(--spacing) * 20)}.xl\:order-none{order:0}.xl\:block{display:block}.xl\:w-96{width:calc(var(--spacing) * 96)}.xl\:w-auto{width:auto}.xl\:shrink-0{flex-shrink:0}.xl\:flex-row{flex-direction:row}:where(.xl\:space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}}}@font-face{font-family:Roca;src:url(../fonts/roca.woff2)format("woff2"),url(../fonts/roca.woff)format("woff");font-weight:700 900;font-style:normal;font-display:swap}[x-cloak]{display:none!important}@keyframes shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-5px)}20%,40%,60%,80%{transform:translate(5px)}}.animate-shake{animation:.5s shake}body{-webkit-user-select:none;user-select:none}input,textarea,[contenteditable]{-webkit-user-select:text;user-select:text}button:disabled{pointer-events:none}input:disabled,select:disabled{cursor:default}input[type=number]::-webkit-outer-spin-button{opacity:.35}input[type=number]::-webkit-inner-spin-button{opacity:.35}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--color-zinc-300);border-radius:5px}::-webkit-scrollbar-thumb:hover{background:var(--color-zinc-400)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
Binary file
Binary file
Binary file