@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* PWA Icon Generator Script
|
||||
* Generiert Placeholder-Icons für die PWA
|
||||
*
|
||||
* Verwendung: node tools/generate-pwa-icons.mjs
|
||||
*
|
||||
* Für echte Icons: Ersetze die generierten Dateien mit deinen
|
||||
* eigenen Icons oder nutze https://www.pwabuilder.com/imageGenerator
|
||||
*/
|
||||
|
||||
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ICONS_DIR = join(__dirname, '../public/assets/icons');
|
||||
|
||||
const SIZES = [72, 96, 128, 144, 152, 192, 384, 512];
|
||||
const BRAND_COLOR = '#C2410C';
|
||||
const BG_COLOR = '#ffffff';
|
||||
|
||||
// Stelle sicher dass der Ordner existiert
|
||||
if (!existsSync(ICONS_DIR)) {
|
||||
mkdirSync(ICONS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert ein einfaches SVG-Icon als Placeholder
|
||||
*/
|
||||
function generateSvgIcon(size) {
|
||||
const padding = size * 0.15;
|
||||
const innerSize = size - (padding * 2);
|
||||
const fontSize = size * 0.35;
|
||||
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
|
||||
<rect width="${size}" height="${size}" fill="${BG_COLOR}" rx="${size * 0.15}"/>
|
||||
<rect x="${padding}" y="${padding}" width="${innerSize}" height="${innerSize}" fill="${BRAND_COLOR}" rx="${innerSize * 0.1}"/>
|
||||
<text x="50%" y="52%" dominant-baseline="middle" text-anchor="middle"
|
||||
font-family="Arial, sans-serif" font-weight="bold" font-size="${fontSize}px" fill="white">
|
||||
L&B
|
||||
</text>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert SVG zu PNG mit Canvas (vereinfachte Base64 Version)
|
||||
* Hinweis: Für echte PNG-Generierung würde man sharp oder canvas npm packages verwenden
|
||||
*/
|
||||
function generatePlaceholderPng(size) {
|
||||
// Da wir keine externen Dependencies haben, generieren wir eine Info-Datei
|
||||
const infoContent = `PWA Icon Placeholder - ${size}x${size}px
|
||||
|
||||
WICHTIG: Ersetze diese Datei mit einem echten PNG-Icon!
|
||||
|
||||
Empfohlene Tools:
|
||||
- PWA Builder: https://www.pwabuilder.com/imageGenerator
|
||||
- Maskable App: https://maskable.app/editor
|
||||
- Real Favicon Generator: https://realfavicongenerator.net/
|
||||
|
||||
Icon-Anforderungen:
|
||||
- Größe: ${size}x${size} Pixel
|
||||
- Format: PNG mit Transparenz
|
||||
- Safe Zone für Maskable: 80% des Icons sollte im inneren Kreis sein
|
||||
`;
|
||||
return infoContent;
|
||||
}
|
||||
|
||||
// Generiere SVG Icons (können direkt verwendet werden)
|
||||
console.log('🎨 Generiere PWA Icons...\n');
|
||||
|
||||
SIZES.forEach(size => {
|
||||
const svgPath = join(ICONS_DIR, `icon-${size}x${size}.svg`);
|
||||
const svg = generateSvgIcon(size);
|
||||
writeFileSync(svgPath, svg);
|
||||
console.log(` ✓ icon-${size}x${size}.svg`);
|
||||
});
|
||||
|
||||
// Erstelle eine README
|
||||
const readme = `# PWA Icons
|
||||
|
||||
Diese SVG-Icons wurden automatisch generiert und dienen als Placeholder.
|
||||
|
||||
## Für die Produktion:
|
||||
|
||||
1. Erstelle ein quadratisches Logo (mindestens 512x512px)
|
||||
2. Gehe zu https://www.pwabuilder.com/imageGenerator
|
||||
3. Lade dein Logo hoch
|
||||
4. Lade die generierten Icons herunter
|
||||
5. Ersetze die SVG-Dateien hier mit den PNG-Dateien
|
||||
|
||||
## Benötigte Größen:
|
||||
${SIZES.map(s => `- icon-${s}x${s}.png`).join('\n')}
|
||||
|
||||
## Icon-Tipps:
|
||||
- Verwende PNG mit Transparenz
|
||||
- Halte wichtige Elemente in der "Safe Zone" (innere 80%)
|
||||
- Teste mit https://maskable.app/editor
|
||||
|
||||
## Schnelle Alternative:
|
||||
Die SVG-Icons funktionieren auch, aber PNGs sind kompatibler.
|
||||
Ändere in manifest.webmanifest die Endungen von .png zu .svg.
|
||||
`;
|
||||
|
||||
writeFileSync(join(ICONS_DIR, 'README.md'), readme);
|
||||
|
||||
console.log('\n✅ PWA Icons generiert!\n');
|
||||
console.log('📝 Nächste Schritte:');
|
||||
console.log(' 1. Öffne public/assets/icons/README.md für Anweisungen');
|
||||
console.log(' 2. Ersetze die SVGs mit echten PNGs für beste Kompatibilität');
|
||||
console.log(' 3. Oder ändere manifest.webmanifest zu .svg Endungen\n');
|
||||
@@ -0,0 +1,125 @@
|
||||
import { writeFileSync, existsSync, readFileSync, mkdirSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* Domain setzen – per ENV überschreibbar:
|
||||
* BASE_URL=https://Leonards & Brandenburger IT.de npm run build
|
||||
*/
|
||||
const BASE_URL = (process.env.BASE_URL || "https://Leonards & Brandenburger IT.de").replace(/\/+$/, "");
|
||||
|
||||
/**
|
||||
* Statische, öffentliche Routen (aus deiner Liste, aber ohne Admin/Auth/Preview).
|
||||
* Passe diese Liste an, falls du noch mehr veröffentlichen willst.
|
||||
*/
|
||||
const STATIC_ROUTES = [
|
||||
"/", // Home
|
||||
"/services",
|
||||
"/about",
|
||||
"/contact",
|
||||
"/imprint",
|
||||
"/process",
|
||||
"/faq",
|
||||
"/policy",
|
||||
"/booking",
|
||||
"/survey",
|
||||
"/it-services"
|
||||
];
|
||||
|
||||
/**
|
||||
* Dynamische Services: /services/:slug
|
||||
* Lies optionale Slugs aus src/data/service-slugs.json
|
||||
* Formatbeispiel:
|
||||
* { "slugs": ["one-pager","all-in-one","large-website","seo-optimization","full-stack-development"] }
|
||||
*/
|
||||
function readServiceSlugs() {
|
||||
try {
|
||||
const p = "src/data/service-slugs.json";
|
||||
if (!existsSync(p)) return [];
|
||||
const json = JSON.parse(readFileSync(p, "utf8"));
|
||||
const arr = Array.isArray(json) ? json : json.slugs;
|
||||
return Array.isArray(arr) ? arr.filter(Boolean) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const serviceSlugs = readServiceSlugs();
|
||||
const DYNAMIC_ROUTES = serviceSlugs.map(slug => `/services/${slug}`);
|
||||
|
||||
/**
|
||||
* Final: Alle URLs, doppelte raus, sortiert
|
||||
*/
|
||||
const allPaths = Array.from(new Set([...STATIC_ROUTES, ...DYNAMIC_ROUTES]));
|
||||
allPaths.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
/**
|
||||
* lastmod: heute im YYYY-MM-DD
|
||||
*/
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
/**
|
||||
* Prioritäten & changefreq: simple Heuristik
|
||||
*/
|
||||
function getMeta(path) {
|
||||
if (path === "/") return { priority: "1.0", changefreq: "weekly" };
|
||||
if (path.startsWith("/services/")) return { priority: "0.7", changefreq: "monthly" };
|
||||
if (path === "/services") return { priority: "0.8", changefreq: "weekly" };
|
||||
return { priority: "0.6", changefreq: "monthly" };
|
||||
}
|
||||
|
||||
/**
|
||||
* sitemap.xml bauen (ohne externes Paket)
|
||||
*/
|
||||
function buildSitemapXml() {
|
||||
const urlsXml = allPaths.map(p => {
|
||||
const { priority, changefreq } = getMeta(p);
|
||||
const loc = `${BASE_URL}${p === "/" ? "/" : p}`;
|
||||
return [
|
||||
" <url>",
|
||||
` <loc>${loc}</loc>`,
|
||||
` <changefreq>${changefreq}</changefreq>`,
|
||||
` <priority>${priority}</priority>`,
|
||||
` <lastmod>${today}</lastmod>`,
|
||||
" </url>"
|
||||
].join("\n");
|
||||
}).join("\n");
|
||||
|
||||
return [
|
||||
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||
`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`,
|
||||
urlsXml,
|
||||
`</urlset>`
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* robots.txt bauen (mit absoluter Sitemap-URL)
|
||||
*/
|
||||
function buildRobotsTxt() {
|
||||
return [
|
||||
`User-agent: *`,
|
||||
`Allow: /`,
|
||||
``,
|
||||
`Sitemap: ${BASE_URL}/sitemap.xml`
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dateien nach src/ schreiben (werden per assets ins Webroot kopiert)
|
||||
*/
|
||||
function ensureDirFor(filePath) {
|
||||
const dir = filePath.split("/").slice(0, -1).join("/");
|
||||
if (dir && !existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function writeFile(path, content) {
|
||||
ensureDirFor(path);
|
||||
writeFileSync(path, content, "utf8");
|
||||
console.log(`✓ ${path} geschrieben`);
|
||||
}
|
||||
|
||||
writeFile("src/sitemap.xml", buildSitemapXml());
|
||||
writeFile("src/robots.txt", buildRobotsTxt());
|
||||
Reference in New Issue
Block a user