Files
vibegif.direct-img.link/gen/manga/knight/salute.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

73 lines
6.6 KiB
JavaScript

// manga knight girl raising her sword to her face in salute with a glint, then sweeping it down; cape and helm plume blowing
import{createCanvas}from'@napi-rs/canvas'
const{sin,cos,PI,max,min}=Math,T=2*PI
export const frames=16,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,16,0,0,T);c.fill()
c.save();c.beginPath();c.ellipse(x,y+2,11,14,0,0,T);c.clip();c.fillStyle=tn;c.fillRect(x-12,y-12,24,30);c.fillStyle='#111';c.fillRect(x-12,y-12,24,10);c.restore()
c.lineWidth=2.5;c.beginPath();c.ellipse(x,y+2,11,14,0,0,T);c.stroke()
c.fillStyle='#111';c.beginPath();c.ellipse(x,y+3,4.5,6.5,0,0,T);c.fill()
c.fillStyle='#fff';c.beginPath();c.ellipse(x-4,y-3,3.5,4.5,-.4,0,T);c.fill();c.beginPath();c.arc(x+4,y+8,2,0,T);c.fill()
c.lineWidth=7;c.beginPath();c.moveTo(x-17,y-4);c.quadraticCurveTo(x,y-15,x+17,y-4);c.moveTo(x+d*16,y-5);c.lineTo(x+d*22,y-9);c.stroke()
// determined brows
c.lineWidth=5;c.beginPath();c.moveTo(x-d*14,y-20);c.lineTo(x+d*12,y-25);c.stroke()
}
const star=(c,x,y,s)=>{c.beginPath();for(let k=0;k<8;k++){const r=k%2?s*.22:s,a=k*PI/4;c.lineTo(x+r*cos(a),y+r*sin(a))}c.closePath();c.fill();c.stroke()}
export default(c,t)=>{
const tn=c.createPattern(TL,'repeat'),w=sin(t*T*2),s=ease((t-.05)/.15)-ease((t-.55)/.15)
const hx=150-26*s,hy=206-48*s,a=-.75-(PI/2-.75)*s
// cape behind, ink black, blowing out to the left
const cp=()=>{c.beginPath();c.moveTo(90,118);c.quadraticCurveTo(60,170,34+6*w,236);c.quadraticCurveTo(50,230,62+4*w,246);c.quadraticCurveTo(76,236,92,250);c.lineTo(168,250);c.quadraticCurveTo(170,170,150,118);c.closePath()}
c.lineWidth=5;c.fillStyle='#111';cp();c.fill();c.stroke()
c.strokeStyle='#fff';c.lineWidth=2.5;c.beginPath();c.moveTo(80,150);c.quadraticCurveTo(62,196,48+5*w,232);c.stroke();c.strokeStyle='#111'
// screentone ponytail streaming left
const pt=()=>{c.beginPath();c.moveTo(92,52);c.bezierCurveTo(60,48,30,70,16+4*w,96+6*w);c.quadraticCurveTo(40,88,46+3*w,104);c.quadraticCurveTo(58,84,88,80);c.closePath()}
c.lineWidth=5;c.fillStyle='#fff';pt();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,40,100,80);c.restore();pt();c.stroke()
c.lineWidth=2.5;c.beginPath();c.moveTo(84,64);c.quadraticCurveTo(56,64,30+4*w,90+5*w);c.moveTo(84,74);c.quadraticCurveTo(62,78,46+3*w,96);c.stroke()
// breastplate and pauldrons
const bp=()=>{c.beginPath();c.moveTo(96,124);c.quadraticCurveTo(84,170,92,232);c.lineTo(150,232);c.quadraticCurveTo(158,170,144,124);c.closePath()}
c.lineWidth=5;c.fillStyle='#fff';bp();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(132,120,30,120);c.restore();bp();c.stroke()
c.lineWidth=3;c.beginPath();c.moveTo(120,150);c.lineTo(120,190);c.moveTo(106,166);c.lineTo(134,166);c.stroke()
c.beginPath();c.moveTo(92,206);c.quadraticCurveTo(120,214,150,206);c.stroke()
c.fillStyle='#fff';c.lineWidth=4;for(const[x,d]of[[94,-1],[146,1]]){c.beginPath();c.ellipse(x,130,20,14,d*.3,PI,0);c.lineTo(x+d*16,142);c.quadraticCurveTo(x,148,x-d*16,140);c.closePath();c.fill();c.stroke();c.beginPath();c.ellipse(x,134,13,7,d*.3,PI,0);c.stroke()}
const arms=()=>{
// arms out to the gauntlets on the hilt
for(const sx of[96,146])for(const[l,st]of[[18,'#111'],[10,'#fff']])c.lineWidth=l,c.strokeStyle=st,c.beginPath(),c.moveTo(sx,140),c.quadraticCurveTo((sx+hx)/2+(sx<120?-10:14),(140+hy)/2+16,hx,hy),c.stroke()
c.strokeStyle='#111'
// sword
c.save();c.translate(hx,hy);c.rotate(a)
c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.moveTo(16,-6);c.lineTo(98,-5);c.lineTo(110,0);c.lineTo(98,5);c.lineTo(16,6);c.closePath();c.fill();c.stroke()
c.lineWidth=2;c.beginPath();c.moveTo(20,0);c.lineTo(94,0);c.stroke()
c.lineWidth=4;c.beginPath();c.rect(10,-18,7,36);c.fill();c.stroke()
c.fillStyle='#111';c.beginPath();c.rect(-16,-4,26,8);c.fill();c.stroke();c.fillStyle='#fff';c.beginPath();c.arc(-20,0,6,0,T);c.fill();c.stroke()
c.restore()
for(const o of[-6,6]){c.lineWidth=4;c.fillStyle='#fff';c.beginPath();c.ellipse(hx+o*cos(a),hy+o*sin(a),9,9,0,0,T);c.fill();c.stroke()}
if(s>.95){c.fillStyle='#fff';c.lineWidth=2.5;const g=1-Math.abs((t-.38)/.12);if(g>0)star(c,hx+112*cos(a),hy+112*sin(a)+2,14*g)}
}
if(s<.5)arms()
// head
c.save();c.translate(118,84);c.scale(.6,.6);c.translate(-128,-120)
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(c,103,142,-1,tn);eye(c,153,142,1,tn)
c.lineWidth=3;for(const d of[-1,1])for(const k of[-5,1])c.beginPath(),c.moveTo(128+d*44+k-3,168),c.lineTo(128+d*44+k+3,160),c.stroke()
c.lineWidth=4;c.beginPath();c.moveTo(120,172);c.lineTo(136,172);c.stroke()
// screentone side locks framing the face
for(const d of[-1,1]){const sl=()=>{c.beginPath();c.moveTo(128+d*50,96);c.quadraticCurveTo(128+d*64,150,128+d*58,196);c.quadraticCurveTo(128+d*50,184,128+d*44,190);c.quadraticCurveTo(128+d*48,140,128+d*40,104);c.closePath()}
c.lineWidth=6;c.fillStyle='#fff';sl();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,0,256,256);c.restore();sl();c.stroke()}
// screentone bangs
const bg=()=>{c.beginPath();c.moveTo(72,150);c.bezierCurveTo(56,80,90,48,128,48);c.bezierCurveTo(166,48,200,80,184,150);c.quadraticCurveTo(180,118,172,108);c.quadraticCurveTo(162,114,156,120);c.quadraticCurveTo(150,104,138,98)
c.quadraticCurveTo(132,112,122,116);c.quadraticCurveTo(116,102,104,98);c.quadraticCurveTo(98,112,90,118);c.quadraticCurveTo(84,110,80,104);c.quadraticCurveTo(72,124,72,150);c.closePath()}
c.lineWidth=7;c.fillStyle='#fff';bg();c.fill();c.save();c.clip();c.fillStyle=tn;c.fillRect(0,0,256,256);c.restore();bg();c.stroke()
// half helm with a cheek guard line and a blowing plume
c.fillStyle='#fff';c.lineWidth=7;c.beginPath();c.moveTo(72,92);c.bezierCurveTo(70,36,186,36,184,92);c.quadraticCurveTo(128,72,72,92);c.fill();c.stroke()
c.lineWidth=4;c.beginPath();c.moveTo(80,80);c.quadraticCurveTo(128,62,176,80);c.stroke()
c.beginPath();c.moveTo(128,40);c.lineTo(128,72);c.stroke()
const pl=()=>{c.beginPath();c.moveTo(124,42);c.bezierCurveTo(110,4,72,-4,36+8*w,8+6*w);c.quadraticCurveTo(66,14,74,22);c.quadraticCurveTo(46,26,30+6*w,40+6*w);c.quadraticCurveTo(80,40,118,50);c.closePath()}
c.lineWidth=6;c.fillStyle='#fff';pl();c.fill();c.stroke()
c.lineWidth=3;c.beginPath();c.moveTo(118,40);c.quadraticCurveTo(84,14,50+6*w,12+4*w);c.moveTo(112,46);c.quadraticCurveTo(80,32,46+5*w,36+5*w);c.stroke()
c.restore()
if(s>=.5)arms()
}