import fs from 'node:fs' import { resolve } from 'node:path' import { parser } from '@slidev/cli' const TOTAL_SECONDS = 30 * 60 const data = await parser.load(resolve('.'), resolve('slides.md')) const slides = data.slides.map((s, i) => ({ n: i + 1, name: s.frontmatter?.name || null, raw: s.content || '', })) function stripText(raw) { let t = raw t = t.replace(//g, ' ') t = t.replace(//gi, ' ') t = t.replace(/```[\s\S]*?```/g, ' [code example] ') t = t.replace(/<[^>]+>/g, ' ') t = t.replace(/^\s*::.*::\s*$/gm, ' ') t = t.replace(/^\s*#\s*/gm, '') t = t.replace(/^\s*[-*]\s+/gm, '') t = t.replace(/^\s*\d+\.\s+/gm, '') t = t.replace(/\[(.*?)\]\((.*?)\)/g, '$1') t = t.replace(/[`*_~]/g, '') t = t.replace(/\|/g, ' ') t = t.replace(/\n{3,}/g, '\n\n') const lines = t.split(/\r?\n/).map(l => l.trim()).filter(Boolean) return lines } function simplify(line) { let t = line const repl = [ [/\binfrastructure\b/gi, 'infra'], [/\bconfiguration\b/gi, 'config'], [/\bvalidation\b/gi, 'check'], [/\breliability\b/gi, 'stable work'], [/\bdeclarative\b/gi, 'declared'], [/\borchestrator\b/gi, 'controller'], [/\bdistributed\b/gi, 'spread out'], [/\bparadigm\b/gi, 'approach'], [/\bpragmatism\b/gi, 'practical work'], [/\bguarantees\b/gi, 'safety rules'], [/\bprovisioning\b/gi, 'setup'], [/\bcompiler\b/gi, 'compiler check'], ] for (const [re, to] of repl) t = t.replace(re, to) t = t.replace(/\bIaC\b/g, 'I A C').replace(/\bCI\/CD\b/g, 'C I slash C D').replace(/24×7×365/g, 'all day, every day') t = t.replace(/\s+—\s+/g, '. ').replace(/\s+->\s+/g, '. then ').replace(/,\s*/g, '. ') t = t.replace(/\s{2,}/g, ' ').trim() return t } function say(line) { const rules = [ [/\bInfrastructure\b/gi, 'Infrastructure (IN-fruh-STRUHK-cher)'], [/\bautomation\b/gi, 'automation (aw-tuh-MAY-shun)'], [/\bconfiguration\b/gi, 'configuration (kun-fig-yuh-RAY-shun)'], [/\bprovisioning\b/gi, 'provisioning (pruh-VIH-zhuh-ning)'], [/\bcompiler\b/gi, 'compiler (kum-PIE-ler)'], [/\breliability\b/gi, 'reliability (rih-lai-uh-BIH-luh-tee)'], [/\bvalidation\b/gi, 'validation (val-ih-DAY-shun)'], [/\bdeclarative\b/gi, 'declarative (dih-KLAR-uh-tiv)'], [/\borchestrator\b/gi, 'orchestrator (OR-kes-tray-ter)'], [/\bparadigm\b/gi, 'paradigm (PAIR-uh-dym)'], [/\bRust\b/g, 'Rust (rahst)'], [/\bSerde\b/g, 'Serde (SER-dee)'], [/\bOption\b/g, 'Option (OP-shun)'], [/\benum\b/gi, 'enum (EE-num)'], [/\bKubernetes\b/gi, 'Kubernetes (koo-ber-NET-eez)'], [/\bAnsible\b/gi, 'Ansible (AN-suh-buhl)'], [/\bTerraform\b/gi, 'Terraform (TEH-ruh-form)'], [/\bCI\/CD\b/g, 'CI/CD (see-eye / see-dee)'], [/\bIaC\b/g, 'IaC (eye-ay-see)'], ] let t = line for (const [re, rep] of rules) t = t.replace(re, rep) t = t.replace(/\s+—\s+/g, ' / ').replace(/\.\s+/g, '. / ').replace(/:\s+/g, ': / ') return t } function phase(n) { if (n <= 3) return ['Act 1 - Hook & Promise', 'Low -> Medium', 'Credibilidad y dolor real', 'Calma, pausas limpias'] if (n <= 7) return ['Act 2 - Escalation', 'Medium -> High', 'Crecimiento de complejidad sin control', 'Ritmo creciente'] if (n <= 14) return ['Act 3 - Problem Anatomy', 'High', 'Causa raiz y costo de fallar tarde', 'Didactico, frases cortas'] if (n <= 18) return ['Act 4 - Turning Point', 'Peak', 'No faltan tools, falta paradigma', 'Silencios intencionales'] if (n <= 23) return ['Act 5 - Resolution', 'High -> Medium', 'Types y compiler como respuesta', 'Claro y tecnico'] if (n <= 31) return ['Act 6 - Proof in Production', 'Medium -> High', 'Casos reales e impacto operativo', 'Energetico y concreto'] return ['Act 7 - Close & CTA', 'Medium -> Low', 'Cierre emocional con accion', 'Lento y memorable'] } const slideTexts = slides.map(s => ({ ...s, lines: stripText(s.raw) })) // 1) reader-script-en.md let out = '# Rustikon 2026 - Slide Text (Reader Version)\n\n' out += 'One section per real Slidev slide (parser-based count).\n\n' for (const s of slideTexts) { out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` const [story, tension, emphasis, delivery] = phase(s.n) out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` if (!s.lines.length) out += '[No readable text]\n\n' else out += s.lines.join('\n') + '\n\n' } fs.writeFileSync('reader-script-en.md', out) // 2) pronunciation out = '# Rustikon 2026 - Reader Script with Pronunciation Guide\n\n' out += 'Use this for practice. Read the SAY line.\n\n' for (const s of slideTexts) { out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` const [story, tension, emphasis, delivery] = phase(s.n) out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` if (!s.lines.length) { out += 'EN: [No readable text]\nSAY: [No readable text]\n\n' continue } for (const l of s.lines) out += `EN: ${l}\nSAY: ${say(l)}\n\n` } fs.writeFileSync('reader-script-en-pronunciation.md', out) // 3) simple out = '# Rustikon 2026 - Simple Reader Script (Easy English)\n\n' out += 'Short and clear lines for easy pronunciation.\n\n' for (const s of slideTexts) { out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` const [story, tension, emphasis, delivery] = phase(s.n) out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` if (!s.lines.length) { out += '[No readable text]\n\n' continue } for (const l of s.lines) out += `${simplify(l)}\n\n` } fs.writeFileSync('reader-script-en-simple.md', out) // 4) live out = '# Rustikon 2026 - Live Reader Script (Speech Only)\n\n' out += 'Only lines to speak. Legend: `/` short pause, `//` long pause.\n\n' for (const s of slideTexts) { out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` const [story, tension, emphasis, delivery] = phase(s.n) out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` const spoken = s.lines.map(simplify).filter(l => /[A-Za-z]/.test(l) && !/^[-=]+$/.test(l)) if (!spoken.length) { out += '(No spoken text. Move forward.)\n\n' continue } for (const l of spoken) out += `${l.replace(/\.\s+/g, '. / ').replace(/\s+—\s+/g, ' / ')}\n\n` } fs.writeFileSync('reader-script-en-live.md', out) // 5) live 30-min const weights = slideTexts.map(s => { const words = s.lines.join(' ').split(/\s+/).filter(Boolean).length return 6 + words }) let secs = weights.map(w => Math.round((w / weights.reduce((a,b)=>a+b,0)) * TOTAL_SECONDS)) const minSec=15,maxSec=90 secs = secs.map(v => Math.max(minSec, Math.min(maxSec, v))) let diff = TOTAL_SECONDS - secs.reduce((a,b)=>a+b,0) while (diff !== 0) { let changed = false for (let i=0; i 0 && secs[i] < maxSec) { secs[i]++; diff--; changed = true } else if (diff < 0 && secs[i] > minSec) { secs[i]--; diff++; changed = true } } if (!changed) break } const mmss = (t)=>`${String(Math.floor(t/60)).padStart(2,'0')}:${String(t%60).padStart(2,'0')}` out = '# Rustikon 2026 - Live Reader (30-Min Timing)\n\n' out += 'Total target: 30:00 (1800s)\n\n' out += 'How to use:\n- Keep your pace near each slide target.\n- If you are late, cut one example line and move on.\n- If you are early, add one short emphasis line.\n\n' let cum = 0 for (let i=0;i /[A-Za-z]/.test(l) && !/^[-=]+$/.test(l)) if (!spoken.length) out += '(No spoken text. Move forward.)\n\n' else for (const l of spoken) out += `${l.replace(/\.\s+/g, '. / ')}\n\n` } out += '## Checkpoints\n\n' for (const mark of [5,10,15,20,25,30]) { const target = mark * 60 let c = 0, slideN = 1 for (let i=0;i= target) { slideN = i + 1; break } } out += `- ${String(mark).padStart(2,'0')}:00 -> around Slide ${slideN}\n` } fs.writeFileSync('reader-script-en-live-30min.md', out) // 6) key moments for 35 slides const key = `# Rustikon 2026 - Key Story Moments\n\nUse this as your minimal narrative map for a 30-minute talk.\n\n## Moment 1 - Hook (Slides 1-3)\nPurpose: Win attention and establish credibility.\n\nAnchor line:\n"I have lived this problem for decades, and I wanted one thing: to sleep."\n\n## Moment 2 - Rising Tension (Slides 4-7)\nPurpose: Show complexity growing faster than control.\n\nAnchor line:\n"We changed tools many times, but not the model."\n\n## Moment 3 - Crisis / Root Cause (Slides 8-14)\nPurpose: Make the cost of late failure undeniable.\n\nAnchor line:\n"Fail late is expensive. Fail early is cheap."\n\n## Moment 4 - Turning Point (Slides 15-18)\nPurpose: Reframe the problem.\n\nAnchor line:\n"The problem was not the tools. The problem was the paradigm."\n\n## Moment 5 - Proof in Production (Slides 24-31)\nPurpose: Show real operational evidence.\n\nAnchor line:\n"At 3 AM, the system responds first. Not me."\n\n## Moment 6 - Emotional Close + CTA (Slides 33-35)\nPurpose: Close with trust and clear action.\n\nAnchor line:\n"Rust gave me deterministic systems and better sleep."\n\n## One-Line Backup Version\n1. I lived this problem for decades.\n2. Complexity grew; certainty did not.\n3. Late failures are the real tax.\n4. Types changed the paradigm.\n5. Production proof: lower MTTR and deterministic workflows.\n6. Start small: model infrastructure as types.\n` fs.writeFileSync('key-moments-storytelling.md', key) console.log('Real Slidev slides:', slideTexts.length) console.log('Docs regenerated with parser-based slide count.')