@adland/embed 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { createReadClient, fetchAdByKey, fetchMetadataURI, fetchAdFromURI, trackVerifiedImpression, trackImpression, trackClick, getAdImage, getAdTitle, getAdDescription, getAdType, WebActionAdapter } from '@adland/react/core';
1
+ import { createReadClient, fetchAdByKey, fetchMetadataURI, fetchAdFromURI, trackVerifiedImpression, trackImpression, trackClick, getAdIcon, getAdTitle, getAdDescription, WebActionAdapter } from '@adland/react/core';
2
2
  export { WebActionAdapter } from '@adland/react/core';
3
+ import { adDestination } from '@adland/data';
3
4
 
4
5
  // src/index.ts
5
6
  var AD_PARTS = [
@@ -8,16 +9,8 @@ var AD_PARTS = [
8
9
  "body",
9
10
  "title",
10
11
  "description",
11
- "badge",
12
12
  "label"
13
13
  ];
14
- var TYPE_LABELS = {
15
- link: "Link",
16
- cast: "Cast",
17
- miniapp: "Mini App",
18
- token: "Token",
19
- farcasterProfile: "Profile"
20
- };
21
14
  function escapeHtml(value) {
22
15
  return value.replace(
23
16
  /[&<>"']/g,
@@ -49,11 +42,25 @@ var BASE_STYLES = `
49
42
  font-family: var(--adland-font, system-ui, -apple-system, sans-serif);
50
43
  cursor: pointer;
51
44
  text-decoration: none;
52
- transition: border-color 0.15s ease, box-shadow 0.15s ease;
45
+ /* Resting shadow, not just a hover one. Only --adland-shadow-hover existed,
46
+ which quietly ruled out every style whose card is raised at rest: a hard
47
+ offset shadow is most of what makes a neo-brutalist card look like one, and
48
+ there was no way to ask for it. Defaults to none, so nothing moves. */
49
+ box-shadow: var(--adland-shadow, none);
50
+ transition: var(
51
+ --adland-transition,
52
+ border-color 0.15s ease,
53
+ box-shadow 0.15s ease,
54
+ transform 0.15s ease
55
+ );
53
56
  }
54
57
  .adland-card:hover {
55
58
  border-color: var(--adland-border-hover, #d1d5db);
56
59
  box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));
60
+ /* The other half of a raised card: shrink the shadow AND move the card into
61
+ the space it vacated, or the card appears to grow rather than press. No
62
+ default, so a theme that sets neither is untouched. */
63
+ transform: var(--adland-transform-hover, none);
57
64
  }
58
65
  .adland-media {
59
66
  width: var(--adland-media-size, 44px);
@@ -62,6 +69,7 @@ var BASE_STYLES = `
62
69
  object-fit: cover;
63
70
  flex: none;
64
71
  background: var(--adland-media-bg, #f3f4f6);
72
+ border: var(--adland-media-border, none);
65
73
  }
66
74
  .adland-body { min-width: 0; flex: 1; }
67
75
  .adland-title {
@@ -82,20 +90,11 @@ var BASE_STYLES = `
82
90
  text-overflow: ellipsis;
83
91
  white-space: nowrap;
84
92
  }
85
- .adland-badge {
86
- flex: none;
87
- font-size: 10px;
88
- font-weight: 600;
89
- padding: 2px 6px;
90
- border-radius: var(--adland-badge-radius, 6px);
91
- background: var(--adland-accent-bg, #eef2ff);
92
- color: var(--adland-accent-fg, #4338ca);
93
- }
94
93
  .adland-label {
95
94
  position: absolute;
96
95
  top: 6px;
97
96
  right: 8px;
98
- font-size: 9px;
97
+ font-size: var(--adland-label-size, 9px);
99
98
  letter-spacing: 0.04em;
100
99
  text-transform: uppercase;
101
100
  color: var(--adland-label, #9ca3af);
@@ -103,22 +102,16 @@ var BASE_STYLES = `
103
102
  .adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }
104
103
  `;
105
104
  function cardMarkup(ad) {
106
- const image = getAdImage(ad);
105
+ const image = getAdIcon(ad);
107
106
  const title = getAdTitle(ad);
108
107
  const description = getAdDescription(ad);
109
- const type = getAdType(ad);
110
- const badge = type ? TYPE_LABELS[type] ?? type : "";
111
- return `<div part="card" class="adland-card">${image ? `<img part="media" class="adland-media" src="${escapeAttr(image)}" alt="" loading="lazy" />` : ""}<div part="body" class="adland-body">${title ? `<p part="title" class="adland-title">${escapeHtml(title)}</p>` : ""}${description ? `<p part="description" class="adland-description">${escapeHtml(description)}</p>` : ""}</div>${badge ? `<span part="badge" class="adland-badge">${escapeHtml(badge)}</span>` : ""}<span part="label" class="adland-label">Ad</span></div>`;
108
+ return `<div part="card" class="adland-card">${image ? `<img part="media" class="adland-media" src="${escapeAttr(image)}" alt="" loading="lazy" />` : ""}<div part="body" class="adland-body">${title ? `<p part="title" class="adland-title">${escapeHtml(title)}</p>` : ""}${description ? `<p part="description" class="adland-description">${escapeHtml(description)}</p>` : ""}</div><span part="label" class="adland-label">Ad</span></div>`;
112
109
  }
113
110
  function emptyMarkup() {
114
111
  return `<div part="card" class="adland-card adland-empty"><span part="description" class="adland-description">Your ad here</span><span part="label" class="adland-label">Ad</span></div>`;
115
112
  }
116
113
  function adWebUrl(ad) {
117
- if (ad.type === "link" || ad.type === "miniapp") {
118
- const url = ad.data.url;
119
- return typeof url === "string" ? url : null;
120
- }
121
- return null;
114
+ return adDestination(ad);
122
115
  }
123
116
 
124
117
  // src/theme.ts
@@ -132,18 +125,21 @@ var THEME_VARS = [
132
125
  "--adland-border-width",
133
126
  "--adland-radius",
134
127
  "--adland-padding",
128
+ "--adland-bleed",
135
129
  "--adland-gap",
136
130
  "--adland-font",
131
+ "--adland-shadow",
137
132
  "--adland-shadow-hover",
133
+ "--adland-transform-hover",
134
+ "--adland-transition",
138
135
  "--adland-media-size",
139
136
  "--adland-media-radius",
140
137
  "--adland-media-bg",
141
138
  "--adland-title-size",
142
139
  "--adland-title-weight",
143
140
  "--adland-description-size",
144
- "--adland-badge-radius",
145
- "--adland-accent-bg",
146
- "--adland-accent-fg"
141
+ "--adland-media-border",
142
+ "--adland-label-size"
147
143
  ];
148
144
  function readThemeVars(host) {
149
145
  const styles = getComputedStyle(host);
@@ -175,6 +171,16 @@ function makeAdlandSlot(adapter) {
175
171
  "data-ad"
176
172
  ];
177
173
  shadowInited = false;
174
+ /**
175
+ * Whether the framed markup is wrapped in a real `<a>`.
176
+ *
177
+ * The click handler used to assume it always was, and told `handleClick`
178
+ * the URL had already been opened. That is only true when a destination
179
+ * existed — and for four of the six ad types it did not, so the click was
180
+ * tracked and then dropped on the floor. Recording what was actually
181
+ * rendered lets the adapter take over when the anchor is missing.
182
+ */
183
+ framedInAnchor = false;
178
184
  cleanupImpression;
179
185
  /**
180
186
  * The address this element is actually showing.
@@ -247,8 +253,10 @@ function makeAdlandSlot(adapter) {
247
253
  }
248
254
  if (token !== this.renderToken || !this.isConnected) return;
249
255
  const inner = result.data ? cardMarkup(result.data) : emptyMarkup();
250
- const mode = this.attr("render-mode") === "inline" ? "inline" : "isolated";
256
+ const requested = this.attr("render-mode");
257
+ const mode = requested === "inline" || requested === "light" ? requested : "isolated";
251
258
  if (mode === "inline") this.renderInline(inner, result.data);
259
+ else if (mode === "light") this.renderLight(inner, result.data);
252
260
  else this.renderIsolated(inner, result.data);
253
261
  const resolved = this.attr("slot") ?? result.slot;
254
262
  this.resolvedSlot = resolved;
@@ -273,9 +281,46 @@ function makeAdlandSlot(adapter) {
273
281
  const card = root.querySelector(".adland-card");
274
282
  card?.addEventListener("click", () => this.handleClick(data, false));
275
283
  }
284
+ /**
285
+ * Into the page itself — no iframe, no shadow root, no styles of ours.
286
+ *
287
+ * ── The mode that exists so you can use your own CSS ─────────────────────
288
+ *
289
+ * The other two isolate, and both isolate from the publisher as much as
290
+ * from the advertiser. `isolated` seals the card in an iframe, where the
291
+ * only way in is the `--adland-*` custom properties copied across the
292
+ * document boundary. `inline` uses a shadow root, which custom properties
293
+ * inherit through and `::part()` can reach — better, and still not a
294
+ * stylesheet: Tailwind emits classes, and a class in the page cannot cross
295
+ * a shadow boundary. So a publisher wanting `rounded-2xl` on the card had
296
+ * no way to say it.
297
+ *
298
+ * Here the markup is ordinary children of `<adland-slot>`. Every class hook
299
+ * — `.adland-card`, `.adland-media`, `.adland-title`, `.adland-description`
300
+ * — is a normal element in the page, so a page stylesheet, a Tailwind
301
+ * arbitrary variant, or a `cn()` on the host all apply directly.
302
+ *
303
+ * ── What you give up, and why it is not the default ──────────────────────
304
+ *
305
+ * Isolation runs both ways. In this mode the publisher's CSS can break the
306
+ * ad, and a reset of theirs can leave an advertiser's creative unreadable
307
+ * with nobody able to see it but their own visitors. `BASE_STYLES` is NOT
308
+ * injected — in the light DOM those rules would leak to the whole page —
309
+ * so an unstyled card is what arrives, and styling it is now the
310
+ * publisher's job rather than their option.
311
+ *
312
+ * That is a real cost and it is why this is opt-in and named. A publisher
313
+ * who wants it asks for it; everybody else keeps the sealed default.
314
+ */
315
+ renderLight(inner, data) {
316
+ this.innerHTML = inner;
317
+ const card = this.querySelector(".adland-card");
318
+ card?.addEventListener("click", () => this.handleClick(data, false));
319
+ }
276
320
  renderIsolated(inner, data) {
277
321
  this.replaceChildren();
278
322
  const url = data ? adWebUrl(data) : this.emptyUrl();
323
+ this.framedInAnchor = !!url;
279
324
  const body = url ? `<a href="${escapeAttr(url)}" target="_blank" rel="noopener noreferrer" style="display:block;text-decoration:none;color:inherit">${inner}</a>` : inner;
280
325
  const theme = readThemeVars(this);
281
326
  const iframe = document.createElement("iframe");
@@ -286,7 +331,7 @@ function makeAdlandSlot(adapter) {
286
331
  iframe.setAttribute("title", "Advertisement");
287
332
  iframe.setAttribute("scrolling", "no");
288
333
  iframe.style.cssText = "width:100%;border:0;display:block;height:100%";
289
- iframe.srcdoc = `<!doctype html><html><head><meta charset="utf-8"><style>:root{${theme}}html,body{margin:0;padding:0}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;
334
+ iframe.srcdoc = `<!doctype html><html><head><meta charset="utf-8"><style>:root{${theme}}html,body{margin:0;padding:0}body{padding:var(--adland-bleed,0)}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;
290
335
  const handler = (e) => {
291
336
  if (e.source !== iframe.contentWindow) return;
292
337
  const msg = e.data;
@@ -294,7 +339,7 @@ function makeAdlandSlot(adapter) {
294
339
  iframe.style.height = `${msg.height}px`;
295
340
  this.style.minHeight = "";
296
341
  } else if (msg?.__adland === "click") {
297
- this.handleClick(data, true);
342
+ this.handleClick(data, this.framedInAnchor);
298
343
  }
299
344
  };
300
345
  this.messageHandler = handler;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/render.ts","../src/theme.ts","../src/element.ts","../src/register.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AdData } from \"@adland/data\";\nimport {\n getAdDescription,\n getAdImage,\n getAdTitle,\n getAdType,\n} from \"@adland/react/core\";\n\n/** Part names exposed for `::part()` styling in inline (shadow DOM) mode. */\nexport const AD_PARTS = [\n \"card\",\n \"media\",\n \"body\",\n \"title\",\n \"description\",\n \"badge\",\n \"label\",\n] as const;\n\nconst TYPE_LABELS: Record<string, string> = {\n link: \"Link\",\n cast: \"Cast\",\n miniapp: \"Mini App\",\n token: \"Token\",\n farcasterProfile: \"Profile\",\n};\n\n/** Escape text for safe interpolation into HTML element content. */\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n}\n\n/** Escape a value for use inside a double-quoted HTML attribute. */\nexport function escapeAttr(value: string): string {\n return value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n}\n\n/**\n * Default ad-card styles. Every visual is driven by a `--adland-*` custom\n * property with a sensible fallback, so a publisher can re-theme the card\n * without overriding a single rule. Custom properties inherit through the\n * shadow boundary (inline mode) and are copied into the iframe (isolated mode).\n */\nexport const BASE_STYLES = `\n:host { display: block; }\n* { box-sizing: border-box; }\n.adland-card {\n position: relative;\n display: flex;\n gap: var(--adland-gap, 12px);\n align-items: center;\n padding: var(--adland-padding, 12px);\n border: var(--adland-border-width, 1px) solid var(--adland-border, #e5e7eb);\n border-radius: var(--adland-radius, 12px);\n background: var(--adland-bg, #ffffff);\n color: var(--adland-fg, #111827);\n font-family: var(--adland-font, system-ui, -apple-system, sans-serif);\n cursor: pointer;\n text-decoration: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.adland-card:hover {\n border-color: var(--adland-border-hover, #d1d5db);\n box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));\n}\n.adland-media {\n width: var(--adland-media-size, 44px);\n height: var(--adland-media-size, 44px);\n border-radius: var(--adland-media-radius, 8px);\n object-fit: cover;\n flex: none;\n background: var(--adland-media-bg, #f3f4f6);\n}\n.adland-body { min-width: 0; flex: 1; }\n.adland-title {\n margin: 0;\n font-size: var(--adland-title-size, 14px);\n font-weight: var(--adland-title-weight, 600);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-description {\n margin: 2px 0 0;\n font-size: var(--adland-description-size, 12px);\n color: var(--adland-muted, #6b7280);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-badge {\n flex: none;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: var(--adland-badge-radius, 6px);\n background: var(--adland-accent-bg, #eef2ff);\n color: var(--adland-accent-fg, #4338ca);\n}\n.adland-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 9px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--adland-label, #9ca3af);\n}\n.adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }\n`;\n\n/** Inner card markup (parts + classes) for a loaded ad. */\nexport function cardMarkup(ad: AdData): string {\n const image = getAdImage(ad);\n const title = getAdTitle(ad);\n const description = getAdDescription(ad);\n const type = getAdType(ad);\n const badge = type ? (TYPE_LABELS[type] ?? type) : \"\";\n\n return `<div part=\"card\" class=\"adland-card\">${\n image\n ? `<img part=\"media\" class=\"adland-media\" src=\"${escapeAttr(image)}\" alt=\"\" loading=\"lazy\" />`\n : \"\"\n }<div part=\"body\" class=\"adland-body\">${\n title ? `<p part=\"title\" class=\"adland-title\">${escapeHtml(title)}</p>` : \"\"\n }${\n description\n ? `<p part=\"description\" class=\"adland-description\">${escapeHtml(description)}</p>`\n : \"\"\n }</div>${\n badge\n ? `<span part=\"badge\" class=\"adland-badge\">${escapeHtml(badge)}</span>`\n : \"\"\n }<span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** Inner card markup for an empty slot (the \"Your ad here\" CTA). */\nexport function emptyMarkup(): string {\n return `<div part=\"card\" class=\"adland-card adland-empty\"><span part=\"description\" class=\"adland-description\">Your ad here</span><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** The external URL an ad maps to on the web, or null if it has none. */\nexport function adWebUrl(ad: AdData): string | null {\n if (ad.type === \"link\" || ad.type === \"miniapp\") {\n const url = (ad.data as { url?: unknown }).url;\n return typeof url === \"string\" ? url : null;\n }\n return null;\n}\n","/**\n * The `--adland-*` custom properties the card reads. Custom properties inherit\n * through the shadow boundary automatically (inline mode), but NOT across an\n * iframe document boundary — so for isolated mode we read whichever of these\n * are set on the host and copy them into the iframe's `:root`.\n */\nexport const THEME_VARS = [\n \"--adland-bg\",\n \"--adland-fg\",\n \"--adland-muted\",\n \"--adland-label\",\n \"--adland-border\",\n \"--adland-border-hover\",\n \"--adland-border-width\",\n \"--adland-radius\",\n \"--adland-padding\",\n \"--adland-gap\",\n \"--adland-font\",\n \"--adland-shadow-hover\",\n \"--adland-media-size\",\n \"--adland-media-radius\",\n \"--adland-media-bg\",\n \"--adland-title-size\",\n \"--adland-title-weight\",\n \"--adland-description-size\",\n \"--adland-badge-radius\",\n \"--adland-accent-bg\",\n \"--adland-accent-fg\",\n] as const;\n\n/**\n * Serialize the theme custom properties currently set on `host` into a CSS\n * declaration string (e.g. `--adland-bg:#000;`). Empty when none are set.\n */\nexport function readThemeVars(host: Element): string {\n const styles = getComputedStyle(host);\n let out = \"\";\n for (const name of THEME_VARS) {\n const value = styles.getPropertyValue(name).trim();\n if (value) out += `${name}:${value};`;\n }\n return out;\n}\n","import type { AdData } from \"@adland/data\";\nimport {\n type ActionAdapter,\n type AdConsent,\n createReadClient,\n fetchAdFromURI,\n fetchAdByKey,\n fetchMetadataURI,\n trackClick,\n trackImpression,\n trackVerifiedImpression,\n} from \"@adland/react/core\";\nimport {\n adWebUrl,\n BASE_STYLES,\n cardMarkup,\n emptyMarkup,\n escapeAttr,\n} from \"./render\";\nimport { readThemeVars } from \"./theme\";\n\nconst DEFAULT_CHAIN = 8453;\n/**\n * Where an unsold slot sends a reader.\n *\n * The 0xSlots app moved off its own subdomain and now lives under a path on the\n * apex domain, so the base carries `/app` and the slot path is appended to it.\n */\nconst DEFAULT_BASE_LINK = \"https://0xslots.org/app\";\nconst RESERVED_HEIGHT = \"68px\";\n\n/**\n * Build the `<adland-slot>` custom-element class bound to a click-routing\n * adapter. The web CDN bundle binds the web adapter (no Farcaster); the\n * Farcaster bundle binds the Mini App adapter.\n */\nexport function makeAdlandSlot(\n adapter: ActionAdapter,\n): CustomElementConstructor {\n return class AdlandSlot extends HTMLElement {\n static observedAttributes = [\n \"slot\",\n \"chain\",\n \"placement\",\n \"render-mode\",\n \"gateway\",\n \"endpoint\",\n \"rpc-url\",\n \"consent\",\n \"verified\",\n \"base-link-url\",\n \"data-ad\",\n ];\n\n private shadowInited = false;\n private cleanupImpression?: () => void;\n /**\n * The address this element is actually showing.\n *\n * The `slot` attribute when there is one, otherwise whatever `slot-key`\n * resolved to. Everything below the fetch needs an address — the impression\n * is recorded against one and the empty-slot CTA links to one — and a name\n * is not an address until the module has answered.\n */\n private resolvedSlot?: string;\n private messageHandler?: (e: MessageEvent) => void;\n private renderToken = 0;\n\n connectedCallback() {\n void this.render();\n }\n\n disconnectedCallback() {\n this.teardown();\n }\n\n attributeChangedCallback() {\n if (this.isConnected) void this.render();\n }\n\n private attr(name: string): string | undefined {\n return this.getAttribute(name) ?? undefined;\n }\n\n private chainId(): number {\n return Number(this.attr(\"chain\") ?? DEFAULT_CHAIN) || DEFAULT_CHAIN;\n }\n\n private teardown() {\n this.cleanupImpression?.();\n this.cleanupImpression = undefined;\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = undefined;\n }\n }\n\n /** Resolve the ad: inline `data-ad` JSON, else an on-chain read. */\n private async loadAd(): Promise<{\n data: AdData | null;\n cid: string | null;\n /** Set when a NAME was resolved; the address it pointed at. */\n slot?: string;\n }> {\n const staticData = this.attr(\"data-ad\");\n if (staticData) {\n try {\n return { data: JSON.parse(staticData) as AdData, cid: null };\n } catch {\n return { data: null, cid: null };\n }\n }\n\n const slot = this.attr(\"slot\");\n const slotKey = this.attr(\"slot-key\");\n if (!slot && !slotKey) return { data: null, cid: null };\n\n const client = createReadClient(this.chainId(), this.attr(\"rpc-url\"));\n\n // A NAME, resolved through the metadata module, in one call.\n //\n // Preferred over an address for the reason the whole registry exists: an\n // address pasted into a page is one nobody can ever change, so redeploying\n // a slot leaves every embed carrying it showing a dead space. `slot` still\n // wins when both are set, because a publisher who pinned an address did so\n // to opt out of trusting us with the pointer.\n if (!slot && slotKey) {\n const snap = await fetchAdByKey(client, slotKey, {\n gateway: this.attr(\"gateway\"),\n adModule: this.attr(\"ad-module\"),\n });\n // The resolved address travels with it: the impression below is\n // recorded against a slot, and a name is not one.\n return snap\n ? { data: snap.data, cid: snap.cid, slot: snap.slot }\n : { data: null, cid: null };\n }\n\n const uri = await fetchMetadataURI(client, slot as string);\n if (!uri) return { data: null, cid: null };\n return fetchAdFromURI(uri, this.attr(\"gateway\"));\n }\n\n private async render() {\n const token = ++this.renderToken;\n this.teardown();\n\n // Reserve height up front so an async load doesn't shift the layout.\n if (!this.style.minHeight) this.style.minHeight = RESERVED_HEIGHT;\n\n let result: { data: AdData | null; cid: string | null; slot?: string };\n try {\n result = await this.loadAd();\n } catch {\n result = { data: null, cid: null };\n }\n // A newer render superseded this one while awaiting — bail.\n if (token !== this.renderToken || !this.isConnected) return;\n\n const inner = result.data ? cardMarkup(result.data) : emptyMarkup();\n const mode =\n this.attr(\"render-mode\") === \"inline\" ? \"inline\" : \"isolated\";\n if (mode === \"inline\") this.renderInline(inner, result.data);\n else this.renderIsolated(inner, result.data);\n\n // The attribute if there was one, otherwise the address the name\n // resolved to. Recording an impression needs an address either way.\n const resolved = this.attr(\"slot\") ?? result.slot;\n this.resolvedSlot = resolved;\n if (resolved) {\n const track =\n this.attr(\"verified\") != null\n ? trackVerifiedImpression\n : trackImpression;\n this.cleanupImpression = track(this, {\n slot: resolved,\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n cid: result.cid,\n empty: !result.data,\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n });\n }\n }\n\n private renderInline(inner: string, data: AdData | null) {\n const root =\n this.shadowRoot ??\n (this.shadowInited ? null : this.attachShadow({ mode: \"open\" }));\n if (!root) return;\n this.shadowInited = true;\n root.innerHTML = `<style>${BASE_STYLES}</style>${inner}`;\n const card = root.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n private renderIsolated(inner: string, data: AdData | null) {\n this.replaceChildren();\n\n const url = data ? adWebUrl(data) : this.emptyUrl();\n const body = url\n ? `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:block;text-decoration:none;color:inherit\">${inner}</a>`\n : inner;\n const theme = readThemeVars(this);\n\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-popups allow-popups-to-escape-sandbox allow-scripts\",\n );\n iframe.setAttribute(\"title\", \"Advertisement\");\n iframe.setAttribute(\"scrolling\", \"no\");\n iframe.style.cssText = \"width:100%;border:0;display:block;height:100%\";\n iframe.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"><style>:root{${theme}}html,body{margin:0;padding:0}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;\n\n const handler = (e: MessageEvent) => {\n if (e.source !== iframe.contentWindow) return;\n const msg = e.data as { __adland?: string; height?: number };\n if (msg?.__adland === \"size\" && typeof msg.height === \"number\") {\n iframe.style.height = `${msg.height}px`;\n this.style.minHeight = \"\";\n } else if (msg?.__adland === \"click\") {\n // The iframe's anchor already opened the URL — only track here.\n this.handleClick(data, true);\n }\n };\n this.messageHandler = handler;\n window.addEventListener(\"message\", handler);\n this.appendChild(iframe);\n }\n\n private baseLink(): string {\n return this.attr(\"base-link-url\") ?? DEFAULT_BASE_LINK;\n }\n\n private emptyUrl(): string | null {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n if (!slot) return null;\n return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;\n }\n\n private handleClick(data: AdData | null, alreadyOpened: boolean) {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n const shared = {\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n };\n\n if (data) {\n if (slot) trackClick(\"click\", { slot, cid: null, ...shared });\n if (!alreadyOpened) void adapter.performAdAction(data);\n } else if (slot) {\n void trackClick(\"click-empty\", { slot, ...shared });\n if (!alreadyOpened) {\n void adapter.performEmptyAdAction(\n slot,\n this.chainId(),\n this.baseLink(),\n );\n }\n }\n }\n };\n}\n","import type { ActionAdapter } from \"@adland/react/core\";\nimport { makeAdlandSlot } from \"./element\";\n\n/**\n * Define the `<adland-slot>` custom element bound to a click-routing adapter.\n * Safe to call more than once and in non-DOM environments — it no-ops if the\n * element is already defined or `customElements` is unavailable (SSR).\n */\nexport function registerAdlandSlot(\n adapter: ActionAdapter,\n tag = \"adland-slot\",\n): void {\n if (typeof customElements === \"undefined\") return;\n if (customElements.get(tag)) return;\n customElements.define(tag, makeAdlandSlot(adapter));\n}\n","import { WebActionAdapter } from \"@adland/react/core\";\nimport { registerAdlandSlot } from \"./register\";\n\nexport { WebActionAdapter } from \"@adland/react/core\";\nexport { makeAdlandSlot } from \"./element\";\nexport { registerAdlandSlot } from \"./register\";\nexport { AD_PARTS } from \"./render\";\nexport { THEME_VARS } from \"./theme\";\n\n// Convenience: importing the package auto-registers <adland-slot> with the web\n// adapter (no Farcaster). For Farcaster routing, call registerAdlandSlot with\n// FarcasterActionAdapter from \"@adland/react/farcaster\" instead.\nregisterAdlandSlot(WebActionAdapter);\n"]}
1
+ {"version":3,"sources":["../src/render.ts","../src/theme.ts","../src/element.ts","../src/register.ts","../src/index.ts"],"names":[],"mappings":";;;;;AAYO,IAAM,QAAA,GAAW;AAAA,EACtB,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF;AAGO,SAAS,WAAW,KAAA,EAAuB;AAChD,EAAA,OAAO,KAAA,CAAM,OAAA;AAAA,IACX,UAAA;AAAA,IACA,CAAC,CAAA,KAAA,CACE;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,GAAA,EAAK,MAAA;AAAA,MACL,GAAA,EAAK,MAAA;AAAA,MACL,GAAA,EAAK,QAAA;AAAA,MACL,GAAA,EAAK;AAAA,OACJ,CAAC;AAAA,GACR;AACF;AAGO,SAAS,WAAW,KAAA,EAAuB;AAChD,EAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CAAE,OAAA,CAAQ,MAAM,MAAM,CAAA;AAC3D;AAQO,IAAM,WAAA,GAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA6EpB,SAAS,WAAW,EAAA,EAAoB;AAgB7C,EAAA,MAAM,KAAA,GAAQ,UAAU,EAAE,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,WAAW,EAAE,CAAA;AAC3B,EAAA,MAAM,WAAA,GAAc,iBAAiB,EAAE,CAAA;AAEvC,EAAA,OAAO,CAAA,qCAAA,EACL,QACI,CAAA,4CAAA,EAA+C,UAAA,CAAW,KAAK,CAAC,CAAA,0BAAA,CAAA,GAChE,EACN,CAAA,qCAAA,EACE,KAAA,GAAQ,CAAA,qCAAA,EAAwC,WAAW,KAAK,CAAC,CAAA,IAAA,CAAA,GAAS,EAC5E,CAAA,EACE,WAAA,GACI,oDAAoD,UAAA,CAAW,WAAW,CAAC,CAAA,IAAA,CAAA,GAC3E,EACN,CAAA,6DAAA,CAAA;AACF;AAGO,SAAS,WAAA,GAAsB;AACpC,EAAA,OAAO,CAAA,gLAAA,CAAA;AACT;AAaO,SAAS,SAAS,EAAA,EAA2B;AAClD,EAAA,OAAO,cAAc,EAAE,CAAA;AACzB;;;ACzKO,IAAM,UAAA,GAAa;AAAA,EACxB,aAAA;AAAA,EACA,aAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,iBAAA;AAAA,EACA,uBAAA;AAAA,EACA,uBAAA;AAAA,EACA,iBAAA;AAAA,EACA,kBAAA;AAAA,EACA,gBAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,uBAAA;AAAA,EACA,0BAAA;AAAA,EACA,qBAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,2BAAA;AAAA,EACA,uBAAA;AAAA,EACA;AACF;AAMO,SAAS,cAAc,IAAA,EAAuB;AACnD,EAAA,MAAM,MAAA,GAAS,iBAAiB,IAAI,CAAA;AACpC,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC7B,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,gBAAA,CAAiB,IAAI,EAAE,IAAA,EAAK;AACjD,IAAA,IAAI,KAAA,EAAO,GAAA,IAAO,CAAA,EAAG,IAAI,IAAI,KAAK,CAAA,CAAA,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,GAAA;AACT;;;ACxBA,IAAM,aAAA,GAAgB,IAAA;AAOtB,IAAM,iBAAA,GAAoB,yBAAA;AAC1B,IAAM,eAAA,GAAkB,MAAA;AAOjB,SAAS,eACd,OAAA,EAC0B;AAC1B,EAAA,OAAO,MAAM,mBAAmB,WAAA,CAAY;AAAA,IAC1C,OAAO,kBAAA,GAAqB;AAAA,MAC1B,MAAA;AAAA,MACA,OAAA;AAAA,MACA,WAAA;AAAA,MACA,aAAA;AAAA,MACA,SAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA;AAAA,MACA,SAAA;AAAA,MACA,UAAA;AAAA,MACA,eAAA;AAAA,MACA;AAAA,KACF;AAAA,IAEQ,YAAA,GAAe,KAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUf,cAAA,GAAiB,KAAA;AAAA,IACjB,iBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,WAAA,GAAc,CAAA;AAAA,IAEtB,iBAAA,GAAoB;AAClB,MAAA,KAAK,KAAK,MAAA,EAAO;AAAA,IACnB;AAAA,IAEA,oBAAA,GAAuB;AACrB,MAAA,IAAA,CAAK,QAAA,EAAS;AAAA,IAChB;AAAA,IAEA,wBAAA,GAA2B;AACzB,MAAA,IAAI,IAAA,CAAK,WAAA,EAAa,KAAK,IAAA,CAAK,MAAA,EAAO;AAAA,IACzC;AAAA,IAEQ,KAAK,IAAA,EAAkC;AAC7C,MAAA,OAAO,IAAA,CAAK,YAAA,CAAa,IAAI,CAAA,IAAK,MAAA;AAAA,IACpC;AAAA,IAEQ,OAAA,GAAkB;AACxB,MAAA,OAAO,OAAO,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA,IAAK,aAAa,CAAA,IAAK,aAAA;AAAA,IACxD;AAAA,IAEQ,QAAA,GAAW;AACjB,MAAA,IAAA,CAAK,iBAAA,IAAoB;AACzB,MAAA,IAAA,CAAK,iBAAA,GAAoB,MAAA;AACzB,MAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,QAAA,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,IAAA,CAAK,cAAc,CAAA;AACzD,QAAA,IAAA,CAAK,cAAA,GAAiB,MAAA;AAAA,MACxB;AAAA,IACF;AAAA;AAAA,IAGA,MAAc,MAAA,GAKX;AACD,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA;AACtC,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,IAAI;AACF,UAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,MAAM,UAAU,CAAA,EAAa,KAAK,IAAA,EAAK;AAAA,QAC7D,CAAA,CAAA,MAAQ;AACN,UAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAAA,QACjC;AAAA,MACF;AAEA,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,MAAM,CAAA;AAC7B,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,UAAU,CAAA;AACpC,MAAA,IAAI,CAAC,QAAQ,CAAC,OAAA,SAAgB,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAEtD,MAAA,MAAM,MAAA,GAAS,iBAAiB,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,IAAA,CAAK,SAAS,CAAC,CAAA;AASpE,MAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACpB,QAAA,MAAM,IAAA,GAAO,MAAM,YAAA,CAAa,MAAA,EAAQ,OAAA,EAAS;AAAA,UAC/C,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA;AAAA,UAC5B,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,WAAW;AAAA,SAChC,CAAA;AAGD,QAAA,OAAO,OACH,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,KAAK,IAAA,CAAK,GAAA,EAAK,IAAA,EAAM,IAAA,CAAK,MAAK,GAClD,EAAE,IAAA,EAAM,IAAA,EAAM,KAAK,IAAA,EAAK;AAAA,MAC9B;AAEA,MAAA,MAAM,GAAA,GAAM,MAAM,gBAAA,CAAiB,MAAA,EAAQ,IAAc,CAAA;AACzD,MAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,KAAK,IAAA,EAAK;AACzC,MAAA,OAAO,cAAA,CAAe,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,IACjD;AAAA,IAEA,MAAc,MAAA,GAAS;AACrB,MAAA,MAAM,KAAA,GAAQ,EAAE,IAAA,CAAK,WAAA;AACrB,MAAA,IAAA,CAAK,QAAA,EAAS;AAGd,MAAA,IAAI,CAAC,IAAA,CAAK,KAAA,CAAM,SAAA,EAAW,IAAA,CAAK,MAAM,SAAA,GAAY,eAAA;AAElD,MAAA,IAAI,MAAA;AACJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,MAAM,KAAK,MAAA,EAAO;AAAA,MAC7B,CAAA,CAAA,MAAQ;AACN,QAAA,MAAA,GAAS,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAAA,MACnC;AAEA,MAAA,IAAI,KAAA,KAAU,IAAA,CAAK,WAAA,IAAe,CAAC,KAAK,WAAA,EAAa;AAErD,MAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,GAAO,WAAW,MAAA,CAAO,IAAI,IAAI,WAAA,EAAY;AAClE,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,aAAa,CAAA;AACzC,MAAA,MAAM,IAAA,GACJ,SAAA,KAAc,QAAA,IAAY,SAAA,KAAc,UACpC,SAAA,GACA,UAAA;AACN,MAAA,IAAI,SAAS,QAAA,EAAU,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,OAAO,IAAI,CAAA;AAAA,WAAA,IAClD,SAAS,OAAA,EAAS,IAAA,CAAK,WAAA,CAAY,KAAA,EAAO,OAAO,IAAI,CAAA;AAAA,WACzD,IAAA,CAAK,cAAA,CAAe,KAAA,EAAO,MAAA,CAAO,IAAI,CAAA;AAI3C,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,MAAM,KAAK,MAAA,CAAO,IAAA;AAC7C,MAAA,IAAA,CAAK,YAAA,GAAe,QAAA;AACpB,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,MAAM,QACJ,IAAA,CAAK,IAAA,CAAK,UAAU,CAAA,IAAK,OACrB,uBAAA,GACA,eAAA;AACN,QAAA,IAAA,CAAK,iBAAA,GAAoB,MAAM,IAAA,EAAM;AAAA,UACnC,IAAA,EAAM,QAAA;AAAA,UACN,OAAA,EAAS,KAAK,OAAA,EAAQ;AAAA,UACtB,SAAA,EAAW,IAAA,CAAK,IAAA,CAAK,WAAW,CAAA;AAAA,UAChC,KAAK,MAAA,CAAO,GAAA;AAAA,UACZ,KAAA,EAAO,CAAC,MAAA,CAAO,IAAA;AAAA,UACf,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,UAAU,CAAA;AAAA,UAC9B,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,SAAS;AAAA,SAC7B,CAAA;AAAA,MACH;AAAA,IACF;AAAA,IAEQ,YAAA,CAAa,OAAe,IAAA,EAAqB;AACvD,MAAA,MAAM,IAAA,GACJ,IAAA,CAAK,UAAA,KACJ,IAAA,CAAK,YAAA,GAAe,IAAA,GAAO,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA,CAAA;AAChE,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,MAAA,IAAA,CAAK,SAAA,GAAY,CAAA,OAAA,EAAU,WAAW,CAAA,QAAA,EAAW,KAAK,CAAA,CAAA;AACtD,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,aAAA,CAA2B,cAAc,CAAA;AAC3D,MAAA,IAAA,EAAM,iBAAiB,OAAA,EAAS,MAAM,KAAK,WAAA,CAAY,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,IACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiCQ,WAAA,CAAY,OAAe,IAAA,EAAqB;AAItD,MAAA,IAAA,CAAK,SAAA,GAAY,KAAA;AACjB,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,aAAA,CAA2B,cAAc,CAAA;AAC3D,MAAA,IAAA,EAAM,iBAAiB,OAAA,EAAS,MAAM,KAAK,WAAA,CAAY,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,IACrE;AAAA,IAEQ,cAAA,CAAe,OAAe,IAAA,EAAqB;AACzD,MAAA,IAAA,CAAK,eAAA,EAAgB;AAErB,MAAA,MAAM,MAAM,IAAA,GAAO,QAAA,CAAS,IAAI,CAAA,GAAI,KAAK,QAAA,EAAS;AAClD,MAAA,IAAA,CAAK,cAAA,GAAiB,CAAC,CAAC,GAAA;AACxB,MAAA,MAAM,IAAA,GAAO,MACT,CAAA,SAAA,EAAY,UAAA,CAAW,GAAG,CAAC,CAAA,qGAAA,EAAwG,KAAK,CAAA,IAAA,CAAA,GACxI,KAAA;AACJ,MAAA,MAAM,KAAA,GAAQ,cAAc,IAAI,CAAA;AAoBhC,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,MAAA,MAAA,CAAO,YAAA;AAAA,QACL,SAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,MAAA,CAAO,YAAA,CAAa,SAAS,eAAe,CAAA;AAC5C,MAAA,MAAA,CAAO,YAAA,CAAa,aAAa,IAAI,CAAA;AACrC,MAAA,MAAA,CAAO,MAAM,OAAA,GAAU,+CAAA;AACvB,MAAA,MAAA,CAAO,SAAS,CAAA,8DAAA,EAAiE,KAAK,CAAA,iEAAA,EAAoE,WAAW,wBAAwB,IAAI,CAAA,6TAAA,CAAA;AAEjM,MAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAAoB;AACnC,QAAA,IAAI,CAAA,CAAE,MAAA,KAAW,MAAA,CAAO,aAAA,EAAe;AACvC,QAAA,MAAM,MAAM,CAAA,CAAE,IAAA;AACd,QAAA,IAAI,KAAK,QAAA,KAAa,MAAA,IAAU,OAAO,GAAA,CAAI,WAAW,QAAA,EAAU;AAC9D,UAAA,MAAA,CAAO,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAA,CAAA;AACnC,UAAA,IAAA,CAAK,MAAM,SAAA,GAAY,EAAA;AAAA,QACzB,CAAA,MAAA,IAAW,GAAA,EAAK,QAAA,KAAa,OAAA,EAAS;AAIpC,UAAA,IAAA,CAAK,WAAA,CAAY,IAAA,EAAM,IAAA,CAAK,cAAc,CAAA;AAAA,QAC5C;AAAA,MACF,CAAA;AACA,MAAA,IAAA,CAAK,cAAA,GAAiB,OAAA;AACtB,MAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,OAAO,CAAA;AAC1C,MAAA,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,IACzB;AAAA,IAEQ,QAAA,GAAmB;AACzB,MAAA,OAAO,IAAA,CAAK,IAAA,CAAK,eAAe,CAAA,IAAK,iBAAA;AAAA,IACvC;AAAA,IAEQ,QAAA,GAA0B;AAChC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,KAAK,MAAM,CAAA;AAClD,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,MAAA,OAAO,CAAA,EAAG,KAAK,QAAA,EAAU,UAAU,IAAI,CAAA,OAAA,EAAU,IAAA,CAAK,OAAA,EAAS,CAAA,CAAA;AAAA,IACjE;AAAA,IAEQ,WAAA,CAAY,MAAqB,aAAA,EAAwB;AAC/D,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,KAAK,MAAM,CAAA;AAClD,MAAA,MAAM,MAAA,GAAS;AAAA,QACb,OAAA,EAAS,KAAK,OAAA,EAAQ;AAAA,QACtB,SAAA,EAAW,IAAA,CAAK,IAAA,CAAK,WAAW,CAAA;AAAA,QAChC,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,UAAU,CAAA;AAAA,QAC9B,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,SAAS;AAAA,OAC9B;AAEA,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,IAAI,IAAA,aAAiB,OAAA,EAAS,EAAE,MAAM,GAAA,EAAK,IAAA,EAAM,GAAG,MAAA,EAAQ,CAAA;AAC5D,QAAA,IAAI,CAAC,aAAA,EAAe,KAAK,OAAA,CAAQ,gBAAgB,IAAI,CAAA;AAAA,MACvD,WAAW,IAAA,EAAM;AACf,QAAA,KAAK,WAAW,aAAA,EAAe,EAAE,IAAA,EAAM,GAAG,QAAQ,CAAA;AAClD,QAAA,IAAI,CAAC,aAAA,EAAe;AAClB,UAAA,KAAK,OAAA,CAAQ,oBAAA;AAAA,YACX,IAAA;AAAA,YACA,KAAK,OAAA,EAAQ;AAAA,YACb,KAAK,QAAA;AAAS,WAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,GACF;AACF;;;AC7UO,SAAS,kBAAA,CACd,OAAA,EACA,GAAA,GAAM,aAAA,EACA;AACN,EAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC3C,EAAA,IAAI,cAAA,CAAe,GAAA,CAAI,GAAG,CAAA,EAAG;AAC7B,EAAA,cAAA,CAAe,MAAA,CAAO,GAAA,EAAK,cAAA,CAAe,OAAO,CAAC,CAAA;AACpD;ACHA,kBAAA,CAAmB,gBAAgB,CAAA","file":"index.js","sourcesContent":["import { type AdData, adDestination } from \"@adland/data\";\nimport { getAdDescription, getAdIcon, getAdTitle } from \"@adland/react/core\";\n\n/**\n * Part names exposed for `::part()` styling in inline (shadow DOM) mode.\n *\n * `badge` was here and is gone. It rendered the ad's TYPE — \"Link\", \"Token\" —\n * which tells a reader nothing they want: they can see it is a link by looking\n * at it, and the word cost a chunk of a row that is already one line of title\n * and one of description. `label` stays, because \"Ad\" is a disclosure rather\n * than a description.\n */\nexport const AD_PARTS = [\n \"card\",\n \"media\",\n \"body\",\n \"title\",\n \"description\",\n \"label\",\n] as const;\n\n/** Escape text for safe interpolation into HTML element content. */\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n}\n\n/** Escape a value for use inside a double-quoted HTML attribute. */\nexport function escapeAttr(value: string): string {\n return value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\");\n}\n\n/**\n * Default ad-card styles. Every visual is driven by a `--adland-*` custom\n * property with a sensible fallback, so a publisher can re-theme the card\n * without overriding a single rule. Custom properties inherit through the\n * shadow boundary (inline mode) and are copied into the iframe (isolated mode).\n */\nexport const BASE_STYLES = `\n:host { display: block; }\n* { box-sizing: border-box; }\n.adland-card {\n position: relative;\n display: flex;\n gap: var(--adland-gap, 12px);\n align-items: center;\n padding: var(--adland-padding, 12px);\n border: var(--adland-border-width, 1px) solid var(--adland-border, #e5e7eb);\n border-radius: var(--adland-radius, 12px);\n background: var(--adland-bg, #ffffff);\n color: var(--adland-fg, #111827);\n font-family: var(--adland-font, system-ui, -apple-system, sans-serif);\n cursor: pointer;\n text-decoration: none;\n /* Resting shadow, not just a hover one. Only --adland-shadow-hover existed,\n which quietly ruled out every style whose card is raised at rest: a hard\n offset shadow is most of what makes a neo-brutalist card look like one, and\n there was no way to ask for it. Defaults to none, so nothing moves. */\n box-shadow: var(--adland-shadow, none);\n transition: var(\n --adland-transition,\n border-color 0.15s ease,\n box-shadow 0.15s ease,\n transform 0.15s ease\n );\n}\n.adland-card:hover {\n border-color: var(--adland-border-hover, #d1d5db);\n box-shadow: var(--adland-shadow-hover, 0 1px 3px rgba(0, 0, 0, 0.08));\n /* The other half of a raised card: shrink the shadow AND move the card into\n the space it vacated, or the card appears to grow rather than press. No\n default, so a theme that sets neither is untouched. */\n transform: var(--adland-transform-hover, none);\n}\n.adland-media {\n width: var(--adland-media-size, 44px);\n height: var(--adland-media-size, 44px);\n border-radius: var(--adland-media-radius, 8px);\n object-fit: cover;\n flex: none;\n background: var(--adland-media-bg, #f3f4f6);\n border: var(--adland-media-border, none);\n}\n.adland-body { min-width: 0; flex: 1; }\n.adland-title {\n margin: 0;\n font-size: var(--adland-title-size, 14px);\n font-weight: var(--adland-title-weight, 600);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-description {\n margin: 2px 0 0;\n font-size: var(--adland-description-size, 12px);\n color: var(--adland-muted, #6b7280);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.adland-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: var(--adland-label-size, 9px);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--adland-label, #9ca3af);\n}\n.adland-empty { justify-content: center; color: var(--adland-muted, #6b7280); }\n`;\n\n/** Inner card markup (parts + classes) for a loaded ad. */\nexport function cardMarkup(ad: AdData): string {\n /*\n * The icon, not the image — this card is a compact row.\n *\n * `@adland/react` exposes both because an ad carries both, and which is right\n * depends on the shape of the space rather than on the ad: `getAdImage`\n * prefers a wide cover, `getAdIcon` prefers a square mark. `.adland-media` is\n * square by construction — `--adland-media-size` sets its width AND its\n * height — so it is unambiguously the second case.\n *\n * It was asking for the image, which for a link ad meant an 840x560 Open\n * Graph cover cropped into a 44px box: the middle of somebody's banner where\n * their favicon belongs, and nothing about it looking like a mistake rather\n * than a bad picture. Both getters fall back to the other, so an ad with only\n * one still renders.\n */\n const image = getAdIcon(ad);\n const title = getAdTitle(ad);\n const description = getAdDescription(ad);\n\n return `<div part=\"card\" class=\"adland-card\">${\n image\n ? `<img part=\"media\" class=\"adland-media\" src=\"${escapeAttr(image)}\" alt=\"\" loading=\"lazy\" />`\n : \"\"\n }<div part=\"body\" class=\"adland-body\">${\n title ? `<p part=\"title\" class=\"adland-title\">${escapeHtml(title)}</p>` : \"\"\n }${\n description\n ? `<p part=\"description\" class=\"adland-description\">${escapeHtml(description)}</p>`\n : \"\"\n }</div><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/** Inner card markup for an empty slot (the \"Your ad here\" CTA). */\nexport function emptyMarkup(): string {\n return `<div part=\"card\" class=\"adland-card adland-empty\"><span part=\"description\" class=\"adland-description\">Your ad here</span><span part=\"label\" class=\"adland-label\">Ad</span></div>`;\n}\n\n/**\n * Where a click on this ad should go, on the open web.\n *\n * Delegates to `adDestination` in `@adland/data`, which is what\n * `WebActionAdapter` in `@adland/react` has always used. This was a second,\n * shorter implementation that handled `link` and `miniapp` and returned null\n * for everything else — so in the default isolated mode a token, cast, tweet\n * or profile ad rendered no anchor and a click on it did nothing at all, while\n * the same ad in inline or light mode worked, because those route through the\n * adapter instead. One destination function, one behaviour, six ad types.\n */\nexport function adWebUrl(ad: AdData): string | null {\n return adDestination(ad);\n}\n","/**\n * The `--adland-*` custom properties the card reads. Custom properties inherit\n * through the shadow boundary automatically (inline mode), but NOT across an\n * iframe document boundary — so for isolated mode we read whichever of these\n * are set on the host and copy them into the iframe's `:root`.\n */\nexport const THEME_VARS = [\n \"--adland-bg\",\n \"--adland-fg\",\n \"--adland-muted\",\n \"--adland-label\",\n \"--adland-border\",\n \"--adland-border-hover\",\n \"--adland-border-width\",\n \"--adland-radius\",\n \"--adland-padding\",\n \"--adland-bleed\",\n \"--adland-gap\",\n \"--adland-font\",\n \"--adland-shadow\",\n \"--adland-shadow-hover\",\n \"--adland-transform-hover\",\n \"--adland-transition\",\n \"--adland-media-size\",\n \"--adland-media-radius\",\n \"--adland-media-bg\",\n \"--adland-title-size\",\n \"--adland-title-weight\",\n \"--adland-description-size\",\n \"--adland-media-border\",\n \"--adland-label-size\",\n] as const;\n\n/**\n * Serialize the theme custom properties currently set on `host` into a CSS\n * declaration string (e.g. `--adland-bg:#000;`). Empty when none are set.\n */\nexport function readThemeVars(host: Element): string {\n const styles = getComputedStyle(host);\n let out = \"\";\n for (const name of THEME_VARS) {\n const value = styles.getPropertyValue(name).trim();\n if (value) out += `${name}:${value};`;\n }\n return out;\n}\n","import type { AdData } from \"@adland/data\";\nimport {\n type ActionAdapter,\n type AdConsent,\n createReadClient,\n fetchAdByKey,\n fetchAdFromURI,\n fetchMetadataURI,\n trackClick,\n trackImpression,\n trackVerifiedImpression,\n} from \"@adland/react/core\";\nimport {\n adWebUrl,\n BASE_STYLES,\n cardMarkup,\n emptyMarkup,\n escapeAttr,\n} from \"./render\";\nimport { readThemeVars } from \"./theme\";\n\nconst DEFAULT_CHAIN = 8453;\n/**\n * Where an unsold slot sends a reader.\n *\n * The 0xSlots app moved off its own subdomain and now lives under a path on the\n * apex domain, so the base carries `/app` and the slot path is appended to it.\n */\nconst DEFAULT_BASE_LINK = \"https://0xslots.org/app\";\nconst RESERVED_HEIGHT = \"68px\";\n\n/**\n * Build the `<adland-slot>` custom-element class bound to a click-routing\n * adapter. The web CDN bundle binds the web adapter (no Farcaster); the\n * Farcaster bundle binds the Mini App adapter.\n */\nexport function makeAdlandSlot(\n adapter: ActionAdapter,\n): CustomElementConstructor {\n return class AdlandSlot extends HTMLElement {\n static observedAttributes = [\n \"slot\",\n \"chain\",\n \"placement\",\n \"render-mode\",\n \"gateway\",\n \"endpoint\",\n \"rpc-url\",\n \"consent\",\n \"verified\",\n \"base-link-url\",\n \"data-ad\",\n ];\n\n private shadowInited = false;\n /**\n * Whether the framed markup is wrapped in a real `<a>`.\n *\n * The click handler used to assume it always was, and told `handleClick`\n * the URL had already been opened. That is only true when a destination\n * existed — and for four of the six ad types it did not, so the click was\n * tracked and then dropped on the floor. Recording what was actually\n * rendered lets the adapter take over when the anchor is missing.\n */\n private framedInAnchor = false;\n private cleanupImpression?: () => void;\n /**\n * The address this element is actually showing.\n *\n * The `slot` attribute when there is one, otherwise whatever `slot-key`\n * resolved to. Everything below the fetch needs an address — the impression\n * is recorded against one and the empty-slot CTA links to one — and a name\n * is not an address until the module has answered.\n */\n private resolvedSlot?: string;\n private messageHandler?: (e: MessageEvent) => void;\n private renderToken = 0;\n\n connectedCallback() {\n void this.render();\n }\n\n disconnectedCallback() {\n this.teardown();\n }\n\n attributeChangedCallback() {\n if (this.isConnected) void this.render();\n }\n\n private attr(name: string): string | undefined {\n return this.getAttribute(name) ?? undefined;\n }\n\n private chainId(): number {\n return Number(this.attr(\"chain\") ?? DEFAULT_CHAIN) || DEFAULT_CHAIN;\n }\n\n private teardown() {\n this.cleanupImpression?.();\n this.cleanupImpression = undefined;\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = undefined;\n }\n }\n\n /** Resolve the ad: inline `data-ad` JSON, else an on-chain read. */\n private async loadAd(): Promise<{\n data: AdData | null;\n cid: string | null;\n /** Set when a NAME was resolved; the address it pointed at. */\n slot?: string;\n }> {\n const staticData = this.attr(\"data-ad\");\n if (staticData) {\n try {\n return { data: JSON.parse(staticData) as AdData, cid: null };\n } catch {\n return { data: null, cid: null };\n }\n }\n\n const slot = this.attr(\"slot\");\n const slotKey = this.attr(\"slot-key\");\n if (!slot && !slotKey) return { data: null, cid: null };\n\n const client = createReadClient(this.chainId(), this.attr(\"rpc-url\"));\n\n // A NAME, resolved through the metadata module, in one call.\n //\n // Preferred over an address for the reason the whole registry exists: an\n // address pasted into a page is one nobody can ever change, so redeploying\n // a slot leaves every embed carrying it showing a dead space. `slot` still\n // wins when both are set, because a publisher who pinned an address did so\n // to opt out of trusting us with the pointer.\n if (!slot && slotKey) {\n const snap = await fetchAdByKey(client, slotKey, {\n gateway: this.attr(\"gateway\"),\n adModule: this.attr(\"ad-module\"),\n });\n // The resolved address travels with it: the impression below is\n // recorded against a slot, and a name is not one.\n return snap\n ? { data: snap.data, cid: snap.cid, slot: snap.slot }\n : { data: null, cid: null };\n }\n\n const uri = await fetchMetadataURI(client, slot as string);\n if (!uri) return { data: null, cid: null };\n return fetchAdFromURI(uri, this.attr(\"gateway\"));\n }\n\n private async render() {\n const token = ++this.renderToken;\n this.teardown();\n\n // Reserve height up front so an async load doesn't shift the layout.\n if (!this.style.minHeight) this.style.minHeight = RESERVED_HEIGHT;\n\n let result: { data: AdData | null; cid: string | null; slot?: string };\n try {\n result = await this.loadAd();\n } catch {\n result = { data: null, cid: null };\n }\n // A newer render superseded this one while awaiting — bail.\n if (token !== this.renderToken || !this.isConnected) return;\n\n const inner = result.data ? cardMarkup(result.data) : emptyMarkup();\n const requested = this.attr(\"render-mode\");\n const mode =\n requested === \"inline\" || requested === \"light\"\n ? requested\n : \"isolated\";\n if (mode === \"inline\") this.renderInline(inner, result.data);\n else if (mode === \"light\") this.renderLight(inner, result.data);\n else this.renderIsolated(inner, result.data);\n\n // The attribute if there was one, otherwise the address the name\n // resolved to. Recording an impression needs an address either way.\n const resolved = this.attr(\"slot\") ?? result.slot;\n this.resolvedSlot = resolved;\n if (resolved) {\n const track =\n this.attr(\"verified\") != null\n ? trackVerifiedImpression\n : trackImpression;\n this.cleanupImpression = track(this, {\n slot: resolved,\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n cid: result.cid,\n empty: !result.data,\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n });\n }\n }\n\n private renderInline(inner: string, data: AdData | null) {\n const root =\n this.shadowRoot ??\n (this.shadowInited ? null : this.attachShadow({ mode: \"open\" }));\n if (!root) return;\n this.shadowInited = true;\n root.innerHTML = `<style>${BASE_STYLES}</style>${inner}`;\n const card = root.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n /**\n * Into the page itself — no iframe, no shadow root, no styles of ours.\n *\n * ── The mode that exists so you can use your own CSS ─────────────────────\n *\n * The other two isolate, and both isolate from the publisher as much as\n * from the advertiser. `isolated` seals the card in an iframe, where the\n * only way in is the `--adland-*` custom properties copied across the\n * document boundary. `inline` uses a shadow root, which custom properties\n * inherit through and `::part()` can reach — better, and still not a\n * stylesheet: Tailwind emits classes, and a class in the page cannot cross\n * a shadow boundary. So a publisher wanting `rounded-2xl` on the card had\n * no way to say it.\n *\n * Here the markup is ordinary children of `<adland-slot>`. Every class hook\n * — `.adland-card`, `.adland-media`, `.adland-title`, `.adland-description`\n * — is a normal element in the page, so a page stylesheet, a Tailwind\n * arbitrary variant, or a `cn()` on the host all apply directly.\n *\n * ── What you give up, and why it is not the default ──────────────────────\n *\n * Isolation runs both ways. In this mode the publisher's CSS can break the\n * ad, and a reset of theirs can leave an advertiser's creative unreadable\n * with nobody able to see it but their own visitors. `BASE_STYLES` is NOT\n * injected — in the light DOM those rules would leak to the whole page —\n * so an unstyled card is what arrives, and styling it is now the\n * publisher's job rather than their option.\n *\n * That is a real cost and it is why this is opt-in and named. A publisher\n * who wants it asks for it; everybody else keeps the sealed default.\n */\n private renderLight(inner: string, data: AdData | null) {\n // `innerHTML` rather than a parsed fragment: `inner` is built by\n // `cardMarkup`, which escapes every interpolated value — `escapeHtml` for\n // text, `escapeAttr` for attributes — so the only markup here is ours.\n this.innerHTML = inner;\n const card = this.querySelector<HTMLElement>(\".adland-card\");\n card?.addEventListener(\"click\", () => this.handleClick(data, false));\n }\n\n private renderIsolated(inner: string, data: AdData | null) {\n this.replaceChildren();\n\n const url = data ? adWebUrl(data) : this.emptyUrl();\n this.framedInAnchor = !!url;\n const body = url\n ? `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:block;text-decoration:none;color:inherit\">${inner}</a>`\n : inner;\n const theme = readThemeVars(this);\n\n /*\n * The frame is a viewport, and anything drawn outside the card's border\n * box is outside the viewport.\n *\n * On an ordinary page a shadow spills past its element and a `transform`\n * moves it over its neighbours; both are visible because the page has\n * room. Here the card is 100% of a body with no padding, so a\n * `--adland-shadow` of `6px 6px` and a `--adland-transform-hover` of\n * `translate(4px, 4px)` are simply cropped by the frame — which showed up\n * as the right border vanishing on hover, an effect with no cause\n * anywhere in the card's own CSS.\n *\n * `--adland-bleed` is the room to draw in. Zero by default, so nothing\n * moves for a theme that does not spill; a raised style sets it to at\n * least its shadow offset plus its hover travel. The height message below\n * reads `scrollHeight`, which includes body padding, so the frame still\n * sizes itself correctly.\n */\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\n \"sandbox\",\n \"allow-popups allow-popups-to-escape-sandbox allow-scripts\",\n );\n iframe.setAttribute(\"title\", \"Advertisement\");\n iframe.setAttribute(\"scrolling\", \"no\");\n iframe.style.cssText = \"width:100%;border:0;display:block;height:100%\";\n iframe.srcdoc = `<!doctype html><html><head><meta charset=\"utf-8\"><style>:root{${theme}}html,body{margin:0;padding:0}body{padding:var(--adland-bleed,0)}${BASE_STYLES}</style></head><body>${body}<script>(function(){function send(t,x){parent.postMessage(Object.assign({__adland:t},x||{}),'*')}addEventListener('click',function(){send('click')});function size(){send('size',{height:document.documentElement.scrollHeight})}new ResizeObserver(size).observe(document.documentElement);size()})()</script></body></html>`;\n\n const handler = (e: MessageEvent) => {\n if (e.source !== iframe.contentWindow) return;\n const msg = e.data as { __adland?: string; height?: number };\n if (msg?.__adland === \"size\" && typeof msg.height === \"number\") {\n iframe.style.height = `${msg.height}px`;\n this.style.minHeight = \"\";\n } else if (msg?.__adland === \"click\") {\n // Only skip the adapter when an anchor really did the navigating.\n // Passing `true` unconditionally meant an ad with no anchor tracked\n // the click and went nowhere.\n this.handleClick(data, this.framedInAnchor);\n }\n };\n this.messageHandler = handler;\n window.addEventListener(\"message\", handler);\n this.appendChild(iframe);\n }\n\n private baseLink(): string {\n return this.attr(\"base-link-url\") ?? DEFAULT_BASE_LINK;\n }\n\n private emptyUrl(): string | null {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n if (!slot) return null;\n return `${this.baseLink()}/slots/${slot}?chain=${this.chainId()}`;\n }\n\n private handleClick(data: AdData | null, alreadyOpened: boolean) {\n const slot = this.resolvedSlot ?? this.attr(\"slot\");\n const shared = {\n chainId: this.chainId(),\n placement: this.attr(\"placement\"),\n endpoint: this.attr(\"endpoint\"),\n consent: this.attr(\"consent\") as AdConsent | undefined,\n };\n\n if (data) {\n if (slot) trackClick(\"click\", { slot, cid: null, ...shared });\n if (!alreadyOpened) void adapter.performAdAction(data);\n } else if (slot) {\n void trackClick(\"click-empty\", { slot, ...shared });\n if (!alreadyOpened) {\n void adapter.performEmptyAdAction(\n slot,\n this.chainId(),\n this.baseLink(),\n );\n }\n }\n }\n };\n}\n","import type { ActionAdapter } from \"@adland/react/core\";\nimport { makeAdlandSlot } from \"./element\";\n\n/**\n * Define the `<adland-slot>` custom element bound to a click-routing adapter.\n * Safe to call more than once and in non-DOM environments — it no-ops if the\n * element is already defined or `customElements` is unavailable (SSR).\n */\nexport function registerAdlandSlot(\n adapter: ActionAdapter,\n tag = \"adland-slot\",\n): void {\n if (typeof customElements === \"undefined\") return;\n if (customElements.get(tag)) return;\n customElements.define(tag, makeAdlandSlot(adapter));\n}\n","import { WebActionAdapter } from \"@adland/react/core\";\nimport { registerAdlandSlot } from \"./register\";\n\nexport { WebActionAdapter } from \"@adland/react/core\";\nexport { makeAdlandSlot } from \"./element\";\nexport { registerAdlandSlot } from \"./register\";\nexport { AD_PARTS } from \"./render\";\nexport { THEME_VARS } from \"./theme\";\n\n// Convenience: importing the package auto-registers <adland-slot> with the web\n// adapter (no Farcaster). For Farcaster routing, call registerAdlandSlot with\n// FarcasterActionAdapter from \"@adland/react/farcaster\" instead.\nregisterAdlandSlot(WebActionAdapter);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adland/embed",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -28,7 +28,7 @@
28
28
  "@0xslots/sdk": "^0.27.0",
29
29
  "viem": "^2.0.0",
30
30
  "@adland/data": "0.20.0",
31
- "@adland/react": "0.23.1"
31
+ "@adland/react": "0.25.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22",