@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,420 @@
|
|
|
1
|
+
<main class='flex-1 container mx-auto px-4'>
|
|
2
|
+
<div class='mx-auto max-w-5xl py-10'>
|
|
3
|
+
<header class='mb-8'>
|
|
4
|
+
<p class='text-xs font-bold uppercase tracking-[0.2em] text-[#b21100]'>Search</p>
|
|
5
|
+
<h1 class='mt-3 text-4xl md:text-5xl font-bold text-slate-900 dark:text-slate-100 font-encode'>
|
|
6
|
+
Find Stories Fast
|
|
7
|
+
</h1>
|
|
8
|
+
<p class='mt-3 text-lg text-slate-600 dark:text-slate-400 font-serif'>
|
|
9
|
+
Search titles and excerpts across current-language coverage.
|
|
10
|
+
</p>
|
|
11
|
+
</header>
|
|
12
|
+
|
|
13
|
+
<section class='rounded-2xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-5 md:p-6'>
|
|
14
|
+
<form id='search-page-form' class='flex flex-col md:flex-row gap-3' role='search' method='get'>
|
|
15
|
+
<input
|
|
16
|
+
id='search-page-input'
|
|
17
|
+
name='q'
|
|
18
|
+
type='search'
|
|
19
|
+
autocomplete='off'
|
|
20
|
+
spellcheck='false'
|
|
21
|
+
placeholder='Search stories...'
|
|
22
|
+
class='w-full rounded-xl border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-950 px-4 py-3 text-base text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-[#b21100]/40'
|
|
23
|
+
/>
|
|
24
|
+
<button
|
|
25
|
+
type='submit'
|
|
26
|
+
class='rounded-xl bg-[#b21100] hover:bg-[#8f0d00] text-white px-6 py-3 font-bold uppercase tracking-wide text-sm transition-colors'
|
|
27
|
+
>
|
|
28
|
+
Search
|
|
29
|
+
</button>
|
|
30
|
+
</form>
|
|
31
|
+
<p id='search-results-meta' class='mt-4 text-sm text-slate-500 dark:text-slate-400'></p>
|
|
32
|
+
</section>
|
|
33
|
+
|
|
34
|
+
<section id='search-results-list' class='mt-8 grid grid-cols-1 md:grid-cols-2 gap-6'></section>
|
|
35
|
+
|
|
36
|
+
<nav id='search-results-pagination' class='mt-8 flex flex-wrap items-center gap-2'></nav>
|
|
37
|
+
</div>
|
|
38
|
+
</main>
|
|
39
|
+
|
|
40
|
+
<script>
|
|
41
|
+
(function () {
|
|
42
|
+
const PAGE_SIZE = 20;
|
|
43
|
+
const CDN_BASE = 'https://cdn.fifthbell.com/content';
|
|
44
|
+
const cache = window.__fifthbellSearchCache || (window.__fifthbellSearchCache = new Map());
|
|
45
|
+
|
|
46
|
+
const form = document.getElementById('search-page-form');
|
|
47
|
+
const input = document.getElementById('search-page-input');
|
|
48
|
+
const meta = document.getElementById('search-results-meta');
|
|
49
|
+
const list = document.getElementById('search-results-list');
|
|
50
|
+
const pagination = document.getElementById('search-results-pagination');
|
|
51
|
+
if (!form || !input || !meta || !list || !pagination) return;
|
|
52
|
+
|
|
53
|
+
function getLanguage() {
|
|
54
|
+
const path = window.location.pathname || '/';
|
|
55
|
+
if (path.startsWith('/es')) return 'es';
|
|
56
|
+
if (path.startsWith('/it')) return 'it';
|
|
57
|
+
return 'en';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizePath(path) {
|
|
61
|
+
if (typeof path !== 'string') return '/';
|
|
62
|
+
const stripped = (path.split('?')[0] || '/').trim();
|
|
63
|
+
if (!stripped) return '/';
|
|
64
|
+
const normalized = `/${stripped.replace(/^\/+/, '')}`.replace(/\/{2,}/g, '/');
|
|
65
|
+
if (normalized !== '/' && normalized.endsWith('/')) {
|
|
66
|
+
return normalized.slice(0, -1);
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function repairMojibake(value) {
|
|
72
|
+
if (typeof value !== 'string' || !value) return '';
|
|
73
|
+
if (!/[ÃÂâ€]/.test(value)) return value;
|
|
74
|
+
try {
|
|
75
|
+
const bytes = new Uint8Array(Array.from(value, (char) => char.charCodeAt(0) & 0xff));
|
|
76
|
+
const decoded = new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
|
77
|
+
if (decoded && decoded !== value) return decoded;
|
|
78
|
+
} catch {
|
|
79
|
+
// Keep original text when repair fails.
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function foldForSearch(value) {
|
|
85
|
+
const repaired = repairMojibake(String(value || ''));
|
|
86
|
+
return repaired
|
|
87
|
+
.normalize('NFD')
|
|
88
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
89
|
+
.toLowerCase()
|
|
90
|
+
.trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function escapeHtml(value) {
|
|
94
|
+
return String(value || '')
|
|
95
|
+
.replace(/&/g, '&')
|
|
96
|
+
.replace(/</g, '<')
|
|
97
|
+
.replace(/>/g, '>')
|
|
98
|
+
.replace(/"/g, '"')
|
|
99
|
+
.replace(/'/g, ''');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getSearchBasePath(language) {
|
|
103
|
+
return language === 'en' ? '/search' : `/${language}/search`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isStorybookPreview() {
|
|
107
|
+
const url = new URL(window.location.href);
|
|
108
|
+
return url.pathname.endsWith('/iframe.html') && url.searchParams.has('id');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isOnSearchPage(language) {
|
|
112
|
+
const searchBasePath = getSearchBasePath(language);
|
|
113
|
+
const currentPath = normalizePath(window.location.pathname);
|
|
114
|
+
if (currentPath === searchBasePath) return true;
|
|
115
|
+
|
|
116
|
+
// In Storybook, pages render inside /iframe.html while still representing search-page context.
|
|
117
|
+
if (isStorybookPreview()) return true;
|
|
118
|
+
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseQueryState() {
|
|
123
|
+
const url = new URL(window.location.href);
|
|
124
|
+
const q = (url.searchParams.get('q') || '').trim();
|
|
125
|
+
const pageRaw = Number.parseInt(url.searchParams.get('page') || '1', 10);
|
|
126
|
+
const page = Number.isFinite(pageRaw) && pageRaw > 0 ? pageRaw : 1;
|
|
127
|
+
return { q, page };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toHref(language, query, page) {
|
|
131
|
+
const base = getSearchBasePath(language);
|
|
132
|
+
const params = new URLSearchParams();
|
|
133
|
+
params.set('q', query);
|
|
134
|
+
if (page > 1) params.set('page', String(page));
|
|
135
|
+
return `${base}?${params.toString()}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function navigateToSearchUrl(url, options = { replace: false }) {
|
|
139
|
+
const language = getLanguage();
|
|
140
|
+
if (!isOnSearchPage(language)) {
|
|
141
|
+
window.location.href = url;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const currentUrl = new URL(window.location.href);
|
|
146
|
+
const targetUrl = new URL(url, window.location.origin);
|
|
147
|
+
const nextQ = (targetUrl.searchParams.get('q') || '').trim();
|
|
148
|
+
const nextPage = (targetUrl.searchParams.get('page') || '').trim();
|
|
149
|
+
|
|
150
|
+
if (nextQ) {
|
|
151
|
+
currentUrl.searchParams.set('q', nextQ);
|
|
152
|
+
} else {
|
|
153
|
+
currentUrl.searchParams.delete('q');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (nextPage) {
|
|
157
|
+
currentUrl.searchParams.set('page', nextPage);
|
|
158
|
+
} else {
|
|
159
|
+
currentUrl.searchParams.delete('page');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const nextRelative = `${currentUrl.pathname}?${currentUrl.searchParams.toString()}${currentUrl.hash}`;
|
|
163
|
+
const method = options.replace ? 'replaceState' : 'pushState';
|
|
164
|
+
window.history[method](null, '', nextRelative);
|
|
165
|
+
runSearch();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function fetchJson(url) {
|
|
169
|
+
const response = await fetch(url, {
|
|
170
|
+
cache: 'no-store',
|
|
171
|
+
headers: { 'Content-Type': 'application/json' }
|
|
172
|
+
});
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
throw new Error(`Failed to fetch ${url}: ${response.status}`);
|
|
175
|
+
}
|
|
176
|
+
return response.json();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function loadIndex(language) {
|
|
180
|
+
const cacheKey = `index:${language}`;
|
|
181
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
182
|
+
|
|
183
|
+
const manifest = await fetchJson(`${CDN_BASE}/search-manifest-${language}.json?v=${Date.now()}`);
|
|
184
|
+
const fileList = [];
|
|
185
|
+
if (manifest?.files?.currentMonth) fileList.push(manifest.files.currentMonth);
|
|
186
|
+
if (Array.isArray(manifest?.files?.yearly)) {
|
|
187
|
+
for (const file of manifest.files.yearly) {
|
|
188
|
+
if (typeof file === 'string') fileList.push(file);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const uniqueFiles = [...new Set(fileList)];
|
|
193
|
+
const payloads = await Promise.all(
|
|
194
|
+
uniqueFiles.map(async (file) => {
|
|
195
|
+
try {
|
|
196
|
+
return await fetchJson(`${CDN_BASE}/${file}?v=${Date.now()}`);
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const combined = [];
|
|
204
|
+
let position = 0;
|
|
205
|
+
for (const payload of payloads) {
|
|
206
|
+
const articles = Array.isArray(payload?.articles) ? payload.articles : [];
|
|
207
|
+
for (const article of articles) {
|
|
208
|
+
combined.push({
|
|
209
|
+
title: repairMojibake(typeof article?.title === 'string' ? article.title : ''),
|
|
210
|
+
slug: normalizePath(typeof article?.slug === 'string' ? article.slug : '/'),
|
|
211
|
+
excerpt: repairMojibake(typeof article?.excerpt === 'string' ? article.excerpt : ''),
|
|
212
|
+
photoUrl: typeof article?.photoUrl === 'string' ? article.photoUrl : '',
|
|
213
|
+
publishedAt: typeof article?.publishedAt === 'string' ? article.publishedAt : '',
|
|
214
|
+
position
|
|
215
|
+
});
|
|
216
|
+
position += 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const deduped = [];
|
|
221
|
+
const seen = new Set();
|
|
222
|
+
for (const article of combined) {
|
|
223
|
+
const key = `${article.slug}::${article.title}`;
|
|
224
|
+
if (seen.has(key)) continue;
|
|
225
|
+
seen.add(key);
|
|
226
|
+
deduped.push(article);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
cache.set(cacheKey, deduped);
|
|
230
|
+
return deduped;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function formatPublishedAt(value, language) {
|
|
234
|
+
if (typeof value !== 'string' || !value) return '';
|
|
235
|
+
const date = new Date(value);
|
|
236
|
+
if (Number.isNaN(date.getTime())) return '';
|
|
237
|
+
const locale = language === 'es' ? 'es-ES' : language === 'it' ? 'it-IT' : 'en-US';
|
|
238
|
+
return date.toLocaleDateString(locale, {
|
|
239
|
+
month: 'short',
|
|
240
|
+
day: 'numeric',
|
|
241
|
+
year: 'numeric'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function scoreArticle(article, tokens, fullQuery) {
|
|
246
|
+
const title = foldForSearch(article.title || '');
|
|
247
|
+
const excerpt = foldForSearch(article.excerpt || '');
|
|
248
|
+
const haystack = `${title} ${excerpt}`;
|
|
249
|
+
let score = 0;
|
|
250
|
+
|
|
251
|
+
if (fullQuery && title.includes(fullQuery)) {
|
|
252
|
+
score += 60;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const token of tokens) {
|
|
256
|
+
if (!haystack.includes(token)) {
|
|
257
|
+
return -1;
|
|
258
|
+
}
|
|
259
|
+
if (title.includes(token)) score += 15;
|
|
260
|
+
if (excerpt.includes(token)) score += 5;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (title.startsWith(tokens[0] || '')) {
|
|
264
|
+
score += 20;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return score;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function searchArticles(articles, query) {
|
|
271
|
+
const normalizedQuery = foldForSearch(query);
|
|
272
|
+
if (!normalizedQuery) return [];
|
|
273
|
+
const tokens = normalizedQuery.split(/\s+/g).filter(Boolean);
|
|
274
|
+
if (tokens.length === 0) return [];
|
|
275
|
+
|
|
276
|
+
return articles
|
|
277
|
+
.map((article) => {
|
|
278
|
+
const score = scoreArticle(article, tokens, normalizedQuery);
|
|
279
|
+
if (score < 0) return null;
|
|
280
|
+
return { ...article, score };
|
|
281
|
+
})
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
.sort((a, b) => {
|
|
284
|
+
const aPublished = Number.isNaN(Date.parse(a.publishedAt || '')) ? 0 : Date.parse(a.publishedAt || '');
|
|
285
|
+
const bPublished = Number.isNaN(Date.parse(b.publishedAt || '')) ? 0 : Date.parse(b.publishedAt || '');
|
|
286
|
+
if (bPublished !== aPublished) return bPublished - aPublished;
|
|
287
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
288
|
+
return a.position - b.position;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function renderEmptyState(message) {
|
|
293
|
+
list.innerHTML = `<div class="col-span-full rounded-xl border border-dashed border-slate-300 dark:border-slate-700 p-8 text-center text-slate-500 dark:text-slate-400">${escapeHtml(message)}</div>`;
|
|
294
|
+
pagination.innerHTML = '';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function renderResults(language, query, page, results) {
|
|
298
|
+
const total = results.length;
|
|
299
|
+
if (!query) {
|
|
300
|
+
meta.textContent = 'Start typing to search in this language.';
|
|
301
|
+
renderEmptyState('No query yet.');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (total === 0) {
|
|
306
|
+
meta.textContent = `No matches for "${query}".`;
|
|
307
|
+
renderEmptyState('Try a different keyword.');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
312
|
+
const currentPage = Math.min(Math.max(page, 1), totalPages);
|
|
313
|
+
const start = (currentPage - 1) * PAGE_SIZE;
|
|
314
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
315
|
+
const visible = results.slice(start, end);
|
|
316
|
+
|
|
317
|
+
meta.textContent = `${total} result${total === 1 ? '' : 's'} for "${query}" • Page ${currentPage} of ${totalPages}`;
|
|
318
|
+
|
|
319
|
+
list.innerHTML = visible
|
|
320
|
+
.map((article) => {
|
|
321
|
+
const href = normalizePath(article.slug || '/');
|
|
322
|
+
const publishedAt = formatPublishedAt(article.publishedAt, language);
|
|
323
|
+
return `
|
|
324
|
+
<article class="group rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden bg-white dark:bg-slate-900">
|
|
325
|
+
<a href="${escapeHtml(href)}" class="block aspect-[16/10] bg-slate-100 dark:bg-slate-800">
|
|
326
|
+
<img src="${escapeHtml(article.photoUrl)}" alt="${escapeHtml(article.title)}" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" loading="lazy" />
|
|
327
|
+
</a>
|
|
328
|
+
<div class="p-4">
|
|
329
|
+
${publishedAt ? `<p class="text-xs uppercase tracking-wider text-slate-500 dark:text-slate-400 mb-2">${escapeHtml(publishedAt)}</p>` : ''}
|
|
330
|
+
<h2 class="text-xl font-bold leading-tight text-slate-900 dark:text-slate-100 font-encode">
|
|
331
|
+
<a href="${escapeHtml(href)}" class="hover:text-[#b21100] dark:hover:text-[#ff2e1a] transition-colors">${escapeHtml(article.title)}</a>
|
|
332
|
+
</h2>
|
|
333
|
+
<p class="mt-2 text-slate-600 dark:text-slate-400 font-serif line-clamp-3">${escapeHtml(article.excerpt)}</p>
|
|
334
|
+
</div>
|
|
335
|
+
</article>
|
|
336
|
+
`;
|
|
337
|
+
})
|
|
338
|
+
.join('');
|
|
339
|
+
|
|
340
|
+
const pageLinks = [];
|
|
341
|
+
if (currentPage > 1) {
|
|
342
|
+
pageLinks.push(
|
|
343
|
+
`<a class="rounded-lg border border-slate-300 dark:border-slate-700 px-3 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors" href="${escapeHtml(toHref(language, query, currentPage - 1))}">Previous</a>`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const rangeStart = Math.max(1, currentPage - 2);
|
|
348
|
+
const rangeEnd = Math.min(totalPages, currentPage + 2);
|
|
349
|
+
for (let i = rangeStart; i <= rangeEnd; i += 1) {
|
|
350
|
+
if (i === currentPage) {
|
|
351
|
+
pageLinks.push(
|
|
352
|
+
`<span class="rounded-lg bg-[#b21100] text-white px-3 py-2 text-sm font-bold">${i}</span>`
|
|
353
|
+
);
|
|
354
|
+
} else {
|
|
355
|
+
pageLinks.push(
|
|
356
|
+
`<a class="rounded-lg border border-slate-300 dark:border-slate-700 px-3 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors" href="${escapeHtml(toHref(language, query, i))}">${i}</a>`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (currentPage < totalPages) {
|
|
362
|
+
pageLinks.push(
|
|
363
|
+
`<a class="rounded-lg border border-slate-300 dark:border-slate-700 px-3 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors" href="${escapeHtml(toHref(language, query, currentPage + 1))}">Next</a>`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
pagination.innerHTML = pageLinks.join('');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function runSearch() {
|
|
371
|
+
const language = getLanguage();
|
|
372
|
+
const state = parseQueryState();
|
|
373
|
+
input.value = state.q;
|
|
374
|
+
|
|
375
|
+
if (!state.q) {
|
|
376
|
+
renderResults(language, '', 1, []);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
meta.textContent = 'Searching...';
|
|
381
|
+
list.innerHTML = '';
|
|
382
|
+
pagination.innerHTML = '';
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
const articles = await loadIndex(language);
|
|
386
|
+
const results = searchArticles(articles, state.q);
|
|
387
|
+
renderResults(language, state.q, state.page, results);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
meta.textContent = 'Search is temporarily unavailable.';
|
|
390
|
+
renderEmptyState('We could not load the search index right now.');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
form.addEventListener('submit', (event) => {
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
const language = getLanguage();
|
|
397
|
+
const query = (input.value || '').trim();
|
|
398
|
+
if (!query) {
|
|
399
|
+
navigateToSearchUrl(getSearchBasePath(language), { replace: false });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
navigateToSearchUrl(toHref(language, query, 1), { replace: false });
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
pagination.addEventListener('click', (event) => {
|
|
406
|
+
const target = event.target;
|
|
407
|
+
if (!(target instanceof Element)) return;
|
|
408
|
+
const anchor = target.closest('a[href]');
|
|
409
|
+
if (!(anchor instanceof HTMLAnchorElement)) return;
|
|
410
|
+
event.preventDefault();
|
|
411
|
+
navigateToSearchUrl(anchor.getAttribute('href') || '/', { replace: false });
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
window.addEventListener('popstate', () => {
|
|
415
|
+
runSearch();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
runSearch();
|
|
419
|
+
})();
|
|
420
|
+
</script>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{{#*inline 'snackMetaImageRow'}}
|
|
2
|
+
{{#if category}}
|
|
3
|
+
<div class='w-full flex items-center justify-between {{#if metaTextSize}}{{metaTextSize}}{{else}}text-xs{{/if}} mb-2'>
|
|
4
|
+
<span class='text-[#b21100] dark:text-[#ff2e1a] uppercase font-bold tracking-wider'>{{category}}</span>
|
|
5
|
+
{{#if readTime}}
|
|
6
|
+
<span class='article-time text-slate-500 dark:text-slate-400'>{{readTime}}</span>
|
|
7
|
+
{{/if}}
|
|
8
|
+
</div>
|
|
9
|
+
{{else if (lookup categories 0)}}
|
|
10
|
+
{{#with (lookup categories 0)}}
|
|
11
|
+
<div class='w-full flex items-center justify-between {{#if ../metaTextSize}}{{../metaTextSize}}{{else}}text-xs{{/if}} mb-2'>
|
|
12
|
+
<span class='text-[#b21100] dark:text-[#ff2e1a] uppercase font-bold tracking-wider'>{{name}}</span>
|
|
13
|
+
{{#if ../readTime}}
|
|
14
|
+
<span class='article-time text-slate-500 dark:text-slate-400'>{{../readTime}}</span>
|
|
15
|
+
{{/if}}
|
|
16
|
+
</div>
|
|
17
|
+
{{/with}}
|
|
18
|
+
{{else if readTime}}
|
|
19
|
+
<div class='w-full flex items-center justify-end {{#if metaTextSize}}{{metaTextSize}}{{else}}text-xs{{/if}} mb-2'>
|
|
20
|
+
<span class='article-time text-slate-500 dark:text-slate-400'>{{readTime}}</span>
|
|
21
|
+
</div>
|
|
22
|
+
{{/if}}
|
|
23
|
+
{{/inline}}
|
|
24
|
+
|
|
25
|
+
{{#*inline 'snackMetaInline'}}
|
|
26
|
+
{{#if category}}
|
|
27
|
+
<div class='flex items-center {{#if metaTextSize}}{{metaTextSize}}{{else}}text-xs{{/if}} mb-2'>
|
|
28
|
+
<span class='text-[#b21100] dark:text-[#ff2e1a] uppercase font-bold tracking-wider'>{{category}}</span>
|
|
29
|
+
{{#if readTime}}
|
|
30
|
+
<span class='mx-2 text-slate-400 dark:text-slate-500'>•</span>
|
|
31
|
+
<span class='article-time text-slate-500 dark:text-slate-400'>{{readTime}}</span>
|
|
32
|
+
{{/if}}
|
|
33
|
+
</div>
|
|
34
|
+
{{else if (lookup categories 0)}}
|
|
35
|
+
{{#with (lookup categories 0)}}
|
|
36
|
+
<div class='flex items-center {{#if ../metaTextSize}}{{../metaTextSize}}{{else}}text-xs{{/if}} mb-2'>
|
|
37
|
+
<span class='text-[#b21100] dark:text-[#ff2e1a] uppercase font-bold tracking-wider'>{{name}}</span>
|
|
38
|
+
{{#if ../readTime}}
|
|
39
|
+
<span class='mx-2 text-slate-400 dark:text-slate-500'>•</span>
|
|
40
|
+
<span class='article-time text-slate-500 dark:text-slate-400'>{{../readTime}}</span>
|
|
41
|
+
{{/if}}
|
|
42
|
+
</div>
|
|
43
|
+
{{/with}}
|
|
44
|
+
{{else if readTime}}
|
|
45
|
+
<div class='flex items-center {{#if metaTextSize}}{{metaTextSize}}{{else}}text-xs{{/if}} mb-2'>
|
|
46
|
+
<span class='article-time text-slate-500 dark:text-slate-400'>{{readTime}}</span>
|
|
47
|
+
</div>
|
|
48
|
+
{{/if}}
|
|
49
|
+
{{/inline}}
|
|
50
|
+
|
|
51
|
+
<article class='{{#if className}}flex flex-col h-full {{className}} items-stretch{{else}}flex flex-col h-full py-4 border-t border-slate-100 dark:border-slate-800 items-stretch{{/if}}'>
|
|
52
|
+
{{#unless (eq showImage false)}}
|
|
53
|
+
{{#if image}}
|
|
54
|
+
{{> snackMetaImageRow}}
|
|
55
|
+
{{else if featuredImage.url}}
|
|
56
|
+
{{> snackMetaImageRow}}
|
|
57
|
+
{{/if}}
|
|
58
|
+
{{/unless}}
|
|
59
|
+
|
|
60
|
+
<div class='{{#if (eq showImage false)}}flex flex-col h-full{{else if image}}flex items-start gap-4{{else if featuredImage.url}}flex items-start gap-4{{else}}flex flex-col h-full{{/if}}'>
|
|
61
|
+
<div class='flex-1'>
|
|
62
|
+
{{#if (eq showImage false)}}
|
|
63
|
+
{{> snackMetaInline}}
|
|
64
|
+
{{else if image}}
|
|
65
|
+
{{else if featuredImage.url}}
|
|
66
|
+
{{else}}
|
|
67
|
+
{{> snackMetaInline}}
|
|
68
|
+
{{/if}}
|
|
69
|
+
|
|
70
|
+
<h3 class='{{#if titleFontWeight}}{{titleFontWeight}}{{else}}font-bold{{/if}} {{#if titleTextSize}}{{titleTextSize}}{{else}}text-lg{{/if}} leading-snug text-slate-900 dark:text-slate-100 {{#if titleClasses}}{{titleClasses}}{{else}}font-encode{{/if}}'>
|
|
71
|
+
<a href='{{url}}' class='hover:text-[#b21100] dark:hover:text-[#ff2e1a] transition-colors'>{{title}}</a>
|
|
72
|
+
</h3>
|
|
73
|
+
{{#unless (eq showExcerpt false)}}
|
|
74
|
+
{{#if excerpt}}
|
|
75
|
+
<p class='text-slate-500 dark:text-slate-400 {{#if excerptTextSize}}{{excerptTextSize}}{{else}}text-lg{{/if}} {{#if excerptClasses}}{{excerptClasses}}{{else}}font-serif leading-relaxed line-clamp-3{{/if}}'>{{excerpt}}</p>
|
|
76
|
+
{{/if}}
|
|
77
|
+
{{/unless}}
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{{#unless (eq showImage false)}}
|
|
81
|
+
{{#if image}}
|
|
82
|
+
<a href='{{url}}' class='{{#if imageSize}}{{imageSize}}{{else}}w-32 h-24{{/if}} flex-shrink-0 block'>
|
|
83
|
+
<img src='{{image}}' alt='{{alt}}' class='w-full h-full object-cover rounded {{#if imageClasses}}{{imageClasses}}{{/if}}' />
|
|
84
|
+
</a>
|
|
85
|
+
{{else if featuredImage.url}}
|
|
86
|
+
<a href='{{url}}' class='{{#if imageSize}}{{imageSize}}{{else}}w-32 h-24{{/if}} flex-shrink-0 block'>
|
|
87
|
+
<img src='{{featuredImage.url}}' alt='{{featuredImage.alt}}' class='w-full h-full object-cover rounded {{#if imageClasses}}{{imageClasses}}{{/if}}' />
|
|
88
|
+
</a>
|
|
89
|
+
{{/if}}
|
|
90
|
+
{{/unless}}
|
|
91
|
+
</div>
|
|
92
|
+
</article>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<section class='spotlight-hero relative w-full min-h-[800px] md:min-h-[900px] bg-[#0C0C0C] overflow-hidden' data-carousel='spotlight'>
|
|
2
|
+
<div class='carousel-background absolute inset-0 w-full h-full'>
|
|
3
|
+
{{#if slides}}
|
|
4
|
+
{{#each slides}}
|
|
5
|
+
<div class='carousel-slide {{#if @first}}active opacity-100{{else}}hidden opacity-0{{/if}} absolute inset-0 bg-cover bg-center transition-opacity duration-700' style='background-image: radial-gradient(71.75% 69.5% at 50% 47.39%, rgba(0, 0, 0, 0) 0%, #000000 100%), url({{image}});'></div>
|
|
6
|
+
{{/each}}
|
|
7
|
+
{{else if hero.url}}
|
|
8
|
+
<div class='carousel-slide active opacity-100 absolute inset-0 bg-cover bg-center transition-opacity duration-700' style='background-image: radial-gradient(71.75% 69.5% at 50% 47.39%, rgba(0, 0, 0, 0) 0%, #000000 100%), url({{hero.url}});'></div>
|
|
9
|
+
{{else}}
|
|
10
|
+
<div class='carousel-slide active opacity-100 absolute inset-0 bg-cover bg-center transition-opacity duration-700' style='background-image: radial-gradient(71.75% 69.5% at 50% 47.39%, rgba(0, 0, 0, 0) 0%, #000000 100%), url(https://images.unsplash.com/photo-1586348943529-beaae6c28db9?w=1440&h=882&fit=crop);'></div>
|
|
11
|
+
{{/if}}
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class='absolute bottom-0 left-0 right-0 h-[200px] pointer-events-none' style='background: linear-gradient(180deg, rgba(3, 7, 18, 0) 0%, var(--color-slate-950) 46.88%);'></div>
|
|
15
|
+
|
|
16
|
+
<div class='container mx-auto relative py-12 md:py-20 px-4 flex flex-col justify-end min-h-[800px] md:min-h-[900px]'>
|
|
17
|
+
<div class='absolute right-0 top-20 flex items-center gap-4'>
|
|
18
|
+
{{#if slides}}
|
|
19
|
+
{{#each slides}}
|
|
20
|
+
<button class='carousel-dot w-3 h-3 rounded-full {{#if @first}}active{{/if}}' data-slide='{{@index}}'></button>
|
|
21
|
+
{{/each}}
|
|
22
|
+
{{else}}
|
|
23
|
+
<button class='carousel-dot w-3 h-3 rounded-full active' data-slide='0'></button>
|
|
24
|
+
{{/if}}
|
|
25
|
+
<button class='carousel-next w-10 h-10 flex items-center justify-center'>
|
|
26
|
+
<svg width='15' height='27' viewBox='0 0 15 27' fill='none'>
|
|
27
|
+
<path d='M1.5 1.5L13.5 13.5L1.5 25.5' stroke='#FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'></path>
|
|
28
|
+
</svg>
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class='w-full max-w-[592px] border-l border-white pl-4 md:pl-10 flex flex-col gap-4 md:gap-6 mb-8 md:mb-12'>
|
|
33
|
+
<h1 class='text-3xl md:text-[44px] leading-tight md:leading-[56px] font-normal text-white font-sans'>
|
|
34
|
+
<a href='{{articleUrl slug=slug categories=categories language=@root.language}}' class='hover:text-slate-300 transition-colors'>{{title}}</a>
|
|
35
|
+
</h1>
|
|
36
|
+
<p class='text-xl md:text-2xl leading-7 md:leading-8 text-white self-stretch font-serif'>{{excerpt}}</p>
|
|
37
|
+
<a href='{{articleUrl slug=slug categories=categories language=@root.language}}' class='border border-[#717273] rounded-md px-4 py-[11px] h-11 flex items-center justify-center whitespace-nowrap self-start hover:bg-white/10 transition-colors'>
|
|
38
|
+
<span class='text-white text-base font-medium leading-[22px] font-sans'>Read More</span>
|
|
39
|
+
</a>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class='w-full flex flex-col gap-6'>
|
|
43
|
+
<div class='grid grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6'>
|
|
44
|
+
{{#each (slice related 0 4)}}
|
|
45
|
+
{{> components/headline
|
|
46
|
+
url=url
|
|
47
|
+
title=title
|
|
48
|
+
time=(formatDate (coalesce updatedAt publishedAt))
|
|
49
|
+
timestamp=(coalesce updatedAt publishedAt)
|
|
50
|
+
image=image
|
|
51
|
+
featuredImage=featuredImage
|
|
52
|
+
titleClass='text-sm lg:text-base leading-tight lg:leading-[22px] font-medium text-[#F9FAFB] font-sans'
|
|
53
|
+
imageFallbackBgClass='bg-slate-700'
|
|
54
|
+
}}
|
|
55
|
+
{{/each}}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class='container mx-auto px-4 flex justify-around'>
|
|
2
|
+
<section class='inline-flex mx-auto my-6 gap-15 overflow-x-auto whitespace-nowrap scrollbar-thin scrollbar-thumb-slate-300 scrollbar-track-transparent'>
|
|
3
|
+
<article class='gap-1 flex items-center flex-none'>
|
|
4
|
+
<div class='flex flex-row items-center'>
|
|
5
|
+
<span class='h-2 w-2 rounded-full bg-[#b21100] mr-2'></span>
|
|
6
|
+
<span class='text-[#b21100] font-medium mr-1'>En vivo</span>
|
|
7
|
+
</div>
|
|
8
|
+
<span class='text-[#121212]'>Clásico Universitario</span>
|
|
9
|
+
</article>
|
|
10
|
+
<article class='gap-2 flex font-normal items-center flex-none'>Trending Up: Apagón en España</article>
|
|
11
|
+
<article class='gap-2 flex font-normal items-center flex-none'>Trending Up: Copa Libertadores</article>
|
|
12
|
+
<article class='gap-2 flex font-normal items-center flex-none'>Trending Up: Terremoto en Magallanes</article>
|
|
13
|
+
</section>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div class='border border-slate-200 dark:border-slate-800 {{#if className}}{{className}}{{/if}}'>
|
|
2
|
+
{{#each items}}
|
|
3
|
+
<details class='group border-b border-slate-200 dark:border-slate-800 last:border-b-0' {{#if open}}open{{/if}}>
|
|
4
|
+
<summary class='flex cursor-pointer list-none items-start justify-between gap-6 px-6 py-5 marker:hidden [&::-webkit-details-marker]:hidden hover:bg-slate-50 dark:hover:bg-slate-900/50'>
|
|
5
|
+
<span class='flex min-w-0 flex-col'>
|
|
6
|
+
<span class='text-base font-semibold text-slate-900 dark:text-slate-100'>{{title}}</span>
|
|
7
|
+
{{#if subtitle}}
|
|
8
|
+
<span class='mt-1.5 text-sm text-slate-500 dark:text-slate-400'>{{subtitle}}</span>
|
|
9
|
+
{{/if}}
|
|
10
|
+
</span>
|
|
11
|
+
<span class='mt-1 shrink-0 text-slate-400 transition-transform duration-200 group-open:rotate-180 group-open:text-[#b21100] dark:group-open:text-[#ff2e1a]' aria-hidden='true'>
|
|
12
|
+
<svg viewBox='0 0 20 20' fill='currentColor' class='h-5 w-5'>
|
|
13
|
+
<path fill-rule='evenodd' d='M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.17l3.71-3.94a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z' clip-rule='evenodd'></path>
|
|
14
|
+
</svg>
|
|
15
|
+
</span>
|
|
16
|
+
</summary>
|
|
17
|
+
<div class='grid grid-rows-[0fr] transition-[grid-template-rows] duration-200 group-open:grid-rows-[1fr]'>
|
|
18
|
+
<div class='overflow-hidden'>
|
|
19
|
+
<div class='border-l-2 border-[#b21100] dark:border-[#ff2e1a] mx-6 mb-6 mt-1 pl-5 pb-1 text-sm leading-relaxed text-slate-700 dark:text-slate-300'>
|
|
20
|
+
{{#if html}}
|
|
21
|
+
{{{html}}}
|
|
22
|
+
{{else}}
|
|
23
|
+
{{{content}}}
|
|
24
|
+
{{/if}}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</details>
|
|
29
|
+
{{/each}}
|
|
30
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<nav aria-label='Breadcrumb' class='{{#if className}}{{className}}{{/if}}'>
|
|
2
|
+
<ol class='inline-flex flex-wrap items-center text-sm py-1'>
|
|
3
|
+
{{#each items}}
|
|
4
|
+
<li class='inline-flex items-center'>
|
|
5
|
+
{{#if @last}}
|
|
6
|
+
<span aria-current='page' class='font-semibold text-slate-900 dark:text-slate-100 px-1.5'>{{label}}</span>
|
|
7
|
+
{{else}}
|
|
8
|
+
<a href='{{coalesce url href}}' class='px-1.5 text-slate-500 transition-colors hover:text-[#b21100] dark:text-slate-400 dark:hover:text-[#ff2e1a]'>
|
|
9
|
+
{{label}}
|
|
10
|
+
</a>
|
|
11
|
+
<span class='text-slate-300 dark:text-slate-600 select-none font-light' aria-hidden='true'>/</span>
|
|
12
|
+
{{/if}}
|
|
13
|
+
</li>
|
|
14
|
+
{{/each}}
|
|
15
|
+
</ol>
|
|
16
|
+
</nav>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{{#if href}}
|
|
2
|
+
<a
|
|
3
|
+
href='{{href}}'
|
|
4
|
+
class='inline-flex items-center justify-center rounded-full w-9 h-9 border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 transition-colors hover:border-[#b21100] hover:text-[#b21100] dark:hover:border-[#ff2e1a] dark:hover:text-[#ff2e1a] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#b21100] {{#if className}}{{className}}{{/if}}'
|
|
5
|
+
{{#if title}}title='{{title}}'{{/if}}
|
|
6
|
+
{{#if ariaLabel}}aria-label='{{ariaLabel}}'{{/if}}
|
|
7
|
+
>
|
|
8
|
+
{{{icon}}}
|
|
9
|
+
</a>
|
|
10
|
+
{{else}}
|
|
11
|
+
<button
|
|
12
|
+
type='{{coalesce type "button"}}'
|
|
13
|
+
class='inline-flex items-center justify-center rounded-full w-9 h-9 border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 transition-colors hover:border-[#b21100] hover:text-[#b21100] dark:hover:border-[#ff2e1a] dark:hover:text-[#ff2e1a] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#b21100] {{#if className}}{{className}}{{/if}}'
|
|
14
|
+
{{#if title}}title='{{title}}'{{/if}}
|
|
15
|
+
{{#if ariaLabel}}aria-label='{{ariaLabel}}'{{/if}}
|
|
16
|
+
>
|
|
17
|
+
{{{icon}}}
|
|
18
|
+
</button>
|
|
19
|
+
{{/if}}
|