Initial commit: Amber Valley Community Hub fallback site
Vite + React 18 + Tailwind CSS community events video library with 42 topic-matched thumbnails, auth modal that always fails, and Docker/nginx setup on port 8081. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
98
scripts/generate-thumbnails.js
Normal file
98
scripts/generate-thumbnails.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const outDir = join(__dirname, '..', 'public', 'thumbnails');
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
// Favicon
|
||||
const favicon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#d97706"/>
|
||||
<text x="16" y="22" text-anchor="middle" fill="white" font-size="18" font-family="sans-serif" font-weight="bold">AV</text>
|
||||
</svg>`;
|
||||
writeFileSync(join(outDir, 'favicon.svg'), favicon);
|
||||
|
||||
// Each entry: [index, search keywords] — keywords chosen to match the video title
|
||||
const thumbnails = [
|
||||
[1, 'jazz,concert,stage'],
|
||||
[2, 'pasta,cooking,italian'],
|
||||
[3, 'soccer,football,tournament'],
|
||||
[4, 'watercolor,painting,art'],
|
||||
[5, 'python,programming,laptop'],
|
||||
[6, 'garden,spring,flowers'],
|
||||
[7, 'book,reading,library'],
|
||||
[8, 'acoustic,guitar,microphone'],
|
||||
[9, 'thai,food,cooking'],
|
||||
[10, 'basketball,court,game'],
|
||||
[11, 'oil,painting,landscape'],
|
||||
[12, 'web,development,code'],
|
||||
[13, 'rose,pruning,garden'],
|
||||
[14, 'science,fiction,books'],
|
||||
[15, 'blues,folk,music'],
|
||||
[16, 'pastry,baking,french'],
|
||||
[17, 'table,tennis,pingpong'],
|
||||
[18, 'digital,art,illustration'],
|
||||
[19, 'cloud,computing,server'],
|
||||
[20, 'compost,soil,garden'],
|
||||
[21, 'mystery,detective,book'],
|
||||
[22, 'piano,recital,concert'],
|
||||
[23, 'sushi,japanese,food'],
|
||||
[24, 'volleyball,beach,sport'],
|
||||
[25, 'pottery,ceramics,clay'],
|
||||
[26, 'artificial,intelligence,robot'],
|
||||
[27, 'herbs,garden,plants'],
|
||||
[28, 'classic,literature,novel'],
|
||||
[29, 'latin,dance,salsa'],
|
||||
[30, 'bread,baking,dough'],
|
||||
[31, 'badminton,racket,sport'],
|
||||
[32, 'sketch,drawing,pencil'],
|
||||
[33, 'cybersecurity,hacker,lock'],
|
||||
[34, 'winter,vegetables,growing'],
|
||||
[35, 'fantasy,tolkien,books'],
|
||||
[36, 'choir,singing,holiday'],
|
||||
[37, 'curry,indian,spices'],
|
||||
[38, 'running,marathon,race'],
|
||||
[39, 'photography,camera,walk'],
|
||||
[40, 'blockchain,crypto,technology'],
|
||||
[41, 'indoor,plants,houseplant'],
|
||||
[42, 'poetry,reading,writing'],
|
||||
];
|
||||
|
||||
async function downloadImage(index, keywords) {
|
||||
const num = String(index).padStart(2, '0');
|
||||
const filePath = join(outDir, `thumb-${num}.jpg`);
|
||||
|
||||
// loremflickr serves real Creative Commons photos matching keywords
|
||||
// lock parameter ensures deterministic results
|
||||
const url = `https://loremflickr.com/640/360/${keywords}?lock=${index}`;
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
const res = await fetch(url, { redirect: 'follow' });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const buffer = Buffer.from(await res.arrayBuffer());
|
||||
if (buffer.length < 1000) throw new Error('Image too small, likely error');
|
||||
writeFileSync(filePath, buffer);
|
||||
console.log(` [${num}] OK (${(buffer.length / 1024).toFixed(0)} KB) — ${keywords}`);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (attempt < 2) {
|
||||
console.log(` [${num}] Retry ${attempt + 1}...`);
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
} else {
|
||||
console.error(` [${num}] FAILED: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Downloading 42 topic-matched thumbnails from loremflickr.com...\n');
|
||||
|
||||
const BATCH = 4;
|
||||
for (let i = 0; i < thumbnails.length; i += BATCH) {
|
||||
const batch = thumbnails.slice(i, i + BATCH);
|
||||
await Promise.all(batch.map(([idx, kw]) => downloadImage(idx, kw)));
|
||||
}
|
||||
|
||||
console.log('\nDone! 42 topic-matched thumbnails in public/thumbnails/');
|
||||
Reference in New Issue
Block a user