clavitor/clavitor.ai/templates/hosted.tmpl

430 lines
24 KiB
Cheetah

{{define "hosted"}}
<!-- Hero -->
<div class="hero container">
<p class="label accent mb-4"><span class="vaultname">clavitor</span> hosted</p>
<h1>Zero cache. Every request hits the vault.</h1>
<p class="lead">Clavitor never caches credentials — not in memory, not on disk, not anywhere. Every request is a fresh decrypt from the vault. That's the security model. To make it fast, we run {{len .Pops}} regions across every continent. Your data lives where you choose. <s>$20</s> $12/yr.</p>
</div>
<!-- Map -->
<div class="container">
<div class="map-wrap">
<svg id="worldmap" viewBox="0 0 1000 460" xmlns="http://www.w3.org/2000/svg">
<image href="/worldmap.svg" x="0" y="0" width="1000" height="460"/>
<text x="500" y="440" font-family="IBM Plex Sans,sans-serif" font-size="18" font-weight="700" fill="#0A0A0A" text-anchor="middle" opacity="0.35" letter-spacing="0.3em">CLAVITOR GLOBAL PRESENCE</text>
</svg>
</div>
<div class="mt-4" style="display:flex;justify-content:space-between;align-items:baseline;gap:16px;flex-wrap:wrap">
<p class="text-sm text-tertiary" style="margin:0">We have strategically chosen our datacenter locations so that almost every place on the planet gets an answer within 60 ms. That's a convenience target, not a requirement — Clavitor works perfectly fine on slower connections, it just won't feel quite as instant. <a href="/glass">See how fast it is from your location.</a> If you'd like a region closer to you, <a href="mailto:support@clavitor.ai">reach out</a> and we'll work on it.</p>
<span style="display:flex;gap:8px;flex-wrap:wrap">
<button id="backup-toggle" class="btn btn-ghost btn-sm" style="white-space:nowrap">Show backup routes</button>
<a href="/glass" class="btn btn-ghost btn-sm" style="white-space:nowrap">See network performance from your location &rarr;</a>
</span>
</div>
<div class="mt-12"></div>
<div id="dc-grid" class="mb-8">
<!-- Self-hosted -->
<div class="dc-card red" data-lon="-999">
<div class="dc-icon">🖥️</div>
<div class="dc-name">Self-hosted</div>
<div class="dc-sub">Your machine. Your rules.</div>
<div class="dc-status"><span class="dc-dot"></span>Free forever</div>
<a href="/install" class="btn btn-red btn-block">Download now &rarr;</a>
</div>
<!-- Zürich HQ -->
<div class="dc-card gold" data-lon="8.5">
<div class="dc-icon">🇨🇭</div>
<div class="dc-name">Zürich, Switzerland</div>
<div class="dc-sub">Capital of Privacy</div>
<div class="dc-status"><span class="dc-dot"></span>Headquarters</div>
<a href="/signup?region=eu-central-2" class="btn btn-gold btn-block">Buy now &rarr;</a>
</div>
<!-- Closest POP — populated by JS -->
<div id="closest-pop" class="dc-card" data-lon="999">
<div class="dc-icon">📍</div>
<div id="closest-name" class="dc-name">Nearest region</div>
<div id="closest-sub" class="dc-sub">Locating you…</div>
<div class="dc-status"><span class="dc-dot"></span>Closest to you</div>
<a id="closest-buy" href="/signup" class="btn btn-accent btn-block">Buy now &rarr;</a>
</div>
</div>
</div>
<hr class="divider">
<!-- Why encryption, not jurisdiction -->
<div class="section container">
<p class="label accent mb-3">Three-tier encryption</p>
<h2 class="mb-4">Jurisdiction is irrelevant.<br>Math is not.</h2>
<p class="lead mb-8">Your vault is encrypted at rest. Your credentials are encrypted per-field. Your identity fields are encrypted client-side with a key that never leaves your device. No server — ours or anyone's — can read what it doesn't have the key to. That's the real protection. Zürich is the belt to that suspenders: a jurisdiction where nobody will even try to force open what mathematics already guarantees they can't.</p>
<div class="grid-3">
<div class="card">
<p class="label mb-2">Vault Encryption</p>
<p>Entire vault encrypted at rest with AES-256-GCM. The baseline. Every password manager does this.</p>
</div>
<div class="card">
<p class="label accent mb-2">Credential Encryption</p>
<p>Per-field encryption. Your AI agent can read the API key it needs — but not the credit card number in the same entry.</p>
</div>
<div class="card red">
<p class="label red mb-2">Identity Encryption</p>
<p>Client-side. WebAuthn PRF. The key is derived from your WebAuthn authenticator — fingerprint, face, or hardware key — and never leaves your device. We cannot decrypt it. Period.</p>
</div>
</div>
</div>
<hr class="divider">
<!-- What hosted adds -->
<div class="section container">
<p class="label accent mb-3">What hosted adds</p>
<h2 class="mb-8">Everything in self-hosted, plus</h2>
<div class="grid-3">
<div class="card alt">
<h3 class="mb-2">Managed infrastructure</h3>
<p>We run it, monitor it, and keep it up. You just use it.</p>
</div>
<div class="card alt">
<h3 class="mb-2">Daily encrypted backups</h3>
<p>Automatic daily backups. Encrypted at rest. Restorable on request.</p>
</div>
<div class="card alt">
<h3 class="mb-2">{{len .Pops}} regions</h3>
<p>Pick your region at signup. Your data stays there. Every continent covered.</p>
</div>
<div class="card alt">
<h3 class="mb-2">Automatic updates</h3>
<p>Security patches and new features deployed automatically. No downtime.</p>
</div>
<div class="card alt">
<h3 class="mb-2">TLS included</h3>
<p>HTTPS out of the box. No Caddy, no certbot, no renewal headaches.</p>
</div>
<div class="card alt">
<h3 class="mb-2">Email support</h3>
<p>Real human support. Not a chatbot. Not a forum post into the void.</p>
</div>
<div class="card alt">
<h3 class="mb-2">Auto scope assignment</h3>
<p>On import, Clavitor extracts the domain from each credential's URL and assigns a scope automatically — "dev", "finance", "shopping". Your agents get the right access from day one, without manual tagging.</p>
</div>
<div class="card alt">
<h3 class="mb-2">API key recognition</h3>
<p>Clavitor detects API keys by pattern during import — OpenAI, AWS, GitHub, Stripe, and 10+ more. They're automatically separated into their own category so agents can find them without digging through password entries.</p>
</div>
</div>
</div>
<hr class="divider">
<!-- Backup strategy -->
<div class="section container">
<p class="label accent mb-3">Disaster recovery</p>
<h2 class="mb-4">Your backup is on the other side of the world.</h2>
<p class="lead mb-8">Every vault is automatically replicated to an inland backup location — no coastline, no tsunami risk, no storm surge. Your data is encrypted end-to-end — nobody but you can read it.</p>
<div class="grid-2 mb-4">
<div class="card">
<h3 class="mb-2">Calgary, Canada</h3>
<p class="mb-4">Deep in the Canadian prairies, over 1,000 km from the nearest ocean. No earthquakes, no hurricanes, no volcanoes — the most geologically stable terrain in North America. Canadian privacy law.</p>
<p class="label mb-3">Backs up</p>
<table class="data-table">
<tbody>
{{range .PopsByCity}}{{if eq .BackupCity "Calgary"}}<tr><td>{{.City}}{{if eq .City "Dubai"}} <span class="text-tertiary text-sm">(down)</span>{{end}}</td><td class="mono text-tertiary">{{.BackupDistFmt}} km <span class="text-sm">({{.BackupDistMiFmt}} mi)</span></td></tr>
{{end}}{{end}}
</tbody>
</table>
</div>
<div class="card">
<h3 class="mb-2">Zürich, Switzerland</h3>
<p class="mb-4">Landlocked in the center of Europe, surrounded by the Alps. Swiss data protection — among the strongest in the world. Politically neutral for over 200 years.</p>
<p class="label mb-3">Backs up</p>
<table class="data-table">
<tbody>
{{range .PopsByCity}}{{if eq .BackupCity "Zürich"}}<tr><td>{{.City}}</td><td class="mono text-tertiary">{{.BackupDistFmt}} km <span class="text-sm">({{.BackupDistMiFmt}} mi)</span></td></tr>
{{end}}{{end}}
</tbody>
</table>
</div>
</div>
<p class="mt-4 mb-4 text-sm text-tertiary"><strong>Why not Almaty?</strong> Almaty is also inland and well-positioned in Central Asia — but it sits on an active seismic fault line. It serves as a great user-facing region for the area, but we chose Calgary and Zürich as backup sites specifically for their geological stability.</p>
<div class="card" style="margin-top:24px;border-left:3px solid var(--brand-red)">
<p class="label red mb-2">Dubai (me-central-1) — temporarily unavailable</p>
<p class="mb-3">On March 1, 2026, drone strikes physically damaged two of three AWS availability zones in the UAE. Both AWS Middle East regions (UAE and Bahrain) remain offline. We are waiting for AWS to restore service before we can offer Dubai as a region again. No customer data was affected — this is exactly why we replicate every vault to an inland backup site on the other side of the world.</p>
<p>If you were planning to use Dubai, the nearest alternatives are <strong>Mumbai</strong>, <strong>Istanbul</strong>, and <strong>Almaty</strong>. If you need to transfer an existing vault to a different region, we will do it for free — contact <a href="mailto:support@clavitor.ai">support@clavitor.ai</a>.</p>
</div>
</div>
<hr class="divider">
<!-- CTA -->
<div class="section container">
<h2 class="mb-4">Ready?</h2>
<p class="lead mb-6"><s>$20</s> $12/yr. 7-day money-back. Every feature included. <strong>Price for life</strong> — your rate never increases.</p>
<div class="btn-row">
<a href="/signup" class="btn btn-primary">Get started</a>
<a href="/pricing" class="btn btn-ghost">Compare plans &rarr;</a>
</div>
</div>
{{end}}
{{define "hosted-script"}}
<script>
(function() {
const W = 1000, H = 460;
const ns = 'http://www.w3.org/2000/svg';
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 addPopDot(svg, pop, delay) {
const [x, y] = project(pop.lon, pop.lat);
const isHQ = pop.city === 'Zürich' || pop.city === 'Calgary';
const isLive = pop.status === 'live';
const isDubai = pop.city === 'Dubai';
const dotColor = isDubai ? '#EA580C' : isHQ ? '#0A0A0A' : isLive ? '#DC2626' : '#F5B7B7';
const textColor = isDubai ? '#EA580C' : isHQ ? '#0A0A0A' : isLive ? '#B91C1C' : '#777777';
const pulseColor = isHQ ? '#0A0A0A' : isLive ? '#DC2626' : '#F5B7B7';
const dotSize = isHQ ? 11 : 9;
const pulseR = isHQ ? 5 : 4;
const pulseMax = isHQ ? 18 : 13;
const tooltip = isDubai ? 'Dubai · Down — AWS me-central-1 damaged by drone strikes (March 2026)' : isLive ? pop.city + ' · Live' : pop.city + ' · Planned';
// Pulse rings — only for live/HQ pops
let r1, r2;
if (isLive || isHQ) {
r1 = document.createElementNS(ns, 'circle');
r1.setAttribute('cx', x); r1.setAttribute('cy', y);
r1.setAttribute('r', pulseR); r1.setAttribute('fill', 'none');
r1.setAttribute('stroke', pulseColor); r1.setAttribute('stroke-width', isHQ ? '2' : '1.5');
const a1 = document.createElementNS(ns, 'animate');
a1.setAttribute('attributeName', 'r'); a1.setAttribute('values', pulseR+';'+pulseMax+';'+pulseR);
a1.setAttribute('dur', '2.4s'); a1.setAttribute('begin', delay+'s'); a1.setAttribute('repeatCount', 'indefinite');
const a2 = document.createElementNS(ns, 'animate');
a2.setAttribute('attributeName', 'stroke-opacity'); a2.setAttribute('values', '0.6;0;0.6');
a2.setAttribute('dur', '2.4s'); a2.setAttribute('begin', delay+'s'); a2.setAttribute('repeatCount', 'indefinite');
r1.appendChild(a1); r1.appendChild(a2);
r2 = document.createElementNS(ns, 'circle');
r2.setAttribute('cx', x); r2.setAttribute('cy', y);
r2.setAttribute('r', pulseR); r2.setAttribute('fill', 'none');
r2.setAttribute('stroke', pulseColor); r2.setAttribute('stroke-width', isHQ ? '1.5' : '1');
const a3 = document.createElementNS(ns, 'animate');
a3.setAttribute('attributeName', 'r'); a3.setAttribute('values', pulseR+';'+pulseMax+';'+pulseR);
a3.setAttribute('dur', '2.4s'); a3.setAttribute('begin', (delay+0.8)+'s'); a3.setAttribute('repeatCount', 'indefinite');
const a4 = document.createElementNS(ns, 'animate');
a4.setAttribute('attributeName', 'stroke-opacity'); a4.setAttribute('values', '0.4;0;0.4');
a4.setAttribute('dur', '2.4s'); a4.setAttribute('begin', (delay+0.8)+'s'); a4.setAttribute('repeatCount', 'indefinite');
r2.appendChild(a3); r2.appendChild(a4);
}
// Square dot
const half = dotSize / 2;
const dot = document.createElementNS(ns, 'rect');
dot.setAttribute('x', x - half); dot.setAttribute('y', y - half);
dot.setAttribute('width', dotSize); dot.setAttribute('height', dotSize);
dot.setAttribute('fill', dotColor); dot.setAttribute('stroke', '#F5F5F5'); dot.setAttribute('stroke-width', '1.5');
const title = document.createElementNS(ns, 'title');
title.textContent = tooltip;
dot.appendChild(title);
// Label
const label = document.createElementNS(ns, 'text');
label.setAttribute('x', x); label.setAttribute('y', y - half - 4);
label.setAttribute('font-family', 'Inter,sans-serif');
label.setAttribute('font-size', '8.5');
label.setAttribute('fill', textColor);
label.setAttribute('text-anchor', 'middle');
label.setAttribute('opacity', '0.85');
label.textContent = pop.city;
const labelTitle = document.createElementNS(ns, 'title');
labelTitle.textContent = tooltip;
label.appendChild(labelTitle);
if (r1) svg.appendChild(r1);
if (r2) svg.appendChild(r2);
svg.appendChild(dot);
svg.appendChild(label);
// Down indicator for Dubai
if (isDubai) {
const bg = document.createElementNS(ns, 'circle');
bg.setAttribute('cx', x); bg.setAttribute('cy', y);
bg.setAttribute('r', '12'); bg.setAttribute('fill', '#EA580C'); bg.setAttribute('fill-opacity', '0.15');
bg.setAttribute('stroke', '#EA580C'); bg.setAttribute('stroke-width', '1.5'); bg.setAttribute('stroke-opacity', '0.4');
svg.appendChild(bg);
const sz = 4;
const cross = document.createElementNS(ns, 'g');
cross.setAttribute('stroke', '#ffffff'); cross.setAttribute('stroke-width', '2.5'); cross.setAttribute('stroke-linecap', 'round');
const l1 = document.createElementNS(ns, 'line');
l1.setAttribute('x1', x - sz); l1.setAttribute('y1', y - sz);
l1.setAttribute('x2', x + sz); l1.setAttribute('y2', y + sz);
const l2 = document.createElementNS(ns, 'line');
l2.setAttribute('x1', x + sz); l2.setAttribute('y1', y - sz);
l2.setAttribute('x2', x - sz); l2.setAttribute('y2', y + sz);
cross.appendChild(l1); cross.appendChild(l2);
const crossTitle = document.createElementNS(ns, 'title');
crossTitle.textContent = tooltip;
cross.appendChild(crossTitle);
svg.appendChild(cross);
}
}
function addVisitorDot(lat, lon, city) {
const svg = document.getElementById('worldmap');
if (!svg) return;
const [x, y] = project(lon, lat);
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', '#0A0A0A'); 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);
const dot = document.createElementNS(ns, 'circle');
dot.setAttribute('cx', x); dot.setAttribute('cy', y);
dot.setAttribute('r', '4'); dot.setAttribute('fill', '#0A0A0A');
dot.setAttribute('stroke', '#ffffff'); dot.setAttribute('stroke-width', '1.5');
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', '#0A0A0A');
label.setAttribute('text-anchor', 'middle');
label.setAttribute('font-weight', '500');
label.textContent = city || 'You';
svg.appendChild(ring);
svg.appendChild(dot);
svg.appendChild(label);
}
const POPS = [{{range .Pops}}
{city:"{{.City}}", country:"{{.Country}}", region:"{{.RegionName}}", lat:{{.Lat}}, lon:{{.Lon}}, status:"{{.Status}}", provider:"{{.Provider}}", dns:"{{.DNS}}"},{{end}}
];
// Render all POP dots from DB data
const svg = document.getElementById('worldmap');
if (svg) {
POPS.forEach((pop, i) => addPopDot(svg, pop, (i * 0.08).toFixed(2)));
}
// Backup route lines
const backupToZurich = new Set(['JP','KR','AU','SG','HK','US','CA','MX','BR','CO']);
function getBackupCity(pop) {
if (pop.city === 'Zürich') return 'Calgary';
if (pop.city === 'Calgary') return 'Zürich';
return backupToZurich.has(pop.country) ? 'Zürich' : 'Calgary';
}
let backupElements = [];
let backupVisible = false;
const backupBtn = document.getElementById('backup-toggle');
if (backupBtn) {
backupBtn.addEventListener('click', function() {
backupVisible = !backupVisible;
if (backupVisible) {
backupBtn.textContent = 'Hide backup routes';
const livePops = POPS.filter(p => p.status === 'live');
const insertBefore = svg.querySelector('rect');
// Highlight backup destination dots
['Zürich', 'Calgary'].forEach(city => {
const pop = livePops.find(p => p.city === city);
if (!pop) return;
const [x, y] = project(pop.lon, pop.lat);
const color = city === 'Zürich' ? '#2563EB' : '#F59E0B';
const ring = document.createElementNS(ns, 'circle');
ring.setAttribute('cx', x); ring.setAttribute('cy', y);
ring.setAttribute('r', '16'); ring.setAttribute('fill', color);
ring.setAttribute('fill-opacity', '0.08');
ring.setAttribute('stroke', color); ring.setAttribute('stroke-width', '1.5');
ring.setAttribute('stroke-opacity', '0.3');
svg.appendChild(ring);
backupElements.push(ring);
});
// Draw routes — black for Zürich-bound, red for Calgary-bound
livePops.forEach(pop => {
const backupCity = getBackupCity(pop);
const target = livePops.find(p => p.city === backupCity);
if (!target || target.city === pop.city) return;
const [x1, y1] = project(pop.lon, pop.lat);
const [x2, y2] = project(target.lon, target.lat);
const color = backupCity === 'Zürich' ? '#2563EB' : '#F59E0B';
const line = document.createElementNS(ns, 'line');
line.setAttribute('x1', x1); line.setAttribute('y1', y1);
line.setAttribute('x2', x2); line.setAttribute('y2', y2);
line.setAttribute('stroke', color); line.setAttribute('stroke-width', '1.5');
line.setAttribute('stroke-opacity', '0.7'); line.setAttribute('stroke-dasharray', '4 3');
svg.insertBefore(line, insertBefore);
backupElements.push(line);
});
} else {
backupBtn.textContent = 'Show backup routes';
backupElements.forEach(el => el.remove());
backupElements = [];
}
});
}
// Find fastest POP by actual response time
function findFastestPop() {
const nameEl = document.getElementById('closest-name');
const subEl = document.getElementById('closest-sub');
const buyEl = document.getElementById('closest-buy');
const livePops = POPS.filter(p => p.status === 'live' && p.dns);
let best = null, bestMs = Infinity;
Promise.all(livePops.map(pop =>
popPing(pop.dns).then(ms => {
if (ms !== null && ms < bestMs) {
bestMs = ms; best = pop;
if (nameEl) nameEl.textContent = best.city;
if (subEl) subEl.textContent = bestMs + ' ms';
if (buyEl) buyEl.href = '/signup?region=' + best.region;
}
})
));
}
// Place visitor dot from geo, then find fastest POP by ping
function handleGeoData(d) {
if (d.latitude && d.longitude) {
addVisitorDot(d.latitude, d.longitude, d.city || 'You');
}
}
function tryIPGeo() {
fetch('/geo')
.then(r => r.json())
.then(d => { if (d.latitude) handleGeoData(d); })
.catch(() => {});
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
pos => handleGeoData({ latitude: pos.coords.latitude, longitude: pos.coords.longitude, city: '' }),
() => tryIPGeo()
);
} else {
tryIPGeo();
}
findFastestPop();
})();
</script>
{{end}}