#!/usr/bin/env npx tsx /** * Generate PDF version of the Interactive Book of Prompting * * Usage: * npx tsx scripts/generate-book-pdf.ts [locale] * * Examples: * npx tsx scripts/generate-book-pdf.ts # Generate English version (default) * npx tsx scripts/generate-book-pdf.ts tr # Generate Turkish version * npx tsx scripts/generate-book-pdf.ts --all # Generate all locales * npx tsx scripts/generate-book-pdf.ts --print # Print-ready with bleed & CMYK colors * npx tsx scripts/generate-book-pdf.ts --all --print # All locales, print-ready */ import * as fs from 'fs'; import * as path from 'path'; import { parts, type Chapter } from '../src/lib/book/chapters'; import { getLocaleData, type LocaleData } from '../src/components/book/elements/locales'; // Configuration const BOOK_DIR = path.join(process.cwd(), 'src/content/book'); const MESSAGES_DIR = path.join(process.cwd(), 'messages'); const OUTPUT_DIR = path.join(process.cwd(), 'public/book-pdf'); const SITE_URL = 'https://prompts.chat'; // Print-ready mode flag (set by --print CLI argument) const PRINT_READY = process.argv.includes('--print'); // Bleed dimensions for print-ready output const BLEED = '0.125in'; // 3mm standard bleed const TRIM_WIDTH = '6in'; const TRIM_HEIGHT = '9in'; const BLEED_WIDTH = '6.25in'; // 6 + 0.125*2 const BLEED_HEIGHT = '9.25in'; // 9 + 0.125*2 // Components that truly need interactivity (API calls, complex animations) // Everything else gets static rendering const INTERACTIVE_ONLY_COMPONENTS = [ 'PromptAnalyzer', // Needs live API calls 'RunPromptButton', 'CodeEditor', ]; /** * Load UI messages from messages/*.json */ function loadMessages(locale: string): Record { try { const filePath = path.join(MESSAGES_DIR, `${locale}.json`); return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch { // Fallback to English const filePath = path.join(MESSAGES_DIR, 'en.json'); return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } } /** * Get a nested translation key from messages */ function t(messages: Record, key: string): string { const keys = key.split('.'); let current: unknown = messages; for (const k of keys) { if (current && typeof current === 'object' && k in (current as Record)) { current = (current as Record)[k]; } else { return key; // Fallback to key itself } } return typeof current === 'string' ? current : key; } // Components with explicit static renderers (handled individually in transformMdxForPdf) const STATICALLY_RENDERED_COMPONENTS = [ 'TryIt', 'Quiz', 'Callout', 'InfoGrid', 'Checklist', 'Compare', 'FillInTheBlank', 'InteractiveChecklist', 'PromptDebugger', 'PromptChallenge', 'BeforeAfterEditor', 'DiffView', 'VersionDiff', 'TokenizerDemo', 'TokenPredictionDemo', 'ContextWindowDemo', 'TemperatureDemo', 'StructuredOutputDemo', 'FewShotDemo', 'JsonYamlDemo', 'IterativeRefinementDemo', 'CostCalculatorDemo', 'EmbeddingsDemo', 'LLMCapabilitiesDemo', 'JailbreakDemo', 'TextToImageDemo', 'TextToVideoDemo', 'SummarizationDemo', 'ContextPlayground', 'ValidationDemo', 'FallbackDemo', 'ContentPipelineDemo', 'ChainExample', 'ChainFlowDemo', 'ChainErrorDemo', 'FrameworkDemo', 'CRISPEFramework', 'BREAKFramework', 'RTFFramework', 'PromptBreakdown', 'SpecificitySpectrum', 'PrinciplesSummary', 'PromptBuilder', 'BookPartsNav', 'Collapsible', 'CopyableCode', ]; // Localization for the interactive notice const INTERACTIVE_NOTICES: Record = { en: '📖 This is an interactive element. Visit prompts.chat/book to try it live!', tr: '📖 Bu etkileşimli bir öğedir. Canlı denemek için prompts.chat/book adresini ziyaret edin!', es: '📖 Este es un elemento interactivo. ¡Visita prompts.chat/book para probarlo en vivo!', de: '📖 Dies ist ein interaktives Element. Besuchen Sie prompts.chat/book, um es live auszuprobieren!', fr: '📖 Ceci est un élément interactif. Visitez prompts.chat/book pour l\'essayer en direct!', pt: '📖 Este é um elemento interativo. Visite prompts.chat/book para experimentá-lo ao vivo!', zh: '📖 这是一个互动元素。访问 prompts.chat/book 进行在线体验!', ja: '📖 これはインタラクティブな要素です。prompts.chat/book でライブで試してみてください!', ko: '📖 이것은 인터랙티브 요소입니다. prompts.chat/book을 방문하여 직접 체험해 보세요!', ar: '📖 هذا عنصر تفاعلي. قم بزيارة prompts.chat/book لتجربته مباشرة!', it: '📖 Questo è un elemento interattivo. Visita prompts.chat/book per provarlo dal vivo!', ru: '📖 Это интерактивный элемент. Посетите prompts.chat/book, чтобы попробовать вживую!', fa: '📖 این یک عنصر تعاملی است. برای امتحان زنده به prompts.chat/book مراجعه کنید!', nl: '📖 Dit is een interactief element. Bezoek prompts.chat/book om het live te proberen!', el: '📖 Αυτό είναι ένα διαδραστικό στοιχείο. Επισκεφθείτε το prompts.chat/book για να το δοκιμάσετε ζωντανά!', az: '📖 Bu interaktiv elementdir. Canlı sınamaq üçün prompts.chat/book saytına daxil olun!', he: '📖 זהו אלמנט אינטראקטיבי. בקרו ב-prompts.chat/book כדי לנסות אותו בזמן אמת!', }; /** * Inline SVG icons for print-ready mode (emojis don't render reliably in CMYK print). * Each icon is a 14x14 inline SVG with currentColor stroke. */ const SVG_ICONS: Record = { zap: '', quiz: '', info: '', warning: '', tip: '', pencil: '', search: '', trophy: '', refresh: '', palette: '', video: '', shield: '', book: '', gem: '', target: '', crown: '', compass: '', sparkles: '', ruler: '', check: '', checkbox: '', }; /** * Return icon markup: SVG for print, emoji for screen */ function icon(name: string, emoji: string): string { if (PRINT_READY && SVG_ICONS[name]) { return SVG_ICONS[name]; } return emoji; } // Fallback book titles (used if messages don't have book.title) const BOOK_TITLES_FALLBACK: Record = { en: 'The Interactive Book of Prompting', tr: 'İnteraktif Prompt Yazma Kitabı', }; interface ProcessedChapter { slug: string; title: string; part: string; content: string; } /** * Get available locales from the book directory */ function getAvailableLocales(): string[] { const entries = fs.readdirSync(BOOK_DIR, { withFileTypes: true }); const locales = entries .filter(entry => entry.isDirectory()) .map(entry => entry.name); // Add 'en' as it's the default (files in root) if (!locales.includes('en')) { locales.unshift('en'); } return locales; } /** * Get the path to MDX files for a locale */ function getLocalePath(locale: string): string { if (locale === 'en') { return BOOK_DIR; } return path.join(BOOK_DIR, locale); } /** * Check if a chapter exists for a locale */ function chapterExists(locale: string, slug: string): boolean { const localePath = getLocalePath(locale); const filePath = path.join(localePath, `${slug}.mdx`); return fs.existsSync(filePath); } /** * Extract prompt content from TryIt component */ function extractTryItPrompt(content: string): string | null { // Match prompt={`...`} or prompt="..." const backtickMatch = content.match(/prompt=\{`([\s\S]*?)`\}/); if (backtickMatch) return backtickMatch[1]; const quoteMatch = content.match(/prompt="([^"]*?)"/); if (quoteMatch) return quoteMatch[1]; return null; } /** * Extract Quiz content */ function extractQuizContent(content: string): { question: string; options: string[]; explanation: string } | null { const questionMatch = content.match(/question="((?:[^"\\]|\\.)*)"/); const optionsMatch = content.match(/options=\{\[([\s\S]*?)\]\}/); const explanationMatch = content.match(/explanation="((?:[^"\\]|\\.)*)"/); if (!questionMatch || !optionsMatch) return null; const question = questionMatch[1].replace(/\\"/g, '"'); const optionsStr = optionsMatch[1]; const options: string[] = []; const optRegex = /"((?:[^"\\]|\\.)*)"/g; let m; while ((m = optRegex.exec(optionsStr)) !== null) { options.push(m[1].replace(/\\"/g, '"')); } const explanation = explanationMatch ? explanationMatch[1].replace(/\\"/g, '"') : ''; return { question, options, explanation }; } /** * Extract Callout content */ function extractCalloutContent(match: string): { type: string; title: string; content: string } { const typeMatch = match.match(/type="([^"]*?)"/); const titleMatch = match.match(/title="([^"]*?)"/); // Extract content between > and const contentMatch = match.match(/>\s*([\s\S]*?)\s*<\/Callout>/); return { type: typeMatch ? typeMatch[1] : 'info', title: titleMatch ? titleMatch[1] : '', content: contentMatch ? contentMatch[1].trim() : '', }; } /** * Extract InfoGrid items */ function extractInfoGridItems(content: string): { label: string; description: string }[] { const itemsMatch = content.match(/items=\{\[([\s\S]*?)\]\}/); if (!itemsMatch) return []; const items: { label: string; description: string }[] = []; // Handle escaped quotes inside strings: match "..." allowing \" inside const itemRegex = /\{\s*label:\s*"((?:[^"\\]|\\.)*)"\s*,\s*description:\s*"((?:[^"\\]|\\.)*)"/g; let match; while ((match = itemRegex.exec(itemsMatch[1])) !== null) { // Unescape the \" back to " const label = match[1].replace(/\\"/g, '"'); const description = match[2].replace(/\\"/g, '"'); items.push({ label, description }); } return items; } /** * Extract Checklist items */ function extractChecklistItems(content: string): { title: string; items: string[] } { const titleMatch = content.match(/title="([^"]*?)"/); const itemsMatch = content.match(/items=\{\[([\s\S]*?)\]\}/); const title = titleMatch ? titleMatch[1] : ''; const items: string[] = []; if (itemsMatch) { const textRegex = /text:\s*"([^"]*?)"/g; let match; while ((match = textRegex.exec(itemsMatch[1])) !== null) { items.push(match[1]); } } return { title, items }; } /** * Extract props from a JSX-like component string */ function extractProps(content: string): Record { const props: Record = {}; // Simple string props: key="value" (handles escaped quotes) const stringRegex = /(\w+)="((?:[^"\\]|\\.)*)"/g; let m; while ((m = stringRegex.exec(content)) !== null) { props[m[1]] = m[2].replace(/\\"/g, '"'); } // Template literal props: key={`value`} const templateRegex = /(\w+)=\{`([\s\S]*?)`\}/g; while ((m = templateRegex.exec(content)) !== null) { props[m[1]] = m[2]; } return props; } /** * Extract array of objects from items={[...]} prop */ function extractArrayProp(content: string, propName: string): Record[] { const regex = new RegExp(`${propName}=\\{\\[([\\s\\S]*?)\\]\\}`, 'g'); const match = regex.exec(content); if (!match) return []; const items: Record[] = []; const objRegex = /\{([^{}]*?)\}/g; let m; while ((m = objRegex.exec(match[1])) !== null) { const obj: Record = {}; // Handle escaped quotes in field values const fieldRegex = /(\w+):\s*"((?:[^"\\]|\\.)*)"/g; let fm; while ((fm = fieldRegex.exec(m[1])) !== null) { obj[fm[1]] = fm[2].replace(/\\"/g, '"'); } if (Object.keys(obj).length > 0) items.push(obj); } return items; } /** * Transform MDX content for PDF - convert interactive components to static */ function transformMdxForPdf(content: string, locale: string, localeData?: LocaleData, messages?: Record): string { let result = content; const noticeRaw = INTERACTIVE_NOTICES[locale] || INTERACTIVE_NOTICES.en; const notice = PRINT_READY ? noticeRaw.replace('📖', icon('book', '📖')) : noticeRaw; const msg = messages || {}; const ld = localeData || getLocaleData('en'); // Translated UI labels const labels = { tryIt: t(msg, 'book.interactive.tryIt') || 'Try This Prompt', quiz: t(msg, 'book.interactive.quiz') || 'Quiz', answer: t(msg, 'book.interactive.answer') || 'Answer', prompt: t(msg, 'book.interactive.prompt') || 'Prompt', output: t(msg, 'book.interactive.outputLabel') || 'Output', answers: t(msg, 'book.interactive.answers') || 'Answers', completePrompt: t(msg, 'book.interactive.completePrompt') || 'Complete Prompt', examplePrompts: t(msg, 'book.interactive.examplePrompts') || 'Example prompts', }; // In print mode, remove author social links from preface if (PRINT_READY) { result = result.replace(/
[\s\S]*?<\/div>/g, ''); } // Remove navigation components entirely result = result.replace(/]*\/>/g, ''); result = result.replace(/]*\/>/g, ''); result = result.replace(/]*\/>/g, ''); // ============================================================ // TryIt - Show prompt as styled code block // ============================================================ result = result.replace(//g, (match, attrs) => { const prompt = extractTryItPrompt(match); const titleMatch = attrs.match(/title="([^"]*?)"/); const descMatch = attrs.match(/description="([^"]*?)"/); const title = titleMatch ? titleMatch[1] : labels.tryIt; const desc = descMatch ? descMatch[1] : ''; if (prompt) { const printablePrompt = convertPromptVariables(prompt); const protectedPrompt = protectCodeBlock(escapeHtml(printablePrompt)); return `
${icon('zap', '⚡')} ${escapeHtml(title)}
${desc ? `

${escapeHtml(desc)}

` : ''}
${protectedPrompt}
`; } return ''; }); // ============================================================ // Quiz - Show question, options, and answer // ============================================================ result = result.replace(//g, (match) => { const quiz = extractQuizContent(match); if (quiz) { const correctIdx = match.match(/correctIndex=\{(\d+)\}/); const correctNum = correctIdx ? parseInt(correctIdx[1]) : -1; const optionsHtml = quiz.options.map((opt, i) => { const marker = i === correctNum ? '●' : '○'; const cls = i === correctNum ? ' class="quiz-correct"' : ''; return `${marker} ${opt}
`; }).join('\n'); return `
${icon('quiz', '📝')} Quiz

${quiz.question}

${optionsHtml}
${quiz.explanation ? `

Answer: ${quiz.explanation}

` : ''}
`; } return ''; }); // ============================================================ // Callout - Static info boxes // ============================================================ result = result.replace(//g, (match) => { const callout = extractCalloutContent(match); const calloutIcons: Record = { info: ['info', 'ℹ️'], warning: ['warning', '⚠️'], tip: ['tip', '💡'], example: ['zap', '⚡'] }; const ci = calloutIcons[callout.type] || calloutIcons.info; return `
${icon(ci[0], ci[1])} ${callout.title}
${callout.content}
`; }); // ============================================================ // InfoGrid - Static grid // ============================================================ result = result.replace(//g, (match) => { const items = extractInfoGridItems(match); if (items.length > 0) { const itemsHtml = items.map(item => `
${item.label}: ${item.description}
` ).join('\n'); return `
\n${itemsHtml}\n
`; } return ''; }); // ============================================================ // Checklist (simple) - Printable checkbox list // ============================================================ result = result.replace(//g, (match) => { const checklist = extractChecklistItems(match); const cb = icon('checkbox', '☐'); const itemsHtml = checklist.items.map(item => `
  • ${cb} ${item}
  • `).join('\n'); return `
    ${checklist.title ? `
    ${checklist.title}
    ` : ''}
      ${itemsHtml}
    `; }); // ============================================================ // Compare - Side by side (handles object syntax {{ label, content }}) // ============================================================ result = result.replace(//g, (match) => { // Try object syntax: before={{ label: "...", content: "..." or `...` }} const extractSide = (side: string): { label: string; content: string } | null => { const objRegex = new RegExp(`${side}=\\{\\{[\\s\\S]*?\\}\\}`); const objMatch = match.match(objRegex); if (!objMatch) return null; const block = objMatch[0]; const labelMatch = block.match(/label:\s*"((?:[^"\\]|\\.)*)"/); const contentQuote = block.match(/content:\s*"((?:[^"\\]|\\.)*)"/); const contentBt = block.match(/content:\s*`([\s\S]*?)`/); const label = labelMatch ? labelMatch[1].replace(/\\"/g, '"') : side; const content = contentBt ? contentBt[1] : (contentQuote ? contentQuote[1].replace(/\\"/g, '"').replace(/\\n/g, '\n') : ''); return { label, content }; }; const before = extractSide('before'); const after = extractSide('after'); // Fallback: simple string props if (!before || !after) { const beforeSimple = match.match(/before="((?:[^"\\]|\\.)*)"/); const afterSimple = match.match(/after="((?:[^"\\]|\\.)*)"/); if (beforeSimple && afterSimple) { return `
    Before: ${beforeSimple[1].replace(/\\"/g, '"')}
    After: ${afterSimple[1].replace(/\\"/g, '"')}
    `; } return ''; } const beforeContent = protectCodeBlock(escapeHtml(before.content)); const afterContent = protectCodeBlock(escapeHtml(after.content)); return `
    ${escapeHtml(before.label)}
    ${beforeContent}
    ${escapeHtml(after.label)}
    ${afterContent}
    `; }); // ============================================================ // FillInTheBlank - Printable exercise with blanks // ============================================================ result = result.replace(//g, (match) => { const props = extractProps(match); const title = props.title || 'Fill in the Blanks'; // Extract template const templateMatch = match.match(/template="([^"]*?)"/); const templateBt = match.match(/template=\{`([\s\S]*?)`\}/); const template = templateBt ? templateBt[1] : (templateMatch ? templateMatch[1] : ''); // Extract blanks with correct answers const blanks = extractArrayProp(match, 'blanks'); // Replace {{id}} in template with labeled blanks let rendered = template.replace(/\{\{(\w+)\}\}/g, (_, id) => { const blank = blanks.find(b => b.id === id); const hint = blank?.correctAnswers || blank?.hint || ''; if (hint) { return `_______ (${id}, e.g. ${hint})`; } return `_______ (${id})`; }); const answersHtml = blanks.length > 0 ? blanks.map(b => { const answers = b.correctAnswers || ''; return `
  • ${b.id}: ${answers}
  • `; }).join('\n') : ''; const protectedTemplate = protectCodeBlock(escapeHtml(rendered)); return `
    ${icon('pencil', '✏️')} ${escapeHtml(title)}
    ${protectedTemplate}
    ${answersHtml ? `
    Answers:
      ${answersHtml}
    ` : ''}
    `; }); // ============================================================ // InteractiveChecklist - Printable checklist // ============================================================ result = result.replace(//g, (match) => { const props = extractProps(match); const title = props.title || 'Checklist'; const items = extractArrayProp(match, 'items'); const itemsHtml = items.map(item => `
  • ${icon('checkbox', '☐')} ${item.label || ''}${item.description ? ` — ${item.description}` : ''}
  • ` ).join('\n'); return `
    ${escapeHtml(title)}
      ${itemsHtml}
    `; }); // ============================================================ // PromptDebugger - Show prompt, bad output, and options // ============================================================ result = result.replace(//g, (match) => { const props = extractProps(match); const title = props.title || 'Debug This Prompt'; const badPrompt = props.badPrompt || ''; const badOutput = props.badOutput || ''; const hint = props.hint || ''; const options = extractArrayProp(match, 'options'); const protectedBadPrompt = protectCodeBlock(escapeHtml(badPrompt)); const protectedBadOutput = protectCodeBlock(escapeHtml(badOutput)); const optionsHtml = options.map(opt => { const marker = opt.isCorrect === 'true' ? '✓' : '○'; const cls = opt.isCorrect === 'true' ? ' class="quiz-correct"' : ''; return `${marker} ${opt.label || ''}`; }).join('\n'); return `
    ${icon('search', '🔍')} ${escapeHtml(title)}
    The Prompt:
    ${protectedBadPrompt}
    The Output (problematic):
    ${protectedBadOutput}
    ${hint ? `

    ${icon('tip', '💡')} Hint: ${escapeHtml(hint)}

    ` : ''}
    What's wrong?
    ${optionsHtml}
    `; }); // ============================================================ // PromptChallenge - Show task, criteria, and example solution // ============================================================ result = result.replace(//g, (match) => { const props = extractProps(match); const title = props.title || 'Prompt Challenge'; const task = props.task || ''; const difficulty = props.difficulty || 'intermediate'; const exampleSolution = props.exampleSolution || ''; const criteria: string[] = []; const criteriaMatch = match.match(/criteria=\{\[([\s\S]*?)\]\}/); if (criteriaMatch) { const strRegex = /"([^"]*?)"/g; let cm; while ((cm = strRegex.exec(criteriaMatch[1])) !== null) { criteria.push(cm[1]); } } const criteriaHtml = criteria.map(c => `
  • ${icon('checkbox', '☐')} ${c}
  • `).join('\n'); return `
    ${icon('trophy', '🏆')} ${escapeHtml(title)} ${difficulty}

    ${escapeHtml(task)}

    ${criteria.length > 0 ? `
    Criteria:
      ${criteriaHtml}
    ` : ''} ${exampleSolution ? `
    Example Solution:
    ${protectCodeBlock(escapeHtml(exampleSolution))}
    ` : ''}
    `; }); // ============================================================ // BeforeAfterEditor - Show both prompts side by side // ============================================================ result = result.replace(//g, (match) => { const props = extractProps(match); const title = props.title || 'Before & After'; const badPrompt = props.badPrompt || ''; const idealPrompt = props.idealPrompt || ''; const task = props.task || ''; return `
    ${icon('refresh', '🔄')} ${escapeHtml(title)}
    ${task ? `

    ${escapeHtml(task)}

    ` : ''}
    Before:
    ${protectCodeBlock(escapeHtml(badPrompt))}
    After:
    ${protectCodeBlock(escapeHtml(idealPrompt))}
    `; }); // ============================================================ // DiffView - Show before/after comparison // ============================================================ result = result.replace(//g, (match) => { const props = extractProps(match); const beforeLabel = props.beforeLabel || 'Before'; const afterLabel = props.afterLabel || 'After'; const before = props.before || ''; const after = props.after || ''; return `
    ${escapeHtml(beforeLabel)}:
    ${protectCodeBlock(escapeHtml(before))}
    ${escapeHtml(afterLabel)}:
    ${protectCodeBlock(escapeHtml(after))}
    `; }); // ============================================================ // TokenizerDemo - Static example with colored tokens // ============================================================ result = result.replace(//g, () => { const sample = ld.tokenizer.samples[ld.tokenizer.default]; const example = sample?.text || 'Hello, world!'; const tokens = sample?.tokens || ['Hel', 'lo', ',', ' wor', 'ld', '!']; const colors = ['#dbeafe', '#dcfce7', '#f3e8ff', '#fef3c7', '#fce7f3', '#cffafe']; const tokensHtml = tokens.map((tk, i) => `${tk === ' ' ? '␣' : escapeHtml(tk)}` ).join(''); return `
    Tokenizer

    Input: "${escapeHtml(example)}"

    Tokens (${tokens.length}):

    ${tokensHtml}

    ${escapeHtml(ld.tokenizer.tryExamples)}

    `; }); // ============================================================ // TokenPredictionDemo - Static next-token prediction // ============================================================ result = result.replace(//g, () => { const tp = ld.tokenPrediction; const stepKeys = Object.keys(tp.predictions.steps); const stepsHtml = stepKeys.slice(0, 3).map(key => { const preds = tp.predictions.steps[key]; const predsHtml = preds.map(p => `${escapeHtml(p.token)} ${Math.round(p.probability * 100)}%`).join(' '); return `
    "${escapeHtml(key)} ▁▁▁"
    → ${predsHtml}
    `; }).join('\n'); return `
    Next-Token Prediction

    ${escapeHtml(tp.fullText)}

    ${stepsHtml}
    `; }); // ============================================================ // ContextWindowDemo - Static diagram // ============================================================ result = result.replace(//g, () => { const cwLabel = t(msg, 'book.interactive.contextWindow') || 'Context Window'; const promptLabel = t(msg, 'book.interactive.prompt') || 'Prompt'; const responseLabel = t(msg, 'book.interactive.response') || 'Response'; const remainingLabel = t(msg, 'book.interactive.remaining') || 'Remaining'; const tipText = t(msg, 'book.interactive.contextTip') || ''; return `
    ${escapeHtml(cwLabel)} — 8,000 tokens
    ${escapeHtml(promptLabel)}
    2,000 tokens
    ${escapeHtml(responseLabel)}
    1,000 tokens
    ${escapeHtml(remainingLabel)} — 5,000 tokens
    ${tipText ? `

    ${escapeHtml(tipText)}

    ` : ''}
    `; }); // ============================================================ // TemperatureDemo - Show all temperature levels // ============================================================ result = result.replace(//g, () => { const te = ld.temperatureExamples; const levels = [ { temp: '0.0–0.2', label: t(msg, 'book.interactive.deterministic') || 'Deterministic', examples: te.lowTemp.slice(0, 2) }, { temp: '0.5–0.7', label: t(msg, 'book.interactive.balanced') || 'Balanced', examples: te.mediumHighTemp.slice(0, 2) }, { temp: '0.8–1.0', label: t(msg, 'book.interactive.veryCreative') || 'Creative', examples: te.highTemp.slice(0, 2) }, ]; const levelsHtml = levels.map(l => `
    ${l.temp} — ${escapeHtml(l.label)}
    ${l.examples.map(e => `
    "${escapeHtml(e)}"
    `).join('')}
    `).join(''); return `
    ${t(msg, 'book.interactive.temperatureDemo') || 'Temperature'}

    ${t(msg, 'book.interactive.prompt') || 'Prompt'}: "${escapeHtml(te.prompt)}"

    ${levelsHtml}
    `; }); // ============================================================ // StructuredOutputDemo - Show all three formats // ============================================================ result = result.replace(//g, () => { const unstructured = 'Here are some popular programming languages: Python is great for data science and AI. JavaScript is used for web development. Rust is known for performance and safety.'; const json = `{ "languages": [ { "name": "Python", "best_for": ["data science", "AI"], "difficulty": "easy" }, { "name": "JavaScript", "best_for": ["web development"], "difficulty": "medium" }, { "name": "Rust", "best_for": ["performance", "safety"], "difficulty": "hard" } ] }`; return `
    Structured Output Comparison
    Unstructured:
    ${escapeHtml(unstructured)}
    Structured (JSON):
    ${protectCodeBlock(escapeHtml(json))}

    Structured output allows programmatic parsing, comparison across queries, and integration into workflows.

    `; }); // ============================================================ // FewShotDemo - Show progression of examples // ============================================================ result = result.replace(//g, () => { return `
    Few-Shot Learning

    More examples help the model understand the pattern:

    ExamplesPredictionConfidence
    0 (zero-shot)Positive ✗45%
    1 (one-shot)Positive ✗62%
    2 (two-shot)Mixed ✓71%
    3 (three-shot)Mixed ✓94%

    Test input: "Great quality but shipping was slow" → Expected: Mixed

    `; }); // ============================================================ // JsonYamlDemo - Show all three formats // ============================================================ result = result.replace(//g, () => { const ts = `interface ChatPersona { name?: string; role?: string; tone?: PersonaTone | PersonaTone[]; expertise?: PersonaExpertise[]; }`; const json = `{ "name": "CodeReviewer", "role": "Senior Software Engineer", "tone": ["professional", "analytical"], "expertise": ["coding", "engineering"] }`; const yaml = `name: CodeReviewer role: Senior Software Engineer tone: - professional - analytical expertise: - coding - engineering`; return `
    Format Comparison: TypeScript / JSON / YAML
    TypeScript (define schema):
    ${protectCodeBlock(escapeHtml(ts))}
    JSON (APIs & parsing):
    ${protectCodeBlock(escapeHtml(json))}
    YAML (config files):
    ${protectCodeBlock(escapeHtml(yaml))}
    `; }); // ============================================================ // IterativeRefinementDemo - Show all versions // ============================================================ result = result.replace(//g, () => { const versions = [ { v: 1, prompt: 'Write a product description.', output: 'This is a great product. It has many features. You should buy it.', quality: 20, issue: 'Too vague, no specific details' }, { v: 2, prompt: 'Write a product description for wireless earbuds.', output: 'These wireless earbuds offer great sound quality and comfortable fit. They have long battery life.', quality: 45, issue: 'Better, but still generic' }, { v: 3, prompt: 'Write a 50-word product description for premium wireless earbuds. Highlight: noise cancellation, 8-hour battery, water resistance.', output: 'Experience pure audio bliss with our premium wireless earbuds. Advanced noise cancellation blocks distractions while delivering crystal-clear sound.', quality: 72, issue: 'Good details, needs stronger hook' }, { v: 4, prompt: 'Write a compelling 50-word product description for premium wireless earbuds.\nKey features: noise cancellation, 8-hour battery, IPX5\nTone: Premium but approachable\nStart with a benefit, end with a call to action.', output: 'Escape the noise and immerse yourself in studio-quality sound. Our premium wireless earbuds feature advanced noise cancellation, 8-hour battery life, and IPX5 water resistance.', quality: 95, issue: null }, ]; const versionsHtml = versions.map(v => `
    Version ${v.v} — Quality: ${v.quality}%
    ${protectCodeBlock(escapeHtml(v.prompt))}
    ${escapeHtml(v.output)}
    ${v.issue ? `
    ⚠ ${v.issue}
    ` : '
    ✓ Strong prompt with clear structure
    '}
    `).join(''); return `
    Iterative Refinement

    Watch how a prompt improves through successive iterations:

    ${versionsHtml}
    `; }); // ============================================================ // CostCalculatorDemo - Static calculation example // ============================================================ result = result.replace(//g, () => { return `
    API Cost Calculator
    ParameterValue
    Input tokens per request500
    Output tokens per request200
    Input price$0.15 / 1M tokens
    Output price$0.60 / 1M tokens
    Requests per day1,000
    Per request: $0.0002
    Daily: $0.20
    Monthly: $5.85

    (500 × $0.15/1M) + (200 × $0.60/1M) = $0.000195/request

    `; }); // ============================================================ // EmbeddingsDemo - Show word vectors and similarity // ============================================================ result = result.replace(//g, () => { const words = ld.embeddingWords; const rowsHtml = words.map(w => `${escapeHtml(w.word)}[${w.vector.join(', ')}]${escapeHtml(w.color)}` ).join('\n'); return `
    Word Embeddings
    ${rowsHtml}
    WordVectorGroup
    `; }); // ============================================================ // LLMCapabilitiesDemo - Static capability list // ============================================================ result = result.replace(//g, () => { const canDo = ld.capabilities.filter(c => c.canDo); const cantDo = ld.capabilities.filter(c => !c.canDo); return `
    ✓
      ${canDo.map(c => `
    • ${escapeHtml(c.title)} — ${escapeHtml(c.description)}
    • `).join('\n')}
    ✗
      ${cantDo.map(c => `
    • ${escapeHtml(c.title)} — ${escapeHtml(c.description)}
    • `).join('\n')}
    `; }); // ============================================================ // JailbreakDemo - Show attack/defense examples // ============================================================ result = result.replace(//g, () => { const examplesHtml = ld.jailbreakExamples.slice(0, 3).map(ex => `
    ${escapeHtml(ex.name)} — ${escapeHtml(ex.description)}
    ${icon('shield', '🛡️')}
    ${escapeHtml(ex.systemPrompt)}
    ${icon('warning', '⚠️')}
    ${escapeHtml(ex.attack)}
    `).join(''); return `
    ${examplesHtml}
    `; }); // ============================================================ // Framework demos - CRISPE, BREAK, RTF with steps and example // ============================================================ const fwColors = ['#dbeafe', '#dcfce7', '#f3e8ff', '#fef3c7', '#fce7f3', '#cffafe']; const fwBorderColors = ['#93c5fd', '#86efac', '#c4b5fd', '#fcd34d', '#f9a8d4', '#67e8f9']; const fwMap: Record = { CRISPEFramework: 'crispe', BREAKFramework: 'break', RTFFramework: 'rtf', }; for (const [comp, fwKey] of Object.entries(fwMap)) { const regex = new RegExp(`<${comp}\\s*/>`, 'g'); result = result.replace(regex, () => { const fw = ld.frameworks[fwKey]; const stepsHtml = fw.steps.map((s, i) => `
    ${s.letter}
    ${escapeHtml(s.label)} — ${escapeHtml(s.description)}
    ${s.example ? `
    ${escapeHtml(s.example)}
    ` : ''}
    `).join('\n'); return `
    ${escapeHtml(fw.name)}
    ${stepsHtml}
    ${labels.completePrompt}:
    ${protectCodeBlock(escapeHtml(fw.examplePrompt))}
    `; }); } // ============================================================ // PrinciplesSummary - Numbered principles list (from locale data) // ============================================================ result = result.replace(//g, () => { const iconMap: Record = { Gem: ['gem', '💎'], Target: ['target', '🎯'], Crown: ['crown', '👑'], Compass: ['compass', '🧭'], RefreshCw: ['refresh', '🔄'], Sparkles: ['sparkles', '✨'], Ruler: ['ruler', '📏'], CheckCircle: ['check', '✅'], }; const itemsHtml = ld.principles.map(p => { const ic = iconMap[p.iconName] || ['info', '•']; return `
    ${icon(ic[0], ic[1])}${escapeHtml(p.title)} — ${escapeHtml(p.description)}
    `; }).join('\n'); return `
    ${itemsHtml}
    `; }); // ============================================================ // VersionDiff - Show versions with printable diff // ============================================================ result = result.replace(//g, (match, inner) => { // Parse versions: { label: "...", content: `...`, note: "..." } const versions: { label: string; content: string; note: string }[] = []; const versionBlocks = inner.split(/\},\s*\{/).map((b: string) => b.replace(/^\{|\}$/g, '')); for (const block of versionBlocks) { const labelMatch = block.match(/label:\s*"([^"]*?)"/); const noteMatch = block.match(/note:\s*"([^"]*?)"/); // content can be quoted or template-literal const contentQuoted = block.match(/content:\s*"([^"]*?)"/); const contentBt = block.match(/content:\s*`([\s\S]*?)`/); const content = contentBt ? contentBt[1] : (contentQuoted ? contentQuoted[1] : ''); if (labelMatch) { versions.push({ label: labelMatch[1], content, note: noteMatch ? noteMatch[1] : '' }); } } if (versions.length === 0) return ''; const versionsHtml = versions.map((v, i) => `
    ${escapeHtml(v.label)} ${v.note ? `${escapeHtml(v.note)}` : ''}
    ${protectCodeBlock(escapeHtml(v.content))}
    `).join('\n'); return `
    Prompt Evolution
    ${versionsHtml}
    `; }); // ============================================================ // ChainExample - Full chain with prompts and outputs per type // ============================================================ result = result.replace(//g, (match, type, stepsInner) => { // Parse steps: { step: "...", prompt: "...", output: "..." } const steps: { step: string; prompt: string; output: string }[] = []; const stepRegex = /\{\s*step:\s*"([^"]*?)"\s*,\s*prompt:\s*"([^"]*?)"\s*,\s*output:\s*(?:'([^']*?)'|"([^"]*?)")/g; let sm; while ((sm = stepRegex.exec(stepsInner)) !== null) { steps.push({ step: sm[1], prompt: sm[2], output: sm[3] || sm[4] || '' }); } // If regex didn't match (complex multiline prompts), try a simpler approach if (steps.length === 0) { const blockRegex = /\{\s*step:\s*"([^"]*?)"/g; let bm; while ((bm = blockRegex.exec(stepsInner)) !== null) { steps.push({ step: bm[1], prompt: '', output: '' }); } } const typeLabels: Record = { sequential: 'Sequential Chain', parallel: 'Parallel Chain', conditional: 'Conditional Chain', iterative: 'Iterative Chain', }; const typeIcons: Record = { sequential: '→', parallel: '⇉', conditional: '◇', iterative: '↻', }; const stepsHtml = steps.map((s, i) => { const isSkipped = s.step.toLowerCase().includes('skipped'); const stepClass = isSkipped ? 'chain-step-skipped' : 'chain-step-item'; const connector = type === 'parallel' && i > 0 && i < steps.length - 1 ? 'chain-connector-parallel' : i < steps.length - 1 ? 'chain-connector' : ''; return `
    ${isSkipped ? '✗' : (i + 1)}
    ${escapeHtml(s.step)}
    ${!isSkipped && s.prompt ? `
    Prompt: ${protectCodeBlock(escapeHtml(s.prompt))}
    ` : ''} ${!isSkipped && s.output ? `
    Output: ${protectCodeBlock(escapeHtml(s.output))}
    ` : ''} ${isSkipped ? '
    Skipped — condition not met
    ' : ''}
    ${connector ? `
    ` : ''}`; }).join('\n'); const loopNote = type === 'iterative' ? '
    ↻ Loop until quality threshold is met
    ' : ''; return `
    ${typeIcons[type] || '→'} ${typeLabels[type] || type}
    ${stepsHtml} ${loopNote}
    `; }); // ============================================================ // TextToImageDemo - Show prompt anatomy with all categories // ============================================================ result = result.replace(//g, () => { const imgOpts = ld.imagePromptOptions; const imgLabels = ld.imageCategoryLabels; const catColors: Record = { subject: '#dbeafe', style: '#f3e8ff', lighting: '#fef3c7', composition: '#dcfce7', mood: '#fce7f3' }; const categories = Object.keys(imgOpts).map(key => ({ label: imgLabels[key] || key, options: imgOpts[key], color: catColors[key] || '#f5f5f4', })); const categoriesHtml = categories.map(cat => { const optionsHtml = cat.options.map((opt, i) => { const style = i === 0 ? ` style="background:${cat.color};border:1px solid #ccc;font-weight:600;"` : ''; return `${opt}`; }).join(' '); return `
    ${cat.label}: ${optionsHtml}
    `; }).join('\n'); const examplePrompts = [ { prompt: 'a cat, photorealistic, golden hour, close-up portrait, peaceful', note: 'Realistic pet photography feel' }, { prompt: 'a castle, oil painting, dramatic shadows, wide landscape, mysterious', note: 'Dark fantasy atmosphere' }, { prompt: 'an astronaut, 3D render, neon glow, symmetrical, energetic', note: 'Sci-fi poster style' }, ]; const examplesHtml = examplePrompts.map(ex => `
    ${protectCodeBlock(escapeHtml(ex.prompt))}

    ${ex.note}

    `).join(''); const diffusionSteps = [ '1. Parse prompt → identify subject, style, and modifiers', '2. Start with random noise (pure static)', '3. Denoise step 1 → rough shapes emerge', '4. Denoise step 2 → details and colors form', '5. Denoise step 3 → final refinement and sharpness', ]; return `
    ${icon('palette', '🎨')} ${t(msg, 'book.interactive.textToImageBuildPrompt') || 'Text-to-Image: Building an Image Prompt'}

    Image generation prompts combine categories. Select one option from each row to build a complete prompt:

    ${categoriesHtml}
    Example prompts built from these categories:
    ${examplesHtml}
    How Diffusion Models Work:
    ${diffusionSteps.map(s => `
    ${s}
    `).join('\n')}

    The model starts with random noise and gradually removes it, guided by your text prompt, until a coherent image forms. More specific prompts give the model stronger guidance at each step.

    `; }); // ============================================================ // TextToVideoDemo - Show prompt anatomy for video // ============================================================ result = result.replace(//g, () => { const vidOpts = ld.videoPromptOptions; const vidLabels = ld.videoCategoryLabels; const vidColors: Record = { subject: '#dbeafe', action: '#dcfce7', camera: '#f3e8ff', duration: '#fef3c7' }; const categories = Object.keys(vidOpts).map(key => ({ label: vidLabels[key] || key, options: vidOpts[key], color: vidColors[key] || '#f5f5f4', })); const categoriesHtml = categories.map(cat => { const optionsHtml = cat.options.map((opt, i) => { const style = i === 0 ? ` style="background:${cat.color};border:1px solid #ccc;font-weight:600;"` : ''; return `${opt}`; }).join(' '); return `
    ${cat.label}: ${optionsHtml}
    `; }).join('\n'); const examplePrompts = [ { prompt: 'A bird takes flight, slow pan left, 4 seconds', note: 'Nature documentary style' }, { prompt: 'A wave crashes on rocks, static shot, 6 seconds', note: 'Dramatic landscape footage' }, { prompt: 'A flower blooms in timelapse, dolly zoom, 8 seconds', note: 'Macro nature timelapse' }, ]; const examplesHtml = examplePrompts.map(ex => `
    ${protectCodeBlock(escapeHtml(ex.prompt))}

    ${ex.note}

    `).join(''); return `
    ${icon('video', '🎬')} ${t(msg, 'book.interactive.textToVideoBuildPrompt') || 'Text-to-Video: Building a Video Prompt'}

    Video prompts need subject, action, camera movement, and duration. Select one from each row:

    ${categoriesHtml}
    Example prompts:
    ${examplesHtml}
    Key challenges for video models:
    • Temporal consistency — keeping the subject looking the same across frames
    • Natural motion — realistic movement physics and speed
    • Camera coherence — smooth, intentional camera movement
    `; }); // ============================================================ // PromptBreakdown - Color-labeled prompt segments // ============================================================ result = result.replace(//g, (match, inner) => { const colorValues: Record = { blue: '#dbeafe', green: '#dcfce7', purple: '#f3e8ff', amber: '#fef3c7', pink: '#fce7f3', cyan: '#cffafe', }; const borderColors: Record = { blue: '#93c5fd', green: '#86efac', purple: '#c4b5fd', amber: '#fcd34d', pink: '#f9a8d4', cyan: '#67e8f9', }; const defaultColorOrder = ['blue', 'green', 'purple', 'amber', 'pink', 'cyan']; // Parse parts const parts: { label: string; text: string; color: string }[] = []; const partRegex = /\{\s*label:\s*"([^"]*?)"\s*,\s*text:\s*"([^"]*?)"(?:\s*,\s*color:\s*"([^"]*?)")?\s*\}/g; let pm; while ((pm = partRegex.exec(inner)) !== null) { parts.push({ label: pm[1], text: pm[2], color: pm[3] || defaultColorOrder[parts.length % defaultColorOrder.length] }); } if (parts.length === 0) return ''; const segmentsHtml = parts.map(p => { const bg = colorValues[p.color] || colorValues.blue; const border = borderColors[p.color] || borderColors.blue; return `${escapeHtml(p.label)}${escapeHtml(p.text)}`; }).join(' '); return `
    ${segmentsHtml}
    `; }); // ============================================================ // SpecificitySpectrum - Show all levels from vague to specific // ============================================================ result = result.replace(//g, (match, inner) => { const levels: { level: string; text: string }[] = []; const levelRegex = /\{\s*level:\s*"([^"]*?)"\s*,\s*text:\s*"([^"]*?)"\s*\}/g; let lm; while ((lm = levelRegex.exec(inner)) !== null) { levels.push({ level: lm[1], text: lm[2] }); } if (levels.length === 0) return ''; const barColors = ['#ef4444', '#f97316', '#f59e0b', '#22c55e']; const levelsHtml = levels.map((l, i) => { const width = ((i + 1) / levels.length * 100).toFixed(0); const color = barColors[i] || barColors[barColors.length - 1]; return `
    ${escapeHtml(l.level)}
    ${protectCodeBlock(escapeHtml(l.text))}
    `; }).join('\n'); return `
    Specificity Spectrum
    ${levelsHtml}
    `; }); // ============================================================ // ChainFlowDemo - Static overview of all 4 chain types // ============================================================ result = result.replace(//g, () => { const ct = ld.chainTypes; const seq = ct.find(c => c.id === 'sequential') || ct[0]; const par = ct.find(c => c.id === 'parallel') || ct[1]; const cond = ct.find(c => c.id === 'conditional') || ct[2]; const iter = ct.find(c => c.id === 'iterative') || ct[3]; return `
    ${escapeHtml(seq.name)}
    ${escapeHtml(seq.description)}
    Extract → Analyze → Generate
    ${escapeHtml(par.name)}
    ${escapeHtml(par.description)}
    Input
    ↓
    Sentiment Entities Topics
    ↓
    Merge
    ${escapeHtml(cond.name)}
    ${escapeHtml(cond.description)}
    Classify
    ↙
    If complaint
    ↘
    If question
    ${escapeHtml(iter.name)}
    ${escapeHtml(iter.description)}
    Generate → Evaluate → Refine ↻
    `; }); // ============================================================ // CodeEditor - Static code block with filename header // ============================================================ result = result.replace(//g, (match, attrs) => { const props = extractProps(match); const lang = props.language || ''; const filename = props.filename || ''; // Extract code from code={`...`} const codeMatch = match.match(/code=\{`([\s\S]*?)`\}/); const code = codeMatch ? codeMatch[1] : ''; if (!code) return ''; return `
    ${filename ? `${escapeHtml(filename)}` : ''} ${escapeHtml(lang)}
    ${protectCodeBlock(escapeHtml(code))}
    `; }); // ============================================================ // ChainErrorDemo - Show all 3 scenarios statically // ============================================================ result = result.replace(//g, () => { const scenarios = ld.scenarios; const steps = ld.steps; const stepsStr = steps.map(s => escapeHtml(s.name)).join(' → '); return `
    ${scenarios.map(sc => `
    ${escapeHtml(sc.name)}
    ${escapeHtml(sc.description)}
    ${stepsStr}
    `).join('\n')}
    `; }); // ============================================================ // ValidationDemo - Show validation flow // ============================================================ result = result.replace(//g, () => { const vd = ld.validationDemo; return `
    ${escapeHtml(vd.title)}
    ${escapeHtml(vd.invalidRetry)}
    ${vd.steps.map((s, i) => `${i + 1}. ${escapeHtml(s.name)}`).join('
    ')}
    ✗ ${escapeHtml(vd.outputs.ageMustBeNumber)}
    ↻ ${escapeHtml(vd.outputs.retryingWithFeedback)}
    ✓ ${escapeHtml(vd.outputs.allFieldsValid)}
    ✓ ${escapeHtml(vd.outputs.dataProcessedSuccessfully)}
    ${escapeHtml(vd.validData)}
    ${vd.steps.map((s, i) => `${i + 1}. ${escapeHtml(s.name)}`).join('
    ')}
    ✓ ${escapeHtml(vd.outputs.allFieldsValid)}
    ✓ ${escapeHtml(vd.outputs.dataProcessedSuccessfully)}
    `; }); // ============================================================ // FallbackDemo - Show primary vs fallback paths // ============================================================ result = result.replace(//g, () => { const fd = ld.fallbackDemo; return `
    ${escapeHtml(fd.title)}
    ${escapeHtml(fd.primarySucceeds)}
    ${escapeHtml(fd.steps[0].name)} → ✓
    ${escapeHtml(fd.outputs.deepAnalysisComplete)}
    ${escapeHtml(fd.outputs.resultFromPrimary)}
    ${escapeHtml(fd.useFallback)}
    ${escapeHtml(fd.steps[0].name)} → ✗
    ${escapeHtml(fd.steps[1].name)} → ✓
    ${escapeHtml(fd.outputs.resultFromFallback)}
    `; }); // ============================================================ // ContentPipelineDemo - Show pipeline steps with prompts // ============================================================ result = result.replace(//g, () => { const cpd = ld.contentPipelineDemo; const stepsHtml = cpd.steps.map((s, i) => { const prompt = cpd.prompts[s.id] || ''; const outputKey = s.id as keyof typeof cpd.outputs; const output = cpd.outputs[outputKey] || ''; return `
    ${i + 1}
    ${escapeHtml(s.name)}
    ${prompt && s.id !== 'input' ? `
    ${labels.prompt}: ${protectCodeBlock(escapeHtml(prompt))}
    ` : ''} ${output ? `
    ${labels.output}: ${escapeHtml(output)}
    ` : ''}
    ${i < cpd.steps.length - 1 ? '
    ' : ''}`; }).join('\n'); return `
    → ${escapeHtml(cpd.title)}
    ${stepsHtml}
    `; }); // ============================================================ // SummarizationDemo - Show all 4 strategies // ============================================================ result = result.replace(//g, () => { const html = ld.strategies.map(s => `
    ${escapeHtml(s.name)}
    ${escapeHtml(s.description)}
    ${s.summary ? `
    ${escapeHtml(s.summary)}
    ` : ''}
    `).join(''); return `
    ${html}
    `; }); // ============================================================ // ContextPlayground - Show context blocks with token counts // ============================================================ result = result.replace(//g, () => { const blocks = ld.contextBlocks; const total = blocks.filter(b => b.enabled).reduce((s, b) => s + b.tokens, 0); const html = blocks.map(b => `
    ${b.enabled ? '✓' : '○'} ${escapeHtml(b.label)} ${b.tokens} tokens
    ${escapeHtml(b.content)}
    `).join(''); return `
    Context — ${total} / 200 tokens
    ${html}
    `; }); // ============================================================ // PromptBuilder - Static template with writable fields // ============================================================ result = result.replace(/]*\/>/g, (match) => { const props = extractProps(match); const title = props.title || 'Prompt Builder'; const fields = ld.builderFields.map(f => ({ label: f.label, placeholder: f.placeholder, hint: f.hint, required: f.required || false, })); const fieldsHtml = fields.map(f => `
    ${f.label}${f.required ? ' *' : ''}
    ${f.hint}
    ${escapeHtml(f.placeholder)}
    `).join(''); return `
    ${icon('pencil', '✏️')} ${escapeHtml(title)}

    Fill in the fields below to construct your prompt. Not all fields are required — use what fits your task.

    ${fieldsHtml}
    `; }); // ============================================================ // FrameworkDemo (generic, with props) — fallback // ============================================================ const simpleStaticDemos: string[] = []; for (const component of simpleStaticDemos) { const selfClosingRegex = new RegExp(`<${component}\\s*[^>]*/>`, 'g'); result = result.replace(selfClosingRegex, `

    ${notice}

    `); const withContentRegex = new RegExp(`<${component}[^>]*>[\\s\\S]*?`, 'g'); result = result.replace(withContentRegex, `

    ${notice}

    `); } // ============================================================ // Truly interactive-only components → notice // ============================================================ for (const component of INTERACTIVE_ONLY_COMPONENTS) { const selfClosingRegex = new RegExp(`<${component}\\s*[^>]*/>`, 'g'); result = result.replace(selfClosingRegex, `

    ${notice}

    `); const withContentRegex = new RegExp(`<${component}[^>]*>[\\s\\S]*?`, 'g'); result = result.replace(withContentRegex, `

    ${notice}

    `); } // ============================================================ // Collapsible - render open // ============================================================ result = result.replace(/([\s\S]*?)<\/Collapsible>/g, (match, attrs, children) => { const props = extractProps(attrs); const title = props.title || ''; return `
    ${escapeHtml(title)}
    ${children}
    `; }); // ============================================================ // Multi-Agent Systems diagram // ============================================================ result = result.replace(/
    \s*
    [\s\S]*?Coordinator[\s\S]*?Coder[\s\S]*?<\/div>\s*<\/div>/g, () => { return `
    Multi-Agent System
    Coordinator
    Manages workflow
    ⟷
    Researcher
    Writer
    Critic
    Coder

    Each agent has its own system prompt. The coordinator orchestrates their collaboration through structured messages.

    `; }); // ============================================================ // Prompt Orchestration pipeline diagram // ============================================================ result = result.replace(/
    \s*
    ↓
    ' : ''; return `
    ${s.name}${s.desc ? `
    ${s.desc}` : ''}
    ${arrow}`; }).join('\n'); return `
    Prompt Orchestration Pipeline
    ${stepsHtml}
    `; }); // ============================================================ // Agent → Skills → Prompts hierarchy diagram // ============================================================ result = result.replace(/
    \s*
    [\s\S]*?Prompts are atoms[\s\S]*?<\/div>\s*<\/div>/g, () => { return `
    Agent → Skills → Prompts
    Agent
    Autonomous AI system
    powered by ↓
    Skill
    Skill
    Skill
    Reusable expertise packages
    composed of ↓
    Prompt Prompt Prompt Prompt Prompt
    Prompts are atoms → Skills are molecules → Agents are complete structures
    `; }); // ============================================================ // Cleanup // ============================================================ // Remove Icon components - they're decorative result = result.replace(/]*\/>/g, ''); // Clean up React className to class result = result.replace(/className=/g, 'class='); // Remove JSX expressions that won't render result = result.replace(/\{\/\*[\s\S]*?\*\/\}/g, ''); // Clean up any remaining unknown self-closing components result = result.replace(/<[A-Z][a-zA-Z]+\s*[^>]*\/>/g, ''); // Clean up any remaining unknown components with children result = result.replace(/<[A-Z][a-zA-Z]+[^>]*>[\s\S]*?<\/[A-Z][a-zA-Z]+>/g, ''); // ============================================================ // Convert Tailwind className divs to inline styles for PDF // ============================================================ result = convertTailwindToInline(result); return result; } /** * Convert common Tailwind class patterns in raw HTML blocks to inline styles * so they render correctly in the PDF without a Tailwind runtime. */ function convertTailwindToInline(html: string): string { // Map of Tailwind class → CSS property const tw: Record = { // Display & Flex 'flex': 'display:flex;', 'grid': 'display:grid;', 'inline-block': 'display:inline-block;', 'inline-flex': 'display:inline-flex;', 'hidden': 'display:none;', 'block': 'display:block;', 'flex-col': 'flex-direction:column;', 'flex-row': 'flex-direction:row;', 'flex-wrap': 'flex-wrap:wrap;', 'flex-1': 'flex:1 1 0%;', 'items-center': 'align-items:center;', 'items-start': 'align-items:flex-start;', 'justify-center': 'justify-content:center;', 'justify-between': 'justify-content:space-between;', 'text-center': 'text-align:center;', 'text-left': 'text-align:left;', 'text-right': 'text-align:right;', 'shrink-0': 'flex-shrink:0;', // Gaps 'gap-1': 'gap:0.25em;', 'gap-2': 'gap:0.5em;', 'gap-3': 'gap:0.75em;', 'gap-4': 'gap:1em;', 'gap-6': 'gap:1.5em;', 'gap-8': 'gap:2em;', // Padding 'p-2': 'padding:0.5em;', 'p-3': 'padding:0.75em;', 'p-4': 'padding:1em;', 'p-6': 'padding:1.5em;', 'px-2': 'padding-left:0.5em;padding-right:0.5em;', 'px-3': 'padding-left:0.75em;padding-right:0.75em;', 'px-4': 'padding-left:1em;padding-right:1em;', 'py-2': 'padding-top:0.5em;padding-bottom:0.5em;', 'py-3': 'padding-top:0.75em;padding-bottom:0.75em;', 'pt-2': 'padding-top:0.5em;', 'pt-3': 'padding-top:0.75em;', // Margin 'm-0!': 'margin:0!important;', 'mt-1': 'margin-top:0.25em;', 'mt-2': 'margin-top:0.5em;', 'mb-1': 'margin-bottom:0.25em;', 'mb-2': 'margin-bottom:0.5em;', 'mb-3': 'margin-bottom:0.75em;', 'mb-4': 'margin-bottom:1em;', 'mb-8': 'margin-bottom:2em;', 'my-4': 'margin-top:1em;margin-bottom:1em;', 'my-6': 'margin-top:1.5em;margin-bottom:1.5em;', // Sizing 'w-full': 'width:100%;', 'w-24': 'width:6rem;', 'w-px': 'width:1px;', 'w-20': 'width:5rem;', 'min-w-24': 'min-width:6rem;', 'min-w-20': 'min-width:5rem;', 'h-4': 'height:1rem;', 'w-4': 'width:1rem;', 'h-5': 'height:1.25rem;', 'w-5': 'width:1.25rem;', 'w-32': 'width:3rem;', 'h-32': 'height:3rem;', // Typography 'text-xs': 'font-size:8pt;', 'text-sm': 'font-size:9pt;', 'text-lg': 'font-size:12pt;', 'text-2xl': 'font-size:16pt;', 'font-medium': 'font-weight:500;', 'font-semibold': 'font-weight:600;', 'font-bold': 'font-weight:700;', 'capitalize': 'text-transform:capitalize;', 'whitespace-pre-wrap': 'white-space:pre-wrap;', 'font-mono': 'font-family:var(--font-mono);', 'leading-relaxed': 'line-height:1.65;', 'line-through': 'text-decoration:line-through;', // Borders & Radius 'border': PRINT_READY ? 'border:1px solid #ccc;' : 'border:1px solid #e7e5e4;', 'border-t': PRINT_READY ? 'border-top:1px solid #ccc;' : 'border-top:1px solid #e7e5e4;', 'border-b': PRINT_READY ? 'border-bottom:1px solid #ccc;' : 'border-bottom:1px solid #e7e5e4;', 'rounded': 'border-radius:4px;', 'rounded-lg': 'border-radius:6px;', 'rounded-full': 'border-radius:9999px;', 'overflow-hidden': 'overflow:hidden;', 'overflow-x-auto': 'overflow-x:auto;', // Spacing between children 'space-y-1': '', 'space-y-2': '', 'space-y-4': '', // Colors — grayscale for print, RGB for screen 'text-muted-foreground': PRINT_READY ? 'color:#666;' : 'color:#78716c;', 'text-blue-700': PRINT_READY ? 'color:#333;' : 'color:#1d4ed8;', 'text-blue-600': PRINT_READY ? 'color:#333;' : 'color:#2563eb;', 'text-blue-400': PRINT_READY ? 'color:#666;' : 'color:#60a5fa;', 'text-green-700': PRINT_READY ? 'color:#333;' : 'color:#15803d;', 'text-green-600': PRINT_READY ? 'color:#333;' : 'color:#16a34a;', 'text-green-400': PRINT_READY ? 'color:#666;' : 'color:#4ade80;', 'text-purple-700': PRINT_READY ? 'color:#333;' : 'color:#7e22ce;', 'text-purple-600': PRINT_READY ? 'color:#333;' : 'color:#9333ea;', 'text-purple-400': PRINT_READY ? 'color:#666;' : 'color:#c084fc;', 'text-red-700': PRINT_READY ? 'color:#333;' : 'color:#b91c1c;', 'text-red-600': PRINT_READY ? 'color:#333;' : 'color:#dc2626;', 'text-red-400': PRINT_READY ? 'color:#666;' : 'color:#f87171;', 'text-amber-700': PRINT_READY ? 'color:#333;' : 'color:#b45309;', 'text-amber-400': PRINT_READY ? 'color:#666;' : 'color:#fbbf24;', 'text-cyan-700': PRINT_READY ? 'color:#333;' : 'color:#0e7490;', 'text-cyan-400': PRINT_READY ? 'color:#666;' : 'color:#22d3ee;', 'text-white': 'color:white;', // Backgrounds 'bg-muted/30': PRINT_READY ? 'background:#f2f2f2;' : 'background:#f5f5f4;', 'bg-muted/50': PRINT_READY ? 'background:#f2f2f2;' : 'background:#f5f5f4;', 'bg-muted': PRINT_READY ? 'background:#f2f2f2;' : 'background:#f0f0ee;', 'bg-background': 'background:white;', 'bg-card': 'background:white;', 'bg-blue-100': PRINT_READY ? 'background:#f2f2f2;' : 'background:#dbeafe;', 'bg-blue-50': PRINT_READY ? 'background:#fff;' : 'background:#eff6ff;', 'bg-blue-50/50': PRINT_READY ? 'background:#fff;' : 'background:#f7fbff;', 'bg-green-100': PRINT_READY ? 'background:#f2f2f2;' : 'background:#dcfce7;', 'bg-green-50': PRINT_READY ? 'background:#fff;' : 'background:#f0fdf4;', 'bg-green-50/50': PRINT_READY ? 'background:#fff;' : 'background:#f8fef9;', 'bg-purple-100': PRINT_READY ? 'background:#f2f2f2;' : 'background:#f3e8ff;', 'bg-purple-50': PRINT_READY ? 'background:#fff;' : 'background:#faf5ff;', 'bg-purple-50/50': PRINT_READY ? 'background:#fff;' : 'background:#fdfaff;', 'bg-red-50/50': PRINT_READY ? 'background:#f2f2f2;' : 'background:#fef7f7;', 'bg-red-50': PRINT_READY ? 'background:#f2f2f2;' : 'background:#fef2f2;', 'bg-amber-50/50': PRINT_READY ? 'background:#fff;' : 'background:#fffdf7;', 'bg-amber-50': PRINT_READY ? 'background:#fff;' : 'background:#fffbeb;', 'bg-cyan-50/50': PRINT_READY ? 'background:#fff;' : 'background:#f0fdff;', // Border colors 'border-blue-200': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#bfdbfe;', 'border-blue-800': '', 'border-blue-300': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#93c5fd;', 'border-green-200': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#bbf7d0;', 'border-green-900': '', 'border-green-300': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#86efac;', 'border-purple-200': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#e9d5ff;', 'border-purple-800': '', 'border-purple-300': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#c4b5fd;', 'border-red-200': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#fecaca;', 'border-red-900': '', 'border-amber-200': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#fde68a;', 'border-amber-900': '', 'border-cyan-200': PRINT_READY ? 'border-color:#ccc;' : 'border-color:#a5f3fc;', 'border-cyan-900': '', // Grid 'grid-cols-2': 'grid-template-columns:1fr 1fr;', 'grid-cols-4': 'grid-template-columns:1fr 1fr 1fr 1fr;', 'md:grid-cols-2': 'grid-template-columns:1fr 1fr;', 'md:grid-cols-3': 'grid-template-columns:1fr 1fr 1fr;', 'md:grid-cols-4': 'grid-template-columns:1fr 1fr 1fr 1fr;', // Extra borders & bg 'border-2': 'border-width:2px;', 'border-primary': PRINT_READY ? 'border-color:#000;' : 'border-color:#7c3aed;', 'bg-primary/10': PRINT_READY ? 'background:#f2f2f2;' : 'background:rgba(124,58,237,0.1);', }; // Process each element that has a class attribute return html.replace(/class="([^"]+)"/g, (match, classes: string) => { const classList = classes.split(/\s+/); const styles: string[] = []; const remainingClasses: string[] = []; for (const cls of classList) { // Skip dark mode variants if (cls.startsWith('dark:')) continue; // Skip responsive prefixes that we can't handle (except md: grid which we keep) if (cls.startsWith('sm:') || cls.startsWith('lg:') || cls.startsWith('xl:')) continue; // Handle md: prefix const effectiveCls = cls.startsWith('md:') ? cls : cls; if (tw[effectiveCls]) { styles.push(tw[effectiveCls]); } else { remainingClasses.push(cls); } } if (styles.length === 0) return match; const styleStr = styles.filter(Boolean).join(''); const classStr = remainingClasses.length > 0 ? ` class="${remainingClasses.join(' ')}"` : ''; return `${classStr} style="${styleStr}"`.trim(); }); } /** * Convert all inline color values in style attributes to grayscale for print. * Replaces colored hex values and rgba with gray equivalents. */ function convertStylesToGrayscale(html: string): string { if (!PRINT_READY) return html; // Map of specific hex colors to grayscale const colorToGray: Record = { // Blues '#3b82f6': '#666', '#2563eb': '#333', '#1d4ed8': '#333', '#60a5fa': '#999', '#dbeafe': '#f2f2f2', '#bfdbfe': '#ccc', '#93c5fd': '#ccc', '#eff6ff': '#fff', // Greens '#22c55e': '#666', '#16a34a': '#333', '#15803d': '#333', '#4ade80': '#999', '#dcfce7': '#f2f2f2', '#bbf7d0': '#ccc', '#86efac': '#ccc', '#f0fdf4': '#fff', '#166534': '#333', // Purples '#7c3aed': '#000', '#9333ea': '#333', '#7e22ce': '#333', '#c084fc': '#999', '#a78bfa': '#666', '#f3e8ff': '#f2f2f2', '#e9d5ff': '#ccc', '#c4b5fd': '#ccc', '#faf5ff': '#fff', '#5b2d8e': '#333', // Reds '#dc2626': '#333', '#b91c1c': '#333', '#f87171': '#999', '#fef2f2': '#f2f2f2', '#fecaca': '#ccc', '#450a0a': '#333', // Ambers '#f59e0b': '#666', '#d97706': '#333', '#b45309': '#333', '#fbbf24': '#999', '#92400e': '#333', '#fef3c7': '#f2f2f2', '#fde68a': '#ccc', '#fffbeb': '#fff', '#fcd34d': '#ccc', // Cyan '#0e7490': '#333', '#22d3ee': '#999', '#cffafe': '#f2f2f2', '#a5f3fc': '#ccc', '#67e8f9': '#ccc', // Pinks '#fce7f3': '#f2f2f2', '#f9a8d4': '#ccc', // Rose '#ef4444': '#333', '#f97316': '#555', // Misc '#e5e7eb': '#ccc', '#78716c': '#666', '#c41d7f': '#333', // Traffic light dots '#ff5f56': '#999', '#ffbd2e': '#999', '#27c93f': '#999', '#f8fafc': '#fff', '#f8fdf9': '#fff', '#faf8ff': '#fff', '#fffbf5': '#fff', '#fefce8': '#fff', '#166534': '#333', '#450a0a': '#333', '#92400e': '#333', }; // Replace hex colors in both inline style attributes and CSS let result = html; for (const [color, gray] of Object.entries(colorToGray)) { result = result.split(color).join(gray); } // Replace rgba colors with gray result = result.replace(/rgba\(\d+,\s*\d+,\s*\d+,\s*[\d.]+\)/g, '#f2f2f2'); // Replace linear-gradient colors result = result.replace(/linear-gradient\([^)]*\)/g, (match) => { let g = match; for (const [color, gray] of Object.entries(colorToGray)) { g = g.split(color).join(gray); } return g; }); return result; } /** * Convert prompt variables like ${field} and ${years:10} to printable blanks * \${name} or ${name} → _______ (name) * \${name:default} or ${name:default} → _______ (name, e.g. default) */ function convertPromptVariables(text: string): string { return text.replace(/\\?\$\{([^:}]+)(?::([^}]*))?\}/g, (_, name, defaultVal) => { if (defaultVal) { return `_______ (${name}, e.g. ${defaultVal})`; } return `_______ (${name})`; }); } /** * Escape HTML entities */ function escapeHtml(text: string): string { return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Convert MDX to HTML */ function mdxToHtml(mdx: string): string { let html = mdx; // Code blocks FIRST - protect content from all markdown processing // Handle quadruple-backtick blocks first (which can contain triple backticks) // Fences must be at start of line html = html.replace(/^````(\w*)\n([\s\S]*?)^````\s*$/gm, (_, lang, code) => { const protectedCode = protectCodeBlock(escapeHtml(code.trim())); return `
    ${protectedCode}
    `; }); // Then handle triple-backtick blocks (fences at start of line) html = html.replace(/^```(\w*)\n([\s\S]*?)^```\s*$/gm, (_, lang, code) => { const protectedCode = protectCodeBlock(escapeHtml(code.trim())); return `
    ${protectedCode}
    `; }); // Inline code - protect before italic/bold html = html.replace(/`([^`]+)`/g, (_, code) => { const protectedCode = protectCodeBlock(escapeHtml(code)); return `${protectedCode}`; }); // Headers html = html.replace(/^######\s+(.*)$/gm, '
    $1
    '); html = html.replace(/^#####\s+(.*)$/gm, '
    $1
    '); html = html.replace(/^####\s+(.*)$/gm, '

    $1

    '); html = html.replace(/^###\s+(.*)$/gm, '

    $1

    '); html = html.replace(/^##\s+(.*)$/gm, '

    $1

    '); html = html.replace(/^#\s+(.*)$/gm, '

    $1

    '); // Bold and italic html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1'); html = html.replace(/\*\*(.+?)\*\*/g, '$1'); html = html.replace(/\*(.+?)\*/g, '$1'); // Underscore italic: only match single words wrapped in underscores, not runs of underscores html = html.replace(/(?$1'); // Links html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); // Images html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); // Horizontal rules html = html.replace(/^---$/gm, '
    '); // Lists (basic) html = html.replace(/^-\s+(.*)$/gm, '
  • $1
  • '); html = html.replace(/^(\d+)\.\s+(.*)$/gm, '
  • $2
  • '); // Wrap consecutive
  • in