test: improve css
This commit is contained in:
parent
437b493567
commit
c1392a7dd9
|
|
@ -2,3 +2,30 @@ body {
|
||||||
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace;
|
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace;
|
||||||
background: #0a0a0a;
|
background: #0a0a0a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Dashboard card overrides ─── */
|
||||||
|
.monitor-card {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, rgba(17, 17, 17, 0.7) 100%);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
.monitor-card:hover {
|
||||||
|
border-color: rgba(55, 55, 64, 0.8);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 0 20px rgba(59, 130, 246, 0.04);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Stat cards ─── */
|
||||||
|
.stat-card {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, rgba(17, 17, 17, 0.7) 100%);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Table rows ─── */
|
||||||
|
.table-row-alt:nth-child(even) {
|
||||||
|
background: rgba(17, 17, 17, 0.3);
|
||||||
|
}
|
||||||
|
.table-row-alt:hover {
|
||||||
|
background: rgba(30, 30, 36, 0.5);
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -5,4 +5,94 @@
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-brand: #3b82f6;
|
--color-brand: #3b82f6;
|
||||||
|
--color-surface: rgba(17, 17, 17, 0.8);
|
||||||
|
--color-surface-hover: rgba(24, 24, 27, 0.9);
|
||||||
|
--color-surface-solid: #111111;
|
||||||
|
--color-border-subtle: rgba(38, 38, 44, 0.6);
|
||||||
|
--color-border-strong: rgba(55, 55, 64, 0.8);
|
||||||
|
--shadow-glow-sm: 0 0 20px rgba(59, 130, 246, 0.06);
|
||||||
|
--shadow-glow-md: 0 0 40px rgba(59, 130, 246, 0.1);
|
||||||
|
--shadow-glow-green: 0 0 12px rgba(34, 197, 94, 0.3);
|
||||||
|
--shadow-glow-red: 0 0 12px rgba(239, 68, 68, 0.3);
|
||||||
|
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
--shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.4), 0 0 20px rgba(59, 130, 246, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Card base ─── */
|
||||||
|
@layer components {
|
||||||
|
.card {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, rgba(17, 17, 17, 0.7) 100%);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
border-color: rgba(55, 55, 64, 0.8);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4), 0 0 20px rgba(59, 130, 246, 0.04);
|
||||||
|
}
|
||||||
|
.card-static {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, rgba(17, 17, 17, 0.7) 100%);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Input focus glow ─── */
|
||||||
|
.input-base {
|
||||||
|
background: rgba(17, 17, 17, 0.6);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.input-base:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1), 0 0 20px rgba(59, 130, 246, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Buttons ─── */
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%);
|
||||||
|
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.btn-secondary {
|
||||||
|
background: rgba(17, 17, 17, 0.6);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
color: #d1d5db;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(24, 24, 27, 0.8);
|
||||||
|
border-color: rgba(55, 55, 64, 0.8);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Status dot glow ─── */
|
||||||
|
.dot-up {
|
||||||
|
background: #22c55e;
|
||||||
|
box-shadow: 0 0 8px rgba(34, 197, 94, 0.4);
|
||||||
|
}
|
||||||
|
.dot-down {
|
||||||
|
background: #ef4444;
|
||||||
|
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
||||||
|
}
|
||||||
|
.dot-unknown {
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Section dividers ─── */
|
||||||
|
.divider {
|
||||||
|
border-color: rgba(38, 38, 44, 0.4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,13 @@
|
||||||
|
|
||||||
<div class="plan-labels grid grid-cols-2 gap-4">
|
<div class="plan-labels grid grid-cols-2 gap-4">
|
||||||
<label for="plan-pro"
|
<label for="plan-pro"
|
||||||
class="cursor-pointer text-left bg-gray-900 border-2 border-gray-800 hover:border-blue-500/50 rounded-xl p-5 transition-colors">
|
class="cursor-pointer text-left card-static hover:border-blue-500/40 border-2 !border-border-subtle p-5 transition-colors">
|
||||||
<div class="text-xs text-gray-500 uppercase tracking-wider font-mono mb-1">Pro</div>
|
<div class="text-xs text-gray-500 uppercase tracking-wider font-mono mb-1">Pro</div>
|
||||||
<div class="text-2xl font-bold text-gray-100">$12<span class="text-sm font-normal text-gray-500">/mo</span></div>
|
<div class="text-2xl font-bold text-gray-100">$12<span class="text-sm font-normal text-gray-500">/mo</span></div>
|
||||||
<div class="text-xs text-gray-500 mt-2">500 monitors, 2s intervals</div>
|
<div class="text-xs text-gray-500 mt-2">500 monitors, 2s intervals</div>
|
||||||
</label>
|
</label>
|
||||||
<label for="plan-lifetime"
|
<label for="plan-lifetime"
|
||||||
class="cursor-pointer text-left bg-gray-900 border-2 border-gray-800 hover:border-yellow-500/50 rounded-xl p-5 transition-colors">
|
class="cursor-pointer text-left card-static hover:border-yellow-500/40 border-2 !border-border-subtle p-5 transition-colors">
|
||||||
<div class="text-xs text-yellow-500/70 uppercase tracking-wider font-mono mb-1">Lifetime</div>
|
<div class="text-xs text-yellow-500/70 uppercase tracking-wider font-mono mb-1">Lifetime</div>
|
||||||
<div class="text-2xl font-bold text-gray-100">$140</div>
|
<div class="text-2xl font-bold text-gray-100">$140</div>
|
||||||
<div class="text-xs text-gray-500 mt-2">One-time, forever</div>
|
<div class="text-xs text-gray-500 mt-2">One-time, forever</div>
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
<!-- Months (visible only when pro selected, via CSS) -->
|
<!-- Months (visible only when pro selected, via CSS) -->
|
||||||
<div id="months-section" class="hidden">
|
<div id="months-section" class="hidden">
|
||||||
<label class="block text-sm text-gray-400 mb-2">How many months?</label>
|
<label class="block text-sm text-gray-400 mb-2">How many months?</label>
|
||||||
<select name="months" class="bg-gray-900 border border-gray-800 rounded-lg px-4 py-2.5 text-gray-100 focus:outline-none focus:border-blue-500">
|
<select name="months" class="input-base px-4 py-2.5 text-gray-100">
|
||||||
<% for (let i = 1; i <= 12; i++) { %>
|
<% for (let i = 1; i <= 12; i++) { %>
|
||||||
<option value="<%= i %>"><%= i %> month<%= i > 1 ? 's' : '' %> — $<%= i * 12 %></option>
|
<option value="<%= i %>"><%= i %> month<%= i > 1 ? 's' : '' %> — $<%= i * 12 %></option>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
<% coins.forEach(function(c, i) { %>
|
<% coins.forEach(function(c, i) { %>
|
||||||
<input type="radio" name="coin" value="<%= c.id %>" id="coin-<%= c.id %>" class="hidden coin-radio" <%= i === 0 ? 'checked' : '' %>>
|
<input type="radio" name="coin" value="<%= c.id %>" id="coin-<%= c.id %>" class="hidden coin-radio" <%= i === 0 ? 'checked' : '' %>>
|
||||||
<label for="coin-<%= c.id %>"
|
<label for="coin-<%= c.id %>"
|
||||||
class="cursor-pointer flex items-center gap-2 bg-gray-800 border-2 border-gray-700 hover:border-gray-500 rounded-lg px-3 py-2.5 transition-colors text-left">
|
class="cursor-pointer flex items-center gap-2 bg-surface border-2 border-border-subtle hover:border-border-strong rounded-lg px-3 py-2.5 transition-colors text-left">
|
||||||
<span class="text-sm font-medium text-gray-300"><%= c.ticker %></span>
|
<span class="text-sm font-medium text-gray-300"><%= c.ticker %></span>
|
||||||
<span class="text-xs text-gray-600"><%= c.label %></span>
|
<span class="text-xs text-gray-600"><%= c.label %></span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="w-full bg-blue-600 hover:bg-blue-500 text-white font-medium py-3 rounded-lg transition-colors">
|
class="w-full btn-primary py-3">
|
||||||
Continue to Payment
|
Continue to Payment
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -98,11 +98,11 @@
|
||||||
<meta http-equiv="refresh" content="5">
|
<meta http-equiv="refresh" content="5">
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-6 text-center space-y-5">
|
<div class="card-static p-6 text-center space-y-5">
|
||||||
|
|
||||||
<% if (inv.status === 'confirming') { %>
|
<% if (inv.status === 'confirming') { %>
|
||||||
<!-- Loading spinner instead of QR -->
|
<!-- Loading spinner instead of QR -->
|
||||||
<div class="w-48 h-48 mx-auto rounded-lg bg-gray-800 flex items-center justify-center">
|
<div class="w-48 h-48 mx-auto rounded-lg bg-surface flex items-center justify-center">
|
||||||
<svg class="w-10 h-10 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
<svg class="w-10 h-10 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<% } else if (inv.status !== 'paid') { %>
|
<% } else if (inv.status !== 'paid') { %>
|
||||||
|
|
@ -137,7 +137,7 @@
|
||||||
<!-- Address -->
|
<!-- Address -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs text-gray-500 mb-1">Send to</label>
|
<label class="block text-xs text-gray-500 mb-1">Send to</label>
|
||||||
<code class="block bg-gray-800 border border-gray-700 rounded-lg px-3 py-2.5 text-blue-400 text-xs font-mono select-all break-all" id="pay-address"><%= inv.address %></code>
|
<code class="block bg-surface-solid border border-border-subtle rounded-lg px-3 py-2.5 text-blue-400 text-xs font-mono select-all break-all" id="pay-address"><%= inv.address %></code>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,43 +23,43 @@
|
||||||
<div class="flex items-center justify-between mb-8">
|
<div class="flex items-center justify-between mb-8">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span id="status-dot"><%~ lastPing ? (lastPing.up ? '<span class="inline-block w-2.5 h-2.5 rounded-full bg-green-400 mr-2" title="Up"></span>' : '<span class="inline-block w-2.5 h-2.5 rounded-full bg-red-400 mr-2" title="Down"></span>') : '<span class="inline-block w-2.5 h-2.5 rounded-full bg-gray-600 mr-2" title="Unknown"></span>' %></span>
|
<span id="status-dot"><%~ lastPing ? (lastPing.up ? '<span class="inline-block w-2.5 h-2.5 rounded-full dot-up mr-2" title="Up"></span>' : '<span class="inline-block w-2.5 h-2.5 rounded-full dot-down mr-2" title="Down"></span>') : '<span class="inline-block w-2.5 h-2.5 rounded-full dot-unknown mr-2" title="Unknown"></span>' %></span>
|
||||||
<h2 id="monitor-name" class="text-xl font-semibold text-gray-100"><%= m.name %></h2>
|
<h2 id="monitor-name" class="text-xl font-semibold text-gray-100"><%= m.name %></h2>
|
||||||
</div>
|
</div>
|
||||||
<p id="monitor-url" class="text-sm text-gray-500 mt-1"><%= m.url %></p>
|
<p id="monitor-url" class="text-sm text-gray-500 mt-1"><%= m.url %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<form action="/dashboard/monitors/<%= m.id %>/toggle" method="POST" class="inline">
|
<form action="/dashboard/monitors/<%= m.id %>/toggle" method="POST" class="inline">
|
||||||
<button type="submit" id="toggle-btn" class="text-sm px-4 py-2 rounded-lg border transition-colors <%= m.enabled ? 'border-gray-700 hover:border-gray-600 text-gray-300' : 'border-green-800 hover:border-green-700 text-green-400' %>"><%= m.enabled ? 'Pause' : 'Resume' %></button>
|
<button type="submit" id="toggle-btn" class="btn-secondary text-sm px-4 py-2 <%= m.enabled ? '' : '!border-green-800/50 !text-green-400 hover:!border-green-700/60' %>"><%= m.enabled ? 'Pause' : 'Resume' %></button>
|
||||||
</form>
|
</form>
|
||||||
<form action="/dashboard/monitors/<%= m.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Delete this monitor? This cannot be undone.')">
|
<form action="/dashboard/monitors/<%= m.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Delete this monitor? This cannot be undone.')">
|
||||||
<button type="submit" id="delete-btn" class="text-sm px-4 py-2 rounded-lg border border-red-900/50 text-red-400 hover:bg-red-900/20 transition-colors">Delete</button>
|
<button type="submit" id="delete-btn" class="text-sm px-4 py-2 rounded-lg border border-red-900/30 text-red-400 hover:bg-red-900/20 hover:border-red-800/40 transition-colors">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats row -->
|
<!-- Stats row -->
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
<div class="stat-card rounded-xl p-4">
|
||||||
<div class="text-xs text-gray-500 mb-1">Status</div>
|
<div class="text-xs text-gray-500 mb-1">Status</div>
|
||||||
<div id="stat-status" class="text-lg font-semibold"><%~ lastPing ? (lastPing.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>') : '<span class="text-gray-500">—</span>' %></div>
|
<div id="stat-status" class="text-lg font-semibold"><%~ lastPing ? (lastPing.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>') : '<span class="text-gray-500">—</span>' %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
<div class="stat-card rounded-xl p-4">
|
||||||
<div class="text-xs text-gray-500 mb-1">Avg Latency</div>
|
<div class="text-xs text-gray-500 mb-1">Avg Latency</div>
|
||||||
<div id="stat-latency" class="text-lg font-semibold text-gray-200"><%= avgLatency != null ? avgLatency + 'ms' : '—' %></div>
|
<div id="stat-latency" class="text-lg font-semibold text-gray-200"><%= avgLatency != null ? avgLatency + 'ms' : '—' %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
<div class="stat-card rounded-xl p-4">
|
||||||
<div class="text-xs text-gray-500 mb-1">Uptime</div>
|
<div class="text-xs text-gray-500 mb-1">Uptime</div>
|
||||||
<div id="stat-uptime" class="text-lg font-semibold text-gray-200"><%= uptime != null ? uptime + '%' : '—' %></div>
|
<div id="stat-uptime" class="text-lg font-semibold text-gray-200"><%= uptime != null ? uptime + '%' : '—' %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
<div class="stat-card rounded-xl p-4">
|
||||||
<div class="text-xs text-gray-500 mb-1">Last Ping</div>
|
<div class="text-xs text-gray-500 mb-1">Last Ping</div>
|
||||||
<div id="stat-last" class="text-lg font-semibold text-gray-200"><%~ lastPing ? it.timeAgoSSR(lastPing.checked_at) : '—' %></div>
|
<div id="stat-last" class="text-lg font-semibold text-gray-200"><%~ lastPing ? it.timeAgoSSR(lastPing.checked_at) : '—' %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Latency chart -->
|
<!-- Latency chart -->
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4 mb-8">
|
<div class="card-static p-4 mb-8">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h3 class="text-sm text-gray-400">Response Time</h3>
|
<h3 class="text-sm text-gray-400">Response Time</h3>
|
||||||
<div id="chart-legend" class="flex gap-3 text-xs text-gray-500"></div>
|
<div id="chart-legend" class="flex gap-3 text-xs text-gray-500"></div>
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
<!-- SSR SVG chart (works without JS, replaced by canvas when JS loads) -->
|
<!-- SSR SVG chart (works without JS, replaced by canvas when JS loads) -->
|
||||||
<div id="chart-ssr" class="w-full h-full"><%~ it.latencyChartSSR(chartPings) %></div>
|
<div id="chart-ssr" class="w-full h-full"><%~ it.latencyChartSSR(chartPings) %></div>
|
||||||
<canvas id="chart-canvas" class="w-full h-full hidden"></canvas>
|
<canvas id="chart-canvas" class="w-full h-full hidden"></canvas>
|
||||||
<div id="chart-tooltip" class="absolute hidden bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-xs pointer-events-none z-10 shadow-lg" style="min-width:140px"></div>
|
<div id="chart-tooltip" class="absolute hidden card-static px-3 py-2 text-xs pointer-events-none z-10" style="min-width:140px"></div>
|
||||||
<div id="chart-crosshair" class="absolute top-0 bottom-0 w-px bg-gray-600/50 pointer-events-none hidden"></div>
|
<div id="chart-crosshair" class="absolute top-0 bottom-0 w-px bg-gray-600/50 pointer-events-none hidden"></div>
|
||||||
<span id="chart-ymax" class="absolute top-1 left-2 text-gray-600 text-xs pointer-events-none"></span>
|
<span id="chart-ymax" class="absolute top-1 left-2 text-gray-600 text-xs pointer-events-none"></span>
|
||||||
<span id="chart-ymin" class="absolute bottom-1 left-2 text-gray-600 text-xs pointer-events-none"></span>
|
<span id="chart-ymin" class="absolute bottom-1 left-2 text-gray-600 text-xs pointer-events-none"></span>
|
||||||
|
|
@ -76,22 +76,22 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status bar -->
|
<!-- Status bar -->
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4 mb-8">
|
<div class="card-static p-4 mb-8">
|
||||||
<h3 class="text-sm text-gray-400 mb-3">Status History</h3>
|
<h3 class="text-sm text-gray-400 mb-3">Status History</h3>
|
||||||
<div id="status-bar" class="flex gap-0.5 h-8 rounded overflow-hidden">
|
<div id="status-bar" class="flex gap-0.5 h-8 rounded-lg overflow-hidden">
|
||||||
<% if (barPings.length > 0) { %>
|
<% if (barPings.length > 0) { %>
|
||||||
<% barPings.forEach(function(c) { %>
|
<% barPings.forEach(function(c) { %>
|
||||||
<div class="flex-1 <%= c.up ? 'bg-green-500/70' : 'bg-red-500/70' %>" title="<%= new Date(c.checked_at).toLocaleString() %> — <%= c.up ? 'Up' : 'Down' %> <%= c.latency_ms ? c.latency_ms + 'ms' : '' %>"></div>
|
<div class="flex-1 rounded-sm <%= c.up ? 'bg-green-500/70' : 'bg-red-500/70' %>" title="<%= new Date(c.checked_at).toLocaleString() %> — <%= c.up ? 'Up' : 'Down' %> <%= c.latency_ms ? c.latency_ms + 'ms' : '' %>"></div>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<div class="flex-1 bg-gray-800 text-center text-xs text-gray-600 leading-8">No data</div>
|
<div class="flex-1 bg-gray-800/50 text-center text-xs text-gray-600 leading-8 rounded-lg">No data</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recent pings table -->
|
<!-- Recent pings table -->
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl mb-8 overflow-hidden">
|
<div class="card-static mb-8 overflow-hidden">
|
||||||
<div class="px-4 py-3 border-b border-gray-800">
|
<div class="px-4 py-3 border-b divider">
|
||||||
<h3 class="text-sm text-gray-400">Recent Pings</h3>
|
<h3 class="text-sm text-gray-400">Recent Pings</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
|
|
@ -113,9 +113,9 @@
|
||||||
<th class="text-left px-4 py-2 font-medium">Error</th>
|
<th class="text-left px-4 py-2 font-medium">Error</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="pings-table" class="divide-y divide-gray-800/50">
|
<tbody id="pings-table" class="divide-y divide-border-subtle">
|
||||||
<% pings.slice(0, 30).forEach(function(c) { %>
|
<% pings.slice(0, 30).forEach(function(c) { %>
|
||||||
<tr class="hover:bg-gray-800/50">
|
<tr class="table-row-alt">
|
||||||
<td class="px-4 py-2"><%~ c.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>' %></td>
|
<td class="px-4 py-2"><%~ c.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>' %></td>
|
||||||
<td class="px-4 py-2 text-gray-300"><%= c.status_code != null ? c.status_code : '—' %></td>
|
<td class="px-4 py-2 text-gray-300"><%= c.status_code != null ? c.status_code : '—' %></td>
|
||||||
<td class="px-4 py-2 text-gray-300"><%= c.latency_ms != null ? c.latency_ms + 'ms' : '—' %></td>
|
<td class="px-4 py-2 text-gray-300"><%= c.latency_ms != null ? c.latency_ms + 'ms' : '—' %></td>
|
||||||
|
|
@ -131,9 +131,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Edit form -->
|
<!-- Edit form -->
|
||||||
<div class="bg-gray-900 border border-gray-800 rounded-xl p-6">
|
<div class="card-static p-6">
|
||||||
<h3 class="text-sm text-gray-400 mb-4">Edit Monitor</h3>
|
<h3 class="text-sm text-gray-400 mb-4">Edit Monitor</h3>
|
||||||
<%~ include('./partials/monitor-form', { _form: { monitor: m, isEdit: true, prefix: 'edit-', bg: 'bg-gray-800', border: 'border-gray-700' }, plan: it.plan }) %>
|
<%~ include('./partials/monitor-form', { _form: { monitor: m, isEdit: true, prefix: 'edit-', bg: 'bg-gray-800/50', border: 'border-border-subtle' }, plan: it.plan }) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -465,8 +465,8 @@
|
||||||
? '<span class="text-green-400">Up</span>'
|
? '<span class="text-green-400">Up</span>'
|
||||||
: '<span class="text-red-400">Down</span>';
|
: '<span class="text-red-400">Down</span>';
|
||||||
document.getElementById('status-dot').innerHTML = ping.up
|
document.getElementById('status-dot').innerHTML = ping.up
|
||||||
? '<span class="inline-block w-2.5 h-2.5 rounded-full bg-green-400 mr-2"></span>'
|
? '<span class="inline-block w-2.5 h-2.5 rounded-full dot-up mr-2"></span>'
|
||||||
: '<span class="inline-block w-2.5 h-2.5 rounded-full bg-red-400 mr-2"></span>';
|
: '<span class="inline-block w-2.5 h-2.5 rounded-full dot-down mr-2"></span>';
|
||||||
|
|
||||||
// Avg latency
|
// Avg latency
|
||||||
if (_latCount > 0)
|
if (_latCount > 0)
|
||||||
|
|
@ -483,7 +483,7 @@
|
||||||
const bar = document.getElementById('status-bar');
|
const bar = document.getElementById('status-bar');
|
||||||
if (bar) {
|
if (bar) {
|
||||||
const seg = document.createElement('div');
|
const seg = document.createElement('div');
|
||||||
seg.className = `flex-1 ${ping.up ? 'bg-green-500/70' : 'bg-red-500/70'}`;
|
seg.className = `flex-1 rounded-sm ${ping.up ? 'bg-green-500/70' : 'bg-red-500/70'}`;
|
||||||
seg.title = `${new Date(ping.checked_at).toLocaleString()} — ${ping.up ? 'Up' : 'Down'}${ping.latency_ms ? ' ' + ping.latency_ms + 'ms' : ''}`;
|
seg.title = `${new Date(ping.checked_at).toLocaleString()} — ${ping.up ? 'Up' : 'Down'}${ping.latency_ms ? ' ' + ping.latency_ms + 'ms' : ''}`;
|
||||||
bar.prepend(seg);
|
bar.prepend(seg);
|
||||||
while (bar.children.length > 60) bar.removeChild(bar.lastChild);
|
while (bar.children.length > 60) bar.removeChild(bar.lastChild);
|
||||||
|
|
@ -493,7 +493,7 @@
|
||||||
const tbody = document.getElementById('pings-table');
|
const tbody = document.getElementById('pings-table');
|
||||||
if (tbody) {
|
if (tbody) {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.className = 'hover:bg-gray-800/50';
|
tr.className = 'table-row-alt';
|
||||||
const regionFlags = {'eu-central':'🇩🇪','us-west':'🇺🇸'};
|
const regionFlags = {'eu-central':'🇩🇪','us-west':'🇺🇸'};
|
||||||
const regionDisplay = ping.region ? `${regionFlags[ping.region] || '🌐'} ${ping.region}` : '—';
|
const regionDisplay = ping.region ? `${regionFlags[ping.region] || '🌐'} ${ping.region}` : '—';
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,19 @@
|
||||||
<h2 class="text-lg font-semibold text-gray-200">Monitors</h2>
|
<h2 class="text-lg font-semibold text-gray-200">Monitors</h2>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div id="summary" class="text-sm text-gray-500"><% if (it.monitors.length > 0) { %><span class="text-green-400"><%= upCount %> up</span> · <span class="<%= downCount > 0 ? 'text-red-400' : 'text-gray-500' %>"><%= downCount %> down</span> · <%= it.monitors.length %> total<% } %></div>
|
<div id="summary" class="text-sm text-gray-500"><% if (it.monitors.length > 0) { %><span class="text-green-400"><%= upCount %> up</span> · <span class="<%= downCount > 0 ? 'text-red-400' : 'text-gray-500' %>"><%= downCount %> down</span> · <%= it.monitors.length %> total<% } %></div>
|
||||||
<a href="/dashboard/monitors/new" class="bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium px-4 py-2 rounded-lg transition-colors">+ New</a>
|
<a href="/dashboard/monitors/new" class="btn-primary text-sm px-4 py-2">+ New</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="monitors-list" class="space-y-3">
|
<div id="monitors-list" class="space-y-3">
|
||||||
<% if (it.monitors.length === 0) { %>
|
<% if (it.monitors.length === 0) { %>
|
||||||
<div id="empty-state" class="text-center py-16">
|
<div id="empty-state" class="text-center py-20">
|
||||||
<p class="text-gray-500 mb-4">No monitors yet</p>
|
<div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gray-800/50 border border-border-subtle flex items-center justify-center">
|
||||||
<a href="/dashboard/monitors/new" class="bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium px-6 py-3 rounded-lg transition-colors inline-block">Create your first monitor</a>
|
<svg class="w-8 h-8 text-gray-600" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-500 mb-1">No monitors yet</p>
|
||||||
|
<p class="text-gray-600 text-sm mb-5">Create your first monitor to start tracking uptime</p>
|
||||||
|
<a href="/dashboard/monitors/new" class="btn-primary text-sm px-6 py-3 inline-block">Create your first monitor</a>
|
||||||
</div>
|
</div>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<% it.monitors.forEach(function(m) {
|
<% it.monitors.forEach(function(m) {
|
||||||
|
|
@ -40,10 +44,10 @@
|
||||||
const bestVals = byRegion[bestRegion] || [];
|
const bestVals = byRegion[bestRegion] || [];
|
||||||
const avgLatency = bestVals.length ? bestVals[bestVals.length - 1] : null;
|
const avgLatency = bestVals.length ? bestVals[bestVals.length - 1] : null;
|
||||||
%>
|
%>
|
||||||
<a href="/dashboard/monitors/<%= m.id %>" data-monitor-id="<%= m.id %>" class="block bg-gray-900 hover:bg-gray-800/80 border border-gray-800 rounded-xl p-4 transition-colors group">
|
<a href="/dashboard/monitors/<%= m.id %>" data-monitor-id="<%= m.id %>" class="block monitor-card rounded-xl p-4 group">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<span class="status-dot w-2.5 h-2.5 rounded-full <%= m.last_ping == null ? 'bg-gray-600' : (m.last_ping.up ? 'bg-green-500' : 'bg-red-500') %>"></span>
|
<span class="status-dot w-2.5 h-2.5 rounded-full shrink-0 <%= m.last_ping == null ? 'dot-unknown' : (m.last_ping.up ? 'dot-up' : 'dot-down') %>"></span>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="font-medium text-gray-100 group-hover:text-white truncate"><%= m.name %></div>
|
<div class="font-medium text-gray-100 group-hover:text-white truncate"><%= m.name %></div>
|
||||||
<div class="text-xs text-gray-500 truncate"><%= m.url %></div>
|
<div class="text-xs text-gray-500 truncate"><%= m.url %></div>
|
||||||
|
|
@ -55,7 +59,7 @@
|
||||||
<div class="text-sm text-gray-300 stat-latency"><%= avgLatency != null ? avgLatency + 'ms' : '—' %></div>
|
<div class="text-sm text-gray-300 stat-latency"><%= avgLatency != null ? avgLatency + 'ms' : '—' %></div>
|
||||||
<div class="text-xs text-gray-500 stat-last"><%~ m.last_ping ? it.timeAgoSSR(m.last_ping.checked_at) : 'no pings' %></div>
|
<div class="text-xs text-gray-500 stat-last"><%~ m.last_ping ? it.timeAgoSSR(m.last_ping.checked_at) : 'no pings' %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs px-2 py-1 rounded <%= m.enabled ? 'bg-gray-800 text-gray-400' : 'bg-yellow-900/30 text-yellow-500' %>"><%= m.enabled ? m.interval_s + 's' : 'paused' %></div>
|
<div class="text-xs px-2 py-1 rounded <%= m.enabled ? 'bg-gray-800/50 text-gray-400 border border-border-subtle' : 'bg-yellow-900/20 text-yellow-500 border border-yellow-800/30' %>"><%= m.enabled ? m.interval_s + 's' : 'paused' %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -143,7 +147,7 @@
|
||||||
|
|
||||||
// Status dot
|
// Status dot
|
||||||
const dot = card.querySelector('.status-dot');
|
const dot = card.querySelector('.status-dot');
|
||||||
if (dot) dot.className = `status-dot w-2.5 h-2.5 rounded-full ${ping.up ? 'bg-green-500' : 'bg-red-500'}`;
|
if (dot) dot.className = `status-dot w-2.5 h-2.5 rounded-full shrink-0 ${ping.up ? 'dot-up' : 'dot-down'}`;
|
||||||
|
|
||||||
// Last ping time
|
// Last ping time
|
||||||
card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at);
|
card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at);
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,15 @@
|
||||||
|
|
||||||
/* Terminal window */
|
/* Terminal window */
|
||||||
.terminal {
|
.terminal {
|
||||||
background: #111111;
|
background: linear-gradient(180deg, #131313 0%, #0f0f0f 100%);
|
||||||
border: 1px solid #1e1e1e;
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 0 60px rgba(59, 130, 246, 0.06), 0 20px 60px rgba(0,0,0,0.5);
|
box-shadow: 0 0 60px rgba(59, 130, 246, 0.06), 0 20px 60px rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
.terminal-bar {
|
.terminal-bar {
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
border-bottom: 1px solid #1e1e1e;
|
border-bottom: 1px solid rgba(38, 38, 44, 0.5);
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -75,13 +75,14 @@
|
||||||
|
|
||||||
/* Glow card */
|
/* Glow card */
|
||||||
.glow-card {
|
.glow-card {
|
||||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(17, 17, 17, 1) 50%);
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, rgba(17, 17, 17, 0.7) 100%);
|
||||||
border: 1px solid #1e1e1e;
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
transition: border-color 0.3s, box-shadow 0.3s;
|
transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s;
|
||||||
}
|
}
|
||||||
.glow-card:hover {
|
.glow-card:hover {
|
||||||
border-color: rgba(59, 130, 246, 0.3);
|
border-color: rgba(59, 130, 246, 0.25);
|
||||||
box-shadow: 0 0 30px rgba(59, 130, 246, 0.06);
|
box-shadow: 0 0 30px rgba(59, 130, 246, 0.06), 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Comparison bar */
|
/* Comparison bar */
|
||||||
|
|
@ -113,14 +114,30 @@
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pricing card tiers */
|
||||||
|
.pricing-free {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, rgba(17, 17, 17, 0.7) 100%);
|
||||||
|
border: 1px solid rgba(38, 38, 44, 0.6);
|
||||||
|
}
|
||||||
|
.pricing-pro {
|
||||||
|
background: linear-gradient(180deg, rgba(59, 130, 246, 0.08) 0%, rgba(17, 17, 17, 0.9) 100%);
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||||
|
box-shadow: 0 0 40px rgba(59, 130, 246, 0.08);
|
||||||
|
}
|
||||||
|
.pricing-lifetime {
|
||||||
|
background: linear-gradient(180deg, rgba(234, 179, 8, 0.06) 0%, rgba(17, 17, 17, 0.9) 100%);
|
||||||
|
border: 1px solid rgba(234, 179, 8, 0.25);
|
||||||
|
box-shadow: 0 0 30px rgba(234, 179, 8, 0.05);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-[#0a0a0a] text-gray-100 font-sans antialiased grid-bg">
|
<body class="bg-[#0a0a0a] text-gray-100 font-sans antialiased grid-bg">
|
||||||
|
|
||||||
<!-- ─── HEADER ─── -->
|
<!-- ─── HEADER ─── -->
|
||||||
<header class="fixed top-0 left-0 right-0 z-50 border-b border-gray-800/60 bg-[#0a0a0a]/80 backdrop-blur-md">
|
<header class="fixed top-0 left-0 right-0 z-50 border-b border-border-subtle bg-[#0a0a0a]/70 backdrop-blur-md">
|
||||||
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||||
<a href="/" class="font-mono text-lg font-bold tracking-tight">Ping<span class="text-blue-400">QL</span></a>
|
<a href="/" class="font-mono text-lg font-bold tracking-tight group">Ping<span class="text-blue-400 transition-all group-hover:drop-shadow-[0_0_8px_rgba(59,130,246,0.4)]">QL</span></a>
|
||||||
<nav class="hidden md:flex items-center gap-7 text-sm text-gray-400">
|
<nav class="hidden md:flex items-center gap-7 text-sm text-gray-400">
|
||||||
<a href="/docs" class="hover:text-gray-200 transition-colors">Docs</a>
|
<a href="/docs" class="hover:text-gray-200 transition-colors">Docs</a>
|
||||||
<a href="/privacy" class="hover:text-gray-200 transition-colors">Privacy</a>
|
<a href="/privacy" class="hover:text-gray-200 transition-colors">Privacy</a>
|
||||||
|
|
@ -128,7 +145,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/dashboard" class="text-sm text-gray-400 hover:text-gray-200 transition-colors">Sign in</a>
|
<a href="/dashboard" class="text-sm text-gray-400 hover:text-gray-200 transition-colors">Sign in</a>
|
||||||
<a href="/dashboard" class="text-sm bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg font-medium transition-colors">Get started</a>
|
<a href="/dashboard" class="text-sm text-white px-4 py-2 rounded-lg font-medium transition-all" style="background:linear-gradient(180deg,#3b82f6 0%,#2563eb 100%);box-shadow:0 1px 2px rgba(0,0,0,0.2),inset 0 1px 0 rgba(255,255,255,0.1)">Get started</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -139,8 +156,8 @@
|
||||||
<div class="grid lg:grid-cols-2 gap-16 items-center">
|
<div class="grid lg:grid-cols-2 gap-16 items-center">
|
||||||
<!-- Left -->
|
<!-- Left -->
|
||||||
<div class="fade-up">
|
<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">
|
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-border-subtle text-xs text-gray-400 mb-6 font-mono">
|
||||||
<span class="w-2 h-2 rounded-full bg-green-500 pulse-dot"></span>
|
<span class="w-2 h-2 rounded-full bg-green-500 pulse-dot" style="box-shadow:0 0 8px rgba(34,197,94,0.4)"></span>
|
||||||
All systems operational
|
All systems operational
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight leading-[1.1] mb-6">
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight leading-[1.1] mb-6">
|
||||||
|
|
@ -150,11 +167,11 @@
|
||||||
Go beyond simple pings. Write queries against status codes, JSON bodies, HTML selectors, headers, cert expiry — everything your response contains.
|
Go beyond simple pings. Write queries against status codes, JSON bodies, HTML selectors, headers, cert expiry — everything your response contains.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-wrap gap-4">
|
<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">
|
<a href="/dashboard" class="inline-flex items-center gap-2 px-6 py-3 text-white font-medium rounded-lg transition-all text-sm" style="background:linear-gradient(180deg,#3b82f6 0%,#2563eb 100%);box-shadow:0 1px 2px rgba(0,0,0,0.2),inset 0 1px 0 rgba(255,255,255,0.1)">
|
||||||
Get Started Free
|
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>
|
<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>
|
||||||
<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">
|
<a href="/docs" class="inline-flex items-center gap-2 px-6 py-3 border border-border-subtle hover:border-border-strong text-gray-300 font-medium rounded-lg transition-colors text-sm">
|
||||||
Read the Docs
|
Read the Docs
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -176,7 +193,7 @@
|
||||||
<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-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>
|
||||||
<span class="syn-brace">}</span></pre>
|
<span class="syn-brace">}</span></pre>
|
||||||
<div class="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-500">
|
<div class="mt-4 pt-4 border-t border-border-subtle text-xs text-gray-500">
|
||||||
<span class="syn-comment">// status < 400 AND $.db.status = "ok" AND $certExpiry > 14 days</span>
|
<span class="syn-comment">// status < 400 AND $.db.status = "ok" AND $certExpiry > 14 days</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -200,7 +217,7 @@
|
||||||
<!-- Comparison -->
|
<!-- Comparison -->
|
||||||
<div class="grid md:grid-cols-2 gap-8 max-w-3xl mx-auto">
|
<div class="grid md:grid-cols-2 gap-8 max-w-3xl mx-auto">
|
||||||
<!-- Others -->
|
<!-- Others -->
|
||||||
<div class="rounded-xl border border-gray-800 bg-[#111] p-6">
|
<div class="rounded-xl border border-border-subtle bg-surface-solid p-6">
|
||||||
<div class="text-sm text-gray-500 uppercase tracking-wider mb-4 font-mono">Others</div>
|
<div class="text-sm text-gray-500 uppercase tracking-wider mb-4 font-mono">Others</div>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|
@ -213,14 +230,14 @@
|
||||||
<span class="text-gray-400 text-sm">GET /api/health → 500</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>
|
<span class="ml-auto text-red-500 text-xs font-mono">DOWN</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-500">
|
<div class="mt-4 pt-4 border-t border-border-subtle text-xs text-gray-500">
|
||||||
That's it. That's all you get.
|
That's it. That's all you get.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PingQL -->
|
<!-- 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="rounded-xl border border-blue-500/30 bg-surface-solid p-6 shadow-glow-sm">
|
||||||
<div class="text-sm text-brand uppercase tracking-wider mb-4 font-mono">PingQL</div>
|
<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="space-y-3 text-sm font-mono">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
@ -349,7 +366,7 @@
|
||||||
<!-- ─── PRIVACY ─── -->
|
<!-- ─── PRIVACY ─── -->
|
||||||
<section id="privacy" class="py-24 px-6">
|
<section id="privacy" class="py-24 px-6">
|
||||||
<div class="max-w-4xl mx-auto">
|
<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="rounded-2xl border border-border-subtle bg-gradient-to-b from-surface-solid to-[#0a0a0a] p-8 sm:p-12" style="box-shadow:0 1px 3px rgba(0,0,0,0.3)">
|
||||||
<div class="flex items-start gap-4 mb-8">
|
<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">
|
<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>
|
<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>
|
||||||
|
|
@ -391,7 +408,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 pt-6 border-t border-gray-800 flex items-center gap-2 text-xs text-gray-500">
|
<div class="mt-8 pt-6 border-t border-border-subtle 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>
|
<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
|
Hosted in EU (Prague) — GDPR compliant
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -461,7 +478,7 @@
|
||||||
|
|
||||||
<!-- CLI teaser -->
|
<!-- CLI teaser -->
|
||||||
<div class="mt-8 text-center">
|
<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">
|
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-border-subtle 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>
|
<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>
|
CLI coming soon — <span class="text-gray-400">pingql watch mon_a1b2c3</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -477,7 +494,7 @@
|
||||||
|
|
||||||
<div class="grid sm:grid-cols-3 gap-6 max-w-4xl mx-auto">
|
<div class="grid sm:grid-cols-3 gap-6 max-w-4xl mx-auto">
|
||||||
<!-- Free -->
|
<!-- Free -->
|
||||||
<div class="glow-card rounded-xl p-8 text-left">
|
<div class="pricing-free 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-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-4xl font-bold mb-1">$0</div>
|
||||||
<div class="text-sm text-gray-500 mb-6">forever</div>
|
<div class="text-sm text-gray-500 mb-6">forever</div>
|
||||||
|
|
@ -506,33 +523,33 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pro -->
|
<!-- Pro -->
|
||||||
<div class="rounded-xl border border-blue-500/30 bg-[#111] p-8 text-left relative">
|
<div class="pricing-pro rounded-xl p-8 text-left relative">
|
||||||
<div class="text-xs text-gray-500 uppercase tracking-wider font-mono mb-2">Pro</div>
|
<div class="text-xs text-blue-400 uppercase tracking-wider font-mono mb-2">Pro</div>
|
||||||
<div class="text-4xl font-bold mb-1">$12</div>
|
<div class="text-4xl font-bold mb-1">$12</div>
|
||||||
<div class="text-sm text-gray-500 mb-6">per month</div>
|
<div class="text-sm text-gray-500 mb-6">per month</div>
|
||||||
<ul class="space-y-3 text-sm text-gray-400">
|
<ul class="space-y-3 text-sm text-gray-400">
|
||||||
<li class="flex items-center gap-2">
|
<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>
|
<svg class="w-4 h-4 text-blue-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>
|
||||||
500 monitors
|
500 monitors
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-2">
|
<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>
|
<svg class="w-4 h-4 text-blue-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>
|
||||||
2s check interval
|
2s check interval
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-2">
|
<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>
|
<svg class="w-4 h-4 text-blue-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>
|
||||||
Webhook notifications
|
Webhook notifications
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-2">
|
<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>
|
<svg class="w-4 h-4 text-blue-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>
|
||||||
Priority support
|
Priority support
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lifetime -->
|
<!-- Lifetime -->
|
||||||
<div class="rounded-xl border border-yellow-500/30 bg-[#111] p-8 text-left relative">
|
<div class="pricing-lifetime rounded-xl p-8 text-left relative">
|
||||||
<div class="text-xs text-yellow-500/70 uppercase tracking-wider font-mono mb-2">Lifetime</div>
|
<div class="text-xs text-yellow-500 uppercase tracking-wider font-mono mb-2">Lifetime</div>
|
||||||
<div class="text-4xl font-bold mb-1">$140</div>
|
<div class="text-4xl font-bold mb-1">$140</div>
|
||||||
<div class="text-sm text-gray-500 mb-6">one-time</div>
|
<div class="text-sm text-gray-500 mb-6">one-time</div>
|
||||||
<ul class="space-y-3 text-sm text-gray-400">
|
<ul class="space-y-3 text-sm text-gray-400">
|
||||||
|
|
@ -557,7 +574,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-12">
|
<div class="mt-12">
|
||||||
<a href="/dashboard" class="inline-flex items-center gap-2 px-8 py-3.5 bg-brand hover:bg-blue-500 text-white font-medium rounded-lg transition-colors text-sm">
|
<a href="/dashboard" class="inline-flex items-center gap-2 px-8 py-3.5 text-white font-medium rounded-lg transition-all text-sm" style="background:linear-gradient(180deg,#3b82f6 0%,#2563eb 100%);box-shadow:0 1px 2px rgba(0,0,0,0.2),inset 0 1px 0 rgba(255,255,255,0.1)">
|
||||||
Get Started
|
Get Started
|
||||||
<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>
|
<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>
|
||||||
|
|
@ -566,11 +583,11 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ─── FOOTER ─── -->
|
<!-- ─── FOOTER ─── -->
|
||||||
<footer class="border-t border-gray-800/50 py-12 px-6">
|
<footer class="border-t border-border-subtle py-16 px-6">
|
||||||
<div class="max-w-5xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-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">
|
<div class="flex items-center gap-8">
|
||||||
<a href="/" class="text-lg font-bold tracking-tight font-mono">Ping<span class="text-brand">QL</span></a>
|
<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">
|
<nav class="flex items-center gap-6 text-sm text-gray-500">
|
||||||
<a href="/docs" class="hover:text-gray-300 transition-colors">Docs</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>
|
<a href="/privacy" class="hover:text-gray-300 transition-colors">Privacy</a>
|
||||||
<a href="/terms" class="hover:text-gray-300 transition-colors">Terms</a>
|
<a href="/terms" class="hover:text-gray-300 transition-colors">Terms</a>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<p class="text-gray-500 text-sm mt-2">Uptime monitoring for developers</p>
|
<p class="text-gray-500 text-sm mt-2">Uptime monitoring for developers</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800" style="box-shadow:0 0 40px rgba(59,130,246,0.08)">
|
<div class="card-static p-6" style="box-shadow:0 0 60px rgba(59,130,246,0.08)">
|
||||||
|
|
||||||
<!-- Sign in form — works with or without JS -->
|
<!-- Sign in form — works with or without JS -->
|
||||||
<div id="screen-login">
|
<div id="screen-login">
|
||||||
|
|
@ -22,21 +22,21 @@
|
||||||
<input type="hidden" name="_form" value="1">
|
<input type="hidden" name="_form" value="1">
|
||||||
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Account Key</label>
|
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Account Key</label>
|
||||||
<input id="key-input" name="key" type="text" placeholder="Your account key" autocomplete="off" spellcheck="false"
|
<input id="key-input" name="key" type="text" placeholder="Your account key" autocomplete="off" spellcheck="false"
|
||||||
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm font-mono tracking-wider"
|
class="w-full input-base px-4 py-3 text-gray-100 placeholder-gray-600 text-sm font-mono tracking-wider"
|
||||||
maxlength="64">
|
maxlength="64">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="w-full mt-3 bg-blue-600 hover:bg-blue-500 text-white font-medium py-3 rounded-lg transition-colors">
|
class="w-full mt-3 btn-primary py-3">
|
||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
<div id="login-error" class="text-red-400 text-sm mt-3 text-center hidden"></div>
|
<div id="login-error" class="text-red-400 text-sm mt-3 text-center hidden"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-6 pt-5 border-t border-gray-800 text-center">
|
<div class="mt-6 pt-5 border-t divider text-center">
|
||||||
<p class="text-gray-500 text-sm mb-3">No account?</p>
|
<p class="text-gray-500 text-sm mb-3">No account?</p>
|
||||||
<form id="register-form" action="/account/register" method="POST">
|
<form id="register-form" action="/account/register" method="POST">
|
||||||
<input type="hidden" name="_form" value="1">
|
<input type="hidden" name="_form" value="1">
|
||||||
<button type="submit" id="register-btn"
|
<button type="submit" id="register-btn"
|
||||||
class="w-full bg-gray-800 hover:bg-gray-700 border border-gray-700 text-gray-300 font-medium py-3 rounded-lg transition-colors">
|
class="w-full btn-secondary py-3">
|
||||||
Create Account
|
Create Account
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -56,24 +56,24 @@
|
||||||
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Your Account Key</label>
|
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Your Account Key</label>
|
||||||
<div class="flex gap-2 mb-5">
|
<div class="flex gap-2 mb-5">
|
||||||
<div id="new-key-display"
|
<div id="new-key-display"
|
||||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-blue-400 text-sm font-mono select-all break-all"></div>
|
class="flex-1 bg-surface-solid border border-border-subtle rounded-lg px-4 py-3 text-blue-400 text-sm font-mono select-all break-all"></div>
|
||||||
<button id="copy-key-btn"
|
<button id="copy-key-btn"
|
||||||
class="px-4 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-400 hover:text-white transition-colors text-sm">
|
class="btn-secondary px-4 text-sm">
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pt-5 border-t border-gray-800">
|
<div class="pt-5 border-t divider">
|
||||||
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-1">Email <span class="text-gray-600 normal-case">(optional — recovery only)</span></label>
|
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-1">Email <span class="text-gray-600 normal-case">(optional — recovery only)</span></label>
|
||||||
<input id="email-input" type="email" placeholder="you@example.com"
|
<input id="email-input" type="email" placeholder="you@example.com"
|
||||||
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm mb-3">
|
class="w-full input-base px-4 py-3 text-gray-100 placeholder-gray-600 text-sm mb-3">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button id="save-email-btn"
|
<button id="save-email-btn"
|
||||||
class="flex-1 bg-blue-600 hover:bg-blue-500 text-white font-medium py-2.5 rounded-lg transition-colors text-sm">
|
class="flex-1 btn-primary py-2.5 text-sm">
|
||||||
Save & Continue
|
Save & Continue
|
||||||
</button>
|
</button>
|
||||||
<button id="skip-email-btn"
|
<button id="skip-email-btn"
|
||||||
class="px-4 bg-gray-800 hover:bg-gray-700 border border-gray-700 text-gray-400 rounded-lg transition-colors text-sm">
|
class="btn-secondary px-4 text-sm">
|
||||||
Skip
|
Skip
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<h2 class="text-lg font-semibold text-gray-200 mt-2">Create Monitor</h2>
|
<h2 class="text-lg font-semibold text-gray-200 mt-2">Create Monitor</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%~ include('./partials/monitor-form', { _form: { monitor: {}, isEdit: false, prefix: '', bg: 'bg-gray-900', border: 'border-gray-800' }, plan: it.plan }) %>
|
<%~ include('./partials/monitor-form', { _form: { monitor: {}, isEdit: false, prefix: '', bg: 'bg-surface-solid', border: 'border-border-subtle' }, plan: it.plan }) %>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<nav class="border-b border-gray-800 px-8 py-4 flex items-center justify-between">
|
<nav class="border-b border-border-subtle bg-[#0a0a0a]/70 backdrop-blur-md px-8 py-4 flex items-center justify-between sticky top-0 z-40">
|
||||||
<a href="/dashboard/home" class="text-xl font-bold tracking-tight">Ping<span class="text-blue-400">QL</span></a>
|
<a href="/dashboard/home" class="text-xl font-bold tracking-tight group">Ping<span class="text-blue-400 transition-all group-hover:drop-shadow-[0_0_8px_rgba(59,130,246,0.4)]">QL</span></a>
|
||||||
<div class="flex items-center gap-5 text-sm text-gray-500">
|
<div class="flex items-center gap-5 text-sm text-gray-500">
|
||||||
<% if (it.nav === 'monitors') { %>
|
<% if (it.nav === 'monitors') { %>
|
||||||
<span class="text-gray-300">Monitors</span>
|
<span class="text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full">Monitors</span>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<a href="/dashboard/home" class="hover:text-gray-300 transition-colors">Monitors</a>
|
<a href="/dashboard/home" class="hover:text-gray-300 transition-colors">Monitors</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (it.nav === 'settings') { %>
|
<% if (it.nav === 'settings') { %>
|
||||||
<span class="text-gray-300">Settings</span>
|
<span class="text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full">Settings</span>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<a href="/dashboard/settings" class="hover:text-gray-300 transition-colors">Settings</a>
|
<a href="/dashboard/settings" class="hover:text-gray-300 transition-colors">Settings</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
const createdDate = new Date(it.account.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
const createdDate = new Date(it.account.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||||
const plan = it.account.plan || 'free';
|
const plan = it.account.plan || 'free';
|
||||||
const planLabel = { free: 'Free', pro: 'Pro', lifetime: 'Lifetime' }[plan] || plan;
|
const planLabel = { free: 'Free', pro: 'Pro', lifetime: 'Lifetime' }[plan] || plan;
|
||||||
const planColor = { free: 'gray', pro: 'blue', lifetime: 'yellow' }[plan] || 'gray';
|
|
||||||
const limits = { free: { monitors: 5, interval: '30s' }, pro: { monitors: 500, interval: '2s' }, lifetime: { monitors: 500, interval: '2s' } }[plan] || { monitors: 5, interval: '30s' };
|
const limits = { free: { monitors: 5, interval: '30s' }, pro: { monitors: 500, interval: '2s' }, lifetime: { monitors: 500, interval: '2s' } }[plan] || { monitors: 5, interval: '30s' };
|
||||||
%>
|
%>
|
||||||
|
|
||||||
|
|
@ -15,10 +14,16 @@
|
||||||
<h1 class="text-xl font-semibold text-white">Settings</h1>
|
<h1 class="text-xl font-semibold text-white">Settings</h1>
|
||||||
|
|
||||||
<!-- Plan -->
|
<!-- Plan -->
|
||||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
<section class="card-static p-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-sm font-semibold text-gray-300">Plan</h2>
|
<h2 class="text-sm font-semibold text-gray-300">Plan</h2>
|
||||||
<span class="text-xs font-medium px-2.5 py-1 rounded-full border border-<%= planColor %>-500/30 text-<%= planColor %>-400"><%= planLabel %></span>
|
<% if (plan === 'free') { %>
|
||||||
|
<span class="text-xs font-medium px-2.5 py-1 rounded-full bg-gray-800/50 border border-border-subtle text-gray-400">Free</span>
|
||||||
|
<% } else if (plan === 'pro') { %>
|
||||||
|
<span class="text-xs font-medium px-2.5 py-1 rounded-full bg-gradient-to-r from-blue-600/20 to-blue-500/10 border border-blue-500/30 text-blue-400">Pro</span>
|
||||||
|
<% } else if (plan === 'lifetime') { %>
|
||||||
|
<span class="text-xs font-medium px-2.5 py-1 rounded-full bg-gradient-to-r from-yellow-600/20 to-yellow-500/10 border border-yellow-500/30 text-yellow-400">Lifetime</span>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-4 text-center">
|
<div class="grid grid-cols-3 gap-4 text-center">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -35,11 +40,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if (plan === 'free') { %>
|
<% if (plan === 'free') { %>
|
||||||
<div class="mt-4 pt-4 border-t border-gray-800">
|
<div class="mt-4 pt-4 border-t divider">
|
||||||
<a href="/dashboard/checkout" class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium rounded-lg transition-colors">Upgrade to Pro</a>
|
<a href="/dashboard/checkout" class="btn-primary inline-flex items-center gap-2 px-4 py-2 text-sm">Upgrade to Pro</a>
|
||||||
</div>
|
</div>
|
||||||
<% } else if (plan === 'pro' && it.account.plan_expires_at) { %>
|
<% } else if (plan === 'pro' && it.account.plan_expires_at) { %>
|
||||||
<div class="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-500">
|
<div class="mt-4 pt-4 border-t divider text-xs text-gray-500">
|
||||||
Pro plan expires <%= new Date(it.account.plan_expires_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) %>.
|
Pro plan expires <%= new Date(it.account.plan_expires_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) %>.
|
||||||
<a href="/dashboard/checkout" class="text-blue-400 hover:text-blue-300 ml-1">Extend or upgrade to Lifetime</a>
|
<a href="/dashboard/checkout" class="text-blue-400 hover:text-blue-300 ml-1">Extend or upgrade to Lifetime</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -48,7 +53,7 @@
|
||||||
|
|
||||||
<!-- Invoices -->
|
<!-- Invoices -->
|
||||||
<% if (it.invoices && it.invoices.length > 0) { %>
|
<% if (it.invoices && it.invoices.length > 0) { %>
|
||||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
<section class="card-static p-6">
|
||||||
<h2 class="text-sm font-semibold text-gray-300 mb-4">Invoices</h2>
|
<h2 class="text-sm font-semibold text-gray-300 mb-4">Invoices</h2>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<% it.invoices.forEach(function(inv) {
|
<% it.invoices.forEach(function(inv) {
|
||||||
|
|
@ -57,7 +62,7 @@
|
||||||
const date = new Date(inv.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
const date = new Date(inv.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||||
const planLabel = inv.plan === 'lifetime' ? 'Lifetime' : `Pro × ${inv.months}mo`;
|
const planLabel = inv.plan === 'lifetime' ? 'Lifetime' : `Pro × ${inv.months}mo`;
|
||||||
%>
|
%>
|
||||||
<div class="flex items-center justify-between p-3 bg-gray-800/50 rounded-lg border border-gray-700/50">
|
<div class="flex items-center justify-between p-3 rounded-lg bg-surface border border-border-subtle hover:border-border-strong transition-colors">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="w-2 h-2 rounded-full bg-<%= statusColor %>-500 <%= inv.status !== 'paid' ? 'animate-pulse' : '' %>"></span>
|
<span class="w-2 h-2 rounded-full bg-<%= statusColor %>-500 <%= inv.status !== 'paid' ? 'animate-pulse' : '' %>"></span>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -80,18 +85,18 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<!-- Account info -->
|
<!-- Account info -->
|
||||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
<section class="card-static p-6">
|
||||||
<h2 class="text-sm font-semibold text-gray-300 mb-4">Account</h2>
|
<h2 class="text-sm font-semibold text-gray-300 mb-4">Account</h2>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<% if (!it.isSubKey) { %>
|
<% if (!it.isSubKey) { %>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs text-gray-500 mb-1">Login Key</label>
|
<label class="block text-xs text-gray-500 mb-1">Login Key</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<code id="login-key-display" class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-blue-400 text-sm font-mono select-all"><%= it.loginKey %></code>
|
<code id="login-key-display" class="flex-1 bg-surface-solid border border-border-subtle rounded-lg px-4 py-2.5 text-blue-400 text-sm font-mono select-all"><%= it.loginKey %></code>
|
||||||
<button onclick="copyLoginKey()" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-400 hover:text-white text-xs transition-colors">Copy</button>
|
<button onclick="copyLoginKey()" class="btn-secondary px-3 text-xs">Copy</button>
|
||||||
<form action="/account/reset-key" method="POST" class="inline" onsubmit="return confirm('Rotate your login key? Your current key stops working immediately.')">
|
<form action="/account/reset-key" method="POST" class="inline" onsubmit="return confirm('Rotate your login key? Your current key stops working immediately.')">
|
||||||
<input type="hidden" name="_form" value="1">
|
<input type="hidden" name="_form" value="1">
|
||||||
<button type="submit" class="px-3 bg-gray-800 hover:bg-gray-700 border border-red-900/50 hover:border-red-700/50 text-red-400 rounded-lg text-xs transition-colors h-full py-2.5">Rotate</button>
|
<button type="submit" class="px-3 py-2.5 rounded-lg border border-red-900/30 text-red-400 hover:bg-red-900/20 hover:border-red-800/40 text-xs transition-colors">Rotate</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -104,7 +109,7 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<% if (it.isSubKey) { %>
|
<% if (it.isSubKey) { %>
|
||||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
<section class="card-static p-6">
|
||||||
<p class="text-sm font-semibold text-gray-200 mb-1">Signed in with a sub-key</p>
|
<p class="text-sm font-semibold text-gray-200 mb-1">Signed in with a sub-key</p>
|
||||||
<p class="text-xs text-gray-500 leading-relaxed">Sub-keys can manage monitors but can't access account credentials. Sign in with your main account key to manage your login key, recovery email, and sub-keys.</p>
|
<p class="text-xs text-gray-500 leading-relaxed">Sub-keys can manage monitors but can't access account credentials. Sign in with your main account key to manage your login key, recovery email, and sub-keys.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -112,18 +117,17 @@
|
||||||
|
|
||||||
<!-- Email (hidden for sub-key sessions) -->
|
<!-- Email (hidden for sub-key sessions) -->
|
||||||
<% if (!it.isSubKey) { %>
|
<% if (!it.isSubKey) { %>
|
||||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
<section class="card-static p-6">
|
||||||
<h2 class="text-sm font-semibold text-gray-300 mb-1">Recovery Email</h2>
|
<h2 class="text-sm font-semibold text-gray-300 mb-1">Recovery Email</h2>
|
||||||
<p class="text-xs text-gray-600 mb-4">Used for account recovery only. Stored as a one-way hash — we can't read it.</p>
|
<p class="text-xs text-gray-600 mb-4">Used for account recovery only. Stored as a one-way hash — we can't read it.</p>
|
||||||
<form action="/account/email" method="POST">
|
<form action="/account/email" method="POST">
|
||||||
<input type="hidden" name="_form" value="1">
|
<input type="hidden" name="_form" value="1">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input id="email-input" name="email" type="email" placeholder="<%= hasEmail ? '●●●●●●●● (set)' : 'you@example.com' %>"
|
<input id="email-input" name="email" type="email" placeholder="<%= hasEmail ? '●●●●●●●● (set)' : 'you@example.com' %>"
|
||||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm">
|
class="flex-1 input-base px-4 py-2.5 text-gray-100 placeholder-gray-600 text-sm">
|
||||||
<button type="submit" id="email-btn"
|
<button type="submit" id="email-btn" class="btn-primary px-4 text-sm">Save</button>
|
||||||
class="px-4 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">Save</button>
|
|
||||||
<% if (hasEmail) { %>
|
<% if (hasEmail) { %>
|
||||||
<button type="submit" name="email" value="" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-500 hover:text-red-400 text-xs transition-colors">Remove</button>
|
<button type="submit" name="email" value="" class="btn-secondary px-3 text-xs text-gray-500 hover:!text-red-400">Remove</button>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -135,7 +139,7 @@
|
||||||
|
|
||||||
<!-- Sub-keys (hidden for sub-key sessions) -->
|
<!-- Sub-keys (hidden for sub-key sessions) -->
|
||||||
<% if (!it.isSubKey) { %>
|
<% if (!it.isSubKey) { %>
|
||||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
<section class="card-static p-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-sm font-semibold text-gray-300">Sub-Keys</h2>
|
<h2 class="text-sm font-semibold text-gray-300">Sub-Keys</h2>
|
||||||
|
|
@ -144,13 +148,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create key form -->
|
<!-- Create key form -->
|
||||||
<form action="/account/keys" method="POST" class="mb-4 p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
<form action="/account/keys" method="POST" class="mb-4 p-4 rounded-lg bg-surface border border-border-subtle">
|
||||||
<input type="hidden" name="_form" value="1">
|
<input type="hidden" name="_form" value="1">
|
||||||
<label class="block text-xs text-gray-500 mb-1.5">New Sub-Key</label>
|
<label class="block text-xs text-gray-500 mb-1.5">New Sub-Key</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input name="label" type="text" placeholder="e.g. ci-pipeline, mobile-app" required
|
<input name="label" type="text" placeholder="e.g. ci-pipeline, mobile-app" required
|
||||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm">
|
class="flex-1 input-base px-3 py-2 text-gray-100 placeholder-gray-600 text-sm">
|
||||||
<button type="submit" class="px-4 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">Create</button>
|
<button type="submit" class="btn-primary px-4 text-sm">Create</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
@ -160,7 +164,7 @@
|
||||||
<p class="text-xs text-gray-600 italic">No sub-keys yet.</p>
|
<p class="text-xs text-gray-600 italic">No sub-keys yet.</p>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<% it.apiKeys.forEach(function(k) { %>
|
<% it.apiKeys.forEach(function(k) { %>
|
||||||
<div class="p-3 bg-gray-800/50 rounded-lg border border-gray-700/50">
|
<div class="p-3 rounded-lg bg-surface border border-border-subtle hover:border-border-strong transition-colors">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<p class="text-sm text-gray-200"><%= k.label %></p>
|
<p class="text-sm text-gray-200"><%= k.label %></p>
|
||||||
<form action="/account/keys/<%= k.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Revoke this key? Any apps using it will stop working.')">
|
<form action="/account/keys/<%= k.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Revoke this key? Any apps using it will stop working.')">
|
||||||
|
|
@ -169,8 +173,8 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<code class="flex-1 bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-blue-400 text-xs font-mono select-all"><%= k.key %></code>
|
<code class="flex-1 bg-surface-solid border border-border-subtle rounded-lg px-3 py-2 text-blue-400 text-xs font-mono select-all"><%= k.key %></code>
|
||||||
<button onclick="copyKey(this, '<%= k.key %>')" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-400 hover:text-white text-xs transition-colors">Copy</button>
|
<button onclick="copyKey(this, '<%= k.key %>')" class="btn-secondary px-3 text-xs">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-600 mt-1.5">created <%= new Date(k.created_at).toLocaleDateString() %> <%~ k.last_used_at ? '· last used ' + it.timeAgoSSR(k.last_used_at) : '· never used' %></p>
|
<p class="text-xs text-gray-600 mt-1.5">created <%= new Date(k.created_at).toLocaleDateString() %> <%~ k.last_used_at ? '· last used ' + it.timeAgoSSR(k.last_used_at) : '· never used' %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<h1 class="text-3xl font-bold tracking-tight">Ping<span class="text-blue-400">QL</span></h1>
|
<h1 class="text-3xl font-bold tracking-tight">Ping<span class="text-blue-400">QL</span></h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800" style="box-shadow:0 0 40px rgba(59,130,246,0.08)">
|
<div class="card-static p-6" style="box-shadow:0 0 60px rgba(59,130,246,0.08)">
|
||||||
<div class="flex items-center gap-3 mb-5">
|
<div class="flex items-center gap-3 mb-5">
|
||||||
<div class="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400 text-lg">✓</div>
|
<div class="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400 text-lg">✓</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -16,17 +16,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Your Account Key</label>
|
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-2">Your Account Key</label>
|
||||||
<code class="block bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-blue-400 text-sm font-mono select-all break-all mb-5"><%= it.key %></code>
|
<code class="block bg-surface-solid border border-border-subtle rounded-lg px-4 py-3 text-blue-400 text-sm font-mono select-all break-all mb-5"><%= it.key %></code>
|
||||||
|
|
||||||
<div class="pt-5 border-t border-gray-800">
|
<div class="pt-5 border-t divider">
|
||||||
<form action="/account/email" method="POST" class="space-y-3">
|
<form action="/account/email" method="POST" class="space-y-3">
|
||||||
<input type="hidden" name="_form" value="1">
|
<input type="hidden" name="_form" value="1">
|
||||||
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-1">Email <span class="text-gray-600 normal-case">(optional — recovery only)</span></label>
|
<label class="block text-xs text-gray-500 uppercase tracking-wider mb-1">Email <span class="text-gray-600 normal-case">(optional — recovery only)</span></label>
|
||||||
<input name="email" type="email" placeholder="you@example.com"
|
<input name="email" type="email" placeholder="you@example.com"
|
||||||
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm">
|
class="w-full input-base px-4 py-3 text-gray-100 placeholder-gray-600 text-sm">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button type="submit" class="flex-1 bg-blue-600 hover:bg-blue-500 text-white font-medium py-2.5 rounded-lg transition-colors text-sm">Save & Continue</button>
|
<button type="submit" class="flex-1 btn-primary py-2.5 text-sm">Save & Continue</button>
|
||||||
<a href="/dashboard/home" class="px-4 bg-gray-800 hover:bg-gray-700 border border-gray-700 text-gray-400 rounded-lg transition-colors text-sm flex items-center">Skip</a>
|
<a href="/dashboard/home" class="btn-secondary px-4 text-sm flex items-center">Skip</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,21 @@ module.exports = {
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
brand: '#3b82f6',
|
brand: '#3b82f6',
|
||||||
|
surface: {
|
||||||
|
DEFAULT: 'rgba(17, 17, 17, 0.8)',
|
||||||
|
hover: 'rgba(24, 24, 27, 0.9)',
|
||||||
|
solid: '#111111',
|
||||||
|
},
|
||||||
|
'border-subtle': 'rgba(38, 38, 44, 0.6)',
|
||||||
|
'border-strong': 'rgba(55, 55, 64, 0.8)',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
'glow-sm': '0 0 20px rgba(59, 130, 246, 0.06)',
|
||||||
|
'glow-md': '0 0 40px rgba(59, 130, 246, 0.1)',
|
||||||
|
'glow-green': '0 0 12px rgba(34, 197, 94, 0.3)',
|
||||||
|
'glow-red': '0 0 12px rgba(239, 68, 68, 0.3)',
|
||||||
|
'card': '0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2)',
|
||||||
|
'card-hover': '0 4px 12px rgba(0, 0, 0, 0.4), 0 0 20px rgba(59, 130, 246, 0.04)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue