Files
Llamagochi/frontend/index.html
Vlad Doloman 71b3c945ee feat: sleep duration timer in stats panel
Track sleeping_since (ISO timestamp) in ServerState; auto-cleared by _set()
whenever state leaves sleeping. Frontend runs a 1s interval showing elapsed
time as "Xs", "Xm Ys", or "Xh Ym" in a new ASLEEP stat row (visible only
during state-sleeping). Timer survives page reload via server-side timestamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 13:49:50 +03:00

165 lines
6.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Llamagochi</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="state-offline">
<div id="app">
<!-- ── Screen panel ───────────────────────────────────────────── -->
<div id="screen-panel">
<div id="screen">
<div id="ws-dot" title="WebSocket connection"></div>
<div id="llama-wrap">
<svg id="llama" viewBox="0 0 100 150" xmlns="http://www.w3.org/2000/svg">
<!-- Tail -->
<path class="part tail" d="M 72 82 Q 88 68 80 88" stroke-width="5"
fill="none" stroke-linecap="round"/>
<!-- Body -->
<rect class="part body" x="28" y="62" width="46" height="42" rx="13"/>
<!-- Neck -->
<rect class="part neck" x="42" y="33" width="16" height="35" rx="7"/>
<!-- Head -->
<ellipse class="part head" cx="50" cy="27" rx="16" ry="14"/>
<!-- Ears -->
<ellipse class="part ear left-ear" cx="38" cy="15" rx="5" ry="9"
transform="rotate(-15 38 15)"/>
<ellipse class="part ear right-ear" cx="62" cy="15" rx="5" ry="9"
transform="rotate(15 62 15)"/>
<!-- Eyes: open (default) -->
<circle class="part eye left-eye" cx="43" cy="25" r="2.8"/>
<circle class="part eye right-eye" cx="57" cy="25" r="2.8"/>
<circle class="shine" cx="44.2" cy="23.8" r="0.9"/>
<circle class="shine" cx="58.2" cy="23.8" r="0.9"/>
<!-- Eyes: closed (sleeping) -->
<path class="part sleep-eye left-sleep-eye"
d="M 40 25 Q 43 23 46 25" stroke-width="2" fill="none" stroke-linecap="round"/>
<path class="part sleep-eye right-sleep-eye"
d="M 54 25 Q 57 23 60 25" stroke-width="2" fill="none" stroke-linecap="round"/>
<!-- Nose -->
<ellipse class="part nose" cx="50" cy="34" rx="5.5" ry="3.5"/>
<circle cx="47.5" cy="34" r="1.2" class="nostril"/>
<circle cx="52.5" cy="34" r="1.2" class="nostril"/>
<!-- Mouth: closed smile -->
<path class="part mouth-closed"
d="M 47 38 Q 50 41 53 38" stroke-width="1.8" fill="none" stroke-linecap="round"/>
<!-- Mouth: open (talking) -->
<ellipse class="part mouth-open" cx="50" cy="39" rx="4.5" ry="3"/>
<!-- Legs + hooves (grouped so animation moves both together) -->
<g class="leg-fl">
<rect class="part leg" x="29" y="100" width="10" height="30" rx="4"/>
<rect class="part hoof" x="29" y="126" width="10" height="4" rx="2"/>
</g>
<g class="leg-fr">
<rect class="part leg" x="40" y="100" width="10" height="30" rx="4"/>
<rect class="part hoof" x="40" y="126" width="10" height="4" rx="2"/>
</g>
<g class="leg-bl">
<rect class="part leg" x="51" y="100" width="10" height="30" rx="4"/>
<rect class="part hoof" x="51" y="126" width="10" height="4" rx="2"/>
</g>
<g class="leg-br">
<rect class="part leg" x="62" y="100" width="10" height="30" rx="4"/>
<rect class="part hoof" x="62" y="126" width="10" height="4" rx="2"/>
</g>
<!-- Book (reading) -->
<g class="part book">
<rect x="10" y="68" width="22" height="16" rx="2"/>
<line x1="21" y1="68" x2="21" y2="84" stroke-width="1"/>
<line x1="12" y1="73" x2="20" y2="73" stroke-width="1"/>
<line x1="12" y1="77" x2="20" y2="77" stroke-width="1"/>
<line x1="22" y1="73" x2="30" y2="73" stroke-width="1"/>
<line x1="22" y1="77" x2="30" y2="77" stroke-width="1"/>
</g>
</svg>
<!-- ZZZ bubble (sleeping) -->
<div id="zzz-wrap" aria-hidden="true">
<span class="z z1">Z</span>
<span class="z z2">Z</span>
<span class="z z3">Z</span>
</div>
<!-- Speech bubble (generating) -->
<div id="speech-bubble" aria-hidden="true">
<span class="dot d1">.</span><span class="dot d2">.</span><span class="dot d3">.</span>
</div>
</div><!-- /llama-wrap -->
<div id="state-label">OFFLINE</div>
<!-- Loading progress bar (loading state only) -->
<div id="progress-wrap">
<div id="progress-bar"><div id="progress-fill"></div></div>
<div id="progress-label"></div>
</div>
</div><!-- /screen -->
</div><!-- /screen-panel -->
<!-- ── Stats panel ────────────────────────────────────────────── -->
<div id="stats-panel">
<div id="resize-handle" title="Drag to resize"></div>
<div class="stat-row" id="row-model">
<span class="sk">MODEL</span>
<span class="sv" id="sv-model"></span>
</div>
<div class="stat-row" id="row-state">
<span class="sk">STATE</span>
<span class="sv" id="sv-state">offline</span>
</div>
<div class="stat-row" id="row-prompt">
<span class="sk">PROMPT</span>
<span class="sv" id="sv-prompt"></span>
</div>
<div class="stat-row" id="row-speed">
<span class="sk">SPEED</span>
<span class="sv" id="sv-speed"></span>
</div>
<div class="stat-row" id="row-tokens">
<span class="sk">TOKENS</span>
<span class="sv" id="sv-tokens"></span>
</div>
<div class="stat-row" id="row-load-stage">
<span class="sk">STAGE</span>
<span class="sv" id="sv-load-stage"></span>
</div>
<div class="stat-row" id="row-sleep-timer">
<span class="sk">ASLEEP</span>
<span class="sv" id="sv-sleep-timer"></span>
</div>
<div class="stat-row" id="row-reqs">
<span class="sk">REQS</span>
<span class="sv" id="sv-reqs">0</span>
</div>
</div><!-- /stats-panel -->
</div><!-- /app -->
<script src="/static/app.js"></script>
</body>
</html>