@konvert7/promoot 0.2.0 → 0.3.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/README.md +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/promoot-block.js +23 -3
- package/dist/types.d.ts +17 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,17 @@ import { PromootBlock } from "@konvert7/promoot";
|
|
|
16
16
|
The `url` is the embed URL from your Promoot dashboard — the same string the iframe
|
|
17
17
|
snippet uses, so one value serves either method.
|
|
18
18
|
|
|
19
|
+
## What it renders
|
|
20
|
+
|
|
21
|
+
Whatever the sponsor bought, without you choosing:
|
|
22
|
+
|
|
23
|
+
- **An image ad** — their artwork, cropped to fill the slot.
|
|
24
|
+
- **A text ad** — their favicon, a headline, a description and their domain, centred.
|
|
25
|
+
Headline and description are both optional; a link on its own renders the mark and
|
|
26
|
+
the bare domain. If no favicon could be captured, a monogram of the domain's first
|
|
27
|
+
letter is shown instead — never both, so a transparent icon can't show the letter
|
|
28
|
+
through it.
|
|
29
|
+
|
|
19
30
|
## Why inline
|
|
20
31
|
|
|
21
32
|
An iframe fails closed. If a content filter ever blocks the frame, your sponsor's ad
|
|
@@ -76,3 +87,4 @@ styles. Disclosure of a paid placement is not the site owner's to remove.
|
|
|
76
87
|
## License
|
|
77
88
|
|
|
78
89
|
MIT
|
|
90
|
+
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { PromootBlock, type PromootBlockProps } from "./promoot-block.js";
|
|
2
|
-
export type { BlockPayload, BlockRender } from "./types.js";
|
|
2
|
+
export type { BlockPayload, BlockRender, Creative } from "./types.js";
|
package/dist/promoot-block.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
export function blockEndpoint(embedUrl) {
|
|
3
3
|
return embedUrl.replace("/embed/", "/api/block/");
|
|
4
4
|
}
|
|
@@ -21,7 +21,15 @@ export function scopedCss(payload) {
|
|
|
21
21
|
return `${scope} { ${payload.paletteVars}; position: relative; display: block; width: 100%; max-width: ${payload.slot.widthPx}px; height: auto; max-height: ${payload.slot.heightPx}px; aspect-ratio: ${payload.slot.widthPx} / ${payload.slot.heightPx}; }
|
|
22
22
|
${scope} a { text-decoration: none; }
|
|
23
23
|
${scope} .promoot-ad { display: block; position: relative; height: 100%; width: 100%; }
|
|
24
|
-
${scope} .promoot-ad img { display: block; height: 100%; width: 100%; object-fit: cover; }
|
|
24
|
+
${scope} .promoot-ad > img { display: block; height: 100%; width: 100%; object-fit: cover; }
|
|
25
|
+
${scope} .promoot-text { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 100%; height: 100%; box-sizing: border-box; padding: 6px 12px; border: 1px solid var(--edge); border-radius: 10px; background: var(--surface); overflow: hidden; text-align: center; font-family: system-ui, -apple-system, sans-serif; }
|
|
26
|
+
${scope} .promoot-ad:hover .promoot-text { border-color: var(--edge-hover); background: var(--surface-hover); }
|
|
27
|
+
${scope} .promoot-mark { position: relative; flex: none; display: grid; place-items: center; width: 24px; height: 24px; margin-bottom: 1px; border-radius: 6px; background: var(--edge); color: var(--surface); font-weight: 700; font-size: 12px; overflow: hidden; }
|
|
28
|
+
${scope} .promoot-mark img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
|
|
29
|
+
${scope} .promoot-text > span:not(.promoot-mark) { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
30
|
+
${scope} .promoot-text .promoot-headline { font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.25; }
|
|
31
|
+
${scope} .promoot-text .promoot-desc { font-size: 12px; color: var(--muted); line-height: 1.25; }
|
|
32
|
+
${scope} .promoot-text .promoot-host { font-size: 11px; color: var(--muted); line-height: 1.25; }
|
|
25
33
|
${scope} .promoot-cta { display: flex; flex-direction: column; align-items: center; gap: 3px; width: 100%; height: 100%; justify-content: center; padding: 0 10px; text-align: center; border: 1px dashed var(--edge); border-radius: 10px; box-sizing: border-box; color: var(--muted); background: var(--surface); font-family: system-ui, -apple-system, sans-serif; }
|
|
26
34
|
${scope} .promoot-cta:hover { border-color: var(--edge-hover); background: var(--surface-hover); }
|
|
27
35
|
${scope} .promoot-cta strong { font-size: 14px; color: var(--ink); }
|
|
@@ -43,10 +51,22 @@ const labelStyle = {
|
|
|
43
51
|
borderRadius: "4px",
|
|
44
52
|
padding: "1px 5px",
|
|
45
53
|
};
|
|
54
|
+
// One or the other, never layered: a favicon with transparent corners would let
|
|
55
|
+
// the monogram show through it, and a broken image painted over the letter
|
|
56
|
+
// hides it anyway.
|
|
57
|
+
function Mark({ creative }) {
|
|
58
|
+
return (_jsx("span", { className: "promoot-mark", children: creative.faviconUrl ? (_jsx("img", { src: creative.faviconUrl, alt: "" })) : (creative.monogram) }));
|
|
59
|
+
}
|
|
60
|
+
function AdCreative({ creative }) {
|
|
61
|
+
if (creative.kind === "IMAGE") {
|
|
62
|
+
return _jsx("img", { src: creative.imageUrl, alt: creative.title });
|
|
63
|
+
}
|
|
64
|
+
return (_jsxs("span", { className: "promoot-text", children: [_jsx(Mark, { creative: creative }), creative.headline && _jsx("span", { className: "promoot-headline", children: creative.headline }), creative.body && _jsx("span", { className: "promoot-desc", children: creative.body }), _jsx("span", { className: "promoot-host", children: creative.host })] }));
|
|
65
|
+
}
|
|
46
66
|
export async function PromootBlock({ url, className, style, fallback = null, }) {
|
|
47
67
|
const block = await loadBlock(url);
|
|
48
68
|
if (!block || block.render === "blank") {
|
|
49
69
|
return _jsx(_Fragment, { children: fallback });
|
|
50
70
|
}
|
|
51
|
-
return (_jsxs("div", { "data-promoot-slot": block.slot.id, className: className, style: style, children: [_jsx("style", { dangerouslySetInnerHTML: { __html: scopedCss(block) } }), block.ad && (_jsxs("a", { className: "promoot-ad", href: block.ad.clickUrl, target: "_blank", rel: "noopener nofollow sponsored", children: [_jsx(
|
|
71
|
+
return (_jsxs("div", { "data-promoot-slot": block.slot.id, className: className, style: style, children: [_jsx("style", { dangerouslySetInnerHTML: { __html: scopedCss(block) } }), block.ad && (_jsxs("a", { className: "promoot-ad", href: block.ad.clickUrl, target: "_blank", rel: "noopener nofollow sponsored", children: [_jsx(AdCreative, { creative: block.ad }), block.slot.sponsoredLabel && (_jsx("span", { style: labelStyle, children: block.slot.sponsoredLabel }))] })), block.pitch && (_jsxs("a", { className: "promoot-cta", href: block.pitch.purchaseUrl, target: "_blank", rel: "noopener", children: [_jsx("strong", { children: block.pitch.headline }), block.pitch.pitch && _jsx("span", { className: "promoot-pitch", children: block.pitch.pitch }), block.pitch.proof && _jsx("span", { className: "promoot-proof", children: block.pitch.proof }), _jsx("span", { className: "promoot-terms", children: block.pitch.terms })] })), _jsx("img", { className: "promoot-beacon", src: block.beaconUrl, width: 1, height: 1, alt: "", "aria-hidden": "true" })] }));
|
|
52
72
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export type BlockRender = "ad" | "pitch" | "blank";
|
|
2
|
+
/** An ad is either artwork the sponsor uploaded, or words they wrote. */
|
|
3
|
+
export type Creative = {
|
|
4
|
+
kind: "IMAGE";
|
|
5
|
+
title: string;
|
|
6
|
+
imageUrl: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "TEXT";
|
|
9
|
+
/** Optional: a link alone is a complete ad, and the host stands in. */
|
|
10
|
+
headline: string | null;
|
|
11
|
+
body: string | null;
|
|
12
|
+
/** Null when the sponsor's site has none, or it could not be captured. */
|
|
13
|
+
faviconUrl: string | null;
|
|
14
|
+
host: string;
|
|
15
|
+
monogram: string;
|
|
16
|
+
};
|
|
2
17
|
/** What GET /api/block/:slotId answers with. */
|
|
3
18
|
export type BlockPayload = {
|
|
4
19
|
slot: {
|
|
@@ -8,12 +23,10 @@ export type BlockPayload = {
|
|
|
8
23
|
sponsoredLabel: string | null;
|
|
9
24
|
};
|
|
10
25
|
render: BlockRender;
|
|
11
|
-
ad: {
|
|
12
|
-
title: string;
|
|
13
|
-
imageUrl: string;
|
|
26
|
+
ad: ({
|
|
14
27
|
clickUrl: string;
|
|
15
28
|
expiresAt: string | null;
|
|
16
|
-
} | null;
|
|
29
|
+
} & Creative) | null;
|
|
17
30
|
pitch: {
|
|
18
31
|
headline: string;
|
|
19
32
|
pitch: string | null;
|
package/package.json
CHANGED