mirror of
https://github.com/samsonjs/spirit-tracker.git
synced 2026-03-25 09:25:51 +00:00
UX Improvements
This commit is contained in:
parent
8531eba4e3
commit
1216ec7fe0
1 changed files with 177 additions and 160 deletions
|
|
@ -1,9 +1,29 @@
|
||||||
function normalizeId(s) {
|
function normalizeId(s) {
|
||||||
return String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
return String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your pinned colors (exact / “roughly right”)
|
// Map normalized store *labels* to canonical ids used by OVERRIDES
|
||||||
const OVERRIDES = {
|
const ALIASES = {
|
||||||
|
strathliquor: "strath",
|
||||||
|
vesselliquor: "vessel",
|
||||||
|
tudorhouse: "tudor",
|
||||||
|
coopworldofwhisky: "coop",
|
||||||
|
|
||||||
|
kensingtonwinemarket: "kensingtonwinemarket",
|
||||||
|
gullliquor: "gullliquor",
|
||||||
|
legacyliquor: "legacyliquor",
|
||||||
|
vintagespirits: "vintagespirits",
|
||||||
|
kegncork: "kegncork",
|
||||||
|
|
||||||
|
// short forms
|
||||||
|
gull: "gullliquor",
|
||||||
|
legacy: "legacyliquor",
|
||||||
|
vintage: "vintagespirits",
|
||||||
|
kwm: "kensingtonwinemarket",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Your pinned colors
|
||||||
|
const OVERRIDES = {
|
||||||
strath: "#76B7FF",
|
strath: "#76B7FF",
|
||||||
bsw: "#E9DF7A",
|
bsw: "#E9DF7A",
|
||||||
kensingtonwinemarket: "#F2C200",
|
kensingtonwinemarket: "#F2C200",
|
||||||
|
|
@ -13,40 +33,43 @@ function normalizeId(s) {
|
||||||
legacyliquor: "#7B4A12",
|
legacyliquor: "#7B4A12",
|
||||||
vintagespirits: "#E34A2C",
|
vintagespirits: "#E34A2C",
|
||||||
|
|
||||||
craftcellars: "#E31B23", // bright red
|
craftcellars: "#E31B23",
|
||||||
maltsandgrains: "#A67C52", // faded brown
|
maltsandgrains: "#A67C52",
|
||||||
|
|
||||||
// aliases
|
// aliases
|
||||||
gull: "#6B0F1A",
|
gull: "#6B0F1A",
|
||||||
legacy: "#7B4A12",
|
legacy: "#7B4A12",
|
||||||
vintage: "#E34A2C",
|
vintage: "#E34A2C",
|
||||||
kwm: "#F2C200",
|
kwm: "#F2C200",
|
||||||
};
|
};
|
||||||
|
|
||||||
// High-contrast qualitative palette (distinct hues).
|
// High-contrast qualitative palette
|
||||||
// (Avoids whites/blacks/yellows that clash w/ your overrides by filtering below.)
|
const PALETTE = [
|
||||||
const PALETTE = [
|
|
||||||
"#1F77B4", "#FF7F0E", "#2CA02C", "#D62728", "#9467BD",
|
"#1F77B4", "#FF7F0E", "#2CA02C", "#D62728", "#9467BD",
|
||||||
"#8C564B", "#E377C2", "#7F7F7F", "#17BECF", "#BCBD22",
|
"#8C564B", "#E377C2", "#7F7F7F", "#17BECF", "#BCBD22",
|
||||||
"#AEC7E8", "#FFBB78", "#98DF8A", "#FF9896", "#C5B0D5",
|
"#AEC7E8", "#FFBB78", "#98DF8A", "#FF9896", "#C5B0D5",
|
||||||
"#C49C94", "#F7B6D2", "#C7C7C7", "#9EDAE5", "#DBDB8D",
|
"#C49C94", "#F7B6D2", "#C7C7C7", "#9EDAE5", "#DBDB8D",
|
||||||
// extras to reduce wrap risk
|
|
||||||
"#393B79", "#637939", "#8C6D31", "#843C39", "#7B4173",
|
"#393B79", "#637939", "#8C6D31", "#843C39", "#7B4173",
|
||||||
"#3182BD", "#31A354", "#756BB1", "#636363", "#E6550D",
|
"#3182BD", "#31A354", "#756BB1", "#636363", "#E6550D",
|
||||||
];
|
];
|
||||||
|
|
||||||
function uniq(arr) {
|
function uniq(arr) {
|
||||||
return [...new Set(arr)];
|
return [...new Set(arr)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildUniverse(base, extra) {
|
function canonicalId(s) {
|
||||||
|
const id = normalizeId(s);
|
||||||
|
return ALIASES[id] || id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUniverse(base, extra) {
|
||||||
const a = Array.isArray(base) ? base : [];
|
const a = Array.isArray(base) ? base : [];
|
||||||
const b = Array.isArray(extra) ? extra : [];
|
const b = Array.isArray(extra) ? extra : [];
|
||||||
return uniq([...a, ...b].map(normalizeId).filter(Boolean));
|
return uniq([...a, ...b].map(canonicalId).filter(Boolean));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default known ids (keeps mapping stable even if a page only sees a subset)
|
// Keep mapping stable even if page sees a subset
|
||||||
const DEFAULT_UNIVERSE = buildUniverse(Object.keys(OVERRIDES), [
|
const DEFAULT_UNIVERSE = buildUniverse(Object.keys(OVERRIDES), [
|
||||||
"bcl",
|
"bcl",
|
||||||
"bsw",
|
"bsw",
|
||||||
"coop",
|
"coop",
|
||||||
|
|
@ -66,19 +89,19 @@ function normalizeId(s) {
|
||||||
"vintage",
|
"vintage",
|
||||||
"vintagespirits",
|
"vintagespirits",
|
||||||
"willowpark",
|
"willowpark",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function isWhiteHex(c) {
|
function isWhiteHex(c) {
|
||||||
return String(c || "").trim().toUpperCase() === "#FFFFFF";
|
return String(c || "").trim().toUpperCase() === "#FFFFFF";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildStoreColorMap(extraUniverse = []) {
|
export function buildStoreColorMap(extraUniverse = []) {
|
||||||
const universe = buildUniverse(DEFAULT_UNIVERSE, extraUniverse).sort();
|
const universe = buildUniverse(DEFAULT_UNIVERSE, extraUniverse).sort();
|
||||||
|
|
||||||
const used = new Set();
|
const used = new Set();
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|
||||||
// pin overrides first
|
// Pin overrides first
|
||||||
for (const id of universe) {
|
for (const id of universe) {
|
||||||
const c = OVERRIDES[id];
|
const c = OVERRIDES[id];
|
||||||
if (c) {
|
if (c) {
|
||||||
|
|
@ -87,7 +110,7 @@ function normalizeId(s) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter palette to avoid exact collisions with overrides (and keep white reserved for Vessel)
|
// Filter palette to avoid collisions and keep white/black reserved
|
||||||
const palette = PALETTE
|
const palette = PALETTE
|
||||||
.map((c) => String(c).toUpperCase())
|
.map((c) => String(c).toUpperCase())
|
||||||
.filter((c) => !used.has(c) && c !== "#FFFFFF" && c !== "#111111");
|
.filter((c) => !used.has(c) && c !== "#FFFFFF" && c !== "#111111");
|
||||||
|
|
@ -95,20 +118,17 @@ function normalizeId(s) {
|
||||||
let pi = 0;
|
let pi = 0;
|
||||||
for (const id of universe) {
|
for (const id of universe) {
|
||||||
if (map.has(id)) continue;
|
if (map.has(id)) continue;
|
||||||
if (pi >= palette.length) {
|
if (pi >= palette.length) pi = 0;
|
||||||
// If you ever exceed palette size, just reuse (rare). Still deterministic.
|
|
||||||
pi = 0;
|
|
||||||
}
|
|
||||||
const c = palette[pi++];
|
const c = palette[pi++];
|
||||||
map.set(id, c);
|
map.set(id, c);
|
||||||
used.add(c);
|
used.add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function storeColor(storeKeyOrLabel, colorMap) {
|
export function storeColor(storeKeyOrLabel, colorMap) {
|
||||||
const id = normalizeId(storeKeyOrLabel);
|
const id = canonicalId(storeKeyOrLabel);
|
||||||
if (!id) return "#7F7F7F";
|
if (!id) return "#7F7F7F";
|
||||||
|
|
||||||
const forced = OVERRIDES[id];
|
const forced = OVERRIDES[id];
|
||||||
|
|
@ -119,23 +139,22 @@ function normalizeId(s) {
|
||||||
if (c) return c;
|
if (c) return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback: deterministic but not “no conflicts”
|
|
||||||
return PALETTE[(id.length + id.charCodeAt(0)) % PALETTE.length];
|
return PALETTE[(id.length + id.charCodeAt(0)) % PALETTE.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function datasetStrokeWidth(color) {
|
export function datasetStrokeWidth(color) {
|
||||||
return isWhiteHex(color) ? 2.5 : 1.5;
|
return isWhiteHex(color) ? 2.5 : 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function datasetPointRadius(color) {
|
export function datasetPointRadius(color) {
|
||||||
return isWhiteHex(color) ? 2.8 : 2.2;
|
return isWhiteHex(color) ? 2.8 : 2.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clamp(v, lo, hi) {
|
function clamp(v, lo, hi) {
|
||||||
return Math.max(lo, Math.min(hi, v));
|
return Math.max(lo, Math.min(hi, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
function hexToRgb(hex) {
|
function hexToRgb(hex) {
|
||||||
const m = String(hex).replace("#", "");
|
const m = String(hex).replace("#", "");
|
||||||
if (m.length !== 6) return null;
|
if (m.length !== 6) return null;
|
||||||
const n = parseInt(m, 16);
|
const n = parseInt(m, 16);
|
||||||
|
|
@ -144,15 +163,16 @@ function normalizeId(s) {
|
||||||
g: (n >> 8) & 255,
|
g: (n >> 8) & 255,
|
||||||
b: n & 255,
|
b: n & 255,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function rgbToHex({ r, g, b }) {
|
function rgbToHex({ r, g, b }) {
|
||||||
const h = (x) => clamp(Math.round(x), 0, 255).toString(16).padStart(2, "0");
|
const h = (x) =>
|
||||||
|
clamp(Math.round(x), 0, 255).toString(16).padStart(2, "0");
|
||||||
return `#${h(r)}${h(g)}${h(b)}`;
|
return `#${h(r)}${h(g)}${h(b)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lighten by mixing with white (amount 0–1)
|
// Lighten by mixing with white (0–1)
|
||||||
export function lighten(hex, amount = 0.25) {
|
export function lighten(hex, amount = 0.25) {
|
||||||
const rgb = hexToRgb(hex);
|
const rgb = hexToRgb(hex);
|
||||||
if (!rgb) return hex;
|
if (!rgb) return hex;
|
||||||
return rgbToHex({
|
return rgbToHex({
|
||||||
|
|
@ -160,7 +180,4 @@ function normalizeId(s) {
|
||||||
g: rgb.g + (255 - rgb.g) * amount,
|
g: rgb.g + (255 - rgb.g) * amount,
|
||||||
b: rgb.b + (255 - rgb.b) * amount,
|
b: rgb.b + (255 - rgb.b) * amount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue