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.tsxBlame2994 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}) => {
81201ccTest User42 // Default to "light" — feedback from operators was the dark default
43 // felt too gamer-ish and not what senior platform engineers expect from
44 // a tool they'd evaluate alongside Vercel / Linear / Stripe. Users who
45 // explicitly want dark can flip via the theme toggle (cookie persists).
46 const initialTheme = theme === "dark" ? "dark" : "light";
05cdb85Claude47 const build = getBuildInfo();
5f2e749Claude48 // L10 — when `fullTitle` is provided, use it verbatim (no " — gluecron"
49 // suffix); otherwise fall back to the existing `title` + suffix behaviour.
50 const renderedTitle = fullTitle
51 ? fullTitle
52 : title
53 ? `${title} — gluecron`
54 : "gluecron";
fc1817aClaude55 return (
6fc53bdClaude56 <html lang="en" data-theme={initialTheme}>
fc1817aClaude57 <head>
58 <meta charset="UTF-8" />
59 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
eae38d1Claude60 <meta name="theme-color" content="#0d1117" />
44fe49bClaude61 {/* PWA removed 2026-05-16 — repeated reload-loop bugs (admin
62 dashboard, deploy pill, admin-screen flash). A git host has
63 no use for service workers or install-as-app. Manifest link
64 and SW registrations are gone permanently. */}
eae38d1Claude65 <link rel="icon" type="image/svg+xml" href="/icon.svg" />
5f2e749Claude66 <title>{renderedTitle}</title>
67 {description && <meta name="description" content={description} />}
68 {(ogTitle || fullTitle || title) && (
69 <meta property="og:title" content={ogTitle ?? fullTitle ?? renderedTitle} />
70 )}
71 {(ogDescription || description) && (
72 <meta
73 property="og:description"
74 content={ogDescription ?? description ?? ""}
75 />
76 )}
77 {ogType && <meta property="og:type" content={ogType} />}
78 {twitterCard && <meta name="twitter:card" content={twitterCard} />}
fa880f2Claude79 <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
80 <style dangerouslySetInnerHTML={{ __html: css }} />
81 <style dangerouslySetInnerHTML={{ __html: hljsThemeCss }} />
fc1817aClaude82 </head>
83 <body>
4a52a98Claude84 <div class="prelaunch-banner" role="status" aria-live="polite">
85 Pre-launch &mdash; Gluecron is in final validation. Public signups
86 and git hosting for non-owner users open after launch review.
87 </div>
cd4f63bTest User88 {/* Block Q3 — Playground banner. Renders only when the active
89 user is a playground account with a future expiry; the small
90 inline script counts down once per minute. Strip is dismissible
91 per-page-load (re-appears on next nav) — gentle, not noisy. */}
92 {user && (user as any).isPlayground && (user as any).playgroundExpiresAt && (
93 <div
94 class="playground-banner"
95 role="status"
96 aria-live="polite"
97 data-playground-expires={
98 ((user as any).playgroundExpiresAt instanceof Date
99 ? (user as any).playgroundExpiresAt.toISOString()
100 : String((user as any).playgroundExpiresAt))
101 }
102 >
103 <span class="playground-banner-icon" aria-hidden="true">{"\u{1F3AE}"}</span>
104 <span class="playground-banner-text">
105 Playground account &mdash;{" "}
106 <span class="playground-banner-countdown">expires soon</span>.{" "}
107 <a href="/play/claim" class="playground-banner-cta">
108 Save your work &rarr;
109 </a>
110 </span>
111 <button
112 type="button"
113 class="playground-banner-dismiss"
114 aria-label="Dismiss"
115 data-playground-dismiss="1"
116 >
117 {"×"}
118 </button>
119 <script
120 dangerouslySetInnerHTML={{
121 __html: /* js */ `
122 (function () {
123 var el = document.currentScript && document.currentScript.parentElement;
124 if (!el) return;
125 var iso = el.getAttribute('data-playground-expires');
126 if (!iso) return;
127 var target = Date.parse(iso);
128 if (isNaN(target)) return;
129 var out = el.querySelector('.playground-banner-countdown');
130 function render() {
131 var ms = target - Date.now();
132 if (!out) return;
133 if (ms <= 0) { out.textContent = 'expired'; return; }
134 var mins = Math.floor(ms / 60000);
135 var hrs = Math.floor(mins / 60);
136 if (hrs > 1) out.textContent = hrs + ' hours left';
137 else if (hrs === 1) out.textContent = '1 hour left';
138 else if (mins > 1) out.textContent = mins + ' minutes left';
139 else out.textContent = 'less than a minute left';
140 }
141 render();
142 setInterval(render, 60000);
143 var dismiss = el.querySelector('[data-playground-dismiss="1"]');
144 if (dismiss) {
145 dismiss.addEventListener('click', function () {
146 el.style.display = 'none';
147 });
148 }
149 })();
150 `,
151 }}
152 />
153 </div>
154 )}
fc1817aClaude155 <header>
156 <nav>
157 <a href="/" class="logo">
158 gluecron
159 </a>
3ef4c9dClaude160 <div class="nav-search">
001af43Claude161 <form method="get" action="/search">
3ef4c9dClaude162 <input
163 type="search"
164 name="q"
165 placeholder="Search (press /)"
166 aria-label="Search"
167 />
168 </form>
169 </div>
06d5ffeClaude170 <div class="nav-right">
f764c07Claude171 {/* Block N3 — site-admin platform-deploy status pill. Hidden
172 by default; revealed client-side once /admin/deploys/latest.json
173 responds 200 (non-admins get 401/403 and the pill stays
174 hidden). Subscribes to the `platform:deploys` SSE topic
175 for live updates. See `deployPillScript` below. */}
176 {user && (
177 <a
178 id="deploy-pill"
179 href="/admin/deploys"
180 class="nav-deploy-pill"
181 style="display:none"
182 aria-label="Platform deploy status"
183 title="Platform deploy status"
184 >
185 <span class="deploy-pill-dot" />
186 <span class="deploy-pill-text">Deploys</span>
187 </a>
188 )}
6fc53bdClaude189 <a
190 href="/theme/toggle"
191 class="nav-link nav-theme"
192 title="Toggle theme"
193 aria-label="Toggle theme"
194 >
195 <span class="theme-icon-dark">{"\u263E"}</span>
196 <span class="theme-icon-light">{"\u2600"}</span>
197 </a>
c81ab7aClaude198 <a href="/explore" class="nav-link">
199 Explore
200 </a>
06d5ffeClaude201 {user ? (
202 <>
f1ab587Claude203 <a href="/dashboard" class="nav-link" style="font-weight: 600">
204 Dashboard
205 </a>
bdbd0deClaude206 <a href="/import" class="nav-link">
207 Import
208 </a>
06d5ffeClaude209 <a href="/new" class="btn btn-sm btn-primary">
210 + New
211 </a>
212 <a href={`/${user.username}`} class="nav-user">
213 {user.displayName || user.username}
214 </a>
215 <a href="/settings" class="nav-link">
216 Settings
217 </a>
218 <a href="/logout" class="nav-link">
219 Sign out
220 </a>
221 </>
222 ) : (
223 <>
224 <a href="/login" class="nav-link">
225 Sign in
226 </a>
227 <a href="/register" class="btn btn-sm btn-primary">
228 Register
229 </a>
230 </>
231 )}
232 </div>
fc1817aClaude233 </nav>
234 </header>
45e31d0Claude235 <main id="main-content">{children}</main>
cf9178bTest User236 {/* Global toast host — populated by the toastScript below from
237 ?success= / ?error= / ?toast= query params. Replaces the
238 per-page banner pattern with one polished slide-in. */}
239 <div
240 id="toast-host"
241 aria-live="polite"
242 aria-atomic="true"
243 style="position:fixed;top:calc(var(--header-h) + 12px);right:16px;z-index:var(--z-toast,10000);display:flex;flex-direction:column;gap:8px;pointer-events:none"
244 />
fc1817aClaude245 <footer>
958d26aClaude246 <div class="footer-inner">
247 <div class="footer-brand">
248 <a href="/" class="logo">gluecron</a>
249 <p class="footer-tag">
250 AI-native code intelligence. Self-hosted git, automated CI,
251 push-time gates. Software that ships itself.
252 </p>
253 </div>
254 <div class="footer-links">
255 <div class="footer-col">
256 <div class="footer-col-title">Product</div>
b0148e9Claude257 <a href="/features">Features</a>
258 <a href="/pricing">Pricing</a>
958d26aClaude259 <a href="/explore">Explore</a>
260 <a href="/marketplace">Marketplace</a>
261 </div>
262 <div class="footer-col">
263 <div class="footer-col-title">Platform</div>
b0148e9Claude264 <a href="/help">Quickstart</a>
958d26aClaude265 <a href="/status">Status</a>
266 <a href="/api/graphql">GraphQL</a>
267 <a href="/mcp">MCP server</a>
268 </div>
269 <div class="footer-col">
b0148e9Claude270 <div class="footer-col-title">Company</div>
271 <a href="/about">About</a>
958d26aClaude272 <a href="/terms">Terms</a>
273 <a href="/privacy">Privacy</a>
274 <a href="/acceptable-use">Acceptable use</a>
275 </div>
276 </div>
277 </div>
278 <div class="footer-bottom">
279 <span>&copy; {new Date().getFullYear()} gluecron</span>
05cdb85Claude280 <span class="footer-build" title={`commit ${build.shaFull}\nbuilt ${build.builtAt}`}>
281 <span class="footer-build-dot" aria-hidden="true" />
282 {build.sha} · {build.branch}
283 </span>
36b4cbdClaude284 </div>
c63b860Claude285 {siteBannerText ? (
286 <div
287 class={`footer-banner footer-banner-${siteBannerLevel || "info"}`}
288 role="status"
289 aria-live="polite"
290 >
291 {siteBannerText}
292 </div>
293 ) : null}
fc1817aClaude294 </footer>
05cdb85Claude295 {/* Live update poller — checks /api/version every 15s, prompts
296 reload when the running sha changes. Pure progressive-
297 enhancement; degrades to nothing if JS is off. */}
298 <div
299 id="version-banner"
300 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"
301 >
302 <span style="display:inline-flex;align-items:center;gap:8px">
303 <span style="width:8px;height:8px;border-radius:50%;background:#34d399;box-shadow:0 0 10px rgba(52,211,153,0.6)" />
304 <span>New version available</span>
305 </span>
306 <button
307 type="button"
308 id="version-banner-reload"
309 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"
310 >
311 Reload
312 </button>
313 </div>
fa880f2Claude314 <script dangerouslySetInnerHTML={{ __html: versionPollerScript }} />
f764c07Claude315 {/* Block N3 — site-admin deploy status pill (script-only). The pill
316 container is rendered above for authed users; this script bootstraps
317 it by fetching /admin/deploys/latest.json. Non-admins get 401/403
318 and the pill stays display:none — zero leak. */}
319 {user && (
320 <script dangerouslySetInnerHTML={{ __html: deployPillScript }} />
321 )}
699e5c7Claude322 {/* Block I4 — Command palette shell (hidden by default) */}
323 <div
324 id="cmdk-backdrop"
325 style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9998"
326 />
327 <div
328 id="cmdk-panel"
329 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"
330 >
331 <input
332 id="cmdk-input"
333 type="text"
334 placeholder="Type a command..."
335 aria-label="Command palette"
dc26881CC LABS App336 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"
699e5c7Claude337 />
338 <div id="cmdk-list" style="max-height:60vh;overflow-y:auto" />
339 </div>
fa880f2Claude340 <script dangerouslySetInnerHTML={{ __html: clientJs }} />
44fe49bClaude341 {/* PWA-kill script: actively unregisters any service worker
342 previously installed under gluecron.com. Recovers any browser
343 still trapped in the SW reload loop from the legacy registrations. */}
344 <script dangerouslySetInnerHTML={{ __html: pwaKillSwitchScript }} />
cf9178bTest User345 <script dangerouslySetInnerHTML={{ __html: toastScript }} />
fa880f2Claude346 <script dangerouslySetInnerHTML={{ __html: navScript }} />
fc1817aClaude347 </body>
348 </html>
349 );
350};
351
05cdb85Claude352// Live version poller. Checks /api/version every 15s; if the sha differs
353// from the one we booted with, reveal the floating 'New version' pill so
354// the user can reload onto the new code without manually refreshing.
355const versionPollerScript = `
356 (function(){
357 var loadedSha = null;
358 var banner, btn;
359 function poll(){
360 fetch('/api/version', { cache: 'no-store' })
361 .then(function(r){ return r.ok ? r.json() : null; })
362 .then(function(j){
363 if (!j || !j.sha) return;
364 if (loadedSha === null) { loadedSha = j.sha; return; }
365 if (j.sha !== loadedSha && banner) {
366 banner.style.display = 'inline-flex';
367 }
368 })
369 .catch(function(){});
370 }
371 function init(){
372 banner = document.getElementById('version-banner');
373 btn = document.getElementById('version-banner-reload');
374 if (btn) btn.addEventListener('click', function(){ window.location.reload(); });
375 poll();
376 setInterval(poll, 15000);
377 }
378 if (document.readyState === 'loading') {
379 document.addEventListener('DOMContentLoaded', init);
380 } else {
381 init();
382 }
383 })();
384`;
385
f764c07Claude386// Block N3 — site-admin deploy status pill. Fetches /admin/deploys/latest.json;
387// if 200, reveals the pill in the nav and subscribes to the `platform:deploys`
388// SSE topic for live status updates. Non-admins get 401/403 and the pill stays
389// display:none — there's no leakage of admin-only data to other users.
390//
391// Pill states (CSS classes on the container drive colour):
392// .deploy-pill-success 🟢 "Deployed 12s ago"
393// .deploy-pill-progress 🟡 "Deploying… 14s" (pulsing dot)
394// .deploy-pill-failed 🔴 "Deploy failed 1m ago"
395// .deploy-pill-empty ⚪ "No deploys yet"
396//
397// Relative-time auto-refreshes every 15s without re-fetching.
398export const deployPillScript = `
399 (function(){
400 var pill, dot, text;
401 var state = { latest: null, asOf: null };
402
403 function classifyAge(ms){
404 if (ms < 0) return 'just now';
405 var s = Math.floor(ms / 1000);
406 if (s < 5) return 'just now';
407 if (s < 60) return s + 's ago';
408 var m = Math.floor(s / 60);
409 if (m < 60) return m + 'm ago';
410 var h = Math.floor(m / 60);
411 if (h < 24) return h + 'h ago';
412 var d = Math.floor(h / 24);
413 return d + 'd ago';
414 }
415 function elapsed(ms){
416 if (ms < 0) ms = 0;
417 var s = Math.floor(ms / 1000);
418 if (s < 60) return s + 's';
419 var m = Math.floor(s / 60);
420 var rem = s - m * 60;
421 return m + 'm ' + rem + 's';
422 }
423
424 function render(){
425 if (!pill || !text || !dot) return;
426 var d = state.latest;
81201ccTest User427 // When there's no deploy event yet, keep the pill HIDDEN. Showing
428 // "No deploys yet" was visible noise on every admin page load and
429 // flashed during reconnect cycles — admins don't need a placeholder.
430 // The pill reveals itself the first time a real deploy fires.
f764c07Claude431 if (!d) {
81201ccTest User432 pill.style.display = 'none';
f764c07Claude433 return;
434 }
435 pill.style.display = 'inline-flex';
436 var now = Date.now();
437 if (d.status === 'in_progress') {
438 pill.className = 'nav-deploy-pill deploy-pill-progress';
439 var started = Date.parse(d.started_at) || now;
440 text.textContent = 'Deploying… ' + elapsed(now - started);
441 } else if (d.status === 'succeeded') {
442 pill.className = 'nav-deploy-pill deploy-pill-success';
443 var ref = Date.parse(d.finished_at || d.started_at) || now;
444 text.textContent = 'Deployed ' + classifyAge(now - ref);
445 } else if (d.status === 'failed') {
446 pill.className = 'nav-deploy-pill deploy-pill-failed';
447 var refF = Date.parse(d.finished_at || d.started_at) || now;
448 text.textContent = 'Deploy failed ' + classifyAge(now - refF);
449 } else {
450 pill.className = 'nav-deploy-pill';
451 text.textContent = d.status;
452 }
453 }
454
455 function fetchLatest(){
456 fetch('/admin/deploys/latest.json', { cache: 'no-store', credentials: 'same-origin' })
457 .then(function(r){ if (!r.ok) return null; return r.json(); })
458 .then(function(j){
459 if (!j || j.ok !== true) return;
460 state.latest = j.latest;
461 state.asOf = j.asOf;
462 render();
463 subscribe();
464 })
465 .catch(function(){});
466 }
467
468 var subscribed = false;
469 function subscribe(){
470 if (subscribed) return;
471 if (typeof EventSource === 'undefined') return;
472 subscribed = true;
473 var es;
bf19c50Test User474 // Exponential backoff with cap. Previously a tight 1500ms reconnect
475 // produced visible looping in the nav whenever the proxy timed out
476 // or the connection blipped — the bottom-of-page deploy pill
477 // re-rendered the placeholder every 1.5s. Cap at 60s and reset on
478 // successful message receipt.
479 var delay = 2000;
480 var DELAY_MAX = 60000;
481 function bump(){
482 delay = Math.min(delay * 2, DELAY_MAX);
483 }
484 function resetDelay(){
485 delay = 2000;
486 }
f764c07Claude487 function connect(){
488 try { es = new EventSource('/live-events/platform:deploys'); }
bf19c50Test User489 catch(e){ bump(); setTimeout(connect, delay); return; }
f764c07Claude490 es.onmessage = function(m){
bf19c50Test User491 resetDelay();
f764c07Claude492 try {
493 var d = JSON.parse(m.data);
494 if (d && d.run_id) {
495 if (!state.latest || state.latest.run_id === d.run_id ||
496 Date.parse(d.started_at) >= Date.parse(state.latest.started_at)) {
497 state.latest = d;
498 render();
499 }
500 }
501 } catch(e){}
502 };
503 es.onerror = function(){
504 try { es.close(); } catch(e){}
bf19c50Test User505 bump();
f764c07Claude506 setTimeout(connect, delay);
507 };
508 }
509 connect();
510 }
511
512 function init(){
513 pill = document.getElementById('deploy-pill');
514 if (!pill) return;
515 dot = pill.querySelector('.deploy-pill-dot');
516 text = pill.querySelector('.deploy-pill-text');
517 fetchLatest();
518 // Refresh relative-time labels every 15s so "12s ago" → "27s ago"
519 // without a fresh fetch.
520 setInterval(render, 15000);
521 }
522 if (document.readyState === 'loading') {
523 document.addEventListener('DOMContentLoaded', init);
524 } else {
525 init();
526 }
527 })();
528`;
529
6fc53bdClaude530// Runs before paint — reads the theme cookie and flips data-theme so there's
81201ccTest User531// no light-to-dark flash on load. SSR default is "light"; cookie-set "dark"
532// is honoured for users who explicitly opted in.
6fc53bdClaude533const themeInitScript = `
534 (function(){
535 try {
536 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
81201ccTest User537 var t = m ? decodeURIComponent(m[1]) : 'light';
538 if (t !== 'light' && t !== 'dark') t = 'light';
6fc53bdClaude539 document.documentElement.setAttribute('data-theme', t);
540 } catch(_){}
541 })();
542`;
543
eae38d1Claude544// Block G1 — register service worker for offline / install support.
545// Kept inline (and tiny) so we don't block first paint.
b1be050CC LABS App546//
cf9178bTest User547// Global toast notifications — reads ?success=, ?error=, ?toast= from the
548// URL on page load and surfaces a polished slide-in toast instead of the
549// per-page banner divs that crowded the layout. Toasts auto-dismiss after
550// 4.5s; query params are scrubbed from the URL via history.replaceState
551// so a subsequent Refresh doesn't re-fire the same toast.
552//
553// Variants: ?success=…, ?error=…, ?toast=info:…, ?toast=warn:… All values
554// must be URI-encoded (callers already do this via encodeURIComponent
555// in c.redirect()).
556const toastScript = `
557 (function(){
558 function showToast(kind, message){
559 if (!message) return;
560 var host = document.getElementById('toast-host');
561 if (!host) return;
562 var el = document.createElement('div');
563 el.className = 'gx-toast gx-toast--' + kind;
564 el.setAttribute('role', kind === 'error' ? 'alert' : 'status');
565 var icon = document.createElement('span');
566 icon.className = 'gx-toast__icon';
567 icon.textContent = kind === 'success' ? '\\u2713'
568 : kind === 'error' ? '\\u00D7'
569 : kind === 'warn' ? '!'
570 : 'i';
571 el.appendChild(icon);
572 var text = document.createElement('span');
573 text.className = 'gx-toast__text';
574 text.textContent = message;
575 el.appendChild(text);
576 var close = document.createElement('button');
577 close.type = 'button';
578 close.className = 'gx-toast__close';
579 close.setAttribute('aria-label', 'Dismiss notification');
580 close.textContent = '\\u00D7';
581 close.addEventListener('click', function(){ dismiss(); });
582 el.appendChild(close);
583 host.appendChild(el);
584 // Force a reflow then add the visible class so the slide-in transitions.
585 void el.offsetWidth;
586 el.classList.add('gx-toast--in');
587 var timer = setTimeout(dismiss, 4500);
588 function dismiss(){
589 clearTimeout(timer);
590 el.classList.remove('gx-toast--in');
591 el.classList.add('gx-toast--out');
592 setTimeout(function(){
593 if (el.parentNode) el.parentNode.removeChild(el);
594 }, 220);
595 }
596 }
597 try {
598 var url = new URL(window.location.href);
599 var hits = 0;
600 var s = url.searchParams.get('success');
601 if (s) { showToast('success', s); url.searchParams.delete('success'); hits++; }
602 var e = url.searchParams.get('error');
603 if (e) { showToast('error', e); url.searchParams.delete('error'); hits++; }
604 var t = url.searchParams.get('toast');
605 if (t) {
606 var ix = t.indexOf(':');
607 var kind = ix > 0 ? t.slice(0, ix) : 'info';
608 var msg = ix > 0 ? t.slice(ix + 1) : t;
609 showToast(kind, msg);
610 url.searchParams.delete('toast');
611 hits++;
612 }
613 if (hits > 0 && window.history && window.history.replaceState) {
614 window.history.replaceState({}, '', url.pathname + (url.searchParams.toString() ? '?' + url.searchParams.toString() : '') + url.hash);
615 }
616 } catch(_) {}
617 })();
618`;
619
44fe49bClaude620// PWA kill-switch (2026-05-16) — replaces the previous pwaRegisterScript
621// and pwaInstallBannerScript. Those two scripts registered /sw.js and
622// /sw-push.js at the same scope, causing a reload loop that made the
623// admin dashboard unusable (deploy pill flashing, typing wiped, buttons
624// uncllickable). Per the SW spec, only one SW can control a scope; two
625// different script URLs at the same scope keep replacing each other.
6345c3eTest User626//
44fe49bClaude627// PWA is gone for good. A git host has no use for service workers,
628// install-as-app, or push notifications via SW. This script actively
629// unregisters every previously installed SW on the gluecron.com origin
630// so any browser still trapped in the loop recovers on the very next
631// page load — without needing the user to clear site data or open
632// DevTools. Idempotent and safe to keep running forever; once all
633// browsers have been cleaned, it's a no-op.
634const pwaKillSwitchScript = `
534f04aClaude635(function(){
636 try {
44fe49bClaude637 if (!('serviceWorker' in navigator)) return;
638 if (!navigator.serviceWorker.getRegistrations) return;
639 navigator.serviceWorker.getRegistrations().then(function(regs){
640 if (!regs || regs.length === 0) return;
641 regs.forEach(function(reg){
642 try { reg.unregister(); } catch(_){}
643 });
644 }).catch(function(){});
645 // Also drop any caches the old SWs left behind so the user gets
646 // truly fresh HTML on every page load. Restricted to gluecron-*
647 // namespaced caches so we don't trample anything a future opt-in
648 // feature might create under a different name.
649 if ('caches' in self) {
650 caches.keys().then(function(keys){
651 keys.forEach(function(k){
652 if (typeof k === 'string' && k.indexOf('gluecron') === 0) {
653 try { caches.delete(k); } catch(_){}
d7ba05dClaude654 }
655 });
656 }).catch(function(){});
534f04aClaude657 }
658 } catch(_) {}
659})();
660`;
661
3ef4c9dClaude662const navScript = `
663 (function(){
664 var chord = null;
665 var chordTimer = null;
666 function isTyping(t){
667 t = t || {};
668 var tag = (t.tagName || '').toLowerCase();
669 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
670 }
71cd5ecClaude671
672 // ---------- Block I4 — Command palette ----------
673 var COMMANDS = [
674 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
675 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
676 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
677 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
678 { label: 'Create new repository', href: '/new', kw: 'add create' },
679 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
680 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
681 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
682 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
683 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
684 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
685 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
686 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
687 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
688 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
689 { label: 'Gists', href: '/gists', kw: 'snippets' },
690 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
691 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
692 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
693 ];
694
695 function fuzzyMatch(item, q){
696 if (!q) return true;
697 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
698 q = q.toLowerCase();
699 var qi = 0;
700 for (var i = 0; i < hay.length && qi < q.length; i++) {
701 if (hay[i] === q[qi]) qi++;
702 }
703 return qi === q.length;
704 }
705
706 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
707
708 function render(){
709 if (!list) return;
710 var html = '';
711 for (var i = 0; i < filtered.length; i++) {
712 var item = filtered[i];
713 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
714 var bg = i === selected ? 'background:var(--bg);' : '';
ea52715copilot-swe-agent[bot]715 html += '<div class="' + cls + '" data-idx="' + i + '" data-url="' + item.href + '"' +
dc26881CC LABS App716 ' style="padding:var(--space-2) var(--space-4);cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
71cd5ecClaude717 '<div>' + item.label + '</div>' +
718 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
719 '</div>';
720 }
721 if (filtered.length === 0) {
dc26881CC LABS App722 html = '<div style="padding:var(--space-4);color:var(--text-muted);text-align:center">No matches.</div>';
71cd5ecClaude723 }
724 list.innerHTML = html;
725 }
726
727 function openPalette(){
728 backdrop = document.getElementById('cmdk-backdrop');
729 panel = document.getElementById('cmdk-panel');
730 input = document.getElementById('cmdk-input');
731 list = document.getElementById('cmdk-list');
732 if (!backdrop || !panel) return;
733 backdrop.style.display = 'block';
734 panel.style.display = 'block';
735 input.value = '';
736 selected = 0;
737 filtered = COMMANDS.slice();
738 render();
739 input.focus();
740 }
741 function closePalette(){
742 if (backdrop) backdrop.style.display = 'none';
743 if (panel) panel.style.display = 'none';
744 }
745 function go(href){ closePalette(); window.location.href = href; }
746
747 document.addEventListener('click', function(e){
748 var t = e.target;
749 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
750 var item = t && t.closest && t.closest('.cmdk-item');
ea52715copilot-swe-agent[bot]751 if (item) { go(item.getAttribute('data-url')); }
71cd5ecClaude752 });
753
754 document.addEventListener('input', function(e){
755 if (e.target && e.target.id === 'cmdk-input') {
756 var q = e.target.value;
757 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
758 selected = 0;
759 render();
760 }
761 });
762
3ef4c9dClaude763 document.addEventListener('keydown', function(e){
71cd5ecClaude764 // Palette-scoped keys take priority when open
765 if (panel && panel.style.display === 'block') {
766 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
767 if (e.key === 'ArrowDown') {
768 e.preventDefault();
769 selected = Math.min(filtered.length - 1, selected + 1);
770 render();
771 return;
772 }
773 if (e.key === 'ArrowUp') {
774 e.preventDefault();
775 selected = Math.max(0, selected - 1);
776 render();
777 return;
778 }
779 if (e.key === 'Enter') {
780 e.preventDefault();
781 var item = filtered[selected];
782 if (item) go(item.href);
783 return;
784 }
785 return;
786 }
787
3ef4c9dClaude788 if (isTyping(e.target)) return;
789 // Single key shortcuts
790 if (e.key === '/') {
791 var el = document.querySelector('.nav-search input');
792 if (el) { e.preventDefault(); el.focus(); return; }
793 }
794 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude795 e.preventDefault();
796 openPalette();
797 return;
3ef4c9dClaude798 }
799 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
800 e.preventDefault(); window.location.href = '/shortcuts'; return;
801 }
802 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
803 e.preventDefault(); window.location.href = '/new'; return;
804 }
805 // "g" chord
806 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
807 chord = 'g';
808 clearTimeout(chordTimer);
809 chordTimer = setTimeout(function(){ chord = null; }, 1200);
810 return;
811 }
812 if (chord === 'g') {
813 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
814 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
815 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
816 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
817 chord = null;
818 }
819 });
820 })();
821`;
822
fc1817aClaude823const css = `
2ce1d0bClaude824 /* ================================================================
958d26aClaude825 * Gluecron design system — 2026.05 "Editorial-Technical"
826 * Slate-noir base · refined violet signature · hairline geometry ·
827 * mono-as-feature · cinematic motion · Inter Tight + JetBrains Mono.
828 * All class names preserved for back-compat across 50+ route views.
2ce1d0bClaude829 * ============================================================== */
6fc53bdClaude830 :root, :root[data-theme='dark'] {
958d26aClaude831 /* Surfaces — slate, not black. More depth, less crush. */
832 --bg: #08090f;
833 --bg-secondary: #0c0d14;
834 --bg-tertiary: #11131c;
835 --bg-elevated: #0f111a;
836 --bg-surface: #161826;
837 --bg-hover: rgba(255,255,255,0.04);
838 --bg-active: rgba(255,255,255,0.08);
839 --bg-inset: rgba(0,0,0,0.30);
840
841 /* Borders — three weights, used deliberately */
842 --border: rgba(255,255,255,0.06);
843 --border-subtle: rgba(255,255,255,0.035);
844 --border-strong: rgba(255,255,255,0.13);
845 --border-focus: rgba(140,109,255,0.55);
846
847 /* Text */
848 --text: #ededf2;
849 --text-strong: #f7f7fb;
850 --text-muted: #8b8c9c;
851 --text-faint: #555665;
852 --text-link: #b69dff;
853
854 /* Accent — refined violet (less candy), warm amber as secondary signal */
855 --accent: #8c6dff;
856 --accent-2: #36c5d6;
857 --accent-warm: #ffb45e;
858 --accent-hover: #a48bff;
859 --accent-pressed:#7559e8;
860 --accent-gradient: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
861 --accent-gradient-soft: linear-gradient(135deg, rgba(140,109,255,0.18) 0%, rgba(54,197,214,0.18) 100%);
862 --accent-gradient-faint: linear-gradient(135deg, rgba(140,109,255,0.07) 0%, rgba(54,197,214,0.07) 100%);
863 --accent-glow: 0 0 24px rgba(140,109,255,0.28);
864
865 /* Semantic */
866 --green: #34d399;
867 --red: #f87171;
868 --yellow: #fbbf24;
869 --amber: #fbbf24;
870 --blue: #60a5fa;
871
fb71554Claude872 /* Type — system fonts FIRST so we never depend on Google Fonts loading.
873 Segoe UI (Win), -apple-system / SF (Mac), Roboto (Android), Inter as
874 optional upgrade if the user already has it. NEVER falls back to serif. */
875 --font-mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'Cascadia Mono', Menlo, Consolas, 'Courier New', monospace;
876 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, 'Inter', sans-serif;
877 --font-display: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, 'Inter Tight', 'Inter', sans-serif;
958d26aClaude878 --mono-feat: 'calt', 'liga', 'ss01';
879
880 /* Radius — sharper than before */
881 --r-sm: 5px;
882 --r: 7px;
883 --r-md: 9px;
884 --r-lg: 12px;
885 --r-xl: 16px;
886 --r-2xl: 22px;
887 --r-full: 9999px;
888 --radius: 7px;
889
890 /* Type scale — bigger display sizes for editorial feel */
891 --t-xs: 11px;
892 --t-sm: 13px;
893 --t-base: 14px;
894 --t-md: 16px;
895 --t-lg: 20px;
896 --t-xl: 28px;
897 --t-2xl: 40px;
898 --t-3xl: 56px;
899 --t-display: 72px;
900 --t-display-lg:96px;
901
902 /* Spacing — 4px base */
2ce1d0bClaude903 --s-0: 0;
958d26aClaude904 --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
905 --s-5: 20px; --s-6: 24px; --s-7: 28px; --s-8: 32px;
906 --s-10:40px; --s-12:48px; --s-14:56px; --s-16:64px;
907 --s-20:80px; --s-24:96px; --s-32:128px;
908
909 /* Elevation — softer + more layered */
910 --elev-0: 0 0 0 1px var(--border);
911 --elev-1: 0 1px 2px rgba(0,0,0,0.50), 0 0 0 1px var(--border);
912 --elev-2: 0 8px 24px -8px rgba(0,0,0,0.60), 0 0 0 1px var(--border);
913 --elev-3: 0 20px 48px -12px rgba(0,0,0,0.70), 0 0 0 1px var(--border-strong);
914 --elev-glow: 0 0 0 1px rgba(140,109,255,0.40), 0 0 32px -4px rgba(140,109,255,0.30);
915 --ring: 0 0 0 3px rgba(140,109,255,0.28);
916 --ring-warn: 0 0 0 3px rgba(251,191,36,0.28);
917 --ring-err: 0 0 0 3px rgba(248,113,113,0.28);
918
919 /* Motion */
920 --ease: cubic-bezier(0.16, 1, 0.3, 1);
921 --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
922 --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
923 --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
924 --t-fast: 120ms;
925 --t-base: 200ms;
926 --t-slow: 360ms;
927 --t-slower:560ms;
928
929 --header-h: 60px;
c63b860Claude930
931 /* Block O3 — visual coherence: named token aliases (additive). */
932 --space-1: var(--s-1);
933 --space-2: var(--s-2);
934 --space-3: var(--s-3);
935 --space-4: var(--s-4);
936 --space-5: var(--s-5);
937 --space-6: var(--s-6);
938 --space-8: var(--s-8);
939 --space-10: var(--s-10);
940 --space-12: var(--s-12);
941 --space-16: var(--s-16);
942 --space-20: var(--s-20);
943 --space-24: var(--s-24);
944 --radius-sm: var(--r-sm);
945 --radius-md: var(--r-md);
946 --radius-lg: var(--r-lg);
947 --radius-xl: var(--r-xl);
948 --radius-full: var(--r-full);
949 --font-size-xs: var(--t-xs);
950 --font-size-sm: var(--t-sm);
951 --font-size-base: var(--t-base);
952 --font-size-md: var(--t-md);
953 --font-size-lg: var(--t-lg);
954 --font-size-xl: var(--t-xl);
955 --font-size-2xl: var(--t-2xl);
956 --font-size-3xl: var(--t-3xl);
957 --font-size-hero: var(--t-display);
958 --leading-tight: 1.2;
959 --leading-snug: 1.35;
960 --leading-normal: 1.5;
961 --leading-relaxed: 1.6;
962 --leading-loose: 1.7;
963 --z-base: 1;
964 --z-nav: 10;
965 --z-sticky: 50;
966 --z-overlay: 100;
967 --z-modal: 1000;
968 --z-toast: 10000;
fc1817aClaude969 }
970
6fc53bdClaude971 :root[data-theme='light'] {
958d26aClaude972 --bg: #fbfbfc;
4c47454Claude973 --bg-secondary: #ffffff;
958d26aClaude974 --bg-tertiary: #f3f3f6;
975 --bg-elevated: #ffffff;
976 --bg-surface: #f6f6f9;
977 --bg-hover: rgba(0,0,0,0.035);
978 --bg-active: rgba(0,0,0,0.07);
979 --bg-inset: rgba(0,0,0,0.04);
980
981 --border: rgba(15,16,28,0.08);
982 --border-subtle: rgba(15,16,28,0.04);
983 --border-strong: rgba(15,16,28,0.16);
984
985 --text: #0e1020;
986 --text-strong: #050617;
987 --text-muted: #5a5b70;
988 --text-faint: #8a8b9e;
989 --text-link: #6d4dff;
990
991 --accent: #6d4dff;
992 --accent-2: #0891b2;
993 --accent-hover: #5a3df0;
994 --accent-pressed:#4a30d6;
995 --accent-glow: 0 0 24px rgba(109,77,255,0.18);
996
997 --green: #059669;
998 --red: #dc2626;
4c47454Claude999 --yellow: #d97706;
958d26aClaude1000
1001 --elev-1: 0 1px 2px rgba(15,16,28,0.06), 0 0 0 1px var(--border);
1002 --elev-2: 0 8px 24px -10px rgba(15,16,28,0.10), 0 0 0 1px var(--border);
1003 --elev-3: 0 20px 48px -16px rgba(15,16,28,0.14), 0 0 0 1px var(--border-strong);
6fc53bdClaude1004 }
1005
1006 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
958d26aClaude1007 .nav-theme { display: inline-flex; align-items: center; font-size: 15px; line-height: 1; opacity: 0.85; }
1008 .nav-theme:hover { opacity: 1; }
6fc53bdClaude1009 :root[data-theme='dark'] .theme-icon-dark { display: none; }
1010 :root[data-theme='light'] .theme-icon-light { display: none; }
1011
fc1817aClaude1012 * { margin: 0; padding: 0; box-sizing: border-box; }
958d26aClaude1013 *::selection { background: rgba(140,109,255,0.32); color: var(--text-strong); }
2ce1d0bClaude1014
958d26aClaude1015 html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
fc1817aClaude1016
1017 body {
1018 font-family: var(--font-sans);
1019 background: var(--bg);
1020 color: var(--text);
cf9178bTest User1021 font-size: 15px;
2ce1d0bClaude1022 line-height: 1.55;
958d26aClaude1023 letter-spacing: -0.011em;
fc1817aClaude1024 min-height: 100vh;
1025 display: flex;
1026 flex-direction: column;
958d26aClaude1027 font-feature-settings: 'cv11', 'ss01', 'ss03', 'calt';
cf9178bTest User1028 /* Subtle: prefers grayscale font smoothing on macOS for thin text,
1029 and disables automatic synthesis of bold/italic which can produce
1030 muddier rendering on certain weights. */
1031 -webkit-font-smoothing: antialiased;
1032 -moz-osx-font-smoothing: grayscale;
1033 font-synthesis: none;
1034 }
1035 /* Tighten heading rhythm — the body is 15/1.55, headings step down
1036 line-height inversely with size so display text doesn't feel airy. */
1037 h1, h2, h3, h4, h5, h6 {
1038 font-family: var(--font-display);
1039 color: var(--text-strong);
1040 letter-spacing: -0.022em;
1041 line-height: 1.18;
1042 font-weight: 650;
1043 margin-top: 0;
1044 }
1045 h1 { font-size: 28px; letter-spacing: -0.028em; }
1046 h2 { font-size: 22px; }
1047 h3 { font-size: 18px; letter-spacing: -0.018em; }
1048 h4 { font-size: 15.5px; letter-spacing: -0.012em; font-weight: 600; }
1049 /* Link refinement — underline only on hover/focus, never by default
1050 on internal nav. Prevents the "blue underline soup" look. */
1051 a { color: var(--text-link); text-decoration: none; transition: color var(--t-fast) var(--ease); }
1052 a:hover { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 3px; }
1053 a:focus-visible {
1054 outline: none;
1055 box-shadow: 0 0 0 3px rgba(109,77,255,0.32);
1056 border-radius: 3px;
fc1817aClaude1057 }
1058
958d26aClaude1059 /* Whole-page atmosphere: very subtle gradient + dot-grid layered behind everything.
1060 Keeps every page feeling like part of the same product without competing with hero art. */
2ce1d0bClaude1061 body::before {
1062 content: '';
1063 position: fixed;
1064 inset: 0;
1065 pointer-events: none;
958d26aClaude1066 z-index: -2;
2ce1d0bClaude1067 background:
958d26aClaude1068 radial-gradient(70% 55% at 85% -20%, rgba(140,109,255,0.07), transparent 65%),
1069 radial-gradient(55% 45% at -10% 115%, rgba(54,197,214,0.05), transparent 65%);
1070 }
1071 body::after {
1072 content: '';
1073 position: fixed;
1074 inset: 0;
1075 pointer-events: none;
1076 z-index: -1;
1077 background-image: radial-gradient(rgba(255,255,255,0.025) 1px, transparent 1px);
1078 background-size: 28px 28px;
1079 background-position: 0 0;
1080 opacity: 0.55;
1081 mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 70%);
1082 -webkit-mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 70%);
1083 }
1084 :root[data-theme='light'] body::before { opacity: 0.55; }
1085 :root[data-theme='light'] body::after {
1086 background-image: radial-gradient(rgba(15,16,28,0.06) 1px, transparent 1px);
1087 opacity: 0.4;
fc1817aClaude1088 }
2ce1d0bClaude1089
1090 a { color: var(--text-link); text-decoration: none; transition: color var(--t-fast) var(--ease); }
1091 a:hover { color: var(--accent-hover); text-decoration: none; }
fc1817aClaude1092
958d26aClaude1093 /* Heading scale — confident, editorial, tight tracking */
1094 h1, h2, h3, h4, h5, h6 {
1095 font-family: var(--font-display);
2ce1d0bClaude1096 font-weight: 600;
958d26aClaude1097 letter-spacing: -0.022em;
1098 line-height: 1.18;
1099 color: var(--text-strong);
1100 }
1101 h1 { font-size: var(--t-xl); letter-spacing: -0.028em; }
1102 h2 { font-size: var(--t-lg); letter-spacing: -0.022em; }
1103 h3 { font-size: var(--t-md); font-weight: 600; letter-spacing: -0.015em; }
1104 h4 { font-size: var(--t-base); font-weight: 600; letter-spacing: -0.01em; }
1105 h5, h6 { font-size: var(--t-sm); font-weight: 600; letter-spacing: -0.005em; }
1106
1107 /* Editorial display heading utility — used by landing + marketing pages */
1108 .display {
1109 font-family: var(--font-display);
1110 font-size: clamp(40px, 7.5vw, 96px);
1111 line-height: 0.98;
1112 letter-spacing: -0.04em;
1113 font-weight: 600;
1114 color: var(--text-strong);
2ce1d0bClaude1115 }
fc1817aClaude1116
958d26aClaude1117 /* Eyebrow — uppercase mono label that sits above section headings */
1118 .eyebrow {
1119 display: inline-flex;
1120 align-items: center;
1121 gap: 8px;
1122 font-family: var(--font-mono);
1123 font-size: 11px;
1124 font-weight: 500;
1125 letter-spacing: 0.14em;
1126 text-transform: uppercase;
1127 color: var(--accent);
1128 margin-bottom: var(--s-3);
1129 }
1130 .eyebrow::before {
1131 content: '';
1132 width: 18px;
1133 height: 1px;
1134 background: currentColor;
1135 opacity: 0.6;
1136 }
1137
1138 /* Section header — paired eyebrow + title + lede */
1139 .section-header { max-width: 720px; margin: 0 auto var(--s-10); text-align: center; }
1140 .section-header.left { text-align: left; margin-left: 0; margin-right: auto; }
1141 .section-header h2 {
1142 font-size: clamp(28px, 4vw, 44px);
1143 line-height: 1.05;
1144 letter-spacing: -0.028em;
1145 margin-bottom: var(--s-3);
1146 }
1147 .section-header p {
1148 color: var(--text-muted);
1149 font-size: var(--t-md);
1150 line-height: 1.6;
1151 max-width: 580px;
1152 margin: 0 auto;
1153 }
1154 .section-header.left p { margin-left: 0; }
fc1817aClaude1155
958d26aClaude1156 code, kbd, samp {
2ce1d0bClaude1157 font-family: var(--font-mono);
958d26aClaude1158 font-feature-settings: var(--mono-feat);
1159 }
1160 code {
1161 font-size: 0.9em;
2ce1d0bClaude1162 background: var(--bg-tertiary);
958d26aClaude1163 border: 1px solid var(--border-subtle);
2ce1d0bClaude1164 padding: 1px 6px;
1165 border-radius: var(--r-sm);
1166 color: var(--text);
1167 }
958d26aClaude1168 kbd, .kbd {
1169 display: inline-flex;
1170 align-items: center;
1171 padding: 1px 6px;
1172 font-family: var(--font-mono);
1173 font-size: 11px;
1174 background: var(--bg-elevated);
1175 border: 1px solid var(--border);
1176 border-bottom-width: 2px;
1177 border-radius: 4px;
1178 color: var(--text);
1179 line-height: 1.5;
1180 vertical-align: middle;
1181 }
2ce1d0bClaude1182
958d26aClaude1183 /* Mono utility for technical chrome (paths, IDs, dates) */
1184 .mono { font-family: var(--font-mono); font-feature-settings: var(--mono-feat); font-size: 0.96em; }
1185 .meta-mono { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--text-muted); letter-spacing: 0; }
1186
1187 /* Pre-launch banner — slim, refined, mono caption */
4a52a98Claude1188 .prelaunch-banner {
958d26aClaude1189 position: relative;
2ce1d0bClaude1190 background:
958d26aClaude1191 linear-gradient(180deg, rgba(251,191,36,0.10), rgba(251,191,36,0.03)),
2ce1d0bClaude1192 var(--bg);
958d26aClaude1193 border-bottom: 1px solid rgba(251,191,36,0.28);
4a52a98Claude1194 color: var(--yellow);
958d26aClaude1195 padding: 7px 24px;
1196 font-family: var(--font-mono);
1197 font-size: 11px;
4a52a98Claude1198 font-weight: 500;
1199 text-align: center;
2ce1d0bClaude1200 line-height: 1.5;
958d26aClaude1201 letter-spacing: 0.04em;
1202 text-transform: uppercase;
1203 }
1204 .prelaunch-banner::before {
1205 content: '◆';
1206 margin-right: 8px;
1207 font-size: 9px;
1208 opacity: 0.7;
1209 vertical-align: 1px;
4a52a98Claude1210 }
1211
cd4f63bTest User1212 /* Block Q3 — Playground banner. Brighter than the prelaunch strip so
1213 visitors don't miss the "save your work" CTA, but slim enough to
1214 not feel like a modal. */
1215 .playground-banner {
1216 position: relative;
1217 display: flex;
1218 align-items: center;
1219 justify-content: center;
1220 gap: 8px;
1221 background:
1222 linear-gradient(180deg, rgba(251,191,36,0.20), rgba(251,191,36,0.06)),
1223 var(--bg);
1224 border-bottom: 1px solid rgba(251,191,36,0.45);
1225 color: var(--yellow, #fbbf24);
1226 padding: 8px 40px 8px 24px;
1227 font-size: 13px;
1228 font-weight: 500;
1229 text-align: center;
1230 line-height: 1.4;
1231 }
1232 .playground-banner-icon { font-size: 14px; }
1233 .playground-banner-text { color: var(--text-strong, #e6edf3); }
1234 .playground-banner-countdown { font-weight: 600; }
1235 .playground-banner-cta {
1236 margin-left: 4px;
1237 color: var(--yellow, #fbbf24);
1238 text-decoration: underline;
1239 font-weight: 600;
1240 }
1241 .playground-banner-cta:hover { opacity: 0.85; }
1242 .playground-banner-dismiss {
1243 position: absolute;
1244 top: 50%;
1245 right: 12px;
1246 transform: translateY(-50%);
1247 background: transparent;
1248 border: none;
1249 color: var(--text-muted, #8b949e);
1250 font-size: 18px;
1251 line-height: 1;
1252 cursor: pointer;
1253 padding: 0 4px;
1254 }
1255 .playground-banner-dismiss:hover { color: var(--text-strong, #e6edf3); }
1256
958d26aClaude1257 /* Header — sticky, blurred, hairline border, taller for breathing room */
fc1817aClaude1258 header {
2ce1d0bClaude1259 position: sticky;
1260 top: 0;
1261 z-index: 100;
fc1817aClaude1262 border-bottom: 1px solid var(--border);
2ce1d0bClaude1263 padding: 0 24px;
1264 height: var(--header-h);
958d26aClaude1265 background: rgba(8,9,15,0.72);
1266 backdrop-filter: saturate(180%) blur(18px);
1267 -webkit-backdrop-filter: saturate(180%) blur(18px);
fc1817aClaude1268 }
958d26aClaude1269 :root[data-theme='light'] header { background: rgba(251,251,252,0.78); }
fc1817aClaude1270
06d5ffeClaude1271 header nav {
1272 display: flex;
1273 align-items: center;
958d26aClaude1274 gap: 18px;
1275 max-width: 1240px;
06d5ffeClaude1276 margin: 0 auto;
2ce1d0bClaude1277 height: 100%;
1278 }
1279 .logo {
958d26aClaude1280 font-family: var(--font-display);
1281 font-size: 16px;
2ce1d0bClaude1282 font-weight: 700;
958d26aClaude1283 letter-spacing: -0.025em;
1284 color: var(--text-strong);
2ce1d0bClaude1285 display: inline-flex;
1286 align-items: center;
958d26aClaude1287 gap: 9px;
1288 transition: opacity var(--t-fast) var(--ease);
06d5ffeClaude1289 }
2ce1d0bClaude1290 .logo::before {
1291 content: '';
958d26aClaude1292 width: 20px; height: 20px;
1293 border-radius: 6px;
2ce1d0bClaude1294 background: var(--accent-gradient);
958d26aClaude1295 box-shadow:
1296 inset 0 1px 0 rgba(255,255,255,0.25),
1297 0 0 0 1px rgba(140,109,255,0.45),
1298 0 0 20px rgba(140,109,255,0.30);
2ce1d0bClaude1299 flex-shrink: 0;
958d26aClaude1300 transition: transform var(--t-base) var(--ease-spring), box-shadow var(--t-base) var(--ease);
1301 }
1302 .logo:hover { text-decoration: none; color: var(--text-strong); }
1303 .logo:hover::before {
1304 transform: rotate(8deg) scale(1.05);
1305 box-shadow:
1306 inset 0 1px 0 rgba(255,255,255,0.30),
1307 0 0 0 1px rgba(140,109,255,0.55),
1308 0 0 28px rgba(140,109,255,0.45);
06d5ffeClaude1309 }
fc1817aClaude1310
2ce1d0bClaude1311 .nav-search {
1312 flex: 1;
958d26aClaude1313 max-width: 360px;
1314 margin: 0 4px 0 8px;
1315 position: relative;
2ce1d0bClaude1316 }
1317 .nav-search input {
1318 width: 100%;
958d26aClaude1319 padding: 7px 12px 7px 32px;
2ce1d0bClaude1320 background: var(--bg-tertiary);
1321 border: 1px solid var(--border);
1322 border-radius: var(--r-sm);
1323 color: var(--text);
1324 font-family: var(--font-sans);
1325 font-size: var(--t-sm);
958d26aClaude1326 transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
1327 }
1328 .nav-search::before {
1329 content: '';
1330 position: absolute;
1331 left: 11px; top: 50%;
1332 transform: translateY(-50%);
1333 width: 12px; height: 12px;
1334 border: 1.5px solid var(--text-faint);
1335 border-radius: 50%;
1336 pointer-events: none;
1337 }
1338 .nav-search::after {
1339 content: '';
1340 position: absolute;
1341 left: 19px; top: calc(50% + 4px);
1342 width: 5px; height: 1.5px;
1343 background: var(--text-faint);
1344 transform: rotate(45deg);
1345 pointer-events: none;
2ce1d0bClaude1346 }
1347 .nav-search input::placeholder { color: var(--text-faint); }
1348 .nav-search input:focus {
1349 outline: none;
1350 background: var(--bg-secondary);
958d26aClaude1351 border-color: var(--border-focus);
1352 box-shadow: var(--ring);
2ce1d0bClaude1353 }
06d5ffeClaude1354
958d26aClaude1355 .nav-right { display: flex; align-items: center; gap: 2px; margin-left: auto; }
f764c07Claude1356
1357 /* Block N3 — site-admin deploy status pill */
1358 .nav-deploy-pill {
1359 display: inline-flex;
1360 align-items: center;
1361 gap: 6px;
1362 padding: 4px 10px;
1363 margin: 0 6px 0 0;
1364 border-radius: 9999px;
1365 font-size: 11px;
1366 font-weight: 600;
1367 line-height: 1;
1368 color: var(--text-strong);
1369 background: var(--bg-elevated);
1370 border: 1px solid var(--border);
1371 text-decoration: none;
1372 transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
1373 }
1374 .nav-deploy-pill:hover { background: var(--bg-hover); text-decoration: none; }
1375 .nav-deploy-pill .deploy-pill-dot {
1376 display: inline-block;
1377 width: 8px; height: 8px;
1378 border-radius: 50%;
1379 background: #6b7280;
1380 }
1381 .deploy-pill-success .deploy-pill-dot { background: #34d399; box-shadow: 0 0 6px rgba(52,211,153,0.45); }
1382 .deploy-pill-failed .deploy-pill-dot { background: #f87171; box-shadow: 0 0 6px rgba(248,113,113,0.45); }
1383 .deploy-pill-failed { border-color: rgba(248,113,113,0.4); }
1384 .deploy-pill-progress .deploy-pill-dot {
1385 background: #fbbf24;
1386 box-shadow: 0 0 6px rgba(251,191,36,0.55);
1387 animation: deployPillPulse 1.2s ease-in-out infinite;
1388 }
1389 @keyframes deployPillPulse {
1390 0%, 100% { opacity: 1; transform: scale(1); }
1391 50% { opacity: 0.45; transform: scale(0.8); }
1392 }
1393 .deploy-pill-empty .deploy-pill-dot { background: #9ca3af; }
1394
2ce1d0bClaude1395 .nav-link {
958d26aClaude1396 position: relative;
2ce1d0bClaude1397 color: var(--text-muted);
1398 font-size: var(--t-sm);
1399 font-weight: 500;
958d26aClaude1400 padding: 7px 11px;
2ce1d0bClaude1401 border-radius: var(--r-sm);
958d26aClaude1402 transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
2ce1d0bClaude1403 }
1404 .nav-link:hover {
958d26aClaude1405 color: var(--text-strong);
2ce1d0bClaude1406 background: var(--bg-hover);
1407 text-decoration: none;
1408 }
958d26aClaude1409 .nav-link.active { color: var(--text-strong); }
1410 .nav-link.active::after {
1411 content: '';
1412 position: absolute;
1413 left: 11px; right: 11px;
1414 bottom: -1px;
1415 height: 2px;
1416 background: var(--accent-gradient);
1417 border-radius: 2px;
1418 }
2ce1d0bClaude1419 .nav-user {
958d26aClaude1420 color: var(--text-strong);
2ce1d0bClaude1421 font-weight: 600;
1422 font-size: var(--t-sm);
1423 padding: 6px 10px;
1424 border-radius: var(--r-sm);
958d26aClaude1425 margin-left: 6px;
2ce1d0bClaude1426 transition: background var(--t-fast) var(--ease);
1427 }
958d26aClaude1428 .nav-user::before {
1429 content: '';
1430 display: inline-block;
1431 width: 8px; height: 8px;
1432 background: var(--green);
1433 border-radius: 50%;
1434 margin-right: 7px;
1435 box-shadow: 0 0 8px rgba(52,211,153,0.5);
1436 vertical-align: 1px;
1437 }
2ce1d0bClaude1438 .nav-user:hover { background: var(--bg-hover); text-decoration: none; }
fc1817aClaude1439
2ce1d0bClaude1440 main {
958d26aClaude1441 max-width: 1240px;
2ce1d0bClaude1442 margin: 0 auto;
958d26aClaude1443 padding: 36px 24px 80px;
2ce1d0bClaude1444 flex: 1;
1445 width: 100%;
1446 }
fc1817aClaude1447
958d26aClaude1448 /* Editorial footer: link grid + tagline row */
fc1817aClaude1449 footer {
1450 border-top: 1px solid var(--border);
958d26aClaude1451 padding: 56px 24px 40px;
fc1817aClaude1452 color: var(--text-muted);
958d26aClaude1453 font-size: var(--t-sm);
1454 background:
1455 linear-gradient(180deg, transparent 0%, rgba(140,109,255,0.025) 100%),
1456 var(--bg);
1457 }
1458 footer .footer-inner {
1459 max-width: 1240px;
1460 margin: 0 auto;
1461 display: grid;
1462 grid-template-columns: 1fr auto;
1463 gap: 48px;
1464 align-items: start;
1465 }
1466 footer .footer-brand .logo { margin-bottom: var(--s-3); }
1467 footer .footer-tag {
1468 color: var(--text-muted);
1469 font-size: var(--t-sm);
1470 max-width: 320px;
1471 line-height: 1.55;
1472 }
1473 footer .footer-links {
1474 display: grid;
1475 grid-template-columns: repeat(3, minmax(120px, auto));
1476 gap: 0 56px;
1477 }
1478 footer .footer-col-title {
1479 font-family: var(--font-mono);
1480 font-size: 11px;
1481 text-transform: uppercase;
1482 letter-spacing: 0.14em;
1483 color: var(--text-faint);
1484 margin-bottom: var(--s-3);
1485 }
1486 footer .footer-col a {
1487 display: block;
1488 color: var(--text-muted);
1489 font-size: var(--t-sm);
1490 padding: 5px 0;
1491 transition: color var(--t-fast) var(--ease);
1492 }
1493 footer .footer-col a:hover { color: var(--text-strong); text-decoration: none; }
1494 footer .footer-bottom {
1495 max-width: 1240px;
1496 margin: 40px auto 0;
1497 padding-top: 24px;
1498 border-top: 1px solid var(--border-subtle);
1499 display: flex;
1500 justify-content: space-between;
1501 align-items: center;
2ce1d0bClaude1502 color: var(--text-faint);
1503 font-size: var(--t-xs);
958d26aClaude1504 font-family: var(--font-mono);
1505 letter-spacing: 0.02em;
1506 }
1507 footer .footer-bottom a { color: var(--text-faint); }
1508 footer .footer-bottom a:hover { color: var(--text-muted); text-decoration: none; }
05cdb85Claude1509 footer .footer-build {
1510 display: inline-flex;
1511 align-items: center;
1512 gap: 6px;
1513 color: var(--text-faint);
1514 font-family: var(--font-mono);
1515 font-size: 11px;
1516 cursor: help;
1517 }
1518 footer .footer-build-dot {
1519 width: 6px; height: 6px;
1520 border-radius: 50%;
1521 background: var(--green);
1522 box-shadow: 0 0 6px rgba(52,211,153,0.55);
1523 animation: footer-build-pulse 2.4s ease-in-out infinite;
1524 }
1525 @keyframes footer-build-pulse {
1526 0%, 100% { opacity: 0.6; }
1527 50% { opacity: 1; }
1528 }
958d26aClaude1529 @media (max-width: 768px) {
1530 footer .footer-inner { grid-template-columns: 1fr; gap: 32px; }
1531 footer .footer-links { grid-template-columns: repeat(2, 1fr); gap: 24px 32px; }
1532 footer .footer-bottom { flex-direction: column; gap: 8px; text-align: center; }
fc1817aClaude1533 }
1534
2ce1d0bClaude1535 /* ============================================================ */
1536 /* Buttons */
1537 /* ============================================================ */
dc26881CC LABS App1538 /* ============================================================ */
1539 /* Buttons — Block U2 senior polish pass. */
1540 /* Rules: */
1541 /* · hover lifts every .btn by 1px + soft drop shadow (180ms) */
1542 /* · active presses back down to 0 (80ms — faster on press) */
1543 /* · focus-visible uses a soft box-shadow ring (no outline) */
1544 /* · primary gradient shifts position on hover for a slow */
1545 /* 600ms shimmer */
1546 /* · disabled never lifts and never animates */
1547 /* ============================================================ */
06d5ffeClaude1548 .btn {
1549 display: inline-flex;
1550 align-items: center;
2ce1d0bClaude1551 justify-content: center;
958d26aClaude1552 gap: 7px;
2ce1d0bClaude1553 padding: 7px 14px;
1554 border-radius: var(--r-sm);
958d26aClaude1555 font-family: var(--font-sans);
2ce1d0bClaude1556 font-size: var(--t-sm);
06d5ffeClaude1557 font-weight: 500;
1558 border: 1px solid var(--border);
2ce1d0bClaude1559 background: var(--bg-elevated);
06d5ffeClaude1560 color: var(--text);
1561 cursor: pointer;
1562 text-decoration: none;
958d26aClaude1563 line-height: 1.25;
1564 letter-spacing: -0.008em;
dc26881CC LABS App1565 /* U2 — hover/transform settles in 180ms; press snaps in 80ms.
1566 Listed once on the base rule so :active can override only the
1567 transform/duration without re-typing the colour/bg transitions. */
2ce1d0bClaude1568 transition:
dc26881CC LABS App1569 background 180ms ease,
1570 border-color 180ms ease,
1571 transform 180ms ease,
1572 box-shadow 180ms ease,
1573 color 180ms ease;
2ce1d0bClaude1574 user-select: none;
1575 white-space: nowrap;
958d26aClaude1576 position: relative;
06d5ffeClaude1577 }
2ce1d0bClaude1578 .btn:hover {
1579 background: var(--bg-surface);
1580 border-color: var(--border-strong);
958d26aClaude1581 color: var(--text-strong);
2ce1d0bClaude1582 text-decoration: none;
dc26881CC LABS App1583 /* U2 — universal hover lift + soft accent drop shadow. */
1584 transform: translateY(-1px);
1585 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.45);
1586 }
1587 .btn:active {
1588 transform: translateY(0);
1589 transition-duration: 80ms;
1590 }
1591 /* U2 — soft modern focus ring via box-shadow, not outline. */
1592 .btn:focus-visible {
1593 outline: none;
1594 box-shadow: 0 0 0 3px rgba(140, 109, 255, 0.35);
2ce1d0bClaude1595 }
1596
1597 .btn-primary {
1598 background: var(--accent-gradient);
dc26881CC LABS App1599 background-size: 200% 100%;
1600 background-position: 0% 50%;
2ce1d0bClaude1601 border-color: transparent;
1602 color: #fff;
1603 font-weight: 600;
958d26aClaude1604 text-shadow: 0 1px 0 rgba(0,0,0,0.15);
2ce1d0bClaude1605 box-shadow:
958d26aClaude1606 inset 0 1px 0 rgba(255,255,255,0.22),
1607 inset 0 -1px 0 rgba(0,0,0,0.10),
1608 0 1px 2px rgba(0,0,0,0.40),
1609 0 0 0 1px rgba(140,109,255,0.30);
dc26881CC LABS App1610 /* U2 — slower 600ms transition on background-position so the
1611 primary CTA shimmers when the cursor lands. */
1612 transition:
1613 background-position 600ms ease,
1614 transform 180ms ease,
1615 box-shadow 180ms ease,
1616 color 180ms ease;
06d5ffeClaude1617 }
958d26aClaude1618 .btn-primary::before {
1619 content: '';
1620 position: absolute;
1621 inset: 0;
1622 border-radius: inherit;
1623 background: linear-gradient(180deg, rgba(255,255,255,0.18), transparent 60%);
1624 opacity: 0;
1625 transition: opacity var(--t-fast) var(--ease);
1626 pointer-events: none;
2ce1d0bClaude1627 }
1628 .btn-primary:hover {
958d26aClaude1629 color: #fff;
2ce1d0bClaude1630 background: var(--accent-gradient);
dc26881CC LABS App1631 background-size: 200% 100%;
1632 background-position: 100% 50%;
958d26aClaude1633 border-color: transparent;
dc26881CC LABS App1634 transform: translateY(-1px);
2ce1d0bClaude1635 box-shadow:
958d26aClaude1636 inset 0 1px 0 rgba(255,255,255,0.30),
1637 inset 0 -1px 0 rgba(0,0,0,0.10),
1638 0 6px 18px -4px rgba(140,109,255,0.45),
1639 0 0 0 1px rgba(140,109,255,0.45);
2ce1d0bClaude1640 }
958d26aClaude1641 .btn-primary:hover::before { opacity: 1; }
dc26881CC LABS App1642 .btn-primary:active { transform: translateY(0); transition-duration: 80ms; }
1643 /* U2 — primary focus ring uses the same accent box-shadow recipe. */
1644 .btn-primary:focus-visible {
1645 box-shadow:
1646 inset 0 1px 0 rgba(255,255,255,0.22),
1647 0 0 0 3px rgba(140,109,255,0.35);
1648 }
2ce1d0bClaude1649
1650 .btn-danger {
1651 background: transparent;
958d26aClaude1652 border-color: rgba(248,113,113,0.40);
2ce1d0bClaude1653 color: var(--red);
1654 }
1655 .btn-danger:hover {
958d26aClaude1656 background: rgba(248,113,113,0.08);
2ce1d0bClaude1657 border-color: var(--red);
958d26aClaude1658 color: var(--red);
dc26881CC LABS App1659 transform: translateY(-1px);
1660 box-shadow: 0 4px 12px -4px rgba(248,113,113,0.30);
2ce1d0bClaude1661 }
dc26881CC LABS App1662 .btn-danger:focus-visible { box-shadow: 0 0 0 3px rgba(248,113,113,0.35); }
2ce1d0bClaude1663
1664 .btn-ghost {
1665 background: transparent;
1666 border-color: transparent;
1667 color: var(--text-muted);
1668 }
1669 .btn-ghost:hover {
1670 background: var(--bg-hover);
958d26aClaude1671 color: var(--text-strong);
2ce1d0bClaude1672 border-color: var(--border);
dc26881CC LABS App1673 transform: translateY(-1px);
1674 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.30);
2ce1d0bClaude1675 }
1676
958d26aClaude1677 .btn-secondary {
1678 background: var(--bg-elevated);
1679 border-color: var(--border-strong);
1680 color: var(--text-strong);
1681 }
1682 .btn-secondary:hover {
1683 background: var(--bg-surface);
1684 border-color: var(--border-strong);
dc26881CC LABS App1685 transform: translateY(-1px);
1686 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.35);
958d26aClaude1687 }
1688
1689 .btn-sm { padding: 4px 10px; font-size: var(--t-xs); border-radius: var(--r-sm); gap: 5px; }
1690 .btn-lg { padding: 11px 22px; font-size: var(--t-base); border-radius: var(--r); }
1691 .btn-xl { padding: 14px 28px; font-size: var(--t-md); border-radius: var(--r); font-weight: 600; }
1692 .btn-block { width: 100%; }
2ce1d0bClaude1693
dc26881CC LABS App1694 /* U2 — disabled never lifts, never shimmers, never glows. */
1695 .btn:disabled,
1696 .btn[aria-disabled='true'],
1697 .btn:disabled:hover,
1698 .btn[aria-disabled='true']:hover {
1699 opacity: 0.5;
2ce1d0bClaude1700 cursor: not-allowed;
1701 pointer-events: none;
dc26881CC LABS App1702 transform: none;
1703 box-shadow: none;
1704 }
1705 @media (prefers-reduced-motion: reduce) {
1706 .btn,
1707 .btn:hover,
1708 .btn:active,
1709 .btn-primary,
1710 .btn-primary:hover {
1711 transform: none;
1712 transition: background-color 80ms linear, color 80ms linear;
1713 }
2ce1d0bClaude1714 }
1715
1716 /* ============================================================ */
1717 /* Forms */
1718 /* ============================================================ */
1719 .form-group { margin-bottom: 20px; }
1720 .form-group label {
1721 display: block;
1722 font-size: var(--t-sm);
1723 font-weight: 500;
1724 margin-bottom: 6px;
1725 color: var(--text);
1726 letter-spacing: -0.005em;
1727 }
1728 .form-group input,
1729 .form-group textarea,
1730 .form-group select,
1731 input[type='text'], input[type='email'], input[type='password'],
1732 input[type='url'], input[type='search'], input[type='number'],
1733 textarea, select {
06d5ffeClaude1734 width: 100%;
2ce1d0bClaude1735 padding: 9px 12px;
1736 background: var(--bg-secondary);
06d5ffeClaude1737 border: 1px solid var(--border);
2ce1d0bClaude1738 border-radius: var(--r-sm);
06d5ffeClaude1739 color: var(--text);
2ce1d0bClaude1740 font-size: var(--t-sm);
06d5ffeClaude1741 font-family: var(--font-sans);
2ce1d0bClaude1742 transition:
1743 border-color var(--t-fast) var(--ease),
1744 background var(--t-fast) var(--ease),
1745 box-shadow var(--t-fast) var(--ease);
06d5ffeClaude1746 }
2ce1d0bClaude1747 .form-group input::placeholder, textarea::placeholder, input::placeholder {
1748 color: var(--text-faint);
06d5ffeClaude1749 }
2ce1d0bClaude1750 .form-group input:hover, textarea:hover, select:hover,
1751 input[type='text']:hover, input[type='email']:hover, input[type='password']:hover {
1752 border-color: var(--border-strong);
1753 }
1754 .form-group input:focus, .form-group textarea:focus, .form-group select:focus,
1755 input:focus, textarea:focus, select:focus {
06d5ffeClaude1756 outline: none;
2ce1d0bClaude1757 background: var(--bg);
1758 border-color: var(--border-focus);
1759 box-shadow: var(--ring);
06d5ffeClaude1760 }
2ce1d0bClaude1761 textarea { font-family: var(--font-mono); font-size: var(--t-sm); line-height: 1.55; }
06d5ffeClaude1762 .input-disabled { opacity: 0.5; cursor: not-allowed; }
1763
2ce1d0bClaude1764 /* ============================================================ */
1765 /* Auth (register / login / verify) */
1766 /* ============================================================ */
1767 .auth-container {
1768 max-width: 420px;
1769 margin: 64px auto;
1770 padding: 32px;
1771 background: var(--bg-elevated);
1772 border: 1px solid var(--border);
1773 border-radius: var(--r-lg);
1774 box-shadow: var(--elev-2);
1775 }
1776 .auth-container h2 {
1777 margin-bottom: 6px;
1778 font-size: var(--t-lg);
1779 letter-spacing: -0.02em;
1780 }
1781 .auth-container > p {
1782 color: var(--text-muted);
1783 font-size: var(--t-sm);
1784 margin-bottom: 24px;
1785 }
1786 .auth-container .btn-primary { width: 100%; padding: 10px 16px; }
06d5ffeClaude1787 .auth-error {
958d26aClaude1788 background: rgba(248,113,113,0.08);
1789 border: 1px solid rgba(248,113,113,0.35);
06d5ffeClaude1790 color: var(--red);
2ce1d0bClaude1791 padding: 10px 14px;
1792 border-radius: var(--r-sm);
06d5ffeClaude1793 margin-bottom: 16px;
2ce1d0bClaude1794 font-size: var(--t-sm);
06d5ffeClaude1795 }
1796 .auth-success {
958d26aClaude1797 background: rgba(52,211,153,0.08);
1798 border: 1px solid rgba(52,211,153,0.35);
06d5ffeClaude1799 color: var(--green);
2ce1d0bClaude1800 padding: 10px 14px;
1801 border-radius: var(--r-sm);
06d5ffeClaude1802 margin-bottom: 16px;
2ce1d0bClaude1803 font-size: var(--t-sm);
1804 }
1805 .auth-switch {
1806 margin-top: 20px;
1807 font-size: var(--t-sm);
1808 color: var(--text-muted);
1809 text-align: center;
1810 }
1811 .banner {
1812 background: var(--accent-gradient-faint);
958d26aClaude1813 border: 1px solid rgba(140,109,255,0.35);
2ce1d0bClaude1814 color: var(--text);
1815 padding: 10px 14px;
1816 border-radius: var(--r-sm);
1817 font-size: var(--t-sm);
06d5ffeClaude1818 }
1819
2ce1d0bClaude1820 /* ============================================================ */
1821 /* Settings */
1822 /* ============================================================ */
1823 .settings-container { max-width: 720px; }
1824 .settings-container h2 { margin-bottom: 8px; font-size: var(--t-xl); letter-spacing: -0.02em; }
1825 .settings-container > h2 + p { color: var(--text-muted); font-size: var(--t-sm); margin-bottom: 24px; }
1826 .settings-container h3 { font-size: var(--t-md); margin-bottom: 12px; margin-top: 28px; }
1827 .settings-container h3:first-of-type { margin-top: 0; }
06d5ffeClaude1828 .ssh-keys-list { margin-bottom: 24px; }
1829 .ssh-key-item {
1830 display: flex;
1831 justify-content: space-between;
1832 align-items: center;
2ce1d0bClaude1833 padding: 14px 16px;
06d5ffeClaude1834 border: 1px solid var(--border);
2ce1d0bClaude1835 border-radius: var(--r-md);
06d5ffeClaude1836 margin-bottom: 8px;
2ce1d0bClaude1837 background: var(--bg-elevated);
1838 transition: border-color var(--t-fast) var(--ease);
06d5ffeClaude1839 }
2ce1d0bClaude1840 .ssh-key-item:hover { border-color: var(--border-strong); }
1841 .ssh-key-meta { font-size: var(--t-xs); color: var(--text-muted); margin-top: 4px; }
1842 .ssh-key-meta code { font-size: var(--t-xs); background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
06d5ffeClaude1843
2ce1d0bClaude1844 /* ============================================================ */
1845 /* Repo header + nav */
1846 /* ============================================================ */
fc1817aClaude1847 .repo-header {
1848 display: flex;
1849 align-items: center;
debcf27Claude1850 gap: 12px;
1851 margin-bottom: 22px;
1852 }
1853 .repo-header-title {
1854 display: flex;
1855 align-items: center;
1856 gap: 10px;
1857 font-family: var(--font-display);
1858 font-size: 24px;
1859 letter-spacing: -0.025em;
1860 flex-wrap: wrap;
1861 }
1862 .repo-header .owner {
1863 color: var(--text-muted);
1864 font-weight: 500;
1865 transition: color var(--t-fast) var(--ease);
1866 }
1867 .repo-header .owner:hover { color: var(--text-link); text-decoration: none; }
1868 .repo-header .separator { color: var(--text-faint); font-weight: 300; }
1869 .repo-header .name {
1870 color: var(--text-strong);
1871 font-weight: 700;
1872 letter-spacing: -0.028em;
fc1817aClaude1873 }
2ce1d0bClaude1874 .repo-header .name:hover { color: var(--text-link); text-decoration: none; }
debcf27Claude1875 .repo-header-fork {
1876 font-family: var(--font-mono);
1877 font-size: 11px;
1878 color: var(--text-muted);
1879 margin-top: 4px;
1880 letter-spacing: 0.01em;
1881 }
1882 .repo-header-fork a { color: var(--text-muted); }
1883 .repo-header-fork a:hover { color: var(--accent); }
1884 .repo-header-pill {
1885 display: inline-flex;
1886 align-items: center;
1887 padding: 2px 10px;
1888 border-radius: var(--r-full);
1889 font-family: var(--font-mono);
1890 font-size: 10px;
1891 font-weight: 600;
1892 letter-spacing: 0.12em;
1893 text-transform: uppercase;
1894 line-height: 1.6;
1895 vertical-align: 4px;
1896 }
1897 .repo-header-pill-archived {
1898 background: rgba(251,191,36,0.10);
1899 color: var(--yellow);
1900 border: 1px solid rgba(251,191,36,0.30);
1901 }
1902 .repo-header-pill-template {
1903 background: var(--accent-gradient-faint);
1904 color: var(--accent);
1905 border: 1px solid rgba(140,109,255,0.30);
fc1817aClaude1906 }
06d5ffeClaude1907 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude1908
1909 .repo-nav {
1910 display: flex;
debcf27Claude1911 gap: 1px;
fc1817aClaude1912 border-bottom: 1px solid var(--border);
debcf27Claude1913 margin-bottom: 28px;
2ce1d0bClaude1914 overflow-x: auto;
1915 scrollbar-width: thin;
fc1817aClaude1916 }
2ce1d0bClaude1917 .repo-nav::-webkit-scrollbar { height: 0; }
fc1817aClaude1918 .repo-nav a {
debcf27Claude1919 position: relative;
1920 padding: 11px 14px;
fc1817aClaude1921 color: var(--text-muted);
1922 border-bottom: 2px solid transparent;
2ce1d0bClaude1923 font-size: var(--t-sm);
1924 font-weight: 500;
1925 margin-bottom: -1px;
debcf27Claude1926 transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
2ce1d0bClaude1927 white-space: nowrap;
1928 }
debcf27Claude1929 .repo-nav a:hover { text-decoration: none; color: var(--text-strong); background: var(--bg-hover); }
2ce1d0bClaude1930 .repo-nav a.active {
debcf27Claude1931 color: var(--text-strong);
1932 font-weight: 600;
1933 }
1934 .repo-nav a.active::after {
1935 content: '';
1936 position: absolute;
1937 left: 14px;
1938 right: 14px;
1939 bottom: -1px;
1940 height: 2px;
1941 background: var(--accent-gradient);
1942 border-radius: 2px;
1943 }
1944 /* AI links in the right-side cluster — sparkle accent */
1945 .repo-nav-ai {
1946 color: var(--accent) !important;
1947 font-weight: 500;
1948 }
1949 .repo-nav-ai:hover {
1950 color: var(--accent-hover) !important;
1951 background: var(--accent-gradient-faint) !important;
1952 }
1953 .repo-nav-ai.active {
1954 color: var(--accent-hover) !important;
2ce1d0bClaude1955 font-weight: 600;
fc1817aClaude1956 }
1957
2ce1d0bClaude1958 .breadcrumb {
1959 display: flex;
debcf27Claude1960 gap: 6px;
2ce1d0bClaude1961 align-items: center;
debcf27Claude1962 margin-bottom: 18px;
2ce1d0bClaude1963 color: var(--text-muted);
1964 font-size: var(--t-sm);
1965 font-family: var(--font-mono);
debcf27Claude1966 font-feature-settings: var(--mono-feat);
2ce1d0bClaude1967 }
1968 .breadcrumb a { color: var(--text-link); font-weight: 500; }
1969 .breadcrumb a:hover { color: var(--accent-hover); }
debcf27Claude1970 .breadcrumb strong {
1971 color: var(--text-strong);
1972 font-weight: 600;
fc1817aClaude1973 }
1974
debcf27Claude1975 /* Page header — eyebrow + title + optional actions row.
1976 Use on dashboard, settings, admin, any "section landing" page. */
1977 .page-header {
1978 display: flex;
1979 align-items: flex-end;
1980 justify-content: space-between;
1981 gap: 16px;
1982 margin-bottom: 28px;
1983 padding-bottom: 20px;
1984 border-bottom: 1px solid var(--border-subtle);
1985 flex-wrap: wrap;
1986 }
1987 .page-header-text { flex: 1; min-width: 280px; }
1988 .page-header .eyebrow { margin-bottom: var(--s-2); }
1989 .page-header h1 {
1990 font-family: var(--font-display);
1991 font-size: clamp(24px, 3vw, 36px);
1992 line-height: 1.1;
1993 letter-spacing: -0.028em;
1994 margin-bottom: 6px;
1995 }
1996 .page-header p {
1997 color: var(--text-muted);
1998 font-size: var(--t-sm);
1999 line-height: 1.55;
2000 max-width: 640px;
2001 }
2002 .page-header-actions { display: flex; gap: 8px; align-items: center; }
fc1817aClaude2003
2ce1d0bClaude2004 /* ============================================================ */
2005 /* File browser table */
2006 /* ============================================================ */
2007 .file-table {
2008 width: 100%;
2009 border: 1px solid var(--border);
2010 border-radius: var(--r-md);
2011 overflow: hidden;
2012 background: var(--bg-elevated);
debcf27Claude2013 border-collapse: collapse;
2ce1d0bClaude2014 }
debcf27Claude2015 .file-table tr { border-bottom: 1px solid var(--border-subtle); transition: background var(--t-fast) var(--ease); }
fc1817aClaude2016 .file-table tr:last-child { border-bottom: none; }
debcf27Claude2017 .file-table td {
2018 padding: 9px 16px;
2019 font-size: var(--t-sm);
2020 font-family: var(--font-mono);
2021 font-feature-settings: var(--mono-feat);
2022 }
2ce1d0bClaude2023 .file-table tr:hover { background: var(--bg-hover); }
debcf27Claude2024 .file-icon {
2025 width: 22px;
2026 color: var(--text-faint);
2027 font-size: 13px;
2028 text-align: center;
2029 }
2030 .file-name a {
2031 color: var(--text);
2032 font-weight: 500;
2033 transition: color var(--t-fast) var(--ease);
2034 }
2035 .file-name a:hover { color: var(--accent); text-decoration: none; }
fc1817aClaude2036
2ce1d0bClaude2037 /* ============================================================ */
2038 /* Blob view */
2039 /* ============================================================ */
fc1817aClaude2040 .blob-view {
2041 border: 1px solid var(--border);
2ce1d0bClaude2042 border-radius: var(--r-md);
fc1817aClaude2043 overflow: hidden;
2ce1d0bClaude2044 background: var(--bg-elevated);
fc1817aClaude2045 }
2046 .blob-header {
2047 background: var(--bg-secondary);
2ce1d0bClaude2048 padding: 10px 16px;
fc1817aClaude2049 border-bottom: 1px solid var(--border);
2ce1d0bClaude2050 font-size: var(--t-sm);
fc1817aClaude2051 color: var(--text-muted);
06d5ffeClaude2052 display: flex;
2053 justify-content: space-between;
2054 align-items: center;
fc1817aClaude2055 }
2056 .blob-code {
2057 overflow-x: auto;
2058 font-family: var(--font-mono);
2ce1d0bClaude2059 font-size: var(--t-sm);
2060 line-height: 1.65;
fc1817aClaude2061 }
2062 .blob-code table { width: 100%; border-collapse: collapse; }
2063 .blob-code .line-num {
2064 width: 1%;
2ce1d0bClaude2065 min-width: 56px;
2066 padding: 0 14px;
fc1817aClaude2067 text-align: right;
2ce1d0bClaude2068 color: var(--text-faint);
fc1817aClaude2069 user-select: none;
2070 white-space: nowrap;
2071 border-right: 1px solid var(--border);
2072 }
2ce1d0bClaude2073 .blob-code .line-content { padding: 0 16px; white-space: pre; }
2074 .blob-code tr:hover { background: var(--bg-hover); }
fc1817aClaude2075
2ce1d0bClaude2076 /* ============================================================ */
2077 /* Commits + diffs */
2078 /* ============================================================ */
2079 .commit-list {
2080 border: 1px solid var(--border);
2081 border-radius: var(--r-md);
2082 overflow: hidden;
2083 background: var(--bg-elevated);
2084 }
fc1817aClaude2085 .commit-item {
2086 display: flex;
2087 justify-content: space-between;
2088 align-items: center;
debcf27Claude2089 padding: 14px 18px;
2090 border-bottom: 1px solid var(--border-subtle);
2ce1d0bClaude2091 transition: background var(--t-fast) var(--ease);
fc1817aClaude2092 }
2093 .commit-item:last-child { border-bottom: none; }
2ce1d0bClaude2094 .commit-item:hover { background: var(--bg-hover); }
debcf27Claude2095 .commit-message {
2096 font-size: var(--t-sm);
2097 font-weight: 500;
2098 line-height: 1.45;
2099 color: var(--text-strong);
2100 letter-spacing: -0.005em;
2101 }
2102 .commit-meta {
2103 font-family: var(--font-mono);
2104 font-size: 11px;
2105 color: var(--text-muted);
2106 margin-top: 5px;
2107 letter-spacing: 0.01em;
2108 }
fc1817aClaude2109 .commit-sha {
2110 font-family: var(--font-mono);
debcf27Claude2111 font-feature-settings: var(--mono-feat);
2112 font-size: 11px;
2113 padding: 4px 9px;
fc1817aClaude2114 background: var(--bg-tertiary);
2115 border: 1px solid var(--border);
2ce1d0bClaude2116 border-radius: var(--r-sm);
debcf27Claude2117 color: var(--accent);
2118 font-weight: 600;
2119 letter-spacing: 0.02em;
2ce1d0bClaude2120 transition: all var(--t-fast) var(--ease);
fc1817aClaude2121 }
debcf27Claude2122 .commit-sha:hover {
2123 border-color: rgba(140,109,255,0.40);
2124 background: var(--accent-gradient-faint);
2125 color: var(--accent-hover);
2126 text-decoration: none;
fc1817aClaude2127 }
2128
2129 .diff-view { margin-top: 16px; }
2130 .diff-file {
2131 border: 1px solid var(--border);
2ce1d0bClaude2132 border-radius: var(--r-md);
fc1817aClaude2133 margin-bottom: 16px;
2134 overflow: hidden;
2ce1d0bClaude2135 background: var(--bg-elevated);
fc1817aClaude2136 }
2137 .diff-file-header {
2138 background: var(--bg-secondary);
2ce1d0bClaude2139 padding: 10px 16px;
fc1817aClaude2140 border-bottom: 1px solid var(--border);
2141 font-family: var(--font-mono);
2ce1d0bClaude2142 font-size: var(--t-sm);
2143 font-weight: 500;
fc1817aClaude2144 }
2145 .diff-content {
2146 overflow-x: auto;
2147 font-family: var(--font-mono);
2ce1d0bClaude2148 font-size: var(--t-sm);
2149 line-height: 1.65;
fc1817aClaude2150 }
958d26aClaude2151 .diff-content .line-add { background: rgba(52,211,153,0.10); color: var(--green); }
2152 .diff-content .line-del { background: rgba(248,113,113,0.08); color: var(--red); }
2153 .diff-content .line-hunk { background: rgba(140,109,255,0.06); color: var(--text-link); }
2ce1d0bClaude2154 .diff-content .line { padding: 0 16px; white-space: pre; display: block; }
fc1817aClaude2155
2156 .stat-add { color: var(--green); font-weight: 600; }
2157 .stat-del { color: var(--red); font-weight: 600; }
2158
2ce1d0bClaude2159 /* ============================================================ */
2160 /* Empty state */
2161 /* ============================================================ */
fc1817aClaude2162 .empty-state {
2163 text-align: center;
958d26aClaude2164 padding: 96px 24px;
fc1817aClaude2165 color: var(--text-muted);
2ce1d0bClaude2166 border: 1px dashed var(--border);
2167 border-radius: var(--r-lg);
958d26aClaude2168 background:
2169 radial-gradient(60% 60% at 50% 0%, rgba(140,109,255,0.05), transparent 70%),
2170 var(--bg-elevated);
2171 position: relative;
2172 overflow: hidden;
2173 }
2174 .empty-state::before {
2175 content: '';
2176 position: absolute;
2177 inset: 0;
2178 background-image: radial-gradient(rgba(255,255,255,0.025) 1px, transparent 1px);
2179 background-size: 24px 24px;
2180 opacity: 0.5;
2181 pointer-events: none;
2182 mask-image: radial-gradient(ellipse at center, #000 30%, transparent 70%);
2183 -webkit-mask-image: radial-gradient(ellipse at center, #000 30%, transparent 70%);
2184 }
2185 :root[data-theme='light'] .empty-state::before {
2186 background-image: radial-gradient(rgba(15,16,28,0.08) 1px, transparent 1px);
fc1817aClaude2187 }
958d26aClaude2188 .empty-state > * { position: relative; z-index: 1; }
2ce1d0bClaude2189 .empty-state h2 {
958d26aClaude2190 font-size: var(--t-xl);
2ce1d0bClaude2191 margin-bottom: 8px;
958d26aClaude2192 color: var(--text-strong);
2193 letter-spacing: -0.022em;
2ce1d0bClaude2194 }
958d26aClaude2195 .empty-state p { font-size: var(--t-md); max-width: 480px; margin: 0 auto; line-height: 1.55; }
fc1817aClaude2196 .empty-state pre {
2197 text-align: left;
2198 display: inline-block;
2199 background: var(--bg-secondary);
958d26aClaude2200 padding: 18px 24px;
2201 border-radius: var(--r-md);
fc1817aClaude2202 border: 1px solid var(--border);
2203 font-family: var(--font-mono);
958d26aClaude2204 font-feature-settings: var(--mono-feat);
2ce1d0bClaude2205 font-size: var(--t-sm);
958d26aClaude2206 margin-top: 24px;
fc1817aClaude2207 line-height: 1.8;
2ce1d0bClaude2208 color: var(--text);
958d26aClaude2209 box-shadow: var(--elev-1);
fc1817aClaude2210 }
2211
2ce1d0bClaude2212 /* ============================================================ */
2213 /* Badges */
2214 /* ============================================================ */
fc1817aClaude2215 .badge {
2ce1d0bClaude2216 display: inline-flex;
2217 align-items: center;
2218 gap: 4px;
2219 padding: 2px 9px;
2220 border-radius: var(--r-full);
2221 font-size: var(--t-xs);
fc1817aClaude2222 font-weight: 500;
2223 background: var(--bg-tertiary);
2224 border: 1px solid var(--border);
2225 color: var(--text-muted);
2ce1d0bClaude2226 line-height: 1.5;
fc1817aClaude2227 }
2228
2ce1d0bClaude2229 /* ============================================================ */
2230 /* Branch dropdown */
2231 /* ============================================================ */
fc1817aClaude2232 .branch-selector {
2233 display: inline-flex;
2234 align-items: center;
2ce1d0bClaude2235 gap: 6px;
2236 padding: 6px 12px;
2237 background: var(--bg-elevated);
fc1817aClaude2238 border: 1px solid var(--border);
2ce1d0bClaude2239 border-radius: var(--r-sm);
2240 font-size: var(--t-sm);
fc1817aClaude2241 color: var(--text);
2242 margin-bottom: 12px;
2ce1d0bClaude2243 transition: border-color var(--t-fast) var(--ease);
fc1817aClaude2244 }
2ce1d0bClaude2245 .branch-selector:hover { border-color: var(--border-strong); }
fc1817aClaude2246
06d5ffeClaude2247 .branch-dropdown {
2248 position: relative;
2249 display: inline-block;
2250 margin-bottom: 12px;
2251 }
2252 .branch-dropdown-content {
2253 display: none;
2254 position: absolute;
2ce1d0bClaude2255 top: calc(100% + 4px);
06d5ffeClaude2256 left: 0;
2257 z-index: 10;
2ce1d0bClaude2258 min-width: 220px;
2259 background: var(--bg-elevated);
2260 border: 1px solid var(--border-strong);
2261 border-radius: var(--r-md);
2262 overflow: hidden;
2263 box-shadow: var(--elev-3);
06d5ffeClaude2264 }
2265 .branch-dropdown:hover .branch-dropdown-content,
2266 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
2267 .branch-dropdown-content a {
2268 display: block;
2ce1d0bClaude2269 padding: 9px 14px;
2270 font-size: var(--t-sm);
06d5ffeClaude2271 color: var(--text);
2272 border-bottom: 1px solid var(--border);
2ce1d0bClaude2273 transition: background var(--t-fast) var(--ease);
06d5ffeClaude2274 }
2275 .branch-dropdown-content a:last-child { border-bottom: none; }
2ce1d0bClaude2276 .branch-dropdown-content a:hover { background: var(--bg-hover); text-decoration: none; }
2277 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; background: var(--accent-gradient-faint); }
06d5ffeClaude2278
2ce1d0bClaude2279 /* ============================================================ */
2280 /* Card grid */
2281 /* ============================================================ */
2282 .card-grid {
2283 display: grid;
2284 grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
2285 gap: 16px;
2286 }
fc1817aClaude2287 .card {
2288 border: 1px solid var(--border);
2ce1d0bClaude2289 border-radius: var(--r-md);
958d26aClaude2290 padding: 20px;
2ce1d0bClaude2291 background: var(--bg-elevated);
2292 transition:
958d26aClaude2293 border-color var(--t-base) var(--ease),
2294 transform var(--t-base) var(--ease-out-quart),
2ce1d0bClaude2295 box-shadow var(--t-base) var(--ease);
2296 position: relative;
2297 overflow: hidden;
958d26aClaude2298 isolation: isolate;
2299 }
2300 .card::before {
2301 content: '';
2302 position: absolute;
2303 inset: 0;
2304 background: linear-gradient(135deg, rgba(140,109,255,0.06), transparent 50%);
2305 opacity: 0;
2306 transition: opacity var(--t-base) var(--ease);
2307 pointer-events: none;
2308 z-index: -1;
2ce1d0bClaude2309 }
2310 .card:hover {
2311 border-color: var(--border-strong);
2312 box-shadow: var(--elev-2);
958d26aClaude2313 transform: translateY(-2px);
2ce1d0bClaude2314 }
958d26aClaude2315 .card:hover::before { opacity: 1; }
2316 .card h3 { font-size: var(--t-md); margin-bottom: 6px; letter-spacing: -0.012em; }
2ce1d0bClaude2317 .card h3 a { color: var(--text); font-weight: 600; }
2318 .card h3 a:hover { color: var(--text-link); }
2319 .card p { font-size: var(--t-sm); color: var(--text-muted); line-height: 1.5; }
2320 .card-meta {
2321 display: flex;
2322 gap: 14px;
2323 margin-top: 14px;
2324 padding-top: 14px;
2325 border-top: 1px solid var(--border);
2326 font-size: var(--t-xs);
2327 color: var(--text-muted);
2328 }
2329 .card-meta span { display: flex; align-items: center; gap: 5px; }
2330
2331 /* ============================================================ */
2332 /* Stat card / box */
2333 /* ============================================================ */
2334 .stat-card {
2335 background: var(--bg-elevated);
2336 border: 1px solid var(--border);
2337 border-radius: var(--r-md);
fc1817aClaude2338 padding: 16px;
2ce1d0bClaude2339 transition: border-color var(--t-fast) var(--ease);
2340 }
2341 .stat-card:hover { border-color: var(--border-strong); }
2342 .stat-label {
2343 font-size: var(--t-xs);
2344 color: var(--text-muted);
2345 text-transform: uppercase;
2346 letter-spacing: 0.06em;
2347 font-weight: 500;
2348 }
2349 .stat-value {
2350 font-size: var(--t-xl);
2351 font-weight: 700;
2352 margin-top: 4px;
2353 letter-spacing: -0.025em;
2354 color: var(--text);
2355 font-feature-settings: 'tnum';
fc1817aClaude2356 }
06d5ffeClaude2357
2ce1d0bClaude2358 /* ============================================================ */
2359 /* Star button */
2360 /* ============================================================ */
06d5ffeClaude2361 .star-btn {
2362 display: inline-flex;
2363 align-items: center;
2364 gap: 6px;
2ce1d0bClaude2365 padding: 5px 12px;
2366 background: var(--bg-elevated);
06d5ffeClaude2367 border: 1px solid var(--border);
2ce1d0bClaude2368 border-radius: var(--r-sm);
06d5ffeClaude2369 color: var(--text);
2ce1d0bClaude2370 font-size: var(--t-sm);
2371 font-weight: 500;
06d5ffeClaude2372 cursor: pointer;
2ce1d0bClaude2373 transition: all var(--t-fast) var(--ease);
2374 }
2375 .star-btn:hover { background: var(--bg-surface); border-color: var(--border-strong); text-decoration: none; }
2376 .star-btn.starred {
2377 color: var(--yellow);
958d26aClaude2378 border-color: rgba(251,191,36,0.4);
2379 background: rgba(251,191,36,0.08);
06d5ffeClaude2380 }
2381
2ce1d0bClaude2382 /* ============================================================ */
2383 /* User profile */
2384 /* ============================================================ */
06d5ffeClaude2385 .user-profile {
2386 display: flex;
2387 gap: 32px;
2388 margin-bottom: 32px;
2ce1d0bClaude2389 padding: 24px;
2390 background: var(--bg-elevated);
2391 border: 1px solid var(--border);
2392 border-radius: var(--r-lg);
06d5ffeClaude2393 }
2394 .user-avatar {
2395 width: 96px;
2396 height: 96px;
2ce1d0bClaude2397 border-radius: var(--r-full);
2398 background: var(--accent-gradient-soft);
2399 border: 1px solid var(--border-strong);
06d5ffeClaude2400 display: flex;
2401 align-items: center;
2402 justify-content: center;
2ce1d0bClaude2403 font-size: 36px;
2404 font-weight: 600;
2405 color: var(--text);
06d5ffeClaude2406 flex-shrink: 0;
2ce1d0bClaude2407 letter-spacing: -0.02em;
06d5ffeClaude2408 }
2ce1d0bClaude2409 .user-info h2 { font-size: var(--t-xl); margin-bottom: 2px; letter-spacing: -0.025em; }
2410 .user-info .username { font-size: var(--t-md); color: var(--text-muted); }
2411 .user-info .bio { font-size: var(--t-sm); color: var(--text-muted); margin-top: 10px; line-height: 1.55; }
06d5ffeClaude2412
2ce1d0bClaude2413 /* ============================================================ */
2414 /* New repo form */
2415 /* ============================================================ */
2416 .new-repo-form { max-width: 640px; }
2417 .new-repo-form h2 { margin-bottom: 24px; font-size: var(--t-xl); letter-spacing: -0.025em; }
2418 .visibility-options { display: flex; gap: 12px; margin-bottom: 20px; }
06d5ffeClaude2419 .visibility-option {
2420 flex: 1;
2ce1d0bClaude2421 padding: 16px;
06d5ffeClaude2422 border: 1px solid var(--border);
2ce1d0bClaude2423 border-radius: var(--r-md);
2424 background: var(--bg-elevated);
06d5ffeClaude2425 cursor: pointer;
2ce1d0bClaude2426 text-align: left;
2427 transition: all var(--t-fast) var(--ease);
2428 }
2429 .visibility-option:hover { border-color: var(--border-strong); background: var(--bg-surface); }
2430 .visibility-option:has(input:checked) {
2431 border-color: var(--accent);
2432 background: var(--accent-gradient-faint);
2433 box-shadow: 0 0 0 1px var(--accent);
06d5ffeClaude2434 }
2435 .visibility-option input { display: none; }
2ce1d0bClaude2436 .visibility-option .vis-label { font-size: var(--t-sm); font-weight: 600; margin-bottom: 4px; }
2437 .visibility-option .vis-desc { font-size: var(--t-xs); color: var(--text-muted); }
79136bbClaude2438
2ce1d0bClaude2439 /* ============================================================ */
2440 /* Issues */
2441 /* ============================================================ */
2442 .issue-tabs { display: flex; gap: 4px; }
2443 .issue-tabs a {
2444 color: var(--text-muted);
2445 font-size: var(--t-sm);
2446 font-weight: 500;
2447 padding: 6px 12px;
2448 border-radius: var(--r-sm);
2449 transition: all var(--t-fast) var(--ease);
2450 }
2451 .issue-tabs a:hover { color: var(--text); background: var(--bg-hover); text-decoration: none; }
2452 .issue-tabs a.active { color: var(--text); background: var(--bg-elevated); }
79136bbClaude2453
2ce1d0bClaude2454 .issue-list {
2455 border: 1px solid var(--border);
2456 border-radius: var(--r-md);
2457 overflow: hidden;
2458 background: var(--bg-elevated);
2459 }
79136bbClaude2460 .issue-item {
2461 display: flex;
2ce1d0bClaude2462 gap: 14px;
79136bbClaude2463 align-items: flex-start;
2ce1d0bClaude2464 padding: 14px 16px;
79136bbClaude2465 border-bottom: 1px solid var(--border);
2ce1d0bClaude2466 transition: background var(--t-fast) var(--ease);
79136bbClaude2467 }
2468 .issue-item:last-child { border-bottom: none; }
2ce1d0bClaude2469 .issue-item:hover { background: var(--bg-hover); }
2470 .issue-state-icon {
2471 font-size: 14px;
2472 padding-top: 2px;
2473 width: 18px;
2474 height: 18px;
2475 display: inline-flex;
2476 align-items: center;
2477 justify-content: center;
2478 border-radius: var(--r-full);
2479 flex-shrink: 0;
2480 }
79136bbClaude2481 .state-open { color: var(--green); }
958d26aClaude2482 .state-closed { color: #b69dff; }
2ce1d0bClaude2483 .issue-title {
debcf27Claude2484 font-family: var(--font-display);
2485 font-size: var(--t-md);
2ce1d0bClaude2486 font-weight: 600;
debcf27Claude2487 line-height: 1.35;
2488 letter-spacing: -0.012em;
2489 }
2490 .issue-title a { color: var(--text-strong); transition: color var(--t-fast) var(--ease); }
2491 .issue-title a:hover { color: var(--accent); text-decoration: none; }
2492 .issue-meta {
2493 font-family: var(--font-mono);
2494 font-size: 11px;
2495 color: var(--text-muted);
2496 margin-top: 5px;
2497 letter-spacing: 0.01em;
2ce1d0bClaude2498 }
79136bbClaude2499
2500 .issue-badge {
2501 display: inline-flex;
2502 align-items: center;
2ce1d0bClaude2503 gap: 6px;
79136bbClaude2504 padding: 4px 12px;
2ce1d0bClaude2505 border-radius: var(--r-full);
2506 font-size: var(--t-sm);
79136bbClaude2507 font-weight: 500;
2ce1d0bClaude2508 line-height: 1.4;
79136bbClaude2509 }
958d26aClaude2510 .badge-open { background: rgba(52,211,153,0.10); color: var(--green); border: 1px solid rgba(52,211,153,0.35); }
2511 .badge-closed { background: rgba(182,157,255,0.10); color: #b69dff; border: 1px solid rgba(182,157,255,0.35); }
2512 .badge-merged { background: rgba(140,109,255,0.10); color: var(--accent); border: 1px solid rgba(140,109,255,0.35); }
2ce1d0bClaude2513 .state-merged { color: var(--accent); }
958d26aClaude2514 .ai-review { border-color: var(--accent); box-shadow: 0 0 0 1px rgba(140,109,255,0.3); }
79136bbClaude2515
2ce1d0bClaude2516 .issue-detail { max-width: 920px; }
79136bbClaude2517 .issue-comment-box {
2518 border: 1px solid var(--border);
2ce1d0bClaude2519 border-radius: var(--r-md);
79136bbClaude2520 margin-bottom: 16px;
2521 overflow: hidden;
2ce1d0bClaude2522 background: var(--bg-elevated);
79136bbClaude2523 }
2524 .comment-header {
2525 background: var(--bg-secondary);
2ce1d0bClaude2526 padding: 10px 16px;
79136bbClaude2527 border-bottom: 1px solid var(--border);
2ce1d0bClaude2528 font-size: var(--t-sm);
79136bbClaude2529 color: var(--text-muted);
2530 }
2531
2ce1d0bClaude2532 /* ============================================================ */
2533 /* Panel — flexible container used across many pages */
2534 /* ============================================================ */
2535 .panel {
2536 background: var(--bg-elevated);
2537 border: 1px solid var(--border);
2538 border-radius: var(--r-md);
2539 overflow: hidden;
2540 }
2541 .panel-item {
2542 display: flex;
2543 align-items: center;
2544 gap: 12px;
2545 padding: 12px 16px;
79136bbClaude2546 border-bottom: 1px solid var(--border);
2ce1d0bClaude2547 transition: background var(--t-fast) var(--ease);
2548 }
2549 .panel-item:last-child { border-bottom: none; }
2550 .panel-item:hover { background: var(--bg-hover); }
2551 .panel-empty {
2552 padding: 24px;
2553 text-align: center;
79136bbClaude2554 color: var(--text-muted);
2ce1d0bClaude2555 font-size: var(--t-sm);
79136bbClaude2556 }
2557
2ce1d0bClaude2558 /* ============================================================ */
2559 /* Search */
2560 /* ============================================================ */
79136bbClaude2561 .search-results .diff-file { margin-bottom: 12px; }
16b325cClaude2562
2ce1d0bClaude2563 /* ============================================================ */
2564 /* Timeline */
2565 /* ============================================================ */
2566 .timeline { position: relative; padding-left: 28px; }
16b325cClaude2567 .timeline::before {
2568 content: '';
2569 position: absolute;
2570 left: 4px;
2571 top: 8px;
2572 bottom: 8px;
2573 width: 2px;
2ce1d0bClaude2574 background: linear-gradient(180deg, var(--border) 0%, transparent 100%);
16b325cClaude2575 }
2ce1d0bClaude2576 .timeline-item { position: relative; padding-bottom: 16px; }
16b325cClaude2577 .timeline-dot {
2578 position: absolute;
2ce1d0bClaude2579 left: -28px;
2580 top: 8px;
2581 width: 12px;
2582 height: 12px;
2583 border-radius: var(--r-full);
2584 background: var(--accent-gradient);
16b325cClaude2585 border: 2px solid var(--bg);
2ce1d0bClaude2586 box-shadow: 0 0 0 1px var(--border-strong);
16b325cClaude2587 }
2588 .timeline-content {
2ce1d0bClaude2589 background: var(--bg-elevated);
16b325cClaude2590 border: 1px solid var(--border);
2ce1d0bClaude2591 border-radius: var(--r-md);
2592 padding: 14px 16px;
16b325cClaude2593 }
f1ab587Claude2594
2ce1d0bClaude2595 /* ============================================================ */
2596 /* Toggle switch */
2597 /* ============================================================ */
f1ab587Claude2598 .toggle-switch {
2599 position: relative;
2600 display: inline-block;
2ce1d0bClaude2601 width: 40px;
2602 height: 22px;
f1ab587Claude2603 flex-shrink: 0;
2604 margin-left: 16px;
2605 }
2606 .toggle-switch input { opacity: 0; width: 0; height: 0; }
2607 .toggle-slider {
2608 position: absolute;
2609 cursor: pointer;
2610 top: 0; left: 0; right: 0; bottom: 0;
2611 background: var(--bg-tertiary);
2612 border: 1px solid var(--border);
2ce1d0bClaude2613 border-radius: var(--r-full);
2614 transition: all var(--t-base) var(--ease);
f1ab587Claude2615 }
2616 .toggle-slider::before {
2617 content: '';
2618 position: absolute;
2ce1d0bClaude2619 height: 16px;
2620 width: 16px;
f1ab587Claude2621 left: 2px;
2622 bottom: 2px;
2623 background: var(--text-muted);
2ce1d0bClaude2624 border-radius: var(--r-full);
2625 transition: all var(--t-base) var(--ease);
f1ab587Claude2626 }
2627 .toggle-switch input:checked + .toggle-slider {
2ce1d0bClaude2628 background: var(--accent-gradient);
2629 border-color: transparent;
958d26aClaude2630 box-shadow: 0 0 0 1px rgba(140,109,255,0.4);
f1ab587Claude2631 }
2632 .toggle-switch input:checked + .toggle-slider::before {
2ce1d0bClaude2633 transform: translateX(18px);
f1ab587Claude2634 background: #fff;
2635 }
ef8d378Claude2636
2637 /* ============================================================
2638 * 2026 polish layer (purely additive — no layout changes).
2639 * Improves typography rendering, focus states, hover affordances,
2640 * and adds the gradient brand cue to primary buttons. Anything
2641 * that could alter dimensions stays in the rules above.
2642 * ============================================================ */
2643 html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
2644 body { letter-spacing: -0.005em; font-feature-settings: 'cv11', 'ss01', 'ss03'; }
2645 *::selection { background: rgba(168,85,247,0.35); color: var(--text); }
2646
2647 h1, h2, h3, h4 { letter-spacing: -0.018em; }
2648 h1 { letter-spacing: -0.025em; }
2649
2650 /* Smoother colour transitions everywhere links live */
2651 a { transition: color 120ms cubic-bezier(0.16,1,0.3,1); }
2652
2653 /* Buttons: focus rings + smoother transitions; primary gets the gradient */
2654 .btn {
2655 transition:
2656 background 120ms cubic-bezier(0.16,1,0.3,1),
2657 border-color 120ms cubic-bezier(0.16,1,0.3,1),
2658 transform 120ms cubic-bezier(0.16,1,0.3,1),
2659 box-shadow 120ms cubic-bezier(0.16,1,0.3,1);
2660 }
2661 .btn:active { transform: translateY(0.5px); }
2662 .btn:focus-visible {
2663 outline: none;
2664 box-shadow: 0 0 0 3px rgba(168,85,247,0.30);
2665 }
2666 .btn-primary {
2667 background: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
2668 border-color: transparent;
2669 box-shadow:
2670 inset 0 1px 0 rgba(255,255,255,0.15),
2671 0 1px 2px rgba(168,85,247,0.25);
2672 }
2673 .btn-primary:hover {
2674 background: linear-gradient(135deg, #b766f8 0%, #22cce0 100%);
2675 filter: none;
2676 box-shadow:
2677 inset 0 1px 0 rgba(255,255,255,0.20),
2678 0 4px 12px rgba(168,85,247,0.30);
2679 }
2680
2681 /* Inputs: cleaner focus ring + hover */
2682 .form-group input:hover,
2683 .form-group textarea:hover,
2684 .form-group select:hover {
2685 border-color: rgba(255,255,255,0.14);
2686 }
2687 .form-group input:focus,
2688 .form-group textarea:focus,
2689 .form-group select:focus {
2690 border-color: rgba(168,85,247,0.55);
2691 box-shadow: 0 0 0 3px rgba(168,85,247,0.22);
2692 }
2693 :root[data-theme='light'] .form-group input:hover,
2694 :root[data-theme='light'] .form-group textarea:hover,
2695 :root[data-theme='light'] .form-group select:hover {
2696 border-color: rgba(0,0,0,0.18);
2697 }
2698
2699 /* Cards: subtle hover lift */
2700 .card {
2701 transition:
2702 border-color 160ms cubic-bezier(0.16,1,0.3,1),
2703 transform 160ms cubic-bezier(0.16,1,0.3,1),
2704 box-shadow 200ms cubic-bezier(0.16,1,0.3,1);
2705 }
2706 .card:hover {
2707 border-color: rgba(255,255,255,0.18);
2708 transform: translateY(-1px);
2709 box-shadow: 0 8px 24px rgba(0,0,0,0.30);
2710 }
2711 :root[data-theme='light'] .card:hover {
2712 border-color: rgba(0,0,0,0.18);
2713 box-shadow: 0 8px 24px rgba(0,0,0,0.08);
2714 }
2715
2716 /* Issue / commit / panel rows: smoother hover */
2717 .issue-item, .commit-item {
2718 transition: background 120ms cubic-bezier(0.16,1,0.3,1);
2719 }
2720 .repo-nav a {
2721 transition: color 120ms cubic-bezier(0.16,1,0.3,1),
2722 border-bottom-color 120ms cubic-bezier(0.16,1,0.3,1);
2723 }
2724 .nav-link {
2725 transition: color 120ms cubic-bezier(0.16,1,0.3,1);
2726 }
2727
2728 /* Auth card: subtle elevation so register/login feel premium */
2729 .auth-container {
2730 background: var(--bg-secondary);
2731 border: 1px solid var(--border);
2732 border-radius: var(--r-lg, 12px);
2733 padding: 32px;
2734 box-shadow: 0 4px 16px rgba(0,0,0,0.30), 0 0 0 1px var(--border);
2735 }
2736 :root[data-theme='light'] .auth-container {
2737 box-shadow: 0 4px 16px rgba(0,0,0,0.06), 0 0 0 1px var(--border);
2738 }
2739 .auth-container h2 { letter-spacing: -0.025em; }
2740
2741 /* Empty state: dashed border, generous padding */
2742 .empty-state {
2743 border: 1px dashed var(--border);
2744 border-radius: var(--r-lg, 12px);
2745 background: var(--bg);
2746 }
2747
2748 /* Badges + commit-sha: smoother transition */
2749 .commit-sha, .badge { transition: all 120ms cubic-bezier(0.16,1,0.3,1); }
2750
2751 /* Gradient text utility — matches landing's accent treatment */
2752 .gradient-text {
2753 background: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
2754 -webkit-background-clip: text;
2755 background-clip: text;
2756 -webkit-text-fill-color: transparent;
2757 }
2758
2759 /* Custom scrollbars (subtle, themed) */
2760 ::-webkit-scrollbar { width: 10px; height: 10px; }
2761 ::-webkit-scrollbar-track { background: transparent; }
2762 ::-webkit-scrollbar-thumb {
2763 background: rgba(255,255,255,0.06);
2764 border: 2px solid var(--bg);
2765 border-radius: 9999px;
2766 }
2767 ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.14); }
2768 :root[data-theme='light'] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.10); }
2769 :root[data-theme='light'] ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.18); }
2770
2771 /* Honour reduced-motion preference */
2772 @media (prefers-reduced-motion: reduce) {
2773 *, *::before, *::after {
2774 animation-duration: 0.01ms !important;
2775 animation-iteration-count: 1 !important;
2776 transition-duration: 0.01ms !important;
2777 }
2778 }
c63b860Claude2779
2780 /* Block O3 — visual coherence additive rules. */
2781 .card.card-p-none { padding: 0; }
2782 .card.card-p-sm { padding: var(--space-3); }
2783 .card.card-p-md { padding: var(--space-4); }
2784 .card.card-p-lg { padding: var(--space-6); }
2785 .card.card-elevated { box-shadow: var(--elev-2); }
2786 .card.card-gradient {
2787 background:
2788 linear-gradient(135deg, rgba(140,109,255,0.05), transparent 60%),
2789 var(--bg-elevated);
2790 }
2791 .card.card-gradient::before { opacity: 1; }
2792 .notice {
2793 padding: var(--space-3) var(--space-4);
2794 border-radius: var(--radius-md);
2795 border: 1px solid var(--border);
2796 background: var(--bg-elevated);
2797 color: var(--text);
2798 font-size: var(--font-size-sm);
2799 margin-bottom: var(--space-6);
2800 line-height: var(--leading-normal);
2801 }
2802 .notice-info { border-color: rgba(96,165,250,0.40); background: rgba(96,165,250,0.08); color: var(--text); }
2803 .notice-success { border-color: var(--green); background: rgba(52,211,153,0.08); color: var(--text); }
2804 .notice-warn { border-color: var(--yellow); background: rgba(251,191,36,0.10); color: var(--yellow); }
2805 .notice-error { border-color: var(--red); background: rgba(248,113,113,0.10); color: var(--red); }
2806 .notice-accent { border-color: var(--accent); background: rgba(140,109,255,0.10); color: var(--text); }
2807 .email-preview {
2808 padding: var(--space-5);
2809 background: #fff;
2810 color: #111;
2811 border-radius: var(--radius-md);
2812 }
2813 .code-block {
2814 margin: var(--space-2) 0 0;
2815 padding: var(--space-3);
2816 background: var(--bg-tertiary);
2817 border: 1px solid var(--border-subtle);
2818 border-radius: var(--radius-sm);
2819 font-family: var(--font-mono);
2820 font-size: var(--font-size-xs);
2821 line-height: var(--leading-normal);
2822 overflow-x: auto;
2823 }
2824 .status-pill-operational {
2825 display: inline-flex;
2826 align-items: center;
2827 padding: var(--space-1) var(--space-3);
2828 border-radius: var(--radius-full);
2829 font-size: var(--font-size-xs);
2830 font-weight: 600;
2831 background: rgba(52,211,153,0.15);
2832 color: var(--green);
2833 }
2834 .api-tag {
2835 display: inline-flex;
2836 align-items: center;
2837 font-size: var(--font-size-xs);
2838 padding: 2px var(--space-2);
2839 border-radius: var(--radius-sm);
2840 font-family: var(--font-mono);
2841 }
2842 .api-tag-auth { background: rgba(96,165,250,0.15); color: var(--accent); }
2843 .api-tag-scope { background: rgba(52,211,153,0.15); color: var(--green); }
2844 .stat-number {
2845 font-size: var(--font-size-xl);
2846 font-weight: 700;
2847 color: var(--text-strong);
2848 line-height: var(--leading-tight);
2849 }
2850 .stat-number-accent { color: var(--accent); }
2851 .stat-number-blue { color: var(--blue); }
2852 .stat-number-purple { color: var(--text-link); }
2853 footer .footer-tag-sub {
2854 margin-top: var(--space-2);
2855 font-size: var(--font-size-xs);
2856 color: var(--text-faint);
2857 line-height: var(--leading-normal);
2858 }
2859 footer .footer-version-pill {
2860 display: inline-flex;
2861 align-items: center;
2862 gap: var(--space-2);
2863 padding: var(--space-1) var(--space-3);
2864 border-radius: var(--radius-full);
2865 border: 1px solid var(--border);
2866 background: var(--bg-elevated);
2867 color: var(--text-faint);
2868 font-family: var(--font-mono);
2869 font-size: var(--font-size-xs);
2870 cursor: help;
2871 }
2872 footer .footer-banner {
2873 max-width: 1240px;
2874 margin: var(--space-6) auto 0;
2875 padding: var(--space-3) var(--space-4);
2876 border-radius: var(--radius-md);
2877 border: 1px solid var(--border);
2878 background: var(--bg-elevated);
2879 color: var(--text);
2880 font-family: var(--font-mono);
2881 font-size: var(--font-size-xs);
2882 letter-spacing: 0.04em;
2883 text-transform: uppercase;
2884 text-align: center;
2885 }
2886 footer .footer-banner-info { border-color: rgba(96,165,250,0.40); color: var(--blue); }
2887 footer .footer-banner-warn { border-color: rgba(251,191,36,0.40); color: var(--yellow); }
2888 footer .footer-banner-error { border-color: rgba(248,113,113,0.45); color: var(--red); }
dc26881CC LABS App2889
cf9178bTest User2890 /* ============================================================ */
2891 /* Global toast notifications. */
2892 /* Slide-in from right, auto-dismiss, ARIA-live for SR users. */
2893 /* ============================================================ */
2894 .gx-toast {
2895 pointer-events: auto;
2896 display: inline-flex;
2897 align-items: flex-start;
2898 gap: 10px;
2899 min-width: 280px;
2900 max-width: min(440px, calc(100vw - 32px));
2901 padding: 12px 14px 12px 12px;
2902 background: var(--bg-elevated);
2903 border: 1px solid var(--border);
2904 border-radius: 10px;
2905 box-shadow:
2906 0 12px 36px -10px rgba(15,16,28,0.18),
2907 0 2px 6px rgba(15,16,28,0.04),
2908 0 0 0 1px rgba(15,16,28,0.02);
2909 color: var(--text);
2910 font-size: 13.5px;
2911 line-height: 1.45;
2912 opacity: 0;
2913 transform: translateX(16px);
2914 transition:
2915 opacity 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
2916 transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
2917 }
2918 .gx-toast--in { opacity: 1; transform: translateX(0); }
2919 .gx-toast--out { opacity: 0; transform: translateX(16px); }
2920 .gx-toast__icon {
2921 flex-shrink: 0;
2922 width: 20px; height: 20px;
2923 border-radius: 50%;
2924 display: inline-flex;
2925 align-items: center;
2926 justify-content: center;
2927 font-size: 12px;
2928 font-weight: 700;
2929 line-height: 1;
2930 margin-top: 1px;
2931 }
2932 .gx-toast__text { flex: 1 1 auto; min-width: 0; padding-top: 2px; word-wrap: break-word; }
2933 .gx-toast__close {
2934 flex-shrink: 0;
2935 width: 22px; height: 22px;
2936 border: 0;
2937 background: transparent;
2938 color: var(--text-muted);
2939 font-size: 18px;
2940 line-height: 1;
2941 cursor: pointer;
2942 border-radius: 4px;
2943 padding: 0;
2944 margin: -2px -2px 0 0;
2945 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2946 }
2947 .gx-toast__close:hover { background: var(--bg-hover); color: var(--text); }
2948 .gx-toast--success .gx-toast__icon { background: rgba(5,150,105,0.14); color: var(--green); }
2949 .gx-toast--error .gx-toast__icon { background: rgba(220,38,38,0.14); color: var(--red); }
2950 .gx-toast--warn .gx-toast__icon { background: rgba(217,119,6,0.16); color: var(--yellow); }
2951 .gx-toast--info .gx-toast__icon { background: rgba(109,77,255,0.14); color: var(--accent); }
2952 @media (prefers-reduced-motion: reduce) {
2953 .gx-toast { transition: opacity 60ms linear; transform: none; }
2954 .gx-toast--in, .gx-toast--out { transform: none; }
2955 }
2956
dc26881CC LABS App2957 /* ============================================================ */
2958 /* Block U4 — cross-document view transitions. */
2959 /* Chrome 126+, Edge, Safari 18.2+ get a soft 200ms fade on */
2960 /* every same-origin navigation. Older browsers ignore these */
2961 /* rules entirely — no JS shim, no breakage. */
2962 /* prefers-reduced-motion disables the animation honourably. */
2963 /* ============================================================ */
2964 @view-transition {
2965 navigation: auto;
2966 }
2967 ::view-transition-old(root),
2968 ::view-transition-new(root) {
2969 animation-duration: 200ms;
2970 animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
2971 }
2972 ::view-transition-old(root) {
2973 animation-name: vt-fade-out;
2974 }
2975 ::view-transition-new(root) {
2976 animation-name: vt-fade-in;
2977 }
2978 @keyframes vt-fade-out {
2979 to { opacity: 0; }
2980 }
2981 @keyframes vt-fade-in {
2982 from { opacity: 0; }
2983 }
2984 @media (prefers-reduced-motion: reduce) {
2985 ::view-transition-old(root),
2986 ::view-transition-new(root) {
2987 animation-duration: 0s;
2988 }
2989 }
2990 /* The transition system picks up its root subject from this rule. */
2991 body {
2992 view-transition-name: root;
2993 }
fc1817aClaude2994`;