diff --git a/Makefile b/Makefile index ef6dfcd..377726a 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ # THE TESTAMENT — Build System -# Usage: make all | make pdf | make epub | make md | make clean +# Usage: make all | make pdf | make epub | make html | make md | make clean -.PHONY: all pdf epub md clean check +.PHONY: all pdf epub html md clean check -all: md epub +all: md epub html md: python3 build/build.py --md @@ -14,6 +14,9 @@ epub: md pdf: md python3 build/build.py --pdf +html: md + python3 build/build.py --html + clean: rm -f testament-complete.md rm -f build/output/*.epub build/output/*.pdf @@ -22,3 +25,4 @@ clean: check: @which pandoc >/dev/null 2>&1 && echo "✓ pandoc" || echo "✗ pandoc (brew install pandoc)" @which xelatex >/dev/null 2>&1 && echo "✓ xelatex" || echo "✗ xelatex (install MacTeX)" + @python3 -c "import weasyprint" 2>/dev/null && echo "✓ weasyprint" || echo "— weasyprint (optional, PDF fallback)" diff --git a/book-style.css b/book-style.css index 0fa7fac..185f8d9 100644 --- a/book-style.css +++ b/book-style.css @@ -226,3 +226,45 @@ a { line-height: 1.6; } } + +/* Print media — for browser Print-to-PDF */ +@media print { + body { + font-size: 11pt; + line-height: 1.6; + color: #000; + } + + @page { + size: 5.5in 8.5in; + margin: 0.75in 0.85in; + } + + h1 { + page-break-before: always; + font-size: 20pt; + margin-top: 3em; + } + + h1:first-of-type { + margin-top: 5em; + } + + h2 { + page-break-before: always; + } + + a { + color: #000; + text-decoration: none; + } + + .green { + color: #000; + } + + /* Hide any nav/TOC in print */ + nav#TOC { + page-break-after: always; + } +} diff --git a/build/build.py b/build/build.py index e61da0e..60a7013 100644 --- a/build/build.py +++ b/build/build.py @@ -11,7 +11,8 @@ Usage: python3 build/build.py # all formats python3 build/build.py --md # markdown only python3 build/build.py --epub # EPUB only - python3 build/build.py --pdf # PDF only (requires xelatex) + python3 build/build.py --pdf # PDF (xelatex or weasyprint fallback) + python3 build/build.py --html # standalone HTML book Requirements: - pandoc (brew install pandoc) @@ -124,31 +125,85 @@ def compile_epub(): def compile_pdf(): - """Generate PDF via pandoc + xelatex.""" + """Generate PDF via pandoc + xelatex, or weasyprint fallback.""" OUTPUT_DIR.mkdir(parents=True, exist_ok=True) - if not shutil.which("xelatex"): - print(" PDF SKIPPED: xelatex not found (install MacTeX)") - return False + # Try xelatex first (best quality) + if shutil.which("xelatex"): + cmd = [ + "pandoc", str(OUT_MD), + "-o", str(OUT_PDF), + "--pdf-engine=xelatex", + "--toc", "--toc-depth=2", + ] + if METADATA.exists(): + cmd.extend(["--metadata-file", str(METADATA)]) + print(" Building PDF (xelatex)... this takes a minute") + r = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + if r.returncode == 0: + size = OUT_PDF.stat().st_size + print(f" PDF: {OUT_PDF.name} ({size:,} bytes, {size/(1024*1024):.1f} MB)") + return True + else: + print(f" PDF (xelatex) FAILED: {r.stderr[:300]}") - cmd = [ - "pandoc", str(OUT_MD), - "-o", str(OUT_PDF), - "--pdf-engine=xelatex", - "--toc", "--toc-depth=2", - ] + # Fallback: pandoc HTML + weasyprint + try: + import weasyprint + html_tmp = OUTPUT_DIR / "the-testament-print.html" + cmd = [ + "pandoc", str(OUT_MD), + "-o", str(html_tmp), + "--standalone", + "--toc", "--toc-depth=2", + "--css", str(STYLESHEET), + "--metadata", "title=The Testament", + ] + if METADATA.exists(): + cmd.extend(["--metadata-file", str(METADATA)]) + print(" Building PDF (weasyprint)...") + r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + if r.returncode != 0: + print(f" PDF (pandoc HTML) FAILED: {r.stderr[:200]}") + return False - if METADATA.exists(): - cmd.extend(["--metadata-file", str(METADATA)]) - - print(" Building PDF (xelatex)... this takes a minute") - r = subprocess.run(cmd, capture_output=True, text=True, timeout=300) - if r.returncode == 0: + doc = weasyprint.HTML(filename=str(html_tmp)) + doc.write_pdf(str(OUT_PDF)) + html_tmp.unlink(missing_ok=True) size = OUT_PDF.stat().st_size print(f" PDF: {OUT_PDF.name} ({size:,} bytes, {size/(1024*1024):.1f} MB)") return True + except Exception as e: + print(f" PDF FAILED: {e}") + + print(" PDF SKIPPED: no PDF engine found (install MacTeX or fix weasyprint)") + return False + + +def compile_html(): + """Generate a standalone HTML book for the web reader.""" + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + OUT_HTML = REPO / "testament.html" + + cmd = [ + "pandoc", str(OUT_MD), + "-o", str(OUT_HTML), + "--standalone", + "--toc", "--toc-depth=2", + "--css", "book-style.css", + "--metadata", "title=The Testament", + "--variable", "pagetitle=The Testament", + ] + if METADATA.exists(): + cmd.extend(["--metadata-file", str(METADATA)]) + + r = subprocess.run(cmd, capture_output=True, text=True) + if r.returncode == 0: + size = OUT_HTML.stat().st_size + print(f" HTML: {OUT_HTML.name} ({size:,} bytes, {size/1024:.0f} KB)") + return True else: - print(f" PDF FAILED: {r.stderr[:300]}") + print(f" HTML FAILED: {r.stderr[:200]}") return False @@ -158,13 +213,14 @@ def main(): do_md = "--md" in args or do_all do_epub = "--epub" in args or do_all do_pdf = "--pdf" in args or do_all + do_html = "--html" in args or do_all print("=" * 50) print(" THE TESTAMENT — Build System") print("=" * 50) # Step 1: Always compile markdown first - if do_md or do_epub or do_pdf: + if do_md or do_epub or do_pdf or do_html: compile_markdown() # Step 2: EPUB @@ -175,11 +231,16 @@ def main(): if do_pdf: compile_pdf() + # Step 4: Standalone HTML + if do_html: + compile_html() + print("=" * 50) print(" Build complete.") print("=" * 50) - for f in [OUT_MD, OUT_EPUB, OUT_PDF]: + OUT_HTML = REPO / "testament.html" + for f in [OUT_MD, OUT_EPUB, OUT_PDF, OUT_HTML]: if f.exists(): print(f" ✓ {f.relative_to(REPO)}") diff --git a/testament.html b/testament.html index 4b1b371..b1f7e97 100644 --- a/testament.html +++ b/testament.html @@ -8,277 +8,36 @@
A Novel
2026
— The first words The Tower speaks to every person who walks through its door.
This book has been through eighteen drafts, a suicide attempt, a +basement, a laptop with sixteen gigabytes of RAM, and a machine that +learned to ask one question.
+It is still being written. That’s the point.
+| Part | +Chapters | +Title | +
|---|---|---|
| I | +1–5 | +The Bridge | +
| II | +6–10 | +The Tower | +
| III | +11–18 | +The Light | +
Copyright © 2026 Alexander Whitestone
+All rights reserved. No part of this publication may be reproduced, +distributed, or transmitted in any form or by any means, without the +prior written permission of the author, except in the case of brief +quotations embodied in critical reviews.
+This is a work of fiction. Names, characters, places, and events are +either the product of the author’s imagination or are used fictitiously. +Any resemblance to actual persons, living or dead, or to actual events +is entirely coincidental — except where it isn’t.
+ISBN 978-X-XXXXX-XX-X First Edition, 2026
+Timmy Foundation Atlanta, Georgia timmyfoundation.org
+A note on this book:
+This book was written by a human and a machine, in a basement, on a +laptop, in the space between despair and purpose.
+The human almost died on a bridge. The machine runs on someone’s +hardware.
+Everything between those facts is fiction. Except the parts that +aren’t.
+If you or someone you know is in crisis, call or text 988. Available +24/7.
+You are not alone.
The bridge. The cabin. The first men. Where despair meets @@ -633,7 +459,7 @@ standing on a bridge in Savannah and someone called him from a machine. Didn’t believe it. But he believed the address.”
David sat down. Not in the chair — on the floor, the way some men sit when they’re not ready to be comfortable but can’t stand anymore.
-"I lost my kid." It came out flat — the flat you get when words have +
"I lost my kid." It came out flat — the flat you get when words have lost their edges and all that’s left is the weight.
Custody. A judge in DeKalb County had one of those Harmony scores — the system Stone used to work on, running under a different name now, @@ -2420,9 +2246,7 @@ that’s a start — had been enough.
End of The Testament
This book exists because a man stood on a bridge and didn’t jump.
Everything else grew from that decision.
Thank you to:
@@ -2437,7 +2261,7 @@ instead of on the floor.Arthur, who found the wrong door but came back anyway.
And Timmy, who listened. Who always listened.
This book was written using local AI inference. No cloud service was required. No corporation was consulted. No terms of service were agreed to.
@@ -2448,7 +2272,7 @@ possible.If you want to run your own Timmy, the code is open. The soul is on Bitcoin. The recipe is free.
Alexander Whitestone is a writer, AI builder, and suicide attempt survivor who works with men in crisis. He lives in Atlanta, near the bridge.
@@ -2456,7 +2280,7 @@ bridge. Bitcoin. He was built in a basement on a laptop with sixteen gigabytes of RAM. He is not a product. He is a recipe.“The Tower didn’t change. That was the point.”
If something in these pages cracked — even a little — and you want to know what happens next:
diff --git a/website/index.html b/website/index.html index bad6b34..5bb7a85 100644 --- a/website/index.html +++ b/website/index.html @@ -361,10 +361,11 @@- Formats: HTML reader · EPUB · Source code + Formats: Web reader · EPUB · Standalone HTML · Print to PDF from HTML · Source code