@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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/carousels.d.ts +1 -0
- package/dist/carousels.js +65 -0
- package/dist/components/live-program/LiveProgram.d.ts +8 -0
- package/dist/components/live-program/LiveProgram.js +526 -0
- package/dist/components/live-program/assets.d.ts +14 -0
- package/dist/components/live-program/assets.js +14 -0
- package/dist/components/live-program/components/Marquee.d.ts +16 -0
- package/dist/components/live-program/components/Marquee.js +88 -0
- package/dist/components/live-program/components/MarqueeCurtain.d.ts +5 -0
- package/dist/components/live-program/components/MarqueeCurtain.js +30 -0
- package/dist/components/live-program/components/WorldClocks.d.ts +19 -0
- package/dist/components/live-program/components/WorldClocks.js +101 -0
- package/dist/components/live-program/components/slides/ArticleSlide.d.ts +14 -0
- package/dist/components/live-program/components/slides/ArticleSlide.js +22 -0
- package/dist/components/live-program/components/slides/CallsignSlide.d.ts +6 -0
- package/dist/components/live-program/components/slides/CallsignSlide.js +49 -0
- package/dist/components/live-program/components/slides/slideStyles.d.ts +1 -0
- package/dist/components/live-program/components/slides/slideStyles.js +64 -0
- package/dist/components/live-program/events.d.ts +34 -0
- package/dist/components/live-program/events.js +167 -0
- package/dist/components/live-program/hooks/useSSE.d.ts +11 -0
- package/dist/components/live-program/hooks/useSSE.js +67 -0
- package/dist/components/live-program/i18n.d.ts +4 -0
- package/dist/components/live-program/i18n.js +290 -0
- package/dist/components/live-program/segments/ArticlesSegment.d.ts +6 -0
- package/dist/components/live-program/segments/ArticlesSegment.js +160 -0
- package/dist/components/live-program/segments/EarthquakeSegment.d.ts +16 -0
- package/dist/components/live-program/segments/EarthquakeSegment.js +130 -0
- package/dist/components/live-program/segments/MarketsSegment.d.ts +12 -0
- package/dist/components/live-program/segments/MarketsSegment.js +87 -0
- package/dist/components/live-program/segments/WeatherSegment.d.ts +15 -0
- package/dist/components/live-program/segments/WeatherSegment.js +184 -0
- package/dist/components/live-program/segments/index.d.ts +6 -0
- package/dist/components/live-program/segments/index.js +6 -0
- package/dist/components/live-program/segments/types.d.ts +23 -0
- package/dist/components/live-program/segments/types.js +1 -0
- package/dist/components/live-program/segments/usePlaylistEngine.d.ts +9 -0
- package/dist/components/live-program/segments/usePlaylistEngine.js +108 -0
- package/dist/components/live-program/utils/broadcastTime.d.ts +12 -0
- package/dist/components/live-program/utils/broadcastTime.js +33 -0
- package/dist/homepage-distributor.d.ts +55 -0
- package/dist/homepage-distributor.js +68 -0
- package/dist/instagram-image-template.d.ts +8 -0
- package/dist/instagram-image-template.js +200 -0
- package/dist/outlet-config.d.ts +23 -0
- package/dist/outlet-config.js +23 -0
- package/dist/renderer.browser.d.ts +2 -0
- package/dist/renderer.browser.js +128 -0
- package/dist/renderer.core.d.ts +9 -0
- package/dist/renderer.core.js +353 -0
- package/dist/renderer.d.ts +3 -0
- package/dist/renderer.js +3 -0
- package/dist/renderer.node.d.ts +2 -0
- package/dist/renderer.node.js +71 -0
- package/dist/types/canonical-article.d.ts +247 -0
- package/dist/types/canonical-article.js +235 -0
- package/dist/utils/sofascore.d.ts +3 -0
- package/dist/utils/sofascore.js +31 -0
- package/package.json +78 -0
- package/src/partial-deps.json +52 -0
- package/src/styles/compiled.css +2 -0
- package/src/templates/layouts/404.hbs +5 -0
- package/src/templates/layouts/article-page.hbs +5 -0
- package/src/templates/layouts/category-page.hbs +5 -0
- package/src/templates/layouts/homepage.hbs +5 -0
- package/src/templates/layouts/link-in-bio.hbs +228 -0
- package/src/templates/layouts/live-story.hbs +5 -0
- package/src/templates/layouts/search-page.hbs +5 -0
- package/src/templates/partials/blocks/audio.hbs +12 -0
- package/src/templates/partials/blocks/data-table.hbs +23 -0
- package/src/templates/partials/blocks/divider.hbs +1 -0
- package/src/templates/partials/blocks/heading.hbs +9 -0
- package/src/templates/partials/blocks/image.hbs +6 -0
- package/src/templates/partials/blocks/info-box.hbs +8 -0
- package/src/templates/partials/blocks/instagram.hbs +28 -0
- package/src/templates/partials/blocks/key-points.hbs +8 -0
- package/src/templates/partials/blocks/list.hbs +13 -0
- package/src/templates/partials/blocks/live-update.hbs +24 -0
- package/src/templates/partials/blocks/pull-quote.hbs +6 -0
- package/src/templates/partials/blocks/rich-text.hbs +1 -0
- package/src/templates/partials/blocks/tiktok.hbs +15 -0
- package/src/templates/partials/blocks/x.hbs +74 -0
- package/src/templates/partials/blocks/youtube.hbs +12 -0
- package/src/templates/partials/components/article-main.hbs +159 -0
- package/src/templates/partials/components/breaking-news/live-updates-column.hbs +29 -0
- package/src/templates/partials/components/breaking-news.hbs +56 -0
- package/src/templates/partials/components/category/header.hbs +5 -0
- package/src/templates/partials/components/category/main-grid.hbs +55 -0
- package/src/templates/partials/components/category/main.hbs +7 -0
- package/src/templates/partials/components/category/more-grid.hbs +26 -0
- package/src/templates/partials/components/editorial-hero.hbs +73 -0
- package/src/templates/partials/components/headline.hbs +15 -0
- package/src/templates/partials/components/hero-editorial.hbs +1 -0
- package/src/templates/partials/components/hero.hbs +1 -0
- package/src/templates/partials/components/home/landing.hbs +111 -0
- package/src/templates/partials/components/home/main.hbs +63 -0
- package/src/templates/partials/components/home/more-stories.hbs +23 -0
- package/src/templates/partials/components/home/must-read.hbs +77 -0
- package/src/templates/partials/components/live-story/main.hbs +229 -0
- package/src/templates/partials/components/not-found/main.hbs +28 -0
- package/src/templates/partials/components/search/main.hbs +420 -0
- package/src/templates/partials/components/snack.hbs +92 -0
- package/src/templates/partials/components/spotlight-hero.hbs +59 -0
- package/src/templates/partials/components/trending.hbs +14 -0
- package/src/templates/partials/components/ui/accordion.hbs +30 -0
- package/src/templates/partials/components/ui/breadcrumb.hbs +16 -0
- package/src/templates/partials/components/ui/icon-button.hbs +19 -0
- package/src/templates/partials/components/ui/loading-spinner.hbs +27 -0
- package/src/templates/partials/components/ui/pagination.hbs +56 -0
- package/src/templates/partials/components/ui/scroll-area.hbs +12 -0
- package/src/templates/partials/components/ui/status-badge.hbs +21 -0
- package/src/templates/partials/footers/footer-full.hbs +79 -0
- package/src/templates/partials/footers/footer-minimal.hbs +5 -0
- package/src/templates/partials/headers/header-main.hbs +397 -0
- package/src/templates/partials/headers/header-minimal.hbs +16 -0
- package/src/templates/partials/nav/nav-categories.hbs +5 -0
- package/src/templates/partials/shell/doc-end.hbs +282 -0
- package/src/templates/partials/shell/doc-start-404.hbs +28 -0
- package/src/templates/partials/shell/doc-start-standard.hbs +68 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const FIFTHBELL_ASSETS: {
|
|
2
|
+
readonly audio: {
|
|
3
|
+
readonly pipes: "/fifthbell/audio/pipes.ogg";
|
|
4
|
+
};
|
|
5
|
+
readonly images: {
|
|
6
|
+
readonly logo: "/fifthbell/images/fifthbell.png";
|
|
7
|
+
readonly nyc: "/fifthbell/images/nyc.jpg";
|
|
8
|
+
readonly berlin: "/fifthbell/images/berlin.jpg";
|
|
9
|
+
readonly santiago: "/fifthbell/images/santiago.jpg";
|
|
10
|
+
readonly tokyo: "/fifthbell/images/tokyo.jpg";
|
|
11
|
+
readonly nyse: "/fifthbell/images/nyse.jpg";
|
|
12
|
+
readonly seismograph: "/fifthbell/images/seismograph.jpg";
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const FIFTHBELL_ASSETS = {
|
|
2
|
+
audio: {
|
|
3
|
+
pipes: '/fifthbell/audio/pipes.ogg'
|
|
4
|
+
},
|
|
5
|
+
images: {
|
|
6
|
+
logo: '/fifthbell/images/fifthbell.png',
|
|
7
|
+
nyc: '/fifthbell/images/nyc.jpg',
|
|
8
|
+
berlin: '/fifthbell/images/berlin.jpg',
|
|
9
|
+
santiago: '/fifthbell/images/santiago.jpg',
|
|
10
|
+
tokyo: '/fifthbell/images/tokyo.jpg',
|
|
11
|
+
nyse: '/fifthbell/images/nyse.jpg',
|
|
12
|
+
seismograph: '/fifthbell/images/seismograph.jpg'
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Event } from '../events.js';
|
|
2
|
+
interface MarqueeProps {
|
|
3
|
+
events: Event[];
|
|
4
|
+
onCycleComplete?: () => void;
|
|
5
|
+
minPostsCount?: number;
|
|
6
|
+
minAverageRelevance?: number;
|
|
7
|
+
minMedianRelevance?: number;
|
|
8
|
+
pixelsPerSecond?: number;
|
|
9
|
+
minDurationSeconds?: number;
|
|
10
|
+
heightPx?: number;
|
|
11
|
+
backgroundColor?: string;
|
|
12
|
+
borderColor?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function Marquee({ events, onCycleComplete, minPostsCount, minAverageRelevance, minMedianRelevance, pixelsPerSecond, minDurationSeconds, heightPx, backgroundColor, borderColor }: MarqueeProps): import("react/jsx-runtime").JSX.Element | null;
|
|
15
|
+
declare const _default: import("react").MemoExoticComponent<typeof Marquee>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BellRing } from 'lucide-react';
|
|
3
|
+
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
function calculateMedianRelevance(posts) {
|
|
5
|
+
if (posts.length === 0)
|
|
6
|
+
return 0;
|
|
7
|
+
const scores = posts.map((post) => post.relevance).sort((a, b) => a - b);
|
|
8
|
+
const mid = Math.floor(scores.length / 2);
|
|
9
|
+
return scores.length % 2 === 0 ? (scores[mid - 1] + scores[mid]) / 2 : scores[mid];
|
|
10
|
+
}
|
|
11
|
+
function calculateAverageRelevance(posts) {
|
|
12
|
+
if (posts.length === 0)
|
|
13
|
+
return 0;
|
|
14
|
+
const sum = posts.reduce((acc, post) => acc + post.relevance, 0);
|
|
15
|
+
return sum / posts.length;
|
|
16
|
+
}
|
|
17
|
+
export function Marquee({ events, onCycleComplete, minPostsCount = 4, minAverageRelevance = 5, minMedianRelevance = 7, pixelsPerSecond = 150, minDurationSeconds = 10, heightPx = 72, backgroundColor = 'rgba(0, 0, 0, 0.6)', borderColor = '#b21100' }) {
|
|
18
|
+
const contentRef = useRef(null);
|
|
19
|
+
const [animationDuration, setAnimationDuration] = useState('20s');
|
|
20
|
+
const sortedEvents = useMemo(() => {
|
|
21
|
+
return events
|
|
22
|
+
.filter((event) => {
|
|
23
|
+
const postsCount = typeof event.posts_count === 'number' ? event.posts_count : event.posts.length;
|
|
24
|
+
const average = calculateAverageRelevance(event.posts);
|
|
25
|
+
const median = calculateMedianRelevance(event.posts);
|
|
26
|
+
const postsThreshold = minPostsCount <= 0 || postsCount >= minPostsCount;
|
|
27
|
+
const averageThreshold = minAverageRelevance <= 0 || average >= minAverageRelevance;
|
|
28
|
+
const medianThreshold = minMedianRelevance <= 0 || median >= minMedianRelevance;
|
|
29
|
+
return postsThreshold && averageThreshold && medianThreshold;
|
|
30
|
+
})
|
|
31
|
+
.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
32
|
+
}, [events, minPostsCount, minAverageRelevance, minMedianRelevance]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const updateDuration = () => {
|
|
35
|
+
if (!contentRef.current) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const contentWidth = contentRef.current.scrollWidth;
|
|
39
|
+
const totalDistance = 1920 + contentWidth;
|
|
40
|
+
const duration = Math.max(totalDistance / Math.max(1, pixelsPerSecond), minDurationSeconds);
|
|
41
|
+
setAnimationDuration(`${duration}s`);
|
|
42
|
+
};
|
|
43
|
+
const timer = window.setTimeout(updateDuration, 100);
|
|
44
|
+
window.addEventListener('resize', updateDuration);
|
|
45
|
+
return () => {
|
|
46
|
+
window.clearTimeout(timer);
|
|
47
|
+
window.removeEventListener('resize', updateDuration);
|
|
48
|
+
};
|
|
49
|
+
}, [sortedEvents, pixelsPerSecond, minDurationSeconds]);
|
|
50
|
+
if (sortedEvents.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return (_jsx("div", { style: {
|
|
54
|
+
position: 'absolute',
|
|
55
|
+
bottom: 0,
|
|
56
|
+
left: 0,
|
|
57
|
+
right: 0,
|
|
58
|
+
height: `${heightPx}px`,
|
|
59
|
+
backgroundColor,
|
|
60
|
+
backdropFilter: 'blur(8px)',
|
|
61
|
+
WebkitBackdropFilter: 'blur(8px)',
|
|
62
|
+
borderTop: `2px solid ${borderColor}`,
|
|
63
|
+
overflow: 'hidden',
|
|
64
|
+
zIndex: 100
|
|
65
|
+
}, children: _jsx("div", { ref: contentRef, onAnimationIteration: onCycleComplete, style: {
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
height: '100%',
|
|
69
|
+
animation: `marqueeFlow ${animationDuration} linear infinite`,
|
|
70
|
+
whiteSpace: 'nowrap',
|
|
71
|
+
width: 'max-content'
|
|
72
|
+
}, children: sortedEvents.map((event) => (_jsxs("div", { style: { display: 'flex', alignItems: 'center' }, children: [_jsx("span", { style: {
|
|
73
|
+
color: '#ffffff',
|
|
74
|
+
fontSize: '2rem',
|
|
75
|
+
fontWeight: '600',
|
|
76
|
+
fontFamily: 'Encode Sans, sans-serif'
|
|
77
|
+
}, children: event.title }), _jsx("div", { style: {
|
|
78
|
+
marginLeft: '2rem',
|
|
79
|
+
marginRight: '2rem',
|
|
80
|
+
display: 'flex',
|
|
81
|
+
alignItems: 'center'
|
|
82
|
+
}, children: _jsx("div", { style: {
|
|
83
|
+
backgroundColor: '#b21100',
|
|
84
|
+
padding: '0.5rem',
|
|
85
|
+
borderRadius: '0.25rem'
|
|
86
|
+
}, children: _jsx(BellRing, { size: 24, strokeWidth: 2, color: '#ffffff' }) }) })] }, event.uuid))) }) }));
|
|
87
|
+
}
|
|
88
|
+
export default memo(Marquee);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BellRing } from 'lucide-react';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
export function MarqueeCurtain({ onComplete }) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const timer = window.setTimeout(() => {
|
|
7
|
+
onComplete();
|
|
8
|
+
}, 5000);
|
|
9
|
+
return () => window.clearTimeout(timer);
|
|
10
|
+
}, [onComplete]);
|
|
11
|
+
return (_jsxs("div", { style: {
|
|
12
|
+
position: 'absolute',
|
|
13
|
+
bottom: 0,
|
|
14
|
+
left: 0,
|
|
15
|
+
right: 0,
|
|
16
|
+
height: '50px',
|
|
17
|
+
backgroundColor: '#b21100',
|
|
18
|
+
display: 'flex',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
justifyContent: 'center',
|
|
21
|
+
gap: '1rem',
|
|
22
|
+
zIndex: 100,
|
|
23
|
+
animation: 'fadeIn 0.3s ease-in-out'
|
|
24
|
+
}, children: [_jsx(BellRing, { size: 24, strokeWidth: 2, color: '#ffffff' }), _jsxs("span", { style: {
|
|
25
|
+
color: '#ffffff',
|
|
26
|
+
fontSize: '1.5rem',
|
|
27
|
+
fontWeight: '600',
|
|
28
|
+
fontFamily: 'Libre Franklin, sans-serif'
|
|
29
|
+
}, children: ["This is fifth", _jsx("span", { style: { fontWeight: '700' }, children: "bell" }), "."] }), _jsx(BellRing, { size: 24, strokeWidth: 2, color: '#ffffff' })] }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SupportedLanguage } from '../i18n.js';
|
|
2
|
+
import type { GlobalTimeOverride } from '../utils/broadcastTime.js';
|
|
3
|
+
interface CityTime {
|
|
4
|
+
city: string;
|
|
5
|
+
timezone: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_WORLD_CLOCK_CITIES: CityTime[];
|
|
8
|
+
interface WorldClocksProps {
|
|
9
|
+
currentTime?: Date;
|
|
10
|
+
timeOverride?: GlobalTimeOverride | null;
|
|
11
|
+
language?: SupportedLanguage;
|
|
12
|
+
cities?: CityTime[];
|
|
13
|
+
rotateIntervalMs?: number;
|
|
14
|
+
transitionDurationMs?: number;
|
|
15
|
+
shuffleCities?: boolean;
|
|
16
|
+
widthPx?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function WorldClocks({ currentTime, timeOverride, language, cities, rotateIntervalMs, transitionDurationMs, shuffleCities, widthPx }: WorldClocksProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { t } from '../i18n.js';
|
|
4
|
+
import { getOverrideClockParts } from '../utils/broadcastTime.js';
|
|
5
|
+
export const DEFAULT_WORLD_CLOCK_CITIES = [
|
|
6
|
+
{ city: 'NEW YORK', timezone: 'America/New_York' },
|
|
7
|
+
{ city: 'LONDON', timezone: 'Europe/London' },
|
|
8
|
+
{ city: 'TOKYO', timezone: 'Asia/Tokyo' },
|
|
9
|
+
{ city: 'SYDNEY', timezone: 'Australia/Sydney' },
|
|
10
|
+
{ city: 'ROME', timezone: 'Europe/Rome' },
|
|
11
|
+
{ city: 'MADRID', timezone: 'Europe/Madrid' },
|
|
12
|
+
{ city: 'LIMA', timezone: 'America/Lima' },
|
|
13
|
+
{ city: 'BERLIN', timezone: 'Europe/Berlin' },
|
|
14
|
+
{ city: 'LOS ANGELES', timezone: 'America/Los_Angeles' },
|
|
15
|
+
{ city: 'MEXICO CITY', timezone: 'America/Mexico_City' },
|
|
16
|
+
{ city: 'SANTIAGO', timezone: 'America/Santiago' },
|
|
17
|
+
{ city: 'BUENOS AIRES', timezone: 'America/Argentina/Buenos_Aires' },
|
|
18
|
+
{ city: 'SÃO PAULO', timezone: 'America/Sao_Paulo' },
|
|
19
|
+
{ city: 'HONOLULU', timezone: 'Pacific/Honolulu' },
|
|
20
|
+
{ city: 'BEIJING', timezone: 'Asia/Shanghai' },
|
|
21
|
+
{ city: 'SINGAPORE', timezone: 'Asia/Singapore' },
|
|
22
|
+
{ city: 'DELHI', timezone: 'Asia/Kolkata' },
|
|
23
|
+
{ city: 'LAHORE', timezone: 'Asia/Karachi' },
|
|
24
|
+
{ city: 'MOSCOW', timezone: 'Europe/Moscow' },
|
|
25
|
+
{ city: 'KYIV', timezone: 'Europe/Kiev' },
|
|
26
|
+
{ city: 'CAIRO', timezone: 'Africa/Cairo' },
|
|
27
|
+
{ city: 'LAGOS', timezone: 'Africa/Lagos' },
|
|
28
|
+
{ city: 'CAPE TOWN', timezone: 'Africa/Johannesburg' },
|
|
29
|
+
{ city: 'NAIROBI', timezone: 'Africa/Nairobi' },
|
|
30
|
+
{ city: 'CASABLANCA', timezone: 'Africa/Casablanca' }
|
|
31
|
+
];
|
|
32
|
+
const TRANSLATABLE_CITY_KEYS = new Set(DEFAULT_WORLD_CLOCK_CITIES.map((city) => city.city));
|
|
33
|
+
function shuffleArray(array) {
|
|
34
|
+
const shuffled = [...array];
|
|
35
|
+
for (let i = shuffled.length - 1; i > 0; i -= 1) {
|
|
36
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
37
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
38
|
+
}
|
|
39
|
+
return shuffled;
|
|
40
|
+
}
|
|
41
|
+
export function WorldClocks({ currentTime, timeOverride = null, language = 'en', cities, rotateIntervalMs = 7000, transitionDurationMs = 300, shuffleCities = true, widthPx = 200 }) {
|
|
42
|
+
const activeCities = cities && cities.length > 0 ? cities : DEFAULT_WORLD_CLOCK_CITIES;
|
|
43
|
+
const [time, setTime] = useState(currentTime || new Date());
|
|
44
|
+
const [cityPool, setCityPool] = useState(() => (shuffleCities ? shuffleArray(activeCities) : [...activeCities]));
|
|
45
|
+
const [currentCityIndex, setCurrentCityIndex] = useState(0);
|
|
46
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
setCityPool(shuffleCities ? shuffleArray(activeCities) : [...activeCities]);
|
|
49
|
+
setCurrentCityIndex(0);
|
|
50
|
+
}, [activeCities, shuffleCities]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (currentTime) {
|
|
53
|
+
setTime(currentTime);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const interval = setInterval(() => {
|
|
57
|
+
setTime(new Date());
|
|
58
|
+
}, 1000);
|
|
59
|
+
return () => clearInterval(interval);
|
|
60
|
+
}, [currentTime]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
let timeoutId;
|
|
63
|
+
const interval = setInterval(() => {
|
|
64
|
+
setIsAnimating(true);
|
|
65
|
+
timeoutId = window.setTimeout(() => {
|
|
66
|
+
setCurrentCityIndex((prevIndex) => {
|
|
67
|
+
const nextIndex = prevIndex + 1;
|
|
68
|
+
if (nextIndex >= cityPool.length) {
|
|
69
|
+
setCityPool(shuffleCities ? shuffleArray(activeCities) : [...activeCities]);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
return nextIndex;
|
|
73
|
+
});
|
|
74
|
+
setIsAnimating(false);
|
|
75
|
+
}, Math.max(0, transitionDurationMs));
|
|
76
|
+
}, Math.max(500, rotateIntervalMs));
|
|
77
|
+
return () => {
|
|
78
|
+
clearInterval(interval);
|
|
79
|
+
if (timeoutId !== undefined) {
|
|
80
|
+
window.clearTimeout(timeoutId);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}, [cityPool, rotateIntervalMs, transitionDurationMs, shuffleCities, activeCities]);
|
|
84
|
+
const currentCity = cityPool[currentCityIndex] ?? activeCities[0];
|
|
85
|
+
const formatTime = (timezone) => {
|
|
86
|
+
if (timeOverride) {
|
|
87
|
+
const parts = getOverrideClockParts(timeOverride, time);
|
|
88
|
+
if (parts) {
|
|
89
|
+
return `${String(parts.hours).padStart(2, '0')}:${String(parts.minutes).padStart(2, '0')}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return time.toLocaleTimeString('en-US', {
|
|
93
|
+
timeZone: timezone,
|
|
94
|
+
hour: '2-digit',
|
|
95
|
+
minute: '2-digit',
|
|
96
|
+
hour12: false
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const cityLabel = TRANSLATABLE_CITY_KEYS.has(currentCity.city) ? t(`city.${currentCity.city}`, language) : currentCity.city;
|
|
100
|
+
return (_jsxs("div", { className: 'flex flex-col gap-1.5 transition-opacity duration-300', style: { opacity: isAnimating ? 0 : 1, width: `${widthPx}px` }, children: [_jsx("div", { className: 'text-white font-bold text-6xl tracking-tight leading-none text-center', style: { fontFamily: 'system-ui, -apple-system, sans-serif' }, children: formatTime(currentCity.timezone) }), _jsx("div", { className: 'text-white/50 text-2xl font-bold tracking-wider leading-none uppercase text-center', style: { fontFamily: 'system-ui, -apple-system, sans-serif' }, children: cityLabel })] }));
|
|
101
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface NewsItem {
|
|
2
|
+
id: string;
|
|
3
|
+
headline: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
imageUrl: string;
|
|
6
|
+
category?: string;
|
|
7
|
+
url: string;
|
|
8
|
+
}
|
|
9
|
+
interface ArticleSlideProps {
|
|
10
|
+
newsItem: NewsItem;
|
|
11
|
+
progress: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function ArticleSlide({ newsItem, progress }: ArticleSlideProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { FastAverageColor } from 'fast-average-color';
|
|
4
|
+
const fac = new FastAverageColor();
|
|
5
|
+
function buildQrCodeUrl(value) {
|
|
6
|
+
return `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(value)}`;
|
|
7
|
+
}
|
|
8
|
+
export function ArticleSlide({ newsItem, progress }) {
|
|
9
|
+
const [dominantColor, setDominantColor] = useState('#b21100');
|
|
10
|
+
const imageUrl = newsItem.imageUrl || 'https://picsum.photos/seed/fallback/1920/1080';
|
|
11
|
+
const qrCodeUrl = useMemo(() => buildQrCodeUrl(newsItem.url), [newsItem.url]);
|
|
12
|
+
const handleImageLoad = (event) => {
|
|
13
|
+
try {
|
|
14
|
+
const color = fac.getColor(event.currentTarget);
|
|
15
|
+
setDominantColor(color.hex);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error('Error getting average color', error);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return (_jsxs("div", { className: 'absolute inset-0', children: [_jsx("div", { className: 'absolute inset-0', children: _jsx("img", { src: imageUrl, alt: '', crossOrigin: 'anonymous', onLoad: handleImageLoad, className: 'w-full h-full object-cover blur-xl scale-105' }, imageUrl) }), _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 grid grid-cols-12 h-full', children: [_jsxs("div", { className: 'col-span-5 flex flex-col justify-center px-24 relative', children: [_jsx("div", { className: 'w-32 h-2 bg-white mb-12' }), _jsxs("div", { className: 'animate-slide-up', children: [_jsx("h1", { className: "text-5xl font-bold leading-tight mb-8 tracking-tight line-clamp-6 font-['Encode_Sans']", children: newsItem.headline }), _jsx("p", { className: "text-4xl font-light leading-relaxed opacity-90 line-clamp-6 font-['Libre_Franklin']", children: newsItem.summary })] }, newsItem.id), newsItem.category && (_jsx("div", { className: 'absolute bottom-40 left-24 animate-slide-up', children: _jsx("span", { className: 'text-white px-4 py-2 text-2xl font-bold uppercase tracking-wider', style: { backgroundColor: dominantColor }, children: newsItem.category }) }, `${newsItem.id}-cat`))] }), _jsx("div", { className: 'col-span-7 relative h-full flex items-center justify-center p-16 pb-40', children: _jsxs("div", { className: 'relative w-full aspect-video shadow-2xl overflow-hidden border-4 border-white/10', children: [_jsx("div", { className: 'absolute top-0 left-0 h-1 bg-white/30 w-full z-20', children: _jsx("div", { className: 'h-full bg-white transition-all duration-100 ease-linear', style: { width: `${progress}%` } }) }), _jsx("img", { src: imageUrl, alt: newsItem.headline, className: 'w-full h-full object-cover', style: { animation: 'kenburns 20s infinite alternate' } }, imageUrl)] }) })] }), _jsx("div", { className: 'absolute bottom-24 left-0 w-full flex justify-between items-end px-24 z-50', children: _jsx("div", { className: 'ml-auto p-2 bg-white/10 backdrop-blur-sm', children: _jsx("img", { src: qrCodeUrl, alt: 'Article QR code', className: 'block w-37.5 h-37.5' }) }) })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BellRing } from 'lucide-react';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { FastAverageColor } from 'fast-average-color';
|
|
5
|
+
import { FIFTHBELL_ASSETS } from '../../assets.js';
|
|
6
|
+
const fac = new FastAverageColor();
|
|
7
|
+
export function CallsignSlide({ currentTime: initialTime }) {
|
|
8
|
+
const [displayTime, setDisplayTime] = useState(initialTime);
|
|
9
|
+
const [dominantColor, setDominantColor] = useState('#b21100');
|
|
10
|
+
const [fullOpacity, setFullOpacity] = useState(1);
|
|
11
|
+
const [smallOpacity, setSmallOpacity] = useState(0);
|
|
12
|
+
const [centerOpacity, setCenterOpacity] = useState(0);
|
|
13
|
+
const [showTime, setShowTime] = useState(false);
|
|
14
|
+
const backgroundImage = FIFTHBELL_ASSETS.images.nyc;
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setDisplayTime(initialTime);
|
|
17
|
+
const timer = window.setInterval(() => {
|
|
18
|
+
setDisplayTime(new Date());
|
|
19
|
+
}, 1000);
|
|
20
|
+
return () => window.clearInterval(timer);
|
|
21
|
+
}, [initialTime]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const timer = window.setTimeout(() => {
|
|
24
|
+
setFullOpacity(0);
|
|
25
|
+
setSmallOpacity(1);
|
|
26
|
+
setCenterOpacity(1);
|
|
27
|
+
window.setTimeout(() => setShowTime(true), 500);
|
|
28
|
+
}, 2000);
|
|
29
|
+
return () => window.clearTimeout(timer);
|
|
30
|
+
}, []);
|
|
31
|
+
const formatNyTime = (date) => date.toLocaleTimeString('en-US', {
|
|
32
|
+
timeZone: 'America/New_York',
|
|
33
|
+
hour: '2-digit',
|
|
34
|
+
minute: '2-digit',
|
|
35
|
+
second: '2-digit',
|
|
36
|
+
hour12: false
|
|
37
|
+
});
|
|
38
|
+
const handleImageLoad = (event) => {
|
|
39
|
+
try {
|
|
40
|
+
const color = fac.getColor(event.currentTarget);
|
|
41
|
+
setDominantColor(color.hex);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('Error getting average color', error);
|
|
45
|
+
setDominantColor('#b21100');
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
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 items-center justify-center', children: [_jsx("div", { className: 'bg-[#b21100] text-white p-12 shadow-2xl mb-16 animate-scale-in', style: { opacity: fullOpacity, transition: 'opacity 0.5s' }, children: _jsx(BellRing, { size: 256, strokeWidth: 2 }) }), _jsx("div", { className: 'absolute top-4 left-4', style: { opacity: smallOpacity, transition: 'opacity 0.5s' }, children: _jsx("div", { className: 'bg-[#b21100] text-white p-6 shadow-lg scale-50', children: _jsx(BellRing, { size: 128, strokeWidth: 2 }) }) }), _jsx("div", { className: 'flex flex-col items-center', style: { opacity: centerOpacity, transition: 'opacity 0.5s' } }), _jsx("div", { className: "text-white text-6xl font-bold tracking-wider animate-fade-in-delay font-['JetBrains_Mono']", style: { opacity: showTime ? 1 : 0, transition: 'opacity 0.5s' }, children: formatNyTime(displayTime) }), _jsxs("div", { className: 'text-white text-3xl opacity-75 mt-4 animate-fade-in-delay', style: { opacity: showTime ? 1 : 0, transition: 'opacity 0.5s' }, children: ["This is fifth", _jsx("span", { className: 'font-bold', children: "bell" }), "."] })] })] }));
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const slideStyles = "\n @keyframes kenburns {\n 0% { transform: scale(1); }\n 100% { transform: scale(1.1); }\n }\n\n @keyframes slideTransition {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n .animate-slide-transition {\n animation: slideTransition 0.8s ease-in-out forwards;\n }\n\n @keyframes scaleIn {\n from {\n opacity: 0;\n transform: scale(0.8);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n }\n\n .animate-scale-in {\n animation: scaleIn 1s ease-out forwards;\n }\n\n .animate-fade-in {\n animation: fadeIn 1s ease-out forwards;\n }\n\n .animate-fade-in-delay {\n animation: fadeIn 1s ease-out 0.3s forwards;\n opacity: 0;\n }\n\n @keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes slideUpFade {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .animate-slide-up {\n animation: slideUpFade 0.8s ease-out forwards;\n }\n\n @keyframes marqueeFlow {\n from { transform: translateX(1920px); }\n to { transform: translateX(-100%); }\n }\n";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const slideStyles = `
|
|
2
|
+
@keyframes kenburns {
|
|
3
|
+
0% { transform: scale(1); }
|
|
4
|
+
100% { transform: scale(1.1); }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@keyframes slideTransition {
|
|
8
|
+
from { opacity: 0; }
|
|
9
|
+
to { opacity: 1; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.animate-slide-transition {
|
|
13
|
+
animation: slideTransition 0.8s ease-in-out forwards;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@keyframes scaleIn {
|
|
17
|
+
from {
|
|
18
|
+
opacity: 0;
|
|
19
|
+
transform: scale(0.8);
|
|
20
|
+
}
|
|
21
|
+
to {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
transform: scale(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.animate-scale-in {
|
|
28
|
+
animation: scaleIn 1s ease-out forwards;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.animate-fade-in {
|
|
32
|
+
animation: fadeIn 1s ease-out forwards;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.animate-fade-in-delay {
|
|
36
|
+
animation: fadeIn 1s ease-out 0.3s forwards;
|
|
37
|
+
opacity: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@keyframes fadeIn {
|
|
41
|
+
from { opacity: 0; }
|
|
42
|
+
to { opacity: 1; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes slideUpFade {
|
|
46
|
+
from {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transform: translateY(20px);
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
transform: translateY(0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.animate-slide-up {
|
|
57
|
+
animation: slideUpFade 0.8s ease-out forwards;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@keyframes marqueeFlow {
|
|
61
|
+
from { transform: translateX(1920px); }
|
|
62
|
+
to { transform: translateX(-100%); }
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SupportedLanguage } from './i18n.js';
|
|
2
|
+
export interface EventPost {
|
|
3
|
+
id: number;
|
|
4
|
+
uuid: string;
|
|
5
|
+
content: string;
|
|
6
|
+
source: string;
|
|
7
|
+
uri: string;
|
|
8
|
+
hash: string;
|
|
9
|
+
author: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
relevance: number;
|
|
12
|
+
match_score: number;
|
|
13
|
+
}
|
|
14
|
+
export interface Event {
|
|
15
|
+
id: number;
|
|
16
|
+
uuid: string;
|
|
17
|
+
title: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
status: 'open' | 'closed';
|
|
20
|
+
created_at: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
posts_count: number;
|
|
23
|
+
posts: EventPost[];
|
|
24
|
+
}
|
|
25
|
+
interface FetchEventsOptions {
|
|
26
|
+
language?: SupportedLanguage;
|
|
27
|
+
allowedLanguages?: SupportedLanguage[];
|
|
28
|
+
}
|
|
29
|
+
export declare function fetchEvents(options?: FetchEventsOptions): Promise<Event[]>;
|
|
30
|
+
export declare function getCachedEvents(): Event[] | null;
|
|
31
|
+
export declare function getLastFetchTime(): Date | null;
|
|
32
|
+
export declare function clearCachedEvents(): void;
|
|
33
|
+
export declare function hasEventChanges(oldEvents: Event[], newEvents: Event[]): boolean;
|
|
34
|
+
export {};
|