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>
This commit is contained in:
2026-10-22 22:28:35 +00:00
co-authored by Claude Opus 5.5
parent 454602f0cd
commit d55bb6fc42
152 changed files with 2848 additions and 3 deletions
+73
View File
@@ -0,0 +1,73 @@
// manga dragon girl with screentone twin buns and curled horns blowing smoke rings that drift off and break up, scaly tail swishing
import{createCanvas}from'@napi-rs/canvas'
const{sin,cos,PI,max,min}=Math,T=2*PI
export const frames=12,delay=100,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()
// relaxed half-lidded eye
const eye=(c,x,y,d,tn)=>{
c.save();c.beginPath();c.rect(x-20,y-6,40,30);c.clip()
c.fillStyle='#fff';c.beginPath();c.ellipse(x,y+2,13,15,0,0,T);c.fill()
c.beginPath();c.ellipse(x+3,y+4,10,13,0,0,T);c.save();c.clip();c.fillStyle=tn;c.fillRect(x-10,y-12,26,30);c.fillStyle='#111';c.fillRect(x-10,y-12,26,14);c.restore();c.lineWidth=2.5;c.stroke()
c.fillStyle='#111';c.beginPath();c.ellipse(x+3,y+5,4,6,0,0,T);c.fill()
c.fillStyle='#fff';c.beginPath();c.ellipse(x-1,y+1,3.5,4,-.4,0,T);c.fill();c.beginPath();c.arc(x+7,y+10,1.8,0,T);c.fill()
c.restore()
c.lineWidth=7;c.beginPath();c.moveTo(x-16,y-4);c.quadraticCurveTo(x,y-9,x+16,y-4);c.moveTo(x+d*15,y-5);c.lineTo(x+d*21,y-9);c.stroke()
c.lineWidth=2.5;c.beginPath();c.moveTo(x-14,y-14);c.quadraticCurveTo(x,y-19,x+14,y-13);c.moveTo(x-6,y+20);c.lineTo(x+6,y+20);c.stroke()
}
const ring=(c,x,y,r,p)=>{
// the outline breaks into dashes as the ring fades out
const g=max(0,(p-.55)/.45)
if(g>.98)return
c.setLineDash(g>0?[22*(1-g)+4,4+14*g]:[]);c.lineDashOffset=0
c.fillStyle='#fff';c.lineWidth=3.5-1.5*g
c.beginPath();c.ellipse(x,y,r,r*.72,-.5,0,T);c.ellipse(x,y,r*.55,r*.36,-.5,0,T,true);if(!g)c.fill('evenodd');c.stroke()
c.setLineDash([])
}
export default(c,t)=>{
const tn=c.createPattern(TL,'repeat'),sw=sin(t*T)
// tail behind: the base stays put, the tip swishes; spikes ride the outer edge
const bz=(p,u)=>p[0].map((_,i)=>(1-u)**3*p[0][i]+3*(1-u)**2*u*p[1][i]+3*(1-u)*u*u*p[2][i]+u**3*p[3][i])
const O=[[84,238],[8,228],[4+4*sw,150],[24+12*sw,100]],tip=O[3]
for(let k=1;k<8;k++){const u=.12+k*.1,[x,y]=bz(O,u),[x2,y2]=bz(O,u+.02),l=Math.hypot(x2-x,y2-y),nx=(y2-y)/l,ny=-(x2-x)/l,h=11-k*.6
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.moveTo(x-(x2-x)/l*5,y-(y2-y)/l*5);c.lineTo(x+nx*h+(x2-x)/l*3,y+ny*h+(y2-y)/l*3);c.lineTo(x+(x2-x)/l*8,y+(y2-y)/l*8);c.closePath();c.fill();c.stroke()}
const tail=()=>{c.beginPath();c.moveTo(...O[0]);c.bezierCurveTo(...O[1],...O[2],...tip);c.quadraticCurveTo(tip[0]+20,tip[1]-12,tip[0]+24,tip[1]+4);c.quadraticCurveTo(tip[0]+14,tip[1]+2,tip[0]+14,tip[1]+14);c.bezierCurveTo(30+8*sw,170,50,214,112,236);c.closePath()}
c.lineWidth=5;c.fillStyle='#fff';tail();c.fill();c.save();c.clip();c.lineWidth=2;for(let y=80;y<250;y+=9)for(let x=(y/9%2?4.5:0);x<120;x+=9)c.beginPath(),c.arc(x,y,4.5,0,PI),c.stroke();c.restore();tail();c.stroke()
const H=f=>{c.save();c.translate(116,104);c.scale(.78,.78);c.translate(-128,-120);f();c.restore()}
// twin buns and long side hair in screentone
const hairTone=f=>{c.lineWidth=6;c.fillStyle='#fff';f();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,0,256,256);c.restore();f();c.stroke()}
H(()=>{
for(const d of[-1,1]){
// curled dragon horns
c.lineWidth=5;c.fillStyle='#fff';c.beginPath();c.moveTo(128+d*34,50);c.bezierCurveTo(128+d*44,20,128+d*78,10,128+d*96,24);c.bezierCurveTo(128+d*80,22,128+d*62,30,128+d*56,58);c.closePath();c.fill();c.stroke()
c.lineWidth=2.5;c.beginPath();for(const u of[.3,.5,.7])c.moveTo(128+d*(40+u*36),44-u*24),c.lineTo(128+d*(50+u*34),48-u*20);c.stroke()
hairTone(()=>{c.beginPath();c.moveTo(128+d*40,80);c.bezierCurveTo(128+d*76,90,128+d*72,170,128+d*66,214);c.quadraticCurveTo(128+d*56,206,128+d*48,214);c.quadraticCurveTo(128+d*52,150,128+d*40,120);c.closePath()})
}
})
// qipao with a high collar
c.lineWidth=5;const body=()=>{c.beginPath();c.moveTo(100,170);c.quadraticCurveTo(64,178,54,252);c.lineTo(178,252);c.quadraticCurveTo(170,178,134,170);c.closePath()}
c.fillStyle='#fff';body();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(40,186,160,70);c.restore();body();c.stroke()
c.fillStyle='#fff';c.beginPath();c.moveTo(104,158);c.lineTo(104,178);c.quadraticCurveTo(117,184,130,178);c.lineTo(130,158);c.fill();c.lineWidth=4;c.stroke()
c.beginPath();c.moveTo(117,182);c.quadraticCurveTo(136,194,150,214);c.stroke()
c.lineWidth=3;for(const[x,y]of[[134,196],[144,206]])c.beginPath(),c.moveTo(x-6,y),c.lineTo(x+6,y),c.stroke(),c.beginPath(),c.arc(x+7,y,2.5,0,T),c.stroke()
// smoke rings drifting up and right from her mouth
const R=[]
for(let k=0;k<3;k++){const p=(t+k/3)%1;if(p>.05)R.push([136+92*p,147-90*p**1.5,6+20*p,p])}
// head
const pf=max(0,1-((t*3)%1)/.3)
H(()=>{
c.lineWidth=6;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(c,103,140,-1,tn);eye(c,153,140,1,tn)
c.lineWidth=2.5;for(const d of[-1,1])for(const k of[-6,0,6])c.beginPath(),c.moveTo(128+d*42+k-3,166),c.lineTo(128+d*42+k+3,158),c.stroke()
// pursed lips, cheek puffs right after each ring leaves
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.ellipse(134,172,5+2*pf,5+pf,0,0,T);c.fill();c.stroke()
if(pf>.3){c.lineWidth=3;c.beginPath();c.arc(170,166,8,-.9,.9);c.stroke()}
// bangs and buns
hairTone(()=>{c.beginPath();c.moveTo(72,140);c.bezierCurveTo(56,70,88,40,128,40);c.bezierCurveTo(168,40,200,70,184,140)
c.quadraticCurveTo(178,112,170,104);c.quadraticCurveTo(160,110,154,118);c.quadraticCurveTo(150,100,140,94);c.quadraticCurveTo(132,108,124,114)
c.quadraticCurveTo(120,100,108,94);c.quadraticCurveTo(102,108,94,114);c.quadraticCurveTo(90,104,84,104);c.quadraticCurveTo(76,118,72,140);c.closePath()})
c.strokeStyle='#fff';c.lineWidth=4;for(const[a,e]of[[1.3,1.42],[1.5,1.6],[1.66,1.72]])c.beginPath(),c.arc(128,110,54,PI*a,PI*e),c.stroke();c.strokeStyle='#111'
for(const d of[-1,1]){hairTone(()=>{c.beginPath();c.arc(128+d*46,52,20,0,T)});c.lineWidth=2.5;c.beginPath();c.arc(128+d*46,52,11,d<0?PI*1.1:PI*1.9,d<0?PI*1.6:PI*.4,d>0);c.stroke()
c.fillStyle='#fff';c.lineWidth=3;c.beginPath();c.moveTo(128+d*30,66);c.lineTo(128+d*24,78);c.lineTo(128+d*36,74);c.closePath();c.fill();c.stroke()}
})
for(const[x,y,r,p]of R)ring(c,x,y,r,p)
}