Skip to content

Commit c45b934

Browse files
michaeloboyleclaude
andcommitted
Update GitHub Pages with Vis.js interactive visualizations
Sync landing page with main branch changes: - Added Vis.js network visualizations to use case examples - Interactive "Run Query" buttons with animated query paths - Code snippets with syntax highlighting - Mobile-responsive graph heights - Auto-play on scroll 🤖 Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <[email protected]>
1 parent 0c2839f commit c45b934

File tree

5 files changed

+765
-16
lines changed

5 files changed

+765
-16
lines changed

index.html

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<link rel="preconnect" href="https://fonts.googleapis.com">
1111
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1212
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
13+
<!-- Vis.js Network Visualization -->
14+
<script type="text/javascript" src="https://unpkg.com/[email protected]/dist/vis-network.min.js"></script>
1315
</head>
1416
<body>
1517
<!-- Navigation -->
@@ -193,29 +195,69 @@ <h3 class="feature-title">Enterprise Security</h3>
193195
<h2 class="section-title">Built for modern applications</h2>
194196
</div>
195197
<div class="use-cases-grid">
196-
<div class="use-case">
198+
<div class="use-case" data-example="job-search">
197199
<div class="use-case-icon">🔍</div>
198200
<h3>Job Boards & Recruiting</h3>
199201
<p>Connect jobs, companies, candidates, and skills. Query complex relationships instantly.</p>
200-
<a href="#" class="use-case-link">See example →</a>
202+
<div id="graph-job-search" class="graph-visualization"></div>
203+
<button class="animate-query-btn" data-target="job-search">▶ Run Query</button>
204+
<div class="code-snippet">
205+
<pre><code><span class="code-comment">// Find remote jobs matching my skills</span>
206+
<span class="code-keyword">const</span> jobs = <span class="code-keyword">await</span> db
207+
.<span class="code-function">nodes</span>(<span class="code-string">'User'</span>, { id: <span class="code-string">'me'</span> })
208+
.<span class="code-function">follow</span>(<span class="code-string">'HAS_SKILL'</span>)
209+
.<span class="code-function">follow</span>(<span class="code-string">'-REQUIRES_SKILL'</span>)
210+
.<span class="code-function">where</span>({ remote: <span class="code-keyword">true</span> })
211+
.<span class="code-function">exec</span>();</code></pre>
212+
</div>
201213
</div>
202-
<div class="use-case">
214+
<div class="use-case" data-example="ecommerce">
203215
<div class="use-case-icon">🛒</div>
204216
<h3>E-Commerce</h3>
205217
<p>Product recommendations, user preferences, purchase history. Real-time personalization.</p>
206-
<a href="#" class="use-case-link">See example →</a>
218+
<div id="graph-ecommerce" class="graph-visualization"></div>
219+
<button class="animate-query-btn" data-target="ecommerce">▶ Run Query</button>
220+
<div class="code-snippet">
221+
<pre><code><span class="code-comment">// Recommend products based on purchases</span>
222+
<span class="code-keyword">const</span> recommended = <span class="code-keyword">await</span> db
223+
.<span class="code-function">nodes</span>(<span class="code-string">'User'</span>, { id: userId })
224+
.<span class="code-function">follow</span>(<span class="code-string">'PURCHASED'</span>)
225+
.<span class="code-function">follow</span>(<span class="code-string">'-PURCHASED'</span>)
226+
.<span class="code-function">groupBy</span>(<span class="code-string">'id'</span>)
227+
.<span class="code-function">exec</span>();</code></pre>
228+
</div>
207229
</div>
208-
<div class="use-case">
230+
<div class="use-case" data-example="social">
209231
<div class="use-case-icon">👥</div>
210232
<h3>Social Networks</h3>
211233
<p>Friend graphs, follows, feeds. Scale to millions of connections effortlessly.</p>
212-
<a href="#" class="use-case-link">See example →</a>
234+
<div id="graph-social" class="graph-visualization"></div>
235+
<button class="animate-query-btn" data-target="social">▶ Run Query</button>
236+
<div class="code-snippet">
237+
<pre><code><span class="code-comment">// Friend-of-friend suggestions</span>
238+
<span class="code-keyword">const</span> suggestions = <span class="code-keyword">await</span> db
239+
.<span class="code-function">nodes</span>(<span class="code-string">'User'</span>, { id: <span class="code-string">'me'</span> })
240+
.<span class="code-function">follow</span>(<span class="code-string">'FRIENDS_WITH'</span>)
241+
.<span class="code-function">follow</span>(<span class="code-string">'FRIENDS_WITH'</span>)
242+
.<span class="code-function">except</span>(myFriends)
243+
.<span class="code-function">exec</span>();</code></pre>
244+
</div>
213245
</div>
214-
<div class="use-case">
246+
<div class="use-case" data-example="crm">
215247
<div class="use-case-icon">🎯</div>
216248
<h3>CRM & Sales</h3>
217249
<p>Company hierarchies, deal pipelines, contact networks. Track every relationship.</p>
218-
<a href="#" class="use-case-link">See example →</a>
250+
<div id="graph-crm" class="graph-visualization"></div>
251+
<button class="animate-query-btn" data-target="crm">▶ Run Query</button>
252+
<div class="code-snippet">
253+
<pre><code><span class="code-comment">// Find decision makers for deals</span>
254+
<span class="code-keyword">const</span> contacts = <span class="code-keyword">await</span> db
255+
.<span class="code-function">nodes</span>(<span class="code-string">'Deal'</span>, { stage: <span class="code-string">'negotiation'</span> })
256+
.<span class="code-function">follow</span>(<span class="code-string">'AT_COMPANY'</span>)
257+
.<span class="code-function">follow</span>(<span class="code-string">'-WORKS_AT'</span>)
258+
.<span class="code-function">where</span>({ role: <span class="code-string">'C-level'</span> })
259+
.<span class="code-function">exec</span>();</code></pre>
260+
</div>
219261
</div>
220262
</div>
221263
</div>
@@ -564,5 +606,6 @@ <h4>Company</h4>
564606
</footer>
565607

566608
<script src="js/main.js"></script>
609+
<script src="js/examples.js"></script>
567610
</body>
568611
</html>

js/examples.js

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// Vis.js Graph Visualizations for Use Case Examples
2+
3+
// Graph configurations for each use case
4+
const graphConfigs = {
5+
'job-search': {
6+
nodes: [
7+
{ id: 1, label: 'You', group: 'user', title: 'User Node' },
8+
{ id: 2, label: 'JavaScript', group: 'skill', title: 'Skill: JavaScript' },
9+
{ id: 3, label: 'React', group: 'skill', title: 'Skill: React' },
10+
{ id: 4, label: 'Frontend Dev', group: 'job', title: 'Job: Frontend Developer' },
11+
{ id: 5, label: 'Full-Stack Dev', group: 'job', title: 'Job: Full-Stack Developer' },
12+
{ id: 6, label: 'Startup Inc', group: 'company', title: 'Company: Startup Inc' },
13+
{ id: 7, label: 'Tech Corp', group: 'company', title: 'Company: Tech Corp' }
14+
],
15+
edges: [
16+
{ from: 1, to: 2, label: 'HAS_SKILL', color: { color: '#FF6B35' } },
17+
{ from: 1, to: 3, label: 'HAS_SKILL', color: { color: '#FF6B35' } },
18+
{ from: 4, to: 2, label: 'REQUIRES_SKILL', dashes: true },
19+
{ from: 4, to: 3, label: 'REQUIRES_SKILL', dashes: true },
20+
{ from: 5, to: 2, label: 'REQUIRES_SKILL', dashes: true },
21+
{ from: 4, to: 6, label: 'AT_COMPANY' },
22+
{ from: 5, to: 7, label: 'AT_COMPANY' }
23+
],
24+
queryPath: [1, 2, 4, 6]
25+
},
26+
'ecommerce': {
27+
nodes: [
28+
{ id: 1, label: 'Alice', group: 'user', title: 'User: Alice' },
29+
{ id: 2, label: 'Bob', group: 'user', title: 'User: Bob' },
30+
{ id: 3, label: 'Laptop', group: 'product', title: 'Product: Laptop' },
31+
{ id: 4, label: 'Mouse', group: 'product', title: 'Product: Mouse' },
32+
{ id: 5, label: 'Keyboard', group: 'product', title: 'Product: Keyboard' },
33+
{ id: 6, label: 'Monitor', group: 'product', title: 'Product: Monitor' }
34+
],
35+
edges: [
36+
{ from: 1, to: 3, label: 'PURCHASED', color: { color: '#FF6B35' } },
37+
{ from: 1, to: 4, label: 'PURCHASED', color: { color: '#FF6B35' } },
38+
{ from: 2, to: 3, label: 'PURCHASED', dashes: true },
39+
{ from: 2, to: 5, label: 'PURCHASED', dashes: true },
40+
{ from: 2, to: 6, label: 'PURCHASED', dashes: true }
41+
],
42+
queryPath: [1, 3, 2, 5]
43+
},
44+
'social': {
45+
nodes: [
46+
{ id: 1, label: 'You', group: 'user', title: 'User: You' },
47+
{ id: 2, label: 'Alice', group: 'friend', title: 'Friend: Alice' },
48+
{ id: 3, label: 'Bob', group: 'friend', title: 'Friend: Bob' },
49+
{ id: 4, label: 'Carol', group: 'fof', title: 'Friend-of-Friend: Carol' },
50+
{ id: 5, label: 'Dave', group: 'fof', title: 'Friend-of-Friend: Dave' },
51+
{ id: 6, label: 'Eve', group: 'fof', title: 'Friend-of-Friend: Eve' }
52+
],
53+
edges: [
54+
{ from: 1, to: 2, label: 'FRIENDS_WITH', color: { color: '#FF6B35' } },
55+
{ from: 1, to: 3, label: 'FRIENDS_WITH', color: { color: '#FF6B35' } },
56+
{ from: 2, to: 4, label: 'FRIENDS_WITH', dashes: true },
57+
{ from: 2, to: 5, label: 'FRIENDS_WITH', dashes: true },
58+
{ from: 3, to: 5, label: 'FRIENDS_WITH', dashes: true },
59+
{ from: 3, to: 6, label: 'FRIENDS_WITH', dashes: true }
60+
],
61+
queryPath: [1, 2, 4]
62+
},
63+
'crm': {
64+
nodes: [
65+
{ id: 1, label: 'Q1 Deal', group: 'deal', title: 'Deal: Q1 Contract' },
66+
{ id: 2, label: 'Acme Corp', group: 'company', title: 'Company: Acme Corp' },
67+
{ id: 3, label: 'CEO', group: 'contact', title: 'Contact: CEO' },
68+
{ id: 4, label: 'CTO', group: 'contact', title: 'Contact: CTO' },
69+
{ id: 5, label: 'CFO', group: 'contact', title: 'Contact: CFO' },
70+
{ id: 6, label: 'VP Sales', group: 'contact', title: 'Contact: VP Sales' }
71+
],
72+
edges: [
73+
{ from: 1, to: 2, label: 'AT_COMPANY', color: { color: '#FF6B35' } },
74+
{ from: 3, to: 2, label: 'WORKS_AT', dashes: true },
75+
{ from: 4, to: 2, label: 'WORKS_AT', dashes: true },
76+
{ from: 5, to: 2, label: 'WORKS_AT', dashes: true },
77+
{ from: 6, to: 2, label: 'WORKS_AT', dashes: true }
78+
],
79+
queryPath: [1, 2, 3]
80+
}
81+
};
82+
83+
// Vis.js styling options
84+
const graphOptions = {
85+
nodes: {
86+
shape: 'dot',
87+
size: 20,
88+
font: {
89+
size: 14,
90+
color: '#111827',
91+
face: 'Inter'
92+
},
93+
borderWidth: 2,
94+
borderWidthSelected: 3
95+
},
96+
edges: {
97+
width: 2,
98+
color: { color: '#9CA3AF', highlight: '#FF6B35' },
99+
arrows: { to: { enabled: true, scaleFactor: 0.5 } },
100+
font: { size: 10, color: '#6B7280', face: 'Inter', align: 'middle' },
101+
smooth: { type: 'continuous' }
102+
},
103+
groups: {
104+
user: { color: { background: '#FF6B35', border: '#E55A2B' } },
105+
skill: { color: { background: '#8BE9FD', border: '#6AC5D9' } },
106+
job: { color: { background: '#50FA7B', border: '#3DD968' } },
107+
company: { color: { background: '#FFB86C', border: '#E69A4E' } },
108+
friend: { color: { background: '#BD93F9', border: '#9B6FDB' } },
109+
fof: { color: { background: '#F1FA8C', border: '#D4DD6F' } },
110+
product: { color: { background: '#FF79C6', border: '#DB5CA8' } },
111+
deal: { color: { background: '#FF6B35', border: '#E55A2B' } },
112+
contact: { color: { background: '#50FA7B', border: '#3DD968' } }
113+
},
114+
physics: {
115+
enabled: true,
116+
stabilization: { iterations: 100 },
117+
barnesHut: {
118+
gravitationalConstant: -8000,
119+
centralGravity: 0.3,
120+
springLength: 150,
121+
springConstant: 0.04
122+
}
123+
},
124+
interaction: {
125+
hover: true,
126+
tooltipDelay: 100,
127+
zoomView: false,
128+
dragView: false
129+
}
130+
};
131+
132+
// Initialize all graphs
133+
const networks = {};
134+
135+
function initializeGraphs() {
136+
Object.keys(graphConfigs).forEach(exampleId => {
137+
const container = document.getElementById(`graph-${exampleId}`);
138+
if (!container) return;
139+
140+
const config = graphConfigs[exampleId];
141+
const data = {
142+
nodes: new vis.DataSet(config.nodes),
143+
edges: new vis.DataSet(config.edges)
144+
};
145+
146+
networks[exampleId] = new vis.Network(container, data, graphOptions);
147+
148+
// Disable physics after initial layout
149+
networks[exampleId].once('stabilizationIterationsDone', () => {
150+
networks[exampleId].setOptions({ physics: false });
151+
});
152+
});
153+
}
154+
155+
// Animate query execution
156+
function animateQuery(exampleId) {
157+
const network = networks[exampleId];
158+
const config = graphConfigs[exampleId];
159+
160+
if (!network || !config.queryPath) return;
161+
162+
const data = network.body.data;
163+
const allNodes = data.nodes.get();
164+
const allEdges = data.edges.get();
165+
166+
// Reset all nodes and edges to default
167+
allNodes.forEach(node => {
168+
data.nodes.update({
169+
id: node.id,
170+
color: undefined,
171+
borderWidth: 2
172+
});
173+
});
174+
175+
allEdges.forEach(edge => {
176+
data.edges.update({
177+
id: edge.id,
178+
color: { color: '#9CA3AF' },
179+
width: 2
180+
});
181+
});
182+
183+
// Animate the query path
184+
const path = config.queryPath;
185+
let step = 0;
186+
187+
function highlightNextStep() {
188+
if (step >= path.length) return;
189+
190+
const nodeId = path[step];
191+
192+
// Highlight current node
193+
data.nodes.update({
194+
id: nodeId,
195+
color: { background: '#FF6B35', border: '#E55A2B' },
196+
borderWidth: 4
197+
});
198+
199+
// Highlight edge to next node
200+
if (step < path.length - 1) {
201+
const nextNodeId = path[step + 1];
202+
const edge = allEdges.find(e =>
203+
(e.from === nodeId && e.to === nextNodeId) ||
204+
(e.to === nodeId && e.from === nextNodeId)
205+
);
206+
207+
if (edge) {
208+
data.edges.update({
209+
id: edge.id,
210+
color: { color: '#FF6B35' },
211+
width: 4
212+
});
213+
}
214+
}
215+
216+
// Focus on current node
217+
network.focus(nodeId, {
218+
scale: 1.2,
219+
animation: {
220+
duration: 500,
221+
easingFunction: 'easeInOutQuad'
222+
}
223+
});
224+
225+
step++;
226+
if (step < path.length) {
227+
setTimeout(highlightNextStep, 800);
228+
} else {
229+
// Final zoom out to show full graph
230+
setTimeout(() => {
231+
network.fit({
232+
animation: {
233+
duration: 1000,
234+
easingFunction: 'easeInOutQuad'
235+
}
236+
});
237+
}, 1000);
238+
}
239+
}
240+
241+
highlightNextStep();
242+
}
243+
244+
// Add click handlers to "Run Query" buttons
245+
document.addEventListener('DOMContentLoaded', () => {
246+
// Initialize graphs
247+
initializeGraphs();
248+
249+
// Add button handlers
250+
document.querySelectorAll('.animate-query-btn').forEach(btn => {
251+
btn.addEventListener('click', function() {
252+
const exampleId = this.getAttribute('data-target');
253+
animateQuery(exampleId);
254+
255+
// Update button text
256+
this.textContent = '⟳ Replay Query';
257+
setTimeout(() => {
258+
this.textContent = '▶ Run Query';
259+
}, 5000);
260+
});
261+
});
262+
263+
// Auto-play first visible graph
264+
const observer = new IntersectionObserver((entries) => {
265+
entries.forEach(entry => {
266+
if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
267+
const useCase = entry.target;
268+
const exampleId = useCase.getAttribute('data-example');
269+
270+
// Auto-play once when scrolled into view
271+
if (!useCase.dataset.played) {
272+
setTimeout(() => {
273+
animateQuery(exampleId);
274+
useCase.dataset.played = 'true';
275+
}, 500);
276+
}
277+
}
278+
});
279+
}, { threshold: 0.5 });
280+
281+
document.querySelectorAll('.use-case').forEach(useCase => {
282+
observer.observe(useCase);
283+
});
284+
});
285+
286+
// Handle window resize
287+
window.addEventListener('resize', () => {
288+
Object.values(networks).forEach(network => {
289+
network.fit();
290+
});
291+
});

0 commit comments

Comments
 (0)