disaster-broadcast-studio 0.1.1 → 0.3.0
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/README.md +19 -7
- package/assets/README.md +1 -1
- package/assets/audio/README.md +16 -4
- package/assets/audio/alert-critical.mjs +4 -0
- package/assets/audio/alert-critical.wav +0 -0
- package/assets/audio/alert-critical.wav.json +15 -0
- package/assets/audio/alert-urgent.mjs +4 -0
- package/assets/audio/alert-urgent.wav +0 -0
- package/assets/audio/alert-urgent.wav.json +15 -0
- package/assets/audio/bulletin.mjs +9 -0
- package/assets/audio/bulletin.wav +0 -0
- package/assets/audio/bulletin.wav.json +15 -0
- package/assets/audio/news.mjs +4 -0
- package/assets/audio/news.wav +0 -0
- package/assets/audio/news.wav.json +15 -0
- package/assets/audio/notification.mjs +24 -9
- package/assets/audio/notification.wav +0 -0
- package/assets/audio/notification.wav.json +15 -0
- package/assets/audio/status.mjs +5 -0
- package/assets/audio/status.wav +0 -0
- package/assets/audio/status.wav.json +15 -0
- package/assets/audio/tones.mjs +17 -0
- package/assets/workspace/README.md +3 -0
- package/dist/cli.js +20 -1
- package/dist/credits.js +2 -2
- package/dist/load.js +8 -7
- package/dist/notifications.d.ts +40 -3
- package/dist/notifications.js +34 -6
- package/dist/preview/browser.d.ts +1 -0
- package/dist/preview/browser.js +87 -0
- package/dist/preview/http.d.ts +3 -0
- package/dist/preview/http.js +78 -0
- package/dist/preview/page.d.ts +1 -0
- package/dist/preview/page.js +30 -0
- package/dist/preview/project.d.ts +22 -0
- package/dist/preview/project.js +81 -0
- package/dist/preview/server.d.ts +6 -0
- package/dist/preview/server.js +57 -0
- package/dist/render.js +4 -40
- package/dist/scenario.d.ts +32 -4
- package/dist/scenario.js +59 -50
- package/dist/timeline.d.ts +68 -5
- package/dist/timeline.js +29 -9
- package/dist/video/Alerts.js +49 -49
- package/dist/video/Broadcast.js +82 -96
- package/dist/video/Bulletin.d.ts +2 -2
- package/dist/video/Bulletin.js +13 -18
- package/dist/video/Chat.d.ts +9 -0
- package/dist/video/Chat.js +34 -0
- package/dist/video/Information.d.ts +10 -0
- package/dist/video/Information.js +100 -0
- package/dist/video/Map.js +28 -12
- package/dist/video/TimeJump.d.ts +8 -0
- package/dist/video/TimeJump.js +42 -0
- package/dist/video/layout.d.ts +5 -0
- package/dist/video/layout.js +2 -0
- package/dist/video/prepare.d.ts +4 -0
- package/dist/video/prepare.js +47 -0
- package/dist/video/transitions.d.ts +609 -0
- package/dist/video/transitions.js +22 -0
- package/dist/video/useBundledFonts.js +16 -7
- package/package.json +2 -1
- package/skills/scenario-writing/SKILL.md +12 -5
- package/skills/scenario-writing/references/README.md +4 -4
- package/skills/scenario-writing/references/audio.md +7 -1
- package/skills/scenario-writing/references/direction.md +31 -7
- package/skills/scenario-writing/references/format.md +63 -12
- package/skills/scenario-writing/references/news-writing.md +10 -0
- package/skills/scenario-writing/references/status-systems.md +4 -2
package/dist/timeline.d.ts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import { type Timeline, type Camera, type CompiledEvent } from './scenario.js';
|
|
2
2
|
export declare const active: (e: CompiledEvent, frame: number) => boolean;
|
|
3
|
+
export declare function chatAt(t: Timeline, frame: number): {
|
|
4
|
+
panel: {
|
|
5
|
+
type: "chat";
|
|
6
|
+
title: string;
|
|
7
|
+
duration: number;
|
|
8
|
+
carry: boolean;
|
|
9
|
+
id: string;
|
|
10
|
+
cut: string;
|
|
11
|
+
at: number;
|
|
12
|
+
} & {
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
order: number;
|
|
16
|
+
};
|
|
17
|
+
messages: ({
|
|
18
|
+
type: "chat-message";
|
|
19
|
+
chat: string;
|
|
20
|
+
author: string;
|
|
21
|
+
text: string;
|
|
22
|
+
color: string;
|
|
23
|
+
id: string;
|
|
24
|
+
cut: string;
|
|
25
|
+
at: number;
|
|
26
|
+
} & {
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
order: number;
|
|
30
|
+
})[];
|
|
31
|
+
} | null;
|
|
3
32
|
export declare const mix: (a: number, b: number, t: number) => number;
|
|
33
|
+
export declare function notificationPulse(frame: number, start: number, end: number): number;
|
|
4
34
|
export declare function cameraAt(t: Timeline, frame: number): Camera;
|
|
5
35
|
export declare function receiptHighlights(t: Timeline, frame: number): Extract<CompiledEvent, {
|
|
6
36
|
type: 'region';
|
|
@@ -8,11 +38,6 @@ export declare function receiptHighlights(t: Timeline, frame: number): Extract<C
|
|
|
8
38
|
export declare function regionOpacity(e: Extract<CompiledEvent, {
|
|
9
39
|
type: 'region';
|
|
10
40
|
}>, frame: number, fill?: boolean): number;
|
|
11
|
-
export declare function statusEntry(frame: number, start: number, duration?: number): {
|
|
12
|
-
opacity: number;
|
|
13
|
-
offset: number;
|
|
14
|
-
glow: number;
|
|
15
|
-
};
|
|
16
41
|
export declare function shutdownAt(events: CompiledEvent[], frame: number): {
|
|
17
42
|
y: number;
|
|
18
43
|
x: number;
|
|
@@ -26,6 +51,10 @@ export declare function stateAt(t: Timeline, frame: number): {
|
|
|
26
51
|
displayEpoch: number;
|
|
27
52
|
jumping: boolean;
|
|
28
53
|
clockPhase: string | null;
|
|
54
|
+
timeJump: {
|
|
55
|
+
progress: number;
|
|
56
|
+
elapsedFrames: number;
|
|
57
|
+
} | null;
|
|
29
58
|
contentFrame: number;
|
|
30
59
|
camera: {
|
|
31
60
|
center: [number, number];
|
|
@@ -34,9 +63,43 @@ export declare function stateAt(t: Timeline, frame: number): {
|
|
|
34
63
|
pitch: number;
|
|
35
64
|
};
|
|
36
65
|
alerts: CompiledEvent[];
|
|
66
|
+
chat: {
|
|
67
|
+
panel: {
|
|
68
|
+
type: "chat";
|
|
69
|
+
title: string;
|
|
70
|
+
duration: number;
|
|
71
|
+
carry: boolean;
|
|
72
|
+
id: string;
|
|
73
|
+
cut: string;
|
|
74
|
+
at: number;
|
|
75
|
+
} & {
|
|
76
|
+
start: number;
|
|
77
|
+
end: number;
|
|
78
|
+
order: number;
|
|
79
|
+
};
|
|
80
|
+
messages: ({
|
|
81
|
+
type: "chat-message";
|
|
82
|
+
chat: string;
|
|
83
|
+
author: string;
|
|
84
|
+
text: string;
|
|
85
|
+
color: string;
|
|
86
|
+
id: string;
|
|
87
|
+
cut: string;
|
|
88
|
+
at: number;
|
|
89
|
+
} & {
|
|
90
|
+
start: number;
|
|
91
|
+
end: number;
|
|
92
|
+
order: number;
|
|
93
|
+
})[];
|
|
94
|
+
} | null;
|
|
37
95
|
visible: CompiledEvent[];
|
|
38
96
|
news: ({
|
|
39
97
|
type: "news";
|
|
98
|
+
sound: false | "default" | {
|
|
99
|
+
asset: string;
|
|
100
|
+
duration: number;
|
|
101
|
+
volume: number;
|
|
102
|
+
};
|
|
40
103
|
source: string;
|
|
41
104
|
headline: string;
|
|
42
105
|
duration: number;
|
package/dist/timeline.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { FPS } from './scenario.js';
|
|
2
2
|
export const active = (e, frame) => e.start <= frame && frame < e.end;
|
|
3
|
+
export function chatAt(t, frame) {
|
|
4
|
+
const panel = t.events.find((e) => e.type === 'chat' && active(e, frame));
|
|
5
|
+
if (!panel || panel.type !== 'chat')
|
|
6
|
+
return null;
|
|
7
|
+
const messages = t.events.filter((e) => e.type === 'chat-message' && e.chat === panel.id && e.start <= frame);
|
|
8
|
+
return { panel, messages };
|
|
9
|
+
}
|
|
3
10
|
export const mix = (a, b, t) => a + (b - a) * t;
|
|
11
|
+
// Three smooth pulses, bounded by the authored frame interval.
|
|
12
|
+
export function notificationPulse(frame, start, end) {
|
|
13
|
+
if (frame < start || frame >= end)
|
|
14
|
+
return 0;
|
|
15
|
+
return Math.sin(((frame - start) / (end - start)) * Math.PI * 3) ** 2;
|
|
16
|
+
}
|
|
4
17
|
export function cameraAt(t, frame) {
|
|
5
18
|
let cam = { ...t.scenario.map.camera };
|
|
6
19
|
for (const e of t.events) {
|
|
@@ -21,7 +34,8 @@ export function receiptHighlights(t, frame) {
|
|
|
21
34
|
return t.events.flatMap((e) => {
|
|
22
35
|
if (e.type !== 'alert' || !e.mapHighlight || !e.regions.length)
|
|
23
36
|
return [];
|
|
24
|
-
const
|
|
37
|
+
const highlightEnd = e.start + Math.round(e.mapHighlight.duration * FPS);
|
|
38
|
+
const end = Math.min(t.durationInFrames, highlightEnd);
|
|
25
39
|
if (frame < e.start || frame >= end)
|
|
26
40
|
return [];
|
|
27
41
|
return [
|
|
@@ -37,13 +51,13 @@ export function receiptHighlights(t, frame) {
|
|
|
37
51
|
order: e.order,
|
|
38
52
|
regions: e.regions,
|
|
39
53
|
color: e.mapHighlight.color,
|
|
40
|
-
fillOpacity: 0.28,
|
|
54
|
+
fillOpacity: 0.28 * notificationPulse(frame, e.start, highlightEnd),
|
|
41
55
|
strokeColor: e.mapHighlight.color,
|
|
42
56
|
strokeOpacity: 0,
|
|
43
57
|
strokeWidth: 0,
|
|
44
58
|
pulse: false,
|
|
45
|
-
fadeIn: Math.min(0.
|
|
46
|
-
fadeOut: Math.min(0.
|
|
59
|
+
fadeIn: Math.min(0.1, e.mapHighlight.duration / 12),
|
|
60
|
+
fadeOut: Math.min(0.1, e.mapHighlight.duration / 12),
|
|
47
61
|
},
|
|
48
62
|
];
|
|
49
63
|
});
|
|
@@ -52,10 +66,6 @@ export function regionOpacity(e, frame, fill = true) {
|
|
|
52
66
|
const fade = envelope(frame, e.start, e.end, e.fadeIn, e.fadeOut);
|
|
53
67
|
return (fade * (fill && e.pulse ? 0.88 + 0.12 * Math.cos(((frame - e.start) / FPS) * Math.PI * 0.9) : 1));
|
|
54
68
|
}
|
|
55
|
-
export function statusEntry(frame, start, duration = 0.55) {
|
|
56
|
-
const p = Math.max(0, Math.min(1, (frame - start) / (duration * FPS)));
|
|
57
|
-
return { opacity: 0.4 + 0.6 * p, offset: (1 - p) * -5, glow: (1 - p) * 0.22 };
|
|
58
|
-
}
|
|
59
69
|
export function shutdownAt(events, frame) {
|
|
60
70
|
const e = events.find((e) => e.type === 'screen' && e.effect === 'shutdown' && e.intensity > 0 && active(e, frame));
|
|
61
71
|
if (!e)
|
|
@@ -76,12 +86,15 @@ export function stateAt(t, frame) {
|
|
|
76
86
|
const jumping = index > 0 && transition > 0 && frame < cut.start + transition;
|
|
77
87
|
const contentFrame = jumping ? cut.start - 1 : frame;
|
|
78
88
|
const previous = index > 0 ? t.cuts[index - 1] : null;
|
|
89
|
+
const previousEnd = previous
|
|
90
|
+
? previous.epoch + ((previous.end - previous.start) * 1000) / FPS
|
|
91
|
+
: cut.epoch;
|
|
79
92
|
const epoch = cut.epoch + ((frame - cut.start) * 1000) / FPS;
|
|
80
93
|
const p = jumping ? (frame - cut.start) / transition : 1;
|
|
81
94
|
const clockPhase = jumping ? (p < 0.15 ? 'lead' : p < 0.75 ? 'run' : 'hold') : null;
|
|
82
95
|
const roll = Math.max(0, Math.min(1, (p - 0.15) / 0.6));
|
|
83
96
|
const displayEpoch = jumping
|
|
84
|
-
? mix(
|
|
97
|
+
? mix(previousEnd, cut.epoch + (transition * 1000) / FPS, roll)
|
|
85
98
|
: epoch;
|
|
86
99
|
const visible = t.events.filter((e) => active(e, contentFrame));
|
|
87
100
|
return {
|
|
@@ -91,9 +104,16 @@ export function stateAt(t, frame) {
|
|
|
91
104
|
displayEpoch,
|
|
92
105
|
jumping,
|
|
93
106
|
clockPhase,
|
|
107
|
+
timeJump: jumping && cut.epoch > previousEnd
|
|
108
|
+
? {
|
|
109
|
+
progress: (frame - cut.start) / Math.max(1, transition - 1),
|
|
110
|
+
elapsedFrames: frame - cut.start,
|
|
111
|
+
}
|
|
112
|
+
: null,
|
|
94
113
|
contentFrame,
|
|
95
114
|
camera: cameraAt(t, contentFrame),
|
|
96
115
|
alerts: t.events.filter((e) => e.type === 'alert' && e.start <= contentFrame).reverse(),
|
|
116
|
+
chat: chatAt(t, contentFrame),
|
|
97
117
|
visible,
|
|
98
118
|
news: visible.find((e) => e.type === 'news'),
|
|
99
119
|
opacity: Math.min(1, cut.fadeIn ? (frame - cut.start) / FPS / cut.fadeIn : 1, cut.fadeOut ? (cut.end - 1 - frame) / FPS / cut.fadeOut : 1),
|
package/dist/video/Alerts.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { alertText } from '../scenario.js';
|
|
3
3
|
import { stateAt, clock } from '../timeline.js';
|
|
4
4
|
import { numberFont } from './fonts.js';
|
|
5
|
+
import { changeProgress } from './transitions.js';
|
|
5
6
|
const colors = {
|
|
6
7
|
안전안내: '#a0adb5',
|
|
7
8
|
긴급재난: '#c2746e',
|
|
8
9
|
위급재난: '#dd655f',
|
|
9
10
|
};
|
|
10
|
-
function AlertCard({ event,
|
|
11
|
+
function AlertCard({ event, emphasis, frame, timeline, }) {
|
|
11
12
|
if (event.type !== 'alert')
|
|
12
13
|
return null;
|
|
13
|
-
const
|
|
14
|
+
const arrival = changeProgress(frame, event.start);
|
|
15
|
+
const latest = emphasis > 0;
|
|
14
16
|
const received = stateAt(timeline, event.start).epoch;
|
|
15
|
-
return (_jsxs("article", { style: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}, children: [page + 1, " / ", chunks.length] })), event.corrects && (_jsxs("div", { style: {
|
|
53
|
-
fontSize: 16,
|
|
54
|
-
color: '#a4afb6',
|
|
55
|
-
marginTop: 12,
|
|
56
|
-
borderTop: '1px solid #343a3f',
|
|
57
|
-
paddingTop: 10,
|
|
58
|
-
}, children: ["\uC774\uC804 \uBB38\uC790 \uC815\uC815 \u00B7", ' ', clock(stateAt(timeline, timeline.events.find((e) => e.id === event.corrects).start).epoch, timeline.scenario.timezone)] }))] }));
|
|
17
|
+
return (_jsx("div", { style: { display: 'grid', gridTemplateRows: `${arrival}fr` }, children: _jsx("div", { style: { minHeight: 0, overflow: 'hidden' }, children: _jsxs("article", { style: {
|
|
18
|
+
padding: '16px 24px',
|
|
19
|
+
opacity: arrival,
|
|
20
|
+
transform: `translateY(${-14 * (1 - arrival)}px)`,
|
|
21
|
+
boxShadow: `inset 4px 0 ${colors[event.level]}${Math.round((1 - changeProgress(frame, event.start, 66)) * 180)
|
|
22
|
+
.toString(16)
|
|
23
|
+
.padStart(2, '0')}`,
|
|
24
|
+
borderBottom: '1px solid #343a3f',
|
|
25
|
+
background: latest ? '#131719' : '#0e1214',
|
|
26
|
+
borderLeft: `2px solid ${colors[event.level]}`,
|
|
27
|
+
}, children: [_jsxs("div", { style: {
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
gap: 10,
|
|
31
|
+
fontSize: 28,
|
|
32
|
+
color: colors[event.level],
|
|
33
|
+
marginBottom: 10,
|
|
34
|
+
}, children: [_jsx("b", { style: {
|
|
35
|
+
border: `1px solid ${colors[event.level]}`,
|
|
36
|
+
padding: '2px 7px',
|
|
37
|
+
fontWeight: 400,
|
|
38
|
+
}, children: event.level }), _jsxs("span", { style: {
|
|
39
|
+
marginLeft: 'auto',
|
|
40
|
+
fontFamily: numberFont,
|
|
41
|
+
fontVariantNumeric: 'tabular-nums',
|
|
42
|
+
fontSize: 26,
|
|
43
|
+
color: '#aab3b9',
|
|
44
|
+
}, children: ["\uC218\uC2E0 ", clock(received, timeline.scenario.timezone)] })] }), (event.occurredAt || event.publishedAt) && (_jsxs("div", { style: { fontSize: 26, color: '#b0bac2', marginBottom: 8 }, children: [event.occurredAt &&
|
|
45
|
+
`발생 ${clock(Date.parse(event.occurredAt), timeline.scenario.timezone)} `, event.publishedAt &&
|
|
46
|
+
`발표 ${clock(Date.parse(event.publishedAt), timeline.scenario.timezone)}`] })), _jsx("div", { style: {
|
|
47
|
+
fontSize: 32 + 8 * emphasis,
|
|
48
|
+
lineHeight: 1.4,
|
|
49
|
+
whiteSpace: 'pre-wrap',
|
|
50
|
+
wordBreak: 'normal',
|
|
51
|
+
overflowWrap: 'anywhere',
|
|
52
|
+
color: latest ? '#edf0f2' : '#c0c9d0',
|
|
53
|
+
}, children: alertText(timeline.scenario.agencies[event.sender], event.text) })] }) }) }));
|
|
59
54
|
}
|
|
60
55
|
export function AlertHistory({ timeline, frame }) {
|
|
61
|
-
|
|
56
|
+
const alerts = stateAt(timeline, frame).alerts;
|
|
57
|
+
return (_jsx(_Fragment, { children: alerts.map((event, i) => (_jsx(AlertCard, { event: event, emphasis: i === 0
|
|
58
|
+
? 1
|
|
59
|
+
: alerts[i - 1].start > event.start
|
|
60
|
+
? 1 - changeProgress(frame, alerts[i - 1].start)
|
|
61
|
+
: 0, frame: frame, timeline: timeline }, event.id))) }));
|
|
62
62
|
}
|
package/dist/video/Broadcast.js
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { AbsoluteFill, useCurrentFrame } from 'remotion';
|
|
3
3
|
import { FPS } from '../scenario.js';
|
|
4
|
-
import { stateAt, clock, envelope, receiptHighlights,
|
|
4
|
+
import { stateAt, clock, envelope, receiptHighlights, shutdownAt } from '../timeline.js';
|
|
5
5
|
import { AlertHistory } from './Alerts.js';
|
|
6
|
+
import { ChatPanel } from './Chat.js';
|
|
6
7
|
import { BroadcastMap } from './Map.js';
|
|
7
8
|
import { SignalEffects, signalLevels } from './SignalEffects.js';
|
|
8
9
|
import { AudioTrack } from './AudioTrack.js';
|
|
9
10
|
import { Bulletin } from './Bulletin.js';
|
|
10
11
|
import { useBundledFonts } from './useBundledFonts.js';
|
|
11
12
|
import { bodyFont, numberFont } from './fonts.js';
|
|
13
|
+
import { News, Status } from './Information.js';
|
|
14
|
+
import { changeProgress, transitionAt } from './transitions.js';
|
|
15
|
+
import { layout } from './layout.js';
|
|
16
|
+
import { TimeJump, timeJumpStrength } from './TimeJump.js';
|
|
12
17
|
export function Broadcast(props) {
|
|
13
18
|
const { timeline, regions, countries, provinces, audio } = props;
|
|
14
19
|
const openingFrames = 5 * FPS;
|
|
15
|
-
const frame = useCurrentFrame(), state = stateAt(timeline, frame)
|
|
20
|
+
const frame = useCurrentFrame(), state = stateAt(timeline, frame);
|
|
16
21
|
useBundledFonts(JSON.stringify(timeline.scenario));
|
|
17
|
-
const
|
|
18
|
-
const bulletin = state.visible.find((e) => e.type === 'bulletin');
|
|
22
|
+
const bulletins = transitionAt(timeline.events, 'bulletin', state.contentFrame);
|
|
19
23
|
const inset = state.visible.find((e) => e.type === 'inset');
|
|
20
24
|
const legend = state.visible.find((e) => e.type === 'legend');
|
|
21
25
|
const signal = signalLevels(state.visible, frame);
|
|
22
26
|
const mapEvents = [...state.visible, ...receiptHighlights(timeline, state.contentFrame)];
|
|
23
|
-
const entry = statusEntry(state.contentFrame, status?.start ?? 0);
|
|
24
27
|
const shutdown = shutdownAt(state.visible, frame);
|
|
28
|
+
const jumpStrength = timeJumpStrength(state.timeJump);
|
|
25
29
|
return (_jsxs(AbsoluteFill, { style: {
|
|
26
30
|
filter: signal.chromatic > 0 ? 'url(#dbs-chromatic)' : undefined,
|
|
27
31
|
background: shutdown ? '#000' : '#080b0d',
|
|
@@ -34,30 +38,17 @@ export function Broadcast(props) {
|
|
|
34
38
|
}, children: [_jsxs("header", { style: {
|
|
35
39
|
position: 'absolute',
|
|
36
40
|
inset: '0 0 auto 0',
|
|
37
|
-
height:
|
|
41
|
+
height: layout.header,
|
|
38
42
|
display: 'flex',
|
|
39
43
|
alignItems: 'center',
|
|
40
44
|
padding: '0 26px',
|
|
41
45
|
borderBottom: '1px solid #495159',
|
|
42
46
|
background: '#0b0e10',
|
|
43
47
|
gap: 14,
|
|
44
|
-
}, children: [
|
|
45
|
-
status.items.map((item, i) => (_jsx("span", { style: {
|
|
46
|
-
opacity: entry.opacity,
|
|
47
|
-
transform: `translateY(${entry.offset}px)`,
|
|
48
|
-
background: `${item.color}${Math.round(entry.glow * 255)
|
|
49
|
-
.toString(16)
|
|
50
|
-
.padStart(2, '0')}`,
|
|
51
|
-
border: `1px solid ${item.color}`,
|
|
52
|
-
color: item.color,
|
|
53
|
-
padding: '3px 10px',
|
|
54
|
-
fontSize: 18,
|
|
55
|
-
lineHeight: 1.3,
|
|
56
|
-
whiteSpace: 'nowrap',
|
|
57
|
-
flexShrink: 0,
|
|
58
|
-
}, children: item.text }, `${status.id}-${i}`))), _jsx("time", { style: {
|
|
48
|
+
}, children: [_jsx(Status, { timeline: timeline, frame: state.contentFrame }), _jsx("time", { style: {
|
|
59
49
|
marginLeft: 'auto',
|
|
60
|
-
|
|
50
|
+
flexShrink: 0,
|
|
51
|
+
fontSize: 32,
|
|
61
52
|
fontFamily: numberFont,
|
|
62
53
|
fontVariantNumeric: 'tabular-nums',
|
|
63
54
|
color: state.jumping ? '#eef2f4' : '#bdc7ce',
|
|
@@ -68,81 +59,76 @@ export function Broadcast(props) {
|
|
|
68
59
|
padding: '2px 8px',
|
|
69
60
|
fontWeight: state.jumping ? 600 : 400,
|
|
70
61
|
letterSpacing: 0.5,
|
|
71
|
-
}, children: clock(state.displayEpoch, timeline.scenario.timezone, true) })] }), _jsxs(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
}, children: clock(state.displayEpoch, timeline.scenario.timezone, true) })] }), _jsxs(AbsoluteFill, { style: {
|
|
63
|
+
filter: jumpStrength > 0
|
|
64
|
+
? `url(#dbs-time-jump) saturate(${1 - jumpStrength * 0.35})`
|
|
65
|
+
: undefined,
|
|
66
|
+
}, children: [_jsxs("main", { style: {
|
|
67
|
+
position: 'absolute',
|
|
68
|
+
left: 0,
|
|
69
|
+
top: layout.header + 1,
|
|
70
|
+
right: layout.sidebar,
|
|
71
|
+
bottom: layout.footer + 1,
|
|
72
|
+
}, children: [_jsx(BroadcastMap, { camera: state.camera, countries: countries, regions: regions, provinces: provinces, events: mapEvents, frame: state.contentFrame }), legend?.type === 'legend' && (_jsx("div", { style: {
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
left: 24,
|
|
75
|
+
bottom: 24,
|
|
76
|
+
display: 'flex',
|
|
77
|
+
flexDirection: 'column',
|
|
78
|
+
gap: 10,
|
|
79
|
+
background: '#0b0e10ed',
|
|
80
|
+
padding: '14px 18px',
|
|
81
|
+
fontSize: 30,
|
|
82
|
+
color: '#d0d5da',
|
|
83
|
+
opacity: changeProgress(state.contentFrame, legend.start),
|
|
84
|
+
transform: `translateY(${10 * (1 - changeProgress(state.contentFrame, legend.start))}px)`,
|
|
85
|
+
}, children: legend.items.map((item, i) => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [_jsx("span", { style: {
|
|
86
|
+
width: 23,
|
|
87
|
+
height: item.symbol === 'line' ? 0 : 16,
|
|
88
|
+
background: item.symbol === 'fill' ? item.color : 'transparent',
|
|
89
|
+
border: item.symbol === 'fill' ? 'none' : `2px solid ${item.color}`,
|
|
90
|
+
} }), _jsx("span", { children: item.text })] }, i))) })), inset?.type === 'inset' && (_jsxs("div", { style: {
|
|
91
|
+
position: 'absolute',
|
|
92
|
+
right: 20,
|
|
93
|
+
bottom: 20,
|
|
94
|
+
width: 420,
|
|
95
|
+
height: 290,
|
|
96
|
+
border: '1px solid #78858e',
|
|
97
|
+
opacity: changeProgress(state.contentFrame, inset.start),
|
|
98
|
+
transform: `translateY(${10 * (1 - changeProgress(state.contentFrame, inset.start))}px)`,
|
|
99
|
+
}, children: [_jsx(BroadcastMap, { camera: inset.camera, countries: countries, regions: regions, provinces: provinces, events: mapEvents, frame: state.contentFrame, inset: true }), _jsx("div", { style: {
|
|
100
|
+
position: 'absolute',
|
|
101
|
+
top: 0,
|
|
102
|
+
left: 0,
|
|
103
|
+
right: 0,
|
|
104
|
+
padding: '7px 12px',
|
|
105
|
+
background: '#0b0e10f2',
|
|
106
|
+
borderBottom: '1px solid #485159',
|
|
107
|
+
fontSize: 28,
|
|
108
|
+
}, children: inset.title })] }))] }), _jsxs("aside", { style: {
|
|
78
109
|
position: 'absolute',
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
top: layout.header + 1,
|
|
111
|
+
right: 0,
|
|
112
|
+
bottom: layout.footer + 1,
|
|
113
|
+
width: layout.sidebar,
|
|
114
|
+
borderLeft: '1px solid #495159',
|
|
115
|
+
background: '#0c1012',
|
|
116
|
+
overflow: 'hidden',
|
|
81
117
|
display: 'flex',
|
|
82
118
|
flexDirection: 'column',
|
|
83
|
-
|
|
84
|
-
background: '#0b0e10ed',
|
|
85
|
-
padding: '14px 18px',
|
|
86
|
-
fontSize: 21,
|
|
87
|
-
color: '#d0d5da',
|
|
88
|
-
}, children: legend.items.map((item, i) => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [_jsx("span", { style: {
|
|
89
|
-
width: 23,
|
|
90
|
-
height: item.symbol === 'line' ? 0 : 16,
|
|
91
|
-
background: item.symbol === 'fill' ? item.color : 'transparent',
|
|
92
|
-
border: item.symbol === 'fill' ? 'none' : `2px solid ${item.color}`,
|
|
93
|
-
} }), _jsx("span", { children: item.text })] }, i))) })), inset?.type === 'inset' && (_jsxs("div", { style: {
|
|
119
|
+
}, children: [_jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'hidden' }, children: _jsx(AlertHistory, { timeline: timeline, frame: state.contentFrame }) }), state.chat && _jsx(ChatPanel, { timeline: timeline, chat: state.chat, frame: state.contentFrame })] }), [bulletins.previous, bulletins.current].map((bulletin, i) => bulletin && (_jsx(Bulletin, { event: bulletin, source: timeline.scenario.agencies[bulletin.source], progress: i === 0 ? 1 - bulletins.progress : bulletins.progress }, bulletin.id))), _jsx("footer", { style: {
|
|
94
120
|
position: 'absolute',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
height:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
background: '#0b0e10f2',
|
|
107
|
-
borderBottom: '1px solid #485159',
|
|
108
|
-
fontSize: 17,
|
|
109
|
-
}, children: inset.title })] }))] }), _jsx("aside", { style: {
|
|
110
|
-
position: 'absolute',
|
|
111
|
-
top: 49,
|
|
112
|
-
right: 0,
|
|
113
|
-
bottom: 93,
|
|
114
|
-
width: 640,
|
|
115
|
-
borderLeft: '1px solid #495159',
|
|
116
|
-
background: '#0c1012',
|
|
117
|
-
overflow: 'hidden',
|
|
118
|
-
}, children: _jsx(AlertHistory, { timeline: timeline, frame: state.contentFrame }) }), bulletin && (_jsx(Bulletin, { event: bulletin, source: timeline.scenario.agencies[bulletin.source], frame: state.contentFrame })), _jsx("footer", { style: {
|
|
119
|
-
position: 'absolute',
|
|
120
|
-
bottom: 0,
|
|
121
|
-
left: 0,
|
|
122
|
-
right: 0,
|
|
123
|
-
height: 92,
|
|
124
|
-
borderTop: '1px solid #606a72',
|
|
125
|
-
display: 'flex',
|
|
126
|
-
alignItems: 'center',
|
|
127
|
-
background: '#0b0e10',
|
|
128
|
-
padding: '0 26px',
|
|
129
|
-
gap: 24,
|
|
130
|
-
}, children: news?.type === 'news' && (_jsxs(_Fragment, { children: [_jsxs("div", { style: { minWidth: 135 }, children: [_jsx("div", { style: { fontSize: 18, color: '#b3c0c9', fontWeight: 600 }, children: timeline.scenario.agencies[news.source] }), _jsx("div", { style: {
|
|
131
|
-
fontSize: 16,
|
|
132
|
-
color: '#85959f',
|
|
133
|
-
marginTop: 5,
|
|
134
|
-
fontFamily: numberFont,
|
|
135
|
-
fontVariantNumeric: 'tabular-nums',
|
|
136
|
-
}, children: clock(news.publishedAt
|
|
137
|
-
? Date.parse(news.publishedAt)
|
|
138
|
-
: stateAt(timeline, news.start).epoch, timeline.scenario.timezone) })] }), _jsx("div", { style: {
|
|
139
|
-
fontSize: news.headline.length > 120 ? 23 : news.headline.length > 85 ? 25 : 30,
|
|
140
|
-
lineHeight: 1.3,
|
|
141
|
-
fontWeight: 400,
|
|
142
|
-
wordBreak: 'keep-all',
|
|
143
|
-
overflowWrap: 'anywhere',
|
|
144
|
-
opacity: Math.min(1, (frame - news.start + 1) / 12),
|
|
145
|
-
}, children: news.headline })] })) })] }), state.visible
|
|
121
|
+
bottom: 0,
|
|
122
|
+
left: 0,
|
|
123
|
+
right: 0,
|
|
124
|
+
height: layout.footer,
|
|
125
|
+
borderTop: '1px solid #606a72',
|
|
126
|
+
display: 'flex',
|
|
127
|
+
alignItems: 'center',
|
|
128
|
+
background: '#0b0e10',
|
|
129
|
+
padding: '0 26px',
|
|
130
|
+
gap: 24,
|
|
131
|
+
}, children: _jsx(News, { timeline: timeline, frame: state.contentFrame }) })] }), _jsx(TimeJump, { jump: state.timeJump })] }), state.visible
|
|
146
132
|
.filter((e) => e.type === 'screen' && ['blackout', 'flash', 'interference'].includes(e.effect))
|
|
147
133
|
.map((event) => {
|
|
148
134
|
if (event.type !== 'screen')
|
|
@@ -173,14 +159,14 @@ export function Broadcast(props) {
|
|
|
173
159
|
alignItems: 'center',
|
|
174
160
|
opacity: frame < openingFrames - 30 ? 1 : (openingFrames - frame) / 30,
|
|
175
161
|
}, children: [_jsx("div", { style: {
|
|
176
|
-
fontSize:
|
|
162
|
+
fontSize: 54,
|
|
177
163
|
fontWeight: 600,
|
|
178
164
|
maxWidth: 1440,
|
|
179
165
|
textAlign: 'center',
|
|
180
166
|
overflowWrap: 'anywhere',
|
|
181
167
|
lineHeight: 1.5,
|
|
182
168
|
}, children: timeline.scenario.title }), _jsx("div", { style: {
|
|
183
|
-
fontSize:
|
|
169
|
+
fontSize: 36,
|
|
184
170
|
color: '#bac3c9',
|
|
185
171
|
marginTop: 22,
|
|
186
172
|
maxWidth: 1200,
|
package/dist/video/Bulletin.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { CompiledEvent } from '../scenario.js';
|
|
3
|
-
export declare function Bulletin({ event, source,
|
|
3
|
+
export declare function Bulletin({ event, source, progress, }: {
|
|
4
4
|
event: Extract<CompiledEvent, {
|
|
5
5
|
type: 'bulletin';
|
|
6
6
|
}>;
|
|
7
7
|
source: string;
|
|
8
|
-
|
|
8
|
+
progress?: number;
|
|
9
9
|
}): React.JSX.Element;
|
package/dist/video/Bulletin.js
CHANGED
|
@@ -1,35 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export function Bulletin({ event, source, frame, }) {
|
|
5
|
-
const chunks = pages(event.text);
|
|
6
|
-
const page = Math.floor((frame - event.start) / FPS / event.pageSeconds) % chunks.length;
|
|
2
|
+
import { layout } from './layout.js';
|
|
3
|
+
export function Bulletin({ event, source, progress = 1, }) {
|
|
7
4
|
return (_jsxs("section", { style: {
|
|
8
5
|
position: 'absolute',
|
|
9
|
-
top:
|
|
10
|
-
bottom:
|
|
6
|
+
top: layout.header + 1,
|
|
7
|
+
bottom: layout.footer + 1,
|
|
11
8
|
left: 0,
|
|
12
9
|
right: 0,
|
|
13
10
|
background: '#0a0d0f',
|
|
11
|
+
opacity: progress,
|
|
12
|
+
transform: `translateY(${16 * (1 - progress)}px)`,
|
|
14
13
|
display: 'flex',
|
|
15
14
|
flexDirection: 'column',
|
|
16
15
|
justifyContent: 'center',
|
|
17
16
|
padding: '50px 190px',
|
|
18
|
-
}, children: [_jsx("div", { style: { fontSize:
|
|
19
|
-
fontSize:
|
|
17
|
+
}, children: [_jsx("div", { style: { fontSize: 32, color: '#b8c3cb', marginBottom: 24 }, children: source }), _jsx("h2", { style: {
|
|
18
|
+
fontSize: 54,
|
|
20
19
|
fontWeight: 600,
|
|
21
20
|
color: event.color,
|
|
22
21
|
margin: '0 0 32px',
|
|
23
22
|
lineHeight: 1.5,
|
|
24
23
|
}, children: event.heading }), _jsx("div", { style: {
|
|
25
|
-
fontSize:
|
|
26
|
-
lineHeight: 1.
|
|
24
|
+
fontSize: 44,
|
|
25
|
+
lineHeight: 1.6,
|
|
27
26
|
whiteSpace: 'pre-wrap',
|
|
28
|
-
wordBreak: '
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
marginTop: 30,
|
|
32
|
-
color: '#8c969d',
|
|
33
|
-
fontFamily: numberFont,
|
|
34
|
-
}, children: [page + 1, " / ", chunks.length] }))] }));
|
|
27
|
+
wordBreak: 'normal',
|
|
28
|
+
overflowWrap: 'anywhere',
|
|
29
|
+
}, children: event.text })] }));
|
|
35
30
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Timeline } from '../scenario.js';
|
|
3
|
+
import { stateAt } from '../timeline.js';
|
|
4
|
+
export declare const chatHeight = 260;
|
|
5
|
+
export declare function ChatPanel({ timeline, chat, frame, }: {
|
|
6
|
+
timeline: Timeline;
|
|
7
|
+
chat: NonNullable<ReturnType<typeof stateAt>['chat']>;
|
|
8
|
+
frame: number;
|
|
9
|
+
}): React.JSX.Element;
|