@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,77 @@
1
+ {
2
+ "$comment": "Content-type templates surfaced by the MCP's list_templates. Templates are suggestions — any components can be mixed freely. Phase 1 reconciles this against the MCP's current TEMPLATE_SUMMARIES; seeded here as the single source of truth.",
3
+ "templates": [
4
+ {
5
+ "name": "informational",
6
+ "description": "Explainer / thought-leadership piece that teaches a concept.",
7
+ "structure": "Intro → TableOfContents → sections → CalloutBox/TipBox asides → FAQList → conclusion CTA",
8
+ "suggestedComponents": [
9
+ "TableOfContents",
10
+ "CalloutBox",
11
+ "TipBox",
12
+ "Definition",
13
+ "Infographic",
14
+ "FAQList",
15
+ "CTABanner"
16
+ ]
17
+ },
18
+ {
19
+ "name": "comparison",
20
+ "description": "Head-to-head of two or more options.",
21
+ "structure": "Intro → ComparisonTable → ProConBlock per option → verdict → FAQList",
22
+ "suggestedComponents": [
23
+ "ComparisonTable",
24
+ "ProConBlock",
25
+ "QuoteBlock",
26
+ "CalloutBox",
27
+ "FAQList",
28
+ "CTABanner"
29
+ ]
30
+ },
31
+ {
32
+ "name": "how-to",
33
+ "description": "Step-by-step guide to accomplish a task.",
34
+ "structure": "Intro → what you'll need → StepBlock sequence → TipBox → FAQList",
35
+ "suggestedComponents": [
36
+ "StepBlock",
37
+ "TipBox",
38
+ "CalloutBox",
39
+ "CodeSnippet",
40
+ "Checklist",
41
+ "FAQList"
42
+ ]
43
+ },
44
+ {
45
+ "name": "listicle",
46
+ "description": "Numbered/curated list of items, tools, or tactics.",
47
+ "structure": "Intro → numbered items → per-item detail → summary → FAQList",
48
+ "suggestedComponents": [
49
+ "NumberedCard",
50
+ "List",
51
+ "CalloutBox",
52
+ "ImageFeature",
53
+ "FAQList",
54
+ "CTABanner"
55
+ ]
56
+ },
57
+ {
58
+ "name": "case-study",
59
+ "description": "Client/result story with metrics.",
60
+ "structure": "Hero → results strip → narrative → metrics → quote → CTA",
61
+ "suggestedComponents": [
62
+ "MetricHighlight",
63
+ "StatCard",
64
+ "BeforeAfter",
65
+ "QuoteBlock",
66
+ "ProConBlock",
67
+ "CTABanner"
68
+ ]
69
+ },
70
+ {
71
+ "name": "freestyle",
72
+ "description": "No fixed structure — mix any components as the topic demands.",
73
+ "structure": "Author's choice.",
74
+ "suggestedComponents": []
75
+ }
76
+ ]
77
+ }
@@ -0,0 +1,78 @@
1
+ /* ============================================================
2
+ blog-kit — CodeSnippet behaviour
3
+ Copy-to-clipboard for code blocks. Each block's copy button
4
+ reads the exact rendered text from .bk-code__code, writes it to
5
+ the clipboard, and flips its label to "Copied" for 1.5s. Falls
6
+ back to a hidden textarea + execCommand where the async Clipboard
7
+ API is unavailable.
8
+
9
+ Framework-neutral ESM: exports initCode() (run it after each
10
+ client navigation) and also self-runs once on load for plain
11
+ <script> usage. Idempotent: a block is wired at most once.
12
+ ============================================================ */
13
+ var RESET_MS = 1500;
14
+
15
+ function writeClipboard(text) {
16
+ if (navigator.clipboard && navigator.clipboard.writeText) {
17
+ return navigator.clipboard.writeText(text);
18
+ }
19
+ return new Promise(function (resolve, reject) {
20
+ try {
21
+ var ta = document.createElement("textarea");
22
+ ta.value = text;
23
+ ta.setAttribute("readonly", "");
24
+ ta.style.position = "absolute";
25
+ ta.style.left = "-9999px";
26
+ document.body.appendChild(ta);
27
+ ta.select();
28
+ document.execCommand("copy");
29
+ document.body.removeChild(ta);
30
+ resolve();
31
+ } catch (err) {
32
+ reject(err);
33
+ }
34
+ });
35
+ }
36
+
37
+ function enhance(root) {
38
+ if (root.getAttribute("data-bk-code") === "ready") return;
39
+ root.setAttribute("data-bk-code", "ready");
40
+
41
+ var btn = root.querySelector("[data-bk-code-copy]");
42
+ var code = root.querySelector(".bk-code__code");
43
+ if (!btn || !code) return;
44
+
45
+ var labelEl = btn.querySelector(".bk-code__copy-label");
46
+ var timer = null;
47
+
48
+ btn.addEventListener("click", function () {
49
+ writeClipboard(code.textContent || "").then(
50
+ function () {
51
+ if (labelEl) labelEl.textContent = "Copied";
52
+ btn.setAttribute("data-copied", "true");
53
+ if (timer) clearTimeout(timer);
54
+ timer = setTimeout(function () {
55
+ if (labelEl) labelEl.textContent = "Copy";
56
+ btn.removeAttribute("data-copied");
57
+ }, RESET_MS);
58
+ },
59
+ function () {
60
+ /* clipboard unavailable — silently ignore */
61
+ }
62
+ );
63
+ });
64
+ }
65
+
66
+ export function initCode() {
67
+ if (typeof document === "undefined") return;
68
+ var blocks = document.querySelectorAll("[data-bk-code]");
69
+ for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
70
+ }
71
+
72
+ if (typeof document !== "undefined") {
73
+ if (document.readyState === "loading") {
74
+ document.addEventListener("DOMContentLoaded", initCode);
75
+ } else {
76
+ initCode();
77
+ }
78
+ }
@@ -0,0 +1,52 @@
1
+ /* ============================================================
2
+ blog-kit — ComparisonTable behaviour
3
+ The desktop comparison table can overflow its container on
4
+ narrow viewports. Native overflow-x scrolls fine but gives no
5
+ visual cue that more columns exist (the hidden-horizontal-
6
+ scroll trap). This script toggles edge-fade hint classes on the
7
+ wrap depending on whether the inner scroller can scroll left or
8
+ right, updating on scroll and resize.
9
+
10
+ Framework-neutral ESM: exports initComparison() (run it after
11
+ each client navigation) and also self-runs once on load for
12
+ plain <script> usage. Idempotent: a block is wired at most once.
13
+ ============================================================ */
14
+ function update(wrap, scroller) {
15
+ var max = scroller.scrollWidth - scroller.clientWidth;
16
+ var x = scroller.scrollLeft;
17
+ var canScroll = max > 1;
18
+ wrap.classList.toggle("bk-comparison__table-wrap--hint-right", canScroll && x < max - 1);
19
+ wrap.classList.toggle("bk-comparison__table-wrap--hint-left", canScroll && x > 1);
20
+ }
21
+
22
+ function enhance(wrap) {
23
+ if (wrap.getAttribute("data-bk-comparison") === "ready") return;
24
+ wrap.setAttribute("data-bk-comparison", "ready");
25
+ var scroller = wrap.querySelector(".bk-comparison__scroll");
26
+ if (!scroller) return;
27
+ var sync = function () {
28
+ update(wrap, scroller);
29
+ };
30
+ scroller.addEventListener("scroll", sync, { passive: true });
31
+ if (typeof window.ResizeObserver === "function") {
32
+ var ro = new ResizeObserver(sync);
33
+ ro.observe(scroller);
34
+ } else {
35
+ window.addEventListener("resize", sync);
36
+ }
37
+ sync();
38
+ }
39
+
40
+ export function initComparison() {
41
+ if (typeof document === "undefined") return;
42
+ var blocks = document.querySelectorAll("[data-bk-comparison]");
43
+ for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
44
+ }
45
+
46
+ if (typeof document !== "undefined") {
47
+ if (document.readyState === "loading") {
48
+ document.addEventListener("DOMContentLoaded", initComparison);
49
+ } else {
50
+ initComparison();
51
+ }
52
+ }
@@ -0,0 +1,52 @@
1
+ /* ============================================================
2
+ blog-kit — DeliveryComparison behaviour
3
+ The N-column comparison matrix can overflow its container on
4
+ narrow viewports. Native overflow-x scrolls fine but gives no
5
+ visual cue that more columns exist (the hidden-horizontal-scroll
6
+ trap). This script toggles edge-fade hint classes on the wrap
7
+ depending on whether the inner scroller can scroll left or right,
8
+ updating on scroll and resize.
9
+
10
+ Framework-neutral ESM: exports initDeliveryComparison() (run it
11
+ after each client navigation) and also self-runs once on load for
12
+ plain <script> usage. Idempotent: a block is wired at most once.
13
+ ============================================================ */
14
+ function update(wrap, scroller) {
15
+ var max = scroller.scrollWidth - scroller.clientWidth;
16
+ var x = scroller.scrollLeft;
17
+ var canScroll = max > 1;
18
+ wrap.classList.toggle("bk-delivery-comparison--hint-right", canScroll && x < max - 1);
19
+ wrap.classList.toggle("bk-delivery-comparison--hint-left", canScroll && x > 1);
20
+ }
21
+
22
+ function enhance(wrap) {
23
+ if (wrap.getAttribute("data-bk-delivery-comparison") === "ready") return;
24
+ wrap.setAttribute("data-bk-delivery-comparison", "ready");
25
+ var scroller = wrap.querySelector(".bk-delivery-comparison__scroll");
26
+ if (!scroller) return;
27
+ var sync = function () {
28
+ update(wrap, scroller);
29
+ };
30
+ scroller.addEventListener("scroll", sync, { passive: true });
31
+ if (typeof window.ResizeObserver === "function") {
32
+ var ro = new ResizeObserver(sync);
33
+ ro.observe(scroller);
34
+ } else {
35
+ window.addEventListener("resize", sync);
36
+ }
37
+ sync();
38
+ }
39
+
40
+ export function initDeliveryComparison() {
41
+ if (typeof document === "undefined") return;
42
+ var blocks = document.querySelectorAll("[data-bk-delivery-comparison]");
43
+ for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
44
+ }
45
+
46
+ if (typeof document !== "undefined") {
47
+ if (document.readyState === "loading") {
48
+ document.addEventListener("DOMContentLoaded", initDeliveryComparison);
49
+ } else {
50
+ initDeliveryComparison();
51
+ }
52
+ }
@@ -0,0 +1,61 @@
1
+ /* ============================================================
2
+ blog-kit — FAQList behaviour
3
+ Native <details> handles open/close on its own. This script's
4
+ only job is SEO: lift every rendered Q&A pair into FAQPage
5
+ JSON-LD so search engines can surface it.
6
+
7
+ Framework-neutral ESM: exports initFaq() (run it after each
8
+ client navigation) and also self-runs once on load for plain
9
+ <script> usage. Idempotent: a block is processed at most once.
10
+ ============================================================ */
11
+ function clean(s) {
12
+ return String(s || "").replace(/\s+/g, " ").trim();
13
+ }
14
+
15
+ function buildSchema(root) {
16
+ var items = root.querySelectorAll(".bk-faq__item");
17
+ var entities = [];
18
+ for (var i = 0; i < items.length; i++) {
19
+ var q = items[i].querySelector(".bk-faq__question");
20
+ var a = items[i].querySelector(".bk-faq__answer");
21
+ var name = clean(q && q.textContent);
22
+ var text = clean(a && a.textContent);
23
+ if (!name) continue;
24
+ entities.push({
25
+ "@type": "Question",
26
+ name: name,
27
+ acceptedAnswer: { "@type": "Answer", text: text },
28
+ });
29
+ }
30
+ if (!entities.length) return null;
31
+ return {
32
+ "@context": "https://schema.org",
33
+ "@type": "FAQPage",
34
+ mainEntity: entities,
35
+ };
36
+ }
37
+
38
+ function enhance(root) {
39
+ if (root.getAttribute("data-bk-faq") === "ready") return;
40
+ root.setAttribute("data-bk-faq", "ready");
41
+ var schema = buildSchema(root);
42
+ if (!schema) return;
43
+ var tag = document.createElement("script");
44
+ tag.type = "application/ld+json";
45
+ tag.textContent = JSON.stringify(schema);
46
+ root.appendChild(tag);
47
+ }
48
+
49
+ export function initFaq() {
50
+ if (typeof document === "undefined") return;
51
+ var blocks = document.querySelectorAll("[data-bk-faq]");
52
+ for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
53
+ }
54
+
55
+ if (typeof document !== "undefined") {
56
+ if (document.readyState === "loading") {
57
+ document.addEventListener("DOMContentLoaded", initFaq);
58
+ } else {
59
+ initFaq();
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ /** Ambient types for the vanilla-JS behaviours module (copied verbatim to
2
+ * dist by copy-assets.mjs; not compiled by tsc). */
3
+ export function initBlogBehaviors(): void;
@@ -0,0 +1,35 @@
1
+ /* ============================================================
2
+ blog-kit — behaviours barrel
3
+ One entry point that wires every interactive component. Importing
4
+ this module also self-runs each behaviour once on load (see each
5
+ file's guarded auto-run), so a plain `<script type="module">` that
6
+ imports it is enough for a static (Astro) site. For SPA frameworks
7
+ call initBlogBehaviors() after each client-side navigation so newly
8
+ mounted markup gets enhanced — each enhance step is idempotent, so
9
+ re-running is safe and only touches not-yet-wired elements.
10
+ ============================================================ */
11
+ import { initToc } from "./toc.js";
12
+ import { initFaq } from "./faq.js";
13
+ import { initComparison } from "./comparison.js";
14
+ import { initCode } from "./code.js";
15
+ import { initKeywordTable } from "./keyword-table.js";
16
+ import { initDeliveryComparison } from "./delivery-comparison.js";
17
+
18
+ export {
19
+ initToc,
20
+ initFaq,
21
+ initComparison,
22
+ initCode,
23
+ initKeywordTable,
24
+ initDeliveryComparison,
25
+ };
26
+
27
+ /** Run every blog-kit behaviour. Idempotent; safe to call on each navigation. */
28
+ export function initBlogBehaviors() {
29
+ initToc();
30
+ initFaq();
31
+ initComparison();
32
+ initCode();
33
+ initKeywordTable();
34
+ initDeliveryComparison();
35
+ }
@@ -0,0 +1,52 @@
1
+ /* ============================================================
2
+ blog-kit — KeywordTable behaviour
3
+ A six-column SEO keyword-ranking table overflows its container
4
+ on narrow viewports. Native overflow-x scrolls fine but gives no
5
+ visual cue that more columns exist (the hidden-horizontal-scroll
6
+ trap). This script toggles edge-fade hint classes on the wrap
7
+ depending on whether the inner scroller can scroll left or right,
8
+ updating on scroll and resize.
9
+
10
+ Framework-neutral ESM: exports initKeywordTable() (run it after
11
+ each client navigation) and also self-runs once on load for
12
+ plain <script> usage. Idempotent: a block is wired at most once.
13
+ ============================================================ */
14
+ function update(wrap, scroller) {
15
+ var max = scroller.scrollWidth - scroller.clientWidth;
16
+ var x = scroller.scrollLeft;
17
+ var canScroll = max > 1;
18
+ wrap.classList.toggle("bk-keyword-table--hint-right", canScroll && x < max - 1);
19
+ wrap.classList.toggle("bk-keyword-table--hint-left", canScroll && x > 1);
20
+ }
21
+
22
+ function enhance(wrap) {
23
+ if (wrap.getAttribute("data-bk-keyword-table") === "ready") return;
24
+ wrap.setAttribute("data-bk-keyword-table", "ready");
25
+ var scroller = wrap.querySelector(".bk-keyword-table__scroll");
26
+ if (!scroller) return;
27
+ var sync = function () {
28
+ update(wrap, scroller);
29
+ };
30
+ scroller.addEventListener("scroll", sync, { passive: true });
31
+ if (typeof window.ResizeObserver === "function") {
32
+ var ro = new ResizeObserver(sync);
33
+ ro.observe(scroller);
34
+ } else {
35
+ window.addEventListener("resize", sync);
36
+ }
37
+ sync();
38
+ }
39
+
40
+ export function initKeywordTable() {
41
+ if (typeof document === "undefined") return;
42
+ var blocks = document.querySelectorAll("[data-bk-keyword-table]");
43
+ for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
44
+ }
45
+
46
+ if (typeof document !== "undefined") {
47
+ if (document.readyState === "loading") {
48
+ document.addEventListener("DOMContentLoaded", initKeywordTable);
49
+ } else {
50
+ initKeywordTable();
51
+ }
52
+ }
@@ -0,0 +1,130 @@
1
+ /* ============================================================
2
+ blog-kit — TableOfContents behaviour
3
+ The TOC is built from the live document, not authored markup:
4
+ it scans the article for <h2> headings (sub-headings inside MDX
5
+ components are not document structure and are skipped), renders
6
+ one numbered entry per heading, smooth-scrolls on click, and
7
+ runs a scrollspy (IntersectionObserver) that flags the heading
8
+ currently in view with the --active modifier. If the scope has
9
+ no <h2>s the container hides itself.
10
+
11
+ Scope resolution (centralised so no site has to wrap its body in
12
+ a particular tag): an explicit data-bk-toc-scope selector wins if
13
+ it matches; otherwise the TOC scans the prose region that
14
+ contains it (.bk-prose, then .blog-root), falling back to body.
15
+
16
+ Framework-neutral ESM: exports initToc() (run it after each
17
+ client navigation) and also self-runs once on load for plain
18
+ <script> usage. Idempotent: a block is wired at most once.
19
+ ============================================================ */
20
+ function pad2(n) {
21
+ return n < 10 ? "0" + n : String(n);
22
+ }
23
+
24
+ function resolveScope(root) {
25
+ var sel = root.getAttribute("data-bk-toc-scope");
26
+ if (sel) {
27
+ var bySel = document.querySelector(sel);
28
+ if (bySel) return bySel;
29
+ }
30
+ return (
31
+ root.closest(".bk-prose") ||
32
+ root.closest(".blog-root") ||
33
+ document.body
34
+ );
35
+ }
36
+
37
+ function build(root) {
38
+ var scope = resolveScope(root);
39
+ if (!scope) {
40
+ root.hidden = true;
41
+ return;
42
+ }
43
+
44
+ // h2 only — sub-headings inside MDX components (h3/h4 from cards,
45
+ // CTAs, etc.) are not document structure and shouldn't be entries.
46
+ var headings = scope.querySelectorAll("h2");
47
+ if (!headings.length) {
48
+ root.hidden = true;
49
+ return;
50
+ }
51
+ root.hidden = false;
52
+
53
+ var list = root.querySelector(".bk-toc__list");
54
+ if (!list) return;
55
+ list.textContent = "";
56
+
57
+ var links = [];
58
+ var byId = {};
59
+
60
+ Array.prototype.forEach.call(headings, function (el, i) {
61
+ var id = el.id;
62
+ var text = el.textContent || "";
63
+
64
+ var li = document.createElement("li");
65
+ li.className = "bk-toc__item";
66
+
67
+ var num = document.createElement("span");
68
+ num.className = "bk-toc__num";
69
+ num.textContent = pad2(i + 1);
70
+
71
+ var a = document.createElement("a");
72
+ a.className = "bk-toc__link";
73
+ a.href = id ? "#" + id : "#";
74
+ a.textContent = text;
75
+ a.addEventListener("click", function (e) {
76
+ e.preventDefault();
77
+ var target = id ? document.getElementById(id) : null;
78
+ if (target) target.scrollIntoView({ behavior: "smooth" });
79
+ });
80
+
81
+ li.appendChild(num);
82
+ li.appendChild(a);
83
+ list.appendChild(li);
84
+
85
+ links.push(a);
86
+ if (id) byId[id] = a;
87
+ });
88
+
89
+ function setActive(id) {
90
+ for (var i = 0; i < links.length; i++) {
91
+ links[i].classList.remove("bk-toc__link--active");
92
+ }
93
+ var hit = byId[id];
94
+ if (hit) hit.classList.add("bk-toc__link--active");
95
+ }
96
+
97
+ if (typeof window.IntersectionObserver === "function") {
98
+ var observer = new IntersectionObserver(
99
+ function (entries) {
100
+ for (var i = 0; i < entries.length; i++) {
101
+ if (entries[i].isIntersecting) setActive(entries[i].target.id);
102
+ }
103
+ },
104
+ { rootMargin: "-80px 0px -60% 0px" }
105
+ );
106
+ Array.prototype.forEach.call(headings, function (el) {
107
+ observer.observe(el);
108
+ });
109
+ }
110
+ }
111
+
112
+ function enhance(root) {
113
+ if (root.getAttribute("data-bk-toc") === "ready") return;
114
+ root.setAttribute("data-bk-toc", "ready");
115
+ build(root);
116
+ }
117
+
118
+ export function initToc() {
119
+ if (typeof document === "undefined") return;
120
+ var blocks = document.querySelectorAll("[data-bk-toc]");
121
+ for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
122
+ }
123
+
124
+ if (typeof document !== "undefined") {
125
+ if (document.readyState === "loading") {
126
+ document.addEventListener("DOMContentLoaded", initToc);
127
+ } else {
128
+ initToc();
129
+ }
130
+ }
@@ -0,0 +1,146 @@
1
+ /* ============================================================
2
+ blog-kit — Base layer (scoped reset + prose typography)
3
+ ------------------------------------------------------------
4
+ Everything is scoped under .blog-root. Two jobs:
5
+ 1. LEAK-OUT: we never emit a bare element selector, so the
6
+ blog can't touch the host page.
7
+ 2. LEAK-IN: we re-declare the inherited properties (font,
8
+ colour, line-height, etc.) explicitly on .blog-root so a
9
+ host site's body/global styles don't cascade into us.
10
+ The ONLY thing we intentionally let through is the --blog-*
11
+ token layer (the tenant's "paint").
12
+
13
+ IMPORTANT — specificity: every element rule below is wrapped in
14
+ :where(.blog-root) so it has NEAR-ZERO specificity (just the
15
+ element, 0,0,1). That guarantees component classes (.bk-*, 0,1,0)
16
+ ALWAYS win over these prose defaults. Without :where, a rule like
17
+ `.blog-root h3` (0,1,1) would out-specify a component's
18
+ `.bk-verdict-card__verdict` (0,1,0) and, e.g., paint a dark-panel
19
+ heading near-black → invisible. Base is a fallback; components win.
20
+ ============================================================ */
21
+
22
+ .blog-root {
23
+ /* Re-assert inherited properties so the host can't bleed in */
24
+ font-family: var(--blog-font-body);
25
+ font-size: 1rem;
26
+ line-height: 1.7;
27
+ font-weight: 400;
28
+ letter-spacing: normal;
29
+ color: var(--blog-text);
30
+ text-align: left;
31
+ -webkit-font-smoothing: antialiased;
32
+ text-rendering: optimizeLegibility;
33
+ }
34
+
35
+ :where(.blog-root) *,
36
+ :where(.blog-root) *::before,
37
+ :where(.blog-root) *::after {
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ /* --- Headings --- */
42
+ :where(.blog-root) h1,
43
+ :where(.blog-root) h2,
44
+ :where(.blog-root) h3,
45
+ :where(.blog-root) h4,
46
+ :where(.blog-root) h5,
47
+ :where(.blog-root) h6 {
48
+ font-family: var(--blog-font-display);
49
+ font-weight: var(--blog-heading-weight);
50
+ text-transform: var(--blog-heading-transform);
51
+ letter-spacing: var(--blog-tracking-tight);
52
+ color: var(--blog-ink);
53
+ line-height: 1.22;
54
+ margin: 0 0 var(--blog-sp-4);
55
+ }
56
+ :where(.blog-root) h1 { font-size: clamp(2rem, 1.5rem + 2.2vw, 2.85rem); margin-bottom: var(--blog-sp-5); }
57
+ :where(.blog-root) h2 { font-size: clamp(1.5rem, 1.25rem + 1.1vw, 1.95rem); margin-top: var(--blog-sp-12); }
58
+ :where(.blog-root) h3 { font-size: clamp(1.2rem, 1.1rem + 0.5vw, 1.4rem); margin-top: var(--blog-sp-8); }
59
+ :where(.blog-root) h4 { font-size: 1.1rem; margin-top: var(--blog-sp-6); }
60
+ :where(.blog-root) h5,
61
+ :where(.blog-root) h6 { font-size: 1rem; margin-top: var(--blog-sp-5); }
62
+
63
+ /* First heading shouldn't push a big top margin */
64
+ :where(.blog-root) > :first-child { margin-top: 0; }
65
+
66
+ /* --- Body copy --- */
67
+ :where(.blog-root) p {
68
+ margin: 0 0 var(--blog-sp-5);
69
+ color: var(--blog-text);
70
+ }
71
+ :where(.blog-root) strong, :where(.blog-root) b { font-weight: 700; color: var(--blog-ink); }
72
+ :where(.blog-root) em, :where(.blog-root) i { font-style: italic; }
73
+ :where(.blog-root) small { font-size: 0.85em; color: var(--blog-mute-2); }
74
+
75
+ /* --- Links --- */
76
+ :where(.blog-root) a {
77
+ color: var(--blog-brand);
78
+ text-decoration: none;
79
+ }
80
+ :where(.blog-root) a:hover { text-decoration: underline; }
81
+
82
+ /* --- Lists (prose defaults; component lists opt out via .bk-*) --- */
83
+ :where(.blog-root) ul,
84
+ :where(.blog-root) ol {
85
+ margin: 0 0 var(--blog-sp-5);
86
+ padding-left: 1.4em;
87
+ }
88
+ :where(.blog-root) li { margin: 0 0 var(--blog-sp-2); }
89
+ :where(.blog-root) li::marker { color: var(--blog-mute-2); }
90
+
91
+ /* --- Inline code --- */
92
+ :where(.blog-root) :not(pre) > code {
93
+ font-family: var(--blog-font-mono);
94
+ font-size: 0.88em;
95
+ background: var(--blog-bg-tertiary);
96
+ border: 1px solid var(--blog-border-subtle);
97
+ border-radius: var(--blog-radius-xs);
98
+ padding: 0.12em 0.36em;
99
+ color: var(--blog-ink);
100
+ }
101
+
102
+ /* --- Media --- */
103
+ :where(.blog-root) img,
104
+ :where(.blog-root) video,
105
+ :where(.blog-root) svg {
106
+ max-width: 100%;
107
+ height: auto;
108
+ }
109
+ :where(.blog-root) img { border-radius: var(--blog-radius-md); }
110
+
111
+ /* --- Blockquote (prose; PullQuote/QuoteBlock are components) --- */
112
+ :where(.blog-root) blockquote {
113
+ margin: var(--blog-sp-6) 0;
114
+ padding: var(--blog-sp-2) 0 var(--blog-sp-2) var(--blog-sp-5);
115
+ border-left: 3px solid var(--blog-brand);
116
+ color: var(--blog-mute);
117
+ font-style: italic;
118
+ }
119
+
120
+ /* --- Horizontal rule --- */
121
+ :where(.blog-root) hr {
122
+ border: 0;
123
+ border-top: 1px solid var(--blog-border-light);
124
+ margin: var(--blog-sp-10) 0;
125
+ }
126
+
127
+ /* --- Tables (prose; ComparisonTable is a component) --- */
128
+ :where(.blog-root) table {
129
+ width: 100%;
130
+ border-collapse: collapse;
131
+ margin: var(--blog-sp-6) 0;
132
+ font-size: 0.95rem;
133
+ }
134
+ :where(.blog-root) th,
135
+ :where(.blog-root) td {
136
+ text-align: left;
137
+ padding: var(--blog-sp-3) var(--blog-sp-4);
138
+ border-bottom: 1px solid var(--blog-border-light);
139
+ }
140
+ :where(.blog-root) th { color: var(--blog-ink); font-weight: 600; }
141
+
142
+ /* --- The article measure --- */
143
+ :where(.blog-root) .bk-prose {
144
+ max-width: var(--blog-max-content);
145
+ margin-inline: auto;
146
+ }