@wyndlass/app-sdk 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,8 @@ This package has:
6
6
  - the runtime Wyndlass's own apps are built on;
7
7
  - a React adapter;
8
8
  - two templates;
9
- - the command `wyndlass-app`, which previews, checks and packs an app.
9
+ - the command `wyndlass-app`, which previews, checks and packs an app;
10
+ - a skill that builds, imports and schedules an app with your coding agent.
10
11
 
11
12
  It needs Node 20 or later. MIT licensed, © Northwynd Labs LLC.
12
13
 
@@ -34,6 +35,22 @@ Each template is a small app with two pages: a heading and the time, then the we
34
35
 
35
36
  The id is a reverse-domain name in your organization's own domain. Ids under `com.wyndlass.` are Wyndlass's, and are refused.
36
37
 
38
+ ## With a coding agent
39
+
40
+ ```sh
41
+ npx @wyndlass/app-sdk skill install
42
+ ```
43
+
44
+ It adds the skill `wyndlass-app` to the project's `.claude/skills/`, where Claude Code finds it. With `--user`, it goes in `~/.claude/skills/` instead, for every project. Running it again replaces the copy with this version's.
45
+
46
+ Then ask your agent for the app you want. The skill:
47
+ 1. asks what the app shows and to whom, the screens' orientation, the data it needs, and its name and id;
48
+ 2. makes it from a template, and previews it with you in both orientations and in the CMS's frame;
49
+ 3. checks and packs it;
50
+ 4. imports, places and schedules it through the Wyndlass MCP, asking you before each.
51
+
52
+ The agent never approves a version: a person does, in the CMS.
53
+
37
54
  ## Preview it
38
55
 
39
56
  `npm run preview` (`wyndlass-app preview`) runs the app as screens run it, at `http://127.0.0.1:5199/`, on your computer only. The page is the same host Wyndlass's players run, with the same contract, deadline and error handling. It draws the app again whenever a source or `app.json` changes, and says why when a source doesn't build.
@@ -44,7 +61,7 @@ Its controls:
44
61
  - **The clock:** play, pause, seek, and speeds from 0.25× to 4×.
45
62
  - **Enter and exit,** with each preset and how long it lasts, and the app's pages.
46
63
  - **Data:** each data source's answer, from samples you can edit in the page. `--data <dir>` reads `<source>.json` there instead (the answer's `data`, or a whole answer). The preview reaches no data service. A source the definition doesn't declare gets no answer, as on a screen.
47
- - **Tables:** each file in `<dir>/tables/`, or one you add in the page. A file may be CSV (its header gives the columns), a JSON array of rows, or a snapshot. A `datatable` param chooses among them, and an app with `slots` is bound to the first.
64
+ - **Tables:** each file in `<dir>/tables/`, or one you add in the page. A file may be CSV, a JSON array of rows, or a snapshot. A CSV's header gives its columns' labels, and their keys are made as Wyndlass makes a table's (`Now serving` is `now_serving`), so a `column` param holds the key it will on screens. A `datatable` param chooses among the tables, a `column` param among the chosen table's columns, and an app with `slots` is bound to the first table.
48
65
  - **The people at the screen,** for an app that declares the `impressions` sensor: someone arrives, looks, or leaves.
49
66
  - **The player's controller,** for what `capabilities.player` allows. The page sends the app the player's events, each one editable. A command the app may give is answered "not applied", and one it may not is refused, as on a screen.
50
67
 
package/cli/commands.mjs CHANGED
@@ -1,7 +1,8 @@
1
- // The CLI's commands (WP-0101 decision 5): create, check, pack and upload. Each returns what it says, as lines, and
2
- // throws CliError for a refusal. Nothing here signs in anywhere (decision 6): what touches Wyndlass goes through the
3
- // person's MCP client or the CMS, and upload only PUTs to an address it is given.
4
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
1
+ // The CLI's commands (WP-0101 decision 5): create, check, pack, upload and skill install. Each returns what it says, as
2
+ // lines, and throws CliError for a refusal. Nothing here signs in anywhere (decision 6): what touches Wyndlass goes
3
+ // through the person's MCP client or the CMS, and upload only PUTs to an address it is given.
4
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
5
6
  import { basename, dirname, join, relative, resolve } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { CliError, MEANING, SDK_VERSION, packageOf, schemaErrors, verdict } from "./package.mjs";
@@ -112,3 +113,21 @@ export async function upload(url, file, headers = []) {
112
113
  if (!res.ok) throw new CliError(`the upload was refused: HTTP ${res.status}${res.status === 403 ? " (the address may have expired: ask for another)" : ""}`);
113
114
  return [`${basename(file)}: uploaded (${body.length} bytes). Import it with app.import.`];
114
115
  }
116
+
117
+ /** The skill for the customer's own coding agent (decision 12), in the package beside the CLI it describes. */
118
+ export const SKILL = "wyndlass-app";
119
+
120
+ /**
121
+ * skill install [--user]: the skill in the project's .claude/skills/wyndlass-app/ (the folder it runs in), or with --user
122
+ * in ~/.claude/skills/wyndlass-app/, where Claude Code finds it. An earlier copy is replaced whole, so a newer SDK's skill
123
+ * leaves none of an older one's files behind.
124
+ */
125
+ export function skillInstall({ user = false, cwd = process.cwd(), home = homedir() } = {}) {
126
+ const target = join(user ? home : resolve(cwd), ".claude", "skills", SKILL);
127
+ const earlier = existsSync(target);
128
+ rmSync(target, { recursive: true, force: true });
129
+ mkdirSync(dirname(target), { recursive: true });
130
+ cpSync(join(sdk, "skill", SKILL), target, { recursive: true });
131
+ return [`${earlier ? "Replaced" : "Installed"} the skill ${SKILL} (@wyndlass/app-sdk ${SDK_VERSION}) in ${user ? target : relative(cwd, target)}.`,
132
+ "Claude Code finds it there: ask it to build an app for your Wyndlass screens."];
133
+ }
package/cli/main.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- // wyndlass-app (WP-0101 decision 5): create, preview, check, pack and upload an app for Wyndlass's screens. The skill's
3
- // install comes with part e. It signs in to nothing (decision 6).
2
+ // wyndlass-app (WP-0101 decision 5): create, preview, check, pack and upload an app for Wyndlass's screens, and install
3
+ // the skill that does it with a coding agent (decision 12). It signs in to nothing (decision 6).
4
4
  import { parseArgs } from "node:util";
5
5
  import { CliError, SDK_VERSION } from "./package.mjs";
6
- import { check, create, pack, TEMPLATES, upload } from "./commands.mjs";
6
+ import { check, create, pack, skillInstall, TEMPLATES, upload } from "./commands.mjs";
7
7
  import { preview } from "./preview.mjs";
8
8
 
9
9
  const HELP = `wyndlass-app ${SDK_VERSION}: build apps for Wyndlass's screens.
@@ -17,6 +17,9 @@ const HELP = `wyndlass-app ${SDK_VERSION}: build apps for Wyndlass's screens.
17
17
  pack [dir] dist/<id>-<version>.zip, the package to import, after check
18
18
  upload <url> <zip> [--header "name: value" ...]
19
19
  PUT the package to the address app.prepareImport gave, with its headers
20
+ skill install [--user]
21
+ the skill for a coding agent, in .claude/skills/wyndlass-app/ here, or with
22
+ --user in ~/.claude/skills/; it replaces an earlier copy
20
23
 
21
24
  Importing, placing and scheduling go through the Wyndlass MCP (app.prepareImport, app.import) or the CMS.`;
22
25
 
@@ -49,6 +52,10 @@ try {
49
52
  } else if (command === "upload") {
50
53
  const { values, positionals } = parseArgs({ args: rest, allowPositionals: true, options: { header: { type: "string", multiple: true } } });
51
54
  lines = await upload(positionals[0], positionals[1], values.header ?? []);
55
+ } else if (command === "skill") {
56
+ const { values, positionals } = parseArgs({ args: rest, allowPositionals: true, options: { user: { type: "boolean" } } });
57
+ if (positionals.length !== 1 || positionals[0] !== "install") throw new CliError("skill has one command: wyndlass-app skill install [--user]");
58
+ lines = skillInstall({ user: values.user === true });
52
59
  } else if (!command || command === "help" || command === "--help" || command === "-h") {
53
60
  lines = [HELP];
54
61
  } else if (command === "--version" || command === "-v") {
package/dist/index.d.ts CHANGED
@@ -33,7 +33,11 @@ export declare function data<T = unknown>(app: WyndlassApp, source: string, para
33
33
  export declare function kept<T = unknown>(app: WyndlassApp, source: string, params: Params): DataAnswer<T> | undefined;
34
34
  /** An image a data answer names, as a blob URL the app can draw; refused outside a host. */
35
35
  export declare function image(app: WyndlassApp, path: string): Promise<string>;
36
- /** A data table's snapshot as the manifest carries it, the bound table's with only the rows the binding chooses; null without one. */
36
+ /**
37
+ * A data table's snapshot as the manifest carries it: a table the layout names, in a datatable param or in the element's
38
+ * binding, which needs no binding of its own; the bound table's with only the rows the binding chooses. Null when the
39
+ * screen has no snapshot of that table.
40
+ */
37
41
  export declare function snapshot(app: WyndlassApp, table: string): unknown | null;
38
42
  /** The app's view of the player's controller (ADR 0050), on screens, as its definition's `capabilities.player` allows. */
39
43
  export declare function controllerOf(app: WyndlassApp): AppController | undefined;
package/dist/index.js CHANGED
@@ -47,7 +47,11 @@ export function image(app, path) {
47
47
  const c = contextOf(app);
48
48
  return c ? c.image(path) : Promise.reject(unavailable());
49
49
  }
50
- /** A data table's snapshot as the manifest carries it, the bound table's with only the rows the binding chooses; null without one. */
50
+ /**
51
+ * A data table's snapshot as the manifest carries it: a table the layout names, in a datatable param or in the element's
52
+ * binding, which needs no binding of its own; the bound table's with only the rows the binding chooses. Null when the
53
+ * screen has no snapshot of that table.
54
+ */
51
55
  export function snapshot(app, table) {
52
56
  return contextOf(app)?.snapshot(table) ?? null;
53
57
  }
@@ -1,7 +1,7 @@
1
1
  /* @wyndlass/app-sdk's preview: player-core's host, compiled. MIT, (c) Northwynd Labs LLC. */
2
- "use strict";(()=>{var Nn=Object.defineProperty;var On=(t,e,n)=>e in t?Nn(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var h=(t,e,n)=>On(t,typeof e!="symbol"?e+"":e,n);var wt=Reflect.apply,Ne=Object.getOwnPropertyDescriptor,fe=Object.getPrototypeOf,R=globalThis,v=(t,e,n)=>wt(t,e,n),O=(t,e)=>t?Ne(t,e)?.get:void 0,Oe=(t,e)=>t?Ne(t,e)?.set:void 0,P=t=>R[t]?.prototype,vt=(t,e,n)=>wt(t,e,n);var Oi=Date.now,$i=new TextEncoder,Di=new TextDecoder,ji=TextEncoder.prototype.encode,Fi=TextDecoder.prototype.decode;var Hi=O(fe(Uint8Array.prototype),"length");var $n=Uint8Array,Dn=t=>new $n(t),jn=[];for(let t=0;t<256;t++)jn[t]=(t<16?"0":"")+t.toString(16);var Fn=Object.create(null);for(let t=0;t<64;t++)Fn["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[t]]=t;var lt=R.crypto?.subtle,$e=lt?fe(lt):void 0,Re=R.crypto,Hn=Re?fe(Re).getRandomValues:void 0,Bi=$e?.digest,Ui=$e?.importKey,qi=$e?.sign;var kt=t=>v(Hn,Re,[Dn(t)]),_i={setTimeout:R.setTimeout,clearTimeout:R.clearTimeout,setInterval:R.setInterval,clearInterval:R.clearInterval};var Wi=R.fetch;var ne=P("MessagePort"),zi=ne&&ne.postMessage,Vi=Oe(ne,"onmessage"),Ki=ne&&ne.start,De=P("MessageEvent"),Zi=O(De,"data"),Ji=O(De,"ports"),Yi=O(De,"source");var xt=P("EventTarget"),Xi=xt?.addEventListener,Gi=xt?.removeEventListener;var Qi=R.Worker,ue=P("Worker"),er=ue&&ue.postMessage,tr=Oe(ue,"onmessage"),nr=Oe(ue,"onerror");var pt=R.Blob,he=R.URL,ct=he?.createObjectURL,Bn=he?.revokeObjectURL,je=pt&&ct?{make:(t,e)=>v(ct,he,[new pt(t,{type:e})]),revoke:t=>v(Bn,he,[t])}:void 0,ee=R.customElements,Fe=ee?fe(ee):void 0,Un=Fe?.define,qn=Fe?.get,He=ee&&Fe?{define:(t,e)=>v(Un,ee,[t,e]),get:t=>v(qn,ee,[t])}:void 0,At=R.document,me=P("Document"),_n=me?.createElement,Wn=me?.createElementNS,zn=WeakSet.prototype.add,sr=WeakSet.prototype.has,Be=new WeakSet,Vn=new WeakSet,ge=(t,e)=>(v(zn,t,[e]),e);function D(t){return ge(Be,v(_n,At,[t]))}var ye=t=>ge(Be,v(Wn,At,["http://www.w3.org/2000/svg",t]));var K=t=>ge(Vn,t);var se=P("Node"),Ue=P("Element"),be=P("HTMLElement"),Z=t=>O(se,t),ut=Z("parentNode"),Kn=Z("firstChild"),Zn=Z("lastChild"),Jn=Z("nextSibling"),Yn=Z("nodeType"),Xn=Z("childNodes"),Gn=O(Ue,"localName"),Qn=se?.appendChild,es=se?.insertBefore,ts=se?.removeChild,q=Ue,ns=q?.getAttribute,ss=q?.getAttributeNames,is=q?.setAttribute,rs=q?.removeAttribute,os=q?.matches,as=q?.attachShadow,ds=q?.querySelectorAll,ls=P("HTMLDialogElement")?.close,ps=O(P("NodeList"),"length"),cs=P("NodeList")?.item,we=P("MutationRecord"),us=O(we,"type"),hs=O(we,"target"),ms=O(we,"addedNodes"),fs=O(we,"removedNodes"),ht=R.MutationObserver,gs=P("MutationObserver")?.observe,Mt=me?Ne(me,"adoptedStyleSheets"):void 0,mt=Mt?.get,ft=Mt?.set,ys=R.requestAnimationFrame,gt=O(be,"style"),ve=P("CSSStyleDeclaration"),bs=ve?.getPropertyValue,ws=ve?.getPropertyPriority,vs=ve?.setProperty,yt=be?.showPopover,bt=be?.hidePopover,ks=O(P("Event"),"target"),xs=P("Event")?.stopImmediatePropagation,ce=t=>{let e=[];for(let n=0,s=v(ps,t,[]);n<s;n++)e[n]=v(cs,t,[n]);return e},te=ht&&se&&Ue&&be&&ve?{parent:t=>v(ut,t,[]),first:t=>v(Kn,t,[]),last:t=>v(Zn,t,[]),next:t=>v(Jn,t,[]),isElement:t=>v(Yn,t,[])===1,name:t=>v(Gn,t,[]),children:t=>ce(v(Xn,t,[])),append:(t,e)=>v(Qn,t,[e]),insert:(t,e,n)=>v(es,t,[e,n]),remove:t=>{let e=v(ut,t,[]);e&&v(ts,e,[t])},attribute:(t,e)=>v(ns,t,[e]),attributes:t=>v(ss,t,[]),setAttribute:(t,e,n)=>v(is,t,[e,n]),removeAttribute:(t,e)=>v(rs,t,[e]),matches:(t,e)=>v(os,t,[e]),all:(t,e)=>ce(v(ds,t,[e])),closeDialog:t=>v(ls,t,[]),shadow:t=>v(as,t,[{mode:"closed"}]),style:(t,e)=>{let n=v(gt,t,[]);return[v(bs,n,[e]),v(ws,n,[e])]},setStyle:(t,e,n,s="")=>v(vs,v(gt,t,[]),[e,n,s]),observer:t=>new ht(t),observe:(t,e,n)=>v(gs,t,[e,n]),record:t=>({type:v(us,t,[]),target:v(hs,t,[]),added:ce(v(ms,t,[])),removed:ce(v(fs,t,[]))}),adopted:mt&&ft?{count:t=>v(mt,t,[]).length,clear:t=>v(ft,t,[[]])}:void 0,frame:t=>v(ys,globalThis,[t]),popover:yt&&bt?{show:t=>v(yt,t,[]),hide:t=>v(bt,t,[])}:void 0,target:t=>v(ks,t,[]),stop:t=>v(xs,t,[])}:void 0;function qe(t){if(ge(Be,t),te)for(let e of te.children(t))te.isElement(e)&&qe(e);return t}var _e=t=>new TextEncoder().encode(t);function As(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),n="";for(let s of e)n+=s.toString(16).padStart(2,"0");return n}async function We(t){return As(await crypto.subtle.digest("SHA-256",t))}var Ms=["name","type","tags","location","site","district","region","organization","path","timeZone"],Es=["text","date","currency","truncate"],Et={site:"SITE",district:"DISTRICT",region:"REGION",organization:"ORGANIZATION"};function ke(t,e,n,s){if(!t)return n==="timeZone"&&e?e:void 0;if(n==="fields")return s&&t.fields?.[s]||void 0;let i=t.path??[];switch(n){case"name":return t.name||void 0;case"type":return t.type||void 0;case"tags":return t.tags?.length?t.tags.join(", "):void 0;case"location":return i.at(-1)?.name;case"path":return i.length?i.map(o=>o.name).join(" \u203A "):void 0;case"timeZone":return e||void 0;default:return Et[n]?[...i].reverse().find(o=>o.kind===Et[n])?.name:void 0}}function Ss(t,e){if(t==null||t==="")return;let n=String(t);if(e==="currency"){let s=Number(t);return Number.isFinite(s)?s.toFixed(2):n}if(e==="date"){let s=Date.parse(n);return Number.isNaN(s)?n:new Date(s).toISOString().slice(0,10)}return e==="truncate"&&n.length>40?n.slice(0,39)+"\u2026":n}function ze(t,e){return(t.source==="screen"?ke(e.screen,e.timeZone,t.field,t.key):Ss(e.tables?.[t.table]?.rows?.[t.row??0]?.[t.column],t.format))??t.fallback??""}function Ts(t){let e=/^\s*(?:screen\.(fields\.[a-z][A-Za-z0-9]{0,31}|[A-Za-z]+)|table\.([A-Za-z0-9_-]+)\[(\d+)\](?:\.([A-Za-z0-9_]+)|\["((?:[^"\\]|\\.)*)"\]))\s*((?:\|\s*(?:[a-z]+|"(?:[^"\\]|\\.)*")\s*)*)$/.exec(t);if(!e)return null;let n=[...e[6].matchAll(/\|\s*([a-z]+|"((?:[^"\\]|\\.)*)")/g)],s=n.find(o=>o[1].startsWith('"'))?.[2]?.replace(/\\(.)/g,"$1"),i=n.find(o=>!o[1].startsWith('"'))?.[1];return e[1]!==void 0?i?null:e[1].startsWith("fields.")?{source:"screen",field:"fields",key:e[1].slice(7),...s!==void 0?{fallback:s}:{}}:Ms.includes(e[1])?{source:"screen",field:e[1],...s!==void 0?{fallback:s}:{}}:null:i&&!Es.includes(i)?null:{source:"table",table:e[2],row:Number(e[3]),column:e[4]??e[5].replace(/\\(.)/g,"$1"),...i?{format:i}:{},...s!==void 0?{fallback:s}:{}}}function Ve(t,e){return t.replace(/\{{2}([^{}]*)\}{2}/g,(n,s)=>{let i=Ts(s);return i?ze(i,e):n})}function St(t,e){return Object.fromEntries(Object.entries(t).map(([n,s])=>[n,typeof s=="string"?Ve(s,e):s]))}var Ke=t=>t==null||t==="";function Pt(t,e,n,s,i,o){return Ps(t,e,n,s,{now:i,timeZone:o})}function Ps(t,e,n,s,i){if(n===null&&(e==="eq"||e==="ne"))return e==="eq"?Ke(t):!Ke(t);if(Ke(t)||n===null||n===void 0)return!1;switch(s){case"number":case"currency":{let o=Number(t),r=Number(n);return typeof n!="boolean"&&n!==""&&Number.isFinite(o)&&Number.isFinite(r)&&Ze(e,o-r)}case"boolean":{let o=n===!0||n==="true"?!0:n===!1||n==="false"?!1:void 0;return o!==void 0&&(e==="eq"?t===o:e==="ne"?t!==o:!1)}case"date":{let o=n==="today"?Ls(i.now,i.timeZone).date:String(n),r=String(t).slice(0,10);return/^\d{4}-\d{2}-\d{2}$/.test(o)&&Ze(e,r<o?-1:r>o?1:0)}case"datetime":{let o=Date.parse(String(t)),r=n==="now"?i.now:Date.parse(String(n));return!Number.isNaN(o)&&!Number.isNaN(r)&&Ze(e,o-r)}default:{let o=String(t).toLowerCase(),r=String(n).toLowerCase();return e==="eq"?o===r:e==="ne"?o!==r:e==="contains"?o.includes(r):!1}}}function Ze(t,e){switch(t){case"eq":return e===0;case"ne":return e!==0;case"lt":return e<0;case"lte":return e<=0;case"gt":return e>0;case"gte":return e>=0;default:return!1}}var Tt=new Map;function Ls(t,e){let n=Tt.get(e);n||(n=new Intl.DateTimeFormat("en-US",{timeZone:e,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),Tt.set(e,n));let s=Object.fromEntries(n.formatToParts(new Date(t)).map(i=>[i.type,i.value]));return{date:`${s.year}-${s.month}-${s.day}`,minute:Number(s.hour)%24*60+Number(s.minute),day:String(s.weekday).slice(0,3).toLowerCase()}}var Lt=(t,e)=>!t||(t.from===void 0||t.from<=e)&&(t.until===void 0||e<t.until);var ie=class{constructor(e,n,s,i,o){h(this,"renderer",e);h(this,"wrap",i);h(this,"onShown",o);h(this,"timer");h(this,"turns",0);h(this,"turnAt");h(this,"left");h(this,"stopped",!1);h(this,"share");h(this,"pages");this.pages=n.pages??[],this.share=n.durationMs/Math.max(1,this.pages.length),this.turnAt=s+this.share,this.arm()}arm(){clearTimeout(this.timer);let e=this.pages.length;if(this.stopped||this.left!==void 0||e<2||!this.wrap&&this.turns+1>=e)return;let n=this.pages[(this.turns+1)%e];this.renderer.prepare?.(n).catch(()=>{}),this.timer=setTimeout(()=>{this.turn(n)},Math.min(Math.max(0,this.turnAt-Date.now()),2**31-1))}async turn(e){try{await this.renderer.show(e)}catch{}this.stopped||(this.turns++,this.turnAt+=this.share,this.onShown?.(e),this.arm())}pause(){this.stopped||this.left!==void 0||(clearTimeout(this.timer),this.left=Math.max(0,this.turnAt-Date.now()))}resume(){this.stopped||this.left===void 0||(this.turnAt=Date.now()+this.left,this.left=void 0,this.arm())}stop(){if(!this.stopped){this.stopped=!0,clearTimeout(this.timer);for(let e of this.pages)this.renderer.release?.(e)}}};var Je=class extends Error{constructor(n,s){super(s);h(this,"code",n);this.name="MediaError"}};function Ye(t){return t instanceof Je?{code:t.code.slice(0,64),message:t.message.slice(0,1e3)}:{code:"not-shown",message:String(t instanceof Error?t.message:t??"").slice(0,1e3)}}var It=t=>t.blob.contentType.startsWith("video/");var Xe="position:absolute;inset:0;width:100%;height:100%;object-fit:contain",Cs=1,xe=class{constructor(e,n={}){h(this,"container",e);h(this,"options",n);h(this,"prepared",new Map);h(this,"shown",null);h(this,"endListeners",new Set);h(this,"itemListeners",new Set);h(this,"appListeners",new Set);h(this,"held",null);h(this,"drawn",0);h(this,"shownHash",null);let s=n.media;s&&(document.documentElement.style.background="transparent",document.body.style.background="transparent",e.style.background="#000",s.onEnd((i,o)=>{let r=this.shown;if(r?.mediaId===i)for(let a of this.endListeners)a(r.slide,o)}))}onMediaEnd(e){this.endListeners.add(e)}onElementItem(e){this.itemListeners.add(e)}onApp(e){this.appListeners.add(e)}app(e){for(let n of this.appListeners)n(e);this.options.onApp?.(e)}prepare(e){let n=this.prepared.get(e);if(n)return n.ready;let s;try{s=this.make(e)}catch(i){return Promise.reject(i)}return this.prepared.set(e,s),s.ready.catch(()=>{this.prepared.get(e)===s&&(this.prepared.delete(e),this.dispose(s))}),s.ready}release(e){let n=this.prepared.get(e);n&&(this.prepared.delete(e),this.dispose(n))}async show(e){await this.prepare(e);let n=this.prepared.get(e);if(!n)throw new Error("the slide was released before it was shown");this.prepared.delete(e);let s=this.shown,i=this.options.media,o=s?Rt(e):void 0;if(n.kind==="layout"&&o===void 0){await H(()=>{s?this.container.insertBefore(n.el,s.el):this.container.append(n.el),this.shown=n,this.shownHash=e.blob.hash}),this.held=[],await n.view.start().catch(()=>{});let r=Promise.resolve();return await H(()=>{this.shown===n&&(n.el.style.visibility="",s?.el.remove(),i&&this.paint("transparent"),i&&s?.mediaId&&(r=i.show([],[s.mediaId],Date.now())))}),await r.catch(()=>{}),s&&this.dispose(s),this.releaseHeld(),"cut"}if(o!==void 0&&i&&n.kind==="native"&&s.kind==="native"){if(n.fadesIn&&!n.queued){let r=Promise.resolve();return await H(()=>{n.el.style.visibility="",this.container.insertBefore(n.el,s.el),r=i.show([n.mediaId],[],Date.now()),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{}),Rs(o).then(async()=>{await i.show([],[s.mediaId],Date.now()).catch(()=>{}),s.el.remove(),this.dispose(s)}),"crossfade"}}else if(o!==void 0&&i&&n.kind==="native"){let r=Promise.resolve();return await H(()=>{n.el.style.visibility="",this.container.insertBefore(n.el,s.el),r=i.show([n.mediaId],[],Date.now()),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{}),this.paint("transparent"),Nt(s.el,1,0,o).then(()=>{s.el.remove(),this.dispose(s)}),"crossfade"}else if(o!==void 0)return await H(()=>{n.el.style.visibility="",n.el.style.opacity="0",this.container.append(n.el),n.kind==="video"&&Ge(n.el),this.shown=n,this.shownHash=e.blob.hash}),n.view&&(this.held=[],n.view.start().then(()=>this.releaseHeld())),Nt(n.el,0,1,o).then(async()=>{s.el.remove(),this.shown===n&&this.paint(i?"#000":""),i&&s.mediaId&&await i.show([],[s.mediaId],Date.now()).catch(()=>{}),this.dispose(s)}),"crossfade";if(i&&n.kind==="native"){let r=Promise.resolve();await H(()=>{n.el.style.visibility="",s?this.container.insertBefore(n.el,s.el):this.container.prepend(n.el),r=i.show([n.mediaId],s?.mediaId?[s.mediaId]:[],Date.now()),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{}),await H(()=>{this.shown===n&&(s?.el.remove(),this.paint("transparent"))})}else{let r=Promise.resolve();await H(()=>{n.el.style.visibility="",n.el.parentNode!==this.container&&this.container.append(n.el),s?.el.remove(),this.paint(i?"#000":""),i&&s?.mediaId&&(r=i.show([],[s.mediaId],Date.now())),n.kind==="video"&&Ge(n.el),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{})}return s&&this.dispose(s),"cut"}paint(e){this.options.background?this.options.background(e):this.container.style.background=e}standby(){let e=this.shown;this.shown=null,this.shownHash=null,this.paint(this.options.media?"#000":""),e&&(e.mediaId&&this.options.media?.show([],[e.mediaId],Date.now()).catch(()=>{}),this.dispose(e))}pause(){let e=this.shown;e?.view?.pause(),e?.kind==="video"?e.el.pause():e?.mediaId&&this.options.media?.control(e.mediaId,"pause").catch(()=>{})}resume(){let e=this.shown;e?.view?.resume(),e?.kind==="video"?Ge(e.el):e?.mediaId&&this.options.media?.control(e.mediaId,"play").catch(()=>{})}make(e){if(e.layout){let{doc:r,source:a,untilPass:d}=e.layout,l=K(D("div"));l.dataset.blob=e.blob.hash,l.style.cssText=`${Xe};visibility:hidden`,this.container.append(l);let c={slide:e,kind:"layout",el:l,ready:Promise.resolve()},p=y=>this.options.path?`${this.options.path}/${y}`:y,m=String(++this.drawn);return c.view=new J(r,a,l,{media:this.options.media,path:this.options.path,endsWith:d?p(d):void 0,onItem:y=>{let A={...y,slide:y.slide?`${m}.${y.slide}`:m};this.held?this.held.push(A):this.item(A)},onPassEnd:y=>{if(d&&y===p(d)&&this.shown===c){for(let A of this.endListeners)A(e);return"hold"}return this.options.onPassEnd?.(y)},onApp:y=>this.app(y)}),c.ready=c.view.ready,c}if(!It(e)){if(!e.bytes&&!e.url)throw new Error(`no bytes for ${e.blob.hash}`);let r=e.bytes?URL.createObjectURL(new Blob([e.bytes],{type:e.blob.contentType})):void 0,a=K(D("img"));return a.src=r??e.url,a.alt="",a.dataset.blob=e.blob.hash,a.style.cssText=this.fill(),{slide:e,kind:"image",el:a,objectUrl:r,ready:a.decode()}}let n=this.options.media;if(n){let r=`m-${Cs++}`,a=K(D("div"));a.dataset.blob=e.blob.hash,a.dataset.media=r,a.style.cssText=`${Xe};visibility:hidden`;let d=this.container.getBoundingClientRect(),l={x:d.left,y:d.top,w:d.width||window.innerWidth,h:d.height||window.innerHeight},c=Rt(e),p=c!==void 0&&this.shown?.kind==="native"&&n.capability.animates&&n.capability.layers>=2,m=p?[{property:"opacity",keyframes:[{t:0,v:0},{t:c,v:1}]}]:[],y={slide:e,kind:"native",el:a,mediaId:r,fadesIn:p,ready:Promise.resolve()},A=this.options.fit??"contain";return y.ready=n.prepare({mediaId:r,blob:e.blob.hash,contentType:e.blob.contentType,box:l,z:p?1:0,loop:e.loop,muted:!1,fit:A,...p?{tracks:m}:{}}).then(f=>{f.queued&&(y.queued=!0)}),y}let s=e.url?void 0:URL.createObjectURL(new Blob([e.bytes],{type:e.blob.contentType})),i=K(D("video"));i.dataset.blob=e.blob.hash,i.style.cssText=`${this.fill()};visibility:hidden`,i.muted=!1,i.playsInline=!0,i.preload="auto",i.loop=e.loop,i.src=e.url??s,i.addEventListener("ended",()=>{if(this.shown?.el===i)for(let r of this.endListeners)r(e)}),i.addEventListener("error",()=>{if(this.shown?.el===i)for(let r of this.endListeners)r(e,`video: ${i.error?.message??"error"}`)});let o=new Promise((r,a)=>{if(i.readyState>=2)return r();i.addEventListener("loadeddata",()=>r(),{once:!0}),i.addEventListener("error",()=>a(new Error(`video: ${i.error?.message??"cannot load"}`)),{once:!0})});return this.container.append(i),{slide:e,kind:"video",el:i,objectUrl:s,ready:o}}item(e){this.options.onItem?.(e);for(let n of this.itemListeners)n(e)}releaseHeld(){let e=this.held;this.held=null,e?.length&&setTimeout(()=>{for(let n of e)this.item(n)},0)}fill(){let e=this.options.fit==="stretch"?"fill":this.options.fit??"contain";return Xe.replace("object-fit:contain",`object-fit:${e}`)}dispose(e){if(this.shown!==e){if(e.view?.dispose(),e.el.remove(),e.kind==="video"){let n=e.el;n.pause(),n.removeAttribute("src"),n.load()}e.mediaId&&this.options.media?.release(e.mediaId).catch(()=>{}),e.objectUrl&&URL.revokeObjectURL(e.objectUrl)}}};function Rt(t){if(t.transition?.type==="crossfade")return Math.max(0,Math.min(t.transition.durationMs??1e3,t.durationMs/2))}var Rs=t=>new Promise(e=>setTimeout(e,t));function Nt(t,e,n,s){return new Promise(i=>{let o=Date.now(),r=!1,a=()=>{r||(r=!0,t.style.opacity=n===1?"":String(n),i())};t.style.opacity=String(e);let d=()=>{if(r)return;let l=Math.min(1,(Date.now()-o)/s);if(l>=1)return a();t.style.opacity=String(e+(n-e)*l),requestAnimationFrame(d)};requestAnimationFrame(d),setTimeout(a,s+250)})}function Ge(t){t.play().catch(()=>(t.muted=!0,t.play())).catch(()=>{})}function H(t){return new Promise((e,n)=>{let s=!1,i=()=>{if(!s){s=!0;try{t(),e()}catch(o){n(o)}}};requestAnimationFrame(i),setTimeout(i,250)})}var re={apiVersion:2,properties:[{name:"device",description:"What the device is: id, type, shell and versions, model, resolution and orientation. Fixed while the player runs."},{name:"mode",description:"Where player-core runs: player, preview (in the CMS) or designer. In preview and the designer, commands change nothing."},{name:"timeZone",description:"The player's time zone (IANA), from the host.",event:"time.changed"},{name:"locale",description:"The player's locale (BCP 47), from the host."},{name:"connection",description:"Whether the player is connected to Wyndlass.",event:"connection.changed"},{name:"state",description:"starting, playing, paused, stopped (the standby screen), idle (nothing to play) or error.",event:"state.changed"},{name:"held",description:"A remote pause or stop in effect, by whom and since when; absent when none.",event:"state.changed"},{name:"content",description:"The manifest playing, when it was issued and switched to; absent when there is none.",event:"content.updated"},{name:"now",description:"What each zone shows: the item, its blob and content type, when it started, and its duration and position when known.",event:"item.start"},{name:"audio",description:"The player's volume (0 to 100) and whether it is muted; only when the host can set them.",event:"audio.changed"},{name:"display",description:"The display's power and brightness; only when the host can report them.",event:"display.changed"},{name:"capabilities",description:"What the host can do: audio, display power, display brightness."}],events:[{name:"state.changed",description:"The player's state changed (playing, paused, stopped, idle, error), and who changed it."},{name:"content.updated",description:"A new manifest was switched to."},{name:"item.start",description:"An item started in a zone (source start)."},{name:"item.end",description:"An item ended in a zone (source end): completed, skipped, stopped, error, replaced by new content, or finished by its app."},{name:"item.error",description:"An item could not be shown."},{name:"connection.changed",description:"The player connected to Wyndlass or lost its connection."},{name:"audio.changed",description:"The volume or mute changed, and who changed it."},{name:"display.changed",description:"The display's power or brightness changed, and who changed it."},{name:"time.changed",description:"The clock was adjusted or the time zone changed."},{name:"app.command",description:"A command for apps on the screen (apps.sendCommand); an app reacts if it knows the name."},{name:"app.failed",description:"An app on the screen could not run, or misbehaved (WP-0100): its bundle did not load, it was not an app, it needs a newer player, it kept throwing and was stopped, or it was late or overran a clip."},{name:"app.held",description:"An app version was held back on this screen until the next manifest, after the page stopped answering twice in an hour while it was on screen (WP-0100)."}],commands:[{name:"player.status",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/status",description:"Everything about the player now: device, mode, time zone, locale, connection, state (and any hold), the content playing, what each zone shows, audio, display and what the host can do. Changes nothing.",idempotent:!0},{name:"player.events",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/events",result:"player-api-results.schema.json#/$defs/events",description:"The player's recent events (the last 200), oldest first, after a sequence number: state changes, content updates, item starts and ends, errors, connection, audio, display and time. Pass next as after to poll for more. Changes nothing.",idempotent:!0},{name:"time.now",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/timeNow",description:"The player's current time, its time zone and UTC offset, and whether its clock is synchronized. Changes nothing.",idempotent:!0},{name:"player.play",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Resumes or starts playback on the screen. From a remote caller it also ends a remote pause or stop.",idempotent:!0},{name:"player.pause",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Freezes the screen on what it shows now: the loop's timer and any media stop where they are. From a remote caller it holds until a remote player.play or a restart; apps cannot resume it, and new content arrives paused.",idempotent:!0},{name:"player.stop",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Stops playback and shows the standby screen (black, display on). From a remote caller it holds until a remote player.play or a restart; apps cannot restart it, and new content arrives stopped.",idempotent:!0},{name:"player.next",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Skips to the next item in a zone (main by default); the current item ends as skipped."},{name:"player.previous",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Goes back to the previous item in a zone (main by default); the current item ends as skipped."},{name:"player.playItem",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/playItem",result:"player-api-results.schema.json#/$defs/now",description:"Shows a given item of the content playing now in a zone (main by default), then continues the loop from it. Refused for an item the playing manifest does not have."},{name:"audio.setVolume",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setVolume",result:"player-api-results.schema.json#/$defs/audio",description:"Sets the player's volume, 0 to 100. Only on players whose host can set it to a level.",needs:"audio.volume",idempotent:!0},{name:"audio.setMuted",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setMuted",result:"player-api-results.schema.json#/$defs/audio",description:"Mutes or unmutes the player. Only on players whose host can.",needs:"audio",idempotent:!0},{name:"display.setPower",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setPower",result:"player-api-results.schema.json#/$defs/display",description:"Turns the display on or to standby. Only on players whose host can. Standby blanks the screen until it is turned on.",needs:"display.power",idempotent:!0},{name:"display.setBrightness",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setBrightness",result:"player-api-results.schema.json#/$defs/display",description:"Sets the display's brightness, 0 to 100. Only on players whose host can.",needs:"display.brightness",idempotent:!0},{name:"apps.sendCommand",access:"control",apps:"none",remote:!0,params:"player-api-params.schema.json#/$defs/sendCommand",result:"player-api-results.schema.json#/$defs/delivered",description:"Sends a named command, with an optional argument, to the apps on the screen; an app that knows the name reacts. Returns how many apps listen."},{name:"app.finish",access:"control",apps:"read",remote:!1,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/none",description:"An app ends its own slot now: its item ends as finished and the loop moves on. Apps only; not a tool."}],host:[{name:"host.describe",kind:"request",description:"What the device is, its time zone, locale, connection and capabilities.",result:"player-api-host.schema.json#/$defs/describeResult"},{name:"host.time",kind:"request",description:"The host's time and time zone.",result:"player-api-common.schema.json#/$defs/timeNow"},{name:"host.getAudio",kind:"request",description:"The player's volume and mute.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.setAudio",kind:"request",description:"Sets the volume, the mute or both; answers the audio after.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.getDisplay",kind:"request",description:"The display's power and brightness.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.setDisplay",kind:"request",description:"Sets the display's power, brightness or both; answers the display after.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.adRequest",kind:"request",description:"Asks Wyndlass's ad broker for one slot of a break over the host's NATS connection (WP-0075); the result is the broker's ad-decision (ad-decision.schema.json). Only a host whose describe says ads is asked."},{name:"host.mediaPrepare",kind:"request",description:"Lays a video out behind the page and prepares it to its first frame; host.mediaState says when (ADR 0055)."},{name:"host.mediaShow",kind:"request",description:"Shows and hides prepared videos; replies once its layer has changed."},{name:"host.mediaControl",kind:"request",description:"Plays, pauses or seeks a video, or asks for its position."},{name:"host.mediaRelease",kind:"request",description:"Removes a video and frees its decoder."},{name:"host.reply",kind:"reply",description:"The host's answer to a request: result or error (unsupported, failed)."},{name:"host.connection",kind:"event",description:"The connection to Wyndlass changed."},{name:"host.timeChanged",kind:"event",description:"The clock was adjusted or the time zone changed."},{name:"host.audioChanged",kind:"event",description:"The volume or mute changed outside the player."},{name:"host.displayChanged",kind:"event",description:"The display changed outside the player."},{name:"host.mediaState",kind:"event",description:"A video was prepared, started, ended or failed, or its position was asked for."},{name:"host.presence",kind:"event",description:"What one of the host's sensors sees now: the presences in view, with random ids that never leave the page (WP-0105)."},{name:"host.sensing",kind:"event",description:"The site's sensing mode as the host applies it, and its sensors (WP-0105)."},{name:"host.serialPorts",kind:"event",description:"The host's sensor controller ports, and which are open (WP-0043)."},{name:"host.serialLine",kind:"event",description:"One line from a sensor controller, as it arrived (WP-0043)."},{name:"host.serialWrite",kind:"request",description:"Writes lines to a sensor controller, a gap apart; replies once written (WP-0043)."},{name:"host.bleWatch",kind:"request",description:"What the page listens for: beacons' iBeacon UUIDs, Eddystone namespaces and addresses; replies once the scan follows it. Sent only to a host that has sent host.bleState (WP-0044)."},{name:"host.bleAdverts",kind:"event",description:"Beacon frames that match the watch, at most four a second of each kind from each beacon (WP-0044)."},{name:"host.bleState",kind:"event",description:"The host's Bluetooth scanner: scanning, idle, off, refused, missing or failed; sent only by a host that has one (WP-0044)."},{name:"host.gpioPins",kind:"event",description:"The GPIO pins the host reads, with their values (WP-0044)."},{name:"host.gpioEdge",kind:"event",description:"A GPIO pin changed (WP-0044)."},{name:"host.mcp",kind:"relay",description:"An MCP message from ctl.dev.{deviceId}, for player-core to answer."},{name:"core.ready",kind:"core",description:"player-core is ready to answer host.mcp; until then the shell answers -32002 itself."},{name:"core.mcpReply",kind:"core",description:"player-core's answer to a host.mcp, for the shell to send back."},{name:"core.sensors",kind:"core",description:"The page's sensor controllers, channels and inputs, and the beacons it hears, whenever they change, for the heartbeat and sensors.recent (WP-0043, WP-0044)."}]};var Ot=Symbol.for("wyndlass.app.attach");var F=class extends Error{constructor(n,s){super(`the app could not run (${n})`);h(this,"reason",n);h(this,"error",s)}};function _(t){try{let e=t instanceof Error?t.name:void 0;return typeof e=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(e)?e:void 0}catch{return}}function oe(t){let e=t?.id;if(typeof e!="string"||e==="")return!0;for(let n=0;n<$t.length;n++)if(e[n]!==$t[n])return!1;return!0}var $t="com.wyndlass.";function Os(t=typeof navigator>"u"?"":navigator.userAgent){let e=/Chrom(?:e|ium)\/(\d+)/.exec(t);return e?Number(e[1]):void 0}function Dt(t,e=re.apiVersion,n=Os()){return!(t?.playerApi!==void 0&&t.playerApi>e||t?.webView!==void 0&&n!==void 0&&t.webView>n)}function $s(t){let e;try{e=t.paging?.()}catch{return null}let n=e;return n&&Number.isInteger(n.count)&&n.count>1&&typeof n.ms=="number"&&n.ms>0?{count:n.count,ms:n.ms}:null}var Qe=new Map;function Ds(){let t=kt(8),e="";for(let n=0;n<8;n++)e+=js[t[n]%26];return e}var js="abcdefghijklmnopqrstuvwxyz",Fs=0;function jt(t,e,n){let s=Qe.get(t);if(!s){let i=je.make([e],"text/javascript");s=import(i).then(o=>Hs(o,n),o=>{throw o instanceof F?o:new F("load",_(o))}).finally(()=>je.revoke(i)),Qe.set(t,s),s.catch(()=>Qe.delete(t))}return s}function Hs(t,e){let n=t.default;if(typeof n=="function"&&n.prototype instanceof HTMLElement){let s=n.prototype[Ot];if(typeof s!="function")throw new F("shape");let i=class extends n{},o=`${e?.element??"wl-app"}-${Ds()}${++Fs}`;try{He.define(o,i)}catch(r){throw new F("shape",_(r))}return{tag:o,shape:"class",attach:(r,a)=>{vt(s,r,[a])}}}if(oe(e)&&typeof t.element=="string"&&He.get(t.element))return{tag:t.element,shape:"legacy"};throw new F("shape")}var Bs=3,Us=6e4,Ae=class{constructor(e,n){h(this,"element",e);h(this,"o",n);h(this,"faults",[]);h(this,"halted",!1)}get stopped(){return this.halted}has(e){try{return!this.halted&&typeof this.element[e]=="function"}catch{return!1}}update(e){this.halted||this.run(()=>this.element.update?.(e))}reset(){this.halted||this.run(()=>this.element.reset?.())}paging(){return this.halted?null:$s(this.element)}hook(e,n){if(this.halted)return Promise.resolve();let s;try{s=this.element[e]?.(n)}catch(i){return this.fault(i),Promise.resolve()}return Promise.resolve(s).then(()=>{},i=>this.fault(i))}stop(e){if(!this.halted){this.halted=!0;try{this.element.remove()}catch{}this.o.dispose?.(),this.o.onStop(e)}}dispose(){this.o.dispose?.()}run(e){try{e()}catch(n){this.fault(n)}}fault(e){let n=(this.o.now??Date.now)();this.faults=this.faults.filter(s=>n-s<Us),this.faults.push(n),this.faults.length>=Bs&&this.stop(_(e))}},qs=24*3600*1e3,Me=class t{constructor(e){h(this,"o",e);h(this,"memory",new Map);h(this,"inflight",new Map);h(this,"images",new Map);h(this,"pictures",new Map)}now(){return this.o.now?.()??Date.now()}url(e){let n=this.o.base();if(!n)return;let s=this.o.token(),i=`${n.replace(/\/$/,"")}${e}`;return s?`${i}${i.includes("?")?"&":"?"}${s}`:i}stored(e){return this.memory.get(e)??this.o.store?.getItem(e)??void 0}static keyOf(e,n){let s=Object.keys(n).sort().map(i=>`${encodeURIComponent(i)}=${encodeURIComponent(String(n[i]))}`).join("&");return{query:s,key:`wyndlass:data:${e}?${s}`}}kept(e,n){let s=this.stored(t.keyOf(e,n).key);if(s)try{let i=JSON.parse(s);return this.dress(i,Date.parse(i.expiresAt)<=this.now())}catch{return}}keep(e,n){this.memory.set(e,n);try{this.o.store?.setItem(e,n)}catch{}}get(e,n){let{query:s,key:i}=t.keyOf(e,n),o=this.inflight.get(i);if(o)return o;let r=this.o.fetch??fetch,a=(async()=>{let d=this.stored(i),l=d?JSON.parse(d):void 0;if(l&&Date.parse(l.expiresAt)>this.now())return this.dress(l,!1);let c=this.url(`/v0/${encodeURIComponent(e)}?${s}`);try{if(!c)throw new Error("no data service");let p=await r(c);if(!p.ok)throw new Error(`the data service answered ${p.status}`);let m=await p.text();return this.keep(i,m),this.dress(JSON.parse(m),!1)}catch(p){if(l&&Date.parse(l.expiresAt)+qs>this.now())return this.dress(l,!0);throw p}})().finally(()=>this.inflight.delete(i));return this.inflight.set(i,a),a}dress(e,n){return{...e,offline:n,ageMs:Math.max(0,this.now()-Date.parse(e.fetchedAt))}}image(e){let n=this.images.get(e);return n||(n=this.imageBlob(e).then(s=>URL.createObjectURL(s)),this.images.set(e,n),n.catch(()=>this.images.delete(e))),n}imageBlob(e){let n=this.pictures.get(e);if(!n){let s=this.url(e);n=s?(this.o.fetch??fetch)(s).then(i=>{if(!i.ok)throw new Error(`the image answered ${i.status}`);return i.blob()}):Promise.reject(new Error("no data service")),this.pictures.set(e,n),n.catch(()=>this.pictures.delete(e))}return n}};var Ft="wyndlass.frame";function Ut(t,e){return!!t&&(t.apps?t.apps(e):!oe(e))}var _s=new Set(["load","shape","unsupported","error","late","overran"]),Ht=50,Bt=250,et=class{constructor(e,n,s,i){h(this,"frame",e);h(this,"port",n);h(this,"o",i);h(this,"halted",!1);h(this,"pages");h(this,"hooks");h(this,"pending",new Map);h(this,"next",0);h(this,"told");h(this,"last");h(this,"quiet");this.hooks=new Set(s.hooks),this.pages=s.paging}get stopped(){return this.halted}has(e){return!this.halted&&this.hooks.has(e)}paging(){return this.halted?null:this.pages}update(e){if(this.halted)return;let n=performance.now(),s=this.last;this.last={t:e,at:n};let i=this.told;if(i&&(i.running?Math.abs(e-(i.t+(n-i.at)))<=Ht:e===i.t)){i.running&&this.watch();return}let o=!!s&&n-s.at<Bt&&e>s.t&&Math.abs(e-s.t-(n-s.at))<=Ht;this.told={t:e,at:n,running:o},this.post({type:"clock",t:e,running:o}),o&&this.watch()}reset(){this.halted||this.post({type:"reset"})}hook(e,n){if(this.halted)return Promise.resolve();let s=++this.next;return new Promise(i=>{this.pending.set(s,i),this.post({type:"hook",id:s,kind:e,options:n})})}stop(e){this.halted||(this.halted=!0,this.frame.remove(),this.dispose(),this.o.onStop(e))}dispose(){clearTimeout(this.quiet),this.port.close();for(let e of this.pending.values())e();this.pending.clear()}heard(e){e.type==="paging"?this.pages=_t(e.paging):e.type==="hooked"&&typeof e.id=="number"?(this.pending.get(e.id)?.(),this.pending.delete(e.id)):e.type==="stopped"&&this.stop(Wt(e.error))}post(e){try{this.port.postMessage(e)}catch{}}watch(){clearTimeout(this.quiet),this.quiet=setTimeout(()=>{!this.told?.running||!this.last||this.halted||(this.told={...this.last,running:!1},this.post({type:"clock",t:this.last.t,running:!1}))},Bt)}};async function qt(t,e,n,s,i,o,r,a,d,l){let c=t.appInfo??n.info,p;try{p=new URL(d.frames).origin}catch{return l("unsupported")}let m=document.createElement("iframe");m.setAttribute("sandbox","allow-scripts"),m.setAttribute("referrerpolicy","no-referrer"),m.title=c?.id??"app",m.tabIndex=-1,m.dataset.frame=c?.id??"",m.style.cssText="display:block;width:100%;height:100%;border:0;pointer-events:none;background:transparent;color-scheme:normal";let y=new Promise(M=>{m.addEventListener("load",()=>M(!0),{once:!0}),setTimeout(()=>M(!1),o)});m.src=`${p}/`,e.append(m);let A=(M,w)=>(m.remove(),l(M,w));if(!await y||!m.contentWindow)return A("load");let{port1:f,port2:k}=new MessageChannel,b={type:"init",element:{id:t.id,type:"app",app:t.app,props:t.props??{},...t.binding?{binding:t.binding}:{},...t.appInfo?{appInfo:t.appInfo}:{}},bundle:{hash:n.hash,bytes:n.bytes.slice(),...n.info?{info:n.info}:{}},...s.place?{place:s.place}:{},screen:s.screen??null,timeZone:s.timeZone,locale:s.locale,preview:!!s.preview,clock:{t:i.now(),running:!1,startedAt:i.startedAt()},fonts:Vs(t.props??{},d),snapshots:Ks(t,s),deadlineMs:o};return new Promise(M=>{let w,T=!1,N=G=>{T||(T=!0,clearTimeout(j),M(G))},j=setTimeout(()=>{w?(N({app:w,late:!0}),w.update(i.now())):(f.close(),N(A("load")))},o+1e3),U=!1;f.onmessage=G=>{let x=G.data;if(!(!x||typeof x!="object")){if(x.type==="ask"){U&&Ws(x,s,f);return}if(w){x.type==="ready"?(N({app:w,late:x.late===!0}),w.update(i.now())):w.heard(x);return}x.type==="hello"&&!U?(U=!0,f.postMessage(b,[b.bundle.bytes.buffer])):x.type==="made"&&Array.isArray(x.hooks)?(w=new et(m,f,{type:"made",hooks:x.hooks.filter(C=>C==="enter"||C==="exit"),paging:_t(x.paging)},{onStop:a}),r(w)):x.type==="failed"&&(f.close(),N(A(_s.has(x.reason)?x.reason:"error",Wt(x.error))))}},m.contentWindow.postMessage({type:Ft},"*",[k])})}async function Ws(t,e,n){if(typeof t.id!="number")return;let s=(i,o=[])=>{try{n.postMessage(i,o)}catch{}};try{if(!e.data)throw new Error("no data service");if(t.what==="data"&&typeof t.source=="string")s({type:"answer",id:t.id,value:await e.data.get(t.source,zs(t.params))});else if(t.what==="image"&&typeof t.path=="string"){let i=await e.data.imageBlob(t.path),o=new Uint8Array(await i.arrayBuffer());s({type:"answer",id:t.id,value:{bytes:o,type:i.type}},[o.buffer])}else throw new TypeError("not a question")}catch(i){s({type:"answer",id:t.id,error:_(i)??"Error"})}}function zs(t){let e={};if(t&&typeof t=="object")for(let[n,s]of Object.entries(t))(typeof s=="string"||typeof s=="number"||typeof s=="boolean")&&(e[n]=s);return e}function _t(t){let e=t;return e&&Number.isInteger(e.count)&&e.count>1&&typeof e.ms=="number"&&e.ms>0?{count:e.count,ms:e.ms}:null}function Wt(t){return typeof t=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(t)?t:void 0}function Vs(t,e){let n=new Set;for(let i of Object.values(t)){let o=i?.family;i&&typeof i=="object"&&typeof o=="string"&&n.add(o)}let s=[];for(let i of e.fonts??[]){let o=n.has(i.family)?e.font?.(i.blob):void 0;o&&s.push({family:i.family,weight:i.weight,style:i.style,blob:i.blob,bytes:o.slice()})}return s}function Ks(t,e){let n=new Set;t.binding?.table&&n.add(t.binding.table);for(let i of Object.values(t.props??{}))typeof i=="string"&&/^tbl_[0-9a-z]{26}$/.test(i)&&n.add(i);let s={};for(let i of n){let o=e.snapshot?.(i);o&&(s[i]=o)}return s}function zt(t){let e=te?.setStyle??((n,s,i,o)=>n.style.setProperty(s,i,o));e(t,"contain","strict","important"),e(t,"overflow","clip","important")}var Vt=t=>!!(t?.filter?.length||t?.sort||t?.limit),Zs=t=>t==null||t==="";function Js(t,e){if(!Zs(t))switch(e){case"number":case"currency":{let n=Number(t);return Number.isFinite(n)?n:void 0}case"datetime":{let n=Date.parse(String(t));return Number.isNaN(n)?void 0:n}case"boolean":return t===!0||String(t).toLowerCase()==="true"?1:0;case"date":return String(t).slice(0,10);default:return String(t).toLowerCase()}}function Ys(t,e){let n=Array.from(t),s=Array.from(e);for(let i=0;i<n.length&&i<s.length;i++){let o=n[i].codePointAt(0)-s[i].codePointAt(0);if(o!==0)return o}return n.length-s.length}function Xs(t,e,n,s,i){let o=t,r=Array.isArray(o?.rows)?o.rows:[],a=Array.isArray(o?.columns)?o.columns:void 0,d=l=>a?.find(c=>c.key===l)?.type??"text";for(let l of e.filter??[]){if(a&&!a.some(m=>m.key===l.column))return[];let c=l.value;if(c!==null&&typeof c=="object"){let m=c;if(c=ke(n,s,m.screen,m.key),c===void 0)return[]}let p=d(l.column);r=r.filter(m=>Pt(m[l.column],l.op,c,p,i,s))}if(e.sort){let{column:l}=e.sort,c=d(l),p=e.sort.order==="desc"?-1:1;r=r.map((m,y)=>({row:m,i:y,key:Js(m[l],c)})).sort((m,y)=>{if(m.key===void 0||y.key===void 0)return m.key===void 0&&y.key===void 0?m.i-y.i:m.key===void 0?1:-1;let A=typeof m.key=="number"&&typeof y.key=="number"?m.key-y.key:Ys(String(m.key),String(y.key));return A===0?m.i-y.i:A<0?-p:p}).map(m=>m.row)}return e.limit?r.slice(0,e.limit):r}function Kt(t,e,n,s,i){return!t||typeof t!="object"?t:{...t,rows:Xs(t,e,n,s,i)}}function Gs(t){if(!t)return"#000000";if("value"in t)return t.value;let e=t.gradient,n=[...e.stops].sort((s,i)=>s.at-i.at).map(s=>`${s.color.value} ${Math.round(s.at*1e4)/100}%`).join(", ");return e.kind==="radial"?`radial-gradient(circle, ${n})`:`linear-gradient(${e.angle??180}deg, ${n})`}function Jt(t,e,n,s=[]){let i=D("div");if(i.dataset.background="",i.style.cssText="position:absolute;inset:0;pointer-events:none",i.style.background=Gs(e),s.length){let o=[`M0 0H${n.width}V${n.height}H0Z`,...s.map(d=>`M${d.x} ${d.y}H${d.x+d.w}V${d.y+d.h}H${d.x}Z`)].join(""),r=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n.width} ${n.height}" preserveAspectRatio="none"><path fill-rule="evenodd" d="${o}"/></svg>`,a=`url("data:image/svg+xml,${encodeURIComponent(r)}")`;i.dataset.holes=String(s.length);for(let[d,l]of[["maskImage",a],["maskSize","100% 100%"],["maskRepeat","no-repeat"]])i.style[d]=l,i.style[`webkit${d[0].toUpperCase()}${d.slice(1)}`]=l}return t.prepend(i),i}function Yt(t,e=t.frame){let n=D("div");return n.dataset.element=t.id,n.style.cssText="position:absolute;box-sizing:border-box",Qs(n,e),n}function Qs(t,e){Object.assign(t.style,{left:`${e.x}px`,top:`${e.y}px`,width:`${e.w}px`,height:`${e.h}px`,transform:e.rotation?`rotate(${e.rotation}deg)`:""})}function Xt(t,e,n){if(e.type==="text"){let s=e.style??{},i=Gt(n);t.textContent=e.bind?ze(e.bind,i):Ve(e.text??"",i),Object.assign(t.style,{fontFamily:s.font?.family?`"${s.font.family}", sans-serif`:"",fontWeight:String(s.font?.weight??400),fontStyle:s.font?.style??"normal",fontSize:`${s.size??48}px`,color:s.color?.value??"#ffffff",background:s.background?.value??"",whiteSpace:"pre-wrap",textAlign:{start:"left",end:"right"}[s.align??"start"]??s.align??"left",display:"flex",flexDirection:"column",justifyContent:{top:"flex-start",middle:"center",bottom:"flex-end"}[s.verticalAlign??"top"],lineHeight:s.lineHeight?String(s.lineHeight):"",opacity:s.opacity!==void 0?String(s.opacity):"",textDecorationLine:s.decoration?.length?s.decoration.join(" "):"",textTransform:s.transform&&s.transform!=="none"?s.transform:"",letterSpacing:s.letterSpacing?`${s.letterSpacing}em`:"",textShadow:s.shadow?`${s.shadow.x??0}px ${s.shadow.y??2}px ${s.shadow.blur??4}px ${s.shadow.color?.value??"#000000"}`:""})}else e.type==="shape"&&ei(e)?(t.style.opacity=e.opacity!==void 0?String(e.opacity):"",t.append(ti(e))):e.type==="shape"?Object.assign(t.style,{background:e.fill?.value??"",opacity:e.opacity!==void 0?String(e.opacity):"",border:e.stroke?.width?`${e.stroke.width}px ${e.stroke.dash??"solid"} ${e.stroke.color?.value??"#000"}`:"",borderRadius:e.shape==="ellipse"?"50%":e.radius?`${e.radius}px`:"",overflow:e.clip!==!1?"hidden":""}):e.type==="library"&&(t.style.overflow="hidden",t.style.background=e.playback?.background?.value??"")}function Gt(t){return{screen:t?.screen??null,timeZone:t?.timeZone,tables:new Proxy({},{get:(e,n)=>typeof n=="string"?t?.snapshot?.(n):void 0})}}var ei=t=>t.shape==="line"||t.shape==="arrow"||t.shape==="polygon";function ti(t){let e=ye("svg");e.setAttribute("width","100%"),e.setAttribute("height","100%"),e.style.cssText="position:absolute;inset:0;overflow:visible";let n=t.frame.w,s=t.frame.h,i=t.stroke?.width??(t.shape==="polygon"?0:4),r=(t.points?.length?t.points:t.shape==="polygon"?[[.5,0],[1,1],[0,1]]:[[0,.5],[1,.5]]).map(([c,p])=>[c*n,p*s]),a=t.stroke?.color?.value??"#000000",d=t.stroke?.dash==="dashed"?`${i*3} ${i*2}`:t.stroke?.dash==="dotted"?`0 ${i*2}`:"",l=ye(t.shape==="polygon"?"polygon":"polyline");if(l.setAttribute("points",r.map(c=>`${c[0]},${c[1]}`).join(" ")),l.setAttribute("fill",t.shape==="polygon"?t.fill?.value??"none":"none"),i&&(l.setAttribute("stroke",a),l.setAttribute("stroke-width",String(i)),l.setAttribute("stroke-linecap",t.stroke?.dash==="dotted"?"round":"butt"),l.setAttribute("stroke-linejoin","round"),d&&l.setAttribute("stroke-dasharray",d)),e.append(l),t.shape==="arrow"&&r.length>=2){let[c,p]=[r[r.length-2],r[r.length-1]],m=Math.atan2(p[1]-c[1],p[0]-c[0]),y=Math.max(10,i*3.5),A=k=>`${p[0]-y*Math.cos(m)+k*y*.5*Math.sin(m)},${p[1]-y*Math.sin(m)-k*y*.5*Math.cos(m)}`;l.setAttribute("points",[...r.slice(0,-1),[p[0]-y*.8*Math.cos(m),p[1]-y*.8*Math.sin(m)]].map(k=>`${k[0]},${k[1]}`).join(" "));let f=ye("polygon");f.setAttribute("points",`${p[0]},${p[1]} ${A(1)} ${A(-1)}`),f.setAttribute("fill",a),e.append(f)}return e}async function Qt(t,e,n,s,i,o,r,a=()=>{},d){let l=t.appInfo??n.info;zt(e),l?.id&&(e.dataset.app=l.id);let c=(x,C)=>(e.dataset.appFailed=x,{failed:x,...C?{error:C}:{}});if(!Dt(l?.requires))return c("unsupported");let p=s.appHost;if(p&&!p.custom&&!oe(l))return c("unsupported");if(Ut(d,l))return qt(t,e,n,s,i,o,r,a,d,c);let m=t.app;if(p?.held?.(m))return e.dataset.appHeld="",{held:!0};await p?.mounting?.(m,l?.id);let y=!1,A=()=>{y||(y=!0,p?.unmounted?.(m))},f;try{f=await jt(n.hash,n.bytes,l)}catch(x){return A(),x instanceof F?c(x.reason,x.error):c("load")}let k=f.tag,b=l?.id?s.controllerFor?.({id:l.id,instance:t.id,capability:l.player??"read"}):void 0,M,w=new Promise(x=>{M=x}),T,N={app:l?.id??t.app.id,element:k,props:St(t.props??{},Gt(s)),place:s.place,screen:s.screen??null,timeZone:s.timeZone??Intl.DateTimeFormat().resolvedOptions().timeZone,locale:s.locale??navigator.language,preview:!!s.preview,now:i.now,startedAt:i.startedAt,ready:()=>M(),data:(x,C)=>s.data?s.data.get(x,C):Promise.reject(new Error("no data service")),kept:(x,C)=>s.data?.kept?.(x,C),image:x=>s.data?s.data.image(x):Promise.reject(new Error("no data service")),snapshot:x=>{let C=s.snapshot?.(x)??null;if(!C||t.binding?.table!==x||!Vt(t.binding))return C;let Q=Kt(C,t.binding,s.screen,N.timeZone,i.now());return T?.from===C&&T.rows.length===Q.rows.length&&T.rows.every((Cn,Rn)=>Cn===Q.rows[Rn])?T.snapshot:(T={from:C,rows:Q.rows,snapshot:Q},Q)},...t.binding?{binding:t.binding}:{},...t.sensors?.includes("impressions")&&s.impressions?.measuring?{impressions:{now:()=>s.impressions.now(),on:x=>s.impressions.on(x)}}:{},...b?{controller:b}:{}},j;try{j=D(k),f.shape==="class"?f.attach(j,N):j.wyndlass=N}catch(x){return b?.dispose(),A(),c("error",_(x))}j.style.cssText="display:block;width:100%;height:100%",p?.contained?.(e,j);let U=new Ae(j,{onStop:a,dispose:()=>{b?.dispose(),A()}});e.append(j),r(U);let G=await Promise.race([w.then(()=>!1),new Promise(x=>setTimeout(()=>x(!0),o))]);return U.update(N.now()),{app:U,late:G}}var Zt=new Set;async function en(t,e){for(let n of t){let s=`${n.family}/${n.weight??400}/${n.style??"normal"}/${n.blob}`,i=e(n.blob);if(!(Zt.has(s)||!i||typeof FontFace>"u"))try{let o=new FontFace(n.family,i,{weight:String(n.weight??400),style:n.style??"normal"});document.fonts.add(await o.load()),Zt.add(s)}catch{}}}function ae(t){let e=[],n=null;for(let s of t){let i=s.start,o=Math.max(0,i.kind==="at"?i.ms:i.kind==="withPrevious"?(n?.startMs??0)+(i.offsetMs??0):(n?.endMs??0)+(i.offsetMs??0));n={startMs:o,endMs:o+s.durationMs},e.push(n)}return{clips:e,autoDurationMs:e.reduce((s,i)=>Math.max(s,i.endMs),0)}}var ni=new Set(["x","y","scaleX","scaleY","rotation","opacity","reveal"]);function si(t,{pageBelow:e=!1}={},n=null){let s=o=>({path:"page",reason:o});if(!n)return s("no-media-layer");if(e)return s("page-element-beneath");let i=t.slice(0,-1).filter(o=>o.type==="shape"&&o.clip!==!1);if(i.some(o=>o.shape!=="rectangle"||(o.radius??0)>0))return s("clipped-to-shape");if(i.some(o=>(o.rotation??0)%360!==0))return s("clipped-by-rotated-shape");if(!n.animates){if(t.some(o=>(o.rotation??0)%360!==0))return s("rotated");if(t.some(o=>(o.opacity??1)<1))return s("translucent");if(t.some(o=>(o.animated??[]).some(r=>ni.has(r))))return s("animated")}return{path:"native"}}var ii={fade:["opacity"],slide:["x","opacity"],scale:["scaleX","scaleY","opacity"],pulse:["scaleX","scaleY"],loop:["scaleX","scaleY"],wipe:["reveal"]};function tn(t,e,n,s){let i=e&&t.overrides?.[e]||{},o=new Map;for(let p of t.animation?.clips??[]){let m=[...p.preset?ii[p.preset]??[]:[],...(p.lanes??[]).map(y=>y.property)];o.set(p.element,[...o.get(p.element)??[],...m])}let r=p=>p.type==="library"&&!!p.source&&!!n[p.source.item.id]?.video,a=p=>!r(p)&&!(p.type==="shape"&&!p.fill&&!(p.stroke?.width??0)),d=[],l=(p,m,y,A)=>{for(let f of p){let k=i[f.id];if(k?.hidden??f.hidden??!1)continue;let b=k?.frame??f.frame,M={x:m.x+b.x,y:m.y+b.y,w:b.w,h:b.h},w={type:f.type==="shape"?"shape":"library",rotation:b.rotation??0,opacity:f.opacity??f.style?.opacity??1,animated:o.get(f.id)??[],...f.type==="shape"?{shape:f.shape,radius:f.radius,clip:f.clip}:{}};d.push({e:f,box:M,chain:[...y,w],ancestors:A}),f.type==="shape"&&l(f.children??[],M,[...y,w],[...A,f])}};l(t.elements,{x:0,y:0},[],[]);let c=(p,m)=>p.x<m.x+m.w&&m.x<p.x+p.w&&p.y<m.y+m.h&&m.y<p.y+p.h;return d.flatMap((p,m)=>{if(!r(p.e))return[];let y=d.slice(0,m).some(A=>a(A.e)&&c(A.box,p.box));return[{element:p.e.id,path:si(p.chain,{pageBelow:y},s),box:p.box}]})}var ri=[0,0,1,1];function tt(t,e){if(e<=0)return 0;if(e>=1)return 1;let[n,s,i,o]=t;if(n===s&&i===o)return e;let r=3*n,a=3*(i-n)-r,d=1-r-a,l=3*s,c=3*(o-s)-l,p=1-l-c,m=w=>((d*w+a)*w+r)*w,y=w=>((p*w+c)*w+l)*w,A=w=>(3*d*w+2*a)*w+r,f=1e-7,k=e;for(let w=0;w<8;w++){let T=m(k)-e;if(Math.abs(T)<f)return y(k);let N=A(k);if(Math.abs(N)<1e-6)break;k-=T/N}let b=0,M=1;for(k=e;b<M;){let w=m(k);if(Math.abs(w-e)<f||(e>w?b=k:M=k,k=(M-b)/2+b,M-b<f))break}return y(k)}function Ee(t,e){let n=t.keyframes;if(e<=n[0].t)return n[0].v;let s=n[n.length-1];if(e>=s.t)return s.v;let i=0;for(;e>=n[i+1].t;)i++;let o=n[i],r=n[i+1];return o.v+(r.v-o.v)*tt(o.easing??ri,(e-o.t)/(r.t-o.t))}function nn(t,e,n){let s={x:t.x,y:t.y,scaleX:1,scaleY:1,rotation:0,opacity:1};for(let i of e??[])s[i.property]=Ee(i,n);return s}var de={linear:[0,0,1,1],ease:[.25,.1,.25,1],"ease-in":[.42,0,1,1],"ease-out":[0,0,.58,1],"ease-in-out":[.42,0,.58,1]};function sn(t){if(!t)return de.ease;if(de[t])return de[t];let n=/^cubic-bezier\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)$/.exec(t)?.slice(1).map(Number);return n&&n.every(Number.isFinite)?n:de.ease}function oi(t){let e=typeof t=="string"?/^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})?$/.exec(t):null;if(!e)return null;let n=parseInt(e[1],16);return[n>>16&255,n>>8&255,n&255,e[2]?parseInt(e[2],16)/255:1]}var an={fade:()=>({opacity:0}),slide:t=>({x:t.x-t.w*.1,opacity:0}),scale:()=>({scaleX:.8,scaleY:.8,opacity:0}),wipe:()=>({reveal:0}),typewriter:()=>({chars:0}),"count-up":()=>({count:0})},zo=Object.keys(an);var ai={x:t=>t.x,y:t=>t.y,scaleX:()=>1,scaleY:()=>1,rotation:()=>0,opacity:()=>1,reveal:()=>1,chars:()=>1,count:()=>1},rn=1e3,di=de["ease-in-out"];function dn(t,e){let n=ae(t).clips,s=new Map,i=new Set,o=a=>{let d=s.get(a);return d||s.set(a,d={keys:new Map,colour:null,loops:[],tickers:[]}),d},r=(a,d,l)=>{let c=o(a).keys,p=c.get(d)??[];c.set(d,p),p.push(l)};t.forEach((a,d)=>{let l=e.get(a.element);if(!l)return;let{startMs:c,endMs:p}=n[d],m=sn(a.easing);if(a.lanes?.length){for(let f of a.lanes)for(let k of f.keyframes){let b={t:c+k.at,easing:sn(k.easing??a.easing)};if(f.property==="color"){let M=oi(k.value);if(!M)continue;let w=o(a.element);w.colour??(w.colour=[[],[],[],[]]),M.forEach((T,N)=>w.colour[N].push({...b,v:T}))}else r(a.element,f.property,{...b,v:Number(k.value)})}return}let y=a.preset??"fade";if(a.kind==="emphasis"){y==="pulse"?(r(a.element,"pulse",{t:c,v:1,easing:m}),r(a.element,"pulse",{t:(c+p)/2,v:1.05,easing:m}),r(a.element,"pulse",{t:p,v:1})):y==="loop"?o(a.element).loops.push({startMs:c,endMs:p}):y==="ticker"?o(a.element).tickers.push({startMs:c,endMs:p}):i.add(y);return}let A=an[y];if(!A){i.add(y);return}for(let[f,k]of Object.entries(A(l))){let b=ai[f](l),[M,w]=a.kind==="enter"?[k,b]:[b,k];r(a.element,f,{t:c,v:M,easing:m}),r(a.element,f,{t:p,v:w})}});for(let a of s.values()){for(let d of a.keys.values())d.sort((l,c)=>l.t-c.t);for(let d of a.colour??[])d.sort((l,c)=>l.t-c.t)}return{motions:s,unplayed:[...i].sort()}}var li=["x","y","scaleX","scaleY","rotation","opacity"];function ln(t,e,n,s=!1){if(s)return pi(t,e,n);let i=(d,l)=>{let c=t?.keys.get(d);return c?Ee({property:"x",keyframes:c},n):l},o=nn(e,li.flatMap(d=>t?.keys.get(d)?[{property:d,keyframes:t.keys.get(d)}]:[]),n),r=i("pulse",1);for(let d of t?.loops??[])if(n>=d.startMs&&n<d.endMs){let l=(n-d.startMs)%rn/rn;r*=1+.05*tt(di,l<.5?l*2:2-l*2)}let a=t?.tickers.find(d=>n>=d.startMs&&n<d.endMs);return{...o,reveal:i("reveal",1),chars:i("chars",1),count:i("count",1),pulse:r,scroll:a?(n-a.startMs)/(a.endMs-a.startMs):null,color:t?.colour?t.colour.map(d=>Ee({property:"x",keyframes:d},n)):null}}function on(t,e){if(e<t[0].t)return t[0].v;let n=0;for(;n+1<t.length&&t[n+1].t<=e;)n++;return n+1<t.length?t[n+1].v:t[n].v}function pi(t,e,n){let s=(i,o)=>{let r=t?.keys.get(i);return r?on(r,n):o};return{x:s("x",e.x),y:s("y",e.y),scaleX:s("scaleX",1),scaleY:s("scaleY",1),rotation:s("rotation",0),opacity:s("opacity",1),reveal:s("reveal",1),chars:s("chars",1),count:s("count",1),pulse:1,scroll:null,color:t?.colour?t.colour.map(i=>on(i,n)):null}}function ci(t,e){let n=Array.from(t);return e>=1?t:n.slice(0,Math.max(0,Math.round(e*n.length))).join("")}function ui(t,e){return e>=1?t:t.replace(/\d+(?:\.\d+)?/g,n=>(Number(n)*Math.max(0,e)).toFixed((n.split(".")[1]??"").length))}function pn(t,e,n){if(t<=1||e<=0)return[{page:0,startMs:0,endMs:n}];let s=[];for(let i=0;i*e<n&&i<1e3;i++)s.push({page:i%t,startMs:i*e,endMs:Math.min((i+1)*e,n)});return s}function cn(t,e,n,s){t.style.left=`${n.x}px`,t.style.top=`${n.y}px`,t.style.transform=`rotate(${(e.frame.rotation??0)+n.rotation}deg) scale(${n.scaleX*n.pulse}, ${n.scaleY*n.pulse})`,t.style.opacity=String((e.opacity??e.style?.opacity??1)*n.opacity),t.style.clipPath=n.reveal>=1?"":`inset(0 ${(1-Math.max(0,n.reveal))*100}% 0 0)`;let i=n.color?`rgba(${n.color.slice(0,3).map(o=>Math.round(o)).join(",")},${n.color[3]})`:null;if(e.type==="text"&&s!==void 0){let o=ui(ci(s,n.chars),n.count);if(n.scroll!==null){let r=t.querySelector("[data-ticker]");r||(r=document.createElement("span"),r.dataset.ticker="",r.style.cssText="display:inline-block;white-space:nowrap;will-change:transform",t.replaceChildren(r),t.style.overflow="hidden"),r.textContent!==o&&(r.textContent=o);let a=e.frame.w,d=r.scrollWidth;r.style.transform=`translateX(${a-n.scroll*(a+d)}px)`}else(t.textContent!==o||t.querySelector("[data-ticker]"))&&(t.textContent=o,t.style.overflow="");i&&(t.style.color=i)}else if(e.type==="shape"&&i){let o=t.querySelector("polygon, polyline");o&&e.shape==="polygon"?o.setAttribute("fill",i):o||(t.style.background=i)}}function hi(t){let e=t>>>0;return()=>{e=e+1831565813|0;let n=Math.imul(e^e>>>15,1|e);return n=n+Math.imul(n^n>>>7,61|n)^n,((n^n>>>14)>>>0)/4294967296}}async function un(t,e,n,s){let i=parseInt((await We(_e(`${t}
2
+ "use strict";(()=>{var On=Object.defineProperty;var $n=(t,e,n)=>e in t?On(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var h=(t,e,n)=>$n(t,typeof e!="symbol"?e+"":e,n);var wt=Reflect.apply,Ne=Object.getOwnPropertyDescriptor,fe=Object.getPrototypeOf,R=globalThis,v=(t,e,n)=>wt(t,e,n),O=(t,e)=>t?Ne(t,e)?.get:void 0,Oe=(t,e)=>t?Ne(t,e)?.set:void 0,P=t=>R[t]?.prototype,vt=(t,e,n)=>wt(t,e,n);var ji=Date.now,Fi=new TextEncoder,Hi=new TextDecoder,Bi=TextEncoder.prototype.encode,Ui=TextDecoder.prototype.decode;var qi=O(fe(Uint8Array.prototype),"length");var Dn=Uint8Array,jn=t=>new Dn(t),Fn=[];for(let t=0;t<256;t++)Fn[t]=(t<16?"0":"")+t.toString(16);var Hn=Object.create(null);for(let t=0;t<64;t++)Hn["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[t]]=t;var lt=R.crypto?.subtle,$e=lt?fe(lt):void 0,Re=R.crypto,Bn=Re?fe(Re).getRandomValues:void 0,_i=$e?.digest,Wi=$e?.importKey,zi=$e?.sign;var kt=t=>v(Bn,Re,[jn(t)]),Vi={setTimeout:R.setTimeout,clearTimeout:R.clearTimeout,setInterval:R.setInterval,clearInterval:R.clearInterval};var Ki=R.fetch;var ne=P("MessagePort"),Zi=ne&&ne.postMessage,Ji=Oe(ne,"onmessage"),Yi=ne&&ne.start,De=P("MessageEvent"),Xi=O(De,"data"),Gi=O(De,"ports"),Qi=O(De,"source");var xt=P("EventTarget"),er=xt?.addEventListener,tr=xt?.removeEventListener;var nr=R.Worker,ue=P("Worker"),sr=ue&&ue.postMessage,ir=Oe(ue,"onmessage"),rr=Oe(ue,"onerror");var pt=R.Blob,he=R.URL,ct=he?.createObjectURL,Un=he?.revokeObjectURL,je=pt&&ct?{make:(t,e)=>v(ct,he,[new pt(t,{type:e})]),revoke:t=>v(Un,he,[t])}:void 0,ee=R.customElements,Fe=ee?fe(ee):void 0,qn=Fe?.define,_n=Fe?.get,He=ee&&Fe?{define:(t,e)=>v(qn,ee,[t,e]),get:t=>v(_n,ee,[t])}:void 0,At=R.document,me=P("Document"),Wn=me?.createElement,zn=me?.createElementNS,Vn=WeakSet.prototype.add,or=WeakSet.prototype.has,Be=new WeakSet,Kn=new WeakSet,ge=(t,e)=>(v(Vn,t,[e]),e);function D(t){return ge(Be,v(Wn,At,[t]))}var ye=t=>ge(Be,v(zn,At,["http://www.w3.org/2000/svg",t]));var K=t=>ge(Kn,t);var se=P("Node"),Ue=P("Element"),be=P("HTMLElement"),Z=t=>O(se,t),ut=Z("parentNode"),Zn=Z("firstChild"),Jn=Z("lastChild"),Yn=Z("nextSibling"),Xn=Z("nodeType"),Gn=Z("childNodes"),Qn=O(Ue,"localName"),es=se?.appendChild,ts=se?.insertBefore,ns=se?.removeChild,_=Ue,ss=_?.getAttribute,is=_?.getAttributeNames,rs=_?.setAttribute,os=_?.removeAttribute,as=_?.matches,ds=_?.attachShadow,ls=_?.querySelectorAll,ps=P("HTMLDialogElement")?.close,cs=O(P("NodeList"),"length"),us=P("NodeList")?.item,we=P("MutationRecord"),hs=O(we,"type"),ms=O(we,"target"),fs=O(we,"addedNodes"),gs=O(we,"removedNodes"),ht=R.MutationObserver,ys=P("MutationObserver")?.observe,Mt=me?Ne(me,"adoptedStyleSheets"):void 0,mt=Mt?.get,ft=Mt?.set,bs=R.requestAnimationFrame,gt=O(be,"style"),ve=P("CSSStyleDeclaration"),ws=ve?.getPropertyValue,vs=ve?.getPropertyPriority,ks=ve?.setProperty,yt=be?.showPopover,bt=be?.hidePopover,xs=O(P("Event"),"target"),As=P("Event")?.stopImmediatePropagation,ce=t=>{let e=[];for(let n=0,s=v(cs,t,[]);n<s;n++)e[n]=v(us,t,[n]);return e},te=ht&&se&&Ue&&be&&ve?{parent:t=>v(ut,t,[]),first:t=>v(Zn,t,[]),last:t=>v(Jn,t,[]),next:t=>v(Yn,t,[]),isElement:t=>v(Xn,t,[])===1,name:t=>v(Qn,t,[]),children:t=>ce(v(Gn,t,[])),append:(t,e)=>v(es,t,[e]),insert:(t,e,n)=>v(ts,t,[e,n]),remove:t=>{let e=v(ut,t,[]);e&&v(ns,e,[t])},attribute:(t,e)=>v(ss,t,[e]),attributes:t=>v(is,t,[]),setAttribute:(t,e,n)=>v(rs,t,[e,n]),removeAttribute:(t,e)=>v(os,t,[e]),matches:(t,e)=>v(as,t,[e]),all:(t,e)=>ce(v(ls,t,[e])),closeDialog:t=>v(ps,t,[]),shadow:t=>v(ds,t,[{mode:"closed"}]),style:(t,e)=>{let n=v(gt,t,[]);return[v(ws,n,[e]),v(vs,n,[e])]},setStyle:(t,e,n,s="")=>v(ks,v(gt,t,[]),[e,n,s]),observer:t=>new ht(t),observe:(t,e,n)=>v(ys,t,[e,n]),record:t=>({type:v(hs,t,[]),target:v(ms,t,[]),added:ce(v(fs,t,[])),removed:ce(v(gs,t,[]))}),adopted:mt&&ft?{count:t=>v(mt,t,[]).length,clear:t=>v(ft,t,[[]])}:void 0,frame:t=>v(bs,globalThis,[t]),popover:yt&&bt?{show:t=>v(yt,t,[]),hide:t=>v(bt,t,[])}:void 0,target:t=>v(xs,t,[]),stop:t=>v(As,t,[])}:void 0;function qe(t){if(ge(Be,t),te)for(let e of te.children(t))te.isElement(e)&&qe(e);return t}var _e=t=>new TextEncoder().encode(t);function Ms(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),n="";for(let s of e)n+=s.toString(16).padStart(2,"0");return n}async function We(t){return Ms(await crypto.subtle.digest("SHA-256",t))}var Es=["name","type","tags","location","site","district","region","organization","path","timeZone"],Ss=["text","date","currency","truncate"],Et={site:"SITE",district:"DISTRICT",region:"REGION",organization:"ORGANIZATION"};function ke(t,e,n,s){if(!t)return n==="timeZone"&&e?e:void 0;if(n==="fields")return s&&t.fields?.[s]||void 0;let i=t.path??[];switch(n){case"name":return t.name||void 0;case"type":return t.type||void 0;case"tags":return t.tags?.length?t.tags.join(", "):void 0;case"location":return i.at(-1)?.name;case"path":return i.length?i.map(o=>o.name).join(" \u203A "):void 0;case"timeZone":return e||void 0;default:return Et[n]?[...i].reverse().find(o=>o.kind===Et[n])?.name:void 0}}function Ts(t,e){if(t==null||t==="")return;let n=String(t);if(e==="currency"){let s=Number(t);return Number.isFinite(s)?s.toFixed(2):n}if(e==="date"){let s=Date.parse(n);return Number.isNaN(s)?n:new Date(s).toISOString().slice(0,10)}return e==="truncate"&&n.length>40?n.slice(0,39)+"\u2026":n}function ze(t,e){return(t.source==="screen"?ke(e.screen,e.timeZone,t.field,t.key):Ts(e.tables?.[t.table]?.rows?.[t.row??0]?.[t.column],t.format))??t.fallback??""}function Ps(t){let e=/^\s*(?:screen\.(fields\.[a-z][A-Za-z0-9]{0,31}|[A-Za-z]+)|table\.([A-Za-z0-9_-]+)\[(\d+)\](?:\.([A-Za-z0-9_]+)|\["((?:[^"\\]|\\.)*)"\]))\s*((?:\|\s*(?:[a-z]+|"(?:[^"\\]|\\.)*")\s*)*)$/.exec(t);if(!e)return null;let n=[...e[6].matchAll(/\|\s*([a-z]+|"((?:[^"\\]|\\.)*)")/g)],s=n.find(o=>o[1].startsWith('"'))?.[2]?.replace(/\\(.)/g,"$1"),i=n.find(o=>!o[1].startsWith('"'))?.[1];return e[1]!==void 0?i?null:e[1].startsWith("fields.")?{source:"screen",field:"fields",key:e[1].slice(7),...s!==void 0?{fallback:s}:{}}:Es.includes(e[1])?{source:"screen",field:e[1],...s!==void 0?{fallback:s}:{}}:null:i&&!Ss.includes(i)?null:{source:"table",table:e[2],row:Number(e[3]),column:e[4]??e[5].replace(/\\(.)/g,"$1"),...i?{format:i}:{},...s!==void 0?{fallback:s}:{}}}function Ve(t,e){return t.replace(/\{{2}([^{}]*)\}{2}/g,(n,s)=>{let i=Ps(s);return i?ze(i,e):n})}function St(t,e){return Object.fromEntries(Object.entries(t).map(([n,s])=>[n,typeof s=="string"?Ve(s,e):s]))}var Ke=t=>t==null||t==="";function Pt(t,e,n,s,i,o){return Ls(t,e,n,s,{now:i,timeZone:o})}function Ls(t,e,n,s,i){if(n===null&&(e==="eq"||e==="ne"))return e==="eq"?Ke(t):!Ke(t);if(Ke(t)||n===null||n===void 0)return!1;switch(s){case"number":case"currency":{let o=Number(t),r=Number(n);return typeof n!="boolean"&&n!==""&&Number.isFinite(o)&&Number.isFinite(r)&&Ze(e,o-r)}case"boolean":{let o=n===!0||n==="true"?!0:n===!1||n==="false"?!1:void 0;return o!==void 0&&(e==="eq"?t===o:e==="ne"?t!==o:!1)}case"date":{let o=n==="today"?Is(i.now,i.timeZone).date:String(n),r=String(t).slice(0,10);return/^\d{4}-\d{2}-\d{2}$/.test(o)&&Ze(e,r<o?-1:r>o?1:0)}case"datetime":{let o=Date.parse(String(t)),r=n==="now"?i.now:Date.parse(String(n));return!Number.isNaN(o)&&!Number.isNaN(r)&&Ze(e,o-r)}default:{let o=String(t).toLowerCase(),r=String(n).toLowerCase();return e==="eq"?o===r:e==="ne"?o!==r:e==="contains"?o.includes(r):!1}}}function Ze(t,e){switch(t){case"eq":return e===0;case"ne":return e!==0;case"lt":return e<0;case"lte":return e<=0;case"gt":return e>0;case"gte":return e>=0;default:return!1}}var Tt=new Map;function Is(t,e){let n=Tt.get(e);n||(n=new Intl.DateTimeFormat("en-US",{timeZone:e,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),Tt.set(e,n));let s=Object.fromEntries(n.formatToParts(new Date(t)).map(i=>[i.type,i.value]));return{date:`${s.year}-${s.month}-${s.day}`,minute:Number(s.hour)%24*60+Number(s.minute),day:String(s.weekday).slice(0,3).toLowerCase()}}var Lt=(t,e)=>!t||(t.from===void 0||t.from<=e)&&(t.until===void 0||e<t.until);var ie=class{constructor(e,n,s,i,o){h(this,"renderer",e);h(this,"wrap",i);h(this,"onShown",o);h(this,"timer");h(this,"turns",0);h(this,"turnAt");h(this,"left");h(this,"stopped",!1);h(this,"share");h(this,"pages");this.pages=n.pages??[],this.share=n.durationMs/Math.max(1,this.pages.length),this.turnAt=s+this.share,this.arm()}arm(){clearTimeout(this.timer);let e=this.pages.length;if(this.stopped||this.left!==void 0||e<2||!this.wrap&&this.turns+1>=e)return;let n=this.pages[(this.turns+1)%e];this.renderer.prepare?.(n).catch(()=>{}),this.timer=setTimeout(()=>{this.turn(n)},Math.min(Math.max(0,this.turnAt-Date.now()),2**31-1))}async turn(e){try{await this.renderer.show(e)}catch{}this.stopped||(this.turns++,this.turnAt+=this.share,this.onShown?.(e),this.arm())}pause(){this.stopped||this.left!==void 0||(clearTimeout(this.timer),this.left=Math.max(0,this.turnAt-Date.now()))}resume(){this.stopped||this.left===void 0||(this.turnAt=Date.now()+this.left,this.left=void 0,this.arm())}stop(){if(!this.stopped){this.stopped=!0,clearTimeout(this.timer);for(let e of this.pages)this.renderer.release?.(e)}}};var Je=class extends Error{constructor(n,s){super(s);h(this,"code",n);this.name="MediaError"}};function Ye(t){return t instanceof Je?{code:t.code.slice(0,64),message:t.message.slice(0,1e3)}:{code:"not-shown",message:String(t instanceof Error?t.message:t??"").slice(0,1e3)}}var It=t=>t.blob.contentType.startsWith("video/");var Xe="position:absolute;inset:0;width:100%;height:100%;object-fit:contain",Rs=1,xe=class{constructor(e,n={}){h(this,"container",e);h(this,"options",n);h(this,"prepared",new Map);h(this,"shown",null);h(this,"endListeners",new Set);h(this,"itemListeners",new Set);h(this,"appListeners",new Set);h(this,"held",null);h(this,"drawn",0);h(this,"shownHash",null);let s=n.media;s&&(document.documentElement.style.background="transparent",document.body.style.background="transparent",e.style.background="#000",s.onEnd((i,o)=>{let r=this.shown;if(r?.mediaId===i)for(let a of this.endListeners)a(r.slide,o)}))}onMediaEnd(e){this.endListeners.add(e)}onElementItem(e){this.itemListeners.add(e)}onApp(e){this.appListeners.add(e)}app(e){for(let n of this.appListeners)n(e);this.options.onApp?.(e)}prepare(e){let n=this.prepared.get(e);if(n)return n.ready;let s;try{s=this.make(e)}catch(i){return Promise.reject(i)}return this.prepared.set(e,s),s.ready.catch(()=>{this.prepared.get(e)===s&&(this.prepared.delete(e),this.dispose(s))}),s.ready}release(e){let n=this.prepared.get(e);n&&(this.prepared.delete(e),this.dispose(n))}async show(e){await this.prepare(e);let n=this.prepared.get(e);if(!n)throw new Error("the slide was released before it was shown");this.prepared.delete(e);let s=this.shown,i=this.options.media,o=s?Rt(e):void 0;if(n.kind==="layout"&&o===void 0){await H(()=>{s?this.container.insertBefore(n.el,s.el):this.container.append(n.el),this.shown=n,this.shownHash=e.blob.hash}),this.held=[],await n.view.start().catch(()=>{});let r=Promise.resolve();return await H(()=>{this.shown===n&&(n.el.style.visibility="",s?.el.remove(),i&&this.paint("transparent"),i&&s?.mediaId&&(r=i.show([],[s.mediaId],Date.now())))}),await r.catch(()=>{}),s&&this.dispose(s),this.releaseHeld(),"cut"}if(o!==void 0&&i&&n.kind==="native"&&s.kind==="native"){if(n.fadesIn&&!n.queued){let r=Promise.resolve();return await H(()=>{n.el.style.visibility="",this.container.insertBefore(n.el,s.el),r=i.show([n.mediaId],[],Date.now()),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{}),Ns(o).then(async()=>{await i.show([],[s.mediaId],Date.now()).catch(()=>{}),s.el.remove(),this.dispose(s)}),"crossfade"}}else if(o!==void 0&&i&&n.kind==="native"){let r=Promise.resolve();return await H(()=>{n.el.style.visibility="",this.container.insertBefore(n.el,s.el),r=i.show([n.mediaId],[],Date.now()),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{}),this.paint("transparent"),Nt(s.el,1,0,o).then(()=>{s.el.remove(),this.dispose(s)}),"crossfade"}else if(o!==void 0)return await H(()=>{n.el.style.visibility="",n.el.style.opacity="0",this.container.append(n.el),n.kind==="video"&&Ge(n.el),this.shown=n,this.shownHash=e.blob.hash}),n.view&&(this.held=[],n.view.start().then(()=>this.releaseHeld())),Nt(n.el,0,1,o).then(async()=>{s.el.remove(),this.shown===n&&this.paint(i?"#000":""),i&&s.mediaId&&await i.show([],[s.mediaId],Date.now()).catch(()=>{}),this.dispose(s)}),"crossfade";if(i&&n.kind==="native"){let r=Promise.resolve();await H(()=>{n.el.style.visibility="",s?this.container.insertBefore(n.el,s.el):this.container.prepend(n.el),r=i.show([n.mediaId],s?.mediaId?[s.mediaId]:[],Date.now()),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{}),await H(()=>{this.shown===n&&(s?.el.remove(),this.paint("transparent"))})}else{let r=Promise.resolve();await H(()=>{n.el.style.visibility="",n.el.parentNode!==this.container&&this.container.append(n.el),s?.el.remove(),this.paint(i?"#000":""),i&&s?.mediaId&&(r=i.show([],[s.mediaId],Date.now())),n.kind==="video"&&Ge(n.el),this.shown=n,this.shownHash=e.blob.hash}),await r.catch(()=>{})}return s&&this.dispose(s),"cut"}paint(e){this.options.background?this.options.background(e):this.container.style.background=e}standby(){let e=this.shown;this.shown=null,this.shownHash=null,this.paint(this.options.media?"#000":""),e&&(e.mediaId&&this.options.media?.show([],[e.mediaId],Date.now()).catch(()=>{}),this.dispose(e))}pause(){let e=this.shown;e?.view?.pause(),e?.kind==="video"?e.el.pause():e?.mediaId&&this.options.media?.control(e.mediaId,"pause").catch(()=>{})}resume(){let e=this.shown;e?.view?.resume(),e?.kind==="video"?Ge(e.el):e?.mediaId&&this.options.media?.control(e.mediaId,"play").catch(()=>{})}make(e){if(e.layout){let{doc:r,source:a,untilPass:l}=e.layout,d=K(D("div"));d.dataset.blob=e.blob.hash,d.style.cssText=`${Xe};visibility:hidden`,this.container.append(d);let u={slide:e,kind:"layout",el:d,ready:Promise.resolve()},p=y=>this.options.path?`${this.options.path}/${y}`:y,m=String(++this.drawn);return u.view=new J(r,a,d,{media:this.options.media,path:this.options.path,endsWith:l?p(l):void 0,onItem:y=>{let A={...y,slide:y.slide?`${m}.${y.slide}`:m};this.held?this.held.push(A):this.item(A)},onPassEnd:y=>{if(l&&y===p(l)&&this.shown===u){for(let A of this.endListeners)A(e);return"hold"}return this.options.onPassEnd?.(y)},onApp:y=>this.app(y)}),u.ready=u.view.ready,u}if(!It(e)){if(!e.bytes&&!e.url)throw new Error(`no bytes for ${e.blob.hash}`);let r=e.bytes?URL.createObjectURL(new Blob([e.bytes],{type:e.blob.contentType})):void 0,a=K(D("img"));return a.src=r??e.url,a.alt="",a.dataset.blob=e.blob.hash,a.style.cssText=this.fill(),{slide:e,kind:"image",el:a,objectUrl:r,ready:a.decode()}}let n=this.options.media;if(n){let r=`m-${Rs++}`,a=K(D("div"));a.dataset.blob=e.blob.hash,a.dataset.media=r,a.style.cssText=`${Xe};visibility:hidden`;let l=this.container.getBoundingClientRect(),d={x:l.left,y:l.top,w:l.width||window.innerWidth,h:l.height||window.innerHeight},u=Rt(e),p=u!==void 0&&this.shown?.kind==="native"&&n.capability.animates&&n.capability.layers>=2,m=p?[{property:"opacity",keyframes:[{t:0,v:0},{t:u,v:1}]}]:[],y={slide:e,kind:"native",el:a,mediaId:r,fadesIn:p,ready:Promise.resolve()},A=this.options.fit??"contain";return y.ready=n.prepare({mediaId:r,blob:e.blob.hash,contentType:e.blob.contentType,box:d,z:p?1:0,loop:e.loop,muted:!1,fit:A,...p?{tracks:m}:{}}).then(f=>{f.queued&&(y.queued=!0)}),y}let s=e.url?void 0:URL.createObjectURL(new Blob([e.bytes],{type:e.blob.contentType})),i=K(D("video"));i.dataset.blob=e.blob.hash,i.style.cssText=`${this.fill()};visibility:hidden`,i.muted=!1,i.playsInline=!0,i.preload="auto",i.loop=e.loop,i.src=e.url??s,i.addEventListener("ended",()=>{if(this.shown?.el===i)for(let r of this.endListeners)r(e)}),i.addEventListener("error",()=>{if(this.shown?.el===i)for(let r of this.endListeners)r(e,`video: ${i.error?.message??"error"}`)});let o=new Promise((r,a)=>{if(i.readyState>=2)return r();i.addEventListener("loadeddata",()=>r(),{once:!0}),i.addEventListener("error",()=>a(new Error(`video: ${i.error?.message??"cannot load"}`)),{once:!0})});return this.container.append(i),{slide:e,kind:"video",el:i,objectUrl:s,ready:o}}item(e){this.options.onItem?.(e);for(let n of this.itemListeners)n(e)}releaseHeld(){let e=this.held;this.held=null,e?.length&&setTimeout(()=>{for(let n of e)this.item(n)},0)}fill(){let e=this.options.fit==="stretch"?"fill":this.options.fit??"contain";return Xe.replace("object-fit:contain",`object-fit:${e}`)}dispose(e){if(this.shown!==e){if(e.view?.dispose(),e.el.remove(),e.kind==="video"){let n=e.el;n.pause(),n.removeAttribute("src"),n.load()}e.mediaId&&this.options.media?.release(e.mediaId).catch(()=>{}),e.objectUrl&&URL.revokeObjectURL(e.objectUrl)}}};function Rt(t){if(t.transition?.type==="crossfade")return Math.max(0,Math.min(t.transition.durationMs??1e3,t.durationMs/2))}var Ns=t=>new Promise(e=>setTimeout(e,t));function Nt(t,e,n,s){return new Promise(i=>{let o=Date.now(),r=!1,a=()=>{r||(r=!0,t.style.opacity=n===1?"":String(n),i())};t.style.opacity=String(e);let l=()=>{if(r)return;let d=Math.min(1,(Date.now()-o)/s);if(d>=1)return a();t.style.opacity=String(e+(n-e)*d),requestAnimationFrame(l)};requestAnimationFrame(l),setTimeout(a,s+250)})}function Ge(t){t.play().catch(()=>(t.muted=!0,t.play())).catch(()=>{})}function H(t){return new Promise((e,n)=>{let s=!1,i=()=>{if(!s){s=!0;try{t(),e()}catch(o){n(o)}}};requestAnimationFrame(i),setTimeout(i,250)})}var re={apiVersion:2,properties:[{name:"device",description:"What the device is: id, type, shell and versions, model, resolution and orientation. Fixed while the player runs."},{name:"mode",description:"Where player-core runs: player, preview (in the CMS) or designer. In preview and the designer, commands change nothing."},{name:"timeZone",description:"The player's time zone (IANA), from the host.",event:"time.changed"},{name:"locale",description:"The player's locale (BCP 47), from the host."},{name:"connection",description:"Whether the player is connected to Wyndlass.",event:"connection.changed"},{name:"state",description:"starting, playing, paused, stopped (the standby screen), idle (nothing to play) or error.",event:"state.changed"},{name:"held",description:"A remote pause or stop in effect, by whom and since when; absent when none.",event:"state.changed"},{name:"content",description:"The manifest playing, when it was issued and switched to; absent when there is none.",event:"content.updated"},{name:"now",description:"What each zone shows: the item, its blob and content type, when it started, and its duration and position when known.",event:"item.start"},{name:"audio",description:"The player's volume (0 to 100) and whether it is muted; only when the host can set them.",event:"audio.changed"},{name:"display",description:"The display's power and brightness; only when the host can report them.",event:"display.changed"},{name:"capabilities",description:"What the host can do: audio, display power, display brightness."}],events:[{name:"state.changed",description:"The player's state changed (playing, paused, stopped, idle, error), and who changed it."},{name:"content.updated",description:"A new manifest was switched to."},{name:"item.start",description:"An item started in a zone (source start)."},{name:"item.end",description:"An item ended in a zone (source end): completed, skipped, stopped, error, replaced by new content, or finished by its app."},{name:"item.error",description:"An item could not be shown."},{name:"connection.changed",description:"The player connected to Wyndlass or lost its connection."},{name:"audio.changed",description:"The volume or mute changed, and who changed it."},{name:"display.changed",description:"The display's power or brightness changed, and who changed it."},{name:"time.changed",description:"The clock was adjusted or the time zone changed."},{name:"app.command",description:"A command for apps on the screen (apps.sendCommand); an app reacts if it knows the name."},{name:"app.failed",description:"An app on the screen could not run, or misbehaved (WP-0100): its bundle did not load, it was not an app, it needs a newer player, it kept throwing and was stopped, or it was late or overran a clip."},{name:"app.held",description:"An app version was held back on this screen until the next manifest, after the page stopped answering twice in an hour while it was on screen (WP-0100)."}],commands:[{name:"player.status",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/status",description:"Everything about the player now: device, mode, time zone, locale, connection, state (and any hold), the content playing, what each zone shows, audio, display and what the host can do. Changes nothing.",idempotent:!0},{name:"player.events",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/events",result:"player-api-results.schema.json#/$defs/events",description:"The player's recent events (the last 200), oldest first, after a sequence number: state changes, content updates, item starts and ends, errors, connection, audio, display and time. Pass next as after to poll for more. Changes nothing.",idempotent:!0},{name:"time.now",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/timeNow",description:"The player's current time, its time zone and UTC offset, and whether its clock is synchronized. Changes nothing.",idempotent:!0},{name:"player.play",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Resumes or starts playback on the screen. From a remote caller it also ends a remote pause or stop.",idempotent:!0},{name:"player.pause",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Freezes the screen on what it shows now: the loop's timer and any media stop where they are. From a remote caller it holds until a remote player.play or a restart; apps cannot resume it, and new content arrives paused.",idempotent:!0},{name:"player.stop",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Stops playback and shows the standby screen (black, display on). From a remote caller it holds until a remote player.play or a restart; apps cannot restart it, and new content arrives stopped.",idempotent:!0},{name:"player.next",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Skips to the next item in a zone (main by default); the current item ends as skipped."},{name:"player.previous",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Goes back to the previous item in a zone (main by default); the current item ends as skipped."},{name:"player.playItem",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/playItem",result:"player-api-results.schema.json#/$defs/now",description:"Shows a given item of the content playing now in a zone (main by default), then continues the loop from it. Refused for an item the playing manifest does not have."},{name:"audio.setVolume",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setVolume",result:"player-api-results.schema.json#/$defs/audio",description:"Sets the player's volume, 0 to 100. Only on players whose host can set it to a level.",needs:"audio.volume",idempotent:!0},{name:"audio.setMuted",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setMuted",result:"player-api-results.schema.json#/$defs/audio",description:"Mutes or unmutes the player. Only on players whose host can.",needs:"audio",idempotent:!0},{name:"display.setPower",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setPower",result:"player-api-results.schema.json#/$defs/display",description:"Turns the display on or to standby. Only on players whose host can. Standby blanks the screen until it is turned on.",needs:"display.power",idempotent:!0},{name:"display.setBrightness",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setBrightness",result:"player-api-results.schema.json#/$defs/display",description:"Sets the display's brightness, 0 to 100. Only on players whose host can.",needs:"display.brightness",idempotent:!0},{name:"apps.sendCommand",access:"control",apps:"none",remote:!0,params:"player-api-params.schema.json#/$defs/sendCommand",result:"player-api-results.schema.json#/$defs/delivered",description:"Sends a named command, with an optional argument, to the apps on the screen; an app that knows the name reacts. Returns how many apps listen."},{name:"app.finish",access:"control",apps:"read",remote:!1,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/none",description:"An app ends its own slot now: its item ends as finished and the loop moves on. Apps only; not a tool."}],host:[{name:"host.describe",kind:"request",description:"What the device is, its time zone, locale, connection and capabilities.",result:"player-api-host.schema.json#/$defs/describeResult"},{name:"host.time",kind:"request",description:"The host's time and time zone.",result:"player-api-common.schema.json#/$defs/timeNow"},{name:"host.getAudio",kind:"request",description:"The player's volume and mute.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.setAudio",kind:"request",description:"Sets the volume, the mute or both; answers the audio after.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.getDisplay",kind:"request",description:"The display's power and brightness.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.setDisplay",kind:"request",description:"Sets the display's power, brightness or both; answers the display after.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.adRequest",kind:"request",description:"Asks Wyndlass's ad broker for one slot of a break over the host's NATS connection (WP-0075); the result is the broker's ad-decision (ad-decision.schema.json). Only a host whose describe says ads is asked."},{name:"host.mediaPrepare",kind:"request",description:"Lays a video out behind the page and prepares it to its first frame; host.mediaState says when (ADR 0055)."},{name:"host.mediaShow",kind:"request",description:"Shows and hides prepared videos; replies once its layer has changed."},{name:"host.mediaControl",kind:"request",description:"Plays, pauses or seeks a video, or asks for its position."},{name:"host.mediaRelease",kind:"request",description:"Removes a video and frees its decoder."},{name:"host.reply",kind:"reply",description:"The host's answer to a request: result or error (unsupported, failed)."},{name:"host.connection",kind:"event",description:"The connection to Wyndlass changed."},{name:"host.timeChanged",kind:"event",description:"The clock was adjusted or the time zone changed."},{name:"host.audioChanged",kind:"event",description:"The volume or mute changed outside the player."},{name:"host.displayChanged",kind:"event",description:"The display changed outside the player."},{name:"host.mediaState",kind:"event",description:"A video was prepared, started, ended or failed, or its position was asked for."},{name:"host.presence",kind:"event",description:"What one of the host's sensors sees now: the presences in view, with random ids that never leave the page (WP-0105)."},{name:"host.sensing",kind:"event",description:"The site's sensing mode as the host applies it, and its sensors (WP-0105)."},{name:"host.serialPorts",kind:"event",description:"The host's sensor controller ports, and which are open (WP-0043)."},{name:"host.serialLine",kind:"event",description:"One line from a sensor controller, as it arrived (WP-0043)."},{name:"host.serialWrite",kind:"request",description:"Writes lines to a sensor controller, a gap apart; replies once written (WP-0043)."},{name:"host.bleWatch",kind:"request",description:"What the page listens for: beacons' iBeacon UUIDs, Eddystone namespaces and addresses; replies once the scan follows it. Sent only to a host that has sent host.bleState (WP-0044)."},{name:"host.bleAdverts",kind:"event",description:"Beacon frames that match the watch, at most four a second of each kind from each beacon (WP-0044)."},{name:"host.bleState",kind:"event",description:"The host's Bluetooth scanner: scanning, idle, off, refused, missing or failed; sent only by a host that has one (WP-0044)."},{name:"host.gpioPins",kind:"event",description:"The GPIO pins the host reads, with their values (WP-0044)."},{name:"host.gpioEdge",kind:"event",description:"A GPIO pin changed (WP-0044)."},{name:"host.mcp",kind:"relay",description:"An MCP message from ctl.dev.{deviceId}, for player-core to answer."},{name:"core.ready",kind:"core",description:"player-core is ready to answer host.mcp; until then the shell answers -32002 itself."},{name:"core.mcpReply",kind:"core",description:"player-core's answer to a host.mcp, for the shell to send back."},{name:"core.sensors",kind:"core",description:"The page's sensor controllers, channels and inputs, and the beacons it hears, whenever they change, for the heartbeat and sensors.recent (WP-0043, WP-0044)."}]};var Ot=Symbol.for("wyndlass.app.attach");var F=class extends Error{constructor(n,s){super(`the app could not run (${n})`);h(this,"reason",n);h(this,"error",s)}};function W(t){try{let e=t instanceof Error?t.name:void 0;return typeof e=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(e)?e:void 0}catch{return}}function oe(t){let e=t?.id;if(typeof e!="string"||e==="")return!0;for(let n=0;n<$t.length;n++)if(e[n]!==$t[n])return!1;return!0}var $t="com.wyndlass.";function $s(t=typeof navigator>"u"?"":navigator.userAgent){let e=/Chrom(?:e|ium)\/(\d+)/.exec(t);return e?Number(e[1]):void 0}function Dt(t,e=re.apiVersion,n=$s()){return!(t?.playerApi!==void 0&&t.playerApi>e||t?.webView!==void 0&&n!==void 0&&t.webView>n)}function Ds(t){let e;try{e=t.paging?.()}catch{return null}let n=e;return n&&Number.isInteger(n.count)&&n.count>1&&typeof n.ms=="number"&&n.ms>0?{count:n.count,ms:n.ms}:null}var Qe=new Map;function js(){let t=kt(8),e="";for(let n=0;n<8;n++)e+=Fs[t[n]%26];return e}var Fs="abcdefghijklmnopqrstuvwxyz",Hs=0;function jt(t,e,n){let s=Qe.get(t);if(!s){let i=je.make([e],"text/javascript");s=import(i).then(o=>Bs(o,n),o=>{throw o instanceof F?o:new F("load",W(o))}).finally(()=>je.revoke(i)),Qe.set(t,s),s.catch(()=>Qe.delete(t))}return s}function Bs(t,e){let n=t.default;if(typeof n=="function"&&n.prototype instanceof HTMLElement){let s=n.prototype[Ot];if(typeof s!="function")throw new F("shape");let i=class extends n{},o=`${e?.element??"wl-app"}-${js()}${++Hs}`;try{He.define(o,i)}catch(r){throw new F("shape",W(r))}return{tag:o,shape:"class",attach:(r,a)=>{vt(s,r,[a])}}}if(oe(e)&&typeof t.element=="string"&&He.get(t.element))return{tag:t.element,shape:"legacy"};throw new F("shape")}var Us=3,qs=6e4,Ae=class{constructor(e,n){h(this,"element",e);h(this,"o",n);h(this,"faults",[]);h(this,"halted",!1)}get stopped(){return this.halted}has(e){try{return!this.halted&&typeof this.element[e]=="function"}catch{return!1}}update(e){this.halted||this.run(()=>this.element.update?.(e))}reset(){this.halted||this.run(()=>this.element.reset?.())}paging(){return this.halted?null:Ds(this.element)}hook(e,n){if(this.halted)return Promise.resolve();let s;try{s=this.element[e]?.(n)}catch(i){return this.fault(i),Promise.resolve()}return Promise.resolve(s).then(()=>{},i=>this.fault(i))}stop(e){if(!this.halted){this.halted=!0;try{this.element.remove()}catch{}this.o.dispose?.(),this.o.onStop(e)}}dispose(){this.o.dispose?.()}run(e){try{e()}catch(n){this.fault(n)}}fault(e){let n=(this.o.now??Date.now)();this.faults=this.faults.filter(s=>n-s<qs),this.faults.push(n),this.faults.length>=Us&&this.stop(W(e))}},_s=24*3600*1e3,Me=class t{constructor(e){h(this,"o",e);h(this,"memory",new Map);h(this,"inflight",new Map);h(this,"images",new Map);h(this,"pictures",new Map)}now(){return this.o.now?.()??Date.now()}url(e){let n=this.o.base();if(!n)return;let s=this.o.token(),i=`${n.replace(/\/$/,"")}${e}`;return s?`${i}${i.includes("?")?"&":"?"}${s}`:i}stored(e){return this.memory.get(e)??this.o.store?.getItem(e)??void 0}static keyOf(e,n){let s=Object.keys(n).sort().map(i=>`${encodeURIComponent(i)}=${encodeURIComponent(String(n[i]))}`).join("&");return{query:s,key:`wyndlass:data:${e}?${s}`}}kept(e,n){let s=this.stored(t.keyOf(e,n).key);if(s)try{let i=JSON.parse(s);return this.dress(i,Date.parse(i.expiresAt)<=this.now())}catch{return}}keep(e,n){this.memory.set(e,n);try{this.o.store?.setItem(e,n)}catch{}}get(e,n){let{query:s,key:i}=t.keyOf(e,n),o=this.inflight.get(i);if(o)return o;let r=this.o.fetch??fetch,a=(async()=>{let l=this.stored(i),d=l?JSON.parse(l):void 0;if(d&&Date.parse(d.expiresAt)>this.now())return this.dress(d,!1);let u=this.url(`/v0/${encodeURIComponent(e)}?${s}`);try{if(!u)throw new Error("no data service");let p=await r(u);if(!p.ok)throw new Error(`the data service answered ${p.status}`);let m=await p.text();return this.keep(i,m),this.dress(JSON.parse(m),!1)}catch(p){if(d&&Date.parse(d.expiresAt)+_s>this.now())return this.dress(d,!0);throw p}})().finally(()=>this.inflight.delete(i));return this.inflight.set(i,a),a}dress(e,n){return{...e,offline:n,ageMs:Math.max(0,this.now()-Date.parse(e.fetchedAt))}}image(e){let n=this.images.get(e);return n||(n=this.imageBlob(e).then(s=>URL.createObjectURL(s)),this.images.set(e,n),n.catch(()=>this.images.delete(e))),n}imageBlob(e){let n=this.pictures.get(e);if(!n){let s=this.url(e);n=s?(this.o.fetch??fetch)(s).then(i=>{if(!i.ok)throw new Error(`the image answered ${i.status}`);return i.blob()}):Promise.reject(new Error("no data service")),this.pictures.set(e,n),n.catch(()=>this.pictures.delete(e))}return n}};var Ft="wyndlass.frame";function Ut(t,e){return!!t&&(t.apps?t.apps(e):!oe(e))}var Ws=new Set(["load","shape","unsupported","error","late","overran"]),Ht=50,Bt=250,et=class{constructor(e,n,s,i){h(this,"frame",e);h(this,"port",n);h(this,"o",i);h(this,"halted",!1);h(this,"pages");h(this,"hooks");h(this,"pending",new Map);h(this,"next",0);h(this,"told");h(this,"last");h(this,"quiet");this.hooks=new Set(s.hooks),this.pages=s.paging}get stopped(){return this.halted}has(e){return!this.halted&&this.hooks.has(e)}paging(){return this.halted?null:this.pages}update(e){if(this.halted)return;let n=performance.now(),s=this.last;this.last={t:e,at:n};let i=this.told;if(i&&(i.running?Math.abs(e-(i.t+(n-i.at)))<=Ht:e===i.t)){i.running&&this.watch();return}let o=!!s&&n-s.at<Bt&&e>s.t&&Math.abs(e-s.t-(n-s.at))<=Ht;this.told={t:e,at:n,running:o},this.post({type:"clock",t:e,running:o}),o&&this.watch()}reset(){this.halted||this.post({type:"reset"})}hook(e,n){if(this.halted)return Promise.resolve();let s=++this.next;return new Promise(i=>{this.pending.set(s,i),this.post({type:"hook",id:s,kind:e,options:n})})}stop(e){this.halted||(this.halted=!0,this.frame.remove(),this.dispose(),this.o.onStop(e))}dispose(){clearTimeout(this.quiet),this.port.close();for(let e of this.pending.values())e();this.pending.clear()}heard(e){e.type==="paging"?this.pages=_t(e.paging):e.type==="hooked"&&typeof e.id=="number"?(this.pending.get(e.id)?.(),this.pending.delete(e.id)):e.type==="stopped"&&this.stop(Wt(e.error))}post(e){try{this.port.postMessage(e)}catch{}}watch(){clearTimeout(this.quiet),this.quiet=setTimeout(()=>{!this.told?.running||!this.last||this.halted||(this.told={...this.last,running:!1},this.post({type:"clock",t:this.last.t,running:!1}))},Bt)}};async function qt(t,e,n,s,i,o,r,a,l,d){let u=t.appInfo??n.info,p;try{p=new URL(l.frames).origin}catch{return d("unsupported")}let m=document.createElement("iframe");m.setAttribute("sandbox","allow-scripts"),m.setAttribute("referrerpolicy","no-referrer"),m.title=u?.id??"app",m.tabIndex=-1,m.dataset.frame=u?.id??"",m.style.cssText="display:block;width:100%;height:100%;border:0;pointer-events:none;background:transparent;color-scheme:normal";let y=new Promise(M=>{m.addEventListener("load",()=>M(!0),{once:!0}),setTimeout(()=>M(!1),o)});m.src=`${p}/`,e.append(m);let A=(M,w)=>(m.remove(),d(M,w));if(!await y||!m.contentWindow)return A("load");let{port1:f,port2:k}=new MessageChannel,b={type:"init",element:{id:t.id,type:"app",app:t.app,props:t.props??{},...t.binding?{binding:t.binding}:{},...t.appInfo?{appInfo:t.appInfo}:{}},bundle:{hash:n.hash,bytes:n.bytes.slice(),...n.info?{info:n.info}:{}},...s.place?{place:s.place}:{},screen:s.screen??null,timeZone:s.timeZone,locale:s.locale,preview:!!s.preview,clock:{t:i.now(),running:!1,startedAt:i.startedAt()},fonts:Ks(t.props??{},l),snapshots:Zs(t,s),deadlineMs:o};return new Promise(M=>{let w,T=!1,N=G=>{T||(T=!0,clearTimeout(j),M(G))},j=setTimeout(()=>{w?(N({app:w,late:!0}),w.update(i.now())):(f.close(),N(A("load")))},o+1e3),q=!1;f.onmessage=G=>{let x=G.data;if(!(!x||typeof x!="object")){if(x.type==="ask"){q&&zs(x,s,f);return}if(w){x.type==="ready"?(N({app:w,late:x.late===!0}),w.update(i.now())):w.heard(x);return}x.type==="hello"&&!q?(q=!0,f.postMessage(b,[b.bundle.bytes.buffer])):x.type==="made"&&Array.isArray(x.hooks)?(w=new et(m,f,{type:"made",hooks:x.hooks.filter(C=>C==="enter"||C==="exit"),paging:_t(x.paging)},{onStop:a}),r(w)):x.type==="failed"&&(f.close(),N(A(Ws.has(x.reason)?x.reason:"error",Wt(x.error))))}},m.contentWindow.postMessage({type:Ft},"*",[k])})}async function zs(t,e,n){if(typeof t.id!="number")return;let s=(i,o=[])=>{try{n.postMessage(i,o)}catch{}};try{if(!e.data)throw new Error("no data service");if(t.what==="data"&&typeof t.source=="string")s({type:"answer",id:t.id,value:await e.data.get(t.source,Vs(t.params))});else if(t.what==="image"&&typeof t.path=="string"){let i=await e.data.imageBlob(t.path),o=new Uint8Array(await i.arrayBuffer());s({type:"answer",id:t.id,value:{bytes:o,type:i.type}},[o.buffer])}else throw new TypeError("not a question")}catch(i){s({type:"answer",id:t.id,error:W(i)??"Error"})}}function Vs(t){let e={};if(t&&typeof t=="object")for(let[n,s]of Object.entries(t))(typeof s=="string"||typeof s=="number"||typeof s=="boolean")&&(e[n]=s);return e}function _t(t){let e=t;return e&&Number.isInteger(e.count)&&e.count>1&&typeof e.ms=="number"&&e.ms>0?{count:e.count,ms:e.ms}:null}function Wt(t){return typeof t=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(t)?t:void 0}function Ks(t,e){let n=new Set;for(let i of Object.values(t)){let o=i?.family;i&&typeof i=="object"&&typeof o=="string"&&n.add(o)}let s=[];for(let i of e.fonts??[]){let o=n.has(i.family)?e.font?.(i.blob):void 0;o&&s.push({family:i.family,weight:i.weight,style:i.style,blob:i.blob,bytes:o.slice()})}return s}function Zs(t,e){let n=new Set;t.binding?.table&&n.add(t.binding.table);for(let i of Object.values(t.props??{}))typeof i=="string"&&/^tbl_[0-9a-z]{26}$/.test(i)&&n.add(i);let s={};for(let i of n){let o=e.snapshot?.(i);o&&(s[i]=o)}return s}function zt(t){let e=te?.setStyle??((n,s,i,o)=>n.style.setProperty(s,i,o));e(t,"contain","strict","important"),e(t,"overflow","clip","important")}var Vt=t=>!!(t?.filter?.length||t?.sort||t?.limit),Js=t=>t==null||t==="";function Ys(t,e){if(!Js(t))switch(e){case"number":case"currency":{let n=Number(t);return Number.isFinite(n)?n:void 0}case"datetime":{let n=Date.parse(String(t));return Number.isNaN(n)?void 0:n}case"boolean":return t===!0||String(t).toLowerCase()==="true"?1:0;case"date":return String(t).slice(0,10);default:return String(t).toLowerCase()}}function Xs(t,e){let n=Array.from(t),s=Array.from(e);for(let i=0;i<n.length&&i<s.length;i++){let o=n[i].codePointAt(0)-s[i].codePointAt(0);if(o!==0)return o}return n.length-s.length}function Gs(t,e,n,s,i){let o=t,r=Array.isArray(o?.rows)?o.rows:[],a=Array.isArray(o?.columns)?o.columns:void 0,l=d=>a?.find(u=>u.key===d)?.type??"text";for(let d of e.filter??[]){if(a&&!a.some(m=>m.key===d.column))return[];let u=d.value;if(u!==null&&typeof u=="object"){let m=u;if(u=ke(n,s,m.screen,m.key),u===void 0)return[]}let p=l(d.column);r=r.filter(m=>Pt(m[d.column],d.op,u,p,i,s))}if(e.sort){let{column:d}=e.sort,u=l(d),p=e.sort.order==="desc"?-1:1;r=r.map((m,y)=>({row:m,i:y,key:Ys(m[d],u)})).sort((m,y)=>{if(m.key===void 0||y.key===void 0)return m.key===void 0&&y.key===void 0?m.i-y.i:m.key===void 0?1:-1;let A=typeof m.key=="number"&&typeof y.key=="number"?m.key-y.key:Xs(String(m.key),String(y.key));return A===0?m.i-y.i:A<0?-p:p}).map(m=>m.row)}return e.limit?r.slice(0,e.limit):r}function Kt(t,e,n,s,i){return!t||typeof t!="object"?t:{...t,rows:Gs(t,e,n,s,i)}}function Qs(t){if(!t)return"#000000";if("value"in t)return t.value;let e=t.gradient,n=[...e.stops].sort((s,i)=>s.at-i.at).map(s=>`${s.color.value} ${Math.round(s.at*1e4)/100}%`).join(", ");return e.kind==="radial"?`radial-gradient(circle, ${n})`:`linear-gradient(${e.angle??180}deg, ${n})`}function Jt(t,e,n,s=[]){let i=D("div");if(i.dataset.background="",i.style.cssText="position:absolute;inset:0;pointer-events:none",i.style.background=Qs(e),s.length){let o=[`M0 0H${n.width}V${n.height}H0Z`,...s.map(l=>`M${l.x} ${l.y}H${l.x+l.w}V${l.y+l.h}H${l.x}Z`)].join(""),r=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n.width} ${n.height}" preserveAspectRatio="none"><path fill-rule="evenodd" d="${o}"/></svg>`,a=`url("data:image/svg+xml,${encodeURIComponent(r)}")`;i.dataset.holes=String(s.length);for(let[l,d]of[["maskImage",a],["maskSize","100% 100%"],["maskRepeat","no-repeat"]])i.style[l]=d,i.style[`webkit${l[0].toUpperCase()}${l.slice(1)}`]=d}return t.prepend(i),i}function Yt(t,e=t.frame){let n=D("div");return n.dataset.element=t.id,n.style.cssText="position:absolute;box-sizing:border-box",ei(n,e),n}function ei(t,e){Object.assign(t.style,{left:`${e.x}px`,top:`${e.y}px`,width:`${e.w}px`,height:`${e.h}px`,transform:e.rotation?`rotate(${e.rotation}deg)`:""})}function Xt(t,e,n){if(e.type==="text"){let s=e.style??{},i=Gt(n);t.textContent=e.bind?ze(e.bind,i):Ve(e.text??"",i),Object.assign(t.style,{fontFamily:s.font?.family?`"${s.font.family}", sans-serif`:"",fontWeight:String(s.font?.weight??400),fontStyle:s.font?.style??"normal",fontSize:`${s.size??48}px`,color:s.color?.value??"#ffffff",background:s.background?.value??"",whiteSpace:"pre-wrap",textAlign:{start:"left",end:"right"}[s.align??"start"]??s.align??"left",display:"flex",flexDirection:"column",justifyContent:{top:"flex-start",middle:"center",bottom:"flex-end"}[s.verticalAlign??"top"],lineHeight:s.lineHeight?String(s.lineHeight):"",opacity:s.opacity!==void 0?String(s.opacity):"",textDecorationLine:s.decoration?.length?s.decoration.join(" "):"",textTransform:s.transform&&s.transform!=="none"?s.transform:"",letterSpacing:s.letterSpacing?`${s.letterSpacing}em`:"",textShadow:s.shadow?`${s.shadow.x??0}px ${s.shadow.y??2}px ${s.shadow.blur??4}px ${s.shadow.color?.value??"#000000"}`:""})}else e.type==="shape"&&ti(e)?(t.style.opacity=e.opacity!==void 0?String(e.opacity):"",t.append(ni(e))):e.type==="shape"?Object.assign(t.style,{background:e.fill?.value??"",opacity:e.opacity!==void 0?String(e.opacity):"",border:e.stroke?.width?`${e.stroke.width}px ${e.stroke.dash??"solid"} ${e.stroke.color?.value??"#000"}`:"",borderRadius:e.shape==="ellipse"?"50%":e.radius?`${e.radius}px`:"",overflow:e.clip!==!1?"hidden":""}):e.type==="library"&&(t.style.overflow="hidden",t.style.background=e.playback?.background?.value??"")}function Gt(t){return{screen:t?.screen??null,timeZone:t?.timeZone,tables:new Proxy({},{get:(e,n)=>typeof n=="string"?t?.snapshot?.(n):void 0})}}var ti=t=>t.shape==="line"||t.shape==="arrow"||t.shape==="polygon";function ni(t){let e=ye("svg");e.setAttribute("width","100%"),e.setAttribute("height","100%"),e.style.cssText="position:absolute;inset:0;overflow:visible";let n=t.frame.w,s=t.frame.h,i=t.stroke?.width??(t.shape==="polygon"?0:4),r=(t.points?.length?t.points:t.shape==="polygon"?[[.5,0],[1,1],[0,1]]:[[0,.5],[1,.5]]).map(([u,p])=>[u*n,p*s]),a=t.stroke?.color?.value??"#000000",l=t.stroke?.dash==="dashed"?`${i*3} ${i*2}`:t.stroke?.dash==="dotted"?`0 ${i*2}`:"",d=ye(t.shape==="polygon"?"polygon":"polyline");if(d.setAttribute("points",r.map(u=>`${u[0]},${u[1]}`).join(" ")),d.setAttribute("fill",t.shape==="polygon"?t.fill?.value??"none":"none"),i&&(d.setAttribute("stroke",a),d.setAttribute("stroke-width",String(i)),d.setAttribute("stroke-linecap",t.stroke?.dash==="dotted"?"round":"butt"),d.setAttribute("stroke-linejoin","round"),l&&d.setAttribute("stroke-dasharray",l)),e.append(d),t.shape==="arrow"&&r.length>=2){let[u,p]=[r[r.length-2],r[r.length-1]],m=Math.atan2(p[1]-u[1],p[0]-u[0]),y=Math.max(10,i*3.5),A=k=>`${p[0]-y*Math.cos(m)+k*y*.5*Math.sin(m)},${p[1]-y*Math.sin(m)-k*y*.5*Math.cos(m)}`;d.setAttribute("points",[...r.slice(0,-1),[p[0]-y*.8*Math.cos(m),p[1]-y*.8*Math.sin(m)]].map(k=>`${k[0]},${k[1]}`).join(" "));let f=ye("polygon");f.setAttribute("points",`${p[0]},${p[1]} ${A(1)} ${A(-1)}`),f.setAttribute("fill",a),e.append(f)}return e}async function Qt(t,e,n,s,i,o,r,a=()=>{},l){let d=t.appInfo??n.info;zt(e),d?.id&&(e.dataset.app=d.id);let u=(x,C)=>(e.dataset.appFailed=x,{failed:x,...C?{error:C}:{}});if(!Dt(d?.requires))return u("unsupported");let p=s.appHost;if(p&&!p.custom&&!oe(d))return u("unsupported");if(Ut(l,d))return qt(t,e,n,s,i,o,r,a,l,u);let m=t.app;if(p?.held?.(m))return e.dataset.appHeld="",{held:!0};await p?.mounting?.(m,d?.id);let y=!1,A=()=>{y||(y=!0,p?.unmounted?.(m))},f;try{f=await jt(n.hash,n.bytes,d)}catch(x){return A(),x instanceof F?u(x.reason,x.error):u("load")}let k=f.tag,b=d?.id?s.controllerFor?.({id:d.id,instance:t.id,capability:d.player??"read"}):void 0,M,w=new Promise(x=>{M=x}),T,N={app:d?.id??t.app.id,element:k,props:St(t.props??{},Gt(s)),place:s.place,screen:s.screen??null,timeZone:s.timeZone??Intl.DateTimeFormat().resolvedOptions().timeZone,locale:s.locale??navigator.language,preview:!!s.preview,now:i.now,startedAt:i.startedAt,ready:()=>M(),data:(x,C)=>s.data?s.data.get(x,C):Promise.reject(new Error("no data service")),kept:(x,C)=>s.data?.kept?.(x,C),image:x=>s.data?s.data.image(x):Promise.reject(new Error("no data service")),snapshot:x=>{let C=s.snapshot?.(x)??null;if(!C||t.binding?.table!==x||!Vt(t.binding))return C;let Q=Kt(C,t.binding,s.screen,N.timeZone,i.now());return T?.from===C&&T.rows.length===Q.rows.length&&T.rows.every((Rn,Nn)=>Rn===Q.rows[Nn])?T.snapshot:(T={from:C,rows:Q.rows,snapshot:Q},Q)},...t.binding?{binding:t.binding}:{},...t.sensors?.includes("impressions")&&s.impressions?.measuring?{impressions:{now:()=>s.impressions.now(),on:x=>s.impressions.on(x)}}:{},...b?{controller:b}:{}},j;try{j=D(k),f.shape==="class"?f.attach(j,N):j.wyndlass=N}catch(x){return b?.dispose(),A(),u("error",W(x))}j.style.cssText="display:block;width:100%;height:100%",p?.contained?.(e,j);let q=new Ae(j,{onStop:a,dispose:()=>{b?.dispose(),A()}});e.append(j),r(q);let G=await Promise.race([w.then(()=>!1),new Promise(x=>setTimeout(()=>x(!0),o))]);return q.update(N.now()),{app:q,late:G}}var Zt=new Set;async function en(t,e){for(let n of t){let s=`${n.family}/${n.weight??400}/${n.style??"normal"}/${n.blob}`,i=e(n.blob);if(!(Zt.has(s)||!i||typeof FontFace>"u"))try{let o=new FontFace(n.family,i,{weight:String(n.weight??400),style:n.style??"normal"});document.fonts.add(await o.load()),Zt.add(s)}catch{}}}function ae(t){let e=[],n=null;for(let s of t){let i=s.start,o=Math.max(0,i.kind==="at"?i.ms:i.kind==="withPrevious"?(n?.startMs??0)+(i.offsetMs??0):(n?.endMs??0)+(i.offsetMs??0));n={startMs:o,endMs:o+s.durationMs},e.push(n)}return{clips:e,autoDurationMs:e.reduce((s,i)=>Math.max(s,i.endMs),0)}}var si=new Set(["x","y","scaleX","scaleY","rotation","opacity","reveal"]);function ii(t,{pageBelow:e=!1}={},n=null){let s=o=>({path:"page",reason:o});if(!n)return s("no-media-layer");if(e)return s("page-element-beneath");let i=t.slice(0,-1).filter(o=>o.type==="shape"&&o.clip!==!1);if(i.some(o=>o.shape!=="rectangle"||(o.radius??0)>0))return s("clipped-to-shape");if(i.some(o=>(o.rotation??0)%360!==0))return s("clipped-by-rotated-shape");if(!n.animates){if(t.some(o=>(o.rotation??0)%360!==0))return s("rotated");if(t.some(o=>(o.opacity??1)<1))return s("translucent");if(t.some(o=>(o.animated??[]).some(r=>si.has(r))))return s("animated")}return{path:"native"}}var ri={fade:["opacity"],slide:["x","opacity"],scale:["scaleX","scaleY","opacity"],pulse:["scaleX","scaleY"],loop:["scaleX","scaleY"],wipe:["reveal"]};function tn(t,e,n,s){let i=e&&t.overrides?.[e]||{},o=new Map;for(let p of t.animation?.clips??[]){let m=[...p.preset?ri[p.preset]??[]:[],...(p.lanes??[]).map(y=>y.property)];o.set(p.element,[...o.get(p.element)??[],...m])}let r=p=>p.type==="library"&&!!p.source&&!!n[p.source.item.id]?.video,a=p=>!r(p)&&!(p.type==="shape"&&!p.fill&&!(p.stroke?.width??0)),l=[],d=(p,m,y,A)=>{for(let f of p){let k=i[f.id];if(k?.hidden??f.hidden??!1)continue;let b=k?.frame??f.frame,M={x:m.x+b.x,y:m.y+b.y,w:b.w,h:b.h},w={type:f.type==="shape"?"shape":"library",rotation:b.rotation??0,opacity:f.opacity??f.style?.opacity??1,animated:o.get(f.id)??[],...f.type==="shape"?{shape:f.shape,radius:f.radius,clip:f.clip}:{}};l.push({e:f,box:M,chain:[...y,w],ancestors:A}),f.type==="shape"&&d(f.children??[],M,[...y,w],[...A,f])}};d(t.elements,{x:0,y:0},[],[]);let u=(p,m)=>p.x<m.x+m.w&&m.x<p.x+p.w&&p.y<m.y+m.h&&m.y<p.y+p.h;return l.flatMap((p,m)=>{if(!r(p.e))return[];let y=l.slice(0,m).some(A=>a(A.e)&&u(A.box,p.box));return[{element:p.e.id,path:ii(p.chain,{pageBelow:y},s),box:p.box}]})}var oi=[0,0,1,1];function tt(t,e){if(e<=0)return 0;if(e>=1)return 1;let[n,s,i,o]=t;if(n===s&&i===o)return e;let r=3*n,a=3*(i-n)-r,l=1-r-a,d=3*s,u=3*(o-s)-d,p=1-d-u,m=w=>((l*w+a)*w+r)*w,y=w=>((p*w+u)*w+d)*w,A=w=>(3*l*w+2*a)*w+r,f=1e-7,k=e;for(let w=0;w<8;w++){let T=m(k)-e;if(Math.abs(T)<f)return y(k);let N=A(k);if(Math.abs(N)<1e-6)break;k-=T/N}let b=0,M=1;for(k=e;b<M;){let w=m(k);if(Math.abs(w-e)<f||(e>w?b=k:M=k,k=(M-b)/2+b,M-b<f))break}return y(k)}function Ee(t,e){let n=t.keyframes;if(e<=n[0].t)return n[0].v;let s=n[n.length-1];if(e>=s.t)return s.v;let i=0;for(;e>=n[i+1].t;)i++;let o=n[i],r=n[i+1];return o.v+(r.v-o.v)*tt(o.easing??oi,(e-o.t)/(r.t-o.t))}function nn(t,e,n){let s={x:t.x,y:t.y,scaleX:1,scaleY:1,rotation:0,opacity:1};for(let i of e??[])s[i.property]=Ee(i,n);return s}var de={linear:[0,0,1,1],ease:[.25,.1,.25,1],"ease-in":[.42,0,1,1],"ease-out":[0,0,.58,1],"ease-in-out":[.42,0,.58,1]};function sn(t){if(!t)return de.ease;if(de[t])return de[t];let n=/^cubic-bezier\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)$/.exec(t)?.slice(1).map(Number);return n&&n.every(Number.isFinite)?n:de.ease}function ai(t){let e=typeof t=="string"?/^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})?$/.exec(t):null;if(!e)return null;let n=parseInt(e[1],16);return[n>>16&255,n>>8&255,n&255,e[2]?parseInt(e[2],16)/255:1]}var an={fade:()=>({opacity:0}),slide:t=>({x:t.x-t.w*.1,opacity:0}),scale:()=>({scaleX:.8,scaleY:.8,opacity:0}),wipe:()=>({reveal:0}),typewriter:()=>({chars:0}),"count-up":()=>({count:0})},Zo=Object.keys(an);var di={x:t=>t.x,y:t=>t.y,scaleX:()=>1,scaleY:()=>1,rotation:()=>0,opacity:()=>1,reveal:()=>1,chars:()=>1,count:()=>1},rn=1e3,li=de["ease-in-out"];function dn(t,e){let n=ae(t).clips,s=new Map,i=new Set,o=a=>{let l=s.get(a);return l||s.set(a,l={keys:new Map,colour:null,loops:[],tickers:[]}),l},r=(a,l,d)=>{let u=o(a).keys,p=u.get(l)??[];u.set(l,p),p.push(d)};t.forEach((a,l)=>{let d=e.get(a.element);if(!d)return;let{startMs:u,endMs:p}=n[l],m=sn(a.easing);if(a.lanes?.length){for(let f of a.lanes)for(let k of f.keyframes){let b={t:u+k.at,easing:sn(k.easing??a.easing)};if(f.property==="color"){let M=ai(k.value);if(!M)continue;let w=o(a.element);w.colour??(w.colour=[[],[],[],[]]),M.forEach((T,N)=>w.colour[N].push({...b,v:T}))}else r(a.element,f.property,{...b,v:Number(k.value)})}return}let y=a.preset??"fade";if(a.kind==="emphasis"){y==="pulse"?(r(a.element,"pulse",{t:u,v:1,easing:m}),r(a.element,"pulse",{t:(u+p)/2,v:1.05,easing:m}),r(a.element,"pulse",{t:p,v:1})):y==="loop"?o(a.element).loops.push({startMs:u,endMs:p}):y==="ticker"?o(a.element).tickers.push({startMs:u,endMs:p}):i.add(y);return}let A=an[y];if(!A){i.add(y);return}for(let[f,k]of Object.entries(A(d))){let b=di[f](d),[M,w]=a.kind==="enter"?[k,b]:[b,k];r(a.element,f,{t:u,v:M,easing:m}),r(a.element,f,{t:p,v:w})}});for(let a of s.values()){for(let l of a.keys.values())l.sort((d,u)=>d.t-u.t);for(let l of a.colour??[])l.sort((d,u)=>d.t-u.t)}return{motions:s,unplayed:[...i].sort()}}var pi=["x","y","scaleX","scaleY","rotation","opacity"];function ln(t,e,n,s=!1){if(s)return ci(t,e,n);let i=(l,d)=>{let u=t?.keys.get(l);return u?Ee({property:"x",keyframes:u},n):d},o=nn(e,pi.flatMap(l=>t?.keys.get(l)?[{property:l,keyframes:t.keys.get(l)}]:[]),n),r=i("pulse",1);for(let l of t?.loops??[])if(n>=l.startMs&&n<l.endMs){let d=(n-l.startMs)%rn/rn;r*=1+.05*tt(li,d<.5?d*2:2-d*2)}let a=t?.tickers.find(l=>n>=l.startMs&&n<l.endMs);return{...o,reveal:i("reveal",1),chars:i("chars",1),count:i("count",1),pulse:r,scroll:a?(n-a.startMs)/(a.endMs-a.startMs):null,color:t?.colour?t.colour.map(l=>Ee({property:"x",keyframes:l},n)):null}}function on(t,e){if(e<t[0].t)return t[0].v;let n=0;for(;n+1<t.length&&t[n+1].t<=e;)n++;return n+1<t.length?t[n+1].v:t[n].v}function ci(t,e,n){let s=(i,o)=>{let r=t?.keys.get(i);return r?on(r,n):o};return{x:s("x",e.x),y:s("y",e.y),scaleX:s("scaleX",1),scaleY:s("scaleY",1),rotation:s("rotation",0),opacity:s("opacity",1),reveal:s("reveal",1),chars:s("chars",1),count:s("count",1),pulse:1,scroll:null,color:t?.colour?t.colour.map(i=>on(i,n)):null}}function ui(t,e){let n=Array.from(t);return e>=1?t:n.slice(0,Math.max(0,Math.round(e*n.length))).join("")}function hi(t,e){return e>=1?t:t.replace(/\d+(?:\.\d+)?/g,n=>(Number(n)*Math.max(0,e)).toFixed((n.split(".")[1]??"").length))}function pn(t,e,n){if(t<=1||e<=0)return[{page:0,startMs:0,endMs:n}];let s=[];for(let i=0;i*e<n&&i<1e3;i++)s.push({page:i%t,startMs:i*e,endMs:Math.min((i+1)*e,n)});return s}function cn(t,e,n,s){t.style.left=`${n.x}px`,t.style.top=`${n.y}px`,t.style.transform=`rotate(${(e.frame.rotation??0)+n.rotation}deg) scale(${n.scaleX*n.pulse}, ${n.scaleY*n.pulse})`,t.style.opacity=String((e.opacity??e.style?.opacity??1)*n.opacity),t.style.clipPath=n.reveal>=1?"":`inset(0 ${(1-Math.max(0,n.reveal))*100}% 0 0)`;let i=n.color?`rgba(${n.color.slice(0,3).map(o=>Math.round(o)).join(",")},${n.color[3]})`:null;if(e.type==="text"&&s!==void 0){let o=hi(ui(s,n.chars),n.count);if(n.scroll!==null){let r=t.querySelector("[data-ticker]");r||(r=document.createElement("span"),r.dataset.ticker="",r.style.cssText="display:inline-block;white-space:nowrap;will-change:transform",t.replaceChildren(r),t.style.overflow="hidden"),r.textContent!==o&&(r.textContent=o);let a=e.frame.w,l=r.scrollWidth;r.style.transform=`translateX(${a-n.scroll*(a+l)}px)`}else(t.textContent!==o||t.querySelector("[data-ticker]"))&&(t.textContent=o,t.style.overflow="");i&&(t.style.color=i)}else if(e.type==="shape"&&i){let o=t.querySelector("polygon, polyline");o&&e.shape==="polygon"?o.setAttribute("fill",i):o||(t.style.background=i)}}function mi(t){let e=t>>>0;return()=>{e=e+1831565813|0;let n=Math.imul(e^e>>>15,1|e);return n=n+Math.imul(n^n>>>7,61|n)^n,((n^n>>>14)>>>0)/4294967296}}async function un(t,e,n,s){let i=parseInt((await We(_e(`${t}
3
3
  ${e}
4
- ${n}`))).slice(0,8),16),o=hi(i),r=Array.from({length:s},(a,d)=>d);for(let a=s-1;a>0;a--){let d=Math.floor(o()*(a+1));[r[a],r[d]]=[r[d],r[a]]}return r}var mi=250,fi=1e3;function Ct(t,e){let n=t.animation?.duration;if(n?.kind==="fixed")return{durationMs:n.ms};if(n?.kind==="untilPlaylistEnds"){let s=fn(t.elements,n.element);return{durationMs:(s?.source?.kind==="playlist"?e.playlist(s.source.item)??[]:s?.source?[{item:s.source.item}]:[]).reduce((r,a)=>r+(e.slide(a,{imageDurationMs:s?.playback?.imageDurationMs})?.durationMs??0),0)||1e4,untilPass:n.element}}return{durationMs:ae(t.animation?.clips??[]).autoDurationMs}}function mn(t,e,n=0){let s={},i=o=>{for(let r of o){if(r.type==="library"&&r.source){let a=r.source.kind==="playlist"?e.playlist(r.source.item)??[]:[{item:r.source.item}];s[r.source.item.id]={video:a.some(d=>{let l=e.slide(d);if(!l)return!1;if(l.blob.contentType.startsWith("video/"))return!0;let c=l.layout;return!!c&&n<4&&Object.values(mn(c.doc,c.source,n+1)).some(p=>p.video)})}}r.children&&i(r.children)}};return i(t.elements),s}function fn(t,e){for(let n of t){if(n.id===e)return n;let s=n.children&&fn(n.children,e);if(s)return s}}var J=class{constructor(e,n,s,i={}){h(this,"source",n);h(this,"o",i);h(this,"el");h(this,"ready");h(this,"unplayed");h(this,"doc");h(this,"motions");h(this,"native");h(this,"animated",[]);h(this,"loops",[]);h(this,"startedAt",0);h(this,"frame",0);h(this,"running",!1);h(this,"disposed",!1);h(this,"apps",[]);h(this,"heldAt");h(this,"late",[]);h(this,"overran",[]);h(this,"timers",[]);h(this,"reduced",typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches);let o=s.clientWidth||window.innerWidth,r=s.clientHeight||window.innerHeight,a=!e.profiles[0].width,d=a?{...e.profiles[0],width:o,height:r}:e.profiles[0];a&&(e={...e,elements:e.elements.map(b=>b.frame.w?b:{...b,frame:{x:0,y:0,w:o,h:r}})});let l=Math.min(o/d.width,r/d.height);this.el=D("div"),this.el.dataset.layout=d.id,this.el.style.cssText=`position:absolute;left:${(o-d.width*l)/2}px;top:${(r-d.height*l)/2}px;width:${d.width}px;height:${d.height}px;transform:scale(${l});transform-origin:0 0;overflow:hidden`,s.append(this.el),this.doc=e;let c=new Map,p=b=>{for(let M of b)c.set(M.id,M.frame),M.children&&p(M.children)};p(e.elements);let m=dn(e.animation?.clips??[],c);this.motions=m.motions;let y=new Set(m.unplayed.map(b=>`preset ${b}`)),A=[this.loadFonts()],f=i.media?tn(e,void 0,mn(e,n),i.media.capability):[];this.native=new Set(f.filter(b=>b.path.path==="native").map(b=>b.element));let k=(b,M)=>{for(let w of b){if(w.hidden)continue;let T=this.element(w,y,A);M.append(T),w.children?.length&&w.type==="shape"&&k(w.children,T)}};k(e.elements,this.el),e.background&&Jt(this.el,e.background,d,f.filter(b=>this.native.has(b.element)).map(b=>b.box)),this.unplayed=[...y].sort(),this.ready=Promise.all(A).then(()=>{})}path(e){return this.o.path?`${this.o.path}/${e}`:e}element(e,n,s){let i=Yt(e);Xt(i,e,this.source);let o=this.motions.get(e.id);if(o){let r={el:i,e,motion:o,text:e.type==="text"?i.textContent??"":void 0};this.animated.push(r),this.place(r,0)}if(e.type==="library"&&e.source){let r=e.source.kind==="playlist"?this.source.playlist(e.source.item)??[]:[{item:e.source.item}],a=new nt(i,r,e.playback??{},this.source,this.path(e.id),{...this.o,media:this.native.has(e.id)?this.o.media:void 0},e.source.kind==="playlist"?e.source.item:void 0);this.loops.push(a),s.push(a.ready);for(let d of a.unplayed)n.add(d)}else if(e.type==="app"){let r=e.app?this.source.app?.(e.app):null;r?s.push(this.mountApp(e,i,r)):n.add(`app ${e.app?.id??e.id}`)}return i}place(e,n){cn(e.el,e.e,ln(e.motion,e.e.frame,n,this.reduced),e.text);let s=e.e.type==="text"?e.el.firstElementChild:null;s&&qe(s)}loadFonts(){return en(this.doc.fonts??[],e=>this.source.font(e))}async mountApp(e,n,s){let i=this.path(e.id),o=e.app,r=(e.appInfo??s.info)?.id,a={now:()=>this.heldAt??Date.now(),startedAt:()=>this.startedAt||Date.now()},d=(p,m)=>this.o.onApp?.({type:"app.failed",element:i,item:o,...r?{app:r}:{},reason:p,...m?{error:m}:{}}),l=this.o.isolate?{...this.o.isolate,fonts:this.doc.fonts,font:p=>this.source.font(p)}:void 0,c=await Qt(e,n,s,this.source,a,this.o.appDeadlineMs??5e3,p=>{this.disposed?p.dispose():this.apps.push({app:p,box:n,path:i,id:e.id,item:o})},p=>d("error",p),l);if(!("held"in c)){if("failed"in c){d(c.failed,c.error);return}c.late&&(this.late.push(i),this.o.onAppLate?.(i,o.id),d("late"))}}async start(e=Date.now()){this.startedAt=e,this.heldAt=void 0,this.running=!0,this.hooks(e);let n=()=>{if(!this.running)return;let s=Date.now(),i=s-this.startedAt;for(let o of this.animated)this.place(o,i);for(let o of this.apps)o.app.update(s);this.frame=requestAnimationFrame(n)};(this.animated.length||this.apps.length)&&(this.frame=requestAnimationFrame(n)),await Promise.all(this.loops.map(s=>s.start()))}hooks(e){let n=this.doc.animation?.clips??[],s=ae(n).clips,i=typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches;for(let o of this.apps){let a=n.map((f,k)=>({c:f,t:s[k]})).filter(f=>f.c.element===o.id&&(f.c.kind==="enter"||f.c.kind==="exit")).filter(f=>o.app.has(f.c.kind));if(!a.length)continue;let d=o.box,l=this.animated.findIndex(f=>f.el===d);l>=0&&(this.animated.splice(l,1),Object.assign(d.style,{opacity:"",transform:"",clipPath:""}));let c=(f,k,b)=>this.hook(o,f.kind,{startAt:k,preset:f.preset??"fade",durationMs:f.durationMs,easing:f.easing,delayMs:0,...f.staggerMs?{staggerMs:f.staggerMs}:{},...b!==void 0?{page:b}:{},reducedMotion:i},d);for(let{c:f,t:k}of a)c(f,e+k.startMs);let p=o.app.paging();if(!p)continue;let m=a.find(f=>f.c.kind==="enter")?.c,y=a.find(f=>f.c.kind==="exit")?.c,A=pn(p.count,p.ms,Ct(this.doc,this.source).durationMs);for(let f=1;f<A.length;f++){let k=e+A[f].startMs;y&&c(y,k-y.durationMs,A[f-1].page),m&&c(m,k,A[f].page)}}}hook(e,n,s,i){let o=!1;e.app.hook(n,s).finally(()=>{o=!0});let r=s.startAt+s.durationMs;this.timers.push(setTimeout(()=>{o||!this.running||(this.overran.includes(e.path)||(this.overran.push(e.path),this.o.onAppOverran?.(e.path,e.item.id),this.o.onApp?.({type:"app.failed",element:e.path,item:e.item,reason:"overran"})),n==="exit"&&(i.style.visibility="hidden"))},Math.max(0,r+mi-Date.now())))}pause(){this.heldAt=Date.now();for(let e of this.loops)e.pause();for(let e of this.apps)e.app.update(this.heldAt)}seek(e){this.running=!1,cancelAnimationFrame(this.frame);for(let n of this.loops)n.pause();for(let n of this.animated)this.place(n,e);this.heldAt=this.startedAt+e;for(let n of this.apps)n.app.update(this.heldAt)}resume(){this.heldAt=void 0;for(let e of this.loops)e.resume()}paging(e){return this.apps.find(n=>n.id===e)?.app.paging()??null}dispose(){this.running=!1,this.disposed=!0,cancelAnimationFrame(this.frame);for(let e of this.timers)clearTimeout(e);for(let e of this.loops)e.stop();for(let e of this.apps)e.app.dispose();this.el.remove()}},hn=t=>Math.min(Math.max(0,t),2**31-1),nt=class{constructor(e,n,s,i,o,r,a){h(this,"region",e);h(this,"playback",s);h(this,"source",i);h(this,"path",o);h(this,"o",r);h(this,"list",a);h(this,"ready");h(this,"unplayed",[]);h(this,"renderer");h(this,"slides");h(this,"order",[]);h(this,"pass",0);h(this,"at",0);h(this,"timer");h(this,"current");h(this,"shownAt",0);h(this,"remaining");h(this,"stopped",!1);h(this,"ended",!1);h(this,"empty",!1);h(this,"open");h(this,"pages");this.renderer=new xe(e,{media:r.media,fit:s.fit,path:o,onItem:r.onItem,onPassEnd:r.onPassEnd,onApp:r.onApp}),s.transition==="slide"&&this.unplayed.push("transition slide");let d=s.transition==="fade"?{type:"crossfade"}:void 0,l=n.map(c=>i.slide(c,{imageDurationMs:s.imageDurationMs,transition:d,region:!0})).filter(c=>!!c);this.slides=l.length===1&&l[0].untilEnd&&(s.onEnd??"loop")==="loop"?[{...l[0],untilEnd:!1,loop:!0}]:l,this.renderer.onMediaEnd(c=>{c===this.current&&!this.stopped&&(clearTimeout(this.timer),this.next("completed"))}),this.ready=this.newPass().then(()=>this.slides.length?this.renderer.prepare(this.slides[this.order[0]]):void 0).catch(()=>{})}async newPass(){let e=this.slides.length;this.order=this.playback.order==="shuffle"?await un(this.source.deviceId,this.path,this.pass,e):[...Array(e).keys()],this.at=0}async start(){this.slides.length&&await this.first()}bounds(e){return[this.source.bounds?.(e.item),this.list&&this.source.bounds?.(this.list)]}seek(e){let n=Date.now(),s=e;for(;s<this.order.length&&!this.bounds(this.slides[this.order[s]]).every(i=>Lt(i,n));)s++;return s}async first(){if(this.at=this.seek(0),this.at<this.order.length){this.empty=!1,await this.show();return}this.empty=!0,this.current=void 0,this.renderer.standby();let e=Date.now(),n=this.slides.flatMap(s=>this.bounds(s).map(i=>i?.from??0)).filter(s=>s>e);clearTimeout(this.timer),n.length&&(this.timer=setTimeout(()=>{!this.stopped&&!this.ended&&this.first()},hn(Math.min(...n)-e)))}async show(){let e=this.slides[this.order[this.at]];this.current=e;try{await this.renderer.show(e)}catch(s){if(this.stopped)return;this.o.onItem?.({type:"item.error",element:this.path,item:e.item,blob:e.blob.hash,...Ye(s)}),this.o.onItem?.({type:"item.end",element:this.path,item:e.item,blob:e.blob.hash,endedAt:Date.now(),reason:"error"}),this.schedule(fi);return}if(this.stopped)return;if(this.shownAt=Date.now(),this.open=e,this.o.onItem?.({type:"item.start",element:this.path,item:e.item,blob:e.blob.hash,contentType:e.blob.contentType,startedAt:this.shownAt,durationMs:e.durationMs}),e.pages&&(this.pages=new ie(this.renderer,e,this.shownAt,!1)),!(this.slides.length===1&&e.loop))this.schedule(e.durationMs+(e.untilEnd?5e3:0));else{let s=this.bounds(e).flatMap(i=>i?.until??[]);s.length&&this.schedule(Math.min(...s)-this.shownAt)}let n=this.peek();n&&n!==e&&this.renderer.prepare(n).catch(()=>{})}peek(){let e=this.seek(this.at+1);if(e<this.order.length)return this.slides[this.order[e]];if(this.o.endsWith!==this.path)return this.playback.onEnd==="hold"||this.playback.onEnd==="hide"||this.playback.order==="shuffle"?void 0:this.slides[this.order[0]]}schedule(e){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.next("completed")},hn(e))}async next(e){if(this.stopped||this.ended||!this.current)return;let n=()=>this.endOpen(e),s=this.seek(this.at+1);if(s<this.order.length){this.at=s,n(),await this.show();return}let o=this.o.onPassEnd?.(this.path)==="hold"?"hold":this.playback.onEnd??"loop";if(o==="hold"){this.ended=!0;return}if(o==="hide"){this.ended=!0,n(),this.renderer.standby(),this.region.style.visibility="hidden";return}this.pass++,await this.newPass(),n(),await this.first()}pause(){clearTimeout(this.timer),this.current&&this.remaining===void 0&&(this.remaining=Math.max(0,this.current.durationMs-(Date.now()-this.shownAt))),this.pages?.pause(),this.renderer.pause()}resume(){if(this.empty&&!this.ended&&!this.stopped){this.first();return}this.remaining===void 0||this.ended||(this.renderer.resume(),this.pages?.resume(),this.shownAt=Date.now()-(this.current.durationMs-this.remaining),this.schedule(this.remaining),this.remaining=void 0)}endOpen(e){this.pages?.stop(),this.pages=void 0;let n=this.open;n&&(this.open=void 0,this.o.onItem?.({type:"item.end",element:this.path,item:n.item,blob:n.blob.hash,endedAt:Date.now(),reason:e}))}stop(){this.endOpen("stopped"),this.stopped=!0,clearTimeout(this.timer),this.renderer.standby();for(let e of this.slides)this.renderer.release(e)}};var st=t=>t!=null&&t!==""&&!(Array.isArray(t)&&t.length===0);function gn(t,e,n,s=[]){let i=t[n];if(!i)return!1;if(!i.when)return!0;if(s.includes(n))return!1;let o=r=>String(r);for(let[r,a]of Object.entries(i.when)){if(!t[r]||r===n)return!1;let l=gn(t,e,r,[...s,n])?e[r]!==void 0?e[r]:t[r].default:void 0,c=Array.isArray(l)?l.map(o):st(l)?[o(l)]:[];if(a.set!==void 0&&a.set!==st(l)||a.anyOf&&!(st(l)&&!Array.isArray(l)?a.anyOf.map(o).includes(o(l)):c.some(p=>a.anyOf.map(o).includes(p)))||a.allOf&&!a.allOf.map(o).every(p=>c.includes(p))||a.noneOf&&c.some(p=>a.noneOf.map(o).includes(p)))return!1}return!0}function yn(t,e){return Object.keys(t).filter(n=>gn(t,e,n))}var da=re.apiVersion,bn=new Map(re.commands.map(t=>[t.name,t]));var W=class extends Error{constructor(n,s){super(s);h(this,"code",n)}};function wn(t,e){let n=e.capability??"read",s={kind:"app",app:e.id,instance:e.instance},i=new Set,o=!1,r=async(a,d={})=>{if(o)throw new W("NOT_ALLOWED","This app has been removed from the screen.");let l=bn.get(a);if(!l)throw new W("UNKNOWN",`There is no command ${a}.`);if(l.apps==="none")throw new W("NOT_ALLOWED",`Apps cannot call ${a}.`);if(l.apps==="control"&&n!=="control")throw new W("NOT_ALLOWED",`${a} needs capabilities.player: control in ${e.id}'s definition.`);let c=await t.call(a,d,s);if(c.ok)return c.result;if(c.code==="PREVIEW")return{applied:!1,reason:"preview"};throw new W(c.code,c.message)};return{app:e.id,instance:e.instance,capability:n,status:()=>t.status(),get:a=>t.status()[a],on(a,d){if(o)return()=>{};let l=t.on(a,d),c=()=>{l(),i.delete(c)};return i.add(c),c},time:()=>r("time.now"),call:r,play:()=>r("player.play"),pause:()=>r("player.pause"),stop:()=>r("player.stop"),next:()=>r("player.next"),previous:()=>r("player.previous"),finish:()=>r("app.finish"),dispose(){o=!0;for(let a of[...i])a()}}}var vn=["none","fade","scale","slide-up","slide-left"],Se=[["1920 \xD7 1080",1920,1080],["1080 \xD7 1920",1080,1920],["3840 \xD7 2160",3840,2160]],gi=[.25,.5,1,2,4],pe=3e4,En=5e3,yi={id:"med_preview",version:1},u={width:1920,height:1080,props:{},touched:new Set,playing:!0,speed:1,t:0,enter:"none",exit:"none",clipMs:800,timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,locale:navigator.language||"en-US",screen:{name:"Preview screen",lat:49.9,lon:-97.14,fields:"{}"},samples:{},tables:new Map,people:[]},I={frames:"",tables:{},controller:{status:{},events:{}}},B={people:new Set,player:new Map},Ce=[],rt=new Map,S=null,ot=null,at=null,Sn=-1,V=null,le=0,X={wall:0,t:0},Te=0,Pe=[],$=t=>document.getElementById(t),kn=$("stage"),bi=$("controls"),xn=$("report");function g(t,e={},...n){let s=document.createElement(t);for(let[i,o]of Object.entries(e))o==null||o===!1||(i.startsWith("on")&&typeof o=="function"?s.addEventListener(i.slice(2),o):i==="value"||i in s&&typeof o!="string"?s[i]=o:s.setAttribute(i,o===!0?"":String(o)));for(let i of n)i!=null&&s.append(i);return s}var wi=0;function E(t,e,n=!1){return e.id||(e.id=`c${++wi}`),n?g("div",{class:"field inline"},e,g("label",{for:e.id},t)):g("div",{class:"field"},g("label",{for:e.id},t),e)}function z(t,e,n){let s=g("select",{onchange:()=>n(s.value)},...t.map(i=>{let[o,r]=Array.isArray(i)?i:[i,i];return g("option",{value:o,selected:o===e},r)}));return s}async function An(){let[t,e,n,s]=await Promise.all([fetch("/app.json").then(r=>r.json()),fetch("/app.js"),fetch("/samples.json").then(r=>r.json()),fetch("/config.json").then(r=>r.json())]);I=s;for(let[r,a]of Object.entries(I.tables))u.tables.has(dt(r))||Tn(r,a);S=t,Sn=Number(e.headers.get("x-preview-version")??-1),at=e.ok?null:await e.text(),ot=e.ok?new Uint8Array(await e.arrayBuffer()):null;let i=S.params??{},o={};for(let[r,a]of Object.entries(i))o[r]=u.touched.has(r)?u.props[r]:a.default;u.props=o;for(let[r,a]of Object.entries(n))r in u.samples||(u.samples[r]=a);Pn(),document.title=`${S.name}: Wyndlass app preview`,$("title").textContent=S.name,$("subtitle").textContent=`${S.id} ${S.version}`}var dt=t=>`tbl_${(t.replace(/\.(json|csv)$/i,"").toLowerCase().replace(/[^a-z0-9]+/g,"")||"table").slice(0,26).padEnd(26,"0")}`;function vi(t){let e=[],n=[],s="",i=!1;for(let o=0;o<t.length;o++){let r=t[o];if(i){r==='"'&&t[o+1]==='"'?(s+='"',o++):r==='"'?i=!1:s+=r;continue}r==='"'?i=!0:r===","?(n.push(s),s=""):r===`
4
+ ${n}`))).slice(0,8),16),o=mi(i),r=Array.from({length:s},(a,l)=>l);for(let a=s-1;a>0;a--){let l=Math.floor(o()*(a+1));[r[a],r[l]]=[r[l],r[a]]}return r}var fi=250,gi=1e3;function Ct(t,e){let n=t.animation?.duration;if(n?.kind==="fixed")return{durationMs:n.ms};if(n?.kind==="untilPlaylistEnds"){let s=fn(t.elements,n.element);return{durationMs:(s?.source?.kind==="playlist"?e.playlist(s.source.item)??[]:s?.source?[{item:s.source.item}]:[]).reduce((r,a)=>r+(e.slide(a,{imageDurationMs:s?.playback?.imageDurationMs})?.durationMs??0),0)||1e4,untilPass:n.element}}return{durationMs:ae(t.animation?.clips??[]).autoDurationMs}}function mn(t,e,n=0){let s={},i=o=>{for(let r of o){if(r.type==="library"&&r.source){let a=r.source.kind==="playlist"?e.playlist(r.source.item)??[]:[{item:r.source.item}];s[r.source.item.id]={video:a.some(l=>{let d=e.slide(l);if(!d)return!1;if(d.blob.contentType.startsWith("video/"))return!0;let u=d.layout;return!!u&&n<4&&Object.values(mn(u.doc,u.source,n+1)).some(p=>p.video)})}}r.children&&i(r.children)}};return i(t.elements),s}function fn(t,e){for(let n of t){if(n.id===e)return n;let s=n.children&&fn(n.children,e);if(s)return s}}var J=class{constructor(e,n,s,i={}){h(this,"source",n);h(this,"o",i);h(this,"el");h(this,"ready");h(this,"unplayed");h(this,"doc");h(this,"motions");h(this,"native");h(this,"animated",[]);h(this,"loops",[]);h(this,"startedAt",0);h(this,"frame",0);h(this,"running",!1);h(this,"disposed",!1);h(this,"apps",[]);h(this,"heldAt");h(this,"late",[]);h(this,"overran",[]);h(this,"timers",[]);h(this,"reduced",typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches);let o=s.clientWidth||window.innerWidth,r=s.clientHeight||window.innerHeight,a=!e.profiles[0].width,l=a?{...e.profiles[0],width:o,height:r}:e.profiles[0];a&&(e={...e,elements:e.elements.map(b=>b.frame.w?b:{...b,frame:{x:0,y:0,w:o,h:r}})});let d=Math.min(o/l.width,r/l.height);this.el=D("div"),this.el.dataset.layout=l.id,this.el.style.cssText=`position:absolute;left:${(o-l.width*d)/2}px;top:${(r-l.height*d)/2}px;width:${l.width}px;height:${l.height}px;transform:scale(${d});transform-origin:0 0;overflow:hidden`,s.append(this.el),this.doc=e;let u=new Map,p=b=>{for(let M of b)u.set(M.id,M.frame),M.children&&p(M.children)};p(e.elements);let m=dn(e.animation?.clips??[],u);this.motions=m.motions;let y=new Set(m.unplayed.map(b=>`preset ${b}`)),A=[this.loadFonts()],f=i.media?tn(e,void 0,mn(e,n),i.media.capability):[];this.native=new Set(f.filter(b=>b.path.path==="native").map(b=>b.element));let k=(b,M)=>{for(let w of b){if(w.hidden)continue;let T=this.element(w,y,A);M.append(T),w.children?.length&&w.type==="shape"&&k(w.children,T)}};k(e.elements,this.el),e.background&&Jt(this.el,e.background,l,f.filter(b=>this.native.has(b.element)).map(b=>b.box)),this.unplayed=[...y].sort(),this.ready=Promise.all(A).then(()=>{})}path(e){return this.o.path?`${this.o.path}/${e}`:e}element(e,n,s){let i=Yt(e);Xt(i,e,this.source);let o=this.motions.get(e.id);if(o){let r={el:i,e,motion:o,text:e.type==="text"?i.textContent??"":void 0};this.animated.push(r),this.place(r,0)}if(e.type==="library"&&e.source){let r=e.source.kind==="playlist"?this.source.playlist(e.source.item)??[]:[{item:e.source.item}],a=new nt(i,r,e.playback??{},this.source,this.path(e.id),{...this.o,media:this.native.has(e.id)?this.o.media:void 0},e.source.kind==="playlist"?e.source.item:void 0);this.loops.push(a),s.push(a.ready);for(let l of a.unplayed)n.add(l)}else if(e.type==="app"){let r=e.app?this.source.app?.(e.app):null;r?s.push(this.mountApp(e,i,r)):n.add(`app ${e.app?.id??e.id}`)}return i}place(e,n){cn(e.el,e.e,ln(e.motion,e.e.frame,n,this.reduced),e.text);let s=e.e.type==="text"?e.el.firstElementChild:null;s&&qe(s)}loadFonts(){return en(this.doc.fonts??[],e=>this.source.font(e))}async mountApp(e,n,s){let i=this.path(e.id),o=e.app,r=(e.appInfo??s.info)?.id,a={now:()=>this.heldAt??Date.now(),startedAt:()=>this.startedAt||Date.now()},l=(p,m)=>this.o.onApp?.({type:"app.failed",element:i,item:o,...r?{app:r}:{},reason:p,...m?{error:m}:{}}),d=this.o.isolate?{...this.o.isolate,fonts:this.doc.fonts,font:p=>this.source.font(p)}:void 0,u=await Qt(e,n,s,this.source,a,this.o.appDeadlineMs??5e3,p=>{this.disposed?p.dispose():this.apps.push({app:p,box:n,path:i,id:e.id,item:o})},p=>l("error",p),d);if(!("held"in u)){if("failed"in u){l(u.failed,u.error);return}u.late&&(this.late.push(i),this.o.onAppLate?.(i,o.id),l("late"))}}async start(e=Date.now()){this.startedAt=e,this.heldAt=void 0,this.running=!0,this.hooks(e);let n=()=>{if(!this.running)return;let s=Date.now(),i=s-this.startedAt;for(let o of this.animated)this.place(o,i);for(let o of this.apps)o.app.update(s);this.frame=requestAnimationFrame(n)};(this.animated.length||this.apps.length)&&(this.frame=requestAnimationFrame(n)),await Promise.all(this.loops.map(s=>s.start()))}hooks(e){let n=this.doc.animation?.clips??[],s=ae(n).clips,i=typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches;for(let o of this.apps){let a=n.map((f,k)=>({c:f,t:s[k]})).filter(f=>f.c.element===o.id&&(f.c.kind==="enter"||f.c.kind==="exit")).filter(f=>o.app.has(f.c.kind));if(!a.length)continue;let l=o.box,d=this.animated.findIndex(f=>f.el===l);d>=0&&(this.animated.splice(d,1),Object.assign(l.style,{opacity:"",transform:"",clipPath:""}));let u=(f,k,b)=>this.hook(o,f.kind,{startAt:k,preset:f.preset??"fade",durationMs:f.durationMs,easing:f.easing,delayMs:0,...f.staggerMs?{staggerMs:f.staggerMs}:{},...b!==void 0?{page:b}:{},reducedMotion:i},l);for(let{c:f,t:k}of a)u(f,e+k.startMs);let p=o.app.paging();if(!p)continue;let m=a.find(f=>f.c.kind==="enter")?.c,y=a.find(f=>f.c.kind==="exit")?.c,A=pn(p.count,p.ms,Ct(this.doc,this.source).durationMs);for(let f=1;f<A.length;f++){let k=e+A[f].startMs;y&&u(y,k-y.durationMs,A[f-1].page),m&&u(m,k,A[f].page)}}}hook(e,n,s,i){let o=!1;e.app.hook(n,s).finally(()=>{o=!0});let r=s.startAt+s.durationMs;this.timers.push(setTimeout(()=>{o||!this.running||(this.overran.includes(e.path)||(this.overran.push(e.path),this.o.onAppOverran?.(e.path,e.item.id),this.o.onApp?.({type:"app.failed",element:e.path,item:e.item,reason:"overran"})),n==="exit"&&(i.style.visibility="hidden"))},Math.max(0,r+fi-Date.now())))}pause(){this.heldAt=Date.now();for(let e of this.loops)e.pause();for(let e of this.apps)e.app.update(this.heldAt)}seek(e){this.running=!1,cancelAnimationFrame(this.frame);for(let n of this.loops)n.pause();for(let n of this.animated)this.place(n,e);this.heldAt=this.startedAt+e;for(let n of this.apps)n.app.update(this.heldAt)}resume(){this.heldAt=void 0;for(let e of this.loops)e.resume()}paging(e){return this.apps.find(n=>n.id===e)?.app.paging()??null}dispose(){this.running=!1,this.disposed=!0,cancelAnimationFrame(this.frame);for(let e of this.timers)clearTimeout(e);for(let e of this.loops)e.stop();for(let e of this.apps)e.app.dispose();this.el.remove()}},hn=t=>Math.min(Math.max(0,t),2**31-1),nt=class{constructor(e,n,s,i,o,r,a){h(this,"region",e);h(this,"playback",s);h(this,"source",i);h(this,"path",o);h(this,"o",r);h(this,"list",a);h(this,"ready");h(this,"unplayed",[]);h(this,"renderer");h(this,"slides");h(this,"order",[]);h(this,"pass",0);h(this,"at",0);h(this,"timer");h(this,"current");h(this,"shownAt",0);h(this,"remaining");h(this,"stopped",!1);h(this,"ended",!1);h(this,"empty",!1);h(this,"open");h(this,"pages");this.renderer=new xe(e,{media:r.media,fit:s.fit,path:o,onItem:r.onItem,onPassEnd:r.onPassEnd,onApp:r.onApp}),s.transition==="slide"&&this.unplayed.push("transition slide");let l=s.transition==="fade"?{type:"crossfade"}:void 0,d=n.map(u=>i.slide(u,{imageDurationMs:s.imageDurationMs,transition:l,region:!0})).filter(u=>!!u);this.slides=d.length===1&&d[0].untilEnd&&(s.onEnd??"loop")==="loop"?[{...d[0],untilEnd:!1,loop:!0}]:d,this.renderer.onMediaEnd(u=>{u===this.current&&!this.stopped&&(clearTimeout(this.timer),this.next("completed"))}),this.ready=this.newPass().then(()=>this.slides.length?this.renderer.prepare(this.slides[this.order[0]]):void 0).catch(()=>{})}async newPass(){let e=this.slides.length;this.order=this.playback.order==="shuffle"?await un(this.source.deviceId,this.path,this.pass,e):[...Array(e).keys()],this.at=0}async start(){this.slides.length&&await this.first()}bounds(e){return[this.source.bounds?.(e.item),this.list&&this.source.bounds?.(this.list)]}seek(e){let n=Date.now(),s=e;for(;s<this.order.length&&!this.bounds(this.slides[this.order[s]]).every(i=>Lt(i,n));)s++;return s}async first(){if(this.at=this.seek(0),this.at<this.order.length){this.empty=!1,await this.show();return}this.empty=!0,this.current=void 0,this.renderer.standby();let e=Date.now(),n=this.slides.flatMap(s=>this.bounds(s).map(i=>i?.from??0)).filter(s=>s>e);clearTimeout(this.timer),n.length&&(this.timer=setTimeout(()=>{!this.stopped&&!this.ended&&this.first()},hn(Math.min(...n)-e)))}async show(){let e=this.slides[this.order[this.at]];this.current=e;try{await this.renderer.show(e)}catch(s){if(this.stopped)return;this.o.onItem?.({type:"item.error",element:this.path,item:e.item,blob:e.blob.hash,...Ye(s)}),this.o.onItem?.({type:"item.end",element:this.path,item:e.item,blob:e.blob.hash,endedAt:Date.now(),reason:"error"}),this.schedule(gi);return}if(this.stopped)return;if(this.shownAt=Date.now(),this.open=e,this.o.onItem?.({type:"item.start",element:this.path,item:e.item,blob:e.blob.hash,contentType:e.blob.contentType,startedAt:this.shownAt,durationMs:e.durationMs}),e.pages&&(this.pages=new ie(this.renderer,e,this.shownAt,!1)),!(this.slides.length===1&&e.loop))this.schedule(e.durationMs+(e.untilEnd?5e3:0));else{let s=this.bounds(e).flatMap(i=>i?.until??[]);s.length&&this.schedule(Math.min(...s)-this.shownAt)}let n=this.peek();n&&n!==e&&this.renderer.prepare(n).catch(()=>{})}peek(){let e=this.seek(this.at+1);if(e<this.order.length)return this.slides[this.order[e]];if(this.o.endsWith!==this.path)return this.playback.onEnd==="hold"||this.playback.onEnd==="hide"||this.playback.order==="shuffle"?void 0:this.slides[this.order[0]]}schedule(e){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.next("completed")},hn(e))}async next(e){if(this.stopped||this.ended||!this.current)return;let n=()=>this.endOpen(e),s=this.seek(this.at+1);if(s<this.order.length){this.at=s,n(),await this.show();return}let o=this.o.onPassEnd?.(this.path)==="hold"?"hold":this.playback.onEnd??"loop";if(o==="hold"){this.ended=!0;return}if(o==="hide"){this.ended=!0,n(),this.renderer.standby(),this.region.style.visibility="hidden";return}this.pass++,await this.newPass(),n(),await this.first()}pause(){clearTimeout(this.timer),this.current&&this.remaining===void 0&&(this.remaining=Math.max(0,this.current.durationMs-(Date.now()-this.shownAt))),this.pages?.pause(),this.renderer.pause()}resume(){if(this.empty&&!this.ended&&!this.stopped){this.first();return}this.remaining===void 0||this.ended||(this.renderer.resume(),this.pages?.resume(),this.shownAt=Date.now()-(this.current.durationMs-this.remaining),this.schedule(this.remaining),this.remaining=void 0)}endOpen(e){this.pages?.stop(),this.pages=void 0;let n=this.open;n&&(this.open=void 0,this.o.onItem?.({type:"item.end",element:this.path,item:n.item,blob:n.blob.hash,endedAt:Date.now(),reason:e}))}stop(){this.endOpen("stopped"),this.stopped=!0,clearTimeout(this.timer),this.renderer.standby();for(let e of this.slides)this.renderer.release(e)}};var st=t=>t!=null&&t!==""&&!(Array.isArray(t)&&t.length===0);function gn(t,e,n,s=[]){let i=t[n];if(!i)return!1;if(!i.when)return!0;if(s.includes(n))return!1;let o=r=>String(r);for(let[r,a]of Object.entries(i.when)){if(!t[r]||r===n)return!1;let d=gn(t,e,r,[...s,n])?e[r]!==void 0?e[r]:t[r].default:void 0,u=Array.isArray(d)?d.map(o):st(d)?[o(d)]:[];if(a.set!==void 0&&a.set!==st(d)||a.anyOf&&!(st(d)&&!Array.isArray(d)?a.anyOf.map(o).includes(o(d)):u.some(p=>a.anyOf.map(o).includes(p)))||a.allOf&&!a.allOf.map(o).every(p=>u.includes(p))||a.noneOf&&u.some(p=>a.noneOf.map(o).includes(p)))return!1}return!0}function yn(t,e){return Object.keys(t).filter(n=>gn(t,e,n))}var ca=re.apiVersion,bn=new Map(re.commands.map(t=>[t.name,t]));var z=class extends Error{constructor(n,s){super(s);h(this,"code",n)}};function wn(t,e){let n=e.capability??"read",s={kind:"app",app:e.id,instance:e.instance},i=new Set,o=!1,r=async(a,l={})=>{if(o)throw new z("NOT_ALLOWED","This app has been removed from the screen.");let d=bn.get(a);if(!d)throw new z("UNKNOWN",`There is no command ${a}.`);if(d.apps==="none")throw new z("NOT_ALLOWED",`Apps cannot call ${a}.`);if(d.apps==="control"&&n!=="control")throw new z("NOT_ALLOWED",`${a} needs capabilities.player: control in ${e.id}'s definition.`);let u=await t.call(a,l,s);if(u.ok)return u.result;if(u.code==="PREVIEW")return{applied:!1,reason:"preview"};throw new z(u.code,u.message)};return{app:e.id,instance:e.instance,capability:n,status:()=>t.status(),get:a=>t.status()[a],on(a,l){if(o)return()=>{};let d=t.on(a,l),u=()=>{d(),i.delete(u)};return i.add(u),u},time:()=>r("time.now"),call:r,play:()=>r("player.play"),pause:()=>r("player.pause"),stop:()=>r("player.stop"),next:()=>r("player.next"),previous:()=>r("player.previous"),finish:()=>r("app.finish"),dispose(){o=!0;for(let a of[...i])a()}}}var vn=["none","fade","scale","slide-up","slide-left"],Se=[["1920 \xD7 1080",1920,1080],["1080 \xD7 1920",1080,1920],["3840 \xD7 2160",3840,2160]],yi=[.25,.5,1,2,4],pe=3e4,En=5e3,bi={id:"med_preview",version:1},c={width:1920,height:1080,props:{},touched:new Set,playing:!0,speed:1,t:0,enter:"none",exit:"none",clipMs:800,timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,locale:navigator.language||"en-US",screen:{name:"Preview screen",lat:49.9,lon:-97.14,fields:"{}"},samples:{},tables:new Map,people:[]},I={frames:"",tables:{},controller:{status:{},events:{}}},U={people:new Set,player:new Map},Ce=[],rt=new Map,S=null,ot=null,at=null,Sn=-1,V=null,le=0,X={wall:0,t:0},Te=0,Pe=[],$=t=>document.getElementById(t),kn=$("stage"),wi=$("controls"),xn=$("report");function g(t,e={},...n){let s=document.createElement(t);for(let[i,o]of Object.entries(e))o==null||o===!1||(i.startsWith("on")&&typeof o=="function"?s.addEventListener(i.slice(2),o):i==="value"||i in s&&typeof o!="string"?s[i]=o:s.setAttribute(i,o===!0?"":String(o)));for(let i of n)i!=null&&s.append(i);return s}var vi=0;function E(t,e,n=!1){return e.id||(e.id=`c${++vi}`),n?g("div",{class:"field inline"},e,g("label",{for:e.id},t)):g("div",{class:"field"},g("label",{for:e.id},t),e)}function B(t,e,n){let s=g("select",{onchange:()=>n(s.value)},...t.map(i=>{let[o,r]=Array.isArray(i)?i:[i,i];return g("option",{value:o,selected:o===e},r)}));return s}async function An(){let[t,e,n,s]=await Promise.all([fetch("/app.json").then(r=>r.json()),fetch("/app.js"),fetch("/samples.json").then(r=>r.json()),fetch("/config.json").then(r=>r.json())]);I=s;for(let[r,a]of Object.entries(I.tables))c.tables.has(dt(r))||Tn(r,a);S=t,Sn=Number(e.headers.get("x-preview-version")??-1),at=e.ok?null:await e.text(),ot=e.ok?new Uint8Array(await e.arrayBuffer()):null;let i=S.params??{},o={};for(let[r,a]of Object.entries(i))o[r]=c.touched.has(r)?c.props[r]:a.default;c.props=o;for(let[r,a]of Object.entries(n))r in c.samples||(c.samples[r]=a);Pn(),document.title=`${S.name}: Wyndlass app preview`,$("title").textContent=S.name,$("subtitle").textContent=`${S.id} ${S.version}`}var dt=t=>`tbl_${(t.replace(/\.(json|csv)$/i,"").toLowerCase().replace(/[^a-z0-9]+/g,"")||"table").slice(0,26).padEnd(26,"0")}`;function ki(t){let e=[],n=[],s="",i=!1;for(let o=0;o<t.length;o++){let r=t[o];if(i){r==='"'&&t[o+1]==='"'?(s+='"',o++):r==='"'?i=!1:s+=r;continue}r==='"'?i=!0:r===","?(n.push(s),s=""):r===`
5
5
  `||r==="\r"?(r==="\r"&&t[o+1]===`
6
- `&&o++,n.push(s),e.push(n),n=[],s=""):s+=r}return(s||n.length)&&(n.push(s),e.push(n)),e.filter(o=>o.some(r=>r!==""))}function ki(t,e){let n=dt(t),s=(o,r)=>({table:n,version:1,rows:r,columns:o.map(a=>({key:a,label:a,type:r.length&&r.every(d=>d[a]===""||d[a]===void 0||Number.isFinite(Number(d[a])))?"number":"text"}))});if(/\.csv$/i.test(t)){let[o=[],...r]=vi(e),a=o.map(c=>c.trim()),d=r.map(c=>Object.fromEntries(a.map((p,m)=>[p,c[m]??""]))),l=s(a,d);for(let c of l.columns)if(c.type==="number")for(let p of d)p[c.key]!==""&&(p[c.key]=Number(p[c.key]));return l}let i=JSON.parse(e);if(i&&typeof i=="object"&&"columns"in i&&"rows"in i)return{...i,table:n};if(Array.isArray(i))return s([...new Set(i.flatMap(o=>Object.keys(o)))],i);throw new Error(`${t} is not a table: a snapshot, an array of rows, or CSV`)}function Tn(t,e){try{u.tables.set(dt(t),ki(t,e)),rt.delete(t)}catch(n){rt.set(t,String(n.message))}}function Pn(){let t=[...u.tables.keys()][0];if(!(!t||!S))for(let[e,n]of Object.entries(S.params??{}))n.type==="datatable"&&!u.props[e]&&(u.props[e]=t,u.touched.add(e))}function xi(){return{measuring:!0,now:()=>[{source:"preview",present:u.people.length,longestDwellS:u.people.length?Math.round((Date.now()-Math.min(...u.people))/1e3):0}],on(t){return B.people.add(t),()=>{B.people.delete(t)}}}}function it(t){if(t==="enter"&&u.people.push(Date.now()),t==="leave"&&!u.people.length)return;let e=t==="leave"?u.people.shift():0,n=t==="leave"?{type:"leave",source:"preview",dwellS:Math.round((Date.now()-e)/1e3)}:{type:t,source:"preview"};for(let s of B.people)s([n]);$("people-count").textContent=`${u.people.length} in view`}var Ai={status:()=>I.controller.status,on(t,e){let n=B.player.get(t)??new Set;return n.add(e),B.player.set(t,n),()=>{n.delete(e)}},async call(t,e,n){return Ce.push(`controller: ${n.app??"the app"} called ${t}, not applied (a preview)`),Y({}),{ok:!1,code:"PREVIEW",message:"a preview"}}},Mi={time:"time.now",play:"player.play",pause:"player.pause",stop:"player.stop",next:"player.next",previous:"player.previous",finish:"app.finish"};function Ei(t){let e=s=>i=>{throw Ce.push(`controller: ${s} refused, ${i.code??"error"}: ${i.message}`),Y({}),i},n={...t,call:(s,i)=>t.call(s,i).catch(e(s))};for(let[s,i]of Object.entries(Mi))n[s]=()=>t[s]().catch(e(i));return n}function Si(t){for(let e of[t.type,"*"])for(let n of B.player.get(e)??[])n(t)}function Ti(){return new Me({base:()=>location.origin,token:()=>{},fetch:(async t=>{let e=new URL(String(t),location.origin);if(e.pathname.endsWith("/v0/image"))return fetch("/picture.svg");let n=decodeURIComponent(e.pathname.split("/").pop()??"");if(!(n in u.samples))return new Response(`the preview has no answer for ${n}: declare it in app.json's data`,{status:404});let s=Date.now();return new Response(JSON.stringify({source:n,params:Object.fromEntries(e.searchParams),fetchedAt:new Date(s).toISOString(),expiresAt:new Date(s+36e5).toISOString(),stale:!1,data:u.samples[n]}))})})}function Pi(){let t=[...u.enter!=="none"?[{element:"app",kind:"enter",preset:u.enter,start:{kind:"at",ms:0},durationMs:u.clipMs,easing:"ease-out"}]:[],...u.exit!=="none"?[{element:"app",kind:"exit",preset:u.exit,start:{kind:"at",ms:pe-u.clipMs},durationMs:u.clipMs,easing:"ease-in"}]:[]];return{profiles:[{id:"preview",width:u.width,height:u.height}],elements:[{type:"app",id:"app",frame:{x:0,y:0,w:u.width,h:u.height},app:yi,props:u.props,...S.slots&&u.tables.size?{binding:{table:[...u.tables.keys()][0]}}:{},...S.capabilities?.sensors?{sensors:S.capabilities.sensors}:{},appInfo:{id:S.id,element:S.element,player:S.capabilities?.player==="control"?"control":"read"}}],animation:{duration:{kind:"fixed",ms:pe},clips:t}}}async function Le(){let t=++le;if(cancelAnimationFrame(Te),V?.dispose(),V=null,kn.replaceChildren(),Pe.length=0,Ce.length=0,!S||!ot){Y({building:!0});return}let e={};try{e=JSON.parse(u.screen.fields||"{}")}catch{}let n=ot,s={deviceId:"preview",slide:()=>null,playlist:()=>null,font:()=>{},app:()=>({hash:`preview-${le}`,bytes:n}),data:Ti(),place:{lat:u.screen.lat,lon:u.screen.lon},screen:{name:u.screen.name,type:"preview",tags:[],path:[{name:"Preview",kind:"organization"}],fields:e},timeZone:u.timeZone,locale:u.locale,preview:!1,snapshot:a=>u.tables.get(a)??null,...I.frames?{}:{...S.capabilities?.sensors?.includes("impressions")?{impressions:xi()}:{},controllerFor:a=>Ei(wn(Ai,a))}};B.people.clear(),B.player.clear();let i=performance.now(),o=!1,r=new J(Pi(),s,kn,{appDeadlineMs:En,...I.frames?{isolate:{frames:I.frames,apps:()=>!0}}:{},onAppLate:()=>{o=!0},onApp:a=>{t===le&&(Pe.push(`app.failed: ${a.reason}${a.error?` (${a.error})`:""}`),Y({}))}});V=r,Y({waiting:!0}),await r.ready,t===le&&(Y({readyMs:o?void 0:Math.round(performance.now()-i),late:o}),await r.start(Date.now()-u.t),t===le&&(r.seek(u.t),X={wall:Date.now(),t:u.t},Li(),Ii()))}function Li(){cancelAnimationFrame(Te);let t=()=>{if(V){if(u.playing){if(u.t=X.t+(Date.now()-X.wall)*u.speed,u.t>=pe){u.t=0,Le();return}V.seek(u.t)}$("clock-seek").value=String(Math.round(u.t)),$("clock-time").textContent=`${(u.t/1e3).toFixed(1)} s`,Te=requestAnimationFrame(t)}};Te=requestAnimationFrame(t)}function Ii(){let t=$("pages"),e=V?.paging("app")??null;t.replaceChildren(e&&e.count>1?g("div",{},g("p",{"data-testid":"pages"},`${e.count} pages of ${(e.ms/1e3).toFixed(1)} s`),g("div",{class:"row"},...Array.from({length:e.count},(n,s)=>g("button",{onclick:()=>Ln(s*e.ms)},`Page ${s+1}`)))):g("p",{"data-testid":"pages"},"One page"))}function Ln(t){u.t=Math.max(0,Math.min(pe-1,t)),X={wall:Date.now(),t:u.t},V?.seek(u.t)}function Y(t){let e=[];if(at?e.push(g("pre",{"data-testid":"build-error"},`The module does not build:
7
- ${at}`)):t.building&&e.push(g("span",{},"Building\u2026")),t.waiting&&e.push(g("span",{"data-testid":"ready"},"Waiting for ready()\u2026")),t.late)e.push(g("span",{class:"late","data-testid":"ready"},`Late: not ready within ${En/1e3} s. A screen reports app.failed (late) and shows the app as it is.`));else if(t.readyMs!==void 0)e.push(g("span",{class:"ok","data-testid":"ready"},`Ready in ${t.readyMs} ms`));else if(!t.waiting&&!t.building){let n=xn.querySelector('[data-testid="ready"]');n&&e.push(n)}for(let n of Pe)e.push(g("span",{class:"failed","data-testid":"failure"},n));I.frames&&Pe.some(n=>/SecurityError/.test(n))&&e.push(g("span",{class:"warn","data-testid":"frame-why"},"In its frame, as in the CMS, an app has no storage, cookies or parent page: localStorage, sessionStorage, document.cookie and parent's document throw SecurityError. Keep nothing there that matters; a screen may lose it too."));for(let n of Ce.slice(-5))e.push(g("span",{"data-testid":"command"},n));xn.replaceChildren(...e)}function Ci(t,e){let n=u.props[t],s=o=>{u.props[t]=o,u.touched.add(t),In(),L()},i=e.label??t;if(e.type==="boolean")return E(i,g("input",{type:"checkbox",checked:n===!0,onchange:o=>s(o.target.checked)}),!0);if(e.type==="enum"){let o=(e.values??[]).map(r=>r&&typeof r=="object"?[String(r.value),String(r.label??r.value)]:String(r));return E(i,z(o,String(n??""),s))}if(e.type==="number")return E(i,g("input",{type:"number",value:n===void 0?"":String(n),min:e.min,max:e.max,step:e.step??"any",onchange:o=>{let r=o.target.value;s(r===""?void 0:Number(r))}}));if(["string","text","color","url","datetime","duration","secret"].includes(e.type)){let o=e.type==="text"?g("textarea",{value:String(n??""),onchange:r=>s(r.target.value)}):g("input",{type:"text",value:String(n??""),placeholder:e.type==="color"?"#rrggbb or #rrggbbaa":void 0,onchange:r=>s(r.target.value)});return E(i,o)}return e.type==="datatable"?E(i,z(["",...u.tables.keys()],String(n??""),s)):E(`${i} (JSON)`,g("textarea",{value:n===void 0?"":JSON.stringify(n,null,2),onchange:o=>{let r=o.target.value.trim();try{s(r?JSON.parse(r):void 0)}catch{s(r)}}}))}function In(){let t=$("params"),e=S?.params??{},n=new Set(yn(e,u.props));t.replaceChildren(...Object.entries(e).filter(([s])=>n.has(s)).map(([s,i])=>Ci(s,i))),t.children.length||t.append(g("p",{},"No params."))}var Mn;function L(){clearTimeout(Mn),Mn=setTimeout(()=>{Le()},150)}function Ie(){let t=!Se.some(([,r,a])=>r===u.width&&a===u.height),e=g("input",{type:"number",value:String(u.width),min:64,max:7680,onchange:r=>{u.width=Number(r.target.value)||1920,L()}}),n=g("input",{type:"number",value:String(u.height),min:64,max:7680,onchange:r=>{u.height=Number(r.target.value)||1080,L()}}),s=g("button",{type:"button","aria-pressed":String(!u.playing),onclick:()=>{u.playing=!u.playing,s.textContent=u.playing?"Pause":"Play",s.setAttribute("aria-pressed",String(!u.playing)),X={wall:Date.now(),t:u.t}}},u.playing?"Pause":"Play"),i=g("input",{id:"clock-seek",type:"range",min:0,max:pe,step:100,value:String(u.t),oninput:r=>Ln(Number(r.target.value))}),o=[g("h2",{},"Screen"),E("Size",z([...Se.map(([r])=>r),"Custom"],t?"Custom":Se.find(([,r,a])=>r===u.width&&a===u.height)[0],r=>{let a=Se.find(([d])=>d===r);a&&(u.width=a[1],u.height=a[2]),Ie(),L()})),t?g("div",{class:"row"},E("Width",e),E("Height",n)):null,g("div",{class:"row"},E("Time zone",g("input",{type:"text",value:u.timeZone,onchange:r=>{u.timeZone=r.target.value||"UTC",L()}})),E("Locale",g("input",{type:"text",value:u.locale,onchange:r=>{u.locale=r.target.value||"en-US",L()}}))),E("Screen's name",g("input",{type:"text",value:u.screen.name,onchange:r=>{u.screen.name=r.target.value,L()}})),g("div",{class:"row"},E("Latitude",g("input",{type:"number",step:"any",value:String(u.screen.lat),onchange:r=>{u.screen.lat=Number(r.target.value),L()}})),E("Longitude",g("input",{type:"number",step:"any",value:String(u.screen.lon),onchange:r=>{u.screen.lon=Number(r.target.value),L()}}))),E("Screen's fields (JSON)",g("textarea",{value:u.screen.fields,onchange:r=>{u.screen.fields=r.target.value,L()}})),g("h2",{},"Clock"),g("div",{class:"row"},s,E("Speed",z(gi.map(r=>[String(r),`${r}\xD7`]),String(u.speed),r=>{X={wall:Date.now(),t:u.t},u.speed=Number(r)})),g("span",{id:"clock-time"},"0.0 s")),E("Time in the layout",i),g("h2",{},"Enter and exit"),g("div",{class:"row"},E("Enter",z(vn,u.enter,r=>{u.enter=r,L()})),E("Exit",z(vn,u.exit,r=>{u.exit=r,L()}))),E("Each lasts (ms)",g("input",{type:"number",min:0,max:5e3,step:100,value:String(u.clipMs),onchange:r=>{u.clipMs=Number(r.target.value)||0,L()}})),g("div",{id:"pages"}),g("h2",{},"Params"),g("div",{id:"params"}),I.frames?g("p",{"data-testid":"framed"},`In a frame on ${I.frames}, as in the CMS: no storage, cookies, parent page, controller or impressions.`):null,g("h2",{},"Tables"),...[...u.tables.values()].map(r=>g("p",{"data-testid":"table"},`${r.table}: ${r.rows.length} rows, ${r.columns.map(a=>a.key).join(", ")}`)),...[...rt].map(([r,a])=>g("p",{class:"failed","data-testid":"table-error"},`${r}: ${a}`)),E("Add a table (JSON or CSV)",g("input",{type:"file",accept:".json,.csv",onchange:async r=>{let a=r.target.files?.[0];a&&(Tn(a.name,await a.text()),Pn(),Ie(),L())}})),...S?.capabilities?.sensors?.includes("impressions")&&!I.frames?[g("h2",{},"People at the screen"),g("div",{class:"row"},g("button",{type:"button",onclick:()=>it("enter")},"Someone arrives"),g("button",{type:"button",onclick:()=>it("look")},"Someone looks"),g("button",{type:"button",onclick:()=>it("leave")},"Someone leaves")),g("p",{id:"people-count"},`${u.people.length} in view`)]:[],...I.frames?[]:[g("h2",{},"The player's controller"),g("p",{},`The app may ${S?.capabilities?.player==="control"?"read it and call its commands":"read it (capabilities.player: read)"}. Commands are not applied here.`),...Object.keys(I.controller.events).length?[E("Event",z(Object.keys(I.controller.events),Object.keys(I.controller.events)[0],r=>{$("event-json").value=JSON.stringify(I.controller.events[r],null,2)})),E("Its JSON",g("textarea",{id:"event-json",rows:6,value:JSON.stringify(Object.values(I.controller.events)[0],null,2)})),g("button",{type:"button",onclick:()=>{try{Si(JSON.parse($("event-json").value))}catch{}}},"Send to the app")]:[]],g("h2",{},"Data"),...Object.keys(u.samples).map(r=>E(`${r} answer's data (JSON)`,g("textarea",{"data-source":r,value:JSON.stringify(u.samples[r],null,2),rows:8,onchange:a=>{try{u.samples[r]=JSON.parse(a.target.value),L()}catch{}}}))),Object.keys(u.samples).length?null:g("p",{},"The app declares no data source with a sample (weather, news or calendar).")];bi.replaceChildren(...o.filter(r=>r!==null)),In()}async function Ri(){await An(),Ie(),await Le();let t=new EventSource("/events"),e=()=>{An().then(()=>(Ie(),Le()))};t.addEventListener("hello",n=>{JSON.parse(n.data).version!==Sn&&e()}),t.addEventListener("reload",e),window.addEventListener("resize",()=>L())}Ri();})();
6
+ `&&o++,n.push(s),e.push(n),n=[],s=""):s+=r}return(s||n.length)&&(n.push(s),e.push(n)),e.filter(o=>o.some(r=>r!==""))}function xi(t,e){let n="";for(let i of t.normalize("NFD").replace(/\p{Mn}/gu,""))/[A-Za-z0-9]/.test(i)?n+=i.toLowerCase():n&&!n.endsWith("_")&&(n+="_");n=n.replace(/^_+|_+$/g,""),/^[a-z]/.test(n)||(n=`c_${n}`),n=n.length>60?n.slice(0,60).replace(/_+$/,""):n;let s=n;for(let i=2;[...e].some(o=>o.toLowerCase()===s);i++)s=`${n}_${i}`;return e.add(s),s}function Ai(t,e){let n=dt(t),s=(o,r)=>({table:n,version:1,rows:r,columns:o.map(({key:a,label:l})=>({key:a,label:l,type:r.length&&r.every(d=>d[a]===""||d[a]===void 0||Number.isFinite(Number(d[a])))?"number":"text"}))});if(/\.csv$/i.test(t)){let[o=[],...r]=ki(e),a=new Set,l=o.map(p=>({label:p.trim(),key:xi(p.trim()||"column",a)})),d=r.map(p=>Object.fromEntries(l.map((m,y)=>[m.key,p[y]??""]))),u=s(l,d);for(let p of u.columns)if(p.type==="number")for(let m of d)m[p.key]!==""&&(m[p.key]=Number(m[p.key]));return u}let i=JSON.parse(e);if(i&&typeof i=="object"&&"columns"in i&&"rows"in i)return{...i,table:n};if(Array.isArray(i))return s([...new Set(i.flatMap(o=>Object.keys(o)))].map(o=>({key:o,label:o})),i);throw new Error(`${t} is not a table: a snapshot, an array of rows, or CSV`)}function Tn(t,e){try{c.tables.set(dt(t),Ai(t,e)),rt.delete(t)}catch(n){rt.set(t,String(n.message))}}function Pn(){let t=[...c.tables.keys()][0];if(!(!t||!S))for(let[e,n]of Object.entries(S.params??{}))n.type==="datatable"&&!c.props[e]&&(c.props[e]=t,c.touched.add(e))}function Mi(){return{measuring:!0,now:()=>[{source:"preview",present:c.people.length,longestDwellS:c.people.length?Math.round((Date.now()-Math.min(...c.people))/1e3):0}],on(t){return U.people.add(t),()=>{U.people.delete(t)}}}}function it(t){if(t==="enter"&&c.people.push(Date.now()),t==="leave"&&!c.people.length)return;let e=t==="leave"?c.people.shift():0,n=t==="leave"?{type:"leave",source:"preview",dwellS:Math.round((Date.now()-e)/1e3)}:{type:t,source:"preview"};for(let s of U.people)s([n]);$("people-count").textContent=`${c.people.length} in view`}var Ei={status:()=>I.controller.status,on(t,e){let n=U.player.get(t)??new Set;return n.add(e),U.player.set(t,n),()=>{n.delete(e)}},async call(t,e,n){return Ce.push(`controller: ${n.app??"the app"} called ${t}, not applied (a preview)`),Y({}),{ok:!1,code:"PREVIEW",message:"a preview"}}},Si={time:"time.now",play:"player.play",pause:"player.pause",stop:"player.stop",next:"player.next",previous:"player.previous",finish:"app.finish"};function Ti(t){let e=s=>i=>{throw Ce.push(`controller: ${s} refused, ${i.code??"error"}: ${i.message}`),Y({}),i},n={...t,call:(s,i)=>t.call(s,i).catch(e(s))};for(let[s,i]of Object.entries(Si))n[s]=()=>t[s]().catch(e(i));return n}function Pi(t){for(let e of[t.type,"*"])for(let n of U.player.get(e)??[])n(t)}function Li(){return new Me({base:()=>location.origin,token:()=>{},fetch:(async t=>{let e=new URL(String(t),location.origin);if(e.pathname.endsWith("/v0/image"))return fetch("/picture.svg");let n=decodeURIComponent(e.pathname.split("/").pop()??"");if(!(n in c.samples))return new Response(`the preview has no answer for ${n}: declare it in app.json's data`,{status:404});let s=Date.now();return new Response(JSON.stringify({source:n,params:Object.fromEntries(e.searchParams),fetchedAt:new Date(s).toISOString(),expiresAt:new Date(s+36e5).toISOString(),stale:!1,data:c.samples[n]}))})})}function Ii(){let t=[...c.enter!=="none"?[{element:"app",kind:"enter",preset:c.enter,start:{kind:"at",ms:0},durationMs:c.clipMs,easing:"ease-out"}]:[],...c.exit!=="none"?[{element:"app",kind:"exit",preset:c.exit,start:{kind:"at",ms:pe-c.clipMs},durationMs:c.clipMs,easing:"ease-in"}]:[]];return{profiles:[{id:"preview",width:c.width,height:c.height}],elements:[{type:"app",id:"app",frame:{x:0,y:0,w:c.width,h:c.height},app:bi,props:c.props,...S.slots&&c.tables.size?{binding:{table:[...c.tables.keys()][0]}}:{},...S.capabilities?.sensors?{sensors:S.capabilities.sensors}:{},appInfo:{id:S.id,element:S.element,player:S.capabilities?.player==="control"?"control":"read"}}],animation:{duration:{kind:"fixed",ms:pe},clips:t}}}async function Le(){let t=++le;if(cancelAnimationFrame(Te),V?.dispose(),V=null,kn.replaceChildren(),Pe.length=0,Ce.length=0,!S||!ot){Y({building:!0});return}let e={};try{e=JSON.parse(c.screen.fields||"{}")}catch{}let n=ot,s={deviceId:"preview",slide:()=>null,playlist:()=>null,font:()=>{},app:()=>({hash:`preview-${le}`,bytes:n}),data:Li(),place:{lat:c.screen.lat,lon:c.screen.lon},screen:{name:c.screen.name,type:"preview",tags:[],path:[{name:"Preview",kind:"organization"}],fields:e},timeZone:c.timeZone,locale:c.locale,preview:!1,snapshot:a=>c.tables.get(a)??null,...I.frames?{}:{...S.capabilities?.sensors?.includes("impressions")?{impressions:Mi()}:{},controllerFor:a=>Ti(wn(Ei,a))}};U.people.clear(),U.player.clear();let i=performance.now(),o=!1,r=new J(Ii(),s,kn,{appDeadlineMs:En,...I.frames?{isolate:{frames:I.frames,apps:()=>!0}}:{},onAppLate:()=>{o=!0},onApp:a=>{t===le&&(Pe.push(`app.failed: ${a.reason}${a.error?` (${a.error})`:""}`),Y({}))}});V=r,Y({waiting:!0}),await r.ready,t===le&&(Y({readyMs:o?void 0:Math.round(performance.now()-i),late:o}),await r.start(Date.now()-c.t),t===le&&(r.seek(c.t),X={wall:Date.now(),t:c.t},Ci(),Ri()))}function Ci(){cancelAnimationFrame(Te);let t=()=>{if(V){if(c.playing){if(c.t=X.t+(Date.now()-X.wall)*c.speed,c.t>=pe){c.t=0,Le();return}V.seek(c.t)}$("clock-seek").value=String(Math.round(c.t)),$("clock-time").textContent=`${(c.t/1e3).toFixed(1)} s`,Te=requestAnimationFrame(t)}};Te=requestAnimationFrame(t)}function Ri(){let t=$("pages"),e=V?.paging("app")??null;t.replaceChildren(e&&e.count>1?g("div",{},g("p",{"data-testid":"pages"},`${e.count} pages of ${(e.ms/1e3).toFixed(1)} s`),g("div",{class:"row"},...Array.from({length:e.count},(n,s)=>g("button",{onclick:()=>Ln(s*e.ms)},`Page ${s+1}`)))):g("p",{"data-testid":"pages"},"One page"))}function Ln(t){c.t=Math.max(0,Math.min(pe-1,t)),X={wall:Date.now(),t:c.t},V?.seek(c.t)}function Y(t){let e=[];if(at?e.push(g("pre",{"data-testid":"build-error"},`The module does not build:
7
+ ${at}`)):t.building&&e.push(g("span",{},"Building\u2026")),t.waiting&&e.push(g("span",{"data-testid":"ready"},"Waiting for ready()\u2026")),t.late)e.push(g("span",{class:"late","data-testid":"ready"},`Late: not ready within ${En/1e3} s. A screen reports app.failed (late) and shows the app as it is.`));else if(t.readyMs!==void 0)e.push(g("span",{class:"ok","data-testid":"ready"},`Ready in ${t.readyMs} ms`));else if(!t.waiting&&!t.building){let n=xn.querySelector('[data-testid="ready"]');n&&e.push(n)}for(let n of Pe)e.push(g("span",{class:"failed","data-testid":"failure"},n));I.frames&&Pe.some(n=>/SecurityError/.test(n))&&e.push(g("span",{class:"warn","data-testid":"frame-why"},"In its frame, as in the CMS, an app has no storage, cookies or parent page: localStorage, sessionStorage, document.cookie and parent's document throw SecurityError. Keep nothing there that matters; a screen may lose it too."));for(let n of Ce.slice(-5))e.push(g("span",{"data-testid":"command"},n));xn.replaceChildren(...e)}function Ni(){let t=Object.entries(S?.params??{}).find(([,n])=>n.type==="datatable")?.[0],e=S?.slots?[...c.tables.keys()][0]:t?c.props[t]:void 0;return typeof e=="string"?c.tables.get(e):void 0}var In=t=>t.label&&t.label!==t.key?`${t.label} (${t.key})`:t.key;function Oi(t,e){let n=c.props[t],s=o=>{c.props[t]=o,c.touched.add(t),Cn(),L()},i=e.label??t;if(e.type==="boolean")return E(i,g("input",{type:"checkbox",checked:n===!0,onchange:o=>s(o.target.checked)}),!0);if(e.type==="enum"){let o=(e.values??[]).map(r=>r&&typeof r=="object"?[String(r.value),String(r.label??r.value)]:String(r));return E(i,B(o,String(n??""),s))}if(e.type==="number")return E(i,g("input",{type:"number",value:n===void 0?"":String(n),min:e.min,max:e.max,step:e.step??"any",onchange:o=>{let r=o.target.value;s(r===""?void 0:Number(r))}}));if(["string","text","color","url","datetime","duration","secret"].includes(e.type)){let o=e.type==="text"?g("textarea",{value:String(n??""),onchange:r=>s(r.target.value)}):g("input",{type:"text",value:String(n??""),placeholder:e.type==="color"?"#rrggbb or #rrggbbaa":void 0,onchange:r=>s(r.target.value)});return E(i,o)}return e.type==="datatable"?E(i,B(["",...c.tables.keys()],String(n??""),s)):e.type==="column"?E(i,B(["",...(Ni()?.columns??[]).map(o=>[o.key,In(o)])],String(n??""),s)):E(`${i} (JSON)`,g("textarea",{value:n===void 0?"":JSON.stringify(n,null,2),onchange:o=>{let r=o.target.value.trim();try{s(r?JSON.parse(r):void 0)}catch{s(r)}}}))}function Cn(){let t=$("params"),e=S?.params??{},n=new Set(yn(e,c.props));t.replaceChildren(...Object.entries(e).filter(([s])=>n.has(s)).map(([s,i])=>Oi(s,i))),t.children.length||t.append(g("p",{},"No params."))}var Mn;function L(){clearTimeout(Mn),Mn=setTimeout(()=>{Le()},150)}function Ie(){let t=!Se.some(([,r,a])=>r===c.width&&a===c.height),e=g("input",{type:"number",value:String(c.width),min:64,max:7680,onchange:r=>{c.width=Number(r.target.value)||1920,L()}}),n=g("input",{type:"number",value:String(c.height),min:64,max:7680,onchange:r=>{c.height=Number(r.target.value)||1080,L()}}),s=g("button",{type:"button","aria-pressed":String(!c.playing),onclick:()=>{c.playing=!c.playing,s.textContent=c.playing?"Pause":"Play",s.setAttribute("aria-pressed",String(!c.playing)),X={wall:Date.now(),t:c.t}}},c.playing?"Pause":"Play"),i=g("input",{id:"clock-seek",type:"range",min:0,max:pe,step:100,value:String(c.t),oninput:r=>Ln(Number(r.target.value))}),o=[g("h2",{},"Screen"),E("Size",B([...Se.map(([r])=>r),"Custom"],t?"Custom":Se.find(([,r,a])=>r===c.width&&a===c.height)[0],r=>{let a=Se.find(([l])=>l===r);a&&(c.width=a[1],c.height=a[2]),Ie(),L()})),t?g("div",{class:"row"},E("Width",e),E("Height",n)):null,g("div",{class:"row"},E("Time zone",g("input",{type:"text",value:c.timeZone,onchange:r=>{c.timeZone=r.target.value||"UTC",L()}})),E("Locale",g("input",{type:"text",value:c.locale,onchange:r=>{c.locale=r.target.value||"en-US",L()}}))),E("Screen's name",g("input",{type:"text",value:c.screen.name,onchange:r=>{c.screen.name=r.target.value,L()}})),g("div",{class:"row"},E("Latitude",g("input",{type:"number",step:"any",value:String(c.screen.lat),onchange:r=>{c.screen.lat=Number(r.target.value),L()}})),E("Longitude",g("input",{type:"number",step:"any",value:String(c.screen.lon),onchange:r=>{c.screen.lon=Number(r.target.value),L()}}))),E("Screen's fields (JSON)",g("textarea",{value:c.screen.fields,onchange:r=>{c.screen.fields=r.target.value,L()}})),g("h2",{},"Clock"),g("div",{class:"row"},s,E("Speed",B(yi.map(r=>[String(r),`${r}\xD7`]),String(c.speed),r=>{X={wall:Date.now(),t:c.t},c.speed=Number(r)})),g("span",{id:"clock-time"},"0.0 s")),E("Time in the layout",i),g("h2",{},"Enter and exit"),g("div",{class:"row"},E("Enter",B(vn,c.enter,r=>{c.enter=r,L()})),E("Exit",B(vn,c.exit,r=>{c.exit=r,L()}))),E("Each lasts (ms)",g("input",{type:"number",min:0,max:5e3,step:100,value:String(c.clipMs),onchange:r=>{c.clipMs=Number(r.target.value)||0,L()}})),g("div",{id:"pages"}),g("h2",{},"Params"),g("div",{id:"params"}),I.frames?g("p",{"data-testid":"framed"},`In a frame on ${I.frames}, as in the CMS: no storage, cookies, parent page, controller or impressions.`):null,g("h2",{},"Tables"),...[...c.tables.values()].map(r=>g("p",{"data-testid":"table"},`${r.table}: ${r.rows.length} rows, ${r.columns.map(In).join(", ")}`)),...[...rt].map(([r,a])=>g("p",{class:"failed","data-testid":"table-error"},`${r}: ${a}`)),E("Add a table (JSON or CSV)",g("input",{type:"file",accept:".json,.csv",onchange:async r=>{let a=r.target.files?.[0];a&&(Tn(a.name,await a.text()),Pn(),Ie(),L())}})),...S?.capabilities?.sensors?.includes("impressions")&&!I.frames?[g("h2",{},"People at the screen"),g("div",{class:"row"},g("button",{type:"button",onclick:()=>it("enter")},"Someone arrives"),g("button",{type:"button",onclick:()=>it("look")},"Someone looks"),g("button",{type:"button",onclick:()=>it("leave")},"Someone leaves")),g("p",{id:"people-count"},`${c.people.length} in view`)]:[],...I.frames?[]:[g("h2",{},"The player's controller"),g("p",{},`The app may ${S?.capabilities?.player==="control"?"read it and call its commands":"read it (capabilities.player: read)"}. Commands are not applied here.`),...Object.keys(I.controller.events).length?[E("Event",B(Object.keys(I.controller.events),Object.keys(I.controller.events)[0],r=>{$("event-json").value=JSON.stringify(I.controller.events[r],null,2)})),E("Its JSON",g("textarea",{id:"event-json",rows:6,value:JSON.stringify(Object.values(I.controller.events)[0],null,2)})),g("button",{type:"button",onclick:()=>{try{Pi(JSON.parse($("event-json").value))}catch{}}},"Send to the app")]:[]],g("h2",{},"Data"),...Object.keys(c.samples).map(r=>E(`${r} answer's data (JSON)`,g("textarea",{"data-source":r,value:JSON.stringify(c.samples[r],null,2),rows:8,onchange:a=>{try{c.samples[r]=JSON.parse(a.target.value),L()}catch{}}}))),Object.keys(c.samples).length?null:g("p",{},"The app declares no data source with a sample (weather, news or calendar).")];wi.replaceChildren(...o.filter(r=>r!==null)),Cn()}async function $i(){await An(),Ie(),await Le();let t=new EventSource("/events"),e=()=>{An().then(()=>(Ie(),Le()))};t.addEventListener("hello",n=>{JSON.parse(n.data).version!==Sn&&e()}),t.addEventListener("reload",e),window.addEventListener("resize",()=>L())}$i();})();
package/dist/react.d.ts CHANGED
@@ -47,6 +47,6 @@ export declare function useData<T = unknown>(source: string, params: Params, eve
47
47
  answer?: DataAnswer<T>;
48
48
  error?: Error;
49
49
  };
50
- /** A data table's snapshot (null without one), read again each frame the layout draws: a bound table's rows change with time. */
50
+ /** A data table's snapshot, as snapshot() gives it (null when the screen has none), read again each frame the layout draws: a bound table's rows change with time. */
51
51
  export declare function useSnapshot(table: string): unknown | null;
52
52
  export {};
package/dist/react.js CHANGED
@@ -141,7 +141,7 @@ export function useData(source, params, everyMs = 10 * 60000) {
141
141
  }, [app, key, everyMs]);
142
142
  return state.key === key ? { answer: state.answer, error: state.error } : { answer: kept(app, source, params) };
143
143
  }
144
- /** A data table's snapshot (null without one), read again each frame the layout draws: a bound table's rows change with time. */
144
+ /** A data table's snapshot, as snapshot() gives it (null when the screen has none), read again each frame the layout draws: a bound table's rows change with time. */
145
145
  export function useSnapshot(table) {
146
146
  const [app] = useBridge();
147
147
  useNow();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyndlass/app-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Build apps for Wyndlass's screens: web components on the app contract (Player API 2), with the runtime Wyndlass's own apps use.",
5
5
  "keywords": [
6
6
  "wyndlass",
@@ -40,6 +40,7 @@
40
40
  "dist",
41
41
  "cli",
42
42
  "templates",
43
+ "skill",
43
44
  "protocol",
44
45
  "README.md",
45
46
  "LICENSE"
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: wyndlass-app
3
+ description: Build an app for Wyndlass's digital signage screens with @wyndlass/app-sdk, preview it, check and pack it, then import, place and schedule it through the Wyndlass MCP. Use when the person wants their own app, widget or live element on their Wyndlass screens, or wants to change one built with @wyndlass/app-sdk.
4
+ ---
5
+
6
+ # Build a Wyndlass app
7
+
8
+ An app is a web component that Wyndlass's screens run inside a layout: one ES2020 module and its definition, `app.json`. You build it on this computer with `@wyndlass/app-sdk`, the person's organization imports it, and a person reviews it before screens play it.
9
+
10
+ Work through the steps in order. Ask before anything that changes Wyndlass: importing, placing in a layout, scheduling.
11
+
12
+ ## 1. Ask
13
+
14
+ Before writing code, ask the person, in one message:
15
+ - **What it shows, and to whom:** the content, and who reads it from how far away (a queue board read from across a room needs large type).
16
+ - **The screens' orientation:** landscape (1920 × 1080), portrait (1080 × 1920), or both.
17
+ - **The data it needs, and where from:** the weather, news feeds, pictures or calendars (data sources Wyndlass gives apps); a data table in Wyndlass; or the organization's own HTTPS service.
18
+ - **Its name, and an id in their own domain,** such as `com.example.queue-board`. Ids under `com.wyndlass.` are Wyndlass's own, and are refused.
19
+
20
+ ## 2. Scaffold
21
+
22
+ ```sh
23
+ npx @wyndlass/app-sdk create <folder> --template react --id <id> --name "<name>"
24
+ cd <folder>
25
+ npm install
26
+ ```
27
+
28
+ Use the `react` template unless the person asks for plain TypeScript (`vanilla`). Read [references/templates.md](references/templates.md) for what each file is, and [references/contract.md](references/contract.md) before you change the code.
29
+
30
+ ## 3. Build and preview
31
+
32
+ - Change `app.json` (its params, the data it reads, what it asks for) and `src/` to do what the person asked. Keep the rules below.
33
+ - Run `npm run typecheck` and `npm run check` after each change, and fix what they say.
34
+ - Start `npm run preview` in the background. It serves `http://127.0.0.1:5199/` on this computer only, and draws the app again on every save.
35
+ - **Ask the person to look,** at the screen size they gave and at the other orientation: under **Screen**, set **Size** to 1920 × 1080, then 1080 × 1920. Ask them to play the clock and to pause it. Change what they ask for.
36
+ - **Before importing,** run `npm run preview -- --frame` once and ask them to look again. In the CMS an app runs in a sandboxed frame with no storage, cookies or parent page. An app that fails there works on screens but not in the designer. The frame is served from a second port: use a regular browser for this check, since a browser built into an agent's app may block that port (`net::ERR_BLOCKED_BY_CLIENT`), and the app then reports `app.failed: load`.
37
+ - **Tables in the preview** are keyed as Wyndlass keys a table's columns: from their labels, lowercased, with underscores (a CSV header `Now serving` is the column `now_serving`). A `column` param holds that key, as it will on screens.
38
+
39
+ ## 4. Pack and check
40
+
41
+ ```sh
42
+ npm run pack
43
+ ```
44
+
45
+ It runs `check` first and writes `dist/<id>-<version>.zip` only when Wyndlass would take it. Its first line gives the zip's size in bytes, which the import asks for. Fix every refusal; heed the warnings (a module over 1 MiB, no `ready()`).
46
+
47
+ ## 5. Import, with the Wyndlass MCP
48
+
49
+ Follow [references/mcp.md](references/mcp.md):
50
+ 1. `whoami`: who you act as, and in which organization. Ask the person **where** to import it (a place, from `node.list`).
51
+ 2. `app.prepareImport` with the place, the zip's file name and its size. It answers an address and headers, open for an hour.
52
+ 3. `npx wyndlass-app upload "<address>" dist/<zip> --header "<name>: <value>"`, once per header it gave, `Content-Type` included. Never pass the file's bytes through the conversation.
53
+ 4. `app.import` with the same place and the upload's id.
54
+ 5. `app.list` until the version is checked: approved, held for review, refused or rejected.
55
+
56
+ - **Refused:** fix the cause in the source, raise `version` in `app.json`, pack and import again.
57
+ - **Held:** give the person the CMS link `app.import` answered, say why it is held, and wait. A person who may approve media approves it in the CMS. You never approve.
58
+ - **The same bytes again** are answered with the version the organization has; other bytes need a new `version`.
59
+
60
+ ## 6. Place it
61
+
62
+ Read `guide.read` with topic `layouts`, sections `elements` and `apps`: its answer is the sections' text. Then, at a place the person names:
63
+ - `layout.save` with the app's item from `app.list`, in a new layout or one they choose (`layout.get` first). An app element is `{"type": "app", "id": "queue", "frame": {"x": 0, "y": 0, "w": 1920, "h": 1080}, "app": {"id": "med_…"}, "props": {…}}`. Its `props` are the app's params as `app.json` types them: a color param is a plain `"#RRGGBB"`, unlike a layout element's own colors (`{"value": "#RRGGBB"}`); a `datatable` param is the table's id (`tbl_…`); a `column` param is a column's key, from `datatable.get`.
64
+ - `layout.check`, and tell them what it says.
65
+
66
+ A table named in a `datatable` param reaches the app on screens as it is: the app needs no `binding` for it. A `binding` is for an app with `slots`, or to choose rows (a filter, a sort, a limit) — the layouts guide's apps section.
67
+
68
+ A held version can be placed and previewed. Screens play it only once it is approved and checked.
69
+
70
+ ## 7. Schedule, only with the person's yes
71
+
72
+ Ask what should play where, and when. Then `campaign.save` at the place and times they gave, and show them the draft. `campaign.publish` previews first: tell them what goes live where, and confirm only once they agree. Never schedule at the organization's top place unless they say so.
73
+
74
+ ## 8. A new version
75
+
76
+ Raise `version` in `app.json`, then pack and import again (steps 4 and 5). Layouts that name no version take the new one once it is approved and checked. A layout whose app element pins a `version` (the item's) moves only when you save it with the new one: show the person first.
77
+
78
+ ## Rules every app keeps
79
+
80
+ Screens or the CMS enforce each of these:
81
+ - **Time from the layout's clock:** `now()`, `draw(epochMs)` or `useNow()`, never `Date.now()`, so a seek or a pause draws the right moment.
82
+ - **`ready()` within 5 s** of the app's start, or the screen reports it late.
83
+ - **ES2020, one module:** `pack` takes care of it. Don't import from a CDN at run time.
84
+ - **No `localStorage`** for anything that matters: in the CMS the app has none.
85
+ - **No secrets** in params or the bundle: anyone who can read the layout or the app can read both.
86
+ - **Data only from the sources `app.json` declares,** and only those apps may read: `weather`, `news`, `image`, `calendar`.
87
+ - **Network hosts declared truthfully** in `capabilities.network`: a person approves them.
88
+
89
+ ## When something goes wrong
90
+
91
+ - **A tool you need is missing:** call `whoami`. The import tools are listed only while the organization has imported apps turned on at its top place. An Owner or Admin turns them on in the CMS, under **Settings**, **Imported apps**.
92
+ - **A screen doesn't show the app:** `device.explainPlay` for that screen. A screen whose player is too old for imported apps leaves it out, and plays the rest of the layout.
93
+ - **More detail:** [references/contract.md](references/contract.md) (the runtime, the definition, the limits, what screens do) and [references/mcp.md](references/mcp.md) (each tool's answer and what to do next).
@@ -0,0 +1,91 @@
1
+ # The contract: the module, the definition, the limits
2
+
3
+ ## The module
4
+
5
+ - **One ES2020 module,** whose default export is the element's class. `pack` bundles and minifies it into `app.js`.
6
+ - **The host defines the class under a tag of its own** (the definition's `element`, a hyphen and a suffix) and hands it its context once, before it joins the page. Never call `customElements.define` or the SDK's `define()`: the host does it.
7
+ - **It imports nothing at run time.** `pack` bundles everything the source imports.
8
+
9
+ ## The runtime (`@wyndlass/app-sdk`)
10
+
11
+ `class X extends WyndlassApp` (vanilla). Write two methods:
12
+ - `mount()`: build the shadow DOM in `this.root`, once.
13
+ - `draw(epochMs)`: show that moment of the layout's clock. The host calls it on every frame it needs, seeking and pausing included.
14
+
15
+ It gives the element:
16
+ - `prop(name, default)`: a param's value, or the default when it is absent or empty;
17
+ - `now()`, `startedAt()`, `timeZone`, `locale`;
18
+ - `css(text)`: styles for the shadow root, as a constructable stylesheet. Call it from `mount()`;
19
+ - `font()`: the `font` param as CSS;
20
+ - `ready()`: the app may be shown. Once, within 5 s of its start;
21
+ - `--w` and `--h`: CSS properties with the box's size, for `fit()`;
22
+ - the enter and exit presets the layout gives (fade, scale, slide-up, slide-left). Return the items to stagger from `staggered()`.
23
+
24
+ A paged app also defines `paging()`, returning `{count, ms}`, so the layout can turn its enter and exit with the pages.
25
+
26
+ **What reads or acts for the app is a function that takes it,** so another app on the page can't use it:
27
+ - `data(this, source, params)`: the data service's answer, a promise. Only for sources `app.json`'s `data` lists;
28
+ - `kept(this, source, params)`: the answer the screen last kept for the same request, at once, or `undefined`. Draw it while `data` asks again, so the app is ready without the network;
29
+ - `image(this, path)`: a picture a data answer names, as a URL the app can draw;
30
+ - `snapshot(this, table)`: the rows of a table the layout names, in a `datatable` param or in the element's `binding`. Where a binding chooses rows (a filter, a sort, a limit), only those. `null` when the screen has no snapshot of that table. A `datatable` param needs no binding;
31
+ - `controllerOf(this)`: the player's controller, as `capabilities.player` allows; absent in the designer and previews;
32
+ - `impressionsOf(this)`: the people at the screen, for an app whose definition declares the `impressions` sensor;
33
+ - `contextOf(this)`: the rest of the context (the screen's `place` and facts, `preview`).
34
+
35
+ **Helpers:** `fit(ofHeight, ofWidth)` (a CSS size fitted to the box), `typographyCss(value)`, `color(value, fallback)`, `escape(text)`, and the presets' curves `easingOf`, `cubicBezier`, `staggerTimes`. `@wyndlass/app-sdk/qr` has `qrModules(text)`, a QR code drawn without the network.
36
+
37
+ **Types:** `AppContext`, `DataAnswer<T>`, `HookOptions`, `AppDefinition`, and each data source's answer as a namespace: `weather.Weather`, `news`, `calendar`, `tables`.
38
+
39
+ ## React (`@wyndlass/app-sdk/react`)
40
+
41
+ `export default reactApp(Component, { css })`: the element's class. The component renders in the app's shadow root, with `css` as its styles. Hooks:
42
+ - `useApp()`: `prop(name, default)`, `ready()`, `timeZone`, `locale`, `place`, `screen`, `preview`;
43
+ - `useNow()`: the layout's clock, drawn again at each of its frames;
44
+ - `usePaging(count, ms)`: the page showing now. It also tells the host the pages;
45
+ - `useData(source, params)`: `{answer, error}`. The kept answer at once, then the service's, asked again every 10 minutes;
46
+ - `useSnapshot(table)`: a data table's rows, as `snapshot` gives them, read again each frame.
47
+
48
+ ## The definition, `app.json`
49
+
50
+ Valid against `node_modules/@wyndlass/app-sdk/protocol/app-definition.schema.json`.
51
+ - **Required:** `id` (reverse-domain, in the organization's own domain, never `com.wyndlass.`), `version` (semver), `name`, `element` (lowercase, with a hyphen).
52
+ - `description`.
53
+ - **`params`:** what a person sets in the designer. Each has a `type` (`string`, `text`, `number`, `boolean`, `enum`, `color`, `font`, `media`, `location`, `datetime`, `duration`, `url`, `datatable`, `column`, `json`), and may have a `label`, `default`, `values` (an enum's choices, or suggestions), `group`, `required`, `min`, `max`, `step`, `help`, `advanced` and `when` (the params it depends on). A `url` param's values and default must be `https`. A `datatable` param's value is a table's id (`tbl_…`), and a `column` param's a column's key. Wyndlass makes a column's key from its label when the table is made: lowercased, other characters an underscore (`Now serving` is `now_serving`); `datatable.get` lists them. A `color` param's value is a plain string, `"#RRGGBB"`.
54
+ - **`data`:** the data sources it reads: `weather`, `news`, `image`, `calendar`. Imported apps may read no others.
55
+ - **`capabilities`:**
56
+ - `player`: `read` (the default) or `control` (playback, audio and display commands);
57
+ - `sensors`: `["impressions"]` for the people at the screen;
58
+ - `audio`: true if it plays sound;
59
+ - `network`: every host it fetches from, such as `api.example.com`. A person sees them when approving it.
60
+
61
+ Asking for more than the approved version (control, a new sensor, audio, a new host) holds the new version for review.
62
+ - **`slots`** (the fields a table's row fills, bound by the layout) and **`animation`** (the presets its enter and exit take).
63
+
64
+ ## Limits
65
+
66
+ - The zip: at most 6 MiB, holding exactly `app.json` (at most 256 KiB) and `app.js` (at most 5 MiB).
67
+ - `check` warns, refusing nothing, of a module over 1 MiB (screens load it at every start) and of no `ready()` in the sources.
68
+
69
+ `check` and `pack` refuse with a code, the same as Wyndlass's import:
70
+
71
+ | Code | Fix |
72
+ |---|---|
73
+ | `too-large` | Shrink the module: drop unused dependencies and large inlined images. |
74
+ | `not-a-zip`, `encrypted`, `folder`, `path`, `entries`, `size-mismatch` | The zip wasn't made by `pack`: make it with `npm run pack`. |
75
+ | `definition-json`, `definition-schema` | Fix `app.json`; the schema errors say where. |
76
+ | `definition-id` | Use the organization's own domain, not `com.wyndlass.`. |
77
+ | `definition-when` | A param's `when` names itself, or a param that doesn't exist. |
78
+ | `definition-url` | A `url` param's values and default must be `https` addresses. |
79
+ | `definition-peers` | Remove `capabilities.peers`: apps can't have it yet. |
80
+ | `definition-data` | `data` lists a source apps may not read. |
81
+ | `module-syntax`, `module-import`, `module-default` | The source doesn't build to one ES2020 module with a default export: check the default export and remove any run-time `import()`. |
82
+
83
+ ## What screens do
84
+
85
+ - **They draw on the layout's clock:** a seek or a pause draws that moment.
86
+ - **An app not ready within 5 s** is shown as it is, and reported late.
87
+ - **They catch every hook.** An app that throws three times in a minute is stopped and its box emptied. The screen reports why (`load`, `shape`, `unsupported`, `error`, `late` or `overran`) with the name of what was thrown, never its message.
88
+ - **Each app paints only in its box.** What it adds outside is removed; a dialog opens inside the box; `alert`, `confirm`, `prompt`, `print` and `window.open` do nothing.
89
+ - **A version that hangs the page twice in an hour** is held back on that screen until its content next changes.
90
+ - **Offline,** the app gets the answers the screen kept.
91
+ - **In the CMS,** the app runs in a sandboxed frame of its own, with no storage, cookies or parent page, and no controller or people. `npm run preview -- --frame` shows it so.
@@ -0,0 +1,86 @@
1
+ # Importing, placing and scheduling through the Wyndlass MCP
2
+
3
+ Everything that touches Wyndlass goes through the person's own MCP connection, as them, with their permissions, approvals and audit. The `wyndlass-app` command signs in to nothing: `upload` only PUTs to an address the MCP gives.
4
+
5
+ Tool names below are the MCP's. A client may show them with a prefix of its own (such as `mcp__wyndlass__app_import`).
6
+
7
+ **Each answer's words** are its text, and the same text is `answer` in its structured result. Some clients show only the structured result: read `answer` there. It has the next steps, the CMS's links, a refusal's reason, and a guide's sections.
8
+
9
+ ## Before importing
10
+
11
+ - **`whoami`:** who you act as, the organization, and which toolsets this connection has. If `app.prepareImport` is missing: the import tools are listed only while imported apps are on at the organization's top place. An Owner or Admin turns them on in the CMS, under **Settings**, **Imported apps**. Tell the person; don't look for another way in.
12
+ - **The place:** ask the person where the app goes. `node.list` with `search` finds a place's id (`node_…`). The app's item is made there on its first import, and layouts at and below it can place it. Importing needs a role that may add media at the place (Editor, Publisher, Admin or Owner). A later version goes to the app's item where it already is, and needs that role there.
13
+
14
+ ## 1. `app.prepareImport`
15
+
16
+ `{node, fileName, bytes}`: the place, the zip's file name (`com.example.queue-board-1.0.0.zip`), and its size in bytes, which `npm run pack` printed (at most 6 MiB).
17
+
18
+ It answers an upload id (`upl_…`), an HTTPS address open for an hour, and the headers to send: `Content-Type: application/zip` and the signed ones.
19
+
20
+ ## 2. `npx wyndlass-app upload`
21
+
22
+ From the project's folder, with the address and every header the answer gave:
23
+
24
+ ```sh
25
+ npx wyndlass-app upload "<address>" dist/<id>-<version>.zip --header "Content-Type: application/zip" --header "<name>: <value>"
26
+ ```
27
+
28
+ It answers `<zip>: uploaded (<n> bytes). Import it with app.import.` Never read the zip into the conversation or pass its bytes to a tool. A refused PUT (an expired address, a header left out): call `app.prepareImport` again.
29
+
30
+ ## 3. `app.import`
31
+
32
+ `{node, upload}`: the same place, and the upload's id. It answers one of:
33
+ - **"Imported <id> <version> as med_…, version <n>."** `med_…` is the app's item, and `<n>` the item's version. Then what is next:
34
+ - "It is approved with its import": a later version that asks for nothing more than the approved one before it. Screens play it once its checks pass;
35
+ - "It is held for review": an app's first version, a version that asks for more (player control, a new sensor, audio, a new network host), or one the place's media review holds. Its words end with the CMS's link to the app, for the person who approves it.
36
+ - **"The workspace has <id> <version> already, with these bytes":** nothing new was made. It says where that version stands.
37
+ - **A refusal:**
38
+ - "… was imported already, with other bytes. A version never changes": raise `version` in `app.json`, pack, and start again at step 1;
39
+ - "… is under com.wyndlass., which is Wyndlass's own": change the id;
40
+ - "… a new version of it needs media_item.edit on it": the person lacks the role where the app is. Tell them;
41
+ - "The file has not finished uploading": the PUT did not complete. Upload again.
42
+
43
+ ## 4. `app.list`
44
+
45
+ Lists Wyndlass's apps, then the organization's own: each with its item (`med_…`), and its newest versions, each:
46
+ - **"being checked, then approved with its import"** or **"…, then held for review":** Wyndlass is scanning and checking it. Call `app.list` again in a little while;
47
+ - **"refused by its checks: <reason>":** fix the cause ([contract.md](contract.md), Limits), raise `version`, pack and import again;
48
+ - **"held for review: a person approves it in the CMS":** give the person the link `app.import` gave, and say why it is held. Wait for them; never approve it yourself;
49
+ - **"approved":** screens may play it;
50
+ - **"rejected in review: <reason>":** tell the person the reason. Change what it says, raise `version`, and import again.
51
+
52
+ ## 5. Place it: `layout.save` and `layout.check`
53
+
54
+ First `guide.read` with topic `layouts`, sections `elements` and `apps`: the layout document, the app element, and its props. Its answer is the sections' text.
55
+
56
+ - **An app element:** `{"type": "app", "id": "queue", "frame": {"x": 0, "y": 0, "w": 1920, "h": 1080}, "app": {"id": "med_…"}, "props": {…}}`, with the item from `app.list`. An imported app needs no `app.use`.
57
+ - **Its `props`** are the definition's params, as `app.json` types them; leave one out for its default:
58
+ - a `color` param is a plain string, `"#RRGGBB"`. A layout element's own colors are objects, `{"value": "#RRGGBB"}`;
59
+ - a `datatable` param is a table's id (`tbl_…`). The app reads that table's rows on screens with no `binding`. A `binding` (`{"table": "tbl_…", …}`) is for an app with `slots`, or to choose rows: the layouts guide's apps section;
60
+ - a `column` param is a column's key, as `datatable.get` lists it.
61
+ - **A data table for the app:** `datatable.create` with `node`, `name` and `columns` (each a `label` and a `type`). Wyndlass makes each column's key from its label (`Number` is `number`, `Now serving` is `now_serving`) and answers them. Then add rows with `datatable.rows`, each row's `values` keyed by those keys, never by the labels.
62
+ - **A new layout:** `layout.save` with `node` (a place the person names), `name` and `document`.
63
+ - **A change to a layout:** `layout.get`, then `layout.save` with `media`, the whole document and `expectedVersion`.
64
+ - **`dryRun: true`** checks a document without saving it.
65
+ - **`layout.check`** gives its load against each screen type's budget and its timeline. Tell the person what it says.
66
+
67
+ A held version can be placed, and previewed in the designer, which runs it in its frame. Screens play it once it is approved and checked; until then they play the newest version before it that can play, or leave the app out.
68
+
69
+ ## 6. Schedule it, only with the person's yes: `campaign.save` and `campaign.publish`
70
+
71
+ Ask what should play where, and when. Never at the organization's top place unless the person says so.
72
+
73
+ - **`campaign.save`:** `node`, `name`, `tier` (`STANDARD` for most content), `items` (the layout's `med_…`), and the person's times: `flightStart`, `flightEnd`, and a `when`. Screens keep playing what they had until it is published.
74
+ - **`campaign.publish`:** the first call only previews: how many screens it reaches, and a `previewHash`. Tell the person what goes live where. Call it again with `confirm: true` and that `previewHash` only once they agree. An approval policy may hold it for a person.
75
+ - **To stop it:** `campaign.pause`.
76
+
77
+ ## A new version
78
+
79
+ 1. Raise `version` in `app.json` (semver: 1.0.0 to 1.1.0), then `npm run pack`.
80
+ 2. Import it: steps 1 to 4.
81
+ 3. **Layouts that name no version** (`app: {"id": "med_…"}`) take the new one once it is approved and checked.
82
+ 4. **A layout that pins one** (`app: {"id": "med_…", "version": <n>}`, the item's version) keeps it. To move it, `layout.save` with the new item version, after showing the person the change.
83
+
84
+ ## When a screen doesn't show it
85
+
86
+ `device.explainPlay` for the screen says what it plays and why, with notes such as a held version and the one playing for it, or "this screen's player is too old for apps you imported". Such a screen leaves imported apps out, and plays the rest of the layout, until its player is updated.
@@ -0,0 +1,38 @@
1
+ # The templates and the project
2
+
3
+ `npx @wyndlass/app-sdk create <folder> --template react|vanilla --id <id> --name "<name>"` makes a working app. Without `--template` it is `vanilla`; without `--id`, `com.example.<folder>`; without `--name`, a name made from the id. The element's name is the id's last part, with `-app` added when that part has no hyphen.
4
+
5
+ ## The project
6
+
7
+ | File | What it is |
8
+ |---|---|
9
+ | `app.json` | The definition: `id`, `version` (starts at 0.1.0), `name`, `element`, `description`, `params`, `data`, and, when it asks for more, `capabilities`. [contract.md](contract.md) lists every field. |
10
+ | `src/app.tsx` (react) or `src/app.ts` (vanilla) | The module. Its default export is the element's class. |
11
+ | `src/app.css` | The app's styles. They apply only inside the app's shadow root. |
12
+ | `src/env.d.ts` | Types for what the source imports: CSS as text, images as data URLs. |
13
+ | `package.json` | The scripts `preview`, `check`, `pack` and `typecheck`, and `@wyndlass/app-sdk`. |
14
+ | `tsconfig.json` | TypeScript's options: ES2020, strict. |
15
+ | `README.md`, `AGENTS.md` | The commands, and the rules screens hold the app to. |
16
+ | `.gitignore` | Leaves out `node_modules/` and `dist/`. |
17
+
18
+ `npm run pack` writes `dist/<id>-<version>.zip` and its source map, `dist/<id>-<version>.js.map`, beside it. Only the zip is imported.
19
+
20
+ ## What the template app does
21
+
22
+ Two pages, eight seconds each: a heading and the time, then the weather for the screen's place. Its params are one of each of four kinds: `heading` (string), `accent` (color), `units` (enum: metric or imperial) and `seconds` (boolean). It reads one data source, `weather`.
23
+
24
+ - **react:** `function Board()` renders both pages and hides the one not showing. `useNow()` gives the time, `usePaging(2, 8000)` the page, `useData("weather", …)` the weather (the kept answer at once, then the service's), and `useApp()` the props, the locale and the time zone. `ready()` is called once there is an answer or an error.
25
+ - **vanilla:** `class App extends WyndlassApp`. `mount()` builds the shadow DOM once and calls `ready()` when the screen kept a weather answer. `draw(epochMs)` shows the page for that moment, asks for the weather every ten minutes, then calls `ready()`. `paging()` says the two pages.
26
+
27
+ ## Making it the person's app
28
+
29
+ 1. **Change `app.json` first:** the `params` people set in the designer (each with a `label` and a `default`), the `data` sources it reads, and `capabilities` if it asks for more.
30
+ 2. **Read each param** with `prop(name, default)` (vanilla) or `useApp().prop(name, default)` (react), with the same default as `app.json`.
31
+ 3. **Draw from the layout's clock:** vanilla's `draw(epochMs)` gets the moment; react's `useNow()` gives it. Work out what changes with time (a page, a countdown, a clock) from that number, never from a timer of your own.
32
+ 4. **Say `ready()`** as soon as the app has something to show, within 5 s: the kept answer, a default, or the failure.
33
+ 5. **Styles** go in `src/app.css`. Size text to the box with `fit()` or the `--w` and `--h` properties ([contract.md](contract.md)), since screens run from 1080p to 4K.
34
+ 6. **Images:** import them from `src/` (`import logo from "./logo.png"`: PNG, JPEG, GIF, WebP or SVG), and `pack` inlines each as a data URL, for an `<img>`'s `src` or a style set from code. A `url()` inside `src/app.css` is not bundled.
35
+ 7. **Fonts:** give the app a param named `font`, of the kind `font`, so the person picks a font in the designer. Apply it as CSS with `font()` (vanilla), or `typographyCss(app.prop("font", null))` (react) ([contract.md](contract.md)).
36
+ 8. **Delete what the template drew** that the person did not ask for, its weather included, and its `data` entry with it.
37
+
38
+ Each source change redraws the preview. `npm run typecheck` and `npm run check` find mistakes before the person sees them.
@@ -11,3 +11,7 @@ An app for Wyndlass's screens, on `@wyndlass/app-sdk` and React. Its README says
11
11
  - **Look at it:** `npm run preview` (http://127.0.0.1:5199/), in both orientations, with the clock played and held, and once with `npm run preview -- --frame`, as the CMS runs it.
12
12
  - **Before importing:** `npm run check`, then `npm run pack`. Raise `version` in `app.json` for each new version.
13
13
  - **Importing and scheduling** go through the Wyndlass MCP, with the person's approval: `app.prepareImport`, `npx wyndlass-app upload <url> dist/<zip> --header "name: value"`, `app.import`, then `app.list` until the version is ready or held. A held version waits for a person who may approve media.
14
+ - **Never approve a version:** a person who may approve media does, in the CMS. Give them the link `app.import` answers, and say why it is held.
15
+ - **Placing:** read `guide.read` (topic `layouts`, sections `elements` and `apps`), then `layout.save` with the app's item from `app.list`, and `layout.check`, at a place the person names.
16
+ - **Scheduling, only with the person's yes:** `campaign.save` at the place and times they give, then `campaign.publish`, which previews first: confirm only once they agree. Never at the organization's top place unless they say so.
17
+ - **Every step in detail** is in the skill: `npx wyndlass-app skill install` puts it in `.claude/skills/wyndlass-app/`.
@@ -11,6 +11,7 @@ An app for Wyndlass's screens (`__APP_ID__`), built on [`@wyndlass/app-sdk`](htt
11
11
  - `npm run check`: the checks Wyndlass makes when you import it.
12
12
  - `npm run pack`: `dist/__APP_ID__-<version>.zip`, the package to import, after the checks.
13
13
  - `npm run typecheck`: TypeScript's check.
14
+ - `npx wyndlass-app skill install`: the skill for a coding agent such as Claude Code, in `.claude/skills/`. It builds, previews, packs, imports and schedules the app with you.
14
15
 
15
16
  Import the package through the Wyndlass MCP (`app.prepareImport`, `npx wyndlass-app upload`, then `app.import`) or in the CMS (Content, Library, Import an app). A person who may approve media there reviews the app's first version, and any that asks for more than the one before it, before screens play it. Raise `version` in `app.json` for each new one.
16
17
 
@@ -3,3 +3,11 @@ declare module "*.css" {
3
3
  const css: string;
4
4
  export default css;
5
5
  }
6
+
7
+ // An image the source imports is a data URL, inlined by pack: for an <img>'s src or a style set from code.
8
+ declare module "*.png" { const url: string; export default url; }
9
+ declare module "*.jpg" { const url: string; export default url; }
10
+ declare module "*.jpeg" { const url: string; export default url; }
11
+ declare module "*.gif" { const url: string; export default url; }
12
+ declare module "*.webp" { const url: string; export default url; }
13
+ declare module "*.svg" { const url: string; export default url; }
@@ -11,3 +11,7 @@ An app for Wyndlass's screens, on `@wyndlass/app-sdk`. Its README says what it i
11
11
  - **Look at it:** `npm run preview` (http://127.0.0.1:5199/), in both orientations, with the clock played and held, and once with `npm run preview -- --frame`, as the CMS runs it.
12
12
  - **Before importing:** `npm run check`, then `npm run pack`. Raise `version` in `app.json` for each new version.
13
13
  - **Importing and scheduling** go through the Wyndlass MCP, with the person's approval: `app.prepareImport`, `npx wyndlass-app upload <url> dist/<zip> --header "name: value"`, `app.import`, then `app.list` until the version is ready or held. A held version waits for a person who may approve media.
14
+ - **Never approve a version:** a person who may approve media does, in the CMS. Give them the link `app.import` answers, and say why it is held.
15
+ - **Placing:** read `guide.read` (topic `layouts`, sections `elements` and `apps`), then `layout.save` with the app's item from `app.list`, and `layout.check`, at a place the person names.
16
+ - **Scheduling, only with the person's yes:** `campaign.save` at the place and times they give, then `campaign.publish`, which previews first: confirm only once they agree. Never at the organization's top place unless they say so.
17
+ - **Every step in detail** is in the skill: `npx wyndlass-app skill install` puts it in `.claude/skills/wyndlass-app/`.
@@ -11,6 +11,7 @@ An app for Wyndlass's screens (`__APP_ID__`), built on [`@wyndlass/app-sdk`](htt
11
11
  - `npm run check`: the checks Wyndlass makes when you import it.
12
12
  - `npm run pack`: `dist/__APP_ID__-<version>.zip`, the package to import, after the checks.
13
13
  - `npm run typecheck`: TypeScript's check.
14
+ - `npx wyndlass-app skill install`: the skill for a coding agent such as Claude Code, in `.claude/skills/`. It builds, previews, packs, imports and schedules the app with you.
14
15
 
15
16
  Import the package through the Wyndlass MCP (`app.prepareImport`, `npx wyndlass-app upload`, then `app.import`) or in the CMS (Content, Library, Import an app). A person who may approve media there reviews the app's first version, and any that asks for more than the one before it, before screens play it. Raise `version` in `app.json` for each new one.
16
17
 
@@ -3,3 +3,11 @@ declare module "*.css" {
3
3
  const css: string;
4
4
  export default css;
5
5
  }
6
+
7
+ // An image the source imports is a data URL, inlined by pack: for an <img>'s src or a style set from code.
8
+ declare module "*.png" { const url: string; export default url; }
9
+ declare module "*.jpg" { const url: string; export default url; }
10
+ declare module "*.jpeg" { const url: string; export default url; }
11
+ declare module "*.gif" { const url: string; export default url; }
12
+ declare module "*.webp" { const url: string; export default url; }
13
+ declare module "*.svg" { const url: string; export default url; }