<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="EdgeVec - High-performance WASM vector database examples and demos. Explore batch operations, soft delete, stress testing, and performance benchmarks.">
<meta name="keywords" content="EdgeVec, WASM, WebAssembly, vector database, HNSW, similarity search, browser database">
<meta name="author" content="EdgeVec Contributors">
<meta name="theme-color" content="#00ffff">
<!-- Open Graph / Social -->
<meta property="og:type" content="website">
<meta property="og:title" content="EdgeVec Examples - WASM Vector Database Demos">
<meta property="og:description" content="Interactive demos showcasing EdgeVec's high-performance vector search capabilities in the browser.">
<title>EdgeVec | Interactive Examples</title>
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect fill='%230a0a0f' width='100' height='100'/><polygon fill='%2300ffff' points='50,10 90,30 90,70 50,90 10,70 10,30'/><polygon fill='%230a0a0f' points='50,25 75,37 75,63 50,75 25,63 25,37'/><circle fill='%23ff00ff' cx='50' cy='50' r='8'/></svg>">
<style>
/* ═══════════════════════════════════════════════════════════════════════════
EDGEVEC EXAMPLES CATALOG - NVIDIA-GRADE UI
Version: 1.0.0 | Cyberpunk Theme
═══════════════════════════════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
/* ═══════════════════════════════════════════════════════════════════════════
CSS CUSTOM PROPERTIES
═══════════════════════════════════════════════════════════════════════════ */
:root {
/* Backgrounds */
--bg-void: #030306;
--bg-primary: #0a0a0f;
--bg-secondary: #0f0f18;
--bg-tertiary: #151520;
--bg-card: #12121c;
--bg-card-hover: #181824;
/* Neon Colors */
--cyan: #00ffff;
--cyan-bright: #40ffff;
--cyan-dim: #00aaaa;
--cyan-dark: #004455;
--magenta: #ff00ff;
--magenta-bright: #ff40ff;
--magenta-dim: #aa00aa;
--purple: #9945ff;
--purple-bright: #b370ff;
--yellow: #ffff00;
--yellow-dim: #aaaa00;
--green: #00ff88;
--green-bright: #40ffa0;
--red: #ff3366;
--orange: #ff6600;
/* Neutrals */
--white: #e8e8f0;
--gray-100: #b0b0c0;
--gray-200: #8080a0;
--gray-300: #606080;
--gray-400: #404060;
--border: #252535;
--border-glow: #353550;
/* Glows */
--glow-cyan: 0 0 30px rgba(0, 255, 255, 0.4), 0 0 60px rgba(0, 255, 255, 0.2);
--glow-magenta: 0 0 30px rgba(255, 0, 255, 0.4), 0 0 60px rgba(255, 0, 255, 0.2);
--glow-green: 0 0 30px rgba(0, 255, 136, 0.4);
--glow-purple: 0 0 30px rgba(153, 69, 255, 0.4);
/* Text Shadows */
--text-glow-cyan: 0 0 10px rgba(0, 255, 255, 0.8), 0 0 20px rgba(0, 255, 255, 0.4);
--text-glow-magenta: 0 0 10px rgba(255, 0, 255, 0.8), 0 0 20px rgba(255, 0, 255, 0.4);
}
/* ═══════════════════════════════════════════════════════════════════════════
RESET & BASE
═══════════════════════════════════════════════════════════════════════════ */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
font-size: 16px;
/* C4 FIX: Prevent iOS Safari horizontal scroll */
overflow-x: hidden;
}
body {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
background: var(--bg-void);
color: var(--white);
min-height: 100vh;
overflow-x: hidden;
line-height: 1.6;
}
a {
color: inherit;
text-decoration: none;
}
/* ═══════════════════════════════════════════════════════════════════════════
ANIMATED BACKGROUND
═══════════════════════════════════════════════════════════════════════════ */
.bg-grid {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
linear-gradient(90deg, rgba(0, 255, 255, 0.015) 1px, transparent 1px),
linear-gradient(rgba(0, 255, 255, 0.015) 1px, transparent 1px);
background-size: 80px 80px;
pointer-events: none;
z-index: 0;
animation: gridPulse 6s ease-in-out infinite;
}
@keyframes gridPulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.7; }
}
.bg-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(ellipse at 20% 20%, rgba(0, 255, 255, 0.08) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(255, 0, 255, 0.06) 0%, transparent 50%),
radial-gradient(ellipse at 50% 50%, rgba(153, 69, 255, 0.04) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
.scanline {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, transparent, var(--cyan), var(--magenta), transparent);
animation: scanDown 10s linear infinite;
pointer-events: none;
z-index: 1;
opacity: 0.25;
}
@keyframes scanDown {
0% { top: -3px; }
100% { top: 100vh; }
}
/* ═══════════════════════════════════════════════════════════════════════════
HEADER
═══════════════════════════════════════════════════════════════════════════ */
header {
position: relative;
z-index: 100;
background: linear-gradient(180deg, rgba(10, 10, 15, 0.95) 0%, rgba(10, 10, 15, 0.85) 100%);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 20px 40px;
display: flex;
justify-content: space-between;
align-items: center;
}
header::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--cyan), var(--magenta), transparent);
opacity: 0.6;
}
.logo {
display: flex;
align-items: center;
gap: 14px;
}
.logo-icon {
width: 48px;
height: 48px;
animation: logoFloat 4s ease-in-out infinite;
}
@keyframes logoFloat {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-4px) rotate(2deg); }
}
.logo-icon svg {
width: 100%;
height: 100%;
filter: drop-shadow(var(--glow-cyan));
}
.logo-text {
font-family: 'Orbitron', sans-serif;
font-size: 28px;
font-weight: 800;
background: linear-gradient(135deg, var(--cyan) 0%, var(--magenta) 50%, var(--cyan) 100%);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: 4px;
animation: gradientShift 4s ease-in-out infinite;
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.header-badge {
display: flex;
align-items: center;
gap: 10px;
}
.version-tag {
font-size: 10px;
font-weight: 600;
color: var(--cyan);
background: rgba(0, 255, 255, 0.1);
padding: 5px 12px;
border-radius: 20px;
border: 1px solid rgba(0, 255, 255, 0.3);
text-transform: uppercase;
letter-spacing: 1px;
}
.github-link {
display: flex;
align-items: center;
gap: 8px;
color: var(--gray-200);
font-size: 12px;
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: 8px;
transition: all 0.3s ease;
}
.github-link:hover {
color: var(--cyan);
border-color: var(--cyan);
background: rgba(0, 255, 255, 0.05);
}
.github-link svg {
width: 18px;
height: 18px;
fill: currentColor;
}
/* ═══════════════════════════════════════════════════════════════════════════
HERO SECTION
═══════════════════════════════════════════════════════════════════════════ */
.hero {
position: relative;
z-index: 10;
padding: 80px 40px 60px;
text-align: center;
}
.hero::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(0, 255, 255, 0.1) 0%, transparent 70%);
transform: translate(-50%, -50%);
pointer-events: none;
}
.hero-title {
font-family: 'Orbitron', sans-serif;
font-size: 52px;
font-weight: 900;
margin-bottom: 20px;
text-transform: uppercase;
letter-spacing: 6px;
background: linear-gradient(135deg, var(--white) 0%, var(--cyan) 50%, var(--magenta) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 18px;
color: var(--gray-100);
max-width: 700px;
margin: 0 auto 16px;
line-height: 1.7;
}
.hero-description {
font-size: 14px;
color: var(--gray-300);
max-width: 600px;
margin: 0 auto;
}
.hero-stats {
display: flex;
justify-content: center;
gap: 40px;
margin-top: 40px;
flex-wrap: wrap;
}
.hero-stat {
text-align: center;
}
.hero-stat-value {
font-family: 'Orbitron', sans-serif;
font-size: 32px;
font-weight: 700;
color: var(--cyan);
text-shadow: var(--text-glow-cyan);
}
.hero-stat-label {
font-size: 11px;
color: var(--gray-300);
text-transform: uppercase;
letter-spacing: 2px;
margin-top: 4px;
}
/* ═══════════════════════════════════════════════════════════════════════════
DEMO CARDS SECTION
═══════════════════════════════════════════════════════════════════════════ */
main {
position: relative;
z-index: 10;
max-width: 1200px;
margin: 0 auto;
padding: 0 40px 80px;
}
.section-header {
text-align: center;
margin-bottom: 50px;
}
.section-title {
font-family: 'Orbitron', sans-serif;
font-size: 14px;
font-weight: 600;
color: var(--magenta);
text-transform: uppercase;
letter-spacing: 4px;
margin-bottom: 12px;
}
.section-description {
font-size: 13px;
color: var(--gray-300);
}
.demos-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 28px;
}
.demo-card {
position: relative;
background: linear-gradient(135deg, var(--bg-card) 0%, rgba(18, 18, 28, 0.8) 100%);
border: 1px solid var(--border);
border-radius: 16px;
padding: 32px 28px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
display: flex;
flex-direction: column;
}
.demo-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--cyan), var(--magenta));
opacity: 0;
transition: opacity 0.3s ease;
}
.demo-card::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 50% 0%, rgba(0, 255, 255, 0.1) 0%, transparent 60%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.demo-card:hover {
transform: translateY(-8px);
border-color: var(--cyan);
box-shadow: var(--glow-cyan);
}
.demo-card:hover::before {
opacity: 1;
}
.demo-card:hover::after {
opacity: 1;
}
.demo-card-header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 20px;
}
.demo-icon {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 26px;
flex-shrink: 0;
position: relative;
}
.demo-icon.benchmark {
background: linear-gradient(135deg, rgba(0, 255, 255, 0.15), rgba(0, 255, 255, 0.05));
border: 1px solid rgba(0, 255, 255, 0.3);
}
.demo-icon.delete {
background: linear-gradient(135deg, rgba(255, 0, 255, 0.15), rgba(255, 0, 255, 0.05));
border: 1px solid rgba(255, 0, 255, 0.3);
}
.demo-icon.stress {
background: linear-gradient(135deg, rgba(255, 102, 0, 0.15), rgba(255, 102, 0, 0.05));
border: 1px solid rgba(255, 102, 0, 0.3);
}
.demo-icon.dashboard {
background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(0, 255, 136, 0.05));
border: 1px solid rgba(0, 255, 136, 0.3);
}
.demo-icon.batch {
background: linear-gradient(135deg, rgba(153, 69, 255, 0.15), rgba(153, 69, 255, 0.05));
border: 1px solid rgba(153, 69, 255, 0.3);
}
.demo-card-title {
font-family: 'Orbitron', sans-serif;
font-size: 18px;
font-weight: 700;
color: var(--white);
margin-bottom: 6px;
}
.demo-card-subtitle {
font-size: 11px;
color: var(--gray-300);
text-transform: uppercase;
letter-spacing: 1.5px;
}
.demo-card-description {
font-size: 14px;
color: var(--gray-200);
line-height: 1.7;
margin-bottom: 24px;
flex-grow: 1;
}
.demo-card-features {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 24px;
}
.feature-tag {
font-size: 10px;
color: var(--cyan);
background: rgba(0, 255, 255, 0.08);
padding: 5px 12px;
border-radius: 20px;
border: 1px solid rgba(0, 255, 255, 0.2);
text-transform: uppercase;
letter-spacing: 1px;
}
.demo-card-link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
background: rgba(0, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--cyan);
font-size: 13px;
font-weight: 600;
transition: all 0.3s ease;
/* C1 FIX: Ensure link is clickable above pseudo-elements */
position: relative;
z-index: 10;
cursor: pointer;
}
.demo-card-link:hover {
background: rgba(0, 255, 255, 0.12);
border-color: var(--cyan);
transform: translateX(4px);
}
.demo-card-link svg {
width: 20px;
height: 20px;
transition: transform 0.3s ease;
}
.demo-card-link:hover svg {
transform: translateX(4px);
}
/* Featured Card (Dashboard) */
.demo-card.featured {
grid-column: 1 / -1;
background: linear-gradient(135deg, rgba(0, 255, 136, 0.05) 0%, var(--bg-card) 50%, rgba(0, 255, 255, 0.05) 100%);
border-color: rgba(0, 255, 136, 0.3);
}
.demo-card.featured .demo-card-link {
background: rgba(0, 255, 136, 0.08);
color: var(--green);
border-color: rgba(0, 255, 136, 0.3);
}
.demo-card.featured .demo-card-link:hover {
background: rgba(0, 255, 136, 0.15);
border-color: var(--green);
}
.featured-badge {
position: absolute;
top: 16px;
right: 16px;
font-size: 9px;
font-weight: 700;
color: var(--bg-primary);
background: linear-gradient(135deg, var(--green), var(--cyan));
padding: 5px 12px;
border-radius: 20px;
text-transform: uppercase;
letter-spacing: 1px;
}
/* ═══════════════════════════════════════════════════════════════════════════
QUICK TRY SECTION - Interactive Search Demo with Filters
═══════════════════════════════════════════════════════════════════════════ */
.quick-try {
background: linear-gradient(135deg, rgba(0, 255, 255, 0.03) 0%, rgba(255, 0, 255, 0.02) 100%);
border: 1px solid var(--border);
border-radius: 20px;
padding: 40px;
margin-bottom: 60px;
position: relative;
overflow: hidden;
}
.quick-try::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--cyan), var(--magenta), var(--cyan));
background-size: 200% 100%;
animation: gradientMove 3s linear infinite;
}
@keyframes gradientMove {
0% { background-position: 0% 0%; }
100% { background-position: 200% 0%; }
}
.quick-try-header {
text-align: center;
margin-bottom: 32px;
}
.quick-try-title {
font-family: 'Orbitron', sans-serif;
font-size: 24px;
font-weight: 700;
color: var(--white);
margin-bottom: 8px;
}
.quick-try-subtitle {
font-size: 13px;
color: var(--gray-200);
}
.quick-try-controls {
display: grid;
grid-template-columns: 1fr auto 1fr auto;
gap: 16px;
align-items: end;
margin-bottom: 24px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.control-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--gray-200);
display: flex;
align-items: center;
gap: 8px;
}
.control-label .filter-help {
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--bg-tertiary);
border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: var(--cyan);
cursor: help;
transition: all 0.2s ease;
}
.control-label .filter-help:hover {
background: var(--cyan);
color: var(--bg-primary);
border-color: var(--cyan);
}
.control-input {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
color: var(--white);
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
transition: all 0.2s ease;
width: 100%;
}
.control-input:focus {
outline: none;
border-color: var(--cyan);
box-shadow: 0 0 0 3px rgba(0, 255, 255, 0.1);
}
.control-input::placeholder {
color: var(--gray-300);
}
.control-input.filter-input {
font-size: 12px;
}
.control-input.filter-input.valid {
border-color: var(--green);
}
.control-input.filter-input.invalid {
border-color: var(--red);
}
.btn-demo {
background: transparent;
border: 1px solid var(--cyan);
border-radius: 8px;
padding: 12px 24px;
color: var(--cyan);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
.btn-demo:hover:not(:disabled) {
background: var(--cyan);
color: var(--bg-primary);
box-shadow: var(--glow-cyan);
}
.btn-demo:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.btn-demo.loading {
position: relative;
color: transparent;
}
.btn-demo.loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
border: 2px solid transparent;
border-top-color: var(--cyan);
border-radius: 50%;
animation: btnSpin 0.8s linear infinite;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
@keyframes btnSpin {
to { transform: translate(-50%, -50%) rotate(360deg); }
}
.btn-load {
border-color: var(--magenta);
color: var(--magenta);
}
.btn-load:hover:not(:disabled) {
background: var(--magenta);
color: var(--bg-primary);
box-shadow: var(--glow-magenta);
}
.quick-try-results {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
min-height: 120px;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.results-title {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--gray-200);
display: flex;
align-items: center;
gap: 8px;
}
.filter-badge {
font-size: 9px;
padding: 3px 8px;
border-radius: 12px;
background: rgba(0, 255, 255, 0.15);
color: var(--cyan);
border: 1px solid rgba(0, 255, 255, 0.3);
display: none;
}
.filter-badge.active {
display: inline-flex;
align-items: center;
gap: 4px;
}
.results-stats {
font-size: 11px;
color: var(--gray-300);
display: flex;
gap: 16px;
}
.results-stats span {
display: flex;
align-items: center;
gap: 4px;
}
.results-stats .value {
color: var(--cyan);
font-weight: 600;
}
.results-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.result-item {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 16px;
background: var(--bg-tertiary);
border-radius: 8px;
border-left: 3px solid var(--cyan);
transition: all 0.2s ease;
}
.result-item:hover {
background: rgba(0, 255, 255, 0.05);
border-left-color: var(--magenta);
}
.result-rank {
font-family: 'Orbitron', sans-serif;
font-size: 14px;
font-weight: 700;
color: var(--cyan);
width: 32px;
}
.result-id {
font-size: 12px;
color: var(--white);
flex: 1;
}
.result-meta {
font-size: 10px;
color: var(--gray-300);
display: flex;
gap: 12px;
}
.result-meta .category {
color: var(--magenta);
}
.result-distance {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--green);
}
.results-empty {
text-align: center;
padding: 32px;
color: var(--gray-300);
font-size: 13px;
}
.results-empty-icon {
font-size: 32px;
margin-bottom: 12px;
opacity: 0.5;
}
.demo-status {
text-align: center;
padding: 8px;
font-size: 11px;
color: var(--gray-300);
border-top: 1px solid var(--border);
margin-top: 16px;
}
.demo-status.ready {
color: var(--green);
}
.demo-status.error {
color: var(--red);
}
/* Tooltip for filter help */
.tooltip {
position: relative;
}
.tooltip-content {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
font-size: 11px;
color: var(--gray-100);
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
z-index: 100;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.tooltip-content::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--border);
}
.tooltip:hover .tooltip-content {
opacity: 1;
visibility: visible;
}
/* Responsive Quick Try */
@media (max-width: 900px) {
.quick-try-controls {
grid-template-columns: 1fr;
gap: 12px;
}
.btn-demo {
width: 100%;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
QUICK START SECTION
═══════════════════════════════════════════════════════════════════════════ */
.quickstart {
margin-top: 60px;
padding: 40px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
}
.quickstart-title {
font-family: 'Orbitron', sans-serif;
font-size: 16px;
font-weight: 700;
color: var(--white);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.quickstart-title::before {
content: '';
width: 4px;
height: 20px;
background: linear-gradient(180deg, var(--cyan), var(--magenta));
border-radius: 2px;
}
.quickstart-code {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 10px;
padding: 20px 24px;
font-size: 13px;
color: var(--gray-100);
overflow-x: auto;
}
.quickstart-code code {
font-family: 'JetBrains Mono', monospace;
}
.quickstart-code .comment {
color: var(--gray-400);
}
.quickstart-code .command {
color: var(--cyan);
}
.quickstart-code .url {
color: var(--magenta);
}
/* ═══════════════════════════════════════════════════════════════════════════
FOOTER
═══════════════════════════════════════════════════════════════════════════ */
footer {
position: relative;
z-index: 10;
background: var(--bg-secondary);
border-top: 1px solid var(--border);
padding: 40px;
text-align: center;
}
.footer-content {
max-width: 800px;
margin: 0 auto;
}
.footer-logo {
font-family: 'Orbitron', sans-serif;
font-size: 20px;
font-weight: 700;
background: linear-gradient(135deg, var(--cyan), var(--magenta));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 16px;
}
.footer-links {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 24px;
margin-bottom: 20px;
}
.footer-link {
color: var(--gray-200);
font-size: 13px;
transition: color 0.3s ease;
}
.footer-link:hover {
color: var(--cyan);
}
.footer-copy {
font-size: 12px;
color: var(--gray-400);
}
.footer-copy a {
color: var(--gray-300);
transition: color 0.3s ease;
}
.footer-copy a:hover {
color: var(--cyan);
}
/* ═══════════════════════════════════════════════════════════════════════════
RESPONSIVE DESIGN
═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
.demos-grid {
grid-template-columns: 1fr;
}
.demo-card.featured {
grid-column: 1;
}
.hero-stats {
gap: 30px;
}
}
@media (max-width: 768px) {
header {
padding: 16px 20px;
flex-direction: column;
gap: 16px;
}
.header-badge {
width: 100%;
justify-content: center;
}
.hero {
padding: 50px 20px 40px;
}
.hero-title {
font-size: 32px;
letter-spacing: 3px;
}
.hero-subtitle {
font-size: 15px;
}
.hero-stats {
gap: 24px;
}
.hero-stat-value {
font-size: 26px;
}
main {
padding: 0 20px 60px;
}
.demos-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.demo-card {
padding: 24px 20px;
}
.quickstart {
padding: 24px 20px;
}
.footer-links {
flex-direction: column;
gap: 12px;
}
}
@media (max-width: 480px) {
.logo-text {
font-size: 22px;
letter-spacing: 2px;
}
.hero-title {
font-size: 26px;
}
.demo-card-header {
flex-direction: column;
align-items: flex-start;
}
.quickstart-code {
font-size: 11px;
padding: 16px;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
iOS SAFARI SPECIFIC FIXES (v0.5.4)
Fix horizontal overflow on small mobile screens
═══════════════════════════════════════════════════════════════════════════ */
@supports (-webkit-touch-callout: none) {
/* iOS Safari specific styles */
body {
/* Prevent horizontal scroll on iOS */
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
main {
/* Ensure main content doesn't overflow */
max-width: 100vw;
box-sizing: border-box;
}
.demos-grid {
/* Force single column on narrow iOS screens */
width: 100%;
max-width: 100%;
}
.demo-card {
/* Prevent card content from causing overflow */
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
}
.demo-card-description {
/* Ensure description text wraps properly */
word-wrap: break-word;
overflow-wrap: break-word;
}
.quickstart-code {
/* Allow horizontal scroll on code blocks only */
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Ensure touch targets are at least 44x44px for iOS */
.demo-card-link,
.btn-small,
.theme-toggle,
.nav-link {
min-height: 44px;
min-width: 44px;
}
}
/* Extra small screens (iPhone SE, etc) */
@media (max-width: 375px) {
main {
padding: 0 12px 40px;
}
.hero {
padding: 30px 12px;
}
.hero-title {
font-size: 22px;
letter-spacing: 1px;
}
.hero-subtitle {
font-size: 13px;
}
.hero-stats {
gap: 16px;
}
.hero-stat-value {
font-size: 22px;
}
.demo-card {
padding: 16px 14px;
}
.demo-card-title {
font-size: 16px;
}
.demo-card-description {
font-size: 13px;
}
.section-title {
font-size: 20px;
}
.quickstart {
padding: 16px 14px;
}
.quickstart-code {
font-size: 10px;
padding: 12px;
}
footer {
padding: 30px 12px;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
ACCESSIBILITY
═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
:focus-visible {
outline: 2px solid var(--cyan);
outline-offset: 2px;
}
/* Skip link for accessibility */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: var(--cyan);
color: var(--bg-primary);
padding: 8px 16px;
z-index: 1000;
font-weight: 600;
}
.skip-link:focus {
top: 0;
}
</style>
</head>
<body>
<!-- Animated Background -->
<div class="bg-grid" aria-hidden="true"></div>
<div class="bg-gradient" aria-hidden="true"></div>
<div class="scanline" aria-hidden="true"></div>
<!-- Skip Link for Accessibility -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Header -->
<header role="banner">
<div class="logo">
<div class="logo-icon" aria-hidden="true">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<polygon fill="#00ffff" points="50,10 90,30 90,70 50,90 10,70 10,30"/>
<polygon fill="#0a0a0f" points="50,25 75,37 75,63 50,75 25,63 25,37"/>
<circle fill="#ff00ff" cx="50" cy="50" r="8"/>
</svg>
</div>
<span class="logo-text">EDGEVEC</span>
</div>
<div class="header-badge">
<span class="version-tag">v0.7.0</span>
<a href="https://github.com/matte1782/edgevec" class="github-link" target="_blank" rel="noopener noreferrer" aria-label="View EdgeVec on GitHub">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
</svg>
GitHub
</a>
</div>
</header>
<!-- Hero Section -->
<section class="hero" aria-labelledby="hero-title">
<h1 id="hero-title" class="hero-title">Interactive Examples</h1>
<p class="hero-subtitle">
Explore EdgeVec's capabilities through interactive demos. From batch operations
to performance benchmarks, see our WASM-first vector database in action.
</p>
<p class="hero-description">
Each demo runs entirely in your browser using WebAssembly. No server required.
</p>
<div class="hero-stats" aria-label="EdgeVec performance highlights">
<div class="hero-stat">
<div class="hero-stat-value">24x</div>
<div class="hero-stat-label">Faster than voy</div>
</div>
<div class="hero-stat">
<div class="hero-stat-value"><250KB</div>
<div class="hero-stat-label">WASM Bundle</div>
</div>
<div class="hero-stat">
<div class="hero-stat-value">0.2ms</div>
<div class="hero-stat-label">Search P50</div>
</div>
<div class="hero-stat">
<div class="hero-stat-value">6</div>
<div class="hero-stat-label">Live Demos</div>
</div>
</div>
</section>
<!-- Main Content -->
<main id="main-content" role="main">
<!-- Quick Try - Interactive Search with Filters -->
<section class="quick-try" aria-labelledby="quick-try-title">
<div class="quick-try-header">
<h2 id="quick-try-title" class="quick-try-title">Quick Try: Filtered Search</h2>
<p class="quick-try-subtitle">Load sample data, apply a filter, and search - all in your browser</p>
</div>
<div class="quick-try-controls">
<div class="control-group">
<label class="control-label" for="vectorCount">Sample Vectors</label>
<select id="vectorCount" class="control-input">
<option value="100">100 vectors</option>
<option value="500" selected>500 vectors</option>
<option value="1000">1,000 vectors</option>
</select>
</div>
<button id="loadDataBtn" class="btn-demo btn-load" aria-label="Load sample data into EdgeVec">
Load Data
</button>
<div class="control-group">
<label class="control-label" for="filterInput">
Filter (optional)
<span class="tooltip filter-help" aria-label="Filter syntax help">
?
<span class="tooltip-content">
Examples: category = "tech" • price < 100 • rating >= 4.0
</span>
</span>
</label>
<input
type="text"
id="filterInput"
class="control-input filter-input"
placeholder='e.g., category = "tech" AND price < 100'
aria-describedby="filter-status"
>
</div>
<button id="searchBtn" class="btn-demo" disabled aria-label="Run filtered search">
Search
</button>
</div>
<div class="quick-try-results">
<div class="results-header">
<div class="results-title">
<span>Results</span>
<span id="filterBadge" class="filter-badge">🔍 Filtered</span>
</div>
<div class="results-stats">
<span>Vectors: <span id="statsVectors" class="value">0</span></span>
<span>Time: <span id="statsTime" class="value">-</span></span>
<span id="statsFiltered" style="display: none;">Matched: <span id="statsMatchCount" class="value">-</span></span>
</div>
</div>
<div id="resultsList" class="results-list">
<div class="results-empty">
<div class="results-empty-icon">🔍</div>
<div>Load sample data and click Search to see results</div>
</div>
</div>
<div id="demoStatus" class="demo-status">
WASM: <span id="wasmStatus">Loading...</span>
</div>
</div>
</section>
<div class="section-header">
<h2 class="section-title">Available Demos</h2>
<p class="section-description">Click any card to launch the interactive demo</p>
</div>
<div class="demos-grid">
<!-- Featured: Performance Dashboard -->
<article class="demo-card featured">
<span class="featured-badge">Featured</span>
<div class="demo-card-header">
<div class="demo-icon dashboard" aria-hidden="true">📊</div>
<div>
<h3 class="demo-card-title">Performance Dashboard</h3>
<span class="demo-card-subtitle">Competitive Analysis</span>
</div>
</div>
<p class="demo-card-description">
Interactive benchmark visualization comparing EdgeVec against hnswlib-node and voy.
See real performance data with charts, tables, and winner badges. Understand where
EdgeVec excels and the trade-offs of browser-based vector search.
</p>
<div class="demo-card-features">
<span class="feature-tag">Chart.js</span>
<span class="feature-tag">Real Data</span>
<span class="feature-tag">Comparison</span>
<span class="feature-tag">P50/P99</span>
</div>
<a href="benchmark-dashboard.html" class="demo-card-link" aria-label="Open Performance Dashboard demo">
<span>Launch Dashboard</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
<!-- Batch Insert -->
<article class="demo-card">
<div class="demo-card-header">
<div class="demo-icon benchmark" aria-hidden="true">⚡</div>
<div>
<h3 class="demo-card-title">Batch Insert</h3>
<span class="demo-card-subtitle">Performance Benchmark</span>
</div>
</div>
<p class="demo-card-description">
Test EdgeVec's bulk insertion capabilities. Insert thousands of vectors
and measure throughput, latency percentiles, and memory usage in real-time.
</p>
<div class="demo-card-features">
<span class="feature-tag">Throughput</span>
<span class="feature-tag">Latency</span>
<span class="feature-tag">Memory</span>
</div>
<a href="batch_insert.html" class="demo-card-link" aria-label="Open Batch Insert demo">
<span>Run Benchmark</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
<!-- Batch Delete -->
<article class="demo-card">
<div class="demo-card-header">
<div class="demo-icon batch" aria-hidden="true">🗑️</div>
<div>
<h3 class="demo-card-title">Batch Delete</h3>
<span class="demo-card-subtitle">Deletion Benchmark</span>
</div>
</div>
<p class="demo-card-description">
Measure batch deletion performance. Delete vectors in bulk and observe
how EdgeVec handles large-scale removal operations efficiently.
</p>
<div class="demo-card-features">
<span class="feature-tag">Bulk Delete</span>
<span class="feature-tag">Performance</span>
</div>
<a href="batch_delete.html" class="demo-card-link" aria-label="Open Batch Delete demo">
<span>Run Benchmark</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
<!-- Soft Delete -->
<article class="demo-card">
<div class="demo-card-header">
<div class="demo-icon delete" aria-hidden="true">🪦</div>
<div>
<h3 class="demo-card-title">Soft Delete & Compaction</h3>
<span class="demo-card-subtitle">RFC-001 Implementation</span>
</div>
</div>
<p class="demo-card-description">
Explore EdgeVec's tombstone-based soft delete system. Mark vectors as deleted
without immediate removal, then compact the index to reclaim space. Visualize
the compaction process in real-time.
</p>
<div class="demo-card-features">
<span class="feature-tag">Tombstones</span>
<span class="feature-tag">Compaction</span>
<span class="feature-tag">RFC-001</span>
</div>
<a href="soft_delete.html" class="demo-card-link" aria-label="Open Soft Delete demo">
<span>Explore Feature</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
<!-- Stress Test -->
<article class="demo-card">
<div class="demo-card-header">
<div class="demo-icon stress" aria-hidden="true">🔥</div>
<div>
<h3 class="demo-card-title">Stress Test</h3>
<span class="demo-card-subtitle">Stability Testing</span>
</div>
</div>
<p class="demo-card-description">
Push EdgeVec to its limits. Run continuous insert/search/delete operations
and monitor stability, memory usage, and performance degradation over time.
</p>
<div class="demo-card-features">
<span class="feature-tag">Stability</span>
<span class="feature-tag">Continuous</span>
<span class="feature-tag">Monitoring</span>
</div>
<a href="stress-test.html" class="demo-card-link" aria-label="Open Stress Test demo">
<span>Start Test</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
<!-- SIMD Benchmark (v0.7.0) -->
<article class="demo-card">
<div class="demo-card-header">
<div class="demo-icon simd" aria-hidden="true">⚡</div>
<div>
<h3 class="demo-card-title">SIMD Benchmark</h3>
<span class="demo-card-subtitle">v0.7.0 Performance Matrix</span>
</div>
</div>
<p class="demo-card-description">
Measure WASM SIMD128 acceleration in your browser. Compare vector operations
throughput, Hamming distance performance, and see 2x+ speedups on supported browsers.
Includes @jsonMartin's 8.75x faster Hamming distance contribution.
</p>
<div class="demo-card-features">
<span class="feature-tag">SIMD128</span>
<span class="feature-tag">2+ Gelem/s</span>
<span class="feature-tag">v0.7.0</span>
</div>
<a href="simd_benchmark.html" class="demo-card-link" aria-label="Open SIMD Benchmark">
<span>Run Benchmark</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
<!-- Filter Playground -->
<article class="demo-card">
<div class="demo-card-header">
<div class="demo-icon filter" aria-hidden="true">🔍</div>
<div>
<h3 class="demo-card-title">Filter Playground</h3>
<span class="demo-card-subtitle">v0.7.0 Metadata Filtering</span>
</div>
</div>
<p class="demo-card-description">
Interactive filter expression builder and validator. Test 15+ operators including
comparison, range, string matching, NULL checks, and complex boolean logic. Real-time
parsing with syntax highlighting and helpful error suggestions.
</p>
<div class="demo-card-features">
<span class="feature-tag">15 Operators</span>
<span class="feature-tag">Live Parse</span>
<span class="feature-tag">v0.7.0</span>
</div>
<a href="filter-playground.html" class="demo-card-link" aria-label="Open Filter Playground">
<span>Try Filters</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</article>
</div>
<!-- Quick Start -->
<section class="quickstart" aria-labelledby="quickstart-title">
<h2 id="quickstart-title" class="quickstart-title">Quick Start</h2>
<div class="quickstart-code">
<code>
<span class="comment"># Clone the repository</span><br>
<span class="command">git clone</span> <span class="url">https://github.com/matte1782/edgevec.git</span><br>
<span class="command">cd</span> edgevec<br><br>
<span class="comment"># IMPORTANT: Start server from PROJECT ROOT!</span><br>
<span class="command">python -m http.server</span> 8080<br><br>
<span class="comment"># Open in browser (include full path)</span><br>
<span class="url">http://localhost:8080/wasm/examples/index.html</span>
</code>
</div>
<p style="color: var(--text-muted); font-size: 0.85em; margin-top: 1em; text-align: center;">
⚠️ Do NOT start server from wasm/examples/ — WASM module requires /pkg/ access from root
</p>
</section>
</main>
<!-- Footer -->
<footer role="contentinfo">
<div class="footer-content">
<div class="footer-logo">EDGEVEC</div>
<nav class="footer-links" aria-label="Footer navigation">
<a href="https://github.com/matte1782/edgevec" class="footer-link">GitHub</a>
<a href="../../README.md" class="footer-link">Documentation</a>
<a href="../../docs/API_REFERENCE.md" class="footer-link">API Reference</a>
<a href="../../docs/BROWSER_COMPATIBILITY.md" class="footer-link">Browser Support</a>
<a href="../../CHANGELOG.md" class="footer-link">Changelog</a>
</nav>
<p class="footer-copy">
© 2025 EdgeVec. High-Performance WASM Vector Database.<br>
Licensed under <a href="../../LICENSE-MIT">MIT</a> or <a href="../../LICENSE-APACHE">Apache-2.0</a>.
</p>
</div>
</footer>
<!-- Quick Try Search Demo JavaScript -->
<script type="module">
// ═══════════════════════════════════════════════════════════════════════════
// QUICK TRY - Interactive Filtered Search Demo
// ═══════════════════════════════════════════════════════════════════════════
const WASM_PATHS = [
'../../pkg/edgevec.js',
'/pkg/edgevec.js',
'../pkg/edgevec.js'
];
// State
let wasmModule = null;
let index = null;
let metadataStore = [];
const dimension = 64;
// DOM elements
const loadDataBtn = document.getElementById('loadDataBtn');
const searchBtn = document.getElementById('searchBtn');
const filterInput = document.getElementById('filterInput');
const vectorCountSelect = document.getElementById('vectorCount');
const resultsList = document.getElementById('resultsList');
const filterBadge = document.getElementById('filterBadge');
const statsVectors = document.getElementById('statsVectors');
const statsTime = document.getElementById('statsTime');
const statsFiltered = document.getElementById('statsFiltered');
const statsMatchCount = document.getElementById('statsMatchCount');
const wasmStatus = document.getElementById('wasmStatus');
const demoStatus = document.getElementById('demoStatus');
// Sample data categories
const categories = ['tech', 'books', 'music', 'games', 'home'];
const sampleProducts = ['Laptop', 'Phone', 'Tablet', 'Headphones', 'Watch', 'Camera', 'Speaker', 'Mouse', 'Keyboard', 'Monitor'];
// ═══════════════════════════════════════════════════════════════════════════
// WASM Module Loading
// ═══════════════════════════════════════════════════════════════════════════
async function loadWasm() {
for (const path of WASM_PATHS) {
try {
wasmModule = await import(path);
await wasmModule.default();
wasmStatus.textContent = 'Ready';
wasmStatus.style.color = 'var(--green)';
demoStatus.classList.add('ready');
loadDataBtn.disabled = false;
console.log('[QuickTry] WASM loaded from:', path);
return true;
} catch (e) {
console.warn('[QuickTry] Failed to load from:', path, e.message);
}
}
wasmStatus.textContent = 'Error - serve from project root';
wasmStatus.style.color = 'var(--red)';
demoStatus.classList.add('error');
return false;
}
// ═══════════════════════════════════════════════════════════════════════════
// Data Generation
// ═══════════════════════════════════════════════════════════════════════════
function generateVector() {
const vec = new Float32Array(dimension);
for (let i = 0; i < dimension; i++) {
vec[i] = Math.random() * 2 - 1;
}
return vec;
}
function generateMetadata(id) {
const category = categories[Math.floor(Math.random() * categories.length)];
const product = sampleProducts[Math.floor(Math.random() * sampleProducts.length)];
return {
id,
name: `${product} ${id}`,
category,
price: Math.floor(Math.random() * 500) + 10,
rating: (Math.random() * 4 + 1).toFixed(1),
inStock: Math.random() > 0.3
};
}
// ═══════════════════════════════════════════════════════════════════════════
// Load Data Handler
// ═══════════════════════════════════════════════════════════════════════════
async function loadData() {
const count = parseInt(vectorCountSelect.value);
loadDataBtn.classList.add('loading');
loadDataBtn.disabled = true;
try {
// Create new index
const config = new wasmModule.EdgeVecConfig(dimension);
index = new wasmModule.EdgeVec(config);
metadataStore = [];
// Insert vectors with metadata
for (let i = 0; i < count; i++) {
const vector = generateVector();
const id = index.insert(vector);
const metadata = generateMetadata(id);
metadataStore.push(metadata);
}
statsVectors.textContent = count.toLocaleString();
searchBtn.disabled = false;
resultsList.innerHTML = `
<div class="results-empty">
<div class="results-empty-icon">✅</div>
<div>Loaded ${count.toLocaleString()} vectors. Try a filter like:<br>
<code style="color: var(--cyan); background: var(--bg-primary); padding: 2px 8px; border-radius: 4px; margin-top: 8px; display: inline-block;">category = "tech" AND price < 200</code></div>
</div>
`;
console.log('[QuickTry] Loaded', count, 'vectors');
} catch (e) {
console.error('[QuickTry] Load error:', e);
resultsList.innerHTML = `
<div class="results-empty" style="color: var(--red);">
<div class="results-empty-icon">❌</div>
<div>Error loading data: ${escapeHtml(e.message)}</div>
</div>
`;
} finally {
loadDataBtn.classList.remove('loading');
loadDataBtn.disabled = false;
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Filter Parsing (Client-side metadata filter)
// ═══════════════════════════════════════════════════════════════════════════
function parseFilter(filterStr) {
if (!filterStr || filterStr.trim() === '') return null;
// Simple client-side filter parser for demo
// Supports: field = "value", field < num, field > num, field >= num, field <= num
// AND/OR logical operators
return (metadata) => {
try {
let expr = filterStr.trim();
// Handle AND
if (expr.toLowerCase().includes(' and ')) {
const parts = expr.split(/\s+and\s+/i);
return parts.every(part => parseFilter(part.trim())(metadata));
}
// Handle OR
if (expr.toLowerCase().includes(' or ')) {
const parts = expr.split(/\s+or\s+/i);
return parts.some(part => parseFilter(part.trim())(metadata));
}
// Handle comparison operators
const match = expr.match(/^(\w+)\s*(=|!=|>=|<=|>|<)\s*(.+)$/);
if (!match) return true;
const [, field, op, rawValue] = match;
const fieldLower = field.toLowerCase();
// Get field value from metadata
let fieldValue = metadata[fieldLower];
if (fieldValue === undefined) {
// Try exact case match
fieldValue = metadata[field];
}
if (fieldValue === undefined) return false;
// Parse the comparison value
let compareValue = rawValue.trim();
if (compareValue.startsWith('"') && compareValue.endsWith('"')) {
compareValue = compareValue.slice(1, -1);
} else if (compareValue === 'true') {
compareValue = true;
} else if (compareValue === 'false') {
compareValue = false;
} else if (!isNaN(parseFloat(compareValue))) {
compareValue = parseFloat(compareValue);
}
// Perform comparison
switch (op) {
case '=': return String(fieldValue).toLowerCase() === String(compareValue).toLowerCase();
case '!=': return String(fieldValue).toLowerCase() !== String(compareValue).toLowerCase();
case '>': return parseFloat(fieldValue) > compareValue;
case '<': return parseFloat(fieldValue) < compareValue;
case '>=': return parseFloat(fieldValue) >= compareValue;
case '<=': return parseFloat(fieldValue) <= compareValue;
default: return true;
}
} catch (e) {
console.warn('[QuickTry] Filter parse error:', e);
return true;
}
};
}
// ═══════════════════════════════════════════════════════════════════════════
// Search Handler
// ═══════════════════════════════════════════════════════════════════════════
async function runSearch() {
if (!index) return;
searchBtn.classList.add('loading');
searchBtn.disabled = true;
try {
const filterStr = filterInput.value.trim();
const filterFn = parseFilter(filterStr);
const hasFilter = filterStr.length > 0;
// Update filter badge
if (hasFilter) {
filterBadge.classList.add('active');
statsFiltered.style.display = 'inline';
} else {
filterBadge.classList.remove('active');
statsFiltered.style.display = 'none';
}
// Generate random query vector
const queryVector = generateVector();
// Time the search
const startTime = performance.now();
const rawResults = index.search(queryVector, 50); // Get more results for filtering
const searchTime = performance.now() - startTime;
// Apply metadata filter
let results = rawResults.map(r => ({
...r,
metadata: metadataStore.find(m => m.id === r.id) || { id: r.id, name: `Vector ${r.id}` }
}));
if (filterFn) {
results = results.filter(r => filterFn(r.metadata));
}
// Limit to top 5
results = results.slice(0, 5);
// Update stats
statsTime.textContent = `${searchTime.toFixed(2)}ms`;
if (hasFilter) {
statsMatchCount.textContent = `${results.length}/${rawResults.length}`;
}
// Render results
if (results.length === 0) {
resultsList.innerHTML = `
<div class="results-empty">
<div class="results-empty-icon">🔍</div>
<div>No vectors match your filter. Try adjusting the criteria.</div>
</div>
`;
} else {
resultsList.innerHTML = results.map((r, i) => `
<div class="result-item">
<span class="result-rank">#${i + 1}</span>
<span class="result-id">${escapeHtml(r.metadata.name)}</span>
<span class="result-meta">
<span class="category">${escapeHtml(r.metadata.category || '-')}</span>
<span>$${r.metadata.price || '-'}</span>
<span>★${r.metadata.rating || '-'}</span>
</span>
<span class="result-distance">${r.score.toFixed(4)}</span>
</div>
`).join('');
}
// Validate filter input visual feedback
if (hasFilter && results.length > 0) {
filterInput.classList.remove('invalid');
filterInput.classList.add('valid');
} else if (hasFilter && results.length === 0) {
filterInput.classList.remove('valid');
filterInput.classList.add('invalid');
} else {
filterInput.classList.remove('valid', 'invalid');
}
} catch (e) {
console.error('[QuickTry] Search error:', e);
resultsList.innerHTML = `
<div class="results-empty" style="color: var(--red);">
<div class="results-empty-icon">❌</div>
<div>Search error: ${escapeHtml(e.message)}</div>
</div>
`;
} finally {
searchBtn.classList.remove('loading');
searchBtn.disabled = false;
}
}
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// ═══════════════════════════════════════════════════════════════════════════
// Event Listeners
// ═══════════════════════════════════════════════════════════════════════════
loadDataBtn.addEventListener('click', loadData);
searchBtn.addEventListener('click', runSearch);
// Enter key in filter input triggers search
filterInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !searchBtn.disabled) {
runSearch();
}
});
// Clear validation styling when typing
filterInput.addEventListener('input', () => {
filterInput.classList.remove('valid', 'invalid');
});
// Initialize
loadWasm();
</script>
</body>
</html>