BUCKYVERSE

Buckyverse: Elixir Static Site Generator with LaTeX → HTML + MathML

Project Overview

Build a static site generator in Elixir that converts LaTeX book sources into HTML with native MathML for math rendering. Designed for hosting digitized out-of-print books, specifically R. Buckminster Fuller's works.

Source Material: Books are managed using Codexis (a book digitization tool) which produces LaTeX manuscripts. The static site generator will consume these LaTeX sources and generate a web-friendly reading experience.


Technical Stack


Project Structure

buckyverse/
├── lib/
│   ├── buckyverse/
│   │   ├── application.ex
│   │   ├── library.ex              # Content loading
│   │   ├── library/
│   │   │   ├── book.ex             # Book struct
│   │   │   ├── chapter.ex          # Chapter struct
│   │   │   └── page.ex             # Page struct
│   │   ├── latex/
│   │   │   ├── converter.ex        # LaTeX → HTML conversion (Pandoc/tex4ht)
│   │   │   ├── parser.ex           # Parse LaTeX structure (\chapter, \section)
│   │   │   └── post_processor.ex   # Clean up HTML output
│   │   ├── templates/
│   │   │   ├── layout.ex           # HTML layout
│   │   │   ├── page.ex             # Page template
│   │   │   ├── home.ex             # Home page template
│   │   │   └── components.ex       # Reusable components
│   │   ├── build.ex                # Static site generator
│   │   └── watch.ex                # Dev server with live reload
│   └── buckyverse.ex
├── priv/
│   ├── books/                      # Content directory (Codexis projects)
│   │   └── grunch-of-giants/       # Example Codexis book project
│   │       ├── meta.yaml           # Codexis book metadata
│   │       ├── config.yaml         # Codexis build config
│   │       ├── manuscript.tex      # Main LaTeX file
│   │       ├── content/
│   │       │   ├── front-matter/
│   │       │   │   ├── foreword.tex
│   │       │   │   └── ...
│   │       │   └── chapters/
│   │       │       ├── 01-fee-x-fie-x-fo-x-fum.tex
│   │       │       ├── 02-astro-age-davids-sling.tex
│   │       │       └── ...
│   │       ├── assets/             # Images, fonts, etc.
│   │       └── styles/             # LaTeX style files
│   └── static/
│       ├── css/
│       │   └── style.css
│       └── js/
│           └── app.js
├── _site/                          # Build output
├── mix.exs
└── README.md

Phase 0: Configuration Decisions

0.1 Library Choices

LaTeX to HTML conversion:

Recommendation: Use Pandoc for LaTeX conversion

HTTP server:

Template system:

0.2 Configuration File (config/config.exs)

import Config

config :buckyverse,
  books_path: "priv/books",        # Directory containing Codexis projects
  static_path: "priv/static",
  output_path: "_site",
  dev_server_port: 4000,
  pandoc_path: "pandoc",           # Pandoc executable
  enable_tex4ht_fallback: false,
  tex4ht_path: "/usr/local/bin/make4ht"

import_config "#{config_env()}.exs"

0.3 Environment Configs

config/dev.exs:

import Config

config :buckyverse,
  enable_live_reload: true,
  minify_html: false

config/prod.exs:

import Config

config :buckyverse,
  enable_live_reload: false,
  minify_html: true

config/test.exs:

import Config

config :buckyverse,
  books_path: "test/fixtures/books",
  output_path: "test/output"

0.4 Git Configuration (.gitignore)

# Build artifacts
/_build/
/deps/
/_site/

# Elixir LS
/.elixir_ls/

# Temp files
*.tmp
/tmp/

# tex4ht artifacts
*.4ct
*.4tc
*.dvi
*.idv
*.lg
*.tmp
*.xref

# OS files
.DS_Store

Phase 1: Project Setup

1.1 Create new Elixir project

mix new buckyverse --sup
cd buckyverse

1.2 Configure mix.exs

Dependencies:

System Requirements:

Note: Books should be Codexis projects with meta.yaml, manuscript.tex, and content/ directory.

Mix aliases:

defp aliases do
  [
    "bucky.build": ["run -e 'Buckyverse.Build.run()'"],
    "bucky.watch": ["run --no-halt -e 'Buckyverse.Watch.start()'"],
    test: ["test"]
  ]
end

1.3 Create directory structure

Create all directories listed in project structure above.

mkdir -p lib/buckyverse/{library,latex,templates}
mkdir -p priv/{books,static/{css,js}}
mkdir -p test/{fixtures/books,support}
mkdir -p config

1.4 Create configuration files

Create config/config.exs, config/dev.exs, config/prod.exs, config/test.exs as specified in Phase 0.


Phase 2: Data Structures

2.1 Book struct (lib/buckyverse/library/book.ex)

Fields:

2.2 Chapter struct (lib/buckyverse/library/chapter.ex)

Fields:

Note: In LaTeX-based system, each chapter is a single page. No separate Page struct needed unless chapters are very long and need splitting.


Phase 3: LaTeX Conversion

3.1 LaTeX Structure Parser (lib/buckyverse/latex/parser.ex)

Parse LaTeX document structure to extract chapters and metadata.

Parse from meta.yaml (Codexis metadata):

Parse from manuscript.tex:

Parse from chapter files:

Public API:

@spec parse_codexis_metadata(path :: String.t()) :: {:ok, map()} | {:error, term()}
def parse_codexis_metadata(path)  # Parse meta.yaml

@spec parse_manuscript(path :: String.t()) :: {:ok, %{chapters: list()}} | {:error, term()}
def parse_manuscript(path)  # Extract chapter includes

@spec extract_chapter_info(path :: String.t()) :: {:ok, %{title: String.t(), label: String.t()}} | {:error, term()}
def extract_chapter_info(path)

Implementation:

3.2 LaTeX to HTML Converter (lib/buckyverse/latex/converter.ex)

Convert individual LaTeX chapter files to HTML with MathML.

Using Pandoc (recommended):

@spec convert_file(input_path :: String.t(), opts :: keyword()) :: {:ok, String.t()} | {:error, term()}
def convert_file(input_path, opts \\ []) do
  args = [
    "--from=latex",
    "--to=html5",
    "--mathml",
    "--standalone",
    "--no-highlight",  # We'll handle code highlighting
    input_path
  ]
  
  case Rambo.run("pandoc", args) do
    {:ok, %{status: 0, out: html}} -> {:ok, html}
    {:ok, %{status: status, err: err}} -> {:error, "Pandoc failed: #{err}"}
    {:error, reason} -> {:error, reason}
  end
end

Options:

Alternative: tex4ht/make4ht:

def convert_with_tex4ht(input_path, opts \\ []) do
  # Create temp directory
  {:ok, tmp_dir} = Briefly.create(directory: true)
  
  # Copy input file to temp
  basename = Path.basename(input_path)
  tmp_input = Path.join(tmp_dir, basename)
  File.cp!(input_path, tmp_input)
  
  # Run make4ht
  args = ["-f", "html5+mathml", "-d", tmp_dir, basename]
  
  case Rambo.run("make4ht", args, cd: tmp_dir) do
    {:ok, %{status: 0}} ->
      html_file = Path.join(tmp_dir, Path.basename(basename, ".tex") <> ".html")
      {:ok, File.read!(html_file)}
    error -> error
  end
end

3.3 HTML Post-Processor (lib/buckyverse/latex/post_processor.ex)

Clean up and enhance Pandoc/tex4ht HTML output.

Processing steps:

  1. Extract body content (remove Pandoc's <head>, keep only <body> contents)
  2. Remove or adjust header levels (h1 → h2, etc.)
  3. Add IDs to headings for TOC
  4. Fix image paths (relative to book directory)
  5. Extract table of contents
  6. Clean up unnecessary classes/attributes
  7. Wrap figures with captions
  8. Add section numbers if present in LaTeX

Public API:

@spec process(html :: String.t(), opts :: keyword()) :: {String.t(), list()}
def process(html, opts \\ [])

Returns: {processed_html, toc_items}

Use Floki for HTML manipulation:

def process(html, opts) do
  {:ok, document} = Floki.parse_document(html)
  
  document
  |> extract_body()
  |> adjust_headers()
  |> add_heading_ids()
  |> fix_image_paths(opts[:book_slug])
  |> extract_toc()
end

Phase 4: Content Processing (Removed)

Note: This phase is replaced by Phase 3 (LaTeX Conversion). Since source content is in LaTeX, we don't need a separate Markdown renderer. All content processing happens through:

  1. LaTeX structure parsing (Phase 3.1)
  2. LaTeX to HTML conversion via Pandoc (Phase 3.2)
  3. HTML post-processing (Phase 3.3)

Phase 5: Content Loading

5.1 Library module (lib/buckyverse/library.ex)

Public API:

Loading logic:

  1. Scan priv/books/ for directories (each is a Codexis project)
  2. For each book directory:
    • Look for manuscript.tex (main LaTeX file)
    • Load meta.yaml (Codexis metadata) for book info
    • Parse manuscript.tex to extract chapter list and structure
    • Extract book slug from directory name
  3. For each chapter referenced in manuscript.tex:
    • Parse chapter .tex file to extract title and label
    • Convert chapter LaTeX to HTML using Pandoc
    • Post-process HTML (extract TOC, fix paths, etc.)
    • Create Chapter struct with HTML content
  4. Build nested struct hierarchy
  5. Copy figures/ directory reference (don't load into memory)

Codexis metadata parsing (meta.yaml):

# From meta.yaml, extract:
- title[0].text → book title
- creator (where role=author) → author name
- description → book description
- copyright_year → publication year
- publisher → publisher name

LaTeX manuscript parsing:

# From manuscript.tex, extract:
- \include{content/chapters/01-fee-x-fie-x-fo-x-fum} → chapter file path
- Order of \include statements → chapter order

Chapter file parsing:

# From chapter .tex file, extract:
- \chapter{...} → chapter title
- \label{ch:...} → chapter identifier/slug

Slug extraction:

Metadata precedence:

Navigation tree structure:

%{
  chapters: [
    %{
      slug: "fee-x-fie-x-fo-x-fum",
      title: "(Fee X fie X fo X fum)⁴",
      position: 1,
      toc: [
        %{level: 2, id: "grunch-definition", text: "GRUNCH Definition"},
        %{level: 2, id: "invisible-giants", text: "Invisible Giants"}
      ]
    },
    %{
      slug: "astro-age-davids-sling",
      title: "Astro-Age David's Sling",
      position: 2,
      toc: [...]
    }
  ]
}

Prev/Next calculation:

  1. Chapters are already ordered from manuscript.tex
  2. Find current chapter index
  3. Return previous and next chapters
  4. Handle frontmatter/backmatter sections appropriately

Error handling:

Caching:


Phase 6: HTML Templates

All templates use EEx (Embedded Elixir) with pure functions returning strings.

6.1 Layout template (lib/buckyverse/templates/layout.ex)

Full HTML document with:

Function signature:

@spec render(content :: String.t(), assigns :: map()) :: String.t()
def render(content, assigns \\ %{})

Assigns:

6.2 Chapter template (lib/buckyverse/templates/chapter.ex)

Three-column layout:

Function signature:

@spec render(assigns :: map()) :: String.t()
def render(assigns)

Required assigns:

6.3 Book index template (lib/buckyverse/templates/book_index.ex)

Function signature:

@spec render(assigns :: map()) :: String.t()
def render(assigns)

Required assigns:

6.4 Home template (lib/buckyverse/templates/home.ex)

Function signature:

@spec render(assigns :: map()) :: String.t()
def render(assigns)

Required assigns:

6.5 Components (lib/buckyverse/templates/components.ex)

sidebar(book, nav_tree, current_chapter):

chapter_nav(book, prev_chapter, next_chapter):

toc(items):

book_card(book):

6.6 URL Structure

All URLs use trailing slashes and rely on index.html:

Link generation:


Phase 7: Static Site Builder

7.1 Build module (lib/buckyverse/build.ex)

run(opts \\ []) function:

  1. Load configuration from Application env
  2. Clean output directory (delete _site/ if exists)
  3. Create _site/ directory
  4. Build all books (pages as nested directories)
  5. Build home page
  6. Copy static assets recursively
  7. Build search index JSON
  8. Print build summary (pages built, time elapsed, errors/warnings)

For each book:

  1. Create _site/{book_slug}/ directory
  2. Write _site/{book_slug}/index.html (book index page)
  3. For each chapter:
    • Create _site/{book_slug}/{chapter_slug}/ directory
    • Calculate prev/next chapters using Library.get_prev_next/2
    • Get navigation tree using Library.get_nav_tree/1
    • Render chapter template with all assigns
    • Wrap in layout template
    • Write _site/{book_slug}/{chapter_slug}/index.html
  4. Copy figures/ directory if exists:
    • Source: priv/books/{book_slug}/figures/
    • Destination: _site/{book_slug}/figures/
    • Preserve directory structure
    • Copy all file types (SVG, PNG, JPG, etc.)

Static assets copying:

Search index:

Error handling:

Build stats:

%{
  books: 1,
  chapters: 8,
  figures: 15,
  warnings: 2,
  elapsed_ms: 1234
}

Options:


Phase 8: Development Server

8.1 Watch module (lib/buckyverse/watch.ex)

start(opts \\ []) function:

  1. Run initial full build
  2. Start FileSystem watcher on priv/books/ and priv/static/
  3. Start HTTP server on port 4000 (from config) serving _site/
  4. On file change events, trigger appropriate rebuild
  5. Log rebuild activity with timestamps
  6. Keep server running until interrupted

File watching strategy:

Watch paths:

Rebuild granularity:

HTTP server implementation:

Use Plug + Cowboy:

defmodule Buckyverse.DevServer do
  use Plug.Router
  
  plug Plug.Static,
    at: "/",
    from: "_site",
    only: ~w(css js images fonts),
    gzip: false
    
  plug :match
  plug :dispatch
  
  # Serve index.html for directory requests
  get "/*path" do
    # Handle trailing slash, serve index.html
  end
  
  match _ do
    send_resp(conn, 404, "Not found")
  end
end

Server features:

Options:

Console output:

Building site...
✓ Built 1 book, 8 chapters in 2.4s

Watching for changes...
Server running at http://localhost:4000

[12:34:56] Changed: priv/books/grunch-of-giants/content/chapters/01-fee-x-fie-x-fo-x-fum.tex
[12:34:56] Rebuilding chapter: fee-x-fie-x-fo-x-fum
[12:34:56] ✓ Rebuilt in 0.5s

Phase 9: Styling

9.1 CSS (priv/static/css/style.css)

Layout:

Sidebar styling:

Content styling:

Math styling:

Navigation:

Responsive:


Phase 10: Sample Content (Already Exists)

Note: The existing book "Grunch of Giants" in priv/books/grunch-of-giants/ serves as sample content. This is a Codexis book project with:

10.1 Codexis Metadata Structure (meta.yaml)

The Codexis meta.yaml file provides rich metadata:

title:
  - type: main
    text: Grunch of Giants
creator:
  - role: author
    text: R. Buckminster Fuller
description: Book description...
copyright_year: 1982
publisher: Design Science Press
identifier:
  - scheme: ISBN-13
    text: 978-1607027591

The static site generator should parse this metadata for book information.

10.2 Example LaTeX Chapter Structure

The existing chapters demonstrate proper LaTeX structure with:

10.3 Testing with Existing Content

The existing Codexis project provides:


Phase 11: Testing

11.1 Test Structure

test/
├── buckyverse/
│   ├── latex/
│   │   ├── parser_test.exs
│   │   └── mathml_test.exs
│   ├── markdown/
│   │   └── renderer_test.exs
│   ├── library_test.exs
│   └── build_test.exs
├── fixtures/
│   └── books/
│       └── test-book/
│           ├── _book.yaml
│           └── 01-chapter/
│               ├── _chapter.yaml
│               └── 01-page.md
├── support/
│   └── test_helpers.ex
└── test_helper.exs

11.2 Test Coverage

LaTeX Parser tests:

LaTeX Converter tests:

HTML Post-Processor tests:

Library tests:

Build tests:

11.3 Test Helpers (test/support/test_helpers.ex)

defmodule Buckyverse.TestHelpers do
  def fixture_path(path), do: Path.join([__DIR__, "..", "fixtures", path])
  
  def create_test_book(attrs \\ %{}) do
    # Helper to create test book structs
  end
  
  def assert_mathml(mathml, expected_tags) do
    # Helper to assert MathML structure
  end
end

11.4 Running Tests

# Run all tests
mix test

# Run specific test file
mix test test/buckyverse/latex/parser_test.exs

# Run with coverage
mix test --cover

# Run in watch mode (requires mix_test_watch)
mix test.watch

Build & Run Commands

# Install dependencies
mix deps.get

# Build static site
mix bucky.build

# Development server with watch
mix bucky.watch

# Preview built site
cd _site && python3 -m http.server 8000

# Test math parser in IEx
iex -S mix

IEx testing:

alias Buckyverse.Library
alias Buckyverse.LaTeX.{Parser, Converter}

# Load books
books = Library.list_books()

# Parse manuscript
{:ok, metadata} = Parser.parse_manuscript("priv/books/grunch-of-giants/manuscript.tex")

# Convert chapter
{:ok, html} = Converter.convert_file("priv/books/grunch-of-giants/content/chapters/01-fee-x-fie-x-fo-x-fum.tex")

Deployment

Production Build

# Set production environment
MIX_ENV=prod mix deps.get
MIX_ENV=prod mix bucky.build

Static output in _site/ can be deployed to:

Netlify

# Install Netlify CLI
npm install -g netlify-cli

# Deploy
netlify deploy --prod --dir=_site

netlify.toml:

[build]
  command = "mix deps.get && mix bucky.build"
  publish = "_site"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 404

Cloudflare Pages

  1. Connect repository
  2. Build settings:
    • Build command: mix deps.get && mix bucky.build
    • Build output directory: _site
    • Root directory: /
  3. Environment variables: MIX_ENV=prod

GitHub Pages

# Build locally
mix bucky.build

# Deploy to gh-pages branch
git subtree push --prefix _site origin gh-pages

# Or use GitHub Actions (see below)

GitHub Actions workflow (.github/workflows/deploy.yml):

name: Deploy to GitHub Pages

on:
  push:
    branches: [main]

jobs:
  build-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: erlef/setup-beam@v1
        with:
          otp-version: '26.0'
          elixir-version: '1.15.0'
      
      - run: mix deps.get
      - run: mix bucky.build
      
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./_site

AWS S3 + CloudFront

# Install AWS CLI
# Configure credentials: aws configure

# Sync to S3 bucket
aws s3 sync _site/ s3://your-bucket-name/ --delete

# Invalidate CloudFront cache
aws cloudfront create-invalidation \
  --distribution-id YOUR_DIST_ID \
  --paths "/*"

MathML Browser Support

Current support (2026):

Fallback for older browsers:
Add MathML polyfill in layout template (optional):

<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js"></script>

Or detect and show warning:

if (!document.createElement('math').namespaceURI) {
  console.warn('MathML not supported');
}

Future Enhancements

  1. Full-text search - Client-side with Lunr.js or server-side with Haystack
  2. PDF generation - Use tex4ht or Tectonic for downloadable PDFs
  3. Phoenix LiveView - Dynamic version with instant navigation
  4. Ash Framework - Structured domain layer for content
  5. Cross-references - Parse \ref{} and \label{} for internal linking
  6. Bibliography - BibTeX support with citations
  7. Figures - Enhanced figure handling with captions and numbering
  8. Comments/Annotations - User annotations with persistence

Key Implementation Notes

LaTeX Structure Parsing Tips

Pandoc Integration Tips

MathML Tips (from Pandoc output)

Content Loading Tips

Performance Optimization

Error Recovery


Appendix A: Configuration Examples

Complete config/config.exs

import Config

config :buckyverse,
  books_path: "priv/books",        # Codexis projects directory
  static_path: "priv/static",
  output_path: "_site",
  dev_server_port: 4000,
  pandoc_path: "pandoc",
  enable_tex4ht_fallback: false,
  tex4ht_path: "/usr/local/bin/make4ht",
  site_title: "Buckyverse",
  site_description: "R. Buckminster Fuller's works, digitized",
  base_url: "https://buckyverse.com"

config :logger,
  level: :info

import_config "#{config_env()}.exs"

Complete mix.exs

defmodule Buckyverse.MixProject do
  use Mix.Project

  def project do
    [
      app: :buckyverse,
      version: "0.1.0",
      elixir: "~> 1.15",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      aliases: aliases()
    ]
  end

  def application do
    [
      extra_applications: [:logger],
      mod: {Buckyverse.Application, []}
    ]
  end

  defp deps do
    [
      {:yaml_elixir, "~> 2.9"},  # Required for Codexis meta.yaml
      {:floki, "~> 0.36"},
      {:slugify, "~> 1.3"},
      {:plug_cowboy, "~> 2.7"},
      {:plug, "~> 1.15"},
      {:rambo, "~> 0.3"},
      {:briefly, "~> 0.5"},
      {:file_system, "~> 1.0"},
      {:jason, "~> 1.4"}
    ]
  end

  defp aliases do
    [
      "bucky.build": ["run -e 'Buckyverse.Build.run()'"],
      "bucky.watch": ["run --no-halt -e 'Buckyverse.Watch.start()'"]
    ]
  end
end

Appendix B: File Organization Patterns

Content Organization (Codexis Projects)

priv/books/
├── grunch-of-giants/                 # Codexis project
│   ├── meta.yaml                     # Codexis metadata (required)
│   ├── config.yaml                   # Codexis build config
│   ├── manuscript.tex                # Main LaTeX file
│   ├── content/
│   │   ├── front-matter/
│   │   │   ├── foreword.tex
│   │   │   ├── dedication.tex
│   │   │   └── ...
│   │   └── chapters/
│   │       ├── 01-fee-x-fie-x-fo-x-fum.tex
│   │       ├── 02-astro-age-davids-sling.tex
│   │       └── ...
│   ├── assets/                       # Images, fonts, etc.
│   │   └── images/
│   │       └── cover.jpg
│   └── styles/                       # LaTeX style files
│       └── base.sty
└── synergetics/                      # Another Codexis project
    ├── meta.yaml
    ├── manuscript.tex
    └── ...

Output Organization

_site/
├── index.html                        # Home page
├── css/
│   └── style.css
├── js/
│   └── app.js
├── search-index.json
├── synergetics/
│   ├── index.html                    # Book index
│   ├── preface/
│   │   ├── index/
│   │   │   └── index.html
│   │   └── preface/
│   │       └── index.html
│   ├── universe/
│   │   ├── index/
│   │   │   └── index.html
│   │   ├── definition/
│   │   │   └── index.html
│   │   └── conceptuality/
│   │       └── index.html
│   └── figures/
│       ├── fig-100.01.svg
│       └── fig-100.02.svg
└── operating-manual/
    └── ...

Appendix C: Common Issues and Solutions

Issue: Pandoc conversion fails for specific LaTeX

Solution:

  1. Check Pandoc version (need 2.19+)
  2. Try tex4ht/make4ht as alternative converter
  3. Simplify problematic LaTeX if possible
  4. Check Pandoc error messages for specific issues
  5. Add preprocessing step to fix common issues

Issue: Build is slow for large books

Solution:

  1. Enable parallel chapter conversion with Task.async_stream/3
  2. Cache converted HTML with ETS
  3. Only rebuild changed chapters in watch mode
  4. Profile with :timer.tc/1 to find bottlenecks
  5. Pandoc is relatively fast, but can be parallelized per-chapter

Issue: Images not displaying

Solution:

  1. Check image paths are relative to chapter directory
  2. Verify figures directory is copied to output
  3. Check URL generation includes book slug
  4. Validate image files exist before build

Issue: Math not rendering in browser

Solution:

  1. Check MathML namespace is included
  2. Verify browser supports MathML (Chrome 109+, Firefox, Safari 14.1+)
  3. Add MathJax polyfill for older browsers
  4. Check for malformed MathML with browser inspector

Solution:

  1. Verify all URLs use absolute paths starting with /
  2. Check trailing slashes are consistent
  3. Validate prev/next calculation logic
  4. Ensure index.html files are created in correct directories

Estimated Timeline

Phase Description Effort
0 Configuration decisions 30 min
1 Project setup 1 hour
2 Data structures 30 min
3 LaTeX conversion (Pandoc integration) 3-4 hours
4 Content processing (removed - integrated into Phase 3)
5 Content loading from LaTeX 2-3 hours
6 HTML templates 2-3 hours
7 Static builder 1-2 hours
8 Dev server 1-2 hours
9 CSS styling 2-3 hours
10 Sample content (already exists)
11 Testing 2-3 hours

Total: ~16-24 hours

Note: Using Pandoc for LaTeX conversion significantly reduces complexity compared to building a custom LaTeX parser. The existing "Grunch of Giants" book provides real content for testing.


Getting Started Checklist


This plan is comprehensive and self-contained. Start with Phase 0 to make key decisions, then work through each phase sequentially. Each phase builds on the previous ones.