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.tsxBlame1166 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>
4a52a98Claude29 <div class="prelaunch-banner" role="status" aria-live="polite">
30 Pre-launch &mdash; Gluecron is in final validation. Public signups
31 and git hosting for non-owner users open after launch review.
32 </div>
fc1817aClaude33 <header>
34 <nav>
35 <a href="/" class="logo">
36 gluecron
37 </a>
3ef4c9dClaude38 <div class="nav-search">
001af43Claude39 <form method="get" action="/search">
3ef4c9dClaude40 <input
41 type="search"
42 name="q"
43 placeholder="Search (press /)"
44 aria-label="Search"
45 />
46 </form>
47 </div>
06d5ffeClaude48 <div class="nav-right">
6fc53bdClaude49 <a
50 href="/theme/toggle"
51 class="nav-link nav-theme"
52 title="Toggle theme"
53 aria-label="Toggle theme"
54 >
55 <span class="theme-icon-dark">{"\u263E"}</span>
56 <span class="theme-icon-light">{"\u2600"}</span>
57 </a>
c81ab7aClaude58 <a href="/explore" class="nav-link">
59 Explore
60 </a>
06d5ffeClaude61 {user ? (
62 <>
3ef4c9dClaude63 <a href="/ask" class="nav-link" title="Ask AI (Cmd+K)">
64 {"\u2728"} Ask
65 </a>
66 <a
67 href="/notifications"
68 class="nav-link nav-notifications"
69 title="Notifications"
70 >
71 {"\u2709"}
72 {notificationCount !== undefined && notificationCount > 0 && (
73 <span class="nav-badge">
74 {notificationCount > 99 ? "99+" : notificationCount}
75 </span>
76 )}
77 </a>
06d5ffeClaude78 <a href="/new" class="btn btn-sm btn-primary">
79 + New
80 </a>
81 <a href={`/${user.username}`} class="nav-user">
82 {user.displayName || user.username}
83 </a>
84 <a href="/settings" class="nav-link">
85 Settings
86 </a>
87 <a href="/logout" class="nav-link">
88 Sign out
89 </a>
90 </>
91 ) : (
92 <>
93 <a href="/login" class="nav-link">
94 Sign in
95 </a>
96 <a href="/register" class="btn btn-sm btn-primary">
97 Register
98 </a>
99 </>
100 )}
101 </div>
fc1817aClaude102 </nav>
103 </header>
45e31d0Claude104 <main id="main-content">{children}</main>
fc1817aClaude105 <footer>
106 <span>gluecron — AI-native code intelligence</span>
45e31d0Claude107 <span style="margin-left:16px">
108 <a href="/api/docs" style="color:var(--text-muted);font-size:12px">API Docs</a>
109 </span>
fc1817aClaude110 </footer>
45e31d0Claude111 <script>{clientJs}</script>
fc1817aClaude112 </body>
113 </html>
114 );
115};
116
6fc53bdClaude117// Runs before paint — reads the theme cookie and flips data-theme so there's
118// no dark-to-light flash on load. SSR default is dark.
119const themeInitScript = `
120 (function(){
121 try {
122 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
123 var t = m ? decodeURIComponent(m[1]) : 'dark';
124 if (t !== 'light' && t !== 'dark') t = 'dark';
125 document.documentElement.setAttribute('data-theme', t);
126 } catch(_){}
127 })();
128`;
129
eae38d1Claude130// Block G1 — register service worker for offline / install support.
131// Kept inline (and tiny) so we don't block first paint.
132const pwaRegisterScript = `
133 if ('serviceWorker' in navigator) {
134 window.addEventListener('load', function(){
135 navigator.serviceWorker.register('/sw.js').catch(function(){});
136 });
137 }
138`;
139
3ef4c9dClaude140const navScript = `
141 (function(){
142 var chord = null;
143 var chordTimer = null;
144 function isTyping(t){
145 t = t || {};
146 var tag = (t.tagName || '').toLowerCase();
147 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
148 }
71cd5ecClaude149
150 // ---------- Block I4 — Command palette ----------
151 var COMMANDS = [
152 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
153 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
154 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
155 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
156 { label: 'Create new repository', href: '/new', kw: 'add create' },
157 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
158 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
159 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
160 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
161 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
162 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
163 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
164 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
165 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
166 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
167 { label: 'Gists', href: '/gists', kw: 'snippets' },
168 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
169 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
170 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
171 ];
172
173 function fuzzyMatch(item, q){
174 if (!q) return true;
175 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
176 q = q.toLowerCase();
177 var qi = 0;
178 for (var i = 0; i < hay.length && qi < q.length; i++) {
179 if (hay[i] === q[qi]) qi++;
180 }
181 return qi === q.length;
182 }
183
184 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
185
186 function render(){
187 if (!list) return;
188 var html = '';
189 for (var i = 0; i < filtered.length; i++) {
190 var item = filtered[i];
191 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
192 var bg = i === selected ? 'background:var(--bg);' : '';
193 html += '<div class="' + cls + '" data-idx="' + i + '" data-href="' + item.href + '"' +
194 ' style="padding:10px 16px;cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
195 '<div>' + item.label + '</div>' +
196 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
197 '</div>';
198 }
199 if (filtered.length === 0) {
200 html = '<div style="padding:16px;color:var(--text-muted);text-align:center">No matches.</div>';
201 }
202 list.innerHTML = html;
203 }
204
205 function openPalette(){
206 backdrop = document.getElementById('cmdk-backdrop');
207 panel = document.getElementById('cmdk-panel');
208 input = document.getElementById('cmdk-input');
209 list = document.getElementById('cmdk-list');
210 if (!backdrop || !panel) return;
211 backdrop.style.display = 'block';
212 panel.style.display = 'block';
213 input.value = '';
214 selected = 0;
215 filtered = COMMANDS.slice();
216 render();
217 input.focus();
218 }
219 function closePalette(){
220 if (backdrop) backdrop.style.display = 'none';
221 if (panel) panel.style.display = 'none';
222 }
223 function go(href){ closePalette(); window.location.href = href; }
224
225 document.addEventListener('click', function(e){
226 var t = e.target;
227 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
228 var item = t && t.closest && t.closest('.cmdk-item');
229 if (item) { go(item.getAttribute('data-href')); }
230 });
231
232 document.addEventListener('input', function(e){
233 if (e.target && e.target.id === 'cmdk-input') {
234 var q = e.target.value;
235 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
236 selected = 0;
237 render();
238 }
239 });
240
3ef4c9dClaude241 document.addEventListener('keydown', function(e){
71cd5ecClaude242 // Palette-scoped keys take priority when open
243 if (panel && panel.style.display === 'block') {
244 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
245 if (e.key === 'ArrowDown') {
246 e.preventDefault();
247 selected = Math.min(filtered.length - 1, selected + 1);
248 render();
249 return;
250 }
251 if (e.key === 'ArrowUp') {
252 e.preventDefault();
253 selected = Math.max(0, selected - 1);
254 render();
255 return;
256 }
257 if (e.key === 'Enter') {
258 e.preventDefault();
259 var item = filtered[selected];
260 if (item) go(item.href);
261 return;
262 }
263 return;
264 }
265
3ef4c9dClaude266 if (isTyping(e.target)) return;
267 // Single key shortcuts
268 if (e.key === '/') {
269 var el = document.querySelector('.nav-search input');
270 if (el) { e.preventDefault(); el.focus(); return; }
271 }
272 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude273 e.preventDefault();
274 openPalette();
275 return;
3ef4c9dClaude276 }
277 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
278 e.preventDefault(); window.location.href = '/shortcuts'; return;
279 }
280 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
281 e.preventDefault(); window.location.href = '/new'; return;
282 }
283 // "g" chord
284 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
285 chord = 'g';
286 clearTimeout(chordTimer);
287 chordTimer = setTimeout(function(){ chord = null; }, 1200);
288 return;
289 }
290 if (chord === 'g') {
291 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
292 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
293 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
294 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
295 chord = null;
296 }
297 });
298 })();
299`;
300
fc1817aClaude301const css = `
6fc53bdClaude302 :root, :root[data-theme='dark'] {
fc1817aClaude303 --bg: #0d1117;
304 --bg-secondary: #161b22;
305 --bg-tertiary: #21262d;
306 --border: #30363d;
307 --text: #e6edf3;
308 --text-muted: #8b949e;
309 --text-link: #58a6ff;
310 --accent: #1f6feb;
311 --accent-hover: #388bfd;
312 --green: #3fb950;
313 --red: #f85149;
314 --yellow: #d29922;
315 --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
316 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
317 --radius: 6px;
318 }
319
6fc53bdClaude320 :root[data-theme='light'] {
321 --bg: #ffffff;
322 --bg-secondary: #f6f8fa;
323 --bg-tertiary: #eaeef2;
324 --border: #d0d7de;
325 --text: #1f2328;
326 --text-muted: #656d76;
327 --text-link: #0969da;
328 --accent: #0969da;
329 --accent-hover: #0550ae;
330 --green: #1a7f37;
331 --red: #cf222e;
332 --yellow: #9a6700;
333 }
334
335 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
336 .nav-theme { display: inline-flex; align-items: center; font-size: 16px; line-height: 1; }
337 :root[data-theme='dark'] .theme-icon-dark { display: none; }
338 :root[data-theme='light'] .theme-icon-light { display: none; }
339
fc1817aClaude340 * { margin: 0; padding: 0; box-sizing: border-box; }
341
342 body {
343 font-family: var(--font-sans);
344 background: var(--bg);
345 color: var(--text);
346 line-height: 1.5;
347 min-height: 100vh;
348 display: flex;
349 flex-direction: column;
350 }
351
352 a { color: var(--text-link); text-decoration: none; }
353 a:hover { text-decoration: underline; }
354
4a52a98Claude355 /* Pre-launch banner - always visible, not dismissible. Amber tone, readable
356 on both light and dark themes via the shared --yellow token. */
357 .prelaunch-banner {
358 background: rgba(210, 153, 34, 0.15);
359 border-bottom: 1px solid var(--yellow);
360 color: var(--yellow);
361 padding: 8px 24px;
362 font-size: 13px;
363 font-weight: 500;
364 text-align: center;
365 line-height: 1.4;
366 }
367
fc1817aClaude368 header {
369 border-bottom: 1px solid var(--border);
370 padding: 12px 24px;
371 background: var(--bg-secondary);
372 }
373
06d5ffeClaude374 header nav {
375 display: flex;
376 align-items: center;
377 justify-content: space-between;
378 max-width: 1200px;
379 margin: 0 auto;
380 }
fc1817aClaude381 .logo { font-size: 20px; font-weight: 700; color: var(--text); }
382 .logo:hover { text-decoration: none; color: var(--text-link); }
383
06d5ffeClaude384 .nav-right { display: flex; align-items: center; gap: 16px; }
385 .nav-link { color: var(--text-muted); font-size: 14px; }
386 .nav-link:hover { color: var(--text); text-decoration: none; }
387 .nav-user { color: var(--text); font-weight: 600; font-size: 14px; }
388 .nav-user:hover { color: var(--text-link); text-decoration: none; }
389
fc1817aClaude390 main { max-width: 1200px; margin: 0 auto; padding: 24px; flex: 1; width: 100%; }
391
392 footer {
393 border-top: 1px solid var(--border);
394 padding: 16px 24px;
395 text-align: center;
396 color: var(--text-muted);
397 font-size: 13px;
398 }
399
06d5ffeClaude400 /* Buttons */
401 .btn {
402 display: inline-flex;
403 align-items: center;
404 gap: 6px;
405 padding: 8px 16px;
406 border-radius: var(--radius);
407 font-size: 14px;
408 font-weight: 500;
409 border: 1px solid var(--border);
410 background: var(--bg-tertiary);
411 color: var(--text);
412 cursor: pointer;
413 text-decoration: none;
414 line-height: 1.4;
415 }
416 .btn:hover { background: var(--border); text-decoration: none; }
417 .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
418 .btn-primary:hover { background: var(--accent-hover); }
419 .btn-danger { background: transparent; border-color: var(--red); color: var(--red); }
420 .btn-danger:hover { background: rgba(248, 81, 73, 0.15); }
421 .btn-sm { padding: 4px 12px; font-size: 13px; }
422
423 /* Forms */
424 .form-group { margin-bottom: 16px; }
425 .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; color: var(--text); }
426 .form-group input, .form-group textarea, .form-group select {
427 width: 100%;
428 padding: 8px 12px;
429 background: var(--bg);
430 border: 1px solid var(--border);
431 border-radius: var(--radius);
432 color: var(--text);
433 font-size: 14px;
434 font-family: var(--font-sans);
435 }
436 .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
437 outline: none;
438 border-color: var(--accent);
439 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
440 }
441 .input-disabled { opacity: 0.5; cursor: not-allowed; }
442
443 /* Auth */
444 .auth-container { max-width: 400px; margin: 40px auto; }
445 .auth-container h2 { margin-bottom: 20px; font-size: 24px; }
446 .auth-error {
447 background: rgba(248, 81, 73, 0.1);
448 border: 1px solid var(--red);
449 color: var(--red);
450 padding: 8px 12px;
451 border-radius: var(--radius);
452 margin-bottom: 16px;
453 font-size: 14px;
454 }
455 .auth-success {
456 background: rgba(63, 185, 80, 0.1);
457 border: 1px solid var(--green);
458 color: var(--green);
459 padding: 8px 12px;
460 border-radius: var(--radius);
461 margin-bottom: 16px;
462 font-size: 14px;
463 }
464 .auth-switch { margin-top: 16px; font-size: 14px; color: var(--text-muted); }
465
466 /* Settings */
467 .settings-container { max-width: 600px; }
468 .settings-container h2 { margin-bottom: 20px; font-size: 24px; }
469 .settings-container h3 { font-size: 18px; margin-bottom: 12px; }
470 .ssh-keys-list { margin-bottom: 24px; }
471 .ssh-key-item {
472 display: flex;
473 justify-content: space-between;
474 align-items: center;
475 padding: 12px 16px;
476 border: 1px solid var(--border);
477 border-radius: var(--radius);
478 margin-bottom: 8px;
479 background: var(--bg-secondary);
480 }
481 .ssh-key-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
482 .ssh-key-meta code { font-size: 11px; background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
483
484 /* Repo header */
fc1817aClaude485 .repo-header {
486 display: flex;
487 align-items: center;
488 gap: 8px;
489 margin-bottom: 16px;
490 font-size: 20px;
491 }
492 .repo-header .owner { color: var(--text-link); }
493 .repo-header .separator { color: var(--text-muted); }
494 .repo-header .name { color: var(--text-link); font-weight: 600; }
06d5ffeClaude495 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude496
497 .repo-nav {
498 display: flex;
499 gap: 0;
500 border-bottom: 1px solid var(--border);
501 margin-bottom: 20px;
502 }
503 .repo-nav a {
504 padding: 8px 16px;
505 color: var(--text-muted);
506 border-bottom: 2px solid transparent;
507 font-size: 14px;
508 }
509 .repo-nav a:hover { text-decoration: none; color: var(--text); }
510 .repo-nav a.active { color: var(--text); border-bottom-color: var(--accent); }
511
512 .breadcrumb { display: flex; gap: 4px; align-items: center; margin-bottom: 16px; color: var(--text-muted); font-size: 14px; }
513 .breadcrumb a { color: var(--text-link); }
514
515 .file-table { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
516 .file-table tr { border-bottom: 1px solid var(--border); }
517 .file-table tr:last-child { border-bottom: none; }
518 .file-table td { padding: 8px 16px; font-size: 14px; }
519 .file-table tr:hover { background: var(--bg-secondary); }
520 .file-icon { width: 20px; color: var(--text-muted); }
521 .file-name a { color: var(--text); }
522 .file-name a:hover { color: var(--text-link); text-decoration: underline; }
523
524 .blob-view {
525 border: 1px solid var(--border);
526 border-radius: var(--radius);
527 overflow: hidden;
528 }
529 .blob-header {
530 background: var(--bg-secondary);
531 padding: 8px 16px;
532 border-bottom: 1px solid var(--border);
533 font-size: 13px;
534 color: var(--text-muted);
06d5ffeClaude535 display: flex;
536 justify-content: space-between;
537 align-items: center;
fc1817aClaude538 }
539 .blob-code {
540 overflow-x: auto;
541 font-family: var(--font-mono);
542 font-size: 13px;
543 line-height: 1.6;
544 }
545 .blob-code table { width: 100%; border-collapse: collapse; }
546 .blob-code .line-num {
547 width: 1%;
548 min-width: 50px;
549 padding: 0 12px;
550 text-align: right;
551 color: var(--text-muted);
552 user-select: none;
553 white-space: nowrap;
554 border-right: 1px solid var(--border);
555 }
556 .blob-code .line-content { padding: 0 12px; white-space: pre; }
557
558 .commit-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
559 .commit-item {
560 display: flex;
561 justify-content: space-between;
562 align-items: center;
563 padding: 12px 16px;
564 border-bottom: 1px solid var(--border);
565 }
566 .commit-item:last-child { border-bottom: none; }
567 .commit-item:hover { background: var(--bg-secondary); }
568 .commit-message { font-size: 14px; font-weight: 500; }
569 .commit-meta { font-size: 12px; color: var(--text-muted); }
570 .commit-sha {
571 font-family: var(--font-mono);
572 font-size: 12px;
573 padding: 2px 8px;
574 background: var(--bg-tertiary);
575 border: 1px solid var(--border);
576 border-radius: var(--radius);
577 color: var(--text-link);
578 }
579
580 .diff-view { margin-top: 16px; }
581 .diff-file {
582 border: 1px solid var(--border);
583 border-radius: var(--radius);
584 margin-bottom: 16px;
585 overflow: hidden;
586 }
587 .diff-file-header {
588 background: var(--bg-secondary);
589 padding: 8px 16px;
590 border-bottom: 1px solid var(--border);
591 font-family: var(--font-mono);
592 font-size: 13px;
593 }
594 .diff-content {
595 overflow-x: auto;
596 font-family: var(--font-mono);
597 font-size: 13px;
598 line-height: 1.6;
599 }
600 .diff-content .line-add { background: rgba(63, 185, 80, 0.15); color: var(--green); }
601 .diff-content .line-del { background: rgba(248, 81, 73, 0.1); color: var(--red); }
602 .diff-content .line-hunk { background: rgba(56, 139, 253, 0.1); color: var(--text-link); }
603 .diff-content .line { padding: 0 12px; white-space: pre; display: block; }
604
605 .stat-add { color: var(--green); font-weight: 600; }
606 .stat-del { color: var(--red); font-weight: 600; }
607
608 .empty-state {
609 text-align: center;
610 padding: 60px 20px;
611 color: var(--text-muted);
612 }
613 .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
614 .empty-state pre {
615 text-align: left;
616 display: inline-block;
617 background: var(--bg-secondary);
618 padding: 16px 24px;
619 border-radius: var(--radius);
620 border: 1px solid var(--border);
621 font-family: var(--font-mono);
622 font-size: 13px;
623 margin-top: 16px;
624 line-height: 1.8;
625 }
626
627 .badge {
628 display: inline-block;
629 padding: 2px 8px;
630 border-radius: 12px;
631 font-size: 12px;
632 font-weight: 500;
633 background: var(--bg-tertiary);
634 border: 1px solid var(--border);
635 color: var(--text-muted);
636 }
637
638 .branch-selector {
639 display: inline-flex;
640 align-items: center;
641 gap: 4px;
642 padding: 4px 12px;
643 background: var(--bg-tertiary);
644 border: 1px solid var(--border);
645 border-radius: var(--radius);
646 font-size: 13px;
647 color: var(--text);
648 margin-bottom: 12px;
06d5ffeClaude649 position: relative;
fc1817aClaude650 }
651
06d5ffeClaude652 .branch-dropdown {
653 position: relative;
654 display: inline-block;
655 margin-bottom: 12px;
656 }
657 .branch-dropdown-content {
658 display: none;
659 position: absolute;
660 top: 100%;
661 left: 0;
662 z-index: 10;
663 min-width: 200px;
664 background: var(--bg-secondary);
665 border: 1px solid var(--border);
666 border-radius: var(--radius);
667 margin-top: 4px;
668 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
669 }
670 .branch-dropdown:hover .branch-dropdown-content,
671 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
672 .branch-dropdown-content a {
673 display: block;
674 padding: 8px 12px;
675 font-size: 13px;
676 color: var(--text);
677 border-bottom: 1px solid var(--border);
678 }
679 .branch-dropdown-content a:last-child { border-bottom: none; }
680 .branch-dropdown-content a:hover { background: var(--bg-tertiary); text-decoration: none; }
681 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; }
682
fc1817aClaude683 .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
684 .card {
685 border: 1px solid var(--border);
686 border-radius: var(--radius);
687 padding: 16px;
688 background: var(--bg-secondary);
06d5ffeClaude689 transition: border-color 0.15s;
fc1817aClaude690 }
06d5ffeClaude691 .card:hover { border-color: var(--text-muted); }
fc1817aClaude692 .card h3 { font-size: 16px; margin-bottom: 4px; }
06d5ffeClaude693 .card h3 a { color: var(--text-link); }
fc1817aClaude694 .card p { font-size: 13px; color: var(--text-muted); }
06d5ffeClaude695 .card-meta { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
696 .card-meta span { display: flex; align-items: center; gap: 4px; }
697
698 .star-btn {
699 display: inline-flex;
700 align-items: center;
701 gap: 6px;
702 padding: 4px 12px;
703 background: var(--bg-tertiary);
704 border: 1px solid var(--border);
705 border-radius: var(--radius);
706 color: var(--text);
707 font-size: 13px;
708 cursor: pointer;
709 }
710 .star-btn:hover { background: var(--border); text-decoration: none; }
711 .star-btn.starred { color: var(--yellow); border-color: var(--yellow); }
712
713 .user-profile {
714 display: flex;
715 gap: 32px;
716 margin-bottom: 32px;
717 }
718 .user-avatar {
719 width: 96px;
720 height: 96px;
721 border-radius: 50%;
722 background: var(--bg-tertiary);
723 border: 1px solid var(--border);
724 display: flex;
725 align-items: center;
726 justify-content: center;
727 font-size: 40px;
728 color: var(--text-muted);
729 flex-shrink: 0;
730 }
731 .user-info h2 { font-size: 24px; margin-bottom: 2px; }
732 .user-info .username { font-size: 16px; color: var(--text-muted); }
733 .user-info .bio { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
734
735 .new-repo-form { max-width: 600px; }
736 .new-repo-form h2 { margin-bottom: 20px; }
737 .visibility-options { display: flex; gap: 12px; margin-bottom: 16px; }
738 .visibility-option {
739 flex: 1;
740 padding: 12px;
741 border: 1px solid var(--border);
742 border-radius: var(--radius);
743 background: var(--bg-secondary);
744 cursor: pointer;
745 text-align: center;
746 }
747 .visibility-option:has(input:checked) { border-color: var(--accent); background: rgba(31, 111, 235, 0.1); }
748 .visibility-option input { display: none; }
749 .visibility-option .vis-label { font-size: 14px; font-weight: 500; }
750 .visibility-option .vis-desc { font-size: 12px; color: var(--text-muted); }
79136bbClaude751
752 /* Issues */
753 .issue-tabs { display: flex; gap: 16px; }
754 .issue-tabs a { color: var(--text-muted); font-size: 14px; font-weight: 500; }
755 .issue-tabs a:hover { color: var(--text); text-decoration: none; }
756 .issue-tabs a.active { color: var(--text); }
757
758 .issue-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
759 .issue-item {
760 display: flex;
761 gap: 12px;
762 align-items: flex-start;
763 padding: 12px 16px;
764 border-bottom: 1px solid var(--border);
765 }
766 .issue-item:last-child { border-bottom: none; }
767 .issue-item:hover { background: var(--bg-secondary); }
768 .issue-state-icon { font-size: 16px; padding-top: 2px; }
769 .state-open { color: var(--green); }
770 .state-closed { color: #986ee2; }
771 .issue-title { font-size: 15px; font-weight: 600; }
772 .issue-title a { color: var(--text); }
773 .issue-title a:hover { color: var(--text-link); }
774 .issue-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
775
776 .issue-badge {
777 display: inline-flex;
778 align-items: center;
779 gap: 4px;
780 padding: 4px 12px;
781 border-radius: 20px;
782 font-size: 13px;
783 font-weight: 500;
784 }
785 .badge-open { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
786 .badge-closed { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
0074234Claude787 .badge-merged { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
788 .state-merged { color: #986ee2; }
789 .ai-review { border-color: var(--accent); }
79136bbClaude790
791 .issue-detail { max-width: 900px; }
792 .issue-comment-box {
793 border: 1px solid var(--border);
794 border-radius: var(--radius);
795 margin-bottom: 16px;
796 overflow: hidden;
797 }
798 .comment-header {
799 background: var(--bg-secondary);
800 padding: 8px 16px;
801 border-bottom: 1px solid var(--border);
802 font-size: 13px;
803 color: var(--text-muted);
804 }
805
806 /* Search */
807 .search-results .diff-file { margin-bottom: 12px; }
45e31d0Claude808 .search-input {
809 flex: 1;
810 padding: 8px 12px;
3ef4c9dClaude811 background: var(--bg);
812 border: 1px solid var(--border);
813 border-radius: var(--radius);
814 color: var(--text);
45e31d0Claude815 font-size: 14px;
3ef4c9dClaude816 }
45e31d0Claude817 .search-input:focus {
818 outline: none;
819 border-color: var(--accent);
820 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
3ef4c9dClaude821 }
822
45e31d0Claude823 /* Toast Notifications */
824 #toast-container {
825 position: fixed;
826 top: 16px;
827 right: 16px;
828 z-index: 9999;
3ef4c9dClaude829 display: flex;
45e31d0Claude830 flex-direction: column;
831 gap: 8px;
3ef4c9dClaude832 }
45e31d0Claude833 .toast {
834 padding: 10px 16px;
835 border-radius: var(--radius);
3ef4c9dClaude836 font-size: 14px;
45e31d0Claude837 font-weight: 500;
838 opacity: 0;
839 transform: translateX(100%);
840 transition: all 0.3s ease;
841 min-width: 200px;
842 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
843 }
844 .toast-visible { opacity: 1; transform: translateX(0); }
845 .toast-info { background: var(--accent); color: #fff; }
846 .toast-success { background: var(--green); color: #fff; }
847 .toast-error { background: var(--red); color: #fff; }
848 .toast-warning { background: var(--yellow); color: #000; }
849
850 /* Keyboard Shortcut Hints */
851 .kbd {
852 display: inline-block;
853 padding: 2px 6px;
854 border: 1px solid var(--border);
855 border-radius: 4px;
856 background: var(--bg-secondary);
857 font-family: var(--font-mono);
858 font-size: 12px;
859 line-height: 1.4;
3ef4c9dClaude860 color: var(--text-muted);
45e31d0Claude861 box-shadow: 0 1px 0 var(--border);
862 }
863 .shortcut-overlay {
864 position: fixed;
865 inset: 0;
866 background: rgba(0, 0, 0, 0.6);
867 z-index: 9998;
3ef4c9dClaude868 display: flex;
869 align-items: center;
45e31d0Claude870 justify-content: center;
3ef4c9dClaude871 }
45e31d0Claude872 .shortcut-modal {
873 background: var(--bg-secondary);
3ef4c9dClaude874 border: 1px solid var(--border);
875 border-radius: var(--radius);
45e31d0Claude876 padding: 24px;
877 min-width: 300px;
878 max-width: 480px;
3ef4c9dClaude879 }
45e31d0Claude880 .shortcut-modal h3 { margin-bottom: 16px; }
881 .shortcut-grid {
3ef4c9dClaude882 display: flex;
45e31d0Claude883 flex-direction: column;
884 gap: 8px;
885 margin-bottom: 16px;
886 font-size: 14px;
3ef4c9dClaude887 }
45e31d0Claude888 .shortcut-grid div {
889 display: flex;
890 align-items: center;
891 gap: 8px;
892 }
893
894 /* Comment Editor with Preview */
895 .comment-editor {
3ef4c9dClaude896 border: 1px solid var(--border);
897 border-radius: var(--radius);
45e31d0Claude898 overflow: hidden;
3ef4c9dClaude899 }
45e31d0Claude900 .editor-tabs {
901 display: flex;
902 border-bottom: 1px solid var(--border);
903 background: var(--bg-secondary);
3ef4c9dClaude904 }
45e31d0Claude905 .editor-tab {
906 padding: 6px 16px;
907 font-size: 13px;
908 background: none;
909 border: none;
3ef4c9dClaude910 color: var(--text-muted);
45e31d0Claude911 cursor: pointer;
912 border-bottom: 2px solid transparent;
3ef4c9dClaude913 }
45e31d0Claude914 .editor-tab:hover { color: var(--text); }
915 .editor-tab.active { color: var(--text); border-bottom-color: var(--accent); }
916 .comment-editor textarea {
3ef4c9dClaude917 width: 100%;
45e31d0Claude918 border: none;
919 padding: 12px;
3ef4c9dClaude920 background: var(--bg);
921 color: var(--text);
45e31d0Claude922 font-family: var(--font-mono);
923 font-size: 13px;
3ef4c9dClaude924 resize: vertical;
45e31d0Claude925 min-height: 120px;
3ef4c9dClaude926 }
45e31d0Claude927 .comment-editor textarea:focus { outline: none; }
928 .editor-preview {
929 min-height: 120px;
930 background: var(--bg);
3ef4c9dClaude931 }
932
45e31d0Claude933 /* Success Button */
934 .btn-success { background: var(--green); border-color: var(--green); color: #fff; }
935 .btn-success:hover { background: #2ea043; }
936 .btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
937 .btn-ghost:hover { color: var(--text); background: var(--bg-tertiary); }
938 .btn-lg { padding: 12px 24px; font-size: 16px; }
939
940 /* Tab count badge */
941 .tab-count {
942 display: inline-block;
943 padding: 0 6px;
944 margin-left: 4px;
3ef4c9dClaude945 font-size: 12px;
946 background: var(--bg-tertiary);
947 border-radius: 10px;
948 }
949
45e31d0Claude950 /* Copy block */
951 .copy-block {
952 margin: 8px 0;
3ef4c9dClaude953 }
954
45e31d0Claude955 /* Progress bar */
956 .progress-bar {
957 width: 100%;
958 height: 8px;
959 background: var(--bg-tertiary);
960 border-radius: 4px;
961 overflow: hidden;
6fc53bdClaude962 }
45e31d0Claude963 .progress-fill {
964 height: 100%;
965 background: var(--accent);
966 border-radius: 4px;
967 transition: width 0.3s ease;
6fc53bdClaude968 }
45e31d0Claude969
970 /* Spinner */
971 .spinner {
972 border: 2px solid var(--border);
973 border-top: 2px solid var(--accent);
974 border-radius: 50%;
975 animation: spin 0.8s linear infinite;
6fc53bdClaude976 }
45e31d0Claude977 @keyframes spin { to { transform: rotate(360deg); } }
978
979 /* Step indicator (onboarding) */
980 .step-indicator { margin-bottom: 32px; }
981 .step-circle {
982 width: 32px;
983 height: 32px;
984 border-radius: 50%;
985 display: flex;
986 align-items: center;
987 justify-content: center;
988 font-size: 14px;
989 font-weight: 600;
6fc53bdClaude990 background: var(--bg-tertiary);
45e31d0Claude991 border: 2px solid var(--border);
6fc53bdClaude992 color: var(--text-muted);
993 }
45e31d0Claude994 .step-completed { background: var(--green); border-color: var(--green); color: #fff; }
995 .step-active { border-color: var(--accent); color: var(--accent); }
996 .step-line {
997 flex: 1;
998 height: 2px;
999 background: var(--border);
1000 min-width: 40px;
6fc53bdClaude1001 }
45e31d0Claude1002 .step-line[data-completed="true"] { background: var(--green); }
6fc53bdClaude1003
45e31d0Claude1004 /* Welcome hero */
1005 .welcome-hero {
1006 text-align: center;
1007 padding: 60px 20px 40px;
1008 max-width: 700px;
1009 margin: 0 auto;
6fc53bdClaude1010 }
45e31d0Claude1011 .welcome-hero h1 { font-size: 36px; margin-bottom: 12px; }
1012 .hero-subtitle { font-size: 18px; color: var(--text-muted); margin-bottom: 32px; }
1013
1014 /* Feature cards */
1015 .feature-card {
1016 text-align: center;
1017 padding: 24px;
1018 transition: border-color 0.2s, transform 0.2s;
1019 }
1020 .feature-card:hover { border-color: var(--accent); transform: translateY(-2px); }
1021 .feature-icon { font-size: 36px; margin-bottom: 12px; }
1022 .feature-card h3 { font-size: 16px; margin-bottom: 8px; }
1023
1024 /* Tooltip */
1025 .tooltip-wrapper { position: relative; }
1026 .tooltip-wrapper:hover::after {
1027 content: attr(data-tooltip);
1028 position: absolute;
1029 bottom: 100%;
1030 left: 50%;
1031 transform: translateX(-50%);
1032 padding: 4px 8px;
6fc53bdClaude1033 background: var(--bg-tertiary);
1034 border: 1px solid var(--border);
45e31d0Claude1035 border-radius: 4px;
6fc53bdClaude1036 font-size: 12px;
45e31d0Claude1037 white-space: nowrap;
1038 z-index: 100;
1039 margin-bottom: 4px;
6fc53bdClaude1040 }
45e31d0Claude1041
1042 /* Notification bell */
1043 .notification-bell {
1044 position: relative;
6fc53bdClaude1045 display: inline-flex;
1046 align-items: center;
45e31d0Claude1047 color: var(--text-muted);
1048 padding: 4px;
6fc53bdClaude1049 }
45e31d0Claude1050 .notification-bell:hover { color: var(--text); text-decoration: none; }
1051 .notification-count {
1052 position: absolute;
1053 top: -4px;
1054 right: -6px;
1055 background: var(--accent);
1056 color: #fff;
1057 font-size: 10px;
1058 font-weight: 700;
1059 padding: 1px 5px;
1060 border-radius: 10px;
1061 min-width: 16px;
1062 text-align: center;
24cf2caClaude1063 }
1064
45e31d0Claude1065 /* Alert variants */
1066 .alert-warning {
1067 background: rgba(210, 153, 34, 0.1);
1068 border: 1px solid var(--yellow);
1069 color: var(--yellow);
1070 padding: 8px 12px;
1071 border-radius: var(--radius);
1072 margin-bottom: 16px;
1073 font-size: 14px;
6fc53bdClaude1074 }
45e31d0Claude1075 .alert-info {
1076 background: rgba(88, 166, 255, 0.1);
1077 border: 1px solid var(--text-link);
1078 color: var(--text-link);
1079 padding: 8px 12px;
1080 border-radius: var(--radius);
1081 margin-bottom: 16px;
1082 font-size: 14px;
3ef4c9dClaude1083 }
1084
45e31d0Claude1085 /* Badge variants */
1086 .badge-success { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
1087 .badge-danger { background: rgba(248, 81, 73, 0.1); color: var(--red); border: 1px solid var(--red); }
1088 .badge-warning { background: rgba(210, 153, 34, 0.1); color: var(--yellow); border: 1px solid var(--yellow); }
1089
1090 /* Mobile responsiveness */
3ef4c9dClaude1091 @media (max-width: 768px) {
1092 main { padding: 16px; }
1093 .card-grid { grid-template-columns: 1fr; }
1094 .user-profile { flex-direction: column; gap: 16px; }
1095 .repo-header { flex-wrap: wrap; }
45e31d0Claude1096 .repo-header-actions { margin-left: 0; }
3ef4c9dClaude1097 .repo-nav { overflow-x: auto; }
45e31d0Claude1098 .blob-code { font-size: 12px; }
1099 .diff-content { font-size: 12px; }
1100 .hamburger-btn {
1101 display: inline-flex;
1102 align-items: center;
1103 justify-content: center;
1104 width: 36px;
1105 height: 36px;
1106 background: none;
1107 border: 1px solid var(--border);
1108 border-radius: var(--radius);
1109 color: var(--text);
1110 font-size: 18px;
1111 cursor: pointer;
1112 }
1113 .mobile-hidden { display: none !important; }
1114 .mobile-visible {
1115 display: flex !important;
1116 position: absolute;
1117 top: 100%;
1118 right: 0;
1119 background: var(--bg-secondary);
1120 border: 1px solid var(--border);
1121 border-radius: var(--radius);
1122 padding: 8px;
1123 flex-direction: column;
1124 gap: 4px;
1125 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
1126 min-width: 200px;
1127 }
1128 .mobile-visible a, .mobile-visible button {
1129 padding: 8px 12px;
1130 display: block;
1131 width: 100%;
1132 text-align: left;
1133 }
1134 .issue-item { flex-wrap: wrap; }
1135 .commit-item { flex-direction: column; gap: 8px; }
1136 .settings-container { max-width: 100%; }
1137 .auth-container { max-width: 100%; }
3ef4c9dClaude1138 }
45e31d0Claude1139
1140 /* Focus visible for keyboard nav */
1141 :focus-visible {
1142 outline: 2px solid var(--accent);
1143 outline-offset: 2px;
1144 }
1145
1146 /* Skip to main content (accessibility) */
1147 .skip-link {
1148 position: absolute;
1149 top: -100px;
1150 left: 0;
1151 background: var(--accent);
1152 color: #fff;
1153 padding: 8px 16px;
1154 z-index: 9999;
1155 font-size: 14px;
1156 }
1157 .skip-link:focus { top: 0; }
1158
1159 /* Markdown body spacing */
1160 .markdown-body { padding: 16px; }
1161 .markdown-body h1, .markdown-body h2, .markdown-body h3 { margin-top: 1.5em; margin-bottom: 0.5em; }
1162 .markdown-body p { margin-bottom: 1em; }
1163 .markdown-body pre { margin: 1em 0; }
1164 .markdown-body code { font-size: 85%; }
1165 .markdown-body ul, .markdown-body ol { padding-left: 2em; margin-bottom: 1em; }
fc1817aClaude1166`;