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,559
Folders
418
Scanned Size
3.85 GB
PHP Files
922
Editable Text Files
12,820
File viewer
guarded to /htdocs
/connlink/test1/ui/old/combat/compat.php
<?php declare(strict_types=1); /** * SIMON Unified Compat + AI + Graph * Location: /htdocs/connlink/test1/ui/compat/compat.php * Reads: /htdocs/connlink/test1/ui/feeds.json * Does not replace index.php. */ ini_set('display_errors','1'); ini_set('display_startup_errors','1'); error_reporting(E_ALL); if (!function_exists('mb_substr')) { function mb_substr($s,$start,$len=null,$enc=null) { return $len === null ? substr((string)$s,(int)$start) : substr((string)$s,(int)$start,(int)$len); } } function sx_h($v): string { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); } function sx_ui_dir(): string { return dirname(__DIR__); } function sx_path(string $rel): string { return rtrim(sx_ui_dir(), '/\\') . '/' . ltrim($rel, '/\\'); } function sx_json_file(): string { return sx_path('feeds.json'); } function sx_read_json(string $file, $fallback = []) { if (!is_file($file)) return $fallback; $raw = @file_get_contents($file); $json = json_decode((string)$raw, true); return is_array($json) ? $json : $fallback; } function sx_feeds(): array { $data = sx_read_json(sx_json_file(), []); $feeds = []; foreach ($data as $row) { if (!is_array($row)) continue; $id = (string)($row['id'] ?? ''); if ($id === '') continue; $feeds[] = [ 'id' => $id, 'name' => (string)($row['name'] ?? ucfirst(str_replace('_',' ', $id))), 'category' => (string)($row['category'] ?? 'general'), 'engine' => (string)($row['engine'] ?? ''), 'enabled' => !empty($row['enabled']), 'url' => (string)($row['url'] ?? ''), 'stream' => (string)($row['stream'] ?? ''), 'place' => (string)($row['place'] ?? ''), 'lat' => $row['lat'] ?? null, 'lon' => $row['lon'] ?? null, 'refresh' => (int)($row['refresh'] ?? 900), 'priority' => (int)($row['priority'] ?? 50), 'color' => (string)($row['color'] ?? '#4be3ff'), 'params' => isset($row['params']) && is_array($row['params']) ? $row['params'] : [], 'map' => isset($row['map']) && is_array($row['map']) ? $row['map'] : [], 'raw' => $row, ]; } usort($feeds, fn($a,$b) => ($b['priority'] <=> $a['priority']) ?: strcmp($a['name'],$b['name'])); return $feeds; } function sx_provider_dir(): string { return sx_path('providers'); } function sx_engines(): array { $out = []; foreach (glob(sx_provider_dir().'/*.php') ?: [] as $file) $out[] = basename($file,'.php'); sort($out); return $out; } function sx_route(array $feed): string { if ($feed['url'] !== '') return $feed['url']; if ($feed['stream'] !== '') return 'stream:'.$feed['stream']; if ($feed['place'] !== '') return $feed['place']; if ($feed['lat'] !== null || $feed['lon'] !== null) return 'lat/lon:'.$feed['lat'].','.$feed['lon']; return ''; } function sx_live_url(array $feed): string { return '../feed_proxy.php?source='.rawurlencode($feed['id']).'&limit=50&force=1'; } function sx_cache_url(array $feed): string { return '../feed_proxy_cached.php?source='.rawurlencode($feed['id']).'&limit=100'; } function sx_abs_url(string $relative): string { if (preg_match('~^https?://~i',$relative)) return $relative; $base = rtrim(dirname(dirname($_SERVER['SCRIPT_NAME'] ?? '/connlink/test1/ui/compat/compat.php')), '/'); if (str_starts_with($relative, '../')) $path = $base . '/' . substr($relative, 3); elseif (str_starts_with($relative, '/')) $path = $relative; else $path = $base . '/' . ltrim($relative,'/'); $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; return $scheme.'://'.$host.$path; } function sx_fetch_json_url(string $url): array { $started = microtime(true); $ctx = stream_context_create(['http'=>[ 'method'=>'GET', 'timeout'=>20, 'header'=>"Accept: application/json\r\nUser-Agent: SIMON-Unified-Compat/2.1\r\n" ]]); $raw = @file_get_contents($url, false, $ctx); $ms = (int)round((microtime(true)-$started)*1000); if ($raw === false) return ['ok'=>false,'json'=>null,'ms'=>$ms,'error'=>'request_failed','raw'=>null]; $json = json_decode($raw,true); if (!is_array($json)) return ['ok'=>false,'json'=>null,'ms'=>$ms,'error'=>'non_json_response','raw'=>mb_substr((string)$raw,0,500)]; return ['ok'=>true,'json'=>$json,'ms'=>$ms,'error'=>null,'raw'=>null]; } function sx_extract_items(array $json): array { foreach (['items','articles','events','results','features','data'] as $k) { if (isset($json[$k]) && is_array($json[$k])) return $json[$k]; } if (isset($json['health']) && is_array($json['health'])) return $json['health']; return array_is_list($json) ? $json : []; } function sx_item_title(array $item): string { return (string)($item['title'] ?? $item['name'] ?? $item['headline'] ?? $item['subject'] ?? $item['event'] ?? ($item['properties']['title'] ?? '(untitled)')); } function sx_item_summary(array $item): string { return (string)($item['summary'] ?? $item['snippet'] ?? $item['description'] ?? $item['details'] ?? $item['message'] ?? $item['text'] ?? $item['body'] ?? ($item['properties']['place'] ?? '')); } function sx_item_url(array $item): string { return (string)($item['url'] ?? $item['link'] ?? $item['href'] ?? ($item['links']['webcast'] ?? '#')); } function sx_words(string $text): array { $text = strtolower(strip_tags(html_entity_decode($text, ENT_QUOTES | ENT_HTML5))); $text = preg_replace('/https?:\/\/\S+/',' ', $text); $text = preg_replace('/[^a-z0-9\s\-]/',' ', $text); $parts = preg_split('/\s+/', (string)$text, -1, PREG_SPLIT_NO_EMPTY) ?: []; $stop = array_flip(['the','and','for','with','from','this','that','they','them','their','then','than','have','has','had','will','would','could','should','about','into','over','under','after','before','also','more','most','some','such','when','where','what','which','while','were','was','are','its','itself','news','http','https','www','com','org','html','said','says','new','top','latest','today','week','daily','official','press','release','story','article','video','photo','image','using','used','use','get','got','one','two','three','your','you','our','out','not','but','can','all','any','may']); $counts = []; foreach ($parts as $p) { $p = trim($p, "- \t\n\r\0\x0B"); if (strlen($p) < 4) continue; if (isset($stop[$p])) continue; if (preg_match('/^\d+$/',$p)) continue; $counts[$p] = ($counts[$p] ?? 0) + 1; } arsort($counts); return $counts; } function sx_topic(string $text, string $category=''): string { $t = strtolower($text.' '.$category); $rules = [ 'Weather / Alert' => ['storm','weather','alert','forecast','wind','rain','flood','temperature','warning','hurricane','tornado'], 'Space / NASA' => ['space','nasa','spacex','launch','rocket','orbit','asteroid','solar','moon','mars','earth','satellite'], 'Technology / AI' => ['ai','artificial','intelligence','openai','github','software','developer','model','data','code','security','cyber'], 'Finance / Market' => ['coin','crypto','bitcoin','market','price','finance','exchange','rate','treasury','stock'], 'Earth / Disaster' => ['earthquake','quake','volcano','fire','wildfire','disaster','fema','magnitude'], 'Culture / Knowledge' => ['book','poem','poetry','history','library','author','wiki'], 'SIMON / Internal' => ['simon','webhook','visit','stream','internal'], 'News / World' => ['world','government','war','election','policy','president','court','global','country'] ]; foreach ($rules as $topic=>$words) { foreach ($words as $w) if (str_contains($t,$w)) return $topic; } return ucfirst($category ?: 'General'); } $feeds = sx_feeds(); $engines = sx_engines(); $engineSet = array_fill_keys($engines,true); $action = (string)($_GET['action'] ?? ''); $source = (string)($_GET['source'] ?? ''); $q = strtolower(trim((string)($_GET['q'] ?? ''))); $feedData = []; $events = []; $enabled = 0; $disabled = 0; $missingProviders = []; $categories = []; $engineCounts = []; $topics = []; foreach ($feeds as $feed) { if ($feed['enabled']) $enabled++; else $disabled++; $categories[$feed['category']] = ($categories[$feed['category']] ?? 0) + 1; $engineCounts[$feed['engine']] = ($engineCounts[$feed['engine']] ?? 0) + 1; if ($feed['engine'] !== '' && !isset($engineSet[$feed['engine']])) $missingProviders[$feed['engine']] = true; } foreach ($feeds as $feed) { if (!$feed['enabled']) continue; if ($source !== '' && $feed['id'] !== $source) continue; if ($source === '' && $action !== 'load_all') continue; $res = sx_fetch_json_url(sx_abs_url(sx_cache_url($feed))); if (!$res['ok']) $res = sx_fetch_json_url(sx_abs_url(sx_live_url($feed))); $items = $res['json'] ? sx_extract_items($res['json']) : []; $feedData[$feed['id']] = ['feed'=>$feed,'result'=>$res,'items'=>$items]; if ($source !== '') break; } foreach ($feedData as $bundle) { $feed = $bundle['feed']; foreach ($bundle['items'] as $item) { if (!is_array($item)) continue; $title = sx_item_title($item); $summary = sx_item_summary($item); $url = sx_item_url($item); $text = trim($title.' '.$summary); $topic = sx_topic($text, $feed['category']); $topics[$topic] = ($topics[$topic] ?? 0) + 1; $events[] = [ 'source_id'=>$feed['id'], 'source'=>$feed['name'], 'category'=>$feed['category'], 'engine'=>$feed['engine'], 'color'=>$feed['color'], 'topic'=>$topic, 'title'=>$title, 'summary'=>$summary, 'url'=>$url, 'keywords'=>array_slice(array_keys(sx_words($text)),0,10), 'signature'=>substr(sha1(strtolower($feed['id'].'|'.$title.'|'.$url)),0,12), ]; } } $wordCounts = []; foreach ($events as $event) { $counts = sx_words($event['title'].' '.$event['summary'].' '.implode(' ', $event['keywords'])); foreach ($counts as $word=>$count) $wordCounts[$word] = ($wordCounts[$word] ?? 0) + $count; } arsort($wordCounts); arsort($topics); arsort($categories); arsort($engineCounts); if ($q !== '') { $events = array_values(array_filter($events, function($e) use ($q) { $hay = strtolower($e['source'].' '.$e['category'].' '.$e['topic'].' '.$e['title'].' '.$e['summary'].' '.implode(' ', $e['keywords'])); return str_contains($hay, $q); })); } $graphNodes = [['id'=>'SIMON','label'=>'SIMON','type'=>'core','size'=>32,'color'=>'#4be3ff']]; $graphEdges = []; foreach ($categories as $cat=>$count) { $id = 'cat_'.substr(sha1($cat),0,8); $graphNodes[] = ['id'=>$id,'label'=>$cat,'type'=>'category','size'=>$count,'color'=>'#ffb347']; $graphEdges[] = ['from'=>'SIMON','to'=>$id]; } foreach ($feeds as $feed) { $catId = 'cat_'.substr(sha1($feed['category']),0,8); $fid = 'feed_'.$feed['id']; $graphNodes[] = ['id'=>$fid,'label'=>$feed['name'],'type'=>$feed['enabled']?'feed':'disabled','size'=>max(8, $feed['priority']/5),'color'=>$feed['color']]; $graphEdges[] = ['from'=>$catId,'to'=>$fid]; } foreach (array_slice($wordCounts,0,18,true) as $word=>$count) { $wid = 'word_'.substr(sha1($word),0,8); $graphNodes[] = ['id'=>$wid,'label'=>$word,'type'=>'word','size'=>$count,'color'=>'#ff5bd1']; $graphEdges[] = ['from'=>'SIMON','to'=>$wid]; } $topWord = array_key_first($wordCounts) ?: 'none'; if (($_GET['format'] ?? '') === 'json') { header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok'=>true,'mode'=>'unified_compat_ai_graph','feeds'=>count($feeds),'enabled'=>$enabled,'disabled'=>$disabled,'missing_providers'=>array_keys($missingProviders),'categories'=>$categories,'engines'=>$engineCounts,'topics'=>$topics,'top_word'=>$topWord,'word_counts'=>array_slice($wordCounts,0,100,true),'events'=>$events,'graph'=>['nodes'=>$graphNodes,'edges'=>$graphEdges]], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); exit; } $aiAdvice = []; if ($missingProviders) $aiAdvice[] = 'Provider modules missing: '.implode(', ', array_keys($missingProviders)).'. These feeds are listed, but provider files are not present.'; if (!$events) $aiAdvice[] = 'No feed items loaded yet. Click Load All or choose a single feed to populate AI analysis.'; if ($events) $aiAdvice[] = 'Dominant noun/keyword: '.$topWord.'. Main topics: '.implode(', ', array_slice(array_keys($topics),0,5)).'.'; if ($disabled) $aiAdvice[] = $disabled.' feeds are disabled. Review whether they should be diagnostics or production feeds.'; if (!$missingProviders && $events) $aiAdvice[] = 'Feed registry and content analysis are healthy. Next step: connect this page to job scheduling and cache history.'; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title>SIMON Unified Compat AI Graph</title> <style> :root{--bg:#05041a;--panel:rgba(16,14,48,.82);--panel2:rgba(10,8,32,.92);--text:#eaf2ff;--muted:#9aa3c4;--line:rgba(75,227,255,.24);--cyan:#4be3ff;--green:#7cff9e;--amber:#ffb347;--red:#ff6b8f;--pink:#ff5bd1;--vio:#7a5cff} *{box-sizing:border-box}html,body{margin:0;min-height:100%;background:#05041a;color:var(--text);font:14px/1.45 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}body{padding:20px;overflow-x:hidden} #nebula{position:fixed;inset:0;z-index:-4;background:radial-gradient(900px 600px at 20% 0%,rgba(122,92,255,.25),transparent 60%),radial-gradient(700px 500px at 100% 10%,rgba(255,91,209,.18),transparent 60%),linear-gradient(180deg,#05041a,#08072a)}#stars{position:fixed;inset:0;z-index:-3;width:100%;height:100%} body:after{content:"";position:fixed;inset:0;z-index:-2;background:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);background-size:42px 42px;opacity:.15;mask-image:radial-gradient(circle,#000,transparent 80%)} .wrap{max-width:1480px;margin:0 auto}.top{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:16px}.brand{display:flex;align-items:center;gap:14px}.logo{width:58px;height:58px;border-radius:18px;background:conic-gradient(from 220deg,var(--vio),var(--cyan),var(--pink),var(--vio));box-shadow:0 0 42px rgba(75,227,255,.32);animation:pulse 3.7s ease-in-out infinite}@keyframes pulse{50%{filter:brightness(1.24);transform:translateY(-2px)}}h1{margin:0;font-size:32px;letter-spacing:-.03em}.sub,.muted,.small{color:var(--muted)}.kicker{color:var(--muted);font-size:11px;letter-spacing:.18em;text-transform:uppercase} .grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;margin:16px 0}.card,.panel{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:20px;padding:16px;box-shadow:0 18px 48px rgba(0,0,0,.35);backdrop-filter:blur(14px)}.card .k{color:var(--muted);font-size:11px;letter-spacing:.12em;text-transform:uppercase}.card .v{font-size:30px;font-weight:950;margin-top:4px}.actions{display:flex;gap:9px;flex-wrap:wrap;margin:10px 0}a.btn,button.btn{display:inline-flex;align-items:center;gap:8px;padding:10px 13px;border-radius:999px;border:1px solid var(--line);background:rgba(75,227,255,.08);color:var(--text);text-decoration:none;font:inherit;cursor:pointer;font-weight:800}.online{color:var(--green)}.warn{color:var(--amber)}.bad{color:var(--red)}.cyan{color:var(--cyan)}.pink{color:var(--pink)} .layout{display:grid;grid-template-columns:1.15fr .85fr;gap:14px;align-items:start}.cols{display:grid;grid-template-columns:1fr 1fr;gap:14px}.graphbox{height:620px;position:relative;overflow:hidden;border-radius:18px;border:1px solid rgba(255,255,255,.08);background:radial-gradient(circle at 50% 50%,rgba(75,227,255,.08),transparent 45%),rgba(0,0,0,.13)}canvas{display:block;width:100%;height:100%}.graphHint{position:absolute;left:14px;bottom:14px;color:var(--muted);font-size:12px;background:rgba(0,0,0,.25);border:1px solid rgba(255,255,255,.08);border-radius:999px;padding:8px 10px} .chips{display:flex;gap:7px;flex-wrap:wrap;margin-top:8px}.chip{font-size:12px;padding:4px 9px;border-radius:999px;border:1px solid rgba(75,227,255,.18);color:var(--cyan);text-decoration:none;background:rgba(75,227,255,.04)}.wordCloud{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.wordCloud a{text-decoration:none;color:var(--text);border:1px solid rgba(255,255,255,.08);border-radius:999px;padding:5px 10px;background:rgba(255,255,255,.04)} .list{display:grid;gap:10px}.event{border:1px solid rgba(255,255,255,.08);border-left:5px solid var(--cyan);border-radius:16px;background:rgba(255,255,255,.035);padding:14px}.event h3{margin:0 0 6px;font-size:15px}.event p{margin:8px 0 0;color:#cbd7f3} table{width:100%;border-collapse:separate;border-spacing:0 9px}th{text-align:left;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.12em;padding:0 10px}td{background:rgba(16,14,48,.72);border-top:1px solid rgba(255,255,255,.07);border-bottom:1px solid rgba(255,255,255,.07);padding:12px 10px;vertical-align:top}td:first-child{border-left:1px solid rgba(255,255,255,.07);border-radius:14px 0 0 14px;font-weight:800}td:last-child{border-right:1px solid rgba(255,255,255,.07);border-radius:0 14px 14px 0}.pill{display:inline-flex;padding:4px 9px;border-radius:999px;border:1px solid rgba(255,255,255,.1);font-size:12px}input,select{border:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.22);color:var(--text);border-radius:999px;padding:10px 13px;min-width:220px} .imenu{position:fixed;right:18px;top:18px;z-index:30}.iorb{width:48px;height:48px;border-radius:999px;border:1px solid rgba(75,227,255,.45);background:radial-gradient(circle at 30% 25%,rgba(255,255,255,.28),rgba(75,227,255,.12) 40%,rgba(16,14,48,.92));color:#fff;font-weight:950;font-size:24px;box-shadow:0 0 34px rgba(75,227,255,.28);cursor:pointer}.imenuPanel{display:none;position:absolute;right:0;top:58px;width:280px;background:rgba(10,8,32,.96);border:1px solid var(--line);border-radius:20px;padding:12px;box-shadow:0 22px 70px rgba(0,0,0,.45)}.imenu.open .imenuPanel{display:grid;gap:8px}.imenuPanel a{color:var(--text);text-decoration:none;border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:10px;background:rgba(255,255,255,.035)}.imenuPanel a span{display:block;color:var(--muted);font-size:12px} @media(max-width:1100px){.grid{grid-template-columns:repeat(2,1fr)}.layout,.cols{grid-template-columns:1fr}.graphbox{height:520px}}@media(max-width:650px){body{padding:14px}.grid{grid-template-columns:1fr}h1{font-size:26px}input,select{width:100%;min-width:0}.imenu{right:12px;top:12px}} </style> </head> <body> <div id="nebula"></div><canvas id="stars"></canvas> <div class="imenu" id="imenu"><button class="iorb" type="button" onclick="document.getElementById('imenu').classList.toggle('open')">I</button><div class="imenuPanel"> <a href="../index.php"><b>Main Index</b><span>Existing SIMON feed page</span></a><a href="../feed_manager.php"><b>Feed Manager</b><span>Edit/import carriers</span></a><a href="../jobs_dashboard.php"><b>Jobs Dashboard</b><span>Run and inspect jobs</span></a><a href="../ai_dashboard.php"><b>AI Dashboard</b><span>Separate AI feed view</span></a><a href="../graph_dashboard.php"><b>Graph Dashboard</b><span>Separate graph view</span></a><a href="../feed_proxy.php?limit=100"><b>Live JSON</b><span>Live feed proxy</span></a><a href="../feed_proxy_cached.php?limit=100"><b>Cached JSON</b><span>Cached feed proxy</span></a><a href="../feeds.json"><b>feeds.json</b><span>Carrier list</span></a><a href="/"><b>Site Home</b><span>Gaylord Sinclair</span></a><a href="/web360/"><b>Web360</b><span>Web360 system</span></a><a href="/arcade/"><b>Arcade</b><span>SIMON Arcade</span></a><a href="/books/"><b>Books</b><span>Books archive</span></a> </div></div> <div class="wrap"> <header class="top"><div class="brand"><div class="logo"></div><div><div class="kicker">SIMON Unified Compat · AI · Graphic</div><h1>Live Feed Intelligence</h1><div class="sub">One page for carrier list, feed breakdown, topic detection, common noun/word graph, AI assistance, and color-coded data feed.</div></div></div><div class="actions"><a class="btn" href="?action=load_all">Load All</a><a class="btn" href="?format=json">JSON</a><a class="btn" href="../feed_manager.php">Feed Manager</a><a class="btn" href="../index.php">Index</a></div></header> <section class="grid"><div class="card"><div class="k">Carriers</div><div class="v cyan"><?=count($feeds)?></div></div><div class="card"><div class="k">Enabled</div><div class="v online"><?=$enabled?></div></div><div class="card"><div class="k">Events Loaded</div><div class="v"><?=count($events)?></div></div><div class="card"><div class="k">Top Word / Noun</div><div class="v pink" style="font-size:24px"><?=sx_h($topWord)?></div></div><div class="card"><div class="k">Missing Providers</div><div class="v bad"><?=count($missingProviders)?></div></div></section> <section class="layout"><div class="panel"><div class="top"><div><div class="kicker">Interactive Graph</div><div class="small">SIMON → categories → feeds → common words. Drag nodes. Hover to inspect.</div></div><div class="actions"><button class="btn" onclick="resetGraph()">Reset Graph</button></div></div><div class="graphbox"><canvas id="graph"></canvas><div class="graphHint" id="graphHint">Graph ready</div></div></div><aside class="panel"><div class="kicker">AI Assistance</div><div class="list"><?php foreach ($aiAdvice as $line): ?><div class="event" style="border-left-color:var(--cyan)"><?=sx_h($line)?></div><?php endforeach; ?></div><div style="margin-top:14px" class="kicker">Topic Breakdown</div><div class="chips"><?php foreach (array_slice($topics,0,18,true) as $topic=>$count): ?><a class="chip" href="?q=<?=urlencode($topic)?>"><?=sx_h($topic)?> · <?=$count?></a><?php endforeach; ?><?php if (!$topics): ?><span class="muted">Load feed data to detect topics.</span><?php endif; ?></div><div style="margin-top:14px" class="kicker">Category Breakdown</div><div class="chips"><?php foreach ($categories as $cat=>$count): ?><a class="chip" href="?q=<?=urlencode($cat)?>"><?=sx_h($cat)?> · <?=$count?></a><?php endforeach; ?></div></aside></section> <section class="cols" style="margin-top:14px"><div class="panel"><div class="top"><div><div class="kicker">Most Common Words / Nouns</div><div class="small">Filtered to meaningful words; click to search feed data.</div></div></div><div class="wordCloud"><?php foreach (array_slice($wordCounts,0,60,true) as $word=>$count): $size = min(30, 11 + ($count * 2)); ?><a href="?q=<?=urlencode($word)?>" style="font-size:<?=$size?>px"><?=sx_h($word)?> <span class="muted"><?=$count?></span></a><?php endforeach; ?><?php if (!$wordCounts): ?><span class="muted">Load a feed or click Load All to build the word graph.</span><?php endif; ?></div></div><div class="panel"><div class="kicker">Feed Controls</div><form method="get" class="actions"><select name="source"><option value="">Choose one feed</option><?php foreach ($feeds as $feed): ?><option value="<?=sx_h($feed['id'])?>" <?=$source===$feed['id']?'selected':''?>><?=sx_h($feed['name'])?></option><?php endforeach; ?></select><input name="q" value="<?=sx_h($q)?>" placeholder="Keyword / topic search"><button class="btn">Analyze</button><a class="btn" href="compat.php">Clear</a></form><?php if ($missingProviders): ?><div class="event" style="border-left-color:var(--red);margin-top:10px"><b>Provider diagnostics:</b> <?=sx_h(implode(', ', array_keys($missingProviders)))?><br><span class="muted">These remain visible for troubleshooting. They are engine/provider files missing under providers/.</span></div><?php endif; ?></div></section> <section class="panel" style="margin-top:14px"><div class="top"><div><div class="kicker">Carrier List / Feed Breakdown</div><div class="small">Every item from feeds.json stays included.</div></div><input id="feedFilter" placeholder="Filter carriers..."></div><table id="carrierTable"><thead><tr><th>Carrier</th><th>Category</th><th>Engine</th><th>Provider</th><th>Route</th><th>Actions</th></tr></thead><tbody><?php foreach ($feeds as $feed): $providerReady = $feed['engine'] !== '' && isset($engineSet[$feed['engine']]); $providerClass = $providerReady ? 'online' : 'bad'; $enabledClass = $feed['enabled'] ? 'online' : 'warn'; $hay = strtolower(json_encode($feed)); ?><tr data-search="<?=sx_h($hay)?>"><td><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:<?=sx_h($feed['color'])?>"></span> <?=sx_h($feed['name'])?><div class="small"><?=sx_h($feed['id'])?> · <span class="<?=$enabledClass?>"><?= $feed['enabled'] ? 'enabled' : 'disabled' ?></span></div></td><td><?=sx_h($feed['category'])?></td><td><?=sx_h($feed['engine'])?></td><td><span class="pill <?=$providerClass?>"><?= $providerReady ? 'provider_ready' : 'provider_missing' ?></span></td><td class="small muted"><?=sx_h(sx_route($feed))?></td><td><div class="actions"><a class="btn" href="<?=sx_h(sx_live_url($feed))?>" target="_blank">Live</a><a class="btn" href="<?=sx_h(sx_cache_url($feed))?>" target="_blank">Cache</a><a class="btn" href="?source=<?=urlencode($feed['id'])?>">Analyze</a><a class="btn" href="../feed_manager.php?edit=<?=urlencode($feed['id'])?>">Edit</a></div></td></tr><?php endforeach; ?></tbody></table></section> <section class="panel" style="margin-top:14px"><div class="top"><div><div class="kicker">Color-Coded Feed Data</div><div class="small"><?=count($events)?> loaded records<?= $q!=='' ? ' matching “'.sx_h($q).'”' : '' ?></div></div><input id="eventFilter" placeholder="Filter loaded feed data..."></div><div class="list" id="eventList"><?php foreach (array_slice($events,0,250) as $event): $hay = strtolower(json_encode($event)); ?><article class="event" data-search="<?=sx_h($hay)?>" style="border-left-color:<?=sx_h($event['color'])?>"><h3><?=sx_h($event['title'])?></h3><div class="small"><?=sx_h($event['source'])?> · <?=sx_h($event['category'])?> · <?=sx_h($event['topic'])?> · sig <?=sx_h($event['signature'])?></div><p><?=sx_h(mb_substr($event['summary'],0,320))?></p><div class="chips"><?php foreach ($event['keywords'] as $kw): ?><a class="chip" href="?q=<?=urlencode($kw)?>"><?=sx_h($kw)?></a><?php endforeach; ?><?php if ($event['url'] && $event['url'] !== '#'): ?><a class="chip" href="<?=sx_h($event['url'])?>" target="_blank">Open</a><?php endif; ?></div></article><?php endforeach; ?><?php if (!$events): ?><div class="event">No feed data loaded yet. Choose a feed and Analyze, or click Load All.</div><?php endif; ?></div></section> </div> <script> const GRAPH_NODES = <?=json_encode($graphNodes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)?>; const GRAPH_EDGES = <?=json_encode($graphEdges, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)?>; (() => {const canvas=document.getElementById('stars'),ctx=canvas.getContext('2d');let w=0,h=0,dpr=Math.min(2,devicePixelRatio||1),stars=[];function resize(){w=innerWidth*dpr;h=innerHeight*dpr;canvas.width=w;canvas.height=h;canvas.style.width=innerWidth+'px';canvas.style.height=innerHeight+'px';stars=Array.from({length:180},()=>({x:Math.random()*w,y:Math.random()*h,r:(.4+Math.random()*1.6)*dpr,a:.08+Math.random()*.32,v:.1+Math.random()*.45}));}function draw(){ctx.clearRect(0,0,w,h);for(const s of stars){s.y+=s.v*dpr;if(s.y>h)s.y=0;ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle=`rgba(190,240,255,${s.a})`;ctx.fill();}requestAnimationFrame(draw);}addEventListener('resize',resize);resize();draw();})(); let resetGraph;(()=>{const canvas=document.getElementById('graph'),hint=document.getElementById('graphHint'),ctx=canvas.getContext('2d');let W=0,H=0,dpr=Math.min(2,devicePixelRatio||1),drag=null,hover=null;const nodes=GRAPH_NODES.map((n,i)=>({...n,x:0,y:0,r:n.type==='core'?34:Math.max(13,Math.min(31,10+Math.sqrt(n.size||1)*3)),i}));const edges=GRAPH_EDGES;const map=Object.fromEntries(nodes.map(n=>[n.id,n]));function color(n){return n.color||(n.type==='core'?'#4be3ff':n.type==='category'?'#ffb347':n.type==='word'?'#ff5bd1':'#7cff9e');}resetGraph=function(){const cx=W/2,cy=H/2;let cat=0,feed=0,word=0;const cats=nodes.filter(n=>n.type==='category').length||1,feeds=nodes.filter(n=>n.type==='feed'||n.type==='disabled').length||1,words=nodes.filter(n=>n.type==='word').length||1;nodes.forEach(n=>{if(n.type==='core'){n.x=cx;n.y=cy;return;}let a=0,r=0;if(n.type==='category'){a=(cat++/cats)*Math.PI*2;r=Math.min(W,H)*.22;}else if(n.type==='word'){a=(word++/words)*Math.PI*2;r=Math.min(W,H)*.39;}else{a=(feed++/feeds)*Math.PI*2;r=Math.min(W,H)*.31;}n.x=cx+Math.cos(a)*r;n.y=cy+Math.sin(a)*r;});};function resize(){const r=canvas.getBoundingClientRect();W=r.width;H=r.height;canvas.width=W*dpr;canvas.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);resetGraph();}function draw(){ctx.clearRect(0,0,W,H);edges.forEach(e=>{const a=map[e.from],b=map[e.to];if(!a||!b)return;ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.strokeStyle='rgba(75,227,255,.17)';ctx.lineWidth=1;ctx.stroke();});nodes.forEach(n=>{const c=color(n);ctx.beginPath();ctx.arc(n.x,n.y,n.r,0,Math.PI*2);ctx.fillStyle='rgba(16,14,48,.9)';ctx.fill();ctx.strokeStyle=c;ctx.lineWidth=n===hover?3:1.4;ctx.stroke();ctx.shadowColor=c;ctx.shadowBlur=n===hover?28:10;ctx.beginPath();ctx.arc(n.x,n.y,n.r*.38,0,Math.PI*2);ctx.fillStyle=c;ctx.globalAlpha=.22;ctx.fill();ctx.globalAlpha=1;ctx.shadowBlur=0;ctx.fillStyle='#eaf2ff';ctx.font=(n.type==='core'?'900 14px':'800 11px')+' system-ui';ctx.textAlign='center';ctx.textBaseline='middle';const label=n.label.length>18?n.label.slice(0,17)+'…':n.label;ctx.fillText(label,n.x,n.y+n.r+13);});requestAnimationFrame(draw);}function pt(e){const r=canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top};}canvas.onpointerdown=e=>{const p=pt(e);drag=nodes.find(n=>Math.hypot(n.x-p.x,n.y-p.y)<n.r+8)||null;if(drag)canvas.setPointerCapture(e.pointerId)};canvas.onpointermove=e=>{const p=pt(e);if(drag){drag.x=p.x;drag.y=p.y;hint.textContent='Dragging '+drag.label;return;}hover=nodes.find(n=>Math.hypot(n.x-p.x,n.y-p.y)<n.r+8)||null;hint.textContent=hover?`${hover.label} · ${hover.type} · ${hover.size||''}`:'Graph ready';};canvas.onpointerup=()=>drag=null;canvas.onclick=()=>{if(hover){location.href='?q='+encodeURIComponent(hover.label);}};addEventListener('resize',resize);resize();draw();})(); function bindFilter(inputId, selector){const input=document.getElementById(inputId);input?.addEventListener('input',()=>{const q=input.value.trim().toLowerCase();document.querySelectorAll(selector).forEach(el=>{const hay=el.dataset.search||el.textContent.toLowerCase();el.style.display=!q||hay.includes(q)?'':'none';});});} bindFilter('feedFilter','#carrierTable tbody tr');bindFilter('eventFilter','#eventList .event'); </script> </body></html>
Save file
Quick jump
open a path
Open