SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,657
Folders
433
Scanned Size
3.90 GB
PHP Files
988
Editable Text Files
12,915
File viewer
guarded to /htdocs
/infinity/dashboard.php
<?php declare(strict_types=1); ini_set('display_errors', isset($_GET['debug']) ? '1' : '0'); ini_set('log_errors', '1'); error_reporting(E_ALL); require __DIR__ . '/_infinity_private/runtime.php'; $db = inf_db(); $userId = inf_require_login(); $tab = (string)($_GET['tab'] ?? 'overview'); $allowedTabs = ['overview','profiles','create','uploads','living','publishing','circles','graves','visits','tributes','account']; if (!in_array($tab, $allowedTabs, true)) $tab = 'overview'; $userStmt = $db->prepare('SELECT * FROM users WHERE id = :id LIMIT 1'); $userStmt->execute(['id' => $userId]); $user = $userStmt->fetch(); if (!$user) { session_destroy(); header('Location: /infinity/login.php', true, 302); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!inf_csrf_ok()) { inf_redirect($tab, '', 'The form expired. Please try again.'); } $action = (string)($_POST['action'] ?? ''); $now = gmdate('c'); if ($action === 'create_profile') { $name = trim((string)($_POST['name'] ?? '')); $type = (string)($_POST['profile_type'] ?? 'living'); $validTypes = ['living','human_memorial','pet_memorial']; if (!in_array($type, $validTypes, true)) $type = 'living'; if ($name === '') inf_redirect('create', '', 'Enter a profile name.'); $stmt = $db->prepare( 'INSERT INTO profiles (user_id,name,profile_type,relationship,about,birth_date,memorial_date,privacy,publication_status,created_at,updated_at) VALUES (:user_id,:name,:profile_type,:relationship,:about,:birth_date,:memorial_date,:privacy,"draft",:created_at,:updated_at)' ); $stmt->execute([ 'user_id' => $userId, 'name' => $name, 'profile_type' => $type, 'relationship' => trim((string)($_POST['relationship'] ?? '')), 'about' => trim((string)($_POST['about'] ?? '')), 'birth_date' => ($_POST['birth_date'] ?? '') ?: null, 'memorial_date' => ($_POST['memorial_date'] ?? '') ?: null, 'privacy' => in_array(($_POST['privacy'] ?? ''), ['private','family','public'], true) ? $_POST['privacy'] : 'private', 'created_at' => $now, 'updated_at' => $now, ]); inf_redirect('profiles', 'Profile created.'); } if ($action === 'upload_media') { $profileId = (int)($_POST['profile_id'] ?? 0); if ($profileId < 1) $profileId = inf_auto_profile($db, $userId); $profile = inf_owned_profile($db, $userId, $profileId); if (!$profile) inf_redirect('uploads', '', 'Select one of your profiles.'); if (!isset($_FILES['media']) || !is_array($_FILES['media']['name'] ?? null)) { inf_redirect('uploads', '', 'Choose at least one photo or video.'); } $allowed = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif', 'video/mp4' => 'mp4', 'video/webm' => 'webm', 'video/quicktime' => 'mov', ]; $finfo = new finfo(FILEINFO_MIME_TYPE); $uploaded = 0; $firstImage = null; $firstVideo = null; foreach ($_FILES['media']['name'] as $i => $original) { $error = (int)($_FILES['media']['error'][$i] ?? UPLOAD_ERR_NO_FILE); if ($error === UPLOAD_ERR_NO_FILE) continue; if ($error !== UPLOAD_ERR_OK) continue; $tmp = (string)($_FILES['media']['tmp_name'][$i] ?? ''); $size = (int)($_FILES['media']['size'][$i] ?? 0); if ($tmp === '' || !is_uploaded_file($tmp) || $size < 1 || $size > INFINITY_MAX_UPLOAD) continue; $mime = (string)$finfo->file($tmp); if (!isset($allowed[$mime])) continue; $kind = str_starts_with($mime, 'image/') ? 'image' : 'video'; $storage = bin2hex(random_bytes(18)) . '.' . $allowed[$mime]; $userDir = INFINITY_UPLOAD_DIR . '/' . $userId . '/' . $profileId; if (!is_dir($userDir) && !mkdir($userDir, 0700, true) && !is_dir($userDir)) continue; $target = $userDir . '/' . $storage; if (!move_uploaded_file($tmp, $target)) continue; $insert = $db->prepare( 'INSERT INTO media (user_id,profile_id,media_kind,original_name,storage_name,mime_type,size_bytes,caption,created_at) VALUES (:user_id,:profile_id,:media_kind,:original_name,:storage_name,:mime_type,:size_bytes,:caption,:created_at)' ); $insert->execute([ 'user_id' => $userId, 'profile_id' => $profileId, 'media_kind' => $kind, 'original_name' => mb_substr((string)$original, 0, 220), 'storage_name' => $storage, 'mime_type' => $mime, 'size_bytes' => $size, 'caption' => trim((string)($_POST['caption'] ?? '')), 'created_at' => $now, ]); $mediaId = (int)$db->lastInsertId(); if ($kind === 'image' && $firstImage === null) $firstImage = $mediaId; if ($kind === 'video' && $firstVideo === null) $firstVideo = $mediaId; $uploaded++; } if ($uploaded < 1) inf_redirect('uploads', '', 'No supported media was uploaded.'); $updates = []; $params = ['id' => $profileId, 'user_id' => $userId, 'updated_at' => $now]; if (empty($profile['avatar_media_id']) && $firstImage) { $updates[] = 'avatar_media_id = :avatar'; $params['avatar'] = $firstImage; } if (empty($profile['cover_media_id'])) { if ($firstVideo) { $updates[] = 'cover_media_id = :cover'; $params['cover'] = $firstVideo; } elseif ($firstImage) { $updates[] = 'cover_media_id = :cover'; $params['cover'] = $firstImage; } } $updates[] = 'updated_at = :updated_at'; $db->prepare('UPDATE profiles SET ' . implode(', ', $updates) . ' WHERE id = :id AND user_id = :user_id') ->execute($params); inf_redirect('profiles', $uploaded . ' media item(s) uploaded and the profile was updated.'); } if ($action === 'living_entry') { $profileId = (int)($_POST['profile_id'] ?? 0); if (!inf_owned_profile($db, $userId, $profileId)) inf_redirect('living', '', 'Choose a profile.'); $title = trim((string)($_POST['title'] ?? '')); $body = trim((string)($_POST['body'] ?? '')); if ($title === '' || $body === '') inf_redirect('living', '', 'Add a title and entry.'); $db->prepare( 'INSERT INTO living_entries (user_id,profile_id,entry_date,title,body,created_at) VALUES (:user_id,:profile_id,:entry_date,:title,:body,:created_at)' )->execute([ 'user_id' => $userId, 'profile_id' => $profileId, 'entry_date' => ($_POST['entry_date'] ?? '') ?: date('Y-m-d'), 'title' => $title, 'body' => $body, 'created_at' => $now ]); inf_redirect('living', 'Living Archive entry added.'); } if ($action === 'publishing') { $profileId = (int)($_POST['profile_id'] ?? 0); if (!inf_owned_profile($db, $userId, $profileId)) inf_redirect('publishing', '', 'Choose a profile.'); $status = in_array(($_POST['publication_status'] ?? ''), ['draft','review','published'], true) ? $_POST['publication_status'] : 'draft'; $privacy = in_array(($_POST['privacy'] ?? ''), ['private','family','public'], true) ? $_POST['privacy'] : 'private'; $db->prepare( 'UPDATE profiles SET publication_status=:status,privacy=:privacy,updated_at=:updated_at WHERE id=:id AND user_id=:user_id' )->execute(['status'=>$status,'privacy'=>$privacy,'updated_at'=>$now,'id'=>$profileId,'user_id'=>$userId]); inf_redirect('publishing', 'Sanctuary publishing settings updated.'); } if ($action === 'circle') { $name = trim((string)($_POST['name'] ?? '')); if ($name === '') inf_redirect('circles', '', 'Enter a circle name.'); $profileId = (int)($_POST['profile_id'] ?? 0); if ($profileId && !inf_owned_profile($db, $userId, $profileId)) $profileId = 0; $db->prepare( 'INSERT INTO family_circles (user_id,profile_id,name,description,created_at) VALUES (:user_id,:profile_id,:name,:description,:created_at)' )->execute([ 'user_id'=>$userId,'profile_id'=>$profileId ?: null,'name'=>$name, 'description'=>trim((string)($_POST['description'] ?? '')),'created_at'=>$now ]); inf_redirect('circles', 'Family Circle created.'); } if ($action === 'grave') { $profileId = (int)($_POST['profile_id'] ?? 0); if (!inf_owned_profile($db, $userId, $profileId)) inf_redirect('graves', '', 'Choose a profile.'); $db->prepare( 'INSERT INTO grave_records (user_id,profile_id,cemetery_name,section_name,lot,plot,latitude,longitude,verification_status,notes,created_at,updated_at) VALUES (:user_id,:profile_id,:cemetery_name,:section_name,:lot,:plot,:latitude,:longitude,"unverified",:notes,:created_at,:updated_at)' )->execute([ 'user_id'=>$userId,'profile_id'=>$profileId, 'cemetery_name'=>trim((string)($_POST['cemetery_name'] ?? '')), 'section_name'=>trim((string)($_POST['section_name'] ?? '')), 'lot'=>trim((string)($_POST['lot'] ?? '')), 'plot'=>trim((string)($_POST['plot'] ?? '')), 'latitude'=>($_POST['latitude'] ?? '') !== '' ? (float)$_POST['latitude'] : null, 'longitude'=>($_POST['longitude'] ?? '') !== '' ? (float)$_POST['longitude'] : null, 'notes'=>trim((string)($_POST['notes'] ?? '')), 'created_at'=>$now,'updated_at'=>$now ]); inf_redirect('graves', 'Grave GPS record saved.'); } if ($action === 'visit') { $profileId = (int)($_POST['profile_id'] ?? 0); if (!inf_owned_profile($db, $userId, $profileId)) inf_redirect('visits', '', 'Choose a profile.'); $db->prepare( 'INSERT INTO visits (user_id,profile_id,visited_at,note,flowers_left,condition_note,privacy,created_at) VALUES (:user_id,:profile_id,:visited_at,:note,:flowers_left,:condition_note,:privacy,:created_at)' )->execute([ 'user_id'=>$userId,'profile_id'=>$profileId, 'visited_at'=>($_POST['visited_at'] ?? '') ?: date('Y-m-d\TH:i'), 'note'=>trim((string)($_POST['note'] ?? '')), 'flowers_left'=>trim((string)($_POST['flowers_left'] ?? '')), 'condition_note'=>trim((string)($_POST['condition_note'] ?? '')), 'privacy'=>in_array(($_POST['privacy'] ?? ''), ['private','family','public'], true) ? $_POST['privacy'] : 'private', 'created_at'=>$now ]); inf_redirect('visits', 'Visit logged.'); } if ($action === 'tribute') { $profileId = (int)($_POST['profile_id'] ?? 0); if (!inf_owned_profile($db, $userId, $profileId)) inf_redirect('tributes', '', 'Choose a profile.'); $text = trim((string)($_POST['tribute_text'] ?? '')); if ($text === '') inf_redirect('tributes', '', 'Write a tribute.'); $db->prepare( 'INSERT INTO tributes (user_id,profile_id,tribute_text,visibility,created_at) VALUES (:user_id,:profile_id,:tribute_text,:visibility,:created_at)' )->execute([ 'user_id'=>$userId,'profile_id'=>$profileId,'tribute_text'=>$text, 'visibility'=>in_array(($_POST['visibility'] ?? ''), ['private','family','public'], true) ? $_POST['visibility'] : 'family', 'created_at'=>$now ]); inf_redirect('tributes', 'Tribute added.'); } } $profiles = inf_profiles($db, $userId); $notice = trim((string)($_GET['notice'] ?? '')); $error = trim((string)($_GET['error'] ?? '')); $mediaStmt = $db->prepare( 'SELECT m.*, p.name AS profile_name FROM media m JOIN profiles p ON p.id=m.profile_id WHERE m.user_id=:user_id ORDER BY m.id DESC LIMIT 30' ); $mediaStmt->execute(['user_id'=>$userId]); $media = $mediaStmt->fetchAll(); function rows_for(PDO $db, string $table, int $userId): array { $allowed = ['living_entries','family_circles','grave_records','visits','tributes']; if (!in_array($table, $allowed, true)) return []; $stmt = $db->prepare("SELECT t.*, p.name AS profile_name FROM {$table} t LEFT JOIN profiles p ON p.id=t.profile_id WHERE t.user_id=:user_id ORDER BY t.id DESC LIMIT 30"); $stmt->execute(['user_id'=>$userId]); return $stmt->fetchAll(); } $livingRows = rows_for($db,'living_entries',$userId); $circleRows = rows_for($db,'family_circles',$userId); $graveRows = rows_for($db,'grave_records',$userId); $visitRows = rows_for($db,'visits',$userId); $tributeRows = rows_for($db,'tributes',$userId); $trackerFile = __DIR__ . '/_inc/trackers.php'; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title>My Infinity</title> <meta name="theme-color" content="#080612"> <?php if (is_file($trackerFile)) require $trackerFile; ?> <link rel="stylesheet" href="/infinity/assets/my-infinity.css"> </head> <body> <div class="cosmos" aria-hidden="true"></div> <div class="app"> <aside class="rail" id="rail"> <a class="brand" href="/infinity/"><span>∞</span><strong>My Infinity</strong></a> <nav> <?php $nav = [ 'overview'=>'Overview','profiles'=>'My Profiles','create'=>'Create Profile', 'uploads'=>'Upload Photos & Videos','living'=>'Living Archive', 'publishing'=>'Sanctuary Publishing','circles'=>'Family Circles', 'graves'=>'Grave Records','visits'=>'Visits','tributes'=>'Tributes', 'account'=>'Account & Privacy' ]; foreach ($nav as $key=>$label): ?> <a class="<?= $tab===$key?'active':'' ?>" href="/infinity/dashboard.php?tab=<?= inf_e($key) ?>"> <i><?= inf_e(mb_substr($label,0,1)) ?></i><span><?= inf_e($label) ?></span> </a> <?php endforeach; ?> </nav> <a class="logout" href="/infinity/logout.php">Log out</a> </aside> <header class="top"> <button class="menu" id="menuButton" type="button">☰</button> <div> <small>PRIVATE WORKSPACE</small> <h1><?= inf_e($nav[$tab]) ?></h1> </div> <div class="user-chip"> <span><?= inf_e(mb_strtoupper(mb_substr((string)$user['display_name'],0,1))) ?></span> <div><strong><?= inf_e((string)$user['display_name']) ?></strong><small><?= inf_e((string)$user['email']) ?></small></div> </div> </header> <main class="content"> <?php if ($notice !== ''): ?><div class="alert ok"><?= inf_e($notice) ?></div><?php endif; ?> <?php if ($error !== ''): ?><div class="alert bad"><?= inf_e($error) ?></div><?php endif; ?> <?php if ($tab === 'overview'): ?> <section class="hero"> <div> <small>WELCOME TO YOUR PRIVATE SPACE</small> <h2>Your memories, profiles, visits, and Sanctuary controls live here.</h2> <p>Upload a photo or video and Infinity can automatically create the first private profile, assign the first image as its portrait, and use the first video or image as its cover.</p> <div class="actions"> <a class="button primary" href="?tab=uploads">Upload Media</a> <a class="button" href="?tab=create">Create Profile</a> </div> </div> <div class="orb">∞</div> </section> <section class="stats"> <article><b><?= count($profiles) ?></b><span>Profiles</span></article> <article><b><?= count($media) ?></b><span>Recent media</span></article> <article><b><?= count($livingRows) ?></b><span>Archive entries</span></article> <article><b><?= count($visitRows) ?></b><span>Visits logged</span></article> </section> <section class="panel"> <div class="panel-head"><div><small>RECENT SPACES</small><h3>My Profiles</h3></div><a href="?tab=profiles">View all</a></div> <div class="profile-grid"> <?php if (!$profiles): ?> <div class="empty">No profile exists yet. Upload media or create one manually.</div> <?php endif; ?> <?php foreach (array_slice($profiles,0,4) as $p): ?> <article class="profile-card"> <div class="profile-visual"> <?php if ($p['cover_media_id']): ?> <img src="/infinity/media.php?id=<?= (int)$p['cover_media_id'] ?>" alt=""> <?php else: ?><span>∞</span><?php endif; ?> </div> <div><small><?= inf_e(str_replace('_',' ',(string)$p['profile_type'])) ?></small><h3><?= inf_e((string)$p['name']) ?></h3><p><?= inf_e((string)$p['privacy']) ?> · <?= inf_e((string)$p['publication_status']) ?></p></div> </article> <?php endforeach; ?> </div> </section> <?php elseif ($tab === 'profiles'): ?> <section class="section-title"><small>PRIVATE PROFILE DIRECTORY</small><h2>My Profiles</h2><p>Every Living Archive, human memorial, and pet memorial you control.</p></section> <div class="profile-grid"> <?php if (!$profiles): ?><div class="empty">No profiles yet.</div><?php endif; ?> <?php foreach ($profiles as $p): ?> <article class="profile-card large"> <div class="profile-visual"> <?php if ($p['cover_media_id']): ?><img src="/infinity/media.php?id=<?= (int)$p['cover_media_id'] ?>" alt=""> <?php else: ?><span>∞</span><?php endif; ?> </div> <div> <small><?= inf_e(str_replace('_',' ',(string)$p['profile_type'])) ?></small> <h3><?= inf_e((string)$p['name']) ?></h3> <p><?= inf_e((string)$p['about']) ?></p> <div class="tags"><span><?= (int)$p['media_count'] ?> media</span><span><?= inf_e((string)$p['privacy']) ?></span><span><?= inf_e((string)$p['publication_status']) ?></span></div> </div> </article> <?php endforeach; ?> </div> <?php elseif ($tab === 'create'): ?> <section class="section-title"><small>NEW INFINITY SPACE</small><h2>Create Profile</h2><p>Create a Living Archive, human memorial, or pet memorial.</p></section> <section class="panel form-panel"> <form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"> <input type="hidden" name="action" value="create_profile"> <div class="form-grid"> <label><span>Profile name</span><input name="name" required></label> <label><span>Profile type</span><select name="profile_type"><option value="living">Living Archive</option><option value="human_memorial">Human Memorial</option><option value="pet_memorial">Pet Memorial</option></select></label> <label><span>Relationship</span><input name="relationship" placeholder="Self, parent, friend, companion..."></label> <label><span>Privacy</span><select name="privacy"><option value="private">Private</option><option value="family">Family only</option><option value="public">Public Sanctuary</option></select></label> <label><span>Birth date</span><input type="date" name="birth_date"></label> <label><span>Memorial date</span><input type="date" name="memorial_date"></label> </div> <label><span>About this life</span><textarea name="about" rows="6"></textarea></label> <button class="button primary" type="submit">Create Profile</button> </form> </section> <?php elseif ($tab === 'uploads'): ?> <section class="section-title"><small>MEDIA CREATES THE VISUAL SPACE</small><h2>Upload Photos & Videos</h2><p>Uploading without selecting a profile automatically creates your first private profile.</p></section> <section class="panel form-panel"> <form method="post" enctype="multipart/form-data"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"> <input type="hidden" name="action" value="upload_media"> <label><span>Profile</span><select name="profile_id"><option value="0">Auto-create or use my first profile</option><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label> <label class="drop"><input type="file" name="media[]" accept="image/*,video/mp4,video/webm,video/quicktime" multiple required><b>Choose photos or videos</b><span>JPG, PNG, WEBP, GIF, MP4, WEBM or MOV · up to 150 MB each</span></label> <label><span>Caption</span><input name="caption" placeholder="Optional caption applied to this upload"></label> <button class="button primary" type="submit">Upload and Build Profile</button> </form> </section> <div class="media-grid"> <?php foreach ($media as $m): ?><article> <?php if ($m['media_kind']==='image'): ?><img src="/infinity/media.php?id=<?= (int)$m['id'] ?>" alt=""> <?php else: ?><video src="/infinity/media.php?id=<?= (int)$m['id'] ?>" controls preload="metadata"></video><?php endif; ?> <div><strong><?= inf_e((string)$m['profile_name']) ?></strong><span><?= inf_e((string)$m['original_name']) ?></span></div> </article><?php endforeach; ?> </div> <?php elseif ($tab === 'living'): ?> <section class="section-title"><small>JOURNAL WHILE LIFE IS HAPPENING</small><h2>Living Archive</h2><p>Add stories, milestones, reflections, health notes, or daily memories.</p></section> <?= profile_required_notice($profiles ?? []) ?> <?php if ($profiles): ?><section class="panel form-panel"><form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"><input type="hidden" name="action" value="living_entry"> <div class="form-grid"><label><span>Profile</span><select name="profile_id"><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label><label><span>Date</span><input type="date" name="entry_date" value="<?= date('Y-m-d') ?>"></label></div> <label><span>Title</span><input name="title" required></label><label><span>Entry</span><textarea name="body" rows="6" required></textarea></label> <button class="button primary">Add Archive Entry</button> </form></section><?php endif; ?> <div class="timeline"><?php foreach($livingRows as $r): ?><article><time><?= inf_e((string)$r['entry_date']) ?></time><div><small><?= inf_e((string)$r['profile_name']) ?></small><h3><?= inf_e((string)$r['title']) ?></h3><p><?= nl2br(inf_e((string)$r['body'])) ?></p></div></article><?php endforeach; ?></div> <?php elseif ($tab === 'publishing'): ?> <section class="section-title"><small>CONTROL WHAT ENTERS SANCTUARY</small><h2>Sanctuary Publishing</h2><p>Nothing becomes public automatically. Choose the profile, audience, and review state.</p></section> <?php if ($profiles): ?><section class="panel form-panel"><form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"><input type="hidden" name="action" value="publishing"> <label><span>Profile</span><select name="profile_id"><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label> <div class="form-grid"><label><span>Publication status</span><select name="publication_status"><option value="draft">Draft</option><option value="review">Family review</option><option value="published">Published</option></select></label><label><span>Audience</span><select name="privacy"><option value="private">Private</option><option value="family">Family Circle</option><option value="public">Public Sanctuary</option></select></label></div> <button class="button primary">Update Publishing</button> </form></section><?php else: ?><div class="empty">Create a profile before publishing.</div><?php endif; ?> <?php elseif ($tab === 'circles'): ?> <section class="section-title"><small>SHARED PRIVATE SPACES</small><h2>Family Circles</h2><p>Organize relatives, caretakers, contributors, and memorial stewards around a profile.</p></section> <section class="panel form-panel"><form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"><input type="hidden" name="action" value="circle"> <div class="form-grid"><label><span>Circle name</span><input name="name" required></label><label><span>Connected profile</span><select name="profile_id"><option value="0">No single profile</option><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label></div> <label><span>Description</span><textarea name="description" rows="4"></textarea></label><button class="button primary">Create Family Circle</button> </form></section> <div class="list"><?php foreach($circleRows as $r): ?><article><h3><?= inf_e((string)$r['name']) ?></h3><p><?= inf_e((string)$r['description']) ?></p><small><?= inf_e((string)($r['profile_name'] ?: 'Multiple profiles')) ?></small></article><?php endforeach; ?></div> <?php elseif ($tab === 'graves'): ?> <section class="section-title"><small>CEMETERY + GPS VERIFICATION</small><h2>Grave Records</h2><p>Record cemetery, section, lot, plot, coordinates, and verification notes.</p></section> <?php if ($profiles): ?><section class="panel form-panel"><form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"><input type="hidden" name="action" value="grave"> <label><span>Profile</span><select name="profile_id"><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label> <div class="form-grid"><label><span>Cemetery</span><input name="cemetery_name"></label><label><span>Section</span><input name="section_name"></label><label><span>Lot</span><input name="lot"></label><label><span>Plot</span><input name="plot"></label><label><span>Latitude</span><input name="latitude" inputmode="decimal"></label><label><span>Longitude</span><input name="longitude" inputmode="decimal"></label></div> <label><span>Notes</span><textarea name="notes" rows="4"></textarea></label><button class="button primary">Save Grave Record</button> </form></section><?php endif; ?> <div class="list"><?php foreach($graveRows as $r): ?><article><h3><?= inf_e((string)$r['profile_name']) ?></h3><p><?= inf_e(trim((string)$r['cemetery_name'].' · '.(string)$r['section_name'].' · Lot '.(string)$r['lot'].' · Plot '.(string)$r['plot'],' ·')) ?></p><small><?= inf_e((string)$r['verification_status']) ?></small></article><?php endforeach; ?></div> <?php elseif ($tab === 'visits'): ?> <section class="section-title"><small>REMEMBRANCE VISIT LOG</small><h2>Visits</h2><p>Record when someone visited, what they left, and the condition of the memorial.</p></section> <?php if ($profiles): ?><section class="panel form-panel"><form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"><input type="hidden" name="action" value="visit"> <div class="form-grid"><label><span>Profile</span><select name="profile_id"><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label><label><span>Visit time</span><input type="datetime-local" name="visited_at" value="<?= date('Y-m-d\TH:i') ?>"></label><label><span>Flowers or items left</span><input name="flowers_left"></label><label><span>Visibility</span><select name="privacy"><option value="private">Private</option><option value="family">Family</option><option value="public">Public</option></select></label></div> <label><span>Visit note</span><textarea name="note" rows="4"></textarea></label><label><span>Headstone or memorial condition</span><textarea name="condition_note" rows="3"></textarea></label><button class="button primary">Log Visit</button> </form></section><?php endif; ?> <div class="timeline"><?php foreach($visitRows as $r): ?><article><time><?= inf_e((string)$r['visited_at']) ?></time><div><small><?= inf_e((string)$r['profile_name']) ?></small><p><?= inf_e((string)$r['note']) ?></p></div></article><?php endforeach; ?></div> <?php elseif ($tab === 'tributes'): ?> <section class="section-title"><small>WORDS OF REMEMBRANCE</small><h2>Tributes</h2><p>Add private, family, or public remembrance messages.</p></section> <?php if ($profiles): ?><section class="panel form-panel"><form method="post"> <input type="hidden" name="csrf" value="<?= inf_e(inf_csrf()) ?>"><input type="hidden" name="action" value="tribute"> <div class="form-grid"><label><span>Profile</span><select name="profile_id"><?php foreach($profiles as $p): ?><option value="<?= (int)$p['id'] ?>"><?= inf_e((string)$p['name']) ?></option><?php endforeach; ?></select></label><label><span>Visibility</span><select name="visibility"><option value="family">Family</option><option value="private">Private</option><option value="public">Public</option></select></label></div> <label><span>Tribute</span><textarea name="tribute_text" rows="6" required></textarea></label><button class="button primary">Add Tribute</button> </form></section><?php endif; ?> <div class="list"><?php foreach($tributeRows as $r): ?><article><small><?= inf_e((string)$r['profile_name']) ?> · <?= inf_e((string)$r['visibility']) ?></small><p><?= nl2br(inf_e((string)$r['tribute_text'])) ?></p></article><?php endforeach; ?></div> <?php elseif ($tab === 'account'): ?> <section class="section-title"><small>IDENTITY + PRIVACY</small><h2>Account & Privacy</h2><p>Your account is stored in Infinity’s private SQLite database with a securely hashed password.</p></section> <section class="panel account-card"><div class="avatar"><?= inf_e(mb_strtoupper(mb_substr((string)$user['display_name'],0,1))) ?></div><div><h3><?= inf_e((string)$user['display_name']) ?></h3><p><?= inf_e((string)$user['email']) ?></p><small>Account created <?= inf_e((string)$user['created_at']) ?></small></div></section> <section class="panel"><h3>Default privacy rules</h3><ul><li>New profiles start private.</li><li>Uploads remain behind login unless a profile is deliberately published.</li><li>Sanctuary publication requires an explicit status and audience choice.</li></ul></section> <?php endif; ?> </main> </div> <script> const rail=document.getElementById('rail'); document.getElementById('menuButton')?.addEventListener('click',()=>document.body.classList.toggle('nav-open')); document.addEventListener('click',e=>{if(innerWidth<860&&document.body.classList.contains('nav-open')&&!rail.contains(e.target)&&!e.target.closest('#menuButton'))document.body.classList.remove('nav-open')}); </script> </body> </html> <?php function profile_required_notice(array $profiles): string { return $profiles ? '' : '<div class="empty">Create a profile before adding archive entries.</div>'; }
Save file
Quick jump
open a path
Open