diff --git a/packages/zorb-web-component/package.json b/packages/zorb-web-component/package.json index c73d08e..f320972 100644 --- a/packages/zorb-web-component/package.json +++ b/packages/zorb-web-component/package.json @@ -25,9 +25,5 @@ "rollup-plugin-svelte": "^7.1.0", "rollup-plugin-typescript": "^1.0.1" }, - "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@types/tinycolor2": "^1.4.3", - "tinycolor2": "^1.4.2" - } + "dependencies": {} } diff --git a/packages/zorb-web-component/rollup.config.js b/packages/zorb-web-component/rollup.config.js index 0999b9a..89afe36 100644 --- a/packages/zorb-web-component/rollup.config.js +++ b/packages/zorb-web-component/rollup.config.js @@ -5,15 +5,9 @@ import resolve from "rollup-plugin-node-resolve"; import commonjs from "rollup-plugin-commonjs"; import dts from "rollup-plugin-dts"; -import tinycolor from "tinycolor2"; - const commonPlugins = [ resolve({ node: true }), - commonjs({ - namedExports: { - tinycolor2: Object.keys(tinycolor), - }, - }), + commonjs(), typescript({ tsconfig: false, esModuleInterop: true, diff --git a/packages/zorb-web-component/src/lib.ts b/packages/zorb-web-component/src/lib.ts index 1646b66..63e9ee3 100644 --- a/packages/zorb-web-component/src/lib.ts +++ b/packages/zorb-web-component/src/lib.ts @@ -1,5 +1,71 @@ -import { arrayify } from "@ethersproject/bytes"; -import tinycolor, { ColorInput } from "tinycolor2"; +// Inline replacement for `arrayify` from @ethersproject/bytes. +// We only ever consume 0x-prefixed even-length hex strings here; the +// ethers helper supported a wider input space we don't need. +const arrayify = (hex: string): Uint8Array => { + if (typeof hex !== "string" || !/^0x[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) { + throw new Error(`invalid hex string: ${hex}`); + } + const out = new Uint8Array((hex.length - 2) / 2); + for (let i = 0; i < out.length; i++) { + out[i] = parseInt(hex.slice(2 + i * 2, 4 + i * 2), 16); + } + return out; +}; + +// Inline replacement for the single tinycolor2 call this file made: +// tinycolor({h, s, l}).toHslString() +// tinycolor doesn't just format the input — it pushes HSL → RGB → HSL, +// clamps RGB to [0,255] (without rounding), then rounds h*360, s*100, l*100 +// to integers. We reproduce that exact pipeline so output stays +// byte-for-byte identical. +const _bound01 = (n: number, max: number): number => { + const c = Math.min(max, Math.max(0, n)); + if (Math.abs(c - max) < 0.000001) return 1; + return (c % max) / max; +}; +const _hueChannel = (p: number, q: number, t: number): number => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; +}; +const hslString = (h: number, s: number, l: number): string => { + const hN = _bound01(h, 360); + const sN = _bound01(s, 100); + const lN = _bound01(l, 100); + let r: number, g: number, b: number; + if (sN === 0) { + r = g = b = lN * 255; + } else { + const q = lN < 0.5 ? lN * (1 + sN) : lN + sN - lN * sN; + const p = 2 * lN - q; + r = _hueChannel(p, q, hN + 1 / 3) * 255; + g = _hueChannel(p, q, hN) * 255; + b = _hueChannel(p, q, hN - 1 / 3) * 255; + } + // tinycolor clamps RGB to [0,255] without rounding before round-tripping. + const rC = Math.min(255, Math.max(0, r)); + const gC = Math.min(255, Math.max(0, g)); + const bC = Math.min(255, Math.max(0, b)); + const rN = rC / 255, gN = gC / 255, bN = bC / 255; + const max = Math.max(rN, gN, bN); + const min = Math.min(rN, gN, bN); + const lOut = (max + min) / 2; + let hOut: number, sOut: number; + if (max === min) { + hOut = 0; sOut = 0; + } else { + const d = max - min; + sOut = lOut > 0.5 ? d / (2 - max - min) : d / (max + min); + if (max === rN) hOut = (gN - bN) / d + (gN < bN ? 6 : 0); + else if (max === gN) hOut = (bN - rN) / d + 2; + else hOut = (rN - gN) / d + 4; + hOut /= 6; + } + return `hsl(${Math.round(hOut * 360)}, ${Math.round(sOut * 100)}%, ${Math.round(lOut * 100)}%)`; +}; const linear = (p: number) => p; @@ -120,7 +186,7 @@ export const gradientForAddress = (address: string) => { const lightnessShiftFn = lerpLightnessFn(bytes[5] % 2); const saturationShiftFn = lerpSaturationFn(bytes[3] % 2); - const inputs: ColorInput[] = [ + const inputs = [ { h: hueShiftFn(startHue, 0), s: saturationShiftFn(startSaturation, endSaturation, 1), @@ -148,10 +214,5 @@ export const gradientForAddress = (address: string) => { } ]; - //return inputs; - - return inputs - .map((c: ColorInput) => tinycolor(c)) - .map((tc: tinycolor.Instance) => tc.toHslString()) - + return inputs.map(c => hslString(c.h, c.s, c.l)); };