*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --border: rgba(56,189,248,0.11); --border-h: rgba(56,189,248,0.26); --accent-out: #38bdf8; --accent-in: #f472b6; --blocked: #cc2200; --text: #dde6f0; --text-dim: #445566; --mono: 'JetBrains Mono', monospace; --ui: 'Space Grotesk', sans-serif; --green: #4ade80; --yellow: #fbbf24; --panel-bg: rgba(4,8,16,0.96); } body { color:var(--text); font-family:var(--ui); overflow:hidden; height:100vh; width:100vw; } ::-webkit-scrollbar { width:3px; } ::-webkit-scrollbar-track { background:transparent; } ::-webkit-scrollbar-thumb { background:var(--border); border-radius:2px; } .panel { background:var(--panel-bg); border:1px solid var(--border); border-radius:12px; backdrop-filter:blur(20px); } button { font-family:var(--ui); cursor:pointer; border:none; outline:none; } .mono { font-family:var(--mono); } /* ── Layout ── */ .app { position:relative; width:100vw; height:100vh; overflow:hidden; } .world-svg { position:absolute; inset:0; width:100%; height:100%; display:block; } /* ── Zoom ── */ .zoom-controls { position:absolute; bottom:72px; right:20px; display:flex; flex-direction:column; gap:4px; z-index:20; } .zoom-controls button { width:32px; height:32px; background:var(--panel-bg); border:1px solid var(--border); color:var(--text-dim); border-radius:8px; font-size:17px; display:flex; align-items:center; justify-content:center; transition:all 0.15s; } .zoom-controls button:hover { border-color:var(--border-h); color:var(--accent-out); } .zoom-reset { font-size:12px !important; } /* ── Clock ── */ .clock { position:absolute; top:14px; left:14px; padding:8px 14px; z-index:20; display:flex; flex-direction:column; gap:2px; } .clock-time { font-size:18px; font-weight:600; letter-spacing:0.05em; } .clock-date { font-size:10px; color:var(--text-dim); } /* ── Topbar ── */ .topbar { position:absolute; top:14px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:20px; padding:9px 18px; white-space:nowrap; z-index:20; } .topbar-left { display:flex; align-items:center; gap:12px; } .logo { display:flex; align-items:center; gap:7px; } .logo-icon { font-size:16px; color:var(--accent-out); filter:drop-shadow(0 0 5px var(--accent-out)); } .logo-text { font-size:14px; font-weight:600; letter-spacing:0.08em; } .conn-badge { display:flex; align-items:center; gap:5px; font-size:10px; font-weight:500; padding:2px 8px; border-radius:20px; } .conn-ok { background:rgba(74,222,128,0.08); color:var(--green); border:1px solid rgba(74,222,128,0.2); } .conn-err { background:rgba(248,113,113,0.08); color:#f87171; border:1px solid rgba(248,113,113,0.2); } .conn-dot { width:5px; height:5px; border-radius:50%; background:currentColor; animation:pulse 2s infinite; } .topbar-stats { display:flex; gap:16px; } .stat { display:flex; flex-direction:column; align-items:center; gap:1px; } .stat-label { font-size:8px; font-weight:500; letter-spacing:0.1em; color:var(--text-dim); text-transform:uppercase; } .stat-value { font-size:12px; font-weight:600; } .topbar-toggles { display:flex; gap:6px; align-items:center; } .toggle-btn { padding:4px 11px; border-radius:20px; font-size:11px; font-weight:500; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); transition:all 0.2s; } .toggle-btn:hover { color:var(--text); } .toggle-active { background:rgba(255,255,255,0.07) !important; } .origin-toggle { display:flex; border-radius:20px; overflow:hidden; border:1px solid var(--border); } .origin-toggle button { padding:4px 9px; font-size:10px; font-weight:500; background:rgba(255,255,255,0.04); color:var(--text-dim); border:none; border-left:1px solid var(--border); transition:all 0.15s; } .origin-toggle button:first-child { border-left:none; } .origin-toggle button:hover { color:var(--text); } .origin-toggle button.active { background:rgba(204,34,0,0.15); color:#ff6666; } .origin-count { display:inline-block; font-size:8px; font-family:var(--mono); background:rgba(255,255,255,0.1); border-radius:8px; padding:0 4px; margin-left:3px; } /* ── Left dock ── */ .left-dock { position:absolute; left:0; top:50%; transform:translateY(-50%); display:flex; flex-direction:column; gap:2px; background:var(--panel-bg); border:1px solid var(--border); border-left:none; border-radius:0 12px 12px 0; padding:6px 4px; z-index:20; } .dock-btn { display:flex; flex-direction:column; align-items:center; gap:3px; padding:10px 10px; border-radius:8px; background:transparent; color:var(--text-dim); transition:all 0.15s; min-width:54px; border:1px solid transparent; position:relative; } .dock-btn:hover { background:rgba(255,255,255,0.05); color:var(--text); } .dock-btn.dock-active { background:rgba(56,189,248,0.08); color:var(--accent-out); border-color:rgba(56,189,248,0.2); } .dock-icon { font-size:16px; line-height:1; } .dock-label { font-size:9px; font-weight:500; letter-spacing:0.04em; white-space:nowrap; } .dock-count { position:absolute; top:5px; right:5px; font-size:8px; font-family:var(--mono); background:rgba(56,189,248,0.2); color:var(--accent-out); border-radius:8px; padding:0 4px; } /* ── Panel stack ── */ .panel-stack { position:absolute; left:70px; top:74px; display:flex; flex-direction:row; gap:8px; height:calc(100vh - 88px); z-index:20; pointer-events:none; } .side-panel { width:272px; height:100%; display:flex; flex-direction:column; overflow:hidden; pointer-events:all; animation:fadeIn 0.15s ease; flex-shrink:0; } .side-panel-header { display:flex; align-items:center; gap:8px; padding:10px 12px; border-bottom:1px solid var(--border); flex-shrink:0; font-size:12px; font-weight:600; } .side-panel-header .tab-count { background:rgba(56,189,248,0.1); color:var(--accent-out); font-size:9px; padding:1px 5px; border-radius:8px; font-family:var(--mono); } .side-panel-header .close-btn { margin-left:auto; } /* ── Search ── */ .search-wrap { display:flex; align-items:center; gap:6px; margin:7px 8px 3px; padding:6px 9px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:7px; flex-shrink:0; transition:border-color 0.15s; } .search-wrap:focus-within { border-color:var(--border-h); } .search-icon { font-size:13px; color:var(--text-dim); flex-shrink:0; } .search-input { flex:1; background:none; border:none; outline:none; font-family:var(--ui); font-size:11px; color:var(--text); } .search-input::placeholder { color:var(--text-dim); } .search-clear { background:none; color:var(--text-dim); font-size:10px; } .search-clear:hover { color:var(--text); } /* ── Device list ── */ .device-list { overflow-y:auto; flex:1; padding:3px 8px 8px; display:flex; flex-direction:column; gap:2px; } .device-item { display:flex; align-items:center; gap:8px; padding:7px 8px; border-radius:7px; background:transparent; border:1px solid transparent; color:var(--text); text-align:left; width:100%; transition:all 0.12s; } .device-item:hover { background:rgba(255,255,255,0.03); border-color:var(--border); } .device-item.selected { background:rgba(56,189,248,0.07); border-color:rgba(56,189,248,0.28); } .all-devices { border-style:dashed; border-color:var(--border); margin-bottom:3px; } .all-devices.selected { border-style:solid; } .device-icon { font-size:15px; flex-shrink:0; } .device-info { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; } .device-name { font-size:11px; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .device-sub { font-size:9px; color:var(--text-dim); } .device-traffic { display:flex; flex-direction:column; align-items:flex-end; gap:1px; font-size:9px; font-family:var(--mono); flex-shrink:0; } /* ── Flow log ── */ .flow-log { overflow-y:auto; flex:1; padding:5px 8px; display:flex; flex-direction:column; gap:2px; } .flow-entry { display:flex; align-items:center; gap:7px; padding:5px 7px; border-radius:6px; background:rgba(255,255,255,0.02); border:1px solid transparent; font-size:11px; transition:background 0.1s; } .flow-entry:hover { background:rgba(255,255,255,0.05); } .flow-entry.flow-blocked { border-color:rgba(204,34,0,0.2); background:rgba(204,34,0,0.05); } .flow-dir { font-size:12px; font-weight:700; flex-shrink:0; width:13px; text-align:center; } .flow-dir.out { color:var(--accent-out); } .flow-dir.in { color:var(--accent-in); } .flow-dir.blocked { color:var(--blocked); } .flow-detail { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; } .flow-device { font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:11px; } .flow-dest { font-size:9px; color:var(--text-dim); } .flow-ip { font-size:8px; color:var(--text-dim); opacity:0.6; } .flow-bytes { font-size:9px; color:var(--text-dim); flex-shrink:0; font-family:var(--mono); } /* ── Top talkers ── */ .talkers-sub { font-size:9px; color:var(--text-dim); font-weight:400; } .talker-list { padding:8px; display:flex; flex-direction:column; gap:8px; overflow-y:auto; flex:1; } .talker-row { display:flex; align-items:center; gap:8px; } .talker-rank { font-size:10px; color:var(--text-dim); font-family:var(--mono); width:12px; flex-shrink:0; } .talker-info { flex:1; min-width:0; } .talker-name { font-size:11px; font-weight:500; display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:4px; } .talker-bar-wrap { height:2px; background:rgba(255,255,255,0.06); border-radius:1px; } .talker-bar { height:100%; background:var(--accent-out); border-radius:1px; transition:width 0.5s ease; } .talker-bytes { display:flex; flex-direction:column; align-items:flex-end; gap:1px; font-size:9px; font-family:var(--mono); flex-shrink:0; } /* ── Arc popup ── */ .arc-popup { position:absolute; top:74px; right:14px; width:240px; padding:13px; z-index:30; animation:fadeIn 0.15s ease; } .arc-popup-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:11px; } .arc-popup-dir { font-size:11px; font-weight:600; letter-spacing:0.05em; } .arc-row { display:flex; justify-content:space-between; align-items:baseline; padding:4px 0; border-bottom:1px solid rgba(255,255,255,0.04); font-size:11px; } .arc-row:last-child { border-bottom:none; } .arc-label { color:var(--text-dim); flex-shrink:0; margin-right:8px; } .arc-value { font-weight:500; text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:160px; } /* ── Device detail ── */ .device-detail { position:absolute; bottom:14px; right:14px; width:230px; padding:13px; z-index:20; animation:fadeIn 0.15s ease; } .detail-header { display:flex; align-items:center; gap:9px; margin-bottom:10px; } .detail-icon { font-size:20px; } .detail-name { font-size:12px; font-weight:600; } .detail-mac { font-size:9px; color:var(--text-dim); font-family:var(--mono); } .detail-grid { display:flex; flex-direction:column; gap:5px; } .detail-row { display:flex; justify-content:space-between; align-items:center; font-size:11px; } .detail-label { color:var(--text-dim); } .detail-value { font-weight:500; } /* ── Shared close btn ── */ .close-btn { background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); border-radius:5px; width:21px; height:21px; font-size:9px; display:flex; align-items:center; justify-content:center; transition:all 0.12s; flex-shrink:0; } .close-btn:hover { color:var(--text); } /* ── Color picker ── */ .color-picker { position:absolute; bottom:60px; right:14px; width:240px; padding:14px; z-index:30; animation:fadeIn 0.15s ease; } .picker-header { display:flex; align-items:center; gap:8px; margin-bottom:12px; } .picker-title { font-size:12px; font-weight:600; flex:1; } .picker-reset { font-size:10px; padding:3px 8px; border-radius:6px; background:rgba(255,255,255,0.06); border:1px solid var(--border); color:var(--text-dim); } .picker-reset:hover { color:var(--text); } .picker-rows { display:flex; flex-direction:column; gap:8px; margin-bottom:14px; } .color-row { display:flex; align-items:center; justify-content:space-between; } .color-label { font-size:11px; color:var(--text-dim); } .color-input-wrap { display:flex; align-items:center; gap:7px; } .color-native { width:28px; height:22px; padding:0; border:1px solid var(--border); border-radius:5px; background:none; cursor:pointer; } .color-native::-webkit-color-swatch-wrapper { padding:2px; } .color-native::-webkit-color-swatch { border:none; border-radius:3px; } .color-hex { font-size:10px; color:var(--text-dim); width:54px; } .picker-preset-label { font-size:9px; text-transform:uppercase; letter-spacing:0.1em; color:var(--text-dim); display:block; margin-bottom:7px; } .preset-btns { display:flex; flex-wrap:wrap; gap:5px; } .preset-btn { display:flex; align-items:center; gap:5px; padding:4px 9px; border-radius:6px; font-size:10px; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); transition:all 0.12s; } .preset-btn:hover { color:var(--text); border-color:var(--border-h); } .preset-swatch { width:8px; height:8px; border-radius:50%; flex-shrink:0; } /* ── Floating draggable panels ── */ .floating-panel { position: absolute; display: flex; flex-direction: column; overflow: hidden; z-index: 25; animation: fadeIn 0.15s ease; min-width: 220px; min-height: 160px; } .fp-header { display: flex; align-items: center; gap: 6px; padding: 9px 12px; border-bottom: 1px solid var(--border); cursor: grab; flex-shrink: 0; user-select: none; } .fp-header:active { cursor: grabbing; } .fp-title { font-size: 12px; font-weight: 600; flex: 1; /* takes all space, pushes everything else right */ } /* badge / sub-label next to title */ .fp-header .tab-count, .fp-header .talkers-sub { flex-shrink: 0; } /* export button — same height as close btn */ .export-btn-sm { font-size: 9px; padding: 0 8px; height: 21px; line-height: 1; border-radius: 5px; background: rgba(255,255,255,0.05); border: 1px solid var(--border); color: var(--text-dim); transition: all 0.15s; flex-shrink: 0; display: flex; align-items: center; } .export-btn-sm:hover { color: var(--text); } .fp-body { flex: 1; overflow: hidden; display: flex; flex-direction: column; } .fp-resize { position: absolute; bottom: 2px; right: 4px; font-size: 14px; color: var(--text-dim); cursor: se-resize; opacity: 0.4; line-height: 1; user-select: none; } .fp-resize:hover { opacity: 0.9; } /* fp-body children that need to scroll */ .fp-body .device-list, .fp-body .flow-log, .fp-body .talker-list { flex: 1; overflow-y: auto; } /* search inside floating panel */ .fp-body .search-wrap { flex-shrink: 0; } /* ── Protocol breakdown ── */ .proto-list { padding:10px 12px; display:flex; flex-direction:column; gap:10px; overflow-y:auto; flex:1; } .proto-row { display:flex; flex-direction:column; gap:4px; } .proto-name { font-size:12px; font-weight:600; } .proto-bar-wrap { height:4px; background:rgba(255,255,255,0.06); border-radius:2px; } .proto-bar { height:100%; border-radius:2px; transition:width 0.4s ease; } .proto-stats { display:flex; gap:10px; font-size:10px; color:var(--text-dim); } .proto-pct { min-width:36px; } .proto-bytes { font-family:var(--mono); } .proto-count { font-family:var(--mono); opacity:0.6; } /* ── Replay bar ── */ .replay-bar { position:absolute; bottom:20px; left:50%; transform:translateX(-50%); padding:10px 16px; z-index:30; min-width:480px; animation:fadeIn 0.15s ease; } .replay-header { display:flex; align-items:center; gap:12px; margin-bottom:8px; font-size:11px; } .replay-label { font-weight:600; color:var(--accent-out); } .replay-time { color:var(--text); } .replay-count { color:var(--text-dim); flex:1; } .replay-live { padding:3px 10px; border-radius:6px; background:rgba(56,189,248,0.1); border:1px solid rgba(56,189,248,0.3); color:var(--accent-out); font-size:10px; } .replay-live:hover { background:rgba(56,189,248,0.2); } .replay-controls { display:flex; align-items:center; gap:8px; } .replay-btn { width:28px; height:28px; border-radius:6px; background:rgba(255,255,255,0.06); border:1px solid var(--border); color:var(--text); font-size:13px; display:flex; align-items:center; justify-content:center; flex-shrink:0; } .replay-btn:hover { border-color:var(--border-h); color:var(--accent-out); } .replay-slider { flex:1; accent-color:var(--accent-out); cursor:pointer; } .replay-pos { font-size:10px; color:var(--text-dim); min-width:32px; text-align:right; } /* ── Dock divider ── */ .dock-divider { height:1px; background:var(--border); margin:4px 6px; } /* ── Heatmap legend ── */ .heatmap-legend { position:absolute; bottom:20px; right:70px; padding:10px 14px; z-index:20; min-width:180px; animation:fadeIn 0.15s ease; } .heatmap-title { font-size:10px; font-weight:600; color:var(--text-dim); text-transform:uppercase; letter-spacing:0.08em; margin-bottom:8px; } .heatmap-scale { display:flex; align-items:center; gap:8px; margin-bottom:6px; } .heatmap-min, .heatmap-max { font-size:10px; font-family:var(--mono); color:var(--text-dim); flex-shrink:0; } .heatmap-gradient { flex:1; height:6px; border-radius:3px; background: linear-gradient(to right, rgba(0,40,60,0.4), rgba(0,130,150,0.6), rgba(0,200,220,0.85)); } .heatmap-top { font-size:10px; color:var(--text-dim); } .heatmap-top strong { color:var(--accent-out); } .heatmap-hover { font-size:10px; color:var(--text); margin-top:4px; font-family:var(--mono); } /* ── Traffic timeline ── */ .timeline { position:absolute; bottom:0; left:0; right:0; padding:5px 14px 5px; z-index:15; border-radius:0; border-left:none; border-right:none; border-bottom:none; background:rgba(4,8,16,0.92); user-select:none; } .timeline-header { display:flex; align-items:center; gap:12px; margin-bottom:3px; } .timeline-label { font-size:9px; color:var(--text-dim); text-transform:uppercase; letter-spacing:0.08em; flex:1; } .timeline-stats { display:flex; gap:12px; font-size:9px; font-family:var(--mono); } .timeline-stat-out { color:var(--accent-out); } .timeline-stat-in { color:var(--accent-in); } .timeline-toggles { display:flex; gap:4px; } .timeline-toggle { padding:1px 7px; border-radius:10px; font-size:9px; font-weight:500; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); transition:all 0.15s; cursor:pointer; } .timeline-toggle:hover { color:var(--text); } .timeline-toggle.on-out { background:rgba(56,189,248,0.1); color:var(--accent-out); border-color:rgba(56,189,248,0.3); } .timeline-toggle.on-in { background:rgba(244,114,182,0.1); color:var(--accent-in); border-color:rgba(244,114,182,0.3); } .timeline-wrap { position:relative; } .timeline-svg { display:block; width:100%; cursor:crosshair; } .timeline-tooltip { position:absolute; top:-36px; transform:translateX(-50%); background:var(--panel-bg); border:1px solid var(--border); padding:3px 8px; border-radius:6px; font-size:9px; font-family:var(--mono); white-space:nowrap; pointer-events:none; z-index:5; } .timeline-cursor { position:absolute; top:0; bottom:0; width:1px; background:rgba(255,255,255,0.3); pointer-events:none; } /* ── Country filter badge ── */ .country-badge { position:absolute; top:74px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:10px; padding:6px 12px; z-index:25; font-size:11px; animation:fadeIn 0.15s ease; } .country-badge strong { color:var(--accent-out); } .country-badge-count { color:var(--text-dim); font-size:10px; } /* ── Device group dot ── */ .group-dot { display:inline-block; width:6px; height:6px; border-radius:50%; margin-right:5px; vertical-align:middle; flex-shrink:0; } /* ── Groups panel ── */ .groups-body { display:flex; flex-direction:column; flex:1; overflow:hidden; } .group-filters { display:flex; flex-wrap:wrap; gap:4px; padding:8px; flex-shrink:0; border-bottom:1px solid var(--border); } .group-filter-btn { padding:3px 10px; border-radius:20px; font-size:10px; font-weight:500; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); transition:all 0.15s; } .group-filter-btn:hover { color:var(--text); } .group-filter-btn.active { background:rgba(56,189,248,0.1); color:var(--accent-out); border-color:rgba(56,189,248,0.3); } .group-count { font-size:8px; font-family:var(--mono); background:rgba(255,255,255,0.1); border-radius:8px; padding:0 4px; margin-left:3px; } .group-device-list { overflow-y:auto; flex:1; padding:4px 8px; display:flex; flex-direction:column; gap:3px; } .group-device-row { display:flex; align-items:center; gap:8px; padding:5px 6px; border-radius:6px; transition:background 0.1s; } .group-device-row:hover { background:rgba(255,255,255,0.03); } .group-device-info { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; } .group-select { background:rgba(255,255,255,0.06); border:1px solid var(--border); color:var(--text-dim); border-radius:5px; font-size:9px; padding:2px 4px; cursor:pointer; flex-shrink:0; font-family:var(--ui); } .group-select:hover { border-color:var(--border-h); } .group-select option { background:#0d1829; } /* ── Misc ── */ .geoip-warning { position:absolute; bottom:14px; left:50%; transform:translateX(-50%); padding:7px 14px; font-size:11px; color:var(--yellow); border-color:rgba(251,191,36,0.15); z-index:20; white-space:nowrap; } .empty-state { color:var(--text-dim); font-size:11px; text-align:center; padding:24px 12px; }