Some checks are pending
build-gate / build (push) Waiting to run
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.
107 lines
6.5 KiB
JavaScript
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 }
|
|
}
|