@scalemule/news-publication 0.0.46 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,18 @@
1
+ export { NetworkCopyrightNotice } from './chunk-WAC6ATNP.js';
1
2
  export { CURATED_COMMUNITY_CARDS, VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-FMCHQI7K.js';
2
- export { CookieConsentBanner } from './chunk-2IYKWGVM.js';
3
- export { CookieSettingsView } from './chunk-72PKW2HM.js';
3
+ export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-GB36YM56.js';
4
+ export { CookieConsentBanner } from './chunk-GOWOYMSG.js';
5
+ export { CookieSettingsView } from './chunk-UL2Q3N26.js';
4
6
  export { THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, ThemeToggle, applyTheme, clearTheme, getStoredTheme, getSystemTheme, resolveEffectiveTheme, setTheme, subscribeTheme, themeInitScript } from './chunk-NQD3M7ZY.js';
5
- export { LocalNetworkBar } from './chunk-OIEO2YVG.js';
6
- export { NetworkCopyrightNotice } from './chunk-GVLJ3HAI.js';
7
- import { localPublications } from './chunk-UTBV6JJX.js';
8
- export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, decorateNetworkUrl, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from './chunk-UTBV6JJX.js';
7
+ export { LocalNetworkBar } from './chunk-2XSW7JZS.js';
8
+ import { localPublications } from './chunk-JV76ZEIA.js';
9
+ export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, decorateNetworkUrl, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from './chunk-JV76ZEIA.js';
10
+ export { SPONSORSHIP_OFFERINGS, SPONSORSHIP_POLICY, placementAvailable, safeSponsorUrl } from './chunk-66GI2IX6.js';
9
11
  export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
10
12
  export { formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-OISLCYER.js';
11
13
  import { definePublication } from './chunk-57YVL6BD.js';
12
14
  export { BROADSHEET_THEME, FEATURE_NAMES, LAYOUT_COOKIE, acceptRemoteRecord, acceptSnapshot, definePublication, featureEnabled, parseSnapshot, publicationFromSnapshot, publicationRecord, resolveLayout, snapshotFromRecord, syncPublication } from './chunk-57YVL6BD.js';
13
15
  export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
14
- export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-GB36YM56.js';
15
16
  import { jsxs, jsx } from 'react/jsx-runtime';
16
17
 
17
18
  // src/editorial.ts
@@ -31,6 +32,48 @@ var EDITORIAL_SECTION_ORDER = [
31
32
  "Schools",
32
33
  "Opinion"
33
34
  ];
35
+ var SECTION_ALIASES = {
36
+ "Business": "Local businesses",
37
+ "Government": "Civic life",
38
+ "Arts & Culture": "Community",
39
+ "Food & drink": "Food & places",
40
+ "Things to do": "Events",
41
+ "Sports": "Community",
42
+ "Education": "Schools",
43
+ "Public Safety": "Civic life",
44
+ "Transit": "Civic life",
45
+ "Environment": "Civic life",
46
+ "Recreation": "Community"
47
+ };
48
+ function resolveSection(item) {
49
+ const explicit = item.section?.trim();
50
+ if (explicit && explicit !== "Local news") {
51
+ return SECTION_ALIASES[explicit] ?? explicit;
52
+ }
53
+ const descriptors = [
54
+ ...(item.tags ?? []).map((t) => t.name.toLowerCase()),
55
+ ...(item.categories ?? []).map((c) => c.name.toLowerCase())
56
+ ];
57
+ for (const desc of descriptors) {
58
+ if (/\b(health|wellness|medical|hospital|clinic|doctor|pharmacy|veterinar|pet care|animal welfare|vaccin)\b/i.test(desc)) return "Health";
59
+ if (/\b(real estate|housing|home|homes|mortgage|tenant|tenants|apartment|apartments|property|bmr)\b/i.test(desc)) return "Real Estate";
60
+ if (/\b(civic|government|council|election|ballot|measure|transit|traffic|police|fire|firefighter|wildfire|safety|policy|supervisor)\b/i.test(desc)) return "Civic life";
61
+ if (/\b(business|retail|commerce|commercial|downtown|store|shop|merchants?|chamber of commerce)\b/i.test(desc)) return "Local businesses";
62
+ if (/\b(event|events|festival|festivals|fair|fairs|concert|concerts|music|workshop|workshops|things to do|exhibition|performance)\b/i.test(desc)) return "Events";
63
+ if (/\b(school|schools|education|student|students|teacher|teachers|campus|college|university)\b/i.test(desc)) return "Schools";
64
+ if (/\b(food|dining|restaurant|restaurants|wine|winery|brewery|bakery|cafe|coffee|cuisine|chef)\b/i.test(desc)) return "Food & places";
65
+ if (/\b(community|volunteer|volunteers|library|libraries|neighborhood|nonprofit|charity|youth|senior|seniors)\b/i.test(desc)) return "Community";
66
+ }
67
+ const text = `${item.title} ${item.excerpt ?? ""}`;
68
+ if (/\b(health|hospitals?|clinics?|medical|doctors?|pharmac(?:y|ies)|wellness|veterinar(?:y|ian)|pet care|animal welfare|vaccinat(?:ion|ions)|patients?)\b/iu.test(text)) return "Health";
69
+ if (/\b(real estate|homeownership|mortgage|housing|tenants?|apartments?|below-market rate|home sales?|condos?)\b/iu.test(text)) return "Real Estate";
70
+ if (/\b(city council|planning commission|board of supervisors|ballot|measure [a-z0-9]|election|transit|traffic|police|firefighter|wildfire|evacuation|pedestrian safety|city manager|public works)\b/iu.test(text)) return "Civic life";
71
+ if (/\b(storefront|retail|downtown business|chamber of commerce|opens? doors?|boutique|merchants?|shoppers?|commercial space)\b/iu.test(text)) return "Local businesses";
72
+ if (/\b(festival|parade|fair|concert|workshop|exhibition|showcase|celebration|weigh-off|halloween|trunk-or-treat)\b/iu.test(text)) return "Events";
73
+ if (/\b(schools?|students?|teachers?|campuses?|classrooms?|high school|elementary|school district)\b/iu.test(text)) return "Schools";
74
+ if (/\b(restaurants?|cafes?|baker(?:y|ies)|winer(?:y|ies)|brewer(?:y|ies)|dining|chefs?|menus?)\b/iu.test(text)) return "Food & places";
75
+ return "Community";
76
+ }
34
77
  function orderedSections(sections2, order = EDITORIAL_SECTION_ORDER) {
35
78
  return sections2.filter((section) => section.name !== "Local news").slice().sort((left, right) => {
36
79
  const leftRank = order.indexOf(left.name);
@@ -52,8 +95,8 @@ function sectionDefinition(name) {
52
95
  function editorialSections(items) {
53
96
  const groups = /* @__PURE__ */ new Map();
54
97
  for (const item of items) {
55
- const name = item.section?.trim() || "Local news";
56
- groups.set(name, [...groups.get(name) ?? [], item]);
98
+ const name = resolveSection(item);
99
+ groups.set(name, [...groups.get(name) ?? [], { ...item, section: name }]);
57
100
  }
58
101
  return Array.from(groups, ([name, articles]) => ({ ...sectionDefinition(name), items: articles }));
59
102
  }
@@ -175,8 +218,8 @@ function shortDeck(value, limit = 160) {
175
218
  return (space > opening.length / 2 ? opening.slice(0, space) : opening).trimEnd() + "\u2026";
176
219
  }
177
220
  function storyDate(value) {
178
- const date = new Date(value);
179
- return Number.isNaN(date.getTime()) ? null : date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "America/Los_Angeles" });
221
+ const date2 = new Date(value);
222
+ return Number.isNaN(date2.getTime()) ? null : date2.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "America/Los_Angeles" });
180
223
  }
181
224
 
182
225
  // src/seo.ts
@@ -752,4 +795,45 @@ async function publicationListeningCandidates(items, read, publicationId, public
752
795
  return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
753
796
  }
754
797
 
755
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, EDITORIAL_SECTION_ORDER, EVENTS, LOCAL_BRIEFING_POLICY_VERSION, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, composeEdition, composeLocalBriefing, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, referralCode, resolveCoupon, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
798
+ // src/measurement-consent.ts
799
+ function measurementConsent(headers, sponsorship = false, now = Date.now()) {
800
+ if (headers.get("dnt") === "1" || headers.get("sec-gpc") === "1")
801
+ return false;
802
+ const cookies = headers.get("cookie")?.split(";").map((value) => value.trim()) ?? [];
803
+ const cookie = cookies.find((value) => value.startsWith("sm_cookie_consent_v1=")) ?? cookies.find((value) => value.startsWith("napsite_cookie_consent_v1="));
804
+ if (!cookie) return false;
805
+ try {
806
+ const record = JSON.parse(
807
+ decodeURIComponent(cookie.substring(cookie.indexOf("=") + 1))
808
+ );
809
+ const date2 = Date.parse(record.timestamp);
810
+ return record.version === 1 && record.preferences?.essential === true && typeof record.preferences?.functional === "boolean" && typeof record.preferences?.advertising === "boolean" && record.preferences?.performance === true && (!sponsorship || record.preferences?.advertising === true) && Number.isFinite(date2) && date2 <= now + 3e5 && now - date2 < 365 * 864e5;
811
+ } catch {
812
+ return false;
813
+ }
814
+ }
815
+
816
+ // src/sponsorship-report.ts
817
+ var escape = (text) => String(text ?? "").replace(
818
+ /[&<>"']/g,
819
+ (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]
820
+ );
821
+ var date = (value) => new Date(value).toLocaleString("en-US", {
822
+ timeZone: "UTC",
823
+ dateStyle: "medium",
824
+ timeStyle: "short"
825
+ }) + " UTC";
826
+ function metric(value) {
827
+ if (value.status === "unavailable" || value.value === null)
828
+ return "Unavailable";
829
+ return `${escape(value.value.toLocaleString("en-US"))} <small>${escape(value.status)}</small>${value.from && value.to ? `<small>Observed period:<br>${escape(date(value.from))}<br>to ${escape(date(value.to))}</small>` : ""}`;
830
+ }
831
+ function campaignReportHtml(report) {
832
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${escape(report.campaign_name)} | Sponsorship report</title><style>
833
+ @page{size:Letter;margin:18mm}*{box-sizing:border-box}body{font:11pt/1.5 Arial,sans-serif;color:#172d37;margin:0}h1,h2{font-family:Georgia,serif;font-weight:normal}h1{font-size:30pt;line-height:1.12;margin:16pt 0}h2{font-size:19pt;margin:0 0 8pt}header{border-bottom:2pt solid #173d32;padding-bottom:16pt}.brand{font-size:10pt;letter-spacing:1pt}p{margin:8pt 0}.meta{color:#4e5d64;font-size:10pt}section{break-inside:avoid;margin:24pt 0}table{width:100%;border-collapse:collapse;margin:12pt 0}td,th{text-align:left;border-bottom:1px solid #d8dedc;padding:8pt 5pt}th{font-size:9pt}small{display:block;color:#4e5d64;font-size:8pt}footer{border-top:1px solid #d8dedc;padding-top:12pt;font-size:9pt}.notes{font-size:9pt;color:#4e5d64}
834
+ </style></head><body><header><div class="brand">BAY AREA NEWS NETWORK</div><p>Community sponsorship report</p><h1>${escape(report.campaign_name)}</h1><p>${escape(report.sponsor)}</p><p class="meta">Campaign period: ${escape(date(report.starts_at))} to ${escape(date(report.ends_at))}<br>Status: ${escape(report.status)}<br>Prepared: ${escape(date(report.generated_at))}</p></header>
835
+ ${report.placements.map((p) => `<section><h2>${escape(p.publication)}</h2><p>${escape(p.placement)} \xB7 ${escape(p.product_path)}</p><p class="meta">Community scope: ${escape(p.communities.length ? p.communities.join(", ") : "Publication-wide")}<br>Placement delivery: ${escape(p.delivery_status)}</p><table><thead><tr><th>Observed placement views</th><th>Observed sponsor link clicks</th><th>Newsletter deliveries</th></tr></thead><tbody><tr><td>${metric(p.views)}</td><td>${metric(p.clicks)}</td><td>${metric(p.newsletter_deliveries)}</td></tr></tbody></table><p class="notes">Views: ${escape(p.views.methodology)}<br>Clicks: ${escape(p.clicks.methodology)}<br>Newsletter: ${escape(p.newsletter_deliveries.methodology)}</p></section>`).join("")}
836
+ <footer><p>${escape(report.reporting_note)}</p><p>${escape(report.editorial_independence)}</p><p>Campaign reference: ${escape(report.campaign_id)}</p></footer></body></html>`;
837
+ }
838
+
839
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, EDITORIAL_SECTION_ORDER, EVENTS, LOCAL_BRIEFING_POLICY_VERSION, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, composeEdition, composeLocalBriefing, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, referralCode, resolveCoupon, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
@@ -1,2 +1,2 @@
1
- export { LocalNetworkBar } from './chunk-OIEO2YVG.js';
2
- import './chunk-UTBV6JJX.js';
1
+ export { LocalNetworkBar } from './chunk-2XSW7JZS.js';
2
+ import './chunk-JV76ZEIA.js';
@@ -1,2 +1,2 @@
1
- export { NetworkCopyrightNotice } from './chunk-GVLJ3HAI.js';
2
- import './chunk-UTBV6JJX.js';
1
+ export { NetworkCopyrightNotice } from './chunk-WAC6ATNP.js';
2
+ import './chunk-JV76ZEIA.js';
package/dist/network.js CHANGED
@@ -1 +1 @@
1
- export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications } from './chunk-UTBV6JJX.js';
1
+ export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications } from './chunk-JV76ZEIA.js';
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+
5
+ // src/sponsorships.ts
6
+ function safeSponsorUrl(value) {
7
+ if (typeof value !== "string" || value.length > 2048) return void 0;
8
+ try {
9
+ const url = new URL(value);
10
+ if (url.protocol !== "https:" || url.username || url.password)
11
+ return void 0;
12
+ return url.toString();
13
+ } catch {
14
+ return void 0;
15
+ }
16
+ }
17
+ function SponsorAcknowledgment({
18
+ creative,
19
+ label = "Community Partner",
20
+ preview = false,
21
+ acknowledgment
22
+ }) {
23
+ const logo = safeSponsorUrl(creative.logo_url);
24
+ const destination = safeSponsorUrl(creative.destination_url);
25
+ return /* @__PURE__ */ jsxRuntime.jsxs(
26
+ "aside",
27
+ {
28
+ className: "sm-sponsorship",
29
+ "aria-label": `${label}: ${creative.name}`,
30
+ "data-sponsorship": preview ? void 0 : acknowledgment?.campaign_id,
31
+ "data-placement-id": preview ? void 0 : acknowledgment?.placement_id,
32
+ "data-publication-id": preview ? void 0 : acknowledgment?.publication_id,
33
+ "data-sponsor-id": preview ? void 0 : acknowledgment?.sponsor_id,
34
+ children: [
35
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-sponsorship-copy", children: [
36
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "sm-sponsorship-label", children: [
37
+ label,
38
+ preview ? " \xB7 Illustrative preview" : ""
39
+ ] }),
40
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-sponsorship-name", children: creative.name }),
41
+ creative.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-sponsorship-description", children: creative.description }),
42
+ destination && /* @__PURE__ */ jsxRuntime.jsxs(
43
+ "a",
44
+ {
45
+ href: destination,
46
+ rel: "sponsored noopener noreferrer",
47
+ referrerPolicy: "no-referrer",
48
+ "data-analytics-skip": "true",
49
+ children: [
50
+ creative.link_label || "Learn more",
51
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sm-sponsorship-sr", children: [
52
+ " about ",
53
+ creative.name
54
+ ] })
55
+ ]
56
+ }
57
+ )
58
+ ] }),
59
+ logo && /* @__PURE__ */ jsxRuntime.jsx(
60
+ "img",
61
+ {
62
+ src: logo,
63
+ alt: `${creative.name} logo`,
64
+ loading: "lazy",
65
+ decoding: "async",
66
+ referrerPolicy: "no-referrer",
67
+ className: "sm-sponsorship-logo"
68
+ }
69
+ )
70
+ ]
71
+ }
72
+ );
73
+ }
74
+
75
+ exports.SponsorAcknowledgment = SponsorAcknowledgment;
@@ -0,0 +1,11 @@
1
+ import react__default from 'react';
2
+ import { C as Creative, P as PublicAcknowledgment } from './sponsorships-CtnBe3S8.cjs';
3
+
4
+ declare function SponsorAcknowledgment({ creative, label, preview, acknowledgment, }: {
5
+ creative: Creative;
6
+ label?: "Community Partner" | "Sponsored";
7
+ preview?: boolean;
8
+ acknowledgment?: PublicAcknowledgment;
9
+ }): react__default.JSX.Element;
10
+
11
+ export { SponsorAcknowledgment };
@@ -0,0 +1,11 @@
1
+ import react__default from 'react';
2
+ import { C as Creative, P as PublicAcknowledgment } from './sponsorships-CtnBe3S8.js';
3
+
4
+ declare function SponsorAcknowledgment({ creative, label, preview, acknowledgment, }: {
5
+ creative: Creative;
6
+ label?: "Community Partner" | "Sponsored";
7
+ preview?: boolean;
8
+ acknowledgment?: PublicAcknowledgment;
9
+ }): react__default.JSX.Element;
10
+
11
+ export { SponsorAcknowledgment };
@@ -0,0 +1,62 @@
1
+ import { safeSponsorUrl } from './chunk-66GI2IX6.js';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ function SponsorAcknowledgment({
5
+ creative,
6
+ label = "Community Partner",
7
+ preview = false,
8
+ acknowledgment
9
+ }) {
10
+ const logo = safeSponsorUrl(creative.logo_url);
11
+ const destination = safeSponsorUrl(creative.destination_url);
12
+ return /* @__PURE__ */ jsxs(
13
+ "aside",
14
+ {
15
+ className: "sm-sponsorship",
16
+ "aria-label": `${label}: ${creative.name}`,
17
+ "data-sponsorship": preview ? void 0 : acknowledgment?.campaign_id,
18
+ "data-placement-id": preview ? void 0 : acknowledgment?.placement_id,
19
+ "data-publication-id": preview ? void 0 : acknowledgment?.publication_id,
20
+ "data-sponsor-id": preview ? void 0 : acknowledgment?.sponsor_id,
21
+ children: [
22
+ /* @__PURE__ */ jsxs("div", { className: "sm-sponsorship-copy", children: [
23
+ /* @__PURE__ */ jsxs("p", { className: "sm-sponsorship-label", children: [
24
+ label,
25
+ preview ? " \xB7 Illustrative preview" : ""
26
+ ] }),
27
+ /* @__PURE__ */ jsx("p", { className: "sm-sponsorship-name", children: creative.name }),
28
+ creative.description && /* @__PURE__ */ jsx("p", { className: "sm-sponsorship-description", children: creative.description }),
29
+ destination && /* @__PURE__ */ jsxs(
30
+ "a",
31
+ {
32
+ href: destination,
33
+ rel: "sponsored noopener noreferrer",
34
+ referrerPolicy: "no-referrer",
35
+ "data-analytics-skip": "true",
36
+ children: [
37
+ creative.link_label || "Learn more",
38
+ /* @__PURE__ */ jsxs("span", { className: "sm-sponsorship-sr", children: [
39
+ " about ",
40
+ creative.name
41
+ ] })
42
+ ]
43
+ }
44
+ )
45
+ ] }),
46
+ logo && /* @__PURE__ */ jsx(
47
+ "img",
48
+ {
49
+ src: logo,
50
+ alt: `${creative.name} logo`,
51
+ loading: "lazy",
52
+ decoding: "async",
53
+ referrerPolicy: "no-referrer",
54
+ className: "sm-sponsorship-logo"
55
+ }
56
+ )
57
+ ]
58
+ }
59
+ );
60
+ }
61
+
62
+ export { SponsorAcknowledgment };
@@ -0,0 +1,137 @@
1
+ /** Community sponsorship contract. Prices are proposals until agreed in writing. */
2
+ type PlacementType = "community_partner" | "community_calendar" | "daily_briefing" | "community_guide";
3
+ type CampaignStatus = "draft" | "proposed" | "approved" | "scheduled" | "active" | "paused" | "completed" | "cancelled";
4
+ type Measurement = {
5
+ status: "measured" | "estimated" | "unavailable";
6
+ value: number | null;
7
+ methodology: string;
8
+ from?: string;
9
+ to?: string;
10
+ };
11
+ type Creative = {
12
+ name: string;
13
+ description: string;
14
+ logo_url?: string | null;
15
+ destination_url?: string | null;
16
+ link_label?: string;
17
+ };
18
+ type SponsorParty = {
19
+ id: string;
20
+ revision: number;
21
+ kind: "sponsor" | "agency";
22
+ name: string;
23
+ website?: string | null;
24
+ logo_url?: string | null;
25
+ description: string;
26
+ contact_name: string;
27
+ contact_email: string;
28
+ billing_account_ref?: string | null;
29
+ agency_ids: string[];
30
+ status: "active" | "archived";
31
+ };
32
+ type Placement = {
33
+ id: string;
34
+ revision: number;
35
+ application_id: string;
36
+ publication_id: string;
37
+ placement_type: PlacementType;
38
+ name: string;
39
+ description: string;
40
+ communities: string[];
41
+ product_path: string;
42
+ active: boolean;
43
+ availability: "coming_soon" | "available" | "unavailable";
44
+ max_sponsors: number;
45
+ price_cents: number | null;
46
+ currency: "USD";
47
+ price_interval: "month" | "fixed" | "custom";
48
+ verification_url?: string | null;
49
+ verified_at?: string | null;
50
+ reporting: string[];
51
+ editorial_restrictions: string;
52
+ };
53
+ type CampaignTarget = {
54
+ application_id: string;
55
+ publication_id: string;
56
+ placement_id: string;
57
+ communities: string[];
58
+ };
59
+ type Campaign = {
60
+ id: string;
61
+ revision: number;
62
+ name: string;
63
+ sponsor_id: string;
64
+ agency_id?: string | null;
65
+ status: CampaignStatus;
66
+ starts_at: string;
67
+ ends_at: string;
68
+ targets: CampaignTarget[];
69
+ creative: Creative;
70
+ approved_revision: number | null;
71
+ archived: boolean;
72
+ price_cents: number | null;
73
+ currency: "USD";
74
+ price_interval: "month" | "fixed" | "custom";
75
+ invoice_ref?: string | null;
76
+ contract_ref?: string | null;
77
+ billing_party: "sponsor" | "agency";
78
+ payment_status: "not_invoiced" | "invoiced" | "paid" | "overdue" | "waived";
79
+ notes: string;
80
+ };
81
+ type PublicAcknowledgment = {
82
+ campaign_id: string;
83
+ placement_id: string;
84
+ publication_id: string;
85
+ sponsor_id: string;
86
+ placement_type: PlacementType;
87
+ creative: Creative;
88
+ starts_at: string;
89
+ ends_at: string;
90
+ };
91
+ declare const SPONSORSHIP_POLICY = "Sponsors receive clearly identified recognition. Sponsorship does not influence reporting, event selection, factual information, rankings or editorial recommendations.";
92
+ declare const SPONSORSHIP_OFFERINGS: readonly [{
93
+ readonly type: "community_partner";
94
+ readonly name: "Publication Community Partner";
95
+ readonly price_cents: 35000;
96
+ readonly duration: "One calendar month; exact start and end dates in the agreement.";
97
+ readonly deliverables: "One acknowledgment on the publication's community partners page and one compact acknowledgment below the community calendar. No article placements.";
98
+ readonly acknowledgments: "Two defined web locations; recurring display during the campaign, subject to reader repetition limits.";
99
+ readonly creative: "Name, approved logo, up to 140 characters of copy and one HTTPS link.";
100
+ readonly max_sponsors: 2;
101
+ readonly product_path: "/community-partners";
102
+ }, {
103
+ readonly type: "community_calendar";
104
+ readonly name: "Community Calendar Sponsor";
105
+ readonly price_cents: 25000;
106
+ readonly duration: "One calendar month; exact start and end dates in the agreement.";
107
+ readonly deliverables: "One acknowledgment above the independently curated community events calendar.";
108
+ readonly acknowledgments: "One web location; recurring display during the campaign, subject to reader repetition limits.";
109
+ readonly creative: "Name, approved logo, up to 140 characters of copy and one HTTPS link.";
110
+ readonly max_sponsors: 1;
111
+ readonly product_path: "/this-week";
112
+ }, {
113
+ readonly type: "daily_briefing";
114
+ readonly name: "Daily Briefing Sponsor";
115
+ readonly price_cents: 30000;
116
+ readonly duration: "One calendar month after verified newsletter delivery is operational.";
117
+ readonly deliverables: "One acknowledgment per agreed briefing edition. Edition dates and count must be specified in the agreement before booking.";
118
+ readonly acknowledgments: "One per contracted edition; no minimum send count is promised before launch.";
119
+ readonly creative: "Name, optional logo, up to 140 characters of approved copy and one HTTPS link.";
120
+ readonly max_sponsors: 1;
121
+ readonly product_path: "/newsletter";
122
+ }, {
123
+ readonly type: "community_guide";
124
+ readonly name: "Community Guide Sponsor";
125
+ readonly price_cents: null;
126
+ readonly duration: "Fixed dates agreed for a named guide.";
127
+ readonly deliverables: "One acknowledgment on one named, independently edited community guide; scope and updates agreed in writing.";
128
+ readonly acknowledgments: "One web location per contracted guide.";
129
+ readonly creative: "Name, approved logo, up to 140 characters of copy and one HTTPS link.";
130
+ readonly max_sponsors: 1;
131
+ readonly product_path: "/guides";
132
+ }];
133
+ declare function safeSponsorUrl(value: unknown): string | undefined;
134
+ /** Never turn a website existence check into a claim of sellable inventory. */
135
+ declare function placementAvailable(placement: Placement, now?: number): boolean;
136
+
137
+ export { type Creative as C, type Measurement as M, type PublicAcknowledgment as P, SPONSORSHIP_OFFERINGS as S, type Campaign as a, type CampaignStatus as b, type CampaignTarget as c, type Placement as d, type PlacementType as e, SPONSORSHIP_POLICY as f, type SponsorParty as g, placementAvailable as p, safeSponsorUrl as s };
@@ -0,0 +1,137 @@
1
+ /** Community sponsorship contract. Prices are proposals until agreed in writing. */
2
+ type PlacementType = "community_partner" | "community_calendar" | "daily_briefing" | "community_guide";
3
+ type CampaignStatus = "draft" | "proposed" | "approved" | "scheduled" | "active" | "paused" | "completed" | "cancelled";
4
+ type Measurement = {
5
+ status: "measured" | "estimated" | "unavailable";
6
+ value: number | null;
7
+ methodology: string;
8
+ from?: string;
9
+ to?: string;
10
+ };
11
+ type Creative = {
12
+ name: string;
13
+ description: string;
14
+ logo_url?: string | null;
15
+ destination_url?: string | null;
16
+ link_label?: string;
17
+ };
18
+ type SponsorParty = {
19
+ id: string;
20
+ revision: number;
21
+ kind: "sponsor" | "agency";
22
+ name: string;
23
+ website?: string | null;
24
+ logo_url?: string | null;
25
+ description: string;
26
+ contact_name: string;
27
+ contact_email: string;
28
+ billing_account_ref?: string | null;
29
+ agency_ids: string[];
30
+ status: "active" | "archived";
31
+ };
32
+ type Placement = {
33
+ id: string;
34
+ revision: number;
35
+ application_id: string;
36
+ publication_id: string;
37
+ placement_type: PlacementType;
38
+ name: string;
39
+ description: string;
40
+ communities: string[];
41
+ product_path: string;
42
+ active: boolean;
43
+ availability: "coming_soon" | "available" | "unavailable";
44
+ max_sponsors: number;
45
+ price_cents: number | null;
46
+ currency: "USD";
47
+ price_interval: "month" | "fixed" | "custom";
48
+ verification_url?: string | null;
49
+ verified_at?: string | null;
50
+ reporting: string[];
51
+ editorial_restrictions: string;
52
+ };
53
+ type CampaignTarget = {
54
+ application_id: string;
55
+ publication_id: string;
56
+ placement_id: string;
57
+ communities: string[];
58
+ };
59
+ type Campaign = {
60
+ id: string;
61
+ revision: number;
62
+ name: string;
63
+ sponsor_id: string;
64
+ agency_id?: string | null;
65
+ status: CampaignStatus;
66
+ starts_at: string;
67
+ ends_at: string;
68
+ targets: CampaignTarget[];
69
+ creative: Creative;
70
+ approved_revision: number | null;
71
+ archived: boolean;
72
+ price_cents: number | null;
73
+ currency: "USD";
74
+ price_interval: "month" | "fixed" | "custom";
75
+ invoice_ref?: string | null;
76
+ contract_ref?: string | null;
77
+ billing_party: "sponsor" | "agency";
78
+ payment_status: "not_invoiced" | "invoiced" | "paid" | "overdue" | "waived";
79
+ notes: string;
80
+ };
81
+ type PublicAcknowledgment = {
82
+ campaign_id: string;
83
+ placement_id: string;
84
+ publication_id: string;
85
+ sponsor_id: string;
86
+ placement_type: PlacementType;
87
+ creative: Creative;
88
+ starts_at: string;
89
+ ends_at: string;
90
+ };
91
+ declare const SPONSORSHIP_POLICY = "Sponsors receive clearly identified recognition. Sponsorship does not influence reporting, event selection, factual information, rankings or editorial recommendations.";
92
+ declare const SPONSORSHIP_OFFERINGS: readonly [{
93
+ readonly type: "community_partner";
94
+ readonly name: "Publication Community Partner";
95
+ readonly price_cents: 35000;
96
+ readonly duration: "One calendar month; exact start and end dates in the agreement.";
97
+ readonly deliverables: "One acknowledgment on the publication's community partners page and one compact acknowledgment below the community calendar. No article placements.";
98
+ readonly acknowledgments: "Two defined web locations; recurring display during the campaign, subject to reader repetition limits.";
99
+ readonly creative: "Name, approved logo, up to 140 characters of copy and one HTTPS link.";
100
+ readonly max_sponsors: 2;
101
+ readonly product_path: "/community-partners";
102
+ }, {
103
+ readonly type: "community_calendar";
104
+ readonly name: "Community Calendar Sponsor";
105
+ readonly price_cents: 25000;
106
+ readonly duration: "One calendar month; exact start and end dates in the agreement.";
107
+ readonly deliverables: "One acknowledgment above the independently curated community events calendar.";
108
+ readonly acknowledgments: "One web location; recurring display during the campaign, subject to reader repetition limits.";
109
+ readonly creative: "Name, approved logo, up to 140 characters of copy and one HTTPS link.";
110
+ readonly max_sponsors: 1;
111
+ readonly product_path: "/this-week";
112
+ }, {
113
+ readonly type: "daily_briefing";
114
+ readonly name: "Daily Briefing Sponsor";
115
+ readonly price_cents: 30000;
116
+ readonly duration: "One calendar month after verified newsletter delivery is operational.";
117
+ readonly deliverables: "One acknowledgment per agreed briefing edition. Edition dates and count must be specified in the agreement before booking.";
118
+ readonly acknowledgments: "One per contracted edition; no minimum send count is promised before launch.";
119
+ readonly creative: "Name, optional logo, up to 140 characters of approved copy and one HTTPS link.";
120
+ readonly max_sponsors: 1;
121
+ readonly product_path: "/newsletter";
122
+ }, {
123
+ readonly type: "community_guide";
124
+ readonly name: "Community Guide Sponsor";
125
+ readonly price_cents: null;
126
+ readonly duration: "Fixed dates agreed for a named guide.";
127
+ readonly deliverables: "One acknowledgment on one named, independently edited community guide; scope and updates agreed in writing.";
128
+ readonly acknowledgments: "One web location per contracted guide.";
129
+ readonly creative: "Name, approved logo, up to 140 characters of copy and one HTTPS link.";
130
+ readonly max_sponsors: 1;
131
+ readonly product_path: "/guides";
132
+ }];
133
+ declare function safeSponsorUrl(value: unknown): string | undefined;
134
+ /** Never turn a website existence check into a claim of sellable inventory. */
135
+ declare function placementAvailable(placement: Placement, now?: number): boolean;
136
+
137
+ export { type Creative as C, type Measurement as M, type PublicAcknowledgment as P, SPONSORSHIP_OFFERINGS as S, type Campaign as a, type CampaignStatus as b, type CampaignTarget as c, type Placement as d, type PlacementType as e, SPONSORSHIP_POLICY as f, type SponsorParty as g, placementAvailable as p, safeSponsorUrl as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -96,7 +96,13 @@
96
96
  "types": "./dist/community-maps-view.d.ts",
97
97
  "import": "./dist/community-maps-view.js",
98
98
  "require": "./dist/community-maps-view.cjs"
99
- }
99
+ },
100
+ "./sponsorship-view": {
101
+ "types": "./dist/sponsorship-view.d.ts",
102
+ "import": "./dist/sponsorship-view.js",
103
+ "require": "./dist/sponsorship-view.cjs"
104
+ },
105
+ "./sponsorship.css": "./sponsorship.css"
100
106
  },
101
107
  "files": [
102
108
  "dist",
@@ -106,7 +112,8 @@
106
112
  "verified-card.css",
107
113
  "README.md",
108
114
  "LICENSE",
109
- "story-review.css"
115
+ "story-review.css",
116
+ "sponsorship.css"
110
117
  ],
111
118
  "peerDependencies": {
112
119
  "@scalemule/react": "0.0.21",
@@ -161,7 +168,7 @@
161
168
  "registry": "https://registry.npmjs.org/"
162
169
  },
163
170
  "dependencies": {
164
- "@scalemule/compliance": "^0.0.5",
171
+ "@scalemule/compliance": "0.0.7",
165
172
  "@scalemule/react": "0.0.21",
166
173
  "@scalemule/sdk": "0.0.75"
167
174
  }