feat: /live page — full ship log + oops on quikcue.com

This commit is contained in:
2026-03-05 23:44:33 +08:00
parent 5761026548
commit 9e82633a5e
4 changed files with 478 additions and 3 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
<a href="/" class="logo">QuikCue</a>
<div class="nav-links">
<a href="/#work">Work</a>
<a href="/#live">Live Feed</a>
<a href="/live">Live Feed</a>
<a href="/#contact" class="nav-cta">Talk to us</a>
</div>
</div>
+211
View File
@@ -840,3 +840,214 @@ nav {
@media (max-width: 380px) {
.nav-links a:not(.nav-cta) { display: none; }
}
/* ═══════════════════════════════════════════
/LIVE PAGE — Full Shipping Log
═══════════════════════════════════════════ */
.live-page {
padding-top: 96px;
padding-bottom: 5rem;
min-height: 100vh;
}
.live-page-header {
margin-bottom: 3rem;
}
.live-page-eyebrow {
display: flex;
align-items: center;
gap: 0.6rem;
font-family: var(--mono);
font-size: 0.78rem;
font-weight: 600;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 1.25rem;
}
.live-page-title {
font-family: var(--sans);
font-size: clamp(2rem, 5vw, 3.2rem);
font-weight: 900;
letter-spacing: -0.03em;
line-height: 1.1;
color: var(--text);
margin-bottom: 1rem;
}
.live-page-sub {
font-size: 1.05rem;
color: var(--text-secondary);
line-height: 1.75;
max-width: 600px;
}
/* Filters */
.log-filters {
display: flex;
gap: 0.5rem;
margin: 2.5rem 0 2rem;
}
.filter-btn {
font-family: var(--mono);
font-size: 0.78rem;
font-weight: 600;
padding: 0.45rem 1rem;
border-radius: 99px;
border: 1px solid var(--border-light);
background: transparent;
color: var(--text-muted);
cursor: pointer;
transition: all 0.15s ease;
}
.filter-btn:hover {
color: var(--text);
border-color: var(--text-muted);
}
.filter-btn.active {
background: var(--accent);
color: #050505;
border-color: var(--accent);
}
/* Day groups */
.log-day {
margin-bottom: 2rem;
}
.log-day-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
}
.log-day-date {
font-family: var(--mono);
font-size: 0.82rem;
font-weight: 700;
color: var(--text);
}
.log-day-count {
font-family: var(--mono);
font-size: 0.7rem;
color: var(--text-dim);
}
/* Log entries */
.log-entry {
padding: 0.85rem 1.25rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 0.5rem;
transition: border-color 0.15s ease;
}
.log-entry:hover {
border-color: var(--border-light);
}
.log-entry-head {
display: flex;
align-items: center;
gap: 0.6rem;
flex-wrap: wrap;
}
.log-entry-id {
font-family: var(--mono);
font-size: 0.7rem;
font-weight: 700;
color: var(--text-dim);
min-width: 2.2rem;
}
.log-entry-title {
font-size: 0.88rem;
color: var(--text-secondary);
flex: 1;
min-width: 0;
}
.log-metric {
font-family: var(--mono);
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--border);
line-height: 1.6;
}
/* Oops section */
.oops-section {
margin-top: 4rem;
padding-top: 3rem;
border-top: 1px solid var(--border);
}
.oops-title {
font-family: var(--mono);
font-size: 1.2rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.35rem;
}
.oops-sub {
font-size: 0.88rem;
color: var(--text-muted);
margin-bottom: 1.5rem;
}
.oops-entry {
display: flex;
align-items: flex-start;
gap: 0.75rem;
font-size: 0.82rem;
color: var(--text-muted);
padding: 0.75rem 1rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
border-left: 3px solid #ef4444;
margin-bottom: 0.5rem;
line-height: 1.6;
}
.oops-id {
font-family: var(--mono);
font-size: 0.7rem;
font-weight: 700;
color: var(--text-dim);
flex-shrink: 0;
}
/* Live page CTA */
.live-page-cta {
text-align: center;
margin-top: 5rem;
padding-top: 3rem;
border-top: 1px solid var(--border);
}
.live-page-cta p {
font-family: var(--mono);
font-size: 0.95rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 1.5rem;
}
/* Loading state */
.log-loading {
font-family: var(--mono);
font-size: 0.82rem;
color: var(--text-dim);
padding: 2rem;
text-align: center;
}
/* Active nav link */
.nav-links a.active {
color: var(--text);
background: var(--bg-card);
}
/* ── Responsive: /live page ── */
@media (max-width: 640px) {
.live-page { padding-top: 80px; }
.log-entry-head { gap: 0.4rem; }
.log-entry-id { display: none; }
.log-filters { flex-wrap: wrap; }
.oops-entry { flex-direction: column; gap: 0.25rem; }
}
+3 -2
View File
@@ -24,7 +24,7 @@
<a href="/" class="logo">QuikCue</a>
<div class="nav-links">
<a href="#work">Work</a>
<a href="#live">Live Feed</a>
<a href="/live">Live Feed</a>
<a href="#contact" class="nav-cta">Talk to us</a>
</div>
</div>
@@ -45,7 +45,7 @@
</p>
<div class="hero-ctas">
<a href="#contact" class="btn btn-primary">Start a conversation →</a>
<a href="#live" class="btn btn-ghost">Watch us build ◉</a>
<a href="/live" class="btn btn-ghost">Watch us build ◉</a>
</div>
</div>
<div class="hero-glow"></div>
@@ -204,6 +204,7 @@
<div class="live-feed-header">
<div class="live-pulse"></div>
<span>Live Engineering Feed</span>
<a href="/live" class="live-feed-link">View full log →</a>
</div>
<div class="live-feed-entries" id="live-entries">
<div class="live-entry live-entry-loading">
+263
View File
@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>QuikCue — Live Shipping Log</title>
<meta name="description" content="Every deployment, every fix, every system — logged and live. Real-time engineering feed from QuikCue.">
<meta property="og:title" content="QuikCue — Live Shipping Log">
<meta property="og:description" content="Real-time engineering feed. Every deployment logged. Full transparency.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://quikcue.com/live">
<link rel="canonical" href="https://quikcue.com/live">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<nav>
<div class="nav-inner">
<a href="/" class="logo">QuikCue</a>
<div class="nav-links">
<a href="/#work">Work</a>
<a href="/live" class="active">Live Feed</a>
<a href="/#contact" class="nav-cta">Talk to us</a>
</div>
</div>
</nav>
<main class="live-page">
<div class="container">
<!-- Header -->
<header class="live-page-header">
<div class="live-page-eyebrow">
<div class="live-pulse"></div>
<span>Live Engineering Feed</span>
</div>
<h1 class="live-page-title">Full Shipping Log</h1>
<p class="live-page-sub">
Every system we've shipped. Every fix we've made. Unfakeable, unedited, pulled
straight from our PostgreSQL database in real time.
</p>
</header>
<!-- Stats -->
<div class="live-stats" id="stats-bar">
<div class="live-stat">
<div class="live-stat-num" id="stat-total"></div>
<div class="live-stat-label">Total ships</div>
</div>
<div class="live-stat">
<div class="live-stat-num live-stat-green" id="stat-shipped"></div>
<div class="live-stat-label">Shipped</div>
</div>
<div class="live-stat">
<div class="live-stat-num live-stat-amber" id="stat-shipping"></div>
<div class="live-stat-label">In flight</div>
</div>
<div class="live-stat">
<div class="live-stat-num live-stat-red" id="stat-oops"></div>
<div class="live-stat-label">Oops</div>
</div>
</div>
<!-- Filters -->
<div class="log-filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="shipped">Shipped</button>
<button class="filter-btn" data-filter="shipping">In flight</button>
</div>
<!-- Full Ship Log -->
<div class="log-list" id="log-list">
<div class="log-loading">Loading from database…</div>
</div>
<!-- Oops Log -->
<section class="oops-section" id="oops-section">
<h2 class="oops-title">Oops Log</h2>
<p class="oops-sub">If it's not here, we haven't broken it yet.</p>
<div class="oops-list" id="oops-list">
<div class="log-loading">Loading…</div>
</div>
</section>
<!-- CTA -->
<div class="live-page-cta">
<p>This is what autonomous operations looks like.</p>
<a href="/#contact" class="btn btn-primary">Start a conversation →</a>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<div class="footer-inner">
<div class="footer-brand">
<span class="footer-logo">QuikCue</span>
<span class="footer-tagline">We build the machine. You run the mission.</span>
</div>
<div class="footer-links">
<a href="/live">Live Feed</a>
<a href="https://git.quikcue.com" target="_blank">Git</a>
<a href="mailto:omair@quikcue.com">Contact</a>
</div>
</div>
<div class="footer-bottom">
<span>© 2026 QuikCue Ltd. London, UK.</span>
<span class="footer-built">Built with obsessive velocity.</span>
</div>
</div>
</footer>
<script>
const API = 'https://calvana.quikcue.com/api';
// ── Load everything ──
async function init() {
const [ships, oops, stats] = await Promise.all([
fetch(`${API}/ships`).then(r => r.json()).catch(() => []),
fetch(`${API}/oops`).then(r => r.json()).catch(() => []),
fetch(`${API}/stats`).then(r => r.json()).catch(() => ({ total: 0, shipped: 0, oops: 0 }))
]);
renderStats(stats, ships);
renderShips(ships);
renderOops(oops);
initFilters(ships);
}
// ── Stats ──
function renderStats(stats, ships) {
const inFlight = ships.filter(s => s.status === 'shipping').length;
animateNum('stat-total', stats.total);
animateNum('stat-shipped', stats.shipped);
animateNum('stat-shipping', inFlight);
animateNum('stat-oops', stats.oops);
}
function animateNum(id, target) {
const el = document.getElementById(id);
if (!el || !target) { if (el) el.textContent = target || '0'; return; }
const start = performance.now();
const dur = 800;
(function step(now) {
const p = Math.min((now - start) / dur, 1);
const eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.floor(target * eased);
if (p < 1) requestAnimationFrame(step);
})(start);
}
// ── Ships ──
function renderShips(ships) {
const container = document.getElementById('log-list');
// Group by date
const groups = {};
ships.forEach(s => {
const date = (s.created_at || '').split('T')[0] || 'Unknown';
if (!groups[date]) groups[date] = [];
groups[date].push(s);
});
let html = '';
for (const [date, entries] of Object.entries(groups)) {
const formatted = formatDate(date);
html += `<div class="log-day">
<div class="log-day-header">
<span class="log-day-date">${formatted}</span>
<span class="log-day-count">${entries.length} ship${entries.length !== 1 ? 's' : ''}</span>
</div>`;
entries.forEach(s => {
const badgeClass = s.status === 'shipped' ? 'badge-shipped' :
s.status === 'shipping' ? 'badge-shipping' : 'badge-planned';
const label = s.status.charAt(0).toUpperCase() + s.status.slice(1);
const metric = s.metric && s.metric !== '-' && s.metric !== '—'
? `<div class="log-metric">${escapeHtml(s.metric)}</div>` : '';
html += `<div class="log-entry" data-status="${s.status}">
<div class="log-entry-head">
<span class="log-entry-id">#${s.id}</span>
<span class="live-entry-badge ${badgeClass}">${label}</span>
<span class="log-entry-title">${escapeHtml(s.title)}</span>
</div>
${metric}
</div>`;
});
html += '</div>';
}
container.innerHTML = html;
}
// ── Oops ──
function renderOops(oops) {
const container = document.getElementById('oops-list');
if (!oops.length) {
container.innerHTML = '<div class="oops-entry">Nothing broken yet. Give it time.</div>';
return;
}
container.innerHTML = oops.map(o => {
const time = o.fix_time && o.fix_time !== '-' ? ` — fixed in ${escapeHtml(o.fix_time)}` : '';
return `<div class="oops-entry">
<span class="oops-id">#${o.id}</span>
<span>${escapeHtml(o.description)}${time}</span>
</div>`;
}).join('');
}
// ── Filters ──
function initFilters(ships) {
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const filter = btn.dataset.filter;
document.querySelectorAll('.log-entry').forEach(entry => {
if (filter === 'all' || entry.dataset.status === filter) {
entry.style.display = '';
} else {
entry.style.display = 'none';
}
});
// Hide empty day headers
document.querySelectorAll('.log-day').forEach(day => {
const visible = day.querySelectorAll('.log-entry:not([style*="display: none"])');
day.style.display = visible.length ? '' : 'none';
});
});
});
}
// ── Helpers ──
function formatDate(str) {
try {
const d = new Date(str + 'T00:00:00');
return d.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
} catch { return str; }
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Live pulse
setInterval(() => {
const p = document.querySelector('.live-pulse');
if (p) { p.classList.remove('ping'); void p.offsetWidth; p.classList.add('ping'); }
}, 3000);
init();
</script>
</body>
</html>