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