Add Messianic lineage visualization to family tree

- Create dedicated lineage page at /family-tree/lineage
- Generate clean SVG visualization showing direct paternal line (Adam → Jesus)
- Use Kekulé numbering system to trace powers of 2 (direct ancestors)
- MacFamilyTree-inspired minimal design with:
  - Vertical layout with connector lines
  - Clickable person boxes linking to detail pages
  - Name, dates, generation, and Kekulé number for each ancestor
  - Tufte ETBembo typography
  - Hover effects
- Update main family tree page with link to lineage (instead of embedding)
- Add explanation of Kekulé numbering system and biblical references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-22 14:55:07 -05:00
parent 613e762020
commit ffe4175204
3 changed files with 173 additions and 0 deletions
+130
View File
@@ -4226,6 +4226,136 @@ def family_tree_search_page(request: Request, q: str = ""):
)
@app.get("/family-tree/lineage", response_class=HTMLResponse)
def family_tree_lineage_page(request: Request):
"""Dedicated page for the Messianic lineage visualization"""
books = list(bible.iter_books())
return templates.TemplateResponse(
"family_tree_lineage.html",
{
"request": request,
"books": books,
"breadcrumbs": [
{"text": "Home", "url": "/"},
{"text": "Family Tree", "url": "/family-tree"},
{"text": "Messianic Lineage", "url": None}
]
}
)
@app.get("/family-tree/lineage.svg")
def family_tree_lineage_svg(request: Request):
"""Generate SVG visualization of the Messianic lineage (Adam to Jesus)"""
static_dir = Path(__file__).parent / "static"
gedcom_path = static_dir / "adameve.ged"
if not gedcom_path.exists() or not GedcomReader:
raise HTTPException(status_code=404, detail="Family tree data not available")
try:
family_tree_data, generations = parse_gedcom_to_tree_data(gedcom_path)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to parse family tree: {str(e)}")
# Find Jesus and trace back through Kekulé #1 ancestors (powers of 2 in father line)
# Kekulé numbering: 1 = subject, 2 = father, 4 = paternal grandfather, 8 = paternal great-grandfather, etc.
lineage = []
# Find all people with Kekulé numbers that are powers of 2 (direct paternal line)
# This includes: 1, 2, 4, 8, 16, 32, 64, 128, etc.
for person_id, person in family_tree_data.items():
kekule = person.get("kekule_number")
if kekule and kekule > 0:
# Check if kekule is a power of 2
if kekule & (kekule - 1) == 0:
lineage.append({
"id": person_id,
"name": person["name"],
"kekule": kekule,
"generation": person.get("generation", 0),
"birth_year": person.get("birth_year", "Unknown"),
"death_year": person.get("death_year", "Unknown")
})
# Sort by Kekulé number (descending = Adam to Jesus)
lineage.sort(key=lambda x: -x["kekule"])
# Generate SVG
width = 800
node_height = 80
node_width = 700
margin_top = 40
margin_bottom = 40
vertical_spacing = 20
height = margin_top + (len(lineage) * (node_height + vertical_spacing)) + margin_bottom
svg_parts = [
f'<?xml version="1.0" encoding="UTF-8"?>',
f'<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">',
'<defs>',
'<style>',
'.person-box { fill: #f9f9f9; stroke: #333; stroke-width: 1.5; }',
'.person-box:hover { fill: #f0f8ff; stroke: #0066cc; }',
'.person-name { font-family: "ETBembo", Palatino, "Book Antiqua", serif; font-size: 18px; font-weight: 600; fill: #111; }',
'.person-dates { font-family: "ETBembo", Palatino, "Book Antiqua", serif; font-size: 14px; fill: #666; }',
'.person-meta { font-family: "ETBembo", Palatino, "Book Antiqua", serif; font-size: 12px; fill: #999; }',
'.connector-line { stroke: #999; stroke-width: 2; fill: none; }',
'</style>',
'</defs>',
]
x = (width - node_width) / 2
# Draw connector lines first (so they appear behind boxes)
for i in range(len(lineage) - 1):
y1 = margin_top + (i * (node_height + vertical_spacing)) + node_height
y2 = margin_top + ((i + 1) * (node_height + vertical_spacing))
mid_x = x + (node_width / 2)
svg_parts.append(f'<line class="connector-line" x1="{mid_x}" y1="{y1}" x2="{mid_x}" y2="{y2}" />')
# Draw person boxes
for i, person in enumerate(lineage):
y = margin_top + (i * (node_height + vertical_spacing))
# Draw box with link
svg_parts.append(f'<a href="/family-tree/person/{person["id"]}">')
svg_parts.append(f'<rect class="person-box" x="{x}" y="{y}" width="{node_width}" height="{node_height}" rx="4" />')
# Name
name_y = y + 28
svg_parts.append(f'<text class="person-name" x="{x + node_width/2}" y="{name_y}" text-anchor="middle">{person["name"]}</text>')
# Dates
dates_text = ""
if person["birth_year"] != "Unknown" and person["death_year"] != "Unknown":
dates_text = f'{person["birth_year"]} {person["death_year"]}'
elif person["birth_year"] != "Unknown":
dates_text = f'Born {person["birth_year"]}'
elif person["death_year"] != "Unknown":
dates_text = f'Died {person["death_year"]}'
if dates_text:
dates_y = y + 48
svg_parts.append(f'<text class="person-dates" x="{x + node_width/2}" y="{dates_y}" text-anchor="middle">{dates_text}</text>')
# Meta (generation and Kekulé number)
meta_text = f'Generation {person["generation"]}'
if person["kekule"] > 1:
meta_text += f' • Kekulé #{person["kekule"]}'
meta_y = y + 66
svg_parts.append(f'<text class="person-meta" x="{x + node_width/2}" y="{meta_y}" text-anchor="middle">{meta_text}</text>')
svg_parts.append('</a>')
svg_parts.append('</svg>')
svg_content = '\n'.join(svg_parts)
return Response(content=svg_content, media_type="image/svg+xml")
def expand_book_abbreviation(abbrev):
"""Expand common Bible book abbreviations to full names"""
abbreviations = {
+7
View File
@@ -135,6 +135,13 @@ section:nth-of-type(3) {
</div>
</section>
<section>
<h2>Quick Links</h2>
<p>
<a href="/family-tree/lineage">View the Messianic Lineage</a> — A visual genealogy showing the direct paternal line from Adam to Jesus Christ.
</p>
</section>
<section>
<h2>The Generations</h2>
@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block title %}The Messianic Lineage - Biblical Family Tree - KJV Study{% endblock %}
{% block description %}The direct paternal line from Adam to Jesus Christ, traced through biblical genealogies.{% endblock %}
{% block content %}
<h1>The Messianic Lineage</h1>
<p class="subtitle">The Direct Paternal Line from Adam to Jesus Christ</p>
<section>
<p>This visualization traces the direct paternal line from Adam, the first man, through the patriarchs, kings, and prophets, culminating in Jesus Christ. The genealogy is calculated using the Kekulé numbering system (also known as Ahnentafel), where each person's father has a number that is double their own.</p>
<p>This represents the genealogical record preserved in Scripture (Genesis 5, Genesis 11, Ruth 4:18-22, 1 Chronicles 1-3, Matthew 1, Luke 3), showing God's redemptive plan working through specific family lines across the generations.</p>
<p style="margin-top: 1.5rem;"><a href="/family-tree">← Back to Family Tree</a></p>
</section>
<section>
<div style="max-width: 100%; overflow-x: auto; margin: 2rem 0;">
<img src="/family-tree/lineage.svg" alt="Messianic Lineage from Adam to Jesus Christ" style="max-width: 100%; height: auto; display: block; margin: 0 auto;" />
</div>
</section>
<section>
<h2>About the Kekulé Numbering System</h2>
<p>The Kekulé number (Ahnentafel number) is a genealogical numbering system where:</p>
<ul>
<li><strong>1</strong> = The subject (Jesus Christ)</li>
<li><strong>2</strong> = Father (Joseph, legal father)</li>
<li><strong>4</strong> = Paternal grandfather (Jacob)</li>
<li><strong>8</strong> = Paternal great-grandfather</li>
<li>And so on, with each father having a number that is double their child's</li>
</ul>
<p>The direct paternal line consists of all ancestors with Kekulé numbers that are powers of 2: 1, 2, 4, 8, 16, 32, 64, etc. This visualization shows only these direct ancestors, creating a clear line from Adam to Christ.</p>
</section>
{% endblock %}