feat: landing page at /
This commit is contained in:
parent
7b38ff192e
commit
d11488ecbf
|
|
@ -0,0 +1,531 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="dark scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PingQL — Uptime monitoring that thinks like a developer</title>
|
||||||
|
<meta name="description" content="Monitor uptime with a MongoDB-style query language. Inspect JSON, HTML, headers, certs, and more — not just ping.">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
mono: ['JetBrains Mono', 'SF Mono', 'Fira Code', 'ui-monospace', 'monospace'],
|
||||||
|
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
brand: '#3b82f6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
body { background: #0a0a0a; }
|
||||||
|
|
||||||
|
/* Grid background */
|
||||||
|
.grid-bg {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px);
|
||||||
|
background-size: 64px 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero glow */
|
||||||
|
.hero-glow {
|
||||||
|
background: radial-gradient(ellipse 60% 40% at 50% 0%, rgba(59, 130, 246, 0.12) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal window */
|
||||||
|
.terminal {
|
||||||
|
background: #111111;
|
||||||
|
border: 1px solid #1e1e1e;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 0 60px rgba(59, 130, 246, 0.06), 0 20px 60px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.terminal-bar {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-bottom: 1px solid #1e1e1e;
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.terminal-dot {
|
||||||
|
width: 12px; height: 12px; border-radius: 50%;
|
||||||
|
}
|
||||||
|
.terminal-body {
|
||||||
|
padding: 20px 24px;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.7;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syntax highlighting */
|
||||||
|
.syn-key { color: #93c5fd; }
|
||||||
|
.syn-str { color: #86efac; }
|
||||||
|
.syn-num { color: #fbbf24; }
|
||||||
|
.syn-op { color: #c084fc; }
|
||||||
|
.syn-brace { color: #6b7280; }
|
||||||
|
.syn-comment { color: #4b5563; font-style: italic; }
|
||||||
|
|
||||||
|
/* Fade-in animation */
|
||||||
|
@keyframes fade-up {
|
||||||
|
from { opacity: 0; transform: translateY(24px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.fade-up {
|
||||||
|
animation: fade-up 0.7s ease-out both;
|
||||||
|
}
|
||||||
|
.fade-up-delay-1 { animation-delay: 0.1s; }
|
||||||
|
.fade-up-delay-2 { animation-delay: 0.2s; }
|
||||||
|
.fade-up-delay-3 { animation-delay: 0.3s; }
|
||||||
|
.fade-up-delay-4 { animation-delay: 0.4s; }
|
||||||
|
|
||||||
|
/* Glow card */
|
||||||
|
.glow-card {
|
||||||
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(17, 17, 17, 1) 50%);
|
||||||
|
border: 1px solid #1e1e1e;
|
||||||
|
transition: border-color 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
.glow-card:hover {
|
||||||
|
border-color: rgba(59, 130, 246, 0.3);
|
||||||
|
box-shadow: 0 0 30px rgba(59, 130, 246, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comparison bar */
|
||||||
|
.ping-bar {
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pulse animation */
|
||||||
|
@keyframes pulse-dot {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
.pulse-dot {
|
||||||
|
animation: pulse-dot 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typing cursor */
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 50% { opacity: 1; }
|
||||||
|
51%, 100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
.cursor {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 18px;
|
||||||
|
background: #3b82f6;
|
||||||
|
animation: blink 1s step-end infinite;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-[#0a0a0a] text-gray-100 font-sans antialiased grid-bg">
|
||||||
|
|
||||||
|
<!-- ─── HERO ─── -->
|
||||||
|
<section class="relative min-h-screen flex items-center hero-glow">
|
||||||
|
<div class="max-w-6xl mx-auto px-6 py-24 w-full">
|
||||||
|
<div class="grid lg:grid-cols-2 gap-16 items-center">
|
||||||
|
<!-- Left -->
|
||||||
|
<div class="fade-up">
|
||||||
|
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-gray-800 text-xs text-gray-400 mb-6 font-mono">
|
||||||
|
<span class="w-2 h-2 rounded-full bg-green-500 pulse-dot"></span>
|
||||||
|
All systems operational
|
||||||
|
</div>
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight leading-[1.1] mb-6">
|
||||||
|
Uptime monitoring<br>that <span class="text-brand">thinks like<br>a developer</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-gray-400 max-w-lg mb-8 leading-relaxed">
|
||||||
|
Go beyond simple pings. Write queries against status codes, JSON bodies, HTML selectors, headers, cert expiry — everything your response contains.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
<a href="/dashboard" class="inline-flex items-center gap-2 px-6 py-3 bg-brand hover:bg-blue-500 text-white font-medium rounded-lg transition-colors text-sm">
|
||||||
|
Get Started Free
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6"/></svg>
|
||||||
|
</a>
|
||||||
|
<a href="/docs" class="inline-flex items-center gap-2 px-6 py-3 border border-gray-700 hover:border-gray-500 text-gray-300 font-medium rounded-lg transition-colors text-sm">
|
||||||
|
Read the Docs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right: terminal -->
|
||||||
|
<div class="fade-up fade-up-delay-2">
|
||||||
|
<div class="terminal">
|
||||||
|
<div class="terminal-bar">
|
||||||
|
<div class="terminal-dot bg-[#ff5f57]"></div>
|
||||||
|
<div class="terminal-dot bg-[#ffbd2e]"></div>
|
||||||
|
<div class="terminal-dot bg-[#28c840]"></div>
|
||||||
|
<span class="ml-3 text-xs text-gray-500 font-mono">query.json</span>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-body">
|
||||||
|
<pre><span class="syn-brace">{</span>
|
||||||
|
<span class="syn-key">"$and"</span><span class="syn-brace">:</span> <span class="syn-brace">[</span>
|
||||||
|
<span class="syn-brace">{</span> <span class="syn-key">"status"</span><span class="syn-brace">:</span> <span class="syn-brace">{</span> <span class="syn-op">"$lt"</span><span class="syn-brace">:</span> <span class="syn-num">400</span> <span class="syn-brace">}</span> <span class="syn-brace">}</span>,
|
||||||
|
<span class="syn-brace">{</span> <span class="syn-op">"$json"</span><span class="syn-brace">:</span> <span class="syn-brace">{</span> <span class="syn-str">"$.latency"</span><span class="syn-brace">:</span> <span class="syn-brace">{</span> <span class="syn-op">"$lt"</span><span class="syn-brace">:</span> <span class="syn-num">200</span> <span class="syn-brace">}</span> <span class="syn-brace">}</span> <span class="syn-brace">}</span>,
|
||||||
|
<span class="syn-brace">{</span> <span class="syn-op">"$certExpiry"</span><span class="syn-brace">:</span> <span class="syn-brace">{</span> <span class="syn-op">"$gt"</span><span class="syn-brace">:</span> <span class="syn-num">14</span> <span class="syn-brace">}</span> <span class="syn-brace">}</span>
|
||||||
|
<span class="syn-brace">]</span>
|
||||||
|
<span class="syn-brace">}</span></pre>
|
||||||
|
<div class="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-500">
|
||||||
|
<span class="syn-comment">// Status < 400 AND latency < 200ms AND cert valid > 14 days</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── NOT JUST PING ─── -->
|
||||||
|
<section class="py-24 px-6">
|
||||||
|
<div class="max-w-5xl mx-auto">
|
||||||
|
<div class="text-center mb-16">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-bold tracking-tight mb-4">Not just ping</h2>
|
||||||
|
<p class="text-gray-400 max-w-2xl mx-auto text-lg">
|
||||||
|
Most monitoring tools ping a URL and check if it returns 200. That tells you almost nothing.
|
||||||
|
PingQL lets you query the <em>entire</em> response.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comparison -->
|
||||||
|
<div class="grid md:grid-cols-2 gap-8 max-w-3xl mx-auto">
|
||||||
|
<!-- Others -->
|
||||||
|
<div class="rounded-xl border border-gray-800 bg-[#111] p-6">
|
||||||
|
<div class="text-sm text-gray-500 uppercase tracking-wider mb-4 font-mono">Others</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-3 h-3 rounded-full bg-green-500 shrink-0"></span>
|
||||||
|
<span class="text-gray-400 text-sm">GET /api/health → 200</span>
|
||||||
|
<span class="ml-auto text-green-500 text-xs font-mono">UP</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="w-3 h-3 rounded-full bg-red-500 shrink-0"></span>
|
||||||
|
<span class="text-gray-400 text-sm">GET /api/health → 500</span>
|
||||||
|
<span class="ml-auto text-red-500 text-xs font-mono">DOWN</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-500">
|
||||||
|
That's it. That's all you get.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PingQL -->
|
||||||
|
<div class="rounded-xl border border-blue-500/30 bg-[#111] p-6 shadow-[0_0_40px_rgba(59,130,246,0.06)]">
|
||||||
|
<div class="text-sm text-brand uppercase tracking-wider mb-4 font-mono">PingQL</div>
|
||||||
|
<div class="space-y-3 text-sm font-mono">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-green-400">✓</span>
|
||||||
|
<span class="text-gray-300">Status code assertions</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-green-400">✓</span>
|
||||||
|
<span class="text-gray-300">JSON body inspection</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-green-400">✓</span>
|
||||||
|
<span class="text-gray-300">HTML / CSS selector matching</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-green-400">✓</span>
|
||||||
|
<span class="text-gray-300">Response time thresholds</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-green-400">✓</span>
|
||||||
|
<span class="text-gray-300">SSL cert expiry checks</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-green-400">✓</span>
|
||||||
|
<span class="text-gray-300">Header & regex matching</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── FEATURES ─── -->
|
||||||
|
<section class="py-24 px-6">
|
||||||
|
<div class="max-w-6xl mx-auto">
|
||||||
|
<div class="text-center mb-16">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-bold tracking-tight mb-4">Everything you need</h2>
|
||||||
|
<p class="text-gray-400 text-lg">Powerful primitives, zero bloat.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<!-- Feature 1 -->
|
||||||
|
<div class="glow-card rounded-xl p-6">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||||
|
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"/></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-white mb-2">MongoDB-style queries</h3>
|
||||||
|
<p class="text-sm text-gray-400 leading-relaxed">Use <code class="text-brand font-mono text-xs">$and</code>, <code class="text-brand font-mono text-xs">$or</code>, <code class="text-brand font-mono text-xs">$lt</code>, <code class="text-brand font-mono text-xs">$gt</code>, <code class="text-brand font-mono text-xs">$regex</code> — a query language you already know.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 2 -->
|
||||||
|
<div class="glow-card rounded-xl p-6">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||||
|
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"/></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-white mb-2">CSS selector parsing</h3>
|
||||||
|
<p class="text-sm text-gray-400 leading-relaxed">Monitor any public page with no API. Use <code class="text-brand font-mono text-xs">$css</code> to extract text from HTML elements.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 3 -->
|
||||||
|
<div class="glow-card rounded-xl p-6">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||||
|
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"/></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-white mb-2">JSONPath inspection</h3>
|
||||||
|
<p class="text-sm text-gray-400 leading-relaxed">Drill into API responses with <code class="text-brand font-mono text-xs">$json</code> and JSONPath expressions. Assert on any nested field.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 4 -->
|
||||||
|
<div class="glow-card rounded-xl p-6">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||||
|
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"/></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-white mb-2">SSL cert monitoring</h3>
|
||||||
|
<p class="text-sm text-gray-400 leading-relaxed">Built-in <code class="text-brand font-mono text-xs">$certExpiry</code> operator. Get alerted before your cert expires, not after.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 5 -->
|
||||||
|
<div class="glow-card rounded-xl p-6">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||||
|
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-white mb-2">Response time assertions</h3>
|
||||||
|
<p class="text-sm text-gray-400 leading-relaxed">Set thresholds on <code class="text-brand font-mono text-xs">responseTime</code>. Know when your API is slow, not just down.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 6 -->
|
||||||
|
<div class="glow-card rounded-xl p-6">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||||
|
<svg class="w-5 h-5 text-brand" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-semibold text-white mb-2">No-login account keys</h3>
|
||||||
|
<p class="text-sm text-gray-400 leading-relaxed">No email required. Get an account key, start monitoring. Privacy first, always.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── PRIVACY ─── -->
|
||||||
|
<section class="py-24 px-6">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<div class="rounded-2xl border border-gray-800 bg-gradient-to-b from-[#111] to-[#0a0a0a] p-8 sm:p-12">
|
||||||
|
<div class="flex items-start gap-4 mb-8">
|
||||||
|
<div class="w-12 h-12 rounded-xl bg-green-500/10 border border-green-500/20 flex items-center justify-center shrink-0">
|
||||||
|
<svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-3xl font-bold tracking-tight mb-2">Privacy by design</h2>
|
||||||
|
<p class="text-gray-400">We built PingQL for developers who care about their data.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid sm:grid-cols-2 gap-6">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-green-400 mt-0.5 shrink-0 font-mono text-sm">$</span>
|
||||||
|
<div>
|
||||||
|
<p class="text-white font-medium text-sm">No tracking, no analytics, no ads</p>
|
||||||
|
<p class="text-gray-500 text-xs mt-1">Zero third-party scripts. Zero cookies.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-green-400 mt-0.5 shrink-0 font-mono text-sm">$</span>
|
||||||
|
<div>
|
||||||
|
<p class="text-white font-medium text-sm">Account keys, not passwords</p>
|
||||||
|
<p class="text-gray-500 text-xs mt-1">Be completely anonymous. No email required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-green-400 mt-0.5 shrink-0 font-mono text-sm">$</span>
|
||||||
|
<div>
|
||||||
|
<p class="text-white font-medium text-sm">Emails hashed if provided</p>
|
||||||
|
<p class="text-gray-500 text-xs mt-1">Optional email for recovery only. We hash it.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-green-400 mt-0.5 shrink-0 font-mono text-sm">$</span>
|
||||||
|
<div>
|
||||||
|
<p class="text-white font-medium text-sm">We never sell data</p>
|
||||||
|
<p class="text-gray-500 text-xs mt-1">Your monitors, your data. Period.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 pt-6 border-t border-gray-800 flex items-center gap-2 text-xs text-gray-500">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"/></svg>
|
||||||
|
Hosted in EU (Prague) — GDPR compliant
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── API EXAMPLE ─── -->
|
||||||
|
<section class="py-24 px-6">
|
||||||
|
<div class="max-w-5xl mx-auto">
|
||||||
|
<div class="text-center mb-16">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-bold tracking-tight mb-4">API-first, always</h2>
|
||||||
|
<p class="text-gray-400 text-lg max-w-2xl mx-auto">Create monitors, query results, and manage everything from your terminal.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid lg:grid-cols-2 gap-6">
|
||||||
|
<!-- Create monitor -->
|
||||||
|
<div class="terminal">
|
||||||
|
<div class="terminal-bar">
|
||||||
|
<div class="terminal-dot bg-[#ff5f57]"></div>
|
||||||
|
<div class="terminal-dot bg-[#ffbd2e]"></div>
|
||||||
|
<div class="terminal-dot bg-[#28c840]"></div>
|
||||||
|
<span class="ml-3 text-xs text-gray-500 font-mono">Create a monitor</span>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-body text-xs sm:text-[13px]">
|
||||||
|
<pre><span class="text-gray-500">$</span> <span class="text-white">curl</span> -X POST https://pingql.com/api/monitors \
|
||||||
|
-H <span class="syn-str">"X-Key: XXXX-XXXX-XXXX-XXXX"</span> \
|
||||||
|
-d <span class="syn-str">'{
|
||||||
|
"url": "https://api.example.com/health",
|
||||||
|
"interval": 60,
|
||||||
|
"query": {
|
||||||
|
"status": { "$lt": 400 },
|
||||||
|
"$json": {
|
||||||
|
"$.ok": { "$eq": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'</span>
|
||||||
|
|
||||||
|
<span class="text-gray-500">// → 201 Created</span>
|
||||||
|
<span class="syn-brace">{</span> <span class="syn-key">"id"</span>: <span class="syn-str">"mon_a1b2c3"</span>, <span class="syn-key">"status"</span>: <span class="syn-str">"active"</span> <span class="syn-brace">}</span></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get results -->
|
||||||
|
<div class="terminal">
|
||||||
|
<div class="terminal-bar">
|
||||||
|
<div class="terminal-dot bg-[#ff5f57]"></div>
|
||||||
|
<div class="terminal-dot bg-[#ffbd2e]"></div>
|
||||||
|
<div class="terminal-dot bg-[#28c840]"></div>
|
||||||
|
<span class="ml-3 text-xs text-gray-500 font-mono">Check results</span>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-body text-xs sm:text-[13px]">
|
||||||
|
<pre><span class="text-gray-500">$</span> <span class="text-white">curl</span> https://pingql.com/api/monitors/mon_a1b2c3 \
|
||||||
|
-H <span class="syn-str">"X-Key: XXXX-XXXX-XXXX-XXXX"</span>
|
||||||
|
|
||||||
|
<span class="syn-brace">{</span>
|
||||||
|
<span class="syn-key">"id"</span>: <span class="syn-str">"mon_a1b2c3"</span>,
|
||||||
|
<span class="syn-key">"url"</span>: <span class="syn-str">"https://api.example.com/health"</span>,
|
||||||
|
<span class="syn-key">"status"</span>: <span class="syn-str">"up"</span>,
|
||||||
|
<span class="syn-key">"lastCheck"</span>: <span class="syn-str">"2024-01-15T10:30:00Z"</span>,
|
||||||
|
<span class="syn-key">"responseTime"</span>: <span class="syn-num">142</span>,
|
||||||
|
<span class="syn-key">"uptime30d"</span>: <span class="syn-num">99.97</span>
|
||||||
|
<span class="syn-brace">}</span></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CLI teaser -->
|
||||||
|
<div class="mt-8 text-center">
|
||||||
|
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-gray-800 text-xs text-gray-500 font-mono">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"/></svg>
|
||||||
|
CLI coming soon — <span class="text-gray-400">pingql watch mon_a1b2c3</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── PRICING ─── -->
|
||||||
|
<section class="py-24 px-6">
|
||||||
|
<div class="max-w-3xl mx-auto text-center">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-bold tracking-tight mb-4">Simple pricing</h2>
|
||||||
|
<p class="text-gray-400 text-lg mb-12">Start for free. No credit card required.</p>
|
||||||
|
|
||||||
|
<div class="grid sm:grid-cols-2 gap-6 max-w-2xl mx-auto">
|
||||||
|
<!-- Free -->
|
||||||
|
<div class="glow-card rounded-xl p-8 text-left">
|
||||||
|
<div class="text-xs text-gray-500 uppercase tracking-wider font-mono mb-2">Free</div>
|
||||||
|
<div class="text-4xl font-bold mb-1">$0</div>
|
||||||
|
<div class="text-sm text-gray-500 mb-6">forever</div>
|
||||||
|
<ul class="space-y-3 text-sm text-gray-400">
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-green-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
Generous monitor limits
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-green-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
Full query language
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-green-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
API access
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-green-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
No credit card
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/dashboard" class="mt-8 block text-center w-full py-3 bg-brand hover:bg-blue-500 text-white text-sm font-medium rounded-lg transition-colors">
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pro -->
|
||||||
|
<div class="rounded-xl border border-gray-700 bg-[#111] p-8 text-left relative">
|
||||||
|
<div class="absolute -top-3 right-6 px-3 py-1 bg-gray-800 border border-gray-700 rounded-full text-xs text-gray-400 font-mono">
|
||||||
|
coming soon
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 uppercase tracking-wider font-mono mb-2">Pro</div>
|
||||||
|
<div class="text-4xl font-bold mb-1 text-gray-500">TBD</div>
|
||||||
|
<div class="text-sm text-gray-600 mb-6">per month</div>
|
||||||
|
<ul class="space-y-3 text-sm text-gray-500">
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-gray-600 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
More monitors
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-gray-600 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
Shorter intervals
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-gray-600 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
Webhook notifications
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-gray-600 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||||
|
Priority support
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="mt-8 block text-center w-full py-3 border border-gray-700 text-gray-500 text-sm font-medium rounded-lg cursor-default">
|
||||||
|
Coming Soon
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ─── FOOTER ─── -->
|
||||||
|
<footer class="border-t border-gray-800/50 py-12 px-6">
|
||||||
|
<div class="max-w-5xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-6">
|
||||||
|
<div class="flex items-center gap-6">
|
||||||
|
<a href="/" class="text-lg font-bold tracking-tight font-mono">Ping<span class="text-brand">QL</span></a>
|
||||||
|
<nav class="flex items-center gap-5 text-sm text-gray-500">
|
||||||
|
<a href="/dashboard" class="hover:text-gray-300 transition-colors">Dashboard</a>
|
||||||
|
<a href="/docs" class="hover:text-gray-300 transition-colors">Docs</a>
|
||||||
|
<a href="/privacy" class="hover:text-gray-300 transition-colors">Privacy</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-600">
|
||||||
|
Built by Ico ♟
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -11,7 +11,7 @@ await migrate();
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(cors())
|
.use(cors())
|
||||||
.get("/", () => ({ name: "PingQL", version: "0.1.0", docs: "/docs", dashboard: "/dashboard" }))
|
.get("/", ({ set }) => { set.headers["content-type"] = "text/html"; return Bun.file(`${import.meta.dir}/dashboard/landing.html`); })
|
||||||
.use(dashboard)
|
.use(dashboard)
|
||||||
.use(account)
|
.use(account)
|
||||||
.use(monitors)
|
.use(monitors)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue