Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 10035x 22473x 178x 178x 171x 171x 171x 171x 171x 171x 3x 3x 3x 3x 168x 168x 168x 168x 171x 2392x 2392x 2392x 2391x 2392x 2391x 2391x 2391x 9548x 9x 2392x 2390x 7170x 7167x 2390x 2390x 2390x 7168x 2389x 2389x 2392x 2386x 2386x 2385x 2386x 2389x 2576x 2576x 2574x 2574x 2572x 2571x 2393x 1x 7656x 2552x 7656x 2552x 7656x 2549x 2549x 3x 3x 2549x 2549x 2549x 2548x 2548x 2548x 2549x 2549x | export type RGBA = { r: number; g: number; b: number; a: number };
const clamp01 = (x: number) => Math.max(0, Math.min(1, x));
const clamp255 = (x: number) => Math.max(0, Math.min(255, Math.round(x)));
function parseHex(hex: string): RGBA | null {
const h = hex.replace("#", "").trim();
if (![3, 4, 6, 8].includes(h.length) || !/^[0-9a-f]+$/i.test(h)) return null;
const expand = (s: string) => (s.length === 1 ? s + s : s);
let r = 0,
g = 0,
b = 0,
a = 255;
if (h.length === 3 || h.length === 4) {
r = parseInt(expand(h[0]!), 16);
g = parseInt(expand(h[1]!), 16);
b = parseInt(expand(h[2]!), 16);
if (h.length === 4) a = parseInt(expand(h[3]!), 16);
} else {
r = parseInt(h.slice(0, 2), 16);
g = parseInt(h.slice(2, 4), 16);
b = parseInt(h.slice(4, 6), 16);
if (h.length === 8) a = parseInt(h.slice(6, 8), 16);
}
return { r, g, b, a: a / 255 };
}
function parseRgb(input: string): RGBA | null {
const s = input.trim().toLowerCase();
const m = s.match(/^rgba?\((.*)\)$/);
if (!m) return null;
const body = m[1]!.trim();
const slashParts = body.split("/").map((x) => x.trim());
const left = slashParts[0]!;
const alphaPart = slashParts[1];
const comps = left.includes(",")
? left.split(",").map((x) => x.trim())
: left.split(/\s+/).map((x) => x.trim());
if (comps.length < 3) return null;
const to255 = (v: string) => {
if (v.endsWith("%")) return clamp255(parseFloat(v) * 2.55);
return clamp255(parseFloat(v));
};
const r = to255(comps[0]!);
const g = to255(comps[1]!);
const b = to255(comps[2]!);
if ([r, g, b].some((n) => Number.isNaN(n))) return null;
let a = 1;
const aStr = alphaPart ?? comps[3];
if (aStr != null) {
const t = aStr.trim();
if (t.endsWith("%")) a = clamp01(parseFloat(t) / 100);
else a = clamp01(parseFloat(t));
if (Number.isNaN(a)) a = 1;
}
return { r, g, b, a };
}
export function parseColorToRgba(input: string): RGBA | null {
const s = input.trim();
if (!s) return null;
const lower = s.toLowerCase();
if (lower.startsWith("var(")) return null;
if (lower === "currentcolor") return null;
if (s.startsWith("#")) return parseHex(s);
if (lower.startsWith("rgb")) return parseRgb(s);
return null;
}
export function luminance({ r, g, b }: RGBA): number {
const srgb = [r, g, b].map((v) => v / 255);
const lin = srgb.map((c) =>
c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
);
return 0.2126 * lin[0]! + 0.7152 * lin[1]! + 0.0722 * lin[2]!;
}
function blendChannel(c: number, target: number, t: number) {
return clamp255(c + (target - c) * t);
}
export function lighten(rgba: RGBA, t = 0.45): RGBA {
const k = clamp01(t);
return {
r: blendChannel(rgba.r, 255, k),
g: blendChannel(rgba.g, 255, k),
b: blendChannel(rgba.b, 255, k),
a: rgba.a,
};
}
export function darken(rgba: RGBA, t = 0.22): RGBA {
const k = clamp01(t);
return {
r: blendChannel(rgba.r, 0, k),
g: blendChannel(rgba.g, 0, k),
b: blendChannel(rgba.b, 0, k),
a: rgba.a,
};
}
export function toRgbaString({ r, g, b, a }: RGBA): string {
return `rgba(${clamp255(r)}, ${clamp255(g)}, ${clamp255(b)}, ${clamp01(a)})`;
}
export type AutoHighlightOptions = {
lightThreshold?: number;
lightenBy?: number;
darkenBy?: number;
alphaBoost?: number;
};
export function generateAutoHighlight(
baseColor: string,
opts: AutoHighlightOptions = {}
): string | null {
const rgba = parseColorToRgba(baseColor);
if (!rgba) return null;
const {
lightThreshold = 0.8,
lightenBy = 0.5,
darkenBy = 0.22,
alphaBoost = 0.06,
} = opts;
const L = luminance(rgba);
const hi =
L >= lightThreshold ? darken(rgba, darkenBy) : lighten(rgba, lightenBy);
hi.a = clamp01(Math.max(hi.a, rgba.a + alphaBoost));
return toRgbaString(hi);
}
|