Custom Code Experiment •
Dragon 1: Canvas
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dragon 1: Canvas</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0a0000;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow: hidden;
}
.scene {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at 50% 55%, #2a0000 0%, #0a0000 70%);
}
#fire-canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.flag-panel {
position: relative;
width: 540px;
height: 340px;
background: linear-gradient(180deg, #c8000e 0%, #c8000e 50%, #eee8d0 50%, #eee8d0 100%);
border: 2px solid rgba(80,40,0,0.5);
box-shadow:
0 0 0 5px #0a0000,
0 0 80px 30px rgba(200,0,10,0.25);
border-radius: 3px;
overflow: visible;
}
.flag-panel::after {
content: '';
position: absolute;
inset: 0;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='4' fill='rgba(0,0,0,0.04)'/%3E%3C/svg%3E");
pointer-events: none;
border-radius: 3px;
}
.dragon-wrap {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 380px;
height: 300px;
animation: breathe 3.2s ease-in-out infinite;
filter: drop-shadow(0 6px 20px rgba(0,0,0,0.7));
overflow: visible;
}
.tail-group {
animation: tail-swish 3s ease-in-out infinite;
transform-origin: 265px 188px;
}
.wing-upper {
animation: wing-beat 3.2s ease-in-out infinite;
transform-origin: 185px 112px;
}
.dragon-eye {
animation: eye-pulse 1.8s ease-in-out infinite alternate;
}
.eye-glow {
animation: eye-glow-anim 1.8s ease-in-out infinite alternate;
}
@keyframes breathe {
0%,100% { transform: translate(-50%,-50%) scale(1) rotate(0deg); }
40% { transform: translate(-50%,-51.5%) scale(1.012) rotate(0.3deg); }
70% { transform: translate(-50%,-50.5%) scale(1.008) rotate(-0.2deg); }
}
@keyframes tail-swish {
0% { transform: rotate(0deg); }
25% { transform: rotate(5deg); }
75% { transform: rotate(-6deg); }
100% { transform: rotate(0deg); }
}
@keyframes wing-beat {
0%,100% { transform: rotate(0deg) scaleY(1); }
30% { transform: rotate(-4deg) scaleY(1.05); }
65% { transform: rotate(3deg) scaleY(0.96); }
}
@keyframes eye-pulse {
from { fill: #ff5500; }
to { fill: #ffdd00; }
}
@keyframes eye-glow-anim {
from { opacity: 0.4; r: 7; }
to { opacity: 0.9; r: 11; }
}
.scene { transition: background 0.08s; }
</style>
</head>
<body>
<div class="scene" id="scene">
<canvas id="fire-canvas"></canvas>
<div class="flag-panel">
<div class="dragon-wrap">
<svg id="dragon-svg" viewBox="0 0 380 300" xmlns="http://www.w3.org/2000/svg" overflow="visible">
<defs>
<radialGradient id="body-grad" cx="50%" cy="35%" r="65%">
<stop offset="0%" stop-color="#ee001c"/>
<stop offset="100%" stop-color="#7a0000"/>
</radialGradient>
<radialGradient id="wing-grad" cx="25%" cy="25%" r="75%">
<stop offset="0%" stop-color="#cc001a" stop-opacity="0.92"/>
<stop offset="100%" stop-color="#4a0000" stop-opacity="0.72"/>
</radialGradient>
<radialGradient id="eye-glow-grad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffdd00" stop-opacity="1"/>
<stop offset="100%" stop-color="#ff4400" stop-opacity="0"/>
</radialGradient>
<filter id="blur-soft">
<feGaussianBlur stdDeviation="1.5"/>
</filter>
</defs>
<g class="tail-group">
<path d="M 255 188 Q 302 204 334 178 Q 358 158 348 132 Q 340 114 324 122 Q 310 132 320 150 Q 328 164 316 172 Q 296 184 272 177 Z"
fill="url(#body-grad)" stroke="#600000" stroke-width="1.5"/>
<polygon points="332,172 343,154 325,167" fill="#bb001a" stroke="#600000" stroke-width="1"/>
<polygon points="346,149 355,132 338,146" fill="#bb001a" stroke="#600000" stroke-width="1"/>
<polygon points="317,172 307,157 312,174" fill="#bb001a" stroke="#600000" stroke-width="1"/>
</g>
<g class="wing-upper">
<path d="M 192 118 Q 232 62 294 46 Q 314 40 312 62 Q 293 77 283 92 Q 313 72 328 88 Q 333 104 312 112 Q 333 98 341 115 Q 341 132 318 133 Q 293 135 268 143 Q 232 151 202 151 Z"
fill="url(#wing-grad)" stroke="#600000" stroke-width="1.5" stroke-linejoin="round"/>
<path d="M 202 151 Q 242 112 294 46" fill="none" stroke="#880010" stroke-width="0.8" opacity="0.5"/>
<path d="M 212 148 Q 258 118 312 112" fill="none" stroke="#880010" stroke-width="0.8" opacity="0.5"/>
<path d="M 222 145 Q 264 123 318 133" fill="none" stroke="#880010" stroke-width="0.8" opacity="0.4"/>
<line x1="192" y1="118" x2="294" y2="46" stroke="#600000" stroke-width="1.8"/>
<line x1="197" y1="123" x2="328" y2="88" stroke="#600000" stroke-width="1.8"/>
<line x1="202" y1="134" x2="341" y2="115" stroke="#600000" stroke-width="1.8"/>
<line x1="202" y1="144" x2="318" y2="133" stroke="#600000" stroke-width="1.8"/>
</g>
<path d="M 197 168 Q 244 194 268 214 Q 248 224 217 213 Q 196 203 192 182 Z"
fill="url(#wing-grad)" stroke="#600000" stroke-width="1" opacity="0.75"/>
<path d="M 238 198 Q 258 218 251 242 Q 247 254 241 256 L 238 256 Q 241 244 243 235 Q 239 220 230 212 Z"
fill="url(#body-grad)" stroke="#600000" stroke-width="1.5"/>
<path d="M 238 256 Q 232 265 228 262 Q 230 255 238 256" fill="#4a0000"/>
<path d="M 241 256 Q 240 266 236 265 Q 236 257 241 256" fill="#4a0000"/>
<path d="M 244 254 Q 246 264 242 264 Q 241 256 244 254" fill="#4a0000"/>
<path d="M 168 178 Q 147 204 140 232 Q 136 246 132 250 L 129 250 Q 132 238 134 230 Q 130 208 147 188 Z"
fill="url(#body-grad)" stroke="#600000" stroke-width="1.5"/>
<path d="M 130 250 Q 122 258 119 256 Q 122 249 130 250" fill="#4a0000"/>
<path d="M 132 250 Q 128 260 124 259 Q 125 251 132 250" fill="#4a0000"/>
<path d="M 135 248 Q 134 258 130 258 Q 130 250 135 248" fill="#4a0000"/>
<ellipse cx="202" cy="182" rx="74" ry="53" fill="url(#body-grad)" stroke="#600000" stroke-width="2"/>
<path d="M 157 167 Q 178 157 202 162 Q 226 157 247 167" fill="none" stroke="#990010" stroke-width="1" opacity="0.45"/>
<path d="M 150 182 Q 174 172 202 177 Q 230 172 254 182" fill="none" stroke="#990010" stroke-width="1" opacity="0.45"/>
<path d="M 154 197 Q 177 187 202 192 Q 227 187 250 197" fill="none" stroke="#990010" stroke-width="1" opacity="0.4"/>
<path d="M 158 152 Q 150 130 147 110 Q 145 92 152 82 Q 160 72 170 77 Q 182 82 180 102 Q 177 122 177 152 Z"
fill="url(#body-grad)" stroke="#600000" stroke-width="1.5"/>
<path d="M 155 142 Q 166 134 178 142" fill="none" stroke="#990010" stroke-width="1" opacity="0.45"/>
<path d="M 152 126 Q 164 118 177 126" fill="none" stroke="#990010" stroke-width="1" opacity="0.45"/>
<path d="M 152 110 Q 163 103 175 110" fill="none" stroke="#990010" stroke-width="1" opacity="0.4"/>
<polygon points="154,140 148,126 157,138" fill="#bb001a" stroke="#600000" stroke-width="1"/>
<polygon points="151,124 145,110 154,122" fill="#bb001a" stroke="#600000" stroke-width="1"/>
<polygon points="152,108 146,94 155,106" fill="#bb001a" stroke="#600000" stroke-width="1"/>
<path d="M 112 94 Q 120 70 140 62 Q 157 56 167 64 Q 180 74 177 90 Q 174 106 160 112 Q 142 118 124 112 Q 112 106 112 94 Z"
fill="url(#body-grad)" stroke="#600000" stroke-width="2"/>
<path d="M 112 94 Q 97 96 82 108 Q 74 118 78 128 Q 84 135 97 131 Q 110 124 114 112 Z"
fill="url(#body-grad)" stroke="#600000" stroke-width="1.5"/>
<path d="M 82 114 Q 97 108 114 110" fill="none" stroke="#600000" stroke-width="1.5"/>
<ellipse cx="85" cy="110" rx="3" ry="2" fill="#3a0000"/>
<ellipse cx="92" cy="108" rx="3" ry="2" fill="#3a0000"/>
<polygon points="157,60 150,38 162,56" fill="#bb001a" stroke="#600000" stroke-width="1.5"/>
<polygon points="147,58 138,38 150,54" fill="#bb001a" stroke="#600000" stroke-width="1.5"/>
<polygon points="167,66 162,45 170,64" fill="#bb001a" stroke="#600000" stroke-width="1.5"/>
<path d="M 162 68 Q 174 54 180 64 Q 174 70 162 68" fill="#990016" stroke="#600000" stroke-width="1"/>
<circle class="eye-glow" cx="150" cy="82" r="9" fill="url(#eye-glow-grad)" filter="url(#blur-soft)"/>
<ellipse cx="150" cy="82" rx="8" ry="7" fill="#110000"/>
<ellipse class="dragon-eye" cx="150" cy="82" rx="5.5" ry="5.5" fill="#ff5500"/>
<ellipse cx="150" cy="82" rx="2" ry="5" fill="#050000"/>
<ellipse cx="148" cy="79" rx="1.5" ry="1.5" fill="rgba(255,248,180,0.75)"/>
<polygon points="97,114 94,125 99,114" fill="#f0eada" stroke="#b89500" stroke-width="0.5"/>
<polygon points="101,112 99,122 104,112" fill="#f0eada" stroke="#b89500" stroke-width="0.5"/>
<polygon points="105,111 104,120 108,111" fill="#f0eada" stroke="#b89500" stroke-width="0.5"/>
<path d="M 162 178 Q 187 191 197 213 Q 182 218 167 208 Q 158 198 160 182 Z"
fill="#bb001a" stroke="#800000" stroke-width="1" opacity="0.55"/>
</svg>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('fire-canvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
function getMouthPos() {
const svg = document.getElementById('dragon-svg');
const rect = svg.getBoundingClientRect();
const sx = rect.width / 380;
const sy = rect.height / 300;
return {
x: rect.left + 80 * sx,
y: rect.top + 118 * sy
};
}
const POOL_SIZE = 420;
const particles = [];
function resetParticle(p) {
const m = getMouthPos();
const speed = 3.5 + Math.random() * 5.5;
const angle = Math.PI + (Math.random() - 0.5) * 0.55;
const jitter = (Math.random() - 0.5) * 1.2;
p.x = m.x + (Math.random() - 0.5) * 6;
p.y = m.y + (Math.random() - 0.5) * 8;
p.vx = Math.cos(angle) * speed;
p.vy = Math.sin(angle) * speed + jitter;
p.life = 0;
p.maxLife = 0.55 + Math.random() * 0.55;
p.size = 6 + Math.random() * 22;
p.type = Math.random() < 0.18 ? 'spark' : 'flame';
p.wobble = (Math.random() - 0.5) * 0.015;
p.wobbleSpeed = 0.08 + Math.random() * 0.12;
p.wobblePhase = Math.random() * Math.PI * 2;
}
for (let i = 0; i < POOL_SIZE; i++) {
const p = {};
resetParticle(p);
p.life = Math.random() * p.maxLife;
particles.push(p);
}
function flameColor(t, alpha) {
let r, g, b;
if (t < 0.15) {
r = 255; g = 255; b = Math.round(200 * (1 - t / 0.15));
} else if (t < 0.4) {
const s = (t - 0.15) / 0.25;
r = 255; g = Math.round(255 - s * 135); b = 0;
} else if (t < 0.7) {
const s = (t - 0.4) / 0.3;
r = 255; g = Math.round(120 - s * 120); b = 0;
} else {
const s = (t - 0.7) / 0.3;
r = Math.round(255 - s * 180); g = 0; b = 0;
}
return `rgba(${r},${g},${b},${alpha.toFixed(3)})`;
}
function sparkColor(t) {
if (t < 0.3) return `rgba(255,255,180,${(1-t*2).toFixed(3)})`;
if (t < 0.6) return `rgba(255,160,0,${(1-t).toFixed(3)})`;
return `rgba(180,20,0,${((1-t)*0.6).toFixed(3)})`;
}
let lastTime = 0;
let frameCount = 0;
function draw(ts) {
const dt = Math.min((ts - lastTime) / 1000, 0.05);
lastTime = ts;
frameCount++;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const m = getMouthPos();
const glow = ctx.createRadialGradient(m.x, m.y, 0, m.x, m.y, 120);
glow.addColorStop(0, 'rgba(255,140,0,0.18)');
glow.addColorStop(0.4, 'rgba(255,60,0,0.10)');
glow.addColorStop(1, 'rgba(255,0,0,0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.ellipse(m.x - 40, m.y, 160, 80, 0, 0, Math.PI * 2);
ctx.fill();
particles.sort((a, b) => (a.type === 'spark' ? 1 : 0) - (b.type === 'spark' ? 1 : 0));
for (const p of particles) {
p.life += dt;
if (p.life >= p.maxLife) { resetParticle(p); continue; }
const t = p.life / p.maxLife;
p.wobblePhase += p.wobbleSpeed;
const perp = Math.sin(p.wobblePhase) * p.wobble * canvas.height;
if (p.type === 'flame') {
p.x += p.vx * (1 - t * 0.5) * dt * 60;
p.y += (p.vy + perp) * (1 - t * 0.4) * dt * 60;
p.vy -= 0.06 * dt * 60;
p.vx *= 1 - 0.012 * dt * 60;
} else {
p.x += p.vx * dt * 60;
p.y += p.vy * dt * 60;
p.vy -= 0.12 * dt * 60;
p.vx *= 1 - 0.008 * dt * 60;
}
let alpha;
if (t < 0.08) alpha = t / 0.08;
else if (t < 0.65) alpha = 1;
else alpha = 1 - (t - 0.65) / 0.35;
alpha = Math.max(0, alpha);
const size = p.size * (1 + t * 0.6);
if (p.type === 'flame') {
const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, size);
grad.addColorStop(0, flameColor(t, alpha * 0.9));
grad.addColorStop(0.4, flameColor(t, alpha * 0.55));
grad.addColorStop(1, flameColor(t, 0));
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = grad;
ctx.beginPath();
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(Math.atan2(p.vy, p.vx));
ctx.scale(1.6, 1);
ctx.arc(0, 0, size, 0, Math.PI * 2);
ctx.restore();
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
} else {
ctx.globalCompositeOperation = 'lighter';
ctx.strokeStyle = sparkColor(t);
ctx.lineWidth = Math.max(0.8, 2 * (1 - t));
ctx.shadowColor = '#ffcc00';
ctx.shadowBlur = 6;
ctx.beginPath();
ctx.moveTo(p.x - p.vx * 3, p.y - p.vy * 3);
ctx.lineTo(p.x, p.y);
ctx.stroke();
ctx.shadowBlur = 0;
ctx.globalCompositeOperation = 'source-over';
}
}
if (frameCount % 4 === 0) {
const intensity = 0.12 + Math.random() * 0.14;
document.getElementById('scene').style.background =
`radial-gradient(ellipse at 50% 55%, rgba(${Math.round(40+intensity*80)},0,0,1) 0%, #0a0000 70%)`;
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
</script>
</body>
</html>