mirror of
https://github.com/direct-img/vibegif.direct-img.link.git
synced 2026-10-29 11:44:51 +00:00
Add image search metadata: CC0 license data, sitemap, robots, 404 page
Every tile image gets ImageObject license data so search engines can list it as Creative Commons, the build writes sitemap.xml with all tile images, robots.txt points at it, unknown paths get a real 404 instead of the gallery, and the page gets a descriptive title, canonical, large image previews and an og.png collage for link previews. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-2
@@ -3,9 +3,21 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>vibegif</title>
|
<title>vibegif · <!--count--> free public domain gifs and animated webp</title>
|
||||||
<link rel="icon" type="image/webp" href="/favicon.webp">
|
<link rel="icon" type="image/webp" href="/favicon.webp">
|
||||||
<meta name="description" content="public domain gallery of <!--count--> gifs and animated webp">
|
<link rel="canonical" href="https://vibegif.direct-img.link/">
|
||||||
|
<meta name="description" content="public domain gallery of <!--count--> gifs and animated webp, free to use and hot link (CC0)">
|
||||||
|
<meta name="robots" content="max-image-preview:large">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:site_name" content="vibegif">
|
||||||
|
<meta property="og:url" content="https://vibegif.direct-img.link/">
|
||||||
|
<meta property="og:title" content="vibegif · <!--count--> free public domain gifs and animated webp">
|
||||||
|
<meta property="og:description" content="public domain gallery of <!--count--> gifs and animated webp, free to use and hot link (CC0)">
|
||||||
|
<meta property="og:image" content="https://vibegif.direct-img.link/og.png">
|
||||||
|
<meta property="og:image:width" content="1200">
|
||||||
|
<meta property="og:image:height" content="630">
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<!--ld-->
|
||||||
<script>try{localStorage.bg&&document.documentElement.classList.add("dark")}catch{}</script>
|
<script>try{localStorage.bg&&document.documentElement.classList.add("dark")}catch{}</script>
|
||||||
<style>
|
<style>
|
||||||
:root{--bg:#fff;--fg:#000;--mute:#888}
|
:root{--bg:#fff;--fg:#000;--mute:#888}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>not found · vibegif</title>
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<link rel="icon" type="image/webp" href="/favicon.webp">
|
||||||
|
<script>try{localStorage.bg&&document.documentElement.classList.add("dark")}catch{}</script>
|
||||||
|
<style>
|
||||||
|
:root{--bg:#fff;--fg:#000;--mute:#888}
|
||||||
|
.dark{--bg:#000;--fg:#fff;--mute:#777}
|
||||||
|
@font-face{font-family:"Stain";src:url("https://cdn.jsdelivr.net/gh/multipleof4/stain.otf@master/dist/Stain.otf") format("opentype");font-display:swap}
|
||||||
|
body{background:var(--bg);color:var(--fg);font-family:"Stain",sans-serif;text-align:center;margin:0;padding:15vh 16px}
|
||||||
|
h1{font-weight:400;font-size:clamp(22px,5vw,44px);margin:16px 0}
|
||||||
|
a{color:var(--mute);font-size:clamp(16px,3vw,22px)}
|
||||||
|
a:hover{color:var(--fg)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="/ghost/float.webp" width="180" height="180" alt="ghost float">
|
||||||
|
<h1>nothing here</h1>
|
||||||
|
<a href="/">back to the gallery</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://vibegif.direct-img.link/sitemap.xml
|
||||||
+28
-9
@@ -4,13 +4,16 @@
|
|||||||
// hand-made files that have no gen file go in the first section
|
// hand-made files that have no gen file go in the first section
|
||||||
// only files inside a subject folder are tiles, so root files like favicon.webp stay out
|
// only files inside a subject folder are tiles, so root files like favicon.webp stay out
|
||||||
// tile images get ?v=<hash> so browsers drop old copies when a file changes, links stay clean for hotlinking
|
// tile images get ?v=<hash> so browsers drop old copies when a file changes, links stay clean for hotlinking
|
||||||
|
// <!--ld--> gets CC0 license data for every tile image, so image search can list them under creative commons
|
||||||
|
// and the build writes sitemap.xml with the page and its tile images (google reads up to 1000 images per page)
|
||||||
import{readdirSync as rd,readFileSync as rf}from'fs'
|
import{readdirSync as rd,readFileSync as rf}from'fs'
|
||||||
import{createHash}from'crypto'
|
import{createHash}from'crypto'
|
||||||
|
const site='https://vibegif.direct-img.link',cc0='https://creativecommons.org/publicdomain/zero/1.0/'
|
||||||
const sections=['bw','color','anime','manga','mini','minicolor']
|
const sections=['bw','color','anime','manga','mini','minicolor']
|
||||||
const hidden=['cat/tail.gif'] // same drawing as cat/wag.gif, kept so old links still work
|
const hidden=['cat/tail.gif'] // same drawing as cat/wag.gif, kept so old links still work
|
||||||
const walk=d=>rd(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(`${d}/${e.name}`):`${d}/${e.name}`)
|
const walk=d=>rd(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(`${d}/${e.name}`):`${d}/${e.name}`)
|
||||||
const dims=(b,x)=>x=='gif'?[b.readUInt16LE(6),b.readUInt16LE(8)]:[b.readUIntLE(24,3)+1,b.readUIntLE(27,3)+1]
|
const dims=(b,x)=>x=='gif'?[b.readUInt16LE(6),b.readUInt16LE(8)]:[b.readUIntLE(24,3)+1,b.readUIntLE(27,3)+1]
|
||||||
const gallery=()=>{
|
const tiles=()=>{
|
||||||
const sec={},g=Object.fromEntries(sections.map(s=>[s,{}]))
|
const sec={},g=Object.fromEntries(sections.map(s=>[s,{}]))
|
||||||
for(const f of walk('gen').filter(f=>f.endsWith('.js'))){const[,s,...r]=f.split('/');sec[r.join('/').slice(0,-3)]=s}
|
for(const f of walk('gen').filter(f=>f.endsWith('.js'))){const[,s,...r]=f.split('/');sec[r.join('/').slice(0,-3)]=s}
|
||||||
for(const f of walk('public').filter(f=>/^public\/[^/]+\/.+\.(webp|gif)$/.test(f)).sort()){
|
for(const f of walk('public').filter(f=>/^public\/[^/]+\/.+\.(webp|gif)$/.test(f)).sort()){
|
||||||
@@ -18,12 +21,28 @@ const gallery=()=>{
|
|||||||
if(!hidden.includes(p))(g[sec[id]??sections[0]][id]??={})[x]=f
|
if(!hidden.includes(p))(g[sec[id]??sections[0]][id]??={})[x]=f
|
||||||
}
|
}
|
||||||
return sections.map(s=>Object.entries(g[s]).map(([id,fm])=>{
|
return sections.map(s=>Object.entries(g[s]).map(([id,fm])=>{
|
||||||
const x=fm.webp?'webp':'gif',b=rf(fm[x]),[w,h]=dims(b,x),v=createHash('md5').update(b).digest('hex').slice(0,8)
|
const x=fm.webp?'webp':'gif',b=rf(fm[x]),[w,h]=dims(b,x)
|
||||||
const links=['webp','gif'].filter(k=>fm[k]).map(k=>`<a href="/${id}.${k}">${k}</a>`).join(' ')
|
return{id,fm,x,w,h,src:`/${id}.${x}?v=${createHash('md5').update(b).digest('hex').slice(0,8)}`}
|
||||||
return`<figure><a href="/${id}.${x}"><img src="/${id}.${x}?v=${v}" alt="${id.replace('/',' ')}" width="${w}" height="${h}" loading="lazy" decoding="async"></a><figcaption>${links}</figcaption></figure>`
|
}))
|
||||||
}).join('\n')).filter(Boolean).join('\n<hr>\n')
|
|
||||||
}
|
}
|
||||||
export default{plugins:[{name:'gallery',transformIndexHtml:h=>{
|
const figure=t=>{
|
||||||
const g=gallery()
|
const links=['webp','gif'].filter(k=>t.fm[k]).map(k=>`<a href="/${t.id}.${k}">${k}</a>`).join(' ')
|
||||||
return h.replace('<!--gallery-->',g).replaceAll('<!--count-->',g.split('<figure>').length-1)
|
return`<figure><a href="/${t.id}.${t.x}"><img src="${t.src}" alt="${t.id.replace('/',' ')}" width="${t.w}" height="${t.h}" loading="lazy" decoding="async"></a><figcaption>${links}</figcaption></figure>`
|
||||||
}}]}
|
}
|
||||||
|
const ld=all=>`<script type="application/ld+json">${JSON.stringify({'@context':'https://schema.org','@graph':
|
||||||
|
all.map(t=>({'@type':'ImageObject',contentUrl:site+t.src,license:cc0,acquireLicensePage:site+'/'}))})}</script>`
|
||||||
|
const sitemap=all=>`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
|
||||||
|
<url><loc>${site}/</loc><lastmod>${new Date().toISOString().slice(0,10)}</lastmod>
|
||||||
|
${all.map(t=>`<image:image><image:loc>${site+t.src}</image:loc></image:image>`).join('\n')}
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
|
`
|
||||||
|
export default{plugins:[{name:'gallery',
|
||||||
|
transformIndexHtml:h=>{
|
||||||
|
const s=tiles(),all=s.flat()
|
||||||
|
return h.replace('<!--gallery-->',s.map(ts=>ts.map(figure).join('\n')).filter(Boolean).join('\n<hr>\n'))
|
||||||
|
.replace('<!--ld-->',ld(all)).replaceAll('<!--count-->',all.length)
|
||||||
|
},
|
||||||
|
generateBundle(){this.emitFile({type:'asset',fileName:'sitemap.xml',source:sitemap(tiles().flat())})}
|
||||||
|
}]}
|
||||||
|
|||||||
Reference in New Issue
Block a user