Files

732 lines
32 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Myron's Dashboard</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--surface: #1a1d27;
--surface2: #22263a;
--border: #2e3350;
--accent: #4f8ef7;
--accent2: #7c5af7;
--green: #22c55e;
--yellow: #eab308;
--red: #ef4444;
--orange: #f97316;
--pink: #ec4899;
--cyan: #06b6d4;
--text: #e2e4f0;
--muted: #8b90a8;
--radius: 12px;
}
body {
background: var(--bg);
color: var(--text);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
min-height: 100vh;
padding: 2rem 1.5rem;
}
header {
text-align: center;
margin-bottom: 2.5rem;
}
header h1 {
font-size: 2rem;
font-weight: 700;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.5px;
}
header p {
color: var(--muted);
font-size: 0.85rem;
margin-top: 0.35rem;
letter-spacing: 0.5px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.25rem;
max-width: 1400px;
margin: 0 auto;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
transition: border-color 0.2s, transform 0.2s;
}
.card:hover { border-color: var(--accent); transform: translateY(-2px); }
.card-header {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.85rem 1rem;
background: var(--surface2);
border-bottom: 1px solid var(--border);
}
.card-header .icon {
font-size: 1.1rem;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
flex-shrink: 0;
}
.card-header h2 {
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--muted);
}
.links { padding: 0.5rem 0; }
.link-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 1rem;
text-decoration: none;
color: var(--text);
font-size: 0.9rem;
transition: background 0.15s;
border-radius: 0;
}
.link-item:hover { background: var(--surface2); }
.link-item .dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.link-item .name { flex: 1; font-weight: 500; }
.link-item .sub {
font-size: 0.75rem;
color: var(--muted);
}
.link-item .arrow {
color: var(--muted);
font-size: 0.7rem;
opacity: 0;
transition: opacity 0.15s;
}
.link-item:hover .arrow { opacity: 1; }
.dot-blue { background: var(--accent); }
.dot-purple { background: var(--accent2); }
.dot-green { background: var(--green); }
.dot-yellow { background: var(--yellow); }
.dot-red { background: var(--red); }
.dot-orange { background: var(--orange); }
.dot-pink { background: var(--pink); }
.dot-cyan { background: var(--cyan); }
.dot-gray { background: var(--muted); }
.icon-blue { background: rgba(79,142,247,0.15); }
.icon-purple { background: rgba(124,90,247,0.15); }
.icon-green { background: rgba(34,197,94,0.15); }
.icon-yellow { background: rgba(234,179,8,0.15); }
.icon-orange { background: rgba(249,115,22,0.15); }
.icon-pink { background: rgba(236,72,153,0.15); }
.icon-cyan { background: rgba(6,182,212,0.15); }
.icon-red { background: rgba(239,68,68,0.15); }
.divider {
height: 1px;
background: var(--border);
margin: 0.25rem 1rem;
}
@media (max-width: 600px) {
body { padding: 1rem; }
.grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header>
<h1>⚡ Blair HQ</h1>
<p id="clock"></p>
</header>
<div class="grid">
<!-- INFRASTRUCTURE & JARVIS -->
<div class="card">
<div class="card-header">
<div class="icon icon-blue">🖥️</div>
<h2>Infrastructure</h2>
</div>
<div class="links">
<a class="link-item" href="https://jarvis.orbishosting.com/login.php" target="_blank">
<span class="dot dot-blue"></span><span class="name">JARVIS Dashboard</span><span class="sub">Main UI</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://jarvis.orbishosting.com/admin" target="_blank">
<span class="dot dot-purple"></span><span class="name">JARVIS Admin</span><span class="sub">Admin panel</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://10.48.200.90:8006" target="_blank">
<span class="dot dot-blue"></span><span class="name">Proxmox PVE1</span><span class="sub">Primary hypervisor</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://10.48.200.91:8006" target="_blank">
<span class="dot dot-blue"></span><span class="name">Proxmox PVE2</span><span class="sub">Secondary hypervisor</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.200:81" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Nginx Proxy Manager</span><span class="sub">10.48.200.200</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="http://10.48.200.210:11434" target="_blank">
<span class="dot dot-purple"></span><span class="name">Ollama</span><span class="sub">Local LLM</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.249:5000" target="_blank">
<span class="dot dot-gray"></span><span class="name">Synology NAS</span><span class="sub">10.48.200.249</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://gitea.orbishosting.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Gitea</span><span class="sub">Local Git server</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.211/phpmyadmin" target="_blank">
<span class="dot dot-orange"></span><span class="name">phpMyAdmin</span><span class="sub">JARVIS DB</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- DOWNLOADS -->
<div class="card">
<div class="card-header">
<div class="icon icon-green">📄</div>
<h2>Downloads</h2>
</div>
<div class="links">
<a class="link-item" href="/downloads/INFRASTRUCTURE-REFERENCE.md" download="INFRASTRUCTURE-REFERENCE.md">
<span class="dot dot-green"></span><span class="name">Infrastructure Reference</span><span class="sub">Complete system map &amp; credentials</span><span class="arrow"></span>
</a>
<a class="link-item" href="/downloads/msp360-linux-installer.zip" download="msp360-linux-installer.zip">
<span class="dot dot-cyan"></span><span class="name">MSP360 Linux Installer</span><span class="sub">Backup setup scripts + Gitea config fetch</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- HOME AUTOMATION -->
<div class="card">
<div class="card-header">
<div class="icon icon-green">🏠</div>
<h2>Home Automation</h2>
</div>
<div class="links">
<a class="link-item" href="https://hoa.orbishosting.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Home Assistant</span><span class="sub">hoa.orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.18:8581" target="_blank">
<span class="dot dot-orange"></span><span class="name">HomeBridge</span><span class="sub">10.48.200.18</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.97:8123" target="_blank">
<span class="dot dot-cyan"></span><span class="name">HA (Direct LAN)</span><span class="sub">10.48.200.97</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- MEDIA -->
<div class="card">
<div class="card-header">
<div class="icon icon-purple">🎬</div>
<h2>Media</h2>
</div>
<div class="links">
<a class="link-item" href="http://10.48.200.33:8096" target="_blank">
<span class="dot dot-purple"></span><span class="name">Jellyfin</span><span class="sub">Media server</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:8989" target="_blank">
<span class="dot dot-blue"></span><span class="name">Sonarr</span><span class="sub">TV shows</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:7878" target="_blank">
<span class="dot dot-yellow"></span><span class="name">Radarr</span><span class="sub">Movies</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:9696" target="_blank">
<span class="dot dot-orange"></span><span class="name">Prowlarr</span><span class="sub">Indexers</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:8080" target="_blank">
<span class="dot dot-green"></span><span class="name">qBittorrent</span><span class="sub">Downloads</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- HOSTING PANEL -->
<div class="card">
<div class="card-header">
<div class="icon icon-cyan">⚙️</div>
<h2>NovaCPX Panel</h2>
</div>
<div class="links">
<a class="link-item" href="https://admin.novacpx.orbishosting.com" target="_blank">
<span class="dot dot-red"></span><span class="name">Admin Panel</span><span class="sub">Root access</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://reseller.novacpx.orbishosting.com" target="_blank">
<span class="dot dot-orange"></span><span class="name">Reseller Panel</span><span class="sub">Reseller access</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://panel.novacpx.orbishosting.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Client Panel</span><span class="sub">End user access</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://novacpx.orbishosting.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">Client Panel (alt)</span><span class="sub">novacpx.orbishosting.com</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- WEBSITES -->
<div class="card">
<div class="card-header">
<div class="icon icon-pink">🌐</div>
<h2>Websites</h2>
</div>
<div class="links">
<a class="link-item" href="https://orbishosting.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">Orbis Hosting</span><span class="sub">orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://orbis.orbishosting.com" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Orbis Portal</span><span class="sub">orbis.orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://tomsjavajive.com" target="_blank">
<span class="dot dot-yellow"></span><span class="name">Tom's Java Jive</span><span class="sub">tomsjavajive.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://tomtomgames.com" target="_blank">
<span class="dot dot-purple"></span><span class="name">Tom Tom Games</span><span class="sub">tomtomgames.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://epictravelexpeditions.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Epic Travel</span><span class="sub">epictravelexpeditions.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://parkerslingshotrentals.com" target="_blank">
<span class="dot dot-red"></span><span class="name">Parker Slingshot Rentals</span><span class="sub">parkerslingshotrentals.com</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- COMMUNICATIONS -->
<div class="card">
<div class="card-header">
<div class="icon icon-orange">📞</div>
<h2>Communications</h2>
</div>
<div class="links">
<a class="link-item" href="https://fusion.orbishosting.com" target="_blank">
<span class="dot dot-orange"></span><span class="name">FusionPBX</span><span class="sub">fusion.orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://fusion.orbishosting.com/app/settings" target="_blank">
<span class="dot dot-yellow"></span><span class="name">PBX Settings</span><span class="sub">Extensions &amp; trunks</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- NETWORKING -->
<div class="card">
<div class="card-header">
<div class="icon icon-yellow">🔒</div>
<h2>Networking</h2>
</div>
<div class="links">
<a class="link-item" href="https://10.48.200.1:9443" target="_blank">
<span class="dot dot-yellow"></span><span class="name">FortiGate</span><span class="sub">10.48.200.1:9443</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.80" target="_blank">
<span class="dot dot-cyan"></span><span class="name">WB610H Bridge</span><span class="sub">10.48.200.80</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="http://10.48.200.250" target="_blank">
<span class="dot dot-green"></span><span class="name">Goalake Switch 1</span><span class="sub">10.48.200.250</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.251" target="_blank">
<span class="dot dot-green"></span><span class="name">Goalake Switch 2</span><span class="sub">10.48.200.251</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.67" target="_blank">
<span class="dot dot-green"></span><span class="name">WireGuard CT</span><span class="sub">10.48.200.67</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://login.tailscale.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">Tailscale</span><span class="sub">login.tailscale.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://dash.cloudflare.com" target="_blank">
<span class="dot dot-orange"></span><span class="name">Cloudflare</span><span class="sub">DNS &amp; CDN</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- GITHUB -->
<div class="card">
<div class="card-header">
<div class="icon icon-purple">🐙</div>
<h2>GitHub Repos</h2>
</div>
<div class="links">
<a class="link-item" href="https://github.com/myronblair/jarvis" target="_blank">
<span class="dot dot-blue"></span><span class="name">JARVIS</span><span class="sub">myronblair/jarvis</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/novacpx" target="_blank">
<span class="dot dot-cyan"></span><span class="name">NovaCPX</span><span class="sub">myronblair/novacpx</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/web-dashboard" target="_blank">
<span class="dot dot-purple"></span><span class="name">Web Dashboard</span><span class="sub">myronblair/web-dashboard</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://github.com/myronblair/tomsjavajive" target="_blank">
<span class="dot dot-orange"></span><span class="name">Tom's Java Jive</span><span class="sub">myronblair/tomsjavajive</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/tomtomgames" target="_blank">
<span class="dot dot-green"></span><span class="name">TomTom Games</span><span class="sub">myronblair/tomtomgames</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/epictravelexpeditions" target="_blank">
<span class="dot dot-blue"></span><span class="name">Epic Travel Expeditions</span><span class="sub">myronblair/epictravelexpeditions</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/parkerslingshot" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Parker Slingshot</span><span class="sub">myronblair/parkerslingshot</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/parkerslingshotrentals" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Parker Slingshot Rentals</span><span class="sub">myronblair/parkerslingshotrentals</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/orbishosting" target="_blank">
<span class="dot dot-purple"></span><span class="name">Orbis Hosting</span><span class="sub">myronblair/orbishosting</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/orbis-hosting-portal" target="_blank">
<span class="dot dot-purple"></span><span class="name">Orbis Hosting Portal</span><span class="sub">myronblair/orbis-hosting-portal</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://github.com/myronblair/infra" target="_blank">
<span class="dot dot-gray"></span><span class="name">Infra</span><span class="sub">myronblair/infra</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/mediastack" target="_blank">
<span class="dot dot-gray"></span><span class="name">MediaStack</span><span class="sub">myronblair/mediastack</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/do-server-config" target="_blank">
<span class="dot dot-gray"></span><span class="name">DO Server Config</span><span class="sub">myronblair/do-server-config</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/proxmox-config" target="_blank">
<span class="dot dot-gray"></span><span class="name">Proxmox Config</span><span class="sub">myronblair/proxmox-config</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/fusionpbx-config" target="_blank">
<span class="dot dot-gray"></span><span class="name">FusionPBX Config</span><span class="sub">myronblair/fusionpbx-config</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://github.com/myronblair" target="_blank">
<span class="dot dot-purple"></span><span class="name">All Repos</span><span class="sub">github.com/myronblair</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- DO SERVER -->
<div class="card">
<div class="card-header">
<div class="icon icon-blue">🌊</div>
<h2>DigitalOcean</h2>
</div>
<div class="links">
<a class="link-item" href="https://cloud.digitalocean.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">DO Dashboard</span><span class="sub">cloud.digitalocean.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://165.22.1.228:8090" target="_blank">
<span class="dot dot-cyan"></span><span class="name">CyberPanel</span><span class="sub">165.22.1.228:8090</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- VANGUARD TECHNOLOGY -->
<div class="card">
<div class="card-header">
<div class="icon icon-cyan">🛡️</div>
<h2>Vanguard Technology</h2>
</div>
<div class="links">
<a class="link-item" href="https://portal.office.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">365 Portal</span><span class="sub">portal.office.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://docs.vanguardtc.com" target="_blank">
<span class="dot dot-purple"></span><span class="name">Docs Dashboard</span><span class="sub">docs.vanguardtc.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://vanguardtc.com/downloads/" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Vanguard Downloads</span><span class="sub">vanguardtc.com/downloads</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://vanguardtc.shield.syncromsp.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Syncro RMM</span><span class="sub">vanguardtc.shield.syncromsp.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://connect.vanguardtc.com/Host#Support/All Sessions//cde1ee04-d49b-47b2-a687-36cb6964761f/Start" target="_blank">
<span class="dot dot-green"></span><span class="name">Connect Remote</span><span class="sub">Host — Support sessions</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://connect.vanguardtc.com" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Connect Join</span><span class="sub">connect.vanguardtc.com</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://msp.eset.com/ema/dashboard" target="_blank">
<span class="dot dot-blue"></span><span class="name">ESET Admin</span><span class="sub">msp.eset.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://app.scalepad.com/account/home" target="_blank">
<span class="dot dot-purple"></span><span class="name">ScalePad</span><span class="sub">app.scalepad.com</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://my.hivelocity.net/auth/login" target="_blank">
<span class="dot dot-orange"></span><span class="name">Hivelocity</span><span class="sub">my.hivelocity.net</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://usa.ingrammicro.com/cep/app/my/dashboard" target="_blank">
<span class="dot dot-gray"></span><span class="name">Ingram Micro</span><span class="sub">usa.ingrammicro.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://partnerfirst.us.tdsynnex.com/login-portal/ec" target="_blank">
<span class="dot dot-gray"></span><span class="name">TD SYNNEX</span><span class="sub">partnerfirst.us.tdsynnex.com</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="https://manage.flowroute.com/accounts/profile/select" target="_blank">
<span class="dot dot-purple"></span><span class="name">FlowRoute</span><span class="sub">manage.flowroute.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://docs.fusionpbx.com/en/latest/#" target="_blank">
<span class="dot dot-orange"></span><span class="name">FusionPBX Docs</span><span class="sub">docs.fusionpbx.com</span><span class="arrow"></span>
</a>
</div>
</div>
</div>
<script>
function tick() {
const now = new Date();
document.getElementById('clock').textContent =
now.toLocaleDateString('en-US', {weekday:'long',year:'numeric',month:'long',day:'numeric'}) +
' · ' +
now.toLocaleTimeString('en-US', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
}
tick(); setInterval(tick, 1000);
// ── Notes — server-backed so they sync across all devices ─────────────
const API = '/notes.php';
let _notes = [];
function escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');
}
async function notesApi(action, body = {}) {
try {
const r = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, ...body }),
credentials: 'same-origin',
});
return await r.json();
} catch (e) { return { ok: false }; }
}
async function fetchNotes() {
const el = document.getElementById('notes-list');
if (el && !_notes.length) el.innerHTML = '<div style="color:#8b90a8;font-size:.85rem">Loading…</div>';
try {
const r = await fetch(API + '?action=list', { credentials: 'same-origin' });
const d = await r.json();
_notes = d.notes || [];
} catch (e) { _notes = []; }
renderNotes();
}
function renderNotes() {
const list = document.getElementById('notes-list');
if (!list) return;
if (!_notes.length) {
list.innerHTML = '<div style="color:#8b90a8;font-size:.85rem;padding:.5rem 0">No notes yet. Add one above.</div>';
return;
}
list.innerHTML = _notes.map(n => `
<div class="note-item ${n.done ? 'note-done' : ''}">
<button class="note-check" onclick="toggleNote('${n.id}')" title="${n.done ? 'Mark incomplete' : 'Mark done'}">
${n.done ? '✅' : '<span class="note-circle"></span>'}
</button>
<div class="note-body">
<div class="note-text">${escHtml(n.text)}</div>
${n.detail ? `<div class="note-detail">${escHtml(n.detail)}</div>` : ''}
<div class="note-meta">${new Date(n.ts).toLocaleString('en-US',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'})}</div>
</div>
<button class="note-del" onclick="deleteNote('${n.id}')" title="Delete">✕</button>
</div>`).join('');
}
async function addNote() {
const txt = document.getElementById('note-input').value.trim();
const det = document.getElementById('note-detail').value.trim();
if (!txt) { document.getElementById('note-input').focus(); return; }
const btn = document.querySelector('.btn-note-add');
btn.disabled = true;
await notesApi('add', { text: txt, detail: det });
btn.disabled = false;
document.getElementById('note-input').value = '';
document.getElementById('note-detail').value = '';
document.getElementById('note-detail').style.display = 'none';
document.getElementById('note-detail-toggle').textContent = '+ Add details';
await fetchNotes();
}
async function toggleNote(id) {
_notes = _notes.map(n => n.id === id ? {...n, done: !n.done} : n);
renderNotes();
notesApi('toggle', { id }).then(fetchNotes);
}
async function deleteNote(id) {
_notes = _notes.filter(n => n.id !== id);
renderNotes();
notesApi('delete', { id }).then(fetchNotes);
}
async function clearDone() {
_notes = _notes.filter(n => !n.done);
renderNotes();
notesApi('clear-done').then(fetchNotes);
}
function toggleDetail() {
const d = document.getElementById('note-detail');
const btn = document.getElementById('note-detail-toggle');
const show = d.style.display === 'none';
d.style.display = show ? 'block' : 'none';
btn.textContent = show ? ' Hide details' : '+ Add details';
if (show) d.focus();
}
document.addEventListener('DOMContentLoaded', () => {
fetchNotes();
// Re-sync when tab becomes visible (switching back from another device)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') fetchNotes();
});
document.getElementById('note-input').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); addNote(); }
});
});
</script>
<!-- ── NOTES SECTION ─────────────────────────────────────────────────────── -->
<style>
.notes-wrap {
max-width:1400px; margin:2rem auto 0; padding:0 1.5rem 3rem;
}
.notes-header {
display:flex; align-items:center; justify-content:space-between;
margin-bottom:1.25rem;
}
.notes-title {
font-size:.75rem; font-weight:700; text-transform:uppercase;
letter-spacing:1.5px; color:#8b90a8;
}
.notes-add {
background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius); padding:1rem; margin-bottom:1rem;
}
.notes-add-row {
display:flex; gap:.6rem; align-items:center;
}
#note-input {
flex:1; background:var(--surface2); border:1px solid var(--border);
border-radius:6px; color:var(--text); padding:.6rem .85rem;
font-size:.9rem; font-family:inherit; outline:none;
transition:border-color .15s;
}
#note-input:focus { border-color:var(--accent); }
#note-detail {
width:100%; margin-top:.6rem; background:var(--surface2);
border:1px solid var(--border); border-radius:6px; color:var(--text);
padding:.6rem .85rem; font-size:.85rem; font-family:inherit;
outline:none; resize:vertical; min-height:70px;
transition:border-color .15s; display:none;
}
#note-detail:focus { border-color:var(--accent); }
.btn-note-add {
background:var(--accent); color:#fff; border:none; border-radius:6px;
padding:.6rem 1.1rem; font-size:.85rem; cursor:pointer; font-weight:600;
white-space:nowrap; transition:opacity .15s;
}
.btn-note-add:hover { opacity:.85; }
#note-detail-toggle {
background:none; border:none; color:#8b90a8; font-size:.78rem;
cursor:pointer; padding:.25rem 0; margin-top:.4rem;
transition:color .15s;
}
#note-detail-toggle:hover { color:var(--text); }
.note-item {
display:flex; align-items:flex-start; gap:.75rem;
padding:.8rem .75rem; border-radius:8px;
background:var(--surface); border:1px solid var(--border);
margin-bottom:.5rem; transition:opacity .2s, border-color .2s;
}
.note-item:hover { border-color:var(--accent); }
.note-done { opacity:.55; }
.note-check {
background:none; border:none; cursor:pointer; padding:.1rem;
font-size:1.1rem; flex-shrink:0; margin-top:.1rem;
}
.note-circle {
display:inline-block; width:18px; height:18px; border-radius:50%;
border:2px solid #8b90a8; vertical-align:middle;
transition:border-color .15s;
}
.note-item:hover .note-circle { border-color:var(--accent); }
.note-body { flex:1; min-width:0; }
.note-text {
font-size:.9rem; color:var(--text); line-height:1.45;
word-break:break-word;
}
.note-done .note-text { text-decoration:line-through; color:#8b90a8; }
.note-detail {
font-size:.8rem; color:#8b90a8; margin-top:.3rem; line-height:1.5;
}
.note-meta {
font-size:.72rem; color:#5a5f78; margin-top:.35rem;
}
.note-del {
background:none; border:none; color:#5a5f78; cursor:pointer;
font-size:.85rem; padding:.1rem .2rem; flex-shrink:0;
opacity:0; transition:opacity .15s, color .15s;
}
.note-item:hover .note-del { opacity:1; }
.note-del:hover { color:#ef4444; }
.btn-clear {
background:none; border:1px solid var(--border); color:#8b90a8;
border-radius:5px; padding:.3rem .7rem; font-size:.75rem;
cursor:pointer; transition:all .15s;
}
.btn-clear:hover { border-color:#ef4444; color:#ef4444; }
@media(max-width:600px){
.notes-wrap { padding:0 1rem 2rem; }
.notes-add-row { flex-direction:column; align-items:stretch; }
}
</style>
<div class="notes-wrap">
<div class="notes-header">
<div class="notes-title">📝 Notes</div>
<button class="btn-clear" onclick="clearDone()">Clear completed</button>
</div>
<div class="notes-add">
<div class="notes-add-row">
<input id="note-input" type="text" placeholder="Add a note — press Enter to save…">
<button class="btn-note-add" onclick="addNote()">Add</button>
</div>
<button id="note-detail-toggle" onclick="toggleDetail()">+ Add details</button>
<textarea id="note-detail" placeholder="Additional details…"></textarea>
</div>
<div id="notes-list"></div>
</div>
</body>
</html>