mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-10-10 03:27:56 +00:00
Completes the markdown-html/ domain at 5 skills. The Tier-3 use case from
Shihipar's essay ("Slide Decks"): a markdown deck (slides separated by
--- HR boundaries or # H1 headings, with optional <!-- notes: ... -->
presenter notes blocks) becomes a single-file HTML presentation with
keyboard nav, presenter mode, and print-to-PDF.
Three stdlib tools pipeline together:
1. slide_splitter.py — splits markdown on --- HR or # H1 boundaries
(or --boundary auto: HR wins ≥ 3, else H1 ≥ 5). Extracts the first
heading per slide as the title. Hard rule: refuses 1-slide decks
(exit 5 — it's a poster) and no-boundary input (exit 6 — route to
md-document). Soft-warns slides > 40 source lines (signal-to-noise;
renders anyway).
2. presenter_notes_parser.py — extracts <!-- notes: ... --> blocks
(also speaker-notes: and presenter: aliases) per slide, attaches
as a separate `notes` field, strips from body. Tracks
notes_coverage_pct for the optional --strict-notes gate (refuses
< 50% coverage when presenter mode is essential).
3. deck_html_renderer.py — single-file HTML deck. All slides as
<section class="slide"> elements, one visible at a time (CSS-
controlled). Vanilla JS keyboard handlers: → / Space / PgDn advance;
← / PgUp previous; Home / End first/last; P toggles presenter mode;
Esc exits presenter. URL-hash deep linking (#3 jumps to slide 3,
back/forward walks slides). Progress bar at top (3px); slide counter
bottom-right. Presenter mode = split view: current slide (60% width)
+ panel (40% width with clock + speaker notes + next-slide preview).
@media print { section { display: block; page-break-after: always; } }
→ Cmd+P produces PDF with one slide per page. prefers-reduced-motion
honored throughout. Reuses md-document/scripts/markdown_parser.py
for slide-body content (consistent paragraphs / lists / code / tables
/ callouts). Prism.js is OPT-IN via --syntax (off by default — most
decks don't need it; keeps the file tiny).
Plus 3 references each citing 5-7 sources:
- presentation_ux.md — Atkinson Beyond Bullet Points + Reynolds
Presentation Zen + Tufte Cognitive Style of PowerPoint + NN/g +
Weinschenk + Marp/reveal.js/Big convergence + Tom MacWright
- keyboard_nav_patterns.md — reveal.js/Big/Spectacle keymap + WCAG
2.1.1 + 2.4.3 + MDN KeyboardEvent + NN/g keyboard accessibility
- single_file_deck_conventions.md — Big + Marp + Pandoc + reveal.js
standalone + WCAG 2.3.3 + @media print
1 template asset documenting the canonical single-file deck shape.
/cs:md-slides slash command with 6 pre-flight gates + pipeline +
output digest.
Repo-level updates:
- markdown-html/.claude-plugin/plugin.json: skills array adds
./skills/md-slides; version 2.10.2 → 2.10.3; description marks
domain COMPLETE at 5 skills.
- .claude-plugin/marketplace.json: markdown-html-skills entry version
and description (domain complete); top-level counters 342 → 343
skills, 545 → 548 Python tools, 688 → 691 references, 89 → 90 slash
commands; metadata.version 2.10.2 → 2.10.3.
- Root CLAUDE.md: v2.10.3 release-notes block above v2.10.2.
Validation:
- check_plugin_json.py → OK
- sync-codex-skills.py --dry-run → 1 new symlink, documentation:
5 skills, total 345
- skill_description_validator.py → PASS (all 5 checks: present,
826/1024 chars, third-person, trigger "use after", action verb
"Convert")
- skill_review_checklist_runner.py → 5/6 PASS (under-100-lines warns
at 102; same advisory as md-document SKILL.md)
- All 3 tools pass --help and --sample
- Hard rules verified end-to-end:
no-boundary input → exit 6 with md-document routing hint
1-slide deck → exit 5 with poster recommendation
--strict-notes with < 50% coverage → exit 7
- Full pipeline on 5-slide sample deck (3 with presenter notes)
produces 12.2 KB single-file HTML with all 16 expected components
(slide-1 + slide-5 anchors, notes attribute populated, P-key
handler, arrow nav, @media print, page-break-after, presenter
panel + clock + next-preview, palette tokens, progress bar, title
in header, "1 / 5" counter, history.replaceState URL hash sync,
prefers-reduced-motion).
Domain status: COMPLETE. All 5 planned skills shipped across 4 PRs
(#780 foundation, #793 md-document, #795 md-review, this PR md-slides).
The markdown-html/ domain operationalizes Shihipar's central claim —
markdown collapses past 100 lines; HTML restores density, clarity,
shareability, and lightweight interaction — across all three layout
families (long-form documents, code reviews, slide decks).
https://claude.ai/code/session_01BK2KoQot1U7J5oSosrCQdc
132 lines
4.8 KiB
HTML
132 lines
4.8 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
md_slides_template.html — Reference shape for deck_html_renderer.py output.
|
|
|
|
Documents the canonical single-file deck layout. The renderer produces this
|
|
same shape dynamically from a parsed deck (slide_splitter + presenter_notes
|
|
_parser) plus the design-system config.
|
|
|
|
See: deck_html_renderer.py for the live implementation.
|
|
-->
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{{DECK_TITLE}}</title>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family={{HEADING_FONT}}&family={{BODY_FONT}}&display=swap">
|
|
|
|
<!-- Prism is OPT-IN for decks (off by default) — pass --syntax to enable -->
|
|
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css"> -->
|
|
|
|
<style>
|
|
:root {
|
|
/* 12 brand tokens from design-system.derived_palette */
|
|
--md-bg: {{BG}}; --md-surface: {{SURFACE}}; --md-border: {{BORDER}};
|
|
--md-text: {{TEXT}}; --md-text-muted: {{TEXT_MUTED}};
|
|
--md-accent: {{ACCENT}}; --md-accent-soft: {{ACCENT_SOFT}};
|
|
--md-code-bg: {{CODE_BG}}; --md-link: {{LINK}}; --md-link-hover: {{LINK_HOVER}};
|
|
--md-success: {{SUCCESS}}; --md-warn: {{WARN}};
|
|
--md-font-heading: 'Inter', system-ui, sans-serif;
|
|
--md-font-body: 'Inter', system-ui, sans-serif;
|
|
}
|
|
/* One slide visible at a time; print stylesheet shows them all */
|
|
.slide { display: none; position: absolute; inset: 0; padding: 4vh 8vw; }
|
|
.slide.active { display: flex; flex-direction: column; justify-content: center; }
|
|
.progress { position: fixed; top: 0; height: 3px; background: var(--md-accent); }
|
|
.presenter-panel { display: none; position: fixed; right: 0; width: 40vw; }
|
|
body.presenter .deck { width: 60vw; }
|
|
body.presenter .presenter-panel { display: flex; }
|
|
@media print {
|
|
.slide { display: flex !important; position: relative; height: 100vh; page-break-after: always; }
|
|
.chrome, .progress, .presenter-panel { display: none !important; }
|
|
}
|
|
/* ... BASE_CSS ... */
|
|
</style>
|
|
</head>
|
|
|
|
<body class="style-{{DESIGN_STYLE}}">
|
|
|
|
<!-- Progress bar at top -->
|
|
<div id="progress" class="progress"></div>
|
|
|
|
<!-- All slides — visibility controlled by JS toggling .active -->
|
|
<div class="deck">
|
|
<section class="slide active" id="slide-1" data-notes="">
|
|
<h1>{{SLIDE_1_TITLE}}</h1>
|
|
<!-- body markdown rendered as HTML (paragraphs / lists / code / tables / callouts) -->
|
|
</section>
|
|
|
|
<section class="slide" id="slide-2" data-notes="Speaker notes for slide 2 go here. Multi-line.">
|
|
<h1>{{SLIDE_2_TITLE}}</h1>
|
|
<ul>
|
|
<li>Point one</li>
|
|
<li>Point two</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<!-- ...more slides... -->
|
|
</div>
|
|
|
|
<!-- Presenter view: appears when P is pressed -->
|
|
<aside class="presenter-panel" aria-label="Presenter view (toggle with P)">
|
|
<h3>Clock</h3>
|
|
<div class="clock">12:34:56</div>
|
|
<h3>Speaker notes</h3>
|
|
<div class="notes"></div>
|
|
<div class="next-preview">
|
|
<h4>Up next (#3)</h4>
|
|
<h1>{{NEXT_SLIDE_TITLE}}</h1>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Bottom-right chrome: slide counter + presenter toggle -->
|
|
<div class="chrome">
|
|
<a href="#" onclick="event.preventDefault();
|
|
document.dispatchEvent(new KeyboardEvent('keydown', {key:'P'}))">P · presenter</a>
|
|
<span id="counter">1 / 5</span>
|
|
</div>
|
|
|
|
<!-- Inline vanilla-JS payload (~3 KB):
|
|
- keyboard nav: ← / → / Space / PgDn / PgUp / Home / End / P / Esc
|
|
- URL hash sync (#3 = slide 3; works for deep links + back button)
|
|
- presenter panel with clock + notes + next-slide preview
|
|
- prefers-reduced-motion honored throughout
|
|
-->
|
|
<script>
|
|
(function () {
|
|
"use strict";
|
|
var slides = document.querySelectorAll(".slide");
|
|
var current = 0;
|
|
|
|
function show(idx) {
|
|
idx = Math.max(0, Math.min(slides.length - 1, idx));
|
|
slides.forEach(function (s, i) { s.classList.toggle("active", i === idx); });
|
|
current = idx;
|
|
history.replaceState(null, "", "#" + (current + 1));
|
|
}
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
switch (e.key) {
|
|
case "ArrowRight":
|
|
case "PageDown":
|
|
case " ": e.preventDefault(); show(current + 1); break;
|
|
case "ArrowLeft":
|
|
case "PageUp": e.preventDefault(); show(current - 1); break;
|
|
case "Home": e.preventDefault(); show(0); break;
|
|
case "End": e.preventDefault(); show(slides.length - 1); break;
|
|
case "p":
|
|
case "P": e.preventDefault();
|
|
document.body.classList.toggle("presenter"); break;
|
|
}
|
|
});
|
|
|
|
// Honor URL hash on load
|
|
if (location.hash) {
|
|
var n = parseInt(location.hash.slice(1), 10);
|
|
if (!isNaN(n)) show(n - 1);
|
|
} else { show(0); }
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|