1e357244e1
Three full-fidelity dashboard mockups (Bridge/Console, Aurora/Glass, Bento/Modular) plus a chooser index and a tracker-detail page in the chosen Aurora language. Self-contained HTML, no build needed.
1556 lines
57 KiB
HTML
1556 lines
57 KiB
HTML
<!doctype html>
|
||
<html lang="en" data-theme="dark">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Notify Bridge — Control</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght,SOFT,WONK@9..144,300..900,0..100,0..1&family=Instrument+Sans:ital,wght@0,400..700;1,400..700&family=JetBrains+Mono:wght@300..700&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* ============================================================
|
||
TOKENS
|
||
A "signal phosphor" palette — deep ink, electric-lime accent,
|
||
warm bone foreground. Not teal, not purple, not gradient slop.
|
||
============================================================ */
|
||
:root[data-theme="dark"] {
|
||
--ink-0: #07080b; /* page bg */
|
||
--ink-1: #0c0e14; /* surface */
|
||
--ink-2: #11141d; /* card */
|
||
--ink-3: #161a25; /* card-elev */
|
||
--rule: #1d2230; /* hairline */
|
||
--rule-strong: #2a3142;
|
||
--bone: #ece8df; /* warm foreground */
|
||
--bone-dim: #a8a496;
|
||
--mute: #6c7185;
|
||
--signal: #d4ff3a; /* phosphor lime — the brand pulse */
|
||
--signal-deep: #9bcc1f;
|
||
--signal-glow: rgba(212, 255, 58, 0.18);
|
||
--signal-glow-soft: rgba(212, 255, 58, 0.06);
|
||
--warn: #ffb454;
|
||
--error: #ff6b6b;
|
||
--calm: #74c7ec;
|
||
}
|
||
:root[data-theme="paper"] {
|
||
--ink-0: #f3efe6;
|
||
--ink-1: #ebe6d8;
|
||
--ink-2: #ffffff;
|
||
--ink-3: #faf6ec;
|
||
--rule: #d9d2bf;
|
||
--rule-strong: #b8af96;
|
||
--bone: #1a1814;
|
||
--bone-dim: #4a463c;
|
||
--mute: #797464;
|
||
--signal: #2d6e00;
|
||
--signal-deep: #1f4f00;
|
||
--signal-glow: rgba(45, 110, 0, 0.14);
|
||
--signal-glow-soft: rgba(45, 110, 0, 0.05);
|
||
--warn: #b35d00;
|
||
--error: #b3261e;
|
||
--calm: #1f5d8c;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
html, body { margin: 0; padding: 0; }
|
||
body {
|
||
background: var(--ink-0);
|
||
color: var(--bone);
|
||
font-family: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif;
|
||
font-feature-settings: "ss01", "cv11";
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
min-height: 100vh;
|
||
overflow-x: hidden;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* Faint horizontal scanlines + corner vignette = console atmosphere */
|
||
body::before {
|
||
content: '';
|
||
position: fixed; inset: 0;
|
||
background-image: repeating-linear-gradient(
|
||
0deg,
|
||
rgba(255,255,255,0.012) 0 1px,
|
||
transparent 1px 3px
|
||
);
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
mix-blend-mode: overlay;
|
||
}
|
||
body::after {
|
||
content: '';
|
||
position: fixed; inset: 0;
|
||
background:
|
||
radial-gradient(ellipse 80% 60% at 50% 100%, var(--signal-glow-soft), transparent 60%),
|
||
radial-gradient(ellipse 100% 60% at 100% 0%, rgba(255, 180, 84, 0.04), transparent 50%);
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
[data-theme="paper"] body::before { display: none; }
|
||
|
||
::selection { background: var(--signal); color: var(--ink-0); }
|
||
|
||
/* Custom scrollbar */
|
||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--rule-strong);
|
||
border-radius: 0;
|
||
}
|
||
::-webkit-scrollbar-thumb:hover { background: var(--mute); }
|
||
|
||
/* ============================================================
|
||
LAYOUT SHELL
|
||
============================================================ */
|
||
.shell {
|
||
position: relative;
|
||
z-index: 2;
|
||
display: grid;
|
||
grid-template-columns: 224px 1fr;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* ============================================================
|
||
SIDEBAR — vertical operator strip
|
||
============================================================ */
|
||
.rail {
|
||
border-right: 1px solid var(--rule);
|
||
background: var(--ink-1);
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
}
|
||
.rail__brand {
|
||
padding: 22px 22px 18px;
|
||
border-bottom: 1px solid var(--rule);
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
}
|
||
.rail__mark {
|
||
font-family: 'Fraunces', serif;
|
||
font-variation-settings: "opsz" 144, "wght" 600, "SOFT" 100;
|
||
font-size: 26px;
|
||
line-height: 0.9;
|
||
letter-spacing: -0.02em;
|
||
color: var(--bone);
|
||
}
|
||
.rail__mark em {
|
||
font-style: italic;
|
||
font-variation-settings: "opsz" 144, "wght" 400, "SOFT" 100;
|
||
color: var(--signal);
|
||
}
|
||
.rail__tag {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
padding-left: 1px;
|
||
}
|
||
|
||
.rail__section {
|
||
padding: 16px 14px 8px;
|
||
}
|
||
.rail__section-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
padding: 0 8px 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.rail__section-label::after {
|
||
content: '';
|
||
flex: 1;
|
||
height: 1px;
|
||
background: var(--rule);
|
||
}
|
||
|
||
.rail__nav { padding: 0 10px 14px; display: flex; flex-direction: column; gap: 1px; }
|
||
.rail__link {
|
||
display: flex; align-items: center;
|
||
gap: 11px;
|
||
padding: 7px 10px;
|
||
color: var(--bone-dim);
|
||
text-decoration: none;
|
||
font-size: 13px;
|
||
border-radius: 4px;
|
||
position: relative;
|
||
transition: color .15s ease, background .15s ease;
|
||
font-weight: 450;
|
||
}
|
||
.rail__link:hover { color: var(--bone); background: var(--ink-2); }
|
||
.rail__link.is-active {
|
||
color: var(--signal);
|
||
background: linear-gradient(90deg, var(--signal-glow-soft), transparent);
|
||
}
|
||
.rail__link.is-active::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: -10px; top: 6px; bottom: 6px;
|
||
width: 2px;
|
||
background: var(--signal);
|
||
box-shadow: 0 0 8px var(--signal-glow);
|
||
}
|
||
.rail__link svg { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.85; }
|
||
.rail__link .count {
|
||
margin-left: auto;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
color: var(--mute);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.rail__link.is-active .count { color: var(--signal); }
|
||
|
||
.rail__foot {
|
||
margin-top: auto;
|
||
border-top: 1px solid var(--rule);
|
||
padding: 14px 18px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.rail__op {
|
||
display: flex; align-items: center; gap: 10px;
|
||
}
|
||
.rail__avatar {
|
||
width: 30px; height: 30px;
|
||
border-radius: 0;
|
||
background: var(--ink-3);
|
||
border: 1px solid var(--rule-strong);
|
||
display: grid; place-items: center;
|
||
font-family: 'Fraunces', serif;
|
||
font-style: italic;
|
||
color: var(--signal);
|
||
font-size: 14px;
|
||
}
|
||
.rail__op-name { font-size: 12.5px; font-weight: 500; }
|
||
.rail__op-role { font-family: 'JetBrains Mono', monospace; font-size: 9px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--mute); margin-top: 1px; }
|
||
.rail__util { display: flex; gap: 4px; }
|
||
.rail__util button {
|
||
flex: 1;
|
||
background: transparent;
|
||
border: 1px solid var(--rule);
|
||
color: var(--bone-dim);
|
||
padding: 5px 0;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
}
|
||
.rail__util button:hover { color: var(--signal); border-color: var(--signal); }
|
||
|
||
/* ============================================================
|
||
MAIN COLUMN
|
||
============================================================ */
|
||
.main {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* Live ticker strip — broadcast control room */
|
||
.ticker {
|
||
display: flex;
|
||
align-items: center;
|
||
border-bottom: 1px solid var(--rule);
|
||
background: var(--ink-1);
|
||
height: 32px;
|
||
overflow: hidden;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 11px;
|
||
color: var(--bone-dim);
|
||
}
|
||
.ticker__label {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
padding: 0 14px;
|
||
height: 100%;
|
||
background: var(--signal);
|
||
color: var(--ink-0);
|
||
font-weight: 600;
|
||
letter-spacing: 0.16em;
|
||
text-transform: uppercase;
|
||
font-size: 10px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ticker__label .blink {
|
||
width: 6px; height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--ink-0);
|
||
animation: blink 1.1s infinite steps(2);
|
||
}
|
||
@keyframes blink { 50% { opacity: 0; } }
|
||
.ticker__feed {
|
||
flex: 1; overflow: hidden; position: relative;
|
||
height: 100%;
|
||
display: flex; align-items: center;
|
||
}
|
||
.ticker__rail {
|
||
display: inline-flex;
|
||
gap: 28px;
|
||
padding-left: 24px;
|
||
white-space: nowrap;
|
||
animation: marquee 38s linear infinite;
|
||
}
|
||
.ticker__feed:hover .ticker__rail { animation-play-state: paused; }
|
||
@keyframes marquee {
|
||
from { transform: translateX(0); }
|
||
to { transform: translateX(-50%); }
|
||
}
|
||
.ticker__item { display: inline-flex; gap: 8px; align-items: center; }
|
||
.ticker__item .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--signal); }
|
||
.ticker__item .dot.warn { background: var(--warn); }
|
||
.ticker__item .dot.calm { background: var(--calm); }
|
||
.ticker__item .dot.error { background: var(--error); }
|
||
.ticker__item .who { color: var(--bone); }
|
||
.ticker__item .when { color: var(--mute); }
|
||
.ticker__clock {
|
||
flex-shrink: 0;
|
||
padding: 0 16px;
|
||
height: 100%;
|
||
display: flex; align-items: center; gap: 14px;
|
||
border-left: 1px solid var(--rule);
|
||
color: var(--bone-dim);
|
||
font-size: 10.5px;
|
||
}
|
||
.ticker__clock strong {
|
||
color: var(--bone);
|
||
font-weight: 500;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* Page container */
|
||
.page { padding: 36px 48px 60px; max-width: 1400px; }
|
||
|
||
/* ============================================================
|
||
HERO HEADER — editorial type
|
||
============================================================ */
|
||
.hero {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
align-items: end;
|
||
gap: 32px;
|
||
padding-bottom: 24px;
|
||
border-bottom: 1px solid var(--rule);
|
||
margin-bottom: 32px;
|
||
}
|
||
.hero__crumb {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10.5px;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
margin-bottom: 14px;
|
||
display: flex; align-items: center; gap: 10px;
|
||
}
|
||
.hero__crumb .sep { color: var(--rule-strong); }
|
||
.hero__crumb .live {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
color: var(--signal);
|
||
}
|
||
.hero__crumb .live::before {
|
||
content: '';
|
||
width: 6px; height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--signal);
|
||
box-shadow: 0 0 6px var(--signal);
|
||
animation: pulse 1.4s ease-in-out infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { transform: scale(1); opacity: 1; }
|
||
50% { transform: scale(1.4); opacity: 0.55; }
|
||
}
|
||
.hero__title {
|
||
font-family: 'Fraunces', serif;
|
||
font-variation-settings: "opsz" 144, "wght" 380, "SOFT" 50;
|
||
font-size: 72px;
|
||
line-height: 0.92;
|
||
letter-spacing: -0.035em;
|
||
margin: 0 0 14px;
|
||
color: var(--bone);
|
||
}
|
||
.hero__title em {
|
||
font-style: italic;
|
||
font-variation-settings: "opsz" 144, "wght" 380, "SOFT" 100;
|
||
color: var(--signal);
|
||
}
|
||
.hero__sub {
|
||
font-size: 15px;
|
||
color: var(--bone-dim);
|
||
max-width: 520px;
|
||
line-height: 1.55;
|
||
}
|
||
.hero__meter {
|
||
text-align: right;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
}
|
||
.hero__meter-label {
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
margin-bottom: 8px;
|
||
}
|
||
.hero__meter-value {
|
||
font-size: 28px;
|
||
color: var(--signal);
|
||
font-weight: 500;
|
||
font-variant-numeric: tabular-nums;
|
||
line-height: 1;
|
||
}
|
||
.hero__meter-value sup { font-size: 12px; color: var(--bone-dim); margin-left: 4px; font-weight: 400; }
|
||
.hero__meter-row {
|
||
margin-top: 12px;
|
||
display: flex; gap: 18px;
|
||
justify-content: flex-end;
|
||
font-size: 10px;
|
||
letter-spacing: 0.15em;
|
||
text-transform: uppercase;
|
||
color: var(--bone-dim);
|
||
}
|
||
.hero__meter-row span strong {
|
||
color: var(--bone);
|
||
font-variant-numeric: tabular-nums;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
/* ============================================================
|
||
STAT GRID — large mono numbers + sparklines
|
||
============================================================ */
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 0;
|
||
border: 1px solid var(--rule);
|
||
margin-bottom: 40px;
|
||
background: var(--ink-1);
|
||
}
|
||
.stat {
|
||
padding: 20px 22px;
|
||
border-right: 1px solid var(--rule);
|
||
position: relative;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
transition: background .18s;
|
||
}
|
||
.stat:last-child { border-right: 0; }
|
||
.stat:hover { background: var(--ink-2); }
|
||
.stat:hover .stat__value { color: var(--signal); }
|
||
.stat__head {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
margin-bottom: 18px;
|
||
}
|
||
.stat__head .delta {
|
||
font-variant-numeric: tabular-nums;
|
||
color: var(--signal);
|
||
letter-spacing: 0.04em;
|
||
text-transform: none;
|
||
}
|
||
.stat__head .delta.down { color: var(--warn); }
|
||
.stat__value {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-weight: 500;
|
||
font-size: 42px;
|
||
line-height: 1;
|
||
color: var(--bone);
|
||
font-variant-numeric: tabular-nums;
|
||
letter-spacing: -0.02em;
|
||
transition: color .2s ease;
|
||
}
|
||
.stat__value .frac { color: var(--mute); font-size: 22px; }
|
||
.stat__foot {
|
||
margin-top: 16px;
|
||
display: flex; align-items: end; justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
.stat__caption {
|
||
font-family: 'Fraunces', serif;
|
||
font-style: italic;
|
||
font-size: 13.5px;
|
||
color: var(--bone-dim);
|
||
line-height: 1.3;
|
||
max-width: 60%;
|
||
}
|
||
.stat__spark { width: 110px; height: 32px; display: block; }
|
||
.stat__spark path.line { fill: none; stroke: var(--signal); stroke-width: 1.4; }
|
||
.stat__spark path.fill { fill: var(--signal-glow); }
|
||
.stat--alt .stat__spark path.line { stroke: var(--calm); }
|
||
.stat--alt .stat__spark path.fill { fill: rgba(116, 199, 236, 0.14); }
|
||
.stat--warm .stat__spark path.line { stroke: var(--warn); }
|
||
.stat--warm .stat__spark path.fill { fill: rgba(255, 180, 84, 0.14); }
|
||
.stat--rose .stat__spark path.line { stroke: #ff97c4; }
|
||
.stat--rose .stat__spark path.fill { fill: rgba(255, 151, 196, 0.14); }
|
||
|
||
/* ============================================================
|
||
TWO COLUMN: live signal + provider grid
|
||
============================================================ */
|
||
.columns {
|
||
display: grid;
|
||
grid-template-columns: 1.55fr 1fr;
|
||
gap: 28px;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.module {
|
||
background: var(--ink-1);
|
||
border: 1px solid var(--rule);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.module__head {
|
||
display: flex; align-items: baseline; justify-content: space-between;
|
||
padding: 18px 22px 14px;
|
||
border-bottom: 1px solid var(--rule);
|
||
gap: 12px;
|
||
}
|
||
.module__title {
|
||
font-family: 'Fraunces', serif;
|
||
font-variation-settings: "opsz" 144, "wght" 500;
|
||
font-size: 22px;
|
||
letter-spacing: -0.02em;
|
||
color: var(--bone);
|
||
margin: 0;
|
||
}
|
||
.module__title em {
|
||
font-style: italic;
|
||
color: var(--signal);
|
||
font-variation-settings: "opsz" 144, "wght" 400, "SOFT" 100;
|
||
}
|
||
.module__num {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
}
|
||
.module__num strong { color: var(--bone); margin-right: 4px; font-weight: 500; }
|
||
|
||
.module__filters {
|
||
display: flex; gap: 4px;
|
||
padding: 10px 22px;
|
||
border-bottom: 1px solid var(--rule);
|
||
background: var(--ink-0);
|
||
}
|
||
.module__filter {
|
||
padding: 5px 11px;
|
||
background: transparent;
|
||
border: 1px solid var(--rule);
|
||
color: var(--bone-dim);
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
}
|
||
.module__filter:hover { color: var(--bone); border-color: var(--rule-strong); }
|
||
.module__filter.is-active {
|
||
color: var(--ink-0);
|
||
background: var(--signal);
|
||
border-color: var(--signal);
|
||
}
|
||
.module__filter .n {
|
||
margin-left: 8px;
|
||
opacity: 0.65;
|
||
font-size: 9px;
|
||
}
|
||
|
||
/* Live signal stream */
|
||
.stream { padding: 4px 0 12px; }
|
||
.signal {
|
||
display: grid;
|
||
grid-template-columns: 78px 18px 1fr auto;
|
||
gap: 14px;
|
||
align-items: start;
|
||
padding: 14px 22px;
|
||
border-bottom: 1px solid var(--rule);
|
||
transition: background .15s;
|
||
position: relative;
|
||
}
|
||
.signal:last-child { border-bottom: 0; }
|
||
.signal:hover { background: var(--ink-2); }
|
||
.signal:hover .signal__when { color: var(--signal); }
|
||
|
||
.signal__when {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 11px;
|
||
color: var(--bone-dim);
|
||
font-variant-numeric: tabular-nums;
|
||
line-height: 1.55;
|
||
transition: color .15s;
|
||
}
|
||
.signal__when small {
|
||
display: block;
|
||
color: var(--mute);
|
||
font-size: 9.5px;
|
||
margin-top: 1px;
|
||
}
|
||
.signal__rail {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
height: 100%;
|
||
padding-top: 5px;
|
||
}
|
||
.signal__bullet {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--signal);
|
||
box-shadow: 0 0 0 3px var(--signal-glow), 0 0 10px var(--signal);
|
||
flex-shrink: 0;
|
||
}
|
||
.signal__bullet.warn { background: var(--warn); box-shadow: 0 0 0 3px rgba(255,180,84,0.18), 0 0 10px var(--warn); }
|
||
.signal__bullet.calm { background: var(--calm); box-shadow: 0 0 0 3px rgba(116,199,236,0.16), 0 0 10px var(--calm); }
|
||
.signal__bullet.error { background: var(--error); box-shadow: 0 0 0 3px rgba(255,107,107,0.18), 0 0 10px var(--error); }
|
||
.signal__bullet.dim { background: var(--mute); box-shadow: none; }
|
||
.signal__line {
|
||
flex: 1;
|
||
width: 1px;
|
||
background: var(--rule);
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.signal__body { min-width: 0; }
|
||
.signal__head {
|
||
display: flex; align-items: center; gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.signal__verb {
|
||
font-size: 14px;
|
||
color: var(--bone);
|
||
font-weight: 500;
|
||
}
|
||
.signal__verb b {
|
||
font-family: 'Fraunces', serif;
|
||
font-style: italic;
|
||
font-weight: 500;
|
||
color: var(--signal);
|
||
}
|
||
.signal__tag {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
color: var(--bone-dim);
|
||
padding: 2px 7px;
|
||
border: 1px solid var(--rule);
|
||
}
|
||
.signal__tag.live {
|
||
color: var(--signal);
|
||
border-color: var(--signal);
|
||
}
|
||
.signal__meta {
|
||
margin-top: 6px;
|
||
font-size: 12.5px;
|
||
color: var(--bone-dim);
|
||
line-height: 1.5;
|
||
}
|
||
.signal__meta .arrow { color: var(--mute); margin: 0 6px; }
|
||
.signal__meta .ch {
|
||
color: var(--bone);
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 11px;
|
||
}
|
||
.signal__count {
|
||
text-align: right;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 11px;
|
||
color: var(--bone-dim);
|
||
}
|
||
.signal__count strong {
|
||
display: block;
|
||
color: var(--bone);
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* Provider deck — right column */
|
||
.deck { padding: 6px 0 16px; }
|
||
.provider {
|
||
display: grid;
|
||
grid-template-columns: 36px 1fr auto;
|
||
gap: 14px;
|
||
align-items: center;
|
||
padding: 14px 22px;
|
||
border-bottom: 1px solid var(--rule);
|
||
cursor: pointer;
|
||
transition: background .15s;
|
||
position: relative;
|
||
}
|
||
.provider:last-child { border-bottom: 0; }
|
||
.provider:hover { background: var(--ink-2); }
|
||
.provider:hover .provider__name { color: var(--signal); }
|
||
.provider__icon {
|
||
width: 36px; height: 36px;
|
||
display: grid; place-items: center;
|
||
background: var(--ink-3);
|
||
border: 1px solid var(--rule);
|
||
color: var(--signal);
|
||
}
|
||
.provider__icon svg { width: 20px; height: 20px; }
|
||
.provider__name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--bone);
|
||
transition: color .15s;
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.provider__pulse {
|
||
width: 7px; height: 7px;
|
||
border-radius: 50%;
|
||
background: var(--signal);
|
||
box-shadow: 0 0 6px var(--signal);
|
||
animation: pulse 1.6s ease-in-out infinite;
|
||
}
|
||
.provider__pulse.idle { background: var(--mute); box-shadow: none; animation: none; opacity: 0.5; }
|
||
.provider__pulse.warn { background: var(--warn); box-shadow: 0 0 6px var(--warn); }
|
||
.provider__sub {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10.5px;
|
||
color: var(--mute);
|
||
margin-top: 2px;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.provider__num {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-variant-numeric: tabular-nums;
|
||
text-align: right;
|
||
}
|
||
.provider__num strong {
|
||
display: block;
|
||
font-size: 16px;
|
||
color: var(--bone);
|
||
font-weight: 500;
|
||
line-height: 1;
|
||
}
|
||
.provider__num small {
|
||
font-size: 9px;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
margin-top: 4px;
|
||
display: block;
|
||
}
|
||
|
||
/* ============================================================
|
||
HEATMAP + ROUTES MAP
|
||
============================================================ */
|
||
.lower {
|
||
display: grid;
|
||
grid-template-columns: 1.4fr 1fr;
|
||
gap: 28px;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.heatmap { padding: 22px; }
|
||
.heatmap__grid {
|
||
display: grid;
|
||
grid-template-columns: 38px repeat(24, 1fr);
|
||
gap: 3px;
|
||
margin-top: 10px;
|
||
}
|
||
.heatmap__hh {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
color: var(--mute);
|
||
letter-spacing: 0.1em;
|
||
text-align: right;
|
||
padding-right: 6px;
|
||
line-height: 14px;
|
||
text-transform: uppercase;
|
||
}
|
||
.heatmap__cell {
|
||
height: 14px;
|
||
background: var(--ink-2);
|
||
border: 1px solid var(--ink-3);
|
||
transition: transform .15s;
|
||
}
|
||
.heatmap__cell:hover { transform: scale(1.5); border-color: var(--signal); z-index: 2; }
|
||
.heatmap__cell.l1 { background: rgba(212, 255, 58, 0.08); }
|
||
.heatmap__cell.l2 { background: rgba(212, 255, 58, 0.18); }
|
||
.heatmap__cell.l3 { background: rgba(212, 255, 58, 0.34); }
|
||
.heatmap__cell.l4 { background: rgba(212, 255, 58, 0.55); }
|
||
.heatmap__cell.l5 { background: var(--signal); box-shadow: 0 0 4px var(--signal-glow); }
|
||
.heatmap__hours {
|
||
grid-column: 2 / -1;
|
||
display: grid;
|
||
grid-template-columns: repeat(24, 1fr);
|
||
gap: 3px;
|
||
margin-top: 6px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 8px;
|
||
color: var(--mute);
|
||
letter-spacing: 0.1em;
|
||
text-align: center;
|
||
}
|
||
.heatmap__hours span { padding-top: 2px; }
|
||
.heatmap__legend {
|
||
display: flex; align-items: center; gap: 8px;
|
||
margin-top: 18px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
}
|
||
.heatmap__legend .swatch { display: inline-flex; gap: 2px; }
|
||
.heatmap__legend .swatch span { width: 10px; height: 10px; }
|
||
|
||
/* Routes — Sankey-style channel map */
|
||
.routes { padding: 22px; }
|
||
.route {
|
||
display: grid;
|
||
grid-template-columns: 1fr 60px 1fr;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 0;
|
||
border-bottom: 1px dashed var(--rule);
|
||
}
|
||
.route:last-child { border-bottom: 0; }
|
||
.route__from, .route__to {
|
||
display: flex; align-items: center; gap: 10px;
|
||
font-size: 13px;
|
||
}
|
||
.route__to { justify-content: flex-end; text-align: right; }
|
||
.route__from svg, .route__to svg { width: 16px; height: 16px; color: var(--bone-dim); }
|
||
.route__name { color: var(--bone); font-weight: 500; }
|
||
.route__sub {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
color: var(--mute);
|
||
margin-top: 2px;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
.route__wire {
|
||
height: 22px;
|
||
position: relative;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.route__wire::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 50% 0 auto 0;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, var(--rule-strong), var(--signal), var(--rule-strong));
|
||
}
|
||
.route__count {
|
||
position: relative;
|
||
background: var(--ink-1);
|
||
padding: 0 6px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10.5px;
|
||
color: var(--signal);
|
||
font-variant-numeric: tabular-nums;
|
||
z-index: 1;
|
||
}
|
||
.route__from-meta { display: flex; flex-direction: column; }
|
||
|
||
/* ============================================================
|
||
QUICK COMPOSE BAND
|
||
A creative new touch: a tactile "compose" cassette
|
||
============================================================ */
|
||
.compose {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
gap: 24px;
|
||
align-items: stretch;
|
||
background:
|
||
linear-gradient(135deg, var(--ink-2), var(--ink-1));
|
||
border: 1px solid var(--rule);
|
||
padding: 26px 28px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.compose::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background-image: repeating-linear-gradient(
|
||
90deg, transparent 0 38px, var(--rule) 38px 39px
|
||
);
|
||
opacity: 0.3;
|
||
pointer-events: none;
|
||
}
|
||
.compose__body { position: relative; }
|
||
.compose__kicker {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--signal);
|
||
margin-bottom: 8px;
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.compose__kicker::before {
|
||
content: '';
|
||
width: 18px; height: 1px; background: var(--signal);
|
||
}
|
||
.compose__title {
|
||
font-family: 'Fraunces', serif;
|
||
font-variation-settings: "opsz" 144, "wght" 380, "SOFT" 50;
|
||
font-size: 36px;
|
||
line-height: 1.05;
|
||
letter-spacing: -0.025em;
|
||
margin: 0 0 10px;
|
||
color: var(--bone);
|
||
}
|
||
.compose__title em {
|
||
font-style: italic;
|
||
color: var(--signal);
|
||
font-variation-settings: "opsz" 144, "wght" 380, "SOFT" 100;
|
||
}
|
||
.compose__sub {
|
||
color: var(--bone-dim);
|
||
font-size: 14px;
|
||
max-width: 520px;
|
||
line-height: 1.55;
|
||
}
|
||
.compose__cta {
|
||
position: relative;
|
||
display: flex; flex-direction: column;
|
||
gap: 8px;
|
||
align-items: stretch;
|
||
justify-content: center;
|
||
}
|
||
.btn {
|
||
border: 1px solid var(--rule-strong);
|
||
background: var(--ink-3);
|
||
color: var(--bone);
|
||
padding: 11px 18px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 11px;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: 10px;
|
||
justify-content: center;
|
||
transition: all .15s;
|
||
}
|
||
.btn:hover { border-color: var(--bone-dim); color: var(--bone); }
|
||
.btn.primary {
|
||
background: var(--signal);
|
||
color: var(--ink-0);
|
||
border-color: var(--signal);
|
||
box-shadow: 0 0 0 0 var(--signal-glow);
|
||
}
|
||
.btn.primary:hover { box-shadow: 0 0 22px var(--signal-glow); }
|
||
.btn svg { width: 14px; height: 14px; }
|
||
|
||
/* Footer thin band */
|
||
.stamp {
|
||
margin-top: 60px;
|
||
padding-top: 22px;
|
||
border-top: 1px solid var(--rule);
|
||
display: flex; justify-content: space-between;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
color: var(--mute);
|
||
}
|
||
.stamp em {
|
||
color: var(--bone);
|
||
font-style: normal;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Theme toggle floating */
|
||
.theme-toggle {
|
||
position: fixed;
|
||
right: 22px; top: 22px;
|
||
z-index: 30;
|
||
display: flex; gap: 0;
|
||
border: 1px solid var(--rule);
|
||
background: var(--ink-1);
|
||
}
|
||
.theme-toggle button {
|
||
padding: 6px 12px;
|
||
background: transparent;
|
||
border: 0;
|
||
color: var(--mute);
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
}
|
||
.theme-toggle button.is-active {
|
||
background: var(--signal);
|
||
color: var(--ink-0);
|
||
}
|
||
|
||
/* Reveal animation */
|
||
@keyframes rise {
|
||
from { opacity: 0; transform: translateY(12px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
.stagger > * { animation: rise .55s cubic-bezier(.2,.7,.2,1) both; }
|
||
.stagger > *:nth-child(1) { animation-delay: .04s; }
|
||
.stagger > *:nth-child(2) { animation-delay: .12s; }
|
||
.stagger > *:nth-child(3) { animation-delay: .20s; }
|
||
.stagger > *:nth-child(4) { animation-delay: .28s; }
|
||
.stagger > *:nth-child(5) { animation-delay: .36s; }
|
||
.stagger > *:nth-child(6) { animation-delay: .44s; }
|
||
.stagger > *:nth-child(7) { animation-delay: .52s; }
|
||
.stagger > *:nth-child(8) { animation-delay: .60s; }
|
||
|
||
@media (max-width: 1100px) {
|
||
.columns, .lower { grid-template-columns: 1fr; }
|
||
}
|
||
@media (max-width: 820px) {
|
||
.shell { grid-template-columns: 1fr; }
|
||
.rail { position: static; height: auto; }
|
||
.hero { grid-template-columns: 1fr; }
|
||
.hero__meter { text-align: left; }
|
||
.hero__meter-row { justify-content: flex-start; }
|
||
.stats { grid-template-columns: repeat(2, 1fr); }
|
||
.page { padding: 24px; }
|
||
.hero__title { font-size: 48px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Theme toggle -->
|
||
<div class="theme-toggle" role="tablist">
|
||
<button id="t-dark" class="is-active" onclick="setTheme('dark')">Console</button>
|
||
<button id="t-paper" onclick="setTheme('paper')">Broadsheet</button>
|
||
</div>
|
||
|
||
<div class="shell">
|
||
|
||
<!-- ========== RAIL ========== -->
|
||
<aside class="rail">
|
||
<div class="rail__brand">
|
||
<div>
|
||
<div class="rail__mark">notify <em>bridge</em></div>
|
||
<div class="rail__tag">control · v0.5.2</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rail__section">
|
||
<div class="rail__section-label">overview</div>
|
||
<nav class="rail__nav">
|
||
<a href="#" class="rail__link is-active">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M3 12h4l3-9 4 18 3-9h4"/></svg>
|
||
Signal
|
||
<span class="count">live</span>
|
||
</a>
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
||
Providers
|
||
<span class="count">04</span>
|
||
</a>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="rail__section">
|
||
<div class="rail__section-label">routing</div>
|
||
<nav class="rail__nav">
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="8"/><path d="M12 4v3M12 17v3M4 12h3M17 12h3"/></svg>
|
||
Trackers <span class="count">12</span>
|
||
</a>
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
|
||
Templates <span class="count">28</span>
|
||
</a>
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
|
||
Targets <span class="count">19</span>
|
||
</a>
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M5 12l3 3 6-6 6 6"/></svg>
|
||
Actions <span class="count">07</span>
|
||
</a>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="rail__section">
|
||
<div class="rail__section-label">operators</div>
|
||
<nav class="rail__nav">
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="4" y="4" width="16" height="16" rx="2"/><circle cx="9" cy="10" r="1.2"/><circle cx="15" cy="10" r="1.2"/><path d="M8 16c1.5 1 6.5 1 8 0"/></svg>
|
||
Bots <span class="count">06</span>
|
||
</a>
|
||
<a href="#" class="rail__link">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M4 18l5-5 5 5 5-9"/></svg>
|
||
Commands <span class="count">14</span>
|
||
</a>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="rail__foot">
|
||
<div class="rail__op">
|
||
<div class="rail__avatar">A</div>
|
||
<div>
|
||
<div class="rail__op-name">alexei</div>
|
||
<div class="rail__op-role">administrator</div>
|
||
</div>
|
||
</div>
|
||
<div class="rail__util">
|
||
<button>EN</button>
|
||
<button>DOC</button>
|
||
<button>OUT</button>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ========== MAIN ========== -->
|
||
<section class="main">
|
||
|
||
<!-- Live ticker -->
|
||
<div class="ticker">
|
||
<div class="ticker__label"><span class="blink"></span>On Air</div>
|
||
<div class="ticker__feed">
|
||
<div class="ticker__rail">
|
||
<span class="ticker__item"><span class="dot"></span><span class="when">02:14:08</span> <span>Immich · </span><span class="who">«Семейный 2025»</span> · 14 assets added → telegram <strong>@family</strong></span>
|
||
<span class="ticker__item"><span class="dot calm"></span><span class="when">02:13:51</span> <span>Gitea · </span>push to <span class="who">notify-bridge/main</span> → matrix <strong>#dev-room</strong></span>
|
||
<span class="ticker__item"><span class="dot warn"></span><span class="when">02:13:32</span> <span>Telegram · </span>cmd <span class="who">/recent</span> by <strong>@alex</strong></span>
|
||
<span class="ticker__item"><span class="dot"></span><span class="when">02:12:55</span> <span>Immich · </span>shared link expired on <span class="who">«Прага 24»</span></span>
|
||
<span class="ticker__item"><span class="dot error"></span><span class="when">02:11:09</span> <span>Email · </span>SMTP retry on <span class="who">smtp.fastmail.com</span></span>
|
||
<span class="ticker__item"><span class="dot"></span><span class="when">02:09:44</span> <span>Action · </span><span class="who">memory_dispatch</span> · 6 recipients</span>
|
||
<!-- duplicate for marquee -->
|
||
<span class="ticker__item"><span class="dot"></span><span class="when">02:14:08</span> <span>Immich · </span><span class="who">«Семейный 2025»</span> · 14 assets added → telegram <strong>@family</strong></span>
|
||
<span class="ticker__item"><span class="dot calm"></span><span class="when">02:13:51</span> <span>Gitea · </span>push to <span class="who">notify-bridge/main</span> → matrix <strong>#dev-room</strong></span>
|
||
<span class="ticker__item"><span class="dot warn"></span><span class="when">02:13:32</span> <span>Telegram · </span>cmd <span class="who">/recent</span> by <strong>@alex</strong></span>
|
||
<span class="ticker__item"><span class="dot"></span><span class="when">02:12:55</span> <span>Immich · </span>shared link expired on <span class="who">«Прага 24»</span></span>
|
||
<span class="ticker__item"><span class="dot error"></span><span class="when">02:11:09</span> <span>Email · </span>SMTP retry on <span class="who">smtp.fastmail.com</span></span>
|
||
<span class="ticker__item"><span class="dot"></span><span class="when">02:09:44</span> <span>Action · </span><span class="who">memory_dispatch</span> · 6 recipients</span>
|
||
</div>
|
||
</div>
|
||
<div class="ticker__clock">
|
||
<span>UTC <strong id="clock">--:--:--</strong></span>
|
||
<span>UPTIME <strong>14d 06h</strong></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Page -->
|
||
<div class="page stagger">
|
||
|
||
<!-- HERO -->
|
||
<header class="hero">
|
||
<div>
|
||
<div class="hero__crumb">
|
||
<span>System</span><span class="sep">/</span><span>Control</span><span class="sep">/</span>
|
||
<span class="live">Live</span>
|
||
</div>
|
||
<h1 class="hero__title">Tonight, <em>everything</em> is flowing.</h1>
|
||
<p class="hero__sub">
|
||
Four providers are listening, twelve trackers are armed, and the bridge has dispatched
|
||
<strong style="color:var(--bone)">2 814</strong> messages across nineteen targets in the last 24 hours.
|
||
Nothing is queued. Nothing is failing.
|
||
</p>
|
||
</div>
|
||
<div class="hero__meter">
|
||
<div class="hero__meter-label">throughput · 24h</div>
|
||
<div class="hero__meter-value">2 814<sup>msgs</sup></div>
|
||
<div class="hero__meter-row">
|
||
<span><strong>99.7</strong>% delivered</span>
|
||
<span><strong>148ms</strong> p50</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- STATS -->
|
||
<div class="stats">
|
||
<div class="stat">
|
||
<div class="stat__head"><span>Providers</span><span class="delta">+1 wk</span></div>
|
||
<div class="stat__value">04</div>
|
||
<div class="stat__foot">
|
||
<div class="stat__caption">Immich, Gitea, GitHub, RSS — all connected.</div>
|
||
<svg class="stat__spark" viewBox="0 0 110 32" preserveAspectRatio="none">
|
||
<path class="fill" d="M0 28 L0 22 L15 18 L30 24 L45 14 L60 16 L75 8 L90 12 L110 6 L110 32 Z"/>
|
||
<path class="line" d="M0 22 L15 18 L30 24 L45 14 L60 16 L75 8 L90 12 L110 6"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="stat stat--alt">
|
||
<div class="stat__head"><span>Trackers · armed</span><span class="delta">+3 24h</span></div>
|
||
<div class="stat__value">12<span class="frac"> / 14</span></div>
|
||
<div class="stat__foot">
|
||
<div class="stat__caption">Two paused awaiting credentials.</div>
|
||
<svg class="stat__spark" viewBox="0 0 110 32" preserveAspectRatio="none">
|
||
<path class="fill" d="M0 28 L0 16 L15 20 L30 12 L45 18 L60 8 L75 14 L90 10 L110 4 L110 32 Z"/>
|
||
<path class="line" d="M0 16 L15 20 L30 12 L45 18 L60 8 L75 14 L90 10 L110 4"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="stat stat--warm">
|
||
<div class="stat__head"><span>Targets · channels</span><span class="delta">+2 wk</span></div>
|
||
<div class="stat__value">19</div>
|
||
<div class="stat__foot">
|
||
<div class="stat__caption">Telegram, Matrix, Email, ntfy, Discord.</div>
|
||
<svg class="stat__spark" viewBox="0 0 110 32" preserveAspectRatio="none">
|
||
<path class="fill" d="M0 28 L0 24 L15 22 L30 18 L45 22 L60 14 L75 12 L90 16 L110 10 L110 32 Z"/>
|
||
<path class="line" d="M0 24 L15 22 L30 18 L45 22 L60 14 L75 12 L90 16 L110 10"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="stat stat--rose">
|
||
<div class="stat__head"><span>Failures · 24h</span><span class="delta down">−4 wk</span></div>
|
||
<div class="stat__value">02</div>
|
||
<div class="stat__foot">
|
||
<div class="stat__caption">SMTP retry on Fastmail; auto-recovered.</div>
|
||
<svg class="stat__spark" viewBox="0 0 110 32" preserveAspectRatio="none">
|
||
<path class="fill" d="M0 28 L0 12 L15 18 L30 22 L45 16 L60 24 L75 26 L90 28 L110 30 L110 32 Z"/>
|
||
<path class="line" d="M0 12 L15 18 L30 22 L45 16 L60 24 L75 26 L90 28 L110 30"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- COLUMNS -->
|
||
<div class="columns">
|
||
|
||
<!-- Live signal stream -->
|
||
<div class="module">
|
||
<div class="module__head">
|
||
<h2 class="module__title">Signal <em>stream</em></h2>
|
||
<div class="module__num"><strong>2 814</strong>events / 24h</div>
|
||
</div>
|
||
<div class="module__filters">
|
||
<button class="module__filter is-active">All <span class="n">2814</span></button>
|
||
<button class="module__filter">Assets <span class="n">1942</span></button>
|
||
<button class="module__filter">Sharing <span class="n">312</span></button>
|
||
<button class="module__filter">Commands <span class="n">488</span></button>
|
||
<button class="module__filter">Failures <span class="n">2</span></button>
|
||
</div>
|
||
<div class="stream">
|
||
<div class="signal">
|
||
<div class="signal__when">02:14:08<small>just now</small></div>
|
||
<div class="signal__rail"><span class="signal__bullet"></span><span class="signal__line"></span></div>
|
||
<div class="signal__body">
|
||
<div class="signal__head">
|
||
<span class="signal__verb">Immich added <b>14 assets</b> to «Семейный 2025»</span>
|
||
<span class="signal__tag live">live</span>
|
||
</div>
|
||
<div class="signal__meta">
|
||
Tracker <span class="ch">family-photos</span><span class="arrow">→</span>
|
||
Target <span class="ch">@family · telegram</span><span class="arrow">→</span>
|
||
Template <span class="ch">assets_added.ru</span>
|
||
</div>
|
||
</div>
|
||
<div class="signal__count"><strong>14</strong>assets</div>
|
||
</div>
|
||
|
||
<div class="signal">
|
||
<div class="signal__when">02:13:51<small>17s ago</small></div>
|
||
<div class="signal__rail"><span class="signal__bullet calm"></span><span class="signal__line"></span></div>
|
||
<div class="signal__body">
|
||
<div class="signal__head">
|
||
<span class="signal__verb">Gitea pushed <b>3 commits</b> to <code style="font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--bone)">notify-bridge/main</code></span>
|
||
<span class="signal__tag">push</span>
|
||
</div>
|
||
<div class="signal__meta">
|
||
Tracker <span class="ch">repo-changes</span><span class="arrow">→</span>
|
||
Target <span class="ch">#dev-room · matrix</span>
|
||
</div>
|
||
</div>
|
||
<div class="signal__count"><strong>03</strong>commits</div>
|
||
</div>
|
||
|
||
<div class="signal">
|
||
<div class="signal__when">02:13:32<small>36s ago</small></div>
|
||
<div class="signal__rail"><span class="signal__bullet warn"></span><span class="signal__line"></span></div>
|
||
<div class="signal__body">
|
||
<div class="signal__head">
|
||
<span class="signal__verb">Telegram bot received <b>/recent</b> from @alex</span>
|
||
<span class="signal__tag">command</span>
|
||
</div>
|
||
<div class="signal__meta">
|
||
Bot <span class="ch">@notifybridge_bot</span><span class="arrow">→</span>
|
||
Resolved <span class="ch">5 recent · 142ms</span>
|
||
</div>
|
||
</div>
|
||
<div class="signal__count"><strong>05</strong>items</div>
|
||
</div>
|
||
|
||
<div class="signal">
|
||
<div class="signal__when">02:12:55<small>1m ago</small></div>
|
||
<div class="signal__rail"><span class="signal__bullet"></span><span class="signal__line"></span></div>
|
||
<div class="signal__body">
|
||
<div class="signal__head">
|
||
<span class="signal__verb">Immich shared link <b>expired</b> on «Прага 24»</span>
|
||
<span class="signal__tag">sharing</span>
|
||
</div>
|
||
<div class="signal__meta">
|
||
Tracker <span class="ch">link-watch</span><span class="arrow">→</span>
|
||
Target <span class="ch">photos@dolgolyov.dev · email</span>
|
||
</div>
|
||
</div>
|
||
<div class="signal__count"><strong>01</strong>link</div>
|
||
</div>
|
||
|
||
<div class="signal">
|
||
<div class="signal__when">02:11:09<small>3m ago</small></div>
|
||
<div class="signal__rail"><span class="signal__bullet error"></span><span class="signal__line"></span></div>
|
||
<div class="signal__body">
|
||
<div class="signal__head">
|
||
<span class="signal__verb">Email · <b>SMTP retry</b> on smtp.fastmail.com</span>
|
||
<span class="signal__tag" style="color:var(--error);border-color:var(--error)">retry · resolved</span>
|
||
</div>
|
||
<div class="signal__meta">
|
||
Backoff <span class="ch">2.4s</span><span class="arrow">→</span>
|
||
Delivered on attempt 2 of 3
|
||
</div>
|
||
</div>
|
||
<div class="signal__count" style="color:var(--error)"><strong style="color:var(--error)">2.4s</strong>retry</div>
|
||
</div>
|
||
|
||
<div class="signal">
|
||
<div class="signal__when">02:09:44<small>4m ago</small></div>
|
||
<div class="signal__rail"><span class="signal__bullet"></span></div>
|
||
<div class="signal__body">
|
||
<div class="signal__head">
|
||
<span class="signal__verb">Action <b>memory_dispatch</b> ran for «Один год назад»</span>
|
||
<span class="signal__tag">scheduled</span>
|
||
</div>
|
||
<div class="signal__meta">
|
||
6 recipients reached · <span class="ch">2 telegram · 3 matrix · 1 ntfy</span>
|
||
</div>
|
||
</div>
|
||
<div class="signal__count"><strong>06</strong>sent</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Provider deck -->
|
||
<div class="module">
|
||
<div class="module__head">
|
||
<h2 class="module__title">On <em>watch</em></h2>
|
||
<div class="module__num"><strong>04</strong>providers</div>
|
||
</div>
|
||
<div class="deck">
|
||
<div class="provider">
|
||
<div class="provider__icon">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M3 17l5-5 4 4 3-3 6 6"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="provider__name"><span class="provider__pulse"></span> Immich</div>
|
||
<div class="provider__sub">photos.dolgolyov.dev · 8 trackers</div>
|
||
</div>
|
||
<div class="provider__num"><strong>1942</strong><small>events 24h</small></div>
|
||
</div>
|
||
|
||
<div class="provider">
|
||
<div class="provider__icon">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M9 8l3-3 3 3M9 16l3 3 3-3M5 12h14"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="provider__name"><span class="provider__pulse"></span> Gitea</div>
|
||
<div class="provider__sub">git.dolgolyov-family.by · 2 trackers</div>
|
||
</div>
|
||
<div class="provider__num"><strong>368</strong><small>events 24h</small></div>
|
||
</div>
|
||
|
||
<div class="provider">
|
||
<div class="provider__icon">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 2a10 10 0 0 0-3 19.5c.5 0 .7-.3.7-.6v-2c-2.7.6-3.3-1.3-3.3-1.3-.4-1-1-1.3-1-1.3-1-.7.1-.7.1-.7 1 .1 1.5 1 1.5 1 1 1.6 2.5 1.2 3.1.9.1-.7.4-1.2.7-1.5-2.1-.2-4.4-1-4.4-4.7 0-1 .4-1.9 1-2.6-.1-.3-.4-1.2.1-2.6 0 0 .8-.3 2.7 1 .8-.2 1.6-.3 2.5-.3.8 0 1.7.1 2.5.3 1.9-1.3 2.7-1 2.7-1 .5 1.4.2 2.3.1 2.6.6.7 1 1.6 1 2.6 0 3.7-2.3 4.5-4.4 4.7.4.3.7 1 .7 1.9v2.8c0 .3.2.6.7.6A10 10 0 0 0 12 2z"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="provider__name"><span class="provider__pulse warn"></span> GitHub</div>
|
||
<div class="provider__sub">anthropics/claude-code · 1 tracker</div>
|
||
</div>
|
||
<div class="provider__num"><strong>088</strong><small>events 24h</small></div>
|
||
</div>
|
||
|
||
<div class="provider">
|
||
<div class="provider__icon">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M5 19V5l8 7-8 7zM13 5l8 7-8 7"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="provider__name"><span class="provider__pulse idle"></span> RSS · 7 feeds</div>
|
||
<div class="provider__sub">last seen 14m ago · idle</div>
|
||
</div>
|
||
<div class="provider__num"><strong>416</strong><small>events 24h</small></div>
|
||
</div>
|
||
|
||
<div class="provider" style="background: var(--ink-0); border-bottom: 0; opacity: 0.6;">
|
||
<div class="provider__icon" style="background: transparent; border-style: dashed; color: var(--mute);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 5v14M5 12h14"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="provider__name" style="color: var(--bone-dim)">Add provider…</div>
|
||
<div class="provider__sub">9 integrations available</div>
|
||
</div>
|
||
<div class="provider__num"><small style="margin-top: 0">Webhook · API</small></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LOWER -->
|
||
<div class="lower">
|
||
|
||
<!-- HEATMAP -->
|
||
<div class="module heatmap">
|
||
<div class="module__head" style="padding: 0 0 14px;">
|
||
<h2 class="module__title">Pulse · <em>last seven days</em></h2>
|
||
<div class="module__num">UTC · grouped by hour</div>
|
||
</div>
|
||
<div class="heatmap__grid" id="heatmap"></div>
|
||
<div class="heatmap__hours" id="heat-hours">
|
||
<span>00</span><span></span><span>02</span><span></span><span>04</span><span></span><span>06</span><span></span>
|
||
<span>08</span><span></span><span>10</span><span></span><span>12</span><span></span><span>14</span><span></span>
|
||
<span>16</span><span></span><span>18</span><span></span><span>20</span><span></span><span>22</span><span></span>
|
||
</div>
|
||
<div class="heatmap__legend">
|
||
<span>Low</span>
|
||
<span class="swatch">
|
||
<span style="background: var(--ink-2)"></span>
|
||
<span style="background: rgba(212, 255, 58, 0.08)"></span>
|
||
<span style="background: rgba(212, 255, 58, 0.18)"></span>
|
||
<span style="background: rgba(212, 255, 58, 0.34)"></span>
|
||
<span style="background: rgba(212, 255, 58, 0.55)"></span>
|
||
<span style="background: var(--signal)"></span>
|
||
</span>
|
||
<span>High · 312/h peak (Sun 21:00)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ROUTES -->
|
||
<div class="module routes">
|
||
<div class="module__head" style="padding: 0 0 14px;">
|
||
<h2 class="module__title">Active <em>wires</em></h2>
|
||
<div class="module__num"><strong>09</strong>routes</div>
|
||
</div>
|
||
<div class="route">
|
||
<div class="route__from">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/></svg>
|
||
<div class="route__from-meta"><span class="route__name">Immich · family</span><span class="route__sub">tracker · family-photos</span></div>
|
||
</div>
|
||
<div class="route__wire"><span class="route__count">→ 1 942</span></div>
|
||
<div class="route__to">
|
||
<div class="route__from-meta"><span class="route__name">@family</span><span class="route__sub">telegram · group</span></div>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/></svg>
|
||
</div>
|
||
</div>
|
||
<div class="route">
|
||
<div class="route__from">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/></svg>
|
||
<div class="route__from-meta"><span class="route__name">Gitea · main</span><span class="route__sub">tracker · repo-changes</span></div>
|
||
</div>
|
||
<div class="route__wire"><span class="route__count">→ 0 368</span></div>
|
||
<div class="route__to">
|
||
<div class="route__from-meta"><span class="route__name">#dev-room</span><span class="route__sub">matrix · dev</span></div>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M4 4h16v16H4zM4 4l8 8 8-8"/></svg>
|
||
</div>
|
||
</div>
|
||
<div class="route">
|
||
<div class="route__from">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="5" width="18" height="14" rx="2"/></svg>
|
||
<div class="route__from-meta"><span class="route__name">Immich · share-watch</span><span class="route__sub">tracker · link-watch</span></div>
|
||
</div>
|
||
<div class="route__wire"><span class="route__count">→ 0 312</span></div>
|
||
<div class="route__to">
|
||
<div class="route__from-meta"><span class="route__name">photos@dolgolyov.dev</span><span class="route__sub">email · transactional</span></div>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M4 4h16v16H4zM4 4l8 8 8-8"/></svg>
|
||
</div>
|
||
</div>
|
||
<div class="route">
|
||
<div class="route__from">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M5 19V5l8 7-8 7z"/></svg>
|
||
<div class="route__from-meta"><span class="route__name">RSS · Hacker News</span><span class="route__sub">tracker · daily-digest</span></div>
|
||
</div>
|
||
<div class="route__wire"><span class="route__count">→ 0 088</span></div>
|
||
<div class="route__to">
|
||
<div class="route__from-meta"><span class="route__name">notify · ntfy.sh</span><span class="route__sub">push · personal</span></div>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 2a7 7 0 0 0-7 7v5l-2 3h18l-2-3V9a7 7 0 0 0-7-7z"/><path d="M9 19a3 3 0 0 0 6 0"/></svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- COMPOSE BAND -->
|
||
<div class="compose">
|
||
<div class="compose__body">
|
||
<div class="compose__kicker">Bridge a new signal</div>
|
||
<h3 class="compose__title">Pick a source. Choose a channel. <em>Compose the wire.</em></h3>
|
||
<p class="compose__sub">
|
||
The composer walks you from provider → tracker → template → target in four steps,
|
||
with live preview at every stage. Or paste a webhook URL and we'll infer the rest.
|
||
</p>
|
||
</div>
|
||
<div class="compose__cta">
|
||
<button class="btn primary">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||
New tracker
|
||
</button>
|
||
<button class="btn">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
|
||
Import from JSON
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STAMP -->
|
||
<div class="stamp">
|
||
<span><em>Notify Bridge</em> · Operator console · v0.5.2</span>
|
||
<span>build <em>770c198</em> · UTC <em id="stamp-clock">--:--</em></span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<script>
|
||
// Theme switcher
|
||
function setTheme(t) {
|
||
document.documentElement.setAttribute('data-theme', t);
|
||
document.getElementById('t-dark').classList.toggle('is-active', t === 'dark');
|
||
document.getElementById('t-paper').classList.toggle('is-active', t === 'paper');
|
||
}
|
||
|
||
// Live clock
|
||
function pad(n){return String(n).padStart(2,'0');}
|
||
function tick() {
|
||
const d = new Date();
|
||
const time = pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds());
|
||
document.getElementById('clock').textContent = time;
|
||
document.getElementById('stamp-clock').textContent = time.slice(0, 5);
|
||
}
|
||
tick(); setInterval(tick, 1000);
|
||
|
||
// Heatmap — generate 7×24 grid with believable distribution
|
||
(function(){
|
||
const days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
|
||
const wrap = document.getElementById('heatmap');
|
||
let html = '';
|
||
days.forEach((d, di) => {
|
||
html += `<span class="heatmap__hh">${d}</span>`;
|
||
for (let h = 0; h < 24; h++) {
|
||
// Believable: low overnight, peaks around 9 + 19-21
|
||
const base = (h >= 8 && h <= 22) ? 1 : 0;
|
||
const peak1 = Math.max(0, 1.6 - Math.abs(h - 9) * 0.5);
|
||
const peak2 = Math.max(0, 2.2 - Math.abs(h - 20) * 0.45);
|
||
const weekend = (di >= 5) ? 1.3 : 1.0;
|
||
const noise = Math.random() * 0.7;
|
||
const score = (base + peak1 + peak2) * weekend + noise;
|
||
let level = 0;
|
||
if (score > 0.6) level = 1;
|
||
if (score > 1.4) level = 2;
|
||
if (score > 2.4) level = 3;
|
||
if (score > 3.2) level = 4;
|
||
if (score > 3.9) level = 5;
|
||
html += `<span class="heatmap__cell ${level ? 'l' + level : ''}" title="${d} ${pad(h)}:00 — ${Math.round(score * 14)} events"></span>`;
|
||
}
|
||
});
|
||
wrap.innerHTML = html;
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|