@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.
- package/dist/analytics.d.ts +47 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +93 -0
- package/dist/analytics.js.map +1 -0
- package/dist/cap.d.ts +28 -0
- package/dist/cap.d.ts.map +1 -0
- package/dist/cap.js +112 -0
- package/dist/cap.js.map +1 -0
- package/dist/config/network.json +230 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -0
- package/dist/consent.d.ts +13 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/consent.js +21 -0
- package/dist/consent.js.map +1 -0
- package/dist/element.d.ts +26 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +159 -0
- package/dist/element.js.map +1 -0
- package/dist/embed-entry.d.ts +7 -0
- package/dist/embed-entry.d.ts.map +1 -0
- package/dist/embed-entry.js +31 -0
- package/dist/embed-entry.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/render.d.ts +33 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +112 -0
- package/dist/render.js.map +1 -0
- package/dist/select.d.ts +43 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +106 -0
- package/dist/select.js.map +1 -0
- package/dist/styles.css +158 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +53 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +111 -0
- package/dist/validate.js.map +1 -0
- package/embed/README.md +98 -0
- package/embed/iife.js +17 -0
- package/package.json +40 -4
- package/README.md +0 -3
package/dist/styles.css
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cross-promo slot styles. All visuals are driven by CSS custom properties so
|
|
3
|
+
* each host portal adapts the promo to its own design tokens:
|
|
4
|
+
*
|
|
5
|
+
* --cp-bg slot background (fallback: subtle translucent gray)
|
|
6
|
+
* --cp-border slot border color (fallback: subtle translucent gray)
|
|
7
|
+
* --cp-fg primary text color (fallback: inherit)
|
|
8
|
+
* --cp-muted secondary text color (fallback: inherit)
|
|
9
|
+
* --cp-radius corner radius (fallback: 12px)
|
|
10
|
+
* --cp-font heading font stack (fallback: inherit)
|
|
11
|
+
*
|
|
12
|
+
* --cp-accent comes from the creative itself (inline) and is not host-set.
|
|
13
|
+
* Heights are reserved per format so creative swaps never shift layout.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
.cp {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
width: 100%;
|
|
19
|
+
border: 1px solid var(--cp-border, rgba(127, 127, 127, 0.25));
|
|
20
|
+
border-radius: var(--cp-radius, 12px);
|
|
21
|
+
background: var(--cp-bg, rgba(127, 127, 127, 0.08));
|
|
22
|
+
color: var(--cp-fg, inherit);
|
|
23
|
+
font-family: inherit;
|
|
24
|
+
overflow-wrap: anywhere;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.cp * {
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.cp-label {
|
|
32
|
+
display: block;
|
|
33
|
+
font-size: 0.625rem;
|
|
34
|
+
letter-spacing: 0.18em;
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
color: var(--cp-muted, inherit);
|
|
37
|
+
opacity: 0.85;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.cp-headline {
|
|
41
|
+
margin: 0.25rem 0 0;
|
|
42
|
+
font-family: var(--cp-font, inherit);
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
line-height: 1.25;
|
|
45
|
+
color: var(--cp-fg, inherit);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.cp-blurb {
|
|
49
|
+
margin: 0.375rem 0 0;
|
|
50
|
+
font-size: 0.8125rem;
|
|
51
|
+
line-height: 1.45;
|
|
52
|
+
color: var(--cp-muted, inherit);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.cp-link {
|
|
56
|
+
color: var(--cp-fg, inherit);
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.cp-link:hover .cp-cta,
|
|
61
|
+
.cp-link:focus-visible .cp-cta {
|
|
62
|
+
text-decoration: underline;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cp-link:focus-visible {
|
|
66
|
+
outline: 2px solid var(--cp-accent, currentColor);
|
|
67
|
+
outline-offset: 2px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.cp-cta {
|
|
71
|
+
display: inline-block;
|
|
72
|
+
font-size: 0.8125rem;
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
color: var(--cp-accent, currentColor);
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Horizontal band: label + copy left, CTA right; stacks on narrow viewports. */
|
|
79
|
+
.cp--band {
|
|
80
|
+
display: flex;
|
|
81
|
+
min-height: 96px;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
gap: 1rem;
|
|
85
|
+
padding: 1rem 1.25rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cp--band .cp-copy {
|
|
89
|
+
min-width: 0;
|
|
90
|
+
flex: 1 1 16rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.cp--band .cp-headline {
|
|
94
|
+
font-size: 1.0625rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.cp--band .cp-cta {
|
|
98
|
+
flex-shrink: 0;
|
|
99
|
+
padding: 0.625rem 1rem;
|
|
100
|
+
border: 1px solid var(--cp-accent, currentColor);
|
|
101
|
+
border-radius: calc(var(--cp-radius, 12px) - 4px);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (max-width: 480px) {
|
|
105
|
+
.cp--band {
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
align-items: flex-start;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Native in-feed card: matches listing card rhythm. */
|
|
112
|
+
.cp--card {
|
|
113
|
+
min-height: 148px;
|
|
114
|
+
padding: 1rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.cp--card .cp-headline {
|
|
118
|
+
font-size: 1rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.cp--card .cp-cta {
|
|
122
|
+
margin-top: 0.75rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Compact pre-footer strip: one line, small footprint. */
|
|
126
|
+
.cp--strip {
|
|
127
|
+
display: flex;
|
|
128
|
+
min-height: 48px;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 0.75rem;
|
|
131
|
+
padding: 0.5rem 0.875rem;
|
|
132
|
+
font-size: 0.8125rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.cp--strip .cp-label {
|
|
136
|
+
flex-shrink: 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.cp--strip .cp-link {
|
|
140
|
+
min-width: 0;
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
text-overflow: ellipsis;
|
|
143
|
+
white-space: nowrap;
|
|
144
|
+
font-weight: 600;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.cp--strip .cp-cta {
|
|
148
|
+
margin-left: auto;
|
|
149
|
+
flex-shrink: 0;
|
|
150
|
+
font-size: 0.75rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@media (max-width: 480px) {
|
|
154
|
+
.cp--strip .cp-blurb,
|
|
155
|
+
.cp--strip .cp-cta {
|
|
156
|
+
display: none;
|
|
157
|
+
}
|
|
158
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type CrossPromoSiteRole = 'display' | 'destination';
|
|
2
|
+
export type CrossPromoFormat = 'band' | 'card' | 'strip';
|
|
3
|
+
export type CrossPromoLocale = 'es' | 'en';
|
|
4
|
+
export interface CrossPromoPlacement {
|
|
5
|
+
/** Placement key unique within the host site (becomes part of creative_slot). */
|
|
6
|
+
id: string;
|
|
7
|
+
format: CrossPromoFormat;
|
|
8
|
+
}
|
|
9
|
+
export interface CrossPromoSite {
|
|
10
|
+
/** Stable kebab-case id used as promotion_id in GA4. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Human-readable destination name used as promotion_name in GA4. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Canonical https destination URL. */
|
|
15
|
+
url: string;
|
|
16
|
+
roles: CrossPromoSiteRole[];
|
|
17
|
+
/** Relative impression weight; normalized at selection time. */
|
|
18
|
+
weight: number;
|
|
19
|
+
/** Placement inventory; required (non-empty) for display sites. */
|
|
20
|
+
placements: CrossPromoPlacement[];
|
|
21
|
+
/** Creative ids from NetworkConfig.creatives that render for this destination. */
|
|
22
|
+
creatives: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface CrossPromoCreative {
|
|
25
|
+
id: string;
|
|
26
|
+
/** Destination site id. */
|
|
27
|
+
destination: string;
|
|
28
|
+
/** Variant label used as creative_name in GA4. */
|
|
29
|
+
variant: string;
|
|
30
|
+
locale: CrossPromoLocale;
|
|
31
|
+
headline: string;
|
|
32
|
+
blurb: string;
|
|
33
|
+
cta: string;
|
|
34
|
+
/** Decorative accent color (hex). */
|
|
35
|
+
accent: string;
|
|
36
|
+
}
|
|
37
|
+
export interface NetworkConfig {
|
|
38
|
+
/** Increments on every change to sites, weights, or creatives; lands in GA4 as cp_config_version. */
|
|
39
|
+
version: number;
|
|
40
|
+
/** utm_medium applied to every outbound promo URL. */
|
|
41
|
+
medium: string;
|
|
42
|
+
/** utm_campaign applied to every outbound promo URL. */
|
|
43
|
+
campaign: string;
|
|
44
|
+
defaultLocale: CrossPromoLocale;
|
|
45
|
+
/** Max impressions of the same destination per visitor per UTC day. */
|
|
46
|
+
capPerDay: number;
|
|
47
|
+
sites: CrossPromoSite[];
|
|
48
|
+
creatives: CrossPromoCreative[];
|
|
49
|
+
}
|
|
50
|
+
export interface PromotionSelection {
|
|
51
|
+
siteId: string;
|
|
52
|
+
creativeId: string;
|
|
53
|
+
}
|
|
54
|
+
export interface ResolvedPromo {
|
|
55
|
+
siteId: string;
|
|
56
|
+
siteName: string;
|
|
57
|
+
creativeId: string;
|
|
58
|
+
variant: string;
|
|
59
|
+
creativeSlot: string;
|
|
60
|
+
locationId: string;
|
|
61
|
+
configVersion: number;
|
|
62
|
+
headline: string;
|
|
63
|
+
blurb: string;
|
|
64
|
+
cta: string;
|
|
65
|
+
accent: string;
|
|
66
|
+
locale: CrossPromoLocale;
|
|
67
|
+
url: string;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE3D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzD,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,kFAAkF;IAClF,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,qGAAqG;IACrG,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,gBAAgB,CAAC;IAChC,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACb"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { NetworkConfig } from './types';
|
|
3
|
+
export declare const networkConfigSchema: z.ZodObject<{
|
|
4
|
+
version: z.ZodNumber;
|
|
5
|
+
medium: z.ZodString;
|
|
6
|
+
campaign: z.ZodString;
|
|
7
|
+
defaultLocale: z.ZodEnum<{
|
|
8
|
+
es: "es";
|
|
9
|
+
en: "en";
|
|
10
|
+
}>;
|
|
11
|
+
capPerDay: z.ZodNumber;
|
|
12
|
+
sites: z.ZodArray<z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
name: z.ZodString;
|
|
15
|
+
url: z.ZodString;
|
|
16
|
+
roles: z.ZodArray<z.ZodEnum<{
|
|
17
|
+
display: "display";
|
|
18
|
+
destination: "destination";
|
|
19
|
+
}>>;
|
|
20
|
+
weight: z.ZodNumber;
|
|
21
|
+
placements: z.ZodArray<z.ZodObject<{
|
|
22
|
+
id: z.ZodString;
|
|
23
|
+
format: z.ZodEnum<{
|
|
24
|
+
band: "band";
|
|
25
|
+
card: "card";
|
|
26
|
+
strip: "strip";
|
|
27
|
+
}>;
|
|
28
|
+
}, z.core.$strip>>;
|
|
29
|
+
creatives: z.ZodArray<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>>;
|
|
31
|
+
creatives: z.ZodArray<z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
destination: z.ZodString;
|
|
34
|
+
variant: z.ZodString;
|
|
35
|
+
locale: z.ZodEnum<{
|
|
36
|
+
es: "es";
|
|
37
|
+
en: "en";
|
|
38
|
+
}>;
|
|
39
|
+
headline: z.ZodString;
|
|
40
|
+
blurb: z.ZodString;
|
|
41
|
+
cta: z.ZodString;
|
|
42
|
+
accent: z.ZodString;
|
|
43
|
+
}, z.core.$strip>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export type NetworkConfigInput = z.input<typeof networkConfigSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* Structural and referential validation for the network config. Returns one
|
|
48
|
+
* message per problem, naming the offending site/creative and field, so a bad
|
|
49
|
+
* config fails the build instead of shipping.
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateNetworkConfig(input: unknown): string[];
|
|
52
|
+
export declare function parseNetworkConfig(input: unknown): NetworkConfig;
|
|
53
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAyC7C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQ9B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAqE9D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAMhE"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const crossPromoSiteRole = z.enum(['display', 'destination']);
|
|
3
|
+
const crossPromoFormat = z.enum(['band', 'card', 'strip']);
|
|
4
|
+
const crossPromoLocale = z.enum(['es', 'en']);
|
|
5
|
+
const httpsUrl = z.string().url().refine((value) => value.startsWith('https://'), { message: 'must be an https URL' });
|
|
6
|
+
const placement = z.object({
|
|
7
|
+
id: z.string().min(1),
|
|
8
|
+
format: crossPromoFormat,
|
|
9
|
+
});
|
|
10
|
+
const site = z.object({
|
|
11
|
+
id: z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'site id must be kebab-case'),
|
|
14
|
+
name: z.string().min(1),
|
|
15
|
+
url: httpsUrl,
|
|
16
|
+
roles: z.array(crossPromoSiteRole).min(1),
|
|
17
|
+
weight: z.number().positive(),
|
|
18
|
+
placements: z.array(placement),
|
|
19
|
+
creatives: z.array(z.string()),
|
|
20
|
+
});
|
|
21
|
+
const creative = z.object({
|
|
22
|
+
id: z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'creative id must be kebab-case'),
|
|
25
|
+
destination: z.string().min(1),
|
|
26
|
+
variant: z.string().min(1),
|
|
27
|
+
locale: crossPromoLocale,
|
|
28
|
+
headline: z.string().min(1).max(80),
|
|
29
|
+
blurb: z.string().min(1).max(160),
|
|
30
|
+
cta: z.string().min(1).max(40),
|
|
31
|
+
accent: z.string().regex(/^#[0-9a-fA-F]{6}$/, 'accent must be a hex color'),
|
|
32
|
+
});
|
|
33
|
+
export const networkConfigSchema = z.object({
|
|
34
|
+
version: z.number().int().positive(),
|
|
35
|
+
medium: z.string().min(1),
|
|
36
|
+
campaign: z.string().min(1),
|
|
37
|
+
defaultLocale: crossPromoLocale,
|
|
38
|
+
capPerDay: z.number().int().positive(),
|
|
39
|
+
sites: z.array(site).min(1),
|
|
40
|
+
creatives: z.array(creative).min(1),
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* Structural and referential validation for the network config. Returns one
|
|
44
|
+
* message per problem, naming the offending site/creative and field, so a bad
|
|
45
|
+
* config fails the build instead of shipping.
|
|
46
|
+
*/
|
|
47
|
+
export function validateNetworkConfig(input) {
|
|
48
|
+
const parsed = networkConfigSchema.safeParse(input);
|
|
49
|
+
if (!parsed.success) {
|
|
50
|
+
return parsed.error.issues.map((issue) => {
|
|
51
|
+
const where = issue.path.length > 0 ? ` at ${issue.path.join('.')}` : '';
|
|
52
|
+
return `invalid network config${where}: ${issue.message}`;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const config = parsed.data;
|
|
56
|
+
const errors = [];
|
|
57
|
+
const sitesById = new Map(config.sites.map((entry) => [entry.id, entry]));
|
|
58
|
+
for (const entry of config.sites) {
|
|
59
|
+
if (sitesById.get(entry.id) !== entry) {
|
|
60
|
+
errors.push(`site ${entry.id}: duplicate site id`);
|
|
61
|
+
}
|
|
62
|
+
if (entry.roles.includes('display') && entry.placements.length === 0) {
|
|
63
|
+
errors.push(`site ${entry.id}: display site without placements`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const creativesById = new Map();
|
|
67
|
+
for (const creativeEntry of config.creatives) {
|
|
68
|
+
if (creativesById.has(creativeEntry.id)) {
|
|
69
|
+
errors.push(`creative ${creativeEntry.id}: duplicate creative id`);
|
|
70
|
+
}
|
|
71
|
+
creativesById.set(creativeEntry.id, creativeEntry);
|
|
72
|
+
}
|
|
73
|
+
for (const creativeEntry of config.creatives) {
|
|
74
|
+
const destination = sitesById.get(creativeEntry.destination);
|
|
75
|
+
if (!destination) {
|
|
76
|
+
errors.push(`creative ${creativeEntry.id}: references unknown destination ${creativeEntry.destination}`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!destination.roles.includes('destination')) {
|
|
80
|
+
errors.push(`creative ${creativeEntry.id}: destination ${creativeEntry.destination} has no destination role`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const entry of config.sites) {
|
|
84
|
+
for (const creativeId of entry.creatives) {
|
|
85
|
+
const creativeEntry = creativesById.get(creativeId);
|
|
86
|
+
if (!creativeEntry) {
|
|
87
|
+
errors.push(`site ${entry.id}: references unknown creative ${creativeId}`);
|
|
88
|
+
}
|
|
89
|
+
else if (creativeEntry.destination !== entry.id) {
|
|
90
|
+
errors.push(`site ${entry.id}: creative ${creativeId} belongs to ${creativeEntry.destination}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Every destination must be promotable in the default locale at minimum.
|
|
94
|
+
const hasDefaultLocaleCreative = entry.creatives.some((creativeId) => {
|
|
95
|
+
const creativeEntry = creativesById.get(creativeId);
|
|
96
|
+
return creativeEntry?.locale === config.defaultLocale;
|
|
97
|
+
});
|
|
98
|
+
if (entry.roles.includes('destination') && !hasDefaultLocaleCreative) {
|
|
99
|
+
errors.push(`site ${entry.id}: no creative in default locale ${config.defaultLocale}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return errors;
|
|
103
|
+
}
|
|
104
|
+
export function parseNetworkConfig(input) {
|
|
105
|
+
const errors = validateNetworkConfig(input);
|
|
106
|
+
if (errors.length > 0) {
|
|
107
|
+
throw new Error(`invalid network config:\n- ${errors.join('\n- ')}`);
|
|
108
|
+
}
|
|
109
|
+
return input;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/embed/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Cross-promo embed for static sites
|
|
2
|
+
|
|
3
|
+
The `<parchar-promo>` element renders weighted cross-promotion slots for the
|
|
4
|
+
Parchar network on any static site. The bundle is self-contained (5 KB
|
|
5
|
+
gzipped), requires no build step, and makes no network requests other than
|
|
6
|
+
Google Analytics measurement.
|
|
7
|
+
|
|
8
|
+
## Snippet
|
|
9
|
+
|
|
10
|
+
Pick the pinned version from the size manifest (`embed/iife.js.size.json`)
|
|
11
|
+
and paste one placeholder per slot plus the loader once per page, right
|
|
12
|
+
before `</body>`:
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<!-- soypolkadot.com: homepage band -->
|
|
16
|
+
<parchar-promo host="soypolkadot-com" slot-id="home-band" format="band" locale="es">
|
|
17
|
+
<!-- Static fallback (no-JS / blocked script): keep exactly one link -->
|
|
18
|
+
<a href="https://becas.parchar.ai/?utm_source=soypolkadot-com&utm_medium=cross-promo&utm_campaign=parchar-network">
|
|
19
|
+
Becas verificadas, sin rumores — Explorar becas
|
|
20
|
+
</a>
|
|
21
|
+
</parchar-promo>
|
|
22
|
+
|
|
23
|
+
<!-- soypolkadot.com: end of guide -->
|
|
24
|
+
<parchar-promo host="soypolkadot-com" slot-id="end-of-guide" format="card" locale="es">
|
|
25
|
+
<a href="https://parchar.ai/?utm_source=soypolkadot-com&utm_medium=cross-promo&utm_campaign=parchar-network">
|
|
26
|
+
Eventos culturales verificados — Ver planes
|
|
27
|
+
</a>
|
|
28
|
+
</parchar-promo>
|
|
29
|
+
|
|
30
|
+
<script
|
|
31
|
+
src="https://cdn.jsdelivr.net/npm/@parchar/cross-promo@<VERSION>/embed/iife.js"
|
|
32
|
+
data-cp-ga-id="G-XXXXXXXXXX"
|
|
33
|
+
integrity="sha384-<HASH-FROM-size.json>"
|
|
34
|
+
crossorigin="anonymous"
|
|
35
|
+
defer
|
|
36
|
+
></script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For artmax.club use `host="artmax-club"` and the slots `home-band` (format
|
|
40
|
+
`band`) and `end-of-page` (format `card`), with `locale="en"` if the page is
|
|
41
|
+
in English.
|
|
42
|
+
|
|
43
|
+
## Attributes
|
|
44
|
+
|
|
45
|
+
| Attribute | Purpose |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `host` | Host site id from `packages/cross-promo/src/config/network.json` — drives self-exclusion and `location_id` in analytics. |
|
|
48
|
+
| `slot-id` | Placement id declared for that host in the network config (becomes `creative_slot`). |
|
|
49
|
+
| `format` | `band`, `card`, or `strip`. |
|
|
50
|
+
| `locale` | Creative language: `es` or `en` (falls back to `es`). |
|
|
51
|
+
|
|
52
|
+
## Loader attributes
|
|
53
|
+
|
|
54
|
+
| Attribute | Purpose |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `data-cp-ga-id` | Network GA4 measurement id. Omit to render promos without measurement. |
|
|
57
|
+
| `data-cp-consent` | `gate` to defer to the host's consent state via `window.__parcharCpConsent()` (return `'granted'`, `'denied'`, `true`, or `false`). Omit for permissive first-party behavior. |
|
|
58
|
+
|
|
59
|
+
soypolkadot.com (cookie banner with opt-in analytics) should use
|
|
60
|
+
`data-cp-consent="gate"` and expose:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<script>
|
|
64
|
+
window.__parcharCpConsent = function () {
|
|
65
|
+
// Return the host cookie-preference state for analytics.
|
|
66
|
+
return document.cookie.includes('analytics=granted') ? 'granted' : 'denied';
|
|
67
|
+
};
|
|
68
|
+
</script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
When consent is denied (or unreadable) promos still render and rotate but
|
|
72
|
+
send no GA requests.
|
|
73
|
+
|
|
74
|
+
## Theming
|
|
75
|
+
|
|
76
|
+
Slots adapt to the host's design tokens through CSS custom properties; set
|
|
77
|
+
them on the element or any ancestor:
|
|
78
|
+
|
|
79
|
+
```css
|
|
80
|
+
parchar-promo {
|
|
81
|
+
--cp-bg: rgba(127, 127, 127, 0.06);
|
|
82
|
+
--cp-border: rgba(127, 127, 127, 0.22);
|
|
83
|
+
--cp-fg: #16161d;
|
|
84
|
+
--cp-muted: #5b5b66;
|
|
85
|
+
--cp-radius: 14px;
|
|
86
|
+
--cp-font: "Playfair Display", serif;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Operations
|
|
91
|
+
|
|
92
|
+
- Weights, destinations, and creatives live in
|
|
93
|
+
`packages/cross-promo/src/config/network.json`; re-run
|
|
94
|
+
`pnpm --filter @parchar/cross-promo build:embed`, publish a new package
|
|
95
|
+
version, and bump the pinned URL + integrity hash on each host.
|
|
96
|
+
- The build fails if the bundle exceeds 20 KB gzipped.
|
|
97
|
+
- `embed/iife.js.size.json` records byte size, gzipped size, and the SRI
|
|
98
|
+
hash for the current build.
|
package/embed/iife.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";(()=>{var B={version:1,medium:"cross-promo",campaign:"parchar-network",defaultLocale:"es",capPerDay:3,sites:[{id:"becas-parchar-ai",name:"becas.parchar.ai",url:"https://becas.parchar.ai/",roles:["display","destination"],weight:35,placements:[{id:"home-band",format:"band"},{id:"explorar-in-feed",format:"card"},{id:"beca-detail-mid",format:"band"},{id:"pre-footer",format:"strip"}],creatives:["becas-evergreen-es","becas-evergreen-en"]},{id:"parchar-ai",name:"parchar.ai",url:"https://parchar.ai/",roles:["display","destination"],weight:35,placements:[{id:"home-band",format:"band"},{id:"explorar-in-feed",format:"card"},{id:"event-detail-mid",format:"band"},{id:"pre-footer",format:"strip"}],creatives:["eventos-evergreen-es","eventos-evergreen-en"]},{id:"medium-horacio",name:"Medium @horacioreyesrios",url:"https://medium.com/@horacioreyesrios",roles:["destination"],weight:10,placements:[],creatives:["medium-evergreen-es","medium-evergreen-en"]},{id:"soypolkadot-com",name:"soypolkadot.com",url:"https://soypolkadot.com/",roles:["display","destination"],weight:5,placements:[{id:"home-band",format:"band"},{id:"end-of-guide",format:"card"}],creatives:["polkadot-evergreen-es","polkadot-evergreen-en"]},{id:"artmax-club",name:"artmax.club",url:"https://artmax.club/",roles:["display","destination"],weight:5,placements:[{id:"home-band",format:"band"},{id:"end-of-page",format:"card"}],creatives:["artmax-evergreen-es","artmax-evergreen-en"]},{id:"ganima-co",name:"ganima.co",url:"https://ganima.co/",roles:["destination"],weight:5,placements:[],creatives:["ganima-evergreen-es","ganima-evergreen-en"]},{id:"instagram-ganima-bug",name:"Instagram @ganima_bug",url:"https://www.instagram.com/ganima_bug/",roles:["destination"],weight:5,placements:[],creatives:["instagram-evergreen-es","instagram-evergreen-en"]}],creatives:[{id:"becas-evergreen-es",destination:"becas-parchar-ai",variant:"becas-evergreen-es",locale:"es",headline:"Becas verificadas, sin rumores",blurb:"Oportunidades con fuente oficial, fechas de cierre y evidencia en cada ficha.",cta:"Explorar becas",accent:"#e63a7a"},{id:"becas-evergreen-en",destination:"becas-parchar-ai",variant:"becas-evergreen-en",locale:"en",headline:"Verified scholarships, no rumors",blurb:"Opportunities with official sources, closing dates and evidence on every listing.",cta:"Browse scholarships",accent:"#e63a7a"},{id:"eventos-evergreen-es",destination:"parchar-ai",variant:"eventos-evergreen-es",locale:"es",headline:"\xBFY el parche de este fin de semana?",blurb:"Conciertos, teatro y expos verificados en tu ciudad, agregados con IA.",cta:"Ver planes",accent:"#f6c24a"},{id:"eventos-evergreen-en",destination:"parchar-ai",variant:"eventos-evergreen-en",locale:"en",headline:"Weekend plans, already verified",blurb:"Concerts, theater and exhibitions in your city, aggregated with AI.",cta:"Browse events",accent:"#f6c24a"},{id:"medium-evergreen-es",destination:"medium-horacio",variant:"medium-evergreen-es",locale:"es",headline:"C\xF3mo se construye Parchar",blurb:"Ensayos del fundador sobre datos abiertos, IA y descubrimiento cultural.",cta:"Leer en Medium",accent:"#10b981"},{id:"medium-evergreen-en",destination:"medium-horacio",variant:"medium-evergreen-en",locale:"en",headline:"How Parchar gets built",blurb:"Founder essays on open data, AI and cultural discovery.",cta:"Read on Medium",accent:"#10b981"},{id:"polkadot-evergreen-es",destination:"soypolkadot-com",variant:"polkadot-evergreen-es",locale:"es",headline:"Polkadot, en buen espa\xF1ol",blurb:"Gu\xEDas revisadas con criterios editoriales abiertos, sin humo.",cta:"Leer las gu\xEDas",accent:"#e6007a"},{id:"polkadot-evergreen-en",destination:"soypolkadot-com",variant:"polkadot-evergreen-en",locale:"en",headline:"Polkadot, carefully explained",blurb:"Reviewed guides with open editorial criteria and no hype.",cta:"Read the guides",accent:"#e6007a"},{id:"artmax-evergreen-es",destination:"artmax-club",variant:"artmax-evergreen-es",locale:"es",headline:"Arte con mensaje",blurb:"Colecciones NFT que amplifican voces sociales del mundo real.",cta:"Ver colecciones",accent:"#8b5cf6"},{id:"artmax-evergreen-en",destination:"artmax-club",variant:"artmax-evergreen-en",locale:"en",headline:"Art with a message",blurb:"NFT collections that amplify real-world social voices.",cta:"View collections",accent:"#8b5cf6"},{id:"ganima-evergreen-es",destination:"ganima-co",variant:"ganima-evergreen-es",locale:"es",headline:"Cuadro a cuadro",blurb:"Estudio bogotano de animaci\xF3n 2D y stop-motion en desarrollo.",cta:"Conocer GANIMA",accent:"#f97316"},{id:"ganima-evergreen-en",destination:"ganima-co",variant:"ganima-evergreen-en",locale:"en",headline:"Frame by frame",blurb:"A Bogot\xE1-based 2D and stop-motion animation studio in development.",cta:"Meet GANIMA",accent:"#f97316"},{id:"instagram-evergreen-es",destination:"instagram-ganima-bug",variant:"instagram-evergreen-es",locale:"es",headline:"El proceso, sin filtro",blurb:"El detr\xE1s de c\xE1maras del estudio GANIMA, directo al feed.",cta:"Seguir a @ganima_bug",accent:"#ec4899"},{id:"instagram-evergreen-en",destination:"instagram-ganima-bug",variant:"instagram-evergreen-en",locale:"en",headline:"The process, unfiltered",blurb:"GANIMA studio's behind-the-scenes, straight to the feed.",cta:"Follow @ganima_bug",accent:"#ec4899"}]};var k=B;function I(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return t>>>0}function S(e){let t=e>>>0;return()=>{t=t+1831565813>>>0;let n=t;return n=Math.imul(n^n>>>15,n|1),n^=n+Math.imul(n^n>>>7,n|61),((n^n>>>14)>>>0)/4294967296}}function P(e=new Date){return e.toISOString().slice(0,10)}function x(e,t){let n=new Map,a=new Map,r=[];for(let o of e.sites)o.roles.includes("destination")&&o.id!==t&&(o.weight<=0||o.creatives.length===0||(r.push(o.id),n.set(o.id,o.weight),a.set(o.id,o.creatives)));return{siteIds:r,weights:n,creatives:a}}function re(e,t,n){var o,i,c;let a=e.reduce((s,u)=>{var l;return s+((l=t.get(u))!=null?l:0)},0),r=n*a;for(let s of e)if(r-=(o=t.get(s))!=null?o:0,r<0)return s;return(c=(i=e[e.length-1])!=null?i:e[0])!=null?c:""}function A(e){var b;let{config:t,hostSiteId:n,slotId:a,timeBucket:r,seedExtra:o,locale:i}=e,c=x(t,n);if(c.siteIds.length===0)return null;let s=o?`${t.version}|${a}|${r}|${o}`:`${t.version}|${a}|${r}`,u=S(I(s)),l=re(c.siteIds,c.weights,u()),g=(b=c.creatives.get(l))!=null?b:[],v=w(g,t,i);return v?{siteId:l,creativeId:v}:null}function w(e,t,n){let a=new Map(t.creatives.map(o=>[o.id,o])),r=e.find(o=>{var i;return((i=a.get(o))==null?void 0:i.locale)===n});return r||e.find(o=>{var i;return((i=a.get(o))==null?void 0:i.locale)===t.defaultLocale})}var F="parchar-cross-promo-cap-v1";function H(){try{let e=globalThis.localStorage;if(!e)return null;let t="__parchar_cp_probe__";return e.setItem(t,t),e.removeItem(t),e}catch{return null}}function N(){let e=H();if(!e)return{};try{let t=e.getItem(F);if(!t)return{};let n=JSON.parse(t);return typeof n!="object"||n===null?{}:n}catch{return{}}}function V(e){let t=H();if(t)try{t.setItem(F,JSON.stringify(e))}catch{}}function T(e,t,n,a){let r=e[t];return(r==null?void 0:r.day)===n&&r.count>=a}function W(e,t,n,a=Date.now()){let r=e[t],o={...e};return(r==null?void 0:r.day)===n?o[t]={day:n,count:r.count+1,lastShown:a}:o[t]={day:n,count:1,lastShown:a},o}function oe(e,t,n){var r,o;return(o=(r=[...t].sort((i,c)=>{var l,g;let s=((l=e[i])==null?void 0:l.day)===n?e[i].lastShown:0,u=((g=e[c])==null?void 0:g.day)===n?e[c].lastShown:0;return s-u})[0])!=null?r:t[0])!=null?o:""}function G(e){var L,_,M,O,R;let{config:t,hostSiteId:n,slotId:a,timeBucket:r,locale:o,capState:i,seedExtra:c}=e,s=x(t,n);if(s.siteIds.length===0)return null;let u=A({config:t,hostSiteId:n,slotId:a,timeBucket:r,locale:o});if(u&&!T(i,u.siteId,r,t.capPerDay))return u;let l=s.siteIds.filter(m=>!T(i,m,r,t.capPerDay));if(l.length===0){let m=oe(i,s.siteIds,r),p=w((L=s.creatives.get(m))!=null?L:[],t,o);return p?{siteId:m,creativeId:p}:null}let g=S(I(`${t.version}|${a}|${r}|cap|${c!=null?c:""}`)),v=new Map(l.map(m=>{var p;return[m,(p=s.weights.get(m))!=null?p:0]})),b=l.reduce((m,p)=>{var D;return m+((D=v.get(p))!=null?D:0)},0),$=g()*b,C=(M=(_=l[l.length-1])!=null?_:l[0])!=null?M:"";for(let m of l)if($-=(O=v.get(m))!=null?O:0,$<0){C=m;break}let E=w((R=s.creatives.get(C))!=null?R:[],t,o);return E?{siteId:C,creativeId:E}:null}var q={es:"Recomendado",en:"Recommended"};function d(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function ae(e){let t=new URL(e.baseUrl);return t.searchParams.set("utm_source",e.hostSiteId),t.searchParams.set("utm_medium",e.medium),t.searchParams.set("utm_campaign",e.campaign),t.searchParams.set("utm_content",`${e.variant}:${e.slotId}`),t.toString()}function U(e,t,n,a){let r=e.sites.find(i=>i.id===t.siteId),o=e.creatives.find(i=>i.id===t.creativeId);return!r||!o?null:{siteId:r.id,siteName:r.name,creativeId:o.id,variant:o.variant,creativeSlot:`${n}/${a}`,locationId:n,configVersion:e.version,headline:o.headline,blurb:o.blurb,cta:o.cta,accent:o.accent,locale:o.locale,url:ae({baseUrl:r.url,hostSiteId:n,variant:o.variant,slotId:a,medium:e.medium,campaign:e.campaign})}}function ie(e,t){var n,a;return(a=(n=q[e])!=null?n:q[t])!=null?a:"Recomendado"}function j(e,t){let n=ie(e.locale,"es"),a=['class="cp-link"',`href="${d(e.url)}"`,`data-cp-promotion-id="${d(e.siteId)}"`,`data-cp-promotion-name="${d(e.siteName)}"`,`data-cp-creative-name="${d(e.variant)}"`,`data-cp-creative-slot="${d(e.creativeSlot)}"`,`data-cp-location-id="${d(e.locationId)}"`,`data-cp-config-version="${e.configVersion}"`].join(" "),r=d(e.accent);return t==="strip"?`<div class="cp cp--strip" style="--cp-accent:${r}">
|
|
2
|
+
<span class="cp-label">${d(n)}</span>
|
|
3
|
+
<a ${a} rel="noopener">${d(e.headline)}</a>
|
|
4
|
+
<span class="cp-cta" aria-hidden="true">${d(e.cta)} \u2192</span>
|
|
5
|
+
</div>`:t==="card"?`<div class="cp cp--card" style="--cp-accent:${r}">
|
|
6
|
+
<p class="cp-label">${d(n)}</p>
|
|
7
|
+
<p class="cp-headline">${d(e.headline)}</p>
|
|
8
|
+
<p class="cp-blurb">${d(e.blurb)}</p>
|
|
9
|
+
<a ${a} rel="noopener"><span class="cp-cta">${d(e.cta)} \u2192</span></a>
|
|
10
|
+
</div>`:`<div class="cp cp--band" style="--cp-accent:${r}">
|
|
11
|
+
<div class="cp-copy">
|
|
12
|
+
<p class="cp-label">${d(n)}</p>
|
|
13
|
+
<p class="cp-headline">${d(e.headline)}</p>
|
|
14
|
+
<p class="cp-blurb">${d(e.blurb)}</p>
|
|
15
|
+
</div>
|
|
16
|
+
<a ${a} rel="noopener"><span class="cp-cta">${d(e.cta)} \u2192</span></a>
|
|
17
|
+
</div>`}var y=()=>"granted";function J(e){return()=>{try{let t=e();return t===!0||t==="granted"?"granted":"denied"}catch{return"denied"}}}var K="parchar-cross-promo";function se(e){return e.getLocation?e.getLocation():typeof window!="undefined"&&window.location?{hostname:window.location.hostname,pathname:window.location.pathname}:{hostname:"",pathname:"/"}}function ce(e){return typeof document=="undefined"?!0:!!document.querySelector(`script[src*="${e}"]`)}function le(e){var t,n;if(typeof window=="undefined"||!e)return!1;if(window.dataLayer=(t=window.dataLayer)!=null?t:[],window.gtag=(n=window.gtag)!=null?n:((...a)=>{var r;(r=window.dataLayer)==null||r.push(a)}),!ce(e)){let a=document.createElement("script");a.async=!0,a.src=`https://www.googletagmanager.com/gtag/js?id=${e}`,document.head.appendChild(a)}return window.gtag("config",e,{send_page_view:!1,groups:K}),!0}function de(e){return e==="localhost"||e==="127.0.0.1"||e==="[::1]"}function ue(e,t,n){var a;return de(t)?!0:((a=e.excludedPathPrefixes)!=null?a:[]).some(r=>n===r||n.startsWith(`${r}/`))}function me(e){return{promotion_id:e.promotionId,promotion_name:e.promotionName,creative_name:e.creativeName,creative_slot:e.creativeSlot,location_id:e.locationId,cp_config_version:e.configVersion,send_to:K}}function Y(e,t,n){var r,o;if(typeof window=="undefined"||!e.measurementId||((r=e.consent)==null?void 0:r.call(e))==="denied")return!1;let a=se(e);return ue(e,a.hostname,a.pathname)?!1:(le(e.measurementId),(o=window.gtag)==null||o.call(window,"event",t,me(n)),!0)}function z(e,t){return Y(e,"view_promotion",t)}function Q(e,t){return Y(e,"select_promotion",t)}var X="parchar-promo",h={config:k,analytics:{measurementId:null,consent:y}};function pe(e){var t,n,a,r,o;return{config:(t=e.config)!=null?t:k,analytics:{measurementId:(n=e.measurementId)!=null?n:null,consent:(a=e.consent)!=null?a:y,excludedPathPrefixes:(r=e.excludedPathPrefixes)!=null?r:[],...(o=e.analyticsOptions)!=null?o:{}}}}function Z(e){var n,a,r,o,i;if(!e)return null;let t=Number(e.getAttribute("data-cp-config-version"));return Number.isFinite(t)?{promotionId:(n=e.getAttribute("data-cp-promotion-id"))!=null?n:"",promotionName:(a=e.getAttribute("data-cp-promotion-name"))!=null?a:"",creativeName:(r=e.getAttribute("data-cp-creative-name"))!=null?r:"",creativeSlot:(o=e.getAttribute("data-cp-creative-slot"))!=null?o:"",locationId:(i=e.getAttribute("data-cp-location-id"))!=null?i:"",configVersion:t}:null}function ge(e,t,n,a,r){let{config:o}=h,i=P(),c=G({config:o,hostSiteId:t,slotId:n,timeBucket:i,locale:r,capState:N(),seedExtra:String(Date.now())}),s=c?U(o,c,t,n):null;s&&(e.innerHTML=j(s,a),fe(e))}function fe(e){let t=e.querySelector(".cp-link");if(!t||(t.addEventListener("click",()=>{let r=Z(e.querySelector(".cp-link"));r&&Q(h.analytics,r)}),typeof IntersectionObserver=="undefined"))return;let n=!1;new IntersectionObserver(r=>{for(let o of r)!o.isIntersecting||n||o.intersectionRatio<.5||window.setTimeout(()=>{if(n)return;let i=Z(e.querySelector(".cp-link"));i&&z(h.analytics,i)&&(n=!0,V(W(N(),i.promotionId,P())))},1e3)},{threshold:[.5]}).observe(e)}function ee(e={}){if(h=pe(e),typeof window=="undefined"||typeof customElements=="undefined"||customElements.get(X))return;class t extends HTMLElement{connectedCallback(){var c,s,u,l;let a=(c=this.getAttribute("host"))!=null?c:"",r=(s=this.getAttribute("slot-id"))!=null?s:"",o=(u=this.getAttribute("format"))!=null?u:"band",i=(l=this.getAttribute("locale"))!=null?l:h.config.defaultLocale;!a||!r||ge(this,a,r,o,i)}}customElements.define(X,t)}function ve(){return typeof document=="undefined"?null:document.currentScript instanceof HTMLElement?document.currentScript:null}var f=ve(),te,he=(te=f==null?void 0:f.getAttribute("data-cp-ga-id"))!=null?te:null,be=f==null?void 0:f.getAttribute("data-cp-consent"),Pe=be==="gate"?J(()=>{var e;return(e=window.__parcharCpConsent)==null?void 0:e.call(window)}):void 0;ee({measurementId:he,consent:Pe});})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parchar/cross-promo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./styles.css": "./dist/styles.css",
|
|
13
|
+
"./embed/iife.js": "./embed/iife.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && cp src/styles.css dist/styles.css",
|
|
17
|
+
"build:embed": "node scripts/build-embed.mjs",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"lint": "tsc --noEmit",
|
|
20
|
+
"clean": "rm -rf dist embed/iife.js"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"zod": "^4.6.7"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"esbuild": "^0.25.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"embed/README.md",
|
|
31
|
+
"embed/iife.js"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"license": "UNLICENSED",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://gitlab.com/parchar.ai/parchar.ai.git",
|
|
40
|
+
"directory": "packages/cross-promo"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/README.md
DELETED