Spaces:
Running
Running
Deploy 4ed0390343adb220188e58f95ab1a8e7e1dd995e (manual: Actions blocked on billing) (part 2)
1b9ed71 verified | /* Small shared helpers. No three.js, no DOM β safe to import anywhere. */ | |
| /** HTML-escape for interpolation into innerHTML β text AND attribute contexts. | |
| * Escapes the five significant characters incl. both quotes, so a value is safe | |
| * whether it lands in element content or inside a "β¦"/'β¦' attribute. Node fields | |
| * (label/table/id/β¦) are attacker-controlled (any `?graph=` URL/data: URI), so | |
| * every innerHTML sink that interpolates them MUST wrap them in this. */ | |
| export const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, | |
| (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); | |
| /** Deterministic PRNG. Same seed always yields the same galaxy. */ | |
| export function mulberry32(a) { | |
| return function () { | |
| a |= 0; a = (a + 0x6d2b79f5) | 0; | |
| let t = Math.imul(a ^ (a >>> 15), 1 | a); | |
| t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; | |
| return ((t ^ (t >>> 14)) >>> 0) / 4294967296; | |
| }; | |
| } | |
| export const clamp = (x, a, b) => Math.max(a, Math.min(b, x)); | |
| export const easeInOut = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2); | |
| export const easeOut = (t) => 1 - Math.pow(1 - t, 3); | |
| export const lerp = (a, b, t) => a + (b - a) * t; | |
| /** #rrggbb or [r,g,b] (0..1) -> [r,g,b] floats. */ | |
| export function toRGB(c) { | |
| if (Array.isArray(c)) return c; | |
| const h = String(c).replace('#', ''); | |
| const n = parseInt(h.length === 3 ? h.split('').map((x) => x + x).join('') : h, 16); | |
| return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255]; | |
| } | |
| export function rgbToCss(c, alpha) { | |
| const [r, g, b] = toRGB(c); | |
| const v = (x) => Math.round(x * 255); | |
| return alpha == null | |
| ? `rgb(${v(r)},${v(g)},${v(b)})` | |
| : `rgba(${v(r)},${v(g)},${v(b)},${alpha})`; | |
| } | |
| /** Compact magnitude formatter: 1_200_000 -> "$1.2M". Override via config.formatMagnitude. */ | |
| export function formatCompact(v, prefix = '$') { | |
| if (v == null || !isFinite(v)) return 'β'; | |
| const a = Math.abs(v); | |
| if (a >= 1e12) return prefix + (v / 1e12).toFixed(a >= 1e13 ? 0 : 1) + 'T'; | |
| if (a >= 1e9) return prefix + (v / 1e9).toFixed(a >= 1e10 ? 0 : 1) + 'B'; | |
| if (a >= 1e6) return prefix + (v / 1e6).toFixed(a >= 1e7 ? 0 : 1) + 'M'; | |
| if (a >= 1e3) return prefix + Math.round(v / 1e3) + 'K'; | |
| return prefix + String(Math.round(v)); | |
| } | |
| export function truncate(s, n) { | |
| if (!s) return ''; | |
| return s.length > n ? s.slice(0, n) + 'β¦' : s; | |
| } | |
| /** Resolve a config value that may be a literal, a key string, or a function. */ | |
| export function accessor(spec, fallback) { | |
| if (spec == null) return fallback; | |
| if (typeof spec === 'function') return spec; | |
| return (d) => d[spec]; | |
| } | |