Files
vibegif.direct-img.link/gen/manga/pirate/spyglass.js
T
multipleof4andClaude Opus 5.5 d55bb6fc42 Add a manga section of 50 black-and-white anime girls after a third divider
Black ink, screentone and white only: reaction tropes (gaan shock, gloom
corner, menacing aura, crow silence, petrify), comedy and action (harisen,
iai draw, kendo, kyudo, miko), school life (locker letters, zero test,
rooftop wind, shared umbrella), town and home (omikuji, shrine prayer,
tanabata, floating lantern, alarm smash) and fantasy (yurei, oni, dragon,
android, summoning circle, knight, pirate, detective, princess).

gen files can now export rim=<px> for a white sticker border, so solid
black ink still shows on the black page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:28:35 +00:00

75 lines
7.1 KiB
JavaScript

// manga pirate captain with an eye patch and wavy ink hair: lifts her spyglass to her eye, extends it, scans, spots something, collapses and lowers it; coat and hair whipping in the wind
import{createCanvas}from'@napi-rs/canvas'
const{sin,cos,PI,max,min}=Math,T=2*PI
export const frames=20,delay=80,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)
const eye=(c,x,y,d,tn)=>{
c.fillStyle='#fff';c.beginPath();c.ellipse(x,y,14,17,0,0,T);c.fill()
c.save();c.beginPath();c.ellipse(x+2,y+2,11.5,15,0,0,T);c.clip();c.fillStyle=tn;c.fillRect(x-12,y-14,28,32);c.fillStyle='#111';c.fillRect(x-12,y-14,28,13);c.restore()
c.lineWidth=2.5;c.beginPath();c.ellipse(x+2,y+2,11.5,15,0,0,T);c.stroke()
c.fillStyle='#111';c.beginPath();c.ellipse(x+2,y+3,4.5,7,0,0,T);c.fill()
c.fillStyle='#fff';c.beginPath();c.ellipse(x-2,y-5,4.5,5.5,-.4,0,T);c.fill();c.beginPath();c.arc(x+6,y+8,2.2,0,T);c.fill()
c.lineWidth=7;c.beginPath();c.moveTo(x-17,y-6);c.quadraticCurveTo(x,y-21,x+17,y-6);c.moveTo(x+d*16,y-7);c.lineTo(x+d*23,y-12);c.moveTo(x+d*17,y-2);c.lineTo(x+d*23,y-3);c.stroke()
c.lineWidth=2.5;c.beginPath();c.moveTo(x-8,y+19);c.lineTo(x+8,y+19);c.stroke()
}
export default(c,t)=>{
const tn=c.createPattern(TL,'repeat'),w=sin(t*T*2),spot=t>.5&&t<.66
// lift: spyglass from her chest up to her eye and back; ex: segments slide out only while it's up
const lf=ease((t-.08)/.12)-ease((t-.8)/.12),ex=ease((t-.22)/.12)-ease((t-.68)/.1),sa0=-.28+.1*sin((t-.34)/.34*T)*(t>.34&&t<.68)
const H=f=>{c.save();c.translate(108,98);c.scale(.62,.62);c.translate(-128,-120);f();c.restore()}
// wavy ink hair streaming left behind
H(()=>{c.lineWidth=7;c.fillStyle='#111';c.beginPath();c.moveTo(128,50);c.bezierCurveTo(60,40,40,100,36,150)
for(const[x,y,k]of[[-6+8*w,170,0],[18,190,1],[-20+10*w,214,2],[30,226,3],[80,230,4]])c.quadraticCurveTo(x+20,y-20+4*sin(t*T*2+k),x,y)
c.lineTo(180,200);c.bezierCurveTo(196,140,190,50,128,50);c.fill();c.stroke()
c.strokeStyle='#fff';c.lineWidth=3;c.beginPath();c.moveTo(60,110);c.quadraticCurveTo(30,150,10+6*w,170);c.moveTo(70,150);c.quadraticCurveTo(46,190,0+8*w,212);c.stroke();c.strokeStyle='#111'})
// coat with flapping tails, screentone, big white cuffs and a jabot
const ct=()=>{c.beginPath();c.moveTo(92,156);c.quadraticCurveTo(64,170,60,206);c.quadraticCurveTo(40,226+6*w,22+8*w,246+4*w);c.quadraticCurveTo(50,242,64,252);c.lineTo(168,252);c.quadraticCurveTo(164,176,128,156);c.closePath()}
c.lineWidth=5;c.fillStyle='#fff';ct();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,150,180,110);c.restore();ct();c.stroke()
c.lineWidth=3;c.beginPath();c.moveTo(60,206);c.quadraticCurveTo(44,228,34+7*w,242);c.stroke()
c.fillStyle='#fff';c.lineWidth=4;c.beginPath();c.moveTo(96,158);c.lineTo(110,252);c.lineTo(132,252);c.lineTo(126,158);c.closePath();c.fill();c.stroke()
c.beginPath();c.moveTo(112,160);for(let y=160,k=0;y<230;y+=10,k++)c.quadraticCurveTo(k%2?98:126,y+5,112,y+10);c.lineTo(112,160);c.fill();c.lineWidth=3;c.stroke()
c.beginPath();c.moveTo(112,166);c.lineTo(112,226);c.stroke()
c.fillStyle='#111';for(const y of[214,232])c.beginPath(),c.arc(140,y,4,0,T),c.fill()
// head
H(()=>{
c.lineWidth=7;c.fillStyle='#fff';c.beginPath();c.moveTo(74,96);c.bezierCurveTo(70,152,100,184,128,186);c.bezierCurveTo(156,184,186,152,182,96);c.bezierCurveTo(182,44,74,44,74,96);c.fill();c.stroke()
// eye patch over her left eye, strap across the forehead
c.lineWidth=3;c.beginPath();c.moveTo(74,112);c.lineTo(96,134);c.moveTo(112,130);c.lineTo(182,104);c.stroke()
c.fillStyle='#111';c.beginPath();c.ellipse(104,141,12,10,-.2,0,T);c.fill()
// her uncovered eye, big and bright; the eyepiece covers it while she looks
eye(c,153,140,1,tn)
// grin and blush
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.moveTo(112,168);c.quadraticCurveTo(128,184,146,166);c.quadraticCurveTo(128,172,112,168);c.closePath();c.fill();c.stroke()
c.lineWidth=3;for(const x of[85,171])for(const k of[-5,1,7])c.beginPath(),c.moveTo(x+k-3,170),c.lineTo(x+k+3,162),c.stroke()
// wavy ink bangs
c.lineWidth=7;c.fillStyle='#111';c.beginPath();c.moveTo(70,150);c.bezierCurveTo(52,74,86,44,128,44);c.bezierCurveTo(170,44,204,74,186,150);c.quadraticCurveTo(184,118,176,104)
c.quadraticCurveTo(168,110,160,108);c.quadraticCurveTo(158,94,146,88);c.quadraticCurveTo(138,104,128,108);c.quadraticCurveTo(118,96,108,90);c.quadraticCurveTo(102,106,92,110);c.quadraticCurveTo(84,104,80,100);c.quadraticCurveTo(72,120,70,150);c.closePath();c.fill();c.stroke()
c.strokeStyle='#fff';c.lineWidth=4;for(const[a,e]of[[1.34,1.46],[1.54,1.64]])c.beginPath(),c.arc(128,110,52,PI*a,PI*e),c.stroke();c.strokeStyle='#111'
// tricorn hat with skull and crossbones
const hat=()=>{c.beginPath();c.moveTo(40,82);c.quadraticCurveTo(70,66,84,40);c.quadraticCurveTo(128,4,172,40);c.quadraticCurveTo(186,66,216,82);c.quadraticCurveTo(170,74,128,86);c.quadraticCurveTo(86,74,40,82);c.closePath()}
c.lineWidth=7;c.fillStyle='#111';hat();c.fill();c.stroke()
c.strokeStyle='#fff';c.lineWidth=3;c.beginPath();c.moveTo(48,80);c.quadraticCurveTo(86,70,128,80);c.quadraticCurveTo(170,70,208,80);c.stroke()
c.lineWidth=4;c.beginPath();c.moveTo(114,44);c.lineTo(142,66);c.moveTo(142,44);c.lineTo(114,66);c.stroke()
c.fillStyle='#fff';c.beginPath();c.arc(128,46,9,0,T);c.fill();c.fillRect(123,50,10,7);c.fillStyle='#111';c.beginPath();c.arc(124.5,46,2.5,0,T);c.arc(131.5,46,2.5,0,T);c.fill()
c.strokeStyle='#111'
})
// spyglass from her right eye, segments sliding out
const ey=[156-38*lf,178-68*lf],L=[34,30*ex,26*ex],sa=-.95+(.95+sa0)*lf
c.save();c.translate(...ey);c.rotate(sa)
let x=4;const seg=[[34,13,true],[30,10.5,false],[26,8,false]]
for(let k=2;k>=0;k--){const o=4+L.slice(0,k).reduce((a,b)=>a+b,0)-(k?6:0),[l,r,tone]=seg[k];if(k&&!ex)continue
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.rect(o,-r,l,2*r);c.fill();if(tone){c.save();c.clip();c.fillStyle=tn;c.fillRect(o,0,l,r);c.restore()}c.stroke()
c.lineWidth=3;c.beginPath();c.moveTo(o+l-3,-r-2);c.lineTo(o+l-3,r+2);c.stroke()}
const tip=4+L[0]+L[1]+L[2]-(ex?12:0)
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.ellipse(tip+(ex?0:0),0,4,ex?9:13,0,0,T);c.fill();c.stroke()
c.restore()
// hand on the scope
const hp=[ey[0]+30*cos(sa),ey[1]+30*sin(sa)+12]
for(const[l,s]of[[20,'#111'],[11,'#fff']])c.lineWidth=l,c.strokeStyle=s,c.beginPath(),c.moveTo(132,172),c.quadraticCurveTo(170,186-14*lf,hp[0],hp[1]),c.stroke()
c.strokeStyle='#111';c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.ellipse(hp[0],hp[1]-2,10,9,0,0,T);c.fill();c.stroke()
// she spotted something
if(spot){const g=min(1,(t-.55)/.06);c.lineWidth=4;c.fillStyle='#fff';c.save();c.translate(178,40);c.scale(1.35*g,1.35*g);c.beginPath();c.moveTo(-5,-20);c.lineTo(5,-20);c.lineTo(2,4);c.lineTo(-2,4);c.closePath();c.fill();c.stroke();c.beginPath();c.arc(0,12,4.5,0,T);c.fill();c.stroke()
c.lineWidth=3;for(const a of[-2.6,-.5])c.beginPath(),c.moveTo(cos(a)*16,sin(a)*16-4),c.lineTo(cos(a)*24,sin(a)*24-4),c.stroke();c.restore()}
}