diff --git a/kjvstudy_org/templates/family_tree.html b/kjvstudy_org/templates/family_tree.html index 24cd30e..f03d42a 100644 --- a/kjvstudy_org/templates/family_tree.html +++ b/kjvstudy_org/templates/family_tree.html @@ -197,6 +197,208 @@ display: none; } + .comprehensive-person-view { + background: var(--card-background); + border-radius: 12px; + padding: 2rem; + border: 1px solid var(--border-color); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + margin-top: 2rem; + display: block; + } + + .person-header { + display: flex; + align-items: center; + gap: 1.5rem; + margin-bottom: 2rem; + padding-bottom: 1.5rem; + border-bottom: 2px solid var(--border-color); + } + + .person-avatar { + width: 100px; + height: 100px; + border-radius: 50%; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + display: flex; + align-items: center; + justify-content: center; + font-size: 2.5rem; + color: white; + flex-shrink: 0; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + border: 3px solid rgba(255, 255, 255, 0.2); + } + + .person-avatar.male { + background: linear-gradient(135deg, #74b9ff, #0984e3); + } + + .person-avatar.female { + background: linear-gradient(135deg, #fd79a8, #e84393); + } + + .person-basic-info h1 { + font-size: 2.2rem; + margin: 0 0 0.5rem 0; + color: var(--text-color); + font-family: 'Crimson Text', serif; + } + + .person-basic-info .person-title { + font-size: 1.1rem; + color: var(--text-muted); + margin-bottom: 0.5rem; + font-style: italic; + } + + .person-basic-info .person-lifespan { + font-size: 1rem; + color: var(--primary-color); + font-weight: 600; + } + + .data-sections { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + } + + .data-section { + background: var(--background-color); + border-radius: 8px; + padding: 1.5rem; + border: 1px solid var(--border-color); + } + + .data-section h3 { + font-size: 1.3rem; + margin: 0 0 1rem 0; + color: var(--primary-color); + font-family: 'Crimson Text', serif; + display: flex; + align-items: center; + gap: 0.5rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--border-color); + } + + .gedcom-field { + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--border-color); + } + + .gedcom-field:last-child { + border-bottom: none; + margin-bottom: 0; + } + + .field-label { + font-weight: 600; + color: var(--text-color); + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 0.25rem; + } + + .field-value { + color: var(--text-muted); + line-height: 1.5; + } + + .scripture-references { + grid-column: 1 / -1; + } + + .verse-reference-item { + background: var(--card-background); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 1rem; + margin-bottom: 1rem; + border-left: 4px solid var(--primary-color); + } + + .verse-ref { + font-weight: 600; + color: var(--primary-color); + margin-bottom: 0.5rem; + } + + .verse-ref a { + color: var(--primary-color); + text-decoration: none; + } + + .verse-ref a:hover { + text-decoration: underline; + } + + .verse-text { + font-family: 'Crimson Text', serif; + font-style: italic; + line-height: 1.6; + color: var(--text-color); + } + + .family-connections { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + margin-top: 1rem; + } + + .connection-card { + background: var(--card-background); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 1rem; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + } + + .connection-card:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + .connection-card .connection-name { + font-weight: 600; + color: var(--text-color); + margin-bottom: 0.25rem; + } + + .connection-card .connection-type { + font-size: 0.8rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + @media (max-width: 768px) { + .data-sections { + grid-template-columns: 1fr; + } + + .person-header { + flex-direction: column; + text-align: center; + } + + .person-basic-info h1 { + font-size: 1.8rem; + } + + .family-connections { + grid-template-columns: 1fr; + } + } + .d3-tree-container { width: 100%; height: 600px; @@ -702,8 +904,8 @@
- - + +
@@ -731,6 +933,54 @@
+ +
+
+
๐Ÿ‘ค
+
+

Select a Person

+
+
+
+
+ +
+
+

๐Ÿ“‹ Personal Information

+
+
+ +
+

๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Family Relationships

+
+
+ +
+

๐Ÿ“… Life Events

+
+
+ +
+

๐Ÿท๏ธ GEDCOM Details

+
+
+ +
+

๐Ÿงฌ Genealogy Path

+
+
+ +
+

๐Ÿ“ˆ Statistical Info

+
+
+ +
+

๐Ÿ“– Scripture References

+
+
+
+
@@ -928,6 +1178,9 @@ updateD3Tree(person, personId); } + // Update comprehensive person view + updateComprehensiveView(person, personId); + // Update current person info document.getElementById('current-name').textContent = person.name; document.getElementById('current-title').textContent = person.title || 'Biblical Figure'; @@ -955,6 +1208,416 @@ // Update D3 tree view updateD3Tree(person, personId); + + // Update comprehensive person view + updateComprehensiveView(person, personId); + } + + function updateComprehensiveView(person, personId) { + const compView = document.getElementById('comprehensive-person-view'); + const avatar = document.getElementById('person-avatar'); + const name = document.getElementById('comp-person-name'); + const title = document.getElementById('comp-person-title'); + const lifespan = document.getElementById('comp-person-lifespan'); + + // Ensure the comprehensive view is visible + compView.style.display = 'block'; + + // Update avatar + const gender = determineGender(person); + avatar.className = `person-avatar ${gender}`; + avatar.textContent = gender === 'female' ? '๐Ÿ‘ฉ' : '๐Ÿ‘จ'; + + // Update basic info + name.textContent = person.name || 'Unknown'; + title.textContent = person.title || 'Biblical Figure'; + + let lifespanText = ''; + if (person.birth_year && person.birth_year !== 'Unknown') { + lifespanText = person.birth_year; + } + if (person.death_year && person.death_year !== 'Unknown') { + lifespanText += ` - ${person.death_year}`; + } + if (person.age_at_death && person.age_at_death !== 'Unknown') { + lifespanText += ` (${person.age_at_death})`; + } + lifespan.textContent = lifespanText || 'Lifespan unknown'; + + // Update personal information + updatePersonalInfo(person); + + // Update family relationships + updateFamilyRelationships(person, personId); + + // Update life events + updateLifeEvents(person); + + // Update GEDCOM technical details + updateGedcomDetails(person, personId); + + // Update genealogy path + updateGenealogyPath(person, personId); + + // Update statistical information + updateStatisticalInfo(person, personId); + + // Update scripture references + updateScriptureReferences(person); + } + + function updatePersonalInfo(person) { + const container = document.getElementById('personal-info-fields'); + container.innerHTML = ''; + + const fields = [ + { label: 'Full Name', value: person.name }, + { label: 'Gender', value: determineGender(person) }, + { label: 'Title/Occupation', value: person.title }, + { label: 'Description', value: person.description }, + { label: 'Birth Year', value: person.birth_year }, + { label: 'Death Year', value: person.death_year }, + { label: 'Age at Death', value: person.age_at_death } + ]; + + fields.forEach(field => { + if (field.value && field.value !== 'Unknown') { + const fieldDiv = document.createElement('div'); + fieldDiv.className = 'gedcom-field'; + fieldDiv.innerHTML = ` +
${field.label}
+
${field.value}
+ `; + container.appendChild(fieldDiv); + } + }); + + if (container.children.length === 0) { + container.innerHTML = '
No personal information available
'; + } + } + + function updateFamilyRelationships(person, personId) { + const container = document.getElementById('family-relationships'); + container.innerHTML = ''; + + const relationships = []; + + // Parents + if (person.parents && person.parents.length > 0) { + person.parents.forEach(parentId => { + const parent = familyData[parentId]; + if (parent) { + relationships.push({ + name: parent.name, + type: 'Parent', + id: parentId + }); + } + }); + } + + // Spouse + if (person.spouse) { + const spouseId = Object.keys(familyData).find(id => familyData[id].name === person.spouse); + if (spouseId) { + relationships.push({ + name: person.spouse, + type: 'Spouse', + id: spouseId + }); + } + } + + // Children + if (person.children && person.children.length > 0) { + person.children.forEach(childId => { + const child = familyData[childId]; + if (child) { + relationships.push({ + name: child.name, + type: 'Child', + id: childId + }); + } + }); + } + + if (relationships.length > 0) { + const connectionsDiv = document.createElement('div'); + connectionsDiv.className = 'family-connections'; + + relationships.forEach(rel => { + const card = document.createElement('div'); + card.className = 'connection-card'; + card.onclick = () => selectPerson(rel.id); + card.innerHTML = ` +
${rel.name}
+
${rel.type}
+ `; + connectionsDiv.appendChild(card); + }); + + container.appendChild(connectionsDiv); + } else { + container.innerHTML = '
No family relationships recorded
'; + } + } + + function updateLifeEvents(person) { + const container = document.getElementById('life-events'); + container.innerHTML = ''; + + const events = []; + + if (person.birth_year && person.birth_year !== 'Unknown') { + events.push({ + label: 'Birth', + value: person.birth_year, + icon: '๐ŸŽ‚' + }); + } + + if (person.death_year && person.death_year !== 'Unknown') { + events.push({ + label: 'Death', + value: person.death_year, + icon: 'โšฐ๏ธ' + }); + } + + if (person.spouse) { + events.push({ + label: 'Marriage', + value: `Married to ${person.spouse}`, + icon: '๐Ÿ’’' + }); + } + + if (person.children && person.children.length > 0) { + events.push({ + label: 'Children', + value: `${person.children.length} children`, + icon: '๐Ÿ‘ถ' + }); + } + + if (events.length > 0) { + events.forEach(event => { + const eventDiv = document.createElement('div'); + eventDiv.className = 'gedcom-field'; + eventDiv.innerHTML = ` +
${event.icon} ${event.label}
+
${event.value}
+ `; + container.appendChild(eventDiv); + }); + } else { + container.innerHTML = '
No life events recorded
'; + } + } + + function updateGedcomDetails(person, personId) { + const container = document.getElementById('gedcom-technical'); + container.innerHTML = ''; + + const details = [ + { label: 'Person ID', value: personId, icon: '๐Ÿ†”' }, + { label: 'Record Type', value: 'GEDCOM Individual', icon: '๐Ÿ“‹' }, + { label: 'Lineage', value: determineLineage(person), icon: '๐ŸŒณ' }, + { label: 'Generation', value: getGenerationInfo(person), icon: '๐Ÿ“Š' }, + { label: 'Gender', value: determineGender(person), icon: determineGender(person) === 'female' ? '๐Ÿ‘ฉ' : '๐Ÿ‘จ' }, + { label: 'Data Source', value: 'Biblical GEDCOM Database', icon: '๐Ÿ’พ' } + ]; + + details.forEach(detail => { + const detailDiv = document.createElement('div'); + detailDiv.className = 'gedcom-field'; + detailDiv.innerHTML = ` +
${detail.icon} ${detail.label}
+
${detail.value}
+ `; + container.appendChild(detailDiv); + }); + } + + function updateScriptureReferences(person) { + const container = document.getElementById('scripture-references'); + container.innerHTML = ''; + + if (person.verses && person.verses.length > 0) { + person.verses.forEach(verse => { + const verseDiv = document.createElement('div'); + verseDiv.className = 'verse-reference-item'; + + // Parse reference for linking + const refParts = verse.reference.split(' '); + let book, chapter, verseLink; + + if (refParts.length >= 2) { + if (refParts[0] in ['1', '2'] && refParts.length >= 3) { + book = refParts[0] + ' ' + refParts[1]; + chapter = refParts[2].split(':')[0]; + } else { + book = refParts[0]; + chapter = refParts[1].split(':')[0]; + } + verseLink = `/book/${book}/chapter/${chapter}`; + } + + verseDiv.innerHTML = ` +
+ ${verseLink ? `${verse.reference}` : verse.reference} +
+
"${verse.text}"
+ `; + container.appendChild(verseDiv); + }); + } else { + container.innerHTML = '
No scripture references available
'; + } + } + + function updateGenealogyPath(person, personId) { + const container = document.getElementById('genealogy-path'); + container.innerHTML = ''; + + // Build path from Adam to current person + const path = buildGenealogyPath(personId); + + if (path.length > 0) { + const pathDiv = document.createElement('div'); + pathDiv.className = 'genealogy-path-display'; + pathDiv.style.cssText = ` + padding: 1rem; + background: var(--card-background); + border-radius: 6px; + border: 1px solid var(--border-color); + font-family: 'Crimson Text', serif; + line-height: 2; + `; + + const pathText = path.map((ancestor, index) => { + return `${ancestor.name}`; + }).join(' โ†’ '); + + pathDiv.innerHTML = ` +
๐Ÿ“ Lineage from Adam
+
${pathText}
+
+ ${path.length} generations from Adam +
+ `; + container.appendChild(pathDiv); + } else { + container.innerHTML = '
Genealogy path not available
'; + } + } + + function updateStatisticalInfo(person, personId) { + const container = document.getElementById('statistical-info'); + container.innerHTML = ''; + + const stats = []; + + // Count descendants + const descendantCount = countDescendants(personId); + stats.push({ label: 'Total Descendants', value: descendantCount, icon: '๐Ÿ‘ถ' }); + + // Count ancestors + const ancestorCount = countAncestors(personId); + stats.push({ label: 'Known Ancestors', value: ancestorCount, icon: '๐Ÿ‘ด' }); + + // Siblings count + const siblingCount = countSiblings(person); + stats.push({ label: 'Siblings', value: siblingCount, icon: '๐Ÿ‘ซ' }); + + // Children count + const childrenCount = person.children ? person.children.length : 0; + stats.push({ label: 'Children', value: childrenCount, icon: '๐Ÿ‘ถ' }); + + stats.forEach(stat => { + const statDiv = document.createElement('div'); + statDiv.className = 'gedcom-field'; + statDiv.innerHTML = ` +
${stat.icon} ${stat.label}
+
${stat.value}
+ `; + container.appendChild(statDiv); + }); + } + + function buildGenealogyPath(personId, visited = new Set()) { + if (visited.has(personId)) return []; + visited.add(personId); + + const person = familyData[personId]; + if (!person) return []; + + // If this is Adam, start the path + if (person.name.toLowerCase() === 'adam') { + return [{ id: personId, name: person.name }]; + } + + // Try to find path through parents + if (person.parents && person.parents.length > 0) { + for (const parentId of person.parents) { + const parentPath = buildGenealogyPath(parentId, visited); + if (parentPath.length > 0) { + return [...parentPath, { id: personId, name: person.name }]; + } + } + } + + return []; + } + + function countDescendants(personId, visited = new Set()) { + if (visited.has(personId)) return 0; + visited.add(personId); + + const person = familyData[personId]; + if (!person || !person.children) return 0; + + let count = person.children.length; + person.children.forEach(childId => { + count += countDescendants(childId, visited); + }); + + return count; + } + + function countAncestors(personId, visited = new Set()) { + if (visited.has(personId)) return 0; + visited.add(personId); + + const person = familyData[personId]; + if (!person || !person.parents) return 0; + + let count = person.parents.length; + person.parents.forEach(parentId => { + count += countAncestors(parentId, visited); + }); + + return count; + } + + function countSiblings(person) { + if (!person.parents || person.parents.length === 0) return 0; + + let siblings = new Set(); + person.parents.forEach(parentId => { + const parent = familyData[parentId]; + if (parent && parent.children) { + parent.children.forEach(childId => { + if (childId !== person.id) { + siblings.add(childId); + } + }); + } + }); + + return siblings.size; } function showParents(person) {