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.tsxBlame1150 lines · 1 contributor
fc1817aClaude1import type { FC, PropsWithChildren } from "hono/jsx";
06d5ffeClaude2import type { User } from "../db/schema";
3import { hljsThemeCss } from "../lib/highlight";
45e31d0Claude4import { clientJs } from "./client-js";
fc1817aClaude5
06d5ffeClaude6export const Layout: FC<
3ef4c9dClaude7 PropsWithChildren<{
8 title?: string;
9 user?: User | null;
10 notificationCount?: number;
6fc53bdClaude11 theme?: "dark" | "light";
3ef4c9dClaude12 }>
6fc53bdClaude13> = ({ children, title, user, notificationCount, theme }) => {
14 const initialTheme = theme === "light" ? "light" : "dark";
fc1817aClaude15 return (
6fc53bdClaude16 <html lang="en" data-theme={initialTheme}>
fc1817aClaude17 <head>
18 <meta charset="UTF-8" />
19 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
eae38d1Claude20 <meta name="theme-color" content="#0d1117" />
21 <link rel="manifest" href="/manifest.webmanifest" />
22 <link rel="icon" type="image/svg+xml" href="/icon.svg" />
fc1817aClaude23 <title>{title ? `${title} — gluecron` : "gluecron"}</title>
6fc53bdClaude24 <script>{themeInitScript}</script>
fc1817aClaude25 <style>{css}</style>
06d5ffeClaude26 <style>{hljsThemeCss}</style>
fc1817aClaude27 </head>
28 <body>
45e31d0Claude29 <a href="#main-content" class="skip-link">Skip to main content</a>
fc1817aClaude30 <header>
31 <nav>
32 <a href="/" class="logo">
33 gluecron
34 </a>
3ef4c9dClaude35 <div class="nav-search">
36 <form method="GET" action="/search">
37 <input
38 type="search"
39 name="q"
40 placeholder="Search (press /)"
41 aria-label="Search"
42 />
43 </form>
44 </div>
06d5ffeClaude45 <div class="nav-right">
6fc53bdClaude46 <a
47 href="/theme/toggle"
48 class="nav-link nav-theme"
49 title="Toggle theme"
50 aria-label="Toggle theme"
51 >
52 <span class="theme-icon-dark">{"\u263E"}</span>
53 <span class="theme-icon-light">{"\u2600"}</span>
54 </a>
c81ab7aClaude55 <a href="/explore" class="nav-link">
56 Explore
57 </a>
06d5ffeClaude58 {user ? (
59 <>
3ef4c9dClaude60 <a href="/ask" class="nav-link" title="Ask AI (Cmd+K)">
61 {"\u2728"} Ask
62 </a>
63 <a
64 href="/notifications"
65 class="nav-link nav-notifications"
66 title="Notifications"
67 >
68 {"\u2709"}
69 {notificationCount !== undefined && notificationCount > 0 && (
70 <span class="nav-badge">
71 {notificationCount > 99 ? "99+" : notificationCount}
72 </span>
73 )}
74 </a>
06d5ffeClaude75 <a href="/new" class="btn btn-sm btn-primary">
76 + New
77 </a>
78 <a href={`/${user.username}`} class="nav-user">
79 {user.displayName || user.username}
80 </a>
81 <a href="/settings" class="nav-link">
82 Settings
83 </a>
84 <a href="/logout" class="nav-link">
85 Sign out
86 </a>
87 </>
88 ) : (
89 <>
90 <a href="/login" class="nav-link">
91 Sign in
92 </a>
93 <a href="/register" class="btn btn-sm btn-primary">
94 Register
95 </a>
96 </>
97 )}
98 </div>
fc1817aClaude99 </nav>
100 </header>
45e31d0Claude101 <main id="main-content">{children}</main>
fc1817aClaude102 <footer>
103 <span>gluecron — AI-native code intelligence</span>
45e31d0Claude104 <span style="margin-left:16px">
105 <a href="/api/docs" style="color:var(--text-muted);font-size:12px">API Docs</a>
106 </span>
fc1817aClaude107 </footer>
45e31d0Claude108 <script>{clientJs}</script>
fc1817aClaude109 </body>
110 </html>
111 );
112};
113
6fc53bdClaude114// Runs before paint — reads the theme cookie and flips data-theme so there's
115// no dark-to-light flash on load. SSR default is dark.
116const themeInitScript = `
117 (function(){
118 try {
119 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
120 var t = m ? decodeURIComponent(m[1]) : 'dark';
121 if (t !== 'light' && t !== 'dark') t = 'dark';
122 document.documentElement.setAttribute('data-theme', t);
123 } catch(_){}
124 })();
125`;
126
eae38d1Claude127// Block G1 — register service worker for offline / install support.
128// Kept inline (and tiny) so we don't block first paint.
129const pwaRegisterScript = `
130 if ('serviceWorker' in navigator) {
131 window.addEventListener('load', function(){
132 navigator.serviceWorker.register('/sw.js').catch(function(){});
133 });
134 }
135`;
136
3ef4c9dClaude137const navScript = `
138 (function(){
139 var chord = null;
140 var chordTimer = null;
141 function isTyping(t){
142 t = t || {};
143 var tag = (t.tagName || '').toLowerCase();
144 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
145 }
71cd5ecClaude146
147 // ---------- Block I4 — Command palette ----------
148 var COMMANDS = [
149 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
150 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
151 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
152 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
153 { label: 'Create new repository', href: '/new', kw: 'add create' },
154 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
155 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
156 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
157 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
158 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
159 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
160 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
161 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
162 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
163 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
164 { label: 'Gists', href: '/gists', kw: 'snippets' },
165 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
166 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
167 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
168 ];
169
170 function fuzzyMatch(item, q){
171 if (!q) return true;
172 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
173 q = q.toLowerCase();
174 var qi = 0;
175 for (var i = 0; i < hay.length && qi < q.length; i++) {
176 if (hay[i] === q[qi]) qi++;
177 }
178 return qi === q.length;
179 }
180
181 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
182
183 function render(){
184 if (!list) return;
185 var html = '';
186 for (var i = 0; i < filtered.length; i++) {
187 var item = filtered[i];
188 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
189 var bg = i === selected ? 'background:var(--bg);' : '';
190 html += '<div class="' + cls + '" data-idx="' + i + '" data-href="' + item.href + '"' +
191 ' style="padding:10px 16px;cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
192 '<div>' + item.label + '</div>' +
193 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
194 '</div>';
195 }
196 if (filtered.length === 0) {
197 html = '<div style="padding:16px;color:var(--text-muted);text-align:center">No matches.</div>';
198 }
199 list.innerHTML = html;
200 }
201
202 function openPalette(){
203 backdrop = document.getElementById('cmdk-backdrop');
204 panel = document.getElementById('cmdk-panel');
205 input = document.getElementById('cmdk-input');
206 list = document.getElementById('cmdk-list');
207 if (!backdrop || !panel) return;
208 backdrop.style.display = 'block';
209 panel.style.display = 'block';
210 input.value = '';
211 selected = 0;
212 filtered = COMMANDS.slice();
213 render();
214 input.focus();
215 }
216 function closePalette(){
217 if (backdrop) backdrop.style.display = 'none';
218 if (panel) panel.style.display = 'none';
219 }
220 function go(href){ closePalette(); window.location.href = href; }
221
222 document.addEventListener('click', function(e){
223 var t = e.target;
224 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
225 var item = t && t.closest && t.closest('.cmdk-item');
226 if (item) { go(item.getAttribute('data-href')); }
227 });
228
229 document.addEventListener('input', function(e){
230 if (e.target && e.target.id === 'cmdk-input') {
231 var q = e.target.value;
232 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
233 selected = 0;
234 render();
235 }
236 });
237
3ef4c9dClaude238 document.addEventListener('keydown', function(e){
71cd5ecClaude239 // Palette-scoped keys take priority when open
240 if (panel && panel.style.display === 'block') {
241 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
242 if (e.key === 'ArrowDown') {
243 e.preventDefault();
244 selected = Math.min(filtered.length - 1, selected + 1);
245 render();
246 return;
247 }
248 if (e.key === 'ArrowUp') {
249 e.preventDefault();
250 selected = Math.max(0, selected - 1);
251 render();
252 return;
253 }
254 if (e.key === 'Enter') {
255 e.preventDefault();
256 var item = filtered[selected];
257 if (item) go(item.href);
258 return;
259 }
260 return;
261 }
262
3ef4c9dClaude263 if (isTyping(e.target)) return;
264 // Single key shortcuts
265 if (e.key === '/') {
266 var el = document.querySelector('.nav-search input');
267 if (el) { e.preventDefault(); el.focus(); return; }
268 }
269 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude270 e.preventDefault();
271 openPalette();
272 return;
3ef4c9dClaude273 }
274 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
275 e.preventDefault(); window.location.href = '/shortcuts'; return;
276 }
277 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
278 e.preventDefault(); window.location.href = '/new'; return;
279 }
280 // "g" chord
281 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
282 chord = 'g';
283 clearTimeout(chordTimer);
284 chordTimer = setTimeout(function(){ chord = null; }, 1200);
285 return;
286 }
287 if (chord === 'g') {
288 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
289 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
290 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
291 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
292 chord = null;
293 }
294 });
295 })();
296`;
297
fc1817aClaude298const css = `
6fc53bdClaude299 :root, :root[data-theme='dark'] {
fc1817aClaude300 --bg: #0d1117;
301 --bg-secondary: #161b22;
302 --bg-tertiary: #21262d;
303 --border: #30363d;
304 --text: #e6edf3;
305 --text-muted: #8b949e;
306 --text-link: #58a6ff;
307 --accent: #1f6feb;
308 --accent-hover: #388bfd;
309 --green: #3fb950;
310 --red: #f85149;
311 --yellow: #d29922;
312 --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
313 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
314 --radius: 6px;
315 }
316
6fc53bdClaude317 :root[data-theme='light'] {
318 --bg: #ffffff;
319 --bg-secondary: #f6f8fa;
320 --bg-tertiary: #eaeef2;
321 --border: #d0d7de;
322 --text: #1f2328;
323 --text-muted: #656d76;
324 --text-link: #0969da;
325 --accent: #0969da;
326 --accent-hover: #0550ae;
327 --green: #1a7f37;
328 --red: #cf222e;
329 --yellow: #9a6700;
330 }
331
332 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
333 .nav-theme { display: inline-flex; align-items: center; font-size: 16px; line-height: 1; }
334 :root[data-theme='dark'] .theme-icon-dark { display: none; }
335 :root[data-theme='light'] .theme-icon-light { display: none; }
336
fc1817aClaude337 * { margin: 0; padding: 0; box-sizing: border-box; }
338
339 body {
340 font-family: var(--font-sans);
341 background: var(--bg);
342 color: var(--text);
343 line-height: 1.5;
344 min-height: 100vh;
345 display: flex;
346 flex-direction: column;
347 }
348
349 a { color: var(--text-link); text-decoration: none; }
350 a:hover { text-decoration: underline; }
351
352 header {
353 border-bottom: 1px solid var(--border);
354 padding: 12px 24px;
355 background: var(--bg-secondary);
356 }
357
06d5ffeClaude358 header nav {
359 display: flex;
360 align-items: center;
361 justify-content: space-between;
362 max-width: 1200px;
363 margin: 0 auto;
364 }
fc1817aClaude365 .logo { font-size: 20px; font-weight: 700; color: var(--text); }
366 .logo:hover { text-decoration: none; color: var(--text-link); }
367
06d5ffeClaude368 .nav-right { display: flex; align-items: center; gap: 16px; }
369 .nav-link { color: var(--text-muted); font-size: 14px; }
370 .nav-link:hover { color: var(--text); text-decoration: none; }
371 .nav-user { color: var(--text); font-weight: 600; font-size: 14px; }
372 .nav-user:hover { color: var(--text-link); text-decoration: none; }
373
fc1817aClaude374 main { max-width: 1200px; margin: 0 auto; padding: 24px; flex: 1; width: 100%; }
375
376 footer {
377 border-top: 1px solid var(--border);
378 padding: 16px 24px;
379 text-align: center;
380 color: var(--text-muted);
381 font-size: 13px;
382 }
383
06d5ffeClaude384 /* Buttons */
385 .btn {
386 display: inline-flex;
387 align-items: center;
388 gap: 6px;
389 padding: 8px 16px;
390 border-radius: var(--radius);
391 font-size: 14px;
392 font-weight: 500;
393 border: 1px solid var(--border);
394 background: var(--bg-tertiary);
395 color: var(--text);
396 cursor: pointer;
397 text-decoration: none;
398 line-height: 1.4;
399 }
400 .btn:hover { background: var(--border); text-decoration: none; }
401 .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
402 .btn-primary:hover { background: var(--accent-hover); }
403 .btn-danger { background: transparent; border-color: var(--red); color: var(--red); }
404 .btn-danger:hover { background: rgba(248, 81, 73, 0.15); }
405 .btn-sm { padding: 4px 12px; font-size: 13px; }
406
407 /* Forms */
408 .form-group { margin-bottom: 16px; }
409 .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; color: var(--text); }
410 .form-group input, .form-group textarea, .form-group select {
411 width: 100%;
412 padding: 8px 12px;
413 background: var(--bg);
414 border: 1px solid var(--border);
415 border-radius: var(--radius);
416 color: var(--text);
417 font-size: 14px;
418 font-family: var(--font-sans);
419 }
420 .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
421 outline: none;
422 border-color: var(--accent);
423 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
424 }
425 .input-disabled { opacity: 0.5; cursor: not-allowed; }
426
427 /* Auth */
428 .auth-container { max-width: 400px; margin: 40px auto; }
429 .auth-container h2 { margin-bottom: 20px; font-size: 24px; }
430 .auth-error {
431 background: rgba(248, 81, 73, 0.1);
432 border: 1px solid var(--red);
433 color: var(--red);
434 padding: 8px 12px;
435 border-radius: var(--radius);
436 margin-bottom: 16px;
437 font-size: 14px;
438 }
439 .auth-success {
440 background: rgba(63, 185, 80, 0.1);
441 border: 1px solid var(--green);
442 color: var(--green);
443 padding: 8px 12px;
444 border-radius: var(--radius);
445 margin-bottom: 16px;
446 font-size: 14px;
447 }
448 .auth-switch { margin-top: 16px; font-size: 14px; color: var(--text-muted); }
449
450 /* Settings */
451 .settings-container { max-width: 600px; }
452 .settings-container h2 { margin-bottom: 20px; font-size: 24px; }
453 .settings-container h3 { font-size: 18px; margin-bottom: 12px; }
454 .ssh-keys-list { margin-bottom: 24px; }
455 .ssh-key-item {
456 display: flex;
457 justify-content: space-between;
458 align-items: center;
459 padding: 12px 16px;
460 border: 1px solid var(--border);
461 border-radius: var(--radius);
462 margin-bottom: 8px;
463 background: var(--bg-secondary);
464 }
465 .ssh-key-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
466 .ssh-key-meta code { font-size: 11px; background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
467
468 /* Repo header */
fc1817aClaude469 .repo-header {
470 display: flex;
471 align-items: center;
472 gap: 8px;
473 margin-bottom: 16px;
474 font-size: 20px;
475 }
476 .repo-header .owner { color: var(--text-link); }
477 .repo-header .separator { color: var(--text-muted); }
478 .repo-header .name { color: var(--text-link); font-weight: 600; }
06d5ffeClaude479 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude480
481 .repo-nav {
482 display: flex;
483 gap: 0;
484 border-bottom: 1px solid var(--border);
485 margin-bottom: 20px;
486 }
487 .repo-nav a {
488 padding: 8px 16px;
489 color: var(--text-muted);
490 border-bottom: 2px solid transparent;
491 font-size: 14px;
492 }
493 .repo-nav a:hover { text-decoration: none; color: var(--text); }
494 .repo-nav a.active { color: var(--text); border-bottom-color: var(--accent); }
495
496 .breadcrumb { display: flex; gap: 4px; align-items: center; margin-bottom: 16px; color: var(--text-muted); font-size: 14px; }
497 .breadcrumb a { color: var(--text-link); }
498
499 .file-table { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
500 .file-table tr { border-bottom: 1px solid var(--border); }
501 .file-table tr:last-child { border-bottom: none; }
502 .file-table td { padding: 8px 16px; font-size: 14px; }
503 .file-table tr:hover { background: var(--bg-secondary); }
504 .file-icon { width: 20px; color: var(--text-muted); }
505 .file-name a { color: var(--text); }
506 .file-name a:hover { color: var(--text-link); text-decoration: underline; }
507
508 .blob-view {
509 border: 1px solid var(--border);
510 border-radius: var(--radius);
511 overflow: hidden;
512 }
513 .blob-header {
514 background: var(--bg-secondary);
515 padding: 8px 16px;
516 border-bottom: 1px solid var(--border);
517 font-size: 13px;
518 color: var(--text-muted);
06d5ffeClaude519 display: flex;
520 justify-content: space-between;
521 align-items: center;
fc1817aClaude522 }
523 .blob-code {
524 overflow-x: auto;
525 font-family: var(--font-mono);
526 font-size: 13px;
527 line-height: 1.6;
528 }
529 .blob-code table { width: 100%; border-collapse: collapse; }
530 .blob-code .line-num {
531 width: 1%;
532 min-width: 50px;
533 padding: 0 12px;
534 text-align: right;
535 color: var(--text-muted);
536 user-select: none;
537 white-space: nowrap;
538 border-right: 1px solid var(--border);
539 }
540 .blob-code .line-content { padding: 0 12px; white-space: pre; }
541
542 .commit-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
543 .commit-item {
544 display: flex;
545 justify-content: space-between;
546 align-items: center;
547 padding: 12px 16px;
548 border-bottom: 1px solid var(--border);
549 }
550 .commit-item:last-child { border-bottom: none; }
551 .commit-item:hover { background: var(--bg-secondary); }
552 .commit-message { font-size: 14px; font-weight: 500; }
553 .commit-meta { font-size: 12px; color: var(--text-muted); }
554 .commit-sha {
555 font-family: var(--font-mono);
556 font-size: 12px;
557 padding: 2px 8px;
558 background: var(--bg-tertiary);
559 border: 1px solid var(--border);
560 border-radius: var(--radius);
561 color: var(--text-link);
562 }
563
564 .diff-view { margin-top: 16px; }
565 .diff-file {
566 border: 1px solid var(--border);
567 border-radius: var(--radius);
568 margin-bottom: 16px;
569 overflow: hidden;
570 }
571 .diff-file-header {
572 background: var(--bg-secondary);
573 padding: 8px 16px;
574 border-bottom: 1px solid var(--border);
575 font-family: var(--font-mono);
576 font-size: 13px;
577 }
578 .diff-content {
579 overflow-x: auto;
580 font-family: var(--font-mono);
581 font-size: 13px;
582 line-height: 1.6;
583 }
584 .diff-content .line-add { background: rgba(63, 185, 80, 0.15); color: var(--green); }
585 .diff-content .line-del { background: rgba(248, 81, 73, 0.1); color: var(--red); }
586 .diff-content .line-hunk { background: rgba(56, 139, 253, 0.1); color: var(--text-link); }
587 .diff-content .line { padding: 0 12px; white-space: pre; display: block; }
588
589 .stat-add { color: var(--green); font-weight: 600; }
590 .stat-del { color: var(--red); font-weight: 600; }
591
592 .empty-state {
593 text-align: center;
594 padding: 60px 20px;
595 color: var(--text-muted);
596 }
597 .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
598 .empty-state pre {
599 text-align: left;
600 display: inline-block;
601 background: var(--bg-secondary);
602 padding: 16px 24px;
603 border-radius: var(--radius);
604 border: 1px solid var(--border);
605 font-family: var(--font-mono);
606 font-size: 13px;
607 margin-top: 16px;
608 line-height: 1.8;
609 }
610
611 .badge {
612 display: inline-block;
613 padding: 2px 8px;
614 border-radius: 12px;
615 font-size: 12px;
616 font-weight: 500;
617 background: var(--bg-tertiary);
618 border: 1px solid var(--border);
619 color: var(--text-muted);
620 }
621
622 .branch-selector {
623 display: inline-flex;
624 align-items: center;
625 gap: 4px;
626 padding: 4px 12px;
627 background: var(--bg-tertiary);
628 border: 1px solid var(--border);
629 border-radius: var(--radius);
630 font-size: 13px;
631 color: var(--text);
632 margin-bottom: 12px;
06d5ffeClaude633 position: relative;
fc1817aClaude634 }
635
06d5ffeClaude636 .branch-dropdown {
637 position: relative;
638 display: inline-block;
639 margin-bottom: 12px;
640 }
641 .branch-dropdown-content {
642 display: none;
643 position: absolute;
644 top: 100%;
645 left: 0;
646 z-index: 10;
647 min-width: 200px;
648 background: var(--bg-secondary);
649 border: 1px solid var(--border);
650 border-radius: var(--radius);
651 margin-top: 4px;
652 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
653 }
654 .branch-dropdown:hover .branch-dropdown-content,
655 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
656 .branch-dropdown-content a {
657 display: block;
658 padding: 8px 12px;
659 font-size: 13px;
660 color: var(--text);
661 border-bottom: 1px solid var(--border);
662 }
663 .branch-dropdown-content a:last-child { border-bottom: none; }
664 .branch-dropdown-content a:hover { background: var(--bg-tertiary); text-decoration: none; }
665 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; }
666
fc1817aClaude667 .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
668 .card {
669 border: 1px solid var(--border);
670 border-radius: var(--radius);
671 padding: 16px;
672 background: var(--bg-secondary);
06d5ffeClaude673 transition: border-color 0.15s;
fc1817aClaude674 }
06d5ffeClaude675 .card:hover { border-color: var(--text-muted); }
fc1817aClaude676 .card h3 { font-size: 16px; margin-bottom: 4px; }
06d5ffeClaude677 .card h3 a { color: var(--text-link); }
fc1817aClaude678 .card p { font-size: 13px; color: var(--text-muted); }
06d5ffeClaude679 .card-meta { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
680 .card-meta span { display: flex; align-items: center; gap: 4px; }
681
682 .star-btn {
683 display: inline-flex;
684 align-items: center;
685 gap: 6px;
686 padding: 4px 12px;
687 background: var(--bg-tertiary);
688 border: 1px solid var(--border);
689 border-radius: var(--radius);
690 color: var(--text);
691 font-size: 13px;
692 cursor: pointer;
693 }
694 .star-btn:hover { background: var(--border); text-decoration: none; }
695 .star-btn.starred { color: var(--yellow); border-color: var(--yellow); }
696
697 .user-profile {
698 display: flex;
699 gap: 32px;
700 margin-bottom: 32px;
701 }
702 .user-avatar {
703 width: 96px;
704 height: 96px;
705 border-radius: 50%;
706 background: var(--bg-tertiary);
707 border: 1px solid var(--border);
708 display: flex;
709 align-items: center;
710 justify-content: center;
711 font-size: 40px;
712 color: var(--text-muted);
713 flex-shrink: 0;
714 }
715 .user-info h2 { font-size: 24px; margin-bottom: 2px; }
716 .user-info .username { font-size: 16px; color: var(--text-muted); }
717 .user-info .bio { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
718
719 .new-repo-form { max-width: 600px; }
720 .new-repo-form h2 { margin-bottom: 20px; }
721 .visibility-options { display: flex; gap: 12px; margin-bottom: 16px; }
722 .visibility-option {
723 flex: 1;
724 padding: 12px;
725 border: 1px solid var(--border);
726 border-radius: var(--radius);
727 background: var(--bg-secondary);
728 cursor: pointer;
729 text-align: center;
730 }
731 .visibility-option:has(input:checked) { border-color: var(--accent); background: rgba(31, 111, 235, 0.1); }
732 .visibility-option input { display: none; }
733 .visibility-option .vis-label { font-size: 14px; font-weight: 500; }
734 .visibility-option .vis-desc { font-size: 12px; color: var(--text-muted); }
79136bbClaude735
736 /* Issues */
737 .issue-tabs { display: flex; gap: 16px; }
738 .issue-tabs a { color: var(--text-muted); font-size: 14px; font-weight: 500; }
739 .issue-tabs a:hover { color: var(--text); text-decoration: none; }
740 .issue-tabs a.active { color: var(--text); }
741
742 .issue-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
743 .issue-item {
744 display: flex;
745 gap: 12px;
746 align-items: flex-start;
747 padding: 12px 16px;
748 border-bottom: 1px solid var(--border);
749 }
750 .issue-item:last-child { border-bottom: none; }
751 .issue-item:hover { background: var(--bg-secondary); }
752 .issue-state-icon { font-size: 16px; padding-top: 2px; }
753 .state-open { color: var(--green); }
754 .state-closed { color: #986ee2; }
755 .issue-title { font-size: 15px; font-weight: 600; }
756 .issue-title a { color: var(--text); }
757 .issue-title a:hover { color: var(--text-link); }
758 .issue-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
759
760 .issue-badge {
761 display: inline-flex;
762 align-items: center;
763 gap: 4px;
764 padding: 4px 12px;
765 border-radius: 20px;
766 font-size: 13px;
767 font-weight: 500;
768 }
769 .badge-open { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
770 .badge-closed { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
0074234Claude771 .badge-merged { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
772 .state-merged { color: #986ee2; }
773 .ai-review { border-color: var(--accent); }
79136bbClaude774
775 .issue-detail { max-width: 900px; }
776 .issue-comment-box {
777 border: 1px solid var(--border);
778 border-radius: var(--radius);
779 margin-bottom: 16px;
780 overflow: hidden;
781 }
782 .comment-header {
783 background: var(--bg-secondary);
784 padding: 8px 16px;
785 border-bottom: 1px solid var(--border);
786 font-size: 13px;
787 color: var(--text-muted);
788 }
789
790 /* Search */
791 .search-results .diff-file { margin-bottom: 12px; }
45e31d0Claude792 .search-input {
793 flex: 1;
794 padding: 8px 12px;
795 background: var(--bg);
796 border: 1px solid var(--border);
797 border-radius: var(--radius);
798 color: var(--text);
799 font-size: 14px;
800 }
801 .search-input:focus {
802 outline: none;
803 border-color: var(--accent);
804 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
805 }
806
807 /* Toast Notifications */
808 #toast-container {
809 position: fixed;
810 top: 16px;
811 right: 16px;
812 z-index: 9999;
813 display: flex;
814 flex-direction: column;
815 gap: 8px;
816 }
817 .toast {
818 padding: 10px 16px;
819 border-radius: var(--radius);
820 font-size: 14px;
821 font-weight: 500;
822 opacity: 0;
823 transform: translateX(100%);
824 transition: all 0.3s ease;
825 min-width: 200px;
826 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
827 }
828 .toast-visible { opacity: 1; transform: translateX(0); }
829 .toast-info { background: var(--accent); color: #fff; }
830 .toast-success { background: var(--green); color: #fff; }
831 .toast-error { background: var(--red); color: #fff; }
832 .toast-warning { background: var(--yellow); color: #000; }
833
834 /* Keyboard Shortcut Hints */
835 .kbd {
836 display: inline-block;
837 padding: 2px 6px;
838 border: 1px solid var(--border);
839 border-radius: 4px;
840 background: var(--bg-secondary);
841 font-family: var(--font-mono);
842 font-size: 12px;
843 line-height: 1.4;
844 color: var(--text-muted);
845 box-shadow: 0 1px 0 var(--border);
846 }
847 .shortcut-overlay {
848 position: fixed;
849 inset: 0;
850 background: rgba(0, 0, 0, 0.6);
851 z-index: 9998;
852 display: flex;
853 align-items: center;
854 justify-content: center;
855 }
856 .shortcut-modal {
857 background: var(--bg-secondary);
858 border: 1px solid var(--border);
859 border-radius: var(--radius);
860 padding: 24px;
861 min-width: 300px;
862 max-width: 480px;
863 }
864 .shortcut-modal h3 { margin-bottom: 16px; }
865 .shortcut-grid {
866 display: flex;
867 flex-direction: column;
868 gap: 8px;
869 margin-bottom: 16px;
870 font-size: 14px;
871 }
872 .shortcut-grid div {
873 display: flex;
874 align-items: center;
875 gap: 8px;
876 }
877
878 /* Comment Editor with Preview */
879 .comment-editor {
880 border: 1px solid var(--border);
881 border-radius: var(--radius);
882 overflow: hidden;
883 }
884 .editor-tabs {
885 display: flex;
886 border-bottom: 1px solid var(--border);
887 background: var(--bg-secondary);
888 }
889 .editor-tab {
890 padding: 6px 16px;
891 font-size: 13px;
892 background: none;
893 border: none;
894 color: var(--text-muted);
895 cursor: pointer;
896 border-bottom: 2px solid transparent;
897 }
898 .editor-tab:hover { color: var(--text); }
899 .editor-tab.active { color: var(--text); border-bottom-color: var(--accent); }
900 .comment-editor textarea {
901 width: 100%;
902 border: none;
903 padding: 12px;
904 background: var(--bg);
905 color: var(--text);
906 font-family: var(--font-mono);
907 font-size: 13px;
908 resize: vertical;
909 min-height: 120px;
910 }
911 .comment-editor textarea:focus { outline: none; }
912 .editor-preview {
913 min-height: 120px;
914 background: var(--bg);
915 }
916
917 /* Success Button */
918 .btn-success { background: var(--green); border-color: var(--green); color: #fff; }
919 .btn-success:hover { background: #2ea043; }
920 .btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
921 .btn-ghost:hover { color: var(--text); background: var(--bg-tertiary); }
922 .btn-lg { padding: 12px 24px; font-size: 16px; }
923
924 /* Tab count badge */
925 .tab-count {
926 display: inline-block;
927 padding: 0 6px;
928 margin-left: 4px;
929 font-size: 12px;
930 background: var(--bg-tertiary);
931 border-radius: 10px;
932 }
933
934 /* Copy block */
935 .copy-block {
936 margin: 8px 0;
937 }
938
939 /* Progress bar */
940 .progress-bar {
941 width: 100%;
942 height: 8px;
943 background: var(--bg-tertiary);
944 border-radius: 4px;
945 overflow: hidden;
946 }
947 .progress-fill {
948 height: 100%;
949 background: var(--accent);
950 border-radius: 4px;
951 transition: width 0.3s ease;
952 }
953
954 /* Spinner */
955 .spinner {
956 border: 2px solid var(--border);
957 border-top: 2px solid var(--accent);
958 border-radius: 50%;
959 animation: spin 0.8s linear infinite;
960 }
961 @keyframes spin { to { transform: rotate(360deg); } }
962
963 /* Step indicator (onboarding) */
964 .step-indicator { margin-bottom: 32px; }
965 .step-circle {
966 width: 32px;
967 height: 32px;
968 border-radius: 50%;
969 display: flex;
970 align-items: center;
971 justify-content: center;
972 font-size: 14px;
973 font-weight: 600;
974 background: var(--bg-tertiary);
975 border: 2px solid var(--border);
976 color: var(--text-muted);
977 }
978 .step-completed { background: var(--green); border-color: var(--green); color: #fff; }
979 .step-active { border-color: var(--accent); color: var(--accent); }
980 .step-line {
981 flex: 1;
982 height: 2px;
983 background: var(--border);
984 min-width: 40px;
985 }
986 .step-line[data-completed="true"] { background: var(--green); }
987
988 /* Welcome hero */
989 .welcome-hero {
990 text-align: center;
991 padding: 60px 20px 40px;
992 max-width: 700px;
993 margin: 0 auto;
994 }
995 .welcome-hero h1 { font-size: 36px; margin-bottom: 12px; }
996 .hero-subtitle { font-size: 18px; color: var(--text-muted); margin-bottom: 32px; }
997
998 /* Feature cards */
999 .feature-card {
1000 text-align: center;
1001 padding: 24px;
1002 transition: border-color 0.2s, transform 0.2s;
1003 }
1004 .feature-card:hover { border-color: var(--accent); transform: translateY(-2px); }
1005 .feature-icon { font-size: 36px; margin-bottom: 12px; }
1006 .feature-card h3 { font-size: 16px; margin-bottom: 8px; }
1007
1008 /* Tooltip */
1009 .tooltip-wrapper { position: relative; }
1010 .tooltip-wrapper:hover::after {
1011 content: attr(data-tooltip);
1012 position: absolute;
1013 bottom: 100%;
1014 left: 50%;
1015 transform: translateX(-50%);
1016 padding: 4px 8px;
1017 background: var(--bg-tertiary);
1018 border: 1px solid var(--border);
1019 border-radius: 4px;
1020 font-size: 12px;
1021 white-space: nowrap;
1022 z-index: 100;
1023 margin-bottom: 4px;
1024 }
1025
1026 /* Notification bell */
1027 .notification-bell {
1028 position: relative;
1029 display: inline-flex;
1030 align-items: center;
1031 color: var(--text-muted);
1032 padding: 4px;
1033 }
1034 .notification-bell:hover { color: var(--text); text-decoration: none; }
1035 .notification-count {
1036 position: absolute;
1037 top: -4px;
1038 right: -6px;
1039 background: var(--accent);
1040 color: #fff;
1041 font-size: 10px;
1042 font-weight: 700;
1043 padding: 1px 5px;
1044 border-radius: 10px;
1045 min-width: 16px;
1046 text-align: center;
1047 }
1048
1049 /* Alert variants */
1050 .alert-warning {
1051 background: rgba(210, 153, 34, 0.1);
1052 border: 1px solid var(--yellow);
1053 color: var(--yellow);
1054 padding: 8px 12px;
1055 border-radius: var(--radius);
1056 margin-bottom: 16px;
1057 font-size: 14px;
1058 }
1059 .alert-info {
1060 background: rgba(88, 166, 255, 0.1);
1061 border: 1px solid var(--text-link);
1062 color: var(--text-link);
1063 padding: 8px 12px;
1064 border-radius: var(--radius);
1065 margin-bottom: 16px;
1066 font-size: 14px;
1067 }
1068
1069 /* Badge variants */
1070 .badge-success { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
1071 .badge-danger { background: rgba(248, 81, 73, 0.1); color: var(--red); border: 1px solid var(--red); }
1072 .badge-warning { background: rgba(210, 153, 34, 0.1); color: var(--yellow); border: 1px solid var(--yellow); }
1073
1074 /* Mobile responsiveness */
1075 @media (max-width: 768px) {
1076 main { padding: 16px; }
1077 .card-grid { grid-template-columns: 1fr; }
1078 .user-profile { flex-direction: column; gap: 16px; }
1079 .repo-header { flex-wrap: wrap; }
1080 .repo-header-actions { margin-left: 0; }
1081 .repo-nav { overflow-x: auto; }
1082 .blob-code { font-size: 12px; }
1083 .diff-content { font-size: 12px; }
1084 .hamburger-btn {
1085 display: inline-flex;
1086 align-items: center;
1087 justify-content: center;
1088 width: 36px;
1089 height: 36px;
1090 background: none;
1091 border: 1px solid var(--border);
1092 border-radius: var(--radius);
1093 color: var(--text);
1094 font-size: 18px;
1095 cursor: pointer;
1096 }
1097 .mobile-hidden { display: none !important; }
1098 .mobile-visible {
1099 display: flex !important;
1100 position: absolute;
1101 top: 100%;
1102 right: 0;
1103 background: var(--bg-secondary);
1104 border: 1px solid var(--border);
1105 border-radius: var(--radius);
1106 padding: 8px;
1107 flex-direction: column;
1108 gap: 4px;
1109 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
1110 min-width: 200px;
1111 }
1112 .mobile-visible a, .mobile-visible button {
1113 padding: 8px 12px;
1114 display: block;
1115 width: 100%;
1116 text-align: left;
1117 }
1118 .issue-item { flex-wrap: wrap; }
1119 .commit-item { flex-direction: column; gap: 8px; }
1120 .settings-container { max-width: 100%; }
1121 .auth-container { max-width: 100%; }
1122 }
1123
1124 /* Focus visible for keyboard nav */
1125 :focus-visible {
1126 outline: 2px solid var(--accent);
1127 outline-offset: 2px;
1128 }
1129
1130 /* Skip to main content (accessibility) */
1131 .skip-link {
1132 position: absolute;
1133 top: -100px;
1134 left: 0;
1135 background: var(--accent);
1136 color: #fff;
1137 padding: 8px 16px;
1138 z-index: 9999;
1139 font-size: 14px;
1140 }
1141 .skip-link:focus { top: 0; }
1142
1143 /* Markdown body spacing */
1144 .markdown-body { padding: 16px; }
1145 .markdown-body h1, .markdown-body h2, .markdown-body h3 { margin-top: 1.5em; margin-bottom: 0.5em; }
1146 .markdown-body p { margin-bottom: 1em; }
1147 .markdown-body pre { margin: 1em 0; }
1148 .markdown-body code { font-size: 85%; }
1149 .markdown-body ul, .markdown-body ol { padding-left: 2em; margin-bottom: 1em; }
fc1817aClaude1150`;