Files
notify-bridge/design-mockups/dashboard.html
T
alexei.dolgolyov 1e357244e1 chore(design): add aurora redesign mockups + chooser
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.
2026-04-25 01:11:42 +03:00

1556 lines
57 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" 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&nbsp;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>