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