clavitor/clavitor.com/templates/hosted.tmpl

399 lines
22 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="Figtree,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. If your location is slower than that, <a href="mailto:support@clavitor.com">reach out</a> and we will work on a solution.</p>
<button id="backup-toggle" class="btn btn-ghost" style="white-space:nowrap;font-size:0.75rem;padding:6px 12px">Show backup routes</button>
</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>
</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.</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}}"},{{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 backupLines = [];
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');
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 line = document.createElementNS(ns, 'line');
line.setAttribute('x1', x1); line.setAttribute('y1', y1);
line.setAttribute('x2', x2); line.setAttribute('y2', y2);
line.setAttribute('stroke', '#DC2626'); line.setAttribute('stroke-width', '0.8');
line.setAttribute('stroke-opacity', '0.3'); line.setAttribute('stroke-dasharray', '4 3');
svg.insertBefore(line, svg.querySelector('rect'));
backupLines.push(line);
});
} else {
backupBtn.textContent = 'Show backup routes';
backupLines.forEach(l => l.remove());
backupLines = [];
}
});
}
function findClosestPop(lat, lon) {
return POPS.reduce((best, p) => {
const d = (lat-p.lat)**2 + (lon-p.lon)**2;
const bd = (lat-best.lat)**2 + (lon-best.lon)**2;
return d < bd ? p : best;
});
}
function handleGeoData(d) {
if (!d.latitude || !d.longitude) return;
addVisitorDot(d.latitude, d.longitude, d.city || 'You');
const closest = findClosestPop(d.latitude, d.longitude);
const nameEl = document.getElementById('closest-name');
const subEl = document.getElementById('closest-sub');
const buyEl = document.getElementById('closest-buy');
if (nameEl) nameEl.textContent = closest.city;
if (subEl) subEl.textContent = d.city ? `~${d.city}` : 'Your region';
if (buyEl) buyEl.href = `/signup?region=${closest.region}`;
}
// Ask browser geolocation first (accurate, triggers permission prompt)
// Fall back to server-side IP lookup if denied or unavailable
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: '', region: '', country_name: '', country_code: ''
});
},
() => tryIPGeo() // denied — fall back to IP
);
} else {
tryIPGeo();
}
})();
</script>
{{end}}