← back to Merry Christmas Tree!compare with ground truth →
model output
GPT-5.4
Merry Christmas Tree!
A 0.79T 0.30
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" />
6<title>Christmas Tree Line Animation</title>
7<style>
8 :root{
9 --bg:#171726;
10 --gold:#d8a06f;
11 --gold2:#e0b07f;
12 --white:#f5f7ff;
13 --blue:#0b6dff;
14 --text:#f2f0f4;
15 }
16 html,body{
17 margin:0;
18 height:100%;
19 background:var(--bg);
20 overflow:hidden;
21 font-family: Georgia, serif;
22 }
23 svg{
24 width:100vw;
25 height:100vh;
26 display:block;
27 background:var(--bg);
28 }
29
30 .gold-line{
31 fill:none;
32 stroke:url(#goldGrad);
33 stroke-width:7;
34 stroke-linecap:round;
35 stroke-linejoin:round;
36 filter:url(#softGlow);
37 }
38
39 .thin-tail{
40 fill:none;
41 stroke:url(#goldGrad);
42 stroke-width:2.2;
43 stroke-linecap:round;
44 opacity:.95;
45 }
46
47 .star-top{
48 fill:var(--gold);
49 opacity:0;
50 transform-box:fill-box;
51 transform-origin:center;
52 animation:topStarIn .45s ease-out forwards;
53 animation-delay:4.55s;
54 }
55
56 .star-outline{
57 fill:none;
58 stroke:#d9d8e8;
59 stroke-width:3;
60 opacity:0;
61 transform-box:fill-box;
62 transform-origin:center;
63 animation:starOutline .55s ease-out forwards;
64 animation-delay:7.2s;
65 }
66
67 .caption{
68 fill:var(--text);
69 font-size:34px;
70 letter-spacing:.5px;
71 opacity:0;
72 filter:url(#textGlow);
73 animation:captionIn .8s ease-out forwards;
74 animation-delay:6.2s;
75 }
76
77 .spark{
78 opacity:0;
79 animation:twinkle 1.2s ease-in-out infinite;
80 }
81
82 .trail-star{
83 filter:url(#blueGlow);
84 }
85
86 .draw{
87 stroke-dasharray:var(--len);
88 stroke-dashoffset:var(--len);
89 animation:draw var(--dur) linear forwards;
90 animation-delay:var(--delay);
91 }
92
93 #orb{
94 filter:url(#blueGlow);
95 }
96
97 @keyframes draw{
98 to{ stroke-dashoffset:0; }
99 }
100 @keyframes twinkle{
101 0%,100%{ opacity:.15; transform:scale(.7); }
102 50%{ opacity:.95; transform:scale(1.15); }
103 }
104 @keyframes captionIn{
105 from{ opacity:0; transform:translateY(10px); }
106 to{ opacity:1; transform:translateY(0); }
107 }
108 @keyframes topStarIn{
109 0%{ opacity:0; transform:scale(.2) rotate(-20deg); }
110 70%{ opacity:1; transform:scale(1.08) rotate(6deg); }
111 100%{ opacity:1; transform:scale(1) rotate(0); }
112 }
113 @keyframes starOutline{
114 0%{ opacity:0; transform:scale(.7); }
115 100%{ opacity:1; transform:scale(1); }
116 }
117</style>
118</head>
119<body>
120<svg viewBox="0 0 1024 768" aria-label="Animated Christmas tree drawing">
121 <defs>
122 <linearGradient id="goldGrad" x1="0" y1="0" x2="1" y2="0">
123 <stop offset="0%" stop-color="#c98f61"/>
124 <stop offset="50%" stop-color="#e0b07f"/>
125 <stop offset="100%" stop-color="#c98f61"/>
126 </linearGradient>
127
128 <filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
129 <feGaussianBlur stdDeviation="0.35" result="b"/>
130 <feMerge>
131 <feMergeNode in="b"/>
132 <feMergeNode in="SourceGraphic"/>
133 </feMerge>
134 </filter>
135
136 <filter id="blueGlow" x="-300%" y="-300%" width="700%" height="700%">
137 <feGaussianBlur stdDeviation="12" result="blur1"/>
138 <feColorMatrix in="blur1" type="matrix"
139 values="0 0 0 0 0.02
140 0 0 0 0 0.42
141 0 0 0 0 1
142 0 0 0 0.75 0" result="blue"/>
143 <feGaussianBlur in="SourceGraphic" stdDeviation="1.2" result="core"/>
144 <feMerge>
145 <feMergeNode in="blue"/>
146 <feMergeNode in="core"/>
147 </feMerge>
148 </filter>
149
150 <filter id="textGlow" x="-20%" y="-20%" width="140%" height="140%">
151 <feGaussianBlur stdDeviation=".4"/>
152 </filter>
153
154 <path id="p1" d="M334 552
155 C338 540, 350 528, 372 516
156 C410 495, 446 478, 434 458
157 C422 438, 372 444, 367 427
158 C362 410, 410 392, 448 370
159 C486 348, 486 332, 458 324
160 C430 316, 406 318, 414 306
161 C438 274, 462 244, 486 196
162 C500 168, 508 146, 521 132" />
163
164 <path id="p2" d="M521 132
165 C534 146, 542 168, 556 196
166 C580 244, 604 274, 628 306
167 C636 318, 612 316, 584 324
168 C556 332, 556 348, 594 370
169 C632 392, 680 410, 675 427
170 C670 444, 620 438, 608 458
171 C596 478, 632 495, 670 516
172 C692 528, 704 540, 708 552" />
173
174 <path id="p3" d="M292 594
175 C360 566, 446 592, 520 604
176 C594 616, 676 620, 732 596
177 C744 591, 752 584, 756 576" />
178
179 <path id="trunk" d="M482 618
180 C480 640, 486 652, 520 652
181 C554 652, 560 640, 558 618" />
182
183 <path id="tailL" d="M334 552 C326 544, 326 536, 340 526" />
184 <path id="tailR" d="M708 552 C718 544, 718 536, 704 526" />
185 </defs>
186
187 <!-- static twinkles -->
188 <g id="ambient"></g>
189
190 <!-- drawn tree -->
191 <use href="#p1" class="gold-line draw" style="--len:560;--dur:1.9s;--delay:.45s"/>
192 <use href="#p2" class="gold-line draw" style="--len:560;--dur:1.9s;--delay:2.35s"/>
193 <use href="#p3" class="gold-line draw" style="--len:500;--dur:1.45s;--delay:5.05s"/>
194 <use href="#trunk" class="gold-line draw" style="--len:120;--dur:.45s;--delay:4.95s"/>
195 <use href="#tailL" class="thin-tail draw" style="--len:40;--dur:.25s;--delay:1.95s"/>
196 <use href="#tailR" class="thin-tail draw" style="--len:40;--dur:.25s;--delay:4.15s"/>
197
198 <!-- top star -->
199 <polygon class="star-top" points="521,78 530,101 555,103 536,118 542,143 521,130 500,143 506,118 487,103 512,101"/>
200 <polygon class="star-outline" points="521,74 531,99 558,101 538,118 544,147 521,133 498,147 504,118 484,101 511,99"/>
201
202 <!-- moving orb and spark trail -->
203 <g id="orbWrap">
204 <g id="orb">
205 <circle r="4.2" fill="#fff"/>
206 <circle r="2.2" fill="#fff"/>
207 </g>
208 <g id="trailStars"></g>
209 </g>
210
211 <!-- caption -->
212 <text class="caption" x="512" y="708" text-anchor="middle"
213 style="font-family:'Papyrus','Bradley Hand','Comic Sans MS',cursive;">
214 This is available to buy at my shop
215 </text>
216</svg>
217
218<script>
219(() => {
220 const svg = document.querySelector('svg');
221 const orb = document.getElementById('orbWrap');
222 const trailStars = document.getElementById('trailStars');
223 const ambient = document.getElementById('ambient');
224
225 const NS = "http://www.w3.org/2000/svg";
226
227 const segments = [
228 { id:'p1', start:450, dur:1900 },
229 { id:'p2', start:2350, dur:1900 },
230 { id:'p3', start:5050, dur:1450 }
231 ];
232
233 const total = 8200;
234
235 const colors = ['#ffffff','#dfe8ff','#b8f7ff','#7fdc8a','#b10f3a','#d94b5f','#7a7f92'];
236 const rand = (a,b)=>a+Math.random()*(b-a);
237
238 function starPoints(r1, r2, n=5){
239 let pts = [];
240 for(let i=0;i<n*2;i++){
241 const a = -Math.PI/2 + i*Math.PI/n;
242 const r = i%2===0 ? r1 : r2;
243 pts.push((Math.cos(a)*r).toFixed(2)+','+(Math.sin(a)*r).toFixed(2));
244 }
245 return pts.join(' ');
246 }
247
248 function addAmbient(){
249 const pts = [
250 [332,672],[352,648],[398,596],[438,506],[498,438],[510,370],[548,420],[590,468],[648,548],[682,612],
251 [470,610],[610,610],[430,690],[690,690],[360,560],[660,560],[492,300],[560,340],[450,350],[520,520]
252 ];
253 pts.forEach((p,i)=>{
254 const g = document.createElementNS(NS,'g');
255 g.setAttribute('class','spark');
256 g.style.animationDelay = `${rand(0,1.2).toFixed(2)}s`;
257 g.setAttribute('transform',`translate(${p[0]} ${p[1]}) scale(${rand(.5,1.1).toFixed(2)})`);
258 const shape = Math.random()>.45 ? 'polygon' : 'path';
259 if(shape==='polygon'){
260 const poly = document.createElementNS(NS,'polygon');
261 poly.setAttribute('points', starPoints(rand(2.2,4.8), rand(.9,2.1)));
262 poly.setAttribute('fill', colors[(Math.random()*colors.length)|0]);
263 poly.setAttribute('opacity', rand(.35,.9).toFixed(2));
264 g.appendChild(poly);
265 }else{
266 const path = document.createElementNS(NS,'path');
267 path.setAttribute('d','M-3 0H3M0 -3V3');
268 path.setAttribute('stroke', colors[(Math.random()*2)|0]);
269 path.setAttribute('stroke-width','1.2');
270 path.setAttribute('stroke-linecap','round');
271 path.setAttribute('opacity', rand(.35,.9).toFixed(2));
272 g.appendChild(path);
273 }
274 ambient.appendChild(g);
275 });
276 }
277
278 addAmbient();
279
280 const pathEls = segments.map(s => document.getElementById(s.id));
281 const lengths = pathEls.map(p => p.getTotalLength());
282
283 let trail = [];
284 let startTime = null;
285
286 function pointAt(globalMs){
287 for(let i=0;i<segments.length;i++){
288 const s = segments[i];
289 if(globalMs >= s.start && globalMs <= s.start + s.dur){
290 const t = (globalMs - s.start) / s.dur;
291 const eased = t;
292 const len = lengths[i] * eased;
293 return pathEls[i].getPointAtLength(len);
294 }
295 }
296 if(globalMs < segments[0].start){
297 return pathEls[0].getPointAtLength(0);
298 }
299 if(globalMs > segments[segments.length-1].start + segments[segments.length-1].dur){
300 return pathEls[pathEls.length-1].getPointAtLength(lengths[lengths.length-1]);
301 }
302 return null;
303 }
304
305 function spawnTrail(x,y){
306 if(Math.random() > 0.55) return;
307 const g = document.createElementNS(NS,'g');
308 g.setAttribute('class','trail-star');
309 g.setAttribute('transform',`translate(${x} ${y}) scale(${rand(.45,1.15).toFixed(2)}) rotate(${rand(0,180).toFixed(1)})`);
310 g.style.opacity = rand(.45,.95).toFixed(2);
311
312 if(Math.random() > .35){
313 const poly = document.createElementNS(NS,'polygon');
314 poly.setAttribute('points', starPoints(rand(5,10), rand(2.2,4.2)));
315 poly.setAttribute('fill', colors[(Math.random()*colors.length)|0]);
316 g.appendChild(poly);
317 }else{
318 const path = document.createElementNS(NS,'path');
319 path.setAttribute('d','M-5 0H5M0 -5V5');
320 path.setAttribute('stroke', colors[(Math.random()*2)|0]);
321 path.setAttribute('stroke-width','1.6');
322 path.setAttribute('stroke-linecap','round');
323 g.appendChild(path);
324 }
325
326 trailStars.appendChild(g);
327 trail.push({el:g, born:performance.now()});
328 }
329
330 function animate(now){
331 if(!startTime) startTime = now;
332 const t = now - startTime;
333
334 const p = pointAt(t);
335 if(p){
336 orb.setAttribute('transform', `translate(${p.x} ${p.y})`);
337 spawnTrail(p.x + rand(-10,10), p.y + rand(-10,10));
338 } else {
339 orb.style.opacity = 0;
340 }
341
342 trail = trail.filter(item=>{
343 const age = now - item.born;
344 const life = 1200;
345 if(age > life){
346 item.el.remove();
347 return false;
348 }
349 const k = age / life;
350 item.el.style.opacity = (1-k)*0.9;
351 item.el.setAttribute('transform',
352 item.el.getAttribute('transform').replace(/scale\([^)]+\)/, `scale(${(1 + k*0.35).toFixed(2)})`)
353 );
354 return true;
355 });
356
357 if(t < total) requestAnimationFrame(animate);
358 else orb.style.opacity = 0;
359 }
360
361 requestAnimationFrame(animate);
362})();
363</script>
364</body>
365</html>