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

708 lines
30 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>The Workshop — Timmy</title>
<link rel="manifest" href="/tower/manifest.json" />
<meta name="theme-color" content="#0a0610" />
<!-- iOS PWA -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="The Workshop" />
<link rel="apple-touch-icon" href="/tower/icons/icon-192.png" />
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #080610;
overflow: hidden;
font-family: 'Courier New', monospace;
touch-action: none;
user-select: none;
-webkit-user-select: none;
}
canvas { display: block; }
/* ── HUD ─────────────────────────────────────────────────────────── */
#hud {
position: fixed; top: 16px; left: 16px;
color: #5588bb; font-size: 11px; line-height: 1.7;
text-shadow: 0 0 6px #2244aa;
pointer-events: none; z-index: 10;
}
#hud h1 {
font-size: 13px; letter-spacing: 3px; margin-bottom: 4px;
color: #7799cc; text-shadow: 0 0 10px #4466aa;
}
#session-hud {
display: none;
color: #22aa66;
text-shadow: 0 0 6px #11663388;
letter-spacing: 1px;
pointer-events: all;
line-height: 1.9;
}
#session-hud-topup {
color: #22aa66; text-decoration: none; margin-left: 5px;
letter-spacing: 1px; text-shadow: 0 0 6px #11663388;
cursor: pointer;
}
#session-hud-topup:hover { color: #44dd88; text-decoration: underline; }
#connection-status {
position: fixed; top: 16px; right: 16px;
font-size: 11px; color: #333355;
pointer-events: none; z-index: 10;
text-shadow: none;
}
#connection-status.connected {
color: #5588bb;
text-shadow: 0 0 6px #3366aa;
}
/* ── Event log ────────────────────────────────────────────────────── */
#event-log {
position: fixed; bottom: 80px; left: 16px;
width: 280px; max-height: 100px; overflow-y: auto;
color: #445566; font-size: 10px; line-height: 1.6;
pointer-events: none; z-index: 10;
}
.log-entry { opacity: 0.7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#clear-history-btn {
position: fixed; bottom: 80px; left: 300px;
background: transparent; border: 1px solid #1a1a2e;
color: #334466; font-family: 'Courier New', monospace;
font-size: 9px; letter-spacing: 1px; padding: 2px 8px;
cursor: pointer; z-index: 10;
transition: color 0.2s, border-color 0.2s;
}
#clear-history-btn:hover { color: #6688bb; border-color: #4466aa; }
/* ── Top button bar ───────────────────────────────────────────────── */
#top-buttons {
position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
display: flex; gap: 8px; z-index: 20;
}
#open-panel-btn {
font-family: 'Courier New', monospace; font-size: 11px; font-weight: bold;
color: #000; background: #4466aa; border: none;
padding: 7px 18px; cursor: pointer; letter-spacing: 2px;
box-shadow: 0 0 14px #2244aa66;
transition: background 0.15s, box-shadow 0.15s;
border-radius: 2px;
min-height: 36px;
}
#open-panel-btn:hover, #open-panel-btn:active {
background: #5577cc;
box-shadow: 0 0 20px #3355aa88;
}
#open-session-btn {
font-family: 'Courier New', monospace; font-size: 11px; font-weight: bold;
color: #d0ffe0; background: #0d3322; border: 1px solid #22aa66;
padding: 7px 18px; cursor: pointer; letter-spacing: 1px;
box-shadow: 0 0 14px #0a441a44;
transition: background 0.15s, box-shadow 0.15s, color 0.15s;
border-radius: 2px;
min-height: 36px;
}
#open-session-btn:hover, #open-session-btn:active {
background: #1a4a30;
box-shadow: 0 0 20px #22aa6666;
color: #88ffcc;
}
/* ── Low balance notice ───────────────────────────────────────────── */
#low-balance-notice {
display: none;
position: fixed; bottom: 65px; left: 0; right: 0;
text-align: center;
background: rgba(120, 50, 10, 0.92);
color: #ffcc80;
font-size: 11px; letter-spacing: 1px;
padding: 6px 12px;
z-index: 25;
border-top: 1px solid #aa6622;
}
#low-balance-notice button {
background: transparent; border: 1px solid #ffcc80;
color: #ffcc80; font-family: 'Courier New', monospace;
font-size: 11px; padding: 2px 10px; cursor: pointer;
margin-left: 8px; letter-spacing: 1px;
transition: background 0.15s;
pointer-events: all;
}
#low-balance-notice button:hover { background: rgba(255,200,100,0.15); }
/* ── Input bar ───────────────────────────────────────────────────── */
#input-bar {
position: fixed; bottom: 0; left: 0; right: 0;
display: flex; align-items: center; gap: 8px;
padding: 10px 16px;
background: rgba(8, 6, 16, 0.88);
border-top: 1px solid #1a1a2e;
z-index: 20;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
#visitor-input {
flex: 1;
background: rgba(20, 16, 36, 0.9);
border: 1px solid #2a2a44;
color: #aabbdd;
font-family: 'Courier New', monospace;
font-size: 14px;
padding: 10px 12px;
outline: none;
min-height: 44px;
border-radius: 3px;
transition: border-color 0.2s, box-shadow 0.4s;
-webkit-appearance: none;
}
#visitor-input::placeholder { color: #333355; }
#visitor-input:focus { border-color: #4466aa; }
#visitor-input.session-active {
border-color: #22aa66;
box-shadow: 0 0 10px #22aa6630, inset 0 0 4px #22aa6618;
animation: session-pulse 3s ease-in-out infinite;
}
@keyframes session-pulse {
0%, 100% { box-shadow: 0 0 10px #22aa6630, inset 0 0 4px #22aa6618; }
50% { box-shadow: 0 0 22px #22aa6670, inset 0 0 8px #22aa6630; }
}
#visitor-input.session-active::placeholder { color: #226644; }
#send-btn {
background: rgba(30, 40, 80, 0.9);
border: 1px solid #2a2a44;
color: #5577aa;
font-family: 'Courier New', monospace;
font-size: 16px;
width: 44px; height: 44px;
cursor: pointer;
border-radius: 3px;
transition: background 0.15s, border-color 0.15s, color 0.15s;
display: flex; align-items: center; justify-content: center;
}
#send-btn:hover, #send-btn:active {
background: rgba(50, 70, 140, 0.9);
border-color: #4466aa;
color: #88aadd;
}
#send-btn:disabled { opacity: 0.35; cursor: not-allowed; }
/* ── Payment panel (right side) ───────────────────────────────────── */
#payment-panel {
position: fixed; top: 0; right: -420px;
width: 400px; height: 100%;
background: rgba(5, 3, 12, 0.97);
border-left: 1px solid #1a1a2e;
padding: 24px 20px;
overflow-y: auto; z-index: 100;
font-family: 'Courier New', monospace;
transition: right 0.35s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: -8px 0 32px rgba(40, 60, 120, 0.15);
}
#payment-panel.open { right: 0; }
#payment-panel h2 {
font-size: 13px; letter-spacing: 3px; color: #6688bb;
text-shadow: 0 0 10px #2244aa;
margin-bottom: 20px; border-bottom: 1px solid #1a1a2e; padding-bottom: 10px;
}
#payment-close {
position: absolute; top: 16px; right: 16px;
background: transparent; border: 1px solid #1a1a2e;
color: #333355; font-family: 'Courier New', monospace;
font-size: 16px; width: 28px; height: 28px;
cursor: pointer; transition: color 0.2s, border-color 0.2s;
}
#payment-close:hover { color: #6688bb; border-color: #4466aa; }
/* ── Session panel (left side) ────────────────────────────────────── */
#session-panel {
position: fixed; top: 0; left: -420px;
width: 400px; height: 100%;
background: rgba(3, 8, 5, 0.97);
border-right: 1px solid #0e2318;
padding: 24px 20px;
overflow-y: auto; z-index: 100;
font-family: 'Courier New', monospace;
transition: left 0.35s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 8px 0 32px rgba(10, 50, 25, 0.20);
}
#session-panel.open { left: 0; }
#session-panel h2 {
font-size: 13px; letter-spacing: 3px; color: #33bb77;
text-shadow: 0 0 10px #116633;
margin-bottom: 20px; border-bottom: 1px solid #0e2318; padding-bottom: 10px;
}
#session-close {
position: absolute; top: 16px; right: 16px;
background: transparent; border: 1px solid #0e2318;
color: #226644; font-family: 'Courier New', monospace;
font-size: 16px; width: 28px; height: 28px;
cursor: pointer; transition: color 0.2s, border-color 0.2s;
}
#session-close:hover { color: #44dd88; border-color: #22aa66; }
/* Amount presets */
.session-amount-presets {
display: flex; gap: 6px; flex-wrap: wrap; margin: 10px 0;
}
.session-amount-btn {
background: transparent; border: 1px solid #0e2318;
color: #226644; font-family: 'Courier New', monospace;
font-size: 11px; letter-spacing: 1px; padding: 5px 11px;
cursor: pointer; transition: all 0.15s; border-radius: 2px;
}
.session-amount-btn:hover { background: #0e2318; border-color: #22aa66; color: #44dd88; }
.session-amount-btn.active { background: #0e2318; border-color: #22aa66; color: #44dd88; }
/* QR placeholder */
.qr-placeholder {
display: flex; align-items: center; justify-content: center;
margin-top: 8px;
background: #020806; border: 1px solid #0e2318;
color: #1a4430; font-size: 11px; letter-spacing: 3px;
height: 80px;
}
/* Amount number inputs */
.session-amount-input {
background: #020806; border: 1px solid #0e2318;
color: #44dd88; font-family: 'Courier New', monospace;
font-size: 15px; font-weight: bold; letter-spacing: 1px;
padding: 6px 10px; width: 110px;
outline: none; border-radius: 2px;
transition: border-color 0.2s;
-moz-appearance: textfield;
}
.session-amount-input:focus { border-color: #22aa66; }
.session-amount-input::-webkit-outer-spin-button,
.session-amount-input::-webkit-inner-spin-button { -webkit-appearance: none; }
.session-amount-row {
display: flex; align-items: center; gap: 6px; margin: 8px 0;
}
.session-amount-row span {
color: #226644; font-size: 11px; letter-spacing: 1px;
}
/* Active session balance tag */
.amount-tag.session-green { border-color: #22aa66; color: #44dd88; }
/* Session status / error lines */
#session-status-fund,
#session-status-invoice,
#session-status-active,
#session-status-topup {
font-size: 11px; margin-top: 8px; min-height: 16px; color: #22aa66;
}
#session-error {
font-size: 11px; margin-top: 8px; min-height: 16px; color: #994444;
}
/* ── Shared panel primitives ──────────────────────────────────────── */
.panel-label { font-size: 10px; letter-spacing: 2px; color: #334466; margin-bottom: 6px; margin-top: 16px; }
#session-panel .panel-label { color: #1a4430; }
#job-input {
width: 100%; background: #060310; border: 1px solid #1a1a2e;
color: #aabbdd; font-family: 'Courier New', monospace; font-size: 12px;
padding: 10px; resize: vertical; min-height: 90px;
outline: none; transition: border-color 0.2s;
}
#job-input:focus { border-color: #4466aa; }
#job-input::placeholder { color: #1a1a2e; }
.panel-btn {
width: 100%; margin-top: 12px;
background: transparent; border: 1px solid #334466;
color: #5577aa; font-family: 'Courier New', monospace;
font-size: 12px; letter-spacing: 2px; padding: 10px;
cursor: pointer; transition: all 0.2s;
}
.panel-btn:hover:not(:disabled) { background: #334466; color: #aabbdd; }
.panel-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.panel-btn.primary { border-color: #4466aa; color: #7799cc; }
.panel-btn.primary:hover:not(:disabled) { background: #4466aa; color: #fff; }
.panel-btn.primary-green { border-color: #22aa66; color: #44dd88; }
.panel-btn.primary-green:hover:not(:disabled) { background: #22aa66; color: #000; }
.panel-btn.danger { border-color: #663333; color: #995555; }
.panel-btn.muted { border-color: #0e2318; color: #226644; }
.panel-btn.muted:hover:not(:disabled) { background: #0e2318; color: #44dd88; }
#job-status { font-size: 11px; margin-top: 8px; color: #5577aa; min-height: 16px; }
#job-error { font-size: 11px; margin-top: 4px; min-height: 16px; color: #994444; }
.invoice-box {
background: #060310; border: 1px solid #1a1a2e;
padding: 10px; margin-top: 8px; font-size: 10px; color: #334466;
word-break: break-all; max-height: 80px; overflow-y: auto;
}
#session-panel .invoice-box {
background: #020806; border-color: #0e2318; color: #1a4430;
}
.copy-row { display: flex; gap: 8px; margin-top: 6px; align-items: stretch; }
.copy-row .invoice-box { flex: 1; margin-top: 0; }
.copy-btn {
background: transparent; border: 1px solid #1a1a2e; color: #334466;
font-family: 'Courier New', monospace; font-size: 10px;
padding: 0 10px; cursor: pointer; transition: all 0.2s; white-space: nowrap;
}
.copy-btn:hover { border-color: #4466aa; color: #6688bb; }
#session-panel .copy-btn { border-color: #0e2318; color: #1a4430; }
#session-panel .copy-btn:hover { border-color: #22aa66; color: #44dd88; }
.amount-tag {
display: inline-block; background: #0a0820;
border: 1px solid #334466; color: #6688bb;
font-size: 16px; font-weight: bold; letter-spacing: 2px;
padding: 6px 14px; margin-top: 8px;
}
#session-panel .amount-tag {
background: #020806; border-color: #22aa66; color: #44dd88;
}
#job-result {
background: #060310; border: 1px solid #1a1a2e;
color: #aabbdd; padding: 12px; font-size: 12px;
line-height: 1.6; margin-top: 8px;
white-space: pre-wrap; max-height: 260px; overflow-y: auto;
}
.panel-link {
display: block; text-align: center; margin-top: 20px;
font-size: 10px; letter-spacing: 1px; color: #1a1a2e;
text-decoration: none; transition: color 0.2s;
}
.panel-link:hover { color: #5577aa; }
/* ── AR pulse animation ──────────────────────────────────────────── */
@keyframes ar-pulse {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.5); }
}
/* ── Crosshair ───────────────────────────────────────────────────── */
#crosshair {
position: fixed; top: 50%; left: 50%;
transform: translate(-50%, -50%);
pointer-events: none; z-index: 12;
opacity: 0.5;
}
#crosshair::before, #crosshair::after {
content: ''; position: absolute;
background: rgba(180, 160, 220, 0.7);
border-radius: 1px;
}
#crosshair::before { width: 16px; height: 1px; top: 0; left: -8px; }
#crosshair::after { width: 1px; height: 16px; top: -8px; left: 0; }
/* ── Lock hint (desktop) ─────────────────────────────────────────── */
#lock-hint {
position: fixed; inset: 0;
display: none;
align-items: center; justify-content: center;
z-index: 11; pointer-events: none;
}
#lock-hint .lock-badge {
background: rgba(8, 6, 20, 0.72);
border: 1px solid rgba(80, 100, 160, 0.5);
border-radius: 8px;
color: #5577aa;
font-family: 'Courier New', monospace;
font-size: 11px; letter-spacing: 2px;
padding: 10px 24px;
text-align: center;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
animation: lock-fade 2s ease-in-out infinite alternate;
}
@keyframes lock-fade {
0% { opacity: 0.45; }
100% { opacity: 0.9; }
}
/* ── Virtual joystick (mobile) ───────────────────────────────────── */
#joy-pad {
position: fixed;
display: none; /* shown by navigation.js on mobile */
align-items: center; justify-content: center;
width: 110px; height: 110px;
border-radius: 50%;
background: rgba(40, 30, 70, 0.38);
border: 1.5px solid rgba(100, 80, 180, 0.45);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
z-index: 18;
pointer-events: none; /* touches handled on canvas */
opacity: 0.35;
transition: opacity 0.25s;
bottom: 80px; left: 20px;
}
#joy-nub {
width: 38px; height: 38px; border-radius: 50%;
background: rgba(140, 110, 220, 0.65);
border: 1.5px solid rgba(180, 150, 255, 0.6);
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 14px rgba(140, 110, 220, 0.5);
transition: transform 0.05s linear;
pointer-events: none;
}
/* ── AR label pulse ──────────────────────────────────────────────── */
.ar-label { transition: opacity 0.25s; }
/* ── WebGL recovery overlay ──────────────────────────────────────── */
#webgl-recovery-overlay {
display: none; position: fixed; inset: 0; z-index: 200;
background: rgba(5, 3, 12, 0.92);
justify-content: center; align-items: center;
pointer-events: none;
}
#webgl-recovery-overlay .recovery-text {
color: #5577aa; font-family: 'Courier New', monospace;
font-size: 15px; letter-spacing: 3px;
animation: ctx-blink 1.2s step-end infinite;
}
@keyframes ctx-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.2; }
}
/* ── Timmy identity card ──────────────────────────────────────────── */
#timmy-id-card {
position: fixed; bottom: 80px; right: 16px;
font-size: 10px; color: #334466;
pointer-events: all; z-index: 10;
text-align: right; line-height: 1.8;
}
#timmy-id-card .id-label {
letter-spacing: 2px; color: #223355;
text-transform: uppercase; font-size: 9px;
}
#timmy-id-card .id-npub {
color: #4466aa; cursor: pointer;
text-decoration: underline dotted;
font-size: 10px; letter-spacing: 0.5px;
}
#timmy-id-card .id-npub:hover { color: #88aadd; }
#timmy-id-card .id-zaps { color: #556688; font-size: 9px; }
</style>
</head>
<body>
<div id="hud">
<h1>THE WORKSHOP</h1>
<div id="fps">FPS: --</div>
<div id="active-jobs">JOBS: 0</div>
<div id="session-hud">
<span id="session-hud-balance">Balance: -- sats</span>
<a href="#" id="session-hud-topup">⚡ Top Up</a>
</div>
</div>
<div id="connection-status">OFFLINE</div>
<div id="event-log"></div>
<button id="clear-history-btn">CLEAR HISTORY</button>
<!-- ── Timmy identity card ────────────────────────────────────────── -->
<div id="timmy-id-card">
<div class="id-label">TIMMY IDENTITY</div>
<div class="id-npub" id="timmy-npub" title="Click to copy Timmy's Nostr npub"></div>
<div class="id-zaps" id="timmy-zap-count">⚡ 0 zaps sent</div>
</div>
<!-- ── Top action buttons ─────────────────────────────────────────── -->
<div id="top-buttons">
<button id="open-panel-btn">⚡ SUBMIT JOB</button>
<button id="open-session-btn">⚡ FUND SESSION</button>
</div>
<!-- ── Low balance notice (above input bar) ───────────────────────── -->
<div id="low-balance-notice">
⚡ Low balance —
<button id="topup-quick-btn">Top Up</button>
</div>
<!-- ── Input bar ──────────────────────────────────────────────────── -->
<div id="input-bar">
<input type="text" id="visitor-input" placeholder="Say something to Timmy…" autocomplete="off" autocorrect="off" spellcheck="false" />
<button id="send-btn" aria-label="Send"></button>
</div>
<!-- ── Payment panel (right side) ────────────────────────────────── -->
<div id="payment-panel">
<button id="payment-close"></button>
<h2>⚡ TIMMY — JOB SUBMISSION</h2>
<div data-step="input">
<div class="panel-label">YOUR REQUEST</div>
<textarea id="job-input" maxlength="500" placeholder="Ask Timmy anything… (max 500 chars)"></textarea>
<button class="panel-btn primary" id="job-submit-btn">CREATE JOB →</button>
<a class="panel-link" href="/api/ui" target="_blank">Open full UI ↗</a>
</div>
<div data-step="eval-invoice" style="display:none">
<div class="panel-label">EVAL FEE</div>
<div class="amount-tag" id="eval-amount">10 sats</div>
<div class="panel-label" style="margin-top:12px">LIGHTNING INVOICE</div>
<div class="copy-row">
<div class="invoice-box" id="eval-payment-request"></div>
<button class="copy-btn" onclick="_timmyCopy('eval-payment-request')">COPY</button>
</div>
<span id="eval-hash" data-hash=""></span>
<button class="panel-btn primary" id="pay-eval-btn">⚡ SIMULATE PAYMENT</button>
</div>
<div data-step="work-invoice" style="display:none">
<div class="panel-label">WORK FEE</div>
<div class="amount-tag" id="work-amount">-- sats</div>
<div class="panel-label" style="margin-top:12px">LIGHTNING INVOICE</div>
<div class="copy-row">
<div class="invoice-box" id="work-payment-request"></div>
<button class="copy-btn" onclick="_timmyCopy('work-payment-request')">COPY</button>
</div>
<span id="work-hash" data-hash=""></span>
<button class="panel-btn primary" id="pay-work-btn">⚡ SIMULATE PAYMENT</button>
</div>
<div data-step="result" style="display:none">
<div class="panel-label" id="result-label">AI RESULT</div>
<pre id="job-result"></pre>
<button class="panel-btn" id="new-job-btn" style="margin-top:16px">← NEW JOB</button>
</div>
<div id="job-status"></div>
<div id="job-error"></div>
</div>
<!-- ── Session panel (left side) ─────────────────────────────────── -->
<div id="session-panel">
<button id="session-close"></button>
<h2>⚡ TIMMY — SESSION</h2>
<!-- Step: fund — choose deposit amount -->
<div data-session-step="fund">
<div class="panel-label">DEPOSIT AMOUNT (20010,000 sats)</div>
<div class="session-amount-presets">
<button class="session-amount-btn active" data-sats="200">200</button>
<button class="session-amount-btn" data-sats="500">500</button>
<button class="session-amount-btn" data-sats="1000">1000</button>
<button class="session-amount-btn" data-sats="2000">2000</button>
<button class="session-amount-btn" data-sats="5000">5000</button>
<button class="session-amount-btn" data-sats="10000">10k</button>
</div>
<div class="session-amount-row">
<input type="number" id="session-amount-input" class="session-amount-input"
min="200" max="10000" value="200" step="1" />
<span>sats</span>
</div>
<button class="panel-btn primary-green" id="session-create-btn" style="margin-top:12px">START SESSION →</button>
<div id="session-status-fund"></div>
</div>
<!-- Step: invoice — pay the deposit -->
<div data-session-step="invoice" style="display:none">
<div class="panel-label">DEPOSIT AMOUNT</div>
<div class="amount-tag" id="session-invoice-amount">-- sats</div>
<div class="panel-label" style="margin-top:14px">SCAN OR COPY INVOICE</div>
<div class="qr-placeholder" id="session-invoice-qr">[ QR ]</div>
<div class="copy-row" style="margin-top:6px">
<div class="invoice-box" id="session-invoice-pr"></div>
<button class="copy-btn" onclick="_timmyCopy('session-invoice-pr')">COPY</button>
</div>
<span id="session-invoice-hash" data-hash="" style="display:none"></span>
<button class="panel-btn primary-green" id="session-pay-btn" style="margin-top:14px">⚡ SIMULATE PAYMENT</button>
<div id="session-status-invoice"></div>
</div>
<!-- Step: active — session running, show balance + topup -->
<div data-session-step="active" style="display:none">
<div class="panel-label">BALANCE</div>
<div class="amount-tag session-green" id="session-active-amount">-- sats</div>
<p style="font-size:10px;color:#1a4430;margin-top:14px;line-height:1.6;letter-spacing:1px">
TYPE IN THE INPUT BAR TO ASK TIMMY.<br>EACH REQUEST DEDUCTS FROM YOUR BALANCE.
</p>
<button class="panel-btn muted" id="session-topup-btn" style="margin-top:20px">⚡ TOP UP BALANCE</button>
<div id="session-status-active"></div>
</div>
<!-- Step: topup — choose topup amount and pay -->
<div data-session-step="topup" style="display:none">
<div class="panel-label">TOPUP AMOUNT (20010,000 sats)</div>
<div class="session-amount-presets">
<button class="session-amount-btn active" data-sats="200">200</button>
<button class="session-amount-btn" data-sats="500">500</button>
<button class="session-amount-btn" data-sats="1000">1000</button>
<button class="session-amount-btn" data-sats="2000">2000</button>
<button class="session-amount-btn" data-sats="5000">5000</button>
<button class="session-amount-btn" data-sats="10000">10k</button>
</div>
<div class="session-amount-row">
<input type="number" id="session-topup-input" class="session-amount-input"
min="200" max="10000" value="200" step="1" />
<span>sats</span>
</div>
<button class="panel-btn primary-green" id="session-topup-create-btn" style="margin-top:12px">CREATE TOPUP INVOICE →</button>
<div id="session-topup-pr-row" style="display:none">
<div class="panel-label" style="margin-top:14px">SCAN OR COPY INVOICE</div>
<div class="qr-placeholder" id="session-topup-qr">[ QR ]</div>
<div class="copy-row" style="margin-top:6px">
<div class="invoice-box" id="session-topup-pr"></div>
<button class="copy-btn" onclick="_timmyCopy('session-topup-pr')">COPY</button>
</div>
<span id="session-topup-hash" data-hash="" style="display:none"></span>
<button class="panel-btn primary-green" id="session-topup-pay-btn" style="margin-top:10px">⚡ SIMULATE TOPUP</button>
</div>
<div id="session-status-topup"></div>
<button class="panel-btn muted" id="session-back-btn" style="margin-top:10px">← BACK</button>
</div>
<div id="session-error"></div>
<!-- ── Trust Panel (elite-only vouch UI) ──────────────────────────── -->
<div id="vouch-panel" style="display:none; margin-top:28px; border-top:1px solid #0e2318; padding-top:16px">
<div class="panel-label" style="color:#bb8822; letter-spacing:2px">TRUST PANEL — ELITE</div>
<p style="font-size:10px;color:#665522;margin-top:6px;line-height:1.6;letter-spacing:0.5px">
VOUCH FOR A NEWCOMER TO GRANT +20 TRUST POINTS.
</p>
<input type="text" id="vouch-pubkey-input"
class="session-amount-input"
style="width:100%;margin-top:10px;font-size:11px;color:#bb8822;border-color:#33280e"
placeholder="npub1... or hex pubkey"
autocomplete="off" autocorrect="off" spellcheck="false" />
<button class="panel-btn" id="vouch-btn"
style="border-color:#bb8822;color:#bb8822;margin-top:10px">
VOUCH
</button>
<div id="vouch-status" style="font-size:11px;margin-top:8px;min-height:16px;color:#22aa66"></div>
</div>
</div>
<!-- ── FPS crosshair ─────────────────────────────────────────────── -->
<div id="crosshair"></div>
<!-- ── Pointer-lock hint (desktop) ──────────────────────────────── -->
<div id="lock-hint">
<div class="lock-badge">CLICK TO ENTER · WASD TO MOVE · ESC TO EXIT</div>
</div>
<!-- ── Virtual joystick (mobile) ─────────────────────────────────── -->
<div id="joy-pad">
<div id="joy-nub"></div>
</div>
<!-- ── AR floating labels container ──────────────────────────────── -->
<div id="ar-labels" style="position:fixed;inset:0;pointer-events:none;z-index:15;overflow:hidden;"></div>
<div id="webgl-recovery-overlay">
<span class="recovery-text">GPU context lost — recovering...</span>
</div>
<script type="module" src="./js/main.js"></script>
</body>
</html>