Files
kjvstudy.org/kjvstudy_org/templates/family_tree.html
kennethreitz 17278b6bb0 Remove timeline visualization
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 14:58:26 -05:00

440 lines
15 KiB
HTML

{% extends "base.html" %}
{% block title %}Biblical Family Tree - KJV Study{% endblock %}
{% block description %}Explore biblical genealogies from Adam to Jesus Christ.{% endblock %}
{% block head %}
<style>
/* Compact the opening sections */
section:first-of-type {
margin-top: 1rem;
}
section:nth-of-type(2),
section:nth-of-type(3) {
margin-top: 1rem;
padding-top: 0;
border-top: none;
}
.stats-line {
max-width: 55%;
margin: 1rem 0;
padding: 0.75rem 0;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
font-size: 1.1rem;
}
.stat-item {
display: inline;
margin-right: 2rem;
}
.stat-number {
font-family: et-book-roman-old-style;
font-weight: 600;
}
.generation-section {
max-width: 55%;
margin: 1.5rem 0;
}
.generation-header {
margin-bottom: 0.5rem;
}
.generation-number {
font-family: et-book-roman-old-style;
font-size: 1.2rem;
font-weight: 600;
}
.people-inline {
font-size: 1.1rem;
line-height: 1.8;
}
.person-entry {
max-width: 55%;
margin: 1.5rem 0;
}
.person-name {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.person-life {
font-style: italic;
color: #666;
margin-bottom: 0.5rem;
}
.person-info {
margin: 0.5rem 0;
line-height: 1.8;
}
.search-form {
max-width: 55%;
margin: 0.5rem 0;
position: relative;
}
.search-form input {
width: 100%;
padding: 0.5rem;
font-size: 1rem;
border: 1px solid #ccc;
font-family: inherit;
}
.search-form input:focus {
outline: none;
border-color: #666;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--bg-color, #fff);
border: 1px solid var(--border-color, #ddd);
border-top: none;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
max-height: 240px;
overflow-y: auto;
display: none;
z-index: 10;
}
.search-dropdown.open {
display: block;
}
.search-dropdown button {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
border: none;
background: transparent;
text-align: left;
font-size: 0.95rem;
cursor: pointer;
}
.search-dropdown button:hover,
.search-dropdown button.active {
background: var(--code-bg, #f6f6f6);
}
</style>
{% endblock %}
{% block content %}
<h1>Biblical Family Tree</h1>
<p class="subtitle">From Adam to Jesus Christ</p>
<section>
<p>The Bible contains detailed genealogies that trace God's plan through specific family lines, culminating in the birth of Jesus Christ. This record spans from the creation of Adam through countless generations to the birth of our Lord.</p>
</section>
<section>
<div class="search-form">
<form method="get" action="/family-tree/search" id="family-tree-search-form">
<input
type="text"
name="q"
placeholder="Search for a person..."
autocomplete="off"
id="family-tree-search-input"
>
</form>
<div class="search-dropdown" id="family-tree-search-dropdown"></div>
</div>
</section>
{% if family_tree_data and generations %}
<section>
<h2>Overview</h2>
<div id="family-tree-stats-loading">
<p style="color: #999;">Loading statistics...</p>
</div>
<table id="family-tree-stats" style="display: none; margin-top: 1rem;">
<tbody>
<tr>
<td>Total People:</td>
<td id="stat-total-people">{{ family_tree_data|length }}</td>
</tr>
<tr>
<td>Total Generations:</td>
<td id="stat-total-generations">{{ generations|length }}</td>
</tr>
<tr>
<td>Longest Lived:</td>
<td id="stat-longest-lived"></td>
</tr>
<tr>
<td>Most Children:</td>
<td id="stat-most-children"></td>
</tr>
<tr>
<td>Most Siblings:</td>
<td id="stat-most-siblings"></td>
</tr>
<tr>
<td>Close Family Marriages:</td>
<td id="stat-close-marriages"></td>
</tr>
</tbody>
</table>
</section>
<section>
<h2>Explore the Family Tree</h2>
<p style="font-size: 1.1rem; line-height: 1.8; margin-bottom: 1.5rem;">
<strong><a href="/family-tree/interactive">Interactive Tree</a></strong>
Navigate the family tree with an interactive, zoomable diagram. Expand and collapse branches, search for any person, and see the Messianic lineage highlighted in gold.
</p>
<p style="font-size: 1.1rem; line-height: 1.8;">
<strong><a href="/family-tree/lineage">Messianic Lineage</a></strong>
View the direct paternal line from Adam to Jesus Christ in a vertical genealogy chart.
</p>
</section>
<section>
<h2>The Generations</h2>
{% for gen_num in generations.keys() | sort %}
<div class="generation-section">
<div class="generation-header">
<h3><span class="generation-number">Generation {{ gen_num }}.</span> <a href="/family-tree/generation/{{ gen_num }}">{{ generations[gen_num]|length }} individual{% if generations[gen_num]|length != 1 %}s{% endif %}</a></h3>
</div>
<p class="people-inline">
{% for person_id in generations[gen_num][:20] %}
<a href="/family-tree/person/{{ person_id }}">{{ family_tree_data[person_id].name }}</a>{% if not loop.last %}, {% endif %}
{% endfor %}{% if generations[gen_num]|length > 20 %}, and {{ generations[gen_num]|length - 20 }} others{% endif %}.
</p>
</div>
{% endfor %}
</section>
<section>
<h2>Notable Figures</h2>
{% set notable = ["Adam", "Noah", "Abraham", "Isaac", "Jacob", "Joseph", "Moses", "David", "Solomon", "Jesus"] %}
{% for person_id, person in family_tree_data.items() %}
{% if person.name in notable %}
<div class="person-entry">
<div class="person-name">
<a href="/family-tree/person/{{ person_id }}">{{ person.name }}</a>
</div>
<div class="person-life">
{% if person.generation %}Generation {{ person.generation }}{% endif %}{% if person.kekule_number is not none %} • Kekulé #{{ person.kekule_number }}{% endif %}{% if person.birth_year != "Unknown" %} • born {{ person.birth_year }}{% endif %}{% if person.age_at_death != "Unknown" %} • lived {{ person.age_at_death }}{% endif %}
</div>
<div class="person-info">
{% if person.spouse %}
Married to {{ person.spouse }}.
{% endif %}
{% if person.parents|length > 0 %}
{% if person.spouse %}{% endif %}
Child of
{% for parent_id in person.parents %}
{% if parent_id in family_tree_data %}<a href="/family-tree/person/{{ parent_id }}">{{ family_tree_data[parent_id].name }}</a>{% if not loop.last %} and {% endif %}{% endif %}
{% endfor %}.
{% endif %}
{% if person.children|length > 0 %}
Father of
{% for child_id in person.children[:5] %}
{% if child_id in family_tree_data %}<a href="/family-tree/person/{{ child_id }}">{{ family_tree_data[child_id].name }}</a>{% if not loop.last %}, {% endif %}{% endif %}
{% endfor %}{% if person.children|length > 5 %}, and {{ person.children|length - 5 }} others{% endif %}.
{% endif %}
</div>
{% if person.verses %}
<p>
{% for verse in person.verses[:2] %}
{% set ref_parts = verse.reference.split(' ') %}
{% if ref_parts|length >= 2 %}
{% set chapter_verse = ref_parts[-1] %}
{% if ':' in chapter_verse %}
{% set chapter = chapter_verse.split(':')[0] %}
{% set verse_part = chapter_verse.split(':')[1] %}
{% if '-' in verse_part %}
{% set verse_num = verse_part.split('-')[0] %}
{% else %}
{% set verse_num = verse_part %}
{% endif %}
{% set book = ' '.join(ref_parts[:-1]) %}
<a href="/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num }}">{{ verse.reference }}</a>{% if not loop.last %}; {% endif %}
{% endif %}
{% endif %}
{% endfor %}
</p>
{% endif %}
</div>
{% endif %}
{% endfor %}
</section>
{% else %}
<section>
<p>Family tree data could not be loaded.</p>
</section>
{% endif %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Fetch and display family tree statistics
const statsContainer = document.getElementById('family-tree-stats');
const statsLoading = document.getElementById('family-tree-stats-loading');
fetch('/api/family-tree/stats')
.then(response => response.json())
.then(data => {
// Update the statistics
document.getElementById('stat-total-people').textContent = data.total_people;
document.getElementById('stat-total-generations').textContent = data.total_generations;
// Longest lived person
if (data.longest_lived && data.longest_lived.value > 0) {
const personName = data.longest_lived.name;
const personId = data.longest_lived.person_id;
document.getElementById('stat-longest-lived').innerHTML =
`<a href="/family-tree/person/${personId}">${personName}</a> (${data.longest_lived.value} years)`;
}
// Most children
if (data.most_children && data.most_children.value > 0) {
const personName = data.most_children.name;
const personId = data.most_children.person_id;
document.getElementById('stat-most-children').innerHTML =
`<a href="/family-tree/person/${personId}">${personName}</a> (${data.most_children.value} children)`;
}
// Most siblings
if (data.most_siblings && data.most_siblings.value > 0) {
const personName = data.most_siblings.name;
const personId = data.most_siblings.person_id;
document.getElementById('stat-most-siblings').innerHTML =
`<a href="/family-tree/person/${personId}">${personName}</a> (${data.most_siblings.value} siblings)`;
}
// Close family marriages
if (data.close_family_marriages !== undefined) {
document.getElementById('stat-close-marriages').textContent =
data.close_family_marriages + ' (common in early biblical times)';
}
// Show stats, hide loading
statsLoading.style.display = 'none';
statsContainer.style.display = 'table';
})
.catch(error => {
console.error('Error loading family tree statistics:', error);
statsLoading.innerHTML = '<p style="color: #999;">Could not load statistics</p>';
});
// Search functionality
const names = {{ person_names|default([])|tojson }};
const input = document.getElementById('family-tree-search-input');
const dropdown = document.getElementById('family-tree-search-dropdown');
const form = document.getElementById('family-tree-search-form');
if (!input || !dropdown || !form || names.length === 0) {
return;
}
let filtered = [];
let activeIndex = -1;
function hideDropdown() {
dropdown.classList.remove('open');
dropdown.innerHTML = '';
filtered = [];
activeIndex = -1;
}
function renderDropdown(items) {
if (!items.length) {
hideDropdown();
return;
}
dropdown.innerHTML = items
.map((name, idx) => `<button type="button" data-index="${idx}">${name}</button>`)
.join('');
dropdown.classList.add('open');
activeIndex = -1;
}
function setActive(index) {
const buttons = dropdown.querySelectorAll('button');
buttons.forEach(btn => btn.classList.remove('active'));
if (buttons[index]) {
buttons[index].classList.add('active');
}
}
function chooseName(name) {
input.value = name;
hideDropdown();
form.submit();
}
input.addEventListener('input', function() {
const query = input.value.trim().toLowerCase();
if (!query) {
hideDropdown();
return;
}
filtered = names.filter(name => name.toLowerCase().includes(query)).slice(0, 8);
renderDropdown(filtered);
});
input.addEventListener('keydown', function(event) {
if (!dropdown.classList.contains('open')) {
return;
}
if (event.key === 'ArrowDown') {
event.preventDefault();
activeIndex = (activeIndex + 1) % filtered.length;
setActive(activeIndex);
} else if (event.key === 'ArrowUp') {
event.preventDefault();
activeIndex = (activeIndex - 1 + filtered.length) % filtered.length;
setActive(activeIndex);
} else if (event.key === 'Enter') {
if (activeIndex >= 0 && filtered[activeIndex]) {
event.preventDefault();
chooseName(filtered[activeIndex]);
}
} else if (event.key === 'Escape') {
hideDropdown();
}
});
dropdown.addEventListener('mousedown', function(event) {
const button = event.target.closest('button');
if (!button) {
return;
}
const index = Number(button.dataset.index);
if (!Number.isNaN(index) && filtered[index]) {
chooseName(filtered[index]);
}
});
document.addEventListener('click', function(event) {
if (!dropdown.contains(event.target) && event.target !== input) {
hideDropdown();
}
});
});
</script>
{% endblock %}