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.tsxBlame1157 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<
3ef4c9dClaude6 PropsWithChildren<{
7 title?: string;
8 user?: User | null;
9 notificationCount?: number;
6fc53bdClaude10 theme?: "dark" | "light";
3ef4c9dClaude11 }>
6fc53bdClaude12> = ({ children, title, user, notificationCount, theme }) => {
13 const initialTheme = theme === "light" ? "light" : "dark";
fc1817aClaude14 return (
6fc53bdClaude15 <html lang="en" data-theme={initialTheme}>
fc1817aClaude16 <head>
17 <meta charset="UTF-8" />
18 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
eae38d1Claude19 <meta name="theme-color" content="#0d1117" />
20 <link rel="manifest" href="/manifest.webmanifest" />
21 <link rel="icon" type="image/svg+xml" href="/icon.svg" />
fc1817aClaude22 <title>{title ? `${title} — gluecron` : "gluecron"}</title>
6fc53bdClaude23 <script>{themeInitScript}</script>
fc1817aClaude24 <style>{css}</style>
06d5ffeClaude25 <style>{hljsThemeCss}</style>
fc1817aClaude26 </head>
27 <body>
28 <header>
29 <nav>
30 <a href="/" class="logo">
31 gluecron
32 </a>
3ef4c9dClaude33 <div class="nav-search">
34 <form method="GET" action="/search">
35 <input
36 type="search"
37 name="q"
38 placeholder="Search (press /)"
39 aria-label="Search"
40 />
41 </form>
42 </div>
06d5ffeClaude43 <div class="nav-right">
6fc53bdClaude44 <a
45 href="/theme/toggle"
46 class="nav-link nav-theme"
47 title="Toggle theme"
48 aria-label="Toggle theme"
49 >
50 <span class="theme-icon-dark">{"\u263E"}</span>
51 <span class="theme-icon-light">{"\u2600"}</span>
52 </a>
c81ab7aClaude53 <a href="/explore" class="nav-link">
54 Explore
55 </a>
06d5ffeClaude56 {user ? (
57 <>
3ef4c9dClaude58 <a href="/ask" class="nav-link" title="Ask AI (Cmd+K)">
59 {"\u2728"} Ask
60 </a>
61 <a
62 href="/notifications"
63 class="nav-link nav-notifications"
64 title="Notifications"
65 >
66 {"\u2709"}
67 {notificationCount !== undefined && notificationCount > 0 && (
68 <span class="nav-badge">
69 {notificationCount > 99 ? "99+" : notificationCount}
70 </span>
71 )}
72 </a>
06d5ffeClaude73 <a href="/new" class="btn btn-sm btn-primary">
74 + New
75 </a>
76 <a href={`/${user.username}`} class="nav-user">
77 {user.displayName || user.username}
78 </a>
79 <a href="/settings" class="nav-link">
80 Settings
81 </a>
82 <a href="/logout" class="nav-link">
83 Sign out
84 </a>
85 </>
86 ) : (
87 <>
88 <a href="/login" class="nav-link">
89 Sign in
90 </a>
91 <a href="/register" class="btn btn-sm btn-primary">
92 Register
93 </a>
94 </>
95 )}
96 </div>
fc1817aClaude97 </nav>
98 </header>
99 <main>{children}</main>
100 <footer>
101 <span>gluecron — AI-native code intelligence</span>
102 </footer>
71cd5ecClaude103 <div
104 id="cmdk-backdrop"
105 style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9998;backdrop-filter:blur(2px)"
106 />
107 <div
108 id="cmdk-panel"
109 role="dialog"
110 aria-label="Command palette"
111 style="display:none;position:fixed;top:15%;left:50%;transform:translateX(-50%);width:min(560px,90vw);background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;box-shadow:0 12px 40px rgba(0,0,0,0.5);z-index:9999;overflow:hidden"
112 >
113 <input
114 id="cmdk-input"
115 type="text"
116 placeholder="Jump to... (↑↓ navigate, Enter go, Esc close)"
117 autocomplete="off"
118 style="width:100%;box-sizing:border-box;padding:14px 16px;background:transparent;border:none;border-bottom:1px solid var(--border);font-size:15px;color:var(--text);outline:none"
119 />
120 <div
121 id="cmdk-list"
122 style="max-height:360px;overflow-y:auto;font-size:13px"
123 />
124 </div>
3ef4c9dClaude125 <script>{navScript}</script>
eae38d1Claude126 <script>{pwaRegisterScript}</script>
fc1817aClaude127 </body>
128 </html>
129 );
130};
131
6fc53bdClaude132// Runs before paint — reads the theme cookie and flips data-theme so there's
133// no dark-to-light flash on load. SSR default is dark.
134const themeInitScript = `
135 (function(){
136 try {
137 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
138 var t = m ? decodeURIComponent(m[1]) : 'dark';
139 if (t !== 'light' && t !== 'dark') t = 'dark';
140 document.documentElement.setAttribute('data-theme', t);
141 } catch(_){}
142 })();
143`;
144
eae38d1Claude145// Block G1 — register service worker for offline / install support.
146// Kept inline (and tiny) so we don't block first paint.
147const pwaRegisterScript = `
148 if ('serviceWorker' in navigator) {
149 window.addEventListener('load', function(){
150 navigator.serviceWorker.register('/sw.js').catch(function(){});
151 });
152 }
153`;
154
3ef4c9dClaude155const navScript = `
156 (function(){
157 var chord = null;
158 var chordTimer = null;
159 function isTyping(t){
160 t = t || {};
161 var tag = (t.tagName || '').toLowerCase();
162 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
163 }
71cd5ecClaude164
165 // ---------- Block I4 — Command palette ----------
166 var COMMANDS = [
167 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
168 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
169 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
170 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
171 { label: 'Create new repository', href: '/new', kw: 'add create' },
172 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
173 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
174 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
175 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
176 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
177 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
178 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
179 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
180 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
181 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
182 { label: 'Gists', href: '/gists', kw: 'snippets' },
183 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
184 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
185 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
186 ];
187
188 function fuzzyMatch(item, q){
189 if (!q) return true;
190 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
191 q = q.toLowerCase();
192 var qi = 0;
193 for (var i = 0; i < hay.length && qi < q.length; i++) {
194 if (hay[i] === q[qi]) qi++;
195 }
196 return qi === q.length;
197 }
198
199 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
200
201 function render(){
202 if (!list) return;
203 var html = '';
204 for (var i = 0; i < filtered.length; i++) {
205 var item = filtered[i];
206 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
207 var bg = i === selected ? 'background:var(--bg);' : '';
208 html += '<div class="' + cls + '" data-idx="' + i + '" data-href="' + item.href + '"' +
209 ' style="padding:10px 16px;cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
210 '<div>' + item.label + '</div>' +
211 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
212 '</div>';
213 }
214 if (filtered.length === 0) {
215 html = '<div style="padding:16px;color:var(--text-muted);text-align:center">No matches.</div>';
216 }
217 list.innerHTML = html;
218 }
219
220 function openPalette(){
221 backdrop = document.getElementById('cmdk-backdrop');
222 panel = document.getElementById('cmdk-panel');
223 input = document.getElementById('cmdk-input');
224 list = document.getElementById('cmdk-list');
225 if (!backdrop || !panel) return;
226 backdrop.style.display = 'block';
227 panel.style.display = 'block';
228 input.value = '';
229 selected = 0;
230 filtered = COMMANDS.slice();
231 render();
232 input.focus();
233 }
234 function closePalette(){
235 if (backdrop) backdrop.style.display = 'none';
236 if (panel) panel.style.display = 'none';
237 }
238 function go(href){ closePalette(); window.location.href = href; }
239
240 document.addEventListener('click', function(e){
241 var t = e.target;
242 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
243 var item = t && t.closest && t.closest('.cmdk-item');
244 if (item) { go(item.getAttribute('data-href')); }
245 });
246
247 document.addEventListener('input', function(e){
248 if (e.target && e.target.id === 'cmdk-input') {
249 var q = e.target.value;
250 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
251 selected = 0;
252 render();
253 }
254 });
255
3ef4c9dClaude256 document.addEventListener('keydown', function(e){
71cd5ecClaude257 // Palette-scoped keys take priority when open
258 if (panel && panel.style.display === 'block') {
259 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
260 if (e.key === 'ArrowDown') {
261 e.preventDefault();
262 selected = Math.min(filtered.length - 1, selected + 1);
263 render();
264 return;
265 }
266 if (e.key === 'ArrowUp') {
267 e.preventDefault();
268 selected = Math.max(0, selected - 1);
269 render();
270 return;
271 }
272 if (e.key === 'Enter') {
273 e.preventDefault();
274 var item = filtered[selected];
275 if (item) go(item.href);
276 return;
277 }
278 return;
279 }
280
3ef4c9dClaude281 if (isTyping(e.target)) return;
282 // Single key shortcuts
283 if (e.key === '/') {
284 var el = document.querySelector('.nav-search input');
285 if (el) { e.preventDefault(); el.focus(); return; }
286 }
287 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude288 e.preventDefault();
289 openPalette();
290 return;
3ef4c9dClaude291 }
292 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
293 e.preventDefault(); window.location.href = '/shortcuts'; return;
294 }
295 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
296 e.preventDefault(); window.location.href = '/new'; return;
297 }
298 // "g" chord
299 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
300 chord = 'g';
301 clearTimeout(chordTimer);
302 chordTimer = setTimeout(function(){ chord = null; }, 1200);
303 return;
304 }
305 if (chord === 'g') {
306 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
307 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
308 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
309 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
310 chord = null;
311 }
312 });
313 })();
314`;
315
fc1817aClaude316const css = `
6fc53bdClaude317 :root, :root[data-theme='dark'] {
fc1817aClaude318 --bg: #0d1117;
319 --bg-secondary: #161b22;
320 --bg-tertiary: #21262d;
321 --border: #30363d;
322 --text: #e6edf3;
323 --text-muted: #8b949e;
324 --text-link: #58a6ff;
325 --accent: #1f6feb;
326 --accent-hover: #388bfd;
327 --green: #3fb950;
328 --red: #f85149;
329 --yellow: #d29922;
330 --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
331 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
332 --radius: 6px;
333 }
334
6fc53bdClaude335 :root[data-theme='light'] {
336 --bg: #ffffff;
337 --bg-secondary: #f6f8fa;
338 --bg-tertiary: #eaeef2;
339 --border: #d0d7de;
340 --text: #1f2328;
341 --text-muted: #656d76;
342 --text-link: #0969da;
343 --accent: #0969da;
344 --accent-hover: #0550ae;
345 --green: #1a7f37;
346 --red: #cf222e;
347 --yellow: #9a6700;
348 }
349
350 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
351 .nav-theme { display: inline-flex; align-items: center; font-size: 16px; line-height: 1; }
352 :root[data-theme='dark'] .theme-icon-dark { display: none; }
353 :root[data-theme='light'] .theme-icon-light { display: none; }
354
fc1817aClaude355 * { margin: 0; padding: 0; box-sizing: border-box; }
356
357 body {
358 font-family: var(--font-sans);
359 background: var(--bg);
360 color: var(--text);
361 line-height: 1.5;
362 min-height: 100vh;
363 display: flex;
364 flex-direction: column;
365 }
366
367 a { color: var(--text-link); text-decoration: none; }
368 a:hover { text-decoration: underline; }
369
370 header {
371 border-bottom: 1px solid var(--border);
372 padding: 12px 24px;
373 background: var(--bg-secondary);
374 }
375
06d5ffeClaude376 header nav {
377 display: flex;
378 align-items: center;
379 justify-content: space-between;
380 max-width: 1200px;
381 margin: 0 auto;
382 }
fc1817aClaude383 .logo { font-size: 20px; font-weight: 700; color: var(--text); }
384 .logo:hover { text-decoration: none; color: var(--text-link); }
385
06d5ffeClaude386 .nav-right { display: flex; align-items: center; gap: 16px; }
387 .nav-link { color: var(--text-muted); font-size: 14px; }
388 .nav-link:hover { color: var(--text); text-decoration: none; }
389 .nav-user { color: var(--text); font-weight: 600; font-size: 14px; }
390 .nav-user:hover { color: var(--text-link); text-decoration: none; }
391
fc1817aClaude392 main { max-width: 1200px; margin: 0 auto; padding: 24px; flex: 1; width: 100%; }
393
394 footer {
395 border-top: 1px solid var(--border);
396 padding: 16px 24px;
397 text-align: center;
398 color: var(--text-muted);
399 font-size: 13px;
400 }
401
06d5ffeClaude402 /* Buttons */
403 .btn {
404 display: inline-flex;
405 align-items: center;
406 gap: 6px;
407 padding: 8px 16px;
408 border-radius: var(--radius);
409 font-size: 14px;
410 font-weight: 500;
411 border: 1px solid var(--border);
412 background: var(--bg-tertiary);
413 color: var(--text);
414 cursor: pointer;
415 text-decoration: none;
416 line-height: 1.4;
417 }
418 .btn:hover { background: var(--border); text-decoration: none; }
419 .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
420 .btn-primary:hover { background: var(--accent-hover); }
421 .btn-danger { background: transparent; border-color: var(--red); color: var(--red); }
422 .btn-danger:hover { background: rgba(248, 81, 73, 0.15); }
423 .btn-sm { padding: 4px 12px; font-size: 13px; }
424
425 /* Forms */
426 .form-group { margin-bottom: 16px; }
427 .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; color: var(--text); }
428 .form-group input, .form-group textarea, .form-group select {
429 width: 100%;
430 padding: 8px 12px;
431 background: var(--bg);
432 border: 1px solid var(--border);
433 border-radius: var(--radius);
434 color: var(--text);
435 font-size: 14px;
436 font-family: var(--font-sans);
437 }
438 .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
439 outline: none;
440 border-color: var(--accent);
441 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
442 }
443 .input-disabled { opacity: 0.5; cursor: not-allowed; }
444
445 /* Auth */
446 .auth-container { max-width: 400px; margin: 40px auto; }
447 .auth-container h2 { margin-bottom: 20px; font-size: 24px; }
448 .auth-error {
449 background: rgba(248, 81, 73, 0.1);
450 border: 1px solid var(--red);
451 color: var(--red);
452 padding: 8px 12px;
453 border-radius: var(--radius);
454 margin-bottom: 16px;
455 font-size: 14px;
456 }
457 .auth-success {
458 background: rgba(63, 185, 80, 0.1);
459 border: 1px solid var(--green);
460 color: var(--green);
461 padding: 8px 12px;
462 border-radius: var(--radius);
463 margin-bottom: 16px;
464 font-size: 14px;
465 }
466 .auth-switch { margin-top: 16px; font-size: 14px; color: var(--text-muted); }
467
468 /* Settings */
469 .settings-container { max-width: 600px; }
470 .settings-container h2 { margin-bottom: 20px; font-size: 24px; }
471 .settings-container h3 { font-size: 18px; margin-bottom: 12px; }
472 .ssh-keys-list { margin-bottom: 24px; }
473 .ssh-key-item {
474 display: flex;
475 justify-content: space-between;
476 align-items: center;
477 padding: 12px 16px;
478 border: 1px solid var(--border);
479 border-radius: var(--radius);
480 margin-bottom: 8px;
481 background: var(--bg-secondary);
482 }
483 .ssh-key-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
484 .ssh-key-meta code { font-size: 11px; background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
485
486 /* Repo header */
fc1817aClaude487 .repo-header {
488 display: flex;
489 align-items: center;
490 gap: 8px;
491 margin-bottom: 16px;
492 font-size: 20px;
493 }
494 .repo-header .owner { color: var(--text-link); }
495 .repo-header .separator { color: var(--text-muted); }
496 .repo-header .name { color: var(--text-link); font-weight: 600; }
06d5ffeClaude497 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude498
499 .repo-nav {
500 display: flex;
501 gap: 0;
502 border-bottom: 1px solid var(--border);
503 margin-bottom: 20px;
504 }
505 .repo-nav a {
506 padding: 8px 16px;
507 color: var(--text-muted);
508 border-bottom: 2px solid transparent;
509 font-size: 14px;
510 }
511 .repo-nav a:hover { text-decoration: none; color: var(--text); }
512 .repo-nav a.active { color: var(--text); border-bottom-color: var(--accent); }
513
514 .breadcrumb { display: flex; gap: 4px; align-items: center; margin-bottom: 16px; color: var(--text-muted); font-size: 14px; }
515 .breadcrumb a { color: var(--text-link); }
516
517 .file-table { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
518 .file-table tr { border-bottom: 1px solid var(--border); }
519 .file-table tr:last-child { border-bottom: none; }
520 .file-table td { padding: 8px 16px; font-size: 14px; }
521 .file-table tr:hover { background: var(--bg-secondary); }
522 .file-icon { width: 20px; color: var(--text-muted); }
523 .file-name a { color: var(--text); }
524 .file-name a:hover { color: var(--text-link); text-decoration: underline; }
525
526 .blob-view {
527 border: 1px solid var(--border);
528 border-radius: var(--radius);
529 overflow: hidden;
530 }
531 .blob-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);
06d5ffeClaude537 display: flex;
538 justify-content: space-between;
539 align-items: center;
fc1817aClaude540 }
541 .blob-code {
542 overflow-x: auto;
543 font-family: var(--font-mono);
544 font-size: 13px;
545 line-height: 1.6;
546 }
547 .blob-code table { width: 100%; border-collapse: collapse; }
548 .blob-code .line-num {
549 width: 1%;
550 min-width: 50px;
551 padding: 0 12px;
552 text-align: right;
553 color: var(--text-muted);
554 user-select: none;
555 white-space: nowrap;
556 border-right: 1px solid var(--border);
557 }
558 .blob-code .line-content { padding: 0 12px; white-space: pre; }
559
560 .commit-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
561 .commit-item {
562 display: flex;
563 justify-content: space-between;
564 align-items: center;
565 padding: 12px 16px;
566 border-bottom: 1px solid var(--border);
567 }
568 .commit-item:last-child { border-bottom: none; }
569 .commit-item:hover { background: var(--bg-secondary); }
570 .commit-message { font-size: 14px; font-weight: 500; }
571 .commit-meta { font-size: 12px; color: var(--text-muted); }
572 .commit-sha {
573 font-family: var(--font-mono);
574 font-size: 12px;
575 padding: 2px 8px;
576 background: var(--bg-tertiary);
577 border: 1px solid var(--border);
578 border-radius: var(--radius);
579 color: var(--text-link);
580 }
581
582 .diff-view { margin-top: 16px; }
583 .diff-file {
584 border: 1px solid var(--border);
585 border-radius: var(--radius);
586 margin-bottom: 16px;
587 overflow: hidden;
588 }
589 .diff-file-header {
590 background: var(--bg-secondary);
591 padding: 8px 16px;
592 border-bottom: 1px solid var(--border);
593 font-family: var(--font-mono);
594 font-size: 13px;
595 }
596 .diff-content {
597 overflow-x: auto;
598 font-family: var(--font-mono);
599 font-size: 13px;
600 line-height: 1.6;
601 }
602 .diff-content .line-add { background: rgba(63, 185, 80, 0.15); color: var(--green); }
603 .diff-content .line-del { background: rgba(248, 81, 73, 0.1); color: var(--red); }
604 .diff-content .line-hunk { background: rgba(56, 139, 253, 0.1); color: var(--text-link); }
605 .diff-content .line { padding: 0 12px; white-space: pre; display: block; }
606
607 .stat-add { color: var(--green); font-weight: 600; }
608 .stat-del { color: var(--red); font-weight: 600; }
609
610 .empty-state {
611 text-align: center;
612 padding: 60px 20px;
613 color: var(--text-muted);
614 }
615 .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
616 .empty-state pre {
617 text-align: left;
618 display: inline-block;
619 background: var(--bg-secondary);
620 padding: 16px 24px;
621 border-radius: var(--radius);
622 border: 1px solid var(--border);
623 font-family: var(--font-mono);
624 font-size: 13px;
625 margin-top: 16px;
626 line-height: 1.8;
627 }
628
629 .badge {
630 display: inline-block;
631 padding: 2px 8px;
632 border-radius: 12px;
633 font-size: 12px;
634 font-weight: 500;
635 background: var(--bg-tertiary);
636 border: 1px solid var(--border);
637 color: var(--text-muted);
638 }
639
640 .branch-selector {
641 display: inline-flex;
642 align-items: center;
643 gap: 4px;
644 padding: 4px 12px;
645 background: var(--bg-tertiary);
646 border: 1px solid var(--border);
647 border-radius: var(--radius);
648 font-size: 13px;
649 color: var(--text);
650 margin-bottom: 12px;
06d5ffeClaude651 position: relative;
fc1817aClaude652 }
653
06d5ffeClaude654 .branch-dropdown {
655 position: relative;
656 display: inline-block;
657 margin-bottom: 12px;
658 }
659 .branch-dropdown-content {
660 display: none;
661 position: absolute;
662 top: 100%;
663 left: 0;
664 z-index: 10;
665 min-width: 200px;
666 background: var(--bg-secondary);
667 border: 1px solid var(--border);
668 border-radius: var(--radius);
669 margin-top: 4px;
670 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
671 }
672 .branch-dropdown:hover .branch-dropdown-content,
673 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
674 .branch-dropdown-content a {
675 display: block;
676 padding: 8px 12px;
677 font-size: 13px;
678 color: var(--text);
679 border-bottom: 1px solid var(--border);
680 }
681 .branch-dropdown-content a:last-child { border-bottom: none; }
682 .branch-dropdown-content a:hover { background: var(--bg-tertiary); text-decoration: none; }
683 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; }
684
fc1817aClaude685 .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
686 .card {
687 border: 1px solid var(--border);
688 border-radius: var(--radius);
689 padding: 16px;
690 background: var(--bg-secondary);
06d5ffeClaude691 transition: border-color 0.15s;
fc1817aClaude692 }
06d5ffeClaude693 .card:hover { border-color: var(--text-muted); }
fc1817aClaude694 .card h3 { font-size: 16px; margin-bottom: 4px; }
06d5ffeClaude695 .card h3 a { color: var(--text-link); }
fc1817aClaude696 .card p { font-size: 13px; color: var(--text-muted); }
06d5ffeClaude697 .card-meta { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
698 .card-meta span { display: flex; align-items: center; gap: 4px; }
699
700 .star-btn {
701 display: inline-flex;
702 align-items: center;
703 gap: 6px;
704 padding: 4px 12px;
705 background: var(--bg-tertiary);
706 border: 1px solid var(--border);
707 border-radius: var(--radius);
708 color: var(--text);
709 font-size: 13px;
710 cursor: pointer;
711 }
712 .star-btn:hover { background: var(--border); text-decoration: none; }
713 .star-btn.starred { color: var(--yellow); border-color: var(--yellow); }
714
715 .user-profile {
716 display: flex;
717 gap: 32px;
718 margin-bottom: 32px;
719 }
720 .user-avatar {
721 width: 96px;
722 height: 96px;
723 border-radius: 50%;
724 background: var(--bg-tertiary);
725 border: 1px solid var(--border);
726 display: flex;
727 align-items: center;
728 justify-content: center;
729 font-size: 40px;
730 color: var(--text-muted);
731 flex-shrink: 0;
732 }
733 .user-info h2 { font-size: 24px; margin-bottom: 2px; }
734 .user-info .username { font-size: 16px; color: var(--text-muted); }
735 .user-info .bio { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
736
737 .new-repo-form { max-width: 600px; }
738 .new-repo-form h2 { margin-bottom: 20px; }
739 .visibility-options { display: flex; gap: 12px; margin-bottom: 16px; }
740 .visibility-option {
741 flex: 1;
742 padding: 12px;
743 border: 1px solid var(--border);
744 border-radius: var(--radius);
745 background: var(--bg-secondary);
746 cursor: pointer;
747 text-align: center;
748 }
749 .visibility-option:has(input:checked) { border-color: var(--accent); background: rgba(31, 111, 235, 0.1); }
750 .visibility-option input { display: none; }
751 .visibility-option .vis-label { font-size: 14px; font-weight: 500; }
752 .visibility-option .vis-desc { font-size: 12px; color: var(--text-muted); }
79136bbClaude753
754 /* Issues */
755 .issue-tabs { display: flex; gap: 16px; }
756 .issue-tabs a { color: var(--text-muted); font-size: 14px; font-weight: 500; }
757 .issue-tabs a:hover { color: var(--text); text-decoration: none; }
758 .issue-tabs a.active { color: var(--text); }
759
760 .issue-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
761 .issue-item {
762 display: flex;
763 gap: 12px;
764 align-items: flex-start;
765 padding: 12px 16px;
766 border-bottom: 1px solid var(--border);
767 }
768 .issue-item:last-child { border-bottom: none; }
769 .issue-item:hover { background: var(--bg-secondary); }
770 .issue-state-icon { font-size: 16px; padding-top: 2px; }
771 .state-open { color: var(--green); }
772 .state-closed { color: #986ee2; }
773 .issue-title { font-size: 15px; font-weight: 600; }
774 .issue-title a { color: var(--text); }
775 .issue-title a:hover { color: var(--text-link); }
776 .issue-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
777
778 .issue-badge {
779 display: inline-flex;
780 align-items: center;
781 gap: 4px;
782 padding: 4px 12px;
783 border-radius: 20px;
784 font-size: 13px;
785 font-weight: 500;
786 }
787 .badge-open { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
788 .badge-closed { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
0074234Claude789 .badge-merged { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
790 .state-merged { color: #986ee2; }
791 .ai-review { border-color: var(--accent); }
79136bbClaude792
793 .issue-detail { max-width: 900px; }
794 .issue-comment-box {
795 border: 1px solid var(--border);
796 border-radius: var(--radius);
797 margin-bottom: 16px;
798 overflow: hidden;
799 }
800 .comment-header {
801 background: var(--bg-secondary);
802 padding: 8px 16px;
803 border-bottom: 1px solid var(--border);
804 font-size: 13px;
805 color: var(--text-muted);
806 }
807
808 /* Search */
809 .search-results .diff-file { margin-bottom: 12px; }
3ef4c9dClaude810
811 /* Nav — search + ask + notifications badge */
812 .nav-search { flex: 1; max-width: 320px; margin: 0 16px; }
813 .nav-search form { width: 100%; }
814 .nav-search input {
815 width: 100%;
816 padding: 6px 10px;
817 background: var(--bg);
818 border: 1px solid var(--border);
819 border-radius: var(--radius);
820 color: var(--text);
821 font-size: 13px;
822 }
823 .nav-search input::placeholder { color: var(--text-muted); }
824 .nav-search input:focus { outline: none; border-color: var(--accent); }
825 .nav-notifications { position: relative; display: inline-flex; align-items: center; }
826 .nav-badge {
827 position: absolute;
828 top: -6px;
829 right: -10px;
830 min-width: 18px;
831 height: 18px;
832 padding: 0 5px;
833 border-radius: 9px;
834 background: var(--red);
835 color: #fff;
836 font-size: 11px;
837 font-weight: 700;
838 display: flex;
839 align-items: center;
840 justify-content: center;
841 }
842
843 /* Notifications list */
844 .notification-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
845 .notification-item {
846 display: flex;
847 gap: 12px;
848 align-items: flex-start;
849 padding: 12px 16px;
850 border-bottom: 1px solid var(--border);
851 background: var(--bg);
852 }
853 .notification-item.unread { background: rgba(56, 139, 253, 0.06); }
854 .notification-item:last-child { border-bottom: none; }
855 .notification-badge {
856 flex-shrink: 0;
857 padding: 2px 8px;
858 border-radius: 10px;
859 font-size: 11px;
860 font-weight: 600;
861 border: 1px solid;
862 text-transform: lowercase;
863 }
864 .notification-body { flex: 1; min-width: 0; }
865 .notification-title { font-size: 14px; font-weight: 500; margin-bottom: 2px; }
866 .notification-title a { color: var(--text); }
867 .notification-title a:hover { color: var(--text-link); }
868 .notification-desc { font-size: 13px; color: var(--text-muted); margin-bottom: 4px; }
869 .notification-meta { font-size: 12px; color: var(--text-muted); }
870 .notification-meta a { color: var(--text-link); }
871 .notification-actions { display: flex; gap: 4px; align-items: center; }
872 .notification-actions .btn { padding: 2px 8px; font-size: 12px; line-height: 1; }
873
874 /* Dashboard */
875 .dashboard-grid {
876 display: grid;
877 grid-template-columns: 2fr 1fr;
878 gap: 24px;
879 }
880 .dashboard-section { margin-bottom: 24px; }
881 .dashboard-section h3 {
882 font-size: 14px;
883 text-transform: uppercase;
884 letter-spacing: 0.5px;
885 color: var(--text-muted);
886 margin-bottom: 12px;
887 display: flex;
888 justify-content: space-between;
889 align-items: center;
890 }
891 .dashboard-section h3 a { font-size: 12px; font-weight: 400; text-transform: none; letter-spacing: 0; }
892 .panel {
893 border: 1px solid var(--border);
894 border-radius: var(--radius);
895 background: var(--bg-secondary);
896 overflow: hidden;
897 }
898 .panel-item {
899 display: flex;
900 align-items: flex-start;
901 gap: 10px;
902 padding: 10px 14px;
903 border-bottom: 1px solid var(--border);
904 font-size: 13px;
905 }
906 .panel-item:last-child { border-bottom: none; }
907 .panel-item .dot {
908 width: 8px; height: 8px; border-radius: 50%;
909 margin-top: 6px; flex-shrink: 0; background: var(--text-muted);
910 }
911 .panel-item .dot.green { background: var(--green); }
912 .panel-item .dot.red { background: var(--red); }
913 .panel-item .dot.yellow { background: var(--yellow); }
914 .panel-item .dot.blue { background: var(--accent); }
915 .panel-item .meta { color: var(--text-muted); font-size: 12px; margin-top: 2px; }
916 .panel-empty { padding: 24px; text-align: center; color: var(--text-muted); font-size: 13px; }
917
918 /* AI Ask chat */
919 .ask-container { max-width: 900px; margin: 0 auto; }
920 .chat-log {
921 border: 1px solid var(--border);
922 border-radius: var(--radius);
923 background: var(--bg-secondary);
924 padding: 16px;
925 margin-bottom: 16px;
926 min-height: 200px;
927 max-height: 60vh;
928 overflow-y: auto;
929 }
930 .chat-message {
931 padding: 10px 14px;
932 border-radius: var(--radius);
933 margin-bottom: 10px;
934 white-space: pre-wrap;
935 word-wrap: break-word;
936 font-size: 14px;
937 line-height: 1.5;
938 }
939 .chat-message.user { background: var(--bg-tertiary); border: 1px solid var(--border); }
940 .chat-message.assistant { background: rgba(188, 140, 255, 0.08); border: 1px solid rgba(188, 140, 255, 0.3); }
941 .chat-message .role {
942 font-size: 11px;
943 font-weight: 600;
944 text-transform: uppercase;
945 letter-spacing: 0.5px;
946 color: var(--text-muted);
947 margin-bottom: 6px;
948 }
949 .chat-form textarea {
950 width: 100%;
951 min-height: 80px;
952 padding: 10px 12px;
953 background: var(--bg);
954 border: 1px solid var(--border);
955 border-radius: var(--radius);
956 color: var(--text);
957 font-family: var(--font-sans);
958 font-size: 14px;
959 resize: vertical;
960 }
961 .chat-hint { font-size: 12px; color: var(--text-muted); margin-top: 6px; }
962 .chat-cited {
963 display: inline-block;
964 font-family: var(--font-mono);
965 font-size: 11px;
966 padding: 1px 6px;
967 background: var(--bg-tertiary);
968 border: 1px solid var(--border);
969 border-radius: 3px;
970 margin-right: 4px;
971 }
972
973 /* Releases */
974 .release-card {
975 border: 1px solid var(--border);
976 border-radius: var(--radius);
977 background: var(--bg-secondary);
978 padding: 16px;
979 margin-bottom: 16px;
980 }
981 .release-header {
982 display: flex;
983 justify-content: space-between;
984 align-items: baseline;
985 margin-bottom: 8px;
986 gap: 12px;
987 flex-wrap: wrap;
988 }
989 .release-name { font-size: 18px; font-weight: 600; }
990 .release-tag {
991 font-family: var(--font-mono);
992 font-size: 12px;
993 padding: 2px 8px;
994 background: var(--bg-tertiary);
995 border: 1px solid var(--border);
996 border-radius: var(--radius);
997 color: var(--green);
998 }
999 .release-latest { background: var(--green); color: var(--bg); border-color: var(--green); }
1000
1001 /* Gate runs */
1002 .gate-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
1003 .gate-run-row {
1004 display: flex;
1005 align-items: center;
1006 gap: 12px;
1007 padding: 10px 14px;
1008 border-bottom: 1px solid var(--border);
1009 font-size: 13px;
1010 }
1011 .gate-run-row:last-child { border-bottom: none; }
1012 .gate-status {
1013 display: inline-flex;
1014 align-items: center;
1015 gap: 4px;
1016 padding: 2px 8px;
1017 border-radius: 10px;
1018 font-size: 11px;
1019 font-weight: 600;
1020 text-transform: uppercase;
1021 }
1022 .gate-status.passed { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
1023 .gate-status.failed { background: rgba(248, 81, 73, 0.15); color: var(--red); border: 1px solid var(--red); }
1024 .gate-status.repaired { background: rgba(188, 140, 255, 0.15); color: #bc8cff; border: 1px solid #bc8cff; }
1025 .gate-status.skipped { background: var(--bg-tertiary); color: var(--text-muted); border: 1px solid var(--border); }
1026 .gate-status.running, .gate-status.pending { background: rgba(210, 153, 34, 0.15); color: var(--yellow); border: 1px solid var(--yellow); }
1027
1028 /* Search */
1029 .search-filters { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
1030 .search-hit {
1031 border: 1px solid var(--border);
1032 border-radius: var(--radius);
1033 padding: 12px 16px;
1034 background: var(--bg-secondary);
1035 margin-bottom: 8px;
1036 }
1037 .search-hit h4 { font-size: 14px; margin-bottom: 4px; }
1038 .search-hit .hit-path { font-size: 12px; color: var(--text-muted); font-family: var(--font-mono); }
1039 .search-hit pre { margin-top: 8px; font-size: 12px; }
1040
6fc53bdClaude1041 /* Audit log */
1042 .audit-log {
1043 border: 1px solid var(--border);
1044 border-radius: var(--radius);
1045 overflow-x: auto;
1046 background: var(--bg-secondary);
1047 }
1048 .audit-table { width: 100%; border-collapse: collapse; font-size: 13px; }
1049 .audit-table th, .audit-table td {
1050 text-align: left;
1051 padding: 8px 12px;
1052 border-bottom: 1px solid var(--border);
1053 vertical-align: top;
1054 }
1055 .audit-table th {
1056 background: var(--bg-tertiary);
1057 font-size: 11px;
1058 text-transform: uppercase;
1059 letter-spacing: 0.5px;
1060 color: var(--text-muted);
1061 font-weight: 600;
1062 }
1063 .audit-table tr:last-child td { border-bottom: none; }
1064 .audit-when { white-space: nowrap; color: var(--text-muted); }
1065 .audit-muted { color: var(--text-muted); font-style: italic; }
1066 .audit-action {
1067 font-family: var(--font-mono);
1068 font-size: 11px;
1069 padding: 1px 6px;
1070 background: var(--bg-tertiary);
1071 border: 1px solid var(--border);
1072 border-radius: 3px;
1073 color: var(--text-link);
1074 }
1075 .audit-ip, .audit-meta code {
1076 font-family: var(--font-mono);
1077 font-size: 11px;
1078 color: var(--text-muted);
1079 }
1080 .audit-target code {
1081 font-family: var(--font-mono);
1082 font-size: 11px;
1083 color: var(--text-muted);
1084 }
1085
1086 /* Reactions */
1087 .reactions {
1088 display: flex;
1089 gap: 6px;
1090 flex-wrap: wrap;
1091 margin-top: 8px;
1092 }
1093 .reaction-btn {
1094 display: inline-flex;
1095 align-items: center;
1096 gap: 4px;
1097 padding: 2px 8px;
1098 border-radius: 12px;
1099 background: var(--bg-tertiary);
1100 border: 1px solid var(--border);
1101 color: var(--text);
1102 font-size: 12px;
1103 cursor: pointer;
1104 font-family: inherit;
1105 }
1106 .reaction-btn:hover { background: var(--border); }
1107 .reaction-btn.active { background: rgba(31, 111, 235, 0.15); border-color: var(--accent); color: var(--accent); }
1108 .reaction-picker {
1109 display: inline-flex;
1110 gap: 4px;
1111 align-items: center;
1112 }
1113 .reaction-picker form { display: inline; }
1114 .reaction-count { font-size: 11px; font-weight: 600; }
1115
24cf2caClaude1116 /* Saved replies */
1117 .saved-replies-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
1118 .saved-reply-item { border-bottom: 1px solid var(--border); background: var(--bg); }
1119 .saved-reply-item:last-child { border-bottom: none; }
1120 .saved-reply-item > summary {
1121 padding: 10px 16px;
1122 cursor: pointer;
1123 list-style: none;
1124 display: flex;
1125 align-items: center;
1126 }
1127 .saved-reply-item > summary::-webkit-details-marker { display: none; }
1128 .saved-reply-item > summary:hover { background: var(--bg-secondary); }
1129 .saved-reply-item code { font-family: var(--font-mono); font-size: 12px; color: var(--text-link); }
1130
6fc53bdClaude1131 /* Draft PR */
1132 .draft-badge {
1133 background: rgba(139, 148, 158, 0.15);
1134 color: var(--text-muted);
1135 border: 1px solid var(--text-muted);
1136 }
1137 .state-draft { color: var(--text-muted); }
1138
3ef4c9dClaude1139 /* Toasts (prepared for future UI hooks) */
1140 .toast-container {
1141 position: fixed; bottom: 24px; right: 24px;
1142 z-index: 50; display: flex; flex-direction: column; gap: 8px;
1143 }
1144
1145 /* Mobile */
1146 @media (max-width: 768px) {
1147 header nav { flex-wrap: wrap; gap: 8px; }
1148 .nav-search { max-width: 100%; margin: 8px 0; order: 3; width: 100%; }
1149 .nav-right { flex-wrap: wrap; gap: 8px; }
1150 main { padding: 16px; }
1151 .dashboard-grid { grid-template-columns: 1fr; }
1152 .card-grid { grid-template-columns: 1fr; }
1153 .user-profile { flex-direction: column; gap: 16px; }
1154 .repo-header { flex-wrap: wrap; }
1155 .repo-nav { overflow-x: auto; }
1156 }
fc1817aClaude1157`;