'use client'; import { useState, useCallback } from 'react'; // ─── Presets ──────────────────────────────────────────────────────────────── const PRESETS = { 'Classic NYT': { '--nui-bg-page': '#F7F4ED', '--nui-bg-surface': '#FBF9F4', '--nui-text-primary': '#1A1A1A', '--nui-text-body': '#22201C', '--nui-text-secondary': '#4A4742', '--nui-text-muted': '#6E6A63', '--nui-rule-hairline': '#C9C2B2', '--nui-rule-decorative': '#1A1A1A', '--nui-accent-primary': '#7A1F1F', '--nui-gutter': '1.5rem', '--font-family-masthead': '"Cormorant Garamond", Georgia, serif', '--font-family-body': '"Source Serif 4", Georgia, serif', '--font-family-meta': '"Inter", system-ui, sans-serif', }, 'The Times': { '--nui-bg-page': '#F5F2EB', '--nui-bg-surface': '#F9F7F2', '--nui-text-primary': '#1C1C1C', '--nui-text-body': '#2A2520', '--nui-text-secondary': '#4A4540', '--nui-text-muted': '#706A62', '--nui-rule-hairline': '#C8BFB0', '--nui-rule-decorative': '#1C1C1C', '--nui-accent-primary': '#1B2A4A', '--nui-gutter': '1.25rem', '--font-family-masthead': '"Cormorant Garamond", Georgia, serif', '--font-family-body': '"Source Serif 4", Georgia, serif', '--font-family-meta': '"Inter", system-ui, sans-serif', }, 'Modern Dark': { '--nui-bg-page': '#14110D', '--nui-bg-surface': '#1C1812', '--nui-text-primary': '#EDE6D6', '--nui-text-body': '#DCD4C2', '--nui-text-secondary': '#A89F8C', '--nui-text-muted': '#857C6A', '--nui-rule-hairline': '#3A352C', '--nui-rule-decorative': '#EDE6D6', '--nui-accent-primary': '#C97A6E', '--nui-gutter': '1.5rem', '--font-family-masthead': '"Cormorant Garamond", Georgia, serif', '--font-family-body': '"Source Serif 4", Georgia, serif', '--font-family-meta': '"Inter", system-ui, sans-serif', }, 'Swiss Modern': { '--nui-bg-page': '#FFFFFF', '--nui-bg-surface': '#F5F5F5', '--nui-text-primary': '#000000', '--nui-text-body': '#111111', '--nui-text-secondary': '#555555', '--nui-text-muted': '#888888', '--nui-rule-hairline': '#CCCCCC', '--nui-rule-decorative': '#000000', '--nui-accent-primary': '#E63329', '--nui-gutter': '2rem', '--font-family-masthead': '"Inter", system-ui, sans-serif', '--font-family-body': '"Inter", system-ui, sans-serif', '--font-family-meta': '"Inter", system-ui, sans-serif', }, } as const; type PresetName = keyof typeof PRESETS; type ThemeVars = Record; // ─── Controls ─────────────────────────────────────────────────────────────── const CONTROLS = [ { label: 'Page Background', key: '--nui-bg-page', type: 'color' }, { label: 'Primary Text', key: '--nui-text-primary', type: 'color' }, { label: 'Body Text', key: '--nui-text-body', type: 'color' }, { label: 'Accent Color', key: '--nui-accent-primary', type: 'color' }, { label: 'Hairline Rule', key: '--nui-rule-hairline', type: 'color' }, { label: 'Gutter', key: '--nui-gutter', type: 'select', options: ['1rem', '1.25rem', '1.5rem', '2rem'] }, { label: 'Masthead Font', key: '--font-family-masthead', type: 'select', options: [ '"Cormorant Garamond", Georgia, serif', '"Playfair Display", Georgia, serif', '"UnifrakturMaguntia", serif', '"Inter", system-ui, sans-serif', ], display: ['Cormorant Garamond', 'Playfair Display', 'UnifrakturMaguntia', 'Inter'], }, { label: 'Body Font', key: '--font-family-body', type: 'select', options: [ '"Source Serif 4", Georgia, serif', '"Lora", Georgia, serif', '"Noto Serif SC", serif', '"Inter", system-ui, sans-serif', ], display: ['Source Serif 4', 'Lora', 'Noto Serif SC', 'Inter'], }, ] as const; // ─── CSS Generator ────────────────────────────────────────────────────────── function generateCSS(vars: ThemeVars): string { const lines = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`).join('\n'); return `:root {\n${lines}\n}`; } // ─── Component Preview ────────────────────────────────────────────────────── function ComponentPreview({ vars }: { vars: ThemeVars }) { const style = Object.fromEntries( Object.entries(vars).map(([k, v]) => [k, v]) ) as React.CSSProperties; return (
{/* Masthead */}
Late City Edition

The Daily Chronicle

Vol. CXLIX · No. 51,895 Tuesday, May 20, 2026 $4.00
{/* Kicker + Headline + Subhead */}
Capitol · Breaking

Historic Accord Reshapes Continental Trade

Negotiators emerge with sweeping framework on tariffs, labor, and emissions

By Eleanor Whitcombe · Brussels
{/* BodyText multi-column */}

After eleven consecutive days of negotiation, delegates from twenty-three nations announced a sweeping framework to reorganize commerce across the continent. The accord would harmonize tariff schedules, set common labor standards, and bind signatories to a shared emissions pathway through 2040.

Officials briefed on the talks said the breakthrough came shortly before midnight, when a dispute over agricultural subsidies was resolved with a side letter granting transitional relief to producers in five smaller economies.

Markets reacted with measured optimism. The continental composite index closed up 1.2 percent, led by capital-goods makers expected to benefit from infrastructure investment.

{/* PullQuote */}

"A long argument that finally became a conversation."

— Margarethe Lindqvist, Chief Negotiator
{/* Rule variants */}
Rule · hairline

Rule · double
Rule · thick

{/* Caption */}

Negotiators applaud after the final draft was approved Monday evening.{' '} Photograph by Jane Doe / Pool

); } // ─── Main Page ─────────────────────────────────────────────────────────────── export default function CreatePage() { const [activePreset, setActivePreset] = useState('Classic NYT'); const [vars, setVars] = useState({ ...PRESETS['Classic NYT'] }); const [copied, setCopied] = useState(false); const applyPreset = useCallback((name: PresetName) => { setActivePreset(name); setVars({ ...PRESETS[name] }); }, []); const updateVar = useCallback((key: string, value: string) => { setActivePreset('Custom' as PresetName); setVars(prev => ({ ...prev, [key]: value })); }, []); const css = generateCSS(vars); const copyCSS = () => { navigator.clipboard.writeText(css); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const downloadCSS = () => { const blob = new Blob([css], { type: 'text/css' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'newspaper-theme.css'; a.click(); URL.revokeObjectURL(url); }; const downloadJSON = () => { const blob = new Blob([JSON.stringify(vars, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'newspaper-theme.json'; a.click(); URL.revokeObjectURL(url); }; const metaStyle = { fontFamily: 'var(--font-family-meta)', fontSize: '13px' }; return (
{/* ── Left Panel ── */} {/* ── Right Preview ── */}
); }