@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,247 @@
1
+ import { z } from 'zod';
2
+ declare const articleReferenceSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ url: z.ZodString;
5
+ title: z.ZodString;
6
+ excerpt: z.ZodOptional<z.ZodString>;
7
+ time: z.ZodOptional<z.ZodString>;
8
+ categories: z.ZodDefault<z.ZodArray<z.ZodObject<{
9
+ name: z.ZodString;
10
+ slug: z.ZodString;
11
+ }, z.core.$strip>>>;
12
+ hero: z.ZodOptional<z.ZodObject<{
13
+ url: z.ZodString;
14
+ alt: z.ZodString;
15
+ }, z.core.$strip>>;
16
+ featuredImage: z.ZodOptional<z.ZodObject<{
17
+ url: z.ZodString;
18
+ alt: z.ZodString;
19
+ caption: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strip>>;
21
+ updatedAt: z.ZodOptional<z.ZodString>;
22
+ publishedAt: z.ZodOptional<z.ZodString>;
23
+ featured: z.ZodOptional<z.ZodBoolean>;
24
+ }, z.core.$loose>;
25
+ export declare const canonicalArticleSchema: z.ZodObject<{
26
+ id: z.ZodCoercedString<unknown>;
27
+ slug: z.ZodString;
28
+ layout: z.ZodEnum<{
29
+ "article-page": "article-page";
30
+ homepage: "homepage";
31
+ "category-page": "category-page";
32
+ "search-page": "search-page";
33
+ 404: "404";
34
+ "live-story": "live-story";
35
+ "link-in-bio": "link-in-bio";
36
+ }>;
37
+ canonicalUrl: z.ZodString;
38
+ contentVersion: z.ZodString;
39
+ publishedAt: z.ZodString;
40
+ updatedAt: z.ZodString;
41
+ status: z.ZodEnum<{
42
+ draft: "draft";
43
+ published: "published";
44
+ }>;
45
+ title: z.ZodString;
46
+ dek: z.ZodOptional<z.ZodString>;
47
+ excerpt: z.ZodOptional<z.ZodString>;
48
+ language: z.ZodEnum<{
49
+ en: "en";
50
+ es: "es";
51
+ it: "it";
52
+ }>;
53
+ sofascore_id: z.ZodOptional<z.ZodNumber>;
54
+ originalArticleId: z.ZodOptional<z.ZodString>;
55
+ featured: z.ZodBoolean;
56
+ authors: z.ZodArray<z.ZodObject<{
57
+ name: z.ZodString;
58
+ slug: z.ZodString;
59
+ }, z.core.$strip>>;
60
+ categories: z.ZodArray<z.ZodObject<{
61
+ name: z.ZodString;
62
+ slug: z.ZodString;
63
+ }, z.core.$strip>>;
64
+ featuredImage: z.ZodOptional<z.ZodObject<{
65
+ url: z.ZodString;
66
+ alt: z.ZodString;
67
+ caption: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>>;
69
+ media: z.ZodOptional<z.ZodArray<z.ZodObject<{
70
+ url: z.ZodString;
71
+ alt: z.ZodString;
72
+ caption: z.ZodOptional<z.ZodString>;
73
+ }, z.core.$strip>>>;
74
+ hero: z.ZodOptional<z.ZodObject<{
75
+ url: z.ZodString;
76
+ alt: z.ZodString;
77
+ }, z.core.$strip>>;
78
+ body: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
79
+ type: z.ZodLiteral<"richText">;
80
+ html: z.ZodString;
81
+ }, z.core.$strip>, z.ZodObject<{
82
+ type: z.ZodLiteral<"heading">;
83
+ text: z.ZodString;
84
+ level: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>]>>;
85
+ }, z.core.$strip>, z.ZodObject<{
86
+ type: z.ZodLiteral<"image">;
87
+ url: z.ZodString;
88
+ alt: z.ZodString;
89
+ caption: z.ZodOptional<z.ZodString>;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ type: z.ZodLiteral<"list">;
92
+ ordered: z.ZodDefault<z.ZodBoolean>;
93
+ items: z.ZodArray<z.ZodString>;
94
+ }, z.core.$strip>, z.ZodObject<{
95
+ type: z.ZodLiteral<"divider">;
96
+ }, z.core.$strip>, z.ZodObject<{
97
+ type: z.ZodLiteral<"infoBox">;
98
+ title: z.ZodOptional<z.ZodString>;
99
+ tone: z.ZodDefault<z.ZodEnum<{
100
+ success: "success";
101
+ neutral: "neutral";
102
+ info: "info";
103
+ warning: "warning";
104
+ }>>;
105
+ html: z.ZodString;
106
+ }, z.core.$strip>, z.ZodObject<{
107
+ type: z.ZodLiteral<"keyPoints">;
108
+ title: z.ZodDefault<z.ZodString>;
109
+ points: z.ZodArray<z.ZodString>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ type: z.ZodLiteral<"dataTable">;
112
+ caption: z.ZodOptional<z.ZodString>;
113
+ headers: z.ZodArray<z.ZodString>;
114
+ rows: z.ZodArray<z.ZodArray<z.ZodString>>;
115
+ }, z.core.$strip>, z.ZodObject<{
116
+ type: z.ZodLiteral<"liveUpdate">;
117
+ timestamp: z.ZodString;
118
+ headline: z.ZodString;
119
+ html: z.ZodOptional<z.ZodString>;
120
+ media: z.ZodOptional<z.ZodArray<z.ZodObject<{
121
+ url: z.ZodString;
122
+ alt: z.ZodString;
123
+ caption: z.ZodOptional<z.ZodString>;
124
+ }, z.core.$strip>>>;
125
+ }, z.core.$strip>, z.ZodObject<{
126
+ type: z.ZodLiteral<"audio">;
127
+ url: z.ZodString;
128
+ title: z.ZodOptional<z.ZodString>;
129
+ caption: z.ZodOptional<z.ZodString>;
130
+ }, z.core.$strip>, z.ZodObject<{
131
+ type: z.ZodLiteral<"youtube">;
132
+ videoId: z.ZodString;
133
+ }, z.core.$strip>, z.ZodObject<{
134
+ type: z.ZodLiteral<"x">;
135
+ url: z.ZodString;
136
+ }, z.core.$strip>, z.ZodObject<{
137
+ type: z.ZodLiteral<"instagram">;
138
+ url: z.ZodString;
139
+ }, z.core.$strip>, z.ZodObject<{
140
+ type: z.ZodLiteral<"tiktok">;
141
+ url: z.ZodString;
142
+ }, z.core.$strip>, z.ZodObject<{
143
+ type: z.ZodLiteral<"pullQuote">;
144
+ text: z.ZodString;
145
+ attribution: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strip>], "type">>;
147
+ seo: z.ZodOptional<z.ZodObject<{
148
+ metaTitle: z.ZodOptional<z.ZodString>;
149
+ metaDescription: z.ZodOptional<z.ZodString>;
150
+ ogImage: z.ZodOptional<z.ZodString>;
151
+ }, z.core.$strip>>;
152
+ navigation: z.ZodOptional<z.ZodObject<{
153
+ categories: z.ZodArray<z.ZodObject<{
154
+ name: z.ZodString;
155
+ slug: z.ZodString;
156
+ }, z.core.$strip>>;
157
+ }, z.core.$strip>>;
158
+ articles: z.ZodOptional<z.ZodArray<z.ZodObject<{
159
+ id: z.ZodString;
160
+ url: z.ZodString;
161
+ title: z.ZodString;
162
+ excerpt: z.ZodOptional<z.ZodString>;
163
+ time: z.ZodOptional<z.ZodString>;
164
+ categories: z.ZodDefault<z.ZodArray<z.ZodObject<{
165
+ name: z.ZodString;
166
+ slug: z.ZodString;
167
+ }, z.core.$strip>>>;
168
+ hero: z.ZodOptional<z.ZodObject<{
169
+ url: z.ZodString;
170
+ alt: z.ZodString;
171
+ }, z.core.$strip>>;
172
+ featuredImage: z.ZodOptional<z.ZodObject<{
173
+ url: z.ZodString;
174
+ alt: z.ZodString;
175
+ caption: z.ZodOptional<z.ZodString>;
176
+ }, z.core.$strip>>;
177
+ updatedAt: z.ZodOptional<z.ZodString>;
178
+ publishedAt: z.ZodOptional<z.ZodString>;
179
+ featured: z.ZodOptional<z.ZodBoolean>;
180
+ }, z.core.$loose>>>;
181
+ heroSlides: z.ZodOptional<z.ZodArray<z.ZodObject<{
182
+ image: z.ZodString;
183
+ alt: z.ZodOptional<z.ZodString>;
184
+ }, z.core.$strip>>>;
185
+ heroLayout: z.ZodOptional<z.ZodEnum<{
186
+ spotlight: "spotlight";
187
+ editorial: "editorial";
188
+ }>>;
189
+ heroCategories: z.ZodOptional<z.ZodArray<z.ZodObject<{
190
+ name: z.ZodString;
191
+ slug: z.ZodString;
192
+ }, z.core.$strip>>>;
193
+ showHero: z.ZodOptional<z.ZodBoolean>;
194
+ showEditorialHero: z.ZodOptional<z.ZodBoolean>;
195
+ breakingNews: z.ZodOptional<z.ZodObject<{
196
+ displayClass: z.ZodOptional<z.ZodString>;
197
+ sidebarFeature: z.ZodOptional<z.ZodObject<{
198
+ category: z.ZodString;
199
+ title: z.ZodString;
200
+ url: z.ZodString;
201
+ image: z.ZodString;
202
+ excerpt: z.ZodOptional<z.ZodString>;
203
+ publishedAt: z.ZodOptional<z.ZodString>;
204
+ }, z.core.$strip>>;
205
+ sidebarSub: z.ZodOptional<z.ZodObject<{
206
+ category: z.ZodString;
207
+ title: z.ZodString;
208
+ url: z.ZodString;
209
+ image: z.ZodString;
210
+ alt: z.ZodOptional<z.ZodString>;
211
+ readTime: z.ZodOptional<z.ZodString>;
212
+ excerpt: z.ZodOptional<z.ZodString>;
213
+ }, z.core.$strip>>;
214
+ main: z.ZodOptional<z.ZodObject<{
215
+ category: z.ZodString;
216
+ title: z.ZodString;
217
+ url: z.ZodString;
218
+ liveUrl: z.ZodOptional<z.ZodString>;
219
+ sofascore_id: z.ZodOptional<z.ZodNumber>;
220
+ image: z.ZodString;
221
+ alt: z.ZodOptional<z.ZodString>;
222
+ excerpt: z.ZodOptional<z.ZodString>;
223
+ }, z.core.$strip>>;
224
+ updates: z.ZodOptional<z.ZodArray<z.ZodObject<{
225
+ timestamp: z.ZodOptional<z.ZodString>;
226
+ time: z.ZodOptional<z.ZodString>;
227
+ text: z.ZodOptional<z.ZodString>;
228
+ html: z.ZodOptional<z.ZodString>;
229
+ }, z.core.$strip>>>;
230
+ snacks: z.ZodOptional<z.ZodArray<z.ZodObject<{
231
+ category: z.ZodOptional<z.ZodString>;
232
+ readTime: z.ZodOptional<z.ZodString>;
233
+ title: z.ZodString;
234
+ url: z.ZodString;
235
+ excerpt: z.ZodOptional<z.ZodString>;
236
+ image: z.ZodOptional<z.ZodString>;
237
+ alt: z.ZodOptional<z.ZodString>;
238
+ }, z.core.$strip>>>;
239
+ }, z.core.$strip>>;
240
+ liveStory: z.ZodOptional<z.ZodObject<{
241
+ lastUpdated: z.ZodOptional<z.ZodString>;
242
+ keyPoints: z.ZodOptional<z.ZodArray<z.ZodString>>;
243
+ }, z.core.$strip>>;
244
+ }, z.core.$loose>;
245
+ export type SelfReference = z.infer<typeof articleReferenceSchema>;
246
+ export type CanonicalArticle = z.infer<typeof canonicalArticleSchema>;
247
+ export {};
@@ -0,0 +1,235 @@
1
+ import { z } from 'zod';
2
+ const isoDateTime = z.string().datetime();
3
+ const authorSchema = z.object({
4
+ name: z.string(),
5
+ slug: z.string()
6
+ });
7
+ const categorySchema = z.object({
8
+ name: z.string(),
9
+ slug: z.string()
10
+ });
11
+ const featuredImageSchema = z.object({
12
+ url: z.string(),
13
+ alt: z.string(),
14
+ caption: z.string().optional()
15
+ });
16
+ const heroSchema = z.object({
17
+ url: z.string(),
18
+ alt: z.string()
19
+ });
20
+ const bodyBlockSchema = z.discriminatedUnion('type', [
21
+ z.object({
22
+ type: z.literal('richText'),
23
+ html: z.string()
24
+ }),
25
+ z.object({
26
+ type: z.literal('heading'),
27
+ text: z.string(),
28
+ level: z.union([z.literal(2), z.literal(3), z.literal(4)]).default(2)
29
+ }),
30
+ z.object({
31
+ type: z.literal('image'),
32
+ url: z.string(),
33
+ alt: z.string(),
34
+ caption: z.string().optional()
35
+ }),
36
+ z.object({
37
+ type: z.literal('list'),
38
+ ordered: z.boolean().default(false),
39
+ items: z.array(z.string()).min(1)
40
+ }),
41
+ z.object({
42
+ type: z.literal('divider')
43
+ }),
44
+ z.object({
45
+ type: z.literal('infoBox'),
46
+ title: z.string().optional(),
47
+ tone: z.enum(['neutral', 'info', 'warning', 'success']).default('neutral'),
48
+ html: z.string()
49
+ }),
50
+ z.object({
51
+ type: z.literal('keyPoints'),
52
+ title: z.string().default('Key Points'),
53
+ points: z.array(z.string()).min(1)
54
+ }),
55
+ z.object({
56
+ type: z.literal('dataTable'),
57
+ caption: z.string().optional(),
58
+ headers: z.array(z.string()).min(1),
59
+ rows: z.array(z.array(z.string())).min(1)
60
+ }),
61
+ z.object({
62
+ type: z.literal('liveUpdate'),
63
+ timestamp: isoDateTime,
64
+ headline: z.string(),
65
+ html: z.string().optional(),
66
+ media: z
67
+ .array(z.object({
68
+ url: z.string(),
69
+ alt: z.string(),
70
+ caption: z.string().optional()
71
+ }))
72
+ .optional()
73
+ }),
74
+ z.object({
75
+ type: z.literal('audio'),
76
+ url: z.string().url(),
77
+ title: z.string().optional(),
78
+ caption: z.string().optional()
79
+ }),
80
+ z.object({
81
+ type: z.literal('youtube'),
82
+ videoId: z.string()
83
+ }),
84
+ z.object({
85
+ type: z.literal('x'),
86
+ url: z.string().url()
87
+ }),
88
+ z.object({
89
+ type: z.literal('instagram'),
90
+ url: z.string().url()
91
+ }),
92
+ z.object({
93
+ type: z.literal('tiktok'),
94
+ url: z.string().url()
95
+ }),
96
+ z.object({
97
+ type: z.literal('pullQuote'),
98
+ text: z.string(),
99
+ attribution: z.string().optional()
100
+ })
101
+ ]);
102
+ const articleReferenceSchema = z
103
+ .object({
104
+ id: z.string().uuid(),
105
+ url: z.string(),
106
+ title: z.string(),
107
+ excerpt: z.string().optional(),
108
+ time: z.string().optional(),
109
+ categories: z.array(categorySchema).default([]),
110
+ hero: heroSchema.optional(),
111
+ featuredImage: featuredImageSchema.optional(),
112
+ updatedAt: isoDateTime.optional(),
113
+ publishedAt: isoDateTime.optional(),
114
+ featured: z.boolean().optional()
115
+ })
116
+ .passthrough();
117
+ export const canonicalArticleSchema = z
118
+ .object({
119
+ id: z.coerce.string(),
120
+ slug: z.string(),
121
+ layout: z.enum([
122
+ 'article-page',
123
+ 'homepage',
124
+ 'category-page',
125
+ 'search-page',
126
+ '404',
127
+ 'live-story',
128
+ 'link-in-bio',
129
+ ]),
130
+ canonicalUrl: z.string(),
131
+ contentVersion: isoDateTime,
132
+ publishedAt: isoDateTime,
133
+ updatedAt: isoDateTime,
134
+ status: z.enum(['draft', 'published']),
135
+ title: z.string(),
136
+ dek: z.string().optional(),
137
+ excerpt: z.string().optional(),
138
+ language: z.enum(['en', 'es', 'it']),
139
+ sofascore_id: z.number().int().positive().optional(),
140
+ originalArticleId: z.string().optional(),
141
+ featured: z.boolean(),
142
+ authors: z.array(authorSchema),
143
+ categories: z.array(categorySchema),
144
+ featuredImage: featuredImageSchema.optional(),
145
+ media: z.array(featuredImageSchema).optional(),
146
+ hero: heroSchema.optional(),
147
+ body: z.array(bodyBlockSchema),
148
+ seo: z
149
+ .object({
150
+ metaTitle: z.string().optional(),
151
+ metaDescription: z.string().optional(),
152
+ ogImage: z.string().optional()
153
+ })
154
+ .optional(),
155
+ navigation: z
156
+ .object({
157
+ categories: z.array(categorySchema)
158
+ })
159
+ .optional(),
160
+ articles: z.array(articleReferenceSchema).optional(),
161
+ heroSlides: z
162
+ .array(z.object({
163
+ image: z.string(),
164
+ alt: z.string().optional()
165
+ }))
166
+ .optional(),
167
+ heroLayout: z.enum(['spotlight', 'editorial']).optional(),
168
+ heroCategories: z.array(categorySchema).optional(),
169
+ showHero: z.boolean().optional(),
170
+ showEditorialHero: z.boolean().optional(),
171
+ breakingNews: z
172
+ .object({
173
+ displayClass: z.string().optional(),
174
+ sidebarFeature: z
175
+ .object({
176
+ category: z.string(),
177
+ title: z.string(),
178
+ url: z.string(),
179
+ image: z.string(),
180
+ excerpt: z.string().optional(),
181
+ publishedAt: isoDateTime.optional()
182
+ })
183
+ .optional(),
184
+ sidebarSub: z
185
+ .object({
186
+ category: z.string(),
187
+ title: z.string(),
188
+ url: z.string(),
189
+ image: z.string(),
190
+ alt: z.string().optional(),
191
+ readTime: z.string().optional(),
192
+ excerpt: z.string().optional()
193
+ })
194
+ .optional(),
195
+ main: z
196
+ .object({
197
+ category: z.string(),
198
+ title: z.string(),
199
+ url: z.string(),
200
+ liveUrl: z.string().optional(),
201
+ sofascore_id: z.number().int().positive().optional(),
202
+ image: z.string(),
203
+ alt: z.string().optional(),
204
+ excerpt: z.string().optional()
205
+ })
206
+ .optional(),
207
+ updates: z
208
+ .array(z.object({
209
+ timestamp: isoDateTime.optional(),
210
+ time: z.string().optional(),
211
+ text: z.string().optional(),
212
+ html: z.string().optional()
213
+ }))
214
+ .optional(),
215
+ snacks: z
216
+ .array(z.object({
217
+ category: z.string().optional(),
218
+ readTime: z.string().optional(),
219
+ title: z.string(),
220
+ url: z.string(),
221
+ excerpt: z.string().optional(),
222
+ image: z.string().optional(),
223
+ alt: z.string().optional()
224
+ }))
225
+ .optional()
226
+ })
227
+ .optional(),
228
+ liveStory: z
229
+ .object({
230
+ lastUpdated: z.string().optional(),
231
+ keyPoints: z.array(z.string()).optional()
232
+ })
233
+ .optional()
234
+ })
235
+ .passthrough();
@@ -0,0 +1,3 @@
1
+ export declare function normalizeSofascoreId(value: unknown): string | null;
2
+ export declare function buildSofascoreAttackMomentumUrl(value: unknown): string;
3
+ export declare function buildSofascoreMatchUrl(value: unknown): string;
@@ -0,0 +1,31 @@
1
+ const SOFASCORE_WIDGET_BASE = 'https://widgets.sofascore.com/embed/attackMomentum';
2
+ const SOFASCORE_MATCH_BASE = 'https://www.sofascore.com/football/match';
3
+ export function normalizeSofascoreId(value) {
4
+ if (typeof value === 'number') {
5
+ if (!Number.isInteger(value) || value <= 0)
6
+ return null;
7
+ return String(value);
8
+ }
9
+ if (typeof value === 'string') {
10
+ const trimmed = value.trim();
11
+ if (!/^\d+$/.test(trimmed))
12
+ return null;
13
+ const numeric = Number.parseInt(trimmed, 10);
14
+ if (!Number.isFinite(numeric) || numeric <= 0)
15
+ return null;
16
+ return String(numeric);
17
+ }
18
+ return null;
19
+ }
20
+ export function buildSofascoreAttackMomentumUrl(value) {
21
+ const normalizedId = normalizeSofascoreId(value);
22
+ if (!normalizedId)
23
+ return '';
24
+ return `${SOFASCORE_WIDGET_BASE}?id=${normalizedId}&widgetTheme=light`;
25
+ }
26
+ export function buildSofascoreMatchUrl(value) {
27
+ const normalizedId = normalizeSofascoreId(value);
28
+ if (!normalizedId)
29
+ return '';
30
+ return `${SOFASCORE_MATCH_BASE}#id:${normalizedId}`;
31
+ }
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@fifthbell/brokaw",
3
+ "description": "Server-side Handlebars renderer and templates for Fifth Bell pages.",
4
+ "version": "0.1.39",
5
+ "license": "MIT",
6
+ "author": "Fifth Bell",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/fifthbell/brokaw.git"
10
+ },
11
+ "homepage": "https://github.com/fifthbell/brokaw#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/fifthbell/brokaw/issues"
14
+ },
15
+ "keywords": [
16
+ "handlebars",
17
+ "ssr",
18
+ "renderer",
19
+ "news",
20
+ "templates"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20.0.0"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src/templates",
28
+ "src/partial-deps.json",
29
+ "src/styles/compiled.css"
30
+ ],
31
+ "type": "module",
32
+ "exports": {
33
+ ".": "./dist/renderer.js",
34
+ "./components/live-program": "./dist/components/live-program/LiveProgram.js",
35
+ "./partial-deps.json": "./src/partial-deps.json"
36
+ },
37
+ "scripts": {
38
+ "clean": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\"",
39
+ "build:css": "tailwindcss -i src/styles/global.css -o src/styles/compiled.css --minify",
40
+ "build": "npm run clean && npm run build:css && tsc -p tsconfig.build.json",
41
+ "storybook": "npm run build:css && storybook dev -p 6006",
42
+ "build-storybook": "npm run build:css && storybook build",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json",
44
+ "test": "npm run test:unit",
45
+ "test:unit": "vitest run --project unit"
46
+ },
47
+ "dependencies": {
48
+ "date-fns": "^4.1.0",
49
+ "fast-average-color": "^9.5.2",
50
+ "framer-motion": "^12.38.0",
51
+ "handlebars": "^4.7.8",
52
+ "lucide-react": "^1.11.0",
53
+ "qrcode-generator": "^2.0.4",
54
+ "react": "^19.2.5",
55
+ "react-dom": "^19.2.5",
56
+ "zod": "^4.3.6"
57
+ },
58
+ "devDependencies": {
59
+ "@chromatic-com/storybook": "^5.0.1",
60
+ "@storybook/addon-a11y": "^10.2.16",
61
+ "@storybook/addon-docs": "^10.2.16",
62
+ "@storybook/addon-vitest": "^10.2.16",
63
+ "@storybook/html-vite": "^10.2.16",
64
+ "@tailwindcss/cli": "^4.2.1",
65
+ "@types/handlebars": "^4.1.0",
66
+ "@types/node": "^20.19.37",
67
+ "@types/react": "^19.2.14",
68
+ "@types/react-dom": "^19.2.3",
69
+ "@vitest/browser-playwright": "^4.0.18",
70
+ "@vitest/coverage-v8": "^4.0.18",
71
+ "playwright": "^1.58.2",
72
+ "storybook": "^10.2.16",
73
+ "tailwindcss": "^4.2.1",
74
+ "tsx": "^4.21.0",
75
+ "typescript": "^5.9.3",
76
+ "vitest": "^4.0.18"
77
+ }
78
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "shell/doc-start-standard": ["article-page", "homepage", "category-page", "search-page"],
3
+ "shell/doc-start-404": ["404"],
4
+ "shell/doc-end": ["article-page", "homepage", "category-page", "search-page", "404"],
5
+ "headers/header-main": ["article-page", "homepage", "category-page", "search-page"],
6
+ "headers/header-minimal": ["404"],
7
+ "footers/footer-full": ["article-page", "homepage", "category-page", "search-page"],
8
+ "footers/footer-minimal": ["404"],
9
+ "nav/nav-categories": ["article-page", "homepage", "category-page", "search-page"],
10
+ "components/article-main": ["article-page"],
11
+ "components/home/main": ["homepage"],
12
+ "components/category/main": ["category-page"],
13
+ "components/search/main": ["search-page"],
14
+ "components/not-found/main": ["404"],
15
+ "blocks/rich-text": ["article-page"],
16
+ "blocks/heading": ["article-page"],
17
+ "blocks/image": ["article-page"],
18
+ "blocks/list": ["article-page"],
19
+ "blocks/divider": ["article-page"],
20
+ "blocks/info-box": ["article-page"],
21
+ "blocks/key-points": ["article-page"],
22
+ "blocks/data-table": ["article-page"],
23
+ "blocks/live-update": ["article-page"],
24
+ "blocks/audio": ["article-page"],
25
+ "blocks/youtube": ["article-page"],
26
+ "blocks/x": ["article-page"],
27
+ "blocks/instagram": ["article-page"],
28
+ "blocks/tiktok": ["article-page"],
29
+ "blocks/pull-quote": ["article-page"],
30
+ "components/home/landing": ["homepage"],
31
+ "components/home/must-read": ["homepage"],
32
+ "components/home/more-stories": ["homepage"],
33
+ "components/category/header": ["category-page"],
34
+ "components/category/main-grid": ["category-page"],
35
+ "components/category/more-grid": ["category-page"],
36
+ "components/breaking-news": ["homepage"],
37
+ "components/breaking-news/live-updates-column": ["homepage"],
38
+ "components/snack": ["homepage", "category-page"],
39
+ "components/headline": ["homepage"],
40
+ "components/spotlight-hero": ["homepage"],
41
+ "components/editorial-hero": ["homepage"],
42
+ "components/hero": ["homepage"],
43
+ "components/hero-editorial": ["homepage"],
44
+ "components/trending": ["homepage"],
45
+ "components/ui/accordion": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"],
46
+ "components/ui/breadcrumb": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"],
47
+ "components/ui/icon-button": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"],
48
+ "components/ui/loading-spinner": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"],
49
+ "components/ui/pagination": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"],
50
+ "components/ui/scroll-area": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"],
51
+ "components/ui/status-badge": ["article-page", "homepage", "category-page", "search-page", "live-story", "404"]
52
+ }