',
+ f'
{toc_header}
',
+ ]
# Track "General" occurrences across all categories that actually have them
general_counter = 0
@@ -1062,18 +1192,21 @@ def generate_toc_from_categories(csv_data=None, general_map=None):
# Add main category row with theme-adaptive picture element
dark_svg = svg_filename
light_svg = svg_filename.replace(".svg", "-light-anim-scanline.svg")
+ toc_lines.append('
")
# Check if this category has subcategories
subcategories = category.get("subcategories", [])
@@ -1105,15 +1238,9 @@ def generate_toc_from_categories(csv_data=None, general_map=None):
# Special handling for "General" subcategories
if sub_title == "General":
if general_map is not None:
- sub_anchor = general_map.get((category_name, sub_title), "general-")
+ sub_anchor = general_map.get((category_id, sub_title), "general-")
else:
- if general_counter == 0:
- # First occurrence: just #general-
- sub_anchor = "general-"
- else:
- # Subsequent occurrences: #general--1, #general--2, etc.
- sub_anchor = f"general--{general_counter}"
- general_counter += 1
+ sub_anchor = f"{category_id}-general"
else:
# Non-General subcategories need "-" suffix due to back-to-top links (π emoji)
sub_anchor = sub_anchor + "-"
@@ -1124,22 +1251,134 @@ def generate_toc_from_categories(csv_data=None, general_map=None):
# Add subcategory row with theme-adaptive picture element
dark_svg = svg_filename
light_svg = svg_filename.replace(".svg", "-light-anim-scanline.svg")
+ toc_lines.append('
")
+
+ toc_lines.append("
")
return "\n".join(toc_lines).strip()
+def _normalize_svg_root(tag: str, target_width: int, target_height: int) -> str:
+ """Ensure root SVG tag enforces target width/height, viewBox, and left anchoring."""
+
+ def ensure_attr(svg_tag: str, name: str, value: str) -> str:
+ if re.search(rf'{name}="[^"]*"', svg_tag):
+ return re.sub(rf'{name}="[^"]*"', f'{name}="{value}"', svg_tag)
+ # Insert before closing ">"
+ return svg_tag.rstrip(">") + f' {name}="{value}">'
+
+ # Force consistent width/height
+ svg_tag = ensure_attr(tag, "width", str(target_width))
+ svg_tag = ensure_attr(svg_tag, "height", str(target_height))
+
+ # Ensure preserveAspectRatio anchors left and keeps aspect
+ svg_tag = ensure_attr(svg_tag, "preserveAspectRatio", "xMinYMid meet")
+
+ # Enforce viewBox to match target dimensions
+ svg_tag = ensure_attr(svg_tag, "viewBox", f"0 0 {target_width} {target_height}")
+
+ return svg_tag
+
+
+def normalize_toc_svgs(assets_dir: str) -> None:
+ """Normalize TOC row/sub SVGs to enforce consistent display height/anchoring."""
+ patterns = ["toc-row-*.svg", "toc-sub-*.svg", "toc-header*.svg"]
+ for pattern in patterns:
+ for path in glob.glob(os.path.join(assets_dir, pattern)):
+ with open(path, encoding="utf-8") as f:
+ content = f.read()
+
+ # Grab the root tag
+ match = re.search(r"