{"id":647,"date":"2026-06-25T09:30:10","date_gmt":"2026-06-25T14:30:10","guid":{"rendered":"https:\/\/netoholicsnetwork.net\/?page_id=647"},"modified":"2026-09-23T22:27:17","modified_gmt":"2026-09-24T03:27:17","slug":"live-node-status","status":"publish","type":"page","link":"https:\/\/netoholicsnetwork.net\/index.php\/live-node-status\/","title":{"rendered":"Live Node Status"},"content":{"rendered":"<div class=\"et_pb_section_0 et_pb_section et_section_regular et_flex_section\"><div class=\"et_pb_row_0 et_pb_row et_flex_row\"><div class=\"et_pb_column_0 et_pb_column et-last-child et_flex_column et_pb_css_mix_blend_mode_passthrough et_flex_column_24_24 et_flex_column_24_24_tablet et_flex_column_24_24_phone\"><div class=\"et_pb_text_0 et_pb_text et_pb_bg_layout_light et_pb_module et_flex_module\"><div class=\"et_pb_text_inner\"><p><!-- wp:shortcode --><br \/>\n    <section class=\"nh-public-status\" data-nh-public-status>\n        <header class=\"nh-public-status__header\">\n            <div class=\"nh-public-status__identity\">\n                <p class=\"nh-public-status__eyebrow\">Netoholics Network<\/p>\n                <p class=\"nh-public-status__node-line\">Node <strong>674982<\/strong><\/p>\n            <\/div>\n            <div class=\"nh-public-status__status-card\">\n                <div class=\"nh-node-status-pill nh-state-unknown\" data-nh-node-pill>\n                    <i class=\"nh-state-dot\" aria-hidden=\"true\"><\/i>\n                    <span data-nh-node-status>Checking<\/span>\n                <\/div>\n                <small>Updated <b data-nh-updated>-<\/b><\/small>\n            <\/div>\n        <\/header>\n\n        <div class=\"nh-public-status__connections\" data-nh-connections><\/div>\n        <div class=\"nh-public-status__bridges\" data-nh-bridges><\/div>\n    <\/section>\n\n    <style>\n        .nh-public-status {\n            --nh-bg: #101418;\n            --nh-panel: #171d22;\n            --nh-panel-2: #1e252b;\n            --nh-line: rgba(255,255,255,.12);\n            --nh-text: #f5f7f8;\n            --nh-muted: #aab4bb;\n            --nh-green: #39ff62;\n            --nh-yellow: #f2c94c;\n            --nh-red: #ff4d5a;\n            background: var(--nh-bg);\n            color: var(--nh-text);\n            border: 1px solid var(--nh-line);\n            border-radius: 8px;\n            padding: 22px;\n            max-width: 1040px;\n            margin: 0 auto;\n            font-family: inherit;\n        }\n        .nh-public-status * { box-sizing: border-box; }\n        .nh-public-status__header,\n        .nh-public-status__bridges {\n            display: grid;\n            gap: 12px;\n        }\n        .nh-public-status__header {\n            grid-template-columns: minmax(0, 1fr) auto;\n            align-items: center;\n            margin-bottom: 16px;\n        }\n        .nh-public-status__identity {\n            min-width: 0;\n        }\n        .nh-public-status__eyebrow {\n            color: var(--nh-text);\n            font-size: 18px;\n            margin: 0;\n            text-transform: uppercase;\n            letter-spacing: 0;\n            font-weight: 900;\n        }\n        .nh-public-status__node-line {\n            color: var(--nh-muted);\n            font-size: 14px;\n            line-height: 1.25;\n            margin: 5px 0 0;\n            padding: 0;\n        }\n        .nh-public-status__node-line strong {\n            color: #f5f7f8;\n            font-size: 15px;\n            font-weight: 900;\n        }\n        .nh-public-status__status-card {\n            display: flex;\n            align-items: center;\n            flex-direction: column;\n            gap: 12px;\n            background: var(--nh-panel);\n            border: 1px solid var(--nh-line);\n            border-radius: 8px;\n            padding: 10px 14px;\n            text-align: left;\n        }\n        .nh-bridge-card span {\n            color: var(--nh-muted);\n            display: block;\n            font-size: 12px;\n            line-height: 1.25;\n        }\n        .nh-public-status__status-card small {\n            color: var(--nh-muted);\n            font-size: 12px;\n            line-height: 1.2;\n            white-space: nowrap;\n        }\n        .nh-public-status__status-card b {\n            color: #dff6ff;\n            font-weight: 900;\n        }\n        .nh-node-status-pill {\n            display: inline-flex;\n            align-items: center;\n            gap: 7px;\n            border: 1px solid rgba(255,255,255,.14);\n            border-radius: 999px;\n            padding: 7px 10px;\n            color: #f5f7f8;\n            font-size: 13px;\n            font-weight: 900;\n            background: rgba(255,255,255,.04);\n            white-space: nowrap;\n        }\n        .nh-bridge-card,\n        .nh-bridge-clients,\n        .nh-public-status__connections {\n            background: var(--nh-panel);\n            border: 1px solid var(--nh-line);\n            border-radius: 8px;\n            padding: 14px;\n        }\n        .nh-public-status__bridges {\n            grid-template-columns: repeat(6, minmax(0, 1fr)) !important;\n            gap: 8px !important;\n            align-items: start;\n        }\n        .nh-public-status__connections {\n            margin-bottom: 14px;\n            overflow-x: auto;\n            overflow-y: visible;\n        }\n        .nh-public-status__section-title {\n            color: var(--nh-text);\n            font-size: 18px;\n            line-height: 1.2;\n            margin: 0 0 10px;\n            letter-spacing: 0;\n        }\n        .nh-last-talkers {\n            display: grid;\n            grid-template-columns: repeat(5, minmax(0, 1fr));\n            gap: 10px;\n            margin-bottom: 12px;\n        }\n        .nh-talker-card {\n            background: var(--nh-panel-2);\n            border: 1px solid var(--nh-line);\n            border-radius: 8px;\n            padding: 10px;\n            min-width: 0;\n        }\n        .nh-talker-card span,\n        .nh-connection-counts span {\n            color: var(--nh-muted);\n            display: block;\n            font-size: 12px;\n            line-height: 1.25;\n        }\n        .nh-talker-card strong {\n            color: #f8fdff;\n            display: block;\n            font-size: 16px;\n            line-height: 1.15;\n            margin-top: 4px;\n            overflow-wrap: anywhere;\n            text-shadow: 0 0 9px rgba(88,221,255,.34);\n        }\n        .nh-talker-card small {\n            color: #c7d9e2;\n            display: block;\n            font-size: 12px;\n            line-height: 1.25;\n            margin-top: 5px;\n        }\n        .nh-connection-table {\n            width: 100%;\n            border-collapse: collapse;\n            min-width: 720px;\n        }\n        .nh-connection-table th,\n        .nh-connection-table td {\n            border-top: 1px solid var(--nh-line);\n            padding: 9px 8px;\n            text-align: left;\n            vertical-align: top;\n            white-space: nowrap;\n        }\n        .nh-connection-table th {\n            color: #eaf9ff !important;\n            font-size: 12px !important;\n            font-weight: 900 !important;\n            text-transform: uppercase !important;\n            text-shadow: 0 0 8px rgba(88,221,255,.24) !important;\n        }\n        .nh-connection-table td:nth-child(2) {\n            max-width: 320px;\n            overflow-wrap: anywhere;\n            white-space: normal;\n        }\n        .nh-connection-counts {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 14px;\n            border-top: 1px solid var(--nh-line);\n            margin-top: 10px;\n            padding-top: 10px;\n        }\n        .nh-connection-counts strong {\n            color: #f5f7f8;\n            font-size: 15px;\n            margin-left: 4px;\n        }\n        .nh-connection-hint {\n            color: var(--nh-muted);\n            font-size: 13px;\n            line-height: 1.35;\n            margin: -4px 0 12px;\n        }\n        .nh-connection-table .nh-lookup-link {\n            appearance: none;\n            background: transparent;\n            border: 0;\n            color: #f8fdff !important;\n            cursor: pointer;\n            font-family: inherit !important;\n            font-size: 14px !important;\n            font-weight: 700 !important;\n            line-height: 1.3 !important;\n            margin: 0;\n            padding: 0;\n            text-align: left;\n            text-decoration: none;\n        }\n        .nh-connection-table .nh-lookup-link:hover,\n        .nh-connection-table .nh-lookup-link:focus {\n            color: #58ddff !important;\n            text-decoration: underline;\n            text-decoration-thickness: 1px;\n            text-underline-offset: 3px;\n        }\n        .nh-keyed {\n            color: #58ddff;\n            font-weight: 900;\n            text-shadow: 0 0 9px rgba(88,221,255,.35);\n        }\n        .nh-map-actions {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 10px;\n            margin: 14px 0;\n        }\n        .nh-map-button,\n        .nh-map-close,\n        .nh-map-fallback {\n            align-items: center;\n            background: #58ddff;\n            border: 1px solid rgba(88,221,255,.55);\n            border-radius: 6px;\n            color: #101418 !important;\n            cursor: pointer;\n            display: inline-flex;\n            font-size: 13px;\n            font-weight: 900;\n            justify-content: center;\n            line-height: 1;\n            padding: 9px 12px;\n            text-decoration: none !important;\n        }\n        .nh-map-button:hover,\n        .nh-map-button:focus,\n        .nh-map-close:hover,\n        .nh-map-close:focus,\n        .nh-map-fallback:hover,\n        .nh-map-fallback:focus {\n            background: #f8fdff;\n            color: #101418 !important;\n        }\n        .nh-map-panel {\n            background: #02070d;\n            border: 1px solid var(--nh-line);\n            border-radius: 8px;\n            margin: 14px 0 18px;\n            overflow: hidden;\n        }\n        .nh-map-panel[hidden] {\n            display: none;\n        }\n        .nh-map-head {\n            align-items: center;\n            background: #0d1115;\n            border-bottom: 1px solid var(--nh-line);\n            display: flex;\n            gap: 12px;\n            justify-content: space-between;\n            padding: 10px 12px;\n        }\n        .nh-map-head strong {\n            color: #eaf9ff;\n            font-size: 15px;\n            font-weight: 900;\n            line-height: 1.2;\n        }\n\t    .nh-map-body {\n\t        display: grid;\n\t        gap: 10px;\n\t        padding: 12px;\n\t    }\n\t    .nh-map-frame {\n\t        background: #071018;\n\t        border: 1px solid rgba(88,221,255,.32);\n\t        border-radius: 6px;\n\t        box-shadow: inset 0 0 35px rgba(0,234,255,.05);\n\t        height: 430px;\n\t        min-height: 320px;\n\t        overflow: hidden;\n\t        position: relative;\n\t    }\n\t    .nh-map-frame--image {\n\t        background: #ffffff;\n\t        height: clamp(300px, 34vw, 430px);\n\t    }\n\t    .nh-map-frame--image img {\n\t        display: block;\n\t        height: 100%;\n\t        object-fit: contain;\n\t        object-position: center center;\n\t        position: relative;\n\t        width: 100%;\n\t        z-index: 1;\n\t    }\n\t    .nh-map-canvas {\n\t        height: 100%;\n\t        min-height: 320px;\n\t        width: 100%;\n\t    }\n\t    .nh-map-canvas .leaflet-container,\n\t    .nh-map-canvas .leaflet-container:focus,\n\t    .nh-map-canvas .leaflet-zoom-box,\n\t    .nh-map-canvas .leaflet-popup-pane,\n\t    .nh-map-canvas .leaflet-tooltip-pane,\n\t    .nh-map-canvas .leaflet-pane,\n\t    .nh-map-canvas .leaflet-pane:focus,\n\t    .nh-map-canvas .leaflet-pane svg,\n\t    .nh-map-canvas .leaflet-pane svg:focus,\n\t    .nh-map-canvas .leaflet-pane path,\n\t    .nh-map-canvas .leaflet-pane path:focus,\n\t    .nh-map-canvas .leaflet-interactive,\n\t    .nh-map-canvas .leaflet-interactive:focus,\n\t    .nh-map-canvas .leaflet-interactive:active {\n\t        box-shadow: none !important;\n\t        filter: none !important;\n\t        outline: none !important;\n\t    }\n\t    .nh-map-canvas .leaflet-zoom-box {\n\t        display: none !important;\n\t    }\n\t    .nh-map-canvas .leaflet-popup-pane,\n\t    .nh-map-canvas .leaflet-tooltip-pane {\n\t        background: transparent !important;\n\t        border: 0 !important;\n\t        box-shadow: none !important;\n\t        display: none !important;\n\t        height: 0 !important;\n\t        pointer-events: none !important;\n\t        width: 0 !important;\n\t    }\n\t    .nh-map-loading {\n\t        align-items: center;\n\t        color: #eaf9ff;\n\t        display: flex;\n\t        font-size: 15px;\n\t        font-weight: 900;\n\t        inset: 0;\n\t        justify-content: center;\n            letter-spacing: 0;\n            min-height: 220px;\n            position: absolute;\n\t        text-transform: uppercase;\n\t        z-index: 0;\n\t    }\n\t    .nh-map-frame.is-loaded .nh-map-loading {\n\t        display: none;\n\t    }\n\t    .nh-map-marker {\n\t        background: #f3b332 !important;\n\t        border: 2px solid #58ddff !important;\n\t        border-radius: 50% !important;\n\t        box-shadow: none !important;\n\t        box-sizing: border-box;\n\t        filter: none !important;\n\t        height: 18px !important;\n\t        width: 18px !important;\n\t    }\n\t    .nh-map-station-card {\n\t        background: #071018 !important;\n\t        border: 1px solid rgba(88,221,255,.78) !important;\n\t        border-radius: 6px !important;\n\t        box-shadow: 0 12px 26px rgba(0,0,0,.38) !important;\n\t        color: #eaf9ff !important;\n\t        color-scheme: dark;\n\t        display: grid;\n\t        filter: none !important;\n\t        forced-color-adjust: none;\n\t        gap: 4px;\n\t        max-width: min(230px, calc(100% - 28px));\n\t        padding: 10px 34px 10px 12px;\n\t        position: absolute;\n\t        right: 14px;\n\t        top: 14px;\n\t        width: max-content;\n\t        z-index: 750;\n\t    }\n\t    .nh-map-station-card[hidden] {\n\t        display: none;\n\t    }\n\t    .nh-map-station-card strong {\n\t        color: #ffffff !important;\n\t        display: block;\n\t        font-size: 13px;\n\t        font-weight: 900;\n\t        line-height: 1.2;\n\t        margin: 0;\n\t    }\n\t    .nh-map-station-card span {\n\t        color: #c7d8df !important;\n\t        display: block;\n\t        font-size: 12px;\n\t        line-height: 1.35;\n\t    }\n\t    .nh-map-station-card span:first-of-type {\n\t        color: #58ddff !important;\n\t        font-weight: 900;\n\t    }\n\t    .nh-map-station-card button {\n\t        appearance: none;\n\t        background: transparent !important;\n\t        border: 0 !important;\n\t        color: #58ddff !important;\n\t        cursor: pointer;\n\t        font: 900 17px\/1 Arial, sans-serif;\n\t        padding: 0;\n\t        position: absolute;\n\t        right: 10px;\n\t        top: 8px;\n\t    }\n\t    .nh-map-summary {\n\t        color: #eaf9ff;\n\t        font-size: 13px;\n\t        font-weight: 800;\n\t        margin: 0;\n\t    }\n\t    .nh-map-unmapped {\n\t        color: var(--nh-muted);\n\t        font-size: 12px;\n\t        line-height: 1.4;\n\t        margin: 0;\n\t    }\n\t    .nh-map-note {\n\t        color: var(--nh-muted);\n\t        font-size: 12px;\n\t        line-height: 1.35;\n\t        margin: 0;\n\t    }\n\t    .nh-map-canvas .leaflet-control-attribution {\n\t        font-size: 10px;\n\t    }\n        .nh-bridge-column {\n            display: grid;\n            gap: 10px;\n            min-width: 0;\n        }\n        .nh-bridge-card {\n            min-height: 116px;\n            padding: 10px;\n        }\n        .nh-bridge-card__top {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            gap: 10px;\n            margin-bottom: 12px;\n        }\n        .nh-bridge-card h3 {\n            color: var(--nh-text);\n            font-size: 15px;\n            line-height: 1.15;\n            margin: 0;\n            letter-spacing: 0;\n        }\n        .nh-state-dot {\n            width: 10px;\n            height: 10px;\n            border-radius: 50%;\n            flex: 0 0 10px;\n            background: var(--nh-yellow);\n            box-shadow: 0 0 10px rgba(242,201,76,.6);\n        }\n        .nh-state-online .nh-state-dot {\n            background: var(--nh-green);\n            box-shadow: 0 0 10px rgba(57,255,98,.72);\n        }\n        .nh-state-offline .nh-state-dot {\n            background: var(--nh-red);\n            box-shadow: 0 0 10px rgba(255,77,90,.62);\n        }\n        .nh-bridge-card dl {\n            display: grid;\n            grid-template-columns: 1fr;\n            gap: 6px;\n            margin: 0;\n        }\n        .nh-bridge-card dd {\n            margin: 2px 0 0;\n            font-weight: 700;\n            line-height: 1.2;\n            overflow-wrap: anywhere;\n        }\n        @media (max-width: 1100px) and (min-width: 761px) {\n            .nh-public-status__bridges {\n                grid-template-columns: repeat(3, minmax(0, 1fr)) !important;\n            }\n        }\n        .nh-bridge-clients {\n            padding: 0;\n            overflow: visible;\n        }\n        .nh-bridge-clients__title {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            gap: 8px;\n            padding: 11px 12px;\n            color: #eaf9ff;\n            background: rgba(255,255,255,.035);\n            border-bottom: 1px solid var(--nh-line);\n            font-size: 13px;\n            font-weight: 900;\n            line-height: 1.2;\n        }\n        .nh-bridge-clients__count {\n            color: #58ddff;\n            font-size: 12px;\n            font-weight: 900;\n        }\n        .nh-client-list {\n            display: grid;\n            overflow: visible;\n        }\n        .nh-client-row {\n            appearance: none;\n            background: transparent;\n            border: 0;\n            border-top: 1px solid rgba(255,255,255,.075);\n            color: inherit;\n            cursor: default;\n            display: grid;\n            font-family: inherit;\n            gap: 3px;\n            margin: 0;\n            padding: 10px 12px;\n            text-align: left;\n            width: 100%;\n        }\n        .nh-client-row:first-child { border-top: 0; }\n        .nh-client-row[data-nh-url] {\n            cursor: pointer;\n        }\n        .nh-client-row[data-nh-url]:hover,\n        .nh-client-row[data-nh-url]:focus {\n            background: rgba(88,221,255,.08);\n            outline: 1px solid rgba(88,221,255,.28);\n            outline-offset: -1px;\n        }\n        .nh-client-row[data-nh-url]:hover .nh-client-call,\n        .nh-client-row[data-nh-url]:focus .nh-client-call {\n            color: #58ddff;\n            text-decoration: underline;\n            text-decoration-thickness: 1px;\n            text-underline-offset: 3px;\n        }\n        .nh-client-main {\n            display: flex;\n            align-items: baseline;\n            justify-content: space-between;\n            gap: 8px;\n            min-width: 0;\n        }\n        .nh-client-call {\n            color: #f8fdff;\n            font-size: 15px;\n            font-weight: 950;\n            line-height: 1.15;\n            overflow-wrap: anywhere;\n            text-shadow: 0 0 9px rgba(88,221,255,.34);\n        }\n        .nh-client-id {\n            color: #9fd7e9;\n            font-size: 11px;\n            line-height: 1.2;\n            white-space: nowrap;\n        }\n        .nh-client-meta {\n            color: #c7d9e2;\n            font-size: 12px;\n            line-height: 1.25;\n        }\n        .nh-public-status__empty {\n            color: var(--nh-muted);\n            font-size: 13px;\n            line-height: 1.35;\n            margin: 0;\n            padding: 12px;\n        }\n        @media (max-width: 760px) {\n            .nh-public-status { padding: 16px; }\n            .nh-public-status__header,\n            .nh-last-talkers,\n            .nh-public-status__bridges {\n                grid-template-columns: 1fr !important;\n            }\n            .nh-public-status__status-card {\n                align-items: flex-start;\n                justify-self: start;\n            }\n            .nh-public-status__header { align-items: start; }\n            .nh-public-status__connections {\n                overflow: visible;\n            }\n            .nh-map-frame {\n                min-height: 180px;\n            }\n            .nh-map-frame--image {\n                height: 260px;\n            }\n            .nh-map-loading {\n                min-height: 180px;\n            }\n            .nh-connection-table,\n            .nh-connection-table thead,\n            .nh-connection-table tbody,\n            .nh-connection-table tr,\n            .nh-connection-table th,\n            .nh-connection-table td {\n                display: block;\n                min-width: 0;\n                width: 100%;\n            }\n            .nh-connection-table thead {\n                position: absolute;\n                width: 1px;\n                height: 1px;\n                overflow: hidden;\n                clip: rect(0, 0, 0, 0);\n            }\n            .nh-connection-table tr {\n                border-top: 1px solid var(--nh-line);\n                padding: 9px 0;\n            }\n            .nh-connection-table td {\n                border-top: 0;\n                display: grid;\n                grid-template-columns: 92px minmax(0, 1fr);\n                gap: 10px;\n                padding: 4px 0;\n                white-space: nowrap;\n            }\n            .nh-connection-table td::before {\n                color: #eaf9ff;\n                content: attr(data-label);\n                font-size: 11px;\n                font-weight: 900;\n                text-transform: uppercase;\n                white-space: nowrap;\n            }\n            .nh-connection-table td:nth-child(2) {\n                max-width: none;\n                white-space: normal;\n            }\n        }\n    <\/style>\n\n    <script>\n    (function () {\n        var root = document.querySelector('[data-nh-public-status]');\n        if (!root) return;\n\n        var endpoint = '\/index.php\/wp-json\/netoholics\/v1\/public-node-status';\n        var statusEndpoint = '\/index.php\/wp-json\/netoholics\/v1\/network-status';\n        var stationMapEndpoint = '\/index.php\/wp-json\/netoholics\/v1\/station-origin-map';\n        var bridgeOrder = ['dmr', 'ysf', 'p25', 'm17', 'nxdn', 'zello'];\n        var statusGeneratedEpoch = 0;\n        var bridgeNames = {\n            dmr: 'DMR Bridge',\n            ysf: 'YSF Bridge',\n            p25: 'P25 Bridge',\n            m17: 'M17 Bridge',\n            nxdn: 'NXDN Bridge',\n            zello: 'Zello Bridge',\n            echolink: 'EchoLink Bridge',\n            dstar: 'D-Star Bridge'\n        };\n        var bridgeNodeNames = {\n            '1883': 'DMR Bridge',\n            '1889': 'YSF Bridge',\n            '1887': 'P25 Bridge',\n            '1886': 'M17 Bridge',\n            '1888': 'NXDN Bridge',\n            '1001': 'Zello Bridge',\n            '1890': 'D-Star Bridge'\n        };\n        var bridgeNodeKeys = {\n            '1883': 'dmr',\n            '1889': 'ysf',\n            '1887': 'p25',\n            '1886': 'm17',\n            '1888': 'nxdn',\n            '1001': 'zello',\n            '1890': 'dstar'\n        };\n        var bridgeWrap = root.querySelector('[data-nh-bridges]');\n        var connectionWrap = root.querySelector('[data-nh-connections]');\n        var openMaps = {};\n        var networkMapRefreshTokens = {};\n        var networkMapRefreshTimers = {};\n        var stationMapCache = null;\n        var stationMapPromise = null;\n        var leafletPromise = null;\n        var stationMaps = {};\n\n        function text(selector, value) {\n            var el = root.querySelector(selector);\n            if (el) el.textContent = value;\n        }\n\n        function stateLabel(state) {\n            if (state === 'online') return 'Online';\n            if (state === 'offline') return 'Offline';\n            return 'Unknown';\n        }\n\n        function normalizeEpoch(epoch) {\n            epoch = Number(epoch || 0);\n            if (!epoch) return 0;\n            var now = Math.floor(Date.now() \/ 1000);\n            var fallback = statusGeneratedEpoch || now;\n            if (epoch > fallback + 120) return fallback;\n            return epoch;\n        }\n\n        function rawClientEpoch(item) {\n            if (!item || typeof item !== 'object') return 0;\n            return Number(item.last_tx_epoch || item.last_seen_epoch || item.last_seen || item.timestamp || 0);\n        }\n\n        function bridgeClientIsFresh(item) {\n            var epoch = rawClientEpoch(item);\n            if (!epoch) return false;\n            var generated = statusGeneratedEpoch || Math.floor(Date.now() \/ 1000);\n            if (epoch > generated + 120) return false;\n            return generated - epoch <= 3600;\n        }\n\n        function cleanStatusPayload(payload) {\n            if (!payload || !payload.bridges) return payload;\n            Object.keys(payload.bridges).forEach(function (key) {\n                var bridge = payload.bridges[key];\n                if (!bridge || typeof bridge !== 'object') return;\n\n                if (Array.isArray(bridge.clients)) {\n                    bridge.clients = bridge.clients.filter(bridgeClientIsFresh);\n                    bridge.client_count = bridge.clients.length;\n                }\n\n                if (Array.isArray(bridge.recent_users)) {\n                    bridge.recent_users = bridge.recent_users.filter(bridgeClientIsFresh);\n                }\n            });\n\n            if (payload.connection_status && Array.isArray(payload.connection_status.nodes)) {\n                payload.connection_status.nodes.forEach(function (node) {\n                    var bridgeKey = bridgeNodeKeys[String(node && node.node ? node.node : '')];\n                    var bridge = bridgeKey && payload.bridges ? payload.bridges[bridgeKey] : null;\n                    var hasFreshClient = bridge && Array.isArray(bridge.clients) && bridge.clients.length > 0;\n                    var current = bridge ? cleanTalkerCall(bridge.current_user || '') : '-';\n                    if (bridgeKey && !hasFreshClient && current === '-') {\n                        node.person = '';\n                    }\n                });\n            }\n\n            return payload;\n        }\n\n        function formatTime(epoch) {\n            epoch = normalizeEpoch(epoch);\n            if (!epoch) return '-';\n            return new Date(epoch * 1000).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', second: '2-digit' });\n        }\n\n        function bridgeDisplayName(key, item) {\n            return bridgeNames[key] || ((item && item.label ? item.label : key.toUpperCase()) + ' Bridge');\n        }\n\n        function connectionDisplayName(node) {\n            var nodeNumber = String(node && node.node ? node.node : '');\n            return nodeNumber || '-';\n        }\n\n        function firstCallsign(value) {\n            var text = String(value || '').toUpperCase();\n            var match = text.match(\/\\b[A-Z]{1,2}\\d[A-Z0-9]{1,4}\\b|\\b[A-Z]{1,2}\\d[A-Z0-9]{1,4}\\\/[A-Z0-9-]+\\b\/g);\n            return match && match.length ? match[0].split('\/')[0] : '';\n        }\n\n        function isEchoLinkNode(value) {\n            return \/^3\\d{6}$\/.test(String(value || '').trim());\n        }\n\n        function echoLinkLookupValue(value) {\n            return String(value || '').trim().replace(\/^3(?=\\d{6}$)\/, '');\n        }\n\n        function actionButton(label, url, className) {\n            return '<button type=\"button\" class=\"nh-lookup-link ' + escapeHtml(className || '') + '\" data-nh-url=\"' + escapeHtml(url) + '\">' + escapeHtml(label || '-') + '<\/button>';\n        }\n\n        function echoLinkButton(label, lookupValue, className) {\n            return '<button type=\"button\" class=\"nh-lookup-link ' + escapeHtml(className || '') + '\" data-nh-echolink-lookup=\"' + escapeHtml(lookupValue) + '\">' + escapeHtml(label || '-') + '<\/button>';\n        }\n\n        function lookupLink(value, label, fallbackClass) {\n            var textValue = String(value || '').trim();\n            var textLabel = String(label || textValue || '-');\n            if (isEchoLinkNode(textValue)) {\n                return echoLinkButton(textLabel, echoLinkLookupValue(textValue), fallbackClass);\n            }\n            var call = firstCallsign(textValue || textLabel);\n            if (call) {\n                return actionButton(textLabel, 'https:\/\/www.qrz.com\/db\/' + encodeURIComponent(call), fallbackClass);\n            }\n            if (\/^\\d{3,10}$\/.test(textValue)) {\n                return actionButton(textLabel, 'https:\/\/stats.allstarlink.org\/stats\/' + encodeURIComponent(textValue), fallbackClass);\n            }\n            return escapeHtml(textLabel || '-');\n        }\n\n        function lookupConnectionLink(node) {\n            var nodeNumber = String(node && node.node ? node.node : '').trim();\n            var label = String(node && node.label ? node.label : '');\n            if (isEchoLinkNode(nodeNumber)) {\n                return lookupLink(nodeNumber, nodeNumber || '-');\n            }\n            var call = firstCallsign(label);\n            if (call) {\n                return actionButton(nodeNumber || '-', 'https:\/\/www.qrz.com\/db\/' + encodeURIComponent(call));\n            }\n            return lookupLink(nodeNumber, nodeNumber || '-');\n        }\n\n        function lookupLabelLink(node) {\n            var label = String(node && node.label ? node.label : '-');\n            var call = firstCallsign(label);\n            if (!call) return escapeHtml(label);\n            return actionButton(label, 'https:\/\/www.qrz.com\/db\/' + encodeURIComponent(call));\n        }\n\n        function submitEchoLinkLookup(value) {\n            var lookupValue = String(value || '').trim();\n            window.open('\/?netoholics_echolink_lookup=' + encodeURIComponent(lookupValue), '_blank', 'noopener');\n        }\n\n        function syncHostLayout() {\n            window.requestAnimationFrame(function () {\n                var rootRect = root.getBoundingClientRect();\n                var rootBottom = rootRect.bottom + window.scrollY;\n                var parent = root.parentElement;\n                while (parent && parent !== document.body) {\n                    var parentRect = parent.getBoundingClientRect();\n                    var needed = Math.ceil(rootBottom - (parentRect.top + window.scrollY) + 24);\n                    if (needed > 0) {\n                        parent.style.minHeight = needed + 'px';\n                    }\n                    parent = parent.parentElement;\n                }\n            });\n        }\n\n        function renderAllStarLinkMapPanel(nodeNumber) {\n            var node = String(nodeNumber || '').trim();\n            if (!node) return '';\n            var mapBaseUrl = 'https:\/\/stats.allstarlink.org\/stats\/' + encodeURIComponent(node) + '\/networkMap';\n            var mapUrl = networkMapRefreshTokens[node]\n                ? mapBaseUrl + '?nh_refresh=' + encodeURIComponent(networkMapRefreshTokens[node])\n                : mapBaseUrl;\n            return '<div class=\"nh-map-panel\" data-nh-map-panel=\"' + escapeHtml(node) + '\"' + (openMaps[node] ? '' : ' hidden') + '>' +\n                    '<div class=\"nh-map-head\"><strong>AllStarLink Network Map<\/strong><button type=\"button\" class=\"nh-map-close\" data-nh-map-close>Close<\/button><\/div>' +\n                    '<div class=\"nh-map-body\"><div class=\"nh-map-frame nh-map-frame--image\"><span class=\"nh-map-loading\">Loading Map<\/span><img src=\"' + escapeHtml(mapUrl) + '\" alt=\"AllStarLink network map for node ' + escapeHtml(node) + '\" loading=\"eager\" decoding=\"async\" data-nh-map-img><\/div>' +\n                    '<p class=\"nh-map-note\"><button type=\"button\" class=\"nh-map-fallback\" data-nh-url=\"' + escapeHtml(mapUrl) + '\">Open Full Map<\/button><\/p><\/div>' +\n                '<\/div>';\n        }\n\n        function freshNetoholicsNetworkMapUrl(node, mapBaseUrl) {\n            networkMapRefreshTokens[node] = Date.now();\n            return mapBaseUrl + (mapBaseUrl.indexOf('?') === -1 ? '?' : '&') +\n                'nh_refresh=' + encodeURIComponent(networkMapRefreshTokens[node]);\n        }\n\n        function refreshOpenNetoholicsNetworkMap(node, mapBaseUrl) {\n            if (!openMaps[node]) return;\n            var freshMapUrl = freshNetoholicsNetworkMapUrl(node, mapBaseUrl);\n            var panel = root.querySelector('[data-nh-map-panel=\"' + node + '\"]');\n            if (panel) {\n                var mapImage = panel.querySelector('[data-nh-map-img]');\n                var fallbackButton = panel.querySelector('[data-nh-url]');\n                if (mapImage) mapImage.src = freshMapUrl;\n                if (fallbackButton) fallbackButton.setAttribute('data-nh-url', freshMapUrl);\n                syncMapLoaders(panel);\n            }\n        }\n\n        function scheduleNetoholicsNetworkMapRefresh(node, mapBaseUrl) {\n            if (networkMapRefreshTimers[node]) {\n                window.clearTimeout(networkMapRefreshTimers[node]);\n            }\n            networkMapRefreshTimers[node] = window.setTimeout(function () {\n                if (!openMaps[node]) return;\n                refreshOpenNetoholicsNetworkMap(node, mapBaseUrl);\n                scheduleNetoholicsNetworkMapRefresh(node, mapBaseUrl);\n            }, 300000);\n        }\n\n        function stopNetoholicsNetworkMapRefresh(node) {\n            if (networkMapRefreshTimers[node]) {\n                window.clearTimeout(networkMapRefreshTimers[node]);\n                delete networkMapRefreshTimers[node];\n            }\n        }\n\n        function renderStationOriginMapPanel() {\n            var key = 'station-origins';\n            return '<div class=\"nh-map-panel\" data-nh-map-panel=\"' + key + '\"' + (openMaps[key] ? '' : ' hidden') + '>' +\n                '<div class=\"nh-map-head\"><strong>Connected Station Map<\/strong><button type=\"button\" class=\"nh-map-close\" data-nh-map-close>Close<\/button><\/div>' +\n                '<div class=\"nh-map-body\">' +\n                    '<div class=\"nh-map-frame\" data-nh-station-map-frame><span class=\"nh-map-loading\">Loading Map<\/span><div class=\"nh-map-canvas\" data-nh-station-map-canvas><\/div><\/div>' +\n                    '<p class=\"nh-map-summary\" data-nh-map-summary>Loading connected station locations...<\/p>' +\n                    '<p class=\"nh-map-unmapped\" data-nh-map-unmapped><\/p>' +\n                    '<p class=\"nh-map-note\">Locations come from public AllStar\/EchoLink-style node labels when available and are approximate city\/region points. Map style by Esri Dark Gray Canvas.<\/p>' +\n                '<\/div>' +\n            '<\/div>';\n        }\n\n        function renderMapPanel(nodeNumber) {\n            var node = String(nodeNumber || '').trim();\n            if (!node) return '';\n            var mapUrl = 'https:\/\/stats.allstarlink.org\/stats\/' + encodeURIComponent(node) + '\/networkMap';\n            var stationOpen = !!openMaps['station-origins'];\n            var stationButtonClass = stationOpen ? ' is-open' : '';\n            var stationButtonState = stationOpen ? ' aria-pressed=\"true\" disabled' : ' aria-pressed=\"false\"';\n            return '<div class=\"nh-map-actions\"><button type=\"button\" class=\"nh-map-button' + stationButtonClass + '\" data-nh-station-map-open=\"station-origins\"' + stationButtonState + '>View Station Map<\/button><button type=\"button\" class=\"nh-map-button\" data-nh-map-open=\"' + escapeHtml(node) + '\" data-nh-map-url=\"' + escapeHtml(mapUrl) + '\">View Network Map<\/button><\/div>' +\n                renderStationOriginMapPanel() +\n                renderAllStarLinkMapPanel(node);\n        }\n\n        function syncMapLoaders(scope) {\n            (scope || root).querySelectorAll('[data-nh-map-img]').forEach(function (img) {\n                var frame = img.closest('.nh-map-frame');\n                var loading = frame ? frame.querySelector('.nh-map-loading') : null;\n                if (!loading) return;\n                if (img.complete && img.naturalWidth > 0) {\n                    loading.hidden = true;\n                    return;\n                }\n                loading.hidden = false;\n                img.addEventListener('load', function () { loading.hidden = true; }, { once: true });\n                img.addEventListener('error', function () { loading.textContent = 'Map unavailable'; }, { once: true });\n            });\n        }\n\n        function loadLeaflet() {\n            if (window.L) return Promise.resolve(window.L);\n            if (leafletPromise) return leafletPromise;\n            leafletPromise = new Promise(function (resolve, reject) {\n                var cssFallback = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/leaflet\/1.9.4\/leaflet.min.css';\n                if (!document.querySelector('link[data-nh-leaflet]')) {\n                    var link = document.createElement('link');\n                    link.rel = 'stylesheet';\n                    link.href = 'https:\/\/netoholicsnetwork.net\/index.php?wvarc_leaflet_asset=css&v=1.9.4';\n                    link.setAttribute('data-nh-leaflet', '1');\n                    link.addEventListener('error', function () {\n                        if (link.href !== cssFallback) link.href = cssFallback;\n                    }, { once: true });\n                    document.head.appendChild(link);\n                }\n                var sources = [\n                    'https:\/\/netoholicsnetwork.net\/index.php?wvarc_leaflet_asset=js&v=1.9.4',\n                    'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/leaflet\/1.9.4\/leaflet.min.js',\n                    'https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js'\n                ];\n                function trySource(index) {\n                    var script = document.createElement('script');\n                    var settled = false;\n                    var timeoutId = null;\n                    script.src = sources[index];\n                    script.async = true;\n                    script.setAttribute('data-nh-leaflet-script', String(index));\n                    function tryNextOrReject() {\n                        if (settled) return;\n                        settled = true;\n                        if (timeoutId) window.clearTimeout(timeoutId);\n                        script.remove();\n                        if (index + 1 < sources.length) {\n                            trySource(index + 1);\n                            return;\n                        }\n                        leafletPromise = null;\n                        reject(new Error('Map library unavailable'));\n                    }\n                    script.onload = function () {\n                        if (settled) return;\n                        settled = true;\n                        if (timeoutId) window.clearTimeout(timeoutId);\n                        if (window.L) {\n                            resolve(window.L);\n                            return;\n                        }\n                        settled = false;\n                        leafletPromise = null;\n                        tryNextOrReject();\n                    };\n                    script.onerror = tryNextOrReject;\n                    document.head.appendChild(script);\n                    timeoutId = window.setTimeout(tryNextOrReject, 2500);\n                }\n                trySource(0);\n            });\n            return leafletPromise;\n        }\n\n        function fetchStationMapData(force) {\n            var now = Date.now();\n            if (!force && stationMapCache && stationMapCache.loadedAt && now - stationMapCache.loadedAt < 5000) {\n                return Promise.resolve(stationMapCache.data);\n            }\n            if (!force && stationMapPromise) return stationMapPromise;\n            stationMapPromise = fetch(stationMapEndpoint + '?t=' + now, { cache: 'no-store', credentials: 'same-origin' })\n                .then(function (response) {\n                    if (!response.ok) throw new Error('station map unavailable');\n                    return response.json();\n                })\n                .then(function (data) {\n                    stationMapCache = { loadedAt: Date.now(), data: data };\n                    stationMapPromise = null;\n                    return data;\n                })\n                .catch(function (error) {\n                    stationMapPromise = null;\n                    throw error;\n                });\n            return stationMapPromise;\n        }\n\n        function stationCardHtml(point) {\n            var callsign = point.callsign || firstCallsign(point.label) || firstCallsign(point.node) || '';\n            var name = String(point.name || '').replace(\/\\s+\/g, ' ').trim();\n            if (!name.replace(\/[-\\s]\/g, '')) {\n                name = '';\n            }\n            return '<button type=\"button\" aria-label=\"Close station information\" data-nh-map-card-close>&times;<\/button>' +\n                '<strong>' + escapeHtml(callsign || '-') + '<\/strong>' +\n                '<span>' + escapeHtml(name || 'Name unavailable') + '<\/span>' +\n                '<span>' + escapeHtml(point.location || 'Location unavailable') + '<\/span>';\n        }\n\n        function stationPointsKey(points) {\n            return (Array.isArray(points) ? points : []).map(function (point) {\n                return [\n                    point.callsign || '',\n                    point.name || '',\n                    point.node || '',\n                    point.label || '',\n                    point.location || '',\n                    point.lat || '',\n                    point.lng || ''\n                ].join('|');\n            }).sort().join('~');\n        }\n\n        function applyStationMapData(panel, data, fitMap) {\n            var state = panel && panel._nhStationMap;\n            if (!state) return;\n            var L = state.L;\n            var map = state.map;\n            var markerLayer = state.markerLayer;\n            var stationCard = state.stationCard;\n            var summary = state.summary;\n            var unmapped = state.unmapped;\n            var points = Array.isArray(data && data.points) ? data.points : [];\n            var nextKey = stationPointsKey(points);\n            if (state.pointsKey === nextKey) return;\n            state.pointsKey = nextKey;\n            markerLayer.clearLayers();\n            stationCard.hidden = true;\n\n            var bounds = [];\n            points.forEach(function (point) {\n                var lat = Number(point.lat);\n                var lng = Number(point.lng);\n                if (!isFinite(lat) || !isFinite(lng)) return;\n                bounds.push([lat, lng]);\n                L.marker([lat, lng], {\n                    icon: L.divIcon({\n                        className: 'nh-map-marker',\n                        iconSize: [18, 18],\n                        iconAnchor: [9, 9]\n                    }),\n                    keyboard: false\n                }).addTo(markerLayer).on('click', function (event) {\n                    if (event && event.originalEvent) L.DomEvent.stopPropagation(event.originalEvent);\n                    stationCard.innerHTML = stationCardHtml(point);\n                    stationCard.hidden = false;\n                });\n            });\n\n            if (fitMap) {\n                if (bounds.length > 1) {\n                    map.fitBounds(bounds, { padding: [28, 28], maxZoom: 4 });\n                } else if (bounds.length === 1) {\n                    map.setView(bounds[0], 4);\n                }\n            }\n            if (summary) {\n                summary.textContent = bounds.length ? bounds.length + ' connected station location' + (bounds.length === 1 ? '' : 's') + ' mapped.' : 'No connected station locations are available right now.';\n            }\n            if (unmapped) {\n                var missed = Array.isArray(data && data.unmapped) ? data.unmapped : [];\n                unmapped.textContent = missed.length ? 'Not mapped: ' + missed.map(function (item) {\n                    return item.callsign || item.node || item.label || '';\n                }).filter(Boolean).slice(0, 8).join(', ') : '';\n            }\n        }\n\n        function refreshStationMap(panel) {\n            if (!panel || panel.hidden || !panel._nhStationMap) return;\n            var state = panel._nhStationMap;\n            var now = Date.now();\n            if (state.refreshing || (state.refreshedAt && now - state.refreshedAt < 5000)) return;\n            state.refreshing = true;\n            state.refreshedAt = now;\n            fetchStationMapData(true)\n                .then(function (data) {\n                    state.refreshing = false;\n                    applyStationMapData(panel, data, false);\n                })\n                .catch(function () {\n                    state.refreshing = false;\n                });\n        }\n\n        function initializeStationMap(panel) {\n            if (!panel || panel.hidden || panel.getAttribute('data-nh-map-ready') === '1') return;\n            panel.setAttribute('data-nh-map-ready', '1');\n            var frame = panel.querySelector('[data-nh-station-map-frame]');\n            var canvas = panel.querySelector('[data-nh-station-map-canvas]');\n            var summary = panel.querySelector('[data-nh-map-summary]');\n            var unmapped = panel.querySelector('[data-nh-map-unmapped]');\n            if (!frame || !canvas) return;\n\n            Promise.all([loadLeaflet(), fetchStationMapData()])\n                .then(function (results) {\n                    var L = results[0];\n                    var data = results[1] || {};\n                    var points = Array.isArray(data.points) ? data.points : [];\n                    frame.classList.add('is-loaded');\n                    var stationCard = frame.querySelector('[data-nh-map-station-card]');\n                    if (!stationCard) {\n                        stationCard = document.createElement('div');\n                        stationCard.className = 'nh-map-station-card';\n                        stationCard.setAttribute('data-nh-map-station-card', '');\n                        stationCard.hidden = true;\n                        frame.appendChild(stationCard);\n                    }\n                    function showStationCard(point) {\n                        stationCard.innerHTML = stationCardHtml(point);\n                        stationCard.hidden = false;\n                    }\n                    stationCard.addEventListener('click', function (event) {\n                        if (event.target.closest('[data-nh-map-card-close]')) {\n                            stationCard.hidden = true;\n                        }\n                    });\n\n                    var map = L.map(canvas, {\n                        boxZoom: false,\n                        keyboard: false,\n                        scrollWheelZoom: false,\n                        worldCopyJump: true\n                    }).setView([20, 0], 2);\n                    stationMaps.stationOrigins = map;\n                    L.tileLayer('https:\/\/server.arcgisonline.com\/ArcGIS\/rest\/services\/Canvas\/World_Dark_Gray_Base\/MapServer\/tile\/{z}\/{y}\/{x}', {\n                        maxZoom: 16,\n                        attribution: 'Tiles &copy; Esri'\n                    }).addTo(map);\n                    L.tileLayer('https:\/\/server.arcgisonline.com\/ArcGIS\/rest\/services\/Canvas\/World_Dark_Gray_Reference\/MapServer\/tile\/{z}\/{y}\/{x}', {\n                        maxZoom: 16,\n                        pane: 'overlayPane'\n                    }).addTo(map);\n                    map.on('zoomend moveend', function () {\n                        if (document.activeElement && frame.contains(document.activeElement) && document.activeElement.blur) {\n                            document.activeElement.blur();\n                        }\n                    });\n                    panel._nhStationMap = {\n                        L: L,\n                        map: map,\n                        markerLayer: L.layerGroup().addTo(map),\n                        stationCard: stationCard,\n                        summary: summary,\n                        unmapped: unmapped,\n                        pointsKey: '',\n                        refreshedAt: Date.now()\n                    };\n                    applyStationMapData(panel, data, true);\n                    window.setTimeout(function () { map.invalidateSize(); }, 150);\n                })\n                .catch(function () {\n                    frame.classList.add('is-loaded');\n                    if (summary) summary.textContent = 'Station origin map is unavailable right now.';\n                    if (unmapped) unmapped.textContent = '';\n                });\n        }\n\n        function initializeOpenStationMaps() {\n            root.querySelectorAll('[data-nh-map-panel=\"station-origins\"]:not([hidden])').forEach(initializeStationMap);\n        }\n\n        function refreshOpenStationMaps() {\n            root.querySelectorAll('[data-nh-map-panel=\"station-origins\"]:not([hidden])').forEach(refreshStationMap);\n        }\n\n        function talkerLabel(entry) {\n            var nodeNumber = String(entry && entry.node ? entry.node : '');\n            var label = entry && entry.label ? String(entry.label).replace(\/\\s+\/g, ' ').trim() : '';\n            if (bridgeNodeNames[nodeNumber]) return bridgeNodeNames[nodeNumber];\n            if (\/EchoLink\/i.test(label)) return 'EchoLink Bridge';\n            return label || nodeNumber || '-';\n        }\n\n        function cleanTalkerCall(value) {\n            var text = String(value || '').replace(\/\\s+\/g, ' ').trim();\n            if (!text || text === '-') return '-';\n            return text.replace(\/[^A-Za-z0-9\/ -]\/g, '').trim() || '-';\n        }\n\n        function talkerCallsign(entry) {\n            var person = cleanTalkerCall(entry && entry.person ? entry.person : '');\n            if (person !== '-') return person;\n            var node = String(entry && entry.node ? entry.node : '').trim();\n            if (node && !\/^\\d+$\/.test(node)) return cleanTalkerCall(node);\n            var label = String(entry && entry.label ? entry.label : '').toUpperCase();\n            var match = label.match(\/\\b[A-Z]{1,2}\\d[A-Z0-9]{1,4}\\b\/);\n            if (match) return cleanTalkerCall(match[0]);\n            return '-';\n        }\n\n        function durationSeconds(value) {\n            var parts = String(value || '').split(':').map(function (part) {\n                return Number(part);\n            });\n            if (parts.some(function (part) { return !Number.isFinite(part); })) return 0;\n            if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];\n            if (parts.length === 2) return parts[0] * 60 + parts[1];\n            if (parts.length === 1) return parts[0];\n            return 0;\n        }\n\n        function bridgeLiveTalker(nodeNumber, bridges, keyupDuration) {\n            var key = bridgeNodeKeys[String(nodeNumber || '')];\n            var bridge = key && bridges ? bridges[key] : null;\n            if (!bridge) return '';\n\n            var current = cleanTalkerCall(bridge.current_user || '');\n            if (current !== '-') return current;\n\n            var generated = statusGeneratedEpoch || Math.floor(Date.now() \/ 1000);\n            var windowSeconds = Math.max(6, durationSeconds(keyupDuration) + 8);\n            var clients = Array.isArray(bridge.clients) ? bridge.clients.slice() : [];\n            clients.sort(function (a, b) {\n                return Number(b.last_tx_epoch || 0) - Number(a.last_tx_epoch || 0);\n            });\n            for (var i = 0; i < clients.length; i += 1) {\n                var txEpoch = Number(clients[i].last_tx_epoch || 0);\n                if (txEpoch > 0 && generated - txEpoch <= windowSeconds) {\n                    return cleanTalkerCall(clients[i].callsign || '');\n                }\n            }\n\n            return '';\n        }\n\n        function recentUserName(user) {\n            if (!user) return '';\n            if (typeof user === 'object') {\n                return user.name || user.callsign || user.user || user.username || '';\n            }\n            var text = String(user);\n            var match = text.match(\/['\"]name['\"]\\s*:\\s*['\"]([^'\"]+)['\"]\/i) ||\n                text.match(\/['\"]callsign['\"]\\s*:\\s*['\"]([^'\"]+)['\"]\/i);\n            return match ? match[1] : text;\n        }\n\n        function statusFromLanding(key, item, statusPayload) {\n            var serviceKey = key === 'node' ? 'allstar' : key;\n            var service = statusPayload && statusPayload.services ? statusPayload.services[serviceKey] : null;\n            return service && service.state ? service.state : (item && item.status ? item.status : 'unknown');\n        }\n\n        function renderBridges(bridges, statusPayload) {\n            bridgeWrap.innerHTML = bridgeOrder.map(function (key) {\n                var item = bridges && bridges[key] ? bridges[key] : {};\n                var state = statusFromLanding(key, item, statusPayload);\n                return '<section class=\"nh-bridge-column\">' +\n                    '<article class=\"nh-bridge-card nh-state-' + state + '\">' +\n                    '<div class=\"nh-bridge-card__top\"><h3>' + escapeHtml(bridgeDisplayName(key, item)) + '<\/h3><i class=\"nh-state-dot\" aria-hidden=\"true\"><\/i><\/div>' +\n                    '<dl>' +\n                    '<div><span>Status<\/span><dd>' + stateLabel(state) + '<\/dd><\/div>' +\n                    '<div><span>Channel<\/span><dd>' + escapeHtml(item.channel || '-') + '<\/dd><\/div>' +\n                    '<div><span>Clients<\/span><dd>' + Number(item.client_count || 0) + '<\/dd><\/div>' +\n                    '<\/dl>' +\n                    '<\/article>' +\n                    renderBridgeClients(key, item) +\n                    '<\/section>';\n            }).join('');\n        }\n\n        function renderTalkerCards(connectionStatus, bridges) {\n            var nodes = connectionStatus && Array.isArray(connectionStatus.nodes) ? connectionStatus.nodes : [];\n            var keyedNode = connectionStatus && connectionStatus.keyed_node ? String(connectionStatus.keyed_node) : '-';\n            var currentNode = nodes.find(function (node) {\n                return node.keyed === 'yes' || (keyedNode !== '-' && String(node.node) === keyedNode);\n            });\n            var history = connectionStatus && Array.isArray(connectionStatus.last_talkers) ? connectionStatus.last_talkers : [];\n            var cards = [];\n\n            if (currentNode) {\n                var currentHistory = history.find(function (entry) {\n                    return String(entry.node || '-') === String(currentNode.node || '-');\n                }) || {};\n                var liveBridgeTalker = bridgeLiveTalker(currentNode.node, bridges, currentNode.last_keyed);\n                cards.push({\n                    title: 'Current Talker',\n                    callsign: talkerCallsign({\n                        person: liveBridgeTalker || currentNode.person || (!bridgeNodeKeys[String(currentNode.node || '')] ? currentHistory.person : ''),\n                        node: currentNode.node,\n                        label: currentNode.label\n                    }),\n                    label: talkerLabel(currentNode),\n                    direction: currentNode.direction || '-',\n                    time: currentNode.last_keyed || '-'\n                });\n            } else {\n                cards.push({\n                    title: 'Current Talker',\n                    callsign: '-',\n                    label: 'No one talking',\n                    direction: '-',\n                    time: '-'\n                });\n            }\n\n            history.filter(function (entry) {\n                return !currentNode || String(entry.node || '-') !== String(currentNode.node || '-') || String(entry.last_keyed || '-') !== String(currentNode.last_keyed || '-');\n            }).slice(0, 4).forEach(function (entry, index) {\n                cards.push({\n                    title: 'Last Talker ' + (index + 1),\n                    callsign: talkerCallsign(entry),\n                    label: talkerLabel(entry),\n                    direction: entry.direction || '-',\n                    time: entry.last_keyed || '-'\n                });\n            });\n\n            while (cards.length < 5) {\n                cards.push({\n                    title: 'Last Talker ' + cards.length,\n                    callsign: '-',\n                    label: 'No recent talker',\n                    direction: '-',\n                    time: '-'\n                });\n            }\n\n            return '<div class=\"nh-last-talkers\">' + cards.slice(0, 5).map(function (card) {\n                return '<div class=\"nh-talker-card\"><span>' + escapeHtml(card.title) + '<\/span><strong>' + escapeHtml(card.callsign) + '<\/strong><small>' + escapeHtml(card.label) + '<br>' + escapeHtml(card.direction) + ' \u00b7 ' + escapeHtml(card.time) + '<\/small><\/div>';\n            }).join('') + '<\/div>';\n        }\n\n        function renderConnections(connectionStatus, bridges) {\n            var data = connectionStatus || {};\n            var hiddenBridgeNodes = { '1883': true, '1886': true, '1887': true, '1888': true, '1889': true, '1890': true, '1001': true, 'BCSTFY': true };\n            var nodes = Array.isArray(data.nodes) ? data.nodes.filter(function (node) {\n                return !hiddenBridgeNodes[String(node && node.node ? node.node : '')];\n            }) : [];\n            var directLinks = Number(data.direct_link_count || data.active_link_count || 0);\n            var totalLinks = Number(data.total_link_count || data.connected_node_count || nodes.length || 0);\n            var adjacentLinks = Number(data.adjacent_link_count || Math.max(totalLinks - directLinks, 0));\n            var preservedStationPanel = null;\n            if (openMaps['station-origins']) {\n                preservedStationPanel = connectionWrap.querySelector('[data-nh-map-panel=\"station-origins\"]');\n            }\n            var rows = nodes.map(function (node) {\n                var keyed = String(node.keyed || '').toLowerCase() === 'yes';\n                return '<tr>' +\n                    '<td data-label=\"Connections\">' + lookupConnectionLink(node) + '<\/td>' +\n                    '<td data-label=\"Station Info\">' + lookupLabelLink(node) + '<\/td>' +\n                    '<td data-label=\"Dir\">' + escapeHtml(node.direction || '-') + '<\/td>' +\n                    '<td data-label=\"Keyed\">' + (keyed ? '<span class=\"nh-keyed\">Yes<\/span>' : 'No') + '<\/td>' +\n                    '<td data-label=\"Connected\">' + escapeHtml(node.elapsed || '-') + '<\/td>' +\n                    '<td data-label=\"Last Keyed\">' + escapeHtml(node.last_keyed || '-') + '<\/td>' +\n                    '<\/tr>';\n            }).join('');\n\n            connectionWrap.innerHTML =\n                '<h3 class=\"nh-public-status__section-title\">Connection Status<\/h3>' +\n                '<p class=\"nh-connection-hint\">Click a station\/callsign for QRZ lookup. EchoLink connections open EchoLink lookup.<\/p>' +\n                renderTalkerCards(data, bridges || {}) +\n                (rows ? '<table class=\"nh-connection-table\"><thead><tr><th>Connections<\/th><th>Station Info<\/th><th>Dir<\/th><th>Keyed<\/th><th>Connected<\/th><th>Last Keyed<\/th><\/tr><\/thead><tbody>' + rows + '<\/tbody><\/table>' : '<p class=\"nh-public-status__empty\">No connected nodes reported right now.<\/p>') +\n                renderMapPanel('674982') +\n                '<div class=\"nh-connection-counts\"><span>' + totalLinks + ' total linked (' + directLinks + ' direct, ' + adjacentLinks + ' adjacent)<\/span><\/div>';\n            if (preservedStationPanel) {\n                var freshStationPanel = connectionWrap.querySelector('[data-nh-map-panel=\"station-origins\"]');\n                if (freshStationPanel) {\n                    freshStationPanel.replaceWith(preservedStationPanel);\n                    preservedStationPanel.hidden = false;\n                    if (stationMaps.stationOrigins) {\n                        window.setTimeout(function () { stationMaps.stationOrigins.invalidateSize(); }, 100);\n                    }\n                }\n            }\n            initializeOpenStationMaps();\n            refreshOpenStationMaps();\n            syncMapLoaders(connectionWrap);\n            syncHostLayout();\n\t    }\n\n        function bridgeClientTitle(key, item) {\n            if (key === 'dmr') return 'DMR Clients';\n            if (key === 'ysf') return 'YSF Clients';\n            if (key === 'zello') return 'Zello Clients';\n            if (key === 'p25') return 'P25 Clients';\n            if (key === 'm17') return 'M17 Clients';\n            if (key === 'nxdn') return 'NXDN Clients';\n            if (key === 'dstar') return 'D-Star Clients';\n            return (item.label || key.toUpperCase()) + ' Clients';\n        }\n\n        function bridgeClientTxText(epoch) {\n            epoch = Number(epoch || 0);\n            return epoch > 0 ? 'Last TX ' + formatTime(epoch) : 'No Recent TX';\n        }\n\n        function renderBridgeClients(key, item) {\n            var clients = item && Array.isArray(item.clients) ? item.clients : [];\n            var recent = item && Array.isArray(item.recent_users) ? item.recent_users : [];\n            var rows = clients.map(function (client) {\n                var lastTx = Number(client.last_tx_epoch || 0);\n                var metaParts = [];\n                var callsign = client.callsign || '-';\n                var clientId = client.id && client.id !== '-' ? client.id : '';\n                var call = firstCallsign(callsign);\n                var tag = call ? 'button type=\"button\"' : 'div';\n                var url = call ? ' data-nh-url=\"' + escapeHtml('https:\/\/www.qrz.com\/db\/' + encodeURIComponent(call)) + '\"' : '';\n                if (client.name && client.name !== '-' && client.name !== callsign) metaParts.push(escapeHtml(client.name));\n                metaParts.push(bridgeClientTxText(lastTx));\n                return '<' + tag + ' class=\"nh-client-row\"' + url + '>' +\n                    '<div class=\"nh-client-main\">' +\n                    '<strong class=\"nh-client-call\">' + escapeHtml(callsign) + '<\/strong>' +\n                    '<span class=\"nh-client-id\">' + escapeHtml(clientId) + '<\/span>' +\n                    '<\/div>' +\n                    '<div class=\"nh-client-meta\">' + metaParts.join(' \u00b7 ') + '<\/div>' +\n                    '<\/' + (call ? 'button' : 'div') + '>';\n            });\n\n            if (!rows.length && recent.length) {\n                rows = recent.map(function (user) {\n                    var name = recentUserName(user) || '-';\n                    var epoch = user && typeof user === 'object' ? Number(user.last_tx_epoch || user.last_seen_epoch || 0) : 0;\n                    var call = firstCallsign(name);\n                    var tag = call ? 'button type=\"button\"' : 'div';\n                    var url = call ? ' data-nh-url=\"' + escapeHtml('https:\/\/www.qrz.com\/db\/' + encodeURIComponent(call)) + '\"' : '';\n                    return '<' + tag + ' class=\"nh-client-row\"' + url + '><div class=\"nh-client-main\"><strong class=\"nh-client-call\">' + escapeHtml(name) + '<\/strong><\/div><div class=\"nh-client-meta\">' + bridgeClientTxText(user && typeof user === 'object' ? Number(user.last_tx_epoch || 0) : 0) + '<\/div><\/' + (call ? 'button' : 'div') + '>';\n                });\n            }\n\n            return '<div class=\"nh-bridge-clients\">' +\n                '<div class=\"nh-bridge-clients__title\"><span>' + bridgeClientTitle(key, item) + '<\/span><span class=\"nh-bridge-clients__count\">' + rows.length + '<\/span><\/div>' +\n                (rows.length ? '<div class=\"nh-client-list\">' + rows.join('') + '<\/div>' : '<p class=\"nh-public-status__empty\">No clients reported right now.<\/p>') +\n                '<\/div>';\n        }\n\n        function escapeHtml(value) {\n            return String(value).replace(\/[&<>\"']\/g, function (char) {\n                return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#039;' })[char];\n            });\n        }\n\n        root.addEventListener('click', function (event) {\n            var echoLink = event.target.closest('[data-nh-echolink-lookup]');\n            if (echoLink) {\n                event.preventDefault();\n                submitEchoLinkLookup(echoLink.getAttribute('data-nh-echolink-lookup') || '');\n                return;\n            }\n\n            var urlButton = event.target.closest('[data-nh-url]');\n            if (urlButton) {\n                event.preventDefault();\n                window.open(urlButton.getAttribute('data-nh-url') || '', '_blank', 'noopener');\n                return;\n            }\n\n            var openStationMap = event.target.closest('[data-nh-station-map-open]');\n            if (openStationMap) {\n                event.preventDefault();\n                var mapKey = openStationMap.getAttribute('data-nh-station-map-open') || 'station-origins';\n                if (openMaps[mapKey]) return;\n                openMaps[mapKey] = true;\n                openStationMap.classList.add('is-open');\n                openStationMap.setAttribute('aria-pressed', 'true');\n                openStationMap.disabled = true;\n                var stationPanel = root.querySelector('[data-nh-map-panel=\"' + mapKey + '\"]');\n                if (stationPanel) {\n                    stationPanel.hidden = false;\n                    if (stationPanel.getAttribute('data-nh-map-ready') === '1' && stationMaps.stationOrigins) {\n                        window.setTimeout(function () { stationMaps.stationOrigins.invalidateSize(); }, 100);\n                    } else {\n                        initializeStationMap(stationPanel);\n                    }\n                    syncHostLayout();\n                }\n                return;\n            }\n\n            var openMap = event.target.closest('[data-nh-map-open]');\n            if (openMap) {\n                event.preventDefault();\n                var node = openMap.getAttribute('data-nh-map-open') || '';\n                if (openMaps[node]) return;\n                var mapBaseUrl = openMap.getAttribute('data-nh-map-url') || '';\n                if (window.matchMedia && window.matchMedia('(max-width: 760px)').matches) {\n                    window.open(freshNetoholicsNetworkMapUrl(node, mapBaseUrl), '_blank', 'noopener');\n                    return;\n                }\n                openMaps[node] = true;\n                var panel = root.querySelector('[data-nh-map-panel=\"' + node + '\"]');\n                if (panel) {\n                    refreshOpenNetoholicsNetworkMap(node, mapBaseUrl);\n                    panel.hidden = false;\n                    syncMapLoaders(panel);\n                    syncHostLayout();\n                }\n                scheduleNetoholicsNetworkMapRefresh(node, mapBaseUrl);\n                return;\n            }\n\n            var closeMap = event.target.closest('[data-nh-map-close]');\n            if (closeMap) {\n                event.preventDefault();\n                var mapPanel = closeMap.closest('[data-nh-map-panel]');\n                if (mapPanel) {\n                    var closedMapKey = mapPanel.getAttribute('data-nh-map-panel') || '';\n                    openMaps[closedMapKey] = false;\n                    stopNetoholicsNetworkMapRefresh(closedMapKey);\n                    delete networkMapRefreshTokens[closedMapKey];\n                    mapPanel.hidden = true;\n                    if (closedMapKey === 'station-origins') {\n                        var stationMapButton = root.querySelector('[data-nh-station-map-open=\"station-origins\"]');\n                        if (stationMapButton) {\n                            stationMapButton.classList.remove('is-open');\n                            stationMapButton.setAttribute('aria-pressed', 'false');\n                            stationMapButton.disabled = false;\n                        }\n                    }\n                    syncHostLayout();\n                }\n            }\n        });\n\n        function refresh() {\n            var stamp = Date.now();\n            Promise.all([\n                fetch(endpoint + '?t=' + stamp, { cache: 'no-store', credentials: 'same-origin' }).then(function (response) {\n                    if (!response.ok) throw new Error('status unavailable');\n                    return response.json();\n                }),\n                fetch(statusEndpoint + '?t=' + stamp, { cache: 'no-store', credentials: 'same-origin' }).then(function (response) {\n                    if (!response.ok) return null;\n                    return response.json();\n                }).catch(function () { return null; })\n            ])\n                .then(function (results) {\n                    var payload = results[0];\n                    var statusPayload = results[1];\n                    statusGeneratedEpoch = payload ? Number(payload.generated_epoch || 0) : 0;\n                    payload = cleanStatusPayload(payload);\n                    var nodeState = statusFromLanding('node', payload ? payload.node : null, statusPayload);\n                    var pill = root.querySelector('[data-nh-node-pill]');\n                    text('[data-nh-node-status]', stateLabel(nodeState));\n                    text('[data-nh-updated]', formatTime(payload ? Number(payload.generated_epoch || 0) : 0));\n                    if (pill) pill.className = 'nh-node-status-pill nh-state-' + nodeState;\n                    renderConnections(payload ? payload.connection_status : {}, payload ? payload.bridges : {});\n                    renderBridges(payload ? payload.bridges : {}, statusPayload);\n                    syncHostLayout();\n                })\n                .catch(function () {\n                    text('[data-nh-node-status]', 'Unknown');\n                    text('[data-nh-updated]', '-');\n                    var pill = root.querySelector('[data-nh-node-pill]');\n                    if (pill) pill.className = 'nh-node-status-pill nh-state-unknown';\n                    renderConnections({});\n                    renderBridges({}, null);\n                    syncHostLayout();\n                });\n        }\n\n        refresh();\n        window.setInterval(refresh, 2000);\n        window.addEventListener('resize', syncHostLayout);\n    })();\n    <\/script><br \/>\n<!-- \/wp:shortcode --><\/p>\n<style id=\"netoholics-connection-first-column-wrap-20260923\">\nbody.page-id-647 .nh-connection-table td:first-child,\nbody.page-id-647 .nh-connection-table td:first-child .nh-lookup-link {\n    min-width: 0;\n    overflow-wrap: anywhere;\n    white-space: normal;\n}\n<\/style>\n<\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-647","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/pages\/647","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/comments?post=647"}],"version-history":[{"count":4,"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/pages\/647\/revisions"}],"predecessor-version":[{"id":770,"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/pages\/647\/revisions\/770"}],"wp:attachment":[{"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/media?parent=647"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}