@kky42/pievo-server 2.4.13 → 3.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.
Files changed (89) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{actionUi-D8m1FDHO.js → actionUi-BZrLpgHT.js} +1 -1
  3. package/.output/public/assets/app-DXLuQNb9.css +2 -0
  4. package/.output/public/assets/index-Di_wpAtL.js +10 -0
  5. package/.output/public/assets/loops._loopId-X3ClNPMY.js +67 -0
  6. package/.output/public/assets/loops._loopId_.runs._runId-ByU2BSyF.js +3 -0
  7. package/.output/public/assets/preload-helper-hRTYB_ju.js +3 -0
  8. package/.output/public/assets/{routes-79D939J4.js → routes-BIOJZsig.js} +1 -1
  9. package/.output/public/assets/routes-K8D5uBiV.js +1 -0
  10. package/.output/server/_chunks/ssr-renderer.mjs +1 -1
  11. package/.output/server/_libs/{drizzle-orm+postgres.mjs → drizzle-orm.mjs} +93 -2218
  12. package/.output/server/_ssr/{MachinesModal-DBIP648Z.mjs → MachinesModal-CmVMec6B.mjs} +52 -46
  13. package/.output/server/_ssr/{actionUi-C_WswNb1.mjs → actionUi-DE-Pb2tv.mjs} +4 -5
  14. package/.output/server/_ssr/{artifactFiles-BvVmixrs.mjs → artifactFiles-Di1xDKvX.mjs} +5 -7
  15. package/.output/server/_ssr/{artifactKind-DRCbYHZU.mjs → artifactKind-DqpmtxDU.mjs} +3 -9
  16. package/.output/server/_ssr/{boot-U-CFI65B.mjs → boot-CYRi-wez.mjs} +531 -1976
  17. package/.output/server/_ssr/lifecycleUi-BsbUsi5K.mjs +106 -0
  18. package/.output/server/_ssr/{loopApi-C4nN0msL.mjs → loopApi-Bez1rHV0.mjs} +50 -65
  19. package/.output/server/_ssr/{loopProjection-DMSNHux6.mjs → loopProjection-esCjyZJg.mjs} +4 -20
  20. package/.output/server/_ssr/{loops._loopId-Q4pRYPip.mjs → loops._loopId-KUezNY69.mjs} +133 -24
  21. package/.output/server/_ssr/{loops._loopId_.runs._runId-DNXVmCt5.mjs → loops._loopId_.runs._runId-BFP9oMrp.mjs} +2 -7
  22. package/.output/server/_ssr/{loops._loopId_.runs._runId-ty3AhkOi.mjs → loops._loopId_.runs._runId-DOkUEesG.mjs} +18 -33
  23. package/.output/server/_ssr/{machineFns-CkNms3zA.mjs → machineFns-CniGcY8s.mjs} +13 -49
  24. package/.output/server/_ssr/networkAccess-CIlK3ION.mjs +30 -0
  25. package/.output/server/_ssr/router-BFIH2UAE.mjs +362 -0
  26. package/.output/server/_ssr/routes-BSVBANI3.mjs +12 -0
  27. package/.output/server/_ssr/{routes-BaanDO41.mjs → routes-DnE7XDe_.mjs} +242 -77
  28. package/.output/server/_ssr/{routes-B3fulW5I.mjs → routes-ngpX7h8D.mjs} +2 -4
  29. package/.output/server/_ssr/ssr.mjs +25 -34
  30. package/.output/server/_ssr/start-B5_9Z5W-.mjs +35 -0
  31. package/.output/server/{_tanstack-start-manifest_v-DkmxsHDK.mjs → _tanstack-start-manifest_v-DnwAupbZ.mjs} +8 -13
  32. package/.output/server/drizzle/0000_baseline.sql +6 -97
  33. package/.output/server/drizzle/meta/_journal.json +2 -16
  34. package/.output/server/index.mjs +43 -50
  35. package/drizzle/0000_baseline.sql +6 -97
  36. package/drizzle/meta/_journal.json +2 -16
  37. package/package.json +4 -8
  38. package/scripts/pievo-server.mjs +49 -47
  39. package/scripts/server-cli-lib.mjs +21 -12
  40. package/scripts/server-network.mjs +139 -0
  41. package/.output/public/assets/SignIn-CDCdDD1a.js +0 -1
  42. package/.output/public/assets/app-PPhgN9iN.css +0 -2
  43. package/.output/public/assets/index-c0XPWXuz.js +0 -10
  44. package/.output/public/assets/loops._loopId-D7i_BGiE.js +0 -67
  45. package/.output/public/assets/loops._loopId_.runs._runId-CksSrEY7.js +0 -3
  46. package/.output/public/assets/preload-helper-Zcmr31XG.js +0 -5
  47. package/.output/public/assets/routes-BYgl3Tmv.js +0 -1
  48. package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -419
  49. package/.output/server/_libs/@aws-sdk/checksums+[...].mjs +0 -10732
  50. package/.output/server/_libs/@aws-sdk/client-s3+[...].mjs +0 -9475
  51. package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -137
  52. package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -440
  53. package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -62
  54. package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -818
  55. package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -59
  56. package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -342
  57. package/.output/server/_libs/aws-crypto__crc32c.mjs +0 -314
  58. package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -2671
  59. package/.output/server/_ssr/SignIn-Bp3uWaCQ.mjs +0 -53
  60. package/.output/server/_ssr/artifacts-CzDJLaa6.mjs +0 -2
  61. package/.output/server/_ssr/auth-BV0MxVdo.mjs +0 -38355
  62. package/.output/server/_ssr/auth-client-RTpMpT4L.mjs +0 -1070
  63. package/.output/server/_ssr/boot-CsLXrLEa.mjs +0 -2
  64. package/.output/server/_ssr/bun-sqlite-dialect-BW9W1_Ps-D9KUBJ-L.mjs +0 -162
  65. package/.output/server/_ssr/core-RrmWoKne.mjs +0 -5
  66. package/.output/server/_ssr/d1-sqlite-dialect-BLC8LXE6-CVHRzH1n.mjs +0 -112
  67. package/.output/server/_ssr/db-BTX-xORB.mjs +0 -2
  68. package/.output/server/_ssr/dist-D7UDVZ5G.mjs +0 -342
  69. package/.output/server/_ssr/factory-DYFx1fg-.mjs +0 -6903
  70. package/.output/server/_ssr/kysely-adapter-CwdB7yBh.mjs +0 -2
  71. package/.output/server/_ssr/kysely-migration-tables-JkVUjPF_-bPAX9LR_.mjs +0 -19
  72. package/.output/server/_ssr/logger-CwiKSMp6.mjs +0 -2
  73. package/.output/server/_ssr/node-sqlite-dialect-B_g92oFK.mjs +0 -162
  74. package/.output/server/_ssr/router-DCNZ2xLm.mjs +0 -543
  75. package/.output/server/_ssr/routes-8JSF7bX_.mjs +0 -27
  76. package/.output/server/_ssr/runDiff-D7CxAgYL.mjs +0 -2
  77. package/.output/server/_ssr/start-4ifArtKx.mjs +0 -4
  78. package/.output/server/_ssr/store-DSNbXxqz.mjs +0 -2
  79. package/.output/server/_ssr/tokens-DzUv7CCp.mjs +0 -2
  80. package/.output/server/drizzle/0001_add_pi_agent.sql +0 -9
  81. package/.output/server/drizzle/0002_remove_teams.sql +0 -15
  82. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  83. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  84. package/.output/server/node_modules/tslib/package.json +0 -47
  85. package/.output/server/node_modules/tslib/tslib.js +0 -484
  86. package/.output/server/package.json +0 -9
  87. package/drizzle/0001_add_pi_agent.sql +0 -9
  88. package/drizzle/0002_remove_teams.sql +0 -15
  89. package/scripts/prestart.mjs +0 -66
@@ -24,6 +24,7 @@ import {
24
24
  withLaunchNonce,
25
25
  writePidRecord,
26
26
  } from "./server-cli-lib.mjs";
27
+ import { currentHttpsUrl, disableHttps, enableHttps, reachableUrls, tailscaleInfo } from "./server-network.mjs";
27
28
 
28
29
  const scriptFile = fileURLToPath(import.meta.url);
29
30
  const packageRoot = path.resolve(path.dirname(scriptFile), "..");
@@ -37,7 +38,6 @@ const LOCK_HANDOFF_TIMEOUT_MS = 5_000;
37
38
  function installShutdown(plan, expected) {
38
39
  let stopping = false;
39
40
  let stopSignal;
40
- let prestartChild;
41
41
  let nitroLoaded = false;
42
42
  let replayedForNitro = false;
43
43
 
@@ -55,9 +55,6 @@ function installShutdown(plan, expected) {
55
55
  stopSignal = signal;
56
56
  console.log(`[pievo-server] received ${signal}; shutting down`);
57
57
  shutdownApplication();
58
- if (prestartChild?.exitCode === null && prestartChild?.signalCode === null) {
59
- try { prestartChild.kill(signal); } catch {}
60
- }
61
58
  }
62
59
  };
63
60
 
@@ -69,8 +66,6 @@ function installShutdown(plan, expected) {
69
66
 
70
67
  return {
71
68
  get stopping() { return stopping; },
72
- trackPrestart(child) { prestartChild = child; },
73
- clearPrestart(child) { if (prestartChild === child) prestartChild = undefined; },
74
69
  nitroLoaded() {
75
70
  nitroLoaded = true;
76
71
  // If the first signal arrived while Nitro was importing, its graceful HTTP
@@ -94,25 +89,32 @@ function installShutdown(plan, expected) {
94
89
  };
95
90
  }
96
91
 
97
- async function runPrestart(plan, shutdown) {
98
- const child = spawn(process.execPath, [path.join(packageRoot, "scripts", "prestart.mjs")], {
99
- cwd: packageRoot,
100
- env: plan.env,
101
- stdio: "inherit",
102
- });
103
- shutdown.trackPrestart(child);
104
- try {
105
- const result = await new Promise((resolve, reject) => {
106
- child.once("error", reject);
107
- child.once("exit", (code, signal) => resolve({ code, signal }));
108
- });
109
- if (shutdown.stopping) throw new Error("startup interrupted by shutdown signal");
110
- if (result.code !== 0) {
111
- throw new Error(`migration prestart failed (${result.signal ? `signal ${result.signal}` : `exit ${result.code ?? "unknown"}`})`);
112
- }
113
- } finally {
114
- shutdown.clearPrestart(child);
92
+ /** Publish Tailscale HTTPS (when enabled) and pass the best connect URL to the server. */
93
+ function publishNetwork(plan) {
94
+ const tailscale = tailscaleInfo();
95
+ let https;
96
+ let httpsNote;
97
+ if (plan.https && tailscale) {
98
+ const result = enableHttps(tailscale, plan.port);
99
+ if (result.ok) https = result.url;
100
+ else httpsNote = result.reason;
115
101
  }
102
+ const urls = reachableUrls({ host: plan.host, port: plan.port, https, tailscale });
103
+ return { urls, httpsNote, plan: { ...plan, env: { ...plan.env, PIEVO_PUBLIC_URL: urls.connect } } };
104
+ }
105
+
106
+ function retractHttps(record) {
107
+ if (record?.https === false) return;
108
+ const tailscale = tailscaleInfo();
109
+ if (tailscale) disableHttps(tailscale, record.port);
110
+ }
111
+
112
+ function printUrls(urls, httpsNote) {
113
+ console.log(` local ${urls.local}`);
114
+ if (urls.lan.length) console.log(` lan ${urls.lan.join(" ")}`);
115
+ if (urls.tailscale.length) console.log(` tailscale ${urls.tailscale.join(" ")}`);
116
+ if (urls.https) console.log(` https ${urls.https}`);
117
+ if (httpsNote) console.log(` https unavailable: ${httpsNote}`);
116
118
  }
117
119
 
118
120
  async function waitForPidAuthority(plan, expected, shutdown) {
@@ -127,7 +129,7 @@ async function waitForPidAuthority(plan, expected, shutdown) {
127
129
 
128
130
  async function serve(plan, expected) {
129
131
  // Signal handling comes first: startup remains controllable while waiting for
130
- // the parent record and while the async migration child is running.
132
+ // the parent record.
131
133
  const shutdown = installShutdown(plan, expected);
132
134
  if (!(await waitForPidAuthority(plan, expected, shutdown))) {
133
135
  throw new Error("launcher did not establish pid authority before startup");
@@ -139,15 +141,6 @@ async function serve(plan, expected) {
139
141
  throw new Error("pid authority changed before startup");
140
142
  }
141
143
 
142
- await runPrestart(plan, shutdown);
143
- if (shutdown.stopping) return;
144
- const afterPrestart = readPidRecord(plan.pidFile);
145
- if (afterPrestart?.pid !== expected.pid
146
- || afterPrestart.startTime !== expected.startTime
147
- || afterPrestart.launchNonce !== expected.launchNonce) {
148
- throw new Error("pid authority changed during migration prestart");
149
- }
150
-
151
144
  try {
152
145
  await import(pathToFileURL(path.join(packageRoot, ".output", "server", "index.mjs")).href);
153
146
  shutdown.nitroLoaded();
@@ -160,7 +153,7 @@ async function serve(plan, expected) {
160
153
  if (shutdown.stopping) return;
161
154
  if (!ready) throw new Error(`server did not become ready at ${plan.readyUrl}`);
162
155
  updatePidRecordState(plan, expected, "running");
163
- console.log(`[pievo-server] ready at http://${plan.host}:${plan.port} (pid ${process.pid})`);
156
+ console.log(`[pievo-server] ready on ${plan.host}:${plan.port} (pid ${process.pid})`);
164
157
  } catch (error) {
165
158
  // Once Nitro may own a listening socket, ask its srvx signal handler to drain
166
159
  // HTTP rather than forcing process.exit from our application handler.
@@ -196,6 +189,7 @@ async function waitForDetachedReady(plan, expected) {
196
189
  }
197
190
 
198
191
  async function cleanFailedLaunch(plan, expected) {
192
+ retractHttps(expected);
199
193
  const state = recordedProcessState(expected);
200
194
  if (state.state === "gone") {
201
195
  clearPidRecord(plan.pidFile, expected);
@@ -212,7 +206,7 @@ async function cleanFailedLaunch(plan, expected) {
212
206
  async function start(basePlan, foreground) {
213
207
  const existing = pidStatus(basePlan.pidFile);
214
208
  if (existing.state === "running") {
215
- console.log(`pievo-server is already ${existing.record.state === "starting" ? "starting" : "running"} (pid ${existing.record.pid}, http://${existing.record.host}:${existing.record.port})`);
209
+ console.log(`pievo server is already ${existing.record.state === "starting" ? "starting" : "running"} (pid ${existing.record.pid}); run \`pievo server status\` for its URLs`);
216
210
  return;
217
211
  }
218
212
  if (existing.state === "unsafe") throw new Error(`${existing.error}; refusing to start over the existing pid record`);
@@ -221,13 +215,15 @@ async function start(basePlan, foreground) {
221
215
  try {
222
216
  const raced = pidStatus(basePlan.pidFile);
223
217
  if (raced.state === "running") {
224
- console.log(`pievo-server is already ${raced.record.state === "starting" ? "starting" : "running"} (pid ${raced.record.pid})`);
218
+ console.log(`pievo server is already ${raced.record.state === "starting" ? "starting" : "running"} (pid ${raced.record.pid})`);
225
219
  return;
226
220
  }
227
221
  if (raced.state === "unsafe") throw new Error(`${raced.error}; refusing to start over the existing pid record`);
228
222
 
229
- const plan = withLaunchNonce(basePlan);
223
+ const published = publishNetwork(basePlan);
224
+ const plan = withLaunchNonce(published.plan);
230
225
  if (foreground) {
226
+ printUrls(published.urls, published.httpsNote);
231
227
  applyProcessEnv(plan.env);
232
228
  const record = writePidRecord(plan, {
233
229
  launchNonce: plan.launchNonce,
@@ -274,8 +270,9 @@ async function start(basePlan, foreground) {
274
270
  }
275
271
  throw error;
276
272
  }
277
- console.log(`pievo-server started (pid ${child.pid}, http://${plan.host}:${plan.port})`);
278
- console.log(`log: ${plan.logFile}`);
273
+ console.log(`pievo server started (pid ${child.pid})`);
274
+ printUrls(published.urls, published.httpsNote);
275
+ console.log(` log ${plan.logFile}`);
279
276
  } finally {
280
277
  release();
281
278
  }
@@ -319,8 +316,10 @@ async function stop(plan) {
319
316
  clearPidRecord(plan.pidFile, stoppedRecord);
320
317
  }
321
318
 
322
- if (stoppedRecord) console.log(`pievo-server stopped (pid ${stoppedRecord.pid})`);
323
- else console.log("pievo-server is not running");
319
+ if (stoppedRecord) {
320
+ retractHttps(stoppedRecord);
321
+ console.log(`pievo server stopped (pid ${stoppedRecord.pid})`);
322
+ } else console.log("pievo server is not running");
324
323
  } finally {
325
324
  release?.();
326
325
  }
@@ -329,17 +328,20 @@ async function stop(plan) {
329
328
  function status(plan) {
330
329
  const current = pidStatus(plan.pidFile);
331
330
  if (current.state === "running") {
332
- console.log(`pievo-server is ${current.record.state === "starting" ? "starting" : "running"} (pid ${current.record.pid})`);
333
- console.log(`url: http://${current.record.host}:${current.record.port}`);
334
- console.log(`data: ${plan.dataDir}`);
331
+ const { host, port } = current.record;
332
+ const tailscale = tailscaleInfo();
333
+ const https = tailscale ? currentHttpsUrl(tailscale, port) : undefined;
334
+ console.log(`pievo server is ${current.record.state === "starting" ? "starting" : "running"} (pid ${current.record.pid})`);
335
+ printUrls(reachableUrls({ host, port, https, tailscale }));
336
+ console.log(` data ${plan.dataDir}`);
335
337
  return 0;
336
338
  }
337
339
  if (current.state === "unsafe") {
338
340
  const pid = current.record?.pid ? ` (pid ${current.record.pid})` : "";
339
- console.error(`pievo-server status is unsafe: ${current.error}${pid}`);
341
+ console.error(`pievo server status is unsafe: ${current.error}${pid}`);
340
342
  return 1;
341
343
  }
342
- console.log(`pievo-server is stopped${current.stale ? " (cleared stale pid file)" : ""}`);
344
+ console.log(`pievo server is stopped${current.stale ? " (cleared stale pid file)" : ""}`);
343
345
  return 1;
344
346
  }
345
347
 
@@ -4,30 +4,32 @@ import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
 
7
- export const DEFAULT_HOST = "127.0.0.1";
8
- export const DEFAULT_PORT = 3000;
7
+ const DEFAULT_HOST = "0.0.0.0";
8
+ const DEFAULT_PORT = 3000;
9
9
  const MALFORMED_LOCK_GRACE_MS = 5_000;
10
10
 
11
11
  export function usage() {
12
- return `Usage: pievo-server <start|status|restart|stop> [options]
12
+ return `Usage: pievo server <start|status|restart|stop> [options]
13
13
 
14
14
  Commands:
15
15
  start start in the background (idempotent)
16
16
  start --foreground run in the foreground
17
- status show local process status
17
+ status show process status and every URL the server is reachable at
18
18
  restart stop, then start the currently installed server
19
19
  stop gracefully stop the local server
20
20
 
21
21
  Options:
22
- --host <host> bind host (default: HOST/NITRO_HOST or 127.0.0.1)
22
+ --host <host> bind host (default: HOST/NITRO_HOST or 0.0.0.0)
23
23
  --port <port> bind port (default: PORT/NITRO_PORT/PIEVO_PORT or 3000)
24
+ --no-https do not publish https://<device>.ts.net via Tailscale Serve
24
25
  --data-dir <path> select the instance (database, blobs, pid, lock, and log)
25
26
  -h, --help show this help
26
27
  -v, --version print the server version and exit
27
28
 
28
- Restart preserves the recorded instance host/port unless a flag or bind environment
29
- variable overrides it. The default bind is local-only. Set --host 0.0.0.0 only with
30
- appropriate auth and network controls.`;
29
+ The server accepts requests only from this machine, private LAN addresses, and
30
+ Tailscale peers. When Tailscale is running, start also publishes the dashboard at
31
+ https://<device>.ts.net (tailnet-only). Restart preserves the recorded host, port,
32
+ and HTTPS choice unless a flag or bind environment variable overrides it.`;
31
33
  }
32
34
 
33
35
  export function parseArgs(argv) {
@@ -44,6 +46,10 @@ export function parseArgs(argv) {
44
46
  out.foreground = true;
45
47
  continue;
46
48
  }
49
+ if (arg === "--no-https" && (command === "start" || command === "restart")) {
50
+ out.https = false;
51
+ continue;
52
+ }
47
53
  const match = arg.match(/^--(host|port|data-dir)(?:=(.*))?$/);
48
54
  if (match) {
49
55
  const key = match[1] === "data-dir" ? "dataDir" : match[1];
@@ -74,7 +80,7 @@ function firstSet(env, names) {
74
80
  }
75
81
 
76
82
  export function buildEnvPlan(options, baseEnv = process.env) {
77
- const dataDir = path.resolve(options.dataDir || baseEnv.PIEVO_DATA_DIR?.trim() || path.join(os.homedir(), ".pievo"));
83
+ const dataDir = path.resolve(options.dataDir || baseEnv.PIEVO_DATA_DIR?.trim() || path.join(os.homedir(), ".pievo-server"));
78
84
  const host = String(options.host || firstSet(baseEnv, ["NITRO_HOST", "HOST"]) || DEFAULT_HOST).trim();
79
85
  if (!host) throw new Error("host cannot be empty");
80
86
  const port = validPort(options.port || firstSet(baseEnv, ["NITRO_PORT", "PORT", "PIEVO_PORT"]) || DEFAULT_PORT);
@@ -88,12 +94,12 @@ export function buildEnvPlan(options, baseEnv = process.env) {
88
94
  PORT: String(port),
89
95
  };
90
96
  delete env.PIEVO_SERVER_LAUNCH_NONCE;
91
- if (!env.DATABASE_URL && !env.PIEVO_DB) env.PIEVO_DB = "pglite";
92
97
  return {
93
98
  env,
94
99
  dataDir,
95
100
  host,
96
101
  port,
102
+ https: options.https !== false,
97
103
  pidFile: path.join(dataDir, "server.pid"),
98
104
  lockFile: path.join(dataDir, "server.start.lock"),
99
105
  logFile: path.join(dataDir, "server.log"),
@@ -102,7 +108,7 @@ export function buildEnvPlan(options, baseEnv = process.env) {
102
108
  }
103
109
 
104
110
  /** Replace the current process environment with the launch plan. Foreground
105
- * starts need the same bind/database/nonce environment a detached child gets. */
111
+ * starts need the same bind/data-dir/nonce environment a detached child gets. */
106
112
  export function applyProcessEnv(nextEnv, currentEnv = process.env) {
107
113
  for (const key of Object.keys(currentEnv)) {
108
114
  if (!(key in nextEnv)) delete currentEnv[key];
@@ -118,6 +124,7 @@ export function buildRestartPlan(options, baseEnv, record) {
118
124
  if (preserved.port === undefined && firstSet(baseEnv, ["NITRO_PORT", "PORT", "PIEVO_PORT"]) === undefined && record?.port) {
119
125
  preserved.port = String(record.port);
120
126
  }
127
+ if (preserved.https === undefined && record?.https === false) preserved.https = false;
121
128
  return buildEnvPlan(preserved, baseEnv);
122
129
  }
123
130
 
@@ -168,7 +175,8 @@ function validPidRecord(value) {
168
175
  && ['starting', 'running'].includes(value.state)
169
176
  && typeof value.launchNonce === "string"
170
177
  && !!value.launchNonce
171
- && typeof value.managedGroup === "boolean";
178
+ && typeof value.managedGroup === "boolean"
179
+ && (value.https === undefined || typeof value.https === "boolean");
172
180
  }
173
181
 
174
182
  function readPidResult(pidFile) {
@@ -219,6 +227,7 @@ export function writePidRecord(plan, options = {}) {
219
227
  startTime,
220
228
  host: plan.host,
221
229
  port: plan.port,
230
+ https: plan.https,
222
231
  state: options.state ?? "starting",
223
232
  launchNonce: options.launchNonce,
224
233
  managedGroup: options.managedGroup ?? false,
@@ -0,0 +1,139 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+
5
+ // The launcher (never request handling) shells out to the local Tailscale CLI.
6
+ // A bounded timeout matters: `tailscale serve` blocks waiting for an admin
7
+ // approval when Serve is not yet enabled on the tailnet.
8
+ const TAILSCALE_TIMEOUT_MS = 15_000;
9
+ const HTTPS_PORT = 443;
10
+ const MAC_APP_CLI = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
11
+
12
+ function defaultExec(file, args) {
13
+ return execFileSync(file, args, {
14
+ encoding: "utf8",
15
+ timeout: TAILSCALE_TIMEOUT_MS,
16
+ stdio: ["ignore", "pipe", "pipe"],
17
+ });
18
+ }
19
+
20
+ function tailscaleCli(exec) {
21
+ for (const candidate of ["tailscale", MAC_APP_CLI]) {
22
+ if (candidate.startsWith("/") && !fs.existsSync(candidate)) continue;
23
+ try {
24
+ exec(candidate, ["version"]);
25
+ return candidate;
26
+ } catch {}
27
+ }
28
+ return undefined;
29
+ }
30
+
31
+ /** This device's Tailscale identity, or undefined when Tailscale is absent or down. */
32
+ export function tailscaleInfo(exec = defaultExec) {
33
+ const cli = tailscaleCli(exec);
34
+ if (!cli) return undefined;
35
+ try {
36
+ const status = JSON.parse(exec(cli, ["status", "--json"]));
37
+ if (status.BackendState !== "Running") return undefined;
38
+ const dnsName = String(status.Self?.DNSName ?? "").replace(/\.$/, "");
39
+ const ipv4 = (status.Self?.TailscaleIPs ?? []).find((ip) => /^\d+\.\d+\.\d+\.\d+$/.test(ip));
40
+ if (!dnsName && !ipv4) return undefined;
41
+ return { cli, dnsName: dnsName || undefined, ipv4, httpsAvailable: (status.CertDomains ?? []).length > 0 };
42
+ } catch {
43
+ return undefined;
44
+ }
45
+ }
46
+
47
+ function serveStatus(info, exec) {
48
+ try {
49
+ return JSON.parse(exec(info.cli, ["serve", "status", "--json"]) || "{}");
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ }
54
+
55
+ function httpsProxyTarget(serve, dnsName) {
56
+ return serve?.Web?.[`${dnsName}:${HTTPS_PORT}`]?.Handlers?.["/"]?.Proxy;
57
+ }
58
+
59
+ function localTarget(port) {
60
+ return `http://127.0.0.1:${port}`;
61
+ }
62
+
63
+ /** The Tailscale HTTPS URL currently proxying to this port, if any. */
64
+ export function currentHttpsUrl(info, port, exec = defaultExec) {
65
+ if (!info?.dnsName) return undefined;
66
+ return httpsProxyTarget(serveStatus(info, exec), info.dnsName) === localTarget(port)
67
+ ? `https://${info.dnsName}`
68
+ : undefined;
69
+ }
70
+
71
+ /**
72
+ * Point `https://<device>.ts.net` at the local server via `tailscale serve`.
73
+ * Never replaces an HTTPS mapping that proxies somewhere else.
74
+ */
75
+ export function enableHttps(info, port, exec = defaultExec) {
76
+ if (!info?.dnsName) return { ok: false, reason: "Tailscale is not running" };
77
+ const serve = serveStatus(info, exec);
78
+ if (!serve) return { ok: false, reason: "could not read the Tailscale Serve configuration" };
79
+ const existing = httpsProxyTarget(serve, info.dnsName);
80
+ if (existing === localTarget(port)) return { ok: true, url: `https://${info.dnsName}` };
81
+ if (existing || serve.TCP?.[String(HTTPS_PORT)]) {
82
+ return { ok: false, reason: `Tailscale HTTPS port ${HTTPS_PORT} is already serving ${existing ?? "another target"}` };
83
+ }
84
+ try {
85
+ exec(info.cli, ["serve", "--bg", "--yes", `--https=${HTTPS_PORT}`, localTarget(port)]);
86
+ } catch (error) {
87
+ const output = `${error?.stdout ?? ""}${error?.stderr ?? ""}`;
88
+ const enableUrl = output.match(/https:\/\/login\.tailscale\.com\/\S+/)?.[0];
89
+ return {
90
+ ok: false,
91
+ reason: enableUrl
92
+ ? `Tailscale Serve is not enabled on this tailnet; approve it at ${enableUrl}, then restart`
93
+ : "tailscale serve failed",
94
+ };
95
+ }
96
+ return currentHttpsUrl(info, port, exec)
97
+ ? { ok: true, url: `https://${info.dnsName}` }
98
+ : { ok: false, reason: "tailscale serve did not apply the HTTPS mapping" };
99
+ }
100
+
101
+ /** Remove the HTTPS mapping only when it still points at this server's port. */
102
+ export function disableHttps(info, port, exec = defaultExec) {
103
+ if (!currentHttpsUrl(info, port, exec)) return false;
104
+ try {
105
+ exec(info.cli, ["serve", `--https=${HTTPS_PORT}`, "off"]);
106
+ return true;
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ function privateIpv4(ip) {
113
+ const [a, b] = ip.split(".").map(Number);
114
+ return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
115
+ }
116
+
117
+ /** Non-loopback private IPv4 addresses on this machine's LAN interfaces. */
118
+ export function lanAddresses(interfaces = os.networkInterfaces()) {
119
+ const out = [];
120
+ for (const entries of Object.values(interfaces)) {
121
+ for (const entry of entries ?? []) {
122
+ if (entry.family === "IPv4" && !entry.internal && privateIpv4(entry.address)) out.push(entry.address);
123
+ }
124
+ }
125
+ return [...new Set(out)];
126
+ }
127
+
128
+ /**
129
+ * Every URL the server can be reached at, best first. `connect` is the URL other
130
+ * devices should use (HTTPS, then Tailscale name, then LAN address).
131
+ */
132
+ export function reachableUrls({ host, port, https, tailscale, lan = lanAddresses() }) {
133
+ const allInterfaces = host === "0.0.0.0" || host === "::";
134
+ const local = `http://${allInterfaces || host === "localhost" ? "127.0.0.1" : host.includes(":") ? `[${host}]` : host}:${port}`;
135
+ if (!allInterfaces) return { local, lan: [], tailscale: [], https, connect: https ?? local };
136
+ const tailscaleUrls = [tailscale?.dnsName, tailscale?.ipv4].filter(Boolean).map((name) => `http://${name}:${port}`);
137
+ const lanUrls = lan.map((ip) => `http://${ip}:${port}`);
138
+ return { local, lan: lanUrls, tailscale: tailscaleUrls, https, connect: https ?? tailscaleUrls[0] ?? lanUrls[0] };
139
+ }
@@ -1 +0,0 @@
1
- import{F as e,d as t}from"./actionUi-D8m1FDHO.js";import{a as n,n as r,o as i,s as a}from"./preload-helper-Zcmr31XG.js";var o=e();function s({callbackURL:e=`/`}){return(0,o.jsx)(`main`,{className:`mx-auto max-w-[1180px] px-8 pb-24`,children:(0,o.jsxs)(`div`,{className:`mx-auto mt-32 max-w-sm rounded-card p-6 text-center`,children:[(0,o.jsx)(a,{size:52}),(0,o.jsx)(`h1`,{className:`mt-4 text-2xl font-semibold tracking-tight text-display`,children:`Pievo`}),(0,o.jsx)(`p`,{className:`mt-2 text-sm text-secondary`,children:`Sign in to manage your scheduled agent loops.`}),(0,o.jsx)(`button`,{className:`${t} mt-6`,onClick:()=>void r.social({provider:`github`,callbackURL:e}),children:`Continue with GitHub`}),(0,o.jsx)(`div`,{className:`mt-5 flex items-center justify-center text-caption text-secondary`,children:(0,o.jsxs)(`a`,{href:n,target:`_blank`,rel:`noreferrer`,className:`inline-flex items-center gap-1.5 transition-colors hover:text-display`,children:[(0,o.jsx)(i,{className:`size-4`}),` Open source`]})})]})})}export{s as t};
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.3.1 | 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-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking: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-outline-style:solid;--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;--tw-content:""}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "Segoe UI", sans-serif;--font-mono:ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--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-pixel:"Geist Pixel", ui-monospace, "SF Mono", Menlo, monospace;--radius-control:10px;--radius-card:16px;--radius-sheet:20px;--text-micro:10px;--text-caption:11px;--text-label:12px;--text-meta:12.5px;--text-body:13px;--color-paper:#f3f4f7;--color-surface:#fff;--color-raised:#eceef2;--color-hairline:#e3e5ea;--color-wire:#cfd3db;--color-disabled:#9aa0ab;--color-secondary:#666c78;--color-primary:#1c1e24;--color-display:#0d0f14;--color-rubik-green:#009b48;--color-rubik-red:#b71234;--color-rubik-orange:#ff5800;--color-rubik-yellow:#ffd500;--color-rubik-blue:#0046ad;--color-accent:#b71234;--color-accent-soft:#b7123414;--color-success:#00813c;--color-success-soft:#009b481a;--color-interactive:var(--color-rubik-blue);--color-warn:#c44700;--color-warn-soft:#ff58001a;--color-running:#8a6d00;--color-running-soft:#ffd50029;--color-run-keep:var(--color-success);--color-run-no-change:var(--color-secondary);--color-run-block:var(--color-running);--color-run-error:var(--color-accent);--color-run-warning:var(--color-warn);--color-run-canceled:var(--color-disabled);--color-run-queued:var(--color-secondary);--color-run-active:var(--color-display);--color-diff-add-bg:#009b481a;--color-diff-del-bg:#b7123414;--color-diff-hunk-bg:var(--color-raised)}}@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;-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}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.start-42{inset-inline-start:calc(var(--spacing) * 42)}.top-0{top:0}.top-1\/2{top:50%}.top-\[46\%\]{top:46%}.left-1\/2{left:50%}.z-10{z-index:10}.z-50{z-index:50}.z-\[900\]{z-index:900}.z-\[901\]{z-index:901}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-0{margin:0}.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)}.mt-7{margin-top:calc(var(--spacing) * 7)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-32{margin-top:calc(var(--spacing) * 32)}.mt-\[5px\]{margin-top:5px}.mt-\[18px\]{margin-top:18px}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.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-3\.5{margin-bottom:calc(var(--spacing) * 3.5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-\[18px\]{margin-bottom:18px}.ml-1{margin-left:var(--spacing)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-\[11px\]{width:11px;height:11px}.size-\[17px\]{width:17px;height:17px}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-5{height:calc(var(--spacing) * 5)}.h-\[22px\]{height:22px}.h-\[min\(70vh\,620px\)\]{height:min(70vh,620px)}.h-\[min\(600px\,68vh\)\]{height:min(600px,68vh)}.h-auto{height:auto}.max-h-44{max-height:calc(var(--spacing) * 44)}.max-h-\[150px\]{max-height:150px}.max-h-\[300px\]{max-height:300px}.max-h-\[420px\]{max-height:420px}.max-h-\[calc\(100dvh-4rem\)\]{max-height:calc(100dvh - 4rem)}.max-h-\[clamp\(280px\,46vh\,520px\)\]{max-height:clamp(280px,46vh,520px)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-6{width:calc(var(--spacing) * 6)}.w-12{width:calc(var(--spacing) * 12)}.w-24{width:calc(var(--spacing) * 24)}.w-\[18px\]{width:18px}.w-full{width:100%}.w-px{width:1px}.max-w-48{max-width:calc(var(--spacing) * 48)}.max-w-160{max-width:calc(var(--spacing) * 160)}.max-w-\[62rem\]{max-width:62rem}.max-w-\[340px\]{max-width:340px}.max-w-\[1180px\]{max-width:1180px}.max-w-\[1360px\]{max-width:1360px}.max-w-full{max-width:100%}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:0}.min-w-max{min-width:max-content}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-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)}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[max-content_minmax\(0\,1fr\)\]{grid-template-columns:max-content 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}.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-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-\[3px\]{gap:3px}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.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)))}:where(.space-y-3\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3.5) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-2\.5{column-gap:calc(var(--spacing) * 2.5)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-x-10{column-gap:calc(var(--spacing) * 10)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.gap-y-2\.5{row-gap:calc(var(--spacing) * 2.5)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-hairline>:not(:last-child)){border-color:var(--color-hairline)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-\[2px\]{border-radius:2px}.rounded-\[4px\]{border-radius:4px}.rounded-card{border-radius:var(--radius-card)}.rounded-control{border-radius:var(--radius-control)}.rounded-full{border-radius:3.40282e38px}.rounded-sheet{border-radius:var(--radius-sheet)}.rounded-sm{border-radius:var(--radius-sm)}.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-none{--tw-border-style:none;border-style:none}.border-display{border-color:var(--color-display)}.border-hairline{border-color:var(--color-hairline)}.border-transparent{border-color:#0000}.border-warn\/40{border-color:#c4470066}@supports (color:color-mix(in lab, red, red)){.border-warn\/40{border-color:color-mix(in oklab, var(--color-warn) 40%, transparent)}}.border-wire{border-color:var(--color-wire)}.bg-\[color\:var\(--color-diff-add-bg\)\]{background-color:var(--color-diff-add-bg)}.bg-\[color\:var\(--color-diff-del-bg\)\]{background-color:var(--color-diff-del-bg)}.bg-\[color\:var\(--color-diff-hunk-bg\)\]{background-color:var(--color-diff-hunk-bg)}.bg-accent-soft{background-color:var(--color-accent-soft)}.bg-black\/25{background-color:#00000040}@supports (color:color-mix(in lab, red, red)){.bg-black\/25{background-color:color-mix(in oklab, var(--color-black) 25%, transparent)}}.bg-disabled{background-color:var(--color-disabled)}.bg-display{background-color:var(--color-display)}.bg-hairline{background-color:var(--color-hairline)}.bg-paper{background-color:var(--color-paper)}.bg-raised{background-color:var(--color-raised)}.bg-rubik-green{background-color:var(--color-rubik-green)}.bg-rubik-orange{background-color:var(--color-rubik-orange)}.bg-rubik-red{background-color:var(--color-rubik-red)}.bg-rubik-yellow{background-color:var(--color-rubik-yellow)}.bg-running-soft{background-color:var(--color-running-soft)}.bg-success-soft{background-color:var(--color-success-soft)}.bg-surface{background-color:var(--color-surface)}.bg-transparent{background-color:#0000}.bg-warn-soft{background-color:var(--color-warn-soft)}.bg-warn\/10{background-color:#c447001a}@supports (color:color-mix(in lab, red, red)){.bg-warn\/10{background-color:color-mix(in oklab, var(--color-warn) 10%, transparent)}}.bg-white{background-color:var(--color-white)}.object-contain{object-fit:contain}.p-0{padding:0}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-0{padding-inline:0}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-\[26px\]{padding-inline:26px}.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-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-14{padding-top:calc(var(--spacing) * 14)}.pt-\[22px\]{padding-top:22px}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-7{padding-bottom:calc(var(--spacing) * 7)}.pb-24{padding-bottom:calc(var(--spacing) * 24)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.pl-3{padding-left:calc(var(--spacing) * 3)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.font-pixel{font-family:var(--font-pixel)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--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-\[9px\]{font-size:9px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[17px\]{font-size:17px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[24px\]{font-size:24px}.text-\[26px\]{font-size:26px}.text-\[clamp\(28px\,4\.5vw\,38px\)\]{font-size:clamp(28px,4.5vw,38px)}.text-body{font-size:var(--text-body)}.text-caption{font-size:var(--text-caption)}.text-label{font-size:var(--text-label)}.text-meta{font-size:var(--text-meta)}.text-micro{font-size:var(--text-micro)}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[1\.55\]{--tw-leading:1.55;line-height:1.55}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[-0\.01em\]{--tw-tracking:-.01em;letter-spacing:-.01em}.tracking-\[-0\.015em\]{--tw-tracking:-.015em;letter-spacing:-.015em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--color-accent)}.text-disabled{color:var(--color-disabled)}.text-display{color:var(--color-display)}.text-interactive{color:var(--color-interactive)}.text-paper{color:var(--color-paper)}.text-primary{color:var(--color-primary)}.text-running{color:var(--color-running)}.text-secondary{color:var(--color-secondary)}.text-success{color:var(--color-success)}.text-warn{color:var(--color-warn)}.text-white{color:var(--color-white)}.text-wire{color:var(--color-wire)}.lowercase{text-transform:lowercase}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-60{opacity:.6}.shadow-card{--tw-shadow:0 2px 2px var(--tw-shadow-color,#0000000a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--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)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.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-\[8px\]{--tw-backdrop-blur:blur(8px);-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-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))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.marker\:content-\[\'\'\] ::marker{--tw-content:"";content:var(--tw-content)}.marker\:content-\[\'\'\]::marker{--tw-content:"";content:var(--tw-content)}.marker\:content-\[\'\'\] ::-webkit-details-marker{--tw-content:"";content:var(--tw-content)}.marker\:content-\[\'\'\]::-webkit-details-marker{--tw-content:"";content:var(--tw-content)}.first\:mt-2:first-child{margin-top:calc(var(--spacing) * 2)}@media (hover:hover){.hover\:border-display:hover{border-color:var(--color-display)}.hover\:border-wire:hover{border-color:var(--color-wire)}.hover\:bg-raised:hover{background-color:var(--color-raised)}.hover\:bg-raised\/60:hover{background-color:#eceef299}@supports (color:color-mix(in lab, red, red)){.hover\:bg-raised\/60:hover{background-color:color-mix(in oklab, var(--color-raised) 60%, transparent)}}.hover\:text-display:hover{color:var(--color-display)}.hover\:text-primary:hover{color:var(--color-primary)}.hover\:opacity-70:hover{opacity:.7}.hover\:opacity-85:hover{opacity:.85}}.focus\:border-transparent:focus{border-color:#0000}.focus\:shadow-focus:focus{--tw-shadow:0 0 0 2px var(--tw-shadow-color,var(--color-surface)), 0 0 0 4px var(--tw-shadow-color,var(--color-interactive));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:text-display:focus-visible{color:var(--color-display)}.focus-visible\:shadow-focus:focus-visible{--tw-shadow:0 0 0 2px var(--tw-shadow-color,var(--color-surface)), 0 0 0 4px var(--tw-shadow-color,var(--color-interactive));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--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)}.focus-visible\:ring-interactive:focus-visible{--tw-ring-color:var(--color-interactive)}.focus-visible\:ring-offset-4:focus-visible{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-surface:focus-visible{--tw-ring-offset-color:var(--color-surface)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:opacity-40:disabled{opacity:.4}.data-\[ending-style\]\:opacity-0[data-ending-style],.data-\[starting-style\]\:opacity-0[data-starting-style]{opacity:0}@media (width>=40rem){.sm\:max-h-none{max-height:none}.sm\:grid-cols-\[210px_1fr\]{grid-template-columns:210px 1fr}.sm\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.sm\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}@media (width>=48rem){.md\:grid-cols-\[180px_minmax\(0\,1fr\)\]{grid-template-columns:180px minmax(0,1fr)}}@media (width>=64rem){.lg\:grid-cols-\[minmax\(0\,1fr\)_minmax\(300px\,360px\)\]{grid-template-columns:minmax(0,1fr) minmax(300px,360px)}.lg\:grid-cols-\[minmax\(300px\,380px\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(300px,380px) minmax(0,1fr)}}@media (prefers-color-scheme:dark){.dark\:bg-black\/45{background-color:#00000073}@supports (color:color-mix(in lab, red, red)){.dark\:bg-black\/45{background-color:color-mix(in oklab, var(--color-black) 45%, transparent)}}}}@font-face{font-family:Geist Pixel;src:url(/fonts/GeistPixel-Square.woff2)format("woff2");font-weight:400;font-style:normal;font-display:swap}:root{--glass-bg:#ffffff8c}@supports (color:color-mix(in lab, red, red)){:root{--glass-bg:color-mix(in srgb, var(--color-surface) 55%, transparent)}}:root{--glass-bg-strong:#ffffffc7}@supports (color:color-mix(in lab, red, red)){:root{--glass-bg-strong:color-mix(in srgb, var(--color-surface) 78%, transparent)}}:root{--glass-border:#0d0f141a}@supports (color:color-mix(in lab, red, red)){:root{--glass-border:color-mix(in srgb, var(--color-display) 10%, transparent)}}:root{--glass-highlight:#ffffffbf;--glass-shadow:0 1px 1px #00000005, 0 8px 24px -8px #0000001a, 0 24px 48px -16px #00000014}@media (prefers-color-scheme:dark){:root{--color-paper:#0a0a0c;--color-surface:#141416;--color-raised:#1c1c1f;--color-hairline:#26262a;--color-wire:#3a3a40;--color-disabled:#6e6e76;--color-secondary:#98989f;--color-primary:#e8e8ed;--color-display:#fff;--color-accent:#ff5d75;--color-accent-soft:#ff5d751f;--color-success:#33c56f;--color-success-soft:#009b482e;--color-interactive:#5c9dff;--color-interactive-soft:#5c9dff21;--color-warn:#ff7a3d;--color-warn-soft:#ff7a3d24;--color-running:#ffd500;--color-running-soft:#ffd5001f;--color-diff-add-bg:#009b482e;--color-diff-del-bg:#ff5d7524;--glass-highlight:#ffffff17;--glass-shadow:0 1px 1px #0003, 0 12px 32px -8px #00000080;--shadow-card:0 0 0 0 transparent}}.glass,.glass-strong{-webkit-backdrop-filter:blur(22px)saturate(1.7);backdrop-filter:blur(22px)saturate(1.7);border:1px solid var(--glass-border);box-shadow:inset 0 1px 0 var(--glass-highlight), var(--glass-shadow)}.glass{background:var(--glass-bg)}.glass-strong{background:var(--glass-bg-strong)}.glass-bar{box-shadow:inset 0 1px 0 var(--glass-highlight);border-width:0 0 1px;border-radius:0}.artifact-markdown{font-family:var(--font-sans);color:var(--color-secondary);font-size:13px;line-height:1.6}.artifact-markdown>:first-child{margin-top:0}.artifact-markdown h1,.artifact-markdown h2,.artifact-markdown h3,.artifact-markdown h4{color:var(--color-display);margin:1.1em 0 .4em;font-weight:600;line-height:1.3}.artifact-markdown h1{font-size:15px}.artifact-markdown h2{font-size:13.5px}.artifact-markdown h3,.artifact-markdown h4{font-size:12.5px}.artifact-markdown p{margin:.5em 0}.artifact-markdown ul,.artifact-markdown ol{margin:.5em 0;padding-left:1.3em}.artifact-markdown ul{list-style:outside}.artifact-markdown ol{list-style:decimal}.artifact-markdown li{margin:.2em 0}.artifact-markdown a{color:var(--color-interactive);text-underline-offset:2px;text-decoration:underline}.artifact-markdown strong{color:var(--color-primary);font-weight:600}.artifact-markdown code{font-family:var(--font-mono);background:var(--color-surface);overflow-wrap:anywhere;border-radius:4px;padding:.1em .35em;font-size:.9em}.artifact-markdown pre{font-family:var(--font-mono);background:var(--color-surface);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;margin:.6em 0;padding:12px 14px;font-size:12px;line-height:1.5}.artifact-markdown pre code{background:0 0;padding:0}.artifact-markdown blockquote{border-left:2px solid var(--color-wire);color:var(--color-disabled);margin:.6em 0;padding-left:12px}.artifact-markdown hr{border:0;border-top:1px solid var(--color-hairline);margin:1em 0}.artifact-markdown table{border-collapse:collapse;width:max-content;max-width:100%;margin:.6em 0;display:block;overflow-x:auto}.artifact-markdown th,.artifact-markdown td{border:1px solid var(--color-hairline);text-align:left;white-space:nowrap;padding:4px 8px}.artifact-markdown img{max-width:100%}*{scrollbar-width:thin;scrollbar-color:var(--color-wire) transparent}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--color-wire);background-clip:padding-box;border:2px solid #0000;border-radius:9999px}::-webkit-scrollbar-thumb:hover{background:var(--color-disabled);background-clip:padding-box}::-webkit-scrollbar-corner{background:0 0}.lp-select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none' stroke='%23999999' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.75 4.5 6 7.75 9.25 4.5'/%3E%3C/svg%3E");background-position:right .7rem center;background-repeat:no-repeat;padding-right:2rem}.dot-grid-subtle{background-image:radial-gradient(circle, var(--color-hairline) .5px, transparent .5px);background-size:12px 12px}@keyframes fadeIn{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}@keyframes runPulse{0%,to{opacity:.9}50%{opacity:.26}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.ring-logo{--ring-base:currentColor;vertical-align:middle;display:inline-block}.ring-logo-plate{--ring-base:#ededed}.ring-logo .ring-cell{fill:var(--ring-base)}.ring-logo .ring-runner{fill:var(--color-interactive)}.ring-logo .ring-trail-1{fill:#0046ad}@supports (color:color-mix(in lab, red, red)){.ring-logo .ring-trail-1{fill:color-mix(in srgb, var(--color-interactive) 55%, var(--ring-base))}}.ring-logo .ring-trail-2{fill:#0046ad}@supports (color:color-mix(in lab, red, red)){.ring-logo .ring-trail-2{fill:color-mix(in srgb, var(--color-interactive) 25%, var(--ring-base))}}.ring-logo-animated .ring-cell{animation:4s linear infinite ringChase}.ring-logo-animated .rc0{animation-delay:-4s}.ring-logo-animated .rc1{animation-delay:-3.5s}.ring-logo-animated .rc2{animation-delay:-3s}.ring-logo-animated .rc3{animation-delay:-2.5s}.ring-logo-animated .rc4{animation-delay:-2s}.ring-logo-animated .rc5{animation-delay:-1.5s}.ring-logo-animated .rc6{animation-delay:-1s}.ring-logo-animated .rc7{animation-delay:-.5s}@keyframes ringChase{0%,9%{fill:var(--color-interactive)}16%,to{fill:var(--ring-base)}}@media (prefers-reduced-motion:reduce){.ring-logo-animated .ring-cell{animation:none}}@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-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-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-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-outline-style{syntax:"*";inherits:false;initial-value:solid}@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}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes pulse{50%{opacity:.5}}