@scalemule/news-publication 0.0.7

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/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/cookie-consent.css +419 -0
  4. package/dist/chunk-C7JTEHTH.js +158 -0
  5. package/dist/chunk-GB36YM56.js +72 -0
  6. package/dist/chunk-PYCU2T5O.js +93 -0
  7. package/dist/chunk-R33MJWND.js +261 -0
  8. package/dist/chunk-TJ7RL3PV.js +252 -0
  9. package/dist/chunk-YQXS2KQI.js +112 -0
  10. package/dist/cookie-consent-banner.cjs +210 -0
  11. package/dist/cookie-consent-banner.d.cts +19 -0
  12. package/dist/cookie-consent-banner.d.ts +19 -0
  13. package/dist/cookie-consent-banner.js +3 -0
  14. package/dist/cookie-settings-view.cjs +470 -0
  15. package/dist/cookie-settings-view.d.cts +15 -0
  16. package/dist/cookie-settings-view.d.ts +15 -0
  17. package/dist/cookie-settings-view.js +3 -0
  18. package/dist/index.cjs +1460 -0
  19. package/dist/index.d.cts +317 -0
  20. package/dist/index.d.ts +317 -0
  21. package/dist/index.js +441 -0
  22. package/dist/marketplace-Ch7mdJZv.d.cts +123 -0
  23. package/dist/marketplace-Ch7mdJZv.d.ts +123 -0
  24. package/dist/marketplace-forms.cjs +134 -0
  25. package/dist/marketplace-forms.d.cts +10 -0
  26. package/dist/marketplace-forms.d.ts +10 -0
  27. package/dist/marketplace-forms.js +115 -0
  28. package/dist/marketplace-view.cjs +110 -0
  29. package/dist/marketplace-view.d.cts +21 -0
  30. package/dist/marketplace-view.d.ts +21 -0
  31. package/dist/marketplace-view.js +85 -0
  32. package/dist/meet-5Ym5bxfh.d.cts +79 -0
  33. package/dist/meet-5Ym5bxfh.d.ts +79 -0
  34. package/dist/meet-forms.cjs +188 -0
  35. package/dist/meet-forms.d.cts +13 -0
  36. package/dist/meet-forms.d.ts +13 -0
  37. package/dist/meet-forms.js +166 -0
  38. package/dist/meet-view.cjs +82 -0
  39. package/dist/meet-view.d.cts +23 -0
  40. package/dist/meet-view.d.ts +23 -0
  41. package/dist/meet-view.js +59 -0
  42. package/dist/snapshot-BS4hn1dk.d.cts +234 -0
  43. package/dist/snapshot-BS4hn1dk.d.ts +234 -0
  44. package/dist/snapshot-file.cjs +192 -0
  45. package/dist/snapshot-file.d.cts +21 -0
  46. package/dist/snapshot-file.d.ts +21 -0
  47. package/dist/snapshot-file.js +38 -0
  48. package/marketplace.css +61 -0
  49. package/package.json +103 -0
  50. package/tokens/broadsheet.css +61 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1460 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/types.ts
7
+ var FEATURE_NAMES = [
8
+ "smile",
9
+ "comments",
10
+ "chat",
11
+ "jobs",
12
+ "classifieds",
13
+ "referrals",
14
+ "coupons"
15
+ ];
16
+
17
+ // src/config.ts
18
+ var BROADSHEET_THEME = {
19
+ accent: "#bb171d",
20
+ ink: "#171917",
21
+ paper: "#ffffff",
22
+ sponsor: "#173d32"
23
+ };
24
+ var DISABLED_FEATURES = {
25
+ smile: false,
26
+ comments: false,
27
+ chat: false,
28
+ jobs: false,
29
+ classifieds: false,
30
+ referrals: false,
31
+ coupons: false
32
+ };
33
+ function featureEnabled(publication, feature) {
34
+ return publication.features[feature] === true;
35
+ }
36
+ function definePublication(input) {
37
+ if (!input.slug.trim() || !input.name.trim()) throw new Error("A publication needs a slug and a name");
38
+ if (!input.towns.length) throw new Error("A publication needs at least one covered town");
39
+ const layout = input.layout ?? "broadsheet";
40
+ const layouts = [.../* @__PURE__ */ new Set([layout, ...input.layouts ?? ["broadsheet"]])];
41
+ const referralsEnabled = input.features?.referrals === true || input.referrals?.enabled === true;
42
+ const couponsEnabled = input.features?.coupons === true || input.coupons?.enabled === true;
43
+ const features = { ...DISABLED_FEATURES, ...input.features, referrals: referralsEnabled, coupons: couponsEnabled };
44
+ for (const name of FEATURE_NAMES) features[name] = features[name] === true;
45
+ return {
46
+ slug: input.slug,
47
+ name: input.name,
48
+ masthead: input.masthead.length ? input.masthead : [input.name],
49
+ region: input.region,
50
+ regionLabel: input.regionLabel,
51
+ towns: input.towns,
52
+ nearbyTowns: input.nearbyTowns ?? [],
53
+ sections: input.sections ?? [],
54
+ description: input.description,
55
+ tagline: input.tagline ?? "Local stories. Close to home.",
56
+ briefTitle: input.briefTitle ?? `${input.regionLabel} in Brief`,
57
+ parent: input.parent === true,
58
+ layout,
59
+ layouts,
60
+ features,
61
+ theme: { ...BROADSHEET_THEME, ...input.theme },
62
+ referrals: { queryParam: input.referrals?.queryParam?.trim() || "ref", enabled: referralsEnabled },
63
+ coupons: { enabled: couponsEnabled, offers: input.coupons?.offers ?? [], resolve: input.coupons?.resolve },
64
+ modules: input.modules ?? {},
65
+ matchTag: input.matchTag
66
+ };
67
+ }
68
+ var LAYOUT_COOKIE = "news_publication_layout";
69
+ function resolveLayout(publication, cookieValue) {
70
+ const requested = cookieValue?.trim();
71
+ if (requested && publication.layouts.includes(requested)) return requested;
72
+ return publication.layout;
73
+ }
74
+
75
+ // src/preset.ts
76
+ var walnutCreekTimes = definePublication({
77
+ slug: "walnutcreektimes",
78
+ name: "Walnut Creek Times",
79
+ masthead: ["Walnut", "Creek", "Times"],
80
+ region: "Walnut Creek",
81
+ regionLabel: "Walnut Creek",
82
+ towns: ["Walnut Creek", "Rossmoor", "Saranap", "Pleasant Hill"],
83
+ nearbyTowns: ["Martinez", "Concord", "Contra Costa"],
84
+ sections: ["Civic life", "Community", "Local businesses", "Events", "Food & places"],
85
+ description: "Local news, independent businesses and community voices from Walnut Creek, Rossmoor, Saranap and Pleasant Hill.",
86
+ layout: "broadsheet",
87
+ layouts: ["broadsheet"],
88
+ features: { smile: true, jobs: true, classifieds: true },
89
+ modules: { home: ["daily-smile"] }
90
+ });
91
+
92
+ // src/cache.ts
93
+ var NEWS_CACHE = {
94
+ feedRevalidateSeconds: 30,
95
+ articleRevalidateSeconds: 60,
96
+ edgeMaxAgeSeconds: 60,
97
+ staleWhileRevalidateSeconds: 300,
98
+ feedTag: "news-feed",
99
+ articleTag: "news-article",
100
+ inventoryTag: "public-news-discovery-v2",
101
+ paths: ["/", "/news"]
102
+ };
103
+ var feedCacheOptions = {
104
+ revalidate: NEWS_CACHE.feedRevalidateSeconds,
105
+ tags: [NEWS_CACHE.feedTag]
106
+ };
107
+ var articleCacheOptions = {
108
+ revalidate: NEWS_CACHE.articleRevalidateSeconds,
109
+ tags: [NEWS_CACHE.articleTag]
110
+ };
111
+ var inventoryCacheOptions = {
112
+ revalidate: NEWS_CACHE.feedRevalidateSeconds,
113
+ tags: [NEWS_CACHE.feedTag, NEWS_CACHE.inventoryTag]
114
+ };
115
+ function edgeCacheControl() {
116
+ return `public, s-maxage=${NEWS_CACHE.edgeMaxAgeSeconds}, stale-while-revalidate=${NEWS_CACHE.staleWhileRevalidateSeconds}`;
117
+ }
118
+ function feedCacheKey(publicationId, before, section) {
119
+ return `${publicationId}:${before ?? ""}:${section ?? ""}`;
120
+ }
121
+ function articleCacheKey(publicationId, slug) {
122
+ return `${publicationId}:${slug}`;
123
+ }
124
+ var NewsError = class extends Error {
125
+ constructor(status, message) {
126
+ super(message);
127
+ this.status = status;
128
+ this.name = "NewsError";
129
+ }
130
+ status;
131
+ };
132
+ function shouldServeLastKnown(error) {
133
+ const status = error instanceof NewsError ? error.status : error && typeof error === "object" && "status" in error ? error.status : void 0;
134
+ return status !== 404;
135
+ }
136
+ async function withLastKnown(store, key, load, accept = () => true) {
137
+ try {
138
+ const value = await load();
139
+ if (accept(value)) store.set(key, value);
140
+ return value;
141
+ } catch (error) {
142
+ if (!shouldServeLastKnown(error)) throw error;
143
+ const cached = store.get(key);
144
+ if (cached) return cached;
145
+ throw error;
146
+ }
147
+ }
148
+ function secretsMatch(provided, secret) {
149
+ if (!provided || !secret) return false;
150
+ const left = Array.from(provided);
151
+ const right = Array.from(secret);
152
+ let mismatch = left.length === right.length ? 0 : 1;
153
+ const length = Math.max(left.length, right.length);
154
+ for (let index = 0; index < length; index += 1) {
155
+ mismatch |= (left[index]?.codePointAt(0) ?? 0) ^ (right[index]?.codePointAt(0) ?? 0);
156
+ }
157
+ return mismatch === 0;
158
+ }
159
+ function revalidateDecision(input) {
160
+ if (!secretsMatch(input.providedSecret, input.configuredSecret)) {
161
+ return { status: 401, body: { error: "Unauthorized" } };
162
+ }
163
+ const tag = input.tag?.trim() || `${NEWS_CACHE.feedTag}, ${NEWS_CACHE.articleTag}`;
164
+ const path = input.path?.trim() || NEWS_CACHE.paths.join(", ");
165
+ return { status: 200, body: { revalidated: true, tag, path, now: input.now ?? Date.now() } };
166
+ }
167
+ function defaultRevalidateTargets() {
168
+ return {
169
+ tags: [NEWS_CACHE.feedTag, NEWS_CACHE.articleTag, NEWS_CACHE.inventoryTag],
170
+ paths: [...NEWS_CACHE.paths]
171
+ };
172
+ }
173
+
174
+ // src/editorial.ts
175
+ var labels = {
176
+ "Local businesses": "Local business",
177
+ "Food & places": "Food & drink"
178
+ };
179
+ function sectionId(name) {
180
+ return "section-" + name.normalize("NFKD").replace(/\p{Mark}/gu, "").toLowerCase().replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-|-$/g, "");
181
+ }
182
+ function sectionPath(name) {
183
+ return name === "Local news" ? "/news" : "/sections/" + encodeURIComponent(sectionId(name).replace(/^section-/, ""));
184
+ }
185
+ function sectionDefinition(name) {
186
+ return { name, label: labels[name] ?? name, id: sectionId(name), items: [] };
187
+ }
188
+ function editorialSections(items) {
189
+ const groups = /* @__PURE__ */ new Map();
190
+ for (const item of items) {
191
+ const name = item.section?.trim() || "Local news";
192
+ groups.set(name, [...groups.get(name) ?? [], item]);
193
+ }
194
+ return Array.from(groups, ([name, articles]) => ({ ...sectionDefinition(name), items: articles }));
195
+ }
196
+ function composeEdition(items, selection = {}, now = Date.now()) {
197
+ const seen = /* @__PURE__ */ new Set();
198
+ const inventory = items.filter((item) => {
199
+ if (!item.id || !item.slug || seen.has(item.id)) return false;
200
+ seen.add(item.id);
201
+ return true;
202
+ });
203
+ const pins = !selection.pinned_until || Date.parse(selection.pinned_until) > now ? selection : {};
204
+ const lead = inventory.find((item) => item.slug === pins.lead_slug) ?? inventory[0];
205
+ const remaining = inventory.filter((item) => item !== lead);
206
+ const requested = new Set(pins.secondary_slugs ?? []);
207
+ const ordered = [
208
+ ...(pins.secondary_slugs ?? []).flatMap((slug) => remaining.find((item) => item.slug === slug) ?? []),
209
+ ...remaining.filter((item) => !requested.has(item.slug))
210
+ ];
211
+ const secondary = Array.from(new Map(ordered.map((item) => [item.id, item])).values()).slice(0, 3);
212
+ for (const item of secondary) remaining.splice(remaining.indexOf(item), 1);
213
+ const eligible = remaining.filter((item) => {
214
+ const age = now - Date.parse(item.published_at);
215
+ return item.excerpt?.trim() && age >= 0 && age <= 7 * 864e5;
216
+ });
217
+ const selected = (pins.brief_slugs ?? []).flatMap((slug) => eligible.find((item) => item.slug === slug) ?? []);
218
+ const candidates = [...selected, ...editorialSections(eligible).map((group) => group.items[0]), ...eligible];
219
+ const briefIds = /* @__PURE__ */ new Set();
220
+ const briefs = candidates.filter((item) => {
221
+ if (!item || briefIds.has(item.id) || briefIds.size >= 4) return false;
222
+ briefIds.add(item.id);
223
+ return true;
224
+ });
225
+ const compact = [...briefs, ...remaining.filter((item) => !briefIds.has(item.id))].slice(0, 4);
226
+ const compactIds = new Set(compact.map((item) => item.id));
227
+ const sections = editorialSections(remaining.filter((item) => !compactIds.has(item.id)));
228
+ return { lead, secondary, briefs, compact, sections, navigation: editorialSections(inventory) };
229
+ }
230
+ function displayHeadline(article, selection = {}) {
231
+ return selection.stories?.[article.slug]?.display_title?.trim() || article.title;
232
+ }
233
+ function briefSummary(article, selection = {}) {
234
+ const chosen = selection.stories?.[article.slug]?.brief?.trim();
235
+ if (chosen) return chosen;
236
+ const excerpt = article.excerpt?.trim();
237
+ if (!excerpt) return "";
238
+ const first = [...new Intl.Segmenter("en", { granularity: "sentence" }).segment(excerpt)][0]?.segment.trim() ?? "";
239
+ return Array.from(first).length <= 280 && /[.!?。!?][”’"']?$/u.test(first) ? first : "";
240
+ }
241
+ function normalizedText(value) {
242
+ return value.normalize("NFKD").replace(/\p{Mark}/gu, "").toLowerCase();
243
+ }
244
+ function townSlug(town) {
245
+ return sectionId(town).replace(/^section-/, "");
246
+ }
247
+ function storyTowns(article, towns, selection = {}) {
248
+ const explicit = selection.stories?.[article.slug]?.towns;
249
+ if (explicit) return towns.filter((town) => explicit.includes(town));
250
+ const words = ` ${normalizedText(`${article.title} ${article.excerpt ?? ""}`).replace(/[^\p{Letter}\p{Number}]+/gu, " ")} `;
251
+ return towns.filter((town) => words.includes(` ${normalizedText(town).replace(/[^\p{Letter}\p{Number}]+/gu, " ")} `));
252
+ }
253
+ function tagSlug(value) {
254
+ return value.normalize("NFKD").replace(/\p{Mark}/gu, "").toLowerCase().replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-|-$/g, "");
255
+ }
256
+ function matchesStoryTag(article, tag) {
257
+ if (!tag) return true;
258
+ const target = tagSlug(tag);
259
+ if ((article.tags ?? []).some((item) => tagSlug(item.slug) === target || tagSlug(item.name) === target)) return true;
260
+ const normalized = normalizedText(`${article.title} ${article.excerpt ?? ""} ${article.section ?? ""}`);
261
+ return normalized.includes(target.replace(/-/g, " ")) || normalized.includes(target);
262
+ }
263
+ function filterNews(items, filters, publication, selection = {}) {
264
+ const terms = normalizedText(filters.q ?? "").split(/\s+/u).filter(Boolean);
265
+ const matchTag = publication.matchTag ?? matchesStoryTag;
266
+ return items.filter((item) => (!filters.section || sectionId(item.section || "Local news") === `section-${filters.section}`) && (!filters.town || storyTowns(item, publication.towns, selection).some((town) => townSlug(town) === filters.town)) && (!filters.tag || matchTag(item, filters.tag)) && terms.every((term) => normalizedText(`${item.title} ${item.excerpt ?? ""} ${item.section ?? ""}`).includes(term)));
267
+ }
268
+ function validCalendarDate(value) {
269
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().startsWith(value);
270
+ }
271
+ function activeNearby(selection = {}, now = Date.now()) {
272
+ const today = new Intl.DateTimeFormat("en-CA", { timeZone: "America/Los_Angeles", year: "numeric", month: "2-digit", day: "2-digit" }).format(now);
273
+ const lastDay = new Date(Date.parse(today) + 6 * 864e5).toISOString().slice(0, 10);
274
+ return (selection.nearby ?? []).filter((update) => validCalendarDate(update.starts) && validCalendarDate(update.ends ?? update.starts) && (update.ends ?? update.starts) >= update.starts && update.starts <= lastDay && (update.ends ?? update.starts) >= today).sort((a, b) => a.starts.localeCompare(b.starts)).slice(0, 24);
275
+ }
276
+ function nearbyUpdates(items, selection = {}, now = Date.now()) {
277
+ const published = new Map(items.map((item) => [item.slug, item]));
278
+ return activeNearby(selection, now).filter((update) => published.has(update.slug)).map((update) => ({ ...update, article: published.get(update.slug) }));
279
+ }
280
+ function shortDeck(value, limit = 160) {
281
+ const text = value?.trim().replace(/\s+/gu, " ") ?? "";
282
+ const characters = Array.from(text);
283
+ if (characters.length <= limit) return text;
284
+ let sentences = "";
285
+ for (const { segment } of new Intl.Segmenter("en", { granularity: "sentence" }).segment(text)) {
286
+ if (Array.from(sentences + segment.trimEnd()).length > limit) break;
287
+ sentences += segment;
288
+ }
289
+ if (sentences.trim()) return sentences.trim();
290
+ const opening = characters.slice(0, limit).join("");
291
+ const space = opening.lastIndexOf(" ");
292
+ return (space > opening.length / 2 ? opening.slice(0, space) : opening).trimEnd() + "\u2026";
293
+ }
294
+ function storyDate(value) {
295
+ const date = new Date(value);
296
+ return Number.isNaN(date.getTime()) ? null : date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "America/Los_Angeles" });
297
+ }
298
+
299
+ // src/seo.ts
300
+ function publicHttps(value) {
301
+ if (!value) return void 0;
302
+ try {
303
+ const url = new URL(value);
304
+ return url.protocol === "https:" && !url.username && !url.password ? url.toString() : void 0;
305
+ } catch {
306
+ return void 0;
307
+ }
308
+ }
309
+ function articleUrl(canonical, slug) {
310
+ return `${canonical.replace(/\/$/, "")}/news/${encodeURIComponent(slug)}`;
311
+ }
312
+ function newsArticleJsonLd(publication, article, canonical, authorName) {
313
+ const url = articleUrl(canonical, article.slug);
314
+ const canonicalUrl = publicHttps(article.canonical_url) ?? url;
315
+ const image = publicHttps(article.cover_image_url);
316
+ return {
317
+ "@context": "https://schema.org",
318
+ "@type": "NewsArticle",
319
+ headline: article.title,
320
+ description: article.excerpt ?? void 0,
321
+ datePublished: article.published_at,
322
+ dateModified: article.updated_at,
323
+ image: image ? [image] : void 0,
324
+ mainEntityOfPage: canonicalUrl,
325
+ url,
326
+ author: { "@type": "Organization", name: authorName || publication.name },
327
+ publisher: { "@type": "NewsMediaOrganization", name: publication.name, url: canonical }
328
+ };
329
+ }
330
+ function publicationJsonLd(publication, canonical) {
331
+ return {
332
+ "@context": "https://schema.org",
333
+ "@type": "NewsMediaOrganization",
334
+ name: publication.name,
335
+ url: canonical,
336
+ description: publication.description,
337
+ areaServed: publication.towns
338
+ };
339
+ }
340
+ function publicationMeta(publication, canonical, indexable) {
341
+ const title = `${publication.name} | Local news, business & community`;
342
+ return {
343
+ title,
344
+ description: publication.description,
345
+ canonical,
346
+ indexable,
347
+ openGraph: {
348
+ title: publication.name,
349
+ description: publication.description,
350
+ url: canonical,
351
+ siteName: publication.name,
352
+ type: "website"
353
+ }
354
+ };
355
+ }
356
+ function articleMeta(publication, article, canonical, indexable) {
357
+ const url = articleUrl(canonical, article.slug);
358
+ const canonicalUrl = publicHttps(article.canonical_url) ?? url;
359
+ const image = publicHttps(article.og_image_url) ?? publicHttps(article.cover_image_url);
360
+ return {
361
+ title: article.seo_title ?? article.title,
362
+ description: article.seo_description ?? article.excerpt ?? publication.description,
363
+ canonical: canonicalUrl,
364
+ indexable: indexable && !article.no_index,
365
+ openGraph: {
366
+ type: "article",
367
+ title: article.title,
368
+ description: article.excerpt ?? void 0,
369
+ url,
370
+ publishedTime: article.published_at,
371
+ modifiedTime: article.updated_at,
372
+ images: image ? [image] : []
373
+ }
374
+ };
375
+ }
376
+ function llmsText(publication, canonical) {
377
+ const origin = canonical.replace(/\/$/, "");
378
+ return [
379
+ `# ${publication.name}`,
380
+ publication.description,
381
+ `Towns: ${publication.towns.join(", ")}`,
382
+ `Site: ${origin}`,
383
+ `Latest: ${origin}/news`,
384
+ `Sitemap: ${origin}/sitemap.xml`
385
+ ].join("\n");
386
+ }
387
+
388
+ // src/analytics.ts
389
+ var ANALYTICS_VERSION = 1;
390
+ var ANALYTICS_COOKIE = "napsite_analytics_v1";
391
+ var SESSION_IDLE_MS = 30 * 60 * 1e3;
392
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
393
+ var browser = (category) => ({ category, source: "browser" });
394
+ var server = () => ({ category: "conversion", source: "server" });
395
+ var EVENTS = {
396
+ page_viewed: browser("navigation"),
397
+ page_engaged: browser("engagement"),
398
+ article_read_completed: browser("content"),
399
+ article_shared: browser("engagement"),
400
+ promotion_viewed: browser("content"),
401
+ promotion_clicked: browser("engagement"),
402
+ newsletter_signup_started: browser("engagement"),
403
+ newsletter_subscription_confirmed: server(),
404
+ web_vital_recorded: browser("system"),
405
+ scroll_depth_reached: browser("content"),
406
+ navigation_clicked: browser("navigation"),
407
+ cta_clicked: browser("engagement"),
408
+ contact_link_clicked: browser("engagement"),
409
+ outbound_link_clicked: browser("engagement"),
410
+ pricing_plan_selected: browser("engagement"),
411
+ comparison_used: browser("engagement"),
412
+ mobile_menu_opened: browser("navigation"),
413
+ form_started: browser("engagement"),
414
+ form_submitted: browser("engagement"),
415
+ form_failed: browser("system"),
416
+ referral_landed: browser("engagement"),
417
+ coupon_applied: browser("engagement"),
418
+ visitor_session_created: browser("system"),
419
+ contact_submitted: server()
420
+ };
421
+ var UTM_FIELDS = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"];
422
+ var PUBLIC_PATHS = /* @__PURE__ */ new Set([
423
+ "/",
424
+ "/news",
425
+ "/this-week",
426
+ "/newsletter",
427
+ "/advertise",
428
+ "/pricing",
429
+ "/contact",
430
+ "/book",
431
+ "/preview",
432
+ "/about",
433
+ "/coverage",
434
+ "/story-tip",
435
+ "/corrections",
436
+ "/how-it-works",
437
+ "/privacy",
438
+ "/smile",
439
+ "/subscribe",
440
+ "/jobs",
441
+ "/jobs/post",
442
+ "/classifieds",
443
+ "/classifieds/post"
444
+ ]);
445
+ function shortText(value, max = 100) {
446
+ if (typeof value !== "string") return void 0;
447
+ const text = value.trim();
448
+ if (!text || /[@\r\n\u0000-\u001f]/.test(text)) return void 0;
449
+ return Array.from(text).slice(0, max).join("");
450
+ }
451
+ function publicUrl(value, originOnly = false) {
452
+ if (typeof value !== "string") return void 0;
453
+ try {
454
+ const url = new URL(value);
455
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) return void 0;
456
+ if (originOnly) return url.origin;
457
+ const path = PUBLIC_PATHS.has(url.pathname) ? url.pathname : /^\/sections\/[^/]+$/.test(url.pathname) ? "/news/section" : /^\/news\/[^/]+$/.test(url.pathname) && url.pathname !== "/news/unsubscribe" ? "/news/article" : /^\/smile\/[^/]+$/.test(url.pathname) ? "/smile" : /^\/jobs\/[^/]+$/.test(url.pathname) ? "/jobs/opening" : /^\/classifieds\/[^/]+$/.test(url.pathname) ? "/classifieds/listing" : "/other";
458
+ return url.origin + path;
459
+ } catch {
460
+ return void 0;
461
+ }
462
+ }
463
+ function attribution(url, referrer = "", publication) {
464
+ const parsed = new URL(url);
465
+ const result = { landing_page: publicUrl(url) };
466
+ const referringOrigin = publicUrl(referrer, true);
467
+ if (referringOrigin && new URL(referringOrigin).hostname.replace(/^www\./, "") !== parsed.hostname.replace(/^www\./, "")) {
468
+ result.referrer = referringOrigin;
469
+ }
470
+ for (const key of UTM_FIELDS) {
471
+ const value = shortText(parsed.searchParams.get(key));
472
+ if (value) result[key] = value;
473
+ }
474
+ if (!result.utm_source && ["gclid", "gbraid", "wbraid"].some((key) => parsed.searchParams.has(key))) {
475
+ result.utm_source = "google";
476
+ result.utm_medium ??= "cpc";
477
+ }
478
+ if (!result.utm_source && parsed.searchParams.has("fbclid")) {
479
+ result.utm_source = "facebook";
480
+ result.utm_medium ??= "social";
481
+ }
482
+ if (publication?.referrals.enabled) {
483
+ const code = shortText(parsed.searchParams.get(publication.referrals.queryParam), 64);
484
+ if (code && /^[a-zA-Z0-9_-]+$/.test(code)) result.referral_code = code;
485
+ }
486
+ return result;
487
+ }
488
+
489
+ // src/commerce.ts
490
+ function resolveCoupon(publication, code) {
491
+ if (!publication.coupons.enabled) return null;
492
+ const normalized = code?.trim();
493
+ if (!normalized) return null;
494
+ if (publication.coupons.resolve) return publication.coupons.resolve(normalized, publication.coupons.offers);
495
+ return publication.coupons.offers.find((offer) => offer.code.localeCompare(normalized, void 0, { sensitivity: "base" }) === 0) ?? null;
496
+ }
497
+ function referralCode(publication, url) {
498
+ if (!publication.referrals.enabled) return null;
499
+ try {
500
+ const value = new URL(url).searchParams.get(publication.referrals.queryParam)?.trim() ?? "";
501
+ return /^[a-zA-Z0-9_-]{1,64}$/.test(value) ? value : null;
502
+ } catch {
503
+ return null;
504
+ }
505
+ }
506
+
507
+ // src/meet.ts
508
+ var MEET_ACTIVITIES = [
509
+ { value: "coffee", label: "Coffee" },
510
+ { value: "walk", label: "Walk" },
511
+ { value: "hike", label: "Hike" },
512
+ { value: "pickleball", label: "Pickleball" },
513
+ { value: "tennis", label: "Tennis" },
514
+ { value: "soccer", label: "Soccer" },
515
+ { value: "bike", label: "Bike ride" },
516
+ { value: "dogs", label: "Dog walk" },
517
+ { value: "books", label: "Book club" },
518
+ { value: "games", label: "Board games" },
519
+ { value: "kids", label: "Kids outing" },
520
+ { value: "volunteer", label: "Volunteer" },
521
+ { value: "other", label: "Something else" }
522
+ ];
523
+ var HOST_WAIT_MS = 48 * 60 * 60 * 1e3;
524
+ function isAdult(bornOn, now = /* @__PURE__ */ new Date()) {
525
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(bornOn)) return false;
526
+ const born = /* @__PURE__ */ new Date(`${bornOn}T00:00:00.000Z`);
527
+ if (Number.isNaN(born.getTime())) return false;
528
+ const adult = new Date(born);
529
+ adult.setUTCFullYear(adult.getUTCFullYear() + 18);
530
+ return adult.getTime() <= now.getTime();
531
+ }
532
+ function canHostMeetup(accountCreatedAt, now = /* @__PURE__ */ new Date()) {
533
+ const created = Date.parse(accountCreatedAt);
534
+ return Number.isFinite(created) && now.getTime() - created >= HOST_WAIT_MS;
535
+ }
536
+ function screenMessage(value) {
537
+ const text = Array.from(value.trim()).slice(0, 1e3).join("");
538
+ if (Array.from(text).length < 1) return { ok: false, reason: "Write a short message." };
539
+ if (/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.test(text)) return { ok: false, reason: "Share an email in person, not in the chat." };
540
+ if (/(https?:\/\/|www\.)\S+/i.test(text)) return { ok: false, reason: "Links are not allowed in the chat." };
541
+ const digits = text.replace(/\D/g, "");
542
+ if (digits.length >= 10) return { ok: false, reason: "Share a phone number in person, not in the chat." };
543
+ return { ok: true, text };
544
+ }
545
+ function activityLabel(value, otherLabel = "") {
546
+ if (value === "other") return Array.from(otherLabel.trim()).slice(0, 40).join("") || "Something else";
547
+ return MEET_ACTIVITIES.find((item) => item.value === value)?.label ?? "";
548
+ }
549
+ function prepareMeetup(input, places, now = /* @__PURE__ */ new Date()) {
550
+ const activity = typeof input.activity === "string" ? input.activity : "";
551
+ if (!MEET_ACTIVITIES.some((item) => item.value === activity)) throw new Error("Choose an activity.");
552
+ const label = activityLabel(activity, typeof input.otherLabel === "string" ? input.otherLabel : "");
553
+ if (activity === "other" && label === "Something else" && !String(input.otherLabel ?? "").trim()) {
554
+ throw new Error("Name the activity.");
555
+ }
556
+ const place = places.find((item) => item.id === input.placeId);
557
+ if (!place) throw new Error("Choose a public place.");
558
+ const starts = typeof input.startsAt === "string" ? Date.parse(input.startsAt) : NaN;
559
+ if (!Number.isFinite(starts) || starts < now.getTime() + 60 * 60 * 1e3) {
560
+ throw new Error("Choose a time at least an hour from now.");
561
+ }
562
+ const notes = screenMessage(typeof input.notes === "string" && input.notes.trim() ? input.notes : "Meet at the public place.");
563
+ if (!notes.ok) throw new Error(notes.reason);
564
+ const capacity = Number(input.capacity ?? 6);
565
+ if (!Number.isInteger(capacity) || capacity < 2 || capacity > 12) throw new Error("Choose a group size from 2 to 12.");
566
+ return {
567
+ activity,
568
+ activityLabel: label,
569
+ placeId: place.id,
570
+ placeName: place.name,
571
+ placeDetail: place.detail,
572
+ startsAt: new Date(starts).toISOString(),
573
+ notes: notes.text,
574
+ capacity
575
+ };
576
+ }
577
+
578
+ // src/marketplace.ts
579
+ var JOB_EMPLOYMENT_TYPES = [
580
+ { value: "full_time", label: "Full-time" },
581
+ { value: "part_time", label: "Part-time" },
582
+ { value: "contract", label: "Contract" },
583
+ { value: "temporary", label: "Temporary" },
584
+ { value: "internship", label: "Internship" }
585
+ ];
586
+ var CLASSIFIED_CATEGORIES = [
587
+ { value: "for-sale", label: "For sale" },
588
+ { value: "services", label: "Services" },
589
+ { value: "housing", label: "Housing" },
590
+ { value: "community", label: "Community" },
591
+ { value: "wanted", label: "Wanted" }
592
+ ];
593
+ function clipChars(value, max) {
594
+ const chars = Array.from(value.trim());
595
+ return chars.length <= max ? chars.join("") : chars.slice(0, max).join("");
596
+ }
597
+ function isEmploymentType(value) {
598
+ return JOB_EMPLOYMENT_TYPES.some((item) => item.value === value);
599
+ }
600
+ function isClassifiedCategory(value) {
601
+ return CLASSIFIED_CATEGORIES.some((item) => item.value === value);
602
+ }
603
+ function employmentLabel(value) {
604
+ return JOB_EMPLOYMENT_TYPES.find((item) => item.value === value)?.label ?? "";
605
+ }
606
+ function categoryLabel(value) {
607
+ return CLASSIFIED_CATEGORIES.find((item) => item.value === value)?.label ?? "";
608
+ }
609
+ function publicJobsPath(query = "") {
610
+ const params = new URLSearchParams({ limit: "50" });
611
+ const q = clipChars(query, 80);
612
+ if (q) params.set("q", q);
613
+ return `/v1/jobs/openings/public/postings?${params}`;
614
+ }
615
+ function publicJobPath(slug) {
616
+ return `/v1/jobs/openings/public/postings/${encodeURIComponent(slug)}`;
617
+ }
618
+ function classifiedSearchPath(options = {}) {
619
+ const params = new URLSearchParams({ status: "active", limit: "50", sort_by: "newest" });
620
+ const query = clipChars(options.query ?? "", 80);
621
+ if (query) params.set("query", query);
622
+ if (options.category && isClassifiedCategory(options.category)) params.set("category", options.category);
623
+ return `/v1/listings/search?${params}`;
624
+ }
625
+ function classifiedPath(id) {
626
+ return `/v1/listings/${encodeURIComponent(id)}`;
627
+ }
628
+ function requireText(value, label, max, min = 1) {
629
+ const text = clipChars(typeof value === "string" ? value : "", max);
630
+ if (Array.from(text).length < min) throw new Error(`Please complete the ${label}.`);
631
+ return text;
632
+ }
633
+ function prepareJobPost(input, towns) {
634
+ const title = requireText(input.title, "job title", 120);
635
+ const company = requireText(input.company, "employer", 120);
636
+ const town = requireText(input.town, "town", 80);
637
+ const description = requireText(input.description, "description", 4e3, 20);
638
+ const employment = typeof input.employment_type === "string" ? input.employment_type : "";
639
+ if (!towns.includes(town)) throw new Error("Please choose a town this paper covers.");
640
+ if (!isEmploymentType(employment)) throw new Error("Please choose a job type.");
641
+ const email = clipChars(typeof input.apply_email === "string" ? input.apply_email : "", 254);
642
+ if (email && !/^[^\s@]+@[^\s@]+$/.test(email)) throw new Error("Please check the apply email.");
643
+ return { title, company, town, employment_type: employment, description, apply_email: email || void 0 };
644
+ }
645
+ function jobEmployerBody(job) {
646
+ return { company_name: job.company, headline: `${job.company} in ${job.town}` };
647
+ }
648
+ function jobOpeningBody(job) {
649
+ const apply = job.apply_email ? `
650
+ Apply: ${job.apply_email}` : "";
651
+ return {
652
+ title: job.title,
653
+ employment_type: job.employment_type,
654
+ location_type: "onsite",
655
+ description_md: `${job.description}
656
+
657
+ Town: ${job.town}${apply}`
658
+ };
659
+ }
660
+ function jobPostingBody(job) {
661
+ return { channel: "partner_board", title_override: job.title, body_md: job.description, language: "en" };
662
+ }
663
+ function prepareClassified(input, towns, region, now = /* @__PURE__ */ new Date()) {
664
+ const title = requireText(input.title, "title", 120);
665
+ const description = requireText(input.description, "description", 4e3, 20);
666
+ const town = requireText(input.town, "town", 80);
667
+ const category = typeof input.category === "string" ? input.category : "";
668
+ if (!towns.includes(town)) throw new Error("Please choose a town this paper covers.");
669
+ if (!isClassifiedCategory(category)) throw new Error("Please choose a category.");
670
+ let price;
671
+ if (input.price !== void 0 && input.price !== "" && input.price !== null) {
672
+ const parsed = Number(input.price);
673
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 1e6) throw new Error("Please check the price.");
674
+ price = parsed;
675
+ }
676
+ const expires = new Date(now);
677
+ expires.setUTCDate(expires.getUTCDate() + 30);
678
+ return {
679
+ title,
680
+ description,
681
+ category,
682
+ price,
683
+ currency: "USD",
684
+ location: { town, region },
685
+ expires_at: expires.toISOString()
686
+ };
687
+ }
688
+
689
+ // src/snapshot.ts
690
+ var SNAPSHOT_VERSION = 1;
691
+ function publicationRecord(publication) {
692
+ return {
693
+ slug: publication.slug,
694
+ name: publication.name,
695
+ masthead: publication.masthead,
696
+ region: publication.region,
697
+ regionLabel: publication.regionLabel,
698
+ towns: publication.towns,
699
+ nearbyTowns: publication.nearbyTowns,
700
+ sections: publication.sections,
701
+ description: publication.description,
702
+ tagline: publication.tagline,
703
+ briefTitle: publication.briefTitle,
704
+ parent: publication.parent,
705
+ layout: publication.layout,
706
+ layouts: publication.layouts,
707
+ features: publication.features,
708
+ theme: publication.theme,
709
+ referrals: publication.referrals,
710
+ coupons: { enabled: publication.coupons.enabled, offers: publication.coupons.offers },
711
+ modules: publication.modules
712
+ };
713
+ }
714
+ function snapshotFromRecord(record, syncedAt = (/* @__PURE__ */ new Date()).toISOString()) {
715
+ const publication = definePublication(record);
716
+ return { version: SNAPSHOT_VERSION, syncedAt, record: publicationRecord(publication) };
717
+ }
718
+ function parseSnapshot(raw) {
719
+ try {
720
+ return acceptSnapshot(JSON.parse(raw));
721
+ } catch {
722
+ return null;
723
+ }
724
+ }
725
+ function acceptSnapshot(value) {
726
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
727
+ const snapshot = value;
728
+ if (snapshot.version !== SNAPSHOT_VERSION || typeof snapshot.syncedAt !== "string") return null;
729
+ if (!Number.isFinite(Date.parse(snapshot.syncedAt))) return null;
730
+ try {
731
+ return snapshotFromRecord(snapshot.record, snapshot.syncedAt);
732
+ } catch {
733
+ return null;
734
+ }
735
+ }
736
+ function acceptRemoteRecord(value, expectedSlug) {
737
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
738
+ const body = value;
739
+ const candidate = body.record ?? body.publication ?? value;
740
+ try {
741
+ const publication = definePublication(candidate);
742
+ if (expectedSlug && publication.slug !== expectedSlug) return null;
743
+ return publicationRecord(publication);
744
+ } catch {
745
+ return null;
746
+ }
747
+ }
748
+ function publicationFromSnapshot(snapshot, hooks = {}) {
749
+ return definePublication({
750
+ ...snapshot.record,
751
+ matchTag: hooks.matchTag,
752
+ coupons: { ...snapshot.record.coupons, resolve: hooks.resolveCoupon }
753
+ });
754
+ }
755
+ async function syncPublication(input) {
756
+ const now = input.now ?? Date.now();
757
+ try {
758
+ const remote = acceptRemoteRecord(await input.fetchRecord(), input.expectedSlug);
759
+ if (remote) {
760
+ const snapshot = snapshotFromRecord(remote, new Date(now).toISOString());
761
+ return { publication: publicationFromSnapshot(snapshot, input.hooks), snapshot, source: "fresh", ageMs: 0 };
762
+ }
763
+ } catch {
764
+ }
765
+ if (!input.previous) {
766
+ throw new Error("This paper has no synced publication config");
767
+ }
768
+ const ageMs = Math.max(0, now - Date.parse(input.previous.syncedAt));
769
+ return {
770
+ publication: publicationFromSnapshot(input.previous, input.hooks),
771
+ snapshot: input.previous,
772
+ source: "retained",
773
+ ageMs
774
+ };
775
+ }
776
+
777
+ // src/cookies.ts
778
+ var COOKIE_CONSENT_KEY = "sm_cookie_consent_v1";
779
+ var COOKIE_CONSENT_BACKUP_KEY = "napsite_cookie_consent_v1";
780
+ var COOKIE_CONSENT_EVENT = "scalemule-cookie-consent-change";
781
+ var COOKIE_CONSENT_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
782
+ var DEFAULT_PREFERENCES_REJECTED = {
783
+ essential: true,
784
+ performance: false,
785
+ functional: false,
786
+ advertising: false
787
+ };
788
+ var DEFAULT_PREFERENCES_ACCEPTED = {
789
+ essential: true,
790
+ performance: true,
791
+ functional: true,
792
+ advertising: true
793
+ };
794
+ var COOKIE_CATEGORIES = [
795
+ {
796
+ id: "essential",
797
+ title: "Strictly Necessary Cookies",
798
+ shortDescription: "Essential for site security, navigation, and privacy preference management.",
799
+ fullDescription: "These cookies are necessary for the website to function properly and securely. They enable core features such as load balancing, CSRF protection, secure session tokens, and remembering your cookie consent choice. Because the site cannot function without them, they cannot be turned off.",
800
+ required: true,
801
+ cookies: [
802
+ {
803
+ name: "sm_cookie_consent_v1",
804
+ provider: "First-Party (ScaleMule)",
805
+ purpose: "Stores your verified cookie privacy and category consent choices.",
806
+ duration: "1 year",
807
+ type: "HTTP Cookie"
808
+ },
809
+ {
810
+ name: "napsite_session_v1",
811
+ provider: "First-Party",
812
+ purpose: "Maintains session continuity, security tokens, and server routing.",
813
+ duration: "30 minutes (idle)",
814
+ type: "Session Storage"
815
+ },
816
+ {
817
+ name: "napsite_preview",
818
+ provider: "First-Party",
819
+ purpose: "Verifies preview access permissions when viewing unpublished newsroom editions.",
820
+ duration: "14 days",
821
+ type: "HTTP Cookie"
822
+ }
823
+ ]
824
+ },
825
+ {
826
+ id: "performance",
827
+ title: "Performance & Analytics Cookies",
828
+ shortDescription: "Help us measure reader engagement, story completion, and site reliability.",
829
+ fullDescription: "These cookies collect anonymous information about how visitors navigate our news publication. They allow us to count article reads, understand scroll depth and reading completion, and identify broken links or slow-loading assets so we can keep our community reporting responsive and fast. All metrics are aggregated and never contain personal information.",
830
+ required: false,
831
+ cookies: [
832
+ {
833
+ name: "napsite_visitor_v1",
834
+ provider: "First-Party",
835
+ purpose: "A randomized anonymous UUIDv7 to count distinct readers without tracking personal identity.",
836
+ duration: "1 year",
837
+ type: "Local Storage"
838
+ },
839
+ {
840
+ name: "napsite_analytics_v1",
841
+ provider: "First-Party",
842
+ purpose: "Connects reading completion and web vitals to the anonymous session.",
843
+ duration: "30 minutes",
844
+ type: "HTTP Cookie"
845
+ }
846
+ ]
847
+ },
848
+ {
849
+ id: "functional",
850
+ title: "Functional & Personalization Cookies",
851
+ shortDescription: "Remember your reading preferences, text size, and narration audio settings.",
852
+ fullDescription: "These cookies enable enhanced convenience features designed for readers. They remember your custom text sizing, keep your preferred audio narration playback speed (e.g., 1.0x vs 1.25x), and preserve your local town filters across the front page so you always see news closest to your neighborhood.",
853
+ required: false,
854
+ cookies: [
855
+ {
856
+ name: "reader_text_size",
857
+ provider: "First-Party",
858
+ purpose: "Remembers your preferred reading font size across articles.",
859
+ duration: "1 year",
860
+ type: "Local Storage"
861
+ },
862
+ {
863
+ name: "audio_narration_speed",
864
+ provider: "First-Party",
865
+ purpose: "Stores your preferred audio player playback rate (e.g. 1.0x, 1.25x).",
866
+ duration: "1 year",
867
+ type: "Local Storage"
868
+ }
869
+ ]
870
+ },
871
+ {
872
+ id: "advertising",
873
+ title: "Advertising & Local Sponsorship Cookies",
874
+ shortDescription: "Support local business sponsors by capping repetition and measuring notices.",
875
+ fullDescription: "Our journalism is supported by local business sponsors and community partners. These cookies ensure you do not see the same sponsor banner repeatedly (frequency capping) and help local merchants measure whether their announcement reached their community. They never track your activity across unrelated third-party websites or build external advertising profiles.",
876
+ required: false,
877
+ cookies: [
878
+ {
879
+ name: "napsite_sponsor_cap",
880
+ provider: "First-Party",
881
+ purpose: "Prevents repetitive display of identical local business announcements.",
882
+ duration: "7 days",
883
+ type: "Local Storage"
884
+ },
885
+ {
886
+ name: "local_sponsor_ref",
887
+ provider: "First-Party",
888
+ purpose: "Attributes community inquiries directly to the sponsoring local merchant.",
889
+ duration: "30 days",
890
+ type: "Local Storage"
891
+ }
892
+ ]
893
+ }
894
+ ];
895
+ var memoryConsent = null;
896
+ function readCookie(name) {
897
+ if (typeof document === "undefined") return null;
898
+ const match = document.cookie.match(new RegExp("(?:^|; )" + name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1") + "=([^;]*)"));
899
+ return match ? decodeURIComponent(match[1]) : null;
900
+ }
901
+ function writeCookie(name, value, maxAgeSeconds) {
902
+ if (typeof document === "undefined") return;
903
+ const secure = typeof location !== "undefined" && location.protocol === "https:" ? "; Secure" : "";
904
+ document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax${secure}`;
905
+ }
906
+ function getCookieConsent() {
907
+ if (typeof window === "undefined") return memoryConsent;
908
+ try {
909
+ const raw = readCookie(COOKIE_CONSENT_KEY) || readCookie(COOKIE_CONSENT_BACKUP_KEY);
910
+ if (raw) {
911
+ const parsed = JSON.parse(raw);
912
+ if (parsed && parsed.version === 1 && parsed.preferences) {
913
+ memoryConsent = parsed;
914
+ return parsed;
915
+ }
916
+ }
917
+ const stored = window.localStorage?.getItem(COOKIE_CONSENT_KEY);
918
+ if (stored) {
919
+ const parsed = JSON.parse(stored);
920
+ if (parsed && parsed.version === 1 && parsed.preferences) {
921
+ memoryConsent = parsed;
922
+ return parsed;
923
+ }
924
+ }
925
+ } catch {
926
+ }
927
+ return memoryConsent;
928
+ }
929
+ function hasConsented() {
930
+ return getCookieConsent() !== null;
931
+ }
932
+ function isCategoryAllowed(category) {
933
+ if (category === "essential") return true;
934
+ const consent = getCookieConsent();
935
+ if (!consent) {
936
+ if (typeof navigator !== "undefined") {
937
+ const nav = navigator;
938
+ if (nav.globalPrivacyControl || nav.doNotTrack === "1") {
939
+ return false;
940
+ }
941
+ }
942
+ return false;
943
+ }
944
+ return Boolean(consent.preferences[category]);
945
+ }
946
+ function setCookieConsent(preferences, source = "settings_save") {
947
+ const mergedPreferences = {
948
+ essential: true,
949
+ performance: Boolean(preferences.performance),
950
+ functional: Boolean(preferences.functional),
951
+ advertising: Boolean(preferences.advertising)
952
+ };
953
+ let anonymous_id;
954
+ if (typeof window !== "undefined") {
955
+ try {
956
+ anonymous_id = window.localStorage?.getItem("napsite_visitor_v1") || void 0;
957
+ } catch {
958
+ }
959
+ }
960
+ const record = {
961
+ version: 1,
962
+ preferences: mergedPreferences,
963
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
964
+ anonymous_id,
965
+ source
966
+ };
967
+ memoryConsent = record;
968
+ const serialized = JSON.stringify(record);
969
+ writeCookie(COOKIE_CONSENT_KEY, serialized, COOKIE_CONSENT_MAX_AGE_SECONDS);
970
+ writeCookie(COOKIE_CONSENT_BACKUP_KEY, serialized, COOKIE_CONSENT_MAX_AGE_SECONDS);
971
+ if (typeof window !== "undefined") {
972
+ try {
973
+ window.localStorage?.setItem(COOKIE_CONSENT_KEY, serialized);
974
+ } catch {
975
+ }
976
+ window.dispatchEvent(new CustomEvent(COOKIE_CONSENT_EVENT, { detail: record }));
977
+ }
978
+ return record;
979
+ }
980
+ function acceptAllCookies() {
981
+ return setCookieConsent(DEFAULT_PREFERENCES_ACCEPTED, "banner_accept");
982
+ }
983
+ function rejectOptionalCookies() {
984
+ return setCookieConsent(DEFAULT_PREFERENCES_REJECTED, "banner_reject");
985
+ }
986
+ function clearCookieConsent() {
987
+ memoryConsent = null;
988
+ if (typeof document !== "undefined") {
989
+ document.cookie = `${COOKIE_CONSENT_KEY}=; Path=/; Max-Age=0; SameSite=Lax`;
990
+ document.cookie = `${COOKIE_CONSENT_BACKUP_KEY}=; Path=/; Max-Age=0; SameSite=Lax`;
991
+ }
992
+ if (typeof window !== "undefined") {
993
+ try {
994
+ window.localStorage?.removeItem(COOKIE_CONSENT_KEY);
995
+ } catch {
996
+ }
997
+ window.dispatchEvent(new CustomEvent(COOKIE_CONSENT_EVENT, { detail: null }));
998
+ }
999
+ }
1000
+ async function recordConsentToCompliance(record, options) {
1001
+ if (typeof window === "undefined") return;
1002
+ const endpoint = options?.endpoint || "/api/compliance/consent";
1003
+ try {
1004
+ const payload = {
1005
+ consent_type: "cookie_policy",
1006
+ granted: record.preferences.performance || record.preferences.advertising,
1007
+ preferences: record.preferences,
1008
+ source: record.source,
1009
+ anonymous_id: record.anonymous_id,
1010
+ timestamp: record.timestamp,
1011
+ url: window.location.href,
1012
+ user_agent: window.navigator.userAgent
1013
+ };
1014
+ if (typeof navigator.sendBeacon === "function") {
1015
+ navigator.sendBeacon(endpoint, new Blob([JSON.stringify(payload)], { type: "application/json" }));
1016
+ } else {
1017
+ await fetch(endpoint, {
1018
+ method: "POST",
1019
+ headers: { "content-type": "application/json" },
1020
+ body: JSON.stringify(payload),
1021
+ keepalive: true
1022
+ });
1023
+ }
1024
+ } catch {
1025
+ }
1026
+ }
1027
+ function CookieConsentBanner({
1028
+ title = "Help Us Improve Our Website with Cookies",
1029
+ text,
1030
+ settingsUrl = "/cookies",
1031
+ className = "",
1032
+ publicationSlug,
1033
+ complianceEndpoint = "/api/compliance/consent"
1034
+ }) {
1035
+ const [mounted, setMounted] = react.useState(false);
1036
+ const [visible, setVisible] = react.useState(false);
1037
+ react.useEffect(() => {
1038
+ setMounted(true);
1039
+ if (!hasConsented()) {
1040
+ const timer = window.setTimeout(() => setVisible(true), 300);
1041
+ return () => window.clearTimeout(timer);
1042
+ }
1043
+ const onConsentChange = () => {
1044
+ if (hasConsented()) {
1045
+ setVisible(false);
1046
+ }
1047
+ };
1048
+ window.addEventListener(COOKIE_CONSENT_EVENT, onConsentChange);
1049
+ return () => window.removeEventListener(COOKIE_CONSENT_EVENT, onConsentChange);
1050
+ }, []);
1051
+ if (!mounted || !visible) return null;
1052
+ const handleAccept = () => {
1053
+ const record = acceptAllCookies();
1054
+ setVisible(false);
1055
+ void recordConsentToCompliance(record, {
1056
+ endpoint: complianceEndpoint});
1057
+ };
1058
+ const handleReject = () => {
1059
+ const record = rejectOptionalCookies();
1060
+ setVisible(false);
1061
+ void recordConsentToCompliance(record, {
1062
+ endpoint: complianceEndpoint});
1063
+ };
1064
+ const defaultText = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1065
+ "We use cookies and process data from your device to analyze website performance, personalize ad content, and improve your experience. Your consent includes data transfers outside of the country you\u2019re located. View",
1066
+ " ",
1067
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" }),
1068
+ " ",
1069
+ "for more information."
1070
+ ] });
1071
+ return /* @__PURE__ */ jsxRuntime.jsx(
1072
+ "aside",
1073
+ {
1074
+ className: `sm-cookie-banner-wrapper ${className}`,
1075
+ role: "region",
1076
+ "aria-label": "Cookie consent",
1077
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-card", children: [
1078
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-content", children: [
1079
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sm-cookie-banner-title", children: title }),
1080
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-cookie-banner-text", children: text ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1081
+ text,
1082
+ " ",
1083
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" })
1084
+ ] }) : defaultText })
1085
+ ] }),
1086
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-actions", children: [
1087
+ /* @__PURE__ */ jsxRuntime.jsx(
1088
+ "button",
1089
+ {
1090
+ type: "button",
1091
+ className: "sm-cookie-btn sm-cookie-btn-primary",
1092
+ onClick: handleAccept,
1093
+ children: "Accept"
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ jsxRuntime.jsx(
1097
+ "button",
1098
+ {
1099
+ type: "button",
1100
+ className: "sm-cookie-btn sm-cookie-btn-secondary",
1101
+ onClick: handleReject,
1102
+ children: "Reject"
1103
+ }
1104
+ )
1105
+ ] })
1106
+ ] })
1107
+ }
1108
+ );
1109
+ }
1110
+ function CookieSettingsView({
1111
+ publicationName = "This Publication",
1112
+ region,
1113
+ complianceEndpoint = "/api/compliance/consent",
1114
+ publicationSlug
1115
+ }) {
1116
+ const [preferences, setPreferences] = react.useState(() => {
1117
+ const existing = getCookieConsent();
1118
+ return existing ? existing.preferences : { ...DEFAULT_PREFERENCES_ACCEPTED };
1119
+ });
1120
+ const [expandedCategories, setExpandedCategories] = react.useState({
1121
+ essential: false,
1122
+ performance: false,
1123
+ functional: false,
1124
+ advertising: false
1125
+ });
1126
+ const [saveSuccess, setSaveSuccess] = react.useState(false);
1127
+ const [lastSavedTime, setLastSavedTime] = react.useState(null);
1128
+ react.useEffect(() => {
1129
+ const existing = getCookieConsent();
1130
+ if (existing) {
1131
+ setPreferences(existing.preferences);
1132
+ setLastSavedTime(new Date(existing.timestamp).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
1133
+ }
1134
+ }, []);
1135
+ const handleToggle = (category) => {
1136
+ if (category === "essential") return;
1137
+ setPreferences((prev) => ({
1138
+ ...prev,
1139
+ [category]: !prev[category]
1140
+ }));
1141
+ setSaveSuccess(false);
1142
+ };
1143
+ const toggleExpand = (category) => {
1144
+ setExpandedCategories((prev) => ({
1145
+ ...prev,
1146
+ [category]: !prev[category]
1147
+ }));
1148
+ };
1149
+ const handleSave = () => {
1150
+ const record = setCookieConsent(preferences, "settings_save");
1151
+ setSaveSuccess(true);
1152
+ setLastSavedTime(new Date(record.timestamp).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
1153
+ void recordConsentToCompliance(record, {
1154
+ endpoint: complianceEndpoint});
1155
+ window.scrollTo({ top: 0, behavior: "smooth" });
1156
+ };
1157
+ const handleAcceptAll = () => {
1158
+ setPreferences({ ...DEFAULT_PREFERENCES_ACCEPTED });
1159
+ const record = setCookieConsent(DEFAULT_PREFERENCES_ACCEPTED, "settings_save");
1160
+ setSaveSuccess(true);
1161
+ setLastSavedTime(new Date(record.timestamp).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
1162
+ void recordConsentToCompliance(record, {
1163
+ endpoint: complianceEndpoint});
1164
+ window.scrollTo({ top: 0, behavior: "smooth" });
1165
+ };
1166
+ const handleRejectAll = () => {
1167
+ setPreferences({ ...DEFAULT_PREFERENCES_REJECTED });
1168
+ const record = setCookieConsent(DEFAULT_PREFERENCES_REJECTED, "settings_save");
1169
+ setSaveSuccess(true);
1170
+ setLastSavedTime(new Date(record.timestamp).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
1171
+ void recordConsentToCompliance(record, {
1172
+ endpoint: complianceEndpoint});
1173
+ window.scrollTo({ top: 0, behavior: "smooth" });
1174
+ };
1175
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-settings-container", children: [
1176
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sm-cookie-settings-header", children: [
1177
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-settings-eyebrow", children: [
1178
+ publicationName,
1179
+ " \xB7 Legal & Privacy"
1180
+ ] }),
1181
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "sm-cookie-settings-title", children: "Cookie Settings & Privacy Controls" }),
1182
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "sm-cookie-settings-intro", children: [
1183
+ "We believe in transparent, neighborly community journalism. When you visit ",
1184
+ publicationName,
1185
+ region ? ` across ${region}` : "",
1186
+ ", we use cookies and process device data to ensure the website is secure, reliable, and easy to read. Review our cookie categories below, view detailed technical disclosures, and choose your preferences at any time."
1187
+ ] })
1188
+ ] }),
1189
+ saveSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-alert-success", role: "status", "aria-live": "polite", children: [
1190
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
1191
+ "path",
1192
+ {
1193
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.707l-4.5 4.5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L8.5 10.586l3.793-3.793a1 1 0 011.414 1.414z",
1194
+ fill: "currentColor"
1195
+ }
1196
+ ) }),
1197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1198
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Preferences Saved." }),
1199
+ " Your cookie choices have been updated",
1200
+ lastSavedTime ? ` (at ${lastSavedTime})` : "",
1201
+ " and will be remembered across all community editions."
1202
+ ] })
1203
+ ] }),
1204
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-global-actions", children: [
1205
+ /* @__PURE__ */ jsxRuntime.jsx(
1206
+ "button",
1207
+ {
1208
+ type: "button",
1209
+ className: "sm-cookie-btn sm-cookie-btn-secondary",
1210
+ onClick: handleRejectAll,
1211
+ children: "Reject Optional Cookies"
1212
+ }
1213
+ ),
1214
+ /* @__PURE__ */ jsxRuntime.jsx(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ className: "sm-cookie-btn sm-cookie-btn-secondary",
1219
+ onClick: handleAcceptAll,
1220
+ children: "Accept All Cookies"
1221
+ }
1222
+ ),
1223
+ /* @__PURE__ */ jsxRuntime.jsx(
1224
+ "button",
1225
+ {
1226
+ type: "button",
1227
+ className: "sm-cookie-btn sm-cookie-btn-primary",
1228
+ onClick: handleSave,
1229
+ children: "Save Preferences"
1230
+ }
1231
+ )
1232
+ ] }),
1233
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-cookie-category-list", children: COOKIE_CATEGORIES.map((category) => {
1234
+ const isEssential = category.id === "essential";
1235
+ const isChecked = isEssential || preferences[category.id];
1236
+ const isExpanded = expandedCategories[category.id];
1237
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1238
+ "section",
1239
+ {
1240
+ className: "sm-cookie-category-card",
1241
+ "aria-labelledby": `cookie-cat-${category.id}`,
1242
+ children: [
1243
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-category-top", children: [
1244
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: `cookie-cat-${category.id}`, className: "sm-cookie-category-heading", children: category.title }),
1245
+ isEssential ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sm-cookie-badge-required", children: "Always Active" }) : /* @__PURE__ */ jsxRuntime.jsxs(
1246
+ "label",
1247
+ {
1248
+ className: "sm-cookie-switch-label",
1249
+ htmlFor: `toggle-${category.id}`,
1250
+ "aria-label": `Toggle ${category.title}`,
1251
+ children: [
1252
+ /* @__PURE__ */ jsxRuntime.jsx(
1253
+ "input",
1254
+ {
1255
+ id: `toggle-${category.id}`,
1256
+ type: "checkbox",
1257
+ className: "sm-cookie-switch-input",
1258
+ checked: isChecked,
1259
+ onChange: () => handleToggle(category.id)
1260
+ }
1261
+ ),
1262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sm-cookie-switch-slider" })
1263
+ ]
1264
+ }
1265
+ )
1266
+ ] }),
1267
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-cookie-category-desc", children: category.fullDescription }),
1268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1269
+ /* @__PURE__ */ jsxRuntime.jsxs(
1270
+ "button",
1271
+ {
1272
+ type: "button",
1273
+ className: "sm-cookie-details-toggle",
1274
+ onClick: () => toggleExpand(category.id),
1275
+ "aria-expanded": isExpanded,
1276
+ children: [
1277
+ /* @__PURE__ */ jsxRuntime.jsx(
1278
+ "svg",
1279
+ {
1280
+ width: "14",
1281
+ height: "14",
1282
+ viewBox: "0 0 20 20",
1283
+ fill: "currentColor",
1284
+ style: {
1285
+ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
1286
+ transition: "transform 0.2s ease"
1287
+ },
1288
+ "aria-hidden": "true",
1289
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1290
+ "path",
1291
+ {
1292
+ fillRule: "evenodd",
1293
+ d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z",
1294
+ clipRule: "evenodd"
1295
+ }
1296
+ )
1297
+ }
1298
+ ),
1299
+ isExpanded ? "Hide cookie disclosures" : `View ${category.cookies.length} cookie disclosures`
1300
+ ]
1301
+ }
1302
+ ),
1303
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-cookie-table-wrapper", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "sm-cookie-table", children: [
1304
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1305
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Cookie Name" }),
1306
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Provider" }),
1307
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Duration" }),
1308
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Storage Type" }),
1309
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Purpose" })
1310
+ ] }) }),
1311
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: category.cookies.map((cookie) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1312
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "sm-cookie-code", children: cookie.name }) }),
1313
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: cookie.provider }),
1314
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: cookie.duration }),
1315
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: cookie.type }),
1316
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: cookie.purpose })
1317
+ ] }, cookie.name)) })
1318
+ ] }) })
1319
+ ] })
1320
+ ]
1321
+ },
1322
+ category.id
1323
+ );
1324
+ }) }),
1325
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-save-bar", children: [
1326
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-cookie-save-status", children: lastSavedTime ? `Last saved: today at ${lastSavedTime}` : "No custom preferences saved yet." }),
1327
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-save-buttons", children: [
1328
+ /* @__PURE__ */ jsxRuntime.jsx(
1329
+ "button",
1330
+ {
1331
+ type: "button",
1332
+ className: "sm-cookie-btn sm-cookie-btn-secondary",
1333
+ onClick: handleRejectAll,
1334
+ children: "Reject Optional"
1335
+ }
1336
+ ),
1337
+ /* @__PURE__ */ jsxRuntime.jsx(
1338
+ "button",
1339
+ {
1340
+ type: "button",
1341
+ className: "sm-cookie-btn sm-cookie-btn-secondary",
1342
+ onClick: handleAcceptAll,
1343
+ children: "Accept All"
1344
+ }
1345
+ ),
1346
+ /* @__PURE__ */ jsxRuntime.jsx(
1347
+ "button",
1348
+ {
1349
+ type: "button",
1350
+ className: "sm-cookie-btn sm-cookie-btn-primary",
1351
+ onClick: handleSave,
1352
+ children: "Save Preferences"
1353
+ }
1354
+ )
1355
+ ] })
1356
+ ] })
1357
+ ] });
1358
+ }
1359
+
1360
+ exports.ANALYTICS_COOKIE = ANALYTICS_COOKIE;
1361
+ exports.ANALYTICS_VERSION = ANALYTICS_VERSION;
1362
+ exports.BROADSHEET_THEME = BROADSHEET_THEME;
1363
+ exports.CLASSIFIED_CATEGORIES = CLASSIFIED_CATEGORIES;
1364
+ exports.COOKIE_CATEGORIES = COOKIE_CATEGORIES;
1365
+ exports.COOKIE_CONSENT_BACKUP_KEY = COOKIE_CONSENT_BACKUP_KEY;
1366
+ exports.COOKIE_CONSENT_EVENT = COOKIE_CONSENT_EVENT;
1367
+ exports.COOKIE_CONSENT_KEY = COOKIE_CONSENT_KEY;
1368
+ exports.COOKIE_CONSENT_MAX_AGE_SECONDS = COOKIE_CONSENT_MAX_AGE_SECONDS;
1369
+ exports.CookieConsentBanner = CookieConsentBanner;
1370
+ exports.CookieSettingsView = CookieSettingsView;
1371
+ exports.DEFAULT_PREFERENCES_ACCEPTED = DEFAULT_PREFERENCES_ACCEPTED;
1372
+ exports.DEFAULT_PREFERENCES_REJECTED = DEFAULT_PREFERENCES_REJECTED;
1373
+ exports.EVENTS = EVENTS;
1374
+ exports.FEATURE_NAMES = FEATURE_NAMES;
1375
+ exports.JOB_EMPLOYMENT_TYPES = JOB_EMPLOYMENT_TYPES;
1376
+ exports.LAYOUT_COOKIE = LAYOUT_COOKIE;
1377
+ exports.MEET_ACTIVITIES = MEET_ACTIVITIES;
1378
+ exports.NEWS_CACHE = NEWS_CACHE;
1379
+ exports.NewsError = NewsError;
1380
+ exports.SESSION_IDLE_MS = SESSION_IDLE_MS;
1381
+ exports.UTM_FIELDS = UTM_FIELDS;
1382
+ exports.UUID = UUID;
1383
+ exports.acceptAllCookies = acceptAllCookies;
1384
+ exports.acceptRemoteRecord = acceptRemoteRecord;
1385
+ exports.acceptSnapshot = acceptSnapshot;
1386
+ exports.activeNearby = activeNearby;
1387
+ exports.activityLabel = activityLabel;
1388
+ exports.articleCacheKey = articleCacheKey;
1389
+ exports.articleCacheOptions = articleCacheOptions;
1390
+ exports.articleMeta = articleMeta;
1391
+ exports.articleUrl = articleUrl;
1392
+ exports.attribution = attribution;
1393
+ exports.briefSummary = briefSummary;
1394
+ exports.canHostMeetup = canHostMeetup;
1395
+ exports.categoryLabel = categoryLabel;
1396
+ exports.classifiedPath = classifiedPath;
1397
+ exports.classifiedSearchPath = classifiedSearchPath;
1398
+ exports.clearCookieConsent = clearCookieConsent;
1399
+ exports.clipChars = clipChars;
1400
+ exports.composeEdition = composeEdition;
1401
+ exports.defaultRevalidateTargets = defaultRevalidateTargets;
1402
+ exports.definePublication = definePublication;
1403
+ exports.displayHeadline = displayHeadline;
1404
+ exports.edgeCacheControl = edgeCacheControl;
1405
+ exports.editorialSections = editorialSections;
1406
+ exports.employmentLabel = employmentLabel;
1407
+ exports.featureEnabled = featureEnabled;
1408
+ exports.feedCacheKey = feedCacheKey;
1409
+ exports.feedCacheOptions = feedCacheOptions;
1410
+ exports.filterNews = filterNews;
1411
+ exports.getCookieConsent = getCookieConsent;
1412
+ exports.hasConsented = hasConsented;
1413
+ exports.inventoryCacheOptions = inventoryCacheOptions;
1414
+ exports.isAdult = isAdult;
1415
+ exports.isCategoryAllowed = isCategoryAllowed;
1416
+ exports.isClassifiedCategory = isClassifiedCategory;
1417
+ exports.isEmploymentType = isEmploymentType;
1418
+ exports.jobEmployerBody = jobEmployerBody;
1419
+ exports.jobOpeningBody = jobOpeningBody;
1420
+ exports.jobPostingBody = jobPostingBody;
1421
+ exports.llmsText = llmsText;
1422
+ exports.matchesStoryTag = matchesStoryTag;
1423
+ exports.nearbyUpdates = nearbyUpdates;
1424
+ exports.newsArticleJsonLd = newsArticleJsonLd;
1425
+ exports.normalizedText = normalizedText;
1426
+ exports.parseSnapshot = parseSnapshot;
1427
+ exports.prepareClassified = prepareClassified;
1428
+ exports.prepareJobPost = prepareJobPost;
1429
+ exports.prepareMeetup = prepareMeetup;
1430
+ exports.publicHttps = publicHttps;
1431
+ exports.publicJobPath = publicJobPath;
1432
+ exports.publicJobsPath = publicJobsPath;
1433
+ exports.publicUrl = publicUrl;
1434
+ exports.publicationFromSnapshot = publicationFromSnapshot;
1435
+ exports.publicationJsonLd = publicationJsonLd;
1436
+ exports.publicationMeta = publicationMeta;
1437
+ exports.publicationRecord = publicationRecord;
1438
+ exports.recordConsentToCompliance = recordConsentToCompliance;
1439
+ exports.referralCode = referralCode;
1440
+ exports.rejectOptionalCookies = rejectOptionalCookies;
1441
+ exports.resolveCoupon = resolveCoupon;
1442
+ exports.resolveLayout = resolveLayout;
1443
+ exports.revalidateDecision = revalidateDecision;
1444
+ exports.screenMessage = screenMessage;
1445
+ exports.secretsMatch = secretsMatch;
1446
+ exports.sectionDefinition = sectionDefinition;
1447
+ exports.sectionId = sectionId;
1448
+ exports.sectionPath = sectionPath;
1449
+ exports.setCookieConsent = setCookieConsent;
1450
+ exports.shortDeck = shortDeck;
1451
+ exports.shortText = shortText;
1452
+ exports.shouldServeLastKnown = shouldServeLastKnown;
1453
+ exports.snapshotFromRecord = snapshotFromRecord;
1454
+ exports.storyDate = storyDate;
1455
+ exports.storyTowns = storyTowns;
1456
+ exports.syncPublication = syncPublication;
1457
+ exports.townSlug = townSlug;
1458
+ exports.validCalendarDate = validCalendarDate;
1459
+ exports.walnutCreekTimes = walnutCreekTimes;
1460
+ exports.withLastKnown = withLastKnown;