diff --git a/gen/balloon/float.js b/gen/bw/balloon/float.js similarity index 100% rename from gen/balloon/float.js rename to gen/bw/balloon/float.js diff --git a/gen/bear/wave.js b/gen/bw/bear/wave.js similarity index 100% rename from gen/bear/wave.js rename to gen/bw/bear/wave.js diff --git a/gen/bee/buzz.js b/gen/bw/bee/buzz.js similarity index 100% rename from gen/bee/buzz.js rename to gen/bw/bee/buzz.js diff --git a/gen/bell/ring.js b/gen/bw/bell/ring.js similarity index 100% rename from gen/bell/ring.js rename to gen/bw/bell/ring.js diff --git a/gen/blob/jiggle.js b/gen/bw/blob/jiggle.js similarity index 100% rename from gen/blob/jiggle.js rename to gen/bw/blob/jiggle.js diff --git a/gen/bulb/idea.js b/gen/bw/bulb/idea.js similarity index 100% rename from gen/bulb/idea.js rename to gen/bw/bulb/idea.js diff --git a/gen/bunny/ears.js b/gen/bw/bunny/ears.js similarity index 100% rename from gen/bunny/ears.js rename to gen/bw/bunny/ears.js diff --git a/gen/cake/candle.js b/gen/bw/cake/candle.js similarity index 100% rename from gen/cake/candle.js rename to gen/bw/cake/candle.js diff --git a/gen/check/done.js b/gen/bw/check/done.js similarity index 100% rename from gen/check/done.js rename to gen/bw/check/done.js diff --git a/gen/chick/hatch.js b/gen/bw/chick/hatch.js similarity index 100% rename from gen/chick/hatch.js rename to gen/bw/chick/hatch.js diff --git a/gen/clock/tick.js b/gen/bw/clock/tick.js similarity index 100% rename from gen/clock/tick.js rename to gen/bw/clock/tick.js diff --git a/gen/cloud/rain.js b/gen/bw/cloud/rain.js similarity index 100% rename from gen/cloud/rain.js rename to gen/bw/cloud/rain.js diff --git a/gen/coffee/steam.js b/gen/bw/coffee/steam.js similarity index 100% rename from gen/coffee/steam.js rename to gen/bw/coffee/steam.js diff --git a/gen/dog/wag.js b/gen/bw/dog/wag.js similarity index 100% rename from gen/dog/wag.js rename to gen/bw/dog/wag.js diff --git a/gen/face/laugh.js b/gen/bw/face/laugh.js similarity index 100% rename from gen/face/laugh.js rename to gen/bw/face/laugh.js diff --git a/gen/fire/burn.js b/gen/bw/fire/burn.js similarity index 100% rename from gen/fire/burn.js rename to gen/bw/fire/burn.js diff --git a/gen/fish/swim.js b/gen/bw/fish/swim.js similarity index 100% rename from gen/fish/swim.js rename to gen/bw/fish/swim.js diff --git a/gen/flower/bloom.js b/gen/bw/flower/bloom.js similarity index 100% rename from gen/flower/bloom.js rename to gen/bw/flower/bloom.js diff --git a/gen/frog/hop.js b/gen/bw/frog/hop.js similarity index 100% rename from gen/frog/hop.js rename to gen/bw/frog/hop.js diff --git a/gen/ghost/float.js b/gen/bw/ghost/float.js similarity index 100% rename from gen/ghost/float.js rename to gen/bw/ghost/float.js diff --git a/gen/gift/open.js b/gen/bw/gift/open.js similarity index 100% rename from gen/gift/open.js rename to gen/bw/gift/open.js diff --git a/gen/hands/clap.js b/gen/bw/hands/clap.js similarity index 100% rename from gen/hands/clap.js rename to gen/bw/hands/clap.js diff --git a/gen/heart/beat.js b/gen/bw/heart/beat.js similarity index 100% rename from gen/heart/beat.js rename to gen/bw/heart/beat.js diff --git a/gen/hourglass/flip.js b/gen/bw/hourglass/flip.js similarity index 100% rename from gen/hourglass/flip.js rename to gen/bw/hourglass/flip.js diff --git a/gen/icecream/melt.js b/gen/bw/icecream/melt.js similarity index 100% rename from gen/icecream/melt.js rename to gen/bw/icecream/melt.js diff --git a/gen/leaf/fall.js b/gen/bw/leaf/fall.js similarity index 100% rename from gen/leaf/fall.js rename to gen/bw/leaf/fall.js diff --git a/gen/lightning/flash.js b/gen/bw/lightning/flash.js similarity index 100% rename from gen/lightning/flash.js rename to gen/bw/lightning/flash.js diff --git a/gen/lol/bounce.js b/gen/bw/lol/bounce.js similarity index 100% rename from gen/lol/bounce.js rename to gen/bw/lol/bounce.js diff --git a/gen/mail/open.js b/gen/bw/mail/open.js similarity index 100% rename from gen/mail/open.js rename to gen/bw/mail/open.js diff --git a/gen/moon/sleep.js b/gen/bw/moon/sleep.js similarity index 100% rename from gen/moon/sleep.js rename to gen/bw/moon/sleep.js diff --git a/gen/note/bounce.js b/gen/bw/note/bounce.js similarity index 100% rename from gen/note/bounce.js rename to gen/bw/note/bounce.js diff --git a/gen/octopus/wiggle.js b/gen/bw/octopus/wiggle.js similarity index 100% rename from gen/octopus/wiggle.js rename to gen/bw/octopus/wiggle.js diff --git a/gen/penguin/waddle.js b/gen/bw/penguin/waddle.js similarity index 100% rename from gen/penguin/waddle.js rename to gen/bw/penguin/waddle.js diff --git a/gen/popcorn/pop.js b/gen/bw/popcorn/pop.js similarity index 100% rename from gen/popcorn/pop.js rename to gen/bw/popcorn/pop.js diff --git a/gen/robot/dance.js b/gen/bw/robot/dance.js similarity index 100% rename from gen/robot/dance.js rename to gen/bw/robot/dance.js diff --git a/gen/rocket/launch.js b/gen/bw/rocket/launch.js similarity index 100% rename from gen/rocket/launch.js rename to gen/bw/rocket/launch.js diff --git a/gen/snail/crawl.js b/gen/bw/snail/crawl.js similarity index 100% rename from gen/snail/crawl.js rename to gen/bw/snail/crawl.js diff --git a/gen/snow/fall.js b/gen/bw/snow/fall.js similarity index 100% rename from gen/snow/fall.js rename to gen/bw/snow/fall.js diff --git a/gen/sparkle/shine.js b/gen/bw/sparkle/shine.js similarity index 100% rename from gen/sparkle/shine.js rename to gen/bw/sparkle/shine.js diff --git a/gen/spinner/load.js b/gen/bw/spinner/load.js similarity index 100% rename from gen/spinner/load.js rename to gen/bw/spinner/load.js diff --git a/gen/sprout/grow.js b/gen/bw/sprout/grow.js similarity index 100% rename from gen/sprout/grow.js rename to gen/bw/sprout/grow.js diff --git a/gen/sun/shine.js b/gen/bw/sun/shine.js similarity index 100% rename from gen/sun/shine.js rename to gen/bw/sun/shine.js diff --git a/gen/thumbs/up.js b/gen/bw/thumbs/up.js similarity index 100% rename from gen/thumbs/up.js rename to gen/bw/thumbs/up.js diff --git a/gen/ufo/beam.js b/gen/bw/ufo/beam.js similarity index 100% rename from gen/ufo/beam.js rename to gen/bw/ufo/beam.js diff --git a/gen/color/butterfly/flutter.js b/gen/color/butterfly/flutter.js new file mode 100644 index 0000000..827f561 --- /dev/null +++ b/gen/color/butterfly/flutter.js @@ -0,0 +1,24 @@ +// butterfly fluttering in a little loop, purple and pink wings with yellow spots +const{sin,cos,PI,abs}=Math,T=2*PI +export const frames=20,delay=60 +const UP='#b197fc',LO='#faa2c1',SP='#ffd43b',BD='#7048e8' +export default(c,t)=>{ + const x=128+20*sin(t*T),y=128+12*sin(2*T*t),r=.12*cos(t*T),s=.22+.78*abs(cos(3*PI*t)) + const W=(d,f,col)=>{c.save();c.translate(x,y);c.rotate(r);c.scale(d*s,1);c.beginPath();f();c.restore();c.fillStyle=col;c.fill();c.stroke()} + for(const d of[-1,1]){ + c.lineWidth=7 + W(d,()=>{c.moveTo(0,4);c.bezierCurveTo(50,10,80,44,58,74);c.bezierCurveTo(40,92,6,62,0,12)},LO) + W(d,()=>{c.moveTo(0,-6);c.bezierCurveTo(30,-72,100,-62,86,-14);c.bezierCurveTo(80,6,30,6,0,0)},UP) + c.lineWidth=4 + W(d,()=>c.ellipse(58,-30,13,13,0,0,T),SP) + W(d,()=>c.ellipse(34,-14,6,6,0,0,T),'#fff') + W(d,()=>c.ellipse(40,46,9,9,0,0,T),SP) + } + c.save();c.translate(x,y);c.rotate(r) + for(const d of[-1,1])for(const[w,st]of[[8,'#111'],[3,'#fff']])c.lineWidth=w,c.strokeStyle=st,c.beginPath(),c.moveTo(d*4,-36),c.quadraticCurveTo(d*10,-62,d*22,-66),c.stroke() + c.lineWidth=4;c.strokeStyle='#111';c.fillStyle='#fff';for(const d of[-1,1])c.beginPath(),c.arc(d*22,-66,4.5,0,T),c.fill(),c.stroke() + c.lineWidth=6;c.fillStyle=BD;c.beginPath();c.ellipse(0,8,8,36,0,0,T);c.fill();c.stroke() + c.beginPath();c.arc(0,-34,11,0,T);c.fill();c.stroke() + c.fillStyle='#fff';for(const d of[-1,1])c.beginPath(),c.arc(d*4,-35,2.2,0,T),c.fill() + c.restore() +} diff --git a/gen/color/crab/dance.js b/gen/color/crab/dance.js new file mode 100644 index 0000000..4e9ce30 --- /dev/null +++ b/gen/color/crab/dance.js @@ -0,0 +1,27 @@ +// crab side-stepping back and forth with its claws up, snapping to the beat +const{sin,cos,PI}=Math,T=2*PI +export const frames=20,delay=70 +const R='#ff6b6b',SP='#ffa8a8',BL='#e03131' +export default(c,t)=>{ + const x=128+22*sin(t*T),sn=(1+cos(4*T*t))/2,b=2*sin(4*T*t) + const D=(w,col,f)=>{for(const[l,s]of[[w,'#111'],[w-8,col]])c.lineWidth=l,c.strokeStyle=s,c.beginPath(),f(),c.stroke();c.lineWidth=7;c.strokeStyle='#111'} + for(const d of[-1,1])for(let k=0;k<3;k++){ + const lift=4*sin(4*T*t+k*2+(d>0?PI:0)) + D(14,R,()=>{c.moveTo(x+d*(34+k*10),176);c.lineTo(x+d*(62+k*11),160+lift);c.lineTo(x+d*(72+k*12),206+(k==1?2:0))}) + } + for(const d of[-1,1]){ + const cx=x+d*72,cy=92+b + D(18,R,()=>{c.moveTo(x+d*40,156);c.quadraticCurveTo(x+d*72,150,cx,cy+18)}) + const m=.12+.4*sn,a=-PI/2-d*.35 + c.fillStyle=R;c.beginPath();c.moveTo(cx,cy);c.arc(cx,cy,26,a+m,a-m+T);c.closePath();c.fill();c.stroke() + } + for(const d of[-1,1])D(12,R,()=>{c.moveTo(x+d*16,150);c.lineTo(x+d*20,110+b)}) + c.fillStyle=R;c.beginPath();c.ellipse(x,166,62,40,0,0,T);c.fill();c.stroke() + c.fillStyle=SP;for(const[dx,dy,r]of[[-30,-18,6],[24,-22,5],[36,-6,4],[-40,-2,4]])c.beginPath(),c.arc(x+dx,166+dy,r,0,T),c.fill() + for(const d of[-1,1]){ + c.fillStyle='#fff';c.beginPath();c.arc(x+d*20,104+b,12,0,T);c.fill();c.stroke() + c.fillStyle='#111';c.beginPath();c.arc(x+d*20+d*2,105+b,5.5,0,T);c.fill() + } + c.lineWidth=5;c.beginPath();c.arc(x,170,12,.2*PI,.8*PI);c.stroke() + c.fillStyle=BL;for(const d of[-1,1])c.beginPath(),c.ellipse(x+d*30,176,7,4,0,0,T),c.fill() +} diff --git a/gen/color/dino/stomp.js b/gen/color/dino/stomp.js new file mode 100644 index 0000000..ced4f14 --- /dev/null +++ b/gen/color/dino/stomp.js @@ -0,0 +1,29 @@ +// little dinosaur stomping in place, roaring each time a foot lands and kicking up dust +const{sin,cos,PI,max}=Math,T=2*PI +export const frames=16,delay=80 +const G='#69db7c',BE='#d8f5a2',SP='#ffa94d',BL='#ffa8a8' +export default(c,t)=>{ + const u=(t*2)%1,y=-5*sin(PI*u),la=t<.5?18*sin(2*PI*t):0,lb=t>=.5?18*sin(2*PI*(t-.5)):0,roar=u<.3,tw=.12*sin(T*t) + const tail=()=>{c.moveTo(78,132+y);c.quadraticCurveTo(40,150+y,14,196+y+20*tw);c.quadraticCurveTo(54,190+y,90,184+y)} + for(const[x,l]of[[98,la],[146,lb]]){c.fillStyle=G;c.beginPath();c.roundRect(x-17,170+y,34,58-y-l,12);c.fill();c.stroke() + c.lineWidth=4;c.beginPath();c.moveTo(x+6,226-l);c.lineTo(x+6,218-l);c.moveTo(x-4,226-l);c.lineTo(x-4,218-l);c.stroke();c.lineWidth=7} + if(u<.25)for(const d of[-1,1]){const fx=t<.5?98:146,s=(1-u/.25);c.fillStyle='#fff';c.beginPath();c.arc(fx+d*(30+18*(1-s)),222,8*s+2,0,T);c.fill();c.stroke()} + c.fillStyle=SP + for(const[x,yy,a]of[[64,126,-.9],[90,104,-.5],[118,96,-.1],[146,98,.2]])c.save(),c.translate(x,yy+y),c.rotate(a),c.beginPath(),c.moveTo(-12,8),c.lineTo(0,-18),c.lineTo(12,8),c.closePath(),c.fill(),c.stroke(),c.restore() + const head=()=>c.ellipse(186,90+y,44,32,-.08,0,T),body=()=>c.ellipse(120,150+y,60,50,0,0,T),neck=()=>{c.moveTo(146,134+y);c.lineTo(172,104+y)} + c.lineWidth=14;c.beginPath();tail();c.stroke();for(const f of[head,body])c.beginPath(),f(),c.stroke() + c.lineWidth=58;c.beginPath();neck();c.stroke() + c.fillStyle=c.strokeStyle=G;c.lineWidth=44;c.beginPath();neck();c.stroke() + c.beginPath();tail();c.fill();for(const f of[head,body])c.beginPath(),f(),c.fill() + c.strokeStyle='#111';c.lineWidth=7 + c.save();c.beginPath();body();c.clip();c.fillStyle=BE;c.beginPath();c.ellipse(140,184+y,42,40,0,0,T);c.fill();c.lineWidth=5;c.stroke() + c.lineWidth=3;for(const yy of[166,180,194])c.beginPath(),c.moveTo(112,yy+y),c.lineTo(170,yy+y),c.stroke();c.restore() + for(const[w,s]of[[16,'#111'],[8,G]])c.lineWidth=w,c.strokeStyle=s,c.beginPath(),c.moveTo(160,148+y),c.lineTo(176,160+y+(roar?-8:0)),c.stroke() + c.lineWidth=7;c.strokeStyle='#111' + c.fillStyle='#111';c.beginPath();c.arc(190,80+y,6,0,T);c.fill();c.beginPath();c.arc(222,82+y,2.5,0,T);c.fill() + c.fillStyle='#fff';c.beginPath();c.arc(188,78+y,2.2,0,T);c.fill() + c.fillStyle=BL;c.beginPath();c.ellipse(176,98+y,8,5,0,0,T);c.fill() + if(roar){c.fillStyle='#111';c.beginPath();c.moveTo(194,104+y);c.quadraticCurveTo(212,124+y,228,100+y);c.closePath();c.fill() + c.fillStyle='#fff';for(const x of[202,214])c.beginPath(),c.moveTo(x,104+y),c.lineTo(x+4,111+y),c.lineTo(x+8,103+y),c.fill()} + else{c.lineWidth=5;c.beginPath();c.moveTo(196,104+y);c.quadraticCurveTo(212,110+y,226,100+y);c.stroke()} +} diff --git a/gen/color/duck/swim.js b/gen/color/duck/swim.js new file mode 100644 index 0000000..17affb4 --- /dev/null +++ b/gen/color/duck/swim.js @@ -0,0 +1,23 @@ +// rubber duck bobbing and rocking on little rolling waves +const{sin,PI}=Math,T=2*PI +export const frames=20,delay=80 +const Y='#ffd43b',YD='#fab005',O='#ff922b',WA='#74c0fc',BL='#ffa8a8' +export default(c,t)=>{ + const b=5*sin(t*T),r=.07*sin(t*T+1) + c.save();c.translate(128,170+b);c.rotate(r);c.translate(-128,-170) + c.fillStyle=Y + c.beginPath();c.moveTo(186,140);c.lineTo(214,118);c.lineTo(206,156);c.closePath();c.fill();c.stroke() + c.beginPath();c.ellipse(136,158,72,44,0,0,T);c.fill();c.stroke() + c.fillStyle=YD;c.beginPath();c.moveTo(122,150);c.quadraticCurveTo(162,128,190,152);c.quadraticCurveTo(160,180,122,150);c.fill();c.lineWidth=5;c.stroke();c.lineWidth=7 + c.fillStyle=Y;c.beginPath();c.arc(98,100,38,0,T);c.fill();c.stroke() + c.lineWidth=5;c.beginPath();c.moveTo(100,62);c.quadraticCurveTo(96,48,108,46);c.stroke();c.lineWidth=7 + c.fillStyle=O;c.beginPath();c.ellipse(60,110,22,11,-.1,0,T);c.fill();c.stroke() + c.lineWidth=4;c.beginPath();c.moveTo(42,111);c.lineTo(72,110);c.stroke() + c.fillStyle='#111';c.beginPath();c.arc(94,92,6,0,T);c.fill() + c.fillStyle='#fff';c.beginPath();c.arc(92,90,2.2,0,T);c.fill() + c.fillStyle=BL;c.beginPath();c.ellipse(112,112,8,5,0,0,T);c.fill() + c.restore() + const wy=x=>194+7*sin(x/22-t*T) + c.lineWidth=7;c.fillStyle=WA;c.beginPath();c.moveTo(22,244);c.quadraticCurveTo(8,244,8,230);for(let x=8;x<=248;x+=4)c.lineTo(x,wy(x));c.lineTo(248,230);c.quadraticCurveTo(248,244,234,244);c.closePath();c.fill();c.stroke() + c.strokeStyle='#fff';c.lineWidth=5;for(const x0 of[40,150,210])c.beginPath(),c.moveTo(x0,wy(x0)+18),c.lineTo(x0+16,wy(x0+16)+18),c.stroke() +} diff --git a/gen/color/fox/sleep.js b/gen/color/fox/sleep.js new file mode 100644 index 0000000..f8f9856 --- /dev/null +++ b/gen/color/fox/sleep.js @@ -0,0 +1,25 @@ +// fox curled up asleep on its own tail, breathing slowly, a sleep bubble swelling and shrinking at its nose +const{sin,PI}=Math,T=2*PI +export const frames=24,delay=90 +const O='#ff922b',W='#fff',IN='#ffd8a8',BU='#d0ebff',BL='#ffa8a8' +export default(c,t)=>{ + const b=sin(t*T),y=-3*b + const F=(col,f)=>{c.fillStyle=col;c.beginPath();f();c.fill();c.stroke()} + F(O,()=>c.ellipse(148,166+y*.5,72,50+2*b,0,0,T)) + c.save();c.translate(214,206);c.rotate(.03*sin(2*T*t));c.translate(-214,-206) + c.beginPath();c.ellipse(132,206,84,24,-.04,0,T);c.fillStyle=O;c.fill() + c.save();c.clip();c.fillStyle=W;c.beginPath();c.ellipse(52,206,34,32,0,0,T);c.fill();c.lineWidth=5;c.stroke();c.restore() + c.beginPath();c.ellipse(132,206,84,24,-.04,0,T);c.stroke();c.restore() + for(const[a,b2,tp]of[[[58,140],[84,116],[52,98]],[[94,116],[118,138],[122,100]]]){ + F(O,()=>{c.moveTo(a[0],a[1]+y);c.lineTo(tp[0],tp[1]+y);c.lineTo(b2[0],b2[1]+y);c.closePath()}) + c.fillStyle=IN;c.beginPath();c.moveTo(a[0]+(b2[0]-a[0])*.3,a[1]+(b2[1]-a[1])*.3+y);c.lineTo(tp[0]+(a[0]+b2[0]-2*tp[0])*.2,tp[1]+12+y);c.lineTo(a[0]+(b2[0]-a[0])*.7,a[1]+(b2[1]-a[1])*.7+y);c.closePath();c.fill() + } + F(O,()=>c.arc(88,152+y,38,0,T)) + F(W,()=>c.ellipse(62,166+y,26,16,-.15,0,T)) + c.fillStyle='#111';c.beginPath();c.ellipse(38,162+y,7,6,0,0,T);c.fill() + c.lineWidth=5;for(const x of[78,104])c.beginPath(),c.arc(x,144+y,7,.15*PI,.85*PI),c.stroke() + c.fillStyle=BL;c.beginPath();c.ellipse(104,166+y,8,5,0,0,T);c.fill() + const r=4+12*(1+b)/2 + c.lineWidth=4;c.fillStyle=BU;c.beginPath();c.arc(30-r*.4,148+y-r*.5,r,0,T);c.fill();c.stroke() + c.fillStyle=W;c.beginPath();c.arc(26-r*.6,144+y-r*.8,r*.25,0,T);c.fill() +} diff --git a/gen/color/jellyfish/pulse.js b/gen/color/jellyfish/pulse.js new file mode 100644 index 0000000..ad77741 --- /dev/null +++ b/gen/color/jellyfish/pulse.js @@ -0,0 +1,21 @@ +// pink jellyfish pulsing its bell, drifting up on each squeeze, tentacles trailing and bubbles rising +const{sin,cos,PI}=Math,T=2*PI +export const frames=20,delay=80 +const PK='#faa2c1',LT='#ffdeeb',DK='#f06595',BU='#d0ebff' +export default(c,t)=>{ + const s=sin(t*T),y=92-10*s,rx=66*(1-.12*s),ry=56*(1+.1*s),bot=y+ry*.55 + c.lineWidth=4;for(const[x0,o]of[[46,0],[210,.5],[196,.2]]){const p=(t+o)%1;c.fillStyle=BU;c.beginPath();c.arc(x0+6*sin(p*T*2),240-p*230,3+5*p,0,T);c.fill();c.stroke()} + const D=(w,col,f)=>{for(const[l,st]of[[w,'#111'],[w-8,col]])c.lineWidth=l,c.strokeStyle=st,c.beginPath(),f(),c.stroke()} + for(const k of[-1.5,-.5,.5,1.5])D(11,PK,()=>{for(let v=0;v<=1;v+=.05)c.lineTo(128+k*rx*.42+9*v*sin(v*7-t*T+k),bot+v*112)}) + for(const k of[-1,0,1])D(24,LT,()=>{for(let v=0;v<=1;v+=.05)c.lineTo(128+k*20+12*v*sin(v*6-t*T+k*2),bot-4+v*80)}) + c.lineWidth=7;c.strokeStyle='#111';c.fillStyle=PK + c.beginPath();c.ellipse(128,bot,rx,ry*1.4,0,PI,T) + const n=6;for(let k=0;k{ + const shut=i==8||i==9,a=.1*sin(t*T) + c.fillStyle=BRANCH;c.beginPath();c.roundRect(14,206,228,20,10);c.fill();c.stroke() + c.fillStyle=LF;for(const[x,r]of[[34,-.5],[226,.5]])c.beginPath(),c.ellipse(x,198,16,8,r,0,T),c.fill(),c.stroke() + c.fillStyle=O;for(const x of[108,148])c.beginPath(),c.ellipse(x,208,14,7,0,0,T),c.fill(),c.stroke() + c.translate(128,206);c.rotate(a);c.translate(-128,-206) + c.fillStyle=BR + for(const d of[-1,1])c.beginPath(),c.moveTo(128+d*52,66),c.lineTo(128+d*66,30),c.lineTo(128+d*24,56),c.closePath(),c.fill(),c.stroke() + c.beginPath();c.ellipse(128,128,70,78,0,0,T);c.fill();c.stroke() + c.fillStyle=BE;c.beginPath();c.ellipse(128,160,44,40,0,0,T);c.fill();c.lineWidth=5;c.stroke() + c.lineWidth=4;for(const[x,y]of[[112,148],[136,146],[124,166],[148,164],[108,172]])c.beginPath(),c.arc(x,y-6,7,.2*PI,.8*PI),c.stroke() + c.lineWidth=7;c.fillStyle=WG + for(const d of[-1,1])c.beginPath(),c.ellipse(128+d*62,150,16,40,-d*(.2+.08*sin(2*T*t)),0,T),c.fill(),c.stroke() + c.fillStyle=FA;for(const d of[-1,1])c.beginPath(),c.arc(128+d*27,98,31,0,T),c.fill(),c.stroke() + for(const d of[-1,1]){ + const x=128+d*27 + if(shut){c.lineWidth=5;c.beginPath();c.arc(x,94,14,.15*PI,.85*PI);c.stroke();c.lineWidth=7;continue} + c.fillStyle='#fff';c.beginPath();c.arc(x,98,21,0,T);c.fill();c.lineWidth=5;c.stroke() + c.fillStyle=Y;c.beginPath();c.arc(x+d*-2,99,13,0,T);c.fill() + c.fillStyle='#111';c.beginPath();c.arc(x+d*-2,99,7,0,T);c.fill() + c.fillStyle='#fff';c.beginPath();c.arc(x+d*-2-3,95,3,0,T);c.fill();c.lineWidth=7 + } + c.fillStyle=O;c.lineWidth=5;c.beginPath();c.moveTo(119,122);c.lineTo(137,122);c.lineTo(128,140);c.closePath();c.fill();c.stroke() +} diff --git a/gen/color/pig/oink.js b/gen/color/pig/oink.js new file mode 100644 index 0000000..d191eb8 --- /dev/null +++ b/gen/color/pig/oink.js @@ -0,0 +1,25 @@ +// pig bobbing its head, ears flapping, snout scrunching up with each oink +const{sin,cos,PI,max}=Math,T=2*PI +export const frames=20,delay=70 +const P='#ffc9d6',S='#ff8fab',D='#c2255c',BL='#ffa8c5' +export default(c,t)=>{ + const k=max(0,sin(2*T*t)),y=-6*k,sx=1+.1*k,sy=1-.16*k + if(k>.6)for(const d of[-1,1])for(const a of[-.35,0,.35]){ + const ang=(d<0?PI:0)+d*a,x0=128+cos(ang)*98,y0=150+y+sin(ang)*72 + for(const[w,s]of[[11,'#111'],[4,'#fff']])c.lineWidth=w,c.strokeStyle=s,c.beginPath(),c.moveTo(x0,y0),c.lineTo(x0+cos(ang)*18,y0+sin(ang)*14),c.stroke() + } + c.lineWidth=7;c.strokeStyle='#111' + for(const d of[-1,1]){ + c.save();c.translate(128+d*54,80+y);c.rotate(d*(.55+.25*sin(2*T*t+1))) + c.fillStyle=P;c.beginPath();c.moveTo(-24,14);c.lineTo(0,-36);c.lineTo(24,14);c.closePath();c.fill();c.stroke() + c.fillStyle=S;c.beginPath();c.moveTo(-12,8);c.lineTo(0,-20);c.lineTo(12,8);c.closePath();c.fill() + c.restore() + } + c.fillStyle=P;c.beginPath();c.ellipse(128,138+y,84,74,0,0,T);c.fill();c.stroke() + c.fillStyle='#111';for(const d of[-1,1])c.beginPath(),c.ellipse(128+d*30,114+y,7,9,0,0,T),c.fill() + c.fillStyle='#fff';for(const d of[-1,1])c.beginPath(),c.arc(128+d*30-2,110+y,2.5,0,T),c.fill() + c.fillStyle=BL;for(const d of[-1,1])c.beginPath(),c.ellipse(128+d*50,150+y,13,8,0,0,T),c.fill() + c.fillStyle=S;c.beginPath();c.ellipse(128,154+y,34*sx,24*sy,0,0,T);c.fill();c.stroke() + c.fillStyle=D;for(const d of[-1,1])c.beginPath(),c.ellipse(128+d*12*sx,154+y,5,8*sy,0,0,T),c.fill() + c.lineWidth=5;c.beginPath();c.arc(128,186+y,10,.2*PI,.8*PI);c.stroke() +} diff --git a/gen/color/turtle/walk.js b/gen/color/turtle/walk.js new file mode 100644 index 0000000..73d59ae --- /dev/null +++ b/gen/color/turtle/walk.js @@ -0,0 +1,28 @@ +// turtle plodding along in place: legs stepping in diagonal pairs, head bobbing, shell rocking +const{sin,cos,PI,abs,max}=Math,T=2*PI +export const frames=16,delay=80 +const SH='#51cf66',RIM='#2f9e44',SK='#ffe066',BL='#ffa8a8' +export default(c,t)=>{ + const y=-2*abs(sin(T*t)),hb=3*sin(2*T*t) + for(const[x,ph]of[[84,0],[110,PI],[150,PI],[176,0]]){ + const l=max(0,sin(T*t+ph))*9,s=6*cos(T*t+ph) + c.fillStyle=SK;c.beginPath();c.roundRect(x-13+s,170+y-l,26,46,12);c.fill();c.stroke() + } + c.fillStyle=SK;c.beginPath();c.moveTo(58,170+y);c.lineTo(34,184+y);c.lineTo(60,186+y);c.closePath();c.fill();c.stroke() + for(const[w,s]of[[34,'#111'],[20,SK]])c.lineWidth=w,c.strokeStyle=s,c.beginPath(),c.moveTo(184,172+y),c.lineTo(204,148+y+hb),c.stroke() + c.lineWidth=7;c.strokeStyle='#111' + c.fillStyle=RIM;c.beginPath();c.roundRect(48,160+y,164,22,11);c.fill();c.stroke() + c.fillStyle=SH;c.beginPath();c.moveTo(56,166+y);c.arc(130,166+y,74,PI,0);c.closePath();c.fill();c.stroke() + c.save();c.beginPath();c.moveTo(56,166+y);c.arc(130,166+y,74,PI,0);c.closePath();c.clip() + c.lineWidth=5;c.beginPath() + const hx=(a,r)=>[130+cos(a)*r,124+y+sin(a)*r] + for(let k=0;k<=6;k++){const[x,yy]=hx(k*PI/3,24);k?c.lineTo(x,yy):c.moveTo(x,yy)} + for(const a of[PI,PI*4/3,PI*5/3,0]){const[x,yy]=hx(a,24),[x2,y2]=hx(a,90);c.moveTo(x,yy);c.lineTo(x2,a==PI||a==0?160+y:y2)} + c.stroke();c.restore();c.lineWidth=7 + c.beginPath();c.moveTo(56,166+y);c.arc(130,166+y,74,PI,0);c.closePath();c.stroke() + c.fillStyle=SK;c.beginPath();c.arc(210,140+y+hb,25,0,T);c.fill();c.stroke() + c.fillStyle='#111';c.beginPath();c.arc(218,132+y+hb,5.5,0,T);c.fill() + c.fillStyle='#fff';c.beginPath();c.arc(216,130+y+hb,2,0,T);c.fill() + c.lineWidth=4;c.beginPath();c.arc(214,144+y+hb,9,.1*PI,.6*PI);c.stroke() + c.fillStyle=BL;c.beginPath();c.ellipse(204,146+y+hb,6,4,0,0,T);c.fill() +} diff --git a/gen/color/whale/spout.js b/gen/color/whale/spout.js new file mode 100644 index 0000000..5925b6e --- /dev/null +++ b/gen/color/whale/spout.js @@ -0,0 +1,28 @@ +// smiling whale bobbing along, tail flapping, water spouting out of its blowhole +const{sin,PI}=Math,T=2*PI +export const frames=24,delay=70 +const B='#4dabf7',BE='#d0ebff',WA='#a5d8ff',BL='#faa2c1' +const body=c=>{c.beginPath();c.moveTo(36,162);c.bezierCurveTo(32,96,150,78,204,146);c.bezierCurveTo(180,198,64,218,36,162)} +export default(c,t)=>{ + const y=4*sin(t*T) + c.lineWidth=4 + for(const d of[-1,0,1])for(let k=0;k<3;k++){ + const p=(t*2+k/3)%1,x=110+d*40*p,yy=92+y-(d?60:78)*4*p*(1-p) + c.fillStyle=WA;c.beginPath();c.arc(x,yy,7-p*3,0,T);c.fill();c.stroke() + } + const h=(1+sin(2*T*t))/2 + c.fillStyle=WA;c.beginPath();c.moveTo(102,100+y);c.quadraticCurveTo(100,70+y,110,52+y-14*h);c.quadraticCurveTo(122,70+y,118,100+y);c.fill();c.stroke() + c.lineWidth=7 + c.save();c.translate(196,150+y);c.rotate(-.55+.2*sin(2*T*t)) + c.fillStyle=B;c.beginPath();c.moveTo(-8,8);c.quadraticCurveTo(10,-20,22,-48);c.quadraticCurveTo(30,-26,52,-30);c.quadraticCurveTo(34,-6,-8,8);c.fill();c.stroke();c.restore() + c.translate(0,y) + body(c);c.fillStyle=B;c.fill() + c.save();body(c);c.clip();c.fillStyle=BE;c.beginPath();c.ellipse(112,214,96,40,-.05,0,T);c.fill();c.stroke() + c.lineWidth=3;for(const yy of[192,202])c.beginPath(),c.moveTo(56,yy),c.quadraticCurveTo(110,yy+10,170,yy-8),c.stroke();c.restore() + body(c);c.stroke() + c.fillStyle=B;c.beginPath();c.ellipse(112,180,22,11,.5,0,T);c.fill();c.stroke() + c.fillStyle='#111';c.beginPath();c.arc(72,138,6,0,T);c.fill() + c.fillStyle='#fff';c.beginPath();c.arc(70,136,2.2,0,T);c.fill() + c.lineWidth=5;c.beginPath();c.moveTo(42,160);c.quadraticCurveTo(66,170,92,160);c.stroke() + c.fillStyle=BL;c.beginPath();c.ellipse(90,148,9,5,0,0,T);c.fill() +} diff --git a/index.html b/index.html index ff872e3..d26f1da 100644 --- a/index.html +++ b/index.html @@ -22,6 +22,7 @@ figcaption a{color:var(--mute);text-decoration:none;margin:0 4px} figcaption a:hover{color:var(--fg);text-decoration:underline} #bg{position:absolute;top:16px;right:16px;width:28px;height:28px;padding:0;border:2px solid var(--fg);border-radius:50%;background:linear-gradient(90deg,var(--fg) 50%,var(--bg) 50%);cursor:pointer} img{width:100%;height:100%;object-fit:contain} +hr{grid-column:1/-1;border:0;border-top:3px dashed var(--mute);margin:24px 0} @media(max-width:480px){main{grid-template-columns:repeat(3,1fr);gap:10px}} diff --git a/public/butterfly/flutter.gif b/public/butterfly/flutter.gif new file mode 100644 index 0000000..71ea304 Binary files /dev/null and b/public/butterfly/flutter.gif differ diff --git a/public/butterfly/flutter.webp b/public/butterfly/flutter.webp new file mode 100644 index 0000000..2d48d84 Binary files /dev/null and b/public/butterfly/flutter.webp differ diff --git a/public/crab/dance.gif b/public/crab/dance.gif new file mode 100644 index 0000000..60604b0 Binary files /dev/null and b/public/crab/dance.gif differ diff --git a/public/crab/dance.webp b/public/crab/dance.webp new file mode 100644 index 0000000..75f8b9c Binary files /dev/null and b/public/crab/dance.webp differ diff --git a/public/dino/stomp.gif b/public/dino/stomp.gif new file mode 100644 index 0000000..f074a2f Binary files /dev/null and b/public/dino/stomp.gif differ diff --git a/public/dino/stomp.webp b/public/dino/stomp.webp new file mode 100644 index 0000000..120d089 Binary files /dev/null and b/public/dino/stomp.webp differ diff --git a/public/duck/swim.gif b/public/duck/swim.gif new file mode 100644 index 0000000..8490965 Binary files /dev/null and b/public/duck/swim.gif differ diff --git a/public/duck/swim.webp b/public/duck/swim.webp new file mode 100644 index 0000000..545009d Binary files /dev/null and b/public/duck/swim.webp differ diff --git a/public/fox/sleep.gif b/public/fox/sleep.gif new file mode 100644 index 0000000..4b151f0 Binary files /dev/null and b/public/fox/sleep.gif differ diff --git a/public/fox/sleep.webp b/public/fox/sleep.webp new file mode 100644 index 0000000..7f0d7fb Binary files /dev/null and b/public/fox/sleep.webp differ diff --git a/public/jellyfish/pulse.gif b/public/jellyfish/pulse.gif new file mode 100644 index 0000000..8106a06 Binary files /dev/null and b/public/jellyfish/pulse.gif differ diff --git a/public/jellyfish/pulse.webp b/public/jellyfish/pulse.webp new file mode 100644 index 0000000..ed3cbb6 Binary files /dev/null and b/public/jellyfish/pulse.webp differ diff --git a/public/owl/blink.gif b/public/owl/blink.gif new file mode 100644 index 0000000..b06560f Binary files /dev/null and b/public/owl/blink.gif differ diff --git a/public/owl/blink.webp b/public/owl/blink.webp new file mode 100644 index 0000000..65059f7 Binary files /dev/null and b/public/owl/blink.webp differ diff --git a/public/pig/oink.gif b/public/pig/oink.gif new file mode 100644 index 0000000..4ae2943 Binary files /dev/null and b/public/pig/oink.gif differ diff --git a/public/pig/oink.webp b/public/pig/oink.webp new file mode 100644 index 0000000..397b254 Binary files /dev/null and b/public/pig/oink.webp differ diff --git a/public/turtle/walk.gif b/public/turtle/walk.gif new file mode 100644 index 0000000..b848546 Binary files /dev/null and b/public/turtle/walk.gif differ diff --git a/public/turtle/walk.webp b/public/turtle/walk.webp new file mode 100644 index 0000000..b315823 Binary files /dev/null and b/public/turtle/walk.webp differ diff --git a/public/whale/spout.gif b/public/whale/spout.gif new file mode 100644 index 0000000..40dffb5 Binary files /dev/null and b/public/whale/spout.gif differ diff --git a/public/whale/spout.webp b/public/whale/spout.webp new file mode 100644 index 0000000..122ae84 Binary files /dev/null and b/public/whale/spout.webp differ diff --git a/scripts/gen.js b/scripts/gen.js index 0d1b491..7b55cb8 100644 --- a/scripts/gen.js +++ b/scripts/gen.js @@ -1,4 +1,5 @@ -// renders gen//.js into public//.{webp,gif} +// renders gen/
//.js into public//.{webp,gif} +// the section folder (bw, color) only picks where it shows in the gallery, it isn't part of the url // npm run gen renders only what's missing (published files never change by accident) // npm run gen -- cat/blink re-renders the named ones import{createCanvas}from'@napi-rs/canvas' @@ -8,8 +9,11 @@ import{dirname,resolve}from'path' import{pathToFileURL as url}from'url' const S=256,redo=process.argv.slice(2) const walk=d=>rd(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(`${d}/${e.name}`):`${d}/${e.name}`) -for(const f of walk('gen').filter(f=>f.endsWith('.js')).sort()){ - const id=f.slice(4,-3),o=`public/${id}` +const files=walk('gen').filter(f=>f.endsWith('.js')).sort(),ids=files.map(f=>f.split('/').slice(2).join('/').slice(0,-3)) +const dup=ids.find((d,i)=>ids.indexOf(d)!=i) +if(dup)throw Error(`${dup} is in more than one section`) +for(const[k,f]of files.entries()){ + const id=ids[k],o=`public/${id}` if(!redo.includes(id)&&ex(o+'.webp')&&ex(o+'.gif'))continue const{default:draw,frames=16,delay=80}=await import(url(resolve(f))) const px=[...Array(frames)].map((_,i)=>{ diff --git a/vite.config.js b/vite.config.js index 3fe7587..4fb939a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,21 +1,25 @@ // fills in index.html from whatever is in public/ // one tile per name: shows the .webp (or the .gif if that's all there is) and links every format +// tiles are split into sections by the gen/
/ folder they come from, with a divider between +// hand-made files that have no gen file go in the first section // tile images get ?v= so browsers drop old copies when a file changes, links stay clean for hotlinking import{readdirSync as rd,readFileSync as rf}from'fs' import{createHash}from'crypto' +const sections=['bw','color'] const hidden=['cat/tail.gif'] // same drawing as cat/wag.gif, kept so old links still work const walk=d=>rd(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(`${d}/${e.name}`):`${d}/${e.name}`) const dims=(b,x)=>x=='gif'?[b.readUInt16LE(6),b.readUInt16LE(8)]:[b.readUIntLE(24,3)+1,b.readUIntLE(27,3)+1] const gallery=()=>{ - const g={} + const sec={},g=Object.fromEntries(sections.map(s=>[s,{}])) + for(const f of walk('gen').filter(f=>f.endsWith('.js'))){const[,s,...r]=f.split('/');sec[r.join('/').slice(0,-3)]=s} for(const f of walk('public').filter(f=>/\.(webp|gif)$/.test(f)).sort()){ - const p=f.slice(7),x=p.split('.').pop() - if(!hidden.includes(p))(g[p.slice(0,-x.length-1)]??={})[x]=f + const p=f.slice(7),x=p.split('.').pop(),id=p.slice(0,-x.length-1) + if(!hidden.includes(p))(g[sec[id]??sections[0]][id]??={})[x]=f } - return Object.entries(g).map(([id,fm])=>{ + return sections.map(s=>Object.entries(g[s]).map(([id,fm])=>{ const x=fm.webp?'webp':'gif',b=rf(fm[x]),[w,h]=dims(b,x),v=createHash('md5').update(b).digest('hex').slice(0,8) const links=['webp','gif'].filter(k=>fm[k]).map(k=>`${k}`).join(' ') return`
${id.replace('/',' ')}
${links}
` - }).join('\n') + }).join('\n')).filter(Boolean).join('\n
\n') } export default{plugins:[{name:'gallery',transformIndexHtml:h=>h.replace('',gallery())}]}