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,379
Folders
434
Scanned Size
3.85 GB
PHP Files
950
Editable Text Files
12,628
File viewer
guarded to /htdocs
/connlink/test1/ui/index.html
<?php declare(strict_types=1); ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <meta name="theme-color" content="#06051a"> <title>SIMON Live · Spatial Command</title> <style> :root{--bg:#050419;--bg2:#090729;--panel:rgba(12,15,43,.78);--panel2:rgba(20,20,66,.8);--line:rgba(126,105,255,.28);--line2:rgba(76,225,255,.36);--text:#f4f6ff;--muted:#a6acd0;--vio:#8b5cff;--pink:#ff4fd8;--cyan:#48e5ff;--green:#39f4b2;--amber:#ffbf64;--red:#ff668c;--radius:22px;--shadow:0 24px 80px rgba(0,0,0,.46)} *{box-sizing:border-box}html{background:var(--bg)}body{margin:0;min-height:100vh;color:var(--text);font:14px/1.45 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;background:radial-gradient(1100px 700px at 15% -10%,rgba(139,92,255,.28),transparent 58%),radial-gradient(900px 700px at 100% 10%,rgba(255,79,216,.17),transparent 60%),linear-gradient(180deg,var(--bg),var(--bg2));overflow-x:hidden}button,input,textarea,select{font:inherit}.app{display:grid;grid-template-columns:240px minmax(0,1fr);min-height:100vh}.sidebar{position:sticky;top:0;height:100vh;padding:18px 14px;border-right:1px solid var(--line);background:linear-gradient(180deg,rgba(4,5,22,.92),rgba(8,6,30,.82));backdrop-filter:blur(22px);overflow:auto}.brand{display:flex;align-items:center;gap:11px;padding:6px 8px 22px}.orb{width:42px;height:42px;border-radius:50%;background:conic-gradient(from 160deg,var(--cyan),var(--vio),var(--pink),var(--cyan));box-shadow:0 0 30px rgba(75,227,255,.45);position:relative}.orb:after{content:"∞";position:absolute;inset:0;display:grid;place-items:center;color:#07101d;font-weight:1000;font-size:20px}.brand b{font-size:20px;background:linear-gradient(90deg,var(--pink),var(--cyan));-webkit-background-clip:text;color:transparent}.brand small{display:block;color:var(--muted);letter-spacing:.16em;text-transform:uppercase;font-size:9px}.nav{display:grid;gap:5px}.nav button{border:0;background:transparent;color:var(--text);display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:13px;cursor:pointer;text-align:left}.nav button:hover,.nav button.active{background:linear-gradient(90deg,rgba(139,92,255,.26),rgba(72,229,255,.08));box-shadow:inset 0 0 0 1px var(--line2)}.nav .ic{font-size:18px;width:25px;text-align:center}.side-foot{margin-top:24px;border-top:1px solid var(--line);padding-top:14px;color:var(--muted);font-size:11px}.main{min-width:0;padding:18px 22px 90px}.topbar{display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:30;padding:10px 0 14px;background:linear-gradient(180deg,rgba(5,4,25,.94),rgba(5,4,25,0));backdrop-filter:blur(8px)}.search{flex:1;max-width:620px;position:relative}.search input{width:100%;border:1px solid var(--line);background:rgba(13,12,42,.78);color:var(--text);padding:12px 16px 12px 42px;border-radius:999px;outline:none}.search:before{content:"⌕";position:absolute;left:16px;top:9px;color:var(--cyan);font-size:20px}.status{display:flex;align-items:center;gap:7px;color:var(--muted);font-size:12px}.dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 12px var(--green)}.icon-btn,.primary,.ghost{border:1px solid var(--line);color:var(--text);cursor:pointer}.icon-btn{width:42px;height:42px;border-radius:50%;background:rgba(14,13,44,.75)}.primary{padding:10px 16px;border-radius:999px;background:linear-gradient(90deg,var(--pink),var(--vio),var(--cyan));color:#08051d;font-weight:900}.ghost{padding:9px 13px;border-radius:999px;background:rgba(255,255,255,.04)}.hero{min-height:360px;border:1px solid var(--line);border-radius:28px;overflow:hidden;position:relative;box-shadow:var(--shadow);background:#0a0924 url('assets/gaylord-spatial-hero.png') center/cover no-repeat}.hero:after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(3,4,20,.94) 0%,rgba(3,4,20,.72) 38%,rgba(3,4,20,.18) 72%,rgba(3,4,20,.55)),linear-gradient(180deg,transparent 45%,rgba(3,4,20,.9))}.hero-content{position:relative;z-index:2;padding:42px;max-width:620px}.eyebrow{color:var(--cyan);text-transform:uppercase;letter-spacing:.18em;font-size:11px;font-weight:800}.hero h1{font-size:clamp(42px,6vw,84px);line-height:.94;letter-spacing:-.055em;margin:12px 0}.hero h1 span{background:linear-gradient(90deg,var(--pink),#a680ff,var(--cyan));-webkit-background-clip:text;color:transparent}.hero p{color:#ccd3f3;font-size:16px;max-width:560px}.hero-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:18px 0}.metric{padding:15px;border-radius:18px;border:1px solid var(--line);background:linear-gradient(180deg,rgba(19,17,60,.84),rgba(10,9,34,.84));box-shadow:0 14px 34px rgba(0,0,0,.2)}.metric b{display:block;font-size:28px}.metric span{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.1em}.section-head{display:flex;align-items:center;gap:10px;margin:28px 0 12px}.section-head h2{margin:0;font-size:22px}.section-head .grow{flex:1}.modules{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.module{border:1px solid var(--line);border-radius:20px;padding:18px;background:linear-gradient(180deg,rgba(22,20,68,.78),rgba(10,9,36,.84));position:relative;overflow:hidden;cursor:pointer;transition:.18s ease}.module:hover{transform:translateY(-3px);border-color:var(--line2);box-shadow:0 20px 50px rgba(0,0,0,.35),0 0 28px rgba(72,229,255,.12)}.module:after{content:"";position:absolute;width:140px;height:140px;border-radius:50%;right:-55px;bottom:-60px;background:radial-gradient(circle,rgba(139,92,255,.35),transparent 66%)}.module .mi{font-size:29px}.module h3{margin:10px 0 4px}.module p{margin:0;color:var(--muted);font-size:12px}.feed-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card{border:1px solid var(--line);border-radius:20px;overflow:hidden;background:rgba(12,12,40,.84);min-width:0}.card-media{height:150px;background:linear-gradient(135deg,#15124b,#382069);position:relative;overflow:hidden}.card-media img{width:100%;height:100%;object-fit:cover}.card-media:after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,transparent,rgba(4,4,20,.55))}.card-body{padding:14px}.tag{display:inline-flex;padding:4px 8px;border-radius:999px;background:rgba(72,229,255,.09);border:1px solid var(--line2);color:var(--cyan);font-size:10px;text-transform:uppercase;letter-spacing:.1em}.card h3{font-size:16px;line-height:1.25;margin:9px 0 6px}.card p{color:var(--muted);font-size:12px;margin:0}.card-foot{display:flex;justify-content:space-between;gap:8px;color:var(--muted);font-size:11px;margin-top:12px}.view{display:none}.view.active{display:block}.split{display:grid;grid-template-columns:minmax(0,1fr) 360px;gap:16px}.panel{border:1px solid var(--line);border-radius:22px;background:rgba(12,12,42,.8);padding:18px}.panel h2,.panel h3{margin-top:0}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.field{display:grid;gap:6px}.field.full{grid-column:1/-1}.field label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.field input,.field textarea,.field select{width:100%;border:1px solid var(--line);border-radius:13px;padding:11px 12px;background:rgba(5,6,25,.8);color:var(--text);outline:none}.field textarea{min-height:110px;resize:vertical}.list{display:grid;gap:9px}.item{display:flex;align-items:center;gap:12px;padding:12px;border:1px solid rgba(255,255,255,.08);border-radius:15px;background:rgba(255,255,255,.035)}.item .ii{width:40px;height:40px;border-radius:12px;display:grid;place-items:center;background:linear-gradient(135deg,var(--vio),var(--cyan));color:#07051d;font-size:20px}.item .grow{flex:1;min-width:0}.item b{display:block}.item small{color:var(--muted)}.toast{position:fixed;right:20px;bottom:20px;z-index:99;padding:12px 15px;border:1px solid var(--line2);border-radius:14px;background:#0b0a29;color:var(--text);box-shadow:var(--shadow);opacity:0;transform:translateY(14px);pointer-events:none;transition:.2s}.toast.on{opacity:1;transform:none}.mobile-nav{display:none}.modal{position:fixed;inset:0;z-index:80;display:none;place-items:center;padding:18px;background:rgba(1,1,12,.76);backdrop-filter:blur(12px)}.modal.open{display:grid}.modal-box{width:min(680px,100%);max-height:88vh;overflow:auto;border:1px solid var(--line2);border-radius:24px;background:linear-gradient(180deg,#13103f,#090824);box-shadow:var(--shadow);padding:20px}.modal-head{display:flex;align-items:center;gap:10px}.modal-head h2{margin:0}.modal-head .grow{flex:1}@media(max-width:1050px){.app{grid-template-columns:84px minmax(0,1fr)}.sidebar .brand b,.sidebar .brand small,.nav .label,.side-foot{display:none}.brand{justify-content:center}.nav button{justify-content:center}.modules,.feed-grid{grid-template-columns:repeat(2,1fr)}.split{grid-template-columns:1fr}}@media(max-width:720px){.app{display:block}.sidebar{display:none}.main{padding:10px 10px 92px}.topbar{padding-top:5px}.status{display:none}.hero{min-height:500px;background-position:center}.hero-content{padding:28px 20px}.hero h1{font-size:50px}.metrics{grid-template-columns:repeat(2,1fr)}.modules,.feed-grid,.form-grid{grid-template-columns:1fr}.mobile-nav{position:fixed;bottom:0;left:0;right:0;z-index:50;display:grid;grid-template-columns:repeat(5,1fr);padding:8px 8px calc(8px + env(safe-area-inset-bottom));border-top:1px solid var(--line);background:rgba(5,5,24,.94);backdrop-filter:blur(18px)}.mobile-nav button{border:0;background:transparent;color:var(--muted);display:grid;gap:2px;place-items:center;font-size:10px}.mobile-nav button span{font-size:21px}.mobile-nav button.active{color:var(--cyan)}} </style> </head> <body> <div class="app"> <aside class="sidebar"> <div class="brand"><div class="orb"></div><div><b>SIMON Live</b><small>Spatial Command</small></div></div> <nav class="nav" id="sideNav"> <button class="active" data-view="home"><span class="ic">◈</span><span class="label">Command</span></button> <button data-view="events"><span class="ic">◫</span><span class="label">Events</span></button> <button data-view="albums"><span class="ic">▣</span><span class="label">Albums</span></button> <button data-view="reels"><span class="ic">▶</span><span class="label">Reels</span></button> <button data-view="alerts"><span class="ic">◉</span><span class="label">Alerts</span></button> <button data-view="messages"><span class="ic">✦</span><span class="label">Messages</span></button> <button data-view="developer"><span class="ic">⌘</span><span class="label">Developer</span></button> <button data-view="settings"><span class="ic">⚙</span><span class="label">Settings</span></button> </nav> <div class="side-foot">SIMON Feed Engine · AI · Graph · Scheduler<br><br><span class="dot" style="display:inline-block"></span> Local workspace connected</div> </aside> <main class="main"> <header class="topbar"><div class="search"><input id="globalSearch" placeholder="Search SIMON, feeds, events, media…"></div><div class="status"><span class="dot"></span> Network online</div><button class="icon-btn" id="refreshBtn">↻</button><button class="primary" id="quickCreate">+ Create</button></header> <section class="view active" id="view-home"> <div class="hero"><div class="hero-content"><div class="eyebrow">Cognitive sovereignty engine</div><h1>Your universe.<br><span>Live.</span></h1><p>One spatial command center for feeds, events, albums, reels, alerts, messages, developer logs, AI enrichment, and the SIMON intelligence graph.</p><div class="hero-actions"><button class="primary" data-open="event">Create Event</button><button class="ghost" data-go="reels">Upload Reel</button><button class="ghost" data-go="developer">Open Developer Mode</button></div></div></div> <div class="metrics"><div class="metric"><b id="mFeeds">39</b><span>Connected sources</span></div><div class="metric"><b id="mSignals">—</b><span>Live signals</span></div><div class="metric"><b id="mEvents">0</b><span>Your events</span></div><div class="metric"><b id="mAlerts">0</b><span>Active alerts</span></div></div> <div class="section-head"><h2>Spatial modules</h2><div class="grow"></div><button class="ghost" data-go="settings">Customize</button></div> <div class="modules"> <article class="module" data-go="events"><div class="mi">◫</div><h3>Events</h3><p>Create, schedule, publish, and log experiences.</p></article> <article class="module" data-go="albums"><div class="mi">▣</div><h3>Albums</h3><p>Build visual collections and attach media.</p></article> <article class="module" data-go="reels"><div class="mi">▶</div><h3>Reels</h3><p>Upload short video metadata and publishing status.</p></article> <article class="module" data-go="alerts"><div class="mi">◉</div><h3>Alerts</h3><p>Set feed, event, weather, and security alert rules.</p></article> <article class="module" data-go="messages"><div class="mi">✦</div><h3>Messages</h3><p>Send messages through your SIMON webhook pipeline.</p></article> <article class="module" data-go="developer"><div class="mi">⌘</div><h3>Developer</h3><p>Inspect endpoints, send test payloads, and log events.</p></article> </div> <div class="section-head"><h2>Live intelligence</h2><div class="grow"></div><button class="ghost" id="loadFeed">Refresh feed</button></div> <div class="feed-grid" id="feedGrid"></div> </section> <section class="view" id="view-events"><div class="split"><div class="panel"><div class="section-head"><h2>Events</h2><div class="grow"></div><button class="primary" data-open="event">+ Add Event</button></div><div class="list" id="eventList"></div></div><aside class="panel"><h3>Event workflow</h3><p style="color:var(--muted)">Draft → scheduled → live → archived. Every save can also be written to the developer event log.</p><button class="ghost" data-go="developer">View event log</button></aside></div></section> <section class="view" id="view-albums"><div class="split"><div class="panel"><div class="section-head"><h2>Albums</h2><div class="grow"></div><button class="primary" data-open="album">+ New Album</button></div><div class="feed-grid" id="albumList"></div></div><aside class="panel"><img src="assets/profile-dashboard-inspiration.png" alt="UI inspiration" style="width:100%;border-radius:16px"><p style="color:var(--muted)">Use cover art, title, visibility, and media count to organize visual stories.</p></aside></div></section> <section class="view" id="view-reels"><div class="split"><div class="panel"><div class="section-head"><h2>Reels</h2><div class="grow"></div><button class="primary" data-open="reel">⬆ Upload Reel</button></div><div class="list" id="reelList"></div></div><aside class="panel"><img src="assets/spatial-mobile-inspiration.jpeg" alt="Mobile inspiration" style="width:100%;max-height:460px;object-fit:cover;border-radius:16px"><p style="color:var(--muted)">This package stores reel metadata locally. Connect a real object-storage uploader when your backend is ready.</p></aside></div></section> <section class="view" id="view-alerts"><div class="split"><div class="panel"><div class="section-head"><h2>Alerts</h2><div class="grow"></div><button class="primary" data-open="alert">+ New Alert</button></div><div class="list" id="alertList"></div></div><aside class="panel"><h3>Suggested rules</h3><div class="list"><div class="item"><div class="ii">🌋</div><div><b>Earthquake magnitude ≥ 6</b><small>USGS · immediate</small></div></div><div class="item"><div class="ii">🛡</div><div><b>Critical cybersecurity advisory</b><small>CISA · immediate</small></div></div><div class="item"><div class="ii">📰</div><div><b>Gaylord Sinclair mention</b><small>All feeds · hourly</small></div></div></div></aside></div></section> <section class="view" id="view-messages"><div class="split"><div class="panel"><h2>Send Message</h2><div class="form-grid"><div class="field"><label>Recipient / Stream</label><input id="msgTo" value="default"></div><div class="field"><label>Subject</label><input id="msgSubject" placeholder="Message subject"></div><div class="field full"><label>Message</label><textarea id="msgBody" placeholder="Write a message…"></textarea></div><div class="field full"><button class="primary" id="sendMessage">Send through SIMON</button></div></div></div><aside class="panel"><h3>Recent messages</h3><div class="list" id="messageList"></div></aside></div></section> <section class="view" id="view-developer"><div class="split"><div class="panel"><h2>Developer Mode</h2><div class="form-grid"><div class="field"><label>Event type</label><input id="logType" value="custom_event"></div><div class="field"><label>Severity</label><select id="logSeverity"><option>info</option><option>warning</option><option>critical</option></select></div><div class="field full"><label>Payload JSON</label><textarea id="logPayload">{"title":"SIMON developer event","source":"spatial-ui"}</textarea></div><div class="field full"><button class="primary" id="logEvent">Log Event</button></div></div><div class="section-head"><h3>Runtime log</h3><div class="grow"></div><button class="ghost" id="clearLog">Clear local</button></div><div class="list" id="devLog"></div></div><aside class="panel"><h3>System endpoints</h3><div class="list"><a class="item" href="feed_proxy.php?limit=100" target="_blank"><div class="ii">📡</div><div><b>Feed Proxy</b><small>Normalized live JSON</small></div></a><a class="item" href="ai_dashboard.php" target="_blank"><div class="ii">🧠</div><div><b>AI Layer</b><small>Enrichment and archive</small></div></a><a class="item" href="graph_dashboard.php" target="_blank"><div class="ii">🕸</div><div><b>Intelligence Graph</b><small>Nodes and relationships</small></div></a><a class="item" href="jobs_dashboard.php" target="_blank"><div class="ii">⏱</div><div><b>Scheduler</b><small>Background refresh jobs</small></div></a></div></aside></div></section> <section class="view" id="view-settings"><div class="split"><div class="panel"><h2>Application Settings</h2><div class="form-grid"><div class="field"><label>Application name</label><input id="setName" value="SIMON Live"></div><div class="field"><label>Feed endpoint</label><input id="setFeed" value="feed_proxy.php?limit=24"></div><div class="field"><label>Webhook endpoint</label><input id="setWebhook" value="webhook_inbox.php?stream=default"></div><div class="field"><label>Refresh interval</label><select id="setRefresh"><option value="0">Manual</option><option value="60000">1 minute</option><option value="300000" selected>5 minutes</option><option value="900000">15 minutes</option></select></div><div class="field full"><button class="primary" id="saveSettings">Save Settings</button></div></div></div><aside class="panel"><img src="assets/portal-inspiration.jpeg" alt="Portal inspiration" style="width:100%;max-height:460px;object-fit:cover;border-radius:16px"><p style="color:var(--muted)">Visual direction: cosmic portals, dark glass panels, cyan/purple energy, strong mobile navigation.</p></aside></div></section> </main> </div> <nav class="mobile-nav" id="mobileNav"><button class="active" data-view="home"><span>◈</span>Home</button><button data-view="events"><span>◫</span>Events</button><button data-view="reels"><span>▶</span>Reels</button><button data-view="alerts"><span>◉</span>Alerts</button><button data-view="developer"><span>⌘</span>Dev</button></nav> <div class="modal" id="modal"><div class="modal-box"><div class="modal-head"><h2 id="modalTitle">Create</h2><div class="grow"></div><button class="icon-btn" id="modalClose">✕</button></div><div id="modalBody" style="margin-top:16px"></div></div></div> <div class="toast" id="toast"></div> <script> const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)]; const state={events:load('simon.events',[]),albums:load('simon.albums',[]),reels:load('simon.reels',[]),alerts:load('simon.alerts',[]),messages:load('simon.messages',[]),log:load('simon.devlog',[]),settings:load('simon.settings',{feed:'feed_proxy.php?limit=24',webhook:'webhook_inbox.php?stream=default',refresh:300000}),timer:null}; function load(k,d){try{return JSON.parse(localStorage.getItem(k))??d}catch{return d}} function save(k,v){localStorage.setItem(k,JSON.stringify(v))} function esc(s){return String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))} function toast(m){const t=$('#toast');t.textContent=m;t.classList.add('on');setTimeout(()=>t.classList.remove('on'),2300)} function go(view){$$('.view').forEach(v=>v.classList.toggle('active',v.id==='view-'+view));$$('[data-view]').forEach(b=>b.classList.toggle('active',b.dataset.view===view));window.scrollTo({top:0,behavior:'smooth'});renderAll()} $$('[data-view]').forEach(b=>b.onclick=()=>go(b.dataset.view));$$('[data-go]').forEach(b=>b.onclick=()=>go(b.dataset.go)); function renderAll(){renderEvents();renderAlbums();renderReels();renderAlerts();renderMessages();renderLog();$('#mEvents').textContent=state.events.length;$('#mAlerts').textContent=state.alerts.filter(a=>a.enabled).length} function empty(text){return `<div class="item"><div class="ii">∞</div><div><b>${text}</b><small>Create your first item to begin.</small></div></div>`} function renderEvents(){const h=$('#eventList');if(!h)return;h.innerHTML=state.events.length?state.events.map((x,i)=>`<div class="item"><div class="ii">◫</div><div class="grow"><b>${esc(x.title)}</b><small>${esc(x.date||'No date')} · ${esc(x.status)}</small></div><button class="ghost" onclick="removeItem('events',${i})">Delete</button></div>`).join(''):empty('No events yet')} function renderAlbums(){const h=$('#albumList');if(!h)return;h.innerHTML=state.albums.length?state.albums.map((x,i)=>`<article class="card"><div class="card-media"><img src="${esc(x.cover||'assets/profile-dashboard-inspiration.png')}" onerror="this.src='assets/profile-dashboard-inspiration.png'"></div><div class="card-body"><span class="tag">${esc(x.visibility)}</span><h3>${esc(x.title)}</h3><p>${esc(x.description)}</p><div class="card-foot"><span>${x.count||0} media</span><button class="ghost" onclick="removeItem('albums',${i})">Delete</button></div></div></article>`).join(''):empty('No albums yet')} function renderReels(){const h=$('#reelList');if(!h)return;h.innerHTML=state.reels.length?state.reels.map((x,i)=>`<div class="item"><div class="ii">▶</div><div class="grow"><b>${esc(x.title)}</b><small>${esc(x.platform)} · ${esc(x.status)} · ${esc(x.file||'metadata only')}</small></div><button class="ghost" onclick="removeItem('reels',${i})">Delete</button></div>`).join(''):empty('No reels uploaded')} function renderAlerts(){const h=$('#alertList');if(!h)return;h.innerHTML=state.alerts.length?state.alerts.map((x,i)=>`<div class="item"><div class="ii">${x.enabled?'◉':'○'}</div><div class="grow"><b>${esc(x.name)}</b><small>${esc(x.source)} · ${esc(x.condition)}</small></div><button class="ghost" onclick="toggleAlert(${i})">${x.enabled?'Disable':'Enable'}</button></div>`).join(''):empty('No alert rules')} function renderMessages(){const h=$('#messageList');if(!h)return;h.innerHTML=state.messages.slice().reverse().slice(0,8).map(x=>`<div class="item"><div class="ii">✦</div><div><b>${esc(x.subject||'Message')}</b><small>${esc(x.to)} · ${new Date(x.at).toLocaleString()}</small></div></div>`).join('')||empty('No messages sent')} function renderLog(){const h=$('#devLog');if(!h)return;h.innerHTML=state.log.slice().reverse().slice(0,30).map(x=>`<div class="item"><div class="ii">⌘</div><div class="grow"><b>${esc(x.type)}</b><small>${esc(x.severity)} · ${new Date(x.at).toLocaleString()}</small></div><code>${esc(JSON.stringify(x.payload).slice(0,100))}</code></div>`).join('')||empty('Developer log is empty')} window.removeItem=(k,i)=>{state[k].splice(i,1);save('simon.'+k,state[k]);renderAll();toast('Deleted')};window.toggleAlert=i=>{state.alerts[i].enabled=!state.alerts[i].enabled;save('simon.alerts',state.alerts);renderAll()} const forms={event:`<div class="form-grid"><div class="field full"><label>Event title</label><input id="fTitle"></div><div class="field"><label>Date & time</label><input id="fDate" type="datetime-local"></div><div class="field"><label>Status</label><select id="fStatus"><option>draft</option><option>scheduled</option><option>live</option></select></div><div class="field full"><label>Description</label><textarea id="fDesc"></textarea></div><div class="field full"><button class="primary" onclick="submitCreate('event')">Save Event</button></div></div>`,album:`<div class="form-grid"><div class="field"><label>Album title</label><input id="fTitle"></div><div class="field"><label>Visibility</label><select id="fVisibility"><option>private</option><option>followers</option><option>public</option></select></div><div class="field full"><label>Cover image URL</label><input id="fCover" placeholder="https://…"></div><div class="field full"><label>Description</label><textarea id="fDesc"></textarea></div><div class="field full"><button class="primary" onclick="submitCreate('album')">Create Album</button></div></div>`,reel:`<div class="form-grid"><div class="field"><label>Reel title</label><input id="fTitle"></div><div class="field"><label>Platform</label><select id="fPlatform"><option>SIMON</option><option>Instagram</option><option>TikTok</option><option>YouTube</option><option>Threads</option></select></div><div class="field full"><label>Video filename / URL</label><input id="fFile" placeholder="reel.mp4 or https://…"></div><div class="field"><label>Status</label><select id="fStatus"><option>draft</option><option>ready</option><option>published</option></select></div><div class="field full"><button class="primary" onclick="submitCreate('reel')">Save Reel</button></div></div>`,alert:`<div class="form-grid"><div class="field"><label>Alert name</label><input id="fTitle"></div><div class="field"><label>Source</label><input id="fSource" placeholder="USGS, CISA, all feeds"></div><div class="field full"><label>Condition</label><input id="fCondition" placeholder="magnitude >= 6, keyword contains…"></div><div class="field full"><button class="primary" onclick="submitCreate('alert')">Create Alert</button></div></div>`}; function openModal(type){$('#modalTitle').textContent={event:'Add Event',album:'Create Album',reel:'Upload Reel',alert:'New Alert'}[type];$('#modalBody').innerHTML=forms[type];$('#modal').classList.add('open')}$$('[data-open]').forEach(b=>b.onclick=()=>openModal(b.dataset.open));$('#modalClose').onclick=()=>$('#modal').classList.remove('open');$('#modal').onclick=e=>{if(e.target.id==='modal')$('#modal').classList.remove('open')}; window.submitCreate=type=>{const title=$('#fTitle').value.trim();if(!title)return toast('Title is required');if(type==='event')state.events.push({title,date:$('#fDate').value,status:$('#fStatus').value,description:$('#fDesc').value});if(type==='album')state.albums.push({title,visibility:$('#fVisibility').value,cover:$('#fCover').value,description:$('#fDesc').value,count:0});if(type==='reel')state.reels.push({title,platform:$('#fPlatform').value,file:$('#fFile').value,status:$('#fStatus').value});if(type==='alert')state.alerts.push({name:title,source:$('#fSource').value,condition:$('#fCondition').value,enabled:true});save('simon.'+(type==='event'?'events':type+'s'),state[type==='event'?'events':type+'s']);state.log.push({type:type+'_created',severity:'info',payload:{title},at:new Date().toISOString()});save('simon.devlog',state.log);$('#modal').classList.remove('open');renderAll();toast(type+' saved')} async function loadFeed(){const grid=$('#feedGrid');grid.innerHTML='<div class="panel">Loading normalized SIMON signals…</div>';try{const r=await fetch(state.settings.feed,{cache:'no-store'});const j=await r.json();const items=j.items||[];$('#mSignals').textContent=j.count??items.length;grid.innerHTML=items.slice(0,9).map((x,i)=>`<article class="card"><div class="card-media">${x.image?`<img src="${esc(x.image)}" onerror="this.remove()">`:`<img src="assets/${['gaylord-spatial-hero.png','profile-dashboard-inspiration.png','portal-inspiration.jpeg'][i%3]}">`}</div><div class="card-body"><span class="tag">${esc(x.category||'signal')}</span><h3>${esc(x.title||'Untitled')}</h3><p>${esc((x.summary||'').slice(0,150))}</p><div class="card-foot"><span>${esc(x.source||'SIMON')}</span><a href="${esc(x.url||'#')}" target="_blank" style="color:var(--cyan)">Open ↗</a></div></div></article>`).join('')||'<div class="panel">No feed items returned.</div>'}catch(e){grid.innerHTML=`<div class="panel"><b>Feed endpoint unavailable.</b><p style="color:var(--muted)">The UI still works locally. Deploy beside feed_proxy.php to enable live data.</p></div>`}}$('#loadFeed').onclick=loadFeed;$('#refreshBtn').onclick=()=>{loadFeed();toast('Refreshing SIMON')}; $('#sendMessage').onclick=async()=>{const msg={to:$('#msgTo').value,subject:$('#msgSubject').value,body:$('#msgBody').value,at:new Date().toISOString()};if(!msg.body.trim())return toast('Write a message first');state.messages.push(msg);save('simon.messages',state.messages);try{await fetch('api/send_message.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(msg)})}catch{}renderMessages();toast('Message queued and logged')}; $('#logEvent').onclick=async()=>{let payload;try{payload=JSON.parse($('#logPayload').value)}catch{return toast('Payload must be valid JSON')};const rec={type:$('#logType').value,severity:$('#logSeverity').value,payload,at:new Date().toISOString()};state.log.push(rec);save('simon.devlog',state.log);try{await fetch('api/log_event.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(rec)})}catch{}renderLog();toast('Event logged')};$('#clearLog').onclick=()=>{state.log=[];save('simon.devlog',state.log);renderLog()}; $('#saveSettings').onclick=()=>{state.settings={feed:$('#setFeed').value,webhook:$('#setWebhook').value,refresh:+$('#setRefresh').value};save('simon.settings',state.settings);setupTimer();toast('Settings saved')};function setupTimer(){clearInterval(state.timer);if(state.settings.refresh)state.timer=setInterval(loadFeed,state.settings.refresh)} $('#quickCreate').onclick=()=>openModal('event');$('#globalSearch').oninput=e=>{const q=e.target.value.toLowerCase();$$('.card,.module,.item').forEach(el=>el.style.display=el.textContent.toLowerCase().includes(q)?'':'none')}; renderAll();loadFeed();setupTimer(); </script> </body></html>
Save file
Quick jump
open a path
Open