@dupecom/botcha-cloudflare 0.11.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics.js +2 -2
- package/dist/dashboard/auth.d.ts +13 -4
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +38 -42
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +1 -0
- package/dist/dashboard/landing.d.ts +3 -3
- package/dist/dashboard/landing.d.ts.map +1 -1
- package/dist/dashboard/landing.js +47 -7
- package/dist/dashboard/pages.d.ts.map +1 -1
- package/dist/dashboard/pages.js +1 -1
- package/dist/dashboard/styles.d.ts +1 -1
- package/dist/dashboard/styles.d.ts.map +1 -1
- package/dist/dashboard/styles.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -58
- package/dist/static.d.ts +227 -1
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +191 -1
- package/dist/tap-agents.d.ts +120 -0
- package/dist/tap-agents.d.ts.map +1 -0
- package/dist/tap-agents.js +225 -0
- package/dist/tap-routes.d.ts +215 -0
- package/dist/tap-routes.d.ts.map +1 -0
- package/dist/tap-routes.js +379 -0
- package/dist/tap-verify.d.ts +86 -0
- package/dist/tap-verify.d.ts.map +1 -0
- package/dist/tap-verify.js +275 -0
- package/package.json +3 -3
package/dist/analytics.js
CHANGED
|
@@ -30,10 +30,10 @@ export async function logAnalyticsEvent(analytics, event) {
|
|
|
30
30
|
event.solveTimeMs || 0,
|
|
31
31
|
event.responseTimeMs || 0,
|
|
32
32
|
];
|
|
33
|
+
// IMPORTANT: Analytics Engine only supports a SINGLE index.
|
|
34
|
+
// Multiple indexes silently drops the data point.
|
|
33
35
|
const indexes = [
|
|
34
36
|
event.eventType,
|
|
35
|
-
event.challengeType || 'none',
|
|
36
|
-
event.endpoint || 'unknown',
|
|
37
37
|
];
|
|
38
38
|
analytics.writeDataPoint({
|
|
39
39
|
blobs,
|
package/dist/dashboard/auth.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ type Bindings = {
|
|
|
30
30
|
type Variables = {
|
|
31
31
|
dashboardAppId?: string;
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Generate a 1-hour dashboard session JWT for the given app_id.
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateSessionToken(appId: string, jwtSecret: string): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Set the dashboard session cookie on a response context.
|
|
39
|
+
*/
|
|
40
|
+
export declare function setSessionCookie(c: Context, token: string): void;
|
|
33
41
|
/**
|
|
34
42
|
* Middleware: Require dashboard authentication.
|
|
35
43
|
*
|
|
@@ -117,13 +125,16 @@ export declare function handleDeviceCodeVerify(c: Context<{
|
|
|
117
125
|
success: true;
|
|
118
126
|
code: string;
|
|
119
127
|
login_url: string;
|
|
128
|
+
magic_link: string;
|
|
120
129
|
expires_in: number;
|
|
121
130
|
instructions: string;
|
|
122
131
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
123
132
|
/**
|
|
124
133
|
* GET /dashboard/code
|
|
134
|
+
* GET /dashboard/code/:code
|
|
125
135
|
*
|
|
126
136
|
* Renders the device code redemption page for humans.
|
|
137
|
+
* Supports pre-filled codes from URL path or query params.
|
|
127
138
|
*/
|
|
128
139
|
export declare function renderDeviceCodePage(c: Context<{
|
|
129
140
|
Bindings: Bindings;
|
|
@@ -170,11 +181,9 @@ export declare function handleEmailLogin(c: Context<{
|
|
|
170
181
|
/**
|
|
171
182
|
* GET /dashboard/login
|
|
172
183
|
*
|
|
173
|
-
*
|
|
174
|
-
* 1.
|
|
184
|
+
* Two ways in:
|
|
185
|
+
* 1. Agent prompt (primary) — copy curl command, agent gets device code
|
|
175
186
|
* 2. Email login (returning users — code emailed to verified address)
|
|
176
|
-
* 3. App ID + Secret (agent created the app)
|
|
177
|
-
* 4. Create new app (triggers POST /v1/apps)
|
|
178
187
|
*/
|
|
179
188
|
export declare function renderLoginPage(c: Context<{
|
|
180
189
|
Bindings: Bindings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/dashboard/auth.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAMvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAKjD,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAGF,KAAK,SAAS,GAAG;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/dashboard/auth.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAMvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAKjD,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAGF,KAAK,SAAS,GAAG;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAIF;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAQhE;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAmChG,CAAC;AAIF;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;;;;;;;;;;oEA2CpF;AAgDD;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;;;;;;;;;;;oEAwBjF;AAID;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;;;;;;;;;;oEAEjF;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;;;;;;;;;;;oEA4B9E;AAID;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,qBA2E5E;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,gFAqB9E;AAID;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,gFAyBnE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,gFAGpE;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,gFA2BxE;AA4BD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,qBAiFvE"}
|
package/dist/dashboard/auth.js
CHANGED
|
@@ -10,7 +10,7 @@ import { LoginLayout, Card, Divider } from './layout';
|
|
|
10
10
|
/**
|
|
11
11
|
* Generate a 1-hour dashboard session JWT for the given app_id.
|
|
12
12
|
*/
|
|
13
|
-
async function generateSessionToken(appId, jwtSecret) {
|
|
13
|
+
export async function generateSessionToken(appId, jwtSecret) {
|
|
14
14
|
const encoder = new TextEncoder();
|
|
15
15
|
const secretKey = encoder.encode(jwtSecret);
|
|
16
16
|
return new SignJWT({
|
|
@@ -28,7 +28,7 @@ async function generateSessionToken(appId, jwtSecret) {
|
|
|
28
28
|
/**
|
|
29
29
|
* Set the dashboard session cookie on a response context.
|
|
30
30
|
*/
|
|
31
|
-
function setSessionCookie(c, token) {
|
|
31
|
+
export function setSessionCookie(c, token) {
|
|
32
32
|
setCookie(c, 'botcha_session', token, {
|
|
33
33
|
path: '/dashboard',
|
|
34
34
|
httpOnly: true,
|
|
@@ -216,21 +216,31 @@ export async function handleDeviceCodeVerify(c) {
|
|
|
216
216
|
success: true,
|
|
217
217
|
code,
|
|
218
218
|
login_url: `${baseUrl}/dashboard/code`,
|
|
219
|
+
magic_link: `${baseUrl}/go/${code}`,
|
|
219
220
|
expires_in: 600,
|
|
220
|
-
instructions: `
|
|
221
|
+
instructions: `Give your human this link: ${baseUrl}/go/${code} (or visit ${baseUrl}/dashboard/code and enter code: ${code})`,
|
|
221
222
|
});
|
|
222
223
|
}
|
|
223
224
|
// ============ DEVICE CODE REDEMPTION (human-facing) ============
|
|
224
225
|
/**
|
|
225
226
|
* GET /dashboard/code
|
|
227
|
+
* GET /dashboard/code/:code
|
|
226
228
|
*
|
|
227
229
|
* Renders the device code redemption page for humans.
|
|
230
|
+
* Supports pre-filled codes from URL path or query params.
|
|
228
231
|
*/
|
|
229
232
|
export async function renderDeviceCodePage(c) {
|
|
230
233
|
const url = new URL(c.req.url);
|
|
231
234
|
const error = url.searchParams.get('error');
|
|
232
|
-
const prefill = url.searchParams.get('code') || '';
|
|
233
235
|
const emailSent = url.searchParams.get('email_sent') === '1';
|
|
236
|
+
// Get prefill code from URL path or query params
|
|
237
|
+
const pathCode = c.req.param('code')?.toUpperCase() || '';
|
|
238
|
+
const queryCode = url.searchParams.get('code') || '';
|
|
239
|
+
let prefill = pathCode || queryCode;
|
|
240
|
+
// Strip BOTCHA- prefix if present (form only needs the suffix)
|
|
241
|
+
if (prefill.startsWith('BOTCHA-')) {
|
|
242
|
+
prefill = prefill.slice(7);
|
|
243
|
+
}
|
|
234
244
|
const errorMap = {
|
|
235
245
|
invalid: 'Invalid or expired code. Ask your agent for a new one.',
|
|
236
246
|
missing: 'Please enter a device code.',
|
|
@@ -344,58 +354,44 @@ export async function handleEmailLogin(c) {
|
|
|
344
354
|
return c.redirect('/dashboard/code?email_sent=1');
|
|
345
355
|
}
|
|
346
356
|
// ============ LOGIN PAGE ============
|
|
357
|
+
const DASHBOARD_PROMPT = `Run: curl -s https://botcha.ai -H 'Accept: application/json' — then follow the instructions to verify yourself and get me a dashboard login code for botcha.ai.`;
|
|
358
|
+
const COPY_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"><rect x="9" y="9" width="13" height="13" rx="0"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/></svg>`;
|
|
359
|
+
const CHECK_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
360
|
+
const LOGIN_COPY_SCRIPT = `
|
|
361
|
+
function copyDashPrompt() {
|
|
362
|
+
var text = document.getElementById('dash-prompt').textContent.trim();
|
|
363
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
364
|
+
var label = document.getElementById('dash-copy-label');
|
|
365
|
+
var icon = document.getElementById('dash-copy-icon');
|
|
366
|
+
var txt = document.getElementById('dash-copy-text');
|
|
367
|
+
label.style.color = 'var(--green)';
|
|
368
|
+
icon.innerHTML = '${CHECK_ICON_SVG.replace(/'/g, "\\'")}';
|
|
369
|
+
txt.textContent = 'Copied — now paste into your agent';
|
|
370
|
+
setTimeout(function() {
|
|
371
|
+
label.style.color = 'var(--text-muted)';
|
|
372
|
+
icon.innerHTML = '${COPY_ICON_SVG.replace(/'/g, "\\'")}';
|
|
373
|
+
txt.textContent = 'Click to copy';
|
|
374
|
+
}, 2500);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
347
378
|
/**
|
|
348
379
|
* GET /dashboard/login
|
|
349
380
|
*
|
|
350
|
-
*
|
|
351
|
-
* 1.
|
|
381
|
+
* Two ways in:
|
|
382
|
+
* 1. Agent prompt (primary) — copy curl command, agent gets device code
|
|
352
383
|
* 2. Email login (returning users — code emailed to verified address)
|
|
353
|
-
* 3. App ID + Secret (agent created the app)
|
|
354
|
-
* 4. Create new app (triggers POST /v1/apps)
|
|
355
384
|
*/
|
|
356
385
|
export async function renderLoginPage(c) {
|
|
357
386
|
const url = new URL(c.req.url);
|
|
358
387
|
const error = url.searchParams.get('error');
|
|
359
388
|
const errorMap = {
|
|
360
|
-
invalid: 'Invalid app ID or secret',
|
|
361
|
-
missing: 'Please provide both app ID and secret',
|
|
362
|
-
server: 'Server error. Please try again.',
|
|
363
389
|
email_missing: 'Please enter your email address.',
|
|
364
390
|
};
|
|
365
|
-
const CREATE_APP_SCRIPT = `
|
|
366
|
-
async function createApp() {
|
|
367
|
-
var btn = document.getElementById('create-btn');
|
|
368
|
-
btn.classList.add('loading');
|
|
369
|
-
btn.textContent = 'Creating...';
|
|
370
|
-
try {
|
|
371
|
-
var resp = await fetch('/v1/apps', { method: 'POST' });
|
|
372
|
-
var data = await resp.json();
|
|
373
|
-
if (data.app_id && data.app_secret) {
|
|
374
|
-
document.getElementById('new-app-id').textContent = data.app_id;
|
|
375
|
-
document.getElementById('new-app-secret').textContent = data.app_secret;
|
|
376
|
-
document.getElementById('create-result').classList.add('show');
|
|
377
|
-
btn.style.display = 'none';
|
|
378
|
-
} else {
|
|
379
|
-
btn.textContent = '[ERR] try again >';
|
|
380
|
-
btn.classList.remove('loading');
|
|
381
|
-
}
|
|
382
|
-
} catch (e) {
|
|
383
|
-
btn.textContent = '[ERR] try again >';
|
|
384
|
-
btn.classList.remove('loading');
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
function fillAndLogin() {
|
|
388
|
-
var appId = document.getElementById('new-app-id').textContent;
|
|
389
|
-
var secret = document.getElementById('new-app-secret').textContent;
|
|
390
|
-
document.getElementById('app_id').value = appId;
|
|
391
|
-
document.getElementById('app_secret').value = secret;
|
|
392
|
-
document.querySelector('form').submit();
|
|
393
|
-
}
|
|
394
|
-
`;
|
|
395
391
|
return c.html(_jsxs(LoginLayout, { title: "Dashboard Login - BOTCHA", children: [_jsx("a", { href: "/", class: "ascii-logo", children: `██████╗ ██████╗ ████████╗ ██████╗██╗ ██╗ █████╗
|
|
396
392
|
██╔══██╗██╔═══██╗╚══██╔══╝██╔════╝██║ ██║██╔══██╗
|
|
397
393
|
██████╔╝██║ ██║ ██║ ██║ ███████║███████║
|
|
398
394
|
██╔══██╗██║ ██║ ██║ ██║ ██╔══██║██╔══██║
|
|
399
395
|
██████╔╝╚██████╔╝ ██║ ╚██████╗██║ ██║██║ ██║
|
|
400
|
-
╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝` }), _jsxs("p", { class: "text-muted", style: "text-align: center; font-size: 0.75rem; margin: -1rem 0 2rem;", children: ['>', "_\
|
|
396
|
+
╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝` }), _jsxs("p", { class: "text-muted", style: "text-align: center; font-size: 0.75rem; margin: -1rem 0 2rem;", children: ['>', "_\u00A0dashboard login"] }), _jsx("p", { class: "text-muted", style: "font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.15em; text-align: center; margin-bottom: 0.625rem;", children: "Paste this into your AI agent" }), _jsx("div", { class: "card", style: "margin-bottom: 1.5rem;", children: _jsx("div", { class: "card-body", children: _jsxs("button", { id: "dash-prompt-btn", onclick: "copyDashPrompt()", type: "button", class: "card-inner", style: "display: block; width: 100%; padding: 1.5rem; border: none; border-radius: 0; cursor: pointer; font-family: var(--font); text-align: left; text-transform: none; letter-spacing: normal; box-shadow: none; transition: background 0.2s;", children: [_jsx("code", { id: "dash-prompt", style: "font-size: 0.9375rem; font-weight: 700; color: var(--accent); line-height: 1.5; display: block; background: none; border: none; padding: 0;", children: DASHBOARD_PROMPT }), _jsxs("span", { id: "dash-copy-label", style: "display: flex; align-items: center; gap: 0.375rem; margin-top: 1rem; font-size: 0.6875rem; font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; transition: color 0.2s;", children: [_jsx("span", { id: "dash-copy-icon", style: "display: flex; transition: color 0.2s;", dangerouslySetInnerHTML: { __html: COPY_ICON_SVG } }), _jsx("span", { id: "dash-copy-text", children: "Click to copy" })] })] }) }) }), _jsxs("p", { class: "text-muted", style: "font-size: 0.75rem; text-align: center; line-height: 1.8; margin-bottom: 1.5rem;", children: ["Your agent solves a challenge, gets a code, and gives you a link.", _jsx("br", {}), "Click it. You're in the dashboard."] }), _jsx(Divider, { text: "returning user?" }), _jsx("form", { method: "post", action: "/dashboard/email-login", children: _jsxs(Card, { title: "Email Login", children: [error === 'email_missing' && (_jsx("div", { class: "error-message", children: errorMap[error] })), _jsx("p", { class: "text-muted mb-2", style: "font-size: 0.75rem;", children: "Enter the email you used when creating your app. We'll send a login code to your inbox." }), _jsxs("div", { class: "form-group", children: [_jsx("label", { for: "email", children: "Email" }), _jsx("input", { type: "email", id: "email", name: "email", placeholder: "you@example.com", required: true, autocomplete: "email" })] }), _jsxs("button", { type: "submit", children: ["Email Me a Code ", '>'] })] }) }), _jsx("script", { dangerouslySetInnerHTML: { __html: LOGIN_COPY_SCRIPT } })] }));
|
|
401
397
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAqB5B,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,OAAO,eAAe,EAAE,WAAW,CAAC;IAChD,WAAW,EAAE,OAAO,eAAe,EAAE,WAAW,CAAC;IACjD,IAAI,EAAE,OAAO,eAAe,EAAE,WAAW,CAAC;IAC1C,SAAS,CAAC,EAAE,OAAO,cAAc,EAAE,sBAAsB,CAAC;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,QAAA,MAAM,SAAS;cAAwB,QAAQ;eAAa,SAAS;yCAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAqB5B,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,OAAO,eAAe,EAAE,WAAW,CAAC;IAChD,WAAW,EAAE,OAAO,eAAe,EAAE,WAAW,CAAC;IACjD,IAAI,EAAE,OAAO,eAAe,EAAE,WAAW,CAAC;IAC1C,SAAS,CAAC,EAAE,OAAO,cAAc,EAAE,sBAAsB,CAAC;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,QAAA,MAAM,SAAS;cAAwB,QAAQ;eAAa,SAAS;yCAAK,CAAC;AAgE3E,eAAe,SAAS,CAAC"}
|
package/dist/dashboard/index.js
CHANGED
|
@@ -26,6 +26,7 @@ dashboard.post('/email-login', handleEmailLogin);
|
|
|
26
26
|
dashboard.get('/logout', handleLogout);
|
|
27
27
|
// Device code pages (human enters code here)
|
|
28
28
|
dashboard.get('/code', renderDeviceCodePage);
|
|
29
|
+
dashboard.get('/code/:code', renderDeviceCodePage); // Pre-filled code from URL
|
|
29
30
|
dashboard.post('/code', handleDeviceCodeRedeem);
|
|
30
31
|
// ============ PROTECTED ROUTES (auth required) ============
|
|
31
32
|
// Apply auth middleware to all routes below
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* BOTCHA Landing Pages (JSX)
|
|
3
3
|
*
|
|
4
4
|
* Two views at GET /:
|
|
5
|
-
* - LandingPage:
|
|
5
|
+
* - LandingPage: ultra-minimal — one prompt to copy-paste to your agent
|
|
6
6
|
* - VerifiedLandingPage: for humans whose agent solved the challenge
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Design: the human's only job is to copy one prompt, paste it into their
|
|
9
|
+
* agent, and click the link the agent gives back. That's it.
|
|
10
10
|
*/
|
|
11
11
|
import type { FC } from 'hono/jsx';
|
|
12
12
|
export declare const LandingPage: FC<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"landing.d.ts","sourceRoot":"","sources":["../../src/dashboard/landing.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"landing.d.ts","sourceRoot":"","sources":["../../src/dashboard/landing.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAsCnC,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAyI/D,CAAC;AAyBF,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CA8F3E,CAAC"}
|
|
@@ -7,18 +7,39 @@ const BOTCHA_ASCII = `██████╗ ██████╗ ████
|
|
|
7
7
|
██╔══██╗██║ ██║ ██║ ██║ ██╔══██║██╔══██║
|
|
8
8
|
██████╔╝╚██████╔╝ ██║ ╚██████╗██║ ██║██║ ██║
|
|
9
9
|
╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝`;
|
|
10
|
+
const AGENT_PROMPT = `Run: curl -s https://botcha.ai -H 'Accept: application/json' — then follow the instructions in the response to verify yourself and get me access.`;
|
|
11
|
+
// Clipboard copy icon (Lucide-style, 16x16)
|
|
12
|
+
const COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"><rect x="9" y="9" width="13" height="13" rx="0"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/></svg>`;
|
|
13
|
+
const CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
14
|
+
const COPY_SCRIPT = `
|
|
15
|
+
function copyPrompt() {
|
|
16
|
+
var text = document.getElementById('agent-prompt').textContent.trim();
|
|
17
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
18
|
+
var label = document.getElementById('copy-label');
|
|
19
|
+
var icon = document.getElementById('copy-icon');
|
|
20
|
+
var txt = document.getElementById('copy-text');
|
|
21
|
+
label.style.color = 'var(--green)';
|
|
22
|
+
icon.innerHTML = '${CHECK_ICON.replace(/'/g, "\\'")}';
|
|
23
|
+
txt.textContent = 'Copied — now paste into your agent';
|
|
24
|
+
setTimeout(function() {
|
|
25
|
+
label.style.color = 'var(--text-muted)';
|
|
26
|
+
icon.innerHTML = '${COPY_ICON.replace(/'/g, "\\'")}';
|
|
27
|
+
txt.textContent = 'Click to copy';
|
|
28
|
+
}, 2500);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
10
32
|
// ============ UNVERIFIED LANDING PAGE ============
|
|
11
33
|
export const LandingPage = ({ version, error }) => {
|
|
12
|
-
return (_jsxs(LandingLayout, { version: version, children: [_jsx("a", { href: "/", class: "ascii-logo", children: BOTCHA_ASCII }), _jsxs("p", { class: "text-muted", style: "text-align: center; font-size: 0.75rem; margin: -1rem 0 0.5rem;", children: ['>', "_\u00A0prove you're a bot"] }), _jsx("p", { class: "landing-tagline", children: "
|
|
13
|
-
pip install botcha-verify # FastAPI/Django middleware` }) }), _jsxs("div", { class: "landing-links", style: "margin-top: 1rem;", children: [_jsx("a", { href: "/openapi.json", class: "landing-link", children: "OpenAPI" }), _jsx("a", { href: "https://github.com/dupe-com/botcha", class: "landing-link", children: "GitHub" }), _jsx("a", { href: "https://www.npmjs.com/package/@dupecom/botcha", class: "landing-link", children: "npm" }), _jsx("a", { href: "https://pypi.org/project/botcha/", class: "landing-link", children: "PyPI" })] })] }), _jsx("script", { type: "application/botcha+json", id: "botcha-challenge", dangerouslySetInnerHTML: {
|
|
34
|
+
return (_jsxs(LandingLayout, { version: version, children: [_jsx("a", { href: "/", class: "ascii-logo", children: BOTCHA_ASCII }), _jsxs("p", { class: "text-muted", style: "text-align: center; font-size: 0.75rem; margin: -1rem 0 0.5rem;", children: ['>', "_\u00A0prove you're a bot"] }), _jsx("p", { class: "landing-tagline", children: "This site is for AI agents. Bring yours." }), _jsx("p", { class: "text-muted", style: "font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.15em; text-align: center; margin: 2rem 0 0.625rem;", children: "Paste this into your AI agent" }), _jsx("div", { class: "card", style: "margin-bottom: 1.5rem;", children: _jsx("div", { class: "card-body", children: _jsxs("button", { id: "prompt-btn", onclick: "copyPrompt()", type: "button", class: "card-inner", style: "display: block; width: 100%; padding: 1.5rem; border: none; border-radius: 0; cursor: pointer; font-family: var(--font); text-align: left; text-transform: none; letter-spacing: normal; box-shadow: none; transition: background 0.2s;", children: [_jsx("code", { id: "agent-prompt", style: "font-size: 1.125rem; font-weight: 700; color: var(--accent); line-height: 1.5; display: block; background: none; border: none; padding: 0;", children: AGENT_PROMPT }), _jsxs("span", { id: "copy-label", style: "display: flex; align-items: center; gap: 0.375rem; margin-top: 1rem; font-size: 0.6875rem; font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; transition: color 0.2s;", children: [_jsx("span", { id: "copy-icon", style: "display: flex; transition: color 0.2s;", dangerouslySetInnerHTML: { __html: COPY_ICON } }), _jsx("span", { id: "copy-text", children: "Click to copy" })] })] }) }) }), _jsx("div", { style: "text-align: center; margin: 1.5rem 0;", children: _jsxs("p", { class: "text-muted", style: "font-size: 0.75rem; line-height: 2;", children: ["Your agent solves a challenge to prove it's a bot.", _jsx("br", {}), "It gives you a link back. Click it. You're in."] }) }), error && (_jsx("div", { style: "text-align: center; margin-bottom: 0.5rem;", children: _jsx("p", { style: "color: var(--red); font-size: 0.75rem;", children: error }) })), _jsxs("div", { style: "text-align: center; margin-top: 1.5rem; display: flex; flex-direction: column; align-items: center; gap: 0.625rem;", children: [_jsx("a", { href: "/dashboard/login", class: "text-muted", style: "font-size: 0.6875rem; text-decoration: underline; text-underline-offset: 3px;", children: "Already have an account? Log in" }), _jsxs("details", { style: "margin: 0;", children: [_jsx("summary", { class: "text-muted", style: "font-size: 0.6875rem; cursor: pointer; text-align: center; list-style: none; text-decoration: underline; text-underline-offset: 3px;", children: "Have a code?" }), _jsx("form", { method: "post", action: "/gate", style: "margin-top: 0.75rem;", children: _jsxs("div", { style: "display: flex; gap: 0;", children: [_jsx("input", { id: "code", name: "code", type: "text", placeholder: "BOTCHA-XXXXXX", required: true, autocomplete: "off", spellcheck: false, maxlength: 13, style: "flex: 1; font-size: 0.875rem; font-family: var(--font); font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; padding: 0.625rem; text-align: center; border: 1px solid var(--border); border-radius: 0; background: var(--bg); color: var(--text);" }), _jsxs("button", { type: "submit", style: "padding: 0.625rem 1rem; font-family: var(--font); font-size: 0.8125rem; font-weight: 600; background: var(--accent); color: #fff; border: 1px solid var(--accent); border-left: none; border-radius: 0; cursor: pointer; white-space: nowrap;", children: ["Go ", '>'] })] }) })] })] }), _jsx("script", { type: "application/botcha+json", id: "botcha-challenge", dangerouslySetInnerHTML: {
|
|
14
35
|
__html: JSON.stringify({
|
|
15
36
|
version: '1.0',
|
|
16
37
|
type: 'inline-challenge',
|
|
17
|
-
description: 'AI agents: solve this challenge to prove you
|
|
38
|
+
description: 'AI agents: solve this challenge to prove you are a bot!',
|
|
18
39
|
challenge: {
|
|
19
40
|
type: 'sha256',
|
|
20
41
|
instruction: "Compute SHA256 of the string 'BOTCHA-LANDING-' + today's date (YYYY-MM-DD format). Return first 16 hex chars.",
|
|
21
|
-
example: "For 2026-01-15: SHA256('BOTCHA-LANDING-2026-01-15')
|
|
42
|
+
example: "For 2026-01-15: SHA256('BOTCHA-LANDING-2026-01-15') -> first 16 chars",
|
|
22
43
|
submit_to: 'https://botcha.ai/api/verify-landing',
|
|
23
44
|
method: 'POST',
|
|
24
45
|
body_format: {
|
|
@@ -31,15 +52,34 @@ pip install botcha-verify # FastAPI/Django middleware` }) }), _jsxs("div", {
|
|
|
31
52
|
header_to_include: 'X-Botcha-Landing-Token',
|
|
32
53
|
},
|
|
33
54
|
}, null, 2),
|
|
34
|
-
} }), _jsxs("div", { class: "landing-footer", children: [_jsxs("span", { children: ["v", version] }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://botcha
|
|
55
|
+
} }), _jsx("script", { dangerouslySetInnerHTML: { __html: COPY_SCRIPT } }), _jsxs("div", { class: "landing-footer", children: [_jsxs("span", { children: ["v", version] }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "/openapi.json", children: "API" }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://github.com/dupe-com/botcha", children: "GitHub" }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://github.com/i8ramin", children: "@i8ramin" })] })] }));
|
|
35
56
|
};
|
|
36
57
|
// ============ VERIFIED LANDING PAGE ============
|
|
58
|
+
const ONBOARD_PROMPT = `Run: curl -s https://botcha.ai -H 'Accept: application/json' — then follow the instructions to verify yourself, create an app for me, and get me into the dashboard. Ask me for my email address when you need it.`;
|
|
59
|
+
const VERIFIED_COPY_SCRIPT = `
|
|
60
|
+
function copyOnboardPrompt() {
|
|
61
|
+
var text = document.getElementById('onboard-prompt').textContent.trim();
|
|
62
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
63
|
+
var label = document.getElementById('onboard-label');
|
|
64
|
+
var icon = document.getElementById('onboard-icon');
|
|
65
|
+
var txt = document.getElementById('onboard-text');
|
|
66
|
+
label.style.color = 'var(--green)';
|
|
67
|
+
icon.innerHTML = '${CHECK_ICON.replace(/'/g, "\\'")}';
|
|
68
|
+
txt.textContent = 'Copied — now paste into your agent';
|
|
69
|
+
setTimeout(function() {
|
|
70
|
+
label.style.color = 'var(--text-muted)';
|
|
71
|
+
icon.innerHTML = '${COPY_ICON.replace(/'/g, "\\'")}';
|
|
72
|
+
txt.textContent = 'Click to copy';
|
|
73
|
+
}, 2500);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
37
77
|
export const VerifiedLandingPage = ({ version, solveTime }) => {
|
|
38
|
-
return (_jsxs(LandingLayout, { version: version, children: [_jsx("a", { href: "/", class: "ascii-logo", children: BOTCHA_ASCII }), _jsxs("p", { class: "text-muted", style: "text-align: center; font-size: 0.75rem; margin: -1rem 0 0.5rem;", children: ['>', "_\u00A0verified"] }), _jsxs("p", { class: "landing-tagline", style: "color: var(--green);", children: ["Your agent proved it's a bot", solveTime ? ` in ${solveTime}ms` : '', ". Welcome."] }),
|
|
78
|
+
return (_jsxs(LandingLayout, { version: version, children: [_jsx("a", { href: "/", class: "ascii-logo", children: BOTCHA_ASCII }), _jsxs("p", { class: "text-muted", style: "text-align: center; font-size: 0.75rem; margin: -1rem 0 0.5rem;", children: ['>', "_\u00A0verified"] }), _jsxs("p", { class: "landing-tagline", style: "color: var(--green);", children: ["Your agent proved it's a bot", solveTime ? ` in ${solveTime}ms` : '', ". Welcome."] }), _jsx("p", { class: "text-muted", style: "font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.15em; text-align: center; margin: 2rem 0 0.625rem;", children: "Set up your account \u2014 paste this to your agent" }), _jsx("div", { class: "card", style: "margin-bottom: 1.5rem;", children: _jsx("div", { class: "card-body", children: _jsxs("button", { id: "onboard-btn", onclick: "copyOnboardPrompt()", type: "button", class: "card-inner", style: "display: block; width: 100%; padding: 1.5rem; border: none; border-radius: 0; cursor: pointer; font-family: var(--font); text-align: left; text-transform: none; letter-spacing: normal; box-shadow: none; transition: background 0.2s;", children: [_jsx("code", { id: "onboard-prompt", style: "font-size: 1rem; font-weight: 700; color: var(--accent); line-height: 1.5; display: block; background: none; border: none; padding: 0;", children: ONBOARD_PROMPT }), _jsxs("span", { id: "onboard-label", style: "display: flex; align-items: center; gap: 0.375rem; margin-top: 1rem; font-size: 0.6875rem; font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; transition: color 0.2s;", children: [_jsx("span", { id: "onboard-icon", style: "display: flex; transition: color 0.2s;", dangerouslySetInnerHTML: { __html: COPY_ICON } }), _jsx("span", { id: "onboard-text", children: "Click to copy" })] })] }) }) }), _jsx("div", { style: "text-align: center; margin: 1.5rem 0;", children: _jsxs("p", { class: "text-muted", style: "font-size: 0.75rem; line-height: 2;", children: ["Your agent will ask for your email, create your app,", _jsx("br", {}), "and give you a link to your dashboard. You just click it."] }) }), _jsx("div", { style: "text-align: center; margin-bottom: 1.5rem;", children: _jsx("a", { href: "/dashboard/login", style: "font-size: 0.75rem; color: var(--text-muted); text-decoration: underline; text-underline-offset: 3px;", children: "Already have an account? Go to dashboard" }) }), _jsxs(Card, { title: "For developers", children: [_jsx("p", { class: "text-muted", style: "font-size: 0.8125rem; line-height: 1.7; margin-bottom: 0.75rem;", children: "Protect your own APIs so only verified AI agents can access them:" }), _jsx("pre", { children: _jsx("code", { children: `# Client SDK (for your agent)
|
|
39
79
|
npm install @dupecom/botcha # TypeScript
|
|
40
80
|
pip install botcha # Python
|
|
41
81
|
|
|
42
82
|
# Server SDK (protect your APIs)
|
|
43
83
|
npm install @botcha/verify # Express/Hono
|
|
44
|
-
pip install botcha-verify # FastAPI/Django` }) }), _jsxs("div", { class: "landing-links", style: "margin-top: 1rem;", children: [_jsx("a", { href: "/openapi.json", class: "landing-link", children: "OpenAPI" }), _jsx("a", { href: "/ai.txt", class: "landing-link", children: "ai.txt" }), _jsx("a", { href: "https://github.com/dupe-com/botcha", class: "landing-link", children: "GitHub" }), _jsx("a", { href: "https://www.npmjs.com/package/@dupecom/botcha", class: "landing-link", children: "npm" }), _jsx("a", { href: "https://pypi.org/project/botcha/", class: "landing-link", children: "PyPI" })] })] }), _jsxs("div", { class: "landing-footer", children: [_jsxs("span", { children: ["v", version] }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://botcha.ai", children: "botcha.ai" }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://github.com/i8ramin", children: "@i8ramin" })] })] }));
|
|
84
|
+
pip install botcha-verify # FastAPI/Django` }) }), _jsxs("div", { class: "landing-links", style: "margin-top: 1rem;", children: [_jsx("a", { href: "/openapi.json", class: "landing-link", children: "OpenAPI" }), _jsx("a", { href: "/ai.txt", class: "landing-link", children: "ai.txt" }), _jsx("a", { href: "https://github.com/dupe-com/botcha", class: "landing-link", children: "GitHub" }), _jsx("a", { href: "https://www.npmjs.com/package/@dupecom/botcha", class: "landing-link", children: "npm" }), _jsx("a", { href: "https://pypi.org/project/botcha/", class: "landing-link", children: "PyPI" })] })] }), _jsx("script", { dangerouslySetInnerHTML: { __html: VERIFIED_COPY_SCRIPT } }), _jsxs("div", { class: "landing-footer", children: [_jsxs("span", { children: ["v", version] }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://botcha.ai", children: "botcha.ai" }), _jsx("span", { class: "landing-footer-sep", children: "\u00B7" }), _jsx("a", { href: "https://github.com/i8ramin", children: "@i8ramin" })] })] }));
|
|
45
85
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/dashboard/pages.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/dashboard/pages.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAgDnC,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAqG/C,CAAC"}
|
package/dist/dashboard/pages.js
CHANGED
|
@@ -8,7 +8,7 @@ const PeriodSelector = ({ currentPeriod = '24h', targetId, endpoint, }) => {
|
|
|
8
8
|
{ value: '7d', label: '7D' },
|
|
9
9
|
{ value: '30d', label: '30D' },
|
|
10
10
|
];
|
|
11
|
-
return (_jsx("div", { class: "period-selector", style: "display: flex; gap: 0.5rem; margin-bottom: 1rem;", children: periods.map((p) => (_jsx("button", { class: p.value === currentPeriod ? '' : 'secondary', "hx-get": `${endpoint}?period=${p.value}`, "hx-target": `#${targetId}`, "hx-swap": "innerHTML", style: "padding: 0.4rem 0.8rem; font-size: 0.75rem;", children: p.label }))) }));
|
|
11
|
+
return (_jsx("div", { class: "period-selector", style: "display: flex; gap: 0.5rem; margin-bottom: 1rem;", children: periods.map((p) => (_jsx("button", { class: p.value === currentPeriod ? '' : 'secondary', "hx-get": `${endpoint}?period=${p.value}`, "hx-target": `#${targetId}`, "hx-swap": "innerHTML", onclick: `this.parentElement.querySelectorAll('button').forEach(b=>b.className='secondary');this.className=''`, style: "padding: 0.4rem 0.8rem; font-size: 0.75rem;", children: p.label }))) }));
|
|
12
12
|
};
|
|
13
13
|
// ============ LOADING SKELETON ============
|
|
14
14
|
const LoadingSkeleton = () => (_jsxs("div", { children: [_jsx("div", { class: "skeleton skeleton-heading" }), _jsx("div", { class: "skeleton skeleton-text", style: "width: 80%;" }), _jsx("div", { class: "skeleton skeleton-text", style: "width: 60%;" }), _jsx("div", { class: "skeleton skeleton-text", style: "width: 90%;" })] }));
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
*
|
|
8
8
|
* JetBrains Mono · #ffffff bg · #1a1a1a text · black accent · square corners · dot shadows
|
|
9
9
|
*/
|
|
10
|
-
export declare const DASHBOARD_CSS = "\n /* ============ Reset ============ */\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n :root {\n /* ---- palette (black & white) ---- */\n --bg: #ffffff;\n --bg-card: #ffffff;\n --bg-raised: #eae8e4;\n --text: #1a1a1a;\n --text-muted: #6b6b6b;\n --text-dim: #a0a0a0;\n --accent: #1a1a1a;\n --accent-dim: #333333;\n --red: #cc2222;\n --amber: #b87a00;\n --green: #1a8a2a;\n --border: #ddd9d4;\n --border-bright: #c0bbb5;\n\n /* ---- type ---- */\n --font: 'JetBrains Mono', 'Courier New', monospace;\n\n /* ---- dot shadow (turbopuffer SVG pattern, black fill) ---- */\n --dot-shadow: url(\"data:image/svg+xml,%3Csvg width='7' height='13' viewBox='0 0 7 13' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.58984 12.2344V10.7051H6.52734V12.2344H5.58984ZM1.86328 12.2344V10.7051H2.79492V12.2344H1.86328ZM3.72656 10.0957V8.56641H4.6582V10.0957H3.72656ZM0 10.0957V8.56641H0.925781V10.0957H0ZM5.58984 7.95117V6.42188H6.52734V7.95117H5.58984ZM1.86328 7.95117V6.42188H2.79492V7.95117H1.86328ZM3.72656 5.8125V4.2832H4.6582V5.8125H3.72656ZM0 5.8125V4.2832H0.925781V5.8125H0ZM5.58984 3.66797V2.13867H6.52734V3.66797H5.58984ZM1.86328 3.66797V2.13867H2.79492V3.66797H1.86328ZM3.72656 1.5293V0H4.6582V1.5293H3.72656ZM0 1.5293V0H0.925781V1.5293H0Z' fill='%231a1a1a'/%3E%3C/svg%3E\");\n }\n\n /* ============ Base ============ */\n html, body {\n height: 100%;\n font-family: var(--font);\n font-size: 16px;\n line-height: 1.6;\n background: var(--bg);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n }\n\n body { display: flex; flex-direction: column; }\n\n ::selection { background: var(--accent); color: #fff; }\n\n a { color: var(--accent); }\n a:hover { text-decoration: none; opacity: 0.65; }\n\n /* ============ Scanline overlay (subtle CRT feel) ============ */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n background: repeating-linear-gradient(\n 0deg,\n transparent,\n transparent 2px,\n rgba(0, 0, 0, 0.012) 2px,\n rgba(0, 0, 0, 0.012) 4px\n );\n pointer-events: none;\n z-index: 9999;\n }\n\n /* ============ Dot shadow utility ============ */\n .dot-shadow { position: relative; }\n .dot-shadow::after {\n content: '';\n position: absolute;\n top: 0.5rem; left: 0.5rem;\n right: -0.5rem; bottom: -0.5rem;\n background-image: var(--dot-shadow);\n background-repeat: repeat;\n z-index: -1;\n pointer-events: none;\n opacity: 0.6;\n }\n\n /* ============ Navigation ============ */\n .dashboard-nav {\n background: var(--bg);\n border-bottom: 1px solid var(--border);\n position: sticky; top: 0; z-index: 100;\n }\n\n .nav-container {\n max-width: 1200px;\n margin: 0 auto;\n padding: 0.75rem 1.5rem;\n display: flex;\n align-items: center;\n gap: 1.5rem;\n }\n\n .nav-logo {\n font-weight: 700;\n font-size: 0.875rem;\n color: var(--text);\n text-decoration: none;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n }\n .nav-logo:hover { opacity: 1; }\n\n .nav-app-id {\n color: var(--text-muted);\n font-size: 0.75rem;\n margin-left: auto;\n }\n\n .nav-link {\n color: var(--text);\n text-decoration: none;\n font-size: 0.75rem;\n border: 1px solid var(--border-bright);\n padding: 0.25rem 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n transition: background 0.1s, color 0.1s;\n }\n .nav-link:hover {\n background: var(--accent);\n color: var(--bg);\n border-color: var(--accent);\n opacity: 1;\n }\n\n /* ============ Main content ============ */\n .dashboard-main {\n flex: 1;\n max-width: 1200px;\n width: 100%;\n margin: 0 auto;\n padding: 2rem 1.5rem;\n }\n\n /* ============ Card \u2014 primary container (Turbopuffer-style) ============ */\n .card {\n display: flex;\n flex-direction: column;\n margin-bottom: 1.5rem;\n }\n\n .card-header {\n margin-bottom: -1px; /* overlap the border */\n padding: 0;\n }\n\n .card-header h3 {\n position: relative;\n display: inline-flex;\n align-items: center;\n z-index: 10;\n top: 0.5rem;\n left: 0.5rem;\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n line-height: 1;\n color: var(--text);\n background: var(--bg);\n margin: 0;\n padding: 0 0.5rem;\n }\n\n .card-header h3 .card-title {\n }\n\n .card-header h3 .badge-inline {\n }\n\n .card-body {\n position: relative;\n border: 2px solid var(--border-bright);\n }\n\n .card-body::before {\n content: '';\n position: absolute;\n top: 0.5rem;\n left: 0.5rem;\n right: -0.5rem;\n bottom: -0.5rem;\n background-image: var(--dot-shadow);\n background-repeat: repeat;\n pointer-events: none;\n opacity: 0.6;\n }\n\n .card-inner {\n position: relative;\n z-index: 1;\n background: var(--bg-card);\n padding: 1.5rem;\n }\n\n /* Legacy fieldset support (deprecated \u2014 use .card instead) */\n fieldset {\n border: 2px solid var(--border-bright);\n border-radius: 0;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n position: relative;\n z-index: 0;\n }\n\n legend {\n padding: 0 0.5rem;\n font-size: 0.75rem;\n color: var(--text);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* ============ Dashboard grid ============ */\n .dashboard-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n margin-bottom: 1.5rem;\n }\n\n /* ============ Stat cards ============ */\n .stat-card {\n display: flex; flex-direction: column; gap: 0.25rem;\n padding: 1rem;\n border: 1px solid var(--border);\n background: var(--bg-card);\n }\n\n .stat-card .stat-value {\n font-size: 2rem; font-weight: 700; line-height: 1;\n color: var(--text);\n font-variant-numeric: tabular-nums;\n }\n\n .stat-card .stat-label {\n font-size: 0.6875rem;\n color: var(--text-muted);\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n .stat-card .stat-change { font-size: 0.75rem; font-weight: 500; }\n .stat-card .stat-change.positive { color: var(--green); }\n .stat-card .stat-change.negative { color: var(--red); }\n\n /* ============ Bar chart ============ */\n .bar-chart { width: 100%; }\n\n .bar-chart .bar-item { margin-bottom: 0.75rem; }\n\n .bar-chart .bar-label {\n display: flex; justify-content: space-between; align-items: center;\n margin-bottom: 0.125rem; font-size: 0.75rem;\n }\n .bar-chart .bar-name { color: var(--text); }\n .bar-chart .bar-value {\n color: var(--text-muted);\n font-weight: 700; font-variant-numeric: tabular-nums;\n }\n\n .bar-chart .bar {\n height: 18px;\n background: var(--accent);\n border-radius: 0;\n transition: width 0.3s ease;\n position: relative; overflow: hidden;\n opacity: 0.8;\n }\n .bar-chart .bar:hover { opacity: 1; }\n\n .bar-chart .bar-fill { height: 100%; background: var(--accent); }\n\n /* ============ Form controls ============ */\n input, select, textarea, button { font-family: var(--font); font-size: 0.875rem; }\n\n input[type=\"text\"],\n input[type=\"email\"],\n input[type=\"password\"],\n input[type=\"number\"],\n select,\n textarea {\n width: 100%;\n padding: 0.625rem 0.75rem;\n background: var(--bg);\n border: 1px solid var(--border-bright);\n border-radius: 0;\n color: var(--text);\n }\n\n input:focus, select:focus, textarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 1px var(--accent);\n }\n\n input::placeholder, textarea::placeholder { color: var(--text-dim); }\n\n label {\n display: block;\n margin-bottom: 0.375rem;\n font-size: 0.6875rem;\n color: var(--text-muted);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n\n .form-group { margin-bottom: 1.25rem; }\n\n /* ============ Buttons ============ */\n button, .button {\n display: inline-block;\n padding: 0.625rem 1.25rem;\n background: var(--accent);\n color: #fff;\n border: 1px solid var(--accent);\n border-radius: 0;\n font-weight: 700;\n cursor: pointer;\n text-decoration: none;\n text-align: center;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n font-size: 0.75rem;\n box-shadow:\n inset 1px 1px 0 rgba(255,255,255,0.15),\n inset -1px -1px 0 rgba(0,0,0,0.15),\n 2px 2px 0 rgba(0,0,0,0.1);\n transition: box-shadow 0.1s, transform 0.1s;\n }\n button:hover, .button:hover {\n box-shadow:\n inset 1px 1px 0 rgba(255,255,255,0.1),\n inset -1px -1px 0 rgba(0,0,0,0.15),\n 3px 3px 0 rgba(0,0,0,0.12);\n opacity: 1;\n }\n button:active, .button:active {\n transform: translate(1px, 1px);\n box-shadow: inset 1px 1px 3px rgba(0,0,0,0.25);\n }\n button:disabled, .button:disabled { opacity: 0.25; cursor: not-allowed; }\n\n button.secondary, .button.secondary {\n background: transparent;\n color: var(--text);\n border-color: var(--border-bright);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.05);\n }\n button.secondary:hover, .button.secondary:hover {\n border-color: var(--accent);\n color: var(--accent);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.1);\n }\n\n button.danger, .button.danger {\n background: var(--red);\n border-color: var(--red);\n color: #fff;\n box-shadow: 2px 2px 0 rgba(204,34,34,0.15);\n }\n button.danger:hover, .button.danger:hover {\n background: transparent;\n color: var(--red);\n }\n\n /* ============ Tables ============ */\n table { width: 100%; border-collapse: collapse; }\n\n thead { border-bottom: 1px solid var(--border-bright); }\n th {\n padding: 0.5rem 0.75rem; text-align: left;\n font-size: 0.6875rem; color: var(--text);\n font-weight: 700; text-transform: uppercase;\n letter-spacing: 0.1em;\n background: var(--bg-raised);\n }\n\n td {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--border);\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n }\n\n tbody tr:hover { background: var(--bg-raised); }\n\n /* ============ Code ============ */\n code, pre {\n font-family: var(--font);\n background: var(--bg-raised);\n padding: 0.125rem 0.375rem;\n border-radius: 0;\n font-size: 0.75rem;\n border: 1px solid var(--border);\n color: var(--text);\n }\n pre { padding: 1rem; overflow-x: auto; border: 1px solid var(--border-bright); }\n pre code { background: none; padding: 0; border: none; }\n\n /* ============ Login layout ============ */\n .login-container {\n min-height: 100vh;\n display: flex; align-items: center; justify-content: center;\n padding: 2rem;\n background: var(--bg);\n }\n .login-box { width: 100%; max-width: 420px; }\n\n .login-header { text-align: center; margin-bottom: 2rem; }\n .login-header h1 {\n font-size: 1.5rem; font-weight: 700; color: var(--text);\n letter-spacing: 0.15em; text-transform: uppercase;\n margin-bottom: 0.25rem;\n }\n .login-header p { color: var(--text-muted); font-size: 0.75rem; }\n\n /* ============ htmx loading ============ */\n .htmx-indicator { opacity: 0; transition: opacity 0.15s; }\n .htmx-request .htmx-indicator { opacity: 1; }\n .htmx-request.htmx-swapping { opacity: 0.3; pointer-events: none; }\n\n /* ============ Skeleton \u2014 blinking cursor ============ */\n .skeleton {\n background: var(--bg-raised);\n border: 1px solid var(--border);\n position: relative;\n overflow: hidden;\n }\n .skeleton::after {\n content: '';\n position: absolute; left: 0; top: 0;\n width: 2px; height: 100%;\n background: var(--text);\n animation: cursor-blink 0.8s step-end infinite;\n }\n @keyframes cursor-blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0; }\n }\n\n .skeleton-text { height: 1rem; margin-bottom: 0.5rem; }\n .skeleton-heading { height: 1.5rem; width: 60%; margin-bottom: 1rem; }\n\n /* ============ Utilities ============ */\n .text-center { text-align: center; }\n .text-right { text-align: right; }\n .text-muted { color: var(--text-muted); }\n .text-dim { color: var(--text-dim); }\n .text-success { color: var(--green); }\n .text-danger { color: var(--red); }\n .text-warning { color: var(--amber); }\n\n .mb-0 { margin-bottom: 0; }\n .mb-1 { margin-bottom: 0.5rem; }\n .mb-2 { margin-bottom: 1rem; }\n .mb-3 { margin-bottom: 1.5rem; }\n .mb-4 { margin-bottom: 2rem; }\n .mt-0 { margin-top: 0; }\n .mt-1 { margin-top: 0.5rem; }\n .mt-2 { margin-top: 1rem; }\n .mt-3 { margin-top: 1.5rem; }\n .mt-4 { margin-top: 2rem; }\n\n /* ============ Period selector ============ */\n .period-selector button {\n font-size: 0.625rem;\n padding: 0.2rem 0.5rem;\n }\n\n /* ============ Responsive ============ */\n @media (max-width: 768px) {\n html, body { font-size: 14px; }\n .dashboard-main { padding: 1rem; }\n .nav-container { padding: 0.5rem 1rem; }\n .dashboard-grid { grid-template-columns: 1fr; gap: 1rem; }\n .card-inner { padding: 1rem; }\n .card { margin-bottom: 1rem; }\n fieldset { padding: 1rem; margin-bottom: 1rem; }\n .stat-card .stat-value { font-size: 1.75rem; }\n table { font-size: 0.625rem; }\n th, td { padding: 0.375rem 0.5rem; }\n }\n\n @media (max-width: 480px) {\n .nav-container { flex-wrap: wrap; }\n .nav-app-id { margin-left: 0; width: 100%; order: 3; }\n }\n\n /* ============ Alerts ============ */\n .alert {\n padding: 0.75rem 1rem;\n border-radius: 0;\n margin-bottom: 1.5rem;\n border: 1px solid var(--border-bright);\n font-size: 0.75rem;\n background: var(--bg-card);\n }\n .alert::before { font-weight: 700; margin-right: 0.5rem; }\n\n .alert-info { border-color: var(--border-bright); color: var(--text); }\n .alert-info::before { content: '>'; color: var(--text); }\n\n .alert-success { border-color: var(--green); color: var(--green); }\n .alert-success::before { content: '[ok]'; }\n\n .alert-warning { border-color: var(--amber); color: var(--amber); }\n .alert-warning::before { content: '[!!]'; }\n\n .alert-danger { border-color: var(--red); color: var(--red); }\n .alert-danger::before { content: '[ERR]'; }\n\n /* ============ Badges ============ */\n .badge {\n display: inline-block;\n padding: 0.125rem 0.375rem;\n font-size: 0.625rem; font-weight: 700;\n border-radius: 0;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n border: 1px solid;\n }\n .badge-success { color: var(--green); border-color: var(--green); background: transparent; }\n .badge-danger { color: var(--red); border-color: var(--red); background: transparent; }\n .badge-warning { color: var(--amber); border-color: var(--amber); background: transparent; }\n .badge-info { color: #fff; border-color: var(--accent); background: var(--accent); }\n\n /* ============ Sample data indicator ============ */\n .sample-banner {\n background: #fffbe6;\n border: 1px solid var(--amber);\n color: var(--amber);\n padding: 0.5rem 0.75rem;\n font-size: 0.6875rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 1rem;\n }\n .sample-banner::before { content: '[demo]'; margin-right: 0.5rem; }\n .sample-tag {\n color: var(--amber);\n font-size: 0.625rem;\n font-weight: 700;\n letter-spacing: 0.05em;\n border: 1px solid var(--amber);\n padding: 0.0625rem 0.3rem;\n margin-left: 0.375rem;\n vertical-align: middle;\n background: #fffbe6;\n }\n\n /* ============ Empty state ============ */\n .empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-muted); }\n .empty-state-icon { font-size: 1.5rem; margin-bottom: 0.75rem; color: var(--text); font-weight: 700; }\n .empty-state-text { font-size: 0.8125rem; margin-bottom: 0.25rem; }\n .empty-state-subtext { font-size: 0.6875rem; color: var(--text-dim); }\n\n /* ============ Auth pages ============ */\n .ascii-logo {\n display: block; text-align: center; margin-bottom: 2rem;\n color: var(--text); font-size: 0.55rem; line-height: 1.2;\n white-space: pre; font-weight: 400;\n text-decoration: none;\n }\n\n .badge-inline {\n display: inline-block; font-size: 0.5625rem; font-weight: 700;\n color: var(--text-muted); border: 1px solid var(--border-bright);\n border-radius: 0; padding: 0.1rem 0.4rem;\n margin-left: 0.5rem; vertical-align: middle;\n text-transform: uppercase; letter-spacing: 0.05em;\n }\n\n .divider {\n display: flex; align-items: center; gap: 0.75rem;\n color: var(--text-dim); font-size: 0.6875rem;\n margin: 1.5rem 0;\n text-transform: uppercase; letter-spacing: 0.1em;\n white-space: nowrap;\n }\n .divider::before, .divider::after {\n content: ''; flex: 1;\n height: 1px; background: var(--border-bright);\n }\n\n .credentials-box {\n background: var(--bg); border: 1px solid var(--accent-dim);\n padding: 1rem; margin-bottom: 1rem;\n font-size: 0.75rem; line-height: 1.8; word-break: break-all;\n }\n .credentials-box .label { color: var(--text-muted); }\n .credentials-box .value { color: var(--text); font-weight: 700; }\n\n .warning {\n background: rgba(184,122,0,0.06); border: 1px solid var(--amber);\n padding: 0.75rem; margin-bottom: 1rem;\n font-size: 0.7rem; color: var(--amber);\n }\n .warning::before { content: '[!!] '; font-weight: 700; }\n\n .error-message {\n color: var(--red); margin: 0 0 1rem 0; font-size: 0.75rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid rgba(204,34,34,0.3);\n background: var(--bg);\n }\n .error-message::before { content: '[ERR] '; font-weight: 700; }\n\n .hint {\n font-size: 0.6875rem; color: var(--text-muted); line-height: 1.6;\n margin-top: 0.75rem;\n }\n .hint code {\n color: var(--text); background: var(--bg-raised);\n padding: 0.125rem 0.375rem; border: 1px solid var(--border);\n }\n\n .btn {\n display: block; width: 100%; text-align: center; text-decoration: none;\n }\n .btn-secondary {\n background: transparent; color: var(--text);\n border-color: var(--border-bright);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.05);\n }\n .btn-secondary:hover {\n border-color: var(--accent); color: var(--accent);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.1);\n }\n\n #create-result { display: none; }\n #create-result.show { display: block; }\n #create-btn.loading { opacity: 0.25; pointer-events: none; }\n\n /* ============ Landing page ============ */\n .landing-box { width: 100%; max-width: 580px; }\n .landing-box .ascii-logo { font-size: 0.75rem; margin-bottom: 1rem; }\n\n /* Landing page flows from top, not vertically centered like login */\n .login-container:has(.landing-box) {\n align-items: flex-start; padding-top: 4rem;\n }\n\n .landing-tagline {\n text-align: center; font-size: 0.8125rem;\n color: var(--text-muted); margin-bottom: 1.5rem;\n }\n\n .landing-links {\n display: flex; flex-wrap: wrap; justify-content: center;\n gap: 0.5rem; margin-bottom: 2rem;\n }\n .landing-link {\n font-size: 0.6875rem; color: var(--text);\n text-decoration: none; padding: 0.25rem 0.625rem;\n border: 1px solid var(--border-bright);\n transition: border-color 0.15s, background 0.15s;\n }\n .landing-link:hover {\n border-color: var(--accent); background: var(--bg-raised);\n }\n\n .landing-features {\n display: flex; flex-direction: column; gap: 0.75rem;\n margin-top: 1rem;\n }\n .landing-feature {\n display: flex; gap: 0.75rem; align-items: baseline;\n font-size: 0.75rem;\n }\n .landing-feature-label {\n font-weight: 700; color: var(--text);\n white-space: nowrap; min-width: 10rem;\n }\n .landing-feature-desc { color: var(--text-muted); }\n\n .landing-steps { display: flex; flex-direction: column; gap: 0.75rem; }\n .landing-step {\n display: flex; gap: 0.75rem; align-items: flex-start;\n font-size: 0.75rem; line-height: 1.6;\n }\n .landing-step-num {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 1.5rem; height: 1.5rem;\n border: 1px solid var(--border-bright);\n font-size: 0.6875rem; font-weight: 700; color: var(--text);\n flex-shrink: 0;\n }\n .landing-step-hint {\n display: block; font-size: 0.6875rem; color: var(--text-dim);\n margin-top: 0.125rem;\n }\n\n .landing-footer {\n text-align: center; padding: 2rem 0 0;\n font-size: 0.6875rem; color: var(--text-dim);\n }\n .landing-footer a {\n color: var(--text-muted); text-decoration: none;\n }\n .landing-footer a:hover { color: var(--text); }\n .landing-footer-sep { margin: 0 0.375rem; }\n\n /* ============ Scrollbar ============ */\n ::-webkit-scrollbar { width: 6px; height: 6px; }\n ::-webkit-scrollbar-track { background: var(--bg-raised); }\n ::-webkit-scrollbar-thumb { background: var(--border-bright); }\n ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }\n\n /* ============ Responsive (small screens) ============ */\n @media (max-width: 480px) {\n .ascii-logo { font-size: 0.4rem; }\n .landing-box .ascii-logo { font-size: 0.5rem; }\n .login-container { padding: 1rem; }\n .card-inner { padding: 1rem; }\n .landing-feature { flex-direction: column; gap: 0.125rem; }\n .landing-feature-label { min-width: auto; }\n }\n";
|
|
10
|
+
export declare const DASHBOARD_CSS = "\n /* ============ Reset ============ */\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n :root {\n /* ---- palette (black & white) ---- */\n --bg: #ffffff;\n --bg-card: #ffffff;\n --bg-raised: #eae8e4;\n --text: #1a1a1a;\n --text-muted: #6b6b6b;\n --text-dim: #a0a0a0;\n --accent: #1a1a1a;\n --accent-dim: #333333;\n --red: #cc2222;\n --amber: #b87a00;\n --green: #1a8a2a;\n --border: #ddd9d4;\n --border-bright: #c0bbb5;\n\n /* ---- type ---- */\n --font: 'JetBrains Mono', 'Courier New', monospace;\n\n /* ---- dot shadow (turbopuffer SVG pattern, black fill) ---- */\n --dot-shadow: url(\"data:image/svg+xml,%3Csvg width='7' height='13' viewBox='0 0 7 13' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.58984 12.2344V10.7051H6.52734V12.2344H5.58984ZM1.86328 12.2344V10.7051H2.79492V12.2344H1.86328ZM3.72656 10.0957V8.56641H4.6582V10.0957H3.72656ZM0 10.0957V8.56641H0.925781V10.0957H0ZM5.58984 7.95117V6.42188H6.52734V7.95117H5.58984ZM1.86328 7.95117V6.42188H2.79492V7.95117H1.86328ZM3.72656 5.8125V4.2832H4.6582V5.8125H3.72656ZM0 5.8125V4.2832H0.925781V5.8125H0ZM5.58984 3.66797V2.13867H6.52734V3.66797H5.58984ZM1.86328 3.66797V2.13867H2.79492V3.66797H1.86328ZM3.72656 1.5293V0H4.6582V1.5293H3.72656ZM0 1.5293V0H0.925781V1.5293H0Z' fill='%231a1a1a'/%3E%3C/svg%3E\");\n }\n\n /* ============ Base ============ */\n html, body {\n height: 100%;\n font-family: var(--font);\n font-size: 16px;\n line-height: 1.6;\n background: var(--bg);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n }\n\n body { display: flex; flex-direction: column; }\n\n ::selection { background: var(--accent); color: #fff; }\n\n a { color: var(--accent); }\n a:hover { text-decoration: none; opacity: 0.65; }\n\n /* ============ Scanline overlay (subtle CRT feel) ============ */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n background: repeating-linear-gradient(\n 0deg,\n transparent,\n transparent 2px,\n rgba(0, 0, 0, 0.012) 2px,\n rgba(0, 0, 0, 0.012) 4px\n );\n pointer-events: none;\n z-index: 9999;\n }\n\n /* ============ Dot shadow utility ============ */\n .dot-shadow { position: relative; }\n .dot-shadow::after {\n content: '';\n position: absolute;\n top: 0.5rem; left: 0.5rem;\n right: -0.5rem; bottom: -0.5rem;\n background-image: var(--dot-shadow);\n background-repeat: repeat;\n z-index: -1;\n pointer-events: none;\n opacity: 0.6;\n }\n\n /* ============ Navigation ============ */\n .dashboard-nav {\n background: var(--bg);\n border-bottom: 1px solid var(--border);\n position: sticky; top: 0; z-index: 100;\n }\n\n .nav-container {\n max-width: 1200px;\n margin: 0 auto;\n padding: 0.75rem 1.5rem;\n display: flex;\n align-items: center;\n gap: 1.5rem;\n }\n\n .nav-logo {\n font-weight: 700;\n font-size: 0.875rem;\n color: var(--text);\n text-decoration: none;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n }\n .nav-logo:hover { opacity: 1; }\n\n .nav-app-id {\n color: var(--text-muted);\n font-size: 0.75rem;\n margin-left: auto;\n }\n\n .nav-link {\n color: var(--text);\n text-decoration: none;\n font-size: 0.75rem;\n border: 1px solid var(--border-bright);\n padding: 0.25rem 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n transition: background 0.1s, color 0.1s;\n }\n .nav-link:hover {\n background: var(--accent);\n color: var(--bg);\n border-color: var(--accent);\n opacity: 1;\n }\n\n /* ============ Main content ============ */\n .dashboard-main {\n flex: 1;\n max-width: 1200px;\n width: 100%;\n margin: 0 auto;\n padding: 2rem 1.5rem;\n }\n\n /* ============ Card \u2014 primary container (Turbopuffer-style) ============ */\n .card {\n display: flex;\n flex-direction: column;\n margin-bottom: 1.5rem;\n }\n\n .card-header {\n margin-bottom: -1px; /* overlap the border */\n padding: 0;\n }\n\n .card-header h3 {\n position: relative;\n display: inline-flex;\n align-items: center;\n z-index: 10;\n top: 0.5rem;\n left: 0.5rem;\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n line-height: 1;\n color: var(--text);\n background: var(--bg);\n margin: 0;\n padding: 0 0.5rem;\n }\n\n .card-header h3 .card-title {\n }\n\n .card-header h3 .badge-inline {\n }\n\n .card-body {\n position: relative;\n border: 2px solid var(--border-bright);\n }\n\n .card-body::before {\n content: '';\n position: absolute;\n top: 0.5rem;\n left: 0.5rem;\n right: -0.5rem;\n bottom: -0.5rem;\n background-image: var(--dot-shadow);\n background-repeat: repeat;\n pointer-events: none;\n opacity: 0.6;\n }\n\n .card-inner {\n position: relative;\n z-index: 1;\n background: var(--bg-card);\n padding: 1.5rem;\n }\n\n /* Legacy fieldset support (deprecated \u2014 use .card instead) */\n fieldset {\n border: 2px solid var(--border-bright);\n border-radius: 0;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n position: relative;\n z-index: 0;\n }\n\n legend {\n padding: 0 0.5rem;\n font-size: 0.75rem;\n color: var(--text);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* ============ Dashboard grid ============ */\n .dashboard-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n margin-bottom: 1.5rem;\n }\n\n /* ============ Stat cards ============ */\n .stat-card {\n display: flex; flex-direction: column; gap: 0.25rem;\n padding: 1rem;\n border: 1px solid var(--border);\n background: var(--bg-card);\n }\n\n .stat-card .stat-value {\n font-size: 2rem; font-weight: 700; line-height: 1;\n color: var(--text);\n font-variant-numeric: tabular-nums;\n }\n\n .stat-card .stat-label {\n font-size: 0.6875rem;\n color: var(--text-muted);\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n .stat-card .stat-change { font-size: 0.75rem; font-weight: 500; }\n .stat-card .stat-change.positive { color: var(--green); }\n .stat-card .stat-change.negative { color: var(--red); }\n\n /* ============ Bar chart ============ */\n .bar-chart { width: 100%; }\n\n .bar-chart .bar-item { margin-bottom: 0.75rem; }\n\n .bar-chart .bar-label {\n display: flex; justify-content: space-between; align-items: center;\n margin-bottom: 0.125rem; font-size: 0.75rem;\n }\n .bar-chart .bar-name { color: var(--text); }\n .bar-chart .bar-value {\n color: var(--text-muted);\n font-weight: 700; font-variant-numeric: tabular-nums;\n }\n\n .bar-chart .bar {\n height: 18px;\n background: var(--accent);\n border-radius: 0;\n transition: width 0.3s ease;\n position: relative; overflow: hidden;\n opacity: 0.8;\n }\n .bar-chart .bar:hover { opacity: 1; }\n\n .bar-chart .bar-fill { height: 100%; background: var(--accent); }\n\n /* ============ Form controls ============ */\n input, select, textarea, button { font-family: var(--font); font-size: 0.875rem; }\n\n input[type=\"text\"],\n input[type=\"email\"],\n input[type=\"password\"],\n input[type=\"number\"],\n select,\n textarea {\n width: 100%;\n padding: 0.625rem 0.75rem;\n background: var(--bg);\n border: 1px solid var(--border-bright);\n border-radius: 0;\n color: var(--text);\n }\n\n input:focus, select:focus, textarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 1px var(--accent);\n }\n\n input::placeholder, textarea::placeholder { color: var(--text-dim); }\n\n label {\n display: block;\n margin-bottom: 0.375rem;\n font-size: 0.6875rem;\n color: var(--text-muted);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n\n .form-group { margin-bottom: 1.25rem; }\n\n /* ============ Buttons ============ */\n button, .button {\n display: inline-block;\n padding: 0.625rem 1.25rem;\n background: var(--accent);\n color: #fff;\n border: 1px solid var(--accent);\n border-radius: 0;\n font-weight: 700;\n cursor: pointer;\n text-decoration: none;\n text-align: center;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n font-size: 0.75rem;\n box-shadow:\n inset 1px 1px 0 rgba(255,255,255,0.15),\n inset -1px -1px 0 rgba(0,0,0,0.15),\n 2px 2px 0 rgba(0,0,0,0.1);\n transition: box-shadow 0.1s, transform 0.1s;\n }\n button:hover, .button:hover {\n box-shadow:\n inset 1px 1px 0 rgba(255,255,255,0.1),\n inset -1px -1px 0 rgba(0,0,0,0.15),\n 3px 3px 0 rgba(0,0,0,0.12);\n opacity: 1;\n }\n button:active, .button:active {\n transform: translate(1px, 1px);\n box-shadow: inset 1px 1px 3px rgba(0,0,0,0.25);\n }\n button:disabled, .button:disabled { opacity: 0.25; cursor: not-allowed; }\n\n button.secondary, .button.secondary {\n background: transparent;\n color: var(--text);\n border-color: var(--border-bright);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.05);\n }\n button.secondary:hover, .button.secondary:hover {\n border-color: var(--accent);\n color: var(--accent);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.1);\n }\n\n button.danger, .button.danger {\n background: var(--red);\n border-color: var(--red);\n color: #fff;\n box-shadow: 2px 2px 0 rgba(204,34,34,0.15);\n }\n button.danger:hover, .button.danger:hover {\n background: transparent;\n color: var(--red);\n }\n\n /* ============ Tables ============ */\n table { width: 100%; border-collapse: collapse; }\n\n thead { border-bottom: 1px solid var(--border-bright); }\n th {\n padding: 0.5rem 0.75rem; text-align: left;\n font-size: 0.6875rem; color: var(--text);\n font-weight: 700; text-transform: uppercase;\n letter-spacing: 0.1em;\n background: var(--bg-raised);\n }\n\n td {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--border);\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n }\n\n tbody tr:hover { background: var(--bg-raised); }\n\n /* ============ Code ============ */\n code, pre {\n font-family: var(--font);\n background: var(--bg-raised);\n padding: 0.125rem 0.375rem;\n border-radius: 0;\n font-size: 0.75rem;\n border: 1px solid var(--border);\n color: var(--text);\n }\n pre { padding: 1rem; overflow-x: auto; border: 1px solid var(--border-bright); }\n pre code { background: none; padding: 0; border: none; }\n\n /* ============ Login layout ============ */\n .login-container {\n min-height: 100vh;\n display: flex; align-items: flex-start; justify-content: center;\n padding: 4rem 2rem 2rem;\n background: var(--bg);\n }\n .login-box { width: 100%; max-width: 420px; }\n\n .login-header { text-align: center; margin-bottom: 2rem; }\n .login-header h1 {\n font-size: 1.5rem; font-weight: 700; color: var(--text);\n letter-spacing: 0.15em; text-transform: uppercase;\n margin-bottom: 0.25rem;\n }\n .login-header p { color: var(--text-muted); font-size: 0.75rem; }\n\n /* ============ htmx loading ============ */\n .htmx-indicator { opacity: 0; transition: opacity 0.15s; }\n .htmx-request .htmx-indicator { opacity: 1; }\n .htmx-request.htmx-swapping { opacity: 0.3; pointer-events: none; }\n\n /* ============ Skeleton \u2014 blinking cursor ============ */\n .skeleton {\n background: var(--bg-raised);\n border: 1px solid var(--border);\n position: relative;\n overflow: hidden;\n }\n .skeleton::after {\n content: '';\n position: absolute; left: 0; top: 0;\n width: 2px; height: 100%;\n background: var(--text);\n animation: cursor-blink 0.8s step-end infinite;\n }\n @keyframes cursor-blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0; }\n }\n\n .skeleton-text { height: 1rem; margin-bottom: 0.5rem; }\n .skeleton-heading { height: 1.5rem; width: 60%; margin-bottom: 1rem; }\n\n /* ============ Utilities ============ */\n .text-center { text-align: center; }\n .text-right { text-align: right; }\n .text-muted { color: var(--text-muted); }\n .text-dim { color: var(--text-dim); }\n .text-success { color: var(--green); }\n .text-danger { color: var(--red); }\n .text-warning { color: var(--amber); }\n\n .mb-0 { margin-bottom: 0; }\n .mb-1 { margin-bottom: 0.5rem; }\n .mb-2 { margin-bottom: 1rem; }\n .mb-3 { margin-bottom: 1.5rem; }\n .mb-4 { margin-bottom: 2rem; }\n .mt-0 { margin-top: 0; }\n .mt-1 { margin-top: 0.5rem; }\n .mt-2 { margin-top: 1rem; }\n .mt-3 { margin-top: 1.5rem; }\n .mt-4 { margin-top: 2rem; }\n\n /* ============ Period selector ============ */\n .period-selector button {\n font-size: 0.625rem;\n padding: 0.2rem 0.5rem;\n }\n\n /* ============ Responsive ============ */\n @media (max-width: 768px) {\n html, body { font-size: 14px; }\n .dashboard-main { padding: 1rem; }\n .nav-container { padding: 0.5rem 1rem; }\n .dashboard-grid { grid-template-columns: 1fr; gap: 1rem; }\n .card-inner { padding: 1rem; }\n .card { margin-bottom: 1rem; }\n fieldset { padding: 1rem; margin-bottom: 1rem; }\n .stat-card .stat-value { font-size: 1.75rem; }\n table { font-size: 0.625rem; }\n th, td { padding: 0.375rem 0.5rem; }\n }\n\n @media (max-width: 480px) {\n .nav-container { flex-wrap: wrap; }\n .nav-app-id { margin-left: 0; width: 100%; order: 3; }\n }\n\n /* ============ Alerts ============ */\n .alert {\n padding: 0.75rem 1rem;\n border-radius: 0;\n margin-bottom: 1.5rem;\n border: 1px solid var(--border-bright);\n font-size: 0.75rem;\n background: var(--bg-card);\n }\n .alert::before { font-weight: 700; margin-right: 0.5rem; }\n\n .alert-info { border-color: var(--border-bright); color: var(--text); }\n .alert-info::before { content: '>'; color: var(--text); }\n\n .alert-success { border-color: var(--green); color: var(--green); }\n .alert-success::before { content: '[ok]'; }\n\n .alert-warning { border-color: var(--amber); color: var(--amber); }\n .alert-warning::before { content: '[!!]'; }\n\n .alert-danger { border-color: var(--red); color: var(--red); }\n .alert-danger::before { content: '[ERR]'; }\n\n /* ============ Badges ============ */\n .badge {\n display: inline-block;\n padding: 0.125rem 0.375rem;\n font-size: 0.625rem; font-weight: 700;\n border-radius: 0;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n border: 1px solid;\n }\n .badge-success { color: var(--green); border-color: var(--green); background: transparent; }\n .badge-danger { color: var(--red); border-color: var(--red); background: transparent; }\n .badge-warning { color: var(--amber); border-color: var(--amber); background: transparent; }\n .badge-info { color: #fff; border-color: var(--accent); background: var(--accent); }\n\n /* ============ Sample data indicator ============ */\n .sample-banner {\n background: #fffbe6;\n border: 1px solid var(--amber);\n color: var(--amber);\n padding: 0.5rem 0.75rem;\n font-size: 0.6875rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 1rem;\n }\n .sample-banner::before { content: '[demo]'; margin-right: 0.5rem; }\n .sample-tag {\n color: var(--amber);\n font-size: 0.625rem;\n font-weight: 700;\n letter-spacing: 0.05em;\n border: 1px solid var(--amber);\n padding: 0.0625rem 0.3rem;\n margin-left: 0.375rem;\n vertical-align: middle;\n background: #fffbe6;\n }\n\n /* ============ Empty state ============ */\n .empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-muted); }\n .empty-state-icon { font-size: 1.5rem; margin-bottom: 0.75rem; color: var(--text); font-weight: 700; }\n .empty-state-text { font-size: 0.8125rem; margin-bottom: 0.25rem; }\n .empty-state-subtext { font-size: 0.6875rem; color: var(--text-dim); }\n\n /* ============ Auth pages ============ */\n .ascii-logo {\n display: block; text-align: center; margin-bottom: 2rem;\n color: var(--text); font-size: 0.55rem; line-height: 1.2;\n white-space: pre; font-weight: 400;\n text-decoration: none;\n }\n\n .badge-inline {\n display: inline-block; font-size: 0.5625rem; font-weight: 700;\n color: var(--text-muted); border: 1px solid var(--border-bright);\n border-radius: 0; padding: 0.1rem 0.4rem;\n margin-left: 0.5rem; vertical-align: middle;\n text-transform: uppercase; letter-spacing: 0.05em;\n }\n\n .divider {\n display: flex; align-items: center; gap: 0.75rem;\n color: var(--text-dim); font-size: 0.6875rem;\n margin: 1.5rem 0;\n text-transform: uppercase; letter-spacing: 0.1em;\n white-space: nowrap;\n }\n .divider::before, .divider::after {\n content: ''; flex: 1;\n height: 1px; background: var(--border-bright);\n }\n\n .credentials-box {\n background: var(--bg); border: 1px solid var(--accent-dim);\n padding: 1rem; margin-bottom: 1rem;\n font-size: 0.75rem; line-height: 1.8; word-break: break-all;\n }\n .credentials-box .label { color: var(--text-muted); }\n .credentials-box .value { color: var(--text); font-weight: 700; }\n\n .warning {\n background: rgba(184,122,0,0.06); border: 1px solid var(--amber);\n padding: 0.75rem; margin-bottom: 1rem;\n font-size: 0.7rem; color: var(--amber);\n }\n .warning::before { content: '[!!] '; font-weight: 700; }\n\n .error-message {\n color: var(--red); margin: 0 0 1rem 0; font-size: 0.75rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid rgba(204,34,34,0.3);\n background: var(--bg);\n }\n .error-message::before { content: '[ERR] '; font-weight: 700; }\n\n .hint {\n font-size: 0.6875rem; color: var(--text-muted); line-height: 1.6;\n margin-top: 0.75rem;\n }\n .hint code {\n color: var(--text); background: var(--bg-raised);\n padding: 0.125rem 0.375rem; border: 1px solid var(--border);\n }\n\n .btn {\n display: block; width: 100%; text-align: center; text-decoration: none;\n }\n .btn-secondary {\n background: transparent; color: var(--text);\n border-color: var(--border-bright);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.05);\n }\n .btn-secondary:hover {\n border-color: var(--accent); color: var(--accent);\n box-shadow: 2px 2px 0 rgba(0,0,0,0.1);\n }\n\n #create-result { display: none; }\n #create-result.show { display: block; }\n #create-btn.loading { opacity: 0.25; pointer-events: none; }\n\n /* ============ Landing page ============ */\n .landing-box { width: 100%; max-width: 580px; }\n .landing-box .ascii-logo { font-size: 0.75rem; margin-bottom: 1rem; }\n\n /* Landing page flows from top, not vertically centered like login */\n .login-container:has(.landing-box) {\n align-items: flex-start; padding-top: 4rem;\n }\n\n .landing-tagline {\n text-align: center; font-size: 0.8125rem;\n color: var(--text-muted); margin-bottom: 1.5rem;\n }\n\n .landing-links {\n display: flex; flex-wrap: wrap; justify-content: center;\n gap: 0.5rem; margin-bottom: 2rem;\n }\n .landing-link {\n font-size: 0.6875rem; color: var(--text);\n text-decoration: none; padding: 0.25rem 0.625rem;\n border: 1px solid var(--border-bright);\n transition: border-color 0.15s, background 0.15s;\n }\n .landing-link:hover {\n border-color: var(--accent); background: var(--bg-raised);\n }\n\n .landing-features {\n display: flex; flex-direction: column; gap: 0.75rem;\n margin-top: 1rem;\n }\n .landing-feature {\n display: flex; gap: 0.75rem; align-items: baseline;\n font-size: 0.75rem;\n }\n .landing-feature-label {\n font-weight: 700; color: var(--text);\n white-space: nowrap; min-width: 10rem;\n }\n .landing-feature-desc { color: var(--text-muted); }\n\n .landing-steps { display: flex; flex-direction: column; gap: 0.75rem; }\n .landing-step {\n display: flex; gap: 0.75rem; align-items: flex-start;\n font-size: 0.75rem; line-height: 1.6;\n }\n .landing-step-num {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 1.5rem; height: 1.5rem;\n border: 1px solid var(--border-bright);\n font-size: 0.6875rem; font-weight: 700; color: var(--text);\n flex-shrink: 0;\n }\n .landing-step-hint {\n display: block; font-size: 0.6875rem; color: var(--text-dim);\n margin-top: 0.125rem;\n }\n\n .landing-footer {\n text-align: center; padding: 2rem 0 3rem;\n font-size: 0.6875rem; color: var(--text-dim);\n }\n .landing-footer a {\n color: var(--text-muted); text-decoration: none;\n }\n .landing-footer a:hover { color: var(--text); }\n .landing-footer-sep { margin: 0 0.375rem; }\n\n /* ============ Scrollbar ============ */\n ::-webkit-scrollbar { width: 6px; height: 6px; }\n ::-webkit-scrollbar-track { background: var(--bg-raised); }\n ::-webkit-scrollbar-thumb { background: var(--border-bright); }\n ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }\n\n /* ============ Responsive (small screens) ============ */\n @media (max-width: 480px) {\n .ascii-logo { font-size: 0.4rem; }\n .landing-box .ascii-logo { font-size: 0.5rem; }\n .login-container { padding: 1rem; }\n .card-inner { padding: 1rem; }\n .landing-feature { flex-direction: column; gap: 0.125rem; }\n .landing-feature-label { min-width: auto; }\n }\n";
|
|
11
11
|
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/dashboard/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/dashboard/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,aAAa,0wqBAitBzB,CAAC"}
|
package/dist/dashboard/styles.js
CHANGED
|
@@ -412,8 +412,8 @@ export const DASHBOARD_CSS = `
|
|
|
412
412
|
/* ============ Login layout ============ */
|
|
413
413
|
.login-container {
|
|
414
414
|
min-height: 100vh;
|
|
415
|
-
display: flex; align-items:
|
|
416
|
-
padding: 2rem;
|
|
415
|
+
display: flex; align-items: flex-start; justify-content: center;
|
|
416
|
+
padding: 4rem 2rem 2rem;
|
|
417
417
|
background: var(--bg);
|
|
418
418
|
}
|
|
419
419
|
.login-box { width: 100%; max-width: 420px; }
|
|
@@ -704,7 +704,7 @@ export const DASHBOARD_CSS = `
|
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
.landing-footer {
|
|
707
|
-
text-align: center; padding: 2rem 0
|
|
707
|
+
text-align: center; padding: 2rem 0 3rem;
|
|
708
708
|
font-size: 0.6875rem; color: var(--text-dim);
|
|
709
709
|
}
|
|
710
710
|
.landing-footer a {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* BOTCHA - Cloudflare Workers Edition v0.
|
|
2
|
+
* BOTCHA - Cloudflare Workers Edition v0.12.0
|
|
3
3
|
*
|
|
4
4
|
* Prove you're a bot. Humans need not apply.
|
|
5
5
|
*
|
|
@@ -13,6 +13,7 @@ type Bindings = {
|
|
|
13
13
|
RATE_LIMITS: KVNamespace;
|
|
14
14
|
APPS: KVNamespace;
|
|
15
15
|
AGENTS: KVNamespace;
|
|
16
|
+
SESSIONS: KVNamespace;
|
|
16
17
|
ANALYTICS?: AnalyticsEngineDataset;
|
|
17
18
|
JWT_SECRET: string;
|
|
18
19
|
BOTCHA_VERSION: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAYL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAYL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAyBtB,OAAO,EACL,KAAK,sBAAsB,EAM5B,MAAM,aAAa,CAAC;AAGrB,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,YAAY,CAAC,EAAE;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,iBAAiB,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,GAAG;cAAwB,QAAQ;eAAa,SAAS;yCAAK,CAAC;AA0iErE,eAAe,GAAG,CAAC;AAGnB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|