html, body { height: 100%; margin: 0; } #map { height: 100%; } .hud { position: absolute; top: 10px; left: 10px; background: rgba(0, 0, 0, .65); color: #fff; padding: 10px 12px; border-radius: 10px; font-family: ui-sans-serif, system-ui, sans-serif; z-index: 999; width: min(340px, calc(100vw - 20px)); max-width: 340px; max-height: 90vh; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; } .hud, .prop-panel, .weather-panel, .peers-panel, .node-search-results { scrollbar-width: thin; scrollbar-color: rgba(148, 163, 184, .7) rgba(15, 23, 42, .35); } .hud::-webkit-scrollbar, .prop-panel::-webkit-scrollbar, .weather-panel::-webkit-scrollbar, .peers-panel::-webkit-scrollbar, .node-search-results::-webkit-scrollbar { width: 10px; } .hud::-webkit-scrollbar-track, .prop-panel::-webkit-scrollbar-track, .weather-panel::-webkit-scrollbar-track, .peers-panel::-webkit-scrollbar-track, .node-search-results::-webkit-scrollbar-track { background: rgba(15, 23, 42, .35); border-radius: 8px; } .hud::-webkit-scrollbar-thumb, .prop-panel::-webkit-scrollbar-thumb, .weather-panel::-webkit-scrollbar-thumb, .peers-panel::-webkit-scrollbar-thumb, .node-search-results::-webkit-scrollbar-thumb { background: rgba(148, 163, 184, .7); border-radius: 8px; border: 2px solid rgba(15, 23, 42, .35); } .hud::-webkit-scrollbar-thumb:hover, .prop-panel::-webkit-scrollbar-thumb:hover, .weather-panel::-webkit-scrollbar-thumb:hover, .peers-panel::-webkit-scrollbar-thumb:hover, .node-search-results::-webkit-scrollbar-thumb:hover { background: rgba(226, 232, 240, .85); } .hud-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .hud-brand { display: flex; align-items: center; gap: 8px; } .hud-toggle { display: inline-flex; align-items: center; justify-content: center; padding: 0; border: none; background: transparent; cursor: pointer; } .hud-logo { width: 28px; height: 28px; border-radius: 6px; object-fit: contain; background: rgba(255, 255, 255, .08); padding: 2px; } .hud.panel-hidden { background: transparent; border: none; padding: 0; width: auto; max-width: none; } .hud.panel-hidden .hud-body { display: none; } .hud.panel-hidden .hud-action { display: none; } .hud.panel-hidden .hud-title { display: none; } .hud-action { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 8px; background: rgba(255, 255, 255, .12); border: 1px solid rgba(255, 255, 255, .2); color: #fff; text-decoration: none; cursor: pointer; transition: background .2s ease, border-color .2s ease; } .hud-action:hover { background: rgba(255, 255, 255, .22); border-color: rgba(255, 255, 255, .4); } .hud-action svg { width: 16px; height: 16px; fill: currentColor; } .hud-action.copied { background: rgba(34, 197, 94, .35); border-color: rgba(34, 197, 94, .6); } .small { opacity: .85; font-size: 12px; } .popup-copy-location { padding: 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: left; text-decoration: underline; text-underline-offset: 2px; } .popup-copy-location:hover { opacity: 1; } .popup-copy-id { padding: 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: left; text-decoration: underline; text-underline-offset: 2px; } .popup-copy-id:hover { opacity: 1; } .popup-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } .popup-action-button { appearance: none; display: inline-flex; align-items: center; justify-content: center; padding: 6px 10px; border: 1px solid #2563eb; border-radius: 8px; background: #2563eb; color: #fff; font: inherit; font-size: 12px; line-height: 1.15; cursor: pointer; text-decoration: none; -webkit-appearance: none; } .popup-action-button:hover, .popup-action-button:visited, .popup-action-button:active, .popup-action-button:focus { color: #fff; opacity: .92; } .qr-modal[hidden] { display: none !important; } .qr-modal { position: fixed; inset: 0; z-index: 5000; display: flex; align-items: center; justify-content: center; padding: 20px; } .qr-modal-backdrop { position: absolute; inset: 0; background: rgba(2, 6, 23, 0.7); backdrop-filter: blur(2px); } .qr-modal-card { position: relative; z-index: 1; width: min(96vw, 440px); border-radius: 20px; padding: 20px 20px 18px; background: rgba(255, 255, 255, 0.98); color: #0f172a; box-shadow: 0 20px 60px rgba(15, 23, 42, 0.35); border: 1px solid rgba(15, 23, 42, 0.12); text-align: center; } .dark-map .qr-modal-card { background: rgba(15, 23, 42, 0.96); color: #f8fafc; border: 1px solid rgba(255, 255, 255, 0.16); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45); } .qr-modal-close { position: absolute; top: 10px; right: 10px; margin-top: 0; } .dark-map .qr-modal-close { color: #f8fafc; } .qr-modal-key { display: block; width: 100%; margin-top: 6px; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; } .qr-modal-key:hover { opacity: 1; } .qr-modal-image-wrap { margin-top: 12px; display: flex; justify-content: center; align-items: center; min-height: 320px; border-radius: 14px; background: linear-gradient(180deg, rgba(241, 245, 249, 0.95), rgba(226, 232, 240, 0.95)); border: 1px solid rgba(148, 163, 184, 0.35); padding: 20px; } .dark-map .qr-modal-image-wrap { background: rgba(255, 255, 255, 0.04); border-color: rgba(255, 255, 255, 0.12); } .qr-modal-image { display: block; width: min(100%, 360px); height: auto; image-rendering: pixelated; } .route-hash-link { color: inherit; text-decoration: underline; text-underline-offset: 2px; } .route-hash-link:hover { opacity: 1; } .pill { display: inline-block; padding: 2px 8px; border-radius: 999px; background: rgba(255, 255, 255, .12); margin-right: 6px; } .legend { margin-top: 8px; display: grid; gap: 6px; } .legend-item { display: flex; align-items: center; gap: 8px; font-size: 12px; opacity: 0.9; } .legend-line { width: 30px; height: 0; border-top: 4px solid; display: inline-block; } .legend-trace { border-color: #ff7a1a; border-top-style: dashed; } .legend-message { border-color: #2b8cff; border-top-style: dashed; } .legend-advert { border-color: #2ecc71; border-top-style: dotted; } .legend-history { border-color: #7dd3fc; border-top-style: solid; } .legend-history-swatch { width: 36px; height: 8px; border-radius: 6px; background: linear-gradient(90deg, #7dd3fc, #f59e0b, #ef4444); border: 1px solid rgba(255, 255, 255, .25); display: inline-block; } .legend-los-clear { border-color: #22c55e; border-top-style: solid; } .legend-los-blocked { border-color: #ef4444; border-top-style: dashed; } .legend-history-group { display: none; } .legend-history-group.active { display: block; } .legend-coverage-group { display: none; } .legend-coverage-group.active { display: block; } .legend-los-group { display: none; } .legend-los-group.active { display: block; } .legend-dot { width: 12px; height: 12px; border-radius: 50%; border: 2px solid; display: inline-block; } .legend-repeater { border-color: #1d4ed8; background: #2b8cff; } .legend-companion { border-color: #6b21a8; background: #a855f7; } .legend-room { border-color: #b45309; background: #f59e0b; } .legend-unknown { border-color: #4b5563; background: #d1d5db; } .legend-online { border-color: #22c55e; background: rgba(34, 197, 94, 0.15); } .legend-los-peak { border-color: #f59e0b; background: #fbbf24; } .legend-los-relay { border-color: #f8fafc; background: linear-gradient(135deg, #22c55e 0%, #f59e0b 100%); } .legend-heat { width: 30px; height: 10px; border-radius: 6px; background: linear-gradient(90deg, #fbbf24, #f97316, #ef4444, #b91c1c); border: 1px solid rgba(255, 255, 255, .25); display: inline-block; } .legend-coverage { width: 16px; height: 16px; border-radius: 3px; border: 1px solid rgba(255, 255, 255, .2); display: inline-block; flex: 0 0 16px; } .legend-coverage-bidir { background: #2e9c44; } .legend-coverage-disc-trace { background: #1fa3c5; } .legend-coverage-tx { background: #ff9f1c; } .legend-coverage-rx { background: #6f42c1; } .legend-coverage-dead { background: #81858c; } .legend-coverage-drop { background: #c51f36; } .legend-toggle { margin-top: 6px; font-size: 12px; background: rgba(255, 255, 255, .12); color: #fff; border: 1px solid rgba(255, 255, 255, .2); border-radius: 8px; padding: 4px 8px; cursor: pointer; } .legend-collapsed .legend { display: none; } .map-toggle { margin-top: 6px; font-size: 12px; background: rgba(255, 255, 255, .12); color: #fff; border: 1px solid rgba(255, 255, 255, .2); border-radius: 8px; padding: 4px 8px; cursor: pointer; } .map-toggle.active { background: rgba(34, 197, 94, .35); border-color: rgba(34, 197, 94, .6); } .node-search { margin-top: 6px; position: relative; width: 100%; } .node-search-input { width: 100%; font-size: 12px; padding: 6px 8px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, .2); background: rgba(15, 23, 42, .6); color: #fff; outline: none; box-sizing: border-box; } .node-search-input::placeholder { color: rgba(255, 255, 255, .65); } .node-search-results { position: absolute; left: 0; right: 0; top: calc(100% + 4px); background: rgba(15, 23, 42, .92); border: 1px solid rgba(255, 255, 255, .2); border-radius: 8px; overflow: hidden; z-index: 1000; max-height: 200px; overflow-y: auto; } .node-search-item { padding: 6px 8px; font-size: 12px; cursor: pointer; display: flex; justify-content: space-between; gap: 8px; } .node-search-item:hover { background: rgba(255, 255, 255, .12); } .node-search-id { opacity: 0.7; } .node-size-control { margin-top: 6px; display: flex; align-items: center; gap: 8px; font-size: 12px; } .node-size-label { opacity: 0.85; } .node-size-range { flex: 1; accent-color: #22c55e; } .node-size-value { min-width: 30px; text-align: right; opacity: 0.75; } .node-label { background: rgba(15, 23, 42, .8); color: #fff; border: 1px solid rgba(255, 255, 255, .25); border-radius: 8px; padding: 2px 6px; font-size: 11px; white-space: nowrap; } .weather-wind-icon { background: transparent; border: 0; } .weather-wind-arrow { display: inline-block; color: rgba(255, 255, 255, 0.92); font-size: 15px; line-height: 1; text-shadow: 0 0 3px rgba(15, 23, 42, 0.75); margin-right: 3px; transform-origin: center center; } .weather-wind-speed { display: inline-block; color: rgba(255, 255, 255, 0.95); font-size: 10px; line-height: 1; text-shadow: 0 0 3px rgba(15, 23, 42, 0.85); white-space: nowrap; vertical-align: middle; } .prop-panel { position: absolute; top: 18px; right: 18px; width: 340px; max-height: 70vh; padding: 10px; border-radius: 12px; background: rgba(15, 23, 42, .78); border: 1px solid rgba(255, 255, 255, .2); font-family: ui-sans-serif, system-ui, sans-serif; color: #fff; display: none; gap: 6px; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; grid-template-columns: 1fr; z-index: 880; } .prop-panel.active { display: grid; } .tool-panel-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 2px; } .tool-panel-collapse { appearance: none; border: 1px solid rgba(255, 255, 255, .2); background: rgba(255, 255, 255, .08); color: #fff; border-radius: 8px; padding: 4px 8px; font: inherit; font-size: 12px; cursor: pointer; } .tool-panel-collapse:hover { background: rgba(255, 255, 255, .16); } .tool-panel.panel-collapsed { overflow: hidden; } .tool-panel.panel-collapsed > :not(.tool-panel-header) { display: none !important; } .prop-field { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 12px; } .prop-panel input, .prop-panel select { background: rgba(0, 0, 0, .35); color: #fff; border: 1px solid rgba(255, 255, 255, .2); border-radius: 6px; padding: 2px 6px; font-size: 12px; font-family: inherit; } .prop-panel input[type="range"] { padding: 0; accent-color: #22c55e; width: 120px; } .prop-panel input[type="number"] { width: 70px; } .prop-panel select { width: 150px; } .los-panel { position: absolute; top: 18px; right: 18px; width: 320px; max-height: 70vh; padding: 10px; border-radius: 12px; background: rgba(15, 23, 42, .78); border: 1px solid rgba(255, 255, 255, .2); color: #fff; font-family: ui-sans-serif, system-ui, sans-serif; display: none; z-index: 900; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; } .los-point-icon { width: 14px; height: 14px; border-radius: 50%; background: #fbbf24; border: 2px solid #f59e0b; box-shadow: 0 0 0 2px rgba(15, 23, 42, .5); cursor: grab; } .los-point-icon.dragging { cursor: grabbing; transform: scale(1.05); } .los-point-icon.selected { box-shadow: 0 0 0 2px rgba(15, 23, 42, .5), 0 0 0 5px rgba(245, 158, 11, .35); } .los-panel.active { display: block; } .los-panel .los-profile { margin-top: 6px; } .los-panel .map-toggle { width: 100%; margin-top: 6px; } .los-field { display: grid; gap: 6px; margin-top: 6px; font-size: 12px; } .los-field.los-inline { grid-auto-flow: column; grid-auto-columns: max-content; align-items: center; gap: 8px; } .los-panel input { background: rgba(0, 0, 0, .35); color: #fff; border: 1px solid rgba(255, 255, 255, .2); border-radius: 6px; padding: 2px 6px; font-size: 12px; font-family: inherit; } .history-panel { position: absolute; top: 18px; right: 18px; width: 320px; max-height: 50vh; padding: 10px; border-radius: 12px; background: rgba(15, 23, 42, .78); border: 1px solid rgba(255, 255, 255, .2); color: #fff; font-family: ui-sans-serif, system-ui, sans-serif; display: none; z-index: 890; overflow: hidden; box-sizing: border-box; pointer-events: auto; } .history-panel.active { display: block; } .weather-panel { position: absolute; top: 18px; right: 18px; width: 320px; max-height: 45vh; padding: 10px; border-radius: 12px; background: rgba(15, 23, 42, .78); border: 1px solid rgba(255, 255, 255, .2); color: #fff; font-family: ui-sans-serif, system-ui, sans-serif; display: none; z-index: 892; overflow: hidden; box-sizing: border-box; pointer-events: auto; } .weather-panel.active { display: block; } .weather-controls { display: grid; gap: 8px; margin-top: 8px; } .weather-controls .map-toggle { width: 100%; } .panel-close { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, .2); background: rgba(15, 23, 42, .5); color: #fff; font-size: 18px; line-height: 24px; cursor: pointer; pointer-events: auto; z-index: 1; } .panel-close:hover { background: rgba(255, 255, 255, .18); } .history-field { display: grid; gap: 6px; margin-top: 6px; font-size: 12px; } .history-panel input { background: rgba(0, 0, 0, .35); color: #fff; border: 1px solid rgba(255, 255, 255, .2); border-radius: 6px; padding: 2px 6px; font-size: 12px; font-family: inherit; } .history-panel input[type="range"] { padding: 0; accent-color: #f59e0b; width: 100%; } .peers-panel { position: absolute; top: 18px; right: 18px; width: 320px; max-height: 60vh; padding: 10px; border-radius: 12px; background: rgba(15, 23, 42, .78); border: 1px solid rgba(255, 255, 255, .2); color: #fff; font-family: ui-sans-serif, system-ui, sans-serif; display: none; z-index: 885; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; } .peers-panel.active { display: block; } .route-details-panel { position: absolute; top: 18px; right: 18px; width: 320px; max-height: 75vh; padding: 10px; border-radius: 12px; background: rgba(15, 23, 42, .92); border: 1px solid rgba(255, 255, 255, .2); color: #fff; font-family: ui-sans-serif, system-ui, sans-serif; display: none; z-index: 895; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; } .route-details-panel.active { display: block; } .route-details-content { margin-top: 8px; display: grid; gap: 4px; } .hop-row { display: flex; align-items: center; gap: 8px; padding: 4px; background: rgba(255, 255, 255, .05); border-radius: 6px; font-size: 12px; } .hop-badge { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; color: #fff; font-weight: bold; font-size: 10px; flex-shrink: 0; } .hop-info { flex: 1; overflow: hidden; } .hop-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; opacity: 0.9; } .hop-meta { opacity: 0.7; font-size: 11px; } .hop-marker { display: flex; align-items: center; justify-content: center; border-radius: 50%; color: #fff; font-weight: bold; font-size: 10px; border: 1px solid rgba(255, 255, 255, 0.8); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); } .peer-section { display: grid; gap: 6px; margin-top: 8px; } .peer-heading { opacity: 0.8; } .peer-list { display: grid; gap: 4px; max-height: 22vh; overflow-y: auto; overflow-x: hidden; padding-right: 4px; min-height: 0; } .peer-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 4px 6px; border-radius: 8px; background: rgba(0, 0, 0, .25); font-size: 12px; cursor: pointer; } .peer-item:hover { background: rgba(255, 255, 255, .08); } .peer-item .peer-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .peer-item .peer-count { opacity: 0.8; font-variant-numeric: tabular-nums; } @media (max-width: 900px) { .los-panel, .prop-panel, .weather-panel, .history-panel, .peers-panel, .route-details-panel { top: auto; right: 12px; left: 12px; bottom: 12px; width: auto; max-height: 45vh; } .route-details-panel { padding: 9px; } .hop-row { padding: 3px 4px; } } .prop-hint { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; margin-left: 6px; color: #cbd5f5; cursor: help; font-weight: 600; } .prop-hint::after { content: attr(data-tooltip); position: absolute; left: 18px; top: 50%; transform: translateY(-50%); background: rgba(15, 23, 42, 0.95); color: #fff; padding: 4px 6px; border-radius: 6px; font-size: 11px; white-space: nowrap; opacity: 0; pointer-events: none; z-index: 10; box-shadow: 0 6px 16px rgba(0, 0, 0, .25); transition: opacity 120ms ease; } .prop-hint:hover::after, .prop-hint:focus::after { opacity: 1; } .leaflet-popup-content { max-width: 260px; overflow-wrap: anywhere; word-break: break-word; } .popup-title { display: block; font-weight: 600; } .popup-id { display: block; opacity: 0.85; } .popup-topic { display: inline-block; overflow-wrap: anywhere; word-break: break-word; } .popup-sample { margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(15, 23, 42, 0.15); } .dark-map .popup-sample { border-top-color: rgba(255, 255, 255, .16); } .dark-map .leaflet-popup-content-wrapper { background: rgba(15, 23, 42, 0.96); color: #f8fafc; border: 1px solid rgba(255, 255, 255, .16); box-shadow: 0 10px 24px rgba(0, 0, 0, .35); } .dark-map .leaflet-popup-tip { background: rgba(15, 23, 42, 0.96); } .dark-map .leaflet-popup-close-button { color: #f8fafc; } .los-profile { margin-top: 6px; padding: 6px 8px; border-radius: 10px; background: rgba(15, 23, 42, .35); border: 1px solid rgba(255, 255, 255, .15); position: relative; } .los-profile-title { margin-bottom: 4px; } .los-profile svg { width: 100%; height: 86px; display: block; } .los-profile-terrain { fill: none; stroke: rgba(226, 232, 240, .85); stroke-width: 2; } .los-profile-los { fill: none; stroke-width: 2; } .los-profile-fresnel { fill: none; stroke: rgba(244, 114, 182, .9); stroke-width: 1.4; stroke-dasharray: 5 4; } .los-profile-tooltip { position: absolute; top: 6px; left: 6px; background: rgba(15, 23, 42, .92); border: 1px solid rgba(255, 255, 255, .2); border-radius: 8px; padding: 4px 6px; font-size: 11px; color: #fff; pointer-events: none; white-space: nowrap; transform: translate(-50%, -120%); } .trail-animated { stroke-dasharray: 6 10; animation: trail-dash 6s linear infinite; } @keyframes trail-dash { to { stroke-dashoffset: -120; } } .route-animated { stroke-dasharray: 12 18; animation: route-dash 8s linear infinite; stroke-linecap: butt; stroke-linejoin: miter; pointer-events: auto; } .flow-marker { width: 22px; height: 22px; margin-left: -11px; margin-top: -11px; pointer-events: none; } @property --flow-mouth { syntax: ''; inherits: false; initial-value: 18deg; } .flow-icon { position: relative; width: 22px; height: 22px; --flow-mouth: 18deg; border-radius: 50%; background: conic-gradient( from calc(90deg - (var(--flow-mouth) * 0.5)), transparent 0deg var(--flow-mouth), rgba(250, 204, 21, 0.96) var(--flow-mouth) 360deg ); opacity: 0.78; filter: drop-shadow(0 0 2px rgba(15, 23, 42, 0.45)); transform-origin: 50% 50%; animation: flow-chomp 0.34s ease-in-out infinite alternate; } .flow-icon::before { content: none; } .flow-icon::after { content: ''; position: absolute; width: 5px; height: 5px; border-radius: 50%; background: rgba(15, 23, 42, 0.9); top: 5px; left: 9px; } @keyframes flow-chomp { from { --flow-mouth: 10deg; } to { --flow-mouth: 40deg; } } @keyframes route-dash { to { stroke-dashoffset: -200; } } .hud-update { margin-top: 8px; padding: 8px 10px; border-radius: 12px; background: rgba(249, 115, 22, 0.12); border: 1px solid rgba(249, 115, 22, 0.4); display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; color: #fed7aa; position: relative; z-index: 2; pointer-events: auto; } .hud-update[hidden] { display: none !important; } .hud-update-text { font-weight: 600; } .hud-update-dismiss { margin-top: 0; pointer-events: auto; }