@dupecom/botcha-cloudflare 0.3.3 → 0.10.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 (47) hide show
  1. package/dist/analytics.d.ts +60 -0
  2. package/dist/analytics.d.ts.map +1 -0
  3. package/dist/analytics.js +130 -0
  4. package/dist/apps.d.ts +159 -0
  5. package/dist/apps.d.ts.map +1 -0
  6. package/dist/apps.js +307 -0
  7. package/dist/auth.d.ts +93 -6
  8. package/dist/auth.d.ts.map +1 -1
  9. package/dist/auth.js +251 -9
  10. package/dist/challenges.d.ts +31 -7
  11. package/dist/challenges.d.ts.map +1 -1
  12. package/dist/challenges.js +551 -144
  13. package/dist/dashboard/api.d.ts +70 -0
  14. package/dist/dashboard/api.d.ts.map +1 -0
  15. package/dist/dashboard/api.js +546 -0
  16. package/dist/dashboard/auth.d.ts +183 -0
  17. package/dist/dashboard/auth.d.ts.map +1 -0
  18. package/dist/dashboard/auth.js +401 -0
  19. package/dist/dashboard/device-code.d.ts +43 -0
  20. package/dist/dashboard/device-code.d.ts.map +1 -0
  21. package/dist/dashboard/device-code.js +77 -0
  22. package/dist/dashboard/index.d.ts +31 -0
  23. package/dist/dashboard/index.d.ts.map +1 -0
  24. package/dist/dashboard/index.js +64 -0
  25. package/dist/dashboard/layout.d.ts +47 -0
  26. package/dist/dashboard/layout.d.ts.map +1 -0
  27. package/dist/dashboard/layout.js +38 -0
  28. package/dist/dashboard/pages.d.ts +11 -0
  29. package/dist/dashboard/pages.d.ts.map +1 -0
  30. package/dist/dashboard/pages.js +18 -0
  31. package/dist/dashboard/styles.d.ts +11 -0
  32. package/dist/dashboard/styles.d.ts.map +1 -0
  33. package/dist/dashboard/styles.js +633 -0
  34. package/dist/email.d.ts +44 -0
  35. package/dist/email.d.ts.map +1 -0
  36. package/dist/email.js +119 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +644 -50
  40. package/dist/rate-limit.d.ts +11 -1
  41. package/dist/rate-limit.d.ts.map +1 -1
  42. package/dist/rate-limit.js +13 -2
  43. package/dist/routes/stream.js +1 -1
  44. package/dist/static.d.ts +728 -0
  45. package/dist/static.d.ts.map +1 -0
  46. package/dist/static.js +818 -0
  47. package/package.json +1 -1
@@ -0,0 +1,633 @@
1
+ /**
2
+ * BOTCHA Dashboard CSS
3
+ *
4
+ * Light terminal aesthetic — black and white with offwhite background.
5
+ * Borrows structural patterns from turbopuffer (dot shadows, hard borders,
6
+ * layered box-shadows on buttons) but with BOTCHA's own identity.
7
+ *
8
+ * JetBrains Mono · #ffffff bg · #1a1a1a text · black accent · square corners · dot shadows
9
+ */
10
+ export const DASHBOARD_CSS = `
11
+ /* ============ Reset ============ */
12
+ * { margin: 0; padding: 0; box-sizing: border-box; }
13
+
14
+ :root {
15
+ /* ---- palette (black & white) ---- */
16
+ --bg: #ffffff;
17
+ --bg-card: #ffffff;
18
+ --bg-raised: #eae8e4;
19
+ --text: #1a1a1a;
20
+ --text-muted: #6b6b6b;
21
+ --text-dim: #a0a0a0;
22
+ --accent: #1a1a1a;
23
+ --accent-dim: #333333;
24
+ --red: #cc2222;
25
+ --amber: #b87a00;
26
+ --green: #1a8a2a;
27
+ --border: #ddd9d4;
28
+ --border-bright: #c0bbb5;
29
+
30
+ /* ---- type ---- */
31
+ --font: 'JetBrains Mono', 'Courier New', monospace;
32
+
33
+ /* ---- dot shadow (turbopuffer SVG pattern, black fill) ---- */
34
+ --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");
35
+ }
36
+
37
+ /* ============ Base ============ */
38
+ html, body {
39
+ height: 100%;
40
+ font-family: var(--font);
41
+ font-size: 16px;
42
+ line-height: 1.6;
43
+ background: var(--bg);
44
+ color: var(--text);
45
+ -webkit-font-smoothing: antialiased;
46
+ }
47
+
48
+ body { display: flex; flex-direction: column; }
49
+
50
+ ::selection { background: var(--accent); color: #fff; }
51
+
52
+ a { color: var(--accent); }
53
+ a:hover { text-decoration: none; opacity: 0.65; }
54
+
55
+ /* ============ Scanline overlay (subtle CRT feel) ============ */
56
+ body::before {
57
+ content: '';
58
+ position: fixed;
59
+ inset: 0;
60
+ background: repeating-linear-gradient(
61
+ 0deg,
62
+ transparent,
63
+ transparent 2px,
64
+ rgba(0, 0, 0, 0.012) 2px,
65
+ rgba(0, 0, 0, 0.012) 4px
66
+ );
67
+ pointer-events: none;
68
+ z-index: 9999;
69
+ }
70
+
71
+ /* ============ Dot shadow utility ============ */
72
+ .dot-shadow { position: relative; }
73
+ .dot-shadow::after {
74
+ content: '';
75
+ position: absolute;
76
+ top: 0.5rem; left: 0.5rem;
77
+ right: -0.5rem; bottom: -0.5rem;
78
+ background-image: var(--dot-shadow);
79
+ background-repeat: repeat;
80
+ z-index: -1;
81
+ pointer-events: none;
82
+ opacity: 0.6;
83
+ }
84
+
85
+ /* ============ Navigation ============ */
86
+ .dashboard-nav {
87
+ background: var(--bg);
88
+ border-bottom: 1px solid var(--border);
89
+ position: sticky; top: 0; z-index: 100;
90
+ }
91
+
92
+ .nav-container {
93
+ max-width: 1200px;
94
+ margin: 0 auto;
95
+ padding: 0.75rem 1.5rem;
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 1.5rem;
99
+ }
100
+
101
+ .nav-logo {
102
+ font-weight: 700;
103
+ font-size: 0.875rem;
104
+ color: var(--text);
105
+ text-decoration: none;
106
+ letter-spacing: 0.15em;
107
+ text-transform: uppercase;
108
+ }
109
+ .nav-logo:hover { opacity: 1; }
110
+
111
+ .nav-app-id {
112
+ color: var(--text-muted);
113
+ font-size: 0.75rem;
114
+ margin-left: auto;
115
+ }
116
+
117
+ .nav-link {
118
+ color: var(--text);
119
+ text-decoration: none;
120
+ font-size: 0.75rem;
121
+ border: 1px solid var(--border-bright);
122
+ padding: 0.25rem 0.75rem;
123
+ text-transform: uppercase;
124
+ letter-spacing: 0.05em;
125
+ transition: background 0.1s, color 0.1s;
126
+ }
127
+ .nav-link:hover {
128
+ background: var(--accent);
129
+ color: var(--bg);
130
+ border-color: var(--accent);
131
+ opacity: 1;
132
+ }
133
+
134
+ /* ============ Main content ============ */
135
+ .dashboard-main {
136
+ flex: 1;
137
+ max-width: 1200px;
138
+ width: 100%;
139
+ margin: 0 auto;
140
+ padding: 2rem 1.5rem;
141
+ }
142
+
143
+ /* ============ Card — primary container (Turbopuffer-style) ============ */
144
+ .card {
145
+ display: flex;
146
+ flex-direction: column;
147
+ margin-bottom: 1.5rem;
148
+ }
149
+
150
+ .card-header {
151
+ margin-bottom: -1px; /* overlap the border */
152
+ padding: 0;
153
+ }
154
+
155
+ .card-header h3 {
156
+ position: relative;
157
+ display: inline-flex;
158
+ align-items: center;
159
+ z-index: 10;
160
+ top: 0.5rem;
161
+ left: 0.5rem;
162
+ font-size: 0.75rem;
163
+ font-weight: 700;
164
+ text-transform: uppercase;
165
+ letter-spacing: 0.1em;
166
+ line-height: 1;
167
+ color: var(--text);
168
+ background: var(--bg);
169
+ margin: 0;
170
+ padding: 0 0.5rem;
171
+ }
172
+
173
+ .card-header h3 .card-title {
174
+ }
175
+
176
+ .card-header h3 .badge-inline {
177
+ }
178
+
179
+ .card-body {
180
+ position: relative;
181
+ border: 2px solid var(--border-bright);
182
+ }
183
+
184
+ .card-body::before {
185
+ content: '';
186
+ position: absolute;
187
+ top: 0.5rem;
188
+ left: 0.5rem;
189
+ right: -0.5rem;
190
+ bottom: -0.5rem;
191
+ background-image: var(--dot-shadow);
192
+ background-repeat: repeat;
193
+ pointer-events: none;
194
+ opacity: 0.6;
195
+ }
196
+
197
+ .card-inner {
198
+ position: relative;
199
+ z-index: 1;
200
+ background: var(--bg-card);
201
+ padding: 1.5rem;
202
+ }
203
+
204
+ /* Legacy fieldset support (deprecated — use .card instead) */
205
+ fieldset {
206
+ border: 2px solid var(--border-bright);
207
+ border-radius: 0;
208
+ padding: 1.5rem;
209
+ margin-bottom: 1.5rem;
210
+ background: var(--bg-card);
211
+ position: relative;
212
+ z-index: 0;
213
+ }
214
+
215
+ legend {
216
+ padding: 0 0.5rem;
217
+ font-size: 0.75rem;
218
+ color: var(--text);
219
+ font-weight: 700;
220
+ text-transform: uppercase;
221
+ letter-spacing: 0.1em;
222
+ }
223
+
224
+ /* ============ Dashboard grid ============ */
225
+ .dashboard-grid {
226
+ display: grid;
227
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
228
+ gap: 1.5rem;
229
+ margin-bottom: 1.5rem;
230
+ }
231
+
232
+ /* ============ Stat cards ============ */
233
+ .stat-card {
234
+ display: flex; flex-direction: column; gap: 0.25rem;
235
+ padding: 1rem;
236
+ border: 1px solid var(--border);
237
+ background: var(--bg-card);
238
+ }
239
+
240
+ .stat-card .stat-value {
241
+ font-size: 2rem; font-weight: 700; line-height: 1;
242
+ color: var(--text);
243
+ font-variant-numeric: tabular-nums;
244
+ }
245
+
246
+ .stat-card .stat-label {
247
+ font-size: 0.6875rem;
248
+ color: var(--text-muted);
249
+ text-transform: uppercase;
250
+ letter-spacing: 0.1em;
251
+ }
252
+
253
+ .stat-card .stat-change { font-size: 0.75rem; font-weight: 500; }
254
+ .stat-card .stat-change.positive { color: var(--green); }
255
+ .stat-card .stat-change.negative { color: var(--red); }
256
+
257
+ /* ============ Bar chart ============ */
258
+ .bar-chart { width: 100%; }
259
+
260
+ .bar-chart .bar-item { margin-bottom: 0.75rem; }
261
+
262
+ .bar-chart .bar-label {
263
+ display: flex; justify-content: space-between; align-items: center;
264
+ margin-bottom: 0.125rem; font-size: 0.75rem;
265
+ }
266
+ .bar-chart .bar-name { color: var(--text); }
267
+ .bar-chart .bar-value {
268
+ color: var(--text-muted);
269
+ font-weight: 700; font-variant-numeric: tabular-nums;
270
+ }
271
+
272
+ .bar-chart .bar {
273
+ height: 18px;
274
+ background: var(--accent);
275
+ border-radius: 0;
276
+ transition: width 0.3s ease;
277
+ position: relative; overflow: hidden;
278
+ opacity: 0.8;
279
+ }
280
+ .bar-chart .bar:hover { opacity: 1; }
281
+
282
+ .bar-chart .bar-fill { height: 100%; background: var(--accent); }
283
+
284
+ /* ============ Form controls ============ */
285
+ input, select, textarea, button { font-family: var(--font); font-size: 0.875rem; }
286
+
287
+ input[type="text"],
288
+ input[type="email"],
289
+ input[type="password"],
290
+ input[type="number"],
291
+ select,
292
+ textarea {
293
+ width: 100%;
294
+ padding: 0.625rem 0.75rem;
295
+ background: var(--bg);
296
+ border: 1px solid var(--border-bright);
297
+ border-radius: 0;
298
+ color: var(--text);
299
+ }
300
+
301
+ input:focus, select:focus, textarea:focus {
302
+ outline: none;
303
+ border-color: var(--accent);
304
+ box-shadow: 0 0 0 1px var(--accent);
305
+ }
306
+
307
+ input::placeholder, textarea::placeholder { color: var(--text-dim); }
308
+
309
+ label {
310
+ display: block;
311
+ margin-bottom: 0.375rem;
312
+ font-size: 0.6875rem;
313
+ color: var(--text-muted);
314
+ font-weight: 700;
315
+ text-transform: uppercase;
316
+ letter-spacing: 0.08em;
317
+ }
318
+
319
+ .form-group { margin-bottom: 1.25rem; }
320
+
321
+ /* ============ Buttons ============ */
322
+ button, .button {
323
+ display: inline-block;
324
+ padding: 0.625rem 1.25rem;
325
+ background: var(--accent);
326
+ color: #fff;
327
+ border: 1px solid var(--accent);
328
+ border-radius: 0;
329
+ font-weight: 700;
330
+ cursor: pointer;
331
+ text-decoration: none;
332
+ text-align: center;
333
+ text-transform: uppercase;
334
+ letter-spacing: 0.08em;
335
+ font-size: 0.75rem;
336
+ box-shadow:
337
+ inset 1px 1px 0 rgba(255,255,255,0.15),
338
+ inset -1px -1px 0 rgba(0,0,0,0.15),
339
+ 2px 2px 0 rgba(0,0,0,0.1);
340
+ transition: box-shadow 0.1s, transform 0.1s;
341
+ }
342
+ button:hover, .button:hover {
343
+ box-shadow:
344
+ inset 1px 1px 0 rgba(255,255,255,0.1),
345
+ inset -1px -1px 0 rgba(0,0,0,0.15),
346
+ 3px 3px 0 rgba(0,0,0,0.12);
347
+ opacity: 1;
348
+ }
349
+ button:active, .button:active {
350
+ transform: translate(1px, 1px);
351
+ box-shadow: inset 1px 1px 3px rgba(0,0,0,0.25);
352
+ }
353
+ button:disabled, .button:disabled { opacity: 0.25; cursor: not-allowed; }
354
+
355
+ button.secondary, .button.secondary {
356
+ background: transparent;
357
+ color: var(--text);
358
+ border-color: var(--border-bright);
359
+ box-shadow: 2px 2px 0 rgba(0,0,0,0.05);
360
+ }
361
+ button.secondary:hover, .button.secondary:hover {
362
+ border-color: var(--accent);
363
+ color: var(--accent);
364
+ box-shadow: 2px 2px 0 rgba(0,0,0,0.1);
365
+ }
366
+
367
+ button.danger, .button.danger {
368
+ background: var(--red);
369
+ border-color: var(--red);
370
+ color: #fff;
371
+ box-shadow: 2px 2px 0 rgba(204,34,34,0.15);
372
+ }
373
+ button.danger:hover, .button.danger:hover {
374
+ background: transparent;
375
+ color: var(--red);
376
+ }
377
+
378
+ /* ============ Tables ============ */
379
+ table { width: 100%; border-collapse: collapse; }
380
+
381
+ thead { border-bottom: 1px solid var(--border-bright); }
382
+ th {
383
+ padding: 0.5rem 0.75rem; text-align: left;
384
+ font-size: 0.6875rem; color: var(--text);
385
+ font-weight: 700; text-transform: uppercase;
386
+ letter-spacing: 0.1em;
387
+ background: var(--bg-raised);
388
+ }
389
+
390
+ td {
391
+ padding: 0.5rem 0.75rem;
392
+ border-bottom: 1px solid var(--border);
393
+ font-size: 0.75rem;
394
+ font-variant-numeric: tabular-nums;
395
+ }
396
+
397
+ tbody tr:hover { background: var(--bg-raised); }
398
+
399
+ /* ============ Code ============ */
400
+ code, pre {
401
+ font-family: var(--font);
402
+ background: var(--bg-raised);
403
+ padding: 0.125rem 0.375rem;
404
+ border-radius: 0;
405
+ font-size: 0.75rem;
406
+ border: 1px solid var(--border);
407
+ color: var(--text);
408
+ }
409
+ pre { padding: 1rem; overflow-x: auto; border: 1px solid var(--border-bright); }
410
+ pre code { background: none; padding: 0; border: none; }
411
+
412
+ /* ============ Login layout ============ */
413
+ .login-container {
414
+ min-height: 100vh;
415
+ display: flex; align-items: center; justify-content: center;
416
+ padding: 2rem;
417
+ background: var(--bg);
418
+ }
419
+ .login-box { width: 100%; max-width: 420px; }
420
+
421
+ .login-header { text-align: center; margin-bottom: 2rem; }
422
+ .login-header h1 {
423
+ font-size: 1.5rem; font-weight: 700; color: var(--text);
424
+ letter-spacing: 0.15em; text-transform: uppercase;
425
+ margin-bottom: 0.25rem;
426
+ }
427
+ .login-header p { color: var(--text-muted); font-size: 0.75rem; }
428
+
429
+ /* ============ htmx loading ============ */
430
+ .htmx-indicator { opacity: 0; transition: opacity 0.15s; }
431
+ .htmx-request .htmx-indicator { opacity: 1; }
432
+ .htmx-request.htmx-swapping { opacity: 0.3; pointer-events: none; }
433
+
434
+ /* ============ Skeleton — blinking cursor ============ */
435
+ .skeleton {
436
+ background: var(--bg-raised);
437
+ border: 1px solid var(--border);
438
+ position: relative;
439
+ overflow: hidden;
440
+ }
441
+ .skeleton::after {
442
+ content: '';
443
+ position: absolute; left: 0; top: 0;
444
+ width: 2px; height: 100%;
445
+ background: var(--text);
446
+ animation: cursor-blink 0.8s step-end infinite;
447
+ }
448
+ @keyframes cursor-blink {
449
+ 0%, 100% { opacity: 1; }
450
+ 50% { opacity: 0; }
451
+ }
452
+
453
+ .skeleton-text { height: 1rem; margin-bottom: 0.5rem; }
454
+ .skeleton-heading { height: 1.5rem; width: 60%; margin-bottom: 1rem; }
455
+
456
+ /* ============ Utilities ============ */
457
+ .text-center { text-align: center; }
458
+ .text-right { text-align: right; }
459
+ .text-muted { color: var(--text-muted); }
460
+ .text-dim { color: var(--text-dim); }
461
+ .text-success { color: var(--green); }
462
+ .text-danger { color: var(--red); }
463
+ .text-warning { color: var(--amber); }
464
+
465
+ .mb-0 { margin-bottom: 0; }
466
+ .mb-1 { margin-bottom: 0.5rem; }
467
+ .mb-2 { margin-bottom: 1rem; }
468
+ .mb-3 { margin-bottom: 1.5rem; }
469
+ .mb-4 { margin-bottom: 2rem; }
470
+ .mt-0 { margin-top: 0; }
471
+ .mt-1 { margin-top: 0.5rem; }
472
+ .mt-2 { margin-top: 1rem; }
473
+ .mt-3 { margin-top: 1.5rem; }
474
+ .mt-4 { margin-top: 2rem; }
475
+
476
+ /* ============ Period selector ============ */
477
+ .period-selector button {
478
+ font-size: 0.625rem;
479
+ padding: 0.2rem 0.5rem;
480
+ }
481
+
482
+ /* ============ Responsive ============ */
483
+ @media (max-width: 768px) {
484
+ html, body { font-size: 14px; }
485
+ .dashboard-main { padding: 1rem; }
486
+ .nav-container { padding: 0.5rem 1rem; }
487
+ .dashboard-grid { grid-template-columns: 1fr; gap: 1rem; }
488
+ .card-inner { padding: 1rem; }
489
+ .card { margin-bottom: 1rem; }
490
+ fieldset { padding: 1rem; margin-bottom: 1rem; }
491
+ .stat-card .stat-value { font-size: 1.75rem; }
492
+ table { font-size: 0.625rem; }
493
+ th, td { padding: 0.375rem 0.5rem; }
494
+ }
495
+
496
+ @media (max-width: 480px) {
497
+ .nav-container { flex-wrap: wrap; }
498
+ .nav-app-id { margin-left: 0; width: 100%; order: 3; }
499
+ }
500
+
501
+ /* ============ Alerts ============ */
502
+ .alert {
503
+ padding: 0.75rem 1rem;
504
+ border-radius: 0;
505
+ margin-bottom: 1.5rem;
506
+ border: 1px solid var(--border-bright);
507
+ font-size: 0.75rem;
508
+ background: var(--bg-card);
509
+ }
510
+ .alert::before { font-weight: 700; margin-right: 0.5rem; }
511
+
512
+ .alert-info { border-color: var(--border-bright); color: var(--text); }
513
+ .alert-info::before { content: '>'; color: var(--text); }
514
+
515
+ .alert-success { border-color: var(--green); color: var(--green); }
516
+ .alert-success::before { content: '[ok]'; }
517
+
518
+ .alert-warning { border-color: var(--amber); color: var(--amber); }
519
+ .alert-warning::before { content: '[!!]'; }
520
+
521
+ .alert-danger { border-color: var(--red); color: var(--red); }
522
+ .alert-danger::before { content: '[ERR]'; }
523
+
524
+ /* ============ Badges ============ */
525
+ .badge {
526
+ display: inline-block;
527
+ padding: 0.125rem 0.375rem;
528
+ font-size: 0.625rem; font-weight: 700;
529
+ border-radius: 0;
530
+ text-transform: uppercase;
531
+ letter-spacing: 0.05em;
532
+ border: 1px solid;
533
+ }
534
+ .badge-success { color: var(--green); border-color: var(--green); background: transparent; }
535
+ .badge-danger { color: var(--red); border-color: var(--red); background: transparent; }
536
+ .badge-warning { color: var(--amber); border-color: var(--amber); background: transparent; }
537
+ .badge-info { color: #fff; border-color: var(--accent); background: var(--accent); }
538
+
539
+ /* ============ Empty state ============ */
540
+ .empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-muted); }
541
+ .empty-state-icon { font-size: 1.5rem; margin-bottom: 0.75rem; color: var(--text); font-weight: 700; }
542
+ .empty-state-text { font-size: 0.8125rem; margin-bottom: 0.25rem; }
543
+ .empty-state-subtext { font-size: 0.6875rem; color: var(--text-dim); }
544
+
545
+ /* ============ Auth pages ============ */
546
+ .ascii-logo {
547
+ text-align: center; margin-bottom: 2rem;
548
+ color: var(--text); font-size: 0.55rem; line-height: 1.2;
549
+ white-space: pre; font-weight: 400;
550
+ }
551
+
552
+ .badge-inline {
553
+ display: inline-block; font-size: 0.5625rem; font-weight: 700;
554
+ color: var(--text-muted); border: 1px solid var(--border-bright);
555
+ border-radius: 0; padding: 0.1rem 0.4rem;
556
+ margin-left: 0.5rem; vertical-align: middle;
557
+ text-transform: uppercase; letter-spacing: 0.05em;
558
+ }
559
+
560
+ .divider {
561
+ display: flex; align-items: center; gap: 0.75rem;
562
+ color: var(--text-dim); font-size: 0.6875rem;
563
+ margin: 1.5rem 0;
564
+ text-transform: uppercase; letter-spacing: 0.1em;
565
+ white-space: nowrap;
566
+ }
567
+ .divider::before, .divider::after {
568
+ content: ''; flex: 1;
569
+ height: 1px; background: var(--border-bright);
570
+ }
571
+
572
+ .credentials-box {
573
+ background: var(--bg); border: 1px solid var(--accent-dim);
574
+ padding: 1rem; margin-bottom: 1rem;
575
+ font-size: 0.75rem; line-height: 1.8; word-break: break-all;
576
+ }
577
+ .credentials-box .label { color: var(--text-muted); }
578
+ .credentials-box .value { color: var(--text); font-weight: 700; }
579
+
580
+ .warning {
581
+ background: rgba(184,122,0,0.06); border: 1px solid var(--amber);
582
+ padding: 0.75rem; margin-bottom: 1rem;
583
+ font-size: 0.7rem; color: var(--amber);
584
+ }
585
+ .warning::before { content: '[!!] '; font-weight: 700; }
586
+
587
+ .error-message {
588
+ color: var(--red); margin: 0 0 1rem 0; font-size: 0.75rem;
589
+ padding: 0.5rem 0.75rem;
590
+ border: 1px solid rgba(204,34,34,0.3);
591
+ background: var(--bg);
592
+ }
593
+ .error-message::before { content: '[ERR] '; font-weight: 700; }
594
+
595
+ .hint {
596
+ font-size: 0.6875rem; color: var(--text-muted); line-height: 1.6;
597
+ margin-top: 0.75rem;
598
+ }
599
+ .hint code {
600
+ color: var(--text); background: var(--bg-raised);
601
+ padding: 0.125rem 0.375rem; border: 1px solid var(--border);
602
+ }
603
+
604
+ .btn {
605
+ display: block; width: 100%; text-align: center; text-decoration: none;
606
+ }
607
+ .btn-secondary {
608
+ background: transparent; color: var(--text);
609
+ border-color: var(--border-bright);
610
+ box-shadow: 2px 2px 0 rgba(0,0,0,0.05);
611
+ }
612
+ .btn-secondary:hover {
613
+ border-color: var(--accent); color: var(--accent);
614
+ box-shadow: 2px 2px 0 rgba(0,0,0,0.1);
615
+ }
616
+
617
+ #create-result { display: none; }
618
+ #create-result.show { display: block; }
619
+ #create-btn.loading { opacity: 0.25; pointer-events: none; }
620
+
621
+ /* ============ Scrollbar ============ */
622
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
623
+ ::-webkit-scrollbar-track { background: var(--bg-raised); }
624
+ ::-webkit-scrollbar-thumb { background: var(--border-bright); }
625
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
626
+
627
+ /* ============ Responsive (small screens) ============ */
628
+ @media (max-width: 480px) {
629
+ .ascii-logo { font-size: 0.4rem; }
630
+ .login-container { padding: 1rem; }
631
+ .card-inner { padding: 1rem; }
632
+ }
633
+ `;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * BOTCHA Email — Resend Integration
3
+ *
4
+ * Sends transactional emails via Resend API.
5
+ * Falls back to console.log when RESEND_API_KEY is not set (local dev).
6
+ *
7
+ * Uses onboarding@resend.dev (Resend test domain) until botcha.ai
8
+ * is verified as a sending domain.
9
+ */
10
+ export interface SendEmailOptions {
11
+ to: string;
12
+ subject: string;
13
+ html: string;
14
+ text?: string;
15
+ }
16
+ interface SendResult {
17
+ success: boolean;
18
+ id?: string;
19
+ error?: string;
20
+ }
21
+ /**
22
+ * Send an email via Resend. Falls back to console logging if no API key.
23
+ */
24
+ export declare function sendEmail(apiKey: string | undefined, options: SendEmailOptions): Promise<SendResult>;
25
+ /**
26
+ * Email verification code email.
27
+ */
28
+ export declare function verificationEmail(code: string): SendEmailOptions & {
29
+ to: '';
30
+ };
31
+ /**
32
+ * Account recovery device code email.
33
+ */
34
+ export declare function recoveryEmail(code: string, loginUrl: string): SendEmailOptions & {
35
+ to: '';
36
+ };
37
+ /**
38
+ * New secret notification email (sent after secret rotation).
39
+ */
40
+ export declare function secretRotatedEmail(appId: string): SendEmailOptions & {
41
+ to: '';
42
+ };
43
+ export {};
44
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/email.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CAyCrB;AAID;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG;IAAE,EAAE,EAAE,EAAE,CAAA;CAAE,CAkB7E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG;IAAE,EAAE,EAAE,EAAE,CAAA;CAAE,CAmB3F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG;IAAE,EAAE,EAAE,EAAE,CAAA;CAAE,CAe/E"}