A polished, accessible Leaflet experience for showing the Praxium Learning Foundation's allies, partners, locations, focus areas, and mapped relationships. The public map is intentionally static so it can be hosted on GitHub Pages and embedded safely in Squarespace or another website builder.
The repository has three related surfaces:
- Public impact map — GitHub Pages
organization-network-map.html— semantic page structure and metadatapraxium-tokens-v2.css— shared Praxium paper/ink/clay design tokensmap.css— responsive visual system for desktop, mobile, and iframesmap.js— map interactions, filters, theme, status handling, and embed APInetwork-data.js— reusable validation, normalization, metrics, and connection de-duplicationcategories.json/categories.legacy.js— canonical categories and thefile://fallback
- Board publishing admin — currently implemented for Vercel Functions
admin/— passphrase login and form-driven editorapi/— authentication, Gist publishing, and geocoding functions
- Offline/bulk editor
admin-editor/— browser-based JSON editorelectron/— desktop wrapper and native file operations
Organization records continue to live in the public GitHub Gist configured by the praxium-data-url meta tag in organization-network-map.html.
praxium-contrast-comparison.html is the retained design rationale for the
accessible clay split: #A3826C remains the recognizable display/decorative
color, while #85624B is used for small interactive text and button fills.
- A branded impact narrative instead of a utility-only map
- Live counts for organizations, focus areas, locations/countries, and mapped relationships
- Search and category filtering with visible result counts and empty states
- Clickable, keyboard-accessible organization cards and map markers
- De-duplicated relationship lines, including support for one-way legacy connection data
- Secure popup rendering: untrusted Gist content is inserted as text, not executable HTML
- Website-matching light mode by default, an optional token-consistent dark mode, full-network reset, mobile explorer drawer, legend, and expand control
- Website-aligned Epilogue/Poppins typography, warm paper/ink surfaces, accessible clay interactions, and the same asymmetric field shape used by the main site
- Last-known-good local cache plus clear stale/error messages when the Gist is unavailable
- Parent-page messages for iframe integrations and a stable
window.praxiumNetworkAPI
The current live data has no mapped connections, so connection metrics and the relationship key stay hidden until the board records real relationships. This avoids claiming impact that the dataset does not yet support.
Node.js 22.12+ is required for the current Electron toolchain. The public map itself has no build step.
# If you use nvm, this selects the version recorded in .nvmrc
nvm use
# Install the locked dependencies
npm ci
# Validate JavaScript syntax and run all automated tests
npm run validate
# Build an unpacked desktop app for the current platform
npm run pack
# Serve the repository with any static web server, then open the map page
python3 -m http.server 8000
# http://localhost:8000/organization-network-map.htmlOpening with file:// is not recommended because browsers may block JSON requests. The map can still use categories.legacy.js as a category fallback in that environment.
Each Gist entry should follow this shape:
{
"id": 1,
"name": "Organization name",
"location": "City, region, country",
"country": "Canada",
"coordinates": [49.2827, -123.1207],
"category": "Non-Profit",
"description": "Public-facing description of the organization.",
"relationship": "Optional explanation of how this organization and Praxium work together.",
"contact": {
"website": "https://example.org/",
"email": "optional@example.org",
"phone": "+1 555 555 5555"
},
"connections": [2, 3]
}idmust be a unique positive integer.coordinatesmust be[latitude, longitude]within valid ranges.categoryshould come fromcategories.jsonunless a deliberate custom category is needed.countryis optional but recommended. When more than one explicit country is present, the impact card reports countries; otherwise it conservatively reports unique locations.relationshipis optional but highly recommended. It turns a directory entry into an impact story by explaining the actual collaboration.connectionscontains organization IDs. The web admin keeps these links reciprocal, but the public map also handles one-way legacy links and draws each pair only once.- Website links are limited to
http/https; malformed or unsafe schemes are ignored.
Invalid individual records are skipped without taking down the entire map. Unknown/self/duplicate connections are ignored, and warnings are surfaced without exposing technical details to visitors.
.github/workflows/static.yml deploys only the public assets on changes to main:
organization-network-map.html(also copied toindex.html)praxium-tokens-v2.cssmap.cssmap.jsnetwork-data.jscategories.jsoncategories.legacy.js
The admin UI, serverless functions, local sample data, and repository files are not included in the Pages artifact.
Production URLs:
https://praxiumlearningfoundation.github.io/interactive_map/
https://praxiumlearningfoundation.github.io/interactive_map/organization-network-map.html
Use pull requests rather than pushing feature work directly to main; CI validates JSON, checks JavaScript syntax, and runs the public-map and API tests before merge.
Recommended embed:
<iframe
src="https://praxiumlearningfoundation.github.io/interactive_map/"
title="Praxium Global Network of allies and partners"
width="100%"
height="720"
loading="lazy"
allow="fullscreen"
style="display:block;width:100%;border:0;border-radius:20px;overflow:hidden;"
></iframe>Notes:
allow="fullscreen"lets the phone expand control use the browser Fullscreen API where the host permits it.- If a host blocks fullscreen, the control still expands within the iframe viewport.
- Give the embed enough height to show the story and explorer comfortably (roughly 650–800 px on desktop). The mobile design intentionally prioritizes the map and opens the organization explorer as a drawer.
- Keep the iframe title; it identifies the map for screen-reader users.
The map emits optional parent-window messages with source: "praxium-network-map" and these types:
ready— data has finished loadingselection— an organization was selected or clearedresize— the map shell's measured height changed
A host page may listen for those events if it wants custom analytics or iframe resizing. Because postMessage cannot safely guess the final production parent origin, receiving pages should always validate event.origin and event.data.source before acting.
Available after map.js loads:
await window.praxiumNetwork.ready;
window.praxiumNetwork.getMetrics();
window.praxiumNetwork.selectOrganization(3);
window.praxiumNetwork.resetView();
window.praxiumNetwork.exportData();
window.praxiumNetwork.importData(jsonStringOrArray);
window.praxiumNetwork.addOrganization(organizationObject);The original exportData, importData, and addOrganization entry points remain available for compatibility.
The /admin interface lets a board member log in, create/edit/delete organizations, geocode addresses, select relationships by organization name, and publish to the Gist without seeing JSON or a GitHub token.
Required environment variables:
GITHUB_PAT— Gist read/write token; never expose it to browser codeGIST_ID— production Gist IDADMIN_PASSPHRASE— shared board passphraseSESSION_SECRET— random 32+ byte cookie-signing secret
Current implementation details:
- Signed, secure,
HttpOnly,SameSite=Strictsession cookie - Passphrase attempt limiting
- Server-side validation of required fields, URLs, categories, and coordinate ranges
- Server-side Nominatim geocoding throttle
- Optimistic concurrency using the Gist version (
409on stale edits) - Reciprocal relationship maintenance and deleted-ID cleanup
- Mocked-Gist API tests in
test/api.test.js
Open admin-editor/index.html through a local server or run the Electron app:
npm install
npm startThe editor supports paste/load, form editing, copy, and JSON download. Packaged desktop builds are configured through Electron Builder.
The data URL points to the stable latest Gist path. Saving a valid edit to the production Gist makes it available to the map on the next refresh without redeploying the public site.
GitHub Pages remains a good fit for the public map: it is static, free, versioned, and does not expose the admin backend. Migrate only the authenticated admin/API unless there is a separate reason to move the public map.
As of September 5, 2026, Microsoft documents a $2,000 USD annual Azure grant for eligible nonprofits. It must be renewed, unused credit does not roll over, and use beyond the grant can become pay-as-you-go. Add a budget and alerts before deploying production resources.
A practical Azure target is:
- Azure Static Web Apps for
admin/and the serverless API, deployed from the same GitHub repository. - Keep the existing Gist as the first migration milestone so the public map URL and data contract do not change.
- Store
GITHUB_PAT,GIST_ID,ADMIN_PASSPHRASE, andSESSION_SECRETin Azure application settings (or Key Vault when the nonprofit environment is ready). - Port the Vercel-style request/response adapters in
api/to Azure Functions handlers while preserving the tested validation/publishing functions. - Replace the shared passphrase with Microsoft Entra ID and restrict access to the Praxium tenant or an explicit administrator role.
- Validate login, Gist read, geocoding, create/update/delete, and the deliberate two-tab conflict test in a staging environment.
- Cut the admin URL over only after the staging checklist passes; the public GitHub Pages iframe can stay unchanged.
A later phase could move structured data from a Gist to Azure Storage or Cosmos DB when Praxium needs richer audit, reporting, or approval workflows. That is an operational upgrade, not a prerequisite for presenting the network professionally.
See docs/PROFESSIONALIZATION_ROADMAP.md for the full content, security,
acceptance-testing, cost-control, and cutover plan.
npm run check # JavaScript syntax checks
npm test # network-data and mocked API tests
npm run validate # both of the aboveGitHub Actions:
.github/workflows/ci.yml— validation on pull requests andmain.github/workflows/static.yml— public Pages deployment.github/workflows/build-release.yml— Electron release artifacts
- Never commit tokens, recovery codes, signing keys, or
.envfiles. The repository.gitignoreexcludes the known local secret files. - The public map treats Gist content as untrusted input and avoids
innerHTMLfor organization-provided values. - The local cache is availability fallback only; visitors are explicitly told when cached data is being shown.
- OpenStreetMap tiles require visible attribution, which remains on the map.
- Nominatim's public service is appropriate for low-volume board edits, not bulk geocoding.
- Before publishing a new impact number, make sure the supporting fields are actually maintained in the data. The interface intentionally computes only defensible metrics.
The biggest remaining opportunity is not another animation or visual effect; it is richer verified data. For each organization, add:
- explicit
country - a concise
relationshipstatement - real
connectionswhere a documented partnership exists - current website and description
Those fields will let the same interface truthfully communicate global reach, collaboration density, and how Praxium's network creates impact.