@liforma/client 0.4.13 → 0.4.14

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +4 -4
  3. package/dist/next/index.js +687 -618
  4. package/dist/next/index.js.map +1 -1
  5. package/dist/next/react/ExperienceThumbnail.d.ts.map +1 -1
  6. package/dist/next/thumbnail/index.d.ts +1 -0
  7. package/dist/next/thumbnail/index.d.ts.map +1 -1
  8. package/dist/next/thumbnail/locationPlate.d.ts +3 -3
  9. package/dist/next/thumbnail/locationPlate.d.ts.map +1 -1
  10. package/dist/next/thumbnail/plateDelivery.d.ts +18 -0
  11. package/dist/next/thumbnail/plateDelivery.d.ts.map +1 -0
  12. package/dist/next/thumbnail/plateDelivery.test.d.ts +2 -0
  13. package/dist/next/thumbnail/plateDelivery.test.d.ts.map +1 -0
  14. package/dist/next/thumbnail/toGalleryThumbUrl.d.ts +1 -1
  15. package/dist/next/thumbnail.js +147 -91
  16. package/dist/next/thumbnail.js.map +1 -1
  17. package/dist/next/v2/embed/iframeHost.d.ts.map +1 -1
  18. package/dist/next/v2/experience.d.ts +3 -0
  19. package/dist/next/v2/experience.d.ts.map +1 -1
  20. package/dist/next/v2/shims/embedBaseUrl.d.ts +4 -1
  21. package/dist/next/v2/shims/embedBaseUrl.d.ts.map +1 -1
  22. package/dist/npm/index.js +23 -10
  23. package/dist/npm/index.js.map +1 -1
  24. package/dist/npm/thumbnail/index.d.ts +1 -0
  25. package/dist/npm/thumbnail/index.d.ts.map +1 -1
  26. package/dist/npm/thumbnail/index.js +106 -70
  27. package/dist/npm/thumbnail/index.js.map +1 -1
  28. package/dist/npm/thumbnail/locationPlate.d.ts +3 -3
  29. package/dist/npm/thumbnail/locationPlate.d.ts.map +1 -1
  30. package/dist/npm/thumbnail/plateDelivery.d.ts +18 -0
  31. package/dist/npm/thumbnail/plateDelivery.d.ts.map +1 -0
  32. package/dist/npm/thumbnail/toGalleryThumbUrl.d.ts +1 -1
  33. package/dist/npm/v2/embed/iframeHost.d.ts.map +1 -1
  34. package/dist/npm/v2/experience.d.ts +3 -0
  35. package/dist/npm/v2/experience.d.ts.map +1 -1
  36. package/dist/npm/v2/liforma-experience-thumbnail.d.ts.map +1 -1
  37. package/dist/npm/v2/shims/embedBaseUrl.d.ts +4 -1
  38. package/dist/npm/v2/shims/embedBaseUrl.d.ts.map +1 -1
  39. package/dist/react/component/experienceComponentLaunch.d.ts.map +1 -1
  40. package/dist/react/component/experienceComponentTypes.d.ts +3 -0
  41. package/dist/react/component/experienceComponentTypes.d.ts.map +1 -1
  42. package/dist/react/index.js +693 -618
  43. package/dist/react/index.js.map +1 -1
  44. package/dist/react/react/ExperienceThumbnail.d.ts.map +1 -1
  45. package/dist/react/thumbnail/index.d.ts +1 -0
  46. package/dist/react/thumbnail/index.d.ts.map +1 -1
  47. package/dist/react/thumbnail/locationPlate.d.ts +3 -3
  48. package/dist/react/thumbnail/locationPlate.d.ts.map +1 -1
  49. package/dist/react/thumbnail/plateDelivery.d.ts +18 -0
  50. package/dist/react/thumbnail/plateDelivery.d.ts.map +1 -0
  51. package/dist/react/thumbnail/plateDelivery.test.d.ts +2 -0
  52. package/dist/react/thumbnail/plateDelivery.test.d.ts.map +1 -0
  53. package/dist/react/thumbnail/toGalleryThumbUrl.d.ts +1 -1
  54. package/dist/react/thumbnail.js +147 -91
  55. package/dist/react/thumbnail.js.map +1 -1
  56. package/dist/react/v2/embed/iframeHost.d.ts.map +1 -1
  57. package/dist/react/v2/experience.d.ts +3 -0
  58. package/dist/react/v2/experience.d.ts.map +1 -1
  59. package/dist/react/v2/shims/embedBaseUrl.d.ts +4 -1
  60. package/dist/react/v2/shims/embedBaseUrl.d.ts.map +1 -1
  61. package/dist/sdk/v2/client.js +2 -2
  62. package/dist/sdk/v2/client.js.map +1 -1
  63. package/dist/sdk/v2/thumbnail.js +2 -2
  64. package/dist/sdk/v2/thumbnail.js.map +1 -1
  65. package/dist/svelte/Experience.svelte +8 -0
  66. package/dist/svelte/ExperienceThumbnail.svelte +49 -8
  67. package/package.json +4 -1
@@ -1,4 +1,4 @@
1
- (function(){function e(e){return e===`face`?`liforma-et--fit-face`:e===`medium`?`liforma-et--fit-medium`:``}var t=/(-)(\d+x\d+)(\.webp)(\?|$)/i;function n(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||e<1||t<1||e<=t?`256x256`:e>800||t>450?`1672x941`:`800x450`}function r(e,n){let r=e.trim();return r&&r.replace(t,(e,t,r,i,a)=>`${t}${n}${i}${a}`)}function i(e,t){let n=()=>{t(e.clientWidth,e.clientHeight)};if(n(),typeof ResizeObserver>`u`)return()=>{};let r=new ResizeObserver(n);return r.observe(e),()=>r.disconnect()}var a=/-(256x256|400x450|800x450|836x971|1672x941|167x94)$/i;function o(e){return e?.trim().replace(/\/$/,``)||`https://cdn.liforma.ai`}function s(e){return/^https?:\/\//i.test(e)||e.startsWith(`//`)}function c(e){return e.startsWith(`//`)?`https:${e}`:e}function l(e){let t=e.indexOf(`?`);return t===-1?{pathname:e,search:``}:{pathname:e.slice(0,t),search:e.slice(t)}}function u(e,t,n=``){return`${e}${t.startsWith(`/`)?t:`/${t}`}${n}`}function d(e,t,n){let i=e.trim();if(!i)return i;let d=o(n);if(s(i))return r(c(i),t);let{pathname:f,search:p}=l(i.replace(/\\/g,`/`)),m=f;if(!m.startsWith(`/`))m=`/locations/${m.replace(/^locations\//i,``)}`;else if(!/^\/locations\//i.test(m))return/^\/avatars\//i.test(m)?u(d,m,p):i;if(/\.webp$/i.test(m)&&!a.test(m.replace(/\.webp$/i,``)))return u(d,m,p);let h=m.replace(/^\/locations\//i,``).replace(/\.webp$/i,``);return h=h.replace(a,``),`${d}/locations/${h}-${t}.webp${p}`}function f(e,t){let n=e.trim();if(!n)return n;let r=o(t);if(s(n))return c(n);let{pathname:i,search:a}=l(n.replace(/\\/g,`/`)),d=i;if(!d.startsWith(`/`))if(/^avatars\//i.test(d))d=`/${d}`;else if(/^[^/]+$/.test(d)&&!a)return`${r}/avatars/${d}/256/${d}_neutral.webp`;else return n;if(!/^\/avatars\//i.test(d))return n;let f=d.replace(/^\/avatars\//i,``).replace(/\/+$/,``);return f&&!f.includes(`/`)&&!a?`${r}/avatars/${f}/256/${f}_neutral.webp`:u(r,d,a)}var p=/(\/avatars\/[^/]+\/)(\d+)(\/)/i,m=/_CH(\.webp)/i;function h(e){return/\/locations\//i.test(e)||/^locations\//i.test(e)?!0:/^https?:\/\//i.test(e)||e.startsWith(`//`)||/^\/?avatars\//i.test(e)||e.startsWith(`/`)?!1:e.includes(`/`)}function g(e,t=`256x256`,n){let r=e.trim();if(!r)return r;if(h(r))return d(r,t,n);let i=f(r,n);return i=i.replace(p,(e,t,n,r)=>n===`256`?`${t}${n}${r}`:`${t}256${r}`),i.replace(m,`_neutral$1`)}function _(e){let t=e.avatarImage?.trim()||e.galleryThumb?.avatarImage?.trim()||``;if(!t)return null;let n=e.backgroundImage?.trim()||e.galleryThumb?.backgroundImage?.trim()||``,r=e.foregroundImage?.trim()||e.galleryThumb?.foregroundImage?.trim()||``,i=e.fallbackImage?.trim()||``,a=e.locationPlateSize??`256x256`,s=o(e.cdnBaseUrl),c=e.normalize!==!1,l=e=>!c&&/^https?:\/\//i.test(e.trim())?e.trim():g(e,a,s),u=e=>!c&&/^https?:\/\//i.test(e.trim())?e.trim():d(e,a,s);return{avatarImage:l(t),...n?{backgroundImage:u(n)}:{},...r?{foregroundImage:u(r)}:{},...i?{fallbackImage:l(i)}:{}}}function v(){if(globalThis.__LIFORMA_STACK===`local`)return!0;if(typeof location>`u`)return!1;try{return new URLSearchParams(location.search).get(`stack`)===`local`}catch{return!1}}var y=`https://player.liforma.ai`,b=`http://localhost:3002`;function x(e){return e.href?.trim()?`anchor`:e.hasClickHandler?`button`:e.experienceId?.trim()?`anchor`:`presentational`}function S(e){let t=e?.trim();return t?t.replace(/\/$/,``):v()?b:y}function C(e){let t=e.experienceId.trim(),n=S(e.playerBaseUrl),r=new URL(`${n}/${encodeURIComponent(t)}`),i=e.returnUrl?.trim();return i&&r.searchParams.set(`returnUrl`,i),r.toString()}function w(e){let t=e.href?.trim();if(t)return t;let n=e.experienceId?.trim();if(!n)return;let r=e.returnUrl?.trim();return!r&&e.useCurrentPageAsReturnUrl&&typeof window<`u`&&(r=window.location.href),C({experienceId:n,...r?{returnUrl:r}:{},playerBaseUrl:e.playerBaseUrl})}function T(){return typeof window>`u`?!1:window.matchMedia(`(hover: none), (pointer: coarse)`).matches}var E=0,D=[];function O(){return T()?4:1/0}function k(){return T()?160:0}function A(){let e=O();for(;E<e&&D.length>0;){let e=D.shift();if(!e||e.cancelled)continue;E+=1,e.start();let t=k();if(t<=0){E=Math.max(0,E-1);continue}window.setTimeout(()=>{E=Math.max(0,E-1),A()},t)}}function j(e){if(!T())return e(),()=>{};let t={start:e,cancelled:!1};return D.push(t),A(),()=>{t.cancelled=!0}}function M(){return T()?`96px 0px`:`280px 0px`}function N(e){let t=e.trim().split(/\s+/)[0]??`0px`,n=Number.parseFloat(t);return Number.isFinite(n)?Math.abs(n):280}function P(e,t){if(typeof window>`u`)return!1;let n=e.getBoundingClientRect();return n.bottom>=-t&&n.top<=window.innerHeight+t}function F(e,t){let{onVisibleChange:n}=t,r=t.rootMargin??M(),i=N(r),a=!1,o=!1,s=null,c=()=>{o||(s?.(),s=j(()=>{s=null,o=!0,n(!0)}))},l=e=>{if(e!==a){if(a=e,e){c();return}s?.(),s=null,n(!1)}};if(typeof IntersectionObserver>`u`)return c(),()=>{s?.(),s=null};let u=new IntersectionObserver(e=>{l(e.some(e=>e.isIntersecting))},{root:null,rootMargin:r,threshold:0});return u.observe(e),P(e,i)&&l(!0),()=>{s?.(),s=null,u.disconnect()}}function I(e,t){let n=()=>t();return e.complete?queueMicrotask(n):(e.addEventListener(`load`,n,{once:!0}),e.addEventListener(`error`,n,{once:!0})),()=>{e.removeEventListener(`load`,n),e.removeEventListener(`error`,n)}}function L(){return typeof window>`u`||window.matchMedia(`(prefers-reduced-motion: reduce)`).matches?!1:window.matchMedia(`(hover: none), (pointer: coarse)`).matches}function R(e,t){return((e.top+e.height/2)/Math.max(t,1)-.5)*2}var z=new Set,B=!1,V=0;function H(){if(V=0,z.size===0||typeof window>`u`)return;let e=window.innerHeight;for(let t of z){if(t.pointerActive?.())continue;let n=t.element.getBoundingClientRect();n.bottom<-64||n.top>e+64||t.onParallax(R(n,e))}}function U(){V===0&&(V=requestAnimationFrame(H))}function W(){B||typeof window>`u`||(B=!0,window.addEventListener(`scroll`,U,{passive:!0}),window.addEventListener(`resize`,U,{passive:!0}))}function G(){z.size>0||!B||typeof window>`u`||(B=!1,V!==0&&(cancelAnimationFrame(V),V=0),window.removeEventListener(`scroll`,U),window.removeEventListener(`resize`,U))}function K(e,t){if(!L())return()=>{};let n={element:e,onParallax:t.onParallax,pointerActive:t.pointerActive};return z.add(n),W(),n.pointerActive?.()||t.onParallax(R(e.getBoundingClientRect(),window.innerHeight)),()=>{z.delete(n),G()}}var q=`:host {
1
+ (function(){function e(e){return e===`face`?`liforma-et--fit-face`:e===`medium`?`liforma-et--fit-medium`:``}var t=/(-)(\d+x\d+)(\.(?:webp|avif))(\?|$)/i;function n(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||e<1||t<1||e<=t?`256x256`:t<=225?`400x225`:e>800||t>450?`1672x941`:`800x450`}function r(e,n){let r=e.trim();return r&&r.replace(t,(e,t,r,i,a)=>`${t}${n}${i}${a}`)}function i(e,t){let n=()=>{t(e.clientWidth,e.clientHeight)};if(n(),typeof ResizeObserver>`u`)return()=>{};let r=new ResizeObserver(n);return r.observe(e),()=>r.disconnect()}var a=/-(256x256|400x225|400x450|800x450|836x971|1672x941|167x94)$/i;function o(e){return e?.trim().replace(/\/$/,``)||`https://cdn.liforma.ai`}function s(e){return/^https?:\/\//i.test(e)||e.startsWith(`//`)}function c(e){return e.startsWith(`//`)?`https:${e}`:e}function l(e){let t=e.indexOf(`?`);return t===-1?{pathname:e,search:``}:{pathname:e.slice(0,t),search:e.slice(t)}}function u(e,t,n=``){return`${e}${t.startsWith(`/`)?t:`/${t}`}${n}`}function d(e,t,n){let i=e.trim();if(!i)return i;let d=o(n);if(s(i))return r(c(i),t);let{pathname:f,search:p}=l(i.replace(/\\/g,`/`)),m=f;if(!m.startsWith(`/`))m=`/locations/${m.replace(/^locations\//i,``)}`;else if(!/^\/locations\//i.test(m))return/^\/avatars\//i.test(m)?u(d,m,p):i;if(/\.webp$/i.test(m)&&!a.test(m.replace(/\.webp$/i,``)))return u(d,m,p);let h=m.replace(/^\/locations\//i,``).replace(/\.webp$/i,``);return h=h.replace(a,``),`${d}/locations/${h}-${t}.webp${p}`}function f(e,t){let n=e.trim();if(!n)return n;let r=o(t);if(s(n))return c(n);let{pathname:i,search:a}=l(n.replace(/\\/g,`/`)),d=i;if(!d.startsWith(`/`))if(/^avatars\//i.test(d))d=`/${d}`;else if(/^[^/]+$/.test(d)&&!a)return`${r}/avatars/${d}/256/${d}_neutral.webp`;else return n;if(!/^\/avatars\//i.test(d))return n;let f=d.replace(/^\/avatars\//i,``).replace(/\/+$/,``);return f&&!f.includes(`/`)&&!a?`${r}/avatars/${f}/256/${f}_neutral.webp`:u(r,d,a)}var p=/(\/avatars\/[^/]+\/)(\d+)(\/)/i,m=/_CH(\.webp)/i;function h(e){return/\/locations\//i.test(e)||/^locations\//i.test(e)?!0:/^https?:\/\//i.test(e)||e.startsWith(`//`)||/^\/?avatars\//i.test(e)||e.startsWith(`/`)?!1:e.includes(`/`)}function g(e,t=`256x256`,n){let r=e.trim();if(!r)return r;if(h(r))return d(r,t,n);let i=f(r,n);return i=i.replace(p,(e,t,n,r)=>n===`256`?`${t}${n}${r}`:`${t}256${r}`),i.replace(m,`_neutral$1`)}function _(e){let t=e.avatarImage?.trim()||e.galleryThumb?.avatarImage?.trim()||``;if(!t)return null;let n=e.backgroundImage?.trim()||e.galleryThumb?.backgroundImage?.trim()||``,r=e.foregroundImage?.trim()||e.galleryThumb?.foregroundImage?.trim()||``,i=e.fallbackImage?.trim()||``,a=e.locationPlateSize??`256x256`,s=o(e.cdnBaseUrl),c=e.normalize!==!1,l=e=>!c&&/^https?:\/\//i.test(e.trim())?e.trim():g(e,a,s),u=e=>!c&&/^https?:\/\//i.test(e.trim())?e.trim():d(e,a,s);return{avatarImage:l(t),...n?{backgroundImage:u(n)}:{},...r?{foregroundImage:u(r)}:{},...i?{fallbackImage:l(i)}:{}}}function v(){if(globalThis.__LIFORMA_STACK===`local`)return!0;if(typeof location>`u`)return!1;try{return new URLSearchParams(location.search).get(`stack`)===`local`}catch{return!1}}var y=`https://player.liforma.ai`,b=`http://localhost:3002`;function x(e){return e.href?.trim()?`anchor`:e.hasClickHandler?`button`:e.experienceId?.trim()?`anchor`:`presentational`}function S(e){let t=e?.trim();return t?t.replace(/\/$/,``):v()?b:y}function C(e){let t=e.experienceId.trim(),n=S(e.playerBaseUrl),r=new URL(`${n}/${encodeURIComponent(t)}`),i=e.returnUrl?.trim();return i&&r.searchParams.set(`returnUrl`,i),r.toString()}function w(e){let t=e.href?.trim();if(t)return t;let n=e.experienceId?.trim();if(!n)return;let r=e.returnUrl?.trim();return!r&&e.useCurrentPageAsReturnUrl&&typeof window<`u`&&(r=window.location.href),C({experienceId:n,...r?{returnUrl:r}:{},playerBaseUrl:e.playerBaseUrl})}function T(){return typeof window>`u`?!1:window.matchMedia(`(hover: none), (pointer: coarse)`).matches}var E=0,D=[];function O(){return T()?4:1/0}function k(){return T()?160:0}function A(){let e=O();for(;E<e&&D.length>0;){let e=D.shift();if(!e||e.cancelled)continue;E+=1,e.start();let t=k();if(t<=0){E=Math.max(0,E-1);continue}window.setTimeout(()=>{E=Math.max(0,E-1),A()},t)}}function ee(e){if(!T())return e(),()=>{};let t={start:e,cancelled:!1};return D.push(t),A(),()=>{t.cancelled=!0}}function j(){return T()?`96px 0px`:`280px 0px`}function M(e){let t=e.trim().split(/\s+/)[0]??`0px`,n=Number.parseFloat(t);return Number.isFinite(n)?Math.abs(n):280}function N(e,t){if(typeof window>`u`)return!1;let n=e.getBoundingClientRect();return n.bottom>=-t&&n.top<=window.innerHeight+t}function P(e,t){let{onVisibleChange:n}=t,r=t.rootMargin??j(),i=M(r),a=!1,o=!1,s=null,c=()=>{o||(s?.(),s=ee(()=>{s=null,o=!0,n(!0)}))},l=e=>{if(e!==a){if(a=e,e){c();return}s?.(),s=null,n(!1)}};if(typeof IntersectionObserver>`u`)return c(),()=>{s?.(),s=null};let u=new IntersectionObserver(e=>{l(e.some(e=>e.isIntersecting))},{root:null,rootMargin:r,threshold:0});return u.observe(e),N(e,i)&&l(!0),()=>{s?.(),s=null,u.disconnect()}}function F(e,t){let n=()=>t();return e.complete?queueMicrotask(n):(e.addEventListener(`load`,n,{once:!0}),e.addEventListener(`error`,n,{once:!0})),()=>{e.removeEventListener(`load`,n),e.removeEventListener(`error`,n)}}function I(){return typeof window>`u`||window.matchMedia(`(prefers-reduced-motion: reduce)`).matches?!1:window.matchMedia(`(hover: none), (pointer: coarse)`).matches}function L(e,t){return((e.top+e.height/2)/Math.max(t,1)-.5)*2}var R=new Set,z=!1,B=0;function V(){if(B=0,R.size===0||typeof window>`u`)return;let e=window.innerHeight;for(let t of R){if(t.pointerActive?.())continue;let n=t.element.getBoundingClientRect();n.bottom<-64||n.top>e+64||t.onParallax(L(n,e))}}function H(){B===0&&(B=requestAnimationFrame(V))}function U(){z||typeof window>`u`||(z=!0,window.addEventListener(`scroll`,H,{passive:!0}),window.addEventListener(`resize`,H,{passive:!0}))}function W(){R.size>0||!z||typeof window>`u`||(z=!1,B!==0&&(cancelAnimationFrame(B),B=0),window.removeEventListener(`scroll`,H),window.removeEventListener(`resize`,H))}function G(e,t){if(!I())return()=>{};let n={element:e,onParallax:t.onParallax,pointerActive:t.pointerActive};return R.add(n),U(),n.pointerActive?.()||t.onParallax(L(e.getBoundingClientRect(),window.innerHeight)),()=>{R.delete(n),W()}}var K=`:host {
2
2
  display: block;
3
3
  width: 100%;
4
4
  height: 100%;
@@ -225,5 +225,5 @@ button.liforma-et {
225
225
  .liforma-et__avatar {
226
226
  transition: none;
227
227
  }
228
- }`,J=`liforma-experience-thumbnail`;function Y(e){if(e?.trim())try{let t=JSON.parse(e);if(t&&typeof t.avatarImage==`string`)return t}catch{}}var X=class extends HTMLElement{static get observedAttributes(){return[`experience-id`,`gallery-thumb`,`avatar-image`,`background-image`,`foreground-image`,`fallback-image`,`alt`,`href`,`target`,`rel`,`return-url`,`player-base-url`,`parallax`,`fit`,`lazy`,`normalize-urls`,`cdn-base-url`]}#e=null;#t=[];#n=!1;#r=!1;#i=!1;#a=!1;#o=0;#s=0;#c=!1;#l=!1;#u=!1;#d=!1;connectedCallback(){this.#m()}disconnectedCallback(){this.#f()}attributeChangedCallback(){this.isConnected&&this.#m()}#f(){for(let e of this.#t)e();this.#t=[],this.#e=null}#p(e,t){if(!this.hasAttribute(e))return t;let n=this.getAttribute(e);return n===null||n===``||n===`true`?!0:n===`false`?!1:t}#m(){this.#f();let t=this.shadowRoot??this.attachShadow({mode:`open`}),r=document.createElement(`style`);r.textContent=q,t.replaceChildren(r);let a=this.getAttribute(`alt`)?.trim()||`Experience`,o=this.getAttribute(`href`),s=this.getAttribute(`experience-id`),c=x({href:o,hasClickHandler:this.hasAttribute(`onclick`)||typeof this.onclick==`function`,experienceId:s}),l=this.#p(`normalize-urls`,!0),u=n(0,0),d=()=>_({galleryThumb:Y(this.getAttribute(`gallery-thumb`)),avatarImage:this.getAttribute(`avatar-image`)??void 0,backgroundImage:this.getAttribute(`background-image`)??void 0,foregroundImage:this.getAttribute(`foreground-image`)??void 0,fallbackImage:this.getAttribute(`fallback-image`)??void 0,normalize:l,locationPlateSize:u,cdnBaseUrl:this.getAttribute(`cdn-base-url`)??void 0}),f=d();if(!f)return;let p=f,m=this.#p(`parallax`,!0),h=this.getAttribute(`fit`)?.trim(),g=h===`face`||h===`medium`?h:`full`,v=this.#p(`lazy`,!0);this.#n=!v;let y=c===`anchor`&&!o?.trim()&&!!s?.trim(),b=w({href:o??void 0,experienceId:s??void 0,returnUrl:this.getAttribute(`return-url`)??void 0,playerBaseUrl:this.getAttribute(`player-base-url`)??void 0,useCurrentPageAsReturnUrl:y&&!this.getAttribute(`return-url`)?.trim()}),S;if(c===`anchor`&&b){let e=document.createElement(`a`);e.href=b;let t=this.getAttribute(`target`),n=this.getAttribute(`rel`);t&&(e.target=t),n&&(e.rel=n),S=e}else if(c===`button`){let e=document.createElement(`button`);e.type=`button`,S=e}else S=document.createElement(`div`),S.setAttribute(`role`,`img`);S.className=`liforma-et`,S.setAttribute(`aria-label`,a),this.#e=S,t.appendChild(S);let C=()=>{let t=!!p.backgroundImage,n=m&&(this.#r||this.#a),r=this.#n&&(this.#u||this.#l||this.#d);S.className=[`liforma-et`,t?`liforma-et--scene`:``,e(g),n?`liforma-et--parallax`:``,this.#a?`liforma-et--scroll-parallax`:``,this.#n&&!r?`liforma-et--loading`:``,r?`liforma-et--revealed`:``].filter(Boolean).join(` `),S.style.setProperty(`--liforma-et-parallax-x`,String(this.#o)),S.style.setProperty(`--liforma-et-parallax-y`,String(this.#s))};this.#t.push(i(this,(e,t)=>{let r=n(e,t);if(r===u)return;u=r;let i=d();if(!i)return;p=i;let a=S.querySelector(`.liforma-et__background`);a&&p.backgroundImage&&a.getAttribute(`src`)!==p.backgroundImage&&(this.#l=!1,a.classList.remove(`liforma-et__media--ready`),a.src=p.backgroundImage);let o=S.querySelector(`.liforma-et__foreground`);o&&p.foregroundImage&&o.getAttribute(`src`)!==p.foregroundImage&&(this.#d=!1,o.classList.remove(`liforma-et__media--ready`),o.src=p.foregroundImage),C()}));let T=d();T&&(p=T);let E=document.createElement(`span`);E.className=`liforma-et__placeholder`,E.setAttribute(`aria-hidden`,`true`),S.appendChild(E);let D=()=>{if(!this.#n)return;let e=this.#c&&p.fallbackImage?p.fallbackImage:p.avatarImage;if(p.backgroundImage&&!S.querySelector(`.liforma-et__background`)){let e=document.createElement(`img`);e.className=`liforma-et__background liforma-et__media`,e.alt=``,e.decoding=`async`,e.src=p.backgroundImage,this.#t.push(I(e,()=>{this.#l=!0,e.classList.add(`liforma-et__media--ready`),C()})),S.appendChild(e)}let t=S.querySelector(`.liforma-et__avatar`);if(t||(t=document.createElement(`img`),t.className=`liforma-et__avatar liforma-et__media`,t.alt=``,t.decoding=`async`,t.addEventListener(`error`,()=>{p.fallbackImage&&!this.#c?(this.#c=!0,t.src=p.fallbackImage):(this.#u=!0,C())}),this.#t.push(I(t,()=>{this.#u=!0,t.classList.add(`liforma-et__media--ready`),C()})),S.appendChild(t)),t.src!==e&&(t.src=e),p.foregroundImage&&!S.querySelector(`.liforma-et__foreground`)){let e=document.createElement(`img`);e.className=`liforma-et__foreground liforma-et__media`,e.alt=``,e.decoding=`async`,e.src=p.foregroundImage,this.#t.push(I(e,()=>{this.#d=!0,e.classList.add(`liforma-et__media--ready`),C()})),S.appendChild(e)}C()};if(v?this.#t.push(F(S,{onVisibleChange:e=>{e&&(this.#n=!0,D(),C())}})):D(),m){this.#t.push(K(S,{onParallax:e=>{this.#a=!0,this.#i||(this.#o=e,this.#s=0,C())},pointerActive:()=>this.#i}));let e=e=>{if(this.#a)return;let t=S.getBoundingClientRect();t.width<=0||t.height<=0||(this.#o=((e.clientX-t.left)/t.width-.5)*2,this.#s=((e.clientY-t.top)/t.height-.5)*2,C())},t=e=>{this.#i=!0,e.pointerType===`mouse`&&(this.#r=!0),C()},n=()=>{let e=this.#i;this.#i=!1,this.#r=!1,this.#a&&e?(this.#o=R(S.getBoundingClientRect(),window.innerHeight),this.#s=0):this.#a||(this.#o=0,this.#s=0),C()};S.addEventListener(`pointermove`,e),S.addEventListener(`pointerdown`,t),S.addEventListener(`pointerup`,n),S.addEventListener(`pointercancel`,n),S.addEventListener(`pointerenter`,e=>{e.pointerType===`mouse`&&(this.#r=!0,C())}),S.addEventListener(`pointerleave`,()=>{this.#i||(this.#r=!1,this.#a||(this.#o=0,this.#s=0),C())}),this.#t.push(()=>{S.removeEventListener(`pointermove`,e),S.removeEventListener(`pointerdown`,t),S.removeEventListener(`pointerup`,n),S.removeEventListener(`pointercancel`,n)})}S.addEventListener(`click`,e=>{if(y&&!this.getAttribute(`return-url`)?.trim()){let t=w({experienceId:s??void 0,playerBaseUrl:this.getAttribute(`player-base-url`)??void 0,useCurrentPageAsReturnUrl:!0});t&&e.button===0&&!e.metaKey&&!e.ctrlKey&&!e.shiftKey&&(e.preventDefault(),window.location.assign(t))}}),C()}};customElements.get(J)||customElements.define(J,X),window.LiformaThumbnail={sdkVersion:`v2`,element:X}})();
228
+ }`,q=`data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=`,J=null;function Y(e){let t=e.trim();if(!t||t.startsWith(`blob:`)||t.startsWith(`data:`))return!1;let n=t.split(/[?#]/)[0]??t,r=/\/avatars\//i.test(n),i=/\/locations\//i.test(n);return!r&&!i||/\.depth\.webp$/i.test(n)?!1:/\.webp$/i.test(n)}function te(e){if(!Y(e))return null;let t=e.search(/[?#]/);if(t<0)return e.replace(/\.webp$/i,`.avif`);let n=e.slice(0,t),r=e.slice(t);return`${n.replace(/\.webp$/i,`.avif`)}${r}`}function ne(){return typeof Image>`u`?Promise.resolve(!1):(J||=new Promise(e=>{let t=new Image;t.onload=()=>e(t.naturalWidth>0&&t.naturalHeight>0),t.onerror=()=>e(!1),t.src=q}),J)}function X(e,t){return t?te(e)??e:e}function re(e){let t=e.trim(),n=t.search(/[?#]/),r=n<0?t:t.slice(0,n);if(!/\.avif$/i.test(r))return null;let i=r.replace(/\.avif$/i,`.webp`);return n<0?i:`${i}${t.slice(n)}`}function Z(e){let t=e.trim();return/-400x225\.(webp|avif)(?:$|[?#])/i.test(t)?t.replace(/-400x225\.(webp|avif)/i,`-800x450.$1`):null}var Q=`liforma-experience-thumbnail`;function ie(e){if(e?.trim())try{let t=JSON.parse(e);if(t&&typeof t.avatarImage==`string`)return t}catch{}}var $=class extends HTMLElement{static get observedAttributes(){return[`experience-id`,`gallery-thumb`,`avatar-image`,`background-image`,`foreground-image`,`fallback-image`,`alt`,`href`,`target`,`rel`,`return-url`,`player-base-url`,`parallax`,`fit`,`lazy`,`normalize-urls`,`cdn-base-url`]}#e=null;#t=[];#n=!1;#r=!1;#i=!1;#a=!1;#o=0;#s=0;#c=!1;#l=!1;#u=!1;#d=!1;#f=!1;#p=!1;#m=!1;#h=!1;#g=!1;connectedCallback(){this.#y()}disconnectedCallback(){this.#_()}attributeChangedCallback(){this.isConnected&&this.#y()}#_(){for(let e of this.#t)e();this.#t=[],this.#e=null}#v(e,t){if(!this.hasAttribute(e))return t;let n=this.getAttribute(e);return n===null||n===``||n===`true`?!0:n===`false`?!1:t}#y(){this.#_();let t=this.shadowRoot??this.attachShadow({mode:`open`}),r=document.createElement(`style`);r.textContent=K,t.replaceChildren(r);let a=this.getAttribute(`alt`)?.trim()||`Experience`,o=this.getAttribute(`href`),s=this.getAttribute(`experience-id`),c=x({href:o,hasClickHandler:this.hasAttribute(`onclick`)||typeof this.onclick==`function`,experienceId:s}),l=this.#v(`normalize-urls`,!0),u=n(0,0),d=()=>_({galleryThumb:ie(this.getAttribute(`gallery-thumb`)),avatarImage:this.getAttribute(`avatar-image`)??void 0,backgroundImage:this.getAttribute(`background-image`)??void 0,foregroundImage:this.getAttribute(`foreground-image`)??void 0,fallbackImage:this.getAttribute(`fallback-image`)??void 0,normalize:l,locationPlateSize:u,cdnBaseUrl:this.getAttribute(`cdn-base-url`)??void 0}),f=d();if(!f)return;let p=f,m=this.#v(`parallax`,!0),h=this.getAttribute(`fit`)?.trim(),g=h===`face`||h===`medium`?h:`full`,v=this.#v(`lazy`,!0);this.#n=!v;let y=c===`anchor`&&!o?.trim()&&!!s?.trim(),b=w({href:o??void 0,experienceId:s??void 0,returnUrl:this.getAttribute(`return-url`)??void 0,playerBaseUrl:this.getAttribute(`player-base-url`)??void 0,useCurrentPageAsReturnUrl:y&&!this.getAttribute(`return-url`)?.trim()}),S;if(c===`anchor`&&b){let e=document.createElement(`a`);e.href=b;let t=this.getAttribute(`target`),n=this.getAttribute(`rel`);t&&(e.target=t),n&&(e.rel=n),S=e}else if(c===`button`){let e=document.createElement(`button`);e.type=`button`,S=e}else S=document.createElement(`div`),S.setAttribute(`role`,`img`);S.className=`liforma-et`,S.setAttribute(`aria-label`,a),this.#e=S,t.appendChild(S);let C=()=>{let t=!!p.backgroundImage,n=m&&(this.#r||this.#a),r=this.#n&&this.#p&&(this.#u||this.#l||this.#d);S.className=[`liforma-et`,t?`liforma-et--scene`:``,e(g),n?`liforma-et--parallax`:``,this.#a?`liforma-et--scroll-parallax`:``,this.#n&&!r?`liforma-et--loading`:``,r?`liforma-et--revealed`:``].filter(Boolean).join(` `),S.style.setProperty(`--liforma-et-parallax-x`,String(this.#o)),S.style.setProperty(`--liforma-et-parallax-y`,String(this.#s))};this.#t.push(i(this,(e,t)=>{let r=n(e,t);if(r===u)return;u=r;let i=d();if(!i)return;p=i;let a=S.querySelector(`.liforma-et__background`);a&&p.backgroundImage&&a.getAttribute(`src`)!==X(p.backgroundImage,this.#f)&&(this.#l=!1,this.#m=!1,a.classList.remove(`liforma-et__media--ready`),a.src=X(p.backgroundImage,this.#f));let o=S.querySelector(`.liforma-et__foreground`);o&&p.foregroundImage&&o.getAttribute(`src`)!==X(p.foregroundImage,this.#f)&&(this.#d=!1,this.#g=!1,o.classList.remove(`liforma-et__media--ready`),o.src=X(p.foregroundImage,this.#f)),C()}));let T=d();T&&(p=T);let E=document.createElement(`span`);E.className=`liforma-et__placeholder`,E.setAttribute(`aria-hidden`,`true`),S.appendChild(E);let D=()=>{if(!this.#n)return;if(!this.#p){ne().then(e=>{this.#f=e,this.#p=!0,D(),C()});return}let e=X(this.#c&&p.fallbackImage?p.fallbackImage:p.avatarImage,this.#f);if(p.backgroundImage&&!S.querySelector(`.liforma-et__background`)){let e=document.createElement(`img`);e.className=`liforma-et__background liforma-et__media`,e.alt=``,e.decoding=`async`,e.src=X(p.backgroundImage,this.#f),e.addEventListener(`error`,()=>{let t=Z(e.src);!t||this.#m||(this.#m=!0,e.src=t)}),this.#t.push(F(e,()=>{this.#l=!0,e.classList.add(`liforma-et__media--ready`),C()})),S.appendChild(e)}let t=S.querySelector(`.liforma-et__avatar`);if(t||(t=document.createElement(`img`),t.className=`liforma-et__avatar liforma-et__media`,t.alt=``,t.decoding=`async`,t.addEventListener(`error`,e=>{let n=e.currentTarget,r=re(n.src);if(r&&!this.#h){this.#h=!0,n.src=r;return}p.fallbackImage&&!this.#c?(this.#c=!0,t.src=X(p.fallbackImage,this.#f)):(this.#u=!0,C())}),this.#t.push(F(t,()=>{this.#u=!0,t.classList.add(`liforma-et__media--ready`),C()})),S.appendChild(t)),t.src!==e&&(this.#h=!1,t.src=e),p.foregroundImage&&!S.querySelector(`.liforma-et__foreground`)){let e=document.createElement(`img`);e.className=`liforma-et__foreground liforma-et__media`,e.alt=``,e.decoding=`async`,e.src=X(p.foregroundImage,this.#f),e.addEventListener(`error`,()=>{let t=Z(e.src);!t||this.#g||(this.#g=!0,e.src=t)}),this.#t.push(F(e,()=>{this.#d=!0,e.classList.add(`liforma-et__media--ready`),C()})),S.appendChild(e)}C()};if(v?this.#t.push(P(S,{onVisibleChange:e=>{e&&(this.#n=!0,D(),C())}})):D(),m){this.#t.push(G(S,{onParallax:e=>{this.#a=!0,this.#i||(this.#o=e,this.#s=0,C())},pointerActive:()=>this.#i}));let e=e=>{if(this.#a)return;let t=S.getBoundingClientRect();t.width<=0||t.height<=0||(this.#o=((e.clientX-t.left)/t.width-.5)*2,this.#s=((e.clientY-t.top)/t.height-.5)*2,C())},t=e=>{this.#i=!0,e.pointerType===`mouse`&&(this.#r=!0),C()},n=()=>{let e=this.#i;this.#i=!1,this.#r=!1,this.#a&&e?(this.#o=L(S.getBoundingClientRect(),window.innerHeight),this.#s=0):this.#a||(this.#o=0,this.#s=0),C()};S.addEventListener(`pointermove`,e),S.addEventListener(`pointerdown`,t),S.addEventListener(`pointerup`,n),S.addEventListener(`pointercancel`,n),S.addEventListener(`pointerenter`,e=>{e.pointerType===`mouse`&&(this.#r=!0,C())}),S.addEventListener(`pointerleave`,()=>{this.#i||(this.#r=!1,this.#a||(this.#o=0,this.#s=0),C())}),this.#t.push(()=>{S.removeEventListener(`pointermove`,e),S.removeEventListener(`pointerdown`,t),S.removeEventListener(`pointerup`,n),S.removeEventListener(`pointercancel`,n)})}S.addEventListener(`click`,e=>{if(y&&!this.getAttribute(`return-url`)?.trim()){let t=w({experienceId:s??void 0,playerBaseUrl:this.getAttribute(`player-base-url`)??void 0,useCurrentPageAsReturnUrl:!0});t&&e.button===0&&!e.metaKey&&!e.ctrlKey&&!e.shiftKey&&(e.preventDefault(),window.location.assign(t))}}),C()}};customElements.get(Q)||customElements.define(Q,$),window.LiformaThumbnail={sdkVersion:`v2`,element:$}})();
229
229
  //# sourceMappingURL=thumbnail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"thumbnail.js","names":["#render","#teardown","#cleanup","#root","#boolAttr","#shouldLoad","#hovering","#scrollParallax","#avatarReady","#backgroundReady","#foregroundReady","#parallaxX","#parallaxY","#avatarLoadFailed","#pointerDown"],"sources":["../../../src/thumbnail/types.ts","../../../src/thumbnail/locationPlate.ts","../../../src/thumbnail/expandAssetUrl.ts","../../../src/thumbnail/toGalleryThumbUrl.ts","../../../src/shims/localStack.ts","../../../src/thumbnail/hostedLaunchUrl.ts","../../../src/thumbnail/thumbLoadScheduler.ts","../../../src/thumbnail/lazyLoad.ts","../../../src/thumbnail/scrollParallax.ts","../../../src/thumbnail/styles.ts","../../../src/v2/liforma-experience-thumbnail.ts","../../../src/v2/thumbnail-cdn.ts"],"sourcesContent":["/**\n * Catalog-shaped layered gallery preview.\n * Values may be absolute URLs or Liforma paths (`/avatars/…`, `/locations/…/scene`).\n */\nexport type GalleryThumb = {\n\treadonly avatarImage: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n};\n\nexport type ThumbnailInteractionKind = 'anchor' | 'button' | 'presentational';\n\nexport type ResolveThumbnailInteractionInput = {\n\treadonly href?: string | null;\n\treadonly hasClickHandler?: boolean;\n\treadonly experienceId?: string | null;\n};\n\nexport type ThumbnailLayers = {\n\treadonly avatarImage: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly fallbackImage?: string;\n};\n\n/** Shared props across Svelte / React / Next (React uses `onClick`). */\nexport type ExperienceThumbnailBaseProps = {\n\treadonly galleryThumb?: GalleryThumb;\n\treadonly avatarImage?: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly alt: string;\n\treadonly experienceId?: string;\n\treadonly href?: string;\n\treadonly target?: string;\n\treadonly rel?: string;\n\treadonly returnUrl?: string;\n\treadonly playerBaseUrl?: string;\n\treadonly parallax?: boolean;\n\t/** Same as `<Experience fit>`: `full` (default), `medium`, or `face`. */\n\treadonly fit?: 'full' | 'face' | 'medium';\n\treadonly lazy?: boolean;\n\treadonly className?: string;\n\treadonly fallbackImage?: string;\n\treadonly normalizeUrls?: boolean;\n\t/** CDN origin for catalog paths. Default `https://cdn.liforma.ai`. */\n\treadonly cdnBaseUrl?: string;\n};\n\nexport function thumbnailFitClass(fit?: 'full' | 'face' | 'medium'): string {\n\tif (fit === 'face') return 'liforma-et--fit-face';\n\tif (fit === 'medium') return 'liforma-et--fit-medium';\n\treturn '';\n}\n","/** Location colour plates used by ExperienceThumbnail (no zoom/portrait crops). */\nexport const THUMBNAIL_LOCATION_PLATE_SIZES = ['256x256', '800x450', '1672x941'] as const;\nexport type ThumbnailLocationPlateSize = (typeof THUMBNAIL_LOCATION_PLATE_SIZES)[number];\n\nconst LOCATION_SIZE_RE = /(-)(\\d+x\\d+)(\\.webp)(\\?|$)/i;\n\n/**\n * Square / portrait hosts keep the gallery crop. Landscape hosts use the 16:9 plate\n * so cover-fit fills height instead of clipping the top and bottom of a square.\n * Unmeasured boxes stay on `256x256`.\n */\nexport function pickThumbnailLocationPlateSize(\n\twidth: number,\n\theight: number\n): ThumbnailLocationPlateSize {\n\tif (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) {\n\t\treturn '256x256';\n\t}\n\tif (width <= height) return '256x256';\n\tif (width > 800 || height > 450) return '1672x941';\n\treturn '800x450';\n}\n\nexport function rewriteThumbnailLocationPlateSize(\n\turl: string,\n\tsize: ThumbnailLocationPlateSize\n): string {\n\tconst trimmed = url.trim();\n\tif (!trimmed) return trimmed;\n\treturn trimmed.replace(\n\t\tLOCATION_SIZE_RE,\n\t\t(_match, dash: string, _current: string, ext: string, end: string) =>\n\t\t\t`${dash}${size}${ext}${end}`\n\t);\n}\n\n/** Observe the thumb host and report CSS box size (for plate picking). */\nexport function attachThumbnailBox(\n\telement: HTMLElement,\n\tonBox: (width: number, height: number) => void\n): () => void {\n\tconst emit = () => {\n\t\tonBox(element.clientWidth, element.clientHeight);\n\t};\n\temit();\n\tif (typeof ResizeObserver === 'undefined') {\n\t\treturn () => {};\n\t}\n\tconst observer = new ResizeObserver(emit);\n\tobserver.observe(element);\n\treturn () => observer.disconnect();\n}\n","import {\n\trewriteThumbnailLocationPlateSize,\n\ttype ThumbnailLocationPlateSize\n} from './locationPlate';\n\n/** Default origin for Liforma gallery plates (catalog paths have no host). */\nexport const DEFAULT_THUMBNAIL_CDN_BASE_URL = 'https://cdn.liforma.ai';\n\nconst LOCATION_SIZE_STEM_RE = /-(256x256|400x450|800x450|836x971|1672x941|167x94)$/i;\n\nexport function normalizeThumbnailCdnBaseUrl(cdnBaseUrl?: string): string {\n\tconst trimmed = cdnBaseUrl?.trim().replace(/\\/$/, '');\n\treturn trimmed || DEFAULT_THUMBNAIL_CDN_BASE_URL;\n}\n\nfunction isHttpUrl(value: string): boolean {\n\treturn /^https?:\\/\\//i.test(value) || value.startsWith('//');\n}\n\nfunction absoluteHttpUrl(value: string): string {\n\tif (value.startsWith('//')) return `https:${value}`;\n\treturn value;\n}\n\nfunction splitPathAndSearch(value: string): { pathname: string; search: string } {\n\tconst q = value.indexOf('?');\n\tif (q === -1) return { pathname: value, search: '' };\n\treturn { pathname: value.slice(0, q), search: value.slice(q) };\n}\n\nfunction joinCdn(cdnBase: string, pathname: string, search = ''): string {\n\tconst path = pathname.startsWith('/') ? pathname : `/${pathname}`;\n\treturn `${cdnBase}${path}${search}`;\n}\n\n/**\n * Expand a catalog location path (or keep a full URL) to a sized plate URL.\n *\n * `/locations/modern/city/office/interior-desk-daytime` →\n * `{cdn}/locations/modern/city/office/interior-desk-daytime-800x450.webp`\n */\nexport function expandThumbnailLocationUrl(\n\tref: string,\n\tlocationPlateSize: ThumbnailLocationPlateSize,\n\tcdnBaseUrl?: string\n): string {\n\tconst raw = ref.trim();\n\tif (!raw) return raw;\n\tconst cdnBase = normalizeThumbnailCdnBaseUrl(cdnBaseUrl);\n\n\tif (isHttpUrl(raw)) {\n\t\treturn rewriteThumbnailLocationPlateSize(absoluteHttpUrl(raw), locationPlateSize);\n\t}\n\n\tconst { pathname: rawPath, search } = splitPathAndSearch(raw.replace(/\\\\/g, '/'));\n\tlet pathname = rawPath;\n\n\tif (!pathname.startsWith('/')) {\n\t\tpathname = `/locations/${pathname.replace(/^locations\\//i, '')}`;\n\t} else if (/^\\/locations\\//i.test(pathname)) {\n\t\t/* already a location path */\n\t} else if (/^\\/avatars\\//i.test(pathname)) {\n\t\treturn joinCdn(cdnBase, pathname, search);\n\t} else {\n\t\treturn raw;\n\t}\n\n\tif (/\\.webp$/i.test(pathname) && !LOCATION_SIZE_STEM_RE.test(pathname.replace(/\\.webp$/i, ''))) {\n\t\treturn joinCdn(cdnBase, pathname, search);\n\t}\n\n\tlet stem = pathname.replace(/^\\/locations\\//i, '').replace(/\\.webp$/i, '');\n\tstem = stem.replace(LOCATION_SIZE_STEM_RE, '');\n\treturn `${cdnBase}/locations/${stem}-${locationPlateSize}.webp${search}`;\n}\n\n/**\n * Expand a catalog avatar path (or keep a full URL) onto the public CDN.\n * `/avatars/{id}` → `{cdn}/avatars/{id}/256/{id}_neutral.webp`\n */\nexport function expandThumbnailAvatarUrl(ref: string, cdnBaseUrl?: string): string {\n\tconst raw = ref.trim();\n\tif (!raw) return raw;\n\tconst cdnBase = normalizeThumbnailCdnBaseUrl(cdnBaseUrl);\n\n\tif (isHttpUrl(raw)) {\n\t\treturn absoluteHttpUrl(raw);\n\t}\n\n\tconst { pathname: rawPath, search } = splitPathAndSearch(raw.replace(/\\\\/g, '/'));\n\tlet pathname = rawPath;\n\n\tif (!pathname.startsWith('/')) {\n\t\tif (/^avatars\\//i.test(pathname)) {\n\t\t\tpathname = `/${pathname}`;\n\t\t} else if (/^[^/]+$/.test(pathname) && !search) {\n\t\t\treturn `${cdnBase}/avatars/${pathname}/256/${pathname}_neutral.webp`;\n\t\t} else {\n\t\t\treturn raw;\n\t\t}\n\t}\n\n\tif (!/^\\/avatars\\//i.test(pathname)) {\n\t\treturn raw;\n\t}\n\n\tconst rest = pathname.replace(/^\\/avatars\\//i, '').replace(/\\/+$/, '');\n\tif (rest && !rest.includes('/') && !search) {\n\t\treturn `${cdnBase}/avatars/${rest}/256/${rest}_neutral.webp`;\n\t}\n\n\treturn joinCdn(cdnBase, pathname, search);\n}\n","import {\n\texpandThumbnailAvatarUrl,\n\texpandThumbnailLocationUrl,\n\tnormalizeThumbnailCdnBaseUrl\n} from './expandAssetUrl';\nimport type { ThumbnailLocationPlateSize } from './locationPlate';\n\nconst AVATAR_TIER_RE = /(\\/avatars\\/[^/]+\\/)(\\d+)(\\/)/i;\nconst AVATAR_CH_PLATE_RE = /_CH(\\.webp)/i;\n\nexport type { ThumbnailLocationPlateSize };\n\nfunction isLocationRef(raw: string): boolean {\n\tif (/\\/locations\\//i.test(raw) || /^locations\\//i.test(raw)) return true;\n\tif (/^https?:\\/\\//i.test(raw) || raw.startsWith('//')) return false;\n\tif (/^\\/?avatars\\//i.test(raw)) return false;\n\tif (raw.startsWith('/')) return false;\n\treturn raw.includes('/');\n}\n\n/**\n * Rewrite known Liforma CDN URLs to gallery thumb tiers.\n * Avatars: /{tier}/ → /256/; locations: -{WxH}.webp → the requested plate\n * (`256x256` by default, `800x450` / `1672x941` for landscape hosts).\n * Catalog paths (`/locations/…/scene`, `/avatars/{id}`) expand onto the CDN first.\n * Unrecognized URLs are returned unchanged.\n */\nexport function toGalleryThumbUrl(\n\turl: string,\n\tlocationPlateSize: ThumbnailLocationPlateSize = '256x256',\n\tcdnBaseUrl?: string\n): string {\n\tconst raw = url.trim();\n\tif (!raw) return raw;\n\n\tif (isLocationRef(raw)) {\n\t\treturn expandThumbnailLocationUrl(raw, locationPlateSize, cdnBaseUrl);\n\t}\n\n\tlet next = expandThumbnailAvatarUrl(raw, cdnBaseUrl);\n\tnext = next.replace(AVATAR_TIER_RE, (_match, prefix: string, tier: string, slash: string) => {\n\t\tif (tier === '256') return `${prefix}${tier}${slash}`;\n\t\treturn `${prefix}256${slash}`;\n\t});\n\treturn next.replace(AVATAR_CH_PLATE_RE, '_neutral$1');\n}\n\nexport function resolveThumbnailLayers(input: {\n\treadonly galleryThumb?: {\n\t\treadonly avatarImage?: string;\n\t\treadonly backgroundImage?: string;\n\t\treadonly foregroundImage?: string;\n\t};\n\treadonly avatarImage?: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly fallbackImage?: string;\n\t/** When false, keep caller absolute URLs (lightbox / preview). Paths still expand. Default true. */\n\treadonly normalize?: boolean;\n\t/** Location plate when expanding or rewriting location assets. Default `256x256`. */\n\treadonly locationPlateSize?: ThumbnailLocationPlateSize;\n\t/** CDN origin for catalog paths. Default `https://cdn.liforma.ai`. */\n\treadonly cdnBaseUrl?: string;\n}): {\n\treadonly avatarImage: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly fallbackImage?: string;\n} | null {\n\tconst avatarSource =\n\t\tinput.avatarImage?.trim() ||\n\t\tinput.galleryThumb?.avatarImage?.trim() ||\n\t\t'';\n\tif (!avatarSource) return null;\n\n\tconst backgroundSource =\n\t\tinput.backgroundImage?.trim() || input.galleryThumb?.backgroundImage?.trim() || '';\n\tconst foregroundSource =\n\t\tinput.foregroundImage?.trim() || input.galleryThumb?.foregroundImage?.trim() || '';\n\tconst fallbackSource = input.fallbackImage?.trim() || '';\n\tconst plate = input.locationPlateSize ?? '256x256';\n\tconst cdnBaseUrl = normalizeThumbnailCdnBaseUrl(input.cdnBaseUrl);\n\tconst rewriteKnown = input.normalize !== false;\n\n\tconst rewriteAvatar = (ref: string) => {\n\t\tif (!rewriteKnown && /^https?:\\/\\//i.test(ref.trim())) return ref.trim();\n\t\treturn toGalleryThumbUrl(ref, plate, cdnBaseUrl);\n\t};\n\tconst rewriteLocation = (ref: string) => {\n\t\tif (!rewriteKnown && /^https?:\\/\\//i.test(ref.trim())) return ref.trim();\n\t\treturn expandThumbnailLocationUrl(ref, plate, cdnBaseUrl);\n\t};\n\n\treturn {\n\t\tavatarImage: rewriteAvatar(avatarSource),\n\t\t...(backgroundSource ? { backgroundImage: rewriteLocation(backgroundSource) } : {}),\n\t\t...(foregroundSource ? { foregroundImage: rewriteLocation(foregroundSource) } : {}),\n\t\t...(fallbackSource ? { fallbackImage: rewriteAvatar(fallbackSource) } : {})\n\t};\n}\n","/**\n * Opt-in local monorepo stack (api :3001, player :3002).\n * Never inferred from hostname alone — third-party demos run on localhost against production.\n */\nexport function useExplicitLocalStack(): boolean {\n\tif ((globalThis as { __LIFORMA_STACK?: string }).__LIFORMA_STACK === 'local') {\n\t\treturn true;\n\t}\n\n\tif (typeof location === 'undefined') {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\treturn new URLSearchParams(location.search).get('stack') === 'local';\n\t} catch {\n\t\treturn false;\n\t}\n}\n","import { useExplicitLocalStack } from '../shims/localStack.ts';\nimport type { ResolveThumbnailInteractionInput, ThumbnailInteractionKind } from './types';\n\nconst PRODUCTION_PLAYER_ORIGIN = 'https://player.liforma.ai';\nconst LOCAL_PLAYER_ORIGIN = 'http://localhost:3002';\n\n/** Priority: href → click-only → hosted (experienceId) → presentational. */\nexport function resolveThumbnailInteraction(\n\tinput: ResolveThumbnailInteractionInput\n): ThumbnailInteractionKind {\n\tif (input.href?.trim()) return 'anchor';\n\tif (input.hasClickHandler) return 'button';\n\tif (input.experienceId?.trim()) return 'anchor';\n\treturn 'presentational';\n}\n\nexport function resolvePlayerBaseUrl(playerBaseUrl?: string): string {\n\tconst configured = playerBaseUrl?.trim();\n\tif (configured) return configured.replace(/\\/$/, '');\n\n\tif (useExplicitLocalStack()) {\n\t\treturn LOCAL_PLAYER_ORIGIN;\n\t}\n\n\treturn PRODUCTION_PLAYER_ORIGIN;\n}\n\n/**\n * Build hosted player launch URL: `{player}/{experienceId}?returnUrl=…`\n * When `returnUrl` is omitted, callers should assign on click (SSR-safe) or pass an explicit URL.\n */\nexport function buildHostedLaunchUrl(options: {\n\treadonly experienceId: string;\n\treadonly returnUrl?: string;\n\treadonly playerBaseUrl?: string;\n}): string {\n\tconst experienceId = options.experienceId.trim();\n\tconst base = resolvePlayerBaseUrl(options.playerBaseUrl);\n\tconst url = new URL(`${base}/${encodeURIComponent(experienceId)}`);\n\tconst returnUrl = options.returnUrl?.trim();\n\tif (returnUrl) {\n\t\turl.searchParams.set('returnUrl', returnUrl);\n\t}\n\treturn url.toString();\n}\n\n/** Resolve href for anchor mode (app link or hosted launch). */\nexport function resolveThumbnailHref(options: {\n\treadonly href?: string;\n\treadonly experienceId?: string;\n\treadonly returnUrl?: string;\n\treadonly playerBaseUrl?: string;\n\t/** When true and returnUrl omitted, use window.location.href (browser only). */\n\treadonly useCurrentPageAsReturnUrl?: boolean;\n}): string | undefined {\n\tconst href = options.href?.trim();\n\tif (href) return href;\n\n\tconst experienceId = options.experienceId?.trim();\n\tif (!experienceId) return undefined;\n\n\tlet returnUrl = options.returnUrl?.trim();\n\tif (!returnUrl && options.useCurrentPageAsReturnUrl && typeof window !== 'undefined') {\n\t\treturnUrl = window.location.href;\n\t}\n\n\treturn buildHostedLaunchUrl({\n\t\texperienceId,\n\t\t...(returnUrl ? { returnUrl } : {}),\n\t\tplayerBaseUrl: options.playerBaseUrl\n\t});\n}\n","/** Touch / coarse pointers — limit how many thumbs start loading at once. */\nexport const COARSE_THUMB_LOAD_CONCURRENCY = 4;\n/** Delay before a started thumb frees its queue slot for the next card. */\nexport const COARSE_THUMB_LOAD_STAGGER_MS = 160;\n\nexport function prefersCoarsePointer(): boolean {\n\tif (typeof window === 'undefined') return false;\n\treturn window.matchMedia('(hover: none), (pointer: coarse)').matches;\n}\n\ntype QueuedLoad = {\n\treadonly start: () => void;\n\tcancelled: boolean;\n};\n\nlet inflight = 0;\nconst waiting: QueuedLoad[] = [];\n\nfunction maxConcurrent(): number {\n\treturn prefersCoarsePointer() ? COARSE_THUMB_LOAD_CONCURRENCY : Number.POSITIVE_INFINITY;\n}\n\nfunction staggerMs(): number {\n\treturn prefersCoarsePointer() ? COARSE_THUMB_LOAD_STAGGER_MS : 0;\n}\n\nfunction pump(): void {\n\tconst limit = maxConcurrent();\n\twhile (inflight < limit && waiting.length > 0) {\n\t\tconst next = waiting.shift();\n\t\tif (!next || next.cancelled) continue;\n\t\tinflight += 1;\n\t\tnext.start();\n\t\tconst delay = staggerMs();\n\t\tif (delay <= 0) {\n\t\t\tinflight = Math.max(0, inflight - 1);\n\t\t\tcontinue;\n\t\t}\n\t\twindow.setTimeout(() => {\n\t\t\tinflight = Math.max(0, inflight - 1);\n\t\t\tpump();\n\t\t}, delay);\n\t}\n}\n\n/**\n * On coarse pointers, start thumb image loads in small waves so old devices\n * are not hit with dozens of concurrent decodes. Fine pointers start immediately.\n * Returns a cancel function (no-op after start has run).\n */\nexport function scheduleThumbLoad(start: () => void): () => void {\n\tif (!prefersCoarsePointer()) {\n\t\tstart();\n\t\treturn () => {};\n\t}\n\n\tconst item: QueuedLoad = { start, cancelled: false };\n\twaiting.push(item);\n\tpump();\n\n\treturn () => {\n\t\titem.cancelled = true;\n\t};\n}\n\n/** Test helper — clear queue state between vitest cases. */\nexport function resetThumbLoadSchedulerForTests(): void {\n\tinflight = 0;\n\twaiting.length = 0;\n}\n","import { prefersCoarsePointer, scheduleThumbLoad } from './thumbLoadScheduler';\n\nexport type LazyThumbOptions = {\n\treadonly rootMargin?: string;\n\treadonly onVisibleChange: (visible: boolean) => void;\n};\n\n/** Desktop: prefetch earlier. Coarse/touch: tighter margin to cut burst loads. */\nexport function getDefaultLazyRootMargin(): string {\n\treturn prefersCoarsePointer() ? '96px 0px' : '280px 0px';\n}\n\nfunction parseVerticalRootMarginPx(rootMargin: string): number {\n\tconst first = rootMargin.trim().split(/\\s+/)[0] ?? '0px';\n\tconst value = Number.parseFloat(first);\n\treturn Number.isFinite(value) ? Math.abs(value) : 280;\n}\n\nfunction isNearViewport(element: HTMLElement, marginPx: number): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst rect = element.getBoundingClientRect();\n\treturn rect.bottom >= -marginPx && rect.top <= window.innerHeight + marginPx;\n}\n\n/** Observe the thumb and enable image loads when near the viewport. */\nexport function attachLazyThumb(element: HTMLElement, options: LazyThumbOptions): () => void {\n\tconst { onVisibleChange } = options;\n\tconst rootMargin = options.rootMargin ?? getDefaultLazyRootMargin();\n\tconst marginPx = parseVerticalRootMarginPx(rootMargin);\n\tlet visible = false;\n\tlet loadStarted = false;\n\tlet cancelScheduled: (() => void) | null = null;\n\n\tconst requestLoad = () => {\n\t\tif (loadStarted) return;\n\t\tcancelScheduled?.();\n\t\tcancelScheduled = scheduleThumbLoad(() => {\n\t\t\tcancelScheduled = null;\n\t\t\tloadStarted = true;\n\t\t\tonVisibleChange(true);\n\t\t});\n\t};\n\n\tconst setVisible = (next: boolean) => {\n\t\tif (next === visible) return;\n\t\tvisible = next;\n\t\tif (next) {\n\t\t\trequestLoad();\n\t\t\treturn;\n\t\t}\n\t\t// Keep images once started; only cancel a queued (not-yet-started) load.\n\t\tcancelScheduled?.();\n\t\tcancelScheduled = null;\n\t\tonVisibleChange(false);\n\t};\n\n\tif (typeof IntersectionObserver === 'undefined') {\n\t\trequestLoad();\n\t\treturn () => {\n\t\t\tcancelScheduled?.();\n\t\t\tcancelScheduled = null;\n\t\t};\n\t}\n\n\tconst observer = new IntersectionObserver(\n\t\t(entries) => {\n\t\t\tsetVisible(entries.some((entry) => entry.isIntersecting));\n\t\t},\n\t\t{ root: null, rootMargin, threshold: 0 }\n\t);\n\n\tobserver.observe(element);\n\n\tif (isNearViewport(element, marginPx)) {\n\t\tsetVisible(true);\n\t}\n\n\treturn () => {\n\t\tcancelScheduled?.();\n\t\tcancelScheduled = null;\n\t\tobserver.disconnect();\n\t};\n}\n\nexport function attachImageReady(element: HTMLImageElement, onReady: () => void): () => void {\n\tconst notify = () => onReady();\n\tif (element.complete) {\n\t\tqueueMicrotask(notify);\n\t} else {\n\t\telement.addEventListener('load', notify, { once: true });\n\t\telement.addEventListener('error', notify, { once: true });\n\t}\n\treturn () => {\n\t\telement.removeEventListener('load', notify);\n\t\telement.removeEventListener('error', notify);\n\t};\n}\n","/** Touch devices without hover — drive thumbnail parallax from scroll position. */\nexport function prefersScrollLinkedParallax(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tif (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return false;\n\treturn window.matchMedia('(hover: none), (pointer: coarse)').matches;\n}\n\n/**\n * Map an element's vertical centre in the viewport to horizontal parallax in [-1, 1].\n */\nexport function horizontalParallaxFromViewportY(rect: DOMRect, viewportHeight: number): number {\n\tconst centerY = rect.top + rect.height / 2;\n\tconst t = centerY / Math.max(viewportHeight, 1);\n\treturn (t - 0.5) * 2;\n}\n\nexport type ScrollLinkedParallaxOptions = {\n\treadonly onParallax: (x: number) => void;\n\treadonly pointerActive?: () => boolean;\n};\n\ntype ScrollParallaxSubscriber = {\n\treadonly element: HTMLElement;\n\treadonly onParallax: (x: number) => void;\n\treadonly pointerActive?: () => boolean;\n};\n\nconst subscribers = new Set<ScrollParallaxSubscriber>();\nlet listening = false;\nlet frame = 0;\n\nfunction syncAll(): void {\n\tframe = 0;\n\tif (subscribers.size === 0 || typeof window === 'undefined') return;\n\tconst viewportHeight = window.innerHeight;\n\tfor (const entry of subscribers) {\n\t\tif (entry.pointerActive?.()) continue;\n\t\tconst rect = entry.element.getBoundingClientRect();\n\t\t// Skip off-screen thumbs — avoids N layout reads + framework updates per scroll.\n\t\tif (rect.bottom < -64 || rect.top > viewportHeight + 64) continue;\n\t\tentry.onParallax(horizontalParallaxFromViewportY(rect, viewportHeight));\n\t}\n}\n\nfunction scheduleSync(): void {\n\tif (frame !== 0) return;\n\tframe = requestAnimationFrame(syncAll);\n}\n\nfunction ensureListening(): void {\n\tif (listening || typeof window === 'undefined') return;\n\tlistening = true;\n\twindow.addEventListener('scroll', scheduleSync, { passive: true });\n\twindow.addEventListener('resize', scheduleSync, { passive: true });\n}\n\nfunction stopListeningIfIdle(): void {\n\tif (subscribers.size > 0 || !listening || typeof window === 'undefined') return;\n\tlistening = false;\n\tif (frame !== 0) {\n\t\tcancelAnimationFrame(frame);\n\t\tframe = 0;\n\t}\n\twindow.removeEventListener('scroll', scheduleSync);\n\twindow.removeEventListener('resize', scheduleSync);\n}\n\n/**\n * Subscribe to scroll/resize and update horizontal parallax from viewport Y.\n * Uses one shared window listener for all thumbs (old iPads with large galleries).\n */\nexport function attachScrollLinkedParallax(\n\telement: HTMLElement,\n\toptions: ScrollLinkedParallaxOptions\n): () => void {\n\tif (!prefersScrollLinkedParallax()) return () => {};\n\n\tconst entry: ScrollParallaxSubscriber = {\n\t\telement,\n\t\tonParallax: options.onParallax,\n\t\tpointerActive: options.pointerActive\n\t};\n\tsubscribers.add(entry);\n\tensureListening();\n\n\tif (!entry.pointerActive?.()) {\n\t\toptions.onParallax(\n\t\t\thorizontalParallaxFromViewportY(element.getBoundingClientRect(), window.innerHeight)\n\t\t);\n\t}\n\n\treturn () => {\n\t\tsubscribers.delete(entry);\n\t\tstopListeningIfIdle();\n\t};\n}\n\n/** Test helper — clear shared scroll state between vitest cases. */\nexport function resetScrollLinkedParallaxForTests(): void {\n\tsubscribers.clear();\n\tif (typeof window !== 'undefined' && listening) {\n\t\twindow.removeEventListener('scroll', scheduleSync);\n\t\twindow.removeEventListener('resize', scheduleSync);\n\t}\n\tlistening = false;\n\tif (frame !== 0) {\n\t\tcancelAnimationFrame(frame);\n\t\tframe = 0;\n\t}\n}\n","/** Shared styles for ExperienceThumbnail (class prefix `liforma-et`). */\nexport const EXPERIENCE_THUMBNAIL_CSS = `\n:host {\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 0;\n}\n\n.liforma-et {\n\t--liforma-et-parallax-x: 0;\n\t--liforma-et-parallax-y: 0;\n\t--liforma-et-bg-scale: 1;\n\t--liforma-et-fg-scale: 1;\n\t--liforma-et-avatar-scale: 1;\n\t--liforma-et-bg-shift: 0px;\n\t--liforma-et-fg-shift: 0px;\n\t--liforma-et-avatar-shift: 0px;\n\t--liforma-et-reveal-duration: 420ms;\n\n\tbox-sizing: border-box;\n\tposition: relative;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 0;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\toverflow: hidden;\n\tbackground: #111;\n\tcolor: inherit;\n\ttext-decoration: none;\n\tcursor: inherit;\n\t-webkit-tap-highlight-color: transparent;\n}\n\na.liforma-et,\nbutton.liforma-et {\n\tcursor: pointer;\n}\n\nbutton.liforma-et {\n\tfont: inherit;\n\tappearance: none;\n}\n\n.liforma-et__placeholder {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 0;\n\tbackground:\n\t\tradial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.06), transparent 58%),\n\t\tlinear-gradient(180deg, #1a1a1a 0%, #101010 100%);\n\topacity: 1;\n\ttransition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et--revealed .liforma-et__placeholder {\n\topacity: 0;\n}\n\n.liforma-et__media {\n\topacity: 0;\n\ttransition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et__media--ready {\n\topacity: 1;\n}\n\n.liforma-et__background {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 1;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center center;\n\ttransform: translate(\n\t\t\tcalc(var(--liforma-et-parallax-x) * var(--liforma-et-bg-shift)),\n\t\t\tcalc(var(--liforma-et-parallax-y) * var(--liforma-et-bg-shift))\n\t\t)\n\t\tscale(var(--liforma-et-bg-scale));\n\ttransform-origin: center center;\n\ttransition:\n\t\topacity var(--liforma-et-reveal-duration) ease,\n\t\ttransform 160ms ease;\n}\n\n.liforma-et__avatar {\n\tposition: relative;\n\tz-index: 2;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: contain;\n\tobject-position: center bottom;\n\ttransform: scale(var(--liforma-et-avatar-scale))\n\t\ttranslate(\n\t\t\tcalc(var(--liforma-et-parallax-x) * var(--liforma-et-avatar-shift)),\n\t\t\tcalc(var(--liforma-et-parallax-y) * var(--liforma-et-avatar-shift))\n\t\t);\n\ttransform-origin: center center;\n\ttransition:\n\t\topacity var(--liforma-et-reveal-duration) ease,\n\t\ttransform 160ms ease;\n}\n\n/* Gallery plates are typically square in a square tile — contain vs cover is a no-op\n without a zoom. Scale the whole scene from the upper third so Face matches\n player fit (location + avatar crop together). */\n.liforma-et--fit-face {\n\t--liforma-et-avatar-scale: 1.85;\n\t--liforma-et-bg-scale: 1.85;\n\t--liforma-et-fg-scale: 1.85;\n}\n\n.liforma-et--fit-face .liforma-et__avatar,\n.liforma-et--fit-face .liforma-et__background,\n.liforma-et--fit-face .liforma-et__foreground {\n\ttransform-origin: 50% 18%;\n}\n\n.liforma-et--fit-face .liforma-et__avatar {\n\tobject-fit: cover;\n\tobject-position: 50% 18%;\n}\n\n/* Bust / medium: less zoom than face so upper chest stays in frame (player 3.75H window). */\n.liforma-et--fit-medium {\n\t--liforma-et-avatar-scale: 1.35;\n\t--liforma-et-bg-scale: 1.35;\n\t--liforma-et-fg-scale: 1.35;\n}\n\n.liforma-et--fit-medium .liforma-et__avatar,\n.liforma-et--fit-medium .liforma-et__background,\n.liforma-et--fit-medium .liforma-et__foreground {\n\ttransform-origin: 50% 28%;\n}\n\n.liforma-et--fit-medium .liforma-et__avatar {\n\tobject-fit: cover;\n\tobject-position: 50% 28%;\n}\n\n.liforma-et__foreground {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 3;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center center;\n\tpointer-events: none;\n\ttransform: translate(\n\t\t\tcalc(var(--liforma-et-parallax-x) * var(--liforma-et-fg-shift) * -1),\n\t\t\tcalc(var(--liforma-et-parallax-y) * var(--liforma-et-fg-shift) * -1)\n\t\t)\n\t\tscale(var(--liforma-et-fg-scale));\n\ttransform-origin: center center;\n\ttransition:\n\t\topacity var(--liforma-et-reveal-duration) ease,\n\t\ttransform 160ms ease;\n}\n\n.liforma-et--parallax {\n\t--liforma-et-bg-scale: 1.08;\n\t--liforma-et-fg-scale: 1.05;\n\t--liforma-et-avatar-scale: 1.08;\n\t--liforma-et-bg-shift: 10px;\n\t--liforma-et-fg-shift: 4px;\n\t--liforma-et-avatar-shift: 4px;\n}\n\n/* Promote layers only while parallax is active — avoids N compositor surfaces on galleries. */\n.liforma-et--parallax .liforma-et__background,\n.liforma-et--parallax .liforma-et__foreground,\n.liforma-et--parallax .liforma-et__avatar {\n\twill-change: transform;\n}\n\n.liforma-et--parallax.liforma-et--fit-face {\n\t--liforma-et-avatar-scale: 1.95;\n\t--liforma-et-bg-scale: 1.95;\n\t--liforma-et-fg-scale: 1.95;\n\t--liforma-et-avatar-shift: 0px;\n}\n\n.liforma-et--parallax.liforma-et--fit-medium {\n\t--liforma-et-avatar-scale: 1.42;\n\t--liforma-et-bg-scale: 1.42;\n\t--liforma-et-fg-scale: 1.42;\n\t--liforma-et-avatar-shift: 0px;\n}\n\n.liforma-et--scroll-parallax .liforma-et__background,\n.liforma-et--scroll-parallax .liforma-et__foreground,\n.liforma-et--scroll-parallax .liforma-et__avatar {\n\ttransition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.liforma-et {\n\t\t--liforma-et-reveal-duration: 0ms;\n\t}\n\n\t.liforma-et--parallax {\n\t\t--liforma-et-bg-scale: 1;\n\t\t--liforma-et-fg-scale: 1;\n\t\t--liforma-et-avatar-scale: 1;\n\t\t--liforma-et-bg-shift: 0px;\n\t\t--liforma-et-fg-shift: 0px;\n\t\t--liforma-et-avatar-shift: 0px;\n\t}\n\n\t.liforma-et--parallax.liforma-et--fit-face {\n\t\t--liforma-et-avatar-scale: 1.85;\n\t\t--liforma-et-bg-scale: 1.85;\n\t\t--liforma-et-fg-scale: 1.85;\n\t}\n\n\t.liforma-et__background,\n\t.liforma-et__foreground,\n\t.liforma-et__avatar {\n\t\ttransition: none;\n\t}\n}\n`.trim();\n","import {\n\tattachImageReady,\n\tattachLazyThumb,\n\tattachScrollLinkedParallax,\n\tattachThumbnailBox,\n\tEXPERIENCE_THUMBNAIL_CSS,\n\thorizontalParallaxFromViewportY,\n\tpickThumbnailLocationPlateSize,\n\tresolveThumbnailHref,\n\tresolveThumbnailInteraction,\n\tresolveThumbnailLayers,\n\tthumbnailFitClass,\n\ttype GalleryThumb\n} from '../thumbnail/index';\n\nconst ELEMENT_NAME = 'liforma-experience-thumbnail';\n\nfunction parseGalleryThumb(raw: string | null): GalleryThumb | undefined {\n\tif (!raw?.trim()) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as GalleryThumb;\n\t\tif (parsed && typeof parsed.avatarImage === 'string') return parsed;\n\t} catch {\n\t\t/* ignore */\n\t}\n\treturn undefined;\n}\n\nclass LiformaExperienceThumbnailElement extends HTMLElement {\n\tstatic get observedAttributes(): string[] {\n\t\treturn [\n\t\t\t'experience-id',\n\t\t\t'gallery-thumb',\n\t\t\t'avatar-image',\n\t\t\t'background-image',\n\t\t\t'foreground-image',\n\t\t\t'fallback-image',\n\t\t\t'alt',\n\t\t\t'href',\n\t\t\t'target',\n\t\t\t'rel',\n\t\t\t'return-url',\n\t\t\t'player-base-url',\n\t\t\t'parallax',\n\t\t\t'fit',\n\t\t\t'lazy',\n\t\t\t'normalize-urls',\n\t\t\t'cdn-base-url'\n\t\t];\n\t}\n\n\t#root: HTMLElement | null = null;\n\t#cleanup: (() => void)[] = [];\n\t#shouldLoad = false;\n\t#hovering = false;\n\t#pointerDown = false;\n\t#scrollParallax = false;\n\t#parallaxX = 0;\n\t#parallaxY = 0;\n\t#avatarLoadFailed = false;\n\t#backgroundReady = false;\n\t#avatarReady = false;\n\t#foregroundReady = false;\n\n\tconnectedCallback(): void {\n\t\tthis.#render();\n\t}\n\n\tdisconnectedCallback(): void {\n\t\tthis.#teardown();\n\t}\n\n\tattributeChangedCallback(): void {\n\t\tif (this.isConnected) this.#render();\n\t}\n\n\t#teardown(): void {\n\t\tfor (const fn of this.#cleanup) fn();\n\t\tthis.#cleanup = [];\n\t\tthis.#root = null;\n\t}\n\n\t#boolAttr(name: string, defaultValue: boolean): boolean {\n\t\tif (!this.hasAttribute(name)) return defaultValue;\n\t\tconst value = this.getAttribute(name);\n\t\tif (value === null || value === '' || value === 'true') return true;\n\t\tif (value === 'false') return false;\n\t\treturn defaultValue;\n\t}\n\n\t#render(): void {\n\t\tthis.#teardown();\n\n\t\tconst shadow = this.shadowRoot ?? this.attachShadow({ mode: 'open' });\n\t\tconst style = document.createElement('style');\n\t\tstyle.textContent = EXPERIENCE_THUMBNAIL_CSS;\n\t\tshadow.replaceChildren(style);\n\n\t\tconst alt = this.getAttribute('alt')?.trim() || 'Experience';\n\t\tconst hrefAttr = this.getAttribute('href');\n\t\tconst experienceId = this.getAttribute('experience-id');\n\t\tconst hasClickHandler = this.hasAttribute('onclick') || typeof this.onclick === 'function';\n\t\tconst interaction = resolveThumbnailInteraction({\n\t\t\thref: hrefAttr,\n\t\t\thasClickHandler,\n\t\t\texperienceId\n\t\t});\n\n\t\tconst normalizeUrls = this.#boolAttr('normalize-urls', true);\n\t\tlet locationPlateSize = pickThumbnailLocationPlateSize(0, 0);\n\t\tconst resolveLayers = () =>\n\t\t\tresolveThumbnailLayers({\n\t\t\t\tgalleryThumb: parseGalleryThumb(this.getAttribute('gallery-thumb')),\n\t\t\t\tavatarImage: this.getAttribute('avatar-image') ?? undefined,\n\t\t\t\tbackgroundImage: this.getAttribute('background-image') ?? undefined,\n\t\t\t\tforegroundImage: this.getAttribute('foreground-image') ?? undefined,\n\t\t\t\tfallbackImage: this.getAttribute('fallback-image') ?? undefined,\n\t\t\t\tnormalize: normalizeUrls,\n\t\t\t\tlocationPlateSize,\n\t\t\t\tcdnBaseUrl: this.getAttribute('cdn-base-url') ?? undefined\n\t\t\t});\n\t\tconst initialLayers = resolveLayers();\n\t\tif (!initialLayers) return;\n\t\tlet layers = initialLayers;\n\n\t\tconst parallaxEnabled = this.#boolAttr('parallax', true);\n\t\tconst fitAttr = this.getAttribute('fit')?.trim();\n\t\tconst fit = fitAttr === 'face' || fitAttr === 'medium' ? fitAttr : 'full';\n\t\tconst lazy = this.#boolAttr('lazy', true);\n\t\tthis.#shouldLoad = !lazy;\n\n\t\tconst isHostedLaunch =\n\t\t\tinteraction === 'anchor' && !hrefAttr?.trim() && Boolean(experienceId?.trim());\n\t\tconst resolvedHref = resolveThumbnailHref({\n\t\t\thref: hrefAttr ?? undefined,\n\t\t\texperienceId: experienceId ?? undefined,\n\t\t\treturnUrl: this.getAttribute('return-url') ?? undefined,\n\t\t\tplayerBaseUrl: this.getAttribute('player-base-url') ?? undefined,\n\t\t\tuseCurrentPageAsReturnUrl:\n\t\t\t\tisHostedLaunch && !this.getAttribute('return-url')?.trim()\n\t\t});\n\n\t\tlet root: HTMLElement;\n\t\tif (interaction === 'anchor' && resolvedHref) {\n\t\t\tconst a = document.createElement('a');\n\t\t\ta.href = resolvedHref;\n\t\t\tconst target = this.getAttribute('target');\n\t\t\tconst rel = this.getAttribute('rel');\n\t\t\tif (target) a.target = target;\n\t\t\tif (rel) a.rel = rel;\n\t\t\troot = a;\n\t\t} else if (interaction === 'button') {\n\t\t\tconst button = document.createElement('button');\n\t\t\tbutton.type = 'button';\n\t\t\troot = button;\n\t\t} else {\n\t\t\troot = document.createElement('div');\n\t\t\troot.setAttribute('role', 'img');\n\t\t}\n\n\t\troot.className = 'liforma-et';\n\t\troot.setAttribute('aria-label', alt);\n\t\tthis.#root = root;\n\t\tshadow.appendChild(root);\n\n\t\tconst syncClass = () => {\n\t\t\tconst hasScene = Boolean(layers.backgroundImage);\n\t\t\tconst parallaxActive = parallaxEnabled && (this.#hovering || this.#scrollParallax);\n\t\t\t// Fade placeholder as soon as any layer can paint — don't wait for the slowest plate.\n\t\t\tconst reveal =\n\t\t\t\tthis.#shouldLoad &&\n\t\t\t\t(this.#avatarReady || this.#backgroundReady || this.#foregroundReady);\n\t\t\troot.className = [\n\t\t\t\t'liforma-et',\n\t\t\t\thasScene ? 'liforma-et--scene' : '',\n\t\t\t\tthumbnailFitClass(fit),\n\t\t\t\tparallaxActive ? 'liforma-et--parallax' : '',\n\t\t\t\tthis.#scrollParallax ? 'liforma-et--scroll-parallax' : '',\n\t\t\t\tthis.#shouldLoad && !reveal ? 'liforma-et--loading' : '',\n\t\t\t\treveal ? 'liforma-et--revealed' : ''\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(' ');\n\t\t\troot.style.setProperty('--liforma-et-parallax-x', String(this.#parallaxX));\n\t\t\troot.style.setProperty('--liforma-et-parallax-y', String(this.#parallaxY));\n\t\t};\n\n\t\tthis.#cleanup.push(\n\t\t\tattachThumbnailBox(this, (width, height) => {\n\t\t\t\tconst next = pickThumbnailLocationPlateSize(width, height);\n\t\t\t\tif (next === locationPlateSize) return;\n\t\t\t\tlocationPlateSize = next;\n\t\t\t\tconst nextLayers = resolveLayers();\n\t\t\t\tif (!nextLayers) return;\n\t\t\t\tlayers = nextLayers;\n\t\t\t\tconst background = root.querySelector(\n\t\t\t\t\t'.liforma-et__background'\n\t\t\t\t) as HTMLImageElement | null;\n\t\t\t\tif (\n\t\t\t\t\tbackground &&\n\t\t\t\t\tlayers.backgroundImage &&\n\t\t\t\t\tbackground.getAttribute('src') !== layers.backgroundImage\n\t\t\t\t) {\n\t\t\t\t\tthis.#backgroundReady = false;\n\t\t\t\t\tbackground.classList.remove('liforma-et__media--ready');\n\t\t\t\t\tbackground.src = layers.backgroundImage;\n\t\t\t\t}\n\t\t\t\tconst foreground = root.querySelector(\n\t\t\t\t\t'.liforma-et__foreground'\n\t\t\t\t) as HTMLImageElement | null;\n\t\t\t\tif (\n\t\t\t\t\tforeground &&\n\t\t\t\t\tlayers.foregroundImage &&\n\t\t\t\t\tforeground.getAttribute('src') !== layers.foregroundImage\n\t\t\t\t) {\n\t\t\t\t\tthis.#foregroundReady = false;\n\t\t\t\t\tforeground.classList.remove('liforma-et__media--ready');\n\t\t\t\t\tforeground.src = layers.foregroundImage;\n\t\t\t\t}\n\t\t\t\tsyncClass();\n\t\t\t})\n\t\t);\n\t\tconst measured = resolveLayers();\n\t\tif (measured) layers = measured;\n\n\t\tconst placeholder = document.createElement('span');\n\t\tplaceholder.className = 'liforma-et__placeholder';\n\t\tplaceholder.setAttribute('aria-hidden', 'true');\n\t\troot.appendChild(placeholder);\n\n\t\tconst paintImages = () => {\n\t\t\tif (!this.#shouldLoad) return;\n\t\t\tconst avatarSrc =\n\t\t\t\tthis.#avatarLoadFailed && layers.fallbackImage\n\t\t\t\t\t? layers.fallbackImage\n\t\t\t\t\t: layers.avatarImage;\n\n\t\t\tif (layers.backgroundImage && !root.querySelector('.liforma-et__background')) {\n\t\t\t\tconst img = document.createElement('img');\n\t\t\t\timg.className = 'liforma-et__background liforma-et__media';\n\t\t\t\timg.alt = '';\n\t\t\t\timg.decoding = 'async';\n\t\t\t\timg.src = layers.backgroundImage;\n\t\t\t\tthis.#cleanup.push(\n\t\t\t\t\tattachImageReady(img, () => {\n\t\t\t\t\t\tthis.#backgroundReady = true;\n\t\t\t\t\t\timg.classList.add('liforma-et__media--ready');\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\troot.appendChild(img);\n\t\t\t}\n\n\t\t\tlet avatar = root.querySelector('.liforma-et__avatar') as HTMLImageElement | null;\n\t\t\tif (!avatar) {\n\t\t\t\tavatar = document.createElement('img');\n\t\t\t\tavatar.className = 'liforma-et__avatar liforma-et__media';\n\t\t\t\tavatar.alt = '';\n\t\t\t\tavatar.decoding = 'async';\n\t\t\t\tavatar.addEventListener('error', () => {\n\t\t\t\t\tif (layers.fallbackImage && !this.#avatarLoadFailed) {\n\t\t\t\t\t\tthis.#avatarLoadFailed = true;\n\t\t\t\t\t\tavatar!.src = layers.fallbackImage;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.#avatarReady = true;\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.#cleanup.push(\n\t\t\t\t\tattachImageReady(avatar, () => {\n\t\t\t\t\t\tthis.#avatarReady = true;\n\t\t\t\t\t\tavatar!.classList.add('liforma-et__media--ready');\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\troot.appendChild(avatar);\n\t\t\t}\n\t\t\tif (avatar.src !== avatarSrc) avatar.src = avatarSrc;\n\n\t\t\tif (layers.foregroundImage && !root.querySelector('.liforma-et__foreground')) {\n\t\t\t\tconst img = document.createElement('img');\n\t\t\t\timg.className = 'liforma-et__foreground liforma-et__media';\n\t\t\t\timg.alt = '';\n\t\t\t\timg.decoding = 'async';\n\t\t\t\timg.src = layers.foregroundImage;\n\t\t\t\tthis.#cleanup.push(\n\t\t\t\t\tattachImageReady(img, () => {\n\t\t\t\t\t\tthis.#foregroundReady = true;\n\t\t\t\t\t\timg.classList.add('liforma-et__media--ready');\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\troot.appendChild(img);\n\t\t\t}\n\t\t\tsyncClass();\n\t\t};\n\n\t\tif (lazy) {\n\t\t\tthis.#cleanup.push(\n\t\t\t\tattachLazyThumb(root, {\n\t\t\t\t\tonVisibleChange: (visible) => {\n\t\t\t\t\t\tif (!visible) return;\n\t\t\t\t\t\tthis.#shouldLoad = true;\n\t\t\t\t\t\tpaintImages();\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t);\n\t\t} else {\n\t\t\tpaintImages();\n\t\t}\n\n\t\tif (parallaxEnabled) {\n\t\t\tthis.#cleanup.push(\n\t\t\t\tattachScrollLinkedParallax(root, {\n\t\t\t\t\tonParallax: (x) => {\n\t\t\t\t\t\tthis.#scrollParallax = true;\n\t\t\t\t\t\tif (!this.#pointerDown) {\n\t\t\t\t\t\t\tthis.#parallaxX = x;\n\t\t\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tpointerActive: () => this.#pointerDown\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tconst onPointerMove = (event: PointerEvent) => {\n\t\t\t\tif (this.#scrollParallax) return;\n\t\t\t\tconst rect = root.getBoundingClientRect();\n\t\t\t\tif (rect.width <= 0 || rect.height <= 0) return;\n\t\t\t\tthis.#parallaxX = ((event.clientX - rect.left) / rect.width - 0.5) * 2;\n\t\t\t\tthis.#parallaxY = ((event.clientY - rect.top) / rect.height - 0.5) * 2;\n\t\t\t\tsyncClass();\n\t\t\t};\n\t\t\tconst onPointerDown = (event: PointerEvent) => {\n\t\t\t\tthis.#pointerDown = true;\n\t\t\t\tif (event.pointerType === 'mouse') this.#hovering = true;\n\t\t\t\tsyncClass();\n\t\t\t};\n\t\t\tconst onPointerUp = () => {\n\t\t\t\tconst wasDown = this.#pointerDown;\n\t\t\t\tthis.#pointerDown = false;\n\t\t\t\tthis.#hovering = false;\n\t\t\t\tif (this.#scrollParallax && wasDown) {\n\t\t\t\t\tthis.#parallaxX = horizontalParallaxFromViewportY(\n\t\t\t\t\t\troot.getBoundingClientRect(),\n\t\t\t\t\t\twindow.innerHeight\n\t\t\t\t\t);\n\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t} else if (!this.#scrollParallax) {\n\t\t\t\t\tthis.#parallaxX = 0;\n\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t}\n\t\t\t\tsyncClass();\n\t\t\t};\n\t\t\troot.addEventListener('pointermove', onPointerMove);\n\t\t\troot.addEventListener('pointerdown', onPointerDown);\n\t\t\troot.addEventListener('pointerup', onPointerUp);\n\t\t\troot.addEventListener('pointercancel', onPointerUp);\n\t\t\troot.addEventListener('pointerenter', (event: PointerEvent) => {\n\t\t\t\tif (event.pointerType === 'mouse') {\n\t\t\t\t\tthis.#hovering = true;\n\t\t\t\t\tsyncClass();\n\t\t\t\t}\n\t\t\t});\n\t\t\troot.addEventListener('pointerleave', () => {\n\t\t\t\tif (!this.#pointerDown) {\n\t\t\t\t\tthis.#hovering = false;\n\t\t\t\t\tif (!this.#scrollParallax) {\n\t\t\t\t\t\tthis.#parallaxX = 0;\n\t\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t\t}\n\t\t\t\t\tsyncClass();\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.#cleanup.push(() => {\n\t\t\t\troot.removeEventListener('pointermove', onPointerMove);\n\t\t\t\troot.removeEventListener('pointerdown', onPointerDown);\n\t\t\t\troot.removeEventListener('pointerup', onPointerUp);\n\t\t\t\troot.removeEventListener('pointercancel', onPointerUp);\n\t\t\t});\n\t\t}\n\n\t\troot.addEventListener('click', (event: MouseEvent) => {\n\t\t\tif (isHostedLaunch && !this.getAttribute('return-url')?.trim()) {\n\t\t\t\tconst next = resolveThumbnailHref({\n\t\t\t\t\texperienceId: experienceId ?? undefined,\n\t\t\t\t\tplayerBaseUrl: this.getAttribute('player-base-url') ?? undefined,\n\t\t\t\t\tuseCurrentPageAsReturnUrl: true\n\t\t\t\t});\n\t\t\t\tif (\n\t\t\t\t\tnext &&\n\t\t\t\t\tevent.button === 0 &&\n\t\t\t\t\t!event.metaKey &&\n\t\t\t\t\t!event.ctrlKey &&\n\t\t\t\t\t!event.shiftKey\n\t\t\t\t) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\twindow.location.assign(next);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tsyncClass();\n\t}\n}\n\nif (!customElements.get(ELEMENT_NAME)) {\n\tcustomElements.define(ELEMENT_NAME, LiformaExperienceThumbnailElement);\n}\n\nexport { LiformaExperienceThumbnailElement };\n","/**\n * Thumbnail-only CDN IIFE — registers `<liforma-experience-thumbnail>` without\n * the session player (`<liforma-experience>`) or widget.\n *\n * Reference the exported class so Rollup keeps the module (customElements.define\n * runs at import time). A bare side-effect import is tree-shaken in lib mode.\n */\nimport { LiformaExperienceThumbnailElement } from './liforma-experience-thumbnail';\n\ndeclare global {\n\tinterface Window {\n\t\tLiformaThumbnail?: {\n\t\t\treadonly sdkVersion: 'v2';\n\t\t\treadonly element: typeof LiformaExperienceThumbnailElement;\n\t\t};\n\t}\n}\n\nwindow.LiformaThumbnail = {\n\tsdkVersion: 'v2',\n\telement: LiformaExperienceThumbnailElement\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"thumbnail.js","names":["#render","#teardown","#cleanup","#root","#boolAttr","#shouldLoad","#hovering","#scrollParallax","#plateFormatReady","#avatarReady","#backgroundReady","#foregroundReady","#parallaxX","#parallaxY","#useAvif","#backgroundTriedFallback","#foregroundTriedFallback","#avatarLoadFailed","#avatarTriedWebpFallback","#pointerDown"],"sources":["../../../src/thumbnail/types.ts","../../../src/thumbnail/locationPlate.ts","../../../src/thumbnail/expandAssetUrl.ts","../../../src/thumbnail/toGalleryThumbUrl.ts","../../../src/shims/localStack.ts","../../../src/thumbnail/hostedLaunchUrl.ts","../../../src/thumbnail/thumbLoadScheduler.ts","../../../src/thumbnail/lazyLoad.ts","../../../src/thumbnail/scrollParallax.ts","../../../src/thumbnail/styles.ts","../../../src/thumbnail/plateDelivery.ts","../../../src/v2/liforma-experience-thumbnail.ts","../../../src/v2/thumbnail-cdn.ts"],"sourcesContent":["/**\n * Catalog-shaped layered gallery preview.\n * Values may be absolute URLs or Liforma paths (`/avatars/…`, `/locations/…/scene`).\n */\nexport type GalleryThumb = {\n\treadonly avatarImage: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n};\n\nexport type ThumbnailInteractionKind = 'anchor' | 'button' | 'presentational';\n\nexport type ResolveThumbnailInteractionInput = {\n\treadonly href?: string | null;\n\treadonly hasClickHandler?: boolean;\n\treadonly experienceId?: string | null;\n};\n\nexport type ThumbnailLayers = {\n\treadonly avatarImage: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly fallbackImage?: string;\n};\n\n/** Shared props across Svelte / React / Next (React uses `onClick`). */\nexport type ExperienceThumbnailBaseProps = {\n\treadonly galleryThumb?: GalleryThumb;\n\treadonly avatarImage?: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly alt: string;\n\treadonly experienceId?: string;\n\treadonly href?: string;\n\treadonly target?: string;\n\treadonly rel?: string;\n\treadonly returnUrl?: string;\n\treadonly playerBaseUrl?: string;\n\treadonly parallax?: boolean;\n\t/** Same as `<Experience fit>`: `full` (default), `medium`, or `face`. */\n\treadonly fit?: 'full' | 'face' | 'medium';\n\treadonly lazy?: boolean;\n\treadonly className?: string;\n\treadonly fallbackImage?: string;\n\treadonly normalizeUrls?: boolean;\n\t/** CDN origin for catalog paths. Default `https://cdn.liforma.ai`. */\n\treadonly cdnBaseUrl?: string;\n};\n\nexport function thumbnailFitClass(fit?: 'full' | 'face' | 'medium'): string {\n\tif (fit === 'face') return 'liforma-et--fit-face';\n\tif (fit === 'medium') return 'liforma-et--fit-medium';\n\treturn '';\n}\n","/** Location colour plates used by ExperienceThumbnail (no zoom/portrait crops). */\nexport const THUMBNAIL_LOCATION_PLATE_SIZES = ['256x256', '400x225', '800x450', '1672x941'] as const;\nexport type ThumbnailLocationPlateSize = (typeof THUMBNAIL_LOCATION_PLATE_SIZES)[number];\n\nconst LOCATION_SIZE_RE = /(-)(\\d+x\\d+)(\\.(?:webp|avif))(\\?|$)/i;\n\n/**\n * Square / portrait hosts keep the gallery crop. Landscape hosts use a 16:9 plate\n * so cover-fit fills height instead of clipping the top and bottom of a square.\n * Unmeasured boxes stay on `256x256`. Hosts 225px tall or smaller use `400x225`.\n */\nexport function pickThumbnailLocationPlateSize(\n\twidth: number,\n\theight: number\n): ThumbnailLocationPlateSize {\n\tif (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) {\n\t\treturn '256x256';\n\t}\n\tif (width <= height) return '256x256';\n\tif (height <= 225) return '400x225';\n\tif (width > 800 || height > 450) return '1672x941';\n\treturn '800x450';\n}\n\nexport function rewriteThumbnailLocationPlateSize(\n\turl: string,\n\tsize: ThumbnailLocationPlateSize\n): string {\n\tconst trimmed = url.trim();\n\tif (!trimmed) return trimmed;\n\treturn trimmed.replace(\n\t\tLOCATION_SIZE_RE,\n\t\t(_match, dash: string, _current: string, ext: string, end: string) =>\n\t\t\t`${dash}${size}${ext}${end}`\n\t);\n}\n\n/** Observe the thumb host and report CSS box size (for plate picking). */\nexport function attachThumbnailBox(\n\telement: HTMLElement,\n\tonBox: (width: number, height: number) => void\n): () => void {\n\tconst emit = () => {\n\t\tonBox(element.clientWidth, element.clientHeight);\n\t};\n\temit();\n\tif (typeof ResizeObserver === 'undefined') {\n\t\treturn () => {};\n\t}\n\tconst observer = new ResizeObserver(emit);\n\tobserver.observe(element);\n\treturn () => observer.disconnect();\n}\n","import {\n\trewriteThumbnailLocationPlateSize,\n\ttype ThumbnailLocationPlateSize\n} from './locationPlate';\n\n/** Default origin for Liforma gallery plates (catalog paths have no host). */\nexport const DEFAULT_THUMBNAIL_CDN_BASE_URL = 'https://cdn.liforma.ai';\n\nconst LOCATION_SIZE_STEM_RE = /-(256x256|400x225|400x450|800x450|836x971|1672x941|167x94)$/i;\n\nexport function normalizeThumbnailCdnBaseUrl(cdnBaseUrl?: string): string {\n\tconst trimmed = cdnBaseUrl?.trim().replace(/\\/$/, '');\n\treturn trimmed || DEFAULT_THUMBNAIL_CDN_BASE_URL;\n}\n\nfunction isHttpUrl(value: string): boolean {\n\treturn /^https?:\\/\\//i.test(value) || value.startsWith('//');\n}\n\nfunction absoluteHttpUrl(value: string): string {\n\tif (value.startsWith('//')) return `https:${value}`;\n\treturn value;\n}\n\nfunction splitPathAndSearch(value: string): { pathname: string; search: string } {\n\tconst q = value.indexOf('?');\n\tif (q === -1) return { pathname: value, search: '' };\n\treturn { pathname: value.slice(0, q), search: value.slice(q) };\n}\n\nfunction joinCdn(cdnBase: string, pathname: string, search = ''): string {\n\tconst path = pathname.startsWith('/') ? pathname : `/${pathname}`;\n\treturn `${cdnBase}${path}${search}`;\n}\n\n/**\n * Expand a catalog location path (or keep a full URL) to a sized plate URL.\n *\n * `/locations/modern/city/office/interior-desk-daytime` →\n * `{cdn}/locations/modern/city/office/interior-desk-daytime-800x450.webp`\n */\nexport function expandThumbnailLocationUrl(\n\tref: string,\n\tlocationPlateSize: ThumbnailLocationPlateSize,\n\tcdnBaseUrl?: string\n): string {\n\tconst raw = ref.trim();\n\tif (!raw) return raw;\n\tconst cdnBase = normalizeThumbnailCdnBaseUrl(cdnBaseUrl);\n\n\tif (isHttpUrl(raw)) {\n\t\treturn rewriteThumbnailLocationPlateSize(absoluteHttpUrl(raw), locationPlateSize);\n\t}\n\n\tconst { pathname: rawPath, search } = splitPathAndSearch(raw.replace(/\\\\/g, '/'));\n\tlet pathname = rawPath;\n\n\tif (!pathname.startsWith('/')) {\n\t\tpathname = `/locations/${pathname.replace(/^locations\\//i, '')}`;\n\t} else if (/^\\/locations\\//i.test(pathname)) {\n\t\t/* already a location path */\n\t} else if (/^\\/avatars\\//i.test(pathname)) {\n\t\treturn joinCdn(cdnBase, pathname, search);\n\t} else {\n\t\treturn raw;\n\t}\n\n\tif (/\\.webp$/i.test(pathname) && !LOCATION_SIZE_STEM_RE.test(pathname.replace(/\\.webp$/i, ''))) {\n\t\treturn joinCdn(cdnBase, pathname, search);\n\t}\n\n\tlet stem = pathname.replace(/^\\/locations\\//i, '').replace(/\\.webp$/i, '');\n\tstem = stem.replace(LOCATION_SIZE_STEM_RE, '');\n\treturn `${cdnBase}/locations/${stem}-${locationPlateSize}.webp${search}`;\n}\n\n/**\n * Expand a catalog avatar path (or keep a full URL) onto the public CDN.\n * `/avatars/{id}` → `{cdn}/avatars/{id}/256/{id}_neutral.webp`\n */\nexport function expandThumbnailAvatarUrl(ref: string, cdnBaseUrl?: string): string {\n\tconst raw = ref.trim();\n\tif (!raw) return raw;\n\tconst cdnBase = normalizeThumbnailCdnBaseUrl(cdnBaseUrl);\n\n\tif (isHttpUrl(raw)) {\n\t\treturn absoluteHttpUrl(raw);\n\t}\n\n\tconst { pathname: rawPath, search } = splitPathAndSearch(raw.replace(/\\\\/g, '/'));\n\tlet pathname = rawPath;\n\n\tif (!pathname.startsWith('/')) {\n\t\tif (/^avatars\\//i.test(pathname)) {\n\t\t\tpathname = `/${pathname}`;\n\t\t} else if (/^[^/]+$/.test(pathname) && !search) {\n\t\t\treturn `${cdnBase}/avatars/${pathname}/256/${pathname}_neutral.webp`;\n\t\t} else {\n\t\t\treturn raw;\n\t\t}\n\t}\n\n\tif (!/^\\/avatars\\//i.test(pathname)) {\n\t\treturn raw;\n\t}\n\n\tconst rest = pathname.replace(/^\\/avatars\\//i, '').replace(/\\/+$/, '');\n\tif (rest && !rest.includes('/') && !search) {\n\t\treturn `${cdnBase}/avatars/${rest}/256/${rest}_neutral.webp`;\n\t}\n\n\treturn joinCdn(cdnBase, pathname, search);\n}\n","import {\n\texpandThumbnailAvatarUrl,\n\texpandThumbnailLocationUrl,\n\tnormalizeThumbnailCdnBaseUrl\n} from './expandAssetUrl';\nimport type { ThumbnailLocationPlateSize } from './locationPlate';\n\nconst AVATAR_TIER_RE = /(\\/avatars\\/[^/]+\\/)(\\d+)(\\/)/i;\nconst AVATAR_CH_PLATE_RE = /_CH(\\.webp)/i;\n\nexport type { ThumbnailLocationPlateSize };\n\nfunction isLocationRef(raw: string): boolean {\n\tif (/\\/locations\\//i.test(raw) || /^locations\\//i.test(raw)) return true;\n\tif (/^https?:\\/\\//i.test(raw) || raw.startsWith('//')) return false;\n\tif (/^\\/?avatars\\//i.test(raw)) return false;\n\tif (raw.startsWith('/')) return false;\n\treturn raw.includes('/');\n}\n\n/**\n * Rewrite known Liforma CDN URLs to gallery thumb tiers.\n * Avatars: /{tier}/ → /256/; locations: -{WxH}.webp → the requested plate\n * (`256x256` by default, `400x225` / `800x450` / `1672x941` for landscape hosts).\n * Catalog paths (`/locations/…/scene`, `/avatars/{id}`) expand onto the CDN first.\n * Unrecognized URLs are returned unchanged.\n */\nexport function toGalleryThumbUrl(\n\turl: string,\n\tlocationPlateSize: ThumbnailLocationPlateSize = '256x256',\n\tcdnBaseUrl?: string\n): string {\n\tconst raw = url.trim();\n\tif (!raw) return raw;\n\n\tif (isLocationRef(raw)) {\n\t\treturn expandThumbnailLocationUrl(raw, locationPlateSize, cdnBaseUrl);\n\t}\n\n\tlet next = expandThumbnailAvatarUrl(raw, cdnBaseUrl);\n\tnext = next.replace(AVATAR_TIER_RE, (_match, prefix: string, tier: string, slash: string) => {\n\t\tif (tier === '256') return `${prefix}${tier}${slash}`;\n\t\treturn `${prefix}256${slash}`;\n\t});\n\treturn next.replace(AVATAR_CH_PLATE_RE, '_neutral$1');\n}\n\nexport function resolveThumbnailLayers(input: {\n\treadonly galleryThumb?: {\n\t\treadonly avatarImage?: string;\n\t\treadonly backgroundImage?: string;\n\t\treadonly foregroundImage?: string;\n\t};\n\treadonly avatarImage?: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly fallbackImage?: string;\n\t/** When false, keep caller absolute URLs (lightbox / preview). Paths still expand. Default true. */\n\treadonly normalize?: boolean;\n\t/** Location plate when expanding or rewriting location assets. Default `256x256`. */\n\treadonly locationPlateSize?: ThumbnailLocationPlateSize;\n\t/** CDN origin for catalog paths. Default `https://cdn.liforma.ai`. */\n\treadonly cdnBaseUrl?: string;\n}): {\n\treadonly avatarImage: string;\n\treadonly backgroundImage?: string;\n\treadonly foregroundImage?: string;\n\treadonly fallbackImage?: string;\n} | null {\n\tconst avatarSource =\n\t\tinput.avatarImage?.trim() ||\n\t\tinput.galleryThumb?.avatarImage?.trim() ||\n\t\t'';\n\tif (!avatarSource) return null;\n\n\tconst backgroundSource =\n\t\tinput.backgroundImage?.trim() || input.galleryThumb?.backgroundImage?.trim() || '';\n\tconst foregroundSource =\n\t\tinput.foregroundImage?.trim() || input.galleryThumb?.foregroundImage?.trim() || '';\n\tconst fallbackSource = input.fallbackImage?.trim() || '';\n\tconst plate = input.locationPlateSize ?? '256x256';\n\tconst cdnBaseUrl = normalizeThumbnailCdnBaseUrl(input.cdnBaseUrl);\n\tconst rewriteKnown = input.normalize !== false;\n\n\tconst rewriteAvatar = (ref: string) => {\n\t\tif (!rewriteKnown && /^https?:\\/\\//i.test(ref.trim())) return ref.trim();\n\t\treturn toGalleryThumbUrl(ref, plate, cdnBaseUrl);\n\t};\n\tconst rewriteLocation = (ref: string) => {\n\t\tif (!rewriteKnown && /^https?:\\/\\//i.test(ref.trim())) return ref.trim();\n\t\treturn expandThumbnailLocationUrl(ref, plate, cdnBaseUrl);\n\t};\n\n\treturn {\n\t\tavatarImage: rewriteAvatar(avatarSource),\n\t\t...(backgroundSource ? { backgroundImage: rewriteLocation(backgroundSource) } : {}),\n\t\t...(foregroundSource ? { foregroundImage: rewriteLocation(foregroundSource) } : {}),\n\t\t...(fallbackSource ? { fallbackImage: rewriteAvatar(fallbackSource) } : {})\n\t};\n}\n","/**\n * Opt-in local monorepo stack (api :3001, player :3002).\n * Never inferred from hostname alone — third-party demos run on localhost against production.\n */\nexport function useExplicitLocalStack(): boolean {\n\tif ((globalThis as { __LIFORMA_STACK?: string }).__LIFORMA_STACK === 'local') {\n\t\treturn true;\n\t}\n\n\tif (typeof location === 'undefined') {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\treturn new URLSearchParams(location.search).get('stack') === 'local';\n\t} catch {\n\t\treturn false;\n\t}\n}\n","import { useExplicitLocalStack } from '../shims/localStack.ts';\nimport type { ResolveThumbnailInteractionInput, ThumbnailInteractionKind } from './types';\n\nconst PRODUCTION_PLAYER_ORIGIN = 'https://player.liforma.ai';\nconst LOCAL_PLAYER_ORIGIN = 'http://localhost:3002';\n\n/** Priority: href → click-only → hosted (experienceId) → presentational. */\nexport function resolveThumbnailInteraction(\n\tinput: ResolveThumbnailInteractionInput\n): ThumbnailInteractionKind {\n\tif (input.href?.trim()) return 'anchor';\n\tif (input.hasClickHandler) return 'button';\n\tif (input.experienceId?.trim()) return 'anchor';\n\treturn 'presentational';\n}\n\nexport function resolvePlayerBaseUrl(playerBaseUrl?: string): string {\n\tconst configured = playerBaseUrl?.trim();\n\tif (configured) return configured.replace(/\\/$/, '');\n\n\tif (useExplicitLocalStack()) {\n\t\treturn LOCAL_PLAYER_ORIGIN;\n\t}\n\n\treturn PRODUCTION_PLAYER_ORIGIN;\n}\n\n/**\n * Build hosted player launch URL: `{player}/{experienceId}?returnUrl=…`\n * When `returnUrl` is omitted, callers should assign on click (SSR-safe) or pass an explicit URL.\n */\nexport function buildHostedLaunchUrl(options: {\n\treadonly experienceId: string;\n\treadonly returnUrl?: string;\n\treadonly playerBaseUrl?: string;\n}): string {\n\tconst experienceId = options.experienceId.trim();\n\tconst base = resolvePlayerBaseUrl(options.playerBaseUrl);\n\tconst url = new URL(`${base}/${encodeURIComponent(experienceId)}`);\n\tconst returnUrl = options.returnUrl?.trim();\n\tif (returnUrl) {\n\t\turl.searchParams.set('returnUrl', returnUrl);\n\t}\n\treturn url.toString();\n}\n\n/** Resolve href for anchor mode (app link or hosted launch). */\nexport function resolveThumbnailHref(options: {\n\treadonly href?: string;\n\treadonly experienceId?: string;\n\treadonly returnUrl?: string;\n\treadonly playerBaseUrl?: string;\n\t/** When true and returnUrl omitted, use window.location.href (browser only). */\n\treadonly useCurrentPageAsReturnUrl?: boolean;\n}): string | undefined {\n\tconst href = options.href?.trim();\n\tif (href) return href;\n\n\tconst experienceId = options.experienceId?.trim();\n\tif (!experienceId) return undefined;\n\n\tlet returnUrl = options.returnUrl?.trim();\n\tif (!returnUrl && options.useCurrentPageAsReturnUrl && typeof window !== 'undefined') {\n\t\treturnUrl = window.location.href;\n\t}\n\n\treturn buildHostedLaunchUrl({\n\t\texperienceId,\n\t\t...(returnUrl ? { returnUrl } : {}),\n\t\tplayerBaseUrl: options.playerBaseUrl\n\t});\n}\n","/** Touch / coarse pointers — limit how many thumbs start loading at once. */\nexport const COARSE_THUMB_LOAD_CONCURRENCY = 4;\n/** Delay before a started thumb frees its queue slot for the next card. */\nexport const COARSE_THUMB_LOAD_STAGGER_MS = 160;\n\nexport function prefersCoarsePointer(): boolean {\n\tif (typeof window === 'undefined') return false;\n\treturn window.matchMedia('(hover: none), (pointer: coarse)').matches;\n}\n\ntype QueuedLoad = {\n\treadonly start: () => void;\n\tcancelled: boolean;\n};\n\nlet inflight = 0;\nconst waiting: QueuedLoad[] = [];\n\nfunction maxConcurrent(): number {\n\treturn prefersCoarsePointer() ? COARSE_THUMB_LOAD_CONCURRENCY : Number.POSITIVE_INFINITY;\n}\n\nfunction staggerMs(): number {\n\treturn prefersCoarsePointer() ? COARSE_THUMB_LOAD_STAGGER_MS : 0;\n}\n\nfunction pump(): void {\n\tconst limit = maxConcurrent();\n\twhile (inflight < limit && waiting.length > 0) {\n\t\tconst next = waiting.shift();\n\t\tif (!next || next.cancelled) continue;\n\t\tinflight += 1;\n\t\tnext.start();\n\t\tconst delay = staggerMs();\n\t\tif (delay <= 0) {\n\t\t\tinflight = Math.max(0, inflight - 1);\n\t\t\tcontinue;\n\t\t}\n\t\twindow.setTimeout(() => {\n\t\t\tinflight = Math.max(0, inflight - 1);\n\t\t\tpump();\n\t\t}, delay);\n\t}\n}\n\n/**\n * On coarse pointers, start thumb image loads in small waves so old devices\n * are not hit with dozens of concurrent decodes. Fine pointers start immediately.\n * Returns a cancel function (no-op after start has run).\n */\nexport function scheduleThumbLoad(start: () => void): () => void {\n\tif (!prefersCoarsePointer()) {\n\t\tstart();\n\t\treturn () => {};\n\t}\n\n\tconst item: QueuedLoad = { start, cancelled: false };\n\twaiting.push(item);\n\tpump();\n\n\treturn () => {\n\t\titem.cancelled = true;\n\t};\n}\n\n/** Test helper — clear queue state between vitest cases. */\nexport function resetThumbLoadSchedulerForTests(): void {\n\tinflight = 0;\n\twaiting.length = 0;\n}\n","import { prefersCoarsePointer, scheduleThumbLoad } from './thumbLoadScheduler';\n\nexport type LazyThumbOptions = {\n\treadonly rootMargin?: string;\n\treadonly onVisibleChange: (visible: boolean) => void;\n};\n\n/** Desktop: prefetch earlier. Coarse/touch: tighter margin to cut burst loads. */\nexport function getDefaultLazyRootMargin(): string {\n\treturn prefersCoarsePointer() ? '96px 0px' : '280px 0px';\n}\n\nfunction parseVerticalRootMarginPx(rootMargin: string): number {\n\tconst first = rootMargin.trim().split(/\\s+/)[0] ?? '0px';\n\tconst value = Number.parseFloat(first);\n\treturn Number.isFinite(value) ? Math.abs(value) : 280;\n}\n\nfunction isNearViewport(element: HTMLElement, marginPx: number): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst rect = element.getBoundingClientRect();\n\treturn rect.bottom >= -marginPx && rect.top <= window.innerHeight + marginPx;\n}\n\n/** Observe the thumb and enable image loads when near the viewport. */\nexport function attachLazyThumb(element: HTMLElement, options: LazyThumbOptions): () => void {\n\tconst { onVisibleChange } = options;\n\tconst rootMargin = options.rootMargin ?? getDefaultLazyRootMargin();\n\tconst marginPx = parseVerticalRootMarginPx(rootMargin);\n\tlet visible = false;\n\tlet loadStarted = false;\n\tlet cancelScheduled: (() => void) | null = null;\n\n\tconst requestLoad = () => {\n\t\tif (loadStarted) return;\n\t\tcancelScheduled?.();\n\t\tcancelScheduled = scheduleThumbLoad(() => {\n\t\t\tcancelScheduled = null;\n\t\t\tloadStarted = true;\n\t\t\tonVisibleChange(true);\n\t\t});\n\t};\n\n\tconst setVisible = (next: boolean) => {\n\t\tif (next === visible) return;\n\t\tvisible = next;\n\t\tif (next) {\n\t\t\trequestLoad();\n\t\t\treturn;\n\t\t}\n\t\t// Keep images once started; only cancel a queued (not-yet-started) load.\n\t\tcancelScheduled?.();\n\t\tcancelScheduled = null;\n\t\tonVisibleChange(false);\n\t};\n\n\tif (typeof IntersectionObserver === 'undefined') {\n\t\trequestLoad();\n\t\treturn () => {\n\t\t\tcancelScheduled?.();\n\t\t\tcancelScheduled = null;\n\t\t};\n\t}\n\n\tconst observer = new IntersectionObserver(\n\t\t(entries) => {\n\t\t\tsetVisible(entries.some((entry) => entry.isIntersecting));\n\t\t},\n\t\t{ root: null, rootMargin, threshold: 0 }\n\t);\n\n\tobserver.observe(element);\n\n\tif (isNearViewport(element, marginPx)) {\n\t\tsetVisible(true);\n\t}\n\n\treturn () => {\n\t\tcancelScheduled?.();\n\t\tcancelScheduled = null;\n\t\tobserver.disconnect();\n\t};\n}\n\nexport function attachImageReady(element: HTMLImageElement, onReady: () => void): () => void {\n\tconst notify = () => onReady();\n\tif (element.complete) {\n\t\tqueueMicrotask(notify);\n\t} else {\n\t\telement.addEventListener('load', notify, { once: true });\n\t\telement.addEventListener('error', notify, { once: true });\n\t}\n\treturn () => {\n\t\telement.removeEventListener('load', notify);\n\t\telement.removeEventListener('error', notify);\n\t};\n}\n","/** Touch devices without hover — drive thumbnail parallax from scroll position. */\nexport function prefersScrollLinkedParallax(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tif (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return false;\n\treturn window.matchMedia('(hover: none), (pointer: coarse)').matches;\n}\n\n/**\n * Map an element's vertical centre in the viewport to horizontal parallax in [-1, 1].\n */\nexport function horizontalParallaxFromViewportY(rect: DOMRect, viewportHeight: number): number {\n\tconst centerY = rect.top + rect.height / 2;\n\tconst t = centerY / Math.max(viewportHeight, 1);\n\treturn (t - 0.5) * 2;\n}\n\nexport type ScrollLinkedParallaxOptions = {\n\treadonly onParallax: (x: number) => void;\n\treadonly pointerActive?: () => boolean;\n};\n\ntype ScrollParallaxSubscriber = {\n\treadonly element: HTMLElement;\n\treadonly onParallax: (x: number) => void;\n\treadonly pointerActive?: () => boolean;\n};\n\nconst subscribers = new Set<ScrollParallaxSubscriber>();\nlet listening = false;\nlet frame = 0;\n\nfunction syncAll(): void {\n\tframe = 0;\n\tif (subscribers.size === 0 || typeof window === 'undefined') return;\n\tconst viewportHeight = window.innerHeight;\n\tfor (const entry of subscribers) {\n\t\tif (entry.pointerActive?.()) continue;\n\t\tconst rect = entry.element.getBoundingClientRect();\n\t\t// Skip off-screen thumbs — avoids N layout reads + framework updates per scroll.\n\t\tif (rect.bottom < -64 || rect.top > viewportHeight + 64) continue;\n\t\tentry.onParallax(horizontalParallaxFromViewportY(rect, viewportHeight));\n\t}\n}\n\nfunction scheduleSync(): void {\n\tif (frame !== 0) return;\n\tframe = requestAnimationFrame(syncAll);\n}\n\nfunction ensureListening(): void {\n\tif (listening || typeof window === 'undefined') return;\n\tlistening = true;\n\twindow.addEventListener('scroll', scheduleSync, { passive: true });\n\twindow.addEventListener('resize', scheduleSync, { passive: true });\n}\n\nfunction stopListeningIfIdle(): void {\n\tif (subscribers.size > 0 || !listening || typeof window === 'undefined') return;\n\tlistening = false;\n\tif (frame !== 0) {\n\t\tcancelAnimationFrame(frame);\n\t\tframe = 0;\n\t}\n\twindow.removeEventListener('scroll', scheduleSync);\n\twindow.removeEventListener('resize', scheduleSync);\n}\n\n/**\n * Subscribe to scroll/resize and update horizontal parallax from viewport Y.\n * Uses one shared window listener for all thumbs (old iPads with large galleries).\n */\nexport function attachScrollLinkedParallax(\n\telement: HTMLElement,\n\toptions: ScrollLinkedParallaxOptions\n): () => void {\n\tif (!prefersScrollLinkedParallax()) return () => {};\n\n\tconst entry: ScrollParallaxSubscriber = {\n\t\telement,\n\t\tonParallax: options.onParallax,\n\t\tpointerActive: options.pointerActive\n\t};\n\tsubscribers.add(entry);\n\tensureListening();\n\n\tif (!entry.pointerActive?.()) {\n\t\toptions.onParallax(\n\t\t\thorizontalParallaxFromViewportY(element.getBoundingClientRect(), window.innerHeight)\n\t\t);\n\t}\n\n\treturn () => {\n\t\tsubscribers.delete(entry);\n\t\tstopListeningIfIdle();\n\t};\n}\n\n/** Test helper — clear shared scroll state between vitest cases. */\nexport function resetScrollLinkedParallaxForTests(): void {\n\tsubscribers.clear();\n\tif (typeof window !== 'undefined' && listening) {\n\t\twindow.removeEventListener('scroll', scheduleSync);\n\t\twindow.removeEventListener('resize', scheduleSync);\n\t}\n\tlistening = false;\n\tif (frame !== 0) {\n\t\tcancelAnimationFrame(frame);\n\t\tframe = 0;\n\t}\n}\n","/** Shared styles for ExperienceThumbnail (class prefix `liforma-et`). */\nexport const EXPERIENCE_THUMBNAIL_CSS = `\n:host {\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 0;\n}\n\n.liforma-et {\n\t--liforma-et-parallax-x: 0;\n\t--liforma-et-parallax-y: 0;\n\t--liforma-et-bg-scale: 1;\n\t--liforma-et-fg-scale: 1;\n\t--liforma-et-avatar-scale: 1;\n\t--liforma-et-bg-shift: 0px;\n\t--liforma-et-fg-shift: 0px;\n\t--liforma-et-avatar-shift: 0px;\n\t--liforma-et-reveal-duration: 420ms;\n\n\tbox-sizing: border-box;\n\tposition: relative;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 0;\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\toverflow: hidden;\n\tbackground: #111;\n\tcolor: inherit;\n\ttext-decoration: none;\n\tcursor: inherit;\n\t-webkit-tap-highlight-color: transparent;\n}\n\na.liforma-et,\nbutton.liforma-et {\n\tcursor: pointer;\n}\n\nbutton.liforma-et {\n\tfont: inherit;\n\tappearance: none;\n}\n\n.liforma-et__placeholder {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 0;\n\tbackground:\n\t\tradial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.06), transparent 58%),\n\t\tlinear-gradient(180deg, #1a1a1a 0%, #101010 100%);\n\topacity: 1;\n\ttransition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et--revealed .liforma-et__placeholder {\n\topacity: 0;\n}\n\n.liforma-et__media {\n\topacity: 0;\n\ttransition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et__media--ready {\n\topacity: 1;\n}\n\n.liforma-et__background {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 1;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center center;\n\ttransform: translate(\n\t\t\tcalc(var(--liforma-et-parallax-x) * var(--liforma-et-bg-shift)),\n\t\t\tcalc(var(--liforma-et-parallax-y) * var(--liforma-et-bg-shift))\n\t\t)\n\t\tscale(var(--liforma-et-bg-scale));\n\ttransform-origin: center center;\n\ttransition:\n\t\topacity var(--liforma-et-reveal-duration) ease,\n\t\ttransform 160ms ease;\n}\n\n.liforma-et__avatar {\n\tposition: relative;\n\tz-index: 2;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: contain;\n\tobject-position: center bottom;\n\ttransform: scale(var(--liforma-et-avatar-scale))\n\t\ttranslate(\n\t\t\tcalc(var(--liforma-et-parallax-x) * var(--liforma-et-avatar-shift)),\n\t\t\tcalc(var(--liforma-et-parallax-y) * var(--liforma-et-avatar-shift))\n\t\t);\n\ttransform-origin: center center;\n\ttransition:\n\t\topacity var(--liforma-et-reveal-duration) ease,\n\t\ttransform 160ms ease;\n}\n\n/* Gallery plates are typically square in a square tile — contain vs cover is a no-op\n without a zoom. Scale the whole scene from the upper third so Face matches\n player fit (location + avatar crop together). */\n.liforma-et--fit-face {\n\t--liforma-et-avatar-scale: 1.85;\n\t--liforma-et-bg-scale: 1.85;\n\t--liforma-et-fg-scale: 1.85;\n}\n\n.liforma-et--fit-face .liforma-et__avatar,\n.liforma-et--fit-face .liforma-et__background,\n.liforma-et--fit-face .liforma-et__foreground {\n\ttransform-origin: 50% 18%;\n}\n\n.liforma-et--fit-face .liforma-et__avatar {\n\tobject-fit: cover;\n\tobject-position: 50% 18%;\n}\n\n/* Bust / medium: less zoom than face so upper chest stays in frame (player 3.75H window). */\n.liforma-et--fit-medium {\n\t--liforma-et-avatar-scale: 1.35;\n\t--liforma-et-bg-scale: 1.35;\n\t--liforma-et-fg-scale: 1.35;\n}\n\n.liforma-et--fit-medium .liforma-et__avatar,\n.liforma-et--fit-medium .liforma-et__background,\n.liforma-et--fit-medium .liforma-et__foreground {\n\ttransform-origin: 50% 28%;\n}\n\n.liforma-et--fit-medium .liforma-et__avatar {\n\tobject-fit: cover;\n\tobject-position: 50% 28%;\n}\n\n.liforma-et__foreground {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 3;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center center;\n\tpointer-events: none;\n\ttransform: translate(\n\t\t\tcalc(var(--liforma-et-parallax-x) * var(--liforma-et-fg-shift) * -1),\n\t\t\tcalc(var(--liforma-et-parallax-y) * var(--liforma-et-fg-shift) * -1)\n\t\t)\n\t\tscale(var(--liforma-et-fg-scale));\n\ttransform-origin: center center;\n\ttransition:\n\t\topacity var(--liforma-et-reveal-duration) ease,\n\t\ttransform 160ms ease;\n}\n\n.liforma-et--parallax {\n\t--liforma-et-bg-scale: 1.08;\n\t--liforma-et-fg-scale: 1.05;\n\t--liforma-et-avatar-scale: 1.08;\n\t--liforma-et-bg-shift: 10px;\n\t--liforma-et-fg-shift: 4px;\n\t--liforma-et-avatar-shift: 4px;\n}\n\n/* Promote layers only while parallax is active — avoids N compositor surfaces on galleries. */\n.liforma-et--parallax .liforma-et__background,\n.liforma-et--parallax .liforma-et__foreground,\n.liforma-et--parallax .liforma-et__avatar {\n\twill-change: transform;\n}\n\n.liforma-et--parallax.liforma-et--fit-face {\n\t--liforma-et-avatar-scale: 1.95;\n\t--liforma-et-bg-scale: 1.95;\n\t--liforma-et-fg-scale: 1.95;\n\t--liforma-et-avatar-shift: 0px;\n}\n\n.liforma-et--parallax.liforma-et--fit-medium {\n\t--liforma-et-avatar-scale: 1.42;\n\t--liforma-et-bg-scale: 1.42;\n\t--liforma-et-fg-scale: 1.42;\n\t--liforma-et-avatar-shift: 0px;\n}\n\n.liforma-et--scroll-parallax .liforma-et__background,\n.liforma-et--scroll-parallax .liforma-et__foreground,\n.liforma-et--scroll-parallax .liforma-et__avatar {\n\ttransition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.liforma-et {\n\t\t--liforma-et-reveal-duration: 0ms;\n\t}\n\n\t.liforma-et--parallax {\n\t\t--liforma-et-bg-scale: 1;\n\t\t--liforma-et-fg-scale: 1;\n\t\t--liforma-et-avatar-scale: 1;\n\t\t--liforma-et-bg-shift: 0px;\n\t\t--liforma-et-fg-shift: 0px;\n\t\t--liforma-et-avatar-shift: 0px;\n\t}\n\n\t.liforma-et--parallax.liforma-et--fit-face {\n\t\t--liforma-et-avatar-scale: 1.85;\n\t\t--liforma-et-bg-scale: 1.85;\n\t\t--liforma-et-fg-scale: 1.85;\n\t}\n\n\t.liforma-et__background,\n\t.liforma-et__foreground,\n\t.liforma-et__avatar {\n\t\ttransition: none;\n\t}\n}\n`.trim();\n","/**\n * CDN colour-plate delivery for thumbnails (avatars + locations).\n * Catalogue identity stays `.webp`; fetch uses `.avif` when the browser can decode it.\n * Depth maps stay WebP. Costume rendered composites have AVIF siblings from the compose worker.\n */\n\nconst AVIF_PROBE_DATA_URI =\n\t'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';\n\nlet avifSupportPromise: Promise<boolean> | null = null;\n\n/** True when this URL is a colour plate WebP that may have an AVIF sibling (not depth). */\nexport function isColourPlateWebpUrl(url: string): boolean {\n\tconst trimmed = url.trim();\n\tif (!trimmed || trimmed.startsWith('blob:') || trimmed.startsWith('data:')) return false;\n\tconst pathOnly = trimmed.split(/[?#]/)[0] ?? trimmed;\n\tconst isAvatar = /\\/avatars\\//i.test(pathOnly);\n\tconst isLocation = /\\/locations\\//i.test(pathOnly);\n\tif (!isAvatar && !isLocation) return false;\n\tif (/\\.depth\\.webp$/i.test(pathOnly)) return false;\n\treturn /\\.webp$/i.test(pathOnly);\n}\n\n/** Rewrite a colour-plate `.webp` path to `.avif`, preserving query/hash. */\nexport function toAvifPlateUrl(url: string): string | null {\n\tif (!isColourPlateWebpUrl(url)) return null;\n\tconst qIndex = url.search(/[?#]/);\n\tif (qIndex < 0) return url.replace(/\\.webp$/i, '.avif');\n\tconst path = url.slice(0, qIndex);\n\tconst suffix = url.slice(qIndex);\n\treturn `${path.replace(/\\.webp$/i, '.avif')}${suffix}`;\n}\n\nexport function supportsAvifDecode(): Promise<boolean> {\n\tif (typeof Image === 'undefined') return Promise.resolve(false);\n\tif (!avifSupportPromise) {\n\t\tavifSupportPromise = new Promise<boolean>((resolve) => {\n\t\t\tconst img = new Image();\n\t\t\timg.onload = () => resolve(img.naturalWidth > 0 && img.naturalHeight > 0);\n\t\t\timg.onerror = () => resolve(false);\n\t\t\timg.src = AVIF_PROBE_DATA_URI;\n\t\t});\n\t}\n\treturn avifSupportPromise;\n}\n\nexport function resetAvifDecodeSupportCacheForTests(): void {\n\tavifSupportPromise = null;\n}\n\n/** WebP catalogue URL → AVIF sibling when `useAvif` is true. */\nexport function thumbnailPlateSrc(url: string, useAvif: boolean): string {\n\tif (!useAvif) return url;\n\treturn toAvifPlateUrl(url) ?? url;\n}\n\n/** Colour-plate `.avif` → catalogue `.webp` (compose worker / missing sibling). */\nexport function thumbnailColourPlateWebpFallbackUrl(url: string): string | null {\n\tconst trimmed = url.trim();\n\tconst qIndex = trimmed.search(/[?#]/);\n\tconst pathOnly = qIndex < 0 ? trimmed : trimmed.slice(0, qIndex);\n\tif (!/\\.avif$/i.test(pathOnly)) return null;\n\tconst webpPath = pathOnly.replace(/\\.avif$/i, '.webp');\n\treturn qIndex < 0 ? webpPath : `${webpPath}${trimmed.slice(qIndex)}`;\n}\n\n/** 400×225 is colour-only; if missing, step up to the published 800×450 sibling. */\nexport function thumbnailLocationPlateFallbackUrl(url: string): string | null {\n\tconst trimmed = url.trim();\n\tif (!/-400x225\\.(webp|avif)(?:$|[?#])/i.test(trimmed)) return null;\n\treturn trimmed.replace(/-400x225\\.(webp|avif)/i, '-800x450.$1');\n}\n","import {\n\tattachImageReady,\n\tattachLazyThumb,\n\tattachScrollLinkedParallax,\n\tattachThumbnailBox,\n\tEXPERIENCE_THUMBNAIL_CSS,\n\thorizontalParallaxFromViewportY,\n\tpickThumbnailLocationPlateSize,\n\tresolveThumbnailHref,\n\tresolveThumbnailInteraction,\n\tresolveThumbnailLayers,\n\tthumbnailFitClass,\n\tthumbnailColourPlateWebpFallbackUrl,\n\tthumbnailLocationPlateFallbackUrl,\n\tthumbnailPlateSrc,\n\tsupportsAvifDecode,\n\ttype GalleryThumb\n} from '../thumbnail/index';\n\nconst ELEMENT_NAME = 'liforma-experience-thumbnail';\n\nfunction parseGalleryThumb(raw: string | null): GalleryThumb | undefined {\n\tif (!raw?.trim()) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as GalleryThumb;\n\t\tif (parsed && typeof parsed.avatarImage === 'string') return parsed;\n\t} catch {\n\t\t/* ignore */\n\t}\n\treturn undefined;\n}\n\nclass LiformaExperienceThumbnailElement extends HTMLElement {\n\tstatic get observedAttributes(): string[] {\n\t\treturn [\n\t\t\t'experience-id',\n\t\t\t'gallery-thumb',\n\t\t\t'avatar-image',\n\t\t\t'background-image',\n\t\t\t'foreground-image',\n\t\t\t'fallback-image',\n\t\t\t'alt',\n\t\t\t'href',\n\t\t\t'target',\n\t\t\t'rel',\n\t\t\t'return-url',\n\t\t\t'player-base-url',\n\t\t\t'parallax',\n\t\t\t'fit',\n\t\t\t'lazy',\n\t\t\t'normalize-urls',\n\t\t\t'cdn-base-url'\n\t\t];\n\t}\n\n\t#root: HTMLElement | null = null;\n\t#cleanup: (() => void)[] = [];\n\t#shouldLoad = false;\n\t#hovering = false;\n\t#pointerDown = false;\n\t#scrollParallax = false;\n\t#parallaxX = 0;\n\t#parallaxY = 0;\n\t#avatarLoadFailed = false;\n\t#backgroundReady = false;\n\t#avatarReady = false;\n\t#foregroundReady = false;\n\t#useAvif = false;\n\t#plateFormatReady = false;\n\t#backgroundTriedFallback = false;\n\t#avatarTriedWebpFallback = false;\n\t#foregroundTriedFallback = false;\n\n\tconnectedCallback(): void {\n\t\tthis.#render();\n\t}\n\n\tdisconnectedCallback(): void {\n\t\tthis.#teardown();\n\t}\n\n\tattributeChangedCallback(): void {\n\t\tif (this.isConnected) this.#render();\n\t}\n\n\t#teardown(): void {\n\t\tfor (const fn of this.#cleanup) fn();\n\t\tthis.#cleanup = [];\n\t\tthis.#root = null;\n\t}\n\n\t#boolAttr(name: string, defaultValue: boolean): boolean {\n\t\tif (!this.hasAttribute(name)) return defaultValue;\n\t\tconst value = this.getAttribute(name);\n\t\tif (value === null || value === '' || value === 'true') return true;\n\t\tif (value === 'false') return false;\n\t\treturn defaultValue;\n\t}\n\n\t#render(): void {\n\t\tthis.#teardown();\n\n\t\tconst shadow = this.shadowRoot ?? this.attachShadow({ mode: 'open' });\n\t\tconst style = document.createElement('style');\n\t\tstyle.textContent = EXPERIENCE_THUMBNAIL_CSS;\n\t\tshadow.replaceChildren(style);\n\n\t\tconst alt = this.getAttribute('alt')?.trim() || 'Experience';\n\t\tconst hrefAttr = this.getAttribute('href');\n\t\tconst experienceId = this.getAttribute('experience-id');\n\t\tconst hasClickHandler = this.hasAttribute('onclick') || typeof this.onclick === 'function';\n\t\tconst interaction = resolveThumbnailInteraction({\n\t\t\thref: hrefAttr,\n\t\t\thasClickHandler,\n\t\t\texperienceId\n\t\t});\n\n\t\tconst normalizeUrls = this.#boolAttr('normalize-urls', true);\n\t\tlet locationPlateSize = pickThumbnailLocationPlateSize(0, 0);\n\t\tconst resolveLayers = () =>\n\t\t\tresolveThumbnailLayers({\n\t\t\t\tgalleryThumb: parseGalleryThumb(this.getAttribute('gallery-thumb')),\n\t\t\t\tavatarImage: this.getAttribute('avatar-image') ?? undefined,\n\t\t\t\tbackgroundImage: this.getAttribute('background-image') ?? undefined,\n\t\t\t\tforegroundImage: this.getAttribute('foreground-image') ?? undefined,\n\t\t\t\tfallbackImage: this.getAttribute('fallback-image') ?? undefined,\n\t\t\t\tnormalize: normalizeUrls,\n\t\t\t\tlocationPlateSize,\n\t\t\t\tcdnBaseUrl: this.getAttribute('cdn-base-url') ?? undefined\n\t\t\t});\n\t\tconst initialLayers = resolveLayers();\n\t\tif (!initialLayers) return;\n\t\tlet layers = initialLayers;\n\n\t\tconst parallaxEnabled = this.#boolAttr('parallax', true);\n\t\tconst fitAttr = this.getAttribute('fit')?.trim();\n\t\tconst fit = fitAttr === 'face' || fitAttr === 'medium' ? fitAttr : 'full';\n\t\tconst lazy = this.#boolAttr('lazy', true);\n\t\tthis.#shouldLoad = !lazy;\n\n\t\tconst isHostedLaunch =\n\t\t\tinteraction === 'anchor' && !hrefAttr?.trim() && Boolean(experienceId?.trim());\n\t\tconst resolvedHref = resolveThumbnailHref({\n\t\t\thref: hrefAttr ?? undefined,\n\t\t\texperienceId: experienceId ?? undefined,\n\t\t\treturnUrl: this.getAttribute('return-url') ?? undefined,\n\t\t\tplayerBaseUrl: this.getAttribute('player-base-url') ?? undefined,\n\t\t\tuseCurrentPageAsReturnUrl:\n\t\t\t\tisHostedLaunch && !this.getAttribute('return-url')?.trim()\n\t\t});\n\n\t\tlet root: HTMLElement;\n\t\tif (interaction === 'anchor' && resolvedHref) {\n\t\t\tconst a = document.createElement('a');\n\t\t\ta.href = resolvedHref;\n\t\t\tconst target = this.getAttribute('target');\n\t\t\tconst rel = this.getAttribute('rel');\n\t\t\tif (target) a.target = target;\n\t\t\tif (rel) a.rel = rel;\n\t\t\troot = a;\n\t\t} else if (interaction === 'button') {\n\t\t\tconst button = document.createElement('button');\n\t\t\tbutton.type = 'button';\n\t\t\troot = button;\n\t\t} else {\n\t\t\troot = document.createElement('div');\n\t\t\troot.setAttribute('role', 'img');\n\t\t}\n\n\t\troot.className = 'liforma-et';\n\t\troot.setAttribute('aria-label', alt);\n\t\tthis.#root = root;\n\t\tshadow.appendChild(root);\n\n\t\tconst syncClass = () => {\n\t\t\tconst hasScene = Boolean(layers.backgroundImage);\n\t\t\tconst parallaxActive = parallaxEnabled && (this.#hovering || this.#scrollParallax);\n\t\t\t// Fade placeholder as soon as any layer can paint — don't wait for the slowest plate.\n\t\t\tconst reveal =\n\t\t\t\tthis.#shouldLoad &&\n\t\t\t\tthis.#plateFormatReady &&\n\t\t\t\t(this.#avatarReady || this.#backgroundReady || this.#foregroundReady);\n\t\t\troot.className = [\n\t\t\t\t'liforma-et',\n\t\t\t\thasScene ? 'liforma-et--scene' : '',\n\t\t\t\tthumbnailFitClass(fit),\n\t\t\t\tparallaxActive ? 'liforma-et--parallax' : '',\n\t\t\t\tthis.#scrollParallax ? 'liforma-et--scroll-parallax' : '',\n\t\t\t\tthis.#shouldLoad && !reveal ? 'liforma-et--loading' : '',\n\t\t\t\treveal ? 'liforma-et--revealed' : ''\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(' ');\n\t\t\troot.style.setProperty('--liforma-et-parallax-x', String(this.#parallaxX));\n\t\t\troot.style.setProperty('--liforma-et-parallax-y', String(this.#parallaxY));\n\t\t};\n\n\t\tthis.#cleanup.push(\n\t\t\tattachThumbnailBox(this, (width, height) => {\n\t\t\t\tconst next = pickThumbnailLocationPlateSize(width, height);\n\t\t\t\tif (next === locationPlateSize) return;\n\t\t\t\tlocationPlateSize = next;\n\t\t\t\tconst nextLayers = resolveLayers();\n\t\t\t\tif (!nextLayers) return;\n\t\t\t\tlayers = nextLayers;\n\t\t\t\tconst background = root.querySelector(\n\t\t\t\t\t'.liforma-et__background'\n\t\t\t\t) as HTMLImageElement | null;\n\t\t\t\tif (\n\t\t\t\t\tbackground &&\n\t\t\t\t\tlayers.backgroundImage &&\n\t\t\t\t\tbackground.getAttribute('src') !==\n\t\t\t\t\t\tthumbnailPlateSrc(layers.backgroundImage, this.#useAvif)\n\t\t\t\t) {\n\t\t\t\t\tthis.#backgroundReady = false;\n\t\t\t\t\tthis.#backgroundTriedFallback = false;\n\t\t\t\t\tbackground.classList.remove('liforma-et__media--ready');\n\t\t\t\t\tbackground.src = thumbnailPlateSrc(layers.backgroundImage, this.#useAvif);\n\t\t\t\t}\n\t\t\t\tconst foreground = root.querySelector(\n\t\t\t\t\t'.liforma-et__foreground'\n\t\t\t\t) as HTMLImageElement | null;\n\t\t\t\tif (\n\t\t\t\t\tforeground &&\n\t\t\t\t\tlayers.foregroundImage &&\n\t\t\t\t\tforeground.getAttribute('src') !==\n\t\t\t\t\t\tthumbnailPlateSrc(layers.foregroundImage, this.#useAvif)\n\t\t\t\t) {\n\t\t\t\t\tthis.#foregroundReady = false;\n\t\t\t\t\tthis.#foregroundTriedFallback = false;\n\t\t\t\t\tforeground.classList.remove('liforma-et__media--ready');\n\t\t\t\t\tforeground.src = thumbnailPlateSrc(layers.foregroundImage, this.#useAvif);\n\t\t\t\t}\n\t\t\t\tsyncClass();\n\t\t\t})\n\t\t);\n\t\tconst measured = resolveLayers();\n\t\tif (measured) layers = measured;\n\n\t\tconst placeholder = document.createElement('span');\n\t\tplaceholder.className = 'liforma-et__placeholder';\n\t\tplaceholder.setAttribute('aria-hidden', 'true');\n\t\troot.appendChild(placeholder);\n\n\t\tconst paintImages = () => {\n\t\t\tif (!this.#shouldLoad) return;\n\t\t\tif (!this.#plateFormatReady) {\n\t\t\t\tvoid supportsAvifDecode().then((ok) => {\n\t\t\t\t\tthis.#useAvif = ok;\n\t\t\t\t\tthis.#plateFormatReady = true;\n\t\t\t\t\tpaintImages();\n\t\t\t\t\tsyncClass();\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst avatarSrc = thumbnailPlateSrc(\n\t\t\t\tthis.#avatarLoadFailed && layers.fallbackImage\n\t\t\t\t\t? layers.fallbackImage\n\t\t\t\t\t: layers.avatarImage,\n\t\t\t\tthis.#useAvif\n\t\t\t);\n\n\t\t\tif (layers.backgroundImage && !root.querySelector('.liforma-et__background')) {\n\t\t\t\tconst img = document.createElement('img');\n\t\t\t\timg.className = 'liforma-et__background liforma-et__media';\n\t\t\t\timg.alt = '';\n\t\t\t\timg.decoding = 'async';\n\t\t\t\timg.src = thumbnailPlateSrc(layers.backgroundImage, this.#useAvif);\n\t\t\t\timg.addEventListener('error', () => {\n\t\t\t\t\tconst fallback = thumbnailLocationPlateFallbackUrl(img.src);\n\t\t\t\t\tif (!fallback || this.#backgroundTriedFallback) return;\n\t\t\t\t\tthis.#backgroundTriedFallback = true;\n\t\t\t\t\timg.src = fallback;\n\t\t\t\t});\n\t\t\t\tthis.#cleanup.push(\n\t\t\t\t\tattachImageReady(img, () => {\n\t\t\t\t\t\tthis.#backgroundReady = true;\n\t\t\t\t\t\timg.classList.add('liforma-et__media--ready');\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\troot.appendChild(img);\n\t\t\t}\n\n\t\t\tlet avatar = root.querySelector('.liforma-et__avatar') as HTMLImageElement | null;\n\t\t\tif (!avatar) {\n\t\t\t\tavatar = document.createElement('img');\n\t\t\t\tavatar.className = 'liforma-et__avatar liforma-et__media';\n\t\t\t\tavatar.alt = '';\n\t\t\t\tavatar.decoding = 'async';\n\t\t\t\tavatar.addEventListener('error', (event) => {\n\t\t\t\t\tconst img = event.currentTarget as HTMLImageElement;\n\t\t\t\t\tconst webp = thumbnailColourPlateWebpFallbackUrl(img.src);\n\t\t\t\t\tif (webp && !this.#avatarTriedWebpFallback) {\n\t\t\t\t\t\tthis.#avatarTriedWebpFallback = true;\n\t\t\t\t\t\timg.src = webp;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (layers.fallbackImage && !this.#avatarLoadFailed) {\n\t\t\t\t\t\tthis.#avatarLoadFailed = true;\n\t\t\t\t\t\tavatar!.src = thumbnailPlateSrc(layers.fallbackImage, this.#useAvif);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.#avatarReady = true;\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tthis.#cleanup.push(\n\t\t\t\t\tattachImageReady(avatar, () => {\n\t\t\t\t\t\tthis.#avatarReady = true;\n\t\t\t\t\t\tavatar!.classList.add('liforma-et__media--ready');\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\troot.appendChild(avatar);\n\t\t\t}\n\t\t\tif (avatar.src !== avatarSrc) {\n\t\t\t\tthis.#avatarTriedWebpFallback = false;\n\t\t\t\tavatar.src = avatarSrc;\n\t\t\t}\n\n\t\t\tif (layers.foregroundImage && !root.querySelector('.liforma-et__foreground')) {\n\t\t\t\tconst img = document.createElement('img');\n\t\t\t\timg.className = 'liforma-et__foreground liforma-et__media';\n\t\t\t\timg.alt = '';\n\t\t\t\timg.decoding = 'async';\n\t\t\t\timg.src = thumbnailPlateSrc(layers.foregroundImage, this.#useAvif);\n\t\t\t\timg.addEventListener('error', () => {\n\t\t\t\t\tconst fallback = thumbnailLocationPlateFallbackUrl(img.src);\n\t\t\t\t\tif (!fallback || this.#foregroundTriedFallback) return;\n\t\t\t\t\tthis.#foregroundTriedFallback = true;\n\t\t\t\t\timg.src = fallback;\n\t\t\t\t});\n\t\t\t\tthis.#cleanup.push(\n\t\t\t\t\tattachImageReady(img, () => {\n\t\t\t\t\t\tthis.#foregroundReady = true;\n\t\t\t\t\t\timg.classList.add('liforma-et__media--ready');\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\troot.appendChild(img);\n\t\t\t}\n\t\t\tsyncClass();\n\t\t};\n\n\t\tif (lazy) {\n\t\t\tthis.#cleanup.push(\n\t\t\t\tattachLazyThumb(root, {\n\t\t\t\t\tonVisibleChange: (visible) => {\n\t\t\t\t\t\tif (!visible) return;\n\t\t\t\t\t\tthis.#shouldLoad = true;\n\t\t\t\t\t\tpaintImages();\n\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t);\n\t\t} else {\n\t\t\tpaintImages();\n\t\t}\n\n\t\tif (parallaxEnabled) {\n\t\t\tthis.#cleanup.push(\n\t\t\t\tattachScrollLinkedParallax(root, {\n\t\t\t\t\tonParallax: (x) => {\n\t\t\t\t\t\tthis.#scrollParallax = true;\n\t\t\t\t\t\tif (!this.#pointerDown) {\n\t\t\t\t\t\t\tthis.#parallaxX = x;\n\t\t\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t\t\t\tsyncClass();\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tpointerActive: () => this.#pointerDown\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tconst onPointerMove = (event: PointerEvent) => {\n\t\t\t\tif (this.#scrollParallax) return;\n\t\t\t\tconst rect = root.getBoundingClientRect();\n\t\t\t\tif (rect.width <= 0 || rect.height <= 0) return;\n\t\t\t\tthis.#parallaxX = ((event.clientX - rect.left) / rect.width - 0.5) * 2;\n\t\t\t\tthis.#parallaxY = ((event.clientY - rect.top) / rect.height - 0.5) * 2;\n\t\t\t\tsyncClass();\n\t\t\t};\n\t\t\tconst onPointerDown = (event: PointerEvent) => {\n\t\t\t\tthis.#pointerDown = true;\n\t\t\t\tif (event.pointerType === 'mouse') this.#hovering = true;\n\t\t\t\tsyncClass();\n\t\t\t};\n\t\t\tconst onPointerUp = () => {\n\t\t\t\tconst wasDown = this.#pointerDown;\n\t\t\t\tthis.#pointerDown = false;\n\t\t\t\tthis.#hovering = false;\n\t\t\t\tif (this.#scrollParallax && wasDown) {\n\t\t\t\t\tthis.#parallaxX = horizontalParallaxFromViewportY(\n\t\t\t\t\t\troot.getBoundingClientRect(),\n\t\t\t\t\t\twindow.innerHeight\n\t\t\t\t\t);\n\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t} else if (!this.#scrollParallax) {\n\t\t\t\t\tthis.#parallaxX = 0;\n\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t}\n\t\t\t\tsyncClass();\n\t\t\t};\n\t\t\troot.addEventListener('pointermove', onPointerMove);\n\t\t\troot.addEventListener('pointerdown', onPointerDown);\n\t\t\troot.addEventListener('pointerup', onPointerUp);\n\t\t\troot.addEventListener('pointercancel', onPointerUp);\n\t\t\troot.addEventListener('pointerenter', (event: PointerEvent) => {\n\t\t\t\tif (event.pointerType === 'mouse') {\n\t\t\t\t\tthis.#hovering = true;\n\t\t\t\t\tsyncClass();\n\t\t\t\t}\n\t\t\t});\n\t\t\troot.addEventListener('pointerleave', () => {\n\t\t\t\tif (!this.#pointerDown) {\n\t\t\t\t\tthis.#hovering = false;\n\t\t\t\t\tif (!this.#scrollParallax) {\n\t\t\t\t\t\tthis.#parallaxX = 0;\n\t\t\t\t\t\tthis.#parallaxY = 0;\n\t\t\t\t\t}\n\t\t\t\t\tsyncClass();\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.#cleanup.push(() => {\n\t\t\t\troot.removeEventListener('pointermove', onPointerMove);\n\t\t\t\troot.removeEventListener('pointerdown', onPointerDown);\n\t\t\t\troot.removeEventListener('pointerup', onPointerUp);\n\t\t\t\troot.removeEventListener('pointercancel', onPointerUp);\n\t\t\t});\n\t\t}\n\n\t\troot.addEventListener('click', (event: MouseEvent) => {\n\t\t\tif (isHostedLaunch && !this.getAttribute('return-url')?.trim()) {\n\t\t\t\tconst next = resolveThumbnailHref({\n\t\t\t\t\texperienceId: experienceId ?? undefined,\n\t\t\t\t\tplayerBaseUrl: this.getAttribute('player-base-url') ?? undefined,\n\t\t\t\t\tuseCurrentPageAsReturnUrl: true\n\t\t\t\t});\n\t\t\t\tif (\n\t\t\t\t\tnext &&\n\t\t\t\t\tevent.button === 0 &&\n\t\t\t\t\t!event.metaKey &&\n\t\t\t\t\t!event.ctrlKey &&\n\t\t\t\t\t!event.shiftKey\n\t\t\t\t) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\twindow.location.assign(next);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tsyncClass();\n\t}\n}\n\nif (!customElements.get(ELEMENT_NAME)) {\n\tcustomElements.define(ELEMENT_NAME, LiformaExperienceThumbnailElement);\n}\n\nexport { LiformaExperienceThumbnailElement };\n","/**\n * Thumbnail-only CDN IIFE — registers `<liforma-experience-thumbnail>` without\n * the session player (`<liforma-experience>`) or widget.\n *\n * Reference the exported class so Rollup keeps the module (customElements.define\n * runs at import time). A bare side-effect import is tree-shaken in lib mode.\n */\nimport { LiformaExperienceThumbnailElement } from './liforma-experience-thumbnail';\n\ndeclare global {\n\tinterface Window {\n\t\tLiformaThumbnail?: {\n\t\t\treadonly sdkVersion: 'v2';\n\t\t\treadonly element: typeof LiformaExperienceThumbnailElement;\n\t\t};\n\t}\n}\n\nwindow.LiformaThumbnail = {\n\tsdkVersion: 'v2',\n\telement: LiformaExperienceThumbnailElement\n};\n"],"mappings":"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"}
@@ -78,6 +78,8 @@
78
78
  castSize,
79
79
  embedBaseUrl,
80
80
  debug = false,
81
+ previewBackground,
82
+ previewForeground,
81
83
  returnUrl,
82
84
  onReady,
83
85
  onStarted,
@@ -324,6 +326,8 @@
324
326
  embedBaseUrl,
325
327
  debug,
326
328
  speechOnly,
329
+ previewBackground,
330
+ previewForeground,
327
331
  conversationProcessor
328
332
  };
329
333
  }
@@ -344,6 +348,8 @@
344
348
  mode,
345
349
  speechInputMode,
346
350
  speechOnly,
351
+ previewBackground,
352
+ previewForeground,
347
353
  attribution,
348
354
  alreadyConverted,
349
355
  conversationProcessor
@@ -365,6 +371,8 @@
365
371
  mode,
366
372
  speechInputMode,
367
373
  speechOnly,
374
+ previewBackground,
375
+ previewForeground,
368
376
  attribution,
369
377
  alreadyConverted,
370
378
  conversationProcessor
@@ -14,7 +14,11 @@
14
14
  resolveThumbnailHref,
15
15
  resolveThumbnailInteraction,
16
16
  resolveThumbnailLayers,
17
+ supportsAvifDecode,
17
18
  thumbnailFitClass,
19
+ thumbnailColourPlateWebpFallbackUrl,
20
+ thumbnailLocationPlateFallbackUrl,
21
+ thumbnailPlateSrc,
18
22
  type GalleryThumb
19
23
  } from '@liforma/client/thumbnail';
20
24
 
@@ -100,6 +104,11 @@
100
104
  let avatarReady = $state(false);
101
105
  let foregroundReady = $state(false);
102
106
  let detachGlobalPointerEnd: (() => void) | null = null;
107
+ let useAvif = $state(false);
108
+ let plateFormatReady = $state(false);
109
+ let backgroundTriedFallback = $state(false);
110
+ let foregroundTriedFallback = $state(false);
111
+ let avatarTriedWebpFallback = $state(false);
103
112
 
104
113
  const parallaxEnabled = $derived(parallax !== false);
105
114
  const parallaxLayersActive = $derived(parallaxEnabled && (hovering || scrollParallax));
@@ -112,7 +121,7 @@
112
121
  );
113
122
  /** Fade placeholder as soon as any layer can paint — don't wait for the slowest plate. */
114
123
  const revealThumb = $derived(
115
- shouldLoad && (avatarReady || backgroundReady || foregroundReady)
124
+ shouldLoad && plateFormatReady && (avatarReady || backgroundReady || foregroundReady)
116
125
  );
117
126
 
118
127
  $effect(() => {
@@ -150,11 +159,18 @@
150
159
  avatarReady = false;
151
160
  foregroundReady = false;
152
161
  avatarLoadFailed = false;
162
+ backgroundTriedFallback = false;
163
+ foregroundTriedFallback = false;
164
+ avatarTriedWebpFallback = false;
153
165
  });
154
166
 
155
167
  onMount(() => {
156
168
  ensureExperienceThumbnailStyles(EXPERIENCE_THUMBNAIL_CSS);
157
169
  refreshHref();
170
+ void supportsAvifDecode().then((ok) => {
171
+ useAvif = ok;
172
+ plateFormatReady = true;
173
+ });
158
174
  });
159
175
 
160
176
  function imageReadyAttachment(onReady: () => void): Attachment<HTMLImageElement> {
@@ -258,10 +274,33 @@
258
274
  }
259
275
  }
260
276
 
261
- function onAvatarError(): void {
277
+ function onAvatarError(event: Event): void {
278
+ const img = event.currentTarget as HTMLImageElement;
279
+ const webp = thumbnailColourPlateWebpFallbackUrl(img.src);
280
+ if (webp && !avatarTriedWebpFallback) {
281
+ avatarTriedWebpFallback = true;
282
+ img.src = webp;
283
+ return;
284
+ }
262
285
  if (layers?.fallbackImage) avatarLoadFailed = true;
263
286
  }
264
287
 
288
+ function onBackgroundError(event: Event): void {
289
+ const img = event.currentTarget as HTMLImageElement;
290
+ const fallback = thumbnailLocationPlateFallbackUrl(img.src);
291
+ if (!fallback || backgroundTriedFallback) return;
292
+ backgroundTriedFallback = true;
293
+ img.src = fallback;
294
+ }
295
+
296
+ function onForegroundError(event: Event): void {
297
+ const img = event.currentTarget as HTMLImageElement;
298
+ const fallback = thumbnailLocationPlateFallbackUrl(img.src);
299
+ if (!fallback || foregroundTriedFallback) return;
300
+ foregroundTriedFallback = true;
301
+ img.src = fallback;
302
+ }
303
+
265
304
  function handleClick(event: MouseEvent): void {
266
305
  onclick?.(event);
267
306
  if (event.defaultPrevented) return;
@@ -363,23 +402,24 @@
363
402
 
364
403
  {#snippet media()}
365
404
  <span class="liforma-et__placeholder" aria-hidden="true"></span>
366
- {#if shouldLoad && layers?.backgroundImage}
405
+ {#if shouldLoad && plateFormatReady && layers?.backgroundImage}
367
406
  <img
368
407
  class="liforma-et__background liforma-et__media"
369
408
  class:liforma-et__media--ready={backgroundReady}
370
- src={layers.backgroundImage}
409
+ src={thumbnailPlateSrc(layers.backgroundImage, useAvif)}
371
410
  alt=""
372
411
  decoding="async"
373
412
  {@attach imageReadyAttachment(() => {
374
413
  backgroundReady = true;
375
414
  })}
415
+ onerror={onBackgroundError}
376
416
  />
377
417
  {/if}
378
- {#if shouldLoad && resolvedAvatarImage}
418
+ {#if shouldLoad && plateFormatReady && resolvedAvatarImage}
379
419
  <img
380
420
  class="liforma-et__avatar liforma-et__media"
381
421
  class:liforma-et__media--ready={avatarReady}
382
- src={resolvedAvatarImage}
422
+ src={thumbnailPlateSrc(resolvedAvatarImage, useAvif)}
383
423
  alt=""
384
424
  decoding="async"
385
425
  {@attach imageReadyAttachment(() => {
@@ -388,16 +428,17 @@
388
428
  onerror={onAvatarError}
389
429
  />
390
430
  {/if}
391
- {#if shouldLoad && layers?.foregroundImage}
431
+ {#if shouldLoad && plateFormatReady && layers?.foregroundImage}
392
432
  <img
393
433
  class="liforma-et__foreground liforma-et__media"
394
434
  class:liforma-et__media--ready={foregroundReady}
395
- src={layers.foregroundImage}
435
+ src={thumbnailPlateSrc(layers.foregroundImage, useAvif)}
396
436
  alt=""
397
437
  decoding="async"
398
438
  {@attach imageReadyAttachment(() => {
399
439
  foregroundReady = true;
400
440
  })}
441
+ onerror={onForegroundError}
401
442
  />
402
443
  {/if}
403
444
  {/snippet}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liforma/client",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
4
4
  "type": "module",
5
5
  "description": "Liforma browser SDK — Experience sessions, speech API, and Svelte embed component.",
6
6
  "license": "MIT",
@@ -175,6 +175,8 @@
175
175
  "generate:location-discontinuities": "node scripts/generate-location-discontinuities.mjs",
176
176
  "build:locations": "node scripts/build-locations.mjs",
177
177
  "publish:locations": "node scripts/publish-locations.mjs",
178
+ "publish:location-400x225": "node scripts/build-location-400x225.mjs",
179
+ "test:location-400x225": "node scripts/test-location-400x225.mjs",
178
180
  "verify:locations": "node scripts/verify-location-sources.mjs",
179
181
  "publish:models": "node scripts/publish-models.mjs",
180
182
  "publish:models:krea-int8": "node scripts/_upload-model-prefix.mjs text-to-image/lf-t2i-turbo-int8",
@@ -204,6 +206,7 @@
204
206
  "preview:test": "npm run build && npm run preview"
205
207
  },
206
208
  "dependencies": {
209
+ "@jsquash/avif": "^1.3.0",
207
210
  "@jsquash/webp": "^1.5.0",
208
211
  "zod": "^4.4.3"
209
212
  },