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.tsxBlame3044 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" />
3a5755eClaude61 {/* 2026 polish — load Inter + Inter Tight + JetBrains Mono for
62 crisp modern typography. `preconnect` keeps the handshake
63 cost off the critical path; `display=swap` means we never
64 block first paint waiting on fonts. */}
65 <link rel="preconnect" href="https://fonts.googleapis.com" />
66 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
67 <link
68 rel="stylesheet"
69 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@600;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
70 />
44fe49bClaude71 {/* PWA removed 2026-05-16 — repeated reload-loop bugs (admin
72 dashboard, deploy pill, admin-screen flash). A git host has
73 no use for service workers or install-as-app. Manifest link
74 and SW registrations are gone permanently. */}
eae38d1Claude75 <link rel="icon" type="image/svg+xml" href="/icon.svg" />
5f2e749Claude76 <title>{renderedTitle}</title>
77 {description && <meta name="description" content={description} />}
78 {(ogTitle || fullTitle || title) && (
79 <meta property="og:title" content={ogTitle ?? fullTitle ?? renderedTitle} />
80 )}
81 {(ogDescription || description) && (
82 <meta
83 property="og:description"
84 content={ogDescription ?? description ?? ""}
85 />
86 )}
87 {ogType && <meta property="og:type" content={ogType} />}
88 {twitterCard && <meta name="twitter:card" content={twitterCard} />}
fa880f2Claude89 <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
90 <style dangerouslySetInnerHTML={{ __html: css }} />
91 <style dangerouslySetInnerHTML={{ __html: hljsThemeCss }} />
fc1817aClaude92 </head>
93 <body>
4a52a98Claude94 <div class="prelaunch-banner" role="status" aria-live="polite">
95 Pre-launch &mdash; Gluecron is in final validation. Public signups
96 and git hosting for non-owner users open after launch review.
97 </div>
cd4f63bTest User98 {/* Block Q3 — Playground banner. Renders only when the active
99 user is a playground account with a future expiry; the small
100 inline script counts down once per minute. Strip is dismissible
101 per-page-load (re-appears on next nav) — gentle, not noisy. */}
102 {user && (user as any).isPlayground && (user as any).playgroundExpiresAt && (
103 <div
104 class="playground-banner"
105 role="status"
106 aria-live="polite"
107 data-playground-expires={
108 ((user as any).playgroundExpiresAt instanceof Date
109 ? (user as any).playgroundExpiresAt.toISOString()
110 : String((user as any).playgroundExpiresAt))
111 }
112 >
113 <span class="playground-banner-icon" aria-hidden="true">{"\u{1F3AE}"}</span>
114 <span class="playground-banner-text">
115 Playground account &mdash;{" "}
116 <span class="playground-banner-countdown">expires soon</span>.{" "}
117 <a href="/play/claim" class="playground-banner-cta">
118 Save your work &rarr;
119 </a>
120 </span>
121 <button
122 type="button"
123 class="playground-banner-dismiss"
124 aria-label="Dismiss"
125 data-playground-dismiss="1"
126 >
127 {"×"}
128 </button>
129 <script
130 dangerouslySetInnerHTML={{
131 __html: /* js */ `
132 (function () {
133 var el = document.currentScript && document.currentScript.parentElement;
134 if (!el) return;
135 var iso = el.getAttribute('data-playground-expires');
136 if (!iso) return;
137 var target = Date.parse(iso);
138 if (isNaN(target)) return;
139 var out = el.querySelector('.playground-banner-countdown');
140 function render() {
141 var ms = target - Date.now();
142 if (!out) return;
143 if (ms <= 0) { out.textContent = 'expired'; return; }
144 var mins = Math.floor(ms / 60000);
145 var hrs = Math.floor(mins / 60);
146 if (hrs > 1) out.textContent = hrs + ' hours left';
147 else if (hrs === 1) out.textContent = '1 hour left';
148 else if (mins > 1) out.textContent = mins + ' minutes left';
149 else out.textContent = 'less than a minute left';
150 }
151 render();
152 setInterval(render, 60000);
153 var dismiss = el.querySelector('[data-playground-dismiss="1"]');
154 if (dismiss) {
155 dismiss.addEventListener('click', function () {
156 el.style.display = 'none';
157 });
158 }
159 })();
160 `,
161 }}
162 />
163 </div>
164 )}
fc1817aClaude165 <header>
166 <nav>
167 <a href="/" class="logo">
168 gluecron
169 </a>
3ef4c9dClaude170 <div class="nav-search">
001af43Claude171 <form method="get" action="/search">
3ef4c9dClaude172 <input
173 type="search"
174 name="q"
175 placeholder="Search (press /)"
176 aria-label="Search"
177 />
178 </form>
179 </div>
06d5ffeClaude180 <div class="nav-right">
f764c07Claude181 {/* Block N3 — site-admin platform-deploy status pill. Hidden
182 by default; revealed client-side once /admin/deploys/latest.json
183 responds 200 (non-admins get 401/403 and the pill stays
184 hidden). Subscribes to the `platform:deploys` SSE topic
185 for live updates. See `deployPillScript` below. */}
186 {user && (
187 <a
188 id="deploy-pill"
189 href="/admin/deploys"
190 class="nav-deploy-pill"
191 style="display:none"
192 aria-label="Platform deploy status"
193 title="Platform deploy status"
194 >
195 <span class="deploy-pill-dot" />
196 <span class="deploy-pill-text">Deploys</span>
197 </a>
198 )}
6fc53bdClaude199 <a
200 href="/theme/toggle"
201 class="nav-link nav-theme"
202 title="Toggle theme"
203 aria-label="Toggle theme"
204 >
205 <span class="theme-icon-dark">{"\u263E"}</span>
206 <span class="theme-icon-light">{"\u2600"}</span>
207 </a>
c81ab7aClaude208 <a href="/explore" class="nav-link">
209 Explore
210 </a>
06d5ffeClaude211 {user ? (
212 <>
f1ab587Claude213 <a href="/dashboard" class="nav-link" style="font-weight: 600">
214 Dashboard
215 </a>
bdbd0deClaude216 <a href="/import" class="nav-link">
217 Import
218 </a>
06d5ffeClaude219 <a href="/new" class="btn btn-sm btn-primary">
220 + New
221 </a>
222 <a href={`/${user.username}`} class="nav-user">
223 {user.displayName || user.username}
224 </a>
225 <a href="/settings" class="nav-link">
226 Settings
227 </a>
228 <a href="/logout" class="nav-link">
229 Sign out
230 </a>
231 </>
232 ) : (
233 <>
234 <a href="/login" class="nav-link">
235 Sign in
236 </a>
237 <a href="/register" class="btn btn-sm btn-primary">
238 Register
239 </a>
240 </>
241 )}
242 </div>
fc1817aClaude243 </nav>
244 </header>
45e31d0Claude245 <main id="main-content">{children}</main>
cf9178bTest User246 {/* Global toast host — populated by the toastScript below from
247 ?success= / ?error= / ?toast= query params. Replaces the
248 per-page banner pattern with one polished slide-in. */}
249 <div
250 id="toast-host"
251 aria-live="polite"
252 aria-atomic="true"
253 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"
254 />
fc1817aClaude255 <footer>
958d26aClaude256 <div class="footer-inner">
257 <div class="footer-brand">
258 <a href="/" class="logo">gluecron</a>
259 <p class="footer-tag">
260 AI-native code intelligence. Self-hosted git, automated CI,
261 push-time gates. Software that ships itself.
262 </p>
263 </div>
264 <div class="footer-links">
265 <div class="footer-col">
266 <div class="footer-col-title">Product</div>
b0148e9Claude267 <a href="/features">Features</a>
268 <a href="/pricing">Pricing</a>
958d26aClaude269 <a href="/explore">Explore</a>
270 <a href="/marketplace">Marketplace</a>
271 </div>
272 <div class="footer-col">
273 <div class="footer-col-title">Platform</div>
b0148e9Claude274 <a href="/help">Quickstart</a>
958d26aClaude275 <a href="/status">Status</a>
276 <a href="/api/graphql">GraphQL</a>
277 <a href="/mcp">MCP server</a>
278 </div>
279 <div class="footer-col">
b0148e9Claude280 <div class="footer-col-title">Company</div>
281 <a href="/about">About</a>
958d26aClaude282 <a href="/terms">Terms</a>
283 <a href="/privacy">Privacy</a>
284 <a href="/acceptable-use">Acceptable use</a>
285 </div>
286 </div>
287 </div>
288 <div class="footer-bottom">
289 <span>&copy; {new Date().getFullYear()} gluecron</span>
05cdb85Claude290 <span class="footer-build" title={`commit ${build.shaFull}\nbuilt ${build.builtAt}`}>
291 <span class="footer-build-dot" aria-hidden="true" />
292 {build.sha} · {build.branch}
293 </span>
36b4cbdClaude294 </div>
c63b860Claude295 {siteBannerText ? (
296 <div
297 class={`footer-banner footer-banner-${siteBannerLevel || "info"}`}
298 role="status"
299 aria-live="polite"
300 >
301 {siteBannerText}
302 </div>
303 ) : null}
fc1817aClaude304 </footer>
05cdb85Claude305 {/* Live update poller — checks /api/version every 15s, prompts
306 reload when the running sha changes. Pure progressive-
307 enhancement; degrades to nothing if JS is off. */}
308 <div
309 id="version-banner"
310 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"
311 >
312 <span style="display:inline-flex;align-items:center;gap:8px">
313 <span style="width:8px;height:8px;border-radius:50%;background:#34d399;box-shadow:0 0 10px rgba(52,211,153,0.6)" />
314 <span>New version available</span>
315 </span>
316 <button
317 type="button"
318 id="version-banner-reload"
319 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"
320 >
321 Reload
322 </button>
323 </div>
fa880f2Claude324 <script dangerouslySetInnerHTML={{ __html: versionPollerScript }} />
f764c07Claude325 {/* Block N3 — site-admin deploy status pill (script-only). The pill
326 container is rendered above for authed users; this script bootstraps
327 it by fetching /admin/deploys/latest.json. Non-admins get 401/403
328 and the pill stays display:none — zero leak. */}
329 {user && (
330 <script dangerouslySetInnerHTML={{ __html: deployPillScript }} />
331 )}
699e5c7Claude332 {/* Block I4 — Command palette shell (hidden by default) */}
333 <div
334 id="cmdk-backdrop"
335 style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9998"
336 />
337 <div
338 id="cmdk-panel"
339 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"
340 >
341 <input
342 id="cmdk-input"
343 type="text"
344 placeholder="Type a command..."
345 aria-label="Command palette"
dc26881CC LABS App346 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"
699e5c7Claude347 />
348 <div id="cmdk-list" style="max-height:60vh;overflow-y:auto" />
349 </div>
fa880f2Claude350 <script dangerouslySetInnerHTML={{ __html: clientJs }} />
44fe49bClaude351 {/* PWA-kill script: actively unregisters any service worker
352 previously installed under gluecron.com. Recovers any browser
353 still trapped in the SW reload loop from the legacy registrations. */}
354 <script dangerouslySetInnerHTML={{ __html: pwaKillSwitchScript }} />
cf9178bTest User355 <script dangerouslySetInnerHTML={{ __html: toastScript }} />
fa880f2Claude356 <script dangerouslySetInnerHTML={{ __html: navScript }} />
fc1817aClaude357 </body>
358 </html>
359 );
360};
361
05cdb85Claude362// Live version poller. Checks /api/version every 15s; if the sha differs
363// from the one we booted with, reveal the floating 'New version' pill so
364// the user can reload onto the new code without manually refreshing.
365const versionPollerScript = `
366 (function(){
367 var loadedSha = null;
368 var banner, btn;
369 function poll(){
370 fetch('/api/version', { cache: 'no-store' })
371 .then(function(r){ return r.ok ? r.json() : null; })
372 .then(function(j){
373 if (!j || !j.sha) return;
374 if (loadedSha === null) { loadedSha = j.sha; return; }
375 if (j.sha !== loadedSha && banner) {
376 banner.style.display = 'inline-flex';
377 }
378 })
379 .catch(function(){});
380 }
381 function init(){
382 banner = document.getElementById('version-banner');
383 btn = document.getElementById('version-banner-reload');
384 if (btn) btn.addEventListener('click', function(){ window.location.reload(); });
385 poll();
386 setInterval(poll, 15000);
387 }
388 if (document.readyState === 'loading') {
389 document.addEventListener('DOMContentLoaded', init);
390 } else {
391 init();
392 }
393 })();
394`;
395
f764c07Claude396// Block N3 — site-admin deploy status pill. Fetches /admin/deploys/latest.json;
397// if 200, reveals the pill in the nav and subscribes to the `platform:deploys`
398// SSE topic for live status updates. Non-admins get 401/403 and the pill stays
399// display:none — there's no leakage of admin-only data to other users.
400//
401// Pill states (CSS classes on the container drive colour):
402// .deploy-pill-success 🟢 "Deployed 12s ago"
403// .deploy-pill-progress 🟡 "Deploying… 14s" (pulsing dot)
404// .deploy-pill-failed 🔴 "Deploy failed 1m ago"
405// .deploy-pill-empty ⚪ "No deploys yet"
406//
407// Relative-time auto-refreshes every 15s without re-fetching.
408export const deployPillScript = `
409 (function(){
410 var pill, dot, text;
411 var state = { latest: null, asOf: null };
412
413 function classifyAge(ms){
414 if (ms < 0) return 'just now';
415 var s = Math.floor(ms / 1000);
416 if (s < 5) return 'just now';
417 if (s < 60) return s + 's ago';
418 var m = Math.floor(s / 60);
419 if (m < 60) return m + 'm ago';
420 var h = Math.floor(m / 60);
421 if (h < 24) return h + 'h ago';
422 var d = Math.floor(h / 24);
423 return d + 'd ago';
424 }
425 function elapsed(ms){
426 if (ms < 0) ms = 0;
427 var s = Math.floor(ms / 1000);
428 if (s < 60) return s + 's';
429 var m = Math.floor(s / 60);
430 var rem = s - m * 60;
431 return m + 'm ' + rem + 's';
432 }
433
434 function render(){
435 if (!pill || !text || !dot) return;
436 var d = state.latest;
81201ccTest User437 // When there's no deploy event yet, keep the pill HIDDEN. Showing
438 // "No deploys yet" was visible noise on every admin page load and
439 // flashed during reconnect cycles — admins don't need a placeholder.
440 // The pill reveals itself the first time a real deploy fires.
f764c07Claude441 if (!d) {
81201ccTest User442 pill.style.display = 'none';
f764c07Claude443 return;
444 }
445 pill.style.display = 'inline-flex';
446 var now = Date.now();
447 if (d.status === 'in_progress') {
448 pill.className = 'nav-deploy-pill deploy-pill-progress';
449 var started = Date.parse(d.started_at) || now;
450 text.textContent = 'Deploying… ' + elapsed(now - started);
451 } else if (d.status === 'succeeded') {
452 pill.className = 'nav-deploy-pill deploy-pill-success';
453 var ref = Date.parse(d.finished_at || d.started_at) || now;
454 text.textContent = 'Deployed ' + classifyAge(now - ref);
455 } else if (d.status === 'failed') {
456 pill.className = 'nav-deploy-pill deploy-pill-failed';
457 var refF = Date.parse(d.finished_at || d.started_at) || now;
458 text.textContent = 'Deploy failed ' + classifyAge(now - refF);
459 } else {
460 pill.className = 'nav-deploy-pill';
461 text.textContent = d.status;
462 }
463 }
464
465 function fetchLatest(){
466 fetch('/admin/deploys/latest.json', { cache: 'no-store', credentials: 'same-origin' })
467 .then(function(r){ if (!r.ok) return null; return r.json(); })
468 .then(function(j){
469 if (!j || j.ok !== true) return;
470 state.latest = j.latest;
471 state.asOf = j.asOf;
472 render();
473 subscribe();
474 })
475 .catch(function(){});
476 }
477
478 var subscribed = false;
479 function subscribe(){
480 if (subscribed) return;
481 if (typeof EventSource === 'undefined') return;
482 subscribed = true;
483 var es;
bf19c50Test User484 // Exponential backoff with cap. Previously a tight 1500ms reconnect
485 // produced visible looping in the nav whenever the proxy timed out
486 // or the connection blipped — the bottom-of-page deploy pill
487 // re-rendered the placeholder every 1.5s. Cap at 60s and reset on
488 // successful message receipt.
489 var delay = 2000;
490 var DELAY_MAX = 60000;
491 function bump(){
492 delay = Math.min(delay * 2, DELAY_MAX);
493 }
494 function resetDelay(){
495 delay = 2000;
496 }
f764c07Claude497 function connect(){
498 try { es = new EventSource('/live-events/platform:deploys'); }
bf19c50Test User499 catch(e){ bump(); setTimeout(connect, delay); return; }
f764c07Claude500 es.onmessage = function(m){
bf19c50Test User501 resetDelay();
f764c07Claude502 try {
503 var d = JSON.parse(m.data);
504 if (d && d.run_id) {
505 if (!state.latest || state.latest.run_id === d.run_id ||
506 Date.parse(d.started_at) >= Date.parse(state.latest.started_at)) {
507 state.latest = d;
508 render();
509 }
510 }
511 } catch(e){}
512 };
513 es.onerror = function(){
514 try { es.close(); } catch(e){}
bf19c50Test User515 bump();
f764c07Claude516 setTimeout(connect, delay);
517 };
518 }
519 connect();
520 }
521
522 function init(){
523 pill = document.getElementById('deploy-pill');
524 if (!pill) return;
525 dot = pill.querySelector('.deploy-pill-dot');
526 text = pill.querySelector('.deploy-pill-text');
527 fetchLatest();
528 // Refresh relative-time labels every 15s so "12s ago" → "27s ago"
529 // without a fresh fetch.
530 setInterval(render, 15000);
531 }
532 if (document.readyState === 'loading') {
533 document.addEventListener('DOMContentLoaded', init);
534 } else {
535 init();
536 }
537 })();
538`;
539
6fc53bdClaude540// Runs before paint — reads the theme cookie and flips data-theme so there's
81201ccTest User541// no light-to-dark flash on load. SSR default is "light"; cookie-set "dark"
542// is honoured for users who explicitly opted in.
6fc53bdClaude543const themeInitScript = `
544 (function(){
545 try {
546 var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
81201ccTest User547 var t = m ? decodeURIComponent(m[1]) : 'light';
548 if (t !== 'light' && t !== 'dark') t = 'light';
6fc53bdClaude549 document.documentElement.setAttribute('data-theme', t);
550 } catch(_){}
551 })();
552`;
553
eae38d1Claude554// Block G1 — register service worker for offline / install support.
555// Kept inline (and tiny) so we don't block first paint.
b1be050CC LABS App556//
cf9178bTest User557// Global toast notifications — reads ?success=, ?error=, ?toast= from the
558// URL on page load and surfaces a polished slide-in toast instead of the
559// per-page banner divs that crowded the layout. Toasts auto-dismiss after
560// 4.5s; query params are scrubbed from the URL via history.replaceState
561// so a subsequent Refresh doesn't re-fire the same toast.
562//
563// Variants: ?success=…, ?error=…, ?toast=info:…, ?toast=warn:… All values
564// must be URI-encoded (callers already do this via encodeURIComponent
565// in c.redirect()).
566const toastScript = `
567 (function(){
568 function showToast(kind, message){
569 if (!message) return;
570 var host = document.getElementById('toast-host');
571 if (!host) return;
572 var el = document.createElement('div');
573 el.className = 'gx-toast gx-toast--' + kind;
574 el.setAttribute('role', kind === 'error' ? 'alert' : 'status');
575 var icon = document.createElement('span');
576 icon.className = 'gx-toast__icon';
577 icon.textContent = kind === 'success' ? '\\u2713'
578 : kind === 'error' ? '\\u00D7'
579 : kind === 'warn' ? '!'
580 : 'i';
581 el.appendChild(icon);
582 var text = document.createElement('span');
583 text.className = 'gx-toast__text';
584 text.textContent = message;
585 el.appendChild(text);
586 var close = document.createElement('button');
587 close.type = 'button';
588 close.className = 'gx-toast__close';
589 close.setAttribute('aria-label', 'Dismiss notification');
590 close.textContent = '\\u00D7';
591 close.addEventListener('click', function(){ dismiss(); });
592 el.appendChild(close);
593 host.appendChild(el);
594 // Force a reflow then add the visible class so the slide-in transitions.
595 void el.offsetWidth;
596 el.classList.add('gx-toast--in');
597 var timer = setTimeout(dismiss, 4500);
598 function dismiss(){
599 clearTimeout(timer);
600 el.classList.remove('gx-toast--in');
601 el.classList.add('gx-toast--out');
602 setTimeout(function(){
603 if (el.parentNode) el.parentNode.removeChild(el);
604 }, 220);
605 }
606 }
607 try {
608 var url = new URL(window.location.href);
609 var hits = 0;
610 var s = url.searchParams.get('success');
611 if (s) { showToast('success', s); url.searchParams.delete('success'); hits++; }
612 var e = url.searchParams.get('error');
613 if (e) { showToast('error', e); url.searchParams.delete('error'); hits++; }
614 var t = url.searchParams.get('toast');
615 if (t) {
616 var ix = t.indexOf(':');
617 var kind = ix > 0 ? t.slice(0, ix) : 'info';
618 var msg = ix > 0 ? t.slice(ix + 1) : t;
619 showToast(kind, msg);
620 url.searchParams.delete('toast');
621 hits++;
622 }
623 if (hits > 0 && window.history && window.history.replaceState) {
624 window.history.replaceState({}, '', url.pathname + (url.searchParams.toString() ? '?' + url.searchParams.toString() : '') + url.hash);
625 }
626 } catch(_) {}
627 })();
628`;
629
44fe49bClaude630// PWA kill-switch (2026-05-16) — replaces the previous pwaRegisterScript
631// and pwaInstallBannerScript. Those two scripts registered /sw.js and
632// /sw-push.js at the same scope, causing a reload loop that made the
633// admin dashboard unusable (deploy pill flashing, typing wiped, buttons
634// uncllickable). Per the SW spec, only one SW can control a scope; two
635// different script URLs at the same scope keep replacing each other.
6345c3eTest User636//
44fe49bClaude637// PWA is gone for good. A git host has no use for service workers,
638// install-as-app, or push notifications via SW. This script actively
639// unregisters every previously installed SW on the gluecron.com origin
640// so any browser still trapped in the loop recovers on the very next
641// page load — without needing the user to clear site data or open
642// DevTools. Idempotent and safe to keep running forever; once all
643// browsers have been cleaned, it's a no-op.
644const pwaKillSwitchScript = `
534f04aClaude645(function(){
646 try {
44fe49bClaude647 if (!('serviceWorker' in navigator)) return;
648 if (!navigator.serviceWorker.getRegistrations) return;
649 navigator.serviceWorker.getRegistrations().then(function(regs){
650 if (!regs || regs.length === 0) return;
651 regs.forEach(function(reg){
652 try { reg.unregister(); } catch(_){}
653 });
654 }).catch(function(){});
655 // Also drop any caches the old SWs left behind so the user gets
656 // truly fresh HTML on every page load. Restricted to gluecron-*
657 // namespaced caches so we don't trample anything a future opt-in
658 // feature might create under a different name.
659 if ('caches' in self) {
660 caches.keys().then(function(keys){
661 keys.forEach(function(k){
662 if (typeof k === 'string' && k.indexOf('gluecron') === 0) {
663 try { caches.delete(k); } catch(_){}
d7ba05dClaude664 }
665 });
666 }).catch(function(){});
534f04aClaude667 }
668 } catch(_) {}
669})();
670`;
671
3ef4c9dClaude672const navScript = `
673 (function(){
674 var chord = null;
675 var chordTimer = null;
676 function isTyping(t){
677 t = t || {};
678 var tag = (t.tagName || '').toLowerCase();
679 return tag === 'input' || tag === 'textarea' || t.isContentEditable;
680 }
71cd5ecClaude681
682 // ---------- Block I4 — Command palette ----------
683 var COMMANDS = [
684 { label: 'Go to Dashboard', href: '/dashboard', kw: 'home' },
685 { label: 'Go to Explore', href: '/explore', kw: 'browse discover' },
686 { label: 'Go to Notifications', href: '/notifications', kw: 'inbox' },
687 { label: 'Go to Ask AI', href: '/ask', kw: 'chat assistant' },
688 { label: 'Create new repository', href: '/new', kw: 'add create' },
689 { label: 'Marketplace', href: '/marketplace', kw: 'apps store' },
690 { label: 'Installed apps', href: '/settings/apps', kw: 'my apps' },
691 { label: 'Register new app', href: '/developer/apps-new', kw: 'developer create' },
692 { label: 'Keyboard shortcuts', href: '/shortcuts', kw: 'help keys' },
693 { label: 'Settings (profile)', href: '/settings', kw: 'account' },
694 { label: '2FA settings', href: '/settings/2fa', kw: 'two factor security' },
695 { label: 'Passkeys settings', href: '/settings/passkeys', kw: 'webauthn' },
696 { label: 'Personal access tokens', href: '/settings/tokens', kw: 'pat api' },
697 { label: 'Billing + quotas', href: '/settings/billing', kw: 'plans money' },
698 { label: 'Audit log (personal)', href: '/settings/audit', kw: 'history' },
699 { label: 'Gists', href: '/gists', kw: 'snippets' },
700 { label: 'GraphQL explorer', href: '/api/graphql', kw: 'api query' },
701 { label: 'Admin dashboard', href: '/admin', kw: 'superuser' },
702 { label: 'Toggle theme', href: '/theme/toggle', kw: 'dark light mode' }
703 ];
704
705 function fuzzyMatch(item, q){
706 if (!q) return true;
707 var hay = (item.label + ' ' + (item.kw||'') + ' ' + item.href).toLowerCase();
708 q = q.toLowerCase();
709 var qi = 0;
710 for (var i = 0; i < hay.length && qi < q.length; i++) {
711 if (hay[i] === q[qi]) qi++;
712 }
713 return qi === q.length;
714 }
715
716 var backdrop, panel, input, list, selected = 0, filtered = COMMANDS.slice();
717
718 function render(){
719 if (!list) return;
720 var html = '';
721 for (var i = 0; i < filtered.length; i++) {
722 var item = filtered[i];
723 var cls = i === selected ? 'cmdk-item cmdk-active' : 'cmdk-item';
724 var bg = i === selected ? 'background:var(--bg);' : '';
ea52715copilot-swe-agent[bot]725 html += '<div class="' + cls + '" data-idx="' + i + '" data-url="' + item.href + '"' +
dc26881CC LABS App726 ' style="padding:var(--space-2) var(--space-4);cursor:pointer;border-bottom:1px solid var(--border);' + bg + '">' +
71cd5ecClaude727 '<div>' + item.label + '</div>' +
728 '<div style="font-size:11px;color:var(--text-muted)">' + item.href + '</div>' +
729 '</div>';
730 }
731 if (filtered.length === 0) {
dc26881CC LABS App732 html = '<div style="padding:var(--space-4);color:var(--text-muted);text-align:center">No matches.</div>';
71cd5ecClaude733 }
734 list.innerHTML = html;
735 }
736
737 function openPalette(){
738 backdrop = document.getElementById('cmdk-backdrop');
739 panel = document.getElementById('cmdk-panel');
740 input = document.getElementById('cmdk-input');
741 list = document.getElementById('cmdk-list');
742 if (!backdrop || !panel) return;
743 backdrop.style.display = 'block';
744 panel.style.display = 'block';
745 input.value = '';
746 selected = 0;
747 filtered = COMMANDS.slice();
748 render();
749 input.focus();
750 }
751 function closePalette(){
752 if (backdrop) backdrop.style.display = 'none';
753 if (panel) panel.style.display = 'none';
754 }
755 function go(href){ closePalette(); window.location.href = href; }
756
757 document.addEventListener('click', function(e){
758 var t = e.target;
759 if (t && t.id === 'cmdk-backdrop') { closePalette(); return; }
760 var item = t && t.closest && t.closest('.cmdk-item');
ea52715copilot-swe-agent[bot]761 if (item) { go(item.getAttribute('data-url')); }
71cd5ecClaude762 });
763
764 document.addEventListener('input', function(e){
765 if (e.target && e.target.id === 'cmdk-input') {
766 var q = e.target.value;
767 filtered = COMMANDS.filter(function(c){ return fuzzyMatch(c, q); });
768 selected = 0;
769 render();
770 }
771 });
772
3ef4c9dClaude773 document.addEventListener('keydown', function(e){
71cd5ecClaude774 // Palette-scoped keys take priority when open
775 if (panel && panel.style.display === 'block') {
776 if (e.key === 'Escape') { e.preventDefault(); closePalette(); return; }
777 if (e.key === 'ArrowDown') {
778 e.preventDefault();
779 selected = Math.min(filtered.length - 1, selected + 1);
780 render();
781 return;
782 }
783 if (e.key === 'ArrowUp') {
784 e.preventDefault();
785 selected = Math.max(0, selected - 1);
786 render();
787 return;
788 }
789 if (e.key === 'Enter') {
790 e.preventDefault();
791 var item = filtered[selected];
792 if (item) go(item.href);
793 return;
794 }
795 return;
796 }
797
3ef4c9dClaude798 if (isTyping(e.target)) return;
799 // Single key shortcuts
800 if (e.key === '/') {
801 var el = document.querySelector('.nav-search input');
802 if (el) { e.preventDefault(); el.focus(); return; }
803 }
804 if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase() === 'k') {
71cd5ecClaude805 e.preventDefault();
806 openPalette();
807 return;
3ef4c9dClaude808 }
809 if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
810 e.preventDefault(); window.location.href = '/shortcuts'; return;
811 }
812 if (e.key === 'n' && !e.ctrlKey && !e.metaKey) {
813 e.preventDefault(); window.location.href = '/new'; return;
814 }
815 // "g" chord
816 if (e.key === 'g' && !e.ctrlKey && !e.metaKey) {
817 chord = 'g';
818 clearTimeout(chordTimer);
819 chordTimer = setTimeout(function(){ chord = null; }, 1200);
820 return;
821 }
822 if (chord === 'g') {
823 if (e.key === 'd') { e.preventDefault(); window.location.href = '/dashboard'; }
824 else if (e.key === 'n') { e.preventDefault(); window.location.href = '/notifications'; }
825 else if (e.key === 'e') { e.preventDefault(); window.location.href = '/explore'; }
826 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
827 chord = null;
828 }
829 });
830 })();
831`;
832
fc1817aClaude833const css = `
2ce1d0bClaude834 /* ================================================================
958d26aClaude835 * Gluecron design system — 2026.05 "Editorial-Technical"
836 * Slate-noir base · refined violet signature · hairline geometry ·
837 * mono-as-feature · cinematic motion · Inter Tight + JetBrains Mono.
838 * All class names preserved for back-compat across 50+ route views.
2ce1d0bClaude839 * ============================================================== */
6fc53bdClaude840 :root, :root[data-theme='dark'] {
958d26aClaude841 /* Surfaces — slate, not black. More depth, less crush. */
842 --bg: #08090f;
843 --bg-secondary: #0c0d14;
844 --bg-tertiary: #11131c;
845 --bg-elevated: #0f111a;
846 --bg-surface: #161826;
847 --bg-hover: rgba(255,255,255,0.04);
848 --bg-active: rgba(255,255,255,0.08);
849 --bg-inset: rgba(0,0,0,0.30);
850
851 /* Borders — three weights, used deliberately */
852 --border: rgba(255,255,255,0.06);
853 --border-subtle: rgba(255,255,255,0.035);
854 --border-strong: rgba(255,255,255,0.13);
855 --border-focus: rgba(140,109,255,0.55);
856
857 /* Text */
858 --text: #ededf2;
859 --text-strong: #f7f7fb;
860 --text-muted: #8b8c9c;
861 --text-faint: #555665;
862 --text-link: #b69dff;
863
864 /* Accent — refined violet (less candy), warm amber as secondary signal */
865 --accent: #8c6dff;
866 --accent-2: #36c5d6;
867 --accent-warm: #ffb45e;
868 --accent-hover: #a48bff;
869 --accent-pressed:#7559e8;
870 --accent-gradient: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
871 --accent-gradient-soft: linear-gradient(135deg, rgba(140,109,255,0.18) 0%, rgba(54,197,214,0.18) 100%);
872 --accent-gradient-faint: linear-gradient(135deg, rgba(140,109,255,0.07) 0%, rgba(54,197,214,0.07) 100%);
873 --accent-glow: 0 0 24px rgba(140,109,255,0.28);
874
875 /* Semantic */
876 --green: #34d399;
877 --red: #f87171;
878 --yellow: #fbbf24;
879 --amber: #fbbf24;
880 --blue: #60a5fa;
881
3a5755eClaude882 /* Type — 2026 polish pass. Inter is the primary sans, Inter Tight for
883 display (headlines), JetBrains Mono for code. All three loaded from
884 Google Fonts with display:swap so we never block first paint. System
885 fallbacks remain in place — if the CDN is unreachable the site still
886 renders cleanly with Segoe UI (Win) / SF (Mac) / Roboto (Android). */
887 --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', 'Cascadia Mono', Menlo, Consolas, 'Courier New', monospace;
888 --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, sans-serif;
889 --font-display: 'Inter Tight', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, sans-serif;
958d26aClaude890 --mono-feat: 'calt', 'liga', 'ss01';
891
892 /* Radius — sharper than before */
893 --r-sm: 5px;
894 --r: 7px;
895 --r-md: 9px;
896 --r-lg: 12px;
897 --r-xl: 16px;
898 --r-2xl: 22px;
899 --r-full: 9999px;
900 --radius: 7px;
901
902 /* Type scale — bigger display sizes for editorial feel */
903 --t-xs: 11px;
904 --t-sm: 13px;
905 --t-base: 14px;
906 --t-md: 16px;
907 --t-lg: 20px;
908 --t-xl: 28px;
909 --t-2xl: 40px;
910 --t-3xl: 56px;
911 --t-display: 72px;
912 --t-display-lg:96px;
913
914 /* Spacing — 4px base */
2ce1d0bClaude915 --s-0: 0;
958d26aClaude916 --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
917 --s-5: 20px; --s-6: 24px; --s-7: 28px; --s-8: 32px;
918 --s-10:40px; --s-12:48px; --s-14:56px; --s-16:64px;
919 --s-20:80px; --s-24:96px; --s-32:128px;
920
921 /* Elevation — softer + more layered */
922 --elev-0: 0 0 0 1px var(--border);
923 --elev-1: 0 1px 2px rgba(0,0,0,0.50), 0 0 0 1px var(--border);
924 --elev-2: 0 8px 24px -8px rgba(0,0,0,0.60), 0 0 0 1px var(--border);
925 --elev-3: 0 20px 48px -12px rgba(0,0,0,0.70), 0 0 0 1px var(--border-strong);
926 --elev-glow: 0 0 0 1px rgba(140,109,255,0.40), 0 0 32px -4px rgba(140,109,255,0.30);
927 --ring: 0 0 0 3px rgba(140,109,255,0.28);
928 --ring-warn: 0 0 0 3px rgba(251,191,36,0.28);
929 --ring-err: 0 0 0 3px rgba(248,113,113,0.28);
930
931 /* Motion */
932 --ease: cubic-bezier(0.16, 1, 0.3, 1);
933 --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
934 --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
935 --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
936 --t-fast: 120ms;
937 --t-base: 200ms;
938 --t-slow: 360ms;
939 --t-slower:560ms;
940
941 --header-h: 60px;
c63b860Claude942
943 /* Block O3 — visual coherence: named token aliases (additive). */
944 --space-1: var(--s-1);
945 --space-2: var(--s-2);
946 --space-3: var(--s-3);
947 --space-4: var(--s-4);
948 --space-5: var(--s-5);
949 --space-6: var(--s-6);
950 --space-8: var(--s-8);
951 --space-10: var(--s-10);
952 --space-12: var(--s-12);
953 --space-16: var(--s-16);
954 --space-20: var(--s-20);
955 --space-24: var(--s-24);
956 --radius-sm: var(--r-sm);
957 --radius-md: var(--r-md);
958 --radius-lg: var(--r-lg);
959 --radius-xl: var(--r-xl);
960 --radius-full: var(--r-full);
961 --font-size-xs: var(--t-xs);
962 --font-size-sm: var(--t-sm);
963 --font-size-base: var(--t-base);
964 --font-size-md: var(--t-md);
965 --font-size-lg: var(--t-lg);
966 --font-size-xl: var(--t-xl);
967 --font-size-2xl: var(--t-2xl);
968 --font-size-3xl: var(--t-3xl);
969 --font-size-hero: var(--t-display);
970 --leading-tight: 1.2;
971 --leading-snug: 1.35;
972 --leading-normal: 1.5;
973 --leading-relaxed: 1.6;
974 --leading-loose: 1.7;
975 --z-base: 1;
976 --z-nav: 10;
977 --z-sticky: 50;
978 --z-overlay: 100;
979 --z-modal: 1000;
980 --z-toast: 10000;
fc1817aClaude981 }
982
6fc53bdClaude983 :root[data-theme='light'] {
958d26aClaude984 --bg: #fbfbfc;
4c47454Claude985 --bg-secondary: #ffffff;
958d26aClaude986 --bg-tertiary: #f3f3f6;
987 --bg-elevated: #ffffff;
988 --bg-surface: #f6f6f9;
989 --bg-hover: rgba(0,0,0,0.035);
990 --bg-active: rgba(0,0,0,0.07);
991 --bg-inset: rgba(0,0,0,0.04);
992
993 --border: rgba(15,16,28,0.08);
994 --border-subtle: rgba(15,16,28,0.04);
995 --border-strong: rgba(15,16,28,0.16);
996
997 --text: #0e1020;
998 --text-strong: #050617;
999 --text-muted: #5a5b70;
1000 --text-faint: #8a8b9e;
1001 --text-link: #6d4dff;
1002
1003 --accent: #6d4dff;
1004 --accent-2: #0891b2;
1005 --accent-hover: #5a3df0;
1006 --accent-pressed:#4a30d6;
1007 --accent-glow: 0 0 24px rgba(109,77,255,0.18);
1008
1009 --green: #059669;
1010 --red: #dc2626;
4c47454Claude1011 --yellow: #d97706;
958d26aClaude1012
1013 --elev-1: 0 1px 2px rgba(15,16,28,0.06), 0 0 0 1px var(--border);
1014 --elev-2: 0 8px 24px -10px rgba(15,16,28,0.10), 0 0 0 1px var(--border);
1015 --elev-3: 0 20px 48px -16px rgba(15,16,28,0.14), 0 0 0 1px var(--border-strong);
6fc53bdClaude1016 }
1017
1018 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
958d26aClaude1019 .nav-theme { display: inline-flex; align-items: center; font-size: 15px; line-height: 1; opacity: 0.85; }
1020 .nav-theme:hover { opacity: 1; }
6fc53bdClaude1021 :root[data-theme='dark'] .theme-icon-dark { display: none; }
1022 :root[data-theme='light'] .theme-icon-light { display: none; }
1023
fc1817aClaude1024 * { margin: 0; padding: 0; box-sizing: border-box; }
958d26aClaude1025 *::selection { background: rgba(140,109,255,0.32); color: var(--text-strong); }
2ce1d0bClaude1026
958d26aClaude1027 html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
fc1817aClaude1028
1029 body {
1030 font-family: var(--font-sans);
1031 background: var(--bg);
1032 color: var(--text);
cf9178bTest User1033 font-size: 15px;
2ce1d0bClaude1034 line-height: 1.55;
958d26aClaude1035 letter-spacing: -0.011em;
fc1817aClaude1036 min-height: 100vh;
1037 display: flex;
1038 flex-direction: column;
958d26aClaude1039 font-feature-settings: 'cv11', 'ss01', 'ss03', 'calt';
cf9178bTest User1040 /* Subtle: prefers grayscale font smoothing on macOS for thin text,
1041 and disables automatic synthesis of bold/italic which can produce
1042 muddier rendering on certain weights. */
1043 -webkit-font-smoothing: antialiased;
1044 -moz-osx-font-smoothing: grayscale;
1045 font-synthesis: none;
1046 }
1047 /* Tighten heading rhythm — the body is 15/1.55, headings step down
1048 line-height inversely with size so display text doesn't feel airy. */
1049 h1, h2, h3, h4, h5, h6 {
1050 font-family: var(--font-display);
1051 color: var(--text-strong);
1052 letter-spacing: -0.022em;
1053 line-height: 1.18;
1054 font-weight: 650;
1055 margin-top: 0;
1056 }
1057 h1 { font-size: 28px; letter-spacing: -0.028em; }
1058 h2 { font-size: 22px; }
1059 h3 { font-size: 18px; letter-spacing: -0.018em; }
1060 h4 { font-size: 15.5px; letter-spacing: -0.012em; font-weight: 600; }
1061 /* Link refinement — underline only on hover/focus, never by default
1062 on internal nav. Prevents the "blue underline soup" look. */
1063 a { color: var(--text-link); text-decoration: none; transition: color var(--t-fast) var(--ease); }
1064 a:hover { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 3px; }
1065 a:focus-visible {
1066 outline: none;
1067 box-shadow: 0 0 0 3px rgba(109,77,255,0.32);
1068 border-radius: 3px;
fc1817aClaude1069 }
1070
958d26aClaude1071 /* Whole-page atmosphere: very subtle gradient + dot-grid layered behind everything.
1072 Keeps every page feeling like part of the same product without competing with hero art. */
2ce1d0bClaude1073 body::before {
1074 content: '';
1075 position: fixed;
1076 inset: 0;
1077 pointer-events: none;
958d26aClaude1078 z-index: -2;
2ce1d0bClaude1079 background:
958d26aClaude1080 radial-gradient(70% 55% at 85% -20%, rgba(140,109,255,0.07), transparent 65%),
1081 radial-gradient(55% 45% at -10% 115%, rgba(54,197,214,0.05), transparent 65%);
1082 }
1083 body::after {
1084 content: '';
1085 position: fixed;
1086 inset: 0;
1087 pointer-events: none;
1088 z-index: -1;
1089 background-image: radial-gradient(rgba(255,255,255,0.025) 1px, transparent 1px);
1090 background-size: 28px 28px;
1091 background-position: 0 0;
1092 opacity: 0.55;
1093 mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 70%);
1094 -webkit-mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 70%);
1095 }
1096 :root[data-theme='light'] body::before { opacity: 0.55; }
1097 :root[data-theme='light'] body::after {
1098 background-image: radial-gradient(rgba(15,16,28,0.06) 1px, transparent 1px);
1099 opacity: 0.4;
fc1817aClaude1100 }
2ce1d0bClaude1101
1102 a { color: var(--text-link); text-decoration: none; transition: color var(--t-fast) var(--ease); }
1103 a:hover { color: var(--accent-hover); text-decoration: none; }
fc1817aClaude1104
958d26aClaude1105 /* Heading scale — confident, editorial, tight tracking */
1106 h1, h2, h3, h4, h5, h6 {
1107 font-family: var(--font-display);
2ce1d0bClaude1108 font-weight: 600;
958d26aClaude1109 letter-spacing: -0.022em;
1110 line-height: 1.18;
1111 color: var(--text-strong);
1112 }
1113 h1 { font-size: var(--t-xl); letter-spacing: -0.028em; }
1114 h2 { font-size: var(--t-lg); letter-spacing: -0.022em; }
1115 h3 { font-size: var(--t-md); font-weight: 600; letter-spacing: -0.015em; }
1116 h4 { font-size: var(--t-base); font-weight: 600; letter-spacing: -0.01em; }
1117 h5, h6 { font-size: var(--t-sm); font-weight: 600; letter-spacing: -0.005em; }
1118
1119 /* Editorial display heading utility — used by landing + marketing pages */
1120 .display {
1121 font-family: var(--font-display);
1122 font-size: clamp(40px, 7.5vw, 96px);
1123 line-height: 0.98;
1124 letter-spacing: -0.04em;
1125 font-weight: 600;
1126 color: var(--text-strong);
2ce1d0bClaude1127 }
fc1817aClaude1128
958d26aClaude1129 /* Eyebrow — uppercase mono label that sits above section headings */
1130 .eyebrow {
1131 display: inline-flex;
1132 align-items: center;
1133 gap: 8px;
1134 font-family: var(--font-mono);
1135 font-size: 11px;
1136 font-weight: 500;
1137 letter-spacing: 0.14em;
1138 text-transform: uppercase;
1139 color: var(--accent);
1140 margin-bottom: var(--s-3);
1141 }
1142 .eyebrow::before {
1143 content: '';
1144 width: 18px;
1145 height: 1px;
1146 background: currentColor;
1147 opacity: 0.6;
1148 }
1149
1150 /* Section header — paired eyebrow + title + lede */
1151 .section-header { max-width: 720px; margin: 0 auto var(--s-10); text-align: center; }
1152 .section-header.left { text-align: left; margin-left: 0; margin-right: auto; }
1153 .section-header h2 {
1154 font-size: clamp(28px, 4vw, 44px);
1155 line-height: 1.05;
1156 letter-spacing: -0.028em;
1157 margin-bottom: var(--s-3);
1158 }
1159 .section-header p {
1160 color: var(--text-muted);
1161 font-size: var(--t-md);
1162 line-height: 1.6;
1163 max-width: 580px;
1164 margin: 0 auto;
1165 }
1166 .section-header.left p { margin-left: 0; }
fc1817aClaude1167
958d26aClaude1168 code, kbd, samp {
2ce1d0bClaude1169 font-family: var(--font-mono);
958d26aClaude1170 font-feature-settings: var(--mono-feat);
1171 }
1172 code {
1173 font-size: 0.9em;
2ce1d0bClaude1174 background: var(--bg-tertiary);
958d26aClaude1175 border: 1px solid var(--border-subtle);
2ce1d0bClaude1176 padding: 1px 6px;
1177 border-radius: var(--r-sm);
1178 color: var(--text);
1179 }
958d26aClaude1180 kbd, .kbd {
1181 display: inline-flex;
1182 align-items: center;
1183 padding: 1px 6px;
1184 font-family: var(--font-mono);
1185 font-size: 11px;
1186 background: var(--bg-elevated);
1187 border: 1px solid var(--border);
1188 border-bottom-width: 2px;
1189 border-radius: 4px;
1190 color: var(--text);
1191 line-height: 1.5;
1192 vertical-align: middle;
1193 }
2ce1d0bClaude1194
958d26aClaude1195 /* Mono utility for technical chrome (paths, IDs, dates) */
1196 .mono { font-family: var(--font-mono); font-feature-settings: var(--mono-feat); font-size: 0.96em; }
1197 .meta-mono { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--text-muted); letter-spacing: 0; }
1198
1199 /* Pre-launch banner — slim, refined, mono caption */
4a52a98Claude1200 .prelaunch-banner {
958d26aClaude1201 position: relative;
2ce1d0bClaude1202 background:
958d26aClaude1203 linear-gradient(180deg, rgba(251,191,36,0.10), rgba(251,191,36,0.03)),
2ce1d0bClaude1204 var(--bg);
958d26aClaude1205 border-bottom: 1px solid rgba(251,191,36,0.28);
4a52a98Claude1206 color: var(--yellow);
958d26aClaude1207 padding: 7px 24px;
1208 font-family: var(--font-mono);
1209 font-size: 11px;
4a52a98Claude1210 font-weight: 500;
1211 text-align: center;
2ce1d0bClaude1212 line-height: 1.5;
958d26aClaude1213 letter-spacing: 0.04em;
1214 text-transform: uppercase;
1215 }
1216 .prelaunch-banner::before {
1217 content: '◆';
1218 margin-right: 8px;
1219 font-size: 9px;
1220 opacity: 0.7;
1221 vertical-align: 1px;
4a52a98Claude1222 }
1223
cd4f63bTest User1224 /* Block Q3 — Playground banner. Brighter than the prelaunch strip so
1225 visitors don't miss the "save your work" CTA, but slim enough to
1226 not feel like a modal. */
1227 .playground-banner {
1228 position: relative;
1229 display: flex;
1230 align-items: center;
1231 justify-content: center;
1232 gap: 8px;
1233 background:
1234 linear-gradient(180deg, rgba(251,191,36,0.20), rgba(251,191,36,0.06)),
1235 var(--bg);
1236 border-bottom: 1px solid rgba(251,191,36,0.45);
1237 color: var(--yellow, #fbbf24);
1238 padding: 8px 40px 8px 24px;
1239 font-size: 13px;
1240 font-weight: 500;
1241 text-align: center;
1242 line-height: 1.4;
1243 }
1244 .playground-banner-icon { font-size: 14px; }
1245 .playground-banner-text { color: var(--text-strong, #e6edf3); }
1246 .playground-banner-countdown { font-weight: 600; }
1247 .playground-banner-cta {
1248 margin-left: 4px;
1249 color: var(--yellow, #fbbf24);
1250 text-decoration: underline;
1251 font-weight: 600;
1252 }
1253 .playground-banner-cta:hover { opacity: 0.85; }
1254 .playground-banner-dismiss {
1255 position: absolute;
1256 top: 50%;
1257 right: 12px;
1258 transform: translateY(-50%);
1259 background: transparent;
1260 border: none;
1261 color: var(--text-muted, #8b949e);
1262 font-size: 18px;
1263 line-height: 1;
1264 cursor: pointer;
1265 padding: 0 4px;
1266 }
1267 .playground-banner-dismiss:hover { color: var(--text-strong, #e6edf3); }
1268
958d26aClaude1269 /* Header — sticky, blurred, hairline border, taller for breathing room */
fc1817aClaude1270 header {
2ce1d0bClaude1271 position: sticky;
1272 top: 0;
1273 z-index: 100;
fc1817aClaude1274 border-bottom: 1px solid var(--border);
2ce1d0bClaude1275 padding: 0 24px;
1276 height: var(--header-h);
958d26aClaude1277 background: rgba(8,9,15,0.72);
1278 backdrop-filter: saturate(180%) blur(18px);
1279 -webkit-backdrop-filter: saturate(180%) blur(18px);
fc1817aClaude1280 }
958d26aClaude1281 :root[data-theme='light'] header { background: rgba(251,251,252,0.78); }
fc1817aClaude1282
06d5ffeClaude1283 header nav {
1284 display: flex;
1285 align-items: center;
958d26aClaude1286 gap: 18px;
1287 max-width: 1240px;
06d5ffeClaude1288 margin: 0 auto;
2ce1d0bClaude1289 height: 100%;
1290 }
1291 .logo {
958d26aClaude1292 font-family: var(--font-display);
1293 font-size: 16px;
2ce1d0bClaude1294 font-weight: 700;
958d26aClaude1295 letter-spacing: -0.025em;
1296 color: var(--text-strong);
2ce1d0bClaude1297 display: inline-flex;
1298 align-items: center;
958d26aClaude1299 gap: 9px;
1300 transition: opacity var(--t-fast) var(--ease);
06d5ffeClaude1301 }
2ce1d0bClaude1302 .logo::before {
1303 content: '';
958d26aClaude1304 width: 20px; height: 20px;
1305 border-radius: 6px;
2ce1d0bClaude1306 background: var(--accent-gradient);
958d26aClaude1307 box-shadow:
1308 inset 0 1px 0 rgba(255,255,255,0.25),
1309 0 0 0 1px rgba(140,109,255,0.45),
1310 0 0 20px rgba(140,109,255,0.30);
2ce1d0bClaude1311 flex-shrink: 0;
958d26aClaude1312 transition: transform var(--t-base) var(--ease-spring), box-shadow var(--t-base) var(--ease);
1313 }
1314 .logo:hover { text-decoration: none; color: var(--text-strong); }
1315 .logo:hover::before {
1316 transform: rotate(8deg) scale(1.05);
1317 box-shadow:
1318 inset 0 1px 0 rgba(255,255,255,0.30),
1319 0 0 0 1px rgba(140,109,255,0.55),
1320 0 0 28px rgba(140,109,255,0.45);
06d5ffeClaude1321 }
fc1817aClaude1322
2ce1d0bClaude1323 .nav-search {
1324 flex: 1;
958d26aClaude1325 max-width: 360px;
1326 margin: 0 4px 0 8px;
1327 position: relative;
2ce1d0bClaude1328 }
1329 .nav-search input {
1330 width: 100%;
958d26aClaude1331 padding: 7px 12px 7px 32px;
2ce1d0bClaude1332 background: var(--bg-tertiary);
1333 border: 1px solid var(--border);
1334 border-radius: var(--r-sm);
1335 color: var(--text);
1336 font-family: var(--font-sans);
1337 font-size: var(--t-sm);
958d26aClaude1338 transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
1339 }
1340 .nav-search::before {
1341 content: '';
1342 position: absolute;
1343 left: 11px; top: 50%;
1344 transform: translateY(-50%);
1345 width: 12px; height: 12px;
1346 border: 1.5px solid var(--text-faint);
1347 border-radius: 50%;
1348 pointer-events: none;
1349 }
1350 .nav-search::after {
1351 content: '';
1352 position: absolute;
1353 left: 19px; top: calc(50% + 4px);
1354 width: 5px; height: 1.5px;
1355 background: var(--text-faint);
1356 transform: rotate(45deg);
1357 pointer-events: none;
2ce1d0bClaude1358 }
1359 .nav-search input::placeholder { color: var(--text-faint); }
1360 .nav-search input:focus {
1361 outline: none;
1362 background: var(--bg-secondary);
958d26aClaude1363 border-color: var(--border-focus);
1364 box-shadow: var(--ring);
2ce1d0bClaude1365 }
06d5ffeClaude1366
958d26aClaude1367 .nav-right { display: flex; align-items: center; gap: 2px; margin-left: auto; }
f764c07Claude1368
1369 /* Block N3 — site-admin deploy status pill */
1370 .nav-deploy-pill {
1371 display: inline-flex;
1372 align-items: center;
1373 gap: 6px;
1374 padding: 4px 10px;
1375 margin: 0 6px 0 0;
1376 border-radius: 9999px;
1377 font-size: 11px;
1378 font-weight: 600;
1379 line-height: 1;
1380 color: var(--text-strong);
1381 background: var(--bg-elevated);
1382 border: 1px solid var(--border);
1383 text-decoration: none;
1384 transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
1385 }
1386 .nav-deploy-pill:hover { background: var(--bg-hover); text-decoration: none; }
1387 .nav-deploy-pill .deploy-pill-dot {
1388 display: inline-block;
1389 width: 8px; height: 8px;
1390 border-radius: 50%;
1391 background: #6b7280;
1392 }
1393 .deploy-pill-success .deploy-pill-dot { background: #34d399; box-shadow: 0 0 6px rgba(52,211,153,0.45); }
1394 .deploy-pill-failed .deploy-pill-dot { background: #f87171; box-shadow: 0 0 6px rgba(248,113,113,0.45); }
1395 .deploy-pill-failed { border-color: rgba(248,113,113,0.4); }
1396 .deploy-pill-progress .deploy-pill-dot {
1397 background: #fbbf24;
1398 box-shadow: 0 0 6px rgba(251,191,36,0.55);
1399 animation: deployPillPulse 1.2s ease-in-out infinite;
1400 }
1401 @keyframes deployPillPulse {
1402 0%, 100% { opacity: 1; transform: scale(1); }
1403 50% { opacity: 0.45; transform: scale(0.8); }
1404 }
1405 .deploy-pill-empty .deploy-pill-dot { background: #9ca3af; }
1406
2ce1d0bClaude1407 .nav-link {
958d26aClaude1408 position: relative;
2ce1d0bClaude1409 color: var(--text-muted);
1410 font-size: var(--t-sm);
1411 font-weight: 500;
958d26aClaude1412 padding: 7px 11px;
2ce1d0bClaude1413 border-radius: var(--r-sm);
958d26aClaude1414 transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
2ce1d0bClaude1415 }
1416 .nav-link:hover {
958d26aClaude1417 color: var(--text-strong);
2ce1d0bClaude1418 background: var(--bg-hover);
1419 text-decoration: none;
1420 }
958d26aClaude1421 .nav-link.active { color: var(--text-strong); }
1422 .nav-link.active::after {
1423 content: '';
1424 position: absolute;
1425 left: 11px; right: 11px;
1426 bottom: -1px;
1427 height: 2px;
1428 background: var(--accent-gradient);
1429 border-radius: 2px;
1430 }
2ce1d0bClaude1431 .nav-user {
958d26aClaude1432 color: var(--text-strong);
2ce1d0bClaude1433 font-weight: 600;
1434 font-size: var(--t-sm);
1435 padding: 6px 10px;
1436 border-radius: var(--r-sm);
958d26aClaude1437 margin-left: 6px;
2ce1d0bClaude1438 transition: background var(--t-fast) var(--ease);
1439 }
958d26aClaude1440 .nav-user::before {
1441 content: '';
1442 display: inline-block;
1443 width: 8px; height: 8px;
1444 background: var(--green);
1445 border-radius: 50%;
1446 margin-right: 7px;
1447 box-shadow: 0 0 8px rgba(52,211,153,0.5);
1448 vertical-align: 1px;
1449 }
2ce1d0bClaude1450 .nav-user:hover { background: var(--bg-hover); text-decoration: none; }
fc1817aClaude1451
2ce1d0bClaude1452 main {
958d26aClaude1453 max-width: 1240px;
2ce1d0bClaude1454 margin: 0 auto;
958d26aClaude1455 padding: 36px 24px 80px;
2ce1d0bClaude1456 flex: 1;
1457 width: 100%;
1458 }
fc1817aClaude1459
958d26aClaude1460 /* Editorial footer: link grid + tagline row */
fc1817aClaude1461 footer {
1462 border-top: 1px solid var(--border);
958d26aClaude1463 padding: 56px 24px 40px;
fc1817aClaude1464 color: var(--text-muted);
958d26aClaude1465 font-size: var(--t-sm);
1466 background:
1467 linear-gradient(180deg, transparent 0%, rgba(140,109,255,0.025) 100%),
1468 var(--bg);
1469 }
1470 footer .footer-inner {
1471 max-width: 1240px;
1472 margin: 0 auto;
1473 display: grid;
1474 grid-template-columns: 1fr auto;
1475 gap: 48px;
1476 align-items: start;
1477 }
1478 footer .footer-brand .logo { margin-bottom: var(--s-3); }
1479 footer .footer-tag {
1480 color: var(--text-muted);
1481 font-size: var(--t-sm);
1482 max-width: 320px;
1483 line-height: 1.55;
1484 }
1485 footer .footer-links {
1486 display: grid;
1487 grid-template-columns: repeat(3, minmax(120px, auto));
1488 gap: 0 56px;
1489 }
1490 footer .footer-col-title {
1491 font-family: var(--font-mono);
1492 font-size: 11px;
1493 text-transform: uppercase;
1494 letter-spacing: 0.14em;
1495 color: var(--text-faint);
1496 margin-bottom: var(--s-3);
1497 }
1498 footer .footer-col a {
1499 display: block;
1500 color: var(--text-muted);
1501 font-size: var(--t-sm);
1502 padding: 5px 0;
1503 transition: color var(--t-fast) var(--ease);
1504 }
1505 footer .footer-col a:hover { color: var(--text-strong); text-decoration: none; }
1506 footer .footer-bottom {
1507 max-width: 1240px;
1508 margin: 40px auto 0;
1509 padding-top: 24px;
1510 border-top: 1px solid var(--border-subtle);
1511 display: flex;
1512 justify-content: space-between;
1513 align-items: center;
2ce1d0bClaude1514 color: var(--text-faint);
1515 font-size: var(--t-xs);
958d26aClaude1516 font-family: var(--font-mono);
1517 letter-spacing: 0.02em;
1518 }
1519 footer .footer-bottom a { color: var(--text-faint); }
1520 footer .footer-bottom a:hover { color: var(--text-muted); text-decoration: none; }
05cdb85Claude1521 footer .footer-build {
1522 display: inline-flex;
1523 align-items: center;
1524 gap: 6px;
1525 color: var(--text-faint);
1526 font-family: var(--font-mono);
1527 font-size: 11px;
1528 cursor: help;
1529 }
1530 footer .footer-build-dot {
1531 width: 6px; height: 6px;
1532 border-radius: 50%;
1533 background: var(--green);
1534 box-shadow: 0 0 6px rgba(52,211,153,0.55);
1535 animation: footer-build-pulse 2.4s ease-in-out infinite;
1536 }
1537 @keyframes footer-build-pulse {
1538 0%, 100% { opacity: 0.6; }
1539 50% { opacity: 1; }
1540 }
958d26aClaude1541 @media (max-width: 768px) {
1542 footer .footer-inner { grid-template-columns: 1fr; gap: 32px; }
1543 footer .footer-links { grid-template-columns: repeat(2, 1fr); gap: 24px 32px; }
1544 footer .footer-bottom { flex-direction: column; gap: 8px; text-align: center; }
fc1817aClaude1545 }
1546
2ce1d0bClaude1547 /* ============================================================ */
1548 /* Buttons */
1549 /* ============================================================ */
dc26881CC LABS App1550 /* ============================================================ */
1551 /* Buttons — Block U2 senior polish pass. */
1552 /* Rules: */
1553 /* · hover lifts every .btn by 1px + soft drop shadow (180ms) */
1554 /* · active presses back down to 0 (80ms — faster on press) */
1555 /* · focus-visible uses a soft box-shadow ring (no outline) */
1556 /* · primary gradient shifts position on hover for a slow */
1557 /* 600ms shimmer */
1558 /* · disabled never lifts and never animates */
1559 /* ============================================================ */
06d5ffeClaude1560 .btn {
1561 display: inline-flex;
1562 align-items: center;
2ce1d0bClaude1563 justify-content: center;
958d26aClaude1564 gap: 7px;
2ce1d0bClaude1565 padding: 7px 14px;
1566 border-radius: var(--r-sm);
958d26aClaude1567 font-family: var(--font-sans);
2ce1d0bClaude1568 font-size: var(--t-sm);
06d5ffeClaude1569 font-weight: 500;
1570 border: 1px solid var(--border);
2ce1d0bClaude1571 background: var(--bg-elevated);
06d5ffeClaude1572 color: var(--text);
1573 cursor: pointer;
1574 text-decoration: none;
958d26aClaude1575 line-height: 1.25;
1576 letter-spacing: -0.008em;
dc26881CC LABS App1577 /* U2 — hover/transform settles in 180ms; press snaps in 80ms.
1578 Listed once on the base rule so :active can override only the
1579 transform/duration without re-typing the colour/bg transitions. */
2ce1d0bClaude1580 transition:
dc26881CC LABS App1581 background 180ms ease,
1582 border-color 180ms ease,
1583 transform 180ms ease,
1584 box-shadow 180ms ease,
1585 color 180ms ease;
2ce1d0bClaude1586 user-select: none;
1587 white-space: nowrap;
958d26aClaude1588 position: relative;
06d5ffeClaude1589 }
2ce1d0bClaude1590 .btn:hover {
1591 background: var(--bg-surface);
1592 border-color: var(--border-strong);
958d26aClaude1593 color: var(--text-strong);
2ce1d0bClaude1594 text-decoration: none;
dc26881CC LABS App1595 /* U2 — universal hover lift + soft accent drop shadow. */
1596 transform: translateY(-1px);
1597 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.45);
1598 }
1599 .btn:active {
1600 transform: translateY(0);
1601 transition-duration: 80ms;
1602 }
1603 /* U2 — soft modern focus ring via box-shadow, not outline. */
1604 .btn:focus-visible {
1605 outline: none;
1606 box-shadow: 0 0 0 3px rgba(140, 109, 255, 0.35);
2ce1d0bClaude1607 }
1608
1609 .btn-primary {
1610 background: var(--accent-gradient);
dc26881CC LABS App1611 background-size: 200% 100%;
1612 background-position: 0% 50%;
2ce1d0bClaude1613 border-color: transparent;
1614 color: #fff;
1615 font-weight: 600;
958d26aClaude1616 text-shadow: 0 1px 0 rgba(0,0,0,0.15);
2ce1d0bClaude1617 box-shadow:
958d26aClaude1618 inset 0 1px 0 rgba(255,255,255,0.22),
1619 inset 0 -1px 0 rgba(0,0,0,0.10),
1620 0 1px 2px rgba(0,0,0,0.40),
1621 0 0 0 1px rgba(140,109,255,0.30);
dc26881CC LABS App1622 /* U2 — slower 600ms transition on background-position so the
1623 primary CTA shimmers when the cursor lands. */
1624 transition:
1625 background-position 600ms ease,
1626 transform 180ms ease,
1627 box-shadow 180ms ease,
1628 color 180ms ease;
06d5ffeClaude1629 }
958d26aClaude1630 .btn-primary::before {
1631 content: '';
1632 position: absolute;
1633 inset: 0;
1634 border-radius: inherit;
1635 background: linear-gradient(180deg, rgba(255,255,255,0.18), transparent 60%);
1636 opacity: 0;
1637 transition: opacity var(--t-fast) var(--ease);
1638 pointer-events: none;
2ce1d0bClaude1639 }
1640 .btn-primary:hover {
958d26aClaude1641 color: #fff;
2ce1d0bClaude1642 background: var(--accent-gradient);
dc26881CC LABS App1643 background-size: 200% 100%;
1644 background-position: 100% 50%;
958d26aClaude1645 border-color: transparent;
dc26881CC LABS App1646 transform: translateY(-1px);
2ce1d0bClaude1647 box-shadow:
958d26aClaude1648 inset 0 1px 0 rgba(255,255,255,0.30),
1649 inset 0 -1px 0 rgba(0,0,0,0.10),
1650 0 6px 18px -4px rgba(140,109,255,0.45),
1651 0 0 0 1px rgba(140,109,255,0.45);
2ce1d0bClaude1652 }
958d26aClaude1653 .btn-primary:hover::before { opacity: 1; }
dc26881CC LABS App1654 .btn-primary:active { transform: translateY(0); transition-duration: 80ms; }
1655 /* U2 — primary focus ring uses the same accent box-shadow recipe. */
1656 .btn-primary:focus-visible {
1657 box-shadow:
1658 inset 0 1px 0 rgba(255,255,255,0.22),
1659 0 0 0 3px rgba(140,109,255,0.35);
1660 }
2ce1d0bClaude1661
1662 .btn-danger {
1663 background: transparent;
958d26aClaude1664 border-color: rgba(248,113,113,0.40);
2ce1d0bClaude1665 color: var(--red);
1666 }
1667 .btn-danger:hover {
958d26aClaude1668 background: rgba(248,113,113,0.08);
2ce1d0bClaude1669 border-color: var(--red);
958d26aClaude1670 color: var(--red);
dc26881CC LABS App1671 transform: translateY(-1px);
1672 box-shadow: 0 4px 12px -4px rgba(248,113,113,0.30);
2ce1d0bClaude1673 }
dc26881CC LABS App1674 .btn-danger:focus-visible { box-shadow: 0 0 0 3px rgba(248,113,113,0.35); }
2ce1d0bClaude1675
1676 .btn-ghost {
1677 background: transparent;
1678 border-color: transparent;
1679 color: var(--text-muted);
1680 }
1681 .btn-ghost:hover {
1682 background: var(--bg-hover);
958d26aClaude1683 color: var(--text-strong);
2ce1d0bClaude1684 border-color: var(--border);
dc26881CC LABS App1685 transform: translateY(-1px);
1686 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.30);
2ce1d0bClaude1687 }
1688
958d26aClaude1689 .btn-secondary {
1690 background: var(--bg-elevated);
1691 border-color: var(--border-strong);
1692 color: var(--text-strong);
1693 }
1694 .btn-secondary:hover {
1695 background: var(--bg-surface);
1696 border-color: var(--border-strong);
dc26881CC LABS App1697 transform: translateY(-1px);
1698 box-shadow: 0 4px 12px -4px rgba(15, 17, 26, 0.35);
958d26aClaude1699 }
1700
1701 .btn-sm { padding: 4px 10px; font-size: var(--t-xs); border-radius: var(--r-sm); gap: 5px; }
1702 .btn-lg { padding: 11px 22px; font-size: var(--t-base); border-radius: var(--r); }
1703 .btn-xl { padding: 14px 28px; font-size: var(--t-md); border-radius: var(--r); font-weight: 600; }
1704 .btn-block { width: 100%; }
2ce1d0bClaude1705
dc26881CC LABS App1706 /* U2 — disabled never lifts, never shimmers, never glows. */
1707 .btn:disabled,
1708 .btn[aria-disabled='true'],
1709 .btn:disabled:hover,
1710 .btn[aria-disabled='true']:hover {
1711 opacity: 0.5;
2ce1d0bClaude1712 cursor: not-allowed;
1713 pointer-events: none;
dc26881CC LABS App1714 transform: none;
1715 box-shadow: none;
1716 }
1717 @media (prefers-reduced-motion: reduce) {
1718 .btn,
1719 .btn:hover,
1720 .btn:active,
1721 .btn-primary,
1722 .btn-primary:hover {
1723 transform: none;
1724 transition: background-color 80ms linear, color 80ms linear;
1725 }
2ce1d0bClaude1726 }
1727
1728 /* ============================================================ */
1729 /* Forms */
1730 /* ============================================================ */
1731 .form-group { margin-bottom: 20px; }
1732 .form-group label {
1733 display: block;
1734 font-size: var(--t-sm);
1735 font-weight: 500;
1736 margin-bottom: 6px;
1737 color: var(--text);
1738 letter-spacing: -0.005em;
1739 }
1740 .form-group input,
1741 .form-group textarea,
1742 .form-group select,
1743 input[type='text'], input[type='email'], input[type='password'],
1744 input[type='url'], input[type='search'], input[type='number'],
1745 textarea, select {
06d5ffeClaude1746 width: 100%;
2ce1d0bClaude1747 padding: 9px 12px;
1748 background: var(--bg-secondary);
06d5ffeClaude1749 border: 1px solid var(--border);
2ce1d0bClaude1750 border-radius: var(--r-sm);
06d5ffeClaude1751 color: var(--text);
2ce1d0bClaude1752 font-size: var(--t-sm);
06d5ffeClaude1753 font-family: var(--font-sans);
2ce1d0bClaude1754 transition:
1755 border-color var(--t-fast) var(--ease),
1756 background var(--t-fast) var(--ease),
1757 box-shadow var(--t-fast) var(--ease);
06d5ffeClaude1758 }
2ce1d0bClaude1759 .form-group input::placeholder, textarea::placeholder, input::placeholder {
1760 color: var(--text-faint);
06d5ffeClaude1761 }
2ce1d0bClaude1762 .form-group input:hover, textarea:hover, select:hover,
1763 input[type='text']:hover, input[type='email']:hover, input[type='password']:hover {
1764 border-color: var(--border-strong);
1765 }
1766 .form-group input:focus, .form-group textarea:focus, .form-group select:focus,
1767 input:focus, textarea:focus, select:focus {
06d5ffeClaude1768 outline: none;
2ce1d0bClaude1769 background: var(--bg);
1770 border-color: var(--border-focus);
1771 box-shadow: var(--ring);
06d5ffeClaude1772 }
2ce1d0bClaude1773 textarea { font-family: var(--font-mono); font-size: var(--t-sm); line-height: 1.55; }
06d5ffeClaude1774 .input-disabled { opacity: 0.5; cursor: not-allowed; }
1775
2ce1d0bClaude1776 /* ============================================================ */
1777 /* Auth (register / login / verify) */
1778 /* ============================================================ */
1779 .auth-container {
98f45b4Claude1780 /* 2026 polish — wider, more generous, with a subtle accent-glow
1781 border and gradient top-edge so it reads as a premium product
1782 gateway, not a stock form. The 480px width feels intentional
1783 (not cramped, not endless). */
1784 max-width: 480px;
1785 margin: 72px auto;
1786 padding: 40px 40px 36px;
2ce1d0bClaude1787 background: var(--bg-elevated);
1788 border: 1px solid var(--border);
98f45b4Claude1789 border-radius: 16px;
1790 box-shadow:
1791 var(--elev-2),
1792 0 24px 64px -16px rgba(140, 109, 255, 0.12);
1793 position: relative;
1794 overflow: hidden;
1795 }
1796 .auth-container::before {
1797 /* Hairline gradient accent on the top edge — signals 'AI-native'
1798 without shouting. Pointer-events disabled so it never interferes
1799 with form interactions. */
1800 content: '';
1801 position: absolute;
1802 top: 0;
1803 left: 0;
1804 right: 0;
1805 height: 2px;
1806 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
1807 opacity: 0.7;
1808 pointer-events: none;
2ce1d0bClaude1809 }
1810 .auth-container h2 {
98f45b4Claude1811 margin: 0 0 8px;
1812 font-size: 28px;
1813 font-weight: 700;
1814 font-family: var(--font-display);
1815 letter-spacing: -0.025em;
1816 color: var(--text-strong);
1817 line-height: 1.15;
1818 }
1819 .auth-container .auth-subtitle {
1820 color: var(--text-muted);
1821 font-size: 14.5px;
1822 line-height: 1.5;
1823 margin: 0 0 24px;
2ce1d0bClaude1824 }
1825 .auth-container > p {
1826 color: var(--text-muted);
1827 font-size: var(--t-sm);
1828 margin-bottom: 24px;
1829 }
98f45b4Claude1830 .auth-container .btn-primary {
1831 width: 100%;
1832 padding: 12px 16px;
1833 font-size: 15px;
1834 font-weight: 600;
1835 margin-top: 4px;
1836 }
06d5ffeClaude1837 .auth-error {
958d26aClaude1838 background: rgba(248,113,113,0.08);
1839 border: 1px solid rgba(248,113,113,0.35);
06d5ffeClaude1840 color: var(--red);
2ce1d0bClaude1841 padding: 10px 14px;
1842 border-radius: var(--r-sm);
06d5ffeClaude1843 margin-bottom: 16px;
2ce1d0bClaude1844 font-size: var(--t-sm);
06d5ffeClaude1845 }
1846 .auth-success {
958d26aClaude1847 background: rgba(52,211,153,0.08);
1848 border: 1px solid rgba(52,211,153,0.35);
06d5ffeClaude1849 color: var(--green);
2ce1d0bClaude1850 padding: 10px 14px;
1851 border-radius: var(--r-sm);
06d5ffeClaude1852 margin-bottom: 16px;
2ce1d0bClaude1853 font-size: var(--t-sm);
1854 }
1855 .auth-switch {
1856 margin-top: 20px;
1857 font-size: var(--t-sm);
1858 color: var(--text-muted);
1859 text-align: center;
1860 }
1861 .banner {
1862 background: var(--accent-gradient-faint);
958d26aClaude1863 border: 1px solid rgba(140,109,255,0.35);
2ce1d0bClaude1864 color: var(--text);
1865 padding: 10px 14px;
1866 border-radius: var(--r-sm);
1867 font-size: var(--t-sm);
06d5ffeClaude1868 }
1869
2ce1d0bClaude1870 /* ============================================================ */
1871 /* Settings */
1872 /* ============================================================ */
1873 .settings-container { max-width: 720px; }
1874 .settings-container h2 { margin-bottom: 8px; font-size: var(--t-xl); letter-spacing: -0.02em; }
1875 .settings-container > h2 + p { color: var(--text-muted); font-size: var(--t-sm); margin-bottom: 24px; }
1876 .settings-container h3 { font-size: var(--t-md); margin-bottom: 12px; margin-top: 28px; }
1877 .settings-container h3:first-of-type { margin-top: 0; }
06d5ffeClaude1878 .ssh-keys-list { margin-bottom: 24px; }
1879 .ssh-key-item {
1880 display: flex;
1881 justify-content: space-between;
1882 align-items: center;
2ce1d0bClaude1883 padding: 14px 16px;
06d5ffeClaude1884 border: 1px solid var(--border);
2ce1d0bClaude1885 border-radius: var(--r-md);
06d5ffeClaude1886 margin-bottom: 8px;
2ce1d0bClaude1887 background: var(--bg-elevated);
1888 transition: border-color var(--t-fast) var(--ease);
06d5ffeClaude1889 }
2ce1d0bClaude1890 .ssh-key-item:hover { border-color: var(--border-strong); }
1891 .ssh-key-meta { font-size: var(--t-xs); color: var(--text-muted); margin-top: 4px; }
1892 .ssh-key-meta code { font-size: var(--t-xs); background: var(--bg-tertiary); padding: 1px 6px; border-radius: 3px; margin-right: 8px; }
06d5ffeClaude1893
2ce1d0bClaude1894 /* ============================================================ */
1895 /* Repo header + nav */
1896 /* ============================================================ */
fc1817aClaude1897 .repo-header {
1898 display: flex;
1899 align-items: center;
debcf27Claude1900 gap: 12px;
1901 margin-bottom: 22px;
1902 }
1903 .repo-header-title {
1904 display: flex;
1905 align-items: center;
1906 gap: 10px;
1907 font-family: var(--font-display);
1908 font-size: 24px;
1909 letter-spacing: -0.025em;
1910 flex-wrap: wrap;
1911 }
1912 .repo-header .owner {
1913 color: var(--text-muted);
1914 font-weight: 500;
1915 transition: color var(--t-fast) var(--ease);
1916 }
1917 .repo-header .owner:hover { color: var(--text-link); text-decoration: none; }
1918 .repo-header .separator { color: var(--text-faint); font-weight: 300; }
1919 .repo-header .name {
1920 color: var(--text-strong);
1921 font-weight: 700;
1922 letter-spacing: -0.028em;
fc1817aClaude1923 }
2ce1d0bClaude1924 .repo-header .name:hover { color: var(--text-link); text-decoration: none; }
debcf27Claude1925 .repo-header-fork {
1926 font-family: var(--font-mono);
1927 font-size: 11px;
1928 color: var(--text-muted);
1929 margin-top: 4px;
1930 letter-spacing: 0.01em;
1931 }
1932 .repo-header-fork a { color: var(--text-muted); }
1933 .repo-header-fork a:hover { color: var(--accent); }
1934 .repo-header-pill {
1935 display: inline-flex;
1936 align-items: center;
1937 padding: 2px 10px;
1938 border-radius: var(--r-full);
1939 font-family: var(--font-mono);
1940 font-size: 10px;
1941 font-weight: 600;
1942 letter-spacing: 0.12em;
1943 text-transform: uppercase;
1944 line-height: 1.6;
1945 vertical-align: 4px;
1946 }
1947 .repo-header-pill-archived {
1948 background: rgba(251,191,36,0.10);
1949 color: var(--yellow);
1950 border: 1px solid rgba(251,191,36,0.30);
1951 }
1952 .repo-header-pill-template {
1953 background: var(--accent-gradient-faint);
1954 color: var(--accent);
1955 border: 1px solid rgba(140,109,255,0.30);
fc1817aClaude1956 }
06d5ffeClaude1957 .repo-header-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
fc1817aClaude1958
1959 .repo-nav {
1960 display: flex;
debcf27Claude1961 gap: 1px;
fc1817aClaude1962 border-bottom: 1px solid var(--border);
debcf27Claude1963 margin-bottom: 28px;
2ce1d0bClaude1964 overflow-x: auto;
1965 scrollbar-width: thin;
fc1817aClaude1966 }
2ce1d0bClaude1967 .repo-nav::-webkit-scrollbar { height: 0; }
fc1817aClaude1968 .repo-nav a {
debcf27Claude1969 position: relative;
1970 padding: 11px 14px;
fc1817aClaude1971 color: var(--text-muted);
1972 border-bottom: 2px solid transparent;
2ce1d0bClaude1973 font-size: var(--t-sm);
1974 font-weight: 500;
1975 margin-bottom: -1px;
debcf27Claude1976 transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
2ce1d0bClaude1977 white-space: nowrap;
1978 }
debcf27Claude1979 .repo-nav a:hover { text-decoration: none; color: var(--text-strong); background: var(--bg-hover); }
2ce1d0bClaude1980 .repo-nav a.active {
debcf27Claude1981 color: var(--text-strong);
1982 font-weight: 600;
1983 }
1984 .repo-nav a.active::after {
1985 content: '';
1986 position: absolute;
1987 left: 14px;
1988 right: 14px;
1989 bottom: -1px;
1990 height: 2px;
1991 background: var(--accent-gradient);
1992 border-radius: 2px;
1993 }
1994 /* AI links in the right-side cluster — sparkle accent */
1995 .repo-nav-ai {
1996 color: var(--accent) !important;
1997 font-weight: 500;
1998 }
1999 .repo-nav-ai:hover {
2000 color: var(--accent-hover) !important;
2001 background: var(--accent-gradient-faint) !important;
2002 }
2003 .repo-nav-ai.active {
2004 color: var(--accent-hover) !important;
2ce1d0bClaude2005 font-weight: 600;
fc1817aClaude2006 }
2007
2ce1d0bClaude2008 .breadcrumb {
2009 display: flex;
debcf27Claude2010 gap: 6px;
2ce1d0bClaude2011 align-items: center;
debcf27Claude2012 margin-bottom: 18px;
2ce1d0bClaude2013 color: var(--text-muted);
2014 font-size: var(--t-sm);
2015 font-family: var(--font-mono);
debcf27Claude2016 font-feature-settings: var(--mono-feat);
2ce1d0bClaude2017 }
2018 .breadcrumb a { color: var(--text-link); font-weight: 500; }
2019 .breadcrumb a:hover { color: var(--accent-hover); }
debcf27Claude2020 .breadcrumb strong {
2021 color: var(--text-strong);
2022 font-weight: 600;
fc1817aClaude2023 }
2024
debcf27Claude2025 /* Page header — eyebrow + title + optional actions row.
2026 Use on dashboard, settings, admin, any "section landing" page. */
2027 .page-header {
2028 display: flex;
2029 align-items: flex-end;
2030 justify-content: space-between;
2031 gap: 16px;
2032 margin-bottom: 28px;
2033 padding-bottom: 20px;
2034 border-bottom: 1px solid var(--border-subtle);
2035 flex-wrap: wrap;
2036 }
2037 .page-header-text { flex: 1; min-width: 280px; }
2038 .page-header .eyebrow { margin-bottom: var(--s-2); }
2039 .page-header h1 {
2040 font-family: var(--font-display);
2041 font-size: clamp(24px, 3vw, 36px);
2042 line-height: 1.1;
2043 letter-spacing: -0.028em;
2044 margin-bottom: 6px;
2045 }
2046 .page-header p {
2047 color: var(--text-muted);
2048 font-size: var(--t-sm);
2049 line-height: 1.55;
2050 max-width: 640px;
2051 }
2052 .page-header-actions { display: flex; gap: 8px; align-items: center; }
fc1817aClaude2053
2ce1d0bClaude2054 /* ============================================================ */
2055 /* File browser table */
2056 /* ============================================================ */
2057 .file-table {
2058 width: 100%;
2059 border: 1px solid var(--border);
2060 border-radius: var(--r-md);
2061 overflow: hidden;
2062 background: var(--bg-elevated);
debcf27Claude2063 border-collapse: collapse;
2ce1d0bClaude2064 }
debcf27Claude2065 .file-table tr { border-bottom: 1px solid var(--border-subtle); transition: background var(--t-fast) var(--ease); }
fc1817aClaude2066 .file-table tr:last-child { border-bottom: none; }
debcf27Claude2067 .file-table td {
2068 padding: 9px 16px;
2069 font-size: var(--t-sm);
2070 font-family: var(--font-mono);
2071 font-feature-settings: var(--mono-feat);
2072 }
2ce1d0bClaude2073 .file-table tr:hover { background: var(--bg-hover); }
debcf27Claude2074 .file-icon {
2075 width: 22px;
2076 color: var(--text-faint);
2077 font-size: 13px;
2078 text-align: center;
2079 }
2080 .file-name a {
2081 color: var(--text);
2082 font-weight: 500;
2083 transition: color var(--t-fast) var(--ease);
2084 }
2085 .file-name a:hover { color: var(--accent); text-decoration: none; }
fc1817aClaude2086
2ce1d0bClaude2087 /* ============================================================ */
2088 /* Blob view */
2089 /* ============================================================ */
fc1817aClaude2090 .blob-view {
2091 border: 1px solid var(--border);
2ce1d0bClaude2092 border-radius: var(--r-md);
fc1817aClaude2093 overflow: hidden;
2ce1d0bClaude2094 background: var(--bg-elevated);
fc1817aClaude2095 }
2096 .blob-header {
2097 background: var(--bg-secondary);
2ce1d0bClaude2098 padding: 10px 16px;
fc1817aClaude2099 border-bottom: 1px solid var(--border);
2ce1d0bClaude2100 font-size: var(--t-sm);
fc1817aClaude2101 color: var(--text-muted);
06d5ffeClaude2102 display: flex;
2103 justify-content: space-between;
2104 align-items: center;
fc1817aClaude2105 }
2106 .blob-code {
2107 overflow-x: auto;
2108 font-family: var(--font-mono);
2ce1d0bClaude2109 font-size: var(--t-sm);
2110 line-height: 1.65;
fc1817aClaude2111 }
2112 .blob-code table { width: 100%; border-collapse: collapse; }
2113 .blob-code .line-num {
2114 width: 1%;
2ce1d0bClaude2115 min-width: 56px;
2116 padding: 0 14px;
fc1817aClaude2117 text-align: right;
2ce1d0bClaude2118 color: var(--text-faint);
fc1817aClaude2119 user-select: none;
2120 white-space: nowrap;
2121 border-right: 1px solid var(--border);
2122 }
2ce1d0bClaude2123 .blob-code .line-content { padding: 0 16px; white-space: pre; }
2124 .blob-code tr:hover { background: var(--bg-hover); }
fc1817aClaude2125
2ce1d0bClaude2126 /* ============================================================ */
2127 /* Commits + diffs */
2128 /* ============================================================ */
2129 .commit-list {
2130 border: 1px solid var(--border);
2131 border-radius: var(--r-md);
2132 overflow: hidden;
2133 background: var(--bg-elevated);
2134 }
fc1817aClaude2135 .commit-item {
2136 display: flex;
2137 justify-content: space-between;
2138 align-items: center;
debcf27Claude2139 padding: 14px 18px;
2140 border-bottom: 1px solid var(--border-subtle);
2ce1d0bClaude2141 transition: background var(--t-fast) var(--ease);
fc1817aClaude2142 }
2143 .commit-item:last-child { border-bottom: none; }
2ce1d0bClaude2144 .commit-item:hover { background: var(--bg-hover); }
debcf27Claude2145 .commit-message {
2146 font-size: var(--t-sm);
2147 font-weight: 500;
2148 line-height: 1.45;
2149 color: var(--text-strong);
2150 letter-spacing: -0.005em;
2151 }
2152 .commit-meta {
2153 font-family: var(--font-mono);
2154 font-size: 11px;
2155 color: var(--text-muted);
2156 margin-top: 5px;
2157 letter-spacing: 0.01em;
2158 }
fc1817aClaude2159 .commit-sha {
2160 font-family: var(--font-mono);
debcf27Claude2161 font-feature-settings: var(--mono-feat);
2162 font-size: 11px;
2163 padding: 4px 9px;
fc1817aClaude2164 background: var(--bg-tertiary);
2165 border: 1px solid var(--border);
2ce1d0bClaude2166 border-radius: var(--r-sm);
debcf27Claude2167 color: var(--accent);
2168 font-weight: 600;
2169 letter-spacing: 0.02em;
2ce1d0bClaude2170 transition: all var(--t-fast) var(--ease);
fc1817aClaude2171 }
debcf27Claude2172 .commit-sha:hover {
2173 border-color: rgba(140,109,255,0.40);
2174 background: var(--accent-gradient-faint);
2175 color: var(--accent-hover);
2176 text-decoration: none;
fc1817aClaude2177 }
2178
2179 .diff-view { margin-top: 16px; }
2180 .diff-file {
2181 border: 1px solid var(--border);
2ce1d0bClaude2182 border-radius: var(--r-md);
fc1817aClaude2183 margin-bottom: 16px;
2184 overflow: hidden;
2ce1d0bClaude2185 background: var(--bg-elevated);
fc1817aClaude2186 }
2187 .diff-file-header {
2188 background: var(--bg-secondary);
2ce1d0bClaude2189 padding: 10px 16px;
fc1817aClaude2190 border-bottom: 1px solid var(--border);
2191 font-family: var(--font-mono);
2ce1d0bClaude2192 font-size: var(--t-sm);
2193 font-weight: 500;
fc1817aClaude2194 }
2195 .diff-content {
2196 overflow-x: auto;
2197 font-family: var(--font-mono);
2ce1d0bClaude2198 font-size: var(--t-sm);
2199 line-height: 1.65;
fc1817aClaude2200 }
958d26aClaude2201 .diff-content .line-add { background: rgba(52,211,153,0.10); color: var(--green); }
2202 .diff-content .line-del { background: rgba(248,113,113,0.08); color: var(--red); }
2203 .diff-content .line-hunk { background: rgba(140,109,255,0.06); color: var(--text-link); }
2ce1d0bClaude2204 .diff-content .line { padding: 0 16px; white-space: pre; display: block; }
fc1817aClaude2205
2206 .stat-add { color: var(--green); font-weight: 600; }
2207 .stat-del { color: var(--red); font-weight: 600; }
2208
2ce1d0bClaude2209 /* ============================================================ */
2210 /* Empty state */
2211 /* ============================================================ */
fc1817aClaude2212 .empty-state {
2213 text-align: center;
958d26aClaude2214 padding: 96px 24px;
fc1817aClaude2215 color: var(--text-muted);
2ce1d0bClaude2216 border: 1px dashed var(--border);
2217 border-radius: var(--r-lg);
958d26aClaude2218 background:
2219 radial-gradient(60% 60% at 50% 0%, rgba(140,109,255,0.05), transparent 70%),
2220 var(--bg-elevated);
2221 position: relative;
2222 overflow: hidden;
2223 }
2224 .empty-state::before {
2225 content: '';
2226 position: absolute;
2227 inset: 0;
2228 background-image: radial-gradient(rgba(255,255,255,0.025) 1px, transparent 1px);
2229 background-size: 24px 24px;
2230 opacity: 0.5;
2231 pointer-events: none;
2232 mask-image: radial-gradient(ellipse at center, #000 30%, transparent 70%);
2233 -webkit-mask-image: radial-gradient(ellipse at center, #000 30%, transparent 70%);
2234 }
2235 :root[data-theme='light'] .empty-state::before {
2236 background-image: radial-gradient(rgba(15,16,28,0.08) 1px, transparent 1px);
fc1817aClaude2237 }
958d26aClaude2238 .empty-state > * { position: relative; z-index: 1; }
2ce1d0bClaude2239 .empty-state h2 {
958d26aClaude2240 font-size: var(--t-xl);
2ce1d0bClaude2241 margin-bottom: 8px;
958d26aClaude2242 color: var(--text-strong);
2243 letter-spacing: -0.022em;
2ce1d0bClaude2244 }
958d26aClaude2245 .empty-state p { font-size: var(--t-md); max-width: 480px; margin: 0 auto; line-height: 1.55; }
fc1817aClaude2246 .empty-state pre {
2247 text-align: left;
2248 display: inline-block;
2249 background: var(--bg-secondary);
958d26aClaude2250 padding: 18px 24px;
2251 border-radius: var(--r-md);
fc1817aClaude2252 border: 1px solid var(--border);
2253 font-family: var(--font-mono);
958d26aClaude2254 font-feature-settings: var(--mono-feat);
2ce1d0bClaude2255 font-size: var(--t-sm);
958d26aClaude2256 margin-top: 24px;
fc1817aClaude2257 line-height: 1.8;
2ce1d0bClaude2258 color: var(--text);
958d26aClaude2259 box-shadow: var(--elev-1);
fc1817aClaude2260 }
2261
2ce1d0bClaude2262 /* ============================================================ */
2263 /* Badges */
2264 /* ============================================================ */
fc1817aClaude2265 .badge {
2ce1d0bClaude2266 display: inline-flex;
2267 align-items: center;
2268 gap: 4px;
2269 padding: 2px 9px;
2270 border-radius: var(--r-full);
2271 font-size: var(--t-xs);
fc1817aClaude2272 font-weight: 500;
2273 background: var(--bg-tertiary);
2274 border: 1px solid var(--border);
2275 color: var(--text-muted);
2ce1d0bClaude2276 line-height: 1.5;
fc1817aClaude2277 }
2278
2ce1d0bClaude2279 /* ============================================================ */
2280 /* Branch dropdown */
2281 /* ============================================================ */
fc1817aClaude2282 .branch-selector {
2283 display: inline-flex;
2284 align-items: center;
2ce1d0bClaude2285 gap: 6px;
2286 padding: 6px 12px;
2287 background: var(--bg-elevated);
fc1817aClaude2288 border: 1px solid var(--border);
2ce1d0bClaude2289 border-radius: var(--r-sm);
2290 font-size: var(--t-sm);
fc1817aClaude2291 color: var(--text);
2292 margin-bottom: 12px;
2ce1d0bClaude2293 transition: border-color var(--t-fast) var(--ease);
fc1817aClaude2294 }
2ce1d0bClaude2295 .branch-selector:hover { border-color: var(--border-strong); }
fc1817aClaude2296
06d5ffeClaude2297 .branch-dropdown {
2298 position: relative;
2299 display: inline-block;
2300 margin-bottom: 12px;
2301 }
2302 .branch-dropdown-content {
2303 display: none;
2304 position: absolute;
2ce1d0bClaude2305 top: calc(100% + 4px);
06d5ffeClaude2306 left: 0;
2307 z-index: 10;
2ce1d0bClaude2308 min-width: 220px;
2309 background: var(--bg-elevated);
2310 border: 1px solid var(--border-strong);
2311 border-radius: var(--r-md);
2312 overflow: hidden;
2313 box-shadow: var(--elev-3);
06d5ffeClaude2314 }
2315 .branch-dropdown:hover .branch-dropdown-content,
2316 .branch-dropdown:focus-within .branch-dropdown-content { display: block; }
2317 .branch-dropdown-content a {
2318 display: block;
2ce1d0bClaude2319 padding: 9px 14px;
2320 font-size: var(--t-sm);
06d5ffeClaude2321 color: var(--text);
2322 border-bottom: 1px solid var(--border);
2ce1d0bClaude2323 transition: background var(--t-fast) var(--ease);
06d5ffeClaude2324 }
2325 .branch-dropdown-content a:last-child { border-bottom: none; }
2ce1d0bClaude2326 .branch-dropdown-content a:hover { background: var(--bg-hover); text-decoration: none; }
2327 .branch-dropdown-content a.active-branch { color: var(--text-link); font-weight: 600; background: var(--accent-gradient-faint); }
06d5ffeClaude2328
2ce1d0bClaude2329 /* ============================================================ */
2330 /* Card grid */
2331 /* ============================================================ */
2332 .card-grid {
2333 display: grid;
2334 grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
2335 gap: 16px;
2336 }
fc1817aClaude2337 .card {
2338 border: 1px solid var(--border);
2ce1d0bClaude2339 border-radius: var(--r-md);
958d26aClaude2340 padding: 20px;
2ce1d0bClaude2341 background: var(--bg-elevated);
2342 transition:
958d26aClaude2343 border-color var(--t-base) var(--ease),
2344 transform var(--t-base) var(--ease-out-quart),
2ce1d0bClaude2345 box-shadow var(--t-base) var(--ease);
2346 position: relative;
2347 overflow: hidden;
958d26aClaude2348 isolation: isolate;
2349 }
2350 .card::before {
2351 content: '';
2352 position: absolute;
2353 inset: 0;
2354 background: linear-gradient(135deg, rgba(140,109,255,0.06), transparent 50%);
2355 opacity: 0;
2356 transition: opacity var(--t-base) var(--ease);
2357 pointer-events: none;
2358 z-index: -1;
2ce1d0bClaude2359 }
2360 .card:hover {
2361 border-color: var(--border-strong);
2362 box-shadow: var(--elev-2);
958d26aClaude2363 transform: translateY(-2px);
2ce1d0bClaude2364 }
958d26aClaude2365 .card:hover::before { opacity: 1; }
2366 .card h3 { font-size: var(--t-md); margin-bottom: 6px; letter-spacing: -0.012em; }
2ce1d0bClaude2367 .card h3 a { color: var(--text); font-weight: 600; }
2368 .card h3 a:hover { color: var(--text-link); }
2369 .card p { font-size: var(--t-sm); color: var(--text-muted); line-height: 1.5; }
2370 .card-meta {
2371 display: flex;
2372 gap: 14px;
2373 margin-top: 14px;
2374 padding-top: 14px;
2375 border-top: 1px solid var(--border);
2376 font-size: var(--t-xs);
2377 color: var(--text-muted);
2378 }
2379 .card-meta span { display: flex; align-items: center; gap: 5px; }
2380
2381 /* ============================================================ */
2382 /* Stat card / box */
2383 /* ============================================================ */
2384 .stat-card {
2385 background: var(--bg-elevated);
2386 border: 1px solid var(--border);
2387 border-radius: var(--r-md);
fc1817aClaude2388 padding: 16px;
2ce1d0bClaude2389 transition: border-color var(--t-fast) var(--ease);
2390 }
2391 .stat-card:hover { border-color: var(--border-strong); }
2392 .stat-label {
2393 font-size: var(--t-xs);
2394 color: var(--text-muted);
2395 text-transform: uppercase;
2396 letter-spacing: 0.06em;
2397 font-weight: 500;
2398 }
2399 .stat-value {
2400 font-size: var(--t-xl);
2401 font-weight: 700;
2402 margin-top: 4px;
2403 letter-spacing: -0.025em;
2404 color: var(--text);
2405 font-feature-settings: 'tnum';
fc1817aClaude2406 }
06d5ffeClaude2407
2ce1d0bClaude2408 /* ============================================================ */
2409 /* Star button */
2410 /* ============================================================ */
06d5ffeClaude2411 .star-btn {
2412 display: inline-flex;
2413 align-items: center;
2414 gap: 6px;
2ce1d0bClaude2415 padding: 5px 12px;
2416 background: var(--bg-elevated);
06d5ffeClaude2417 border: 1px solid var(--border);
2ce1d0bClaude2418 border-radius: var(--r-sm);
06d5ffeClaude2419 color: var(--text);
2ce1d0bClaude2420 font-size: var(--t-sm);
2421 font-weight: 500;
06d5ffeClaude2422 cursor: pointer;
2ce1d0bClaude2423 transition: all var(--t-fast) var(--ease);
2424 }
2425 .star-btn:hover { background: var(--bg-surface); border-color: var(--border-strong); text-decoration: none; }
2426 .star-btn.starred {
2427 color: var(--yellow);
958d26aClaude2428 border-color: rgba(251,191,36,0.4);
2429 background: rgba(251,191,36,0.08);
06d5ffeClaude2430 }
2431
2ce1d0bClaude2432 /* ============================================================ */
2433 /* User profile */
2434 /* ============================================================ */
06d5ffeClaude2435 .user-profile {
2436 display: flex;
2437 gap: 32px;
2438 margin-bottom: 32px;
2ce1d0bClaude2439 padding: 24px;
2440 background: var(--bg-elevated);
2441 border: 1px solid var(--border);
2442 border-radius: var(--r-lg);
06d5ffeClaude2443 }
2444 .user-avatar {
2445 width: 96px;
2446 height: 96px;
2ce1d0bClaude2447 border-radius: var(--r-full);
2448 background: var(--accent-gradient-soft);
2449 border: 1px solid var(--border-strong);
06d5ffeClaude2450 display: flex;
2451 align-items: center;
2452 justify-content: center;
2ce1d0bClaude2453 font-size: 36px;
2454 font-weight: 600;
2455 color: var(--text);
06d5ffeClaude2456 flex-shrink: 0;
2ce1d0bClaude2457 letter-spacing: -0.02em;
06d5ffeClaude2458 }
2ce1d0bClaude2459 .user-info h2 { font-size: var(--t-xl); margin-bottom: 2px; letter-spacing: -0.025em; }
2460 .user-info .username { font-size: var(--t-md); color: var(--text-muted); }
2461 .user-info .bio { font-size: var(--t-sm); color: var(--text-muted); margin-top: 10px; line-height: 1.55; }
06d5ffeClaude2462
2ce1d0bClaude2463 /* ============================================================ */
2464 /* New repo form */
2465 /* ============================================================ */
2466 .new-repo-form { max-width: 640px; }
2467 .new-repo-form h2 { margin-bottom: 24px; font-size: var(--t-xl); letter-spacing: -0.025em; }
2468 .visibility-options { display: flex; gap: 12px; margin-bottom: 20px; }
06d5ffeClaude2469 .visibility-option {
2470 flex: 1;
2ce1d0bClaude2471 padding: 16px;
06d5ffeClaude2472 border: 1px solid var(--border);
2ce1d0bClaude2473 border-radius: var(--r-md);
2474 background: var(--bg-elevated);
06d5ffeClaude2475 cursor: pointer;
2ce1d0bClaude2476 text-align: left;
2477 transition: all var(--t-fast) var(--ease);
2478 }
2479 .visibility-option:hover { border-color: var(--border-strong); background: var(--bg-surface); }
2480 .visibility-option:has(input:checked) {
2481 border-color: var(--accent);
2482 background: var(--accent-gradient-faint);
2483 box-shadow: 0 0 0 1px var(--accent);
06d5ffeClaude2484 }
2485 .visibility-option input { display: none; }
2ce1d0bClaude2486 .visibility-option .vis-label { font-size: var(--t-sm); font-weight: 600; margin-bottom: 4px; }
2487 .visibility-option .vis-desc { font-size: var(--t-xs); color: var(--text-muted); }
79136bbClaude2488
2ce1d0bClaude2489 /* ============================================================ */
2490 /* Issues */
2491 /* ============================================================ */
2492 .issue-tabs { display: flex; gap: 4px; }
2493 .issue-tabs a {
2494 color: var(--text-muted);
2495 font-size: var(--t-sm);
2496 font-weight: 500;
2497 padding: 6px 12px;
2498 border-radius: var(--r-sm);
2499 transition: all var(--t-fast) var(--ease);
2500 }
2501 .issue-tabs a:hover { color: var(--text); background: var(--bg-hover); text-decoration: none; }
2502 .issue-tabs a.active { color: var(--text); background: var(--bg-elevated); }
79136bbClaude2503
2ce1d0bClaude2504 .issue-list {
2505 border: 1px solid var(--border);
2506 border-radius: var(--r-md);
2507 overflow: hidden;
2508 background: var(--bg-elevated);
2509 }
79136bbClaude2510 .issue-item {
2511 display: flex;
2ce1d0bClaude2512 gap: 14px;
79136bbClaude2513 align-items: flex-start;
2ce1d0bClaude2514 padding: 14px 16px;
79136bbClaude2515 border-bottom: 1px solid var(--border);
2ce1d0bClaude2516 transition: background var(--t-fast) var(--ease);
79136bbClaude2517 }
2518 .issue-item:last-child { border-bottom: none; }
2ce1d0bClaude2519 .issue-item:hover { background: var(--bg-hover); }
2520 .issue-state-icon {
2521 font-size: 14px;
2522 padding-top: 2px;
2523 width: 18px;
2524 height: 18px;
2525 display: inline-flex;
2526 align-items: center;
2527 justify-content: center;
2528 border-radius: var(--r-full);
2529 flex-shrink: 0;
2530 }
79136bbClaude2531 .state-open { color: var(--green); }
958d26aClaude2532 .state-closed { color: #b69dff; }
2ce1d0bClaude2533 .issue-title {
debcf27Claude2534 font-family: var(--font-display);
2535 font-size: var(--t-md);
2ce1d0bClaude2536 font-weight: 600;
debcf27Claude2537 line-height: 1.35;
2538 letter-spacing: -0.012em;
2539 }
2540 .issue-title a { color: var(--text-strong); transition: color var(--t-fast) var(--ease); }
2541 .issue-title a:hover { color: var(--accent); text-decoration: none; }
2542 .issue-meta {
2543 font-family: var(--font-mono);
2544 font-size: 11px;
2545 color: var(--text-muted);
2546 margin-top: 5px;
2547 letter-spacing: 0.01em;
2ce1d0bClaude2548 }
79136bbClaude2549
2550 .issue-badge {
2551 display: inline-flex;
2552 align-items: center;
2ce1d0bClaude2553 gap: 6px;
79136bbClaude2554 padding: 4px 12px;
2ce1d0bClaude2555 border-radius: var(--r-full);
2556 font-size: var(--t-sm);
79136bbClaude2557 font-weight: 500;
2ce1d0bClaude2558 line-height: 1.4;
79136bbClaude2559 }
958d26aClaude2560 .badge-open { background: rgba(52,211,153,0.10); color: var(--green); border: 1px solid rgba(52,211,153,0.35); }
2561 .badge-closed { background: rgba(182,157,255,0.10); color: #b69dff; border: 1px solid rgba(182,157,255,0.35); }
2562 .badge-merged { background: rgba(140,109,255,0.10); color: var(--accent); border: 1px solid rgba(140,109,255,0.35); }
2ce1d0bClaude2563 .state-merged { color: var(--accent); }
958d26aClaude2564 .ai-review { border-color: var(--accent); box-shadow: 0 0 0 1px rgba(140,109,255,0.3); }
79136bbClaude2565
2ce1d0bClaude2566 .issue-detail { max-width: 920px; }
79136bbClaude2567 .issue-comment-box {
2568 border: 1px solid var(--border);
2ce1d0bClaude2569 border-radius: var(--r-md);
79136bbClaude2570 margin-bottom: 16px;
2571 overflow: hidden;
2ce1d0bClaude2572 background: var(--bg-elevated);
79136bbClaude2573 }
2574 .comment-header {
2575 background: var(--bg-secondary);
2ce1d0bClaude2576 padding: 10px 16px;
79136bbClaude2577 border-bottom: 1px solid var(--border);
2ce1d0bClaude2578 font-size: var(--t-sm);
79136bbClaude2579 color: var(--text-muted);
2580 }
2581
2ce1d0bClaude2582 /* ============================================================ */
2583 /* Panel — flexible container used across many pages */
2584 /* ============================================================ */
2585 .panel {
2586 background: var(--bg-elevated);
2587 border: 1px solid var(--border);
2588 border-radius: var(--r-md);
2589 overflow: hidden;
2590 }
2591 .panel-item {
2592 display: flex;
2593 align-items: center;
2594 gap: 12px;
2595 padding: 12px 16px;
79136bbClaude2596 border-bottom: 1px solid var(--border);
2ce1d0bClaude2597 transition: background var(--t-fast) var(--ease);
2598 }
2599 .panel-item:last-child { border-bottom: none; }
2600 .panel-item:hover { background: var(--bg-hover); }
2601 .panel-empty {
2602 padding: 24px;
2603 text-align: center;
79136bbClaude2604 color: var(--text-muted);
2ce1d0bClaude2605 font-size: var(--t-sm);
79136bbClaude2606 }
2607
2ce1d0bClaude2608 /* ============================================================ */
2609 /* Search */
2610 /* ============================================================ */
79136bbClaude2611 .search-results .diff-file { margin-bottom: 12px; }
16b325cClaude2612
2ce1d0bClaude2613 /* ============================================================ */
2614 /* Timeline */
2615 /* ============================================================ */
2616 .timeline { position: relative; padding-left: 28px; }
16b325cClaude2617 .timeline::before {
2618 content: '';
2619 position: absolute;
2620 left: 4px;
2621 top: 8px;
2622 bottom: 8px;
2623 width: 2px;
2ce1d0bClaude2624 background: linear-gradient(180deg, var(--border) 0%, transparent 100%);
16b325cClaude2625 }
2ce1d0bClaude2626 .timeline-item { position: relative; padding-bottom: 16px; }
16b325cClaude2627 .timeline-dot {
2628 position: absolute;
2ce1d0bClaude2629 left: -28px;
2630 top: 8px;
2631 width: 12px;
2632 height: 12px;
2633 border-radius: var(--r-full);
2634 background: var(--accent-gradient);
16b325cClaude2635 border: 2px solid var(--bg);
2ce1d0bClaude2636 box-shadow: 0 0 0 1px var(--border-strong);
16b325cClaude2637 }
2638 .timeline-content {
2ce1d0bClaude2639 background: var(--bg-elevated);
16b325cClaude2640 border: 1px solid var(--border);
2ce1d0bClaude2641 border-radius: var(--r-md);
2642 padding: 14px 16px;
16b325cClaude2643 }
f1ab587Claude2644
2ce1d0bClaude2645 /* ============================================================ */
2646 /* Toggle switch */
2647 /* ============================================================ */
f1ab587Claude2648 .toggle-switch {
2649 position: relative;
2650 display: inline-block;
2ce1d0bClaude2651 width: 40px;
2652 height: 22px;
f1ab587Claude2653 flex-shrink: 0;
2654 margin-left: 16px;
2655 }
2656 .toggle-switch input { opacity: 0; width: 0; height: 0; }
2657 .toggle-slider {
2658 position: absolute;
2659 cursor: pointer;
2660 top: 0; left: 0; right: 0; bottom: 0;
2661 background: var(--bg-tertiary);
2662 border: 1px solid var(--border);
2ce1d0bClaude2663 border-radius: var(--r-full);
2664 transition: all var(--t-base) var(--ease);
f1ab587Claude2665 }
2666 .toggle-slider::before {
2667 content: '';
2668 position: absolute;
2ce1d0bClaude2669 height: 16px;
2670 width: 16px;
f1ab587Claude2671 left: 2px;
2672 bottom: 2px;
2673 background: var(--text-muted);
2ce1d0bClaude2674 border-radius: var(--r-full);
2675 transition: all var(--t-base) var(--ease);
f1ab587Claude2676 }
2677 .toggle-switch input:checked + .toggle-slider {
2ce1d0bClaude2678 background: var(--accent-gradient);
2679 border-color: transparent;
958d26aClaude2680 box-shadow: 0 0 0 1px rgba(140,109,255,0.4);
f1ab587Claude2681 }
2682 .toggle-switch input:checked + .toggle-slider::before {
2ce1d0bClaude2683 transform: translateX(18px);
f1ab587Claude2684 background: #fff;
2685 }
ef8d378Claude2686
2687 /* ============================================================
2688 * 2026 polish layer (purely additive — no layout changes).
2689 * Improves typography rendering, focus states, hover affordances,
2690 * and adds the gradient brand cue to primary buttons. Anything
2691 * that could alter dimensions stays in the rules above.
2692 * ============================================================ */
2693 html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
2694 body { letter-spacing: -0.005em; font-feature-settings: 'cv11', 'ss01', 'ss03'; }
2695 *::selection { background: rgba(168,85,247,0.35); color: var(--text); }
2696
2697 h1, h2, h3, h4 { letter-spacing: -0.018em; }
2698 h1 { letter-spacing: -0.025em; }
2699
2700 /* Smoother colour transitions everywhere links live */
2701 a { transition: color 120ms cubic-bezier(0.16,1,0.3,1); }
2702
2703 /* Buttons: focus rings + smoother transitions; primary gets the gradient */
2704 .btn {
2705 transition:
2706 background 120ms cubic-bezier(0.16,1,0.3,1),
2707 border-color 120ms cubic-bezier(0.16,1,0.3,1),
2708 transform 120ms cubic-bezier(0.16,1,0.3,1),
2709 box-shadow 120ms cubic-bezier(0.16,1,0.3,1);
2710 }
2711 .btn:active { transform: translateY(0.5px); }
2712 .btn:focus-visible {
2713 outline: none;
2714 box-shadow: 0 0 0 3px rgba(168,85,247,0.30);
2715 }
2716 .btn-primary {
2717 background: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
2718 border-color: transparent;
2719 box-shadow:
2720 inset 0 1px 0 rgba(255,255,255,0.15),
2721 0 1px 2px rgba(168,85,247,0.25);
2722 }
2723 .btn-primary:hover {
2724 background: linear-gradient(135deg, #b766f8 0%, #22cce0 100%);
2725 filter: none;
2726 box-shadow:
2727 inset 0 1px 0 rgba(255,255,255,0.20),
2728 0 4px 12px rgba(168,85,247,0.30);
2729 }
2730
2731 /* Inputs: cleaner focus ring + hover */
2732 .form-group input:hover,
2733 .form-group textarea:hover,
2734 .form-group select:hover {
2735 border-color: rgba(255,255,255,0.14);
2736 }
2737 .form-group input:focus,
2738 .form-group textarea:focus,
2739 .form-group select:focus {
2740 border-color: rgba(168,85,247,0.55);
2741 box-shadow: 0 0 0 3px rgba(168,85,247,0.22);
2742 }
2743 :root[data-theme='light'] .form-group input:hover,
2744 :root[data-theme='light'] .form-group textarea:hover,
2745 :root[data-theme='light'] .form-group select:hover {
2746 border-color: rgba(0,0,0,0.18);
2747 }
2748
2749 /* Cards: subtle hover lift */
2750 .card {
2751 transition:
2752 border-color 160ms cubic-bezier(0.16,1,0.3,1),
2753 transform 160ms cubic-bezier(0.16,1,0.3,1),
2754 box-shadow 200ms cubic-bezier(0.16,1,0.3,1);
2755 }
2756 .card:hover {
2757 border-color: rgba(255,255,255,0.18);
2758 transform: translateY(-1px);
2759 box-shadow: 0 8px 24px rgba(0,0,0,0.30);
2760 }
2761 :root[data-theme='light'] .card:hover {
2762 border-color: rgba(0,0,0,0.18);
2763 box-shadow: 0 8px 24px rgba(0,0,0,0.08);
2764 }
2765
2766 /* Issue / commit / panel rows: smoother hover */
2767 .issue-item, .commit-item {
2768 transition: background 120ms cubic-bezier(0.16,1,0.3,1);
2769 }
2770 .repo-nav a {
2771 transition: color 120ms cubic-bezier(0.16,1,0.3,1),
2772 border-bottom-color 120ms cubic-bezier(0.16,1,0.3,1);
2773 }
2774 .nav-link {
2775 transition: color 120ms cubic-bezier(0.16,1,0.3,1);
2776 }
2777
2778 /* Auth card: subtle elevation so register/login feel premium */
2779 .auth-container {
2780 background: var(--bg-secondary);
2781 border: 1px solid var(--border);
2782 border-radius: var(--r-lg, 12px);
2783 padding: 32px;
2784 box-shadow: 0 4px 16px rgba(0,0,0,0.30), 0 0 0 1px var(--border);
2785 }
2786 :root[data-theme='light'] .auth-container {
2787 box-shadow: 0 4px 16px rgba(0,0,0,0.06), 0 0 0 1px var(--border);
2788 }
2789 .auth-container h2 { letter-spacing: -0.025em; }
2790
2791 /* Empty state: dashed border, generous padding */
2792 .empty-state {
2793 border: 1px dashed var(--border);
2794 border-radius: var(--r-lg, 12px);
2795 background: var(--bg);
2796 }
2797
2798 /* Badges + commit-sha: smoother transition */
2799 .commit-sha, .badge { transition: all 120ms cubic-bezier(0.16,1,0.3,1); }
2800
2801 /* Gradient text utility — matches landing's accent treatment */
2802 .gradient-text {
2803 background: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
2804 -webkit-background-clip: text;
2805 background-clip: text;
2806 -webkit-text-fill-color: transparent;
2807 }
2808
2809 /* Custom scrollbars (subtle, themed) */
2810 ::-webkit-scrollbar { width: 10px; height: 10px; }
2811 ::-webkit-scrollbar-track { background: transparent; }
2812 ::-webkit-scrollbar-thumb {
2813 background: rgba(255,255,255,0.06);
2814 border: 2px solid var(--bg);
2815 border-radius: 9999px;
2816 }
2817 ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.14); }
2818 :root[data-theme='light'] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.10); }
2819 :root[data-theme='light'] ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.18); }
2820
2821 /* Honour reduced-motion preference */
2822 @media (prefers-reduced-motion: reduce) {
2823 *, *::before, *::after {
2824 animation-duration: 0.01ms !important;
2825 animation-iteration-count: 1 !important;
2826 transition-duration: 0.01ms !important;
2827 }
2828 }
c63b860Claude2829
2830 /* Block O3 — visual coherence additive rules. */
2831 .card.card-p-none { padding: 0; }
2832 .card.card-p-sm { padding: var(--space-3); }
2833 .card.card-p-md { padding: var(--space-4); }
2834 .card.card-p-lg { padding: var(--space-6); }
2835 .card.card-elevated { box-shadow: var(--elev-2); }
2836 .card.card-gradient {
2837 background:
2838 linear-gradient(135deg, rgba(140,109,255,0.05), transparent 60%),
2839 var(--bg-elevated);
2840 }
2841 .card.card-gradient::before { opacity: 1; }
2842 .notice {
2843 padding: var(--space-3) var(--space-4);
2844 border-radius: var(--radius-md);
2845 border: 1px solid var(--border);
2846 background: var(--bg-elevated);
2847 color: var(--text);
2848 font-size: var(--font-size-sm);
2849 margin-bottom: var(--space-6);
2850 line-height: var(--leading-normal);
2851 }
2852 .notice-info { border-color: rgba(96,165,250,0.40); background: rgba(96,165,250,0.08); color: var(--text); }
2853 .notice-success { border-color: var(--green); background: rgba(52,211,153,0.08); color: var(--text); }
2854 .notice-warn { border-color: var(--yellow); background: rgba(251,191,36,0.10); color: var(--yellow); }
2855 .notice-error { border-color: var(--red); background: rgba(248,113,113,0.10); color: var(--red); }
2856 .notice-accent { border-color: var(--accent); background: rgba(140,109,255,0.10); color: var(--text); }
2857 .email-preview {
2858 padding: var(--space-5);
2859 background: #fff;
2860 color: #111;
2861 border-radius: var(--radius-md);
2862 }
2863 .code-block {
2864 margin: var(--space-2) 0 0;
2865 padding: var(--space-3);
2866 background: var(--bg-tertiary);
2867 border: 1px solid var(--border-subtle);
2868 border-radius: var(--radius-sm);
2869 font-family: var(--font-mono);
2870 font-size: var(--font-size-xs);
2871 line-height: var(--leading-normal);
2872 overflow-x: auto;
2873 }
2874 .status-pill-operational {
2875 display: inline-flex;
2876 align-items: center;
2877 padding: var(--space-1) var(--space-3);
2878 border-radius: var(--radius-full);
2879 font-size: var(--font-size-xs);
2880 font-weight: 600;
2881 background: rgba(52,211,153,0.15);
2882 color: var(--green);
2883 }
2884 .api-tag {
2885 display: inline-flex;
2886 align-items: center;
2887 font-size: var(--font-size-xs);
2888 padding: 2px var(--space-2);
2889 border-radius: var(--radius-sm);
2890 font-family: var(--font-mono);
2891 }
2892 .api-tag-auth { background: rgba(96,165,250,0.15); color: var(--accent); }
2893 .api-tag-scope { background: rgba(52,211,153,0.15); color: var(--green); }
2894 .stat-number {
2895 font-size: var(--font-size-xl);
2896 font-weight: 700;
2897 color: var(--text-strong);
2898 line-height: var(--leading-tight);
2899 }
2900 .stat-number-accent { color: var(--accent); }
2901 .stat-number-blue { color: var(--blue); }
2902 .stat-number-purple { color: var(--text-link); }
2903 footer .footer-tag-sub {
2904 margin-top: var(--space-2);
2905 font-size: var(--font-size-xs);
2906 color: var(--text-faint);
2907 line-height: var(--leading-normal);
2908 }
2909 footer .footer-version-pill {
2910 display: inline-flex;
2911 align-items: center;
2912 gap: var(--space-2);
2913 padding: var(--space-1) var(--space-3);
2914 border-radius: var(--radius-full);
2915 border: 1px solid var(--border);
2916 background: var(--bg-elevated);
2917 color: var(--text-faint);
2918 font-family: var(--font-mono);
2919 font-size: var(--font-size-xs);
2920 cursor: help;
2921 }
2922 footer .footer-banner {
2923 max-width: 1240px;
2924 margin: var(--space-6) auto 0;
2925 padding: var(--space-3) var(--space-4);
2926 border-radius: var(--radius-md);
2927 border: 1px solid var(--border);
2928 background: var(--bg-elevated);
2929 color: var(--text);
2930 font-family: var(--font-mono);
2931 font-size: var(--font-size-xs);
2932 letter-spacing: 0.04em;
2933 text-transform: uppercase;
2934 text-align: center;
2935 }
2936 footer .footer-banner-info { border-color: rgba(96,165,250,0.40); color: var(--blue); }
2937 footer .footer-banner-warn { border-color: rgba(251,191,36,0.40); color: var(--yellow); }
2938 footer .footer-banner-error { border-color: rgba(248,113,113,0.45); color: var(--red); }
dc26881CC LABS App2939
cf9178bTest User2940 /* ============================================================ */
2941 /* Global toast notifications. */
2942 /* Slide-in from right, auto-dismiss, ARIA-live for SR users. */
2943 /* ============================================================ */
2944 .gx-toast {
2945 pointer-events: auto;
2946 display: inline-flex;
2947 align-items: flex-start;
2948 gap: 10px;
2949 min-width: 280px;
2950 max-width: min(440px, calc(100vw - 32px));
2951 padding: 12px 14px 12px 12px;
2952 background: var(--bg-elevated);
2953 border: 1px solid var(--border);
2954 border-radius: 10px;
2955 box-shadow:
2956 0 12px 36px -10px rgba(15,16,28,0.18),
2957 0 2px 6px rgba(15,16,28,0.04),
2958 0 0 0 1px rgba(15,16,28,0.02);
2959 color: var(--text);
2960 font-size: 13.5px;
2961 line-height: 1.45;
2962 opacity: 0;
2963 transform: translateX(16px);
2964 transition:
2965 opacity 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
2966 transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
2967 }
2968 .gx-toast--in { opacity: 1; transform: translateX(0); }
2969 .gx-toast--out { opacity: 0; transform: translateX(16px); }
2970 .gx-toast__icon {
2971 flex-shrink: 0;
2972 width: 20px; height: 20px;
2973 border-radius: 50%;
2974 display: inline-flex;
2975 align-items: center;
2976 justify-content: center;
2977 font-size: 12px;
2978 font-weight: 700;
2979 line-height: 1;
2980 margin-top: 1px;
2981 }
2982 .gx-toast__text { flex: 1 1 auto; min-width: 0; padding-top: 2px; word-wrap: break-word; }
2983 .gx-toast__close {
2984 flex-shrink: 0;
2985 width: 22px; height: 22px;
2986 border: 0;
2987 background: transparent;
2988 color: var(--text-muted);
2989 font-size: 18px;
2990 line-height: 1;
2991 cursor: pointer;
2992 border-radius: 4px;
2993 padding: 0;
2994 margin: -2px -2px 0 0;
2995 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2996 }
2997 .gx-toast__close:hover { background: var(--bg-hover); color: var(--text); }
2998 .gx-toast--success .gx-toast__icon { background: rgba(5,150,105,0.14); color: var(--green); }
2999 .gx-toast--error .gx-toast__icon { background: rgba(220,38,38,0.14); color: var(--red); }
3000 .gx-toast--warn .gx-toast__icon { background: rgba(217,119,6,0.16); color: var(--yellow); }
3001 .gx-toast--info .gx-toast__icon { background: rgba(109,77,255,0.14); color: var(--accent); }
3002 @media (prefers-reduced-motion: reduce) {
3003 .gx-toast { transition: opacity 60ms linear; transform: none; }
3004 .gx-toast--in, .gx-toast--out { transform: none; }
3005 }
3006
dc26881CC LABS App3007 /* ============================================================ */
3008 /* Block U4 — cross-document view transitions. */
3009 /* Chrome 126+, Edge, Safari 18.2+ get a soft 200ms fade on */
3010 /* every same-origin navigation. Older browsers ignore these */
3011 /* rules entirely — no JS shim, no breakage. */
3012 /* prefers-reduced-motion disables the animation honourably. */
3013 /* ============================================================ */
3014 @view-transition {
3015 navigation: auto;
3016 }
3017 ::view-transition-old(root),
3018 ::view-transition-new(root) {
3019 animation-duration: 200ms;
3020 animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
3021 }
3022 ::view-transition-old(root) {
3023 animation-name: vt-fade-out;
3024 }
3025 ::view-transition-new(root) {
3026 animation-name: vt-fade-in;
3027 }
3028 @keyframes vt-fade-out {
3029 to { opacity: 0; }
3030 }
3031 @keyframes vt-fade-in {
3032 from { opacity: 0; }
3033 }
3034 @media (prefers-reduced-motion: reduce) {
3035 ::view-transition-old(root),
3036 ::view-transition-new(root) {
3037 animation-duration: 0s;
3038 }
3039 }
3040 /* The transition system picks up its root subject from this rule. */
3041 body {
3042 view-transition-name: root;
3043 }
fc1817aClaude3044`;