diff --git a/packages/docs/app/create/page.tsx b/packages/docs/app/create/page.tsx new file mode 100644 index 0000000..2c2faaa --- /dev/null +++ b/packages/docs/app/create/page.tsx @@ -0,0 +1,484 @@ +'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 ── */} +
+ +
+
+ ); +} diff --git a/packages/docs/components/Header.tsx b/packages/docs/components/Header.tsx index 9a04558..551c668 100644 --- a/packages/docs/components/Header.tsx +++ b/packages/docs/components/Header.tsx @@ -7,6 +7,7 @@ const navItems = [ { label: 'Components', href: '/components/article' }, { label: 'Themes', href: '/theme' }, { label: 'Blocks', href: '/blocks' }, + { label: 'Create', href: '/create' }, ]; export function Header() {