Skip to content

Commit cb99634

Browse files
apartsinclaude
andcommitted
Batch polish: authors page, cover animations, card redesign, callout/caption fixes, agent compaction
Added About the Authors page with bios for Apartsin and Aperstein. Added author names to cover page below tagline. Cover page: floating particles, title shimmer, Easter egg hover effects. Course cards: CSS grid with color-coded badges for syllabi and tracks. Front matter cards: standardized pathway/intro cards to book.css. Fixed 7 nested callouts across 6 files. Removed 42 duplicate code captions across 20 files. Compacted 32 agent skill files (50% size reduction, shared CSS). Short TOC: compact inline layout with middot separators. FM.4: added 5 missing callout type entries (note, tip, exercise, algorithm, key-takeaway). Prism.js: rebuilt bundle with toml + diff support. Removed 112 practical-example blocks from 29 chapter index pages. Added diagnostic scripts for SVG quality, missing images, and nested callouts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ef1cc3b commit cb99634

134 files changed

Lines changed: 3093 additions & 8466 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

_add_prism.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def process_file(html_path: Path) -> bool:
3434
if "prism-theme.css" in text or "prism-bundle" in text:
3535
return False
3636

37+
# Only add Prism to files that contain code blocks
38+
if '<code class="language-' not in text and "<code class='language-" not in text:
39+
return False
40+
3741
prefix = get_relative_prefix(html_path)
3842
tags = build_prism_tags(prefix)
3943

_find_missing_images.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""Scan HTML files for <img> tags referencing images that don't exist on disk."""
2+
3+
import os
4+
import re
5+
import glob
6+
from collections import defaultdict
7+
from pathlib import Path
8+
9+
ROOT = Path(os.path.dirname(os.path.abspath(__file__)))
10+
11+
# Patterns to find HTML files
12+
HTML_GLOBS = [
13+
"part-*/module-*/**/*.html",
14+
"part-*/index.html",
15+
"appendices/**/*.html",
16+
"front-matter/**/*.html",
17+
]
18+
19+
# Regex to extract img tags (src and alt)
20+
IMG_RE = re.compile(
21+
r'<img\s[^>]*?src=["\']([^"\']+)["\'][^>]*?>',
22+
re.IGNORECASE | re.DOTALL,
23+
)
24+
ALT_RE = re.compile(r'alt=["\']([^"\']*)["\']', re.IGNORECASE)
25+
26+
27+
def find_html_files():
28+
files = set()
29+
for pattern in HTML_GLOBS:
30+
for path in ROOT.glob(pattern):
31+
files.add(path)
32+
return sorted(files)
33+
34+
35+
def extract_images(html_path):
36+
"""Return list of (src, alt) tuples from img tags in the file."""
37+
try:
38+
text = html_path.read_text(encoding="utf-8", errors="replace")
39+
except Exception:
40+
return []
41+
results = []
42+
for match in IMG_RE.finditer(text):
43+
src = match.group(1)
44+
alt_match = ALT_RE.search(match.group(0))
45+
alt = alt_match.group(1) if alt_match else "(no alt text)"
46+
results.append((src, alt))
47+
return results
48+
49+
50+
def resolve_src(html_path, src):
51+
"""Resolve an img src relative to the HTML file's directory."""
52+
# Skip external URLs and data URIs
53+
if src.startswith(("http://", "https://", "data:", "//", "mailto:")):
54+
return None
55+
html_dir = html_path.parent
56+
resolved = (html_dir / src).resolve()
57+
return resolved
58+
59+
60+
def main():
61+
html_files = find_html_files()
62+
print(f"Scanning {len(html_files)} HTML files...\n")
63+
64+
# Group missing images by directory
65+
# key: directory relative to ROOT, value: list of (html_file, src, alt, resolved)
66+
missing_by_dir = defaultdict(list)
67+
total_images = 0
68+
total_missing = 0
69+
70+
for html_path in html_files:
71+
images = extract_images(html_path)
72+
for src, alt in images:
73+
resolved = resolve_src(html_path, src)
74+
if resolved is None:
75+
continue # external URL
76+
total_images += 1
77+
if not resolved.exists():
78+
total_missing += 1
79+
rel_html = html_path.relative_to(ROOT)
80+
rel_dir = rel_html.parent
81+
missing_by_dir[str(rel_dir)].append({
82+
"html": str(rel_html),
83+
"src": src,
84+
"alt": alt,
85+
"resolved": str(resolved.relative_to(ROOT)) if resolved.is_relative_to(ROOT) else str(resolved),
86+
})
87+
88+
# Sort directories by part/module number
89+
def sort_key(dirname):
90+
parts = dirname.replace("\\", "/").split("/")
91+
nums = []
92+
for p in parts:
93+
m = re.search(r'(\d+)', p)
94+
nums.append(int(m.group(1)) if m else 999)
95+
return nums
96+
97+
sorted_dirs = sorted(missing_by_dir.keys(), key=sort_key)
98+
99+
# Print report
100+
print("=" * 80)
101+
print(f"MISSING IMAGE REPORT")
102+
print(f"Total images found in HTML: {total_images}")
103+
print(f"Total missing images: {total_missing}")
104+
print("=" * 80)
105+
106+
for dirname in sorted_dirs:
107+
entries = missing_by_dir[dirname]
108+
print(f"\n{'-' * 80}")
109+
print(f" [{dirname}] ({len(entries)} missing)")
110+
print(f"{'-' * 80}")
111+
for e in entries:
112+
print(f" File: {e['html']}")
113+
print(f" src: {e['src']}")
114+
print(f" alt: {e['alt']}")
115+
print(f" expected at: {e['resolved']}")
116+
print()
117+
118+
# Summary table
119+
print("\n" + "=" * 80)
120+
print("SUMMARY BY DIRECTORY")
121+
print("=" * 80)
122+
for dirname in sorted_dirs:
123+
count = len(missing_by_dir[dirname])
124+
print(f" {count:4d} {dirname}")
125+
print(f" {'-' * 40}")
126+
print(f" {total_missing:4d} TOTAL")
127+
128+
129+
if __name__ == "__main__":
130+
main()

0 commit comments

Comments
 (0)