---
name: md-to-html-book
description: Convert a large Markdown book/document to a beautiful dark-themed responsive HTML file. Use when the user wants an HTML version of their MD document optimised for mobile reading (iPhone/Android).
---

# MD → HTML Book Converter

Converts a markdown book/document into a self-contained HTML file with dark theme, fluid typography, and mobile-first responsive design.

## Prerequisites

System Python 3.12+ with `markdown` library:
```bash
/Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -c "import markdown"
```

If missing: `pip3 install markdown`

## Usage (single-source approach: MD contains everything)

This is the **recommended method** — the `.md` file becomes the single source of truth. Raw HTML/JS is embedded at the end of the markdown file, and the build script wraps it in a base stylesheet.

### 1. Prepare

Save two files:

**`book_style.css`** — base dark-theme CSS for the book (see `references/style.css` in this skill).

**`book.md`** — your markdown file WITH interactive HTML/JS appended at the end:

```markdown
...book content...

---

<!-- HTML wrapper: progress bar, scroll-to-top, reading position -->
<style>
#scroll-top { position:fixed; bottom:30px; right:30px; width:44px; height:44px;
  background:#e67e22; color:#000; border:none; border-radius:50%; font-size:22px;
  cursor:pointer; display:none; z-index:9998; align-items:center; justify-content:center;
  opacity:0.85; transition:opacity .2s; line-height:44px; text-align:center; }
#scroll-top:hover { opacity:1; }
#progress-bar { position:fixed; top:0; left:0; height:3px; background:#e67e22;
  width:0%; z-index:9999; transition:width .1s; }
</style>

<div id="progress-bar"></div>
<div id="scroll-top" onclick="window.scrollTo({top:0,behavior:'smooth'})">↑</div>

<script>
(function(){
  var bar = document.getElementById('progress-bar');
  window.addEventListener('scroll', function(){
    var h = document.documentElement.scrollHeight - window.innerHeight;
    bar.style.width = (window.scrollY / h * 100) + '%';
  });
})();

(function(){
  var btn = document.getElementById('scroll-top');
  window.addEventListener('scroll', function(){
    btn.style.display = window.scrollY > 300 ? 'flex' : 'none';
  });
})();

(function(){
  var key = 'book_scroll_pos';
  var saved = localStorage.getItem(key);
  if(saved) { setTimeout(function(){ window.scrollTo(0, parseInt(saved,10)); }, 100); }
  var timer = null;
  window.addEventListener('scroll', function(){
    if(timer) clearTimeout(timer);
    timer = setTimeout(function(){ localStorage.setItem(key, window.scrollY); }, 500);
  });
})();
</script>
```

### 2. Build HTML

```bash
python3 << 'PYEOF'
import markdown, os

# Paths
input_path = 'book.md'
output_path = 'book.html'

with open(input_path, 'r') as f:
    md = f.read()

# Convert markdown to HTML — raw HTML (script/style) passes through intact
html_body = markdown.markdown(
    md,
    extensions=['tables', 'fenced_code', 'codehilite', 'toc', 'nl2br'],
    extension_configs={
        'codehilite': {'css_class': 'highlight'},
        'toc': {'permalink': True, 'title': 'Содержание'}
    }
)

# Load base CSS
with open('book_style.css', 'r') as f:
    base_css = f.read()

# Assemble
full = f'''<!DOCTYPE html>
<html lang=ru>
<head>
    <meta charset=UTF-8>
    <meta name=viewport content='width=device-width,initial-scale=1.0,maximum-scale=5.0,user-scalable=yes'>
    <title>Book Title</title>
    {base_css}
</head>
<body>
    {html_body}
</body>
</html>'''

with open(output_path, 'w') as f:
    f.write(full)

print(f"HTML: {os.path.getsize(output_path)} bytes")
PYEOF
```

## Book Maintenance: Adding New Chapters Safely

When inserting a large new chapter (200+ lines) into an existing `book.md`, **do NOT use the `patch` tool** — it's designed for small edits and can corrupt large insertions.

### Safe multi-step insertion (Python)

Use `execute_code` with a careful Python script:

```python
# 1. READ both files
with open("book.md", "r") as f:
    content = f.read()

with open("new_chapter.md", "r") as f:
    new_chapter = f.read()

# 2. LOCATE insertion point by unique markers
# Option A: insert BEFORE a known section
marker = "# Пенроуз: физика на границе метафизики"
if marker in content:
    pos = content.find(marker)
    updated = content[:pos] + "\n\n" + new_chapter.strip() + "\n\n---\n\n" + content[pos:]
else:
    print("ERROR: marker not found!")

# Option B: insert AFTER a known line, BEFORE the following separator
end_marker = "Документировано. 22 мая 2026, 22:00 по Канарам."
sep_marker = "\n---\n"
if end_marker in content and sep_marker in content:
    # Find the *next* --- after the end marker (not any ---)
    end_pos = content.find(end_marker) + len(end_marker)
    sep_pos = content.find(sep_marker, end_pos)
    if sep_pos > end_pos:
        updated = content[:end_pos] + "\n\n" + new_chapter.strip() + "\n\n" + content[sep_pos:]
    else:
        print("ERROR: separator not found after end marker!")

# 3. WRITE
with open("book.md", "w") as f:
    f.write(updated)

print(f"OK: {len(updated)} bytes")
```

### Safety pitfalls (learned from experience)

1. **⚠️ Always backup first:** `cp book.md book.md.bak` before any insertion
2. **Find the right cut point, not the marker end:** If inserting between a line and `---`, use `content[:end_marker] + insertion + content[sep_pos:]` — where `sep_pos` is the position of the `---`, NOT `content[:end_marker] + insertion + content[end_marker:]` (that duplicates content)
3. **Verify file size:** After insertion, check `wc -c book.md` — it should be roughly `original_size + chapter_size + newlines`
4. **Test with `head` and `tail`:** Verify the first 5 and last 5 lines survived
5. **Always update the TOC** if the book has a manual table of contents — add the new chapter entry in the right position

### Rebuilding HTML after changes

After any `.md` changes, rebuild the HTML:

```bash
/Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -c "
import markdown, os
md = open('book.md').read()
body = markdown.markdown(md, extensions=['tables','fenced_code','codehilite','toc','nl2br'],
    extension_configs={'codehilite': {'css_class': 'highlight'},
    'toc': {'permalink': True, 'title': 'Содержание'}})
css = open('book_style.css').read()
with open('book.html', 'w') as f:
    f.write(f'''<!DOCTYPE html><html lang=ru><head><meta charset=UTF-8>
    <meta name=viewport content='width=device-width,initial-scale=1.0'>
    <title>Book Title</title>{css}</head><body>{body}</body></html>''')
print(f'OK: {os.path.getsize(\"book.html\")} bytes')
"
```

## Key features of this approach

1. **MD is the single source of truth** — all content, metadata, and interactive elements live in one file
2. **Raw HTML in MD passes through** — python-markdown preserves `<script>` and `<style>` blocks as-is
3. **Interactive features** (built into the HTML wrapper):
   - **Reading progress bar** — orange bar at the very top of the page
   - **Scroll-to-top button** — appears when scrolling down
   - **Position persistence** — via `localStorage`; returns to the same spot on re-open
4. **Auto-generated table of contents** — add `[TOC]` marker in your markdown to insert a clickable TOC

## CSS Template (dark theme, mobile-responsive)

Complete CSS is in `references/style.css`. Key features:
- Dark background (`#0d0d0d`) with amber/orange accents
- `clamp()` for fluid typography (no breakpoint jumps)
- Two media queries: 600px (tablets/phones) and 380px (tiny phones)
- Tables: horizontal scroll on mobile (`min-width` + `overflow-x`)
- Code blocks: `-webkit-overflow-scrolling: touch` for iOS
- Print styles: white background, page-break-inside: avoid
- `word-break: break-word` and `hyphens: auto` for long words

## Pitfalls

- Use system Python, not venv — the sandbox Python doesn't have `markdown`
- The `toc` extension auto-generates anchor links on headers — adds `¶` permalinks
- `nl2br` extension converts newlines to `<br>` — sometimes unwanted in code blocks
- Tables need `min-width: 400px` on mobile to force horizontal scroll instead of squishing
- Russian text works fine with `charset=UTF-8`
- **IMPORTANT:** python-markdown must NOT have `safe_mode` enabled — otherwise raw HTML (script/style) gets escaped. Default behaviour preserves raw HTML.
- When embedding JS in `.md`, wrap in `(function(){...})();` IIFE to avoid variable leakage
- `localStorage` key should be unique per book to avoid conflicts if multiple books are open
