${escapeHtml(item.title)}
${item.dek ? `${escapeHtml(item.dek)}
` : ''}${escapeHtml(item.summary)}
Read the full briefimport fs from 'node:fs/promises'; import path from 'node:path'; import Parser from 'rss-parser'; import OpenAI from 'openai'; import sharp from 'sharp'; import { FEEDS } from './feeds.js'; const ROOT = path.resolve(process.cwd()); const OUT_DIR = path.join(ROOT, 'public'); const ARTICLES_DIR = path.join(OUT_DIR, 'articles'); const SECTIONS_DIR = path.join(OUT_DIR, 'sections'); const IMAGES_DIR = path.join(OUT_DIR, 'images'); const STATE_FILE = path.join(OUT_DIR, 'articles-state.json'); const IMAGE_META_FILE = path.join(OUT_DIR, 'image-meta.json'); const MAX_ITEMS_PER_FEED = Number(process.env.MAX_ITEMS_PER_FEED || 8); const MAX_HOMEPAGE_ITEMS = Number(process.env.MAX_HOMEPAGE_ITEMS || 36); const MODEL = process.env.OPENAI_MODEL || 'gpt-4.1-mini'; const SITE_NAME = process.env.SITE_NAME || 'Signal Ledger'; const SITE_DOMAIN = process.env.SITE_DOMAIN || 'signalledger.nl'; const SITE_URL = `https://${SITE_DOMAIN}`; const SITE_TAGLINE = process.env.SITE_TAGLINE || 'A digital news magazine for readers who want clear reporting, context, and judgment.'; const EDITOR_NOTE = process.env.EDITOR_NOTE || 'Signal Ledger is built as a serious front page for readers who want more than headlines. We synthesize major reporting, add context, explain why events matter, and keep source attribution visible without turning the site into a mere link directory.'; const CONTACT_EMAIL = process.env.CONTACT_EMAIL || 'signalledger@jopdorp.nl'; const PARENT_COMPANY = process.env.PARENT_COMPANY || 'Jopdorp'; const ADSENSE_CLIENT = process.env.ADSENSE_CLIENT || 'ca-pub-1269854634225826'; const ADSENSE_SLOT = process.env.ADSENSE_SLOT || '7019613848'; const IMAGE_MODEL = process.env.OPENAI_IMAGE_MODEL || 'gpt-image-1'; const GENERATED_IMAGE_LIMIT = Number(process.env.GENERATED_IMAGE_LIMIT || 3); const parser = new Parser({ timeout: 15000 }); function escapeHtml(input = '') { return String(input) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function stripHtml(input = '') { return String(input) .replace(/` : ''; return `
${escapeHtml(item.dek)}
` : ''}${escapeHtml(item.summary)}
Read the full briefDigital news magazine
${escapeHtml(SITE_TAGLINE)}
${escapeHtml(EDITOR_NOTE)}
${escapeHtml(item.dek)}
` : ''}This story sits alongside related Signal Ledger coverage that helps frame the broader pattern.
${escapeHtml(item.dek)}
` : ''}${escapeHtml(item.summary)}
${escapeHtml(item.whyItMatters)}
${escapeHtml(item.context)}
${escapeHtml(item.viewpoint)}
${escapeHtml(item.sourceNote)}
${sourceHref}Section
Magazine briefs, analysis, and context from the ${escapeHtml(group.category)} desk.
Archive
A running index of current Signal Ledger briefs.
${escapeHtml(item.dek)}
About
${escapeHtml(EDITOR_NOTE)}
${escapeHtml(SITE_NAME)} aims to sit between the raw newswire and the over-heated opinion economy. We publish compact, readable briefs with context, judgment, and a magazine voice.
Each story is grounded in attributed reporting from established publishers, then rewritten into a distinct Signal Ledger brief with analysis, framing, and context for readers who want understanding rather than repetition.
Editorial and business contact: ${escapeHtml(CONTACT_EMAIL)}.
${escapeHtml(SITE_NAME)} is a subsidiary of ${escapeHtml(PARENT_COMPANY)}.
Planned expansions include author bylines, topic pages, and daily briefing edition pages.