← back to Only CSS: Peacockcompare with ground truth →
model output
Gemini 3 Flash Preview
Only CSS: Peacock
A 0.56T 0.24
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Spiraling Sawblade Animation</title>
7 <style>
8 body {
9 margin: 0;
10 padding: 0;
11 background-color: #000;
12 overflow: hidden;
13 display: flex;
14 justify-content: center;
15 align-items: center;
16 height: 100vh;
17 }
18 canvas {
19 display: block;
20 }
21 </style>
22</head>
23<body>
24 <canvas id="canvas"></canvas>
25
26 <script>
27 const canvas = document.getElementById('canvas');
28 const ctx = canvas.getContext('2d');
29
30 let width, height, cx, cy;
31 let startTime = null;
32 const duration = 4000; // Total animation loop duration in ms
33 const spiralStart = 1500; // When the spiral starts expanding
34 const totalSegments = 160; // Number of overlapping shapes in the trail
35
36 function resize() {
37 width = canvas.width = window.innerWidth;
38 height = canvas.height = window.innerHeight;
39 cx = width / 2;
40 cy = height / 2;
41 }
42
43 window.addEventListener('resize', resize);
44 resize();
45
46 /**
47 * Draws the specific "sawblade" quadrant shape seen in the video.
48 * The shape consists of 4 quadrants, each with serrated edges.
49 */
50 function drawSawblade(x, y, radius, color, rotation) {
51 ctx.save();
52 ctx.translate(x, y);
53 ctx.rotate(rotation);
54 ctx.fillStyle = color;
55
56 for (let q = 0; q < 4; q++) {
57 ctx.save();
58 ctx.rotate(q * Math.PI / 2);
59 ctx.beginPath();
60 ctx.moveTo(0, 0);
61
62 // Draw serrated quadrant edge
63 const teeth = 8;
64 const angleStep = (Math.PI / 2) / teeth;
65 for (let i = 0; i <= teeth; i++) {
66 const angle = i * angleStep;
67 // Alternating radius creates the "shredded" look
68 const r = (i % 2 === 0) ? radius : radius * 0.4;
69 ctx.lineTo(Math.cos(angle) * r, Math.sin(angle) * r);
70 }
71
72 ctx.closePath();
73 ctx.fill();
74 ctx.restore();
75 }
76 ctx.restore();
77 }
78
79 function animate(timestamp) {
80 if (!startTime) startTime = timestamp;
81 let elapsed = (timestamp - startTime) % duration;
82
83 ctx.clearRect(0, 0, width, height);
84
85 // Overall rotation speed (constant throughout)
86 const globalRotation = (elapsed / 1000) * Math.PI * 1.5;
87
88 if (elapsed < spiralStart) {
89 // Phase 1: Rotating in center (0s - 1.5s)
90 drawSawblade(cx, cy, 35, '#ff0000', globalRotation);
91 } else {
92 // Phase 2: Spiraling out (1.5s - 4.0s)
93 const spiralProgress = (elapsed - spiralStart) / (duration - spiralStart);
94
95 // We draw the trail up to the current progress point
96 const visibleCount = Math.floor(spiralProgress * totalSegments);
97
98 for (let i = 0; i <= visibleCount; i++) {
99 const p = i / totalSegments;
100
101 // Spiral parameters: radius grows and angle rotates
102 // Math.pow(p, 1.2) adds a slight acceleration to the expansion
103 const radius = Math.pow(p, 1.2) * (Math.min(width, height) * 0.4);
104 const spiralAngle = p * Math.PI * 2.8; // Approx 1.4 turns
105
106 // Starting direction (pointing upwards at 1.5s mark)
107 const startOffset = -Math.PI / 2;
108
109 const x = cx + radius * Math.cos(spiralAngle + startOffset + globalRotation);
110 const y = cy + radius * Math.sin(spiralAngle + startOffset + globalRotation);
111
112 // Color transition: Red -> Magenta -> Purple -> Blue
113 // Red is hue 0, Blue is hue 240-270
114 const hue = p * 270;
115 const color = `hsl(${hue}, 100%, 50%)`;
116
117 // Subtle fade-in for each segment as it appears
118 ctx.globalAlpha = i === visibleCount ? (spiralProgress * totalSegments % 1) : 1;
119
120 // The individual shape rotates with the system
121 drawSawblade(x, y, 35, color, globalRotation + spiralAngle);
122 }
123 ctx.globalAlpha = 1;
124 }
125
126 requestAnimationFrame(animate);
127 }
128
129 requestAnimationFrame(animate);
130 </script>
131</body>
132</html>