redesign: hero → map → security explanation
- Hero: 'Your vault. Wherever you want it.' - Map immediately under hero with 22 region dots - Three cards: self-hosted / Zürich HQ / closest POP - Security model: problem (AI access) → sealed/agent layers → why Zürich - Added /geo endpoint to main.go for IP-based location fallback - Geo: browser geolocation first, IP fallback on deny - Closest POP card wired to deeplink /signup?region=<id>
This commit is contained in:
parent
e9a1efeede
commit
27c0ad6d9b
325
hosted.html
325
hosted.html
|
|
@ -50,126 +50,191 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="pt-20">
|
||||
<div class="text-center py-16 px-6">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-white mb-4">vault<span class="text-accent font-mono">1984</span> Hosted</h1>
|
||||
<p class="text-gray-400 text-lg max-w-xl mx-auto">We run it. You own it. Your L2 keys never leave your device.</p>
|
||||
<!-- Hero -->
|
||||
<div style="height:80px"></div>
|
||||
<section class="pb-12" style="padding-top:0">
|
||||
<div class="max-w-7xl mx-auto px-6 text-center">
|
||||
<p class="text-green-400 text-xs font-mono uppercase tracking-widest mb-4">Hosting</p>
|
||||
<h1 class="text-4xl md:text-6xl font-bold text-white mb-5">Your vault. Wherever you want it.</h1>
|
||||
<p class="text-gray-400 text-xl max-w-2xl mx-auto">We run it. You own it. Pick your region — your data stays there.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- World Map — Infrastructure -->
|
||||
<section class="py-8 border-t border-white/5">
|
||||
<!-- Map -->
|
||||
<section class="pb-10">
|
||||
<div class="max-w-7xl mx-auto px-6">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-white text-center mb-4">Your vault. Deployed close to you.</h2>
|
||||
<p class="text-gray-400 text-center mb-4 max-w-2xl mx-auto">Hosted on Hostkey TIER III infrastructure. Pick your region at signup.</p>
|
||||
<p class="text-gray-400 text-center mb-3 max-w-2xl mx-auto">Or run it yourself. We embrace that too. No account, no payment, no questions asked.</p>
|
||||
|
||||
<div class="mx-auto mb-4 rounded-2xl border border-green-500/20 bg-green-500/5 p-6 text-center">
|
||||
<p class="text-green-400 text-xs font-mono uppercase tracking-widest mb-4">The security model</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-white mb-4">Location is latency. Not security.</h2>
|
||||
<p class="text-gray-300 leading-relaxed">
|
||||
Your L2 encryption key is derived client-side from your Touch ID or security key. It never leaves your device. Our servers store ciphertext they cannot decrypt — regardless of who owns the rack, the building, or the country it sits in.
|
||||
</p>
|
||||
<p class="text-gray-400 mt-4 leading-relaxed">
|
||||
Pick your region for speed. Pick it for compliance if your organisation requires it. But your private fields are safe in any of them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="relative mb-4 rounded-2xl overflow-hidden border border-white/5">
|
||||
<div class="relative mb-6 rounded-2xl overflow-hidden border border-white/5">
|
||||
<svg id="worldmap" viewBox="0 0 1000 460" xmlns="http://www.w3.org/2000/svg" class="w-full" style="display:block;background:#0a1628;">
|
||||
<image href="/worldmap.svg" x="0" y="0" width="1000" height="460"/>
|
||||
<circle cx="284.7" cy="143.8" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.00s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.00s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="284.7" cy="143.8" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.80s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="0.80s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="284.7" cy="143.8" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="284.7" y="155.8" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Washington D.C.</text>
|
||||
<circle cx="160.0" cy="143.1" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.08s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.08s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="160.0" cy="143.1" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.88s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="0.88s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="160.0" cy="143.1" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="160.0" y="135.1" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">San Francisco</text>
|
||||
<circle cx="295.6" cy="122.8" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.16s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.16s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="295.6" cy="122.8" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.96s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="0.96s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="295.6" cy="122.8" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="295.6" y="114.8" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Montréal</text>
|
||||
<circle cx="224.7" cy="187.0" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.24s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.24s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="224.7" cy="187.0" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.04s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.04s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="224.7" cy="187.0" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="370.6" cy="282.7" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.32s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.32s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="370.6" cy="282.7" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.12s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.12s" repeatCount="indefinite"/></circle>
|
||||
<text x="224.7" y="199.0" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Mexico City</text>
|
||||
<circle cx="294.2" cy="219.7" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.32s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.32s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="294.2" cy="219.7" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.12s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.12s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="294.2" cy="219.7" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="294.2" y="231.7" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Bogotá</text>
|
||||
<circle cx="370.6" cy="282.7" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.40s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.40s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="370.6" cy="282.7" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.20s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.20s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="370.6" cy="282.7" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="499.7" cy="106.0" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.40s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.40s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="499.7" cy="106.0" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.20s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.20s" repeatCount="indefinite"/></circle>
|
||||
<text x="370.6" y="294.7" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">São Paulo</text>
|
||||
<circle cx="303.9" cy="306.0" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.48s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.48s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="303.9" cy="306.0" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.28s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.28s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="303.9" cy="306.0" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="303.9" y="318.0" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Santiago</text>
|
||||
<circle cx="499.7" cy="106.0" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.56s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.56s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="499.7" cy="106.0" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.36s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.36s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="499.7" cy="106.0" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="506.4" cy="113.4" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.48s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.48s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="506.4" cy="113.4" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.28s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.28s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="506.4" cy="113.4" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="524.2" cy="110.0" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.56s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.56s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="524.2" cy="110.0" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.36s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.36s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="524.2" cy="110.0" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="499.7" y="98.0" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">London</text>
|
||||
<circle cx="523.6" cy="117.6" r="5" fill="none" stroke="#D4AF37" stroke-width="2"><animate attributeName="r" values="5;18;5" dur="2.4s" begin="0.64s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.64s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="523.6" cy="117.6" r="5" fill="none" stroke="#D4AF37" stroke-width="1.5"><animate attributeName="r" values="5;18;5" dur="2.4s" begin="1.44s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.44s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="523.6" cy="117.6" r="6" fill="#D4AF37" stroke="#0a1628" stroke-width="2"/>
|
||||
<circle cx="523.6" cy="117.6" r="3" fill="#0a1628"/>
|
||||
<text x="523.6" y="109.6" font-family="Inter,sans-serif" font-size="8.5" fill="#D4AF37" text-anchor="middle" opacity="0.85">Zürich</text>
|
||||
<circle cx="489.7" cy="136.4" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.72s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.72s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="489.7" cy="136.4" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.52s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.52s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="489.7" cy="136.4" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="489.7" y="128.4" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Madrid</text>
|
||||
<circle cx="550.3" cy="82.1" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.80s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.80s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="550.3" cy="82.1" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.60s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.60s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="550.3" cy="82.1" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="653.6" cy="173.6" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.88s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.88s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="653.6" cy="173.6" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.68s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.68s" repeatCount="indefinite"/></circle>
|
||||
<text x="550.3" y="74.1" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Stockholm</text>
|
||||
<circle cx="580.3" cy="134.8" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.88s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.88s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="580.3" cy="134.8" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.68s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.68s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="580.3" cy="134.8" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="580.3" y="126.8" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Istanbul</text>
|
||||
<circle cx="653.6" cy="173.6" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.96s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.96s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="653.6" cy="173.6" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.76s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.76s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="653.6" cy="173.6" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="596.7" cy="157.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="0.96s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="0.96s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="596.7" cy="157.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.76s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.76s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="596.7" cy="157.2" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="551.1" cy="307.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.04s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.04s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="551.1" cy="307.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.84s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.84s" repeatCount="indefinite"/></circle>
|
||||
<text x="653.6" y="165.6" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Dubai</text>
|
||||
<circle cx="509.4" cy="215.7" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.04s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.04s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="509.4" cy="215.7" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.84s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.84s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="509.4" cy="215.7" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="509.4" y="227.7" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Lagos</text>
|
||||
<circle cx="602.2" cy="232.8" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.12s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.12s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="602.2" cy="232.8" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.92s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.92s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="602.2" cy="232.8" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="602.2" y="244.8" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Nairobi</text>
|
||||
<circle cx="551.1" cy="307.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.20s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.20s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="551.1" cy="307.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.00s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.00s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="551.1" cy="307.2" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="702.5" cy="187.7" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.12s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.12s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="702.5" cy="187.7" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.92s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="1.92s" repeatCount="indefinite"/></circle>
|
||||
<text x="551.1" y="319.2" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Cape Town</text>
|
||||
<circle cx="702.5" cy="187.7" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.28s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.28s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="702.5" cy="187.7" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.08s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.08s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="702.5" cy="187.7" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="788.3" cy="227.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.20s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.20s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="788.3" cy="227.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.00s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.00s" repeatCount="indefinite"/></circle>
|
||||
<text x="702.5" y="179.7" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Mumbai</text>
|
||||
<circle cx="788.3" cy="227.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.36s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.36s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="788.3" cy="227.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.16s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.16s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="788.3" cy="227.2" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="796.7" cy="243.6" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.28s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.28s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="796.7" cy="243.6" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.08s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.08s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="796.7" cy="243.6" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="782.5" cy="223.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.36s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.36s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="782.5" cy="223.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.16s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.16s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="782.5" cy="223.2" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="788.3" y="239.2" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Singapore</text>
|
||||
<circle cx="920.0" cy="307.2" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.44s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.44s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="920.0" cy="307.2" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.24s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.24s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="920.0" cy="307.2" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="852.5" cy="143.6" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.52s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.52s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="852.5" cy="143.6" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.32s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.32s" repeatCount="indefinite"/></circle>
|
||||
<text x="920.0" y="319.2" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Sydney</text>
|
||||
<circle cx="888.1" cy="148.3" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.52s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.52s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="888.1" cy="148.3" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.32s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.32s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="888.1" cy="148.3" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="888.1" y="140.3" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Tokyo</text>
|
||||
<circle cx="852.5" cy="143.6" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.60s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.60s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="852.5" cy="143.6" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.40s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.40s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="852.5" cy="143.6" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<circle cx="817.2" cy="180.3" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.60s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.60s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="817.2" cy="180.3" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.40s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.40s" repeatCount="indefinite"/></circle>
|
||||
<text x="852.5" y="135.6" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Seoul</text>
|
||||
<circle cx="817.2" cy="180.3" r="4" fill="none" stroke="#22C55E" stroke-width="1.5"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="1.68s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.6;0;0.6" dur="2.4s" begin="1.68s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="817.2" cy="180.3" r="4" fill="none" stroke="#22C55E" stroke-width="1"><animate attributeName="r" values="4;13;4" dur="2.4s" begin="2.48s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" begin="2.48s" repeatCount="indefinite"/></circle>
|
||||
<circle cx="817.2" cy="180.3" r="4.5" fill="#22C55E" stroke="#0a1628" stroke-width="1.5"/>
|
||||
<text x="817.2" y="172.3" font-family="Inter,sans-serif" font-size="8.5" fill="#6ee7a0" text-anchor="middle" opacity="0.85">Hong Kong</text>
|
||||
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="dc-grid" style="display:flex;gap:12px;width:100%;margin-bottom:2.5rem">
|
||||
<div class="rounded-xl p-4 text-center card-hover" data-lon="-77.4" style="background:#0d1f10;border:1px solid rgba(34,197,94,0.25);flex:1;min-width:0">
|
||||
<div class="text-2xl mb-1.5">🇺🇸</div>
|
||||
<div class="text-white font-semibold text-sm">Virginia</div>
|
||||
<div class="text-gray-500 text-xs mb-2">US East</div>
|
||||
<div class="flex items-center justify-center gap-1.5 text-xs text-accent"><span class="w-1.5 h-1.5 rounded-full bg-accent inline-block"></span>Live</div>
|
||||
<!-- Self-hosted -->
|
||||
<div class="rounded-xl p-4 text-center card-hover" style="background:#1f0a0a;border:1px solid rgba(239,68,68,0.35);flex:1;min-width:0">
|
||||
<div class="text-2xl mb-1.5">🖥️</div>
|
||||
<div class="text-white font-semibold text-sm">Self-hosted</div>
|
||||
<div class="text-gray-500 text-xs mb-2">Your machine. Your rules.</div>
|
||||
<div class="flex items-center justify-center gap-1.5 text-xs mb-3" style="color:#EF4444"><span class="w-1.5 h-1.5 rounded-full inline-block" style="background:#EF4444"></span>Free forever</div>
|
||||
<a href="/install" class="block w-full text-center text-xs font-semibold py-1.5 px-3 rounded-lg transition-colors" style="background:rgba(239,68,68,0.15);color:#EF4444;border:1px solid rgba(239,68,68,0.3)" onmouseover="this.style.background='rgba(239,68,68,0.25)'" onmouseout="this.style.background='rgba(239,68,68,0.15)'">Download now →</a>
|
||||
</div>
|
||||
<div class="rounded-xl p-4 text-center card-hover" data-lon="8.5" style="background:#3d2e00;border:1px solid rgba(212,175,55,0.5);flex:1;min-width:0">
|
||||
<div class="text-2xl mb-2">🇨🇭</div>
|
||||
<div class="text-white font-semibold text-sm">Zürich</div>
|
||||
<div class="text-gray-500 text-xs mb-2">EU Central</div>
|
||||
<div class="flex items-center justify-center gap-1.5 text-xs" style="color:#D4AF37"><span class="w-1.5 h-1.5 rounded-full inline-block" style="background:#D4AF37"></span>Live</div>
|
||||
<!-- Zürich HQ -->
|
||||
<div class="rounded-xl p-4 text-center card-hover" style="background:#3d2e00;border:1px solid rgba(212,175,55,0.5);flex:1;min-width:0">
|
||||
<div class="text-2xl mb-1.5">🇨🇭</div>
|
||||
<div class="text-white font-semibold text-sm">Zürich, Switzerland</div>
|
||||
<div class="text-gray-500 text-xs mb-2">Capital of Security</div>
|
||||
<div class="flex items-center justify-center gap-1.5 text-xs mb-3" style="color:#D4AF37"><span class="w-1.5 h-1.5 rounded-full inline-block" style="background:#D4AF37"></span>Headquarters</div>
|
||||
<a href="/signup?region=eu-central-2" class="block w-full text-center text-xs font-semibold py-1.5 px-3 rounded-lg transition-colors" style="background:rgba(212,175,55,0.15);color:#D4AF37;border:1px solid rgba(212,175,55,0.3)" onmouseover="this.style.background='rgba(212,175,55,0.25)'" onmouseout="this.style.background='rgba(212,175,55,0.15)'">Buy now →</a>
|
||||
</div>
|
||||
<div class="rounded-xl p-4 text-center card-hover" data-lon="151.2" style="background:#0d1f10;border:1px solid rgba(34,197,94,0.25);flex:1;min-width:0">
|
||||
<div class="text-2xl mb-2">🇦🇺</div>
|
||||
<div class="text-white font-semibold text-sm">Sydney</div>
|
||||
<div class="text-gray-500 text-xs mb-2">Asia Pacific South</div>
|
||||
<div class="flex items-center justify-center gap-1.5 text-xs text-accent"><span class="w-1.5 h-1.5 rounded-full bg-accent inline-block"></span>Live</div>
|
||||
<!-- Closest POP — populated by JS -->
|
||||
<div id="closest-pop" class="rounded-xl p-4 text-center card-hover" style="background:#0d1f10;border:1px solid rgba(34,197,94,0.25);flex:1;min-width:0">
|
||||
<div class="text-2xl mb-1.5">📍</div>
|
||||
<div id="closest-name" class="text-white font-semibold text-sm">Nearest region</div>
|
||||
<div id="closest-sub" class="text-gray-500 text-xs mb-2">Locating you…</div>
|
||||
<div class="flex items-center justify-center gap-1.5 text-xs mb-3 text-accent"><span class="w-1.5 h-1.5 rounded-full bg-accent inline-block"></span>Closest to you</div>
|
||||
<a id="closest-buy" href="/signup" class="block w-full text-center text-xs font-semibold py-1.5 px-3 rounded-lg transition-colors" style="background:rgba(34,197,94,0.15);color:#22C55E;border:1px solid rgba(34,197,94,0.3)" onmouseover="this.style.background='rgba(34,197,94,0.25)'" onmouseout="this.style.background='rgba(34,197,94,0.15)'">Buy now →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Why it matters -->
|
||||
<section class="py-16 border-t border-white/5">
|
||||
<div class="max-w-7xl mx-auto px-6">
|
||||
|
||||
<div class="text-center mb-14 max-w-3xl mx-auto">
|
||||
<p class="text-green-400 text-xs font-mono uppercase tracking-widest mb-3">The security model</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-white mb-4">Your AI needs access.<br>Not to everything.</h2>
|
||||
<p class="text-gray-400 text-lg leading-relaxed max-w-2xl mx-auto">A password manager that blocks AI agents is useless in 2025. But one that hands them everything is a liability. vault1984 solves this with two layers.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6 mb-14 max-w-5xl mx-auto">
|
||||
<div class="rounded-2xl p-6 border border-white/5" style="background:#0d1a0d">
|
||||
<p class="text-green-400 text-xs font-mono uppercase tracking-widest mb-3">Sealed fields</p>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Only you. Only in person.</h3>
|
||||
<p class="text-gray-400 leading-relaxed">Passwords and private notes are encrypted on your device with a key derived from your fingerprint or hardware token. We store a locked box. No key ever reaches our servers. Not a court order. Not your AI assistant. Sealed fields require your physical presence to unlock.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl p-6 border border-white/5" style="background:#0d1627">
|
||||
<p class="text-blue-400 text-xs font-mono uppercase tracking-widest mb-3">Agent fields</p>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Your AI, scoped and controlled.</h3>
|
||||
<p class="text-gray-400 leading-relaxed">Fields you designate as agent-accessible are encrypted on our servers. You issue scoped tokens — Claude gets your GitHub token, nothing else. Revoke at any time. The agent never sees sealed fields, no matter what.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-xs font-mono uppercase tracking-widest mb-3" style="color:#D4AF37">Why Zürich</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-white mb-4">Sealed fields: jurisdiction irrelevant.<br>Agent fields: it isn't.</h2>
|
||||
<p class="text-gray-400 text-lg leading-relaxed mb-6">Sealed fields are protected by math — the server's location doesn't matter. But agent fields live on a server in a jurisdiction. A US server is subject to the CLOUD Act. A UK server to the Investigatory Powers Act. Zürich is subject to Swiss law — which does not cooperate with foreign government data requests. No backdoors. Both layers protected.</p>
|
||||
<div class="grid md:grid-cols-3 gap-4 max-w-5xl mx-auto">
|
||||
<div class="rounded-xl p-4 border border-white/5" style="background:#1a0a0a">
|
||||
<p class="text-red-400 text-xs font-mono uppercase tracking-widest mb-2">Self-hosted · US</p>
|
||||
<p class="text-gray-300 text-sm leading-relaxed">Your server, your rules — until a court says otherwise. CLOUD Act applies to US persons regardless of encryption.</p>
|
||||
</div>
|
||||
<div class="rounded-xl p-4 border border-white/5" style="background:#0d1627">
|
||||
<p class="text-blue-400 text-xs font-mono uppercase tracking-widest mb-2">Self-hosted · anywhere</p>
|
||||
<p class="text-gray-300 text-sm leading-relaxed">Full control. Your infrastructure, your jurisdiction. The right choice if you know what you're doing.</p>
|
||||
</div>
|
||||
<div class="rounded-xl p-4" style="background:#3d2e00;border:1px solid rgba(212,175,55,0.3)">
|
||||
<p class="text-xs font-mono uppercase tracking-widest mb-2" style="color:#D4AF37">Hosted · Zürich, Switzerland</p>
|
||||
<p class="text-gray-300 text-sm leading-relaxed">Swiss law. Swiss courts. No CLOUD Act. No backdoors. We handle the infrastructure — you get the protection.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Access Methods -->
|
||||
<!-- Access Methods -->
|
||||
<section class="py-8 border-t border-white/5">
|
||||
<div class="max-w-7xl mx-auto px-6 text-center">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4">Your agent and you — same vault, right access</h2>
|
||||
|
|
@ -424,67 +489,75 @@
|
|||
svg.appendChild(label);
|
||||
}
|
||||
|
||||
const POPS = [
|
||||
{name:"Washington D.C.", region:"us-east-1", lat:37.5, lon:-77.5},
|
||||
{name:"San Francisco", region:"us-west-1", lat:37.8, lon:-122.4},
|
||||
{name:"Montréal", region:"ca-central-1", lat:45.5, lon:-73.6},
|
||||
{name:"Mexico City", region:"mx-central-1", lat:19.4, lon:-99.1},
|
||||
{name:"Bogotá", region:"sa-bogota", lat:4.7, lon:-74.1},
|
||||
{name:"São Paulo", region:"sa-east-1", lat:-23.6, lon:-46.6},
|
||||
{name:"Santiago", region:"sa-west-1", lat:-33.4, lon:-70.6},
|
||||
{name:"London", region:"eu-west-2", lat:51.5, lon:-0.1},
|
||||
{name:"Zürich", region:"eu-central-2", lat:47.4, lon:8.5},
|
||||
{name:"Madrid", region:"eu-south-2", lat:40.4, lon:-3.7},
|
||||
{name:"Stockholm", region:"eu-north-1", lat:59.3, lon:18.1},
|
||||
{name:"Istanbul", region:"tr-west-1", lat:41.0, lon:28.9},
|
||||
{name:"Dubai", region:"me-central-1", lat:25.2, lon:55.3},
|
||||
{name:"Lagos", region:"af-west-1", lat:6.5, lon:3.4},
|
||||
{name:"Nairobi", region:"af-east-1", lat:-1.3, lon:36.8},
|
||||
{name:"Cape Town", region:"af-south-1", lat:-33.9, lon:18.4},
|
||||
{name:"Mumbai", region:"ap-south-1", lat:19.1, lon:72.9},
|
||||
{name:"Singapore", region:"ap-southeast-1", lat:1.3, lon:103.8},
|
||||
{name:"Sydney", region:"ap-southeast-2", lat:-33.9, lon:151.2},
|
||||
{name:"Tokyo", region:"ap-northeast-1", lat:35.7, lon:139.7},
|
||||
{name:"Seoul", region:"ap-northeast-2", lat:37.6, lon:126.9},
|
||||
{name:"Hong Kong", region:"ap-east-1", lat:22.3, lon:114.2},
|
||||
];
|
||||
|
||||
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 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 city = d.city || 'You';
|
||||
const country = d.country_name || '';
|
||||
const region = d.region || '';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'rounded-xl p-4 text-center card-hover';
|
||||
card.setAttribute('data-lon', d.longitude);
|
||||
card.style.cssText = 'background:#1f0a0a;border:1px solid rgba(239,68,68,0.35);flex:1;min-width:0';
|
||||
card.innerHTML = `
|
||||
<div class="text-2xl mb-2">${flag}</div>
|
||||
<div class="text-white font-semibold text-sm">${city}</div>
|
||||
<div class="text-gray-500 text-xs">${country}</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>`;
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
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.name;
|
||||
if (subEl) subEl.textContent = d.city ? `~${d.city}` : 'Your region';
|
||||
if (buyEl) buyEl.href = `/signup?region=${closest.region}`;
|
||||
}
|
||||
|
||||
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(() => {});
|
||||
// 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>
|
||||
</body>
|
||||
|
|
|
|||
26
main.go
26
main.go
|
|
@ -2,22 +2,44 @@ package main
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed *.html *.svg *.css
|
||||
var static embed.FS
|
||||
|
||||
func geoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
// Strip port
|
||||
if i := strings.LastIndex(ip, ":"); i >= 0 {
|
||||
ip = ip[:i]
|
||||
}
|
||||
ip = strings.Trim(ip, "[]")
|
||||
|
||||
resp, err := http.Get("https://ipapi.co/" + ip + "/json/")
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"geo failed"}`, 502)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8099"
|
||||
}
|
||||
|
||||
http.HandleFunc("/geo", geoHandler)
|
||||
http.Handle("/", http.FileServer(http.FS(static)))
|
||||
|
||||
log.Printf("vault1984-web starting on :%s", port)
|
||||
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
|||
BIN
vault1984-web
BIN
vault1984-web
Binary file not shown.
Loading…
Reference in New Issue