@scalemule/news-publication 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -590,6 +590,15 @@ var CLASSIFIED_CATEGORIES = [
590
590
  { value: "community", label: "Community" },
591
591
  { value: "wanted", label: "Wanted" }
592
592
  ];
593
+ function dealsFeedPath() {
594
+ return "/v1/deals/feed";
595
+ }
596
+ function dealGoPath(offerId) {
597
+ return `/v1/deals/go/${encodeURIComponent(offerId)}`;
598
+ }
599
+ function dealDollars(cents) {
600
+ return cents != null ? `$${(cents / 100).toFixed(2)}` : null;
601
+ }
593
602
  function clipChars(value, max) {
594
603
  const chars = Array.from(value.trim());
595
604
  return chars.length <= max ? chars.join("") : chars.slice(0, max).join("");
@@ -859,6 +868,13 @@ var COOKIE_CATEGORIES = [
859
868
  duration: "1 year",
860
869
  type: "Local Storage"
861
870
  },
871
+ {
872
+ name: "news_publication_theme",
873
+ provider: "First-Party",
874
+ purpose: "Remembers your reading theme preference (light, dark, or system).",
875
+ duration: "1 year",
876
+ type: "HTTP Cookie"
877
+ },
862
878
  {
863
879
  name: "audio_narration_speed",
864
880
  provider: "First-Party",
@@ -1357,6 +1373,282 @@ function CookieSettingsView({
1357
1373
  ] });
1358
1374
  }
1359
1375
 
1376
+ // src/theme.ts
1377
+ var THEME_COOKIE = "news_publication_theme";
1378
+ var THEME_STORAGE_KEY = "news_publication_theme";
1379
+ var THEME_EVENT = "news_publication_theme_change";
1380
+ var THEME_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
1381
+ var memoryTheme = "system";
1382
+ function getSystemTheme() {
1383
+ if (typeof window === "undefined" || !window.matchMedia) return "light";
1384
+ try {
1385
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1386
+ } catch {
1387
+ return "light";
1388
+ }
1389
+ }
1390
+ function resolveEffectiveTheme(mode) {
1391
+ if (mode === "system") return getSystemTheme();
1392
+ return mode;
1393
+ }
1394
+ function getStoredTheme() {
1395
+ if (typeof window === "undefined") return memoryTheme;
1396
+ try {
1397
+ const stored = window.localStorage?.getItem?.(THEME_STORAGE_KEY);
1398
+ if (stored === "light" || stored === "dark" || stored === "system") {
1399
+ memoryTheme = stored;
1400
+ return stored;
1401
+ }
1402
+ } catch {
1403
+ }
1404
+ if (typeof document !== "undefined" && document.cookie) {
1405
+ try {
1406
+ const match = document.cookie.match(new RegExp(`(?:^|; )${THEME_COOKIE}=([^;]*)`));
1407
+ if (match) {
1408
+ const decoded = decodeURIComponent(match[1]);
1409
+ if (decoded === "light" || decoded === "dark" || decoded === "system") {
1410
+ memoryTheme = decoded;
1411
+ return decoded;
1412
+ }
1413
+ }
1414
+ } catch {
1415
+ }
1416
+ }
1417
+ return memoryTheme;
1418
+ }
1419
+ function applyTheme(mode) {
1420
+ if (typeof document === "undefined") return;
1421
+ try {
1422
+ document.documentElement.setAttribute("data-theme", mode);
1423
+ const elements = document.querySelectorAll(".publication");
1424
+ elements.forEach((el) => el.setAttribute("data-theme", mode));
1425
+ } catch {
1426
+ }
1427
+ }
1428
+ var memoryListeners = /* @__PURE__ */ new Set();
1429
+ function setTheme(mode) {
1430
+ memoryTheme = mode;
1431
+ if (typeof window !== "undefined") {
1432
+ try {
1433
+ window.localStorage?.setItem?.(THEME_STORAGE_KEY, mode);
1434
+ } catch {
1435
+ }
1436
+ if (typeof document !== "undefined") {
1437
+ try {
1438
+ document.cookie = `${THEME_COOKIE}=${encodeURIComponent(mode)}; path=/; max-age=${THEME_MAX_AGE_SECONDS}; SameSite=Lax`;
1439
+ } catch {
1440
+ }
1441
+ }
1442
+ applyTheme(mode);
1443
+ const effective = resolveEffectiveTheme(mode);
1444
+ try {
1445
+ window.dispatchEvent(
1446
+ new CustomEvent(THEME_EVENT, {
1447
+ detail: { mode, effective }
1448
+ })
1449
+ );
1450
+ } catch {
1451
+ }
1452
+ }
1453
+ memoryListeners.forEach((listener) => {
1454
+ try {
1455
+ listener();
1456
+ } catch {
1457
+ }
1458
+ });
1459
+ }
1460
+ function clearTheme() {
1461
+ memoryTheme = "system";
1462
+ if (typeof document !== "undefined") {
1463
+ try {
1464
+ document.cookie = `${THEME_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
1465
+ document.documentElement.removeAttribute("data-theme");
1466
+ } catch {
1467
+ }
1468
+ }
1469
+ if (typeof window !== "undefined") {
1470
+ try {
1471
+ window.localStorage?.removeItem?.(THEME_STORAGE_KEY);
1472
+ } catch {
1473
+ }
1474
+ }
1475
+ memoryListeners.forEach((listener) => {
1476
+ try {
1477
+ listener();
1478
+ } catch {
1479
+ }
1480
+ });
1481
+ }
1482
+ function subscribeTheme(listener) {
1483
+ memoryListeners.add(listener);
1484
+ if (typeof window === "undefined") {
1485
+ return () => {
1486
+ memoryListeners.delete(listener);
1487
+ };
1488
+ }
1489
+ const onStorage = (e) => {
1490
+ if (e.key === THEME_STORAGE_KEY) {
1491
+ applyTheme(getStoredTheme());
1492
+ listener();
1493
+ }
1494
+ };
1495
+ const onCustom = () => {
1496
+ listener();
1497
+ };
1498
+ window.addEventListener("storage", onStorage);
1499
+ window.addEventListener(THEME_EVENT, onCustom);
1500
+ let mediaQuery;
1501
+ let onMediaChange;
1502
+ if (window.matchMedia) {
1503
+ try {
1504
+ mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
1505
+ onMediaChange = () => {
1506
+ if (getStoredTheme() === "system") {
1507
+ applyTheme("system");
1508
+ listener();
1509
+ }
1510
+ };
1511
+ if (mediaQuery.addEventListener) {
1512
+ mediaQuery.addEventListener("change", onMediaChange);
1513
+ } else if (mediaQuery.addListener) {
1514
+ mediaQuery.addListener(onMediaChange);
1515
+ }
1516
+ } catch {
1517
+ }
1518
+ }
1519
+ return () => {
1520
+ window.removeEventListener("storage", onStorage);
1521
+ window.removeEventListener(THEME_EVENT, onCustom);
1522
+ if (mediaQuery && onMediaChange) {
1523
+ if (mediaQuery.removeEventListener) {
1524
+ mediaQuery.removeEventListener("change", onMediaChange);
1525
+ } else if (mediaQuery.removeListener) {
1526
+ mediaQuery.removeListener(onMediaChange);
1527
+ }
1528
+ }
1529
+ };
1530
+ }
1531
+ function themeInitScript() {
1532
+ return `(function(){try{var k='${THEME_STORAGE_KEY}',m=localStorage.getItem(k);if(!m){var c=document.cookie.match(/(?:^|; )news_publication_theme=([^;]*)/);m=c?decodeURIComponent(c[1]):'system';}if(m==='light'||m==='dark'||m==='system'){document.documentElement.setAttribute('data-theme',m);}}catch(e){}})();`;
1533
+ }
1534
+ function SunIcon() {
1535
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1536
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "4" }),
1537
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" })
1538
+ ] });
1539
+ }
1540
+ function MoonIcon() {
1541
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }) });
1542
+ }
1543
+ function SystemIcon() {
1544
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1545
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }),
1546
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", x2: "16", y1: "21", y2: "21" }),
1547
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "17", y2: "21" })
1548
+ ] });
1549
+ }
1550
+ function ThemeToggle({
1551
+ variant = "compact",
1552
+ className = "",
1553
+ showPromotion = false,
1554
+ accountUrl = "/account"
1555
+ }) {
1556
+ const currentTheme = react.useSyncExternalStore(subscribeTheme, getStoredTheme, () => "system");
1557
+ const [mounted, setMounted] = react.useState(false);
1558
+ const [promoVisible, setPromoVisible] = react.useState(false);
1559
+ react.useEffect(() => {
1560
+ setMounted(true);
1561
+ }, []);
1562
+ const handleSelect = (mode) => {
1563
+ setTheme(mode);
1564
+ if (showPromotion) {
1565
+ setPromoVisible(true);
1566
+ }
1567
+ };
1568
+ const active = mounted ? currentTheme : "system";
1569
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `pub-theme-toggle-container ${className}`.trim(), children: [
1570
+ /* @__PURE__ */ jsxRuntime.jsxs(
1571
+ "div",
1572
+ {
1573
+ className: "pub-theme-toggle",
1574
+ "data-variant": variant,
1575
+ role: "group",
1576
+ "aria-label": "Display theme selection",
1577
+ children: [
1578
+ /* @__PURE__ */ jsxRuntime.jsxs(
1579
+ "button",
1580
+ {
1581
+ type: "button",
1582
+ onClick: () => handleSelect("light"),
1583
+ "aria-pressed": active === "light",
1584
+ "data-active": active === "light",
1585
+ title: "Light theme (broadsheet day edition)",
1586
+ "aria-label": "Light theme",
1587
+ children: [
1588
+ /* @__PURE__ */ jsxRuntime.jsx(SunIcon, {}),
1589
+ variant !== "compact" && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Light" })
1590
+ ]
1591
+ }
1592
+ ),
1593
+ /* @__PURE__ */ jsxRuntime.jsxs(
1594
+ "button",
1595
+ {
1596
+ type: "button",
1597
+ onClick: () => handleSelect("dark"),
1598
+ "aria-pressed": active === "dark",
1599
+ "data-active": active === "dark",
1600
+ title: "Dark theme (broadsheet evening edition)",
1601
+ "aria-label": "Dark theme",
1602
+ children: [
1603
+ /* @__PURE__ */ jsxRuntime.jsx(MoonIcon, {}),
1604
+ variant !== "compact" && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Dark" })
1605
+ ]
1606
+ }
1607
+ ),
1608
+ /* @__PURE__ */ jsxRuntime.jsxs(
1609
+ "button",
1610
+ {
1611
+ type: "button",
1612
+ onClick: () => handleSelect("system"),
1613
+ "aria-pressed": active === "system",
1614
+ "data-active": active === "system",
1615
+ title: "System preference (matches device setting)",
1616
+ "aria-label": "System theme",
1617
+ children: [
1618
+ /* @__PURE__ */ jsxRuntime.jsx(SystemIcon, {}),
1619
+ variant !== "compact" && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "System" })
1620
+ ]
1621
+ }
1622
+ )
1623
+ ]
1624
+ }
1625
+ ),
1626
+ promoVisible && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pub-theme-promo", role: "status", children: [
1627
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Theme saved for this browser. " }),
1628
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: accountUrl, children: "Create a free reader account" }),
1629
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: " to sync preferences across all your devices." }),
1630
+ /* @__PURE__ */ jsxRuntime.jsx(
1631
+ "button",
1632
+ {
1633
+ type: "button",
1634
+ onClick: () => setPromoVisible(false),
1635
+ "aria-label": "Dismiss note",
1636
+ style: {
1637
+ marginLeft: "8px",
1638
+ background: "none",
1639
+ border: "none",
1640
+ cursor: "pointer",
1641
+ color: "var(--pub-muted)",
1642
+ fontSize: "12px",
1643
+ padding: "0 4px"
1644
+ },
1645
+ children: "\xD7"
1646
+ }
1647
+ )
1648
+ ] })
1649
+ ] });
1650
+ }
1651
+
1360
1652
  exports.ANALYTICS_COOKIE = ANALYTICS_COOKIE;
1361
1653
  exports.ANALYTICS_VERSION = ANALYTICS_VERSION;
1362
1654
  exports.BROADSHEET_THEME = BROADSHEET_THEME;
@@ -1378,6 +1670,11 @@ exports.MEET_ACTIVITIES = MEET_ACTIVITIES;
1378
1670
  exports.NEWS_CACHE = NEWS_CACHE;
1379
1671
  exports.NewsError = NewsError;
1380
1672
  exports.SESSION_IDLE_MS = SESSION_IDLE_MS;
1673
+ exports.THEME_COOKIE = THEME_COOKIE;
1674
+ exports.THEME_EVENT = THEME_EVENT;
1675
+ exports.THEME_MAX_AGE_SECONDS = THEME_MAX_AGE_SECONDS;
1676
+ exports.THEME_STORAGE_KEY = THEME_STORAGE_KEY;
1677
+ exports.ThemeToggle = ThemeToggle;
1381
1678
  exports.UTM_FIELDS = UTM_FIELDS;
1382
1679
  exports.UUID = UUID;
1383
1680
  exports.acceptAllCookies = acceptAllCookies;
@@ -1385,6 +1682,7 @@ exports.acceptRemoteRecord = acceptRemoteRecord;
1385
1682
  exports.acceptSnapshot = acceptSnapshot;
1386
1683
  exports.activeNearby = activeNearby;
1387
1684
  exports.activityLabel = activityLabel;
1685
+ exports.applyTheme = applyTheme;
1388
1686
  exports.articleCacheKey = articleCacheKey;
1389
1687
  exports.articleCacheOptions = articleCacheOptions;
1390
1688
  exports.articleMeta = articleMeta;
@@ -1396,8 +1694,12 @@ exports.categoryLabel = categoryLabel;
1396
1694
  exports.classifiedPath = classifiedPath;
1397
1695
  exports.classifiedSearchPath = classifiedSearchPath;
1398
1696
  exports.clearCookieConsent = clearCookieConsent;
1697
+ exports.clearTheme = clearTheme;
1399
1698
  exports.clipChars = clipChars;
1400
1699
  exports.composeEdition = composeEdition;
1700
+ exports.dealDollars = dealDollars;
1701
+ exports.dealGoPath = dealGoPath;
1702
+ exports.dealsFeedPath = dealsFeedPath;
1401
1703
  exports.defaultRevalidateTargets = defaultRevalidateTargets;
1402
1704
  exports.definePublication = definePublication;
1403
1705
  exports.displayHeadline = displayHeadline;
@@ -1409,6 +1711,8 @@ exports.feedCacheKey = feedCacheKey;
1409
1711
  exports.feedCacheOptions = feedCacheOptions;
1410
1712
  exports.filterNews = filterNews;
1411
1713
  exports.getCookieConsent = getCookieConsent;
1714
+ exports.getStoredTheme = getStoredTheme;
1715
+ exports.getSystemTheme = getSystemTheme;
1412
1716
  exports.hasConsented = hasConsented;
1413
1717
  exports.inventoryCacheOptions = inventoryCacheOptions;
1414
1718
  exports.isAdult = isAdult;
@@ -1439,6 +1743,7 @@ exports.recordConsentToCompliance = recordConsentToCompliance;
1439
1743
  exports.referralCode = referralCode;
1440
1744
  exports.rejectOptionalCookies = rejectOptionalCookies;
1441
1745
  exports.resolveCoupon = resolveCoupon;
1746
+ exports.resolveEffectiveTheme = resolveEffectiveTheme;
1442
1747
  exports.resolveLayout = resolveLayout;
1443
1748
  exports.revalidateDecision = revalidateDecision;
1444
1749
  exports.screenMessage = screenMessage;
@@ -1447,13 +1752,16 @@ exports.sectionDefinition = sectionDefinition;
1447
1752
  exports.sectionId = sectionId;
1448
1753
  exports.sectionPath = sectionPath;
1449
1754
  exports.setCookieConsent = setCookieConsent;
1755
+ exports.setTheme = setTheme;
1450
1756
  exports.shortDeck = shortDeck;
1451
1757
  exports.shortText = shortText;
1452
1758
  exports.shouldServeLastKnown = shouldServeLastKnown;
1453
1759
  exports.snapshotFromRecord = snapshotFromRecord;
1454
1760
  exports.storyDate = storyDate;
1455
1761
  exports.storyTowns = storyTowns;
1762
+ exports.subscribeTheme = subscribeTheme;
1456
1763
  exports.syncPublication = syncPublication;
1764
+ exports.themeInitScript = themeInitScript;
1457
1765
  exports.townSlug = townSlug;
1458
1766
  exports.validCalendarDate = validCalendarDate;
1459
1767
  exports.walnutCreekTimes = walnutCreekTimes;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { P as PublicationTheme, a as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, b as NearbyUpdate, c as NewsItem, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './snapshot-BS4hn1dk.cjs';
2
2
  export { A as ArticleCitation, e as CategoryRef, f as CouponSettings, g as CoverImageMetadata, h as FEATURE_NAMES, i as FeatureFlags, j as NewsFeed, k as NewsLayoutId, l as PublicationModules, m as PublicationRecord, n as PublicationSnapshot, R as ReferralSettings, S as SnapshotHooks, o as StoryDisplay, p as SyncSource, q as SyncedPublication, T as TagRef, r as acceptRemoteRecord, s as acceptSnapshot, t as parseSnapshot, u as publicationFromSnapshot, v as publicationRecord, w as snapshotFromRecord, x as syncPublication } from './snapshot-BS4hn1dk.cjs';
3
3
  export { M as MEET_ACTIVITIES, a as MeetActivity, b as MeetupInput, P as PreparedMeetup, c as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-5Ym5bxfh.cjs';
4
- export { C as CLASSIFIED_CATEGORIES, a as ClassifiedCategory, b as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, P as PreparedClassified, c as PreparedJob, d as PublicJob, e as categoryLabel, f as classifiedPath, g as classifiedSearchPath, h as clipChars, i as employmentLabel, j as isClassifiedCategory, k as isEmploymentType, l as jobEmployerBody, m as jobOpeningBody, n as jobPostingBody, p as prepareClassified, o as prepareJobPost, q as publicJobPath, r as publicJobsPath } from './marketplace-Ch7mdJZv.cjs';
4
+ export { C as CLASSIFIED_CATEGORIES, a as ClassifiedCategory, b as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, P as PreparedClassified, c as PreparedJob, d as PublicDeal, e as PublicDealOffer, f 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-CpGWPbFk.cjs';
5
5
  export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.cjs';
6
6
  export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-view.cjs';
7
+ export { ThemeToggle, ThemeToggleProps } from './theme-toggle.cjs';
7
8
  import 'react';
8
9
 
9
10
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
@@ -314,4 +315,45 @@ declare function recordConsentToCompliance(record: CookieConsentRecord, options?
314
315
  publicationSlug?: string;
315
316
  }): Promise<void>;
316
317
 
317
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EVENTS, EditorialSection, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearCookieConsent, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, hasConsented, inventoryCacheOptions, isCategoryAllowed, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
318
+ type ThemeMode = "light" | "dark" | "system";
319
+ type EffectiveTheme = "light" | "dark";
320
+ declare const THEME_COOKIE = "news_publication_theme";
321
+ declare const THEME_STORAGE_KEY = "news_publication_theme";
322
+ declare const THEME_EVENT = "news_publication_theme_change";
323
+ declare const THEME_MAX_AGE_SECONDS: number;
324
+ /**
325
+ * Returns the current system color scheme preference.
326
+ */
327
+ declare function getSystemTheme(): EffectiveTheme;
328
+ /**
329
+ * Resolves the effective "light" or "dark" appearance from a chosen mode.
330
+ */
331
+ declare function resolveEffectiveTheme(mode: ThemeMode): EffectiveTheme;
332
+ /**
333
+ * Reads stored theme mode from localStorage, falling back to cookie, memory, then "system".
334
+ */
335
+ declare function getStoredTheme(): ThemeMode;
336
+ /**
337
+ * Applies the given theme mode to documentElement attribute and publication elements.
338
+ */
339
+ declare function applyTheme(mode: ThemeMode): void;
340
+ /**
341
+ * Persists the reader's theme mode in memory, localStorage, and cookie,
342
+ * applies the attribute, and broadcasts an event for other components and browser tabs.
343
+ */
344
+ declare function setTheme(mode: ThemeMode): void;
345
+ /**
346
+ * Resets stored theme to "system" and cleans cookies/storage.
347
+ */
348
+ declare function clearTheme(): void;
349
+ /**
350
+ * Subscribes to theme changes across in-memory calls, storage events, custom events, and system media queries.
351
+ */
352
+ declare function subscribeTheme(listener: () => void): () => void;
353
+ /**
354
+ * Inline JavaScript to inject into <head> to synchronously set data-theme before
355
+ * any DOM renders, eliminating any Flash of Unstyled Content (FOUC).
356
+ */
357
+ declare function themeInitScript(): string;
358
+
359
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearCookieConsent, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, getStoredTheme, getSystemTheme, hasConsented, inventoryCacheOptions, isCategoryAllowed, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { P as PublicationTheme, a as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, b as NearbyUpdate, c as NewsItem, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './snapshot-BS4hn1dk.js';
2
2
  export { A as ArticleCitation, e as CategoryRef, f as CouponSettings, g as CoverImageMetadata, h as FEATURE_NAMES, i as FeatureFlags, j as NewsFeed, k as NewsLayoutId, l as PublicationModules, m as PublicationRecord, n as PublicationSnapshot, R as ReferralSettings, S as SnapshotHooks, o as StoryDisplay, p as SyncSource, q as SyncedPublication, T as TagRef, r as acceptRemoteRecord, s as acceptSnapshot, t as parseSnapshot, u as publicationFromSnapshot, v as publicationRecord, w as snapshotFromRecord, x as syncPublication } from './snapshot-BS4hn1dk.js';
3
3
  export { M as MEET_ACTIVITIES, a as MeetActivity, b as MeetupInput, P as PreparedMeetup, c as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-5Ym5bxfh.js';
4
- export { C as CLASSIFIED_CATEGORIES, a as ClassifiedCategory, b as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, P as PreparedClassified, c as PreparedJob, d as PublicJob, e as categoryLabel, f as classifiedPath, g as classifiedSearchPath, h as clipChars, i as employmentLabel, j as isClassifiedCategory, k as isEmploymentType, l as jobEmployerBody, m as jobOpeningBody, n as jobPostingBody, p as prepareClassified, o as prepareJobPost, q as publicJobPath, r as publicJobsPath } from './marketplace-Ch7mdJZv.js';
4
+ export { C as CLASSIFIED_CATEGORIES, a as ClassifiedCategory, b as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, P as PreparedClassified, c as PreparedJob, d as PublicDeal, e as PublicDealOffer, f 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-CpGWPbFk.js';
5
5
  export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.js';
6
6
  export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-view.js';
7
+ export { ThemeToggle, ThemeToggleProps } from './theme-toggle.js';
7
8
  import 'react';
8
9
 
9
10
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
@@ -314,4 +315,45 @@ declare function recordConsentToCompliance(record: CookieConsentRecord, options?
314
315
  publicationSlug?: string;
315
316
  }): Promise<void>;
316
317
 
317
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EVENTS, EditorialSection, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearCookieConsent, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, hasConsented, inventoryCacheOptions, isCategoryAllowed, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
318
+ type ThemeMode = "light" | "dark" | "system";
319
+ type EffectiveTheme = "light" | "dark";
320
+ declare const THEME_COOKIE = "news_publication_theme";
321
+ declare const THEME_STORAGE_KEY = "news_publication_theme";
322
+ declare const THEME_EVENT = "news_publication_theme_change";
323
+ declare const THEME_MAX_AGE_SECONDS: number;
324
+ /**
325
+ * Returns the current system color scheme preference.
326
+ */
327
+ declare function getSystemTheme(): EffectiveTheme;
328
+ /**
329
+ * Resolves the effective "light" or "dark" appearance from a chosen mode.
330
+ */
331
+ declare function resolveEffectiveTheme(mode: ThemeMode): EffectiveTheme;
332
+ /**
333
+ * Reads stored theme mode from localStorage, falling back to cookie, memory, then "system".
334
+ */
335
+ declare function getStoredTheme(): ThemeMode;
336
+ /**
337
+ * Applies the given theme mode to documentElement attribute and publication elements.
338
+ */
339
+ declare function applyTheme(mode: ThemeMode): void;
340
+ /**
341
+ * Persists the reader's theme mode in memory, localStorage, and cookie,
342
+ * applies the attribute, and broadcasts an event for other components and browser tabs.
343
+ */
344
+ declare function setTheme(mode: ThemeMode): void;
345
+ /**
346
+ * Resets stored theme to "system" and cleans cookies/storage.
347
+ */
348
+ declare function clearTheme(): void;
349
+ /**
350
+ * Subscribes to theme changes across in-memory calls, storage events, custom events, and system media queries.
351
+ */
352
+ declare function subscribeTheme(listener: () => void): () => void;
353
+ /**
354
+ * Inline JavaScript to inject into <head> to synchronously set data-theme before
355
+ * any DOM renders, eliminating any Flash of Unstyled Content (FOUC).
356
+ */
357
+ declare function themeInitScript(): string;
358
+
359
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearCookieConsent, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, getStoredTheme, getSystemTheme, hasConsented, inventoryCacheOptions, isCategoryAllowed, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
+ 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';
1
2
  import { definePublication } from './chunk-C7JTEHTH.js';
2
3
  export { BROADSHEET_THEME, FEATURE_NAMES, LAYOUT_COOKIE, acceptRemoteRecord, acceptSnapshot, definePublication, featureEnabled, parseSnapshot, publicationFromSnapshot, publicationRecord, resolveLayout, snapshotFromRecord, syncPublication } from './chunk-C7JTEHTH.js';
3
- export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-YQXS2KQI.js';
4
+ export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
4
5
  export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-GB36YM56.js';
5
- export { CookieConsentBanner } from './chunk-PYCU2T5O.js';
6
- export { CookieSettingsView } from './chunk-R33MJWND.js';
7
- export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, acceptAllCookies, clearCookieConsent, getCookieConsent, hasConsented, isCategoryAllowed, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent } from './chunk-TJ7RL3PV.js';
6
+ export { CookieConsentBanner } from './chunk-5QHNNRVQ.js';
7
+ export { CookieSettingsView } from './chunk-AYSBXLOT.js';
8
+ export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, acceptAllCookies, clearCookieConsent, getCookieConsent, hasConsented, isCategoryAllowed, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent } from './chunk-RNXTBN5Z.js';
8
9
 
9
10
  // src/preset.ts
10
11
  var walnutCreekTimes = definePublication({
@@ -43,6 +43,25 @@ type PublicJob = {
43
43
  remote_allowed?: boolean;
44
44
  published_at?: string | null;
45
45
  };
46
+ type PublicDealOffer = {
47
+ id: string;
48
+ merchant: string;
49
+ price_cents?: number | null;
50
+ };
51
+ type PublicDeal = {
52
+ id: string;
53
+ title: string;
54
+ body: string;
55
+ price_cents?: number | null;
56
+ compare_at_cents?: number | null;
57
+ coupon_code?: string | null;
58
+ image_url?: string | null;
59
+ locality?: string | null;
60
+ offers: PublicDealOffer[];
61
+ };
62
+ declare function dealsFeedPath(): string;
63
+ declare function dealGoPath(offerId: string): string;
64
+ declare function dealDollars(cents?: number | null): string | null;
46
65
  type ClassifiedListing = {
47
66
  id: string;
48
67
  title: string;
@@ -120,4 +139,4 @@ declare function prepareClassified(input: {
120
139
  price?: unknown;
121
140
  }, towns: string[], region: string, now?: Date): PreparedClassified;
122
141
 
123
- export { CLASSIFIED_CATEGORIES as C, type EmploymentType as E, JOB_EMPLOYMENT_TYPES as J, type PreparedClassified as P, type ClassifiedCategory as a, type ClassifiedListing as b, type PreparedJob as c, type PublicJob as d, categoryLabel as e, classifiedPath as f, classifiedSearchPath as g, clipChars as h, employmentLabel as i, isClassifiedCategory as j, isEmploymentType as k, jobEmployerBody as l, jobOpeningBody as m, jobPostingBody as n, prepareJobPost as o, prepareClassified as p, publicJobPath as q, publicJobsPath as r };
142
+ export { CLASSIFIED_CATEGORIES as C, type EmploymentType as E, JOB_EMPLOYMENT_TYPES as J, type PreparedClassified as P, type ClassifiedCategory as a, type ClassifiedListing as b, type PreparedJob as c, type PublicDeal as d, type PublicDealOffer as e, type PublicJob as f, categoryLabel as g, classifiedPath as h, classifiedSearchPath as i, clipChars as j, dealDollars as k, dealGoPath as l, dealsFeedPath as m, employmentLabel as n, isClassifiedCategory as o, isEmploymentType as p, jobEmployerBody as q, jobOpeningBody as r, jobPostingBody as s, prepareClassified as t, prepareJobPost as u, publicJobPath as v, publicJobsPath as w };
@@ -43,6 +43,25 @@ type PublicJob = {
43
43
  remote_allowed?: boolean;
44
44
  published_at?: string | null;
45
45
  };
46
+ type PublicDealOffer = {
47
+ id: string;
48
+ merchant: string;
49
+ price_cents?: number | null;
50
+ };
51
+ type PublicDeal = {
52
+ id: string;
53
+ title: string;
54
+ body: string;
55
+ price_cents?: number | null;
56
+ compare_at_cents?: number | null;
57
+ coupon_code?: string | null;
58
+ image_url?: string | null;
59
+ locality?: string | null;
60
+ offers: PublicDealOffer[];
61
+ };
62
+ declare function dealsFeedPath(): string;
63
+ declare function dealGoPath(offerId: string): string;
64
+ declare function dealDollars(cents?: number | null): string | null;
46
65
  type ClassifiedListing = {
47
66
  id: string;
48
67
  title: string;
@@ -120,4 +139,4 @@ declare function prepareClassified(input: {
120
139
  price?: unknown;
121
140
  }, towns: string[], region: string, now?: Date): PreparedClassified;
122
141
 
123
- export { CLASSIFIED_CATEGORIES as C, type EmploymentType as E, JOB_EMPLOYMENT_TYPES as J, type PreparedClassified as P, type ClassifiedCategory as a, type ClassifiedListing as b, type PreparedJob as c, type PublicJob as d, categoryLabel as e, classifiedPath as f, classifiedSearchPath as g, clipChars as h, employmentLabel as i, isClassifiedCategory as j, isEmploymentType as k, jobEmployerBody as l, jobOpeningBody as m, jobPostingBody as n, prepareJobPost as o, prepareClassified as p, publicJobPath as q, publicJobsPath as r };
142
+ export { CLASSIFIED_CATEGORIES as C, type EmploymentType as E, JOB_EMPLOYMENT_TYPES as J, type PreparedClassified as P, type ClassifiedCategory as a, type ClassifiedListing as b, type PreparedJob as c, type PublicDeal as d, type PublicDealOffer as e, type PublicJob as f, categoryLabel as g, classifiedPath as h, classifiedSearchPath as i, clipChars as j, dealDollars as k, dealGoPath as l, dealsFeedPath as m, employmentLabel as n, isClassifiedCategory as o, isEmploymentType as p, jobEmployerBody as q, jobOpeningBody as r, jobPostingBody as s, prepareClassified as t, prepareJobPost as u, publicJobPath as v, publicJobsPath as w };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { JOB_EMPLOYMENT_TYPES, CLASSIFIED_CATEGORIES } from './chunk-YQXS2KQI.js';
2
+ import { JOB_EMPLOYMENT_TYPES, CLASSIFIED_CATEGORIES } from './chunk-C7ZPLH2K.js';
3
3
  import { useState } from 'react';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5