vault1984-web/index.html

515 lines
33 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vault1984 — AI-native password manager</title>
<meta name="description" content="Field-level encryption for password managers that live alongside AI assistants. Your AI gets what it needs. Your secrets stay yours.">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/vault1984.css">
<style>
.hero-grid { display:grid; grid-template-columns:1fr 1fr; gap:4rem; align-items:center; }
.hero-grid svg { max-width:480px; width:100%; }
.feature-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:1.25rem; }
.feature-card { border-radius:var(--radius); border:1px solid var(--border); padding:2rem; background:rgba(100,140,200,0.08); transition:transform 0.2s,box-shadow 0.2s; }
.feature-card:hover { transform:translateY(-2px); box-shadow:0 8px 24px rgba(0,0,0,0.3); }
.feature-card h3 { margin-bottom:0.75rem; }
.tier-card { border-radius:var(--radius); border:1px solid; padding:2rem; }
.tier-card.agent { border-color:rgba(34,197,94,0.3); background:rgba(34,197,94,0.05); }
.tier-card.sealed { border-color:rgba(239,68,68,0.3); background:rgba(239,68,68,0.05); }
.tier-label { display:inline-flex; align-items:center; gap:0.5rem; font-family:var(--font-mono); font-size:0.75rem; font-weight:600; padding:0.25rem 0.625rem; border-radius:9999px; margin-bottom:1.25rem; }
.tier-label.agent { background:rgba(34,197,94,0.1); color:var(--accent); }
.tier-label.sealed{ background:rgba(239,68,68,0.1); color:var(--red); }
.swarm-section { background:rgba(100,140,200,0.04); border-top:1px solid var(--border); border-bottom:1px solid var(--border); }
.code-inline { background:rgba(0,0,0,0.35); border:1px solid var(--border); border-radius:var(--radius); padding:1.25rem; font-family:var(--font-mono); font-size:0.8125rem; overflow-x:auto; line-height:1.7; }
</style>
</head>
<body>
<nav class="nav">
<div class="nav-inner">
<a href="/" class="nav-logo">vault<span class="n">1984</span></a>
<div class="nav-links">
<a href="https://github.com/johanjongsma/vault1984" target="_blank" rel="noopener" class="nav-link">GitHub</a>
<a href="/hosted.html" class="nav-link">Hosted</a>
<a href="/install.html" class="nav-link">Self-host</a>
<a href="#" class="nav-link btn btn-ghost">Sign in</a>
<a href="/hosted.html" class="btn btn-primary">Get hosted &mdash; $12/yr</a>
</div>
</div>
</nav>
<!-- Hero -->
<div class="section container">
<div class="hero-grid">
<div>
<p class="label accent mb-6">George Orwell &mdash; 1984</p>
<h1 class="mb-6">"If you want to keep a secret, you must also hide it from yourself."</h1>
<p class="lead mb-6">We did. Your Sealed key is derived in your browser from your Touch ID. Our servers have never seen it. They could not decrypt your private fields even if they wanted to. Or anybody else.</p>
<div style="display:flex;flex-wrap:wrap;gap:1rem">
<a href="/hosted.html" class="btn btn-primary">Get hosted &mdash; $12/yr</a>
<a href="/install.html" class="btn btn-ghost">Self-host free &rarr;</a>
</div>
</div>
<div style="display:flex;justify-content:center">
<!-- Hero SVG: L1/L2 split diagram -->
<div class="flex justify-center">
<svg viewBox="0 0 480 380" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-full max-w-md">
<!-- Background -->
<rect x="0" y="0" width="480" height="380" rx="12" fill="#111f38"/>
<!-- Top labels -->
<text x="130" y="35" font-family="JetBrains Mono, monospace" font-size="11" fill="#94a3b8" text-anchor="middle">AI Agent</text>
<text x="350" y="35" font-family="JetBrains Mono, monospace" font-size="11" fill="#94a3b8" text-anchor="middle">You only</text>
<!-- Arrows -->
<path d="M130 42 L130 58" stroke="#22C55E" stroke-width="1.5" marker-end="url(#arrowGreen)"/>
<path d="M350 42 L350 58" stroke="#EF4444" stroke-width="1.5" marker-end="url(#arrowRed)"/>
<defs>
<marker id="arrowGreen" markerWidth="8" markerHeight="6" refX="4" refY="3" orient="auto"><path d="M0,0 L4,3 L0,6" fill="none" stroke="#22C55E" stroke-width="1.5"/></marker>
<marker id="arrowRed" markerWidth="8" markerHeight="6" refX="4" refY="3" orient="auto"><path d="M0,0 L4,3 L0,6" fill="none" stroke="#EF4444" stroke-width="1.5"/></marker>
</defs>
<!-- L1 Column -->
<rect x="30" y="65" width="200" height="260" rx="8" fill="none" stroke="#22C55E" stroke-width="1" stroke-opacity="0.3"/>
<rect x="30" y="65" width="200" height="30" rx="8" fill="#22C55E" fill-opacity="0.1"/>
<text x="130" y="85" font-family="JetBrains Mono, monospace" font-size="12" fill="#22C55E" text-anchor="middle" font-weight="600">L1 — AI can read</text>
<!-- L1 items -->
<g>
<rect x="50" y="115" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="80" y="138" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">github_token</text>
<circle cx="192" cy="133" r="8" fill="#22C55E" fill-opacity="0.15"/>
<path d="M188 133 L190.5 135.5 L196 130" stroke="#22C55E" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g>
<rect x="50" y="163" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="80" y="186" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">ssh_key</text>
<circle cx="192" cy="181" r="8" fill="#22C55E" fill-opacity="0.15"/>
<path d="M188 181 L190.5 183.5 L196 178" stroke="#22C55E" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g>
<rect x="50" y="211" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="80" y="234" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">totp_github</text>
<circle cx="192" cy="229" r="8" fill="#22C55E" fill-opacity="0.15"/>
<path d="M188 229 L190.5 231.5 L196 226" stroke="#22C55E" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g>
<rect x="50" y="259" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="80" y="282" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">oauth_slack</text>
<circle cx="192" cy="277" r="8" fill="#22C55E" fill-opacity="0.15"/>
<path d="M188 277 L190.5 279.5 L196 274" stroke="#22C55E" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<!-- L2 Column -->
<rect x="250" y="65" width="200" height="260" rx="8" fill="none" stroke="#EF4444" stroke-width="1" stroke-opacity="0.3"/>
<rect x="250" y="65" width="200" height="30" rx="8" fill="#EF4444" fill-opacity="0.1"/>
<text x="350" y="85" font-family="JetBrains Mono, monospace" font-size="12" fill="#EF4444" text-anchor="middle" font-weight="600">L2 — you only</text>
<!-- L2 items -->
<g>
<rect x="270" y="115" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="300" y="138" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">credit_card</text>
<rect x="408" y="125" width="16" height="16" rx="3" fill="#EF4444" fill-opacity="0.15"/>
<path d="M413 131 L413 135 M416 131 L416 135 M411 133 L411 129 Q411 127 413 127 L416 127 Q418 127 418 129 L418 133 Z" stroke="#EF4444" stroke-width="1.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g>
<rect x="270" y="163" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="300" y="186" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">cvv</text>
<rect x="408" y="173" width="16" height="16" rx="3" fill="#EF4444" fill-opacity="0.15"/>
<path d="M413 179 L413 183 M416 179 L416 183 M411 181 L411 177 Q411 175 413 175 L416 175 Q418 175 418 177 L418 181 Z" stroke="#EF4444" stroke-width="1.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g>
<rect x="270" y="211" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="300" y="234" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">passport</text>
<rect x="408" y="221" width="16" height="16" rx="3" fill="#EF4444" fill-opacity="0.15"/>
<path d="M413 227 L413 231 M416 227 L416 231 M411 229 L411 225 Q411 223 413 223 L416 223 Q418 223 418 225 L418 229 Z" stroke="#EF4444" stroke-width="1.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g>
<rect x="270" y="259" width="160" height="36" rx="6" fill="#0A1628"/>
<text x="300" y="282" font-family="JetBrains Mono, monospace" font-size="11" fill="#d1d5db">ssn</text>
<rect x="408" y="269" width="16" height="16" rx="3" fill="#EF4444" fill-opacity="0.15"/>
<path d="M413 275 L413 279 M416 275 L416 279 M411 277 L411 273 Q411 271 413 271 L416 271 Q418 271 418 273 L418 277 Z" stroke="#EF4444" stroke-width="1.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<!-- Center vault icon -->
<rect x="224" y="340" width="32" height="28" rx="4" fill="#111f38" stroke="#94a3b8" stroke-width="1"/>
<circle cx="240" cy="352" r="3" fill="none" stroke="#94a3b8" stroke-width="1"/>
<line x1="240" y1="355" x2="240" y2="360" stroke="#94a3b8" stroke-width="1"/>
</svg>
</div>
</div>
</div>
<hr class="divider">
<!-- The Problem -->
<div class="section container">
<h2 class="mb-4" style="text-align:center">The problem</h2>
<p class="lead mb-12" style="text-align:center;max-width:640px;margin-left:auto;margin-right:auto">Every password manager was built before AI agents existed. Now they need to catch up.</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon red"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/></svg></div>
<h3>All-or-nothing is broken</h3>
<p>All others give your AI agent access to everything in your vault, or nothing at all. Your AI needs your GitHub token — it shouldn't also see your passport number.</p>
</div>
<div class="feature-card">
<div class="feature-icon red"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg></div>
<h3>Policy isn't security</h3>
<p>"AI-safe" vaults still decrypt everything server-side. If the server can read it, it's not truly private. Math beats policy every time.</p>
</div>
<div class="feature-card">
<div class="feature-icon red"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg></div>
<h3>Agents need credentials — and 2FA</h3>
<p>Your AI can't log in, pass two-factor, or rotate keys without access. vault<span class="vaultname" style="font-size:inherit"><span class="n">1984</span></span> lets it do all three — without exposing your credit card to the same pipeline.</p>
</div>
</div>
</div>
<hr class="divider">
<!-- How it works -->
<div class="section container">
<div style="max-width:800px;margin:0 auto;text-align:center">
<h2 class="mb-6">How it works</h2>
<blockquote style="font-size:clamp(1.25rem,2.5vw,1.875rem);font-weight:700;color:var(--text);line-height:1.3;margin-bottom:1.5rem">
"Your assistant can book your flights.<br>
<span class="gradient-text">Not read your diary.</span>"
</blockquote>
<p class="lead mb-8">Every field is encrypted. But some get a second lock. That second key is derived from your fingerprint and only exists in your browser. We hold the safe. Only you hold that key.</p>
</div>
<div class="grid-2" style="max-width:900px;margin:0 auto">
<div class="tier-card agent">
<span class="tier-label agent">Agent fields</span>
<h3 class="mb-3">AI-readable</h3>
<p class="mb-4" style="font-size:0.9rem">Encrypted at rest, decryptable by the vault server. Your AI agent reads these via MCP.</p>
<ul class="checklist">
<li>API keys &amp; tokens</li>
<li>SSH keys</li>
<li>TOTP 2FA codes &mdash; AI generates them for you</li>
<li>OAuth tokens</li>
<li>Structured notes</li>
</ul>
</div>
<div class="tier-card sealed">
<span class="tier-label sealed">Sealed fields</span>
<h3 class="mb-3">Touch ID only</h3>
<p class="mb-4" style="font-size:0.9rem">Encrypted client-side with WebAuthn PRF. The server never sees the plaintext. Ever.</p>
<ul class="checklist red">
<li>Credit card numbers</li>
<li>CVV</li>
<li>Passport &amp; SSN</li>
<li>Private signing keys</li>
<li>Private notes</li>
</ul>
</div>
</div>
</div>
<hr class="divider">
<!-- Features -->
<div class="section container">
<h2 class="mb-4" style="text-align:center">Built different</h2>
<p class="lead mb-10" style="text-align:center;max-width:600px;margin-left:auto;margin-right:auto">Not another password manager with an AI checkbox. The architecture is the feature.</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"/></svg></div>
<h3>Field-level AI visibility</h3>
<p>Each field has its own encryption tier. Your AI reads the username, not the CVV. Same entry, different access.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4"/></svg></div>
<h3>WebAuthn PRF</h3>
<p>Sealed encryption uses WebAuthn PRF — a cryptographic key derived from your biometric hardware. Math, not policy. We literally cannot decrypt it.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></div>
<h3>AI-powered 2FA</h3>
<p>Store TOTP secrets as Agent fields. Your AI generates time-based codes on demand via MCP — no more switching to your phone.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg></div>
<h3>Scoped MCP tokens</h3>
<p>Create separate MCP tokens per agent. Each token sees only its designated entries. Compromise one, the rest stay clean.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/></svg></div>
<h3>One binary, one file</h3>
<p>No Docker. No Postgres. No Redis. One Go binary, one SQLite file. Runs on a Raspberry Pi. Runs on a $4/month VPS.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg></div>
<h3>LLM field mapping</h3>
<p>Import from any password manager. The built-in LLM automatically classifies which fields should be Agent vs Sealed.</p>
</div>
</div>
</div>
<hr class="divider">
<!-- Multi-agent swarms -->
<div class="section swarm-section">
<div class="container">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4rem;align-items:center">
<div>
<h2 class="mb-4">10 agents.<br><span class="gradient-text">Each gets exactly what it needs.</span></h2>
<p class="lead mb-6">Create scoped MCP tokens per agent. One compromised agent exposes one scope — not your entire vault.</p>
<div class="code-inline">
<p class="code-label">~/.claude/mcp.json</p>
<pre style="margin:0;color:var(--muted)">{
"mcpServers": {
"vault-dev": {
"url": "http://localhost:1984/mcp",
"headers": { "Authorization": "Bearer <span style="color:var(--accent)">mcp_dev_a3f8...</span>" }
},
"vault-social": {
"url": "http://localhost:1984/mcp",
"headers": { "Authorization": "Bearer <span style="color:var(--accent)">mcp_social_7b2e...</span>" }
}
}
}</pre>
</div>
</div>
<div style="display:flex;justify-content:center">
<!-- Multi-agent SVG -->
<div class="flex justify-center">
<svg viewBox="0 0 400 360" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-full max-w-sm">
<!-- Center vault -->
<rect x="160" y="140" width="80" height="80" rx="12" fill="#111f38" stroke="#94a3b8" stroke-width="1.5"/>
<text x="200" y="175" font-family="JetBrains Mono, monospace" font-size="10" fill="#94a3b8" text-anchor="middle">vault</text>
<text x="200" y="195" font-family="JetBrains Mono, monospace" font-size="14" fill="white" text-anchor="middle" font-weight="600">1984</text>
<!-- Agent 1 — dev -->
<circle cx="80" cy="60" r="32" fill="#22C55E" fill-opacity="0.08" stroke="#22C55E" stroke-width="1"/>
<text x="80" y="56" font-family="JetBrains Mono, monospace" font-size="9" fill="#22C55E" text-anchor="middle">Agent 1</text>
<text x="80" y="68" font-family="JetBrains Mono, monospace" font-size="8" fill="#94a3b8" text-anchor="middle">dev</text>
<line x1="108" y1="80" x2="165" y2="145" stroke="#22C55E" stroke-width="1" stroke-opacity="0.4" stroke-dasharray="4 3"/>
<!-- Agent 2 — social -->
<circle cx="320" cy="60" r="32" fill="#22C55E" fill-opacity="0.08" stroke="#22C55E" stroke-width="1"/>
<text x="320" y="56" font-family="JetBrains Mono, monospace" font-size="9" fill="#22C55E" text-anchor="middle">Agent 2</text>
<text x="320" y="68" font-family="JetBrains Mono, monospace" font-size="8" fill="#94a3b8" text-anchor="middle">social</text>
<line x1="292" y1="80" x2="235" y2="145" stroke="#22C55E" stroke-width="1" stroke-opacity="0.4" stroke-dasharray="4 3"/>
<!-- Agent 3 — finance -->
<circle cx="50" cy="220" r="32" fill="#22C55E" fill-opacity="0.08" stroke="#22C55E" stroke-width="1"/>
<text x="50" y="216" font-family="JetBrains Mono, monospace" font-size="9" fill="#22C55E" text-anchor="middle">Agent 3</text>
<text x="50" y="228" font-family="JetBrains Mono, monospace" font-size="8" fill="#94a3b8" text-anchor="middle">finance</text>
<line x1="78" y1="204" x2="164" y2="190" stroke="#22C55E" stroke-width="1" stroke-opacity="0.4" stroke-dasharray="4 3"/>
<!-- Agent 4 — infra -->
<circle cx="350" cy="220" r="32" fill="#22C55E" fill-opacity="0.08" stroke="#22C55E" stroke-width="1"/>
<text x="350" y="216" font-family="JetBrains Mono, monospace" font-size="9" fill="#22C55E" text-anchor="middle">Agent 4</text>
<text x="350" y="228" font-family="JetBrains Mono, monospace" font-size="8" fill="#94a3b8" text-anchor="middle">infra</text>
<line x1="322" y1="204" x2="236" y2="190" stroke="#22C55E" stroke-width="1" stroke-opacity="0.4" stroke-dasharray="4 3"/>
<!-- Agent 5 — deploy -->
<circle cx="200" cy="330" r="32" fill="#22C55E" fill-opacity="0.08" stroke="#22C55E" stroke-width="1"/>
<text x="200" y="326" font-family="JetBrains Mono, monospace" font-size="9" fill="#22C55E" text-anchor="middle">Agent 5</text>
<text x="200" y="338" font-family="JetBrains Mono, monospace" font-size="8" fill="#94a3b8" text-anchor="middle">deploy</text>
<line x1="200" y1="298" x2="200" y2="220" stroke="#22C55E" stroke-width="1" stroke-opacity="0.4" stroke-dasharray="4 3"/>
<!-- Scope labels -->
<rect x="10" y="98" width="140" height="20" rx="4" fill="#0A1628"/>
<text x="80" y="112" font-family="JetBrains Mono, monospace" font-size="7.5" fill="#94a3b8" text-anchor="middle">github ssh gitlab</text>
<rect x="250" y="98" width="140" height="20" rx="4" fill="#0A1628"/>
<text x="320" y="112" font-family="JetBrains Mono, monospace" font-size="7.5" fill="#94a3b8" text-anchor="middle">twitter slack discord</text>
<rect x="0" y="256" width="100" height="20" rx="4" fill="#0A1628"/>
<text x="50" y="270" font-family="JetBrains Mono, monospace" font-size="7.5" fill="#94a3b8" text-anchor="middle">stripe plaid</text>
<rect x="300" y="256" width="100" height="20" rx="4" fill="#0A1628"/>
<text x="350" y="270" font-family="JetBrains Mono, monospace" font-size="7.5" fill="#94a3b8" text-anchor="middle">aws k8s docker</text>
<rect x="150" y="296" width="100" height="16" rx="4" fill="#0A1628"/>
<text x="200" y="308" font-family="JetBrains Mono, monospace" font-size="7.5" fill="#94a3b8" text-anchor="middle">vercel netlify</text>
</svg>
</div>
</div>
</div>
</div>
<hr class="divider">
<!-- Hosted CTA -->
<div class="section container" style="text-align:center;max-width:720px">
<h2 class="mb-4">Don't want to run it yourself?</h2>
<p class="lead mb-3">We host vault<span class="vaultname" style="font-size:inherit"><span class="n">1984</span></span> across 22 regions on every continent. $12/year. Pick your region at signup.</p>
<p class="mb-8" style="font-size:0.875rem">Your Sealed keys are derived in your browser. We mathematically cannot read your private fields.</p>
<div style="display:flex;flex-wrap:wrap;gap:1rem;justify-content:center">
<a href="/hosted.html" class="btn btn-primary">See hosted plans &rarr;</a>
<a href="/install.html" class="btn btn-ghost">Self-host guide</a>
</div>
</div>
<hr class="divider">
<!-- Quick install -->
<div class="section container">
<h2 class="mb-4" style="text-align:center">Up and running in 30 seconds</h2>
<p class="lead mb-10" style="text-align:center">One command. No dependencies.</p>
<div style="max-width:720px;margin:0 auto">
<div class="code-block mb-6">
<p class="code-label">Terminal</p>
<div><span class="comment"># Self-host in 30 seconds</span></div>
<div><span class="prompt">$</span> curl -fsSL vault1984.com/install.sh | sh</div>
<div><span class="prompt">$</span> vault1984</div>
<div class="comment"># Running on http://localhost:1984</div>
</div>
<div class="code-block">
<p class="code-label">MCP config for Claude Code / Cursor / Codex</p>
<pre style="margin:0;color:var(--muted)">{
"mcpServers": {
"vault1984": {
"url": "http://localhost:1984/mcp",
"headers": { "Authorization": "Bearer <span style="color:var(--accent)">mcp_your_token_here</span>" }
}
}
}</pre>
</div>
<p class="mt-4" style="text-align:center"><a href="/install.html" style="color:var(--accent)">Full install guide &rarr;</a></p>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="footer-inner">
<div class="footer-links">
<a href="/" class="vaultname">vault<span class="n">1984</span></a>
<a href="https://github.com/johanjongsma/vault1984" target="_blank" rel="noopener">GitHub</a>
<a href="#">Discord</a>
<a href="#">X</a>
</div>
<div class="footer-links">
<a href="/privacy.html">Privacy</a>
<a href="/terms.html">Terms</a>
<span>MIT License</span>
</div>
</div>
<p class="footer-copy">Built for humans with AI assistants.</p>
</div>
</footer>
<script>
(function() {
const W = 1000, H = 460;
function project(lon, lat) {
const latR = Math.min(Math.abs(lat), 85) * Math.PI / 180 * (lat < 0 ? -1 : 1);
const miller = 1.25 * Math.log(Math.tan(Math.PI/4 + 0.4*latR));
const maxMiller = 1.25 * Math.log(Math.tan(Math.PI/4 + 0.4*80*Math.PI/180));
const x = (lon + 180) / 360 * W;
const y = H/2 - (miller / (2*maxMiller)) * H;
return [Math.round(x*10)/10, Math.round(y*10)/10];
}
function addVisitorDot(lat, lon, city) {
const svg = document.getElementById('worldmap');
if (!svg) return;
const [x, y] = project(lon, lat);
const ns = 'http://www.w3.org/2000/svg';
// Pulse ring
const ring = document.createElementNS(ns, 'circle');
ring.setAttribute('cx', x); ring.setAttribute('cy', y);
ring.setAttribute('r', '3'); ring.setAttribute('fill', 'none');
ring.setAttribute('stroke', '#EF4444'); ring.setAttribute('stroke-width', '1.5');
const a1 = document.createElementNS(ns, 'animate');
a1.setAttribute('attributeName', 'r'); a1.setAttribute('values', '3;16;3');
a1.setAttribute('dur', '2s'); a1.setAttribute('repeatCount', 'indefinite');
const a2 = document.createElementNS(ns, 'animate');
a2.setAttribute('attributeName', 'stroke-opacity'); a2.setAttribute('values', '0.8;0;0.8');
a2.setAttribute('dur', '2s'); a2.setAttribute('repeatCount', 'indefinite');
ring.appendChild(a1); ring.appendChild(a2);
// Dot
const dot = document.createElementNS(ns, 'circle');
dot.setAttribute('cx', x); dot.setAttribute('cy', y);
dot.setAttribute('r', '4'); dot.setAttribute('fill', '#EF4444');
dot.setAttribute('stroke', '#0a1628'); dot.setAttribute('stroke-width', '1.5');
// Label
const label = document.createElementNS(ns, 'text');
label.setAttribute('x', x); label.setAttribute('y', y + 15);
label.setAttribute('font-family', 'Inter,sans-serif');
label.setAttribute('font-size', '10');
label.setAttribute('fill', '#EF4444');
label.setAttribute('text-anchor', 'middle');
label.setAttribute('font-weight', '500');
label.textContent = city || 'You';
svg.appendChild(ring);
svg.appendChild(dot);
svg.appendChild(label);
}
function handleGeoData(d) {
if (!d.latitude || !d.longitude) return;
addVisitorDot(d.latitude, d.longitude, d.city || 'You');
const grid = document.getElementById('dc-grid');
if (!grid) return;
// Build visitor card
const flag = d.country_code ? d.country_code.toUpperCase().split('').map(c =>
String.fromCodePoint(c.charCodeAt(0) + 127397)).join('') : '📍';
const label = [d.city, d.country_name].filter(Boolean).join(', ') || 'Your location';
const region = d.region || '';
const card = document.createElement('div');
card.className = 'rounded-xl p-5 text-center card-hover';
card.setAttribute('data-lon', d.longitude);
card.style.cssText = 'background:#1f0a0a;border:1px solid rgba(239,68,68,0.35)';
card.innerHTML = `
<div class="text-2xl mb-2">${flag}</div>
<div class="text-white font-semibold text-sm">${label}</div>
<div class="text-gray-500 text-xs mb-2">${region}</div>
<div class="flex items-center justify-center gap-1.5 text-xs text-gray-400">
<span class="w-1.5 h-1.5 rounded-full inline-block" style="background:#EF4444;opacity:0.6"></span>You are here
</div>`;
// Expand to 5 columns
grid.style.gridTemplateColumns = "repeat(5,1fr)";
// Insert at correct longitude position
const cards = [...grid.children];
const insertBefore = cards.find(c => parseFloat(c.getAttribute('data-lon')) > d.longitude);
if (insertBefore) grid.insertBefore(card, insertBefore);
else grid.appendChild(card);
}
fetch('/geo')
.then(r => r.json())
.then(d => {
if (d.latitude) {
handleGeoData(d);
} else if (d.private && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(pos => {
const lat = pos.coords.latitude, lon = pos.coords.longitude;
// Reverse geocode via open-meteo's free geocoding isn't ideal;
// use bigdatacloud free reverse geocode — no key, no signup
fetch(`/geo?lat=${lat}&lon=${lon}`)
.then(r => r.json())
.then(g => handleGeoData({
latitude: lat, longitude: lon,
city: g.city || 'You',
region: g.region || '',
country_name: g.country_name || '',
country_code: g.country_code || ''
}))
.catch(() => handleGeoData({ latitude: lat, longitude: lon,
city: 'You', region: '', country_name: '', country_code: '' }));
}, () => {});
}
})
.catch(() => {});
})();
</script>
</body>
</html>