@fifthbell/brokaw 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/dist/carousels.d.ts +1 -0
  4. package/dist/carousels.js +65 -0
  5. package/dist/components/live-program/LiveProgram.d.ts +8 -0
  6. package/dist/components/live-program/LiveProgram.js +526 -0
  7. package/dist/components/live-program/assets.d.ts +14 -0
  8. package/dist/components/live-program/assets.js +14 -0
  9. package/dist/components/live-program/components/Marquee.d.ts +16 -0
  10. package/dist/components/live-program/components/Marquee.js +88 -0
  11. package/dist/components/live-program/components/MarqueeCurtain.d.ts +5 -0
  12. package/dist/components/live-program/components/MarqueeCurtain.js +30 -0
  13. package/dist/components/live-program/components/WorldClocks.d.ts +19 -0
  14. package/dist/components/live-program/components/WorldClocks.js +101 -0
  15. package/dist/components/live-program/components/slides/ArticleSlide.d.ts +14 -0
  16. package/dist/components/live-program/components/slides/ArticleSlide.js +22 -0
  17. package/dist/components/live-program/components/slides/CallsignSlide.d.ts +6 -0
  18. package/dist/components/live-program/components/slides/CallsignSlide.js +49 -0
  19. package/dist/components/live-program/components/slides/slideStyles.d.ts +1 -0
  20. package/dist/components/live-program/components/slides/slideStyles.js +64 -0
  21. package/dist/components/live-program/events.d.ts +34 -0
  22. package/dist/components/live-program/events.js +167 -0
  23. package/dist/components/live-program/hooks/useSSE.d.ts +11 -0
  24. package/dist/components/live-program/hooks/useSSE.js +67 -0
  25. package/dist/components/live-program/i18n.d.ts +4 -0
  26. package/dist/components/live-program/i18n.js +290 -0
  27. package/dist/components/live-program/segments/ArticlesSegment.d.ts +6 -0
  28. package/dist/components/live-program/segments/ArticlesSegment.js +160 -0
  29. package/dist/components/live-program/segments/EarthquakeSegment.d.ts +16 -0
  30. package/dist/components/live-program/segments/EarthquakeSegment.js +130 -0
  31. package/dist/components/live-program/segments/MarketsSegment.d.ts +12 -0
  32. package/dist/components/live-program/segments/MarketsSegment.js +87 -0
  33. package/dist/components/live-program/segments/WeatherSegment.d.ts +15 -0
  34. package/dist/components/live-program/segments/WeatherSegment.js +184 -0
  35. package/dist/components/live-program/segments/index.d.ts +6 -0
  36. package/dist/components/live-program/segments/index.js +6 -0
  37. package/dist/components/live-program/segments/types.d.ts +23 -0
  38. package/dist/components/live-program/segments/types.js +1 -0
  39. package/dist/components/live-program/segments/usePlaylistEngine.d.ts +9 -0
  40. package/dist/components/live-program/segments/usePlaylistEngine.js +108 -0
  41. package/dist/components/live-program/utils/broadcastTime.d.ts +12 -0
  42. package/dist/components/live-program/utils/broadcastTime.js +33 -0
  43. package/dist/homepage-distributor.d.ts +55 -0
  44. package/dist/homepage-distributor.js +68 -0
  45. package/dist/instagram-image-template.d.ts +8 -0
  46. package/dist/instagram-image-template.js +200 -0
  47. package/dist/outlet-config.d.ts +23 -0
  48. package/dist/outlet-config.js +23 -0
  49. package/dist/renderer.browser.d.ts +2 -0
  50. package/dist/renderer.browser.js +128 -0
  51. package/dist/renderer.core.d.ts +9 -0
  52. package/dist/renderer.core.js +353 -0
  53. package/dist/renderer.d.ts +3 -0
  54. package/dist/renderer.js +3 -0
  55. package/dist/renderer.node.d.ts +2 -0
  56. package/dist/renderer.node.js +71 -0
  57. package/dist/types/canonical-article.d.ts +247 -0
  58. package/dist/types/canonical-article.js +235 -0
  59. package/dist/utils/sofascore.d.ts +3 -0
  60. package/dist/utils/sofascore.js +31 -0
  61. package/package.json +78 -0
  62. package/src/partial-deps.json +52 -0
  63. package/src/styles/compiled.css +2 -0
  64. package/src/templates/layouts/404.hbs +5 -0
  65. package/src/templates/layouts/article-page.hbs +5 -0
  66. package/src/templates/layouts/category-page.hbs +5 -0
  67. package/src/templates/layouts/homepage.hbs +5 -0
  68. package/src/templates/layouts/link-in-bio.hbs +228 -0
  69. package/src/templates/layouts/live-story.hbs +5 -0
  70. package/src/templates/layouts/search-page.hbs +5 -0
  71. package/src/templates/partials/blocks/audio.hbs +12 -0
  72. package/src/templates/partials/blocks/data-table.hbs +23 -0
  73. package/src/templates/partials/blocks/divider.hbs +1 -0
  74. package/src/templates/partials/blocks/heading.hbs +9 -0
  75. package/src/templates/partials/blocks/image.hbs +6 -0
  76. package/src/templates/partials/blocks/info-box.hbs +8 -0
  77. package/src/templates/partials/blocks/instagram.hbs +28 -0
  78. package/src/templates/partials/blocks/key-points.hbs +8 -0
  79. package/src/templates/partials/blocks/list.hbs +13 -0
  80. package/src/templates/partials/blocks/live-update.hbs +24 -0
  81. package/src/templates/partials/blocks/pull-quote.hbs +6 -0
  82. package/src/templates/partials/blocks/rich-text.hbs +1 -0
  83. package/src/templates/partials/blocks/tiktok.hbs +15 -0
  84. package/src/templates/partials/blocks/x.hbs +74 -0
  85. package/src/templates/partials/blocks/youtube.hbs +12 -0
  86. package/src/templates/partials/components/article-main.hbs +159 -0
  87. package/src/templates/partials/components/breaking-news/live-updates-column.hbs +29 -0
  88. package/src/templates/partials/components/breaking-news.hbs +56 -0
  89. package/src/templates/partials/components/category/header.hbs +5 -0
  90. package/src/templates/partials/components/category/main-grid.hbs +55 -0
  91. package/src/templates/partials/components/category/main.hbs +7 -0
  92. package/src/templates/partials/components/category/more-grid.hbs +26 -0
  93. package/src/templates/partials/components/editorial-hero.hbs +73 -0
  94. package/src/templates/partials/components/headline.hbs +15 -0
  95. package/src/templates/partials/components/hero-editorial.hbs +1 -0
  96. package/src/templates/partials/components/hero.hbs +1 -0
  97. package/src/templates/partials/components/home/landing.hbs +111 -0
  98. package/src/templates/partials/components/home/main.hbs +63 -0
  99. package/src/templates/partials/components/home/more-stories.hbs +23 -0
  100. package/src/templates/partials/components/home/must-read.hbs +77 -0
  101. package/src/templates/partials/components/live-story/main.hbs +229 -0
  102. package/src/templates/partials/components/not-found/main.hbs +28 -0
  103. package/src/templates/partials/components/search/main.hbs +420 -0
  104. package/src/templates/partials/components/snack.hbs +92 -0
  105. package/src/templates/partials/components/spotlight-hero.hbs +59 -0
  106. package/src/templates/partials/components/trending.hbs +14 -0
  107. package/src/templates/partials/components/ui/accordion.hbs +30 -0
  108. package/src/templates/partials/components/ui/breadcrumb.hbs +16 -0
  109. package/src/templates/partials/components/ui/icon-button.hbs +19 -0
  110. package/src/templates/partials/components/ui/loading-spinner.hbs +27 -0
  111. package/src/templates/partials/components/ui/pagination.hbs +56 -0
  112. package/src/templates/partials/components/ui/scroll-area.hbs +12 -0
  113. package/src/templates/partials/components/ui/status-badge.hbs +21 -0
  114. package/src/templates/partials/footers/footer-full.hbs +79 -0
  115. package/src/templates/partials/footers/footer-minimal.hbs +5 -0
  116. package/src/templates/partials/headers/header-main.hbs +397 -0
  117. package/src/templates/partials/headers/header-minimal.hbs +16 -0
  118. package/src/templates/partials/nav/nav-categories.hbs +5 -0
  119. package/src/templates/partials/shell/doc-end.hbs +282 -0
  120. package/src/templates/partials/shell/doc-start-404.hbs +28 -0
  121. package/src/templates/partials/shell/doc-start-standard.hbs +68 -0
@@ -0,0 +1,160 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { ArticleSlide } from '../components/slides/ArticleSlide.js';
4
+ import { t } from '../i18n.js';
5
+ const MOCK_ARTICLES = [
6
+ {
7
+ id: '1',
8
+ headline: 'Global Climate Summit Reaches Historic Agreement',
9
+ summary: 'World leaders have agreed to ambitious new targets for reducing carbon emissions, marking a significant step forward in the fight against climate change.',
10
+ imageUrl: 'https://picsum.photos/seed/news1/1920/1080',
11
+ category: 'Environment',
12
+ url: 'https://fifthbell.com/climate-summit-agreement'
13
+ },
14
+ {
15
+ id: '2',
16
+ headline: 'Breakthrough in Quantum Computing Announced',
17
+ summary: 'Researchers have achieved a major milestone in quantum computing, potentially revolutionizing data processing and encryption technologies.',
18
+ imageUrl: 'https://picsum.photos/seed/news2/1920/1080',
19
+ category: 'Technology',
20
+ url: 'https://fifthbell.com/quantum-computing-breakthrough'
21
+ },
22
+ {
23
+ id: '3',
24
+ headline: 'New Archaeological Discovery Rewrites Ancient History',
25
+ summary: 'Archaeologists have unearthed artifacts that challenge our understanding of ancient civilizations and their technological capabilities.',
26
+ imageUrl: 'https://picsum.photos/seed/news3/1920/1080',
27
+ category: 'Science',
28
+ url: 'https://fifthbell.com/archaeological-discovery'
29
+ },
30
+ {
31
+ id: '4',
32
+ headline: 'International Space Station Mission Extended',
33
+ summary: 'NASA and international partners announce extension of ISS operations, paving the way for continued scientific research in orbit.',
34
+ imageUrl: 'https://picsum.photos/seed/news4/1920/1080',
35
+ category: 'Space',
36
+ url: 'https://fifthbell.com/iss-mission-extended'
37
+ },
38
+ {
39
+ id: '5',
40
+ headline: 'Renewable Energy Surpasses Fossil Fuels',
41
+ summary: 'For the first time in history, renewable energy sources have generated more electricity than traditional fossil fuels globally.',
42
+ imageUrl: 'https://picsum.photos/seed/news5/1920/1080',
43
+ category: 'Energy',
44
+ url: 'https://fifthbell.com/renewable-energy-milestone'
45
+ }
46
+ ];
47
+ function normalizePath(input) {
48
+ if (!input)
49
+ return '/';
50
+ const stripped = input.split('?')[0] || '/';
51
+ const normalized = `/${stripped.replace(/^\/+/, '')}`.replace(/\/{2,}/g, '/');
52
+ return normalized !== '/' && normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;
53
+ }
54
+ function asPathString(value) {
55
+ if (typeof value !== 'string') {
56
+ return undefined;
57
+ }
58
+ const trimmed = value.trim();
59
+ if (!trimmed) {
60
+ return undefined;
61
+ }
62
+ return normalizePath(trimmed);
63
+ }
64
+ function buildArticlePath(article, language) {
65
+ const explicitUrl = asPathString(article.url);
66
+ const bareSlug = typeof article.slug === 'string' ? article.slug.trim() : '';
67
+ const primaryCategorySlug = article.categories?.[0]?.slug?.trim();
68
+ return (explicitUrl ||
69
+ (primaryCategorySlug
70
+ ? language === 'en'
71
+ ? `/${primaryCategorySlug}/${bareSlug.replace(/^\//, '')}`
72
+ : `/${language}/${primaryCategorySlug}/${bareSlug.replace(/^\//, '')}`
73
+ : normalizePath(bareSlug || article.canonicalUrl || '/')));
74
+ }
75
+ export async function fetchArticles(language = 'en') {
76
+ try {
77
+ const response = await fetch(`https://cdn.fifthbell.com/content/homepage-current-${language}.json?_=${Date.now()}`, {
78
+ cache: 'no-store'
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(`Failed to fetch homepage feed: ${response.status} ${response.statusText}`);
82
+ }
83
+ const data = await response.json();
84
+ const now = new Date();
85
+ const recentThreshold = new Date(now.getTime() - 12 * 60 * 60 * 1000);
86
+ const feedArticles = Array.isArray(data.articles) ? data.articles : [];
87
+ const articlesWithImages = feedArticles.filter((article) => article.featuredImage?.url);
88
+ const sortByPublishedDesc = (a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime();
89
+ const featuredRecentArticles = articlesWithImages
90
+ .filter((article) => article.featured === true && new Date(article.publishedAt) >= recentThreshold)
91
+ .sort(sortByPublishedDesc);
92
+ const everythingElseArticles = articlesWithImages
93
+ .filter((article) => !(article.featured === true && new Date(article.publishedAt) >= recentThreshold))
94
+ .sort(sortByPublishedDesc);
95
+ const selectedArticles = [...featuredRecentArticles, ...everythingElseArticles].slice(0, 10);
96
+ const items = selectedArticles.map((article) => ({
97
+ id: article.id.toString(),
98
+ headline: article.title,
99
+ summary: article.excerpt,
100
+ imageUrl: article.featuredImage.url,
101
+ category: article.categories?.[0]?.name,
102
+ url: `https://fifthbell.com${buildArticlePath(article, language)}`
103
+ }));
104
+ const finalItems = items.length > 0 ? items : MOCK_ARTICLES;
105
+ finalItems.forEach((item) => {
106
+ const image = new Image();
107
+ image.crossOrigin = 'anonymous';
108
+ image.src = item.imageUrl;
109
+ });
110
+ return finalItems;
111
+ }
112
+ catch (error) {
113
+ console.error('Error fetching news items:', error);
114
+ return MOCK_ARTICLES;
115
+ }
116
+ }
117
+ function ArticlesSegmentRenderer({ items, itemIndex, progress, language }) {
118
+ const [displayedIndex, setDisplayedIndex] = useState(itemIndex);
119
+ const [isTransitioning, setIsTransitioning] = useState(false);
120
+ const previousIndexRef = useRef(itemIndex);
121
+ useEffect(() => {
122
+ if (itemIndex !== previousIndexRef.current && items.length > 0) {
123
+ setIsTransitioning(true);
124
+ const timer = window.setTimeout(() => {
125
+ setDisplayedIndex(itemIndex);
126
+ setIsTransitioning(false);
127
+ previousIndexRef.current = itemIndex;
128
+ }, 800);
129
+ return () => window.clearTimeout(timer);
130
+ }
131
+ return undefined;
132
+ }, [itemIndex, items.length]);
133
+ if (items.length === 0) {
134
+ return _jsx("div", { className: 'text-white', children: t('articles.noArticles', language) });
135
+ }
136
+ const currentItem = items[itemIndex % items.length];
137
+ const previousItem = items[displayedIndex % items.length];
138
+ return (_jsxs("div", { className: 'relative w-full h-full', children: [isTransitioning && previousItem && (_jsx("div", { className: 'absolute inset-0', children: _jsx(ArticleSlide, { newsItem: previousItem, progress: 100 }) })), _jsx("div", { className: `absolute inset-0 ${isTransitioning ? 'animate-slide-transition' : ''}`, children: _jsx(ArticleSlide, { newsItem: currentItem, progress: progress }) })] }));
139
+ }
140
+ export function createArticlesSegment(articles, onDataUpdate, language = 'en') {
141
+ return {
142
+ id: 'articles',
143
+ label: t('segment.articles', language),
144
+ get itemCount() {
145
+ return articles.length > 0 ? articles.length : MOCK_ARTICLES.length;
146
+ },
147
+ durationMsPerItem: 10000,
148
+ render: (itemIndex, progress) => {
149
+ const items = articles.length > 0 ? articles : MOCK_ARTICLES;
150
+ return _jsx(ArticlesSegmentRenderer, { items: items, itemIndex: itemIndex, progress: progress, language: language }, `article-${itemIndex}`);
151
+ },
152
+ prefetch: async () => {
153
+ if (!onDataUpdate) {
154
+ return;
155
+ }
156
+ const freshArticles = await fetchArticles(language);
157
+ onDataUpdate(freshArticles);
158
+ }
159
+ };
160
+ }
@@ -0,0 +1,16 @@
1
+ import type { Segment } from './types.js';
2
+ import { type SupportedLanguage } from '../i18n.js';
3
+ export interface EarthquakeData {
4
+ id: number;
5
+ magnitude: number;
6
+ location: string;
7
+ depth: number;
8
+ time: string;
9
+ timestamp: number;
10
+ lat: number;
11
+ lng: number;
12
+ sources: string[];
13
+ detailedPlace?: string;
14
+ }
15
+ export declare function fetchEarthquakes(language?: SupportedLanguage): Promise<EarthquakeData[]>;
16
+ export declare function createEarthquakeSegment(earthquakes: EarthquakeData[], onDataUpdate?: (data: EarthquakeData[]) => void, language?: SupportedLanguage): Segment;
@@ -0,0 +1,130 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { AlertTriangle, Clock, Layers, Link, MapPin } from 'lucide-react';
4
+ import { FastAverageColor } from 'fast-average-color';
5
+ import { FIFTHBELL_ASSETS } from '../assets.js';
6
+ import { t } from '../i18n.js';
7
+ function formatTimeAgo(timestamp, language = 'en') {
8
+ const now = Date.now();
9
+ const diffMs = now - timestamp;
10
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
11
+ const diffDays = Math.floor(diffHours / 24);
12
+ if (diffDays > 0) {
13
+ return diffDays === 1 ? t('earthquakes.timeAgo.dayAgo', language) : t('earthquakes.timeAgo.daysAgo', language, { count: diffDays });
14
+ }
15
+ if (diffHours > 0) {
16
+ return diffHours === 1 ? t('earthquakes.timeAgo.hourAgo', language) : t('earthquakes.timeAgo.hoursAgo', language, { count: diffHours });
17
+ }
18
+ const diffMinutes = Math.floor(diffMs / (1000 * 60));
19
+ if (diffMinutes > 0) {
20
+ return diffMinutes === 1 ? t('earthquakes.timeAgo.minuteAgo', language) : t('earthquakes.timeAgo.minutesAgo', language, { count: diffMinutes });
21
+ }
22
+ return t('earthquakes.timeAgo.justNow', language);
23
+ }
24
+ export async function fetchEarthquakes(language = 'en') {
25
+ try {
26
+ const response = await fetch(`https://api.monitor.gaulatti.com/earthquakes?_=${Date.now()}`);
27
+ if (!response.ok) {
28
+ return [];
29
+ }
30
+ const data = await response.json();
31
+ const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
32
+ const recentQuakes = data
33
+ .filter((quake) => quake.magnitude >= 4.5 && new Date(quake.timestamp).getTime() >= oneDayAgo)
34
+ .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
35
+ .slice(0, 6);
36
+ return recentQuakes.map((quake) => {
37
+ const timestamp = new Date(quake.timestamp).getTime();
38
+ let detailedPlace = '';
39
+ const sources = [];
40
+ quake.datapoints.forEach((datapoint) => {
41
+ sources.push(datapoint.source);
42
+ if (datapoint.additionalData?.place && !detailedPlace) {
43
+ detailedPlace = datapoint.additionalData.place;
44
+ }
45
+ else if (datapoint.additionalData?.localPlace && !detailedPlace) {
46
+ detailedPlace = datapoint.additionalData.localPlace;
47
+ }
48
+ });
49
+ return {
50
+ id: quake.id,
51
+ magnitude: Math.round(quake.magnitude * 10) / 10,
52
+ location: detailedPlace || `${quake.latitude.toFixed(2)}°, ${quake.longitude.toFixed(2)}°`,
53
+ depth: Math.round(quake.depth),
54
+ time: formatTimeAgo(timestamp, language),
55
+ timestamp,
56
+ lat: quake.latitude,
57
+ lng: quake.longitude,
58
+ sources: [...new Set(sources)],
59
+ detailedPlace
60
+ };
61
+ });
62
+ }
63
+ catch (error) {
64
+ console.error('Failed to fetch earthquake data:', error);
65
+ return [];
66
+ }
67
+ }
68
+ function getMagnitudeColor(magnitude) {
69
+ if (magnitude >= 7.0)
70
+ return '#dc2626';
71
+ if (magnitude >= 5.0)
72
+ return '#eab308';
73
+ return '#22c55e';
74
+ }
75
+ const fac = new FastAverageColor();
76
+ function EarthquakeSlide({ earthquakes, progress, language }) {
77
+ const [dominantColor, setDominantColor] = useState('#7c2d12');
78
+ const backgroundImage = FIFTHBELL_ASSETS.images.seismograph;
79
+ const handleImageLoad = (event) => {
80
+ try {
81
+ const color = fac.getColor(event.currentTarget);
82
+ setDominantColor(color.hex);
83
+ }
84
+ catch (error) {
85
+ console.error('Error getting average color', error);
86
+ setDominantColor('#7c2d12');
87
+ }
88
+ };
89
+ if (earthquakes.length === 0) {
90
+ return (_jsxs("div", { className: 'absolute inset-0', children: [_jsx("div", { className: 'absolute inset-0 opacity-75', style: { background: 'linear-gradient(to bottom right, #7c2d12, #000000)' } }), _jsx("div", { className: 'relative z-10 h-full flex items-center justify-center', children: _jsx("div", { className: 'text-4xl font-light', children: t('earthquakes.noData', language) }) })] }));
91
+ }
92
+ return (_jsxs("div", { className: 'absolute inset-0 animate-slide-transition', children: [_jsx("div", { className: 'absolute inset-0', children: _jsx("img", { src: backgroundImage, alt: '', crossOrigin: 'anonymous', onLoad: handleImageLoad, className: 'w-full h-full object-cover blur-xl scale-105' }, backgroundImage) }), _jsx("div", { className: 'absolute inset-0 opacity-75 mix-blend-multiply transition-all duration-1000', style: { background: `linear-gradient(to bottom right, ${dominantColor}, #000000)` } }), _jsx("div", { className: 'absolute inset-0 bg-[radial-gradient(circle_at_30%_30%,rgba(255,255,255,0.1),transparent_60%)]' }), _jsxs("div", { className: 'relative z-10 h-full flex flex-col justify-center px-24 py-24', children: [_jsxs("div", { className: 'mb-8 animate-slide-up', children: [_jsx("div", { className: 'w-32 h-2 bg-white mb-8' }), _jsxs("div", { className: 'flex items-center space-x-4 mb-4', children: [_jsx(AlertTriangle, { size: 56, className: 'text-orange-500', strokeWidth: 2 }), _jsx("h1", { className: "text-5xl font-bold tracking-tight leading-tight font-['Encode_Sans']", children: t('earthquakes.header', language) })] }), _jsx("h2", { className: 'text-2xl font-light opacity-90 leading-relaxed', children: t('earthquakes.subtitle', language) })] }), _jsx("div", { className: 'grid grid-cols-2 gap-6 animate-slide-up', children: earthquakes.map((quake, index) => {
93
+ const baseColor = getMagnitudeColor(quake.magnitude);
94
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(baseColor);
95
+ const rgb = result
96
+ ? { r: Number.parseInt(result[1], 16), g: Number.parseInt(result[2], 16), b: Number.parseInt(result[3], 16) }
97
+ : { r: 239, g: 68, b: 68 };
98
+ let magnitudeIntensity;
99
+ if (quake.magnitude < 5.0) {
100
+ magnitudeIntensity = (quake.magnitude - 4.5) / 0.5;
101
+ }
102
+ else if (quake.magnitude < 7.0) {
103
+ magnitudeIntensity = (quake.magnitude - 5.0) / 2.0;
104
+ }
105
+ else {
106
+ magnitudeIntensity = Math.min((quake.magnitude - 7.0) / 1.0, 1);
107
+ }
108
+ const opacity = 0.15 + magnitudeIntensity * 0.35;
109
+ const backgroundColor = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
110
+ return (_jsxs("div", { className: 'grid grid-cols-[auto_1fr] gap-x-12 items-center p-6 backdrop-blur-sm rounded-lg border border-white/5', style: { animationDelay: `${index * 0.05}s`, backgroundColor }, children: [_jsx("div", { className: 'flex flex-col items-center', style: { width: '100px' }, children: _jsxs("div", { className: 'text-5xl font-bold leading-none', children: ["M", quake.magnitude.toFixed(1)] }) }), _jsxs("div", { className: 'flex flex-col justify-center min-w-0', children: [_jsx("h3", { className: "text-3xl font-bold mb-2 line-clamp-2 leading-tight font-['Encode_Sans']", title: quake.location, children: quake.location }), _jsxs("div", { className: 'grid grid-cols-2 gap-x-4 gap-y-1.5 text-xl', children: [_jsxs("div", { className: 'flex items-center space-x-2', children: [_jsx(Clock, { size: 16, className: 'opacity-70 shrink-0' }), _jsx("span", { className: 'truncate', children: quake.time })] }), _jsxs("div", { className: 'flex items-center space-x-2', children: [_jsx(Layers, { size: 16, className: 'opacity-70 shrink-0' }), _jsxs("span", { className: 'truncate', children: [quake.depth, " ", t('earthquakes.depth', language)] })] }), _jsxs("div", { className: 'flex items-center space-x-2 opacity-80', children: [_jsx(MapPin, { size: 16, className: 'opacity-70 shrink-0' }), _jsxs("span", { className: 'truncate', children: [quake.lat.toFixed(2), "\u00B0, ", quake.lng.toFixed(2), "\u00B0"] })] }), quake.sources.length > 0 && (_jsxs("div", { className: 'flex items-center space-x-2 opacity-80', children: [_jsx(Link, { size: 16, className: 'opacity-70 shrink-0' }), _jsx("span", { className: 'truncate', children: quake.sources.join(', ') })] }))] })] })] }, quake.id));
111
+ }) }), _jsx("div", { className: 'absolute bottom-24 left-24 right-24 h-1 bg-white/30', children: _jsx("div", { className: 'h-full bg-white transition-all duration-100 ease-linear', style: { width: `${progress}%` } }) })] })] }));
112
+ }
113
+ function EarthquakeLoadingSlide({ language }) {
114
+ return (_jsxs("div", { className: 'absolute inset-0', children: [_jsx("div", { className: 'absolute inset-0 opacity-75', style: { background: 'linear-gradient(to bottom right, #7c2d12, #000000)' } }), _jsx("div", { className: 'relative z-10 h-full flex items-center justify-center', children: _jsx("div", { className: 'text-4xl font-light animate-pulse', children: t('earthquakes.loading', language) }) })] }));
115
+ }
116
+ export function createEarthquakeSegment(earthquakes, onDataUpdate, language = 'en') {
117
+ return {
118
+ id: 'earthquakes',
119
+ label: t('segment.earthquakes', language),
120
+ itemCount: 1,
121
+ durationMsPerItem: 10000,
122
+ render: (itemIndex, progress) => earthquakes.length === 0 ? (_jsx(EarthquakeLoadingSlide, { language: language }, 'earthquake-loading')) : (_jsx(EarthquakeSlide, { earthquakes: earthquakes, progress: progress, language: language }, `earthquake-${itemIndex}`)),
123
+ prefetch: async () => {
124
+ if (!onDataUpdate) {
125
+ return;
126
+ }
127
+ onDataUpdate(await fetchEarthquakes(language));
128
+ }
129
+ };
130
+ }
@@ -0,0 +1,12 @@
1
+ import type { Segment } from './types.js';
2
+ import { type SupportedLanguage } from '../i18n.js';
3
+ export interface MarketData {
4
+ symbol: string;
5
+ name: string;
6
+ price: number;
7
+ change: number;
8
+ changePercent: number;
9
+ }
10
+ export declare function fetchMarketData(): Promise<MarketData[]>;
11
+ export declare function getLastUpdateTime(): string;
12
+ export declare function createMarketsSegment(marketData: MarketData[], onDataUpdate?: (data: MarketData[]) => void, language?: SupportedLanguage): Segment;
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { TrendingDown, TrendingUp } from 'lucide-react';
4
+ import { FastAverageColor } from 'fast-average-color';
5
+ import { FIFTHBELL_ASSETS } from '../assets.js';
6
+ import { t } from '../i18n.js';
7
+ let marketCache = null;
8
+ const CACHE_DURATION_MS = 15 * 60 * 1000;
9
+ export async function fetchMarketData() {
10
+ const now = Date.now();
11
+ if (marketCache && now - marketCache.timestamp < CACHE_DURATION_MS) {
12
+ return marketCache.data;
13
+ }
14
+ try {
15
+ const response = await fetch(`https://financialmodelingprep.com/stable/most-actives?apikey=a8mpam9ffQSf9ROfBBKovbPHRw25qtNH&_=${Date.now()}`);
16
+ if (!response.ok) {
17
+ return marketCache?.data || [];
18
+ }
19
+ const data = await response.json();
20
+ const marketData = data
21
+ .filter((stock) => stock.price > 1 && Math.abs(stock.changesPercentage) < 200)
22
+ .sort((a, b) => Math.abs(b.changesPercentage) - Math.abs(a.changesPercentage))
23
+ .slice(0, 6)
24
+ .map((stock) => ({
25
+ symbol: stock.symbol,
26
+ name: stock.name,
27
+ price: stock.price,
28
+ change: stock.change,
29
+ changePercent: stock.changesPercentage
30
+ }));
31
+ marketCache = {
32
+ data: marketData,
33
+ timestamp: now,
34
+ updateTime: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
35
+ };
36
+ return marketData;
37
+ }
38
+ catch (error) {
39
+ console.error('[MarketsSegment] Failed to fetch market data:', error);
40
+ return marketCache?.data || [];
41
+ }
42
+ }
43
+ export function getLastUpdateTime() {
44
+ return marketCache?.updateTime || '--:--';
45
+ }
46
+ const fac = new FastAverageColor();
47
+ function MarketSlide({ progress, marketData, language }) {
48
+ const [dominantColor, setDominantColor] = useState('#065f46');
49
+ const backgroundImage = FIFTHBELL_ASSETS.images.nyse;
50
+ const handleImageLoad = (event) => {
51
+ try {
52
+ const color = fac.getColor(event.currentTarget);
53
+ setDominantColor(color.hex);
54
+ }
55
+ catch (error) {
56
+ console.error('Error getting average color', error);
57
+ setDominantColor('#065f46');
58
+ }
59
+ };
60
+ if (marketData.length === 0) {
61
+ return (_jsxs("div", { className: 'absolute inset-0', children: [_jsx("div", { className: 'absolute inset-0 opacity-75', style: { background: 'linear-gradient(to bottom right, #065f46, #000000)' } }), _jsx("div", { className: 'relative z-10 h-full flex items-center justify-center', children: _jsx("div", { className: 'text-4xl font-light animate-pulse', children: t('markets.loading', language) }) })] }));
62
+ }
63
+ return (_jsxs("div", { className: 'absolute inset-0 animate-slide-transition', children: [_jsx("div", { className: 'absolute inset-0', children: _jsx("img", { src: backgroundImage, alt: '', crossOrigin: 'anonymous', onLoad: handleImageLoad, className: 'w-full h-full object-cover blur-xl scale-105' }, backgroundImage) }), _jsx("div", { className: 'absolute inset-0 opacity-75 mix-blend-multiply transition-all duration-1000', style: { background: `linear-gradient(to bottom right, ${dominantColor}, #000000)` } }), _jsx("div", { className: 'absolute inset-0 bg-[radial-gradient(circle_at_30%_30%,rgba(255,255,255,0.1),transparent_60%)]' }), _jsxs("div", { className: 'relative z-10 h-full flex flex-col justify-center px-24', children: [_jsxs("div", { className: 'mb-12 animate-slide-up', children: [_jsx("div", { className: 'w-32 h-2 bg-white mb-12' }), _jsx("h1", { className: "text-5xl font-bold tracking-tight mb-8 leading-tight font-['Encode_Sans']", children: t('markets.header', language) }), _jsx("h2", { className: 'text-3xl font-light opacity-90 leading-relaxed', children: t('markets.subtitle', language) })] }), _jsx("div", { className: 'grid grid-cols-2 gap-4 animate-slide-up', children: marketData.map((stock, index) => {
64
+ const isPositive = stock.change >= 0;
65
+ const changeColor = isPositive ? 'text-green-400' : 'text-red-400';
66
+ const Icon = isPositive ? TrendingUp : TrendingDown;
67
+ const logIntensity = Math.log10(1 + Math.abs(stock.changePercent)) / Math.log10(1 + 100);
68
+ const opacity = 0.15 + Math.min(logIntensity, 1) * 0.35;
69
+ const backgroundColor = isPositive ? `rgba(34, 197, 94, ${opacity})` : `rgba(239, 68, 68, ${opacity})`;
70
+ return (_jsxs("div", { className: 'grid grid-cols-2 gap-x-12 items-center p-6 backdrop-blur-sm rounded-lg border border-white/5', style: { animationDelay: `${index * 0.05}s`, backgroundColor }, children: [_jsxs("div", { className: 'flex flex-col', children: [_jsx("span", { className: "text-4xl font-bold mb-1 font-['Encode_Sans']", children: stock.symbol }), _jsx("span", { className: 'text-2xl opacity-70', children: stock.name })] }), _jsxs("div", { className: 'flex flex-col items-end', children: [_jsxs("div", { className: "text-5xl font-bold mb-1 font-['Encode_Sans']", children: ["$", stock.price.toFixed(2)] }), _jsxs("div", { className: `flex items-center space-x-2 ${changeColor}`, children: [_jsx(Icon, { size: 24, strokeWidth: 2 }), _jsxs("span", { className: 'text-3xl font-bold', children: [isPositive ? '+' : '', stock.change.toFixed(2)] }), _jsxs("span", { className: 'text-2xl opacity-80', children: ["(", isPositive ? '+' : '', stock.changePercent.toFixed(2), "%)"] })] })] })] }, stock.symbol));
71
+ }) }), _jsx("div", { className: 'mt-6 mb-24 text-xl opacity-60 animate-fade-in-delay', children: _jsx("p", { children: t('markets.lastUpdate', language, { time: getLastUpdateTime() }) }) }), _jsx("div", { className: 'absolute bottom-24 left-24 right-24 h-1 bg-white/30', children: _jsx("div", { className: 'h-full bg-white transition-all duration-100 ease-linear', style: { width: `${progress}%` } }) })] })] }));
72
+ }
73
+ export function createMarketsSegment(marketData, onDataUpdate, language = 'en') {
74
+ return {
75
+ id: 'markets',
76
+ label: t('segment.markets', language),
77
+ itemCount: 1,
78
+ durationMsPerItem: 10000,
79
+ render: (itemIndex, progress) => (_jsx(MarketSlide, { progress: progress, marketData: marketData, language: language }, `market-${itemIndex}`)),
80
+ prefetch: async () => {
81
+ if (!onDataUpdate) {
82
+ return;
83
+ }
84
+ onDataUpdate(await fetchMarketData());
85
+ }
86
+ };
87
+ }
@@ -0,0 +1,15 @@
1
+ import type { Segment } from './types.js';
2
+ import { type SupportedLanguage } from '../i18n.js';
3
+ export interface WeatherCityData {
4
+ name: string;
5
+ temp: number;
6
+ condition: 'sunny' | 'cloudy' | 'rainy' | 'windy';
7
+ high: number;
8
+ low: number;
9
+ }
10
+ export interface WeatherRegionData {
11
+ region: string;
12
+ cities: WeatherCityData[];
13
+ }
14
+ export declare function fetchWeatherData(): Promise<WeatherRegionData[]>;
15
+ export declare function createWeatherSegment(weatherData: WeatherRegionData[], onDataUpdate?: (data: WeatherRegionData[]) => void, language?: SupportedLanguage): Segment;
@@ -0,0 +1,184 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Cloud, CloudRain, Sun, Wind } from 'lucide-react';
4
+ import { FastAverageColor } from 'fast-average-color';
5
+ import { FIFTHBELL_ASSETS } from '../assets.js';
6
+ import { t } from '../i18n.js';
7
+ const CITY_LOCATIONS = {
8
+ 'New York': { lat: 40.7128, lon: -74.006 },
9
+ 'San Juan': { lat: 18.4655, lon: -66.1057 },
10
+ 'Los Angeles': { lat: 34.0522, lon: -118.2437 },
11
+ Honolulu: { lat: 21.3099, lon: -157.8581 },
12
+ 'Mexico City': { lat: 19.4326, lon: -99.1332 },
13
+ Havana: { lat: 23.1136, lon: -82.3666 },
14
+ London: { lat: 51.5074, lon: -0.1278 },
15
+ Paris: { lat: 48.8566, lon: 2.3522 },
16
+ Berlin: { lat: 52.52, lon: 13.405 },
17
+ Rome: { lat: 41.9028, lon: 12.4964 },
18
+ Madrid: { lat: 40.4168, lon: -3.7038 },
19
+ Athens: { lat: 37.9838, lon: 23.7275 },
20
+ Santiago: { lat: -33.4489, lon: -70.6693 },
21
+ 'Buenos Aires': { lat: -34.6037, lon: -58.3816 },
22
+ Rio: { lat: -22.9068, lon: -43.1729 },
23
+ Lima: { lat: -12.0464, lon: -77.0428 },
24
+ Caracas: { lat: 10.4806, lon: -66.9036 },
25
+ Bogotá: { lat: 4.711, lon: -74.0721 },
26
+ Tokyo: { lat: 35.6762, lon: 139.6503 },
27
+ Seoul: { lat: 37.5665, lon: 126.978 },
28
+ Shanghai: { lat: 31.2304, lon: 121.4737 },
29
+ 'Hong Kong': { lat: 22.3193, lon: 114.1694 },
30
+ Bangkok: { lat: 13.7563, lon: 100.5018 },
31
+ Jakarta: { lat: -6.2088, lon: 106.8456 }
32
+ };
33
+ const REGIONS = [
34
+ { region: 'North America', cities: ['New York', 'San Juan', 'Los Angeles', 'Honolulu', 'Mexico City', 'Havana'] },
35
+ { region: 'Europe', cities: ['Rome', 'Berlin', 'Paris', 'Madrid', 'London', 'Athens'] },
36
+ { region: 'South America', cities: ['Santiago', 'Buenos Aires', 'Rio', 'Lima', 'Caracas', 'Bogotá'] },
37
+ { region: 'Asia', cities: ['Tokyo', 'Seoul', 'Shanghai', 'Hong Kong', 'Bangkok', 'Jakarta'] }
38
+ ];
39
+ function getWeatherCondition(weatherCode) {
40
+ if (weatherCode === 0 || weatherCode === 1)
41
+ return 'sunny';
42
+ if (weatherCode === 2 || weatherCode === 3)
43
+ return 'cloudy';
44
+ if (weatherCode >= 51 && weatherCode <= 67)
45
+ return 'rainy';
46
+ if (weatherCode >= 71 && weatherCode <= 86)
47
+ return 'rainy';
48
+ if (weatherCode >= 95)
49
+ return 'rainy';
50
+ return 'cloudy';
51
+ }
52
+ async function fetchCityWeather(cityName) {
53
+ const location = CITY_LOCATIONS[cityName];
54
+ if (!location)
55
+ return null;
56
+ try {
57
+ const response = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${location.lat}&longitude=${location.lon}&current=temperature_2m,weather_code&daily=temperature_2m_max,temperature_2m_min&temperature_unit=fahrenheit&timezone=auto`);
58
+ if (!response.ok) {
59
+ return null;
60
+ }
61
+ const data = await response.json();
62
+ return {
63
+ name: cityName,
64
+ temp: Math.round(data.current.temperature_2m),
65
+ condition: getWeatherCondition(data.current.weather_code),
66
+ high: Math.round(data.daily.temperature_2m_max[0]),
67
+ low: Math.round(data.daily.temperature_2m_min[0])
68
+ };
69
+ }
70
+ catch (error) {
71
+ console.error(`Failed to fetch weather for ${cityName}:`, error);
72
+ return null;
73
+ }
74
+ }
75
+ let weatherCache = null;
76
+ const CACHE_DURATION_MS = 60 * 60 * 1000;
77
+ export async function fetchWeatherData() {
78
+ const now = Date.now();
79
+ if (weatherCache && now - weatherCache.timestamp < CACHE_DURATION_MS) {
80
+ return weatherCache.data;
81
+ }
82
+ const results = [];
83
+ for (const regionDef of REGIONS) {
84
+ const cityResults = await Promise.all(regionDef.cities.map((city) => fetchCityWeather(city)));
85
+ const cities = cityResults.filter((city) => city !== null);
86
+ if (cities.length > 0) {
87
+ results.push({ region: regionDef.region, cities });
88
+ }
89
+ }
90
+ weatherCache = { data: results, timestamp: now };
91
+ return results;
92
+ }
93
+ function getWeatherIcon(condition, size = 48) {
94
+ const iconProps = { size, strokeWidth: 2 };
95
+ switch (condition) {
96
+ case 'sunny':
97
+ return _jsx(Sun, { ...iconProps, className: 'text-yellow-400' });
98
+ case 'rainy':
99
+ return _jsx(CloudRain, { ...iconProps, className: 'text-blue-400' });
100
+ case 'windy':
101
+ return _jsx(Wind, { ...iconProps, className: 'text-gray-400' });
102
+ default:
103
+ return _jsx(Cloud, { ...iconProps, className: 'text-gray-300' });
104
+ }
105
+ }
106
+ function fahrenheitToCelsius(value) {
107
+ return Math.round(((value - 32) * 5) / 9);
108
+ }
109
+ function getRegionBackground(region) {
110
+ switch (region) {
111
+ case 'North America':
112
+ return FIFTHBELL_ASSETS.images.nyc;
113
+ case 'Europe':
114
+ return FIFTHBELL_ASSETS.images.berlin;
115
+ case 'South America':
116
+ return FIFTHBELL_ASSETS.images.santiago;
117
+ case 'Asia':
118
+ return FIFTHBELL_ASSETS.images.tokyo;
119
+ default:
120
+ return '';
121
+ }
122
+ }
123
+ const fac = new FastAverageColor();
124
+ function WeatherSlide({ weatherData, progress, unit, language }) {
125
+ const [dominantColor, setDominantColor] = useState('#1e40af');
126
+ const [isTransitioning, setIsTransitioning] = useState(false);
127
+ const [displayUnit, setDisplayUnit] = useState(unit);
128
+ const backgroundImage = getRegionBackground(weatherData.region);
129
+ const unitLabel = displayUnit === 'fahrenheit' ? '°F' : '°C';
130
+ useEffect(() => {
131
+ if (unit !== displayUnit) {
132
+ setIsTransitioning(true);
133
+ const timer = window.setTimeout(() => {
134
+ setDisplayUnit(unit);
135
+ setIsTransitioning(false);
136
+ }, 300);
137
+ return () => window.clearTimeout(timer);
138
+ }
139
+ return undefined;
140
+ }, [unit, displayUnit]);
141
+ const handleImageLoad = (event) => {
142
+ try {
143
+ const color = fac.getColor(event.currentTarget);
144
+ setDominantColor(color.hex);
145
+ }
146
+ catch (error) {
147
+ console.error('Error getting average color', error);
148
+ setDominantColor('#1e40af');
149
+ }
150
+ };
151
+ return (_jsxs("div", { className: 'absolute inset-0 animate-slide-transition', children: [backgroundImage && (_jsx("div", { className: 'absolute inset-0', children: _jsx("img", { src: backgroundImage, alt: '', crossOrigin: 'anonymous', onLoad: handleImageLoad, className: 'w-full h-full object-cover blur-xl scale-105' }, backgroundImage) })), _jsx("div", { className: 'absolute inset-0 opacity-75 mix-blend-multiply transition-all duration-1000', style: { background: `linear-gradient(to bottom right, ${dominantColor}, #000000)` } }), _jsx("div", { className: 'absolute inset-0 bg-[radial-gradient(circle_at_30%_30%,rgba(255,255,255,0.1),transparent_60%)]' }), _jsxs("div", { className: 'relative z-10 h-full flex flex-col justify-center px-24', children: [_jsxs("div", { className: 'mb-12 animate-slide-up', children: [_jsx("div", { className: 'w-32 h-2 bg-white mb-12' }), _jsx("h1", { className: "text-5xl font-bold tracking-tight mb-8 leading-tight font-['Encode_Sans']", children: t('weather.header', language) }), _jsx("h2", { className: 'text-3xl font-light opacity-90 leading-relaxed', children: t(`region.${weatherData.region}`, language) })] }), _jsx("div", { className: 'grid grid-cols-2 gap-8 animate-slide-up', children: weatherData.cities.map((city, index) => {
152
+ const temp = displayUnit === 'celsius' ? fahrenheitToCelsius(city.temp) : city.temp;
153
+ const high = displayUnit === 'celsius' ? fahrenheitToCelsius(city.high) : city.high;
154
+ const low = displayUnit === 'celsius' ? fahrenheitToCelsius(city.low) : city.low;
155
+ return (_jsxs("div", { className: 'flex items-center space-x-6 p-6 bg-white/10 backdrop-blur-sm rounded-lg', style: { animationDelay: `${index * 0.1}s` }, children: [_jsx("div", { className: 'shrink-0', children: getWeatherIcon(city.condition, 48) }), _jsxs("div", { className: 'flex-1', children: [_jsx("h3", { className: "text-3xl font-bold mb-2 font-['Encode_Sans']", children: city.name }), _jsxs("span", { className: "text-5xl font-bold transition-opacity duration-300 font-['Encode_Sans']", style: { opacity: isTransitioning ? 0 : 1 }, children: [temp, unitLabel] })] }), _jsxs("div", { className: 'flex flex-col items-end space-y-1', children: [_jsxs("span", { className: 'text-2xl opacity-70 transition-opacity duration-300', style: { opacity: isTransitioning ? 0 : 0.7 }, children: [t('weather.high', language), " ", high, unitLabel] }), _jsxs("span", { className: 'text-2xl opacity-70 transition-opacity duration-300', style: { opacity: isTransitioning ? 0 : 0.7 }, children: [t('weather.low', language), " ", low, unitLabel] })] })] }, city.name));
156
+ }) }), _jsx("div", { className: 'absolute bottom-24 left-24 right-24 h-1 bg-white/30', children: _jsx("div", { className: 'h-full bg-white transition-all duration-100 ease-linear', style: { width: `${progress}%` } }) })] })] }));
157
+ }
158
+ function WeatherLoadingSlide({ language }) {
159
+ return (_jsxs("div", { className: 'absolute inset-0', children: [_jsx("div", { className: 'absolute inset-0 opacity-75', style: { background: 'linear-gradient(to bottom right, #1e40af, #000000)' } }), _jsx("div", { className: 'relative z-10 h-full flex items-center justify-center', children: _jsx("div", { className: 'text-4xl font-light animate-pulse', children: t('weather.loading', language) }) })] }));
160
+ }
161
+ export function createWeatherSegment(weatherData, onDataUpdate, language = 'en') {
162
+ return {
163
+ id: 'weather',
164
+ label: t('segment.weather', language),
165
+ get itemCount() {
166
+ return weatherData.length > 0 ? weatherData.length * 2 : 8;
167
+ },
168
+ durationMsPerItem: 5000,
169
+ render: (itemIndex, progress) => {
170
+ if (weatherData.length === 0) {
171
+ return _jsx(WeatherLoadingSlide, { language: language }, 'weather-loading');
172
+ }
173
+ const regionIndex = Math.floor(itemIndex / 2) % weatherData.length;
174
+ const unit = itemIndex % 2 === 0 ? 'fahrenheit' : 'celsius';
175
+ return _jsx(WeatherSlide, { weatherData: weatherData[regionIndex], progress: progress, unit: unit, language: language }, `weather-${regionIndex}`);
176
+ },
177
+ prefetch: async () => {
178
+ if (!onDataUpdate) {
179
+ return;
180
+ }
181
+ onDataUpdate(await fetchWeatherData());
182
+ }
183
+ };
184
+ }
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export * from './usePlaylistEngine.js';
3
+ export { createArticlesSegment, fetchArticles, type NewsItem } from './ArticlesSegment.js';
4
+ export { createWeatherSegment, fetchWeatherData, type WeatherRegionData, type WeatherCityData } from './WeatherSegment.js';
5
+ export { createEarthquakeSegment, fetchEarthquakes, type EarthquakeData } from './EarthquakeSegment.js';
6
+ export * from './MarketsSegment.js';
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export * from './usePlaylistEngine.js';
3
+ export { createArticlesSegment, fetchArticles } from './ArticlesSegment.js';
4
+ export { createWeatherSegment, fetchWeatherData } from './WeatherSegment.js';
5
+ export { createEarthquakeSegment, fetchEarthquakes } from './EarthquakeSegment.js';
6
+ export * from './MarketsSegment.js';