import Phaser from '../vendor/phaser.js'; /** * Converts a config color (number or CSS string like '#1b2540') * into the integer format Phaser expects. */ export function toColor(value, fallback = 0xffffff) { if (typeof value === 'number' && Number.isFinite(value)) { return value >>> 0; } if (typeof value === 'string' && value.length > 0) { const color = Phaser.Display.Color.ValueToColor(value.trim()); if (color) return color.color; } return fallback; } /** * Converts a color (number or CSS string) to a '#rrggbb' CSS string. * * Phaser v4 (4.2.1 Giedi) quirk: Text styles are written straight to the * canvas — `fillStyle = style.color` — so a numeric color (e.g. 0xeaf6ff) * is an *invalid* fillStyle and the text silently renders black. Canvas- * backed text (add.text / TextStyle) must therefore always get CSS strings; * keep toColor for Graphics/shape APIs that want numbers. */ export function toCss(value, fallback = '#ffffff') { const n = toColor(value, toColor(fallback)); return '#' + (n >>> 0).toString(16).padStart(6, '0'); } /** * Rotate a color's HUE (degrees; + is clockwise on the hue wheel) and * rescale its saturation and lightness (1 = unchanged). Returns a 0xRRGGBB * number (ready for setTint()). * * Used by the nebula FRONT gas layer: the accent tint is the system's base * color rotated a little and lifted — two gas species of one nebula * (see data/game.json → nebula.front.accent). Pure math (no Phaser), so it * is testable in bare Node; grays stay grays (s=0 ⇒ hue is irrelevant), * and black/white are preserved. */ export function shiftHue(color, { hueShift = 0, satScale = 1, lightScale = 1 } = {}) { const n = toColor(color, 0xffffff) >>> 0; const r = ((n >> 16) & 255) / 255; const g = ((n >> 8) & 255) / 255; const b = (n & 255) / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const d = max - min; const l = (max + min) / 2; let h = 0; const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1)); if (d !== 0) { if (max === r) h = ((g - b) / d) % 6; else if (max === g) h = (b - r) / d + 2; else h = (r - g) / d + 4; h *= 60; if (h < 0) h += 360; } h = (h + hueShift) % 360; const s2 = Math.min(1, Math.max(0, s * satScale)); const l2 = Math.min(1, Math.max(0, l * lightScale)); const c = (1 - Math.abs(2 * l2 - 1)) * s2; const hp = h / 60; const x = c * (1 - Math.abs((hp % 2) - 1)); const [r2, g2, b2] = hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x]; const m = l2 - c / 2; const to255 = (v) => Math.min(255, Math.max(0, Math.round((v + m) * 255))); return ((to255(r2) << 16) | (to255(g2) << 8) | to255(b2)) >>> 0; }