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