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