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,334
Folders
408
Scanned Size
3.84 GB
PHP Files
886
Editable Text Files
12,595
File viewer
guarded to /htdocs
/connlink/test1/ui/old/index.php
<?php declare(strict_types=1); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: strict-origin-when-cross-origin'); header('Permissions-Policy: geolocation=(self), camera=(), microphone=(), payment=(), usb=(), interest-cohort=()'); header('Cross-Origin-Opener-Policy: same-origin-allow-popups'); $siteName = 'Gaylord Sinclair'; $appName = 'SIMON Live Intelligence Feed'; $siteUrl = 'https://www.gaylordsinclair.com/'; $pageUrl = 'https://www.gaylordsinclair.com/connlink/test1/ui/'; $pageTitle = 'SIMON · Live Intelligence Feed'; $pageDescription = 'SIMON Live Intelligence Feed aggregates news, alerts, weather, NASA, markets, technology, social signals, and webhook events into one real-time Gaylord Sinclair dashboard.'; $pageImage = 'https://www.gaylordsinclair.com/assets/og/simon-live-intelligence-feed.jpg'; $ga4Id = 'G-S6MEHB4BEY'; $impactSiteVerification = 'e8ec6683-663c-421a-a30d-f8c0fe517345'; $simonWebhookBase = '/connlink/test1/ui/webhook_inbox.php'; $simonVisitStream = 'visits'; $simonPulseStream = 'default'; $simonKey = ''; $year = (int)date('Y'); function h(?string $value): string { return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } ?> <!doctype html> <html lang="en" data-site="gaylord-sinclair" data-system="simon" data-app="live-intelligence-feed"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title><?= h($pageTitle) ?></title> <meta name="description" content="<?= h($pageDescription) ?>" /> <meta name="keywords" content="SIMON, Gaylord Sinclair, live intelligence feed, AI dashboard, news feed, weather, NASA, alerts, webhooks, Web360, Kosmos, feed analysis" /> <meta name="author" content="Gaylord Sinclair" /> <meta name="publisher" content="Gaylord Sinclair" /> <meta name="copyright" content="© <?= $year ?> Gaylord Sinclair" /> <meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" /> <meta name="googlebot" content="index,follow" /> <meta name="theme-color" content="#05041a" /> <meta name="color-scheme" content="dark" /> <meta name="application-name" content="SIMON Live" /> <meta name="apple-mobile-web-app-title" content="SIMON Live" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="mobile-web-app-capable" content="yes" /> <meta name="format-detection" content="telephone=no,email=no,address=no" /> <meta name="referrer" content="strict-origin-when-cross-origin" /> <meta name="impact-site-verification" content="<?= h($impactSiteVerification) ?>" /> <link rel="canonical" href="<?= h($pageUrl) ?>" /> <!-- Open Graph --> <meta property="og:locale" content="en_US" /> <meta property="og:type" content="website" /> <meta property="og:site_name" content="<?= h($siteName) ?>" /> <meta property="og:title" content="<?= h($pageTitle) ?>" /> <meta property="og:description" content="<?= h($pageDescription) ?>" /> <meta property="og:url" content="<?= h($pageUrl) ?>" /> <meta property="og:image" content="<?= h($pageImage) ?>" /> <meta property="og:image:alt" content="SIMON Live Intelligence Feed by Gaylord Sinclair" /> <!-- Twitter / X --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content="<?= h($pageTitle) ?>" /> <meta name="twitter:description" content="<?= h($pageDescription) ?>" /> <meta name="twitter:image" content="<?= h($pageImage) ?>" /> <!-- Icons / PWA --> <link rel="icon" href="/favicon.ico" sizes="any" /> <link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png" /> <link rel="icon" type="image/png" sizes="192x192" href="/assets/icons/icon-192.png" /> <link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png" /> <link rel="manifest" href="/manifest.json" /> <link rel="mask-icon" href="/assets/icons/safari-pinned-tab.svg" color="#7a5cff" /> <!-- Performance hints --> <link rel="dns-prefetch" href="//www.googletagmanager.com" /> <link rel="dns-prefetch" href="//www.google-analytics.com" /> <link rel="dns-prefetch" href="//api.open-meteo.com" /> <link rel="dns-prefetch" href="//api.weather.gov" /> <link rel="dns-prefetch" href="//www.reddit.com" /> <link rel="preconnect" href="https://www.googletagmanager.com" crossorigin /> <link rel="preconnect" href="https://www.google-analytics.com" crossorigin /> <link rel="preconnect" href="https://api.open-meteo.com" crossorigin /> <script> window.SIMON_CONFIG = { SITE_NAME: <?= json_encode($siteName) ?>, APP_NAME: <?= json_encode($appName) ?>, PAGE_TITLE: <?= json_encode($pageTitle) ?>, PAGE_URL: <?= json_encode($pageUrl) ?>, GA4_ID: <?= json_encode($ga4Id) ?>, IMPACT_SITE_VERIFICATION: <?= json_encode($impactSiteVerification) ?>, WEBHOOK_BASE: <?= json_encode($simonWebhookBase) ?>, VISIT_STREAM: <?= json_encode($simonVisitStream) ?>, PULSE_STREAM: <?= json_encode($simonPulseStream) ?>, KEY: <?= json_encode($simonKey) ?>, FEED_PROXY: '/connlink/test1/ui/feed_proxy.php', NEWSAPI_PROXY: '/connlink/test1/ui/newsapi_proxy.php', GSEARCH_PROXY: '/connlink/test1/ui/gsearch_proxy.php', SEARCH_PROXY_BASE: '/connlink/test1/ui', CONNLINK: '/web360/connlink/connector.php', SOURCE: 'simon-live-feed.php' }; </script> <!-- Google Analytics 4 --> <script async src="https://www.googletagmanager.com/gtag/js?id=<?= h($ga4Id) ?>"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){ dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', <?= json_encode($ga4Id) ?>, { anonymize_ip: true, send_page_view: true }); </script> <script type="application/ld+json"> <?= json_encode([ '@context' => 'https://schema.org', '@graph' => [ [ '@type' => 'Organization', '@id' => $siteUrl . '#organization', 'name' => 'Gaylord Sinclair', 'url' => $siteUrl, 'logo' => $siteUrl . 'assets/icons/icon-512.png', 'sameAs' => [ 'https://gaylordsinclair.substack.com/' ] ], [ '@type' => 'WebSite', '@id' => $siteUrl . '#website', 'name' => 'Gaylord Sinclair', 'url' => $siteUrl, 'publisher' => ['@id' => $siteUrl . '#organization'], 'potentialAction' => [ '@type' => 'SearchAction', 'target' => $siteUrl . 'search/?q={search_term_string}', 'query-input' => 'required name=search_term_string' ] ], [ '@type' => 'SoftwareApplication', '@id' => $pageUrl . '#software', 'name' => 'SIMON Live Intelligence Feed', 'applicationCategory' => 'BusinessApplication', 'operatingSystem' => 'Web', 'url' => $pageUrl, 'creator' => ['@id' => $siteUrl . '#organization'], 'description' => $pageDescription ], [ '@type' => 'WebPage', '@id' => $pageUrl . '#webpage', 'url' => $pageUrl, 'name' => $pageTitle, 'description' => $pageDescription, 'isPartOf' => ['@id' => $siteUrl . '#website'], 'about' => ['@id' => $pageUrl . '#software'] ] ] ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) ?> </script> <style> :root{ --bg:#05041a; --bg-2:#08072a; --panel:rgba(16,14,48,.72); --indigo:#7a5cff; --cyan:#4be3ff; --teal:#34f0c8; --pink:#ff5bd1; --amber:#ffb347; --green:#7cff9e; --red:#ff6b8f; --text:#eaf2ff; --muted:#9aa3c4; --line:rgba(122,92,255,0.22); --line-2:rgba(75,227,255,0.28); --edge:rgba(255,255,255,.08); --grad:linear-gradient(135deg,#7a5cff 0%,#4be3ff 100%); --grad-2:linear-gradient(135deg,#ff5bd1 0%,#7a5cff 100%); } *{box-sizing:border-box} html,body{margin:0;min-height:100%;background:var(--bg);color:var(--text); font:14px/1.45 ui-sans-serif,system-ui,-apple-system,"SF Pro Display","Segoe UI",Roboto,sans-serif;} body{ min-height:100vh; background:var(--bg); position:relative; overflow-x:hidden; } /* ===== Cosmic storm — fixed behind everything ===== */ #cosmic{ position:fixed; inset:0; z-index:-2; width:100vw; height:100vh; background: radial-gradient(1200px 700px at 18% 10%, rgba(122,92,255,.22), transparent 55%), radial-gradient(1000px 700px at 85% 20%, rgba(255,91,209,.18), transparent 60%), radial-gradient(900px 600px at 50% 120%, rgba(75,227,255,.18), transparent 60%), linear-gradient(180deg, #04041a 0%, #07062a 100%); pointer-events:none; } /* storm veil — drifting nebula blobs */ .storm{ position:fixed; inset:0; z-index:-1; pointer-events:none; overflow:hidden; filter:blur(40px) saturate(140%); opacity:.85; } .storm .blob{ position:absolute; width:38vw; height:38vw; border-radius:50%; mix-blend-mode:screen; opacity:.55; animation:drift 24s ease-in-out infinite alternate; } .storm .b1{ left:-10vw; top:-8vh; background:radial-gradient(circle, #7a5cff, transparent 60%); animation-duration:32s } .storm .b2{ right:-12vw; top:6vh; background:radial-gradient(circle, #ff5bd1, transparent 60%); animation-duration:28s; animation-delay:-6s } .storm .b3{ left:30vw; bottom:-15vh; background:radial-gradient(circle, #4be3ff, transparent 60%); animation-duration:36s; animation-delay:-12s } .storm .b4{ right:20vw; top:40vh; width:26vw; height:26vw; background:radial-gradient(circle, #34f0c8, transparent 60%); animation-duration:30s; animation-delay:-3s } .storm .b5{ left:10vw; top:55vh; width:22vw; height:22vw; background:radial-gradient(circle, #ffb347, transparent 60%); opacity:.35; animation-duration:26s; animation-delay:-9s } @keyframes drift{ 0% { transform:translate(0,0) scale(1) rotate(0deg) } 50% { transform:translate(6vw,-4vh) scale(1.15) rotate(40deg) } 100% { transform:translate(-4vw,6vh) scale(.95) rotate(-30deg) } } /* lightning flash overlay */ .flash{ position:fixed; inset:0; z-index:-1; pointer-events:none; background:radial-gradient(800px 600px at 60% 30%, rgba(200,230,255,.5), transparent 65%); opacity:0; animation:flash 14s linear infinite; } @keyframes flash{ 0%,96%,100%{opacity:0} 96.5%{opacity:.35} 97% {opacity:.08} 97.6%{opacity:.5} 98% {opacity:.0} } /* ===== Top nav ===== */ .nav{ position:sticky;top:0;z-index:40; display:flex;align-items:center;gap:14px;padding:12px 22px; background:linear-gradient(180deg, rgba(6,6,30,.82), rgba(6,6,30,.35)); backdrop-filter:blur(12px); border-bottom:1px solid var(--edge); flex-wrap:wrap; } @media (max-width:780px){ .nav-links{order:3;flex-basis:100%;margin-left:0} .searchbox{min-width:200px} } .brand{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:.04em} .brand .logo{ width:30px;height:30px;border-radius:9px; background:conic-gradient(from 220deg,var(--indigo),var(--cyan),var(--pink),var(--indigo)); box-shadow:0 0 24px rgba(122,92,255,.55); } .brand small{color:var(--muted);font-weight:500;letter-spacing:.22em;text-transform:uppercase;margin-left:4px;font-size:10px} .nav-links{display:flex;gap:22px;margin-left:12px} .nav-links a{color:var(--text);text-decoration:none;font-size:13px;opacity:.8} .nav-links a:hover,.nav-links a.on{opacity:1;color:var(--cyan)} .grow{flex:1} .searchbox{ position:relative;min-width:240px;max-width:420px;flex:.8; } .searchbox input{ width:100%;padding:9px 14px 9px 36px;border-radius:999px; background:rgba(16,14,48,.6);border:1px solid var(--line);color:var(--text);font:inherit;outline:none; } .searchbox input:focus{border-color:var(--line-2);box-shadow:0 0 0 3px rgba(75,227,255,.12)} .searchbox::before{content:"🔎";position:absolute;left:12px;top:50%;transform:translateY(-50%);opacity:.7;font-size:12px} .searchGo{ position:absolute;right:6px;top:50%;transform:translateY(-50%); height:28px;min-width:34px;padding:0 10px;border-radius:999px;border:1px solid rgba(75,227,255,.22); background:linear-gradient(90deg,#4be3ff,#7a5cff);color:#0a071c;font-weight:900; font:700 13px ui-monospace,Menlo,monospace;cursor:pointer;letter-spacing:.04em; box-shadow:0 4px 14px rgba(75,227,255,.28);transition:transform .14s ease, box-shadow .14s ease; } .searchGo:hover{transform:translateY(-50%) scale(1.04);box-shadow:0 6px 18px rgba(122,92,255,.34)} /* ===== Web Search Overlay ===== */ .websearch{ position:fixed;inset:0;z-index:95;display:none; background:radial-gradient(1200px 900px at 50% 0%, rgba(75,227,255,.06), transparent 60%),rgba(7,5,26,.86); backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px); align-items:flex-start;justify-content:center;overflow:auto;padding:6vh 18px 40px; } .websearch.open{display:flex} .ws-card{ width:min(980px,100%);border-radius:22px;border:1px solid var(--line); background:linear-gradient(180deg, rgba(18,16,58,.72), rgba(10,8,30,.78)); box-shadow:0 30px 80px rgba(0,0,0,.6), 0 0 60px rgba(75,227,255,.08); overflow:hidden;color:#e9f2ff; } .ws-head{ display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--edge); background:linear-gradient(180deg, rgba(75,227,255,.05), transparent); } .ws-head h2{margin:0;font-size:14px;letter-spacing:.18em;text-transform:uppercase;color:#cfe8ff} .ws-head .q{color:var(--teal);font-weight:800;margin-left:4px} .ws-head .close{margin-left:auto;background:transparent;border:1px solid var(--line);color:#eef;border-radius:999px;padding:5px 12px;font:inherit;cursor:pointer} .ws-tabs{display:flex;gap:6px;padding:10px 16px 0;border-bottom:1px solid var(--edge);flex-wrap:wrap} .ws-tab{ background:rgba(255,255,255,.04);border:1px solid var(--edge);color:#dde8ff; padding:6px 12px;border-radius:999px;cursor:pointer;font:inherit;font-size:12px;letter-spacing:.08em; text-transform:uppercase;transition:border-color .14s ease, background .14s ease; } .ws-tab .n{color:var(--teal);margin-left:6px;font-weight:900} .ws-tab.active{border-color:var(--teal);background:rgba(75,227,255,.08);color:#fff} .ws-body{padding:14px 16px 18px;display:grid;gap:10px;max-height:72vh;overflow:auto} .ws-res{ display:grid;grid-template-columns:72px 1fr;gap:12px;padding:10px 12px;border-radius:14px; border:1px solid var(--edge);background:rgba(255,255,255,.03);text-decoration:none;color:#eaf2ff; transition:transform .12s ease, border-color .12s ease, background .12s ease; } .ws-res:hover{transform:translateY(-1px);border-color:rgba(75,227,255,.32);background:rgba(75,227,255,.06)} .ws-res .thumb{ width:72px;height:72px;border-radius:10px;background:#12103a;display:grid;place-items:center; color:#6677aa;font-size:22px;overflow:hidden; } .ws-res .thumb img{width:100%;height:100%;object-fit:cover} .ws-res .ttl{font-weight:800;font-size:14px;line-height:1.25;margin-bottom:2px;color:#f1f6ff} .ws-res .src{color:var(--teal);font-size:11px;letter-spacing:.06em;text-transform:uppercase;margin-bottom:4px} .ws-res .snip{color:var(--muted);font-size:13px;line-height:1.4} .ws-empty{color:var(--muted);font-size:13px;padding:24px;text-align:center;border:1px dashed var(--edge);border-radius:14px} .ws-hint{padding:10px 16px;color:var(--muted);font-size:11.5px;border-top:1px solid var(--edge)} .btn{ border:0;padding:9px 16px;border-radius:999px;cursor:pointer; font:inherit;font-size:13px;font-weight:700;letter-spacing:.02em; background:var(--grad);color:#0b0820;box-shadow:0 8px 24px rgba(122,92,255,.35); } .btn.ghost{background:transparent;border:1px solid var(--line);color:var(--text);box-shadow:none} .btn.ghost:hover{border-color:var(--line-2)} /* ===== Hero ===== */ .hero{ padding:42px 42px 18px; position:relative; } @media (max-width:960px){ .hero{padding:28px 20px 10px} } .hero h1{ margin:0 0 12px 0;font-size:52px;line-height:1.02;font-weight:800;letter-spacing:-0.02em; } .hero h1 .g{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent} .hero p.lede{color:var(--muted);font-size:15px;max-width:560px;margin:0 0 22px} .hero .cta{display:flex;gap:10px;margin-bottom:22px} .hero .stats{display:grid;grid-template-columns:repeat(4, minmax(0,1fr));gap:10px;max-width:560px} .stat-card{ padding:12px 14px;border-radius:16px;border:1px solid var(--line); background:linear-gradient(180deg, rgba(22,18,60,.72), rgba(12,8,32,.72)); backdrop-filter:blur(6px); } .stat-card .n{font-size:22px;font-weight:800;letter-spacing:-0.01em} .stat-card .l{font-size:10.5px;color:var(--muted);letter-spacing:.08em;text-transform:uppercase;margin-top:2px} /* ===== Section rails ===== */ .section{padding:20px 42px;position:relative;z-index:2} @media (max-width:960px){ .section{padding:14px 14px} } .sec-head{display:flex;align-items:center;gap:10px;margin:8px 0 12px} .sec-head h2{margin:0;font-size:22px;font-weight:700;letter-spacing:-0.01em} .sec-head .dot{width:8px;height:8px;border-radius:50%;box-shadow:0 0 12px currentColor} .sec-head .more{margin-left:auto;font-size:12px;color:var(--text);cursor:pointer;border:1px solid var(--line);padding:5px 10px;border-radius:999px;background:transparent} .sec-head .more:hover{border-color:var(--line-2)} .rail{ display:flex;gap:14px;overflow-x:auto;padding:6px 2px 12px 2px; scroll-snap-type:x mandatory;scrollbar-width:thin; } .rail::-webkit-scrollbar{height:9px} .rail::-webkit-scrollbar-thumb{background:linear-gradient(90deg,var(--indigo),var(--cyan));border-radius:10px} /* ===== Cards — flat panels, no 3D tilt ===== */ .card{ flex:0 0 260px;scroll-snap-align:start; position:relative;border-radius:18px;overflow:hidden;cursor:pointer; border:1px solid rgba(255,255,255,.08); background:linear-gradient(180deg, rgba(22,18,60,.85), rgba(14,10,40,.92)); box-shadow:0 14px 36px rgba(0,0,0,.5); transition:transform .18s ease, box-shadow .2s ease, border-color .2s ease; } .card:hover{ border-color:var(--line-2); transform:translateY(-3px); box-shadow:0 22px 54px rgba(0,0,0,.7), 0 0 30px rgba(75,227,255,.18); } .card .thumb{ position:relative;height:160px;overflow:hidden; background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.18), transparent 40%), linear-gradient(135deg, #1a1052, #4a1d7a); } .card .thumb::after{ content:"";position:absolute;inset:0; background:repeating-linear-gradient(90deg, rgba(75,227,255,.06) 0 1px, transparent 1px 12px), repeating-linear-gradient(0deg, rgba(122,92,255,.06) 0 1px, transparent 1px 12px); mix-blend-mode:screen; } .card .thumb .badge{ position:absolute;left:10px;top:10px;z-index:2; font-size:10px;letter-spacing:.1em;text-transform:uppercase; padding:4px 8px;border-radius:999px; background:rgba(0,0,0,.45);border:1px solid rgba(255,255,255,.15);color:#fff; backdrop-filter:blur(4px); } .card .thumb .glyph{ position:absolute;inset:0;display:grid;place-items:center; font-size:64px;text-shadow:0 0 32px rgba(75,227,255,.5); } .card .body{padding:12px 14px 14px} .card .src{font-size:10px;color:var(--cyan);letter-spacing:.14em;text-transform:uppercase;margin-bottom:4px} .card .title{font-size:14px;font-weight:700;line-height:1.3; display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2.6em} .card .snippet{font-size:11.5px;color:var(--muted);margin-top:6px;line-height:1.4; display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2.8em} .card .foot{display:flex;justify-content:space-between;align-items:center;margin-top:10px;font-size:11px;color:var(--muted)} .card .foot .go{color:var(--cyan)} .card.cat-news .thumb{background:radial-gradient(circle at 30% 30%, rgba(75,227,255,.35), transparent 40%), linear-gradient(135deg,#0f2a52,#1b3d7a)} .card.cat-alert .thumb{background:radial-gradient(circle at 30% 30%, rgba(255,107,143,.38), transparent 40%), linear-gradient(135deg,#4a0b2e,#7a1d4a)} .card.cat-weather .thumb{background:radial-gradient(circle at 30% 30%, rgba(75,227,255,.35), transparent 40%), linear-gradient(135deg,#0b3a4a,#1d6a7a)} .card.cat-nasa .thumb{background:radial-gradient(circle at 30% 30%, rgba(75,227,255,.32), transparent 40%), linear-gradient(135deg,#0b1b4a,#2a1d7a)} .card.cat-tech .thumb{background:radial-gradient(circle at 30% 30%, rgba(255,179,71,.35), transparent 40%), linear-gradient(135deg,#3a2a0b,#7a4a1d)} .card.cat-market .thumb{background:radial-gradient(circle at 30% 30%, rgba(124,255,158,.32), transparent 40%), linear-gradient(135deg,#0b3a2a,#1d7a4a)} .card.cat-gov .thumb{background:radial-gradient(circle at 30% 30%, rgba(122,92,255,.35), transparent 40%), linear-gradient(135deg,#1a1052,#3a1d7a)} .card.cat-quake .thumb{background:radial-gradient(circle at 30% 30%, rgba(255,179,71,.35), transparent 40%), linear-gradient(135deg,#3a1a0b,#7a3a1d)} .card.cat-wiki .thumb{background:radial-gradient(circle at 30% 30%, rgba(255,91,209,.32), transparent 40%), linear-gradient(135deg,#3a0b3a,#7a1d6a)} .card.cat-quote .thumb{background:radial-gradient(circle at 30% 30%, rgba(255,91,209,.28), transparent 40%), linear-gradient(135deg,#2a0b3a,#4a1d7a)} /* Category tiles — plain, flat */ .cat-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px,1fr));gap:14px} .cat-tile{ position:relative;padding:18px;border-radius:18px;cursor:pointer; background:linear-gradient(180deg, rgba(22,18,60,.85), rgba(14,10,40,.92)); border:1px solid rgba(255,255,255,.08);overflow:hidden;min-height:140px; transition:transform .18s ease, box-shadow .2s ease, border-color .2s ease; } .cat-tile:hover{border-color:var(--line-2);transform:translateY(-3px);box-shadow:0 18px 44px rgba(0,0,0,.55), 0 0 28px rgba(75,227,255,.2)} .cat-tile .ic{ width:56px;height:56px;border-radius:16px;display:grid;place-items:center; font-size:28px;margin-bottom:10px;position:relative; background:linear-gradient(135deg, rgba(75,227,255,.18), rgba(122,92,255,.22)); border:1px solid rgba(255,255,255,.12); box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 6px 16px rgba(0,0,0,.35); } .cat-tile .ic::after{ content:"";position:absolute;inset:-1px;border-radius:17px;pointer-events:none; background:conic-gradient(from 140deg, transparent, rgba(255,255,255,.18), transparent 40%); mix-blend-mode:overlay;opacity:.6; } .cat-tile:hover .ic{transform:translateY(-1px) scale(1.02);box-shadow:inset 0 1px 0 rgba(255,255,255,.14), 0 10px 22px rgba(75,227,255,.25)} .cat-tile.cat-news .ic{background:linear-gradient(135deg,#4be3ff33,#22304a)} .cat-tile.cat-alert .ic{background:linear-gradient(135deg,#ff6b6b33,#3a0b1a)} .cat-tile.cat-weather .ic{background:linear-gradient(135deg,#4be3ff33,#0b3a4a)} .cat-tile.cat-gov .ic{background:linear-gradient(135deg,#7a5cff33,#1a1052)} .cat-tile.cat-quake .ic{background:linear-gradient(135deg,#ffb34733,#3a1a0b)} .cat-tile.cat-nasa .ic{background:linear-gradient(135deg,#ff5bd133,#3a0b3a)} .cat-tile.cat-tech .ic{background:linear-gradient(135deg,#4be3ff33,#0b2a3a)} .cat-tile.cat-market .ic{background:linear-gradient(135deg,#7cff9e33,#0b3a2a)} .cat-tile.cat-wiki .ic{background:linear-gradient(135deg,#ff5bd133,#3a0b3a)} .cat-tile.cat-quote .ic{background:linear-gradient(135deg,#ff5bd133,#2a0b3a)} .cat-tile.top100-tile .ic{background:linear-gradient(135deg,#ffb34755,#ff6b8f55)} .cat-tile .t{font-size:16px;font-weight:800} .cat-tile .s{font-size:11px;color:var(--muted);margin-top:2px} .cat-tile .count{position:absolute;right:14px;top:14px;font-size:11px;color:var(--muted);padding:3px 8px;border:1px solid var(--line);border-radius:999px} .cat-tile::after{ content:"";position:absolute;right:-30px;bottom:-30px;width:160px;height:160px;border-radius:50%; background:radial-gradient(circle, rgba(75,227,255,.18), transparent 60%); pointer-events:none; } /* ===== Weather feature ===== */ .wx-feat{ position:relative;border-radius:22px;overflow:hidden;padding:22px; background: radial-gradient(700px 260px at 15% 20%, rgba(75,227,255,.28), transparent 60%), linear-gradient(135deg, #0b3a4a 0%, #1a1052 100%); border:1px solid rgba(75,227,255,.2); display:grid;grid-template-columns:auto 1fr;gap:18px;align-items:center; } .wx-feat .wx-icon{ width:96px;height:96px;border-radius:28px;display:grid;place-items:center;font-size:58px; background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.14); box-shadow:0 0 28px rgba(75,227,255,.25); } .wx-feat .wx-temp{font-size:52px;font-weight:800;line-height:1;letter-spacing:-0.02em} .wx-feat .wx-temp small{font-size:18px;color:var(--muted);margin-left:4px;font-weight:500} .wx-feat .wx-status{font-size:14px;color:var(--text);margin-top:6px} .wx-feat .wx-place{font-size:12px;color:var(--muted);margin-top:2px} .wx-feat .wx-extra{display:flex;gap:14px;margin-top:6px;font-size:12px;color:var(--muted)} .wx-feat .wx-extra span b{color:var(--text);font-weight:700} .wx-cast{ grid-column:1/-1;display:grid;grid-template-columns:repeat(7, minmax(0,1fr));gap:8px;margin-top:14px; } .wx-day{ padding:10px 6px;border-radius:12px;text-align:center; background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08); transition:transform .18s ease, background .18s ease;cursor:pointer; } .wx-day:hover{background:rgba(75,227,255,.1);transform:translateY(-3px)} .wx-day .dow{font-size:11px;color:var(--muted);letter-spacing:.08em;text-transform:uppercase} .wx-day .ico{font-size:26px;margin:2px 0} .wx-day .hi{font-size:14px;font-weight:700} .wx-day .lo{font-size:12px;color:var(--muted)} .wx-day .pop{font-size:10px;color:var(--cyan);margin-top:2px} /* ===== Floating info button + side drawer ===== */ .fab{ position:fixed;right:22px;bottom:22px;z-index:60; width:58px;height:58px;border-radius:50%;border:0;cursor:pointer; background:var(--grad);color:#0b0820;font-size:22px;font-weight:900; box-shadow:0 18px 40px rgba(122,92,255,.5), 0 0 28px rgba(75,227,255,.4); display:grid;place-items:center; animation:fabPulse 3.2s ease-in-out infinite; } .fab:hover{transform:translateY(-2px) scale(1.04)} @keyframes fabPulse{ 0%,100%{box-shadow:0 18px 40px rgba(122,92,255,.5), 0 0 22px rgba(75,227,255,.35)} 50% {box-shadow:0 24px 54px rgba(122,92,255,.7), 0 0 46px rgba(75,227,255,.7)} } .fab .dot{position:absolute;right:6px;top:6px;width:10px;height:10px;border-radius:50%;background:var(--teal);box-shadow:0 0 10px var(--teal)} .drawer{ position:fixed;top:0;right:0;bottom:0;width:360px;max-width:90vw;z-index:70; background:linear-gradient(180deg, rgba(10,8,32,.98), rgba(8,6,26,.98)); border-left:1px solid var(--line-2); box-shadow:-30px 0 80px rgba(0,0,0,.7); transform:translateX(110%);transition:transform .32s cubic-bezier(.2,.8,.2,1); display:flex;flex-direction:column; } .drawer.open{transform:none} .drawer header{ display:flex;align-items:center;gap:10px;padding:16px 18px;border-bottom:1px solid var(--edge); } .drawer header .t{font-weight:800;letter-spacing:.04em} .drawer header small{color:var(--muted);letter-spacing:.18em;text-transform:uppercase;font-size:10px;margin-left:4px} .drawer header .x{margin-left:auto;background:transparent;border:1px solid var(--line);color:var(--text);padding:4px 10px;border-radius:999px;cursor:pointer;font:inherit} .drawer .content{flex:1;overflow:auto;padding:14px 18px 22px} .drawer .content::-webkit-scrollbar{width:8px} .drawer .content::-webkit-scrollbar-thumb{background:linear-gradient(180deg,var(--indigo),var(--cyan));border-radius:10px} .dgroup{margin:14px 0 4px} .dgroup .h{font-size:10.5px;color:var(--muted);letter-spacing:.16em;text-transform:uppercase;margin-bottom:8px} .drow{display:flex;gap:8px;flex-wrap:wrap} .drawer .chip{ padding:6px 11px;border-radius:999px;border:1px solid var(--line); background:rgba(18,14,46,.6);color:var(--text);cursor:pointer;font-size:12px; display:inline-flex;align-items:center;gap:6px;user-select:none; } .drawer .chip:hover{border-color:var(--line-2)} .drawer .chip.active{background:var(--grad);color:#0b0820;border-color:transparent;font-weight:700} .drawer select.chip{appearance:none} .drawer .kv{display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px dashed var(--edge)} .drawer .kv b{font-weight:700} .drawer .kv .g{color:var(--green)} .drawer .kv .r{color:var(--red)} .drawer .kv .a{color:var(--amber)} /* GS menu */ .gs-menu{display:grid;grid-template-columns:repeat(2,1fr);gap:8px} .gs-btn{ display:flex;align-items:center;gap:8px;padding:10px 12px;border-radius:12px; border:1px solid var(--line);background:linear-gradient(180deg, rgba(22,18,60,.7), rgba(12,8,32,.7)); color:var(--text);cursor:pointer;font:inherit;font-size:12px;text-align:left;text-decoration:none; transition:border-color .18s ease, transform .18s ease, box-shadow .2s ease; } .gs-btn:hover{border-color:var(--line-2);transform:translateY(-2px);box-shadow:0 10px 26px rgba(0,0,0,.5), 0 0 20px rgba(75,227,255,.18)} .gs-btn .ic{font-size:18px} .gs-btn .lbl{font-weight:700;letter-spacing:.02em} .gs-btn .sub{font-size:10px;color:var(--muted)} .scrim{position:fixed;inset:0;z-index:65;background:rgba(0,0,0,.4);opacity:0;pointer-events:none;transition:opacity .3s ease;backdrop-filter:blur(2px)} .scrim.show{opacity:1;pointer-events:auto} /* ===== Reader Canvas (right slide-over, keeps UI behind) ===== */ .reader{ position:fixed;top:0;right:0;bottom:0;width:min(560px,96vw);z-index:75; background:linear-gradient(180deg, rgba(16,12,44,.98), rgba(10,8,32,.98)); border-left:1px solid var(--line-2); box-shadow:-30px 0 80px rgba(0,0,0,.7); transform:translateX(110%);transition:transform .32s cubic-bezier(.2,.8,.2,1); display:flex;flex-direction:column; } .reader.open{transform:none} .reader .hdr{ position:relative;height:180px;overflow:hidden;flex:0 0 auto; background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.18), transparent 40%), linear-gradient(135deg, #1a1052, #4a1d7a); } .reader .hdr .gl{position:absolute;inset:0;display:grid;place-items:center;font-size:96px;text-shadow:0 0 40px rgba(75,227,255,.55)} .reader .hdr .wave{ position:absolute;inset:0; background: repeating-linear-gradient(35deg, rgba(255,255,255,.06) 0 1px, transparent 1px 14px), repeating-linear-gradient(-45deg, rgba(75,227,255,.08) 0 1px, transparent 1px 18px); mix-blend-mode:screen; } .reader .hdr .x{ position:absolute;right:12px;top:12px;z-index:2; width:34px;height:34px;border-radius:50%;border:1px solid rgba(255,255,255,.2); background:rgba(0,0,0,.45);color:#fff;font-size:16px;cursor:pointer; } .reader .body{padding:18px 22px 26px;overflow:auto;flex:1} .reader .body::-webkit-scrollbar{width:8px} .reader .body::-webkit-scrollbar-thumb{background:linear-gradient(180deg,var(--indigo),var(--cyan));border-radius:10px} .reader .badge{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(0,0,0,.45);border:1px solid rgba(255,255,255,.15);font-size:11px;letter-spacing:.1em;text-transform:uppercase} .reader h2{margin:10px 0 6px;font-size:22px;letter-spacing:-0.01em;line-height:1.2} .reader .meta{color:var(--muted);font-size:12px} .reader .snip{font-size:14px;line-height:1.6;color:var(--text);margin-top:14px;white-space:pre-wrap} .reader .pillrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px} .reader .pill{font-size:10.5px;padding:4px 9px;border-radius:999px;background:rgba(255,255,255,.05);border:1px solid var(--edge);color:var(--muted)} .reader .actions{display:flex;gap:10px;margin-top:18px;flex-wrap:wrap} .reader .actions .ext{ padding:9px 14px;border-radius:999px;border:1px solid var(--line); background:transparent;color:var(--text);font:inherit;font-size:12px;cursor:pointer;text-decoration:none; display:inline-flex;align-items:center;gap:6px; } .reader .actions .ext:hover{border-color:var(--line-2)} /* ===== Nav weather pill (hover-to-expand) ===== */ .wx-pill{ position:relative;display:inline-flex;align-items:center;gap:8px; padding:6px 14px 6px 10px;border-radius:999px;cursor:pointer; background:linear-gradient(135deg, rgba(75,227,255,.18), rgba(122,92,255,.18)); border:1px solid var(--line);color:var(--text);font:inherit;font-size:13px; transition:border-color .2s ease, box-shadow .2s ease; } .wx-pill:hover{border-color:var(--line-2); box-shadow:0 0 0 3px rgba(75,227,255,.1)} .wx-pill .ic{font-size:18px;line-height:1} .wx-pill .t{font-weight:800;letter-spacing:-.01em} .wx-pill .p{font-size:11px;color:var(--muted);letter-spacing:.06em;text-transform:uppercase} .wx-pop{ position:absolute;top:calc(100% + 10px);right:0;z-index:50; width:min(520px, 86vw);padding:14px;border-radius:16px; background:linear-gradient(180deg, rgba(16,12,44,.98), rgba(10,8,32,.98)); border:1px solid var(--line-2); box-shadow:0 30px 80px rgba(0,0,0,.7); opacity:0;pointer-events:none;transform:translateY(-6px); transition:opacity .2s ease, transform .22s ease; } .wx-pill:hover .wx-pop, .wx-pill:focus-within .wx-pop, .wx-pop:hover{opacity:1;pointer-events:auto;transform:none} .wx-pop .top{display:flex;align-items:center;gap:12px;padding-bottom:10px;border-bottom:1px dashed var(--edge)} .wx-pop .top .big{font-size:34px;font-weight:800;letter-spacing:-.02em;line-height:1} .wx-pop .top .big small{font-size:14px;color:var(--muted);margin-left:4px;font-weight:500} .wx-pop .top .lab{font-size:12px;color:var(--text)} .wx-pop .top .loc{font-size:11px;color:var(--muted);margin-top:2px} .wx-pop .days{display:grid;grid-template-columns:repeat(7, minmax(0,1fr));gap:6px;margin-top:10px} .wx-pop .d{padding:8px 4px;border-radius:10px;text-align:center;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06)} .wx-pop .d:hover{background:rgba(75,227,255,.1)} .wx-pop .d .dow{font-size:10px;color:var(--muted);letter-spacing:.06em;text-transform:uppercase} .wx-pop .d .ic{font-size:20px;margin:2px 0} .wx-pop .d .hi{font-size:12px;font-weight:700} .wx-pop .d .lo{font-size:11px;color:var(--muted)} .wx-pop .foot{display:flex;justify-content:space-between;align-items:center;margin-top:8px;font-size:11px;color:var(--muted)} .wx-pop .act{padding:4px 10px;border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--text);cursor:pointer;font:inherit;font-size:11px} .wx-pop .act:hover{border-color:var(--line-2)} /* ===== Top-100 tile + center panel ===== */ .top100-tile{ background:linear-gradient(135deg, rgba(255,107,143,.22), rgba(122,92,255,.22)) !important; border-color:var(--line-2) !important; } .top100-tile::before{ content:"🔥 TOP 100"; position:absolute; right:14px; top:14px; font-size:10px; font-weight:800; letter-spacing:.12em; padding:3px 9px; border-radius:999px; color:#0b0820; background:linear-gradient(90deg,#ffb347,#ff6b8f); z-index:2; } .centerpanel{ position:fixed; inset:0; z-index:82; display:grid; place-items:center; padding:24px; background:rgba(2,1,14,.78); backdrop-filter:blur(8px); opacity:0; pointer-events:none; transition:opacity .3s ease; } .centerpanel.show{opacity:1; pointer-events:auto} .cp-box{ width:min(1100px, 96vw); height:min(86vh, 820px); display:flex; flex-direction:column; border-radius:22px; overflow:hidden; background:linear-gradient(180deg, rgba(16,12,44,.98), rgba(10,8,32,.98)); border:1px solid var(--line-2); box-shadow:0 40px 120px rgba(0,0,0,.8); transform:scale(.96) translateY(10px); transition:transform .28s cubic-bezier(.2,.8,.2,1); } .centerpanel.show .cp-box{transform:none} .cp-hdr{ display:flex; align-items:center; gap:12px; padding:16px 22px; border-bottom:1px solid var(--edge); background:linear-gradient(180deg, rgba(22,18,60,.9), rgba(14,10,40,.6)); } .cp-title{font-size:18px; font-weight:800; letter-spacing:.02em; display:flex; align-items:center; gap:8px} .cp-counter{font-size:11px; color:var(--muted); letter-spacing:.12em; text-transform:uppercase; padding:4px 10px; border-radius:999px; border:1px solid var(--line); background:rgba(12,8,32,.6)} .cp-progress{flex:1; height:6px; border-radius:999px; background:rgba(255,255,255,.08); overflow:hidden; margin:0 12px} .cp-progress .bar{height:100%; width:0%; background:var(--grad); box-shadow:0 0 12px rgba(75,227,255,.5); transition:width .1s linear} .cp-auto{padding:6px 12px; border-radius:999px; border:1px solid var(--line); background:transparent; color:var(--text); cursor:pointer; font:inherit; font-size:12px} .cp-auto.on{background:var(--grad); color:#0b0820; border-color:transparent; font-weight:700} .cp-close{width:34px; height:34px; border-radius:50%; border:1px solid var(--line); background:transparent; color:var(--text); cursor:pointer; font-size:16px} .cp-body{ flex:1; overflow:auto; padding:14px 22px 22px; scroll-behavior:smooth; scroll-snap-type:y proximity; } .cp-body::-webkit-scrollbar{width:10px} .cp-body::-webkit-scrollbar-thumb{background:linear-gradient(180deg,var(--indigo),var(--cyan));border-radius:10px} .cp-row{ display:grid; grid-template-columns:auto 56px 1fr auto; gap:14px; align-items:center; padding:14px 14px; margin:8px 0; border-radius:14px; cursor:pointer; background:linear-gradient(180deg, rgba(22,18,60,.6), rgba(14,10,40,.7)); border:1px solid rgba(255,255,255,.06); opacity:0; transform:translateY(14px); transition:opacity .5s ease, transform .5s cubic-bezier(.2,.8,.2,1), border-color .2s ease, background .2s ease; scroll-snap-align:start; } .cp-row.in{opacity:1; transform:none} .cp-row:hover{border-color:var(--line-2); background:linear-gradient(180deg, rgba(30,24,80,.7), rgba(18,14,50,.8))} .cp-row .rank{ font-size:22px; font-weight:900; width:42px; text-align:right; background:linear-gradient(180deg, var(--cyan), var(--indigo)); -webkit-background-clip:text; background-clip:text; color:transparent; } .cp-row .gly{ width:56px; height:56px; border-radius:14px; display:grid; place-items:center; font-size:28px; background:radial-gradient(circle at 30% 30%, rgba(75,227,255,.28), transparent 50%), linear-gradient(135deg,#1a1052,#3a1d7a); border:1px solid rgba(255,255,255,.08); } .cp-row .info .t{font-size:14.5px; font-weight:700; line-height:1.3; display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} .cp-row .info .m{font-size:11px; color:var(--muted); margin-top:4px; letter-spacing:.04em} .cp-row .go{font-size:11px; color:var(--cyan); padding:6px 10px; border:1px solid var(--line); border-radius:999px} /* ===== Top 100 news ticker (banner scroller) ===== */ .ticker{ position:relative; margin:14px 42px 0; border-radius:14px; overflow:hidden; background:linear-gradient(90deg, rgba(22,18,60,.85), rgba(14,10,40,.92)); border:1px solid var(--line); box-shadow:inset 0 1px 0 rgba(255,255,255,.05); display:flex; align-items:center; gap:14px; padding:0; z-index:2; } .ticker .badge{ flex:0 0 auto; padding:10px 14px; font-size:11px; font-weight:800; letter-spacing:.14em; color:#0b0820; background:linear-gradient(90deg,#ffb347,#ff6b8f); border-right:1px solid rgba(0,0,0,.2); cursor:pointer; user-select:none; } .ticker .badge:hover{filter:brightness(1.08)} .ticker-track-wrap{ flex:1; overflow:hidden; position:relative; mask-image:linear-gradient(90deg, transparent, #000 40px, #000 calc(100% - 40px), transparent); -webkit-mask-image:linear-gradient(90deg, transparent, #000 40px, #000 calc(100% - 40px), transparent); } .ticker-track{ display:flex; gap:32px; padding:12px 0; white-space:nowrap; animation:tickerMove 90s linear infinite; } .ticker-track:hover{ animation-play-state:paused } .ticker-item{ font-size:13px; color:var(--text); cursor:pointer; display:inline-flex; gap:8px; align-items:center } .ticker-item .rk{ font-size:10px; color:var(--cyan); font-weight:800; padding:2px 7px; border:1px solid var(--line-2); border-radius:999px } .ticker-item .sep{ color:var(--muted); } .ticker-item .src{ color:var(--muted); font-size:11px; letter-spacing:.08em; text-transform:uppercase } .ticker-item:hover{ color:var(--cyan) } @keyframes tickerMove{ from{transform:translateX(0)} to{transform:translateX(-50%)} } @media (max-width:720px){ .ticker{margin:10px 14px 0} } /* ===== Social section ===== */ .social-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(160px,1fr)); gap:10px; } .soc-tile{ position:relative; padding:14px; border-radius:16px; cursor:pointer; background:linear-gradient(180deg, rgba(22,18,60,.85), rgba(14,10,40,.92)); border:1px solid rgba(255,255,255,.08); overflow:hidden; transition:transform .18s ease, border-color .2s ease, box-shadow .2s ease; display:flex; align-items:center; gap:12px; } .soc-tile:hover{ transform:translateY(-2px); border-color:var(--line-2); box-shadow:0 16px 36px rgba(0,0,0,.55), 0 0 22px rgba(75,227,255,.18) } .soc-tile .sic{ width:44px; height:44px; border-radius:12px; display:grid; place-items:center; font-size:20px; flex:0 0 auto; font-weight:800; background:linear-gradient(135deg, rgba(255,255,255,.1), rgba(255,255,255,.02)); border:1px solid rgba(255,255,255,.14); } .soc-tile .info{ min-width:0 } .soc-tile .name{ font-size:13px; font-weight:700; letter-spacing:.01em } .soc-tile .handle{ font-size:11px; color:var(--muted); margin-top:2px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap } .soc-tile .ext{ position:absolute; right:10px; top:10px; font-size:10px; color:var(--muted) } .soc-tile.sx .sic{ background:linear-gradient(135deg,#000,#333); color:#fff } .soc-tile.sig .sic{ background:linear-gradient(135deg,#f58529,#dd2a7b,#8134af); color:#fff } .soc-tile.sfb .sic{ background:linear-gradient(135deg,#1877f2,#0a4ea7); color:#fff } .soc-tile.stk .sic{ background:linear-gradient(135deg,#010101,#25f4ee,#fe2c55); color:#fff } .soc-tile.syt .sic{ background:linear-gradient(135deg,#ff0000,#8e0000); color:#fff } .soc-tile.sli .sic{ background:linear-gradient(135deg,#0a66c2,#08396e); color:#fff } .soc-tile.srd .sic{ background:linear-gradient(135deg,#ff4500,#b23200); color:#fff } .soc-tile.spt .sic{ background:linear-gradient(135deg,#e60023,#7a0013); color:#fff } .soc-tile.sth .sic{ background:linear-gradient(135deg,#111,#555); color:#fff } .soc-tile.smt .sic{ background:linear-gradient(135deg,#6364ff,#2b2dcf); color:#fff } .soc-tile.sbs .sic{ background:linear-gradient(135deg,#0085ff,#004a99); color:#fff } .soc-tile.sdc .sic{ background:linear-gradient(135deg,#5865f2,#2a32a4); color:#fff } .soc-tile.stw .sic{ background:linear-gradient(135deg,#9146ff,#4d1db1); color:#fff } .soc-tile.ssn .sic{ background:linear-gradient(135deg,#fffc00,#dcd900); color:#000 } .soc-tile.sgh .sic{ background:linear-gradient(135deg,#0d1117,#2a2f3a); color:#fff } .soc-tile.ssb .sic{ background:linear-gradient(135deg,#ff6719,#a33a00); color:#fff } .soc-tile.smd .sic{ background:linear-gradient(135deg,#000,#333); color:#fff } .soc-tile.stu .sic{ background:linear-gradient(135deg,#001935,#32506d); color:#fff } .soc-tile.sva .sic{ background:linear-gradient(135deg,#00aff0,#0078a8); color:#fff } /* ===== Screensaver ===== */ .screensaver{ position:fixed; inset:0; z-index:95; background:rgba(2,1,14,.72); backdrop-filter:blur(6px); display:none; align-items:center; justify-content:center; flex-direction:column; gap:20px; cursor:none; } .screensaver.on{display:flex} .ss-clock{ font-size:clamp(60px,12vw,180px); font-weight:800; letter-spacing:-0.02em; background:var(--grad); -webkit-background-clip:text; background-clip:text; color:transparent; text-shadow:0 0 50px rgba(75,227,255,.3); animation:ssFloat 10s ease-in-out infinite alternate; } .ss-date{ font-size:14px; letter-spacing:.28em; color:var(--muted); text-transform:uppercase } .ss-ticker-wrap{ width:86vw; max-width:1100px; overflow:hidden; border-top:1px solid var(--line); border-bottom:1px solid var(--line); padding:14px 0; margin-top:20px; mask-image:linear-gradient(90deg, transparent, #000 60px, #000 calc(100% - 60px), transparent); -webkit-mask-image:linear-gradient(90deg, transparent, #000 60px, #000 calc(100% - 60px), transparent); } .ss-ticker{ display:flex; gap:44px; white-space:nowrap; animation:tickerMove 120s linear infinite; font-size:18px; } .ss-ticker .rk{ color:var(--cyan); font-weight:800; padding:3px 10px; border:1px solid var(--line-2); border-radius:999px; font-size:12px; margin-right:10px } .ss-brand{ position:fixed; top:28px; left:28px; font-size:12px; letter-spacing:.3em; color:var(--muted); text-transform:uppercase } .ss-hint{ position:fixed; bottom:28px; right:28px; font-size:11px; letter-spacing:.2em; color:var(--muted); text-transform:uppercase; opacity:.8 } @keyframes ssFloat{ 0%{transform:translateY(-6px)} 100%{transform:translateY(6px)} } /* ===== Toast ===== */ .toast{ position:fixed;right:16px;bottom:94px;z-index:55; background:rgba(10,7,28,.92);border:1px solid var(--line-2);color:var(--text); padding:10px 14px;border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.55); font-size:12px;opacity:0;transform:translateY(10px);transition:.25s ease;max-width:340px } .toast.show{opacity:1;transform:none} .footer{padding:28px 42px 120px;color:var(--muted);font-size:12px;display:flex;gap:14px;flex-wrap:wrap;align-items:center;position:relative;z-index:2} .footer .s{padding:4px 9px;border-radius:999px;border:1px solid var(--line);background:rgba(12,8,32,.6)} </style> </head> <body> <!-- ===== Cosmic storm canvas + drifting nebula + lightning flash ===== --> <canvas id="cosmic"></canvas> <div class="storm"><div class="blob b1"></div><div class="blob b2"></div><div class="blob b3"></div><div class="blob b4"></div><div class="blob b5"></div></div> <div class="flash"></div> <!-- ===== Nav ===== --> <div class="nav"> <div class="brand"><div class="logo"></div><div>SIMON<small>· LIVE</small></div></div> <div class="nav-links"> <a href="#home" class="on">Home</a> <a href="#explore">Explore</a> <a href="#alerts">Alerts</a> <a href="#weather">Weather</a> <a href="#market">Market</a> </div> <span class="grow"></span> <div class="searchbox"> <input id="q" type="search" placeholder="Filter feed · Enter to search the web…"> <button class="searchGo" id="searchGo" title="Search the web (Enter)">↵</button> </div> <!-- Weather pill: hover to expand 7-day forecast --> <div class="wx-pill" id="wxPill" tabindex="0" title="Weather · hover to expand"> <span class="ic" id="wxPillIc">⛅</span> <span><span class="t" id="wxPillT">—°</span><br><span class="p" id="wxPillP">Set location</span></span> <div class="wx-pop" id="wxPop"> <div class="top"> <div class="ic" style="font-size:40px" id="wxPopIc">⛅</div> <div> <div class="big" id="wxPopTemp">—<small>°F</small></div> <div class="lab" id="wxPopLab">Weather gated on location</div> <div class="loc" id="wxPopLoc">📍 Tap “Use location” to unlock</div> </div> <span class="grow"></span> <button class="act" id="wxPopGeo">📍 Use location</button> </div> <div class="days" id="wxPopDays"></div> <div class="foot"> <span id="wxPopExtra">—</span> <button class="act" id="wxPopOpen">Open Weather ↓</button> </div> </div> </div> <button class="btn" id="refreshTop">Refresh</button> </div> <!-- ===== Hero ===== --> <section class="hero" id="home"> <div> <h1>Real-time <span class="g">intelligence</span><br/>across every signal.</h1> <p class="lede">SIMON aggregates <b id="leadN">28</b> live feeds — news, alerts, weather, NASA, quakes, markets, tech, books, wiki — into one cosmic dashboard. Filter, expand, read, listen.</p> <div class="cta"> <button class="btn" id="heroExplore">Explore feeds</button> <button class="btn ghost" id="heroGeo">📍 Use my location</button> </div> <div class="stats"> <div class="stat-card"><div class="n" id="sFeeds">–</div><div class="l">Feeds</div></div> <div class="stat-card"><div class="n" id="sItems">–</div><div class="l">Signals</div></div> <div class="stat-card"><div class="n" id="sOk">–</div><div class="l">Online</div></div> <div class="stat-card"><div class="n" id="sFresh">–</div><div class="l">Last 1h</div></div> </div> </div> </section> <!-- ===== Top 100 News Ticker (banner reel) ===== --> <div class="ticker" id="newsTicker" role="region" aria-label="Top 100 news ticker"> <div class="badge" id="tickerBadge">🔥 TOP 100</div> <div class="ticker-track-wrap"><div class="ticker-track" id="tickerTrack"></div></div> </div> <!-- ===== Popular ===== --> <section class="section" id="explore"> <div class="sec-head"> <span class="dot" style="color:var(--cyan);background:var(--cyan)"></span> <h2>Popular right now</h2> <button class="more" data-filter="all">See all</button> </div> <div class="rail" id="railPopular"></div> </section> <!-- ===== Category grid ===== --> <section class="section"> <div class="sec-head"> <span class="dot" style="color:var(--pink);background:var(--pink)"></span> <h2>Explore by category</h2> </div> <div class="cat-grid" id="catGrid"></div> </section> <!-- ===== News ===== --> <section class="section"> <div class="sec-head"><span class="dot" style="color:var(--cyan);background:var(--cyan)"></span><h2>News</h2><button class="more" data-filter="news">More</button></div> <div class="rail" id="railNews"></div> </section> <!-- ===== Alerts ===== --> <section class="section" id="alerts"> <div class="sec-head"><span class="dot" style="color:var(--red);background:var(--red)"></span><h2>Alerts & disasters</h2><button class="more" data-filter="alert">More</button></div> <div class="rail" id="railAlerts"></div> </section> <!-- ===== Weather ===== --> <section class="section" id="weather"> <div class="sec-head"><span class="dot" style="color:var(--cyan);background:var(--cyan)"></span><h2>Weather</h2><button class="more" id="wxGeo">📍 Use location</button></div> <div id="wxHost"></div> </section> <!-- ===== NASA / Tech / Market / Wiki ===== --> <section class="section"> <div class="sec-head"><span class="dot" style="color:var(--cyan);background:var(--cyan)"></span><h2>NASA & Space</h2><button class="more" data-filter="nasa">More</button></div> <div class="rail" id="railNasa"></div> </section> <section class="section"> <div class="sec-head"><span class="dot" style="color:var(--amber);background:var(--amber)"></span><h2>Tech</h2><button class="more" data-filter="tech">More</button></div> <div class="rail" id="railTech"></div> </section> <section class="section" id="market"> <div class="sec-head"><span class="dot" style="color:var(--green);background:var(--green)"></span><h2>Market</h2><button class="more" data-filter="market">More</button></div> <div class="rail" id="railMarket"></div> </section> <section class="section"> <div class="sec-head"><span class="dot" style="color:var(--pink);background:var(--pink)"></span><h2>On this day · Signal</h2><button class="more" data-filter="wiki">More</button></div> <div class="rail" id="railWiki"></div> </section> <!-- ===== Social (your accounts + live social feed) ===== --> <section class="section" id="social"> <div class="sec-head"> <span class="dot" style="color:var(--pink);background:var(--pink)"></span> <h2>Social</h2> <span class="s" style="color:var(--muted);font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin-left:8px">Your accounts · live reddit</span> <button class="more" id="socSetBtn">Set handles</button> </div> <div class="social-grid" id="socialGrid"></div> <div class="sec-head" style="margin-top:16px"> <span class="dot" style="color:var(--cyan);background:var(--cyan)"></span> <h2 style="font-size:16px">Live from Reddit · r/popular</h2> </div> <div class="rail" id="railSocial"></div> </section> <div class="footer"> <span class="s">feeds: <b id="fFeeds">–</b></span> <span class="s">items: <b id="fItems">–</b></span> <span class="s">ok: <b id="fOk">–</b></span> <span class="s">fail: <b id="fFail">–</b></span> <span class="grow"></span> <span id="clock"></span> </div> <!-- ===== Floating info button ===== --> <button class="fab" id="fab" title="Gaylord Sinclair · Controls">ⓘ<span class="dot" id="fabDot"></span></button> <!-- ===== Scrim + Drawer ===== --> <div class="scrim" id="scrim"></div> <aside class="drawer" id="drawer"> <header> <div class="brand" style="gap:8px"><div class="logo" style="width:22px;height:22px;border-radius:7px"></div><div class="t">Controls<small>· GAYLORD SINCLAIR</small></div></div> <button class="x" id="drawerClose">Close</button> </header> <div class="content"> <div class="dgroup"> <div class="h">Gaylord Sinclair · Ecosystem</div> <div class="gs-menu" id="gsMenu"></div> </div> <div class="dgroup"> <div class="h">Filter</div> <div class="drow" id="filterChips"></div> </div> <div class="dgroup"> <div class="h">Density per rail</div> <div class="drow"> <select class="chip" id="density"> <option value="10">10 cards</option> <option value="16" selected>16 cards</option> <option value="24">24 cards</option> <option value="40">40 cards</option> </select> </div> </div> <div class="dgroup"> <div class="h">Auto-refresh</div> <div class="drow"> <select class="chip" id="autoRefresh"> <option value="0">Off</option> <option value="60000">every 1 min</option> <option value="300000" selected>every 5 min</option> <option value="900000">every 15 min</option> </select> </div> </div> <div class="dgroup"> <div class="h">Audio</div> <div class="drow"> <button class="chip active" id="sndBtn">🔊 Hover sound</button> </div> </div> <div class="dgroup"> <div class="h">Location</div> <div class="drow"><button class="chip" id="geoBtn">📍 Use my location</button></div> <div id="geoShow" style="color:var(--muted);font-size:11.5px;margin-top:6px"></div> </div> <div class="dgroup"> <div class="h">Keys & endpoints</div> <div class="drow" style="flex-direction:column;align-items:stretch;gap:8px"> <button class="chip" id="setNasaKey">🛰️ NASA API key</button> <button class="chip" id="setNewsKey">📰 NewsAPI proxy URL</button> <button class="chip" id="setWebhookUrl">🪝 Webhook inbox URL</button> <button class="chip" id="copyWebhookInfo">📋 Copy webhook POST URL</button> </div> <div id="keysShow" style="color:var(--muted);font-size:10.5px;margin-top:8px;line-height:1.5"></div> </div> <div class="dgroup"> <div class="h">Feed health</div> <div id="healthList"></div> </div> </div> </aside> <!-- ===== Web Search Overlay ===== --> <div class="websearch" id="websearch" role="dialog" aria-modal="true" aria-label="Web search results"> <div class="ws-card"> <div class="ws-head"> <h2>🔎 SIMON · Web search for <span class="q" id="wsQuery">—</span></h2> <button class="close" id="wsClose" type="button">Close ✕</button> </div> <div class="ws-tabs" id="wsTabs"> <button class="ws-tab active" data-src="all">All <span class="n" id="wsN_all">0</span></button> <button class="ws-tab" data-src="google">Google <span class="n" id="wsN_google">0</span></button> <button class="ws-tab" data-src="duck">DuckDuckGo <span class="n" id="wsN_duck">0</span></button> <button class="ws-tab" data-src="wiki">Wikipedia <span class="n" id="wsN_wiki">0</span></button> <button class="ws-tab" data-src="news">News <span class="n" id="wsN_news">0</span></button> </div> <div class="ws-body" id="wsBody"> <div class="ws-empty" id="wsEmpty">Type a query above and press Enter.</div> </div> <div class="ws-hint"> Google results require <code>GOOGLE_CSE_KEY</code> + <code>GOOGLE_CSE_CX</code> in <code>secrets.php</code>. DuckDuckGo & Wikipedia work out of the box. News pulls from the NewsAPI proxy. </div> </div> </div> <!-- ===== Reader Canvas (keeps UI in the back) ===== --> <aside class="reader" id="reader" aria-hidden="true"> <div class="hdr" id="rHdr"> <div class="wave"></div> <div class="gl" id="rGlyph">🛰️</div> <button class="x" id="rClose" title="Close">✕</button> </div> <div class="body"> <span class="badge" id="rBadge">SOURCE</span> <h2 id="rTitle">…</h2> <div class="meta" id="rMeta">—</div> <div class="pillrow" id="rPills"></div> <div class="snip" id="rSnip">—</div> <div class="actions"> <a class="ext" id="rOpen" href="#" target="_blank" rel="noopener">↗ Open original in new tab</a> <button class="ext" id="rCopy">⧉ Copy link</button> <button class="ext" id="rBackClose">Close reader</button> </div> </div> </aside> <!-- ===== Top 100 center panel ===== --> <div class="centerpanel" id="cpanel" aria-hidden="true"> <div class="cp-box"> <div class="cp-hdr"> <div class="cp-title">🔥 Top 100 News</div> <div class="cp-counter" id="cpCount">0 / 0</div> <div class="cp-progress"><div class="bar" id="cpBar"></div></div> <button class="cp-auto" id="cpAuto">▶ Auto-scroll</button> <button class="cp-close" id="cpClose" title="Close">✕</button> </div> <div class="cp-body" id="cpBody"></div> </div> </div> <div class="toast" id="toast"></div> <!-- ===== Screensaver (idle mode) ===== --> <div class="screensaver" id="screensaver" aria-hidden="true"> <div class="ss-brand">SIMON · GAYLORD SINCLAIR</div> <div class="ss-date" id="ssDate">—</div> <div class="ss-clock" id="ssClock">--:--</div> <div class="ss-ticker-wrap"><div class="ss-ticker" id="ssTicker"></div></div> <div class="ss-hint">MOVE TO DISMISS</div> </div> <script> /* =========================================================== SIMON · LIVE — flat NFT-style + cosmic storm + reader canvas =========================================================== */ const SIMON_CFG = window.SIMON_CONFIG || {}; const CONNLINK = SIMON_CFG.CONNLINK || '/web360/connlink/connector.php'; /* ---------- Gaylord Sinclair menu ---------- */ const GS_MENU = [ { ic:'🏛️', lbl:'GS Home', sub:'gaylordsinclair.com', url:'https://gaylordsinclair.com/' }, { ic:'🧠', lbl:'SIMON', sub:'Intelligence layer', url:'https://gaylordsinclair.com/connlink/test1/web360/ui/simon_feed_helix.html' }, { ic:'🛡️', lbl:'Guardian', sub:'Auth & security', url:'https://gaylordsinclair.com/guardian/' }, { ic:'🔗', lbl:'LINK Registry', sub:'Integration hub', url:'https://gaylordsinclair.com/connlink/' }, { ic:'🧱', lbl:'WEB360', sub:'Builder engine', url:'https://gaylordsinclair.com/connlink/test1/web360/' }, { ic:'📚', lbl:'Books', sub:'Published works', url:'https://gaylordsinclair.com/books/' }, { ic:'🦸', lbl:'Comics', sub:'Q-Ball & Co.', url:'https://gaylordsinclair.com/comics/' }, { ic:'🎨', lbl:'Art Portfolio', sub:'Galleries', url:'https://gaylordsinclair.com/art/' }, { ic:'👥', lbl:'Collective', sub:'Community', url:'https://gaylordsinclair.com/collective/' }, { ic:'🧪', lbl:'ConnLink Test', sub:'Dev sandbox', url:'https://gaylordsinclair.com/connlink/test1/' }, { ic:'⚙️', lbl:'Feed Manager', sub:'Edit feeds.json', url:'/connlink/test1/ui/feed_manager.php' }, { ic:'📡', lbl:'Feed Proxy', sub:'Live JSON endpoint', url:'/connlink/test1/ui/feed_proxy.php?limit=100' }, ]; /* ---------- User keys / endpoints (localStorage) ---------- */ function _lskey(k,d){ try{ return localStorage.getItem(k) || d; }catch{ return d; } } const NASA_KEY = ()=>_lskey('simon_nasa_key','DEMO_KEY'); /* NewsAPI key now lives server-side in secrets.php. The proxy below keeps the key off the browser and caches responses to preserve quota. */ const NEWSAPI_PROXY = ()=>_lskey('simon_newsapi_proxy', SIMON_CFG.NEWSAPI_PROXY || '/connlink/test1/ui/newsapi_proxy.php'); const GSEARCH_PROXY = ()=>_lskey('simon_gsearch_proxy', SIMON_CFG.GSEARCH_PROXY || '/connlink/test1/ui/gsearch_proxy.php'); const WEBHOOK_URL = ()=>_lskey('simon_webhook_url', (SIMON_CFG.WEBHOOK_BASE || '/connlink/test1/ui/webhook_inbox.php') + '?stream=' + encodeURIComponent(SIMON_CFG.PULSE_STREAM || 'default')); /* ---------- Carriers (29) ---------- */ const CARRIERS = [ /* SIMON Feed Engine v2 — local registry/router source. This preserves the working feeds already listed below and adds the new PHP feed engine as one clean source. */ { key:'simon_engine_v2', label:'SIMON Engine v2', cat:'news', glyph:'🧠', source:'direct', url:()=> { const qs = new URLSearchParams({ limit:'100' }); if (STATE?.geo?.lat!=null && STATE?.geo?.lon!=null){ qs.set('lat', STATE.geo.lat); qs.set('lon', STATE.geo.lon); if (STATE.geo.place) qs.set('place', STATE.geo.place); } return '/connlink/test1/ui/feed_proxy.php?' + qs.toString(); }, normalize: r => (r.items||[]).map(e => ({ title:e.title, snippet:e.summary || e.text || '', url:e.url || '#', at:e.published || e.at || e.created_at, label:e.source || e.provider || 'SIMON Engine', cat:({space:'nasa', earth:'quake', social:'news', simon:'news'}[e.category] || e.category || 'news'), glyph:({weather:'⛅', tech:'💻', space:'🛰️', nasa:'🛰️', earth:'🌋', quake:'🌋', social:'👁️', simon:'🧠', news:'📰'}[e.category] || '🧠') })) }, /* News (5) */ { key:'newsapi', label:'NewsAPI', cat:'news', glyph:'📰', source:'proxy', /* Server-side proxy holds the key in secrets.php and caches upstream responses (5 min) to preserve the 1000-call/day Developer quota. If the proxy isn't deployed yet, falls back to the saurav.tech mirror. */ url:()=>{ const p = NEWSAPI_PROXY(); if (p) return `${p}?endpoint=top-headlines&country=us&pageSize=25`; return 'https://saurav.tech/NewsAPI/top-headlines/category/general/us.json'; }, normalize: r => (r.articles||r.items||[]).slice(0,25).map(a => ({title:a.title, snippet:a.description||a.content||'', url:a.url, at:a.publishedAt})) }, { key:'gdelt', label:'GDELT', cat:'news', glyph:'🌐', source:'direct+proxy', url:'https://api.gdeltproject.org/api/v2/doc/doc?query=news&mode=artlist&maxrecords=25&format=json&sort=datedesc', normalize: r => (r.articles||[]).map(a => ({title:a.title, snippet:a.domain, url:a.url, at:a.seendate})) }, { key:'reddit_worldnews', label:'r/worldnews', cat:'news', glyph:'👁️', source:'direct', url:'https://www.reddit.com/r/worldnews/top.json?limit=25&t=day', normalize: r => (r.data?.children||[]).map(c => ({title:c.data.title, snippet:(c.data.selftext||'').slice(0,220)||`↑${c.data.ups} · ${c.data.num_comments}💬`, url:'https://reddit.com'+c.data.permalink, at:new Date(c.data.created_utc*1000).toISOString()})) }, { key:'tvmaze', label:'TVmaze', cat:'news', glyph:'📺', source:'direct', url:'https://api.tvmaze.com/schedule?country=US', normalize: r => (r||[]).slice(0,20).map(e => ({title:`${e.show?.name}: ${e.name||'—'}`, snippet:`${e.show?.network?.name||e.show?.webChannel?.name||''} · ${e.airtime||''} · ${(e.show?.genres||[]).join(', ')}`, url:e.show?.url||e.url, at:e.airstamp})) }, { key:'spaceflight_news', label:'Spaceflight News', cat:'news', glyph:'🛸', source:'direct', url:'https://api.spaceflightnewsapi.net/v4/articles/?limit=20', normalize: r => (r.results||[]).map(a => ({title:a.title, snippet:a.summary, url:a.url, at:a.published_at})) }, /* Alerts / Gov / Quakes (4) */ { key:'nws_alerts', label:'NWS Alerts', cat:'alert', glyph:'⚠️', source:'direct+proxy', url:(ctx)=> ctx?.lat!=null ? `https://api.weather.gov/alerts/active?point=${ctx.lat},${ctx.lon}` : 'https://api.weather.gov/alerts/active?status=actual&severity=Severe,Extreme&limit=25', normalize: r => (r.features||[]).map(f => ({title:f.properties?.headline||f.properties?.event, snippet:(f.properties?.description||'').slice(0,400), url:f.properties?.uri, at:f.properties?.sent})) }, { key:'usgs_quakes', label:'USGS Quakes', cat:'quake', glyph:'🌋', source:'direct+proxy', url:'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson', normalize: r => (r.features||[]).map(f => ({title:`M${f.properties?.mag} · ${f.properties?.place}`, snippet:`depth ${f.geometry?.coordinates?.[2]}km · ${f.properties?.type}`, url:f.properties?.url, at:new Date(f.properties?.time).toISOString()})) }, { key:'openfema', label:'FEMA', cat:'gov', glyph:'🏛️', source:'direct+proxy', url:'https://www.fema.gov/api/open/v2/DisasterDeclarationsSummaries?$orderby=declarationDate%20desc&$top=25', normalize: r => (r.DisasterDeclarationsSummaries||[]).map(d => ({title:`${d.incidentType} · ${d.state}`, snippet:d.declarationTitle||d.declarationType, url:'https://www.fema.gov/disaster/'+d.disasterNumber, at:d.declarationDate})) }, { key:'treasury_debt', label:'US Treasury', cat:'gov', glyph:'💵', source:'direct', url:'https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/debt_to_penny?sort=-record_date&page%5Bsize%5D=10', normalize: r => (r.data||[]).map(d => ({title:`US Debt: $${(+d.tot_pub_debt_out_amt||0).toLocaleString(undefined,{maximumFractionDigits:0})}`, snippet:`Intragov: $${(+d.intragov_hold_amt||0).toLocaleString()} · Public: $${(+d.debt_held_public_amt||0).toLocaleString()}`, url:'https://fiscaldata.treasury.gov/datasets/debt-to-the-penny/debt-to-the-penny', at:d.record_date})) }, /* Weather (1) */ { key:'open_meteo', label:'Open-Meteo', cat:'weather', glyph:'⛅', source:'direct', requires:'geo', url:(ctx)=> `https://api.open-meteo.com/v1/forecast?latitude=${ctx.lat}&longitude=${ctx.lon}¤t=temperature_2m,weathercode,wind_speed_10m,relative_humidity_2m,apparent_temperature&daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_probability_max&timezone=auto&temperature_unit=fahrenheit&wind_speed_unit=mph`, normalize: r => { const c=r.current||{}; const d=r.daily||{}; const [ic,label]=wmo(c.weathercode); return [{title:`${Math.round(c.temperature_2m)}°F · ${label} ${ic}`, snippet:`feels ${Math.round(c.apparent_temperature)}°F · 💨${Math.round(c.wind_speed_10m)}mph · 💧${Math.round(c.relative_humidity_2m)}%`, url:'#', at:c.time, _weather:{now:c, daily:d}}]; } }, /* NASA / Space (4) */ { key:'eonet', label:'NASA EONET', cat:'nasa', glyph:'🛰️', source:'direct+proxy', url:'https://eonet.gsfc.nasa.gov/api/v3/events?limit=30&status=open', normalize: r => (r.events||[]).map(e => ({title:`${e.categories?.[0]?.title||'Event'}: ${e.title}`, snippet:(e.description||'').slice(0,300), url:e.sources?.[0]?.url||e.link, at:e.geometry?.[e.geometry.length-1]?.date})) }, { key:'nasa_apod', label:'APOD', cat:'nasa', glyph:'🌌', source:'direct', url:()=> `https://api.nasa.gov/planetary/apod?api_key=${NASA_KEY()}&count=5`, normalize: r => (Array.isArray(r)?r:[r]).map(a => ({title:a.title, snippet:(a.explanation||'').slice(0,400), url:a.hdurl||a.url, at:a.date})) }, { key:'nasa_neo', label:'NEO', cat:'nasa', glyph:'☄️', source:'direct', url:()=> `https://api.nasa.gov/neo/rest/v1/feed?api_key=${NASA_KEY()}&start_date=${new Date().toISOString().slice(0,10)}&end_date=${new Date().toISOString().slice(0,10)}`, normalize: r => { const d=r.near_earth_objects||{}; const out=[]; for (const k in d){ for (const n of d[k]){ const dia=Math.round(n.estimated_diameter?.meters?.estimated_diameter_max||0); const haz=n.is_potentially_hazardous_asteroid?' ⚠':''; const ap=n.close_approach_data?.[0]; out.push({title:`${n.name} · Ø${dia}m${haz}`, snippet:ap?`miss ${Math.round(+ap.miss_distance?.kilometers||0).toLocaleString()}km @ ${Math.round(+ap.relative_velocity?.kilometers_per_hour||0).toLocaleString()}km/h`:'', url:n.nasa_jpl_url, at:k}); } } return out; } }, { key:'spacex', label:'SpaceX', cat:'nasa', glyph:'🚀', source:'direct', url:'https://api.spacexdata.com/v4/launches/upcoming', normalize: r => (r||[]).slice(0,15).map(l => ({title:`${l.name}${l.flight_number?` · #${l.flight_number}`:''}`, snippet:(l.details||'').slice(0,400)||'Upcoming launch', url:l.links?.webcast||l.links?.article||'https://www.spacex.com/launches/', at:l.date_utc})) }, /* Space weather (1) */ { key:'noaa_swpc', label:'Space Wx', cat:'alert', glyph:'☀️', source:'direct', url:'https://services.swpc.noaa.gov/products/alerts.json', normalize: r => (Array.isArray(r)?r:[]).slice(0,20).map(a => ({title:(a.message||'').split('\n')[0].slice(0,140)||'SWPC alert', snippet:(a.message||'').replace(/\s+/g,' ').slice(0,400), url:'https://www.swpc.noaa.gov/products/alerts-watches-and-warnings', at:a.issue_datetime})) }, /* Tech (4) */ { key:'hn', label:'Hacker News', cat:'tech', glyph:'🧠', source:'direct', url:'https://hacker-news.firebaseio.com/v0/topstories.json', normalize: async (ids) => { const top = (ids||[]).slice(0,20); const all = await Promise.all(top.map(id => fetch('https://hacker-news.firebaseio.com/v0/item/'+id+'.json').then(r=>r.json()).catch(()=>null))); return all.filter(Boolean).map(s => ({title:s.title, snippet:`${s.score||0}▲ · ${s.descendants||0}💬 · by ${s.by||''}`, url:s.url||('https://news.ycombinator.com/item?id='+s.id), at:new Date((s.time||0)*1000).toISOString()})); } }, { key:'dev_to', label:'DEV.to', cat:'tech', glyph:'💻', source:'direct', url:'https://dev.to/api/articles?top=1&per_page=20', normalize: r => (r||[]).map(a => ({title:a.title, snippet:a.description, url:a.url, at:a.published_at})) }, { key:'stackoverflow', label:'StackOverflow', cat:'tech', glyph:'❓', source:'direct', url:'https://api.stackexchange.com/2.3/questions?order=desc&sort=hot&site=stackoverflow&pagesize=20', normalize: r => (r.items||[]).map(q => ({title:q.title, snippet:`${q.score}▲ · ${q.answer_count}✔ · ${(q.tags||[]).join(', ')}`, url:q.link, at:new Date((q.creation_date||0)*1000).toISOString()})) }, { key:'github_trending', label:'GitHub', cat:'tech', glyph:'🐙', source:'direct', url:()=>{ const d=new Date(); d.setDate(d.getDate()-7); return `https://api.github.com/search/repositories?q=created:%3E${d.toISOString().slice(0,10)}&sort=stars&order=desc&per_page=20`; }, normalize: r => (r.items||[]).map(x => ({title:`${x.full_name} · ${x.stargazers_count}★`, snippet:x.description||x.language||'', url:x.html_url, at:x.created_at})) }, /* Market (3) */ { key:'coingecko', label:'CoinGecko', cat:'market', glyph:'₿', source:'direct', url:'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=20&page=1', normalize: r => (r||[]).map(c => ({title:`${c.symbol.toUpperCase()} $${c.current_price?.toLocaleString()}`, snippet:`24h ${c.price_change_percentage_24h?.toFixed(2)}% · cap $${Math.round((c.market_cap||0)/1e9)}B`, url:`https://www.coingecko.com/en/coins/${c.id}`, at:c.last_updated})) }, { key:'cryptocompare_news', label:'Crypto News', cat:'market', glyph:'🪙', source:'direct', url:'https://www.reddit.com/r/CryptoCurrency/top.json?limit=20&t=day', normalize: r => (r.data?.children||[]).map(c => ({title:c.data.title, snippet:(c.data.selftext||'').slice(0,260)||`↑${c.data.ups} · ${c.data.num_comments}💬 · r/${c.data.subreddit}`, url:'https://reddit.com'+c.data.permalink, at:new Date((c.data.created_utc||0)*1000).toISOString()})) }, { key:'frankfurter', label:'FX', cat:'market', glyph:'💱', source:'direct', url:'https://api.exchangerate.host/latest?base=USD&symbols=EUR,GBP,JPY,CAD,AUD,CHF,CNY,INR,MXN,BRL', normalize: r => { const rates=r.rates||{}; const ks=['EUR','GBP','JPY','CAD','AUD','CHF','CNY','INR','MXN','BRL']; return ks.filter(k=>rates[k]).map(k => ({title:`1 USD → ${(+rates[k]).toFixed(4)} ${k}`, snippet:`as of ${r.date||new Date().toISOString().slice(0,10)}`, url:'https://exchangerate.host/', at:r.date||new Date().toISOString()})); } }, /* Wiki / Culture / Books (3) */ { key:'wiki_onthisday', label:'On This Day', cat:'wiki', glyph:'📜', source:'direct', url:()=>{ const d=new Date(); return `https://en.wikipedia.org/api/rest_v1/feed/onthisday/events/${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getDate()).padStart(2,'0')}`; }, normalize: r => (r.events||[]).slice(0,18).map(e => ({title:`${e.year}: ${e.text}`, snippet:e.pages?.[0]?.extract||'', url:e.pages?.[0]?.content_urls?.desktop?.page, at:String(e.year)})) }, { key:'open_library', label:'Books Trending', cat:'wiki', glyph:'📚', source:'direct', url:'https://openlibrary.org/trending/daily.json', normalize: r => (r.works||[]).slice(0,20).map(w => ({title:w.title, snippet:`${(w.author_name||[]).join(', ')}${w.first_publish_year?` · ${w.first_publish_year}`:''}`, url:'https://openlibrary.org'+w.key, at:w.last_modified_i?new Date(w.last_modified_i*1000).toISOString():null})) }, { key:'poetrydb', label:'Poetry', cat:'wiki', glyph:'🕊️', source:'direct', url:'https://poetrydb.org/random/5', normalize: r => (Array.isArray(r)?r:[]).map(p => ({title:`${p.title} — ${p.author}`, snippet:(p.lines||[]).slice(0,12).join('\n'), url:`https://en.wikipedia.org/wiki/${encodeURIComponent(p.author||'')}`, at:new Date().toISOString()})) }, /* Quote / Signal (3) */ { key:'quotable', label:'Signal', cat:'quote', glyph:'💬', source:'direct', url:'https://dummyjson.com/quotes/random/12', normalize: r => (Array.isArray(r)?r:(r.quotes||[])).map(q => ({title:`"${q.quote||q.content}"`, snippet:`— ${q.author||'Unknown'}`, url:'https://dummyjson.com/quotes', at:new Date().toISOString()})) }, { key:'chucknorris', label:'Jokes', cat:'quote', glyph:'🥋', source:'direct', url:'https://api.chucknorris.io/jokes/search?query=life', normalize: r => (r.result||[]).slice(0,15).map(j => ({title:(j.value||'').slice(0,120), snippet:j.value||'', url:j.url, at:j.updated_at||new Date().toISOString()})) }, { key:'advice_slip', label:'Advice', cat:'quote', glyph:'💡', source:'direct', url:'https://api.adviceslip.com/advice', normalize: r => { const s = r.slip||{}; return s.advice ? [{title:s.advice, snippet:`Slip #${s.id||''}`, url:'https://api.adviceslip.com/', at:new Date().toISOString()}] : []; } }, /* Webhook inbox (Perior, Zapier, n8n, custom) */ { key:'webhook_inbox', label:'Webhook', cat:'news', glyph:'🪝', source:'direct', url:()=> WEBHOOK_URL(), normalize: r => { const arr = Array.isArray(r) ? r : (r.items || r.events || r.payloads || r.data || []); return arr.slice(0,25).map(e => ({ title: e.title || e.subject || e.name || e.headline || e.event || '(webhook event)', snippet: (e.summary || e.description || e.body || e.message || e.text || JSON.stringify(e).slice(0,400)), url: e.url || e.link || e.href || '#', at: e.at || e.timestamp || e.time || e.received_at || e.created_at || new Date().toISOString(), })); } }, ]; const GLYPH = Object.fromEntries(CARRIERS.map(c => [c.key, c.glyph])); const FILTERS = [ { key:'all', label:'ALL', cats:null }, { key:'news', label:'NEWS', cats:['news'] }, { key:'alert', label:'ALERTS', cats:['alert','gov','quake'] }, { key:'weather', label:'WEATHER', cats:['weather'] }, { key:'nasa', label:'NASA', cats:['nasa'] }, { key:'tech', label:'TECH', cats:['tech'] }, { key:'market', label:'MARKET', cats:['market'] }, { key:'wiki', label:'WIKI', cats:['wiki','quote'] }, ]; const CAT_META = { news: { title:'News', sub:'Wire + social + TV', ic:'📰' }, alert: { title:'Alerts', sub:'NWS + SWPC', ic:'⚠️' }, weather: { title:'Weather', sub:'7-day forecast', ic:'⛅' }, gov: { title:'Gov', sub:'FEMA + Treasury', ic:'🏛️' }, quake: { title:'Quakes', sub:'USGS ≥M2.5', ic:'🌋' }, nasa: { title:'NASA', sub:'EONET·APOD·NEO·SpaceX', ic:'🛰️' }, tech: { title:'Tech', sub:'HN·DEV·SO·GH', ic:'💻' }, market: { title:'Market', sub:'Crypto + News + FX', ic:'₿' }, wiki: { title:'Wiki', sub:'On this day · Books · Poetry', ic:'📜' }, quote: { title:'Signal', sub:'Quotes · Advice', ic:'💬' }, }; const STATE = { filter:'all', query:'', soundOn:true, stormOn:true, density:16, autoRefreshMs:300000, autoTimer:null, items:[], byCarrier:{}, status:{}, geo:{lat:null,lon:null,place:null}, }; /* ---------- WMO ---------- */ function wmo(code){ if (code===0) return ['☀️','Clear']; if ([1,2].includes(code)) return ['🌤️','Mostly clear']; if (code===3) return ['☁️','Overcast']; if ([45,48].includes(code)) return ['🌫️','Fog']; if ([51,53,55].includes(code)) return ['🌦️','Drizzle']; if ([61,63,65].includes(code)) return ['🌧️','Rain']; if ([66,67].includes(code)) return ['🌧️','Freezing rain']; if ([71,73,75,77].includes(code)) return ['❄️','Snow']; if ([80,81,82].includes(code)) return ['🌧️','Showers']; if ([85,86].includes(code)) return ['🌨️','Snow showers']; if ([95].includes(code)) return ['⛈️','Thunder']; if ([96,99].includes(code)) return ['⛈️','Severe']; return ['🌡️','—']; } /* ---------- Sound ---------- */ const SND = { ctx:null, get C(){ if(!this.ctx){ try{ this.ctx=new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} } return this.ctx; }, ping(freq=880,dur=0.07,type='sine',vol=0.05){ if (!STATE.soundOn) return; const c=this.C; if(!c) return; const o=c.createOscillator(), g=c.createGain(); o.type=type; o.frequency.setValueAtTime(freq,c.currentTime); g.gain.setValueAtTime(vol,c.currentTime); g.gain.exponentialRampToValueAtTime(0.0001,c.currentTime+dur); o.connect(g); g.connect(c.destination); o.start(); o.stop(c.currentTime+dur); }, hover(){ this.ping(680,0.035,'triangle',0.02); }, click(){ this.ping(980,0.08,'sine',0.045); setTimeout(()=>this.ping(1320,0.06,'sine',0.03),55); }, pop() { this.ping(520,0.12,'square',0.028); }, sweep(){ if (!STATE.soundOn) return; const c=this.C; if(!c) return; const o=c.createOscillator(), g=c.createGain(); o.type='sawtooth'; o.frequency.setValueAtTime(320,c.currentTime); o.frequency.exponentialRampToValueAtTime(1200,c.currentTime+0.3); g.gain.setValueAtTime(0.02,c.currentTime); g.gain.exponentialRampToValueAtTime(0.0001,c.currentTime+0.3); o.connect(g); g.connect(c.destination); o.start(); o.stop(c.currentTime+0.3); } }; /* ---------- Toast ---------- */ const toastEl=document.getElementById('toast'); let toastTo=null; function toast(msg,ms=2400){ toastEl.textContent=msg; toastEl.classList.add('show'); clearTimeout(toastTo); toastTo=setTimeout(()=>toastEl.classList.remove('show'),ms); } /* ---------- Fetch ---------- */ function resolveUrl(c,ctx){ return typeof c.url==='function' ? c.url(ctx) : c.url; } async function fetchCarrier(c){ if (c.requires==='geo' && STATE.geo.lat==null) return { ok:false, skipped:true, items:[], err:'no-geo' }; const url=resolveUrl(c,STATE.geo); const t0=performance.now(); const tryDirect=async()=>{ const r=await fetch(url,{headers:{Accept:'application/json'}}); if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); }; const tryProxy =async()=>{ const r=await fetch(`${CONNLINK}?carrier=${encodeURIComponent(c.key)}`); if(!r.ok) throw new Error('proxy HTTP '+r.status); return r.json(); }; try{ let raw; if (c.source==='direct') raw=await tryDirect(); else if (c.source==='proxy') raw=await tryProxy(); else if (c.source==='direct+proxy') { try{ raw=await tryDirect(); } catch{ raw=await tryProxy(); } } const items=await Promise.resolve(c.normalize(raw)); return { ok:true, items:(items||[]).map(it=>({...it, carrier:c.key, cat:c.cat, label:c.label, glyph:c.glyph})), ms:Math.round(performance.now()-t0) }; } catch(e){ return { ok:false, items:[], err:String(e.message||e), ms:Math.round(performance.now()-t0) }; } } async function fetchAll(){ SND.sweep(); const res=await Promise.all(CARRIERS.map(c=>fetchCarrier(c).then(r=>({c,...r})))); STATE.items=[]; STATE.byCarrier={}; STATE.status={}; for (const r of res){ STATE.status[r.c.key]={ ok:r.ok, ms:r.ms, err:r.err, skipped:r.skipped }; STATE.byCarrier[r.c.key]=r.items||[]; STATE.items.push(...(r.items||[])); } render(); updateHealthList(); updateStats(); loadReddit(); } function updateStats(){ const ok=Object.values(STATE.status).filter(s=>s.ok).length; const fail=Object.values(STATE.status).filter(s=>!s.ok && !s.skipped).length; const fresh = STATE.items.filter(i => i.at && (Date.now() - new Date(i.at).getTime()) < 3600e3).length; document.getElementById('sFeeds').textContent = CARRIERS.length; document.getElementById('sItems').textContent = STATE.items.length; document.getElementById('sOk').textContent = ok; document.getElementById('sFresh').textContent = fresh; document.getElementById('fFeeds').textContent = CARRIERS.length; document.getElementById('fItems').textContent = STATE.items.length; document.getElementById('fOk').textContent = ok; document.getElementById('fFail').textContent = fail; document.getElementById('leadN').textContent = CARRIERS.length; document.getElementById('fabDot').style.background = fail>0 ? 'var(--red)' : 'var(--teal)'; document.getElementById('fabDot').style.boxShadow = '0 0 10px ' + (fail>0 ? 'var(--red)' : 'var(--teal)'); } function updateHealthList(){ const host = document.getElementById('healthList'); host.innerHTML = ''; CARRIERS.forEach(c => { const s = STATE.status[c.key]; const row = document.createElement('div'); row.className='kv'; const status = !s ? '—' : s.skipped ? 'skip' : s.ok ? `${s.ms}ms` : '✗'; const cls = !s ? '' : s.skipped ? 'a' : s.ok ? 'g' : 'r'; row.innerHTML = `<span>${c.glyph} ${c.label}</span><b class="${cls}">${status}</b>`; host.appendChild(row); }); } /* ---------- Filter / query ---------- */ function visibleItems(){ const f=FILTERS.find(x=>x.key===STATE.filter); let list = (!f||!f.cats) ? STATE.items.slice() : STATE.items.filter(it => f.cats.includes(it.cat)); if (STATE.query){ const q = STATE.query.toLowerCase(); list = list.filter(it => (it.title||'').toLowerCase().includes(q) || (it.snippet||'').toLowerCase().includes(q) || (it.label||'').toLowerCase().includes(q)); } list.sort((a,b)=> (new Date(b.at||0)) - (new Date(a.at||0))); return list; } function itemsByCat(cats){ const list = STATE.items.filter(it => cats.includes(it.cat)); if (STATE.query){ const q = STATE.query.toLowerCase(); return list.filter(it => (it.title||'').toLowerCase().includes(q) || (it.snippet||'').toLowerCase().includes(q) || (it.label||'').toLowerCase().includes(q)) .sort((a,b)=> (new Date(b.at||0)) - (new Date(a.at||0))); } return list.sort((a,b)=> (new Date(b.at||0)) - (new Date(a.at||0))); } /* ---------- Card (plain, no tilt) ---------- */ function mkCard(it){ const el = document.createElement('article'); el.className = 'card cat-'+it.cat; el.innerHTML = ` <div class="thumb"> <div class="badge">${escapeHtml(it.label||it.carrier)}</div> <div class="glyph">${it.glyph || '✨'}</div> </div> <div class="body"> <div class="src">${escapeHtml((it.cat||'').toUpperCase())} · ${shortTime(it.at)}</div> <div class="title">${escapeHtml(it.title||'')}</div> <div class="snippet">${escapeHtml(it.snippet||'')}</div> <div class="foot"><span>${it.glyph||''} ${escapeHtml(it.label||'')}</span><span class="go">↗ read</span></div> </div>`; el.addEventListener('pointerenter', ()=>SND.hover()); el.addEventListener('click', (e)=>{ e.preventDefault(); SND.click(); openReader(it); }); return el; } function fillRail(railEl, list, limit){ railEl.innerHTML = ''; const L = limit ?? STATE.density; const items = list.slice(0, L); if (!items.length){ const empty = document.createElement('div'); empty.className='card'; empty.style.cssText='padding:18px;max-width:280px;flex:0 0 280px'; empty.innerHTML = `<div class="body"><div class="src">—</div><div class="title">No items</div><div class="snippet">Try another filter or tap 📍 Location to unlock geo feeds.</div></div>`; railEl.appendChild(empty); return; } items.forEach(it => railEl.appendChild(mkCard(it))); } /* ---------- Category tiles (plain, no tilt) ---------- */ function renderCatGrid(){ const host = document.getElementById('catGrid'); host.innerHTML = ''; // 🔥 Top 100 tile (first, prominent) const top100 = document.createElement('div'); top100.className = 'cat-tile top100-tile'; const newsCount = STATE.items.filter(i=>i.cat==='news').length; top100.innerHTML = `<div class="count">${Math.min(100, newsCount)}</div> <div class="ic">📡</div> <div class="t">Top 100 News</div> <div class="s">Aggregated & ranked · auto-scroll</div>`; top100.addEventListener('pointerenter', ()=>SND.hover()); top100.addEventListener('click', ()=>{ SND.click(); openTop100(); }); host.appendChild(top100); Object.entries(CAT_META).forEach(([cat, meta])=>{ const count = STATE.items.filter(i=>i.cat===cat).length; const tile = document.createElement('div'); tile.className = 'cat-tile cat-'+cat; tile.innerHTML = `<div class="count">${count}</div> <div class="ic">${meta.ic}</div> <div class="t">${meta.title}</div> <div class="s">${meta.sub}</div>`; tile.addEventListener('pointerenter', ()=>SND.hover()); tile.addEventListener('click', ()=>{ SND.click(); const f = FILTERS.find(f=>f.cats && f.cats.includes(cat)); STATE.filter = f ? f.key : 'all'; renderFilterChips(); render(); document.getElementById('explore').scrollIntoView({behavior:'smooth'}); }); host.appendChild(tile); }); } /* ---------- Weather feature ---------- */ function renderWeather(){ const host = document.getElementById('wxHost'); host.innerHTML = ''; const wx = (STATE.byCarrier.open_meteo||[])[0]?._weather; renderWxPill(wx); if (!wx){ const d = document.createElement('div'); d.className='wx-feat'; d.innerHTML = `<div class="wx-icon">⛅</div> <div><div class="wx-temp">—<small>°F</small></div> <div class="wx-status">Weather is gated on your location</div> <div class="wx-place">Tap 📍 Location to unlock current conditions + 7-day forecast</div></div>`; host.appendChild(d); return; } const [ic,lab] = wmo(wx.now.weathercode); const box = document.createElement('div'); box.className='wx-feat'; box.innerHTML = ` <div class="wx-icon">${ic}</div> <div> <div class="wx-temp">${Math.round(wx.now.temperature_2m)}<small>°F</small></div> <div class="wx-status">${escapeHtml(lab)} · feels like ${Math.round(wx.now.apparent_temperature)}°F</div> <div class="wx-place">📍 ${escapeHtml(STATE.geo.place||'—')}</div> <div class="wx-extra"><span>💨 <b>${Math.round(wx.now.wind_speed_10m)}</b> mph</span><span>💧 <b>${Math.round(wx.now.relative_humidity_2m)}</b>%</span></div> </div> <div class="wx-cast"></div>`; const cast = box.querySelector('.wx-cast'); const days = wx.daily?.time||[]; days.slice(0,7).forEach((dStr,i)=>{ const [icD,labD]=wmo(wx.daily.weathercode[i]); const dow=new Date(dStr).toLocaleDateString(undefined,{weekday:'short'}); const hi=Math.round(wx.daily.temperature_2m_max[i]); const lo=Math.round(wx.daily.temperature_2m_min[i]); const pop=wx.daily.precipitation_probability_max?.[i] ?? null; const day = document.createElement('div'); day.className='wx-day'; day.title=labD; day.innerHTML=`<div class="dow">${dow}</div><div class="ico">${icD}</div><div class="hi">${hi}°</div><div class="lo">${lo}°</div>${pop!=null?`<div class="pop">💧${pop}%</div>`:''}`; day.addEventListener('mouseenter', ()=>SND.hover()); cast.appendChild(day); }); host.appendChild(box); } /* ---------- Nav weather pill + hover popover ---------- */ function renderWxPill(wx){ const ic = document.getElementById('wxPillIc'); const t = document.getElementById('wxPillT'); const p = document.getElementById('wxPillP'); const popIc = document.getElementById('wxPopIc'); const popTemp=document.getElementById('wxPopTemp'); const popLab =document.getElementById('wxPopLab'); const popLoc =document.getElementById('wxPopLoc'); const popDays=document.getElementById('wxPopDays'); const popExtra=document.getElementById('wxPopExtra'); if (!wx){ ic.textContent='⛅'; t.textContent='—°'; p.textContent='Set location'; popIc.textContent='⛅'; popTemp.innerHTML='—<small>°F</small>'; popLab.textContent='Weather gated on location'; popLoc.textContent='📍 Tap “Use location” to unlock'; popDays.innerHTML=''; popExtra.textContent='—'; return; } const [glyph,label]=wmo(wx.now.weathercode); const temp=Math.round(wx.now.temperature_2m); ic.textContent=glyph; t.textContent=`${temp}°F`; p.textContent = STATE.geo.place ? STATE.geo.place.split(',')[0] : label; popIc.textContent=glyph; popTemp.innerHTML=`${temp}<small>°F</small>`; popLab.textContent=`${label} · feels ${Math.round(wx.now.apparent_temperature)}°F`; popLoc.textContent=`📍 ${STATE.geo.place||'—'}`; popExtra.textContent=`💨 ${Math.round(wx.now.wind_speed_10m)}mph · 💧 ${Math.round(wx.now.relative_humidity_2m)}%`; popDays.innerHTML=''; const days = wx.daily?.time||[]; days.slice(0,7).forEach((dStr,i)=>{ const [icD,labD]=wmo(wx.daily.weathercode[i]); const dow=new Date(dStr).toLocaleDateString(undefined,{weekday:'short'}); const hi=Math.round(wx.daily.temperature_2m_max[i]); const lo=Math.round(wx.daily.temperature_2m_min[i]); const d=document.createElement('div'); d.className='d'; d.title=labD; d.innerHTML=`<div class="dow">${dow}</div><div class="ic">${icD}</div><div class="hi">${hi}°</div><div class="lo">${lo}°</div>`; popDays.appendChild(d); }); } /* ---------- Hero feature (removed — box retired) ---------- */ function renderHero(){ /* intentionally empty — hero-card removed */ } /* ---------- Reader Canvas ---------- */ const reader = document.getElementById('reader'); let _currentItem = null; function openReader(it){ _currentItem = it; document.getElementById('rBadge').textContent = (it.label || it.carrier || 'SOURCE').toUpperCase(); document.getElementById('rTitle').textContent = it.title || '—'; document.getElementById('rMeta').textContent = `${(it.cat||'').toUpperCase()} · ${shortTime(it.at)}${it.at?` · ${new Date(it.at).toLocaleString?.()||''}`:''}`; document.getElementById('rSnip').textContent = it.snippet || '(No extended preview available for this source. Open the original for the full article.)'; document.getElementById('rGlyph').textContent = it.glyph || '✨'; // pills const pills = document.getElementById('rPills'); pills.innerHTML = ''; [`${it.glyph||''} ${it.label||''}`.trim(), (it.cat||'').toUpperCase(), shortTime(it.at)].filter(Boolean).forEach(p=>{ const s=document.createElement('span'); s.className='pill'; s.textContent=p; pills.appendChild(s); }); // header tint const hdr=document.getElementById('rHdr'); hdr.style.background = ({ news: 'radial-gradient(circle at 30% 30%, rgba(75,227,255,.35), transparent 40%), linear-gradient(135deg,#0f2a52,#1b3d7a)', alert: 'radial-gradient(circle at 30% 30%, rgba(255,107,143,.4), transparent 40%), linear-gradient(135deg,#4a0b2e,#7a1d4a)', weather: 'radial-gradient(circle at 30% 30%, rgba(75,227,255,.35), transparent 40%), linear-gradient(135deg,#0b3a4a,#1d6a7a)', nasa: 'radial-gradient(circle at 30% 30%, rgba(75,227,255,.32), transparent 40%), linear-gradient(135deg,#0b1b4a,#2a1d7a)', tech: 'radial-gradient(circle at 30% 30%, rgba(255,179,71,.35), transparent 40%), linear-gradient(135deg,#3a2a0b,#7a4a1d)', market: 'radial-gradient(circle at 30% 30%, rgba(124,255,158,.32), transparent 40%), linear-gradient(135deg,#0b3a2a,#1d7a4a)', gov: 'radial-gradient(circle at 30% 30%, rgba(122,92,255,.35), transparent 40%), linear-gradient(135deg,#1a1052,#3a1d7a)', quake: 'radial-gradient(circle at 30% 30%, rgba(255,179,71,.35), transparent 40%), linear-gradient(135deg,#3a1a0b,#7a3a1d)', wiki: 'radial-gradient(circle at 30% 30%, rgba(255,91,209,.32), transparent 40%), linear-gradient(135deg,#3a0b3a,#7a1d6a)', quote: 'radial-gradient(circle at 30% 30%, rgba(255,91,209,.28), transparent 40%), linear-gradient(135deg,#2a0b3a,#4a1d7a)', }[it.cat] || 'linear-gradient(135deg, #1a1052, #4a1d7a)'); // external open link const a = document.getElementById('rOpen'); if (it.url && it.url!=='#'){ a.style.display=''; a.href=it.url; } else { a.style.display='none'; a.href='#'; } reader.classList.add('open'); reader.setAttribute('aria-hidden','false'); SND.pop(); } function closeReader(){ reader.classList.remove('open'); reader.setAttribute('aria-hidden','true'); _currentItem=null; } document.getElementById('rClose').addEventListener('click', closeReader); document.getElementById('rBackClose').addEventListener('click', closeReader); document.getElementById('rCopy').addEventListener('click', async ()=>{ if (!_currentItem) return; try{ await navigator.clipboard.writeText(_currentItem.url||_currentItem.title||''); toast('Link copied.'); }catch{ toast('Copy blocked.'); } }); document.addEventListener('keydown', (e)=>{ if (e.key==='Escape'){ closeReader(); closeDrawer(); closeTop100(); } }); /* ---------- Top 100 Center Panel ---------- */ const cpanel = document.getElementById('cpanel'); const cpBody = document.getElementById('cpBody'); const cpBar = document.getElementById('cpBar'); const cpCount= document.getElementById('cpCount'); const cpAutoBtn = document.getElementById('cpAuto'); let _cpIO = null, _cpAuto = null; function openTop100(){ // gather top 100 news items, sorted by recency const list = STATE.items .filter(i => i.cat === 'news') .sort((a,b)=> new Date(b.at||0) - new Date(a.at||0)) .slice(0, 100); cpBody.innerHTML = ''; if (!list.length){ const empty=document.createElement('div'); empty.className='cp-row in'; empty.style.gridTemplateColumns='1fr'; empty.innerHTML=`<div class="info"><div class="t">No news items yet.</div><div class="m">Try refreshing or waiting for feeds to load.</div></div>`; cpBody.appendChild(empty); } else { list.forEach((it,i)=>{ const row = document.createElement('div'); row.className='cp-row'; row.innerHTML = ` <div class="rank">${String(i+1).padStart(2,'0')}</div> <div class="gly">${it.glyph||'📰'}</div> <div class="info"> <div class="t">${escapeHtml(it.title||'')}</div> <div class="m">${escapeHtml(it.label||it.carrier||'')} · ${shortTime(it.at)}${it.snippet?` · ${escapeHtml((it.snippet||'').slice(0,120))}`:''}</div> </div> <div class="go">↗ read</div>`; row.addEventListener('pointerenter', ()=>SND.hover()); row.addEventListener('click', ()=>{ SND.click(); openReader(it); }); cpBody.appendChild(row); }); } cpCount.textContent = `0 / ${list.length}`; cpBar.style.width = '0%'; cpanel.classList.add('show'); cpanel.setAttribute('aria-hidden','false'); SND.pop(); // fade-in-on-scroll dynamic if (_cpIO) _cpIO.disconnect(); _cpIO = new IntersectionObserver(entries=>{ for (const e of entries){ if (e.isIntersecting){ e.target.classList.add('in'); _cpIO.unobserve(e.target); } } }, { root: cpBody, threshold:.1 }); cpBody.querySelectorAll('.cp-row').forEach(r=>_cpIO.observe(r)); // first few visible instantly cpBody.querySelectorAll('.cp-row').forEach((r,i)=>{ if (i<6) r.classList.add('in'); }); // progress + counter on scroll cpBody.onscroll = ()=>{ const pct = cpBody.scrollHeight - cpBody.clientHeight > 0 ? (cpBody.scrollTop / (cpBody.scrollHeight - cpBody.clientHeight)) * 100 : 0; cpBar.style.width = pct.toFixed(1) + '%'; // nearest row in viewport = "current" const rows = cpBody.querySelectorAll('.cp-row'); const top = cpBody.scrollTop; let cur = 0; rows.forEach((r,i)=>{ if (r.offsetTop - top <= 120) cur = i+1; }); cpCount.textContent = `${cur} / ${rows.length}`; }; } function closeTop100(){ cpanel.classList.remove('show'); cpanel.setAttribute('aria-hidden','true'); if (_cpAuto){ clearInterval(_cpAuto); _cpAuto=null; cpAutoBtn.classList.remove('on'); cpAutoBtn.textContent='▶ Auto-scroll'; } } document.getElementById('cpClose').addEventListener('click', closeTop100); cpanel.addEventListener('click', (e)=>{ if (e.target===cpanel) closeTop100(); }); cpAutoBtn.addEventListener('click', ()=>{ if (_cpAuto){ clearInterval(_cpAuto); _cpAuto=null; cpAutoBtn.classList.remove('on'); cpAutoBtn.textContent='▶ Auto-scroll'; SND.pop(); return; } cpAutoBtn.classList.add('on'); cpAutoBtn.textContent='⏸ Scrolling'; _cpAuto = setInterval(()=>{ if (cpBody.scrollTop + cpBody.clientHeight >= cpBody.scrollHeight - 2){ cpBody.scrollTop = 0; } else { cpBody.scrollTop += 2; } }, 24); SND.click(); }); /* ---------- Drawer ---------- */ const drawer = document.getElementById('drawer'); const scrim = document.getElementById('scrim'); function openDrawer(){ drawer.classList.add('open'); scrim.classList.add('show'); SND.sweep(); } function closeDrawer(){ drawer.classList.remove('open'); scrim.classList.remove('show'); } document.getElementById('fab').addEventListener('click', openDrawer); document.getElementById('drawerClose').addEventListener('click', closeDrawer); scrim.addEventListener('click', ()=>{ closeDrawer(); }); /* ---------- GS Menu ---------- */ function renderGsMenu(){ const host = document.getElementById('gsMenu'); host.innerHTML=''; GS_MENU.forEach(m=>{ const a = document.createElement('a'); a.className='gs-btn'; a.href=m.url; a.target='_blank'; a.rel='noopener'; a.innerHTML = `<span class="ic">${m.ic}</span><span><span class="lbl">${escapeHtml(m.lbl)}</span><br><span class="sub">${escapeHtml(m.sub)}</span></span>`; a.addEventListener('mouseenter', ()=>SND.hover()); a.addEventListener('click', ()=>SND.click()); host.appendChild(a); }); } /* ---------- Filter chips ---------- */ function renderFilterChips(){ const host = document.getElementById('filterChips'); host.innerHTML = ''; FILTERS.forEach(f=>{ const b = document.createElement('button'); b.className = 'chip' + (STATE.filter===f.key?' active':''); b.textContent = f.label; b.addEventListener('click', ()=>{ STATE.filter = f.key; renderFilterChips(); render(); SND.click(); }); host.appendChild(b); }); } /* ---------- Render ---------- */ function render(){ renderHero(); renderCatGrid(); fillRail(document.getElementById('railPopular'), visibleItems(), Math.min(12, STATE.density)); fillRail(document.getElementById('railNews'), itemsByCat(['news'])); fillRail(document.getElementById('railAlerts'), itemsByCat(['alert','gov','quake'])); fillRail(document.getElementById('railNasa'), itemsByCat(['nasa'])); fillRail(document.getElementById('railTech'), itemsByCat(['tech'])); fillRail(document.getElementById('railMarket'), itemsByCat(['market'])); fillRail(document.getElementById('railWiki'), itemsByCat(['wiki','quote'])); fillRail(document.getElementById('railSocial'), STATE.social || []); renderWeather(); renderTicker(); renderSocial(); } /* ---------- News Ticker (reel/banner) ---------- */ function renderTicker(){ const track = document.getElementById('tickerTrack'); if (!track) return; const top = STATE.items.filter(i=>i.cat==='news') .sort((a,b)=> new Date(b.at||0)-new Date(a.at||0)).slice(0,100); if (!top.length){ track.innerHTML = `<span class="ticker-item"><span class="rk">—</span><span>Loading signal feeds…</span></span>`; return; } const row = top.map((it,i)=> ` <span class="ticker-item" data-url="${(it.url||'').replace(/"/g,'"')}" data-idx="${i}"> <span class="rk">${String(i+1).padStart(2,'0')}</span> <span class="src">${escapeHtml((it.label||it.carrier||'').toUpperCase())}</span> <span class="sep">·</span> <span>${escapeHtml(it.title||'')}</span> </span>`).join(''); // duplicate track content for seamless looping track.innerHTML = row + row; track.querySelectorAll('.ticker-item').forEach(el=>{ el.addEventListener('click', (e)=>{ e.preventDefault(); const idx = parseInt(el.dataset.idx,10) % top.length; SND.click(); openReader(top[idx]); }); }); } document.getElementById('tickerBadge')?.addEventListener('click', ()=>{ SND.click(); openTop100(); }); /* ---------- Social ---------- */ const SOCIAL_PLATFORMS = [ { k:'sx', name:'X (Twitter)', glyph:'𝕏', url:u=>`https://x.com/${u}` }, { k:'sig', name:'Instagram', glyph:'📷', url:u=>`https://instagram.com/${u}` }, { k:'sfb', name:'Facebook', glyph:'f', url:u=>`https://facebook.com/${u}` }, { k:'stk', name:'TikTok', glyph:'♪', url:u=>`https://tiktok.com/@${u}` }, { k:'syt', name:'YouTube', glyph:'▶', url:u=>`https://youtube.com/@${u}` }, { k:'sli', name:'LinkedIn', glyph:'in', url:u=>`https://linkedin.com/in/${u}` }, { k:'srd', name:'Reddit', glyph:'r', url:u=>`https://reddit.com/user/${u}` }, { k:'spt', name:'Pinterest', glyph:'P', url:u=>`https://pinterest.com/${u}` }, { k:'sth', name:'Threads', glyph:'@', url:u=>`https://threads.net/@${u}` }, { k:'smt', name:'Mastodon', glyph:'𝕄', url:u=>u.includes('@')?`https://${u.split('@')[2]}/@${u.split('@')[1]}`:`https://mastodon.social/@${u}` }, { k:'sbs', name:'Bluesky', glyph:'☁', url:u=>`https://bsky.app/profile/${u}` }, { k:'sdc', name:'Discord', glyph:'🎮', url:u=>u.startsWith('http')?u:`https://discord.com/users/${u}` }, { k:'stw', name:'Twitch', glyph:'🎥', url:u=>`https://twitch.tv/${u}` }, { k:'ssn', name:'Snapchat', glyph:'👻', url:u=>`https://snapchat.com/add/${u}` }, { k:'sgh', name:'GitHub', glyph:'⌨', url:u=>`https://github.com/${u}` }, { k:'ssb', name:'Substack', glyph:'S', url:u=>u.includes('.')?`https://${u}`:`https://${u}.substack.com` }, { k:'smd', name:'Medium', glyph:'M', url:u=>`https://medium.com/@${u}` }, { k:'stu', name:'Tumblr', glyph:'t', url:u=>`https://${u}.tumblr.com` }, { k:'sva', name:'Vimeo', glyph:'V', url:u=>`https://vimeo.com/${u}` }, ]; function loadSocHandles(){ try{ return JSON.parse(localStorage.getItem('simon_social')||'{}'); }catch{ return {}; } } function saveSocHandles(h){ try{ localStorage.setItem('simon_social', JSON.stringify(h)); }catch{} } function renderSocial(){ const host = document.getElementById('socialGrid'); if (!host) return; const handles = loadSocHandles(); host.innerHTML = ''; SOCIAL_PLATFORMS.forEach(p=>{ const tile = document.createElement('div'); tile.className = 'soc-tile '+p.k; const h = handles[p.k]; const displayHandle = h ? (h.startsWith('@')?h:'@'+h) : 'click to set handle'; tile.innerHTML = ` <div class="sic">${p.glyph}</div> <div class="info"> <div class="name">${p.name}</div> <div class="handle">${escapeHtml(displayHandle)}</div> </div> <div class="ext">↗</div>`; tile.addEventListener('pointerenter', ()=>SND.hover()); tile.addEventListener('click', ()=>{ SND.click(); const existing = handles[p.k]; if (!existing){ const raw = prompt(`${p.name} handle/username:`); if (!raw) return; const clean = raw.trim().replace(/^@/,''); handles[p.k] = clean; saveSocHandles(handles); renderSocial(); try{ window.open(p.url(clean), '_blank', 'noopener'); }catch{} } else { try{ window.open(p.url(existing), '_blank', 'noopener'); }catch{} } }); host.appendChild(tile); }); } document.getElementById('socSetBtn')?.addEventListener('click', ()=>{ SND.click(); const handles = loadSocHandles(); const key = prompt('Which platform key? e.g. sx (X), sig (Instagram), syt (YouTube), sli (LinkedIn), sgh (GitHub), srd (Reddit), stk (TikTok), sbs (Bluesky), sth (Threads)'); if (!key) return; const plat = SOCIAL_PLATFORMS.find(p=>p.k===key.trim().toLowerCase()); if (!plat){ toast('Unknown platform key'); return; } const raw = prompt(`${plat.name} handle (without @):`, handles[plat.k]||''); if (raw===null) return; const clean = raw.trim().replace(/^@/,''); if (!clean){ delete handles[plat.k]; } else { handles[plat.k] = clean; } saveSocHandles(handles); renderSocial(); toast(`Saved ${plat.name}`); }); /* Load Reddit as a live social rail (JSON endpoint, CORS-friendly) */ STATE.social = []; async function loadReddit(){ try{ const r = await fetch('https://www.reddit.com/r/popular.json?limit=20'); if (!r.ok) return; const j = await r.json(); const items = (j?.data?.children||[]).map(c=>{ const d = c.data||{}; return { cat:'news', carrier:'reddit', label:'r/'+(d.subreddit||'popular'), glyph:'👽', title: d.title||'', url: 'https://reddit.com'+(d.permalink||''), at: new Date((d.created_utc||0)*1000).toISOString(), summary: d.selftext ? d.selftext.slice(0,280) : (d.url_overridden_by_dest||'') }; }); STATE.social = items; fillRail(document.getElementById('railSocial'), items); }catch(e){ /* silent */ } } /* ---------- Screensaver (idle mode) ---------- */ const IDLE_MS = 120000; // 2 minutes let _idleTimer = null; function resetIdle(){ if (_idleTimer) clearTimeout(_idleTimer); hideScreensaver(); _idleTimer = setTimeout(showScreensaver, IDLE_MS); } function showScreensaver(){ const ss = document.getElementById('screensaver'); if (!ss || ss.classList.contains('on')) return; // populate ticker const top = STATE.items.filter(i=>i.cat==='news') .sort((a,b)=> new Date(b.at||0)-new Date(a.at||0)).slice(0,100); const track = document.getElementById('ssTicker'); if (top.length){ const row = top.map((it,i)=>`<span class="ticker-item"><span class="rk">${String(i+1).padStart(2,'0')}</span><span>${escapeHtml(it.title||'')}</span></span>`).join(''); track.innerHTML = row + row; } else { track.innerHTML = '<span class="ticker-item"><span class="rk">—</span><span>SIMON · Gaylord Sinclair · Cognitive Sovereignty Engine</span></span>'; } updateSsClock(); ss.classList.add('on'); ss.setAttribute('aria-hidden','false'); } function hideScreensaver(){ const ss = document.getElementById('screensaver'); if (!ss) return; if (ss.classList.contains('on')){ ss.classList.remove('on'); ss.setAttribute('aria-hidden','true'); } } function updateSsClock(){ const c = document.getElementById('ssClock'); const d = document.getElementById('ssDate'); if (!c) return; const now = new Date(); const hh = now.getHours(); const mm = String(now.getMinutes()).padStart(2,'0'); const h12 = ((hh+11)%12)+1; const ap = hh<12?'AM':'PM'; c.textContent = `${h12}:${mm} ${ap}`; if (d) d.textContent = now.toLocaleDateString(undefined,{weekday:'long', month:'long', day:'numeric', year:'numeric'}); } setInterval(updateSsClock, 10000); ['mousemove','mousedown','keydown','touchstart','scroll','wheel'].forEach(ev=>{ window.addEventListener(ev, resetIdle, {passive:true}); }); // wake immediately on any input inside the screensaver document.getElementById('screensaver')?.addEventListener('click', hideScreensaver); resetIdle(); /* ---------- Wire HUD ---------- */ document.getElementById('q').addEventListener('input', (e)=>{ STATE.query = e.target.value.trim(); render(); }); document.getElementById('q').addEventListener('keydown', (e)=>{ if (e.key === 'Enter'){ e.preventDefault(); const v = e.currentTarget.value.trim(); if (v) openWebSearch(v); } }); document.getElementById('searchGo').addEventListener('click', ()=>{ const v = document.getElementById('q').value.trim(); if (v) openWebSearch(v); }); document.getElementById('wsClose').addEventListener('click', closeWebSearch); document.getElementById('websearch').addEventListener('click', (e)=>{ if (e.target.id==='websearch') closeWebSearch(); }); document.querySelectorAll('#wsTabs .ws-tab').forEach(btn=>{ btn.addEventListener('click', ()=>{ document.querySelectorAll('#wsTabs .ws-tab').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); renderWebSearch(btn.dataset.src); }); }); /* ---------- Web Search (DuckDuckGo + Google CSE + Wikipedia + NewsAPI) ---------- */ const WS = { query:'', results:{ google:[], duck:[], wiki:[], news:[] } }; function openWebSearch(q){ WS.query = q; document.getElementById('wsQuery').textContent = q; document.getElementById('websearch').classList.add('open'); document.getElementById('wsBody').innerHTML = `<div class="ws-empty">Searching for <b>${escapeHtml(q)}</b>…</div>`; ['all','google','duck','wiki','news'].forEach(k=>{ const n=document.getElementById('wsN_'+k); if(n) n.textContent='…'; }); WS.results = { google:[], duck:[], wiki:[], news:[] }; // reset active to 'all' document.querySelectorAll('#wsTabs .ws-tab').forEach(b=>b.classList.toggle('active', b.dataset.src==='all')); runWebSearch(q); SND.click && SND.click(); } function closeWebSearch(){ document.getElementById('websearch').classList.remove('open'); } async function runWebSearch(q){ const ddgQ = encodeURIComponent(q); const wikiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${ddgQ}&srlimit=10&format=json&origin=*`; const ddgUrl = `https://api.duckduckgo.com/?q=${ddgQ}&format=json&no_html=1&skip_disambig=1&t=simon`; const gUrl = `${GSEARCH_PROXY()}?q=${ddgQ}&num=10`; // News pulled via NewsAPI proxy (everything endpoint) const newsUrl = `${NEWSAPI_PROXY()}?endpoint=everything&q=${ddgQ}&pageSize=10&sortBy=publishedAt`; const tasks = [ { key:'google', fn: async ()=>{ try{ const r = await fetch(gUrl, {cache:'no-store'}); if(!r.ok) throw 0; const j = await r.json(); return (j.results||[]).map(x=>({title:x.title, snippet:x.snippet, url:x.url, source:x.source||'google', image:x.image||''})); }catch(e){ return []; } }}, { key:'duck', fn: async ()=>{ try{ const r = await fetch(ddgUrl, {cache:'no-store'}); if(!r.ok) throw 0; const j = await r.json(); const out = []; if (j.AbstractText) out.push({ title:j.Heading||q, snippet:j.AbstractText, url:j.AbstractURL||'#', source:j.AbstractSource||'duckduckgo', image:j.Image?('https://duckduckgo.com'+j.Image):''}); (j.RelatedTopics||[]).forEach(t=>{ if (t.Text && t.FirstURL) out.push({ title:(t.Text.split(' - ')[0]||t.Text).slice(0,90), snippet:t.Text, url:t.FirstURL, source:'duckduckgo', image:t.Icon && t.Icon.URL ? ('https://duckduckgo.com'+t.Icon.URL) : '' }); else if (t.Topics) t.Topics.forEach(tt=>{ if (tt.Text && tt.FirstURL) out.push({ title:(tt.Text.split(' - ')[0]||tt.Text).slice(0,90), snippet:tt.Text, url:tt.FirstURL, source:'duckduckgo', image:tt.Icon && tt.Icon.URL ? ('https://duckduckgo.com'+tt.Icon.URL) : '' }); }); }); return out.slice(0,12); }catch(e){ // Fallback: DDG HTML redirect link (always works, but no snippets). return [{ title:q, snippet:'Open on DuckDuckGo →', url:`https://duckduckgo.com/?q=${ddgQ}`, source:'duckduckgo', image:'' }]; } }}, { key:'wiki', fn: async ()=>{ try{ const r = await fetch(wikiUrl); if(!r.ok) throw 0; const j = await r.json(); return (j.query?.search||[]).map(h=>({ title: h.title, snippet: (h.snippet||'').replace(/<[^>]+>/g,''), url: 'https://en.wikipedia.org/wiki/'+encodeURIComponent(h.title.replace(/ /g,'_')), source: 'wikipedia', image: '' })); }catch(e){ return []; } }}, { key:'news', fn: async ()=>{ try{ const r = await fetch(newsUrl, {cache:'no-store'}); if(!r.ok) throw 0; const j = await r.json(); return (j.articles||[]).map(a=>({ title: a.title||'', snippet: a.description||'', url: a.url||'#', source: (a.source && a.source.name) || 'news', image: a.urlToImage||'' })); }catch(e){ return []; } }}, ]; // Run in parallel; update UI as each settles. tasks.forEach(t=>{ t.fn().then(list=>{ if (WS.query !== q) return; // stale WS.results[t.key] = list || []; const n = document.getElementById('wsN_'+t.key); if (n) n.textContent = (list||[]).length; const all = WS.results.google.length + WS.results.duck.length + WS.results.wiki.length + WS.results.news.length; const na = document.getElementById('wsN_all'); if (na) na.textContent = all; const active = document.querySelector('#wsTabs .ws-tab.active')?.dataset.src || 'all'; renderWebSearch(active); }); }); } function renderWebSearch(which){ const body = document.getElementById('wsBody'); let rows = []; if (which === 'all'){ // interleave for variety const g = WS.results.google.slice(), d = WS.results.duck.slice(), w = WS.results.wiki.slice(), n = WS.results.news.slice(); while (g.length || d.length || w.length || n.length){ if (g.length) rows.push(g.shift()); if (d.length) rows.push(d.shift()); if (w.length) rows.push(w.shift()); if (n.length) rows.push(n.shift()); } } else { rows = WS.results[which] || []; } if (!rows.length){ body.innerHTML = `<div class="ws-empty">No results yet for <b>${escapeHtml(WS.query)}</b>. ${which==='google' ? '(Google CSE may not be configured.)' : ''}</div>`; return; } body.innerHTML = rows.map(r=>` <a class="ws-res" href="${escapeHtml(r.url||'#')}" target="_blank" rel="noopener"> <div class="thumb">${r.image ? `<img src="${escapeHtml(r.image)}" alt="" loading="lazy" onerror="this.style.display='none'">` : '🌐'}</div> <div> <div class="src">${escapeHtml(r.source||'')}</div> <div class="ttl">${escapeHtml(r.title||'(untitled)')}</div> <div class="snip">${escapeHtml((r.snippet||'').slice(0,260))}</div> </div> </a>`).join(''); } document.getElementById('refreshTop').addEventListener('click', ()=>{ SND.click(); fetchAll(); }); document.getElementById('sndBtn').addEventListener('click', (e)=>{ STATE.soundOn = !STATE.soundOn; e.currentTarget.textContent = STATE.soundOn ? '🔊 Hover sound' : '🔇 Muted'; e.currentTarget.classList.toggle('active', STATE.soundOn); if (STATE.soundOn) SND.click(); }); document.getElementById('density').addEventListener('change', (e)=>{ STATE.density = parseInt(e.target.value,10); render(); SND.pop(); }); document.getElementById('autoRefresh').addEventListener('change', (e)=>{ const ms = parseInt(e.target.value,10); STATE.autoRefreshMs = ms; if (STATE.autoTimer) { clearInterval(STATE.autoTimer); STATE.autoTimer=null; } if (ms>0) STATE.autoTimer = setInterval(fetchAll, ms); toast(ms>0 ? `Auto-refresh: every ${Math.round(ms/60000)} min` : 'Auto-refresh off'); SND.pop(); }); document.querySelectorAll('[data-filter]').forEach(btn=>{ btn.addEventListener('click',()=>{ STATE.filter = btn.dataset.filter; renderFilterChips(); render(); SND.click(); document.getElementById('explore').scrollIntoView({behavior:'smooth'}); }); }); function requestGeo(){ SND.click(); if (!navigator.geolocation){ toast('Geolocation not available.'); return; } toast('Requesting your location…'); navigator.geolocation.getCurrentPosition(async pos=>{ STATE.geo.lat=pos.coords.latitude; STATE.geo.lon=pos.coords.longitude; try{ const r=await fetch(`https://geocoding-api.open-meteo.com/v1/reverse?latitude=${STATE.geo.lat}&longitude=${STATE.geo.lon}&count=1`); const j=await r.json(); const p=j?.results?.[0]; STATE.geo.place = p ? [p.name,p.admin1,p.country_code].filter(Boolean).join(', ') : `${STATE.geo.lat.toFixed(2)}, ${STATE.geo.lon.toFixed(2)}`; }catch{ STATE.geo.place = `${STATE.geo.lat.toFixed(2)}, ${STATE.geo.lon.toFixed(2)}`; } const gs = document.getElementById('geoShow'); gs.textContent = '📍 '+STATE.geo.place; toast('Location set: '+STATE.geo.place); try{ localStorage.setItem('simon_geo', JSON.stringify(STATE.geo)); }catch{} fetchAll(); }, err=>toast('Location blocked: '+(err.message||err.code)), { enableHighAccuracy:true, timeout:10000, maximumAge:300000 }); } function clearGeo(){ STATE.geo={lat:null,lon:null,place:null}; try{ localStorage.removeItem('simon_geo'); }catch{} const gs = document.getElementById('geoShow'); if(gs) gs.textContent = '📍 Not set'; toast('Location cleared.'); } document.getElementById('wxPill').addEventListener('click', (e)=>{ // only request-geo if we have no fix yet if (STATE.geo.lat==null) requestGeo(); }); document.getElementById('wxPopGeo').addEventListener('click', (e)=>{ e.stopPropagation(); requestGeo(); }); document.getElementById('wxPopOpen').addEventListener('click', (e)=>{ e.stopPropagation(); document.getElementById('weather').scrollIntoView({behavior:'smooth'}); SND.click(); }); document.getElementById('heroGeo').addEventListener('click', requestGeo); document.getElementById('wxGeo').addEventListener('click', requestGeo); document.getElementById('geoBtn').addEventListener('click', requestGeo); /* ---------- Keys / endpoints management ---------- */ function refreshKeysShow(){ const host = document.getElementById('keysShow'); if(!host) return; const nk = NASA_KEY(); const np = NEWSAPI_PROXY(); const wh = WEBHOOK_URL(); host.innerHTML = ` <div>NASA: <b style="color:${nk==='DEMO_KEY'?'var(--amber)':'var(--teal)'}">${nk==='DEMO_KEY'?'DEMO_KEY (rate-limited)':'custom key set'}</b></div> <div>NewsAPI: <b style="color:${np?'var(--teal)':'var(--amber)'}">${np?'via server proxy':'free mirror only'}</b></div> <div>Webhook: <b style="color:var(--cyan)">${escapeHtml(wh)}</b></div>`; } document.getElementById('setNasaKey').addEventListener('click', ()=>{ SND.click(); const v = prompt('NASA API key (get one free at api.nasa.gov). Leave blank to use DEMO_KEY:', NASA_KEY()==='DEMO_KEY'?'':NASA_KEY()); if (v===null) return; try{ v.trim() ? localStorage.setItem('simon_nasa_key', v.trim()) : localStorage.removeItem('simon_nasa_key'); }catch{} toast('NASA key saved. Refreshing feeds…'); refreshKeysShow(); fetchAll(); }); document.getElementById('setNewsKey').addEventListener('click', ()=>{ SND.click(); /* Key now lives server-side in secrets.php. This button lets you point SIMON at a different proxy URL if you host it elsewhere. */ const v = prompt('NewsAPI proxy URL (server-side; key stays in secrets.php). Leave blank to disable proxy and use free mirror:', NEWSAPI_PROXY()); if (v===null) return; const clean = v.trim(); try{ clean ? localStorage.setItem('simon_newsapi_proxy', clean) : localStorage.removeItem('simon_newsapi_proxy'); }catch{} toast('NewsAPI proxy saved. Refreshing feeds…'); refreshKeysShow(); fetchAll(); }); document.getElementById('setWebhookUrl').addEventListener('click', ()=>{ SND.click(); const v = prompt('Webhook inbox URL (GET should return {items:[...]} array of events). Default is /connlink/test1/ui/webhook_inbox.php?stream=default', WEBHOOK_URL()); if (v===null) return; const clean = v.trim(); try{ clean ? localStorage.setItem('simon_webhook_url', clean) : localStorage.removeItem('simon_webhook_url'); }catch{} toast('Webhook URL saved. Refreshing feeds…'); refreshKeysShow(); fetchAll(); }); document.getElementById('copyWebhookInfo').addEventListener('click', async ()=>{ SND.click(); const url = WEBHOOK_URL(); // Build an absolute URL (so you can paste it into Perior / Zapier / n8n directly) const abs = (()=>{ try{ return new URL(url, location.href).toString(); }catch{ return url; } })(); const streamName = (abs.match(/[?&]stream=([^&]+)/)||[null,'default'])[1]; const postUrl = abs.split('?')[0] + '?stream=' + encodeURIComponent(streamName); try{ await navigator.clipboard.writeText(postUrl); toast('Copied POST URL:\n'+postUrl); } catch{ prompt('Copy this POST URL:', postUrl); } }); refreshKeysShow(); document.getElementById('heroExplore').addEventListener('click', ()=>{ document.getElementById('explore').scrollIntoView({behavior:'smooth'}); SND.click(); }); /* ---------- Helpers ---------- */ function escapeHtml(s){ return String(s==null?'':s).replace(/[&<>"']/g,c=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); } function shortTime(iso){ if (!iso) return ''; const d=new Date(iso); if (isNaN(d)) return String(iso).slice(0,10); const diff=(new Date()-d)/1000; if (diff<60) return 'now'; if (diff<3600) return Math.round(diff/60)+'m'; if (diff<86400) return Math.round(diff/3600)+'h'; if (diff<7*86400) return Math.round(diff/86400)+'d'; return d.toLocaleDateString(); } function tickClock(){ const d=new Date(); document.getElementById('clock').textContent = d.toLocaleString(undefined,{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false})+' · '+d.toDateString(); } setInterval(tickClock,1000); tickClock(); /* ---------- Cosmic storm canvas ---------- Drifting starfield + occasional streaks (meteors) + soft electric arcs. */ (function cosmicStorm(){ const cv = document.getElementById('cosmic'); const ctx = cv.getContext('2d'); let W=0,H=0, stars=[], streaks=[], arcs=[], tLast=0; function resize(){ cv.width = W = Math.floor(window.innerWidth*devicePixelRatio); cv.height = H = Math.floor(window.innerHeight*devicePixelRatio); } window.addEventListener('resize', resize); resize(); const N_STARS = 220; for (let i=0;i<N_STARS;i++){ stars.push({x:Math.random()*W, y:Math.random()*H, r:Math.random()*1.6+0.2, vx:(Math.random()-0.5)*0.15, vy:(Math.random()-0.5)*0.15, tw:Math.random()*Math.PI*2, spd:0.02+Math.random()*0.04}); } function maybeSpawnStreak(){ if (streaks.length<2 && Math.random()<0.01){ const y = Math.random()*H*0.6; streaks.push({x:-120, y, vx:8+Math.random()*10, vy:1+Math.random()*2, life:1, len:80+Math.random()*120, hue:Math.floor(190+Math.random()*80)}); } } function maybeSpawnArc(){ if (arcs.length<1 && Math.random()<0.004){ const x=Math.random()*W, y=Math.random()*H*0.8; const pts=[]; let px=x, py=y; for (let i=0;i<10;i++){ px += (Math.random()-0.5)*60; py += (Math.random()-0.5)*60; pts.push([px,py]); } arcs.push({pts, life:1}); } } function step(t){ const dt = Math.min(40,(t-tLast)||16); tLast=t; if (!STATE.stormOn){ ctx.clearRect(0,0,W,H); requestAnimationFrame(step); return; } // fade trail ctx.fillStyle='rgba(5,4,26,0.25)'; ctx.fillRect(0,0,W,H); // stars for (const s of stars){ s.x += s.vx*dt*0.1; s.y += s.vy*dt*0.1; s.tw += s.spd*dt*0.1; if (s.x<0) s.x=W; if (s.x>W) s.x=0; if (s.y<0) s.y=H; if (s.y>H) s.y=0; const a = 0.45 + 0.55*Math.sin(s.tw); ctx.beginPath(); ctx.arc(s.x,s.y, s.r*devicePixelRatio, 0, Math.PI*2); ctx.fillStyle = `rgba(220,235,255,${a.toFixed(3)})`; ctx.fill(); } // streaks maybeSpawnStreak(); for (const m of streaks){ m.x += m.vx*dt*0.12; m.y += m.vy*dt*0.12; m.life -= 0.003*dt; const grd = ctx.createLinearGradient(m.x, m.y, m.x-m.len, m.y-m.len*0.3); grd.addColorStop(0, `hsla(${m.hue}, 90%, 80%, ${Math.max(0,m.life).toFixed(2)})`); grd.addColorStop(1, 'rgba(0,0,0,0)'); ctx.strokeStyle=grd; ctx.lineWidth=1.6*devicePixelRatio; ctx.beginPath(); ctx.moveTo(m.x,m.y); ctx.lineTo(m.x-m.len, m.y-m.len*0.3); ctx.stroke(); } streaks = streaks.filter(m => m.life>0 && m.x<W+200); // arcs maybeSpawnArc(); for (const a of arcs){ ctx.strokeStyle=`rgba(180,220,255,${(a.life*0.8).toFixed(2)})`; ctx.lineWidth=1.2*devicePixelRatio; ctx.beginPath(); ctx.moveTo(a.pts[0][0],a.pts[0][1]); for (let i=1;i<a.pts.length;i++) ctx.lineTo(a.pts[i][0], a.pts[i][1]); ctx.stroke(); a.life -= 0.03; } arcs = arcs.filter(a=>a.life>0); requestAnimationFrame(step); } requestAnimationFrame(step); })(); /* ---------- Boot ---------- */ try{ const g = JSON.parse(localStorage.getItem('simon_geo')||'null'); if (g && typeof g.lat==='number' && typeof g.lon==='number'){ STATE.geo = { lat:g.lat, lon:g.lon, place:g.place||null }; const gs = document.getElementById('geoShow'); if (gs && STATE.geo.place) gs.textContent = '📍 '+STATE.geo.place; } }catch{} renderFilterChips(); renderGsMenu(); renderCatGrid(); renderSocial(); // show social tiles immediately, even before fetches complete updateSsClock(); fetchAll(); STATE.autoTimer = setInterval(fetchAll, STATE.autoRefreshMs); </script> <script> /* ===== SIMON production telemetry: GA4 + webhook visit/click/search/error/perf ===== */ (() => { 'use strict'; const CFG = window.SIMON_CONFIG || {}; const base = CFG.WEBHOOK_BASE || '/connlink/test1/ui/webhook_inbox.php'; const visitStream = CFG.VISIT_STREAM || 'visits'; const pulseStream = CFG.PULSE_STREAM || 'default'; const key = CFG.KEY ? '&key=' + encodeURIComponent(CFG.KEY) : ''; function sid() { try { let id = sessionStorage.getItem('simon_session_id'); if (!id) { id = 's_' + Math.random().toString(36).slice(2) + Date.now().toString(36); sessionStorage.setItem('simon_session_id', id); } return id; } catch (_) { return 's_' + Date.now().toString(36); } } function payload(type, extra = {}) { return { type, title: extra.title || document.title, url: location.href, path: location.pathname, referrer: document.referrer || '', at: new Date().toISOString(), source: CFG.SOURCE || 'simon-live-feed.php', session: sid(), screen: window.innerWidth + 'x' + window.innerHeight, viewport: document.documentElement.clientWidth + 'x' + document.documentElement.clientHeight, dpr: window.devicePixelRatio || 1, tz: (Intl.DateTimeFormat().resolvedOptions().timeZone || 'Local'), lang: navigator.language || '', ua: navigator.userAgent || '', ...extra }; } function post(stream, data) { try { fetch(base + '?stream=' + encodeURIComponent(stream) + key, { method: 'POST', headers: {'Content-Type':'application/json'}, keepalive: true, body: JSON.stringify(data) }).catch(() => {}); } catch (_) {} } function ga(eventName, params = {}) { try { if (typeof gtag === 'function') { gtag('event', eventName, params); } } catch (_) {} } function track(type, extra = {}, stream = visitStream) { const data = payload(type, extra); post(stream, data); ga('simon_' + type, { event_category: 'SIMON', event_label: extra.label || extra.title || type, page_path: location.pathname }); } window.SIMON_TRACK = track; track('page_view', {label:'SIMON Live Intelligence Feed'}); let maxScroll = 0; let scrollSent = {}; function handleScroll() { const doc = document.documentElement; const total = Math.max(1, doc.scrollHeight - window.innerHeight); maxScroll = Math.max(maxScroll, Math.round((window.scrollY / total) * 100)); [25,50,75,90,100].forEach(mark => { if (maxScroll >= mark && !scrollSent[mark]) { scrollSent[mark] = true; track('scroll_depth', {label: mark + '%', percent: mark}); } }); } window.addEventListener('scroll', handleScroll, {passive:true}); document.addEventListener('click', (event) => { const target = event.target && event.target.closest ? event.target.closest('a,button,[data-track]') : null; if (!target) return; const isLink = target.tagName === 'A'; const href = isLink ? (target.getAttribute('href') || '') : ''; const text = (target.innerText || target.getAttribute('aria-label') || target.title || target.id || '').trim().slice(0,120); const outbound = isLink && href && /^https?:\/\//i.test(href) && !href.includes(location.hostname); const download = isLink && /\.(pdf|zip|docx?|xlsx?|pptx?|epub|mp3|mp4|mov|jpg|jpeg|png|webp)$/i.test(href.split('?')[0]); track(download ? 'download_click' : outbound ? 'outbound_click' : 'ui_click', { label: text || href || target.id || 'click', href, outbound, download }, pulseStream); }, true); document.addEventListener('submit', (event) => { const q = event.target && event.target.querySelector ? event.target.querySelector('input[type="search"],input[name="q"],#q') : null; if (!q) return; track('search_submit', {label: q.value || '', query: q.value || ''}, pulseStream); }, true); window.addEventListener('error', (event) => { track('client_error', { label: event.message || 'script error', message: event.message || '', filename: event.filename || '', lineno: event.lineno || 0, colno: event.colno || 0 }, pulseStream); }); window.addEventListener('unhandledrejection', (event) => { track('promise_rejection', { label: 'unhandled promise rejection', message: String(event.reason && (event.reason.message || event.reason) || '') }, pulseStream); }); window.addEventListener('load', () => { setTimeout(() => { const nav = performance.getEntriesByType && performance.getEntriesByType('navigation')[0]; if (nav) { track('performance', { label: 'navigation', load_ms: Math.round(nav.loadEventEnd), dom_ms: Math.round(nav.domContentLoadedEventEnd), transfer_size: nav.transferSize || 0 }, pulseStream); } }, 800); }); window.addEventListener('beforeunload', () => { track('page_exit', { label: 'exit', seconds: Math.round(performance.now() / 1000), max_scroll: maxScroll }); }); })(); </script> </body> </html>
Save file
Quick jump
open a path
Open