@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,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
+ }
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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"}
@@ -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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"}
@@ -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&amp;utm_medium=cross-promo&amp;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&amp;utm_medium=cross-promo&amp;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("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.