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,282
Folders
406
Scanned Size
3.84 GB
PHP Files
876
Editable Text Files
12,545
File viewer
guarded to /htdocs
/live/news_reader.php
<?php declare(strict_types=1); require_once __DIR__ . '/bootstrap.php'; $feeds = simon_read_json_file(SIMON_FEEDS_FILE, []); $feedCount = is_array($feeds) ? count(array_filter($feeds, fn($feed) => is_array($feed) && !empty($feed['enabled']))) : 0; ?> <!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="#17171a"> <title>SIMON Daily</title> <style> :root{color-scheme:dark;--bg:#17171a;--rail:#252123;--panel:#303033;--text:#f7f7fa;--muted:#aaa7ad;--line:rgba(255,255,255,.09);--accent:#4be3ff;--accent2:#7a5cff;--pink:#ff3f7d;--green:#34f0c8;--radius:18px} *{box-sizing:border-box}html{background:var(--bg)}body{margin:0;background:var(--bg);color:var(--text);font:15px/1.38 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;min-height:100vh}button,input{font:inherit}button{color:inherit}.app{display:grid;grid-template-columns:300px minmax(0,1fr);min-height:100vh}.sidebar{position:sticky;top:0;height:100vh;padding:24px 14px;background:linear-gradient(180deg,#292426,#211f21);border-right:1px solid var(--line);overflow:auto}.brand{display:flex;align-items:center;gap:11px;padding:6px 10px 22px}.brand-mark{display:grid;place-items:center;width:38px;height:38px;border-radius:12px;background:conic-gradient(from 220deg,var(--accent2),var(--accent),var(--pink),var(--accent2));color:#070710;font-weight:1000;box-shadow:0 0 26px rgba(75,227,255,.18)}.brand b{font-size:20px}.brand small{display:block;color:var(--muted);font-size:11px;letter-spacing:.08em;text-transform:uppercase}.nav{display:grid;gap:4px}.nav-button,.source-button{display:flex;align-items:center;gap:11px;width:100%;border:0;border-radius:11px;padding:10px 12px;background:transparent;text-align:left;font-weight:750;cursor:pointer}.nav-button:hover,.source-button:hover{background:rgba(255,255,255,.06)}.nav-button.active{background:linear-gradient(110deg,var(--pink),#e93375)}.nav-icon{width:24px;text-align:center;font-size:18px}.count{margin-left:auto;color:var(--muted);font-variant-numeric:tabular-nums}.active .count{color:#fff}.section-label{display:flex;align-items:center;justify-content:space-between;margin:22px 10px 8px;color:var(--muted);font-size:12px;font-weight:800;letter-spacing:.04em}.source-list{display:grid;gap:2px}.source-button{font-weight:650}.source-dot{width:24px;height:24px;border-radius:7px;display:grid;place-items:center;color:#080814;font-size:11px;font-weight:900}.main{min-width:0;padding:38px 42px 70px}.topbar{display:flex;align-items:center;gap:16px;min-height:54px}.menu-button{display:none;border:0;background:var(--panel);width:42px;height:42px;border-radius:12px}.headline{font-size:36px;letter-spacing:-.045em;margin:0}.headline span{color:var(--muted);font-weight:650}.live-pill{margin-left:auto;display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:999px;padding:7px 11px;color:var(--muted);font-size:12px}.live-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 12px var(--green)}.search-wrap{display:none;margin:18px 0}.search-wrap.open{display:block}.search-input{width:100%;border:1px solid var(--line);background:#242427;color:var(--text);border-radius:14px;padding:13px 15px;outline:none}.search-input:focus{border-color:var(--accent)}.section-title{margin:28px 0 16px;color:var(--pink);font-size:26px;letter-spacing:-.03em}.status{display:none;margin:20px 0;border:1px solid var(--line);border-radius:14px;background:#242427;padding:13px 15px;color:var(--muted)}.status.show{display:block}.hero-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.story-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}.story-card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:var(--panel);box-shadow:0 14px 34px rgba(0,0,0,.18);transition:transform .18s ease,border-color .18s ease}.story-card:hover{transform:translateY(-3px);border-color:rgba(255,255,255,.2)}.story-link{color:inherit;text-decoration:none}.story-image{position:relative;aspect-ratio:16/10;background:radial-gradient(circle at 75% 25%,var(--card-color,#7a5cff),transparent 28%),linear-gradient(145deg,#151526,#30304a);overflow:hidden}.story-image img{width:100%;height:100%;object-fit:cover;display:block}.fallback{position:absolute;inset:0;display:grid;place-items:center}.fallback span{font-size:clamp(36px,6vw,74px);font-weight:1000;opacity:.7}.story-body{padding:14px 15px 17px}.story-source{display:flex;align-items:center;gap:7px;color:#ddd;font-size:11px;font-weight:800;margin-bottom:8px}.source-mini{width:20px;height:20px;border-radius:6px;display:grid;place-items:center;background:var(--card-color,#7a5cff);color:#070710}.story-title{margin:0;font-size:19px;line-height:1.14;letter-spacing:-.025em;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.story-meta{display:flex;align-items:center;gap:8px;margin-top:12px;color:var(--muted);font-size:11px}.save-button{position:absolute;right:10px;top:10px;z-index:3;width:36px;height:36px;border:1px solid rgba(255,255,255,.18);border-radius:50%;background:rgba(15,15,18,.75);backdrop-filter:blur(8px);cursor:pointer}.save-button.saved{background:var(--pink);border-color:var(--pink)}.small-card .story-title{font-size:16px}.small-card .story-body{padding:12px}.empty{grid-column:1/-1;border:1px dashed var(--line);border-radius:var(--radius);padding:42px;text-align:center;color:var(--muted)}.loading{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}.skeleton{height:390px;border-radius:var(--radius);background:linear-gradient(100deg,#252529 25%,#34343a 45%,#252529 65%);background-size:220% 100%;animation:shimmer 1.2s infinite}@keyframes shimmer{to{background-position:-220% 0}}.footer{margin-top:42px;padding-top:22px;border-top:1px solid var(--line);display:flex;justify-content:space-between;gap:15px;color:var(--muted);font-size:12px} @media(max-width:1200px){.story-grid{grid-template-columns:repeat(3,1fr)}.main{padding-inline:28px}.app{grid-template-columns:260px minmax(0,1fr)}}@media(max-width:860px){.app{display:block}.sidebar{position:fixed;z-index:20;left:0;top:0;width:min(310px,86vw);transform:translateX(-105%);transition:transform .2s ease;box-shadow:30px 0 70px rgba(0,0,0,.45)}.sidebar.open{transform:translateX(0)}.menu-button{display:grid;place-items:center}.main{padding:22px 18px 60px}.headline{font-size:29px}.hero-grid,.story-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.live-pill{padding:7px 9px}}@media(max-width:560px){.hero-grid,.story-grid,.loading{grid-template-columns:1fr}.headline{font-size:25px}.main{padding-inline:13px}.section-title{font-size:22px}.footer{display:block}.footer span{display:block;margin-top:6px}} </style> </head> <body> <div class="app"> <aside class="sidebar" id="sidebar" aria-label="SIMON Daily navigation"> <div class="brand"><div class="brand-mark">S</div><div><b>SIMON Daily</b><small><?=$feedCount?> connected sources</small></div></div> <nav class="nav"> <button class="nav-button" id="searchButton"><span class="nav-icon">⌕</span>Search</button> <button class="nav-button active" data-view="today"><span class="nav-icon">◫</span>Today <span class="count" id="todayCount">—</span></button> <button class="nav-button" data-view="social"><span class="nav-icon">◎</span>Social</button> </nav> <div class="section-label"><span>Library</span></div> <nav class="nav"> <button class="nav-button" data-view="saved"><span class="nav-icon">▮</span>Saved Stories <span class="count" id="savedCount">0</span></button> <button class="nav-button" data-view="history"><span class="nav-icon">◷</span>History <span class="count" id="historyCount">0</span></button> </nav> <div class="section-label"><span>Favorites</span><span>⌄</span></div> <div class="source-list" id="categoryList"></div> <div class="section-label"><span>System</span></div> <nav class="nav"> <a class="nav-button" href="feed_manager.php" style="text-decoration:none"><span class="nav-icon">⚙</span>Feed Manager</a> <a class="nav-button" href="index.php" style="text-decoration:none"><span class="nav-icon">◇</span>Command Center</a> </nav> </aside> <main class="main"> <div class="topbar"> <button class="menu-button" id="menuButton" aria-label="Open navigation">☰</button> <h1 class="headline">SIMON <span id="dateLabel">Daily</span></h1> <div class="live-pill"><span class="live-dot"></span><span id="liveText">Connecting</span></div> </div> <div class="search-wrap" id="searchWrap"><input class="search-input" id="searchInput" type="search" placeholder="Search stories, sources, and categories…" autocomplete="off"></div> <div class="status show" id="status">Loading your feed library…</div> <section id="content" aria-live="polite"><h2 class="section-title">Top Stories</h2><div class="loading"><div class="skeleton"></div><div class="skeleton"></div><div class="skeleton"></div></div></section> <footer class="footer"><span>SIMON Daily · Your independent feed library</span><span>Stories open at their original publishers</span></footer> </main> </div> <script> const state={items:[],view:'today',query:'',saved:new Map(),history:[]}; const colors={news:'#4be3ff',world:'#7a5cff',technology:'#ffb347',ai:'#b09cff',science:'#7cff9e',space:'#7a5cff',weather:'#34f0c8',earth:'#ff6b8f',security:'#ff5bd1',government:'#ff6b8f',finance:'#34f0c8',entertainment:'#ff5bd1',social:'#eaf2ff',simon:'#9df4ff'}; const icons={news:'N',world:'W',technology:'T',ai:'AI',science:'S',space:'✦',weather:'☁',earth:'E',security:'!',government:'G',finance:'$',entertainment:'▶',social:'@',simon:'S'}; const content=document.getElementById('content'),statusBox=document.getElementById('status'),sidebar=document.getElementById('sidebar'); function loadLocal(){try{for(const item of JSON.parse(localStorage.getItem('simonDailySaved')||'[]'))state.saved.set(item.id,item);state.history=JSON.parse(localStorage.getItem('simonDailyHistory')||'[]')}catch(e){state.saved=new Map();state.history=[]}updateCounts()} function saveLocal(){localStorage.setItem('simonDailySaved',JSON.stringify([...state.saved.values()]));localStorage.setItem('simonDailyHistory',JSON.stringify(state.history.slice(0,80)));updateCounts()} function updateCounts(){document.getElementById('savedCount').textContent=state.saved.size;document.getElementById('historyCount').textContent=state.history.length;document.getElementById('todayCount').textContent=state.items.length||'—'} function cleanItem(item){return{...item,title:(item.title||'Untitled story').trim(),source:(item.source||'Unknown source').trim(),category:(item.category||'general').trim(),url:item.url||'#',image:item.image||''}} function timeAgo(value){const then=Date.parse(value);if(!then)return'';const hours=Math.max(0,Math.floor((Date.now()-then)/3600000));if(hours<1)return'Just now';if(hours<24)return hours+'h ago';return Math.floor(hours/24)+'d ago'} function initial(value){const parts=value.trim().split(/\s+/);return(parts.length>1?parts.slice(0,2).map(v=>v[0]).join(''):value.slice(0,2)).toUpperCase()} function storyCard(item,small=false){item=cleanItem(item);const card=document.createElement('article');card.className='story-card'+(small?' small-card':'');card.style.setProperty('--card-color',item.color||colors[item.category]||'#7a5cff');const save=document.createElement('button');save.className='save-button'+(state.saved.has(item.id)?' saved':'');save.type='button';save.setAttribute('aria-label',state.saved.has(item.id)?'Remove saved story':'Save story');save.textContent=state.saved.has(item.id)?'✓':'+';save.addEventListener('click',e=>{e.preventDefault();e.stopPropagation();state.saved.has(item.id)?state.saved.delete(item.id):state.saved.set(item.id,item);saveLocal();render()});const link=document.createElement('a');link.className='story-link';link.href=item.url;link.target='_blank';link.rel='noopener noreferrer';link.addEventListener('click',()=>{state.history=[item,...state.history.filter(v=>v.id!==item.id)];saveLocal()});const imageWrap=document.createElement('div');imageWrap.className='story-image';const fallback=document.createElement('div');fallback.className='fallback';const fallbackText=document.createElement('span');fallbackText.textContent=initial(item.source);fallback.append(fallbackText);imageWrap.append(fallback);if(item.image){const img=document.createElement('img');img.src=item.image;img.alt='';img.loading='lazy';img.addEventListener('error',()=>img.remove());imageWrap.append(img)}const body=document.createElement('div');body.className='story-body';const source=document.createElement('div');source.className='story-source';const mini=document.createElement('span');mini.className='source-mini';mini.textContent=initial(item.source);const sourceName=document.createElement('span');sourceName.textContent=item.source;source.append(mini,sourceName);const title=document.createElement('h3');title.className='story-title';title.textContent=item.title;const meta=document.createElement('div');meta.className='story-meta';const category=document.createElement('span');category.textContent=item.category.replaceAll('_',' ');const age=document.createElement('span');age.textContent=timeAgo(item.published);meta.append(category);if(age.textContent)meta.append(' · ',age);body.append(source,title,meta);link.append(imageWrap,body);card.append(save,link);return card} function section(title,items,hero=false){const frag=document.createDocumentFragment();const heading=document.createElement('h2');heading.className='section-title';heading.textContent=title;const grid=document.createElement('div');grid.className=hero?'hero-grid':'story-grid';if(!items.length){const empty=document.createElement('div');empty.className='empty';empty.textContent=state.view==='saved'?'Save a story and it will appear here.':state.view==='history'?'Stories you open will appear here.':'No stories match this view.';grid.append(empty)}else items.forEach(item=>grid.append(storyCard(item,!hero)));frag.append(heading,grid);return frag} function filteredItems(){let items=state.items;if(state.view==='saved')items=[...state.saved.values()];else if(state.view==='history')items=state.history;else if(state.view!=='today')items=items.filter(item=>item.category===state.view);if(state.query){const q=state.query.toLowerCase();items=items.filter(item=>(item.title+' '+item.summary+' '+item.source+' '+item.category).toLowerCase().includes(q))}return items} function render(){const items=filteredItems();content.replaceChildren();let label='Today';if(state.view==='saved')label='Saved Stories';else if(state.view==='history')label='History';else if(state.view!=='today')label=state.view[0].toUpperCase()+state.view.slice(1);if(state.query)label='Search Results';if(state.view==='today'&&!state.query){const imageFirst=[...items].sort((a,b)=>(b.image?1:0)-(a.image?1:0)||b.score-a.score);const hero=imageFirst.slice(0,3),ids=new Set(hero.map(v=>v.id));content.append(section('Top Stories',hero,true));content.append(section('Latest',items.filter(v=>!ids.has(v.id)).slice(0,28),false))}else content.append(section(label,items.slice(0,60),false));document.querySelectorAll('[data-view]').forEach(button=>button.classList.toggle('active',button.dataset.view===state.view));updateCounts()} function buildCategories(){const counts={};state.items.forEach(item=>counts[item.category]=(counts[item.category]||0)+1);const list=document.getElementById('categoryList');list.replaceChildren();Object.entries(counts).sort((a,b)=>b[1]-a[1]).forEach(([category,count])=>{const button=document.createElement('button');button.className='source-button';button.dataset.view=category;const dot=document.createElement('span');dot.className='source-dot';dot.style.background=colors[category]||'#7a5cff';dot.textContent=icons[category]||category[0].toUpperCase();const name=document.createElement('span');name.textContent=category[0].toUpperCase()+category.slice(1);const badge=document.createElement('span');badge.className='count';badge.textContent=count;button.append(dot,name,badge);list.append(button)})} document.addEventListener('click',event=>{const button=event.target.closest('[data-view]');if(!button)return;state.view=button.dataset.view;state.query='';document.getElementById('searchInput').value='';document.getElementById('searchWrap').classList.remove('open');sidebar.classList.remove('open');render()}); document.getElementById('searchButton').addEventListener('click',()=>{const wrap=document.getElementById('searchWrap');wrap.classList.toggle('open');if(wrap.classList.contains('open'))document.getElementById('searchInput').focus()}); document.getElementById('searchInput').addEventListener('input',event=>{state.query=event.target.value.trim();render()});document.getElementById('menuButton').addEventListener('click',()=>sidebar.classList.toggle('open')); const now=new Date();document.getElementById('dateLabel').textContent=now.toLocaleDateString(undefined,{month:'long',day:'numeric'});loadLocal(); fetch('feed_proxy.php?limit=300').then(response=>{if(!response.ok)throw new Error('Feed service unavailable');return response.json()}).then(data=>{state.items=(data.items||[]).map(cleanItem).filter(item=>item.title!=='Untitled story'&&item.url!=='#');buildCategories();document.getElementById('liveText').textContent=state.items.length+' live stories';statusBox.classList.remove('show');render()}).catch(error=>{statusBox.textContent='SIMON could not load the live feed. '+error.message;document.getElementById('liveText').textContent='Offline';content.replaceChildren(section('Feed unavailable',[],false))}); </script> </body> </html>
Save file
Quick jump
open a path
Open