Files
Timmy-time-dashboard/src/dashboard/templates/base.html
Claude (Opus 4.6) 3349948f7f
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
[claude] Homepage value proposition — 10-second clarity (#809) (#1338)
2026-03-24 02:41:57 +00:00

582 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="#080412" />
<title>{% block title %}Timmy AI Workshop | Lightning-Powered AI Jobs — Pay Per Task with Bitcoin{% endblock %}</title>
{# SEO: description #}
<meta name="description" content="{% block meta_description %}Run AI jobs in seconds — pay per task in sats over Bitcoin Lightning. No subscription, no waiting, instant results. Timmy AI Workshop powers your workflow.{% endblock %}" />
<meta name="robots" content="{% block meta_robots %}index, follow{% endblock %}" />
{# Canonical URL — override per-page via {% block canonical_url %} #}
{% block canonical_url %}
<link rel="canonical" href="{{ site_url }}" />
{% endblock %}
{# Open Graph #}
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Timmy AI Workshop" />
<meta property="og:title" content="{% block og_title %}Timmy AI Workshop | Lightning-Powered AI Jobs — Pay Per Task with Bitcoin{% endblock %}" />
<meta property="og:description" content="{% block og_description %}Pay-per-task AI jobs over Bitcoin Lightning. No subscriptions — just instant, sovereign AI results priced in sats.{% endblock %}" />
<meta property="og:url" content="{% block og_url %}{{ site_url }}{% endblock %}" />
<meta property="og:image" content="{% block og_image %}{{ site_url }}/static/og-workshop.png{% endblock %}" />
<meta property="og:image:alt" content="Timmy AI Workshop — 3D lightning-powered AI task engine" />
{# Twitter / X Card #}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{% block twitter_title %}Timmy AI Workshop | Lightning-Powered AI Jobs{% endblock %}" />
<meta name="twitter:description" content="Pay-per-task AI over Bitcoin Lightning. No subscription — just sats and instant results." />
<meta name="twitter:image" content="{% block twitter_image %}{{ site_url }}/static/og-workshop.png{% endblock %}" />
{# JSON-LD Structured Data #}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "SoftwareApplication",
"name": "Timmy AI Workshop",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web",
"url": "{{ site_url }}",
"description": "Lightning-powered AI task engine. Pay per task in sats — no subscription required.",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "SAT",
"description": "Pay-per-task pricing over Bitcoin Lightning Network"
}
},
{
"@type": "Service",
"name": "Timmy AI Workshop",
"serviceType": "AI Task Automation",
"description": "On-demand AI jobs priced in satoshis. Instant results, no subscription.",
"provider": {
"@type": "Organization",
"name": "Alexander Whitestone",
"url": "{{ site_url }}"
},
"paymentAccepted": "Bitcoin Lightning",
"url": "{{ site_url }}"
},
{
"@type": "Organization",
"name": "Alexander Whitestone",
"url": "{{ site_url }}",
"description": "Sovereign AI infrastructure powered by Bitcoin Lightning."
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How do I pay for AI tasks?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Tasks are priced in satoshis (sats) and settled instantly over the Bitcoin Lightning Network. No credit card or subscription required."
}
},
{
"@type": "Question",
"name": "Is there a subscription fee?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Timmy AI Workshop is strictly pay-per-task — you only pay for what you use, in sats."
}
},
{
"@type": "Question",
"name": "How fast are results?",
"acceptedAnswer": {
"@type": "Answer",
"text": "AI jobs run on local sovereign infrastructure and return results in seconds, with no cloud round-trips."
}
}
]
}
]
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous" />
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/static/style.css?v=6" />
<link rel="stylesheet" href="/static/css/mission-control.css?v=2" />
{% block extra_styles %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.3/dist/htmx.min.js" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var match = document.cookie.match(/csrf_token=([^;]+)/);
if (match) {
document.body.setAttribute('hx-headers', JSON.stringify({"X-CSRF-Token": match[1]}));
}
});
</script>
<script defer src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
</head>
<body>
<header class="mc-header">
<div class="mc-header-left">
<a href="/dashboard" class="mc-title">MISSION CONTROL</a>
<span class="mc-subtitle">MISSION CONTROL</span>
<span class="mc-conn-status" id="conn-status">
<span class="mc-conn-dot amber" id="conn-dot"></span>
<span id="conn-label">CONNECTING</span>
</span>
</div>
<!-- Desktop nav — grouped dropdowns matching mobile sections -->
<div class="mc-header-right mc-desktop-nav">
<a href="/" class="mc-test-link">HOME</a>
<a href="/dashboard" class="mc-test-link">DASHBOARD</a>
<div class="mc-nav-dropdown">
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">CORE &#x25BE;</button>
<div class="mc-dropdown-menu">
<a href="/calm" class="mc-test-link">CALM</a>
<a href="/tasks" class="mc-test-link">TASKS</a>
<a href="/briefing" class="mc-test-link">BRIEFING</a>
<a href="/thinking" class="mc-test-link mc-link-thinking">THINKING</a>
<a href="/swarm/mission-control" class="mc-test-link">MISSION CTRL</a>
<a href="/monitoring" class="mc-test-link">MONITORING</a>
<a href="/swarm/live" class="mc-test-link">SWARM</a>
<a href="/scorecards" class="mc-test-link">SCORECARDS</a>
<a href="/bugs" class="mc-test-link mc-link-bugs">BUGS</a>
</div>
</div>
<div class="mc-nav-dropdown">
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">AGENTS &#x25BE;</button>
<div class="mc-dropdown-menu">
<a href="/hands" class="mc-test-link">HANDS</a>
<a href="/work-orders/queue" class="mc-test-link">WORK ORDERS</a>
<a href="/self-modify/queue" class="mc-test-link">UPGRADES</a>
<a href="/self-coding" class="mc-test-link">SELF-CODING</a>
</div>
</div>
<div class="mc-nav-dropdown">
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">INTEL &#x25BE;</button>
<div class="mc-dropdown-menu">
<a href="/nexus" class="mc-test-link">NEXUS</a>
<a href="/spark/ui" class="mc-test-link">SPARK</a>
<a href="/memory" class="mc-test-link">MEMORY</a>
<a href="/marketplace/ui" class="mc-test-link">MARKET</a>
<a href="/self-correction/ui" class="mc-test-link">SELF-CORRECT</a>
</div>
</div>
<div class="mc-nav-dropdown">
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">SYSTEM &#x25BE;</button>
<div class="mc-dropdown-menu">
<a href="/tools" class="mc-test-link">TOOLS</a>
<a href="/swarm/events" class="mc-test-link">EVENTS</a>
<a href="/router/status" class="mc-test-link">ROUTER</a>
<a href="/grok/status" class="mc-test-link mc-link-grok">GROK</a>
<a href="/db-explorer" class="mc-test-link">DB EXPLORER</a>
</div>
</div>
<div class="mc-nav-dropdown">
<button class="mc-test-link mc-dropdown-toggle" aria-expanded="false">MORE &#x25BE;</button>
<div class="mc-dropdown-menu">
<a href="/lightning/ledger" class="mc-test-link">LEDGER</a>
<a href="/creative/ui" class="mc-test-link">CREATIVE</a>
<a href="/voice/button" class="mc-test-link">VOICE</a>
<a href="/voice/settings" class="mc-test-link">VOICE SETTINGS</a>
<a href="/mobile" class="mc-test-link" title="Mobile-optimized view">MOBILE</a>
<a href="/mobile/local" class="mc-test-link" title="Local AI on iPhone">LOCAL AI</a>
<div class="mc-dropdown-divider"></div>
<a href="/legal/tos" class="mc-test-link">TERMS</a>
<a href="/legal/privacy" class="mc-test-link">PRIVACY</a>
<a href="/legal/risk" class="mc-test-link">RISK</a>
</div>
</div>
<div class="mc-nav-dropdown" id="notif-dropdown">
<button id="enable-notifications" class="mc-test-link mc-dropdown-toggle mc-notif-btn" title="Notifications" aria-expanded="false">&#x1F514;<span id="notif-badge" class="notif-badge"></span></button>
<div class="mc-dropdown-menu mc-notif-menu">
<div id="notif-list" class="mc-notif-list">
<div class="mc-notif-empty">Loading...</div>
</div>
</div>
</div>
<span class="mc-time" id="clock"></span>
</div>
<!-- Mobile hamburger -->
<button class="mc-hamburger" id="hamburger-btn" aria-label="Menu">
<span></span><span></span><span></span>
</button>
</header>
<!-- Mobile slide-out menu -->
<div class="mc-mobile-overlay" id="mobile-overlay"></div>
<nav class="mc-mobile-menu" id="mobile-menu">
<div class="mc-mobile-menu-header">
<span class="mc-mobile-menu-title">NAVIGATE</span>
<span class="mc-time" id="clock-mobile"></span>
</div>
<a href="/" class="mc-mobile-link">HOME</a>
<a href="/dashboard" class="mc-mobile-link">DASHBOARD</a>
<div class="mc-mobile-section-label">CORE</div>
<a href="/calm" class="mc-mobile-link">CALM</a>
<a href="/tasks" class="mc-mobile-link">TASKS</a>
<a href="/briefing" class="mc-mobile-link">BRIEFING</a>
<a href="/thinking" class="mc-mobile-link">THINKING</a>
<a href="/swarm/mission-control" class="mc-mobile-link">MISSION CONTROL</a>
<a href="/swarm/live" class="mc-mobile-link">SWARM</a>
<a href="/scorecards" class="mc-mobile-link">SCORECARDS</a>
<a href="/bugs" class="mc-mobile-link">BUGS</a>
<div class="mc-mobile-section-label">INTELLIGENCE</div>
<a href="/spark/ui" class="mc-mobile-link">SPARK</a>
<a href="/memory" class="mc-mobile-link">MEMORY</a>
<a href="/marketplace/ui" class="mc-mobile-link">MARKET</a>
<a href="/self-correction/ui" class="mc-mobile-link">SELF-CORRECT</a>
<div class="mc-mobile-section-label">AGENTS</div>
<a href="/hands" class="mc-mobile-link">HANDS</a>
<a href="/work-orders/queue" class="mc-mobile-link">WORK ORDERS</a>
<a href="/self-modify/queue" class="mc-mobile-link">UPGRADES</a>
<a href="/self-coding" class="mc-mobile-link">SELF-CODING</a>
<div class="mc-mobile-section-label">SYSTEM</div>
<a href="/tools" class="mc-mobile-link">TOOLS</a>
<a href="/swarm/events" class="mc-mobile-link">EVENTS</a>
<a href="/router/status" class="mc-mobile-link">ROUTER</a>
<a href="/grok/status" class="mc-mobile-link">GROK</a>
<a href="/db-explorer" class="mc-mobile-link">DB EXPLORER</a>
<div class="mc-mobile-section-label">COMMERCE</div>
<a href="/lightning/ledger" class="mc-mobile-link">LEDGER</a>
<a href="/creative/ui" class="mc-mobile-link">CREATIVE</a>
<a href="/voice/button" class="mc-mobile-link">VOICE</a>
<a href="/voice/settings" class="mc-mobile-link">VOICE SETTINGS</a>
<a href="/mobile" class="mc-mobile-link">MOBILE</a>
<a href="/mobile/local" class="mc-mobile-link">LOCAL AI</a>
<div class="mc-mobile-section-label">LEGAL</div>
<a href="/legal/tos" class="mc-mobile-link">TERMS OF SERVICE</a>
<a href="/legal/privacy" class="mc-mobile-link">PRIVACY POLICY</a>
<a href="/legal/risk" class="mc-mobile-link">RISK DISCLAIMERS</a>
<div class="mc-mobile-menu-footer">
<button id="enable-notifications-mobile" class="mc-mobile-link mc-mobile-notif-btn">&#x1F514; NOTIFICATIONS</button>
</div>
</nav>
<!-- Toast container -->
<div class="mc-toast-container" id="toast-container"></div>
<!-- Magical floating particles canvas -->
<canvas id="magic-particles" aria-hidden="true"></canvas>
<main class="mc-main">
{% block content %}{% endblock %}
</main>
<footer class="mc-footer">
<a href="/legal/tos" class="mc-footer-link">Terms</a>
<span class="mc-footer-sep">·</span>
<a href="/legal/privacy" class="mc-footer-link">Privacy</a>
<span class="mc-footer-sep">·</span>
<a href="/legal/risk" class="mc-footer-link">Risk Disclaimers</a>
</footer>
<script>
// ── Magical floating particles ──
(function() {
var canvas = document.getElementById('magic-particles');
if (!canvas) return;
var ctx = canvas.getContext('2d');
var particles = [];
var PARTICLE_COUNT = 40;
var raf;
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
var colors = [
{ r: 168, g: 85, b: 247 }, // purple
{ r: 124, g: 58, b: 237 }, // violet
{ r: 192, g: 132, b: 252 }, // light purple
{ r: 249, g: 115, b: 22 }, // orange
{ r: 237, g: 224, b: 255 }, // white-purple
];
function createParticle() {
var c = colors[Math.floor(Math.random() * colors.length)];
return {
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.3,
vy: (Math.random() - 0.5) * 0.3,
r: Math.random() * 2.2 + 0.5,
alpha: Math.random() * 0.4 + 0.05,
alphaDir: (Math.random() - 0.5) * 0.008,
color: c,
phase: Math.random() * Math.PI * 2,
drift: Math.random() * 0.2 + 0.05
};
}
for (var i = 0; i < PARTICLE_COUNT; i++) {
particles.push(createParticle());
}
var time = 0;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
time += 0.003;
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
p.x += p.vx + Math.sin(time + p.phase) * p.drift;
p.y += p.vy + Math.cos(time + p.phase) * p.drift;
p.alpha += p.alphaDir;
if (p.alpha <= 0.02 || p.alpha >= 0.45) p.alphaDir *= -1;
p.alpha = Math.max(0.02, Math.min(0.45, p.alpha));
if (p.x < -20) p.x = canvas.width + 20;
if (p.x > canvas.width + 20) p.x = -20;
if (p.y < -20) p.y = canvas.height + 20;
if (p.y > canvas.height + 20) p.y = -20;
// Glow
var grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r * 6);
grad.addColorStop(0, 'rgba(' + p.color.r + ',' + p.color.g + ',' + p.color.b + ',' + (p.alpha * 0.6) + ')');
grad.addColorStop(1, 'rgba(' + p.color.r + ',' + p.color.g + ',' + p.color.b + ',0)');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r * 6, 0, Math.PI * 2);
ctx.fill();
// Core
ctx.fillStyle = 'rgba(' + p.color.r + ',' + p.color.g + ',' + p.color.b + ',' + p.alpha + ')';
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fill();
}
raf = requestAnimationFrame(draw);
}
// Only run when tab is visible
function onVis() {
if (document.hidden) {
cancelAnimationFrame(raf);
} else {
draw();
}
}
document.addEventListener('visibilitychange', onVis);
draw();
// Respect reduced motion preference
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
cancelAnimationFrame(raf);
canvas.style.display = 'none';
}
})();
</script>
<script>
// Clock
function updateClock() {
const t = new Date().toLocaleTimeString('en-US', { hour12: false });
const el = document.getElementById('clock');
const el2 = document.getElementById('clock-mobile');
if (el) el.textContent = t;
if (el2) el2.textContent = t;
}
setInterval(updateClock, 1000);
updateClock();
// Mobile menu
const hamburger = document.getElementById('hamburger-btn');
const overlay = document.getElementById('mobile-overlay');
const menu = document.getElementById('mobile-menu');
function toggleMenu() {
const open = menu.classList.toggle('open');
overlay.classList.toggle('open', open);
hamburger.classList.toggle('open', open);
document.body.style.overflow = open ? 'hidden' : '';
}
hamburger.addEventListener('click', toggleMenu);
overlay.addEventListener('click', toggleMenu);
// Highlight current page in mobile menu and desktop dropdowns
var currentPath = window.location.pathname;
document.querySelectorAll('.mc-mobile-link, .mc-dropdown-menu .mc-test-link').forEach(function(a) {
if (a.getAttribute('href') === currentPath) {
a.classList.add('active');
}
});
// Desktop dropdown toggles (More menu + Notification bell)
document.querySelectorAll('.mc-dropdown-toggle').forEach(function(toggle) {
toggle.addEventListener('click', function(e) {
e.stopPropagation();
var dd = this.closest('.mc-nav-dropdown');
// Close other dropdowns first
document.querySelectorAll('.mc-nav-dropdown.open').forEach(function(other) {
if (other !== dd) {
other.classList.remove('open');
var btn = other.querySelector('.mc-dropdown-toggle');
if (btn) btn.setAttribute('aria-expanded', 'false');
}
});
var isOpen = dd.classList.toggle('open');
this.setAttribute('aria-expanded', isOpen);
// Load notifications when opening the bell
if (isOpen && dd.id === 'notif-dropdown') { loadNotifications(); }
});
});
document.addEventListener('click', function() {
document.querySelectorAll('.mc-nav-dropdown.open').forEach(function(dd) {
dd.classList.remove('open');
var btn = dd.querySelector('.mc-dropdown-toggle');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
});
// Notification loader
function loadNotifications() {
fetch('/api/notifications')
.then(function(r) { return r.json(); })
.then(function(data) {
var list = document.getElementById('notif-list');
if (!data.length) {
list.innerHTML = '';
var emptyDiv = document.createElement('div');
emptyDiv.className = 'mc-notif-empty';
emptyDiv.textContent = 'No recent notifications';
list.appendChild(emptyDiv);
return;
}
list.innerHTML = '';
data.forEach(function(n) {
var item = document.createElement('div');
item.className = 'mc-notif-item';
var title = document.createElement('div');
title.className = 'mc-notif-title';
title.textContent = n.title || n.event_type || 'Event';
var ts = document.createElement('div');
ts.className = 'mc-notif-ts';
ts.textContent = n.timestamp || '';
item.appendChild(title);
item.appendChild(ts);
list.appendChild(item);
});
var badge = document.getElementById('notif-badge');
if (badge) { badge.classList.add('hidden'); }
})
.catch(function() {});
}
</script>
<!-- Toast + connection status system -->
<script>
// ── Toast notifications ──
window.McToast = {
show: function(message, level) {
level = level || 'info';
var container = document.getElementById('toast-container');
if (!container) return;
var toast = document.createElement('div');
toast.className = 'mc-toast ' + level;
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(function() {
toast.classList.add('show');
});
setTimeout(function() {
toast.classList.remove('show');
setTimeout(function() { toast.remove(); }, 300);
}, 4000);
}
};
// ── Global connection status ──
(function() {
var dot = document.getElementById('conn-dot');
var label = document.getElementById('conn-label');
var wsConnected = false;
var ollamaOk = null; // null = unknown, true/false
function updateIndicator() {
if (!dot || !label) return;
if (!wsConnected) {
dot.className = 'mc-conn-dot red';
label.textContent = 'Reconnecting...';
} else if (ollamaOk === false) {
dot.className = 'mc-conn-dot amber';
label.textContent = 'NO LLM';
} else {
dot.className = 'mc-conn-dot green';
label.textContent = 'LIVE';
}
}
function checkOllama() {
fetch('/health')
.then(function(r) { return r.json(); })
.then(function(data) {
var prev = ollamaOk;
ollamaOk = data.services && data.services.ollama === 'up';
updateIndicator();
if (prev === false && ollamaOk) {
McToast.show('Ollama reconnected', 'info');
} else if (prev === true && !ollamaOk) {
McToast.show('Ollama unreachable', 'warn');
}
})
.catch(function() {
ollamaOk = false;
updateIndicator();
});
}
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
var reconnectDelay = 1000;
function connectStatusWs() {
var ws;
try {
ws = new WebSocket(protocol + '//' + window.location.host + '/swarm/live');
} catch(e) {
// WebSocket constructor failed (e.g. invalid environment) — retry
setTimeout(connectStatusWs, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
return;
}
ws.onopen = function() {
wsConnected = true;
reconnectDelay = 1000;
updateIndicator();
checkOllama();
};
ws.onclose = function() {
if (wsConnected) {
McToast.show('WebSocket disconnected', 'error');
}
wsConnected = false;
updateIndicator();
setTimeout(connectStatusWs, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
};
ws.onerror = function() {};
}
connectStatusWs();
// Poll Ollama health every 30s
setInterval(checkOllama, 30000);
})();
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="/static/notifications.js"></script>
</body>
</html>