Files
notify-bridge/design-mockups/dashboard-bento.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

1161 lines
42 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="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Notify Bridge — Bento</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=Manrope:wght@300..800&family=JetBrains+Mono:wght@300..600&display=swap" rel="stylesheet">
<style>
/* ============================================================
OPTION C — "BENTO MODULAR"
Apple Keynote / Linear blog energy. Mixed-size colorful tiles
in a tight grid. Each tile commits to a single role and color.
Generous radius, confident type, playful but disciplined.
============================================================ */
:root[data-theme="light"] {
--bg: #f4f3ef;
--surface: #ffffff;
--ink: #0c0d11;
--ink-dim: #4d5159;
--mute: #898d97;
--rule: #e7e6e0;
--rule-strong: #cfcec7;
/* Tile palette — bold, confident, never muddy */
--tile-violet: #6d4ce6;
--tile-violet-fg: #ffffff;
--tile-mint: #c8f078;
--tile-mint-fg: #1a2e0c;
--tile-coral: #ff6f5b;
--tile-coral-fg: #ffffff;
--tile-honey: #ffd23a;
--tile-honey-fg: #2a1f00;
--tile-cobalt: #1d3aff;
--tile-cobalt-fg: #ffffff;
--tile-rose: #ffd1e0;
--tile-rose-fg: #4a0d2e;
--tile-ink: #0c0d11;
--tile-ink-fg: #ffffff;
--tile-bone: #f0eee8;
--tile-bone-fg: #0c0d11;
/* Status */
--ok: #1f9d56;
--warn: #c97600;
--err: #d6322f;
}
:root[data-theme="dark"] {
--bg: #0a0a0c;
--surface: #14151a;
--ink: #f0eee8;
--ink-dim: #b0b3bd;
--mute: #6f7280;
--rule: #232530;
--rule-strong: #353846;
--tile-violet: #8366ff;
--tile-violet-fg: #ffffff;
--tile-mint: #b0e85d;
--tile-mint-fg: #142605;
--tile-coral: #ff7864;
--tile-coral-fg: #2a0500;
--tile-honey: #ffcf3d;
--tile-honey-fg: #2a1f00;
--tile-cobalt: #4566ff;
--tile-cobalt-fg: #ffffff;
--tile-rose: #ff9bbf;
--tile-rose-fg: #2a0010;
--tile-ink: #f0eee8;
--tile-ink-fg: #0c0d11;
--tile-bone: #1c1d24;
--tile-bone-fg: #f0eee8;
--ok: #4cd383;
--warn: #ffb454;
--err: #ff7a7a;
}
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--ink);
font-family: 'Manrope', ui-sans-serif, system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
overflow-x: hidden;
font-size: 14px;
line-height: 1.5;
letter-spacing: -0.005em;
transition: background .3s ease, color .3s ease;
}
::selection { background: var(--tile-violet); color: white; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: 999px; }
/* ============================================================
APP SHELL — narrow rail, wide canvas
============================================================ */
.shell {
display: grid;
grid-template-columns: 76px 1fr;
min-height: 100vh;
}
/* RAIL — icon-only, tactile */
.rail {
background: var(--surface);
border-right: 1px solid var(--rule);
display: flex; flex-direction: column;
padding: 18px 12px;
align-items: center;
gap: 4px;
position: sticky; top: 0;
height: 100vh;
}
.rail__logo {
width: 44px; height: 44px;
border-radius: 14px;
background: var(--tile-ink);
color: var(--tile-ink-fg);
display: grid; place-items: center;
font-family: 'Manrope', sans-serif;
font-weight: 800;
font-size: 19px;
letter-spacing: -0.06em;
margin-bottom: 16px;
box-shadow: 0 6px 16px -8px rgba(0,0,0,0.3);
}
.rail__btn {
width: 44px; height: 44px;
border-radius: 14px;
border: 0; background: transparent;
color: var(--ink-dim);
display: grid; place-items: center;
cursor: pointer;
transition: all .15s;
position: relative;
}
.rail__btn svg { width: 20px; height: 20px; }
.rail__btn:hover { background: var(--bg); color: var(--ink); }
.rail__btn.is-active {
background: var(--ink);
color: var(--surface);
}
.rail__btn .badge {
position: absolute;
top: 4px; right: 4px;
min-width: 16px; height: 16px;
padding: 0 4px;
border-radius: 8px;
background: var(--tile-coral);
color: white;
font-size: 9px; font-weight: 700;
display: grid; place-items: center;
border: 2px solid var(--surface);
}
.rail__sep {
width: 24px; height: 1px;
background: var(--rule);
margin: 8px 0;
}
.rail__foot {
margin-top: auto;
display: flex; flex-direction: column; gap: 4px;
align-items: center;
}
.rail__avatar {
width: 40px; height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--tile-violet), var(--tile-coral));
color: white;
display: grid; place-items: center;
font-weight: 700;
font-size: 14px;
border: 3px solid var(--surface);
box-shadow: 0 0 0 1px var(--rule);
}
/* ============================================================
MAIN CANVAS
============================================================ */
.main { padding: 28px 36px 60px; max-width: 1480px; }
/* TOP BAR */
.topbar {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 28px;
}
.crumb {
font-size: 13px;
color: var(--mute);
font-weight: 500;
}
.crumb b { color: var(--ink); font-weight: 600; }
.topbar .grow { flex: 1; }
.search {
display: flex; align-items: center; gap: 10px;
background: var(--surface);
border: 1px solid var(--rule);
border-radius: 14px;
padding: 9px 14px;
color: var(--mute);
font-size: 13px;
width: 280px;
cursor: text;
transition: border-color .15s;
}
.search:hover { border-color: var(--rule-strong); }
.search svg { width: 15px; height: 15px; }
.search .kbd {
margin-left: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
padding: 2px 6px;
border-radius: 6px;
background: var(--bg);
color: var(--ink-dim);
}
.icon-btn {
width: 40px; height: 40px;
border-radius: 12px;
background: var(--surface);
border: 1px solid var(--rule);
color: var(--ink-dim);
display: grid; place-items: center;
cursor: pointer;
transition: all .15s;
}
.icon-btn:hover { border-color: var(--rule-strong); color: var(--ink); }
.icon-btn svg { width: 16px; height: 16px; }
.new-btn {
display: inline-flex; align-items: center; gap: 8px;
height: 40px; padding: 0 18px;
border-radius: 12px;
background: var(--ink); color: var(--surface);
border: 0;
font-family: inherit;
font-size: 13px; font-weight: 600;
cursor: pointer;
transition: transform .15s;
}
.new-btn:hover { transform: translateY(-1px); }
.new-btn svg { width: 14px; height: 14px; }
/* HERO TILE */
.hero {
background: var(--ink);
color: var(--surface);
border-radius: 28px;
padding: 36px 40px 32px;
margin-bottom: 16px;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
right: -120px; top: -120px;
width: 460px; height: 460px;
border-radius: 50%;
background: radial-gradient(circle, var(--tile-mint) 0%, transparent 60%);
opacity: 0.45;
filter: blur(20px);
}
.hero__row {
display: grid;
grid-template-columns: 1fr auto;
align-items: end; gap: 28px;
position: relative;
}
.hero__kicker {
display: inline-flex; align-items: center; gap: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.18em;
background: rgba(255,255,255,0.1);
padding: 6px 12px;
border-radius: 999px;
color: var(--tile-mint);
margin-bottom: 22px;
}
.hero__kicker .dot {
width: 7px; height: 7px;
border-radius: 50%;
background: var(--tile-mint);
animation: ping 1.4s ease-in-out infinite;
box-shadow: 0 0 8px var(--tile-mint);
}
@keyframes ping {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.5); opacity: 0.5; }
}
.hero__title {
font-size: 72px;
font-weight: 700;
line-height: 0.96;
letter-spacing: -0.04em;
margin: 0 0 16px;
color: var(--surface);
max-width: 12ch;
}
.hero__title em {
font-style: normal;
color: var(--tile-mint);
}
.hero__sub {
font-size: 16px;
color: rgba(255,255,255,0.72);
max-width: 480px;
line-height: 1.5;
font-weight: 400;
}
.hero__sub b { color: var(--surface); font-weight: 600; }
.hero__big {
text-align: right;
}
.hero__big-label {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.16em;
color: rgba(255,255,255,0.55);
margin-bottom: 8px;
}
.hero__big-num {
font-size: 96px;
font-weight: 700;
letter-spacing: -0.05em;
color: var(--tile-mint);
line-height: 0.9;
font-variant-numeric: tabular-nums;
}
.hero__big-row {
margin-top: 14px;
display: flex; gap: 10px;
justify-content: flex-end;
}
.hero-pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 5px 12px;
border-radius: 999px;
background: rgba(255,255,255,0.1);
font-size: 12px;
color: rgba(255,255,255,0.85);
}
.hero-pill b {
color: var(--surface); font-weight: 600;
font-variant-numeric: tabular-nums;
}
/* ============================================================
BENTO GRID — 12 columns, tiles span as needed
============================================================ */
.bento {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: minmax(140px, auto);
gap: 16px;
}
.tile {
border-radius: 24px;
padding: 22px 24px;
position: relative;
overflow: hidden;
display: flex; flex-direction: column;
transition: transform .25s cubic-bezier(.4,.4,0,1);
}
.tile:hover { transform: translateY(-3px); }
.tile--surface { background: var(--surface); border: 1px solid var(--rule); color: var(--ink); }
.tile--violet { background: var(--tile-violet); color: var(--tile-violet-fg); }
.tile--mint { background: var(--tile-mint); color: var(--tile-mint-fg); }
.tile--coral { background: var(--tile-coral); color: var(--tile-coral-fg); }
.tile--honey { background: var(--tile-honey); color: var(--tile-honey-fg); }
.tile--cobalt { background: var(--tile-cobalt); color: var(--tile-cobalt-fg); }
.tile--rose { background: var(--tile-rose); color: var(--tile-rose-fg); }
.tile--ink { background: var(--tile-ink); color: var(--tile-ink-fg); }
.tile--bone { background: var(--tile-bone); color: var(--tile-bone-fg); border: 1px solid var(--rule); }
.tile__head {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 18px;
}
.tile__label {
font-family: 'JetBrains Mono', monospace;
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.16em;
opacity: 0.7;
font-weight: 500;
}
.tile__icon-circle {
width: 36px; height: 36px;
border-radius: 12px;
background: rgba(0,0,0,0.08);
display: grid; place-items: center;
}
.tile--violet .tile__icon-circle, .tile--coral .tile__icon-circle, .tile--cobalt .tile__icon-circle, .tile--ink .tile__icon-circle {
background: rgba(255,255,255,0.15);
}
.tile__icon-circle svg { width: 18px; height: 18px; }
.tile__num {
font-size: 56px;
font-weight: 700;
letter-spacing: -0.04em;
line-height: 0.95;
font-variant-numeric: tabular-nums;
margin-bottom: 6px;
margin-top: auto;
}
.tile__num .frac { font-size: 28px; opacity: 0.55; font-weight: 600; }
.tile__caption {
font-size: 13px;
line-height: 1.4;
opacity: 0.85;
margin-top: 4px;
}
.tile__caption b { font-weight: 700; opacity: 1; }
.tile__delta {
display: inline-flex; align-items: center; gap: 4px;
font-size: 11.5px;
font-weight: 600;
padding: 3px 9px;
border-radius: 999px;
background: rgba(0,0,0,0.10);
font-variant-numeric: tabular-nums;
font-family: 'JetBrains Mono', monospace;
}
.tile--violet .tile__delta, .tile--coral .tile__delta, .tile--cobalt .tile__delta, .tile--ink .tile__delta {
background: rgba(255,255,255,0.2);
}
/* SPANS */
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; }
.span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }
.row-2 { grid-row: span 2; }
.row-3 { grid-row: span 3; }
/* SIGNAL TILE — large, surface */
.signal-tile { padding: 0; overflow: hidden; }
.signal-tile__head {
padding: 22px 26px 14px;
display: flex; align-items: center; justify-content: space-between;
}
.signal-tile__title {
font-size: 22px;
font-weight: 700;
letter-spacing: -0.025em;
margin: 0;
}
.signal-tile__title small {
font-weight: 500;
color: var(--mute);
font-size: 13px;
margin-left: 8px;
}
.seg {
display: inline-flex; padding: 3px;
background: var(--bg);
border-radius: 12px;
gap: 2px;
}
.seg button {
padding: 6px 12px;
border: 0; background: transparent;
color: var(--ink-dim);
font-family: inherit;
font-size: 12px; font-weight: 600;
border-radius: 9px;
cursor: pointer;
transition: all .15s;
}
.seg button.is-active {
background: var(--surface);
color: var(--ink);
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
}
.signal-list { padding: 0 0 4px; }
.signal {
display: grid;
grid-template-columns: 40px 1fr auto;
gap: 14px;
padding: 14px 26px;
align-items: center;
cursor: pointer;
transition: background .15s;
}
.signal + .signal { border-top: 1px solid var(--rule); }
.signal:hover { background: var(--bg); }
.signal__avatar {
width: 40px; height: 40px;
border-radius: 12px;
display: grid; place-items: center;
background: var(--avatar-bg, var(--tile-violet));
color: var(--avatar-fg, white);
}
.signal__avatar svg { width: 20px; height: 20px; }
.signal__head {
font-size: 14.5px;
line-height: 1.4;
color: var(--ink);
font-weight: 500;
}
.signal__head b { font-weight: 700; }
.signal__head .v { color: var(--ink-dim); font-weight: 400; }
.signal__sub {
margin-top: 4px;
font-size: 12px;
color: var(--mute);
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.signal__sub .ch {
color: var(--ink); font-weight: 500;
background: var(--bg);
padding: 2px 7px;
border-radius: 6px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
}
.signal__sub .arrow { color: var(--mute); }
.signal__when {
text-align: right;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--mute);
}
.signal__when b {
display: block;
color: var(--ink);
font-size: 13px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
/* PROVIDER TILES — bold-color blocks */
.prov-tile {
cursor: pointer;
}
.prov-tile__name {
font-size: 18px;
font-weight: 700;
letter-spacing: -0.015em;
display: flex; align-items: center; gap: 8px;
margin-bottom: 4px;
}
.prov-tile__name .pulse {
width: 8px; height: 8px;
border-radius: 50%;
background: currentColor;
animation: ping 1.6s ease-in-out infinite;
}
.prov-tile__sub {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
opacity: 0.65;
margin-bottom: auto;
}
.prov-tile__num {
font-size: 36px;
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1;
font-variant-numeric: tabular-nums;
margin-top: 16px;
}
.prov-tile__num small {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.12em;
font-weight: 600;
opacity: 0.65;
margin-left: 6px;
}
/* WAVE / CHART TILE */
.chart-tile { padding-bottom: 12px; }
.chart-tile .tile__head { margin-bottom: 8px; }
.chart-svg {
width: 100%; height: 180px; display: block;
margin-top: 8px;
}
.chart-legend {
display: flex; gap: 18px;
margin-top: 4px;
font-size: 11px; color: var(--ink-dim); font-weight: 500;
}
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i {
display: inline-block; width: 14px; height: 3px; border-radius: 2px;
}
/* WIRES TILE */
.wires-tile { padding: 22px 26px; }
.wires-tile .tile__head { margin-bottom: 8px; }
.wire {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 10px;
align-items: center;
padding: 10px 0;
}
.wire + .wire { border-top: 1px solid var(--rule); }
.wire__from, .wire__to { font-size: 13px; }
.wire__to { text-align: right; }
.wire__name { color: var(--ink); font-weight: 600; }
.wire__sub { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: var(--mute); margin-top: 2px; }
.wire__count {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 600;
color: var(--ink);
background: var(--bg);
padding: 4px 10px;
border-radius: 999px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* COMPOSE TILE — full width, surface dark */
.compose-tile {
padding: 32px 36px;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 28px;
}
.compose-tile__title {
font-size: 36px;
font-weight: 700;
letter-spacing: -0.035em;
line-height: 1.05;
margin: 0 0 8px;
}
.compose-tile__title em { font-style: normal; color: var(--tile-mint); }
.compose-tile__sub {
font-size: 15px;
opacity: 0.7;
max-width: 540px;
line-height: 1.5;
}
.compose-tile__cta { display: flex; gap: 10px; }
.ghost-btn-dark {
display: inline-flex; align-items: center; gap: 8px;
padding: 0 18px; height: 44px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.18);
background: rgba(255,255,255,0.06);
color: var(--surface);
font-family: inherit;
font-size: 13.5px; font-weight: 600;
cursor: pointer;
transition: all .15s;
}
.ghost-btn-dark:hover { background: rgba(255,255,255,0.14); }
.primary-btn-light {
display: inline-flex; align-items: center; gap: 8px;
padding: 0 22px; height: 44px;
border-radius: 14px;
background: var(--tile-mint);
color: var(--tile-mint-fg);
border: 0;
font-family: inherit;
font-size: 13.5px; font-weight: 700;
cursor: pointer;
transition: transform .15s;
}
.primary-btn-light:hover { transform: translateY(-1px); }
/* HEALTH TILE — small numerical readout */
.health-rows { display: flex; flex-direction: column; gap: 10px; margin-top: auto; }
.health-row {
display: flex; justify-content: space-between; align-items: center;
font-size: 13px;
}
.health-row .key { color: var(--ink-dim); font-weight: 500; }
.health-row .val {
font-family: 'JetBrains Mono', monospace;
font-weight: 600; font-variant-numeric: tabular-nums;
color: var(--ink);
}
.health-row .val .dot {
display: inline-block; width: 7px; height: 7px;
border-radius: 50%; margin-right: 6px;
background: var(--ok);
}
/* CHANNELS TILE — channel chips */
.channels-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px;
margin-top: auto;
}
.channel-chip {
display: flex; align-items: center; gap: 9px;
padding: 10px 12px;
border-radius: 12px;
background: rgba(255,255,255,0.12);
font-size: 12.5px;
font-weight: 600;
}
.tile--bone .channel-chip, .tile--mint .channel-chip, .tile--honey .channel-chip, .tile--rose .channel-chip {
background: rgba(0,0,0,0.06);
}
.channel-chip svg { width: 14px; height: 14px; }
.channel-chip small {
margin-left: auto;
font-family: 'JetBrains Mono', monospace;
font-weight: 500; opacity: 0.7;
font-variant-numeric: tabular-nums;
}
/* THEME TOGGLE */
.theme-toggle {
position: fixed; right: 24px; top: 24px; z-index: 50;
background: var(--surface);
border: 1px solid var(--rule);
border-radius: 999px;
padding: 4px;
display: inline-flex;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.theme-toggle button {
padding: 7px 14px;
border: 0; background: transparent;
color: var(--ink-dim);
font-family: inherit;
font-size: 11.5px; font-weight: 600;
border-radius: 999px;
cursor: pointer;
}
.theme-toggle button.is-active {
background: var(--ink); color: var(--surface);
}
/* Reveal */
@keyframes rise {
from { opacity: 0; transform: translateY(14px); }
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:.10s}
.stagger > *:nth-child(3){animation-delay:.16s}
.stagger > *:nth-child(4){animation-delay:.22s}
.stagger > *:nth-child(5){animation-delay:.28s}
.stagger > *:nth-child(6){animation-delay:.34s}
.stagger > *:nth-child(7){animation-delay:.40s}
.stagger > *:nth-child(8){animation-delay:.46s}
.stagger > *:nth-child(9){animation-delay:.52s}
.stagger > *:nth-child(10){animation-delay:.58s}
.stagger > *:nth-child(11){animation-delay:.64s}
.stagger > *:nth-child(12){animation-delay:.70s}
/* Responsive */
@media (max-width: 1240px) {
.span-3 { grid-column: span 4; }
.span-4 { grid-column: span 6; }
.span-5 { grid-column: span 6; }
.span-7 { grid-column: span 12; }
.span-8 { grid-column: span 12; }
}
@media (max-width: 820px) {
.shell { grid-template-columns: 1fr; }
.rail { position: static; height: auto; flex-direction: row; padding: 12px 16px; gap: 6px; }
.rail__sep { width: 1px; height: 24px; margin: 0 4px; }
.rail__foot { margin-top: 0; margin-left: auto; flex-direction: row; }
.main { padding: 18px; }
.hero { padding: 24px 20px; }
.hero__row { grid-template-columns: 1fr; }
.hero__big { text-align: left; }
.hero__big-row { justify-content: flex-start; }
.hero__title { font-size: 44px; }
.hero__big-num { font-size: 64px; }
.span-3, .span-4, .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
}
</style>
</head>
<body>
<div class="theme-toggle">
<button id="t-light" class="is-active" onclick="setTheme('light')">Daylight</button>
<button id="t-dark" onclick="setTheme('dark')">Midnight</button>
</div>
<div class="shell">
<!-- RAIL -->
<aside class="rail">
<div class="rail__logo">N</div>
<button class="rail__btn is-active" title="Signal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M3 12h4l3-9 4 18 3-9h4"/></svg>
</button>
<button class="rail__btn" title="Providers">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>
</button>
<div class="rail__sep"></div>
<button class="rail__btn" title="Trackers">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="8"/><path d="M12 4v3M12 17v3M4 12h3M17 12h3"/></svg>
</button>
<button class="rail__btn" title="Templates">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
</button>
<button class="rail__btn" title="Targets">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><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>
<span class="badge">2</span>
</button>
<button class="rail__btn" title="Actions">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M5 12l3 3 6-6 6 6"/></svg>
</button>
<div class="rail__sep"></div>
<button class="rail__btn" title="Bots">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="4" y="4" width="16" height="16" rx="3"/><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>
</button>
<button class="rail__btn" title="Commands">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 18l5-5 5 5 5-9"/></svg>
</button>
<div class="rail__foot">
<button class="rail__btn" title="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
</button>
<div class="rail__avatar">A</div>
</div>
</aside>
<!-- MAIN -->
<main class="main">
<!-- TOP BAR -->
<div class="topbar">
<div class="crumb"><b>Control</b> · Signal overview</div>
<div class="grow"></div>
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
Search
<span class="kbd">⌘K</span>
</div>
<button class="icon-btn" title="Notifications">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10 21a2 2 0 0 0 4 0"/></svg>
</button>
<button class="new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M5 12h14M12 5v14"/></svg>
New tracker
</button>
</div>
<!-- HERO -->
<section class="hero">
<div class="hero__row">
<div>
<div class="hero__kicker"><span class="dot"></span>Live · all systems nominal</div>
<h1 class="hero__title">Tonight, <em>everything</em><br>is flowing.</h1>
<p class="hero__sub">
Four providers are listening, twelve trackers are armed, and the bridge has dispatched
<b>2 814 messages</b> across nineteen targets in the last 24 hours.
</p>
</div>
<div class="hero__big">
<div class="hero__big-label">throughput · 24h</div>
<div class="hero__big-num">2 814</div>
<div class="hero__big-row">
<span class="hero-pill"><b>99.7%</b> delivered</span>
<span class="hero-pill"><b>148ms</b> p50</span>
</div>
</div>
</div>
</section>
<!-- BENTO -->
<section class="bento stagger">
<!-- Row 1: 4 stat tiles -->
<div class="tile tile--violet span-3">
<div class="tile__head">
<span class="tile__label">Providers</span>
<span class="tile__delta">+1</span>
</div>
<div class="tile__num">04</div>
<div class="tile__caption">Immich · Gitea · GitHub · RSS</div>
</div>
<div class="tile tile--mint span-3">
<div class="tile__head">
<span class="tile__label">Trackers armed</span>
<span class="tile__delta">+3 24h</span>
</div>
<div class="tile__num">12<span class="frac">/14</span></div>
<div class="tile__caption"><b>2 paused</b> awaiting credentials</div>
</div>
<div class="tile tile--honey span-3">
<div class="tile__head">
<span class="tile__label">Targets</span>
<span class="tile__delta">+2 wk</span>
</div>
<div class="tile__num">19</div>
<div class="tile__caption">5 channels · TG · Matrix · Mail · ntfy · Discord</div>
</div>
<div class="tile tile--coral span-3">
<div class="tile__head">
<span class="tile__label">Failures · 24h</span>
<span class="tile__delta">4</span>
</div>
<div class="tile__num">02</div>
<div class="tile__caption">Auto-recovered · SMTP backoff</div>
</div>
<!-- Row 2 — Signal stream (large) + Providers stack (right) -->
<div class="tile tile--surface signal-tile span-7 row-3">
<div class="signal-tile__head">
<h2 class="signal-tile__title">Signal stream <small>2 814 events / 24h</small></h2>
<div class="seg">
<button class="is-active">All</button>
<button>Assets</button>
<button>Cmds</button>
<button>Fails</button>
</div>
</div>
<div class="signal-list">
<div class="signal" style="--avatar-bg: var(--tile-violet); --avatar-fg: white;">
<div class="signal__avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><rect x="3" y="5" width="18" height="14" rx="2.5"/><circle cx="9" cy="11" r="2"/><path d="M3 17l5-5 4 4 3-3 6 6"/></svg>
</div>
<div>
<div class="signal__head"><span class="v">Immich added</span> <b>14 assets</b> <span class="v">to</span> <b>«Семейный 2025»</b></div>
<div class="signal__sub"><span class="ch">family-photos</span><span class="arrow"></span><span class="ch">@family · telegram</span><span class="arrow"></span><span class="ch">assets_added.ru</span></div>
</div>
<div class="signal__when"><b>02:14</b>just now</div>
</div>
<div class="signal" style="--avatar-bg: var(--tile-mint); --avatar-fg: var(--tile-mint-fg);">
<div class="signal__avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="12" cy="12" r="9"/><path d="M9 8l3-3 3 3M9 16l3 3 3-3M5 12h14"/></svg>
</div>
<div>
<div class="signal__head"><span class="v">Gitea pushed</span> <b>3 commits</b> <span class="v">to</span> <b>notify-bridge/main</b></div>
<div class="signal__sub"><span class="ch">repo-changes</span><span class="arrow"></span><span class="ch">#dev-room · matrix</span></div>
</div>
<div class="signal__when"><b>02:13</b>17s ago</div>
</div>
<div class="signal" style="--avatar-bg: var(--tile-cobalt); --avatar-fg: white;">
<div class="signal__avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/></svg>
</div>
<div>
<div class="signal__head"><span class="v">Telegram bot received</span> <b>/recent</b> <span class="v">from</span> <b>@alex</b></div>
<div class="signal__sub"><span class="ch">@notifybridge_bot</span><span class="arrow"></span><span class="ch">resolved 5 · 142ms</span></div>
</div>
<div class="signal__when"><b>02:13</b>36s ago</div>
</div>
<div class="signal" style="--avatar-bg: var(--tile-honey); --avatar-fg: var(--tile-honey-fg);">
<div class="signal__avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.5 13.5l7 4M8.5 10.5l7-4"/></svg>
</div>
<div>
<div class="signal__head"><span class="v">Immich shared link</span> <b>expired</b> <span class="v">on</span> <b>«Прага 24»</b></div>
<div class="signal__sub"><span class="ch">link-watch</span><span class="arrow"></span><span class="ch">photos@dolgolyov.dev · email</span></div>
</div>
<div class="signal__when"><b>02:12</b>1m ago</div>
</div>
<div class="signal" style="--avatar-bg: var(--tile-coral); --avatar-fg: white;">
<div class="signal__avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M22 4H2l8 9v7l4 2v-9z"/></svg>
</div>
<div>
<div class="signal__head"><span class="v">Email</span> · <b>SMTP retry</b> <span class="v">on</span> <b>smtp.fastmail.com</b></div>
<div class="signal__sub"><span class="ch">backoff 2.4s</span><span class="arrow"></span><span class="ch">delivered on attempt 2/3</span></div>
</div>
<div class="signal__when"><b>02:11</b>3m ago</div>
</div>
<div class="signal" style="--avatar-bg: var(--tile-rose); --avatar-fg: var(--tile-rose-fg);">
<div class="signal__avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M3 9h18M8 14h2M14 14h2"/></svg>
</div>
<div>
<div class="signal__head"><span class="v">Action</span> <b>memory_dispatch</b> <span class="v">ran for</span> <b>«Один год назад»</b></div>
<div class="signal__sub"><span class="ch">6 recipients</span><span class="arrow"></span><span class="ch">2 telegram · 3 matrix · 1 ntfy</span></div>
</div>
<div class="signal__when"><b>02:09</b>4m ago</div>
</div>
</div>
</div>
<!-- Provider tiles - right column -->
<div class="tile tile--cobalt prov-tile span-5">
<div class="tile__head">
<span class="tile__label">Top provider</span>
<div class="tile__icon-circle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><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="prov-tile__name"><span class="pulse"></span>Immich</div>
<div class="prov-tile__sub">photos.dolgolyov.dev · 8 trackers</div>
<div class="prov-tile__num">1 942<small>events 24h</small></div>
</div>
<div class="tile tile--bone prov-tile span-3">
<div class="tile__head">
<span class="tile__label">Provider</span>
<div class="tile__icon-circle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M9 8l3-3 3 3M9 16l3 3 3-3M5 12h14"/></svg>
</div>
</div>
<div class="prov-tile__name"><span class="pulse" style="color: var(--ok)"></span>Gitea</div>
<div class="prov-tile__sub">git.dolgolyov-family.by</div>
<div class="prov-tile__num">368<small>24h</small></div>
</div>
<div class="tile tile--bone prov-tile span-2" style="grid-column: span 2;">
<div class="tile__head">
<span class="tile__label">RSS</span>
</div>
<div class="prov-tile__name" style="font-size: 14px"><span class="pulse" style="color: var(--mute); animation: none; opacity: 0.5"></span>7 feeds</div>
<div class="prov-tile__sub">idle 14m</div>
<div class="prov-tile__num" style="font-size: 28px">416</div>
</div>
<!-- Row 3 — chart wide, health, channels -->
<div class="tile tile--surface chart-tile span-7">
<div class="tile__head">
<span class="tile__label">Pulse · 7 days</span>
<div class="seg">
<button>24h</button>
<button class="is-active">7d</button>
<button>30d</button>
</div>
</div>
<svg class="chart-svg" viewBox="0 0 700 180" preserveAspectRatio="none">
<defs>
<linearGradient id="bg1" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#6d4ce6" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#6d4ce6" stop-opacity="0"/>
</linearGradient>
<linearGradient id="bg2" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#ff6f5b" stop-opacity="0.35"/>
<stop offset="100%" stop-color="#ff6f5b" stop-opacity="0"/>
</linearGradient>
</defs>
<path fill="url(#bg1)" d="M0 140 C 70 100, 140 110, 200 80 S 320 60, 380 90 S 500 50, 570 70 S 660 100, 700 80 L 700 180 L 0 180 Z"/>
<path fill="none" stroke="#6d4ce6" stroke-width="2.5" d="M0 140 C 70 100, 140 110, 200 80 S 320 60, 380 90 S 500 50, 570 70 S 660 100, 700 80"/>
<path fill="url(#bg2)" d="M0 160 C 80 140, 160 145, 240 130 S 360 120, 420 135 S 540 110, 610 125 S 680 145, 700 135 L 700 180 L 0 180 Z"/>
<path fill="none" stroke="#ff6f5b" stroke-width="2.5" d="M0 160 C 80 140, 160 145, 240 130 S 360 120, 420 135 S 540 110, 610 125 S 680 145, 700 135"/>
<line x1="500" y1="20" x2="500" y2="180" stroke="rgba(0,0,0,0.12)" stroke-dasharray="3 3"/>
<circle cx="500" cy="62" r="6" fill="#6d4ce6" stroke="white" stroke-width="2"/>
<text x="510" y="44" fill="#0c0d11" font-size="11" font-weight="600" font-family="JetBrains Mono">Sun 21:00 · 312/h</text>
</svg>
<div class="chart-legend">
<span><i style="background:#6d4ce6"></i>Assets · 1 942</span>
<span><i style="background:#ff6f5b"></i>Commands · 488</span>
<span><i style="background:#1f9d56"></i>Sharing · 312</span>
</div>
</div>
<!-- Health tile -->
<div class="tile tile--ink span-2" style="grid-column: span 2;">
<div class="tile__head">
<span class="tile__label">Health</span>
<div class="tile__icon-circle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 12h-4l-3 9-6-18-3 9H2"/></svg>
</div>
</div>
<div class="health-rows">
<div class="health-row"><span class="key">Uptime</span><span class="val">14d 06h</span></div>
<div class="health-row"><span class="key">Queue</span><span class="val">000</span></div>
<div class="health-row"><span class="key">DB</span><span class="val"><span class="dot"></span>OK</span></div>
<div class="health-row"><span class="key">SMTP</span><span class="val"><span class="dot"></span>OK</span></div>
<div class="health-row"><span class="key">Web</span><span class="val"><span class="dot"></span>OK</span></div>
</div>
</div>
<!-- Channels tile -->
<div class="tile tile--rose span-3">
<div class="tile__head">
<span class="tile__label">Channels live</span>
<div class="tile__icon-circle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><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>
</div>
</div>
<div class="channels-grid">
<div class="channel-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/></svg>
Telegram <small>9</small>
</div>
<div class="channel-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
Matrix <small>4</small>
</div>
<div class="channel-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M4 4h16v16H4z"/><path d="M4 4l8 8 8-8"/></svg>
Email <small>3</small>
</div>
<div class="channel-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M12 2a7 7 0 0 0-7 7v5l-2 3h18l-2-3V9a7 7 0 0 0-7-7z"/></svg>
ntfy <small>2</small>
</div>
<div class="channel-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="12" cy="12" r="9"/></svg>
Discord <small>1</small>
</div>
<div class="channel-chip" style="opacity: 0.55">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M12 5v14M5 12h14"/></svg>
Add
</div>
</div>
</div>
<!-- Compose tile — full width -->
<div class="tile tile--ink compose-tile span-12">
<div>
<h3 class="compose-tile__title">Pick a source. Choose a channel. <em>Compose the wire.</em></h3>
<p class="compose-tile__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-tile__cta">
<button class="ghost-btn-dark">
<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 JSON
</button>
<button class="primary-btn-light">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
Start composing
</button>
</div>
</div>
</section>
</main>
</div>
<script>
function setTheme(t) {
document.documentElement.setAttribute('data-theme', t);
document.getElementById('t-light').classList.toggle('is-active', t === 'light');
document.getElementById('t-dark').classList.toggle('is-active', t === 'dark');
}
</script>
</body>
</html>