Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

layout.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

layout.tsxBlame612 lines · 1 contributor
fc1817aClaude1import type { FC, PropsWithChildren } from "hono/jsx";
06d5ffeClaude2import type { User } from "../db/schema";
3import { hljsThemeCss } from "../lib/highlight";
fc1817aClaude4
06d5ffeClaude5export const Layout: FC<
6 PropsWithChildren<{ title?: string; user?: User | null }>
7> = ({ children, title, user }) => {
fc1817aClaude8 return (
9 <html lang="en">
10 <head>
11 <meta charset="UTF-8" />
12 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13 <title>{title ? `${title} — gluecron` : "gluecron"}</title>
14 <style>{css}</style>
06d5ffeClaude15 <style>{hljsThemeCss}</style>
fc1817aClaude16 </head>
17 <body>
18 <header>
19 <nav>
20 <a href="/" class="logo">
21 gluecron
22 </a>
06d5ffeClaude23 <div class="nav-right">
c81ab7aClaude24 <a href="/explore" class="nav-link">
25 Explore
26 </a>
06d5ffeClaude27 {user ? (
28 <>
f1ab587Claude29 <a href="/dashboard" class="nav-link" style="font-weight: 600">
30 Dashboard
31 </a>
06d5ffeClaude32 <a href="/new" class="btn btn-sm btn-primary">
33 + New
34 </a>
35 <a href={`/${user.username}`} class="nav-user">
36 {user.displayName || user.username}
37 </a>
38 <a href="/settings" class="nav-link">
39 Settings
40 </a>
41 <a href="/logout" class="nav-link">
42 Sign out
43 </a>
44 </>
45 ) : (
46 <>
47 <a href="/login" class="nav-link">
48 Sign in
49 </a>
50 <a href="/register" class="btn btn-sm btn-primary">
51 Register
52 </a>
53 </>
54 )}
55 </div>
fc1817aClaude56 </nav>
57 </header>
58 <main>{children}</main>
59 <footer>
60 <span>gluecron — AI-native code intelligence</span>
61 </footer>
62 </body>
63 </html>
64 );
65};
66
67const css = `
68 :root {
69 --bg: #0d1117;
70 --bg-secondary: #161b22;
71 --bg-tertiary: #21262d;
72 --border: #30363d;
73 --text: #e6edf3;
74 --text-muted: #8b949e;
75 --text-link: #58a6ff;
76 --accent: #1f6feb;
77 --accent-hover: #388bfd;
78 --green: #3fb950;
79 --red: #f85149;
80 --yellow: #d29922;
81 --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
82 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
83 --radius: 6px;
84 }
85
86 * { margin: 0; padding: 0; box-sizing: border-box; }
87
88 body {
89 font-family: var(--font-sans);
90 background: var(--bg);
91 color: var(--text);
92 line-height: 1.5;
93 min-height: 100vh;
94 display: flex;
95 flex-direction: column;
96 }
97
98 a { color: var(--text-link); text-decoration: none; }
99 a:hover { text-decoration: underline; }
100
101 header {
102 border-bottom: 1px solid var(--border);
103 padding: 12px 24px;
104 background: var(--bg-secondary);
105 }
106
06d5ffeClaude107 header nav {
108 display: flex;
109 align-items: center;
110 justify-content: space-between;
111 max-width: 1200px;
112 margin: 0 auto;
113 }
fc1817aClaude114 .logo { font-size: 20px; font-weight: 700; color: var(--text); }
115 .logo:hover { text-decoration: none; color: var(--text-link); }
116
06d5ffeClaude117 .nav-right { display: flex; align-items: center; gap: 16px; }
118 .nav-link { color: var(--text-muted); font-size: 14px; }
119 .nav-link:hover { color: var(--text); text-decoration: none; }
120 .nav-user { color: var(--text); font-weight: 600; font-size: 14px; }
121 .nav-user:hover { color: var(--text-link); text-decoration: none; }
122
fc1817aClaude123 main { max-width: 1200px; margin: 0 auto; padding: 24px; flex: 1; width: 100%; }
124
125 footer {
126 border-top: 1px solid var(--border);
127 padding: 16px 24px;
128 text-align: center;
129 color: var(--text-muted);
130 font-size: 13px;
131 }
132
06d5ffeClaude133 /* Buttons */
134 .btn {
135 display: inline-flex;
136 align-items: center;
137 gap: 6px;
138 padding: 8px 16px;
139 border-radius: var(--radius);
140 font-size: 14px;
141 font-weight: 500;
142 border: 1px solid var(--border);
143 background: var(--bg-tertiary);
144 color: var(--text);
145 cursor: pointer;
146 text-decoration: none;
147 line-height: 1.4;
148 }
149 .btn:hover { background: var(--border); text-decoration: none; }
150 .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
151 .btn-primary:hover { background: var(--accent-hover); }
152 .btn-danger { background: transparent; border-color: var(--red); color: var(--red); }
153 .btn-danger:hover { background: rgba(248, 81, 73, 0.15); }
154 .btn-sm { padding: 4px 12px; font-size: 13px; }
155
156 /* Forms */
157 .form-group { margin-bottom: 16px; }
158 .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; color: var(--text); }
159 .form-group input, .form-group textarea, .form-group select {
160 width: 100%;
161 padding: 8px 12px;
162 background: var(--bg);
163 border: 1px solid var(--border);
164 border-radius: var(--radius);
165 color: var(--text);
166 font-size: 14px;
167 font-family: var(--font-sans);
168 }
169 .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
170 outline: none;
171 border-color: var(--accent);
172 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
173 }
174 .input-disabled { opacity: 0.5; cursor: not-allowed; }
175
176 /* Auth */
177 .auth-container { max-width: 400px; margin: 40px auto; }
178 .auth-container h2 { margin-bottom: 20px; font-size: 24px; }
179 .auth-error {
180 background: rgba(248, 81, 73, 0.1);
181 border: 1px solid var(--red);
182 color: var(--red);
183 padding: 8px 12px;
184 border-radius: var(--radius);
185 margin-bottom: 16px;
186 font-size: 14px;
187 }
188 .auth-success {
189 background: rgba(63, 185, 80, 0.1);
190 border: 1px solid var(--green);
191 color: var(--green);
192 padding: 8px 12px;
193 border-radius: var(--radius);
194 margin-bottom: 16px;
195 font-size: 14px;
196 }
197 .auth-switch { margin-top: 16px; font-size: 14px; color: var(--text-muted); }
198
199 /* Settings */
200 .settings-container { max-width: 600px; }
201 .settings-container h2 { margin-bottom: 20px; font-size: 24px; }
202 .settings-container h3 { font-size: 18px; margin-bottom: 12px; }
203 .ssh-keys-list { margin-bottom: 24px; }
204 .ssh-key-item {
205 display: flex;
206 justify-content: space-between;
207 align-items: center;
208 padding: 12px 16px;
209 border: 1px solid var(--border);
210 border-radius: var(--radius);
211 margin-bottom: 8px;
212 background: var(--bg-secondary);
213 }
214 .ssh-key-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
215 .ssh-key-meta code { font-size: 11px; background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
216
217 /* Repo header */
fc1817aClaude218 .repo-header {
219 display: flex;
220 align-items: center;
221 gap: 8px;
222 margin-bottom: 16px;
223 font-size: 20px;
224 }
225 .repo-header .owner { color: var(--text-link); }
226 .repo-header .separator { color: var(--text-muted); }
227 .repo-header .name { color: var(--text-link); font-weight: 600; }
06d5ffeClaude228 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude229
230 .repo-nav {
231 display: flex;
232 gap: 0;
233 border-bottom: 1px solid var(--border);
234 margin-bottom: 20px;
235 }
236 .repo-nav a {
237 padding: 8px 16px;
238 color: var(--text-muted);
239 border-bottom: 2px solid transparent;
240 font-size: 14px;
241 }
242 .repo-nav a:hover { text-decoration: none; color: var(--text); }
243 .repo-nav a.active { color: var(--text); border-bottom-color: var(--accent); }
244
245 .breadcrumb { display: flex; gap: 4px; align-items: center; margin-bottom: 16px; color: var(--text-muted); font-size: 14px; }
246 .breadcrumb a { color: var(--text-link); }
247
248 .file-table { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
249 .file-table tr { border-bottom: 1px solid var(--border); }
250 .file-table tr:last-child { border-bottom: none; }
251 .file-table td { padding: 8px 16px; font-size: 14px; }
252 .file-table tr:hover { background: var(--bg-secondary); }
253 .file-icon { width: 20px; color: var(--text-muted); }
254 .file-name a { color: var(--text); }
255 .file-name a:hover { color: var(--text-link); text-decoration: underline; }
256
257 .blob-view {
258 border: 1px solid var(--border);
259 border-radius: var(--radius);
260 overflow: hidden;
261 }
262 .blob-header {
263 background: var(--bg-secondary);
264 padding: 8px 16px;
265 border-bottom: 1px solid var(--border);
266 font-size: 13px;
267 color: var(--text-muted);
06d5ffeClaude268 display: flex;
269 justify-content: space-between;
270 align-items: center;
fc1817aClaude271 }
272 .blob-code {
273 overflow-x: auto;
274 font-family: var(--font-mono);
275 font-size: 13px;
276 line-height: 1.6;
277 }
278 .blob-code table { width: 100%; border-collapse: collapse; }
279 .blob-code .line-num {
280 width: 1%;
281 min-width: 50px;
282 padding: 0 12px;
283 text-align: right;
284 color: var(--text-muted);
285 user-select: none;
286 white-space: nowrap;
287 border-right: 1px solid var(--border);
288 }
289 .blob-code .line-content { padding: 0 12px; white-space: pre; }
290
291 .commit-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
292 .commit-item {
293 display: flex;
294 justify-content: space-between;
295 align-items: center;
296 padding: 12px 16px;
297 border-bottom: 1px solid var(--border);
298 }
299 .commit-item:last-child { border-bottom: none; }
300 .commit-item:hover { background: var(--bg-secondary); }
301 .commit-message { font-size: 14px; font-weight: 500; }
302 .commit-meta { font-size: 12px; color: var(--text-muted); }
303 .commit-sha {
304 font-family: var(--font-mono);
305 font-size: 12px;
306 padding: 2px 8px;
307 background: var(--bg-tertiary);
308 border: 1px solid var(--border);
309 border-radius: var(--radius);
310 color: var(--text-link);
311 }
312
313 .diff-view { margin-top: 16px; }
314 .diff-file {
315 border: 1px solid var(--border);
316 border-radius: var(--radius);
317 margin-bottom: 16px;
318 overflow: hidden;
319 }
320 .diff-file-header {
321 background: var(--bg-secondary);
322 padding: 8px 16px;
323 border-bottom: 1px solid var(--border);
324 font-family: var(--font-mono);
325 font-size: 13px;
326 }
327 .diff-content {
328 overflow-x: auto;
329 font-family: var(--font-mono);
330 font-size: 13px;
331 line-height: 1.6;
332 }
333 .diff-content .line-add { background: rgba(63, 185, 80, 0.15); color: var(--green); }
334 .diff-content .line-del { background: rgba(248, 81, 73, 0.1); color: var(--red); }
335 .diff-content .line-hunk { background: rgba(56, 139, 253, 0.1); color: var(--text-link); }
336 .diff-content .line { padding: 0 12px; white-space: pre; display: block; }
337
338 .stat-add { color: var(--green); font-weight: 600; }
339 .stat-del { color: var(--red); font-weight: 600; }
340
341 .empty-state {
342 text-align: center;
343 padding: 60px 20px;
344 color: var(--text-muted);
345 }
346 .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
347 .empty-state pre {
348 text-align: left;
349 display: inline-block;
350 background: var(--bg-secondary);
351 padding: 16px 24px;
352 border-radius: var(--radius);
353 border: 1px solid var(--border);
354 font-family: var(--font-mono);
355 font-size: 13px;
356 margin-top: 16px;
357 line-height: 1.8;
358 }
359
360 .badge {
361 display: inline-block;
362 padding: 2px 8px;
363 border-radius: 12px;
364 font-size: 12px;
365 font-weight: 500;
366 background: var(--bg-tertiary);
367 border: 1px solid var(--border);
368 color: var(--text-muted);
369 }
370
371 .branch-selector {
372 display: inline-flex;
373 align-items: center;
374 gap: 4px;
375 padding: 4px 12px;
376 background: var(--bg-tertiary);
377 border: 1px solid var(--border);
378 border-radius: var(--radius);
379 font-size: 13px;
380 color: var(--text);
381 margin-bottom: 12px;
06d5ffeClaude382 position: relative;
fc1817aClaude383 }
384
06d5ffeClaude385 .branch-dropdown {
386 position: relative;
387 display: inline-block;
388 margin-bottom: 12px;
389 }
390 .branch-dropdown-content {
391 display: none;
392 position: absolute;
393 top: 100%;
394 left: 0;
395 z-index: 10;
396 min-width: 200px;
397 background: var(--bg-secondary);
398 border: 1px solid var(--border);
399 border-radius: var(--radius);
400 margin-top: 4px;
401 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
402 }
403 .branch-dropdown:hover .branch-dropdown-content,
404 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
405 .branch-dropdown-content a {
406 display: block;
407 padding: 8px 12px;
408 font-size: 13px;
409 color: var(--text);
410 border-bottom: 1px solid var(--border);
411 }
412 .branch-dropdown-content a:last-child { border-bottom: none; }
413 .branch-dropdown-content a:hover { background: var(--bg-tertiary); text-decoration: none; }
414 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; }
415
fc1817aClaude416 .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
417 .card {
418 border: 1px solid var(--border);
419 border-radius: var(--radius);
420 padding: 16px;
421 background: var(--bg-secondary);
06d5ffeClaude422 transition: border-color 0.15s;
fc1817aClaude423 }
06d5ffeClaude424 .card:hover { border-color: var(--text-muted); }
fc1817aClaude425 .card h3 { font-size: 16px; margin-bottom: 4px; }
06d5ffeClaude426 .card h3 a { color: var(--text-link); }
fc1817aClaude427 .card p { font-size: 13px; color: var(--text-muted); }
06d5ffeClaude428 .card-meta { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
429 .card-meta span { display: flex; align-items: center; gap: 4px; }
430
431 .star-btn {
432 display: inline-flex;
433 align-items: center;
434 gap: 6px;
435 padding: 4px 12px;
436 background: var(--bg-tertiary);
437 border: 1px solid var(--border);
438 border-radius: var(--radius);
439 color: var(--text);
440 font-size: 13px;
441 cursor: pointer;
442 }
443 .star-btn:hover { background: var(--border); text-decoration: none; }
444 .star-btn.starred { color: var(--yellow); border-color: var(--yellow); }
445
446 .user-profile {
447 display: flex;
448 gap: 32px;
449 margin-bottom: 32px;
450 }
451 .user-avatar {
452 width: 96px;
453 height: 96px;
454 border-radius: 50%;
455 background: var(--bg-tertiary);
456 border: 1px solid var(--border);
457 display: flex;
458 align-items: center;
459 justify-content: center;
460 font-size: 40px;
461 color: var(--text-muted);
462 flex-shrink: 0;
463 }
464 .user-info h2 { font-size: 24px; margin-bottom: 2px; }
465 .user-info .username { font-size: 16px; color: var(--text-muted); }
466 .user-info .bio { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
467
468 .new-repo-form { max-width: 600px; }
469 .new-repo-form h2 { margin-bottom: 20px; }
470 .visibility-options { display: flex; gap: 12px; margin-bottom: 16px; }
471 .visibility-option {
472 flex: 1;
473 padding: 12px;
474 border: 1px solid var(--border);
475 border-radius: var(--radius);
476 background: var(--bg-secondary);
477 cursor: pointer;
478 text-align: center;
479 }
480 .visibility-option:has(input:checked) { border-color: var(--accent); background: rgba(31, 111, 235, 0.1); }
481 .visibility-option input { display: none; }
482 .visibility-option .vis-label { font-size: 14px; font-weight: 500; }
483 .visibility-option .vis-desc { font-size: 12px; color: var(--text-muted); }
79136bbClaude484
485 /* Issues */
486 .issue-tabs { display: flex; gap: 16px; }
487 .issue-tabs a { color: var(--text-muted); font-size: 14px; font-weight: 500; }
488 .issue-tabs a:hover { color: var(--text); text-decoration: none; }
489 .issue-tabs a.active { color: var(--text); }
490
491 .issue-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
492 .issue-item {
493 display: flex;
494 gap: 12px;
495 align-items: flex-start;
496 padding: 12px 16px;
497 border-bottom: 1px solid var(--border);
498 }
499 .issue-item:last-child { border-bottom: none; }
500 .issue-item:hover { background: var(--bg-secondary); }
501 .issue-state-icon { font-size: 16px; padding-top: 2px; }
502 .state-open { color: var(--green); }
503 .state-closed { color: #986ee2; }
504 .issue-title { font-size: 15px; font-weight: 600; }
505 .issue-title a { color: var(--text); }
506 .issue-title a:hover { color: var(--text-link); }
507 .issue-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
508
509 .issue-badge {
510 display: inline-flex;
511 align-items: center;
512 gap: 4px;
513 padding: 4px 12px;
514 border-radius: 20px;
515 font-size: 13px;
516 font-weight: 500;
517 }
518 .badge-open { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
519 .badge-closed { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
0074234Claude520 .badge-merged { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
521 .state-merged { color: #986ee2; }
522 .ai-review { border-color: var(--accent); }
79136bbClaude523
524 .issue-detail { max-width: 900px; }
525 .issue-comment-box {
526 border: 1px solid var(--border);
527 border-radius: var(--radius);
528 margin-bottom: 16px;
529 overflow: hidden;
530 }
531 .comment-header {
532 background: var(--bg-secondary);
533 padding: 8px 16px;
534 border-bottom: 1px solid var(--border);
535 font-size: 13px;
536 color: var(--text-muted);
537 }
538
539 /* Search */
540 .search-results .diff-file { margin-bottom: 12px; }
16b325cClaude541
542 /* Timeline */
543 .timeline { position: relative; padding-left: 24px; }
544 .timeline::before {
545 content: '';
546 position: absolute;
547 left: 4px;
548 top: 8px;
549 bottom: 8px;
550 width: 2px;
551 background: var(--border);
552 }
553 .timeline-item {
554 position: relative;
555 padding-bottom: 16px;
556 }
557 .timeline-dot {
558 position: absolute;
559 left: -24px;
560 top: 6px;
561 width: 10px;
562 height: 10px;
563 border-radius: 50%;
564 background: var(--text-muted);
565 border: 2px solid var(--bg);
566 }
567 .timeline-content {
568 background: var(--bg-secondary);
569 border: 1px solid var(--border);
570 border-radius: var(--radius);
571 padding: 12px 16px;
572 }
f1ab587Claude573
574 /* Toggle switch */
575 .toggle-switch {
576 position: relative;
577 display: inline-block;
578 width: 44px;
579 height: 24px;
580 flex-shrink: 0;
581 margin-left: 16px;
582 }
583 .toggle-switch input { opacity: 0; width: 0; height: 0; }
584 .toggle-slider {
585 position: absolute;
586 cursor: pointer;
587 top: 0; left: 0; right: 0; bottom: 0;
588 background: var(--bg-tertiary);
589 border: 1px solid var(--border);
590 border-radius: 12px;
591 transition: 0.2s;
592 }
593 .toggle-slider::before {
594 content: '';
595 position: absolute;
596 height: 18px;
597 width: 18px;
598 left: 2px;
599 bottom: 2px;
600 background: var(--text-muted);
601 border-radius: 50%;
602 transition: 0.2s;
603 }
604 .toggle-switch input:checked + .toggle-slider {
605 background: var(--accent);
606 border-color: var(--accent);
607 }
608 .toggle-switch input:checked + .toggle-slider::before {
609 transform: translateX(20px);
610 background: #fff;
611 }
fc1817aClaude612`;