gitmaps 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +167 -0
  2. package/app/api/auth/favorites/route.ts +56 -0
  3. package/app/api/auth/github/callback/route.ts +103 -0
  4. package/app/api/auth/github/route.ts +32 -0
  5. package/app/api/auth/me/route.ts +52 -0
  6. package/app/api/auth/positions/route.ts +50 -0
  7. package/app/api/chat/route.ts +101 -0
  8. package/app/api/connections/route.ts +72 -0
  9. package/app/api/github/repos/route.ts +111 -0
  10. package/app/api/positions/route.ts +80 -0
  11. package/app/api/repo/branch-diff/route.ts +201 -0
  12. package/app/api/repo/branches/route.ts +53 -0
  13. package/app/api/repo/browse/route.ts +55 -0
  14. package/app/api/repo/clone/route.ts +78 -0
  15. package/app/api/repo/clone-stream/route.ts +131 -0
  16. package/app/api/repo/file-content/route.ts +28 -0
  17. package/app/api/repo/file-delete/route.ts +62 -0
  18. package/app/api/repo/file-history/route.ts +45 -0
  19. package/app/api/repo/file-rename/route.ts +83 -0
  20. package/app/api/repo/file-save/route.ts +45 -0
  21. package/app/api/repo/files/route.ts +169 -0
  22. package/app/api/repo/git-blame/route.ts +86 -0
  23. package/app/api/repo/git-commit/route.ts +40 -0
  24. package/app/api/repo/git-heatmap/route.ts +55 -0
  25. package/app/api/repo/imports/route.ts +154 -0
  26. package/app/api/repo/load/route.ts +56 -0
  27. package/app/api/repo/mode/route.ts +14 -0
  28. package/app/api/repo/search/route.ts +127 -0
  29. package/app/api/repo/tree/route.ts +104 -0
  30. package/app/api/repo/upload/route.ts +53 -0
  31. package/app/api/repo/validate-path.ts +53 -0
  32. package/app/canvas_users.db +0 -0
  33. package/app/canvas_users.db-shm +0 -0
  34. package/app/canvas_users.db-wal +0 -0
  35. package/app/globals.css +7899 -0
  36. package/app/layout.tsx +493 -0
  37. package/app/lib/auth.ts +193 -0
  38. package/app/lib/auto-save.ts +137 -0
  39. package/app/lib/branch-compare.ts +443 -0
  40. package/app/lib/breadcrumbs.ts +170 -0
  41. package/app/lib/canvas-export.ts +358 -0
  42. package/app/lib/canvas-text.ts +912 -0
  43. package/app/lib/canvas.ts +564 -0
  44. package/app/lib/card-arrangement.ts +188 -0
  45. package/app/lib/card-context-menu.tsx +453 -0
  46. package/app/lib/card-diff-markers.ts +270 -0
  47. package/app/lib/card-expand.ts +189 -0
  48. package/app/lib/card-groups.ts +246 -0
  49. package/app/lib/cards.tsx +914 -0
  50. package/app/lib/chat.tsx +308 -0
  51. package/app/lib/code-editor.ts +508 -0
  52. package/app/lib/command-palette.ts +262 -0
  53. package/app/lib/connections.tsx +1037 -0
  54. package/app/lib/context.ts +94 -0
  55. package/app/lib/cursor-sharing.ts +281 -0
  56. package/app/lib/dependency-graph.ts +438 -0
  57. package/app/lib/events.tsx +1747 -0
  58. package/app/lib/file-card-plugin.ts +134 -0
  59. package/app/lib/file-modal.tsx +849 -0
  60. package/app/lib/file-preview.ts +400 -0
  61. package/app/lib/file-tabs.ts +318 -0
  62. package/app/lib/galaxydraw-bridge.ts +477 -0
  63. package/app/lib/galaxydraw.test.ts +229 -0
  64. package/app/lib/global-search.ts +264 -0
  65. package/app/lib/goto-definition.ts +224 -0
  66. package/app/lib/heatmap.ts +178 -0
  67. package/app/lib/hidden-files.tsx +222 -0
  68. package/app/lib/layers.ts +0 -0
  69. package/app/lib/layers.tsx +365 -0
  70. package/app/lib/loading.tsx +45 -0
  71. package/app/lib/multi-repo.ts +286 -0
  72. package/app/lib/new-file-dialog.tsx +230 -0
  73. package/app/lib/onboarding.tsx +213 -0
  74. package/app/lib/perf-overlay.ts +360 -0
  75. package/app/lib/positions.ts +176 -0
  76. package/app/lib/pr-review.ts +374 -0
  77. package/app/lib/production-mode.ts +47 -0
  78. package/app/lib/repo.tsx +977 -0
  79. package/app/lib/settings-modal.tsx +374 -0
  80. package/app/lib/settings.ts +97 -0
  81. package/app/lib/shortcuts-panel.ts +141 -0
  82. package/app/lib/status-bar.ts +128 -0
  83. package/app/lib/symbol-outline.ts +212 -0
  84. package/app/lib/syntax.ts +177 -0
  85. package/app/lib/tab-diff.ts +238 -0
  86. package/app/lib/user.tsx +133 -0
  87. package/app/lib/utils.ts +78 -0
  88. package/app/lib/viewport-culling.ts +728 -0
  89. package/app/page.client.tsx +215 -0
  90. package/app/page.tsx +291 -0
  91. package/app/state/machine.js +196 -0
  92. package/app/styles/main.css +2168 -0
  93. package/banner.png +0 -0
  94. package/cli.ts +44 -0
  95. package/package.json +75 -0
  96. package/packages/galaxydraw/README.md +296 -0
  97. package/packages/galaxydraw/banner.png +0 -0
  98. package/packages/galaxydraw/demo/build-static.ts +100 -0
  99. package/packages/galaxydraw/demo/client.ts +154 -0
  100. package/packages/galaxydraw/demo/dist/client.js +8 -0
  101. package/packages/galaxydraw/demo/index.html +256 -0
  102. package/packages/galaxydraw/demo/server.ts +96 -0
  103. package/packages/galaxydraw/dist/index.js +984 -0
  104. package/packages/galaxydraw/dist/index.js.map +16 -0
  105. package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
  106. package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
  107. package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
  108. package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
  109. package/packages/galaxydraw/package.json +49 -0
  110. package/packages/galaxydraw/perf.test.ts +284 -0
  111. package/packages/galaxydraw/src/core/cards.ts +435 -0
  112. package/packages/galaxydraw/src/core/engine.ts +339 -0
  113. package/packages/galaxydraw/src/core/events.ts +81 -0
  114. package/packages/galaxydraw/src/core/layout.ts +136 -0
  115. package/packages/galaxydraw/src/core/minimap.ts +216 -0
  116. package/packages/galaxydraw/src/core/state.ts +177 -0
  117. package/packages/galaxydraw/src/core/viewport.ts +106 -0
  118. package/packages/galaxydraw/src/galaxydraw.css +166 -0
  119. package/packages/galaxydraw/src/index.ts +40 -0
  120. package/packages/galaxydraw/tsconfig.json +30 -0
  121. package/server.ts +62 -0
@@ -0,0 +1,215 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * page.client.tsx — Slim orchestrator
4
+ *
5
+ * Creates the XState actor, initialises all sub-modules, and returns a
6
+ * cleanup function. All heavy logic lives in `./lib/*`.
7
+ *
8
+ * Uses an AbortController to cancel in-flight async work when cleanup runs,
9
+ * preventing the "stopped actor" race condition.
10
+ */
11
+ import { measure } from 'measure-fn';
12
+ import { createActor } from 'xstate';
13
+ import { canvasMachine } from './state/machine.js';
14
+ import { createCanvasContext } from './lib/context';
15
+ import { loadSavedPositions } from './lib/positions';
16
+ import { loadHiddenFiles, updateHiddenUI } from './lib/hidden-files';
17
+ import { setupCanvasInteraction, setupEventListeners } from './lib/events';
18
+ import { loadConnections } from './lib/connections';
19
+ import { clearCanvas, updateCanvasTransform, updateZoomUI, restoreViewport } from './lib/canvas';
20
+ import { setupPillInteraction } from './lib/viewport-culling';
21
+ import { loadRepository } from './lib/repo';
22
+ import { initLayers, renderLayersUI } from './lib/layers';
23
+ import { setupAuth, updateFavoriteStar } from './lib/user';
24
+ import { setupPerfOverlay } from './lib/perf-overlay';
25
+ import { initGalaxyDrawState, initCardManager } from './lib/galaxydraw-bridge';
26
+ import { initFilePreview, destroyFilePreview } from './lib/file-preview';
27
+ import { initBranchCompare } from './lib/branch-compare';
28
+ import { initCommandPalette } from './lib/command-palette';
29
+ import { initShortcutsPanel } from './lib/shortcuts-panel';
30
+ import { initStatusBar } from './lib/status-bar';
31
+
32
+ export default function mount(): () => void {
33
+ // Stop any previous actor from a prior mount
34
+ if ((window as any).__gitcanvas_cleanup__) {
35
+ try { (window as any).__gitcanvas_cleanup__(); } catch (_) { }
36
+ }
37
+
38
+ const actor = createActor(canvasMachine);
39
+ const ctx = createCanvasContext(actor);
40
+ let disposed = false;
41
+
42
+ // ─── Init ────────────────────────────────────────────
43
+ async function init() {
44
+ return measure('app:init', async () => {
45
+ ctx.canvas = document.getElementById('canvasContent');
46
+ ctx.canvasViewport = document.getElementById('canvasViewport');
47
+
48
+ // Reuse existing SVG overlay from server-rendered DOM
49
+ ctx.svgOverlay = document.getElementById('connectionsOverlay') as unknown as SVGSVGElement;
50
+ if (!ctx.svgOverlay && ctx.canvas) {
51
+ // Fallback: create overlay if not present
52
+ ctx.svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg') as SVGSVGElement;
53
+ ctx.svgOverlay.id = 'connectionsOverlay';
54
+ ctx.svgOverlay.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1;overflow:visible;';
55
+ ctx.canvas.appendChild(ctx.svgOverlay);
56
+ }
57
+
58
+ // Init galaxydraw state engine (binds to existing DOM)
59
+ initGalaxyDrawState(ctx);
60
+
61
+ // Init galaxydraw card manager (Phase 4 — registers file + diff plugins)
62
+ initCardManager(ctx);
63
+
64
+ actor.start();
65
+ setupCanvasInteraction(ctx);
66
+ setupEventListeners(ctx);
67
+ setupPillInteraction(ctx);
68
+ setupPerfOverlay(ctx);
69
+ if (ctx.canvasViewport) initFilePreview(ctx.canvasViewport, ctx);
70
+ initBranchCompare(ctx);
71
+ initCommandPalette(ctx);
72
+ initShortcutsPanel();
73
+ initStatusBar(ctx);
74
+ await loadSavedPositions(ctx); // initial load (may be empty if no repo yet)
75
+ if (disposed) return; // bail if cleaned up during await
76
+ loadHiddenFiles(ctx);
77
+ updateHiddenUI(ctx);
78
+ await loadConnections(ctx);
79
+ if (disposed) return; // bail if cleaned up during await
80
+
81
+ // Init auth UI
82
+ setupAuth();
83
+
84
+ // ── Shared Layout Decoder ──────────────────────────────────────────
85
+ const applySharedLayout = async (ctx: CanvasContext) => {
86
+ const urlParams = new URLSearchParams(window.location.search);
87
+ const sharedLayout = urlParams.get('layout');
88
+ if (!sharedLayout) return;
89
+
90
+ try {
91
+ const parsed = JSON.parse(atob(sharedLayout));
92
+ if (parsed.positions) {
93
+ ctx.positions = new Map(Object.entries(parsed.positions));
94
+ const { savePosition } = await import('./lib/positions');
95
+ // Quick dummy save to trigger debounced persistence
96
+ savePosition(ctx, '_share_', '_trigger_', 0, 0);
97
+ }
98
+ if (parsed.hiddenFiles) {
99
+ ctx.hiddenFiles = new Set(parsed.hiddenFiles);
100
+ const { saveHiddenFiles } = await import('./lib/hidden-files');
101
+ saveHiddenFiles(ctx);
102
+ updateHiddenUI(ctx);
103
+ }
104
+ if (parsed.zoom !== undefined) ctx.actor.send({ type: 'SET_ZOOM', zoom: parsed.zoom });
105
+ if (parsed.offsetX !== undefined) ctx.actor.send({ type: 'SET_OFFSET', x: parsed.offsetX, y: parsed.offsetY });
106
+ if (parsed.cardSizes) {
107
+ for (const [path, size] of Object.entries(parsed.cardSizes)) {
108
+ ctx.actor.send({ type: 'RESIZE_CARD', path, width: (size as any).width, height: (size as any).height });
109
+ }
110
+ }
111
+
112
+ const cleanUrl = new URL(window.location.href);
113
+ cleanUrl.searchParams.delete('layout');
114
+ window.history.replaceState({}, '', cleanUrl.toString());
115
+ const { showToast } = await import('./lib/utils');
116
+ showToast('Shared layout applied!', 'success');
117
+ } catch (e) {
118
+ console.error('Failed to decode shared layout', e);
119
+ }
120
+ };
121
+
122
+ // Check URL hash for repo slug (or legacy full path)
123
+ const hashValue = decodeURIComponent(window.location.hash.replace('#', ''));
124
+ if (hashValue) {
125
+ // Resolve slug to full path (check localStorage mapping)
126
+ const resolvedPath = localStorage.getItem(`gitcanvas:slug:${hashValue}`) || hashValue;
127
+
128
+ // Hide landing immediately since we have a repo
129
+ const landing = document.getElementById('landingOverlay');
130
+ if (landing) landing.style.display = 'none';
131
+
132
+ const sel = document.getElementById('repoSelect') as HTMLSelectElement;
133
+ if (sel) sel.value = resolvedPath;
134
+
135
+ // Init layers based on repo
136
+ ctx.actor.send({ type: 'LOAD_REPO', path: resolvedPath });
137
+ ctx.snap().context.repoPath = resolvedPath;
138
+ await loadSavedPositions(ctx); // reload positions for this repo
139
+ if (disposed) return;
140
+ await applySharedLayout(ctx);
141
+ initLayers(ctx);
142
+ renderLayersUI(ctx);
143
+ restoreViewport(ctx);
144
+ updateCanvasTransform(ctx);
145
+ updateZoomUI(ctx);
146
+
147
+ if (!disposed) {
148
+ import('./lib/pr-review').then(({ initReviewStore }) => {
149
+ initReviewStore(hashValue);
150
+ });
151
+ loadRepository(ctx, resolvedPath);
152
+ updateFavoriteStar(resolvedPath);
153
+ }
154
+ } else {
155
+ const saved = localStorage.getItem('gitcanvas:lastRepo');
156
+ if (saved) {
157
+ const sel2 = document.getElementById('repoSelect') as HTMLSelectElement;
158
+ if (sel2) sel2.value = saved;
159
+
160
+ // Set URL hash to friendly slug instead of full path
161
+ const savedSlug = saved.replace(/\\/g, '/').split('/').filter(Boolean).pop() || saved;
162
+ history.replaceState(null, '', '#' + encodeURIComponent(savedSlug));
163
+ // Store slug→path mapping
164
+ localStorage.setItem(`gitcanvas:slug:${savedSlug}`, saved);
165
+
166
+ ctx.actor.send({ type: 'LOAD_REPO', path: saved });
167
+ ctx.snap().context.repoPath = saved;
168
+ await loadSavedPositions(ctx);
169
+ if (disposed) return;
170
+ await applySharedLayout(ctx);
171
+ initLayers(ctx);
172
+ renderLayersUI(ctx);
173
+ restoreViewport(ctx);
174
+ updateCanvasTransform(ctx);
175
+ updateZoomUI(ctx);
176
+
177
+ // Actually load the repo data
178
+ if (!disposed) {
179
+ import('./lib/pr-review').then(({ initReviewStore }) => {
180
+ initReviewStore(savedSlug);
181
+ });
182
+ loadRepository(ctx, saved);
183
+ }
184
+ }
185
+ }
186
+
187
+ // Listen for hash changes
188
+ window.addEventListener('hashchange', () => {
189
+ if (disposed) return;
190
+ const hashSlug = decodeURIComponent(window.location.hash.replace('#', ''));
191
+ const resolvedPath = localStorage.getItem(`gitcanvas:slug:${hashSlug}`) || hashSlug;
192
+ if (resolvedPath && resolvedPath !== ctx.snap().context.repoPath) {
193
+ const sel3 = document.getElementById('repoSelect') as HTMLSelectElement;
194
+ if (sel3) sel3.value = resolvedPath;
195
+ loadRepository(ctx, resolvedPath);
196
+ updateFavoriteStar(resolvedPath);
197
+ }
198
+ });
199
+ });
200
+ }
201
+
202
+ // ─── Boot ────────────────────────────────────────────
203
+ init();
204
+
205
+ // ─── Cleanup ─────────────────────────────────────────
206
+ const cleanup = () => {
207
+ disposed = true;
208
+ (window as any).__gitcanvas_cleanup__ = null;
209
+ try { actor.stop(); } catch (_) { }
210
+ if (ctx.canvasViewport) destroyFilePreview(ctx.canvasViewport);
211
+ clearCanvas(ctx);
212
+ };
213
+ (window as any).__gitcanvas_cleanup__ = cleanup;
214
+ return cleanup;
215
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Home Page — JSX server component
3
+ *
4
+ * Returns the canvas viewport as JSX.
5
+ * Client interactivity is mounted by page.client.tsx.
6
+ */
7
+
8
+ const FEATURED_REPOS_FALLBACK = [
9
+ { name: 'facebook/react', desc: 'UI Library', lang: 'JavaScript', stars: '230k' },
10
+ { name: 'denoland/deno', desc: 'JS Runtime', lang: 'TypeScript', stars: '100k' },
11
+ { name: 'sveltejs/svelte', desc: 'Compiler Framework', lang: 'TypeScript', stars: '80k' },
12
+ { name: 'oven-sh/bun', desc: 'JS Toolkit', lang: 'Zig', stars: '75k' },
13
+ { name: 'vercel/next.js', desc: 'React Framework', lang: 'TypeScript', stars: '127k' },
14
+ { name: 'tailwindlabs/tailwindcss', desc: 'CSS Framework', lang: 'TypeScript', stars: '85k' },
15
+ ];
16
+
17
+ // Cache GitHub stats for 5 minutes
18
+ let _cachedRepos: typeof FEATURED_REPOS_FALLBACK | null = null;
19
+ let _cacheTime = 0;
20
+ const CACHE_TTL = 5 * 60 * 1000;
21
+
22
+ function formatStars(n: number): string {
23
+ return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
24
+ }
25
+
26
+ async function getFeaturedRepos() {
27
+ if (_cachedRepos && Date.now() - _cacheTime < CACHE_TTL) return _cachedRepos;
28
+ try {
29
+ const results = await Promise.all(
30
+ FEATURED_REPOS_FALLBACK.map(async (r) => {
31
+ const resp = await fetch(`https://api.github.com/repos/${r.name}`, {
32
+ headers: { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'GitMaps' },
33
+ signal: AbortSignal.timeout(3000),
34
+ });
35
+ if (!resp.ok) return r;
36
+ const data = await resp.json();
37
+ return {
38
+ name: r.name,
39
+ desc: r.desc,
40
+ lang: data.language || r.lang,
41
+ stars: formatStars(data.stargazers_count || 0),
42
+ };
43
+ })
44
+ );
45
+ _cachedRepos = results;
46
+ _cacheTime = Date.now();
47
+ return results;
48
+ } catch {
49
+ return _cachedRepos || FEATURED_REPOS_FALLBACK;
50
+ }
51
+ }
52
+
53
+ export default function Page() {
54
+ // Use fallback values synchronously — Melina does NOT support async page components.
55
+ // The GitHub star counts are fetched client-side or pre-cached.
56
+ const featuredRepos = _cachedRepos || FEATURED_REPOS_FALLBACK;
57
+ // Trigger async cache fill for next render
58
+ getFeaturedRepos().catch(() => { });
59
+
60
+ const langColors: Record<string, string> = {
61
+ JavaScript: '#f1e05a',
62
+ TypeScript: '#3178c6',
63
+ Zig: '#ec915c',
64
+ Rust: '#dea584',
65
+ Go: '#00ADD8',
66
+ };
67
+
68
+ return (
69
+ <div className="canvas-viewport" id="canvasViewport">
70
+ <div className="canvas-content" id="canvasContent">
71
+ <svg className="connections-overlay" id="connectionsOverlay"></svg>
72
+ </div>
73
+
74
+ {/* Landing overlay — visible until a repo is loaded */}
75
+ <div className="landing-overlay" id="landingOverlay">
76
+ {/* Animated grid canvas background */}
77
+ <div className="landing-grid-bg">
78
+ <div className="grid-line gl-h1"></div>
79
+ <div className="grid-line gl-h2"></div>
80
+ <div className="grid-line gl-h3"></div>
81
+ <div className="grid-line gl-v1"></div>
82
+ <div className="grid-line gl-v2"></div>
83
+ <div className="grid-line gl-v3"></div>
84
+ </div>
85
+
86
+ {/* Animated background particles */}
87
+ <div className="landing-bg-particles" id="landingParticles">
88
+ <div className="particle p1"></div>
89
+ <div className="particle p2"></div>
90
+ <div className="particle p3"></div>
91
+ <div className="particle p4"></div>
92
+ <div className="particle p5"></div>
93
+ <div className="particle p6"></div>
94
+ <div className="particle p7"></div>
95
+ <div className="particle p8"></div>
96
+ </div>
97
+ {/* Gradient mesh background */}
98
+ <div className="landing-mesh"></div>
99
+
100
+ <div className="landing-content">
101
+ {/* Hero Section */}
102
+ <div className="landing-hero">
103
+ <div className="landing-badge">
104
+ <span className="badge-dot"></span>
105
+ Open Source · Spatial Code Explorer
106
+ </div>
107
+
108
+ <div className="landing-icon">
109
+ <svg viewBox="0 0 140 140" width="100" height="100" fill="none">
110
+ {/* Outer ring with dash animation */}
111
+ <circle cx="70" cy="70" r="64" stroke="url(#lgHero)" strokeWidth="1.5" opacity="0.2" strokeDasharray="6 8" className="ring-outer" />
112
+ <circle cx="70" cy="70" r="48" stroke="url(#lgHero)" strokeWidth="1.2" opacity="0.15" strokeDasharray="4 6" className="ring-mid" />
113
+ <circle cx="70" cy="70" r="30" stroke="url(#lgHero)" strokeWidth="1" opacity="0.1" />
114
+
115
+ {/* Center glow core */}
116
+ <circle cx="70" cy="70" r="8" fill="url(#lgHero)" opacity="0.9" className="core-glow" />
117
+ <circle cx="70" cy="70" r="4" fill="#fff" opacity="0.8" />
118
+
119
+ {/* Orbiting nodes with connection lines */}
120
+ <g className="orbit-group">
121
+ <line x1="70" y1="6" x2="70" y2="62" stroke="#a78bfa" strokeWidth="0.6" opacity="0.2" />
122
+ <circle cx="70" cy="6" r="5" fill="#a78bfa" opacity="0.85" className="node-orbit n1" />
123
+ <circle cx="70" cy="6" r="2" fill="#fff" opacity="0.6" />
124
+
125
+ <line x1="126" y1="46" x2="76" y2="68" stroke="#60a5fa" strokeWidth="0.6" opacity="0.2" />
126
+ <circle cx="126" cy="46" r="4.5" fill="#60a5fa" opacity="0.8" className="node-orbit n2" />
127
+ <circle cx="126" cy="46" r="1.8" fill="#fff" opacity="0.5" />
128
+
129
+ <line x1="112" y1="110" x2="74" y2="74" stroke="#34d399" strokeWidth="0.6" opacity="0.18" />
130
+ <circle cx="112" cy="110" r="4" fill="#34d399" opacity="0.75" className="node-orbit n3" />
131
+
132
+ <line x1="28" y1="110" x2="66" y2="74" stroke="#f472b6" strokeWidth="0.6" opacity="0.18" />
133
+ <circle cx="28" cy="110" r="4" fill="#f472b6" opacity="0.65" className="node-orbit n4" />
134
+
135
+ <line x1="14" y1="46" x2="64" y2="68" stroke="#fbbf24" strokeWidth="0.6" opacity="0.2" />
136
+ <circle cx="14" cy="46" r="3.5" fill="#fbbf24" opacity="0.7" className="node-orbit n5" />
137
+
138
+ {/* Extra subtle node */}
139
+ <circle cx="98" cy="20" r="2.5" fill="#818cf8" opacity="0.4" className="node-orbit n6" />
140
+ <circle cx="42" cy="20" r="2" fill="#c084fc" opacity="0.35" />
141
+ </g>
142
+
143
+ <defs>
144
+ <linearGradient id="lgHero" x1="0%" y1="0%" x2="100%" y2="100%">
145
+ <stop offset="0%" stopColor="#a78bfa" />
146
+ <stop offset="50%" stopColor="#60a5fa" />
147
+ <stop offset="100%" stopColor="#34d399" />
148
+ </linearGradient>
149
+ <radialGradient id="rgCore">
150
+ <stop offset="0%" stopColor="#c4b5fd" />
151
+ <stop offset="100%" stopColor="#7c3aed" />
152
+ </radialGradient>
153
+ </defs>
154
+ </svg>
155
+ </div>
156
+
157
+ <h1 className="landing-title">
158
+ <span className="title-git">Git</span>
159
+ <span className="title-maps">Maps</span>
160
+ </h1>
161
+ <p className="landing-subtitle">
162
+ See your codebase in a new dimension.<br />
163
+ <span className="subtitle-accent">Every file becomes a card. Every change tells a story.</span>
164
+ </p>
165
+ </div>
166
+
167
+ {/* Feature Cards */}
168
+ <div className="landing-features">
169
+ <div className="feature-card fc-1">
170
+ <div className="feature-glow"></div>
171
+ <div className="feature-icon-wrap">
172
+ <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
173
+ <rect x="3" y="3" width="7" height="7" rx="1" />
174
+ <rect x="14" y="3" width="7" height="7" rx="1" />
175
+ <rect x="3" y="14" width="7" height="7" rx="1" />
176
+ <rect x="14" y="14" width="7" height="7" rx="1" />
177
+ </svg>
178
+ </div>
179
+ <h3>Spatial Code Review</h3>
180
+ <p>Every file becomes a card on an infinite 2D canvas. Arrange, group, and navigate them spatially — not as a flat list.</p>
181
+ </div>
182
+
183
+ <div className="feature-card fc-2">
184
+ <div className="feature-glow"></div>
185
+ <div className="feature-icon-wrap">
186
+ <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
187
+ <circle cx="6" cy="6" r="3" />
188
+ <circle cx="18" cy="18" r="3" />
189
+ <path d="M8.59 13.51l6.83 6.83" />
190
+ <line x1="21" y1="3" x2="14" y2="10" />
191
+ <polyline points="21 10 21 3 14 3" />
192
+ </svg>
193
+ </div>
194
+ <h3>Interwingled Files</h3>
195
+ <p>Draw connections between related lines across files. See how <code>auth.ts:42</code> relates to <code>middleware.ts:15</code> — visually.</p>
196
+ </div>
197
+
198
+ <div className="feature-card fc-3">
199
+ <div className="feature-glow"></div>
200
+ <div className="feature-icon-wrap">
201
+ <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
202
+ <polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" />
203
+ <line x1="12" y1="22" x2="12" y2="15.5" />
204
+ <polyline points="22 8.5 12 15.5 2 8.5" />
205
+ <polyline points="2 15.5 12 8.5 22 15.5" />
206
+ <line x1="12" y1="2" x2="12" y2="8.5" />
207
+ </svg>
208
+ </div>
209
+ <h3>Layers of Focus</h3>
210
+ <p>Create layers to isolate subsets of files. "Auth layer" shows only auth-related changes. Switch context without losing position.</p>
211
+ </div>
212
+
213
+ <div className="feature-card fc-4">
214
+ <div className="feature-glow"></div>
215
+ <div className="feature-icon-wrap">
216
+ <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
217
+ <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
218
+ <polyline points="7.5 4.21 12 6.81 16.5 4.21" />
219
+ <polyline points="7.5 19.79 7.5 14.6 3 12" />
220
+ <polyline points="21 12 16.5 14.6 16.5 19.79" />
221
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
222
+ <line x1="12" y1="22.08" x2="12" y2="12" />
223
+ </svg>
224
+ </div>
225
+ <h3>Clone & Explore</h3>
226
+ <p>Paste any GitHub URL and explore it instantly. Real-time clone progress, smart caching, and full commit timeline navigation.</p>
227
+ </div>
228
+ </div>
229
+
230
+ {/* Featured Repos — Quick start */}
231
+ <div className="landing-repos">
232
+ <div className="repos-header">
233
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
234
+ <path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
235
+ <path d="M9 18c-4.51 2-5-2-7-2" />
236
+ </svg>
237
+ <span>Explore popular repositories</span>
238
+ </div>
239
+ <div className="repos-grid">
240
+ {featuredRepos.map((repo) => (
241
+ <button
242
+ key={repo.name}
243
+ className="repo-card-btn"
244
+ data-repo={`https://github.com/${repo.name}`}
245
+ id={`landing-repo-${repo.name.replace('/', '-')}`}
246
+ >
247
+ <div className="repo-card-name">{repo.name.split('/')[1]}</div>
248
+ <div className="repo-card-org">{repo.name.split('/')[0]}</div>
249
+ <div className="repo-card-meta">
250
+ <span className="repo-card-lang">
251
+ <span className="lang-dot" style={{ background: langColors[repo.lang] || '#888' }}></span>
252
+ {repo.lang}
253
+ </span>
254
+ <span className="repo-card-stars">★ {repo.stars}</span>
255
+ </div>
256
+ </button>
257
+ ))}
258
+ </div>
259
+ </div>
260
+
261
+ {/* Call to Action */}
262
+ <div className="landing-cta">
263
+ <div className="cta-arrows">
264
+ <span className="cta-arrow a1">←</span>
265
+ <span className="cta-text">Select a repository from the sidebar to begin</span>
266
+ </div>
267
+ <div className="cta-or">or click Import from GitHub in the sidebar</div>
268
+ </div>
269
+
270
+ {/* Stats Row */}
271
+ <div className="landing-stats">
272
+ <div className="landing-stat">
273
+ <span className="stat-num">∞</span>
274
+ <span className="stat-desc">Infinite Canvas</span>
275
+ </div>
276
+ <div className="stat-divider"></div>
277
+ <div className="landing-stat">
278
+ <span className="stat-num">0ms</span>
279
+ <span className="stat-desc">Local-First</span>
280
+ </div>
281
+ <div className="stat-divider"></div>
282
+ <div className="landing-stat">
283
+ <span className="stat-num">OSS</span>
284
+ <span className="stat-desc">Open Source</span>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ );
291
+ }