← back to Only CSS: Codevember #5 Lightsabercompare with ground truth →
model output
Claude Sonnet 4.6
Only CSS: Codevember #5 Lightsaber
A 0.77T 0.22
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>Lightsaber Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9
10 body {
11 background: #000;
12 width: 100vw;
13 height: 100vh;
14 overflow: hidden;
15 display: flex;
16 align-items: center;
17 justify-content: center;
18 }
19
20 .scene {
21 position: relative;
22 width: 1024px;
23 height: 768px;
24 }
25
26 .lightsaber {
27 position: absolute;
28 left: 50%;
29 top: 50%;
30 transform-origin: bottom center;
31 animation: saberSwing 8s ease-in-out infinite;
32 }
33
34 .hilt {
35 position: absolute;
36 bottom: 0;
37 left: 50%;
38 transform: translateX(-50%);
39 width: 5px;
40 height: 20px;
41 background: linear-gradient(to bottom, #ddd, #aaa, #888);
42 border-radius: 2px;
43 z-index: 2;
44 }
45
46 .blade {
47 position: absolute;
48 bottom: 18px;
49 left: 50%;
50 transform: translateX(-50%);
51 width: 3px;
52 border-radius: 2px 2px 0 0;
53 animation: bladeColor 8s ease-in-out infinite, bladeGlow 8s ease-in-out infinite, bladeLength 8s ease-in-out infinite;
54 }
55
56 .glow {
57 position: absolute;
58 bottom: 0;
59 left: 50%;
60 transform: translateX(-50%);
61 border-radius: 50%;
62 animation: glowColor 8s ease-in-out infinite, glowPulse 8s ease-in-out infinite;
63 pointer-events: none;
64 }
65
66 @keyframes saberSwing {
67 0% { transform: translate(-50px, -180px) rotate(-5deg); }
68 12% { transform: translate(-50px, -180px) rotate(130deg); }
69 25% { transform: translate(-50px, -180px) rotate(135deg); }
70 37% { transform: translate(-50px, -180px) rotate(-10deg); }
71 50% { transform: translate(-50px, -180px) rotate(-5deg); }
72 62% { transform: translate(-50px, -180px) rotate(130deg); }
73 75% { transform: translate(-50px, -180px) rotate(135deg); }
74 87% { transform: translate(-50px, -180px) rotate(-10deg); }
75 100% { transform: translate(-50px, -180px) rotate(-5deg); }
76 }
77
78 @keyframes bladeLength {
79 0% { height: 160px; }
80 5% { height: 10px; }
81 10% { height: 160px; }
82 50% { height: 160px; }
83 55% { height: 10px; }
84 60% { height: 160px; }
85 100% { height: 160px; }
86 }
87
88 @keyframes bladeColor {
89 0% { background: linear-gradient(to top, #4488ff, #88aaff, #ffffff); }
90 40% { background: linear-gradient(to top, #4488ff, #88aaff, #ffffff); }
91 45% { background: linear-gradient(to top, #00ffcc, #88ffee, #ffffff); }
92 48% { background: linear-gradient(to top, #00ff44, #88ff88, #ffffff); }
93 50% { background: linear-gradient(to top, #00ff44, #88ff88, #ffffff); }
94 90% { background: linear-gradient(to top, #00ff44, #88ff88, #ffffff); }
95 95% { background: linear-gradient(to top, #ff2200, #ff6644, #ffffff); }
96 100% { background: linear-gradient(to top, #ff2200, #ff6644, #ffffff); }
97 }
98
99 @keyframes bladeGlow {
100 0% { box-shadow: 0 0 8px 4px rgba(68,136,255,0.8), 0 0 20px 8px rgba(68,136,255,0.4); }
101 40% { box-shadow: 0 0 8px 4px rgba(68,136,255,0.8), 0 0 20px 8px rgba(68,136,255,0.4); }
102 45% { box-shadow: 0 0 8px 4px rgba(0,255,200,0.8), 0 0 20px 8px rgba(0,255,200,0.4); }
103 48% { box-shadow: 0 0 8px 4px rgba(0,255,68,0.8), 0 0 20px 8px rgba(0,255,68,0.4); }
104 50% { box-shadow: 0 0 8px 4px rgba(0,255,68,0.8), 0 0 20px 8px rgba(0,255,68,0.4); }
105 90% { box-shadow: 0 0 8px 4px rgba(0,255,68,0.8), 0 0 20px 8px rgba(0,255,68,0.4); }
106 95% { box-shadow: 0 0 8px 4px rgba(255,34,0,0.8), 0 0 20px 8px rgba(255,34,0,0.4); }
107 100% { box-shadow: 0 0 8px 4px rgba(255,34,0,0.8), 0 0 20px 8px rgba(255,34,0,0.4); }
108 }
109
110 @keyframes glowColor {
111 0% { background: radial-gradient(ellipse, rgba(68,136,255,0.3) 0%, transparent 70%); }
112 40% { background: radial-gradient(ellipse, rgba(68,136,255,0.3) 0%, transparent 70%); }
113 45% { background: radial-gradient(ellipse, rgba(0,255,200,0.3) 0%, transparent 70%); }
114 48% { background: radial-gradient(ellipse, rgba(0,255,68,0.3) 0%, transparent 70%); }
115 50% { background: radial-gradient(ellipse, rgba(0,255,68,0.3) 0%, transparent 70%); }
116 90% { background: radial-gradient(ellipse, rgba(0,255,68,0.3) 0%, transparent 70%); }
117 95% { background: radial-gradient(ellipse, rgba(255,34,0,0.3) 0%, transparent 70%); }
118 100% { background: radial-gradient(ellipse, rgba(255,34,0,0.3) 0%, transparent 70%); }
119 }
120
121 @keyframes glowPulse {
122 0% { width: 120px; height: 200px; bottom: -20px; }
123 5% { width: 40px; height: 60px; bottom: -10px; }
124 10% { width: 120px; height: 200px; bottom: -20px; }
125 50% { width: 120px; height: 200px; bottom: -20px; }
126 55% { width: 40px; height: 60px; bottom: -10px; }
127 60% { width: 120px; height: 200px; bottom: -20px; }
128 100% { width: 120px; height: 200px; bottom: -20px; }
129 }
130</style>
131</head>
132<body>
133<div class="scene">
134 <div class="lightsaber">
135 <div class="glow"></div>
136 <div class="blade"></div>
137 <div class="hilt"></div>
138 </div>
139</div>
140
141<script>
142 // Use JS to drive a more precise animation with canvas for better control
143 const scene = document.querySelector('.scene');
144 scene.innerHTML = '';
145
146 const canvas = document.createElement('canvas');
147 canvas.width = 1024;
148 canvas.height = 768;
149 canvas.style.width = '100%';
150 canvas.style.height = '100%';
151 scene.appendChild(canvas);
152
153 const ctx = canvas.getContext('2d');
154
155 // Animation state
156 let startTime = null;
157 const totalDuration = 8000; // 8 seconds per cycle
158
159 // Color phases: blue -> (shrink) -> green -> (shrink) -> red -> (shrink) -> blue...
160 // Based on frames: starts blue, swings, shrinks, green appears, swings, shrinks, red appears
161
162 function getColor(t) {
163 // t is 0-1 within cycle
164 if (t < 0.45) return { r: 68, g: 136, b: 255, name: 'blue' };
165 if (t < 0.50) {
166 // transition blue->green
167 const p = (t - 0.45) / 0.05;
168 return { r: Math.round(68*(1-p)), g: Math.round(136 + (255-136)*p), b: Math.round(255*(1-p)), name: 'trans' };
169 }
170 if (t < 0.90) return { r: 0, g: 255, b: 68, name: 'green' };
171 if (t < 0.95) {
172 // transition green->red
173 const p = (t - 0.90) / 0.05;
174 return { r: Math.round(255*p), g: Math.round(255*(1-p)), b: 0, name: 'trans' };
175 }
176 return { r: 255, g: 34, b: 0, name: 'red' };
177 }
178
179 function getAngle(t) {
180 // t is 0-1
181 // Swing pattern: starts near vertical (-5deg from vertical = pointing up slightly tilted)
182 // swings to ~130deg (pointing down-right), then back
183 // The pivot is at the hilt (bottom of blade)
184 // 0 = pointing straight up, positive = clockwise
185
186 // Phase 1 (blue): 0 -> 0.12: swing from -5 to 130 deg (fast swing down)
187 // Phase 1: 0.12 -> 0.25: hold at ~135
188 // Phase 1: 0.25 -> 0.37: swing back to -10
189 // Phase 1: 0.37 -> 0.45: hold near vertical
190 // Shrink at 0.45-0.50
191 // Phase 2 (green): similar pattern
192 // Phase 2: 0.50 -> 0.62: swing from -5 to 130
193 // Phase 2: 0.62 -> 0.75: hold
194 // Phase 2: 0.75 -> 0.87: swing back
195 // Phase 2: 0.87 -> 0.90: hold
196 // Shrink at 0.90-0.95
197 // Phase 3 (red): 0.95 -> 1.0: appear vertical
198
199 function easeInOut(x) {
200 return x < 0.5 ? 2*x*x : 1 - Math.pow(-2*x+2,2)/2;
201 }
202
203 if (t < 0.12) {
204 return -5 + easeInOut(t/0.12) * 135;
205 } else if (t < 0.25) {
206 return 130;
207 } else if (t < 0.37) {
208 return 130 - easeInOut((t-0.25)/0.12) * 140;
209 } else if (t < 0.45) {
210 return -10;
211 } else if (t < 0.50) {
212 return -10; // shrinking
213 } else if (t < 0.62) {
214 return -5 + easeInOut((t-0.50)/0.12) * 135;
215 } else if (t < 0.75) {
216 return 130;
217 } else if (t < 0.87) {
218 return 130 - easeInOut((t-0.75)/0.12) * 140;
219 } else if (t < 0.90) {
220 return -10;
221 } else if (t < 0.95) {
222 return -10; // shrinking
223 } else {
224 // red appears, swing up
225 return -5 + easeInOut((t-0.95)/0.05) * (-5);
226 }
227 }
228
229 function getBladeLength(t) {
230 // Shrink at transitions
231 const maxLen = 160;
232 const minLen = 8;
233
234 function shrinkCurve(p) {
235 // p: 0=full, 0.5=min, 1=full
236 if (p < 0.5) return maxLen - (maxLen - minLen) * (p/0.5);
237 return minLen + (maxLen - minLen) * ((p-0.5)/0.5);
238 }
239
240 if (t >= 0.44 && t < 0.52) {
241 return shrinkCurve((t - 0.44) / 0.08);
242 }
243 if (t >= 0.89 && t < 0.97) {
244 return shrinkCurve((t - 0.89) / 0.08);
245 }
246 return maxLen;
247 }
248
249 function draw(timestamp) {
250 if (!startTime) startTime = timestamp;
251 const elapsed = (timestamp - startTime) % totalDuration;
252 const t = elapsed / totalDuration;
253
254 ctx.clearRect(0, 0, 1024, 768);
255 ctx.fillStyle = '#000';
256 ctx.fillRect(0, 0, 1024, 768);
257
258 const color = getColor(t);
259 const angleDeg = getAngle(t);
260 const bladeLen = getBladeLength(t);
261
262 // Hilt position (center of scene, slightly right and up from center)
263 const hiltX = 512;
264 const hiltY = 390;
265
266 const angleRad = (angleDeg - 90) * Math.PI / 180; // -90 so 0deg = pointing up
267
268 // Blade tip
269 const tipX = hiltX + Math.cos(angleRad) * bladeLen;
270 const tipY = hiltY + Math.sin(angleRad) * bladeLen;
271
272 // Draw ambient glow
273 const glowRadius = 80;
274 const midX = hiltX + Math.cos(angleRad) * bladeLen * 0.5;
275 const midY = hiltY + Math.sin(angleRad) * bladeLen * 0.5;
276
277 const grad = ctx.createRadialGradient(midX, midY, 0, midX, midY, glowRadius);
278 grad.addColorStop(0, `rgba(${color.r},${color.g},${color.b},0.15)`);
279 grad.addColorStop(1, 'rgba(0,0,0,0)');
280 ctx.fillStyle = grad;
281 ctx.beginPath();
282 ctx.ellipse(midX, midY, glowRadius, glowRadius * 1.2, angleRad, 0, Math.PI * 2);
283 ctx.fill();
284
285 // Draw blade glow (wide)
286 ctx.save();
287 ctx.lineCap = 'round';
288
289 // Outer glow
290 ctx.beginPath();
291 ctx.moveTo(hiltX, hiltY);
292 ctx.lineTo(tipX, tipY);
293 ctx.strokeStyle = `rgba(${color.r},${color.g},${color.b},0.15)`;
294 ctx.lineWidth = 20;
295 ctx.stroke();
296
297 // Mid glow
298 ctx.beginPath();
299 ctx.moveTo(hiltX, hiltY);
300 ctx.lineTo(tipX, tipY);
301 ctx.strokeStyle = `rgba(${color.r},${color.g},${color.b},0.4)`;
302 ctx.lineWidth = 8;
303 ctx.stroke();
304
305 // Core blade
306 const bladeGrad = ctx.createLinearGradient(hiltX, hiltY, tipX, tipY);
307 bladeGrad.addColorStop(0, `rgba(${color.r},${color.g},${color.b},1)`);
308 bladeGrad.addColorStop(0.7, `rgba(${color.r},${color.g},${color.b},1)`);
309 bladeGrad.addColorStop(1, `rgba(${Math.min(255,color.r+100)},${Math.min(255,color.g+100)},${Math.min(255,color.b+100)},0.8)`);
310
311 ctx.beginPath();
312 ctx.moveTo(hiltX, hiltY);
313 ctx.lineTo(tipX, tipY);
314 ctx.strokeStyle = bladeGrad;
315 ctx.lineWidth = 3;
316 ctx.stroke();
317
318 // White core
319 ctx.beginPath();
320 ctx.moveTo(hiltX, hiltY);
321 ctx.lineTo(tipX, tipY);
322 ctx.strokeStyle = 'rgba(255,255,255,0.9)';
323 ctx.lineWidth = 1.5;
324 ctx.stroke();
325
326 ctx.restore();
327
328 // Draw hilt
329 const hiltLen = 20;
330 const hiltEndX = hiltX - Math.cos(angleRad) * hiltLen;
331 const hiltEndY = hiltY - Math.sin(angleRad) * hiltLen;
332
333 ctx.save();
334 ctx.lineCap = 'round';
335 ctx.beginPath();
336 ctx.moveTo(hiltX, hiltY);
337 ctx.lineTo(hiltEndX, hiltEndY);
338 ctx.strokeStyle = '#cccccc';
339 ctx.lineWidth = 5;
340 ctx.stroke();
341
342 ctx.beginPath();
343 ctx.moveTo(hiltX, hiltY);
344 ctx.lineTo(hiltEndX, hiltEndY);
345 ctx.strokeStyle = '#888888';
346 ctx.lineWidth = 3;
347 ctx.stroke();
348 ctx.restore();
349
350 requestAnimationFrame(draw);
351 }
352
353 requestAnimationFrame(draw);
354</script>
355</body>
356</html>