Compare commits

...

3 Commits

Author SHA1 Message Date
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
8 changed files with 1595 additions and 540 deletions

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" ✅
---

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)

File diff suppressed because it is too large Load Diff

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>
@@ -365,6 +366,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>