<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DupeHell — Synthetic Data Generator for Record Linkage Benchmarking</title>
<link rel="icon" type="image/png" href="docs/assets/logo_dupehell-3.png">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0a0e17;
--surface:#131a2b;
--elevated:#1a2338;
--border:#1e2a45;
--cyan:#00d4aa;
--amber:#f0b429;
--blue:#4a8cff;
--text:#e2e8f0;
--muted:#8892a4;
--code:#a8b8d8;
--danger:#ef4444;
--success:#22d68c;
--radius:8px;
--ff:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;
--fm:"SF Mono","Fira Code","Cascadia Code","JetBrains Mono",Consolas,monospace;
}
html{scroll-behavior:smooth}
body{font-family:var(--ff);background:var(--bg);color:var(--text);line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:var(--blue);text-decoration:none}
a:hover{text-decoration:underline}
::selection{background:rgba(0,212,170,.3);color:#fff}
body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellipse 80% 60% at 50% -10%,rgba(0,212,170,.06) 0,transparent 70%),radial-gradient(ellipse 60% 50% at 80% 80%,rgba(74,140,255,.04) 0,transparent 60%),radial-gradient(ellipse 50% 40% at 20% 90%,rgba(240,180,41,.03) 0,transparent 50%);pointer-events:none;z-index:0}
.container{max-width:1200px;margin:0 auto;padding:0 24px;position:relative;z-index:1}
.reveal{opacity:0;transform:translateY(30px);transition:opacity .7s ease,transform .7s ease}
.reveal.visible{opacity:1;transform:translateY(0)}
.hero{min-height:100vh;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:40px 0;position:relative;text-align:center}
.hero-wordmark{position:relative;margin-bottom:8px}
.hero-logo{width:100%;max-width:600px;height:auto;filter:invert(1) drop-shadow(0 0 12px rgba(0,212,170,.25));display:block}
.hero-tagline{font-size:clamp(1rem,2vw,1.3rem);color:var(--muted);margin-bottom:32px;font-weight:400}
.hero-badges{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:36px;justify-content:center}
.hero-badge{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 24px;display:flex;flex-direction:column;align-items:center;min-width:140px;transition:border-color .3s,box-shadow .3s}
.hero-badge:hover{border-color:rgba(0,212,170,.3);box-shadow:0 0 25px rgba(0,212,170,.08)}
.hero-badge .num{font-family:var(--fm);font-size:1.8rem;font-weight:700;color:var(--cyan);line-height:1.2}
.hero-badge .lbl{font-size:.8rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}
.install-bar{display:flex;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:4px;max-width:520px;width:100%;margin:0 auto 24px}
.install-bar code{flex:1;padding:12px 16px;font-family:var(--fm);font-size:.9rem;color:var(--code);background:transparent;border:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.install-bar .copy-btn{background:var(--cyan);border:none;color:#0a0e17;font-family:var(--fm);font-size:.75rem;font-weight:600;padding:8px 16px;border-radius:6px;cursor:pointer;transition:background .2s,transform .15s;text-transform:uppercase;letter-spacing:.04em}
.install-bar .copy-btn:hover{background:#00e6b8;transform:scale(1.02)}
.install-bar .copy-btn:active{transform:scale(.97)}
.install-bar .copy-btn.copied{background:var(--success);color:#0a0e17}
.hero-ethics{position:absolute;bottom:24px;right:24px;font-size:.7rem;color:var(--muted);background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.2);padding:6px 14px;border-radius:20px;display:inline-flex;align-items:center;gap:6px}
.hero-ethics svg{width:12px;height:12px;fill:none;stroke:var(--danger);stroke-width:2}
.quickstart{padding:80px 0}
.quickstart h2{font-family:var(--fm);font-size:1.5rem;margin-bottom:8px;color:var(--text)}
.quickstart .sub{color:var(--muted);margin-bottom:32px;font-size:.95rem}
.qs-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.code-panel{background:#050810;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
.code-panel .panel-head{display:flex;align-items:center;gap:8px;padding:10px 16px;background:rgba(255,255,255,.03);border-bottom:1px solid var(--border)}
.code-panel .panel-head .dot{width:10px;height:10px;border-radius:50%;opacity:.6}
.code-panel .panel-head .dot.r{background:#ef4444}
.code-panel .panel-head .dot.y{background:var(--amber)}
.code-panel .panel-head .dot.g{background:var(--success)}
.code-panel .panel-head .label{font-family:var(--fm);font-size:.75rem;color:var(--muted);margin-left:auto;text-transform:uppercase;letter-spacing:.05em}
.code-panel pre{padding:20px;overflow-x:auto;font-family:var(--fm);font-size:.82rem;line-height:1.7;color:var(--code);tab-size:2}
.code-panel pre .kw{color:#c792ea}
.code-panel pre .fn{color:#82aaff}
.code-panel pre .str{color:#c3e88d}
.code-panel pre .cm{color:#546e7a;font-style:italic}
.code-panel pre .op{color:#89ddff}
.code-panel pre .prompt{color:var(--success);user-select:none}
.code-panel pre .flag{color:var(--amber)}
.benchmarks{padding:80px 0}
.benchmarks h2{font-family:var(--fm);font-size:1.8rem;margin-bottom:6px}
.benchmarks .sub{color:var(--muted);margin-bottom:20px;font-size:.9rem;max-width:600px}
.bench-top{margin-bottom:32px}
.chart-wrap{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px}
.chart-wrap h3{font-family:var(--fm);font-size:.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:16px}
.chart-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.chart-header h3{margin-bottom:0}
.chart-legend{display:flex;align-items:center;gap:12px;font-family:var(--fm);font-size:.7rem;color:var(--code)}
.chart-legend span{display:flex;align-items:center;gap:4px}
.chart-legend .dot{width:7px;height:7px;border-radius:2px}
.chart-wrap svg{width:100%;height:auto;display:block}
.table-wrap{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow-x:auto;margin-bottom:20px}
.table-wrap table{width:100%;border-collapse:collapse;font-size:.82rem;white-space:nowrap}
.table-wrap th{background:rgba(255,255,255,.03);padding:12px 14px;text-align:left;font-family:var(--fm);font-size:.7rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--border);font-weight:500}
.table-wrap td{padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.04);font-family:var(--fm);color:var(--code);font-size:.8rem}
.table-wrap tr:hover td{background:rgba(0,212,170,.03)}
.table-wrap td.highlight{color:var(--cyan);font-weight:600}
.callout-box{background:linear-gradient(135deg,rgba(0,212,170,.08),rgba(0,212,170,.02));border:1px solid rgba(0,212,170,.2);border-radius:var(--radius);padding:16px 20px;display:flex;align-items:center;gap:12px}
.callout-box .icon{font-size:1.2rem;flex-shrink:0}
.callout-box .txt{font-size:.85rem;color:var(--code)}
.callout-box .txt strong{color:var(--cyan)}
.domains{padding:80px 0}
.domains h2{font-family:var(--fm);font-size:1.5rem;margin-bottom:6px}
.domains .sub{color:var(--muted);margin-bottom:32px;font-size:.9rem}
.domain-grid{display:flex;flex-wrap:wrap;gap:8px}
.domain-pill{display:inline-block;padding:6px 16px;border-radius:20px;font-family:var(--fm);font-size:.78rem;background:var(--surface);border:1px solid var(--border);color:var(--code);transition:all .25s;cursor:default}
.domain-pill:hover{border-color:rgba(0,212,170,.3);box-shadow:0 0 18px rgba(0,212,170,.08);transform:translateY(-1px)}
.domain-pill.kyc{background:rgba(0,212,170,.12);border-color:var(--cyan);color:var(--cyan);font-weight:600}
.features{padding:80px 0}
.features h2{font-family:var(--fm);font-size:1.5rem;margin-bottom:6px}
.features .sub{color:var(--muted);margin-bottom:40px;font-size:.9rem}
.features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
.feat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;transition:border-color .3s,transform .3s}
.feat-card:hover{border-color:rgba(0,212,170,.2);transform:translateY(-3px)}
.feat-card .icon-wrap{width:40px;height:40px;border-radius:8px;background:rgba(0,212,170,.1);display:flex;align-items:center;justify-content:center;margin-bottom:14px}
.feat-card .icon-wrap svg{width:20px;height:20px;stroke:var(--cyan);fill:none;stroke-width:1.8}
.feat-card h3{font-size:.95rem;font-weight:600;margin-bottom:6px;color:var(--text)}
.feat-card p{font-size:.82rem;color:var(--muted);line-height:1.5}
.ethics-banner{padding:60px 0}
.ethics-banner .inner{background:rgba(239,68,68,.06);border-left:4px solid var(--danger);border-radius:var(--radius);padding:24px 32px;display:flex;align-items:center;gap:14px}
.ethics-banner .inner svg{width:20px;height:20px;fill:none;stroke:var(--danger);stroke-width:2;flex-shrink:0}
.ethics-banner .inner .txt{font-size:.9rem;color:var(--text)}
.ethics-banner .inner .txt strong{color:var(--danger)}
.ethics-banner .inner .txt a{color:var(--muted);text-decoration:underline}
.footer{padding:48px 0;border-top:1px solid var(--border)}
.footer-grid{display:grid;grid-template-columns:2fr 1fr 2fr;gap:32px}
.footer h4{font-family:var(--fm);font-size:.8rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}
.footer-links{list-style:none}
.footer-links li{margin-bottom:6px}
.footer-links a{font-size:.85rem;color:var(--muted);transition:color .2s}
.footer-links a:hover{color:var(--cyan);text-decoration:none}
.footer .bibtex{background:#050810;border:1px solid var(--border);border-radius:6px;padding:14px 16px;font-family:var(--fm);font-size:.72rem;color:var(--code);line-height:1.6;overflow-x:auto;white-space:pre}
.footer .copyright{font-size:.78rem;color:var(--muted);margin-top:24px}
@media(max-width:1024px){
.features-grid{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:768px){
.qs-grid{grid-template-columns:1fr}
.hero-badges{gap:8px}
.hero-badge{padding:12px 16px;min-width:100px}
.hero-badge .num{font-size:1.3rem}
.features-grid{grid-template-columns:repeat(2,1fr)}
.footer-grid{grid-template-columns:1fr 1fr}
.domain-grid{justify-content:center}
.chart-wrap{padding:16px}
}
@media(max-width:480px){
.container{padding:0 16px}
.hero-badge{min-width:85px;padding:10px 12px}
.hero-badge .num{font-size:1.1rem}
.features-grid{grid-template-columns:1fr}
.footer-grid{grid-template-columns:1fr}
.table-wrap{font-size:.72rem}
.table-wrap td,.table-wrap th{padding:8px 10px}
}
</style>
</head>
<body>
<section class="hero">
<div class="container">
<div class="hero-wordmark">
<img src="docs/assets/logo_dupehell-3.png" alt="DupeHell" class="hero-logo">
</div>
<p class="hero-tagline">Synthetic data generator for record linkage benchmarking</p>
<div class="install-bar">
<code>pip install dupehell</code>
<button class="copy-btn" data-copy="pip install dupehell">Copy</button>
</div>
</div>
</section>
<section class="quickstart" id="quickstart">
<div class="container reveal">
<h2>Quick Start</h2>
<p class="sub">Generate a dataset in two lines of Python or one CLI command.</p>
<div class="qs-grid">
<div class="code-panel">
<div class="panel-head">
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
<span class="label">Python</span>
</div>
<pre><span class="kw">from</span> dupehell <span class="kw">import</span> <span class="fn">generate</span>
<span class="fn">r</span> = <span class="fn">generate</span>(
<span class="flag">domain</span>=<span class="str">"publishing"</span>,
<span class="flag">size</span>=<span class="num">10000</span>,
<span class="flag">seed</span>=<span class="num">42</span>,
<span class="flag">difficulty</span>=<span class="str">"hell"</span>
)
<span class="fn">print</span>(<span class="fn">r</span>.dataset)
<span class="fn">print</span>(<span class="fn">r</span>.ground_truth)
<span class="fn">print</span>(<span class="fn">r</span>.total_records)</pre>
</div>
<div class="code-panel">
<div class="panel-head">
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
<span class="label">CLI</span>
</div>
<pre><span class="prompt">$</span> cargo run --release -- \
<span class="flag">--domain</span> kyc \
<span class="flag">--size</span> <span class="num">100000</span> \
<span class="flag">--seed</span> <span class="num">42</span></pre>
</div>
</div>
</div>
</section>
<section class="benchmarks" id="benchmarks">
<div class="container reveal">
<h2>Performance Benchmarks</h2>
<p class="sub">All runs on Intel Core i7-13850HX (20C/28T), 32 GB DDR5. Identical seed across formats.</p>
<div class="bench-top">
<div class="chart-wrap">
<div class="chart-header">
<h3>Throughput vs Dataset Size (hell difficulty)</h3>
<div class="chart-legend">
<span><span class="dot" style="background:#00d4aa"></span>IPC</span>
</div>
</div>
<svg viewBox="0 0 650 160" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="ipcGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#00d4aa" stop-opacity=".15"/>
<stop offset="100%" stop-color="#00d4aa" stop-opacity="0"/>
</linearGradient>
<linearGradient id="parquetGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f0b429" stop-opacity=".12"/>
<stop offset="100%" stop-color="#f0b429" stop-opacity="0"/>
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<line x1="70" y1="20" x2="620" y2="20" stroke="#1e2a45" stroke-width="1"/>
<line x1="70" y1="50" x2="620" y2="50" stroke="#1e2a45" stroke-width="1"/>
<line x1="70" y1="80" x2="620" y2="80" stroke="#1e2a45" stroke-width="1"/>
<line x1="70" y1="110" x2="620" y2="110" stroke="#1e2a45" stroke-width="1"/>
<line x1="70" y1="140" x2="620" y2="140" stroke="#1e2a45" stroke-width="1"/>
<text x="62" y="24" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="end">800K</text>
<text x="62" y="54" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="end">600K</text>
<text x="62" y="84" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="end">400K</text>
<text x="62" y="114" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="end">200K</text>
<text x="62" y="144" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="end">0</text>
<text x="70" y="158" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="middle">1M</text>
<text x="253" y="158" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="middle">10M</text>
<text x="437" y="158" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="middle">20M</text>
<text x="620" y="158" fill="#8892a4" font-family="SF Mono,Fira Code,monospace" font-size="8" text-anchor="middle">50M</text>
<polygon points="70,39.7 253,36.2 437,38.3 620,45.0 620,140 70,140" fill="url(#ipcGrad)"/>
<polyline points="70,39.7 253,36.2 437,38.3 620,45.0" fill="none" stroke="#00d4aa" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" filter="url(#glow)"/>
<circle cx="70" cy="39.7" r="4.5" fill="#00d4aa" stroke="#0a0e17" stroke-width="2"/>
<circle cx="253" cy="36.2" r="4.5" fill="#00d4aa" stroke="#0a0e17" stroke-width="2"/>
<circle cx="437" cy="38.3" r="4.5" fill="#00d4aa" stroke="#0a0e17" stroke-width="2"/>
<circle cx="620" cy="45.0" r="4.5" fill="#00d4aa" stroke="#0a0e17" stroke-width="2"/>
<text x="70" y="33" fill="#00d4aa" font-family="SF Mono,Fira Code,monospace" font-size="7" text-anchor="middle">695.5K</text>
<text x="253" y="30" fill="#00d4aa" font-family="SF Mono,Fira Code,monospace" font-size="7" text-anchor="middle">718.8K</text>
<text x="437" y="32" fill="#00d4aa" font-family="SF Mono,Fira Code,monospace" font-size="7" text-anchor="middle">704.9K</text>
<text x="620" y="39" fill="#00d4aa" font-family="SF Mono,Fira Code,monospace" font-size="7" text-anchor="middle">660.0K</text>
</svg>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>Size</th><th>Ø rec/s</th><th>Fastest domain</th><th>Slowest domain</th><th>Range</th></tr>
</thead>
<tbody>
<tr><td>1M</td><td>695,476</td><td>renewable_energy 1.0s</td><td>kyc 3.2s</td><td>2.2s</td></tr>
<tr><td>10M</td><td>718,822</td><td>renewable_energy 9.6s</td><td>kyc 34.7s</td><td>25.1s</td></tr>
<tr><td>20M</td><td>704,888</td><td>renewable_energy 20.1s</td><td>kyc 72.6s</td><td>52.5s</td></tr>
<tr><td>50M</td><td>659,987</td><td>social_media 56.4s</td><td>kyc 165.2s</td><td>108.8s</td></tr>
</tbody>
</table>
</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>Size</th><th>IPC Ø rec/s</th><th>Parquet Ø rec/s</th><th>Overhead</th></tr>
</thead>
<tbody>
<tr><td>1M</td><td>695,476</td><td>650,883</td><td>-6.4%</td></tr>
<tr><td>5M</td><td>734,773</td><td>705,807</td><td>-3.9%</td></tr>
<tr><td>10M</td><td>718,822</td><td>700,238</td><td>-2.6%</td></tr>
<tr><td>20M</td><td>704,888</td><td>690,241</td><td>-2.1%</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="domains" id="domains">
<div class="container reveal">
<h2>40 Domains</h2>
<p class="sub">Specialized schemas for entity resolution across industries and use cases.</p>
<div class="domain-grid">
<span class="domain-pill kyc">kyc</span>
<span class="domain-pill">healthcare</span>
<span class="domain-pill">ecommerce</span>
<span class="domain-pill">fintech</span>
<span class="domain-pill">publishing</span>
<span class="domain-pill">gaming</span>
<span class="domain-pill">banking</span>
<span class="domain-pill">telecom</span>
<span class="domain-pill">insurance</span>
<span class="domain-pill">supplychain</span>
<span class="domain-pill" style="color:var(--muted);border-color:var(--muted)">and 30+ more</span>
</div>
</div>
</section>
<section class="features" id="features">
<div class="container reveal">
<h2>Features</h2>
<p class="sub">What DupeHell brings to entity-resolution benchmarking.</p>
<div class="features-grid">
<div class="feat-card">
<div class="icon-wrap">
<svg viewBox="0 0 24 24"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
</div>
<h3>Noise Engine</h3>
<p>9 categories, 35+ noise types — typos, OCR errors, homoglyphs, date swaps, phonetic variants, Unicode pollution.</p>
</div>
<div class="feat-card">
<div class="icon-wrap">
<svg viewBox="0 0 24 24"><path d="M12 2L2 12l10 10 10-10L12 2z"/><path d="M8 12l2 2 4-4"/></svg>
</div>
<h3>Hard Negatives</h3>
<p>8 adversarial patterns — same_field, mix_identifier, same_email, mix_arrays, and more — for robust evaluation.</p>
</div>
<div class="feat-card">
<div class="icon-wrap">
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
</div>
<h3>40 Domains</h3>
<p>KYC, healthcare, ecommerce, gaming, fintech, publishing, and 34 more — each with a purpose-built schema.</p>
</div>
<div class="feat-card">
<div class="icon-wrap">
<svg viewBox="0 0 24 24"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/></svg>
</div>
<h3>Dual Format</h3>
<p>Arrow IPC for maximum throughput or Parquet ZSTD(3) for compact storage. Same data, identical F1.</p>
</div>
<div class="feat-card">
<div class="icon-wrap">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
</div>
<h3>Deterministic</h3>
<p>Same seed + domain + difficulty = byte-identical output every time. Perfect for reproducible research.</p>
</div>
<div class="feat-card">
<div class="icon-wrap">
<svg viewBox="0 0 24 24"><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="12" cy="18" r="3"/><line x1="6" y1="9" x2="12" y2="15"/><line x1="18" y1="9" x2="12" y2="15"/><line x1="6" y1="6" x2="18" y2="6"/></svg>
</div>
<h3>Graph Output</h3>
<p>Optional property graph (nodes + typed fk/exact_dup/fuzzy_dup/hard_neg edges) via --graph, for graph-based ER and community detection benchmarks.</p>
</div>
</div>
</div>
</section>
<section class="ethics-banner" id="ethics">
<div class="container reveal">
<div class="inner">
<svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><line x1="12" y1="8" x2="12" y2="13"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<span class="txt"><strong>Educational & Research Use Only</strong> — This tool is designed for benchmarking and educational purposes. See <a href="ETHICS.md">ETHICS.md</a> for prohibited uses and licensing restrictions.</span>
</div>
</div>
</section>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div>
<h4>DupeHell</h4>
<p style="color:var(--muted);font-size:.82rem;line-height:1.6;margin-bottom:12px">Synthetic multi-domain dataset generator for record linkage and entity resolution benchmarking.</p>
<p style="color:var(--muted);font-size:.78rem">© 2026 · MIT License</p>
</div>
<div>
<h4>Links</h4>
<ul class="footer-links">
<li><a href="https://github.com/vntoinekaio/DupeHell" target="_blank">GitHub</a></li>
<li><a href="docs/API.md">API Docs</a></li>
<li><a href="docs/GETTING_STARTED.md">Getting Started</a></li>
</ul>
</div>
<div>
<h4>Citation</h4>
<div class="bibtex">@misc{dupehell2026,
title = {DupeHell: Synthetic Multi-Domain
Data for Entity Resolution},
author = {DupeHell Contributors},
year = {2026},
url = {https://github.com/vntoinekaio/DupeHell}
}</div>
</div>
</div>
</div>
</footer>
<script>
(function(){
'use strict';
var copyBtn = document.querySelector('.copy-btn');
if (copyBtn) {
copyBtn.addEventListener('click', function() {
var text = this.getAttribute('data-copy');
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
flashCopied(copyBtn);
}).catch(function() {
fallbackCopy(text, copyBtn);
});
} else {
fallbackCopy(text, copyBtn);
}
});
}
function fallbackCopy(text, btn) {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
try {
document.execCommand('copy');
flashCopied(btn);
} catch(e) {}
document.body.removeChild(ta);
}
function flashCopied(btn) {
btn.textContent = 'Copied!';
btn.classList.add('copied');
setTimeout(function() {
btn.textContent = 'Copy';
btn.classList.remove('copied');
}, 2000);
}
var reveals = document.querySelectorAll('.reveal');
if ('IntersectionObserver' in window) {
var ro = new IntersectionObserver(function(entries) {
entries.forEach(function(e) {
if (e.isIntersecting) {
e.target.classList.add('visible');
ro.unobserve(e.target);
}
});
}, { threshold: 0.1 });
reveals.forEach(function(el) { ro.observe(el); });
} else {
reveals.forEach(function(el) { el.classList.add('visible'); });
}
})();
</script>
</body>
</html>