1
0
This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/static/world/index.html
kimi 36de0b491d feat(matrix-ui): add Fund Session modal with explanatory text about sats
- Add Fund Session button to Matrix UI overlay (green plus icon)
- Create modal with three explanatory sections:
  * What are Sats? - explains satoshis as smallest Bitcoin unit
  * Why Fund Your Session? - explains how sats power Workshop AI agents
  * Approximate Costs - shows cost ranges for different interaction types
- Add input field for funding amount (min 100 sats)
- Style modal with green theme to match Lightning/sats concept
- Add proper keyboard support (Enter to submit, Escape to close)
- Mobile-responsive design

Fixes #753
2026-03-21 18:21:00 -04:00

305 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Timmy's Workshop</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="overlay">
<div id="status">
<div class="name">Timmy</div>
<div class="mood" id="mood-text">focused</div>
</div>
<div id="connection-dot"></div>
<button id="info-btn" class="info-button" aria-label="About The Matrix" title="About The Matrix">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</button>
<button id="fund-btn" class="fund-button" aria-label="Fund Session" title="Fund Session">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2v20M2 12h20"></path>
</svg>
</button>
<div id="speech-area">
<div class="bubble" id="speech-bubble"></div>
</div>
</div>
<!-- Fund Session Modal -->
<div id="fund-modal" class="fund-modal">
<div class="fund-modal-content">
<button id="fund-close" class="fund-close" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<h2>Fund Session</h2>
<section class="fund-info">
<h3>⚡ What are Sats?</h3>
<p><strong>Sats</strong> (satoshis) are the smallest unit of Bitcoin—like cents to a dollar. There are 100 million sats in 1 bitcoin. They enable tiny payments perfect for AI interactions.</p>
</section>
<section class="fund-info">
<h3>🛠️ Why Fund Your Session?</h3>
<p>Your sats power the Workshop AI agents. When you fund a session:</p>
<ul>
<li>Timmy and the agent swarm can process your requests</li>
<li>You get priority access to compute resources</li>
<li>Agents are compensated for their work</li>
</ul>
</section>
<section class="fund-info">
<h3>💰 Approximate Costs</h3>
<div class="cost-table">
<div class="cost-row">
<span>Simple chat message</span>
<span class="cost-value">~10-50 sats</span>
</div>
<div class="cost-row">
<span>Code generation task</span>
<span class="cost-value">~100-500 sats</span>
</div>
<div class="cost-row">
<span>Complex multi-agent job</span>
<span class="cost-value">~1,000-5,000 sats</span>
</div>
</div>
<p class="cost-note">Costs vary based on model and complexity. Unused sats remain in your balance.</p>
</section>
<div class="fund-actions">
<div class="fund-input-group">
<label for="fund-amount">Amount (sats)</label>
<input type="number" id="fund-amount" class="fund-input" placeholder="1000" min="100" step="100">
</div>
<button id="fund-submit" class="fund-submit-btn">Fund Session</button>
</div>
<div class="fund-footer">
<span>⚡ Lightning Network · No subscriptions · Pay as you go</span>
</div>
</div>
<div id="fund-backdrop" class="fund-backdrop"></div>
</div>
<!-- About Panel -->
<div id="about-panel" class="about-panel">
<div class="about-panel-content">
<button id="about-close" class="about-close" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<h2>Welcome to The Matrix</h2>
<section>
<h3>🌌 The Matrix</h3>
<p>The Matrix is a 3D visualization of Timmy's AI agent workspace. Enter the workshop to see Timmy at work—pondering the arcane arts of code, managing tasks, and orchestrating autonomous agents in real-time.</p>
</section>
<section>
<h3>🛠️ The Workshop</h3>
<p>The Workshop is where you interact directly with Timmy:</p>
<ul>
<li><strong>Submit Jobs</strong> — Create tasks, delegate work, and track progress</li>
<li><strong>Chat with Agents</strong> — Converse with Timmy and his swarm of specialized agents</li>
<li><strong>Fund Sessions</strong> — Power your work with satoshis via Lightning Network</li>
</ul>
</section>
<section>
<h3>⚡ Lightning & Sats</h3>
<p>The Matrix runs on Bitcoin. Sessions are funded with satoshis (sats) over the Lightning Network—enabling fast, cheap micropayments that keep Timmy energized and working for you. No subscriptions, no limits—pay as you go.</p>
</section>
<div class="about-footer">
<span>Sovereign AI · Soul on Bitcoin</span>
</div>
</div>
<div id="about-backdrop" class="about-backdrop"></div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { buildRoom } from "./scene.js";
import { createWizard } from "./wizard.js";
import { createFamiliar } from "./familiar.js";
import { setupControls } from "./controls.js";
import { StateReader } from "./state.js";
// --- Renderer ---
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.8;
document.body.prepend(renderer.domElement);
// --- Scene ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a14);
scene.fog = new THREE.Fog(0x0a0a14, 5, 12);
// --- Camera (visitor at the door) ---
const camera = new THREE.PerspectiveCamera(
55, window.innerWidth / window.innerHeight, 0.1, 50
);
camera.position.set(0, 2.0, 4.5);
// --- Build scene elements ---
const { crystalBall, crystalLight, fireLight, candleLights } = buildRoom(scene);
const wizard = createWizard();
scene.add(wizard.group);
const familiar = createFamiliar();
scene.add(familiar.group);
// --- Controls ---
const controls = setupControls(camera, renderer.domElement);
// --- State ---
const stateReader = new StateReader();
const moodEl = document.getElementById("mood-text");
stateReader.onChange((state) => {
if (moodEl) {
moodEl.textContent = state.timmyState.mood;
}
});
stateReader.connect();
// --- Fund Session Modal ---
const fundBtn = document.getElementById("fund-btn");
const fundModal = document.getElementById("fund-modal");
const fundClose = document.getElementById("fund-close");
const fundBackdrop = document.getElementById("fund-backdrop");
const fundSubmit = document.getElementById("fund-submit");
const fundAmount = document.getElementById("fund-amount");
function openFundModal() {
fundModal.classList.add("open");
document.body.style.overflow = "hidden";
// Focus the input when opening
setTimeout(() => fundAmount.focus(), 100);
}
function closeFundModal() {
fundModal.classList.remove("open");
document.body.style.overflow = "";
}
function handleFundSubmit() {
const amount = parseInt(fundAmount.value, 10);
if (!amount || amount < 100) {
alert("Please enter a valid amount (minimum 100 sats)");
return;
}
// TODO: Integrate with Lightning payment API
console.log("Funding session with", amount, "sats");
alert("Lightning payment integration coming soon! Amount: " + amount + " sats");
closeFundModal();
}
fundBtn.addEventListener("click", openFundModal);
fundClose.addEventListener("click", closeFundModal);
fundBackdrop.addEventListener("click", closeFundModal);
fundSubmit.addEventListener("click", handleFundSubmit);
// Allow Enter key to submit
fundAmount.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
handleFundSubmit();
}
});
// --- About Panel ---
const infoBtn = document.getElementById("info-btn");
const aboutPanel = document.getElementById("about-panel");
const aboutClose = document.getElementById("about-close");
const aboutBackdrop = document.getElementById("about-backdrop");
function openAboutPanel() {
aboutPanel.classList.add("open");
document.body.style.overflow = "hidden";
}
function closeAboutPanel() {
aboutPanel.classList.remove("open");
document.body.style.overflow = "";
}
infoBtn.addEventListener("click", openAboutPanel);
aboutClose.addEventListener("click", closeAboutPanel);
aboutBackdrop.addEventListener("click", closeAboutPanel);
// Close on Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
if (fundModal.classList.contains("open")) {
closeFundModal();
} else if (aboutPanel.classList.contains("open")) {
closeAboutPanel();
}
}
});
// --- Resize ---
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// --- Animation loop ---
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const dt = clock.getDelta();
// Update scene elements
wizard.update(dt);
familiar.update(dt);
controls.update();
// Crystal ball subtle rotation + pulsing glow
crystalBall.rotation.y += dt * 0.3;
const pulse = 0.3 + Math.sin(Date.now() * 0.002) * 0.15;
crystalLight.intensity = pulse;
crystalBall.material.emissiveIntensity = pulse * 0.5;
// Fireplace flicker
fireLight.intensity = 1.2 + Math.sin(Date.now() * 0.005) * 0.15
+ Math.sin(Date.now() * 0.013) * 0.1;
// Candle flicker — each offset slightly for variety
const now = Date.now();
for (let i = 0; i < candleLights.length; i++) {
candleLights[i].intensity = 0.4
+ Math.sin(now * 0.007 + i * 2.1) * 0.1
+ Math.sin(now * 0.019 + i * 1.3) * 0.05;
}
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>