From 944b0deb72028ddfb68f72ace667e8f6ed97f219 Mon Sep 17 00:00:00 2001 From: Bridge Node 7 Date: Wed, 5 Aug 2026 22:25:59 -0700 Subject: [PATCH] fix(site): harden Partner UX and navigation validation --- docs/VISUAL_CONTRACT.md | 8 ++++++ partner/index.html | 3 +- scripts/validate_site.py | 59 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/VISUAL_CONTRACT.md b/docs/VISUAL_CONTRACT.md index ec02736..a5751d4 100644 --- a/docs/VISUAL_CONTRACT.md +++ b/docs/VISUAL_CONTRACT.md @@ -63,3 +63,11 @@ Structural tests support release review, while visual quality remains a separate - Independent-policy disclosure remains adjacent to the policy reference - No ordinary executable JavaScript, remote runtime dependencies, or protected company information - Responsive single-column mobile transformation and direct-preview behavior + +## Protected Partner journey + +- **Partner** is the global public pathway for aligned organizations and remains present exactly once in every primary navigation. +- The homepage **Strategic Partners** orbital node and Convergence card both route to Partner. +- Partner and Golden Age cross-link contextually while Golden Age remains outside primary navigation. +- Partner is marked as the current page only on `/partner/`. +- Sticky-anchor destinations remain fully visible below the navigation at desktop and mobile widths. diff --git a/partner/index.html b/partner/index.html index ade4773..baeb2f7 100644 --- a/partner/index.html +++ b/partner/index.html @@ -28,6 +28,7 @@ } *{box-sizing:border-box} html{scroll-behavior:smooth} +section[id]{scroll-margin-top:110px} body{ margin:0;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:var(--text); background:radial-gradient(circle at 14% 7%,rgba(120,215,255,.15),transparent 28rem),radial-gradient(circle at 86% 13%,rgba(198,167,255,.14),transparent 30rem),radial-gradient(circle at 50% 52%,rgba(241,200,107,.08),transparent 36rem),linear-gradient(180deg,var(--bg),#071425 48%,#04070f);overflow-x:hidden @@ -104,7 +105,7 @@ .footer a{color:#fff;display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:6px 4px} .footer p{line-height:1.7;margin:8px 0} @media(max-width:920px){.grid-4{grid-template-columns:repeat(2,1fr)}.pathway{grid-template-columns:repeat(3,1fr)}.pathway>div:first-child{grid-column:1/-1;min-height:auto;border-bottom:1px solid var(--line)}.pathway>div:nth-child(2){border-left:0}} -@media(max-width:700px){.nav-inner{padding:12px 16px;flex-direction:column;justify-content:center}.brand{font-size:11.5px;gap:9px}.mark{width:34px;height:34px}.nav-links{width:100%;justify-content:center;gap:6px}.nav-links a{font-size:12px;padding:8px 9px}.shell{padding:0 16px}.hero{padding:54px 0 44px}.actions{display:grid}.btn{width:100%}.pathway,.grid-3,.grid-4,.platform{grid-template-columns:1fr}.pathway>div:first-child{grid-column:auto}.pathway>div+div,.pathway>div:nth-child(2),.platform article+article{border-left:0;border-top:1px solid var(--line)}section{padding:56px 0}.card,.platform article{min-height:auto}.inquiry-box{padding:28px 20px}} +@media(max-width:700px){section[id]{scroll-margin-top:150px}.nav-inner{padding:12px 16px;flex-direction:column;justify-content:center}.brand{font-size:11.5px;gap:9px}.mark{width:34px;height:34px}.nav-links{width:100%;justify-content:center;gap:6px}.nav-links a{font-size:12px;padding:8px 9px}.shell{padding:0 16px}.hero{padding:54px 0 44px}.actions{display:grid}.btn{width:100%}.pathway,.grid-3,.grid-4,.platform{grid-template-columns:1fr}.pathway>div:first-child{grid-column:auto}.pathway>div+div,.pathway>div:nth-child(2),.platform article+article{border-left:0;border-top:1px solid var(--line)}section{padding:56px 0}.card,.platform article{min-height:auto}.inquiry-box{padding:28px 20px}} @media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.btn{transition:none}.btn:hover{transform:none}} diff --git a/scripts/validate_site.py b/scripts/validate_site.py index 9e5e5cf..0417c3f 100644 --- a/scripts/validate_site.py +++ b/scripts/validate_site.py @@ -165,6 +165,20 @@ "404.html": "privacy/index.html#security", } +EXPECTED_PARTNER_NAV = { + "index.html": "partner/index.html", + "materials/index.html": "../partner/index.html", + "readiness/index.html": "../partner/index.html", + "partner/index.html": "../partner/index.html", + "golden-age/index.html": "../partner/index.html", + "privacy/index.html": "../partner/index.html", + "404.html": "partner/index.html", +} +HOME_PARTNER_NODE = '' +HOME_PARTNER_CARD = '' +PARTNER_GOLDEN_AGE_LINK = 'Explore A New Golden Age' +GOLDEN_AGE_PARTNER_LINK = 'Partner with Bridge Node 7' + class PageParser(HTMLParser): def __init__(self) -> None: super().__init__(convert_charrefs=True) @@ -208,6 +222,22 @@ def handle_endtag(self, tag: str) -> None: self._script_text = [] + +def primary_nav_links(text: str) -> list[tuple[str, str, bool]] | None: + match = re.search(r']*aria-label="Primary navigation"[^>]*>(.*?)', text, re.I | re.S) + if not match: + return None + links: list[tuple[str, str, bool]] = [] + for attrs, body in re.findall(r']*)>(.*?)', match.group(1), re.I | re.S): + href_match = re.search(r'\bhref=(["\'])(.*?)\1', attrs, re.I | re.S) + href = href_match.group(2) if href_match else "" + current = bool(re.search(r'\baria-current=(["\'])page\1', attrs, re.I | re.S)) + label = re.sub(r'<[^>]+>', '', body) + label = re.sub(r'\s+', ' ', label).strip() + links.append((label, href, current)) + return links + + def fail(errors: list[str], msg: str) -> None: errors.append(msg) @@ -331,6 +361,22 @@ def main() -> int: if '
int: fail(errors, f"index.html: expected 22 radial path lines, found {radial_path_count}") if domain_node_count != 7: fail(errors, f"index.html: expected 7 domain nodes, found {domain_node_count}") + + if home.count(HOME_PARTNER_NODE) != 1: + fail(errors, f"index.html: expected exactly one Strategic Partners orbital link to Partner, found {home.count(HOME_PARTNER_NODE)}") + if home.count(HOME_PARTNER_CARD) != 1: + fail(errors, f"index.html: expected exactly one Strategic Partners card link to Partner, found {home.count(HOME_PARTNER_CARD)}") + partner_text = (ROOT / "partner/index.html").read_text(encoding="utf-8") + if partner_text.count(PARTNER_GOLDEN_AGE_LINK) != 1: + fail(errors, "partner/index.html: exact Golden Age pathway missing") + golden_age_text = (ROOT / "golden-age/index.html").read_text(encoding="utf-8") + if golden_age_text.count(GOLDEN_AGE_PARTNER_LINK) != 1: + fail(errors, "golden-age/index.html: exact Partner pathway missing") if MAIL_SUBJECT not in home: fail(errors, "index.html: full strategic inquiry interaction missing") if "

Strategic Inquiry

" in home: @@ -372,6 +429,8 @@ def main() -> int: print(f"Required files: {len(REQUIRED)}") print("Direct-preview paths: PASS") print("Protected homepage interactions: PASS") + print("Protected Partner navigation: PASS") + print("Protected Partner and Golden Age journey: PASS") print("Protected content-page experience: PASS") print("Remote runtime dependencies: NONE") print("Public material-specific routes: NONE")