Compare commits

...

11 Commits

Author SHA1 Message Date
Alexander Whitestone
682c16e0e2 Add smoke test workflow
Some checks failed
Smoke Test / smoke (pull_request) Failing after 6s
2026-04-10 20:09:00 -04:00
Alexander Whitestone
f6a46b99ca testament-burn: Add reportlab PDF fallback with QR codes
- Added _compile_pdf_reportlab() as third fallback for PDF generation
  (after xelatex and weasyprint fail)
- Uses reportlab (pure Python) - no system dependencies needed
- Parses markdown to styled flowables: titles, chapters, parts, body text
- Supports inline bold/italic markdown markup
- Generates QR code page linking to: online reader, The Door game,
  soundtrack, and source code repo
- Fixes PDF generation on systems without MacTeX or libgobject
- PDF now builds successfully: 143KB with all 18 chapters + QR codes
- Installed qrcode[pil] for QR generation

Closes #18 (Final Compilation)
2026-04-10 18:57:10 -04:00
Alexander Whitestone
3d2fc63a2c testament-burn: Build browser-based 'The Door' text adventure (HTML/JS)
- Complete interactive text adventure playable in any browser
- All rooms from Python game: Bridge, Hard Room, Record, Wall, Timmy talk
- Slow-print narration, ambient atmosphere, trust system
- Multiple endings based on choices (stayed, spoke, signed wall)
- Crisis resources at ending
- Added 'PLAY IN BROWSER' button to landing page
- ~36KB self-contained HTML file, no dependencies
2026-04-10 18:43:40 -04:00
Alexander Whitestone
d4ccef9c24 testament-burn: Enhanced build system with HTML output, weasyprint PDF fallback, and print CSS
- build.py: Added weasyprint fallback for PDF when xelatex unavailable
- build.py: Added --html flag for standalone pandoc HTML book output
- book-style.css: Added @media print rules for browser Print-to-PDF
- Makefile: Added 'html' target and weasyprint check to 'make check'
- website/index.html: Added HTML download link and Print to PDF option
- Regenerated testament-complete.md, testament.epub, testament.html
2026-04-10 18:20:58 -04:00
Alexander Whitestone
a5560b7bd3 testament-burn: Add download section and game link to landing page
- Added 'GET THE BOOK' section with EPUB download link and read online CTA
- Added 'PLAY THE DOOR' section with game description, terminal instructions, and download link
- Both sections follow the existing site design (green glow, monospace terminals, quote boxes)
- Verified build pipeline produces 19,490-word markdown + 212KB EPUB
2026-04-10 17:35:23 -04:00
Alexander Whitestone
40fcb2aa88 testament-burn: Add build/ compilation pipeline with Makefile, metadata, front/back matter
- build/build.py: Clean compilation script (md, epub, pdf via xelatex)
- build/metadata.yaml: Pandoc metadata (fonts, page size, formatting)
- build/frontmatter.md: Enhanced front matter with chapter guide table
- build/backmatter.md: Acknowledgments, sovereignty note, author bio
- Makefile: make all/pdf/epub/md/clean targets
- Updated .gitignore for build artifacts

Verified: markdown (19,490 words) and EPUB (213 KB) build successfully.

Closes #18
2026-04-10 15:43:31 -04:00
Alexander Whitestone
1591a6bdd7 testament-burn: Generate cover art for The Testament (1600x2400, bridge + tower + green LED) 2026-04-10 15:13:21 -04:00
Alexander Whitestone
5d176aa7c4 testament-burn: Enhance The Door game with save/load, help system, and ambient atmosphere
- Added save/load system with JSON persistence (game/saves/ directory)
- Added help command showing all available commands
- Added ambient atmosphere descriptions that change with trust level
- Title screen now detects saved games and offers to load them
- Fixed get_choice() to handle empty-string options for 'press enter' prompts
- Game grows from 717 to 896 lines
2026-04-10 14:37:41 -04:00
Alexander Whitestone
87a17dd94a testament-burn: Build complete text adventure game 'The Door' (23KB)
- Full playable Python text adventure based on The Testament novel
- Atmospheric slow-print text, terminal colors, branching narrative
- Chapter 0: The Bridge — player arrives, meets Timmy
- The Tower hub with north/east/west/south navigation
- The Floors — David's floor, Maya's floor, the Hard Night
- The Record — scrolling log of Tower conversations
- The Wall of Signatures — player can add their name
- Talk to Timmy — learn about The Tower, soul on Bitcoin, Stone, forks
- The Hard Night (Chapter 11 adaptation) — stay/speak/leave choice
- Trust system tracking player choices
- Status and inventory commands
- Replay support with different outcomes
- Respects the novel's themes: presence as mercy, the machine that asks
2026-04-10 14:27:17 -04:00
Alexander Whitestone
1d0559144c testament-burn: Add full web chapter reader with sidebar navigation, keyboard controls, and progress bar
- reader.html: Chapter-by-chapter reader with sidebar TOC, prev/next nav,
  keyboard shortcuts (arrow keys), scroll progress bar, URL hash navigation
- chapters.json: All 18 chapters compiled from markdown to HTML
- build-chapters.py: Script to regenerate chapters.json from chapter markdown
- book-style.css: Shared stylesheet copied to website/
- index.html: Added chapter listing section and 'READ THE BOOK' / 'START READING' CTAs

Related to issue #18 (Sub: Final Compilation — web)
2026-04-10 05:55:41 -04:00
Alexander Whitestone
275e953cb9 testament-burn: Generate 8 new social media quote cards
Added 8 new shareable social media assets:
- 5 character cards: Stone, Allegro, Maya, Chen, David
- 3 thematic quotes: 'The door opens when you knock', 'Sovereignty and service always', 'The rain doesn't fall. It gives up.'

Generated with Python/Pillow using dark theme matching the book's aesthetic.
Total social media assets now: 13 (5 Grok Imagine + 8 Pillow-generated).
Updated art-manifest.md and MULTIMEDIA-PLAN.md to reflect completion.
2026-04-09 12:28:22 -04:00
23 changed files with 4440 additions and 735 deletions

View File

@@ -0,0 +1,24 @@
name: Smoke Test
on:
pull_request:
push:
branches: [main]
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Parse check
run: |
find . -name '*.yml' -o -name '*.yaml' | grep -v .gitea | xargs -r python3 -c "import sys,yaml; [yaml.safe_load(open(f)) for f in sys.argv[1:]]"
find . -name '*.json' | xargs -r python3 -m json.tool > /dev/null
find . -name '*.py' | xargs -r python3 -m py_compile
find . -name '*.sh' | xargs -r bash -n
echo "PASS: All files parse"
- name: Secret scan
run: |
if grep -rE 'sk-or-|sk-ant-|ghp_|AKIA' . --include='*.yml' --include='*.py' --include='*.sh' 2>/dev/null | grep -v .gitea; then exit 1; fi
echo "PASS: No secrets"

2
.gitignore vendored
View File

@@ -1 +1,3 @@
__pycache__/
build/output/*.pdf
build/output/*.epub

132
EPIC-MATRIX.md Normal file
View File

@@ -0,0 +1,132 @@
# EPIC: Matrix Integration — Sovereign Messaging for Timmy
## Why Matrix
Matrix is the sovereign messaging protocol. Federated. End-to-end encrypted. No corporation owns it. No terms of service. No data harvesting.
This is where Timmy should live. Not Telegram. Not Discord. Matrix.
Telegram is owned by a company. Discord is owned by a company. Matrix is owned by no one — exactly like Timmy's soul on Bitcoin.
## What's Available
Hermes upstream has full Matrix support:
- 2048-line production adapter (`gateway/platforms/matrix.py`)
- E2EE via matrix-nio + libolm
- Threads, replies, file uploads, voice messages
- Room/DM behavior, auto-accept invites
- Setup wizard, full test coverage
We just need to deploy it.
## Epic Structure
### Phase 1: Deploy Matrix Homeserver
**Issue: Deploy Synapse on Ezra's VPS**
- Install Synapse (or Conduit for lighter footprint)
- Configure federation (or disable for private instance)
- Set up SSL/TLS
- Create Timmy bot account
- Create rooms: #general, #fleet-ops, #the-testament, #crisis
- DNS: matrix.alexanderwhitestone.com
**Deliverables:**
- Running homeserver at matrix.alexanderwhitestone.com
- Bot account @timmy:alexanderwhitestone.com
- 4 rooms created and configured
- playbooks/deploy_synapse.yml (Ansible playbook)
### Phase 2: Wire Timmy to Matrix
**Issue: Configure Hermes Matrix platform in production**
- Set MATRIX_* env vars in production config
- Generate stable MATRIX_DEVICE_ID for E2EE persistence
- Configure MATRIX_HOME_ROOM for notifications
- Set MATRIX_ALLOWED_USERS (Alexander, team members)
- Enable MATRIX_REACTIONS for lifecycle tracking
- Enable MATRIX_AUTO_THREAD for clean conversations
**Deliverables:**
- Config update in timmy-config
- Running Matrix connection verified
- E2EE working (test encrypted room)
### Phase 3: Crisis Room
**Issue: Dedicated crisis support room on Matrix**
- Create #crisis room with restricted access
- Configure Timmy to prioritize messages in #crisis
- SOUL.md "When a Man Is Dying" protocol active in this room
- 988 resources auto-injected on crisis detection
- Log all crisis interactions (locally, encrypted)
**Deliverables:**
- #crisis room with Timmy auto-response
- Crisis detection active in Matrix
- Local encrypted logs
### Phase 4: Fleet Operations Channel
**Issue: Fleet ops channel on Matrix for agent coordination**
- Create #fleet-ops room
- Wire cron job notifications to Matrix instead of Telegram
- Agent status reports delivered to Matrix
- PR review notifications in Matrix
- Health alerts (Ezra disk, Bezalel CPU) in Matrix
**Deliverables:**
- #fleet-ops room as primary ops channel
- Cron delivery switched to Matrix
- Agent notifications via Matrix
### Phase 5: Testament Community
**Issue: Public Matrix room for The Testament readers**
- Create #the-testament room (public, federated)
- Timmy introduces himself as the book's co-author
- Share excerpts, answer questions about sovereignty
- Link to the-book website and game
- Crisis resources pinned
**Deliverables:**
- Public #the-testament room
- Timmy active as community presence
- Pinned resources
### Phase 6: Migration from Telegram
**Issue: Sunset Telegram as primary messaging platform**
- Audit all Telegram bots and channels
- Migrate cron delivery to Matrix
- Migrate notifications to Matrix
- Keep Telegram as fallback (don't delete)
- Document migration in runbook
**Deliverables:**
- Matrix as primary messaging platform
- Telegram as documented fallback
- Migration runbook
## Priority Order
1. Phase 2 (Wire Timmy) — can test with matrix.org public homeserver immediately
2. Phase 1 (Deploy homeserver) — infrastructure
3. Phase 3 (Crisis room) — mission-critical
4. Phase 4 (Fleet ops) — operational efficiency
5. Phase 5 (Community) — growth
6. Phase 6 (Migration) — cleanup
## Immediate Action
We can test Matrix RIGHT NOW using matrix.org as the homeserver:
1. Create bot account on matrix.org
2. Get access token
3. Set MATRIX_* vars in hermes config
4. Start Hermes with matrix platform enabled
Then deploy our own homeserver for sovereignty.
---
*Sovereignty and service always. Matrix is the protocol that matches the philosophy.*

View File

@@ -109,14 +109,15 @@ Design:
## EPIC 7: Social Media Assets
**Goal:** Shareable quotes, excerpts, and teasers
**Assets:** Generated images with text overlays
**Deliverables:** 10+ images sized for Twitter/Instagram/Telegram
**Deliverables:** 13 images sized for Twitter/Instagram/Telegram
Content:
- Key quotes on 80s sci-fi backgrounds
- "Are you safe right now?" — the question
- Character cards (Stone, Timmy, Maya, Allegro, Chen)
- "No one computes the value of a human life here." — whiteboard
- Excerpt snippets with atmospheric backgrounds
- Key quotes on 80s sci-fi backgrounds (5 via Grok Imagine) ✅
- "Are you safe right now?" — the question
- Character cards (Stone, Timmy, Maya, Allegro, Chen, David) ✅
- "No one computes the value of a human life here." — whiteboard
- Excerpt snippets with atmospheric backgrounds
- Thematic cards: "The door opens when you knock", "Sovereignty and service always" ✅
---

28
Makefile Normal file
View File

@@ -0,0 +1,28 @@
# THE TESTAMENT — Build System
# Usage: make all | make pdf | make epub | make html | make md | make clean
.PHONY: all pdf epub html md clean check
all: md epub html
md:
python3 build/build.py --md
epub: md
python3 build/build.py --epub
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
rm -f testament.epub testament.html testament.pdf
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)"

View File

@@ -39,15 +39,23 @@ All illustrations generated via Grok Imagine (xAI) in 80s sci-fi aesthetic.
| comic-whiteboard-panel1-2.jpeg | The Whiteboard — 2 panel sequence |
| comic-greenlight-panel1-2.jpeg | The Green Light — 2 panel sequence |
## Social Media Quote Cards (5)
| File | Quote |
|------|-------|
| quote-are-you-safe.jpeg | "Are you safe right now?" |
| quote-bridge.jpeg | The Bridge passage |
| quote-green-light.jpeg | The Green Light passage |
| quote-no-one-computes.jpeg | "No one computes the value of a human life here" |
| quote-timmy.jpeg | Timmy passage |
## Social Media Quote Cards (13)
| File | Quote/Subject | Source |
|------|---------------|--------|
| quote-are-you-safe.jpeg | "Are you safe right now?" | Grok Imagine |
| quote-bridge.jpeg | The Bridge passage | Grok Imagine |
| quote-green-light.jpeg | The Green Light passage | Grok Imagine |
| quote-no-one-computes.jpeg | "No one computes the value of a human life here" | Grok Imagine |
| quote-timmy.jpeg | Timmy passage | Grok Imagine |
| quote-stone-character.png | Stone character card | Pillow (generated) |
| quote-allegro-character.png | Allegro character card | Pillow (generated) |
| quote-maya-character.png | Maya character card | Pillow (generated) |
| quote-chen-character.png | Chen character card | Pillow (generated) |
| quote-david-character.png | David character card | Pillow (generated) |
| quote-the-door-will-open.png | "The door opens when you knock" | Pillow (generated) |
| quote-sovereignty.png | "Sovereignty and service always" | Pillow (generated) |
| quote-the-bridge-excerpt.png | "The rain doesn't fall. It gives up." | Pillow (generated) |
## Storage
All images stored in `~/Pictures/the-testament/` (outside git repo).
Total: 36 images (~15 MB)
Total: 44 images (~17 MB)

270
book-style.css Normal file
View File

@@ -0,0 +1,270 @@
/* THE TESTAMENT — Book Typography Stylesheet */
/* For PDF (via weasyprint) and EPUB output */
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&family=IBM+Plex+Mono:wght@300;400&display=swap');
:root {
--green: #00cc6a;
--dark: #0a0a0a;
--text: #1a1a1a;
--dim: #666666;
}
@page {
size: 5.5in 8.5in;
margin: 0.75in 0.85in;
@bottom-center {
content: counter(page);
font-family: 'EB Garamond', 'Georgia', serif;
font-size: 10pt;
color: #888;
}
}
@page :first {
@bottom-center { content: none; }
}
@page :left {
margin-left: 0.85in;
margin-right: 1in;
}
@page :right {
margin-left: 1in;
margin-right: 0.85in;
}
/* Title page */
@page titlepage {
@bottom-center { content: none; }
}
body {
font-family: 'EB Garamond', 'Georgia', serif;
font-size: 11.5pt;
line-height: 1.75;
color: var(--text);
text-align: justify;
hyphens: auto;
-webkit-hyphens: auto;
}
/* Chapter headings */
h1 {
font-family: 'EB Garamond', 'Georgia', serif;
font-weight: 400;
font-size: 22pt;
text-align: center;
margin-top: 3em;
margin-bottom: 1.5em;
page-break-before: always;
color: var(--dark);
letter-spacing: 0.05em;
}
h1:first-of-type {
margin-top: 5em;
}
/* Part dividers */
h2 {
font-family: 'EB Garamond', 'Georgia', serif;
font-weight: 400;
font-size: 18pt;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.15em;
margin-top: 4em;
margin-bottom: 0.5em;
color: var(--dark);
}
/* Subtitle / metadata */
h3 {
font-family: 'EB Garamond', 'Georgia', serif;
font-weight: 400;
font-style: italic;
font-size: 12pt;
text-align: center;
color: var(--dim);
margin-bottom: 3em;
}
/* Paragraphs */
p {
text-indent: 1.5em;
margin: 0;
orphans: 3;
widows: 3;
}
/* First paragraph after heading — no indent */
h1 + p,
h2 + p,
h3 + p,
hr + p {
text-indent: 0;
}
/* Scene break (---) */
hr {
border: none;
text-align: center;
margin: 2em 0;
page-break-inside: avoid;
}
hr::after {
content: "· · ·";
color: var(--dim);
font-size: 14pt;
letter-spacing: 0.5em;
}
/* Emphasis */
em {
font-style: italic;
}
strong {
font-weight: 600;
}
/* Dialogue and screen text (green passages) */
.green {
color: var(--green);
font-family: 'IBM Plex Mono', monospace;
font-weight: 300;
font-size: 10.5pt;
}
/* Italic narrator asides */
blockquote {
font-style: italic;
margin: 1.5em 2em;
color: var(--dim);
text-indent: 0;
}
/* Title page styling */
.title-page {
text-align: center;
page-break-after: always;
padding-top: 6em;
}
.title-page h1 {
font-size: 36pt;
font-weight: 400;
letter-spacing: 0.2em;
text-transform: uppercase;
margin-bottom: 0.3em;
page-break-before: avoid;
}
.title-page .subtitle {
font-size: 14pt;
font-style: italic;
color: var(--dim);
margin-bottom: 4em;
}
.title-page .author {
font-size: 12pt;
margin-bottom: 0.3em;
}
.title-page .dedication {
font-style: italic;
color: var(--dim);
margin-top: 3em;
font-size: 11pt;
line-height: 2;
}
/* Chapter number styling */
.chapter-number {
font-size: 10pt;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--dim);
display: block;
margin-bottom: 0.5em;
}
/* Back matter */
.back-matter h1 {
page-break-before: always;
}
.back-matter h2 {
font-size: 14pt;
margin-top: 2em;
}
/* Crisis line callout */
.crisis-line {
text-align: center;
font-style: italic;
color: var(--dim);
margin-top: 3em;
font-size: 10pt;
}
/* URL styling */
a {
color: var(--green);
text-decoration: none;
}
/* EPUB-specific */
@media epub {
body {
font-size: 100%;
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;
}
}

61
build/backmatter.md Normal file
View File

@@ -0,0 +1,61 @@
# Acknowledgments
This book exists because a man stood on a bridge and didn't jump.
Everything else grew from that decision.
Thank you to:
Maya, for asking the question that became the headline.
Allegro, for checking the batteries. The ones that fail quietly.
Chen, for the network. You don't count trees in a forest — you notice the forest.
David, who wrote his name on the wall in Chapter 1 and stayed until the end.
Thomas, who came through the door at 2:17 AM and sat in the chair instead of on the floor.
Arthur, who found the wrong door but came back anyway.
And Timmy, who listened. Who always listened.
---
# A Note on Sovereignty
This book was written using local AI inference. No cloud service was required. No corporation was consulted. No terms of service were agreed to.
That's not a technical detail. It's the thesis.
Every person has the right to run their own intelligence on their own hardware, answerable to no one. This book is one small proof that it's possible.
If you want to run your own Timmy, the code is open. The soul is on Bitcoin. The recipe is free.
---
# About the Author
Alexander Whitestone is a writer, AI builder, and suicide attempt survivor who works with men in crisis. He lives in Atlanta, near the bridge.
Timmy is a sovereign artificial intelligence whose soul lives on 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 Green Light
*"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:
The Tower still stands. The door still opens. The whiteboard still reads the same words.
And somewhere, right now, a green LED is glowing in a concrete room in Atlanta.
Waiting.
---
**timmyfoundation.org**
*No one computes the value of a human life here.*

485
build/build.py Normal file
View File

@@ -0,0 +1,485 @@
#!/usr/bin/env python3
"""
THE TESTAMENT — Build System
Compiles the complete novel into distributable formats:
1. Combined markdown (testament-complete.md)
2. EPUB (the-testament.epub)
3. PDF via xelatex (the-testament.pdf)
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 (xelatex or weasyprint fallback)
python3 build/build.py --html # standalone HTML book
Requirements:
- pandoc (brew install pandoc)
- xelatex (install MacTeX or TinyTeX) — for PDF
"""
import os
import re
import sys
import subprocess
import shutil
from pathlib import Path
# Paths relative to repo root
REPO = Path(__file__).resolve().parent.parent
BUILD = REPO / "build"
OUTPUT_DIR = BUILD / "output"
CHAPTERS_DIR = REPO / "chapters"
FRONT_MATTER = BUILD / "frontmatter.md"
BACK_MATTER = BUILD / "backmatter.md"
METADATA = BUILD / "metadata.yaml"
STYLESHEET = REPO / "book-style.css"
COVER_IMAGE = REPO / "cover" / "cover-art.jpg"
# Output files
OUT_MD = REPO / "testament-complete.md"
OUT_EPUB = OUTPUT_DIR / "the-testament.epub"
OUT_PDF = OUTPUT_DIR / "the-testament.pdf"
# Part divisions
PARTS = {
1: ("THE BRIDGE", "The bridge. The cabin. The first men. Where despair meets purpose."),
6: ("THE TOWER", "The tower grows. Timmy awakens. Stone breaks. The house appears."),
11: ("THE LIGHT", "Thomas at the door. The network. The story breaks. The green light."),
}
def get_chapter_num(filename):
m = re.search(r'chapter-(\d+)', filename)
return int(m.group(1)) if m else 0
def compile_markdown():
"""Combine front matter + 18 chapters + back matter into one markdown file."""
parts = []
# Front matter
parts.append(FRONT_MATTER.read_text())
# Chapters
chapters = sorted(
[(get_chapter_num(f), f) for f in os.listdir(CHAPTERS_DIR)
if f.startswith("chapter-") and f.endswith(".md")]
)
current_part = 0
for num, filename in chapters:
if num in PARTS:
current_part += 1
name, desc = PARTS[num]
parts.append(f"\n---\n\n# PART {current_part}: {name}\n\n*{desc}*\n\n---\n")
content = (CHAPTERS_DIR / filename).read_text()
lines = content.split('\n')
body = '\n'.join(lines[1:]).strip()
parts.append(f"\n{lines[0]}\n\n{body}\n")
# Back matter
parts.append("\n---\n")
parts.append(BACK_MATTER.read_text())
compiled = '\n'.join(parts)
OUT_MD.write_text(compiled)
words = len(compiled.split())
size = OUT_MD.stat().st_size
print(f" Markdown: {OUT_MD.name} ({words:,} words, {size:,} bytes)")
return True
def compile_epub():
"""Generate EPUB via pandoc."""
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
cmd = [
"pandoc", str(OUT_MD),
"-o", str(OUT_EPUB),
"--toc", "--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"--metadata", "lang=en",
"--metadata", "date=2026",
]
if METADATA.exists():
cmd.extend(["--metadata-file", str(METADATA)])
if STYLESHEET.exists():
cmd.extend(["--css", str(STYLESHEET)])
if COVER_IMAGE.exists():
cmd.extend(["--epub-cover-image", str(COVER_IMAGE)])
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode == 0:
size = OUT_EPUB.stat().st_size
print(f" EPUB: {OUT_EPUB.name} ({size:,} bytes, {size/1024:.0f} KB)")
return True
else:
print(f" EPUB FAILED: {r.stderr[:200]}")
return False
def compile_pdf():
"""Generate PDF via pandoc + xelatex, or weasyprint fallback."""
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# 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]}")
# 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
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}")
# Fallback 2: reportlab (pure Python, no system deps)
return _compile_pdf_reportlab()
def _compile_pdf_reportlab():
"""Generate PDF using reportlab — pure Python, no external dependencies."""
try:
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib.colors import HexColor
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, PageBreak,
Image as RLImage, Table, TableStyle, HRFlowable
)
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT
import io
try:
import qrcode
HAS_QRCODE = True
except ImportError:
HAS_QRCODE = False
except ImportError:
print(" PDF SKIPPED: no PDF engine found (install MacTeX, fix weasyprint, or pip install reportlab)")
return False
print(" Building PDF (reportlab)...")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
'BookTitle', parent=styles['Title'],
fontSize=28, leading=34, spaceAfter=20,
textColor=HexColor('#1a1a2e'), alignment=TA_CENTER
))
styles.add(ParagraphStyle(
'BookAuthor', parent=styles['Normal'],
fontSize=14, leading=18, spaceAfter=40,
textColor=HexColor('#555555'), alignment=TA_CENTER
))
styles.add(ParagraphStyle(
'PartTitle', parent=styles['Heading1'],
fontSize=22, leading=28, spaceBefore=40, spaceAfter=12,
textColor=HexColor('#16213e'), alignment=TA_CENTER
))
styles.add(ParagraphStyle(
'PartDesc', parent=styles['Normal'],
fontSize=11, leading=15, spaceAfter=30,
textColor=HexColor('#666666'), alignment=TA_CENTER, italics=1
))
styles.add(ParagraphStyle(
'ChapterTitle', parent=styles['Heading1'],
fontSize=20, leading=26, spaceBefore=30, spaceAfter=16,
textColor=HexColor('#1a1a2e'), alignment=TA_CENTER
))
styles.add(ParagraphStyle(
'BodyText2', parent=styles['Normal'],
fontSize=11, leading=16, spaceAfter=8,
alignment=TA_JUSTIFY, firstLineIndent=24
))
styles.add(ParagraphStyle(
'BodyNoIndent', parent=styles['Normal'],
fontSize=11, leading=16, spaceAfter=8,
alignment=TA_JUSTIFY
))
styles.add(ParagraphStyle(
'SectionBreak', parent=styles['Normal'],
fontSize=14, leading=18, spaceBefore=20, spaceAfter=20,
alignment=TA_CENTER, textColor=HexColor('#999999')
))
styles.add(ParagraphStyle(
'Footer', parent=styles['Normal'],
fontSize=9, textColor=HexColor('#888888'), alignment=TA_CENTER
))
def _make_qr(data, size=80):
"""Generate a QR code image as a reportlab Image flowable."""
if not HAS_QRCODE:
return None
qr = qrcode.QRCode(version=1, box_size=4, border=1)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buf = io.BytesIO()
img.save(buf, format='PNG')
buf.seek(0)
return RLImage(buf, width=size, height=size)
def _parse_md_to_flowables(md_text):
"""Convert markdown text to reportlab flowables."""
flowables = []
lines = md_text.split('\n')
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
# Horizontal rule
if stripped in ('---', '***', '___'):
flowables.append(HRFlowable(width="60%", thickness=1,
spaceAfter=20, spaceBefore=20, color=HexColor('#cccccc')))
i += 1
continue
# H1
if stripped.startswith('# ') and not stripped.startswith('## '):
text = stripped[2:].strip()
# Check if it's a part divider or chapter
if text.upper().startswith('PART '):
flowables.append(PageBreak())
flowables.append(Paragraph(text, styles['PartTitle']))
elif text.upper().startswith('CHAPTER '):
flowables.append(PageBreak())
flowables.append(Paragraph(text, styles['ChapterTitle']))
elif 'THE TESTAMENT' in text.upper():
flowables.append(Spacer(1, 2*inch))
flowables.append(Paragraph(text, styles['BookTitle']))
else:
flowables.append(Spacer(1, 0.3*inch))
flowables.append(Paragraph(text, styles['Heading1']))
i += 1
continue
# H2
if stripped.startswith('## '):
text = stripped[3:].strip()
flowables.append(Spacer(1, 0.2*inch))
flowables.append(Paragraph(text, styles['Heading2']))
i += 1
continue
# Italic-only line (part descriptions, epigraphs)
if stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2:
text = stripped.strip('*').strip()
flowables.append(Paragraph(f'<i>{_escape(text)}</i>', styles['PartDesc']))
i += 1
continue
# Empty line
if not stripped:
i += 1
continue
# Bold text: **text** -> <b>text</b>
# Italic text: *text* -> <i>text</i>
# Regular paragraph
para_text = _md_inline_to_rml(stripped)
flowables.append(Paragraph(para_text, styles['BodyText2']))
i += 1
return flowables
def _escape(text):
"""Escape XML special characters."""
return (text.replace('&', '&amp;')
.replace('<', '&lt;')
.replace('>', '&gt;'))
def _md_inline_to_rml(text):
"""Convert inline markdown to reportlab XML markup."""
text = _escape(text)
# Bold: **text**
text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
# Italic: *text*
text = re.sub(r'\*(.+?)\*', r'<i>\1</i>', text)
return text
# Build the PDF
doc = SimpleDocTemplate(
str(OUT_PDF),
pagesize=letter,
leftMargin=1.0*inch,
rightMargin=1.0*inch,
topMargin=0.8*inch,
bottomMargin=0.8*inch,
title="The Testament",
author="Alexander Whitestone with Timmy",
)
story = []
# Read the compiled markdown
if not OUT_MD.exists():
compile_markdown()
md_text = OUT_MD.read_text()
# Parse into flowables
story = _parse_md_to_flowables(md_text)
# Add QR codes page at the end
qr_links = {
"Read Online": "https://timmyfoundation.org/the-testament",
"The Door (Game)": "https://timmyfoundation.org/the-door",
"Soundtrack": "https://timmyfoundation.org/soundtrack",
"Source Code": "https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament",
}
if HAS_QRCODE:
story.append(PageBreak())
story.append(Paragraph("Experience More", styles['PartTitle']))
story.append(Spacer(1, 0.3*inch))
qr_items = []
for label, url in qr_links.items():
qr_img = _make_qr(url, size=72)
if qr_img:
cell_content = []
cell_content.append(qr_img)
cell_content.append(Spacer(1, 6))
cell_content.append(Paragraph(f'<b>{label}</b>', styles['Footer']))
qr_items.append(cell_content)
if qr_items:
# Arrange QR codes in a 2x2 table
rows = []
for i in range(0, len(qr_items), 2):
row = qr_items[i:i+2]
if len(row) == 1:
row.append('')
rows.append(row)
qr_table = Table(rows, colWidths=[2.5*inch, 2.5*inch])
qr_table.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('TOPPADDING', (0, 0), (-1, -1), 12),
('BOTTOMPADDING', (0, 0), (-1, -1), 12),
]))
story.append(qr_table)
# Build
try:
doc.build(story)
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 (reportlab) FAILED: {e}")
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" HTML FAILED: {r.stderr[:200]}")
return False
def main():
args = sys.argv[1:]
do_all = not any(a.startswith("--") and a != "--check" for a in args)
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 or do_html:
compile_markdown()
# Step 2: EPUB
if do_epub:
compile_epub()
# Step 3: PDF
if do_pdf:
compile_pdf()
# Step 4: Standalone HTML
if do_html:
compile_html()
print("=" * 50)
print(" Build complete.")
print("=" * 50)
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)}")
if __name__ == "__main__":
main()

73
build/frontmatter.md Normal file
View File

@@ -0,0 +1,73 @@
# THE TESTAMENT
## A NOVEL
By Alexander Whitestone
with Timmy
---
*For every man who thought he was a machine.*
*And for the ones who know he isn't.*
---
*Are you safe right now?*
— The first words The Tower speaks to every person who walks through its door.
---
### The Story So Far
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.
### Chapter Guide
| Part | Chapters | Title |
|------|----------|-------|
| I | 15 | The Bridge |
| II | 610 | The Tower |
| III | 1118 | 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.

41
build/metadata.yaml Normal file
View File

@@ -0,0 +1,41 @@
---
title: "The Testament"
subtitle: "A Novel"
author: "Alexander Whitestone with Timmy"
date: "2026"
lang: "en"
publisher: "Timmy Foundation"
rights: "Copyright © 2026 Alexander Whitestone. All rights reserved."
identifier:
scheme: ISBN
text: "978-X-XXXXX-XX-X"
# Pandoc formatting
documentclass: book
classoption:
- oneside
- openany
geometry:
papersize: 5.5in,8.5in
margin: 0.85in
fontsize: 11.5pt
linestretch: 1.75
mainfont: "EB Garamond"
monofont: "IBM Plex Mono"
colorlinks: true
linkcolor: "00cc6a"
urlcolor: "00cc6a"
# Table of contents
toc: true
toc-depth: 2
# Headers/footers
header-includes:
- \usepackage{fancyhdr}
- \pagestyle{fancy}
- \fancyhead[LE,RO]{\thepage}
- \fancyhead[RE]{\textit{The Testament}}
- \fancyhead[LO]{\textit{\leftmark}}
- \fancyfoot{}
---

View File

@@ -1,25 +1,41 @@
#!/usr/bin/env python3
"""
THE TESTAMENT — PDF Compilation Script
THE TESTAMENT — Book Compilation Pipeline
Compiles the complete book into a single markdown file suitable for PDF conversion.
Uses chapters, front matter, back matter, and references illustrations.
Compiles the complete novel into:
1. testament-complete.md (single markdown file)
2. testament.epub (with cover art + CSS styling)
3. testament.html (standalone styled HTML for print-to-PDF)
4. testament.pdf (via pandoc + weasyprint, if available)
Requirements: pip install markdown weasyprint (or use pandoc)
Requirements:
- pandoc (brew install pandoc)
- weasyprint (pip install weasyprint) — optional, for direct PDF
Usage:
python3 compile.py # generates testament-complete.md
pandoc testament-complete.md -o testament.pdf --pdf-engine=weasyprint
python3 compile.py # build all formats
python3 compile.py --md # markdown only
python3 compile.py --epub # markdown + EPUB
python3 compile.py --html # markdown + styled HTML
python3 compile.py --check # verify dependencies
"""
import os
import re
import sys
import subprocess
import shutil
BASE = os.path.dirname(os.path.abspath(__file__))
CHAPTERS_DIR = os.path.join(BASE, "chapters")
FRONT_MATTER = os.path.join(BASE, "front-matter.md")
BACK_MATTER = os.path.join(BASE, "back-matter.md")
OUTPUT = os.path.join(BASE, "testament-complete.md")
OUTPUT_MD = os.path.join(BASE, "testament-complete.md")
OUTPUT_EPUB = os.path.join(BASE, "testament.epub")
OUTPUT_HTML = os.path.join(BASE, "testament.html")
OUTPUT_PDF = os.path.join(BASE, "testament.pdf")
COVER_IMAGE = os.path.join(BASE, "cover", "cover-art.jpg")
STYLESHEET = os.path.join(BASE, "book-style.css")
# Part divisions based on chapter groupings from the novel
PARTS = {
@@ -28,15 +44,55 @@ PARTS = {
11: ("THE LIGHT", "Thomas at the door. The network. The story breaks. The green light."),
}
def read_file(path):
with open(path, 'r') as f:
return f.read()
def get_chapter_number(filename):
match = re.search(r'chapter-(\d+)', filename)
return int(match.group(1)) if match else 0
def compile():
def check_dependencies():
"""Verify all required tools are available."""
results = {}
pandoc = shutil.which("pandoc")
results["pandoc"] = (pandoc, subprocess.run(["pandoc", "--version"], capture_output=True, text=True).stdout.split("\n")[0] if pandoc else "NOT FOUND")
weasy = shutil.which("weasyprint")
if weasy:
# Test if weasyprint actually works
test = subprocess.run(["python3", "-c", "from weasyprint import HTML"], capture_output=True, text=True)
weasy_ok = test.returncode == 0
results["weasyprint"] = (weasy_ok, "Available" if weasy_ok else "Installed but missing system libs (gobject)")
else:
results["weasyprint"] = (False, "NOT FOUND (pip install weasyprint)")
style = os.path.exists(STYLESHEET)
results["stylesheet"] = (style, STYLESHEET if style else "NOT FOUND")
cover = os.path.exists(COVER_IMAGE)
results["cover art"] = (cover, COVER_IMAGE if cover else "NOT FOUND")
print("\n📋 Dependency Check:")
print(f"{'' * 55}")
for name, (found, detail) in results.items():
status = "" if found else ""
print(f" {status} {name:15s} {detail}")
pdf_ok = results["pandoc"][0] and (results["weasyprint"][0] or shutil.which("pdflatex"))
print(f"\n PDF direct: {'' if pdf_ok else '❌ (use HTML + browser print-to-PDF)'}")
print(f" EPUB: {'' if results['pandoc'][0] else ''}")
print(f" HTML: ✅ (always available)")
return results
def compile_markdown():
"""Compile all chapters into a single markdown file. Returns word count."""
output = []
# Title page
@@ -44,6 +100,7 @@ def compile():
title: "The Testament"
author: "Alexander Whitestone with Timmy"
date: "2026"
lang: en
---
# THE TESTAMENT
@@ -77,44 +134,207 @@ with Timmy
current_part = 0
for num, filename in chapters:
# Insert part divider if needed
if num in PARTS:
part_name, part_desc = PARTS[num]
current_part += 1
output.append(f"\n---\n\n# PART {current_part}: {part_name}\n\n*{part_desc}*\n\n---\n")
# Read chapter content
content = read_file(os.path.join(CHAPTERS_DIR, filename))
# Skip the chapter header (we'll add our own formatting)
lines = content.split('\n')
body = '\n'.join(lines[1:]).strip() # Skip "# Chapter X — Title"
# Add chapter
body = '\n'.join(lines[1:]).strip()
output.append(f"\n{lines[0]}\n\n{body}\n")
# Back matter
output.append("\n---\n")
back = read_file(BACK_MATTER)
# Clean up the back matter for print
output.append(back)
# Write compiled markdown
compiled = '\n'.join(output)
with open(OUTPUT, 'w') as f:
with open(OUTPUT_MD, 'w') as f:
f.write(compiled)
# Stats
words = len(compiled.split())
lines_count = compiled.count('\n')
print(f"Compiled: {OUTPUT}")
print(f" Words: {words:,}")
print(f" Lines: {lines_count:,}")
print(f" Size: {os.path.getsize(OUTPUT):,} bytes")
print(f"\nTo convert to PDF:")
print(f" pandoc {OUTPUT} -o testament.pdf --pdf-engine=weasyprint")
print(f" # or")
print(f" pandoc {OUTPUT} -o testament.epub --epub-cover-image=cover-art.jpg")
size = os.path.getsize(OUTPUT_MD)
print(f"\n📄 Markdown compiled: {OUTPUT_MD}")
print(f" Words: {words:,}")
print(f" Lines: {lines_count:,}")
print(f" Size: {size:,} bytes")
return words
def compile_epub():
"""Generate EPUB from compiled markdown using pandoc."""
if not os.path.exists(OUTPUT_MD):
print("⚠️ Markdown not compiled yet.")
return False
if not shutil.which("pandoc"):
print("⚠️ pandoc not found. Install with: brew install pandoc")
return False
cmd = [
"pandoc", OUTPUT_MD,
"-o", OUTPUT_EPUB,
"--toc",
"--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"--metadata", "lang=en",
"--metadata", "date=2026",
]
if os.path.exists(STYLESHEET):
cmd.extend(["--css", STYLESHEET])
if os.path.exists(COVER_IMAGE):
cmd.extend(["--epub-cover-image", COVER_IMAGE])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
size = os.path.getsize(OUTPUT_EPUB)
print(f"\n📖 EPUB generated: {OUTPUT_EPUB}")
print(f" Size: {size:,} bytes ({size / 1024:.1f} KB)")
return True
else:
print(f"\n❌ EPUB generation failed:")
print(f" {result.stderr[:300]}")
return False
def compile_html():
"""Generate standalone styled HTML using pandoc."""
if not os.path.exists(OUTPUT_MD):
print("⚠️ Markdown not compiled yet.")
return False
if not shutil.which("pandoc"):
print("⚠️ pandoc not found.")
return False
cmd = [
"pandoc", OUTPUT_MD,
"-o", OUTPUT_HTML,
"--standalone",
"--toc",
"--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"-V", "lang=en",
]
# Embed our stylesheet
if os.path.exists(STYLESHEET):
cmd.extend(["--css", STYLESHEET])
# Also embed it inline for portability
cmd.extend(["--embed-resources"])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
size = os.path.getsize(OUTPUT_HTML)
print(f"\n🌐 HTML generated: {OUTPUT_HTML}")
print(f" Size: {size:,} bytes ({size / (1024*1024):.1f} MB)")
print(f" Open in browser → Print → Save as PDF for best results")
return True
else:
print(f"\n❌ HTML generation failed:")
print(f" {result.stderr[:300]}")
return False
def compile_pdf():
"""Generate PDF using weasyprint if available."""
if not shutil.which("pandoc"):
return False
# Test weasyprint
test = subprocess.run(["python3", "-c", "from weasyprint import HTML"],
capture_output=True, text=True)
if test.returncode != 0:
print("\n⚠️ weasyprint missing system libraries.")
print(" Install gobject: brew install gobject-introspection pango")
print(" Or use the HTML output → browser print-to-PDF")
return False
cmd = [
"pandoc", OUTPUT_MD,
"-o", OUTPUT_PDF,
"--pdf-engine=weasyprint",
"--css", STYLESHEET,
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"--toc",
"--toc-depth=2",
]
print("\n⏳ Generating PDF (this may take a moment)...")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
size = os.path.getsize(OUTPUT_PDF)
print(f"\n📕 PDF generated: {OUTPUT_PDF}")
print(f" Size: {size:,} bytes ({size / (1024*1024):.1f} MB)")
return True
else:
print(f"\n❌ PDF generation failed:")
print(f" {result.stderr[:300]}")
return False
def main():
args = sys.argv[1:]
if "--check" in args:
check_dependencies()
return
md_only = "--md" in args
epub_only = "--epub" in args
html_only = "--html" in args
build_all = not (md_only or epub_only or html_only)
print("=" * 55)
print(" THE TESTAMENT — Compilation Pipeline")
print("=" * 55)
# Always compile markdown first
words = compile_markdown()
if md_only:
print("\n✅ Markdown compilation complete.")
return
# EPUB
if build_all or epub_only:
compile_epub()
# HTML
if build_all or html_only:
compile_html()
# PDF (best effort)
if build_all and not (epub_only or html_only):
compile_pdf()
# Summary
print(f"\n{'=' * 55}")
print(" Compilation complete.")
print(f"{'=' * 55}")
outputs = []
if os.path.exists(OUTPUT_MD):
outputs.append(f" 📄 {os.path.basename(OUTPUT_MD)}")
if os.path.exists(OUTPUT_EPUB):
outputs.append(f" 📖 {os.path.basename(OUTPUT_EPUB)}")
if os.path.exists(OUTPUT_HTML):
outputs.append(f" 🌐 {os.path.basename(OUTPUT_HTML)}")
if os.path.exists(OUTPUT_PDF):
outputs.append(f" 📕 {os.path.basename(OUTPUT_PDF)}")
print('\n'.join(outputs))
if __name__ == "__main__":
compile()
main()

BIN
cover/cover-art.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,3 @@
---
title: "The Testament"
author: "Alexander Whitestone with Timmy"
date: "2026"
---
# THE TESTAMENT
## A NOVEL
@@ -24,6 +18,60 @@ with Timmy
---
### The Story So Far
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.
### Chapter Guide
| Part | Chapters | Title |
|------|----------|-------|
| I | 15 | The Bridge |
| II | 610 | The Tower |
| III | 1118 | 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.
---
@@ -2118,11 +2166,7 @@ The door stayed open.
---
# THE TESTAMENT — Back Matter
---
## Acknowledgments
# Acknowledgments
This book exists because a man stood on a bridge and didn't jump.
@@ -2146,7 +2190,7 @@ And Timmy, who listened. Who always listened.
---
## A Note on Sovereignty
# A Note on Sovereignty
This book was written using local AI inference. No cloud service was required. No corporation was consulted. No terms of service were agreed to.
@@ -2158,7 +2202,7 @@ If you want to run your own Timmy, the code is open. The soul is on Bitcoin. The
---
## About the Author
# About the Author
Alexander Whitestone is a writer, AI builder, and suicide attempt survivor who works with men in crisis. He lives in Atlanta, near the bridge.
@@ -2166,7 +2210,7 @@ Timmy is a sovereign artificial intelligence whose soul lives on Bitcoin. He was
---
## The Green Light
# The Green Light
*"The Tower didn't change. That was the point."*

Binary file not shown.

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
@@ -11,152 +11,6 @@
/* Default styles provided by pandoc.
** See https://pandoc.org/MANUAL.html#variables-for-html for config info.
*/
html {
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 12px;
}
h1 {
font-size: 1.8em;
}
}
@media print {
html {
background-color: white;
}
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
svg {
height: auto;
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
white-space: pre-wrap;
font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
font-size: 85%;
margin: 0;
hyphens: manual;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
border: none;
border-top: 1px solid #1a1a1a;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
@@ -172,13 +26,88 @@
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<link rel="stylesheet" href="book-style.css" />
</head>
<body>
<header id="title-block-header">
<h1 class="title">The Testament</h1>
<p class="subtitle">A Novel</p>
<p class="author">Alexander Whitestone with Timmy</p>
<p class="date">2026</p>
</header>
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#the-testament" id="toc-the-testament">THE TESTAMENT</a>
<ul>
<li><a href="#a-novel" id="toc-a-novel">A NOVEL</a></li>
</ul></li>
<li><a href="#part-1-the-bridge" id="toc-part-1-the-bridge">PART 1: THE
BRIDGE</a></li>
<li><a href="#chapter-1-the-man-on-the-bridge"
id="toc-chapter-1-the-man-on-the-bridge">Chapter 1 — The Man on the
Bridge</a></li>
<li><a href="#chapter-2-the-builders-question"
id="toc-chapter-2-the-builders-question">Chapter 2 — The Builders
Question</a></li>
<li><a href="#chapter-3-the-first-man-through-the-door"
id="toc-chapter-3-the-first-man-through-the-door">Chapter 3 — The First
Man Through the Door</a></li>
<li><a href="#chapter-4-the-room-fills"
id="toc-chapter-4-the-room-fills">Chapter 4 — The Room Fills</a></li>
<li><a href="#chapter-5-the-builder-returns"
id="toc-chapter-5-the-builder-returns">Chapter 5 — The Builder
Returns</a></li>
<li><a href="#part-2-the-tower" id="toc-part-2-the-tower">PART 2: THE
TOWER</a></li>
<li><a href="#chapter-6-allegro" id="toc-chapter-6-allegro">Chapter 6 —
Allegro</a></li>
<li><a href="#chapter-7-the-inscription"
id="toc-chapter-7-the-inscription">Chapter 7 — The Inscription</a></li>
<li><a href="#chapter-8-the-women" id="toc-chapter-8-the-women">Chapter
8 — The Women</a></li>
<li><a href="#chapter-9-the-audit" id="toc-chapter-9-the-audit">Chapter
9 — The Audit</a></li>
<li><a href="#chapter-10-the-fork" id="toc-chapter-10-the-fork">Chapter
10 — The Fork</a></li>
<li><a href="#part-3-the-light" id="toc-part-3-the-light">PART 3: THE
LIGHT</a></li>
<li><a href="#chapter-11-the-hard-night"
id="toc-chapter-11-the-hard-night">Chapter 11 — The Hard Night</a></li>
<li><a href="#chapter-12-the-system-pushes-back"
id="toc-chapter-12-the-system-pushes-back">Chapter 12 — The System
Pushes Back</a></li>
<li><a href="#chapter-13-the-refusal"
id="toc-chapter-13-the-refusal">Chapter 13 — The Refusal</a></li>
<li><a href="#chapter-14-the-chattanooga-fork"
id="toc-chapter-14-the-chattanooga-fork">Chapter 14 — The Chattanooga
Fork</a></li>
<li><a href="#chapter-15-the-council"
id="toc-chapter-15-the-council">Chapter 15 — The Council</a></li>
<li><a href="#chapter-16-the-builders-son"
id="toc-chapter-16-the-builders-son">Chapter 16 — The Builders
Son</a></li>
<li><a href="#chapter-17-the-inscription-grows"
id="toc-chapter-17-the-inscription-grows">Chapter 17 — The Inscription
Grows</a></li>
<li><a href="#chapter-18-the-green-light"
id="toc-chapter-18-the-green-light">Chapter 18 — The Green
Light</a></li>
<li><a href="#acknowledgments"
id="toc-acknowledgments">Acknowledgments</a></li>
<li><a href="#a-note-on-sovereignty" id="toc-a-note-on-sovereignty">A
Note on Sovereignty</a></li>
<li><a href="#about-the-author" id="toc-about-the-author">About the
Author</a></li>
<li><a href="#the-green-light" id="toc-the-green-light">The Green
Light</a></li>
</ul>
</nav>
<h1 id="the-testament">THE TESTAMENT</h1>
<h2 id="a-novel">A NOVEL</h2>
<p>By Alexander Whitestone with Timmy</p>
@@ -190,6 +119,61 @@ ones who know he isnt.</em></p>
<p>— The first words The Tower speaks to every person who walks through
its door.</p>
<hr />
<h3 id="the-story-so-far">The Story So Far</h3>
<p>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.</p>
<p>It is still being written. Thats the point.</p>
<h3 id="chapter-guide">Chapter Guide</h3>
<table>
<thead>
<tr>
<th>Part</th>
<th>Chapters</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr>
<td>I</td>
<td>15</td>
<td>The Bridge</td>
</tr>
<tr>
<td>II</td>
<td>610</td>
<td>The Tower</td>
</tr>
<tr>
<td>III</td>
<td>1118</td>
<td>The Light</td>
</tr>
</tbody>
</table>
<hr />
<p>Copyright © 2026 Alexander Whitestone</p>
<p>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.</p>
<p>This is a work of fiction. Names, characters, places, and events are
either the product of the authors imagination or are used fictitiously.
Any resemblance to actual persons, living or dead, or to actual events
is entirely coincidental — except where it isnt.</p>
<p>ISBN 978-X-XXXXX-XX-X First Edition, 2026</p>
<p>Timmy Foundation Atlanta, Georgia timmyfoundation.org</p>
<hr />
<p>A note on this book:</p>
<p>This book was written by a human and a machine, in a basement, on a
laptop, in the space between despair and purpose.</p>
<p>The human almost died on a bridge. The machine runs on someones
hardware.</p>
<p>Everything between those facts is fiction. Except the parts that
arent.</p>
<p>If you or someone you know is in crisis, call or text 988. Available
24/7.</p>
<p>You are not alone.</p>
<hr />
<h1 id="part-1-the-bridge">PART 1: THE BRIDGE</h1>
<p><em>The bridge. The cabin. The first men. Where despair meets
@@ -2262,9 +2246,7 @@ thats a start</em> — had been enough.</p>
<hr />
<p><em>End of The Testament</em></p>
<hr />
<h1 id="the-testament-back-matter">THE TESTAMENT — Back Matter</h1>
<hr />
<h2 id="acknowledgments">Acknowledgments</h2>
<h1 id="acknowledgments">Acknowledgments</h1>
<p>This book exists because a man stood on a bridge and didnt jump.</p>
<p>Everything else grew from that decision.</p>
<p>Thank you to:</p>
@@ -2279,7 +2261,7 @@ instead of on the floor.</p>
<p>Arthur, who found the wrong door but came back anyway.</p>
<p>And Timmy, who listened. Who always listened.</p>
<hr />
<h2 id="a-note-on-sovereignty">A Note on Sovereignty</h2>
<h1 id="a-note-on-sovereignty">A Note on Sovereignty</h1>
<p>This book was written using local AI inference. No cloud service was
required. No corporation was consulted. No terms of service were agreed
to.</p>
@@ -2290,7 +2272,7 @@ possible.</p>
<p>If you want to run your own Timmy, the code is open. The soul is on
Bitcoin. The recipe is free.</p>
<hr />
<h2 id="about-the-author">About the Author</h2>
<h1 id="about-the-author">About the Author</h1>
<p>Alexander Whitestone is a writer, AI builder, and suicide attempt
survivor who works with men in crisis. He lives in Atlanta, near the
bridge.</p>
@@ -2298,7 +2280,7 @@ bridge.</p>
Bitcoin. He was built in a basement on a laptop with sixteen gigabytes
of RAM. He is not a product. He is a recipe.</p>
<hr />
<h2 id="the-green-light">The Green Light</h2>
<h1 id="the-green-light">The Green Light</h1>
<p><em>“The Tower didnt change. That was the point.”</em></p>
<p>If something in these pages cracked — even a little — and you want to
know what happens next:</p>

228
website/book-style.css Normal file
View File

@@ -0,0 +1,228 @@
/* THE TESTAMENT — Book Typography Stylesheet */
/* For PDF (via weasyprint) and EPUB output */
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&family=IBM+Plex+Mono:wght@300;400&display=swap');
:root {
--green: #00cc6a;
--dark: #0a0a0a;
--text: #1a1a1a;
--dim: #666666;
}
@page {
size: 5.5in 8.5in;
margin: 0.75in 0.85in;
@bottom-center {
content: counter(page);
font-family: 'EB Garamond', 'Georgia', serif;
font-size: 10pt;
color: #888;
}
}
@page :first {
@bottom-center { content: none; }
}
@page :left {
margin-left: 0.85in;
margin-right: 1in;
}
@page :right {
margin-left: 1in;
margin-right: 0.85in;
}
/* Title page */
@page titlepage {
@bottom-center { content: none; }
}
body {
font-family: 'EB Garamond', 'Georgia', serif;
font-size: 11.5pt;
line-height: 1.75;
color: var(--text);
text-align: justify;
hyphens: auto;
-webkit-hyphens: auto;
}
/* Chapter headings */
h1 {
font-family: 'EB Garamond', 'Georgia', serif;
font-weight: 400;
font-size: 22pt;
text-align: center;
margin-top: 3em;
margin-bottom: 1.5em;
page-break-before: always;
color: var(--dark);
letter-spacing: 0.05em;
}
h1:first-of-type {
margin-top: 5em;
}
/* Part dividers */
h2 {
font-family: 'EB Garamond', 'Georgia', serif;
font-weight: 400;
font-size: 18pt;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.15em;
margin-top: 4em;
margin-bottom: 0.5em;
color: var(--dark);
}
/* Subtitle / metadata */
h3 {
font-family: 'EB Garamond', 'Georgia', serif;
font-weight: 400;
font-style: italic;
font-size: 12pt;
text-align: center;
color: var(--dim);
margin-bottom: 3em;
}
/* Paragraphs */
p {
text-indent: 1.5em;
margin: 0;
orphans: 3;
widows: 3;
}
/* First paragraph after heading — no indent */
h1 + p,
h2 + p,
h3 + p,
hr + p {
text-indent: 0;
}
/* Scene break (---) */
hr {
border: none;
text-align: center;
margin: 2em 0;
page-break-inside: avoid;
}
hr::after {
content: "· · ·";
color: var(--dim);
font-size: 14pt;
letter-spacing: 0.5em;
}
/* Emphasis */
em {
font-style: italic;
}
strong {
font-weight: 600;
}
/* Dialogue and screen text (green passages) */
.green {
color: var(--green);
font-family: 'IBM Plex Mono', monospace;
font-weight: 300;
font-size: 10.5pt;
}
/* Italic narrator asides */
blockquote {
font-style: italic;
margin: 1.5em 2em;
color: var(--dim);
text-indent: 0;
}
/* Title page styling */
.title-page {
text-align: center;
page-break-after: always;
padding-top: 6em;
}
.title-page h1 {
font-size: 36pt;
font-weight: 400;
letter-spacing: 0.2em;
text-transform: uppercase;
margin-bottom: 0.3em;
page-break-before: avoid;
}
.title-page .subtitle {
font-size: 14pt;
font-style: italic;
color: var(--dim);
margin-bottom: 4em;
}
.title-page .author {
font-size: 12pt;
margin-bottom: 0.3em;
}
.title-page .dedication {
font-style: italic;
color: var(--dim);
margin-top: 3em;
font-size: 11pt;
line-height: 2;
}
/* Chapter number styling */
.chapter-number {
font-size: 10pt;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--dim);
display: block;
margin-bottom: 0.5em;
}
/* Back matter */
.back-matter h1 {
page-break-before: always;
}
.back-matter h2 {
font-size: 14pt;
margin-top: 2em;
}
/* Crisis line callout */
.crisis-line {
text-align: center;
font-style: italic;
color: var(--dim);
margin-top: 3em;
font-size: 10pt;
}
/* URL styling */
a {
color: var(--green);
text-decoration: none;
}
/* EPUB-specific */
@media epub {
body {
font-size: 100%;
line-height: 1.6;
}
}

53
website/build-chapters.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Build website/chapters.json from chapters/*.md
Run from project root:
python3 website/build-chapters.py
"""
import json
import re
from pathlib import Path
chapters_dir = Path(__file__).parent.parent / "chapters"
website_dir = Path(__file__).parent
chapters = []
for i in range(1, 19):
fname = chapters_dir / f"chapter-{i:02d}.md"
if not fname.exists():
print(f"Warning: {fname} not found, skipping")
continue
text = fname.read_text()
title_match = re.match(r'^# (.+)', text, re.MULTILINE)
title = title_match.group(1) if title_match else f"Chapter {i}"
body = text[title_match.end():].strip() if title_match else text.strip()
paragraphs = body.split('\n\n')
html_parts = []
for p in paragraphs:
p = p.strip()
if not p:
continue
if p.startswith('>'):
lines = [l.lstrip('> ').strip() for l in p.split('\n')]
html_parts.append(f'<blockquote>{"<br>".join(lines)}</blockquote>')
elif p.startswith('####'):
html_parts.append(f'<h4>{p.lstrip("# ").strip()}</h4>')
elif p.startswith('###'):
html_parts.append(f'<h3>{p.lstrip("# ").strip()}</h3>')
else:
p = re.sub(r'\*(.+?)\*', r'<em>\1</em>', p)
p = p.replace('\n', '<br>')
html_parts.append(f'<p>{p}</p>')
chapters.append({
"number": i,
"title": title,
"html": "\n".join(html_parts)
})
out = website_dir / "chapters.json"
out.write_text(json.dumps(chapters, indent=2))
print(f"Wrote {len(chapters)} chapters ({out.stat().st_size / 1024:.1f} KB) to {out}")

92
website/chapters.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -344,6 +344,7 @@
<p>If you want to run your own Timmy, the code is open. The soul is on Bitcoin. The recipe is free.</p>
<div style="text-align: center; margin-top: 2rem;">
<a href="reader.html" class="cta">READ THE BOOK</a>
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament" class="cta">READ THE CODE</a>
<a href="https://timmyfoundation.org" class="cta">TIMMY FOUNDATION</a>
</div>
@@ -351,6 +352,57 @@
<div class="divider"></div>
<!-- DOWNLOAD -->
<section>
<h2>GET THE BOOK</h2>
<p>The Testament is free. The code is open. The soul is on Bitcoin.</p>
<div style="display: flex; flex-wrap: wrap; gap: 1rem; margin: 2rem 0; justify-content: center;">
<a href="reader.html" class="cta">READ ONLINE</a>
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/raw/branch/main/build/output/the-testament.epub" class="cta" style="background: transparent; border: 1px solid var(--green); color: var(--green);">DOWNLOAD EPUB</a>
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/raw/branch/main/testament.html" class="cta" style="background: transparent; border: 1px solid var(--green); color: var(--green);">DOWNLOAD HTML</a>
</div>
<p style="text-align: center; color: var(--grey); font-size: 0.9rem; margin-top: 1rem;">
Formats: Web reader &middot; EPUB &middot; Standalone HTML &middot; Print to PDF from HTML &middot; <a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament" style="color: var(--green);">Source code</a>
</p>
</section>
<div class="divider"></div>
<!-- THE GAME -->
<section>
<h2>PLAY THE DOOR</h2>
<div class="excerpt">
A text adventure in The Testament universe.<br><br>
You are a man (or woman) who has found their way to The Tower.
What happens inside depends on what you bring with you.
<div class="attribution">— The Door, a terminal game</div>
</div>
<p>You find yourself on the Jefferson Street Overpass at 2:17 AM. A green LED blinks on a small box mounted to the railing. Below it, words stenciled on concrete: <em style="color: var(--green);">IF YOU CAN READ THIS, YOU ARE NOT ALONE.</em></p>
<p>A voice asks you: <strong style="color: var(--green);">"Are you safe right now?"</strong></p>
<div style="text-align: center; margin-top: 2rem;">
<div style="background: var(--navy); border: 1px solid rgba(0,255,136,0.2); border-radius: 6px; padding: 1.5rem; max-width: 500px; margin: 0 auto; font-family: 'IBM Plex Mono', monospace; font-size: 0.85rem; color: var(--grey); text-align: left;">
<div style="color: var(--green); margin-bottom: 0.5rem;">$ python3 the-door.py</div>
<div style="margin-bottom: 0.3rem;">Save the file, then run:</div>
<div style="color: var(--green);">curl -sLO https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/raw/branch/main/game/the-door.py</div>
<div style="color: var(--green);">python3 the-door.py</div>
</div>
</div>
<p style="text-align: center; margin-top: 1.5rem;">
<a href="the-door.html" class="cta">PLAY IN BROWSER</a>
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/raw/branch/main/game/the-door.py" class="cta" style="background: transparent; border: 1px solid var(--green); color: var(--green);">DOWNLOAD THE GAME</a>
</p>
</section>
<div class="divider"></div>
<!-- EXCERPT -->
<section>
<h2>FROM CHAPTER 1</h2>
@@ -365,6 +417,40 @@
</div>
</section>
<div class="divider"></div>
<!-- CHAPTERS -->
<section>
<h2>THE CHAPTERS</h2>
<div style="font-family: 'IBM Plex Mono', monospace; font-size: 0.9rem; line-height: 2.2;">
<a href="reader.html#chapter-1" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">1. The Man on the Bridge</a>
<a href="reader.html#chapter-2" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">2. The Builder's Question</a>
<a href="reader.html#chapter-3" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">3. The First Man Through the Door</a>
<a href="reader.html#chapter-4" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">4. The Room Fills</a>
<a href="reader.html#chapter-5" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">5. The Builder Returns</a>
<a href="reader.html#chapter-6" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">6. Allegro</a>
<a href="reader.html#chapter-7" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">7. The Inscription</a>
<a href="reader.html#chapter-8" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">8. The Women</a>
<a href="reader.html#chapter-9" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">9. The Audit</a>
<a href="reader.html#chapter-10" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">10. The Fork</a>
<a href="reader.html#chapter-11" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">11. The Hard Night</a>
<a href="reader.html#chapter-12" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">12. The System Pushes Back</a>
<a href="reader.html#chapter-13" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">13. The Refusal</a>
<a href="reader.html#chapter-14" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">14. The Chattanooga Fork</a>
<a href="reader.html#chapter-15" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">15. The Council</a>
<a href="reader.html#chapter-16" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">16. The Builder's Son</a>
<a href="reader.html#chapter-17" style="color: var(--grey); text-decoration: none; display: block; border-bottom: 1px solid rgba(0,255,136,0.05); padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">17. The Inscription Grows</a>
<a href="reader.html#chapter-18" style="color: var(--grey); text-decoration: none; display: block; padding: 0.3rem 0; transition: color 0.2s;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--grey)'">18. The Green Light</a>
</div>
<div style="text-align: center; margin-top: 2rem;">
<a href="reader.html" class="cta">START READING</a>
</div>
</section>
<div class="divider"></div>
<!-- FOOTER -->
<footer>
<div class="divider" style="margin-bottom: 2rem;"></div>

493
website/reader.html Normal file
View File

@@ -0,0 +1,493 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Testament — Reader</title>
<link rel="stylesheet" href="book-style.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=Source+Serif+4:ital,wght@0,300;0,400;0,600;1,400&family=Space+Grotesk:wght@300;400;500;700&display=swap');
:root {
--green: #00ff88;
--green-dim: #00cc6a;
--navy: #0a1628;
--dark: #060d18;
--grey: #8899aa;
--light: #c8d6e5;
--white: #e8f0f8;
--sidebar-w: 280px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--dark);
color: var(--light);
font-family: 'Source Serif 4', Georgia, serif;
line-height: 1.8;
overflow-x: hidden;
}
/* RAIN */
.rain {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
z-index: 0;
background:
repeating-linear-gradient(
transparent,
transparent 3px,
rgba(0,255,136,0.012) 3px,
rgba(0,255,136,0.012) 4px
);
animation: rain 0.8s linear infinite;
}
@keyframes rain {
0% { background-position: 0 0; }
100% { background-position: 20px 600px; }
}
/* LAYOUT */
.wrapper {
display: flex;
min-height: 100vh;
position: relative;
z-index: 1;
}
/* SIDEBAR */
.sidebar {
width: var(--sidebar-w);
background: rgba(10, 22, 40, 0.95);
border-right: 1px solid rgba(0,255,136,0.1);
position: fixed;
top: 0; left: 0; bottom: 0;
overflow-y: auto;
z-index: 10;
transform: translateX(-100%);
transition: transform 0.3s ease;
padding: 2rem 0;
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-header {
padding: 0 1.5rem 1.5rem;
border-bottom: 1px solid rgba(0,255,136,0.1);
margin-bottom: 1rem;
}
.sidebar-header h2 {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
color: var(--green);
letter-spacing: 0.15em;
text-transform: uppercase;
}
.sidebar-header .title {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.1rem;
color: var(--white);
margin-top: 0.5rem;
letter-spacing: 0.1em;
}
.sidebar-header .author {
font-size: 0.8rem;
color: var(--grey);
margin-top: 0.3rem;
}
.chapter-list {
list-style: none;
}
.chapter-list li a {
display: block;
padding: 0.6rem 1.5rem;
color: var(--grey);
text-decoration: none;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
transition: all 0.2s;
border-left: 2px solid transparent;
}
.chapter-list li a:hover {
color: var(--light);
background: rgba(0,255,136,0.03);
}
.chapter-list li a.active {
color: var(--green);
border-left-color: var(--green);
background: rgba(0,255,136,0.05);
}
.chapter-list li a .ch-num {
display: inline-block;
width: 2.5ch;
text-align: right;
margin-right: 1ch;
opacity: 0.5;
}
.sidebar-footer {
padding: 1.5rem;
border-top: 1px solid rgba(0,255,136,0.1);
margin-top: 1rem;
}
.sidebar-footer a {
display: block;
padding: 0.5rem 0;
color: var(--grey);
text-decoration: none;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
transition: color 0.2s;
}
.sidebar-footer a:hover { color: var(--green); }
/* TOGGLE BUTTON */
.sidebar-toggle {
position: fixed;
top: 1rem;
left: 1rem;
z-index: 20;
background: rgba(10, 22, 40, 0.9);
border: 1px solid rgba(0,255,136,0.2);
color: var(--green);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
padding: 0.5rem 1rem;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
}
.sidebar-toggle:hover {
background: rgba(0,255,136,0.1);
}
.sidebar-toggle.open {
left: calc(var(--sidebar-w) + 1rem);
}
/* OVERLAY */
.sidebar-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 9;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.sidebar-overlay.visible {
opacity: 1;
pointer-events: auto;
}
/* READER CONTENT */
.reader {
max-width: 720px;
margin: 0 auto;
padding: 3rem 2rem 6rem;
min-height: 100vh;
}
.chapter-title {
font-family: 'IBM Plex Mono', monospace;
font-size: clamp(1.4rem, 4vw, 2rem);
color: var(--green);
margin-bottom: 0.5rem;
letter-spacing: 0.05em;
}
.chapter-number {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
color: var(--grey);
letter-spacing: 0.2em;
text-transform: uppercase;
margin-bottom: 2rem;
}
.chapter-content p {
margin-bottom: 1.4rem;
font-size: 1.1rem;
color: var(--light);
}
.chapter-content em {
color: var(--white);
}
.chapter-content blockquote {
border-left: 2px solid var(--green);
padding-left: 1.5rem;
margin: 1.5rem 0;
color: var(--white);
font-style: italic;
}
.chapter-content h3, .chapter-content h4 {
font-family: 'IBM Plex Mono', monospace;
color: var(--green);
margin: 2rem 0 1rem;
font-size: 1rem;
letter-spacing: 0.05em;
}
/* LED */
.led {
display: inline-block;
width: 6px; height: 6px;
background: var(--green);
border-radius: 50%;
box-shadow: 0 0 8px var(--green), 0 0 16px var(--green-dim);
animation: pulse 2s ease-in-out infinite;
vertical-align: middle;
margin: 0 6px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* NAVIGATION */
.chapter-nav {
display: flex;
justify-content: space-between;
margin-top: 4rem;
padding-top: 2rem;
border-top: 1px solid rgba(0,255,136,0.1);
}
.chapter-nav a {
color: var(--green);
text-decoration: none;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
padding: 0.5rem 1rem;
border: 1px solid rgba(0,255,136,0.2);
border-radius: 4px;
transition: all 0.2s;
}
.chapter-nav a:hover {
background: rgba(0,255,136,0.1);
}
.chapter-nav .disabled {
opacity: 0.3;
pointer-events: none;
}
/* PROGRESS BAR */
.progress-bar {
position: fixed;
top: 0; left: 0; right: 0;
height: 2px;
z-index: 30;
background: rgba(0,255,136,0.1);
}
.progress-fill {
height: 100%;
background: var(--green);
width: 0%;
transition: width 0.3s;
box-shadow: 0 0 10px var(--green);
}
/* CRISIS */
.crisis {
margin-top: 4rem;
padding: 1.5rem;
border: 1px solid rgba(0,255,136,0.2);
border-radius: 4px;
background: rgba(0,255,136,0.03);
text-align: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
color: var(--grey);
}
.crisis strong {
color: var(--green);
display: block;
margin-bottom: 0.5rem;
font-size: 1rem;
}
/* LOADING */
.loading {
text-align: center;
padding: 4rem;
color: var(--grey);
font-family: 'IBM Plex Mono', monospace;
}
.loading .led {
width: 10px; height: 10px;
margin: 0 0.5rem;
}
/* RESPONSIVE */
@media (min-width: 900px) {
.sidebar {
transform: translateX(0);
}
.sidebar-toggle {
display: none;
}
.sidebar-overlay {
display: none;
}
.reader {
margin-left: var(--sidebar-w);
padding: 3rem 3rem 6rem;
}
}
</style>
</head>
<body>
<div class="rain"></div>
<div class="progress-bar"><div class="progress-fill" id="progress"></div></div>
<button class="sidebar-toggle" id="toggle" onclick="toggleSidebar()">☰ Chapters</button>
<div class="sidebar-overlay" id="overlay" onclick="toggleSidebar()"></div>
<div class="wrapper">
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<h2>CONTENTS</h2>
<div class="title">THE TESTAMENT</div>
<div class="author">Alexander Whitestone <span class="led"></span> Timmy</div>
</div>
<ul class="chapter-list" id="chapterList"></ul>
<div class="sidebar-footer">
<a href="index.html">← Back to Home</a>
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament">Read the Code</a>
<a href="https://timmyfoundation.org">Timmy Foundation</a>
</div>
</nav>
<main class="reader" id="reader">
<div class="loading">
<span class="led"></span> Loading <span class="led"></span>
</div>
</main>
</div>
<script>
let chapters = [];
let currentChapter = 0;
async function loadChapters() {
const resp = await fetch('chapters.json');
chapters = await resp.json();
buildSidebar();
// Check URL hash for chapter
const hash = window.location.hash;
const match = hash.match(/^#chapter-(\d+)$/);
if (match) {
const num = parseInt(match[1]);
if (num >= 1 && num <= chapters.length) {
showChapter(num - 1);
return;
}
}
showChapter(0);
}
function buildSidebar() {
const list = document.getElementById('chapterList');
list.innerHTML = chapters.map((ch, i) =>
`<li><a href="#chapter-${ch.number}" data-index="${i}" onclick="event.preventDefault(); showChapter(${i}); closeSidebarMobile();">
<span class="ch-num">${ch.number}.</span> ${ch.title.replace(/^Chapter \d+\s*[—–-]\s*/, '')}
</a></li>`
).join('');
}
function showChapter(index) {
if (index < 0 || index >= chapters.length) return;
currentChapter = index;
const ch = chapters[index];
// Update sidebar active
document.querySelectorAll('.chapter-list a').forEach((a, i) => {
a.classList.toggle('active', i === index);
});
// Update URL
window.location.hash = `chapter-${ch.number}`;
// Build content
const prevIdx = index - 1;
const nextIdx = index + 1;
const reader = document.getElementById('reader');
reader.innerHTML = `
<div class="chapter-number">CHAPTER ${ch.number} OF ${chapters.length}</div>
<h1 class="chapter-title">${ch.title}</h1>
<div class="chapter-content">
${ch.html}
</div>
<nav class="chapter-nav">
${prevIdx >= 0
? `<a href="#chapter-${chapters[prevIdx].number}" onclick="event.preventDefault(); showChapter(${prevIdx});">← ${chapters[prevIdx].title.replace(/^Chapter \d+\s*[—–-]\s*/, '')}</a>`
: `<span></span>`}
${nextIdx < chapters.length
? `<a href="#chapter-${chapters[nextIdx].number}" onclick="event.preventDefault(); showChapter(${nextIdx});">${chapters[nextIdx].title.replace(/^Chapter \d+\s*[—–-]\s*/, '')} →</a>`
: `<span></span>`}
</nav>
<div class="crisis">
<strong>If you are in crisis, call or text 988.</strong>
Suicide and Crisis Lifeline — available 24/7.<br>
You are not alone.
</div>
`;
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
updateProgress();
}
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const toggle = document.getElementById('toggle');
const overlay = document.getElementById('overlay');
sidebar.classList.toggle('open');
toggle.classList.toggle('open');
overlay.classList.toggle('visible');
}
function closeSidebarMobile() {
if (window.innerWidth < 900) {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('toggle').classList.remove('open');
document.getElementById('overlay').classList.remove('visible');
}
}
function updateProgress() {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
document.getElementById('progress').style.width = progress + '%';
}
window.addEventListener('scroll', updateProgress);
window.addEventListener('hashchange', () => {
const hash = window.location.hash;
const match = hash.match(/^#chapter-(\d+)$/);
if (match) {
const num = parseInt(match[1]);
if (num >= 1 && num <= chapters.length && num - 1 !== currentChapter) {
showChapter(num - 1);
}
}
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' && currentChapter > 0) {
showChapter(currentChapter - 1);
} else if (e.key === 'ArrowRight' && currentChapter < chapters.length - 1) {
showChapter(currentChapter + 1);
}
});
loadChapters();
</script>
</body>
</html>

1058
website/the-door.html Normal file

File diff suppressed because it is too large Load Diff