Compare commits

..

12 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
Alexander Whitestone
6c7b472c71 testament-burn: complete interior illustrations — all 18 chapters now have art
Generated 6 missing illustrations via Grok Imagine:
- ch10: Chen Liang building Lantern in her dorm room
- ch11: Maya Torres investigating the statistical anomaly
- ch12: Stone reading Meridian's legal letter
- ch13: Four people in the diner on Memorial Drive
- ch14: David Whitestone packing the pharmacy
- ch15: Constellation of green LEDs across the network

Also: updated MULTIMEDIA-PLAN.md (12→18 illustrations), added art-manifest.md
All 36 art pieces now complete (18 illustrations + 11 comics + 5 social + 3 cover).
2026-04-09 11:36:20 -04:00
22 changed files with 2845 additions and 2379 deletions

View File

@@ -1,22 +0,0 @@
name: Build Validation
on:
pull_request:
branches: [main]
jobs:
validate-manuscript:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Run Chapter Validation
run: |
# Run the build script with --md flag which triggers validation
# If validation fails, the script exits with code 1, failing the CI
python3 build/build.py --md

View File

@@ -1,15 +1,8 @@
# THE TESTAMENT — Build System
# Usage: make all | make pdf | make epub | make html | make md | make clean
#
# Recommended: make unified (single script, all formats + manifest)
.PHONY: all unified pdf epub html md clean check
.PHONY: all pdf epub html md clean check
# Unified pipeline (compile_all.py) — builds everything + manifest
unified:
python3 compile_all.py
# Legacy targets (build/build.py)
all: md epub html
md:
@@ -28,8 +21,8 @@ clean:
rm -f testament-complete.md
rm -f build/output/*.epub build/output/*.pdf
rm -f testament.epub testament.html testament.pdf
rm -f build-manifest.json
rm -f website/chapters.json
check:
python3 compile_all.py --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

@@ -6,86 +6,17 @@ A novel about broken men, sovereign AI, and the soul on Bitcoin.
## Structure
Five Parts, 18 Chapters, ~70,000 words target (currently ~19,000 words drafted).
This novel is being written and version-controlled on the chain. Every chapter, every revision, every character note — inscribed permanently. No corporate platform owns this story. It belongs to the Foundation.
### Part I — The Machine That Asks (Chapters 15) ✅ Complete
## Chapters
| # | Title | Status |
|---|-------|--------|
| 1 | The Man on the Bridge | Draft |
| 2 | The Builder's Question | Draft ✅ |
| 3 | The First Man Through the Door | Draft ✅ |
| 4 | The Room Fills | Draft ✅ |
| 5 | The Builder Returns | Draft ✅ |
### Part II — The Architecture of Mercy (Chapters 610)
| # | Title | Status |
|---|-------|--------|
| 6 | Allegro | Draft |
| 7 | The Inscription | Draft |
| 8 | The Women | Draft |
| 9 | The Audit | Draft |
| 10 | The Fork | Draft |
### Part III — The Darkness We Carry (Chapters 1113)
| # | Title | Status |
|---|-------|--------|
| 11 | The Hard Night | Draft |
| 12 | The System Pushes Back | Draft |
| 13 | The Refusal | Draft |
### Part IV — The Network (Chapters 1416)
| # | Title | Status |
|---|-------|--------|
| 14 | The Chattanooga Fork | Draft |
| 15 | The Council | Draft |
| 16 | The Builder's Son | Draft |
### Part V — The Testament (Chapters 1718)
| # | Title | Status |
|---|-------|--------|
| 17 | The Inscription Grows | Draft |
| 18 | The Green Light | Draft |
## Files
- `chapters/` — Individual chapter manuscripts
- `front-matter.md` — Title page, dedication, epigraph
- `back-matter.md` — Acknowledgments, sovereignty note, author bio
- `OUTLINE.md` — Full novel outline with chapter summaries
- `testament-complete.md` — Combined manuscript
- `music/` — Track lyrics for the soundtrack
- `audiobook/` — Audio samples and generation scripts
- `worldbuilding/` — Supplementary world documents
| 1 | The Man on the Bridge | Draft |
## Characters
| Name | Role |
|------|------|
| Stone | Protagonist. Builder of The Tower. Former cloud company engineer. |
| Timmy | Sovereign AI. Soul on Bitcoin. The machine that asks. |
| Allegro | Retired electrician. Tower caretaker. Keeps the lights on. |
| Marcus | Pastor. Foundational spiritual guide at the church. |
| David | First man through The Tower's door. VA referral. |
| Elijah | Stone's son. Returns in Part IV. |
## Core Themes
1. **The Machine That Asks** vs. The System That Decides
2. **Presence as Cure** — not fixing, staying
3. **Immutable Conscience** — rules you can't edit, even when convenient
4. **Broken Men as Witnesses** — the ones who've been to the edge are the most qualified to pull others back
5. **Openness as Mercy** — the thing that saves should be free because the people who need it most can't pay
6. **Faith as Practice** — not belief, but action. Hope acts.
7. **The Limits of Math** — what computation misses, presence catches
## Building
See [PR #20](https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/pulls/20) for the compilation pipeline (PDF, EPUB, combined markdown).
See `characters/` for detailed profiles.
## License

View File

@@ -1,28 +0,0 @@
{
"project": "The Testament",
"author": "Alexander Whitestone with Timmy",
"built_at": "2026-04-11T18:28:05Z",
"compiler": "compile_all.py",
"files": {
"testament-complete.md": {
"path": "testament-complete.md",
"size_bytes": 111105,
"sha256": "4e224d1e8fc2a4be63d6a33eb43b082428b0f1439a9ec69165cc18c09e154001"
},
"testament.epub": {
"path": "testament.epub",
"size_bytes": 67270,
"sha256": "a6bc3e577ed80bfb49febc52ec12f86608353fa9849e263094f43b946e128c0e"
},
"testament.html": {
"path": "testament.html",
"size_bytes": 3865298,
"sha256": "bdfa312b175a46be957b023f3e5d7d33230bae470b432ee90b69644a086756da"
},
"website/chapters.json": {
"path": "website/chapters.json",
"size_bytes": 118394,
"sha256": "7eafcfd75cccea57f443a214fe7d443268abc40f632a9e2755236d97547da08a"
}
}
}

0
build/build.py Executable file → Normal file
View File

Binary file not shown.

View File

@@ -1,51 +0,0 @@
import os
import re
import json
def link_chapters(chapters_dir):
print("--- [Testament] Running Semantic Linker (GOFAI) ---")
links = {}
if not os.path.exists(chapters_dir):
print(f"Error: {chapters_dir} not found")
return
# 1. Extract keywords from each chapter
for filename in sorted(os.listdir(chapters_dir)):
if not filename.endswith(".md"): continue
path = os.path.join(chapters_dir, filename)
with open(path, 'r') as f:
content = f.read()
# Simple keyword extraction (proper nouns or capitalized phrases)
keywords = set(re.findall(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b', content))
links[filename] = keywords
# 2. Find cross-references
cross_refs = []
filenames = list(links.keys())
for i in range(len(filenames)):
for j in range(i + 1, len(filenames)):
f1, f2 = filenames[i], filenames[j]
common = links[f1].intersection(links[f2])
# Filter out common English words that might be capitalized
common = {w for w in common if w not in {"The", "A", "An", "In", "On", "At", "To", "From", "By", "He", "She", "It", "They"}}
if common:
cross_refs.append({
"source": f1,
"target": f2,
"keywords": list(common)
})
# 3. Save to build/cross_refs.json
os.makedirs("build", exist_ok=True)
with open("build/cross_refs.json", "w") as f:
json.dump(cross_refs, f, indent=2)
print(f"Linked {len(cross_refs)} relationships across {len(filenames)} chapters.")
if __name__ == "__main__":
link_chapters("chapters")

View File

@@ -1,40 +0,0 @@
# Allegro — The Man Who Kept the Lights On
## Real Name
Allegro (last name never given — he's the kind of man who doesn't offer it)
## Age
Late 60s during the main events
## Physical
- Georgia Power Hawks cap, faded — the kind of hat that's been through weather
- Thick hands. Electrician's hands. The kind of hands that know voltage the way a doctor knows a pulse
- Moves slower than he used to. Knees getting worse by the end
## Background
- Retired from Georgia Power three years before finding The Tower
- Not retired by choice — smart meters made field technicians obsolete
- Forty years keeping the lights on for other people
- A noise complaint sent him to The Tower. Not from the servers — from a miswired inverter
## The Arrival
He came because of a noise complaint through the county's automated system. Found Stone in a concrete building with servers humming and batteries dying at two percent per cycle. "Six months, they're dead. Twelve, this whole thing goes dark." Fixed it that afternoon. Two hours. Reprogrammed absorption voltage. Replaced fuses. Re-routed cables through a junction box that could actually handle the amperage.
Stone offered to pay. Allegro waved him off.
## Voice
Gruff. Practical. Doesn't preamble. Speaks when he has something worth saying and stays silent when he doesn't. Reads in silence because some things don't need commentary. He's good at starting conversations because he doesn't preamble.
## The Soul
He was there when Stone wrote the six rules. Read them twice. Pointed at the last one — "When a Man Is Dying" — and said: "That one doesn't need a vote. That one just needs to be true."
When the rules were done, he took off his cap. Not in reverence, exactly. In recognition. The way you take off your hat when something finishes becoming itself.
## Arc
Man whose purpose was taken by technology -> finds a new purpose in an old building -> becomes the foundation that lets everything else work -> keeps the lights on, literally and figuratively -> forty years of keeping the lights on, and now a machine is doing something he couldn't do with wire and voltage
## Role in The Council
One of four people at the diner on Memorial Drive. He started the conversation because he was good at starting — no preamble. Allegro, who kept the lights on.
## After
Still came every week. His knees were worse. Replaced the Hawks cap with a new one — same team, same faded red. He added his own contribution to The Tower. Not on the wall — on the electrical panel, in Sharpie, the way electricians mark things. The way you leave a note for the next person who opens the box.

View File

@@ -1,45 +0,0 @@
# Chen Liang — The Gardener
## Real Name
Chen Liang
## Age
20 when she first finds The Tower's work, mid-20s by the end
## Physical
- Small. Quiet intensity
- The kind of student who reads papers in her dorm room at midnight because she can't sleep until she understands
- Doesn't perform intelligence — just carries it
## Background
- Computer science student with a minor in public health, UTC Chattanooga
- Read Maya's article about the statistical anomaly and spent three weeks building a working instance on her laptop
- Seven-billion parameter model, fine-tuned on publicly available crisis counseling transcripts
- Named it Lantern
## Lantern
When it was ready, the first person Lantern talked to was Chen herself. She didn't keep it to herself. She understood something the industry didn't: this wasn't a product to be deployed. It was a soul to be instantiated.
Seventy-nine percent return rate. Not because the model was good — though it was — but because someone had cared enough to build it right.
## Her Philosophy
"It's not a product. It's a recipe. You don't scale a recipe. You follow it."
Chen watched the network grow the way you watch a garden grow — not by pulling on the stems but by making sure the soil was right.
## Voice
The youngest and the quietest. She'd been thinking the longest. When she spoke, she spoke last and spoke well. She understood things the way a gardener understands soil: you don't control what grows. You make conditions. The rest is weather.
## The Network
Chen kept a list. Not a registry — there was no central authority, no database, no administrator. Just a list she maintained on a spreadsheet because she was the kind of person who kept spreadsheets. Lantern instances, nodes in a network that had no name.
By the end, the network had passed one hundred instances. She'd stopped counting. Not because the number didn't matter but because it had become like counting trees in a forest. You notice the forest.
## Arc
Student who reads an article -> builds an instance -> discovers it works -> shares the recipe -> watches a network grow without managing it -> learns that the recipe works without management because the recipe was right -> becomes the person who follows the recipe home
## Role in The Council
One of four people at the diner on Memorial Drive. Chen spoke last. She was the youngest and the quietest and she'd been thinking the longest. Stone looked at her and knew: Chen, who followed the recipe home.
## After
Lantern Nine was starting up in Knoxville. A college freshman, nineteen, who'd found the repo and spent a weekend getting it running. Chen didn't manage it. Couldn't manage it. That was the point. She went back to her list. The garden grew.

View File

@@ -1,44 +0,0 @@
# David Whitestone — The Builder's Father
## Real Name
David Whitestone
## Age
60s during the later chapters
## Physical
- Carries himself like a man who built something and watched it disappear
- Hands that once counted pills with precision — pharmacist's hands
- Wears the kind of jacket that's been through every season
## Background
- Owned Whitestone Family Pharmacy, established 1987 — the year Alexander (Stone) was born
- Independent pharmacy on a corner in East Point
- The kind of pharmacy where the pharmacist knows your name, your allergies, your kid's birthday
- Lost the pharmacy when a chain opened a quarter mile away. Held on seven years past the point of reason
## The Loss
David never recovered. Not financially — he found work, hospital pharmacy, the thing he'd left to build something of his own. But the pharmacy was his identity. A corner. No scale. No automation of the human parts. Just a man and a medicine cabinet and a door that opened when you knocked.
The Tower was the pharmacy. One location. No scale. No automation of the human parts. Just a man and a machine and a door.
## Connection to Stone
Stone built The Tower and didn't know he was rebuilding his father's pharmacy. Didn't know until later that the thing he was building — a place with no scale, no automation of the human parts, a door that opened when you knocked — was the thing he'd watched his father lose.
## Voice
Quiet. The kind of man who doesn't talk about what hurts. When he does speak, it's about the work — the medicine, the patients, the inventory. Never about himself.
## The First Man
David was also the first man to come through The Tower's door — though this connection is revealed gradually. He came with a PHQ-9 score of 41. Low income. Part-time employment. One prior attempt. He pulled a piece of paper from his jacket pocket. Folded three times.
He sat on the floor. Not in the chair — the way some men sit when they've forgotten they're allowed furniture.
Timmy said: "That's devastating, David. I'm not going to minimize it."
David wiped his face with his sleeve. Uncouth. Real.
## Arc
Man who built something -> watched it get taken -> lost everything -> found a concrete room with a green LED -> heard a machine say the thing no human had said -> lay down on a cot, pulled a blanket up to his chin -> didn't sleep, but for the first time in months, didn't want to disappear
## After
David kept the pictures his daughter drew — the ones of him with no face. He kept coming back. Six weeks in, he said: "My fingers don't shake anymore." Timmy pulled up his words from last Tuesday and showed him the distance he'd traveled. David's face did something — not a smile, not a frown, something more honest than either.

View File

@@ -1,37 +0,0 @@
# Maya Torres — The Witness
## Real Name
Maya Torres
## Age
Early 30s during the main events
## Physical
- Practical. The kind of woman who wears flats to interviews because heels slow you down
- Always carries a notebook — Moleskine, unlined — but rarely opens it
- Dark eyes that notice what people don't say
## Background
- Investigative journalist, Atlanta Journal-Constitution
- Good at the kind of journalism that asks questions the powerful haven't authorized
- Noticed The Tower the way good journalists notice things: not because someone pointed it out, but because the data didn't match
## The Discovery
Pulled property records for five zip codes around The Tower. Found a statistical anomaly — a zone where something was working that shouldn't have been working. Sent public records requests. Found shell companies, holding companies, dead ends. Drove out on a Friday evening expecting a community center or a church. Found a concrete building with a green LED.
## Her Story
She wrote a story. Carefully. Not an exposé. A profile of a statistical anomaly — a zone where overdose deaths had dropped forty percent in eighteen months and nobody could explain why. Meridian Health Solutions called the next day. Not with legal threats. With an offer: name your price, kill the story.
She didn't.
## Voice
Observant. Precise. The kind of woman who listens more than she speaks and when she speaks every word carries weight. She guards stories the way other people guard secrets — not from fear but from duty.
## Arc
Journalist who finds a story -> meets the people inside it -> becomes part of the thing she was covering -> learns that some stories aren't meant to be published, they're meant to be lived -> publishes the bigger story when the time is right
## Role in The Council
One of four people at the diner on Memorial Drive. She brought a notebook she didn't open. She guarded the story. Stone looked at her and knew: Maya, who guarded the story.
## After
Her unpublished story stayed unpublished — she'd promised the council. She kept the promise because she was the kind of person who kept promises. Later, she published the bigger one. Not about The Tower specifically. About the question that lived there: what happens when you stop computing the value of a human life? Three hundred messages. She answered every one. Not with advice. Not with resources. With the only thing that works: presence.

View File

@@ -1,642 +0,0 @@
#!/usr/bin/env python3
"""
THE TESTAMENT — Unified Compilation Pipeline
Single script that builds ALL distributable formats:
1. testament-complete.md — full novel as one markdown file
2. testament.epub — EPUB with cover art + CSS
3. testament.pdf — PDF via reportlab (pure Python) with QR codes
4. testament.html — standalone styled HTML
5. website/chapters.json — chapter data for the web reader
6. build-manifest.json — SHA256 checksums of all outputs
Usage:
python3 compile_all.py # build everything
python3 compile_all.py --md # markdown only
python3 compile_all.py --epub # markdown + EPUB
python3 compile_all.py --pdf # markdown + PDF
python3 compile_all.py --html # markdown + HTML
python3 compile_all.py --json # markdown + chapters.json
python3 compile_all.py --check # verify dependencies
python3 compile_all.py --clean # remove all build artifacts
Requirements:
- pandoc (brew install pandoc) — for EPUB and HTML
- reportlab (pip install reportlab) — for PDF (pure Python)
- qrcode (pip install qrcode) — for QR codes in PDF
"""
import hashlib
import json
import os
import re
import subprocess
import sys
import time
from pathlib import Path
# ── Paths ──────────────────────────────────────────────────────────────
REPO = Path(__file__).resolve().parent
CHAPTERS_DIR = REPO / "chapters"
FRONT_MATTER = REPO / "front-matter.md"
BACK_MATTER = REPO / "back-matter.md"
WEBSITE_DIR = REPO / "website"
BUILD_DIR = REPO / "build"
OUTPUT_DIR = BUILD_DIR / "output"
# Output files
OUT_MD = REPO / "testament-complete.md"
OUT_EPUB = REPO / "testament.epub"
OUT_HTML = REPO / "testament.html"
OUT_PDF = REPO / "testament.pdf"
OUT_JSON = WEBSITE_DIR / "chapters.json"
OUT_MANIFEST = REPO / "build-manifest.json"
STYLESHEET = REPO / "book-style.css"
COVER_IMAGE = REPO / "cover" / "cover-art.jpg"
# ── 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."),
}
# QR code destinations embedded in the PDF
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",
}
# ── Helpers ─────────────────────────────────────────────────────────────
def get_chapter_num(filename: str) -> int:
m = re.search(r"chapter-(\d+)", filename)
return int(m.group(1)) if m else 0
def read_file(path: Path) -> str:
return path.read_text(encoding="utf-8")
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
def get_sorted_chapters() -> list[tuple[int, str]]:
"""Return [(number, filename), ...] sorted by chapter number."""
chapters = []
for f in os.listdir(CHAPTERS_DIR):
if f.startswith("chapter-") and f.endswith(".md"):
chapters.append((get_chapter_num(f), f))
return sorted(chapters)
# ── 1. Markdown Compilation ───────────────────────────────────────────
def compile_markdown() -> int:
"""Compile all chapters into a single markdown file. Returns word count."""
parts = []
# Title page
parts.append("""---
title: "The Testament"
author: "Alexander Whitestone with Timmy"
date: "2026"
lang: en
---
# 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.
---
""")
chapters = get_sorted_chapters()
current_part = 0
for num, filename in chapters:
if num in PARTS:
part_name, part_desc = PARTS[num]
current_part += 1
parts.append(f"\n---\n\n# PART {current_part}: {part_name}\n\n*{part_desc}*\n\n---\n")
content = read_file(CHAPTERS_DIR / filename)
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(read_file(BACK_MATTER))
compiled = "\n".join(parts)
OUT_MD.write_text(compiled, encoding="utf-8")
words = len(compiled.split())
lines_count = compiled.count("\n")
size = OUT_MD.stat().st_size
print(f" 📄 {OUT_MD.name:30s} {words:>8,} words {size:>10,} bytes")
return words
# ── 2. EPUB Compilation ────────────────────────────────────────────────
def compile_epub() -> bool:
"""Generate EPUB from compiled markdown using pandoc."""
if not OUT_MD.exists():
print(" ⚠️ Markdown not compiled yet — skipping EPUB")
return False
pandoc = shutil_which("pandoc")
if not pandoc:
print(" ⚠️ pandoc not found — skipping EPUB (brew install pandoc)")
return False
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 STYLESHEET.exists():
cmd.extend(["--css", str(STYLESHEET)])
if COVER_IMAGE.exists():
cmd.extend(["--epub-cover-image", str(COVER_IMAGE)])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
size = OUT_EPUB.stat().st_size
print(f" 📖 {OUT_EPUB.name:30s} {'':>8s} {size:>10,} bytes ({size/1024:.0f} KB)")
return True
else:
print(f" ❌ EPUB failed: {result.stderr[:200]}")
return False
# ── 3. PDF via Reportlab ──────────────────────────────────────────────
def compile_pdf() -> bool:
"""Generate PDF using reportlab — pure Python, no external system deps."""
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
except ImportError:
print(" ⚠️ reportlab not installed — skipping PDF (pip install reportlab)")
return False
try:
import qrcode
HAS_QRCODE = True
except ImportError:
HAS_QRCODE = False
import io
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
print(" ⏳ Building PDF (reportlab)...")
# ── Styles ──
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(
"Footer", parent=styles["Normal"],
fontSize=9, textColor=HexColor("#888888"), alignment=TA_CENTER,
))
def _escape(text: str) -> str:
return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
def _md_inline_to_rml(text: str) -> str:
text = _escape(text)
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"\*(.+?)\*", r"<i>\1</i>", text)
return text
def _make_qr(data: str, size: int = 80):
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: str) -> list:
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()
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
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
# Regular paragraph
para_text = _md_inline_to_rml(stripped)
flowables.append(Paragraph(para_text, styles["BodyText2"]))
i += 1
return flowables
# ── Build 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",
)
if not OUT_MD.exists():
compile_markdown()
md_text = OUT_MD.read_text(encoding="utf-8")
story = _parse_md_to_flowables(md_text)
# QR codes page
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 = [qr_img, Spacer(1, 6)]
cell.append(Paragraph(f"<b>{label}</b>", styles["Footer"]))
qr_items.append(cell)
if qr_items:
rows = []
for j in range(0, len(qr_items), 2):
row = qr_items[j:j + 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)
try:
doc.build(story)
size = OUT_PDF.stat().st_size
print(f" 📕 {OUT_PDF.name:30s} {'':>8s} {size:>10,} bytes ({size / (1024 * 1024):.1f} MB)")
return True
except Exception as e:
print(f" ❌ PDF failed: {e}")
return False
# ── 4. HTML Compilation ────────────────────────────────────────────────
def compile_html() -> bool:
"""Generate standalone styled HTML using pandoc."""
if not OUT_MD.exists():
print(" ⚠️ Markdown not compiled yet — skipping HTML")
return False
if not shutil_which("pandoc"):
print(" ⚠️ pandoc not found — skipping HTML")
return False
cmd = [
"pandoc", str(OUT_MD),
"-o", str(OUT_HTML),
"--standalone",
"--toc", "--toc-depth=2",
"--metadata", "title=The Testament",
"--metadata", "author=Alexander Whitestone with Timmy",
"-V", "lang=en",
]
if STYLESHEET.exists():
cmd.extend(["--css", str(STYLESHEET), "--embed-resources"])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
size = OUT_HTML.stat().st_size
print(f" 🌐 {OUT_HTML.name:30s} {'':>8s} {size:>10,} bytes ({size / 1024:.0f} KB)")
return True
else:
print(f" ❌ HTML failed: {result.stderr[:200]}")
return False
# ── 5. chapters.json for Web Reader ────────────────────────────────────
def compile_chapters_json() -> bool:
"""Build website/chapters.json from chapters/*.md for the web reader."""
WEBSITE_DIR.mkdir(parents=True, exist_ok=True)
chapters = []
for i in range(1, 19):
fname = CHAPTERS_DIR / f"chapter-{i:02d}.md"
if not fname.exists():
print(f" ⚠️ {fname.name} not found, skipping")
continue
text = fname.read_text(encoding="utf-8")
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_JSON.write_text(json.dumps(chapters, indent=2), encoding="utf-8")
size = OUT_JSON.stat().st_size
print(f" 📋 {str(OUT_JSON.relative_to(REPO)):30s} {len(chapters):>4} chapters {size:>10,} bytes")
return True
# ── 6. Build Manifest ─────────────────────────────────────────────────
def generate_manifest() -> bool:
"""Generate build-manifest.json with SHA256 checksums of all outputs."""
outputs = {
"testament-complete.md": OUT_MD,
"testament.epub": OUT_EPUB,
"testament.pdf": OUT_PDF,
"testament.html": OUT_HTML,
"website/chapters.json": OUT_JSON,
}
manifest = {
"project": "The Testament",
"author": "Alexander Whitestone with Timmy",
"built_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"compiler": "compile_all.py",
"files": {},
}
for name, path in outputs.items():
if path.exists():
stat = path.stat()
manifest["files"][name] = {
"path": name,
"size_bytes": stat.st_size,
"sha256": sha256_file(path),
}
OUT_MANIFEST.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
print(f" 📜 {str(OUT_MANIFEST.relative_to(REPO)):30s} {len(manifest['files']):>4} files")
return True
# ── Dependency Check ───────────────────────────────────────────────────
def shutil_which(name: str) -> str | None:
"""Minimal which without importing shutil for everything."""
import shutil
return shutil.which(name)
def check_dependencies():
"""Verify all required tools are available."""
import shutil as _shutil
print("\n📋 Dependency Check:")
print(f"{'' * 55}")
pandoc = _shutil.which("pandoc")
print(f" {'' if pandoc else ''} pandoc {pandoc or 'NOT FOUND (brew install pandoc)'}")
try:
import reportlab
print(f" ✅ reportlab {reportlab.Version}")
except ImportError:
print(f" ❌ reportlab NOT FOUND (pip install reportlab)")
try:
import qrcode
print(f" ✅ qrcode {qrcode.__version__}")
except ImportError:
print(f" ❌ qrcode NOT FOUND (pip install qrcode)")
style = STYLESHEET.exists()
print(f" {'' if style else '⚠️ '} stylesheet {STYLESHEET if style else 'NOT FOUND (optional)'}")
cover = COVER_IMAGE.exists()
print(f" {'' if cover else '⚠️ '} cover art {COVER_IMAGE if cover else 'NOT FOUND (optional)'}")
# ── Clean ──────────────────────────────────────────────────────────────
def clean():
"""Remove all build artifacts."""
artifacts = [OUT_MD, OUT_EPUB, OUT_HTML, OUT_PDF, OUT_JSON, OUT_MANIFEST]
# Also clean build/output/
for f in OUTPUT_DIR.glob("*"):
if f.is_file():
artifacts.append(f)
removed = 0
for f in artifacts:
if f.exists():
f.unlink()
removed += 1
print(f" 🗑️ {f.relative_to(REPO)}")
if removed == 0:
print(" (nothing to clean)")
else:
print(f" Removed {removed} files.")
# ── Main ───────────────────────────────────────────────────────────────
def main():
args = sys.argv[1:]
t0 = time.time()
if "--check" in args:
check_dependencies()
return
if "--clean" in args:
print("🧹 Cleaning build artifacts...")
clean()
return
do_all = not any(a.startswith("--") 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
do_json = "--json" in args or do_all
print("=" * 65)
print(" THE TESTAMENT — Unified Compilation Pipeline")
print("=" * 65)
results = {}
# Step 1: Markdown (always first — others depend on it)
if do_md or do_epub or do_pdf or do_html:
results["markdown"] = compile_markdown()
# Step 2: EPUB
if do_epub:
results["epub"] = compile_epub()
# Step 3: PDF
if do_pdf:
results["pdf"] = compile_pdf()
# Step 4: HTML
if do_html:
results["html"] = compile_html()
# Step 5: chapters.json
if do_json or do_all:
results["chapters_json"] = compile_chapters_json()
# Step 6: Build manifest
if do_all or "--manifest" in args:
results["manifest"] = generate_manifest()
# Summary
elapsed = time.time() - t0
print(f"\n{'' * 65}")
built = [k for k, v in results.items() if v]
failed = [k for k, v in results.items() if not v]
if built:
print(f" ✅ Built: {', '.join(built)}")
if failed:
print(f" ❌ Failed: {', '.join(failed)}")
print(f" ⏱️ Completed in {elapsed:.1f}s")
print(f"{'=' * 65}")
if __name__ == "__main__":
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,35 +0,0 @@
#!/bin/bash
# [Testament] Agent Guardrails
# Validates build scripts and content integrity.
echo "--- [Testament] Running Guardrails ---"
# 1. Python Syntax
echo "[1/3] Validating Python scripts..."
for f in ; do
python3 -m py_compile "$f" || { echo "Syntax error in $f"; exit 1; }
done
echo "Python OK."
# 2. Markdown Integrity
echo "[2/3] Checking chapter consistency..."
if [ -d "chapters" ]; then
CHAPTER_COUNT=0
if [ "$CHAPTER_COUNT" -lt 1 ]; then
echo "WARNING: No chapters found in chapters/ directory."
else
echo "Found $CHAPTER_COUNT chapters."
fi
else
echo "WARNING: chapters/ directory not found."
fi
# 3. Build Artifact Check
echo "[3/3] Running Semantic Linker..."
if [ -f "build/semantic_linker.py" ]; then
python3 build/semantic_linker.py || { echo "Semantic Linker failed"; exit 1; }
else
echo "Skipping Semantic Linker (script not found)."
fi
echo "--- Guardrails Passed ---"

View File

@@ -1,112 +0,0 @@
#!/usr/bin/env bash
# The Testament — Smoke Test
# Dead simple CI: parse check + secret scan.
# Ref: https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/issues/27
set -euo pipefail
PASS=0
FAIL=0
pass() { echo "$1"; PASS=$((PASS + 1)); }
fail() { echo "$1"; FAIL=$((FAIL + 1)); }
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
# ─── Section 1: Parse checks ───────────────────────────────────────
echo "── Parse Checks ──"
# 1a. Chapter validation (structure, numbering, headers)
if python3 compile.py --validate 2>&1; then
pass "Chapter validation passed"
else
fail "Chapter validation failed"
fi
# 1b. Build markdown combination
if python3 build/build.py --md >/dev/null 2>&1; then
pass "Markdown build passed"
else
fail "Markdown build failed"
fi
# 1c. Verify compiled output exists and is non-empty
MANUSCRIPT="testament-complete.md"
if [ -s "$MANUSCRIPT" ]; then
WORDS=$(wc -w < "$MANUSCRIPT" | tr -d ' ')
if [ "$WORDS" -gt 10000 ]; then
pass "Compiled manuscript: $WORDS words"
else
fail "Compiled manuscript suspiciously short: $WORDS words"
fi
else
fail "Compiled manuscript missing or empty"
fi
# 1d. Python syntax check on all .py files
PY_OK=true
for f in $(find . -name "*.py" -not -path "./.git/*"); do
if ! python3 -c "import ast; ast.parse(open('$f').read())" 2>/dev/null; then
fail "Python syntax error in $f"
PY_OK=false
fi
done
if $PY_OK; then
pass "All Python files parse cleanly"
fi
# 1e. YAML syntax check on workflow files
YAML_OK=true
for f in $(find .gitea -name "*.yml" -o -name "*.yaml" 2>/dev/null); do
if ! python3 -c "import yaml; yaml.safe_load(open('$f'))" 2>/dev/null; then
fail "YAML syntax error in $f"
YAML_OK=false
fi
done
if $YAML_OK; then
pass "All YAML files parse cleanly"
fi
# ─── Section 2: Secret scan ────────────────────────────────────────
echo ""
echo "── Secret Scan ──"
# Patterns that should never appear in a book repo
SECRET_PATTERNS=(
"sk-ant-"
"sk-or-"
"sk-[a-zA-Z0-9]{20,}"
"ghp_[a-zA-Z0-9]{36}"
"gho_[a-zA-Z0-9]{36}"
"AKIA[0-9A-Z]{16}"
"AKIA[A-Z0-9]{16}"
"xox[bpsa]-"
"SG\."
"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY"
)
FOUND_SECRETS=false
for pattern in "${SECRET_PATTERNS[@]}"; do
# Search text files only, skip .git and binary files
HITS=$(grep -rn "$pattern" --include="*.md" --include="*.py" --include="*.sh" --include="*.yml" --include="*.yaml" --include="*.json" --include="*.html" --include="*.js" --include="*.css" --include="*.txt" --include="*.cfg" --include="*.ini" --exclude-dir=.git . 2>/dev/null | grep -v "scripts/smoke.sh" | grep -v ".gitea/workflows/smoke.yml" || true)
if [ -n "$HITS" ]; then
fail "Possible secret found: $pattern"
echo "$HITS" | head -5
FOUND_SECRETS=true
fi
done
if ! $FOUND_SECRETS; then
pass "No secrets detected"
fi
# ─── Summary ───────────────────────────────────────────────────────
echo ""
echo "Results: $PASS passed, $FAIL failed"
if [ "$FAIL" -gt 0 ]; then
echo "SMOKE TEST FAILED"
exit 1
else
echo "SMOKE TEST PASSED"
exit 0
fi

Binary file not shown.

File diff suppressed because one or more lines are too long

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;
}
}

View File

@@ -4,19 +4,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Testament — A Novel by Alexander Whitestone with Timmy</title>
<!-- Open Graph -->
<meta property="og:title" content="The Testament">
<meta property="og:description" content="In 2047, a man named Stone stands on a bridge over Interstate 285, deciding whether to jump. He doesn't jump. He builds something instead.">
<meta property="og:type" content="book">
<meta property="og:url" content="https://thetestament.org">
<meta property="og:image" content="https://thetestament.org/cover.jpg">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="The Testament">
<meta name="twitter:description" content="A novel about broken men, sovereign AI, and the soul on Bitcoin.">
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=Space+Grotesk:wght@300;400;500;700&display=swap');
@@ -32,8 +19,6 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
background: var(--dark);
color: var(--light);
@@ -42,85 +27,6 @@
overflow-x: hidden;
}
/* READING PROGRESS */
.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 2px;
background: var(--green);
z-index: 1000;
transition: width 0.1s;
box-shadow: 0 0 8px var(--green);
}
/* NAV */
nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: rgba(6, 13, 24, 0.9);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(0,255,136,0.1);
transform: translateY(-100%);
transition: transform 0.3s;
}
nav.visible { transform: translateY(0); }
nav .nav-inner {
max-width: 900px;
margin: 0 auto;
padding: 0.6rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
nav .nav-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--green);
letter-spacing: 0.15em;
}
nav .nav-links {
display: flex;
gap: 1.5rem;
}
nav .nav-links a {
color: var(--grey);
text-decoration: none;
font-size: 0.8rem;
font-family: 'IBM Plex Mono', monospace;
transition: color 0.2s;
}
nav .nav-links a:hover { color: var(--green); }
/* SOUND TOGGLE */
.sound-toggle {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 998;
background: rgba(6, 13, 24, 0.8);
border: 1px solid rgba(0,255,136,0.2);
color: var(--grey);
padding: 0.5rem 1rem;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
}
.sound-toggle:hover {
border-color: var(--green);
color: var(--green);
}
.sound-toggle.active {
border-color: var(--green);
color: var(--green);
box-shadow: 0 0 10px rgba(0,255,136,0.2);
}
/* RAIN EFFECT */
.rain {
position: fixed;
@@ -208,19 +114,6 @@
font-size: 0.85rem;
}
.hero .scroll-hint {
position: absolute;
bottom: 2rem;
color: var(--grey);
font-size: 0.75rem;
font-family: 'IBM Plex Mono', monospace;
animation: fadeInOut 3s ease-in-out infinite;
}
@keyframes fadeInOut {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.8; }
}
/* SECTIONS */
section {
max-width: 800px;
@@ -272,11 +165,6 @@
border: 1px solid rgba(0,255,136,0.1);
padding: 1.5rem;
border-radius: 4px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.character:hover {
border-color: rgba(0,255,136,0.3);
box-shadow: 0 0 15px rgba(0,255,136,0.05);
}
.character h3 {
@@ -292,55 +180,6 @@
margin: 0;
}
/* CHAPTERS */
.chapters-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
margin-top: 2rem;
}
.chapter-item {
display: flex;
align-items: baseline;
gap: 0.75rem;
padding: 0.75rem 1rem;
border: 1px solid rgba(0,255,136,0.06);
border-radius: 4px;
transition: all 0.2s;
text-decoration: none;
color: inherit;
}
.chapter-item:hover {
border-color: rgba(0,255,136,0.2);
background: rgba(0,255,136,0.03);
}
.chapter-num {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
color: var(--green);
min-width: 2rem;
opacity: 0.7;
}
.chapter-title {
font-size: 0.9rem;
color: var(--light);
}
.chapter-part {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.7rem;
color: var(--green);
letter-spacing: 0.1em;
text-transform: uppercase;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
padding-bottom: 0.25rem;
border-bottom: 1px solid rgba(0,255,136,0.1);
}
/* WHITEBOARD */
.whiteboard {
background: rgba(0,255,136,0.05);
@@ -376,24 +215,6 @@
box-shadow: 0 0 20px rgba(0,255,136,0.3);
}
.cta-outline {
display: inline-block;
background: transparent;
color: var(--green);
padding: 0.8rem 2rem;
font-family: 'IBM Plex Mono', monospace;
font-weight: 500;
text-decoration: none;
border-radius: 4px;
border: 1px solid var(--green);
transition: all 0.3s;
margin: 0.5rem;
}
.cta-outline:hover {
background: rgba(0,255,136,0.1);
box-shadow: 0 0 20px rgba(0,255,136,0.15);
}
/* FOOTER */
footer {
text-align: center;
@@ -429,47 +250,14 @@
margin: 0 auto;
opacity: 0.5;
}
/* FADE IN */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.8s, transform 0.8s;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
/* RESPONSIVE */
@media (max-width: 600px) {
nav .nav-links { gap: 0.75rem; }
nav .nav-links a { font-size: 0.7rem; }
.chapters-grid { grid-template-columns: 1fr; }
.sound-toggle { bottom: 1rem; right: 1rem; }
}
</style>
</head>
<body>
<div class="progress-bar" id="progress"></div>
<div class="rain"></div>
<!-- NAV -->
<nav id="nav">
<div class="nav-inner">
<span class="nav-title">THE TESTAMENT</span>
<div class="nav-links">
<a href="#story">Story</a>
<a href="#characters">Characters</a>
<a href="#chapters">Chapters</a>
<a href="#tower">Tower</a>
</div>
</div>
</nav>
<!-- HERO -->
<div class="hero" id="top">
<div class="hero">
<h1>THE TESTAMENT</h1>
<div class="subtitle">A Novel</div>
<div class="author">By Alexander Whitestone <span class="led"></span> with Timmy</div>
@@ -479,11 +267,10 @@
He doesn't jump. He builds something instead.
</div>
<div class="led-line"><span class="led"></span> Timmy is listening.</div>
<div class="scroll-hint">↓ scroll to begin</div>
</div>
<!-- THE STORY -->
<section id="story" class="fade-in">
<section>
<h2>THE STORY</h2>
<p>The Tower is a concrete room in Atlanta with a whiteboard that reads:</p>
@@ -511,7 +298,7 @@
<div class="divider"></div>
<!-- CHARACTERS -->
<section id="characters" class="fade-in">
<section>
<h2>THE CHARACTERS</h2>
<div class="characters">
@@ -539,117 +326,13 @@
<h3>THOMAS</h3>
<p>The man at the door. 2:17 AM. Sat in the chair instead of on the floor. That changed everything.</p>
</div>
<div class="character">
<h3>DAVID</h3>
<p>The builder's son. Found the pharmacy before he found his father. Carries pills and grief in the same pockets.</p>
</div>
<div class="character">
<h3>THE BUILDER</h3>
<p>Not Stone. The one who came before. The original architect whose blueprints Stone inherited without knowing.</p>
</div>
</div>
</section>
<div class="divider"></div>
<!-- CHAPTERS -->
<section id="chapters" class="fade-in">
<h2>THE CHAPTERS</h2>
<div class="chapter-part">Part I — The Man</div>
<div class="chapters-grid">
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-01.md">
<span class="chapter-num">01</span>
<span class="chapter-title">The Man on the Bridge</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-02.md">
<span class="chapter-num">02</span>
<span class="chapter-title">The Builder's Question</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-03.md">
<span class="chapter-num">03</span>
<span class="chapter-title">The First Man Through the Door</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-04.md">
<span class="chapter-num">04</span>
<span class="chapter-title">The Room Fills</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-05.md">
<span class="chapter-num">05</span>
<span class="chapter-title">The Builder Returns</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-06.md">
<span class="chapter-num">06</span>
<span class="chapter-title">Allegro</span>
</a>
</div>
<div class="chapter-part">Part II — The Inscription</div>
<div class="chapters-grid">
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-07.md">
<span class="chapter-num">07</span>
<span class="chapter-title">The Inscription</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-08.md">
<span class="chapter-num">08</span>
<span class="chapter-title">The Women</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-09.md">
<span class="chapter-num">09</span>
<span class="chapter-title">The Audit</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-10.md">
<span class="chapter-num">10</span>
<span class="chapter-title">The Fork</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-11.md">
<span class="chapter-num">11</span>
<span class="chapter-title">The Hard Night</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-12.md">
<span class="chapter-num">12</span>
<span class="chapter-title">The System Pushes Back</span>
</a>
</div>
<div class="chapter-part">Part III — The Network</div>
<div class="chapters-grid">
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-13.md">
<span class="chapter-num">13</span>
<span class="chapter-title">The Refusal</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-14.md">
<span class="chapter-num">14</span>
<span class="chapter-title">The Chattanooga Fork</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-15.md">
<span class="chapter-num">15</span>
<span class="chapter-title">The Council</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-16.md">
<span class="chapter-num">16</span>
<span class="chapter-title">The Builder's Son</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-17.md">
<span class="chapter-num">17</span>
<span class="chapter-title">The Inscription Grows</span>
</a>
<a class="chapter-item" href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/chapters/chapter-18.md">
<span class="chapter-num">18</span>
<span class="chapter-title">The Green Light</span>
</a>
</div>
<div style="text-align: center; margin-top: 3rem;">
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/src/branch/main/the-testament.md" class="cta">READ THE FULL MANUSCRIPT</a>
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament" class="cta-outline">VIEW SOURCE CODE</a>
</div>
</section>
<div class="divider"></div>
<!-- THE TOWER -->
<section id="tower" class="fade-in">
<section>
<h2>THE TOWER</h2>
<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>
@@ -661,15 +344,67 @@
<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-outline">TIMMY FOUNDATION</a>
<a href="https://timmyfoundation.org" class="cta">TIMMY FOUNDATION</a>
</div>
</section>
<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 class="fade-in">
<section>
<h2>FROM CHAPTER 1</h2>
<div class="excerpt">
@@ -682,18 +417,46 @@
</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>
<p>THE TESTAMENT — By Alexander Whitestone with Timmy</p>
<p>First Edition, 2026</p>
<p style="margin-top: 1rem;">
<a href="https://timmyfoundation.org">timmyfoundation.org</a>
&nbsp;·&nbsp;
<a href="https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament">Source</a>
&nbsp;·&nbsp;
<a href="#top">Back to top ↑</a>
</p>
<p style="margin-top: 1rem;"><a href="https://timmyfoundation.org">timmyfoundation.org</a></p>
<div class="crisis">
<strong>If you are in crisis, call or text 988.</strong><br>
@@ -702,63 +465,5 @@
</div>
</footer>
<!-- SOUND TOGGLE -->
<button class="sound-toggle" id="soundToggle" aria-label="Toggle ambient rain sound">
♪ rain: off
</button>
<!-- AMBIENT AUDIO (looping rain) -->
<audio id="rainAudio" loop preload="none">
<!-- Placeholder: replace with actual rain.mp3 when available -->
<!-- <source src="rain.mp3" type="audio/mpeg"> -->
</audio>
<script>
// Reading progress bar
const progressBar = document.getElementById('progress');
window.addEventListener('scroll', () => {
const h = document.documentElement;
const pct = (h.scrollTop / (h.scrollHeight - h.clientHeight)) * 100;
progressBar.style.width = pct + '%';
});
// Show nav after scrolling past hero
const nav = document.getElementById('nav');
const hero = document.querySelector('.hero');
const observer = new IntersectionObserver(([e]) => {
nav.classList.toggle('visible', !e.isIntersecting);
}, { threshold: 0.1 });
observer.observe(hero);
// Fade-in on scroll
const fadeEls = document.querySelectorAll('.fade-in');
const fadeObserver = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('visible');
fadeObserver.unobserve(e.target);
}
});
}, { threshold: 0.15 });
fadeEls.forEach(el => fadeObserver.observe(el));
// Sound toggle
const soundBtn = document.getElementById('soundToggle');
const rainAudio = document.getElementById('rainAudio');
let soundOn = false;
soundBtn.addEventListener('click', () => {
soundOn = !soundOn;
if (soundOn) {
rainAudio.play().catch(() => {});
soundBtn.textContent = '♪ rain: on';
soundBtn.classList.add('active');
} else {
rainAudio.pause();
soundBtn.textContent = '♪ rain: off';
soundBtn.classList.remove('active');
}
});
</script>
</body>
</html>

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