@omega.js/web 0.1.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 (1162) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +640 -0
  3. package/bin/omega +2 -0
  4. package/bin/omega-web +2 -0
  5. package/bin/omg +2 -0
  6. package/core/_includes/admin/sections/sidebar.json +100 -0
  7. package/core/_includes/admin/sections/topbar.json +11 -0
  8. package/core/_includes/backend/sections/sidebar.json +39 -0
  9. package/core/_includes/backend/sections/topbar.json +16 -0
  10. package/core/_includes/core/body.html +102 -0
  11. package/core/_includes/core/foot.html +536 -0
  12. package/core/_includes/core/head.html +423 -0
  13. package/core/_includes/core/pricing/price-per-unit.html +33 -0
  14. package/core/_includes/core/showcase/entry-body.html +94 -0
  15. package/core/_includes/core/showcase/index-body.html +43 -0
  16. package/core/_includes/core/showcase/nav.html +60 -0
  17. package/core/_includes/frontend/sections/footer.json +74 -0
  18. package/core/_includes/frontend/sections/nav.json +70 -0
  19. package/core/_includes/global/sections/account.json +31 -0
  20. package/core/_includes/modules/engagement/giscus.html +26 -0
  21. package/core/_layouts/blueprint/404.html +10 -0
  22. package/core/_layouts/blueprint/about.html +10 -0
  23. package/core/_layouts/blueprint/account/index.html +11 -0
  24. package/core/_layouts/blueprint/admin/calendar/index.html +590 -0
  25. package/core/_layouts/blueprint/admin/dashboard/index.html +301 -0
  26. package/core/_layouts/blueprint/admin/firebase/index.html +214 -0
  27. package/core/_layouts/blueprint/admin/posts/editor.html +157 -0
  28. package/core/_layouts/blueprint/admin/posts/index.html +126 -0
  29. package/core/_layouts/blueprint/admin/stackblitz/index.html +30 -0
  30. package/core/_layouts/blueprint/admin/studio/index.html +85 -0
  31. package/core/_layouts/blueprint/admin/users/index.html +272 -0
  32. package/core/_layouts/blueprint/admin/users/new.html +82 -0
  33. package/core/_layouts/blueprint/admin/verts/index.html +204 -0
  34. package/core/_layouts/blueprint/alternatives/alternative.html +15 -0
  35. package/core/_layouts/blueprint/alternatives/index.html +10 -0
  36. package/core/_layouts/blueprint/app.html +10 -0
  37. package/core/_layouts/blueprint/auth/reset.html +19 -0
  38. package/core/_layouts/blueprint/auth/signin.html +19 -0
  39. package/core/_layouts/blueprint/auth/signup.html +19 -0
  40. package/core/_layouts/blueprint/auth/token.html +19 -0
  41. package/core/_layouts/blueprint/blog/categories/category.html +8 -0
  42. package/core/_layouts/blueprint/blog/categories/index.html +11 -0
  43. package/core/_layouts/blueprint/blog/index.html +10 -0
  44. package/core/_layouts/blueprint/blog/post.html +5 -0
  45. package/core/_layouts/blueprint/blog/tags/index.html +11 -0
  46. package/core/_layouts/blueprint/blog/tags/tag.html +8 -0
  47. package/core/_layouts/blueprint/careers.md +13 -0
  48. package/core/_layouts/blueprint/collection/category.html +9 -0
  49. package/core/_layouts/blueprint/collection/document.html +9 -0
  50. package/core/_layouts/blueprint/collection/index.html +9 -0
  51. package/core/_layouts/blueprint/connections/callback.html +11 -0
  52. package/core/_layouts/blueprint/contact.html +14 -0
  53. package/core/_layouts/blueprint/dashboard/index.html +10 -0
  54. package/core/_layouts/blueprint/download.html +20 -0
  55. package/core/_layouts/blueprint/extension/index.html +14 -0
  56. package/core/_layouts/blueprint/extension/installed.html +11 -0
  57. package/core/_layouts/blueprint/feedback.html +10 -0
  58. package/core/_layouts/blueprint/index.html +16 -0
  59. package/core/_layouts/blueprint/legal/cookies.md +57 -0
  60. package/core/_layouts/blueprint/legal/privacy.md +109 -0
  61. package/core/_layouts/blueprint/legal/terms.md +155 -0
  62. package/core/_layouts/blueprint/payment/checkout.html +15 -0
  63. package/core/_layouts/blueprint/payment/confirmation.html +14 -0
  64. package/core/_layouts/blueprint/portal/email-preferences.html +15 -0
  65. package/core/_layouts/blueprint/pricing.html +20 -0
  66. package/core/_layouts/blueprint/status.html +10 -0
  67. package/core/_layouts/blueprint/team/index.html +10 -0
  68. package/core/_layouts/blueprint/team/member.html +5 -0
  69. package/core/_layouts/blueprint/updates/index.html +10 -0
  70. package/core/_layouts/blueprint/updates/update.html +10 -0
  71. package/core/_layouts/core/root.html +45 -0
  72. package/core/_layouts/modules/utilities/redirect.html +129 -0
  73. package/core/css/_studio.scss +237 -0
  74. package/core/css/components/_index.scss +146 -0
  75. package/core/css/core/_.scss +0 -0
  76. package/core/css/core/_alert.scss +87 -0
  77. package/core/css/core/_animations.scss +343 -0
  78. package/core/css/core/_bindings.scss +116 -0
  79. package/core/css/core/_blog-search.scss +57 -0
  80. package/core/css/core/_chatsy.scss +61 -0
  81. package/core/css/core/_consent.scss +424 -0
  82. package/core/css/core/_custom-font-awesome.scss +146 -0
  83. package/core/css/core/_exit-popup.scss +42 -0
  84. package/core/css/core/_generated-badge.scss +33 -0
  85. package/core/css/core/_initialize.scss +29 -0
  86. package/core/css/core/_lazy-loading.scss +104 -0
  87. package/core/css/core/_social-sharing.scss +41 -0
  88. package/core/css/core/_tones.scss +32 -0
  89. package/core/css/core/_utilities.scss +157 -0
  90. package/core/css/main.scss +54 -0
  91. package/core/css/motion/_index.scss +368 -0
  92. package/core/css/pages/404/index.scss +0 -0
  93. package/core/css/pages/admin/calendar/index.scss +692 -0
  94. package/core/css/pages/admin/firebase/index.scss +33 -0
  95. package/core/css/pages/admin/index.scss +9 -0
  96. package/core/css/pages/admin/users/index.scss +9 -0
  97. package/core/css/pages/admin/verts/index.scss +9 -0
  98. package/core/css/pages/alternatives/[alternative]/index.scss +0 -0
  99. package/core/css/pages/blog/[slug].scss +55 -0
  100. package/core/css/pages/dashboard/account/index.scss +242 -0
  101. package/core/css/pages/download/index.scss +480 -0
  102. package/core/css/pages/extension/installed/index.scss +317 -0
  103. package/core/css/pages/feedback/index.scss +31 -0
  104. package/core/css/pages/payment/confirmation/index.scss +14 -0
  105. package/core/css/pages/pricing/index.scss +0 -0
  106. package/core/css/pages/status/index.scss +162 -0
  107. package/core/css/pages/test/libraries/layers/index.scss +28 -0
  108. package/core/css/pages/updates/[update].scss +46 -0
  109. package/core/css/pages/updates/index.scss +13 -0
  110. package/core/css/shell/_index.scss +207 -0
  111. package/core/css/tokens/_index.scss +166 -0
  112. package/core/icons/flags/ar.svg +1 -0
  113. package/core/icons/flags/bd.svg +1 -0
  114. package/core/icons/flags/ca.svg +1 -0
  115. package/core/icons/flags/ch.svg +1 -0
  116. package/core/icons/flags/cn.svg +1 -0
  117. package/core/icons/flags/co.svg +1 -0
  118. package/core/icons/flags/cw.svg +1 -0
  119. package/core/icons/flags/cz.svg +1 -0
  120. package/core/icons/flags/de.svg +1 -0
  121. package/core/icons/flags/dk.svg +1 -0
  122. package/core/icons/flags/eg.svg +1 -0
  123. package/core/icons/flags/es.svg +1 -0
  124. package/core/icons/flags/fi.svg +1 -0
  125. package/core/icons/flags/fr.svg +1 -0
  126. package/core/icons/flags/gb.svg +1 -0
  127. package/core/icons/flags/gq.svg +1 -0
  128. package/core/icons/flags/gr.svg +1 -0
  129. package/core/icons/flags/gt.svg +1 -0
  130. package/core/icons/flags/hn.svg +1 -0
  131. package/core/icons/flags/id.svg +1 -0
  132. package/core/icons/flags/ie.svg +1 -0
  133. package/core/icons/flags/in.svg +1 -0
  134. package/core/icons/flags/ir.svg +1 -0
  135. package/core/icons/flags/it.svg +1 -0
  136. package/core/icons/flags/jp.svg +1 -0
  137. package/core/icons/flags/kp.svg +1 -0
  138. package/core/icons/flags/kr.svg +1 -0
  139. package/core/icons/flags/ly.svg +1 -0
  140. package/core/icons/flags/ma.svg +1 -0
  141. package/core/icons/flags/mu.svg +1 -0
  142. package/core/icons/flags/mx.svg +1 -0
  143. package/core/icons/flags/ng.svg +1 -0
  144. package/core/icons/flags/pa.svg +1 -0
  145. package/core/icons/flags/ph.svg +1 -0
  146. package/core/icons/flags/pk.svg +1 -0
  147. package/core/icons/flags/pt.svg +1 -0
  148. package/core/icons/flags/ru.svg +1 -0
  149. package/core/icons/flags/sa.svg +1 -0
  150. package/core/icons/flags/sd.svg +1 -0
  151. package/core/icons/flags/se.svg +1 -0
  152. package/core/icons/flags/th.svg +1 -0
  153. package/core/icons/flags/tm.svg +1 -0
  154. package/core/icons/flags/tr.svg +1 -0
  155. package/core/icons/flags/us.svg +1 -0
  156. package/core/icons/flags/uy.svg +1 -0
  157. package/core/icons/flags/vn.svg +1 -0
  158. package/core/icons/flags/za.svg +1 -0
  159. package/core/images/exit-popup/subscriber-1.jpg +0 -0
  160. package/core/images/exit-popup/subscriber-2.jpg +0 -0
  161. package/core/images/exit-popup/subscriber-3.jpg +0 -0
  162. package/core/images/exit-popup/subscriber-4.jpg +0 -0
  163. package/core/images/placeholder/photo-1.jpg +0 -0
  164. package/core/images/placeholder/photo-2.jpg +0 -0
  165. package/core/images/placeholder/photo-3.jpg +0 -0
  166. package/core/js/core/analytics-loader.js +253 -0
  167. package/core/js/core/app-shell.js +136 -0
  168. package/core/js/core/appearance.js +195 -0
  169. package/core/js/core/auth.js +457 -0
  170. package/core/js/core/complete.js +72 -0
  171. package/core/js/core/consent.js +384 -0
  172. package/core/js/core/dev-icon-audit.js +63 -0
  173. package/core/js/core/dev-palette.js +670 -0
  174. package/core/js/core/dev-sections.js +53 -0
  175. package/core/js/core/exit-popup.js +276 -0
  176. package/core/js/core/initialize.js +8 -0
  177. package/core/js/core/language-switcher.js +167 -0
  178. package/core/js/core/lazy-loading.js +479 -0
  179. package/core/js/core/motion.js +28 -0
  180. package/core/js/core/query-strings.js +146 -0
  181. package/core/js/core/service-worker.js +28 -0
  182. package/core/js/core/social-sharing.js +293 -0
  183. package/core/js/first-paint.js +35 -0
  184. package/core/js/layouts/modules/utilities/redirect.js +122 -0
  185. package/core/js/libs/admin-helpers.js +62 -0
  186. package/core/js/libs/alert-dismiss.js +35 -0
  187. package/core/js/libs/analytics.js +368 -0
  188. package/core/js/libs/auth/email.js +144 -0
  189. package/core/js/libs/auth/errors.js +119 -0
  190. package/core/js/libs/auth/forms.js +206 -0
  191. package/core/js/libs/auth/index.js +181 -0
  192. package/core/js/libs/auth/oauth.js +394 -0
  193. package/core/js/libs/auth/orphan.js +110 -0
  194. package/core/js/libs/auth/password-toggle.js +44 -0
  195. package/core/js/libs/auth/session-params.js +266 -0
  196. package/core/js/libs/auth/tracking.js +41 -0
  197. package/core/js/libs/charts.js +516 -0
  198. package/core/js/libs/consent-region.js +127 -0
  199. package/core/js/libs/dev.js +150 -0
  200. package/core/js/libs/graph.js +201 -0
  201. package/core/js/libs/initialize-tooltips.js +31 -0
  202. package/core/js/libs/logger.js +38 -0
  203. package/core/js/libs/omega-copy.js +89 -0
  204. package/core/js/libs/path-prefix.js +44 -0
  205. package/core/js/libs/payment-config.js +76 -0
  206. package/core/js/libs/recaptcha.js +94 -0
  207. package/core/js/libs/sale-name.js +295 -0
  208. package/core/js/libs/showcase-frames.js +37 -0
  209. package/core/js/libs/srcset.js +69 -0
  210. package/core/js/libs/tracking-consent.js +161 -0
  211. package/core/js/main.js +144 -0
  212. package/core/js/pages/404/index.js +57 -0
  213. package/core/js/pages/admin/calendar/calendar-core.js +701 -0
  214. package/core/js/pages/admin/calendar/calendar-events.js +853 -0
  215. package/core/js/pages/admin/calendar/calendar-renderer.js +807 -0
  216. package/core/js/pages/admin/calendar/campaign-preview.js +105 -0
  217. package/core/js/pages/admin/calendar/index.js +40 -0
  218. package/core/js/pages/admin/firebase/index.js +727 -0
  219. package/core/js/pages/admin/index.js +531 -0
  220. package/core/js/pages/admin/posts/editor/index.js +230 -0
  221. package/core/js/pages/admin/posts/index.js +237 -0
  222. package/core/js/pages/admin/studio/index.js +538 -0
  223. package/core/js/pages/admin/users/index.js +669 -0
  224. package/core/js/pages/admin/users/new/index.js +65 -0
  225. package/core/js/pages/admin/verts/index.js +407 -0
  226. package/core/js/pages/alternatives/[alternative]/index.js +17 -0
  227. package/core/js/pages/app/index.js +135 -0
  228. package/core/js/pages/blog/[slug].js +132 -0
  229. package/core/js/pages/blog/_search.mjs +108 -0
  230. package/core/js/pages/blog/index.js +193 -0
  231. package/core/js/pages/blog/page/[num]/index.js +7 -0
  232. package/core/js/pages/connections/callback/index.js +148 -0
  233. package/core/js/pages/contact/index.js +170 -0
  234. package/core/js/pages/cookies.js +2 -0
  235. package/core/js/pages/dashboard/account/index.js +366 -0
  236. package/core/js/pages/dashboard/account/modules/orders.js +82 -0
  237. package/core/js/pages/dashboard/account/sections/api-keys.js +149 -0
  238. package/core/js/pages/dashboard/account/sections/billing.js +1667 -0
  239. package/core/js/pages/dashboard/account/sections/connections.js +333 -0
  240. package/core/js/pages/dashboard/account/sections/data-request.js +225 -0
  241. package/core/js/pages/dashboard/account/sections/delete.js +80 -0
  242. package/core/js/pages/dashboard/account/sections/notifications.js +167 -0
  243. package/core/js/pages/dashboard/account/sections/orders.js +151 -0
  244. package/core/js/pages/dashboard/account/sections/profile.js +200 -0
  245. package/core/js/pages/dashboard/account/sections/referrals.js +217 -0
  246. package/core/js/pages/dashboard/account/sections/refund.js +253 -0
  247. package/core/js/pages/dashboard/account/sections/security.js +726 -0
  248. package/core/js/pages/dashboard/account/sections/team.js +247 -0
  249. package/core/js/pages/download/index.js +418 -0
  250. package/core/js/pages/extension/index.js +164 -0
  251. package/core/js/pages/extension/installed/index.js +135 -0
  252. package/core/js/pages/feedback/index.js +193 -0
  253. package/core/js/pages/legal/_document.js +91 -0
  254. package/core/js/pages/payment/checkout/index.js +420 -0
  255. package/core/js/pages/payment/checkout/modules/api.js +139 -0
  256. package/core/js/pages/payment/checkout/modules/dev-section.js +175 -0
  257. package/core/js/pages/payment/checkout/modules/discount.js +69 -0
  258. package/core/js/pages/payment/checkout/modules/pricing.js +73 -0
  259. package/core/js/pages/payment/checkout/modules/state.js +339 -0
  260. package/core/js/pages/payment/checkout/modules/tracking.js +54 -0
  261. package/core/js/pages/payment/confirmation/index.js +91 -0
  262. package/core/js/pages/payment/confirmation/modules/celebration.js +45 -0
  263. package/core/js/pages/payment/confirmation/modules/state.js +112 -0
  264. package/core/js/pages/payment/confirmation/modules/tracking.js +133 -0
  265. package/core/js/pages/payment/confirmation/modules/verify.js +142 -0
  266. package/core/js/pages/portal/email-preferences/index.js +183 -0
  267. package/core/js/pages/pricing/index.js +425 -0
  268. package/core/js/pages/privacy.js +2 -0
  269. package/core/js/pages/reset/index.js +12 -0
  270. package/core/js/pages/signin/index.js +22 -0
  271. package/core/js/pages/signup/index.js +23 -0
  272. package/core/js/pages/status/index.js +687 -0
  273. package/core/js/pages/terms.js +2 -0
  274. package/core/js/pages/test/components/[category]/[name]/index.js +26 -0
  275. package/core/js/pages/test/libraries/appearance/index.js +220 -0
  276. package/core/js/pages/test/libraries/form-manager/index.js +328 -0
  277. package/core/js/pages/test/libraries/layers/index.js +16 -0
  278. package/core/js/pages/test/libraries/verts/index.js +68 -0
  279. package/core/js/pages/test/sections/[category]/[name]/index.js +28 -0
  280. package/core/js/pages/test/styleguide/index.js +35 -0
  281. package/core/js/pages/token/index.js +203 -0
  282. package/core/logos/brandmarks/original/apple-app-store.svg +1 -0
  283. package/core/logos/brandmarks/original/apple.svg +6 -0
  284. package/core/logos/brandmarks/original/bitbucket.svg +14 -0
  285. package/core/logos/brandmarks/original/bitcoin.svg +1 -0
  286. package/core/logos/brandmarks/original/bluesky.svg +7 -0
  287. package/core/logos/brandmarks/original/bootstrap.svg +43 -0
  288. package/core/logos/brandmarks/original/brave.svg +1 -0
  289. package/core/logos/brandmarks/original/chrome.svg +1 -0
  290. package/core/logos/brandmarks/original/discord.svg +1 -0
  291. package/core/logos/brandmarks/original/dropbox.svg +10 -0
  292. package/core/logos/brandmarks/original/edge.svg +1 -0
  293. package/core/logos/brandmarks/original/facebook-f.svg +1 -0
  294. package/core/logos/brandmarks/original/facebook.svg +8 -0
  295. package/core/logos/brandmarks/original/figma.svg +10 -0
  296. package/core/logos/brandmarks/original/firefox.svg +1 -0
  297. package/core/logos/brandmarks/original/github.svg +6 -0
  298. package/core/logos/brandmarks/original/gitlab.svg +17 -0
  299. package/core/logos/brandmarks/original/google-a-s.svg +1 -0
  300. package/core/logos/brandmarks/original/google-chrome-webstore.svg +49 -0
  301. package/core/logos/brandmarks/original/google-play-store.svg +1 -0
  302. package/core/logos/brandmarks/original/google-web-search.svg +1 -0
  303. package/core/logos/brandmarks/original/google.svg +1 -0
  304. package/core/logos/brandmarks/original/gulp.svg +4 -0
  305. package/core/logos/brandmarks/original/html-5.svg +1 -0
  306. package/core/logos/brandmarks/original/instagram.svg +1 -0
  307. package/core/logos/brandmarks/original/javascript.svg +1 -0
  308. package/core/logos/brandmarks/original/jira.svg +18 -0
  309. package/core/logos/brandmarks/original/kick.svg +7 -0
  310. package/core/logos/brandmarks/original/linkedin.svg +1 -0
  311. package/core/logos/brandmarks/original/linux.svg +28 -0
  312. package/core/logos/brandmarks/original/messenger.svg +16 -0
  313. package/core/logos/brandmarks/original/microsoft.svg +1 -0
  314. package/core/logos/brandmarks/original/npm.svg +8 -0
  315. package/core/logos/brandmarks/original/opera.svg +1 -0
  316. package/core/logos/brandmarks/original/patreon.svg +6 -0
  317. package/core/logos/brandmarks/original/pinterest.svg +6 -0
  318. package/core/logos/brandmarks/original/reddit.svg +1 -0
  319. package/core/logos/brandmarks/original/safari.svg +31 -0
  320. package/core/logos/brandmarks/original/sass.svg +3 -0
  321. package/core/logos/brandmarks/original/signal.svg +6 -0
  322. package/core/logos/brandmarks/original/skype.svg +47 -0
  323. package/core/logos/brandmarks/original/slack.svg +33 -0
  324. package/core/logos/brandmarks/original/soundcloud.svg +1 -0
  325. package/core/logos/brandmarks/original/spotify.svg +5 -0
  326. package/core/logos/brandmarks/original/steam.svg +6 -0
  327. package/core/logos/brandmarks/original/telegram.svg +14 -0
  328. package/core/logos/brandmarks/original/tiktok.svg +1 -0
  329. package/core/logos/brandmarks/original/trello.svg +16 -0
  330. package/core/logos/brandmarks/original/tumblr.svg +1 -0
  331. package/core/logos/brandmarks/original/twitch.svg +6 -0
  332. package/core/logos/brandmarks/original/twitter.svg +6 -0
  333. package/core/logos/brandmarks/original/unity.svg +6 -0
  334. package/core/logos/brandmarks/original/x.svg +7 -0
  335. package/core/logos/brandmarks/original/yahoo-y.svg +1 -0
  336. package/core/logos/brandmarks/original/youtube-square.svg +1 -0
  337. package/core/logos/brandmarks/original/youtube.svg +1 -0
  338. package/core/logos/combomarks/dark/airbnb.svg +30 -0
  339. package/core/logos/combomarks/dark/slack.svg +38 -0
  340. package/core/logos/combomarks/dark/spotify.svg +35 -0
  341. package/core/logos/combomarks/dark/twilio.svg +26 -0
  342. package/core/logos/combomarks/dark/weebly.svg +35 -0
  343. package/core/logos/combomarks/gray/airbnb.svg +33 -0
  344. package/core/logos/combomarks/gray/alphabet.svg +23 -0
  345. package/core/logos/combomarks/gray/amazon.svg +38 -0
  346. package/core/logos/combomarks/gray/boring-company.svg +91 -0
  347. package/core/logos/combomarks/gray/burberry.svg +239 -0
  348. package/core/logos/combomarks/gray/embark.svg +21 -0
  349. package/core/logos/combomarks/gray/fitbit.svg +61 -0
  350. package/core/logos/combomarks/gray/fred-perry.svg +94 -0
  351. package/core/logos/combomarks/gray/google.svg +27 -0
  352. package/core/logos/combomarks/gray/hollister.svg +34 -0
  353. package/core/logos/combomarks/gray/hubspot.svg +27 -0
  354. package/core/logos/combomarks/gray/kaplan.svg +33 -0
  355. package/core/logos/combomarks/gray/lenovo.svg +27 -0
  356. package/core/logos/combomarks/gray/levis.svg +25 -0
  357. package/core/logos/combomarks/gray/mapbox.svg +37 -0
  358. package/core/logos/combomarks/gray/netflix.svg +15 -0
  359. package/core/logos/combomarks/gray/new-balance.svg +32 -0
  360. package/core/logos/combomarks/gray/nike.svg +17 -0
  361. package/core/logos/combomarks/gray/paypal.svg +41 -0
  362. package/core/logos/combomarks/gray/puma.svg +82 -0
  363. package/core/logos/combomarks/gray/samsung.svg +26 -0
  364. package/core/logos/combomarks/gray/shopify.svg +36 -0
  365. package/core/logos/combomarks/gray/slack.svg +42 -0
  366. package/core/logos/combomarks/gray/spotify.svg +38 -0
  367. package/core/logos/combomarks/gray/stripe.svg +24 -0
  368. package/core/logos/combomarks/gray/tnf.svg +30 -0
  369. package/core/logos/combomarks/gray/twilio.svg +29 -0
  370. package/core/logos/combomarks/gray/uber.svg +24 -0
  371. package/core/logos/combomarks/gray/weebly.svg +38 -0
  372. package/core/logos/combomarks/original/airbnb.svg +33 -0
  373. package/core/logos/combomarks/original/alphabet.svg +23 -0
  374. package/core/logos/combomarks/original/amazon.svg +38 -0
  375. package/core/logos/combomarks/original/asana.svg +8 -0
  376. package/core/logos/combomarks/original/boring-company.svg +91 -0
  377. package/core/logos/combomarks/original/discord.svg +6 -0
  378. package/core/logos/combomarks/original/embark.svg +24 -0
  379. package/core/logos/combomarks/original/fitbit.svg +60 -0
  380. package/core/logos/combomarks/original/github.svg +6 -0
  381. package/core/logos/combomarks/original/gitlab.svg +20 -0
  382. package/core/logos/combomarks/original/google.svg +28 -0
  383. package/core/logos/combomarks/original/hubspot.svg +29 -0
  384. package/core/logos/combomarks/original/instagram.svg +7 -0
  385. package/core/logos/combomarks/original/kickstarter.svg +6 -0
  386. package/core/logos/combomarks/original/lenovo.svg +24 -0
  387. package/core/logos/combomarks/original/linkedin.svg +6 -0
  388. package/core/logos/combomarks/original/mapbox.svg +39 -0
  389. package/core/logos/combomarks/original/mastodon.svg +7 -0
  390. package/core/logos/combomarks/original/medium.svg +6 -0
  391. package/core/logos/combomarks/original/meta.svg +22 -0
  392. package/core/logos/combomarks/original/microsoft.svg +13 -0
  393. package/core/logos/combomarks/original/monday.svg +12 -0
  394. package/core/logos/combomarks/original/netflix.svg +15 -0
  395. package/core/logos/combomarks/original/notion.svg +9 -0
  396. package/core/logos/combomarks/original/paypal.svg +41 -0
  397. package/core/logos/combomarks/original/reddit.svg +13 -0
  398. package/core/logos/combomarks/original/samsung.svg +25 -0
  399. package/core/logos/combomarks/original/shopify.svg +35 -0
  400. package/core/logos/combomarks/original/slack.svg +47 -0
  401. package/core/logos/combomarks/original/spotify.svg +37 -0
  402. package/core/logos/combomarks/original/stackoverflow.svg +28 -0
  403. package/core/logos/combomarks/original/stripe.svg +24 -0
  404. package/core/logos/combomarks/original/tiktok.svg +12 -0
  405. package/core/logos/combomarks/original/tumblr.svg +6 -0
  406. package/core/logos/combomarks/original/uber.svg +24 -0
  407. package/core/logos/combomarks/original/weebly.svg +34 -0
  408. package/core/logos/combomarks/original/whatsapp.svg +20 -0
  409. package/core/logos/combomarks/original/youtube.svg +10 -0
  410. package/core/logos/combomarks/original/zoom.svg +7 -0
  411. package/core/logos/combomarks/white/airbnb.svg +33 -0
  412. package/core/logos/combomarks/white/alphabet.svg +23 -0
  413. package/core/logos/combomarks/white/amazon.svg +38 -0
  414. package/core/logos/combomarks/white/boring-company.svg +91 -0
  415. package/core/logos/combomarks/white/embark.svg +21 -0
  416. package/core/logos/combomarks/white/fitbit.svg +61 -0
  417. package/core/logos/combomarks/white/google.svg +27 -0
  418. package/core/logos/combomarks/white/hubspot.svg +27 -0
  419. package/core/logos/combomarks/white/kaplan.svg +33 -0
  420. package/core/logos/combomarks/white/lenovo.svg +27 -0
  421. package/core/logos/combomarks/white/mapbox.svg +37 -0
  422. package/core/logos/combomarks/white/netflix.svg +15 -0
  423. package/core/logos/combomarks/white/paypal.svg +40 -0
  424. package/core/logos/combomarks/white/samsung.svg +26 -0
  425. package/core/logos/combomarks/white/shopify.svg +36 -0
  426. package/core/logos/combomarks/white/slack.svg +42 -0
  427. package/core/logos/combomarks/white/spotify.svg +38 -0
  428. package/core/logos/combomarks/white/stripe.svg +24 -0
  429. package/core/logos/combomarks/white/twilio.svg +29 -0
  430. package/core/logos/combomarks/white/uber.svg +24 -0
  431. package/core/logos/combomarks/white/weebly.svg +38 -0
  432. package/defaults/pages/404.md +4 -0
  433. package/defaults/pages/about.md +4 -0
  434. package/defaults/pages/admin/calendar/index.html +4 -0
  435. package/defaults/pages/admin/firebase/index.html +4 -0
  436. package/defaults/pages/admin/index.md +4 -0
  437. package/defaults/pages/admin/posts/editor.html +4 -0
  438. package/defaults/pages/admin/posts/index.html +4 -0
  439. package/defaults/pages/admin/stackblitz/index.html +4 -0
  440. package/defaults/pages/admin/studio/index.html +4 -0
  441. package/defaults/pages/admin/users/index.html +4 -0
  442. package/defaults/pages/admin/users/new.html +4 -0
  443. package/defaults/pages/admin/verts/index.html +4 -0
  444. package/defaults/pages/ads-txt.html +11 -0
  445. package/defaults/pages/alternatives/index.md +4 -0
  446. package/defaults/pages/app.md +4 -0
  447. package/defaults/pages/auth/reset.md +4 -0
  448. package/defaults/pages/auth/signin.md +4 -0
  449. package/defaults/pages/auth/signup.md +4 -0
  450. package/defaults/pages/auth/token.md +4 -0
  451. package/defaults/pages/blog/categories/category.html +21 -0
  452. package/defaults/pages/blog/categories.md +4 -0
  453. package/defaults/pages/blog/index-json.html +27 -0
  454. package/defaults/pages/blog/tags/tag.html +21 -0
  455. package/defaults/pages/blog/tags.md +4 -0
  456. package/defaults/pages/blog.md +22 -0
  457. package/defaults/pages/careers.md +4 -0
  458. package/defaults/pages/connections/callback.md +4 -0
  459. package/defaults/pages/contact.md +4 -0
  460. package/defaults/pages/dashboard/account.md +4 -0
  461. package/defaults/pages/dashboard/index.md +12 -0
  462. package/defaults/pages/download.md +4 -0
  463. package/defaults/pages/extension/index.html +4 -0
  464. package/defaults/pages/extension/installed.html +4 -0
  465. package/defaults/pages/feedback.md +4 -0
  466. package/defaults/pages/feeds/posts-json.html +40 -0
  467. package/defaults/pages/feeds/posts-xml.html +38 -0
  468. package/defaults/pages/humans.html +25 -0
  469. package/defaults/pages/index.md +4 -0
  470. package/defaults/pages/legal/cookies.md +4 -0
  471. package/defaults/pages/legal/privacy.md +4 -0
  472. package/defaults/pages/legal/terms.md +4 -0
  473. package/defaults/pages/llms-txt.html +46 -0
  474. package/defaults/pages/opensearch.html +16 -0
  475. package/defaults/pages/pages-json.html +42 -0
  476. package/defaults/pages/payment/checkout.md +4 -0
  477. package/defaults/pages/payment/confirmation.md +4 -0
  478. package/defaults/pages/portal/email-preferences.md +4 -0
  479. package/defaults/pages/pricing.md +4 -0
  480. package/defaults/pages/redirects/account.md +12 -0
  481. package/defaults/pages/redirects/admin/dashboard.md +11 -0
  482. package/defaults/pages/redirects/auth/change-password.md +9 -0
  483. package/defaults/pages/redirects/auth/forgot.md +9 -0
  484. package/defaults/pages/redirects/auth/join.md +9 -0
  485. package/defaults/pages/redirects/auth/login.md +9 -0
  486. package/defaults/pages/redirects/auth/recover.md +9 -0
  487. package/defaults/pages/redirects/auth/register.md +9 -0
  488. package/defaults/pages/redirects/auth/reset-password.md +9 -0
  489. package/defaults/pages/redirects/billing/cancel.md +9 -0
  490. package/defaults/pages/redirects/billing/refund.md +9 -0
  491. package/defaults/pages/robots.html +36 -0
  492. package/defaults/pages/security-txt.html +11 -0
  493. package/defaults/pages/sitemap.html +37 -0
  494. package/defaults/pages/status.md +4 -0
  495. package/defaults/pages/team/index.md +4 -0
  496. package/defaults/pages/test/account/dashboard.html +16 -0
  497. package/defaults/pages/test/index.md +12 -0
  498. package/defaults/pages/test/libraries/appearance.html +268 -0
  499. package/defaults/pages/test/libraries/cover.html +50 -0
  500. package/defaults/pages/test/libraries/error.html +30 -0
  501. package/defaults/pages/test/libraries/firestore.html +202 -0
  502. package/defaults/pages/test/libraries/form-manager.html +374 -0
  503. package/defaults/pages/test/libraries/layers.html +52 -0
  504. package/defaults/pages/test/libraries/lazy-loading.html +450 -0
  505. package/defaults/pages/test/libraries/verts.html +149 -0
  506. package/defaults/pages/test/redirect/external.md +13 -0
  507. package/defaults/pages/test/redirect/internal.md +13 -0
  508. package/defaults/pages/test/styleguide/index.html +588 -0
  509. package/defaults/pages/test/translation/index.md +50 -0
  510. package/defaults/pages/updates/index.md +4 -0
  511. package/defaults/sample-posts/2026-01-12-welcome-to-the-blog.md +26 -0
  512. package/defaults/sample-posts/2026-02-03-a-field-guide-to-good-defaults.md +50 -0
  513. package/defaults/sample-posts/2026-02-24-shipping-week-notes.md +37 -0
  514. package/defaults/sample-posts/2026-03-17-the-case-for-one-config.md +36 -0
  515. package/defaults/sample-posts/2026-04-09-write-release-notes-people-read.md +34 -0
  516. package/defaults/sample-posts/2026-05-21-dark-mode-done-properly.md +38 -0
  517. package/defaults/sample-posts/2026-06-02-the-boring-deploy-is-the-good-deploy.md +30 -0
  518. package/defaults/sample-posts/2026-06-11-naming-things-a-survival-guide.md +30 -0
  519. package/defaults/sample-posts/2026-06-19-your-pricing-page-is-a-product.md +28 -0
  520. package/defaults/sample-posts/2026-06-30-email-is-an-api-with-feelings.md +28 -0
  521. package/defaults/sample-posts/2026-07-08-ship-the-docs-with-the-diff.md +30 -0
  522. package/defaults/sample-team/avery-quinn.md +22 -0
  523. package/defaults/sample-team/jordan-reyes.md +25 -0
  524. package/defaults/sample-team/riley-nakamura.md +22 -0
  525. package/defaults/sample-team/sam-okafor.md +22 -0
  526. package/defaults/sample-updates/v1.0.0.md +28 -0
  527. package/defaults/sample-updates/v1.1.0.md +27 -0
  528. package/defaults/sample-updates/v1.2.0.md +27 -0
  529. package/defaults/sample-updates/v1.3.0.md +28 -0
  530. package/defaults/showcase/components.html +62 -0
  531. package/defaults/showcase/entry.html +76 -0
  532. package/defaults/showcase/frame.html +62 -0
  533. package/defaults/showcase/index.html +59 -0
  534. package/dist/assets.js +1349 -0
  535. package/dist/brand-tokens.js +195 -0
  536. package/dist/build.js +192 -0
  537. package/dist/cachebreak-html.js +71 -0
  538. package/dist/cli-run.js +25 -0
  539. package/dist/cli.js +47 -0
  540. package/dist/collections.js +183 -0
  541. package/dist/commands/audit.js +470 -0
  542. package/dist/commands/build.js +197 -0
  543. package/dist/commands/clean.js +18 -0
  544. package/dist/commands/customize.js +125 -0
  545. package/dist/commands/deploy.js +441 -0
  546. package/dist/commands/dev.js +1067 -0
  547. package/dist/commands/install.js +31 -0
  548. package/dist/commands/lib/deploy-precheck.js +43 -0
  549. package/dist/commands/lib/ensure-target.js +115 -0
  550. package/dist/commands/migrate.js +86 -0
  551. package/dist/commands/purge.js +31 -0
  552. package/dist/commands/test.js +237 -0
  553. package/dist/commands/translate.js +57 -0
  554. package/dist/commands/update.js +22 -0
  555. package/dist/commands/version.js +11 -0
  556. package/dist/config-sections.js +245 -0
  557. package/dist/consumer-scan.js +118 -0
  558. package/dist/consumer.js +49 -0
  559. package/dist/customize.js +328 -0
  560. package/dist/decisions.js +153 -0
  561. package/dist/dist-audit.js +417 -0
  562. package/dist/dynamic-pages.js +357 -0
  563. package/dist/engine.js +1402 -0
  564. package/dist/firebase-auth-helpers.js +198 -0
  565. package/dist/font-metrics.js +420 -0
  566. package/dist/frontmatter-liquid.js +253 -0
  567. package/dist/github-secrets.js +80 -0
  568. package/dist/hero-animations.js +170 -0
  569. package/dist/imagemin.js +261 -0
  570. package/dist/index.js +41 -0
  571. package/dist/inline-icons.js +65 -0
  572. package/dist/language-flags.js +55 -0
  573. package/dist/layers.js +80 -0
  574. package/dist/layouts.js +44 -0
  575. package/dist/limit-collections.js +199 -0
  576. package/dist/link-resolver.js +303 -0
  577. package/dist/markdown-images.js +85 -0
  578. package/dist/merge.js +20 -0
  579. package/dist/migrate/codemod.js +172 -0
  580. package/dist/migrate/config-convert.js +448 -0
  581. package/dist/migrate/consumer-assets.js +130 -0
  582. package/dist/migrate/index.js +223 -0
  583. package/dist/migrate/lint.js +108 -0
  584. package/dist/migrate/rules.js +1929 -0
  585. package/dist/minify-html.js +148 -0
  586. package/dist/mode-helpers.js +96 -0
  587. package/dist/omega-bin.js +7 -0
  588. package/dist/overrides.js +190 -0
  589. package/dist/path-prefix.js +167 -0
  590. package/dist/paths.js +37 -0
  591. package/dist/pricing.js +301 -0
  592. package/dist/purge.js +95 -0
  593. package/dist/redirect-page.js +45 -0
  594. package/dist/sample-content.js +210 -0
  595. package/dist/scaffold.js +123 -0
  596. package/dist/sections.js +810 -0
  597. package/dist/service-worker.js +200 -0
  598. package/dist/social-pages.js +142 -0
  599. package/dist/srcset.js +67 -0
  600. package/dist/static-assets.js +154 -0
  601. package/dist/target-shortlinks.js +123 -0
  602. package/dist/theme-vocabulary.js +76 -0
  603. package/dist/translate/collect-text-nodes.js +93 -0
  604. package/dist/translate/default-routes.js +152 -0
  605. package/dist/translate/generate-defaults.js +297 -0
  606. package/dist/translate/index.js +568 -0
  607. package/dist/translate/packaged-defaults.js +127 -0
  608. package/dist/translate/sitemap.js +108 -0
  609. package/dist/vendor/account/engine.js +182 -0
  610. package/dist/vendor/account/features.js +220 -0
  611. package/dist/vendor/account/index.js +53 -0
  612. package/dist/vendor/account/schema.js +272 -0
  613. package/dist/vendor/account/subscription.js +38 -0
  614. package/dist/vendor/config/company.js +31 -0
  615. package/dist/vendor/config/defaults.js +173 -0
  616. package/dist/vendor/config/demo.js +18 -0
  617. package/dist/vendor/config/desktop-artifacts.js +110 -0
  618. package/dist/vendor/config/edit.js +769 -0
  619. package/dist/vendor/config/env-delivery.js +145 -0
  620. package/dist/vendor/config/env-rules.js +93 -0
  621. package/dist/vendor/config/env-schema.js +1078 -0
  622. package/dist/vendor/config/env.js +445 -0
  623. package/dist/vendor/config/hooks.js +97 -0
  624. package/dist/vendor/config/index.js +237 -0
  625. package/dist/vendor/config/instances.js +208 -0
  626. package/dist/vendor/config/load.js +490 -0
  627. package/dist/vendor/config/merge.js +68 -0
  628. package/dist/vendor/config/order.js +139 -0
  629. package/dist/vendor/config/ports.js +374 -0
  630. package/dist/vendor/config/providers.js +32 -0
  631. package/dist/vendor/config/repo.js +142 -0
  632. package/dist/vendor/config/retired-keys.js +430 -0
  633. package/dist/vendor/config/schema.js +1610 -0
  634. package/dist/vendor/config/secrets.js +50 -0
  635. package/dist/vendor/config/seed.js +34 -0
  636. package/dist/vendor/config/site-global.js +205 -0
  637. package/dist/vendor/config/validate.js +554 -0
  638. package/dist/vendor/config/winback.js +61 -0
  639. package/dist/vendor/devkit/actions-secrets.js +145 -0
  640. package/dist/vendor/devkit/attach-log-file.js +262 -0
  641. package/dist/vendor/devkit/bare-requires.js +153 -0
  642. package/dist/vendor/devkit/bundle.js +292 -0
  643. package/dist/vendor/devkit/ci-workflows.js +520 -0
  644. package/dist/vendor/devkit/cli-router.js +156 -0
  645. package/dist/vendor/devkit/command-path.js +46 -0
  646. package/dist/vendor/devkit/defaults-engine.js +372 -0
  647. package/dist/vendor/devkit/deploy-precheck.js +54 -0
  648. package/dist/vendor/devkit/deploy-record.js +180 -0
  649. package/dist/vendor/devkit/deploy.js +286 -0
  650. package/dist/vendor/devkit/env-watch.js +132 -0
  651. package/dist/vendor/devkit/framework-deps.js +46 -0
  652. package/dist/vendor/devkit/icons.js +178 -0
  653. package/dist/vendor/devkit/license.js +155 -0
  654. package/dist/vendor/devkit/local-https.js +360 -0
  655. package/dist/vendor/devkit/local.js +1905 -0
  656. package/dist/vendor/devkit/logger.js +128 -0
  657. package/dist/vendor/devkit/merge-line-files.js +296 -0
  658. package/dist/vendor/devkit/omega-bin.js +345 -0
  659. package/dist/vendor/devkit/reads.js +274 -0
  660. package/dist/vendor/devkit/safe-install.js +18 -0
  661. package/dist/vendor/devkit/scaffold-guard.js +96 -0
  662. package/dist/vendor/devkit/stop-signals.js +28 -0
  663. package/dist/vendor/devkit/strip-dev-blocks-plugin.js +29 -0
  664. package/dist/vendor/devkit/strip-dev-blocks.js +32 -0
  665. package/dist/vendor/devkit/target-secrets.js +165 -0
  666. package/dist/vendor/devkit/test/scope.js +162 -0
  667. package/dist/vendor/devkit/translate/cache.js +84 -0
  668. package/dist/vendor/devkit/translate/engine.js +243 -0
  669. package/dist/vendor/devkit/translate/index.js +49 -0
  670. package/dist/vendor/devkit/translate/languages.js +134 -0
  671. package/dist/vendor/devkit/translate/providers.js +188 -0
  672. package/dist/vendor/devkit/update.js +569 -0
  673. package/dist/vendor/template-kit/data/language-flags.js +54 -0
  674. package/dist/vendor/template-kit/data/languages.js +195 -0
  675. package/dist/vendor/template-kit/data/social-urls.js +26 -0
  676. package/dist/vendor/template-kit/filters.js +227 -0
  677. package/dist/vendor/template-kit/index.js +28 -0
  678. package/dist/vendor/template-kit/jekyll-compat.js +268 -0
  679. package/dist/vendor/template-kit/register-liquid.js +0 -0
  680. package/dist/vendor/template-kit/tags/collections.js +191 -0
  681. package/dist/vendor/template-kit/tags/conditionals.js +101 -0
  682. package/dist/vendor/template-kit/tags/content.js +175 -0
  683. package/dist/vendor/template-kit/tags/index.js +33 -0
  684. package/dist/vendor/template-kit/tags/media.js +376 -0
  685. package/dist/vendor/template-kit/variable-resolver.js +163 -0
  686. package/docs/ads-system.md +109 -0
  687. package/docs/classy-v2/DIRECTION.md +92 -0
  688. package/docs/classy-v2/draft-1.html +1932 -0
  689. package/docs/classy-v2/draft-2.html +1956 -0
  690. package/docs/frontmatter.md +153 -0
  691. package/docs/index.md +254 -0
  692. package/docs/libs.md +298 -0
  693. package/docs/omega-sections-spec.md +159 -0
  694. package/docs/page-contract.md +112 -0
  695. package/docs/sections.md +845 -0
  696. package/docs/shared/agent-docs.md +89 -0
  697. package/docs/shared/analytics.md +612 -0
  698. package/docs/shared/brands.md +51 -0
  699. package/docs/shared/breaking-changes.md +497 -0
  700. package/docs/shared/config.md +1387 -0
  701. package/docs/shared/deploys.md +215 -0
  702. package/docs/shared/icons.md +201 -0
  703. package/docs/shared/local-dev.md +147 -0
  704. package/docs/shared/logging.md +202 -0
  705. package/docs/shared/monitoring.md +153 -0
  706. package/docs/shared/publishing.md +183 -0
  707. package/docs/shared/rulings.md +34 -0
  708. package/docs/shared/testing.md +147 -0
  709. package/docs/shared/theming.md +604 -0
  710. package/docs/shared/translation.md +291 -0
  711. package/docs/shared/updates.md +61 -0
  712. package/docs/template-kit.md +128 -0
  713. package/docs/test-framework.md +163 -0
  714. package/package.json +122 -0
  715. package/runtime/boot.js +149 -0
  716. package/runtime/icons.js +77 -0
  717. package/runtime/manager.js +36 -0
  718. package/scaffold/.github/workflows/build.yml +139 -0
  719. package/scaffold/.nvmrc +1 -0
  720. package/scaffold/AGENTS.md +55 -0
  721. package/scaffold/CLAUDE.md +1 -0
  722. package/scaffold/_.gitattributes +4 -0
  723. package/scaffold/_.gitignore +27 -0
  724. package/scaffold/config/omega.json5 +20 -0
  725. package/scaffold/src/assets/css/pages/.gitkeep +0 -0
  726. package/scaffold/src/assets/images/.gitkeep +0 -0
  727. package/scaffold/src/assets/js/pages/.gitkeep +0 -0
  728. package/scaffold/src/pages/.gitkeep +0 -0
  729. package/scaffold/src/pages/example.md.txt +94 -0
  730. package/scaffold/src/service-worker.js +16 -0
  731. package/sw/entry.js +19 -0
  732. package/sw/manager.js +281 -0
  733. package/themes/_template/README.md +115 -0
  734. package/themes/_template/_config.scss +60 -0
  735. package/themes/_template/_theme.js +14 -0
  736. package/themes/_template/_theme.scss +28 -0
  737. package/themes/_template/css/base/_root.scss +19 -0
  738. package/themes/_template/css/components/_components.scss +23 -0
  739. package/themes/base/README.md +5 -0
  740. package/themes/base/_components/data/org-chart/component.html +33 -0
  741. package/themes/base/_components/data/org-chart/component.json5 +33 -0
  742. package/themes/base/_components/data/org-chart/component.scss +183 -0
  743. package/themes/base/_components/heading/masthead/component.html +22 -0
  744. package/themes/base/_components/heading/masthead/component.json5 +30 -0
  745. package/themes/base/_components/heading/section-head/component.html +18 -0
  746. package/themes/base/_components/heading/section-head/component.json5 +27 -0
  747. package/themes/base/_components/pricing/features/component.html +29 -0
  748. package/themes/base/_components/pricing/features/component.json5 +30 -0
  749. package/themes/base/_hero/orbit/index.html +30 -0
  750. package/themes/base/_hero/orbit/script.js +33 -0
  751. package/themes/base/_hero/orbit/style.scss +133 -0
  752. package/themes/base/_includes/admin/sections/sidebar.html +3 -0
  753. package/themes/base/_includes/admin/sections/topbar.html +3 -0
  754. package/themes/base/_includes/backend/sections/sidebar.html +3 -0
  755. package/themes/base/_includes/backend/sections/topbar.html +3 -0
  756. package/themes/base/_includes/frontend/components/connection-card.html +49 -0
  757. package/themes/base/_includes/frontend/components/post-card.html +54 -0
  758. package/themes/base/_includes/frontend/sections/account-connections.html +42 -0
  759. package/themes/base/_includes/frontend/sections/account-section-header.html +11 -0
  760. package/themes/base/_includes/frontend/sections/footer.html +154 -0
  761. package/themes/base/_includes/frontend/sections/nav.html +276 -0
  762. package/themes/base/_includes/global/sections/account.html +72 -0
  763. package/themes/base/_includes/global/sections/app-sidebar.html +248 -0
  764. package/themes/base/_includes/global/sections/app-topbar.html +228 -0
  765. package/themes/base/_includes/global/sections/page-header.html +99 -0
  766. package/themes/base/_layouts/admin/core/minimal-viewport-locked.html +22 -0
  767. package/themes/base/_layouts/admin/core/minimal.html +23 -0
  768. package/themes/base/_layouts/backend/core/base.html +57 -0
  769. package/themes/base/_layouts/backend/core/minimal-viewport-locked.html +32 -0
  770. package/themes/base/_layouts/backend/core/minimal.html +32 -0
  771. package/themes/base/_layouts/backend/pages/dashboard/index.html +230 -0
  772. package/themes/base/_layouts/frontend/core/base.html +31 -0
  773. package/themes/base/_layouts/frontend/core/cover.html +40 -0
  774. package/themes/base/_layouts/frontend/core/minimal.html +47 -0
  775. package/themes/base/_layouts/frontend/pages/404.html +54 -0
  776. package/themes/base/_layouts/frontend/pages/about.html +144 -0
  777. package/themes/base/_layouts/frontend/pages/account/index.html +2104 -0
  778. package/themes/base/_layouts/frontend/pages/alternatives/alternative.html +319 -0
  779. package/themes/base/_layouts/frontend/pages/alternatives/index.html +186 -0
  780. package/themes/base/_layouts/frontend/pages/app.html +142 -0
  781. package/themes/base/_layouts/frontend/pages/auth/reset.html +53 -0
  782. package/themes/base/_layouts/frontend/pages/auth/signin.html +90 -0
  783. package/themes/base/_layouts/frontend/pages/auth/signup.html +141 -0
  784. package/themes/base/_layouts/frontend/pages/auth/token.html +44 -0
  785. package/themes/base/_layouts/frontend/pages/blog/categories/category.html +88 -0
  786. package/themes/base/_layouts/frontend/pages/blog/categories/index.html +88 -0
  787. package/themes/base/_layouts/frontend/pages/blog/index.html +255 -0
  788. package/themes/base/_layouts/frontend/pages/blog/post.html +261 -0
  789. package/themes/base/_layouts/frontend/pages/blog/tags/index.html +84 -0
  790. package/themes/base/_layouts/frontend/pages/blog/tags/tag.html +88 -0
  791. package/themes/base/_layouts/frontend/pages/collection/category.html +72 -0
  792. package/themes/base/_layouts/frontend/pages/collection/document.html +54 -0
  793. package/themes/base/_layouts/frontend/pages/collection/index.html +84 -0
  794. package/themes/base/_layouts/frontend/pages/connections/callback.html +55 -0
  795. package/themes/base/_layouts/frontend/pages/contact.html +214 -0
  796. package/themes/base/_layouts/frontend/pages/download.html +793 -0
  797. package/themes/base/_layouts/frontend/pages/extension/index.html +253 -0
  798. package/themes/base/_layouts/frontend/pages/extension/installed.html +198 -0
  799. package/themes/base/_layouts/frontend/pages/feedback.html +171 -0
  800. package/themes/base/_layouts/frontend/pages/index.html +66 -0
  801. package/themes/base/_layouts/frontend/pages/legal/document.html +78 -0
  802. package/themes/base/_layouts/frontend/pages/payment/checkout.html +459 -0
  803. package/themes/base/_layouts/frontend/pages/payment/confirmation.html +131 -0
  804. package/themes/base/_layouts/frontend/pages/portal/email-preferences.html +104 -0
  805. package/themes/base/_layouts/frontend/pages/pricing.html +596 -0
  806. package/themes/base/_layouts/frontend/pages/status.html +361 -0
  807. package/themes/base/_layouts/frontend/pages/team/index.html +210 -0
  808. package/themes/base/_layouts/frontend/pages/team/member.html +125 -0
  809. package/themes/base/_layouts/frontend/pages/updates/index.html +119 -0
  810. package/themes/base/_layouts/frontend/pages/updates/update.html +118 -0
  811. package/themes/base/_sections/about/hero/section.html +68 -0
  812. package/themes/base/_sections/about/hero/section.json5 +58 -0
  813. package/themes/base/_sections/about/letter/section.html +60 -0
  814. package/themes/base/_sections/about/letter/section.json5 +56 -0
  815. package/themes/base/_sections/about/photo-band/section.html +24 -0
  816. package/themes/base/_sections/about/photo-band/section.json5 +23 -0
  817. package/themes/base/_sections/about/principles/section.html +17 -0
  818. package/themes/base/_sections/about/principles/section.json5 +34 -0
  819. package/themes/base/_sections/about/timeline/section.html +20 -0
  820. package/themes/base/_sections/about/timeline/section.json5 +34 -0
  821. package/themes/base/_sections/marketing/bento/section.html +91 -0
  822. package/themes/base/_sections/marketing/bento/section.json5 +152 -0
  823. package/themes/base/_sections/marketing/cta/section.html +44 -0
  824. package/themes/base/_sections/marketing/cta/section.json5 +47 -0
  825. package/themes/base/_sections/marketing/faq/section.html +52 -0
  826. package/themes/base/_sections/marketing/faq/section.json5 +68 -0
  827. package/themes/base/_sections/marketing/hero/section.html +349 -0
  828. package/themes/base/_sections/marketing/hero/section.json5 +335 -0
  829. package/themes/base/_sections/marketing/newsletter-cta/section.html +38 -0
  830. package/themes/base/_sections/marketing/newsletter-cta/section.js +86 -0
  831. package/themes/base/_sections/marketing/newsletter-cta/section.json5 +29 -0
  832. package/themes/base/_sections/marketing/pricing-cards/section.html +115 -0
  833. package/themes/base/_sections/marketing/pricing-cards/section.json5 +72 -0
  834. package/themes/base/_sections/marketing/product-demo/section.html +62 -0
  835. package/themes/base/_sections/marketing/product-demo/section.js +44 -0
  836. package/themes/base/_sections/marketing/product-demo/section.json5 +52 -0
  837. package/themes/base/_sections/marketing/prose/section.html +22 -0
  838. package/themes/base/_sections/marketing/prose/section.json5 +41 -0
  839. package/themes/base/_sections/marketing/showcase/section.html +35 -0
  840. package/themes/base/_sections/marketing/showcase/section.json5 +52 -0
  841. package/themes/base/_sections/marketing/stats/section.html +32 -0
  842. package/themes/base/_sections/marketing/stats/section.json5 +44 -0
  843. package/themes/base/_sections/marketing/testimonials/section.html +54 -0
  844. package/themes/base/_sections/marketing/testimonials/section.json5 +70 -0
  845. package/themes/base/_sections/marketing/trusted-by/section.html +30 -0
  846. package/themes/base/_sections/marketing/trusted-by/section.json5 +38 -0
  847. package/themes/base/_sections/verts/unit/section.html +7 -0
  848. package/themes/base/_sections/verts/unit/section.js +22 -0
  849. package/themes/base/_sections/verts/unit/section.json5 +24 -0
  850. package/themes/base/_sections/verts/unit/section.scss +24 -0
  851. package/themes/base/_theme.js +13 -0
  852. package/themes/base/_theme.scss +4 -0
  853. package/themes/bootstrap/_theme.js +16 -0
  854. package/themes/bootstrap/_theme.scss +11 -0
  855. package/themes/bootstrap/js/index.esm.js +19 -0
  856. package/themes/bootstrap/js/index.umd.js +34 -0
  857. package/themes/bootstrap/js/src/alert.js +87 -0
  858. package/themes/bootstrap/js/src/base-component.js +86 -0
  859. package/themes/bootstrap/js/src/button.js +72 -0
  860. package/themes/bootstrap/js/src/carousel.js +474 -0
  861. package/themes/bootstrap/js/src/collapse.js +297 -0
  862. package/themes/bootstrap/js/src/dom/data.js +55 -0
  863. package/themes/bootstrap/js/src/dom/event-handler.js +317 -0
  864. package/themes/bootstrap/js/src/dom/manipulator.js +71 -0
  865. package/themes/bootstrap/js/src/dom/selector-engine.js +126 -0
  866. package/themes/bootstrap/js/src/dropdown.js +458 -0
  867. package/themes/bootstrap/js/src/modal.js +378 -0
  868. package/themes/bootstrap/js/src/offcanvas.js +282 -0
  869. package/themes/bootstrap/js/src/popover.js +97 -0
  870. package/themes/bootstrap/js/src/scrollspy.js +296 -0
  871. package/themes/bootstrap/js/src/tab.js +315 -0
  872. package/themes/bootstrap/js/src/toast.js +224 -0
  873. package/themes/bootstrap/js/src/tooltip.js +632 -0
  874. package/themes/bootstrap/js/src/util/backdrop.js +151 -0
  875. package/themes/bootstrap/js/src/util/component-functions.js +35 -0
  876. package/themes/bootstrap/js/src/util/config.js +65 -0
  877. package/themes/bootstrap/js/src/util/focustrap.js +115 -0
  878. package/themes/bootstrap/js/src/util/index.js +306 -0
  879. package/themes/bootstrap/js/src/util/sanitizer.js +117 -0
  880. package/themes/bootstrap/js/src/util/scrollbar.js +114 -0
  881. package/themes/bootstrap/js/src/util/swipe.js +146 -0
  882. package/themes/bootstrap/js/src/util/template-factory.js +160 -0
  883. package/themes/bootstrap/overrides/_accordion.scss +21 -0
  884. package/themes/bootstrap/overrides/_avatars.scss +51 -0
  885. package/themes/bootstrap/overrides/_blog-images.scss +45 -0
  886. package/themes/bootstrap/overrides/_buttons-adaptive.scss +141 -0
  887. package/themes/bootstrap/overrides/_color-shades.scss +25 -0
  888. package/themes/bootstrap/overrides/_cursor-utilities.scss +13 -0
  889. package/themes/bootstrap/overrides/_index.scss +30 -0
  890. package/themes/bootstrap/overrides/_links.scss +26 -0
  891. package/themes/bootstrap/overrides/_soft-colors.scss +99 -0
  892. package/themes/bootstrap/overrides/_spacing-extended.scss +74 -0
  893. package/themes/bootstrap/overrides/_spinners.scss +22 -0
  894. package/themes/bootstrap/overrides/_typography-utilities.scss +40 -0
  895. package/themes/bootstrap/scss/_accordion.scss +153 -0
  896. package/themes/bootstrap/scss/_alert.scss +68 -0
  897. package/themes/bootstrap/scss/_badge.scss +38 -0
  898. package/themes/bootstrap/scss/_breadcrumb.scss +40 -0
  899. package/themes/bootstrap/scss/_button-group.scss +147 -0
  900. package/themes/bootstrap/scss/_buttons.scss +216 -0
  901. package/themes/bootstrap/scss/_card.scss +238 -0
  902. package/themes/bootstrap/scss/_carousel.scss +226 -0
  903. package/themes/bootstrap/scss/_close.scss +66 -0
  904. package/themes/bootstrap/scss/_containers.scss +41 -0
  905. package/themes/bootstrap/scss/_dropdown.scss +250 -0
  906. package/themes/bootstrap/scss/_forms.scss +9 -0
  907. package/themes/bootstrap/scss/_functions.scss +302 -0
  908. package/themes/bootstrap/scss/_grid.scss +39 -0
  909. package/themes/bootstrap/scss/_helpers.scss +12 -0
  910. package/themes/bootstrap/scss/_images.scss +42 -0
  911. package/themes/bootstrap/scss/_list-group.scss +199 -0
  912. package/themes/bootstrap/scss/_maps.scss +174 -0
  913. package/themes/bootstrap/scss/_mixins.scss +42 -0
  914. package/themes/bootstrap/scss/_modal.scss +240 -0
  915. package/themes/bootstrap/scss/_nav.scss +197 -0
  916. package/themes/bootstrap/scss/_navbar.scss +289 -0
  917. package/themes/bootstrap/scss/_offcanvas.scss +147 -0
  918. package/themes/bootstrap/scss/_pagination.scss +109 -0
  919. package/themes/bootstrap/scss/_placeholders.scss +51 -0
  920. package/themes/bootstrap/scss/_popover.scss +196 -0
  921. package/themes/bootstrap/scss/_progress.scss +68 -0
  922. package/themes/bootstrap/scss/_reboot.scss +611 -0
  923. package/themes/bootstrap/scss/_root.scss +187 -0
  924. package/themes/bootstrap/scss/_spinners.scss +85 -0
  925. package/themes/bootstrap/scss/_tables.scss +171 -0
  926. package/themes/bootstrap/scss/_toasts.scss +73 -0
  927. package/themes/bootstrap/scss/_tooltip.scss +119 -0
  928. package/themes/bootstrap/scss/_transitions.scss +27 -0
  929. package/themes/bootstrap/scss/_type.scss +106 -0
  930. package/themes/bootstrap/scss/_utilities.scss +806 -0
  931. package/themes/bootstrap/scss/_variables-dark.scss +102 -0
  932. package/themes/bootstrap/scss/_variables.scss +1753 -0
  933. package/themes/bootstrap/scss/bootstrap-grid.scss +62 -0
  934. package/themes/bootstrap/scss/bootstrap-reboot.scss +10 -0
  935. package/themes/bootstrap/scss/bootstrap-utilities.scss +19 -0
  936. package/themes/bootstrap/scss/bootstrap.scss +52 -0
  937. package/themes/bootstrap/scss/forms/_floating-labels.scss +97 -0
  938. package/themes/bootstrap/scss/forms/_form-check.scss +189 -0
  939. package/themes/bootstrap/scss/forms/_form-control.scss +214 -0
  940. package/themes/bootstrap/scss/forms/_form-range.scss +91 -0
  941. package/themes/bootstrap/scss/forms/_form-select.scss +80 -0
  942. package/themes/bootstrap/scss/forms/_form-text.scss +11 -0
  943. package/themes/bootstrap/scss/forms/_input-group.scss +132 -0
  944. package/themes/bootstrap/scss/forms/_labels.scss +36 -0
  945. package/themes/bootstrap/scss/forms/_validation.scss +12 -0
  946. package/themes/bootstrap/scss/helpers/_clearfix.scss +3 -0
  947. package/themes/bootstrap/scss/helpers/_color-bg.scss +7 -0
  948. package/themes/bootstrap/scss/helpers/_colored-links.scss +30 -0
  949. package/themes/bootstrap/scss/helpers/_focus-ring.scss +5 -0
  950. package/themes/bootstrap/scss/helpers/_icon-link.scss +25 -0
  951. package/themes/bootstrap/scss/helpers/_position.scss +36 -0
  952. package/themes/bootstrap/scss/helpers/_ratio.scss +26 -0
  953. package/themes/bootstrap/scss/helpers/_stacks.scss +15 -0
  954. package/themes/bootstrap/scss/helpers/_stretched-link.scss +15 -0
  955. package/themes/bootstrap/scss/helpers/_text-truncation.scss +7 -0
  956. package/themes/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
  957. package/themes/bootstrap/scss/helpers/_vr.scss +8 -0
  958. package/themes/bootstrap/scss/mixins/_alert.scss +18 -0
  959. package/themes/bootstrap/scss/mixins/_backdrop.scss +14 -0
  960. package/themes/bootstrap/scss/mixins/_banner.scss +7 -0
  961. package/themes/bootstrap/scss/mixins/_border-radius.scss +78 -0
  962. package/themes/bootstrap/scss/mixins/_box-shadow.scss +18 -0
  963. package/themes/bootstrap/scss/mixins/_breakpoints.scss +127 -0
  964. package/themes/bootstrap/scss/mixins/_buttons.scss +70 -0
  965. package/themes/bootstrap/scss/mixins/_caret.scss +69 -0
  966. package/themes/bootstrap/scss/mixins/_clearfix.scss +9 -0
  967. package/themes/bootstrap/scss/mixins/_color-mode.scss +21 -0
  968. package/themes/bootstrap/scss/mixins/_color-scheme.scss +7 -0
  969. package/themes/bootstrap/scss/mixins/_container.scss +11 -0
  970. package/themes/bootstrap/scss/mixins/_deprecate.scss +10 -0
  971. package/themes/bootstrap/scss/mixins/_forms.scss +163 -0
  972. package/themes/bootstrap/scss/mixins/_gradients.scss +47 -0
  973. package/themes/bootstrap/scss/mixins/_grid.scss +151 -0
  974. package/themes/bootstrap/scss/mixins/_image.scss +16 -0
  975. package/themes/bootstrap/scss/mixins/_list-group.scss +26 -0
  976. package/themes/bootstrap/scss/mixins/_lists.scss +7 -0
  977. package/themes/bootstrap/scss/mixins/_pagination.scss +10 -0
  978. package/themes/bootstrap/scss/mixins/_reset-text.scss +17 -0
  979. package/themes/bootstrap/scss/mixins/_resize.scss +6 -0
  980. package/themes/bootstrap/scss/mixins/_table-variants.scss +24 -0
  981. package/themes/bootstrap/scss/mixins/_text-truncate.scss +8 -0
  982. package/themes/bootstrap/scss/mixins/_transition.scss +26 -0
  983. package/themes/bootstrap/scss/mixins/_utilities.scss +97 -0
  984. package/themes/bootstrap/scss/mixins/_visually-hidden.scss +38 -0
  985. package/themes/bootstrap/scss/tests/jasmine.js +16 -0
  986. package/themes/bootstrap/scss/tests/mixins/_auto-import-of-variables-dark.test.scss +7 -0
  987. package/themes/bootstrap/scss/tests/mixins/_color-modes.test.scss +69 -0
  988. package/themes/bootstrap/scss/tests/mixins/_media-query-color-mode-full.test.scss +8 -0
  989. package/themes/bootstrap/scss/tests/mixins/_utilities.test.scss +393 -0
  990. package/themes/bootstrap/scss/tests/sass-true/register.js +14 -0
  991. package/themes/bootstrap/scss/tests/sass-true/runner.js +17 -0
  992. package/themes/bootstrap/scss/tests/utilities/_api.test.scss +75 -0
  993. package/themes/bootstrap/scss/utilities/_api.scss +47 -0
  994. package/themes/bootstrap/scss/vendor/_rfs.scss +348 -0
  995. package/themes/classy/README.md +89 -0
  996. package/themes/classy/_config.scss +245 -0
  997. package/themes/classy/_theme.js +31 -0
  998. package/themes/classy/_theme.scss +57 -0
  999. package/themes/classy/css/app/_panels.scss +183 -0
  1000. package/themes/classy/css/base/_fonts.scss +79 -0
  1001. package/themes/classy/css/base/_no-raise.scss +49 -0
  1002. package/themes/classy/css/base/_root.scss +113 -0
  1003. package/themes/classy/css/base/_typography.scss +154 -0
  1004. package/themes/classy/css/base/_utilities.scss +284 -0
  1005. package/themes/classy/css/components/_alerts.scss +51 -0
  1006. package/themes/classy/css/components/_badges.scss +46 -0
  1007. package/themes/classy/css/components/_buttons.scss +175 -0
  1008. package/themes/classy/css/components/_cards.scss +41 -0
  1009. package/themes/classy/css/components/_dropdowns.scss +48 -0
  1010. package/themes/classy/css/components/_forms.scss +171 -0
  1011. package/themes/classy/css/components/_receipt.scss +45 -0
  1012. package/themes/classy/css/layout/_footer.scss +298 -0
  1013. package/themes/classy/css/layout/_general.scss +209 -0
  1014. package/themes/classy/css/layout/_nav.scss +154 -0
  1015. package/themes/classy/css/layout/_shell.scss +458 -0
  1016. package/themes/classy/css/marketing/_bento.scss +238 -0
  1017. package/themes/classy/css/marketing/_content.scss +1328 -0
  1018. package/themes/classy/css/marketing/_downloads.scss +246 -0
  1019. package/themes/classy/css/marketing/_hero.scss +311 -0
  1020. package/themes/classy/css/marketing/_sections.scss +649 -0
  1021. package/themes/classy/css/pages/404/index.scss +67 -0
  1022. package/themes/classy/css/pages/_auth.scss +235 -0
  1023. package/themes/classy/css/pages/admin/firebase/index.scss +22 -0
  1024. package/themes/classy/css/pages/blog/[slug].scss +56 -0
  1025. package/themes/classy/css/pages/cookies.scss +2 -0
  1026. package/themes/classy/css/pages/dashboard/account/index.scss +31 -0
  1027. package/themes/classy/css/pages/feedback/index.scss +39 -0
  1028. package/themes/classy/css/pages/legal/_document.scss +192 -0
  1029. package/themes/classy/css/pages/payment/checkout/index.scss +585 -0
  1030. package/themes/classy/css/pages/payment/confirmation/index.scss +9 -0
  1031. package/themes/classy/css/pages/privacy.scss +2 -0
  1032. package/themes/classy/css/pages/status/index.scss +164 -0
  1033. package/themes/classy/css/pages/terms.scss +2 -0
  1034. package/themes/classy/css/pages/updates/index.scss +128 -0
  1035. package/themes/classy/fonts/inter-italic-latin-ext.woff2 +0 -0
  1036. package/themes/classy/fonts/inter-italic-latin.woff2 +0 -0
  1037. package/themes/classy/fonts/inter-normal-latin-ext.woff2 +0 -0
  1038. package/themes/classy/fonts/inter-normal-latin.woff2 +0 -0
  1039. package/themes/classy/fonts/newsreader-italic-latin-ext.woff2 +0 -0
  1040. package/themes/classy/fonts/newsreader-italic-latin.woff2 +0 -0
  1041. package/themes/classy/fonts/newsreader-normal-latin-ext.woff2 +0 -0
  1042. package/themes/classy/fonts/newsreader-normal-latin.woff2 +0 -0
  1043. package/themes/classy/js/hero-demo-form.js +56 -0
  1044. package/themes/neobrutalism/README.md +118 -0
  1045. package/themes/neobrutalism/_config.scss +139 -0
  1046. package/themes/neobrutalism/_layouts/frontend/core/base.html +30 -0
  1047. package/themes/neobrutalism/_layouts/frontend/pages/index.html +275 -0
  1048. package/themes/neobrutalism/_theme.js +24 -0
  1049. package/themes/neobrutalism/_theme.scss +61 -0
  1050. package/themes/neobrutalism/css/base/_mixins.scss +46 -0
  1051. package/themes/neobrutalism/css/base/_root.scss +169 -0
  1052. package/themes/neobrutalism/css/base/_typography.scss +61 -0
  1053. package/themes/neobrutalism/css/base/_utilities.scss +89 -0
  1054. package/themes/neobrutalism/css/components/_billing-toggle.scss +74 -0
  1055. package/themes/neobrutalism/css/components/_buttons.scss +148 -0
  1056. package/themes/neobrutalism/css/components/_cards.scss +47 -0
  1057. package/themes/neobrutalism/css/components/_forms.scss +88 -0
  1058. package/themes/neobrutalism/css/components/_marketing.scss +244 -0
  1059. package/themes/neobrutalism/css/layout/_general.scss +213 -0
  1060. package/themes/neobrutalism/css/layout/_navigation.scss +171 -0
  1061. package/themes/neobrutalism/css/pages/index.scss +203 -0
  1062. package/themes/neobrutalism/css/pages/pricing/index.scss +58 -0
  1063. package/themes/neobrutalism/css/pages/test/libraries/layers/index.scss +7 -0
  1064. package/themes/neobrutalism/js/pages/test/libraries/layers/index.js +9 -0
  1065. package/themes/newsflash/README.md +117 -0
  1066. package/themes/newsflash/_components/heading/lede/component.html +14 -0
  1067. package/themes/newsflash/_components/heading/lede/component.json5 +23 -0
  1068. package/themes/newsflash/_components/heading/rule-head/component.html +17 -0
  1069. package/themes/newsflash/_components/heading/rule-head/component.json5 +23 -0
  1070. package/themes/newsflash/_components/news/byline/component.html +12 -0
  1071. package/themes/newsflash/_components/news/byline/component.json5 +18 -0
  1072. package/themes/newsflash/_components/news/story-card/component.html +25 -0
  1073. package/themes/newsflash/_components/news/story-card/component.json5 +21 -0
  1074. package/themes/newsflash/_config.scss +147 -0
  1075. package/themes/newsflash/_layouts/frontend/core/base.html +51 -0
  1076. package/themes/newsflash/_layouts/frontend/pages/blog/index.html +299 -0
  1077. package/themes/newsflash/_layouts/frontend/pages/blog/post.html +235 -0
  1078. package/themes/newsflash/_layouts/frontend/pages/index.html +328 -0
  1079. package/themes/newsflash/_sections/marketing/desks/section.html +39 -0
  1080. package/themes/newsflash/_sections/marketing/desks/section.json5 +49 -0
  1081. package/themes/newsflash/_sections/marketing/newsletter-cta/section.html +103 -0
  1082. package/themes/newsflash/_sections/marketing/newsletter-cta/section.json5 +50 -0
  1083. package/themes/newsflash/_sections/marketing/rundown/section.html +28 -0
  1084. package/themes/newsflash/_sections/marketing/rundown/section.json5 +40 -0
  1085. package/themes/newsflash/_theme.js +24 -0
  1086. package/themes/newsflash/_theme.scss +70 -0
  1087. package/themes/newsflash/css/base/_fonts.scss +80 -0
  1088. package/themes/newsflash/css/base/_mixins.scss +51 -0
  1089. package/themes/newsflash/css/base/_root.scss +257 -0
  1090. package/themes/newsflash/css/base/_typography.scss +49 -0
  1091. package/themes/newsflash/css/base/_utilities.scss +122 -0
  1092. package/themes/newsflash/css/components/_badges.scss +65 -0
  1093. package/themes/newsflash/css/components/_billing-toggle.scss +61 -0
  1094. package/themes/newsflash/css/components/_buttons.scss +139 -0
  1095. package/themes/newsflash/css/components/_cards.scss +47 -0
  1096. package/themes/newsflash/css/components/_editorial.scss +173 -0
  1097. package/themes/newsflash/css/components/_forms.scss +75 -0
  1098. package/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
  1099. package/themes/newsflash/css/components/_panels.scss +156 -0
  1100. package/themes/newsflash/css/components/_ticker.scss +67 -0
  1101. package/themes/newsflash/css/layout/_general.scss +330 -0
  1102. package/themes/newsflash/css/layout/_navigation.scss +168 -0
  1103. package/themes/newsflash/css/pages/404/index.scss +79 -0
  1104. package/themes/newsflash/css/pages/blog/[slug].scss +186 -0
  1105. package/themes/newsflash/css/pages/blog/index.scss +17 -0
  1106. package/themes/newsflash/css/pages/index.scss +157 -0
  1107. package/themes/newsflash/css/pages/pricing/index.scss +55 -0
  1108. package/themes/newsflash/css/pages/test/libraries/layers/index.scss +7 -0
  1109. package/themes/newsflash/fonts/fraunces-italic-latin-ext.woff2 +0 -0
  1110. package/themes/newsflash/fonts/fraunces-italic-latin.woff2 +0 -0
  1111. package/themes/newsflash/fonts/fraunces-normal-latin-ext.woff2 +0 -0
  1112. package/themes/newsflash/fonts/fraunces-normal-latin.woff2 +0 -0
  1113. package/themes/newsflash/fonts/schibsted-grotesk-italic-latin-ext.woff2 +0 -0
  1114. package/themes/newsflash/fonts/schibsted-grotesk-italic-latin.woff2 +0 -0
  1115. package/themes/newsflash/fonts/schibsted-grotesk-normal-latin-ext.woff2 +0 -0
  1116. package/themes/newsflash/fonts/schibsted-grotesk-normal-latin.woff2 +0 -0
  1117. package/themes/newsflash/js/pages/blog/[slug].js +29 -0
  1118. package/themes/newsflash/js/pages/test/libraries/layers/index.js +9 -0
  1119. package/translations/es/pages/404.json +86 -0
  1120. package/translations/es/pages/account.json +36 -0
  1121. package/translations/es/pages/app.json +44 -0
  1122. package/translations/es/pages/cancel.json +36 -0
  1123. package/translations/es/pages/change-password.json +36 -0
  1124. package/translations/es/pages/connections/callback.json +41 -0
  1125. package/translations/es/pages/dashboard/account.json +475 -0
  1126. package/translations/es/pages/dashboard.json +36 -0
  1127. package/translations/es/pages/forgot.json +36 -0
  1128. package/translations/es/pages/join.json +36 -0
  1129. package/translations/es/pages/login.json +36 -0
  1130. package/translations/es/pages/payment/checkout.json +83 -0
  1131. package/translations/es/pages/payment/confirmation.json +62 -0
  1132. package/translations/es/pages/portal/email-preferences.json +46 -0
  1133. package/translations/es/pages/recover.json +36 -0
  1134. package/translations/es/pages/refund.json +36 -0
  1135. package/translations/es/pages/register.json +36 -0
  1136. package/translations/es/pages/reset-password.json +36 -0
  1137. package/translations/es/pages/reset.json +42 -0
  1138. package/translations/es/pages/signin.json +45 -0
  1139. package/translations/es/pages/signup.json +60 -0
  1140. package/translations/es/pages/token.json +39 -0
  1141. package/translations/fa/pages/404.json +86 -0
  1142. package/translations/fa/pages/account.json +36 -0
  1143. package/translations/fa/pages/app.json +44 -0
  1144. package/translations/fa/pages/cancel.json +36 -0
  1145. package/translations/fa/pages/change-password.json +36 -0
  1146. package/translations/fa/pages/connections/callback.json +41 -0
  1147. package/translations/fa/pages/dashboard/account.json +475 -0
  1148. package/translations/fa/pages/dashboard.json +36 -0
  1149. package/translations/fa/pages/forgot.json +36 -0
  1150. package/translations/fa/pages/join.json +36 -0
  1151. package/translations/fa/pages/login.json +36 -0
  1152. package/translations/fa/pages/payment/checkout.json +83 -0
  1153. package/translations/fa/pages/payment/confirmation.json +62 -0
  1154. package/translations/fa/pages/portal/email-preferences.json +46 -0
  1155. package/translations/fa/pages/recover.json +36 -0
  1156. package/translations/fa/pages/refund.json +36 -0
  1157. package/translations/fa/pages/register.json +36 -0
  1158. package/translations/fa/pages/reset-password.json +36 -0
  1159. package/translations/fa/pages/reset.json +42 -0
  1160. package/translations/fa/pages/signin.json +45 -0
  1161. package/translations/fa/pages/signup.json +60 -0
  1162. package/translations/fa/pages/token.json +39 -0
@@ -0,0 +1,1929 @@
1
+ /**
2
+ * rules.js — the executable codemod rules for `omega migrate`.
3
+ *
4
+ * The 8 rule classes extracted from the A2 real-file ports (DECISION.md
5
+ * "consumer conversion plan"), plus the analytics-spelling normalization
6
+ * (rule 9, from the omega.json5 flip), the WM → @omega.js/client rename
7
+ * surface (rules 10–15, #248), the classy gradient utilities classy v2
8
+ * dropped (rule 16, #296) and the dead `asset_path` frontmatter key (rule 17,
9
+ * #470). Every rule is a pure text → text transform over
10
+ * ONE file: `apply(text, ctx)` returns `{ text, edits, findings }` where
11
+ * `edits` are applied rewrites and `findings` are lint-level observations the
12
+ * rule could NOT safely fix (surfaced by both `omega migrate` and
13
+ * `omega migrate --check`).
14
+ *
15
+ * TWO tables, by file surface: RULES over the src/** templates, JS_RULES over
16
+ * the consumer JS. Rules run in ORDER within each: page.resolved must collapse
17
+ * before the generic page.<key> rewrite, page.canonical.url before both, and
18
+ * the theme-prefixed include after the leading slash is gone.
19
+ */
20
+
21
+ // Tags whose quoted args template-kit resolves as variables — the rule-3
22
+ // capture hoist is scoped to these (interpolation inside OTHER tags' quoted
23
+ // strings is a Jekyll silent no-op we only report).
24
+ const { TAGS, filters: { FILTER_NAMES } } = require('../vendor/template-kit/index.js');
25
+
26
+ // The config namespace (#607): a page's frontmatter overrides omega.json5
27
+ // under `config:`, and nothing that is not a config section may sit there.
28
+ const {
29
+ CONFIG_SECTIONS, RANDOM_ID_ASSIGN_IDIOM,
30
+ templateReads, randomIdReads, assignsRandomId,
31
+ } = require('../config-sections.js');
32
+
33
+ /** Does this bare frontmatter key belong under `config:`? */
34
+ const isMovableSection = (key) => CONFIG_SECTIONS.has(key);
35
+
36
+ // Packaged theme ids whose hardcoded layout prefixes the engine aliases
37
+ const PACKAGED_THEMES = ['classy', 'neobrutalism', 'newsflash', 'bootstrap'];
38
+
39
+ // Eleventy's real `page` properties — page.<key> references to these are
40
+ // valid and stay; everything else is Jekyll frontmatter-through-page.
41
+ const VALID_PAGE_PROPS = [
42
+ 'url', 'date', 'fileSlug', 'filePathStem', 'inputPath', 'outputPath',
43
+ 'outputFileExtension', 'templateSyntax', 'rawInput', 'lang',
44
+ ];
45
+
46
+ // Jekyll page props with NO mechanical equivalent — flagged, never rewritten
47
+ const MANUAL_PAGE_PROPS = ['next', 'previous', 'collection'];
48
+
49
+ /**
50
+ * Split text into lines preserving the trailing-newline state.
51
+ * @param {string} text
52
+ * @returns {{ lines: string[], trailingNewline: boolean }}
53
+ */
54
+ function toLines(text) {
55
+ const trailingNewline = text.endsWith('\n');
56
+ const lines = text.split('\n');
57
+ if (trailingNewline) lines.pop();
58
+ return { lines, trailingNewline };
59
+ }
60
+
61
+ /**
62
+ * Reassemble lines into text.
63
+ */
64
+ function fromLines(lines, trailingNewline) {
65
+ return lines.join('\n') + (trailingNewline ? '\n' : '');
66
+ }
67
+
68
+ /**
69
+ * Line-by-line regex replace helper: applies `pattern` → `replacement` per
70
+ * line and records one edit per changed line.
71
+ * @param {string} id - rule id for the edit records
72
+ * @param {string} text
73
+ * @param {RegExp} pattern - must carry the g flag
74
+ * @param {string|function} replacement
75
+ * @returns {{ text: string, edits: object[] }}
76
+ */
77
+ function replacePerLine(id, text, pattern, replacement) {
78
+ const { lines, trailingNewline } = toLines(text);
79
+ const edits = [];
80
+ const out = lines.map((line, index) => {
81
+ const replaced = line.replace(pattern, replacement);
82
+ if (replaced !== line) edits.push({ rule: id, line: index + 1, before: line.trim(), after: replaced.trim() });
83
+ return replaced;
84
+ });
85
+ return { text: fromLines(out, trailingNewline), edits };
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Rule 0 — legacy `uj_`/`uj-`/`site.uj` spellings → the `omega` names
90
+ // (runs first: every later rule matches template-kit's CURRENT tag names)
91
+ // ---------------------------------------------------------------------------
92
+
93
+ // The context-coupled filters register-liquid installs directly, so they are
94
+ // absent from FILTER_NAMES but are still part of the renamed surface.
95
+ const CONTEXT_FILTERS = ['omega_liquify', 'omega_content_format', 'omega_increment_return'];
96
+
97
+ // `uj_<name>` for every name the kit registers today — a legacy template that
98
+ // spelled something else keeps its text (it was never a template-kit call).
99
+ const LEGACY_NAMES = [...Object.keys(TAGS), ...Object.keys(FILTER_NAMES), ...CONTEXT_FILTERS]
100
+ .map((name) => name.replace(/^omega_/, ''));
101
+
102
+ const LEGACY_CLASSES = {
103
+ 'uj-password-show': 'omega-password-show',
104
+ 'uj-password-hide': 'omega-password-hide',
105
+ 'uj-language-flag': 'omega-language-flag',
106
+ 'uj-language-dropdown': 'omega-language-dropdown',
107
+ 'data-uj-no-translate': 'data-omega-no-translate',
108
+ };
109
+
110
+ const legacyPrefix = {
111
+ id: 'legacy-prefix',
112
+ title: '`uj_*` tags/filters → `omega_*`; `site.uj` → `site.omega`; `uj-*` classes → `omega-*`',
113
+ apply(text) {
114
+ const names = new RegExp(`\\buj_(${LEGACY_NAMES.join('|')})\\b`, 'g');
115
+ const classes = new RegExp(Object.keys(LEGACY_CLASSES).join('|'), 'g');
116
+ const { lines, trailingNewline } = toLines(text);
117
+ const edits = [];
118
+ const out = lines.map((line, index) => {
119
+ const replaced = line
120
+ .replace(names, 'omega_$1')
121
+ .replace(/\bsite\.uj\b/g, 'site.omega')
122
+ .replace(/\buj-schema-/g, 'omega-schema-')
123
+ .replace(classes, (whole) => LEGACY_CLASSES[whole]);
124
+ if (replaced !== line) edits.push({ rule: 'legacy-prefix', line: index + 1, before: line.trim(), after: replaced.trim() });
125
+ return replaced;
126
+ });
127
+ return { text: fromLines(out, trailingNewline), edits, findings: [] };
128
+ },
129
+ };
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Rule 27 — the icon TAG → native Font Awesome markup (#619)
133
+ //
134
+ // Icons are not a tag any more, on either side of the rename: authoring is
135
+ // `<i class="fa-solid fa-rocket">`, which the build inlines and the runtime
136
+ // watcher upgrades. So a legacy `{% uj_icon %}` (and an already-half-migrated
137
+ // `{% omega_icon %}`) converts to markup, never to a tag that no longer
138
+ // exists. Runs FIRST — before the uj_→omega_ rename and before the tag-arg
139
+ // hoist, both of which would otherwise treat it as a live tag.
140
+ //
141
+ // The name arg becomes the `fa-<name>` class (a Liquid variable interpolates
142
+ // into it), the positional class arg rides along verbatim, and the weight is
143
+ // always `fa-solid`: the icon lookup falls back to `brands/` for any weight,
144
+ // so a brand name resolves without the converter knowing the icon set.
145
+ // ---------------------------------------------------------------------------
146
+ const ICON_TAG_PATTERN = /\{%-?\s*(?:uj|omega)_icon\s*([^%]*?)-?%\}/g;
147
+
148
+ const iconTagMarkup = {
149
+ id: 'icon-tag-markup',
150
+ title: '`uj_icon`/`omega_icon` tags → native Font Awesome markup',
151
+ apply(text) {
152
+ const { lines, trailingNewline } = toLines(text);
153
+ const edits = [];
154
+ const out = lines.map((line, index) => {
155
+ const replaced = line.replace(ICON_TAG_PATTERN, (whole, markup) => {
156
+ const args = splitTagArgs(markup.trim());
157
+ if (!args.length) return whole;
158
+
159
+ const name = quotedValue(args[0]);
160
+ const nameClass = name === null ? `fa-{{ ${args[0]} }}` : `fa-${name}`;
161
+ // Only the POSITIONAL class arg carries over; a `label=` option has no
162
+ // native equivalent, so the icon converts and the option drops.
163
+ const extraArg = args[1] && !args[1].includes('=') ? args[1] : null;
164
+ const extraValue = extraArg === null ? '' : (quotedValue(extraArg) ?? `{{ ${extraArg} }}`);
165
+ const classes = extraValue ? `fa-solid ${nameClass} ${extraValue}` : `fa-solid ${nameClass}`;
166
+
167
+ return `<i class="${classes}"></i>`;
168
+ });
169
+ if (replaced !== line) edits.push({ rule: 'icon-tag-markup', line: index + 1, before: line.trim(), after: replaced.trim() });
170
+ return replaced;
171
+ });
172
+ return { text: fromLines(out, trailingNewline), edits, findings: [] };
173
+ },
174
+ };
175
+
176
+ /** Split a tag's markup on top-level commas, respecting quotes. */
177
+ function splitTagArgs(markup) {
178
+ const args = [];
179
+ let current = '';
180
+ let quote = null;
181
+ for (const char of markup) {
182
+ if (quote) {
183
+ current += char;
184
+ if (char === quote) quote = null;
185
+ } else if (char === '"' || char === "'") {
186
+ quote = char;
187
+ current += char;
188
+ } else if (char === ',') {
189
+ args.push(current.trim());
190
+ current = '';
191
+ } else {
192
+ current += char;
193
+ }
194
+ }
195
+ if (current.trim()) args.push(current.trim());
196
+ return args;
197
+ }
198
+
199
+ /** The inside of a quoted arg, or null when the arg is an expression. */
200
+ function quotedValue(arg) {
201
+ return /^(['"]).*\1$/.test(arg) ? arg.slice(1, -1).trim() : null;
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Rule 1 — page.resolved.* → resolved.*
206
+ // ---------------------------------------------------------------------------
207
+ const pageResolved = {
208
+ id: 'page-resolved',
209
+ title: '`page.resolved.` → `resolved.`',
210
+ apply(text) {
211
+ const { text: out, edits } = replacePerLine('page-resolved', text, /\bpage\.resolved\./g, 'resolved.');
212
+ return { text: out, edits, findings: [] };
213
+ },
214
+ };
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Rule 2 — bracket/hardcoded theme layout values → plain layout names
218
+ // ---------------------------------------------------------------------------
219
+ const bracketLayout = {
220
+ id: 'bracket-layout',
221
+ title: 'bracket theme layout values → plain layout names',
222
+ apply(text) {
223
+ const prefix = new RegExp(
224
+ `themes/(?:\\[\\s*site\\.theme\\.id\\s*\\]|${PACKAGED_THEMES.join('|')})/`,
225
+ 'g'
226
+ );
227
+ const { lines, trailingNewline } = toLines(text);
228
+ const edits = [];
229
+ const out = lines.map((line, index) => {
230
+ if (!/^\s*layout:/.test(line)) return line;
231
+ const replaced = line.replace(prefix, '');
232
+ if (replaced !== line) edits.push({ rule: 'bracket-layout', line: index + 1, before: line.trim(), after: replaced.trim() });
233
+ return replaced;
234
+ });
235
+ return { text: fromLines(out, trailingNewline), edits, findings: [] };
236
+ },
237
+ };
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Rule 3 — {{ … }} inside quoted template-kit tag args → {% capture %} hoist
241
+ // (upstream Jekyll silently no-ops these — live somiibo ships the bug)
242
+ // ---------------------------------------------------------------------------
243
+ const TK_TAG_NAMES = Object.keys(TAGS);
244
+
245
+ const tagArgInterpolation = {
246
+ id: 'tag-arg-interpolation',
247
+ title: 'interpolated quoted tag args → {% capture %} hoist',
248
+ apply(text) {
249
+ const { lines, trailingNewline } = toLines(text);
250
+ const edits = [];
251
+ const findings = [];
252
+ const out = [];
253
+ let captureCount = 0;
254
+
255
+ const tagLine = new RegExp(`\\{%-?\\s*(${TK_TAG_NAMES.join('|')})\\b[^%]*%\\}`);
256
+ for (let i = 0; i < lines.length; i++) {
257
+ const line = lines[i];
258
+ const match = line.match(tagLine);
259
+ // Only interpolation INSIDE the tag span matters — {{ }} elsewhere on
260
+ // the line is ordinary output.
261
+ if (!match || !/\{\{[^}]*\}\}/.test(match[0])) { out.push(line); continue; }
262
+
263
+ // Rewrite quoted args containing {{ }} INSIDE the tag span only — a
264
+ // quoted interpolation elsewhere on the line (an HTML attribute) is
265
+ // ordinary output and must not be touched.
266
+ const indent = (line.match(/^\s*/) || [''])[0];
267
+ const captures = [];
268
+ const span = match[0];
269
+ const rewrittenSpan = span.replace(/(["'])((?:(?!\1).)*\{\{(?:(?!\1).)*)\1/g, (whole, quote, inner) => {
270
+ // Only hoist when the quoted string actually interpolates
271
+ if (!/\{\{.*\}\}/.test(inner)) return whole;
272
+ captureCount += 1;
273
+ const varName = `omega_migrate_arg_${captureCount}`;
274
+ captures.push(`${indent}{% capture ${varName} %}${inner}{% endcapture %}`);
275
+ return varName;
276
+ });
277
+ const rewritten = line.replace(span, rewrittenSpan);
278
+
279
+ if (captures.length === 0) {
280
+ // {{ }} on a tk-tag line but outside its quoted args — report only
281
+ findings.push({
282
+ check: 'tag-arg-interpolation', line: i + 1, severity: 'warning',
283
+ message: `\`{{ }}\` near {% ${match[1]} %} but not in a quoted arg — verify manually`,
284
+ });
285
+ out.push(line);
286
+ continue;
287
+ }
288
+
289
+ for (const capture of captures) out.push(capture);
290
+ edits.push({ rule: 'tag-arg-interpolation', line: i + 1, before: line.trim(), after: rewritten.trim() });
291
+ out.push(rewritten);
292
+ }
293
+ return { text: fromLines(out, trailingNewline), edits, findings };
294
+ },
295
+ };
296
+
297
+ // ---------------------------------------------------------------------------
298
+ // Rule 4 — forloop.parentloop.* → hoisted {% assign %} after the parent for
299
+ // (LiquidJS has no parentloop — upstream these render empty)
300
+ // ---------------------------------------------------------------------------
301
+ const PARENTLOOP_PROPS = ['index', 'index0', 'first', 'last', 'length', 'rindex', 'rindex0'];
302
+
303
+ const LOOP_TOKEN = /\{%-?\s*(for\s+\w+\s+in|endfor|raw|endraw|comment|endcomment)\b/g;
304
+
305
+ /**
306
+ * Walk one line's loop-structure tokens in order, updating the open-for stack
307
+ * (line indexes) and the inert-span flag. Returns how many tokens touched the
308
+ * for stack (0 = the line is structure-neutral).
309
+ */
310
+ function trackLoopTokens(line, lineIndex, forStack, state) {
311
+ let structural = 0;
312
+ for (const [, token] of line.matchAll(LOOP_TOKEN)) {
313
+ if (token === 'raw' || token === 'comment') { state.inert += 1; continue; }
314
+ if (token === 'endraw' || token === 'endcomment') { state.inert = Math.max(0, state.inert - 1); continue; }
315
+ if (state.inert > 0) continue;
316
+ if (token === 'endfor') { if (forStack.length) forStack.pop(); structural += 1; continue; }
317
+ forStack.push(lineIndex);
318
+ structural += 1;
319
+ }
320
+ return structural;
321
+ }
322
+
323
+ const parentloop = {
324
+ id: 'parentloop',
325
+ title: '`forloop.parentloop.*` → hoisted parent-loop {% assign %}',
326
+ apply(text) {
327
+ const { lines, trailingNewline } = toLines(text);
328
+ const edits = [];
329
+ const findings = [];
330
+
331
+ // Pass 1: locate for/endfor nesting (token-order aware — a line may open
332
+ // or close several loops) and decide which parent-for lines need which
333
+ // props hoisted. forStack holds line indexes of open {% for %}s.
334
+ const forStack = [];
335
+ const state = { inert: 0 };
336
+ const hoists = new Map(); // parent for-line index → Set of props
337
+ const lineRewrites = new Map(); // ref line index → depth of its parent for
338
+
339
+ const flag = (line, message) => findings.push({ check: 'parentloop', line, severity: 'error', message });
340
+
341
+ for (let i = 0; i < lines.length; i++) {
342
+ const line = lines[i];
343
+ const hasRef = state.inert === 0 && /forloop\.parentloop\./.test(line);
344
+
345
+ if (hasRef && /forloop\.parentloop\.parentloop/.test(line)) {
346
+ flag(i + 1, 'multi-level `forloop.parentloop.parentloop` — hoist manually');
347
+ } else if (hasRef && /\{%-?\s*(?:for\s+\w+\s+in|endfor)\b/.test(line)) {
348
+ // A ref sharing its line with for/endfor tokens is ambiguous — the
349
+ // stack state at the ref position is not the line-start state.
350
+ flag(i + 1, '`forloop.parentloop` on the same line as a loop open/close — hoist manually');
351
+ } else if (hasRef) {
352
+ if (forStack.length < 2) {
353
+ flag(i + 1, '`forloop.parentloop` outside a nested loop — remove or hoist manually');
354
+ } else if (forStack[forStack.length - 1] === forStack[forStack.length - 2]) {
355
+ // Parent and inner for share a line: an assign inserted after that
356
+ // line would land INSIDE the inner loop and read the wrong forloop.
357
+ flag(i + 1, 'parent and inner `{% for %}` share a line — hoist manually');
358
+ } else {
359
+ const parentForLine = forStack[forStack.length - 2];
360
+ const parentDepth = forStack.length - 1; // 1-based depth of the parent
361
+ const props = hoists.get(parentForLine) || new Set();
362
+ for (const [, prop] of line.matchAll(/forloop\.parentloop\.(\w+)/g)) {
363
+ if (PARENTLOOP_PROPS.includes(prop)) props.add(prop);
364
+ else flag(i + 1, `unknown parentloop property \`${prop}\` — hoist manually`);
365
+ }
366
+ hoists.set(parentForLine, props);
367
+ lineRewrites.set(i, parentDepth);
368
+ }
369
+ }
370
+
371
+ trackLoopTokens(line, i, forStack, state);
372
+ }
373
+
374
+ if (hoists.size === 0) return { text, edits, findings };
375
+
376
+ // Pass 2: emit — insert assigns right after each parent for-line (that
377
+ // line ends in the parent loop's scope: were an unclosed inner loop open
378
+ // there, pass 1 flagged the ref instead), and rewrite references with
379
+ // depth-scoped names (omega_parentloop<depth>_<prop>) so nested hoists
380
+ // never shadow each other.
381
+ const depthByFor = new Map();
382
+ {
383
+ const stack = [];
384
+ const rescanState = { inert: 0 };
385
+ for (let i = 0; i < lines.length; i++) {
386
+ trackLoopTokens(lines[i], i, stack, rescanState);
387
+ if (stack.length && stack[stack.length - 1] === i) depthByFor.set(i, stack.length);
388
+ }
389
+ }
390
+
391
+ const out = [];
392
+ for (let i = 0; i < lines.length; i++) {
393
+ let line = lines[i];
394
+ if (lineRewrites.has(i)) {
395
+ const depth = lineRewrites.get(i);
396
+ const rewritten = line.replace(/forloop\.parentloop\.(\w+)/g, (whole, prop) =>
397
+ PARENTLOOP_PROPS.includes(prop) ? `omega_parentloop${depth}_${prop}` : whole);
398
+ if (rewritten !== line) {
399
+ edits.push({ rule: 'parentloop', line: i + 1, before: line.trim(), after: rewritten.trim() });
400
+ line = rewritten;
401
+ }
402
+ }
403
+ out.push(line);
404
+ if (hoists.has(i)) {
405
+ const indent = (lines[i].match(/^\s*/) || [''])[0];
406
+ const depth = depthByFor.get(i);
407
+ for (const prop of hoists.get(i)) {
408
+ out.push(`${indent} {% assign omega_parentloop${depth}_${prop} = forloop.${prop} %}`);
409
+ edits.push({ rule: 'parentloop', line: i + 1, before: '(parent loop)', after: `{% assign omega_parentloop${depth}_${prop} = forloop.${prop} %}` });
410
+ }
411
+ }
412
+ }
413
+ return { text: fromLines(out, trailingNewline), edits, findings };
414
+ },
415
+ };
416
+
417
+ // ---------------------------------------------------------------------------
418
+ // Rule 5 — strip leading slash from include paths
419
+ // ---------------------------------------------------------------------------
420
+ const includeSlash = {
421
+ id: 'include-slash',
422
+ title: 'strip leading `/` from include paths',
423
+ apply(text) {
424
+ const { text: out, edits } = replacePerLine('include-slash', text, /(\{%-?\s*include(?:_cached)?\s+)\//g, '$1');
425
+ return { text: out, edits, findings: [] };
426
+ },
427
+ };
428
+
429
+ // ---------------------------------------------------------------------------
430
+ // Rule 7 (before 6) — page.canonical.url → {{ site.url }}{{ page.url }}
431
+ // ---------------------------------------------------------------------------
432
+ const canonicalUrl = {
433
+ id: 'canonical-url',
434
+ title: '`page.canonical.url` → `{{ site.url }}{{ page.url }}`',
435
+ apply(text) {
436
+ const { text: replaced, edits } = replacePerLine(
437
+ 'canonical-url', text,
438
+ /\{\{\s*page\.canonical\.url\s*\}\}/g,
439
+ '{{ site.url }}{{ page.url }}'
440
+ );
441
+ const findings = [];
442
+ const { lines } = toLines(replaced);
443
+ lines.forEach((line, index) => {
444
+ if (/page\.canonical/.test(line)) {
445
+ findings.push({
446
+ check: 'canonical-url', line: index + 1, severity: 'error',
447
+ message: '`page.canonical` outside a plain output — rewrite to `site.url`/`page.url` manually',
448
+ });
449
+ }
450
+ });
451
+ return { text: replaced, edits, findings };
452
+ },
453
+ };
454
+
455
+ // ---------------------------------------------------------------------------
456
+ // Rule 6 — page.<key> Jekyll frontmatter reads → top-level keys
457
+ //
458
+ // The rewrite is SCOPE-AWARE ([#541](https://github.com/Omega-JS-Stack/omega/issues/541)):
459
+ // inside a `{% for %}`/`{% tablerow %}` binding the same name, `<key>` is the
460
+ // LOOP ITEM, so the rename would silently repoint the read — `{% if tag !=
461
+ // page.tag.name %}` inside `{% for tag in … %}` becomes a comparison of the
462
+ // item with itself, an always-true exclude guard on a page that still builds.
463
+ // Those occurrences are skipped and reported; occurrences outside the loop
464
+ // still rewrite.
465
+ //
466
+ // Its own tracker, not rule 4's: that one answers "how deep, on which line"
467
+ // for the parentloop hoist and must NOT count `{% tablerow %}` (a tablerow
468
+ // exposes `tablerowloop`, never `forloop`); this one answers "which NAMES are
469
+ // bound here", where a tablerow shadows exactly like a for.
470
+ // ---------------------------------------------------------------------------
471
+ const SCOPE_TOKEN = /\{%-?\s*(?:for|tablerow)\s+(\w+)\s+in\b|\{%-?\s*(endfor|endtablerow|raw|endraw|comment|endcomment)\b/g;
472
+
473
+ /**
474
+ * Walk one line's scope tokens in order, updating the bound-name stack and the
475
+ * inert-span flag.
476
+ * @returns {string[]} the names bound by loops OPENED on this line (in scope
477
+ * for the rest of it)
478
+ */
479
+ function trackLoopScope(line, stack, state) {
480
+ const opened = [];
481
+ for (const [, name, keyword] of line.matchAll(SCOPE_TOKEN)) {
482
+ if (keyword === 'raw' || keyword === 'comment') { state.inert += 1; continue; }
483
+ if (keyword === 'endraw' || keyword === 'endcomment') { state.inert = Math.max(0, state.inert - 1); continue; }
484
+ if (state.inert > 0) continue;
485
+ if (keyword) { if (stack.length) stack.pop(); continue; }
486
+ stack.push(name);
487
+ opened.push(name);
488
+ }
489
+ return opened;
490
+ }
491
+
492
+ const pageProps = {
493
+ id: 'page-props',
494
+ title: '`page.<frontmatterKey>` → `<key>`; `page.content` → `content`; `page.slug` → `page.fileSlug`',
495
+ apply(text) {
496
+ const { lines, trailingNewline } = toLines(text);
497
+ const edits = [];
498
+ const findings = [];
499
+ const stack = [];
500
+ const state = { inert: 0 };
501
+ // The lookbehind keeps `page.` out of larger words and paths —
502
+ // `words-on-a-page.png`, `/landing-page.html`, `data.page.url` are NOT
503
+ // page-object references.
504
+ const out = lines.map((line, index) => {
505
+ const bound = new Set(stack);
506
+ for (const name of trackLoopScope(line, stack, state)) bound.add(name);
507
+ let replaced = line
508
+ .replace(/(?<![\w./-])page\.content\b/g, (whole) => (bound.has('content') ? whole : 'content'))
509
+ .replace(/(?<![\w./-])page\.slug\b/g, 'page.fileSlug');
510
+ replaced = replaced.replace(/(?<![\w./-])page\.([A-Za-z_][\w]*)\b/g, (whole, key) => {
511
+ if (VALID_PAGE_PROPS.includes(key)) return whole;
512
+ if (MANUAL_PAGE_PROPS.includes(key)) {
513
+ findings.push({
514
+ check: 'page-props', line: index + 1, severity: 'error',
515
+ message: `\`page.${key}\` has no mechanical equivalent — port manually (collections API)`,
516
+ });
517
+ return whole;
518
+ }
519
+ if (bound.has(key)) {
520
+ findings.push({
521
+ check: 'page-props', line: index + 1, severity: 'error',
522
+ message: `\`page.${key}\` left alone: an enclosing \`{% for ${key} in … %}\`/\`{% tablerow %}\` binds \`${key}\`, `
523
+ + `so the rename would read the LOOP ITEM instead of the page's \`${key}\` — hoist the page value before the loop `
524
+ + `(\`{% assign page_${key} = ${key} %}\` above it) or rename the loop variable, by hand`,
525
+ });
526
+ return whole;
527
+ }
528
+ return key;
529
+ });
530
+ if (replaced !== line) edits.push({ rule: 'page-props', line: index + 1, before: line.trim(), after: replaced.trim() });
531
+ return replaced;
532
+ });
533
+ return { text: fromLines(out, trailingNewline), edits, findings };
534
+ },
535
+ };
536
+
537
+ // ---------------------------------------------------------------------------
538
+ // Rule 9 — flat analytics spellings → providers shape
539
+ // ---------------------------------------------------------------------------
540
+ const analyticsShape = {
541
+ id: 'analytics-shape',
542
+ title: '`site.analytics.<provider>` → `site.analytics.providers.<provider>.id`',
543
+ apply(text) {
544
+ const { text: out, edits } = replacePerLine(
545
+ 'analytics-shape', text,
546
+ /\b(site|resolved)\.analytics\.(google|meta|tiktok)\b(?!\.)/g,
547
+ '$1.analytics.providers.$2.id'
548
+ );
549
+ return { text: out, edits, findings: [] };
550
+ },
551
+ };
552
+
553
+ // ---------------------------------------------------------------------------
554
+ // Rule 24 — config READS → `resolved.config.<section>`
555
+ // ([#611](https://github.com/Omega-JS-Stack/omega/issues/611)). #607 moved the
556
+ // brand config off `site.*` onto `resolved.config.*`, and the old spellings do
557
+ // not fail: `{{ site.brand.name }}` renders an EMPTY STRING, on every page that
558
+ // carries one. Rule 23 moves the frontmatter KEYS a page restates; this one
559
+ // moves the reads, in bodies and frontmatter values alike.
560
+ //
561
+ // TWO dead spellings, because the port produces both: the `site.<section>` a
562
+ // UJM page wrote by hand, and the flat `resolved.<section>` rule 1 manufactures
563
+ // out of `page.resolved.<section>`. `site.meta` is the meta WALK rather than a
564
+ // config section, so it lands on `resolved.meta`, where the walk lives.
565
+ //
566
+ // Runs AFTER rule 9: `site.analytics.<provider>` is that rule's input, and
567
+ // renaming the root first would strand the flat spelling it answers to.
568
+ // ---------------------------------------------------------------------------
569
+
570
+ /**
571
+ * What one read becomes, or null when it is not this rule's business.
572
+ * @param {{ root: string, key: string }} read
573
+ * @returns {string|null} the replacement ROOT+KEY (the dotted tail rides along)
574
+ */
575
+ function movedRead(read) {
576
+ // `meta` is not config at all — it is the page's own walk — so `site.meta`
577
+ // lands on `resolved.meta`, never under `config`.
578
+ if (read.key === 'meta') return read.root === 'site' ? 'resolved.meta' : null;
579
+ return CONFIG_SECTIONS.has(read.key) ? `resolved.config.${read.key}` : null;
580
+ }
581
+
582
+ // ---------------------------------------------------------------------------
583
+ // The SELF-REFERENCE (#671, part 2)
584
+ //
585
+ // A UJM page wrote `meta: { title: "{{ site.meta.title }}" }` to mean "use the
586
+ // site default". Rewritten it becomes `{{ resolved.meta.title }}` — and THIS
587
+ // block is what the meta walk resolves that from, so the page's <title> shipped
588
+ // the literal string `{{ resolved.meta.title }}`. There is nothing to rewrite
589
+ // it to: absence already falls through to the config default, so the read is
590
+ // DROPPED, key and all, with a finding naming the file. Both spellings go —
591
+ // a brand migrated before 0.46.0 is carrying the rewritten shape, and re-running
592
+ // migrate has to repair it rather than call the page done.
593
+ // ---------------------------------------------------------------------------
594
+
595
+ /**
596
+ * Split a YAML flow mapping's body on its TOP-LEVEL commas (quotes and nested
597
+ * brackets hold their commas).
598
+ * @param {string} body - the text between `{` and `}`
599
+ * @returns {string[]}
600
+ */
601
+ function splitFlowPairs(body) {
602
+ const pairs = [];
603
+ let depth = 0;
604
+ let quote = null;
605
+ let start = 0;
606
+ for (let i = 0; i < body.length; i++) {
607
+ const char = body[i];
608
+ if (quote) {
609
+ if (char === quote) quote = null;
610
+ continue;
611
+ }
612
+ if (char === '"' || char === "'") quote = char;
613
+ else if (char === '{' || char === '[') depth += 1;
614
+ else if (char === '}' || char === ']') depth -= 1;
615
+ else if (char === ',' && depth === 0) {
616
+ pairs.push(body.slice(start, i));
617
+ start = i + 1;
618
+ }
619
+ }
620
+ pairs.push(body.slice(start));
621
+ return pairs.filter((pair) => pair.trim() !== '');
622
+ }
623
+
624
+ /**
625
+ * The `}` closing the flow mapping opened on `lines[start]`, quote-aware — a
626
+ * brace inside a quoted scalar is text, not structure.
627
+ * @param {string[]} lines
628
+ * @param {number} start - index of the line carrying the opening brace
629
+ * @param {number} limit - one past the last line the mapping may reach
630
+ * @returns {{ line: number, column: number }|null} null when it never closes
631
+ */
632
+ function flowMappingEnd(lines, start, limit) {
633
+ let depth = 0;
634
+ let quote = null;
635
+ for (let i = start; i < limit; i++) {
636
+ const line = lines[i];
637
+ for (let c = 0; c < line.length; c++) {
638
+ const char = line[c];
639
+ if (quote) {
640
+ if (char === quote) quote = null;
641
+ continue;
642
+ }
643
+ if (char === '"' || char === "'") quote = char;
644
+ else if (char === '{') depth += 1;
645
+ else if (char === '}') {
646
+ depth -= 1;
647
+ if (depth === 0) return { line: i, column: c };
648
+ }
649
+ }
650
+ quote = null; // a quoted YAML scalar does not span lines
651
+ }
652
+ return null;
653
+ }
654
+
655
+ /**
656
+ * The lines nested under the key on `lines[opener]` — everything more indented,
657
+ * blanks skipped, up to the first line that dedents.
658
+ * @param {string[]} lines
659
+ * @param {number} opener
660
+ * @param {number} limit - one past the last line to consider (the fence)
661
+ * @returns {number[]} line indexes
662
+ */
663
+ function nestedLines(lines, opener, limit) {
664
+ const indent = lines[opener].match(/^[ \t]*/)[0].length;
665
+ const children = [];
666
+ for (let i = opener + 1; i < limit; i++) {
667
+ if (lines[i].trim() === '') continue;
668
+ if (lines[i].match(/^[ \t]*/)[0].length <= indent) break;
669
+ children.push(i);
670
+ }
671
+ return children;
672
+ }
673
+
674
+ /**
675
+ * Drop the reads of the meta walk that sit inside the page's OWN `meta:` block.
676
+ * @param {string} text
677
+ * @returns {{ text: string, edits: object[], findings: object[] }}
678
+ */
679
+ function dropSelfMetaReads(text) {
680
+ const { lines, trailingNewline } = toLines(text);
681
+ const sourceLines = [...lines];
682
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
683
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
684
+
685
+ const readLines = new Set(
686
+ templateReads(text).filter((read) => read.key === 'meta' && read.line <= fenceEnd).map((read) => read.line),
687
+ );
688
+ if (!readLines.size) return { text, edits: [], findings: [] };
689
+
690
+ const drop = new Set();
691
+ const rewritten = new Set();
692
+ const findings = [];
693
+ const say = (line, key, source) => findings.push({
694
+ check: 'config-reads', line, severity: 'warning',
695
+ message: `dropped \`${key}\` from this page's own \`meta:\` block (#671) — it read \`${source}\`, which IS the walk this block feeds, `
696
+ + 'so the page rendered the literal expression. Absence falls through to the config default',
697
+ });
698
+
699
+ let block = null; // the `meta:` block we are inside: { opener, indent, children }
700
+ const blocks = [];
701
+ for (let i = 1; i < fenceEnd; i++) {
702
+ const line = lines[i];
703
+ const blank = line.trim() === '';
704
+ const indent = blank ? Infinity : line.match(/^[ \t]*/)[0].length;
705
+ if (block && !blank && indent <= block.indent) block = null;
706
+
707
+ if (block) {
708
+ if (!blank) block.children.push(i);
709
+ continue;
710
+ }
711
+
712
+ const opener = line.match(/^([ \t]*)meta:\s*$/);
713
+ if (opener) {
714
+ block = { opener: i, indent: opener[1].length, children: [] };
715
+ blocks.push(block);
716
+ continue;
717
+ }
718
+
719
+ // The flow spelling, on ONE line (`meta: { title: "{{ site.meta.title }}" }`)
720
+ // or spread over several with the closing brace below — both are legal
721
+ // YAML, and a self-reference hides in either. Judged pair by pair, per
722
+ // line, exactly like the block form.
723
+ const flow = line.match(/^([ \t]*)meta:\s*\{/);
724
+ if (!flow) continue;
725
+ const close = flowMappingEnd(lines, i, fenceEnd);
726
+ if (!close) continue;
727
+
728
+ const open = line.indexOf('{', flow[1].length);
729
+ const segments = [];
730
+ for (let j = i; j <= close.line; j++) {
731
+ const body = lines[j].slice(j === i ? open + 1 : 0, j === close.line ? close.column : lines[j].length);
732
+ const pairs = splitFlowPairs(body);
733
+ segments.push({
734
+ index: j,
735
+ kept: !readLines.has(j + 1) ? pairs : pairs.filter((pair) => {
736
+ if (!/\{\{[^}]*\b(site|resolved)\.meta\b/.test(pair)) return true;
737
+ say(j + 1, (pair.match(/^\s*([\w-]+)\s*:/) || [null, 'meta'])[1], pair.trim());
738
+ return false;
739
+ }),
740
+ });
741
+ }
742
+
743
+ if (segments.every((segment) => segment.kept.length === 0)) {
744
+ for (let j = i; j <= close.line; j++) drop.add(j);
745
+ } else {
746
+ const last = segments.filter((segment) => segment.kept.length > 0).pop();
747
+ for (const segment of segments) {
748
+ const j = segment.index;
749
+ const head = j === i ? lines[j].slice(0, open + 1) : '';
750
+ const tail = j === close.line ? lines[j].slice(close.column) : '';
751
+ if (segment.kept.length === 0) {
752
+ // A line that carried nothing else goes; the opener and the closer
753
+ // stay to carry their braces.
754
+ if (!head && !tail) drop.add(j);
755
+ else lines[j] = `${head}${tail ? lines[j].match(/^[ \t]*/)[0] : ''}${tail}`;
756
+ } else {
757
+ const body = segment.kept.join(',') + (segment === last ? '' : ',');
758
+ lines[j] = `${head}${body}${tail}`;
759
+ }
760
+ if (lines[j] !== sourceLines[j] && !drop.has(j)) rewritten.add(j);
761
+ }
762
+ // The one-line form reads as one pair list — collapse the space the
763
+ // dropped pairs left before the closing brace.
764
+ if (i === close.line) lines[i] = lines[i].replace(/\s+\}(\s*)$/, ' }$1');
765
+ }
766
+ i = close.line;
767
+ }
768
+
769
+ for (const entry of blocks) {
770
+ for (const child of entry.children.filter((line) => readLines.has(line + 1))) {
771
+ drop.add(child);
772
+ say(child + 1, (lines[child].match(/^\s*([\w-]+)\s*:/) || [null, 'meta'])[1], lines[child].trim());
773
+ }
774
+ }
775
+
776
+ // An emptied key is a NULL key, not an absent one — and this engine's
777
+ // deepMerge lets a null REPLACE the config defaults it was supposed to fall
778
+ // through to. So every key the drop emptied goes with it, from the deepest
779
+ // nested one (an `og:` whose only child was a self-read) up to the `meta:`
780
+ // opener itself — a fixed point, because dropping one empties its parent.
781
+ for (let changed = true; changed;) {
782
+ changed = false;
783
+ for (let i = 1; i < fenceEnd; i++) {
784
+ if (drop.has(i) || !/^[ \t]*[\w-]+:\s*$/.test(lines[i])) continue;
785
+ const children = nestedLines(lines, i, fenceEnd);
786
+ if (children.length === 0 || !children.every((child) => drop.has(child))) continue;
787
+ drop.add(i);
788
+ changed = true;
789
+ }
790
+ }
791
+
792
+ if (!drop.size && !rewritten.size) return { text, edits: [], findings: [] };
793
+
794
+ // One edit per changed line: a drop the report never counted was a repair
795
+ // the codemod never wrote (the write and the file listing both gate on edits).
796
+ const edits = [...new Set([...drop, ...rewritten])].sort((a, b) => a - b).map((index) => ({
797
+ rule: 'config-reads',
798
+ line: index + 1,
799
+ before: sourceLines[index].trim(),
800
+ after: drop.has(index) ? '(removed — a page may not read the walk its own `meta:` block feeds)' : lines[index].trim(),
801
+ }));
802
+
803
+ return { text: fromLines(lines.filter((line, index) => !drop.has(index)), trailingNewline), edits, findings };
804
+ }
805
+
806
+ const configReads = {
807
+ id: 'config-reads',
808
+ title: '`site.<section>` / `resolved.<section>` reads → `resolved.config.<section>`',
809
+ apply(text) {
810
+ // A page's own `meta:` block first (#671): those reads are dropped, not
811
+ // moved, and dropping before the rewrite catches BOTH spellings on one path.
812
+ const self = dropSelfMetaReads(text);
813
+
814
+ // The census is config-sections.js's, shared with the engine's build guard
815
+ // (#611): only LIQUID context counts, and fenced code blocks and `{% raw %}`
816
+ // bodies are display, not markup (#521). Without that this rule turned
817
+ // `https://site.company.com/x` into `resolved.config.company.com` and
818
+ // rewrote the legacy spelling inside a page's own documentation of it —
819
+ // both found by the blind-verifier walk (2026-08-25).
820
+ //
821
+ // Splice from the END so every earlier offset stays valid.
822
+ const reads = templateReads(self.text).filter((read) => movedRead(read) !== null);
823
+ if (!reads.length) {
824
+ // Already migrated — idempotent (the self-reference drop may still have
825
+ // repaired a page rewritten by an older migrate, and THAT is an edit:
826
+ // the codemod writes and lists what the rules report as changed).
827
+ return { text: self.text, edits: self.edits, findings: self.findings };
828
+ }
829
+
830
+ let out = self.text;
831
+ const lines = new Map(); // line → { before, after } — one edit per changed line
832
+ for (const read of [...reads].reverse()) {
833
+ const tail = read.expression.slice(`${read.root}.${read.key}`.length);
834
+ out = out.slice(0, read.index) + movedRead(read) + tail + out.slice(read.index + read.expression.length);
835
+ }
836
+ const beforeLines = toLines(self.text).lines;
837
+ const afterLines = toLines(out).lines;
838
+ for (const read of reads) {
839
+ if (lines.has(read.line)) continue;
840
+ lines.set(read.line, { before: beforeLines[read.line - 1], after: afterLines[read.line - 1] });
841
+ }
842
+
843
+ const moved = [...lines].map(([line, { before, after }]) => ({
844
+ rule: 'config-reads', line, before: before.trim(), after: after.trim(),
845
+ }));
846
+
847
+ return {
848
+ text: out,
849
+ edits: [...self.edits, ...moved],
850
+ findings: [...self.findings, {
851
+ check: 'config-reads', line: moved[0].line, severity: 'warning',
852
+ message: 'rewrote config reads to `resolved.config.<section>` (#607) — the brand config left the `site` global, '
853
+ + 'where they rendered an empty string with no error. Check the page still reads what it meant',
854
+ }],
855
+ };
856
+ },
857
+ };
858
+
859
+ // Rule 8 (Jekyll defaults/collections → targets.web) lives in config-convert.js
860
+ // — it moves config, not template text.
861
+
862
+ // ---------------------------------------------------------------------------
863
+ // Rules 10–15 — the WM → @omega.js/client rename surface
864
+ // ([#248](https://github.com/Omega-JS-Stack/omega/issues/248))
865
+ //
866
+ // Every UJM consumer carries it (~40 sites in one brand). The markup pair is
867
+ // the dangerous half: nothing fails at BUILD time, the bindings and the
868
+ // sign-out button simply go dead at runtime. The markup rule therefore runs
869
+ // over templates AND consumer JS (a querySelector spells the same class); the
870
+ // import rules are JS-only (JS_RULES) and the frontmatter/include rules
871
+ // template-only.
872
+ // ---------------------------------------------------------------------------
873
+
874
+ // Rule 10 — the client-runtime markup hooks, renamed with the runtime (#16)
875
+ const CLIENT_MARKUP = {
876
+ 'data-wm-bind': 'data-omega-bind',
877
+ 'auth-signout-btn': 'omega-signout',
878
+ };
879
+
880
+ const clientMarkup = {
881
+ id: 'client-markup',
882
+ title: '`data-wm-bind` → `data-omega-bind`; `auth-signout-btn` → `omega-signout`',
883
+ apply(text) {
884
+ // Both ends are guarded against `-` and word chars: a class list and an
885
+ // attribute name are hyphenated namespaces, so a bare alternation would
886
+ // eat `js-auth-signout-btn` and `auth-signout-btn-large` — consumer names
887
+ // that are DIFFERENT hooks and must survive untouched.
888
+ const pattern = new RegExp(`(?<![\\w-])(?:${Object.keys(CLIENT_MARKUP).join('|')})(?![\\w-])`, 'g');
889
+ const { text: out, edits } = replacePerLine('client-markup', text, pattern, (whole) => CLIENT_MARKUP[whole]);
890
+ return { text: out, edits, findings: [] };
891
+ },
892
+ };
893
+
894
+ // ---------------------------------------------------------------------------
895
+ // Rule 11 — the client block's frontmatter key names: `web_manager:` →
896
+ // `client:` (#1: it configures @omega.js/client, and there is no dual-read) and
897
+ // its `cookieConsent:` sub-key → `consent:` (#383: the banner became a real
898
+ // consent gate, and a page that disabled the old name would silently stop
899
+ // disabling anything)
900
+ // ---------------------------------------------------------------------------
901
+ const clientFrontmatter = {
902
+ id: 'client-frontmatter',
903
+ title: 'frontmatter `web_manager:` → `client:`, `cookieConsent:` → `consent:`',
904
+ apply(text) {
905
+ const { lines, trailingNewline } = toLines(text);
906
+ // Scoped to the leading `---` fence: a `web_manager:` in the body is prose
907
+ // or data, never the client-config key.
908
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
909
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
910
+
911
+ const edits = [];
912
+ const out = lines.map((line, index) => {
913
+ if (index === 0 || index >= fenceEnd) return line;
914
+ // `cookieConsent` is always INDENTED here — it only ever existed as a
915
+ // sub-key of the client block, never at the frontmatter's top level.
916
+ const replaced = line
917
+ .replace(/^web_manager:/, 'client:')
918
+ .replace(/^(\s+)cookieConsent:/, '$1consent:');
919
+ if (replaced !== line) edits.push({ rule: 'client-frontmatter', line: index + 1, before: line.trim(), after: replaced.trim() });
920
+ return replaced;
921
+ });
922
+ return { text: fromLines(out, trailingNewline), edits, findings: [] };
923
+ },
924
+ };
925
+
926
+ // ---------------------------------------------------------------------------
927
+ // Rule 12 — theme-prefixed include paths → the plain layered path
928
+ // (runs AFTER include-slash, which owns the leading `/`)
929
+ // ---------------------------------------------------------------------------
930
+ const includeThemePath = {
931
+ id: 'include-theme-path',
932
+ title: '`{% include themes/<id>/… %}` → the layered include path',
933
+ apply(text) {
934
+ const pattern = new RegExp(
935
+ `(\\{%-?\\s*include(?:_cached)?\\s+)themes/(?:\\[\\s*site\\.theme\\.id\\s*\\]|${PACKAGED_THEMES.join('|')})/`,
936
+ 'g'
937
+ );
938
+ const { text: out, edits } = replacePerLine('include-theme-path', text, pattern, '$1');
939
+ return { text: out, edits, findings: [] };
940
+ },
941
+ };
942
+
943
+ // ---------------------------------------------------------------------------
944
+ // Rule 13 — the web-manager package + its singleton identifier
945
+ // (WebManager is not an OMEGA concept: the package is @omega.js/client and the
946
+ // singleton is `omega`, subpath modules included)
947
+ // ---------------------------------------------------------------------------
948
+ const clientImport = {
949
+ id: 'client-import',
950
+ title: "`'web-manager'` → `'@omega.js/client'`; the `webManager` singleton → `omega`",
951
+ apply(text) {
952
+ const { lines, trailingNewline } = toLines(text);
953
+ const edits = [];
954
+ const out = lines.map((line, index) => {
955
+ const replaced = line
956
+ .replace(/(["'])web-manager(\/[^"']*)?\1/g, (whole, quote, subpath) => `${quote}@omega.js/client${subpath || ''}${quote}`)
957
+ .replace(/\bwebManager\b/g, 'omega');
958
+ if (replaced !== line) edits.push({ rule: 'client-import', line: index + 1, before: line.trim(), after: replaced.trim() });
959
+ return replaced;
960
+ });
961
+ return { text: fromLines(out, trailingNewline), edits, findings: [] };
962
+ },
963
+ };
964
+
965
+ // ---------------------------------------------------------------------------
966
+ // Rule 14 — the `__main_assets__/js/libs/*` runtime libs
967
+ //
968
+ // form-manager MOVED (same class, new home). authorized-fetch did not: its
969
+ // successor is the singleton's own `omega.request()`, so the import line goes
970
+ // and the calls are renamed — but the OPTIONS are the client's, not
971
+ // wonderful-fetch's, so every rewritten call is reported for review.
972
+ //
973
+ // The rename alone shipped two dead shapes with no error at all
974
+ // ([#594](https://github.com/Omega-JS-Stack/omega/issues/594)), so the rewrite
975
+ // carries them too:
976
+ // - `response: 'json'` — @omega.js/client parses by content type and RETURNS
977
+ // the body, so the option means nothing. Dropped from the rewritten call.
978
+ // - `err.status` — the HTTP status lives on `error.code`
979
+ // (packages/client/src/modules/request.js), so `if (err.status === 429)`
980
+ // compared `undefined` and the whole rate-limit branch went dark. Every
981
+ // `.status` read on a CAUGHT error in a rewritten file becomes `.code`.
982
+ // What is not mechanically rewritable is named at its line instead: a
983
+ // non-json `response`, options behind an identifier, a `.status` read on
984
+ // something that is not a catch binding.
985
+ // ---------------------------------------------------------------------------
986
+ const FORM_MANAGER_SPECIFIER = /(["'])__main_assets__\/js\/libs\/form-manager\.js\1/g;
987
+ const AUTHORIZED_FETCH_IMPORT = /^\s*import\s+[\w{},\s*]+\s+from\s+["']__main_assets__\/js\/libs\/authorized-fetch\.js["'];?\s*$/;
988
+ const CLIENT_DEFAULT_IMPORT = /import\s+omega\s+from\s+["']@omega\.js\/client["']/;
989
+
990
+ // `catch (err)`, `.catch((err) =>` and `.catch(err =>` — the three ways a
991
+ // consumer binds the error whose `.status` the rewrite has to move. The arrow
992
+ // with no parens is the commonest of them, and its terminator is the `=>`.
993
+ const CATCH_PARAM = /\bcatch\s*\(\s*\(?\s*([A-Za-z_$][\w$]*)\s*(?:[),]|=>)/g;
994
+ // One `response: <string>` option, with whichever comma separates it. The
995
+ // replacer keeps ONE of the two commas so the neighbours stay separated.
996
+ const RESPONSE_OPTION = /(,\s*)?\bresponse\s*:\s*(['"])([^'"]*)\2(\s*,)?/;
997
+ const STATUS_READ = /\b([A-Za-z_$][\w$]*)\.status\b/g;
998
+
999
+ // A `/` that opens a REGEX rather than dividing: what may precede one is an
1000
+ // operator, an opening bracket, or a keyword — never a value.
1001
+ const REGEX_PRECEDER = /(?:^|[(,=:[!&|?{};+\-*%~^<>]|\b(?:return|typeof|case|in|of|do|else|yield|await|new|delete|void))\s*$/;
1002
+
1003
+ /**
1004
+ * The end of the string/template/regex literal opening at `start`.
1005
+ * @param {string} text
1006
+ * @param {number} start - index of the opening delimiter
1007
+ * @param {string} close - the delimiter that ends it
1008
+ * @param {boolean} multiline - whether it may cross a newline
1009
+ * @returns {number} index of the closing delimiter, -1 unterminated
1010
+ */
1011
+ function literalEnd(text, start, close, multiline) {
1012
+ for (let i = start + 1; i < text.length; i++) {
1013
+ if (text[i] === '\\') i += 1;
1014
+ else if (text[i] === close) return i;
1015
+ else if (!multiline && text[i] === '\n') return -1;
1016
+ }
1017
+ return -1;
1018
+ }
1019
+
1020
+ /**
1021
+ * Walk JS source from `start`, handing `visit(char, index)` every character
1022
+ * that is real CODE — string, template, comment and regex bodies are SKIPPED.
1023
+ * A paren inside a url (`'/search('`) is text, and counting it desynced every
1024
+ * span this rule measures, which handed the option strip unrelated code.
1025
+ * @param {string} text
1026
+ * @param {number} start
1027
+ * @param {function(string, number): (number|undefined)} visit - a returned index stops the walk
1028
+ * @returns {number|null} what `visit` returned, or null when nothing stopped it
1029
+ */
1030
+ function walkCode(text, start, visit) {
1031
+ for (let i = start; i < text.length; i++) {
1032
+ const char = text[i];
1033
+ const next = text[i + 1];
1034
+
1035
+ if (char === '/' && next === '/') {
1036
+ const line = text.indexOf('\n', i);
1037
+ if (line < 0) return null;
1038
+ i = line;
1039
+ continue;
1040
+ }
1041
+ if (char === '/' && next === '*') {
1042
+ const close = text.indexOf('*/', i + 2);
1043
+ if (close < 0) return null;
1044
+ i = close + 1;
1045
+ continue;
1046
+ }
1047
+ if (char === '"' || char === "'" || char === '`') {
1048
+ const close = literalEnd(text, i, char, char === '`');
1049
+ if (close < 0) return null;
1050
+ i = close;
1051
+ continue;
1052
+ }
1053
+ if (char === '/' && REGEX_PRECEDER.test(text.slice(Math.max(0, i - 12), i))) {
1054
+ const close = literalEnd(text, i, '/', false);
1055
+ if (close < 0) return null;
1056
+ i = close;
1057
+ continue;
1058
+ }
1059
+
1060
+ const stop = visit(char, i);
1061
+ if (stop !== undefined) return stop;
1062
+ }
1063
+ return null;
1064
+ }
1065
+
1066
+ /**
1067
+ * The index of the character closing the group opened at `open`.
1068
+ * @param {string} text
1069
+ * @param {number} open - index of the `(` or `{`
1070
+ * @returns {number|null} null when it never closes
1071
+ */
1072
+ function groupEnd(text, open) {
1073
+ const closer = text[open] === '(' ? ')' : '}';
1074
+ let depth = 0;
1075
+ return walkCode(text, open, (char, index) => {
1076
+ if (char === text[open]) depth += 1;
1077
+ else if (char === closer) {
1078
+ depth -= 1;
1079
+ if (depth === 0) return index;
1080
+ }
1081
+ return undefined;
1082
+ });
1083
+ }
1084
+
1085
+ /**
1086
+ * The end of the argument list opened at (entry, column) — parens balanced
1087
+ * across lines, string and comment bodies skipped. A call's options object is
1088
+ * the span this walk covers.
1089
+ * @param {Array<{ text: string }>} entries - the file's surviving lines
1090
+ * @param {number} start - index of the entry carrying the opening paren
1091
+ * @param {number} column - offset of the opening paren in that entry
1092
+ * @returns {number} index of the entry carrying the closing paren
1093
+ */
1094
+ function argumentSpanEnd(entries, start, column) {
1095
+ const text = entries.slice(start).map((entry) => entry.text).join('\n');
1096
+ const close = groupEnd(text, column);
1097
+ // Never balanced: the span is the call's own line and nothing is stripped
1098
+ // below it. A run of the file's remaining lines is how a shape this walk
1099
+ // cannot read turns into edits to code that has nothing to do with the call.
1100
+ if (close === null) return start;
1101
+ return start + (text.slice(0, close).match(/\n/g) || []).length;
1102
+ }
1103
+
1104
+ /**
1105
+ * Every catch binding in a file, with the LINE SPAN of the block it is bound
1106
+ * over: a `{ … }` body, or an arrow's bare expression (which ends where the
1107
+ * `.catch(` call does).
1108
+ * @param {string} text - the whole source
1109
+ * @returns {Array<{ name: string, start: number, end: number }>} 1-based lines
1110
+ */
1111
+ function catchScopes(text) {
1112
+ const lineAt = (index) => (text.slice(0, index).match(/\n/g) || []).length + 1;
1113
+ const scopes = [];
1114
+
1115
+ CATCH_PARAM.lastIndex = 0;
1116
+ let match;
1117
+ while ((match = CATCH_PARAM.exec(text)) !== null) {
1118
+ // Past whatever is left of the header (`) => `, a second parameter) to the
1119
+ // first character of the body itself.
1120
+ const body = walkCode(text, match.index + match[0].length, (char, index) => (/[\s),=>]/.test(char) ? undefined : index));
1121
+ if (body === null) continue;
1122
+ const end = text[body] === '{' ? groupEnd(text, body) : groupEnd(text, text.indexOf('(', match.index));
1123
+ if (end === null) continue;
1124
+ scopes.push({ name: match[1], start: lineAt(match.index), end: lineAt(end) });
1125
+ }
1126
+ return scopes;
1127
+ }
1128
+
1129
+ const clientLibs = {
1130
+ id: 'client-libs',
1131
+ title: '`__main_assets__/js/libs/form-manager.js` → the client module; `authorizedFetch()` → `omega.request()`',
1132
+ apply(text) {
1133
+ const { lines, trailingNewline } = toLines(text);
1134
+ const findings = [];
1135
+ // Every surviving line with the ORIGINAL line number it reports as: the
1136
+ // import line and an option-only line are removed, so the two numbering
1137
+ // schemes part ways immediately and every finding owes the source's.
1138
+ const entries = [];
1139
+ const removed = new Map(); // original line → what the edit records as `after`
1140
+ const callEntries = [];
1141
+ let rewroteCall = false;
1142
+
1143
+ for (let i = 0; i < lines.length; i++) {
1144
+ const line = lines[i];
1145
+
1146
+ if (AUTHORIZED_FETCH_IMPORT.test(line)) {
1147
+ removed.set(i + 1, '(removed — omega.request() replaces it)');
1148
+ continue;
1149
+ }
1150
+
1151
+ const replaced = line
1152
+ .replace(FORM_MANAGER_SPECIFIER, '$1@omega.js/client/modules/form-manager.js$1')
1153
+ .replace(/\bauthorizedFetch\(/g, 'omega.request(');
1154
+ if (/\bauthorizedFetch\(/.test(line)) {
1155
+ rewroteCall = true;
1156
+ callEntries.push(entries.length);
1157
+ findings.push({
1158
+ check: 'client-libs', line: i + 1, severity: 'warning',
1159
+ message: '`authorizedFetch(…)` → `omega.request(…)`: same (url, options) shape and the same Bearer attach, and the two dead shapes went with it '
1160
+ + '(`response: \'json\'` dropped — the parsed body IS the return value; `err.status` → `err.code`, where @omega.js/client puts the HTTP status). '
1161
+ + 'What is left is yours to verify: `output`, and the route segment (`/backend-manager/` is now `/omega/`)',
1162
+ });
1163
+ }
1164
+ entries.push({ line: i + 1, text: replaced });
1165
+ }
1166
+
1167
+ if (rewroteCall) {
1168
+ // ---- the options of each rewritten call
1169
+ for (const start of callEntries) {
1170
+ const column = entries[start].text.indexOf('omega.request(') + 'omega.request'.length;
1171
+ const end = argumentSpanEnd(entries, start, column);
1172
+ let sawOptions = false;
1173
+
1174
+ for (let i = start; i <= end; i++) {
1175
+ const match = entries[i].text.match(RESPONSE_OPTION);
1176
+ if (!match) continue;
1177
+ sawOptions = true;
1178
+ if (match[3] !== 'json') {
1179
+ findings.push({
1180
+ check: 'client-libs', line: entries[i].line, severity: 'warning',
1181
+ message: `\`response: '${match[3]}'\` has no @omega.js/client equivalent — \`omega.request\` parses by content type and returns the body. Port this call's output handling by hand`,
1182
+ });
1183
+ continue;
1184
+ }
1185
+ const stripped = entries[i].text
1186
+ .replace(RESPONSE_OPTION, (whole, lead, quote, value, trail) => (lead && trail ? trail : ''))
1187
+ .replace(/\{\s+\}/, '{}');
1188
+ if (stripped.trim() === '') removed.set(entries[i].line, '(removed — omega.request returns the parsed body)');
1189
+ else entries[i].text = stripped;
1190
+ }
1191
+
1192
+ // Options behind an identifier (`omega.request(url, OPTIONS)`): a text
1193
+ // codemod cannot see inside them, and they are exactly where the dead
1194
+ // wonderful-fetch keys hide.
1195
+ const spanText = entries.slice(start, end + 1).map((entry) => entry.text).join('\n');
1196
+ if (!sawOptions && /,\s*[A-Za-z_$][\w$]*\s*\)/.test(spanText)) {
1197
+ findings.push({
1198
+ check: 'client-libs', line: entries[start].line, severity: 'warning',
1199
+ message: 'the options come from a variable — check it by hand for the wonderful-fetch keys (`response`, `output`) @omega.js/client does not have',
1200
+ });
1201
+ }
1202
+ }
1203
+
1204
+ // ---- the error shape: authorizedFetch WAS this file's fetch, so an
1205
+ // error caught here is one of its errors — but only INSIDE the block that
1206
+ // binds it. Rewriting every `<name>.status` in the file by name renamed a
1207
+ // same-named parameter of an unrelated function.
1208
+ const scopes = catchScopes(lines.join('\n'));
1209
+
1210
+ for (const entry of entries) {
1211
+ entry.text = entry.text.replace(STATUS_READ, (read, identifier) => {
1212
+ const bound = scopes.some((scope) => scope.name === identifier && entry.line >= scope.start && entry.line <= scope.end);
1213
+ if (bound) return `${identifier}.code`;
1214
+ findings.push({
1215
+ check: 'client-libs', line: entry.line, severity: 'warning',
1216
+ message: `left \`${read}\` alone — @omega.js/client puts the HTTP status on \`error.code\` (request.js), and only the author knows whose \`.status\` this is`,
1217
+ });
1218
+ return read;
1219
+ });
1220
+ }
1221
+ }
1222
+
1223
+ // The edits, in source order: a removed line, then any line whose text moved.
1224
+ const edits = [];
1225
+ for (const entry of entries) {
1226
+ const before = lines[entry.line - 1];
1227
+ if (removed.has(entry.line)) continue;
1228
+ if (entry.text !== before) edits.push({ rule: 'client-libs', line: entry.line, before: before.trim(), after: entry.text.trim() });
1229
+ }
1230
+ for (const [line, after] of removed) edits.push({ rule: 'client-libs', line, before: lines[line - 1].trim(), after });
1231
+ edits.sort((a, b) => a.line - b.line);
1232
+
1233
+ // `omega.request()` needs the singleton in scope. Every real consumer that
1234
+ // called authorizedFetch also imported web-manager (rule 13 renames that
1235
+ // import ahead of this one), so a miss here is a genuinely odd file — say
1236
+ // so rather than emit a module that throws on first call.
1237
+ const result = fromLines(entries.filter((entry) => !removed.has(entry.line)).map((entry) => entry.text), trailingNewline);
1238
+ if (rewroteCall && !CLIENT_DEFAULT_IMPORT.test(result)) {
1239
+ findings.push({
1240
+ check: 'client-libs', line: 1, severity: 'error',
1241
+ message: 'rewrote `authorizedFetch()` to `omega.request()` but the file has no client singleton — add `import omega from \'@omega.js/client\';`',
1242
+ });
1243
+ }
1244
+
1245
+ return { text: result, edits, findings };
1246
+ },
1247
+ };
1248
+
1249
+ // ---------------------------------------------------------------------------
1250
+ // Rule 21 — Kramdown inline attribute lists
1251
+ // ([#565](https://github.com/Omega-JS-Stack/omega/issues/565))
1252
+ //
1253
+ // UJM page bodies wear IALs (`[Get your API key](/account){: .btn .btn-lg }`)
1254
+ // because Jekyll renders markdown with Kramdown. `@omega.js/web` renders it
1255
+ // with bare markdown-it, so the link ships UNSTYLED and the `{: … }` PRINTS on
1256
+ // the page. The framework stays plugin-free (Ian's ruling 2026-08-21: no
1257
+ // standing compat machinery in framework runs), so the conversion is here,
1258
+ // once: a class-only IAL on a link IS an anchor with that class list.
1259
+ //
1260
+ // MARKDOWN FILES ONLY. An `.html` page was never markdown-rendered — under
1261
+ // Jekyll too its `[text](url){: … }` was literal text — so rewriting it there
1262
+ // would invent a link the legacy page never had.
1263
+ //
1264
+ // Anything else wearing an IAL (a block-level one under a heading, a link IAL
1265
+ // carrying an id or an attribute) is reported: markdown-it prints all of them,
1266
+ // and their targets are not one mechanical shape.
1267
+ // ---------------------------------------------------------------------------
1268
+ const MARKDOWN_FILE = /\.(?:md|markdown)$/i;
1269
+ const CLASS_ONLY_LINK_IAL = /\[([^\]\n]*)\]\((\S+)\)\{:\s*((?:\.[\w-]+\s*)+)\}/g;
1270
+ const SURVIVING_IAL = /\{:(?:\s|[.#])/;
1271
+
1272
+ const markdownIal = {
1273
+ id: 'markdown-ial',
1274
+ title: '`[text](url){: .a .b }` → `<a href="url" class="a b">text</a>`',
1275
+ apply(text, ctx = {}) {
1276
+ if (!MARKDOWN_FILE.test(ctx.filePath || '')) return { text, edits: [], findings: [] };
1277
+
1278
+ const { text: out, edits } = replacePerLine(
1279
+ 'markdown-ial', text, CLASS_ONLY_LINK_IAL,
1280
+ (whole, label, href, classes) => `<a href="${href}" class="${classes.trim().split(/\s+/).map((name) => name.slice(1)).join(' ')}">${label}</a>`
1281
+ );
1282
+
1283
+ const findings = [];
1284
+ const { lines } = toLines(out);
1285
+ lines.forEach((line, index) => {
1286
+ if (!SURVIVING_IAL.test(line)) return;
1287
+ findings.push({
1288
+ check: 'markdown-ial', line: index + 1, severity: 'error',
1289
+ message: 'Kramdown inline attribute list survives — markdown-it does not read them, so this `{: … }` PRINTS on the page '
1290
+ + 'and whatever it decorated renders unstyled. A class-only IAL on a link converts automatically; this one carries an id, '
1291
+ + 'an attribute, or a block-level target — write the element as HTML by hand',
1292
+ });
1293
+ });
1294
+ return { text: out, edits, findings };
1295
+ },
1296
+ };
1297
+
1298
+ // ---------------------------------------------------------------------------
1299
+ // Rule 20 — the retired `modules/adunits/*` includes
1300
+ // ([#559](https://github.com/Omega-JS-Stack/omega/issues/559))
1301
+ //
1302
+ // The ads rebuild (docs/web/ads-system.md) retired the UJM adunit includes for
1303
+ // ONE section, `verts/unit`, whose fallback ladder covers what adsense.html
1304
+ // and promo-server.html did separately. An include the engine cannot resolve
1305
+ // renders nothing and fails nothing, so a ported page loses its ad unit
1306
+ // silently.
1307
+ //
1308
+ // Only adsense.html maps mechanically: `type` carries, and UJM's `vert-size`
1309
+ // (named around Liquid's built-in `size` filter) IS the section's `size`.
1310
+ // Values pass through verbatim — a section tag's inline args are Liquid
1311
+ // expressions, and its strings liquify at the call site.
1312
+ //
1313
+ // Runs AFTER include-slash, which owns the leading `/`.
1314
+ // ---------------------------------------------------------------------------
1315
+ const ADUNIT_INCLUDE = /\{%(-?)\s*include(?:_cached)?\s+modules\/adunits\/([\w-]+)\.html([^%]*?)(-?)%\}/g;
1316
+ const INCLUDE_ARG = /([\w-]+)\s*=\s*("[^"]*"|'[^']*'|\S+)/g;
1317
+ const ADUNIT_ARGS = { type: 'type', 'vert-size': 'size' };
1318
+ const VERT_UNIT_POINTER = 'the successor is `{% section "verts/unit", type: …, size: … %}` (docs/web/ads-system.md)';
1319
+
1320
+ const adunitSection = {
1321
+ id: 'adunit-section',
1322
+ title: '`{% include modules/adunits/adsense.html %}` → `{% section "verts/unit" %}`',
1323
+ apply(text) {
1324
+ const { lines, trailingNewline } = toLines(text);
1325
+ const edits = [];
1326
+ const findings = [];
1327
+ const out = lines.map((line, index) => {
1328
+ const replaced = line.replace(ADUNIT_INCLUDE, (whole, open, unit, argText, close) => {
1329
+ if (unit !== 'adsense') {
1330
+ findings.push({
1331
+ check: 'adunit-section', line: index + 1, severity: 'error',
1332
+ message: `\`modules/adunits/${unit}.html\` is RETIRED — the include resolves to nothing and the unit silently vanishes. `
1333
+ + `${VERT_UNIT_POINTER}; port this one by hand (in-house inventory is \`type: "house"\` with \`vert_id\`)`,
1334
+ });
1335
+ return whole;
1336
+ }
1337
+
1338
+ const pairs = [];
1339
+ const dropped = [];
1340
+ for (const [, key, value] of argText.matchAll(INCLUDE_ARG)) {
1341
+ if (ADUNIT_ARGS[key]) pairs.push(`${ADUNIT_ARGS[key]}: ${value}`);
1342
+ else dropped.push(key);
1343
+ }
1344
+ if (dropped.length > 0) {
1345
+ findings.push({
1346
+ check: 'adunit-section', line: index + 1, severity: 'warning',
1347
+ message: `\`${dropped.join('`, `')}\` did not carry onto \`verts/unit\` — the section has no such arg. `
1348
+ + 'The AdSense slot comes from `advertising.providers.adsense` config, and the unit owns its own markup',
1349
+ });
1350
+ }
1351
+ return `{%${open} section "verts/unit"${pairs.map((pair) => `, ${pair}`).join('')} ${close}%}`;
1352
+ });
1353
+ if (replaced !== line) edits.push({ rule: 'adunit-section', line: index + 1, before: line.trim(), after: replaced.trim() });
1354
+ return replaced;
1355
+ });
1356
+ return { text: fromLines(out, trailingNewline), edits, findings };
1357
+ },
1358
+ };
1359
+
1360
+ // ---------------------------------------------------------------------------
1361
+ // Rule 19 — the UJM library accessor
1362
+ // ([#560](https://github.com/Omega-JS-Stack/omega/issues/560))
1363
+ //
1364
+ // Rule 13 renames the singleton and stops there, so `webManager.uj()` ships as
1365
+ // `omega.uj()` — build-clean and DEAD (`@omega.js/client` has no `uj`; the
1366
+ // first call is a TypeError). The successor is `omega.library()`, the accessor
1367
+ // the web runtime installs on the singleton (core/js/main.js).
1368
+ //
1369
+ // The rewrite claims the two receivers that ARE the singleton and the no-arg
1370
+ // call `uj()` always was. Anything else wearing `.uj(` is reported: an unknown
1371
+ // receiver is not mechanically this accessor, and a silent rewrite there would
1372
+ // trade one runtime TypeError for another.
1373
+ // ---------------------------------------------------------------------------
1374
+ const UJ_ACCESSOR = /\b(?:webManager|omega)\.uj\(\s*\)/g;
1375
+ const UJ_SURVIVOR = /\.uj\(/;
1376
+
1377
+ const ujLibrary = {
1378
+ id: 'uj-library',
1379
+ title: '`webManager.uj()`/`omega.uj()` → `omega.library()`',
1380
+ apply(text) {
1381
+ const { text: out, edits } = replacePerLine('uj-library', text, UJ_ACCESSOR, 'omega.library()');
1382
+ const findings = [];
1383
+ const { lines } = toLines(out);
1384
+ lines.forEach((line, index) => {
1385
+ if (!UJ_SURVIVOR.test(line)) return;
1386
+ findings.push({
1387
+ check: 'uj-library', line: index + 1, severity: 'error',
1388
+ message: '`.uj(` survives — `@omega.js/client` has no `uj()`, so this call is a runtime TypeError. '
1389
+ + 'The UJM library accessor is `omega.library()` on the client singleton; rewrite this receiver by hand',
1390
+ });
1391
+ });
1392
+ return { text: out, edits, findings };
1393
+ },
1394
+ };
1395
+
1396
+ // ---------------------------------------------------------------------------
1397
+ // Rule 15 — the service-worker entry's framework import
1398
+ // (only the /service-worker subpath: the bare `ultimate-jekyll-manager` import
1399
+ // is the asset layer's main.js, owned by consumer-assets.js)
1400
+ // ---------------------------------------------------------------------------
1401
+ const serviceWorkerImport = {
1402
+ id: 'service-worker-import',
1403
+ title: "`'ultimate-jekyll-manager/service-worker'` → `'@omega.js/web/service-worker'`",
1404
+ apply(text) {
1405
+ const { text: out, edits } = replacePerLine(
1406
+ 'service-worker-import', text,
1407
+ /(["'])ultimate-jekyll-manager\/service-worker\1/g,
1408
+ '$1@omega.js/web/service-worker$1'
1409
+ );
1410
+ return { text: out, edits, findings: [] };
1411
+ },
1412
+ };
1413
+
1414
+ // ---------------------------------------------------------------------------
1415
+ // Rule 16 — the legacy classy gradient utilities → the v2 dotgrid hero
1416
+ // ([#296](https://github.com/Omega-JS-Stack/omega/issues/296))
1417
+ //
1418
+ // UJM-era heroes wear `.gradient-animated` (a 5s shimmer over the gradient
1419
+ // background) and `.gradient-grain` (a ::before noise overlay). Classy v2
1420
+ // ships NEITHER — its binding constraint is zero gradients (classy-v2
1421
+ // DIRECTION.md) — so those classes silently do nothing on a ported page.
1422
+ // The conversion is once, here: v2's Marketing DNA puts a "subtle dotted grid
1423
+ // (masked)" behind every hero, and that is `.omega-dotgrid`
1424
+ // (themes/classy/css/base/_utilities.scss) — the same shape the grain had, a
1425
+ // masked ::before backdrop with the content lifted above it. The animated
1426
+ // half becomes the dotgrid's LIVE canvas, opted in per element with
1427
+ // `data-omega-dotfield` exactly as every packaged hero authors the pair. Both
1428
+ // classes on one element collapse to ONE `omega-dotgrid`.
1429
+ //
1430
+ // `.bg-gradient-*` is NOT in scope: v2 deliberately keeps those names,
1431
+ // neutralized to flat token paint for straggler markup.
1432
+ // ---------------------------------------------------------------------------
1433
+ const LEGACY_GRADIENT_CLASSES = /(?<![\w-])gradient-(?:animated|grain)(?![\w-])/g;
1434
+ const GRADIENT_ANIMATED = /(?<![\w-])gradient-animated(?![\w-])/;
1435
+ const DOTGRID_CLASS = /(?<![\w-])omega-dotgrid(?![\w-])/;
1436
+ // Each occurrence with the whitespace ahead of it — dropping a duplicate takes
1437
+ // its separator along instead of leaving a double space in the class list.
1438
+ const DOTGRID_OCCURRENCE = /\s*(?<![\w-])omega-dotgrid(?![\w-])/g;
1439
+ const CLASS_ATTRIBUTE = /class\s*=\s*(["'])([^"']*)\1/g;
1440
+ const LIQUID_VALUE = /\{[{%]/;
1441
+
1442
+ const gradientUtilities = {
1443
+ id: 'gradient-utilities',
1444
+ title: '`gradient-animated`/`gradient-grain` → `omega-dotgrid` (+ `data-omega-dotfield` for the motion)',
1445
+ apply(text) {
1446
+ const { lines, trailingNewline } = toLines(text);
1447
+ const edits = [];
1448
+ const out = lines.map((line, index) => {
1449
+ let replaced = line.replace(LEGACY_GRADIENT_CLASSES, 'omega-dotgrid');
1450
+ if (replaced === line) return line;
1451
+
1452
+ replaced = replaced.replace(CLASS_ATTRIBUTE, (whole, quote, value) => {
1453
+ // A class list Liquid assembles may emit its dotgrid conditionally —
1454
+ // there a "duplicate" is the only class the other branch has, so the
1455
+ // literal duplicate (harmless in a class attribute) stays.
1456
+ if (LIQUID_VALUE.test(value)) return whole;
1457
+ let kept = false;
1458
+ const collapsed = value.replace(DOTGRID_OCCURRENCE, (occurrence) => {
1459
+ if (kept) return '';
1460
+ kept = true;
1461
+ return occurrence;
1462
+ });
1463
+ return `class=${quote}${collapsed}${quote}`;
1464
+ });
1465
+
1466
+ // The live canvas rides the element that carries the class — and only
1467
+ // when the page asked for motion in the first place.
1468
+ if (GRADIENT_ANIMATED.test(line) && !/data-omega-dotfield/.test(replaced)) {
1469
+ replaced = replaced.replace(CLASS_ATTRIBUTE, (whole, quote, value) =>
1470
+ (DOTGRID_CLASS.test(value) ? `${whole} data-omega-dotfield` : whole));
1471
+ }
1472
+
1473
+ edits.push({ rule: 'gradient-utilities', line: index + 1, before: line.trim(), after: replaced.trim() });
1474
+ return replaced;
1475
+ });
1476
+ return { text: fromLines(out, trailingNewline), edits, findings: [] };
1477
+ },
1478
+ };
1479
+
1480
+ // ---------------------------------------------------------------------------
1481
+ // Rule 17 — the dead `asset_path` frontmatter key
1482
+ // ([#470](https://github.com/Omega-JS-Stack/omega/issues/470))
1483
+ //
1484
+ // UJM pages used it to borrow another page's module (agents/new.md carrying
1485
+ // `asset_path: dashboard/agents/edit`). Page assets now resolve by URL ALONE
1486
+ // (spec §7), so the key is inert: the page renders and ships with no js/css.
1487
+ // Nothing is rewritten — the successor is a FILE the porter adds, so this is a
1488
+ // finding that names the key and the idiom, never a generic ignored-key note.
1489
+ //
1490
+ // The successor named FIRST is the `[name]` wildcard family file, because the
1491
+ // key's real job was a FAMILY of URLs sharing one module — the re-exporting
1492
+ // index.js is the one-off escape hatch, second (#470 amendment).
1493
+ // ---------------------------------------------------------------------------
1494
+ const ASSET_PATH_KEY = /^asset_path\s*:/;
1495
+
1496
+ const assetPath = {
1497
+ id: 'asset-path',
1498
+ title: '`asset_path:` frontmatter → a `[name]` wildcard page module (reported, never rewritten)',
1499
+ apply(text) {
1500
+ const { lines } = toLines(text);
1501
+ // Scoped to the leading `---` fence: `asset_path:` in the body is prose.
1502
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
1503
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
1504
+
1505
+ const findings = [];
1506
+ lines.forEach((line, index) => {
1507
+ if (index === 0 || index >= fenceEnd || !ASSET_PATH_KEY.test(line)) return;
1508
+ findings.push({
1509
+ check: 'asset-path', line: index + 1, severity: 'error',
1510
+ message: '`asset_path` is DEAD — page assets resolve by URL alone (spec §7), so this page ships with NO js/css. '
1511
+ + 'A FAMILY of pages sharing one module is a `[name]` wildcard file: `js/pages/blog/[slug].js` + `css/pages/blog/[slug].scss` '
1512
+ + 'serve every /blog/<slug> page, no frontmatter involved. '
1513
+ + 'For a ONE-OFF page borrowing another page\'s module: a re-exporting `js/pages/<this page>/index.js` '
1514
+ + '(`export { default } from \'../<other page>/index.js\';`) and a `css/pages/<this page>/index.scss` that `@use`s the other sheet',
1515
+ });
1516
+ });
1517
+ return { text, edits: [], findings };
1518
+ },
1519
+ };
1520
+
1521
+ // ---------------------------------------------------------------------------
1522
+ // Rule 18 — the UJM → OMEGA section-arg renames
1523
+ // ([#545](https://github.com/Omega-JS-Stack/omega/issues/545))
1524
+ //
1525
+ // Three section args were spelled differently in UJM, and section-arg
1526
+ // validation is warn-only: a migrated page keeps the old key, the section
1527
+ // never reads it, and the eyebrow / CTA sub line / CTA button simply vanish.
1528
+ // Deterministic one-liners, so they belong in the table exactly like the
1529
+ // client-runtime renames (#248).
1530
+ //
1531
+ // Scoped to the leading `---` fence's TOP-LEVEL section blocks and to their
1532
+ // DIRECT children: a UJM pricing plan's `tagline:` is a plan's tagline, and
1533
+ // `hero.demo.options.button` is the demo lane's own button.
1534
+ // ---------------------------------------------------------------------------
1535
+ const SECTION_ARGS = {
1536
+ hero: { tagline: 'badge.text' },
1537
+ cta: { description: 'subheadline', button: 'primary_button' },
1538
+ };
1539
+
1540
+ const YAML_KEY = /^(\s*)([\w-]+)\s*:/;
1541
+
1542
+ /**
1543
+ * Read a frontmatter block: the indent of its direct children and the keys
1544
+ * they declare (needed before rewriting — `hero.badge` may already be taken by
1545
+ * the legacy badge object).
1546
+ * @param {string[]} lines
1547
+ * @param {number} start - the block's opener line index
1548
+ * @param {number} end - the frontmatter fence's closing line index
1549
+ * @param {number} [openerLead] - the opener's own indent (0 for a top-level
1550
+ * block); the first line back at or above it ends the block
1551
+ * @returns {{ indent: number|null, keys: Set<string> }}
1552
+ */
1553
+ function readSectionBlock(lines, start, end, openerLead = 0) {
1554
+ let indent = null;
1555
+ const keys = new Set();
1556
+ for (let i = start + 1; i < end; i++) {
1557
+ const line = lines[i];
1558
+ if (!line.trim() || /^\s*#/.test(line)) continue;
1559
+ const lead = (line.match(/^\s*/) || [''])[0].length;
1560
+ if (lead <= openerLead) break; // a sibling (or the next top-level key) ends the block
1561
+ if (indent === null) indent = lead;
1562
+ const match = line.match(YAML_KEY);
1563
+ if (match && lead === indent) keys.add(match[2]);
1564
+ }
1565
+ return { indent, keys };
1566
+ }
1567
+
1568
+ const sectionArgs = {
1569
+ id: 'section-args',
1570
+ title: '`hero.tagline` → `hero.badge.text`; `cta.description` → `cta.subheadline`; `cta.button` → `cta.primary_button`',
1571
+ apply(text) {
1572
+ const { lines, trailingNewline } = toLines(text);
1573
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
1574
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
1575
+
1576
+ const edits = [];
1577
+ const findings = [];
1578
+ const out = [];
1579
+ let block = null;
1580
+
1581
+ for (let i = 0; i < lines.length; i++) {
1582
+ const line = lines[i];
1583
+ if (i === 0 || i >= fenceEnd) { out.push(line); block = null; continue; }
1584
+ // A top-level key (or any unindented content) ends the block above it
1585
+ if (line.trim() && !/^\s/.test(line)) block = null;
1586
+
1587
+ const opener = line.match(/^([\w-]+):\s*$/);
1588
+ if (opener && SECTION_ARGS[opener[1]]) {
1589
+ const { indent, keys } = readSectionBlock(lines, i, fenceEnd);
1590
+ block = indent === null ? null : { renames: SECTION_ARGS[opener[1]], indent, keys };
1591
+ out.push(line);
1592
+ continue;
1593
+ }
1594
+
1595
+ const child = block && line.match(new RegExp(`^(\\s{${block.indent}})([\\w-]+)(\\s*:)(.*)$`));
1596
+ const target = child && block.renames[child[2]];
1597
+ if (!target) { out.push(line); continue; }
1598
+
1599
+ const [, lead, key, colon, value] = child;
1600
+ if (!target.includes('.')) {
1601
+ const replaced = `${lead}${target}${colon}${value}`;
1602
+ edits.push({ rule: 'section-args', line: i + 1, before: line.trim(), after: replaced.trim() });
1603
+ out.push(replaced);
1604
+ continue;
1605
+ }
1606
+
1607
+ // The nested target (`badge.text`) becomes a real two-line mapping — but
1608
+ // only when the block has no `badge:` of its own: the UJM badge is a
1609
+ // DIFFERENT object ({ title, subtitle, icon }), and a second key of the
1610
+ // same name would drop one of the two.
1611
+ const [parent, leaf] = target.split('.');
1612
+ if (block.keys.has(parent)) {
1613
+ findings.push({
1614
+ check: 'section-args', line: i + 1, severity: 'error',
1615
+ message: `\`${key}\` here is now \`${parent}.${leaf}\`, but this block already declares \`${parent}:\` `
1616
+ + '(the UJM badge object) — merge the two by hand; a second key of the same name would drop one',
1617
+ });
1618
+ out.push(line);
1619
+ continue;
1620
+ }
1621
+ if (!value.trim()) {
1622
+ findings.push({
1623
+ check: 'section-args', line: i + 1, severity: 'error',
1624
+ message: `\`${key}\` here is now \`${parent}.${leaf}\`, but this one carries a block, not a string — re-nest it by hand`,
1625
+ });
1626
+ out.push(line);
1627
+ continue;
1628
+ }
1629
+
1630
+ const nested = `${lead.repeat(2)}${leaf}${colon}${value}`;
1631
+ out.push(`${lead}${parent}:`, nested);
1632
+ edits.push({ rule: 'section-args', line: i + 1, before: line.trim(), after: `${parent}: { ${leaf}:${value} }`.trim() });
1633
+ }
1634
+
1635
+ return { text: fromLines(out, trailingNewline), edits, findings };
1636
+ },
1637
+ };
1638
+
1639
+ // ---------------------------------------------------------------------------
1640
+ // Rule 22 — the hero's silently-dropped secondary button
1641
+ // ([#579](https://github.com/Omega-JS-Stack/omega/issues/579))
1642
+ //
1643
+ // `marketing/hero` defaults `secondary_button.enabled: false` (the button is
1644
+ // opt-in per its section.json5, and that default STAYS — a theme's own heroes
1645
+ // depend on it). A UJM page never authored an `enabled` key: writing the block
1646
+ // WAS the opt-in. So a migrated page keeps every button arg, builds clean,
1647
+ // looks right — and has no second CTA. Migrate answers the question the legacy
1648
+ // page answered by existing, and the report says it did.
1649
+ //
1650
+ // Scoped like rule 18: a top-level `hero:` block's DIRECT child. `cta`'s
1651
+ // secondary button has no such default (it renders whenever it is set), so it
1652
+ // is deliberately not here.
1653
+ // ---------------------------------------------------------------------------
1654
+ const heroSecondaryButton = {
1655
+ id: 'hero-secondary-button',
1656
+ title: '`hero.secondary_button` without `enabled` → `enabled: true`',
1657
+ apply(text) {
1658
+ const { lines, trailingNewline } = toLines(text);
1659
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
1660
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
1661
+
1662
+ const edits = [];
1663
+ const findings = [];
1664
+ const out = [];
1665
+ let block = null;
1666
+
1667
+ for (let i = 0; i < lines.length; i++) {
1668
+ const line = lines[i];
1669
+ out.push(line);
1670
+ if (i === 0 || i >= fenceEnd) { block = null; continue; }
1671
+ if (line.trim() && !/^\s/.test(line)) block = /^hero:\s*$/.test(line) ? readSectionBlock(lines, i, fenceEnd) : null;
1672
+ if (!block || block.indent === null) continue;
1673
+
1674
+ // The opener alone — an inline mapping carries its own value and is not
1675
+ // the shape a UJM page ever wrote.
1676
+ if (line !== `${' '.repeat(block.indent)}secondary_button:`) continue;
1677
+
1678
+ const { indent, keys } = readSectionBlock(lines, i, fenceEnd, block.indent);
1679
+ if (indent === null || keys.has('enabled')) continue;
1680
+
1681
+ const enabled = `${' '.repeat(indent)}enabled: true`;
1682
+ out.push(enabled);
1683
+ edits.push({ rule: 'hero-secondary-button', line: i + 1, before: line.trim(), after: enabled.trim() });
1684
+ findings.push({
1685
+ check: 'hero-secondary-button', line: i + 1, severity: 'warning',
1686
+ message: 'this hero authored a secondary button but no `enabled` key, and `marketing/hero` defaults it OFF — '
1687
+ + 'migrate wrote `enabled: true` so the CTA survives the port. Delete the line if the button was meant to be hidden',
1688
+ });
1689
+ }
1690
+
1691
+ return { text: fromLines(out, trailingNewline), edits, findings };
1692
+ },
1693
+ };
1694
+
1695
+ // ---------------------------------------------------------------------------
1696
+ // Rule 23 — bare config sections in frontmatter → under a `config:` parent
1697
+ // (#607). A UJM-era page restated omega.json5 keys bare (`theme:`, `client:`,
1698
+ // `inbound:`), which merged into the same flat tree the config seeded and let
1699
+ // the two namespaces drift. The page lane is `config:` now and the build
1700
+ // ERRORS on a bare config section — so this rule is what carries a legacy page
1701
+ // across. Page machinery (`meta:`, `schema:`) is not config and stays put.
1702
+ //
1703
+ // Runs LAST with the other inserting rules: it re-indents whole blocks, so
1704
+ // every line-numbered finding above it reports the pre-rewrite line.
1705
+ // ---------------------------------------------------------------------------
1706
+ const configParent = {
1707
+ id: 'config-parent',
1708
+ title: 'bare config sections in frontmatter → under `config:`',
1709
+ apply(text) {
1710
+ const { lines, trailingNewline } = toLines(text);
1711
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
1712
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
1713
+
1714
+ // Split the frontmatter into top-level blocks: an unindented `key:` opens
1715
+ // one and everything under it (indented lines, blanks) rides along until
1716
+ // the next unindented line. A comment ABOVE a key belongs to that key —
1717
+ // it is the note explaining the block, and moving the block without it
1718
+ // would strand the note over a `config:` line it never described.
1719
+ const blocks = [];
1720
+ let current = null;
1721
+ let lead = [];
1722
+ for (let i = 1; i < fenceEnd; i++) {
1723
+ const line = lines[i];
1724
+ const opener = line.match(/^([\w-]+):/);
1725
+ if (opener) {
1726
+ current = { key: opener[1], line: i + 1 - lead.length, body: [...lead, line] };
1727
+ lead = [];
1728
+ blocks.push(current);
1729
+ continue;
1730
+ }
1731
+ if (/^#/.test(line)) {
1732
+ lead.push(line); // a note waiting for the key it describes
1733
+ continue;
1734
+ }
1735
+ if (lead.length) { blocks.push({ key: null, body: lead }); lead = []; } // …that never came
1736
+ if (current && (line.trim() === '' || /^\s/.test(line))) {
1737
+ current.body.push(line);
1738
+ continue;
1739
+ }
1740
+ blocks.push({ key: null, body: [line] }); // a fence-level line of some other shape
1741
+ current = null;
1742
+ }
1743
+ if (lead.length) blocks.push({ key: null, body: lead });
1744
+
1745
+ const moved = blocks.filter((block) => block.key && isMovableSection(block.key));
1746
+ if (!moved.length) return { text, edits: [], findings: [] }; // already migrated — idempotent
1747
+
1748
+ const kept = blocks.filter((block) => !moved.includes(block));
1749
+ const existing = kept.find((block) => block.key === 'config');
1750
+
1751
+ // Trailing blank lines belong BETWEEN blocks, not inside the moved one.
1752
+ const indented = moved.flatMap((block) => {
1753
+ while (block.body.length > 1 && block.body[block.body.length - 1].trim() === '') block.body.pop();
1754
+ return block.body.map((line) => (line.trim() === '' ? line : ` ${line}`));
1755
+ });
1756
+
1757
+ if (existing) {
1758
+ existing.body.push(...indented);
1759
+ } else {
1760
+ kept.push({ key: 'config', body: ['config:', ...indented] });
1761
+ }
1762
+
1763
+ const edits = moved.map((block) => ({
1764
+ rule: 'config-parent', line: block.line,
1765
+ before: `${block.key}:`, after: `config: ${block.key}:`,
1766
+ }));
1767
+
1768
+ return {
1769
+ text: fromLines([lines[0], ...kept.flatMap((block) => block.body), ...lines.slice(fenceEnd)], trailingNewline),
1770
+ edits,
1771
+ findings: [{
1772
+ check: 'config-parent', line: moved[0].line, severity: 'warning',
1773
+ message: `moved ${moved.map((block) => `\`${block.key}\``).join(', ')} under \`config:\` — a page overrides omega.json5 there now (#607), `
1774
+ + 'and no config section keeps a bare spelling. Check the merged result renders what the page meant',
1775
+ }],
1776
+ };
1777
+ },
1778
+ };
1779
+
1780
+ // ---------------------------------------------------------------------------
1781
+ // Rule 26 — the retired `append:` frontmatter flag
1782
+ // ([#607](https://github.com/Omega-JS-Stack/omega/issues/607), Ian 2026-08-26)
1783
+ //
1784
+ // `append: true` was the legacy UJM add-below contract kept behind a flag: the
1785
+ // page body rendered BELOW the layout's default `{% composition %}` instead of
1786
+ // replacing it. The flag is deleted — a page that wants the default bands
1787
+ // writes them (`omega customize <url>` materializes exactly that) — so a page
1788
+ // still carrying it renders the same as one without, and the key survives only
1789
+ // as an unknown frontmatter key the build strips with a warning. Dropped here,
1790
+ // with the finding that says what changed.
1791
+ //
1792
+ // Frontmatter-scoped and TOP-LEVEL only: `append` is a `| append:` Liquid
1793
+ // filter everywhere else, and an indented `append:` belongs to some other
1794
+ // block's data.
1795
+ // ---------------------------------------------------------------------------
1796
+ const appendFlag = {
1797
+ id: 'append-flag',
1798
+ title: 'the retired `append:` frontmatter flag → dropped',
1799
+ apply(text) {
1800
+ const { lines, trailingNewline } = toLines(text);
1801
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
1802
+ if (fenceEnd < 0) return { text, edits: [], findings: [] };
1803
+
1804
+ const at = lines.findIndex((line, index) => index > 0 && index < fenceEnd && /^append:/.test(line));
1805
+ if (at < 0) return { text, edits: [], findings: [] }; // already migrated — idempotent
1806
+
1807
+ const out = [...lines];
1808
+ out.splice(at, 1);
1809
+
1810
+ return {
1811
+ text: fromLines(out, trailingNewline),
1812
+ edits: [{ rule: 'append-flag', line: at + 1, before: lines[at].trim(), after: '' }],
1813
+ findings: [{
1814
+ check: 'append-flag', line: at + 1, severity: 'warning',
1815
+ message: '`append: true` is gone (#607) — a page body REPLACES the layout\'s default composition, and there is no '
1816
+ + 'flag to keep both. Run `omega customize <url>` to materialize the default bands into this page, then edit them '
1817
+ + 'with your own content below',
1818
+ }],
1819
+ };
1820
+ },
1821
+ };
1822
+
1823
+ // ---------------------------------------------------------------------------
1824
+ // Rule 25 — the UJM `random_id` global → an `omega_random` assign
1825
+ // ([#595](https://github.com/Omega-JS-Stack/omega/issues/595))
1826
+ //
1827
+ // UJM handed every render a fresh `random_id`, and consumer includes scoped
1828
+ // repeated markup with it (`id="tool-faq-{{ random_id }}"`). OMEGA has no such
1829
+ // global, so the read renders EMPTY and every accordion on the page shares one
1830
+ // container id — silently. `omega_random` is the successor, and the idiom is
1831
+ // one assign at the top of the file, which the reads then keep spelling
1832
+ // exactly as they did.
1833
+ //
1834
+ // Runs LAST: it inserts a line at the top, so every line-numbered edit and
1835
+ // finding above it reports the pre-insert line.
1836
+ // ---------------------------------------------------------------------------
1837
+ const randomIdGlobal = {
1838
+ id: 'random-id',
1839
+ title: '`{{ random_id }}` (the UJM per-render global) → `{% assign random_id = 100 | omega_random %}`',
1840
+ apply(text) {
1841
+ const reads = randomIdReads(text);
1842
+ if (!reads.length || assignsRandomId(text)) return { text, edits: [], findings: [] }; // already migrated — idempotent
1843
+
1844
+ const { lines, trailingNewline } = toLines(text);
1845
+ // Under the frontmatter fence if there is one: an assign inside it is YAML,
1846
+ // not Liquid. A file with no fence (an include, the shape that carried this
1847
+ // idiom) takes line 1.
1848
+ const fenceEnd = lines[0] === '---' ? lines.indexOf('---', 1) : -1;
1849
+ const at = fenceEnd < 0 ? 0 : fenceEnd + 1;
1850
+
1851
+ const out = [...lines];
1852
+ out.splice(at, 0, RANDOM_ID_ASSIGN_IDIOM);
1853
+
1854
+ return {
1855
+ text: fromLines(out, trailingNewline),
1856
+ edits: [{ rule: 'random-id', line: at + 1, before: '', after: RANDOM_ID_ASSIGN_IDIOM }],
1857
+ findings: [{
1858
+ check: 'random-id', line: reads[0].line, severity: 'warning',
1859
+ message: `\`random_id\` was a UJM per-render GLOBAL and OMEGA has none — unported it renders empty and every id it scopes collides. `
1860
+ + `Migrate wrote \`${RANDOM_ID_ASSIGN_IDIOM}\` at the top of the file; check the value's range suits the ids it builds`,
1861
+ }],
1862
+ };
1863
+ },
1864
+ };
1865
+
1866
+ const RULES = [
1867
+ iconTagMarkup, // 27 — FIRST: the icon tag stops being a tag (#619), so
1868
+ // neither the uj_→omega_ rename nor the tag-arg
1869
+ // hoist may see one
1870
+ legacyPrefix, // 0 — must precede every rule that matches tag names
1871
+ pageResolved, // 1
1872
+ bracketLayout, // 2
1873
+ tagArgInterpolation, // 3
1874
+ parentloop, // 4
1875
+ includeSlash, // 5
1876
+ canonicalUrl, // 7 — must precede page-props
1877
+ pageProps, // 6
1878
+ analyticsShape, // 9
1879
+ clientMarkup, // 10
1880
+ clientFrontmatter, // 11
1881
+ includeThemePath, // 12 — must follow include-slash
1882
+ adunitSection, // 20 — must follow include-slash
1883
+ markdownIal, // 21 — must precede gradient-utilities: the class list
1884
+ // it writes is a class list like any other
1885
+ gradientUtilities, // 16
1886
+ assetPath, // 17
1887
+ configReads, // 24 — LAST of the text rewrites: it renames the roots
1888
+ // rules 9 and 12 answer to (`site.analytics.…`,
1889
+ // `themes/[ site.theme.id ]/…`), so it goes after
1890
+ // every rule that reads one
1891
+ appendFlag, // 26 — before the re-indenting rules: it deletes a
1892
+ // frontmatter line, so its own finding is the
1893
+ // pre-move line like every rule above
1894
+ sectionArgs, // 18 — the INSERTING rules go last, so every
1895
+ heroSecondaryButton, // 22 line-numbered finding above them reports the
1896
+ configParent, // 23 pre-rewrite line
1897
+ randomIdGlobal, // 25 — LAST: it inserts at the TOP, which moves every
1898
+ // other line in the file
1899
+ ];
1900
+
1901
+ // The consumer-JS table (src/**/*.js — the tree the template walk skips).
1902
+ // Template rules never run over JS: `page.title` in a script is an object
1903
+ // property, not a Jekyll frontmatter read.
1904
+ const JS_RULES = [
1905
+ clientMarkup, // 10 — the same class/attribute names, spelled in JS
1906
+ clientImport, // 13 — must precede client-libs (it puts `omega` in scope)
1907
+ clientLibs, // 14
1908
+ ujLibrary, // 19
1909
+ serviceWorkerImport, // 15
1910
+ ];
1911
+
1912
+ // The section-descriptor table (src/**/*.json — bindings and classes declared
1913
+ // as DATA, not markup). Only the rules whose subject is a LITERAL TOKEN belong
1914
+ // here: the page-props, include and frontmatter rules answer to template text,
1915
+ // and a `page.`-looking string in a descriptor is a JSON value, not a Jekyll
1916
+ // read. A `{{ … }}` binding is the exception — the descriptor emits it into the
1917
+ // page, so it is a read like any other (nav.json's logo text is the case).
1918
+ const JSON_RULES = [
1919
+ clientMarkup, // 10 — the same class/attribute names, spelled in a descriptor
1920
+ configReads, // 24 — a binding that emits config renders empty too (#611)
1921
+ ];
1922
+
1923
+ module.exports = {
1924
+ RULES, JS_RULES, JSON_RULES,
1925
+ // Rule 24 alone, for the ONE file that is not a template and still carries
1926
+ // reads: the brand's own omega.json5 (#671, migrate/index.js step 2b).
1927
+ configReads,
1928
+ VALID_PAGE_PROPS, MANUAL_PAGE_PROPS, PACKAGED_THEMES,
1929
+ };