@unmap/themes 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -850,4 +850,19 @@ declare const THEME_PRESETS: ThemePreset[];
850
850
  */
851
851
  declare function shuffleSeeds(rng?: () => number): Required<ThemeSeeds>;
852
852
 
853
- export { BUILDINGS_3D_LAYER_ID, type BasemapOverrides, type ContrastWarning, DEFAULT_LAYER_VISIBILITY, DEFAULT_MARKER, DEFAULT_ROUTE, DEFAULT_SEEDS, type DeepPartial, LABEL_PAIRS, LAYER_GROUPS, type LabelRamp, type LayerGroup, MAX_CODE_LENGTH, MODES, type MarkerTheme, type Oklch, PRESET_CODE_PREFIX, PRESET_CODE_RE, RECIPES, type RampSet, type Recipe, type ResolvedTheme, type RoadRamp, type RouteTheme, STOCK_RAMPS, STYLE_THEMES, THEME_BASES, THEME_CODE_PREFIX, THEME_PRESETS, type ThemeBase, type ThemeDoc, ThemeError, type ThemeHalf, type ThemeLayers, type ThemeMode, type ThemePreset, type ThemeSeeds, amber, applyThemeLayers, aqua, azure, buildRecipes, cobalt, contrast, contrastWarnings, coral, decodePreset, decodeTheme, effectiveLayerOrder, ember, encodePreset, encodeTheme, fern, generateTheme, graphite, hexToOklch, indigo, ink, isLayerVisible, isStockLayerOrder, isStockLayers, isThemePresetCode, jade, layerGroupOf, lilac, linen, makeFlavor, mergeRecipe, mist, normalizeThemeBase, oklchToHex, paper, peach, presetId, registryThemeKey, resolveTheme, rose, sandstone, shuffleSeeds, sky, validateTheme, verdant, wave };
853
+ /**
854
+ * Map an extracted image palette onto the four simple-mode seeds. Deterministic: the same
855
+ * colours always produce the same seeds, and `generateTheme` then carries the contrast
856
+ * guarantee. Lightness on land/water/nature is display-only (the generator re-hues stock
857
+ * ladders); accent is used verbatim for the marker and route.
858
+ */
859
+ interface PaletteColor {
860
+ hex: string;
861
+ proportion: number;
862
+ }
863
+ /**
864
+ * Brand colours in, the four simple-mode seeds out. Empty input falls back to the stock seeds.
865
+ */
866
+ declare function seedsFromPalette(colors: PaletteColor[]): Required<ThemeSeeds>;
867
+
868
+ export { BUILDINGS_3D_LAYER_ID, type BasemapOverrides, type ContrastWarning, DEFAULT_LAYER_VISIBILITY, DEFAULT_MARKER, DEFAULT_ROUTE, DEFAULT_SEEDS, type DeepPartial, LABEL_PAIRS, LAYER_GROUPS, type LabelRamp, type LayerGroup, MAX_CODE_LENGTH, MODES, type MarkerTheme, type Oklch, PRESET_CODE_PREFIX, PRESET_CODE_RE, type PaletteColor, RECIPES, type RampSet, type Recipe, type ResolvedTheme, type RoadRamp, type RouteTheme, STOCK_RAMPS, STYLE_THEMES, THEME_BASES, THEME_CODE_PREFIX, THEME_PRESETS, type ThemeBase, type ThemeDoc, ThemeError, type ThemeHalf, type ThemeLayers, type ThemeMode, type ThemePreset, type ThemeSeeds, amber, applyThemeLayers, aqua, azure, buildRecipes, cobalt, contrast, contrastWarnings, coral, decodePreset, decodeTheme, effectiveLayerOrder, ember, encodePreset, encodeTheme, fern, generateTheme, graphite, hexToOklch, indigo, ink, isLayerVisible, isStockLayerOrder, isStockLayers, isThemePresetCode, jade, layerGroupOf, lilac, linen, makeFlavor, mergeRecipe, mist, normalizeThemeBase, oklchToHex, paper, peach, presetId, registryThemeKey, resolveTheme, rose, sandstone, seedsFromPalette, shuffleSeeds, sky, validateTheme, verdant, wave };
package/dist/index.js CHANGED
@@ -2139,14 +2139,13 @@ function buildRecipes(r) {
2139
2139
  };
2140
2140
  const PASTEL_DARK = {
2141
2141
  background: r.lilac[1e3],
2142
- // Shares `orchid`'s ground, and cannot stop sharing it by moving a step. lilac[950] is the
2143
- // only free one and it sits within 1.01:1 of this style's navy water, which erases the
2144
- // coastline (the gateway's cartography suite holds land against water at 1.08). Every other
2145
- // ramp either lands on another style's earth (indigo[900] is `sunset`'s), merges with this
2146
- // style's own scrub, or is the wrong hue for a lavender map. Separating these two needs a
2147
- // decision about pastel's water or its ramp, not a step move.
2148
- earth: r.lilac[900],
2149
- water: r.cobalt[950],
2142
+ earth: r.lilac[950],
2143
+ // Water lighter than land, which `blueprint`, `tropic` and `bold` already do: the three
2144
+ // styles whose water is the thing you remember about them. This one's is periwinkle, and at
2145
+ // cobalt[950] the dark half had turned it near-black, so the name stopped being true after
2146
+ // sundown. Lifting it also unpins the ground, which had to sit at `orchid`'s lilac[900]
2147
+ // because the only free step below was within 1.01:1 of that near-black water.
2148
+ water: r.cobalt[800],
2150
2149
  parkA: r.jade[850],
2151
2150
  parkB: r.jade[800],
2152
2151
  woodA: r.fern[800],
@@ -2183,18 +2182,18 @@ function buildRecipes(r) {
2183
2182
  bridgeCasing: r.lilac[1e3],
2184
2183
  labels: {
2185
2184
  city: r.lilac[25],
2186
- cityHalo: r.lilac[900],
2185
+ cityHalo: r.lilac[950],
2187
2186
  state: r.lilac[300],
2188
- stateHalo: r.lilac[900],
2187
+ stateHalo: r.lilac[950],
2189
2188
  country: r.lilac[300],
2190
2189
  subplace: r.lilac[300],
2191
- subplaceHalo: r.lilac[900],
2190
+ subplaceHalo: r.lilac[950],
2192
2191
  address: r.lilac[400],
2193
- addressHalo: r.lilac[900],
2192
+ addressHalo: r.lilac[950],
2194
2193
  roadMinor: r.lilac[400],
2195
- roadMinorHalo: r.lilac[900],
2194
+ roadMinorHalo: r.lilac[950],
2196
2195
  roadMajor: r.lilac[100],
2197
- roadMajorHalo: r.lilac[900],
2196
+ roadMajorHalo: r.lilac[950],
2198
2197
  ocean: r.cobalt[400]
2199
2198
  },
2200
2199
  pois: {
@@ -3165,6 +3164,77 @@ async function decodeDeflate(code) {
3165
3164
  }
3166
3165
  return validateTheme(parsed);
3167
3166
  }
3167
+
3168
+ // src/seeds-from-palette.ts
3169
+ var CHROMATIC_FLOOR = 0.04;
3170
+ var HUE_DEDUP = 20;
3171
+ var WATER_HUE = { lo: 190, hi: 270 };
3172
+ var NATURE_HUE = { lo: 100, hi: 170 };
3173
+ var DISPLAY_L = { lo: 0.35, hi: 0.7 };
3174
+ var wrapHue = (h) => (h % 360 + 360) % 360;
3175
+ var hueDelta = (a, b) => {
3176
+ const d = Math.abs(wrapHue(a) - wrapHue(b));
3177
+ return Math.min(d, 360 - d);
3178
+ };
3179
+ var inBand = (h, band) => {
3180
+ const hue = wrapHue(h);
3181
+ return hue >= band.lo && hue <= band.hi;
3182
+ };
3183
+ var normalizeHex = (hex) => {
3184
+ const raw = hex.startsWith("#") ? hex.slice(1) : hex;
3185
+ return `#${raw.slice(0, 6).toUpperCase()}`;
3186
+ };
3187
+ var parse = (color) => {
3188
+ if (!/^#?[0-9a-fA-F]{6}/.test(color.hex)) return null;
3189
+ const hex = normalizeHex(color.hex);
3190
+ const { l, c, h } = hexToOklch(hex);
3191
+ if (!Number.isFinite(l) || !Number.isFinite(c) || !Number.isFinite(h)) return null;
3192
+ return { hex, proportion: Math.max(color.proportion, 0), l, c, h, score: 0 };
3193
+ };
3194
+ var displaySeed = (color) => oklchToHex({
3195
+ l: Math.min(DISPLAY_L.hi, Math.max(DISPLAY_L.lo, color.l)),
3196
+ c: color.c,
3197
+ h: color.h
3198
+ });
3199
+ var byScore = (a, b) => b.score - a.score || b.proportion - a.proportion || a.hex.localeCompare(b.hex);
3200
+ var distinctHues = (ranked) => {
3201
+ const out = [];
3202
+ for (const color of ranked) {
3203
+ if (out.some((kept) => hueDelta(kept.h, color.h) < HUE_DEDUP)) continue;
3204
+ out.push(color);
3205
+ }
3206
+ return out;
3207
+ };
3208
+ var pickInBand = (candidates, band) => candidates.find((color) => inBand(color.h, band));
3209
+ function fromNeutrals(neutrals) {
3210
+ const mid = neutrals.slice().sort((a, b) => Math.abs(a.l - 0.5) - Math.abs(b.l - 0.5))[0] ?? parse({ hex: "#808080", proportion: 1 });
3211
+ const grey = displaySeed(mid);
3212
+ return { land: grey, water: grey, nature: grey, accent: DEFAULT_SEEDS.accent };
3213
+ }
3214
+ function seedsFromPalette(colors) {
3215
+ const parsed = colors.map(parse).filter((c) => c !== null);
3216
+ if (parsed.length === 0) return { ...DEFAULT_SEEDS };
3217
+ const chromatic = parsed.filter((c) => c.c >= CHROMATIC_FLOOR);
3218
+ if (chromatic.length === 0) return fromNeutrals(parsed);
3219
+ const total = chromatic.reduce((sum, c) => sum + c.proportion, 0) || 1;
3220
+ for (const color of chromatic) {
3221
+ color.score = 0.7 * (color.proportion / total) + 0.3 * Math.min(color.c / 0.15, 1);
3222
+ }
3223
+ const ranked = chromatic.slice().sort(byScore);
3224
+ const distinct = distinctHues(ranked);
3225
+ const land = distinct[0];
3226
+ const rest = distinct.slice(1);
3227
+ const water = pickInBand(rest, WATER_HUE) ?? rest[0] ?? land;
3228
+ const afterWater = distinct.filter((c) => c !== land && c !== water);
3229
+ const nature = pickInBand(afterWater, NATURE_HUE) ?? afterWater[0] ?? land;
3230
+ const accent = ranked.slice().sort((a, b) => b.c - a.c || byScore(a, b))[0];
3231
+ return {
3232
+ land: displaySeed(land),
3233
+ water: displaySeed(water),
3234
+ nature: displaySeed(nature),
3235
+ accent: accent.hex
3236
+ };
3237
+ }
3168
3238
  export {
3169
3239
  BUILDINGS_3D_LAYER_ID,
3170
3240
  DEFAULT_LAYER_VISIBILITY,
@@ -3225,6 +3295,7 @@ export {
3225
3295
  resolveTheme,
3226
3296
  rose,
3227
3297
  sandstone,
3298
+ seedsFromPalette,
3228
3299
  shuffleSeeds,
3229
3300
  sky,
3230
3301
  validateTheme,