Spaces:
Sleeping
Sleeping
File size: 8,080 Bytes
9044b78 68a8db7 61cd9b6 ebe2921 68a8db7 6fd6db0 68a8db7 4b5c4ea 68a8db7 4b5c4ea 68a8db7 4b5c4ea 68a8db7 4b5c4ea 68a8db7 4b5c4ea 68a8db7 4b5c4ea 68a8db7 4b5c4ea 68a8db7 6fd6db0 68a8db7 9d6e9b4 68a8db7 dec52d9 101de84 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | 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
|