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.tsxBlame2866 lines · 4 contributors
fc1817aClaude1import type { FC, PropsWithChildren } from "hono/jsx";
06d5ffeClaude2import type { User } from "../db/schema";
3import { hljsThemeCss } from "../lib/highlight";
45e31d0Claude4import { clientJs } from "./client-js";
05cdb85Claude5import { getBuildInfo } from "../lib/build-info";
fc1817aClaude6
06d5ffeClaude7export const Layout: FC<
3ef4c9dClaude8 PropsWithChildren<{
9 title?: string;
10 user?: User | null;
11 notificationCount?: number;
6fc53bdClaude12 theme?: "dark" | "light";
5f2e749Claude13 // Block L10 — additive SEO + Open Graph fields. All optional;
14 // omission preserves the prior render exactly (regression-safe).
15 fullTitle?: string;
16 description?: string;
17 ogTitle?: string;
18 ogDescription?: string;
19 ogType?: string;
20 twitterCard?: "summary" | "summary_large_image";
c63b860Claude21 // Block O3 — site-wide footer banner stripe. When non-empty,
22 // renders below the footer-bottom row; wired from
23 // admin.system_flags.site_banner_text.
24 siteBannerText?: string;
25 siteBannerLevel?: "info" | "warn" | "error";
3ef4c9dClaude26 }>
5f2e749Claude27> = ({
28 children,
29 title,
30 user,
31 notificationCount,
32 theme,
33 fullTitle,
34 description,
35 ogTitle,
36 ogDescription,
37 ogType,
38 twitterCard,
c63b860Claude39 siteBannerText,
40 siteBannerLevel,
5f2e749Claude41}) => {
6fc53bdClaude42 const initialTheme = theme === "light" ? "light" : "dark";
05cdb85Claude43 const build = getBuildInfo();
5f2e749Claude44 // L10 — when `fullTitle` is provided, use it verbatim (no " — gluecron"
45 // suffix); otherwise fall back to the existing `title` + suffix behaviour.
46 const renderedTitle = fullTitle
47 ? fullTitle
48 : title
49 ? `${title} — gluecron`
50 : "gluecron";
fc1817aClaude51 return (
6fc53bdClaude52 <html lang="en" data-theme={initialTheme}>
fc1817aClaude53 <head>
54 <meta charset="UTF-8" />
55 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
eae38d1Claude56 <meta name="theme-color" content="#0d1117" />
57 <link rel="manifest" href="/manifest.webmanifest" />
58 <link rel="icon" type="image/svg+xml" href="/icon.svg" />
5f2e749Claude59 <title>{renderedTitle}</title>
60 {description && <meta name="description" content={description} />}
61 {(ogTitle || fullTitle || title) && (
62 <meta property="og:title" content={ogTitle ?? fullTitle ?? renderedTitle} />
63 )}
64 {(ogDescription || description) && (
65 <meta
66 property="og:description"
67 content={ogDescription ?? description ?? ""}
68 />
69 )}
70 {ogType && <meta property="og:type" content={ogType} />}
71 {twitterCard && <meta name="twitter:card" content={twitterCard} />}
fa880f2Claude72 <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
73 <style dangerouslySetInnerHTML={{ __html: css }} />
74 <style dangerouslySetInnerHTML={{ __html: hljsThemeCss }} />
fc1817aClaude75 </head>
76 <body>
4a52a98Claude77 <div class="prelaunch-banner" role="status" aria-live="polite">
78 Pre-launch &mdash; Gluecron is in final validation. Public signups
79 and git hosting for non-owner users open after launch review.
80 </div>
cd4f63bTest User81 {/* Block Q3 — Playground banner. Renders only when the active
82 user is a playground account with a future expiry; the small
83 inline script counts down once per minute. Strip is dismissible
84 per-page-load (re-appears on next nav) — gentle, not noisy. */}
85 {user && (user as any).isPlayground && (user as any).playgroundExpiresAt && (
86 <div
87 class="playground-banner"
88 role="status"
89 aria-live="polite"
90 data-playground-expires={
91 ((user as any).playgroundExpiresAt instanceof Date
92 ? (user as any).playgroundExpiresAt.toISOString()
93 : String((user as any).playgroundExpiresAt))
94 }
95 >
96 <span class="playground-banner-icon" aria-hidden="true">{"\u{1F3AE}"}</span>
97 <span class="playground-banner-text">
98 Playground account &mdash;{" "}
99 <span class="playground-banner-countdown">expires soon</span>.{" "}
100 <a href="/play/claim" class="playground-banner-cta">
101 Save your work &rarr;
102 </a>
103 </span>
104 <button
105 type="button"
106 class="playground-banner-dismiss"
107 aria-label="Dismiss"
108 data-playground-dismiss="1"
109 >
110 {"×"}
111 </button>
112 <script
113 dangerouslySetInnerHTML={{
114 __html: /* js */ `
115 (function () {
116 var el = document.currentScript && document.currentScript.parentElement;
117 if (!el) return;
118 var iso = el.getAttribute('data-playground-expires');
119 if (!iso) return;
120 var target = Date.parse(iso);
121 if (isNaN(target)) return;
122 var out = el.querySelector('.playground-banner-countdown');
123 function render() {
124 var ms = target - Date.now();
125 if (!out) return;
126 if (ms <= 0) { out.textContent = 'expired'; return; }
127 var mins = Math.floor(ms / 60000);
128 var hrs = Math.floor(mins / 60);
129 if (hrs > 1) out.textContent = hrs + ' hours left';
130 else if (hrs === 1) out.textContent = '1 hour left';
131 else if (mins > 1) out.textContent = mins + ' minutes left';
132 else out.textContent = 'less than a minute left';
133 }
134 render();
135 setInterval(render, 60000);
136 var dismiss = el.querySelector('[data-playground-dismiss="1"]');
137 if (dismiss) {
138 dismiss.addEventListener('click', function () {
139 el.style.display = 'none';
140 });
141 }
142 })();
143 `,
144 }}
145 />
146 </div>
147 )}
fc1817aClaude148 <header>
149 <nav>
150 <a href="/" class="logo">
151 gluecron
152 </a>
3ef4c9dClaude153 <div class="nav-search">
001af43Claude154 <form method="get" action="/search">
3ef4c9dClaude155 <input
156 type="search"
157 name="q"
158 placeholder="Search (press /)"
159 aria-label="Search"
160 />
161 </form>
162 </div>
06d5ffeClaude163 <div class="nav-right">
f764c07Claude164 {/* Block N3 — site-admin platform-deploy status pill. Hidden
165 by default; revealed client-side once /admin/deploys/latest.json
166 responds 200 (non-admins get 401/403 and the pill stays
167 hidden). Subscribes to the `platform:deploys` SSE topic
168 for live updates. See `deployPillScript` below. */}
169 {user && (
170 <a
171 id="deploy-pill"
172 href="/admin/deploys"
173 class="nav-deploy-pill"
174 style="display:none"
175 aria-label="Platform deploy status"
176 title="Platform deploy status"
177 >
178 <span class="deploy-pill-dot" />
179 <span class="deploy-pill-text">Deploys</span>
180 </a>
181 )}
6fc53bdClaude182 <a
183 href="/theme/toggle"
184 class="nav-link nav-theme"
185 title="Toggle theme"
186 aria-label="Toggle theme"
187 >
188 <span class="theme-icon-dark">{"\u263E"}</span>
189 <span class="theme-icon-light">{"\u2600"}</span>
190 </a>
c81ab7aClaude191 <a href="/explore" class="nav-link">
192 Explore
193 </a>
06d5ffeClaude194 {user ? (
195 <>
f1ab587Claude196 <a href="/dashboard" class="nav-link" style="font-weight: 600">
197 Dashboard
198 </a>
bdbd0deClaude199 <a href="/import" class="nav-link">
200 Import
201 </a>
06d5ffeClaude202 <a href="/new" class="btn btn-sm btn-primary">
203 + New
204 </a>
205 <a href={`/${user.username}`} class="nav-user">
206 {user.displayName || user.username}
207 </a>
208 <a href="/settings" class="nav-link">
209 Settings
210 </a>
211 <a href="/logout" class="nav-link">
212 Sign out
213 </a>
214 </>
215 ) : (
216 <>
217 <a href="/login" class="nav-link">
218 Sign in
219 </a>
220 <a href="/register" class="btn btn-sm btn-primary">
221 Register
222 </a>
223 </>
224 )}
225 </div>
fc1817aClaude226 </nav>
227 </header>
45e31d0Claude228 <main id="main-content">{children}</main>
fc1817aClaude229 <footer>
958d26aClaude230 <div class="footer-inner">
231 <div class="footer-brand">
232 <a href="/" class="logo">gluecron</a>
233 <p class="footer-tag">
234 AI-native code intelligence. Self-hosted git, automated CI,
235 push-time gates. Software that ships itself.
236 </p>
237 </div>
238 <div class="footer-links">
239 <div class="footer-col">
240 <div class="footer-col-title">Product</div>
b0148e9Claude241 <a href="/features">Features</a>
242 <a href="/pricing">Pricing</a>
958d26aClaude243 <a href="/explore">Explore</a>
244 <a href="/marketplace">Marketplace</a>
245 </div>
246 <div class="footer-col">
247 <div class="footer-col-title">Platform</div>
b0148e9Claude248 <a href="/help">Quickstart</a>
958d26aClaude249 <a href="/status">Status</a>
250 <a href="/api/graphql">GraphQL</a>
251 <a href="/mcp">MCP server</a>
252 </div>
253 <div class="footer-col">
b0148e9Claude254 <div class="footer-col-title">Company</div>
255 <a href="/about">About</a>
958d26aClaude256 <a href="/terms">Terms</a>
257 <a href="/privacy">Privacy</a>
258 <a href="/acceptable-use">Acceptable use</a>
259 </div>
260 </div>
261 </div>
262 <div class="footer-bottom">
263 <span>&copy; {new Date().getFullYear()} gluecron</span>
05cdb85Claude264 <span class="footer-build" title={`commit ${build.shaFull}\nbuilt ${build.builtAt}`}>
265 <span class="footer-build-dot" aria-hidden="true" />
266 {build.sha} · {build.branch}
267 </span>
36b4cbdClaude268 </div>
c63b860Claude269 {siteBannerText ? (
270 <div
271 class={`footer-banner footer-banner-${siteBannerLevel || "info"}`}
272 role="status"
273 aria-live="polite"
274 >
275 {siteBannerText}
276 </div>
277 ) : null}
fc1817aClaude278 </footer>
05cdb85Claude279 {/* Live update poller — checks /api/version every 15s, prompts
280 reload when the running sha changes. Pure progressive-
281 enhancement; degrades to nothing if JS is off. */}
282 <div
283 id="version-banner"
284 style="display:none;position:fixed;bottom:18px;left:50%;transform:translateX(-50%);z-index:9999;background:var(--bg-elevated);border:1px solid rgba(140,109,255,0.45);border-radius:9999px;padding:8px 14px 8px 14px;font-size:13px;color:var(--text-strong);box-shadow:0 12px 28px -8px rgba(0,0,0,0.55),0 0 24px -6px rgba(140,109,255,0.40);font-family:var(--font-sans);align-items:center;gap:10px"
285 >
286 <span style="display:inline-flex;align-items:center;gap:8px">
287 <span style="width:8px;height:8px;border-radius:50%;background:#34d399;box-shadow:0 0 10px rgba(52,211,153,0.6)" />
288 <span>New version available</span>
289 </span>
290 <button
291 type="button"
292 id="version-banner-reload"
293 style="background:linear-gradient(135deg,#8c6dff 0%,#36c5d6 100%);color:#fff;border:0;border-radius:9999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit"
294 >
295 Reload
296 </button>
297 </div>
fa880f2Claude298 <script dangerouslySetInnerHTML={{ __html: versionPollerScript }} />
f764c07Claude299 {/* Block N3 — site-admin deploy status pill (script-only). The pill
300 container is rendered above for authed users; this script bootstraps
301 it by fetching /admin/deploys/latest.json. Non-admins get 401/403
302 and the pill stays display:none — zero leak. */}
303 {user && (
304 <script dangerouslySetInnerHTML={{ __html: deployPillScript }} />
305 )}
699e5c7Claude306 {/* Block I4 — Command palette shell (hidden by default) */}
307 <div
308 id="cmdk-backdrop"
309 style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9998"
310 />
311 <div
312 id="cmdk-panel"
313 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"
314 >
315 <input
316 id="cmdk-input"
317 type="text"
318 placeholder="Type a command..."
319 aria-label="Command palette"
dc26881CC LABS App320 style="width:100%;padding:var(--space-3) var(--space-4);background:transparent;color:var(--text);border:0;border-bottom:1px solid var(--border);outline:none;font-size:14px"
699e5c7Claude321 />
322 <div id="cmdk-list" style="max-height:60vh;overflow-y:auto" />
323 </div>
fa880f2Claude324 <script dangerouslySetInnerHTML={{ __html: clientJs }} />
325 <script dangerouslySetInnerHTML={{ __html: pwaRegisterScript }} />
534f04aClaude326 {/* Block M2 — smart install banner + push-SW bootstrap. Authed
327 users only; the banner respects a 3+ visits + 14-day cooldown
328 heuristic. The push SW is registered on the same gate so we
329 don't ask anonymous visitors to opt into anything. */}
330 {user && (
331 <script
332 dangerouslySetInnerHTML={{ __html: pwaInstallBannerScript }}
333 />
334 )}
fa880f2Claude335 <script dangerouslySetInnerHTML={{ __html: navScript }} />
fc1817aClaude336 </body>
337 </html>
338 );
339};
340
05cdb85Claude341// Live version poller. Checks /api/version every 15s; if the sha differs
342// from the one we booted with, reveal the floating 'New version' pill so
343// the user can reload onto the new code without manually refreshing.
344const versionPollerScript = `
345 (function(){
346 var loadedSha = null;
347 var banner, btn;
348 function poll(){
349 fetch('/api/version', { cache: 'no-store' })
350 .then(function(r){ return r.ok ? r.json() : null; })
351 .then(function(j){
352 if (!j || !j.sha) return;
353 if (loadedSha === null) { loadedSha = j.sha; return; }
354 if (j.sha !== loadedSha && banner) {
355 banner.style.display = 'inline-flex';
356 }
357 })
358 .catch(function(){});
359 }
360 function init(){
361 banner = document.getElementById('version-banner');
362 btn = document.getElementById('version-banner-reload');
363 if (btn) btn.addEventListener('click', function(){ window.location.reload(); });
364 poll();
365 setInterval(poll, 15000);
366 }
367 if (document.readyState === 'loading') {
368 document.addEventListener('DOMContentLoaded', init);
369 } else {
370 init();
371 }
372 })();
373`;
374
f764c07Claude375// Block N3 — site-admin deploy status pill. Fetches /admin/deploys/latest.json;
376// if 200, reveals the pill in the nav and subscribes to the `platform:deploys`
377// SSE topic for live status updates. Non-admins get 401/403 and the pill stays
378// display:none — there's no leakage of admin-only data to other users.
379//
380// Pill states (CSS classes on the container drive colour):
381// .deploy-pill-success 🟢 "Deployed 12s ago"
382// .deploy-pill-progress 🟡 "Deploying… 14s" (pulsing dot)
383// .deploy-pill-failed 🔴 "Deploy failed 1m ago"
384// .deploy-pill-empty ⚪ "No deploys yet"
385//
386// Relative-time auto-refreshes every 15s without re-fetching.
387export const deployPillScript = `
388 (function(){
389 var pill, dot, text;
390 var state = { latest: null, asOf: null };
391
392 function classifyAge(ms){
393 if (ms < 0) return 'just now';
394 var s = Math.floor(ms / 1000);
395 if (s < 5) return 'just now';
396 if (s < 60) return s + 's ago';
397 var m = Math.floor(s / 60);
398 if (m < 60) return m + 'm ago';
399 var h = Math.floor(m / 60);
400 if (h < 24) return h + 'h ago';
401 var d = Math.floor(h / 24);
402 return d + 'd ago';
403 }
404 function elapsed(ms){
405 if (ms < 0) ms = 0;
406 var s = Math.floor(ms / 1000);
407 if (s < 60) return s + 's';
408 var m = Math.floor(s / 60);
409 var rem = s - m * 60;
410 return m + 'm ' + rem + 's';
411 }
412
413 function render(){
414 if (!pill || !text || !dot) return;
415 var d = state.latest;
416 if (!d) {
417 pill.className = 'nav-deploy-pill deploy-pill-empty';
418 text.textContent = 'No deploys yet';
419 pill.style.display = 'inline-flex';
420 return;
421 }
422 pill.style.display = 'inline-flex';
423 var now = Date.now();
424 if (d.status === 'in_progress') {
425 pill.className = 'nav-deploy-pill deploy-pill-progress';
426 var started = Date.parse(d.started_at) || now;
427 text.textContent = 'Deploying… ' + elapsed(now - started);
428 } else if (d.status === 'succeeded') {
429 pill.className = 'nav-deploy-pill deploy-pill-success';
430 var ref = Date.parse(d.finished_at || d.started_at) || now;
431 text.textContent = 'Deployed ' + classifyAge(now - ref);
432 } else if (d.status === 'failed') {
433 pill.className = 'nav-deploy-pill deploy-pill-failed';
434 var refF = Date.parse(d.finished_at || d.started_at) || now;
435 text.textContent = 'Deploy failed ' + classifyAge(now - refF);
436 } else {
437 pill.className = 'nav-deploy-pill';
438 text.textContent = d.status;
439 }
440 }
441
442 function fetchLatest(){
443 fetch('/admin/deploys/latest.json', { cache: 'no-store', credentials: 'same-origin' })
444 .then(function(r){ if (!r.ok) return null; return r.json(); })
445 .then(function(j){
446 if (!j || j.ok !== true) return;
447 state.latest = j.latest;
448 state.asOf = j.asOf;
449 render();
450 subscribe();
451 })
452 .catch(function(){});
453 }
454
455 var subscribed = false;
456 function subscribe(){
457 if (subscribed) return;
458 if (typeof EventSource === 'undefined') return;
459 subscribed = true;
460 var es;
bf19c50Test User461 // Exponential backoff with cap. Previously a tight 1500ms reconnect
462 // produced visible looping in the nav whenever the proxy timed out
463 // or the connection blipped — the bottom-of-page deploy pill
464 // re-rendered the placeholder every 1.5s. Cap at 60s and reset on
465 // successful message receipt.
466 var delay = 2000;
467 var DELAY_MAX = 60000;
468 function bump(){
469 delay = Math.min(delay * 2, DELAY_MAX);
470 }
471 function resetDelay(){
472 delay = 2000;
473 }
f764c07Claude474 function connect(){
475 try { es = new EventSource('/live-events/platform:deploys'); }
bf19c50Test User476 catch(e){ bump(); setTimeout(connect, delay); return; }
f764c07Claude477 es.onmessage = function(m){
bf19c50Test User478 resetDelay();
f764c07Claude479 try {
480 var d = JSON.parse(m.data);
481 if (d && d.run_id) {
482 if (!state.latest || state.latest.run_id === d.run_id ||
483 Date.parse(d.started_at) >= Date.parse(state.latest.started_at)) {
484 state.latest = d;
485 render();
486 }
487 }
488 } catch(e){}
489 };
490 es.onerror = function(){
491 try { es.close(); } catch(e){}
bf19c50Test User492 bump();
f764c07Claude493 setTimeout(connect, delay);
494 };
495 }
496 connect();
497 }
498
499 function init(){
500 pill = document.getElementById('deploy-pill');
501 if (!pill) return;
502 dot = pill.querySelector('.deploy-pill-dot');
503 text = pill.querySelector('.deploy-pill-text');
504 fetchLatest();
505 // Refresh relative-time labels every 15s so "12s ago" → "27s ago"
506 // without a fresh fetch.
507 setInterval(render, 15000);
508 }
509 if (document.readyState === 'loading') {
510 document.addEventListener('DOMContentLoaded', init);
511 } else {
512 init();
513 }
514 })();
515`;
516
6fc53bdClaude517// Runs before paint — reads the theme cookie and flips data-theme so there's
518// no dark-to-light flash on load. SSR default is dark.
519const themeInitScript = `
520 (function(){
521 try {
522 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
523 var t = m ? decodeURIComponent(m[1]) : 'dark';
524 if (t !== 'light' && t !== 'dark') t = 'dark';
525 document.documentElement.setAttribute('data-theme', t);
526 } catch(_){}
527 })();
528`;
529
eae38d1Claude530// Block G1 — register service worker for offline / install support.
531// Kept inline (and tiny) so we don't block first paint.
b1be050CC LABS App532//
533// Block S2 extension — when an updated SW activates we force a single
534// reload so the user picks up the fresh HTML immediately instead of
535// being stuck on the previous deploy's cached page (the "I saw /login's
536// old version even after merging" bug that triggered S2 in the first
537// place). The reload only fires when `controller` is already set, which
538// means this is NOT the first-ever install (no double-load on first
539// visit). A page-scoped guard prevents reload loops if the SW updates
540// twice in quick succession.
eae38d1Claude541const pwaRegisterScript = `
542 if ('serviceWorker' in navigator) {
543 window.addEventListener('load', function(){
b1be050CC LABS App544 navigator.serviceWorker.register('/sw.js').then(function(reg){
545 var reloaded = false;
546 reg.addEventListener('updatefound', function(){
547 var newSW = reg.installing;
548 if (!newSW) return;
549 newSW.addEventListener('statechange', function(){
550 if (newSW.state === 'activated' && navigator.serviceWorker.controller && !reloaded) {
551 reloaded = true;
552 window.location.reload();
553 }
554 });
555 });
556 }).catch(function(){});
eae38d1Claude557 });
558 }
559`;
560
534f04aClaude561// Block M2 — smart install banner (authed users only). Three heuristics:
562// 1. user is authenticated (gated server-side via the `{user &&}` JSX)
563// 2. visit counter (localStorage) ≥ 3
564// 3. dismissed-cooldown < 14 days ago
565// Also bootstraps the push + offline SW at /sw-push.js. Idempotent — safe
566// to load on every page; only inserts DOM nodes if all conditions match.
567const pwaInstallBannerScript = `
568(function(){
569 try {
570 var DISMISS_KEY = 'gc:pwa-banner-dismissed-at';
571 var VISITS_KEY = 'gc:pwa-visit-count';
572 var DISMISS_MS = 14 * 24 * 60 * 60 * 1000;
573 var visits = parseInt(localStorage.getItem(VISITS_KEY) || '0', 10) || 0;
574 visits++;
575 try { localStorage.setItem(VISITS_KEY, String(visits)); } catch(_){}
576 // Bootstrap the push + offline SW (separate from /sw.js's locked behaviour).
577 if ('serviceWorker' in navigator) {
578 window.addEventListener('load', function(){
579 navigator.serviceWorker.register('/sw-push.js', { scope: '/' })
580 .catch(function(){});
581 });
582 }
583 var deferredPrompt = null;
584 window.addEventListener('beforeinstallprompt', function(e){
585 e.preventDefault();
586 deferredPrompt = e;
587 maybeShow();
588 });
589 function maybeShow(){
590 if (!deferredPrompt) return;
591 if (visits < 3) return;
592 var dismissedAt = parseInt(localStorage.getItem(DISMISS_KEY) || '0', 10) || 0;
593 if (dismissedAt && (Date.now() - dismissedAt) < DISMISS_MS) return;
594 if (document.getElementById('gc-install-banner')) return;
595 var bar = document.createElement('div');
596 bar.id = 'gc-install-banner';
597 bar.setAttribute('role', 'dialog');
598 bar.setAttribute('aria-label', 'Install Gluecron');
599 bar.style.cssText = 'position:fixed;left:12px;right:12px;bottom:12px;z-index:9997;'
600 + 'background:var(--bg-elevated,#161b22);color:var(--text,#c9d1d9);'
601 + 'border:1px solid var(--border,#30363d);border-radius:8px;padding:12px 14px;'
602 + 'display:flex;gap:10px;align-items:center;justify-content:space-between;'
603 + 'box-shadow:0 8px 24px rgba(0,0,0,0.45);font-size:13px;line-height:1.3;'
604 + 'max-width:560px;margin:0 auto';
605 bar.innerHTML = '<span style="flex:1">'
606 + '<strong>Install Gluecron</strong> to keep working when offline + get push notifications.'
607 + '</span>'
608 + '<button type="button" id="gc-install-go" style="background:#238636;color:#fff;'
609 + 'border:0;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;'
610 + 'font-family:inherit">Install</button>'
611 + '<button type="button" id="gc-install-no" style="background:transparent;color:inherit;'
612 + 'border:1px solid var(--border,#30363d);border-radius:6px;padding:6px 12px;'
613 + 'font-size:12px;cursor:pointer;font-family:inherit">Not now</button>';
614 document.body.appendChild(bar);
615 var go = bar.querySelector('#gc-install-go');
616 var no = bar.querySelector('#gc-install-no');
617 if (go) go.addEventListener('click', function(){
618 try { deferredPrompt.prompt(); } catch(_){}
619 bar.parentNode && bar.parentNode.removeChild(bar);
620 deferredPrompt = null;
621 });
622 if (no) no.addEventListener('click', function(){
623 try { localStorage.setItem(DISMISS_KEY, String(Date.now())); } catch(_){}
624 bar.parentNode && bar.parentNode.removeChild(bar);
625 });
626 }
627 } catch(_) {}
628})();
629`;
630
3ef4c9dClaude631const navScript = `
632 (function(){
633 var chord = null;
634 var chordTimer = null;
635 function isTyping(t){
636 t = t || {};
637 var tag = (t.tagName || '').toLowerCase();
638 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
639 }
71cd5ecClaude640
641 // ---------- Block I4 — Command palette ----------
642 var COMMANDS = [
643 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
644 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
645 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
646 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
647 { label: 'Create new repository', href: '/new', kw: 'add create' },
648 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
649 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
650 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
651 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
652 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
653 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
654 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
655 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
656 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
657 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
658 { label: 'Gists', href: '/gists', kw: 'snippets' },
659 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
660 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
661 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
662 ];
663
664 function fuzzyMatch(item, q){
665 if (!q) return true;
666 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
667 q = q.toLowerCase();
668 var qi = 0;
669 for (var i = 0; i < hay.length && qi < q.length; i++) {
670 if (hay[i] === q[qi]) qi++;
671 }
672 return qi === q.length;
673 }
674
675 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
676
677 function render(){
678 if (!list) return;
679 var html = '';
680 for (var i = 0; i < filtered.length; i++) {
681 var item = filtered[i];
682 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
683 var bg = i === selected ? 'background:var(--bg);' : '';
ea52715copilot-swe-agent[bot]684 html += '<div class="' + cls + '" data-idx="' + i + '" data-url="' + item.href + '"' +
dc26881CC LABS App685 ' style="padding:var(--space-2) var(--space-4);cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
71cd5ecClaude686 '<div>' + item.label + '</div>' +
687 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
688 '</div>';
689 }
690 if (filtered.length === 0) {
dc26881CC LABS App691 html = '<div style="padding:var(--space-4);color:var(--text-muted);text-align:center">No matches.</div>';
71cd5ecClaude692 }
693 list.innerHTML = html;
694 }
695
696 function openPalette(){
697 backdrop = document.getElementById('cmdk-backdrop');
698 panel = document.getElementById('cmdk-panel');
699 input = document.getElementById('cmdk-input');
700 list = document.getElementById('cmdk-list');
701 if (!backdrop || !panel) return;
702 backdrop.style.display = 'block';
703 panel.style.display = 'block';
704 input.value = '';
705 selected = 0;
706 filtered = COMMANDS.slice();
707 render();
708 input.focus();
709 }
710 function closePalette(){
711 if (backdrop) backdrop.style.display = 'none';
712 if (panel) panel.style.display = 'none';
713 }
714 function go(href){ closePalette(); window.location.href = href; }
715
716 document.addEventListener('click', function(e){
717 var t = e.target;
718 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
719 var item = t && t.closest && t.closest('.cmdk-item');
ea52715copilot-swe-agent[bot]720 if (item) { go(item.getAttribute('data-url')); }
71cd5ecClaude721 });
722
723 document.addEventListener('input', function(e){
724 if (e.target && e.target.id === 'cmdk-input') {
725 var q = e.target.value;
726 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
727 selected = 0;
728 render();
729 }
730 });
731
3ef4c9dClaude732 document.addEventListener('keydown', function(e){
71cd5ecClaude733 // Palette-scoped keys take priority when open
734 if (panel && panel.style.display === 'block') {
735 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
736 if (e.key === 'ArrowDown') {
737 e.preventDefault();
738 selected = Math.min(filtered.length - 1, selected + 1);
739 render();
740 return;
741 }
742 if (e.key === 'ArrowUp') {
743 e.preventDefault();
744 selected = Math.max(0, selected - 1);
745 render();
746 return;
747 }
748 if (e.key === 'Enter') {
749 e.preventDefault();
750 var item = filtered[selected];
751 if (item) go(item.href);
752 return;
753 }
754 return;
755 }
756
3ef4c9dClaude757 if (isTyping(e.target)) return;
758 // Single key shortcuts
759 if (e.key === '/') {
760 var el = document.querySelector('.nav-search input');
761 if (el) { e.preventDefault(); el.focus(); return; }
762 }
763 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude764 e.preventDefault();
765 openPalette();
766 return;
3ef4c9dClaude767 }
768 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
769 e.preventDefault(); window.location.href = '/shortcuts'; return;
770 }
771 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
772 e.preventDefault(); window.location.href = '/new'; return;
773 }
774 // "g" chord
775 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
776 chord = 'g';
777 clearTimeout(chordTimer);
778 chordTimer = setTimeout(function(){ chord = null; }, 1200);
779 return;
780 }
781 if (chord === 'g') {
782 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
783 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
784 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
785 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
786 chord = null;
787 }
788 });
789 })();
790`;
791
fc1817aClaude792const css = `
2ce1d0bClaude793 /* ================================================================
958d26aClaude794 * Gluecron design system — 2026.05 "Editorial-Technical"
795 * Slate-noir base · refined violet signature · hairline geometry ·
796 * mono-as-feature · cinematic motion · Inter Tight + JetBrains Mono.
797 * All class names preserved for back-compat across 50+ route views.
2ce1d0bClaude798 * ============================================================== */
6fc53bdClaude799 :root, :root[data-theme='dark'] {
958d26aClaude800 /* Surfaces — slate, not black. More depth, less crush. */
801 --bg: #08090f;
802 --bg-secondary: #0c0d14;
803 --bg-tertiary: #11131c;
804 --bg-elevated: #0f111a;
805 --bg-surface: #161826;
806 --bg-hover: rgba(255,255,255,0.04);
807 --bg-active: rgba(255,255,255,0.08);
808 --bg-inset: rgba(0,0,0,0.30);
809
810 /* Borders — three weights, used deliberately */
811 --border: rgba(255,255,255,0.06);
812 --border-subtle: rgba(255,255,255,0.035);
813 --border-strong: rgba(255,255,255,0.13);
814 --border-focus: rgba(140,109,255,0.55);
815
816 /* Text */
817 --text: #ededf2;
818 --text-strong: #f7f7fb;
819 --text-muted: #8b8c9c;
820 --text-faint: #555665;
821 --text-link: #b69dff;
822
823 /* Accent — refined violet (less candy), warm amber as secondary signal */
824 --accent: #8c6dff;
825 --accent-2: #36c5d6;
826 --accent-warm: #ffb45e;
827 --accent-hover: #a48bff;
828 --accent-pressed:#7559e8;
829 --accent-gradient: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
830 --accent-gradient-soft: linear-gradient(135deg, rgba(140,109,255,0.18) 0%, rgba(54,197,214,0.18) 100%);
831 --accent-gradient-faint: linear-gradient(135deg, rgba(140,109,255,0.07) 0%, rgba(54,197,214,0.07) 100%);
832 --accent-glow: 0 0 24px rgba(140,109,255,0.28);
833
834 /* Semantic */
835 --green: #34d399;
836 --red: #f87171;
837 --yellow: #fbbf24;
838 --amber: #fbbf24;
839 --blue: #60a5fa;
840
fb71554Claude841 /* Type — system fonts FIRST so we never depend on Google Fonts loading.
842 Segoe UI (Win), -apple-system / SF (Mac), Roboto (Android), Inter as
843 optional upgrade if the user already has it. NEVER falls back to serif. */
844 --font-mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'Cascadia Mono', Menlo, Consolas, 'Courier New', monospace;
845 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, 'Inter', sans-serif;
846 --font-display: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, 'Inter Tight', 'Inter', sans-serif;
958d26aClaude847 --mono-feat: 'calt', 'liga', 'ss01';
848
849 /* Radius — sharper than before */
850 --r-sm: 5px;
851 --r: 7px;
852 --r-md: 9px;
853 --r-lg: 12px;
854 --r-xl: 16px;
855 --r-2xl: 22px;
856 --r-full: 9999px;
857 --radius: 7px;
858
859 /* Type scale — bigger display sizes for editorial feel */
860 --t-xs: 11px;
861 --t-sm: 13px;
862 --t-base: 14px;
863 --t-md: 16px;
864 --t-lg: 20px;
865 --t-xl: 28px;
866 --t-2xl: 40px;
867 --t-3xl: 56px;
868 --t-display: 72px;
869 --t-display-lg:96px;
870
871 /* Spacing — 4px base */
2ce1d0bClaude872 --s-0: 0;
958d26aClaude873 --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
874 --s-5: 20px; --s-6: 24px; --s-7: 28px; --s-8: 32px;
875 --s-10:40px; --s-12:48px; --s-14:56px; --s-16:64px;
876 --s-20:80px; --s-24:96px; --s-32:128px;
877
878 /* Elevation — softer + more layered */
879 --elev-0: 0 0 0 1px var(--border);
880 --elev-1: 0 1px 2px rgba(0,0,0,0.50), 0 0 0 1px var(--border);
881 --elev-2: 0 8px 24px -8px rgba(0,0,0,0.60), 0 0 0 1px var(--border);
882 --elev-3: 0 20px 48px -12px rgba(0,0,0,0.70), 0 0 0 1px var(--border-strong);
883 --elev-glow: 0 0 0 1px rgba(140,109,255,0.40), 0 0 32px -4px rgba(140,109,255,0.30);
884 --ring: 0 0 0 3px rgba(140,109,255,0.28);
885 --ring-warn: 0 0 0 3px rgba(251,191,36,0.28);
886 --ring-err: 0 0 0 3px rgba(248,113,113,0.28);
887
888 /* Motion */
889 --ease: cubic-bezier(0.16, 1, 0.3, 1);
890 --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
891 --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
892 --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
893 --t-fast: 120ms;
894 --t-base: 200ms;
895 --t-slow: 360ms;
896 --t-slower:560ms;
897
898 --header-h: 60px;
c63b860Claude899
900 /* Block O3 — visual coherence: named token aliases (additive). */
901 --space-1: var(--s-1);
902 --space-2: var(--s-2);
903 --space-3: var(--s-3);
904 --space-4: var(--s-4);
905 --space-5: var(--s-5);
906 --space-6: var(--s-6);
907 --space-8: var(--s-8);
908 --space-10: var(--s-10);
909 --space-12: var(--s-12);
910 --space-16: var(--s-16);
911 --space-20: var(--s-20);
912 --space-24: var(--s-24);
913 --radius-sm: var(--r-sm);
914 --radius-md: var(--r-md);
915 --radius-lg: var(--r-lg);
916 --radius-xl: var(--r-xl);
917 --radius-full: var(--r-full);
918 --font-size-xs: var(--t-xs);
919 --font-size-sm: var(--t-sm);
920 --font-size-base: var(--t-base);
921 --font-size-md: var(--t-md);
922 --font-size-lg: var(--t-lg);
923 --font-size-xl: var(--t-xl);
924 --font-size-2xl: var(--t-2xl);
925 --font-size-3xl: var(--t-3xl);
926 --font-size-hero: var(--t-display);
927 --leading-tight: 1.2;
928 --leading-snug: 1.35;
929 --leading-normal: 1.5;
930 --leading-relaxed: 1.6;
931 --leading-loose: 1.7;
932 --z-base: 1;
933 --z-nav: 10;
934 --z-sticky: 50;
935 --z-overlay: 100;
936 --z-modal: 1000;
937 --z-toast: 10000;
fc1817aClaude938 }
939
6fc53bdClaude940 :root[data-theme='light'] {
958d26aClaude941 --bg: #fbfbfc;
4c47454Claude942 --bg-secondary: #ffffff;
958d26aClaude943 --bg-tertiary: #f3f3f6;
944 --bg-elevated: #ffffff;
945 --bg-surface: #f6f6f9;
946 --bg-hover: rgba(0,0,0,0.035);
947 --bg-active: rgba(0,0,0,0.07);
948 --bg-inset: rgba(0,0,0,0.04);
949
950 --border: rgba(15,16,28,0.08);
951 --border-subtle: rgba(15,16,28,0.04);
952 --border-strong: rgba(15,16,28,0.16);
953
954 --text: #0e1020;
955 --text-strong: #050617;
956 --text-muted: #5a5b70;
957 --text-faint: #8a8b9e;
958 --text-link: #6d4dff;
959
960 --accent: #6d4dff;
961 --accent-2: #0891b2;
962 --accent-hover: #5a3df0;
963 --accent-pressed:#4a30d6;
964 --accent-glow: 0 0 24px rgba(109,77,255,0.18);
965
966 --green: #059669;
967 --red: #dc2626;
4c47454Claude968 --yellow: #d97706;
958d26aClaude969
970 --elev-1: 0 1px 2px rgba(15,16,28,0.06), 0 0 0 1px var(--border);
971 --elev-2: 0 8px 24px -10px rgba(15,16,28,0.10), 0 0 0 1px var(--border);
972 --elev-3: 0 20px 48px -16px rgba(15,16,28,0.14), 0 0 0 1px var(--border-strong);
6fc53bdClaude973 }
974
975 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
958d26aClaude976 .nav-theme { display: inline-flex; align-items: center; font-size: 15px; line-height: 1; opacity: 0.85; }
977 .nav-theme:hover { opacity: 1; }
6fc53bdClaude978 :root[data-theme='dark'] .theme-icon-dark { display: none; }
979 :root[data-theme='light'] .theme-icon-light { display: none; }
980
fc1817aClaude981 * { margin: 0; padding: 0; box-sizing: border-box; }
958d26aClaude982 *::selection { background: rgba(140,109,255,0.32); color: var(--text-strong); }
2ce1d0bClaude983
958d26aClaude984 html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
fc1817aClaude985
986 body {
987 font-family: var(--font-sans);
988 background: var(--bg);
989 color: var(--text);
2ce1d0bClaude990 line-height: 1.55;
958d26aClaude991 letter-spacing: -0.011em;
fc1817aClaude992 min-height: 100vh;
993 display: flex;
994 flex-direction: column;
958d26aClaude995 font-feature-settings: 'cv11', 'ss01', 'ss03', 'calt';
fc1817aClaude996 }
997
958d26aClaude998 /* Whole-page atmosphere: very subtle gradient + dot-grid layered behind everything.
999 Keeps every page feeling like part of the same product without competing with hero art. */
2ce1d0bClaude1000 body::before {
1001 content: '';
1002 position: fixed;
1003 inset: 0;
1004 pointer-events: none;
958d26aClaude1005 z-index: -2;
2ce1d0bClaude1006 background:
958d26aClaude1007 radial-gradient(70% 55% at 85% -20%, rgba(140,109,255,0.07), transparent 65%),
1008 radial-gradient(55% 45% at -10% 115%, rgba(54,197,214,0.05), transparent 65%);
1009 }
1010 body::after {
1011 content: '';
1012 position: fixed;
1013 inset: 0;
1014 pointer-events: none;
1015 z-index: -1;
1016 background-image: radial-gradient(rgba(255,255,255,0.025) 1px, transparent 1px);
1017 background-size: 28px 28px;
1018 background-position: 0 0;
1019 opacity: 0.55;
1020 mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 70%);
1021 -webkit-mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 70%);
1022 }
1023 :root[data-theme='light'] body::before { opacity: 0.55; }
1024 :root[data-theme='light'] body::after {
1025 background-image: radial-gradient(rgba(15,16,28,0.06) 1px, transparent 1px);
1026 opacity: 0.4;
fc1817aClaude1027 }
2ce1d0bClaude1028
1029 a { color: var(--text-link); text-decoration: none; transition: color var(--t-fast) var(--ease); }
1030 a:hover { color: var(--accent-hover); text-decoration: none; }
fc1817aClaude1031
958d26aClaude1032 /* Heading scale — confident, editorial, tight tracking */
1033 h1, h2, h3, h4, h5, h6 {
1034 font-family: var(--font-display);
2ce1d0bClaude1035 font-weight: 600;
958d26aClaude1036 letter-spacing: -0.022em;
1037 line-height: 1.18;
1038 color: var(--text-strong);
1039 }
1040 h1 { font-size: var(--t-xl); letter-spacing: -0.028em; }
1041 h2 { font-size: var(--t-lg); letter-spacing: -0.022em; }
1042 h3 { font-size: var(--t-md); font-weight: 600; letter-spacing: -0.015em; }
1043 h4 { font-size: var(--t-base); font-weight: 600; letter-spacing: -0.01em; }
1044 h5, h6 { font-size: var(--t-sm); font-weight: 600; letter-spacing: -0.005em; }
1045
1046 /* Editorial display heading utility — used by landing + marketing pages */
1047 .display {
1048 font-family: var(--font-display);
1049 font-size: clamp(40px, 7.5vw, 96px);
1050 line-height: 0.98;
1051 letter-spacing: -0.04em;
1052 font-weight: 600;
1053 color: var(--text-strong);
2ce1d0bClaude1054 }
fc1817aClaude1055
958d26aClaude1056 /* Eyebrow — uppercase mono label that sits above section headings */
1057 .eyebrow {
1058 display: inline-flex;
1059 align-items: center;
1060 gap: 8px;
1061 font-family: var(--font-mono);
1062 font-size: 11px;
1063 font-weight: 500;
1064 letter-spacing: 0.14em;
1065 text-transform: uppercase;
1066 color: var(--accent);
1067 margin-bottom: var(--s-3);
1068 }
1069 .eyebrow::before {
1070 content: '';
1071 width: 18px;
1072 height: 1px;
1073 background: currentColor;
1074 opacity: 0.6;
1075 }
1076
1077 /* Section header — paired eyebrow + title + lede */
1078 .section-header { max-width: 720px; margin: 0 auto var(--s-10); text-align: center; }
1079 .section-header.left { text-align: left; margin-left: 0; margin-right: auto; }
1080 .section-header h2 {
1081 font-size: clamp(28px, 4vw, 44px);
1082 line-height: 1.05;
1083 letter-spacing: -0.028em;
1084 margin-bottom: var(--s-3);
1085 }
1086 .section-header p {
1087 color: var(--text-muted);
1088 font-size: var(--t-md);
1089 line-height: 1.6;
1090 max-width: 580px;
1091 margin: 0 auto;
1092 }
1093 .section-header.left p { margin-left: 0; }
fc1817aClaude1094
958d26aClaude1095 code, kbd, samp {
2ce1d0bClaude1096 font-family: var(--font-mono);
958d26aClaude1097 font-feature-settings: var(--mono-feat);
1098 }
1099 code {
1100 font-size: 0.9em;
2ce1d0bClaude1101 background: var(--bg-tertiary);
958d26aClaude1102 border: 1px solid var(--border-subtle);
2ce1d0bClaude1103 padding: 1px 6px;
1104 border-radius: var(--r-sm);
1105 color: var(--text);
1106 }
958d26aClaude1107 kbd, .kbd {
1108 display: inline-flex;
1109 align-items: center;
1110 padding: 1px 6px;
1111 font-family: var(--font-mono);
1112 font-size: 11px;
1113 background: var(--bg-elevated);
1114 border: 1px solid var(--border);
1115 border-bottom-width: 2px;
1116 border-radius: 4px;
1117 color: var(--text);
1118 line-height: 1.5;
1119 vertical-align: middle;
1120 }
2ce1d0bClaude1121
958d26aClaude1122 /* Mono utility for technical chrome (paths, IDs, dates) */
1123 .mono { font-family: var(--font-mono); font-feature-settings: var(--mono-feat); font-size: 0.96em; }
1124 .meta-mono { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--text-muted); letter-spacing: 0; }
1125
1126 /* Pre-launch banner — slim, refined, mono caption */
4a52a98Claude1127 .prelaunch-banner {
958d26aClaude1128 position: relative;
2ce1d0bClaude1129 background:
958d26aClaude1130 linear-gradient(180deg, rgba(251,191,36,0.10), rgba(251,191,36,0.03)),
2ce1d0bClaude1131 var(--bg);
958d26aClaude1132 border-bottom: 1px solid rgba(251,191,36,0.28);
4a52a98Claude1133 color: var(--yellow);
958d26aClaude1134 padding: 7px 24px;
1135 font-family: var(--font-mono);
1136 font-size: 11px;
4a52a98Claude1137 font-weight: 500;
1138 text-align: center;
2ce1d0bClaude1139 line-height: 1.5;
958d26aClaude1140 letter-spacing: 0.04em;
1141 text-transform: uppercase;
1142 }
1143 .prelaunch-banner::before {
1144 content: '◆';
1145 margin-right: 8px;
1146 font-size: 9px;
1147 opacity: 0.7;
1148 vertical-align: 1px;
4a52a98Claude1149 }
1150
cd4f63bTest User1151 /* Block Q3 — Playground banner. Brighter than the prelaunch strip so
1152 visitors don't miss the "save your work" CTA, but slim enough to
1153 not feel like a modal. */
1154 .playground-banner {
1155 position: relative;
1156 display: flex;
1157 align-items: center;
1158 justify-content: center;
1159 gap: 8px;
1160 background:
1161 linear-gradient(180deg, rgba(251,191,36,0.20), rgba(251,191,36,0.06)),
1162 var(--bg);
1163 border-bottom: 1px solid rgba(251,191,36,0.45);
1164 color: var(--yellow, #fbbf24);
1165 padding: 8px 40px 8px 24px;
1166 font-size: 13px;
1167 font-weight: 500;
1168 text-align: center;
1169 line-height: 1.4;
1170 }
1171 .playground-banner-icon { font-size: 14px; }
1172 .playground-banner-text { color: var(--text-strong, #e6edf3); }
1173 .playground-banner-countdown { font-weight: 600; }
1174 .playground-banner-cta {
1175 margin-left: 4px;
1176 color: var(--yellow, #fbbf24);
1177 text-decoration: underline;
1178 font-weight: 600;
1179 }
1180 .playground-banner-cta:hover { opacity: 0.85; }
1181 .playground-banner-dismiss {
1182 position: absolute;
1183 top: 50%;
1184 right: 12px;
1185 transform: translateY(-50%);
1186 background: transparent;
1187 border: none;
1188 color: var(--text-muted, #8b949e);
1189 font-size: 18px;
1190 line-height: 1;
1191 cursor: pointer;
1192 padding: 0 4px;
1193 }
1194 .playground-banner-dismiss:hover { color: var(--text-strong, #e6edf3); }
1195
958d26aClaude1196 /* Header — sticky, blurred, hairline border, taller for breathing room */
fc1817aClaude1197 header {
2ce1d0bClaude1198 position: sticky;
1199 top: 0;
1200 z-index: 100;
fc1817aClaude1201 border-bottom: 1px solid var(--border);
2ce1d0bClaude1202 padding: 0 24px;
1203 height: var(--header-h);
958d26aClaude1204 background: rgba(8,9,15,0.72);
1205 backdrop-filter: saturate(180%) blur(18px);
1206 -webkit-backdrop-filter: saturate(180%) blur(18px);
fc1817aClaude1207 }
958d26aClaude1208 :root[data-theme='light'] header { background: rgba(251,251,252,0.78); }
fc1817aClaude1209
06d5ffeClaude1210 header nav {
1211 display: flex;
1212 align-items: center;
958d26aClaude1213 gap: 18px;
1214 max-width: 1240px;
06d5ffeClaude1215 margin: 0 auto;
2ce1d0bClaude1216 height: 100%;
1217 }
1218 .logo {
958d26aClaude1219 font-family: var(--font-display);
1220 font-size: 16px;
2ce1d0bClaude1221 font-weight: 700;
958d26aClaude1222 letter-spacing: -0.025em;
1223 color: var(--text-strong);
2ce1d0bClaude1224 display: inline-flex;
1225 align-items: center;
958d26aClaude1226 gap: 9px;
1227 transition: opacity var(--t-fast) var(--ease);
06d5ffeClaude1228 }
2ce1d0bClaude1229 .logo::before {
1230 content: '';
958d26aClaude1231 width: 20px; height: 20px;
1232 border-radius: 6px;
2ce1d0bClaude1233 background: var(--accent-gradient);
958d26aClaude1234 box-shadow:
1235 inset 0 1px 0 rgba(255,255,255,0.25),
1236 0 0 0 1px rgba(140,109,255,0.45),
1237 0 0 20px rgba(140,109,255,0.30);
2ce1d0bClaude1238 flex-shrink: 0;
958d26aClaude1239 transition: transform var(--t-base) var(--ease-spring), box-shadow var(--t-base) var(--ease);
1240 }
1241 .logo:hover { text-decoration: none; color: var(--text-strong); }
1242 .logo:hover::before {
1243 transform: rotate(8deg) scale(1.05);
1244 box-shadow:
1245 inset 0 1px 0 rgba(255,255,255,0.30),
1246 0 0 0 1px rgba(140,109,255,0.55),
1247 0 0 28px rgba(140,109,255,0.45);
06d5ffeClaude1248 }
fc1817aClaude1249
2ce1d0bClaude1250 .nav-search {
1251 flex: 1;
958d26aClaude1252 max-width: 360px;
1253 margin: 0 4px 0 8px;
1254 position: relative;
2ce1d0bClaude1255 }
1256 .nav-search input {
1257 width: 100%;
958d26aClaude1258 padding: 7px 12px 7px 32px;
2ce1d0bClaude1259 background: var(--bg-tertiary);
1260 border: 1px solid var(--border);
1261 border-radius: var(--r-sm);
1262 color: var(--text);
1263 font-family: var(--font-sans);
1264 font-size: var(--t-sm);
958d26aClaude1265 transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
1266 }
1267 .nav-search::before {
1268 content: '';
1269 position: absolute;
1270 left: 11px; top: 50%;
1271 transform: translateY(-50%);
1272 width: 12px; height: 12px;
1273 border: 1.5px solid var(--text-faint);
1274 border-radius: 50%;
1275 pointer-events: none;
1276 }
1277 .nav-search::after {
1278 content: '';
1279 position: absolute;
1280 left: 19px; top: calc(50% + 4px);
1281 width: 5px; height: 1.5px;
1282 background: var(--text-faint);
1283 transform: rotate(45deg);
1284 pointer-events: none;
2ce1d0bClaude1285 }
1286 .nav-search input::placeholder { color: var(--text-faint); }
1287 .nav-search input:focus {
1288 outline: none;
1289 background: var(--bg-secondary);
958d26aClaude1290 border-color: var(--border-focus);
1291 box-shadow: var(--ring);
2ce1d0bClaude1292 }
06d5ffeClaude1293
958d26aClaude1294 .nav-right { display: flex; align-items: center; gap: 2px; margin-left: auto; }
f764c07Claude1295
1296 /* Block N3 — site-admin deploy status pill */
1297 .nav-deploy-pill {
1298 display: inline-flex;
1299 align-items: center;
1300 gap: 6px;
1301 padding: 4px 10px;
1302 margin: 0 6px 0 0;
1303 border-radius: 9999px;
1304 font-size: 11px;
1305 font-weight: 600;
1306 line-height: 1;
1307 color: var(--text-strong);
1308 background: var(--bg-elevated);
1309 border: 1px solid var(--border);
1310 text-decoration: none;
1311 transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
1312 }
1313 .nav-deploy-pill:hover { background: var(--bg-hover); text-decoration: none; }
1314 .nav-deploy-pill .deploy-pill-dot {
1315 display: inline-block;
1316 width: 8px; height: 8px;
1317 border-radius: 50%;
1318 background: #6b7280;
1319 }
1320 .deploy-pill-success .deploy-pill-dot { background: #34d399; box-shadow: 0 0 6px rgba(52,211,153,0.45); }
1321 .deploy-pill-failed .deploy-pill-dot { background: #f87171; box-shadow: 0 0 6px rgba(248,113,113,0.45); }
1322 .deploy-pill-failed { border-color: rgba(248,113,113,0.4); }
1323 .deploy-pill-progress .deploy-pill-dot {
1324 background: #fbbf24;
1325 box-shadow: 0 0 6px rgba(251,191,36,0.55);
1326 animation: deployPillPulse 1.2s ease-in-out infinite;
1327 }
1328 @keyframes deployPillPulse {
1329 0%, 100% { opacity: 1; transform: scale(1); }
1330 50% { opacity: 0.45; transform: scale(0.8); }
1331 }
1332 .deploy-pill-empty .deploy-pill-dot { background: #9ca3af; }
1333
2ce1d0bClaude1334 .nav-link {
958d26aClaude1335 position: relative;
2ce1d0bClaude1336 color: var(--text-muted);
1337 font-size: var(--t-sm);
1338 font-weight: 500;
958d26aClaude1339 padding: 7px 11px;
2ce1d0bClaude1340 border-radius: var(--r-sm);
958d26aClaude1341 transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
2ce1d0bClaude1342 }
1343 .nav-link:hover {
958d26aClaude1344 color: var(--text-strong);
2ce1d0bClaude1345 background: var(--bg-hover);
1346 text-decoration: none;
1347 }
958d26aClaude1348 .nav-link.active { color: var(--text-strong); }
1349 .nav-link.active::after {
1350 content: '';
1351 position: absolute;
1352 left: 11px; right: 11px;
1353 bottom: -1px;
1354 height: 2px;
1355 background: var(--accent-gradient);
1356 border-radius: 2px;
1357 }
2ce1d0bClaude1358 .nav-user {
958d26aClaude1359 color: var(--text-strong);
2ce1d0bClaude1360 font-weight: 600;
1361 font-size: var(--t-sm);
1362 padding: 6px 10px;
1363 border-radius: var(--r-sm);
958d26aClaude1364 margin-left: 6px;
2ce1d0bClaude1365 transition: background var(--t-fast) var(--ease);
1366 }
958d26aClaude1367 .nav-user::before {
1368 content: '';
1369 display: inline-block;
1370 width: 8px; height: 8px;
1371 background: var(--green);
1372 border-radius: 50%;
1373 margin-right: 7px;
1374 box-shadow: 0 0 8px rgba(52,211,153,0.5);
1375 vertical-align: 1px;
1376 }
2ce1d0bClaude1377 .nav-user:hover { background: var(--bg-hover); text-decoration: none; }
fc1817aClaude1378
2ce1d0bClaude1379 main {
958d26aClaude1380 max-width: 1240px;
2ce1d0bClaude1381 margin: 0 auto;
958d26aClaude1382 padding: 36px 24px 80px;
2ce1d0bClaude1383 flex: 1;
1384 width: 100%;
1385 }
fc1817aClaude1386
958d26aClaude1387 /* Editorial footer: link grid + tagline row */
fc1817aClaude1388 footer {
1389 border-top: 1px solid var(--border);
958d26aClaude1390 padding: 56px 24px 40px;
fc1817aClaude1391 color: var(--text-muted);
958d26aClaude1392 font-size: var(--t-sm);
1393 background:
1394 linear-gradient(180deg, transparent 0%, rgba(140,109,255,0.025) 100%),
1395 var(--bg);
1396 }
1397 footer .footer-inner {
1398 max-width: 1240px;
1399 margin: 0 auto;
1400 display: grid;
1401 grid-template-columns: 1fr auto;
1402 gap: 48px;
1403 align-items: start;
1404 }
1405 footer .footer-brand .logo { margin-bottom: var(--s-3); }
1406 footer .footer-tag {
1407 color: var(--text-muted);
1408 font-size: var(--t-sm);
1409 max-width: 320px;
1410 line-height: 1.55;
1411 }
1412 footer .footer-links {
1413 display: grid;
1414 grid-template-columns: repeat(3, minmax(120px, auto));
1415 gap: 0 56px;
1416 }
1417 footer .footer-col-title {
1418 font-family: var(--font-mono);
1419 font-size: 11px;
1420 text-transform: uppercase;
1421 letter-spacing: 0.14em;
1422 color: var(--text-faint);
1423 margin-bottom: var(--s-3);
1424 }
1425 footer .footer-col a {
1426 display: block;
1427 color: var(--text-muted);
1428 font-size: var(--t-sm);
1429 padding: 5px 0;
1430 transition: color var(--t-fast) var(--ease);
1431 }
1432 footer .footer-col a:hover { color: var(--text-strong); text-decoration: none; }
1433 footer .footer-bottom {
1434 max-width: 1240px;
1435 margin: 40px auto 0;
1436 padding-top: 24px;
1437 border-top: 1px solid var(--border-subtle);
1438 display: flex;
1439 justify-content: space-between;
1440 align-items: center;
2ce1d0bClaude1441 color: var(--text-faint);
1442 font-size: var(--t-xs);
958d26aClaude1443 font-family: var(--font-mono);
1444 letter-spacing: 0.02em;
1445 }
1446 footer .footer-bottom a { color: var(--text-faint); }
1447 footer .footer-bottom a:hover { color: var(--text-muted); text-decoration: none; }
05cdb85Claude1448 footer .footer-build {
1449 display: inline-flex;
1450 align-items: center;
1451 gap: 6px;
1452 color: var(--text-faint);
1453 font-family: var(--font-mono);
1454 font-size: 11px;
1455 cursor: help;
1456 }
1457 footer .footer-build-dot {
1458 width: 6px; height: 6px;
1459 border-radius: 50%;
1460 background: var(--green);
1461 box-shadow: 0 0 6px rgba(52,211,153,0.55);
1462 animation: footer-build-pulse 2.4s ease-in-out infinite;
1463 }
1464 @keyframes footer-build-pulse {
1465 0%, 100% { opacity: 0.6; }
1466 50% { opacity: 1; }
1467 }
958d26aClaude1468 @media (max-width: 768px) {
1469 footer .footer-inner { grid-template-columns: 1fr; gap: 32px; }
1470 footer .footer-links { grid-template-columns: repeat(2, 1fr); gap: 24px 32px; }
1471 footer .footer-bottom { flex-direction: column; gap: 8px; text-align: center; }
fc1817aClaude1472 }
1473
2ce1d0bClaude1474 /* ============================================================ */
1475 /* Buttons */
1476 /* ============================================================ */
dc26881CC LABS App1477 /* ============================================================ */
1478 /* Buttons — Block U2 senior polish pass. */
1479 /* Rules: */
1480 /* · hover lifts every .btn by 1px + soft drop shadow (180ms) */
1481 /* · active presses back down to 0 (80ms — faster on press) */
1482 /* · focus-visible uses a soft box-shadow ring (no outline) */
1483 /* · primary gradient shifts position on hover for a slow */
1484 /* 600ms shimmer */
1485 /* · disabled never lifts and never animates */
1486 /* ============================================================ */
06d5ffeClaude1487 .btn {
1488 display: inline-flex;
1489 align-items: center;
2ce1d0bClaude1490 justify-content: center;
958d26aClaude1491 gap: 7px;
2ce1d0bClaude1492 padding: 7px 14px;
1493 border-radius: var(--r-sm);
958d26aClaude1494 font-family: var(--font-sans);
2ce1d0bClaude1495 font-size: var(--t-sm);
06d5ffeClaude1496 font-weight: 500;
1497 border: 1px solid var(--border);
2ce1d0bClaude1498 background: var(--bg-elevated);
06d5ffeClaude1499 color: var(--text);
1500 cursor: pointer;
1501 text-decoration: none;
958d26aClaude1502 line-height: 1.25;
1503 letter-spacing: -0.008em;
dc26881CC LABS App1504 /* U2 — hover/transform settles in 180ms; press snaps in 80ms.
1505 Listed once on the base rule so :active can override only the
1506 transform/duration without re-typing the colour/bg transitions. */
2ce1d0bClaude1507 transition:
dc26881CC LABS App1508 background 180ms ease,
1509 border-color 180ms ease,
1510 transform 180ms ease,
1511 box-shadow 180ms ease,
1512 color 180ms ease;
2ce1d0bClaude1513 user-select: none;
1514 white-space: nowrap;
958d26aClaude1515 position: relative;
06d5ffeClaude1516 }
2ce1d0bClaude1517 .btn:hover {
1518 background: var(--bg-surface);
1519 border-color: var(--border-strong);
958d26aClaude1520 color: var(--text-strong);
2ce1d0bClaude1521 text-decoration: none;
dc26881CC LABS App1522 /* U2 — universal hover lift + soft accent drop shadow. */
1523 transform: translateY(-1px);
1524 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.45);
1525 }
1526 .btn:active {
1527 transform: translateY(0);
1528 transition-duration: 80ms;
1529 }
1530 /* U2 — soft modern focus ring via box-shadow, not outline. */
1531 .btn:focus-visible {
1532 outline: none;
1533 box-shadow: 0 0 0 3px rgba(140, 109, 255, 0.35);
2ce1d0bClaude1534 }
1535
1536 .btn-primary {
1537 background: var(--accent-gradient);
dc26881CC LABS App1538 background-size: 200% 100%;
1539 background-position: 0% 50%;
2ce1d0bClaude1540 border-color: transparent;
1541 color: #fff;
1542 font-weight: 600;
958d26aClaude1543 text-shadow: 0 1px 0 rgba(0,0,0,0.15);
2ce1d0bClaude1544 box-shadow:
958d26aClaude1545 inset 0 1px 0 rgba(255,255,255,0.22),
1546 inset 0 -1px 0 rgba(0,0,0,0.10),
1547 0 1px 2px rgba(0,0,0,0.40),
1548 0 0 0 1px rgba(140,109,255,0.30);
dc26881CC LABS App1549 /* U2 — slower 600ms transition on background-position so the
1550 primary CTA shimmers when the cursor lands. */
1551 transition:
1552 background-position 600ms ease,
1553 transform 180ms ease,
1554 box-shadow 180ms ease,
1555 color 180ms ease;
06d5ffeClaude1556 }
958d26aClaude1557 .btn-primary::before {
1558 content: '';
1559 position: absolute;
1560 inset: 0;
1561 border-radius: inherit;
1562 background: linear-gradient(180deg, rgba(255,255,255,0.18), transparent 60%);
1563 opacity: 0;
1564 transition: opacity var(--t-fast) var(--ease);
1565 pointer-events: none;
2ce1d0bClaude1566 }
1567 .btn-primary:hover {
958d26aClaude1568 color: #fff;
2ce1d0bClaude1569 background: var(--accent-gradient);
dc26881CC LABS App1570 background-size: 200% 100%;
1571 background-position: 100% 50%;
958d26aClaude1572 border-color: transparent;
dc26881CC LABS App1573 transform: translateY(-1px);
2ce1d0bClaude1574 box-shadow:
958d26aClaude1575 inset 0 1px 0 rgba(255,255,255,0.30),
1576 inset 0 -1px 0 rgba(0,0,0,0.10),
1577 0 6px 18px -4px rgba(140,109,255,0.45),
1578 0 0 0 1px rgba(140,109,255,0.45);
2ce1d0bClaude1579 }
958d26aClaude1580 .btn-primary:hover::before { opacity: 1; }
dc26881CC LABS App1581 .btn-primary:active { transform: translateY(0); transition-duration: 80ms; }
1582 /* U2 — primary focus ring uses the same accent box-shadow recipe. */
1583 .btn-primary:focus-visible {
1584 box-shadow:
1585 inset 0 1px 0 rgba(255,255,255,0.22),
1586 0 0 0 3px rgba(140,109,255,0.35);
1587 }
2ce1d0bClaude1588
1589 .btn-danger {
1590 background: transparent;
958d26aClaude1591 border-color: rgba(248,113,113,0.40);
2ce1d0bClaude1592 color: var(--red);
1593 }
1594 .btn-danger:hover {
958d26aClaude1595 background: rgba(248,113,113,0.08);
2ce1d0bClaude1596 border-color: var(--red);
958d26aClaude1597 color: var(--red);
dc26881CC LABS App1598 transform: translateY(-1px);
1599 box-shadow: 0 4px 12px -4px rgba(248,113,113,0.30);
2ce1d0bClaude1600 }
dc26881CC LABS App1601 .btn-danger:focus-visible { box-shadow: 0 0 0 3px rgba(248,113,113,0.35); }
2ce1d0bClaude1602
1603 .btn-ghost {
1604 background: transparent;
1605 border-color: transparent;
1606 color: var(--text-muted);
1607 }
1608 .btn-ghost:hover {
1609 background: var(--bg-hover);
958d26aClaude1610 color: var(--text-strong);
2ce1d0bClaude1611 border-color: var(--border);
dc26881CC LABS App1612 transform: translateY(-1px);
1613 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.30);
2ce1d0bClaude1614 }
1615
958d26aClaude1616 .btn-secondary {
1617 background: var(--bg-elevated);
1618 border-color: var(--border-strong);
1619 color: var(--text-strong);
1620 }
1621 .btn-secondary:hover {
1622 background: var(--bg-surface);
1623 border-color: var(--border-strong);
dc26881CC LABS App1624 transform: translateY(-1px);
1625 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.35);
958d26aClaude1626 }
1627
1628 .btn-sm { padding: 4px 10px; font-size: var(--t-xs); border-radius: var(--r-sm); gap: 5px; }
1629 .btn-lg { padding: 11px 22px; font-size: var(--t-base); border-radius: var(--r); }
1630 .btn-xl { padding: 14px 28px; font-size: var(--t-md); border-radius: var(--r); font-weight: 600; }
1631 .btn-block { width: 100%; }
2ce1d0bClaude1632
dc26881CC LABS App1633 /* U2 — disabled never lifts, never shimmers, never glows. */
1634 .btn:disabled,
1635 .btn[aria-disabled='true'],
1636 .btn:disabled:hover,
1637 .btn[aria-disabled='true']:hover {
1638 opacity: 0.5;
2ce1d0bClaude1639 cursor: not-allowed;
1640 pointer-events: none;
dc26881CC LABS App1641 transform: none;
1642 box-shadow: none;
1643 }
1644 @media (prefers-reduced-motion: reduce) {
1645 .btn,
1646 .btn:hover,
1647 .btn:active,
1648 .btn-primary,
1649 .btn-primary:hover {
1650 transform: none;
1651 transition: background-color 80ms linear, color 80ms linear;
1652 }
2ce1d0bClaude1653 }
1654
1655 /* ============================================================ */
1656 /* Forms */
1657 /* ============================================================ */
1658 .form-group { margin-bottom: 20px; }
1659 .form-group label {
1660 display: block;
1661 font-size: var(--t-sm);
1662 font-weight: 500;
1663 margin-bottom: 6px;
1664 color: var(--text);
1665 letter-spacing: -0.005em;
1666 }
1667 .form-group input,
1668 .form-group textarea,
1669 .form-group select,
1670 input[type='text'], input[type='email'], input[type='password'],
1671 input[type='url'], input[type='search'], input[type='number'],
1672 textarea, select {
06d5ffeClaude1673 width: 100%;
2ce1d0bClaude1674 padding: 9px 12px;
1675 background: var(--bg-secondary);
06d5ffeClaude1676 border: 1px solid var(--border);
2ce1d0bClaude1677 border-radius: var(--r-sm);
06d5ffeClaude1678 color: var(--text);
2ce1d0bClaude1679 font-size: var(--t-sm);
06d5ffeClaude1680 font-family: var(--font-sans);
2ce1d0bClaude1681 transition:
1682 border-color var(--t-fast) var(--ease),
1683 background var(--t-fast) var(--ease),
1684 box-shadow var(--t-fast) var(--ease);
06d5ffeClaude1685 }
2ce1d0bClaude1686 .form-group input::placeholder, textarea::placeholder, input::placeholder {
1687 color: var(--text-faint);
06d5ffeClaude1688 }
2ce1d0bClaude1689 .form-group input:hover, textarea:hover, select:hover,
1690 input[type='text']:hover, input[type='email']:hover, input[type='password']:hover {
1691 border-color: var(--border-strong);
1692 }
1693 .form-group input:focus, .form-group textarea:focus, .form-group select:focus,
1694 input:focus, textarea:focus, select:focus {
06d5ffeClaude1695 outline: none;
2ce1d0bClaude1696 background: var(--bg);
1697 border-color: var(--border-focus);
1698 box-shadow: var(--ring);
06d5ffeClaude1699 }
2ce1d0bClaude1700 textarea { font-family: var(--font-mono); font-size: var(--t-sm); line-height: 1.55; }
06d5ffeClaude1701 .input-disabled { opacity: 0.5; cursor: not-allowed; }
1702
2ce1d0bClaude1703 /* ============================================================ */
1704 /* Auth (register / login / verify) */
1705 /* ============================================================ */
1706 .auth-container {
1707 max-width: 420px;
1708 margin: 64px auto;
1709 padding: 32px;
1710 background: var(--bg-elevated);
1711 border: 1px solid var(--border);
1712 border-radius: var(--r-lg);
1713 box-shadow: var(--elev-2);
1714 }
1715 .auth-container h2 {
1716 margin-bottom: 6px;
1717 font-size: var(--t-lg);
1718 letter-spacing: -0.02em;
1719 }
1720 .auth-container > p {
1721 color: var(--text-muted);
1722 font-size: var(--t-sm);
1723 margin-bottom: 24px;
1724 }
1725 .auth-container .btn-primary { width: 100%; padding: 10px 16px; }
06d5ffeClaude1726 .auth-error {
958d26aClaude1727 background: rgba(248,113,113,0.08);
1728 border: 1px solid rgba(248,113,113,0.35);
06d5ffeClaude1729 color: var(--red);
2ce1d0bClaude1730 padding: 10px 14px;
1731 border-radius: var(--r-sm);
06d5ffeClaude1732 margin-bottom: 16px;
2ce1d0bClaude1733 font-size: var(--t-sm);
06d5ffeClaude1734 }
1735 .auth-success {
958d26aClaude1736 background: rgba(52,211,153,0.08);
1737 border: 1px solid rgba(52,211,153,0.35);
06d5ffeClaude1738 color: var(--green);
2ce1d0bClaude1739 padding: 10px 14px;
1740 border-radius: var(--r-sm);
06d5ffeClaude1741 margin-bottom: 16px;
2ce1d0bClaude1742 font-size: var(--t-sm);
1743 }
1744 .auth-switch {
1745 margin-top: 20px;
1746 font-size: var(--t-sm);
1747 color: var(--text-muted);
1748 text-align: center;
1749 }
1750 .banner {
1751 background: var(--accent-gradient-faint);
958d26aClaude1752 border: 1px solid rgba(140,109,255,0.35);
2ce1d0bClaude1753 color: var(--text);
1754 padding: 10px 14px;
1755 border-radius: var(--r-sm);
1756 font-size: var(--t-sm);
06d5ffeClaude1757 }
1758
2ce1d0bClaude1759 /* ============================================================ */
1760 /* Settings */
1761 /* ============================================================ */
1762 .settings-container { max-width: 720px; }
1763 .settings-container h2 { margin-bottom: 8px; font-size: var(--t-xl); letter-spacing: -0.02em; }
1764 .settings-container > h2 + p { color: var(--text-muted); font-size: var(--t-sm); margin-bottom: 24px; }
1765 .settings-container h3 { font-size: var(--t-md); margin-bottom: 12px; margin-top: 28px; }
1766 .settings-container h3:first-of-type { margin-top: 0; }
06d5ffeClaude1767 .ssh-keys-list { margin-bottom: 24px; }
1768 .ssh-key-item {
1769 display: flex;
1770 justify-content: space-between;
1771 align-items: center;
2ce1d0bClaude1772 padding: 14px 16px;
06d5ffeClaude1773 border: 1px solid var(--border);
2ce1d0bClaude1774 border-radius: var(--r-md);
06d5ffeClaude1775 margin-bottom: 8px;
2ce1d0bClaude1776 background: var(--bg-elevated);
1777 transition: border-color var(--t-fast) var(--ease);
06d5ffeClaude1778 }
2ce1d0bClaude1779 .ssh-key-item:hover { border-color: var(--border-strong); }
1780 .ssh-key-meta { font-size: var(--t-xs); color: var(--text-muted); margin-top: 4px; }
1781 .ssh-key-meta code { font-size: var(--t-xs); background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
06d5ffeClaude1782
2ce1d0bClaude1783 /* ============================================================ */
1784 /* Repo header + nav */
1785 /* ============================================================ */
fc1817aClaude1786 .repo-header {
1787 display: flex;
1788 align-items: center;
debcf27Claude1789 gap: 12px;
1790 margin-bottom: 22px;
1791 }
1792 .repo-header-title {
1793 display: flex;
1794 align-items: center;
1795 gap: 10px;
1796 font-family: var(--font-display);
1797 font-size: 24px;
1798 letter-spacing: -0.025em;
1799 flex-wrap: wrap;
1800 }
1801 .repo-header .owner {
1802 color: var(--text-muted);
1803 font-weight: 500;
1804 transition: color var(--t-fast) var(--ease);
1805 }
1806 .repo-header .owner:hover { color: var(--text-link); text-decoration: none; }
1807 .repo-header .separator { color: var(--text-faint); font-weight: 300; }
1808 .repo-header .name {
1809 color: var(--text-strong);
1810 font-weight: 700;
1811 letter-spacing: -0.028em;
fc1817aClaude1812 }
2ce1d0bClaude1813 .repo-header .name:hover { color: var(--text-link); text-decoration: none; }
debcf27Claude1814 .repo-header-fork {
1815 font-family: var(--font-mono);
1816 font-size: 11px;
1817 color: var(--text-muted);
1818 margin-top: 4px;
1819 letter-spacing: 0.01em;
1820 }
1821 .repo-header-fork a { color: var(--text-muted); }
1822 .repo-header-fork a:hover { color: var(--accent); }
1823 .repo-header-pill {
1824 display: inline-flex;
1825 align-items: center;
1826 padding: 2px 10px;
1827 border-radius: var(--r-full);
1828 font-family: var(--font-mono);
1829 font-size: 10px;
1830 font-weight: 600;
1831 letter-spacing: 0.12em;
1832 text-transform: uppercase;
1833 line-height: 1.6;
1834 vertical-align: 4px;
1835 }
1836 .repo-header-pill-archived {
1837 background: rgba(251,191,36,0.10);
1838 color: var(--yellow);
1839 border: 1px solid rgba(251,191,36,0.30);
1840 }
1841 .repo-header-pill-template {
1842 background: var(--accent-gradient-faint);
1843 color: var(--accent);
1844 border: 1px solid rgba(140,109,255,0.30);
fc1817aClaude1845 }
06d5ffeClaude1846 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude1847
1848 .repo-nav {
1849 display: flex;
debcf27Claude1850 gap: 1px;
fc1817aClaude1851 border-bottom: 1px solid var(--border);
debcf27Claude1852 margin-bottom: 28px;
2ce1d0bClaude1853 overflow-x: auto;
1854 scrollbar-width: thin;
fc1817aClaude1855 }
2ce1d0bClaude1856 .repo-nav::-webkit-scrollbar { height: 0; }
fc1817aClaude1857 .repo-nav a {
debcf27Claude1858 position: relative;
1859 padding: 11px 14px;
fc1817aClaude1860 color: var(--text-muted);
1861 border-bottom: 2px solid transparent;
2ce1d0bClaude1862 font-size: var(--t-sm);
1863 font-weight: 500;
1864 margin-bottom: -1px;
debcf27Claude1865 transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
2ce1d0bClaude1866 white-space: nowrap;
1867 }
debcf27Claude1868 .repo-nav a:hover { text-decoration: none; color: var(--text-strong); background: var(--bg-hover); }
2ce1d0bClaude1869 .repo-nav a.active {
debcf27Claude1870 color: var(--text-strong);
1871 font-weight: 600;
1872 }
1873 .repo-nav a.active::after {
1874 content: '';
1875 position: absolute;
1876 left: 14px;
1877 right: 14px;
1878 bottom: -1px;
1879 height: 2px;
1880 background: var(--accent-gradient);
1881 border-radius: 2px;
1882 }
1883 /* AI links in the right-side cluster — sparkle accent */
1884 .repo-nav-ai {
1885 color: var(--accent) !important;
1886 font-weight: 500;
1887 }
1888 .repo-nav-ai:hover {
1889 color: var(--accent-hover) !important;
1890 background: var(--accent-gradient-faint) !important;
1891 }
1892 .repo-nav-ai.active {
1893 color: var(--accent-hover) !important;
2ce1d0bClaude1894 font-weight: 600;
fc1817aClaude1895 }
1896
2ce1d0bClaude1897 .breadcrumb {
1898 display: flex;
debcf27Claude1899 gap: 6px;
2ce1d0bClaude1900 align-items: center;
debcf27Claude1901 margin-bottom: 18px;
2ce1d0bClaude1902 color: var(--text-muted);
1903 font-size: var(--t-sm);
1904 font-family: var(--font-mono);
debcf27Claude1905 font-feature-settings: var(--mono-feat);
2ce1d0bClaude1906 }
1907 .breadcrumb a { color: var(--text-link); font-weight: 500; }
1908 .breadcrumb a:hover { color: var(--accent-hover); }
debcf27Claude1909 .breadcrumb strong {
1910 color: var(--text-strong);
1911 font-weight: 600;
fc1817aClaude1912 }
1913
debcf27Claude1914 /* Page header — eyebrow + title + optional actions row.
1915 Use on dashboard, settings, admin, any "section landing" page. */
1916 .page-header {
1917 display: flex;
1918 align-items: flex-end;
1919 justify-content: space-between;
1920 gap: 16px;
1921 margin-bottom: 28px;
1922 padding-bottom: 20px;
1923 border-bottom: 1px solid var(--border-subtle);
1924 flex-wrap: wrap;
1925 }
1926 .page-header-text { flex: 1; min-width: 280px; }
1927 .page-header .eyebrow { margin-bottom: var(--s-2); }
1928 .page-header h1 {
1929 font-family: var(--font-display);
1930 font-size: clamp(24px, 3vw, 36px);
1931 line-height: 1.1;
1932 letter-spacing: -0.028em;
1933 margin-bottom: 6px;
1934 }
1935 .page-header p {
1936 color: var(--text-muted);
1937 font-size: var(--t-sm);
1938 line-height: 1.55;
1939 max-width: 640px;
1940 }
1941 .page-header-actions { display: flex; gap: 8px; align-items: center; }
fc1817aClaude1942
2ce1d0bClaude1943 /* ============================================================ */
1944 /* File browser table */
1945 /* ============================================================ */
1946 .file-table {
1947 width: 100%;
1948 border: 1px solid var(--border);
1949 border-radius: var(--r-md);
1950 overflow: hidden;
1951 background: var(--bg-elevated);
debcf27Claude1952 border-collapse: collapse;
2ce1d0bClaude1953 }
debcf27Claude1954 .file-table tr { border-bottom: 1px solid var(--border-subtle); transition: background var(--t-fast) var(--ease); }
fc1817aClaude1955 .file-table tr:last-child { border-bottom: none; }
debcf27Claude1956 .file-table td {
1957 padding: 9px 16px;
1958 font-size: var(--t-sm);
1959 font-family: var(--font-mono);
1960 font-feature-settings: var(--mono-feat);
1961 }
2ce1d0bClaude1962 .file-table tr:hover { background: var(--bg-hover); }
debcf27Claude1963 .file-icon {
1964 width: 22px;
1965 color: var(--text-faint);
1966 font-size: 13px;
1967 text-align: center;
1968 }
1969 .file-name a {
1970 color: var(--text);
1971 font-weight: 500;
1972 transition: color var(--t-fast) var(--ease);
1973 }
1974 .file-name a:hover { color: var(--accent); text-decoration: none; }
fc1817aClaude1975
2ce1d0bClaude1976 /* ============================================================ */
1977 /* Blob view */
1978 /* ============================================================ */
fc1817aClaude1979 .blob-view {
1980 border: 1px solid var(--border);
2ce1d0bClaude1981 border-radius: var(--r-md);
fc1817aClaude1982 overflow: hidden;
2ce1d0bClaude1983 background: var(--bg-elevated);
fc1817aClaude1984 }
1985 .blob-header {
1986 background: var(--bg-secondary);
2ce1d0bClaude1987 padding: 10px 16px;
fc1817aClaude1988 border-bottom: 1px solid var(--border);
2ce1d0bClaude1989 font-size: var(--t-sm);
fc1817aClaude1990 color: var(--text-muted);
06d5ffeClaude1991 display: flex;
1992 justify-content: space-between;
1993 align-items: center;
fc1817aClaude1994 }
1995 .blob-code {
1996 overflow-x: auto;
1997 font-family: var(--font-mono);
2ce1d0bClaude1998 font-size: var(--t-sm);
1999 line-height: 1.65;
fc1817aClaude2000 }
2001 .blob-code table { width: 100%; border-collapse: collapse; }
2002 .blob-code .line-num {
2003 width: 1%;
2ce1d0bClaude2004 min-width: 56px;
2005 padding: 0 14px;
fc1817aClaude2006 text-align: right;
2ce1d0bClaude2007 color: var(--text-faint);
fc1817aClaude2008 user-select: none;
2009 white-space: nowrap;
2010 border-right: 1px solid var(--border);
2011 }
2ce1d0bClaude2012 .blob-code .line-content { padding: 0 16px; white-space: pre; }
2013 .blob-code tr:hover { background: var(--bg-hover); }
fc1817aClaude2014
2ce1d0bClaude2015 /* ============================================================ */
2016 /* Commits + diffs */
2017 /* ============================================================ */
2018 .commit-list {
2019 border: 1px solid var(--border);
2020 border-radius: var(--r-md);
2021 overflow: hidden;
2022 background: var(--bg-elevated);
2023 }
fc1817aClaude2024 .commit-item {
2025 display: flex;
2026 justify-content: space-between;
2027 align-items: center;
debcf27Claude2028 padding: 14px 18px;
2029 border-bottom: 1px solid var(--border-subtle);
2ce1d0bClaude2030 transition: background var(--t-fast) var(--ease);
fc1817aClaude2031 }
2032 .commit-item:last-child { border-bottom: none; }
2ce1d0bClaude2033 .commit-item:hover { background: var(--bg-hover); }
debcf27Claude2034 .commit-message {
2035 font-size: var(--t-sm);
2036 font-weight: 500;
2037 line-height: 1.45;
2038 color: var(--text-strong);
2039 letter-spacing: -0.005em;
2040 }
2041 .commit-meta {
2042 font-family: var(--font-mono);
2043 font-size: 11px;
2044 color: var(--text-muted);
2045 margin-top: 5px;
2046 letter-spacing: 0.01em;
2047 }
fc1817aClaude2048 .commit-sha {
2049 font-family: var(--font-mono);
debcf27Claude2050 font-feature-settings: var(--mono-feat);
2051 font-size: 11px;
2052 padding: 4px 9px;
fc1817aClaude2053 background: var(--bg-tertiary);
2054 border: 1px solid var(--border);
2ce1d0bClaude2055 border-radius: var(--r-sm);
debcf27Claude2056 color: var(--accent);
2057 font-weight: 600;
2058 letter-spacing: 0.02em;
2ce1d0bClaude2059 transition: all var(--t-fast) var(--ease);
fc1817aClaude2060 }
debcf27Claude2061 .commit-sha:hover {
2062 border-color: rgba(140,109,255,0.40);
2063 background: var(--accent-gradient-faint);
2064 color: var(--accent-hover);
2065 text-decoration: none;
fc1817aClaude2066 }
2067
2068 .diff-view { margin-top: 16px; }
2069 .diff-file {
2070 border: 1px solid var(--border);
2ce1d0bClaude2071 border-radius: var(--r-md);
fc1817aClaude2072 margin-bottom: 16px;
2073 overflow: hidden;
2ce1d0bClaude2074 background: var(--bg-elevated);
fc1817aClaude2075 }
2076 .diff-file-header {
2077 background: var(--bg-secondary);
2ce1d0bClaude2078 padding: 10px 16px;
fc1817aClaude2079 border-bottom: 1px solid var(--border);
2080 font-family: var(--font-mono);
2ce1d0bClaude2081 font-size: var(--t-sm);
2082 font-weight: 500;
fc1817aClaude2083 }
2084 .diff-content {
2085 overflow-x: auto;
2086 font-family: var(--font-mono);
2ce1d0bClaude2087 font-size: var(--t-sm);
2088 line-height: 1.65;
fc1817aClaude2089 }
958d26aClaude2090 .diff-content .line-add { background: rgba(52,211,153,0.10); color: var(--green); }
2091 .diff-content .line-del { background: rgba(248,113,113,0.08); color: var(--red); }
2092 .diff-content .line-hunk { background: rgba(140,109,255,0.06); color: var(--text-link); }
2ce1d0bClaude2093 .diff-content .line { padding: 0 16px; white-space: pre; display: block; }
fc1817aClaude2094
2095 .stat-add { color: var(--green); font-weight: 600; }
2096 .stat-del { color: var(--red); font-weight: 600; }
2097
2ce1d0bClaude2098 /* ============================================================ */
2099 /* Empty state */
2100 /* ============================================================ */
fc1817aClaude2101 .empty-state {
2102 text-align: center;
958d26aClaude2103 padding: 96px 24px;
fc1817aClaude2104 color: var(--text-muted);
2ce1d0bClaude2105 border: 1px dashed var(--border);
2106 border-radius: var(--r-lg);
958d26aClaude2107 background:
2108 radial-gradient(60% 60% at 50% 0%, rgba(140,109,255,0.05), transparent 70%),
2109 var(--bg-elevated);
2110 position: relative;
2111 overflow: hidden;
2112 }
2113 .empty-state::before {
2114 content: '';
2115 position: absolute;
2116 inset: 0;
2117 background-image: radial-gradient(rgba(255,255,255,0.025) 1px, transparent 1px);
2118 background-size: 24px 24px;
2119 opacity: 0.5;
2120 pointer-events: none;
2121 mask-image: radial-gradient(ellipse at center, #000 30%, transparent 70%);
2122 -webkit-mask-image: radial-gradient(ellipse at center, #000 30%, transparent 70%);
2123 }
2124 :root[data-theme='light'] .empty-state::before {
2125 background-image: radial-gradient(rgba(15,16,28,0.08) 1px, transparent 1px);
fc1817aClaude2126 }
958d26aClaude2127 .empty-state > * { position: relative; z-index: 1; }
2ce1d0bClaude2128 .empty-state h2 {
958d26aClaude2129 font-size: var(--t-xl);
2ce1d0bClaude2130 margin-bottom: 8px;
958d26aClaude2131 color: var(--text-strong);
2132 letter-spacing: -0.022em;
2ce1d0bClaude2133 }
958d26aClaude2134 .empty-state p { font-size: var(--t-md); max-width: 480px; margin: 0 auto; line-height: 1.55; }
fc1817aClaude2135 .empty-state pre {
2136 text-align: left;
2137 display: inline-block;
2138 background: var(--bg-secondary);
958d26aClaude2139 padding: 18px 24px;
2140 border-radius: var(--r-md);
fc1817aClaude2141 border: 1px solid var(--border);
2142 font-family: var(--font-mono);
958d26aClaude2143 font-feature-settings: var(--mono-feat);
2ce1d0bClaude2144 font-size: var(--t-sm);
958d26aClaude2145 margin-top: 24px;
fc1817aClaude2146 line-height: 1.8;
2ce1d0bClaude2147 color: var(--text);
958d26aClaude2148 box-shadow: var(--elev-1);
fc1817aClaude2149 }
2150
2ce1d0bClaude2151 /* ============================================================ */
2152 /* Badges */
2153 /* ============================================================ */
fc1817aClaude2154 .badge {
2ce1d0bClaude2155 display: inline-flex;
2156 align-items: center;
2157 gap: 4px;
2158 padding: 2px 9px;
2159 border-radius: var(--r-full);
2160 font-size: var(--t-xs);
fc1817aClaude2161 font-weight: 500;
2162 background: var(--bg-tertiary);
2163 border: 1px solid var(--border);
2164 color: var(--text-muted);
2ce1d0bClaude2165 line-height: 1.5;
fc1817aClaude2166 }
2167
2ce1d0bClaude2168 /* ============================================================ */
2169 /* Branch dropdown */
2170 /* ============================================================ */
fc1817aClaude2171 .branch-selector {
2172 display: inline-flex;
2173 align-items: center;
2ce1d0bClaude2174 gap: 6px;
2175 padding: 6px 12px;
2176 background: var(--bg-elevated);
fc1817aClaude2177 border: 1px solid var(--border);
2ce1d0bClaude2178 border-radius: var(--r-sm);
2179 font-size: var(--t-sm);
fc1817aClaude2180 color: var(--text);
2181 margin-bottom: 12px;
2ce1d0bClaude2182 transition: border-color var(--t-fast) var(--ease);
fc1817aClaude2183 }
2ce1d0bClaude2184 .branch-selector:hover { border-color: var(--border-strong); }
fc1817aClaude2185
06d5ffeClaude2186 .branch-dropdown {
2187 position: relative;
2188 display: inline-block;
2189 margin-bottom: 12px;
2190 }
2191 .branch-dropdown-content {
2192 display: none;
2193 position: absolute;
2ce1d0bClaude2194 top: calc(100% + 4px);
06d5ffeClaude2195 left: 0;
2196 z-index: 10;
2ce1d0bClaude2197 min-width: 220px;
2198 background: var(--bg-elevated);
2199 border: 1px solid var(--border-strong);
2200 border-radius: var(--r-md);
2201 overflow: hidden;
2202 box-shadow: var(--elev-3);
06d5ffeClaude2203 }
2204 .branch-dropdown:hover .branch-dropdown-content,
2205 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
2206 .branch-dropdown-content a {
2207 display: block;
2ce1d0bClaude2208 padding: 9px 14px;
2209 font-size: var(--t-sm);
06d5ffeClaude2210 color: var(--text);
2211 border-bottom: 1px solid var(--border);
2ce1d0bClaude2212 transition: background var(--t-fast) var(--ease);
06d5ffeClaude2213 }
2214 .branch-dropdown-content a:last-child { border-bottom: none; }
2ce1d0bClaude2215 .branch-dropdown-content a:hover { background: var(--bg-hover); text-decoration: none; }
2216 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; background: var(--accent-gradient-faint); }
06d5ffeClaude2217
2ce1d0bClaude2218 /* ============================================================ */
2219 /* Card grid */
2220 /* ============================================================ */
2221 .card-grid {
2222 display: grid;
2223 grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
2224 gap: 16px;
2225 }
fc1817aClaude2226 .card {
2227 border: 1px solid var(--border);
2ce1d0bClaude2228 border-radius: var(--r-md);
958d26aClaude2229 padding: 20px;
2ce1d0bClaude2230 background: var(--bg-elevated);
2231 transition:
958d26aClaude2232 border-color var(--t-base) var(--ease),
2233 transform var(--t-base) var(--ease-out-quart),
2ce1d0bClaude2234 box-shadow var(--t-base) var(--ease);
2235 position: relative;
2236 overflow: hidden;
958d26aClaude2237 isolation: isolate;
2238 }
2239 .card::before {
2240 content: '';
2241 position: absolute;
2242 inset: 0;
2243 background: linear-gradient(135deg, rgba(140,109,255,0.06), transparent 50%);
2244 opacity: 0;
2245 transition: opacity var(--t-base) var(--ease);
2246 pointer-events: none;
2247 z-index: -1;
2ce1d0bClaude2248 }
2249 .card:hover {
2250 border-color: var(--border-strong);
2251 box-shadow: var(--elev-2);
958d26aClaude2252 transform: translateY(-2px);
2ce1d0bClaude2253 }
958d26aClaude2254 .card:hover::before { opacity: 1; }
2255 .card h3 { font-size: var(--t-md); margin-bottom: 6px; letter-spacing: -0.012em; }
2ce1d0bClaude2256 .card h3 a { color: var(--text); font-weight: 600; }
2257 .card h3 a:hover { color: var(--text-link); }
2258 .card p { font-size: var(--t-sm); color: var(--text-muted); line-height: 1.5; }
2259 .card-meta {
2260 display: flex;
2261 gap: 14px;
2262 margin-top: 14px;
2263 padding-top: 14px;
2264 border-top: 1px solid var(--border);
2265 font-size: var(--t-xs);
2266 color: var(--text-muted);
2267 }
2268 .card-meta span { display: flex; align-items: center; gap: 5px; }
2269
2270 /* ============================================================ */
2271 /* Stat card / box */
2272 /* ============================================================ */
2273 .stat-card {
2274 background: var(--bg-elevated);
2275 border: 1px solid var(--border);
2276 border-radius: var(--r-md);
fc1817aClaude2277 padding: 16px;
2ce1d0bClaude2278 transition: border-color var(--t-fast) var(--ease);
2279 }
2280 .stat-card:hover { border-color: var(--border-strong); }
2281 .stat-label {
2282 font-size: var(--t-xs);
2283 color: var(--text-muted);
2284 text-transform: uppercase;
2285 letter-spacing: 0.06em;
2286 font-weight: 500;
2287 }
2288 .stat-value {
2289 font-size: var(--t-xl);
2290 font-weight: 700;
2291 margin-top: 4px;
2292 letter-spacing: -0.025em;
2293 color: var(--text);
2294 font-feature-settings: 'tnum';
fc1817aClaude2295 }
06d5ffeClaude2296
2ce1d0bClaude2297 /* ============================================================ */
2298 /* Star button */
2299 /* ============================================================ */
06d5ffeClaude2300 .star-btn {
2301 display: inline-flex;
2302 align-items: center;
2303 gap: 6px;
2ce1d0bClaude2304 padding: 5px 12px;
2305 background: var(--bg-elevated);
06d5ffeClaude2306 border: 1px solid var(--border);
2ce1d0bClaude2307 border-radius: var(--r-sm);
06d5ffeClaude2308 color: var(--text);
2ce1d0bClaude2309 font-size: var(--t-sm);
2310 font-weight: 500;
06d5ffeClaude2311 cursor: pointer;
2ce1d0bClaude2312 transition: all var(--t-fast) var(--ease);
2313 }
2314 .star-btn:hover { background: var(--bg-surface); border-color: var(--border-strong); text-decoration: none; }
2315 .star-btn.starred {
2316 color: var(--yellow);
958d26aClaude2317 border-color: rgba(251,191,36,0.4);
2318 background: rgba(251,191,36,0.08);
06d5ffeClaude2319 }
2320
2ce1d0bClaude2321 /* ============================================================ */
2322 /* User profile */
2323 /* ============================================================ */
06d5ffeClaude2324 .user-profile {
2325 display: flex;
2326 gap: 32px;
2327 margin-bottom: 32px;
2ce1d0bClaude2328 padding: 24px;
2329 background: var(--bg-elevated);
2330 border: 1px solid var(--border);
2331 border-radius: var(--r-lg);
06d5ffeClaude2332 }
2333 .user-avatar {
2334 width: 96px;
2335 height: 96px;
2ce1d0bClaude2336 border-radius: var(--r-full);
2337 background: var(--accent-gradient-soft);
2338 border: 1px solid var(--border-strong);
06d5ffeClaude2339 display: flex;
2340 align-items: center;
2341 justify-content: center;
2ce1d0bClaude2342 font-size: 36px;
2343 font-weight: 600;
2344 color: var(--text);
06d5ffeClaude2345 flex-shrink: 0;
2ce1d0bClaude2346 letter-spacing: -0.02em;
06d5ffeClaude2347 }
2ce1d0bClaude2348 .user-info h2 { font-size: var(--t-xl); margin-bottom: 2px; letter-spacing: -0.025em; }
2349 .user-info .username { font-size: var(--t-md); color: var(--text-muted); }
2350 .user-info .bio { font-size: var(--t-sm); color: var(--text-muted); margin-top: 10px; line-height: 1.55; }
06d5ffeClaude2351
2ce1d0bClaude2352 /* ============================================================ */
2353 /* New repo form */
2354 /* ============================================================ */
2355 .new-repo-form { max-width: 640px; }
2356 .new-repo-form h2 { margin-bottom: 24px; font-size: var(--t-xl); letter-spacing: -0.025em; }
2357 .visibility-options { display: flex; gap: 12px; margin-bottom: 20px; }
06d5ffeClaude2358 .visibility-option {
2359 flex: 1;
2ce1d0bClaude2360 padding: 16px;
06d5ffeClaude2361 border: 1px solid var(--border);
2ce1d0bClaude2362 border-radius: var(--r-md);
2363 background: var(--bg-elevated);
06d5ffeClaude2364 cursor: pointer;
2ce1d0bClaude2365 text-align: left;
2366 transition: all var(--t-fast) var(--ease);
2367 }
2368 .visibility-option:hover { border-color: var(--border-strong); background: var(--bg-surface); }
2369 .visibility-option:has(input:checked) {
2370 border-color: var(--accent);
2371 background: var(--accent-gradient-faint);
2372 box-shadow: 0 0 0 1px var(--accent);
06d5ffeClaude2373 }
2374 .visibility-option input { display: none; }
2ce1d0bClaude2375 .visibility-option .vis-label { font-size: var(--t-sm); font-weight: 600; margin-bottom: 4px; }
2376 .visibility-option .vis-desc { font-size: var(--t-xs); color: var(--text-muted); }
79136bbClaude2377
2ce1d0bClaude2378 /* ============================================================ */
2379 /* Issues */
2380 /* ============================================================ */
2381 .issue-tabs { display: flex; gap: 4px; }
2382 .issue-tabs a {
2383 color: var(--text-muted);
2384 font-size: var(--t-sm);
2385 font-weight: 500;
2386 padding: 6px 12px;
2387 border-radius: var(--r-sm);
2388 transition: all var(--t-fast) var(--ease);
2389 }
2390 .issue-tabs a:hover { color: var(--text); background: var(--bg-hover); text-decoration: none; }
2391 .issue-tabs a.active { color: var(--text); background: var(--bg-elevated); }
79136bbClaude2392
2ce1d0bClaude2393 .issue-list {
2394 border: 1px solid var(--border);
2395 border-radius: var(--r-md);
2396 overflow: hidden;
2397 background: var(--bg-elevated);
2398 }
79136bbClaude2399 .issue-item {
2400 display: flex;
2ce1d0bClaude2401 gap: 14px;
79136bbClaude2402 align-items: flex-start;
2ce1d0bClaude2403 padding: 14px 16px;
79136bbClaude2404 border-bottom: 1px solid var(--border);
2ce1d0bClaude2405 transition: background var(--t-fast) var(--ease);
79136bbClaude2406 }
2407 .issue-item:last-child { border-bottom: none; }
2ce1d0bClaude2408 .issue-item:hover { background: var(--bg-hover); }
2409 .issue-state-icon {
2410 font-size: 14px;
2411 padding-top: 2px;
2412 width: 18px;
2413 height: 18px;
2414 display: inline-flex;
2415 align-items: center;
2416 justify-content: center;
2417 border-radius: var(--r-full);
2418 flex-shrink: 0;
2419 }
79136bbClaude2420 .state-open { color: var(--green); }
958d26aClaude2421 .state-closed { color: #b69dff; }
2ce1d0bClaude2422 .issue-title {
debcf27Claude2423 font-family: var(--font-display);
2424 font-size: var(--t-md);
2ce1d0bClaude2425 font-weight: 600;
debcf27Claude2426 line-height: 1.35;
2427 letter-spacing: -0.012em;
2428 }
2429 .issue-title a { color: var(--text-strong); transition: color var(--t-fast) var(--ease); }
2430 .issue-title a:hover { color: var(--accent); text-decoration: none; }
2431 .issue-meta {
2432 font-family: var(--font-mono);
2433 font-size: 11px;
2434 color: var(--text-muted);
2435 margin-top: 5px;
2436 letter-spacing: 0.01em;
2ce1d0bClaude2437 }
79136bbClaude2438
2439 .issue-badge {
2440 display: inline-flex;
2441 align-items: center;
2ce1d0bClaude2442 gap: 6px;
79136bbClaude2443 padding: 4px 12px;
2ce1d0bClaude2444 border-radius: var(--r-full);
2445 font-size: var(--t-sm);
79136bbClaude2446 font-weight: 500;
2ce1d0bClaude2447 line-height: 1.4;
79136bbClaude2448 }
958d26aClaude2449 .badge-open { background: rgba(52,211,153,0.10); color: var(--green); border: 1px solid rgba(52,211,153,0.35); }
2450 .badge-closed { background: rgba(182,157,255,0.10); color: #b69dff; border: 1px solid rgba(182,157,255,0.35); }
2451 .badge-merged { background: rgba(140,109,255,0.10); color: var(--accent); border: 1px solid rgba(140,109,255,0.35); }
2ce1d0bClaude2452 .state-merged { color: var(--accent); }
958d26aClaude2453 .ai-review { border-color: var(--accent); box-shadow: 0 0 0 1px rgba(140,109,255,0.3); }
79136bbClaude2454
2ce1d0bClaude2455 .issue-detail { max-width: 920px; }
79136bbClaude2456 .issue-comment-box {
2457 border: 1px solid var(--border);
2ce1d0bClaude2458 border-radius: var(--r-md);
79136bbClaude2459 margin-bottom: 16px;
2460 overflow: hidden;
2ce1d0bClaude2461 background: var(--bg-elevated);
79136bbClaude2462 }
2463 .comment-header {
2464 background: var(--bg-secondary);
2ce1d0bClaude2465 padding: 10px 16px;
79136bbClaude2466 border-bottom: 1px solid var(--border);
2ce1d0bClaude2467 font-size: var(--t-sm);
79136bbClaude2468 color: var(--text-muted);
2469 }
2470
2ce1d0bClaude2471 /* ============================================================ */
2472 /* Panel — flexible container used across many pages */
2473 /* ============================================================ */
2474 .panel {
2475 background: var(--bg-elevated);
2476 border: 1px solid var(--border);
2477 border-radius: var(--r-md);
2478 overflow: hidden;
2479 }
2480 .panel-item {
2481 display: flex;
2482 align-items: center;
2483 gap: 12px;
2484 padding: 12px 16px;
79136bbClaude2485 border-bottom: 1px solid var(--border);
2ce1d0bClaude2486 transition: background var(--t-fast) var(--ease);
2487 }
2488 .panel-item:last-child { border-bottom: none; }
2489 .panel-item:hover { background: var(--bg-hover); }
2490 .panel-empty {
2491 padding: 24px;
2492 text-align: center;
79136bbClaude2493 color: var(--text-muted);
2ce1d0bClaude2494 font-size: var(--t-sm);
79136bbClaude2495 }
2496
2ce1d0bClaude2497 /* ============================================================ */
2498 /* Search */
2499 /* ============================================================ */
79136bbClaude2500 .search-results .diff-file { margin-bottom: 12px; }
16b325cClaude2501
2ce1d0bClaude2502 /* ============================================================ */
2503 /* Timeline */
2504 /* ============================================================ */
2505 .timeline { position: relative; padding-left: 28px; }
16b325cClaude2506 .timeline::before {
2507 content: '';
2508 position: absolute;
2509 left: 4px;
2510 top: 8px;
2511 bottom: 8px;
2512 width: 2px;
2ce1d0bClaude2513 background: linear-gradient(180deg, var(--border) 0%, transparent 100%);
16b325cClaude2514 }
2ce1d0bClaude2515 .timeline-item { position: relative; padding-bottom: 16px; }
16b325cClaude2516 .timeline-dot {
2517 position: absolute;
2ce1d0bClaude2518 left: -28px;
2519 top: 8px;
2520 width: 12px;
2521 height: 12px;
2522 border-radius: var(--r-full);
2523 background: var(--accent-gradient);
16b325cClaude2524 border: 2px solid var(--bg);
2ce1d0bClaude2525 box-shadow: 0 0 0 1px var(--border-strong);
16b325cClaude2526 }
2527 .timeline-content {
2ce1d0bClaude2528 background: var(--bg-elevated);
16b325cClaude2529 border: 1px solid var(--border);
2ce1d0bClaude2530 border-radius: var(--r-md);
2531 padding: 14px 16px;
16b325cClaude2532 }
f1ab587Claude2533
2ce1d0bClaude2534 /* ============================================================ */
2535 /* Toggle switch */
2536 /* ============================================================ */
f1ab587Claude2537 .toggle-switch {
2538 position: relative;
2539 display: inline-block;
2ce1d0bClaude2540 width: 40px;
2541 height: 22px;
f1ab587Claude2542 flex-shrink: 0;
2543 margin-left: 16px;
2544 }
2545 .toggle-switch input { opacity: 0; width: 0; height: 0; }
2546 .toggle-slider {
2547 position: absolute;
2548 cursor: pointer;
2549 top: 0; left: 0; right: 0; bottom: 0;
2550 background: var(--bg-tertiary);
2551 border: 1px solid var(--border);
2ce1d0bClaude2552 border-radius: var(--r-full);
2553 transition: all var(--t-base) var(--ease);
f1ab587Claude2554 }
2555 .toggle-slider::before {
2556 content: '';
2557 position: absolute;
2ce1d0bClaude2558 height: 16px;
2559 width: 16px;
f1ab587Claude2560 left: 2px;
2561 bottom: 2px;
2562 background: var(--text-muted);
2ce1d0bClaude2563 border-radius: var(--r-full);
2564 transition: all var(--t-base) var(--ease);
f1ab587Claude2565 }
2566 .toggle-switch input:checked + .toggle-slider {
2ce1d0bClaude2567 background: var(--accent-gradient);
2568 border-color: transparent;
958d26aClaude2569 box-shadow: 0 0 0 1px rgba(140,109,255,0.4);
f1ab587Claude2570 }
2571 .toggle-switch input:checked + .toggle-slider::before {
2ce1d0bClaude2572 transform: translateX(18px);
f1ab587Claude2573 background: #fff;
2574 }
ef8d378Claude2575
2576 /* ============================================================
2577 * 2026 polish layer (purely additive — no layout changes).
2578 * Improves typography rendering, focus states, hover affordances,
2579 * and adds the gradient brand cue to primary buttons. Anything
2580 * that could alter dimensions stays in the rules above.
2581 * ============================================================ */
2582 html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
2583 body { letter-spacing: -0.005em; font-feature-settings: 'cv11', 'ss01', 'ss03'; }
2584 *::selection { background: rgba(168,85,247,0.35); color: var(--text); }
2585
2586 h1, h2, h3, h4 { letter-spacing: -0.018em; }
2587 h1 { letter-spacing: -0.025em; }
2588
2589 /* Smoother colour transitions everywhere links live */
2590 a { transition: color 120ms cubic-bezier(0.16,1,0.3,1); }
2591
2592 /* Buttons: focus rings + smoother transitions; primary gets the gradient */
2593 .btn {
2594 transition:
2595 background 120ms cubic-bezier(0.16,1,0.3,1),
2596 border-color 120ms cubic-bezier(0.16,1,0.3,1),
2597 transform 120ms cubic-bezier(0.16,1,0.3,1),
2598 box-shadow 120ms cubic-bezier(0.16,1,0.3,1);
2599 }
2600 .btn:active { transform: translateY(0.5px); }
2601 .btn:focus-visible {
2602 outline: none;
2603 box-shadow: 0 0 0 3px rgba(168,85,247,0.30);
2604 }
2605 .btn-primary {
2606 background: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
2607 border-color: transparent;
2608 box-shadow:
2609 inset 0 1px 0 rgba(255,255,255,0.15),
2610 0 1px 2px rgba(168,85,247,0.25);
2611 }
2612 .btn-primary:hover {
2613 background: linear-gradient(135deg, #b766f8 0%, #22cce0 100%);
2614 filter: none;
2615 box-shadow:
2616 inset 0 1px 0 rgba(255,255,255,0.20),
2617 0 4px 12px rgba(168,85,247,0.30);
2618 }
2619
2620 /* Inputs: cleaner focus ring + hover */
2621 .form-group input:hover,
2622 .form-group textarea:hover,
2623 .form-group select:hover {
2624 border-color: rgba(255,255,255,0.14);
2625 }
2626 .form-group input:focus,
2627 .form-group textarea:focus,
2628 .form-group select:focus {
2629 border-color: rgba(168,85,247,0.55);
2630 box-shadow: 0 0 0 3px rgba(168,85,247,0.22);
2631 }
2632 :root[data-theme='light'] .form-group input:hover,
2633 :root[data-theme='light'] .form-group textarea:hover,
2634 :root[data-theme='light'] .form-group select:hover {
2635 border-color: rgba(0,0,0,0.18);
2636 }
2637
2638 /* Cards: subtle hover lift */
2639 .card {
2640 transition:
2641 border-color 160ms cubic-bezier(0.16,1,0.3,1),
2642 transform 160ms cubic-bezier(0.16,1,0.3,1),
2643 box-shadow 200ms cubic-bezier(0.16,1,0.3,1);
2644 }
2645 .card:hover {
2646 border-color: rgba(255,255,255,0.18);
2647 transform: translateY(-1px);
2648 box-shadow: 0 8px 24px rgba(0,0,0,0.30);
2649 }
2650 :root[data-theme='light'] .card:hover {
2651 border-color: rgba(0,0,0,0.18);
2652 box-shadow: 0 8px 24px rgba(0,0,0,0.08);
2653 }
2654
2655 /* Issue / commit / panel rows: smoother hover */
2656 .issue-item, .commit-item {
2657 transition: background 120ms cubic-bezier(0.16,1,0.3,1);
2658 }
2659 .repo-nav a {
2660 transition: color 120ms cubic-bezier(0.16,1,0.3,1),
2661 border-bottom-color 120ms cubic-bezier(0.16,1,0.3,1);
2662 }
2663 .nav-link {
2664 transition: color 120ms cubic-bezier(0.16,1,0.3,1);
2665 }
2666
2667 /* Auth card: subtle elevation so register/login feel premium */
2668 .auth-container {
2669 background: var(--bg-secondary);
2670 border: 1px solid var(--border);
2671 border-radius: var(--r-lg, 12px);
2672 padding: 32px;
2673 box-shadow: 0 4px 16px rgba(0,0,0,0.30), 0 0 0 1px var(--border);
2674 }
2675 :root[data-theme='light'] .auth-container {
2676 box-shadow: 0 4px 16px rgba(0,0,0,0.06), 0 0 0 1px var(--border);
2677 }
2678 .auth-container h2 { letter-spacing: -0.025em; }
2679
2680 /* Empty state: dashed border, generous padding */
2681 .empty-state {
2682 border: 1px dashed var(--border);
2683 border-radius: var(--r-lg, 12px);
2684 background: var(--bg);
2685 }
2686
2687 /* Badges + commit-sha: smoother transition */
2688 .commit-sha, .badge { transition: all 120ms cubic-bezier(0.16,1,0.3,1); }
2689
2690 /* Gradient text utility — matches landing's accent treatment */
2691 .gradient-text {
2692 background: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
2693 -webkit-background-clip: text;
2694 background-clip: text;
2695 -webkit-text-fill-color: transparent;
2696 }
2697
2698 /* Custom scrollbars (subtle, themed) */
2699 ::-webkit-scrollbar { width: 10px; height: 10px; }
2700 ::-webkit-scrollbar-track { background: transparent; }
2701 ::-webkit-scrollbar-thumb {
2702 background: rgba(255,255,255,0.06);
2703 border: 2px solid var(--bg);
2704 border-radius: 9999px;
2705 }
2706 ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.14); }
2707 :root[data-theme='light'] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.10); }
2708 :root[data-theme='light'] ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.18); }
2709
2710 /* Honour reduced-motion preference */
2711 @media (prefers-reduced-motion: reduce) {
2712 *, *::before, *::after {
2713 animation-duration: 0.01ms !important;
2714 animation-iteration-count: 1 !important;
2715 transition-duration: 0.01ms !important;
2716 }
2717 }
c63b860Claude2718
2719 /* Block O3 — visual coherence additive rules. */
2720 .card.card-p-none { padding: 0; }
2721 .card.card-p-sm { padding: var(--space-3); }
2722 .card.card-p-md { padding: var(--space-4); }
2723 .card.card-p-lg { padding: var(--space-6); }
2724 .card.card-elevated { box-shadow: var(--elev-2); }
2725 .card.card-gradient {
2726 background:
2727 linear-gradient(135deg, rgba(140,109,255,0.05), transparent 60%),
2728 var(--bg-elevated);
2729 }
2730 .card.card-gradient::before { opacity: 1; }
2731 .notice {
2732 padding: var(--space-3) var(--space-4);
2733 border-radius: var(--radius-md);
2734 border: 1px solid var(--border);
2735 background: var(--bg-elevated);
2736 color: var(--text);
2737 font-size: var(--font-size-sm);
2738 margin-bottom: var(--space-6);
2739 line-height: var(--leading-normal);
2740 }
2741 .notice-info { border-color: rgba(96,165,250,0.40); background: rgba(96,165,250,0.08); color: var(--text); }
2742 .notice-success { border-color: var(--green); background: rgba(52,211,153,0.08); color: var(--text); }
2743 .notice-warn { border-color: var(--yellow); background: rgba(251,191,36,0.10); color: var(--yellow); }
2744 .notice-error { border-color: var(--red); background: rgba(248,113,113,0.10); color: var(--red); }
2745 .notice-accent { border-color: var(--accent); background: rgba(140,109,255,0.10); color: var(--text); }
2746 .email-preview {
2747 padding: var(--space-5);
2748 background: #fff;
2749 color: #111;
2750 border-radius: var(--radius-md);
2751 }
2752 .code-block {
2753 margin: var(--space-2) 0 0;
2754 padding: var(--space-3);
2755 background: var(--bg-tertiary);
2756 border: 1px solid var(--border-subtle);
2757 border-radius: var(--radius-sm);
2758 font-family: var(--font-mono);
2759 font-size: var(--font-size-xs);
2760 line-height: var(--leading-normal);
2761 overflow-x: auto;
2762 }
2763 .status-pill-operational {
2764 display: inline-flex;
2765 align-items: center;
2766 padding: var(--space-1) var(--space-3);
2767 border-radius: var(--radius-full);
2768 font-size: var(--font-size-xs);
2769 font-weight: 600;
2770 background: rgba(52,211,153,0.15);
2771 color: var(--green);
2772 }
2773 .api-tag {
2774 display: inline-flex;
2775 align-items: center;
2776 font-size: var(--font-size-xs);
2777 padding: 2px var(--space-2);
2778 border-radius: var(--radius-sm);
2779 font-family: var(--font-mono);
2780 }
2781 .api-tag-auth { background: rgba(96,165,250,0.15); color: var(--accent); }
2782 .api-tag-scope { background: rgba(52,211,153,0.15); color: var(--green); }
2783 .stat-number {
2784 font-size: var(--font-size-xl);
2785 font-weight: 700;
2786 color: var(--text-strong);
2787 line-height: var(--leading-tight);
2788 }
2789 .stat-number-accent { color: var(--accent); }
2790 .stat-number-blue { color: var(--blue); }
2791 .stat-number-purple { color: var(--text-link); }
2792 footer .footer-tag-sub {
2793 margin-top: var(--space-2);
2794 font-size: var(--font-size-xs);
2795 color: var(--text-faint);
2796 line-height: var(--leading-normal);
2797 }
2798 footer .footer-version-pill {
2799 display: inline-flex;
2800 align-items: center;
2801 gap: var(--space-2);
2802 padding: var(--space-1) var(--space-3);
2803 border-radius: var(--radius-full);
2804 border: 1px solid var(--border);
2805 background: var(--bg-elevated);
2806 color: var(--text-faint);
2807 font-family: var(--font-mono);
2808 font-size: var(--font-size-xs);
2809 cursor: help;
2810 }
2811 footer .footer-banner {
2812 max-width: 1240px;
2813 margin: var(--space-6) auto 0;
2814 padding: var(--space-3) var(--space-4);
2815 border-radius: var(--radius-md);
2816 border: 1px solid var(--border);
2817 background: var(--bg-elevated);
2818 color: var(--text);
2819 font-family: var(--font-mono);
2820 font-size: var(--font-size-xs);
2821 letter-spacing: 0.04em;
2822 text-transform: uppercase;
2823 text-align: center;
2824 }
2825 footer .footer-banner-info { border-color: rgba(96,165,250,0.40); color: var(--blue); }
2826 footer .footer-banner-warn { border-color: rgba(251,191,36,0.40); color: var(--yellow); }
2827 footer .footer-banner-error { border-color: rgba(248,113,113,0.45); color: var(--red); }
dc26881CC LABS App2828
2829 /* ============================================================ */
2830 /* Block U4 — cross-document view transitions. */
2831 /* Chrome 126+, Edge, Safari 18.2+ get a soft 200ms fade on */
2832 /* every same-origin navigation. Older browsers ignore these */
2833 /* rules entirely — no JS shim, no breakage. */
2834 /* prefers-reduced-motion disables the animation honourably. */
2835 /* ============================================================ */
2836 @view-transition {
2837 navigation: auto;
2838 }
2839 ::view-transition-old(root),
2840 ::view-transition-new(root) {
2841 animation-duration: 200ms;
2842 animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
2843 }
2844 ::view-transition-old(root) {
2845 animation-name: vt-fade-out;
2846 }
2847 ::view-transition-new(root) {
2848 animation-name: vt-fade-in;
2849 }
2850 @keyframes vt-fade-out {
2851 to { opacity: 0; }
2852 }
2853 @keyframes vt-fade-in {
2854 from { opacity: 0; }
2855 }
2856 @media (prefers-reduced-motion: reduce) {
2857 ::view-transition-old(root),
2858 ::view-transition-new(root) {
2859 animation-duration: 0s;
2860 }
2861 }
2862 /* The transition system picks up its root subject from this rule. */
2863 body {
2864 view-transition-name: root;
2865 }
fc1817aClaude2866`;