@parchar/cross-promo 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/analytics.d.ts +47 -0
  2. package/dist/analytics.d.ts.map +1 -0
  3. package/dist/analytics.js +93 -0
  4. package/dist/analytics.js.map +1 -0
  5. package/dist/cap.d.ts +28 -0
  6. package/dist/cap.d.ts.map +1 -0
  7. package/dist/cap.js +112 -0
  8. package/dist/cap.js.map +1 -0
  9. package/dist/config/network.json +230 -0
  10. package/dist/config.d.ts +5 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +6 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/consent.d.ts +13 -0
  15. package/dist/consent.d.ts.map +1 -0
  16. package/dist/consent.js +21 -0
  17. package/dist/consent.js.map +1 -0
  18. package/dist/element.d.ts +26 -0
  19. package/dist/element.d.ts.map +1 -0
  20. package/dist/element.js +159 -0
  21. package/dist/element.js.map +1 -0
  22. package/dist/embed-entry.d.ts +7 -0
  23. package/dist/embed-entry.d.ts.map +1 -0
  24. package/dist/embed-entry.js +31 -0
  25. package/dist/embed-entry.js.map +1 -0
  26. package/dist/index.d.ts +10 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +9 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/render.d.ts +33 -0
  31. package/dist/render.d.ts.map +1 -0
  32. package/dist/render.js +112 -0
  33. package/dist/render.js.map +1 -0
  34. package/dist/select.d.ts +43 -0
  35. package/dist/select.d.ts.map +1 -0
  36. package/dist/select.js +106 -0
  37. package/dist/select.js.map +1 -0
  38. package/dist/styles.css +158 -0
  39. package/dist/types.d.ts +69 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/validate.d.ts +53 -0
  44. package/dist/validate.d.ts.map +1 -0
  45. package/dist/validate.js +111 -0
  46. package/dist/validate.js.map +1 -0
  47. package/embed/README.md +98 -0
  48. package/embed/iife.js +17 -0
  49. package/package.json +40 -4
  50. package/README.md +0 -3
@@ -0,0 +1,159 @@
1
+ import { networkConfig } from './config';
2
+ import { pickPromotionWithCap, readCapState, recordImpression, writeCapState } from './cap';
3
+ import { promoMarkup, resolvePromo } from './render';
4
+ import { utcDayBucket } from './select';
5
+ import { trackPromoSelect, trackPromoView, } from './analytics';
6
+ import { permissiveConsent } from './consent';
7
+ const ELEMENT_NAME = 'parchar-promo';
8
+ /**
9
+ * Live options: read at connect time so a host page can re-configure the
10
+ * network (and tests can swap consent) after the element is defined —
11
+ * custom elements cannot be redefined.
12
+ */
13
+ let activeOptions = {
14
+ config: networkConfig,
15
+ analytics: { measurementId: null, consent: permissiveConsent },
16
+ };
17
+ function resolveOptions(options) {
18
+ return {
19
+ config: options.config ?? networkConfig,
20
+ analytics: {
21
+ measurementId: options.measurementId ?? null,
22
+ consent: options.consent ?? permissiveConsent,
23
+ excludedPathPrefixes: options.excludedPathPrefixes ?? [],
24
+ ...(options.analyticsOptions ?? {}),
25
+ },
26
+ };
27
+ }
28
+ function readLinkDataset(link) {
29
+ if (!link)
30
+ return null;
31
+ const version = Number(link.getAttribute('data-cp-config-version'));
32
+ if (!Number.isFinite(version))
33
+ return null;
34
+ return {
35
+ promotionId: link.getAttribute('data-cp-promotion-id') ?? '',
36
+ promotionName: link.getAttribute('data-cp-promotion-name') ?? '',
37
+ creativeName: link.getAttribute('data-cp-creative-name') ?? '',
38
+ creativeSlot: link.getAttribute('data-cp-creative-slot') ?? '',
39
+ locationId: link.getAttribute('data-cp-location-id') ?? '',
40
+ configVersion: version,
41
+ };
42
+ }
43
+ /** Renders the cap-aware pick into a container that already holds SSR markup. */
44
+ function renderInto(container, host, slotId, format, locale) {
45
+ const { config } = activeOptions;
46
+ const day = utcDayBucket();
47
+ const selection = pickPromotionWithCap({
48
+ config,
49
+ hostSiteId: host,
50
+ slotId,
51
+ timeBucket: day,
52
+ locale,
53
+ capState: readCapState(),
54
+ seedExtra: String(Date.now()),
55
+ });
56
+ const promo = selection ? resolvePromo(config, selection, host, slotId) : null;
57
+ if (!promo)
58
+ return;
59
+ container.innerHTML = promoMarkup(promo, format);
60
+ wirePromoContainer(container);
61
+ }
62
+ /** Wires click + impression tracking to a container holding promo markup. */
63
+ function wirePromoContainer(container) {
64
+ const link = container.querySelector('.cp-link');
65
+ if (!link)
66
+ return;
67
+ link.addEventListener('click', () => {
68
+ const payload = readLinkDataset(container.querySelector('.cp-link'));
69
+ if (payload)
70
+ trackPromoSelect(activeOptions.analytics, payload);
71
+ });
72
+ if (typeof IntersectionObserver === 'undefined')
73
+ return;
74
+ let viewTracked = false;
75
+ const observer = new IntersectionObserver((entries) => {
76
+ for (const entry of entries) {
77
+ if (!entry.isIntersecting || viewTracked)
78
+ continue;
79
+ if (entry.intersectionRatio < 0.5)
80
+ continue;
81
+ window.setTimeout(() => {
82
+ if (viewTracked)
83
+ return;
84
+ const payload = readLinkDataset(container.querySelector('.cp-link'));
85
+ if (!payload)
86
+ return;
87
+ if (!trackPromoView(activeOptions.analytics, payload))
88
+ return;
89
+ viewTracked = true;
90
+ writeCapState(recordImpression(readCapState(), payload.promotionId, utcDayBucket()));
91
+ }, 1000);
92
+ }
93
+ }, { threshold: [0.5] });
94
+ observer.observe(container);
95
+ }
96
+ /**
97
+ * Registers the `<parchar-promo>` element for embed hosts (static sites).
98
+ * Options also become the live configuration for in-place enhancement.
99
+ */
100
+ export function defineParcharPromo(options = {}) {
101
+ activeOptions = resolveOptions(options);
102
+ if (typeof window === 'undefined' || typeof customElements === 'undefined')
103
+ return;
104
+ if (customElements.get(ELEMENT_NAME))
105
+ return;
106
+ class ParcharPromoElement extends HTMLElement {
107
+ connectedCallback() {
108
+ const host = this.getAttribute('host') ?? '';
109
+ const slotId = this.getAttribute('slot-id') ?? '';
110
+ const format = (this.getAttribute('format') ?? 'band');
111
+ const locale = this.getAttribute('locale') ?? activeOptions.config.defaultLocale;
112
+ if (!host || !slotId)
113
+ return;
114
+ renderInto(this, host, slotId, format, locale);
115
+ }
116
+ }
117
+ customElements.define(ELEMENT_NAME, ParcharPromoElement);
118
+ }
119
+ const ENHANCED_FLAG = 'data-cp-enhanced';
120
+ /**
121
+ * In-place enhancement for server-rendered slots: the SvelteKit apps render a
122
+ * placeholder div with deterministic SSR markup; this applies the visitor's
123
+ * cap state (substituting the creative when the server pick is capped) and
124
+ * wires GA4 view/click events without disturbing Svelte's ownership of the
125
+ * surrounding tree. Idempotent; safe to call after every client navigation.
126
+ */
127
+ export function enhanceCrossPromoPlaceholders(root = document) {
128
+ if (typeof window === 'undefined')
129
+ return;
130
+ for (const placeholder of Array.from(root.querySelectorAll(`[data-parchar-promo]:not([${ENHANCED_FLAG}])`))) {
131
+ const container = placeholder;
132
+ container.setAttribute(ENHANCED_FLAG, 'true');
133
+ const host = container.getAttribute('data-cp-host') ?? '';
134
+ const slotId = container.getAttribute('data-cp-slot') ?? '';
135
+ const format = (container.getAttribute('data-cp-format') ?? 'band');
136
+ const locale = container.getAttribute('data-cp-locale') ?? activeOptions.config.defaultLocale;
137
+ if (!host || !slotId)
138
+ continue;
139
+ const { config } = activeOptions;
140
+ const day = utcDayBucket();
141
+ const current = container.querySelector('.cp-link')?.getAttribute('data-cp-promotion-id');
142
+ const selection = pickPromotionWithCap({
143
+ config,
144
+ hostSiteId: host,
145
+ slotId,
146
+ timeBucket: day,
147
+ locale,
148
+ capState: readCapState(),
149
+ seedExtra: String(Date.now()),
150
+ });
151
+ if (selection && selection.siteId !== current) {
152
+ const promo = resolvePromo(config, selection, host, slotId);
153
+ if (promo)
154
+ container.innerHTML = promoMarkup(promo, format);
155
+ }
156
+ wirePromoContainer(container);
157
+ }
158
+ }
159
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ declare global {
2
+ interface Window {
3
+ __parcharCpConsent?: () => 'granted' | 'denied' | boolean | undefined;
4
+ }
5
+ }
6
+ export {};
7
+ //# sourceMappingURL=embed-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed-entry.d.ts","sourceRoot":"","sources":["../src/embed-entry.ts"],"names":[],"mappings":"AA4BA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,kBAAkB,CAAC,EAAE,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;KACvE;CACF"}
@@ -0,0 +1,31 @@
1
+ import { defineParcharPromo } from './element';
2
+ import { gatedConsent } from './consent';
3
+ /**
4
+ * Standalone embed entry for static sites (soypolkadot.com, artmax.club).
5
+ * Configure via attributes on the script tag:
6
+ *
7
+ * <script
8
+ * src="https://cdn.jsdelivr.net/npm/@parchar/cross-promo@X/embed/iife.js"
9
+ * data-cp-ga-id="G-XXXXXXXXXX"
10
+ * data-cp-consent="gate"
11
+ * crossorigin="anonymous"
12
+ * integrity="sha384-..."
13
+ * defer></script>
14
+ *
15
+ * data-cp-consent: omit for permissive first-party behavior; "gate" defers to
16
+ * window.__parcharCpConsent() (return 'granted' | 'denied'), which hosts with
17
+ * a cookie-preference banner provide.
18
+ */
19
+ function currentScript() {
20
+ if (typeof document === 'undefined')
21
+ return null;
22
+ return document.currentScript instanceof HTMLElement ? document.currentScript : null;
23
+ }
24
+ const script = currentScript();
25
+ const gaId = script?.getAttribute('data-cp-ga-id') ?? null;
26
+ const consentMode = script?.getAttribute('data-cp-consent');
27
+ const consent = consentMode === 'gate'
28
+ ? gatedConsent(() => window.__parcharCpConsent?.())
29
+ : undefined;
30
+ defineParcharPromo({ measurementId: gaId, consent });
31
+ //# sourceMappingURL=embed-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed-entry.js","sourceRoot":"","sources":["../src/embed-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;;;;;;;;GAeG;AACH,SAAS,aAAa;IACpB,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,QAAQ,CAAC,aAAa,YAAY,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;AACvF,CAAC;AAED,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;AAC/B,MAAM,IAAI,GAAG,MAAM,EAAE,YAAY,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC;AAC3D,MAAM,WAAW,GAAG,MAAM,EAAE,YAAY,CAAC,iBAAiB,CAAC,CAAC;AAQ5D,MAAM,OAAO,GACX,WAAW,KAAK,MAAM;IACpB,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC;IACnD,CAAC,CAAC,SAAS,CAAC;AAEhB,kBAAkB,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { networkConfig } from './config';
2
+ export { parseNetworkConfig, validateNetworkConfig } from './validate';
3
+ export type { CrossPromoCreative, CrossPromoFormat, CrossPromoLocale, CrossPromoPlacement, CrossPromoSite, CrossPromoSiteRole, NetworkConfig, PromotionSelection, ResolvedPromo, } from './types';
4
+ export { configuredShares, hashString, mulberry32, pickCreativeForLocale, pickPromotion, promotionCandidates, totalWeight, utcDayBucket, } from './select';
5
+ export { isCapped, pickPromotionWithCap, readCapState, recordImpression, writeCapState, type CapState, } from './cap';
6
+ export { buildPromoUrl, promoMarkup, resolvePromo, resolveServerPromo, type ServerPromoInput, } from './render';
7
+ export { defaultAnalyticsOptions, ensureNetworkGtag, isLocalHostname, shouldSkipPromoAnalytics, trackPromoSelect, trackPromoView, type CrossPromoAnalyticsOptions, type PromoEventPayload, } from './analytics';
8
+ export { gatedConsent, permissiveConsent, type ConsentProvider, type PromoConsent } from './consent';
9
+ export { defineParcharPromo, enhanceCrossPromoPlaceholders, type DefineParcharPromoOptions, } from './element';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACvE,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,KAAK,QAAQ,GACd,MAAM,OAAO,CAAC;AACf,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,KAAK,gBAAgB,GACtB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,EACd,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AACrG,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { networkConfig } from './config';
2
+ export { parseNetworkConfig, validateNetworkConfig } from './validate';
3
+ export { configuredShares, hashString, mulberry32, pickCreativeForLocale, pickPromotion, promotionCandidates, totalWeight, utcDayBucket, } from './select';
4
+ export { isCapped, pickPromotionWithCap, readCapState, recordImpression, writeCapState, } from './cap';
5
+ export { buildPromoUrl, promoMarkup, resolvePromo, resolveServerPromo, } from './render';
6
+ export { defaultAnalyticsOptions, ensureNetworkGtag, isLocalHostname, shouldSkipPromoAnalytics, trackPromoSelect, trackPromoView, } from './analytics';
7
+ export { gatedConsent, permissiveConsent } from './consent';
8
+ export { defineParcharPromo, enhanceCrossPromoPlaceholders, } from './element';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAYvE,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,aAAa,GAEd,MAAM,OAAO,CAAC;AACf,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,kBAAkB,GAEnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,GAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAA2C,MAAM,WAAW,CAAC;AACrG,OAAO,EACL,kBAAkB,EAClB,6BAA6B,GAE9B,MAAM,WAAW,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { CrossPromoFormat, NetworkConfig, PromotionSelection, ResolvedPromo } from './types';
2
+ export interface BuildPromoUrlInput {
3
+ baseUrl: string;
4
+ hostSiteId: string;
5
+ variant: string;
6
+ slotId: string;
7
+ medium: string;
8
+ campaign: string;
9
+ }
10
+ /** Outbound URL with full UTM attribution; hosts that drop params keep the click-side event. */
11
+ export declare function buildPromoUrl(input: BuildPromoUrlInput): string;
12
+ export declare function resolvePromo(config: NetworkConfig, selection: PromotionSelection, hostSiteId: string, slotId: string): ResolvedPromo | null;
13
+ export interface ServerPromoInput {
14
+ config: NetworkConfig;
15
+ hostSiteId: string;
16
+ slotId: string;
17
+ format: CrossPromoFormat;
18
+ locale: string;
19
+ date?: Date;
20
+ }
21
+ export interface ServerPromoResult {
22
+ promo: ResolvedPromo;
23
+ markup: string;
24
+ }
25
+ /** Deterministic server-side resolve + markup for one slot (SSR / no-JS path). */
26
+ export declare function resolveServerPromo(input: ServerPromoInput): ServerPromoResult | null;
27
+ /**
28
+ * Single source of promo markup, shared by the Svelte SSR path and the custom
29
+ * element. All config-sourced strings are HTML-escaped; only this builder may
30
+ * produce promo markup.
31
+ */
32
+ export declare function promoMarkup(promo: ResolvedPromo, format: CrossPromoFormat): string;
33
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAiBlG,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAO/D;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,aAAa,GAAG,IAAI,CA2BtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,IAAI,CAYpF;AAMD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAwClF"}
package/dist/render.js ADDED
@@ -0,0 +1,112 @@
1
+ import { pickPromotion, utcDayBucket } from './select';
2
+ const NETWORK_LABELS = {
3
+ es: 'Recomendado',
4
+ en: 'Recommended',
5
+ };
6
+ function escapeHtml(value) {
7
+ return value
8
+ .replaceAll('&', '&amp;')
9
+ .replaceAll('<', '&lt;')
10
+ .replaceAll('>', '&gt;')
11
+ .replaceAll('"', '&quot;')
12
+ .replaceAll("'", '&#39;');
13
+ }
14
+ /** Outbound URL with full UTM attribution; hosts that drop params keep the click-side event. */
15
+ export function buildPromoUrl(input) {
16
+ const url = new URL(input.baseUrl);
17
+ url.searchParams.set('utm_source', input.hostSiteId);
18
+ url.searchParams.set('utm_medium', input.medium);
19
+ url.searchParams.set('utm_campaign', input.campaign);
20
+ url.searchParams.set('utm_content', `${input.variant}:${input.slotId}`);
21
+ return url.toString();
22
+ }
23
+ export function resolvePromo(config, selection, hostSiteId, slotId) {
24
+ const site = config.sites.find((entry) => entry.id === selection.siteId);
25
+ const creative = config.creatives.find((entry) => entry.id === selection.creativeId);
26
+ if (!site || !creative)
27
+ return null;
28
+ return {
29
+ siteId: site.id,
30
+ siteName: site.name,
31
+ creativeId: creative.id,
32
+ variant: creative.variant,
33
+ creativeSlot: `${hostSiteId}/${slotId}`,
34
+ locationId: hostSiteId,
35
+ configVersion: config.version,
36
+ headline: creative.headline,
37
+ blurb: creative.blurb,
38
+ cta: creative.cta,
39
+ accent: creative.accent,
40
+ locale: creative.locale,
41
+ url: buildPromoUrl({
42
+ baseUrl: site.url,
43
+ hostSiteId,
44
+ variant: creative.variant,
45
+ slotId,
46
+ medium: config.medium,
47
+ campaign: config.campaign,
48
+ }),
49
+ };
50
+ }
51
+ /** Deterministic server-side resolve + markup for one slot (SSR / no-JS path). */
52
+ export function resolveServerPromo(input) {
53
+ const selection = pickPromotion({
54
+ config: input.config,
55
+ hostSiteId: input.hostSiteId,
56
+ slotId: input.slotId,
57
+ timeBucket: utcDayBucket(input.date),
58
+ locale: input.locale,
59
+ });
60
+ if (!selection)
61
+ return null;
62
+ const promo = resolvePromo(input.config, selection, input.hostSiteId, input.slotId);
63
+ if (!promo)
64
+ return null;
65
+ return { promo, markup: promoMarkup(promo, input.format) };
66
+ }
67
+ function labelFor(locale, fallback) {
68
+ return NETWORK_LABELS[locale] ?? NETWORK_LABELS[fallback] ?? 'Recomendado';
69
+ }
70
+ /**
71
+ * Single source of promo markup, shared by the Svelte SSR path and the custom
72
+ * element. All config-sourced strings are HTML-escaped; only this builder may
73
+ * produce promo markup.
74
+ */
75
+ export function promoMarkup(promo, format) {
76
+ const label = labelFor(promo.locale, 'es');
77
+ const attrs = [
78
+ `class="cp-link"`,
79
+ `href="${escapeHtml(promo.url)}"`,
80
+ `data-cp-promotion-id="${escapeHtml(promo.siteId)}"`,
81
+ `data-cp-promotion-name="${escapeHtml(promo.siteName)}"`,
82
+ `data-cp-creative-name="${escapeHtml(promo.variant)}"`,
83
+ `data-cp-creative-slot="${escapeHtml(promo.creativeSlot)}"`,
84
+ `data-cp-location-id="${escapeHtml(promo.locationId)}"`,
85
+ `data-cp-config-version="${promo.configVersion}"`,
86
+ ].join(' ');
87
+ const accent = escapeHtml(promo.accent);
88
+ if (format === 'strip') {
89
+ return `<div class="cp cp--strip" style="--cp-accent:${accent}">
90
+ <span class="cp-label">${escapeHtml(label)}</span>
91
+ <a ${attrs} rel="noopener">${escapeHtml(promo.headline)}</a>
92
+ <span class="cp-cta" aria-hidden="true">${escapeHtml(promo.cta)} →</span>
93
+ </div>`;
94
+ }
95
+ if (format === 'card') {
96
+ return `<div class="cp cp--card" style="--cp-accent:${accent}">
97
+ <p class="cp-label">${escapeHtml(label)}</p>
98
+ <p class="cp-headline">${escapeHtml(promo.headline)}</p>
99
+ <p class="cp-blurb">${escapeHtml(promo.blurb)}</p>
100
+ <a ${attrs} rel="noopener"><span class="cp-cta">${escapeHtml(promo.cta)} →</span></a>
101
+ </div>`;
102
+ }
103
+ return `<div class="cp cp--band" style="--cp-accent:${accent}">
104
+ <div class="cp-copy">
105
+ <p class="cp-label">${escapeHtml(label)}</p>
106
+ <p class="cp-headline">${escapeHtml(promo.headline)}</p>
107
+ <p class="cp-blurb">${escapeHtml(promo.blurb)}</p>
108
+ </div>
109
+ <a ${attrs} rel="noopener"><span class="cp-cta">${escapeHtml(promo.cta)} →</span></a>
110
+ </div>`;
111
+ }
112
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { NetworkConfig, PromotionSelection } from './types';
2
+ /** FNV-1a 32-bit hash: stable across runtimes, good enough to seed a PRNG. */
3
+ export declare function hashString(value: string): number;
4
+ /** Deterministic PRNG (mulberry32) so SSR picks are reproducible per bucket. */
5
+ export declare function mulberry32(seed: number): () => number;
6
+ /** UTC day bucket so cached SSR HTML stays coherent within a day. */
7
+ export declare function utcDayBucket(date?: Date): string;
8
+ export interface PromotionCandidates {
9
+ siteIds: string[];
10
+ weights: Map<string, number>;
11
+ creatives: Map<string, string[]>;
12
+ }
13
+ /**
14
+ * Destination candidates for a render: sites with the destination role,
15
+ * excluding the host itself, renormalizing the remaining weights
16
+ * proportionally among the rest.
17
+ */
18
+ export declare function promotionCandidates(config: NetworkConfig, hostSiteId: string): PromotionCandidates;
19
+ export declare function totalWeight(candidates: PromotionCandidates): number;
20
+ export interface PickPromotionInput {
21
+ config: NetworkConfig;
22
+ hostSiteId: string;
23
+ slotId: string;
24
+ timeBucket: string;
25
+ /** Extra entropy for client-side substitutions; keep undefined for SSR. */
26
+ seedExtra?: string;
27
+ locale: string;
28
+ }
29
+ /**
30
+ * Pure weighted selection of a destination + creative for one slot render.
31
+ * Deterministic given (config.version, slotId, timeBucket, seedExtra): the
32
+ * same cached SSR HTML therefore serves the same creative within a bucket.
33
+ * The creative follows the locale with Spanish fallback.
34
+ */
35
+ export declare function pickPromotion(input: PickPromotionInput): PromotionSelection | null;
36
+ /** Locale-aware creative choice with default-locale fallback. */
37
+ export declare function pickCreativeForLocale(creativeIds: string[], config: NetworkConfig, locale: string): string | undefined;
38
+ /**
39
+ * Expected impression share of each destination across the whole network
40
+ * (before self-exclusion), for share-vs-target observability and tests.
41
+ */
42
+ export declare function configuredShares(config: NetworkConfig): Map<string, number>;
43
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEjE,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CASrD;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAE5D;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,MAAM,GACjB,mBAAmB,CAerB;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAKnE;AAgBD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,kBAAkB,GAAG,IAAI,CAelF;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EAAE,EACrB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,SAAS,CAKpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU3E"}
package/dist/select.js ADDED
@@ -0,0 +1,106 @@
1
+ /** FNV-1a 32-bit hash: stable across runtimes, good enough to seed a PRNG. */
2
+ export function hashString(value) {
3
+ let hash = 0x811c9dc5;
4
+ for (let index = 0; index < value.length; index += 1) {
5
+ hash ^= value.charCodeAt(index);
6
+ hash = Math.imul(hash, 0x01000193);
7
+ }
8
+ return hash >>> 0;
9
+ }
10
+ /** Deterministic PRNG (mulberry32) so SSR picks are reproducible per bucket. */
11
+ export function mulberry32(seed) {
12
+ let state = seed >>> 0;
13
+ return () => {
14
+ state = (state + 0x6d2b79f5) >>> 0;
15
+ let t = state;
16
+ t = Math.imul(t ^ (t >>> 15), t | 1);
17
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
18
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
19
+ };
20
+ }
21
+ /** UTC day bucket so cached SSR HTML stays coherent within a day. */
22
+ export function utcDayBucket(date = new Date()) {
23
+ return date.toISOString().slice(0, 10);
24
+ }
25
+ /**
26
+ * Destination candidates for a render: sites with the destination role,
27
+ * excluding the host itself, renormalizing the remaining weights
28
+ * proportionally among the rest.
29
+ */
30
+ export function promotionCandidates(config, hostSiteId) {
31
+ const weights = new Map();
32
+ const creatives = new Map();
33
+ const siteIds = [];
34
+ for (const site of config.sites) {
35
+ if (!site.roles.includes('destination'))
36
+ continue;
37
+ if (site.id === hostSiteId)
38
+ continue;
39
+ if (site.weight <= 0 || site.creatives.length === 0)
40
+ continue;
41
+ siteIds.push(site.id);
42
+ weights.set(site.id, site.weight);
43
+ creatives.set(site.id, site.creatives);
44
+ }
45
+ return { siteIds, weights, creatives };
46
+ }
47
+ export function totalWeight(candidates) {
48
+ return candidates.siteIds.reduce((sum, siteId) => sum + (candidates.weights.get(siteId) ?? 0), 0);
49
+ }
50
+ function weightedPick(siteIds, weights, roll) {
51
+ const total = siteIds.reduce((sum, siteId) => sum + (weights.get(siteId) ?? 0), 0);
52
+ let cursor = roll * total;
53
+ for (const siteId of siteIds) {
54
+ cursor -= weights.get(siteId) ?? 0;
55
+ if (cursor < 0)
56
+ return siteId;
57
+ }
58
+ return siteIds[siteIds.length - 1] ?? siteIds[0] ?? '';
59
+ }
60
+ /**
61
+ * Pure weighted selection of a destination + creative for one slot render.
62
+ * Deterministic given (config.version, slotId, timeBucket, seedExtra): the
63
+ * same cached SSR HTML therefore serves the same creative within a bucket.
64
+ * The creative follows the locale with Spanish fallback.
65
+ */
66
+ export function pickPromotion(input) {
67
+ const { config, hostSiteId, slotId, timeBucket, seedExtra, locale } = input;
68
+ const candidates = promotionCandidates(config, hostSiteId);
69
+ if (candidates.siteIds.length === 0)
70
+ return null;
71
+ const seedSource = seedExtra
72
+ ? `${config.version}|${slotId}|${timeBucket}|${seedExtra}`
73
+ : `${config.version}|${slotId}|${timeBucket}`;
74
+ const rng = mulberry32(hashString(seedSource));
75
+ const siteId = weightedPick(candidates.siteIds, candidates.weights, rng());
76
+ const creativeIds = candidates.creatives.get(siteId) ?? [];
77
+ const creativeId = pickCreativeForLocale(creativeIds, config, locale);
78
+ if (!creativeId)
79
+ return null;
80
+ return { siteId, creativeId };
81
+ }
82
+ /** Locale-aware creative choice with default-locale fallback. */
83
+ export function pickCreativeForLocale(creativeIds, config, locale) {
84
+ const byId = new Map(config.creatives.map((creative) => [creative.id, creative]));
85
+ const exact = creativeIds.find((id) => byId.get(id)?.locale === locale);
86
+ if (exact)
87
+ return exact;
88
+ return creativeIds.find((id) => byId.get(id)?.locale === config.defaultLocale);
89
+ }
90
+ /**
91
+ * Expected impression share of each destination across the whole network
92
+ * (before self-exclusion), for share-vs-target observability and tests.
93
+ */
94
+ export function configuredShares(config) {
95
+ const total = config.sites
96
+ .filter((site) => site.roles.includes('destination'))
97
+ .reduce((sum, site) => sum + site.weight, 0);
98
+ const shares = new Map();
99
+ for (const site of config.sites) {
100
+ if (!site.roles.includes('destination'))
101
+ continue;
102
+ shares.set(site.id, site.weight / total);
103
+ }
104
+ return shares;
105
+ }
106
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"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"}