# Axler Visual Framework · Specification

**Version:** 1.0
**Date:** 2026-04-23
**Status:** Stable

This document is the authoritative contract between the framework (`shared/`)
and any page that uses it. Any deviation must update this spec first.

---

## 1. Purpose & Scope

### 1.1 Purpose

Provide a shared substrate for producing self-contained HTML pages that teach
one section of Sheldon Axler's *Linear Algebra Done Right* 4e. Each page
contains a symbol glossary, Q&A expository content, 2D/3D interactive
visualizations, and 8 self-test exercises.

### 1.2 Non-goals

- **Not a general-purpose visualization toolkit.** Scoped to
  linear-algebra-on-vectors-over-$\mathbf{R}$-or-$\mathbf{C}$ with dim $\le 3$.
  SVG/Canvas2D and Three.js are the only rendering backends.
- **Not a CMS / SPA.** No build step, no bundler, no framework (no React/Vue).
  Pure static HTML + vanilla ES2019 + CDN dependencies.
- **Not locale-aware.** Prose is Chinese; code/UI labels are mixed ASCII.
  Framework does not translate content.

### 1.3 Users

Two kinds of consumers:

1. **Page authors** (human or AI): produce one `.html` per Axler section.
2. **Readers**: open the `.html` in a modern browser; pages must work over
   both `http://` and `file://` protocols.

---

## 2. File & Directory Contract

### 2.1 Required layout

```
axler_visual/
├── shared/                       # IMMUTABLE contract — see §3
│   ├── style.css
│   ├── utils.js
│   ├── eigen.js
│   └── canvas.js
├── templates/
│   └── section-template.html     # scaffolded blank page
├── index.html                    # navigation hub
└── <SECTION_ID>.html             # one per Axler section
```

`<SECTION_ID>` SHOULD follow the pattern `{chapter}{subchapter}_{slug}`, e.g.
`5A_invariant_subspaces.html`, `7E_singular_value_decomposition.html`.
Compound pages covering multiple subsections MAY omit the subchapter letter
(`7_spectral_theorem.html` covers 7A+7B).

### 2.2 File size targets (soft)

| File | Target | Hard cap |
|---|---|---|
| `shared/style.css` | ≤ 6 KB | 12 KB |
| `shared/utils.js` | ≤ 2 KB | 4 KB |
| `shared/eigen.js` | ≤ 4 KB | 8 KB |
| `shared/canvas.js` | ≤ 10 KB | 16 KB |
| Section page | 35–55 KB | 80 KB |

Exceeding hard caps requires justification (new algorithm, new large
interactive) and SHOULD trigger a split.

### 2.3 Page self-containment

A section page MAY reference:
- `shared/*` by relative path (from the same directory)
- HTTPS CDNs for MathJax, Three.js, OrbitControls (versions pinned — see §2.4)
- Its own inline `<style>` and `<script>` (for page-specific CSS/JS)

A section page MUST NOT reference:
- Another section page's assets
- Local resources outside `axler_visual/` and `shared/`
- HTTP (non-HTTPS) CDNs

### 2.4 Pinned CDN versions

```
MathJax         v3   https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js
Three.js        r128 https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js
OrbitControls   r128 https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js
```

Three.js MUST stay at r128 until shared/ is rewritten for module imports
(r150+ removed the non-module `examples/js/controls/` path).

---

## 3. `shared/` Contract

### 3.1 Load order

Section pages MUST load shared scripts in this order, before any page-specific
JS that uses them:

```html
<link rel="stylesheet" href="shared/style.css" />
<script src="shared/utils.js"></script>
<script src="shared/eigen.js"></script>
<script src="shared/canvas.js"></script>
```

### 3.2 Global namespace

All shared functions and classes are attached to the global scope.
**Name collisions with page-local variables are forbidden**.

Reserved identifiers (MUST NOT shadow):

```
Utils:    fmt, cfmt, clearOut, addText, addLine, addBold, addBoldLine,
          addSpan, apply2x2, apply3x3, norm2, norm3, normalize2,
          normalize3, symmetrize3, isSymmetric2x2, isNormal2x2
Eigen:    eig2, eig3General, eig3Symmetric
Canvas:   Canvas2D, Scene3D
```

### 3.3 `style.css` — CSS variable contract

```css
/* Colors — semantic. Do not hardcode hex in page <style>. */
--bg, --panel, --panel2, --line       /* surfaces */
--text, --dim                         /* text */
--accent, --cyan                      /* primary highlights */
--eig, --red, --teal, --violet, --good  /* semantic data colors */
```

Semantic meaning table:

| Variable | Meaning |
|---|---|
| `--accent` | Eigenvalues, emphasis, primary buttons |
| `--cyan` | Type hints, "A" badges, normal vectors |
| `--eig` | Highlighted eigen-direction (ribbons, arrows) |
| `--red` | Complex eigenvalues, pathological cases |
| `--teal` | Iteration trajectories $T^k v$ |
| `--violet` | Orthonormal basis, secondary emphasis |
| `--good` | Success / ✓ states |

Page-local CSS MUST use these variables (not hex literals) for these semantics.

### 3.4 `style.css` — required classes

Page authors MUST use these class names (do not rename):

```
Layout:        .row  .col  .card
Content:       .def  .thm  .note  .formula  .muted  .tag
Navigation:    .toc  .toc .title  (anchor children)
Interactive:   .ctl  .val  .matinput  .readout  .presets  .toggles  .legend
Glossary:      table.glos  td.sym
Q&A:           .qa  .q  .a  .qdivider
Exercise:      .ex  .ex details  summary  .ex .diff
Misc:          ul.lst  kbd  canvas.plane  .three-container
```

Do not re-style these with `!important` in page CSS.

### 3.5 `utils.js` — API

```
// Formatting
fmt(x: number, d?: number = 3) -> string
cfmt(re: number, im: number, d?: number = 3) -> string

// Safe DOM construction (no innerHTML usage)
clearOut(el: HTMLElement) -> void
addText(el, text: string) -> void
addLine(el, text: string) -> void        // adds trailing "\n"
addBold(el, text: string) -> void
addBoldLine(el, text: string) -> void
addSpan(el, text: string, className?: string) -> void

// Linear-algebra micro-helpers
apply2x2(a, b, c, d, x, y) -> [number, number]
apply3x3(M: number[][], v: number[]) -> number[]
norm2(v) / norm3(v) -> number
normalize2(v) / normalize3(v) -> number[]
symmetrize3(M) -> number[][]
isSymmetric2x2(a, b, c, d) -> boolean
isNormal2x2(a, b, c, d) -> boolean
```

**Invariants:**
- `fmt(0)` and `fmt(1e-12)` both return `'0'`.
- `cfmt(x, 0)` drops the imaginary part entirely.
- All math functions are pure (no side effects, no mutation of inputs).

### 3.6 `eigen.js` — API

```
// Return shapes
type Real2    = { real: true,  vals: [number, number], vecs: [vec2, vec2] }
type Complex2 = { real: false, vals: [[re, im], [re, im]] }

// 2x2 analytical
eig2(a, b, c, d) -> Real2 | Complex2

// 3x3 general (Cardano) — returns only REAL eigenvalues
eig3General(M) -> Array<{ val: number, vec: vec3 }>

// 3x3 symmetric (Jacobi) — guarantees 3 real eigenvalues + orthonormal vecs
eig3Symmetric(A) -> { vals: number[3], vecs: vec3[3] }   // sorted descending
```

**Invariants:**
- `eig2` is pure and deterministic.
- `eig3Symmetric` produces orthonormal vectors up to 1e-10 in the Frobenius
  norm of $QQ^\top - I$.
- `eig3General` does NOT guarantee ordering; caller sorts if needed.
- When a 3×3 matrix has one real + two complex conjugate eigenvalues,
  `eig3General` returns only the real one.

### 3.7 `canvas.js` — `Canvas2D` class

```
class Canvas2D {
  constructor(canvasId: string, opts?: { range?=3.5, bg?='#070809' })

  // state
  M          : [a, b, c, d]
  sp         : Real2 | Complex2       // cached eigen-analysis of M
  W, H       : canvas CSS-pixel size
  s          : scale (px per world unit)
  cx, cy     : origin in pixels

  // hook (page author sets this)
  redraw     : (ctx, self) -> void | null

  // methods
  setMatrix(M: number[])                // updates M + redraws
  onClick(fn: (x, y, event) -> void)    // world coords
  draw()                                // manual redraw
  w2p(x, y) -> [px, py]                 // world -> pixel
  p2w(px, py) -> [x, y]                 // pixel -> world
  apply(x, y) -> [x', y']               // matrix action on point

  // built-in layer drawers — call these from redraw hook
  clear()
  drawGrid()
  drawDeformedGrid(opts?)
  drawEigenlines(opts?)
  drawUnitCircle(opts?)
  drawImageEllipse(opts?)
  drawArrows(opts?)
}
```

**Behavior:**
- When `redraw` is unset, `draw()` calls all default layers (grid →
  eigenlines → unit circle → image ellipse → arrows).
- `setMatrix` triggers `draw()` automatically.
- High-DPI resize is handled internally on `window.resize`.
- Click events fire with **world coordinates** (already de-projected).

### 3.8 `canvas.js` — `Scene3D` class

```
class Scene3D {
  constructor(containerId: string, opts?: {
    bg?=0x050607,
    cameraStart?=Vector3(5, 4, 5),
    axesSize?=3
  })

  // state
  scene, camera, renderer, controls   // Three.js objects
  M          : number[][]
  paused     : boolean

  // hook
  onUpdate   : (M: number[][]) -> void | null

  // methods
  setMatrix(M: number[][])            // triggers onUpdate
  resetCamera()
  setUnitCubeWire(color?=0x5cb3ff, opacity?=0.35)
  setTransformedCube(M, color?=0xf5b942, opacity?=0.35)
  setEigenAxes(eigs, opts?: {
    color?=0xffd166,
    scaleByValue?=false,
    length?=3,
    endSpheres?=true
  })
}
```

**Behavior:**
- Keyboard: `Space` toggles pause, `R` resets camera.
- Animation loop starts automatically in constructor.
- `setUnitCubeWire` / `setTransformedCube` / `setEigenAxes` are idempotent —
  calling them replaces the previous mesh (disposes old geometry).

---

## 4. Page Structure Contract

Every section page MUST follow this macro structure:

```
<!doctype html>
<html lang="zh">
<head>
  <title>Axler {SECTION_ID} · {TITLE}</title>
  ... MathJax + Three.js CDN + shared/ ...
</head>
<body>
  <nav class="toc"> 15 anchors </nav>

  <h1>{TITLE} <small>· Axler 4e · pp.{START}–{END}</small></h1>
  <p class="muted">{ONE-SENTENCE_PITCH}</p>

  <h2 id="glos">0 · 符号对照</h2>
  <div class="card"><table class="glos"> ... </table></div>

  <!-- Q1 through Q15 -->
  <div class="qa" id="q1"> ... </div>
  <hr class="qdivider">
  ...
  <div class="qa" id="q15"> ... </div>
  <hr class="qdivider">

  <!-- 8 self-test exercises -->
  <div class="qa" id="ex"> ... </div>

  <!-- Page-specific JS: wiring, custom redraws -->
  <script> ... </script>
</body>
</html>
```

### 4.1 Q&A cardinality

| Section | Expected Q count | Hard min/max |
|---|---|---|
| Core content | Q1–Q12 | min 8, max 15 |
| Closing | Q13 theorem summary, Q14 applications, Q15 next step | required |
| Exercises | 8 problems | min 6, max 10 |

### 4.2 Q&A slot conventions

- **Q1** introduces the core object / definition of the section
- **Q2** provides motivation ("why care?")
- **Q3–Q7** develop the main machinery (definitions, theorems, examples)
- **Q4 or Q5** is typically the 2D interactive slot
- **Q8–Q10** often contains the 3D interactive slot
- **Q11–Q12** deeper results / connections
- **Q13** theorem summary (all key theorems boxed)
- **Q14** real-world applications (1–2 deep examples, not 10 shallow)
- **Q15** "what's next" — pointer to adjacent sections

### 4.3 Exercise structure

Each `.ex` block MUST contain a heading with difficulty stars, a problem
statement, a `<details>` 提示 block, and a `<details>` 答案 block.

Difficulty stars:
- ★ = concept check or simple computation
- ★★ = proof / non-trivial computation
- ★★★ = synthesis / open-ended

Distribution SHOULD be approximately: 2–3 ★, 3–4 ★★, 1–2 ★★★.

### 4.4 Symbol glossary

Every page MUST start (after the intro paragraph) with a 4-column glossary
table (`符号 / 念作 / 含义 / 类型`).

The "类型" column helps students build type intuition (e.g., `$\lambda$` has
type "scalar in $\mathbf{F}$", `$T$` has type "operator $V \to V$").

Include EVERY non-standard symbol that appears in this page's Q&A or
exercises. Do not omit symbols introduced in prior sections — glossary is
intended to be self-contained per page.

---

## 5. Security & Safety Rules

### 5.1 No innerHTML assignment

Dynamic DOM content MUST be built with `createTextNode` /
`createElement` / `appendChild` — or equivalently via `utils.js` helpers
(`addText`, `addBold`, `addSpan`, etc.).

Assigning to `innerHTML` is forbidden, even for trusted-looking input.
Reasons:
1. Claude Code's security hook blocks such writes
2. XSS-safety across locales (Chinese TeX inside user-entered text)
3. Auditability

### 5.2 No dynamic code compilation

Page JS MUST NOT compile or execute dynamically-constructed source strings.
All logic is written as static source in `<script>` tags.

### 5.3 Pure MathJax inputs

MathJax inputs (content between `$...$` / `$$...$$`) are static strings
written by the author. Never interpolate user-provided strings into LaTeX
without escaping.

### 5.4 No third-party tracking

Page MUST NOT include analytics, tracking pixels, or external references
beyond the CDN allowlist in §2.4.

---

## 6. Canonical Procedure: Adding a New Section

### 6.1 Steps

1. Copy `templates/section-template.html` to `{SECTION_ID}.html` in the root
2. Edit `<title>`, `<h1>`, intro `<p class="muted">`
3. Update the `<nav class="toc">` labels (15 anchors)
4. Fill the glossary `<table class="glos">` with this section's symbols
5. Write Q1 through Q15 (§4.2 conventions)
6. Write 8 exercises (§4.3)
7. In the bottom `<script>`:
   - Set `cv.redraw` to the per-page drawing function
   - Customize `renderSpec2d` to show the readout for this section
   - If using 3D: customize `sc.onUpdate` and `renderSpec3d`
8. Verify locally: `python3 -m http.server 8766`, open
   `http://127.0.0.1:8766/{SECTION_ID}.html`, check console for errors
9. Update `index.html` to link the new page

### 6.2 Validation checklist

Before considering a section page "done", verify:

- [ ] Console shows zero errors (favicon 404 is acceptable)
- [ ] All 15 Q anchors scroll to their targets
- [ ] MathJax renders all formulas (no raw LaTeX visible)
- [ ] 2D canvas responds to all sliders + presets without throwing
- [ ] 3D canvas responds to all matrix inputs + OrbitControls
- [ ] All 8 exercises have both 提示 and 答案 `<details>` blocks
- [ ] Page renders readably at 920px, 1280px, and 1920px widths
- [ ] No innerHTML assignments in page `<script>`
- [ ] No hardcoded hex colors — all via CSS variables
- [ ] File size ≤ 80 KB

---

## 7. Extending `shared/`

Changes to `shared/*` are load-bearing — every existing page depends on the
contract. Follow this procedure.

### 7.1 When to add to shared/

| Situation | Destination |
|---|---|
| Used once in one page | Page-local `<script>` |
| Used by 2+ pages | Consider hoisting to `shared/` |
| Algorithm (not rendering) | `shared/eigen.js` (or new `shared/algorithms.js`) |
| DOM/formatting helper | `shared/utils.js` |
| Canvas rendering primitive | `shared/canvas.js` (method on existing class or new helper) |
| CSS pattern | `shared/style.css` — must use existing CSS variables |

### 7.2 Backwards compatibility

Additions are always allowed. Breaking changes require:

1. Bump spec version (§ header)
2. Migrate ALL existing section pages
3. Note migration in `README.md`

Acceptable breaking changes: removing unused functions, renaming misleading
identifiers, tightening a signature. Prohibited: changing an existing
function's output semantics without renaming.

### 7.3 Testing added shared code

New shared code MUST be demonstrated working in `demo_shared.html` before
other pages depend on it. If you add a new method to `Canvas2D` or
`Scene3D`, the demo page's script block should exercise it.

---

## 8. Known Limitations

1. **`eig3General` ordering is not deterministic** across all inputs —
   returns real eigenvalues in the order Cardano produces them.
2. **`Canvas2D` range is fixed at construction** — no dynamic zoom.
3. **`Scene3D` camera reset is hardcoded to the initial position** —
   no multi-view presets.
4. **No dim > 3** — framework provides no 4D or higher visualization
   primitives.
5. **No complex-field visualization** — $\mathbf{C}^n$ eigenvectors are
   rendered as their real parts only.
6. **Three.js r128 pin** — keeps us on pre-module Three.js for the simple
   `<script>` loading pattern.

---

## 9. Versioning

```
1.0   2026-04-23   Initial spec. 4-file shared/, Canvas2D + Scene3D classes.
                   Demo: 5A, 5D_v2_fresh, 7_spectral_theorem.
```

Future minor bumps: additions to shared APIs. Major bumps: breaking
changes with migration required.

---

## 10. References

- Axler, S. *Linear Algebra Done Right*, 4th ed. Springer 2024.
- Three.js r128 docs: https://threejs.org/docs/index.html?r=128
- MathJax 3 docs: https://docs.mathjax.org/en/latest/
- Project `README.md` for higher-level workflow notes.
