Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Convert markdown into a self-contained HTML slide deck with layouts, speaker notes, keyboard navigation, and a content-density linter. Use when building a deck from markdown, cutting an overloaded deck, or timing a talk.
.claude/skills/borghei-md-slides/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 111% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 259% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 223% | 0% |
Turn a markdown file into a slide deck that is one HTML file: six layouts, speaker notes, keyboard and remote navigation, light/dark theming, and handout printing. The density linter is the part that matters most — it catches the slides an audience cannot absorb before you are standing in front of them.
---??? marker on each content slideBefore building, confirm these inputs. If any is unknown or vague, ASK — do not assume:
Stop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact.
way to learn the same thing.
bashpython3 markdown-html/md-slides/scripts/slide_density_linter.py \ --input markdown-html/md-slides/assets/sample_deck.md --profile present python3 markdown-html/md-slides/scripts/md_to_slides.py \ --input markdown-html/md-slides/assets/sample_deck.md \ --out build/deck.html --format text
* markers — those slides have no notes, so their duration isguessed from on-slide content and is the least reliable number in the sheet.
bashpython3 markdown-html/md-slides/scripts/notes_runsheet.py \ --input markdown-html/md-slides/assets/sample_deck.md \ --wpm 130 --target-minutes 15 --format text python3 markdown-html/md-slides/scripts/notes_runsheet.py \ --input markdown-html/md-slides/assets/sample_deck.md \ --format markdown > build/runsheet.md
## heading to get a first pass with the document's ownstructure.
than a slide's budget. That failure list is the edit plan.
bashpython3 markdown-html/md-slides/scripts/md_to_slides.py \ --input markdown-html/md-slides/assets/sample_deck.md --split-on h2 --out build/draft.html python3 markdown-html/md-slides/scripts/slide_density_linter.py \ --input markdown-html/md-slides/assets/sample_deck.md --profile present --format json
| Metric | present target | Warn | Error | read warn / error | |--------|------------------|------|-------|---------------------| | Words per slide | <= 40 | 50 | 75 | 90 / 130 | | Bullets per slide | <= 5 | 6 | 8 | 8 / 12 | | Words per bullet | <= 8 | 12 | 20 | 18 / 28 | | Heading characters | <= 50 | 60 | 90 | 70 / 100 | | Table rows | <= 5 | 6 | 9 | 9 / 14 | | Code lines | <= 10 | 12 | 20 | 18 / 30 |
Every threshold is a proxy for one rule: a slide must be readable in under 5 seconds, or it competes with the presenter. An audience cannot read and listen simultaneously — when a slide carries prose, the room reads it faster than you can say it and then disengages.
title, section, quote, and image layouts are exempt from the body rules.
| Layout | Use for | Limit | |--------|---------|-------| | title | Opening slide | One per deck; heading plus one subtitle line | | section | Divider between movements | One every 5-8 content slides | | default | Heading plus content | The workhorse; full density budget applies | | two-column | A comparison, or image beside explanation | RECOMMENDED] Not a way to fit twice the content | | quote | One sentence worth sitting with | One per deck; a second dilutes the first | | image | Full-bleed visual | Alt text mandatory — the linter errors without it |
| Talk length | Content slides | Note | |-------------|----------------|------| | 5 min | 5-7 | ~45s per slide | | 15 min | 12-18 | The common conference slot | | 30 min | 20-30 | Plus 2-3 section dividers | | 60 min | 30-45 | Needs interaction, not more slides |
The runsheet adds a 4-second transition allowance per slide — real, and routinely forgotten. Thirty slides carry two minutes of dead air before anyone speaks.
| Content | Belongs | |---------|---------| | The claim | Slide heading | | The evidence, compressed | Slide body, at label length | | The sentences | Speaker notes PROVEN] | | The full table | Appendix slide | | The caveat | Speaker notes, then Q&A |
| Context | Minimum | |---------|---------| | Monitor / screen share | 4.5:1 (WCAG AA) | | Well-lit room | 7:1 | | Bright room, weak projector | 10:1 |
PROVEN] Present light in a bright room, dark in a dark one. The T key toggles theme so this is decided in the room, not an hour before.
Mistake: Full paragraphs on every slide, because the deck must also work as a leave-behind for people who were not there. Why it happens: It is one artifact instead of two, and the request to "make sure it stands alone" is reasonable on its face. Instead: Pick one job. A presented deck uses the present budget with the sentences in speaker notes; a circulated deck uses --profile read. Trying to serve both produces something too dense to present and too fragmentary to read. If it will mostly be read, write a document and build a thin deck that points at it.
Mistake: Writing each bullet as a complete sentence, so the slide reads correctly on its own. Why it happens: Fragments feel unfinished while drafting, and complete sentences feel more rigorous. Instead: A bullet is a label the presenter expands, not a sentence the audience reads. Past roughly 12 words it is prose and the room stops listening. Move the sentence into the speaker notes, where it is genuinely useful — that is what notes are for, and it is why the linter flags a dense slide with empty notes.
Mistake: Heading a slide with its subject — "Options", "Results", "Storage costs". Why it happens: It matches how the deck was outlined, and outlines are built from topics. Instead: Write the heading as the sentence you want remembered: "Cold data is paying hot prices", "Latency held; spend fell 31%". Someone who reads only the headings should still receive the argument. This single change improves a deck more than any layout decision.
Mistake: Discovering the deck runs long and planning to talk quickly rather than cutting slides. Why it happens: Cutting means giving up content you already built and believe in. Instead: Cut. Speaking faster converts an over-long talk into an over-long talk nobody follows, and it eliminates the pauses that let a point land. The runsheet says "cut content, do not speak faster" for this reason.
Mistake: Authoring in a windowed browser and presenting full screen without checking. Why it happens: The deck looks finished on the laptop, and full screen feels like the same thing but bigger. Instead: Open it full screen on the actual display and walk every slide with the actual remote. Type scales with viewport width, so every size decision changes — tables and code blocks are set smaller than body text and are the first things to become unreadable from the back row. Presenter remotes send PageUp/PageDown, which is also worth confirming before you are on stage.
| File | Purpose | |------|---------| | scripts/md_to_slides.py | CLI: build a self-contained HTML deck with inlined theme and navigation | | scripts/slide_render.py | Slide splitting, layouts, note extraction, escaping-first renderer — imported by md_to_slides.py, not a CLI | | scripts/slide_density_linter.py | Flag slides over the word, bullet, table, and code budgets; CI gate | | scripts/notes_runsheet.py | Timed runsheet from speaker notes; text, JSON, or markdown | | references/slide-density-and-layout.md | Thresholds and their rationale, layout patterns, deck length | | references/deck-accessibility-and-delivery.md | Focus management, keyboard interface, projection contrast, pre-flight | | assets/sample_deck.md | Working deck using every layout; passes the density gate | | assets/deck_theme.css | Bundled deck theme — this skill's own copy | | assets/deck_nav.js | Inlined navigation: keyboard, hash routing, notes, theme toggle | | assets/deck_outline_template.md | Starting structure for a new deck |
All scripts share one exit-code contract: 0 clean, 2 gate failed (findings at or above the threshold), 1 the tool itself errored. A CI job can therefore tell a real defect from a broken invocation.
Three CLI tools, one module. slide_render.py is a library, not a fourth command — it holds the parser and renderer that md_to_slides.py imports. A single-file converter came to 324 lines, over the 300-line ceiling, and the only ways to fit were deleting docstrings or dropping features. Splitting CLI from parser is the remedy the tool-design standard prescribes for an oversized script, and same-directory imports keep the package self-contained: nothing here imports from another skill, and md-document carries its own separate copy of the equivalent renderer rather than sharing this one.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 8,354 | 13,618 | +63% | 1 | 1 | 0% | 1,292 | 3,264 | +153% | 0 | 0 | — |
case-10 | fail→pass | 10,044 | 4,089 | -59% | 1 | 1 | 0% | 1,661 | 3,505 | +111% | 0 | 0 | — |
case-02 | fail→fail | 12,026 | 9,497 | -21% | 1 | 1 | 0% | 1,569 | 3,293 | +110% | 0 | 0 | — |
case-03 | fail→fail | 31,122 | 6,902 | -78% | 1 | 1 | 0% | 6,140 | 3,300 | -46% | 0 | 0 | — |
case-04 | fail→pass | 12,174 | 6,169 | -49% | 1 | 1 | 0% | 1,862 | 3,729 | +100% | 0 | 0 | — |
case-05 | pass→pass | 9,518 | 7,244 | -24% | 1 | 1 | 0% | 1,738 | 4,149 | +139% | 0 | 0 | — |
case-11 | fail→pass | 6,204 | 2,184 | -65% | 1 | 1 | 0% | 895 | 3,215 | +259% | 0 | 0 | — |
case-06 | fail→pass | 13,310 | 4,962 | -63% | 1 | 1 | 0% | 1,971 | 3,611 | +83% | 0 | 0 | — |
case-07 | fail→pass | 6,525 | 3,469 | -47% | 1 | 1 | 0% | 1,098 | 3,549 | +223% | 0 | 0 | — |
case-08 | fail→pass | 15,186 | 2,080 | -86% | 1 | 1 | 0% | 2,360 | 3,228 | +37% | 0 | 0 | — |
case-09 | fail→pass | 8,231 | 2,855 | -65% | 1 | 1 | 0% | 1,296 | 3,396 | +162% | 0 | 0 | — |
case-12 | fail→pass | 15,350 | 3,670 | -76% | 1 | 1 | 0% | 2,650 | 3,473 | +31% | 0 | 0 | — |
case-13 | fail→pass | 4,803 | 2,094 | -56% | 1 | 1 | 0% | 619 | 3,209 | +418% | 0 | 0 | — |
case-14 | fail→pass | 4,057 | 2,062 | -49% | 1 | 1 | 0% | 477 | 3,137 | +558% | 0 | 0 | — |
case-15 | fail→pass | 9,796 | 7,598 | -22% | 1 | 1 | 0% | 1,464 | 3,656 | +150% | 0 | 0 | — |
case-16 | fail→pass | 11,795 | 5,883 | -50% | 1 | 1 | 0% | 1,870 | 3,715 | +99% | 0 | 0 | — |
case-17 | fail→pass | 12,089 | 2,155 | -82% | 1 | 1 | 0% | 1,779 | 3,195 | +80% | 0 | 0 | — |
case-18 | fail→pass | 10,256 | 2,715 | -74% | 1 | 1 | 0% | 1,662 | 3,327 | +100% | 0 | 0 | — |
case-19 | fail→pass | 20,519 | 1,807 | -91% | 1 | 1 | 0% | 3,359 | 3,141 | -6% | 0 | 0 | — |
case-20 | pass→pass | 10,409 | 6,861 | -34% | 1 | 1 | 0% | 1,505 | 3,814 | +153% | 0 | 0 | — |
case-21 | pass→pass | 10,118 | 7,233 | -29% | 1 | 1 | 0% | 1,498 | 3,951 | +164% | 0 | 0 | — |
case-22 | pass→pass | 8,786 | 2,162 | -75% | 1 | 1 | 0% | 1,418 | 3,200 | +126% | 0 | 0 | — |
case-23 | pass→pass | 11,649 | 2,234 | -81% | 1 | 1 | 0% | 1,740 | 3,220 | +85% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 20 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +65 percentage points is the difference between those two pass rates over the 20 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.