File: //tmp/generate-static-campaign-hub.mjs
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
const webroot = process.env.DATEWEB_WEBROOT || process.env.DATEWEB_PUBLIC_HTML || '/home/dateweb.de/public_html';
const site = 'https://dateweb.de';
const route = path.join(webroot, 'kampagnen');
fs.mkdirSync(route, { recursive: true });
const guides = [
['Dating-Trends 2026', '/ratgeber/dating-trends-2026/', 'guide_dating_trends_2026_launch', 'dating_trends_2026', 'Clear Coding, Dating-Fatigue, KI mit Grenzen und sichere erste Treffen.'],
['Dating-Fatigue überwinden', '/ratgeber/dating-fatigue/', 'guide_dating_fatigue_launch', 'dating_fatigue', 'Weniger Swipen, weniger Rauschen und wieder mehr echte Begegnung.'],
['Ghosting beim Online-Dating', '/ratgeber/ghosting-online-dating/', 'guide_ghosting_launch', 'ghosting_online_dating', 'Funkstille ruhig einordnen und verbindlicher weiterdaten.'],
['Dating ab 40', '/ratgeber/dating-ab-40/', 'guide_dating_ab_40_launch', 'dating_ab_40', 'Profil, Erwartungen und erste Treffen für Singles ab 40 klarer gestalten.'],
['Dating nach Trennung', '/ratgeber/dating-nach-trennung/', 'guide_dating_nach_trennung_launch', 'dating_nach_trennung', 'Ruhig neu starten, ohne alte Muster mitzunehmen.'],
];
const cities = [
['Dating in Frankfurt', '/dating/frankfurt/', 'local_dating_frankfurt_launch', 'dating_frankfurt', 'After-Work, Museumsufer und kurze klare Treffen für volle Kalender.'],
['Dating in München', '/dating/muenchen/', 'local_dating_muenchen_launch', 'dating_muenchen', 'Klare Absicht zwischen Karriere, Alltag und sicheren Treffpunkten.'],
['Dating in Köln', '/dating/koeln/', 'local_dating_koeln_launch', 'dating_koeln', 'Locker ins Gespräch kommen, aber mit konkretem nächsten Schritt.'],
['Dating in Düsseldorf', '/dating/duesseldorf/', 'local_dating_duesseldorf_launch', 'dating_duesseldorf', 'Stil, Rhein-Nähe und klare Absicht verbinden.'],
['Dating in Stuttgart', '/dating/stuttgart/', 'local_dating_stuttgart_launch', 'dating_stuttgart', 'Volle Kalender und Qualitätsanspruch besser verbinden.'],
];
function esc(value) {
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
}
function campaignUrl(pathname, campaign, source, medium, keyword) {
const url = new URL(pathname, site);
url.searchParams.set('mtm_campaign', campaign);
url.searchParams.set('mtm_source', source);
url.searchParams.set('mtm_medium', medium);
url.searchParams.set('mtm_keyword', keyword);
return url.toString();
}
function cards(items, type) {
return items.map(([title, pathname, campaign, keyword, description]) => {
const newsletter = campaignUrl(pathname, campaign, 'newsletter', 'owned', keyword);
const social = campaignUrl(pathname, campaign, 'social', 'organic', keyword);
const community = campaignUrl(pathname, campaign, 'community', 'organic', keyword);
const feed = campaignUrl(pathname, campaign, 'feed', 'owned', keyword);
const copyA = `${title}: ${description}`;
const copyB = `Für Singles ab 30: ${description} dateweb.de zeigt den nächsten konkreten Schritt.`;
return `<article class="campaign-card" data-growth-marker="campaign_hub_${esc(type)}_card"><h3>${esc(title)}</h3><p>${esc(description)}</p><dl><dt>Newsletter</dt><dd><a href="${esc(newsletter)}">${esc(newsletter)}</a></dd><dt>Social</dt><dd><a href="${esc(social)}">${esc(social)}</a></dd><dt>Community</dt><dd><a href="${esc(community)}">${esc(community)}</a></dd><dt>Feed</dt><dd><a href="${esc(feed)}">${esc(feed)}</a></dd></dl><div class="copy"><strong>Copy 1</strong><p>${esc(copyA)}</p><strong>Copy 2</strong><p>${esc(copyB)}</p></div></article>`;
}).join('\n');
}
const schema = {
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: 'dateweb.de Kampagnen-Hub',
description: 'Messbare Kampagnenlinks und Copy-Varianten für dateweb.de Ratgeber und lokale Dating-Seiten.',
url: `${site}/kampagnen/`,
hasPart: [...guides, ...cities].map(([name, pathname]) => ({ '@type': 'WebPage', name, url: `${site}${pathname}` })),
};
const html = `<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>dateweb.de Kampagnen-Hub: Ratgeber und lokale Dating-Links</title>
<meta name="description" content="Messbare dateweb.de Kampagnenlinks für Ratgeber, lokale Dating-Seiten, Newsletter, Social und Community-Ausspielung.">
<link rel="canonical" href="${site}/kampagnen/">
<meta property="og:type" content="website">
<meta property="og:title" content="dateweb.de Kampagnen-Hub">
<meta property="og:description" content="Ratgeber- und lokale Dating-Kampagnen mit Matomo-Parametern und Copy-Varianten.">
<meta property="og:url" content="${site}/kampagnen/">
<meta property="og:image" content="${site}/media/generated/dateweb-hero-editorial-optimized.webp">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="dateweb.de Kampagnen-Hub">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image:alt" content="dateweb.de Kampagnen-Hub">
<style>
:root{--ink:#1f2423;--muted:#5f6a66;--line:#e7d8c7;--paper:#fffaf2;--teal:#1f716c;--white:#fffefb}*{box-sizing:border-box}body{margin:0;color:var(--ink);font-family:Georgia,'Times New Roman',serif;background:linear-gradient(135deg,#fffaf2,#edf8f4);line-height:1.62}main{width:min(1120px,calc(100% - 32px));margin:auto;padding:42px 0 70px}.brand{font:900 15px/1 ui-sans-serif,system-ui,sans-serif;text-decoration:none}h1{font-size:clamp(42px,7vw,78px);line-height:.92;letter-spacing:-.06em;margin:32px 0 18px}.lead{font-size:22px;color:#3f4945;max-width:820px}.meta{font:800 13px/1 ui-sans-serif,system-ui,sans-serif;color:var(--teal);letter-spacing:.08em;text-transform:uppercase}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin:24px 0 42px}.campaign-card{background:#fff;border:1px solid var(--line);border-radius:24px;padding:22px;box-shadow:0 12px 34px rgba(42,35,24,.08)}h2{font-size:clamp(30px,4vw,48px);line-height:1;letter-spacing:-.04em;margin:38px 0 14px}h3{font-size:25px;line-height:1.08;margin:0 0 8px}.campaign-card p{color:var(--muted);margin:.35rem 0 1rem}dl{display:grid;gap:9px;margin:18px 0}dt{font:900 12px/1 ui-sans-serif,system-ui,sans-serif;color:var(--teal);text-transform:uppercase;letter-spacing:.06em}dd{margin:0;overflow-wrap:anywhere;font:13px/1.45 ui-sans-serif,system-ui,sans-serif}.copy{border-top:1px solid var(--line);padding-top:14px}.copy strong{font:900 12px/1 ui-sans-serif,system-ui,sans-serif}.cta{display:inline-flex;padding:14px 20px;border-radius:999px;background:var(--ink);color:#fff;text-decoration:none;font:800 15px/1 ui-sans-serif,system-ui,sans-serif}@media(max-width:780px){.grid{grid-template-columns:1fr}}
</style>
<script type="application/ld+json">${JSON.stringify(schema).replaceAll('</', '<\\/')}</script>
</head>
<body data-growth-marker="campaign_hub_growth">
<main>
<a class="brand" href="/">dateweb.de</a>
<p class="meta">campaign_hub_growth</p>
<h1>Kampagnen-Hub für messbare Dating-Impulse</h1>
<p class="lead">Diese Seite bündelt die wichtigsten Ratgeber- und lokalen Dating-Links mit Matomo-Parametern. Sie macht Ausspielung einfacher und sorgt dafür, dass Newsletter-, Social-, Community- und Feed-Klicks im Growth-Report sichtbar werden.</p>
<a class="cta" href="/feed/?from=campaign_hub_feed">Zum öffentlichen Feed</a>
<h2>Ratgeber-Kampagnen</h2>
<section class="grid" data-growth-marker="campaign_hub_guide_links">${cards(guides, 'guide')}</section>
<h2>Lokale Kampagnen</h2>
<section class="grid" data-growth-marker="campaign_hub_local_links">${cards(cities, 'local')}</section>
</main>
</body>
</html>`;
fs.writeFileSync(path.join(route, 'index.html'), html, 'utf8');
fs.writeFileSync(path.join(route, 'index.php'), `<?php\nheader_remove('X-Powered-By');\nheader('X-Frame-Options: SAMEORIGIN');\nheader('X-Content-Type-Options: nosniff');\nheader('Referrer-Policy: strict-origin-when-cross-origin');\nheader('Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()');\nheader('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');\nheader('X-Permitted-Cross-Domain-Policies: none');\nheader('Content-Type: text/html; charset=UTF-8');\nreadfile(__DIR__ . '/index.html');\n`, 'utf8');
const sitemapPath = path.join(webroot, 'sitemap.xml');
if (fs.existsSync(sitemapPath)) {
let sitemap = fs.readFileSync(sitemapPath, 'utf8');
const url = `${site}/kampagnen/`;
if (!sitemap.includes(url)) {
sitemap = sitemap.replace('</urlset>', ` <url>\n <loc>${url}</loc>\n <changefreq>weekly</changefreq>\n <priority>0.64</priority>\n </url>\n</urlset>`);
fs.writeFileSync(sitemapPath, sitemap, 'utf8');
console.log('Added campaign hub URL to sitemap.xml');
}
}
console.log('generated campaign hub: /kampagnen/');