familytree

A self-hosted family tree builder.
Log | Files | Refs | README

index.html (20170B)


      1 <!DOCTYPE html>
      2 <html lang="en">
      3 <head>
      4   <meta charset="utf-8">
      5   <meta name="viewport" content="width=device-width, initial-scale=1.0">
      6   <link rel="preconnect" href="https://fonts.googleapis.com">
      7   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
      8   <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=EB+Garamond:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
      9   <link rel="stylesheet" href="/static/style.css">
     10   <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🌳</text></svg>">
     11   <title>Family Tree</title>
     12 </head>
     13 <body>
     14 
     15   <!-- Topbar -->
     16   <nav class="topbar">
     17     <div class="topbar-inner">
     18       <div class="topbar-left">
     19         <span class="topbar-name">Family Tree</span>
     20         <button class="btn-sidebar-toggle" id="sidebarToggle" data-i18n-title="toggle-sidebar" title="Toggle sidebar">
     21           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
     22             <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M3.75 17.25h16.5"/>
     23           </svg>
     24         </button>
     25       </div>
     26       <div class="topbar-right">
     27         <span class="topbar-username" id="topbarUsername"></span>
     28         <a href="https://auth.piribauer.ch/if/user/" class="nav-link" target="_blank" rel="noopener" data-i18n="account">Account</a>
     29         <a href="/outpost.goauthentik.io/sign_out" class="nav-link signout" data-i18n="sign-out">Sign out</a>
     30         <div class="lang-switcher">
     31           <button class="lang-opt lang-active" data-lang="en" onclick="setLang('en')">EN</button>
     32           <button class="lang-opt" data-lang="de" onclick="setLang('de')">DE</button>
     33         </div>
     34         <a href="https://hub.piribauer.ch" class="topbar-home" aria-label="Hub">
     35           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
     36             <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/>
     37           </svg>
     38         </a>
     39       </div>
     40     </div>
     41   </nav>
     42 
     43   <!-- App body -->
     44   <div class="app-body">
     45 
     46     <!-- Sidebar -->
     47     <aside class="sidebar" id="sidebar">
     48       <div class="sidebar-header" data-i18n="sidebar-trees">Trees</div>
     49       <div class="tree-list" id="treeList"></div>
     50       <div class="sidebar-actions">
     51         <button class="btn-new-tree" id="btnNewTree">
     52           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
     53             <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
     54           </svg>
     55           <span data-i18n="new-tree">New tree</span>
     56         </button>
     57       </div>
     58     </aside>
     59 
     60     <!-- Canvas area -->
     61     <main class="canvas-area" id="canvasArea">
     62 
     63       <!-- Empty state -->
     64       <div class="empty-state" id="emptyState">
     65         <div class="empty-icon">
     66           <svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.5">
     67             <circle cx="24" cy="10" r="4"/>
     68             <line x1="24" y1="14" x2="24" y2="22"/>
     69             <line x1="24" y1="22" x2="12" y2="30"/>
     70             <line x1="24" y1="22" x2="36" y2="30"/>
     71             <circle cx="12" cy="34" r="4"/>
     72             <circle cx="36" cy="34" r="4"/>
     73           </svg>
     74         </div>
     75         <p class="empty-title" data-i18n="empty-title">No tree selected</p>
     76         <p class="empty-sub" data-i18n="empty-sub">Create a tree or select one from the sidebar</p>
     77         <button class="btn btn-primary" id="emptyNewTree" data-i18n="create-tree-btn">Create a tree</button>
     78       </div>
     79 
     80       <!-- Canvas toolbar -->
     81       <div class="canvas-toolbar" id="canvasToolbar" style="display:none">
     82         <button class="toolbar-btn" id="btnZoomIn" data-i18n-title="zoom-in" title="Zoom in">
     83           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
     84             <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35M11 8v6M8 11h6"/>
     85           </svg>
     86         </button>
     87         <button class="toolbar-btn" id="btnZoomOut" data-i18n-title="zoom-out" title="Zoom out">
     88           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
     89             <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35M8 11h6"/>
     90           </svg>
     91         </button>
     92         <button class="toolbar-btn" id="btnFit" data-i18n-title="fit-screen" title="Fit to screen">
     93           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
     94             <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9M20.25 20.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/>
     95           </svg>
     96         </button>
     97         <div class="toolbar-sep"></div>
     98         <button class="toolbar-btn" id="btnDownloadPDF" data-i18n-title="download-pdf" title="Download PDF">
     99           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    100             <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12M12 16.5V3"/>
    101           </svg>
    102           <span data-i18n="download-pdf">PDF</span>
    103         </button>
    104         <div class="toolbar-sep"></div>
    105         <button class="toolbar-btn toolbar-btn-primary" id="btnAddPerson" data-i18n-title="add-person" title="Add person">
    106           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    107             <path stroke-linecap="round" stroke-linejoin="round" d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"/>
    108           </svg>
    109           <span data-i18n="add-person">Add person</span>
    110         </button>
    111       </div>
    112 
    113       <!-- SVG canvas -->
    114       <svg id="treeCanvas" style="display:none"></svg>
    115 
    116     </main>
    117 
    118     <!-- Detail panel -->
    119     <aside class="detail-panel" id="detailPanel">
    120       <div class="detail-header">
    121         <span class="detail-name" id="detailName"></span>
    122         <div class="detail-header-actions">
    123           <button class="btn-icon" id="btnEditPerson" data-i18n-title="btn-edit" title="Edit">
    124             <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    125               <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/>
    126             </svg>
    127           </button>
    128           <button class="btn-icon danger" id="btnDeletePerson" data-i18n-title="btn-delete" title="Delete">
    129             <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    130               <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>
    131             </svg>
    132           </button>
    133           <button class="btn-icon" id="btnCloseDetail" data-i18n-title="btn-close" title="Close">
    134             <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    135               <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
    136             </svg>
    137           </button>
    138         </div>
    139       </div>
    140       <div class="detail-body" id="detailBody"></div>
    141     </aside>
    142 
    143   </div>
    144 
    145   <!-- Context menu -->
    146   <div class="ctx-menu" id="ctxMenu">
    147     <div class="ctx-item" id="ctxRename">
    148       <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:14px;height:14px">
    149         <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487z"/>
    150       </svg>
    151       <span data-i18n="ctx-rename">Rename</span>
    152     </div>
    153     <div class="ctx-item" id="ctxMembers">
    154       <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:14px;height:14px">
    155         <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
    156       </svg>
    157       <span data-i18n="ctx-members">Manage members</span>
    158     </div>
    159     <div class="ctx-item" id="ctxLeave">
    160       <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:14px;height:14px">
    161         <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"/>
    162       </svg>
    163       <span data-i18n="ctx-leave">Leave tree</span>
    164     </div>
    165     <div class="ctx-item danger" id="ctxDelete">
    166       <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:14px;height:14px">
    167         <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>
    168       </svg>
    169       <span data-i18n="ctx-delete">Delete tree</span>
    170     </div>
    171   </div>
    172 
    173   <!-- ── Modals ── -->
    174 
    175   <!-- New/rename tree -->
    176   <div class="modal-overlay" id="modalTree">
    177     <div class="modal">
    178       <h2 id="modalTreeTitle" data-i18n="new-tree-title">New tree</h2>
    179       <div class="form-group">
    180         <label class="form-label" data-i18n="form-name">Name</label>
    181         <input class="form-input" id="treeNameInput" type="text" data-i18n-placeholder="tree-name-ph" placeholder="e.g. Smith family">
    182       </div>
    183       <div class="modal-actions">
    184         <button class="btn" id="btnTreeCancel" data-i18n="cancel">Cancel</button>
    185         <button class="btn btn-primary" id="btnTreeSave" data-i18n="create">Create</button>
    186       </div>
    187     </div>
    188   </div>
    189 
    190   <!-- Add/edit person -->
    191   <div class="modal-overlay" id="modalPerson">
    192     <div class="modal modal-wide">
    193       <h2 id="modalPersonTitle" data-i18n="add-person-title">Add person</h2>
    194       <div class="form-row">
    195         <div class="form-group">
    196           <label class="form-label" data-i18n="form-first-name">First name</label>
    197           <input class="form-input" id="personFirstName" type="text" data-i18n-placeholder="form-first-name" placeholder="First name">
    198         </div>
    199         <div class="form-group">
    200           <label class="form-label" data-i18n="form-last-name">Last name</label>
    201           <input class="form-input" id="personLastName" type="text" data-i18n-placeholder="form-last-name" placeholder="Last name">
    202         </div>
    203       </div>
    204       <div class="form-row">
    205         <div class="form-group">
    206           <label class="form-label" data-i18n="form-birth-date">Birth date</label>
    207           <input class="form-input" id="personBirthDate" type="text" placeholder="DD.MM.YYYY">
    208         </div>
    209         <div class="form-group">
    210           <label class="form-label" data-i18n="form-death-date">Death date</label>
    211           <input class="form-input" id="personDeathDate" type="text" placeholder="DD.MM.YYYY">
    212         </div>
    213       </div>
    214       <div class="form-row">
    215         <div class="form-group">
    216           <label class="form-label" data-i18n="form-birth-place">Birth place</label>
    217           <input class="form-input" id="personBirthPlace" type="text" placeholder="City, Country">
    218         </div>
    219         <div class="form-group">
    220           <label class="form-label" data-i18n="form-burial-place">Burial place</label>
    221           <input class="form-input" id="personBurialPlace" type="text" placeholder="Cemetery, City">
    222         </div>
    223       </div>
    224       <div class="form-group">
    225         <label class="form-label" data-i18n="form-biography">Biography</label>
    226         <textarea class="form-input" id="personBio" rows="3" data-i18n-placeholder="bio-ph" placeholder="A short note…"></textarea>
    227       </div>
    228       <div class="modal-actions">
    229         <button class="btn" id="btnPersonCancel" data-i18n="cancel">Cancel</button>
    230         <button class="btn btn-primary" id="btnPersonSave" data-i18n="save">Save</button>
    231       </div>
    232     </div>
    233   </div>
    234 
    235   <!-- Connect new person -->
    236   <div class="modal-overlay" id="modalConnect">
    237     <div class="modal">
    238       <h2>Connect <span id="connectPersonName" style="color:var(--lightblue)"></span></h2>
    239       <p style="font-size:0.875rem;color:var(--snow1);opacity:0.6;margin-bottom:1.25rem" data-i18n="connect-how">
    240         How would you like to add this person to the tree?
    241       </p>
    242       <div class="connect-options">
    243         <button class="btn connect-btn" id="connectAsPartner">
    244           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"/></svg>
    245           <span data-i18n="connect-partner">Partner of someone</span>
    246         </button>
    247         <button class="btn connect-btn" id="connectAsChild">
    248           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3"/></svg>
    249           <span data-i18n="connect-child">Child of someone</span>
    250         </button>
    251         <button class="btn connect-btn" id="connectAsParent">
    252           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18"/></svg>
    253           <span data-i18n="connect-parent">Parent of someone</span>
    254         </button>
    255         <button class="btn connect-btn" id="connectAsSibling">
    256           <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"/></svg>
    257           <span data-i18n="connect-sibling">Sibling of someone</span>
    258         </button>
    259       </div>
    260       <div class="modal-actions">
    261         <button class="btn" id="btnConnectSkip" data-i18n="skip-for-now">Skip for now</button>
    262       </div>
    263     </div>
    264   </div>
    265 
    266   <!-- Add/edit partnership -->
    267   <div class="modal-overlay" id="modalPartnership">
    268     <div class="modal">
    269       <h2 id="modalPartnershipTitle" data-i18n="add-partnership-title">Add partnership</h2>
    270       <div class="form-group">
    271         <label class="form-label" data-i18n="form-person1">Person 1</label>
    272         <select class="form-input" id="partnerPerson1"></select>
    273       </div>
    274       <div class="form-group">
    275         <label class="form-label" data-i18n="form-person2">Person 2</label>
    276         <select class="form-input" id="partnerPerson2"></select>
    277       </div>
    278       <div class="form-group">
    279         <label class="form-label" data-i18n="form-type">Type</label>
    280         <select class="form-input" id="partnerType">
    281           <option value="married" data-i18n="type-married">Married</option>
    282           <option value="partnered" selected data-i18n="type-partnered">Partnered</option>
    283           <option value="separated" data-i18n="type-separated">Separated</option>
    284         </select>
    285       </div>
    286       <div class="form-row">
    287         <div class="form-group">
    288           <label class="form-label" data-i18n="form-start-date">Start date</label>
    289           <input class="form-input" id="partnerStart" type="text" placeholder="YYYY-MM-DD">
    290         </div>
    291         <div class="form-group">
    292           <label class="form-label" data-i18n="form-end-date">End date</label>
    293           <input class="form-input" id="partnerEnd" type="text" placeholder="YYYY-MM-DD">
    294         </div>
    295       </div>
    296       <div class="modal-actions">
    297         <button class="btn" id="btnPartnerCancel" data-i18n="cancel">Cancel</button>
    298         <button class="btn btn-primary" id="btnPartnerSave" data-i18n="save">Save</button>
    299       </div>
    300     </div>
    301   </div>
    302 
    303   <!-- Add parent link -->
    304   <div class="modal-overlay" id="modalParentLink">
    305     <div class="modal">
    306       <h2 data-i18n="add-parent-link-title">Add parent link</h2>
    307       <div class="form-group">
    308         <label class="form-label" data-i18n="form-child">Child</label>
    309         <select class="form-input" id="linkChild"></select>
    310       </div>
    311       <div class="form-group">
    312         <label class="form-label" data-i18n="form-link-via">Link via</label>
    313         <select class="form-input" id="linkViaType" onchange="app.onLinkViaTypeChange()">
    314           <option value="partnership" data-i18n="link-partnership">Partnership (two parents)</option>
    315           <option value="single" data-i18n="link-single-parent">Single parent</option>
    316         </select>
    317       </div>
    318       <div class="form-group" id="linkPartnershipGroup">
    319         <label class="form-label" data-i18n="form-partnership">Partnership</label>
    320         <select class="form-input" id="linkPartnership"></select>
    321       </div>
    322       <div class="form-group" id="linkParentGroup" style="display:none">
    323         <label class="form-label" data-i18n="form-parent">Parent</label>
    324         <select class="form-input" id="linkParent"></select>
    325       </div>
    326       <div class="modal-actions">
    327         <button class="btn" id="btnLinkCancel" data-i18n="cancel">Cancel</button>
    328         <button class="btn btn-primary" id="btnLinkSave" data-i18n="save">Save</button>
    329       </div>
    330     </div>
    331   </div>
    332 
    333   <!-- Manage members -->
    334   <div class="modal-overlay" id="modalMembers">
    335     <div class="modal modal-wide">
    336       <h2 data-i18n="members-title">Members</h2>
    337       <div id="membersList"></div>
    338       <div class="members-invite-section">
    339         <p class="form-label" style="margin-bottom:0.6rem;margin-top:1.25rem" data-i18n="add-by-username">Add by username</p>
    340         <div class="invite-row">
    341           <input class="form-input" id="inviteUsername" type="text" data-i18n-placeholder="username-ph" placeholder="username">
    342           <select class="form-input" id="inviteRole" style="width:110px;flex-shrink:0">
    343             <option value="editor" data-i18n="role-editor">Editor</option>
    344             <option value="viewer" selected data-i18n="role-viewer">Viewer</option>
    345           </select>
    346           <button class="btn btn-primary" id="btnInviteSend" data-i18n="invite-btn">Add</button>
    347         </div>
    348         <div id="pendingList"></div>
    349       </div>
    350       <div class="modal-actions">
    351         <button class="btn btn-primary" id="btnMembersClose" data-i18n="done">Done</button>
    352       </div>
    353     </div>
    354   </div>
    355 
    356   <script src="/static/d3.v7.min.js"></script>
    357   <script src="/static/i18n.js"></script>
    358   <script src="/static/app.js"></script>
    359 </body>
    360 </html>