Your Living Family Story

Connect your family,
one branch at a time

Add relatives and invite them via WhatsApp. Each confirmation grows your tree and brings your family closer — wherever they are in the world.

7
Family Members
4
Confirmed
2
Pending
3
Generations
AN
Antonio Greco
Your Grandfather
Confirmed
LG
Lucia Greco
Your Grandmother
Confirmed
JR
James Rivera
Your Grandfather
Pending
MR
Marie Rivera
Your Grandmother
Invited
MG
Marco Greco
Your Father
Confirmed
SR
Sofia Rivera
Your Mother
Confirmed
You
Marc Greco
You
Owner
AG
Anna Greco
Your Sister
Pending
gp1: { name: 'Antonio Greco', relation: 'Your Grandfather', initials: 'AN', status: 'confirmed', phone: '+39 02 1234 5678', dob: '1938-03-15', bg: 'rgba(122,158,126,0.15)', color: '#4A7A4E' }, gp2: { name: 'Lucia Greco', relation: 'Your Grandmother', initials: 'LG', status: 'confirmed', phone: '+39 02 1234 5679', dob: '1942-07-22', bg: 'rgba(212,165,165,0.2)', color: '#A05050' }, gp3: { name: 'James Rivera', relation: 'Your Grandfather', initials: 'JR', status: 'pending', phone: '+1 305 555 0101', dob: '1940-11-08', bg: 'rgba(200,146,42,0.12)', color: '#9A6B0F' }, gp4: { name: 'Marie Rivera', relation: 'Your Grandmother', initials: 'MR', status: 'invited', phone: '+1 305 555 0102', dob: '1944-05-30', bg: 'rgba(212,165,165,0.15)', color: '#A05050' }, dad: { name: 'Marco Greco', relation: 'Your Father', initials: 'MG', status: 'confirmed', phone: '+1 212 555 0201', dob: '1965-08-12', bg: 'rgba(61,43,31,0.1)', color: 'var(--bark)' }, mom: { name: 'Sofia Rivera', relation: 'Your Mother', initials: 'SR', status: 'confirmed', phone: '+1 212 555 0202', dob: '1968-02-28', bg: 'rgba(212,165,165,0.2)', color: '#A05050' }, self: { name: 'Marc Greco', relation: 'You', initials: 'MG', status: 'self', phone: '+1 917 555 0001', dob: '1990-06-15', bg: 'var(--bark)', color: 'var(--cream)' }, sib1: { name: 'Anna Greco', relation: 'Your Sister', initials: 'AG', status: 'pending', phone: '+1 917 555 0002', dob: '1993-09-04', bg: 'rgba(200,146,42,0.12)', color: '#9A6B0F' }, }; const statusConfig = { confirmed: { label: 'Confirmed', color: 'var(--sage)', badgeClass: 'badge-confirmed' }, pending: { label: 'Pending Confirmation', color: 'var(--gold)', badgeClass: 'badge-pending' }, invited: { label: 'Invited', color: '#A05050', badgeClass: 'badge-invited' }, self: { label: 'Tree Owner', color: 'var(--bark)', badgeClass: '' }, }; let nextId = 10; function renderMemberList() { const list = document.getElementById('memberList'); list.innerHTML = ''; Object.entries(members).forEach(([id, m]) => { const s = statusConfig[m.status]; list.innerHTML += `
${m.initials}
${m.name}
${m.relation}
${s.label.split(' ')[0]}
`; }); } function selectNode(id) { const m = members[id]; if (!m) return; const s = statusConfig[m.status]; // Update selected state document.querySelectorAll('.tree-node').forEach(n => n.classList.remove('selected')); document.querySelectorAll('.member-card').forEach(n => n.classList.remove('active')); const node = document.getElementById('node-' + id); if (node) node.classList.add('selected'); const mc = document.getElementById('mc-' + id); if (mc) mc.classList.add('active'); // Fill panel document.getElementById('panelAvatar').style.cssText = `background:${m.bg}; color:${m.color}; width:64px; height:64px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-family:'Cormorant Garamond',serif; font-size:1.6rem; font-weight:600; flex-shrink:0;`; document.getElementById('panelAvatar').textContent = m.initials; document.getElementById('panelName').textContent = m.name; document.getElementById('panelRelation').textContent = m.relation; document.getElementById('panelBadge').innerHTML = `${s.label}`; let bodyHtml = `

Details

Phone ${m.phone}
Born ${m.dob ? new Date(m.dob).toLocaleDateString('en-US', {year:'numeric',month:'long',day:'numeric'}) : 'Unknown'}
Status ${s.label}
`; if (m.status === 'pending' || m.status === 'invited') { const msg = generateWhatsAppMsg(m.name, m.relation); bodyHtml += `

WhatsApp Invitation

${m.status === 'pending' ? 'Waiting for confirmation. You can resend the invite below.' : 'Invitation sent. Still waiting for a response.'}

${msg}
`; } if (m.status === 'confirmed' && id !== 'self') { bodyHtml += `
✓ Confirmed Member
${m.name} has confirmed their place in your family tree. They can now add their own relatives to grow the tree further.
`; } document.getElementById('panelBody').innerHTML = bodyHtml; document.getElementById('detailPanel').classList.add('open'); } function closePanel() { document.getElementById('detailPanel').classList.remove('open'); document.querySelectorAll('.tree-node').forEach(n => n.classList.remove('selected')); document.querySelectorAll('.member-card').forEach(n => n.classList.remove('active')); } function generateWhatsAppMsg(name, relation) { const fname = name.split(' ')[0]; return `Hi ${fname}! 👋 Marc Greco is building a KinTree family tree and has added you as his ${relation.toLowerCase().replace('your ', '')}. Tap the link below to confirm your connection and see your family tree grow! 🌳\n\nhttps://kintree.ai/join/abc123`; } function openAddModal() { document.getElementById('addModal').classList.add('open'); } function closeAddModal() { document.getElementById('addModal').classList.remove('open'); document.getElementById('modalName').value = ''; document.getElementById('modalRelation').value = ''; document.getElementById('modalPhone').value = ''; document.getElementById('modalDob').value = ''; } function addMember() { const name = document.getElementById('modalName').value.trim(); const relation = document.getElementById('modalRelation').value; const phone = document.getElementById('modalPhone').value.trim(); if (!name || !relation || !phone) { showToast('⚠️ Please fill in all required fields'); return; } const initials = name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase(); const colors = [ { bg: 'rgba(122,158,126,0.15)', color: '#4A7A4E' }, { bg: 'rgba(200,146,42,0.12)', color: '#9A6B0F' }, { bg: 'rgba(212,165,165,0.2)', color: '#A05050' }, { bg: 'rgba(61,43,31,0.1)', color: 'var(--bark)' }, ]; const c = colors[nextId % colors.length]; const id = 'member' + nextId++; members[id] = { name, relation: 'Your ' + relation, initials, status: 'pending', phone, dob: document.getElementById('modalDob').value, bg: c.bg, color: c.color }; // Add node to tree const gen3 = document.getElementById('gen3'); const addBtn = gen3.querySelector('.add-node'); const newNode = document.createElement('div'); newNode.className = 'tree-node'; newNode.id = 'node-' + id; newNode.style.animationDelay = '0.1s'; newNode.onclick = () => selectNode(id); newNode.innerHTML = `
${initials}
${name}
Your ${relation}
Pending
`; const spacer = document.createElement('div'); spacer.style.width = '24px'; gen3.insertBefore(spacer, addBtn); gen3.insertBefore(newNode, addBtn); // Update stats updateStats(); renderMemberList(); closeAddModal(); // Show success const msg = generateWhatsAppMsg(name, 'Your ' + relation); document.getElementById('successMsg').textContent = `A WhatsApp invitation has been sent to ${name} at ${phone}. Once they confirm, they'll appear on your tree!`; document.getElementById('successPreview').textContent = msg; document.getElementById('successModal').classList.add('open'); } function closeSuccessModal() { document.getElementById('successModal').classList.remove('open'); } function quickAdd() { const name = document.getElementById('quickName').value.trim(); const relation = document.getElementById('quickRelation').value; const phone = document.getElementById('quickPhone').value.trim(); if (!name || !relation || !phone) { showToast('⚠️ Please fill in name, relationship, and phone'); return; } document.getElementById('modalName').value = name; document.getElementById('modalRelation').value = relation; document.getElementById('modalPhone').value = phone; document.getElementById('quickName').value = ''; document.getElementById('quickRelation').value = ''; document.getElementById('quickPhone').value = ''; addMember(); } function resendInvite(id) { const m = members[id]; showToast(`📱 Invite resent to ${m.name}`); } function shareTree() { navigator.clipboard?.writeText('https://kintree.ai/tree/marc-greco-abc123').catch(() => {}); showToast('🔗 Tree link copied to clipboard!'); } function updateStats() { const all = Object.values(members); document.getElementById('statTotal').textContent = all.length; document.getElementById('statConfirmed').textContent = all.filter(m => m.status === 'confirmed' || m.status === 'self').length; document.getElementById('statPending').textContent = all.filter(m => m.status === 'pending' || m.status === 'invited').length; } function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3000); } // Close modal on overlay click document.getElementById('addModal').addEventListener('click', function(e) { if (e.target === this) closeAddModal(); }); document.getElementById('successModal').addEventListener('click', function(e) { if (e.target === this) closeSuccessModal(); }); // Close panel on escape document.addEventListener('keydown', e => { if (e.key === 'Escape') { closePanel(); closeAddModal(); closeSuccessModal(); } }); // Init renderMemberList(); updateStats();