Does the new menu give search engines and assistive tech what the current one does, at stage 1 (static prototype in the theme) and stage 2 (WordPress walker)? Verdicts, the fixes that must land first, the sitewide link-equity delta, and the markup contract the walker has to satisfy.
Every menu link is a real <a href> in the server HTML on both surfaces; nothing is injected on open; panels hide with CSS display:none or the hidden attribute and stay in the DOM; no nofollow; JS only adds aria-*. The mobile sheet carries the same 36 hrefs as desktop, so mobile-first indexing sees the whole menu.
Three hard stops before it ships: the preview noindex,nofollow meta, the prototype's click interception that kills every link, and the hidden "Full design" markup carrying ten links to URLs that 404 today. Five further required fixes below.
Server-rendered, page-independent fragments cached in a transient, two passes for desktop and mobile: crawl-neutral by construction. Four plan points would break parity with stage 1 and need amending: <button> tabs (§5.7), the per-item hide flags, rendering "proposed" items whose pages do not exist, and group titles as heading tags.
With the contract below adopted by both stages, nav-parity-check.py proves neutrality mechanically: run it stage 1 vs stage 2 on staging and require exit 0.
For the prototype owner (complete-menu/build.py); the theme drop-in inherits what the build emits.
<meta name="robots" content="noindex,nofollow"> is line 6 of the prototype head. Nothing from that head may be copied. Acceptance: curl -s URL | grep -ci noindex = 0 on /, /business-foreign-exchange and a corridor page, on staging and production.preventDefault() on every a[href] and shows a toast. Delete the dev bar, toast, hero-copy swap, context selector, measure(), placement-hash and setMode() code; ship only dropdown, keyboard, sheet and tab behaviour..mm-inner variants (data-mode="all"|"live" × wide/medium) and each mobile tab two panes. The "all" variants are hidden but in the DOM: 12 extra hrefs, 10 of them 404 today. Emit only live, drop data-mode, replace body:not(.live-only) … [data-mode=all], body.live-only … [data-mode=live]{display:flex} with .mm-inner.mm-lg{display:flex} (same for mm-md). The Proposed pill, its title, data-nopage and Trade Options leave with them. Acceptance: script reports desktop 34 distinct hrefs, mobile 36, all 200./currencies/transfer-aed-to-gbp/ 301s to the slash-less URL on production (both UAs); the page canonical and all 812 sitemap entries are slash-less. Six sitewide links through a redirect on every page. Note: the project CLAUDE.md table shows the slashed form; production disagrees and wins./business-foreign-exchange#industries resolve.No element with id="industries" exists on the hub page. Add the id to the sector-grid row in the same release, or link to the hub without the fragment.<nav> landmark and stop reusing Dante's class names.The sheet is a role="dialog" with tablist and tabpanels and zero <nav> inside. Wrap the tab strip and panes in <nav id="mobile-menu" aria-label="Mobile"> (the theme's sf_mobile_menu() already emits that id). Desktop: rename nav.mega-menu → nav#main-navigation.ct-nav, ul.menu → ul.ct-nav__list, li.menu-item → li.ct-nav__item; Dante's functions.js binds dcMegaMenu to #main-navigation .menu and click handlers to .menu-item a, and the homepage critical CSS has 88 #main-navigation rules. Drop the empty nav.search-nav the parent emits.<h3 class="b-h5"> in the sheet; header.php echoes the mobile menu (line 265) before the header (line 284), so they would precede every page's <h1>. Use <div class="b-h5" role="heading" aria-level="2"> and give desktop .mm-hdr the same role, so both landmarks use one mechanism and SEO tools counting h1–h6 see nothing..chev and header SVGs lack aria-hidden. Add aria-hidden="true" to .arrow, .pair/.pair7, .nearr; aria-hidden="true" focusable="false" on the SVGs.<head> on every template (including .mm{display:none} and the sheet rules) and never route it through the theme's held/async stylesheet path; on the homepage the main sheets do not start downloading until the hero has painted, so hidden-state rules arriving late paint 77 links as an open list. Regenerate the seven critical CSS files in the same PR.html:not(.ct-nav-js) .has-mega:hover>.mm, html:not(.ct-nav-js) .has-mega:focus-within>.mm{display:block} with a one-line head script adding the class. Without JS the tabs already work as hub links (verified) but panels never open.aria-haspopup="true" (the panel is not a menu), keep aria-expanded/aria-controls; keep the tab as <a href>. Optionally set aria-current="page" from JS.aria-labelledby/aria-describedby in both stages or neither.https://app.currencytransfer.com 301s to /sign-in; link there directly.<button> tabs conflict with stage 1. Keep <a href> tabs (contract C6); if the disclosure-button pattern is preferred, apply it to stage 1 first so both stages match.role="heading" aria-level="2" on a div, never h2/h3.menu, menu-item, mega-menu, std-menu, sub-menu, parent inside either landmark.nav-parity-check.py stage 1 vs stage 2 on staging with --key name; require exit 0.Sitewide header links are the strongest internal-link signal the site sends, and because no static page is in any sitemap (the misc sitemap holds only the homepage), they are also how Google discovers and re-crawls those pages. Production carries 15 distinct hrefs; Launch carries 36; Full design 49. Status is the production response on 24 Sep (Googlebot UA).
| Delta | URL | Prod | Launch | Full | Status | Notes |
|---|---|---|---|---|---|---|
| LOSS | /product"Solutions" tab · "Online Currency Exchange" | desktop + mobile | – | – | 200 | Loses its only sitewide link. Remaining internal links found: two homepage body cards; none in the footer, none on 20 sampled template pages, not in any sitemap. Decide: 301 to the Business hub, give it a slot, or a footer link. |
| LOSS | https://www.payplexo.com"PayPlexo" | desktop + mobile | – | – | 200 | Sister brand; the outbound link disappears sitewide. Owner decision. |
| GAIN | /personal-transfers/overseas-property | – | yes | yes | 200 | Personal › Why you're transferring |
| GAIN | /personal-transfers/expat-money-transfers | – | yes | yes | 200 | Personal › Why you're transferring |
| GAIN | /personal-transfers/large-money-transfers | – | yes | yes | 200 | Personal › Why you're transferring |
| GAIN | /saudi-expat-guide | – | yes | yes | 200 | Personal › Why you're transferring (Launch placement) |
| GAIN | /currencies/transfer-aed-to-gbpalso aed-to-usd, sar-to-gbp, sar-to-eur, gbp-to-eur, gbp-to-aud | – | yes ×6 | yes ×6 | 301 → slash-less | Personal › Send money from. Prototype links the slashed form; fix 4 removes the redirect. |
| GAIN | /countries | – | yes | yes | 200 | "All countries & currencies" (takes the Supported Currencies role) |
| GAIN | /faq | – | yes | yes | 200 | Personal › Tools & help; already in the footer |
| GAIN | /global-mobility-solutions | – | yes | yes | 200 | Business › For employers |
| GAIN | /business-foreign-exchange/import-and-exportalso /technology, /marketplaces | – | yes ×3 | yes ×3 | 200 | Business › Industries. The other 8 sector pages keep their links from the hub body (11 sector links there). |
| GAIN | /business-foreign-exchange#industries | – | yes | yes | 200 (no anchor) | "All industries". Fragment target missing on the hub (fix 5). |
| GAIN | /partnerships | – | yes | yes | 200 | Business › Work with us |
| GAIN | /team · /reviews · /press | – | yes ×3 | yes ×3 | 200 | Company › Who we are; team and reviews already in the footer |
| GAIN | /partners · /security-regulation | – | yes ×2 | yes ×2 | 200 | Company › Trust & contact; both already in the footer |
| SAME | /business-foreign-exchange · /personal-transfers · /about | yes | yes | yes | 200 | Tabs and overview rows. Anchor text changes: "About" → "Company" + "About CurrencyTransfer …" |
| SAME | /spot-transfers · /forward-contracts · /rate-alerts · /market-orders · /mass-payments · /currency-options · /api-solutions | yes (Solutions) | yes | yes | 200 | Redistributed across Personal › Tools & help and Business › Manage risk / Pay at scale; descriptions join the anchor text. Rate Alerts stays. |
| SAME | /contact-us | mobile only | yes | yes | 200 | Now on desktop too ("Book a call", "Contact us") |
| SAME | https://app.currencytransfer.com · /sign-up | yes | yes | yes | 301 → /sign-in · 200 | Utility links; move from inside the desktop nav to the header cells |
| FULL ONLY | /personal-transfers/salary-and-bonus-repatriation · /personal-transfers/pension-and-inheritance-transfers · /buying-property-in-spain · /buying-property-in-israel · /employee-programme · /affiliate-networks · /agencies · /partnerships/agencies · /multi-currency-accounts · /trade-options | – | – | yes | 404 | Present in the prototype DOM as hidden "all" variants (fix 3). Trade Options is medium layout only. |
| FULL ONLY | /lp/revenue-protection-links · /lp/international-workforce | – | – | yes | 200 | Live landing pages, deliberately excluded from Launch |
| FULL ONLY | /refer-and-earn | – | – | desktop only | 200 | Plaque; not in the sheet, so Full design is not at mobile parity for it. Footer link exists. |
Nothing loses its only internal link: /product keeps two homepage body links. It does lose its only sitewide one, and it is not in a sitemap, so its crawl frequency and internal PageRank drop sharply. Sign this off knowingly or give it a slot.
| Check | Production today | Prototype, Launch mode |
|---|---|---|
| Raw HTML | Byte-identical for Chrome, Googlebot and Googlebot-smartphone UAs. Desktop nav#main-navigation 14 links; nav#mobile-menu 16 links (+ close link); a third, empty nav.search-nav. No aria-labels, no headings in nav, no nofollow. Three href="#" controls. | Live URL byte-identical to source. Header 3 links; nav[aria-label=Main] 77 anchors / 34 distinct hrefs (wide + medium variants); sheet 39 anchors / 36 distinct. All in server HTML. |
| After JS | dcMegaMenu adds classes, one icon span, wraps the sub-menu; adds/removes no links. Sub-menu display:none → block on hover-intent. Homepage: chain held until the hero paints; dropdown inert for ~2 s then works. | Only aria-haspopup/expanded/controls added to the 3 tabs. Hover/Enter opens panels: 16 / 14 / 7 links visible, all pre-existing. Sheet: 16 / 14 / 7 (+2 utility) per tab. |
| No JS | All links present; dropdown never opens. | All links present; tabs follow their hub href (verified); panels never open; burger inert. |
| Mobile parity | Mobile-only: /contact-us. | desktop+header 36 = sheet 36. Full design: /refer-and-earn, /trade-options desktop only. |
| Canonical form | Slash-less everywhere; /faq/, /about/, /countries/, /currencies/…/ all 301 to slash-less. | 6 currency hrefs slashed (301); #industries target missing; everything else 200. |
| Structured data | Organization, WebSite + SearchAction, WebPage, BreadcrumbList. No SiteNavigationElement. | None. Recommendation: do not add SiteNavigationElement (no Google use; sitelinks come from structure and links). Keep BreadcrumbList. |
Prototype failures are all preview chrome: is-crawlable (the noindex meta), meta-description, robots-txt (pages.dev serves the page for /robots.txt), and contrast on two dev-bar elements. Production's 88 comes from the comparison grid's ARIA roles and contrast in the cookie dialog and calculator, none in the header. No nav-related audit (link-name, crawlable-anchors, heading-order, aria-*, target-size) fails on any run.
Both stages must satisfy every clause. The parity script checks C2, C3 (as far as static HTML shows), C5, C6, C7 and C8.
<nav id="main-navigation" class="ct-nav" aria-label="Main"> and <nav id="mobile-menu" aria-label="Mobile"> inside #mobile-menu-wrap › #ct-sheet[role=dialog]. No other <nav> in the header. Log in / Sign up sit in the header outside #main-navigation and in the sheet footer outside #mobile-menu; the logo link / is in the header.aria-expanded, aria-controls, aria-selected, tabindex, sheet aria-hidden, pane hidden and state classes.display:none via CSS inlined in <head>, opened by a class on the item. Sheet visibility:hidden + aria-hidden="true" when closed; inactive panes carry hidden. Never display:none on an individual link, no lazy rendering.www.currencytransfer.com; lower-case, no trailing slash anywhere (including /currencies/…), no query string; the only fragment is #industries while its id exists. Every target 200 at deploy. No rel="nofollow", target="_blank" or title. Items without a live target are omitted, not hidden.<a href="{hub}">{Label}</a> in the server HTML (Personal → /personal-transfers, Business → /business-foreign-exchange, Company → /about); JS may add aria-expanded/aria-controls and intercept the click. Mobile tabs are <button role="tab">; the hub link is the overview row. Accessible name = visible label + description where rendered, nothing else: badges, arrows, chevrons, pills and icons are aria-hidden. Launch mode has no pills.<div … role="heading" aria-level="2"> in both landmarks. No h1–h6 inside either.set(hrefs in #mobile-menu ∪ sheet footer) == set(hrefs in #main-navigation ∪ header utility). Script output must read desktop-only=['/']; mobile-only=none; stage 2 asserts it after the walker's two passes.menu, menu-item, mega-menu, std-menu, sub-menu, parent never appear inside either landmark. Ids main-navigation, mobile-menu, mm-*, bp-*, bt-* stay stable across stages.current-menu-item, no server-side aria-current, no page-specific text.Extracted by nav-parity-check.py --mode live from the prototype build sha256 b4780480…, then normalised for the fixes that change hrefs or names (slash-less currency URLs, glyphs and badges removed from names). Wide and medium layout variants that produce the same (href, name) are listed once; where the medium variant renders a description that the wide one does not, both names appear. Section is the tab; group is the nearest preceding group title. This is the list the stage-2 walker must reproduce in this order; the authoritative version is whatever the script extracts from the stage-1 staging render after the fixes.
| # | Depth | Section | Group | Href | Accessible name |
|---|---|---|---|---|---|
| 1 | 1 | Personal | – | /personal-transfers | Personal |
| 2 | 1 | Personal | – | /personal-transfers | Personal Transfers Everything we do for private clients · minimum transfer £5,000 or equivalent |
| 3 | 2 | Personal | Why you're transferring | /personal-transfers/overseas-property | Buying property abroad |
| 4 | 2 | Personal | Why you're transferring | /personal-transfers/expat-money-transfers | Expat money transfers |
| 5 | 2 | Personal | Why you're transferring | /personal-transfers/large-money-transfers | Large one-off transfers |
| 6 | 2 | Personal | Why you're transferring | /forward-contracts | Fix a rate for a future payment |
| 7 | 2 | Personal | Why you're transferring | /saudi-expat-guide | Saudi Arabia expat guide |
| 8 | 2 | Personal | Send money from | /currencies/transfer-aed-to-gbp | UAE to UK |
| 9 | 2 | Personal | Send money from | /currencies/transfer-aed-to-usd | UAE to US |
| 10 | 2 | Personal | Send money from | /currencies/transfer-sar-to-gbp | Saudi Arabia to UK |
| 11 | 2 | Personal | Send money from | /currencies/transfer-sar-to-eur | Saudi Arabia to Europe |
| 12 | 2 | Personal | Send money from | /currencies/transfer-gbp-to-eur | UK to Europe |
| 13 | 2 | Personal | Send money from | /currencies/transfer-gbp-to-aud | UK to Australia |
| 14 | 2 | Personal | Send money from | /countries | All countries & currencies |
| 15 | 2 | Personal | Tools & help | /spot-transfers | Spot Transfers Same-day transfers at the live rate |
| 16 | 2 | Personal | Tools & help | /rate-alerts | Rate Alerts Get told when your rate arrives |
| 17 | 2 | Personal | Tools & help | /faq | Help & FAQs Answers on accounts, security and fees |
| 18 | 1 | Business | – | /business-foreign-exchange | Business |
| 19 | 1 | Business | – | /business-foreign-exchange | Business Foreign Exchange Dedicated specialist, hedging and payments for companies |
| 20 | 2 | Business | Manage currency risk | /forward-contracts | Forward Contracts Lock in a rate for future payments |
| 21 | 2 | Business | Manage currency risk | /market-orders | Market Orders Set a target rate, we execute |
| 22 | 2 | Business | Manage currency risk | /currency-options | Currency Options Buy and sell FX without deposits |
| 23 | 2 | Business | Pay at scale | /spot-transfers | Spot Transfers |
| 24 | 2 | Business | Pay at scale | /mass-payments | Mass Payments Send thousands of payments at once |
| 25 | 2 | Business | Pay at scale | /api-solutions | API Solutions Automate payments and rates by API |
| 26 | 2 | Business | For employers | /global-mobility-solutions | Global Mobility & Payroll |
| 27 | 2 | Business | Industries | /business-foreign-exchange/import-and-export | Import & Export |
| 28 | 2 | Business | Industries | /business-foreign-exchange/technology | Technology & SaaS |
| 29 | 2 | Business | Industries | /business-foreign-exchange/marketplaces | Online Marketplace Sellers |
| 30 | 2 | Business | Industries | /business-foreign-exchange#industries | All industries |
| 31 | 2 | Business | Work with us | /partnerships | Partner with us |
| 32 | 2 | Business | Work with us | /contact-us | Book a call |
| 33 | 2 | Business | Pay at scale | /spot-transfers | Spot Transfers Same-day international payments |
| 34 | 2 | Business | For employers | /global-mobility-solutions | Global Mobility & Payroll Relocation and payroll payments, automated |
| 35 | 1 | Company | – | /about | Company |
| 36 | 1 | Company | – | /about | About CurrencyTransfer International payments platform, founded 2010, London |
| 37 | 2 | Company | Who we are | /team | Our team |
| 38 | 2 | Company | Who we are | /reviews | Reviews |
| 39 | 2 | Company | Who we are | /press | Press |
| 40 | 2 | Company | Trust & contact | /partners | Payment partners The regulated firms that execute your transfers |
| 41 | 2 | Company | Trust & contact | /security-regulation | Security & regulation How client funds are safeguarded |
| 42 | 2 | Company | Trust & contact | /contact-us | Contact us |
| # | Depth | Section | Group | Href | Accessible name |
|---|---|---|---|---|---|
| 1 | 0 | Personal | – | /personal-transfers | Personal Transfers Dedicated concierge, £5,000 minimum transfer |
| 2 | 1 | Personal | Why you're transferring | /personal-transfers/overseas-property | Buying property abroad |
| 3 | 1 | Personal | Why you're transferring | /personal-transfers/expat-money-transfers | Expat money transfers |
| 4 | 1 | Personal | Why you're transferring | /personal-transfers/large-money-transfers | Large one-off transfers |
| 5 | 1 | Personal | Why you're transferring | /forward-contracts | Fix a rate for a future payment |
| 6 | 1 | Personal | Why you're transferring | /saudi-expat-guide | Saudi Arabia expat guide |
| 7 | 1 | Personal | Tools & help | /spot-transfers | Spot Transfers |
| 8 | 1 | Personal | Tools & help | /rate-alerts | Rate Alerts |
| 9 | 1 | Personal | Tools & help | /faq | Help & FAQs |
| 10 | 1 | Personal | Send money from | /currencies/transfer-aed-to-gbp | UAE to UK |
| 11 | 1 | Personal | Send money from | /currencies/transfer-aed-to-usd | UAE to US |
| 12 | 1 | Personal | Send money from | /currencies/transfer-sar-to-gbp | Saudi Arabia to UK |
| 13 | 1 | Personal | Send money from | /currencies/transfer-sar-to-eur | Saudi Arabia to Europe |
| 14 | 1 | Personal | Send money from | /currencies/transfer-gbp-to-eur | UK to Europe |
| 15 | 1 | Personal | Send money from | /currencies/transfer-gbp-to-aud | UK to Australia |
| 16 | 1 | Personal | Send money from | /countries | All countries & currencies |
| 17 | 0 | Business | – | /business-foreign-exchange | Business Foreign Exchange Tech-powered corporate currency exchange |
| 18 | 1 | Business | Manage currency risk | /forward-contracts | Forward Contracts |
| 19 | 1 | Business | Manage currency risk | /market-orders | Market Orders |
| 20 | 1 | Business | Manage currency risk | /currency-options | Currency Options |
| 21 | 1 | Business | Pay at scale | /mass-payments | Mass Payments |
| 22 | 1 | Business | Pay at scale | /api-solutions | API Solutions |
| 23 | 1 | Business | Pay at scale | /global-mobility-solutions | Global Mobility & Payroll |
| 24 | 1 | Business | Industries | /business-foreign-exchange/import-and-export | Import & Export |
| 25 | 1 | Business | Industries | /business-foreign-exchange/technology | Technology & SaaS |
| 26 | 1 | Business | Industries | /business-foreign-exchange/marketplaces | Online Marketplace Sellers |
| 27 | 1 | Business | Industries | /business-foreign-exchange#industries | All industries |
| 28 | 1 | Business | Work with us | /partnerships | Partner with us |
| 29 | 1 | Business | Work with us | /contact-us | Book a call |
| 30 | 1 | Company | Company information | /about | About us |
| 31 | 1 | Company | Company information | /team | Our team |
| 32 | 1 | Company | Company information | /reviews | Reviews |
| 33 | 1 | Company | Company information | /partners | Payment partners |
| 34 | 1 | Company | Company information | /security-regulation | Security & regulation |
| 35 | 1 | Company | Company information | /press | Press |
| 36 | 1 | Company | Company information | /contact-us | Contact us |
| 37 | 0 | – | – | https://app.currencytransfer.com | Log in |
| 38 | 0 | – | – | https://app.currencytransfer.com/sign-up | Sign Up |
Header, outside both landmarks: / "CurrencyTransfer" (logo), https://app.currencytransfer.com "Log in", https://app.currencytransfer.com/sign-up "Sign up".
docs/nav-audit-2026-09-22/nav-parity-check.py, Python 3 standard library. Extracts the tuple list from a URL or file (no JS), reports the checks above, diffs two sources. Exit 0 identical, 1 differences, 2 error.
python3 nav-parity-check.py https://staging.currencytransfer.com/ # report one source python3 nav-parity-check.py complete-menu/index.html --mode live # prototype, Launch mode python3 nav-parity-check.py STAGE1_URL STAGE2_URL --key name # stage 1 vs stage 2: require exit 0 python3 nav-parity-check.py A B --key href # href sets + "same href, name changed" python3 nav-parity-check.py A B --key strict # adds depth, section, group
Proof runs on 24 Sep: source file vs live prototype (Launch) → 0 / 0 differences, 85 common tuples, order identical, exit 0. Production home vs production Business page → 0 / 0, 33 common, exit 0. Prototype vs production (--key href) → 9 tuples only in production (/product ×2, PayPlexo ×2, three # controls, Log in and Sign up moving from the nav to the header kind), 23 hrefs only in the prototype, 16 name changes, exit 1. Launch vs Full design → 40 tuples only in Full design (the 13 hrefs above), exit 1.
Prepared 24 September 2026 from the served production HTML, the live prototype and the theme source (read-only). Playwright on Chromium; Lighthouse 12.8.2 on Chrome for Testing 151. Unlisted page: do not index.