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.tsxBlame950 lines · 2 contributors
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" />
4c47454Claude23 <link rel="preconnect" href="https://fonts.googleapis.com" />
24 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
25 <link
26 rel="stylesheet"
27 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
28 />
fc1817aClaude29 <title>{title ? `${title} — gluecron` : "gluecron"}</title>
6fc53bdClaude30 <script>{themeInitScript}</script>
fc1817aClaude31 <style>{css}</style>
06d5ffeClaude32 <style>{hljsThemeCss}</style>
fc1817aClaude33 </head>
34 <body>
4a52a98Claude35 <div class="prelaunch-banner" role="status" aria-live="polite">
36 Pre-launch &mdash; Gluecron is in final validation. Public signups
37 and git hosting for non-owner users open after launch review.
38 </div>
fc1817aClaude39 <header>
40 <nav>
41 <a href="/" class="logo">
42 gluecron
43 </a>
3ef4c9dClaude44 <div class="nav-search">
001af43Claude45 <form method="get" action="/search">
3ef4c9dClaude46 <input
47 type="search"
48 name="q"
49 placeholder="Search (press /)"
50 aria-label="Search"
51 />
52 </form>
53 </div>
06d5ffeClaude54 <div class="nav-right">
6fc53bdClaude55 <a
56 href="/theme/toggle"
57 class="nav-link nav-theme"
58 title="Toggle theme"
59 aria-label="Toggle theme"
60 >
61 <span class="theme-icon-dark">{"\u263E"}</span>
62 <span class="theme-icon-light">{"\u2600"}</span>
63 </a>
c81ab7aClaude64 <a href="/explore" class="nav-link">
65 Explore
66 </a>
06d5ffeClaude67 {user ? (
68 <>
f1ab587Claude69 <a href="/dashboard" class="nav-link" style="font-weight: 600">
70 Dashboard
71 </a>
bdbd0deClaude72 <a href="/import" class="nav-link">
73 Import
74 </a>
06d5ffeClaude75 <a href="/new" class="btn btn-sm btn-primary">
76 + New
77 </a>
78 <a href={`/${user.username}`} class="nav-user">
79 {user.displayName || user.username}
80 </a>
81 <a href="/settings" class="nav-link">
82 Settings
83 </a>
84 <a href="/logout" class="nav-link">
85 Sign out
86 </a>
87 </>
88 ) : (
89 <>
90 <a href="/login" class="nav-link">
91 Sign in
92 </a>
93 <a href="/register" class="btn btn-sm btn-primary">
94 Register
95 </a>
96 </>
97 )}
98 </div>
fc1817aClaude99 </nav>
100 </header>
45e31d0Claude101 <main id="main-content">{children}</main>
fc1817aClaude102 <footer>
36b4cbdClaude103 <span>
104 &copy; {new Date().getFullYear()} gluecron — AI-native code intelligence
105 </span>
106 <div style="margin-top: 6px; display: flex; gap: 16px; justify-content: center; font-size: 12px">
107 <a href="/terms" style="color: var(--text-muted)">Terms</a>
108 <a href="/privacy" style="color: var(--text-muted)">Privacy</a>
109 <a href="/acceptable-use" style="color: var(--text-muted)">Acceptable Use</a>
110 </div>
fc1817aClaude111 </footer>
699e5c7Claude112 {/* Block I4 — Command palette shell (hidden by default) */}
113 <div
114 id="cmdk-backdrop"
115 style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9998"
116 />
117 <div
118 id="cmdk-panel"
119 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"
120 >
121 <input
122 id="cmdk-input"
123 type="text"
124 placeholder="Type a command..."
125 aria-label="Command palette"
126 style="width:100%;padding:12px 16px;background:transparent;color:var(--text);border:0;border-bottom:1px solid var(--border);outline:none;font-size:14px"
127 />
128 <div id="cmdk-list" style="max-height:60vh;overflow-y:auto" />
129 </div>
45e31d0Claude130 <script>{clientJs}</script>
699e5c7Claude131 <script>{pwaRegisterScript}</script>
132 <script>{navScript}</script>
fc1817aClaude133 </body>
134 </html>
135 );
136};
137
6fc53bdClaude138// Runs before paint — reads the theme cookie and flips data-theme so there's
139// no dark-to-light flash on load. SSR default is dark.
140const themeInitScript = `
141 (function(){
142 try {
143 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
144 var t = m ? decodeURIComponent(m[1]) : 'dark';
145 if (t !== 'light' && t !== 'dark') t = 'dark';
146 document.documentElement.setAttribute('data-theme', t);
147 } catch(_){}
148 })();
149`;
150
eae38d1Claude151// Block G1 — register service worker for offline / install support.
152// Kept inline (and tiny) so we don't block first paint.
153const pwaRegisterScript = `
154 if ('serviceWorker' in navigator) {
155 window.addEventListener('load', function(){
156 navigator.serviceWorker.register('/sw.js').catch(function(){});
157 });
158 }
159`;
160
3ef4c9dClaude161const navScript = `
162 (function(){
163 var chord = null;
164 var chordTimer = null;
165 function isTyping(t){
166 t = t || {};
167 var tag = (t.tagName || '').toLowerCase();
168 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
169 }
71cd5ecClaude170
171 // ---------- Block I4 — Command palette ----------
172 var COMMANDS = [
173 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
174 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
175 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
176 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
177 { label: 'Create new repository', href: '/new', kw: 'add create' },
178 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
179 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
180 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
181 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
182 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
183 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
184 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
185 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
186 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
187 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
188 { label: 'Gists', href: '/gists', kw: 'snippets' },
189 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
190 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
191 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
192 ];
193
194 function fuzzyMatch(item, q){
195 if (!q) return true;
196 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
197 q = q.toLowerCase();
198 var qi = 0;
199 for (var i = 0; i < hay.length && qi < q.length; i++) {
200 if (hay[i] === q[qi]) qi++;
201 }
202 return qi === q.length;
203 }
204
205 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
206
207 function render(){
208 if (!list) return;
209 var html = '';
210 for (var i = 0; i < filtered.length; i++) {
211 var item = filtered[i];
212 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
213 var bg = i === selected ? 'background:var(--bg);' : '';
ea52715copilot-swe-agent[bot]214 html += '<div class="' + cls + '" data-idx="' + i + '" data-url="' + item.href + '"' +
71cd5ecClaude215 ' style="padding:10px 16px;cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
216 '<div>' + item.label + '</div>' +
217 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
218 '</div>';
219 }
220 if (filtered.length === 0) {
221 html = '<div style="padding:16px;color:var(--text-muted);text-align:center">No matches.</div>';
222 }
223 list.innerHTML = html;
224 }
225
226 function openPalette(){
227 backdrop = document.getElementById('cmdk-backdrop');
228 panel = document.getElementById('cmdk-panel');
229 input = document.getElementById('cmdk-input');
230 list = document.getElementById('cmdk-list');
231 if (!backdrop || !panel) return;
232 backdrop.style.display = 'block';
233 panel.style.display = 'block';
234 input.value = '';
235 selected = 0;
236 filtered = COMMANDS.slice();
237 render();
238 input.focus();
239 }
240 function closePalette(){
241 if (backdrop) backdrop.style.display = 'none';
242 if (panel) panel.style.display = 'none';
243 }
244 function go(href){ closePalette(); window.location.href = href; }
245
246 document.addEventListener('click', function(e){
247 var t = e.target;
248 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
249 var item = t && t.closest && t.closest('.cmdk-item');
ea52715copilot-swe-agent[bot]250 if (item) { go(item.getAttribute('data-url')); }
71cd5ecClaude251 });
252
253 document.addEventListener('input', function(e){
254 if (e.target && e.target.id === 'cmdk-input') {
255 var q = e.target.value;
256 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
257 selected = 0;
258 render();
259 }
260 });
261
3ef4c9dClaude262 document.addEventListener('keydown', function(e){
71cd5ecClaude263 // Palette-scoped keys take priority when open
264 if (panel && panel.style.display === 'block') {
265 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
266 if (e.key === 'ArrowDown') {
267 e.preventDefault();
268 selected = Math.min(filtered.length - 1, selected + 1);
269 render();
270 return;
271 }
272 if (e.key === 'ArrowUp') {
273 e.preventDefault();
274 selected = Math.max(0, selected - 1);
275 render();
276 return;
277 }
278 if (e.key === 'Enter') {
279 e.preventDefault();
280 var item = filtered[selected];
281 if (item) go(item.href);
282 return;
283 }
284 return;
285 }
286
3ef4c9dClaude287 if (isTyping(e.target)) return;
288 // Single key shortcuts
289 if (e.key === '/') {
290 var el = document.querySelector('.nav-search input');
291 if (el) { e.preventDefault(); el.focus(); return; }
292 }
293 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude294 e.preventDefault();
295 openPalette();
296 return;
3ef4c9dClaude297 }
298 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
299 e.preventDefault(); window.location.href = '/shortcuts'; return;
300 }
301 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
302 e.preventDefault(); window.location.href = '/new'; return;
303 }
304 // "g" chord
305 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
306 chord = 'g';
307 clearTimeout(chordTimer);
308 chordTimer = setTimeout(function(){ chord = null; }, 1200);
309 return;
310 }
311 if (chord === 'g') {
312 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
313 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
314 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
315 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
316 chord = null;
317 }
318 });
319 })();
320`;
321
fc1817aClaude322const css = `
6fc53bdClaude323 :root, :root[data-theme='dark'] {
4c47454Claude324 --bg: #0a0a0f;
325 --bg-secondary: #12121a;
326 --bg-tertiary: #1a1a25;
327 --bg-elevated: #12121a;
328 --bg-surface: #1a1a25;
329 --bg-hover: rgba(255,255,255,0.04);
330 --bg-active: rgba(255,255,255,0.08);
331 --border: rgba(255,255,255,0.08);
332 --border-strong: rgba(255,255,255,0.14);
333 --border-focus: rgba(168,85,247,0.5);
334 --text: #e6e6f0;
335 --text-muted: #8b8ba0;
336 --text-faint: #5a5a70;
337 --text-link: #a855f7;
338 --accent: #a855f7;
339 --accent-2: #06b6d4;
340 --accent-hover: #b966f8;
341 --accent-gradient: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
342 --accent-gradient-soft: linear-gradient(135deg, rgba(168,85,247,0.15) 0%, rgba(6,182,212,0.15) 100%);
343 --green: #10b981;
344 --red: #ef4444;
345 --yellow: #f59e0b;
346 --amber: #f59e0b;
347 --blue: #3b82f6;
348 --font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;
349 --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
350 --radius: 8px;
351 --r-sm: 6px;
352 --r: 8px;
353 --r-lg: 12px;
354 --r-xl: 16px;
355 --r-full: 9999px;
356 --t-xs: 11px;
357 --t-sm: 13px;
358 --t-base: 14px;
359 --t-md: 16px;
360 --t-lg: 20px;
361 --t-xl: 28px;
362 --t-2xl: 40px;
363 --t-3xl: 56px;
364 --s-1: 4px;
365 --s-2: 8px;
366 --s-3: 12px;
367 --s-4: 16px;
368 --s-5: 20px;
369 --s-6: 24px;
370 --s-8: 32px;
371 --s-10: 40px;
372 --s-12: 48px;
373 --s-16: 64px;
374 --elev-0: 0 0 0 1px var(--border);
375 --elev-1: 0 1px 2px rgba(0,0,0,0.3), 0 0 0 1px var(--border);
376 --elev-2: 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px var(--border);
377 --elev-glow: 0 0 0 1px rgba(168,85,247,0.3), 0 0 24px rgba(168,85,247,0.15);
378 --ease: cubic-bezier(0.16, 1, 0.3, 1);
379 --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
380 --t-fast: 120ms;
381 --t-slow: 320ms;
fc1817aClaude382 }
383
6fc53bdClaude384 :root[data-theme='light'] {
4c47454Claude385 --bg: #fafafa;
386 --bg-secondary: #ffffff;
387 --bg-tertiary: #f4f4f7;
388 --bg-elevated: #ffffff;
389 --bg-surface: #f4f4f7;
390 --bg-hover: rgba(0,0,0,0.04);
391 --bg-active: rgba(0,0,0,0.08);
392 --border: rgba(0,0,0,0.08);
393 --border-strong: rgba(0,0,0,0.14);
394 --text: #0a0a0f;
395 --text-muted: #5a5a70;
396 --text-faint: #8b8ba0;
397 --text-link: #7c3aed;
398 --accent: #7c3aed;
399 --accent-2: #0891b2;
400 --accent-hover: #6d28d9;
401 --green: #059669;
402 --red: #dc2626;
403 --yellow: #d97706;
6fc53bdClaude404 }
405
406 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
407 .nav-theme { display: inline-flex; align-items: center; font-size: 16px; line-height: 1; }
408 :root[data-theme='dark'] .theme-icon-dark { display: none; }
409 :root[data-theme='light'] .theme-icon-light { display: none; }
410
fc1817aClaude411 * { margin: 0; padding: 0; box-sizing: border-box; }
412
413 body {
414 font-family: var(--font-sans);
415 background: var(--bg);
416 color: var(--text);
417 line-height: 1.5;
418 min-height: 100vh;
419 display: flex;
420 flex-direction: column;
421 }
422
423 a { color: var(--text-link); text-decoration: none; }
424 a:hover { text-decoration: underline; }
425
4a52a98Claude426 /* Pre-launch banner - always visible, not dismissible. Amber tone, readable
427 on both light and dark themes via the shared --yellow token. */
428 .prelaunch-banner {
429 background: rgba(210, 153, 34, 0.15);
430 border-bottom: 1px solid var(--yellow);
431 color: var(--yellow);
432 padding: 8px 24px;
433 font-size: 13px;
434 font-weight: 500;
435 text-align: center;
436 line-height: 1.4;
437 }
438
fc1817aClaude439 header {
440 border-bottom: 1px solid var(--border);
441 padding: 12px 24px;
442 background: var(--bg-secondary);
443 }
444
06d5ffeClaude445 header nav {
446 display: flex;
447 align-items: center;
448 justify-content: space-between;
449 max-width: 1200px;
450 margin: 0 auto;
451 }
fc1817aClaude452 .logo { font-size: 20px; font-weight: 700; color: var(--text); }
453 .logo:hover { text-decoration: none; color: var(--text-link); }
454
06d5ffeClaude455 .nav-right { display: flex; align-items: center; gap: 16px; }
456 .nav-link { color: var(--text-muted); font-size: 14px; }
457 .nav-link:hover { color: var(--text); text-decoration: none; }
458 .nav-user { color: var(--text); font-weight: 600; font-size: 14px; }
459 .nav-user:hover { color: var(--text-link); text-decoration: none; }
460
fc1817aClaude461 main { max-width: 1200px; margin: 0 auto; padding: 24px; flex: 1; width: 100%; }
462
463 footer {
464 border-top: 1px solid var(--border);
465 padding: 16px 24px;
466 text-align: center;
467 color: var(--text-muted);
468 font-size: 13px;
469 }
470
06d5ffeClaude471 /* Buttons */
472 .btn {
473 display: inline-flex;
474 align-items: center;
475 gap: 6px;
476 padding: 8px 16px;
477 border-radius: var(--radius);
478 font-size: 14px;
479 font-weight: 500;
480 border: 1px solid var(--border);
481 background: var(--bg-tertiary);
482 color: var(--text);
483 cursor: pointer;
484 text-decoration: none;
485 line-height: 1.4;
486 }
487 .btn:hover { background: var(--border); text-decoration: none; }
488 .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
489 .btn-primary:hover { background: var(--accent-hover); }
490 .btn-danger { background: transparent; border-color: var(--red); color: var(--red); }
491 .btn-danger:hover { background: rgba(248, 81, 73, 0.15); }
492 .btn-sm { padding: 4px 12px; font-size: 13px; }
493
494 /* Forms */
495 .form-group { margin-bottom: 16px; }
496 .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; color: var(--text); }
497 .form-group input, .form-group textarea, .form-group select {
498 width: 100%;
499 padding: 8px 12px;
500 background: var(--bg);
501 border: 1px solid var(--border);
502 border-radius: var(--radius);
503 color: var(--text);
504 font-size: 14px;
505 font-family: var(--font-sans);
506 }
507 .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
508 outline: none;
509 border-color: var(--accent);
510 box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);
511 }
512 .input-disabled { opacity: 0.5; cursor: not-allowed; }
513
514 /* Auth */
515 .auth-container { max-width: 400px; margin: 40px auto; }
516 .auth-container h2 { margin-bottom: 20px; font-size: 24px; }
517 .auth-error {
518 background: rgba(248, 81, 73, 0.1);
519 border: 1px solid var(--red);
520 color: var(--red);
521 padding: 8px 12px;
522 border-radius: var(--radius);
523 margin-bottom: 16px;
524 font-size: 14px;
525 }
526 .auth-success {
527 background: rgba(63, 185, 80, 0.1);
528 border: 1px solid var(--green);
529 color: var(--green);
530 padding: 8px 12px;
531 border-radius: var(--radius);
532 margin-bottom: 16px;
533 font-size: 14px;
534 }
535 .auth-switch { margin-top: 16px; font-size: 14px; color: var(--text-muted); }
536
537 /* Settings */
538 .settings-container { max-width: 600px; }
539 .settings-container h2 { margin-bottom: 20px; font-size: 24px; }
540 .settings-container h3 { font-size: 18px; margin-bottom: 12px; }
541 .ssh-keys-list { margin-bottom: 24px; }
542 .ssh-key-item {
543 display: flex;
544 justify-content: space-between;
545 align-items: center;
546 padding: 12px 16px;
547 border: 1px solid var(--border);
548 border-radius: var(--radius);
549 margin-bottom: 8px;
550 background: var(--bg-secondary);
551 }
552 .ssh-key-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
553 .ssh-key-meta code { font-size: 11px; background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
554
555 /* Repo header */
fc1817aClaude556 .repo-header {
557 display: flex;
558 align-items: center;
559 gap: 8px;
560 margin-bottom: 16px;
561 font-size: 20px;
562 }
563 .repo-header .owner { color: var(--text-link); }
564 .repo-header .separator { color: var(--text-muted); }
565 .repo-header .name { color: var(--text-link); font-weight: 600; }
06d5ffeClaude566 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude567
568 .repo-nav {
569 display: flex;
570 gap: 0;
571 border-bottom: 1px solid var(--border);
572 margin-bottom: 20px;
573 }
574 .repo-nav a {
575 padding: 8px 16px;
576 color: var(--text-muted);
577 border-bottom: 2px solid transparent;
578 font-size: 14px;
579 }
580 .repo-nav a:hover { text-decoration: none; color: var(--text); }
581 .repo-nav a.active { color: var(--text); border-bottom-color: var(--accent); }
582
583 .breadcrumb { display: flex; gap: 4px; align-items: center; margin-bottom: 16px; color: var(--text-muted); font-size: 14px; }
584 .breadcrumb a { color: var(--text-link); }
585
586 .file-table { width: 100%; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
587 .file-table tr { border-bottom: 1px solid var(--border); }
588 .file-table tr:last-child { border-bottom: none; }
589 .file-table td { padding: 8px 16px; font-size: 14px; }
590 .file-table tr:hover { background: var(--bg-secondary); }
591 .file-icon { width: 20px; color: var(--text-muted); }
592 .file-name a { color: var(--text); }
593 .file-name a:hover { color: var(--text-link); text-decoration: underline; }
594
595 .blob-view {
596 border: 1px solid var(--border);
597 border-radius: var(--radius);
598 overflow: hidden;
599 }
600 .blob-header {
601 background: var(--bg-secondary);
602 padding: 8px 16px;
603 border-bottom: 1px solid var(--border);
604 font-size: 13px;
605 color: var(--text-muted);
06d5ffeClaude606 display: flex;
607 justify-content: space-between;
608 align-items: center;
fc1817aClaude609 }
610 .blob-code {
611 overflow-x: auto;
612 font-family: var(--font-mono);
613 font-size: 13px;
614 line-height: 1.6;
615 }
616 .blob-code table { width: 100%; border-collapse: collapse; }
617 .blob-code .line-num {
618 width: 1%;
619 min-width: 50px;
620 padding: 0 12px;
621 text-align: right;
622 color: var(--text-muted);
623 user-select: none;
624 white-space: nowrap;
625 border-right: 1px solid var(--border);
626 }
627 .blob-code .line-content { padding: 0 12px; white-space: pre; }
628
629 .commit-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
630 .commit-item {
631 display: flex;
632 justify-content: space-between;
633 align-items: center;
634 padding: 12px 16px;
635 border-bottom: 1px solid var(--border);
636 }
637 .commit-item:last-child { border-bottom: none; }
638 .commit-item:hover { background: var(--bg-secondary); }
639 .commit-message { font-size: 14px; font-weight: 500; }
640 .commit-meta { font-size: 12px; color: var(--text-muted); }
641 .commit-sha {
642 font-family: var(--font-mono);
643 font-size: 12px;
644 padding: 2px 8px;
645 background: var(--bg-tertiary);
646 border: 1px solid var(--border);
647 border-radius: var(--radius);
648 color: var(--text-link);
649 }
650
651 .diff-view { margin-top: 16px; }
652 .diff-file {
653 border: 1px solid var(--border);
654 border-radius: var(--radius);
655 margin-bottom: 16px;
656 overflow: hidden;
657 }
658 .diff-file-header {
659 background: var(--bg-secondary);
660 padding: 8px 16px;
661 border-bottom: 1px solid var(--border);
662 font-family: var(--font-mono);
663 font-size: 13px;
664 }
665 .diff-content {
666 overflow-x: auto;
667 font-family: var(--font-mono);
668 font-size: 13px;
669 line-height: 1.6;
670 }
671 .diff-content .line-add { background: rgba(63, 185, 80, 0.15); color: var(--green); }
672 .diff-content .line-del { background: rgba(248, 81, 73, 0.1); color: var(--red); }
673 .diff-content .line-hunk { background: rgba(56, 139, 253, 0.1); color: var(--text-link); }
674 .diff-content .line { padding: 0 12px; white-space: pre; display: block; }
675
676 .stat-add { color: var(--green); font-weight: 600; }
677 .stat-del { color: var(--red); font-weight: 600; }
678
679 .empty-state {
680 text-align: center;
681 padding: 60px 20px;
682 color: var(--text-muted);
683 }
684 .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
685 .empty-state pre {
686 text-align: left;
687 display: inline-block;
688 background: var(--bg-secondary);
689 padding: 16px 24px;
690 border-radius: var(--radius);
691 border: 1px solid var(--border);
692 font-family: var(--font-mono);
693 font-size: 13px;
694 margin-top: 16px;
695 line-height: 1.8;
696 }
697
698 .badge {
699 display: inline-block;
700 padding: 2px 8px;
701 border-radius: 12px;
702 font-size: 12px;
703 font-weight: 500;
704 background: var(--bg-tertiary);
705 border: 1px solid var(--border);
706 color: var(--text-muted);
707 }
708
709 .branch-selector {
710 display: inline-flex;
711 align-items: center;
712 gap: 4px;
713 padding: 4px 12px;
714 background: var(--bg-tertiary);
715 border: 1px solid var(--border);
716 border-radius: var(--radius);
717 font-size: 13px;
718 color: var(--text);
719 margin-bottom: 12px;
06d5ffeClaude720 position: relative;
fc1817aClaude721 }
722
06d5ffeClaude723 .branch-dropdown {
724 position: relative;
725 display: inline-block;
726 margin-bottom: 12px;
727 }
728 .branch-dropdown-content {
729 display: none;
730 position: absolute;
731 top: 100%;
732 left: 0;
733 z-index: 10;
734 min-width: 200px;
735 background: var(--bg-secondary);
736 border: 1px solid var(--border);
737 border-radius: var(--radius);
738 margin-top: 4px;
739 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
740 }
741 .branch-dropdown:hover .branch-dropdown-content,
742 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
743 .branch-dropdown-content a {
744 display: block;
745 padding: 8px 12px;
746 font-size: 13px;
747 color: var(--text);
748 border-bottom: 1px solid var(--border);
749 }
750 .branch-dropdown-content a:last-child { border-bottom: none; }
751 .branch-dropdown-content a:hover { background: var(--bg-tertiary); text-decoration: none; }
752 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; }
753
fc1817aClaude754 .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
755 .card {
756 border: 1px solid var(--border);
757 border-radius: var(--radius);
758 padding: 16px;
759 background: var(--bg-secondary);
06d5ffeClaude760 transition: border-color 0.15s;
fc1817aClaude761 }
06d5ffeClaude762 .card:hover { border-color: var(--text-muted); }
fc1817aClaude763 .card h3 { font-size: 16px; margin-bottom: 4px; }
06d5ffeClaude764 .card h3 a { color: var(--text-link); }
fc1817aClaude765 .card p { font-size: 13px; color: var(--text-muted); }
06d5ffeClaude766 .card-meta { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
767 .card-meta span { display: flex; align-items: center; gap: 4px; }
768
769 .star-btn {
770 display: inline-flex;
771 align-items: center;
772 gap: 6px;
773 padding: 4px 12px;
774 background: var(--bg-tertiary);
775 border: 1px solid var(--border);
776 border-radius: var(--radius);
777 color: var(--text);
778 font-size: 13px;
779 cursor: pointer;
780 }
781 .star-btn:hover { background: var(--border); text-decoration: none; }
782 .star-btn.starred { color: var(--yellow); border-color: var(--yellow); }
783
784 .user-profile {
785 display: flex;
786 gap: 32px;
787 margin-bottom: 32px;
788 }
789 .user-avatar {
790 width: 96px;
791 height: 96px;
792 border-radius: 50%;
793 background: var(--bg-tertiary);
794 border: 1px solid var(--border);
795 display: flex;
796 align-items: center;
797 justify-content: center;
798 font-size: 40px;
799 color: var(--text-muted);
800 flex-shrink: 0;
801 }
802 .user-info h2 { font-size: 24px; margin-bottom: 2px; }
803 .user-info .username { font-size: 16px; color: var(--text-muted); }
804 .user-info .bio { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
805
806 .new-repo-form { max-width: 600px; }
807 .new-repo-form h2 { margin-bottom: 20px; }
808 .visibility-options { display: flex; gap: 12px; margin-bottom: 16px; }
809 .visibility-option {
810 flex: 1;
811 padding: 12px;
812 border: 1px solid var(--border);
813 border-radius: var(--radius);
814 background: var(--bg-secondary);
815 cursor: pointer;
816 text-align: center;
817 }
818 .visibility-option:has(input:checked) { border-color: var(--accent); background: rgba(31, 111, 235, 0.1); }
819 .visibility-option input { display: none; }
820 .visibility-option .vis-label { font-size: 14px; font-weight: 500; }
821 .visibility-option .vis-desc { font-size: 12px; color: var(--text-muted); }
79136bbClaude822
823 /* Issues */
824 .issue-tabs { display: flex; gap: 16px; }
825 .issue-tabs a { color: var(--text-muted); font-size: 14px; font-weight: 500; }
826 .issue-tabs a:hover { color: var(--text); text-decoration: none; }
827 .issue-tabs a.active { color: var(--text); }
828
829 .issue-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
830 .issue-item {
831 display: flex;
832 gap: 12px;
833 align-items: flex-start;
834 padding: 12px 16px;
835 border-bottom: 1px solid var(--border);
836 }
837 .issue-item:last-child { border-bottom: none; }
838 .issue-item:hover { background: var(--bg-secondary); }
839 .issue-state-icon { font-size: 16px; padding-top: 2px; }
840 .state-open { color: var(--green); }
841 .state-closed { color: #986ee2; }
842 .issue-title { font-size: 15px; font-weight: 600; }
843 .issue-title a { color: var(--text); }
844 .issue-title a:hover { color: var(--text-link); }
845 .issue-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
846
847 .issue-badge {
848 display: inline-flex;
849 align-items: center;
850 gap: 4px;
851 padding: 4px 12px;
852 border-radius: 20px;
853 font-size: 13px;
854 font-weight: 500;
855 }
856 .badge-open { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid var(--green); }
857 .badge-closed { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
0074234Claude858 .badge-merged { background: rgba(152, 110, 226, 0.15); color: #986ee2; border: 1px solid #986ee2; }
859 .state-merged { color: #986ee2; }
860 .ai-review { border-color: var(--accent); }
79136bbClaude861
862 .issue-detail { max-width: 900px; }
863 .issue-comment-box {
864 border: 1px solid var(--border);
865 border-radius: var(--radius);
866 margin-bottom: 16px;
867 overflow: hidden;
868 }
869 .comment-header {
870 background: var(--bg-secondary);
871 padding: 8px 16px;
872 border-bottom: 1px solid var(--border);
873 font-size: 13px;
874 color: var(--text-muted);
875 }
876
877 /* Search */
878 .search-results .diff-file { margin-bottom: 12px; }
16b325cClaude879
880 /* Timeline */
881 .timeline { position: relative; padding-left: 24px; }
882 .timeline::before {
883 content: '';
884 position: absolute;
885 left: 4px;
886 top: 8px;
887 bottom: 8px;
888 width: 2px;
889 background: var(--border);
890 }
891 .timeline-item {
892 position: relative;
893 padding-bottom: 16px;
894 }
895 .timeline-dot {
896 position: absolute;
897 left: -24px;
898 top: 6px;
899 width: 10px;
900 height: 10px;
901 border-radius: 50%;
902 background: var(--text-muted);
903 border: 2px solid var(--bg);
904 }
905 .timeline-content {
906 background: var(--bg-secondary);
907 border: 1px solid var(--border);
908 border-radius: var(--radius);
909 padding: 12px 16px;
910 }
f1ab587Claude911
912 /* Toggle switch */
913 .toggle-switch {
914 position: relative;
915 display: inline-block;
916 width: 44px;
917 height: 24px;
918 flex-shrink: 0;
919 margin-left: 16px;
920 }
921 .toggle-switch input { opacity: 0; width: 0; height: 0; }
922 .toggle-slider {
923 position: absolute;
924 cursor: pointer;
925 top: 0; left: 0; right: 0; bottom: 0;
926 background: var(--bg-tertiary);
927 border: 1px solid var(--border);
928 border-radius: 12px;
929 transition: 0.2s;
930 }
931 .toggle-slider::before {
932 content: '';
933 position: absolute;
934 height: 18px;
935 width: 18px;
936 left: 2px;
937 bottom: 2px;
938 background: var(--text-muted);
939 border-radius: 50%;
940 transition: 0.2s;
941 }
942 .toggle-switch input:checked + .toggle-slider {
943 background: var(--accent);
944 border-color: var(--accent);
945 }
946 .toggle-switch input:checked + .toggle-slider::before {
947 transform: translateX(20px);
948 background: #fff;
949 }
fc1817aClaude950`;