mirror of
https://github.com/direct-img/vibegif.direct-img.link.git
synced 2026-10-29 03:34:51 +00:00
Anime, 50 more colored girls: daily moments (yawn, hiccup, toothbrush, hair dryer, blanket burrito, pocky), hobbies (baker, barista, DJ, piano, violin, polaroid, knitting, origami, pottery), sports (swimming, tennis, badminton, skating, ballet, jump rope, hula hoop, bicycle, campfire, surfing), holidays (halloween, christmas, valentine, mochi, birthday, easter, tsukimi, suikawari, taiko, teru teru bozu) and fantasy (fairy, bunny hood, slime, cyber, magical girl, alien, jiangshi, tanuki, yuki-onna, little devil). Manga, 50 more black-and-white girls: reactions (doki, stare, ufufu, denial, fired up, panic, sigh, confused, shiver, poka poka), sports and martial arts (boxing, fencing, volleyball, sprint, gymnast, baseball, naginata, nunchaku, parkour, motorbike), school (library, flute, calligraphy, science, hanko, transfer student, tug of war, sweeping, note passing, yakisoba bread), home and town (claw machine, cooking, ironing, bus stop, matcha, sewing, boxes, stray cat, foggy window, piggy bank) and yokai and fantasy (tengu, kappa, rokurokubi, temari, alchemist, mummy, wolf girl, medusa, genie, bard). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
6.0 KiB
JavaScript
65 lines
6.0 KiB
JavaScript
// manga medusa girl whose hair is little snakes wiggling and flicking their tongues, her ink sunglasses slipping down her nose until she pushes them back up
|
|
import{createCanvas}from'@napi-rs/canvas'
|
|
const{sin,cos,PI,max,min,atan2}=Math,T=2*PI
|
|
export const frames=12,delay=110,rim=4
|
|
const TL=createCanvas(6,6),tx=TL.getContext('2d');tx.fillStyle='#111';for(const[a,b]of[[1.5,1.5],[4.5,4.5]])tx.beginPath(),tx.arc(a,b,1.05,0,T),tx.fill()
|
|
const ease=x=>x<0?0:x>1?1:x*x*(3-2*x)
|
|
// gem-like eye: toned iris with a vertical slit
|
|
const eye=(c,x,y,d,tn)=>{
|
|
c.fillStyle='#fff';c.beginPath();c.ellipse(x,y,14,15,0,0,T);c.fill()
|
|
c.save();c.beginPath();c.ellipse(x,y+2,11,13,0,0,T);c.clip();c.fillStyle=tn;c.fillRect(x-12,y-12,24,28);c.fillStyle='#111';c.fillRect(x-12,y-12,24,7);c.restore()
|
|
c.lineWidth=2.5;c.beginPath();c.ellipse(x,y+2,11,13,0,0,T);c.stroke()
|
|
c.fillStyle='#111';c.beginPath();c.ellipse(x,y+3,2.5,9,0,0,T);c.fill()
|
|
c.fillStyle='#fff';c.beginPath();c.arc(x-5,y-2,3.5,0,T);c.fill()
|
|
c.lineWidth=6;c.beginPath();c.moveTo(x-16,y-5);c.quadraticCurveTo(x,y-18,x+16,y-5);c.lineTo(x+d*19,y-10);c.stroke()
|
|
}
|
|
// a little snake: toned banded body along a wiggling curve, round head with dot eyes and a flicking forked tongue
|
|
const snake=(c,pts,tn,tongue)=>{
|
|
for(const[l,s]of[[17,'#111'],[10,'#fff']]){c.lineWidth=l;c.strokeStyle=s;c.beginPath();for(const[x,y]of pts)c.lineTo(x,y);c.stroke()}
|
|
c.strokeStyle=tn;c.lineWidth=6;c.setLineDash([5,6]);c.beginPath();for(const[x,y]of pts.slice(0,-2))c.lineTo(x,y);c.stroke();c.setLineDash([]);c.strokeStyle='#111'
|
|
const[x,y]=pts[pts.length-1],[px,py]=pts[pts.length-3],a=atan2(y-py,x-px)
|
|
c.save();c.translate(x,y);c.rotate(a)
|
|
if(tongue>0){c.lineWidth=2.5;c.beginPath();c.moveTo(10,0);c.lineTo(10+10*tongue,0);c.lineTo(14+10*tongue,-4);c.moveTo(10+10*tongue,0);c.lineTo(14+10*tongue,4);c.stroke()}
|
|
c.fillStyle='#fff';c.lineWidth=4;c.beginPath();c.ellipse(2,0,11,8.5,0,0,T);c.fill();c.stroke()
|
|
c.fillStyle='#111';for(const d of[-1,1])c.beginPath(),c.arc(4,d*4,2,0,T),c.fill()
|
|
c.restore()
|
|
}
|
|
export default(c,t)=>{
|
|
const tn=c.createPattern(TL,'repeat')
|
|
// sunglasses slide down the nose, then a finger pushes them back up
|
|
const sl=ease(t/.7)*(1-ease((t-.78)/.08)),push=t>.72&&t<.96?sin((t-.72)/.24*PI):0,gy=130+22*sl
|
|
// snakes: base on the hairline, each curls outward and wiggles with its own phase
|
|
// [angle on the hairline, length, wiggle phase, curl]
|
|
const sn=[[-2.55,54,0,-1.3],[-2,50,1.6,1.2],[-1.57,46,3.1,-1.1],[-1.14,50,4.4,1.2],[-.6,54,5.6,1.3],[2.75,46,.8,1.4],[.4,46,3.4,-1.4]]
|
|
const path=([a0,len,ph,cu])=>{const bx=128+54*cos(a0),by=114+52*sin(a0),out=[];let x=bx,y=by;for(let k=0;k<=12;k++){const s=k/12,a=a0+cu*s+.35*sin(t*T*2+ph+s*3);out.push([x,y]);x+=cos(a)*len/12;y+=sin(a)*len/12}return out}
|
|
const tg=k=>max(0,sin(t*T*4+k*1.7))
|
|
// hanging side snakes go behind the face
|
|
for(const k of[5,6])snake(c,path(sn[k]),tn,tg(k))
|
|
// chiton: white with a toned sash and a round brooch
|
|
c.lineWidth=6;c.fillStyle='#fff';c.beginPath();c.moveTo(100,190);c.quadraticCurveTo(62,198,54,256);c.lineTo(202,256);c.quadraticCurveTo(194,198,156,190);c.closePath();c.fill();c.stroke()
|
|
c.fillStyle=tn;c.beginPath();c.moveTo(150,192);c.lineTo(170,196);c.lineTo(100,256);c.lineTo(74,256);c.closePath();c.fill();c.stroke()
|
|
c.fillStyle='#fff';c.lineWidth=4;c.beginPath();c.arc(156,202,9,0,T);c.fill();c.stroke();c.beginPath();c.arc(156,202,4,0,T);c.stroke()
|
|
c.fillStyle='#fff';c.beginPath();c.rect(116,168,24,24);c.fill();c.lineWidth=5;c.beginPath();c.moveTo(116,168);c.lineTo(116,190);c.moveTo(140,168);c.lineTo(140,190);c.stroke()
|
|
// toned hair cap the snakes grow out of
|
|
c.lineWidth=6;c.beginPath();c.moveTo(68,150);c.bezierCurveTo(50,74,88,48,128,48);c.bezierCurveTo(168,48,206,74,188,150);c.closePath();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(40,40,180,120);c.restore();c.stroke()
|
|
c.fillStyle='#fff';c.beginPath();c.moveTo(74,100);c.bezierCurveTo(70,154,100,186,128,188);c.bezierCurveTo(156,186,186,154,182,100);c.bezierCurveTo(182,50,74,50,74,100);c.fill();c.stroke()
|
|
eye(c,103,132,-1,tn);eye(c,153,132,1,tn)
|
|
c.lineWidth=2.5;for(const d of[-1,1])for(const k of[-5,0,5])c.beginPath(),c.moveTo(128+d*44+k-3,170),c.lineTo(128+d*44+k+3,162),c.stroke()
|
|
// smug little cat smile, a sweat drop when they've slipped far
|
|
c.lineWidth=3.5;c.beginPath();c.moveTo(118,172);c.quadraticCurveTo(123,177,128,172);c.quadraticCurveTo(133,177,138,172);c.stroke()
|
|
if(sl>.8){c.fillStyle='#fff';c.lineWidth=3;c.beginPath();c.moveTo(186,108);c.quadraticCurveTo(196,124,188,130);c.quadraticCurveTo(178,124,186,108);c.fill();c.stroke()}
|
|
// short wavy toned fringe
|
|
const fr=()=>{c.beginPath();c.moveTo(72,120);c.bezierCurveTo(60,70,92,52,128,52);c.bezierCurveTo(164,52,196,70,184,120);for(const[x,y]of[[172,96],[156,106],[142,90],[128,104],[114,90],[100,106],[84,96]])c.quadraticCurveTo(x+6,y+10,x,y);c.closePath()}
|
|
c.lineWidth=6;c.fillStyle='#fff';fr();c.fill();c.save();fr();c.clip();c.fillStyle=tn;c.fillRect(56,48,144,76);c.restore();fr();c.stroke()
|
|
// sunglasses: ink lenses with glints, covering the lower part of her eyes as they slip
|
|
c.lineWidth=5;c.beginPath();c.moveTo(78,gy);c.lineTo(70,gy-8);c.moveTo(178,gy);c.lineTo(186,gy-8);c.stroke()
|
|
for(const x of[103,153]){c.fillStyle='#111';c.beginPath();c.roundRect(x-20,gy-6,40,22,8);c.fill();c.stroke()
|
|
c.strokeStyle='#fff';c.lineWidth=3;c.beginPath();c.moveTo(x-12,gy+10);c.lineTo(x-4,gy);c.moveTo(x-4,gy+12);c.lineTo(x+2,gy+5);c.stroke();c.strokeStyle='#111';c.lineWidth=5}
|
|
c.beginPath();c.moveTo(123,gy);c.quadraticCurveTo(128,gy-4,133,gy);c.stroke()
|
|
// top snakes over the hair cap, hissing
|
|
for(let k=0;k<5;k++)snake(c,path(sn[k]),tn,tg(k))
|
|
if(sl<.3){c.lineWidth=2.5;for(const[x,y]of[[46,74],[214,74]])for(let k=0;k<3;k++)c.beginPath(),c.arc(x,y,6+k*5,-.4,.4),c.stroke()}
|
|
// finger pushing the bridge back up
|
|
if(push>.02){const fy=gy+26+44*(1-push);c.fillStyle='#fff';c.lineWidth=4;c.beginPath();c.roundRect(122,fy-18,12,30,6);c.fill();c.stroke();c.beginPath();c.ellipse(128,fy+22,18,14,0,0,T);c.fill();c.stroke()}
|
|
}
|