favicon-env 0.2.0 → 0.3.0

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.
@@ -0,0 +1,13 @@
1
+ import type { Badge, BadgeCorner } from './types';
2
+ /** Default badge background — the red dot/pill you get when no `color` is set. */
3
+ export declare const DEFAULT_BADGE_COLOR = "#ef4444";
4
+ /** Normalise the `badge` shorthand (a `string` is a dot of that colour) to a `Badge`. */
5
+ export declare function normalizeBadge(badge: string | Badge): Badge;
6
+ /** A badge's text as a string (`''` when omitted) — so a `0` renders and `undefined` doesn't. */
7
+ export declare function badgeText(badge: Badge): string;
8
+ /**
9
+ * Top-left corner for a `bw`×`bh` badge inside a `cw`×`ch` box, per `corner`
10
+ * (relative to the box origin; the caller adds any offset). Shared by the canvas
11
+ * and SVG renderers so placement stays identical across runtime and build-time.
12
+ */
13
+ export declare function placeBadge(corner: BadgeCorner, cw: number, ch: number, bw: number, bh: number, margin: number): [number, number];
@@ -46,8 +46,10 @@ function contrastColor(color) {
46
46
  // src/filter.ts
47
47
  function cssFilter(tint) {
48
48
  if (tint.filter) return tint.filter;
49
- if (typeof tint.hue === "number") return `hue-rotate(${tint.hue}deg)`;
50
- return null;
49
+ const parts = [];
50
+ if (typeof tint.hue === "number") parts.push(`hue-rotate(${tint.hue}deg)`);
51
+ if (tint.invert) parts.push(`invert(${tint.invert === true ? 1 : tint.invert})`);
52
+ return parts.length > 0 ? parts.join(" ") : null;
51
53
  }
52
54
 
53
55
  // src/ssr.ts
@@ -163,5 +165,5 @@ function faviconDataUri(svg, tint) {
163
165
  }
164
166
 
165
167
  export { DEFAULT_BADGE_COLOR, badgeText, contrastColor, cssFilter, faviconDataUri, normalizeBadge, placeBadge, svgToDataUri, tintSvg };
166
- //# sourceMappingURL=chunk-3JXNCGZF.js.map
167
- //# sourceMappingURL=chunk-3JXNCGZF.js.map
168
+ //# sourceMappingURL=chunk-OA3YX73Z.js.map
169
+ //# sourceMappingURL=chunk-OA3YX73Z.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Black or white — whichever reads better on `color` — by perceived lightness.
3
+ * Handles `#hex`, `rgb()`, and CSS Color 4 `oklch()`/`oklab()`/`lab()`/`lch()`;
4
+ * falls back to white for anything else (named colours, `hsl()`, `color()`, …).
5
+ */
6
+ export declare function contrastColor(color: string): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Default environment heuristic, based on `location.hostname`:
3
+ *
4
+ * - `localhost`, `127.0.0.1`, `::1`, `*.local`, `*.localhost`, a raw IPv4 → `'dev'`
5
+ * - a `staging` / `stg` / `qa` / `uat` / `preview` / `preprod` / `test` / `dev`
6
+ * segment anywhere in the host → `'staging'`
7
+ * - anything else → `'prod'`
8
+ *
9
+ * It's intentionally simple. Pass your own `detect` to `envFavicon` when your
10
+ * hostnames don't follow these conventions.
11
+ *
12
+ * @param hostname override the host to classify (defaults to `location.hostname`)
13
+ */
14
+ export declare function defaultDetect(hostname?: string): string;
@@ -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 C(e){return e.text==null?"":String(e.text)}function x(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 B(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){return e.filter?e.filter:typeof e.hue=="number"?`hue-rotate(${e.hue}deg)`:null}function O(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"]',$;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:O(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,B(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=C(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]=x(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.filter||!!t.tint||!!n))return t.src&&v(t.src,Y(t.src)),Promise.resolve();$??($=K());let s=t.src??e.source??$??"/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 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);})();
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/** The CSS `filter` for a tint — an explicit `filter` beats `hue`; `null` if neither is set. */\nexport function cssFilter(tint: EnvTint): string | null {\n if (tint.filter) return tint.filter;\n if (typeof tint.hue === 'number') return `hue-rotate(${tint.hue}deg)`;\n return 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 || Boolean(tint.filter) || Boolean(tint.tint) || 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`/`filter` is a plain CSS\n // 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","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/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"]}
@@ -0,0 +1,7 @@
1
+ import type { EnvTint } from './types';
2
+ /**
3
+ * The CSS `filter` for a tint. An explicit `filter` wins outright; otherwise
4
+ * `hue` and `invert` compose into one filter string (both applied, in that
5
+ * order). `null` when none of them are set.
6
+ */
7
+ export declare function cssFilter(tint: EnvTint): string | null;
package/dist/hash.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Deterministic hue (0–359) derived from a string via FNV-1a. Stable across
3
+ * loads and well distributed, so distinct inputs (e.g. `location.host` values)
4
+ * get distinct colours. Powers `envFavicon`'s auto mode.
5
+ *
6
+ * @param input the string to hash (e.g. `location.host`)
7
+ * @param offset extra degrees added to the result, to shift the whole palette
8
+ */
9
+ export declare function hashHue(input: string, offset?: number): number;
package/dist/index.d.ts CHANGED
@@ -1,54 +1,5 @@
1
- import { E as EnvFaviconOptions, a as EnvRule, b as EnvTint } from './ssr-BzQ5WSMw.js';
2
- export { A as AutoOptions, B as Badge, c as BadgeCorner, d as BadgeTextFn, e as EnvConfig, R as RuleBadge, f as faviconDataUri, s as svgToDataUri, t as tintSvg } from './ssr-BzQ5WSMw.js';
3
-
4
- /**
5
- * Return the tint of the first `rule` whose `match` matches `url`, or `null` if
6
- * none do. A `RegExp` `match` is tested against `url.host` and its captures are
7
- * interpolated into `badge.text` (`$1`, `$<name>`); a function `match` receives
8
- * the `URL`. Exposed so the same rules can drive server-side rendering from a
9
- * request URL — pair the result with `favicon-env/ssr`'s `faviconDataUri`.
10
- */
11
- declare function matchRules(rules: EnvRule[], url: URL): EnvTint | null;
12
- /**
13
- * Tint / decorate the page favicon for the current environment. Resolves a tint
14
- * from `rules` (URL match) → `auto` → `environments`/`detect`, then reads the
15
- * existing `<link rel="icon">` (or a per-env `src` / `options.source`), redraws
16
- * it on a `<canvas>` with the hue / filter / badge, and swaps in the result as a
17
- * PNG data URL.
18
- *
19
- * A no-op during SSR (no `document`) or when nothing resolves. Works with any
20
- * favicon format (svg / png / ico). A custom `src` with no recolour or badge is
21
- * swapped in directly, skipping the canvas — which avoids cross-origin taint and
22
- * keeps vector sources sharp. If a canvas source is cross-origin without CORS
23
- * headers the canvas is tainted and the favicon is left untouched.
24
- *
25
- * @returns a promise that resolves once the swap has been attempted.
26
- */
27
- declare function envFavicon(options?: EnvFaviconOptions): Promise<void>;
28
-
29
- /**
30
- * Default environment heuristic, based on `location.hostname`:
31
- *
32
- * - `localhost`, `127.0.0.1`, `::1`, `*.local`, `*.localhost`, a raw IPv4 → `'dev'`
33
- * - a `staging` / `stg` / `qa` / `uat` / `preview` / `preprod` / `test` / `dev`
34
- * segment anywhere in the host → `'staging'`
35
- * - anything else → `'prod'`
36
- *
37
- * It's intentionally simple. Pass your own `detect` to `envFavicon` when your
38
- * hostnames don't follow these conventions.
39
- *
40
- * @param hostname override the host to classify (defaults to `location.hostname`)
41
- */
42
- declare function defaultDetect(hostname?: string): string;
43
-
44
- /**
45
- * Deterministic hue (0–359) derived from a string via FNV-1a. Stable across
46
- * loads and well distributed, so distinct inputs (e.g. `location.host` values)
47
- * get distinct colours. Powers `envFavicon`'s auto mode.
48
- *
49
- * @param input the string to hash (e.g. `location.host`)
50
- * @param offset extra degrees added to the result, to shift the whole palette
51
- */
52
- declare function hashHue(input: string, offset?: number): number;
53
-
54
- export { EnvFaviconOptions, EnvRule, EnvTint, defaultDetect, envFavicon, hashHue, matchRules };
1
+ export { envFavicon, matchRules } from './tint';
2
+ export { faviconDataUri, svgToDataUri, tintSvg } from './ssr';
3
+ export { defaultDetect } from './detect';
4
+ export { hashHue } from './hash';
5
+ export type { AutoOptions, Badge, BadgeCorner, BadgeTextFn, EnvConfig, EnvFaviconOptions, EnvRule, EnvTint, RuleBadge, } from './types';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeBadge, cssFilter, DEFAULT_BADGE_COLOR, badgeText, placeBadge, contrastColor } from './chunk-3JXNCGZF.js';
2
- export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-3JXNCGZF.js';
1
+ import { normalizeBadge, cssFilter, DEFAULT_BADGE_COLOR, badgeText, placeBadge, contrastColor } from './chunk-OA3YX73Z.js';
2
+ export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-OA3YX73Z.js';
3
3
 
4
4
  // src/detect.ts
5
5
  function defaultDetect(hostname = typeof location === "undefined" ? "" : location.hostname) {
@@ -184,7 +184,7 @@ function envFavicon(options = {}) {
184
184
  }
185
185
  return Promise.resolve();
186
186
  }
187
- const needsCanvas = tint.hue != null || Boolean(tint.filter) || Boolean(tint.tint) || Boolean(badge);
187
+ const needsCanvas = tint.hue != null || Boolean(tint.invert) || Boolean(tint.filter) || Boolean(tint.tint) || Boolean(badge);
188
188
  if (!needsCanvas) {
189
189
  if (tint.src) applyFavicon(tint.src, inferType(tint.src));
190
190
  return Promise.resolve();
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 || Boolean(tint.filter) || Boolean(tint.tint) || 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`/`filter` is a plain CSS\n // 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 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"]}
package/dist/ssr.d.ts CHANGED
@@ -1 +1,20 @@
1
- export { f as faviconDataUri, s as svgToDataUri, t as tintSvg } from './ssr-BzQ5WSMw.js';
1
+ import type { EnvConfig } from './types';
2
+ /**
3
+ * Return `svg` (an SVG *string*) with the environment's tint and/or badge baked
4
+ * in — the form that survives being rendered as an `<img>` / favicon, with no
5
+ * first-paint flash. The tint is a CSS `filter` on a wrapping group; the badge
6
+ * is an appended `<g>` positioned via the SVG's `viewBox` (a badge is skipped if
7
+ * no `viewBox` or `width`/`height` can be read).
8
+ *
9
+ * Returns the SVG unchanged when `tint` is falsy, has nothing to apply, or the
10
+ * input isn't a recognisable `<svg>…</svg>` document.
11
+ */
12
+ export declare function tintSvg(svg: string, tint: EnvConfig): string;
13
+ /** Percent-encode an SVG string as a `data:` URI suitable for a favicon `href`. */
14
+ export declare function svgToDataUri(svg: string): string;
15
+ /**
16
+ * Convenience: `tintSvg` + `svgToDataUri`. Give it your favicon SVG and the
17
+ * config for the current build's environment; get back a ready-to-use
18
+ * `<link rel="icon" href="…">` value with no first-paint flash.
19
+ */
20
+ export declare function faviconDataUri(svg: string, tint: EnvConfig): string;
package/dist/ssr.js CHANGED
@@ -1,3 +1,3 @@
1
- export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-3JXNCGZF.js';
1
+ export { faviconDataUri, svgToDataUri, tintSvg } from './chunk-OA3YX73Z.js';
2
2
  //# sourceMappingURL=ssr.js.map
3
3
  //# sourceMappingURL=ssr.js.map
package/dist/tint.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { EnvFaviconOptions, EnvRule, EnvTint } from './types';
2
+ /**
3
+ * Return the tint of the first `rule` whose `match` matches `url`, or `null` if
4
+ * none do. A `RegExp` `match` is tested against `url.host` and its captures are
5
+ * interpolated into `badge.text` (`$1`, `$<name>`); a function `match` receives
6
+ * the `URL`. Exposed so the same rules can drive server-side rendering from a
7
+ * request URL — pair the result with `favicon-env/ssr`'s `faviconDataUri`.
8
+ */
9
+ export declare function matchRules(rules: EnvRule[], url: URL): EnvTint | null;
10
+ /**
11
+ * Tint / decorate the page favicon for the current environment. Resolves a tint
12
+ * from `rules` (URL match) → `auto` → `environments`/`detect`, then reads the
13
+ * existing `<link rel="icon">` (or a per-env `src` / `options.source`), redraws
14
+ * it on a `<canvas>` with the hue / filter / badge, and swaps in the result as a
15
+ * PNG data URL.
16
+ *
17
+ * A no-op during SSR (no `document`) or when nothing resolves. Works with any
18
+ * favicon format (svg / png / ico). A custom `src` with no recolour or badge is
19
+ * swapped in directly, skipping the canvas — which avoids cross-origin taint and
20
+ * keeps vector sources sharp. If a canvas source is cross-origin without CORS
21
+ * headers the canvas is tainted and the favicon is left untouched.
22
+ *
23
+ * @returns a promise that resolves once the swap has been attempted.
24
+ */
25
+ export declare function envFavicon(options?: EnvFaviconOptions): Promise<void>;