favicon-env 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -781,7 +781,7 @@ The favicon must be an SVG in `public/`. Omit a mode to leave it unchanged.
781
781
 
782
782
  ### `favicon-env/ssr` — build-time
783
783
 
784
- - `tintSvg(svg, tint) => string` — SVG string with the tint baked in as a wrapping filtered group.
784
+ - `tintSvg(svg, tint) => string` — SVG string with the tint baked in as a group wrapped in an SVG `<filter>`.
785
785
  - `svgToDataUri(svg) => string` — percent-encoded `data:image/svg+xml,…`.
786
786
  - `faviconDataUri(svg, tint) => string` — the two combined; a ready favicon `href`.
787
787
 
@@ -795,7 +795,7 @@ The favicon must be an SVG in `public/`. Omit a mode to leave it unchanged.
795
795
 
796
796
  - Cross-origin favicons need CORS permission for runtime canvas processing; failures leave the icon unchanged.
797
797
  - Runtime mode may briefly show the original icon. Use the SSR helper to avoid this.
798
- - Runtime mode requires canvas `ctx.filter`; unsupported browsers keep the original icon.
798
+ - WebKit/Safari implements no canvas `ctx.filter`, and ignores a CSS `filter` on an inner SVG element. Both modes therefore apply `hue` / `invert` / `tint` / `filter` as colour matrices, so every engine renders the same icon. The two exceptions are `blur()` and `drop-shadow()` inside an explicit `filter` string — they have no matrix form, so they need Chromium or Firefox.
799
799
 
800
800
  ## License
801
801
 
@@ -52,6 +52,133 @@ function cssFilter(tint) {
52
52
  return parts.length > 0 ? parts.join(" ") : null;
53
53
  }
54
54
 
55
+ // src/matrix.ts
56
+ var rgbRow = (row) => [row[0], row[1], row[2], 0, row[3]];
57
+ function matrix(r, g, b, alpha = 1) {
58
+ return [...rgbRow(r), ...rgbRow(g), ...rgbRow(b), 0, 0, 0, alpha, 0];
59
+ }
60
+ var [LR, LG, LB] = [0.213, 0.715, 0.072];
61
+ function saturate(s) {
62
+ return matrix(
63
+ [LR + 0.787 * s, LG - LG * s, LB - LB * s, 0],
64
+ [LR - LR * s, LG + 0.285 * s, LB - LB * s, 0],
65
+ [LR - LR * s, LG - LG * s, LB + 0.928 * s, 0]
66
+ );
67
+ }
68
+ function hueRotate(deg) {
69
+ const rad = deg * Math.PI / 180;
70
+ const c = Math.cos(rad);
71
+ const s = Math.sin(rad);
72
+ return matrix(
73
+ [LR + c * 0.787 - s * 0.213, LG - c * 0.715 - s * 0.715, LB - c * 0.072 + s * 0.928, 0],
74
+ [LR - c * 0.213 + s * 0.143, LG + c * 0.285 + s * 0.14, LB - c * 0.072 - s * 0.283, 0],
75
+ [LR - c * 0.213 - s * 0.787, LG - c * 0.715 + s * 0.715, LB + c * 0.928 + s * 0.072, 0]
76
+ );
77
+ }
78
+ function sepia(a) {
79
+ const k = 1 - a;
80
+ return matrix(
81
+ [k + 0.393 * a, 0.769 * a, 0.189 * a, 0],
82
+ [0.349 * a, k + 0.686 * a, 0.168 * a, 0],
83
+ [0.272 * a, 0.534 * a, k + 0.131 * a, 0]
84
+ );
85
+ }
86
+ function invert(a) {
87
+ const k = 1 - 2 * a;
88
+ return matrix([k, 0, 0, a], [0, k, 0, a], [0, 0, k, a]);
89
+ }
90
+ function linear(slope, intercept) {
91
+ return matrix([slope, 0, 0, intercept], [0, slope, 0, intercept], [0, 0, slope, intercept]);
92
+ }
93
+ function opacity(o) {
94
+ return matrix([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], o);
95
+ }
96
+ function amount(raw, fallback) {
97
+ const text = raw.trim();
98
+ if (text === "") return fallback;
99
+ const m = /^([+-]?(?:\d+\.?\d*|\.\d+))(%?)$/.exec(text);
100
+ if (!m) return null;
101
+ const n = Number(m[1]);
102
+ return m[2] ? n / 100 : n;
103
+ }
104
+ function angle(raw) {
105
+ const text = raw.trim();
106
+ if (text === "") return 0;
107
+ const m = /^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|grad|turn)?$/.exec(text);
108
+ if (!m) return null;
109
+ const n = Number(m[1]);
110
+ if (m[2] === "rad") return n * 180 / Math.PI;
111
+ if (m[2] === "grad") return n * 0.9;
112
+ if (m[2] === "turn") return n * 360;
113
+ return n;
114
+ }
115
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
116
+ var FUNCTIONS = {
117
+ "hue-rotate": (raw) => {
118
+ const deg = angle(raw);
119
+ return deg == null ? null : hueRotate(deg);
120
+ },
121
+ // grayscale(a) is exactly saturate(1 - a).
122
+ grayscale: (raw) => {
123
+ const a = amount(raw, 1);
124
+ return a == null ? null : saturate(1 - clamp01(a));
125
+ },
126
+ sepia: (raw) => {
127
+ const a = amount(raw, 1);
128
+ return a == null ? null : sepia(clamp01(a));
129
+ },
130
+ saturate: (raw) => {
131
+ const a = amount(raw, 1);
132
+ return a == null || a < 0 ? null : saturate(a);
133
+ },
134
+ invert: (raw) => {
135
+ const a = amount(raw, 1);
136
+ return a == null ? null : invert(clamp01(a));
137
+ },
138
+ opacity: (raw) => {
139
+ const a = amount(raw, 1);
140
+ return a == null ? null : opacity(clamp01(a));
141
+ },
142
+ brightness: (raw) => {
143
+ const a = amount(raw, 1);
144
+ return a == null || a < 0 ? null : linear(a, 0);
145
+ },
146
+ contrast: (raw) => {
147
+ const a = amount(raw, 1);
148
+ return a == null || a < 0 ? null : linear(a, 0.5 - 0.5 * a);
149
+ }
150
+ };
151
+ var FUNCTION_RE = /([a-z-]+)\(([^()]*)\)/gi;
152
+ function filterMatrices(filter) {
153
+ const matrices = [];
154
+ FUNCTION_RE.lastIndex = 0;
155
+ let m;
156
+ while ((m = FUNCTION_RE.exec(filter)) !== null) {
157
+ const build = FUNCTIONS[m[1].toLowerCase()];
158
+ if (!build) return null;
159
+ const built = build(m[2]);
160
+ if (!built) return null;
161
+ matrices.push(built);
162
+ }
163
+ const rest = filter.replace(FUNCTION_RE, "").trim();
164
+ if (rest !== "" && rest !== "none") return null;
165
+ return matrices;
166
+ }
167
+ function applyMatrices(data, matrices) {
168
+ for (const m of matrices) {
169
+ for (let i = 0; i < data.length; i += 4) {
170
+ const r = data[i] / 255;
171
+ const g = data[i + 1] / 255;
172
+ const b = data[i + 2] / 255;
173
+ const a = data[i + 3] / 255;
174
+ data[i] = (m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4]) * 255;
175
+ data[i + 1] = (m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9]) * 255;
176
+ data[i + 2] = (m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14]) * 255;
177
+ data[i + 3] = (m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19]) * 255;
178
+ }
179
+ }
180
+ }
181
+
55
182
  // src/ssr.ts
56
183
  var XML_ESCAPES = {
57
184
  "&": "&amp;",
@@ -123,6 +250,11 @@ function svgCover([minX, minY, w, h], badge) {
123
250
  );
124
251
  return `<g${op}>${rect}${label}</g>`;
125
252
  }
253
+ var round5 = (n) => Math.round(n * 1e5) / 1e5;
254
+ function svgFilterChain(matrices) {
255
+ const primitives = matrices.map((m) => `<feColorMatrix type="matrix" values="${m.map(round5).join(" ")}"/>`).join("");
256
+ return `<filter id="__favenv_f" color-interpolation-filters="sRGB">${primitives}</filter>`;
257
+ }
126
258
  function svgColorize(color) {
127
259
  return `<filter id="__favenv_c" color-interpolation-filters="sRGB"><feColorMatrix type="saturate" values="0" result="g"/><feFlood flood-color="${escapeXml(color)}" result="f"/><feBlend in="f" in2="g" mode="multiply" result="b"/><feComposite in="b" in2="SourceGraphic" operator="in"/></filter>`;
128
260
  }
@@ -150,8 +282,14 @@ function tintSvg(svg, tint) {
150
282
  defs = svgColorize(colorize);
151
283
  body = `<g filter="url(#__favenv_c)">${inner}</g>`;
152
284
  } else if (filter) {
153
- defs = `<style>.__favenv{filter:${filter}}</style>`;
154
- body = `<g class="__favenv">${inner}</g>`;
285
+ const matrices = filterMatrices(filter);
286
+ if (!matrices) {
287
+ defs = `<style>.__favenv{filter:${escapeXml(filter)}}</style>`;
288
+ body = `<g class="__favenv">${inner}</g>`;
289
+ } else if (matrices.length > 0) {
290
+ defs = svgFilterChain(matrices);
291
+ body = `<g filter="url(#__favenv_f)">${inner}</g>`;
292
+ }
155
293
  }
156
294
  const viewBox = badge ? parseViewBox(open[0]) : null;
157
295
  const badgeSvg = badge && viewBox ? svgBadge(viewBox, badge) : "";
@@ -164,6 +302,6 @@ function faviconDataUri(svg, tint) {
164
302
  return svgToDataUri(tintSvg(svg, tint));
165
303
  }
166
304
 
167
- export { DEFAULT_BADGE_COLOR, badgeText, contrastColor, cssFilter, faviconDataUri, normalizeBadge, placeBadge, svgToDataUri, tintSvg };
168
- //# sourceMappingURL=chunk-OA3YX73Z.js.map
169
- //# sourceMappingURL=chunk-OA3YX73Z.js.map
305
+ export { DEFAULT_BADGE_COLOR, applyMatrices, badgeText, contrastColor, cssFilter, faviconDataUri, filterMatrices, normalizeBadge, placeBadge, svgToDataUri, tintSvg };
306
+ //# sourceMappingURL=chunk-T7D7UPKB.js.map
307
+ //# sourceMappingURL=chunk-T7D7UPKB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/badge.ts","../src/color.ts","../src/filter.ts","../src/matrix.ts","../src/ssr.ts"],"names":[],"mappings":"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","file":"chunk-T7D7UPKB.js","sourcesContent":["import type { Badge, BadgeCorner } from './types';\n\n/** Default badge background — the red dot/pill you get when no `color` is set. */\nexport const DEFAULT_BADGE_COLOR = '#ef4444';\n\n/** Normalise the `badge` shorthand (a `string` is a dot of that colour) to a `Badge`. */\nexport function normalizeBadge(badge: string | Badge): Badge {\n return typeof badge === 'string' ? { color: badge } : badge;\n}\n\n/** A badge's text as a string (`''` when omitted) — so a `0` renders and `undefined` doesn't. */\nexport function badgeText(badge: Badge): string {\n return badge.text == null ? '' : String(badge.text);\n}\n\n/**\n * Top-left corner for a `bw`×`bh` badge inside a `cw`×`ch` box, per `corner`\n * (relative to the box origin; the caller adds any offset). Shared by the canvas\n * and SVG renderers so placement stays identical across runtime and build-time.\n */\nexport function placeBadge(\n corner: BadgeCorner,\n cw: number,\n ch: number,\n bw: number,\n bh: number,\n margin: number,\n): [number, number] {\n if (corner === 'center') return [(cw - bw) / 2, (ch - bh) / 2];\n return [\n corner.endsWith('left') ? margin : cw - bw - margin,\n corner.startsWith('top') ? margin : ch - bh - margin,\n ];\n}\n","/** Parse `#rgb` / `#rrggbb` / `rgb(…)` / `rgba(…)` into `[r, g, b]` (0–255), or `null`. */\nfunction parseRgb(color: string): [number, number, number] | null {\n const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color);\n if (hex) {\n const h = hex[1].length === 3 ? hex[1].replace(/./g, (c) => c + c) : hex[1];\n const n = Number.parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n }\n const rgb = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/i.exec(color);\n if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n return null;\n}\n\n/**\n * Perceived lightness (0–1) of `color` for the black/white text decision:\n * rec601 luma for `#hex` / `rgb()`, or the `L` channel of CSS Color 4 colours —\n * `oklch()`/`oklab()` (0–1) and `lab()`/`lch()` (0–100). `null` if unreadable.\n */\nfunction lightness(color: string): number | null {\n const rgb = parseRgb(color);\n if (rgb) return (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;\n const lch = /^(okl(?:ch|ab)|l(?:ch|ab))\\(\\s*([\\d.]+)(%?)/i.exec(color);\n if (lch) {\n const l = Number(lch[2]);\n if (lch[3]) return l / 100; // a percentage L, in any of these spaces\n return /^ok/i.test(lch[1]) ? l : l / 100; // oklab/oklch L is 0–1, lab/lch is 0–100\n }\n return null;\n}\n\n/**\n * Black or white — whichever reads better on `color` — by perceived lightness.\n * Handles `#hex`, `rgb()`, and CSS Color 4 `oklch()`/`oklab()`/`lab()`/`lch()`;\n * falls back to white for anything else (named colours, `hsl()`, `color()`, …).\n */\nexport function contrastColor(color: string): string {\n const l = lightness(color);\n if (l == null) return '#fff';\n return l > 0.6 ? '#000' : '#fff';\n}\n","import type { EnvTint } from './types';\n\n/**\n * The CSS `filter` for a tint. An explicit `filter` wins outright; otherwise\n * `hue` and `invert` compose into one filter string (both applied, in that\n * order). `null` when none of them are set.\n */\nexport function cssFilter(tint: EnvTint): string | null {\n if (tint.filter) return tint.filter;\n const parts: string[] = [];\n if (typeof tint.hue === 'number') parts.push(`hue-rotate(${tint.hue}deg)`);\n // `invert: true` → invert(1); a number is used verbatim. `0` / `false` is a no-op.\n if (tint.invert) parts.push(`invert(${tint.invert === true ? 1 : tint.invert})`);\n return parts.length > 0 ? parts.join(' ') : null;\n}\n","/**\n * CSS `filter` functions re-expressed as colour matrices — the one form every\n * engine can apply.\n *\n * Two engine gaps make this necessary, and they are independent:\n * 1. WebKit/Safari does not implement `CanvasRenderingContext2D.filter` at all\n * (`'filter' in ctx` is `false`), so assigning it is a silent no-op and the\n * runtime path draws the icon untinted.\n * 2. WebKit also ignores a CSS `filter` set on an *inner* SVG element, so the\n * SSR path can't wrap the artwork in a filtered `<g>` either. A real SVG\n * `<filter>` element is honoured everywhere.\n *\n * Each matrix is 20 numbers in `feColorMatrix` row-major order (4 rows × 5\n * columns, the last column a constant), and a filter string becomes a *list* of\n * them applied in sequence — not one pre-multiplied matrix — because the Filter\n * Effects spec clamps every primitive's result to 0–1 before the next one runs,\n * and `hue-rotate` routinely overflows that range.\n */\n\n/** 4×5 colour matrix, row-major, in `feColorMatrix` `values` order. */\nexport type ColorMatrix = readonly number[];\n\n/** One output channel: the r/g/b coefficients plus a constant. */\ntype Row = readonly [number, number, number, number];\n\nconst rgbRow = (row: Row): number[] => [row[0], row[1], row[2], 0, row[3]];\n\n/**\n * Assemble the three colour rows into a full matrix. No colour function reads\n * the alpha channel, so that column is always 0; `alpha` scales alpha itself.\n */\nfunction matrix(r: Row, g: Row, b: Row, alpha = 1): ColorMatrix {\n return [...rgbRow(r), ...rgbRow(g), ...rgbRow(b), 0, 0, 0, alpha, 0];\n}\n\n// The luminance coefficients the Filter Effects spec uses for `saturate` and\n// `hue-rotate` (not the same set as `feColorMatrix type=\"luminanceToAlpha\"`).\nconst [LR, LG, LB] = [0.213, 0.715, 0.072];\n\nfunction saturate(s: number): ColorMatrix {\n return matrix(\n [LR + 0.787 * s, LG - LG * s, LB - LB * s, 0],\n [LR - LR * s, LG + 0.285 * s, LB - LB * s, 0],\n [LR - LR * s, LG - LG * s, LB + 0.928 * s, 0],\n );\n}\n\nfunction hueRotate(deg: number): ColorMatrix {\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n return matrix(\n [LR + c * 0.787 - s * 0.213, LG - c * 0.715 - s * 0.715, LB - c * 0.072 + s * 0.928, 0],\n [LR - c * 0.213 + s * 0.143, LG + c * 0.285 + s * 0.14, LB - c * 0.072 - s * 0.283, 0],\n [LR - c * 0.213 - s * 0.787, LG - c * 0.715 + s * 0.715, LB + c * 0.928 + s * 0.072, 0],\n );\n}\n\n/** `sepia(a)` — a linear blend from the identity (`a = 0`) to the spec's matrix. */\nfunction sepia(a: number): ColorMatrix {\n const k = 1 - a;\n return matrix(\n [k + 0.393 * a, 0.769 * a, 0.189 * a, 0],\n [0.349 * a, k + 0.686 * a, 0.168 * a, 0],\n [0.272 * a, 0.534 * a, k + 0.131 * a, 0],\n );\n}\n\n/** `invert(a)`: `c' = a + c * (1 - 2a)` — affine, so the constant column carries `a`. */\nfunction invert(a: number): ColorMatrix {\n const k = 1 - 2 * a;\n return matrix([k, 0, 0, a], [0, k, 0, a], [0, 0, k, a]);\n}\n\n/** A per-channel `slope`/`intercept` ramp — `brightness` and `contrast` both reduce to one. */\nfunction linear(slope: number, intercept: number): ColorMatrix {\n return matrix([slope, 0, 0, intercept], [0, slope, 0, intercept], [0, 0, slope, intercept]);\n}\n\nfunction opacity(o: number): ColorMatrix {\n return matrix([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], o);\n}\n\n/** Parse one filter-function argument. `%` is divided out; a bare number is used as-is. */\nfunction amount(raw: string, fallback: number): number | null {\n const text = raw.trim();\n if (text === '') return fallback;\n const m = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+))(%?)$/.exec(text);\n if (!m) return null;\n const n = Number(m[1]);\n return m[2] ? n / 100 : n;\n}\n\n/** Parse a `<angle>` for `hue-rotate` — `deg` (default), `rad`, `grad` or `turn`. */\nfunction angle(raw: string): number | null {\n const text = raw.trim();\n if (text === '') return 0;\n const m = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+))(deg|rad|grad|turn)?$/.exec(text);\n if (!m) return null;\n const n = Number(m[1]);\n if (m[2] === 'rad') return (n * 180) / Math.PI;\n if (m[2] === 'grad') return n * 0.9;\n if (m[2] === 'turn') return n * 360;\n return n;\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n));\n\n/**\n * Every CSS filter function that is a pure colour transform, with the argument\n * defaults and clamps CSS gives it. `blur()` and `drop-shadow()` are\n * deliberately absent — they move pixels around, so they can't be a matrix, and\n * a string containing one falls back to the CSS path.\n */\nconst FUNCTIONS: Record<string, (raw: string) => ColorMatrix | null> = {\n 'hue-rotate': (raw) => {\n const deg = angle(raw);\n return deg == null ? null : hueRotate(deg);\n },\n // grayscale(a) is exactly saturate(1 - a).\n grayscale: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : saturate(1 - clamp01(a));\n },\n sepia: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : sepia(clamp01(a));\n },\n saturate: (raw) => {\n const a = amount(raw, 1);\n return a == null || a < 0 ? null : saturate(a);\n },\n invert: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : invert(clamp01(a));\n },\n opacity: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : opacity(clamp01(a));\n },\n brightness: (raw) => {\n const a = amount(raw, 1);\n return a == null || a < 0 ? null : linear(a, 0);\n },\n contrast: (raw) => {\n const a = amount(raw, 1);\n return a == null || a < 0 ? null : linear(a, 0.5 - 0.5 * a);\n },\n};\n\nconst FUNCTION_RE = /([a-z-]+)\\(([^()]*)\\)/gi;\n\n/**\n * Turn a CSS `filter` string into the chain of colour matrices that computes it,\n * or `null` if any part of it isn't a colour transform (`blur`, `drop-shadow`,\n * `url(#…)`, a typo). `'none'` and an empty string yield an empty chain.\n *\n * Callers treat `null` as \"only a native implementation can do this\" — the\n * runtime path leaves the icon untouched, the SSR path emits a CSS `filter` as\n * it did before.\n */\nexport function filterMatrices(filter: string): ColorMatrix[] | null {\n const matrices: ColorMatrix[] = [];\n FUNCTION_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = FUNCTION_RE.exec(filter)) !== null) {\n const build = FUNCTIONS[m[1].toLowerCase()];\n if (!build) return null;\n const built = build(m[2]);\n if (!built) return null;\n matrices.push(built);\n }\n // Reject anything the scan didn't account for (bar whitespace), so a\n // half-understood string can't silently apply half a filter.\n const rest = filter.replace(FUNCTION_RE, '').trim();\n if (rest !== '' && rest !== 'none') return null;\n return matrices;\n}\n\n/**\n * Apply a matrix chain to raw `ImageData` bytes, in place. Values are\n * non-premultiplied on both sides (what `getImageData` hands back, and what\n * `feColorMatrix` is defined over), and `Uint8ClampedArray` supplies the\n * per-primitive clamp for free.\n */\nexport function applyMatrices(data: Uint8ClampedArray, matrices: readonly ColorMatrix[]): void {\n for (const m of matrices) {\n for (let i = 0; i < data.length; i += 4) {\n const r = data[i] / 255;\n const g = data[i + 1] / 255;\n const b = data[i + 2] / 255;\n const a = data[i + 3] / 255;\n data[i] = (m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4]) * 255;\n data[i + 1] = (m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9]) * 255;\n data[i + 2] = (m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14]) * 255;\n data[i + 3] = (m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19]) * 255;\n }\n }\n}\n","import { badgeText, DEFAULT_BADGE_COLOR, normalizeBadge, placeBadge } from './badge';\nimport { contrastColor } from './color';\nimport { cssFilter } from './filter';\nimport { type ColorMatrix, filterMatrices } from './matrix';\nimport type { Badge, EnvConfig } from './types';\n\nconst XML_ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&apos;',\n};\n\nfunction escapeXml(value: string): string {\n return value.replace(/[&<>\"']/g, (c) => XML_ESCAPES[c] ?? c);\n}\n\nconst round = (n: number): number => Math.round(n * 100) / 100;\n\n/** ` opacity=\"…\"` attribute for a translucent badge group, else `''`. */\nfunction opacityAttr(badge: Badge): string {\n return (badge.opacity ?? 1) < 1 ? ` opacity=\"${round(badge.opacity ?? 1)}\"` : '';\n}\n\n/** Attributes that squeeze the glyphs into `len` when the natural text would overflow. */\nfunction fitText(len: number): string {\n return ` textLength=\"${round(len)}\" lengthAdjust=\"spacingAndGlyphs\"`;\n}\n\n/** A centred, bold `<text>` element at (`cx`, `cy`) — the one badge-label form. */\nfunction svgText(\n cx: number,\n cy: number,\n fill: string,\n fontSize: number,\n fit: string,\n text: string,\n): string {\n return (\n `<text x=\"${round(cx)}\" y=\"${round(cy)}\" fill=\"${escapeXml(fill)}\" ` +\n `font-family=\"system-ui, sans-serif\" font-size=\"${round(fontSize)}\" font-weight=\"700\" ` +\n `text-anchor=\"middle\" dominant-baseline=\"central\"${fit}>${escapeXml(text)}</text>`\n );\n}\n\n/** Read the drawable box from a root `<svg>` tag: its `viewBox`, else `width`/`height`. */\nfunction parseViewBox(openTag: string): [number, number, number, number] | null {\n const vb = /viewBox\\s*=\\s*[\"']([^\"']+)[\"']/i.exec(openTag);\n if (vb) {\n const parts = vb[1]\n .trim()\n .split(/[\\s,]+/)\n .map(Number);\n if (parts.length === 4 && parts.every((n) => !Number.isNaN(n))) {\n return [parts[0], parts[1], parts[2], parts[3]];\n }\n }\n const w = /\\bwidth\\s*=\\s*[\"']?([\\d.]+)/i.exec(openTag);\n const h = /\\bheight\\s*=\\s*[\"']?([\\d.]+)/i.exec(openTag);\n if (w && h) return [0, 0, Number(w[1]), Number(h[1])];\n return null;\n}\n\n/** Build an SVG `<g>` badge (dot, or a pill with text) sized to the viewBox. */\nfunction svgBadge([minX, minY, w, h]: [number, number, number, number], badge: Badge): string {\n const text = badgeText(badge);\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const corner = badge.corner ?? 'bottom-right';\n const bh = h * (badge.size ?? 0.5);\n const fontSize = bh * 0.62;\n const margin = w * 0.02;\n // SVG has no text metrics at build time; approximate glyph width at ~0.62em,\n // then clamp to the icon so a long label (e.g. a big PR number) can't overflow.\n const natural = text ? Math.max(bh, text.length * fontSize * 0.62 + bh * 0.5) : bh;\n const bw = text ? Math.min(natural, w - margin * 2) : bh;\n const [px, py] = placeBadge(corner, w, h, bw, bh, margin);\n const x = minX + px;\n const y = minY + py;\n const rx = text ? Math.min(bh / 2, w * 0.24) : bh / 2;\n // If clamped, force the glyphs to fit the pill width.\n const fit = bw < natural ? fitText(bw - bh * 0.4) : '';\n const label = text\n ? svgText(x + bw / 2, y + bh / 2, badge.textColor ?? contrastColor(color), fontSize, fit, text)\n : '';\n return (\n `<g${opacityAttr(badge)}><rect x=\"${round(x)}\" y=\"${round(y)}\" width=\"${round(bw)}\" height=\"${round(bh)}\" ` +\n `rx=\"${round(rx)}\" fill=\"${escapeXml(color)}\" stroke=\"rgba(0,0,0,0.35)\" ` +\n `stroke-width=\"${round(h * 0.015)}\"/>${label}</g>`\n );\n}\n\n/** Build a full-icon \"cover\" tile — a background rect plus a big centred number. */\nfunction svgCover([minX, minY, w, h]: [number, number, number, number], badge: Badge): string {\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const text = badgeText(badge);\n const side = Math.min(w, h);\n const op = opacityAttr(badge);\n const rect =\n `<rect x=\"${round(minX)}\" y=\"${round(minY)}\" width=\"${round(w)}\" height=\"${round(h)}\" ` +\n `rx=\"${round(side * 0.2)}\" fill=\"${escapeXml(color)}\"/>`;\n if (!text) return `<g${op}>${rect}</g>`;\n const fontSize = side * 0.62;\n const maxLen = w * 0.84;\n const fit = text.length * fontSize * 0.62 > maxLen ? fitText(maxLen) : '';\n const label = svgText(\n minX + w / 2,\n minY + h / 2,\n badge.textColor ?? contrastColor(color),\n fontSize,\n fit,\n text,\n );\n return `<g${op}>${rect}${label}</g>`;\n}\n\n/** Matrix coefficients need more precision than geometry; 5 places is exact enough. */\nconst round5 = (n: number): number => Math.round(n * 1e5) / 1e5;\n\n/**\n * An SVG `<filter>` that runs a `hue`/`invert`/`filter` chain as `feColorMatrix`\n * primitives. A CSS `filter` on an inner SVG element is ignored by WebKit, so\n * this — not `<g style=\"filter:…\">` — is the form every engine honours. The\n * chain is one primitive per CSS filter function, which is also how a browser\n * evaluates the string (each result clamped to 0–1 before the next).\n */\nfunction svgFilterChain(matrices: readonly ColorMatrix[]): string {\n const primitives = matrices\n .map((m) => `<feColorMatrix type=\"matrix\" values=\"${m.map(round5).join(' ')}\"/>`)\n .join('');\n return `<filter id=\"__favenv_f\" color-interpolation-filters=\"sRGB\">${primitives}</filter>`;\n}\n\n/**\n * An SVG `<filter>` that colourises the icon to `color`, mirroring the runtime\n * canvas path: desaturate → multiply the flood colour → re-mask the original\n * alpha. `color-interpolation-filters=\"sRGB\"` keeps it visually matching canvas.\n */\nfunction svgColorize(color: string): string {\n return (\n `<filter id=\"__favenv_c\" color-interpolation-filters=\"sRGB\">` +\n `<feColorMatrix type=\"saturate\" values=\"0\" result=\"g\"/>` +\n `<feFlood flood-color=\"${escapeXml(color)}\" result=\"f\"/>` +\n `<feBlend in=\"f\" in2=\"g\" mode=\"multiply\" result=\"b\"/>` +\n `<feComposite in=\"b\" in2=\"SourceGraphic\" operator=\"in\"/>` +\n `</filter>`\n );\n}\n\n/**\n * Return `svg` (an SVG *string*) with the environment's tint and/or badge baked\n * in — the form that survives being rendered as an `<img>` / favicon, with no\n * first-paint flash. The tint is a CSS `filter` on a wrapping group; the badge\n * is an appended `<g>` positioned via the SVG's `viewBox` (a badge is skipped if\n * no `viewBox` or `width`/`height` can be read).\n *\n * Returns the SVG unchanged when `tint` is falsy, has nothing to apply, or the\n * input isn't a recognisable `<svg>…</svg>` document.\n */\nexport function tintSvg(svg: string, tint: EnvConfig): string {\n if (!tint) return svg;\n const filter = cssFilter(tint);\n const colorize = tint.filter ? undefined : tint.tint; // explicit `filter` beats `tint`\n const badge = tint.badge ? normalizeBadge(tint.badge) : null;\n if (!filter && !colorize && !badge) return svg;\n const open = /<svg\\b[^>]*>/i.exec(svg);\n if (!open) return svg;\n const close = svg.lastIndexOf('</svg>');\n if (close === -1) return svg;\n const openEnd = open.index + open[0].length;\n\n // `shape: 'cover'` replaces the icon's content with a full-bleed number tile.\n // A translucent cover (opacity < 1) keeps the base showing through instead.\n if (badge?.shape === 'cover') {\n const vb = parseViewBox(open[0]);\n if (!vb) return svg;\n const base = (badge.opacity ?? 1) < 1 ? svg.slice(openEnd, close) : '';\n return `${svg.slice(0, openEnd)}${base}${svgCover(vb, badge)}${svg.slice(close)}`;\n }\n\n const inner = svg.slice(openEnd, close);\n // No XML comments injected here — XML comments may not contain `--`, which\n // every `--custom-property` does, and that silently breaks favicon SVGs.\n // Both `tint` and `hue`/`invert`/`filter` go through an SVG `<filter>`: WebKit\n // ignores a CSS `filter` on an inner SVG element, so the CSS form below is\n // only a last resort for a filter with no matrix equivalent (`blur`, …).\n let defs = '';\n let body = inner;\n if (colorize) {\n defs = svgColorize(colorize);\n body = `<g filter=\"url(#__favenv_c)\">${inner}</g>`;\n } else if (filter) {\n const matrices = filterMatrices(filter);\n if (!matrices) {\n defs = `<style>.__favenv{filter:${escapeXml(filter)}}</style>`;\n body = `<g class=\"__favenv\">${inner}</g>`;\n } else if (matrices.length > 0) {\n defs = svgFilterChain(matrices);\n body = `<g filter=\"url(#__favenv_f)\">${inner}</g>`;\n }\n }\n const viewBox = badge ? parseViewBox(open[0]) : null;\n const badgeSvg = badge && viewBox ? svgBadge(viewBox, badge) : '';\n return `${svg.slice(0, openEnd)}${defs}${body}${badgeSvg}${svg.slice(close)}`;\n}\n\n/** Percent-encode an SVG string as a `data:` URI suitable for a favicon `href`. */\nexport function svgToDataUri(svg: string): string {\n return `data:image/svg+xml,${encodeURIComponent(svg)}`;\n}\n\n/**\n * Convenience: `tintSvg` + `svgToDataUri`. Give it your favicon SVG and the\n * config for the current build's environment; get back a ready-to-use\n * `<link rel=\"icon\" href=\"…\">` value with no first-paint flash.\n */\nexport function faviconDataUri(svg: string, tint: EnvConfig): string {\n return svgToDataUri(tintSvg(svg, tint));\n}\n"]}
@@ -1,2 +1,2 @@
1
- "use strict";var faviconEnv=(()=>{var h=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var H=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},_=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of W(t))!P.call(e,o)&&o!==n&&h(e,o,{get:()=>t[o],enumerable:!(r=U(t,o))||r.enumerable});return e};var j=e=>_(h({},"__esModule",{value:!0}),e);var z={};H(z,{envFavicon:()=>p});var M="#ef4444";function R(e){return typeof e=="string"?{color:e}:e}function B(e){return e.text==null?"":String(e.text)}function C(e,t,n,r,o,i){return e==="center"?[(t-r)/2,(n-o)/2]:[e.endsWith("left")?i:t-r-i,e.startsWith("top")?i:n-o-i]}function q(e){let t=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e);if(t){let r=t[1].length===3?t[1].replace(/./g,i=>i+i):t[1],o=Number.parseInt(r,16);return[o>>16&255,o>>8&255,o&255]}let n=/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i.exec(e);return n?[Number(n[1]),Number(n[2]),Number(n[3])]:null}function G(e){let t=q(e);if(t)return(.299*t[0]+.587*t[1]+.114*t[2])/255;let n=/^(okl(?:ch|ab)|l(?:ch|ab))\(\s*([\d.]+)(%?)/i.exec(e);if(n){let r=Number(n[2]);return n[3]?r/100:/^ok/i.test(n[1])?r:r/100}return null}function x(e){let t=G(e);return t==null?"#fff":t>.6?"#000":"#fff"}function L(e=typeof location>"u"?"":location.hostname){let t=e.toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t==="[::1]"||t.endsWith(".local")||t.endsWith(".localhost")||/^\d{1,3}(?:\.\d{1,3}){3}$/.test(t)?"dev":/(?:^|[.-])(?:staging|stg|qa|uat|preview|preprod|test|dev)(?:[.-]|$)/.test(t)?"staging":"prod"}function S(e){if(e.filter)return e.filter;let t=[];return typeof e.hue=="number"&&t.push(`hue-rotate(${e.hue}deg)`),e.invert&&t.push(`invert(${e.invert===!0?1:e.invert})`),t.length>0?t.join(" "):null}function $(e,t=0){let n=2166136261;for(let r=0;r<e.length;r+=1)n^=e.charCodeAt(r),n=Math.imul(n,16777619);return((n>>>0)%360+t%360+360)%360}var k='link[rel~="icon"]',O;function K(){let e=document.querySelectorAll(k);return e.item(e.length-1)?.href||void 0}function J(e,t){return e.replace(/\$(?:(\d+)|<([^>]+)>)/g,(n,r,o)=>o==null?t[Number(r)]??"":t.groups?.[o]??"")}function Q(e,t,n){if(typeof e=="string")return{color:e};let{text:r,...o}=e,i;return typeof r=="function"?i=r(t,n):typeof r=="string"&&t?i=J(r,t):i=r,{...o,text:i}}function A(e,t,n){let r={};return e.hue!=null&&(r.hue=e.hue),e.filter&&(r.filter=e.filter),e.src&&(r.src=e.src),e.badge!=null&&(r.badge=Q(e.badge,t,n)),r}function V(e,t){for(let n of e)if(typeof n.match=="function"){if(n.match(t))return A(n,null,t)}else{let r=t.host.match(n.match);if(r)return A(n,r,t)}return null}function X(e){let t=typeof location>"u"?null:new URL(location.href);if(e.rules&&t){let o=V(e.rules,t);if(o)return o}if(e.auto){let o=typeof e.auto=="object"?e.auto.offset??0:0;return{hue:$(t?.host??"",o)}}let n=(e.detect??L)();return(n?e.environments?.[n]:void 0)||null}function Y(e){if(e.startsWith("data:"))return/^data:([^;,]+)/.exec(e)?.[1];if(/\.svg(?:[?#]|$)/i.test(e))return"image/svg+xml";if(/\.png(?:[?#]|$)/i.test(e))return"image/png";if(/\.ico(?:[?#]|$)/i.test(e))return"image/x-icon"}function v(e,t){document.querySelectorAll(k).forEach(r=>r.remove());let n=document.createElement("link");n.rel="icon",t&&(n.type=t),n.href=e,n.dataset.faviconEnv="",document.head.append(n)}function F(e,t){return e.fillStyle=t,x(e.fillStyle)}function N(e,t,n,r,o,i){let c=Math.min(i,r/2,o/2);e.beginPath(),e.moveTo(t+c,n),e.arcTo(t+r,n,t+r,n+o,c),e.arcTo(t+r,n+o,t,n+o,c),e.arcTo(t,n+o,t,n,c),e.arcTo(t,n,t+r,n,c),e.closePath()}function D(e,t,n){let r=n.color??M,o=B(n),i='system-ui, -apple-system, "Segoe UI", sans-serif';if(n.shape==="cover"){if(N(e,0,0,t,t,Math.round(t*.2)),e.fillStyle=r,e.fill(),o){let E=t*.84,g=Math.round(t*.62);e.font=`700 ${g}px ${i}`;let T=e.measureText(o).width;T>E&&(g=Math.max(7,Math.floor(g*(E/T))),e.font=`700 ${g}px ${i}`),e.fillStyle=n.textColor??F(e,r),e.textAlign="center",e.textBaseline="middle",e.fillText(o,t/2,t/2)}return}let c=n.corner??"bottom-right",s=Math.round(t*(n.size??.5)),u=Math.round(t*.02),l=t-u*2,d=Math.round(s*.5),a=Math.round(s*.66);e.font=`700 ${a}px ${i}`;let f=o?Math.max(s,Math.ceil(e.measureText(o).width)+d):s;f>l&&(a=Math.max(7,Math.floor(a*(l/f))),e.font=`700 ${a}px ${i}`,f=Math.min(l,Math.max(s,Math.ceil(e.measureText(o).width)+d)));let[m,w]=C(c,t,t,f,s,u),b=Math.round(m),y=Math.round(w),I=o?Math.min(s/2,t*.24):s/2;N(e,b,y,f,s,I),e.fillStyle=r,e.fill(),e.lineWidth=Math.max(1,t*.03),e.strokeStyle="rgba(0, 0, 0, 0.35)",e.stroke(),o&&(e.fillStyle=n.textColor??F(e,r),e.textAlign="center",e.textBaseline="middle",e.fillText(o,b+f/2,y+s/2))}function p(e={}){if(typeof document>"u"||typeof HTMLCanvasElement>"u")return Promise.resolve();let t=X(e);if(!t)return Promise.resolve();let n=t.badge!=null?R(t.badge):void 0,r=e.size??64,o=n?.shape==="cover",i=n?.opacity??1;if(o&&i>=1){let u=document.createElement("canvas");u.width=r,u.height=r;let l=u.getContext("2d");return l&&(D(l,r,n),v(u.toDataURL("image/png"),"image/png")),Promise.resolve()}if(!(t.hue!=null||!!t.invert||!!t.filter||!!t.tint||!!n))return t.src&&v(t.src,Y(t.src)),Promise.resolve();O??(O=K());let s=t.src??e.source??O??"/favicon.ico";return new Promise(u=>{let l=new Image;l.crossOrigin="anonymous",l.decoding="async",l.addEventListener("error",()=>u()),l.addEventListener("load",()=>{try{let d=document.createElement("canvas");d.width=r,d.height=r;let a=d.getContext("2d");if(a){let f=!o&&!t.filter?t.tint:void 0;if(f)a.filter="grayscale(1)",a.drawImage(l,0,0,r,r),a.filter="none",a.globalCompositeOperation="multiply",a.fillStyle=f,a.fillRect(0,0,r,r),a.globalCompositeOperation="destination-in",a.drawImage(l,0,0,r,r),a.globalCompositeOperation="source-over";else{if(!o){let m=S(t);m&&(a.filter=m)}a.drawImage(l,0,0,r,r),a.filter="none"}n&&(a.globalAlpha=i,D(a,r,n),a.globalAlpha=1),v(d.toDataURL("image/png"),"image/png")}}catch{}u()}),l.src=s})}function Z(){let e=document.currentScript;if(!e)return;let t=e.dataset;if(t.auto!==void 0){p({auto:!0});return}let n={};for(let[r,o]of Object.entries(t)){let i=Number(o);o!==void 0&&o!==""&&!Number.isNaN(i)&&(n[r]={hue:i})}Object.keys(n).length>0&&p({environments:n})}Z();return j(z);})();
1
+ "use strict";var faviconEnv=(()=>{var C=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var nn=(n,t)=>{for(var e in t)C(n,e,{get:t[e],enumerable:!0})},tn=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Z(t))!z.call(n,o)&&o!==e&&C(n,o,{get:()=>t[o],enumerable:!(r=Y(t,o))||r.enumerable});return n};var en=n=>tn(C({},"__esModule",{value:!0}),n);var yn={};nn(yn,{envFavicon:()=>x});var I="#ef4444";function N(n){return typeof n=="string"?{color:n}:n}function O(n){return n.text==null?"":String(n.text)}function w(n,t,e,r,o,i){return n==="center"?[(t-r)/2,(e-o)/2]:[n.endsWith("left")?i:t-r-i,n.startsWith("top")?i:e-o-i]}function rn(n){let t=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(n);if(t){let r=t[1].length===3?t[1].replace(/./g,i=>i+i):t[1],o=Number.parseInt(r,16);return[o>>16&255,o>>8&255,o&255]}let e=/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i.exec(n);return e?[Number(e[1]),Number(e[2]),Number(e[3])]:null}function on(n){let t=rn(n);if(t)return(.299*t[0]+.587*t[1]+.114*t[2])/255;let e=/^(okl(?:ch|ab)|l(?:ch|ab))\(\s*([\d.]+)(%?)/i.exec(n);if(e){let r=Number(e[2]);return e[3]?r/100:/^ok/i.test(e[1])?r:r/100}return null}function A(n){let t=on(n);return t==null?"#fff":t>.6?"#000":"#fff"}function F(n=typeof location>"u"?"":location.hostname){let t=n.toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t==="[::1]"||t.endsWith(".local")||t.endsWith(".localhost")||/^\d{1,3}(?:\.\d{1,3}){3}$/.test(t)?"dev":/(?:^|[.-])(?:staging|stg|qa|uat|preview|preprod|test|dev)(?:[.-]|$)/.test(t)?"staging":"prod"}function D(n){if(n.filter)return n.filter;let t=[];return typeof n.hue=="number"&&t.push(`hue-rotate(${n.hue}deg)`),n.invert&&t.push(`invert(${n.invert===!0?1:n.invert})`),t.length>0?t.join(" "):null}function k(n,t=0){let e=2166136261;for(let r=0;r<n.length;r+=1)e^=n.charCodeAt(r),e=Math.imul(e,16777619);return((e>>>0)%360+t%360+360)%360}var R=n=>[n[0],n[1],n[2],0,n[3]];function v(n,t,e,r=1){return[...R(n),...R(t),...R(e),0,0,0,r,0]}var[m,d,g]=[.213,.715,.072];function U(n){return v([m+.787*n,d-d*n,g-g*n,0],[m-m*n,d+.285*n,g-g*n,0],[m-m*n,d-d*n,g+.928*n,0])}function ln(n){let t=n*Math.PI/180,e=Math.cos(t),r=Math.sin(t);return v([m+e*.787-r*.213,d-e*.715-r*.715,g-e*.072+r*.928,0],[m-e*.213+r*.143,d+e*.285+r*.14,g-e*.072-r*.283,0],[m-e*.213-r*.787,d-e*.715+r*.715,g+e*.928+r*.072,0])}function un(n){let t=1-n;return v([t+.393*n,.769*n,.189*n,0],[.349*n,t+.686*n,.168*n,0],[.272*n,.534*n,t+.131*n,0])}function an(n){let t=1-2*n;return v([t,0,0,n],[0,t,0,n],[0,0,t,n])}function P(n,t){return v([n,0,0,t],[0,n,0,t],[0,0,n,t])}function sn(n){return v([1,0,0,0],[0,1,0,0],[0,0,1,0],n)}function h(n,t){let e=n.trim();if(e==="")return t;let r=/^([+-]?(?:\d+\.?\d*|\.\d+))(%?)$/.exec(e);if(!r)return null;let o=Number(r[1]);return r[2]?o/100:o}function cn(n){let t=n.trim();if(t==="")return 0;let e=/^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|grad|turn)?$/.exec(t);if(!e)return null;let r=Number(e[1]);return e[2]==="rad"?r*180/Math.PI:e[2]==="grad"?r*.9:e[2]==="turn"?r*360:r}var M=n=>Math.min(1,Math.max(0,n)),fn={"hue-rotate":n=>{let t=cn(n);return t==null?null:ln(t)},grayscale:n=>{let t=h(n,1);return t==null?null:U(1-M(t))},sepia:n=>{let t=h(n,1);return t==null?null:un(M(t))},saturate:n=>{let t=h(n,1);return t==null||t<0?null:U(t)},invert:n=>{let t=h(n,1);return t==null?null:an(M(t))},opacity:n=>{let t=h(n,1);return t==null?null:sn(M(t))},brightness:n=>{let t=h(n,1);return t==null||t<0?null:P(t,0)},contrast:n=>{let t=h(n,1);return t==null||t<0?null:P(t,.5-.5*t)}},E=/([a-z-]+)\(([^()]*)\)/gi;function W(n){let t=[];E.lastIndex=0;let e;for(;(e=E.exec(n))!==null;){let o=fn[e[1].toLowerCase()];if(!o)return null;let i=o(e[2]);if(!i)return null;t.push(i)}let r=n.replace(E,"").trim();return r!==""&&r!=="none"?null:t}function _(n,t){for(let e of t)for(let r=0;r<n.length;r+=4){let o=n[r]/255,i=n[r+1]/255,s=n[r+2]/255,u=n[r+3]/255;n[r]=(e[0]*o+e[1]*i+e[2]*s+e[3]*u+e[4])*255,n[r+1]=(e[5]*o+e[6]*i+e[7]*s+e[8]*u+e[9])*255,n[r+2]=(e[10]*o+e[11]*i+e[12]*s+e[13]*u+e[14])*255,n[r+3]=(e[15]*o+e[16]*i+e[17]*s+e[18]*u+e[19])*255}}var Q='link[rel~="icon"]',H;function mn(){let n=document.querySelectorAll(Q);return n.item(n.length-1)?.href||void 0}function dn(n,t){return n.replace(/\$(?:(\d+)|<([^>]+)>)/g,(e,r,o)=>o==null?t[Number(r)]??"":t.groups?.[o]??"")}function gn(n,t,e){if(typeof n=="string")return{color:n};let{text:r,...o}=n,i;return typeof r=="function"?i=r(t,e):typeof r=="string"&&t?i=dn(r,t):i=r,{...o,text:i}}function j(n,t,e){let r={};return n.hue!=null&&(r.hue=n.hue),n.filter&&(r.filter=n.filter),n.src&&(r.src=n.src),n.badge!=null&&(r.badge=gn(n.badge,t,e)),r}function pn(n,t){for(let e of n)if(typeof e.match=="function"){if(e.match(t))return j(e,null,t)}else{let r=t.host.match(e.match);if(r)return j(e,r,t)}return null}function hn(n){let t=typeof location>"u"?null:new URL(location.href);if(n.rules&&t){let o=pn(n.rules,t);if(o)return o}if(n.auto){let o=typeof n.auto=="object"?n.auto.offset??0:0;return{hue:k(t?.host??"",o)}}let e=(n.detect??F)();return(e?n.environments?.[e]:void 0)||null}function vn(n){if(n.startsWith("data:"))return/^data:([^;,]+)/.exec(n)?.[1];if(/\.svg(?:[?#]|$)/i.test(n))return"image/svg+xml";if(/\.png(?:[?#]|$)/i.test(n))return"image/png";if(/\.ico(?:[?#]|$)/i.test(n))return"image/x-icon"}function T(n,t){document.querySelectorAll(Q).forEach(r=>r.remove());let e=document.createElement("link");e.rel="icon",t&&(e.type=t),e.href=n,e.dataset.faviconEnv="",document.head.append(e)}function q(n,t,e,r){if(typeof n.filter=="string"){n.filter=r,n.drawImage(t,0,0,e,e),n.filter="none";return}n.drawImage(t,0,0,e,e);let o=W(r);if(!o||o.length===0)return;let i=n.getImageData(0,0,e,e);_(i.data,o),n.putImageData(i,0,0)}function G(n,t){return n.fillStyle=t,A(n.fillStyle)}function K(n,t,e,r,o,i){let s=Math.min(i,r/2,o/2);n.beginPath(),n.moveTo(t+s,e),n.arcTo(t+r,e,t+r,e+o,s),n.arcTo(t+r,e+o,t,e+o,s),n.arcTo(t,e+o,t,e,s),n.arcTo(t,e,t+r,e,s),n.closePath()}function J(n,t,e){let r=e.color??I,o=O(e),i='system-ui, -apple-system, "Segoe UI", sans-serif';if(e.shape==="cover"){if(K(n,0,0,t,t,Math.round(t*.2)),n.fillStyle=r,n.fill(),o){let S=t*.84,y=Math.round(t*.62);n.font=`700 ${y}px ${i}`;let $=n.measureText(o).width;$>S&&(y=Math.max(7,Math.floor(y*(S/$))),n.font=`700 ${y}px ${i}`),n.fillStyle=e.textColor??G(n,r),n.textAlign="center",n.textBaseline="middle",n.fillText(o,t/2,t/2)}return}let s=e.corner??"bottom-right",u=Math.round(t*(e.size??.5)),c=Math.round(t*.02),a=t-c*2,p=Math.round(u*.5),l=Math.round(u*.66);n.font=`700 ${l}px ${i}`;let f=o?Math.max(u,Math.ceil(n.measureText(o).width)+p):u;f>a&&(l=Math.max(7,Math.floor(l*(a/f))),n.font=`700 ${l}px ${i}`,f=Math.min(a,Math.max(u,Math.ceil(n.measureText(o).width)+p)));let[b,V]=w(s,t,t,f,u,c),B=Math.round(b),L=Math.round(V),X=o?Math.min(u/2,t*.24):u/2;K(n,B,L,f,u,X),n.fillStyle=r,n.fill(),n.lineWidth=Math.max(1,t*.03),n.strokeStyle="rgba(0, 0, 0, 0.35)",n.stroke(),o&&(n.fillStyle=e.textColor??G(n,r),n.textAlign="center",n.textBaseline="middle",n.fillText(o,B+f/2,L+u/2))}function x(n={}){if(typeof document>"u"||typeof HTMLCanvasElement>"u")return Promise.resolve();let t=hn(n);if(!t)return Promise.resolve();let e=t.badge!=null?N(t.badge):void 0,r=n.size??64,o=e?.shape==="cover",i=e?.opacity??1;if(o&&i>=1){let c=document.createElement("canvas");c.width=r,c.height=r;let a=c.getContext("2d");return a&&(J(a,r,e),T(c.toDataURL("image/png"),"image/png")),Promise.resolve()}if(!(t.hue!=null||!!t.invert||!!t.filter||!!t.tint||!!e))return t.src&&T(t.src,vn(t.src)),Promise.resolve();H??(H=mn());let u=t.src??n.source??H??"/favicon.ico";return new Promise(c=>{let a=new Image;a.crossOrigin="anonymous",a.decoding="async",a.addEventListener("error",()=>c()),a.addEventListener("load",()=>{try{let p=document.createElement("canvas");p.width=r,p.height=r;let l=p.getContext("2d");if(l){let f=!o&&!t.filter?t.tint:void 0;if(f)q(l,a,r,"grayscale(1)"),l.globalCompositeOperation="multiply",l.fillStyle=f,l.fillRect(0,0,r,r),l.globalCompositeOperation="destination-in",l.drawImage(a,0,0,r,r),l.globalCompositeOperation="source-over";else{let b=o?null:D(t);b?q(l,a,r,b):l.drawImage(a,0,0,r,r)}e&&(l.globalAlpha=i,J(l,r,e),l.globalAlpha=1),T(p.toDataURL("image/png"),"image/png")}}catch{}c()}),a.src=u})}function bn(){let n=document.currentScript;if(!n)return;let t=n.dataset;if(t.auto!==void 0){x({auto:!0});return}let e={};for(let[r,o]of Object.entries(t)){let i=Number(o);o!==void 0&&o!==""&&!Number.isNaN(i)&&(e[r]={hue:i})}Object.keys(e).length>0&&x({environments:e})}bn();return en(yn);})();
2
2
  //# sourceMappingURL=favicon-env.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/global.ts","../src/badge.ts","../src/color.ts","../src/detect.ts","../src/filter.ts","../src/hash.ts","../src/tint.ts"],"sourcesContent":["import { envFavicon } from './tint';\nimport type { EnvFaviconOptions, EnvTint } from './types';\n\n// IIFE entry for no-build sites. Exposed as `window.faviconEnv.envFavicon(...)`,\n// and auto-runs from the loading <script>'s data-* attributes:\n// <script src=\"…/favicon-env.global.js\" data-auto></script>\n// <script src=\"…/favicon-env.global.js\" data-dev=\"130\" data-staging=\"45\"></script>\nfunction boot(): void {\n const el = document.currentScript as HTMLScriptElement | null;\n if (!el) return;\n const data = el.dataset;\n if (data.auto !== undefined) {\n void envFavicon({ auto: true });\n return;\n }\n const environments: Record<string, EnvTint> = {};\n for (const [name, value] of Object.entries(data)) {\n const hue = Number(value);\n if (value !== undefined && value !== '' && !Number.isNaN(hue)) {\n environments[name] = { hue };\n }\n }\n if (Object.keys(environments).length > 0) void envFavicon({ environments });\n}\n\nboot();\n\nexport { envFavicon };\nexport type { EnvFaviconOptions };\n","import type { Badge, BadgeCorner } from './types';\n\n/** Default badge background — the red dot/pill you get when no `color` is set. */\nexport const DEFAULT_BADGE_COLOR = '#ef4444';\n\n/** Normalise the `badge` shorthand (a `string` is a dot of that colour) to a `Badge`. */\nexport function normalizeBadge(badge: string | Badge): Badge {\n return typeof badge === 'string' ? { color: badge } : badge;\n}\n\n/** A badge's text as a string (`''` when omitted) — so a `0` renders and `undefined` doesn't. */\nexport function badgeText(badge: Badge): string {\n return badge.text == null ? '' : String(badge.text);\n}\n\n/**\n * Top-left corner for a `bw`×`bh` badge inside a `cw`×`ch` box, per `corner`\n * (relative to the box origin; the caller adds any offset). Shared by the canvas\n * and SVG renderers so placement stays identical across runtime and build-time.\n */\nexport function placeBadge(\n corner: BadgeCorner,\n cw: number,\n ch: number,\n bw: number,\n bh: number,\n margin: number,\n): [number, number] {\n if (corner === 'center') return [(cw - bw) / 2, (ch - bh) / 2];\n return [\n corner.endsWith('left') ? margin : cw - bw - margin,\n corner.startsWith('top') ? margin : ch - bh - margin,\n ];\n}\n","/** Parse `#rgb` / `#rrggbb` / `rgb(…)` / `rgba(…)` into `[r, g, b]` (0–255), or `null`. */\nfunction parseRgb(color: string): [number, number, number] | null {\n const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color);\n if (hex) {\n const h = hex[1].length === 3 ? hex[1].replace(/./g, (c) => c + c) : hex[1];\n const n = Number.parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n }\n const rgb = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/i.exec(color);\n if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n return null;\n}\n\n/**\n * Perceived lightness (0–1) of `color` for the black/white text decision:\n * rec601 luma for `#hex` / `rgb()`, or the `L` channel of CSS Color 4 colours —\n * `oklch()`/`oklab()` (0–1) and `lab()`/`lch()` (0–100). `null` if unreadable.\n */\nfunction lightness(color: string): number | null {\n const rgb = parseRgb(color);\n if (rgb) return (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;\n const lch = /^(okl(?:ch|ab)|l(?:ch|ab))\\(\\s*([\\d.]+)(%?)/i.exec(color);\n if (lch) {\n const l = Number(lch[2]);\n if (lch[3]) return l / 100; // a percentage L, in any of these spaces\n return /^ok/i.test(lch[1]) ? l : l / 100; // oklab/oklch L is 0–1, lab/lch is 0–100\n }\n return null;\n}\n\n/**\n * Black or white — whichever reads better on `color` — by perceived lightness.\n * Handles `#hex`, `rgb()`, and CSS Color 4 `oklch()`/`oklab()`/`lab()`/`lch()`;\n * falls back to white for anything else (named colours, `hsl()`, `color()`, …).\n */\nexport function contrastColor(color: string): string {\n const l = lightness(color);\n if (l == null) return '#fff';\n return l > 0.6 ? '#000' : '#fff';\n}\n","/**\n * Default environment heuristic, based on `location.hostname`:\n *\n * - `localhost`, `127.0.0.1`, `::1`, `*.local`, `*.localhost`, a raw IPv4 → `'dev'`\n * - a `staging` / `stg` / `qa` / `uat` / `preview` / `preprod` / `test` / `dev`\n * segment anywhere in the host → `'staging'`\n * - anything else → `'prod'`\n *\n * It's intentionally simple. Pass your own `detect` to `envFavicon` when your\n * hostnames don't follow these conventions.\n *\n * @param hostname override the host to classify (defaults to `location.hostname`)\n */\nexport function defaultDetect(\n hostname: string = typeof location === 'undefined' ? '' : location.hostname,\n): string {\n const h = hostname.toLowerCase();\n if (\n h === 'localhost' ||\n h === '127.0.0.1' ||\n h === '::1' ||\n h === '[::1]' ||\n h.endsWith('.local') ||\n h.endsWith('.localhost') ||\n /^\\d{1,3}(?:\\.\\d{1,3}){3}$/.test(h)\n ) {\n return 'dev';\n }\n if (/(?:^|[.-])(?:staging|stg|qa|uat|preview|preprod|test|dev)(?:[.-]|$)/.test(h)) {\n return 'staging';\n }\n return 'prod';\n}\n","import type { EnvTint } from './types';\n\n/**\n * The CSS `filter` for a tint. An explicit `filter` wins outright; otherwise\n * `hue` and `invert` compose into one filter string (both applied, in that\n * order). `null` when none of them are set.\n */\nexport function cssFilter(tint: EnvTint): string | null {\n if (tint.filter) return tint.filter;\n const parts: string[] = [];\n if (typeof tint.hue === 'number') parts.push(`hue-rotate(${tint.hue}deg)`);\n // `invert: true` → invert(1); a number is used verbatim. `0` / `false` is a no-op.\n if (tint.invert) parts.push(`invert(${tint.invert === true ? 1 : tint.invert})`);\n return parts.length > 0 ? parts.join(' ') : null;\n}\n","/**\n * Deterministic hue (0–359) derived from a string via FNV-1a. Stable across\n * loads and well distributed, so distinct inputs (e.g. `location.host` values)\n * get distinct colours. Powers `envFavicon`'s auto mode.\n *\n * @param input the string to hash (e.g. `location.host`)\n * @param offset extra degrees added to the result, to shift the whole palette\n */\nexport function hashHue(input: string, offset = 0): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (((h >>> 0) % 360) + (offset % 360) + 360) % 360;\n}\n","import { badgeText, DEFAULT_BADGE_COLOR, normalizeBadge, placeBadge } from './badge';\nimport { contrastColor } from './color';\nimport { defaultDetect } from './detect';\nimport { cssFilter } from './filter';\nimport { hashHue } from './hash';\nimport type { Badge, EnvFaviconOptions, EnvRule, EnvTint, RuleBadge } from './types';\n\n/** Selector for the favicon `<link>`(s) we read from and replace. */\nconst ICON_LINK = 'link[rel~=\"icon\"]';\n\n/** Remembered original favicon href, so repeat calls don't tint an already-tinted icon. */\nlet originalSource: string | undefined;\n\nfunction currentIconHref(): string | undefined {\n const links = document.querySelectorAll<HTMLLinkElement>(ICON_LINK);\n const last = links.item(links.length - 1);\n return last?.href || undefined;\n}\n\n/** Fill `$1` / `$<name>` placeholders in a template from a regex match. */\nfunction interpolate(template: string, match: RegExpMatchArray): string {\n return template.replace(/\\$(?:(\\d+)|<([^>]+)>)/g, (_full, index, name) =>\n name == null ? (match[Number(index)] ?? '') : (match.groups?.[name] ?? ''),\n );\n}\n\nfunction resolveRuleBadge(\n badge: string | RuleBadge,\n match: RegExpMatchArray | null,\n url: URL,\n): Badge {\n if (typeof badge === 'string') return { color: badge };\n const { text, ...rest } = badge;\n let resolved: string | number | undefined;\n if (typeof text === 'function') resolved = text(match, url);\n else if (typeof text === 'string' && match) resolved = interpolate(text, match);\n else resolved = text;\n return { ...rest, text: resolved };\n}\n\nfunction ruleToTint(rule: EnvRule, match: RegExpMatchArray | null, url: URL): EnvTint {\n const tint: EnvTint = {};\n if (rule.hue != null) tint.hue = rule.hue;\n if (rule.filter) tint.filter = rule.filter;\n if (rule.src) tint.src = rule.src;\n if (rule.badge != null) tint.badge = resolveRuleBadge(rule.badge, match, url);\n return tint;\n}\n\n/**\n * Return the tint of the first `rule` whose `match` matches `url`, or `null` if\n * none do. A `RegExp` `match` is tested against `url.host` and its captures are\n * interpolated into `badge.text` (`$1`, `$<name>`); a function `match` receives\n * the `URL`. Exposed so the same rules can drive server-side rendering from a\n * request URL — pair the result with `favicon-env/ssr`'s `faviconDataUri`.\n */\nexport function matchRules(rules: EnvRule[], url: URL): EnvTint | null {\n for (const rule of rules) {\n if (typeof rule.match === 'function') {\n if (rule.match(url)) return ruleToTint(rule, null, url);\n } else {\n const m = url.host.match(rule.match);\n if (m) return ruleToTint(rule, m, url);\n }\n }\n return null;\n}\n\nfunction resolveTint(options: EnvFaviconOptions): EnvTint | null {\n const url = typeof location === 'undefined' ? null : new URL(location.href);\n if (options.rules && url) {\n const matched = matchRules(options.rules, url);\n if (matched) return matched;\n }\n if (options.auto) {\n const offset = typeof options.auto === 'object' ? (options.auto.offset ?? 0) : 0;\n return { hue: hashHue(url?.host ?? '', offset) };\n }\n const env = (options.detect ?? defaultDetect)();\n const tint = env ? options.environments?.[env] : undefined;\n return tint || null;\n}\n\nfunction inferType(src: string): string | undefined {\n if (src.startsWith('data:')) return /^data:([^;,]+)/.exec(src)?.[1];\n if (/\\.svg(?:[?#]|$)/i.test(src)) return 'image/svg+xml';\n if (/\\.png(?:[?#]|$)/i.test(src)) return 'image/png';\n if (/\\.ico(?:[?#]|$)/i.test(src)) return 'image/x-icon';\n return undefined;\n}\n\nfunction applyFavicon(href: string, type?: string): void {\n document.querySelectorAll(ICON_LINK).forEach((link) => link.remove());\n const link = document.createElement('link');\n link.rel = 'icon';\n if (type) link.type = type;\n link.href = href;\n link.dataset.faviconEnv = '';\n document.head.append(link);\n}\n\nfunction contrastText(ctx: CanvasRenderingContext2D, background: string): string {\n ctx.fillStyle = background; // canvas normalises any CSS colour to #rrggbb / rgba(…)\n return contrastColor(ctx.fillStyle);\n}\n\nfunction traceRoundRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n): void {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + rr, y);\n ctx.arcTo(x + w, y, x + w, y + h, rr);\n ctx.arcTo(x + w, y + h, x, y + h, rr);\n ctx.arcTo(x, y + h, x, y, rr);\n ctx.arcTo(x, y, x + w, y, rr);\n ctx.closePath();\n}\n\nfunction drawBadge(ctx: CanvasRenderingContext2D, size: number, badge: Badge): void {\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const text = badgeText(badge);\n const family = 'system-ui, -apple-system, \"Segoe UI\", sans-serif';\n\n // `shape: 'cover'` fills the whole icon and centres the number as big as it fits.\n if (badge.shape === 'cover') {\n traceRoundRect(ctx, 0, 0, size, size, Math.round(size * 0.2));\n ctx.fillStyle = color;\n ctx.fill();\n if (text) {\n const maxWidth = size * 0.84;\n let fontSize = Math.round(size * 0.62);\n ctx.font = `700 ${fontSize}px ${family}`;\n const textWidth = ctx.measureText(text).width;\n if (textWidth > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / textWidth)));\n ctx.font = `700 ${fontSize}px ${family}`;\n }\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, size / 2, size / 2);\n }\n return;\n }\n\n const corner = badge.corner ?? 'bottom-right';\n const h = Math.round(size * (badge.size ?? 0.5));\n const margin = Math.round(size * 0.02);\n const maxWidth = size - margin * 2;\n const pad = Math.round(h * 0.5);\n // A dot / short label sits in a pill that widens (then the font shrinks) to fit\n // longer text. Bump badge.size + corner:'center' to make a number dominate.\n let fontSize = Math.round(h * 0.66);\n ctx.font = `700 ${fontSize}px ${family}`;\n let w = text ? Math.max(h, Math.ceil(ctx.measureText(text).width) + pad) : h;\n if (w > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / w)));\n ctx.font = `700 ${fontSize}px ${family}`;\n w = Math.min(maxWidth, Math.max(h, Math.ceil(ctx.measureText(text).width) + pad));\n }\n const [px, py] = placeBadge(corner, size, size, w, h, margin);\n const x = Math.round(px);\n const y = Math.round(py);\n const radius = text ? Math.min(h / 2, size * 0.24) : h / 2;\n\n traceRoundRect(ctx, x, y, w, h, radius);\n ctx.fillStyle = color;\n ctx.fill();\n ctx.lineWidth = Math.max(1, size * 0.03);\n ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';\n ctx.stroke();\n\n if (text) {\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, x + w / 2, y + h / 2);\n }\n}\n\n/**\n * Tint / decorate the page favicon for the current environment. Resolves a tint\n * from `rules` (URL match) → `auto` → `environments`/`detect`, then reads the\n * existing `<link rel=\"icon\">` (or a per-env `src` / `options.source`), redraws\n * it on a `<canvas>` with the hue / filter / badge, and swaps in the result as a\n * PNG data URL.\n *\n * A no-op during SSR (no `document`) or when nothing resolves. Works with any\n * favicon format (svg / png / ico). A custom `src` with no recolour or badge is\n * swapped in directly, skipping the canvas — which avoids cross-origin taint and\n * keeps vector sources sharp. If a canvas source is cross-origin without CORS\n * headers the canvas is tainted and the favicon is left untouched.\n *\n * @returns a promise that resolves once the swap has been attempted.\n */\nexport function envFavicon(options: EnvFaviconOptions = {}): Promise<void> {\n if (typeof document === 'undefined' || typeof HTMLCanvasElement === 'undefined') {\n return Promise.resolve();\n }\n const tint = resolveTint(options);\n if (!tint) return Promise.resolve();\n\n const badge = tint.badge != null ? normalizeBadge(tint.badge) : undefined;\n const size = options.size ?? 64;\n const cover = badge?.shape === 'cover';\n const alpha = badge?.opacity ?? 1;\n\n // An opaque `cover` replaces the icon entirely — no base image needed, so draw\n // it synchronously. A translucent cover falls through to composite over the base.\n if (cover && alpha >= 1) {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n drawBadge(ctx, size, badge);\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n return Promise.resolve();\n }\n\n // Plain image swap: a custom `src` with nothing to composite skips the canvas.\n const needsCanvas =\n tint.hue != null ||\n Boolean(tint.invert) ||\n Boolean(tint.filter) ||\n Boolean(tint.tint) ||\n Boolean(badge);\n if (!needsCanvas) {\n if (tint.src) applyFavicon(tint.src, inferType(tint.src));\n return Promise.resolve();\n }\n\n originalSource ??= currentIconHref();\n const source = tint.src ?? options.source ?? originalSource ?? '/favicon.ico';\n\n return new Promise<void>((resolve) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.decoding = 'async';\n img.addEventListener('error', () => resolve());\n img.addEventListener('load', () => {\n try {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n // `cover` replaces the icon, so it ignores any recolour. Otherwise:\n // `tint` colourises the base to an exact colour (grayscale → multiply →\n // re-mask the original alpha), else a `hue`/`invert`/`filter` is a plain\n // CSS filter. The base is always drawn — a translucent cover shows through.\n const colorize = !cover && !tint.filter ? tint.tint : undefined;\n if (colorize) {\n ctx.filter = 'grayscale(1)';\n ctx.drawImage(img, 0, 0, size, size);\n ctx.filter = 'none';\n ctx.globalCompositeOperation = 'multiply';\n ctx.fillStyle = colorize;\n ctx.fillRect(0, 0, size, size);\n ctx.globalCompositeOperation = 'destination-in';\n ctx.drawImage(img, 0, 0, size, size);\n ctx.globalCompositeOperation = 'source-over';\n } else {\n if (!cover) {\n const filter = cssFilter(tint);\n if (filter) ctx.filter = filter;\n }\n ctx.drawImage(img, 0, 0, size, size);\n ctx.filter = 'none';\n }\n if (badge) {\n ctx.globalAlpha = alpha;\n drawBadge(ctx, size, badge);\n ctx.globalAlpha = 1;\n }\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n } catch {\n // Tainted canvas (cross-origin source without CORS headers) or an\n // unsupported API — leave the existing favicon in place.\n }\n resolve();\n });\n img.src = source;\n });\n}\n"],"mappings":"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","names":["global_exports","__export","envFavicon","DEFAULT_BADGE_COLOR","normalizeBadge","badge","badgeText","placeBadge","corner","cw","ch","bw","bh","margin","parseRgb","color","hex","h","c","n","rgb","lightness","lch","l","contrastColor","defaultDetect","hostname","h","cssFilter","tint","parts","hashHue","input","offset","h","i","ICON_LINK","originalSource","currentIconHref","links","interpolate","template","match","_full","index","name","resolveRuleBadge","badge","url","text","rest","resolved","ruleToTint","rule","tint","matchRules","rules","m","resolveTint","options","matched","offset","hashHue","env","defaultDetect","inferType","src","applyFavicon","href","type","link","contrastText","ctx","background","contrastColor","traceRoundRect","x","y","w","h","r","rr","drawBadge","size","color","DEFAULT_BADGE_COLOR","badgeText","family","maxWidth","fontSize","textWidth","corner","margin","pad","px","py","placeBadge","radius","envFavicon","normalizeBadge","cover","alpha","canvas","source","resolve","img","colorize","filter","cssFilter","boot","el","data","envFavicon","environments","name","value","hue"]}
1
+ {"version":3,"sources":["../src/global.ts","../src/badge.ts","../src/color.ts","../src/detect.ts","../src/filter.ts","../src/hash.ts","../src/matrix.ts","../src/tint.ts"],"sourcesContent":["import { envFavicon } from './tint';\nimport type { EnvFaviconOptions, EnvTint } from './types';\n\n// IIFE entry for no-build sites. Exposed as `window.faviconEnv.envFavicon(...)`,\n// and auto-runs from the loading <script>'s data-* attributes:\n// <script src=\"…/favicon-env.global.js\" data-auto></script>\n// <script src=\"…/favicon-env.global.js\" data-dev=\"130\" data-staging=\"45\"></script>\nfunction boot(): void {\n const el = document.currentScript as HTMLScriptElement | null;\n if (!el) return;\n const data = el.dataset;\n if (data.auto !== undefined) {\n void envFavicon({ auto: true });\n return;\n }\n const environments: Record<string, EnvTint> = {};\n for (const [name, value] of Object.entries(data)) {\n const hue = Number(value);\n if (value !== undefined && value !== '' && !Number.isNaN(hue)) {\n environments[name] = { hue };\n }\n }\n if (Object.keys(environments).length > 0) void envFavicon({ environments });\n}\n\nboot();\n\nexport { envFavicon };\nexport type { EnvFaviconOptions };\n","import type { Badge, BadgeCorner } from './types';\n\n/** Default badge background — the red dot/pill you get when no `color` is set. */\nexport const DEFAULT_BADGE_COLOR = '#ef4444';\n\n/** Normalise the `badge` shorthand (a `string` is a dot of that colour) to a `Badge`. */\nexport function normalizeBadge(badge: string | Badge): Badge {\n return typeof badge === 'string' ? { color: badge } : badge;\n}\n\n/** A badge's text as a string (`''` when omitted) — so a `0` renders and `undefined` doesn't. */\nexport function badgeText(badge: Badge): string {\n return badge.text == null ? '' : String(badge.text);\n}\n\n/**\n * Top-left corner for a `bw`×`bh` badge inside a `cw`×`ch` box, per `corner`\n * (relative to the box origin; the caller adds any offset). Shared by the canvas\n * and SVG renderers so placement stays identical across runtime and build-time.\n */\nexport function placeBadge(\n corner: BadgeCorner,\n cw: number,\n ch: number,\n bw: number,\n bh: number,\n margin: number,\n): [number, number] {\n if (corner === 'center') return [(cw - bw) / 2, (ch - bh) / 2];\n return [\n corner.endsWith('left') ? margin : cw - bw - margin,\n corner.startsWith('top') ? margin : ch - bh - margin,\n ];\n}\n","/** Parse `#rgb` / `#rrggbb` / `rgb(…)` / `rgba(…)` into `[r, g, b]` (0–255), or `null`. */\nfunction parseRgb(color: string): [number, number, number] | null {\n const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color);\n if (hex) {\n const h = hex[1].length === 3 ? hex[1].replace(/./g, (c) => c + c) : hex[1];\n const n = Number.parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n }\n const rgb = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/i.exec(color);\n if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n return null;\n}\n\n/**\n * Perceived lightness (0–1) of `color` for the black/white text decision:\n * rec601 luma for `#hex` / `rgb()`, or the `L` channel of CSS Color 4 colours —\n * `oklch()`/`oklab()` (0–1) and `lab()`/`lch()` (0–100). `null` if unreadable.\n */\nfunction lightness(color: string): number | null {\n const rgb = parseRgb(color);\n if (rgb) return (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;\n const lch = /^(okl(?:ch|ab)|l(?:ch|ab))\\(\\s*([\\d.]+)(%?)/i.exec(color);\n if (lch) {\n const l = Number(lch[2]);\n if (lch[3]) return l / 100; // a percentage L, in any of these spaces\n return /^ok/i.test(lch[1]) ? l : l / 100; // oklab/oklch L is 0–1, lab/lch is 0–100\n }\n return null;\n}\n\n/**\n * Black or white — whichever reads better on `color` — by perceived lightness.\n * Handles `#hex`, `rgb()`, and CSS Color 4 `oklch()`/`oklab()`/`lab()`/`lch()`;\n * falls back to white for anything else (named colours, `hsl()`, `color()`, …).\n */\nexport function contrastColor(color: string): string {\n const l = lightness(color);\n if (l == null) return '#fff';\n return l > 0.6 ? '#000' : '#fff';\n}\n","/**\n * Default environment heuristic, based on `location.hostname`:\n *\n * - `localhost`, `127.0.0.1`, `::1`, `*.local`, `*.localhost`, a raw IPv4 → `'dev'`\n * - a `staging` / `stg` / `qa` / `uat` / `preview` / `preprod` / `test` / `dev`\n * segment anywhere in the host → `'staging'`\n * - anything else → `'prod'`\n *\n * It's intentionally simple. Pass your own `detect` to `envFavicon` when your\n * hostnames don't follow these conventions.\n *\n * @param hostname override the host to classify (defaults to `location.hostname`)\n */\nexport function defaultDetect(\n hostname: string = typeof location === 'undefined' ? '' : location.hostname,\n): string {\n const h = hostname.toLowerCase();\n if (\n h === 'localhost' ||\n h === '127.0.0.1' ||\n h === '::1' ||\n h === '[::1]' ||\n h.endsWith('.local') ||\n h.endsWith('.localhost') ||\n /^\\d{1,3}(?:\\.\\d{1,3}){3}$/.test(h)\n ) {\n return 'dev';\n }\n if (/(?:^|[.-])(?:staging|stg|qa|uat|preview|preprod|test|dev)(?:[.-]|$)/.test(h)) {\n return 'staging';\n }\n return 'prod';\n}\n","import type { EnvTint } from './types';\n\n/**\n * The CSS `filter` for a tint. An explicit `filter` wins outright; otherwise\n * `hue` and `invert` compose into one filter string (both applied, in that\n * order). `null` when none of them are set.\n */\nexport function cssFilter(tint: EnvTint): string | null {\n if (tint.filter) return tint.filter;\n const parts: string[] = [];\n if (typeof tint.hue === 'number') parts.push(`hue-rotate(${tint.hue}deg)`);\n // `invert: true` → invert(1); a number is used verbatim. `0` / `false` is a no-op.\n if (tint.invert) parts.push(`invert(${tint.invert === true ? 1 : tint.invert})`);\n return parts.length > 0 ? parts.join(' ') : null;\n}\n","/**\n * Deterministic hue (0–359) derived from a string via FNV-1a. Stable across\n * loads and well distributed, so distinct inputs (e.g. `location.host` values)\n * get distinct colours. Powers `envFavicon`'s auto mode.\n *\n * @param input the string to hash (e.g. `location.host`)\n * @param offset extra degrees added to the result, to shift the whole palette\n */\nexport function hashHue(input: string, offset = 0): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (((h >>> 0) % 360) + (offset % 360) + 360) % 360;\n}\n","/**\n * CSS `filter` functions re-expressed as colour matrices — the one form every\n * engine can apply.\n *\n * Two engine gaps make this necessary, and they are independent:\n * 1. WebKit/Safari does not implement `CanvasRenderingContext2D.filter` at all\n * (`'filter' in ctx` is `false`), so assigning it is a silent no-op and the\n * runtime path draws the icon untinted.\n * 2. WebKit also ignores a CSS `filter` set on an *inner* SVG element, so the\n * SSR path can't wrap the artwork in a filtered `<g>` either. A real SVG\n * `<filter>` element is honoured everywhere.\n *\n * Each matrix is 20 numbers in `feColorMatrix` row-major order (4 rows × 5\n * columns, the last column a constant), and a filter string becomes a *list* of\n * them applied in sequence — not one pre-multiplied matrix — because the Filter\n * Effects spec clamps every primitive's result to 0–1 before the next one runs,\n * and `hue-rotate` routinely overflows that range.\n */\n\n/** 4×5 colour matrix, row-major, in `feColorMatrix` `values` order. */\nexport type ColorMatrix = readonly number[];\n\n/** One output channel: the r/g/b coefficients plus a constant. */\ntype Row = readonly [number, number, number, number];\n\nconst rgbRow = (row: Row): number[] => [row[0], row[1], row[2], 0, row[3]];\n\n/**\n * Assemble the three colour rows into a full matrix. No colour function reads\n * the alpha channel, so that column is always 0; `alpha` scales alpha itself.\n */\nfunction matrix(r: Row, g: Row, b: Row, alpha = 1): ColorMatrix {\n return [...rgbRow(r), ...rgbRow(g), ...rgbRow(b), 0, 0, 0, alpha, 0];\n}\n\n// The luminance coefficients the Filter Effects spec uses for `saturate` and\n// `hue-rotate` (not the same set as `feColorMatrix type=\"luminanceToAlpha\"`).\nconst [LR, LG, LB] = [0.213, 0.715, 0.072];\n\nfunction saturate(s: number): ColorMatrix {\n return matrix(\n [LR + 0.787 * s, LG - LG * s, LB - LB * s, 0],\n [LR - LR * s, LG + 0.285 * s, LB - LB * s, 0],\n [LR - LR * s, LG - LG * s, LB + 0.928 * s, 0],\n );\n}\n\nfunction hueRotate(deg: number): ColorMatrix {\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n return matrix(\n [LR + c * 0.787 - s * 0.213, LG - c * 0.715 - s * 0.715, LB - c * 0.072 + s * 0.928, 0],\n [LR - c * 0.213 + s * 0.143, LG + c * 0.285 + s * 0.14, LB - c * 0.072 - s * 0.283, 0],\n [LR - c * 0.213 - s * 0.787, LG - c * 0.715 + s * 0.715, LB + c * 0.928 + s * 0.072, 0],\n );\n}\n\n/** `sepia(a)` — a linear blend from the identity (`a = 0`) to the spec's matrix. */\nfunction sepia(a: number): ColorMatrix {\n const k = 1 - a;\n return matrix(\n [k + 0.393 * a, 0.769 * a, 0.189 * a, 0],\n [0.349 * a, k + 0.686 * a, 0.168 * a, 0],\n [0.272 * a, 0.534 * a, k + 0.131 * a, 0],\n );\n}\n\n/** `invert(a)`: `c' = a + c * (1 - 2a)` — affine, so the constant column carries `a`. */\nfunction invert(a: number): ColorMatrix {\n const k = 1 - 2 * a;\n return matrix([k, 0, 0, a], [0, k, 0, a], [0, 0, k, a]);\n}\n\n/** A per-channel `slope`/`intercept` ramp — `brightness` and `contrast` both reduce to one. */\nfunction linear(slope: number, intercept: number): ColorMatrix {\n return matrix([slope, 0, 0, intercept], [0, slope, 0, intercept], [0, 0, slope, intercept]);\n}\n\nfunction opacity(o: number): ColorMatrix {\n return matrix([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], o);\n}\n\n/** Parse one filter-function argument. `%` is divided out; a bare number is used as-is. */\nfunction amount(raw: string, fallback: number): number | null {\n const text = raw.trim();\n if (text === '') return fallback;\n const m = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+))(%?)$/.exec(text);\n if (!m) return null;\n const n = Number(m[1]);\n return m[2] ? n / 100 : n;\n}\n\n/** Parse a `<angle>` for `hue-rotate` — `deg` (default), `rad`, `grad` or `turn`. */\nfunction angle(raw: string): number | null {\n const text = raw.trim();\n if (text === '') return 0;\n const m = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+))(deg|rad|grad|turn)?$/.exec(text);\n if (!m) return null;\n const n = Number(m[1]);\n if (m[2] === 'rad') return (n * 180) / Math.PI;\n if (m[2] === 'grad') return n * 0.9;\n if (m[2] === 'turn') return n * 360;\n return n;\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n));\n\n/**\n * Every CSS filter function that is a pure colour transform, with the argument\n * defaults and clamps CSS gives it. `blur()` and `drop-shadow()` are\n * deliberately absent — they move pixels around, so they can't be a matrix, and\n * a string containing one falls back to the CSS path.\n */\nconst FUNCTIONS: Record<string, (raw: string) => ColorMatrix | null> = {\n 'hue-rotate': (raw) => {\n const deg = angle(raw);\n return deg == null ? null : hueRotate(deg);\n },\n // grayscale(a) is exactly saturate(1 - a).\n grayscale: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : saturate(1 - clamp01(a));\n },\n sepia: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : sepia(clamp01(a));\n },\n saturate: (raw) => {\n const a = amount(raw, 1);\n return a == null || a < 0 ? null : saturate(a);\n },\n invert: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : invert(clamp01(a));\n },\n opacity: (raw) => {\n const a = amount(raw, 1);\n return a == null ? null : opacity(clamp01(a));\n },\n brightness: (raw) => {\n const a = amount(raw, 1);\n return a == null || a < 0 ? null : linear(a, 0);\n },\n contrast: (raw) => {\n const a = amount(raw, 1);\n return a == null || a < 0 ? null : linear(a, 0.5 - 0.5 * a);\n },\n};\n\nconst FUNCTION_RE = /([a-z-]+)\\(([^()]*)\\)/gi;\n\n/**\n * Turn a CSS `filter` string into the chain of colour matrices that computes it,\n * or `null` if any part of it isn't a colour transform (`blur`, `drop-shadow`,\n * `url(#…)`, a typo). `'none'` and an empty string yield an empty chain.\n *\n * Callers treat `null` as \"only a native implementation can do this\" — the\n * runtime path leaves the icon untouched, the SSR path emits a CSS `filter` as\n * it did before.\n */\nexport function filterMatrices(filter: string): ColorMatrix[] | null {\n const matrices: ColorMatrix[] = [];\n FUNCTION_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = FUNCTION_RE.exec(filter)) !== null) {\n const build = FUNCTIONS[m[1].toLowerCase()];\n if (!build) return null;\n const built = build(m[2]);\n if (!built) return null;\n matrices.push(built);\n }\n // Reject anything the scan didn't account for (bar whitespace), so a\n // half-understood string can't silently apply half a filter.\n const rest = filter.replace(FUNCTION_RE, '').trim();\n if (rest !== '' && rest !== 'none') return null;\n return matrices;\n}\n\n/**\n * Apply a matrix chain to raw `ImageData` bytes, in place. Values are\n * non-premultiplied on both sides (what `getImageData` hands back, and what\n * `feColorMatrix` is defined over), and `Uint8ClampedArray` supplies the\n * per-primitive clamp for free.\n */\nexport function applyMatrices(data: Uint8ClampedArray, matrices: readonly ColorMatrix[]): void {\n for (const m of matrices) {\n for (let i = 0; i < data.length; i += 4) {\n const r = data[i] / 255;\n const g = data[i + 1] / 255;\n const b = data[i + 2] / 255;\n const a = data[i + 3] / 255;\n data[i] = (m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4]) * 255;\n data[i + 1] = (m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9]) * 255;\n data[i + 2] = (m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14]) * 255;\n data[i + 3] = (m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19]) * 255;\n }\n }\n}\n","import { badgeText, DEFAULT_BADGE_COLOR, normalizeBadge, placeBadge } from './badge';\nimport { contrastColor } from './color';\nimport { defaultDetect } from './detect';\nimport { cssFilter } from './filter';\nimport { hashHue } from './hash';\nimport { applyMatrices, filterMatrices } from './matrix';\nimport type { Badge, EnvFaviconOptions, EnvRule, EnvTint, RuleBadge } from './types';\n\n/** Selector for the favicon `<link>`(s) we read from and replace. */\nconst ICON_LINK = 'link[rel~=\"icon\"]';\n\n/** Remembered original favicon href, so repeat calls don't tint an already-tinted icon. */\nlet originalSource: string | undefined;\n\nfunction currentIconHref(): string | undefined {\n const links = document.querySelectorAll<HTMLLinkElement>(ICON_LINK);\n const last = links.item(links.length - 1);\n return last?.href || undefined;\n}\n\n/** Fill `$1` / `$<name>` placeholders in a template from a regex match. */\nfunction interpolate(template: string, match: RegExpMatchArray): string {\n return template.replace(/\\$(?:(\\d+)|<([^>]+)>)/g, (_full, index, name) =>\n name == null ? (match[Number(index)] ?? '') : (match.groups?.[name] ?? ''),\n );\n}\n\nfunction resolveRuleBadge(\n badge: string | RuleBadge,\n match: RegExpMatchArray | null,\n url: URL,\n): Badge {\n if (typeof badge === 'string') return { color: badge };\n const { text, ...rest } = badge;\n let resolved: string | number | undefined;\n if (typeof text === 'function') resolved = text(match, url);\n else if (typeof text === 'string' && match) resolved = interpolate(text, match);\n else resolved = text;\n return { ...rest, text: resolved };\n}\n\nfunction ruleToTint(rule: EnvRule, match: RegExpMatchArray | null, url: URL): EnvTint {\n const tint: EnvTint = {};\n if (rule.hue != null) tint.hue = rule.hue;\n if (rule.filter) tint.filter = rule.filter;\n if (rule.src) tint.src = rule.src;\n if (rule.badge != null) tint.badge = resolveRuleBadge(rule.badge, match, url);\n return tint;\n}\n\n/**\n * Return the tint of the first `rule` whose `match` matches `url`, or `null` if\n * none do. A `RegExp` `match` is tested against `url.host` and its captures are\n * interpolated into `badge.text` (`$1`, `$<name>`); a function `match` receives\n * the `URL`. Exposed so the same rules can drive server-side rendering from a\n * request URL — pair the result with `favicon-env/ssr`'s `faviconDataUri`.\n */\nexport function matchRules(rules: EnvRule[], url: URL): EnvTint | null {\n for (const rule of rules) {\n if (typeof rule.match === 'function') {\n if (rule.match(url)) return ruleToTint(rule, null, url);\n } else {\n const m = url.host.match(rule.match);\n if (m) return ruleToTint(rule, m, url);\n }\n }\n return null;\n}\n\nfunction resolveTint(options: EnvFaviconOptions): EnvTint | null {\n const url = typeof location === 'undefined' ? null : new URL(location.href);\n if (options.rules && url) {\n const matched = matchRules(options.rules, url);\n if (matched) return matched;\n }\n if (options.auto) {\n const offset = typeof options.auto === 'object' ? (options.auto.offset ?? 0) : 0;\n return { hue: hashHue(url?.host ?? '', offset) };\n }\n const env = (options.detect ?? defaultDetect)();\n const tint = env ? options.environments?.[env] : undefined;\n return tint || null;\n}\n\nfunction inferType(src: string): string | undefined {\n if (src.startsWith('data:')) return /^data:([^;,]+)/.exec(src)?.[1];\n if (/\\.svg(?:[?#]|$)/i.test(src)) return 'image/svg+xml';\n if (/\\.png(?:[?#]|$)/i.test(src)) return 'image/png';\n if (/\\.ico(?:[?#]|$)/i.test(src)) return 'image/x-icon';\n return undefined;\n}\n\nfunction applyFavicon(href: string, type?: string): void {\n document.querySelectorAll(ICON_LINK).forEach((link) => link.remove());\n const link = document.createElement('link');\n link.rel = 'icon';\n if (type) link.type = type;\n link.href = href;\n link.dataset.faviconEnv = '';\n document.head.append(link);\n}\n\n/**\n * Draw `img` through `filter`. Chromium and Firefox get the native `ctx.filter`;\n * WebKit/Safari ships no such property (assigning it is a silent no-op, which is\n * why hue / invert / auto used to do nothing there), so the filter is replayed\n * as a colour-matrix pass over the drawn pixels instead. A filter no matrix can\n * express (`blur`, `drop-shadow`) is skipped on that path — the icon is drawn\n * untinted rather than half-filtered, and any badge still lands on top.\n */\nfunction drawFiltered(\n ctx: CanvasRenderingContext2D,\n img: CanvasImageSource,\n size: number,\n filter: string,\n): void {\n if (typeof ctx.filter === 'string') {\n ctx.filter = filter;\n ctx.drawImage(img, 0, 0, size, size);\n ctx.filter = 'none';\n return;\n }\n ctx.drawImage(img, 0, 0, size, size);\n const matrices = filterMatrices(filter);\n if (!matrices || matrices.length === 0) return;\n const image = ctx.getImageData(0, 0, size, size);\n applyMatrices(image.data, matrices);\n ctx.putImageData(image, 0, 0);\n}\n\nfunction contrastText(ctx: CanvasRenderingContext2D, background: string): string {\n ctx.fillStyle = background; // canvas normalises any CSS colour to #rrggbb / rgba(…)\n return contrastColor(ctx.fillStyle);\n}\n\nfunction traceRoundRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n): void {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + rr, y);\n ctx.arcTo(x + w, y, x + w, y + h, rr);\n ctx.arcTo(x + w, y + h, x, y + h, rr);\n ctx.arcTo(x, y + h, x, y, rr);\n ctx.arcTo(x, y, x + w, y, rr);\n ctx.closePath();\n}\n\nfunction drawBadge(ctx: CanvasRenderingContext2D, size: number, badge: Badge): void {\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const text = badgeText(badge);\n const family = 'system-ui, -apple-system, \"Segoe UI\", sans-serif';\n\n // `shape: 'cover'` fills the whole icon and centres the number as big as it fits.\n if (badge.shape === 'cover') {\n traceRoundRect(ctx, 0, 0, size, size, Math.round(size * 0.2));\n ctx.fillStyle = color;\n ctx.fill();\n if (text) {\n const maxWidth = size * 0.84;\n let fontSize = Math.round(size * 0.62);\n ctx.font = `700 ${fontSize}px ${family}`;\n const textWidth = ctx.measureText(text).width;\n if (textWidth > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / textWidth)));\n ctx.font = `700 ${fontSize}px ${family}`;\n }\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, size / 2, size / 2);\n }\n return;\n }\n\n const corner = badge.corner ?? 'bottom-right';\n const h = Math.round(size * (badge.size ?? 0.5));\n const margin = Math.round(size * 0.02);\n const maxWidth = size - margin * 2;\n const pad = Math.round(h * 0.5);\n // A dot / short label sits in a pill that widens (then the font shrinks) to fit\n // longer text. Bump badge.size + corner:'center' to make a number dominate.\n let fontSize = Math.round(h * 0.66);\n ctx.font = `700 ${fontSize}px ${family}`;\n let w = text ? Math.max(h, Math.ceil(ctx.measureText(text).width) + pad) : h;\n if (w > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / w)));\n ctx.font = `700 ${fontSize}px ${family}`;\n w = Math.min(maxWidth, Math.max(h, Math.ceil(ctx.measureText(text).width) + pad));\n }\n const [px, py] = placeBadge(corner, size, size, w, h, margin);\n const x = Math.round(px);\n const y = Math.round(py);\n const radius = text ? Math.min(h / 2, size * 0.24) : h / 2;\n\n traceRoundRect(ctx, x, y, w, h, radius);\n ctx.fillStyle = color;\n ctx.fill();\n ctx.lineWidth = Math.max(1, size * 0.03);\n ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';\n ctx.stroke();\n\n if (text) {\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, x + w / 2, y + h / 2);\n }\n}\n\n/**\n * Tint / decorate the page favicon for the current environment. Resolves a tint\n * from `rules` (URL match) → `auto` → `environments`/`detect`, then reads the\n * existing `<link rel=\"icon\">` (or a per-env `src` / `options.source`), redraws\n * it on a `<canvas>` with the hue / filter / badge, and swaps in the result as a\n * PNG data URL.\n *\n * A no-op during SSR (no `document`) or when nothing resolves. Works with any\n * favicon format (svg / png / ico). A custom `src` with no recolour or badge is\n * swapped in directly, skipping the canvas — which avoids cross-origin taint and\n * keeps vector sources sharp. If a canvas source is cross-origin without CORS\n * headers the canvas is tainted and the favicon is left untouched.\n *\n * @returns a promise that resolves once the swap has been attempted.\n */\nexport function envFavicon(options: EnvFaviconOptions = {}): Promise<void> {\n if (typeof document === 'undefined' || typeof HTMLCanvasElement === 'undefined') {\n return Promise.resolve();\n }\n const tint = resolveTint(options);\n if (!tint) return Promise.resolve();\n\n const badge = tint.badge != null ? normalizeBadge(tint.badge) : undefined;\n const size = options.size ?? 64;\n const cover = badge?.shape === 'cover';\n const alpha = badge?.opacity ?? 1;\n\n // An opaque `cover` replaces the icon entirely — no base image needed, so draw\n // it synchronously. A translucent cover falls through to composite over the base.\n if (cover && alpha >= 1) {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n drawBadge(ctx, size, badge);\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n return Promise.resolve();\n }\n\n // Plain image swap: a custom `src` with nothing to composite skips the canvas.\n const needsCanvas =\n tint.hue != null ||\n Boolean(tint.invert) ||\n Boolean(tint.filter) ||\n Boolean(tint.tint) ||\n Boolean(badge);\n if (!needsCanvas) {\n if (tint.src) applyFavicon(tint.src, inferType(tint.src));\n return Promise.resolve();\n }\n\n originalSource ??= currentIconHref();\n const source = tint.src ?? options.source ?? originalSource ?? '/favicon.ico';\n\n return new Promise<void>((resolve) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.decoding = 'async';\n img.addEventListener('error', () => resolve());\n img.addEventListener('load', () => {\n try {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n // `cover` replaces the icon, so it ignores any recolour. Otherwise:\n // `tint` colourises the base to an exact colour (grayscale → multiply →\n // re-mask the original alpha), else a `hue`/`invert`/`filter` is one CSS\n // filter. Both go through `drawFiltered`, which is what keeps them\n // working in WebKit. The base is always drawn — a translucent cover\n // shows through.\n const colorize = !cover && !tint.filter ? tint.tint : undefined;\n if (colorize) {\n drawFiltered(ctx, img, size, 'grayscale(1)');\n ctx.globalCompositeOperation = 'multiply';\n ctx.fillStyle = colorize;\n ctx.fillRect(0, 0, size, size);\n ctx.globalCompositeOperation = 'destination-in';\n ctx.drawImage(img, 0, 0, size, size);\n ctx.globalCompositeOperation = 'source-over';\n } else {\n const filter = cover ? null : cssFilter(tint);\n if (filter) drawFiltered(ctx, img, size, filter);\n else ctx.drawImage(img, 0, 0, size, size);\n }\n if (badge) {\n ctx.globalAlpha = alpha;\n drawBadge(ctx, size, badge);\n ctx.globalAlpha = 1;\n }\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n } catch {\n // Tainted canvas (cross-origin source without CORS headers) or an\n // unsupported API — leave the existing favicon in place.\n }\n resolve();\n });\n img.src = source;\n });\n}\n"],"mappings":"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","names":["global_exports","__export","envFavicon","DEFAULT_BADGE_COLOR","normalizeBadge","badge","badgeText","placeBadge","corner","cw","ch","bw","bh","margin","parseRgb","color","hex","h","c","n","rgb","lightness","lch","l","contrastColor","defaultDetect","hostname","h","cssFilter","tint","parts","hashHue","input","offset","h","i","rgbRow","row","matrix","r","g","b","alpha","LR","LG","LB","saturate","s","hueRotate","deg","rad","c","sepia","a","k","invert","linear","slope","intercept","opacity","o","amount","raw","fallback","text","m","n","angle","clamp01","FUNCTIONS","FUNCTION_RE","filterMatrices","filter","matrices","build","built","rest","applyMatrices","data","i","ICON_LINK","originalSource","currentIconHref","links","interpolate","template","match","_full","index","name","resolveRuleBadge","badge","url","text","rest","resolved","ruleToTint","rule","tint","matchRules","rules","m","resolveTint","options","matched","offset","hashHue","env","defaultDetect","inferType","src","applyFavicon","href","type","link","drawFiltered","ctx","img","size","filter","matrices","filterMatrices","image","applyMatrices","contrastText","background","contrastColor","traceRoundRect","x","y","w","h","r","rr","drawBadge","color","DEFAULT_BADGE_COLOR","badgeText","family","maxWidth","fontSize","textWidth","corner","margin","pad","px","py","placeBadge","radius","envFavicon","normalizeBadge","cover","alpha","canvas","source","resolve","colorize","cssFilter","boot","el","data","envFavicon","environments","name","value","hue"]}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeBadge, cssFilter, DEFAULT_BADGE_COLOR, badgeText, placeBadge, contrastColor } from './chunk-OA3YX73Z.js';
2
- export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-OA3YX73Z.js';
1
+ import { normalizeBadge, cssFilter, DEFAULT_BADGE_COLOR, badgeText, placeBadge, filterMatrices, applyMatrices, contrastColor } from './chunk-T7D7UPKB.js';
2
+ export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-T7D7UPKB.js';
3
3
 
4
4
  // src/detect.ts
5
5
  function defaultDetect(hostname = typeof location === "undefined" ? "" : location.hostname) {
@@ -95,6 +95,20 @@ function applyFavicon(href, type) {
95
95
  link.dataset.faviconEnv = "";
96
96
  document.head.append(link);
97
97
  }
98
+ function drawFiltered(ctx, img, size, filter) {
99
+ if (typeof ctx.filter === "string") {
100
+ ctx.filter = filter;
101
+ ctx.drawImage(img, 0, 0, size, size);
102
+ ctx.filter = "none";
103
+ return;
104
+ }
105
+ ctx.drawImage(img, 0, 0, size, size);
106
+ const matrices = filterMatrices(filter);
107
+ if (!matrices || matrices.length === 0) return;
108
+ const image = ctx.getImageData(0, 0, size, size);
109
+ applyMatrices(image.data, matrices);
110
+ ctx.putImageData(image, 0, 0);
111
+ }
98
112
  function contrastText(ctx, background) {
99
113
  ctx.fillStyle = background;
100
114
  return contrastColor(ctx.fillStyle);
@@ -205,9 +219,7 @@ function envFavicon(options = {}) {
205
219
  if (ctx) {
206
220
  const colorize = !cover && !tint.filter ? tint.tint : void 0;
207
221
  if (colorize) {
208
- ctx.filter = "grayscale(1)";
209
- ctx.drawImage(img, 0, 0, size, size);
210
- ctx.filter = "none";
222
+ drawFiltered(ctx, img, size, "grayscale(1)");
211
223
  ctx.globalCompositeOperation = "multiply";
212
224
  ctx.fillStyle = colorize;
213
225
  ctx.fillRect(0, 0, size, size);
@@ -215,12 +227,9 @@ function envFavicon(options = {}) {
215
227
  ctx.drawImage(img, 0, 0, size, size);
216
228
  ctx.globalCompositeOperation = "source-over";
217
229
  } else {
218
- if (!cover) {
219
- const filter = cssFilter(tint);
220
- if (filter) ctx.filter = filter;
221
- }
222
- ctx.drawImage(img, 0, 0, size, size);
223
- ctx.filter = "none";
230
+ const filter = cover ? null : cssFilter(tint);
231
+ if (filter) drawFiltered(ctx, img, size, filter);
232
+ else ctx.drawImage(img, 0, 0, size, size);
224
233
  }
225
234
  if (badge) {
226
235
  ctx.globalAlpha = alpha;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/detect.ts","../src/hash.ts","../src/tint.ts"],"names":["link","maxWidth","fontSize"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Default environment heuristic, based on `location.hostname`:\n *\n * - `localhost`, `127.0.0.1`, `::1`, `*.local`, `*.localhost`, a raw IPv4 → `'dev'`\n * - a `staging` / `stg` / `qa` / `uat` / `preview` / `preprod` / `test` / `dev`\n * segment anywhere in the host → `'staging'`\n * - anything else → `'prod'`\n *\n * It's intentionally simple. Pass your own `detect` to `envFavicon` when your\n * hostnames don't follow these conventions.\n *\n * @param hostname override the host to classify (defaults to `location.hostname`)\n */\nexport function defaultDetect(\n hostname: string = typeof location === 'undefined' ? '' : location.hostname,\n): string {\n const h = hostname.toLowerCase();\n if (\n h === 'localhost' ||\n h === '127.0.0.1' ||\n h === '::1' ||\n h === '[::1]' ||\n h.endsWith('.local') ||\n h.endsWith('.localhost') ||\n /^\\d{1,3}(?:\\.\\d{1,3}){3}$/.test(h)\n ) {\n return 'dev';\n }\n if (/(?:^|[.-])(?:staging|stg|qa|uat|preview|preprod|test|dev)(?:[.-]|$)/.test(h)) {\n return 'staging';\n }\n return 'prod';\n}\n","/**\n * Deterministic hue (0–359) derived from a string via FNV-1a. Stable across\n * loads and well distributed, so distinct inputs (e.g. `location.host` values)\n * get distinct colours. Powers `envFavicon`'s auto mode.\n *\n * @param input the string to hash (e.g. `location.host`)\n * @param offset extra degrees added to the result, to shift the whole palette\n */\nexport function hashHue(input: string, offset = 0): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (((h >>> 0) % 360) + (offset % 360) + 360) % 360;\n}\n","import { badgeText, DEFAULT_BADGE_COLOR, normalizeBadge, placeBadge } from './badge';\nimport { contrastColor } from './color';\nimport { defaultDetect } from './detect';\nimport { cssFilter } from './filter';\nimport { hashHue } from './hash';\nimport type { Badge, EnvFaviconOptions, EnvRule, EnvTint, RuleBadge } from './types';\n\n/** Selector for the favicon `<link>`(s) we read from and replace. */\nconst ICON_LINK = 'link[rel~=\"icon\"]';\n\n/** Remembered original favicon href, so repeat calls don't tint an already-tinted icon. */\nlet originalSource: string | undefined;\n\nfunction currentIconHref(): string | undefined {\n const links = document.querySelectorAll<HTMLLinkElement>(ICON_LINK);\n const last = links.item(links.length - 1);\n return last?.href || undefined;\n}\n\n/** Fill `$1` / `$<name>` placeholders in a template from a regex match. */\nfunction interpolate(template: string, match: RegExpMatchArray): string {\n return template.replace(/\\$(?:(\\d+)|<([^>]+)>)/g, (_full, index, name) =>\n name == null ? (match[Number(index)] ?? '') : (match.groups?.[name] ?? ''),\n );\n}\n\nfunction resolveRuleBadge(\n badge: string | RuleBadge,\n match: RegExpMatchArray | null,\n url: URL,\n): Badge {\n if (typeof badge === 'string') return { color: badge };\n const { text, ...rest } = badge;\n let resolved: string | number | undefined;\n if (typeof text === 'function') resolved = text(match, url);\n else if (typeof text === 'string' && match) resolved = interpolate(text, match);\n else resolved = text;\n return { ...rest, text: resolved };\n}\n\nfunction ruleToTint(rule: EnvRule, match: RegExpMatchArray | null, url: URL): EnvTint {\n const tint: EnvTint = {};\n if (rule.hue != null) tint.hue = rule.hue;\n if (rule.filter) tint.filter = rule.filter;\n if (rule.src) tint.src = rule.src;\n if (rule.badge != null) tint.badge = resolveRuleBadge(rule.badge, match, url);\n return tint;\n}\n\n/**\n * Return the tint of the first `rule` whose `match` matches `url`, or `null` if\n * none do. A `RegExp` `match` is tested against `url.host` and its captures are\n * interpolated into `badge.text` (`$1`, `$<name>`); a function `match` receives\n * the `URL`. Exposed so the same rules can drive server-side rendering from a\n * request URL — pair the result with `favicon-env/ssr`'s `faviconDataUri`.\n */\nexport function matchRules(rules: EnvRule[], url: URL): EnvTint | null {\n for (const rule of rules) {\n if (typeof rule.match === 'function') {\n if (rule.match(url)) return ruleToTint(rule, null, url);\n } else {\n const m = url.host.match(rule.match);\n if (m) return ruleToTint(rule, m, url);\n }\n }\n return null;\n}\n\nfunction resolveTint(options: EnvFaviconOptions): EnvTint | null {\n const url = typeof location === 'undefined' ? null : new URL(location.href);\n if (options.rules && url) {\n const matched = matchRules(options.rules, url);\n if (matched) return matched;\n }\n if (options.auto) {\n const offset = typeof options.auto === 'object' ? (options.auto.offset ?? 0) : 0;\n return { hue: hashHue(url?.host ?? '', offset) };\n }\n const env = (options.detect ?? defaultDetect)();\n const tint = env ? options.environments?.[env] : undefined;\n return tint || null;\n}\n\nfunction inferType(src: string): string | undefined {\n if (src.startsWith('data:')) return /^data:([^;,]+)/.exec(src)?.[1];\n if (/\\.svg(?:[?#]|$)/i.test(src)) return 'image/svg+xml';\n if (/\\.png(?:[?#]|$)/i.test(src)) return 'image/png';\n if (/\\.ico(?:[?#]|$)/i.test(src)) return 'image/x-icon';\n return undefined;\n}\n\nfunction applyFavicon(href: string, type?: string): void {\n document.querySelectorAll(ICON_LINK).forEach((link) => link.remove());\n const link = document.createElement('link');\n link.rel = 'icon';\n if (type) link.type = type;\n link.href = href;\n link.dataset.faviconEnv = '';\n document.head.append(link);\n}\n\nfunction contrastText(ctx: CanvasRenderingContext2D, background: string): string {\n ctx.fillStyle = background; // canvas normalises any CSS colour to #rrggbb / rgba(…)\n return contrastColor(ctx.fillStyle);\n}\n\nfunction traceRoundRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n): void {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + rr, y);\n ctx.arcTo(x + w, y, x + w, y + h, rr);\n ctx.arcTo(x + w, y + h, x, y + h, rr);\n ctx.arcTo(x, y + h, x, y, rr);\n ctx.arcTo(x, y, x + w, y, rr);\n ctx.closePath();\n}\n\nfunction drawBadge(ctx: CanvasRenderingContext2D, size: number, badge: Badge): void {\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const text = badgeText(badge);\n const family = 'system-ui, -apple-system, \"Segoe UI\", sans-serif';\n\n // `shape: 'cover'` fills the whole icon and centres the number as big as it fits.\n if (badge.shape === 'cover') {\n traceRoundRect(ctx, 0, 0, size, size, Math.round(size * 0.2));\n ctx.fillStyle = color;\n ctx.fill();\n if (text) {\n const maxWidth = size * 0.84;\n let fontSize = Math.round(size * 0.62);\n ctx.font = `700 ${fontSize}px ${family}`;\n const textWidth = ctx.measureText(text).width;\n if (textWidth > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / textWidth)));\n ctx.font = `700 ${fontSize}px ${family}`;\n }\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, size / 2, size / 2);\n }\n return;\n }\n\n const corner = badge.corner ?? 'bottom-right';\n const h = Math.round(size * (badge.size ?? 0.5));\n const margin = Math.round(size * 0.02);\n const maxWidth = size - margin * 2;\n const pad = Math.round(h * 0.5);\n // A dot / short label sits in a pill that widens (then the font shrinks) to fit\n // longer text. Bump badge.size + corner:'center' to make a number dominate.\n let fontSize = Math.round(h * 0.66);\n ctx.font = `700 ${fontSize}px ${family}`;\n let w = text ? Math.max(h, Math.ceil(ctx.measureText(text).width) + pad) : h;\n if (w > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / w)));\n ctx.font = `700 ${fontSize}px ${family}`;\n w = Math.min(maxWidth, Math.max(h, Math.ceil(ctx.measureText(text).width) + pad));\n }\n const [px, py] = placeBadge(corner, size, size, w, h, margin);\n const x = Math.round(px);\n const y = Math.round(py);\n const radius = text ? Math.min(h / 2, size * 0.24) : h / 2;\n\n traceRoundRect(ctx, x, y, w, h, radius);\n ctx.fillStyle = color;\n ctx.fill();\n ctx.lineWidth = Math.max(1, size * 0.03);\n ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';\n ctx.stroke();\n\n if (text) {\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, x + w / 2, y + h / 2);\n }\n}\n\n/**\n * Tint / decorate the page favicon for the current environment. Resolves a tint\n * from `rules` (URL match) → `auto` → `environments`/`detect`, then reads the\n * existing `<link rel=\"icon\">` (or a per-env `src` / `options.source`), redraws\n * it on a `<canvas>` with the hue / filter / badge, and swaps in the result as a\n * PNG data URL.\n *\n * A no-op during SSR (no `document`) or when nothing resolves. Works with any\n * favicon format (svg / png / ico). A custom `src` with no recolour or badge is\n * swapped in directly, skipping the canvas — which avoids cross-origin taint and\n * keeps vector sources sharp. If a canvas source is cross-origin without CORS\n * headers the canvas is tainted and the favicon is left untouched.\n *\n * @returns a promise that resolves once the swap has been attempted.\n */\nexport function envFavicon(options: EnvFaviconOptions = {}): Promise<void> {\n if (typeof document === 'undefined' || typeof HTMLCanvasElement === 'undefined') {\n return Promise.resolve();\n }\n const tint = resolveTint(options);\n if (!tint) return Promise.resolve();\n\n const badge = tint.badge != null ? normalizeBadge(tint.badge) : undefined;\n const size = options.size ?? 64;\n const cover = badge?.shape === 'cover';\n const alpha = badge?.opacity ?? 1;\n\n // An opaque `cover` replaces the icon entirely — no base image needed, so draw\n // it synchronously. A translucent cover falls through to composite over the base.\n if (cover && alpha >= 1) {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n drawBadge(ctx, size, badge);\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n return Promise.resolve();\n }\n\n // Plain image swap: a custom `src` with nothing to composite skips the canvas.\n const needsCanvas =\n tint.hue != null ||\n Boolean(tint.invert) ||\n Boolean(tint.filter) ||\n Boolean(tint.tint) ||\n Boolean(badge);\n if (!needsCanvas) {\n if (tint.src) applyFavicon(tint.src, inferType(tint.src));\n return Promise.resolve();\n }\n\n originalSource ??= currentIconHref();\n const source = tint.src ?? options.source ?? originalSource ?? '/favicon.ico';\n\n return new Promise<void>((resolve) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.decoding = 'async';\n img.addEventListener('error', () => resolve());\n img.addEventListener('load', () => {\n try {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n // `cover` replaces the icon, so it ignores any recolour. Otherwise:\n // `tint` colourises the base to an exact colour (grayscale → multiply →\n // re-mask the original alpha), else a `hue`/`invert`/`filter` is a plain\n // CSS filter. The base is always drawn — a translucent cover shows through.\n const colorize = !cover && !tint.filter ? tint.tint : undefined;\n if (colorize) {\n ctx.filter = 'grayscale(1)';\n ctx.drawImage(img, 0, 0, size, size);\n ctx.filter = 'none';\n ctx.globalCompositeOperation = 'multiply';\n ctx.fillStyle = colorize;\n ctx.fillRect(0, 0, size, size);\n ctx.globalCompositeOperation = 'destination-in';\n ctx.drawImage(img, 0, 0, size, size);\n ctx.globalCompositeOperation = 'source-over';\n } else {\n if (!cover) {\n const filter = cssFilter(tint);\n if (filter) ctx.filter = filter;\n }\n ctx.drawImage(img, 0, 0, size, size);\n ctx.filter = 'none';\n }\n if (badge) {\n ctx.globalAlpha = alpha;\n drawBadge(ctx, size, badge);\n ctx.globalAlpha = 1;\n }\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n } catch {\n // Tainted canvas (cross-origin source without CORS headers) or an\n // unsupported API — leave the existing favicon in place.\n }\n resolve();\n });\n img.src = source;\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/detect.ts","../src/hash.ts","../src/tint.ts"],"names":["link","maxWidth","fontSize"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Default environment heuristic, based on `location.hostname`:\n *\n * - `localhost`, `127.0.0.1`, `::1`, `*.local`, `*.localhost`, a raw IPv4 → `'dev'`\n * - a `staging` / `stg` / `qa` / `uat` / `preview` / `preprod` / `test` / `dev`\n * segment anywhere in the host → `'staging'`\n * - anything else → `'prod'`\n *\n * It's intentionally simple. Pass your own `detect` to `envFavicon` when your\n * hostnames don't follow these conventions.\n *\n * @param hostname override the host to classify (defaults to `location.hostname`)\n */\nexport function defaultDetect(\n hostname: string = typeof location === 'undefined' ? '' : location.hostname,\n): string {\n const h = hostname.toLowerCase();\n if (\n h === 'localhost' ||\n h === '127.0.0.1' ||\n h === '::1' ||\n h === '[::1]' ||\n h.endsWith('.local') ||\n h.endsWith('.localhost') ||\n /^\\d{1,3}(?:\\.\\d{1,3}){3}$/.test(h)\n ) {\n return 'dev';\n }\n if (/(?:^|[.-])(?:staging|stg|qa|uat|preview|preprod|test|dev)(?:[.-]|$)/.test(h)) {\n return 'staging';\n }\n return 'prod';\n}\n","/**\n * Deterministic hue (0–359) derived from a string via FNV-1a. Stable across\n * loads and well distributed, so distinct inputs (e.g. `location.host` values)\n * get distinct colours. Powers `envFavicon`'s auto mode.\n *\n * @param input the string to hash (e.g. `location.host`)\n * @param offset extra degrees added to the result, to shift the whole palette\n */\nexport function hashHue(input: string, offset = 0): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (((h >>> 0) % 360) + (offset % 360) + 360) % 360;\n}\n","import { badgeText, DEFAULT_BADGE_COLOR, normalizeBadge, placeBadge } from './badge';\nimport { contrastColor } from './color';\nimport { defaultDetect } from './detect';\nimport { cssFilter } from './filter';\nimport { hashHue } from './hash';\nimport { applyMatrices, filterMatrices } from './matrix';\nimport type { Badge, EnvFaviconOptions, EnvRule, EnvTint, RuleBadge } from './types';\n\n/** Selector for the favicon `<link>`(s) we read from and replace. */\nconst ICON_LINK = 'link[rel~=\"icon\"]';\n\n/** Remembered original favicon href, so repeat calls don't tint an already-tinted icon. */\nlet originalSource: string | undefined;\n\nfunction currentIconHref(): string | undefined {\n const links = document.querySelectorAll<HTMLLinkElement>(ICON_LINK);\n const last = links.item(links.length - 1);\n return last?.href || undefined;\n}\n\n/** Fill `$1` / `$<name>` placeholders in a template from a regex match. */\nfunction interpolate(template: string, match: RegExpMatchArray): string {\n return template.replace(/\\$(?:(\\d+)|<([^>]+)>)/g, (_full, index, name) =>\n name == null ? (match[Number(index)] ?? '') : (match.groups?.[name] ?? ''),\n );\n}\n\nfunction resolveRuleBadge(\n badge: string | RuleBadge,\n match: RegExpMatchArray | null,\n url: URL,\n): Badge {\n if (typeof badge === 'string') return { color: badge };\n const { text, ...rest } = badge;\n let resolved: string | number | undefined;\n if (typeof text === 'function') resolved = text(match, url);\n else if (typeof text === 'string' && match) resolved = interpolate(text, match);\n else resolved = text;\n return { ...rest, text: resolved };\n}\n\nfunction ruleToTint(rule: EnvRule, match: RegExpMatchArray | null, url: URL): EnvTint {\n const tint: EnvTint = {};\n if (rule.hue != null) tint.hue = rule.hue;\n if (rule.filter) tint.filter = rule.filter;\n if (rule.src) tint.src = rule.src;\n if (rule.badge != null) tint.badge = resolveRuleBadge(rule.badge, match, url);\n return tint;\n}\n\n/**\n * Return the tint of the first `rule` whose `match` matches `url`, or `null` if\n * none do. A `RegExp` `match` is tested against `url.host` and its captures are\n * interpolated into `badge.text` (`$1`, `$<name>`); a function `match` receives\n * the `URL`. Exposed so the same rules can drive server-side rendering from a\n * request URL — pair the result with `favicon-env/ssr`'s `faviconDataUri`.\n */\nexport function matchRules(rules: EnvRule[], url: URL): EnvTint | null {\n for (const rule of rules) {\n if (typeof rule.match === 'function') {\n if (rule.match(url)) return ruleToTint(rule, null, url);\n } else {\n const m = url.host.match(rule.match);\n if (m) return ruleToTint(rule, m, url);\n }\n }\n return null;\n}\n\nfunction resolveTint(options: EnvFaviconOptions): EnvTint | null {\n const url = typeof location === 'undefined' ? null : new URL(location.href);\n if (options.rules && url) {\n const matched = matchRules(options.rules, url);\n if (matched) return matched;\n }\n if (options.auto) {\n const offset = typeof options.auto === 'object' ? (options.auto.offset ?? 0) : 0;\n return { hue: hashHue(url?.host ?? '', offset) };\n }\n const env = (options.detect ?? defaultDetect)();\n const tint = env ? options.environments?.[env] : undefined;\n return tint || null;\n}\n\nfunction inferType(src: string): string | undefined {\n if (src.startsWith('data:')) return /^data:([^;,]+)/.exec(src)?.[1];\n if (/\\.svg(?:[?#]|$)/i.test(src)) return 'image/svg+xml';\n if (/\\.png(?:[?#]|$)/i.test(src)) return 'image/png';\n if (/\\.ico(?:[?#]|$)/i.test(src)) return 'image/x-icon';\n return undefined;\n}\n\nfunction applyFavicon(href: string, type?: string): void {\n document.querySelectorAll(ICON_LINK).forEach((link) => link.remove());\n const link = document.createElement('link');\n link.rel = 'icon';\n if (type) link.type = type;\n link.href = href;\n link.dataset.faviconEnv = '';\n document.head.append(link);\n}\n\n/**\n * Draw `img` through `filter`. Chromium and Firefox get the native `ctx.filter`;\n * WebKit/Safari ships no such property (assigning it is a silent no-op, which is\n * why hue / invert / auto used to do nothing there), so the filter is replayed\n * as a colour-matrix pass over the drawn pixels instead. A filter no matrix can\n * express (`blur`, `drop-shadow`) is skipped on that path — the icon is drawn\n * untinted rather than half-filtered, and any badge still lands on top.\n */\nfunction drawFiltered(\n ctx: CanvasRenderingContext2D,\n img: CanvasImageSource,\n size: number,\n filter: string,\n): void {\n if (typeof ctx.filter === 'string') {\n ctx.filter = filter;\n ctx.drawImage(img, 0, 0, size, size);\n ctx.filter = 'none';\n return;\n }\n ctx.drawImage(img, 0, 0, size, size);\n const matrices = filterMatrices(filter);\n if (!matrices || matrices.length === 0) return;\n const image = ctx.getImageData(0, 0, size, size);\n applyMatrices(image.data, matrices);\n ctx.putImageData(image, 0, 0);\n}\n\nfunction contrastText(ctx: CanvasRenderingContext2D, background: string): string {\n ctx.fillStyle = background; // canvas normalises any CSS colour to #rrggbb / rgba(…)\n return contrastColor(ctx.fillStyle);\n}\n\nfunction traceRoundRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n): void {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + rr, y);\n ctx.arcTo(x + w, y, x + w, y + h, rr);\n ctx.arcTo(x + w, y + h, x, y + h, rr);\n ctx.arcTo(x, y + h, x, y, rr);\n ctx.arcTo(x, y, x + w, y, rr);\n ctx.closePath();\n}\n\nfunction drawBadge(ctx: CanvasRenderingContext2D, size: number, badge: Badge): void {\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const text = badgeText(badge);\n const family = 'system-ui, -apple-system, \"Segoe UI\", sans-serif';\n\n // `shape: 'cover'` fills the whole icon and centres the number as big as it fits.\n if (badge.shape === 'cover') {\n traceRoundRect(ctx, 0, 0, size, size, Math.round(size * 0.2));\n ctx.fillStyle = color;\n ctx.fill();\n if (text) {\n const maxWidth = size * 0.84;\n let fontSize = Math.round(size * 0.62);\n ctx.font = `700 ${fontSize}px ${family}`;\n const textWidth = ctx.measureText(text).width;\n if (textWidth > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / textWidth)));\n ctx.font = `700 ${fontSize}px ${family}`;\n }\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, size / 2, size / 2);\n }\n return;\n }\n\n const corner = badge.corner ?? 'bottom-right';\n const h = Math.round(size * (badge.size ?? 0.5));\n const margin = Math.round(size * 0.02);\n const maxWidth = size - margin * 2;\n const pad = Math.round(h * 0.5);\n // A dot / short label sits in a pill that widens (then the font shrinks) to fit\n // longer text. Bump badge.size + corner:'center' to make a number dominate.\n let fontSize = Math.round(h * 0.66);\n ctx.font = `700 ${fontSize}px ${family}`;\n let w = text ? Math.max(h, Math.ceil(ctx.measureText(text).width) + pad) : h;\n if (w > maxWidth) {\n fontSize = Math.max(7, Math.floor(fontSize * (maxWidth / w)));\n ctx.font = `700 ${fontSize}px ${family}`;\n w = Math.min(maxWidth, Math.max(h, Math.ceil(ctx.measureText(text).width) + pad));\n }\n const [px, py] = placeBadge(corner, size, size, w, h, margin);\n const x = Math.round(px);\n const y = Math.round(py);\n const radius = text ? Math.min(h / 2, size * 0.24) : h / 2;\n\n traceRoundRect(ctx, x, y, w, h, radius);\n ctx.fillStyle = color;\n ctx.fill();\n ctx.lineWidth = Math.max(1, size * 0.03);\n ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';\n ctx.stroke();\n\n if (text) {\n ctx.fillStyle = badge.textColor ?? contrastText(ctx, color);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, x + w / 2, y + h / 2);\n }\n}\n\n/**\n * Tint / decorate the page favicon for the current environment. Resolves a tint\n * from `rules` (URL match) → `auto` → `environments`/`detect`, then reads the\n * existing `<link rel=\"icon\">` (or a per-env `src` / `options.source`), redraws\n * it on a `<canvas>` with the hue / filter / badge, and swaps in the result as a\n * PNG data URL.\n *\n * A no-op during SSR (no `document`) or when nothing resolves. Works with any\n * favicon format (svg / png / ico). A custom `src` with no recolour or badge is\n * swapped in directly, skipping the canvas — which avoids cross-origin taint and\n * keeps vector sources sharp. If a canvas source is cross-origin without CORS\n * headers the canvas is tainted and the favicon is left untouched.\n *\n * @returns a promise that resolves once the swap has been attempted.\n */\nexport function envFavicon(options: EnvFaviconOptions = {}): Promise<void> {\n if (typeof document === 'undefined' || typeof HTMLCanvasElement === 'undefined') {\n return Promise.resolve();\n }\n const tint = resolveTint(options);\n if (!tint) return Promise.resolve();\n\n const badge = tint.badge != null ? normalizeBadge(tint.badge) : undefined;\n const size = options.size ?? 64;\n const cover = badge?.shape === 'cover';\n const alpha = badge?.opacity ?? 1;\n\n // An opaque `cover` replaces the icon entirely — no base image needed, so draw\n // it synchronously. A translucent cover falls through to composite over the base.\n if (cover && alpha >= 1) {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n drawBadge(ctx, size, badge);\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n return Promise.resolve();\n }\n\n // Plain image swap: a custom `src` with nothing to composite skips the canvas.\n const needsCanvas =\n tint.hue != null ||\n Boolean(tint.invert) ||\n Boolean(tint.filter) ||\n Boolean(tint.tint) ||\n Boolean(badge);\n if (!needsCanvas) {\n if (tint.src) applyFavicon(tint.src, inferType(tint.src));\n return Promise.resolve();\n }\n\n originalSource ??= currentIconHref();\n const source = tint.src ?? options.source ?? originalSource ?? '/favicon.ico';\n\n return new Promise<void>((resolve) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.decoding = 'async';\n img.addEventListener('error', () => resolve());\n img.addEventListener('load', () => {\n try {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n // `cover` replaces the icon, so it ignores any recolour. Otherwise:\n // `tint` colourises the base to an exact colour (grayscale → multiply →\n // re-mask the original alpha), else a `hue`/`invert`/`filter` is one CSS\n // filter. Both go through `drawFiltered`, which is what keeps them\n // working in WebKit. The base is always drawn — a translucent cover\n // shows through.\n const colorize = !cover && !tint.filter ? tint.tint : undefined;\n if (colorize) {\n drawFiltered(ctx, img, size, 'grayscale(1)');\n ctx.globalCompositeOperation = 'multiply';\n ctx.fillStyle = colorize;\n ctx.fillRect(0, 0, size, size);\n ctx.globalCompositeOperation = 'destination-in';\n ctx.drawImage(img, 0, 0, size, size);\n ctx.globalCompositeOperation = 'source-over';\n } else {\n const filter = cover ? null : cssFilter(tint);\n if (filter) drawFiltered(ctx, img, size, filter);\n else ctx.drawImage(img, 0, 0, size, size);\n }\n if (badge) {\n ctx.globalAlpha = alpha;\n drawBadge(ctx, size, badge);\n ctx.globalAlpha = 1;\n }\n applyFavicon(canvas.toDataURL('image/png'), 'image/png');\n }\n } catch {\n // Tainted canvas (cross-origin source without CORS headers) or an\n // unsupported API — leave the existing favicon in place.\n }\n resolve();\n });\n img.src = source;\n });\n}\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * CSS `filter` functions re-expressed as colour matrices — the one form every
3
+ * engine can apply.
4
+ *
5
+ * Two engine gaps make this necessary, and they are independent:
6
+ * 1. WebKit/Safari does not implement `CanvasRenderingContext2D.filter` at all
7
+ * (`'filter' in ctx` is `false`), so assigning it is a silent no-op and the
8
+ * runtime path draws the icon untinted.
9
+ * 2. WebKit also ignores a CSS `filter` set on an *inner* SVG element, so the
10
+ * SSR path can't wrap the artwork in a filtered `<g>` either. A real SVG
11
+ * `<filter>` element is honoured everywhere.
12
+ *
13
+ * Each matrix is 20 numbers in `feColorMatrix` row-major order (4 rows × 5
14
+ * columns, the last column a constant), and a filter string becomes a *list* of
15
+ * them applied in sequence — not one pre-multiplied matrix — because the Filter
16
+ * Effects spec clamps every primitive's result to 0–1 before the next one runs,
17
+ * and `hue-rotate` routinely overflows that range.
18
+ */
19
+ /** 4×5 colour matrix, row-major, in `feColorMatrix` `values` order. */
20
+ export type ColorMatrix = readonly number[];
21
+ /**
22
+ * Turn a CSS `filter` string into the chain of colour matrices that computes it,
23
+ * or `null` if any part of it isn't a colour transform (`blur`, `drop-shadow`,
24
+ * `url(#…)`, a typo). `'none'` and an empty string yield an empty chain.
25
+ *
26
+ * Callers treat `null` as "only a native implementation can do this" — the
27
+ * runtime path leaves the icon untouched, the SSR path emits a CSS `filter` as
28
+ * it did before.
29
+ */
30
+ export declare function filterMatrices(filter: string): ColorMatrix[] | null;
31
+ /**
32
+ * Apply a matrix chain to raw `ImageData` bytes, in place. Values are
33
+ * non-premultiplied on both sides (what `getImageData` hands back, and what
34
+ * `feColorMatrix` is defined over), and `Uint8ClampedArray` supplies the
35
+ * per-primitive clamp for free.
36
+ */
37
+ export declare function applyMatrices(data: Uint8ClampedArray, matrices: readonly ColorMatrix[]): void;
package/dist/ssr.js CHANGED
@@ -1,3 +1,3 @@
1
- export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-OA3YX73Z.js';
1
+ export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-T7D7UPKB.js';
2
2
  //# sourceMappingURL=ssr.js.map
3
3
  //# sourceMappingURL=ssr.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "favicon-env",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Tint your favicon per environment (dev/staging/prod) so you can tell instances apart at a glance. Runtime canvas mode + build-time SVG mode. Zero dependencies.",
5
5
  "keywords": [
6
6
  "canvas",
@@ -88,8 +88,13 @@
88
88
  "test:frameworks:firefox": "node test/frameworks/run.mjs --browser firefox",
89
89
  "test:frameworks:webkit": "node test/frameworks/run.mjs --browser webkit",
90
90
  "test:frameworks:browsers": "pnpm test:frameworks && pnpm test:frameworks:firefox && pnpm test:frameworks:webkit",
91
+ "pretest:pixels": "pnpm build",
92
+ "test:pixels": "node test/pixels.mjs",
93
+ "test:pixels:firefox": "pnpm test:pixels -- --browser firefox",
94
+ "test:pixels:webkit": "pnpm test:pixels -- --browser webkit",
95
+ "test:pixels:browsers": "pnpm test:pixels && pnpm test:pixels:firefox && pnpm test:pixels:webkit",
91
96
  "dev:frameworks": "node test/frameworks/serve-all.mjs",
92
- "test:all": "pnpm test && pnpm test:frameworks",
97
+ "test:all": "pnpm test && pnpm test:pixels && pnpm test:frameworks",
93
98
  "ncu": "pnpm dlx npm-check-updates",
94
99
  "changeset": "changeset",
95
100
  "version": "changeset version && pnpm run skill:sync",
@@ -11,7 +11,7 @@ description: >
11
11
  metadata:
12
12
  type: core
13
13
  library: favicon-env
14
- library_version: '0.3.2'
14
+ library_version: '0.3.3'
15
15
  sources:
16
16
  - 'Amir-Abushanab/favicon-env:README.md'
17
17
  - 'Amir-Abushanab/favicon-env:src/tint.ts'
@@ -172,8 +172,10 @@ const href = faviconDataUri(favicon, { hue: 130 });
172
172
  // → render into <link rel="icon" type="image/svg+xml" href={href}>
173
173
  ```
174
174
 
175
- `favicon-env/ssr` is pure string manipulation (no DOM), safe in Node. It bakes a
176
- CSS `filter` and/or badge into the SVG using its `viewBox`.
175
+ `favicon-env/ssr` is pure string manipulation (no DOM), safe in Node. It bakes an
176
+ SVG `<filter>` (a `feColorMatrix` chain) and/or badge into the SVG using its
177
+ `viewBox` — an SVG `<filter>` rather than a CSS one because WebKit ignores a CSS
178
+ `filter` set on an inner SVG element.
177
179
 
178
180
  ## Common Mistakes
179
181
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/badge.ts","../src/color.ts","../src/filter.ts","../src/ssr.ts"],"names":[],"mappings":"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","file":"chunk-OA3YX73Z.js","sourcesContent":["import type { Badge, BadgeCorner } from './types';\n\n/** Default badge background — the red dot/pill you get when no `color` is set. */\nexport const DEFAULT_BADGE_COLOR = '#ef4444';\n\n/** Normalise the `badge` shorthand (a `string` is a dot of that colour) to a `Badge`. */\nexport function normalizeBadge(badge: string | Badge): Badge {\n return typeof badge === 'string' ? { color: badge } : badge;\n}\n\n/** A badge's text as a string (`''` when omitted) — so a `0` renders and `undefined` doesn't. */\nexport function badgeText(badge: Badge): string {\n return badge.text == null ? '' : String(badge.text);\n}\n\n/**\n * Top-left corner for a `bw`×`bh` badge inside a `cw`×`ch` box, per `corner`\n * (relative to the box origin; the caller adds any offset). Shared by the canvas\n * and SVG renderers so placement stays identical across runtime and build-time.\n */\nexport function placeBadge(\n corner: BadgeCorner,\n cw: number,\n ch: number,\n bw: number,\n bh: number,\n margin: number,\n): [number, number] {\n if (corner === 'center') return [(cw - bw) / 2, (ch - bh) / 2];\n return [\n corner.endsWith('left') ? margin : cw - bw - margin,\n corner.startsWith('top') ? margin : ch - bh - margin,\n ];\n}\n","/** Parse `#rgb` / `#rrggbb` / `rgb(…)` / `rgba(…)` into `[r, g, b]` (0–255), or `null`. */\nfunction parseRgb(color: string): [number, number, number] | null {\n const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color);\n if (hex) {\n const h = hex[1].length === 3 ? hex[1].replace(/./g, (c) => c + c) : hex[1];\n const n = Number.parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n }\n const rgb = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/i.exec(color);\n if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n return null;\n}\n\n/**\n * Perceived lightness (0–1) of `color` for the black/white text decision:\n * rec601 luma for `#hex` / `rgb()`, or the `L` channel of CSS Color 4 colours —\n * `oklch()`/`oklab()` (0–1) and `lab()`/`lch()` (0–100). `null` if unreadable.\n */\nfunction lightness(color: string): number | null {\n const rgb = parseRgb(color);\n if (rgb) return (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;\n const lch = /^(okl(?:ch|ab)|l(?:ch|ab))\\(\\s*([\\d.]+)(%?)/i.exec(color);\n if (lch) {\n const l = Number(lch[2]);\n if (lch[3]) return l / 100; // a percentage L, in any of these spaces\n return /^ok/i.test(lch[1]) ? l : l / 100; // oklab/oklch L is 0–1, lab/lch is 0–100\n }\n return null;\n}\n\n/**\n * Black or white — whichever reads better on `color` — by perceived lightness.\n * Handles `#hex`, `rgb()`, and CSS Color 4 `oklch()`/`oklab()`/`lab()`/`lch()`;\n * falls back to white for anything else (named colours, `hsl()`, `color()`, …).\n */\nexport function contrastColor(color: string): string {\n const l = lightness(color);\n if (l == null) return '#fff';\n return l > 0.6 ? '#000' : '#fff';\n}\n","import type { EnvTint } from './types';\n\n/**\n * The CSS `filter` for a tint. An explicit `filter` wins outright; otherwise\n * `hue` and `invert` compose into one filter string (both applied, in that\n * order). `null` when none of them are set.\n */\nexport function cssFilter(tint: EnvTint): string | null {\n if (tint.filter) return tint.filter;\n const parts: string[] = [];\n if (typeof tint.hue === 'number') parts.push(`hue-rotate(${tint.hue}deg)`);\n // `invert: true` → invert(1); a number is used verbatim. `0` / `false` is a no-op.\n if (tint.invert) parts.push(`invert(${tint.invert === true ? 1 : tint.invert})`);\n return parts.length > 0 ? parts.join(' ') : null;\n}\n","import { badgeText, DEFAULT_BADGE_COLOR, normalizeBadge, placeBadge } from './badge';\nimport { contrastColor } from './color';\nimport { cssFilter } from './filter';\nimport type { Badge, EnvConfig } from './types';\n\nconst XML_ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&apos;',\n};\n\nfunction escapeXml(value: string): string {\n return value.replace(/[&<>\"']/g, (c) => XML_ESCAPES[c] ?? c);\n}\n\nconst round = (n: number): number => Math.round(n * 100) / 100;\n\n/** ` opacity=\"…\"` attribute for a translucent badge group, else `''`. */\nfunction opacityAttr(badge: Badge): string {\n return (badge.opacity ?? 1) < 1 ? ` opacity=\"${round(badge.opacity ?? 1)}\"` : '';\n}\n\n/** Attributes that squeeze the glyphs into `len` when the natural text would overflow. */\nfunction fitText(len: number): string {\n return ` textLength=\"${round(len)}\" lengthAdjust=\"spacingAndGlyphs\"`;\n}\n\n/** A centred, bold `<text>` element at (`cx`, `cy`) — the one badge-label form. */\nfunction svgText(\n cx: number,\n cy: number,\n fill: string,\n fontSize: number,\n fit: string,\n text: string,\n): string {\n return (\n `<text x=\"${round(cx)}\" y=\"${round(cy)}\" fill=\"${escapeXml(fill)}\" ` +\n `font-family=\"system-ui, sans-serif\" font-size=\"${round(fontSize)}\" font-weight=\"700\" ` +\n `text-anchor=\"middle\" dominant-baseline=\"central\"${fit}>${escapeXml(text)}</text>`\n );\n}\n\n/** Read the drawable box from a root `<svg>` tag: its `viewBox`, else `width`/`height`. */\nfunction parseViewBox(openTag: string): [number, number, number, number] | null {\n const vb = /viewBox\\s*=\\s*[\"']([^\"']+)[\"']/i.exec(openTag);\n if (vb) {\n const parts = vb[1]\n .trim()\n .split(/[\\s,]+/)\n .map(Number);\n if (parts.length === 4 && parts.every((n) => !Number.isNaN(n))) {\n return [parts[0], parts[1], parts[2], parts[3]];\n }\n }\n const w = /\\bwidth\\s*=\\s*[\"']?([\\d.]+)/i.exec(openTag);\n const h = /\\bheight\\s*=\\s*[\"']?([\\d.]+)/i.exec(openTag);\n if (w && h) return [0, 0, Number(w[1]), Number(h[1])];\n return null;\n}\n\n/** Build an SVG `<g>` badge (dot, or a pill with text) sized to the viewBox. */\nfunction svgBadge([minX, minY, w, h]: [number, number, number, number], badge: Badge): string {\n const text = badgeText(badge);\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const corner = badge.corner ?? 'bottom-right';\n const bh = h * (badge.size ?? 0.5);\n const fontSize = bh * 0.62;\n const margin = w * 0.02;\n // SVG has no text metrics at build time; approximate glyph width at ~0.62em,\n // then clamp to the icon so a long label (e.g. a big PR number) can't overflow.\n const natural = text ? Math.max(bh, text.length * fontSize * 0.62 + bh * 0.5) : bh;\n const bw = text ? Math.min(natural, w - margin * 2) : bh;\n const [px, py] = placeBadge(corner, w, h, bw, bh, margin);\n const x = minX + px;\n const y = minY + py;\n const rx = text ? Math.min(bh / 2, w * 0.24) : bh / 2;\n // If clamped, force the glyphs to fit the pill width.\n const fit = bw < natural ? fitText(bw - bh * 0.4) : '';\n const label = text\n ? svgText(x + bw / 2, y + bh / 2, badge.textColor ?? contrastColor(color), fontSize, fit, text)\n : '';\n return (\n `<g${opacityAttr(badge)}><rect x=\"${round(x)}\" y=\"${round(y)}\" width=\"${round(bw)}\" height=\"${round(bh)}\" ` +\n `rx=\"${round(rx)}\" fill=\"${escapeXml(color)}\" stroke=\"rgba(0,0,0,0.35)\" ` +\n `stroke-width=\"${round(h * 0.015)}\"/>${label}</g>`\n );\n}\n\n/** Build a full-icon \"cover\" tile — a background rect plus a big centred number. */\nfunction svgCover([minX, minY, w, h]: [number, number, number, number], badge: Badge): string {\n const color = badge.color ?? DEFAULT_BADGE_COLOR;\n const text = badgeText(badge);\n const side = Math.min(w, h);\n const op = opacityAttr(badge);\n const rect =\n `<rect x=\"${round(minX)}\" y=\"${round(minY)}\" width=\"${round(w)}\" height=\"${round(h)}\" ` +\n `rx=\"${round(side * 0.2)}\" fill=\"${escapeXml(color)}\"/>`;\n if (!text) return `<g${op}>${rect}</g>`;\n const fontSize = side * 0.62;\n const maxLen = w * 0.84;\n const fit = text.length * fontSize * 0.62 > maxLen ? fitText(maxLen) : '';\n const label = svgText(\n minX + w / 2,\n minY + h / 2,\n badge.textColor ?? contrastColor(color),\n fontSize,\n fit,\n text,\n );\n return `<g${op}>${rect}${label}</g>`;\n}\n\n/**\n * An SVG `<filter>` that colourises the icon to `color`, mirroring the runtime\n * canvas path: desaturate → multiply the flood colour → re-mask the original\n * alpha. `color-interpolation-filters=\"sRGB\"` keeps it visually matching canvas.\n */\nfunction svgColorize(color: string): string {\n return (\n `<filter id=\"__favenv_c\" color-interpolation-filters=\"sRGB\">` +\n `<feColorMatrix type=\"saturate\" values=\"0\" result=\"g\"/>` +\n `<feFlood flood-color=\"${escapeXml(color)}\" result=\"f\"/>` +\n `<feBlend in=\"f\" in2=\"g\" mode=\"multiply\" result=\"b\"/>` +\n `<feComposite in=\"b\" in2=\"SourceGraphic\" operator=\"in\"/>` +\n `</filter>`\n );\n}\n\n/**\n * Return `svg` (an SVG *string*) with the environment's tint and/or badge baked\n * in — the form that survives being rendered as an `<img>` / favicon, with no\n * first-paint flash. The tint is a CSS `filter` on a wrapping group; the badge\n * is an appended `<g>` positioned via the SVG's `viewBox` (a badge is skipped if\n * no `viewBox` or `width`/`height` can be read).\n *\n * Returns the SVG unchanged when `tint` is falsy, has nothing to apply, or the\n * input isn't a recognisable `<svg>…</svg>` document.\n */\nexport function tintSvg(svg: string, tint: EnvConfig): string {\n if (!tint) return svg;\n const filter = cssFilter(tint);\n const colorize = tint.filter ? undefined : tint.tint; // explicit `filter` beats `tint`\n const badge = tint.badge ? normalizeBadge(tint.badge) : null;\n if (!filter && !colorize && !badge) return svg;\n const open = /<svg\\b[^>]*>/i.exec(svg);\n if (!open) return svg;\n const close = svg.lastIndexOf('</svg>');\n if (close === -1) return svg;\n const openEnd = open.index + open[0].length;\n\n // `shape: 'cover'` replaces the icon's content with a full-bleed number tile.\n // A translucent cover (opacity < 1) keeps the base showing through instead.\n if (badge?.shape === 'cover') {\n const vb = parseViewBox(open[0]);\n if (!vb) return svg;\n const base = (badge.opacity ?? 1) < 1 ? svg.slice(openEnd, close) : '';\n return `${svg.slice(0, openEnd)}${base}${svgCover(vb, badge)}${svg.slice(close)}`;\n }\n\n const inner = svg.slice(openEnd, close);\n // No XML comments injected here — XML comments may not contain `--`, which\n // every `--custom-property` does, and that silently breaks favicon SVGs.\n // `tint` colourises via an SVG `<filter>`; else `hue`/`filter` is a CSS filter.\n let defs = '';\n let body = inner;\n if (colorize) {\n defs = svgColorize(colorize);\n body = `<g filter=\"url(#__favenv_c)\">${inner}</g>`;\n } else if (filter) {\n defs = `<style>.__favenv{filter:${filter}}</style>`;\n body = `<g class=\"__favenv\">${inner}</g>`;\n }\n const viewBox = badge ? parseViewBox(open[0]) : null;\n const badgeSvg = badge && viewBox ? svgBadge(viewBox, badge) : '';\n return `${svg.slice(0, openEnd)}${defs}${body}${badgeSvg}${svg.slice(close)}`;\n}\n\n/** Percent-encode an SVG string as a `data:` URI suitable for a favicon `href`. */\nexport function svgToDataUri(svg: string): string {\n return `data:image/svg+xml,${encodeURIComponent(svg)}`;\n}\n\n/**\n * Convenience: `tintSvg` + `svgToDataUri`. Give it your favicon SVG and the\n * config for the current build's environment; get back a ready-to-use\n * `<link rel=\"icon\" href=\"…\">` value with no first-paint flash.\n */\nexport function faviconDataUri(svg: string, tint: EnvConfig): string {\n return svgToDataUri(tintSvg(svg, tint));\n}\n"]}