@liforma/client 0.4.9 → 0.4.11

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 (138) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/dist/next/index.js +763 -623
  3. package/dist/next/index.js.map +1 -1
  4. package/dist/next/react/ExperienceThumbnail.d.ts +2 -0
  5. package/dist/next/react/ExperienceThumbnail.d.ts.map +1 -1
  6. package/dist/next/react/ExperienceWidget.d.ts +3 -0
  7. package/dist/next/react/ExperienceWidget.d.ts.map +1 -1
  8. package/dist/next/thumbnail/expandAssetUrl.d.ts +17 -0
  9. package/dist/next/thumbnail/expandAssetUrl.d.ts.map +1 -0
  10. package/dist/next/thumbnail/expandAssetUrl.test.d.ts +2 -0
  11. package/dist/next/thumbnail/expandAssetUrl.test.d.ts.map +1 -0
  12. package/dist/next/thumbnail/index.d.ts +3 -0
  13. package/dist/next/thumbnail/index.d.ts.map +1 -1
  14. package/dist/next/thumbnail/locationPlate.d.ts +13 -0
  15. package/dist/next/thumbnail/locationPlate.d.ts.map +1 -0
  16. package/dist/next/thumbnail/locationPlate.test.d.ts +2 -0
  17. package/dist/next/thumbnail/locationPlate.test.d.ts.map +1 -0
  18. package/dist/next/thumbnail/styles.d.ts.map +1 -1
  19. package/dist/next/thumbnail/toGalleryThumbUrl.d.ts +12 -2
  20. package/dist/next/thumbnail/toGalleryThumbUrl.d.ts.map +1 -1
  21. package/dist/next/thumbnail/types.d.ts +7 -1
  22. package/dist/next/thumbnail/types.d.ts.map +1 -1
  23. package/dist/next/thumbnail.js +273 -192
  24. package/dist/next/thumbnail.js.map +1 -1
  25. package/dist/next/v2/embed/iframeHost.d.ts +6 -2
  26. package/dist/next/v2/embed/iframeHost.d.ts.map +1 -1
  27. package/dist/next/v2/embed/protocol.d.ts +25 -2
  28. package/dist/next/v2/embed/protocol.d.ts.map +1 -1
  29. package/dist/next/v2/experience.d.ts +25 -3
  30. package/dist/next/v2/experience.d.ts.map +1 -1
  31. package/dist/next/v2/shims/embedBaseUrl.d.ts +15 -1
  32. package/dist/next/v2/shims/embedBaseUrl.d.ts.map +1 -1
  33. package/dist/next/v2/types/sessionInteraction.d.ts +13 -1
  34. package/dist/next/v2/types/sessionInteraction.d.ts.map +1 -1
  35. package/dist/next/v2/types/sessionManifest.d.ts +1 -0
  36. package/dist/next/v2/types/sessionManifest.d.ts.map +1 -1
  37. package/dist/next/v2/types/stage.d.ts +8 -0
  38. package/dist/next/v2/types/stage.d.ts.map +1 -0
  39. package/dist/next/widget/mountExperienceWidget.d.ts.map +1 -1
  40. package/dist/next/widget/types.d.ts +1 -0
  41. package/dist/next/widget/types.d.ts.map +1 -1
  42. package/dist/npm/index.js +566 -454
  43. package/dist/npm/index.js.map +1 -1
  44. package/dist/npm/thumbnail/expandAssetUrl.d.ts +17 -0
  45. package/dist/npm/thumbnail/expandAssetUrl.d.ts.map +1 -0
  46. package/dist/npm/thumbnail/index.d.ts +3 -0
  47. package/dist/npm/thumbnail/index.d.ts.map +1 -1
  48. package/dist/npm/thumbnail/index.js +151 -75
  49. package/dist/npm/thumbnail/index.js.map +1 -1
  50. package/dist/npm/thumbnail/locationPlate.d.ts +13 -0
  51. package/dist/npm/thumbnail/locationPlate.d.ts.map +1 -0
  52. package/dist/npm/thumbnail/styles.d.ts.map +1 -1
  53. package/dist/npm/thumbnail/toGalleryThumbUrl.d.ts +12 -2
  54. package/dist/npm/thumbnail/toGalleryThumbUrl.d.ts.map +1 -1
  55. package/dist/npm/thumbnail/types.d.ts +7 -1
  56. package/dist/npm/thumbnail/types.d.ts.map +1 -1
  57. package/dist/npm/v2/embed/iframeHost.d.ts +6 -2
  58. package/dist/npm/v2/embed/iframeHost.d.ts.map +1 -1
  59. package/dist/npm/v2/embed/protocol.d.ts +25 -2
  60. package/dist/npm/v2/embed/protocol.d.ts.map +1 -1
  61. package/dist/npm/v2/experience.d.ts +25 -3
  62. package/dist/npm/v2/experience.d.ts.map +1 -1
  63. package/dist/npm/v2/index.d.ts +2 -1
  64. package/dist/npm/v2/index.d.ts.map +1 -1
  65. package/dist/npm/v2/liforma-experience-thumbnail.d.ts.map +1 -1
  66. package/dist/npm/v2/liforma-experience.d.ts +2 -0
  67. package/dist/npm/v2/liforma-experience.d.ts.map +1 -1
  68. package/dist/npm/v2/npm.d.ts +2 -1
  69. package/dist/npm/v2/npm.d.ts.map +1 -1
  70. package/dist/npm/v2/shims/embedBaseUrl.d.ts +15 -1
  71. package/dist/npm/v2/shims/embedBaseUrl.d.ts.map +1 -1
  72. package/dist/npm/v2/types/sessionInteraction.d.ts +13 -1
  73. package/dist/npm/v2/types/sessionInteraction.d.ts.map +1 -1
  74. package/dist/npm/v2/types/sessionManifest.d.ts +1 -0
  75. package/dist/npm/v2/types/sessionManifest.d.ts.map +1 -1
  76. package/dist/npm/v2/types/sessionManifestSchema.d.ts +17 -0
  77. package/dist/npm/v2/types/sessionManifestSchema.d.ts.map +1 -1
  78. package/dist/npm/v2/types/stage.d.ts +8 -0
  79. package/dist/npm/v2/types/stage.d.ts.map +1 -0
  80. package/dist/npm/widget/mountExperienceWidget.d.ts.map +1 -1
  81. package/dist/npm/widget/types.d.ts +1 -0
  82. package/dist/npm/widget/types.d.ts.map +1 -1
  83. package/dist/react/component/experienceComponentRuntime.d.ts +4 -0
  84. package/dist/react/component/experienceComponentRuntime.d.ts.map +1 -1
  85. package/dist/react/component/experienceComponentTypes.d.ts +15 -1
  86. package/dist/react/component/experienceComponentTypes.d.ts.map +1 -1
  87. package/dist/react/index.js +884 -719
  88. package/dist/react/index.js.map +1 -1
  89. package/dist/react/react/Experience.d.ts.map +1 -1
  90. package/dist/react/react/ExperienceThumbnail.d.ts +2 -0
  91. package/dist/react/react/ExperienceThumbnail.d.ts.map +1 -1
  92. package/dist/react/react/ExperienceWidget.d.ts +3 -0
  93. package/dist/react/react/ExperienceWidget.d.ts.map +1 -1
  94. package/dist/react/thumbnail/expandAssetUrl.d.ts +17 -0
  95. package/dist/react/thumbnail/expandAssetUrl.d.ts.map +1 -0
  96. package/dist/react/thumbnail/expandAssetUrl.test.d.ts +2 -0
  97. package/dist/react/thumbnail/expandAssetUrl.test.d.ts.map +1 -0
  98. package/dist/react/thumbnail/index.d.ts +3 -0
  99. package/dist/react/thumbnail/index.d.ts.map +1 -1
  100. package/dist/react/thumbnail/locationPlate.d.ts +13 -0
  101. package/dist/react/thumbnail/locationPlate.d.ts.map +1 -0
  102. package/dist/react/thumbnail/locationPlate.test.d.ts +2 -0
  103. package/dist/react/thumbnail/locationPlate.test.d.ts.map +1 -0
  104. package/dist/react/thumbnail/styles.d.ts.map +1 -1
  105. package/dist/react/thumbnail/toGalleryThumbUrl.d.ts +12 -2
  106. package/dist/react/thumbnail/toGalleryThumbUrl.d.ts.map +1 -1
  107. package/dist/react/thumbnail/types.d.ts +7 -1
  108. package/dist/react/thumbnail/types.d.ts.map +1 -1
  109. package/dist/react/thumbnail.js +273 -192
  110. package/dist/react/thumbnail.js.map +1 -1
  111. package/dist/react/v2/embed/iframeHost.d.ts +6 -2
  112. package/dist/react/v2/embed/iframeHost.d.ts.map +1 -1
  113. package/dist/react/v2/embed/protocol.d.ts +25 -2
  114. package/dist/react/v2/embed/protocol.d.ts.map +1 -1
  115. package/dist/react/v2/experience.d.ts +25 -3
  116. package/dist/react/v2/experience.d.ts.map +1 -1
  117. package/dist/react/v2/shims/embedBaseUrl.d.ts +15 -1
  118. package/dist/react/v2/shims/embedBaseUrl.d.ts.map +1 -1
  119. package/dist/react/v2/types/sessionInteraction.d.ts +13 -1
  120. package/dist/react/v2/types/sessionInteraction.d.ts.map +1 -1
  121. package/dist/react/v2/types/sessionManifest.d.ts +1 -0
  122. package/dist/react/v2/types/sessionManifest.d.ts.map +1 -1
  123. package/dist/react/v2/types/stage.d.ts +8 -0
  124. package/dist/react/v2/types/stage.d.ts.map +1 -0
  125. package/dist/react/widget/mountExperienceWidget.d.ts.map +1 -1
  126. package/dist/react/widget/types.d.ts +1 -0
  127. package/dist/react/widget/types.d.ts.map +1 -1
  128. package/dist/sdk/v2/client.js +2 -2
  129. package/dist/sdk/v2/client.js.map +1 -1
  130. package/dist/sdk/v2/thumbnail.js +9 -2
  131. package/dist/sdk/v2/thumbnail.js.map +1 -1
  132. package/dist/svelte/Experience.svelte +43 -0
  133. package/dist/svelte/Experience.svelte.d.ts +3 -0
  134. package/dist/svelte/ExperienceThumbnail.svelte +24 -2
  135. package/dist/svelte/ExperienceThumbnail.svelte.d.ts +2 -0
  136. package/dist/svelte/ExperienceWidget.svelte +9 -0
  137. package/dist/svelte/ExperienceWidget.svelte.d.ts +3 -0
  138. package/package.json +1 -1
@@ -0,0 +1,17 @@
1
+ import { type ThumbnailLocationPlateSize } from './locationPlate';
2
+ /** Default origin for Liforma gallery plates (catalog paths have no host). */
3
+ export declare const DEFAULT_THUMBNAIL_CDN_BASE_URL = "https://cdn.liforma.ai";
4
+ export declare function normalizeThumbnailCdnBaseUrl(cdnBaseUrl?: string): string;
5
+ /**
6
+ * Expand a catalog location path (or keep a full URL) to a sized plate URL.
7
+ *
8
+ * `/locations/modern/city/office/interior-desk-daytime` →
9
+ * `{cdn}/locations/modern/city/office/interior-desk-daytime-800x450.webp`
10
+ */
11
+ export declare function expandThumbnailLocationUrl(ref: string, locationPlateSize: ThumbnailLocationPlateSize, cdnBaseUrl?: string): string;
12
+ /**
13
+ * Expand a catalog avatar path (or keep a full URL) onto the public CDN.
14
+ * `/avatars/{id}` → `{cdn}/avatars/{id}/256/{id}_neutral.webp`
15
+ */
16
+ export declare function expandThumbnailAvatarUrl(ref: string, cdnBaseUrl?: string): string;
17
+ //# sourceMappingURL=expandAssetUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expandAssetUrl.d.ts","sourceRoot":"","sources":["../../../src/thumbnail/expandAssetUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAEzB,8EAA8E;AAC9E,eAAO,MAAM,8BAA8B,2BAA2B,CAAC;AAIvE,wBAAgB,4BAA4B,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxE;AAsBD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACzC,GAAG,EAAE,MAAM,EACX,iBAAiB,EAAE,0BAA0B,EAC7C,UAAU,CAAC,EAAE,MAAM,GACjB,MAAM,CA6BR;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAgCjF"}
@@ -1,6 +1,9 @@
1
1
  export type { GalleryThumb, ExperienceThumbnailBaseProps, ThumbnailInteractionKind } from './types';
2
2
  export { thumbnailFitClass } from './types';
3
3
  export { toGalleryThumbUrl, resolveThumbnailLayers } from './toGalleryThumbUrl';
4
+ export { DEFAULT_THUMBNAIL_CDN_BASE_URL, expandThumbnailAvatarUrl, expandThumbnailLocationUrl, normalizeThumbnailCdnBaseUrl } from './expandAssetUrl';
5
+ export { pickThumbnailLocationPlateSize, rewriteThumbnailLocationPlateSize, attachThumbnailBox, THUMBNAIL_LOCATION_PLATE_SIZES } from './locationPlate';
6
+ export type { ThumbnailLocationPlateSize } from './locationPlate';
4
7
  export { resolveThumbnailInteraction, resolvePlayerBaseUrl, buildHostedLaunchUrl, resolveThumbnailHref } from './hostedLaunchUrl';
5
8
  export { attachLazyThumb, attachImageReady, getDefaultLazyRootMargin } from './lazyLoad';
6
9
  export { prefersCoarsePointer, scheduleThumbLoad, COARSE_THUMB_LOAD_CONCURRENCY, COARSE_THUMB_LOAD_STAGGER_MS } from './thumbLoadScheduler';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/thumbnail/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACN,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,oBAAoB,EACpB,iBAAiB,EACjB,6BAA6B,EAC7B,4BAA4B,EAC5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,0BAA0B,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/thumbnail/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACN,8BAA8B,EAC9B,wBAAwB,EACxB,0BAA0B,EAC1B,4BAA4B,EAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,8BAA8B,EAC9B,iCAAiC,EACjC,kBAAkB,EAClB,8BAA8B,EAC9B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EACN,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,oBAAoB,EACpB,iBAAiB,EACjB,6BAA6B,EAC7B,4BAA4B,EAC5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,0BAA0B,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
@@ -3,28 +3,104 @@ function e(e) {
3
3
  return e === "face" ? "liforma-et--fit-face" : e === "medium" ? "liforma-et--fit-medium" : "";
4
4
  }
5
5
  //#endregion
6
+ //#region src/thumbnail/locationPlate.ts
7
+ var t = [
8
+ "256x256",
9
+ "800x450",
10
+ "1672x941"
11
+ ], n = /(-)(\d+x\d+)(\.webp)(\?|$)/i;
12
+ function r(e, t) {
13
+ return !Number.isFinite(e) || !Number.isFinite(t) || e < 1 || t < 1 || e <= t ? "256x256" : e > 800 || t > 450 ? "1672x941" : "800x450";
14
+ }
15
+ function i(e, t) {
16
+ let r = e.trim();
17
+ return r && r.replace(n, (e, n, r, i, a) => `${n}${t}${i}${a}`);
18
+ }
19
+ function a(e, t) {
20
+ let n = () => {
21
+ t(e.clientWidth, e.clientHeight);
22
+ };
23
+ if (n(), typeof ResizeObserver > "u") return () => {};
24
+ let r = new ResizeObserver(n);
25
+ return r.observe(e), () => r.disconnect();
26
+ }
27
+ //#endregion
28
+ //#region src/thumbnail/expandAssetUrl.ts
29
+ var o = "https://cdn.liforma.ai", s = /-(256x256|400x450|800x450|836x971|1672x941|167x94)$/i;
30
+ function c(e) {
31
+ return e?.trim().replace(/\/$/, "") || "https://cdn.liforma.ai";
32
+ }
33
+ function l(e) {
34
+ return /^https?:\/\//i.test(e) || e.startsWith("//");
35
+ }
36
+ function u(e) {
37
+ return e.startsWith("//") ? `https:${e}` : e;
38
+ }
39
+ function d(e) {
40
+ let t = e.indexOf("?");
41
+ return t === -1 ? {
42
+ pathname: e,
43
+ search: ""
44
+ } : {
45
+ pathname: e.slice(0, t),
46
+ search: e.slice(t)
47
+ };
48
+ }
49
+ function f(e, t, n = "") {
50
+ return `${e}${t.startsWith("/") ? t : `/${t}`}${n}`;
51
+ }
52
+ function p(e, t, n) {
53
+ let r = e.trim();
54
+ if (!r) return r;
55
+ let a = c(n);
56
+ if (l(r)) return i(u(r), t);
57
+ let { pathname: o, search: p } = d(r.replace(/\\/g, "/")), m = o;
58
+ if (!m.startsWith("/")) m = `/locations/${m.replace(/^locations\//i, "")}`;
59
+ else if (!/^\/locations\//i.test(m)) return /^\/avatars\//i.test(m) ? f(a, m, p) : r;
60
+ if (/\.webp$/i.test(m) && !s.test(m.replace(/\.webp$/i, ""))) return f(a, m, p);
61
+ let h = m.replace(/^\/locations\//i, "").replace(/\.webp$/i, "");
62
+ return h = h.replace(s, ""), `${a}/locations/${h}-${t}.webp${p}`;
63
+ }
64
+ function m(e, t) {
65
+ let n = e.trim();
66
+ if (!n) return n;
67
+ let r = c(t);
68
+ if (l(n)) return u(n);
69
+ let { pathname: i, search: a } = d(n.replace(/\\/g, "/")), o = i;
70
+ if (!o.startsWith("/")) if (/^avatars\//i.test(o)) o = `/${o}`;
71
+ else if (/^[^/]+$/.test(o) && !a) return `${r}/avatars/${o}/256/${o}_neutral.webp`;
72
+ else return n;
73
+ if (!/^\/avatars\//i.test(o)) return n;
74
+ let s = o.replace(/^\/avatars\//i, "").replace(/\/+$/, "");
75
+ return s && !s.includes("/") && !a ? `${r}/avatars/${s}/256/${s}_neutral.webp` : f(r, o, a);
76
+ }
77
+ //#endregion
6
78
  //#region src/thumbnail/toGalleryThumbUrl.ts
7
- var t = /(\/avatars\/[^/]+\/)(\d+)(\/)/i, n = /_CH(\.webp)/i, r = /(-)(\d+x\d+)(\.webp)(\?|$)/i;
8
- function i(e) {
9
- let i = e.trim();
10
- if (!i) return i;
11
- let a = i.replace(t, (e, t, n, r) => n === "256" ? `${t}${n}${r}` : `${t}256${r}`);
12
- return a = a.replace(n, "_neutral$1"), a = a.replace(r, (e, t, n, r, i) => n === "256x256" ? `${t}${n}${r}${i}` : `${t}256x256${r}${i}`), a;
13
- }
14
- function a(e) {
79
+ var h = /(\/avatars\/[^/]+\/)(\d+)(\/)/i, g = /_CH(\.webp)/i;
80
+ function _(e) {
81
+ 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("/");
82
+ }
83
+ function v(e, t = "256x256", n) {
84
+ let r = e.trim();
85
+ if (!r) return r;
86
+ if (_(r)) return p(r, t, n);
87
+ let i = m(r, n);
88
+ return i = i.replace(h, (e, t, n, r) => n === "256" ? `${t}${n}${r}` : `${t}256${r}`), i.replace(g, "_neutral$1");
89
+ }
90
+ function y(e) {
15
91
  let t = e.avatarImage?.trim() || e.galleryThumb?.avatarImage?.trim() || "";
16
92
  if (!t) return null;
17
- let n = e.backgroundImage?.trim() || e.galleryThumb?.backgroundImage?.trim() || "", r = e.foregroundImage?.trim() || e.galleryThumb?.foregroundImage?.trim() || "", a = e.fallbackImage?.trim() || "";
93
+ 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", o = c(e.cdnBaseUrl), s = e.normalize !== !1, l = (e) => !s && /^https?:\/\//i.test(e.trim()) ? e.trim() : v(e, a, o), u = (e) => !s && /^https?:\/\//i.test(e.trim()) ? e.trim() : p(e, a, o);
18
94
  return {
19
- avatarImage: i(t),
20
- ...n ? { backgroundImage: i(n) } : {},
21
- ...r ? { foregroundImage: i(r) } : {},
22
- ...a ? { fallbackImage: i(a) } : {}
95
+ avatarImage: l(t),
96
+ ...n ? { backgroundImage: u(n) } : {},
97
+ ...r ? { foregroundImage: u(r) } : {},
98
+ ...i ? { fallbackImage: l(i) } : {}
23
99
  };
24
100
  }
25
101
  //#endregion
26
102
  //#region src/shims/localStack.ts
27
- function o() {
103
+ function b() {
28
104
  if (globalThis.__LIFORMA_STACK === "local") return !0;
29
105
  if (typeof location > "u") return !1;
30
106
  try {
@@ -35,25 +111,25 @@ function o() {
35
111
  }
36
112
  //#endregion
37
113
  //#region src/thumbnail/hostedLaunchUrl.ts
38
- var s = "https://player.liforma.ai", c = "http://localhost:3002";
39
- function l(e) {
114
+ var x = "https://player.liforma.ai", S = "http://localhost:3002";
115
+ function C(e) {
40
116
  return e.href?.trim() ? "anchor" : e.hasClickHandler ? "button" : e.experienceId?.trim() ? "anchor" : "presentational";
41
117
  }
42
- function u(e) {
118
+ function w(e) {
43
119
  let t = e?.trim();
44
- return t ? t.replace(/\/$/, "") : o() ? c : s;
120
+ return t ? t.replace(/\/$/, "") : b() ? S : x;
45
121
  }
46
- function d(e) {
47
- let t = e.experienceId.trim(), n = u(e.playerBaseUrl), r = new URL(`${n}/${encodeURIComponent(t)}`), i = e.returnUrl?.trim();
122
+ function T(e) {
123
+ let t = e.experienceId.trim(), n = w(e.playerBaseUrl), r = new URL(`${n}/${encodeURIComponent(t)}`), i = e.returnUrl?.trim();
48
124
  return i && r.searchParams.set("returnUrl", i), r.toString();
49
125
  }
50
- function f(e) {
126
+ function E(e) {
51
127
  let t = e.href?.trim();
52
128
  if (t) return t;
53
129
  let n = e.experienceId?.trim();
54
130
  if (!n) return;
55
131
  let r = e.returnUrl?.trim();
56
- return !r && e.useCurrentPageAsReturnUrl && typeof window < "u" && (r = window.location.href), d({
132
+ return !r && e.useCurrentPageAsReturnUrl && typeof window < "u" && (r = window.location.href), T({
57
133
  experienceId: n,
58
134
  ...r ? { returnUrl: r } : {},
59
135
  playerBaseUrl: e.playerBaseUrl
@@ -61,60 +137,60 @@ function f(e) {
61
137
  }
62
138
  //#endregion
63
139
  //#region src/thumbnail/thumbLoadScheduler.ts
64
- var p = 4, m = 160;
65
- function h() {
140
+ var D = 4, O = 160;
141
+ function k() {
66
142
  return typeof window > "u" ? !1 : window.matchMedia("(hover: none), (pointer: coarse)").matches;
67
143
  }
68
- var g = 0, _ = [];
69
- function v() {
70
- return h() ? 4 : Infinity;
144
+ var A = 0, j = [];
145
+ function M() {
146
+ return k() ? 4 : Infinity;
71
147
  }
72
- function y() {
73
- return h() ? 160 : 0;
148
+ function N() {
149
+ return k() ? 160 : 0;
74
150
  }
75
- function b() {
76
- let e = v();
77
- for (; g < e && _.length > 0;) {
78
- let e = _.shift();
151
+ function P() {
152
+ let e = M();
153
+ for (; A < e && j.length > 0;) {
154
+ let e = j.shift();
79
155
  if (!e || e.cancelled) continue;
80
- g += 1, e.start();
81
- let t = y();
156
+ A += 1, e.start();
157
+ let t = N();
82
158
  if (t <= 0) {
83
- g = Math.max(0, g - 1);
159
+ A = Math.max(0, A - 1);
84
160
  continue;
85
161
  }
86
162
  window.setTimeout(() => {
87
- g = Math.max(0, g - 1), b();
163
+ A = Math.max(0, A - 1), P();
88
164
  }, t);
89
165
  }
90
166
  }
91
- function x(e) {
92
- if (!h()) return e(), () => {};
167
+ function F(e) {
168
+ if (!k()) return e(), () => {};
93
169
  let t = {
94
170
  start: e,
95
171
  cancelled: !1
96
172
  };
97
- return _.push(t), b(), () => {
173
+ return j.push(t), P(), () => {
98
174
  t.cancelled = !0;
99
175
  };
100
176
  }
101
177
  //#endregion
102
178
  //#region src/thumbnail/lazyLoad.ts
103
- function S() {
104
- return h() ? "96px 0px" : "280px 0px";
179
+ function I() {
180
+ return k() ? "96px 0px" : "280px 0px";
105
181
  }
106
- function C(e) {
182
+ function L(e) {
107
183
  let t = e.trim().split(/\s+/)[0] ?? "0px", n = Number.parseFloat(t);
108
184
  return Number.isFinite(n) ? Math.abs(n) : 280;
109
185
  }
110
- function w(e, t) {
186
+ function R(e, t) {
111
187
  if (typeof window > "u") return !1;
112
188
  let n = e.getBoundingClientRect();
113
189
  return n.bottom >= -t && n.top <= window.innerHeight + t;
114
190
  }
115
- function T(e, t) {
116
- let { onVisibleChange: n } = t, r = t.rootMargin ?? S(), i = C(r), a = !1, o = !1, s = null, c = () => {
117
- o || (s?.(), s = x(() => {
191
+ function z(e, t) {
192
+ let { onVisibleChange: n } = t, r = t.rootMargin ?? I(), i = L(r), a = !1, o = !1, s = null, c = () => {
193
+ o || (s?.(), s = F(() => {
118
194
  s = null, o = !0, n(!0);
119
195
  }));
120
196
  }, l = (e) => {
@@ -136,11 +212,11 @@ function T(e, t) {
136
212
  rootMargin: r,
137
213
  threshold: 0
138
214
  });
139
- return u.observe(e), w(e, i) && l(!0), () => {
215
+ return u.observe(e), R(e, i) && l(!0), () => {
140
216
  s?.(), s = null, u.disconnect();
141
217
  };
142
218
  }
143
- function E(e, t) {
219
+ function B(e, t) {
144
220
  let n = () => t();
145
221
  return e.complete ? queueMicrotask(n) : (e.addEventListener("load", n, { once: !0 }), e.addEventListener("error", n, { once: !0 })), () => {
146
222
  e.removeEventListener("load", n), e.removeEventListener("error", n);
@@ -148,49 +224,49 @@ function E(e, t) {
148
224
  }
149
225
  //#endregion
150
226
  //#region src/thumbnail/scrollParallax.ts
151
- function D() {
227
+ function V() {
152
228
  return typeof window > "u" || window.matchMedia("(prefers-reduced-motion: reduce)").matches ? !1 : window.matchMedia("(hover: none), (pointer: coarse)").matches;
153
229
  }
154
- function O(e, t) {
230
+ function H(e, t) {
155
231
  return ((e.top + e.height / 2) / Math.max(t, 1) - .5) * 2;
156
232
  }
157
- var k = /* @__PURE__ */ new Set(), A = !1, j = 0;
158
- function M() {
159
- if (j = 0, k.size === 0 || typeof window > "u") return;
233
+ var U = /* @__PURE__ */ new Set(), W = !1, G = 0;
234
+ function K() {
235
+ if (G = 0, U.size === 0 || typeof window > "u") return;
160
236
  let e = window.innerHeight;
161
- for (let t of k) {
237
+ for (let t of U) {
162
238
  if (t.pointerActive?.()) continue;
163
239
  let n = t.element.getBoundingClientRect();
164
- n.bottom < -64 || n.top > e + 64 || t.onParallax(O(n, e));
240
+ n.bottom < -64 || n.top > e + 64 || t.onParallax(H(n, e));
165
241
  }
166
242
  }
167
- function N() {
168
- j === 0 && (j = requestAnimationFrame(M));
243
+ function q() {
244
+ G === 0 && (G = requestAnimationFrame(K));
169
245
  }
170
- function P() {
171
- A || typeof window > "u" || (A = !0, window.addEventListener("scroll", N, { passive: !0 }), window.addEventListener("resize", N, { passive: !0 }));
246
+ function J() {
247
+ W || typeof window > "u" || (W = !0, window.addEventListener("scroll", q, { passive: !0 }), window.addEventListener("resize", q, { passive: !0 }));
172
248
  }
173
- function F() {
174
- k.size > 0 || !A || typeof window > "u" || (A = !1, j !== 0 && (cancelAnimationFrame(j), j = 0), window.removeEventListener("scroll", N), window.removeEventListener("resize", N));
249
+ function Y() {
250
+ U.size > 0 || !W || typeof window > "u" || (W = !1, G !== 0 && (cancelAnimationFrame(G), G = 0), window.removeEventListener("scroll", q), window.removeEventListener("resize", q));
175
251
  }
176
- function I(e, t) {
177
- if (!D()) return () => {};
252
+ function X(e, t) {
253
+ if (!V()) return () => {};
178
254
  let n = {
179
255
  element: e,
180
256
  onParallax: t.onParallax,
181
257
  pointerActive: t.pointerActive
182
258
  };
183
- return k.add(n), P(), n.pointerActive?.() || t.onParallax(O(e.getBoundingClientRect(), window.innerHeight)), () => {
184
- k.delete(n), F();
259
+ return U.add(n), J(), n.pointerActive?.() || t.onParallax(H(e.getBoundingClientRect(), window.innerHeight)), () => {
260
+ U.delete(n), Y();
185
261
  };
186
262
  }
187
263
  //#endregion
188
264
  //#region src/thumbnail/attachThumbnailParallax.ts
189
- function L(e) {
265
+ function Z(e) {
190
266
  let t = 0, n = 0, r = !1, i = !1, a = !1, o = () => {
191
267
  let a = r || i;
192
268
  e.classList.toggle("liforma-et--parallax", a), e.classList.toggle("liforma-et--scroll-parallax", i), e.style.setProperty("--liforma-et-parallax-x", String(t)), e.style.setProperty("--liforma-et-parallax-y", String(n));
193
- }, s = I(e, {
269
+ }, s = X(e, {
194
270
  onParallax: (e) => {
195
271
  i = !0, a || (t = e, n = 0, o());
196
272
  },
@@ -203,7 +279,7 @@ function L(e) {
203
279
  a = !0, e.pointerType === "mouse" && (r = !0), o();
204
280
  }, u = () => {
205
281
  let s = a;
206
- a = !1, r = !1, i && s ? (t = O(e.getBoundingClientRect(), window.innerHeight), n = 0) : i || (t = 0, n = 0), o();
282
+ a = !1, r = !1, i && s ? (t = H(e.getBoundingClientRect(), window.innerHeight), n = 0) : i || (t = 0, n = 0), o();
207
283
  }, d = (e) => {
208
284
  e.pointerType === "mouse" && (r = !0, o());
209
285
  }, f = () => {
@@ -215,17 +291,17 @@ function L(e) {
215
291
  }
216
292
  //#endregion
217
293
  //#region src/thumbnail/styles.ts
218
- var R = ".liforma-et {\n --liforma-et-parallax-x: 0;\n --liforma-et-parallax-y: 0;\n --liforma-et-bg-scale: 1;\n --liforma-et-fg-scale: 1;\n --liforma-et-avatar-scale: 1;\n --liforma-et-bg-shift: 0px;\n --liforma-et-fg-shift: 0px;\n --liforma-et-avatar-shift: 0px;\n --liforma-et-reveal-duration: 420ms;\n\n box-sizing: border-box;\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 0.65rem;\n overflow: hidden;\n background: #111;\n color: inherit;\n text-decoration: none;\n cursor: inherit;\n -webkit-tap-highlight-color: transparent;\n}\n\na.liforma-et,\nbutton.liforma-et {\n cursor: pointer;\n}\n\nbutton.liforma-et {\n font: inherit;\n appearance: none;\n}\n\n.liforma-et__placeholder {\n position: absolute;\n inset: 0;\n z-index: 0;\n background:\n radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.06), transparent 58%),\n linear-gradient(180deg, #1a1a1a 0%, #101010 100%);\n opacity: 1;\n transition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et--revealed .liforma-et__placeholder {\n opacity: 0;\n}\n\n.liforma-et__media {\n opacity: 0;\n transition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et__media--ready {\n opacity: 1;\n}\n\n.liforma-et__background {\n position: absolute;\n inset: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center center;\n transform: translate(\n calc(var(--liforma-et-parallax-x) * var(--liforma-et-bg-shift)),\n calc(var(--liforma-et-parallax-y) * var(--liforma-et-bg-shift))\n )\n scale(var(--liforma-et-bg-scale));\n transform-origin: center center;\n transition:\n opacity var(--liforma-et-reveal-duration) ease,\n transform 160ms ease;\n}\n\n.liforma-et__avatar {\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n height: 100%;\n object-fit: contain;\n object-position: center bottom;\n transform: scale(var(--liforma-et-avatar-scale))\n translate(\n calc(var(--liforma-et-parallax-x) * var(--liforma-et-avatar-shift)),\n calc(var(--liforma-et-parallax-y) * var(--liforma-et-avatar-shift))\n );\n transform-origin: center center;\n transition:\n opacity var(--liforma-et-reveal-duration) ease,\n transform 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 --liforma-et-avatar-scale: 1.85;\n --liforma-et-bg-scale: 1.85;\n --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 transform-origin: 50% 18%;\n}\n\n.liforma-et--fit-face .liforma-et__avatar {\n object-fit: cover;\n object-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 --liforma-et-avatar-scale: 1.35;\n --liforma-et-bg-scale: 1.35;\n --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 transform-origin: 50% 28%;\n}\n\n.liforma-et--fit-medium .liforma-et__avatar {\n object-fit: cover;\n object-position: 50% 28%;\n}\n\n.liforma-et__foreground {\n position: absolute;\n inset: 0;\n z-index: 3;\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center center;\n pointer-events: none;\n transform: translate(\n calc(var(--liforma-et-parallax-x) * var(--liforma-et-fg-shift) * -1),\n calc(var(--liforma-et-parallax-y) * var(--liforma-et-fg-shift) * -1)\n )\n scale(var(--liforma-et-fg-scale));\n transform-origin: center center;\n transition:\n opacity var(--liforma-et-reveal-duration) ease,\n transform 160ms ease;\n}\n\n.liforma-et--parallax {\n --liforma-et-bg-scale: 1.08;\n --liforma-et-fg-scale: 1.05;\n --liforma-et-avatar-scale: 1.08;\n --liforma-et-bg-shift: 10px;\n --liforma-et-fg-shift: 4px;\n --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 will-change: transform;\n}\n\n.liforma-et--parallax.liforma-et--fit-face {\n --liforma-et-avatar-scale: 1.95;\n --liforma-et-bg-scale: 1.95;\n --liforma-et-fg-scale: 1.95;\n --liforma-et-avatar-shift: 0px;\n}\n\n.liforma-et--parallax.liforma-et--fit-medium {\n --liforma-et-avatar-scale: 1.42;\n --liforma-et-bg-scale: 1.42;\n --liforma-et-fg-scale: 1.42;\n --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 transition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .liforma-et {\n --liforma-et-reveal-duration: 0ms;\n }\n\n .liforma-et--parallax {\n --liforma-et-bg-scale: 1;\n --liforma-et-fg-scale: 1;\n --liforma-et-avatar-scale: 1;\n --liforma-et-bg-shift: 0px;\n --liforma-et-fg-shift: 0px;\n --liforma-et-avatar-shift: 0px;\n }\n\n .liforma-et--parallax.liforma-et--fit-face {\n --liforma-et-avatar-scale: 1.85;\n --liforma-et-bg-scale: 1.85;\n --liforma-et-fg-scale: 1.85;\n }\n\n .liforma-et__background,\n .liforma-et__foreground,\n .liforma-et__avatar {\n transition: none;\n }\n}", z = !1;
219
- function B(e) {
220
- if (typeof document > "u" || z) return;
294
+ var Q = ":host {\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n}\n\n.liforma-et {\n --liforma-et-parallax-x: 0;\n --liforma-et-parallax-y: 0;\n --liforma-et-bg-scale: 1;\n --liforma-et-fg-scale: 1;\n --liforma-et-avatar-scale: 1;\n --liforma-et-bg-shift: 0px;\n --liforma-et-fg-shift: 0px;\n --liforma-et-avatar-shift: 0px;\n --liforma-et-reveal-duration: 420ms;\n\n box-sizing: border-box;\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 0.65rem;\n overflow: hidden;\n background: #111;\n color: inherit;\n text-decoration: none;\n cursor: inherit;\n -webkit-tap-highlight-color: transparent;\n}\n\na.liforma-et,\nbutton.liforma-et {\n cursor: pointer;\n}\n\nbutton.liforma-et {\n font: inherit;\n appearance: none;\n}\n\n.liforma-et__placeholder {\n position: absolute;\n inset: 0;\n z-index: 0;\n background:\n radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.06), transparent 58%),\n linear-gradient(180deg, #1a1a1a 0%, #101010 100%);\n opacity: 1;\n transition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et--revealed .liforma-et__placeholder {\n opacity: 0;\n}\n\n.liforma-et__media {\n opacity: 0;\n transition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n.liforma-et__media--ready {\n opacity: 1;\n}\n\n.liforma-et__background {\n position: absolute;\n inset: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center center;\n transform: translate(\n calc(var(--liforma-et-parallax-x) * var(--liforma-et-bg-shift)),\n calc(var(--liforma-et-parallax-y) * var(--liforma-et-bg-shift))\n )\n scale(var(--liforma-et-bg-scale));\n transform-origin: center center;\n transition:\n opacity var(--liforma-et-reveal-duration) ease,\n transform 160ms ease;\n}\n\n.liforma-et__avatar {\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n height: 100%;\n object-fit: contain;\n object-position: center bottom;\n transform: scale(var(--liforma-et-avatar-scale))\n translate(\n calc(var(--liforma-et-parallax-x) * var(--liforma-et-avatar-shift)),\n calc(var(--liforma-et-parallax-y) * var(--liforma-et-avatar-shift))\n );\n transform-origin: center center;\n transition:\n opacity var(--liforma-et-reveal-duration) ease,\n transform 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 --liforma-et-avatar-scale: 1.85;\n --liforma-et-bg-scale: 1.85;\n --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 transform-origin: 50% 18%;\n}\n\n.liforma-et--fit-face .liforma-et__avatar {\n object-fit: cover;\n object-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 --liforma-et-avatar-scale: 1.35;\n --liforma-et-bg-scale: 1.35;\n --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 transform-origin: 50% 28%;\n}\n\n.liforma-et--fit-medium .liforma-et__avatar {\n object-fit: cover;\n object-position: 50% 28%;\n}\n\n.liforma-et__foreground {\n position: absolute;\n inset: 0;\n z-index: 3;\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center center;\n pointer-events: none;\n transform: translate(\n calc(var(--liforma-et-parallax-x) * var(--liforma-et-fg-shift) * -1),\n calc(var(--liforma-et-parallax-y) * var(--liforma-et-fg-shift) * -1)\n )\n scale(var(--liforma-et-fg-scale));\n transform-origin: center center;\n transition:\n opacity var(--liforma-et-reveal-duration) ease,\n transform 160ms ease;\n}\n\n.liforma-et--parallax {\n --liforma-et-bg-scale: 1.08;\n --liforma-et-fg-scale: 1.05;\n --liforma-et-avatar-scale: 1.08;\n --liforma-et-bg-shift: 10px;\n --liforma-et-fg-shift: 4px;\n --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 will-change: transform;\n}\n\n.liforma-et--parallax.liforma-et--fit-face {\n --liforma-et-avatar-scale: 1.95;\n --liforma-et-bg-scale: 1.95;\n --liforma-et-fg-scale: 1.95;\n --liforma-et-avatar-shift: 0px;\n}\n\n.liforma-et--parallax.liforma-et--fit-medium {\n --liforma-et-avatar-scale: 1.42;\n --liforma-et-bg-scale: 1.42;\n --liforma-et-fg-scale: 1.42;\n --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 transition: opacity var(--liforma-et-reveal-duration) ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .liforma-et {\n --liforma-et-reveal-duration: 0ms;\n }\n\n .liforma-et--parallax {\n --liforma-et-bg-scale: 1;\n --liforma-et-fg-scale: 1;\n --liforma-et-avatar-scale: 1;\n --liforma-et-bg-shift: 0px;\n --liforma-et-fg-shift: 0px;\n --liforma-et-avatar-shift: 0px;\n }\n\n .liforma-et--parallax.liforma-et--fit-face {\n --liforma-et-avatar-scale: 1.85;\n --liforma-et-bg-scale: 1.85;\n --liforma-et-fg-scale: 1.85;\n }\n\n .liforma-et__background,\n .liforma-et__foreground,\n .liforma-et__avatar {\n transition: none;\n }\n}", $ = !1;
295
+ function ee(e) {
296
+ if (typeof document > "u" || $) return;
221
297
  if (document.querySelector("style[data-liforma-et]")) {
222
- z = !0;
298
+ $ = !0;
223
299
  return;
224
300
  }
225
301
  let t = document.createElement("style");
226
- t.setAttribute("data-liforma-et", ""), t.textContent = e, document.head.appendChild(t), z = !0;
302
+ t.setAttribute("data-liforma-et", ""), t.textContent = e, document.head.appendChild(t), $ = !0;
227
303
  }
228
304
  //#endregion
229
- export { p as COARSE_THUMB_LOAD_CONCURRENCY, m as COARSE_THUMB_LOAD_STAGGER_MS, R as EXPERIENCE_THUMBNAIL_CSS, E as attachImageReady, T as attachLazyThumb, I as attachScrollLinkedParallax, L as attachThumbnailParallax, d as buildHostedLaunchUrl, B as ensureExperienceThumbnailStyles, S as getDefaultLazyRootMargin, O as horizontalParallaxFromViewportY, h as prefersCoarsePointer, D as prefersScrollLinkedParallax, u as resolvePlayerBaseUrl, f as resolveThumbnailHref, l as resolveThumbnailInteraction, a as resolveThumbnailLayers, x as scheduleThumbLoad, e as thumbnailFitClass, i as toGalleryThumbUrl };
305
+ export { D as COARSE_THUMB_LOAD_CONCURRENCY, O as COARSE_THUMB_LOAD_STAGGER_MS, o as DEFAULT_THUMBNAIL_CDN_BASE_URL, Q as EXPERIENCE_THUMBNAIL_CSS, t as THUMBNAIL_LOCATION_PLATE_SIZES, B as attachImageReady, z as attachLazyThumb, X as attachScrollLinkedParallax, a as attachThumbnailBox, Z as attachThumbnailParallax, T as buildHostedLaunchUrl, ee as ensureExperienceThumbnailStyles, m as expandThumbnailAvatarUrl, p as expandThumbnailLocationUrl, I as getDefaultLazyRootMargin, H as horizontalParallaxFromViewportY, c as normalizeThumbnailCdnBaseUrl, r as pickThumbnailLocationPlateSize, k as prefersCoarsePointer, V as prefersScrollLinkedParallax, w as resolvePlayerBaseUrl, E as resolveThumbnailHref, C as resolveThumbnailInteraction, y as resolveThumbnailLayers, i as rewriteThumbnailLocationPlateSize, F as scheduleThumbLoad, e as thumbnailFitClass, v as toGalleryThumbUrl };
230
306
 
231
307
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/thumbnail/types.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/attachThumbnailParallax.ts","../../../src/thumbnail/styles.ts","../../../src/thumbnail/ensureStyles.ts"],"sourcesContent":["/** Catalog-shaped layered gallery preview (CDN URLs). */\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};\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","const AVATAR_TIER_RE = /(\\/avatars\\/[^/]+\\/)(\\d+)(\\/)/i;\nconst AVATAR_CH_PLATE_RE = /_CH(\\.webp)/i;\nconst LOCATION_SIZE_RE = /(-)(\\d+x\\d+)(\\.webp)(\\?|$)/i;\n\n/**\n * Rewrite known Liforma CDN URLs to gallery thumb tiers.\n * Avatars: /{tier}/ → /256/; locations: -{WxH}.webp → -256x256.webp.\n * Unrecognized URLs are returned unchanged.\n */\nexport function toGalleryThumbUrl(url: string): string {\n\tconst raw = url.trim();\n\tif (!raw) return raw;\n\n\tlet next = raw.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\tnext = next.replace(AVATAR_CH_PLATE_RE, '_neutral$1');\n\n\tnext = next.replace(\n\t\tLOCATION_SIZE_RE,\n\t\t(_match, dash: string, size: string, ext: string, end: string) => {\n\t\t\tif (size === '256x256') return `${dash}${size}${ext}${end}`;\n\t\t\treturn `${dash}256x256${ext}${end}`;\n\t\t}\n\t);\n\n\treturn next;\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}): {\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\n\treturn {\n\t\tavatarImage: toGalleryThumbUrl(avatarSource),\n\t\t...(backgroundSource ? { backgroundImage: toGalleryThumbUrl(backgroundSource) } : {}),\n\t\t...(foregroundSource ? { foregroundImage: toGalleryThumbUrl(foregroundSource) } : {}),\n\t\t...(fallbackSource ? { fallbackImage: toGalleryThumbUrl(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';\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","import {\n\tattachScrollLinkedParallax,\n\thorizontalParallaxFromViewportY\n} from './scrollParallax';\n\n/**\n * Drive `--liforma-et-parallax-*` CSS vars and `liforma-et--parallax` /\n * `liforma-et--scroll-parallax` classes from pointer (desktop) or scroll (touch).\n * Matches ExperienceThumbnail / `<liforma-experience-thumbnail>` behaviour.\n */\nexport function attachThumbnailParallax(root: HTMLElement): () => void {\n\tlet parallaxX = 0;\n\tlet parallaxY = 0;\n\tlet hovering = false;\n\tlet scrollParallax = false;\n\tlet pointerDown = false;\n\n\tconst sync = () => {\n\t\tconst parallaxActive = hovering || scrollParallax;\n\t\troot.classList.toggle('liforma-et--parallax', parallaxActive);\n\t\troot.classList.toggle('liforma-et--scroll-parallax', scrollParallax);\n\t\troot.style.setProperty('--liforma-et-parallax-x', String(parallaxX));\n\t\troot.style.setProperty('--liforma-et-parallax-y', String(parallaxY));\n\t};\n\n\tconst detachScroll = attachScrollLinkedParallax(root, {\n\t\tonParallax: (x) => {\n\t\t\tscrollParallax = true;\n\t\t\tif (!pointerDown) {\n\t\t\t\tparallaxX = x;\n\t\t\t\tparallaxY = 0;\n\t\t\t\tsync();\n\t\t\t}\n\t\t},\n\t\tpointerActive: () => pointerDown\n\t});\n\n\tconst onPointerMove = (event: PointerEvent) => {\n\t\tif (scrollParallax) return;\n\t\tconst rect = root.getBoundingClientRect();\n\t\tif (rect.width <= 0 || rect.height <= 0) return;\n\t\tparallaxX = ((event.clientX - rect.left) / rect.width - 0.5) * 2;\n\t\tparallaxY = ((event.clientY - rect.top) / rect.height - 0.5) * 2;\n\t\tsync();\n\t};\n\n\tconst onPointerDown = (event: PointerEvent) => {\n\t\tpointerDown = true;\n\t\tif (event.pointerType === 'mouse') hovering = true;\n\t\tsync();\n\t};\n\n\tconst onPointerUp = () => {\n\t\tconst wasDown = pointerDown;\n\t\tpointerDown = false;\n\t\thovering = false;\n\t\tif (scrollParallax && wasDown) {\n\t\t\tparallaxX = horizontalParallaxFromViewportY(\n\t\t\t\troot.getBoundingClientRect(),\n\t\t\t\twindow.innerHeight\n\t\t\t);\n\t\t\tparallaxY = 0;\n\t\t} else if (!scrollParallax) {\n\t\t\tparallaxX = 0;\n\t\t\tparallaxY = 0;\n\t\t}\n\t\tsync();\n\t};\n\n\tconst onPointerEnter = (event: PointerEvent) => {\n\t\tif (event.pointerType === 'mouse') {\n\t\t\thovering = true;\n\t\t\tsync();\n\t\t}\n\t};\n\n\tconst onPointerLeave = () => {\n\t\tif (!pointerDown) {\n\t\t\thovering = false;\n\t\t\tif (!scrollParallax) {\n\t\t\t\tparallaxX = 0;\n\t\t\t\tparallaxY = 0;\n\t\t\t}\n\t\t\tsync();\n\t\t}\n\t};\n\n\troot.addEventListener('pointermove', onPointerMove);\n\troot.addEventListener('pointerdown', onPointerDown);\n\troot.addEventListener('pointerup', onPointerUp);\n\troot.addEventListener('pointercancel', onPointerUp);\n\troot.addEventListener('pointerenter', onPointerEnter);\n\troot.addEventListener('pointerleave', onPointerLeave);\n\n\treturn () => {\n\t\tdetachScroll();\n\t\troot.removeEventListener('pointermove', onPointerMove);\n\t\troot.removeEventListener('pointerdown', onPointerDown);\n\t\troot.removeEventListener('pointerup', onPointerUp);\n\t\troot.removeEventListener('pointercancel', onPointerUp);\n\t\troot.removeEventListener('pointerenter', onPointerEnter);\n\t\troot.removeEventListener('pointerleave', onPointerLeave);\n\t\troot.classList.remove('liforma-et--parallax', 'liforma-et--scroll-parallax');\n\t\troot.style.removeProperty('--liforma-et-parallax-x');\n\t\troot.style.removeProperty('--liforma-et-parallax-y');\n\t};\n}\n","/** Shared styles for ExperienceThumbnail (class prefix `liforma-et`). */\nexport const EXPERIENCE_THUMBNAIL_CSS = `\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.65rem;\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","let styleMounted = false;\n\n/** Inject shared thumbnail CSS once per document. */\nexport function ensureExperienceThumbnailStyles(css: string): void {\n\tif (typeof document === 'undefined' || styleMounted) return;\n\tif (document.querySelector('style[data-liforma-et]')) {\n\t\tstyleMounted = true;\n\t\treturn;\n\t}\n\tconst style = document.createElement('style');\n\tstyle.setAttribute('data-liforma-et', '');\n\tstyle.textContent = css;\n\tdocument.head.appendChild(style);\n\tstyleMounted = true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"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/attachThumbnailParallax.ts","../../../src/thumbnail/styles.ts","../../../src/thumbnail/ensureStyles.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';\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","import {\n\tattachScrollLinkedParallax,\n\thorizontalParallaxFromViewportY\n} from './scrollParallax';\n\n/**\n * Drive `--liforma-et-parallax-*` CSS vars and `liforma-et--parallax` /\n * `liforma-et--scroll-parallax` classes from pointer (desktop) or scroll (touch).\n * Matches ExperienceThumbnail / `<liforma-experience-thumbnail>` behaviour.\n */\nexport function attachThumbnailParallax(root: HTMLElement): () => void {\n\tlet parallaxX = 0;\n\tlet parallaxY = 0;\n\tlet hovering = false;\n\tlet scrollParallax = false;\n\tlet pointerDown = false;\n\n\tconst sync = () => {\n\t\tconst parallaxActive = hovering || scrollParallax;\n\t\troot.classList.toggle('liforma-et--parallax', parallaxActive);\n\t\troot.classList.toggle('liforma-et--scroll-parallax', scrollParallax);\n\t\troot.style.setProperty('--liforma-et-parallax-x', String(parallaxX));\n\t\troot.style.setProperty('--liforma-et-parallax-y', String(parallaxY));\n\t};\n\n\tconst detachScroll = attachScrollLinkedParallax(root, {\n\t\tonParallax: (x) => {\n\t\t\tscrollParallax = true;\n\t\t\tif (!pointerDown) {\n\t\t\t\tparallaxX = x;\n\t\t\t\tparallaxY = 0;\n\t\t\t\tsync();\n\t\t\t}\n\t\t},\n\t\tpointerActive: () => pointerDown\n\t});\n\n\tconst onPointerMove = (event: PointerEvent) => {\n\t\tif (scrollParallax) return;\n\t\tconst rect = root.getBoundingClientRect();\n\t\tif (rect.width <= 0 || rect.height <= 0) return;\n\t\tparallaxX = ((event.clientX - rect.left) / rect.width - 0.5) * 2;\n\t\tparallaxY = ((event.clientY - rect.top) / rect.height - 0.5) * 2;\n\t\tsync();\n\t};\n\n\tconst onPointerDown = (event: PointerEvent) => {\n\t\tpointerDown = true;\n\t\tif (event.pointerType === 'mouse') hovering = true;\n\t\tsync();\n\t};\n\n\tconst onPointerUp = () => {\n\t\tconst wasDown = pointerDown;\n\t\tpointerDown = false;\n\t\thovering = false;\n\t\tif (scrollParallax && wasDown) {\n\t\t\tparallaxX = horizontalParallaxFromViewportY(\n\t\t\t\troot.getBoundingClientRect(),\n\t\t\t\twindow.innerHeight\n\t\t\t);\n\t\t\tparallaxY = 0;\n\t\t} else if (!scrollParallax) {\n\t\t\tparallaxX = 0;\n\t\t\tparallaxY = 0;\n\t\t}\n\t\tsync();\n\t};\n\n\tconst onPointerEnter = (event: PointerEvent) => {\n\t\tif (event.pointerType === 'mouse') {\n\t\t\thovering = true;\n\t\t\tsync();\n\t\t}\n\t};\n\n\tconst onPointerLeave = () => {\n\t\tif (!pointerDown) {\n\t\t\thovering = false;\n\t\t\tif (!scrollParallax) {\n\t\t\t\tparallaxX = 0;\n\t\t\t\tparallaxY = 0;\n\t\t\t}\n\t\t\tsync();\n\t\t}\n\t};\n\n\troot.addEventListener('pointermove', onPointerMove);\n\troot.addEventListener('pointerdown', onPointerDown);\n\troot.addEventListener('pointerup', onPointerUp);\n\troot.addEventListener('pointercancel', onPointerUp);\n\troot.addEventListener('pointerenter', onPointerEnter);\n\troot.addEventListener('pointerleave', onPointerLeave);\n\n\treturn () => {\n\t\tdetachScroll();\n\t\troot.removeEventListener('pointermove', onPointerMove);\n\t\troot.removeEventListener('pointerdown', onPointerDown);\n\t\troot.removeEventListener('pointerup', onPointerUp);\n\t\troot.removeEventListener('pointercancel', onPointerUp);\n\t\troot.removeEventListener('pointerenter', onPointerEnter);\n\t\troot.removeEventListener('pointerleave', onPointerLeave);\n\t\troot.classList.remove('liforma-et--parallax', 'liforma-et--scroll-parallax');\n\t\troot.style.removeProperty('--liforma-et-parallax-x');\n\t\troot.style.removeProperty('--liforma-et-parallax-y');\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.65rem;\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","let styleMounted = false;\n\n/** Inject shared thumbnail CSS once per document. */\nexport function ensureExperienceThumbnailStyles(css: string): void {\n\tif (typeof document === 'undefined' || styleMounted) return;\n\tif (document.querySelector('style[data-liforma-et]')) {\n\t\tstyleMounted = true;\n\t\treturn;\n\t}\n\tconst style = document.createElement('style');\n\tstyle.setAttribute('data-liforma-et', '');\n\tstyle.textContent = css;\n\tdocument.head.appendChild(style);\n\tstyleMounted = true;\n}\n"],"mappings":";AAiDA,SAAgB,EAAkB,GAA0C;CAG3E,OAFI,MAAQ,SAAe,yBACvB,MAAQ,WAAiB,2BACtB;AACR;;;ACpDA,IAAa,IAAiC;CAAC;CAAW;CAAW;AAAU,GAGzE,IAAmB;AAOzB,SAAgB,EACf,GACA,GAC6B;CAM7B,OALI,CAAC,OAAO,SAAS,CAAK,KAAK,CAAC,OAAO,SAAS,CAAM,KAAK,IAAQ,KAAK,IAAS,KAG7E,KAAS,IAAe,YACxB,IAAQ,OAAO,IAAS,MAAY,aACjC;AACR;AAEA,SAAgB,EACf,GACA,GACS;CACT,IAAM,IAAU,EAAI,KAAK;CAEzB,OADK,KACE,EAAQ,QACd,IACC,GAAQ,GAAc,GAAkB,GAAa,MACrD,GAAG,IAAO,IAAO,IAAM,GACzB;AACD;AAGA,SAAgB,EACf,GACA,GACa;CACb,IAAM,UAAa;EAClB,EAAM,EAAQ,aAAa,EAAQ,YAAY;CAChD;CAEA,IADA,EAAK,GACD,OAAO,iBAAmB,KAC7B,aAAa,CAAC;CAEf,IAAM,IAAW,IAAI,eAAe,CAAI;CAExC,OADA,EAAS,QAAQ,CAAO,SACX,EAAS,WAAW;AAClC;;;AC7CA,IAAa,IAAiC,0BAExC,IAAwB;AAE9B,SAAgB,EAA6B,GAA6B;CAEzE,OADgB,GAAY,KAAK,EAAE,QAAQ,OAAO,EAAE,KAAA;AAErD;AAEA,SAAS,EAAU,GAAwB;CAC1C,OAAO,gBAAgB,KAAK,CAAK,KAAK,EAAM,WAAW,IAAI;AAC5D;AAEA,SAAS,EAAgB,GAAuB;CAE/C,OADI,EAAM,WAAW,IAAI,IAAU,SAAS,MACrC;AACR;AAEA,SAAS,EAAmB,GAAqD;CAChF,IAAM,IAAI,EAAM,QAAQ,GAAG;CAE3B,OADI,MAAM,KAAW;EAAE,UAAU;EAAO,QAAQ;CAAG,IAC5C;EAAE,UAAU,EAAM,MAAM,GAAG,CAAC;EAAG,QAAQ,EAAM,MAAM,CAAC;CAAE;AAC9D;AAEA,SAAS,EAAQ,GAAiB,GAAkB,IAAS,IAAY;CAExE,OAAO,GAAG,IADG,EAAS,WAAW,GAAG,IAAI,IAAW,IAAI,MAC5B;AAC5B;AAQA,SAAgB,EACf,GACA,GACA,GACS;CACT,IAAM,IAAM,EAAI,KAAK;CACrB,IAAI,CAAC,GAAK,OAAO;CACjB,IAAM,IAAU,EAA6B,CAAU;CAEvD,IAAI,EAAU,CAAG,GAChB,OAAO,EAAkC,EAAgB,CAAG,GAAG,CAAiB;CAGjF,IAAM,EAAE,UAAU,GAAS,cAAW,EAAmB,EAAI,QAAQ,OAAO,GAAG,CAAC,GAC5E,IAAW;CAEf,IAAI,CAAC,EAAS,WAAW,GAAG,GAC3B,IAAW,cAAc,EAAS,QAAQ,iBAAiB,EAAE;MACvD,IAAI,mBAAkB,KAAK,CAAQ,GAKzC,OAHU,gBAAgB,KAAK,CAAQ,IAChC,EAAQ,GAAS,GAAU,CAAM,IAEjC;CAGR,IAAI,WAAW,KAAK,CAAQ,KAAK,CAAC,EAAsB,KAAK,EAAS,QAAQ,YAAY,EAAE,CAAC,GAC5F,OAAO,EAAQ,GAAS,GAAU,CAAM;CAGzC,IAAI,IAAO,EAAS,QAAQ,mBAAmB,EAAE,EAAE,QAAQ,YAAY,EAAE;CAEzE,OADA,IAAO,EAAK,QAAQ,GAAuB,EAAE,GACtC,GAAG,EAAQ,aAAa,EAAK,GAAG,EAAkB,OAAO;AACjE;AAMA,SAAgB,EAAyB,GAAa,GAA6B;CAClF,IAAM,IAAM,EAAI,KAAK;CACrB,IAAI,CAAC,GAAK,OAAO;CACjB,IAAM,IAAU,EAA6B,CAAU;CAEvD,IAAI,EAAU,CAAG,GAChB,OAAO,EAAgB,CAAG;CAG3B,IAAM,EAAE,UAAU,GAAS,cAAW,EAAmB,EAAI,QAAQ,OAAO,GAAG,CAAC,GAC5E,IAAW;CAEf,IAAI,CAAC,EAAS,WAAW,GAAG,GAC3B,IAAI,cAAc,KAAK,CAAQ,GAC9B,IAAW,IAAI;MACT,IAAI,UAAU,KAAK,CAAQ,KAAK,CAAC,GACvC,OAAO,GAAG,EAAQ,WAAW,EAAS,OAAO,EAAS;MAEtD,OAAO;CAIT,IAAI,CAAC,gBAAgB,KAAK,CAAQ,GACjC,OAAO;CAGR,IAAM,IAAO,EAAS,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,QAAQ,EAAE;CAKrE,OAJI,KAAQ,CAAC,EAAK,SAAS,GAAG,KAAK,CAAC,IAC5B,GAAG,EAAQ,WAAW,EAAK,OAAO,EAAK,iBAGxC,EAAQ,GAAS,GAAU,CAAM;AACzC;;;ACzGA,IAAM,IAAiB,kCACjB,IAAqB;AAI3B,SAAS,EAAc,GAAsB;CAK5C,OAJI,iBAAiB,KAAK,CAAG,KAAK,gBAAgB,KAAK,CAAG,IAAU,KAChE,gBAAgB,KAAK,CAAG,KAAK,EAAI,WAAW,IAAI,KAChD,iBAAiB,KAAK,CAAG,KACzB,EAAI,WAAW,GAAG,IAAU,KACzB,EAAI,SAAS,GAAG;AACxB;AASA,SAAgB,EACf,GACA,IAAgD,WAChD,GACS;CACT,IAAM,IAAM,EAAI,KAAK;CACrB,IAAI,CAAC,GAAK,OAAO;CAEjB,IAAI,EAAc,CAAG,GACpB,OAAO,EAA2B,GAAK,GAAmB,CAAU;CAGrE,IAAI,IAAO,EAAyB,GAAK,CAAU;CAKnD,OAJA,IAAO,EAAK,QAAQ,IAAiB,GAAQ,GAAgB,GAAc,MACtE,MAAS,QAAc,GAAG,IAAS,IAAO,MACvC,GAAG,EAAO,KAAK,GACtB,GACM,EAAK,QAAQ,GAAoB,YAAY;AACrD;AAEA,SAAgB,EAAuB,GAqB9B;CACR,IAAM,IACL,EAAM,aAAa,KAAK,KACxB,EAAM,cAAc,aAAa,KAAK,KACtC;CACD,IAAI,CAAC,GAAc,OAAO;CAE1B,IAAM,IACL,EAAM,iBAAiB,KAAK,KAAK,EAAM,cAAc,iBAAiB,KAAK,KAAK,IAC3E,IACL,EAAM,iBAAiB,KAAK,KAAK,EAAM,cAAc,iBAAiB,KAAK,KAAK,IAC3E,IAAiB,EAAM,eAAe,KAAK,KAAK,IAChD,IAAQ,EAAM,qBAAqB,WACnC,IAAa,EAA6B,EAAM,UAAU,GAC1D,IAAe,EAAM,cAAc,IAEnC,KAAiB,MAClB,CAAC,KAAgB,gBAAgB,KAAK,EAAI,KAAK,CAAC,IAAU,EAAI,KAAK,IAChE,EAAkB,GAAK,GAAO,CAAU,GAE1C,KAAmB,MACpB,CAAC,KAAgB,gBAAgB,KAAK,EAAI,KAAK,CAAC,IAAU,EAAI,KAAK,IAChE,EAA2B,GAAK,GAAO,CAAU;CAGzD,OAAO;EACN,aAAa,EAAc,CAAY;EACvC,GAAI,IAAmB,EAAE,iBAAiB,EAAgB,CAAgB,EAAE,IAAI,CAAC;EACjF,GAAI,IAAmB,EAAE,iBAAiB,EAAgB,CAAgB,EAAE,IAAI,CAAC;EACjF,GAAI,IAAiB,EAAE,eAAe,EAAc,CAAc,EAAE,IAAI,CAAC;CAC1E;AACD;;;AC/FA,SAAgB,IAAiC;CAChD,IAAK,WAA4C,oBAAoB,SACpE,OAAO;CAGR,IAAI,OAAO,WAAa,KACvB,OAAO;CAGR,IAAI;EACH,OAAO,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,OAAO,MAAM;CAC9D,QAAQ;EACP,OAAO;CACR;AACD;;;ACfA,IAAM,IAA2B,6BAC3B,IAAsB;AAG5B,SAAgB,EACf,GAC2B;CAI3B,OAHI,EAAM,MAAM,KAAK,IAAU,WAC3B,EAAM,kBAAwB,WAC9B,EAAM,cAAc,KAAK,IAAU,WAChC;AACR;AAEA,SAAgB,EAAqB,GAAgC;CACpE,IAAM,IAAa,GAAe,KAAK;CAOvC,OANI,IAAmB,EAAW,QAAQ,OAAO,EAAE,IAE/C,EAAsB,IAClB,IAGD;AACR;AAMA,SAAgB,EAAqB,GAI1B;CACV,IAAM,IAAe,EAAQ,aAAa,KAAK,GACzC,IAAO,EAAqB,EAAQ,aAAa,GACjD,IAAM,IAAI,IAAI,GAAG,EAAK,GAAG,mBAAmB,CAAY,GAAG,GAC3D,IAAY,EAAQ,WAAW,KAAK;CAI1C,OAHI,KACH,EAAI,aAAa,IAAI,aAAa,CAAS,GAErC,EAAI,SAAS;AACrB;AAGA,SAAgB,EAAqB,GAOd;CACtB,IAAM,IAAO,EAAQ,MAAM,KAAK;CAChC,IAAI,GAAM,OAAO;CAEjB,IAAM,IAAe,EAAQ,cAAc,KAAK;CAChD,IAAI,CAAC,GAAc;CAEnB,IAAI,IAAY,EAAQ,WAAW,KAAK;CAKxC,OAJI,CAAC,KAAa,EAAQ,6BAA6B,OAAO,SAAW,QACxE,IAAY,OAAO,SAAS,OAGtB,EAAqB;EAC3B;EACA,GAAI,IAAY,EAAE,aAAU,IAAI,CAAC;EACjC,eAAe,EAAQ;CACxB,CAAC;AACF;;;ACtEA,IAAa,IAAgC,GAEhC,IAA+B;AAE5C,SAAgB,IAAgC;CAE/C,OADI,OAAO,SAAW,MAAoB,KACnC,OAAO,WAAW,kCAAkC,EAAE;AAC9D;AAOA,IAAI,IAAW,GACT,IAAwB,CAAC;AAE/B,SAAS,IAAwB;CAChC,OAAO,EAAqB,IAAA,IAAoC;AACjE;AAEA,SAAS,IAAoB;CAC5B,OAAO,EAAqB,IAAA,MAAmC;AAChE;AAEA,SAAS,IAAa;CACrB,IAAM,IAAQ,EAAc;CAC5B,OAAO,IAAW,KAAS,EAAQ,SAAS,IAAG;EAC9C,IAAM,IAAO,EAAQ,MAAM;EAC3B,IAAI,CAAC,KAAQ,EAAK,WAAW;EAE7B,AADA,KAAY,GACZ,EAAK,MAAM;EACX,IAAM,IAAQ,EAAU;EACxB,IAAI,KAAS,GAAG;GACf,IAAW,KAAK,IAAI,GAAG,IAAW,CAAC;GACnC;EACD;EACA,OAAO,iBAAiB;GAEvB,AADA,IAAW,KAAK,IAAI,GAAG,IAAW,CAAC,GACnC,EAAK;EACN,GAAG,CAAK;CACT;AACD;AAOA,SAAgB,EAAkB,GAA+B;CAChE,IAAI,CAAC,EAAqB,GAEzB,OADA,EAAM,SACO,CAAC;CAGf,IAAM,IAAmB;EAAE;EAAO,WAAW;CAAM;CAInD,OAHA,EAAQ,KAAK,CAAI,GACjB,EAAK,SAEQ;EACZ,EAAK,YAAY;CAClB;AACD;;;ACvDA,SAAgB,IAAmC;CAClD,OAAO,EAAqB,IAAI,aAAa;AAC9C;AAEA,SAAS,EAA0B,GAA4B;CAC9D,IAAM,IAAQ,EAAW,KAAK,EAAE,MAAM,KAAK,EAAE,MAAM,OAC7C,IAAQ,OAAO,WAAW,CAAK;CACrC,OAAO,OAAO,SAAS,CAAK,IAAI,KAAK,IAAI,CAAK,IAAI;AACnD;AAEA,SAAS,EAAe,GAAsB,GAA2B;CACxE,IAAI,OAAO,SAAW,KAAa,OAAO;CAC1C,IAAM,IAAO,EAAQ,sBAAsB;CAC3C,OAAO,EAAK,UAAU,CAAC,KAAY,EAAK,OAAO,OAAO,cAAc;AACrE;AAGA,SAAgB,EAAgB,GAAsB,GAAuC;CAC5F,IAAM,EAAE,uBAAoB,GACtB,IAAa,EAAQ,cAAc,EAAyB,GAC5D,IAAW,EAA0B,CAAU,GACjD,IAAU,IACV,IAAc,IACd,IAAuC,MAErC,UAAoB;EACrB,MACJ,IAAkB,GAClB,IAAkB,QAAwB;GAGzC,AAFA,IAAkB,MAClB,IAAc,IACd,EAAgB,EAAI;EACrB,CAAC;CACF,GAEM,KAAc,MAAkB;EACjC,UAAS,GAEb;OADA,IAAU,GACN,GAAM;IACT,EAAY;IACZ;GACD;GAIA,AAFA,IAAkB,GAClB,IAAkB,MAClB,EAAgB,EAAK;EAJrB;CAKD;CAEA,IAAI,OAAO,uBAAyB,KAEnC,OADA,EAAY,SACC;EAEZ,AADA,IAAkB,GAClB,IAAkB;CACnB;CAGD,IAAM,IAAW,IAAI,sBACnB,MAAY;EACZ,EAAW,EAAQ,MAAM,MAAU,EAAM,cAAc,CAAC;CACzD,GACA;EAAE,MAAM;EAAM;EAAY,WAAW;CAAE,CACxC;CAQA,OANA,EAAS,QAAQ,CAAO,GAEpB,EAAe,GAAS,CAAQ,KACnC,EAAW,EAAI,SAGH;EAGZ,AAFA,IAAkB,GAClB,IAAkB,MAClB,EAAS,WAAW;CACrB;AACD;AAEA,SAAgB,EAAiB,GAA2B,GAAiC;CAC5F,IAAM,UAAe,EAAQ;CAO7B,OANI,EAAQ,WACX,eAAe,CAAM,KAErB,EAAQ,iBAAiB,QAAQ,GAAQ,EAAE,MAAM,GAAK,CAAC,GACvD,EAAQ,iBAAiB,SAAS,GAAQ,EAAE,MAAM,GAAK,CAAC,UAE5C;EAEZ,AADA,EAAQ,oBAAoB,QAAQ,CAAM,GAC1C,EAAQ,oBAAoB,SAAS,CAAM;CAC5C;AACD;;;AC/FA,SAAgB,IAAuC;CAGtD,OAFI,OAAO,SAAW,OAClB,OAAO,WAAW,kCAAkC,EAAE,UAAgB,KACnE,OAAO,WAAW,kCAAkC,EAAE;AAC9D;AAKA,SAAgB,EAAgC,GAAe,GAAgC;CAG9F,SAFgB,EAAK,MAAM,EAAK,SAAS,KACrB,KAAK,IAAI,GAAgB,CAAC,IAClC,MAAO;AACpB;AAaA,IAAM,oBAAc,IAAI,IAA8B,GAClD,IAAY,IACZ,IAAQ;AAEZ,SAAS,IAAgB;CAExB,IADA,IAAQ,GACJ,EAAY,SAAS,KAAK,OAAO,SAAW,KAAa;CAC7D,IAAM,IAAiB,OAAO;CAC9B,KAAK,IAAM,KAAS,GAAa;EAChC,IAAI,EAAM,gBAAgB,GAAG;EAC7B,IAAM,IAAO,EAAM,QAAQ,sBAAsB;EAE7C,EAAK,SAAS,OAAO,EAAK,MAAM,IAAiB,MACrD,EAAM,WAAW,EAAgC,GAAM,CAAc,CAAC;CACvE;AACD;AAEA,SAAS,IAAqB;CACzB,MAAU,MACd,IAAQ,sBAAsB,CAAO;AACtC;AAEA,SAAS,IAAwB;CAC5B,KAAa,OAAO,SAAW,QACnC,IAAY,IACZ,OAAO,iBAAiB,UAAU,GAAc,EAAE,SAAS,GAAK,CAAC,GACjE,OAAO,iBAAiB,UAAU,GAAc,EAAE,SAAS,GAAK,CAAC;AAClE;AAEA,SAAS,IAA4B;CAChC,EAAY,OAAO,KAAK,CAAC,KAAa,OAAO,SAAW,QAC5D,IAAY,IACR,MAAU,MACb,qBAAqB,CAAK,GAC1B,IAAQ,IAET,OAAO,oBAAoB,UAAU,CAAY,GACjD,OAAO,oBAAoB,UAAU,CAAY;AAClD;AAMA,SAAgB,EACf,GACA,GACa;CACb,IAAI,CAAC,EAA4B,GAAG,aAAa,CAAC;CAElD,IAAM,IAAkC;EACvC;EACA,YAAY,EAAQ;EACpB,eAAe,EAAQ;CACxB;CAUA,OATA,EAAY,IAAI,CAAK,GACrB,EAAgB,GAEX,EAAM,gBAAgB,KAC1B,EAAQ,WACP,EAAgC,EAAQ,sBAAsB,GAAG,OAAO,WAAW,CACpF,SAGY;EAEZ,AADA,EAAY,OAAO,CAAK,GACxB,EAAoB;CACrB;AACD;;;ACrFA,SAAgB,EAAwB,GAA+B;CACtE,IAAI,IAAY,GACZ,IAAY,GACZ,IAAW,IACX,IAAiB,IACjB,IAAc,IAEZ,UAAa;EAClB,IAAM,IAAiB,KAAY;EAInC,AAHA,EAAK,UAAU,OAAO,wBAAwB,CAAc,GAC5D,EAAK,UAAU,OAAO,+BAA+B,CAAc,GACnE,EAAK,MAAM,YAAY,2BAA2B,OAAO,CAAS,CAAC,GACnE,EAAK,MAAM,YAAY,2BAA2B,OAAO,CAAS,CAAC;CACpE,GAEM,IAAe,EAA2B,GAAM;EACrD,aAAa,MAAM;GAElB,AADA,IAAiB,IACZ,MACJ,IAAY,GACZ,IAAY,GACZ,EAAK;EAEP;EACA,qBAAqB;CACtB,CAAC,GAEK,KAAiB,MAAwB;EAC9C,IAAI,GAAgB;EACpB,IAAM,IAAO,EAAK,sBAAsB;EACpC,EAAK,SAAS,KAAK,EAAK,UAAU,MACtC,MAAc,EAAM,UAAU,EAAK,QAAQ,EAAK,QAAQ,MAAO,GAC/D,MAAc,EAAM,UAAU,EAAK,OAAO,EAAK,SAAS,MAAO,GAC/D,EAAK;CACN,GAEM,KAAiB,MAAwB;EAG9C,AAFA,IAAc,IACV,EAAM,gBAAgB,YAAS,IAAW,KAC9C,EAAK;CACN,GAEM,UAAoB;EACzB,IAAM,IAAU;EAahB,AAZA,IAAc,IACd,IAAW,IACP,KAAkB,KACrB,IAAY,EACX,EAAK,sBAAsB,GAC3B,OAAO,WACR,GACA,IAAY,KACD,MACX,IAAY,GACZ,IAAY,IAEb,EAAK;CACN,GAEM,KAAkB,MAAwB;EAC/C,AAAI,EAAM,gBAAgB,YACzB,IAAW,IACX,EAAK;CAEP,GAEM,UAAuB;EAC5B,AAAK,MACJ,IAAW,IACN,MACJ,IAAY,GACZ,IAAY,IAEb,EAAK;CAEP;CASA,OAPA,EAAK,iBAAiB,eAAe,CAAa,GAClD,EAAK,iBAAiB,eAAe,CAAa,GAClD,EAAK,iBAAiB,aAAa,CAAW,GAC9C,EAAK,iBAAiB,iBAAiB,CAAW,GAClD,EAAK,iBAAiB,gBAAgB,CAAc,GACpD,EAAK,iBAAiB,gBAAgB,CAAc,SAEvC;EAUZ,AATA,EAAa,GACb,EAAK,oBAAoB,eAAe,CAAa,GACrD,EAAK,oBAAoB,eAAe,CAAa,GACrD,EAAK,oBAAoB,aAAa,CAAW,GACjD,EAAK,oBAAoB,iBAAiB,CAAW,GACrD,EAAK,oBAAoB,gBAAgB,CAAc,GACvD,EAAK,oBAAoB,gBAAgB,CAAc,GACvD,EAAK,UAAU,OAAO,wBAAwB,6BAA6B,GAC3E,EAAK,MAAM,eAAe,yBAAyB,GACnD,EAAK,MAAM,eAAe,yBAAyB;CACpD;AACD;;;ACzGA,IAAa,IAA2B,4jLCDpC,IAAe;AAGnB,SAAgB,GAAgC,GAAmB;CAClE,IAAI,OAAO,WAAa,OAAe,GAAc;CACrD,IAAI,SAAS,cAAc,wBAAwB,GAAG;EACrD,IAAe;EACf;CACD;CACA,IAAM,IAAQ,SAAS,cAAc,OAAO;CAI5C,AAHA,EAAM,aAAa,mBAAmB,EAAE,GACxC,EAAM,cAAc,GACpB,SAAS,KAAK,YAAY,CAAK,GAC/B,IAAe;AAChB"}
@@ -0,0 +1,13 @@
1
+ /** Location colour plates used by ExperienceThumbnail (no zoom/portrait crops). */
2
+ export declare const THUMBNAIL_LOCATION_PLATE_SIZES: readonly ["256x256", "800x450", "1672x941"];
3
+ export type ThumbnailLocationPlateSize = (typeof THUMBNAIL_LOCATION_PLATE_SIZES)[number];
4
+ /**
5
+ * Square / portrait hosts keep the gallery crop. Landscape hosts use the 16:9 plate
6
+ * so cover-fit fills height instead of clipping the top and bottom of a square.
7
+ * Unmeasured boxes stay on `256x256`.
8
+ */
9
+ export declare function pickThumbnailLocationPlateSize(width: number, height: number): ThumbnailLocationPlateSize;
10
+ export declare function rewriteThumbnailLocationPlateSize(url: string, size: ThumbnailLocationPlateSize): string;
11
+ /** Observe the thumb host and report CSS box size (for plate picking). */
12
+ export declare function attachThumbnailBox(element: HTMLElement, onBox: (width: number, height: number) => void): () => void;
13
+ //# sourceMappingURL=locationPlate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locationPlate.d.ts","sourceRoot":"","sources":["../../../src/thumbnail/locationPlate.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,eAAO,MAAM,8BAA8B,6CAA8C,CAAC;AAC1F,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAIzF;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC7C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACZ,0BAA0B,CAO5B;AAED,wBAAgB,iCAAiC,CAChD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,0BAA0B,GAC9B,MAAM,CAQR;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAC5C,MAAM,IAAI,CAWZ"}