Myco / ui /interface.py
byte-vortex's picture
Deploy Myco from CI
dec52d9 verified
Raw
History Blame Contribute Delete
8.08 kB
import gradio as gr
import html
from game.engine import get_myco_narrative
def cute_mushroom_garden_html() -> str:
"""
Pure iframe srcdoc implementation of a cute mushroom garden.
Generates happy, blinking mushrooms that grow, sway, and shrink.
"""
iframe_content = """
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
overflow: hidden;
/* Soft magical forest background */
background: linear-gradient(180deg, #1a2b4c 0%, #2a4c5e 50%, #4a7c59 100%);
}
canvas { display: block; }
</style>
</head>
<body>
<canvas id="cute-canvas"></canvas>
<script>
const canvas = document.getElementById('cute-canvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
const mushrooms = [];
const fireflies = [];
// Helper to draw a single cute mushroom
function drawMushroom(m, time) {
ctx.save();
ctx.translate(m.x, m.y);
// Bouncing and swaying math
const sway = Math.sin(time * 0.002 + m.phase) * 0.1;
const bounce = Math.sin(time * 0.005 + m.phase) * 0.05 + 1;
ctx.rotate(sway);
ctx.scale(m.currentSize, m.currentSize * bounce);
// 1. Draw Stem
ctx.fillStyle = '#fff4e6';
ctx.beginPath();
ctx.roundRect(-12, 0, 24, 35, 10);
ctx.fill();
// 2. Draw Cute Face
ctx.fillStyle = '#4a3b32';
const isBlinking = (time % m.blinkInterval) < 150;
if (isBlinking) {
// Closed eyes (wincing/blinking)
ctx.fillRect(-7, 12, 4, 2);
ctx.fillRect(3, 12, 4, 2);
} else {
// Open eyes
ctx.beginPath(); ctx.arc(-5, 12, 2.5, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(5, 12, 2.5, 0, Math.PI*2); ctx.fill();
}
// Tiny smile
ctx.strokeStyle = '#4a3b32';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(0, 15, 3, 0, Math.PI, false);
ctx.stroke();
// Blush
ctx.fillStyle = 'rgba(255, 150, 150, 0.5)';
ctx.beginPath(); ctx.arc(-9, 16, 3, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(9, 16, 3, 0, Math.PI*2); ctx.fill();
// 3. Draw Cap
ctx.fillStyle = `hsl(${m.hue}, 80%, 75%)`; // Soft pastel colors
ctx.beginPath();
ctx.moveTo(-35, 5);
// Bezier curves make it look like a puffy umbrella
ctx.bezierCurveTo(-35, -30, 35, -30, 35, 5);
ctx.bezierCurveTo(35, 15, -35, 15, -35, 5);
ctx.fill();
// Cap Shadow/Detail
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath(); ctx.arc(-15, -12, 6, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(12, -8, 4, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(0, -18, 5, 0, Math.PI*2); ctx.fill();
ctx.restore();
}
function animate(time) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// --- Fireflies Logic ---
if (Math.random() < 0.1 && fireflies.length < 50) {
fireflies.push({
x: Math.random() * canvas.width,
y: canvas.height + 10,
size: Math.random() * 2 + 1,
speed: Math.random() * 1 + 0.5,
wobble: Math.random() * Math.PI * 2
});
}
ctx.fillStyle = '#fffae6';
for (let i = fireflies.length - 1; i >= 0; i--) {
let f = fireflies[i];
f.y -= f.speed;
f.x += Math.sin(time * 0.002 + f.wobble) * 0.5;
ctx.globalAlpha = Math.sin(time * 0.005 + f.wobble) * 0.5 + 0.5;
ctx.beginPath();
ctx.arc(f.x, f.y, f.size, 0, Math.PI * 2);
ctx.fill();
if (f.y < -10) fireflies.splice(i, 1);
}
ctx.globalAlpha = 1.0;
// --- Mushroom Logic ---
// Randomly spawn new mushrooms
if (Math.random() < 0.02 && mushrooms.length < 35) {
mushrooms.push({
x: Math.random() * canvas.width,
y: (canvas.height * 0.4) + (Math.random() * canvas.height * 0.6), // Spawn in bottom 60%
targetSize: Math.random() * 1.5 + 0.8,
currentSize: 0,
hue: Math.random() * 360,
phase: Math.random() * Math.PI * 2,
blinkInterval: Math.floor(Math.random() * 3000) + 2000,
life: Math.random() * 500 + 300 // How long they stay before shrinking
});
}
// Sort mushrooms by Y coordinate so closer ones draw on top (Depth sorting)
mushrooms.sort((a, b) => a.y - b.y);
for (let i = mushrooms.length - 1; i >= 0; i--) {
let m = mushrooms[i];
m.life--;
if (m.life < 0) {
m.targetSize = 0; // Shrink away
}
// Smoothly grow or shrink
m.currentSize += (m.targetSize - m.currentSize) * 0.05;
drawMushroom(m, time);
// Remove if totally shrunk
if (m.life < 0 && m.currentSize < 0.05) {
mushrooms.splice(i, 1);
}
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>
</body>
</html>
"""
# Safely escape the HTML
escaped_srcdoc = html.escape(iframe_content)
# Return a clean, borderless container
return f'''
<div style="width: 100%; height: 500px; border-radius: 16px; overflow: hidden; border: 4px solid #f8c8dc; box-shadow: 0 8px 32px rgba(0,0,0,0.15);">
<iframe
srcdoc="{escaped_srcdoc}"
style="width: 100%; height: 100%; border: none;"
scrolling="no">
</iframe>
</div>
'''
def render_shroom_tab():
# 1. Assign the block to 'demo' so it can be returned
with gr.Blocks() as demo:
with gr.Tabs():
with gr.Tab("✨ Magic Mushroom Garden"):
# The pure animation
gr.HTML(value=cute_mushroom_garden_html())
# Subtle attribution
gr.Markdown(
"<sub>*Story generated by Myco AI*</sub>",
elem_id="ai-attribution"
)
# 2. Define the narrative component ONCE
narrative = gr.Markdown(
value="Myco is searching for a story hidden in the moss...",
elem_id="narrative-card"
)
# 3. Timer updates the ONE narrative component
gr.Timer(20).tick(fn=get_myco_narrative, outputs=narrative)
return demo