@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,27 @@
1
+ <div class='inline-flex items-center justify-center gap-2 {{#if className}}{{className}}{{/if}}' role='status' aria-live='polite'>
2
+ {{#if (eq size "sm")}}
3
+ <svg viewBox='0 0 24 24' class='h-4 w-4 text-[#b21100] dark:text-[#ff2e1a]' fill='none' aria-hidden='true'>
4
+ <circle cx='12' cy='12' r='9' stroke='currentColor' stroke-width='2' opacity='0.15'/>
5
+ <circle cx='12' cy='12' r='9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-dasharray='16 40' fill='none'>
6
+ <animateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='0.75s' repeatCount='indefinite'/>
7
+ </circle>
8
+ </svg>
9
+ {{else}}
10
+ {{#if (eq size "lg")}}
11
+ <svg viewBox='0 0 24 24' class='h-12 w-12 text-[#b21100] dark:text-[#ff2e1a]' fill='none' aria-hidden='true'>
12
+ <circle cx='12' cy='12' r='9' stroke='currentColor' stroke-width='2' opacity='0.15'/>
13
+ <circle cx='12' cy='12' r='9' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-dasharray='20 36' fill='none'>
14
+ <animateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='0.75s' repeatCount='indefinite'/>
15
+ </circle>
16
+ </svg>
17
+ {{else}}
18
+ <svg viewBox='0 0 24 24' class='h-8 w-8 text-[#b21100] dark:text-[#ff2e1a]' fill='none' aria-hidden='true'>
19
+ <circle cx='12' cy='12' r='9' stroke='currentColor' stroke-width='2' opacity='0.15'/>
20
+ <circle cx='12' cy='12' r='9' stroke='currentColor' stroke-width='2.25' stroke-linecap='round' stroke-dasharray='18 38' fill='none'>
21
+ <animateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='0.75s' repeatCount='indefinite'/>
22
+ </circle>
23
+ </svg>
24
+ {{/if}}
25
+ {{/if}}
26
+ <span class='sr-only'>{{coalesce label "Loading"}}</span>
27
+ </div>
@@ -0,0 +1,56 @@
1
+ <nav aria-label='Pagination' class='flex items-center justify-center gap-6 {{#if className}}{{className}}{{/if}}'>
2
+ {{#if prev.url}}
3
+ <a
4
+ href='{{prev.url}}'
5
+ class='inline-flex items-center gap-1 text-sm font-medium text-slate-500 dark:text-slate-400 transition-colors hover:text-[#b21100] dark:hover:text-[#ff2e1a]'
6
+ aria-label='Previous page'
7
+ >
8
+ <svg viewBox='0 0 20 20' fill='currentColor' class='h-4 w-4' aria-hidden='true'><path fill-rule='evenodd' d='M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z' clip-rule='evenodd'/></svg>
9
+ {{coalesce prev.label "Prev"}}
10
+ </a>
11
+ {{else}}
12
+ <span class='inline-flex items-center gap-1 text-sm font-medium text-slate-300 dark:text-slate-700' aria-disabled='true'>
13
+ <svg viewBox='0 0 20 20' fill='currentColor' class='h-4 w-4' aria-hidden='true'><path fill-rule='evenodd' d='M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z' clip-rule='evenodd'/></svg>
14
+ {{coalesce prev.label "Prev"}}
15
+ </span>
16
+ {{/if}}
17
+
18
+ <ol class='flex items-center gap-0.5'>
19
+ {{#each pages}}
20
+ <li>
21
+ {{#if ellipsis}}
22
+ <span class='inline-flex h-9 w-9 items-center justify-center text-sm text-slate-400 dark:text-slate-500'>…</span>
23
+ {{else}}
24
+ {{#if current}}
25
+ <span aria-current='page' class='inline-flex h-9 w-9 items-center justify-center text-sm font-bold text-[#b21100] dark:text-[#ff2e1a] border-b-2 border-[#b21100] dark:border-[#ff2e1a]'>
26
+ {{label}}
27
+ </span>
28
+ {{else}}
29
+ <a
30
+ href='{{url}}'
31
+ class='inline-flex h-9 w-9 items-center justify-center text-sm font-medium text-slate-600 dark:text-slate-300 transition-colors hover:text-[#b21100] dark:hover:text-[#ff2e1a]'
32
+ >
33
+ {{label}}
34
+ </a>
35
+ {{/if}}
36
+ {{/if}}
37
+ </li>
38
+ {{/each}}
39
+ </ol>
40
+
41
+ {{#if next.url}}
42
+ <a
43
+ href='{{next.url}}'
44
+ class='inline-flex items-center gap-1 text-sm font-medium text-slate-500 dark:text-slate-400 transition-colors hover:text-[#b21100] dark:hover:text-[#ff2e1a]'
45
+ aria-label='Next page'
46
+ >
47
+ {{coalesce next.label "Next"}}
48
+ <svg viewBox='0 0 20 20' fill='currentColor' class='h-4 w-4' aria-hidden='true'><path fill-rule='evenodd' d='M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 1 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z' clip-rule='evenodd'/></svg>
49
+ </a>
50
+ {{else}}
51
+ <span class='inline-flex items-center gap-1 text-sm font-medium text-slate-300 dark:text-slate-700' aria-disabled='true'>
52
+ {{coalesce next.label "Next"}}
53
+ <svg viewBox='0 0 20 20' fill='currentColor' class='h-4 w-4' aria-hidden='true'><path fill-rule='evenodd' d='M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 1 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z' clip-rule='evenodd'/></svg>
54
+ </span>
55
+ {{/if}}
56
+ </nav>
@@ -0,0 +1,12 @@
1
+ <div
2
+ class='{{#if (eq orientation "both")}}overflow-auto{{else}}{{#if (eq orientation "horizontal")}}overflow-x-auto overflow-y-hidden{{else}}overflow-y-auto overflow-x-hidden{{/if}}{{/if}} [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-slate-300 [&::-webkit-scrollbar-thumb:hover]:bg-slate-400 dark:[&::-webkit-scrollbar-thumb]:bg-slate-600 dark:[&::-webkit-scrollbar-thumb:hover]:bg-slate-500 {{#if className}}{{className}}{{/if}}'
3
+ {{#if maxHeight}}style='max-height: {{maxHeight}};'{{/if}}
4
+ >
5
+ {{#if content}}
6
+ {{{content}}}
7
+ {{else}}
8
+ {{#if @partial-block}}
9
+ {{> @partial-block}}
10
+ {{/if}}
11
+ {{/if}}
12
+ </div>
@@ -0,0 +1,21 @@
1
+ <span class='inline-flex items-baseline gap-3 {{#if className}}{{className}}{{/if}}'>
2
+ <span class='inline-flex items-center gap-1.5 border-l-2 pl-2 pr-3 py-0.5 text-xs font-semibold uppercase tracking-wider leading-5
3
+ {{#if (eq variant "live")}}border-emerald-500 bg-emerald-50 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-300
4
+ {{else}}{{#if (eq variant "offline")}}border-slate-400 bg-slate-100 text-slate-600 dark:bg-slate-800/60 dark:text-slate-400
5
+ {{else}}{{#if (eq variant "warning")}}border-amber-500 bg-amber-50 text-amber-700 dark:bg-amber-950/30 dark:text-amber-400
6
+ {{else}}{{#if (eq variant "info")}}border-sky-500 bg-sky-50 text-sky-700 dark:bg-sky-950/30 dark:text-sky-400
7
+ {{else}}border-slate-400 bg-slate-100 text-slate-600 dark:bg-slate-800/60 dark:text-slate-400
8
+ {{/if}}{{/if}}{{/if}}{{/if}}'>
9
+ <span class='h-1.5 w-1.5 rounded-full flex-shrink-0
10
+ {{#if (eq variant "live")}}bg-emerald-500 animate-pulse
11
+ {{else}}{{#if (eq variant "offline")}}bg-slate-400
12
+ {{else}}{{#if (eq variant "warning")}}bg-amber-500
13
+ {{else}}{{#if (eq variant "info")}}bg-sky-500
14
+ {{else}}bg-slate-500
15
+ {{/if}}{{/if}}{{/if}}{{/if}}'></span>
16
+ <span>{{label}}</span>
17
+ </span>
18
+ {{#if description}}
19
+ <span class='text-xs leading-5 text-slate-500 dark:text-slate-400'>{{description}}</span>
20
+ {{/if}}
21
+ </span>
@@ -0,0 +1,79 @@
1
+ <footer
2
+ class='sticky top-[100vh] bg-slate-50 dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 mt-8 transition-colors'
3
+ style='view-transition-name: site-footer;'
4
+ >
5
+ <div class='container mx-auto px-4 py-6'>
6
+ <div class='flex flex-col md:grid md:grid-cols-3 items-center gap-4'>
7
+ <div class='flex items-center justify-center md:justify-start gap-3'>
8
+ <a
9
+ href='https://bsky.app/profile/fifthbell.com'
10
+ target='_blank'
11
+ rel='noopener noreferrer'
12
+ class='text-slate-600 dark:text-slate-400 hover:text-[#0085ff] dark:hover:text-[#0085ff] transition-colors'
13
+ aria-label='Follow us on Bluesky'
14
+ >
15
+ <svg class='w-5 h-5' viewBox='0 0 600 530' fill='currentColor'>
16
+ <path
17
+ d='M135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-.0174-2.9357-1.1937.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z'
18
+ ></path>
19
+ </svg>
20
+ </a>
21
+ <a
22
+ href='https://x.com/thisisfifthbell'
23
+ target='_blank'
24
+ rel='noopener noreferrer'
25
+ class='text-slate-600 dark:text-slate-400 hover:text-[#000000] dark:hover:text-[#ffffff] transition-colors'
26
+ aria-label='Follow us on X (Twitter)'
27
+ >
28
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor'>
29
+ <path
30
+ d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'
31
+ ></path>
32
+ </svg>
33
+ </a>
34
+ <a
35
+ href='https://instagram.com/thisisfifthbell'
36
+ target='_blank'
37
+ rel='noopener noreferrer'
38
+ class='text-slate-600 dark:text-slate-400 hover:text-[#E4405F] dark:hover:text-[#E4405F] transition-colors'
39
+ aria-label='Follow us on Instagram'
40
+ >
41
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor'>
42
+ <path
43
+ d='M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z'
44
+ ></path>
45
+ </svg>
46
+ </a>
47
+ <a
48
+ href='https://www.youtube.com/@thisisfifthbell'
49
+ target='_blank'
50
+ rel='noopener noreferrer'
51
+ class='text-slate-600 dark:text-slate-400 hover:text-[#FF0000] dark:hover:text-[#FF0000] transition-colors'
52
+ aria-label='Subscribe on YouTube'
53
+ >
54
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor'>
55
+ <path
56
+ d='M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'
57
+ ></path>
58
+ </svg>
59
+ </a>
60
+ </div>
61
+
62
+ <div class='flex items-center justify-center md:space-x-3'>
63
+ <div class='bg-slate-600 dark:bg-slate-700 text-slate-50 dark:text-slate-300 p-2 transition-colors'>
64
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>
65
+ <path d='M10.268 21a2 2 0 0 0 3.464 0'></path>
66
+ <path d='M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326'></path>
67
+ <path d='M4 2C2.8 3.7 2 5.7 2 8'></path>
68
+ <path d='M22 8a10 10 0 0 0-2-6'></path>
69
+ </svg>
70
+ </div>
71
+ <span class='hidden md:inline text-slate-600 dark:text-slate-400 transition-colors'>fifth<span class='font-semibold'>bell</span></span>
72
+ </div>
73
+
74
+ <div class='flex items-center justify-center md:justify-end text-sm text-slate-600 dark:text-slate-400 transition-colors'>
75
+ <span>© 2026 fifth<b>bell</b>. All rights reserved.</span>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </footer>
@@ -0,0 +1,5 @@
1
+ <footer class='sticky top-[100vh] bg-slate-50 dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 mt-8 transition-colors' style='view-transition-name: site-footer;'>
2
+ <div class='container mx-auto px-4 py-6 text-center text-sm text-slate-600 dark:text-slate-400'>
3
+ <span>© 2026 fifth<b>bell</b>. All rights reserved.</span>
4
+ </div>
5
+ </footer>
@@ -0,0 +1,397 @@
1
+ <header
2
+ class='border-b border-slate-200 dark:border-slate-700 relative z-[60] bg-white dark:bg-slate-900 transition-colors'
3
+ style='view-transition-name: site-header;'
4
+ >
5
+ <div class='container mx-auto px-4'>
6
+ <nav class='flex items-center justify-between h-16'>
7
+ <div class='flex items-center space-x-8'>
8
+ <div class='relative'>
9
+ <button
10
+ id='menu-button'
11
+ class='flex items-center space-x-2 p-2 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-black dark:text-white'
12
+ aria-label='Menu'
13
+ aria-expanded='false'
14
+ type='button'
15
+ >
16
+ <svg class='w-4 h-4' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
17
+ <line x1='3' y1='6' x2='21' y2='6'></line>
18
+ <line x1='3' y1='12' x2='21' y2='12'></line>
19
+ <line x1='3' y1='18' x2='21' y2='18'></line>
20
+ </svg>
21
+ </button>
22
+
23
+ <div
24
+ id='dropdown-menu'
25
+ class='fixed top-16 left-0 right-0 bottom-0 bg-white/98 dark:bg-slate-900/98 backdrop-blur-md shadow-lg z-50 pointer-events-none border-t border-slate-200 dark:border-slate-700 overflow-hidden transition-colors'
26
+ style='opacity: 0; transform: translateX(-100%); transition: transform 600ms ease-out, opacity 300ms ease-out 300ms;'
27
+ >
28
+ <div class='container mx-auto px-4 py-4 md:py-8 h-full overflow-y-auto overscroll-contain'>
29
+ <div class='grid grid-cols-1 lg:grid-cols-5 gap-6 lg:gap-8'>
30
+ <div class='lg:col-span-1'>
31
+ <h3
32
+ class='text-xs font-bold text-slate-400 dark:text-slate-600 uppercase tracking-wider mb-4 border-b border-slate-200 dark:border-slate-800 pb-2'
33
+ >SECTIONS</h3>
34
+ <div id='sections-links' class='space-y-3'>
35
+ <a
36
+ href='{{logoLink}}'
37
+ class='block text-lg font-bold text-black dark:text-white hover:text-[#b21100] dark:hover:text-[#ff2e1a] transition-colors'
38
+ >TOP STORIES</a>
39
+ <div id='category-links-container' class='space-y-3'>
40
+ <p class='text-sm text-slate-400 dark:text-slate-500'>Loading sections...</p>
41
+ </div>
42
+ </div>
43
+
44
+ <div class='mt-8 pt-6 border-t border-slate-200 dark:border-slate-800'>
45
+ <h3 class='text-xs font-bold text-slate-400 dark:text-slate-600 uppercase tracking-wider mb-4'>FOLLOW US</h3>
46
+ <div class='flex items-center gap-3'>
47
+ <a
48
+ href='https://bsky.app/profile/fifthbell.com'
49
+ target='_blank'
50
+ rel='noopener noreferrer'
51
+ class='text-slate-600 dark:text-slate-400 hover:text-[#0085ff] dark:hover:text-[#0085ff] transition-colors'
52
+ aria-label='Follow us on Bluesky'
53
+ >
54
+ <svg class='w-5 h-5' viewBox='0 0 600 530' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
55
+ <path
56
+ d='M135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-.0174-2.9357-1.1937.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z'
57
+ ></path>
58
+ </svg>
59
+ </a>
60
+ <a
61
+ href='https://x.com/thisisfifthbell'
62
+ target='_blank'
63
+ rel='noopener noreferrer'
64
+ class='text-slate-600 dark:text-slate-400 hover:text-[#000000] dark:hover:text-[#ffffff] transition-colors'
65
+ aria-label='Follow us on X (Twitter)'
66
+ >
67
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
68
+ <path
69
+ d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'
70
+ ></path>
71
+ </svg>
72
+ </a>
73
+ <a
74
+ href='https://instagram.com/thisisfifthbell'
75
+ target='_blank'
76
+ rel='noopener noreferrer'
77
+ class='text-slate-600 dark:text-slate-400 hover:text-[#E4405F] dark:hover:text-[#E4405F] transition-colors'
78
+ aria-label='Follow us on Instagram'
79
+ >
80
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
81
+ <path
82
+ d='M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z'
83
+ ></path>
84
+ </svg>
85
+ </a>
86
+ <a
87
+ href='https://www.youtube.com/@thisisfifthbell'
88
+ target='_blank'
89
+ rel='noopener noreferrer'
90
+ class='text-slate-600 dark:text-slate-400 hover:text-[#FF0000] dark:hover:text-[#FF0000] transition-colors'
91
+ aria-label='Subscribe on YouTube'
92
+ >
93
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
94
+ <path
95
+ d='M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'
96
+ ></path>
97
+ </svg>
98
+ </a>
99
+ </div>
100
+ </div>
101
+
102
+ <div class='mt-8 pt-6 border-t border-slate-200 dark:border-slate-800'>
103
+ <h3 class='text-xs font-bold text-slate-400 dark:text-slate-600 uppercase tracking-wider mb-4'>THEME</h3>
104
+ <div class='flex items-center gap-2'>
105
+ <button
106
+ id='theme-light'
107
+ class='theme-btn flex items-center justify-center p-2 rounded hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-300'
108
+ aria-label='Light mode'
109
+ data-theme='light'
110
+ type='button'
111
+ >
112
+ <svg
113
+ class='w-5 h-5'
114
+ viewBox='0 0 24 24'
115
+ fill='none'
116
+ stroke='currentColor'
117
+ stroke-width='2'
118
+ stroke-linecap='round'
119
+ stroke-linejoin='round'
120
+ >
121
+ <circle cx='12' cy='12' r='4'></circle>
122
+ <path d='M12 2v2'></path>
123
+ <path d='M12 20v2'></path>
124
+ <path d='m4.93 4.93 1.41 1.41'></path>
125
+ <path d='m17.66 17.66 1.41 1.41'></path>
126
+ <path d='M2 12h2'></path>
127
+ <path d='M20 12h2'></path>
128
+ <path d='m6.34 17.66-1.41 1.41'></path>
129
+ <path d='m19.07 4.93-1.41 1.41'></path>
130
+ </svg>
131
+ </button>
132
+ <button
133
+ id='theme-dark'
134
+ class='theme-btn flex items-center justify-center p-2 rounded hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-300'
135
+ aria-label='Dark mode'
136
+ data-theme='dark'
137
+ type='button'
138
+ >
139
+ <svg
140
+ class='w-5 h-5'
141
+ viewBox='0 0 24 24'
142
+ fill='none'
143
+ stroke='currentColor'
144
+ stroke-width='2'
145
+ stroke-linecap='round'
146
+ stroke-linejoin='round'
147
+ >
148
+ <path d='M12 3a6 6 0 1 0 9 9 9 9 0 1 1-9-9z'></path>
149
+ </svg>
150
+ </button>
151
+ <button
152
+ id='theme-system'
153
+ class='theme-btn flex items-center justify-center p-2 rounded hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-300'
154
+ aria-label='System theme'
155
+ data-theme='system'
156
+ type='button'
157
+ >
158
+ <svg
159
+ class='w-5 h-5'
160
+ viewBox='0 0 24 24'
161
+ fill='none'
162
+ stroke='currentColor'
163
+ stroke-width='2'
164
+ stroke-linecap='round'
165
+ stroke-linejoin='round'
166
+ >
167
+ <rect x='2' y='3' width='20' height='14' rx='2' ry='2'></rect>
168
+ <line x1='8' y1='21' x2='16' y2='21'></line>
169
+ <line x1='12' y1='17' x2='12' y2='21'></line>
170
+ </svg>
171
+ </button>
172
+ </div>
173
+ </div>
174
+
175
+ <div class='mt-8 pt-6 border-t border-slate-200 dark:border-slate-800'>
176
+ <h3 class='text-xs font-bold text-slate-400 dark:text-slate-600 uppercase tracking-wider mb-4'>LANGUAGE</h3>
177
+ <div class='flex items-center gap-2'>
178
+ <button
179
+ class='lang-btn flex items-center justify-center px-3 py-2 rounded hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-300 font-medium text-sm'
180
+ aria-label='English'
181
+ data-lang='en'
182
+ type='button'
183
+ >
184
+ EN
185
+ </button>
186
+ <button
187
+ class='lang-btn flex items-center justify-center px-3 py-2 rounded hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-300 font-medium text-sm'
188
+ aria-label='Español'
189
+ data-lang='es'
190
+ type='button'
191
+ >
192
+ ES
193
+ </button>
194
+ <button
195
+ class='lang-btn flex items-center justify-center px-3 py-2 rounded hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-300 font-medium text-sm'
196
+ aria-label='Italiano'
197
+ data-lang='it'
198
+ type='button'
199
+ >
200
+ IT
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <div id='mini-featured' class='lg:col-span-4 hidden lg:block'>
207
+ <div class='flex flex-col items-center justify-center py-20'>
208
+ <div class='animate-spin rounded-full h-12 w-12 border-b-2 border-slate-300 dark:border-slate-600'></div>
209
+ <p class='text-slate-400 dark:text-slate-500 mt-4'>Loading stories...</p>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+ </div>
217
+
218
+ <div class='absolute left-1/2 transform -translate-x-1/2 bg-[#b21100] text-white p-3'>
219
+ <a href='{{logoLink}}' aria-label='fifthbell - Home'>
220
+ <svg class='w-7 h-7' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>
221
+ <path d='M10.268 21a2 2 0 0 0 3.464 0'></path>
222
+ <path d='M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326'></path>
223
+ <path d='M4 2C2.8 3.7 2 5.7 2 8'></path>
224
+ <path d='M22 8a10 10 0 0 0-2-6'></path>
225
+ </svg>
226
+ </a>
227
+ </div>
228
+
229
+ <div class='flex items-center space-x-4'>
230
+ <a
231
+ href='{{#if (eq language "en")}}/search{{else}}/{{language}}/search{{/if}}'
232
+ class='flex items-center justify-center p-2 border border-slate-300 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-slate-700 dark:text-slate-200'
233
+ aria-label='Search'
234
+ >
235
+ <svg class='w-4 h-4' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
236
+ <circle cx='11' cy='11' r='8'></circle>
237
+ <line x1='21' y1='21' x2='16.65' y2='16.65'></line>
238
+ </svg>
239
+ </a>
240
+ <form
241
+ class='hidden md:flex items-center'
242
+ role='search'
243
+ action='{{#if (eq language "en")}}/search{{else}}/{{language}}/search{{/if}}'
244
+ method='get'
245
+ >
246
+ <label for='header-search-input' class='sr-only'>Search</label>
247
+ <input
248
+ id='header-search-input'
249
+ name='q'
250
+ type='search'
251
+ autocomplete='off'
252
+ spellcheck='false'
253
+ placeholder='Search'
254
+ class='w-44 lg:w-56 px-3 py-2 border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900 text-sm 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'
255
+ />
256
+ </form>
257
+ <a
258
+ href='https://www.youtube.com/watch?v=3Zq7RR-9Asc'
259
+ target='_blank'
260
+ rel='noopener noreferrer'
261
+ class='hidden md:flex items-center space-x-2 bg-[#b21100] hover:bg-[#8f0d00] text-white px-4 py-2 rounded font-bold transition-colors uppercase tracking-wide text-sm'
262
+ >
263
+ <svg class='w-5 h-5' viewBox='0 0 24 24' fill='currentColor'>
264
+ <path d='M8 5v14l11-7z'></path>
265
+ </svg>
266
+ <span>Watch Live</span>
267
+ </a>
268
+ </div>
269
+ </nav>
270
+ </div>
271
+ </header>
272
+
273
+ <script>
274
+ (function () { const menuButton = document.getElementById('menu-button'); const dropdownMenu = document.getElementById('dropdown-menu'); const sectionsLinks =
275
+ document.getElementById('sections-links'); const categoryLinksContainer = document.getElementById('category-links-container'); const miniFeatured =
276
+ document.getElementById('mini-featured'); if (!menuButton || !dropdownMenu) return; let isOpen = false; let contentLoaded = false; let currentCategoryKey =
277
+ '__home__'; let activeCategorySlug = ''; let requestVersion = 0; const cache = new Map(); function getCurrentLang() { const p = window.location.pathname ||
278
+ '/'; if (p.startsWith('/es')) return 'es'; if (p.startsWith('/it')) return 'it'; return 'en'; } function buildLocalePath(slug) { const lang =
279
+ getCurrentLang(); if (!slug) return lang === 'en' ? '/' : `/${lang}`; const normalized = String(slug).startsWith('/') ? String(slug) : `/${slug}`; if
280
+ (normalized.startsWith('/es/') || normalized.startsWith('/it/')) return normalized; if (lang === 'en') return normalized; return `/${lang}${normalized}`; }
281
+ function normalizePathInput(value) { if (typeof value !== 'string') return ''; const trimmed = value.trim(); if (!trimmed) return ''; try { if
282
+ (/^https?:\/\//i.test(trimmed)) { const parsed = new URL(trimmed); const normalizedAbsolute = `/${(parsed.pathname || '').replace(/^\/+/,
283
+ '')}`.replace(/\/{2,}/g, '/'); if (normalizedAbsolute !== '/' && normalizedAbsolute.endsWith('/')) { return normalizedAbsolute.slice(0, -1); } return
284
+ normalizedAbsolute || '/'; } } catch { /* Fall back to raw string normalization below */ } const withoutQueryOrHash = trimmed.split('#')[0].split('?')[0]; const
285
+ normalized = `/${withoutQueryOrHash.replace(/^\/+/, '')}`.replace(/\/{2,}/g, '/'); if (normalized !== '/' && normalized.endsWith('/')) { return
286
+ normalized.slice(0, -1); } return normalized; } function cleanPathSegment(value) { if (typeof value !== 'string') return ''; return
287
+ value.trim().replace(/^\/+|\/+$/g, ''); } function getArticleLeafPath(article) { const explicitUrl = normalizePathInput(article?.url); if (explicitUrl &&
288
+ explicitUrl !== '/') { return buildLocalePath(explicitUrl); } const canonicalUrl = normalizePathInput(article?.canonicalUrl); if (canonicalUrl && canonicalUrl
289
+ !== '/') { return buildLocalePath(canonicalUrl); } const slugRaw = typeof article?.slug === 'string' ? article.slug : ''; const normalizedSlugPath =
290
+ normalizePathInput(slugRaw); const slugSegments = normalizedSlugPath.split('/').filter(Boolean); if (slugSegments.length > 1) { return
291
+ buildLocalePath(normalizedSlugPath); } const slugSegment = cleanPathSegment(slugRaw); const primaryCategorySlug =
292
+ cleanPathSegment(article?.categories?.[0]?.slug) || cleanPathSegment(article?.category?.slug); if (slugSegment && primaryCategorySlug) { return
293
+ buildLocalePath(`/${primaryCategorySlug}/${slugSegment}`); } if (slugSegment) { return buildLocalePath(`/${slugSegment}`); } return buildLocalePath('/'); }
294
+ function getCdnUrl(categorySlug) { const lang = getCurrentLang(); const file = categorySlug ? `${categorySlug}-current-${lang}.json` :
295
+ `homepage-current-${lang}.json`; return `https://cdn.fifthbell.com/content/${file}?v=${Date.now()}-${Math.random().toString(36).slice(2)}`; } function
296
+ getArticlesFromPayload(payload) { if (!payload || typeof payload !== 'object') return []; return payload.docs || payload.articles || []; } function
297
+ showCategoryLinksLoading() { if (!categoryLinksContainer) return; categoryLinksContainer.innerHTML = `<p class='text-sm text-slate-400
298
+ dark:text-slate-500'>Loading sections...</p>`; } function showCategoryLinksError() { if (!categoryLinksContainer) return; categoryLinksContainer.innerHTML =
299
+ `<p class='text-sm text-red-500'>Failed to load sections</p>`; } function setActiveCategoryLink(categorySlug) { if (!sectionsLinks) return; activeCategorySlug
300
+ = categorySlug || ''; const links = sectionsLinks.querySelectorAll('.category-link'); links.forEach((link) => { const isActive =
301
+ (link.getAttribute('data-category-slug') || '') === activeCategorySlug; if (isActive) { link.classList.add('text-[#b21100]', 'dark:text-[#ff2e1a]');
302
+ link.classList.remove('text-black', 'dark:text-white'); link.setAttribute('aria-current', 'page'); } else { link.classList.remove('text-[#b21100]',
303
+ 'dark:text-[#ff2e1a]'); link.classList.add('text-black', 'dark:text-white'); link.setAttribute('aria-current', 'false'); } }); } function
304
+ renderCategories(categories) { if (!categoryLinksContainer) return; if (!Array.isArray(categories) || categories.length === 0) {
305
+ categoryLinksContainer.innerHTML = `<p class='text-sm text-slate-400 dark:text-slate-500'>No sections available</p>`; return; }
306
+ categoryLinksContainer.innerHTML = categories .map( (category) => `<a href="${buildLocalePath(category.slug)}" class="category-link block text-lg font-bold
307
+ text-black dark:text-white hover:text-[#b21100] dark:hover:text-[#ff2e1a] transition-colors" data-category-slug="${category.slug}"
308
+ aria-current="false">${String(category.name || '').toUpperCase()}</a>` ) .join(''); setActiveCategoryLink(activeCategorySlug); } function
309
+ showMiniFeaturedLoading() { if (!miniFeatured) return; miniFeatured.innerHTML = ` <div class='flex flex-col items-center justify-center py-20'> <div
310
+ class='animate-spin rounded-full h-12 w-12 border-b-2 border-slate-300 dark:border-slate-600'></div> <p class='text-slate-400 dark:text-slate-500
311
+ mt-4'>Loading stories...</p> </div> `; } function renderMiniFeatured(articles, categoryName) { if (!miniFeatured) return; if (!Array.isArray(articles) ||
312
+ articles.length === 0) { miniFeatured.innerHTML = `<h3 class='text-xs font-bold text-slate-400 uppercase tracking-wider mb-6 border-b border-slate-200
313
+ dark:border-slate-800 pb-2'>${(categoryName || 'Featured Stories').toUpperCase()}</h3>` + `<p class='text-slate-400 dark:text-slate-500'>No stories
314
+ available</p>`; return; } const top = (articles || []).slice(0, 2); const grid = (articles || []).slice(2, 11); const topHtml = top .map( (article) => ` <div>
315
+ <div class='relative mb-5'> <img src='${article?.featuredImage?.url || ''}' alt='${article?.featuredImage?.alt || article?.title || ''}' class='w-full h-50
316
+ object-cover rounded' /> </div> <div class='flex items-center text-slate-500 dark:text-slate-400 text-sm mb-3'> <span class='uppercase font-bold
317
+ tracking-wider'>${article?.categories?.[0]?.name || categoryName || 'GENERAL'}</span> <span class='mx-2'>•</span> <span>5 min</span> </div> <h2 class='text-xl
318
+ font-bold text-slate-900 dark:text-slate-100 leading-tight'> <a href='${getArticleLeafPath(article)}' class='hover:text-[#b21100] dark:hover:text-[#ff2e1a]
319
+ transition-colors'>${article?.title || ''}</a> </h2> </div> ` ) .join(''); const rows = []; for (let i = 0; i < grid.length; i += 3) { const row =
320
+ grid.slice(i, i + 3); if (!row.length) continue; rows.push(` <div class='grid grid-cols-3 gap-6 ${i > 0 ? 'pt-6 border-t border-slate-200 dark:border-slate-800' : ''}'> ${row .map( (article, idx) => ` <div class='${idx === 0 ? 'pr-4 border-r border-slate-200 dark:border-slate-800' : idx === 1 ?
321
+ 'px-4 border-r border-slate-200 dark:border-slate-800' : 'pl-4'}'> <div class='flex items-center text-slate-500 dark:text-slate-400 text-sm mb-3'> <span
322
+ class='uppercase font-bold'>${article?.categories?.[0]?.name || categoryName || 'GENERAL'}</span> </div> <h3 class='font-medium text-base leading-tight
323
+ text-slate-900 dark:text-slate-100'> <a href='${getArticleLeafPath(article)}' class='hover:text-[#b21100] dark:hover:text-[#ff2e1a]
324
+ transition-colors'>${article?.title || ''}</a> </h3> </div> ` ) .join('')} </div> `); } miniFeatured.innerHTML = ` <h3 class='text-xs font-bold text-slate-400
325
+ dark:text-slate-600 uppercase tracking-wider mb-6 border-b border-slate-200 dark:border-slate-800 pb-2'> ${(categoryName || 'Featured Stories').toUpperCase()}
326
+ </h3> <div class='grid grid-cols-2 gap-8 mb-10'>${topHtml}</div> <div class='space-y-6'>${rows.join('')}</div> `; } function getCategoryName(payload,
327
+ categorySlug) { if (!categorySlug) return 'Featured Stories'; const fromPrimaryCategory = payload?.category?.name; if (fromPrimaryCategory) return
328
+ fromPrimaryCategory; const fromCategoriesArray = (payload?.categories || []).find((category) => category?.slug === categorySlug)?.name; if
329
+ (fromCategoriesArray) return fromCategoriesArray; return String(categorySlug).replace(/-/g, ' '); } async function fetchPayload(categorySlug) { const response
330
+ = await fetch(getCdnUrl(categorySlug), { headers: { 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error(`Failed to fetch ${categorySlug
331
+ || 'homepage'} data`); return response.json(); } async function loadCategoryContent(categorySlug) { const key = categorySlug || '__home__'; if (key ===
332
+ currentCategoryKey && cache.has(key)) return; currentCategoryKey = key; if (cache.has(key)) { const cached = cache.get(key);
333
+ renderMiniFeatured(cached.articles, cached.categoryName); return; } const requestId = ++requestVersion; showMiniFeaturedLoading(); try { const payload = await
334
+ fetchPayload(categorySlug); if (requestId !== requestVersion || key !== currentCategoryKey) return; const articles = getArticlesFromPayload(payload); const
335
+ categoryName = getCategoryName(payload, categorySlug); const data = { articles, categoryName }; cache.set(key, data); renderMiniFeatured(articles,
336
+ categoryName); } catch (error) { if (requestId !== requestVersion || key !== currentCategoryKey) return; if (miniFeatured) { miniFeatured.innerHTML = `<h3
337
+ class='text-xs font-bold text-slate-400 uppercase tracking-wider mb-6 border-b border-slate-200 dark:border-slate-800 pb-2'>ERROR</h3>` + `<p
338
+ class='text-red-500'>Failed to load stories</p>`; } } } function shouldPreviewOnClick(event) { if (!(miniFeatured instanceof HTMLElement)) return false; if
339
+ (!window.matchMedia('(min-width: 1024px)').matches) return false; if (event.button !== 0) return false; if (event.metaKey || event.ctrlKey || event.shiftKey
340
+ || event.altKey) return false; return true; } function attachSectionHandlers() { if (!sectionsLinks) return; const topStories =
341
+ sectionsLinks.querySelector('a[href="{{logoLink}}"]'); if (topStories) { topStories.classList.add('category-link');
342
+ topStories.setAttribute('data-category-slug', ''); } const links = sectionsLinks.querySelectorAll('.category-link'); links.forEach((link) => { const
343
+ selectCategory = () => { const slug = link.getAttribute('data-category-slug') || ''; setActiveCategoryLink(slug); loadCategoryContent(slug); };
344
+ link.addEventListener('mouseenter', selectCategory); link.addEventListener('focus', selectCategory); link.addEventListener('click', (event) => { if
345
+ (!shouldPreviewOnClick(event)) return; event.preventDefault(); selectCategory(); }); }); } async function initializeMenuContent() { if (contentLoaded) return;
346
+ showCategoryLinksLoading(); showMiniFeaturedLoading(); try { const payload = await fetchPayload(''); const categories = (payload.categories || []).filter((c) => c && c.slug && (c.featured ?? true)); renderCategories(categories); attachSectionHandlers(); const homeArticles = getArticlesFromPayload(payload); const
347
+ homeData = { articles: homeArticles, categoryName: 'Featured Stories' }; cache.set('__home__', homeData); currentCategoryKey = '__home__';
348
+ setActiveCategoryLink(''); renderMiniFeatured(homeArticles, homeData.categoryName); contentLoaded = true; } catch { showCategoryLinksError(); if
349
+ (miniFeatured) { miniFeatured.innerHTML = `<h3 class='text-xs font-bold text-slate-400 uppercase tracking-wider mb-6 border-b border-slate-200
350
+ dark:border-slate-800 pb-2'>ERROR</h3>` + `<p class='text-red-500'>Failed to load stories</p>`; } } } function toggleMenu() { isOpen = !isOpen;
351
+ menuButton.setAttribute('aria-expanded', String(isOpen)); dropdownMenu.style.pointerEvents = isOpen ? 'auto' : 'none'; dropdownMenu.style.transform = isOpen ?
352
+ 'translateX(0)' : 'translateX(-100%)'; dropdownMenu.style.opacity = isOpen ? '1' : '0'; document.body.style.overflow = isOpen ? 'hidden' : ''; if (isOpen) {
353
+ initializeMenuContent(); } } menuButton.addEventListener('click', (event) => { event.stopPropagation(); toggleMenu(); }); document.addEventListener('click',
354
+ (event) => { if (!isOpen) return; const target = event.target; if (target instanceof Node && dropdownMenu.contains(target)) return; if (target === menuButton)
355
+ return; toggleMenu(); }); })();
356
+ </script>
357
+
358
+ <script>
359
+ (function () { const THEME_KEY = 'fifthbell_theme'; const themeButtons = document.querySelectorAll('.theme-btn'); if (!themeButtons.length) return; function
360
+ getSystemTheme() { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } function applyTheme(theme) { const actualTheme =
361
+ theme === 'system' ? getSystemTheme() : theme; if (actualTheme === 'dark') { document.documentElement.classList.add('dark'); } else {
362
+ document.documentElement.classList.remove('dark'); } } function updateActiveButton(theme) { themeButtons.forEach((button) => { const buttonTheme =
363
+ button.getAttribute('data-theme'); if (buttonTheme === theme) { button.classList.add('!bg-[#b21100]', '!text-white');
364
+ button.classList.remove('text-slate-700', 'dark:text-slate-300'); button.setAttribute('aria-pressed', 'true'); } else {
365
+ button.classList.remove('!bg-[#b21100]', '!text-white'); button.classList.add('text-slate-700', 'dark:text-slate-300'); button.setAttribute('aria-pressed',
366
+ 'false'); } }); } function setTheme(theme) { localStorage.setItem(THEME_KEY, theme); applyTheme(theme); updateActiveButton(theme); } function initTheme() {
367
+ const savedTheme = localStorage.getItem(THEME_KEY) || 'system'; applyTheme(savedTheme); updateActiveButton(savedTheme); } themeButtons.forEach((button) => {
368
+ button.addEventListener('click', () => { const theme = button.getAttribute('data-theme'); if (theme) setTheme(theme); }); });
369
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { if ((localStorage.getItem(THEME_KEY) || 'system') === 'system') {
370
+ applyTheme('system'); } }); initTheme(); })();
371
+ </script>
372
+
373
+ <script>
374
+ (function () { const langButtons = document.querySelectorAll('.lang-btn'); if (!langButtons.length) return; function getCurrentLanguageFromPath() { const path
375
+ = window.location.pathname || '/'; if (path.startsWith('/es')) return 'es'; if (path.startsWith('/it')) return 'it'; return 'en'; } function
376
+ updateActiveButton(lang) { langButtons.forEach((button) => { const buttonLang = button.getAttribute('data-lang'); if (buttonLang === lang) {
377
+ button.classList.add('!bg-[#b21100]', '!text-white'); button.classList.remove('text-slate-700', 'dark:text-slate-300'); button.setAttribute('aria-pressed',
378
+ 'true'); } else { button.classList.remove('!bg-[#b21100]', '!text-white'); button.classList.add('text-slate-700', 'dark:text-slate-300');
379
+ button.setAttribute('aria-pressed', 'false'); } }); } function getTargetPathForLanguage(targetLang) { const currentPath = window.location.pathname || '/';
380
+ const currentLang = getCurrentLanguageFromPath(); if (currentLang === targetLang) return null; let basePath = currentPath; if (currentLang === 'es' &&
381
+ currentPath.startsWith('/es')) { basePath = currentPath.substring(3) || '/'; } else if (currentLang === 'it' && currentPath.startsWith('/it')) { basePath =
382
+ currentPath.substring(3) || '/'; } const segments = basePath.split('/').filter(Boolean); let categorySlugs = new Set(['politics', 'world', 'business',
383
+ 'sports', 'technology', 'weather', 'crime', 'new-york']); try { const cached = localStorage.getItem('fifthbell_categories'); if (cached) { const parsed =
384
+ JSON.parse(cached); const categories = parsed?.data; if (Array.isArray(categories)) { categorySlugs = new Set(categories.map((item) => item.slug)); } } }
385
+ catch { /* ignore invalid local cache */ } const isHomepage = segments.length === 0; const isCategoryPage = segments.length === 1 &&
386
+ categorySlugs.has(segments[0]); const isSearchPage = segments.length >= 1 && segments[0] === 'search'; if (!isHomepage && !isCategoryPage &&
387
+ !isSearchPage) { return targetLang === 'en' ? '/' : `/${targetLang}`; } if (isSearchPage) { const currentSearch = window.location.search || ''; return
388
+ targetLang === 'en' ? `/search${currentSearch}` : `/${targetLang}/search${currentSearch}`; } if (targetLang === 'en') return
389
+ basePath === '/' ? '/' : basePath; return basePath === '/' ? `/${targetLang}` : `/${targetLang}${basePath}`; } function closeMenuIfOpen() { const menuButton =
390
+ document.getElementById('menu-button'); const dropdownMenu = document.getElementById('dropdown-menu'); if (!(menuButton instanceof HTMLElement) ||
391
+ !(dropdownMenu instanceof HTMLElement)) return; const isExpanded = menuButton.getAttribute('aria-expanded') === 'true'; if (!isExpanded) return;
392
+ menuButton.setAttribute('aria-expanded', 'false'); dropdownMenu.style.pointerEvents = 'none'; dropdownMenu.style.transform = 'translateX(-100%)';
393
+ dropdownMenu.style.opacity = '0'; document.body.style.overflow = ''; } langButtons.forEach((button) => { button.addEventListener('click', () => { const
394
+ targetLang = button.getAttribute('data-lang'); if (!targetLang) return; updateActiveButton(targetLang); closeMenuIfOpen(); const
395
+ targetPath = getTargetPathForLanguage(targetLang); if (targetPath) window.location.href = targetPath; }); });
396
+ updateActiveButton(getCurrentLanguageFromPath()); })();
397
+ </script>