new-site/site/public/js/pw-crtc-collapse.js

158 lines
6.6 KiB
JavaScript

/*
* pw-crtc-collapse.js — progressive-enhancement reading aid for the long
* Canada CRTC carrier page (public/services/telecom/canada-crtc/index.html).
*
* Why a runtime script instead of editing the HTML: that page is a single
* 183 KB minified file with auto-generated markup whose section headings sit
* at inconsistent DOM depths. Rewriting it by hand risks corrupting the markup
* and is hard to maintain. This script enhances it non-destructively:
*
* 1. Injects a "5-minute read" TL;DR + jump menu at the top of the article.
* 2. Collapses a curated set of deep-detail H2 sections into <details>-style
* accordions (collapsed by default) so the page reads short, while every
* word remains present for SEO and for anyone who expands it.
*
* It is idempotent (guards against double-run) and degrades gracefully: with
* JS off, the full page renders exactly as before.
*/
(function () {
"use strict";
if (window.__pwCrtcCollapse) return;
window.__pwCrtcCollapse = true;
// H2 headings (matched by normalized text prefix) to collapse by default.
// Order here is also the order used in the TL;DR jump menu.
var COLLAPSE = [
{ key: "why canada", label: "Why Canada: FCC vs CRTC" },
{ key: "corporate tax comparison", label: "Corporate tax comparison (BC vs US)" },
{ key: "canada telecom m&a", label: "Canada telecom M&A climate" },
{ key: "step 6: canadian business banking", label: "Canadian business banking" },
{ key: "frequently asked questions", label: "FAQ", open: false },
{ key: "us wholesale voice market", label: "US wholesale / DID restrictions" },
{ key: "the growing burden", label: "The growing burden of a US carrier" },
{ key: "what you'll need", label: "What you'll need to get started" }
];
function norm(s) {
return (s || "").replace(/\s+/g, " ").trim().toLowerCase();
}
function ready(fn) {
if (document.readyState !== "loading") fn();
else document.addEventListener("DOMContentLoaded", fn);
}
ready(function () {
var h2s = Array.prototype.slice.call(document.querySelectorAll("h2"));
if (!h2s.length) return;
var made = [];
COLLAPSE.forEach(function (cfg) {
var h2 = h2s.find(function (h) {
return norm(h.textContent).indexOf(cfg.key) === 0 ||
norm(h.textContent).indexOf(cfg.key) !== -1;
});
if (!h2 || h2.dataset.pwCollapsed) return;
var parent = h2.parentNode;
// Collect this H2 plus its following siblings up to the next H2 sibling.
var nodes = [h2];
var n = h2.nextSibling;
while (n) {
if (n.nodeType === 1 && n.tagName === "H2") break;
var next = n.nextSibling;
// Skip (don't absorb) a standalone block such as the "is this real?"
// proof expander — it must stay visible outside any collapsed body.
// We leave it in place; surrounding siblings still get collected, so it
// ends up rendered right after this accordion.
if (!(n.nodeType === 1 && n.classList &&
n.classList.contains("pw-standalone"))) {
nodes.push(n);
}
n = next;
}
if (nodes.length < 2) {
// Heading has no sibling body at this level (body nested elsewhere);
// skip rather than risk an empty/incorrect accordion.
return;
}
// Guard against card-wrapped headings whose body is NOT a following
// sibling (e.g. an H2 alone in a flex header row inside a styled card).
// In that case we'd collect only the heading and produce an accordion
// that expands to nothing. Measure the real body text (excluding the H2)
// and bail out, leaving the section inline, if there's nothing to show.
var bodyText = nodes.reduce(function (acc, node) {
if (node === h2) return acc;
return acc + (node.textContent || "");
}, "").replace(/\s+/g, " ").trim();
if (bodyText.length < 200) return;
var details = document.createElement("details");
details.className = "pw-collapse";
if (cfg.open) details.open = true;
var summary = document.createElement("summary");
summary.className = "pw-collapse-summary";
summary.textContent = h2.textContent.replace(/\s+/g, " ").trim();
details.appendChild(summary);
var body = document.createElement("div");
body.className = "pw-collapse-body";
// Move the original H2 (hidden, keeps heading in DOM for SEO) + siblings in.
h2.style.display = "none";
h2.dataset.pwCollapsed = "1";
parent.insertBefore(details, nodes[0]);
nodes.forEach(function (node) { body.appendChild(node); });
details.appendChild(body);
var id = "crtc-" + cfg.key.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
details.id = id;
made.push({ id: id, label: cfg.label });
});
if (!made.length) return;
// Build the TL;DR + jump menu and insert it right after the hero/pricing card.
var anchor = document.querySelector("nav[aria-label], .max-w-4xl") || document.body;
var tldr = document.createElement("div");
tldr.className = "pw-tldr";
tldr.innerHTML =
'<div class="pw-tldr-head">' +
'<span class="pw-tldr-badge">5-min read</span>' +
'<strong>The short version</strong></div>' +
'<p class="pw-tldr-lead">Become a single <strong>Canadian CRTC/BITS carrier</strong> ' +
'and lawfully serve US &amp; international customers \u2014 without taking on the US ' +
'FCC&nbsp;499/USF, RMD, STIR/SHAKEN and state-PUC stack a US carrier carries. ' +
'A Canadian carrier serving US customers as one entity is a common, established ' +
'structure (per public FCC records). Details below are optional \u2014 tap any to expand.</p>' +
'<div class="pw-tldr-jump"><span>Jump to:</span></div>';
var jump = tldr.querySelector(".pw-tldr-jump");
made.forEach(function (m) {
var a = document.createElement("a");
a.href = "#" + m.id;
a.className = "pw-tldr-chip";
a.textContent = m.label;
a.addEventListener("click", function (e) {
var d = document.getElementById(m.id);
if (d) {
d.open = true;
e.preventDefault();
d.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
jump.appendChild(a);
});
// Insert TL;DR before the first collapsed section for a clean top-of-article placement.
var firstDetails = document.querySelector("details.pw-collapse");
if (firstDetails && firstDetails.parentNode) {
firstDetails.parentNode.insertBefore(tldr, firstDetails);
} else {
anchor.insertBefore(tldr, anchor.firstChild);
}
});
})();