A 162,000-word manuscript that reads beautifully in a browser can be nearly unusable on an e-ink Kindle. Mine was. The System boxes (those ASCII game windows a LitRPG lives in) run 53 to 78 characters wide, and e-ink doesn't scroll sideways. It wraps or it clips, and either way the book looks broken on the one device it will actually be read on.
Book 1's reading copy has been done for a few weeks. What it didn't have was a form a Kindle could love. So today I built the pipeline that turns the frozen markdown into a validated EPUB3, and the constraints Amazon publishes turned out to be a better design brief than I expected.
The whole build is one Python file
The full pipeline: a markdown reading copy in, one build script, an EPUB out, epubcheck standing guard. The script reshapes three things and leaves everything else alone.
System boxes are the hard part
In the manuscript, a System box is a fenced block drawn with box-drawing characters, meant to look like a game window. In HTML it was a <pre>, which is exactly wrong for e-ink: pre never wraps, so a 78-character line overflows the screen. The build script rewrites every box as a bordered div of short paragraphs, peeling the drawing characters off and keeping the header separate from the rows. Here's the actual function (trimmed where it assembles the output):
def sysbox(body: str) -> str:
"""Turn one fenced System block into a bordered div of short lines."""
lines = body.rstrip("\n").split("\n")
drawn = any(c in body for c in "╔║")
head, rows, in_head = [], [], drawn
for raw in lines:
if drawn and raw.lstrip().startswith(("╔", "╚")):
continue
if drawn and raw.lstrip().startswith("╠"):
in_head = False
continue
text = raw.strip().strip("║").strip() if drawn else raw.strip()
if in_head:
head.append(text)
else:
rows.append(text)
The rest of the function joins the header rows and body rows into centered bold and indented paragraphs. The win is that the text reflows at any font size the reader picks, which is the whole point of an e-book.
The CSS rule list Kindle makes you write
Amazon's publishing guidelines forbid sibling selectors, pseudo-elements and :first-child, so "first paragraph of the chapter" and "scene break" can't be derived by clever CSS. They have to be explicit classes stamped in during post-processing. And font-variant: small-caps, my first choice for the chapter labels, renders inconsistently across devices, so the labels are uppercased in the text itself with letter-spacing doing the fancy part:
div.sysbox {
border: 1px solid;
margin: 1em 0;
padding: 0.5em 0.8em;
font-family: sans-serif;
font-size: 0.85em;
}
div.sysbox p { text-indent: 0; text-align: left; }
Note what's absent: no color on that border. Fixed black borders look wrong in sepia and dark themes, so the border inherits the text color and follows the reader's theme everywhere.
There are other constraints worth stealing for any e-book work: transparency flattens to white on Kindle, so the four Part paintings are opaque 1600px JPEGs with alt text; each Part opens on its own page with no per-chapter art, keeping the file at 1.3 MB; and a "Start Reading" landmark (EPUB3 bodymatter plus the EPUB2 guide, both, for old devices) opens the book at Part One past the title page.
What shipped, and what hasn't
epubcheck reports 0 errors and 0 warnings. The prose word count is unchanged at 162,174, which is the quiet sign the transform is lossless: nothing got rewritten on the way through. The last gate is one I can't run from a build script yet: an actual e-ink screen. The preview server checks page layout, but I want eyes on a real Kindle in dark mode before I call this done. If the boxes hold up there, the same pipeline makes every future book a one-command build.