{"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-11-07T00:41:46","modified_gmt":"2026-11-07T06:41:46","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-public-status .nh-bridge-card dl > div > 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-public-status .nh-bridge-card,\n        .nh-public-status .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-public-status .nh-bridge-column {\n            display: grid;\n            gap: 10px;\n            min-width: 0;\n        }\n        .nh-public-status .nh-bridge-card {\n            min-height: 116px;\n            padding: 10px;\n        }\n        .nh-public-status .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-public-status .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-public-status .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-public-status .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-public-status .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-public-status .nh-bridge-card dl {\n            display: grid;\n            grid-template-columns: 1fr;\n            gap: 6px;\n            margin: 0;\n        }\n        .nh-public-status .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-public-status .nh-bridge-clients {\n            padding: 0;\n            overflow: visible;\n        }\n        .nh-public-status .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-public-status .nh-bridge-clients__count {\n            color: #58ddff;\n            font-size: 12px;\n            font-weight: 900;\n        }\n        .nh-public-status .nh-client-list {\n            display: grid;\n            overflow: visible;\n        }\n        .nh-public-status .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-public-status .nh-client-row:first-child { border-top: 0; }\n        .nh-public-status .nh-client-row[data-nh-url] {\n            cursor: pointer;\n        }\n        .nh-public-status .nh-client-row[data-nh-url]:hover,\n        .nh-public-status .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-public-status .nh-client-row[data-nh-url]:hover .nh-client-call,\n        .nh-public-status .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-public-status .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-public-status .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-public-status .nh-client-id {\n            color: #9fd7e9;\n            font-size: 11px;\n            line-height: 1.2;\n            white-space: nowrap;\n        }\n        .nh-public-status .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 hiddenBridgeNodes = { '1883': true, '1884': true, '1885': true, '1886': true, '1887': true, '1888': true, '1889': true, '1890': true, '1001': true, 'BCSTFY': true, '674982': true, '641894': true, '674980': true };\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            var state = service && service.state ? service.state : (item && item.status ? item.status : 'unknown');\n            if (state === 'unknown' && item && item.status === 'online') return 'online';\n            return state;\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 renderPublicHubTalkers(talkers) {\n            var names = {kentucky:'Kentucky', phoenix:'Phoenix', georgia:'Georgia'};\n            var events = (Array.isArray(talkers) ? talkers : []).filter(function (entry) {\n                return entry && !hiddenBridgeNodes[String(entry.node || '')];\n            }).map(function (entry) {\n                var hubId = String(entry.hubId || '').toLowerCase();\n                return {\n                    hub: names[hubId] || String(entry.heardOn || hubId),\n                    node: String(entry.node || ''),\n                    callsign: String(entry.callsign || ''),\n                    description: String(entry.description || ''),\n                    duration: String(entry.duration || '-'),\n                    current: entry.current === true,\n                    eventEpoch: Number(entry.eventEpoch || entry.startedEpoch || 0),\n                    startedEpoch: Number(entry.startedEpoch || 0)\n                };\n            });\n            events.sort(function (a,b) {\n                return Number(b.current) - Number(a.current) || b.eventEpoch - a.eventEpoch;\n            });\n            var current = events.find(function (entry) { return entry.current; });\n            var history = events.filter(function (entry) { return !entry.current; }).slice(0, 4);\n            var cards = [current || {callsign:'-', description:'No one talking', hub:'', duration:'-'}].concat(history);\n            while (cards.length < 5) cards.push({callsign:'-', description:'No recent talker', hub:'', duration:'-'});\n            return '<div class=\"nh-last-talkers\">' + cards.map(function (entry, index) {\n                var identity = entry.callsign || (entry.node ? 'Node ' + entry.node : '-');\n                return '<div class=\"nh-talker-card\"><span>' + (index === 0 ? 'Current Talker' : 'Last Talker ' + index) + '<\/span>' +\n                    '<strong>' + escapeHtml(identity) + '<\/strong><small>' +\n                    escapeHtml(entry.description || (entry.node ? 'Node ' + entry.node : 'No recent talker')) + '<br>' +\n                    escapeHtml(entry.hub) + ' \u00b7 ' + escapeHtml(entry.duration) + '<\/small><\/div>';\n            }).join('') + '<\/div>';\n        }\n\n        function renderConnections(connectionStatus, bridges) {\n            var data = connectionStatus || {};\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 hubData = (Date.now() - Number(window.__nhPublicHubReceivedAt || 0) < 180000) ? window.__nhPublicHubData : null;\n            if (hubData && Array.isArray(hubData.hubs) && hubData.hubs.length === 3) {\n                var hubNames = { kentucky: 'Kentucky', phoenix: 'Phoenix', georgia: 'Georgia' };\n                var hubTables = hubData.hubs.map(function (hub) {\n                    var hubConnections = (Array.isArray(hub.connections) ? hub.connections : []).filter(function (entry) {\n                        return entry && !hiddenBridgeNodes[String(entry.node || '')];\n                    });\n                    var hubRows = hubConnections.map(function (entry) {\n                        return '<tr><td data-label=\"Connections\">' + lookupConnectionLink(entry) + '<\/td>' +\n                            '<td data-label=\"Station Info\">' + lookupLabelLink(entry) + '<\/td>' +\n                            '<td data-label=\"Dir\">' + escapeHtml(entry.direction || '-') + '<\/td>' +\n                            '<td data-label=\"Keyed\">' + (entry.keyed === true || String(entry.keyed).toLowerCase() === 'yes' ? 'Yes' : 'No') + '<\/td>' +\n                            '<td data-label=\"Connected\">' + escapeHtml(entry.connected || '-') + '<\/td>' +\n                            '<td data-label=\"Last Keyed\">' + escapeHtml(entry.received || '-') + '<\/td><\/tr>';\n                    }).join('');\n                    var heading = escapeHtml(hubNames[hub.id] || hub.id) + ' \u2014 ' + hubConnections.length + ' connected';\n                    if (hub.stale || !hub.available) {\n                        return '<h4 class=\"nh-public-status__section-title\">' + heading + '<\/h4><p>Connection data temporarily unavailable.<\/p>';\n                    }\n                    return '<h4 class=\"nh-public-status__section-title\">' + heading + '<\/h4>' +\n                        (hubRows ? '<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>' + hubRows + '<\/tbody><\/table>' :\n                        '<p class=\"nh-public-status__empty\">No connected stations reported right now.<\/p>');\n                }).join('');\n                connectionWrap.innerHTML = '<h3 class=\"nh-public-status__section-title\">Connection Status<\/h3>' +\n                    '<p class=\"nh-connection-hint\">Click a station\/callsign for QRZ lookup.<\/p>' + renderPublicHubTalkers(hubData.talkers) + hubTables + renderMapPanel('674982');\n                initializeOpenStationMaps();\n                syncHostLayout();\n                return;\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                    if (!window.__nhPublicHubFetchPending && (!window.__nhPublicHubLastFetch || Date.now() - window.__nhPublicHubLastFetch > 15000)) {\n                        window.__nhPublicHubFetchPending = true;\n                        window.__nhPublicHubLastFetch = Date.now();\n                        fetch('\/index.php\/wp-json\/netoholics\/v1\/public-hub-connections', {cache:'no-store', credentials:'same-origin'})\n                            .then(function (response) {\n                                if (!response.ok) throw new Error('hub feed unavailable');\n                                return response.json();\n                            })\n                            .then(function (value) {\n                                var ids = value && Array.isArray(value.hubs) ? value.hubs.map(function (hub) {\n                                    return String(hub && hub.id || '');\n                                }).sort().join(',') : '';\n                                var generatedEpoch = Number(value && value.generatedEpoch || 0);\n                                var sourceAge = Math.floor(Date.now() \/ 1000) - generatedEpoch;\n                                if (!value || value.schema !== 'netoholics-public-hubs-v1' ||\n                                        ids !== 'georgia,kentucky,phoenix' || !Array.isArray(value.talkers) ||\n                                        generatedEpoch <= 0 || sourceAge < -30 || sourceAge > 180) {\n                                    throw new Error('hub feed invalid or stale');\n                                }\n                                window.__nhPublicHubData = value;\n                                window.__nhPublicHubReceivedAt = Date.now();\n                                renderConnections(payload ? payload.connection_status : {}, payload ? payload.bridges : {});\n                            })\n                            .catch(function () {})\n                            .finally(function () { window.__nhPublicHubFetchPending = false; });\n                    }\n                    renderConnections(payload ? payload.connection_status : {}, payload ? payload.bridges : {});\n                    renderBridges(payload ? payload.bridges : {}, statusPayload);\n                    syncHostLayout();\n                })\n                .catch(function () {\n                    if (statusGeneratedEpoch > 0) return;\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-live-status-page-css-20261106\">body.page-id-647 .nh-connection-table td:first-child,body.page-id-647 .nh-connection-table td:first-child .nh-lookup-link{min-width:0;overflow-wrap:anywhere;white-space:normal}body.page-id-647 .nh-connection-counts{align-items:baseline}body.page-id-647 .nh-connection-counts span:first-child{color:#eaf9ff;font-weight:800}body.page-id-647:not(.nh-talker-first-paint-fallback) .nh-last-talkers:not([data-nh-six-talkers=\"true\"]){visibility:hidden!important}body.page-id-647 .nh-talker-card{position:relative;display:flex;flex-direction:column}body.page-id-647 .nh-talker-card>span:first-child{flex:0 0 auto;padding-right:64px}body.page-id-647 .nh-talker-card>strong{display:block;margin:4px 0 3px;line-height:1.05}body.page-id-647 .nh-talker-card>small{display:flex;flex:1;flex-direction:column;justify-content:space-between;min-height:0;line-height:1.1}body.page-id-647 .nh-talker-detail-line{display:block}body.page-id-647 .nh-talker-card strong a{color:inherit;text-decoration:none}body.page-id-647 .nh-talker-card strong a:hover,body.page-id-647 .nh-talker-card strong a:focus-visible{text-decoration:underline}body.page-id-647 .nh-talker-local-time{position:absolute;top:10px;right:12px;color:#8fdff5;font-size:11px;font-weight:700;line-height:1}body.page-id-647 .nh-bridge-clients{background:#0d1317;overflow:hidden}body.page-id-647 .nh-bridge-clients__title{width:100%;-webkit-appearance:none;appearance:none;background:transparent;border:0;border-bottom:1px solid var(--nh-line);border-radius:0;box-shadow:none;font:inherit;text-align:left;cursor:pointer}body.page-id-647 .nh-bridge-clients__title:hover,body.page-id-647 .nh-bridge-clients__title:focus-visible{background:rgba(88,221,255,.09);outline:1px solid rgba(88,221,255,.32);outline-offset:-1px}body.page-id-647 .nh-bridge-clients__arrow{color:#58ddff;font-size:14px;line-height:1;transition:transform .16s ease}body.page-id-647 .nh-bridge-clients__title[aria-expanded=\"true\"] .nh-bridge-clients__arrow{transform:rotate(180deg)}body.page-id-647 .nh-bridge-clients>[hidden]{display:none!important}body.page-id-647:not(.nh-status-source-ready) [data-nh-node-pill],body.page-id-647:not(.nh-status-source-ready) .nh-bridge-card{visibility:hidden!important}@media (max-width:760px){body.page-id-647 .nh-connection-counts{display:grid;gap:7px;grid-template-columns:repeat(2,minmax(0,1fr))}body.page-id-647 .nh-connection-counts span:first-child{grid-column:1\/-1}}<\/style>\n<p><script id=\"netoholics-asr-connection-summary-20261013\">(function(){var labels={dmr:'DMR',ysf:'YSF',p25:'P25',nxdn:'NXDN',m17:'M17',zello:'Zello',dstar:'D-Star'};var order=['dmr','ysf','p25','nxdn','m17','zello','dstar'];function count(value){value=Number(value);return Number.isFinite(value)?Math.max(0,Math.floor(value)):0}function render(){var payload=window.__nhLatestPublicStatus;var target=document.querySelector('[data-nh-connections] .nh-connection-counts');if(!payload||!target)return;var status=payload.connection_status||{};var bridges=payload.bridges||{};var direct=count(status.direct_link_count);var adjacent=count(status.adjacent_link_count);var nodes=Array.isArray(status.nodes)?status.nodes:[];var echo=count(status.echolink_link_count)||nodes.filter(function(node){return String(node.direction||'').toUpperCase()==='ECHOLINK'}).length;var aslDirect=Math.max(0,direct-echo);var digitalTotal=0;var parts=[];order.forEach(function(mode){var value=count((bridges[mode]||{}).client_count);if(!value)return;digitalTotal+=value;parts.push({label:labels[mode],count:value})});var total=direct+adjacent+digitalTotal;var html='<span><strong>'+total+'<\/strong> total connections<\/span>'+'<span><strong>'+aslDirect+'<\/strong> ASL direct<\/span>'+(echo?'<span><strong>'+echo+'<\/strong> EchoLink<\/span>':'')+parts.map(function(part){return '<span><strong>'+part.count+'<\/strong> '+part.label+'<\/span>'}).join('')+'<span><strong>'+adjacent+'<\/strong> adjacent<\/span>';if(target.innerHTML!==html){target.innerHTML=html;target.setAttribute('aria-label',total+' total connections: '+[aslDirect+' ASL direct'].concat(echo?[echo+' EchoLink']:[],parts.map(function(part){return part.count+' '+part.label}),[adjacent+' adjacent']).join(', '))}}function schedule(){window.setTimeout(render,0)}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',schedule)}else{schedule()}var root=document.querySelector('[data-nh-public-status]');if(root){new MutationObserver(schedule).observe(root,{childList:true,subtree:true})}window.setInterval(render,2000)}());<\/script><script id=\"netoholics-talker-card-first-paint-20261013\">(function(){var done=false,attempts=0,timer=0;function promote(){var wrap=document.querySelector('[data-nh-connections]');if(!wrap)return false;if(wrap.querySelector('.nh-last-talkers[data-nh-six-talkers=\"true\"]')){done=true;return true}if(!window.__nhLatestPublicStatus||!wrap.querySelector('.nh-last-talkers'))return false;wrap.innerHTML=wrap.innerHTML;done=!!wrap.querySelector('.nh-last-talkers[data-nh-six-talkers=\"true\"]');return done}function fallback(){if(!done&&document.body)document.body.classList.add('nh-talker-first-paint-fallback')}function tick(){attempts+=1;if(promote()||attempts>=120){window.clearInterval(timer);if(!done)fallback()}}timer=window.setInterval(tick,25);tick();window.setTimeout(fallback,3000)}());<\/script><script id=\"netoholics-bridge-client-accordion-20261101\">(function(){if(window.__nhBridgeClientAccordionInstalled)return;window.__nhBridgeClientAccordionInstalled=true;var descriptor=Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML');if(!descriptor||!descriptor.get||!descriptor.set)return;var bridgeKeys=['dmr','ysf','p25','m17','nxdn','zello'];function body(panel){return panel&&panel.querySelector('.nh-client-list,.nh-public-status__empty')}function setOpen(column,open){var button=column&&column.querySelector('.nh-bridge-clients__title');var content=body(column&&column.querySelector('.nh-bridge-clients'));if(button)button.setAttribute('aria-expanded',open?'true':'false');if(content)content.hidden=!open}function syncLayout(){window.dispatchEvent(new Event('resize'));window.requestAnimationFrame(function(){window.dispatchEvent(new Event('resize'))})}function renderTalkerDetails(card,entry,node,person){var strong=card.querySelector('strong');if(strong&&person&&person!=='-'&&!\/^[0-9]+$\/.test(person)){var link=strong.querySelector('a');if(!link){strong.textContent='';link=document.createElement('a');link.target='_blank';link.rel='noopener';strong.appendChild(link)}link.href='https:\/\/www.qrz.com\/db\/'+encodeURIComponent(person);link.textContent=person;link.setAttribute('aria-label','Look up '+person+' on QRZ')}var small=card.querySelector('small');if(small){var talkerLocation=String(entry.location||entry.qrz_location||entry.label_location||'').trim()||'Location not reported';var direction=String(entry.direction||'').toUpperCase();var directionLabel=direction==='ECHOLINK'||(direction==='REMOTE'&&node===person)?'EchoLink':direction==='REMOTE'?'Linked Node':direction==='IN'?'Local RF':(direction||'Link');var duration=String(entry.last_keyed||'').trim();var nodeLabel=node&&node!==person?'Node '+node:(direction==='ECHOLINK'||(direction==='REMOTE'&&node===person)?'EchoLink connection':direction==='REMOTE'?'Linked node':directionLabel);var lines=[nodeLabel,talkerLocation,directionLabel+(duration?' \u2022 '+duration:'')];small.textContent='';lines.forEach(function(line){var detail=document.createElement('span');detail.className='nh-talker-detail-line';detail.textContent=line;small.appendChild(detail)})}var stamp=card.querySelector('.nh-talker-local-time');var eventTime=String(entry.event_time||'').trim();if(eventTime){var eventDate=new Date(eventTime);if(!Number.isNaN(eventDate.getTime())){if(!stamp){stamp=document.createElement('time');stamp.className='nh-talker-local-time';card.appendChild(stamp)}stamp.dateTime=eventTime;stamp.textContent=eventDate.toLocaleTimeString([],{hour:'numeric',minute:'2-digit'});stamp.title=eventDate.toLocaleString()}}}function patchTalkers(root){var payload=window.__nhLatestPublicStatus||{};var status=payload.connection_status||{};var nodes=Array.isArray(status.nodes)?status.nodes:[];var history=Array.isArray(status.last_talkers)?status.last_talkers:[];var keyed=String(status.keyed_node||'-');var current=nodes.find(function(node){return String(node.keyed||'').toLowerCase()==='yes'||(keyed!=='-'&&String(node.node||'')===keyed)})||null;var entries=[current];history.slice(0,5).forEach(function(entry){entries.push(entry)});var talkers=root.querySelector('.nh-last-talkers');var cards=Array.from(root.querySelectorAll('.nh-last-talkers>.nh-talker-card'));while(talkers&&cards.length&&cards.length<6){var clone=cards[cards.length-1].cloneNode(true);var marker=clone.querySelector('span');if(marker)marker.textContent='Last Talker '+cards.length;talkers.appendChild(clone);cards.push(clone)}if(talkers&&cards.length===6)talkers.setAttribute('data-nh-six-talkers','true');cards.forEach(function(card,index){var entry=entries[index];if(!entry)return;var node=String(entry.node||'');var liveNode=nodes.find(function(item){return String(item.node||'')===node})||{};var person=String(entry.person||entry.callsign||'').trim();var label=(String(entry.label||'')+' '+String(liveNode.label||'')).toUpperCase();var match=label.match(\/[A-Z]{1,2}[0-9][A-Z0-9]{1,4}\/);if((!person||person==='-'||\/^[0-9]+$\/.test(person))&&match)person=match[0];var strong=card.querySelector('strong');if(strong&&person&&person!=='-'&&!\/^[0-9]+$\/.test(person))strong.textContent=person;renderTalkerDetails(card,entry,node,person);if(node!=='1002')return;var source=String(entry.source||entry.source_type||entry.network||entry.protocol||entry.transport||entry.label||'').trim();var location=String(entry.location||entry.qrz_location||entry.label_location||'Zello ASL Bridge').trim();var sourceEl=card.querySelector('.nh-talker-source');var locationEl=card.querySelector('.nh-talker-location');if(sourceEl&&source)sourceEl.textContent=source+' \u2022 '+node;if(locationEl&&location)locationEl.textContent=location})}function prepare(root){var payload=window.__nhLatestPublicStatus||{};root.querySelectorAll('.nh-bridge-column').forEach(function(column,index){column.querySelectorAll('.nh-bridge-card dl>div').forEach(function(row){var label=row.querySelector('span');if(label&&label.textContent.trim()==='Clients')row.remove()});var panel=column.querySelector('.nh-bridge-clients');var title=panel&&panel.querySelector('.nh-bridge-clients__title');if(title&&title.tagName!=='BUTTON'){var button=document.createElement('button');button.type='button';button.className=title.className;button.innerHTML=title.innerHTML+'<span class=\"nh-bridge-clients__arrow\" aria-hidden=\"true\">\u25be<\/span>';title.replaceWith(button)}var bridge=payload.bridges&&payload.bridges[bridgeKeys[index]];if(bridge){var rows=Array.from(column.querySelectorAll('.nh-bridge-card dl>div'));var destination=rows.find(function(row){var rowLabel=row.querySelector('span');return rowLabel&&rowLabel.textContent.trim()!=='Status'});if(destination){var destinationLabel=destination.querySelector('span');var destinationValue=destination.querySelector('dd');if(destinationLabel)destinationLabel.textContent=bridge.destination_label||'Channel';if(destinationValue)destinationValue.textContent=bridgeKeys[index]==='m17'?'M17-NET':String(bridge.channel||'-')}var heading=column.querySelector('.nh-bridge-clients__title>span:first-child');if(heading)heading.textContent=bridgeKeys[index]==='zello'?(bridge.count_label||'Recent Talkers'):bridgeKeys[index].toUpperCase()+' '+(bridge.count_label||'Clients')}setOpen(column,false)})}function attrs(oldEl,newEl){Array.from(oldEl.attributes).forEach(function(a){if(!newEl.hasAttribute(a.name))oldEl.removeAttribute(a.name)});Array.from(newEl.attributes).forEach(function(a){if(oldEl.getAttribute(a.name)!==a.value)oldEl.setAttribute(a.name,a.value)})}function sync(oldNode,newNode){if(!oldNode||!newNode)return;if(oldNode.nodeType!==newNode.nodeType||(oldNode.nodeType===1&&oldNode.tagName!==newNode.tagName)){oldNode.replaceWith(newNode.cloneNode(true));return}if(oldNode.nodeType===3){if(oldNode.nodeValue!==newNode.nodeValue)oldNode.nodeValue=newNode.nodeValue;return}attrs(oldNode,newNode);var oldChildren=Array.from(oldNode.childNodes);var newChildren=Array.from(newNode.childNodes);var common=Math.min(oldChildren.length,newChildren.length);for(var i=0;i<common;i+=1)sync(oldChildren[i],newChildren[i]);for(var j=oldChildren.length-1;j>=newChildren.length;j-=1)oldNode.childNodes[j].remove();for(var k=common;k<newChildren.length;k+=1)oldNode.appendChild(newChildren[k].cloneNode(true))}Object.defineProperty(Element.prototype,'innerHTML',{configurable:true,enumerable:descriptor.enumerable,get:descriptor.get,set:function(value){if(this&&this.matches&&this.matches('[data-nh-connections]')&&typeof value==='string'){var stableCounts=this.querySelector('.nh-connection-counts');var connectionTemp=document.createElement('div');descriptor.set.call(connectionTemp,value);Array.from(connectionTemp.querySelectorAll('tbody tr')).forEach(function(row){var cells=Array.from(row.querySelectorAll('td')).map(function(cell){return String(cell.textContent||'').trim()});var text=String(row.textContent||'').toLowerCase();if(cells.some(function(cellValue){return cellValue==='1001'||cellValue==='1002'})||text.indexOf('broadcastify')!==-1)row.remove()});patchTalkers(connectionTemp);while(this.firstChild)this.removeChild(this.firstChild);while(connectionTemp.firstChild)this.appendChild(connectionTemp.firstChild);var nextCounts=this.querySelector('.nh-connection-counts');if(stableCounts&&nextCounts)nextCounts.replaceWith(stableCounts);return}if(!this||!this.matches||!this.matches('[data-nh-bridges]')||typeof value!=='string')return descriptor.set.call(this,value);var temp=document.createElement('div');descriptor.set.call(temp,value);prepare(temp);var incoming=Array.from(temp.querySelectorAll(':scope>.nh-bridge-column'));var current=Array.from(this.querySelectorAll(':scope>.nh-bridge-column'));if(!current.length||current.length!==incoming.length)return descriptor.set.call(this,temp.innerHTML);incoming.forEach(function(next,index){var open=current[index].querySelector('.nh-bridge-clients__title[aria-expanded=\"true\"]')!==null;setOpen(next,open);sync(current[index],next)})}});var root=document.querySelector('[data-nh-bridges]');if(root){root.addEventListener('click',function(event){var button=event.target.closest('.nh-bridge-clients__title');if(!button||!root.contains(button))return;var column=button.closest('.nh-bridge-column');setOpen(column,button.getAttribute('aria-expanded')!=='true');syncLayout()});if(root.children.length)root.innerHTML=descriptor.get.call(root)}}());<\/script><script id=\"netoholics-asr-live-card-adapter-20261105\">(function(){if(window.__nhAsrLiveCardAdapter)return;window.__nhAsrLiveCardAdapter=true;var keys=['dmr','ysf','p25','m17','nxdn','zello'];function setText(element,value){if(!element)return;value=String(value);if(element.textContent!==value)element.textContent=value}function cleanConnections(){var wrap=document.querySelector('[data-nh-connections]');if(!wrap)return;Array.from(wrap.querySelectorAll('tbody tr')).forEach(function(row){var cells=Array.from(row.querySelectorAll('td')).map(function(cell){return String(cell.textContent||'').trim()});var text=String(row.textContent||'').toLowerCase();if(cells.some(function(value){return value==='1001'||value==='1002'})||text.indexOf('broadcastify')!==-1)row.remove()})}function renderTalkerDetails(card,entry,node,person){var strong=card.querySelector('strong');if(strong&&person&&person!=='-'&&!\/^[0-9]+$\/.test(person)){var link=strong.querySelector('a');if(!link){strong.textContent='';link=document.createElement('a');link.target='_blank';link.rel='noopener';strong.appendChild(link)}link.href='https:\/\/www.qrz.com\/db\/'+encodeURIComponent(person);link.textContent=person;link.setAttribute('aria-label','Look up '+person+' on QRZ')}var small=card.querySelector('small');if(small){var talkerLocation=String(entry.location||entry.qrz_location||entry.label_location||'').trim()||'Location not reported';var direction=String(entry.direction||'').toUpperCase();var directionLabel=direction==='ECHOLINK'||(direction==='REMOTE'&&node===person)?'EchoLink':direction==='REMOTE'?'Linked Node':direction==='IN'?'Local RF':(direction||'Link');var duration=String(entry.last_keyed||'').trim();var nodeLabel=node&&node!==person?'Node '+node:(direction==='ECHOLINK'||(direction==='REMOTE'&&node===person)?'EchoLink connection':direction==='REMOTE'?'Linked node':directionLabel);var lines=[nodeLabel,talkerLocation,directionLabel+(duration?' \u2022 '+duration:'')];small.textContent='';lines.forEach(function(line){var detail=document.createElement('span');detail.className='nh-talker-detail-line';detail.textContent=line;small.appendChild(detail)})}var stamp=card.querySelector('.nh-talker-local-time');var eventTime=String(entry.event_time||'').trim();if(eventTime){var eventDate=new Date(eventTime);if(!Number.isNaN(eventDate.getTime())){if(!stamp){stamp=document.createElement('time');stamp.className='nh-talker-local-time';card.appendChild(stamp)}stamp.dateTime=eventTime;stamp.textContent=eventDate.toLocaleTimeString([],{hour:'numeric',minute:'2-digit'});stamp.title=eventDate.toLocaleString()}}}function fixTalkers(payload){var status=payload.connection_status||{};var nodes=Array.isArray(status.nodes)?status.nodes:[];var history=Array.isArray(status.last_talkers)?status.last_talkers:[];var keyed=String(status.keyed_node||'-');var current=nodes.find(function(node){return String(node.keyed||'').toLowerCase()==='yes'||(keyed!=='-'&&String(node.node||'')===keyed)})||null;var entries=[current];history.slice(0,5).forEach(function(entry){entries.push(entry)});var cards=Array.from(document.querySelectorAll('.nh-last-talkers>.nh-talker-card'));cards.forEach(function(card,index){var entry=entries[index];if(!entry)return;var node=String(entry.node||'');var liveNode=nodes.find(function(item){return String(item.node||'')===node})||{};var person=String(entry.person||entry.callsign||'').trim();var label=(String(entry.label||'')+' '+String(liveNode.label||'')).toUpperCase();var match=label.match(\/[A-Z]{1,2}[0-9][A-Z0-9]{1,4}\/);if((!person||person==='-'||\/^[0-9]+$\/.test(person))&&match)person=match[0];var strong=card.querySelector('strong');if(strong&&person&&person!=='-'&&!\/^[0-9]+$\/.test(person))setText(strong,person);renderTalkerDetails(card,entry,node,person);if(node!=='1002')return;var source=String(entry.source||entry.source_type||entry.network||entry.protocol||entry.transport||entry.label||'').trim();var location=String(entry.location||entry.qrz_location||entry.label_location||'Zello ASL Bridge').trim();var sourceEl=card.querySelector('.nh-talker-source');var locationEl=card.querySelector('.nh-talker-location');if(sourceEl&&source)setText(sourceEl,source+' \u2022 '+node);if(locationEl&&location)setText(locationEl,location)})}function apply(){var payload=window.__nhLatestPublicStatus;if(!payload||!payload.bridges)return;fixTalkers(payload);var root=document.querySelector('[data-nh-bridges]');if(!root)return;Array.from(root.querySelectorAll(':scope>.nh-bridge-column')).forEach(function(column,index){var bridge=payload.bridges[keys[index]];if(!bridge)return;var rows=Array.from(column.querySelectorAll('.nh-bridge-card dl>div'));var destination=rows.find(function(row){var label=row.querySelector('span');return label&&label.textContent.trim()!=='Status'});if(destination){var label=destination.querySelector('span');var value=destination.querySelector('dd');setText(label,bridge.destination_label||'Channel');var destinationValue=keys[index]==='m17'?'M17-NET':bridge.channel;if(value&&destinationValue)setText(value,destinationValue)}var title=column.querySelector('.nh-bridge-clients__title>span:first-child');if(title){var mode=keys[index]==='zello'?'Zello':keys[index].toUpperCase();var titleText=keys[index]==='zello'?(bridge.count_label||'Recent Talkers'):mode+' '+(bridge.count_label||'Clients');setText(title,titleText)}});cleanConnections();document.body.classList.add('nh-status-source-ready')}function start(){apply();window.setInterval(apply,1000)}if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start);else start()}());<\/script><\/p>\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":45,"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/pages\/647\/revisions"}],"predecessor-version":[{"id":886,"href":"https:\/\/netoholicsnetwork.net\/index.php\/wp-json\/wp\/v2\/pages\/647\/revisions\/886"}],"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}]}}