@scalemule/news-publication 0.0.62 → 0.0.64

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.
@@ -1,4 +1,4 @@
1
- import { NETWORK_PARENT } from './chunk-6S5FARL3.js';
1
+ import { NETWORK_PARENT } from './chunk-XWXPERLK.js';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
 
4
4
  function NetworkCopyrightNotice({
@@ -512,7 +512,13 @@ var ALLOWED_CARD_TYPES = /* @__PURE__ */ new Set([
512
512
  "ACTIVITY",
513
513
  "EVENT",
514
514
  "ORGANIZATION",
515
- "PRODUCT"
515
+ "PRODUCT",
516
+ "FRAMEWORK",
517
+ "STANDARD",
518
+ "SPECIFICATION",
519
+ "TOPIC",
520
+ "RESOURCE",
521
+ "REFERENCE"
516
522
  ]);
517
523
  function clip(val, maxChars) {
518
524
  if (typeof val !== "string") return void 0;
@@ -554,9 +560,18 @@ function parseRawCards(rawCards) {
554
560
  const id = clip(c.id, 64);
555
561
  const title = clip(c.title, 300);
556
562
  if (!id || !title) continue;
557
- const rawType = typeof c.card_type === "string" ? c.card_type : c.cardType;
558
- const cardType = ALLOWED_CARD_TYPES.has(rawType) ? rawType : "PLACE";
559
- const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 VERIFIED DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 VERIFIED FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 VERIFIED FACTSHEET" : cardType === "ORGANIZATION" ? "COMMUNITY ORGANIZATION \xB7 DIRECTORY" : "VERIFIED FACTSHEET");
563
+ const rawKind = (typeof c.card_type === "string" ? c.card_type : typeof c.cardType === "string" ? c.cardType : "").trim().toUpperCase();
564
+ let cardType;
565
+ if (ALLOWED_CARD_TYPES.has(rawKind)) {
566
+ cardType = rawKind;
567
+ } else if (rawKind === "RESEARCH_FRAMEWORK") {
568
+ cardType = "FRAMEWORK";
569
+ } else if (c.address && c.hours) {
570
+ cardType = "PLACE";
571
+ } else {
572
+ cardType = "REFERENCE";
573
+ }
574
+ const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 FACTSHEET" : cardType === "ORGANIZATION" ? "ORGANIZATION \xB7 DIRECTORY" : cardType === "PRODUCT" ? "PRODUCT \xB7 FACTSHEET" : cardType === "FRAMEWORK" ? "TECHNICAL FRAMEWORK" : cardType === "STANDARD" ? "TECHNICAL STANDARD" : cardType === "SPECIFICATION" ? "SPECIFICATION" : cardType === "TOPIC" ? "TOPIC REFERENCE" : cardType === "RESOURCE" ? "DIGITAL RESOURCE" : "FACTSHEET");
560
575
  const subtitle = clip(c.subtitle, 300);
561
576
  const summary = clip(c.summary, 1e3);
562
577
  const badge = clip(c.badge, 64);
@@ -577,16 +592,23 @@ function parseRawCards(rawCards) {
577
592
  } else {
578
593
  const timeframe = clip(c.timeframe, 200);
579
594
  if (timeframe) facts.push({ label: "Timeframe", value: timeframe });
595
+ const isPhysicalPlace = cardType === "PLACE" || cardType === "EVENT" || cardType === "ACTIVITY";
580
596
  const address = clip(c.address, 300);
581
597
  if (address) {
582
598
  facts.push({
583
- label: "Address",
599
+ label: isPhysicalPlace ? "Address" : "Location",
584
600
  value: address,
585
- href: `https://maps.google.com/?q=${encodeURIComponent(address)}`
601
+ href: isPhysicalPlace ? `https://maps.google.com/?q=${encodeURIComponent(address)}` : void 0
586
602
  });
587
603
  }
588
604
  const hours = clip(c.hours, 300);
589
- if (hours) facts.push({ label: "Hours", value: hours });
605
+ if (hours) {
606
+ facts.push({ label: isPhysicalPlace ? "Hours" : "Details", value: hours });
607
+ }
608
+ const website = clip(c.website || c.url, 500);
609
+ if (website && isSafeActionUrl(website)) {
610
+ facts.push({ label: "Website", value: website.replace(/^https?:\/\//, ""), href: website });
611
+ }
590
612
  const phone = clip(c.phone, 80);
591
613
  if (phone) {
592
614
  facts.push({
@@ -634,8 +656,8 @@ function parseRawCards(rawCards) {
634
656
  };
635
657
  }
636
658
  }
637
- const verificationNote = clip(c.verification_note || c.verificationNote, 300) || "Verified by publication reporting staff";
638
- const verifiedAt = clip(c.verified_at || c.verifiedAt, 100) || "October 2026";
659
+ const verificationNote = clip(c.verification_note || c.verificationNote, 300);
660
+ const verifiedAt = clip(c.verified_at || c.verifiedAt, 100);
639
661
  let rating = void 0;
640
662
  if (typeof c.rating === "number" || typeof c.rating === "string") {
641
663
  rating = c.rating;
@@ -713,12 +735,12 @@ function VerifiedCardComponent({
713
735
  },
714
736
  i
715
737
  )) }),
716
- /* @__PURE__ */ jsxs("footer", { className: "article-verified-card__footer", children: [
717
- /* @__PURE__ */ jsxs("div", { className: "article-verified-card__seal", children: [
738
+ (card.verificationNote || card.verifiedAt) && /* @__PURE__ */ jsxs("footer", { className: "article-verified-card__footer", children: [
739
+ card.verificationNote && /* @__PURE__ */ jsxs("div", { className: "article-verified-card__seal", children: [
718
740
  /* @__PURE__ */ jsx("span", { className: "article-verified-card__seal-check", "aria-hidden": "true", children: "\u2713" }),
719
741
  /* @__PURE__ */ jsx("span", { children: card.verificationNote })
720
742
  ] }),
721
- /* @__PURE__ */ jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
743
+ card.verifiedAt && /* @__PURE__ */ jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
722
744
  ] })
723
745
  ]
724
746
  }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-6S5FARL3.js';
2
+ import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-XWXPERLK.js';
3
3
  import { useState, useId, useRef, useEffect } from 'react';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
 
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { getCookieConsent, DEFAULT_PREFERENCES_ACCEPTED, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_REJECTED } from './chunk-6S5FARL3.js';
2
+ import { effectivePreferences, getCookieConsent, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED } from './chunk-XWXPERLK.js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
@@ -10,8 +10,7 @@ function CookieSettingsView({
10
10
  publicationSlug
11
11
  }) {
12
12
  const [preferences, setPreferences] = useState(() => {
13
- const existing = getCookieConsent();
14
- return existing ? existing.preferences : { ...DEFAULT_PREFERENCES_ACCEPTED };
13
+ return { ...effectivePreferences() };
15
14
  });
16
15
  const [expandedCategories, setExpandedCategories] = useState({
17
16
  essential: false,
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { hasConsented, rejectOptionalCookies, DEFAULT_CONSENT_HUB_URL, isGpcActive, COOKIE_CONSENT_EVENT, acceptAllCookies, recordConsentToCompliance } from './chunk-6S5FARL3.js';
2
+ import { hasConsented, rejectOptionalCookies, DEFAULT_CONSENT_HUB_URL, consentModel, isGpcActive, COOKIE_CONSENT_EVENT, acceptAllCookies, recordConsentToCompliance } from './chunk-XWXPERLK.js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
@@ -18,6 +18,12 @@ function CookieConsentBanner({
18
18
  const [visible, setVisible] = useState(false);
19
19
  useEffect(() => {
20
20
  setMounted(true);
21
+ if (countryCode && /^[A-Za-z]{2}$/.test(countryCode)) {
22
+ try {
23
+ document.cookie = `sm_geo_country=${countryCode.toUpperCase()}; Path=/; SameSite=Lax`;
24
+ } catch {
25
+ }
26
+ }
21
27
  const url = new URL(window.location.href);
22
28
  if (url.searchParams.has("sm_consent")) {
23
29
  if (url.searchParams.get("sm_consent") === "0" && !hasConsented())
@@ -25,11 +31,12 @@ function CookieConsentBanner({
25
31
  url.searchParams.delete("sm_consent");
26
32
  window.history.replaceState(window.history.state, "", url);
27
33
  }
28
- setVisible(!hasConsented() && !isGpcActive());
34
+ const asks = forceShow || consentModel(countryCode) === "opt-in";
35
+ setVisible(asks && !hasConsented() && !isGpcActive());
29
36
  }, [countryCode, forceShow, consentHubUrl]);
30
37
  useEffect(() => {
31
38
  const onConsentChange = () => {
32
- setVisible(!hasConsented() && !isGpcActive());
39
+ setVisible((visible2) => visible2 && !hasConsented() && !isGpcActive());
33
40
  };
34
41
  window.addEventListener(COOKIE_CONSENT_EVENT, onConsentChange);
35
42
  return () => window.removeEventListener(COOKIE_CONSENT_EVENT, onConsentChange);
@@ -52,7 +59,7 @@ function CookieConsentBanner({
52
59
  });
53
60
  };
54
61
  const defaultText = /* @__PURE__ */ jsxs(Fragment, { children: [
55
- "With your permission, we measure reading activity and sponsor acknowledgments. You can read the publication with optional cookies turned off. View",
62
+ "We count page views without cookies or identifiers. With your permission, first-party cookies also let us measure reading sessions and sponsor acknowledgments. You can read the publication with optional cookies turned off. View",
56
63
  " ",
57
64
  /* @__PURE__ */ jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" }),
58
65
  " ",
@@ -1,5 +1,5 @@
1
1
  import { recordConsentToCompliance as recordConsentToCompliance$1, getCookieConsent, decorateNetworkUrl } from '@scalemule/compliance';
2
- export { 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, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, isGpcActive, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
2
+ export { 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, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, consentModel, decorateNetworkUrl, effectivePreferences, getCookieConsent, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, isGpcActive, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
3
3
 
4
4
  // src/cookies.ts
5
5
  async function recordConsentToCompliance(record, options) {
@@ -61,7 +61,7 @@ var COOKIE_CATEGORIES = [
61
61
  id: "performance",
62
62
  title: "Performance & Analytics Cookies",
63
63
  shortDescription: "Help us measure reader engagement, story completion, and site reliability.",
64
- fullDescription: "With your permission, first-party identifiers support page-visit and reading measurements. Aggregate reports help us improve the publication. Measurements are incomplete when readers decline consent and are not a count of unique people.",
64
+ fullDescription: "We always count page views and reading activity without cookies or any identifier stored on your device: the server derives a daily-rotating, non-reversible hash that cannot be linked back to you, and your IP address is truncated before it is stored. With these cookies turned on, first-party identifiers additionally let us recognise a return visit and measure sessions. In the United States and most other regions they are on by default and you can turn them off here; in the EU, EEA, UK and Switzerland they stay off until you accept them.",
65
65
  required: false,
66
66
  cookies: [
67
67
  {
@@ -157,6 +157,12 @@ function CookieConsentBanner({
157
157
  const [visible, setVisible] = react.useState(false);
158
158
  react.useEffect(() => {
159
159
  setMounted(true);
160
+ if (countryCode && /^[A-Za-z]{2}$/.test(countryCode)) {
161
+ try {
162
+ document.cookie = `sm_geo_country=${countryCode.toUpperCase()}; Path=/; SameSite=Lax`;
163
+ } catch {
164
+ }
165
+ }
160
166
  const url = new URL(window.location.href);
161
167
  if (url.searchParams.has("sm_consent")) {
162
168
  if (url.searchParams.get("sm_consent") === "0" && !compliance.hasConsented())
@@ -164,11 +170,12 @@ function CookieConsentBanner({
164
170
  url.searchParams.delete("sm_consent");
165
171
  window.history.replaceState(window.history.state, "", url);
166
172
  }
167
- setVisible(!compliance.hasConsented() && !compliance.isGpcActive());
173
+ const asks = forceShow || compliance.consentModel(countryCode) === "opt-in";
174
+ setVisible(asks && !compliance.hasConsented() && !compliance.isGpcActive());
168
175
  }, [countryCode, forceShow, consentHubUrl]);
169
176
  react.useEffect(() => {
170
177
  const onConsentChange = () => {
171
- setVisible(!compliance.hasConsented() && !compliance.isGpcActive());
178
+ setVisible((visible2) => visible2 && !compliance.hasConsented() && !compliance.isGpcActive());
172
179
  };
173
180
  window.addEventListener(compliance.COOKIE_CONSENT_EVENT, onConsentChange);
174
181
  return () => window.removeEventListener(compliance.COOKIE_CONSENT_EVENT, onConsentChange);
@@ -191,7 +198,7 @@ function CookieConsentBanner({
191
198
  });
192
199
  };
193
200
  const defaultText = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
194
- "With your permission, we measure reading activity and sponsor acknowledgments. You can read the publication with optional cookies turned off. View",
201
+ "We count page views without cookies or identifiers. With your permission, first-party cookies also let us measure reading sessions and sponsor acknowledgments. You can read the publication with optional cookies turned off. View",
195
202
  " ",
196
203
  /* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" }),
197
204
  " ",
@@ -15,7 +15,7 @@ interface CookieConsentBannerProps {
15
15
  complianceEndpoint?: string;
16
16
  /** Explicit ISO 3166-1 alpha-2 country code (e.g. from server headers or GeoIP) */
17
17
  countryCode?: string | null;
18
- /** Force display of banner regardless of visitor geography */
18
+ /** Force display of banner regardless of visitor geography (opt-out regions show no banner by default) */
19
19
  forceShow?: boolean;
20
20
  /** URL of the central network consent iframe bridge. Default: "https://bayareachronicle.com/consent-bridge.html" */
21
21
  consentHubUrl?: string;
@@ -15,7 +15,7 @@ interface CookieConsentBannerProps {
15
15
  complianceEndpoint?: string;
16
16
  /** Explicit ISO 3166-1 alpha-2 country code (e.g. from server headers or GeoIP) */
17
17
  countryCode?: string | null;
18
- /** Force display of banner regardless of visitor geography */
18
+ /** Force display of banner regardless of visitor geography (opt-out regions show no banner by default) */
19
19
  forceShow?: boolean;
20
20
  /** URL of the central network consent iframe bridge. Default: "https://bayareachronicle.com/consent-bridge.html" */
21
21
  consentHubUrl?: string;
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- export { CookieConsentBanner } from './chunk-XPOZGZTM.js';
3
- import './chunk-6S5FARL3.js';
2
+ export { CookieConsentBanner } from './chunk-XSAVJJVW.js';
3
+ import './chunk-XWXPERLK.js';
@@ -170,7 +170,7 @@ var COOKIE_CATEGORIES = [
170
170
  id: "performance",
171
171
  title: "Performance & Analytics Cookies",
172
172
  shortDescription: "Help us measure reader engagement, story completion, and site reliability.",
173
- fullDescription: "With your permission, first-party identifiers support page-visit and reading measurements. Aggregate reports help us improve the publication. Measurements are incomplete when readers decline consent and are not a count of unique people.",
173
+ fullDescription: "We always count page views and reading activity without cookies or any identifier stored on your device: the server derives a daily-rotating, non-reversible hash that cannot be linked back to you, and your IP address is truncated before it is stored. With these cookies turned on, first-party identifiers additionally let us recognise a return visit and measure sessions. In the United States and most other regions they are on by default and you can turn them off here; in the EU, EEA, UK and Switzerland they stay off until you accept them.",
174
174
  required: false,
175
175
  cookies: [
176
176
  {
@@ -242,8 +242,7 @@ function CookieSettingsView({
242
242
  publicationSlug
243
243
  }) {
244
244
  const [preferences, setPreferences] = react.useState(() => {
245
- const existing = compliance.getCookieConsent();
246
- return existing ? existing.preferences : { ...compliance.DEFAULT_PREFERENCES_ACCEPTED };
245
+ return { ...compliance.effectivePreferences() };
247
246
  });
248
247
  const [expandedCategories, setExpandedCategories] = react.useState({
249
248
  essential: false,
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- export { CookieSettingsView } from './chunk-GYYUJYI4.js';
3
- import './chunk-6S5FARL3.js';
2
+ export { CookieSettingsView } from './chunk-X7AU7DV6.js';
3
+ import './chunk-XWXPERLK.js';
package/dist/index.cjs CHANGED
@@ -303,6 +303,7 @@ function newsArticleJsonLd(publication, article, canonical, authorName) {
303
303
  headline: article.title,
304
304
  description: article.excerpt ?? void 0,
305
305
  datePublished: article.published_at,
306
+ ...article.metadata?.editorial?.modified_at ? { dateModified: article.metadata.editorial.modified_at } : {},
306
307
  // updated_at also changes for media/operational maintenance. A trusted
307
308
  // editorial modification timestamp is required before emitting dateModified.
308
309
  image: image ? [image] : void 0,
@@ -1526,7 +1527,7 @@ var COOKIE_CATEGORIES = [
1526
1527
  id: "performance",
1527
1528
  title: "Performance & Analytics Cookies",
1528
1529
  shortDescription: "Help us measure reader engagement, story completion, and site reliability.",
1529
- fullDescription: "With your permission, first-party identifiers support page-visit and reading measurements. Aggregate reports help us improve the publication. Measurements are incomplete when readers decline consent and are not a count of unique people.",
1530
+ fullDescription: "We always count page views and reading activity without cookies or any identifier stored on your device: the server derives a daily-rotating, non-reversible hash that cannot be linked back to you, and your IP address is truncated before it is stored. With these cookies turned on, first-party identifiers additionally let us recognise a return visit and measure sessions. In the United States and most other regions they are on by default and you can turn them off here; in the EU, EEA, UK and Switzerland they stay off until you accept them.",
1530
1531
  required: false,
1531
1532
  cookies: [
1532
1533
  {
@@ -1606,6 +1607,12 @@ function CookieConsentBanner({
1606
1607
  const [visible, setVisible] = react.useState(false);
1607
1608
  react.useEffect(() => {
1608
1609
  setMounted(true);
1610
+ if (countryCode && /^[A-Za-z]{2}$/.test(countryCode)) {
1611
+ try {
1612
+ document.cookie = `sm_geo_country=${countryCode.toUpperCase()}; Path=/; SameSite=Lax`;
1613
+ } catch {
1614
+ }
1615
+ }
1609
1616
  const url = new URL(window.location.href);
1610
1617
  if (url.searchParams.has("sm_consent")) {
1611
1618
  if (url.searchParams.get("sm_consent") === "0" && !compliance.hasConsented())
@@ -1613,11 +1620,12 @@ function CookieConsentBanner({
1613
1620
  url.searchParams.delete("sm_consent");
1614
1621
  window.history.replaceState(window.history.state, "", url);
1615
1622
  }
1616
- setVisible(!compliance.hasConsented() && !compliance.isGpcActive());
1623
+ const asks = forceShow || compliance.consentModel(countryCode) === "opt-in";
1624
+ setVisible(asks && !compliance.hasConsented() && !compliance.isGpcActive());
1617
1625
  }, [countryCode, forceShow, consentHubUrl]);
1618
1626
  react.useEffect(() => {
1619
1627
  const onConsentChange = () => {
1620
- setVisible(!compliance.hasConsented() && !compliance.isGpcActive());
1628
+ setVisible((visible2) => visible2 && !compliance.hasConsented() && !compliance.isGpcActive());
1621
1629
  };
1622
1630
  window.addEventListener(compliance.COOKIE_CONSENT_EVENT, onConsentChange);
1623
1631
  return () => window.removeEventListener(compliance.COOKIE_CONSENT_EVENT, onConsentChange);
@@ -1640,7 +1648,7 @@ function CookieConsentBanner({
1640
1648
  });
1641
1649
  };
1642
1650
  const defaultText = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1643
- "With your permission, we measure reading activity and sponsor acknowledgments. You can read the publication with optional cookies turned off. View",
1651
+ "We count page views without cookies or identifiers. With your permission, first-party cookies also let us measure reading sessions and sponsor acknowledgments. You can read the publication with optional cookies turned off. View",
1644
1652
  " ",
1645
1653
  /* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" }),
1646
1654
  " ",
@@ -1692,8 +1700,7 @@ function CookieSettingsView({
1692
1700
  publicationSlug
1693
1701
  }) {
1694
1702
  const [preferences, setPreferences] = react.useState(() => {
1695
- const existing = compliance.getCookieConsent();
1696
- return existing ? existing.preferences : { ...compliance.DEFAULT_PREFERENCES_ACCEPTED };
1703
+ return { ...compliance.effectivePreferences() };
1697
1704
  });
1698
1705
  const [expandedCategories, setExpandedCategories] = react.useState({
1699
1706
  essential: false,
@@ -2939,7 +2946,13 @@ var ALLOWED_CARD_TYPES = /* @__PURE__ */ new Set([
2939
2946
  "ACTIVITY",
2940
2947
  "EVENT",
2941
2948
  "ORGANIZATION",
2942
- "PRODUCT"
2949
+ "PRODUCT",
2950
+ "FRAMEWORK",
2951
+ "STANDARD",
2952
+ "SPECIFICATION",
2953
+ "TOPIC",
2954
+ "RESOURCE",
2955
+ "REFERENCE"
2943
2956
  ]);
2944
2957
  function clip(val, maxChars) {
2945
2958
  if (typeof val !== "string") return void 0;
@@ -2981,9 +2994,18 @@ function parseRawCards(rawCards) {
2981
2994
  const id = clip(c.id, 64);
2982
2995
  const title = clip(c.title, 300);
2983
2996
  if (!id || !title) continue;
2984
- const rawType = typeof c.card_type === "string" ? c.card_type : c.cardType;
2985
- const cardType = ALLOWED_CARD_TYPES.has(rawType) ? rawType : "PLACE";
2986
- const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 VERIFIED DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 VERIFIED FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 VERIFIED FACTSHEET" : cardType === "ORGANIZATION" ? "COMMUNITY ORGANIZATION \xB7 DIRECTORY" : "VERIFIED FACTSHEET");
2997
+ const rawKind = (typeof c.card_type === "string" ? c.card_type : typeof c.cardType === "string" ? c.cardType : "").trim().toUpperCase();
2998
+ let cardType;
2999
+ if (ALLOWED_CARD_TYPES.has(rawKind)) {
3000
+ cardType = rawKind;
3001
+ } else if (rawKind === "RESEARCH_FRAMEWORK") {
3002
+ cardType = "FRAMEWORK";
3003
+ } else if (c.address && c.hours) {
3004
+ cardType = "PLACE";
3005
+ } else {
3006
+ cardType = "REFERENCE";
3007
+ }
3008
+ const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 FACTSHEET" : cardType === "ORGANIZATION" ? "ORGANIZATION \xB7 DIRECTORY" : cardType === "PRODUCT" ? "PRODUCT \xB7 FACTSHEET" : cardType === "FRAMEWORK" ? "TECHNICAL FRAMEWORK" : cardType === "STANDARD" ? "TECHNICAL STANDARD" : cardType === "SPECIFICATION" ? "SPECIFICATION" : cardType === "TOPIC" ? "TOPIC REFERENCE" : cardType === "RESOURCE" ? "DIGITAL RESOURCE" : "FACTSHEET");
2987
3009
  const subtitle = clip(c.subtitle, 300);
2988
3010
  const summary = clip(c.summary, 1e3);
2989
3011
  const badge = clip(c.badge, 64);
@@ -3004,16 +3026,23 @@ function parseRawCards(rawCards) {
3004
3026
  } else {
3005
3027
  const timeframe = clip(c.timeframe, 200);
3006
3028
  if (timeframe) facts.push({ label: "Timeframe", value: timeframe });
3029
+ const isPhysicalPlace = cardType === "PLACE" || cardType === "EVENT" || cardType === "ACTIVITY";
3007
3030
  const address = clip(c.address, 300);
3008
3031
  if (address) {
3009
3032
  facts.push({
3010
- label: "Address",
3033
+ label: isPhysicalPlace ? "Address" : "Location",
3011
3034
  value: address,
3012
- href: `https://maps.google.com/?q=${encodeURIComponent(address)}`
3035
+ href: isPhysicalPlace ? `https://maps.google.com/?q=${encodeURIComponent(address)}` : void 0
3013
3036
  });
3014
3037
  }
3015
3038
  const hours = clip(c.hours, 300);
3016
- if (hours) facts.push({ label: "Hours", value: hours });
3039
+ if (hours) {
3040
+ facts.push({ label: isPhysicalPlace ? "Hours" : "Details", value: hours });
3041
+ }
3042
+ const website = clip(c.website || c.url, 500);
3043
+ if (website && isSafeActionUrl(website)) {
3044
+ facts.push({ label: "Website", value: website.replace(/^https?:\/\//, ""), href: website });
3045
+ }
3017
3046
  const phone = clip(c.phone, 80);
3018
3047
  if (phone) {
3019
3048
  facts.push({
@@ -3061,8 +3090,8 @@ function parseRawCards(rawCards) {
3061
3090
  };
3062
3091
  }
3063
3092
  }
3064
- const verificationNote = clip(c.verification_note || c.verificationNote, 300) || "Verified by publication reporting staff";
3065
- const verifiedAt = clip(c.verified_at || c.verifiedAt, 100) || "October 2026";
3093
+ const verificationNote = clip(c.verification_note || c.verificationNote, 300);
3094
+ const verifiedAt = clip(c.verified_at || c.verifiedAt, 100);
3066
3095
  let rating = void 0;
3067
3096
  if (typeof c.rating === "number" || typeof c.rating === "string") {
3068
3097
  rating = c.rating;
@@ -3140,12 +3169,12 @@ function VerifiedCardComponent({
3140
3169
  },
3141
3170
  i
3142
3171
  )) }),
3143
- /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "article-verified-card__footer", children: [
3144
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__seal", children: [
3172
+ (card.verificationNote || card.verifiedAt) && /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "article-verified-card__footer", children: [
3173
+ card.verificationNote && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__seal", children: [
3145
3174
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__seal-check", "aria-hidden": "true", children: "\u2713" }),
3146
3175
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: card.verificationNote })
3147
3176
  ] }),
3148
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
3177
+ card.verifiedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
3149
3178
  ] })
3150
3179
  ]
3151
3180
  }
@@ -3575,10 +3604,18 @@ Object.defineProperty(exports, "clearCookieConsent", {
3575
3604
  enumerable: true,
3576
3605
  get: function () { return compliance.clearCookieConsent; }
3577
3606
  });
3607
+ Object.defineProperty(exports, "consentModel", {
3608
+ enumerable: true,
3609
+ get: function () { return compliance.consentModel; }
3610
+ });
3578
3611
  Object.defineProperty(exports, "decorateNetworkUrl", {
3579
3612
  enumerable: true,
3580
3613
  get: function () { return compliance.decorateNetworkUrl; }
3581
3614
  });
3615
+ Object.defineProperty(exports, "effectivePreferences", {
3616
+ enumerable: true,
3617
+ get: function () { return compliance.effectivePreferences; }
3618
+ });
3582
3619
  Object.defineProperty(exports, "getCookieConsent", {
3583
3620
  enumerable: true,
3584
3621
  get: function () { return compliance.getCookieConsent; }
@@ -3587,6 +3624,10 @@ Object.defineProperty(exports, "hasConsented", {
3587
3624
  enumerable: true,
3588
3625
  get: function () { return compliance.hasConsented; }
3589
3626
  });
3627
+ Object.defineProperty(exports, "impliedPreferences", {
3628
+ enumerable: true,
3629
+ get: function () { return compliance.impliedPreferences; }
3630
+ });
3590
3631
  Object.defineProperty(exports, "isCategoryAllowed", {
3591
3632
  enumerable: true,
3592
3633
  get: function () { return compliance.isCategoryAllowed; }
package/dist/index.d.cts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-BJFCWWi0.cjs';
2
- export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-BJFCWWi0.cjs';
1
+ import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-DBi49N8m.cjs';
2
+ export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-DBi49N8m.cjs';
3
3
  export { M as MEET_ACTIVITIES, b as MeetActivity, c as MeetupInput, a as PreparedMeetup, P as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-CO9Unz5M.cjs';
4
4
  export { b as CLASSIFIED_CATEGORIES, c as ClassifiedCategory, C as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, d as PreparedClassified, e as PreparedJob, P as PublicDeal, f as PublicDealOffer, a as PublicJob, g as categoryLabel, h as classifiedPath, i as classifiedSearchPath, j as clipChars, k as dealDollars, l as dealGoPath, m as dealsFeedPath, n as employmentLabel, o as isClassifiedCategory, p as isEmploymentType, q as jobEmployerBody, r as jobOpeningBody, s as jobPostingBody, t as prepareClassified, u as prepareJobPost, v as publicJobPath, w as publicJobsPath } from './marketplace-29i5w4LS.cjs';
5
- export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-Dx4Lv-lB.cjs';
5
+ export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-qj4BeNzB.cjs';
6
6
  import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
7
- export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, CookieCategory, CookieCategoryDefinition, CookieConsentRecord, CookieDisclosure, CookiePreferences, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
7
+ export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, CookieCategory, CookieCategoryDefinition, CookieConsentRecord, CookieDisclosure, CookiePreferences, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, consentModel, decorateNetworkUrl, effectivePreferences, getCookieConsent, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
8
8
  export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.cjs';
9
9
  export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-view.cjs';
10
10
  export { ThemeToggle, ThemeToggleProps } from './theme-toggle.cjs';
@@ -397,14 +397,15 @@ declare function newsArticleJsonLd(publication: NewsPublication, article: NewsAr
397
397
  url: string;
398
398
  };
399
399
  isBasedOn?: string | undefined;
400
+ image: string[] | undefined;
401
+ mainEntityOfPage: string;
402
+ url: string;
403
+ dateModified?: string | undefined;
400
404
  "@context": string;
401
405
  "@type": string;
402
406
  headline: string;
403
407
  description: string | undefined;
404
408
  datePublished: string;
405
- image: string[] | undefined;
406
- mainEntityOfPage: string;
407
- url: string;
408
409
  };
409
410
  declare function publicationJsonLd(publication: NewsPublication, canonical: string): {
410
411
  "@context": string;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-BJFCWWi0.js';
2
- export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-BJFCWWi0.js';
1
+ import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-DBi49N8m.js';
2
+ export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-DBi49N8m.js';
3
3
  export { M as MEET_ACTIVITIES, b as MeetActivity, c as MeetupInput, a as PreparedMeetup, P as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-BWpxY20X.js';
4
4
  export { b as CLASSIFIED_CATEGORIES, c as ClassifiedCategory, C as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, d as PreparedClassified, e as PreparedJob, P as PublicDeal, f as PublicDealOffer, a as PublicJob, g as categoryLabel, h as classifiedPath, i as classifiedSearchPath, j as clipChars, k as dealDollars, l as dealGoPath, m as dealsFeedPath, n as employmentLabel, o as isClassifiedCategory, p as isEmploymentType, q as jobEmployerBody, r as jobOpeningBody, s as jobPostingBody, t as prepareClassified, u as prepareJobPost, v as publicJobPath, w as publicJobsPath } from './marketplace-29i5w4LS.js';
5
- export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-DmRw8G_B.js';
5
+ export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-CjP7bSXJ.js';
6
6
  import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
7
- export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, CookieCategory, CookieCategoryDefinition, CookieConsentRecord, CookieDisclosure, CookiePreferences, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
7
+ export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, CookieCategory, CookieCategoryDefinition, CookieConsentRecord, CookieDisclosure, CookiePreferences, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, consentModel, decorateNetworkUrl, effectivePreferences, getCookieConsent, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
8
8
  export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.js';
9
9
  export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-view.js';
10
10
  export { ThemeToggle, ThemeToggleProps } from './theme-toggle.js';
@@ -397,14 +397,15 @@ declare function newsArticleJsonLd(publication: NewsPublication, article: NewsAr
397
397
  url: string;
398
398
  };
399
399
  isBasedOn?: string | undefined;
400
+ image: string[] | undefined;
401
+ mainEntityOfPage: string;
402
+ url: string;
403
+ dateModified?: string | undefined;
400
404
  "@context": string;
401
405
  "@type": string;
402
406
  headline: string;
403
407
  description: string | undefined;
404
408
  datePublished: string;
405
- image: string[] | undefined;
406
- mainEntityOfPage: string;
407
- url: string;
408
409
  };
409
410
  declare function publicationJsonLd(publication: NewsPublication, canonical: string): {
410
411
  "@context": string;
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- export { CURATED_COMMUNITY_CARDS, VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-FMCHQI7K.js';
2
- export { CookieConsentBanner } from './chunk-XPOZGZTM.js';
3
- export { CookieSettingsView } from './chunk-GYYUJYI4.js';
1
+ export { CURATED_COMMUNITY_CARDS, VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-KYUVXEJY.js';
2
+ export { CookieConsentBanner } from './chunk-XSAVJJVW.js';
3
+ export { CookieSettingsView } from './chunk-X7AU7DV6.js';
4
4
  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-F53CPZQO.js';
6
- export { NetworkCopyrightNotice } from './chunk-DM4KQNJH.js';
7
- import { localPublications } from './chunk-6S5FARL3.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, topicalPublications } from './chunk-6S5FARL3.js';
5
+ export { LocalNetworkBar } from './chunk-Q77T2S7B.js';
6
+ export { NetworkCopyrightNotice } from './chunk-FWEYKWKP.js';
7
+ import { localPublications } from './chunk-XWXPERLK.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, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-XWXPERLK.js';
9
9
  export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
10
10
  export { formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-OISLCYER.js';
11
11
  import { definePublication } from './chunk-57YVL6BD.js';
@@ -246,6 +246,7 @@ function newsArticleJsonLd(publication, article, canonical, authorName) {
246
246
  headline: article.title,
247
247
  description: article.excerpt ?? void 0,
248
248
  datePublished: article.published_at,
249
+ ...article.metadata?.editorial?.modified_at ? { dateModified: article.metadata.editorial.modified_at } : {},
249
250
  // updated_at also changes for media/operational maintenance. A trusted
250
251
  // editorial modification timestamp is required before emitting dateModified.
251
252
  image: image ? [image] : void 0,
@@ -1,2 +1,2 @@
1
- export { LocalNetworkBar } from './chunk-F53CPZQO.js';
2
- import './chunk-6S5FARL3.js';
1
+ export { LocalNetworkBar } from './chunk-Q77T2S7B.js';
2
+ import './chunk-XWXPERLK.js';
@@ -1,2 +1,2 @@
1
- export { NetworkCopyrightNotice } from './chunk-DM4KQNJH.js';
2
- import './chunk-6S5FARL3.js';
1
+ export { NetworkCopyrightNotice } from './chunk-FWEYKWKP.js';
2
+ import './chunk-XWXPERLK.js';
package/dist/network.js CHANGED
@@ -1 +1 @@
1
- export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications } from './chunk-6S5FARL3.js';
1
+ export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications } from './chunk-XWXPERLK.js';
@@ -1,4 +1,4 @@
1
- import { P as PublicationInput, N as NewsPublication } from './types-BJFCWWi0.js';
1
+ import { P as PublicationInput, N as NewsPublication } from './types-DBi49N8m.js';
2
2
 
3
3
  /**
4
4
  * Serializable application config. Code hooks (`matchTag`, `coupons.resolve`)
@@ -1,5 +1,5 @@
1
- import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-Dx4Lv-lB.cjs';
2
- import './types-BJFCWWi0.cjs';
1
+ import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-qj4BeNzB.cjs';
2
+ import './types-DBi49N8m.cjs';
3
3
 
4
4
  declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-DmRw8G_B.js';
2
- import './types-BJFCWWi0.js';
1
+ import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-CjP7bSXJ.js';
2
+ import './types-DBi49N8m.js';
3
3
 
4
4
  declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { P as PublicationInput, N as NewsPublication } from './types-BJFCWWi0.cjs';
1
+ import { P as PublicationInput, N as NewsPublication } from './types-DBi49N8m.cjs';
2
2
 
3
3
  /**
4
4
  * Serializable application config. Code hooks (`matchTag`, `coupons.resolve`)
@@ -134,7 +134,7 @@ type ArticleCitation = {
134
134
  title?: string;
135
135
  publisher?: string;
136
136
  };
137
- type CardType = "PLACE" | "ACTIVITY" | "EVENT" | "ORGANIZATION" | "PRODUCT";
137
+ type CardType = "PLACE" | "ACTIVITY" | "EVENT" | "ORGANIZATION" | "PRODUCT" | "FRAMEWORK" | "STANDARD" | "SPECIFICATION" | "TOPIC" | "RESOURCE" | "REFERENCE";
138
138
  type VerifiedCardAction = {
139
139
  label: string;
140
140
  url: string;
@@ -167,8 +167,8 @@ type VerifiedCard = {
167
167
  badge?: string;
168
168
  facts: VerifiedCardFact[];
169
169
  actions: VerifiedCardAction[];
170
- verificationNote: string;
171
- verifiedAt: string;
170
+ verificationNote?: string;
171
+ verifiedAt?: string;
172
172
  image?: VerifiedCardImage;
173
173
  rating?: number | string;
174
174
  };
@@ -205,6 +205,15 @@ type NewsArticle = NewsItem & {
205
205
  attribution: string;
206
206
  ai_generated: boolean;
207
207
  }[];
208
+ editorial?: {
209
+ modified_at?: string;
210
+ notices?: {
211
+ id?: string;
212
+ kind: string;
213
+ published_at?: string;
214
+ text: string;
215
+ }[];
216
+ };
208
217
  cards?: VerifiedCard[] | unknown[];
209
218
  };
210
219
  };
@@ -134,7 +134,7 @@ type ArticleCitation = {
134
134
  title?: string;
135
135
  publisher?: string;
136
136
  };
137
- type CardType = "PLACE" | "ACTIVITY" | "EVENT" | "ORGANIZATION" | "PRODUCT";
137
+ type CardType = "PLACE" | "ACTIVITY" | "EVENT" | "ORGANIZATION" | "PRODUCT" | "FRAMEWORK" | "STANDARD" | "SPECIFICATION" | "TOPIC" | "RESOURCE" | "REFERENCE";
138
138
  type VerifiedCardAction = {
139
139
  label: string;
140
140
  url: string;
@@ -167,8 +167,8 @@ type VerifiedCard = {
167
167
  badge?: string;
168
168
  facts: VerifiedCardFact[];
169
169
  actions: VerifiedCardAction[];
170
- verificationNote: string;
171
- verifiedAt: string;
170
+ verificationNote?: string;
171
+ verifiedAt?: string;
172
172
  image?: VerifiedCardImage;
173
173
  rating?: number | string;
174
174
  };
@@ -205,6 +205,15 @@ type NewsArticle = NewsItem & {
205
205
  attribution: string;
206
206
  ai_generated: boolean;
207
207
  }[];
208
+ editorial?: {
209
+ modified_at?: string;
210
+ notices?: {
211
+ id?: string;
212
+ kind: string;
213
+ published_at?: string;
214
+ text: string;
215
+ }[];
216
+ };
208
217
  cards?: VerifiedCard[] | unknown[];
209
218
  };
210
219
  };
@@ -514,7 +514,13 @@ var ALLOWED_CARD_TYPES = /* @__PURE__ */ new Set([
514
514
  "ACTIVITY",
515
515
  "EVENT",
516
516
  "ORGANIZATION",
517
- "PRODUCT"
517
+ "PRODUCT",
518
+ "FRAMEWORK",
519
+ "STANDARD",
520
+ "SPECIFICATION",
521
+ "TOPIC",
522
+ "RESOURCE",
523
+ "REFERENCE"
518
524
  ]);
519
525
  function clip(val, maxChars) {
520
526
  if (typeof val !== "string") return void 0;
@@ -556,9 +562,18 @@ function parseRawCards(rawCards) {
556
562
  const id = clip(c.id, 64);
557
563
  const title = clip(c.title, 300);
558
564
  if (!id || !title) continue;
559
- const rawType = typeof c.card_type === "string" ? c.card_type : c.cardType;
560
- const cardType = ALLOWED_CARD_TYPES.has(rawType) ? rawType : "PLACE";
561
- const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 VERIFIED DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 VERIFIED FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 VERIFIED FACTSHEET" : cardType === "ORGANIZATION" ? "COMMUNITY ORGANIZATION \xB7 DIRECTORY" : "VERIFIED FACTSHEET");
565
+ const rawKind = (typeof c.card_type === "string" ? c.card_type : typeof c.cardType === "string" ? c.cardType : "").trim().toUpperCase();
566
+ let cardType;
567
+ if (ALLOWED_CARD_TYPES.has(rawKind)) {
568
+ cardType = rawKind;
569
+ } else if (rawKind === "RESEARCH_FRAMEWORK") {
570
+ cardType = "FRAMEWORK";
571
+ } else if (c.address && c.hours) {
572
+ cardType = "PLACE";
573
+ } else {
574
+ cardType = "REFERENCE";
575
+ }
576
+ const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 FACTSHEET" : cardType === "ORGANIZATION" ? "ORGANIZATION \xB7 DIRECTORY" : cardType === "PRODUCT" ? "PRODUCT \xB7 FACTSHEET" : cardType === "FRAMEWORK" ? "TECHNICAL FRAMEWORK" : cardType === "STANDARD" ? "TECHNICAL STANDARD" : cardType === "SPECIFICATION" ? "SPECIFICATION" : cardType === "TOPIC" ? "TOPIC REFERENCE" : cardType === "RESOURCE" ? "DIGITAL RESOURCE" : "FACTSHEET");
562
577
  const subtitle = clip(c.subtitle, 300);
563
578
  const summary = clip(c.summary, 1e3);
564
579
  const badge = clip(c.badge, 64);
@@ -579,16 +594,23 @@ function parseRawCards(rawCards) {
579
594
  } else {
580
595
  const timeframe = clip(c.timeframe, 200);
581
596
  if (timeframe) facts.push({ label: "Timeframe", value: timeframe });
597
+ const isPhysicalPlace = cardType === "PLACE" || cardType === "EVENT" || cardType === "ACTIVITY";
582
598
  const address = clip(c.address, 300);
583
599
  if (address) {
584
600
  facts.push({
585
- label: "Address",
601
+ label: isPhysicalPlace ? "Address" : "Location",
586
602
  value: address,
587
- href: `https://maps.google.com/?q=${encodeURIComponent(address)}`
603
+ href: isPhysicalPlace ? `https://maps.google.com/?q=${encodeURIComponent(address)}` : void 0
588
604
  });
589
605
  }
590
606
  const hours = clip(c.hours, 300);
591
- if (hours) facts.push({ label: "Hours", value: hours });
607
+ if (hours) {
608
+ facts.push({ label: isPhysicalPlace ? "Hours" : "Details", value: hours });
609
+ }
610
+ const website = clip(c.website || c.url, 500);
611
+ if (website && isSafeActionUrl(website)) {
612
+ facts.push({ label: "Website", value: website.replace(/^https?:\/\//, ""), href: website });
613
+ }
592
614
  const phone = clip(c.phone, 80);
593
615
  if (phone) {
594
616
  facts.push({
@@ -636,8 +658,8 @@ function parseRawCards(rawCards) {
636
658
  };
637
659
  }
638
660
  }
639
- const verificationNote = clip(c.verification_note || c.verificationNote, 300) || "Verified by publication reporting staff";
640
- const verifiedAt = clip(c.verified_at || c.verifiedAt, 100) || "October 2026";
661
+ const verificationNote = clip(c.verification_note || c.verificationNote, 300);
662
+ const verifiedAt = clip(c.verified_at || c.verifiedAt, 100);
641
663
  let rating = void 0;
642
664
  if (typeof c.rating === "number" || typeof c.rating === "string") {
643
665
  rating = c.rating;
@@ -715,12 +737,12 @@ function VerifiedCardComponent({
715
737
  },
716
738
  i
717
739
  )) }),
718
- /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "article-verified-card__footer", children: [
719
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__seal", children: [
740
+ (card.verificationNote || card.verifiedAt) && /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "article-verified-card__footer", children: [
741
+ card.verificationNote && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__seal", children: [
720
742
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__seal-check", "aria-hidden": "true", children: "\u2713" }),
721
743
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: card.verificationNote })
722
744
  ] }),
723
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
745
+ card.verifiedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
724
746
  ] })
725
747
  ]
726
748
  }
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { V as VerifiedCard } from './types-BJFCWWi0.cjs';
2
+ import { V as VerifiedCard } from './types-DBi49N8m.cjs';
3
3
 
4
4
  /**
5
5
  * Validate that an action link is safe for readers.
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { V as VerifiedCard } from './types-BJFCWWi0.js';
2
+ import { V as VerifiedCard } from './types-DBi49N8m.js';
3
3
 
4
4
  /**
5
5
  * Validate that an action link is safe for readers.
@@ -1 +1 @@
1
- export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-FMCHQI7K.js';
1
+ export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-KYUVXEJY.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.62",
3
+ "version": "0.0.64",
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",
@@ -197,7 +197,7 @@
197
197
  "toys.css"
198
198
  ],
199
199
  "peerDependencies": {
200
- "@scalemule/react": "0.0.21",
200
+ "@scalemule/react": "0.0.22",
201
201
  "react": ">=18.0.0"
202
202
  },
203
203
  "peerDependenciesMeta": {
@@ -217,7 +217,7 @@
217
217
  "prepublishOnly": "node -e \"const v=require('./package.json').version; if(!/^0\\.(0|1)\\.\\d+$/.test(v)){console.error('ERR: SDK '+v+' violates patch-only policy (0.0.x or 0.1.x only). See CONTRIBUTING.md SDK versioning section.');process.exit(1)}\" && npm run lint && npm test && npm run build"
218
218
  },
219
219
  "devDependencies": {
220
- "@scalemule/react": "0.0.21",
220
+ "@scalemule/react": "0.0.22",
221
221
  "@testing-library/react": "^16.3.3",
222
222
  "@types/node": "^22.15.0",
223
223
  "@types/react": "^19.1.0",
@@ -249,8 +249,8 @@
249
249
  "registry": "https://registry.npmjs.org/"
250
250
  },
251
251
  "dependencies": {
252
- "@scalemule/compliance": "0.0.7",
253
- "@scalemule/react": "0.0.21",
252
+ "@scalemule/compliance": "0.0.8",
253
+ "@scalemule/react": "0.0.22",
254
254
  "@scalemule/sdk": "0.0.75"
255
255
  }
256
256
  }