@alikhalilll/a-smart-skeleton 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -2
  3. package/dist/_chunks/core.js +2187 -0
  4. package/dist/_chunks/core.js.map +1 -0
  5. package/dist/_chunks/index.d.ts +406 -0
  6. package/dist/core/index.cjs +67 -0
  7. package/dist/core/index.d.cts +2 -0
  8. package/dist/core/index.d.ts +2 -0
  9. package/dist/core/index.js +2 -0
  10. package/dist/element/index.cjs +47 -0
  11. package/dist/element/index.cjs.map +1 -0
  12. package/dist/element/index.d.cts +24 -0
  13. package/dist/element/index.d.ts +24 -0
  14. package/dist/element/index.js +46 -0
  15. package/dist/element/index.js.map +1 -0
  16. package/dist/index.cjs +512 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.cts +204 -0
  19. package/dist/index.d.ts +204 -0
  20. package/dist/index.js +441 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/nuxt/index.cjs +36 -0
  23. package/dist/nuxt/index.cjs.map +1 -0
  24. package/dist/nuxt/index.d.cts +30 -0
  25. package/dist/nuxt/index.d.ts +30 -0
  26. package/dist/nuxt/index.js +36 -0
  27. package/dist/nuxt/index.js.map +1 -0
  28. package/dist/resolver/index.cjs +26 -0
  29. package/dist/resolver/index.cjs.map +1 -0
  30. package/dist/resolver/index.d.cts +19 -0
  31. package/dist/resolver/index.d.ts +19 -0
  32. package/dist/resolver/index.js +26 -0
  33. package/dist/resolver/index.js.map +1 -0
  34. package/dist/styles.css +2 -0
  35. package/package.json +144 -3
  36. package/src/components/ASmartSkeleton.vue +228 -0
  37. package/src/components/ASmartSkeletonBox.vue +23 -0
  38. package/src/components/ASmartSkeletonProvider.vue +34 -0
  39. package/src/components/ASmartSkeletonSuspense.vue +142 -0
  40. package/src/composables/useSmartSkeleton.ts +20 -0
  41. package/src/composables/useStyles.ts +8 -0
  42. package/src/core/boot.ts +63 -0
  43. package/src/core/canvas.ts +7 -0
  44. package/src/core/constants.ts +76 -0
  45. package/src/core/debug.ts +154 -0
  46. package/src/core/dev.ts +53 -0
  47. package/src/core/fade.ts +490 -0
  48. package/src/core/ghost.ts +236 -0
  49. package/src/core/images.ts +28 -0
  50. package/src/core/index.ts +71 -0
  51. package/src/core/inline.generated.ts +9 -0
  52. package/src/core/measure.ts +454 -0
  53. package/src/core/memory.ts +146 -0
  54. package/src/core/motion.ts +12 -0
  55. package/src/core/plan.ts +368 -0
  56. package/src/core/reveal.ts +89 -0
  57. package/src/core/reveals.ts +30 -0
  58. package/src/core/shapes.ts +84 -0
  59. package/src/core/shimmer.ts +173 -0
  60. package/src/core/skeleton.ts +43 -0
  61. package/src/core/snapshot.ts +159 -0
  62. package/src/core/styles.ts +167 -0
  63. package/src/core/sweep.ts +203 -0
  64. package/src/core/theme.ts +162 -0
  65. package/src/element/index.ts +59 -0
  66. package/src/index.ts +27 -0
  67. package/src/nuxt/index.ts +59 -0
  68. package/src/resolver/index.ts +35 -0
  69. package/src/types.ts +100 -0
  70. package/src/vue/Skeleton.ts +275 -0
  71. package/src/vue/Suspense.ts +157 -0
  72. package/src/vue/config.ts +78 -0
  73. package/src/vue/index.ts +7 -0
  74. package/web-types.json +256 -0
@@ -0,0 +1,2187 @@
1
+ //#region src/core/constants.ts
2
+ const ATTR_REVEAL = "ask-reveal";
3
+ const ATTR_PANE = "ask-pane";
4
+ const ATTR_LOADING = "ask-loading";
5
+ const ATTR_GHOST = "ask-ghost";
6
+ const ATTR_SNAPSHOT = "ask-snapshot";
7
+ const ATTR_LAYER = "ask-layer";
8
+ const ATTR_RECT = "ask-rect";
9
+ const ATTR_BAR = "ask-bar";
10
+ const ATTR_INACTIVE = "ask-inactive";
11
+ const ATTR_EXITING = "ask-exiting";
12
+ const ATTR_COVERED = "ask-covered";
13
+ const ATTR_OWN = "ask-own";
14
+ const ATTR_IGNORE = "ask-ignore";
15
+ const ATTR_BLOCK = "ask-block";
16
+ const GHOSTED = Symbol.for("a-smart-skeleton.ghost");
17
+ const UNCOVER = Symbol.for("a-smart-skeleton.uncover");
18
+ const ATTR_STATUS = "ask-status";
19
+ const ATTR_XRAY = "ask-xray";
20
+ const ATTR_DEBUG = "ask-debug";
21
+ const BASE = "var(--ask-base, rgb(128 128 128 / 0.2))";
22
+ const HIGHLIGHT = "var(--ask-highlight, rgb(255 255 255 / 0.12))";
23
+ const FADE_DURATION = 200;
24
+ const CASCADE_SPREAD = 320;
25
+ const RESIZE_DURATION = 280;
26
+ const IMAGE_TIMEOUT = 1e4;
27
+ const DURATIONS = {
28
+ shimmer: {
29
+ slow: 4e3,
30
+ fast: 2e3
31
+ },
32
+ pulse: {
33
+ slow: 2400,
34
+ fast: 1200
35
+ }
36
+ };
37
+ const TEXT_TAGS = [
38
+ "a",
39
+ "b",
40
+ "code",
41
+ "dd",
42
+ "dt",
43
+ "em",
44
+ "h1",
45
+ "h2",
46
+ "h3",
47
+ "h4",
48
+ "h5",
49
+ "h6",
50
+ "i",
51
+ "label",
52
+ "li",
53
+ "p",
54
+ "small",
55
+ "span",
56
+ "strong",
57
+ "td",
58
+ "th",
59
+ "time"
60
+ ];
61
+ const MEDIA_TAGS = [
62
+ "audio",
63
+ "canvas",
64
+ "embed",
65
+ "iframe",
66
+ "img",
67
+ "input",
68
+ "object",
69
+ "picture",
70
+ "select",
71
+ "svg",
72
+ "textarea",
73
+ "video"
74
+ ];
75
+ //#endregion
76
+ //#region src/core/styles.ts
77
+ const media = MEDIA_TAGS.join(", ");
78
+ /** The whole stylesheet, for rendering into a page yourself. */
79
+ const SMART_SKELETON_CSS = `
80
+ [${ATTR_REVEAL}] {
81
+ position: relative;
82
+ }
83
+
84
+ a-smart-skeleton-reveal,
85
+ ask-reveal {
86
+ display: block;
87
+ }
88
+
89
+ a-smart-skeleton-reveal[loading]:not(:defined),
90
+ ask-reveal[loading]:not(:defined) {
91
+ visibility: hidden;
92
+ }
93
+
94
+ [${ATTR_PANE}] {
95
+ display: block;
96
+ }
97
+
98
+ [${ATTR_LOADING}] {
99
+ position: relative;
100
+ -webkit-user-select: none;
101
+ user-select: none;
102
+ pointer-events: none;
103
+ }
104
+
105
+ [${ATTR_INACTIVE}] {
106
+ display: none !important;
107
+ }
108
+
109
+ [${ATTR_LAYER}] {
110
+ position: absolute;
111
+ inset: 0;
112
+ overflow: visible;
113
+ }
114
+
115
+ [${ATTR_BAR}] {
116
+ display: block;
117
+ background-color: ${BASE};
118
+ background-repeat: no-repeat;
119
+ }
120
+
121
+ /* Containers keep their surface; what they hold is drawn by bones, measured before the first paint */
122
+ @scope ([${ATTR_LOADING}]) to ([${ATTR_OWN}], [${ATTR_LAYER}]) {
123
+ :scope,
124
+ :scope * {
125
+ color: transparent !important;
126
+ -webkit-text-fill-color: transparent !important;
127
+ text-shadow: none !important;
128
+ text-decoration-color: transparent !important;
129
+ }
130
+
131
+ :scope ::placeholder {
132
+ color: transparent !important;
133
+ }
134
+
135
+ /* A line to measure in empty text; content only, never a size, or a dot-sized span would grow round it */
136
+ :scope :is(${TEXT_TAGS.join(", ")}):empty:not(svg *)::before {
137
+ content: "000000";
138
+ }
139
+
140
+ /* Leaves are drawn by bones, so their own paint never shows; visibility, as measuring reads it */
141
+ :scope :is(:not(:has(*)), ${media}, [${ATTR_COVERED}], [${ATTR_BLOCK}]):not(svg *) {
142
+ visibility: hidden !important;
143
+ }
144
+
145
+ /* No script to measure: a bar struck through each line of text instead */
146
+ @media (scripting: none) {
147
+ :scope :not(:has(*)):not(svg *) {
148
+ visibility: visible !important;
149
+ }
150
+
151
+ :scope,
152
+ :scope * {
153
+ text-decoration: line-through ${BASE} !important;
154
+ text-decoration-thickness: 0.72em !important;
155
+ text-decoration-skip-ink: none !important;
156
+ }
157
+
158
+ :scope :is(${media}, [${ATTR_BLOCK}]):not(svg *) {
159
+ visibility: visible !important;
160
+ background: ${BASE} !important;
161
+ }
162
+
163
+ :scope svg * {
164
+ fill: transparent !important;
165
+ stroke: transparent !important;
166
+ }
167
+
168
+ :scope :is(img, video) {
169
+ object-position: -100vw 0;
170
+ }
171
+ }
172
+ }
173
+
174
+ /* Read by screen readers in the skeleton's place, never seen */
175
+ [${ATTR_STATUS}] {
176
+ position: absolute;
177
+ width: 1px;
178
+ height: 1px;
179
+ margin: -1px;
180
+ overflow: hidden;
181
+ clip-path: inset(50%);
182
+ white-space: nowrap;
183
+ }
184
+
185
+ /* Debug x-ray: the ghost shown through its bones, to see what was measured */
186
+ [${ATTR_XRAY}] [${ATTR_LOADING}] [${ATTR_GHOST}],
187
+ [${ATTR_XRAY}] [${ATTR_LOADING}] [${ATTR_GHOST}] *:not(svg *) {
188
+ visibility: visible !important;
189
+ color: rgb(255 0 80 / 0.7) !important;
190
+ -webkit-text-fill-color: rgb(255 0 80 / 0.7) !important;
191
+ }
192
+
193
+ /* Lets addStyles see a stylesheet already on the page, from styles.css or a server render */
194
+ :root {
195
+ --ask: 1;
196
+ }`;
197
+ let styleEl = null;
198
+ let refCount = 0;
199
+ let present;
200
+ function addStyles() {
201
+ refCount++;
202
+ if (styleEl) return;
203
+ present ??= !!getComputedStyle(document.documentElement).getPropertyValue("--ask");
204
+ if (present) return;
205
+ styleEl = document.createElement("style");
206
+ styleEl.dataset.aSmartSkeleton = "true";
207
+ styleEl.textContent = SMART_SKELETON_CSS;
208
+ document.head.appendChild(styleEl);
209
+ }
210
+ function removeStyles() {
211
+ refCount--;
212
+ if (refCount > 0 || !styleEl) return;
213
+ styleEl.remove();
214
+ styleEl = null;
215
+ }
216
+ //#endregion
217
+ //#region src/core/inline.generated.ts
218
+ const INLINE_SCRIPT = "\"use strict\";(()=>{var B=\"ask-ghost\";var D=\"ask-layer\",L=\"ask-rect\";var R=\"ask-covered\",$=\"ask-own\",v=\"ask-ignore\",H=\"ask-block\",I=Symbol.for(\"a-smart-skeleton.ghost\"),G=Symbol.for(\"a-smart-skeleton.uncover\"),P=\"a-smart-skeleton.boot\";var W=\"var(--ask-base, rgb(128 128 128 / 0.2))\",z=\"var(--ask-highlight, rgb(255 255 255 / 0.12))\";var F={shimmer:{slow:4e3,fast:2e3},pulse:{slow:2400,fast:1200}},X=[\"a\",\"b\",\"code\",\"dd\",\"dt\",\"em\",\"h1\",\"h2\",\"h3\",\"h4\",\"h5\",\"h6\",\"i\",\"label\",\"li\",\"p\",\"small\",\"span\",\"strong\",\"td\",\"th\",\"time\"],U=[\"audio\",\"canvas\",\"embed\",\"iframe\",\"img\",\"input\",\"object\",\"picture\",\"select\",\"svg\",\"textarea\",\"video\"];var gt,V=()=>gt??(gt=/jsdom/.test(navigator.userAgent)?null:document.createElement(\"canvas\").getContext(\"2d\"));var J=(t,n)=>t?{left:Math.max(t.left,n.left),right:Math.min(t.right,n.right),top:Math.max(t.top,n.top),bottom:Math.min(t.bottom,n.bottom)}:n,A=t=>[[t.left,t.top],[t.right,t.top],[t.right,t.bottom],[t.left,t.bottom]],k=(t,n)=>Math.abs(t[0]-n[0])<1&&Math.abs(t[1]-n[1])<1;function Q(t,n){let e=Math.min(n.right-n.left,n.bottom-n.top),o=r=>r.endsWith(\"%\")?parseFloat(r)/100*e:parseFloat(r)||0;return[o(t.borderTopLeftRadius),o(t.borderTopRightRadius),o(t.borderBottomRightRadius),o(t.borderBottomLeftRadius)]}function bt(t,n,e){let o=J(t,n),r=Q(e,n),c=t?A(t):[],a=A(n),m=A(o).map((f,T)=>k(f,a[T])?r[T]:t&&k(f,c[T])?t.corners[T]:0),{left:s,right:i,top:l,bottom:h}=o;return{left:s,right:i,top:l,bottom:h,corners:m}}function Tt(t,n,e,o){if(!t)return e;let r=A(t),c=A(n).map((m,s)=>t.corners[s]>0&&k(m,r[s]));if(!c.some(Boolean))return e;let a=Q(o,n);return c.map((m,s)=>m?Math.max(a[s],t.corners[s]):a[s]).map(m=>`${m}px`).join(\" \")}var j=(t,n)=>{let e=J(n,t);return e.right-e.left>=2&&e.bottom-e.top>=2?e:null},q={top:\"ascender\",bottom:\"descender\"},C=new Map,tt=t=>[...t.getClientRects?.()??[]];function St(t){let n=`${t.fontStyle} ${t.fontWeight} ${t.fontSize} ${t.fontFamily}`;if(C.has(n))return C.get(n);let e=V(),o=null;if(e){e.font=n;let r=e.measureText(\"H\"),c=a=>e.measureText(a).actualBoundingBoxAscent;o={ascent:r.fontBoundingBoxAscent,descent:r.fontBoundingBoxDescent,ascender:c(\"bdfhkl\"),capHeight:r.actualBoundingBoxAscent,xHeight:c(\"xz\"),descender:e.measureText(\"gjpqy\").actualBoundingBoxDescent}}return C.set(n,o),o}var Y={ascender:\"ascender\",\"cap-height\":\"capHeight\",\"x-height\":\"xHeight\"},K=(t,n,e,o)=>{let r=t.getPropertyValue(n).trim();return e.includes(r)?r:o};function xt(t,n,e){let o=St(n);if(!o){let s=Math.min(e*.72,t.bottom-t.top),i=(t.top+t.bottom)/2;return{barTop:i-s/2,barBottom:i+s/2}}let r=K(n,\"--ask-text-top\",Object.keys(Y),q.top),c=K(n,\"--ask-text-bottom\",[\"baseline\",\"descender\"],q.bottom),a=o.ascent+o.descent,m=t.top+(t.bottom-t.top-a)/2+o.ascent;return{barTop:m-o[Y[r]],barBottom:m+(c===\"descender\"?o.descender:0)}}var Et=new Set(U),Rt=new Set(X),et=.4,At=/[,/]\\s*0\\)$/,y=(t,n)=>t!==\"\"&&t!==n,yt=[\"Top\",\"Right\",\"Bottom\",\"Left\"],Mt=t=>yt.every(n=>y(t[`border${n}Style`],\"none\")&&y(t[`border${n}Width`],\"0px\")),Ct=t=>{let[n=0,e=0,o=0,r=0]=(t.replace(/rgba?\\([^)]*\\)|#\\w+/g,\"\").match(/-?[\\d.]+px/g)??[]).map(parseFloat);return t.includes(\"inset\")&&!o&&!r&&n===0!=(e===0)&&Math.abs(n+e)<=2},kt=t=>y(t,\"none\")&&!t.split(/,(?![^(]*\\))/).every(Ct),Ot=t=>y(t.backgroundColor,\"transparent\")&&!At.test(t.backgroundColor)||y(t.backgroundImage,\"none\")||kt(t.boxShadow)||Mt(t),ot=t=>Et.has(t.localName),nt=t=>Rt.has(t.localName)&&!t.firstElementChild&&!t.textContent?.trim(),wt=t=>{let n=document.createTreeWalker(t,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT),e=document.createRange(),o=[],r=0;for(;n.nextNode();){let i=n.currentNode;if(i.nodeType===Node.TEXT_NODE){let l=i.parentElement;if(!i.textContent?.trim()||!l)continue;r++;let h=l.getBoundingClientRect();if(h.width<=1||h.height<=1)continue;e.selectNodeContents(i),o.push(tt(e).filter(f=>f.width||f.height))}else nt(i)&&(r++,o.push([...i.getClientRects()]))}if(!o.some(i=>i.length))return r;let c=(parseFloat(getComputedStyle(t).fontSize)||16)*et,a=(i,l)=>Math.min(i.bottom,l.bottom)-Math.max(i.top,l.top)>=Math.min(i.height,l.height)/2&&Math.max(i.left,l.left)-Math.min(i.right,l.right)<=c,m=o.map((i,l)=>l),s=i=>m[i]===i?i:m[i]=s(m[i]);for(let i=0;i<o.length;i++)for(let l=i+1;l<o.length;l++)o[i].some(h=>o[l].some(f=>a(h,f)))&&(m[s(l)]=s(i));return new Set(o.map((i,l)=>s(l))).size},Nt=(t,n)=>t.hasAttribute(H)||ot(t)||Ot(n)&&wt(t)<=1;function _t(t,n){if(parseFloat(n.borderRadius)||!t.parentElement)return n.borderRadius||\"0px\";let e=t.parentElement,o=getComputedStyle(e),r=t.getBoundingClientRect(),c=e.getBoundingClientRect();return Math.abs(r.width-c.width)<1&&Math.abs(r.height-c.height)<1&&o.overflow!==\"visible\"?o.borderRadius:n.borderRadius||\"0px\"}function Z(t){t.sort((e,o)=>e.left-o.left);let n=[];for(let e of t){let o=n.find(r=>{let c=Math.min(r.bottom,e.bottom)-Math.max(r.top,e.top),a=Math.min(r.bottom-r.top,e.bottom-e.top),m=e.left-r.right;return c>=a/2&&m<=Math.max(r.fontSize,e.fontSize)*et});o?(o.left=Math.min(o.left,e.left),o.right=Math.max(o.right,e.right),o.top=Math.min(o.top,e.top),o.bottom=Math.max(o.bottom,e.bottom),o.fontSize=Math.max(o.fontSize,e.fontSize),o.barTop=Math.min(o.barTop,e.barTop),o.barBottom=Math.max(o.barBottom,e.barBottom)):n.push({...e})}return n.map(e=>{let o=e.barBottom-e.barTop;return{x:e.left,y:e.barTop,width:e.right-e.left,height:o,radius:e.radius||`${Math.round(o/3)}px`}})}function rt(t,n,{mark:e=!0,onCovered:o}={}){let r=n.getBoundingClientRect(),c=n.offsetWidth?r.width/n.offsetWidth:1,a=n.offsetHeight?r.height/n.offsetHeight:1,m=d=>({left:(d.left-r.left)/c,right:(d.right-r.left)/c,top:(d.top-r.top)/a,bottom:(d.bottom-r.top)/a}),s=[],i=[],l=new Set,h=document.createRange(),f=(d,g,b)=>{let u=j(d,b);if(!u)return;let p=parseFloat(g.fontSize)||16,M=m(u);i.push({...M,fontSize:p,...xt(M,g,p),radius:g.getPropertyValue(\"--ask-radius\").trim()})},T=(d,g)=>{let b=d.data.search(/\\S/);if(b<0||!d.parentElement)return;h.setStart(d,b),h.setEnd(d,d.data.trimEnd().length);let u=getComputedStyle(d.parentElement);for(let p of tt(h))f(p,u,g)},_=(d,g)=>{for(let b of d.childNodes){if(b.nodeType===Node.TEXT_NODE){T(b,g);continue}if(b.nodeType!==Node.ELEMENT_NODE)continue;let u=b;if(u.hasAttribute(v)||u.hasAttribute($))continue;let p=getComputedStyle(u);if(p.display===\"none\")continue;if(Nt(u,p)){let E=j(u.getBoundingClientRect(),g);if(E){let S=m(E);s.push({x:S.left,y:S.top,width:S.right-S.left,height:S.bottom-S.top,radius:Tt(g,E,_t(u,p),p)})}ot(u)||l.add(u);continue}if(nt(u)){for(let E of u.getClientRects())f(E,p,g);continue}let M=p.overflowX!==\"visible\"||p.overflowY!==\"visible\";_(u,M?bt(g,u.getBoundingClientRect(),p):g)}};if(_(t,null),o?.(l),!e)return[...s,...Z(i)];for(let d of t.querySelectorAll(`[${R}]`))l.has(d)||d.removeAttribute(R);for(let d of l)d.hasAttribute(R)||d.setAttribute(R,\"\");return[...s,...Z(i)]}var Bt=t=>typeof matchMedia<\"u\"&&matchMedia(t).matches,Dt=()=>typeof Element<\"u\"&&typeof Element.prototype.animate==\"function\",st=()=>!Dt()||Bt(\"(prefers-reduced-motion: reduce)\");var it=t=>t.getAnimations?.()??[];var Lt=.7,$t=1280,at=320,vt=2.5,Ht=.45,It=[\"shimmer\",\"group-shimmer\",\"pulse\",\"none\"];function mt(t){let n=getComputedStyle(t),e=(c,a,m)=>{let s=n.getPropertyValue(c).trim();return a.includes(s)?s:m},o=e(\"--ask-animation\",It,\"shimmer\"),r=e(\"--ask-speed\",[\"slow\",\"fast\"],\"slow\");return{animation:o,duration:F[o===\"pulse\"?\"pulse\":\"shimmer\"][r]}}var w=20,Gt=Math.sin(w*Math.PI/180),lt=Math.cos(w*Math.PI/180),Pt=Gt/lt;var Wt=`${90+w}deg`,O=.85,x=12,ct=t=>(1-Math.cos(Math.PI*t))/2;function zt(t){let n=t*vt,e=(r,c)=>`color-mix(in srgb, ${z} ${(c*100).toFixed(1)}%, transparent) ${(r*t).toFixed(2)}px`,o=[];for(let r=0;r<=x;r++)o.push(e(r/x*O,ct(r/x)));for(let r=1;r<=x;r++)o.push(e(O+r/x*(1-O),ct(1-r/x)));return{width:n/lt,image:`repeating-linear-gradient(${Wt}, ${o.join(\", \")}, transparent ${n.toFixed(2)}px)`}}function Ft(t,n,e=mt(t)){for(let f of it(t))f.cancel();if(t.style.backgroundImage=\"\",t.style.backgroundRepeat=\"\",st()||typeof t.animate!=\"function\"||e.animation===\"none\")return;let o={duration:e.duration,iterations:1/0};if(e.animation===\"pulse\"){t.animate([{opacity:1,easing:\"ease-in-out\"},{opacity:Ht,easing:\"ease-in-out\"},{opacity:1}],o).startTime=0;return}let r=t.getBoundingClientRect(),c=r.left+scrollX,a=e.animation===\"group-shimmer\"?n??{left:c,top:r.top,width:r.width,height:r.height}:{left:0,top:0,width:document.documentElement.clientWidth||innerWidth,height:innerHeight},m=Math.min(Math.max(a.width*.4,80),at),{width:s,image:i}=zt(m),l=c-a.left+(r.top-a.top)*Pt,h=e.duration*Lt/($t+at);t.style.backgroundImage=i,t.style.backgroundSize=`${s}px 100%`,t.style.backgroundRepeat=\"repeat-x\",t.animate([{backgroundPosition:`${-l}px 0`},{backgroundPosition:`${s-l}px 0`}],{duration:s*h,iterations:1/0}).startTime=0}var Xt=t=>t.shadowRoot??t.attachShadow({mode:\"open\"});function dt(t,n){let e=Xt(t),o=t.getBoundingClientRect(),r={left:o.left+scrollX,top:o.top,width:o.width,height:o.height},c=mt(t);for(n.forEach((a,m)=>{let s=e.children[m];s||(s=document.createElement(\"div\"),s.setAttribute(L,\"\"),s.style.cssText=`position:absolute;display:block;pointer-events:none;background-color:${W};background-repeat:no-repeat`,e.append(s)),Object.assign(s.style,{left:`${a.x}px`,top:`${a.y}px`,width:`${a.width}px`,height:`${a.height}px`,borderRadius:a.radius});let i=`${Math.round(r.left+a.x)}|${Math.round(a.y)}|${Math.round(a.height)}|${Math.round(r.left)}|${Math.round(r.width)}|${Math.round(r.height)}|${document.documentElement.clientWidth}|${c.animation}|${c.duration}`;s.dataset.motion!==i&&(s.dataset.motion=i,Ft(s,r,c))});e.children.length>n.length;)e.lastElementChild.remove()}var pt=t=>t.parentElement?`${pt(t.parentElement)} > :nth-child(${[...t.parentElement.children].indexOf(t)+1})`:\":root\";function N(t){let n=t?.previousElementSibling,e=n?.querySelector(`:scope > [${B}]`),o=n?.querySelector(`:scope > [${D}]`);if(!n||!e||!o)return;let r=typeof CSSStyleSheet==\"function\"&&\"replaceSync\"in CSSStyleSheet.prototype?new CSSStyleSheet:null;r&&(document.adoptedStyleSheets=[...document.adoptedStyleSheets,r],n[G]=()=>{document.adoptedStyleSheets=document.adoptedStyleSheets.filter(s=>s!==r)});let c=s=>r?.replaceSync(s.size?`${[...s].map(pt).join(\", \")} { visibility: hidden !important; }`:\"\"),a=()=>{if(!o.isConnected||n[I])return m.disconnect();e.getClientRects().length&&dt(o,rt(e,o,{mark:!1,onCovered:c}))},m=new ResizeObserver(a);m.observe(n),document.fonts?.ready.then(a)}var ht=self,ft=Symbol.for(P),ut=ht[ft];ht[ft]=N;Array.isArray(ut)&&ut.forEach(N);N(document.currentScript);})();";
219
+ /** For every skeleton after a page's first: calls the boot INLINE_SCRIPT left on window. */
220
+ const INLINE_STUB = "(s=>{let k=Symbol.for(\"a-smart-skeleton.boot\"),b=self[k];typeof b==\"function\"?b(s):(self[k]=b||[]).push(s)})(document.currentScript)";
221
+ /** Either, as a whole element — for templates where a literal script tag would be read as the component's own. */
222
+ const inlineScriptTag = (nonce, stub = false) => `<script${nonce ? ` nonce="${nonce.replace(/"/g, "&quot;")}"` : ""}>${stub ? INLINE_STUB : INLINE_SCRIPT}<\/script>`;
223
+ //#endregion
224
+ //#region src/core/theme.ts
225
+ const DEFAULT_COLORS = {
226
+ light: {
227
+ base: "rgb(0 0 0 / 0.07)",
228
+ highlight: "rgb(255 255 255 / 0.6)"
229
+ },
230
+ dark: {
231
+ base: "rgb(255 255 255 / 0.08)",
232
+ highlight: "rgb(255 255 255 / 0.1)"
233
+ }
234
+ };
235
+ const DEFAULT_THEME = {
236
+ ...DEFAULT_COLORS,
237
+ animation: "shimmer",
238
+ speed: "slow",
239
+ text: {
240
+ top: "ascender",
241
+ bottom: "descender"
242
+ }
243
+ };
244
+ const ANIMATION = new Set([
245
+ "shimmer",
246
+ "group-shimmer",
247
+ "pulse",
248
+ "none"
249
+ ]);
250
+ const SPEED = new Set(["slow", "fast"]);
251
+ const TEXT_TOP = new Set([
252
+ "ascender",
253
+ "cap-height",
254
+ "x-height"
255
+ ]);
256
+ const TEXT_BOTTOM = new Set(["baseline", "descender"]);
257
+ const LENGTH = /^[\d.\s,%()+\-*/a-z]+$/i;
258
+ const safeLength = (value) => {
259
+ const trimmed = value.trim();
260
+ if (!trimmed || trimmed.length > 100) return null;
261
+ if (/[<>;}{"'`]/.test(trimmed)) return null;
262
+ return LENGTH.test(trimmed) ? trimmed : null;
263
+ };
264
+ const safeColor = (value) => {
265
+ const trimmed = value.trim();
266
+ if (!trimmed || trimmed.length > 200) return null;
267
+ if (/[;}<>]/.test(trimmed)) return null;
268
+ return trimmed;
269
+ };
270
+ /** A theme's custom properties for one set of colours, for a style attribute. */
271
+ function themeVariables(theme = {}, mode = null) {
272
+ const variables = {};
273
+ const colors = mode && (theme[mode] ?? DEFAULT_COLORS[mode]);
274
+ if (colors) {
275
+ const base = safeColor(colors.base);
276
+ const highlight = safeColor(colors.highlight);
277
+ if (base) variables["--ask-base"] = base;
278
+ if (highlight) variables["--ask-highlight"] = highlight;
279
+ }
280
+ if (theme.radius) {
281
+ const radius = safeLength(theme.radius);
282
+ if (radius) variables["--ask-radius"] = radius;
283
+ }
284
+ if (theme.animation && ANIMATION.has(theme.animation)) variables["--ask-animation"] = theme.animation;
285
+ if (theme.speed && SPEED.has(theme.speed)) variables["--ask-speed"] = theme.speed;
286
+ if (theme.text?.top && TEXT_TOP.has(theme.text.top)) variables["--ask-text-top"] = theme.text.top;
287
+ if (theme.text?.bottom && TEXT_BOTTOM.has(theme.text.bottom)) variables["--ask-text-bottom"] = theme.text.bottom;
288
+ return variables;
289
+ }
290
+ const declarations = (variables) => Object.entries(variables).map(([name, value]) => `${name}: ${value};`).join(" ");
291
+ /** CSS for a theme on `selector`; colours follow `mode`, and are left out when neither is given. */
292
+ function skeletonCSS({ theme = {}, mode, text, selector = ":root" }) {
293
+ const resolved = {
294
+ ...theme,
295
+ text: {
296
+ ...theme.text,
297
+ ...text
298
+ }
299
+ };
300
+ const colored = mode ?? (theme.light || theme.dark ? "system" : null);
301
+ const base = themeVariables(resolved, colored === "dark" ? "dark" : colored ? "light" : null);
302
+ const rules = [];
303
+ if (Object.keys(base).length) rules.push(`${selector} { ${declarations(base)} }`);
304
+ if (colored === "system") {
305
+ const dark = themeVariables({ dark: theme.dark }, "dark");
306
+ rules.push(`@media (prefers-color-scheme: dark) { ${selector} { ${declarations(dark)} } }`);
307
+ }
308
+ return rules.join("\n");
309
+ }
310
+ let themeEl = null;
311
+ /** Sets a theme for the whole page, for setups without a provider. */
312
+ function applyTheme(theme, mode, { nonce } = {}) {
313
+ themeEl ??= document.head.appendChild(document.createElement("style"));
314
+ if (nonce) themeEl.nonce = nonce;
315
+ themeEl.textContent = skeletonCSS({
316
+ theme,
317
+ mode
318
+ });
319
+ }
320
+ /** The same CSS as a whole element, for templates that can only render HTML strings. */
321
+ const themeStyleTag = (config, nonce) => {
322
+ const css = skeletonCSS(config);
323
+ return css ? `<style${nonce ? ` nonce="${nonce.replace(/"/g, "&quot;")}"` : ""}>${css}</style>` : "";
324
+ };
325
+ //#endregion
326
+ //#region src/core/debug.ts
327
+ const entries = /* @__PURE__ */ new Map();
328
+ let enabled;
329
+ let host$1 = null;
330
+ let frame = 0;
331
+ function fromPage() {
332
+ try {
333
+ return new URLSearchParams(location.search).has("ask-debug") || localStorage.getItem("a-smart-skeleton:debug") === "1";
334
+ } catch {
335
+ return false;
336
+ }
337
+ }
338
+ const everywhere = () => enabled ??= typeof window !== "undefined" && fromPage();
339
+ /** On with `?ask-debug`, `a-smart-skeleton:debug` = "1" in localStorage or `setDebug(true)`; for `element`, also inside `[ask-debug]`. */
340
+ const debugging = (element) => everywhere() || !!element?.closest(`[ask-debug]`);
341
+ function setDebug(on) {
342
+ enabled = on;
343
+ if (on) start();
344
+ else stop();
345
+ }
346
+ function reportLoading(reveal, source) {
347
+ if (!reveal || !debugging(reveal)) return;
348
+ entries.set(reveal, {
349
+ state: "loading",
350
+ source
351
+ });
352
+ show();
353
+ }
354
+ function reportReveal(reveal, moved) {
355
+ if (!reveal || !debugging(reveal)) return;
356
+ entries.set(reveal, {
357
+ ...entries.get(reveal),
358
+ state: "shown",
359
+ moved
360
+ });
361
+ show();
362
+ }
363
+ function show() {
364
+ start();
365
+ cancelAnimationFrame(frame);
366
+ draw();
367
+ }
368
+ const PANEL_CSS = `
369
+ :host { all: initial; }
370
+ .box { position: fixed; box-sizing: border-box; border: 1px dashed; border-radius: 4px; }
371
+ .label {
372
+ position: absolute; left: -1px; bottom: 100%; margin-bottom: 2px;
373
+ padding: 1px 6px; border-radius: 4px; white-space: nowrap;
374
+ font: 500 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace; color: #fff;
375
+ }
376
+ .loading { border-color: #3b82f6; } .loading .label { background: #3b82f6; }
377
+ .still { border-color: #22c55e; } .still .label { background: #16a34a; }
378
+ .moved { border-color: #ef4444; border-style: solid; } .moved .label { background: #dc2626; }
379
+ .panel {
380
+ position: fixed; right: 12px; bottom: 12px; display: flex; gap: 8px; align-items: center;
381
+ padding: 6px 10px; border-radius: 8px; background: #111; color: #fff; pointer-events: auto;
382
+ font: 500 12px/16px ui-sans-serif, system-ui, sans-serif; box-shadow: 0 4px 16px rgb(0 0 0 / 0.3);
383
+ }
384
+ .panel label { display: flex; gap: 4px; align-items: center; cursor: pointer; }
385
+ `;
386
+ function start() {
387
+ if (typeof document === "undefined") return;
388
+ if (!host$1?.isConnected) {
389
+ host$1 = document.createElement("div");
390
+ host$1.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:2147483647";
391
+ const shadow = host$1.attachShadow({ mode: "open" });
392
+ shadow.innerHTML = `<style>${PANEL_CSS}</style><div class="boxes"></div>`;
393
+ document.body.append(host$1);
394
+ }
395
+ const shadow = host$1.shadowRoot;
396
+ if (enabled && !shadow.querySelector(".panel")) {
397
+ const panel = document.createElement("div");
398
+ panel.className = "panel";
399
+ panel.innerHTML = `a-smart-skeleton debug <label><input type="checkbox" class="xray"> x-ray</label>`;
400
+ panel.querySelector(".xray").addEventListener("change", (event) => {
401
+ document.documentElement.toggleAttribute(ATTR_XRAY, event.target.checked);
402
+ });
403
+ shadow.append(panel);
404
+ }
405
+ if (!frame) frame = requestAnimationFrame(draw);
406
+ }
407
+ function stop() {
408
+ host$1?.shadowRoot?.querySelector(".panel")?.remove();
409
+ document.documentElement.removeAttribute(ATTR_XRAY);
410
+ if ([...entries.keys()].some((reveal) => debugging(reveal))) return;
411
+ cancelAnimationFrame(frame);
412
+ frame = 0;
413
+ host$1?.remove();
414
+ host$1 = null;
415
+ }
416
+ const describe = ({ state, source, moved }) => {
417
+ const from = source ? ` · ${source}` : "";
418
+ if (state === "loading") return {
419
+ kind: "loading",
420
+ text: `skeleton${from}`
421
+ };
422
+ if (!moved || moved.width === 0 && moved.height === 0) return {
423
+ kind: "still",
424
+ text: `✓ no movement${from}`
425
+ };
426
+ return {
427
+ kind: "moved",
428
+ text: `✗ moved ${moved.width}×${moved.height}px${from}`
429
+ };
430
+ };
431
+ function draw() {
432
+ frame = 0;
433
+ if (!host$1?.shadowRoot) return;
434
+ const boxes = host$1.shadowRoot.querySelector(".boxes");
435
+ for (const reveal of entries.keys()) if (!reveal.isConnected || !debugging(reveal)) entries.delete(reveal);
436
+ const items = [...entries];
437
+ while (boxes.children.length > items.length) boxes.lastElementChild.remove();
438
+ items.forEach(([reveal, entry], index) => {
439
+ let box = boxes.children[index];
440
+ if (!box) {
441
+ box = boxes.appendChild(document.createElement("div"));
442
+ box.innerHTML = `<span class="label"></span>`;
443
+ }
444
+ const rect = reveal.getBoundingClientRect();
445
+ const { kind, text } = describe(entry);
446
+ box.className = `box ${kind}`;
447
+ Object.assign(box.style, {
448
+ left: `${rect.left - 2}px`,
449
+ top: `${rect.top - 2}px`,
450
+ width: `${rect.width + 4}px`,
451
+ height: `${rect.height + 4}px`
452
+ });
453
+ box.firstElementChild.textContent = text;
454
+ });
455
+ if (enabled || entries.size) frame = requestAnimationFrame(draw);
456
+ }
457
+ //#endregion
458
+ //#region src/core/canvas.ts
459
+ let context;
460
+ const textContext = () => context ??= /jsdom/.test(navigator.userAgent) ? null : document.createElement("canvas").getContext("2d");
461
+ //#endregion
462
+ //#region src/core/snapshot.ts
463
+ const MEDIA$1 = new Set(MEDIA_TAGS);
464
+ const REMOVED = "script, noscript, template, base, meta, link";
465
+ const REPLACED = new Set([
466
+ "iframe",
467
+ "object",
468
+ "embed"
469
+ ]);
470
+ const STRIPPED = new Set([
471
+ "src",
472
+ "srcset",
473
+ "sizes",
474
+ "poster",
475
+ "data",
476
+ "srcdoc",
477
+ "action",
478
+ "formaction",
479
+ "formtarget",
480
+ "background",
481
+ "ping",
482
+ "dynsrc",
483
+ "lowsrc",
484
+ "value",
485
+ "alt",
486
+ ATTR_COVERED
487
+ ]);
488
+ const STYLE_UNSAFE = /url\s*\(\s*['"]?\s*(javascript|vbscript):|expression\s*\(|<|>/i;
489
+ const fill = (value) => value.replace(/[A-Z]/g, "X").replace(/[a-z]/g, "x").replace(/\d/g, "0");
490
+ function fillText(original, copy) {
491
+ const filled = fill(copy.data);
492
+ copy.data = filled;
493
+ const parent = original.parentElement;
494
+ const context = textContext();
495
+ if (!parent || !context || !filled.trim()) return;
496
+ const style = getComputedStyle(parent);
497
+ context.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
498
+ const shape = (value) => {
499
+ const collapsed = value.replace(/\s+/g, " ");
500
+ if (style.textTransform === "uppercase") return collapsed.toUpperCase();
501
+ if (style.textTransform === "lowercase") return collapsed.toLowerCase();
502
+ return collapsed;
503
+ };
504
+ const delta = (context.measureText(shape(original.data)).width - context.measureText(shape(filled)).width) / shape(filled).length;
505
+ if (Math.abs(delta) < .01) return;
506
+ const span = document.createElement("span");
507
+ span.style.letterSpacing = `${(parseFloat(style.letterSpacing) || 0) + delta}px`;
508
+ copy.replaceWith(span);
509
+ span.append(copy);
510
+ }
511
+ function sanitize(root) {
512
+ for (const element of root.querySelectorAll(REMOVED)) element.remove();
513
+ for (const element of root.querySelectorAll("*")) {
514
+ if (REPLACED.has(element.localName)) {
515
+ const stand = document.createElement("span");
516
+ stand.style.cssText = element.getAttribute("style") ?? "";
517
+ stand.style.display ||= "inline-block";
518
+ element.replaceWith(stand);
519
+ continue;
520
+ }
521
+ for (const { name, value } of [...element.attributes]) {
522
+ const lower = name.toLowerCase();
523
+ if (lower.startsWith("on") || STRIPPED.has(lower) || lower.endsWith("href") && /^\s*(javascript|vbscript|data):/i.test(value) || lower === "style" && STYLE_UNSAFE.test(value)) element.removeAttribute(name);
524
+ }
525
+ if (element.localName === "img") element.setAttribute("alt", "");
526
+ }
527
+ for (const source of root.querySelectorAll("video source, picture source")) source.remove();
528
+ }
529
+ /** The markup in `root`, safe to keep: no scripts, handlers or media sources; `filler` replaces its text too. */
530
+ function serialize(root, filler) {
531
+ const clone = root.cloneNode(true);
532
+ const show = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
533
+ const original = document.createTreeWalker(root, show);
534
+ const copy = document.createTreeWalker(clone, show);
535
+ const texts = [];
536
+ const media = [];
537
+ while (original.nextNode() && copy.nextNode()) {
538
+ const a = original.currentNode;
539
+ const b = copy.currentNode;
540
+ if (a.nodeType === Node.TEXT_NODE) {
541
+ if (filler) texts.push([a, b]);
542
+ } else if (MEDIA$1.has(a.localName) || REPLACED.has(a.localName)) media.push([a, b]);
543
+ }
544
+ for (const [a, b] of media) {
545
+ const rect = a.getBoundingClientRect();
546
+ b.style.boxSizing = "border-box";
547
+ b.style.width = `${rect.width}px`;
548
+ if (rect.width && rect.height) {
549
+ b.style.maxWidth = "100%";
550
+ b.style.height = "auto";
551
+ b.style.aspectRatio = `${rect.width} / ${rect.height}`;
552
+ } else b.style.height = `${rect.height}px`;
553
+ }
554
+ for (const [a, b] of texts) fillText(a, b);
555
+ sanitize(clone);
556
+ return clone.innerHTML;
557
+ }
558
+ function restore$1(html) {
559
+ const template = document.createElement("template");
560
+ template.innerHTML = html;
561
+ sanitize(template.content);
562
+ return template.content;
563
+ }
564
+ const signature = (element, depth) => [...element.children].map((child) => {
565
+ const classes = [...child.classList].sort().join(".");
566
+ const inner = depth > 1 && child.firstElementChild ? `(${signature(child, depth - 1)})` : "";
567
+ return `${child.localName}${classes ? `.${classes}` : ""}${inner}`;
568
+ }).join(",");
569
+ const hash = (value) => {
570
+ let h = 5381;
571
+ for (let i = 0; i < value.length; i++) h = h * 33 ^ value.charCodeAt(i);
572
+ return (h >>> 0).toString(36);
573
+ };
574
+ /** Identifies content by its page and the shape of its top two levels — the parts that rarely hang on data. */
575
+ function autoKey(element) {
576
+ const shape = signature(element, 2);
577
+ return shape ? `${location.pathname}|${hash(shape)}` : null;
578
+ }
579
+ //#endregion
580
+ //#region src/core/dev.ts
581
+ let dev = false;
582
+ try {
583
+ dev = process.env.NODE_ENV !== "production";
584
+ } catch {
585
+ dev = false;
586
+ }
587
+ const isDev = dev;
588
+ const warned = /* @__PURE__ */ new Set();
589
+ function warnOnce(id, message) {
590
+ if (!isDev || warned.has(id)) return;
591
+ warned.add(id);
592
+ console.warn(`[a-smart-skeleton] ${message}`);
593
+ }
594
+ const URL_LIKE = /^(https?:|\/|data:|blob:)/;
595
+ /**
596
+ * Warns once per app if the page has a meta CSP that restricts inline scripts and
597
+ * the consumer did not pass a nonce through the provider. Silent when CSP is absent
598
+ * or when the loading pane isn't on the client yet — we can't read CSP headers from
599
+ * script, so the meta-tag check is a best-effort signal to developers.
600
+ */
601
+ function checkNonce(nonce) {
602
+ if (!isDev || typeof document === "undefined" || nonce) return;
603
+ const content = document.querySelector("meta[http-equiv=\"Content-Security-Policy\" i]")?.getAttribute("content") ?? "";
604
+ if (!/script-src/i.test(content)) return;
605
+ if (/'unsafe-inline'/.test(content) || /\*/.test(content)) return;
606
+ warnOnce("csp:missing-nonce", "ASmartSkeleton ships an inline boot script that measures the server-rendered skeleton before first paint. Your page has a Content-Security-Policy with script-src but no nonce was passed to <ASmartSkeletonProvider :nonce=\"…\">, so the browser will block it and first paint will be blank. Pass the same nonce you used for the response header.");
607
+ }
608
+ /** Same keys and lengths as `data`, with every string replaced by filler. */
609
+ function sampleFrom(data) {
610
+ if (typeof data === "string") return URL_LIKE.test(data) ? "" : fill(data);
611
+ if (Array.isArray(data)) return data.map(sampleFrom);
612
+ if (data instanceof Date) return data;
613
+ if (data && typeof data === "object") return Object.fromEntries(Object.entries(data).map(([key, value]) => [key, sampleFrom(value)]));
614
+ return data;
615
+ }
616
+ //#endregion
617
+ //#region src/core/measure.ts
618
+ const intersect = (clip, rect) => clip ? {
619
+ left: Math.max(clip.left, rect.left),
620
+ right: Math.min(clip.right, rect.right),
621
+ top: Math.max(clip.top, rect.top),
622
+ bottom: Math.min(clip.bottom, rect.bottom)
623
+ } : rect;
624
+ const cornersOf = (rect) => [
625
+ [rect.left, rect.top],
626
+ [rect.right, rect.top],
627
+ [rect.right, rect.bottom],
628
+ [rect.left, rect.bottom]
629
+ ];
630
+ const near = (a, b) => Math.abs(a[0] - b[0]) < 1 && Math.abs(a[1] - b[1]) < 1;
631
+ function radiiOf(style, rect) {
632
+ const size = Math.min(rect.right - rect.left, rect.bottom - rect.top);
633
+ const px = (value) => value.endsWith("%") ? parseFloat(value) / 100 * size : parseFloat(value) || 0;
634
+ return [
635
+ px(style.borderTopLeftRadius),
636
+ px(style.borderTopRightRadius),
637
+ px(style.borderBottomRightRadius),
638
+ px(style.borderBottomLeftRadius)
639
+ ];
640
+ }
641
+ function clipWithin(clip, rect, style) {
642
+ const box = intersect(clip, rect);
643
+ const own = radiiOf(style, rect);
644
+ const outer = clip ? cornersOf(clip) : [];
645
+ const ownCorners = cornersOf(rect);
646
+ const corners = cornersOf(box).map((corner, i) => near(corner, ownCorners[i]) ? own[i] : clip && near(corner, outer[i]) ? clip.corners[i] : 0);
647
+ const { left, right, top, bottom } = box;
648
+ return {
649
+ left,
650
+ right,
651
+ top,
652
+ bottom,
653
+ corners
654
+ };
655
+ }
656
+ function roundedBy(clip, box, own, style) {
657
+ if (!clip) return own;
658
+ const clipCorners = cornersOf(clip);
659
+ const touching = cornersOf(box).map((corner, i) => clip.corners[i] > 0 && near(corner, clipCorners[i]));
660
+ if (!touching.some(Boolean)) return own;
661
+ const mine = radiiOf(style, box);
662
+ return touching.map((touches, i) => touches ? Math.max(mine[i], clip.corners[i]) : mine[i]).map((r) => `${r}px`).join(" ");
663
+ }
664
+ const clipped = (rect, clip) => {
665
+ const box = intersect(clip, rect);
666
+ return box.right - box.left >= 2 && box.bottom - box.top >= 2 ? box : null;
667
+ };
668
+ const DEFAULT_TEXT = {
669
+ top: "ascender",
670
+ bottom: "descender"
671
+ };
672
+ const metricsByFont = /* @__PURE__ */ new Map();
673
+ const lineRects = (range) => [...range.getClientRects?.() ?? []];
674
+ function metricsOf(style) {
675
+ const font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
676
+ if (metricsByFont.has(font)) return metricsByFont.get(font);
677
+ const context = textContext();
678
+ let metrics = null;
679
+ if (context) {
680
+ context.font = font;
681
+ const box = context.measureText("H");
682
+ const up = (text) => context.measureText(text).actualBoundingBoxAscent;
683
+ metrics = {
684
+ ascent: box.fontBoundingBoxAscent,
685
+ descent: box.fontBoundingBoxDescent,
686
+ ascender: up("bdfhkl"),
687
+ capHeight: box.actualBoundingBoxAscent,
688
+ xHeight: up("xz"),
689
+ descender: context.measureText("gjpqy").actualBoundingBoxDescent
690
+ };
691
+ }
692
+ metricsByFont.set(font, metrics);
693
+ return metrics;
694
+ }
695
+ const TOPS = {
696
+ ascender: "ascender",
697
+ "cap-height": "capHeight",
698
+ "x-height": "xHeight"
699
+ };
700
+ const rule = (style, name, allowed, fallback) => {
701
+ const value = style.getPropertyValue(name).trim();
702
+ return allowed.includes(value) ? value : fallback;
703
+ };
704
+ function barOf(rect, style, fontSize) {
705
+ const metrics = metricsOf(style);
706
+ if (!metrics) {
707
+ const height = Math.min(fontSize * .72, rect.bottom - rect.top);
708
+ const middle = (rect.top + rect.bottom) / 2;
709
+ return {
710
+ barTop: middle - height / 2,
711
+ barBottom: middle + height / 2
712
+ };
713
+ }
714
+ const top = rule(style, "--ask-text-top", Object.keys(TOPS), DEFAULT_TEXT.top);
715
+ const bottom = rule(style, "--ask-text-bottom", ["baseline", "descender"], DEFAULT_TEXT.bottom);
716
+ const box = metrics.ascent + metrics.descent;
717
+ const baseline = rect.top + (rect.bottom - rect.top - box) / 2 + metrics.ascent;
718
+ return {
719
+ barTop: baseline - metrics[TOPS[top]],
720
+ barBottom: baseline + (bottom === "descender" ? metrics.descender : 0)
721
+ };
722
+ }
723
+ const MEDIA = new Set(MEDIA_TAGS);
724
+ const TEXT = new Set(TEXT_TAGS);
725
+ const LINE_GAP = .4;
726
+ const TRANSPARENT = /[,/]\s*0\)$/;
727
+ const isSet = (value, none) => value !== "" && value !== none;
728
+ const SIDES = [
729
+ "Top",
730
+ "Right",
731
+ "Bottom",
732
+ "Left"
733
+ ];
734
+ const bordered = (style) => SIDES.every((side) => isSet(style[`border${side}Style`], "none") && isSet(style[`border${side}Width`], "0px"));
735
+ const isRule = (shadow) => {
736
+ const [x = 0, y = 0, blur = 0, spread = 0] = (shadow.replace(/rgba?\([^)]*\)|#\w+/g, "").match(/-?[\d.]+px/g) ?? []).map(parseFloat);
737
+ return shadow.includes("inset") && !blur && !spread && x === 0 !== (y === 0) && Math.abs(x + y) <= 2;
738
+ };
739
+ const shadowPaints = (boxShadow) => isSet(boxShadow, "none") && !boxShadow.split(/,(?![^(]*\))/).every(isRule);
740
+ const isPainted = (style) => isSet(style.backgroundColor, "transparent") && !TRANSPARENT.test(style.backgroundColor) || isSet(style.backgroundImage, "none") || shadowPaints(style.boxShadow) || bordered(style);
741
+ const isMedia = (element) => MEDIA.has(element.localName);
742
+ const isEmptyText = (element) => TEXT.has(element.localName) && !element.firstElementChild && !element.textContent?.trim();
743
+ /** Labels inside: one per piece of text, however many lines it wraps to or nodes it's split into. */
744
+ const countLabels = (element) => {
745
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
746
+ const range = document.createRange();
747
+ const pieces = [];
748
+ let nodes = 0;
749
+ while (walker.nextNode()) {
750
+ const node = walker.currentNode;
751
+ if (node.nodeType === Node.TEXT_NODE) {
752
+ const parent = node.parentElement;
753
+ if (!node.textContent?.trim() || !parent) continue;
754
+ nodes++;
755
+ const box = parent.getBoundingClientRect();
756
+ if (box.width <= 1 || box.height <= 1) continue;
757
+ range.selectNodeContents(node);
758
+ pieces.push(lineRects(range).filter((r) => r.width || r.height));
759
+ } else if (isEmptyText(node)) {
760
+ nodes++;
761
+ pieces.push([...node.getClientRects()]);
762
+ }
763
+ }
764
+ if (!pieces.some((rects) => rects.length)) return nodes;
765
+ const reach = (parseFloat(getComputedStyle(element).fontSize) || 16) * LINE_GAP;
766
+ const beside = (a, b) => Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top) >= Math.min(a.height, b.height) / 2 && Math.max(a.left, b.left) - Math.min(a.right, b.right) <= reach;
767
+ const label = pieces.map((_, i) => i);
768
+ const find = (i) => label[i] === i ? i : label[i] = find(label[i]);
769
+ for (let i = 0; i < pieces.length; i++) for (let j = i + 1; j < pieces.length; j++) if (pieces[i].some((a) => pieces[j].some((b) => beside(a, b)))) label[find(j)] = find(i);
770
+ return new Set(pieces.map((_, i) => find(i))).size;
771
+ };
772
+ const isBlock = (element, style) => element.hasAttribute("ask-block") || isMedia(element) || isPainted(style) && countLabels(element) <= 1;
773
+ function radiusOf(element, style) {
774
+ if (parseFloat(style.borderRadius) || !element.parentElement) return style.borderRadius || "0px";
775
+ const parent = element.parentElement;
776
+ const parentStyle = getComputedStyle(parent);
777
+ const a = element.getBoundingClientRect();
778
+ const b = parent.getBoundingClientRect();
779
+ return Math.abs(a.width - b.width) < 1 && Math.abs(a.height - b.height) < 1 && parentStyle.overflow !== "visible" ? parentStyle.borderRadius : style.borderRadius || "0px";
780
+ }
781
+ function toBars(lines) {
782
+ lines.sort((a, b) => a.left - b.left);
783
+ const merged = [];
784
+ for (const line of lines) {
785
+ const last = merged.find((m) => {
786
+ const overlap = Math.min(m.bottom, line.bottom) - Math.max(m.top, line.top);
787
+ const shorter = Math.min(m.bottom - m.top, line.bottom - line.top);
788
+ const gap = line.left - m.right;
789
+ return overlap >= shorter / 2 && gap <= Math.max(m.fontSize, line.fontSize) * LINE_GAP;
790
+ });
791
+ if (last) {
792
+ last.left = Math.min(last.left, line.left);
793
+ last.right = Math.max(last.right, line.right);
794
+ last.top = Math.min(last.top, line.top);
795
+ last.bottom = Math.max(last.bottom, line.bottom);
796
+ last.fontSize = Math.max(last.fontSize, line.fontSize);
797
+ last.barTop = Math.min(last.barTop, line.barTop);
798
+ last.barBottom = Math.max(last.barBottom, line.barBottom);
799
+ } else merged.push({ ...line });
800
+ }
801
+ return merged.map((line) => {
802
+ const height = line.barBottom - line.barTop;
803
+ return {
804
+ x: line.left,
805
+ y: line.barTop,
806
+ width: line.right - line.left,
807
+ height,
808
+ radius: line.radius || `${Math.round(height / 3)}px`
809
+ };
810
+ });
811
+ }
812
+ /** Measures what `root` holds into rects relative to `layer`; without `mark`, `root` is left untouched. */
813
+ function measureSkeleton(root, layer, { mark = true, onCovered } = {}) {
814
+ const origin = layer.getBoundingClientRect();
815
+ const scaleX = layer.offsetWidth ? origin.width / layer.offsetWidth : 1;
816
+ const scaleY = layer.offsetHeight ? origin.height / layer.offsetHeight : 1;
817
+ const local = (rect) => ({
818
+ left: (rect.left - origin.left) / scaleX,
819
+ right: (rect.right - origin.left) / scaleX,
820
+ top: (rect.top - origin.top) / scaleY,
821
+ bottom: (rect.bottom - origin.top) / scaleY
822
+ });
823
+ const blocks = [];
824
+ const lines = [];
825
+ const covered = /* @__PURE__ */ new Set();
826
+ const range = document.createRange();
827
+ const addLine = (rect, style, clip) => {
828
+ const box = clipped(rect, clip);
829
+ if (!box) return;
830
+ const fontSize = parseFloat(style.fontSize) || 16;
831
+ const line = local(box);
832
+ lines.push({
833
+ ...line,
834
+ fontSize,
835
+ ...barOf(line, style, fontSize),
836
+ radius: style.getPropertyValue("--ask-radius").trim()
837
+ });
838
+ };
839
+ const addText = (node, clip) => {
840
+ const start = node.data.search(/\S/);
841
+ if (start < 0 || !node.parentElement) return;
842
+ range.setStart(node, start);
843
+ range.setEnd(node, node.data.trimEnd().length);
844
+ const style = getComputedStyle(node.parentElement);
845
+ for (const rect of lineRects(range)) addLine(rect, style, clip);
846
+ };
847
+ const visit = (parent, clip) => {
848
+ for (const node of parent.childNodes) {
849
+ if (node.nodeType === Node.TEXT_NODE) {
850
+ addText(node, clip);
851
+ continue;
852
+ }
853
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
854
+ const element = node;
855
+ if (element.hasAttribute("ask-ignore") || element.hasAttribute("ask-own")) continue;
856
+ const style = getComputedStyle(element);
857
+ if (style.display === "none") continue;
858
+ if (isBlock(element, style)) {
859
+ const visible = clipped(element.getBoundingClientRect(), clip);
860
+ if (visible) {
861
+ const box = local(visible);
862
+ blocks.push({
863
+ x: box.left,
864
+ y: box.top,
865
+ width: box.right - box.left,
866
+ height: box.bottom - box.top,
867
+ radius: roundedBy(clip, visible, radiusOf(element, style), style)
868
+ });
869
+ }
870
+ if (!isMedia(element)) covered.add(element);
871
+ continue;
872
+ }
873
+ if (isEmptyText(element)) {
874
+ for (const rect of element.getClientRects()) addLine(rect, style, clip);
875
+ continue;
876
+ }
877
+ visit(element, style.overflowX !== "visible" || style.overflowY !== "visible" ? clipWithin(clip, element.getBoundingClientRect(), style) : clip);
878
+ }
879
+ };
880
+ visit(root, null);
881
+ onCovered?.(covered);
882
+ if (!mark) return [...blocks, ...toBars(lines)];
883
+ for (const element of root.querySelectorAll(`[${ATTR_COVERED}]`)) if (!covered.has(element)) element.removeAttribute(ATTR_COVERED);
884
+ for (const element of covered) if (!element.hasAttribute("ask-covered")) element.setAttribute(ATTR_COVERED, "");
885
+ return [...blocks, ...toBars(lines)];
886
+ }
887
+ //#endregion
888
+ //#region src/core/motion.ts
889
+ const prefers = (query) => typeof matchMedia !== "undefined" && matchMedia(query).matches;
890
+ const canAnimate = () => typeof Element !== "undefined" && typeof Element.prototype.animate === "function";
891
+ const prefersReducedMotion = () => !canAnimate() || prefers("(prefers-reduced-motion: reduce)");
892
+ const prefersReducedTransparency = () => prefers("(prefers-reduced-transparency: reduce)");
893
+ const animationsOf = (element) => element.getAnimations?.() ?? [];
894
+ //#endregion
895
+ //#region src/core/shimmer.ts
896
+ const SWEEP = .7;
897
+ const MAX_BAND = 320;
898
+ const SPACING = 2.5;
899
+ const PULSE_LOW = .45;
900
+ const ANIMATIONS = [
901
+ "shimmer",
902
+ "group-shimmer",
903
+ "pulse",
904
+ "none"
905
+ ];
906
+ /** The animation an element inherits from the theme: `--ask-animation` and `--ask-speed`. */
907
+ function motionOf(element) {
908
+ const style = getComputedStyle(element);
909
+ const read = (name, allowed, fallback) => {
910
+ const value = style.getPropertyValue(name).trim();
911
+ return allowed.includes(value) ? value : fallback;
912
+ };
913
+ const animation = read("--ask-animation", ANIMATIONS, "shimmer");
914
+ const speed = read("--ask-speed", ["slow", "fast"], "slow");
915
+ return {
916
+ animation,
917
+ duration: DURATIONS[animation === "pulse" ? "pulse" : "shimmer"][speed]
918
+ };
919
+ }
920
+ const SLANT = 20;
921
+ const SIN = Math.sin(SLANT * Math.PI / 180);
922
+ const COS = Math.cos(SLANT * Math.PI / 180);
923
+ const SLANT_TAN = SIN / COS;
924
+ const SLANT_COS = COS;
925
+ const SLANT_ANGLE = `110deg`;
926
+ const PEAK = .85;
927
+ const ease = (t) => (1 - Math.cos(Math.PI * t)) / 2;
928
+ /** Highlights `SPACING` band-widths apart, in px from the box's corner, so every rect draws the same stream. */
929
+ function highlights(band) {
930
+ const period = band * SPACING;
931
+ const stop = (at, alpha) => `color-mix(in srgb, ${HIGHLIGHT} ${(alpha * 100).toFixed(1)}%, transparent) ${(at * band).toFixed(2)}px`;
932
+ const stops = [];
933
+ for (let i = 0; i <= 12; i++) stops.push(stop(i / 12 * PEAK, ease(i / 12)));
934
+ for (let i = 1; i <= 12; i++) stops.push(stop(PEAK + i / 12 * (1 - PEAK), ease(1 - i / 12)));
935
+ return {
936
+ width: period / COS,
937
+ image: `repeating-linear-gradient(${SLANT_ANGLE}, ${stops.join(", ")}, transparent ${period.toFixed(2)}px)`
938
+ };
939
+ }
940
+ /** Animates `element` as its slice of one sweep, on the document clock, so however late it mounts it joins the sweep under way. */
941
+ function shimmer(element, group, motion = motionOf(element)) {
942
+ for (const animation of animationsOf(element)) animation.cancel();
943
+ element.style.backgroundImage = "";
944
+ element.style.backgroundRepeat = "";
945
+ if (prefersReducedMotion() || typeof element.animate !== "function") return;
946
+ if (motion.animation === "none") return;
947
+ const timing = {
948
+ duration: motion.duration,
949
+ iterations: Infinity
950
+ };
951
+ if (motion.animation === "pulse") {
952
+ element.animate([
953
+ {
954
+ opacity: 1,
955
+ easing: "ease-in-out"
956
+ },
957
+ {
958
+ opacity: PULSE_LOW,
959
+ easing: "ease-in-out"
960
+ },
961
+ { opacity: 1 }
962
+ ], timing).startTime = 0;
963
+ return;
964
+ }
965
+ const rect = element.getBoundingClientRect();
966
+ const x = rect.left + scrollX;
967
+ const area = motion.animation === "group-shimmer" ? group ?? {
968
+ left: x,
969
+ top: rect.top,
970
+ width: rect.width,
971
+ height: rect.height
972
+ } : {
973
+ left: 0,
974
+ top: 0,
975
+ width: document.documentElement.clientWidth || innerWidth,
976
+ height: innerHeight
977
+ };
978
+ const { width: tile, image } = highlights(Math.min(Math.max(area.width * .4, 80), MAX_BAND));
979
+ const along = x - area.left + (rect.top - area.top) * SLANT_TAN;
980
+ const pace = motion.duration * SWEEP / 1600;
981
+ element.style.backgroundImage = image;
982
+ element.style.backgroundSize = `${tile}px 100%`;
983
+ element.style.backgroundRepeat = "repeat-x";
984
+ element.animate([{ backgroundPosition: `${-along}px 0` }, { backgroundPosition: `${tile - along}px 0` }], {
985
+ duration: tile * pace,
986
+ iterations: Infinity
987
+ }).startTime = 0;
988
+ }
989
+ /** ms per px of the band in `element`'s shimmer; null when it has no band. */
990
+ function paceOf(element) {
991
+ if (prefersReducedMotion()) return null;
992
+ const motion = motionOf(element);
993
+ if (motion.animation !== "shimmer" && motion.animation !== "group-shimmer") return null;
994
+ return motion.duration * SWEEP / 1600;
995
+ }
996
+ const shadow = (layer) => layer.shadowRoot ?? layer.attachShadow({ mode: "open" });
997
+ /** The rect elements drawn into `layer`. */
998
+ const rectsOf = (layer) => layer.shadowRoot ? [...layer.shadowRoot.children] : [];
999
+ /** Draws `rects` into `layer`, reusing the elements already there. */
1000
+ function drawSkeleton(layer, rects) {
1001
+ const root = shadow(layer);
1002
+ const rect = layer.getBoundingClientRect();
1003
+ const group = {
1004
+ left: rect.left + scrollX,
1005
+ top: rect.top,
1006
+ width: rect.width,
1007
+ height: rect.height
1008
+ };
1009
+ const motion = motionOf(layer);
1010
+ rects.forEach((part, index) => {
1011
+ let element = root.children[index];
1012
+ if (!element) {
1013
+ element = document.createElement("div");
1014
+ element.setAttribute(ATTR_RECT, "");
1015
+ element.style.cssText = `position:absolute;display:block;pointer-events:none;background-color:${BASE};background-repeat:no-repeat`;
1016
+ root.append(element);
1017
+ }
1018
+ Object.assign(element.style, {
1019
+ left: `${part.x}px`,
1020
+ top: `${part.y}px`,
1021
+ width: `${part.width}px`,
1022
+ height: `${part.height}px`,
1023
+ borderRadius: part.radius
1024
+ });
1025
+ const key = `${Math.round(group.left + part.x)}|${Math.round(part.y)}|${Math.round(part.height)}|${Math.round(group.left)}|${Math.round(group.width)}|${Math.round(group.height)}|${document.documentElement.clientWidth}|${motion.animation}|${motion.duration}`;
1026
+ if (element.dataset.motion !== key) {
1027
+ element.dataset.motion = key;
1028
+ shimmer(element, group, motion);
1029
+ }
1030
+ });
1031
+ while (root.children.length > rects.length) root.lastElementChild.remove();
1032
+ }
1033
+ //#endregion
1034
+ //#region src/core/plan.ts
1035
+ const SKELETON_DELAY = 150;
1036
+ const QUICK_FADE = 120;
1037
+ const SMALL_HEIGHT = 72;
1038
+ const SMALL_PIECES = 3;
1039
+ const SECONDARY_DELAY = 60;
1040
+ const ACTIVE_WINDOW = 400;
1041
+ const MAX_DELAY = 1e3;
1042
+ const MAX_DURATION = 1e3;
1043
+ const ENTER = new Set([
1044
+ "filter",
1045
+ "transform",
1046
+ "translate",
1047
+ "scale",
1048
+ "rotate",
1049
+ "clipPath",
1050
+ "easing",
1051
+ "composite"
1052
+ ]);
1053
+ let lastInput = -Infinity;
1054
+ let listening = false;
1055
+ function listen() {
1056
+ if (listening || typeof addEventListener === "undefined") return;
1057
+ listening = true;
1058
+ for (const type of [
1059
+ "wheel",
1060
+ "touchmove",
1061
+ "keydown"
1062
+ ]) addEventListener(type, () => lastInput = performance.now(), {
1063
+ passive: true,
1064
+ capture: true
1065
+ });
1066
+ }
1067
+ const loading = /* @__PURE__ */ new WeakMap();
1068
+ /** Notes a skeleton going up in `reveal` — when, and whether it's a refetch over the data on screen. */
1069
+ function noteSkeleton(reveal, { served, refetch }) {
1070
+ listen();
1071
+ const since = loading.get(reveal)?.since ?? performance.now();
1072
+ loading.set(reveal, {
1073
+ since: served ? -Infinity : since,
1074
+ refetch
1075
+ });
1076
+ }
1077
+ function pieces(root) {
1078
+ const found = [];
1079
+ const visit = (element) => {
1080
+ for (const child of element.children) {
1081
+ const ownText = [...child.childNodes].some((node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim());
1082
+ if (!child.firstElementChild || ownText || child.localName === "svg") found.push(child);
1083
+ else visit(child);
1084
+ }
1085
+ };
1086
+ visit(root);
1087
+ return found;
1088
+ }
1089
+ function unitOf(piece, root) {
1090
+ for (let element = piece; element && element !== root; element = element.parentElement) {
1091
+ const parent = element.parentElement;
1092
+ if (!parent) break;
1093
+ if ([...parent.children].filter((sibling) => sibling.localName === element.localName && sibling.className === element.className).length >= 3) return element;
1094
+ }
1095
+ return piece;
1096
+ }
1097
+ const contains = (outer, inner) => {
1098
+ const x = inner.left + inner.width / 2;
1099
+ const y = inner.top + inner.height / 2;
1100
+ return x >= outer.left && x <= outer.right && y >= outer.top && y <= outer.bottom;
1101
+ };
1102
+ /** The box a wipe crosses: the reveal, and any pane leaving it that stands taller or wider. */
1103
+ function revealBox(root) {
1104
+ let { left, top, right, bottom } = root.getBoundingClientRect();
1105
+ for (const pane of root.querySelectorAll(`:scope > [${ATTR_EXITING}]`)) {
1106
+ const rect = pane.getBoundingClientRect();
1107
+ left = Math.min(left, rect.left);
1108
+ top = Math.min(top, rect.top);
1109
+ right = Math.max(right, rect.right);
1110
+ bottom = Math.max(bottom, rect.bottom);
1111
+ }
1112
+ return new DOMRect(left, top, right - left, bottom - top);
1113
+ }
1114
+ const clamp = (value, max) => Math.min(Math.max(value, 0), max);
1115
+ let batch = -Infinity;
1116
+ /** Times each piece of `content`, and each skeleton rect it replaces, by `order`. */
1117
+ function sequencePlan(content, root, order, spec, duration) {
1118
+ const now = performance.now();
1119
+ if (now - batch > 150) batch = now;
1120
+ const late = now - batch;
1121
+ const reveal = root.getBoundingClientRect();
1122
+ const units = /* @__PURE__ */ new Map();
1123
+ const found = /* @__PURE__ */ new Map();
1124
+ const sizeOf = (element) => parseFloat(getComputedStyle(element).fontSize) || 16;
1125
+ for (const piece of pieces(content)) {
1126
+ const element = unitOf(piece, content);
1127
+ let unit = units.get(element);
1128
+ if (!unit) {
1129
+ unit = {
1130
+ rect: element.getBoundingClientRect(),
1131
+ size: 0
1132
+ };
1133
+ units.set(element, unit);
1134
+ }
1135
+ const size = sizeOf(piece);
1136
+ unit.size = Math.max(unit.size, size);
1137
+ found.set(piece, {
1138
+ unit,
1139
+ size
1140
+ });
1141
+ }
1142
+ const at = (piece) => {
1143
+ const delay = order(piece);
1144
+ return delay === null ? null : clamp(delay, MAX_DELAY) + Math.max(spec.delay ?? 0, 0);
1145
+ };
1146
+ return {
1147
+ kind: "sequence",
1148
+ duration,
1149
+ enter: spec.enter,
1150
+ pieceDelay: (element) => {
1151
+ const piece = found.get(element);
1152
+ if (!piece) return null;
1153
+ return at({
1154
+ rect: element.getBoundingClientRect(),
1155
+ unit: piece.unit.rect,
1156
+ secondary: piece.size < piece.unit.size,
1157
+ reveal,
1158
+ late
1159
+ });
1160
+ },
1161
+ boneDelay: (rect) => {
1162
+ const unit = [...units.values()].find((unit) => contains(unit.rect, rect));
1163
+ return at({
1164
+ rect,
1165
+ unit: unit?.rect ?? null,
1166
+ secondary: !!unit && rect.height < unit.size * .8,
1167
+ reveal,
1168
+ late
1169
+ });
1170
+ }
1171
+ };
1172
+ }
1173
+ /** Keeps `enter` to what paints, warning in development about anything that would move the page. */
1174
+ function enterOf(enter, warn) {
1175
+ if (!enter) return void 0;
1176
+ const kept = {};
1177
+ for (const [key, value] of Object.entries(enter)) if (ENTER.has(key)) kept[key] = value;
1178
+ else if (key !== "opacity") warn(key);
1179
+ return kept;
1180
+ }
1181
+ /** Decides how `content` replaces the skeleton in `root`, now that it has arrived. */
1182
+ function planReveal(content, root, reveal, warn = () => {}) {
1183
+ const info = loading.get(root);
1184
+ loading.delete(root);
1185
+ let spec = reveal;
1186
+ while (spec.choose) {
1187
+ const { choose, ...rest } = spec;
1188
+ spec = {
1189
+ ...rest,
1190
+ ...choose({
1191
+ content,
1192
+ reveal: root,
1193
+ waited: info ? performance.now() - info.since : Infinity,
1194
+ input: performance.now() - lastInput < ACTIVE_WINDOW,
1195
+ refetch: !!info?.refetch,
1196
+ height: content.getBoundingClientRect().height,
1197
+ pieces: pieces(content).length
1198
+ })
1199
+ };
1200
+ }
1201
+ const duration = clamp(spec.duration ?? 200, MAX_DURATION);
1202
+ spec = {
1203
+ ...spec,
1204
+ delay: Math.min(spec.delay ?? 0, MAX_DELAY)
1205
+ };
1206
+ if (!duration) return { kind: "instant" };
1207
+ const enter = enterOf(spec.enter, warn);
1208
+ if (spec.wipe) {
1209
+ const rect = revealBox(root);
1210
+ if (rect.width * rect.height <= innerWidth * innerHeight * 1.5) return {
1211
+ kind: "wipe",
1212
+ wipe: spec.wipe,
1213
+ start: performance.now() + (spec.delay ?? 0),
1214
+ duration,
1215
+ reveal: rect,
1216
+ enter
1217
+ };
1218
+ } else if (typeof spec.order === "function") return sequencePlan(content, root, spec.order, {
1219
+ ...spec,
1220
+ enter
1221
+ }, duration);
1222
+ return {
1223
+ kind: "together",
1224
+ delay: Math.max(spec.delay ?? 0, 0),
1225
+ duration,
1226
+ enter
1227
+ };
1228
+ }
1229
+ /** `sequence`: each piece timed by its place in the reveal, top to bottom, then a little left to right. */
1230
+ const byPosition = ({ rect, reveal }) => {
1231
+ const down = (rect.top - reveal.top) / Math.max(reveal.height, 1);
1232
+ const across = (rect.left - reveal.left) / Math.max(reveal.width, 1);
1233
+ return clamp(down * .8 + across * .2, 1) * 320;
1234
+ };
1235
+ const onScreen = (rect) => rect.bottom > 0 && rect.top < innerHeight;
1236
+ /** `smart`'s order: rows arrive whole, in page order, on screen only, main text first. */
1237
+ const byRow = ({ rect, unit, secondary, late }) => {
1238
+ if (!unit) return onScreen(rect) ? 0 : null;
1239
+ if (!onScreen(unit)) return null;
1240
+ const at = clamp(unit.top / innerHeight, 1) * 320;
1241
+ return Math.max(at - late, 0) + (secondary ? SECONDARY_DELAY : 0);
1242
+ };
1243
+ const instant = { duration: 0 };
1244
+ const together = { order: "together" };
1245
+ const sequence = { order: byPosition };
1246
+ /** The cases `smart` settles before looking at the content: instant, or a quick crossfade. */
1247
+ const quickly = (context) => context.input || context.waited < 150 ? instant : context.refetch ? {
1248
+ order: "together",
1249
+ duration: QUICK_FADE
1250
+ } : null;
1251
+ /** `together` for a small reveal, else `byRow`. */
1252
+ const byContent = (context) => context.height < SMALL_HEIGHT || context.pieces <= SMALL_PIECES ? together : { order: byRow };
1253
+ /** Picks for each reveal from how it loaded and what it holds, holding its skeleton back a moment. */
1254
+ const smart = {
1255
+ hold: 150,
1256
+ choose: (context) => quickly(context) ?? byContent(context)
1257
+ };
1258
+ //#endregion
1259
+ //#region src/core/shapes.ts
1260
+ const STORAGE_KEY = "a-smart-skeleton:shapes";
1261
+ const VERSION = 2;
1262
+ const MAX_SHAPES = 50;
1263
+ const MAX_LENGTH = 2e4;
1264
+ const session = /* @__PURE__ */ new Map();
1265
+ const shown = /* @__PURE__ */ new Map();
1266
+ /** Registers a save for content on screen under `key`; returns its unregister. */
1267
+ function trackShape(key, save) {
1268
+ if (!shown.has(key)) shown.set(key, /* @__PURE__ */ new Set());
1269
+ shown.get(key).add(save);
1270
+ return () => shown.get(key)?.delete(save);
1271
+ }
1272
+ /** Saves every shown copy of `key` now, so a skeleton about to recall it gets the latest. */
1273
+ function flushShapes(key) {
1274
+ for (const save of shown.get(key) ?? []) save();
1275
+ }
1276
+ function read() {
1277
+ try {
1278
+ const raw = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "null");
1279
+ if (!raw || typeof raw !== "object" || raw.v !== VERSION) return Object.create(null);
1280
+ const incoming = raw.shapes;
1281
+ if (!incoming || typeof incoming !== "object") return Object.create(null);
1282
+ const out = Object.create(null);
1283
+ for (const key of Object.keys(incoming)) {
1284
+ const value = incoming[key];
1285
+ if (typeof value === "string") out[key] = value;
1286
+ }
1287
+ return out;
1288
+ } catch {
1289
+ return Object.create(null);
1290
+ }
1291
+ }
1292
+ function write(shapes) {
1293
+ try {
1294
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
1295
+ v: VERSION,
1296
+ shapes
1297
+ }));
1298
+ } catch {}
1299
+ }
1300
+ function rememberShape(key, element) {
1301
+ session.set(key, serialize(element, false));
1302
+ const html = serialize(element, true);
1303
+ if (html.length > MAX_LENGTH) return;
1304
+ const shapes = read();
1305
+ delete shapes[key];
1306
+ shapes[key] = html;
1307
+ const keys = Object.keys(shapes);
1308
+ for (const stale of keys.slice(0, keys.length - MAX_SHAPES)) delete shapes[stale];
1309
+ write(shapes);
1310
+ }
1311
+ function recallShape(key) {
1312
+ return session.get(key) ?? read()[key] ?? null;
1313
+ }
1314
+ /** Forgets remembered shapes — call it on sign-out. */
1315
+ function forgetShapes({ persisted = true } = {}) {
1316
+ session.clear();
1317
+ if (!persisted) return;
1318
+ try {
1319
+ localStorage.removeItem(STORAGE_KEY);
1320
+ } catch {}
1321
+ }
1322
+ //#endregion
1323
+ //#region src/core/ghost.ts
1324
+ const FALLBACK = [
1325
+ 100,
1326
+ 90,
1327
+ 60
1328
+ ].map((width) => `<div ${ATTR_BLOCK} style="width:${width}%;height:0.75em;margin:0.5em 0;border-radius:0.25em"></div>`).join("");
1329
+ const served = new WeakSet(typeof document === "undefined" ? [] : document.querySelectorAll(`[${ATTR_LOADING}]`));
1330
+ const loadingSizes = /* @__PURE__ */ new WeakMap();
1331
+ /** The size a reveal's skeleton last measured at, taken once by whatever replaces it. */
1332
+ function takeLoadingSize(reveal) {
1333
+ if (!reveal) return void 0;
1334
+ const size = loadingSizes.get(reveal);
1335
+ loadingSizes.delete(reveal);
1336
+ return size;
1337
+ }
1338
+ function child(root, attribute) {
1339
+ for (const element of root.children) if (element.hasAttribute(attribute)) return element;
1340
+ const element = document.createElement("div");
1341
+ element.setAttribute(attribute, "");
1342
+ root.append(element);
1343
+ return element;
1344
+ }
1345
+ const hasContent = (element) => !!element.firstElementChild || !!element.textContent?.trim();
1346
+ /** Draws a skeleton over content laid out unseen in `root`, so it takes exactly the content's space. */
1347
+ var Ghost = class {
1348
+ root;
1349
+ options;
1350
+ ghost;
1351
+ snapshot;
1352
+ layer;
1353
+ source = null;
1354
+ hadContent = false;
1355
+ frame = 0;
1356
+ mutations;
1357
+ resize;
1358
+ constructor(root, options = {}) {
1359
+ this.root = root;
1360
+ this.options = options;
1361
+ addStyles();
1362
+ root[GHOSTED] = true;
1363
+ root.setAttribute(ATTR_LOADING, "");
1364
+ root.setAttribute("aria-hidden", "true");
1365
+ root.inert = true;
1366
+ this.ghost = child(root, ATTR_GHOST);
1367
+ this.snapshot = child(root, ATTR_SNAPSHOT);
1368
+ this.layer = child(root, ATTR_LAYER);
1369
+ this.hadContent = hasContent(this.ghost);
1370
+ this.note();
1371
+ this.choose();
1372
+ this.measure();
1373
+ root[UNCOVER]?.();
1374
+ this.mutations = new MutationObserver(this.schedule);
1375
+ this.mutations.observe(this.ghost, {
1376
+ childList: true,
1377
+ subtree: true,
1378
+ characterData: true,
1379
+ attributes: true
1380
+ });
1381
+ this.resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(this.schedule);
1382
+ this.resize?.observe(root);
1383
+ root.addEventListener("load", this.schedule, true);
1384
+ addEventListener("resize", this.schedule);
1385
+ document.fonts?.ready.then(this.schedule);
1386
+ }
1387
+ /** Swaps in fresh options without tearing down the observers or stylesheet refcount. */
1388
+ update(options = {}) {
1389
+ this.options = options;
1390
+ this.note();
1391
+ this.choose();
1392
+ this.measure();
1393
+ }
1394
+ note() {
1395
+ const reveal = this.root.closest(`[${ATTR_REVEAL}]`);
1396
+ if (!reveal) return;
1397
+ noteSkeleton(reveal, {
1398
+ served: served.has(this.root),
1399
+ refetch: !!this.options.refetch && this.hadContent
1400
+ });
1401
+ }
1402
+ schedule = () => {
1403
+ if (this.root.closest(`[ask-exiting]`)) return;
1404
+ cancelAnimationFrame(this.frame);
1405
+ this.frame = requestAnimationFrame(() => {
1406
+ const has = hasContent(this.ghost);
1407
+ if (has !== this.hadContent) {
1408
+ this.hadContent = has;
1409
+ this.choose();
1410
+ }
1411
+ this.measure();
1412
+ });
1413
+ };
1414
+ choose() {
1415
+ let next = "fallback";
1416
+ let html = null;
1417
+ const settled = this.options.exact || served.has(this.root);
1418
+ const own = !!this.ghost.querySelector(`[${ATTR_OWN}]`);
1419
+ if ((settled || own) && this.hadContent) next = "ghost";
1420
+ else {
1421
+ const key = this.options.id ?? autoKey(this.ghost);
1422
+ if (key) {
1423
+ flushShapes(key);
1424
+ html = recallShape(key);
1425
+ }
1426
+ if (html) next = "recall";
1427
+ else if (this.hadContent) next = "ghost";
1428
+ }
1429
+ if (next !== this.source) if (next === "recall" && html) this.snapshot.replaceChildren(restore$1(html));
1430
+ else if (next === "fallback") {
1431
+ this.snapshot.innerHTML = FALLBACK;
1432
+ warnOnce(`fallback:${this.options.id}`, `Nothing to draw a skeleton from${this.options.id ? ` for "${this.options.id}"` : ""}. Pass \`sample\` data, or an \`id\` so its shape is remembered after the first load. For a lone placeholder box, use \`SkeletonBox\`.`);
1433
+ } else this.snapshot.replaceChildren();
1434
+ this.ghost.toggleAttribute(ATTR_INACTIVE, next !== "ghost");
1435
+ this.snapshot.toggleAttribute(ATTR_INACTIVE, next === "ghost");
1436
+ this.source = next;
1437
+ reportLoading(this.root.closest(`[${ATTR_REVEAL}]`), next === "recall" ? "remembered shape" : next === "fallback" ? "fallback" : this.options.exact ? "data" : served.has(this.root) ? "server render" : "sample");
1438
+ }
1439
+ measure() {
1440
+ const active = this.source === "ghost" ? this.ghost : this.snapshot;
1441
+ drawSkeleton(this.layer, measureSkeleton(active, this.layer));
1442
+ const reveal = this.root.closest(`[${ATTR_REVEAL}]`);
1443
+ if (reveal) loadingSizes.set(reveal, {
1444
+ width: this.root.offsetWidth,
1445
+ height: this.root.offsetHeight,
1446
+ guessed: !(this.options.exact && this.source === "ghost"),
1447
+ images: [...active.querySelectorAll("img")].map((image) => image.getBoundingClientRect().height)
1448
+ });
1449
+ }
1450
+ destroy() {
1451
+ cancelAnimationFrame(this.frame);
1452
+ this.mutations.disconnect();
1453
+ this.resize?.disconnect();
1454
+ this.root.removeEventListener("load", this.schedule, true);
1455
+ removeEventListener("resize", this.schedule);
1456
+ removeStyles();
1457
+ }
1458
+ };
1459
+ //#endregion
1460
+ //#region src/core/memory.ts
1461
+ const SAVE_DELAY = 250;
1462
+ const anonIds = /* @__PURE__ */ new WeakMap();
1463
+ let anonSeq = 0;
1464
+ const anonId = (element) => {
1465
+ let id = anonIds.get(element);
1466
+ if (!id) {
1467
+ id = `anon:${++anonSeq}`;
1468
+ anonIds.set(element, id);
1469
+ }
1470
+ return id;
1471
+ };
1472
+ /** Remembers the shape of shown content, so its next skeleton can be drawn from it. */
1473
+ var Memory = class {
1474
+ element;
1475
+ options;
1476
+ key;
1477
+ warnKey;
1478
+ timer;
1479
+ mutations;
1480
+ resize;
1481
+ untrack;
1482
+ constructor(element, options = {}) {
1483
+ this.element = element;
1484
+ this.options = options;
1485
+ this.key = options.id ?? autoKey(element);
1486
+ this.warnKey = this.key ?? anonId(element);
1487
+ this.untrack = this.key ? trackShape(this.key, () => this.save()) : () => {};
1488
+ if (isDev || debugging(element)) this.check();
1489
+ this.save();
1490
+ this.mutations = new MutationObserver(this.schedule);
1491
+ this.mutations.observe(element, {
1492
+ childList: true,
1493
+ subtree: true,
1494
+ characterData: true
1495
+ });
1496
+ this.resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(this.schedule);
1497
+ this.resize?.observe(element);
1498
+ element.addEventListener("load", this.schedule, true);
1499
+ }
1500
+ schedule = () => {
1501
+ clearTimeout(this.timer);
1502
+ this.timer = setTimeout(() => this.save(), SAVE_DELAY);
1503
+ };
1504
+ save() {
1505
+ clearTimeout(this.timer);
1506
+ if (this.key && this.element.isConnected && this.element.offsetHeight) rememberShape(this.key, this.element);
1507
+ }
1508
+ check() {
1509
+ const { data, id, rendered, sample } = this.options;
1510
+ const name = id ? `<Skeleton id="${id}">` : "<Skeleton>";
1511
+ const reveal = this.element.closest(`[${ATTR_REVEAL}]`);
1512
+ const before = takeLoadingSize(reveal);
1513
+ if (!before) return;
1514
+ if (before.guessed && rendered && !sample && data != null) warnOnce(`sample:${this.warnKey}`, `${name} has no \`sample\`, so a first load guesses its shape${id ? "" : " and it can't be remembered"}. Pass this to make it exact:\n sample={${JSON.stringify(sampleFrom(data))}}`);
1515
+ const images = [...this.element.querySelectorAll("img")].filter((image) => !image.complete);
1516
+ Promise.all(images.map((image) => new Promise((resolve) => {
1517
+ image.addEventListener("load", resolve, { once: true });
1518
+ image.addEventListener("error", resolve, { once: true });
1519
+ }))).then(() => {
1520
+ if (!this.element.isConnected) return;
1521
+ const width = this.element.offsetWidth;
1522
+ const height = this.element.offsetHeight;
1523
+ reportReveal(reveal, {
1524
+ width: width - before.width,
1525
+ height: height - before.height
1526
+ });
1527
+ if (Math.abs(width - before.width) <= 1 && Math.abs(height - before.height) <= 1) return;
1528
+ const unsized = [...this.element.querySelectorAll("img")].filter((image, i) => (before.images[i] ?? 0) < 1 && image.getBoundingClientRect().height >= 1).map((image) => image.currentSrc.split("/").pop() || "an image");
1529
+ if (unsized.length) {
1530
+ warnOnce(`unsized:${this.warnKey}`, `${name}'s skeleton had no room for ${unsized.length === 1 ? "an image" : `${unsized.length} images`} (${unsized.slice(0, 3).join(", ")}${unsized.length > 3 ? ", …" : ""}), so the layout jumped when they loaded. Give each \`width\` and \`height\` attributes, or an \`aspect-ratio\`, so its space is known before it loads.`);
1531
+ return;
1532
+ }
1533
+ warnOnce(`jump:${this.warnKey}`, before.guessed ? `${name}'s skeleton was ${before.width}×${before.height}px but its content is ${width}×${height}px, so the layout jumped on reveal. ${rendered ? "Pass `sample` data shaped like the real thing" : "Pass an `id` so its shape is remembered"}, and give images a size.` : `${name}'s skeleton was drawn from the data on screen (${before.width}×${before.height}px), but the new data is ${width}×${height}px, so the layout jumped on reveal. When new data adds to what's shown — another page of a list — give the addition a reveal of its own.`);
1534
+ });
1535
+ }
1536
+ destroy() {
1537
+ this.save();
1538
+ clearTimeout(this.timer);
1539
+ this.mutations.disconnect();
1540
+ this.resize?.disconnect();
1541
+ this.element.removeEventListener("load", this.schedule, true);
1542
+ this.untrack();
1543
+ }
1544
+ };
1545
+ //#endregion
1546
+ //#region src/core/skeleton.ts
1547
+ const DEFAULT_SKELETON_BOX_OPTIONS = {
1548
+ height: "1em",
1549
+ width: "60%",
1550
+ radius: "var(--ask-radius, 0.25em)"
1551
+ };
1552
+ const toCSS = (value) => typeof value === "number" ? `${value}px` : value;
1553
+ function skeletonBoxStyle({ height = DEFAULT_SKELETON_BOX_OPTIONS.height, width = DEFAULT_SKELETON_BOX_OPTIONS.width, radius = DEFAULT_SKELETON_BOX_OPTIONS.radius } = {}) {
1554
+ return {
1555
+ height: toCSS(height),
1556
+ width: toCSS(width),
1557
+ borderRadius: toCSS(radius)
1558
+ };
1559
+ }
1560
+ /** A single bar, shimmering in step with every skeleton on the page once it is in the document. */
1561
+ function createSkeletonBox(options) {
1562
+ addStyles();
1563
+ const element = document.createElement("span");
1564
+ element.setAttribute(ATTR_BAR, "");
1565
+ Object.assign(element.style, skeletonBoxStyle(options));
1566
+ requestAnimationFrame(() => {
1567
+ if (element.isConnected) shimmer(element);
1568
+ });
1569
+ return element;
1570
+ }
1571
+ //#endregion
1572
+ //#region src/core/images.ts
1573
+ const noop$1 = () => {};
1574
+ const deferred = (image) => {
1575
+ if (image.loading !== "lazy") return false;
1576
+ const rect = image.getBoundingClientRect();
1577
+ return rect.bottom < -innerHeight || rect.top > innerHeight * 2;
1578
+ };
1579
+ /** Images inside still on their way. */
1580
+ const pendingImages = (element) => [...element.querySelectorAll("img")].filter((image) => !image.complete && !deferred(image));
1581
+ /** Loaded and decoded, so its first frame on screen is the picture; failing counts, as nothing more is coming. */
1582
+ function imageReady(image) {
1583
+ const loaded = image.complete ? Promise.resolve() : new Promise((resolve) => {
1584
+ image.addEventListener("load", () => resolve(), { once: true });
1585
+ image.addEventListener("error", () => resolve(), { once: true });
1586
+ });
1587
+ return Promise.race([loaded.then(() => image.decode?.()).then(noop$1, noop$1), new Promise((resolve) => setTimeout(resolve, IMAGE_TIMEOUT))]);
1588
+ }
1589
+ //#endregion
1590
+ //#region src/core/sweep.ts
1591
+ const WAVE_SPREAD = 360;
1592
+ const WAVE_PACE = 2;
1593
+ const FEATHER = 40;
1594
+ const PAD = FEATHER / SLANT_COS;
1595
+ const GLOSS = 320;
1596
+ const GLOSS_LAG = 120;
1597
+ const GLOSS_LIFT = .45;
1598
+ const uOf = (rect) => rect.left + rect.top * SLANT_TAN;
1599
+ const spanOf = (rect) => rect.width + rect.height * SLANT_TAN;
1600
+ const frontAt = (progress, reveal) => uOf(reveal) - PAD + (spanOf(reveal) + 2 * PAD) * progress;
1601
+ let wave = {
1602
+ start: -Infinity,
1603
+ origin: 0,
1604
+ speed: 1
1605
+ };
1606
+ /** When the shared front reaches `reveal`, and how long it takes across — null without a shimmer to match. */
1607
+ function timing(reveal) {
1608
+ const layer = reveal.querySelector(`:scope > [${ATTR_EXITING}] [${ATTR_LAYER}]`);
1609
+ const pace = layer && paceOf(layer);
1610
+ if (!pace) return null;
1611
+ const rect = revealBox(reveal);
1612
+ if (rect.width * rect.height > innerWidth * innerHeight * 1.5) return null;
1613
+ const from = uOf(rect);
1614
+ const to = from + spanOf(rect);
1615
+ const now = performance.now();
1616
+ const elapsed = now - wave.start;
1617
+ if (!(elapsed <= 150 && (from - wave.origin) / wave.speed >= elapsed && (to - wave.origin) / wave.speed - elapsed <= 600)) wave = {
1618
+ start: now,
1619
+ origin: from,
1620
+ speed: Math.max(WAVE_PACE / pace, (to - from) / WAVE_SPREAD)
1621
+ };
1622
+ const { start, origin, speed } = wave;
1623
+ return {
1624
+ delay: (from - PAD - origin) / speed - (now - start),
1625
+ duration: (to - from + 2 * PAD) / speed
1626
+ };
1627
+ }
1628
+ const stops = (incoming) => (progress, { rect, reveal }) => {
1629
+ const at = (frontAt(progress, reveal) - uOf(rect)) * SLANT_COS;
1630
+ return `linear-gradient(${SLANT_ANGLE}, ${Array.from({ length: 13 }, (_, i) => {
1631
+ const shown = incoming ? 1 - ease(i / 12) : ease(i / 12);
1632
+ const offset = -40 + 2 * FEATHER * i / 12;
1633
+ return `rgb(0 0 0 / ${shown.toFixed(3)}) ${(at + offset).toFixed(2)}px`;
1634
+ }).join(", ")})`;
1635
+ };
1636
+ const SVG = "http://www.w3.org/2000/svg";
1637
+ let host = null;
1638
+ let glosses = 0;
1639
+ function filterHost() {
1640
+ if (!host?.isConnected) {
1641
+ host = document.createElementNS(SVG, "svg");
1642
+ host.setAttribute("aria-hidden", "true");
1643
+ host.style.cssText = "position:absolute;width:0;height:0;overflow:hidden";
1644
+ document.body.append(host);
1645
+ }
1646
+ return host;
1647
+ }
1648
+ const svg = (tag, attributes) => {
1649
+ const node = document.createElementNS(SVG, tag);
1650
+ for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, String(value));
1651
+ return node;
1652
+ };
1653
+ const gloss = (element, { rect, reveal, progress }) => {
1654
+ if (prefersReducedTransparency()) return;
1655
+ const from = uOf(rect);
1656
+ const span = spanOf(rect);
1657
+ const half = GLOSS / 2 / SLANT_COS;
1658
+ const centre = () => frontAt(progress(), reveal) - from - GLOSS_LAG / SLANT_COS;
1659
+ if (centre() - half > span) return;
1660
+ const width = 2 * half + rect.height * SLANT_TAN;
1661
+ const cx = width / 2;
1662
+ const cy = rect.height / 2;
1663
+ const dx = GLOSS / 2 * SLANT_COS;
1664
+ const dy = GLOSS / 2 * SLANT_TAN * SLANT_COS;
1665
+ const canvas = document.createElement("canvas");
1666
+ canvas.width = Math.ceil(width);
1667
+ canvas.height = Math.ceil(rect.height);
1668
+ const context = canvas.getContext("2d");
1669
+ if (!context) return;
1670
+ const gradient = context.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy);
1671
+ for (let i = 0; i <= 12; i++) {
1672
+ const t = i / 12;
1673
+ gradient.addColorStop(t, `rgb(255 255 255 / ${ease(1 - Math.abs(t * 2 - 1)).toFixed(3)})`);
1674
+ }
1675
+ context.fillStyle = gradient;
1676
+ context.fillRect(0, 0, canvas.width, canvas.height);
1677
+ const id = `ask-gloss-${++glosses}`;
1678
+ const filter = svg("filter", {
1679
+ id,
1680
+ "color-interpolation-filters": "sRGB",
1681
+ primitiveUnits: "objectBoundingBox"
1682
+ });
1683
+ const lit = svg("feComponentTransfer", {
1684
+ in: "SourceGraphic",
1685
+ result: "lit"
1686
+ });
1687
+ for (const channel of [
1688
+ "R",
1689
+ "G",
1690
+ "B"
1691
+ ]) lit.append(svg(`feFunc${channel}`, {
1692
+ type: "linear",
1693
+ slope: 1 - GLOSS_LIFT,
1694
+ intercept: GLOSS_LIFT
1695
+ }));
1696
+ const image = svg("feImage", {
1697
+ href: canvas.toDataURL(),
1698
+ y: 0,
1699
+ width: width / rect.width,
1700
+ height: 1,
1701
+ preserveAspectRatio: "none",
1702
+ result: "band"
1703
+ });
1704
+ filter.append(lit, image, svg("feComposite", {
1705
+ in: "lit",
1706
+ in2: "band",
1707
+ operator: "in",
1708
+ result: "shine"
1709
+ }), svg("feComposite", {
1710
+ in: "shine",
1711
+ in2: "SourceGraphic",
1712
+ operator: "over"
1713
+ }));
1714
+ filterHost().append(filter);
1715
+ element.style.filter = `url(#${id})`;
1716
+ const tick = () => {
1717
+ const at = centre();
1718
+ if (at - half > span || !element.isConnected) {
1719
+ if (element.style.filter.includes(id)) element.style.filter = "";
1720
+ filter.remove();
1721
+ return;
1722
+ }
1723
+ image.setAttribute("x", ((at - cx - cy * SLANT_TAN) / rect.width).toFixed(4));
1724
+ requestAnimationFrame(tick);
1725
+ };
1726
+ tick();
1727
+ };
1728
+ const wipe$1 = {
1729
+ mask: stops(true),
1730
+ out: stops(false),
1731
+ effect: gloss
1732
+ };
1733
+ /** `smart`, wiping content in behind one band of the shimmer. */
1734
+ const sweep = {
1735
+ hold: 150,
1736
+ choose: (context) => {
1737
+ const quick = quickly(context);
1738
+ if (quick) return quick;
1739
+ const swept = timing(context.reveal);
1740
+ return swept ? {
1741
+ wipe: wipe$1,
1742
+ ...swept
1743
+ } : byContent(context);
1744
+ }
1745
+ };
1746
+ //#endregion
1747
+ //#region src/core/reveals.ts
1748
+ const NAMED = {
1749
+ together,
1750
+ sequence,
1751
+ smart,
1752
+ sweep
1753
+ };
1754
+ const revealSpec = (mode = "smart") => typeof mode === "string" ? NAMED[mode] : mode;
1755
+ /** Longest a pane can take to leave under `mode`, for a framework that has to know up front. */
1756
+ function exitDuration(mode) {
1757
+ const spec = revealSpec(mode);
1758
+ const hold = spec.hold ?? 0;
1759
+ if (spec === together) return 200;
1760
+ if (spec === sequence) return 520;
1761
+ if (spec === smart) return hold + 320 + 200;
1762
+ if (spec === sweep) return hold + 600 + 200;
1763
+ if (spec.duration === 0) return 0;
1764
+ return hold + (spec.choose ? MAX_DELAY : Math.min(Math.max(spec.delay ?? 0, 0), MAX_DELAY)) + MAX_DELAY + MAX_DURATION;
1765
+ }
1766
+ //#endregion
1767
+ //#region src/core/fade.ts
1768
+ const noop = () => {};
1769
+ const settle = (animation) => animation.finished.then(noop, noop);
1770
+ const plans = /* @__PURE__ */ new WeakMap();
1771
+ function appear(element, delay = 0, duration = 200, from = 0, enter) {
1772
+ if (prefersReducedMotion()) return Promise.resolve();
1773
+ return settle(element.animate([{
1774
+ ...enter,
1775
+ opacity: from
1776
+ }, { opacity: 1 }], {
1777
+ duration,
1778
+ delay,
1779
+ easing: "ease",
1780
+ fill: "backwards"
1781
+ }));
1782
+ }
1783
+ const isSkeleton = (element) => !!element.querySelector(`[${ATTR_LAYER}], [${ATTR_GHOST}], :scope > [${ATTR_OWN}]`);
1784
+ const sameBox = (a, b) => Math.abs(a.left - b.left) < 2 && Math.abs(a.top - b.top) < 2 && Math.abs(a.width - b.width) < 2 && Math.abs(a.height - b.height) < 2;
1785
+ const standIns = /* @__PURE__ */ new WeakMap();
1786
+ function clearStandIns(root) {
1787
+ for (const stand of standIns.get(root) ?? []) stand.remove();
1788
+ standIns.delete(root);
1789
+ }
1790
+ function holdImages(element, root) {
1791
+ const images = pendingImages(element);
1792
+ const overSkeleton = [...root.children].some((pane) => pane !== element && pane.hasAttribute("ask-pane") && isSkeleton(pane));
1793
+ if (!images.length || !overSkeleton) return;
1794
+ for (const image of images) image.style.opacity = "0";
1795
+ queueMicrotask(() => {
1796
+ if (!element.isConnected) return;
1797
+ const origin = root.getBoundingClientRect();
1798
+ const bones = [...root.querySelectorAll(`:scope > [${ATTR_EXITING}] [${ATTR_LAYER}]`)].flatMap(rectsOf);
1799
+ const stands = standIns.get(root) ?? /* @__PURE__ */ new Set();
1800
+ standIns.set(root, stands);
1801
+ for (const image of images) {
1802
+ const rect = image.getBoundingClientRect();
1803
+ const bone = bones.find((b) => sameBox(b.getBoundingClientRect(), rect));
1804
+ const box = bone?.getBoundingClientRect() ?? rect;
1805
+ const stand = document.createElement("div");
1806
+ stand.setAttribute(ATTR_RECT, "");
1807
+ Object.assign(stand.style, {
1808
+ position: "absolute",
1809
+ left: `${box.left - origin.left - root.clientLeft}px`,
1810
+ top: `${box.top - origin.top - root.clientTop}px`,
1811
+ width: `${box.width}px`,
1812
+ height: `${box.height}px`,
1813
+ borderRadius: bone?.style.borderRadius || getComputedStyle(image).borderRadius,
1814
+ backgroundColor: BASE,
1815
+ backgroundRepeat: "no-repeat",
1816
+ pointerEvents: "none"
1817
+ });
1818
+ root.append(stand);
1819
+ stands.add(stand);
1820
+ if (bone) bone.style.visibility = "hidden";
1821
+ shimmer(stand);
1822
+ imageReady(image).then(() => {
1823
+ if (!stands.has(stand)) return;
1824
+ stands.delete(stand);
1825
+ image.style.opacity = "";
1826
+ appear(image);
1827
+ if (prefersReducedMotion()) return stand.remove();
1828
+ settle(stand.animate([{ opacity: 1 }, { opacity: 0 }], {
1829
+ duration: 200,
1830
+ easing: "ease",
1831
+ fill: "forwards"
1832
+ })).then(() => stand.remove());
1833
+ });
1834
+ }
1835
+ });
1836
+ }
1837
+ function sequenceIn(element, plan) {
1838
+ if (prefersReducedMotion()) return Promise.resolve();
1839
+ return Promise.all(pieces(element).map((piece) => {
1840
+ const delay = plan.pieceDelay(piece);
1841
+ if (delay === null) return;
1842
+ return appear(piece, delay, plan.duration, 0, plan.enter);
1843
+ })).then(noop);
1844
+ }
1845
+ function clearExiting(root) {
1846
+ for (const pane of root.querySelectorAll(`:scope > [${ATTR_EXITING}]`)) {
1847
+ for (const animation of animationsOf(pane)) animation.finish();
1848
+ pane.style.opacity = "0";
1849
+ }
1850
+ }
1851
+ function takeOver(root) {
1852
+ let from = 0;
1853
+ for (const pane of root.querySelectorAll(`:scope > [${ATTR_EXITING}]`)) {
1854
+ if (isSkeleton(pane)) continue;
1855
+ from = Math.max(from, parseFloat(getComputedStyle(pane).opacity) || 0);
1856
+ for (const animation of animationsOf(pane)) animation.finish();
1857
+ pane.style.opacity = "0";
1858
+ }
1859
+ return from;
1860
+ }
1861
+ /** Brings a pane in: a skeleton after the reveal's `hold`, content as `reveal` plans; `initial` has nothing to replace. */
1862
+ function fadeIn(element, reveal = "smart", { initial = false } = {}) {
1863
+ let arriving = arrivals.get(element);
1864
+ if (!arriving) {
1865
+ arriving = arrive(element, reveal, initial);
1866
+ arrivals.set(element, arriving);
1867
+ }
1868
+ return arriving;
1869
+ }
1870
+ const arrivals = /* @__PURE__ */ new WeakMap();
1871
+ function arrive(element, reveal, initial) {
1872
+ const root = element.parentElement;
1873
+ const spec = revealSpec(reveal);
1874
+ if (isSkeleton(element)) {
1875
+ if (root) clearStandIns(root);
1876
+ const hold = Math.max(spec.hold ?? 0, 0);
1877
+ if (!hold) {
1878
+ if (root) plans.delete(root);
1879
+ return initial ? Promise.resolve() : appear(element);
1880
+ }
1881
+ if (root) plans.set(root, {
1882
+ kind: "skeleton",
1883
+ hold
1884
+ });
1885
+ if (initial) return Promise.resolve();
1886
+ return appear(element, hold);
1887
+ }
1888
+ if (initial || !root) return Promise.resolve();
1889
+ const from = takeOver(root);
1890
+ if (from > .01) {
1891
+ const duration = 200 * (1 - from);
1892
+ plans.set(root, {
1893
+ kind: "together",
1894
+ delay: 0,
1895
+ duration
1896
+ });
1897
+ return appear(element, 0, duration, from);
1898
+ }
1899
+ holdImages(element, root);
1900
+ const plan = planReveal(element, root, spec, (key) => warnOnce(`enter:${key}`, `\`enter\` can't set \`${key}\`: only properties that change nothing's layout animate, so the reveal never moves the page.`));
1901
+ plans.set(root, plan);
1902
+ if (plan.kind === "instant") {
1903
+ clearExiting(root);
1904
+ return Promise.resolve();
1905
+ }
1906
+ if (plan.kind === "sequence") return sequenceIn(element, plan);
1907
+ if (plan.kind === "wipe") return wipe(element, plan, true);
1908
+ return appear(element, plan.delay, plan.duration, 0, plan.enter);
1909
+ }
1910
+ /** Measures `element` for `fadeOut`: before its replacement is inserted, or scroll anchoring moves the page. */
1911
+ function boxOf(element) {
1912
+ const rect = element.getBoundingClientRect();
1913
+ const parent = element.offsetParent ?? document.documentElement;
1914
+ const origin = parent.getBoundingClientRect();
1915
+ return {
1916
+ top: rect.top - origin.top - parent.clientTop,
1917
+ left: rect.left - origin.left - parent.clientLeft,
1918
+ width: rect.width,
1919
+ height: rect.height
1920
+ };
1921
+ }
1922
+ function popOut(element, box = boxOf(element)) {
1923
+ Object.assign(element.style, {
1924
+ position: "absolute",
1925
+ top: `${box.top}px`,
1926
+ left: `${box.left}px`,
1927
+ width: `${box.width}px`,
1928
+ height: `${box.height}px`,
1929
+ margin: "0",
1930
+ boxSizing: "border-box",
1931
+ pointerEvents: "none"
1932
+ });
1933
+ element.inert = true;
1934
+ element.setAttribute(ATTR_EXITING, "");
1935
+ return box;
1936
+ }
1937
+ const resizes = /* @__PURE__ */ new WeakMap();
1938
+ function resize(root, box) {
1939
+ const incoming = root.querySelector(`:scope > [${ATTR_PANE}]:not([${ATTR_EXITING}])`);
1940
+ if (!incoming) return;
1941
+ const running = resizes.get(root);
1942
+ const shown = root.getBoundingClientRect().height;
1943
+ running?.animation.cancel();
1944
+ const to = root.getBoundingClientRect().height;
1945
+ const from = running ? shown : to - incoming.getBoundingClientRect().height + box.height;
1946
+ if (Math.abs(from - to) < .5) {
1947
+ if (running) restore(root, running);
1948
+ return;
1949
+ }
1950
+ const { overflow, boxSizing } = running ?? root.style;
1951
+ Object.assign(root.style, {
1952
+ overflow: "clip",
1953
+ boxSizing: "border-box"
1954
+ });
1955
+ const animation = root.animate([{ height: `${from}px` }, { height: `${to}px` }], {
1956
+ duration: 280,
1957
+ easing: "cubic-bezier(0.2, 0, 0, 1)"
1958
+ });
1959
+ const current = {
1960
+ animation,
1961
+ overflow,
1962
+ boxSizing
1963
+ };
1964
+ resizes.set(root, current);
1965
+ settle(animation).then(() => {
1966
+ if (resizes.get(root) === current) restore(root, current);
1967
+ });
1968
+ }
1969
+ function restore(root, { overflow, boxSizing }) {
1970
+ resizes.delete(root);
1971
+ Object.assign(root.style, {
1972
+ overflow,
1973
+ boxSizing
1974
+ });
1975
+ }
1976
+ /** Resolves once `element` can be removed, leaving as the pane replacing it decides; its parent must be positioned. */
1977
+ function fadeOut(element, box) {
1978
+ const opacity = parseFloat(getComputedStyle(element).opacity);
1979
+ const from = Number.isNaN(opacity) ? 1 : opacity;
1980
+ for (const animation of animationsOf(element)) animation.cancel();
1981
+ clearMask(element);
1982
+ element.style.filter = "";
1983
+ const parent = element.parentElement;
1984
+ if (parent && !isSkeleton(element)) clearStandIns(parent);
1985
+ const stood = popOut(element, box);
1986
+ const fade = (delay, duration) => {
1987
+ if (!canAnimate()) {
1988
+ element.style.opacity = "0";
1989
+ return Promise.resolve();
1990
+ }
1991
+ return settle(element.animate([{ opacity: from }, { opacity: 0 }], {
1992
+ duration: duration * from,
1993
+ delay,
1994
+ easing: "ease",
1995
+ fill: "forwards"
1996
+ }));
1997
+ };
1998
+ return Promise.resolve().then(() => {
1999
+ const plan = parent && plans.get(parent);
2000
+ const instant = prefersReducedMotion() || plan?.kind === "instant";
2001
+ if (parent && !instant) resize(parent, stood);
2002
+ if (plan?.kind === "skeleton") return fade(plan.hold, 200);
2003
+ if (instant) {
2004
+ element.style.opacity = "0";
2005
+ return;
2006
+ }
2007
+ if (plan?.kind === "sequence" && isSkeleton(element)) return sequenceOut(element, plan);
2008
+ if (plan?.kind === "wipe" && isSkeleton(element)) return wipe(element, plan, false);
2009
+ if (plan?.kind === "together") return fade(plan.delay, plan.duration);
2010
+ return fade(0, 200);
2011
+ });
2012
+ }
2013
+ function sequenceOut(element, plan) {
2014
+ for (const ghost of element.querySelectorAll(`[${ATTR_GHOST}], [${ATTR_SNAPSHOT}]`)) ghost.style.visibility = "hidden";
2015
+ const own = [...element.querySelectorAll(`[${ATTR_GHOST}] [${ATTR_OWN}]`)].filter((part) => !part.parentElement?.closest(`[${ATTR_OWN}]`));
2016
+ for (const part of own) part.style.visibility = "visible";
2017
+ const bones = [...[...element.querySelectorAll(`[${ATTR_LAYER}]`)].flatMap(rectsOf), ...own];
2018
+ return Promise.all(bones.map((bone) => {
2019
+ const rect = bone.getBoundingClientRect();
2020
+ const go = (delay) => settle(bone.animate([{ opacity: 1 }, { opacity: 0 }], {
2021
+ duration: plan.duration,
2022
+ delay,
2023
+ easing: "ease",
2024
+ fill: "forwards"
2025
+ }));
2026
+ const delay = plan.boneDelay(rect);
2027
+ if (delay === null) {
2028
+ bone.style.opacity = "0";
2029
+ return;
2030
+ }
2031
+ return go(delay);
2032
+ })).then(noop);
2033
+ }
2034
+ const STALL = 100;
2035
+ const fronts = /* @__PURE__ */ new WeakMap();
2036
+ function setMask(element, image) {
2037
+ element.style.setProperty("mask-image", image);
2038
+ element.style.setProperty("-webkit-mask-image", image);
2039
+ }
2040
+ function setInverseMask(element, image) {
2041
+ setMask(element, `linear-gradient(#000 0 0), ${image}`);
2042
+ element.style.setProperty("-webkit-mask-composite", "xor");
2043
+ element.style.setProperty("mask-composite", "exclude");
2044
+ }
2045
+ function clearMask(element) {
2046
+ fronts.get(element)?.();
2047
+ for (const property of [
2048
+ "mask-image",
2049
+ "-webkit-mask-image",
2050
+ "mask-composite",
2051
+ "-webkit-mask-composite"
2052
+ ]) element.style.removeProperty(property);
2053
+ }
2054
+ function wipe(element, plan, incoming) {
2055
+ const pane = {
2056
+ rect: element.getBoundingClientRect(),
2057
+ reveal: plan.reveal
2058
+ };
2059
+ const { mask, out } = plan.wipe;
2060
+ const draw = (at) => {
2061
+ if (incoming) setMask(element, mask(at, pane));
2062
+ else if (out) setMask(element, out(at, pane));
2063
+ else setInverseMask(element, mask(at, pane));
2064
+ };
2065
+ const progress = () => (performance.now() - plan.start) / plan.duration;
2066
+ if (progress() >= 1) {
2067
+ if (!incoming) element.style.opacity = "0";
2068
+ return Promise.resolve();
2069
+ }
2070
+ if (incoming) {
2071
+ plan.wipe.effect?.(element, {
2072
+ ...pane,
2073
+ progress
2074
+ });
2075
+ if (plan.enter) element.animate([plan.enter, {}], {
2076
+ duration: plan.duration,
2077
+ delay: Math.max(plan.start - performance.now(), 0),
2078
+ easing: "ease",
2079
+ fill: "backwards"
2080
+ });
2081
+ }
2082
+ return new Promise((resolve) => {
2083
+ let frame = 0;
2084
+ let last = performance.now();
2085
+ let at = 0;
2086
+ const tick = () => {
2087
+ const now = performance.now();
2088
+ if (now - last > STALL) plan.stalled = true;
2089
+ last = now;
2090
+ if (plan.stalled) return done(at);
2091
+ at = Math.min(Math.max(progress(), 0), 1);
2092
+ draw(at);
2093
+ if (at < 1) frame = requestAnimationFrame(tick);
2094
+ else done(null);
2095
+ };
2096
+ const done = (stalledAt = null) => {
2097
+ cancelAnimationFrame(frame);
2098
+ clearTimeout(deadline);
2099
+ fronts.delete(element);
2100
+ resolve(stalledAt);
2101
+ };
2102
+ const deadline = setTimeout(() => done(null), Math.max(plan.start + plan.duration - performance.now(), 0) + STALL);
2103
+ fronts.set(element, done);
2104
+ tick();
2105
+ }).then((stalledAt) => {
2106
+ clearMask(element);
2107
+ if (stalledAt === null) {
2108
+ if (!incoming) element.style.opacity = "0";
2109
+ return;
2110
+ }
2111
+ const rest = 200 * (1 - stalledAt);
2112
+ if (incoming) return appear(element, 0, rest, stalledAt);
2113
+ return settle(element.animate([{ opacity: 1 - stalledAt }, { opacity: 0 }], {
2114
+ duration: rest,
2115
+ easing: "ease",
2116
+ fill: "forwards"
2117
+ })).then(() => {
2118
+ element.style.opacity = "0";
2119
+ });
2120
+ });
2121
+ }
2122
+ //#endregion
2123
+ //#region src/core/reveal.ts
2124
+ /** Shows a skeleton of content while it loads, then crossfades to the content itself. */
2125
+ var Reveal = class {
2126
+ element;
2127
+ options;
2128
+ current = null;
2129
+ ghost = null;
2130
+ memory = null;
2131
+ arrival = Promise.resolve();
2132
+ constructor(element, options = {}) {
2133
+ this.element = element;
2134
+ this.options = options;
2135
+ addStyles();
2136
+ element.setAttribute(ATTR_REVEAL, "");
2137
+ }
2138
+ /** Shows a skeleton measured from `content`, rendered from sample data, or else the shape last shown. */
2139
+ load(content) {
2140
+ const loading = document.createElement("div");
2141
+ const ghost = document.createElement("div");
2142
+ ghost.setAttribute(ATTR_GHOST, "");
2143
+ if (content) ghost.append(content);
2144
+ loading.append(ghost);
2145
+ const pane = this.swap(loading);
2146
+ if (this.options.label) {
2147
+ const status = document.createElement("span");
2148
+ status.setAttribute(ATTR_STATUS, "");
2149
+ status.textContent = this.options.label;
2150
+ pane.prepend(status);
2151
+ }
2152
+ this.element.setAttribute("aria-busy", "true");
2153
+ this.ghost = new Ghost(loading, { id: this.options.id });
2154
+ return pane;
2155
+ }
2156
+ show(content) {
2157
+ const loading = this.element.getAttribute("aria-busy") === "true";
2158
+ const pane = this.swap(content);
2159
+ if (loading && this.options.onReveal) this.arrival.then(this.options.onReveal);
2160
+ this.element.removeAttribute("aria-busy");
2161
+ this.memory = new Memory(pane, { id: this.options.id });
2162
+ return pane;
2163
+ }
2164
+ swap(content) {
2165
+ this.memory?.destroy();
2166
+ this.ghost?.destroy();
2167
+ this.memory = this.ghost = null;
2168
+ const pane = document.createElement("div");
2169
+ pane.setAttribute(ATTR_PANE, "");
2170
+ pane.append(content);
2171
+ const previous = this.current;
2172
+ this.current = pane;
2173
+ if (previous) fadeOut(previous).then(() => previous.remove());
2174
+ this.element.append(pane);
2175
+ this.arrival = fadeIn(pane, this.options.reveal, { initial: !previous });
2176
+ return pane;
2177
+ }
2178
+ destroy() {
2179
+ this.memory?.destroy();
2180
+ this.ghost?.destroy();
2181
+ removeStyles();
2182
+ }
2183
+ };
2184
+ //#endregion
2185
+ export { ATTR_LOADING as $, DEFAULT_TEXT as A, themeVariables as B, smart as C, rectsOf as D, motionOf as E, DEFAULT_COLORS as F, addStyles as G, INLINE_STUB as H, DEFAULT_THEME as I, ATTR_BLOCK as J, removeStyles as K, applyTheme as L, checkNonce as M, debugging as N, shimmer as O, setDebug as P, ATTR_LAYER as Q, skeletonCSS as R, sequence as S, drawSkeleton as T, inlineScriptTag as U, INLINE_SCRIPT as V, SMART_SKELETON_CSS as W, ATTR_GHOST as X, ATTR_DEBUG as Y, ATTR_IGNORE as Z, byPosition as _, exitDuration as a, ATTR_STATUS as at, planReveal as b, pendingImages as c, DURATIONS as ct, skeletonBoxStyle as d, RESIZE_DURATION as dt, ATTR_OWN as et, Memory as f, byContent as g, SKELETON_DELAY as h, fadeOut as i, ATTR_SNAPSHOT as it, measureSkeleton as j, prefersReducedMotion as k, DEFAULT_SKELETON_BOX_OPTIONS as l, FADE_DURATION as lt, forgetShapes as m, boxOf as n, ATTR_RECT as nt, sweep as o, ATTR_XRAY as ot, Ghost as p, ATTR_BAR as q, fadeIn as r, ATTR_REVEAL as rt, imageReady as s, CASCADE_SPREAD as st, Reveal as t, ATTR_PANE as tt, createSkeletonBox as u, IMAGE_TIMEOUT as ut, byRow as v, together as w, quickly as x, instant as y, themeStyleTag as z };
2186
+
2187
+ //# sourceMappingURL=core.js.map