mirror of
https://github.com/MeshCore-Beacon/beacon-web.git
synced 2026-09-03 04:03:51 +00:00
150 lines
18 KiB
HTML
150 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>BEACON — Command Red Palette</title>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap');
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
:root {
|
||
--bg-base: #0C0A10;
|
||
--bg-surface: #14111A;
|
||
--bg-raised: #1E1A26;
|
||
--border: #2A2434;
|
||
--border-subtle: #1E1828;
|
||
--primary: #E85D75;
|
||
--primary-dim: #8B2E42;
|
||
--primary-bg: rgba(232, 93, 117, 0.06);
|
||
--secondary: #8B9FE8;
|
||
--secondary-dim: #4A5A9E;
|
||
--secondary-bg: rgba(139, 159, 232, 0.06);
|
||
--green: #5AD8A6;
|
||
--green-bg: rgba(90, 216, 166, 0.08);
|
||
--danger: #FF6B6B;
|
||
--danger-bg: rgba(255, 107, 107, 0.08);
|
||
--warn: #F0C674;
|
||
--warn-bg: rgba(240, 198, 116, 0.07);
|
||
--text-bright: #F0ECF4;
|
||
--text-normal: #B4AACC;
|
||
--text-muted: #6E6488;
|
||
--text-dim: #443C58;
|
||
--font-mono: 'JetBrains Mono', monospace;
|
||
--font-sans: 'Inter', system-ui, sans-serif;
|
||
}
|
||
body { background: var(--bg-base); color: var(--text-normal); font-family: var(--font-sans); font-size: 13px; line-height: 1.5; min-height: 100vh; }
|
||
.app-header { display: flex; align-items: center; justify-content: space-between; padding: 0 16px; height: 42px; background: var(--bg-surface); border-bottom: 1px solid var(--border); }
|
||
.app-title { font-family: var(--font-mono); font-weight: 700; font-size: 14px; color: var(--primary); letter-spacing: 1px; text-transform: uppercase; }
|
||
.app-title span { color: var(--text-dim); font-weight: 400; letter-spacing: 0; text-transform: none; }
|
||
.header-right { display: flex; align-items: center; gap: 12px; }
|
||
.ws-badge { display: flex; align-items: center; gap: 5px; font-family: var(--font-mono); font-size: 10px; color: var(--green); background: var(--green-bg); border: 1px solid rgba(90, 216, 166, 0.15); padding: 2px 8px; border-radius: 3px; }
|
||
.ws-badge .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; }
|
||
.scope-selector { display: flex; align-items: center; gap: 6px; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 4px; padding: 4px 12px; color: var(--text-bright); font-family: var(--font-mono); font-size: 12px; font-weight: 600; }
|
||
.scope-selector .label { color: var(--text-muted); font-weight: 400; font-size: 10px; margin-right: 2px; }
|
||
.scope-selector .arrow { color: var(--text-dim); font-size: 10px; }
|
||
.tab-bar { display: flex; background: var(--bg-surface); border-bottom: 1px solid var(--border); padding: 0 16px; }
|
||
.tab { padding: 9px 18px; font-size: 12px; font-weight: 500; color: var(--text-muted); border-bottom: 2px solid transparent; cursor: pointer; letter-spacing: 0.3px; transition: color 0.15s; }
|
||
.tab.active { color: var(--primary); border-bottom-color: var(--primary); }
|
||
.tab:hover:not(.active) { color: var(--text-normal); }
|
||
.filter-bar { display: flex; align-items: center; gap: 6px; padding: 7px 16px; border-bottom: 1px solid var(--border-subtle); background: var(--bg-base); }
|
||
.filter-pill { font-size: 11px; padding: 3px 10px; border-radius: 3px; border: 1px solid var(--border); color: var(--text-muted); background: var(--bg-surface); font-family: var(--font-mono); cursor: pointer; transition: all 0.15s; }
|
||
.filter-pill:hover { border-color: var(--text-dim); color: var(--text-normal); }
|
||
.filter-pill.active { border-color: var(--primary-dim); background: var(--primary-bg); color: var(--primary); }
|
||
.filter-sep { color: var(--border); font-size: 14px; margin: 0 2px; }
|
||
.catchup-banner { margin: 0 16px; padding: 6px 12px; background: var(--primary-bg); border: 1px solid rgba(232, 93, 117, 0.12); border-top: none; color: var(--primary); font-size: 12px; font-weight: 500; text-align: center; cursor: pointer; font-family: var(--font-mono); border-radius: 0 0 4px 4px; }
|
||
.packet-list { padding: 4px 16px 40px; }
|
||
.packet-row { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 4px; margin-top: 2px; padding: 8px 12px; cursor: pointer; transition: all 0.15s; }
|
||
.packet-row:hover { border-color: #382E44; background: #18141E; }
|
||
.packet-row.expanded { border-color: var(--primary); background: #16121C; }
|
||
.packet-row.new-pulse { border-color: var(--primary); box-shadow: 0 0 12px rgba(232, 93, 117, 0.08); }
|
||
.row-main { display: flex; align-items: center; gap: 10px; }
|
||
.row-expand { color: var(--text-dim); font-size: 11px; width: 14px; font-family: var(--font-mono); }
|
||
.row-hash { font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: var(--primary); letter-spacing: 0.5px; }
|
||
.row-type { font-family: var(--font-mono); font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: 2px; letter-spacing: 0.5px; text-transform: uppercase; }
|
||
.type-advert { background: var(--green-bg); color: var(--green); border: 1px solid rgba(90, 216, 166, 0.12); }
|
||
.type-text { background: var(--secondary-bg); color: var(--secondary); border: 1px solid rgba(139, 159, 232, 0.12); }
|
||
.type-trace { background: var(--warn-bg); color: var(--warn); border: 1px solid rgba(240, 198, 116, 0.12); }
|
||
.type-ack { background: rgba(110, 100, 136, 0.1); color: var(--text-muted); border: 1px solid rgba(110, 100, 136, 0.12); }
|
||
.type-group { background: rgba(200, 140, 255, 0.06); color: #C88CFF; border: 1px solid rgba(200, 140, 255, 0.12); }
|
||
.type-req { background: var(--primary-bg); color: var(--primary); border: 1px solid rgba(232, 93, 117, 0.12); }
|
||
.row-summary { flex: 1; color: var(--text-bright); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.row-summary .quoted { color: var(--text-muted); font-style: italic; }
|
||
.row-count { font-family: var(--font-mono); font-size: 11px; color: var(--green); font-weight: 600; background: var(--green-bg); padding: 1px 6px; border-radius: 2px; }
|
||
.row-meta { display: flex; align-items: center; gap: 8px; margin-top: 4px; padding-left: 24px; font-size: 11px; color: var(--text-dim); }
|
||
.row-meta .dot { font-size: 4px; color: var(--border); }
|
||
.row-meta .route { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; background: rgba(110, 100, 136, 0.08); padding: 1px 5px; border-radius: 2px; }
|
||
.row-meta .observer { color: var(--text-normal); }
|
||
.row-meta .iata { font-family: var(--font-mono); font-weight: 700; color: var(--primary); font-size: 10px; }
|
||
.observations { margin-top: 8px; padding-left: 24px; display: flex; flex-direction: column; gap: 3px; }
|
||
.obs-card { background: var(--bg-base); border: 1px solid var(--border); border-radius: 4px; padding: 9px 12px; border-left: 2px solid var(--primary-dim); }
|
||
.obs-header { display: flex; align-items: center; gap: 8px; font-size: 11px; margin-bottom: 6px; }
|
||
.obs-header .obs-name { color: var(--text-bright); font-weight: 600; }
|
||
.obs-header .obs-iata { font-family: var(--font-mono); color: var(--primary); font-weight: 600; font-size: 10px; background: var(--primary-bg); padding: 1px 5px; border-radius: 2px; }
|
||
.obs-header .obs-time { color: var(--text-dim); margin-left: auto; font-family: var(--font-mono); font-size: 10px; }
|
||
.obs-stats { display: flex; gap: 18px; font-family: var(--font-mono); font-size: 11px; }
|
||
.obs-stats .stat { display: flex; flex-direction: column; }
|
||
.obs-stats .label { color: var(--text-dim); font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||
.obs-stats .value { color: var(--text-normal); font-weight: 500; }
|
||
.obs-stats .value.good { color: var(--green); }
|
||
.obs-stats .value.mid { color: var(--warn); }
|
||
.obs-stats .value.bad { color: var(--danger); }
|
||
.obs-path { display: flex; align-items: center; gap: 4px; margin-top: 7px; font-family: var(--font-mono); font-size: 10px; padding-top: 6px; border-top: 1px solid var(--border-subtle); }
|
||
.obs-path .path-label { color: var(--text-dim); text-transform: uppercase; font-size: 9px; letter-spacing: 0.5px; margin-right: 4px; }
|
||
.hop { padding: 2px 6px; border-radius: 2px; font-weight: 500; }
|
||
.hop.resolved { background: var(--green-bg); color: var(--green); border: 1px solid rgba(90, 216, 166, 0.12); }
|
||
.hop.ambiguous { background: var(--warn-bg); color: var(--warn); border: 1px solid rgba(240, 198, 116, 0.12); }
|
||
.obs-path .arrow { color: var(--text-dim); font-size: 12px; }
|
||
.obs-actions { display: flex; gap: 14px; margin-top: 7px; font-size: 10px; }
|
||
.obs-actions a { color: var(--secondary); text-decoration: none; font-weight: 500; }
|
||
.status-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: space-between; padding: 5px 16px; background: var(--bg-surface); border-top: 1px solid var(--border); font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); }
|
||
.status-left { display: flex; align-items: center; gap: 16px; }
|
||
.status-item .val { color: var(--text-muted); }
|
||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
||
.palette-label { text-align: center; padding: 24px 16px 8px; font-size: 18px; font-weight: 700; color: var(--primary); font-family: var(--font-mono); letter-spacing: 1px; }
|
||
.palette-label span { display: block; font-size: 12px; color: var(--text-muted); font-weight: 400; margin-top: 4px; letter-spacing: 0; }
|
||
.palette-label .sub { font-size: 11px; color: var(--text-dim); margin-top: 8px; max-width: 600px; margin-left: auto; margin-right: auto; line-height: 1.6; }
|
||
.swatches { display: flex; gap: 8px; padding: 12px 16px 16px; flex-wrap: wrap; justify-content: center; }
|
||
.swatch { display: flex; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); }
|
||
.swatch .dot { width: 12px; height: 12px; border-radius: 2px; border: 1px solid rgba(255,255,255,0.1); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="palette-label">
|
||
OPTION J — COMMAND RED
|
||
<span>Military command center aesthetic · muted crimson on deep purple-black · slate-blue secondary</span>
|
||
<div class="sub">Red reads as "monitoring active systems" without being alarming at this saturation. The purple-black base and lavender text give it a distinct look that no other mesh tool has. Blue secondary balances the warmth.</div>
|
||
</div>
|
||
<div class="swatches">
|
||
<div class="swatch"><div class="dot" style="background:#E85D75"></div>Primary</div>
|
||
<div class="swatch"><div class="dot" style="background:#8B9FE8"></div>Secondary</div>
|
||
<div class="swatch"><div class="dot" style="background:#5AD8A6"></div>Good</div>
|
||
<div class="swatch"><div class="dot" style="background:#FF6B6B"></div>Danger</div>
|
||
<div class="swatch"><div class="dot" style="background:#0C0A10"></div>Base</div>
|
||
<div class="swatch"><div class="dot" style="background:#14111A"></div>Surface</div>
|
||
<div class="swatch"><div class="dot" style="background:#F0ECF4"></div>Bright</div>
|
||
<div class="swatch"><div class="dot" style="background:#6E6488"></div>Muted</div>
|
||
</div>
|
||
<div class="app-header"><div class="app-title">BEACON <span>v0.1</span></div><div class="header-right"><div class="ws-badge"><span class="dot"></span> LIVE</div><div class="scope-selector"><span class="label">SCOPE</span> YOW <span class="arrow">▾</span></div></div></div>
|
||
<div class="tab-bar"><div class="tab active">Packets</div><div class="tab">Nodes</div><div class="tab">Observers</div><div class="tab">Channels</div><div class="tab">Stats</div><div class="tab">Map</div></div>
|
||
<div class="filter-bar"><div class="filter-pill active">All types</div><div class="filter-pill">Advert</div><div class="filter-pill">Text</div><div class="filter-pill">Group</div><div class="filter-pill">Trace</div><div class="filter-pill">Ack</div><div class="filter-sep">│</div><div class="filter-pill active">All routes</div><div class="filter-pill">Flood</div><div class="filter-pill">Direct</div></div>
|
||
<div class="catchup-banner">▲ 47 new packets · click to scroll to top</div>
|
||
<div class="packet-list">
|
||
<div class="packet-row expanded">
|
||
<div class="row-main"><span class="row-expand">▾</span><span class="row-hash">9E9B7D6A</span><span class="row-type type-advert">Advert</span><span class="row-summary">Advert <span class="quoted">"WW7STR/PugetMesh Cougar"</span></span><span class="row-count">×15</span></div>
|
||
<div class="row-meta"><span class="route">Flood</span><span class="dot">·</span><span>02:37 PM</span><span class="dot">·</span><span class="observer">FlightlessDt</span><span class="dot">·</span><span class="iata">KEH</span></div>
|
||
<div class="observations">
|
||
<div class="obs-card"><div class="obs-header"><span class="obs-name">FlightlessDt</span><span class="obs-iata">KEH</span><span class="obs-time">02:37:36</span></div><div class="obs-stats"><div class="stat"><span class="label">SNR</span><span class="value good">10.75</span></div><div class="stat"><span class="label">RSSI</span><span class="value mid">-98</span></div><div class="stat"><span class="label">Prop</span><span class="value">1.936s</span></div><div class="stat"><span class="label">Hops</span><span class="value">1</span></div></div><div class="obs-path"><span class="path-label">Path</span><span class="hop resolved">YOW_Kanata</span><span class="arrow">→</span><span class="hop resolved">FlightlessDt</span></div><div class="obs-actions"><a href="#">Open analyzer</a><a href="#">Show on map</a></div></div>
|
||
<div class="obs-card"><div class="obs-header"><span class="obs-name">NodeRunner</span><span class="obs-iata">SEA</span><span class="obs-time">02:37:38</span></div><div class="obs-stats"><div class="stat"><span class="label">SNR</span><span class="value mid">7.20</span></div><div class="stat"><span class="label">RSSI</span><span class="value bad">-105</span></div><div class="stat"><span class="label">Prop</span><span class="value">4.122s</span></div><div class="stat"><span class="label">Hops</span><span class="value">2</span></div></div><div class="obs-path"><span class="path-label">Path</span><span class="hop resolved">YOW_Kanata</span><span class="arrow">→</span><span class="hop ambiguous">AE9B (2 candidates)</span><span class="arrow">→</span><span class="hop resolved">NodeRunner</span></div><div class="obs-actions"><a href="#">Open analyzer</a><a href="#">Show on map</a></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="packet-row"><div class="row-main"><span class="row-expand">▸</span><span class="row-hash">3FA1C20E</span><span class="row-type type-group">Group</span><span class="row-summary">Group text on <span class="quoted">#ottawa</span></span><span class="row-count">×8</span></div><div class="row-meta"><span class="route">Flood</span><span class="dot">·</span><span>02:36 PM</span><span class="dot">·</span><span class="observer">Hull_Hospital</span><span class="dot">·</span><span class="iata">YOW</span></div></div>
|
||
<div class="packet-row new-pulse"><div class="row-main"><span class="row-expand">▸</span><span class="row-hash">B7D44F19</span><span class="row-type type-trace">Trace</span><span class="row-summary">Trace 4-byte path, 6 hops</span><span class="row-count">×3</span></div><div class="row-meta"><span class="route">Flood</span><span class="dot">·</span><span>02:36 PM</span><span class="dot">·</span><span class="observer">GatineauPeak</span><span class="dot">·</span><span class="iata">YOW</span></div></div>
|
||
<div class="packet-row"><div class="row-main"><span class="row-expand">▸</span><span class="row-hash">1CE88A03</span><span class="row-type type-ack">Ack</span><span class="row-summary">Acknowledgment</span><span class="row-count">×2</span></div><div class="row-meta"><span class="route">Direct</span><span class="dot">·</span><span>02:35 PM</span><span class="dot">·</span><span class="observer">RobaStick_YOW</span><span class="dot">·</span><span class="iata">YOW</span></div></div>
|
||
<div class="packet-row"><div class="row-main"><span class="row-expand">▸</span><span class="row-hash">D9F21B7C</span><span class="row-type type-text">Text</span><span class="row-summary">Plain text message</span><span class="row-count">×12</span></div><div class="row-meta"><span class="route">Flood</span><span class="dot">·</span><span>02:34 PM</span><span class="dot">·</span><span class="observer">Bells_Corners</span><span class="dot">·</span><span class="iata">YOW</span></div></div>
|
||
<div class="packet-row"><div class="row-main"><span class="row-expand">▸</span><span class="row-hash">44AE0FD2</span><span class="row-type type-req">Request</span><span class="row-summary">Request dest=A3F1 src=9E2B</span><span class="row-count">×5</span></div><div class="row-meta"><span class="route">Direct</span><span class="dot">·</span><span>02:33 PM</span><span class="dot">·</span><span class="observer">Hull_Hospital</span><span class="dot">·</span><span class="iata">YOW</span></div></div>
|
||
<div class="packet-row"><div class="row-main"><span class="row-expand">▸</span><span class="row-hash">E501CAFE</span><span class="row-type type-advert">Advert</span><span class="row-summary">Advert <span class="quoted">"VE3XYZ/Ottawa_South"</span></span><span class="row-count">×22</span></div><div class="row-meta"><span class="route">Flood</span><span class="dot">·</span><span>02:32 PM</span><span class="dot">·</span><span class="observer">GatineauPeak</span><span class="dot">·</span><span class="iata">YOW</span></div></div>
|
||
</div>
|
||
<div class="status-bar"><div class="status-left"><div class="status-item"><span class="val">1,247</span> packets</div><div class="status-item"><span class="val">34</span> observers</div><div class="status-item"><span class="val">189</span> nodes</div></div><div>mqtt1 + mqtt2</div></div>
|
||
</body>
|
||
</html>
|