Add code-generated animations and build the gallery from public/

Each animation is its own file in gen/<subject>/<action>.js; npm run gen
renders the missing ones to public/ as webp + gif. The homepage grid is
now generated at build time by a small Vite plugin.

First four: ghost/float, heart/beat, cloud/rain, coffee/steam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 19:28:07 +00:00
co-authored by Claude Opus 5.5
parent 9c9f796e97
commit ac5081d294
17 changed files with 1063 additions and 8 deletions
+17
View File
@@ -0,0 +1,17 @@
// sleepy rain cloud drifting while drops fall out from under it
const{sin,PI}=Math,T=2*PI
export default(c,t)=>{
for(const[x,o]of[[72,0],[100,.55],[128,.25],[156,.8],[184,.4]]){
const y=150+((t+o)%1)*120
c.beginPath();c.moveTo(x,y-14);c.bezierCurveTo(x+3,y-7,x+8,y-3,x+8,y+3);c.arc(x,y+3,8,0,PI);c.bezierCurveTo(x-8,y-3,x-3,y-7,x,y-14)
c.lineWidth=5;c.fill();c.stroke()
}
const y=sin(t*T)*3,parts=[[78,118+y,36],[124,92+y,48],[176,112+y,38]],base=()=>c.roundRect(44,112+y,168,52,26)
c.lineWidth=14;for(const[x,yy,r]of parts)c.beginPath(),c.arc(x,yy,r,0,T),c.stroke()
c.beginPath();base();c.stroke()
for(const[x,yy,r]of parts)c.beginPath(),c.arc(x,yy,r,0,T),c.fill()
c.beginPath();base();c.fill()
c.lineWidth=6
for(const x of[104,152])c.beginPath(),c.arc(x,126+y,9,PI*.15,PI*.85),c.stroke()
c.beginPath();c.arc(128,154+y,8,PI*1.2,PI*1.8);c.stroke()
}
+22
View File
@@ -0,0 +1,22 @@
// happy coffee mug rocking a little while steam curls up out of it
const{sin,PI}=Math,T=2*PI
export const delay=90
export default(c,t)=>{
c.save();c.translate(119,214);c.rotate(.04*sin(t*T));c.translate(-119,-214)
for(const[w,s]of[[27,'#111'],[13,'#fff']])c.lineWidth=w,c.strokeStyle=s,c.beginPath(),c.arc(172,160,26,-PI/2,PI/2),c.stroke()
c.lineWidth=7;c.strokeStyle='#111'
c.beginPath();c.moveTo(66,120);c.lineTo(66,192);c.quadraticCurveTo(66,214,88,214);c.lineTo(150,214);c.quadraticCurveTo(172,214,172,192);c.lineTo(172,120)
c.ellipse(119,120,53,12,0,0,PI,true);c.fill();c.stroke()
c.beginPath();c.ellipse(119,120,53,12,0,0,T);c.fill();c.stroke()
c.fillStyle='#111';c.beginPath();c.ellipse(119,121,42,7,0,0,T);c.fill()
c.lineWidth=6
for(const x of[100,138])c.beginPath(),c.arc(x,166,8,PI*1.15,PI*1.85),c.stroke()
c.beginPath();c.arc(119,174,10,PI*.2,PI*.8);c.stroke()
c.restore()
for(const[x0,top,ph]of[[93,46,0],[119,30,.5],[145,46,1]])
for(const[w,s]of[[13,'#111'],[6,'#fff']]){
c.lineWidth=w;c.strokeStyle=s;c.beginPath()
for(let y=100;y>=top;y-=2)c.lineTo(x0+(2+5*(100-y)/70)*sin((100-y)/40*T-t*T+ph),y)
c.stroke()
}
}
+18
View File
@@ -0,0 +1,18 @@
// ghost bobbing in place, hem rippling, little arms flapping, blinks once per loop
const{sin,PI}=Math,T=2*PI
export default(c,t,i)=>{
const y=sin(t*T)*8,L=66,R=190,hem=x=>196+y+7*sin((x-L)/(R-L)*3*T-t*T)
for(const s of[-1,1]){
c.beginPath();c.ellipse(128+s*68,150+y,20,10,s*(.5+.3*sin(t*T*2)),0,T);c.fill();c.stroke()
}
c.beginPath();c.moveTo(L,hem(L));c.arc(128,104+y,62,PI,0)
for(let x=R;x>=L;x-=2)c.lineTo(x,hem(x))
c.closePath();c.fill();c.stroke()
c.fillStyle='#111'
for(const x of[106,150]){
c.beginPath()
if(i==11||i==12)c.moveTo(x-7,108+y),c.lineTo(x+7,108+y),c.stroke()
else c.ellipse(x,108+y,6.5,10,0,0,T),c.fill(),c.fillStyle='#fff',c.beginPath(),c.arc(x-2,104+y,2.5,0,T),c.fill(),c.fillStyle='#111'
}
c.beginPath();c.ellipse(128,138+y,6,8,0,0,T);c.fill()
}
+19
View File
@@ -0,0 +1,19 @@
// heart thumping with a double beat, pop lines burst out on the big one
const{exp,cos,sin,PI}=Math
const p=(t,m)=>exp(-((((t-m+1.5)%1-.5)/.06)**2))
export const delay=60
export default(c,t)=>{
const s=1+.14*p(t,.1)+.07*p(t,.32),k=p(t,.1)
c.translate(128,140);c.scale(s,s);c.lineWidth/=s
c.beginPath();c.moveTo(0,70)
c.bezierCurveTo(-30,45,-92,10,-92,-30);c.bezierCurveTo(-92,-62,-66,-82,-44,-82);c.bezierCurveTo(-22,-82,-6,-68,0,-50)
c.bezierCurveTo(6,-68,22,-82,44,-82);c.bezierCurveTo(66,-82,92,-62,92,-30);c.bezierCurveTo(92,10,30,45,0,70)
c.closePath();c.fill();c.stroke()
c.beginPath();c.arc(-46,-34,24,PI*1.1,PI*1.45);c.stroke()
if(k<.15)return
for(const[x,a]of[[-46,-140],[-46,-110],[-46,-80],[46,-40],[46,-70],[46,-100]])
for(const[w,s2]of[[12,'#111'],[5,'#fff']]){
const r=a*PI/180;c.lineWidth=w/s;c.strokeStyle=s2;c.beginPath()
c.moveTo(x+cos(r)*56,-38+sin(r)*56);c.lineTo(x+cos(r)*(56+16*k),-38+sin(r)*(56+16*k));c.stroke()
}
}
+5 -7
View File
@@ -13,7 +13,10 @@ h1{font-weight:400;font-size:clamp(22px,5vw,44px);line-height:1.2;margin:8px aut
p{font-size:clamp(16px,3vw,22px);margin-bottom:16px} p{font-size:clamp(16px,3vw,22px);margin-bottom:16px}
iframe{border:0;border-radius:6px;margin-bottom:28px} iframe{border:0;border-radius:6px;margin-bottom:28px}
main{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px;max-width:1400px;margin:auto} main{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px;max-width:1400px;margin:auto}
a{display:flex;align-items:center;justify-content:center;aspect-ratio:1} figure>a{display:flex;align-items:center;justify-content:center;aspect-ratio:1}
figcaption{font-size:14px;margin-top:4px}
figcaption a{color:#888;text-decoration:none;margin:0 4px}
figcaption a:hover{color:#000;text-decoration:underline}
img{width:100%;height:100%;object-fit:contain} img{width:100%;height:100%;object-fit:contain}
@media(max-width:480px){main{grid-template-columns:repeat(3,1fr);gap:10px}} @media(max-width:480px){main{grid-template-columns:repeat(3,1fr);gap:10px}}
</style> </style>
@@ -23,12 +26,7 @@ img{width:100%;height:100%;object-fit:contain}
<p>feel free to hot link them</p> <p>feel free to hot link them</p>
<iframe src="https://github.com/sponsors/multipleof4/button" title="Sponsor multipleof4" height="32" width="114"></iframe> <iframe src="https://github.com/sponsors/multipleof4/button" title="Sponsor multipleof4" height="32" width="114"></iframe>
<main> <main>
<a href="/bird/wave.webp"><img src="/bird/wave.webp" alt="bird wave" width="256" height="256" loading="lazy" decoding="async"></a> <!--gallery-->
<a href="/cat/blink.webp"><img src="/cat/blink.webp" alt="cat blink" width="100" height="67" loading="lazy" decoding="async"></a>
<a href="/cat/tail.webp"><img src="/cat/tail.webp" alt="cat tail" width="256" height="171" loading="lazy" decoding="async"></a>
<a href="/cat/wink.webp"><img src="/cat/wink.webp" alt="cat wink" width="256" height="171" loading="lazy" decoding="async"></a>
<a href="/cat/tail.gif"><img src="/cat/tail.gif" alt="cat tail" width="120" height="90" loading="lazy" decoding="async"></a>
<a href="/heart/wink.gif"><img src="/heart/wink.gif" alt="heart wink" width="128" height="128" loading="lazy" decoding="async"></a>
</main> </main>
</body> </body>
</html> </html>
+935
View File
@@ -6,9 +6,873 @@
"": { "": {
"name": "vibegif.direct-img.link", "name": "vibegif.direct-img.link",
"devDependencies": { "devDependencies": {
"@napi-rs/canvas": "^1.0.13",
"sharp": "^0.35.5",
"vite": "^8.3.5" "vite": "^8.3.5"
} }
}, },
"node_modules/@emnapi/runtime": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz",
"integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz",
"integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.3.4"
}
},
"node_modules/@img/sharp-freebsd-wasm32": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz",
"integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"dependencies": {
"@img/sharp-wasm32": "0.35.5"
},
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz",
"integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz",
"integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz",
"integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz",
"integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz",
"integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz",
"integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz",
"integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz",
"integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz",
"integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz",
"integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz",
"integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.3.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz",
"integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz",
"integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz",
"integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz",
"integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.3.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz",
"integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.3.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz",
"integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz",
"integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.3.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz",
"integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==",
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.11.3"
},
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-webcontainers-wasm32": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz",
"integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/sharp-wasm32": "0.35.5"
},
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz",
"integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz",
"integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz",
"integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@napi-rs/canvas": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.13.tgz",
"integrity": "sha512-b87ugZpQ5ViwqVBkjervxpwa0GmJPsKHLBNoZN6GpjWeHzTbDUGryriq+2DYW2XbB+xCqSIINQysC0/I87nPbw==",
"dev": true,
"license": "MIT",
"workspaces": [
"e2e/*"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"optionalDependencies": {
"@napi-rs/canvas-android-arm64": "1.0.13",
"@napi-rs/canvas-darwin-arm64": "1.0.13",
"@napi-rs/canvas-darwin-x64": "1.0.13",
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.13",
"@napi-rs/canvas-linux-arm64-gnu": "1.0.13",
"@napi-rs/canvas-linux-arm64-musl": "1.0.13",
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.13",
"@napi-rs/canvas-linux-x64-gnu": "1.0.13",
"@napi-rs/canvas-linux-x64-musl": "1.0.13",
"@napi-rs/canvas-win32-arm64-msvc": "1.0.13",
"@napi-rs/canvas-win32-x64-msvc": "1.0.13"
}
},
"node_modules/@napi-rs/canvas-android-arm64": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.13.tgz",
"integrity": "sha512-trn7AaDzQ+yeeA0ezhkC7cB1ZDPtp4UYk0EvB5F8qtYL9jEVWtVrU30WOpI52DszoTxaTsCgf5PP4ZVaOlOIHQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-darwin-arm64": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.13.tgz",
"integrity": "sha512-6Y2djEetL8copxfNK6hg7oig/xqTwOchv19hBOm+zBNdKc/02Z3kjZN5wrLzv/l5O04/bAQS6I/AlQpwBlXgPg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-darwin-x64": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.13.tgz",
"integrity": "sha512-OZoPwwu4TMMg8GTCFBrg8UFExwQTU/55i3uTC/izDat6PHE+ro9dimyQoOboFf91+NY7Pnn70ScR8f8I8+eZXw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.13.tgz",
"integrity": "sha512-dZSYaKROuswU2eUWG5aBKndNVe68elo1YX93xtRax7q658tj8rYE+HwduovWJgs6qwv1r1/CLR60nDeBbyjvyg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.13.tgz",
"integrity": "sha512-W6FzYE8ADHI93In1vi80KBL6btDtAMuj50Ch5ptkWS8xuntyzbR4h5JDUmXoakxZn2yMInksudTEqVSWoFqFGg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.13.tgz",
"integrity": "sha512-+6fQ+rR49hkAa4voh2stLIwQ+4t+gmoseM8Bc53/DlMXb73IKymDPWK9A/NtWeqTW4ijWppYuyuxS2hPI8tzNA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.13.tgz",
"integrity": "sha512-17XEMO3t9RW/TtHtSFf2AkaSWuCWdzbPeidz7zrOFawHxoLSJCnpTI1hN5aUvrZNnkNNmt/vZVJCh0ryUaik3A==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.13.tgz",
"integrity": "sha512-lc48IwknVHBsNzXn9Xs5OkUsRX38Ml3Z1aOybQ51mMEzZckm8+4A0qROlyYHEoBPzr1kL3IU8ZUAqDb7wtdX8w==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-linux-x64-musl": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.13.tgz",
"integrity": "sha512-bGY1r26S2bs8UNFJcPWcCSNdCFbKep5PATsvtgtC9A5u6SRgr2/jJE/RlhHL5m9ZpixNDhVkLY/5h16Kx5V9YA==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.13.tgz",
"integrity": "sha512-2vRPBZoaIvJrUkDhZTE2Hn0YrnpJRCWVM3EJACoBKjuWI6WgPxGtUoRMsxRviCsyks8bKTSFdyw14CellnG2qA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.13.tgz",
"integrity": "sha512-DUlW3lZPz63kYsf8mxgyyZuevhlH0T3/BGP9l+NazhB0F1+gL6D4hqZdnhZpw24DlWmTpATDcyYSZlJOi+DdfQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.155.0", "version": "0.155.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz",
@@ -717,6 +1581,69 @@
"@rolldown/binding-win32-x64-msvc": "1.2.15" "@rolldown/binding-win32-x64-msvc": "1.2.15"
} }
}, },
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp": {
"version": "0.35.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz",
"integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.1.0",
"detect-libc": "^2.1.2",
"semver": "^7.8.5"
},
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.35.5",
"@img/sharp-darwin-x64": "0.35.5",
"@img/sharp-freebsd-wasm32": "0.35.5",
"@img/sharp-libvips-darwin-arm64": "1.3.4",
"@img/sharp-libvips-darwin-x64": "1.3.4",
"@img/sharp-libvips-linux-arm": "1.3.4",
"@img/sharp-libvips-linux-arm64": "1.3.4",
"@img/sharp-libvips-linux-ppc64": "1.3.4",
"@img/sharp-libvips-linux-riscv64": "1.3.4",
"@img/sharp-libvips-linux-s390x": "1.3.4",
"@img/sharp-libvips-linux-x64": "1.3.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4",
"@img/sharp-libvips-linuxmusl-x64": "1.3.4",
"@img/sharp-linux-arm": "0.35.5",
"@img/sharp-linux-arm64": "0.35.5",
"@img/sharp-linux-ppc64": "0.35.5",
"@img/sharp-linux-riscv64": "0.35.5",
"@img/sharp-linux-s390x": "0.35.5",
"@img/sharp-linux-x64": "0.35.5",
"@img/sharp-linuxmusl-arm64": "0.35.5",
"@img/sharp-linuxmusl-x64": "0.35.5",
"@img/sharp-webcontainers-wasm32": "0.35.5",
"@img/sharp-win32-arm64": "0.35.5",
"@img/sharp-win32-ia32": "0.35.5",
"@img/sharp-win32-x64": "0.35.5"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
@@ -744,6 +1671,14 @@
"url": "https://github.com/sponsors/SuperchupuDev" "url": "https://github.com/sponsors/SuperchupuDev"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/vite": { "node_modules/vite": {
"version": "8.3.5", "version": "8.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz",
+4 -1
View File
@@ -5,9 +5,12 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview",
"gen": "node scripts/gen.js"
}, },
"devDependencies": { "devDependencies": {
"@napi-rs/canvas": "^1.0.13",
"sharp": "^0.35.5",
"vite": "^8.3.5" "vite": "^8.3.5"
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+26
View File
@@ -0,0 +1,26 @@
// renders gen/<subject>/<action>.js into public/<subject>/<action>.{webp,gif}
// 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'
import sharp from'sharp'
import{readdirSync as rd,existsSync as ex,mkdirSync as mk,statSync as st}from'fs'
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}`
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)=>{
const c=createCanvas(S,S).getContext('2d')
Object.assign(c,{lineWidth:7,lineCap:'round',lineJoin:'round',strokeStyle:'#111',fillStyle:'#fff'})
draw(c,i/frames,i)
return Buffer.from(c.getImageData(0,0,S,S).data.buffer)
})
mk(dirname(o),{recursive:true})
const img=()=>sharp(Buffer.concat(px),{raw:{width:S,height:S*frames,channels:4,pageHeight:S}})
await img().webp({lossless:true,effort:6,loop:0,delay}).toFile(o+'.webp')
await img().gif({effort:10,loop:0,delay}).toFile(o+'.gif')
console.log(`${id} webp ${st(o+'.webp').size}b gif ${st(o+'.gif').size}b`)
}
+17
View File
@@ -0,0 +1,17 @@
// fills <!--gallery--> in index.html from whatever is in public/
// anything rendered from gen/ gets one tile with webp + gif links, older hand-made files get a tile each
import{readdirSync as rd,readFileSync as rf,existsSync as ex}from'fs'
const walk=d=>rd(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(`${d}/${e.name}`):`${d}/${e.name}`)
const dims=f=>{const b=rf(f);return f.endsWith('.gif')?[b.readUInt16LE(6),b.readUInt16LE(8)]:[b.readUIntLE(24,3)+1,b.readUIntLE(27,3)+1]}
const gallery=()=>{
const seen=new Set
return walk('public').filter(f=>/\.(webp|gif)$/.test(f)).sort().map(f=>{
const id=f.slice(7).replace(/\.\w+$/,''),g=ex(`gen/${id}.js`)
if(g&&seen.has(id))return''
seen.add(id)
const src=g?`/${id}.webp`:f.slice(6),[w,h]=dims('public'+src)
const links=(g?['webp','gif']:[f.split('.').pop()]).map(x=>`<a href="/${id}.${x}">${x}</a>`).join(' ')
return`<figure><a href="${src}"><img src="${src}" alt="${id.replace('/',' ')}" width="${w}" height="${h}" loading="lazy" decoding="async"></a><figcaption>${links}</figcaption></figure>`
}).filter(Boolean).join('\n')
}
export default{plugins:[{name:'gallery',transformIndexHtml:h=>h.replace('<!--gallery-->',gallery())}]}