86 lines
3.1 KiB
JavaScript
86 lines
3.1 KiB
JavaScript
// Generates flat, neutral placeholder SVG images so every image slot on the
|
||
// homepage has a real file on disk that can be swapped later without any
|
||
// code changes. Run with: node scripts/gen-placeholders.mjs
|
||
import { mkdirSync, writeFileSync } from "node:fs";
|
||
import { dirname, join } from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const PUBLIC = join(__dirname, "..", "public", "images");
|
||
|
||
// Two flat, muted tones so placeholders alternate subtly instead of being
|
||
// one flat wall of gray.
|
||
const TONES = ["#EBEDE9", "#E4E7E2"];
|
||
|
||
function icon(cx, cy, s, stroke) {
|
||
// Simple flat "image" glyph: rounded frame + mountain + sun dot.
|
||
return `
|
||
<g transform="translate(${cx - s / 2} ${cy - s / 2})" fill="none" stroke="${stroke}" stroke-width="${Math.max(
|
||
2,
|
||
s * 0.045
|
||
)}" stroke-linejoin="round" stroke-linecap="round">
|
||
<rect x="0" y="0" width="${s}" height="${s * 0.72}" rx="${s * 0.06}" />
|
||
<circle cx="${s * 0.24}" cy="${s * 0.24}" r="${s * 0.07}" />
|
||
<path d="M0 ${s * 0.58} L${s * 0.32} ${s * 0.32} L${s * 0.56} ${s * 0.5} L${s * 0.78} ${s * 0.3} L${s} ${
|
||
s * 0.5
|
||
}" />
|
||
</g>`;
|
||
}
|
||
|
||
function svgPlaceholder({ w, h, label, tone }) {
|
||
const s = Math.min(w, h) * 0.22;
|
||
const cx = w / 2;
|
||
const cy = h / 2 - s * 0.12;
|
||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w}" height="${h}">
|
||
<rect width="${w}" height="${h}" fill="${tone}" />
|
||
${icon(cx, cy, s, "#B9BEB4")}
|
||
<text x="${cx}" y="${cy + s * 0.62 + s * 0.34}" text-anchor="middle" font-family="Arial, sans-serif" font-size="${Math.max(
|
||
11,
|
||
s * 0.16
|
||
)}" fill="#9AA096" letter-spacing="1">${label} · ${w}×${h}</text>
|
||
</svg>`;
|
||
}
|
||
|
||
let toneIndex = 0;
|
||
function nextTone() {
|
||
const t = TONES[toneIndex % TONES.length];
|
||
toneIndex += 1;
|
||
return t;
|
||
}
|
||
|
||
function write(relPath, w, h, label) {
|
||
const full = join(PUBLIC, relPath);
|
||
mkdirSync(dirname(full), { recursive: true });
|
||
writeFileSync(full, svgPlaceholder({ w, h, label, tone: nextTone() }), "utf8");
|
||
console.log("wrote", relPath);
|
||
}
|
||
|
||
// Hero slider — full width slides
|
||
for (let i = 1; i <= 3; i++) write(`hero/slide-${i}.svg`, 1600, 560, "HERO SLIDE");
|
||
|
||
// Special sale carousel — first card is a banner image, rest are product shots
|
||
write("special-sale/banner.svg", 480, 620, "SALE BANNER");
|
||
for (let i = 1; i <= 8; i++) write(`special-sale/product-${i}.svg`, 480, 620, "SALE ITEM");
|
||
|
||
// Categories
|
||
const categories = ["girls", "boys", "baby", "party", "sets", "outerwear"];
|
||
for (const key of categories) write(`categories/${key}.svg`, 520, 600, "CATEGORY");
|
||
|
||
// Two-banner section
|
||
write("banners/banner-1.svg", 770, 360, "BANNER");
|
||
write("banners/banner-2.svg", 770, 360, "BANNER");
|
||
|
||
// Single full-width banner
|
||
write("banners/full-banner.svg", 1600, 420, "FULL BANNER");
|
||
|
||
// Top selling products carousel
|
||
for (let i = 1; i <= 12; i++) write(`products/product-${i}.svg`, 480, 620, "PRODUCT");
|
||
|
||
// About us
|
||
write("about/about.svg", 720, 640, "ABOUT US");
|
||
|
||
// Blog carousel
|
||
for (let i = 1; i <= 4; i++) write(`blog/post-${i}.svg`, 520, 380, "BLOG");
|
||
|
||
console.log("Done. Placeholder images generated in public/images/");
|