Talk, Don't Type: Turning Spoken Ideas into Visual Slide Decks

Two frictions show up in almost every “AI content” tool.
The first is the blank prompt box. You already know what you think. The product still asks you to write a structured brief about it — audience, hook, tone, three bullets — before anything happens.
The second is template fatigue. Visual generators pick a handful of layouts, swap the text, tint the accent color, and call it a design. Every deck looks like it came from the same Figma file.
Boykli is the pipeline I wanted instead: spoken ramble → extract a real argument → let a model design the slides as HTML, CSS, and inline SVG → render a PDF that matches the preview. Voice is how ideas get in. Generative layout is how they get out looking different twice in a row.
Stage 1: Talk, don’t brief
The voice agent is a live interview, not a dictation box. You talk for a couple of minutes. It asks one question at a time — a specific story, a number, a named tool, an opinion other people get wrong — then it stops.
Audio is native live, not “record a blob, then transcribe.” The browser streams to a WebSocket proxy that attaches a Vertex or Google AI Studio key the client never sees. Tickets expire. A second tab replaces the first. Heartbeats drop sleeping clients. Upstream close codes (1005/1006) have to be sanitized or they take down the process.
The hard part is not getting words. Live audio already gives you a transcript. The hard part is that people do not speak in posts. They say “um,” restart the sentence, wander, then land the actual point three turns later. I did not build a separate filler-stripping NLP stage. The interviewer is instructed to keep digging until it has a concrete scene plus an opinion. Then it calls a tool to end the session. Drafts are generated from the saved transcript on the server — never from whatever the client claims the conversation was.
A second model pass turns that transcript into three posts (story, framework, opinion) with a strict rule: only facts that were said. No invented “I almost quit” beats. No “Here’s the thing.” JSON only. If the speaker never named a metric, the post does not grow one.
It still fails in recognizable ways. Domain jargon gets smoothed into generic “the system.” If you never told a story, the interviewer cannot invent one you will recognize. A two-minute ramble with no example produces polite, empty drafts. That is the model doing what it was told: don’t fabricate.
Stage 2: The model is the layout engine
Once you have an argument, a template carousel is the boring next step. I didn’t want that. I wanted Neo-Brutalist one run, Bauhaus the next, a fake terminal after that — not a color swap on the same six rectangles.
So the model is treated as a UI designer. It must emit a complete HTML document: fixed 1080×1080 (or 1080×1350) slides, inline CSS, inline SVG. No JavaScript. No raster <img> tags (they show up as white boxes in the sandbox). No Tailwind — class names that mean nothing once you isolate the page.
The model cannot see rendered font metrics. It will overflow a box, stack absolute copy, put a yellow rectangle through a headline, or set .slide { height: auto } and collapse the iframe. The contract is how we fake spatial reasoning:
- Document flow only. Absolute positioning is reserved for overlays we inject later.
- Overflow hidden, word-break on text, type floors (body never under 21px on that canvas).
- Each archetype is a checklist: hard black borders and offset shadows for Neo-Brutalist; type zone vs geometry zone for Bauhaus; near-black field and monospace for Terminal.
- If output is truncated (
finish_reason: length), we throw it away. A half-</div>is not a deck.
We still repair after the fact. Weaker models emit soft blur shadows on a neo-brutalist brief; we force hard frames onto cards and CTAs without wrapping whole columns (that framed half-slides). Bauhaus decks put white shapes over type; we push decorative empties behind and only rewrite white-on-cream when we find an explicit light background. Guessing “missing bg = white” destroyed inverted black slides.
Grok, Claude, Gemini, and GPT all get the same contract. Latency and obedience are the trade-off. Faster models invent softer UI and more @handles. Slower ones still overflow; they just overflow with better copy. I have not found a model that knows the pixel height of its own headline.
Stage 3: Identity is not generative
If you ask the model to “include the brand,” it hallucinates a second logo or invents @YOURBRAND when you sent nothing.
So it never draws identity. With a brand kit on, the bottom 220px of every slide is a dead zone. After generation we parse the HTML, delete any badge or logo it smuggled in, and inject ours: handle and avatar bottom-left, logo bottom-right, URLs allowlisted, handle escaped. Brand colors only attach to the quiet “Normal” design. Loud archetypes keep their own palettes.
Preview is an iframe of that HTML. LinkedIn wants a PDF. Client canvas capture drifts — fonts, emoji, a different picture than the iframe. The matching path is a shared headless Chrome: one browser for the process, a queue, a timeout that closes the page, idle shutdown, relaunch on crash. Scripts go first. We wait for fonts with a cap (network idle hung on font CDNs), screenshot a clipped 1080×H viewport at 2×, one slide in memory at a time. Transparent regions used to become black pages. .slide is pinned in capture CSS because the model loves width: 100%.
The model designs the slide. Branding is added separately.
This is the same Node process as everything else. Preview and PDF share a CSS engine. A render storm can contend with voice sockets and other generation. That is not elegant. It is why the queue exists.
ramble (live audio)
→ interview until there is a scene + an opinion
→ posts from the saved transcript
→ optional: model emits HTML/CSS/SVG under the contract
→ parse, strip junk, overlay brand
→ pooled Chrome → PDF
What still breaks
Voice still loses people who only speak in abstractions. It will not magically recover a metric you never said. Live audio is also picky about keys, quotas, and one-tab-at-a-time; that is operational, not poetic.
On slides: split-screen still dies if the model drops height: auto on the canvas. Inline SVG paths clip when a viewBox doesn’t match the 1080 box. Token cutoffs still happen on eight-slide portrait decks. I trust the canvas, the safe zone, and the repair pass more than I trust the raw string.
I would still generate HTML. Templates cannot do a different Bauhaus poster every time. Next time I would constrain the legal DOM more tightly and hammer less CSS after the fact. The repair pass is how you can tell we designed this after the screenshots looked wrong.
Decks from this pipeline
More coming soon — from your suggestions.