visionary-image 1.0.3 → 1.1.0-pre.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.
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  React image component with built-in Blurhash placeholders for better UX and Core Web Vitals.
4
4
 
5
- ![GitHub Release](https://img.shields.io/github/v/release/visionary-ux/visionary-image?color=beige) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/visionary-ux/visionary-image/.github%2Fworkflows%2Fci-cd-workflow.yml?branch=master&style=flat-square)](https://github.com/visionary-ux/visionary-image/actions/workflows/ci-cd-workflow.yml?query=branch%3Amaster) [![npm package size (minzipped, via Bundlephobia)](https://img.shields.io/bundlephobia/minzip/visionary-image?style=flat-square&color=blue)](https://bundlephobia.com/package/visionary-image) [![Storybook demo](https://img.shields.io/badge/-Storybook-FF4785?style=flat-square&logo=storybook&logoColor=white)](https://visionary-ux.github.io/visionary-image/)
5
+ ![GitHub Release](https://img.shields.io/github/v/release/visionary-ux/visionary-image?color=beige) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/visionary-ux/visionary-image/.github%2Fworkflows%2Fci-cd-workflow.yml?branch=master)](https://github.com/visionary-ux/visionary-image/actions/workflows/ci-cd-workflow.yml?query=branch%3Amaster) [![npm package size (minzipped, via Bundlephobia)](https://img.shields.io/bundlephobia/minzip/visionary-image?color=blue)](https://bundlephobia.com/package/visionary-image) ![NPM Downloads](https://img.shields.io/npm/d18m/visionary-image?color=lightgray) [![Storybook demo](https://img.shields.io/badge/-Storybook-FF4785?logo=storybook&logoColor=white)](https://visionary-ux.github.io/visionary-image/)
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **Easy Blurhash:** Get started with Blurhash in 60 seconds.
10
10
  - **Layout stability**: Eliminates Cumulative Layout Shift (CLS) with true-to-size, responsive placeholders.
11
11
  - **Automatic lazy loading**: Off-screen images are deferred, reducing initial pageload size and optimizing Interaction to Next Paint (INP).
12
- - **Lighning-fast previews**: Renders placeholders early (by initial DOM layout) using URL-embedded Blurhash data, powered by [`visionary-url`](https://github.com/visionary-ux/visionary-url).
12
+ - **Lighning-fast previews**: Renders placeholders early (by initial DOM layout) using URL-embedded Blurhash data, powered by [`blurhash-url`](https://github.com/visionary-ux/blurhash-url).
13
13
  - **Framework tested**: Compatible with Remix, Next.js, and Vite and supports both client and server-side rendering (SSR, SSG).
14
14
  - **Additional features**: Prevent image dragging; prevent user selecting image
15
15
  - **Developer friendly**: Written in Typescript and [unit tested](./src/components/Image/__test__/).
@@ -32,45 +32,46 @@ npm install --save visionary-image
32
32
 
33
33
  ## Usage
34
34
 
35
- Begin by creating a Visionary image URL. This is then passed to the `src` prop of the Image component.
35
+ Begin by creating a Blurhash URL. This is then passed to the `src` prop of the Image component.
36
36
 
37
- ### Creating a Visionary Image URL
37
+ ### Creating a Blurhash URL
38
38
 
39
- There are several ways to create a Visionary URL.
39
+ There are several ways to create a Blurhash URL.
40
40
 
41
- 1. Use the [Visionary URL Maker](https://visionary.cloud/url-maker) for public image URLs
41
+ 1. Use the [Blurhash URL Maker](https://visionary.cloud/url-maker) for public image URLs
42
42
  2. Use the [Drag & Drop Blurhash Generator](https://visionary.cloud/image-to-blurhash) for local image files
43
- 3. Use [visionary-url](https://github.com/visionary-ux/visionary-url) to programmatically generate a URL
43
+ 3. Use [blurhash-url](https://github.com/visionary-ux/blurhash-url) to programmatically generate a URL
44
44
 
45
45
  ### Render Image
46
46
 
47
47
  ```tsx
48
48
  import { Image } from "visionary-image";
49
49
 
50
- const ImageDetails = () => <Image alt="..." src="<Visionary URL>" />;
50
+ const ImageDetails = () => <Image alt="..." src="<Blurhash URL>" />;
51
51
  ```
52
52
 
53
53
  ## Component Props
54
54
 
55
- | Name | Description |
56
- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
- | `alt` <br/> string | Image `alt` tag. Adding alt text to images is highly recommended to accommodate accessible devices and improve discoverability. |
58
- | `bgColorAlpha` <br/> number | Base layer (background color) alpha channel.<br /> Default: `0.7` |
59
- | `className` <br/> string | Classname applied to the container `div` or the fallback `img` element. |
60
- | `debug` <br/> boolean | Prints handy debug info to the console (Visionary URL data, render times). |
61
- | `disableBlurLayer` <br/> boolean | Disables rendering of the blur (canvas) layer. |
62
- | `disableImageLayer` <br/> boolean | Disables rendering of the image layer. |
63
- | `height` <br/>number, string | If set, will override internally computed image height. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
64
- | `hideImageLayer` <br/> boolean | Hides the image layer, revealing the blur layer underneath. |
65
- | `lazy` <br/> boolean | Should image load lazily. <br/> Default: `true` |
66
- | `onClick` <br/> function | Callback function to invoke when the image is clicked. function. |
67
- | `onError` <br/> function | Error callback function. |
68
- | `onLoad` <br/> function | Image loaded callback function. |
69
- | `preventDrag` <br/>boolean | Prevents user from dragging the image element. |
70
- | `preventSelection` <br/>boolean | Prevents user from highlighting the image element. |
71
- | `punch` <br/>number | Blurhash punch parameter.<br /> Default: `1` |
72
- | `src` <br/>string | Visionary URL, Visionary Code, or ordinary image URL.<br/> If `src` contains Visionary data, placeholders will be rendered, otherwise falls back to an `img` element. <br/> **required** |
73
- | `width` <br/>number, string | If set, will override internally computed image width. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
55
+ | Name | Description |
56
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
57
+ | `alt` <br/> string | Image `alt` tag. Adding alt text to images is highly recommended to accommodate accessible devices and improve discoverability. |
58
+ | `bgColorAlpha` <br/> number | Base layer (background color) alpha channel.<br /> Default: `0.7` |
59
+ | `className` <br/> string | Classname applied to the container `div` or the fallback `img` element. |
60
+ | `debug` <br/> boolean | Prints handy debug info to the console (Blurhash URL data, render times). |
61
+ | `disableBlurLayer` <br/> boolean | Disables rendering of the blur (canvas) layer. |
62
+ | `disableImageLayer` <br/> boolean | Disables rendering of the image layer. |
63
+ | `height` <br/>number, string | If set, will override internally computed image height. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
64
+ | `hideImageLayer` <br/> boolean | Hides the image layer, revealing the blur layer underneath. |
65
+ | `lazy` <br/> boolean | Should image load lazily. <br/> Default: `true` |
66
+ | `onClick` <br/> function | Callback function to invoke when the image is clicked. function. |
67
+ | `onError` <br/> function | Error callback function. |
68
+ | `onLoad` <br/> function | Image loaded callback function. |
69
+ | `preventDrag` <br/>boolean | Prevents user from dragging the image element. |
70
+ | `preventSelection` <br/>boolean | Prevents user from highlighting the image element. |
71
+ | `priority` <br/>boolean | Mark as priority image (above-the-fold). Sets `fetchpriority="high"` and `loading="eager"`. Use for LCP images.<br /> Default: `false` |
72
+ | `punch` <br/>number | Blurhash punch parameter.<br /> Default: `1` |
73
+ | `src` <br/>string | Blurhash URL, Visionary Code, or ordinary image URL.<br/> If `src` contains Blurhash URL data, placeholders will be rendered, otherwise falls back to an `img` element. <br/> **required** |
74
+ | `width` <br/>number, string | If set, will override internally computed image width. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
74
75
 
75
76
  ## Relevant Questions
76
77
 
@@ -85,3 +86,10 @@ Canvas operations are highly efficient in modern browsers. Rendering the 24x24 p
85
86
  ### What is Blurhash and where can I learn more?
86
87
 
87
88
  Blurhash uses Discrete Cosine Transforms to represent a color-accurate image placeholder as a compact string (in 20-30 characters). Check out the official [Blurhash docs](https://github.com/woltapp/blurhash) for more info.
89
+
90
+ ## Related packages
91
+
92
+ | Package | Use for |
93
+ | -------------------------------------------------------------------------- | ------------------------------------------------------------------ |
94
+ | **`visionary-image`** (this package) | React apps (`<Image />`), or the `<visionary-image>` web component |
95
+ | [`visionary-image-js`](https://github.com/visionary-ux/visionary-image-js) | Zero-config `<script>` / CDN, or framework-agnostic SSR HTML |
@@ -0,0 +1,100 @@
1
+ import { c as e, n as t, o as n, s as r } from "./canvas-D7cApRRm.js";
2
+ //#region src/lib/util.ts
3
+ var i = (e, t) => `rgba(${e.r},${e.g},${e.b},${t})`, a = (e) => {
4
+ let t = e.replace(/^#/, "");
5
+ if (t.length !== 3 && t.length !== 6) return null;
6
+ t.length === 3 && (t = `${t[0]}${t[0]}${t[1]}${t[1]}${t[2]}${t[2]}`);
7
+ let n = parseInt(t.slice(0, 2), 16), r = parseInt(t.slice(2, 4), 16);
8
+ return {
9
+ b: parseInt(t.slice(4, 6), 16),
10
+ g: r,
11
+ r: n
12
+ };
13
+ }, o = null;
14
+ function s() {
15
+ return o ||= new IntersectionObserver((e) => {
16
+ e.forEach((e) => {
17
+ if (e.isIntersecting) {
18
+ let t = e.target;
19
+ t.renderBlurhashNow(), o?.unobserve(t);
20
+ }
21
+ });
22
+ }, { rootMargin: "200px" }), o;
23
+ }
24
+ var c = typeof HTMLElement < "u" ? HTMLElement : class {}, l = class extends c {
25
+ canvas = null;
26
+ img = null;
27
+ container = null;
28
+ blurhash = null;
29
+ hasRenderedBlurhash = !1;
30
+ static get observedAttributes() {
31
+ return [
32
+ "src",
33
+ "alt",
34
+ "lazy",
35
+ "priority"
36
+ ];
37
+ }
38
+ connectedCallback() {
39
+ this.render();
40
+ }
41
+ disconnectedCallback() {
42
+ s().unobserve(this);
43
+ }
44
+ attributeChangedCallback() {
45
+ this.container && (this.hasRenderedBlurhash = !1, this.render());
46
+ }
47
+ renderBlurhashNow() {
48
+ this.hasRenderedBlurhash || !this.blurhash || !this.canvas || (this.renderBlurhashToCanvas(this.blurhash), this.hasRenderedBlurhash = !0);
49
+ }
50
+ render() {
51
+ let e = this.getAttribute("src");
52
+ if (!e) return;
53
+ let t = r(e);
54
+ if (!t) {
55
+ this.renderFallbackImage(e);
56
+ return;
57
+ }
58
+ let { fields: o } = t, { blurhash: c, sourceWidth: l, sourceHeight: u, bcc: d } = o, f = this.getAttribute("priority") === "true", p = !f && this.getAttribute("lazy") !== "false";
59
+ this.blurhash = c || null;
60
+ let m = l / u;
61
+ if (this.container = document.createElement("div"), this.container.style.cssText = `
62
+ position: relative;
63
+ width: 100%;
64
+ aspect-ratio: ${m};
65
+ overflow: hidden;
66
+ `, d) {
67
+ let e = a(d);
68
+ e && (this.container.style.backgroundColor = i(e, n));
69
+ }
70
+ c && (this.canvas = document.createElement("canvas"), this.canvas.width = 24, this.canvas.height = 24, this.canvas.style.cssText = "\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n ", this.container.appendChild(this.canvas), p ? s().observe(this) : (this.renderBlurhashToCanvas(c), this.hasRenderedBlurhash = !0)), this.img = document.createElement("img"), this.img.src = e, this.img.alt = this.getAttribute("alt") || "", this.img.loading = p ? "lazy" : "eager", f && (this.img.fetchPriority = "high"), this.img.style.cssText = "\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n ", this.img.onload = () => {
71
+ this.canvas && (this.canvas.style.opacity = "0", this.canvas.style.transition = "opacity 0.3s ease-out");
72
+ }, this.container.appendChild(this.img), this.innerHTML = "", this.appendChild(this.container);
73
+ }
74
+ renderBlurhashToCanvas(n) {
75
+ if (!this.canvas) return;
76
+ let r = this.canvas.getContext("2d");
77
+ if (!r) return;
78
+ let i = t(), a = `${n}:24:1`, o = i.get(a);
79
+ if (!o) try {
80
+ o = e(n, 24, 24, 1), i.set(a, o);
81
+ } catch (e) {
82
+ console.warn("Blurhash decode error:", e);
83
+ return;
84
+ }
85
+ let s = r.createImageData(24, 24);
86
+ s.data.set(o), r.putImageData(s, 0, 0);
87
+ }
88
+ renderFallbackImage(e) {
89
+ this.innerHTML = "";
90
+ let t = document.createElement("img");
91
+ t.src = e, t.alt = this.getAttribute("alt") || "";
92
+ let n = this.getAttribute("priority") === "true";
93
+ t.loading = n || this.getAttribute("lazy") === "false" ? "eager" : "lazy", n && (t.fetchPriority = "high"), t.style.cssText = "width: 100%; height: auto;", this.appendChild(t);
94
+ }
95
+ };
96
+ function u(e = "visionary-image") {
97
+ typeof HTMLElement > "u" || typeof customElements > "u" || customElements.get(e) || customElements.define(e, l);
98
+ }
99
+ //#endregion
100
+ export { u as n, l as t };
@@ -0,0 +1,17 @@
1
+ const e=require("./canvas-CnN_nKut.cjs");var t=(e,t)=>`rgba(${e.r},${e.g},${e.b},${t})`,n=e=>{let t=e.replace(/^#/,``);if(t.length!==3&&t.length!==6)return null;t.length===3&&(t=`${t[0]}${t[0]}${t[1]}${t[1]}${t[2]}${t[2]}`);let n=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16);return{b:parseInt(t.slice(4,6),16),g:r,r:n}},r=null;function i(){return r||=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){let t=e.target;t.renderBlurhashNow(),r?.unobserve(t)}})},{rootMargin:`200px`}),r}var a=typeof HTMLElement<`u`?HTMLElement:class{},o=class extends a{canvas=null;img=null;container=null;blurhash=null;hasRenderedBlurhash=!1;static get observedAttributes(){return[`src`,`alt`,`lazy`,`priority`]}connectedCallback(){this.render()}disconnectedCallback(){i().unobserve(this)}attributeChangedCallback(){this.container&&(this.hasRenderedBlurhash=!1,this.render())}renderBlurhashNow(){this.hasRenderedBlurhash||!this.blurhash||!this.canvas||(this.renderBlurhashToCanvas(this.blurhash),this.hasRenderedBlurhash=!0)}render(){let r=this.getAttribute(`src`);if(!r)return;let a=e.s(r);if(!a){this.renderFallbackImage(r);return}let{fields:o}=a,{blurhash:s,sourceWidth:c,sourceHeight:l,bcc:u}=o,d=this.getAttribute(`priority`)===`true`,f=!d&&this.getAttribute(`lazy`)!==`false`;this.blurhash=s||null;let p=c/l;if(this.container=document.createElement(`div`),this.container.style.cssText=`
2
+ position: relative;
3
+ width: 100%;
4
+ aspect-ratio: ${p};
5
+ overflow: hidden;
6
+ `,u){let r=n(u);r&&(this.container.style.backgroundColor=t(r,e.o))}s&&(this.canvas=document.createElement(`canvas`),this.canvas.width=24,this.canvas.height=24,this.canvas.style.cssText=`
7
+ position: absolute;
8
+ inset: 0;
9
+ width: 100%;
10
+ height: 100%;
11
+ `,this.container.appendChild(this.canvas),f?i().observe(this):(this.renderBlurhashToCanvas(s),this.hasRenderedBlurhash=!0)),this.img=document.createElement(`img`),this.img.src=r,this.img.alt=this.getAttribute(`alt`)||``,this.img.loading=f?`lazy`:`eager`,d&&(this.img.fetchPriority=`high`),this.img.style.cssText=`
12
+ position: absolute;
13
+ inset: 0;
14
+ width: 100%;
15
+ height: 100%;
16
+ object-fit: cover;
17
+ `,this.img.onload=()=>{this.canvas&&(this.canvas.style.opacity=`0`,this.canvas.style.transition=`opacity 0.3s ease-out`)},this.container.appendChild(this.img),this.innerHTML=``,this.appendChild(this.container)}renderBlurhashToCanvas(t){if(!this.canvas)return;let n=this.canvas.getContext(`2d`);if(!n)return;let r=e.n(),i=`${t}:24:1`,a=r.get(i);if(!a)try{a=e.c(t,24,24,1),r.set(i,a)}catch(e){console.warn(`Blurhash decode error:`,e);return}let o=n.createImageData(24,24);o.data.set(a),n.putImageData(o,0,0)}renderFallbackImage(e){this.innerHTML=``;let t=document.createElement(`img`);t.src=e,t.alt=this.getAttribute(`alt`)||``;let n=this.getAttribute(`priority`)===`true`;t.loading=n||this.getAttribute(`lazy`)===`false`?`eager`:`lazy`,n&&(t.fetchPriority=`high`),t.style.cssText=`width: 100%; height: auto;`,this.appendChild(t)}};function s(e=`visionary-image`){typeof HTMLElement>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,o)}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return o}});
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./canvas-CnN_nKut.cjs");exports.getOrDecodePixels=e.t,exports.getPixelCache=e.n,exports.getRenderedCanvasSet=e.r,exports.initBlurhashCanvases=e.i,exports.initOnDOMLoaded=e.a;
@@ -0,0 +1,50 @@
1
+ /** Get or decode blurhash pixels, using cache when available */
2
+ export declare function getOrDecodePixels(blurhash: string, size: number, punch: number): Uint8ClampedArray | null;
3
+
4
+ /** Returns the global pixel cache */
5
+ export declare function getPixelCache(): Map<string, Uint8ClampedArray>;
6
+
7
+ /** Returns the global set tracking rendered canvas keys */
8
+ export declare function getRenderedCanvasSet(): Set<string>;
9
+
10
+ /**
11
+ * Finds server-rendered canvas elements and renders blurhash to them.
12
+ * Uses IntersectionObserver: above-fold renders immediately, below-fold defers.
13
+ *
14
+ * Exported via `visionary-image/blurhash` for manual control:
15
+ * ```js
16
+ * import { initBlurhashCanvases } from 'visionary-image/blurhash';
17
+ * document.addEventListener('DOMContentLoaded', () => initBlurhashCanvases());
18
+ * ```
19
+ */
20
+ export declare function initBlurhashCanvases(debug?: boolean): void;
21
+
22
+ /**
23
+ * Runs initBlurhashCanvases() at DOMContentLoaded (or immediately if already loaded).
24
+ * Called by the loader entry point (`visionary-image/loader`).
25
+ *
26
+ * Usage:
27
+ * ```js
28
+ * import 'visionary-image/loader';
29
+ * ```
30
+ */
31
+ export declare function initOnDOMLoaded(debug?: boolean): void;
32
+
33
+ export { }
34
+
35
+ /**
36
+ * Blurhash rendering utilities shared across entry points:
37
+ *
38
+ * - `visionary-image` (React component) - uses getRenderedCanvasSet(), getOrDecodePixels()
39
+ * - `visionary-image/loader` (early loader) - calls initOnDOMLoaded()
40
+ * - `visionary-image/blurhash` (manual) - exports initBlurhashCanvases()
41
+ * - `visionary-image/web-component` - uses getPixelCache()
42
+ */
43
+ declare global {
44
+ interface Window {
45
+ /** Set of canvas keys that have been rendered */
46
+ V7Y_CANVAS_RENDERED?: Set<string>;
47
+ /** Cache of decoded blurhash pixels, keyed by "blurhash:size:punch" */
48
+ V7Y_PIXEL_CACHE?: Map<string, Uint8ClampedArray>;
49
+ }
50
+ }
@@ -0,0 +1,50 @@
1
+ /** Get or decode blurhash pixels, using cache when available */
2
+ export declare function getOrDecodePixels(blurhash: string, size: number, punch: number): Uint8ClampedArray | null;
3
+
4
+ /** Returns the global pixel cache */
5
+ export declare function getPixelCache(): Map<string, Uint8ClampedArray>;
6
+
7
+ /** Returns the global set tracking rendered canvas keys */
8
+ export declare function getRenderedCanvasSet(): Set<string>;
9
+
10
+ /**
11
+ * Finds server-rendered canvas elements and renders blurhash to them.
12
+ * Uses IntersectionObserver: above-fold renders immediately, below-fold defers.
13
+ *
14
+ * Exported via `visionary-image/blurhash` for manual control:
15
+ * ```js
16
+ * import { initBlurhashCanvases } from 'visionary-image/blurhash';
17
+ * document.addEventListener('DOMContentLoaded', () => initBlurhashCanvases());
18
+ * ```
19
+ */
20
+ export declare function initBlurhashCanvases(debug?: boolean): void;
21
+
22
+ /**
23
+ * Runs initBlurhashCanvases() at DOMContentLoaded (or immediately if already loaded).
24
+ * Called by the loader entry point (`visionary-image/loader`).
25
+ *
26
+ * Usage:
27
+ * ```js
28
+ * import 'visionary-image/loader';
29
+ * ```
30
+ */
31
+ export declare function initOnDOMLoaded(debug?: boolean): void;
32
+
33
+ export { }
34
+
35
+ /**
36
+ * Blurhash rendering utilities shared across entry points:
37
+ *
38
+ * - `visionary-image` (React component) - uses getRenderedCanvasSet(), getOrDecodePixels()
39
+ * - `visionary-image/loader` (early loader) - calls initOnDOMLoaded()
40
+ * - `visionary-image/blurhash` (manual) - exports initBlurhashCanvases()
41
+ * - `visionary-image/web-component` - uses getPixelCache()
42
+ */
43
+ declare global {
44
+ interface Window {
45
+ /** Set of canvas keys that have been rendered */
46
+ V7Y_CANVAS_RENDERED?: Set<string>;
47
+ /** Cache of decoded blurhash pixels, keyed by "blurhash:size:punch" */
48
+ V7Y_PIXEL_CACHE?: Map<string, Uint8ClampedArray>;
49
+ }
50
+ }
@@ -0,0 +1,2 @@
1
+ import { a as e, i as t, n, r, t as i } from "./canvas-D7cApRRm.js";
2
+ export { i as getOrDecodePixels, n as getPixelCache, r as getRenderedCanvasSet, t as initBlurhashCanvases, e as initOnDOMLoaded };
@@ -0,0 +1 @@
1
+ var e=(e,t,n)=>{let r=0;for(;t<n;)r=r*83+`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~`.indexOf(e[t++]);return r},t=Math.pow,n=Math.PI,r=3294.6,i=269.025,a=e=>e>10.31475?t(e/i+.052132,2.4):e/r,o=e=>~~(e>1227e-8?i*t(e,.416666)-13.025:e*r+1),s=4096,c=new Uint8ClampedArray(4097);for(let e=0;e<=s;e++)c[e]=o(e/s);var l=e=>e<=0?0:e>=1?255:c[e*s+.5|0],u=e=>(e<0?-1:1)*e*e,d=e=>{for(e+=n/2;e>n;)e-=n*2;let t=1.27323954*e-.405284735*u(e);return .225*(u(t)-t)+t};function f(t){let n=e(t,2,6);return[n>>16,n>>8&255,n&255]}function p(t,r,i,o){let s=e(t,0,1),c=s%9+1,p=~~(s/9)+1,m=c*p,h,g,_,v,y,b,x,S,C,w,T,E,D=(e(t,1,2)+1)/13446*(o|1),O=new Float64Array(m*3),k=f(t);for(h=0;h<3;h++)O[h]=a(k[h]);for(h=1;h<m;h++)E=e(t,4+h*2,6+h*2),O[h*3]=u(~~(E/361)-9)*D,O[h*3+1]=u(~~(E/19)%19-9)*D,O[h*3+2]=u(E%19-9)*D;let A=new Float64Array(p*i),j=new Float64Array(c*r);for(g=0;g<p;g++)for(v=0;v<i;v++)A[g*i+v]=d(n*v*g/i);for(h=0;h<c;h++)for(_=0;_<r;_++)j[h*r+_]=d(n*_*h/r);let M=r*4,N=new Uint8ClampedArray(M*i),P=new Float64Array(c*3);for(v=0;v<i;v++){for(h=0;h<c;h++){for(y=b=x=0,g=0;g<p;g++)C=A[g*i+v],w=(h+g*c)*3,y+=O[w]*C,b+=O[w+1]*C,x+=O[w+2]*C;P[h*3]=y,P[h*3+1]=b,P[h*3+2]=x}for(_=0;_<r;_++){for(y=b=x=0,h=0;h<c;h++)S=j[h*r+_],w=h*3,y+=P[w]*S,b+=P[w+1]*S,x+=P[w+2]*S;T=4*_+v*M,N[T]=l(y),N[T+1]=l(b),N[T+2]=l(x),N[T+3]=255}}return N}var m=(e=>(e.AUTO=`auto`,e.AVIF=`avif`,e.JPEG=`jpeg`,e.WEBP=`webp`,e))(m||{}),h=(e=>(e.xs=`xs`,e.sm=`sm`,e.md=`md`,e.lg=`lg`,e.xl=`xl`,e.xxl=`xxl`,e[`4k`]=`4k`,e[`5k`]=`5k`,e.full=`full`,e))(h||{}),g=(e=>(e.DEBUG=`debug`,e.DOWNLOAD=`download`,e.FOLLOW=`follow`,e))(g||{});m.AUTO,h.lg;var _={[h.xs]:160,[h.sm]:320,[h.md]:640,[h.lg]:1280,[h.xl]:1920,[h.xxl]:2560,[h[`4k`]]:3840,[h[`5k`]]:5120,[h.full]:0};Object.keys(_).reduce((e,t)=>{let n=t,r=_[n];return r&&(e[r]=n),e},{});var v=null,y=()=>{if(typeof Buffer<`u`&&typeof Buffer.from==`function`)return`buffer`;if(typeof TextEncoder<`u`&&typeof TextDecoder<`u`&&typeof btoa==`function`&&typeof atob==`function`)return`text-encoding`;throw Error(`No UTF-8 base64 implementation available`)},b=()=>(v===null&&(v=y()),v),x=e=>{let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);return t},S=e=>{let t=e.length%4;return t===0?e:e+`=`.repeat(4-t)},C=e=>Buffer.from(e,`base64`).toString(`utf8`),w=e=>new TextDecoder().decode(x(atob(S(e)))),T=e=>{switch(b()){case`buffer`:return C(e);case`text-encoding`:return w(e)}},E=e=>{if(typeof e!=`string`)throw Error(`decodeBase64Url: input must be a string`);return T(D(e))},D=e=>e.replace(/-/g,`+`).replace(/_/g,`/`),O=class extends Error{message=`invalid blurhash component dimensions`},k=e=>e===g.DEBUG,A=e=>e===g.DOWNLOAD,j=e=>e===g.FOLLOW,M=e=>Object.values(m).includes(e),N=e=>Object.values(h).includes(e),P=(e=[])=>{let t={};for(let n of e)N(n)?t.size=n:k(n)?t.debug=!0:A(n)?t.download=!0:j(n)?t.follow=!0:M(n)&&(t.format=n);return t},F=e=>e.filter(Boolean),I=(e=``)=>/^[A-Za-z0-9_-]*$/.test(e),L=e=>Number.isInteger(e)&&e>0,R=e=>{if(!e||e.length<6)throw new O;let t=z(e[0]);if(t<0)throw new O;let n=t%9+1,r=Math.floor(t/9)+1,i=4+2*n*r;if(e.length!==i)throw new O;return{xComponents:n,yComponents:r}},z=e=>`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~`.indexOf(e),B=e=>{if(typeof e!=`string`)return null;let t=e.trim();if(!t.length||!I(t))return null;let n=E(t);if(!n)return null;let r=n.split(`!`);if(r.length<3||r.length>6)return null;let[i,a,o,s,c,l]=r,u=i.trim();if(!u.length)return null;let d=Number(a.trim()),f=Number(o.trim());if(!L(d)||!L(f))return null;let p={sourceHeight:f,sourceWidth:d,url:u};if(s&&(p.bcc=s),c){p.blurhash=c;try{let e=R(c);p.blurhashX=e.xComponents,p.blurhashY=e.yComponents}catch{return null}}return l&&(p.altText=l),p},V=e=>{if(I(e)){let t=B(e);if(t)return{fields:t,options:{}}}return H(e)},H=e=>{let t=e?.trim();if(!t)return null;try{let e=U(t);if(!e)return null;let{code:n,optionTokens:r}=e,i=B(n);return i?{fields:i,options:P(r)}:null}catch{}return null},U=e=>{try{let t=F(new URL(e).pathname.split(`/`));if(t[0]!==`image`||![3,4].includes(t.length))throw Error(`Unrecognized URL`);let n=t[1].trim();if(!n.length||!I(n))throw Error(`URL is not formatted as base64url`);return t.length===4?{code:n,optionTokens:t[2].split(`,`)}:{code:n,optionTokens:[]}}catch{return null}return null},W=.7;h.lg,process.env.NODE_ENV;function G(){return typeof window>`u`?new Set:(window.V7Y_CANVAS_RENDERED||(window.V7Y_CANVAS_RENDERED=new Set),window.V7Y_CANVAS_RENDERED)}function K(){return typeof window>`u`?new Map:(window.V7Y_PIXEL_CACHE||(window.V7Y_PIXEL_CACHE=new Map),window.V7Y_PIXEL_CACHE)}function q(e,t,n){return`${e}:${t}:${n}`}function J(e,t,n){let r=q(e,t,n),i=K(),a=i.get(r);if(a)return a;try{let a=p(e,t,t,n);return i.set(r,a),a}catch(e){return console.warn(`Blurhash decode error:`,e),null}}var Y=null,X=!1;function Z(){return Y||=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){let t=e.target;Q(t,X),Y?.unobserve(t)}})},{rootMargin:`200px`}),Y}function Q(e,t){let n=e.dataset.v7yKey;if(!n)return;let r=G();if(r.has(n)){t&&console.log(`[visionary-loader] Canvas already rendered, skipping`);return}let i=e.parentElement?.querySelector(`img`);if(!i?.src){t&&console.log(`[visionary-loader] Canvas has no sibling img, skipping`);return}let a=V(i.src);if(!a?.fields?.blurhash){t&&console.log(`[visionary-loader] Sibling img is not a Blurhash URL, skipping`);return}let{blurhash:o}=a.fields;t&&console.log(`[visionary-loader] Rendering canvas:`,{blurhash:o.slice(0,20)+`...`,punch:1,size:24});let s=J(o,24,1);if(!s)return;let c=e.getContext(`2d`);if(!c)return;let l=c.createImageData(24,24);l.data.set(s),c.putImageData(l,0,0),r.add(n),t&&console.log(`[visionary-loader] Canvas rendered at ${performance.now().toFixed(1)}ms`)}function $(e=!1){X=e;let t=document.querySelectorAll(`canvas[data-v7y-key]`);e&&console.log(`[visionary-loader] Found ${t.length} canvas(es) with data-v7y-key`);let n=Z();t.forEach(e=>{n.observe(e)})}function ee(e=!1){if(typeof document>`u`)return;let t=()=>{e&&console.log(`[visionary-loader] initOnDOMLoaded running at ${performance.now().toFixed(1)}ms, readyState: ${document.readyState}`),$(e)};document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,t):t()}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return ee}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return $}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return J}});
@@ -0,0 +1,245 @@
1
+ //#region node_modules/.pnpm/fast-blurhash@1.2.0/node_modules/fast-blurhash/index.js
2
+ var e = (e, t, n) => {
3
+ let r = 0;
4
+ for (; t < n;) r = r * 83 + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(e[t++]);
5
+ return r;
6
+ }, t = Math.pow, n = Math.PI, r = 3294.6, i = 269.025, a = (e) => e > 10.31475 ? t(e / i + .052132, 2.4) : e / r, o = (e) => ~~(e > 1227e-8 ? i * t(e, .416666) - 13.025 : e * r + 1), s = 4096, c = /* @__PURE__ */ new Uint8ClampedArray(4097);
7
+ for (let e = 0; e <= s; e++) c[e] = o(e / s);
8
+ var l = (e) => e <= 0 ? 0 : e >= 1 ? 255 : c[e * s + .5 | 0], u = (e) => (e < 0 ? -1 : 1) * e * e, d = (e) => {
9
+ for (e += n / 2; e > n;) e -= n * 2;
10
+ let t = 1.27323954 * e - .405284735 * u(e);
11
+ return .225 * (u(t) - t) + t;
12
+ };
13
+ function f(t) {
14
+ let n = e(t, 2, 6);
15
+ return [
16
+ n >> 16,
17
+ n >> 8 & 255,
18
+ n & 255
19
+ ];
20
+ }
21
+ function p(t, r, i, o) {
22
+ let s = e(t, 0, 1), c = s % 9 + 1, p = ~~(s / 9) + 1, m = c * p, h, g, _, v, y, b, x, S, C, w, T, E, D = (e(t, 1, 2) + 1) / 13446 * (o | 1), O = new Float64Array(m * 3), k = f(t);
23
+ for (h = 0; h < 3; h++) O[h] = a(k[h]);
24
+ for (h = 1; h < m; h++) E = e(t, 4 + h * 2, 6 + h * 2), O[h * 3] = u(~~(E / 361) - 9) * D, O[h * 3 + 1] = u(~~(E / 19) % 19 - 9) * D, O[h * 3 + 2] = u(E % 19 - 9) * D;
25
+ let A = new Float64Array(p * i), j = new Float64Array(c * r);
26
+ for (g = 0; g < p; g++) for (v = 0; v < i; v++) A[g * i + v] = d(n * v * g / i);
27
+ for (h = 0; h < c; h++) for (_ = 0; _ < r; _++) j[h * r + _] = d(n * _ * h / r);
28
+ let M = r * 4, N = new Uint8ClampedArray(M * i), P = new Float64Array(c * 3);
29
+ for (v = 0; v < i; v++) {
30
+ for (h = 0; h < c; h++) {
31
+ for (y = b = x = 0, g = 0; g < p; g++) C = A[g * i + v], w = (h + g * c) * 3, y += O[w] * C, b += O[w + 1] * C, x += O[w + 2] * C;
32
+ P[h * 3] = y, P[h * 3 + 1] = b, P[h * 3 + 2] = x;
33
+ }
34
+ for (_ = 0; _ < r; _++) {
35
+ for (y = b = x = 0, h = 0; h < c; h++) S = j[h * r + _], w = h * 3, y += P[w] * S, b += P[w + 1] * S, x += P[w + 2] * S;
36
+ T = 4 * _ + v * M, N[T] = l(y), N[T + 1] = l(b), N[T + 2] = l(x), N[T + 3] = 255;
37
+ }
38
+ }
39
+ return N;
40
+ }
41
+ //#endregion
42
+ //#region node_modules/.pnpm/blurhash-url@1.4.0/node_modules/blurhash-url/dist/constants.js
43
+ var m = /* @__PURE__ */ ((e) => (e.AUTO = "auto", e.AVIF = "avif", e.JPEG = "jpeg", e.WEBP = "webp", e))(m || {}), h = /* @__PURE__ */ ((e) => (e.xs = "xs", e.sm = "sm", e.md = "md", e.lg = "lg", e.xl = "xl", e.xxl = "xxl", e["4k"] = "4k", e["5k"] = "5k", e.full = "full", e))(h || {}), g = /* @__PURE__ */ ((e) => (e.DEBUG = "debug", e.DOWNLOAD = "download", e.FOLLOW = "follow", e))(g || {});
44
+ m.AUTO, h.lg;
45
+ var _ = {
46
+ [h.xs]: 160,
47
+ [h.sm]: 320,
48
+ [h.md]: 640,
49
+ [h.lg]: 1280,
50
+ [h.xl]: 1920,
51
+ [h.xxl]: 2560,
52
+ [h["4k"]]: 3840,
53
+ [h["5k"]]: 5120,
54
+ [h.full]: 0
55
+ };
56
+ Object.keys(_).reduce((e, t) => {
57
+ let n = t, r = _[n];
58
+ return r && (e[r] = n), e;
59
+ }, {});
60
+ //#endregion
61
+ //#region node_modules/.pnpm/visionary-base64url@0.1.4/node_modules/visionary-base64url/dist/index.js
62
+ var v = null, y = () => {
63
+ if (typeof Buffer < "u" && typeof Buffer.from == "function") return "buffer";
64
+ if (typeof TextEncoder < "u" && typeof TextDecoder < "u" && typeof btoa == "function" && typeof atob == "function") return "text-encoding";
65
+ throw Error("No UTF-8 base64 implementation available");
66
+ }, b = () => (v === null && (v = y()), v), x = (e) => {
67
+ let t = new Uint8Array(e.length);
68
+ for (let n = 0; n < e.length; n++) t[n] = e.charCodeAt(n);
69
+ return t;
70
+ }, S = (e) => {
71
+ let t = e.length % 4;
72
+ return t === 0 ? e : e + "=".repeat(4 - t);
73
+ }, C = (e) => Buffer.from(e, "base64").toString("utf8"), w = (e) => new TextDecoder().decode(x(atob(S(e)))), T = (e) => {
74
+ switch (b()) {
75
+ case "buffer": return C(e);
76
+ case "text-encoding": return w(e);
77
+ }
78
+ }, E = (e) => {
79
+ if (typeof e != "string") throw Error("decodeBase64Url: input must be a string");
80
+ return T(D(e));
81
+ }, D = (e) => e.replace(/-/g, "+").replace(/_/g, "/"), O = class extends Error {
82
+ message = "invalid blurhash component dimensions";
83
+ }, k = (e) => e === g.DEBUG, A = (e) => e === g.DOWNLOAD, j = (e) => e === g.FOLLOW, M = (e) => Object.values(m).includes(e), N = (e) => Object.values(h).includes(e), P = (e = []) => {
84
+ let t = {};
85
+ for (let n of e) N(n) ? t.size = n : k(n) ? t.debug = !0 : A(n) ? t.download = !0 : j(n) ? t.follow = !0 : M(n) && (t.format = n);
86
+ return t;
87
+ }, F = (e) => e.filter(Boolean), I = (e = "") => /^[A-Za-z0-9_-]*$/.test(e), L = (e) => Number.isInteger(e) && e > 0, R = (e) => {
88
+ if (!e || e.length < 6) throw new O();
89
+ let t = z(e[0]);
90
+ if (t < 0) throw new O();
91
+ let n = t % 9 + 1, r = Math.floor(t / 9) + 1, i = 4 + 2 * n * r;
92
+ if (e.length !== i) throw new O();
93
+ return {
94
+ xComponents: n,
95
+ yComponents: r
96
+ };
97
+ }, z = (e) => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(e), B = (e) => {
98
+ if (typeof e != "string") return null;
99
+ let t = e.trim();
100
+ if (!t.length || !I(t)) return null;
101
+ let n = E(t);
102
+ if (!n) return null;
103
+ let r = n.split("!");
104
+ if (r.length < 3 || r.length > 6) return null;
105
+ let [i, a, o, s, c, l] = r, u = i.trim();
106
+ if (!u.length) return null;
107
+ let d = Number(a.trim()), f = Number(o.trim());
108
+ if (!L(d) || !L(f)) return null;
109
+ let p = {
110
+ sourceHeight: f,
111
+ sourceWidth: d,
112
+ url: u
113
+ };
114
+ if (s && (p.bcc = s), c) {
115
+ p.blurhash = c;
116
+ try {
117
+ let e = R(c);
118
+ p.blurhashX = e.xComponents, p.blurhashY = e.yComponents;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+ return l && (p.altText = l), p;
124
+ }, V = (e) => {
125
+ if (I(e)) {
126
+ let t = B(e);
127
+ if (t) return {
128
+ fields: t,
129
+ options: {}
130
+ };
131
+ }
132
+ return H(e);
133
+ }, H = (e) => {
134
+ let t = e?.trim();
135
+ if (!t) return null;
136
+ try {
137
+ let e = U(t);
138
+ if (!e) return null;
139
+ let { code: n, optionTokens: r } = e, i = B(n);
140
+ return i ? {
141
+ fields: i,
142
+ options: P(r)
143
+ } : null;
144
+ } catch {}
145
+ return null;
146
+ }, U = (e) => {
147
+ try {
148
+ let t = F(new URL(e).pathname.split("/"));
149
+ if (t[0] !== "image" || ![3, 4].includes(t.length)) throw Error("Unrecognized URL");
150
+ let n = t[1].trim();
151
+ if (!n.length || !I(n)) throw Error("URL is not formatted as base64url");
152
+ return t.length === 4 ? {
153
+ code: n,
154
+ optionTokens: t[2].split(",")
155
+ } : {
156
+ code: n,
157
+ optionTokens: []
158
+ };
159
+ } catch {
160
+ return null;
161
+ }
162
+ return null;
163
+ }, W = .7;
164
+ h.lg, process.env.NODE_ENV;
165
+ //#endregion
166
+ //#region src/lib/canvas.ts
167
+ function G() {
168
+ return typeof window > "u" ? /* @__PURE__ */ new Set() : (window.V7Y_CANVAS_RENDERED || (window.V7Y_CANVAS_RENDERED = /* @__PURE__ */ new Set()), window.V7Y_CANVAS_RENDERED);
169
+ }
170
+ function K() {
171
+ return typeof window > "u" ? /* @__PURE__ */ new Map() : (window.V7Y_PIXEL_CACHE || (window.V7Y_PIXEL_CACHE = /* @__PURE__ */ new Map()), window.V7Y_PIXEL_CACHE);
172
+ }
173
+ function q(e, t, n) {
174
+ return `${e}:${t}:${n}`;
175
+ }
176
+ function J(e, t, n) {
177
+ let r = q(e, t, n), i = K(), a = i.get(r);
178
+ if (a) return a;
179
+ try {
180
+ let a = p(e, t, t, n);
181
+ return i.set(r, a), a;
182
+ } catch (e) {
183
+ return console.warn("Blurhash decode error:", e), null;
184
+ }
185
+ }
186
+ var Y = null, X = !1;
187
+ function Z() {
188
+ return Y ||= new IntersectionObserver((e) => {
189
+ e.forEach((e) => {
190
+ if (e.isIntersecting) {
191
+ let t = e.target;
192
+ Q(t, X), Y?.unobserve(t);
193
+ }
194
+ });
195
+ }, { rootMargin: "200px" }), Y;
196
+ }
197
+ function Q(e, t) {
198
+ let n = e.dataset.v7yKey;
199
+ if (!n) return;
200
+ let r = G();
201
+ if (r.has(n)) {
202
+ t && console.log("[visionary-loader] Canvas already rendered, skipping");
203
+ return;
204
+ }
205
+ let i = e.parentElement?.querySelector("img");
206
+ if (!i?.src) {
207
+ t && console.log("[visionary-loader] Canvas has no sibling img, skipping");
208
+ return;
209
+ }
210
+ let a = V(i.src);
211
+ if (!a?.fields?.blurhash) {
212
+ t && console.log("[visionary-loader] Sibling img is not a Blurhash URL, skipping");
213
+ return;
214
+ }
215
+ let { blurhash: o } = a.fields;
216
+ t && console.log("[visionary-loader] Rendering canvas:", {
217
+ blurhash: o.slice(0, 20) + "...",
218
+ punch: 1,
219
+ size: 24
220
+ });
221
+ let s = J(o, 24, 1);
222
+ if (!s) return;
223
+ let c = e.getContext("2d");
224
+ if (!c) return;
225
+ let l = c.createImageData(24, 24);
226
+ l.data.set(s), c.putImageData(l, 0, 0), r.add(n), t && console.log(`[visionary-loader] Canvas rendered at ${performance.now().toFixed(1)}ms`);
227
+ }
228
+ function $(e = !1) {
229
+ X = e;
230
+ let t = document.querySelectorAll("canvas[data-v7y-key]");
231
+ e && console.log(`[visionary-loader] Found ${t.length} canvas(es) with data-v7y-key`);
232
+ let n = Z();
233
+ t.forEach((e) => {
234
+ n.observe(e);
235
+ });
236
+ }
237
+ function ee(e = !1) {
238
+ if (typeof document > "u") return;
239
+ let t = () => {
240
+ e && console.log(`[visionary-loader] initOnDOMLoaded running at ${performance.now().toFixed(1)}ms, readyState: ${document.readyState}`), $(e);
241
+ };
242
+ document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", t) : t();
243
+ }
244
+ //#endregion
245
+ export { ee as a, p as c, $ as i, K as n, W as o, G as r, V as s, J as t };