Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
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.tsxBlame897 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>
40e7738Claude61 <a href="/ai/live" class="nav-link" title="Live AI activity">
62 AI&nbsp;Live
63 </a>
06d5ffeClaude64 {user ? (
65 <>
f1ab587Claude66 <a href="/dashboard" class="nav-link" style="font-weight: 600">
67 Dashboard
68 </a>
bdbd0deClaude69 <a href="/import" class="nav-link">
70 Import
71 </a>
06d5ffeClaude72 <a href="/new" class="btn btn-sm btn-primary">
73 + New
74 </a>
75 <a href={`/${user.username}`} class="nav-user">
76 {user.displayName || user.username}
77 </a>
78 <a href="/settings" class="nav-link">
79 Settings
80 </a>
81 <a href="/logout" class="nav-link">
82 Sign out
83 </a>
84 </>
85 ) : (
86 <>
87 <a href="/login" class="nav-link">
88 Sign in
89 </a>
90 <a href="/register" class="btn btn-sm btn-primary">
91 Register
92 </a>
93 </>
94 )}
95 </div>
fc1817aClaude96 </nav>
97 </header>
45e31d0Claude98 <main id="main-content">{children}</main>
fc1817aClaude99 <footer>
36b4cbdClaude100 <span>
101 &copy; {new Date().getFullYear()} gluecron — AI-native code intelligence
102 </span>
103 <div style="margin-top: 6px; display: flex; gap: 16px; justify-content: center; font-size: 12px">
104 <a href="/terms" style="color: var(--text-muted)">Terms</a>
105 <a href="/privacy" style="color: var(--text-muted)">Privacy</a>
106 <a href="/acceptable-use" style="color: var(--text-muted)">Acceptable Use</a>
107 </div>
fc1817aClaude108 </footer>
699e5c7Claude109 {/* Block I4 — Command palette shell (hidden by default) */}
110 <div
111 id="cmdk-backdrop"
112 style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9998"
113 />
114 <div
115 id="cmdk-panel"
116 style="display:none;position:fixed;top:10%;left:50%;transform:translateX(-50%);width:min(560px,92vw);background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 12px 32px rgba(0,0,0,0.4);z-index:9999;overflow:hidden"
117 >
118 <input
119 id="cmdk-input"
120 type="text"
121 placeholder="Type a command..."
122 aria-label="Command palette"
123 style="width:100%;padding:12px 16px;background:transparent;color:var(--text);border:0;border-bottom:1px solid var(--border);outline:none;font-size:14px"
124 />
125 <div id="cmdk-list" style="max-height:60vh;overflow-y:auto" />
126 </div>
45e31d0Claude127 <script>{clientJs}</script>
699e5c7Claude128 <script>{pwaRegisterScript}</script>
129 <script>{navScript}</script>
fc1817aClaude130 </body>
131 </html>
132 );
133};
134
6fc53bdClaude135// Runs before paint — reads the theme cookie and flips data-theme so there's
136// no dark-to-light flash on load. SSR default is dark.
137const themeInitScript = `
138 (function(){
139 try {
140 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
141 var t = m ? decodeURIComponent(m[1]) : 'dark';
142 if (t !== 'light' && t !== 'dark') t = 'dark';
143 document.documentElement.setAttribute('data-theme', t);
144 } catch(_){}
145 })();
146`;
147
eae38d1Claude148// Block G1 — register service worker for offline / install support.
149// Kept inline (and tiny) so we don't block first paint.
150const pwaRegisterScript = `
151 if ('serviceWorker' in navigator) {
152 window.addEventListener('load', function(){
153 navigator.serviceWorker.register('/sw.js').catch(function(){});
154 });
155 }
156`;
157
3ef4c9dClaude158const navScript = `
159 (function(){
160 var chord = null;
161 var chordTimer = null;
162 function isTyping(t){
163 t = t || {};
164 var tag = (t.tagName || '').toLowerCase();
165 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
166 }
71cd5ecClaude167
168 // ---------- Block I4 — Command palette ----------
169 var COMMANDS = [
170 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
171 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
172 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
173 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
174 { label: 'Create new repository', href: '/new', kw: 'add create' },
175 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
176 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
177 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
178 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
179 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
180 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
181 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
182 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
183 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
184 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
185 { label: 'Gists', href: '/gists', kw: 'snippets' },
186 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
187 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
188 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
189 ];
190
191 function fuzzyMatch(item, q){
192 if (!q) return true;
193 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
194 q = q.toLowerCase();
195 var qi = 0;
196 for (var i = 0; i < hay.length && qi < q.length; i++) {
197 if (hay[i] === q[qi]) qi++;
198 }
199 return qi === q.length;
200 }
201
202 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
203
204 function render(){
205 if (!list) return;
206 var html = '';
207 for (var i = 0; i < filtered.length; i++) {
208 var item = filtered[i];
209 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
210 var bg = i === selected ? 'background:var(--bg);' : '';
211 html += '<div class="' + cls + '" data-idx="' + i + '" data-href="' + item.href + '"' +
212 ' style="padding:10px 16px;cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
213 '<div>' + item.label + '</div>' +
214 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
215 '</div>';
216 }
217 if (filtered.length === 0) {
218 html = '<div style="padding:16px;color:var(--text-muted);text-align:center">No matches.</div>';
219 }
220 list.innerHTML = html;
221 }
222
223 function openPalette(){
224 backdrop = document.getElementById('cmdk-backdrop');
225 panel = document.getElementById('cmdk-panel');
226 input = document.getElementById('cmdk-input');
227 list = document.getElementById('cmdk-list');
228 if (!backdrop || !panel) return;
229 backdrop.style.display = 'block';
230 panel.style.display = 'block';
231 input.value = '';
232 selected = 0;
233 filtered = COMMANDS.slice();
234 render();
235 input.focus();
236 }
237 function closePalette(){
238 if (backdrop) backdrop.style.display = 'none';
239 if (panel) panel.style.display = 'none';
240 }
241 function go(href){ closePalette(); window.location.href = href; }
242
243 document.addEventListener('click', function(e){
244 var t = e.target;
245 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
246 var item = t && t.closest && t.closest('.cmdk-item');
247 if (item) { go(item.getAttribute('data-href')); }
248 });
249
250 document.addEventListener('input', function(e){
251 if (e.target && e.target.id === 'cmdk-input') {
252 var q = e.target.value;
253 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
254 selected = 0;
255 render();
256 }
257 });
258
3ef4c9dClaude259 document.addEventListener('keydown', function(e){
71cd5ecClaude260 // Palette-scoped keys take priority when open
261 if (panel && panel.style.display === 'block') {
262 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
263 if (e.key === 'ArrowDown') {
264 e.preventDefault();
265 selected = Math.min(filtered.length - 1, selected + 1);
266 render();
267 return;
268 }
269 if (e.key === 'ArrowUp') {
270 e.preventDefault();
271 selected = Math.max(0, selected - 1);
272 render();
273 return;
274 }
275 if (e.key === 'Enter') {
276 e.preventDefault();
277 var item = filtered[selected];
278 if (item) go(item.href);
279 return;
280 }
281 return;
282 }
283
3ef4c9dClaude284 if (isTyping(e.target)) return;
285 // Single key shortcuts
286 if (e.key === '/') {
287 var el = document.querySelector('.nav-search input');
288 if (el) { e.preventDefault(); el.focus(); return; }
289 }
290 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude291 e.preventDefault();
292 openPalette();
293 return;
3ef4c9dClaude294 }
295 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
296 e.preventDefault(); window.location.href = '/shortcuts'; return;
297 }
298 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
299 e.preventDefault(); window.location.href = '/new'; return;
300 }
301 // "g" chord
302 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
303 chord = 'g';
304 clearTimeout(chordTimer);
305 chordTimer = setTimeout(function(){ chord = null; }, 1200);
306 return;
307 }
308 if (chord === 'g') {
309 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
310 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
311 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
312 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
313 chord = null;
314 }
315 });
316 })();
317`;
318
fc1817aClaude319const css = `
6fc53bdClaude320 :root, :root[data-theme='dark'] {
fc1817aClaude321 --bg: #0d1117;
322 --bg-secondary: #161b22;
323 --bg-tertiary: #21262d;
324 --border: #30363d;
325 --text: #e6edf3;
326 --text-muted: #8b949e;
327 --text-link: #58a6ff;
328 --accent: #1f6feb;
329 --accent-hover: #388bfd;
330 --green: #3fb950;
331 --red: #f85149;
332 --yellow: #d29922;
333 --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
334 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
335 --radius: 6px;
336 }
337
6fc53bdClaude338 :root[data-theme='light'] {
339 --bg: #ffffff;
340 --bg-secondary: #f6f8fa;
341 --bg-tertiary: #eaeef2;
342 --border: #d0d7de;
343 --text: #1f2328;
344 --text-muted: #656d76;
345 --text-link: #0969da;
346 --accent: #0969da;
347 --accent-hover: #0550ae;
348 --green: #1a7f37;
349 --red: #cf222e;
350 --yellow: #9a6700;
351 }
352
353 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
354 .nav-theme { display: inline-flex; align-items: center; font-size: 16px; line-height: 1; }
355 :root[data-theme='dark'] .theme-icon-dark { display: none; }
356 :root[data-theme='light'] .theme-icon-light { display: none; }
357
fc1817aClaude358 * { margin: 0; padding: 0; box-sizing: border-box; }
359
360 body {
361 font-family: var(--font-sans);
362 background: var(--bg);
363 color: var(--text);
364 line-height: 1.5;
365 min-height: 100vh;
366 display: flex;
367 flex-direction: column;
368 }
369
370 a { color: var(--text-link); text-decoration: none; }
371 a:hover { text-decoration: underline; }
372
4a52a98Claude373 /* Pre-launch banner - always visible, not dismissible. Amber tone, readable
374 on both light and dark themes via the shared --yellow token. */
375 .prelaunch-banner {
376 background: rgba(210, 153, 34, 0.15);
377 border-bottom: 1px solid var(--yellow);
378 color: var(--yellow);
379 padding: 8px 24px;
380 font-size: 13px;
381 font-weight: 500;
382 text-align: center;
383 line-height: 1.4;
384 }
385
fc1817aClaude386 header {
387 border-bottom: 1px solid var(--border);
388 padding: 12px 24px;
389 background: var(--bg-secondary);
390 }
391
06d5ffeClaude392 header nav {
393 display: flex;
394 align-items: center;
395 justify-content: space-between;
396 max-width: 1200px;
397 margin: 0 auto;
398 }
fc1817aClaude399 .logo { font-size: 20px; font-weight: 700; color: var(--text); }
400 .logo:hover { text-decoration: none; color: var(--text-link); }
401
06d5ffeClaude402 .nav-right { display: flex; align-items: center; gap: 16px; }
403 .nav-link { color: var(--text-muted); font-size: 14px; }
404 .nav-link:hover { color: var(--text); text-decoration: none; }
405 .nav-user { color: var(--text); font-weight: 600; font-size: 14px; }
406 .nav-user:hover { color: var(--text-link); text-decoration: none; }
407
fc1817aClaude408 main { max-width: 1200px; margin: 0 auto; padding: 24px; flex: 1; width: 100%; }
409
410 footer {
411 border-top: 1px solid var(--border);
412 padding: 16px 24px;
413 text-align: center;
414 color: var(--text-muted);
415 font-size: 13px;
416 }
417
06d5ffeClaude418 /* Buttons */
419 .btn {
420 display: inline-flex;
421 align-items: center;
422 gap: 6px;
423 padding: 8px 16px;
424 border-radius: var(--radius);
425 font-size: 14px;
426 font-weight: 500;
427 border: 1px solid var(--border);
428 background: var(--bg-tertiary);
429 color: var(--text);
430 cursor: pointer;
431 text-decoration: none;
432 line-height: 1.4;
433 }
434 .btn:hover { background: var(--border); text-decoration: none; }
435 .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
436 .btn-primary:hover { background: var(--accent-hover); }
437 .btn-danger { background: transparent; border-color: var(--red); color: var(--red); }
438 .btn-danger:hover { background: rgba(248, 81, 73, 0.15); }
439 .btn-sm { padding: 4px 12px; font-size: 13px; }
440
441 /* Forms */
442 .form-group { margin-bottom: 16px; }
443 .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; color: var(--text); }
444 .form-group input, .form-group textarea, .form-group select {
445 width: 100%;
446 padding: 8px 12px;
447 background: var(--bg);
448 border: 1px solid var(--border);
449 border-radius: var(--radius);
450 color: var(--text);
451 font-size: 14px;
452 font-family: var(--font-sans);
453 }
454 .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
455 outline: none;
456 border-color: var(--accent);
457 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
458 }
459 .input-disabled { opacity: 0.5; cursor: not-allowed; }
460
461 /* Auth */
462 .auth-container { max-width: 400px; margin: 40px auto; }
463 .auth-container h2 { margin-bottom: 20px; font-size: 24px; }
464 .auth-error {
465 background: rgba(248, 81, 73, 0.1);
466 border: 1px solid var(--red);
467 color: var(--red);
468 padding: 8px 12px;
469 border-radius: var(--radius);
470 margin-bottom: 16px;
471 font-size: 14px;
472 }
473 .auth-success {
474 background: rgba(63, 185, 80, 0.1);
475 border: 1px solid var(--green);
476 color: var(--green);
477 padding: 8px 12px;
478 border-radius: var(--radius);
479 margin-bottom: 16px;
480 font-size: 14px;
481 }
482 .auth-switch { margin-top: 16px; font-size: 14px; color: var(--text-muted); }
483
484 /* Settings */
485 .settings-container { max-width: 600px; }
486 .settings-container h2 { margin-bottom: 20px; font-size: 24px; }
487 .settings-container h3 { font-size: 18px; margin-bottom: 12px; }
488 .ssh-keys-list { margin-bottom: 24px; }
489 .ssh-key-item {
490 display: flex;
491 justify-content: space-between;
492 align-items: center;
493 padding: 12px 16px;
494 border: 1px solid var(--border);
495 border-radius: var(--radius);
496 margin-bottom: 8px;
497 background: var(--bg-secondary);
498 }
499 .ssh-key-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
500 .ssh-key-meta code { font-size: 11px; background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
501
502 /* Repo header */
fc1817aClaude503 .repo-header {
504 display: flex;
505 align-items: center;
506 gap: 8px;
507 margin-bottom: 16px;
508 font-size: 20px;
509 }
510 .repo-header .owner { color: var(--text-link); }
511 .repo-header .separator { color: var(--text-muted); }
512 .repo-header .name { color: var(--text-link); font-weight: 600; }
06d5ffeClaude513 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude514
515 .repo-nav {
516 display: flex;
517 gap: 0;
518 border-bottom: 1px solid var(--border);
519 margin-bottom: 20px;
520 }
521 .repo-nav a {
522 padding: 8px 16px;
523 color: var(--text-muted);
524 border-bottom: 2px solid transparent;
525 font-size: 14px;
526 }
527 .repo-nav a:hover { text-decoration: none; color: var(--text); }
528 .repo-nav a.active { color: var(--text); border-bottom-color: var(--accent); }
529
530 .breadcrumb { display: flex; gap: 4px; align-items: center; margin-bottom: 16px; color: var(--text-muted); font-size: 14px; }
531 .breadcrumb a { color: var(--text-link); }
532
533 .file-table { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
534 .file-table tr { border-bottom: 1px solid var(--border); }
535 .file-table tr:last-child { border-bottom: none; }
536 .file-table td { padding: 8px 16px; font-size: 14px; }
537 .file-table tr:hover { background: var(--bg-secondary); }
538 .file-icon { width: 20px; color: var(--text-muted); }
539 .file-name a { color: var(--text); }
540 .file-name a:hover { color: var(--text-link); text-decoration: underline; }
541
542 .blob-view {
543 border: 1px solid var(--border);
544 border-radius: var(--radius);
545 overflow: hidden;
546 }
547 .blob-header {
548 background: var(--bg-secondary);
549 padding: 8px 16px;
550 border-bottom: 1px solid var(--border);
551 font-size: 13px;
552 color: var(--text-muted);
06d5ffeClaude553 display: flex;
554 justify-content: space-between;
555 align-items: center;
fc1817aClaude556 }
557 .blob-code {
558 overflow-x: auto;
559 font-family: var(--font-mono);
560 font-size: 13px;
561 line-height: 1.6;
562 }
563 .blob-code table { width: 100%; border-collapse: collapse; }
564 .blob-code .line-num {
565 width: 1%;
566 min-width: 50px;
567 padding: 0 12px;
568 text-align: right;
569 color: var(--text-muted);
570 user-select: none;
571 white-space: nowrap;
572 border-right: 1px solid var(--border);
573 }
574 .blob-code .line-content { padding: 0 12px; white-space: pre; }
575
576 .commit-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
577 .commit-item {
578 display: flex;
579 justify-content: space-between;
580 align-items: center;
581 padding: 12px 16px;
582 border-bottom: 1px solid var(--border);
583 }
584 .commit-item:last-child { border-bottom: none; }
585 .commit-item:hover { background: var(--bg-secondary); }
586 .commit-message { font-size: 14px; font-weight: 500; }
587 .commit-meta { font-size: 12px; color: var(--text-muted); }
588 .commit-sha {
589 font-family: var(--font-mono);
590 font-size: 12px;
591 padding: 2px 8px;
592 background: var(--bg-tertiary);
593 border: 1px solid var(--border);
594 border-radius: var(--radius);
595 color: var(--text-link);
596 }
597
598 .diff-view { margin-top: 16px; }
599 .diff-file {
600 border: 1px solid var(--border);
601 border-radius: var(--radius);
602 margin-bottom: 16px;
603 overflow: hidden;
604 }
605 .diff-file-header {
606 background: var(--bg-secondary);
607 padding: 8px 16px;
608 border-bottom: 1px solid var(--border);
609 font-family: var(--font-mono);
610 font-size: 13px;
611 }
612 .diff-content {
613 overflow-x: auto;
614 font-family: var(--font-mono);
615 font-size: 13px;
616 line-height: 1.6;
617 }
618 .diff-content .line-add { background: rgba(63, 185, 80, 0.15); color: var(--green); }
619 .diff-content .line-del { background: rgba(248, 81, 73, 0.1); color: var(--red); }
620 .diff-content .line-hunk { background: rgba(56, 139, 253, 0.1); color: var(--text-link); }
621 .diff-content .line { padding: 0 12px; white-space: pre; display: block; }
622
623 .stat-add { color: var(--green); font-weight: 600; }
624 .stat-del { color: var(--red); font-weight: 600; }
625
626 .empty-state {
627 text-align: center;
628 padding: 60px 20px;
629 color: var(--text-muted);
630 }
631 .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
632 .empty-state pre {
633 text-align: left;
634 display: inline-block;
635 background: var(--bg-secondary);
636 padding: 16px 24px;
637 border-radius: var(--radius);
638 border: 1px solid var(--border);
639 font-family: var(--font-mono);
640 font-size: 13px;
641 margin-top: 16px;
642 line-height: 1.8;
643 }
644
645 .badge {
646 display: inline-block;
647 padding: 2px 8px;
648 border-radius: 12px;
649 font-size: 12px;
650 font-weight: 500;
651 background: var(--bg-tertiary);
652 border: 1px solid var(--border);
653 color: var(--text-muted);
654 }
655
656 .branch-selector {
657 display: inline-flex;
658 align-items: center;
659 gap: 4px;
660 padding: 4px 12px;
661 background: var(--bg-tertiary);
662 border: 1px solid var(--border);
663 border-radius: var(--radius);
664 font-size: 13px;
665 color: var(--text);
666 margin-bottom: 12px;
06d5ffeClaude667 position: relative;
fc1817aClaude668 }
669
06d5ffeClaude670 .branch-dropdown {
671 position: relative;
672 display: inline-block;
673 margin-bottom: 12px;
674 }
675 .branch-dropdown-content {
676 display: none;
677 position: absolute;
678 top: 100%;
679 left: 0;
680 z-index: 10;
681 min-width: 200px;
682 background: var(--bg-secondary);
683 border: 1px solid var(--border);
684 border-radius: var(--radius);
685 margin-top: 4px;
686 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
687 }
688 .branch-dropdown:hover .branch-dropdown-content,
689 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
690 .branch-dropdown-content a {
691 display: block;
692 padding: 8px 12px;
693 font-size: 13px;
694 color: var(--text);
695 border-bottom: 1px solid var(--border);
696 }
697 .branch-dropdown-content a:last-child { border-bottom: none; }
698 .branch-dropdown-content a:hover { background: var(--bg-tertiary); text-decoration: none; }
699 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; }
700
fc1817aClaude701 .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
702 .card {
703 border: 1px solid var(--border);
704 border-radius: var(--radius);
705 padding: 16px;
706 background: var(--bg-secondary);
06d5ffeClaude707 transition: border-color 0.15s;
fc1817aClaude708 }
06d5ffeClaude709 .card:hover { border-color: var(--text-muted); }
fc1817aClaude710 .card h3 { font-size: 16px; margin-bottom: 4px; }
06d5ffeClaude711 .card h3 a { color: var(--text-link); }
fc1817aClaude712 .card p { font-size: 13px; color: var(--text-muted); }
06d5ffeClaude713 .card-meta { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
714 .card-meta span { display: flex; align-items: center; gap: 4px; }
715
716 .star-btn {
717 display: inline-flex;
718 align-items: center;
719 gap: 6px;
720 padding: 4px 12px;
721 background: var(--bg-tertiary);
722 border: 1px solid var(--border);
723 border-radius: var(--radius);
724 color: var(--text);
725 font-size: 13px;
726 cursor: pointer;
727 }
728 .star-btn:hover { background: var(--border); text-decoration: none; }
729 .star-btn.starred { color: var(--yellow); border-color: var(--yellow); }
730
731 .user-profile {
732 display: flex;
733 gap: 32px;
734 margin-bottom: 32px;
735 }
736 .user-avatar {
737 width: 96px;
738 height: 96px;
739 border-radius: 50%;
740 background: var(--bg-tertiary);
741 border: 1px solid var(--border);
742 display: flex;
743 align-items: center;
744 justify-content: center;
745 font-size: 40px;
746 color: var(--text-muted);
747 flex-shrink: 0;
748 }
749 .user-info h2 { font-size: 24px; margin-bottom: 2px; }
750 .user-info .username { font-size: 16px; color: var(--text-muted); }
751 .user-info .bio { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
752
753 .new-repo-form { max-width: 600px; }
754 .new-repo-form h2 { margin-bottom: 20px; }
755 .visibility-options { display: flex; gap: 12px; margin-bottom: 16px; }
756 .visibility-option {
757 flex: 1;
758 padding: 12px;
759 border: 1px solid var(--border);
760 border-radius: var(--radius);
761 background: var(--bg-secondary);
762 cursor: pointer;
763 text-align: center;
764 }
765 .visibility-option:has(input:checked) { border-color: var(--accent); background: rgba(31, 111, 235, 0.1); }
766 .visibility-option input { display: none; }
767 .visibility-option .vis-label { font-size: 14px; font-weight: 500; }
768 .visibility-option .vis-desc { font-size: 12px; color: var(--text-muted); }
79136bbClaude769
770 /* Issues */
771 .issue-tabs { display: flex; gap: 16px; }
772 .issue-tabs a { color: var(--text-muted); font-size: 14px; font-weight: 500; }
773 .issue-tabs a:hover { color: var(--text); text-decoration: none; }
774 .issue-tabs a.active { color: var(--text); }
775
776 .issue-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
777 .issue-item {
778 display: flex;
779 gap: 12px;
780 align-items: flex-start;
781 padding: 12px 16px;
782 border-bottom: 1px solid var(--border);
783 }
784 .issue-item:last-child { border-bottom: none; }
785 .issue-item:hover { background: var(--bg-secondary); }
786 .issue-state-icon { font-size: 16px; padding-top: 2px; }
787 .state-open { color: var(--green); }
788 .state-closed { color: #986ee2; }
789 .issue-title { font-size: 15px; font-weight: 600; }
790 .issue-title a { color: var(--text); }
791 .issue-title a:hover { color: var(--text-link); }
792 .issue-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
793
794 .issue-badge {
795 display: inline-flex;
796 align-items: center;
797 gap: 4px;
798 padding: 4px 12px;
799 border-radius: 20px;
800 font-size: 13px;
801 font-weight: 500;
802 }
803 .badge-open { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
804 .badge-closed { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
0074234Claude805 .badge-merged { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
806 .state-merged { color: #986ee2; }
807 .ai-review { border-color: var(--accent); }
79136bbClaude808
809 .issue-detail { max-width: 900px; }
810 .issue-comment-box {
811 border: 1px solid var(--border);
812 border-radius: var(--radius);
813 margin-bottom: 16px;
814 overflow: hidden;
815 }
816 .comment-header {
817 background: var(--bg-secondary);
818 padding: 8px 16px;
819 border-bottom: 1px solid var(--border);
820 font-size: 13px;
821 color: var(--text-muted);
822 }
823
824 /* Search */
825 .search-results .diff-file { margin-bottom: 12px; }
16b325cClaude826
827 /* Timeline */
828 .timeline { position: relative; padding-left: 24px; }
829 .timeline::before {
830 content: '';
831 position: absolute;
832 left: 4px;
833 top: 8px;
834 bottom: 8px;
835 width: 2px;
836 background: var(--border);
837 }
838 .timeline-item {
839 position: relative;
840 padding-bottom: 16px;
841 }
842 .timeline-dot {
843 position: absolute;
844 left: -24px;
845 top: 6px;
846 width: 10px;
847 height: 10px;
848 border-radius: 50%;
849 background: var(--text-muted);
850 border: 2px solid var(--bg);
851 }
852 .timeline-content {
853 background: var(--bg-secondary);
854 border: 1px solid var(--border);
855 border-radius: var(--radius);
856 padding: 12px 16px;
857 }
f1ab587Claude858
859 /* Toggle switch */
860 .toggle-switch {
861 position: relative;
862 display: inline-block;
863 width: 44px;
864 height: 24px;
865 flex-shrink: 0;
866 margin-left: 16px;
867 }
868 .toggle-switch input { opacity: 0; width: 0; height: 0; }
869 .toggle-slider {
870 position: absolute;
871 cursor: pointer;
872 top: 0; left: 0; right: 0; bottom: 0;
873 background: var(--bg-tertiary);
874 border: 1px solid var(--border);
875 border-radius: 12px;
876 transition: 0.2s;
877 }
878 .toggle-slider::before {
879 content: '';
880 position: absolute;
881 height: 18px;
882 width: 18px;
883 left: 2px;
884 bottom: 2px;
885 background: var(--text-muted);
886 border-radius: 50%;
887 transition: 0.2s;
888 }
889 .toggle-switch input:checked + .toggle-slider {
890 background: var(--accent);
891 border-color: var(--accent);
892 }
893 .toggle-switch input:checked + .toggle-slider::before {
894 transform: translateX(20px);
895 background: #fff;
896 }
fc1817aClaude897`;