@highjumpdigitalsoftware/blog-kit 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/INTEGRATION.md +76 -0
  2. package/LICENSE +74 -0
  3. package/README.md +102 -0
  4. package/astro/AdPreview.astro +64 -0
  5. package/astro/AdPreviewPair.astro +10 -0
  6. package/astro/AuditFindings.astro +29 -0
  7. package/astro/AuditScores.astro +60 -0
  8. package/astro/AuthorCard.astro +32 -0
  9. package/astro/BeforeAfter.astro +26 -0
  10. package/astro/BlogBehaviors.astro +15 -0
  11. package/astro/CTABanner.astro +28 -0
  12. package/astro/CalloutBox.astro +28 -0
  13. package/astro/CaseStudyHero.astro +45 -0
  14. package/astro/ChannelMixBars.astro +33 -0
  15. package/astro/Checklist.astro +24 -0
  16. package/astro/ChecklistItem.astro +15 -0
  17. package/astro/CodeSnippet.astro +20 -0
  18. package/astro/ComparisonTable.astro +103 -0
  19. package/astro/Definition.astro +30 -0
  20. package/astro/DeliveryComparison.astro +40 -0
  21. package/astro/FAQList.astro +43 -0
  22. package/astro/FurtherReading.astro +34 -0
  23. package/astro/ImageFeature.astro +22 -0
  24. package/astro/Infographic.astro +12 -0
  25. package/astro/KeyMetric.astro +40 -0
  26. package/astro/KeywordTable.astro +69 -0
  27. package/astro/List.astro +46 -0
  28. package/astro/MetricHighlight.astro +77 -0
  29. package/astro/NewsletterCTA.astro +40 -0
  30. package/astro/NumberedCard.astro +6 -0
  31. package/astro/ProConBlock.astro +38 -0
  32. package/astro/ProseList.astro +46 -0
  33. package/astro/QuoteBlock.astro +72 -0
  34. package/astro/RegionCallout.astro +24 -0
  35. package/astro/RelatedPosts.astro +47 -0
  36. package/astro/ResultsStrip.astro +59 -0
  37. package/astro/ScoreBar.astro +19 -0
  38. package/astro/SerpPreview.astro +35 -0
  39. package/astro/ServicePromoCard.astro +21 -0
  40. package/astro/StatCard.astro +48 -0
  41. package/astro/StepBlock.astro +5 -0
  42. package/astro/TableOfContents.astro +12 -0
  43. package/astro/TimelineBlock.astro +30 -0
  44. package/astro/TipBox.astro +14 -0
  45. package/astro/TrafficChart.astro +61 -0
  46. package/astro/VerdictCard.astro +48 -0
  47. package/astro/blogkit/Article.astro +63 -0
  48. package/astro/blogkit/BlogIndex.astro +144 -0
  49. package/core/behaviors/code.js +78 -0
  50. package/core/behaviors/comparison.js +52 -0
  51. package/core/behaviors/delivery-comparison.js +52 -0
  52. package/core/behaviors/faq.js +61 -0
  53. package/core/behaviors/index.d.ts +3 -0
  54. package/core/behaviors/index.js +35 -0
  55. package/core/behaviors/keyword-table.js +52 -0
  56. package/core/behaviors/toc.js +130 -0
  57. package/core/css/base.css +146 -0
  58. package/core/css/components.css +2632 -0
  59. package/core/css/index-listing.css +207 -0
  60. package/core/css/index.css +13 -0
  61. package/core/css/tokens.css +127 -0
  62. package/core/icons.ts +20 -0
  63. package/core/lib.ts +70 -0
  64. package/core/manifest/components.json +573 -0
  65. package/core/manifest/frontmatter.json +19 -0
  66. package/core/manifest/templates.json +77 -0
  67. package/dist/core/behaviors/code.js +78 -0
  68. package/dist/core/behaviors/comparison.js +52 -0
  69. package/dist/core/behaviors/delivery-comparison.js +52 -0
  70. package/dist/core/behaviors/faq.js +61 -0
  71. package/dist/core/behaviors/index.d.ts +3 -0
  72. package/dist/core/behaviors/index.js +35 -0
  73. package/dist/core/behaviors/keyword-table.js +52 -0
  74. package/dist/core/behaviors/toc.js +130 -0
  75. package/dist/core/css/base.css +146 -0
  76. package/dist/core/css/components.css +2632 -0
  77. package/dist/core/css/index-listing.css +207 -0
  78. package/dist/core/css/index.css +13 -0
  79. package/dist/core/css/tokens.css +127 -0
  80. package/dist/core/icons.d.ts +2 -0
  81. package/dist/core/icons.d.ts.map +1 -0
  82. package/dist/core/icons.js +20 -0
  83. package/dist/core/icons.js.map +1 -0
  84. package/dist/core/lib.d.ts +21 -0
  85. package/dist/core/lib.d.ts.map +1 -0
  86. package/dist/core/lib.js +57 -0
  87. package/dist/core/lib.js.map +1 -0
  88. package/dist/core/manifest/components.json +573 -0
  89. package/dist/core/manifest/frontmatter.json +19 -0
  90. package/dist/core/manifest/templates.json +77 -0
  91. package/dist/package/adapters/hjd-api.d.ts +14 -0
  92. package/dist/package/adapters/hjd-api.d.ts.map +1 -0
  93. package/dist/package/adapters/hjd-api.js +57 -0
  94. package/dist/package/adapters/hjd-api.js.map +1 -0
  95. package/dist/package/adapters/index.d.ts +13 -0
  96. package/dist/package/adapters/index.d.ts.map +1 -0
  97. package/dist/package/adapters/index.js +16 -0
  98. package/dist/package/adapters/index.js.map +1 -0
  99. package/dist/package/adapters/local.d.ts +13 -0
  100. package/dist/package/adapters/local.d.ts.map +1 -0
  101. package/dist/package/adapters/local.js +72 -0
  102. package/dist/package/adapters/local.js.map +1 -0
  103. package/dist/package/adapters/source.d.ts +39 -0
  104. package/dist/package/adapters/source.d.ts.map +1 -0
  105. package/dist/package/adapters/source.js +19 -0
  106. package/dist/package/adapters/source.js.map +1 -0
  107. package/dist/package/article.d.ts +17 -0
  108. package/dist/package/article.d.ts.map +1 -0
  109. package/dist/package/article.js +37 -0
  110. package/dist/package/article.js.map +1 -0
  111. package/dist/package/astro/data.d.ts +45 -0
  112. package/dist/package/astro/data.d.ts.map +1 -0
  113. package/dist/package/astro/data.js +81 -0
  114. package/dist/package/astro/data.js.map +1 -0
  115. package/dist/package/astro/freshness.d.ts +11 -0
  116. package/dist/package/astro/freshness.d.ts.map +1 -0
  117. package/dist/package/astro/freshness.js +48 -0
  118. package/dist/package/astro/freshness.js.map +1 -0
  119. package/dist/package/astro/index.d.ts +12 -0
  120. package/dist/package/astro/index.d.ts.map +1 -0
  121. package/dist/package/astro/index.js +31 -0
  122. package/dist/package/astro/index.js.map +1 -0
  123. package/dist/package/blog-index.d.ts +10 -0
  124. package/dist/package/blog-index.d.ts.map +1 -0
  125. package/dist/package/blog-index.js +27 -0
  126. package/dist/package/blog-index.js.map +1 -0
  127. package/dist/package/cli/exchange.d.ts +27 -0
  128. package/dist/package/cli/exchange.d.ts.map +1 -0
  129. package/dist/package/cli/exchange.js +94 -0
  130. package/dist/package/cli/exchange.js.map +1 -0
  131. package/dist/package/cli/index.d.ts +3 -0
  132. package/dist/package/cli/index.d.ts.map +1 -0
  133. package/dist/package/cli/index.js +301 -0
  134. package/dist/package/cli/index.js.map +1 -0
  135. package/dist/package/config/define.d.ts +13 -0
  136. package/dist/package/config/define.d.ts.map +1 -0
  137. package/dist/package/config/define.js +14 -0
  138. package/dist/package/config/define.js.map +1 -0
  139. package/dist/package/config/resolve.d.ts +11 -0
  140. package/dist/package/config/resolve.d.ts.map +1 -0
  141. package/dist/package/config/resolve.js +43 -0
  142. package/dist/package/config/resolve.js.map +1 -0
  143. package/dist/package/config/types.d.ts +74 -0
  144. package/dist/package/config/types.d.ts.map +1 -0
  145. package/dist/package/config/types.js +13 -0
  146. package/dist/package/config/types.js.map +1 -0
  147. package/dist/package/index-core.d.ts +28 -0
  148. package/dist/package/index-core.d.ts.map +1 -0
  149. package/dist/package/index-core.js +102 -0
  150. package/dist/package/index-core.js.map +1 -0
  151. package/dist/package/index.d.ts +13 -0
  152. package/dist/package/index.d.ts.map +1 -0
  153. package/dist/package/index.js +25 -0
  154. package/dist/package/index.js.map +1 -0
  155. package/dist/package/mdx/render-astro.d.ts +18 -0
  156. package/dist/package/mdx/render-astro.d.ts.map +1 -0
  157. package/dist/package/mdx/render-astro.js +75 -0
  158. package/dist/package/mdx/render-astro.js.map +1 -0
  159. package/dist/package/mdx/render.d.ts +13 -0
  160. package/dist/package/mdx/render.d.ts.map +1 -0
  161. package/dist/package/mdx/render.js +37 -0
  162. package/dist/package/mdx/render.js.map +1 -0
  163. package/dist/react/AdPreview.d.ts +26 -0
  164. package/dist/react/AdPreview.d.ts.map +1 -0
  165. package/dist/react/AdPreview.js +8 -0
  166. package/dist/react/AdPreview.js.map +1 -0
  167. package/dist/react/AdPreviewPair.d.ts +7 -0
  168. package/dist/react/AdPreviewPair.d.ts.map +1 -0
  169. package/dist/react/AdPreviewPair.js +5 -0
  170. package/dist/react/AdPreviewPair.js.map +1 -0
  171. package/dist/react/AuditFindings.d.ts +14 -0
  172. package/dist/react/AuditFindings.d.ts.map +1 -0
  173. package/dist/react/AuditFindings.js +5 -0
  174. package/dist/react/AuditFindings.js.map +1 -0
  175. package/dist/react/AuditScores.d.ts +12 -0
  176. package/dist/react/AuditScores.d.ts.map +1 -0
  177. package/dist/react/AuditScores.js +25 -0
  178. package/dist/react/AuditScores.js.map +1 -0
  179. package/dist/react/AuthorCard.d.ts +10 -0
  180. package/dist/react/AuthorCard.d.ts.map +1 -0
  181. package/dist/react/AuthorCard.js +6 -0
  182. package/dist/react/AuthorCard.js.map +1 -0
  183. package/dist/react/BeforeAfter.d.ts +12 -0
  184. package/dist/react/BeforeAfter.d.ts.map +1 -0
  185. package/dist/react/BeforeAfter.js +7 -0
  186. package/dist/react/BeforeAfter.js.map +1 -0
  187. package/dist/react/BlogBehaviors.d.ts +10 -0
  188. package/dist/react/BlogBehaviors.d.ts.map +1 -0
  189. package/dist/react/BlogBehaviors.js +20 -0
  190. package/dist/react/BlogBehaviors.js.map +1 -0
  191. package/dist/react/CTABanner.d.ts +8 -0
  192. package/dist/react/CTABanner.d.ts.map +1 -0
  193. package/dist/react/CTABanner.js +9 -0
  194. package/dist/react/CTABanner.js.map +1 -0
  195. package/dist/react/CalloutBox.d.ts +13 -0
  196. package/dist/react/CalloutBox.d.ts.map +1 -0
  197. package/dist/react/CalloutBox.js +9 -0
  198. package/dist/react/CalloutBox.js.map +1 -0
  199. package/dist/react/CaseStudyHero.d.ts +20 -0
  200. package/dist/react/CaseStudyHero.d.ts.map +1 -0
  201. package/dist/react/CaseStudyHero.js +7 -0
  202. package/dist/react/CaseStudyHero.js.map +1 -0
  203. package/dist/react/ChannelMixBars.d.ts +18 -0
  204. package/dist/react/ChannelMixBars.d.ts.map +1 -0
  205. package/dist/react/ChannelMixBars.js +6 -0
  206. package/dist/react/ChannelMixBars.js.map +1 -0
  207. package/dist/react/Checklist.d.ts +10 -0
  208. package/dist/react/Checklist.d.ts.map +1 -0
  209. package/dist/react/Checklist.js +7 -0
  210. package/dist/react/Checklist.js.map +1 -0
  211. package/dist/react/ChecklistItem.d.ts +7 -0
  212. package/dist/react/ChecklistItem.d.ts.map +1 -0
  213. package/dist/react/ChecklistItem.js +5 -0
  214. package/dist/react/ChecklistItem.js.map +1 -0
  215. package/dist/react/CodeSnippet.d.ts +17 -0
  216. package/dist/react/CodeSnippet.d.ts.map +1 -0
  217. package/dist/react/CodeSnippet.js +14 -0
  218. package/dist/react/CodeSnippet.js.map +1 -0
  219. package/dist/react/ComparisonTable.d.ts +22 -0
  220. package/dist/react/ComparisonTable.d.ts.map +1 -0
  221. package/dist/react/ComparisonTable.js +35 -0
  222. package/dist/react/ComparisonTable.js.map +1 -0
  223. package/dist/react/Definition.d.ts +9 -0
  224. package/dist/react/Definition.d.ts.map +1 -0
  225. package/dist/react/Definition.js +19 -0
  226. package/dist/react/Definition.js.map +1 -0
  227. package/dist/react/DeliveryComparison.d.ts +16 -0
  228. package/dist/react/DeliveryComparison.d.ts.map +1 -0
  229. package/dist/react/DeliveryComparison.js +7 -0
  230. package/dist/react/DeliveryComparison.js.map +1 -0
  231. package/dist/react/FAQList.d.ts +20 -0
  232. package/dist/react/FAQList.d.ts.map +1 -0
  233. package/dist/react/FAQList.js +19 -0
  234. package/dist/react/FAQList.js.map +1 -0
  235. package/dist/react/FurtherReading.d.ts +21 -0
  236. package/dist/react/FurtherReading.d.ts.map +1 -0
  237. package/dist/react/FurtherReading.js +13 -0
  238. package/dist/react/FurtherReading.js.map +1 -0
  239. package/dist/react/ImageFeature.d.ts +9 -0
  240. package/dist/react/ImageFeature.d.ts.map +1 -0
  241. package/dist/react/ImageFeature.js +6 -0
  242. package/dist/react/ImageFeature.js.map +1 -0
  243. package/dist/react/Infographic.d.ts +6 -0
  244. package/dist/react/Infographic.d.ts.map +1 -0
  245. package/dist/react/Infographic.js +7 -0
  246. package/dist/react/Infographic.js.map +1 -0
  247. package/dist/react/KeyMetric.d.ts +16 -0
  248. package/dist/react/KeyMetric.d.ts.map +1 -0
  249. package/dist/react/KeyMetric.js +15 -0
  250. package/dist/react/KeyMetric.js.map +1 -0
  251. package/dist/react/KeywordTable.d.ts +18 -0
  252. package/dist/react/KeywordTable.d.ts.map +1 -0
  253. package/dist/react/KeywordTable.js +23 -0
  254. package/dist/react/KeywordTable.js.map +1 -0
  255. package/dist/react/List.d.ts +11 -0
  256. package/dist/react/List.d.ts.map +1 -0
  257. package/dist/react/List.js +21 -0
  258. package/dist/react/List.js.map +1 -0
  259. package/dist/react/MetricHighlight.d.ts +15 -0
  260. package/dist/react/MetricHighlight.d.ts.map +1 -0
  261. package/dist/react/MetricHighlight.js +26 -0
  262. package/dist/react/MetricHighlight.js.map +1 -0
  263. package/dist/react/NewsletterCTA.d.ts +9 -0
  264. package/dist/react/NewsletterCTA.d.ts.map +1 -0
  265. package/dist/react/NewsletterCTA.js +5 -0
  266. package/dist/react/NewsletterCTA.js.map +1 -0
  267. package/dist/react/NumberedCard.d.ts +9 -0
  268. package/dist/react/NumberedCard.d.ts.map +1 -0
  269. package/dist/react/NumberedCard.js +7 -0
  270. package/dist/react/NumberedCard.js.map +1 -0
  271. package/dist/react/ProConBlock.d.ts +6 -0
  272. package/dist/react/ProConBlock.d.ts.map +1 -0
  273. package/dist/react/ProConBlock.js +7 -0
  274. package/dist/react/ProConBlock.js.map +1 -0
  275. package/dist/react/ProseList.d.ts +17 -0
  276. package/dist/react/ProseList.d.ts.map +1 -0
  277. package/dist/react/ProseList.js +26 -0
  278. package/dist/react/ProseList.js.map +1 -0
  279. package/dist/react/QuoteBlock.d.ts +17 -0
  280. package/dist/react/QuoteBlock.d.ts.map +1 -0
  281. package/dist/react/QuoteBlock.js +26 -0
  282. package/dist/react/QuoteBlock.js.map +1 -0
  283. package/dist/react/RegionCallout.d.ts +13 -0
  284. package/dist/react/RegionCallout.d.ts.map +1 -0
  285. package/dist/react/RegionCallout.js +5 -0
  286. package/dist/react/RegionCallout.js.map +1 -0
  287. package/dist/react/RelatedPosts.d.ts +20 -0
  288. package/dist/react/RelatedPosts.d.ts.map +1 -0
  289. package/dist/react/RelatedPosts.js +7 -0
  290. package/dist/react/RelatedPosts.js.map +1 -0
  291. package/dist/react/ResultsStrip.d.ts +18 -0
  292. package/dist/react/ResultsStrip.d.ts.map +1 -0
  293. package/dist/react/ResultsStrip.js +22 -0
  294. package/dist/react/ResultsStrip.js.map +1 -0
  295. package/dist/react/ScoreBar.d.ts +8 -0
  296. package/dist/react/ScoreBar.d.ts.map +1 -0
  297. package/dist/react/ScoreBar.js +6 -0
  298. package/dist/react/ScoreBar.js.map +1 -0
  299. package/dist/react/SerpPreview.d.ts +18 -0
  300. package/dist/react/SerpPreview.d.ts.map +1 -0
  301. package/dist/react/SerpPreview.js +13 -0
  302. package/dist/react/SerpPreview.js.map +1 -0
  303. package/dist/react/ServicePromoCard.d.ts +14 -0
  304. package/dist/react/ServicePromoCard.d.ts.map +1 -0
  305. package/dist/react/ServicePromoCard.js +12 -0
  306. package/dist/react/ServicePromoCard.js.map +1 -0
  307. package/dist/react/StatCard.d.ts +13 -0
  308. package/dist/react/StatCard.d.ts.map +1 -0
  309. package/dist/react/StatCard.js +20 -0
  310. package/dist/react/StatCard.js.map +1 -0
  311. package/dist/react/StepBlock.d.ts +8 -0
  312. package/dist/react/StepBlock.d.ts.map +1 -0
  313. package/dist/react/StepBlock.js +5 -0
  314. package/dist/react/StepBlock.js.map +1 -0
  315. package/dist/react/TableOfContents.d.ts +14 -0
  316. package/dist/react/TableOfContents.d.ts.map +1 -0
  317. package/dist/react/TableOfContents.js +12 -0
  318. package/dist/react/TableOfContents.js.map +1 -0
  319. package/dist/react/TimelineBlock.d.ts +14 -0
  320. package/dist/react/TimelineBlock.d.ts.map +1 -0
  321. package/dist/react/TimelineBlock.js +8 -0
  322. package/dist/react/TimelineBlock.js.map +1 -0
  323. package/dist/react/TipBox.d.ts +6 -0
  324. package/dist/react/TipBox.d.ts.map +1 -0
  325. package/dist/react/TipBox.js +5 -0
  326. package/dist/react/TipBox.js.map +1 -0
  327. package/dist/react/TrafficChart.d.ts +16 -0
  328. package/dist/react/TrafficChart.d.ts.map +1 -0
  329. package/dist/react/TrafficChart.js +14 -0
  330. package/dist/react/TrafficChart.js.map +1 -0
  331. package/dist/react/VerdictCard.d.ts +15 -0
  332. package/dist/react/VerdictCard.d.ts.map +1 -0
  333. package/dist/react/VerdictCard.js +5 -0
  334. package/dist/react/VerdictCard.js.map +1 -0
  335. package/dist/react/components-map.d.ts +133 -0
  336. package/dist/react/components-map.d.ts.map +1 -0
  337. package/dist/react/components-map.js +120 -0
  338. package/dist/react/components-map.js.map +1 -0
  339. package/dist/react/index.d.ts +5 -0
  340. package/dist/react/index.d.ts.map +1 -0
  341. package/dist/react/index.js +13 -0
  342. package/dist/react/index.js.map +1 -0
  343. package/package.json +116 -0
  344. package/react/AdPreview.tsx +94 -0
  345. package/react/AdPreviewPair.tsx +16 -0
  346. package/react/AuditFindings.tsx +43 -0
  347. package/react/AuditScores.tsx +73 -0
  348. package/react/AuthorCard.tsx +35 -0
  349. package/react/BeforeAfter.tsx +27 -0
  350. package/react/BlogBehaviors.tsx +21 -0
  351. package/react/CTABanner.tsx +32 -0
  352. package/react/CalloutBox.tsx +31 -0
  353. package/react/CaseStudyHero.tsx +71 -0
  354. package/react/ChannelMixBars.tsx +50 -0
  355. package/react/Checklist.tsx +31 -0
  356. package/react/ChecklistItem.tsx +19 -0
  357. package/react/CodeSnippet.tsx +36 -0
  358. package/react/ComparisonTable.tsx +114 -0
  359. package/react/Definition.tsx +36 -0
  360. package/react/DeliveryComparison.tsx +62 -0
  361. package/react/FAQList.tsx +61 -0
  362. package/react/FurtherReading.tsx +46 -0
  363. package/react/ImageFeature.tsx +26 -0
  364. package/react/Infographic.tsx +18 -0
  365. package/react/KeyMetric.tsx +61 -0
  366. package/react/KeywordTable.tsx +92 -0
  367. package/react/List.tsx +58 -0
  368. package/react/MetricHighlight.tsx +86 -0
  369. package/react/NewsletterCTA.tsx +48 -0
  370. package/react/NumberedCard.tsx +7 -0
  371. package/react/ProConBlock.tsx +42 -0
  372. package/react/ProseList.tsx +72 -0
  373. package/react/QuoteBlock.tsx +89 -0
  374. package/react/RegionCallout.tsx +38 -0
  375. package/react/RelatedPosts.tsx +58 -0
  376. package/react/ResultsStrip.tsx +77 -0
  377. package/react/ScoreBar.tsx +27 -0
  378. package/react/SerpPreview.tsx +59 -0
  379. package/react/ServicePromoCard.tsx +43 -0
  380. package/react/StatCard.tsx +62 -0
  381. package/react/StepBlock.tsx +5 -0
  382. package/react/TableOfContents.tsx +27 -0
  383. package/react/TimelineBlock.tsx +35 -0
  384. package/react/TipBox.tsx +16 -0
  385. package/react/TrafficChart.tsx +79 -0
  386. package/react/VerdictCard.tsx +60 -0
  387. package/react/components-map.ts +122 -0
  388. package/react/index.ts +13 -0
  389. package/templates/blogkit/app/api/blogkit/revalidate/route.ts.tmpl +32 -0
  390. package/templates/blogkit/app/blog/[slug]/page.tsx.tmpl +41 -0
  391. package/templates/blogkit/app/blog/page.tsx.tmpl +18 -0
  392. package/templates/blogkit/blogkit.config.ts.tmpl +23 -0
  393. package/templates/blogkit-astro/BLOGKIT_ASTRO_SETUP.md.tmpl +49 -0
  394. package/templates/blogkit-astro/src/blogkit.config.ts.tmpl +29 -0
  395. package/templates/blogkit-astro/src/pages/api/blogkit/revalidate.ts.tmpl +46 -0
  396. package/templates/blogkit-astro/src/pages/blog/[slug].astro.tmpl +39 -0
  397. package/templates/blogkit-astro/src/pages/blog/index.astro.tmpl +29 -0
@@ -0,0 +1,27 @@
1
+ /* blog-kit React adapter — ScoreBar
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-score-bar).
3
+ A single labelled rating row — uppercase label, a numeric score, and a
4
+ gradient-filled progress track whose width reflects score / maxScore. */
5
+
6
+ export interface ScoreBarProps {
7
+ label: string;
8
+ score: number;
9
+ /** Denominator the score is out of (default 10). */
10
+ maxScore?: number;
11
+ }
12
+
13
+ export function ScoreBar({ label, score, maxScore = 10 }: ScoreBarProps) {
14
+ const percentage = Math.max(0, Math.min(100, (score / maxScore) * 100));
15
+
16
+ return (
17
+ <div className="bk-score-bar">
18
+ <div className="bk-score-bar__head">
19
+ <span className="bk-score-bar__label">{label}</span>
20
+ <span className="bk-score-bar__score">{score}</span>
21
+ </div>
22
+ <div className="bk-score-bar__track">
23
+ <div className="bk-score-bar__fill" style={{ width: `${percentage}%` }} />
24
+ </div>
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,59 @@
1
+ /* blog-kit React adapter — SerpPreview
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-serp-preview).
3
+ A mock search-engine results page: a rounded search bar showing the query,
4
+ followed by a stack of result rows (URL breadcrumb, blue heading, snippet),
5
+ with an optional "Sponsored" tag for ad results. Ported from hjd's b-serp
6
+ (cyan/dark mapped to neutral --blog-* tokens; the Google-blue link colour
7
+ maps to --blog-info). */
8
+ import { ICONS } from "../core/icons";
9
+
10
+ export interface SerpResult {
11
+ /** Result title (the blue clickable heading). Plain text. */
12
+ title: string;
13
+ /** URL breadcrumb line. HTML allowed — wrap the domain in <strong>. */
14
+ url: string;
15
+ /** Snippet/description. HTML allowed — <em> bolds the matched terms. */
16
+ description: string;
17
+ /** When true, the row is flagged with a "Sponsored" tag. */
18
+ ad?: boolean;
19
+ }
20
+
21
+ export interface SerpPreviewProps {
22
+ /** The text shown in the search bar. */
23
+ query: string;
24
+ /** The list of result rows. */
25
+ results?: SerpResult[];
26
+ }
27
+
28
+ export function SerpPreview({ query, results = [] }: SerpPreviewProps) {
29
+ return (
30
+ <div className="bk-serp-preview">
31
+ <div className="bk-serp-preview__bar">
32
+ <span
33
+ className="bk-serp-preview__bar-icon"
34
+ aria-hidden="true"
35
+ dangerouslySetInnerHTML={{ __html: ICONS["search"] }}
36
+ />
37
+ <input className="bk-serp-preview__input" defaultValue={query} readOnly />
38
+ </div>
39
+ <div className="bk-serp-preview__results">
40
+ {results.map((r, i) => (
41
+ <div key={i} className="bk-serp-preview__result">
42
+ {r.ad && (
43
+ <span className="bk-serp-preview__tag bk-serp-preview__tag--ad">Sponsored</span>
44
+ )}
45
+ <span
46
+ className="bk-serp-preview__url"
47
+ dangerouslySetInnerHTML={{ __html: r.url }}
48
+ />
49
+ <h4 className="bk-serp-preview__heading">{r.title}</h4>
50
+ <p
51
+ className="bk-serp-preview__desc"
52
+ dangerouslySetInnerHTML={{ __html: r.description }}
53
+ />
54
+ </div>
55
+ ))}
56
+ </div>
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,43 @@
1
+ /* blog-kit React adapter — ServicePromoCard
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-service-promo-card).
3
+ A linked promo row: an icon tile, a title + optional sub-line, and a
4
+ brand-coloured uppercase arrow label. The whole card is a single link.
5
+ Ported from hjd ServicePromoCard (.b-svc). Cyan accents → --blog-brand
6
+ and the brand tint/soft tokens; greys → ink/mute tokens. */
7
+ import { ICONS } from "../core/icons";
8
+
9
+ export interface ServicePromoCardProps {
10
+ /** Title line, rendered bold uppercase. */
11
+ title: string;
12
+ /** Optional sub-line below the title. */
13
+ sub?: string;
14
+ /** Link target for the whole card. */
15
+ href?: string;
16
+ /** Optional raw SVG string for the icon tile; defaults to a search glyph. */
17
+ icon?: string;
18
+ /** Brand-coloured arrow label on the right. */
19
+ arrowLabel?: string;
20
+ }
21
+
22
+ export function ServicePromoCard({
23
+ title,
24
+ sub,
25
+ href = "#",
26
+ icon,
27
+ arrowLabel = "Learn more →",
28
+ }: ServicePromoCardProps) {
29
+ return (
30
+ <a className="bk-service-promo-card" href={href}>
31
+ <span
32
+ className="bk-service-promo-card__icon"
33
+ aria-hidden="true"
34
+ dangerouslySetInnerHTML={{ __html: icon || ICONS["search"] }}
35
+ />
36
+ <span className="bk-service-promo-card__body">
37
+ <span className="bk-service-promo-card__title">{title}</span>
38
+ {sub && <span className="bk-service-promo-card__sub">{sub}</span>}
39
+ </span>
40
+ <span className="bk-service-promo-card__arrow">{arrowLabel}</span>
41
+ </a>
42
+ );
43
+ }
@@ -0,0 +1,62 @@
1
+ /* blog-kit React adapter — StatCard
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-stat-card).
3
+ A single highlighted metric — big brand-coloured number (+ optional unit),
4
+ an uppercase label, optional description, and an optional dashed-divider
5
+ "SOURCE" attribution row (links out when sourceUrl is set). */
6
+ import { ICONS } from "../core/icons";
7
+
8
+ export interface StatCardProps {
9
+ value: string;
10
+ label: string;
11
+ description?: string;
12
+ /** Optional unit rendered slightly smaller after the value, e.g. "×" or "s". */
13
+ unit?: string;
14
+ /** Optional source attribution, shown below in a dashed-divider row. */
15
+ source?: string;
16
+ /** Optional URL — when set alongside `source`, the row becomes a link. */
17
+ sourceUrl?: string;
18
+ }
19
+
20
+ /** Split a raw value like "3.2×" into its numeric head and trailing unit. */
21
+ function splitValue(raw: string): { num: string; unit: string } {
22
+ const m = raw.match(/^([\d.,\-+]+)(.*)$/);
23
+ if (!m) return { num: raw, unit: "" };
24
+ return { num: m[1], unit: m[2].trim() };
25
+ }
26
+
27
+ export function StatCard({ value, label, description, unit, source, sourceUrl }: StatCardProps) {
28
+ const parts = unit !== undefined ? { num: value, unit } : splitValue(value);
29
+
30
+ return (
31
+ <div className="bk-stat-card">
32
+ <div className="bk-stat-card__value">
33
+ <span className="bk-stat-card__num">{parts.num}</span>
34
+ {parts.unit && <span className="bk-stat-card__unit">{parts.unit}</span>}
35
+ </div>
36
+ <div className="bk-stat-card__label">{label}</div>
37
+ {description && <div className="bk-stat-card__desc">{description}</div>}
38
+ {source &&
39
+ (sourceUrl ? (
40
+ <a
41
+ className="bk-stat-card__source bk-stat-card__source--link"
42
+ href={sourceUrl}
43
+ target="_blank"
44
+ rel="nofollow noopener noreferrer"
45
+ >
46
+ <span className="bk-stat-card__source-kicker">Source</span>
47
+ {source}
48
+ <span
49
+ className="bk-stat-card__source-icon"
50
+ aria-hidden="true"
51
+ dangerouslySetInnerHTML={{ __html: ICONS["external"] }}
52
+ />
53
+ </a>
54
+ ) : (
55
+ <div className="bk-stat-card__source">
56
+ <span className="bk-stat-card__source-kicker">Source</span>
57
+ {source}
58
+ </div>
59
+ ))}
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from "react";
2
+ export interface StepBlockProps { stepNumber?: number | string; title?: string; children?: ReactNode; }
3
+ export function StepBlock({ stepNumber, title, children }: StepBlockProps) {
4
+ return (<div className="bk-step"><div className="bk-step__num">{stepNumber}</div><div className="bk-step__main">{title ? <h3 className="bk-step__title">{title}</h3> : null}<div className="bk-step__body">{children}</div></div></div>);
5
+ }
@@ -0,0 +1,27 @@
1
+ export interface TableOfContentsProps {
2
+ /** Heading rendered above the list. Defaults to "In this article". */
3
+ heading?: string;
4
+ /** CSS selector for the element whose <h2>s become entries. Defaults to "article". */
5
+ scope?: string;
6
+ }
7
+
8
+ /**
9
+ * Sticky-friendly "in this article" navigation. The list is built at runtime
10
+ * by the toc behaviour, which scans the article for <h2> headings, numbers
11
+ * them, smooth-scrolls on click, and runs a scrollspy that flags the heading
12
+ * in view. The container hides itself when the article has no <h2>s.
13
+ */
14
+ export function TableOfContents({ heading, scope }: TableOfContentsProps) {
15
+ const label = heading ?? "In this article";
16
+ return (
17
+ <nav
18
+ className="bk-toc"
19
+ data-bk-toc
20
+ data-bk-toc-scope={scope ?? "article"}
21
+ aria-label={label}
22
+ >
23
+ <p className="bk-toc__heading">{label}</p>
24
+ <ul className="bk-toc__list" />
25
+ </nav>
26
+ );
27
+ }
@@ -0,0 +1,35 @@
1
+ /* blog-kit React adapter — TimelineBlock
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-timeline).
3
+ Emits the SAME markup/classes as the Astro adapter. */
4
+ import type { ReactNode } from "react";
5
+
6
+ export interface TimelineEvent {
7
+ date: string;
8
+ title: string;
9
+ description: string;
10
+ }
11
+
12
+ export interface TimelineBlockProps {
13
+ /** Chronological events, oldest → newest. */
14
+ events?: TimelineEvent[];
15
+ children?: ReactNode;
16
+ }
17
+
18
+ /** Vertical timeline / recap — dated milestones connected by a rail. */
19
+ export function TimelineBlock({ events = [], children }: TimelineBlockProps) {
20
+ if (!events.length) return children ? <div className="bk-timeline">{children}</div> : null;
21
+
22
+ return (
23
+ <div className="bk-timeline">
24
+ <span className="bk-timeline__rail" aria-hidden="true" />
25
+ {events.map((event, i) => (
26
+ <div className="bk-timeline__item" key={i}>
27
+ <span className="bk-timeline__dot" aria-hidden="true" />
28
+ <div className="bk-timeline__date">{event.date}</div>
29
+ <h4 className="bk-timeline__title">{event.title}</h4>
30
+ <p className="bk-timeline__desc">{event.description}</p>
31
+ </div>
32
+ ))}
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,16 @@
1
+ export interface TipBoxProps {
2
+ tip: string;
3
+ context?: string;
4
+ }
5
+
6
+ export function TipBox({ tip, context }: TipBoxProps) {
7
+ return (
8
+ <div className="bk-tip">
9
+ <span className="bk-tip__badge">Pro tip</span>
10
+ <p className="bk-tip__body">
11
+ <strong className="bk-tip__lead">{tip}</strong>
12
+ {context ? <span className="bk-tip__context"> {context}</span> : null}
13
+ </p>
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,79 @@
1
+ /* blog-kit React adapter — TrafficChart
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-traffic-chart).
3
+ A compact SVG line chart for a metric over time — an uppercase title + a
4
+ two-item legend (this period / previous period), then an inline <svg> with
5
+ two axis lines, a solid brand line for the current series (with point
6
+ markers), a dashed neutral line for the previous series, and mono-font
7
+ x-axis labels. Colours come entirely from .bk-traffic-chart CSS classes
8
+ (no literal colours in markup); only geometry is computed inline.
9
+ Ported from hjd-website's blog TrafficChart (cyan #10C8F0 → --blog-brand,
10
+ greys → the mute/border tokens). */
11
+
12
+ export interface TrafficChartSeries {
13
+ /** Current-period values; rendered as the solid brand line + markers. */
14
+ current?: number[];
15
+ /** Previous-period values; rendered as the dashed neutral line. */
16
+ previous?: number[];
17
+ }
18
+
19
+ export interface TrafficChartProps {
20
+ /** Uppercase heading (defaults to "Organic sessions · 6 months"). */
21
+ title?: string;
22
+ /** The two comparable series. */
23
+ series?: TrafficChartSeries;
24
+ /** X-axis tick labels, one per data point. */
25
+ labels?: string[];
26
+ }
27
+
28
+ const W = 600;
29
+ const H = 220;
30
+ const PAD = 40;
31
+
32
+ export function TrafficChart({
33
+ title = "Organic sessions · 6 months",
34
+ series = {},
35
+ labels = [],
36
+ }: TrafficChartProps) {
37
+ const current = series.current || [];
38
+ const previous = series.previous || [];
39
+ const max = Math.max(...current, ...previous, 1);
40
+ const xs = (i: number, len: number) =>
41
+ PAD + 20 + ((W - PAD - 30) * i) / Math.max(len - 1, 1);
42
+ const ys = (v: number) => H - 30 - ((H - 50) * v) / max;
43
+ const points = (arr: number[]) =>
44
+ arr.map((v, i) => `${xs(i, arr.length)},${ys(v)}`).join(" ");
45
+
46
+ return (
47
+ <div className="bk-traffic-chart">
48
+ <div className="bk-traffic-chart__head">
49
+ <div className="bk-traffic-chart__title">{title}</div>
50
+ <div className="bk-traffic-chart__legend">
51
+ <span className="bk-traffic-chart__item">
52
+ <span className="bk-traffic-chart__dot" />This period
53
+ </span>
54
+ <span className="bk-traffic-chart__item">
55
+ <span className="bk-traffic-chart__dot bk-traffic-chart__dot--prev" />Previous period
56
+ </span>
57
+ </div>
58
+ </div>
59
+ <svg className="bk-traffic-chart__svg" viewBox={`0 0 ${W} ${H}`} role="img">
60
+ <line className="bk-traffic-chart__axis" x1={PAD} y1={20} x2={PAD} y2={H - 30} />
61
+ <line className="bk-traffic-chart__axis" x1={PAD} y1={H - 30} x2={W - 10} y2={H - 30} />
62
+ {previous.length > 0 && (
63
+ <polyline className="bk-traffic-chart__line bk-traffic-chart__line--prev" points={points(previous)} />
64
+ )}
65
+ {current.length > 0 && (
66
+ <polyline className="bk-traffic-chart__line" points={points(current)} />
67
+ )}
68
+ {current.map((v, i) => (
69
+ <circle key={i} className="bk-traffic-chart__pt" cx={xs(i, current.length)} cy={ys(v)} r={4} />
70
+ ))}
71
+ <g className="bk-traffic-chart__labels">
72
+ {labels.map((l, i) => (
73
+ <text key={i} x={xs(i, labels.length) - 10} y={H - 10}>{l}</text>
74
+ ))}
75
+ </g>
76
+ </svg>
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,60 @@
1
+ /* blog-kit React adapter — VerdictCard
2
+ Thin wrapper: all styling lives in core/css/components.css (.bk-verdict-card).
3
+ A dark, brand-bordered "verdict" panel: an uppercase kicker + optional /10
4
+ score in the header, then an optional winner pill, a big verdict headline,
5
+ a summary line, and optional bullet chips. */
6
+ import type { ReactNode } from "react";
7
+
8
+ export interface VerdictCardProps {
9
+ verdict: string;
10
+ score?: number;
11
+ summary: string;
12
+ /** Short uppercase kicker above the verdict. Defaults to "The verdict". */
13
+ kicker?: string;
14
+ /** Optional bullet chips shown under the summary. */
15
+ bullets?: string[];
16
+ /** Legacy — renders as a leading pill if supplied. */
17
+ winner?: string;
18
+ children?: ReactNode;
19
+ }
20
+
21
+ export function VerdictCard({
22
+ verdict,
23
+ score,
24
+ summary,
25
+ kicker = "The verdict",
26
+ bullets,
27
+ winner,
28
+ }: VerdictCardProps) {
29
+ return (
30
+ <div className="bk-verdict-card">
31
+ <div className="bk-verdict-card__header">
32
+ <span className="bk-verdict-card__kicker">{kicker}</span>
33
+ {score !== undefined && (
34
+ <span className="bk-verdict-card__score">
35
+ {score}
36
+ <span className="bk-verdict-card__score-max">/10</span>
37
+ </span>
38
+ )}
39
+ </div>
40
+ <div className="bk-verdict-card__body">
41
+ {winner && (
42
+ <div className="bk-verdict-card__winner">
43
+ Winner: <span className="bk-verdict-card__winner-name">{winner}</span>
44
+ </div>
45
+ )}
46
+ <h3 className="bk-verdict-card__verdict">{verdict}</h3>
47
+ <p className="bk-verdict-card__summary">{summary}</p>
48
+ {bullets && bullets.length > 0 && (
49
+ <div className="bk-verdict-card__bullets">
50
+ {bullets.map((b, i) => (
51
+ <span key={i} className="bk-verdict-card__chip">
52
+ {b}
53
+ </span>
54
+ ))}
55
+ </div>
56
+ )}
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,122 @@
1
+ /* ============================================================
2
+ blog-kit React component MAPS — Next-free.
3
+ ------------------------------------------------------------
4
+ The presentational React components are framework-neutral, but
5
+ the sibling `react/index.ts` barrel ALSO re-exports
6
+ <BlogBehaviors>, which imports `next/navigation`. Evaluating that
7
+ barrel therefore pulls Next, so it cannot be imported under raw
8
+ Node / Astro SSR (no Next).
9
+
10
+ This module re-exports ONLY the component MAPS (no BlogBehaviors),
11
+ so the runtime-MDX render path (Next OR Astro) can import the map
12
+ without dragging Next in. `react/index.ts` re-exports these maps so
13
+ the maps stay single-source — edit the imports here, not in two
14
+ places.
15
+ ============================================================ */
16
+ import { AdPreview } from "./AdPreview";
17
+ import { AdPreviewPair } from "./AdPreviewPair";
18
+ import { AuditFindings } from "./AuditFindings";
19
+ import { AuditScores } from "./AuditScores";
20
+ import { AuthorCard } from "./AuthorCard";
21
+ import { BeforeAfter } from "./BeforeAfter";
22
+ import { CTABanner } from "./CTABanner";
23
+ import { CalloutBox } from "./CalloutBox";
24
+ import { CaseStudyHero } from "./CaseStudyHero";
25
+ import { ChannelMixBars } from "./ChannelMixBars";
26
+ import { Checklist } from "./Checklist";
27
+ import { ChecklistItem } from "./ChecklistItem";
28
+ import { CodeSnippet } from "./CodeSnippet";
29
+ import { ComparisonTable } from "./ComparisonTable";
30
+ import { Definition } from "./Definition";
31
+ import { DeliveryComparison } from "./DeliveryComparison";
32
+ import { FAQList } from "./FAQList";
33
+ import { FurtherReading } from "./FurtherReading";
34
+ import { ImageFeature } from "./ImageFeature";
35
+ import { Infographic } from "./Infographic";
36
+ import { KeyMetric } from "./KeyMetric";
37
+ import { KeywordTable } from "./KeywordTable";
38
+ import { List } from "./List";
39
+ import { MetricHighlight } from "./MetricHighlight";
40
+ import { NewsletterCTA } from "./NewsletterCTA";
41
+ import { NumberedCard } from "./NumberedCard";
42
+ import { ProConBlock } from "./ProConBlock";
43
+ import { ProseList } from "./ProseList";
44
+ import { QuoteBlock } from "./QuoteBlock";
45
+ import { RegionCallout } from "./RegionCallout";
46
+ import { RelatedPosts } from "./RelatedPosts";
47
+ import { ResultsStrip } from "./ResultsStrip";
48
+ import { ScoreBar } from "./ScoreBar";
49
+ import { SerpPreview } from "./SerpPreview";
50
+ import { ServicePromoCard } from "./ServicePromoCard";
51
+ import { StatCard } from "./StatCard";
52
+ import { StepBlock } from "./StepBlock";
53
+ import { TableOfContents } from "./TableOfContents";
54
+ import { TimelineBlock } from "./TimelineBlock";
55
+ import { TipBox } from "./TipBox";
56
+ import { TrafficChart } from "./TrafficChart";
57
+ import { VerdictCard } from "./VerdictCard";
58
+
59
+ export const kitComponents = {
60
+ AdPreview,
61
+ AdPreviewPair,
62
+ AuditFindings,
63
+ AuditScores,
64
+ AuthorCard,
65
+ BeforeAfter,
66
+ CTABanner,
67
+ CalloutBox,
68
+ CaseStudyHero,
69
+ ChannelMixBars,
70
+ Checklist,
71
+ ChecklistItem,
72
+ CodeSnippet,
73
+ ComparisonTable,
74
+ Definition,
75
+ DeliveryComparison,
76
+ FAQList,
77
+ FurtherReading,
78
+ ImageFeature,
79
+ Infographic,
80
+ KeyMetric,
81
+ KeywordTable,
82
+ List,
83
+ MetricHighlight,
84
+ NewsletterCTA,
85
+ NumberedCard,
86
+ ProConBlock,
87
+ ProseList,
88
+ QuoteBlock,
89
+ RegionCallout,
90
+ RelatedPosts,
91
+ ResultsStrip,
92
+ ScoreBar,
93
+ SerpPreview,
94
+ ServicePromoCard,
95
+ StatCard,
96
+ StepBlock,
97
+ TableOfContents,
98
+ TimelineBlock,
99
+ TipBox,
100
+ TrafficChart,
101
+ VerdictCard,
102
+ };
103
+
104
+ /** Old MDX names kept renderable during migration → canonical component. */
105
+ export const aliasComponents: Record<string, unknown> = {
106
+ AuditTable: AuditFindings,
107
+ CaseStudy: CaseStudyHero,
108
+ CaseStudyBanner: CaseStudyHero,
109
+ FindingsList: AuditFindings,
110
+ GoogleResult: SerpPreview,
111
+ IssueList: AuditFindings,
112
+ LineChart: TrafficChart,
113
+ OfficeGrid: RegionCallout,
114
+ ProsCons: ProConBlock,
115
+ PullQuote: QuoteBlock,
116
+ RegionContacts: RegionCallout,
117
+ SearchPreview: SerpPreview,
118
+ TimelineRecap: TimelineBlock,
119
+ TrendChart: TrafficChart,
120
+ };
121
+
122
+ export const blogComponents = { ...kitComponents, ...aliasComponents };
package/react/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /* blog-kit React adapter — barrel (generated by assembler).
2
+ blogComponents is the map passed to <MDXRemote components={...} />.
3
+ Per-site custom components merge on top in the site's /blog route.
4
+
5
+ The component MAPS now live in ./components-map (Next-free) so the
6
+ Astro runtime-MDX render path can import the map WITHOUT pulling
7
+ <BlogBehaviors> (which imports next/navigation). This barrel keeps
8
+ its full public surface — maps + BlogBehaviors — for the Next path. */
9
+ export { kitComponents, aliasComponents, blogComponents } from "./components-map";
10
+
11
+ /** Client-side activator for interactive components — render once inside
12
+ the post route's `.blog-root`. See react/BlogBehaviors.tsx. */
13
+ export { BlogBehaviors } from "./BlogBehaviors";
@@ -0,0 +1,32 @@
1
+ // blog-kit on-demand revalidation route. Scaffolded by `npx @highjumpdigitalsoftware/blog-kit`.
2
+ // High Jump Digital POSTs here on publish to bust the ISR cache instantly
3
+ // (otherwise the ISR timer picks the new post up within `revalidate` seconds).
4
+ //
5
+ // Auth: a shared secret in BLOGKIT_REVALIDATE_SECRET. The caller sends it as
6
+ // the `x-blogkit-secret` header. Without the secret set, the route refuses
7
+ // (so an un-provisioned site can't be cache-poisoned).
8
+ import { NextRequest, NextResponse } from "next/server";
9
+ import { revalidatePath, revalidateTag } from "next/cache";
10
+
11
+ export async function POST(req: NextRequest) {
12
+ const secret = process.env.BLOGKIT_REVALIDATE_SECRET;
13
+ if (!secret) {
14
+ return NextResponse.json({ ok: false, error: "not_configured" }, { status: 503 });
15
+ }
16
+ if (req.headers.get("x-blogkit-secret") !== secret) {
17
+ return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 });
18
+ }
19
+
20
+ let body: { slug?: string } = {};
21
+ try {
22
+ body = await req.json();
23
+ } catch {
24
+ /* empty body = revalidate everything */
25
+ }
26
+
27
+ revalidateTag("blogkit");
28
+ revalidatePath("/blog");
29
+ if (body.slug) revalidatePath(`/blog/${body.slug}`);
30
+
31
+ return NextResponse.json({ ok: true, revalidated: body.slug ?? "all" });
32
+ }
@@ -0,0 +1,41 @@
1
+ // blog-kit article route (/blog/[slug]) — ISR. Scaffolded by `npx @highjumpdigitalsoftware/blog-kit`.
2
+ import "@highjumpdigitalsoftware/blog-kit/styles.css";
3
+ import { notFound } from "next/navigation";
4
+ import { Article, getArticle, createSource, resolveConfig } from "@highjumpdigitalsoftware/blog-kit";
5
+ import rawConfig from "../../blogkit.config";
6
+
7
+ const config = resolveConfig(rawConfig);
8
+
9
+ // Static HTML per slug, revalidated in the background. New posts appear
10
+ // without a redeploy: dynamicParams lets unknown slugs render on demand.
11
+ export const revalidate = 60;
12
+ export const dynamicParams = true;
13
+
14
+ export async function generateStaticParams() {
15
+ const source = createSource(config);
16
+ const slugs = await source.listSlugs();
17
+ return slugs.map((slug) => ({ slug }));
18
+ }
19
+
20
+ export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
21
+ const { slug } = await params;
22
+ const post = await getArticle(config, slug);
23
+ if (!post) return { title: "Not found" };
24
+ return {
25
+ title: `${post.title} · ${config.brand.name}`,
26
+ description: post.description,
27
+ };
28
+ }
29
+
30
+ export default async function ArticlePage({ params }: { params: Promise<{ slug: string }> }) {
31
+ const { slug } = await params;
32
+ const post = await getArticle(config, slug);
33
+ if (!post) notFound();
34
+ return (
35
+ <main style={{ maxWidth: 900, margin: "0 auto", padding: "120px 20px 80px" }}>
36
+ <h1 style={{ marginBottom: 24 }}>{post.title}</h1>
37
+ {/* @ts-expect-error async server component */}
38
+ <Article config={config} slug={slug} />
39
+ </main>
40
+ );
41
+ }
@@ -0,0 +1,18 @@
1
+ // blog-kit listing route (/blog) — ISR. Scaffolded by `npx @highjumpdigitalsoftware/blog-kit`.
2
+ import "@highjumpdigitalsoftware/blog-kit/styles.css";
3
+ import { BlogIndex, resolveConfig } from "@highjumpdigitalsoftware/blog-kit";
4
+ import rawConfig from "../blogkit.config";
5
+
6
+ const config = resolveConfig(rawConfig);
7
+
8
+ // Static HTML, revalidated in the background — SEO-correct, no redeploy.
9
+ export const revalidate = 60;
10
+
11
+ export const metadata = {
12
+ title: `Blog · ${config.brand.name}`,
13
+ };
14
+
15
+ export default async function BlogIndexPage() {
16
+ // @ts-expect-error async server component
17
+ return <BlogIndex config={config} page={1} />;
18
+ }