doxa-maps/library/styles/registry.js
doxa-maps 40c4cc67d4
Some checks are pending
build-gate / build (push) Waiting to run
Move the eight CARTO basemap styles to OpenFreeMap
Eight of the twelve named styles resolved their MapLibre URL to
basemaps.cartocdn.com. Those are keyless today -- verified live, style JSON
and a real vector tile and a font all 200 with no token -- but CARTO's own
docs reserve the right to extend the key requirement to vector tiles. That
is a future silent outage already shipped, and the same single-point-of-
failure shape we are trying to escape by leaving the URL-restricted Mapbox
token. A dependency that is free today and gated tomorrow is a deferred one.

  dark-matter -> openfreemap dark      (midnight-glow, dusk, ink, wayfinder-night)
  positron    -> openfreemap positron  (paper)
  voyager     -> openfreemap liberty   (satellite, hybrid, wayfinder-day)

Each replacement verified before the swap: 200 keyless, zero keyed URLs in
sources/glyphs/sprite, real tiles serve. Built output now has zero CARTO
references. Embed suite 40/40 pages mount cleanly.

The header records the bar for adding a style here: keyless, commercial use
permitted, self-hostable. MapTiler and Stadia free tiers fail the second.
2026-09-22 02:27:48 +00:00

107 lines
6.5 KiB
JavaScript

// library/styles/registry.js — the named 12-style pack, every style on BOTH engines.
//
// WHY NAMES: profile-config carried raw style URLs, which (a) tie an embed to one
// engine (a mapbox:// URL is meaningless to MapLibre) and (b) scatter taste
// decisions across profiles. A NAME ('midnight-glow') resolves per provider, so
// the same embed config works on Mapbox and token-free MapLibre alike.
//
// PAIRING RULES:
// · mapbox side: official style URLs (Standard's lightPreset variants give
// day/dawn/dusk/night from ONE style doc — seamless theme flips, see
// useMapTheme.js). `lightPreset` here is a HINT profiles may apply on load.
// · maplibre side: FREE, no-key styles only — OpenFreeMap (liberty/bright/
// positron/dark). No free satellite imagery exists for MapLibre, so imagery
// styles fall back to the closest free look (noted per entry).
//
// ★ CARTO REMOVED 2026-09-22, and this is a rule, not a preference. Eight of these
// twelve entries previously resolved to basemaps.cartocdn.com. Those URLs were keyless
// *at the time* — verified live, a real style.json + a 275KB .mvt + a font .pbf all 200
// with no token — but CARTO's own documentation reserves the right to extend its API-key
// requirement to vector tiles. That is a future silent outage already shipped in our
// code, and it is the SAME single-point-of-failure shape we are trying to escape by
// moving off the URL-restricted Mapbox token. A dependency that is free today and
// gated tomorrow is not a free dependency; it is a deferred one.
//
// Substitutions (each OpenFreeMap style verified 200 keyless before the swap):
// carto dark-matter -> openfreemap dark (midnight-glow, dusk, ink, wayfinder-night)
// carto positron -> openfreemap positron (paper)
// carto voyager -> openfreemap liberty (satellite, hybrid, wayfinder-day)
//
// THE BAR FOR ADDING A maplibre: URL HERE: keyless, commercial use permitted, and
// self-hostable. MapTiler and Stadia free tiers fail the second test outright — both
// prohibit commercial use — so they are disqualified on licence, not merely on key risk.
//
// A raw URL still works everywhere a name does (resolveNamedStyle passes
// mapbox:// and http(s):// through untouched) — nothing existing breaks.
export const STYLE_REGISTRY = [
{ id: 'midnight-glow', label: 'Midnight Glow', mood: 'the prayer-map dark — pins glow like embers',
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'night',
maplibre: 'https://tiles.openfreemap.org/styles/dark',
bestFor: ['doxa-simple-map', 'my-upg-100-list'] },
{ id: 'daylight', label: 'Daylight', mood: 'the same Standard world, lights on',
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'day',
maplibre: 'https://tiles.openfreemap.org/styles/liberty',
bestFor: ['doxa-research-map', 'gospel-resources'] },
{ id: 'dawn', label: 'Dawn', mood: 'soft warm horizon — hopeful, quiet',
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'dawn',
maplibre: 'https://tiles.openfreemap.org/styles/positron',
bestFor: ['doxa-simple-map'] },
{ id: 'dusk', label: 'Dusk', mood: 'blue hour — calm contrast for bright pins',
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'dusk',
maplibre: 'https://tiles.openfreemap.org/styles/dark',
bestFor: ['my-upg-100-list'] },
{ id: 'paper', label: 'Paper', mood: 'near-white minimal — data reads like print',
mapbox: 'mapbox://styles/mapbox/light-v11',
maplibre: 'https://tiles.openfreemap.org/styles/positron',
bestFor: ['research-dashboard', 'cities-internet'] },
{ id: 'ink', label: 'Ink', mood: 'near-black minimal — maximum pin contrast',
mapbox: 'mapbox://styles/mapbox/dark-v11',
maplibre: 'https://tiles.openfreemap.org/styles/dark',
bestFor: ['my-upg-100-list', 'cities-internet'] },
{ id: 'streetlife', label: 'Street Life', mood: 'full street detail — city-scale work',
mapbox: 'mapbox://styles/mapbox/streets-v12',
maplibre: 'https://tiles.openfreemap.org/styles/bright',
bestFor: ['cities-internet', 'key-cities'] },
{ id: 'terrain', label: 'Terrain', mood: 'relief + parks — where people actually live',
mapbox: 'mapbox://styles/mapbox/outdoors-v12',
maplibre: 'https://tiles.openfreemap.org/styles/liberty',
bestFor: ['doxa-research-map', 'country-posters'] },
{ id: 'satellite', label: 'Satellite', mood: 'imagery — the real ground',
mapbox: 'mapbox://styles/mapbox/satellite-v9',
maplibre: 'https://tiles.openfreemap.org/styles/liberty', // no free imagery on MapLibre — closest free look
bestFor: ['country-posters'] },
{ id: 'hybrid', label: 'Hybrid', mood: 'imagery + labels — orientation without losing ground truth',
mapbox: 'mapbox://styles/mapbox/satellite-streets-v12',
maplibre: 'https://tiles.openfreemap.org/styles/liberty', // same fallback note as satellite
bestFor: ['key-cities'] },
{ id: 'wayfinder-day', label: 'Wayfinder Day', mood: 'high-legibility navigation palette',
mapbox: 'mapbox://styles/mapbox/navigation-day-v1',
maplibre: 'https://tiles.openfreemap.org/styles/liberty',
bestFor: ['gospel-resources'] },
{ id: 'wayfinder-night',label: 'Wayfinder Night',mood: 'dark navigation — dashboards at night',
mapbox: 'mapbox://styles/mapbox/navigation-night-v1',
maplibre: 'https://tiles.openfreemap.org/styles/dark',
bestFor: ['research-dashboard'] },
]
const BY_ID = Object.fromEntries(STYLE_REGISTRY.map(s => [s.id, s]))
/**
* Resolve a profile-config `style` value for an engine.
* · a registry NAME → that entry's per-provider style URL
* · a raw mapbox:// or http(s):// URL → passed through untouched (legacy behavior)
* · anything else / undefined → undefined (caller keeps its own default)
* Returns { style, lightPreset } — lightPreset only for named Standard variants.
*/
export function resolveNamedStyle(value, provider = 'mapbox') {
if (typeof value !== 'string' || !value) return { style: undefined, lightPreset: undefined }
const entry = BY_ID[value]
if (entry) {
return provider === 'maplibre'
? { style: entry.maplibre, lightPreset: undefined }
: { style: entry.mapbox, lightPreset: entry.lightPreset }
}
if (/^(mapbox:\/\/|https?:\/\/)/.test(value)) return { style: value, lightPreset: undefined }
return { style: undefined, lightPreset: undefined }
}