this repo has no description
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>JMAP Email Client</title>
7 <style>
8 :root {
9 --bg-color: #1a1a2e;
10 --card-bg: #16213e;
11 --accent: #0f3460;
12 --highlight: #e94560;
13 --text: #eee;
14 --text-muted: #888;
15 --success: #4ade80;
16 --error: #f87171;
17 --warning: #fbbf24;
18 }
19
20 * {
21 box-sizing: border-box;
22 margin: 0;
23 padding: 0;
24 }
25
26 body {
27 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
28 background: var(--bg-color);
29 color: var(--text);
30 min-height: 100vh;
31 padding: 20px;
32 }
33
34 .container {
35 max-width: 1200px;
36 margin: 0 auto;
37 }
38
39 header {
40 text-align: center;
41 margin-bottom: 30px;
42 }
43
44 h1 {
45 font-size: 2rem;
46 margin-bottom: 10px;
47 }
48
49 h1 span {
50 color: var(--highlight);
51 }
52
53 .subtitle {
54 color: var(--text-muted);
55 font-size: 0.9rem;
56 }
57
58 /* Top Section - Two Column Layout */
59 .top-section {
60 display: grid;
61 grid-template-columns: 1fr 1fr;
62 gap: 20px;
63 margin-bottom: 20px;
64 }
65
66 @media (max-width: 800px) {
67 .top-section {
68 grid-template-columns: 1fr;
69 }
70 }
71
72 /* Login Form */
73 .login-card {
74 background: var(--card-bg);
75 border-radius: 12px;
76 padding: 16px;
77 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
78 }
79
80 .login-card h3 {
81 margin-bottom: 12px;
82 font-size: 0.9rem;
83 color: var(--text-muted);
84 text-transform: uppercase;
85 letter-spacing: 1px;
86 }
87
88 .form-row {
89 display: flex;
90 gap: 12px;
91 margin-bottom: 12px;
92 }
93
94 .form-group {
95 flex: 1;
96 margin-bottom: 12px;
97 }
98
99 .form-group:last-child {
100 margin-bottom: 0;
101 }
102
103 .form-group.small {
104 flex: 0.4;
105 }
106
107 label {
108 display: block;
109 margin-bottom: 4px;
110 font-weight: 500;
111 font-size: 0.75rem;
112 color: var(--text-muted);
113 }
114
115 input[type="text"],
116 input[type="password"] {
117 width: 100%;
118 padding: 8px 12px;
119 border: 2px solid var(--accent);
120 border-radius: 6px;
121 background: var(--bg-color);
122 color: var(--text);
123 font-size: 0.85rem;
124 transition: border-color 0.2s;
125 }
126
127 input:focus {
128 outline: none;
129 border-color: var(--highlight);
130 }
131
132 .btn-row {
133 display: flex;
134 gap: 8px;
135 }
136
137 .btn {
138 flex: 1;
139 padding: 10px;
140 border: none;
141 border-radius: 6px;
142 font-size: 0.85rem;
143 font-weight: 600;
144 cursor: pointer;
145 transition: transform 0.1s, opacity 0.2s;
146 }
147
148 .btn:hover {
149 transform: translateY(-1px);
150 }
151
152 .btn:active {
153 transform: translateY(0);
154 }
155
156 .btn:disabled {
157 opacity: 0.5;
158 cursor: not-allowed;
159 transform: none;
160 }
161
162 .btn-primary {
163 background: var(--highlight);
164 color: white;
165 }
166
167 .btn-secondary {
168 background: var(--accent);
169 color: var(--text);
170 }
171
172 /* Status/Log Panel */
173 .log-panel {
174 background: var(--card-bg);
175 border-radius: 12px;
176 padding: 20px;
177 margin-bottom: 30px;
178 max-height: 500px;
179 overflow-y: auto;
180 }
181
182 .log-panel h3 {
183 margin-bottom: 15px;
184 font-size: 0.9rem;
185 color: var(--text-muted);
186 text-transform: uppercase;
187 letter-spacing: 1px;
188 }
189
190 .log-entry {
191 font-family: 'SF Mono', Monaco, 'Courier New', monospace;
192 font-size: 0.85rem;
193 padding: 8px 0;
194 border-bottom: 1px solid var(--accent);
195 }
196
197 .log-entry:last-child {
198 border-bottom: none;
199 }
200
201 .log-entry-header {
202 display: flex;
203 align-items: center;
204 gap: 8px;
205 }
206
207 .log-info .log-entry-header { color: var(--text); }
208 .log-success .log-entry-header { color: var(--success); }
209 .log-error .log-entry-header { color: var(--error); }
210 .log-warning .log-entry-header { color: var(--warning); }
211
212 .log-time {
213 color: var(--text-muted);
214 font-size: 0.8rem;
215 flex-shrink: 0;
216 }
217
218 .log-message {
219 flex: 1;
220 }
221
222 .log-expand-btn {
223 background: var(--accent);
224 border: none;
225 color: var(--text-muted);
226 padding: 2px 8px;
227 border-radius: 4px;
228 font-size: 0.7rem;
229 cursor: pointer;
230 font-family: inherit;
231 transition: background 0.2s, color 0.2s;
232 flex-shrink: 0;
233 }
234
235 .log-expand-btn:hover {
236 background: var(--highlight);
237 color: white;
238 }
239
240 .log-expand-btn.expanded {
241 background: var(--highlight);
242 color: white;
243 }
244
245 /* JSON content within log entry */
246 .log-json {
247 display: none;
248 margin-top: 8px;
249 border-radius: 8px;
250 overflow: hidden;
251 }
252
253 .log-json.visible {
254 display: block;
255 }
256
257 .log-json-header {
258 padding: 6px 12px;
259 font-size: 0.75rem;
260 font-weight: 600;
261 display: flex;
262 justify-content: space-between;
263 align-items: center;
264 }
265
266 .log-json.request .log-json-header {
267 background: var(--accent);
268 color: var(--highlight);
269 }
270
271 .log-json.response .log-json-header {
272 background: #1a3a2e;
273 color: var(--success);
274 }
275
276 .log-json-body {
277 background: var(--bg-color);
278 padding: 12px;
279 font-size: 0.75rem;
280 line-height: 1.4;
281 white-space: pre-wrap;
282 word-break: break-all;
283 max-height: 300px;
284 overflow-y: auto;
285 color: var(--text-muted);
286 }
287
288 .log-json-body.collapsed {
289 max-height: 100px;
290 }
291
292 .json-toggle-size {
293 background: none;
294 border: none;
295 color: inherit;
296 cursor: pointer;
297 font-size: 0.7rem;
298 opacity: 0.7;
299 }
300
301 .json-toggle-size:hover {
302 opacity: 1;
303 }
304
305 /* Session Info */
306 .session-info {
307 background: var(--card-bg);
308 border-radius: 12px;
309 padding: 16px;
310 display: none;
311 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
312 }
313
314 .session-info.visible {
315 display: block;
316 }
317
318 .session-info h3 {
319 margin-bottom: 12px;
320 font-size: 0.9rem;
321 color: var(--success);
322 text-transform: uppercase;
323 letter-spacing: 1px;
324 }
325
326 .session-detail {
327 display: flex;
328 margin-bottom: 6px;
329 font-size: 0.85rem;
330 }
331
332 .session-detail .label {
333 width: 100px;
334 color: var(--text-muted);
335 flex-shrink: 0;
336 }
337
338 .session-detail .value {
339 color: var(--text);
340 word-break: break-all;
341 font-family: 'SF Mono', Monaco, 'Courier New', monospace;
342 font-size: 0.8rem;
343 }
344
345 .search-box {
346 margin-top: 12px;
347 padding-top: 12px;
348 border-top: 1px solid var(--accent);
349 display: flex;
350 gap: 8px;
351 }
352
353 .search-box input {
354 flex: 1;
355 padding: 8px 12px;
356 border: 2px solid var(--accent);
357 border-radius: 6px;
358 background: var(--bg-color);
359 color: var(--text);
360 font-size: 0.85rem;
361 }
362
363 .search-box input:focus {
364 outline: none;
365 border-color: var(--highlight);
366 }
367
368 .btn-small {
369 flex: 0;
370 padding: 8px 16px;
371 white-space: nowrap;
372 }
373
374 /* Email List */
375 .email-list {
376 display: none;
377 }
378
379 .email-list.visible {
380 display: block;
381 }
382
383 .email-list h2 {
384 margin-bottom: 20px;
385 }
386
387 .email-item {
388 background: var(--card-bg);
389 border-radius: 8px;
390 padding: 16px 20px;
391 margin-bottom: 12px;
392 cursor: pointer;
393 transition: background 0.2s, transform 0.1s;
394 border-left: 4px solid transparent;
395 }
396
397 .email-item:hover {
398 background: var(--accent);
399 transform: translateX(4px);
400 }
401
402 .email-item.unread {
403 border-left-color: var(--highlight);
404 }
405
406 .email-header {
407 display: flex;
408 justify-content: space-between;
409 align-items: flex-start;
410 margin-bottom: 8px;
411 }
412
413 .email-from {
414 font-weight: 600;
415 font-size: 1rem;
416 }
417
418 .email-date {
419 color: var(--text-muted);
420 font-size: 0.85rem;
421 }
422
423 .email-subject {
424 font-size: 0.95rem;
425 color: var(--text);
426 margin-bottom: 6px;
427 }
428
429 .email-preview {
430 color: var(--text-muted);
431 font-size: 0.85rem;
432 white-space: nowrap;
433 overflow: hidden;
434 text-overflow: ellipsis;
435 }
436
437 .email-keywords {
438 margin-top: 8px;
439 }
440
441 .keyword-tag {
442 display: inline-block;
443 background: var(--accent);
444 color: var(--text-muted);
445 padding: 2px 8px;
446 border-radius: 4px;
447 font-size: 0.75rem;
448 margin-right: 6px;
449 }
450
451 .keyword-tag.flagged {
452 background: var(--warning);
453 color: var(--bg-color);
454 }
455
456 /* Loading spinner */
457 .spinner {
458 display: inline-block;
459 width: 20px;
460 height: 20px;
461 border: 2px solid var(--text-muted);
462 border-top-color: var(--highlight);
463 border-radius: 50%;
464 animation: spin 0.8s linear infinite;
465 margin-right: 10px;
466 vertical-align: middle;
467 }
468
469 @keyframes spin {
470 to { transform: rotate(360deg); }
471 }
472
473 /* Responsive */
474 @media (max-width: 600px) {
475 body {
476 padding: 10px;
477 }
478
479 .login-card {
480 padding: 20px;
481 }
482
483 h1 {
484 font-size: 1.5rem;
485 }
486 }
487 </style>
488</head>
489<body>
490 <div class="container">
491 <header>
492 <h1>JMAP <span>Email Client</span></h1>
493 <p class="subtitle">Built with OCaml and Brr</p>
494 </header>
495
496 <!-- Top Section: Login + Session Info -->
497 <div class="top-section">
498 <!-- Login Form -->
499 <div class="login-card" id="login-card">
500 <h3>Connection</h3>
501 <div class="form-group">
502 <label for="session-url">Session URL</label>
503 <input type="text" id="session-url"
504 value="https://api.fastmail.com/jmap/session"
505 placeholder="https://api.fastmail.com/jmap/session">
506 </div>
507 <div class="form-row">
508 <div class="form-group">
509 <label for="api-token">API Token</label>
510 <input type="password" id="api-token"
511 placeholder="Enter your JMAP API token">
512 </div>
513 </div>
514 <div class="btn-row">
515 <button class="btn btn-primary" id="connect-btn">Connect</button>
516 <button class="btn btn-secondary" id="disconnect-btn" style="display: none;">Disconnect</button>
517 </div>
518 </div>
519
520 <!-- Session Info -->
521 <div class="session-info" id="session-info">
522 <h3>Connected</h3>
523 <div class="session-detail">
524 <span class="label">Username:</span>
525 <span class="value" id="session-username">-</span>
526 </div>
527 <div class="session-detail">
528 <span class="label">API URL:</span>
529 <span class="value" id="session-api-url">-</span>
530 </div>
531 <div class="session-detail">
532 <span class="label">Account ID:</span>
533 <span class="value" id="session-account-id">-</span>
534 </div>
535 <div class="search-box">
536 <input type="text" id="email-search" placeholder="Search emails...">
537 <button class="btn btn-primary btn-small" id="search-btn">Search</button>
538 </div>
539 </div>
540 </div>
541
542 <!-- Log Panel with expandable JSON -->
543 <div class="log-panel" id="log-panel">
544 <h3>Activity Log</h3>
545 <div id="log-entries"></div>
546 </div>
547
548 <!-- Email List -->
549 <div class="email-list" id="email-list">
550 <h2>Recent Emails</h2>
551 <div id="emails"></div>
552 </div>
553 </div>
554
555 <script type="text/javascript" defer src="brr.js"></script>
556 <noscript>
557 <p style="text-align: center; padding: 50px; color: #888;">
558 Please enable JavaScript to use this application.
559 </p>
560 </noscript>
561</body>
562</html>