@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
package/dist/assets.js ADDED
@@ -0,0 +1,1349 @@
1
+ /**
2
+ * The @omega.js/web asset pipeline over ordered LAYER ROOTS (site → active
3
+ * theme → base → core). Each layer root follows one convention:
4
+ *
5
+ * <layer>/js/main.js - the main-bundle module (core ships the default)
6
+ * <layer>/js/pages/** - page modules (index.js per page dir, or flat)
7
+ * <layer>/js/layouts/** - layout modules, keyed by layout name
8
+ * <layer>/css/main.scss - the main-bundle entry (core ships the default)
9
+ * <layer>/css/pages/** - page styles (same shape as js/pages)
10
+ * <layer>/css/layouts/** - layout styles (same shape as js/layouts)
11
+ * <layer>/_theme.scss - theme layers only: the theme's sass entry
12
+ * <layer>/_theme.js - theme layers only: the theme's js module
13
+ *
14
+ * ONE RULE for page and layout assets, JS and CSS alike (#624): EVERY layer
15
+ * that ships a file for a key is built, and they all LOAD, in layer order —
16
+ * core, then the base theme, then the active theme, then the consumer. Nothing
17
+ * replaces anything: a framework page script always runs, the theme decorates,
18
+ * the consumer adds. So a manifest key holds a LIST in load order:
19
+ * { js: { main, firstPaint, pages: {key: [url]}, layouts: {key: [url]} },
20
+ * css: { main, critical, pages: {key: [sheet]}, layouts: {key: [sheet]} } }
21
+ * A css `sheet` is `{ href }`, a hashed file like the js lanes, or (under
22
+ * INLINE_MAX_BYTES) `{ inline }` carrying the compiled css itself, which the
23
+ * head prints as a `<style>` block where the link stood (#767).
24
+ * `css.critical` is css TEXT the same way: the first-paint subset the head
25
+ * inlines so the main sheet can load deferred (#750).
26
+ * Each layer's output carries a suffix naming its owning layer (core: none,
27
+ * a theme: `.<theme id>`, the consumer: `.site`), so two layers' bundles for
28
+ * one key never collide — content hashes aside, dev mode's stable names need it.
29
+ *
30
+ * The site-wide `main.js` / `main.scss` are the one REPLACE lane: the first
31
+ * layer that ships one wins, and it extends the layers below it by importing
32
+ * the same name (`import coreMain from 'omega:main'`, `@use 'omega:main'`).
33
+ *
34
+ * Special import specifiers (UJM conventions, resolved by esbuild plugin):
35
+ * @omega.js/client → the @omega.js/client entry (options.clientEntry)
36
+ * omega:<name> → the same name from the layers strictly BELOW the
37
+ * importing file's OWN layer (the JS twin of sass's
38
+ * `omega:` importer, which stays skip-self)
39
+ * __main_assets__/js/... → the core layer (framework runtime modules)
40
+ * __main_assets__/themes/…→ the packaged themes dir (e.g. bootstrap js)
41
+ * __theme__/... → active theme root, base fallback
42
+ * <framework dependency> → resolved from @omega.js/web's own installation,
43
+ * so consumer code imports @tanstack/charts & friends bare
44
+ *
45
+ * Every js entry is wrapped in a boot stub around the runtime handshake
46
+ * (runtime/boot.js): the main bundle calls bootMain(mod) — @omega.js/client
47
+ * initialize + global module — page bundles call bootPage(mod), which
48
+ * awaits the main boot before running the page module with
49
+ * ({ manager, options }), and layout bundles call bootLayout(mod) with the
50
+ * module's namespace, whose default export is optional. Bundles are ESM with code splitting: @omega.js/client
51
+ * and the boot runtime land in a shared chunk that evaluates once per page,
52
+ * so every bundle sees the SAME initialized singleton (webpack's single
53
+ * module graph, reproduced with `<script type="module">` semantics).
54
+ */
55
+ const crypto = require('node:crypto');
56
+ const fs = require('node:fs');
57
+ const path = require('node:path');
58
+ const sass = require('sass');
59
+ const Logger = require('./vendor/devkit/logger.js');
60
+ const { bundle } = require('./vendor/devkit/bundle.js');
61
+ const { collectLayered, collectProviders } = require('./layers.js');
62
+ const { collectSectionAssets } = require('./sections.js');
63
+ const { collectHeroAnimations } = require('./hero-animations.js');
64
+ const { resolvePathPrefix, prefixCss } = require('./path-prefix.js');
65
+ const { checkThemeVocabulary } = require('./theme-vocabulary.js');
66
+ const { readFontMetricsFile, systemFontMetrics } = require('./font-metrics.js');
67
+
68
+ // Variables
69
+ const logger = new Logger('assets');
70
+
71
+ // A page file is an ENTRY when it's a per-page index.js/index.scss, or a flat
72
+ // file at most two segments below pages/ (pages/index.js, pages/blog/[slug].js).
73
+ // Deeper non-index files are helpers (checkout modules/, account sections/),
74
+ // and underscore basenames are shared partials in BOTH languages (sass's own
75
+ // convention, mirrored for js: pages/legal/_document.js backs the terms/
76
+ // cookies/privacy entries without being an entry itself).
77
+ function isPageEntry(rel) {
78
+ const base = path.basename(rel);
79
+ if (base.startsWith('_')) return false;
80
+ if (base === 'index.js' || base === 'index.scss') return true;
81
+ return rel.split('/').length <= 3; // 'pages' + up to 2 segments
82
+ }
83
+
84
+ // The framework's own installation root (src/ in the monorepo, dist/ in a
85
+ // published install — both sit one level under the package root).
86
+ const FRAMEWORK_ROOT = path.resolve(__dirname, '..');
87
+
88
+ function pageKey(rel) {
89
+ return rel.replace(/^pages\//, '').replace(/\.(js|scss)$/, '');
90
+ }
91
+
92
+ // A layout file is an entry at ANY depth — its key IS the layout name a page
93
+ // writes in frontmatter (`layout: modules/utilities/redirect`), which the
94
+ // layout tree nests as deeply as it likes. Underscore basenames stay partials,
95
+ // the same convention pages use in both languages.
96
+ function layoutKey(rel) {
97
+ if (path.basename(rel).startsWith('_')) return null;
98
+ return rel.replace(/^layouts\//, '').replace(/\.(js|scss)$/, '');
99
+ }
100
+
101
+ /**
102
+ * The layered entry list for one asset lane: every layer that ships a file for
103
+ * a key, in LOAD order — core first, the consumer last (#624). The list is the
104
+ * reverse of the layer chain, which is resolution order (first layer wins).
105
+ * @param {Map<string, Array<{dir: string, file: string}>>} providers - collectProviders output
106
+ * @param {function(string): (string|null)} keyOf - relative path → manifest key, or null for a non-entry
107
+ * @param {function(string): string} suffixOf - layer root → output-name suffix
108
+ * @returns {Map<string, Array<{ file: string, ownerRoot: string, suffix: string }>>}
109
+ */
110
+ function laneEntries(providers, keyOf, suffixOf) {
111
+ const lanes = new Map();
112
+
113
+ for (const [rel, list] of providers) {
114
+ const key = keyOf(rel);
115
+ if (key == null) continue;
116
+ lanes.set(key, [...list].reverse().map(({ dir, file }) => {
117
+ const ownerRoot = path.dirname(dir);
118
+ return { file, ownerRoot, suffix: suffixOf(ownerRoot) };
119
+ }));
120
+ }
121
+
122
+ return lanes;
123
+ }
124
+
125
+ // Wildcard segments use the Next.js filename convention (Windows-safe):
126
+ // js/pages/blog/[slug].js → key blog/[slug] matches any /blog/<segment> page.
127
+ const WILDCARD_SEGMENT = /^\[[^\]/]+\]$/;
128
+
129
+ /**
130
+ * Resolve one manifest bucket's entry for a key (spec §7). Precedence: exact
131
+ * key, the per-page-dir `<key>/index` spelling, then wildcard keys — a `[name]`
132
+ * segment matches exactly one URL segment (a trailing `/index` on the wildcard
133
+ * key is the per-page-dir spelling and never consumes a URL segment). Among
134
+ * wildcard matches the most-literal key wins; ties break lexicographically for
135
+ * determinism. Pages key on the URL, layouts on the layout name; both resolve
136
+ * here, because they are one rule.
137
+ * @param {object|undefined} map - manifest bucket (js.pages / js.layouts / css.pages / css.layouts)
138
+ * @param {string} base - the key ('/' → 'index', /blog/hi → 'blog/hi', a layout's name)
139
+ * @returns {Array<string|object>|null} the lane's entries in load order (js urls,
140
+ * css `{ href }` / `{ inline }` sheets), or null
141
+ */
142
+ function resolvePageAsset(map, base) {
143
+ if (!map) return null;
144
+ const exact = map[base] ?? map[`${base}/index`];
145
+ if (exact != null) return exact;
146
+
147
+ const baseSegments = base.split('/');
148
+ const matches = Object.keys(map).filter((key) => {
149
+ if (!key.includes('[')) return false;
150
+ const segments = key.replace(/\/index$/, '').split('/');
151
+ if (segments.length !== baseSegments.length) return false;
152
+ return segments.every((seg, i) => WILDCARD_SEGMENT.test(seg) || seg === baseSegments[i]);
153
+ });
154
+ if (!matches.length) return null;
155
+
156
+ const literals = (key) => key.split('/').filter((seg) => !WILDCARD_SEGMENT.test(seg)).length;
157
+ matches.sort((a, b) => literals(b) - literals(a) || a.localeCompare(b));
158
+ return map[matches[0]];
159
+ }
160
+
161
+ /**
162
+ * Build the js + css bundles for a site, returning the asset manifest.
163
+ * @param {object} options
164
+ * @param {string[]} options.layers - ordered layer roots (site → active theme → base → core)
165
+ * @param {string[]} options.themeRoots - the theme layer dirs within `layers`
166
+ * (resolveThemeLayers output — consumer-local theme dirs can't be derived)
167
+ * @param {string} options.themesDir - the themes dir (for __main_assets__/themes)
168
+ * @param {string} options.coreDir - the core layer root (for __main_assets__)
169
+ * @param {string} options.outDir - the site output dir (_site)
170
+ * @param {string} options.clientEntry - path to @omega.js/client's entry (aliased as `@omega.js/client`)
171
+ * @param {string[]} [options.sectionRoots] - section/component/hero-animation
172
+ * resolution bases (consumer dir, then theme layers — registerSectionTags'
173
+ * order). Feeds the spec §7 asset lanes: every entry's section.scss (and
174
+ * every `_hero/<name>/style.scss`, #441) compiles into the main sheet via
175
+ * `omega:sections` (PurgeCSS self-trims unused ones) and every section.js /
176
+ * `script.js` bundles into the main bundle behind DOM-presence init
177
+ * (data-omega-section/-component/-hero attributes, boot.js bootSections)
178
+ * @param {string} [options.pathPrefix] - the base path the built site is served
179
+ * under (#355): emitted stylesheets' `url()` targets are written under it.
180
+ * Manifest URLs stay root-relative — the HTML pass mounts them (src/path-prefix.js)
181
+ * @param {boolean} [options.dev] - dev mode: stable (un-hashed) names, no minify —
182
+ * asset rebuilds keep their URLs so rendered HTML stays valid without a re-render
183
+ * @param {function} [options.warn] - warning sink for the theme fall-through
184
+ * guard and the js/modules/ notice (default the devkit logger)
185
+ * @param {'css'|'js'} [options.only] - rebuild just one half (dev watcher
186
+ * narrowing: a css-only rebuild writes no js files, so the dev server
187
+ * hot-swaps stylesheets instead of full-reloading; dev's stable names make
188
+ * the partial manifest harmless). Omit for the full build.
189
+ * @returns {Promise<{ js: object, css: object }>}
190
+ */
191
+ async function buildAssets(options) {
192
+ const manifest = { js: { pages: {}, layouts: {} }, css: { pages: {}, layouts: {} } };
193
+ // Explicit (resolveThemeLayers output) — a consumer-local theme dir can't
194
+ // be recognized by its parent dir, so callers name the theme roots.
195
+ const themeRoots = options.themeRoots;
196
+ // Every layer's file for a key is built, so their outputs need distinct
197
+ // names: the suffix names the OWNING layer. Core is the unsuffixed floor, a
198
+ // theme carries its own id, and the consumer layer — the chain holds exactly
199
+ // one — is `.site`.
200
+ const layerSuffix = (layerRoot) => {
201
+ if (layerRoot === options.coreDir) return '';
202
+ if (themeRoots.includes(layerRoot)) return `.${path.basename(layerRoot)}`;
203
+ return '.site';
204
+ };
205
+ // The §7 asset lanes: every resolved section/component, plus the hero
206
+ // animations (#441), which ride the SAME lanes by design — one
207
+ // `{ kind, id, scss, js }` list, so the sass importer and the bundle's
208
+ // registry take a hero folder with no special case.
209
+ const sectionAssets = [
210
+ ...collectSectionAssets(options.sectionRoots || []),
211
+ ...collectHeroAnimations(options.sectionRoots || []),
212
+ ];
213
+
214
+ if (options.only !== 'css') {
215
+ // ---- JS entries: layered union of page modules + the main bundle
216
+ const jsDirs = options.layers.map((layer) => path.join(layer, 'js')).filter((dir) => fs.existsSync(dir));
217
+ // The framework's own layers — theme roots + core (the #469 orphan guard
218
+ // answers to consumer files only).
219
+ const frameworkLayers = new Set([...themeRoots, options.coreDir]);
220
+ const entryPoints = {};
221
+ const keyBySpecifier = new Map();
222
+ // #469: the CONSUMER files isPageEntry passed over, minus the `_` partials
223
+ // it rejects by name. Judged against the bundle graph below.
224
+ const consumerJsDirs = new Set(jsDirs.filter((dir) => !frameworkLayers.has(path.dirname(dir))));
225
+ const notEntries = [];
226
+
227
+ const jsPageProviders = collectProviders(jsDirs, /^pages\/.*\.js$/);
228
+ for (const [rel, providers] of jsPageProviders) {
229
+ if (isPageEntry(rel) || path.basename(rel).startsWith('_')) continue;
230
+ for (const { dir, file } of providers) {
231
+ if (consumerJsDirs.has(dir)) notEntries.push(file);
232
+ }
233
+ }
234
+
235
+ // Page and layout modules ride ONE rule (#624): every layer's file is an
236
+ // entry, and the manifest keeps them in load order.
237
+ const jsLanes = [
238
+ ['pages', laneEntries(jsPageProviders, (rel) => (isPageEntry(rel) ? pageKey(rel) : null), layerSuffix)],
239
+ ['layouts', laneEntries(collectProviders(jsDirs, /^layouts\/.*\.js$/), layoutKey, layerSuffix)],
240
+ ];
241
+ for (const [bucket, lanes] of jsLanes) {
242
+ for (const [key, entries] of lanes) {
243
+ // Sized up front so the metafile pass can fill each layer's slot by
244
+ // index — esbuild reports outputs in its own order.
245
+ manifest.js[bucket][key] = new Array(entries.length);
246
+ entries.forEach(({ file, suffix }, slot) => {
247
+ entryPoints[`${bucket}/${key}${suffix}`] = `omega-boot:${file}`;
248
+ keyBySpecifier.set(file, [bucket, key, slot]);
249
+ });
250
+ }
251
+ }
252
+
253
+ const mainJs = collectLayered(jsDirs, /^main\.js$/).get('main.js');
254
+ if (mainJs) {
255
+ entryPoints.main = `omega-boot:${mainJs}`;
256
+ keyBySpecifier.set(mainJs, ['main']);
257
+ }
258
+
259
+ // The first-paint entry (#585): layered like main.js, but deliberately NOT
260
+ // wrapped in the boot stub — the stub's runtime import is what pulls
261
+ // @omega.js/client into the shared chunk, and this script exists precisely
262
+ // to run without it. Plain entry, its own tiny bundle, loaded from the head
263
+ // ahead of the main bundle.
264
+ const firstPaintJs = collectLayered(jsDirs, /^first-paint\.js$/).get('first-paint.js');
265
+ if (firstPaintJs) {
266
+ entryPoints['first-paint'] = firstPaintJs;
267
+ keyBySpecifier.set(firstPaintJs, ['firstPaint']);
268
+ }
269
+
270
+ const bootRuntime = path.resolve(FRAMEWORK_ROOT, 'runtime', 'boot.js');
271
+
272
+ const bootPlugin = {
273
+ name: 'omega-boot',
274
+ setup(build) {
275
+ // Boot stubs: main → bootMain (initialize + global module), page
276
+ // modules → bootPage (awaits the main boot), layout modules →
277
+ // bootLayout. The runtime import is what pulls @omega.js/client into
278
+ // the shared chunk.
279
+ //
280
+ // A LAYOUT module is handed to the runtime as its NAMESPACE, so
281
+ // shipping no default export is legal there: layout chrome that must
282
+ // act BEFORE the client is ready runs at import time and boots nothing
283
+ // (the redirect layout's hop, which cannot wait on the firebase
284
+ // runtime). The stub never names `default` for one, because esbuild
285
+ // proves that access undefined and warns on every build (#742); the
286
+ // optional default is read at RUNTIME, inside bootLayout. Main and page
287
+ // modules keep the default import, where a missing export is a typo and
288
+ // esbuild says so.
289
+ build.onResolve({ filter: /^omega-boot:/ }, (args) => ({
290
+ path: args.path.slice('omega-boot:'.length),
291
+ namespace: 'omega-boot',
292
+ }));
293
+ build.onLoad({ filter: /.*/, namespace: 'omega-boot' }, (args) => {
294
+ const isMain = args.path === mainJs;
295
+ const spec = keyBySpecifier.get(args.path);
296
+ const isLayout = Boolean(spec) && spec[0] === 'layouts';
297
+ const boot = isMain ? 'bootMain' : (isLayout ? 'bootLayout' : 'bootPage');
298
+ const lines = [
299
+ `import { ${boot} } from ${JSON.stringify(bootRuntime)};`,
300
+ isLayout
301
+ ? `import * as mod from ${JSON.stringify(args.path)};`
302
+ : `import mod from ${JSON.stringify(args.path)};`,
303
+ `${boot}(mod);`,
304
+ ];
305
+ // §7 section-JS lane: the main stub imports every section.js (and
306
+ // every hero animation's script.js, #441) and registers the id → init
307
+ // map; bootSections inits per [data-omega-<kind>="<id>"] element
308
+ // after the main boot.
309
+ const jsEntries = isMain ? sectionAssets.filter((entry) => entry.js) : [];
310
+ if (jsEntries.length) {
311
+ lines[0] = `import { bootMain, bootSections } from ${JSON.stringify(bootRuntime)};`;
312
+ const registry = { section: [], component: [], hero: [] };
313
+ jsEntries.forEach((entry, i) => {
314
+ lines.push(`import sectionInit${i} from ${JSON.stringify(entry.js)};`);
315
+ registry[entry.kind].push(`${JSON.stringify(entry.id)}: sectionInit${i}`);
316
+ });
317
+ lines.push(`bootSections({ section: { ${registry.section.join(', ')} }, component: { ${registry.component.join(', ')} }, hero: { ${registry.hero.join(', ')} } });`);
318
+ }
319
+ return { resolveDir: path.dirname(args.path), contents: lines.join('\n') };
320
+ });
321
+
322
+ // `omega:<name>` — the JS twin of sass's layered `omega:` importer:
323
+ // the SAME relative name resolved from the layers BELOW the importing
324
+ // file. That is how a consumer `js/main.js` EXTENDS the framework's
325
+ // site-wide bundle instead of replacing it: `import coreMain from
326
+ // 'omega:main'`, matching `@use 'omega:main'` on the css side.
327
+ //
328
+ // BELOW is literal here: the scan starts one layer UNDER the importer's
329
+ // own, so a theme's main.js reaches core and can never resolve upward
330
+ // into the consumer layer that extends it (a cycle, and core drops out
331
+ // of the graph entirely). The sass importer deliberately stays
332
+ // skip-self — `@use` is a configure-and-extend contract sass resolves
333
+ // per file, and no theme sheet has needed the stricter rule; changing
334
+ // it there is its own decision, not a side effect of this one. An
335
+ // importer outside every layer (a node_modules file) matches no layer
336
+ // and scans the whole chain.
337
+ build.onResolve({ filter: /^omega:/ }, (args) => {
338
+ const name = args.path.slice('omega:'.length);
339
+ const importer = path.resolve(args.importer);
340
+ const importerLayer = options.layers
341
+ .findIndex((root) => importer.startsWith(path.resolve(root) + path.sep));
342
+ for (const root of options.layers.slice(importerLayer + 1)) {
343
+ for (const candidate of [path.join(root, 'js', `${name}.js`), path.join(root, `${name}.js`)]) {
344
+ if (fs.existsSync(candidate)) return { path: candidate };
345
+ }
346
+ }
347
+ // Nothing below ships it — esbuild's own "Could not resolve" says so.
348
+ return null;
349
+ });
350
+
351
+ // UJM asset-aliases: __main_assets__ → core layer / themes dir; __theme__ → active theme (base fallback)
352
+ build.onResolve({ filter: /^__main_assets__\// }, (args) => {
353
+ const rest = args.path.slice('__main_assets__/'.length);
354
+ const target = rest.startsWith('themes/')
355
+ ? path.join(options.themesDir, rest.slice('themes/'.length))
356
+ : path.join(options.coreDir, rest);
357
+ return { path: target };
358
+ });
359
+ // The walk includes each shadowed packaged theme (withShadowedTwins,
360
+ // #773): a consumer theme standing in for `classy` and shipping no
361
+ // `_theme.js` means to INHERIT the skin — Bootstrap on window, the
362
+ // tooltip and hero-form boots — exactly as its `_theme.scss` inherits
363
+ // the sheet. Base's floor still answers for a theme with a NEW id,
364
+ // which shadows nothing.
365
+ const themeResolveRoots = withShadowedTwins(themeRoots, options);
366
+ build.onResolve({ filter: /^__theme__\// }, (args) => {
367
+ const rest = args.path.slice('__theme__/'.length);
368
+ for (const root of themeResolveRoots) {
369
+ if (fs.existsSync(path.join(root, rest))) return { path: path.join(root, rest) };
370
+ }
371
+ return { path: path.join(themeResolveRoots[0] || options.themesDir, rest) };
372
+ });
373
+ },
374
+ };
375
+
376
+ // The ONE esbuild wrapper (#736): @omega.js/devkit composes the shared parts
377
+ // — the framework-deps resolve hook, the production `@dev-only` strip, the
378
+ // minify/sourcemap rules, the timing line — so only web's own boot plugin
379
+ // and entry shape live here. `omega dev` rebuilds one-shot from its own
380
+ // source watcher (it watches layer roots the bundle graph never sees), so
381
+ // this is always mode 'build' with dev as the OUTPUT rule.
382
+ const result = await bundle({
383
+ frameworkRoot: FRAMEWORK_ROOT,
384
+ entries: entryPoints,
385
+ dev: Boolean(options.dev),
386
+ // ESM + splitting is load-bearing: shared modules (@omega.js/client, the boot
387
+ // runtime) go into one chunk the browser evaluates once — the singleton
388
+ // survives across the main and page bundles.
389
+ format: 'esm',
390
+ splitting: true,
391
+ outdir: path.join(options.outDir, 'assets', 'js'),
392
+ entryNames: options.dev ? '[dir]/[name]' : '[dir]/[name]-[hash]',
393
+ chunkNames: 'chunks/[name]-[hash]',
394
+ // Directory alias so SUBPATH imports work too (@omega.js/client/modules/dom.js)
395
+ alias: { '@omega.js/client': path.dirname(options.clientEntry) },
396
+ plugins: [bootPlugin],
397
+ define: { 'process.env.NODE_ENV': options.dev ? '"development"' : '"production"' },
398
+ });
399
+
400
+ for (const [outFile, meta] of Object.entries(result.metafile.outputs)) {
401
+ if (!meta.entryPoint) continue;
402
+ const abs = meta.entryPoint.replace(/^omega-boot:/, '');
403
+ const spec = keyBySpecifier.get(path.resolve(abs)) || keyBySpecifier.get(abs);
404
+ if (!spec) continue;
405
+ const url = `/${path.relative(options.outDir, outFile)}`;
406
+ if (spec[0] === 'main') manifest.js.main = url;
407
+ else if (spec[0] === 'firstPaint') manifest.js.firstPaint = url;
408
+ else manifest.js[spec[0]][spec[1]][spec[2]] = url;
409
+ }
410
+
411
+ // ---- Orphaned page modules (#469): a consumer `js/pages/` file that is
412
+ // neither an ENTRY nor an INPUT of one is dead code — the build stays green
413
+ // and the page it was written for ships with no JS. The legacy UJM shape
414
+ // (js/pages/dashboard/agents/edit.js, 4 segments) lands here every time.
415
+ // The bundle graph is the judge, not the path: deep helpers are legitimate
416
+ // (payment/checkout/modules/, dashboard/account/sections/) and stay silent
417
+ // because their page entry imports them. Framework layers are out of scope
418
+ // — a production build strips `@dev-only` blocks BEFORE esbuild records
419
+ // inputs, so core's dev-block-only helpers read as orphans there.
420
+ const bundled = new Set(Object.keys(result.metafile.inputs).map((input) => path.resolve(input)));
421
+ const orphans = notEntries.filter((abs) => !bundled.has(abs));
422
+ if (orphans.length) {
423
+ const warn = options.warn || logger.warn.bind(logger);
424
+ warn(
425
+ `js/pages/ modules NOTHING loads — not page entries, and no entry imports them, so these `
426
+ + `pages ship with no JS: ${orphans.join(', ')}\n`
427
+ + ` the fix for a FAMILY of pages: one \`[name]\` wildcard entry serving every URL under it `
428
+ + `(js/pages/blog/[slug].js, or js/pages/dashboard/agents/[id]/index.js deeper)\n`
429
+ + ` the fix for ONE page: a page entry is an \`index.js\` at any depth (js/pages/dashboard/agents/edit/index.js), `
430
+ + `or a flat file of 3 segments or fewer (js/pages/dashboard/agents.js)\n`
431
+ + ` a deliberate helper is imported by its page entry, or named with a leading underscore (js/pages/legal/_document.js)`,
432
+ );
433
+ }
434
+
435
+ // ---- `js/modules/` is not an asset lane (#249, #624). It was the
436
+ // framework's own standalone-IIFE lane at fixed URLs until the layout lane
437
+ // replaced its last user (the redirect script), and a consumer's copy was
438
+ // never built at all. Nothing here builds one now — but a directory that
439
+ // silently ships nothing is the trap #249 was filed for, so the build names
440
+ // it. Shared code goes in `js/libs/`, imported normally (docs/web/libs.md).
441
+ const strayModuleDirs = options.layers
442
+ .map((layer) => path.join(layer, 'js', 'modules'))
443
+ .filter((dir) => fs.existsSync(dir) && fs.readdirSync(dir).some((file) => file.endsWith('.js')));
444
+ if (strayModuleDirs.length) {
445
+ const warn = options.warn || logger.warn.bind(logger);
446
+ warn(
447
+ `js/modules/ is not an asset lane — nothing builds these directories, so their files ship `
448
+ + `nowhere: ${strayModuleDirs.join(', ')}\n`
449
+ + ` the fix: move shared modules to js/libs/ and import them from js/main.js or a page module\n`
450
+ + ` a script that belongs to ONE layout goes in js/layouts/<layout>.js\n`
451
+ + ` contract: docs/web/libs.md`,
452
+ );
453
+ }
454
+ }
455
+
456
+ if (options.only === 'js') {
457
+ return manifest;
458
+ }
459
+
460
+ // ---- CSS: the main bundle + page styles. `omega:` imports resolve through
461
+ // the LAYER ROOTS (a bare `@use 'theme'` would resolve relative to the
462
+ // importing file first — beating any layering — so layered lookups use an
463
+ // explicit scheme, which sass never resolves relatively).
464
+ const cssDirs = options.layers.map((layer) => path.join(layer, 'css')).filter((dir) => fs.existsSync(dir));
465
+ const importers = [layeredFileImporter(options.layers), sectionsImporter(sectionAssets)];
466
+ const pathPrefix = resolvePathPrefix(options.pathPrefix);
467
+ const emitCss = (rawCss, rel) => {
468
+ // Base path (#355) before the hash: the digest names the bytes actually
469
+ // served, and a theme sheet's `url(/assets/fonts/…)` is out of reach of
470
+ // every HTML pass. No prefix → the sheet is untouched.
471
+ const css = prefixCss(rawCss, pathPrefix);
472
+ const hash = crypto.createHash('md5').update(css).digest('hex').slice(0, 8);
473
+ const outRel = path.join('assets', 'css', options.dev ? `${rel}.css` : rel.replace(/(\.css)?$/, `-${hash}.css`));
474
+ fs.mkdirSync(path.dirname(path.join(options.outDir, outRel)), { recursive: true });
475
+ fs.writeFileSync(path.join(options.outDir, outRel), css);
476
+ return `/${outRel}`;
477
+ };
478
+ // #767: a page or layout sheet small enough to carry in the document ships
479
+ // as css TEXT (`{ inline }`) rather than a url (`{ href }`): the same
480
+ // pipeline bytes, base path and all, just never a request. Dev is exempt for
481
+ // #750's reason: a stable blocking link is what keeps a watch rebuild
482
+ // flash-free.
483
+ const inlineOrEmitCss = (rawCss, rel) => {
484
+ const css = escapeStyleText(prefixCss(rawCss, pathPrefix));
485
+ if (!options.dev && Buffer.byteLength(css) <= INLINE_MAX_BYTES) return { inline: css };
486
+ return { href: emitCss(rawCss, rel) };
487
+ };
488
+ const compileScss = (file, ownerRoot) => sass.compile(file, {
489
+ importers,
490
+ loadPaths: [ownerRoot, path.join(ownerRoot, 'css'), ...options.layers, ...cssDirs],
491
+ style: options.dev ? 'expanded' : 'compressed',
492
+ quietDeps: true,
493
+ // Classy-era legacy patterns only — new core css (tokens/) compiles with
494
+ // ZERO deprecations (pinned in test/tokens.test.js); this list shrinks as
495
+ // the C3 reskin rewrites the theme sheets. 'mixed-decls' graduated to
496
+ // standard Dart Sass behavior — silencing it now WARNS (friction #16).
497
+ silenceDeprecations: ['import', 'global-builtin', 'color-functions', 'legacy-js-api'],
498
+ }).css;
499
+
500
+ const mainScss = collectLayered(cssDirs, /^main\.scss$/).get('main.scss');
501
+ // Held past the block: the font lane below reads the SHEET for its preloads.
502
+ let mainCss = null;
503
+ if (mainScss) {
504
+ const ownerRoot = path.dirname(path.dirname(mainScss));
505
+ // #768: the metric-matched fallback faces go in HERE, on the compiled
506
+ // sheet, before the emit and before the critical extractor, so the
507
+ // inlined block and the deferred sheet carry the same faces and the same
508
+ // stacks, and a swap moves nothing.
509
+ mainCss = injectFallbackFonts(compileScss(mainScss, ownerRoot), options);
510
+ manifest.css.main = emitCss(mainCss, 'main');
511
+ // #98: the compiled bundle is the only honest place to ask whether the
512
+ // active theme reached the shared fall-through vocabulary at all.
513
+ checkThemeVocabulary({ css: mainCss, themeRoots, warn: options.warn });
514
+ // #750: the first-paint subset the head inlines, so the sheet above can
515
+ // stop render-blocking. Dev is exempt — it never purges either, and a
516
+ // stable blocking link is what keeps a watch rebuild flash-free.
517
+ manifest.css.critical = options.dev ? '' : await extractCriticalCss({
518
+ css: prefixCss(mainCss, pathPrefix),
519
+ // The consumer's own templates first (its nav override is the markup
520
+ // that actually paints), then the theme layers, then core.
521
+ roots: [...new Set([...(options.sectionRoots || []), ...options.layers])],
522
+ warn: options.warn,
523
+ });
524
+ }
525
+
526
+ // Page and layout css on the SAME rule as their js twins (#624): every
527
+ // layer's sheet for a key compiles to its own bundle, and the manifest keeps
528
+ // them in load order, so the head links core's first and the consumer's last
529
+ // — the cascade decides, not a replacement.
530
+ const cssLanes = [
531
+ ['pages', laneEntries(collectProviders(cssDirs, /^pages\/.*\.scss$/), (rel) => (isPageEntry(rel) ? pageKey(rel) : null), layerSuffix)],
532
+ ['layouts', laneEntries(collectProviders(cssDirs, /^layouts\/.*\.scss$/), layoutKey, layerSuffix)],
533
+ ];
534
+ //
535
+ // A sheet that compiles to NOTHING (core's pricing, 404 and alternatives
536
+ // files are empty) is never emitted: the head links every page sheet as a
537
+ // render-blocking stylesheet, and on Slow 4G that empty request queued
538
+ // behind the font preloads and held the page's first paint a second past
539
+ // the home page's (#763 proof). No rules, no link.
540
+ //
541
+ // And a sheet SMALL enough is never a request either (#767): the round trip
542
+ // costs about a second on that connection whatever the sheet weighs, so a
543
+ // compiled sheet at or under INLINE_MAX_BYTES rides the manifest as css text
544
+ // and the head prints it as a <style> block where its link stood. Each entry
545
+ // is `{ inline }` or `{ href }`, in the same load order (#624). The split
546
+ // is per sheet, so a heavy layer keeps its link beside a light one's block
547
+ // without moving either in the cascade.
548
+ for (const [bucket, lanes] of cssLanes) {
549
+ for (const [key, entries] of lanes) {
550
+ const sheets = entries
551
+ .map(({ file, ownerRoot, suffix }) => ({ css: compileScss(file, ownerRoot), rel: path.join(bucket, `${key}${suffix}`) }))
552
+ .filter(({ css }) => css.trim() !== '')
553
+ .map(({ css, rel }) => inlineOrEmitCss(css, rel));
554
+ if (sheets.length) manifest.css[bucket][key] = sheets;
555
+ }
556
+ }
557
+
558
+ // ---- Fonts: every layer's fonts/ dir lands at /assets/fonts (first layer
559
+ // wins — a consumer's file beats the theme's vendored face), and a
560
+ // consumer-local theme is followed by the packaged theme it shadows, whose
561
+ // faces it inherits through the hatch (fontLayers, #773). Stable names
562
+ // by design: @font-face src URLs are written in theme css. The union is
563
+ // then PRUNED to faces some emitted stylesheet actually references — a
564
+ // sibling theme imports classy's token-pure floor but not its faces, so
565
+ // the base layer's woff2s were pure artifact fat (cp199 parked finding).
566
+ // Partial builds (options.only) skip the prune: their css set is not the
567
+ // full picture.
568
+ const fontDirs = fontLayers(options).map((layer) => path.join(layer, 'fonts')).filter((dir) => fs.existsSync(dir));
569
+ const copiedFonts = [];
570
+ for (const [rel, abs] of collectLayered(fontDirs)) {
571
+ const dest = path.join(options.outDir, 'assets', 'fonts', rel);
572
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
573
+ fs.copyFileSync(abs, dest);
574
+ copiedFonts.push(rel);
575
+ }
576
+ if (!options.only && copiedFonts.length > 0) {
577
+ let cssText = '';
578
+ const readCssTree = (dir) => {
579
+ if (!fs.existsSync(dir)) return;
580
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
581
+ const abs = path.join(dir, entry.name);
582
+ if (entry.isDirectory()) {
583
+ readCssTree(abs);
584
+ } else if (entry.name.endsWith('.css')) {
585
+ cssText += fs.readFileSync(abs, 'utf8');
586
+ }
587
+ }
588
+ };
589
+ readCssTree(path.join(options.outDir, 'assets', 'css'));
590
+ // The tree is no longer the whole picture (#767): a page or layout sheet
591
+ // small enough to inline never reaches disk, it rides the manifest as css
592
+ // text. Its `url(…woff2)` is a real reference to a real file, so a face
593
+ // only that sheet declares would look unreferenced and be deleted out
594
+ // from under the page that needs it.
595
+ cssText += inlinedSheetCss(manifest.css);
596
+ for (const rel of copiedFonts) {
597
+ const urlRel = rel.split(path.sep).join('/');
598
+ if (!cssText.includes(urlRel) && !cssText.includes(path.basename(rel))) {
599
+ fs.rmSync(path.join(options.outDir, 'assets', 'fonts', rel), { force: true });
600
+ }
601
+ }
602
+ }
603
+
604
+ // ---- Font preloads (#765): the first-paint faces the head preloads, read
605
+ // off the compiled sheet's @font-face rules (see firstPaintFontFaces) and
606
+ // kept to the ones a browser can actually FETCH. A local face names a file
607
+ // this build writes, and the inheritance hatch hands a partial theme the
608
+ // default skin's @font-face rules without its font FILES (fonts are skin
609
+ // assets, #177) — so the rule alone would name four files nothing wrote,
610
+ // and a preload for a face that is not there is a guaranteed 404. The one
611
+ // theme that DOES get the files is a consumer-local theme shadowing a
612
+ // packaged id: fontLayers walks the dir it shadows, so its inherited faces
613
+ // are fetchable and stay in the list (#773).
614
+ // The local URLs stay ROOT-RELATIVE like every other manifest URL: the html
615
+ // transform is the one place a manifest URL becomes markup, and prefixing
616
+ // both would mount it twice (path-prefix.js). A face hosted on another
617
+ // origin (absolute or protocol-relative src) rides through untouched — the
618
+ // browser can fetch it, and the head's loop already emits the crossorigin
619
+ // the spec asks for.
620
+ const fetchable = (url) => (
621
+ ABSOLUTE_URL.test(url) ? true : fs.existsSync(path.join(options.outDir, url.slice(1)))
622
+ );
623
+ manifest.fontPreloads = mainCss === null ? [] : firstPaintFontFaces(mainCss, options.warn).filter(fetchable);
624
+
625
+ return manifest;
626
+ }
627
+
628
+ // A @font-face block and the pieces of it the preload rule reads. Font-face
629
+ // bodies never nest braces, so the block match is exact.
630
+ const FONT_FACE_BLOCK = /@font-face\s*\{([^}]*)\}/gi;
631
+ const FONT_FACE_FAMILY = /font-family\s*:\s*([^;}]+)/i;
632
+ const FONT_FACE_SRC = /src\s*:/i;
633
+ const FONT_FACE_UNICODE_RANGE = /unicode-range\s*:\s*([^;}]+)/i;
634
+ const FONT_SRC_URL = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^'")]*))\s*\)/gi;
635
+
636
+ // A src that names another origin: absolute (https:, and `data:` for that
637
+ // matter) or protocol-relative.
638
+ const ABSOLUTE_URL = /^(?:\/\/|[a-z][a-z0-9+.-]*:)/i;
639
+
640
+ /**
641
+ * The value of a face's `src:` descriptor, ended at the first `;` OUTSIDE
642
+ * url() and outside quotes. A `data:font/woff2;base64,…` URI carries a
643
+ * semicolon of its own, and stopping there swallowed every later source in
644
+ * the same face — the real file among them included.
645
+ * @param {string} body - a @font-face block body
646
+ * @returns {string|null} the descriptor value, null when the face declares none
647
+ */
648
+ function readSrcDescriptor(body) {
649
+ const start = FONT_FACE_SRC.exec(body);
650
+ if (!start) return null;
651
+
652
+ const from = start.index + start[0].length;
653
+ let depth = 0;
654
+ let quote = null;
655
+ let i = from;
656
+ for (; i < body.length; i += 1) {
657
+ const char = body[i];
658
+ if (quote) {
659
+ if (char === quote && body[i - 1] !== '\\') quote = null;
660
+ } else if (char === '"' || char === "'") {
661
+ quote = char;
662
+ } else if (char === '(') {
663
+ depth += 1;
664
+ } else if (char === ')') {
665
+ depth = Math.max(0, depth - 1);
666
+ } else if (char === ';' && depth === 0) {
667
+ break;
668
+ }
669
+ }
670
+
671
+ return body.slice(from, i);
672
+ }
673
+
674
+ // Basic latin — the block the first viewport's copy is written in.
675
+ const BASIC_LATIN_MAX = 0xFF;
676
+
677
+ /**
678
+ * Does a `unicode-range` descriptor cover any basic-latin codepoint? Every
679
+ * form reduces to its LOWEST codepoint: `U+0131` is its own value, `U+0100-02BA`
680
+ * its start, and a `U+00??` wildcard the digits with `?` read as 0 — so a range
681
+ * reaches basic latin exactly when that value is at or below U+00FF.
682
+ * @param {string} descriptor - the raw comma-separated descriptor value
683
+ * @returns {boolean}
684
+ */
685
+ function coversBasicLatin(descriptor) {
686
+ return descriptor.split(',').some((token) => {
687
+ const match = /^\s*u\+([0-9a-f?]{1,6})/i.exec(token);
688
+
689
+ return !!match && parseInt(match[1].replace(/\?/g, '0'), 16) <= BASIC_LATIN_MAX;
690
+ });
691
+ }
692
+
693
+ /**
694
+ * The first-paint faces of a compiled stylesheet (#765) — the woff2 files the
695
+ * head preloads so glyphs are ready when the layout paints (no system-font
696
+ * flash on a cold cache).
697
+ *
698
+ * The source is what the CSS DECLARES, never what the files are named: a theme
699
+ * that vendors ONE variable face, or names its files its own way, is covered
700
+ * the day it ships, and nothing has a naming convention to keep. A face is a
701
+ * first-paint face when it declares no `unicode-range` (it covers everything)
702
+ * or when one of its ranges reaches basic latin. The `-latin-ext` subsets and
703
+ * their kin stay out: they cover glyphs the first viewport's copy does not
704
+ * use, and every extra preload competes with the ones it does.
705
+ *
706
+ * BOTH styles ride the list (#467): every display headline renders its accent
707
+ * as an <em> (heading/masthead, the hero's headline_accent), so the first
708
+ * viewport asks for the italic face as surely as the normal one.
709
+ *
710
+ * woff2 only — the one format the head's `type="font/woff2"` link names.
711
+ * Sorted and deduped: the emitted HTML must be deterministic.
712
+ *
713
+ * A src the head cannot mount — anything neither root-relative nor absolute
714
+ * nor protocol-relative — is SKIPPED and named: a page-relative `url(../fonts/
715
+ * x.woff2)` resolves against the PAGE, so `/blog/post/` would ask for a path
716
+ * the site never wrote, and a silently wrong preload is the failure mode this
717
+ * rule exists to end.
718
+ * @param {string} css - a compiled bundle, URLs as the sheet carries them
719
+ * @param {function} [warn] - warning sink (default the devkit logger)
720
+ * @returns {string[]} the face URLs, deduped and sorted
721
+ */
722
+ function firstPaintFontFaces(css, warn) {
723
+ const report = warn || logger.warn.bind(logger);
724
+ const urls = new Set();
725
+ for (const [, body] of String(css).matchAll(FONT_FACE_BLOCK)) {
726
+ const range = FONT_FACE_UNICODE_RANGE.exec(body);
727
+ if (range && !coversBasicLatin(range[1])) continue;
728
+
729
+ const src = readSrcDescriptor(body);
730
+ if (src === null) continue;
731
+
732
+ const family = FONT_FACE_FAMILY.exec(body);
733
+ for (const [, doubleQuoted, singleQuoted, bare] of src.matchAll(FONT_SRC_URL)) {
734
+ const url = (doubleQuoted ?? singleQuoted ?? bare).trim();
735
+ if (!/\.woff2(?:[?#]|$)/i.test(url)) continue;
736
+
737
+ if (!url.startsWith('/') && !ABSOLUTE_URL.test(url)) {
738
+ report(
739
+ `@font-face ${family ? family[1].trim() : '(unnamed)'}: src "${url}" is page-relative, `
740
+ + 'so it is NOT preloaded — a relative font URL resolves against the page that links it, '
741
+ + 'and every route below the root would ask for a path this build never wrote\n'
742
+ + ' the fix: write the src root-relative (url(/assets/fonts/…)), which is where the '
743
+ + 'asset lane copies every layer\'s fonts/ dir',
744
+ );
745
+ continue;
746
+ }
747
+
748
+ urls.add(url);
749
+ }
750
+ }
751
+
752
+ return [...urls].sort();
753
+ }
754
+
755
+ /**
756
+ * Every sheet the manifest carries as css TEXT rather than as a file (#767):
757
+ * the page and layout entries that inlined. The font prune reads these beside
758
+ * the emitted css tree, since a face an inlined sheet declares is referenced
759
+ * exactly as surely as one an emitted sheet does.
760
+ * @param {object} css - the manifest's css bucket
761
+ * @returns {string} the inlined sheets, concatenated
762
+ */
763
+ function inlinedSheetCss(css) {
764
+ return ['pages', 'layouts']
765
+ .flatMap((bucket) => Object.values(css[bucket] || {}).flat())
766
+ .filter((sheet) => sheet && sheet.inline)
767
+ .map((sheet) => sheet.inline)
768
+ .join('');
769
+ }
770
+
771
+ // ─── Metric-matched fallback faces (#768) ────────────────────────────────────
772
+
773
+ // A face's style descriptor, and the `--omega-font-*` type stacks the theme
774
+ // tokens carry. A custom property's value is verbatim text (sass never
775
+ // normalizes it), so the rewrite below edits it in place rather than
776
+ // reprinting it.
777
+ const FONT_FACE_STYLE = /font-style\s*:\s*([^;}]+)/i;
778
+ const FONT_TOKEN_DECLARATION = /--omega-font-[a-z0-9-]*\s*:\s*([^;}]+)/gi;
779
+
780
+ // Where the asset lane mounts every layer's fonts/ dir, and so the one prefix
781
+ // a face's src can carry back to a file on disk.
782
+ const FONTS_MOUNT = '/assets/fonts/';
783
+
784
+ /**
785
+ * Generate a metric-matched fallback face per vendored family (#768) and name
786
+ * it in the stacks: the transform that ends the swap-time reflow.
787
+ *
788
+ * `font-display: swap` paints the system fallback first and the real face when
789
+ * it lands. Unless the two occupy the same lines, every paragraph moves: the
790
+ * #763 proof measured /terms at CLS 0.158, one shift at 3.1 s on the body copy.
791
+ * So for each family the sheet vendors, this reads the FONT FILE's metrics
792
+ * (src/font-metrics.js), reads the same five numbers for the system family the
793
+ * theme's own stack already names next, and emits
794
+ *
795
+ * @font-face{font-family:"Inter Fallback";src:local("Helvetica Neue");
796
+ * size-adjust:…;ascent-override:…;descent-override:…;line-gap-override:…}
797
+ *
798
+ * That is the fontaine / next-font formula: size-adjust is the width ratio of
799
+ * the two faces, and each vertical override is the web face's own proportion
800
+ * divided by it (an override is read against the ALREADY adjusted em).
801
+ *
802
+ * The input is the COMPILED sheet, so a consumer theme's own faces get this
803
+ * with nothing to declare, and no scss source names a fallback family. It runs
804
+ * before the critical extractor, so the inlined block and the deferred sheet
805
+ * agree, which is the one thing that makes the swap free.
806
+ *
807
+ * A family the lane cannot cover is SKIPPED, never fatal: a face whose file
808
+ * this build does not ship has nothing to measure (the #177 inheritance hatch,
809
+ * silent, the way the preload lane drops those too), and a stack that names no
810
+ * system family the metrics table knows is named in one warning.
811
+ * @param {string} css - the compiled main bundle
812
+ * @param {object} options - buildAssets options (layers, warn)
813
+ * @returns {string} the sheet, with the generated faces appended
814
+ */
815
+ function injectFallbackFonts(css, options) {
816
+ const report = options.warn || logger.warn.bind(logger);
817
+ const faces = [];
818
+ const fallbacks = new Map();
819
+ for (const [family, file] of vendoredFaces(css, fontLayers(options))) {
820
+ const stack = fontStackNaming(css, family);
821
+ if (!stack) continue;
822
+
823
+ const system = firstKnownSystemFamily(stack, family);
824
+ if (!system) {
825
+ report(
826
+ `@font-face ${family}: no metric-matched fallback, because the --omega-font-* stack that names it `
827
+ + `("${stack.trim()}") reaches no family this build can measure, so the swap from the system `
828
+ + 'font to it will still move the text\n'
829
+ + ' the fix: name one of the common system families (Arial, Helvetica, Helvetica Neue, Georgia, '
830
+ + 'Times New Roman, Verdana) in the stack behind the web family',
831
+ );
832
+ continue;
833
+ }
834
+
835
+ let metrics = null;
836
+ try {
837
+ metrics = readFontMetricsFile(file);
838
+ } catch (error) {
839
+ report(`@font-face ${family}: ${file} could not be measured (${error.message}), so it gets no metric-matched fallback`);
840
+ continue;
841
+ }
842
+
843
+ faces.push(fallbackFace(family, system, metrics));
844
+ fallbacks.set(family.toLowerCase(), `${family} Fallback`);
845
+ }
846
+
847
+ return faces.length === 0 ? css : nameFallbacksInStacks(css, fallbacks) + faces.join('');
848
+ }
849
+
850
+ /**
851
+ * The vendored families of a sheet and the file to measure each one from: the
852
+ * NORMAL face (an italic sets different widths), preferring the one that
853
+ * covers basic latin, the subset body copy is written in. A face whose src
854
+ * this build did not write is not a family this lane can cover.
855
+ * @param {string} css - the compiled main bundle
856
+ * @param {string[]} layers - the layer roots, in order
857
+ * @returns {Map<string, string>} family name → font file on disk
858
+ */
859
+ function vendoredFaces(css, layers) {
860
+ const best = new Map();
861
+ for (const [, body] of String(css).matchAll(FONT_FACE_BLOCK)) {
862
+ const family = FONT_FACE_FAMILY.exec(body);
863
+ const src = readSrcDescriptor(body);
864
+ if (!family || src === null) continue;
865
+
866
+ const style = FONT_FACE_STYLE.exec(body);
867
+ if (style && style[1].trim().toLowerCase() !== 'normal') continue;
868
+
869
+ const name = unquoteFamily(family[1]);
870
+ const range = FONT_FACE_UNICODE_RANGE.exec(body);
871
+ const rank = !range || coversBasicLatin(range[1]) ? 2 : 1;
872
+ if ((best.get(name) || { rank: 0 }).rank >= rank) continue;
873
+
874
+ const file = fontFileOnDisk(src, layers);
875
+ if (file) best.set(name, { file, rank });
876
+ }
877
+
878
+ return new Map([...best].map(([name, { file }]) => [name, file]));
879
+ }
880
+
881
+ /**
882
+ * The layer chain the FONT lanes walk: every asset layer, and behind each
883
+ * consumer-local theme the packaged theme it SHADOWS
884
+ * ([#773](https://github.com/Omega-JS-Stack/omega/issues/773)).
885
+ *
886
+ * A tier-2 theme wins its id whole (`resolveThemeLayers`), and the
887
+ * `@forward 'omega:theme'` hatch hands it the packaged skin's `@font-face`
888
+ * rules — which name files that live in the very dir the consumer theme
889
+ * replaced. Inheriting the sheet has to imply inheriting the files it points
890
+ * at, or every one of those URLs 404s and the preload lane drops the faces as
891
+ * unfetchable. The twin sits directly BEHIND its shadower, so a consumer face
892
+ * of the same name still wins.
893
+ * @param {object} options - buildAssets options ({ layers, themeRoots, themesDir })
894
+ * @returns {string[]} layer roots, in order
895
+ */
896
+ function fontLayers(options) {
897
+ return withShadowedTwins(options.layers || [], options);
898
+ }
899
+
900
+ /**
901
+ * A layer list with the PACKAGED theme each consumer-local theme shadows
902
+ * inserted directly behind it
903
+ * ([#773](https://github.com/Omega-JS-Stack/omega/issues/773)).
904
+ *
905
+ * A tier-2 theme wins its id whole (`resolveThemeLayers`), which means the dir
906
+ * it replaced is no longer in any chain — so anything the consumer theme
907
+ * INHERITS rather than ships (the skin's faces through the scss hatch, the
908
+ * skin's `_theme.js` behaviors) would resolve past it to base and be lost. The
909
+ * twin sits between them: the consumer's own file still wins, and base is
910
+ * still the floor for a theme with a NEW id, which shadows nothing.
911
+ * @param {string[]} roots - layer roots, in order
912
+ * @param {object} options - { themeRoots, themesDir }
913
+ * @returns {string[]} roots, each shadowing theme followed by its twin
914
+ */
915
+ function withShadowedTwins(roots, options) {
916
+ const themeRoots = options.themeRoots || [];
917
+ const themesDir = options.themesDir;
918
+
919
+ return roots.flatMap((layer) => {
920
+ if (!themesDir || !themeRoots.includes(layer)) return [layer];
921
+ // Already the packaged dir — nothing shadows it.
922
+ if (!path.relative(themesDir, layer).startsWith('..')) return [layer];
923
+ const twin = path.join(themesDir, path.basename(layer));
924
+ return fs.existsSync(twin) ? [layer, twin] : [layer];
925
+ });
926
+ }
927
+
928
+ /**
929
+ * The file behind a face's `src`, in the layer that ships it. Only the faces
930
+ * THIS build writes can be measured: a `url(/assets/fonts/…)` is a layer's
931
+ * `fonts/` file (first layer wins, as the copy lane resolves it), and anything
932
+ * else (a CDN face, a data URI, a page-relative url) is out of reach.
933
+ * @param {string} src - the src descriptor value
934
+ * @param {string[]} layers - the layer roots, in order
935
+ * @returns {string|null}
936
+ */
937
+ function fontFileOnDisk(src, layers) {
938
+ for (const [, doubleQuoted, singleQuoted, bare] of src.matchAll(FONT_SRC_URL)) {
939
+ const url = (doubleQuoted ?? singleQuoted ?? bare).trim();
940
+ if (!url.startsWith(FONTS_MOUNT)) continue;
941
+
942
+ const rel = url.slice(FONTS_MOUNT.length).replace(/[?#].*$/, '');
943
+ for (const layer of layers) {
944
+ const file = path.join(layer, 'fonts', rel);
945
+ if (fs.existsSync(file)) return file;
946
+ }
947
+ }
948
+
949
+ return null;
950
+ }
951
+
952
+ /**
953
+ * The first `--omega-font-*` stack that names a family: the theme's own
954
+ * statement of what this face falls back to.
955
+ * @param {string} css - the compiled main bundle
956
+ * @param {string} family - the web family
957
+ * @returns {string|null} the stack's value, null when no token names it
958
+ */
959
+ function fontStackNaming(css, family) {
960
+ for (const [, value] of String(css).matchAll(FONT_TOKEN_DECLARATION)) {
961
+ if (value.split(',').some((entry) => unquoteFamily(entry) === family)) return value;
962
+ }
963
+
964
+ return null;
965
+ }
966
+
967
+ /**
968
+ * The family a metric-matched face is built against: the first one AFTER the
969
+ * web family in its own stack that the metrics table knows. `-apple-system`,
970
+ * `ui-serif`, `roboto` and their kin are not measurable families, so they are
971
+ * simply passed over. The name comes from the TABLE, so the generated
972
+ * `local()` carries the family's canonical spelling whatever case the stack
973
+ * happens to be written in (`georgia`).
974
+ * @param {string} stack - the stack's value
975
+ * @param {string} family - the web family
976
+ * @returns {{name: string, metrics: object}|null}
977
+ */
978
+ function firstKnownSystemFamily(stack, family) {
979
+ const entries = stack.split(',').map((entry) => unquoteFamily(entry));
980
+ const known = [];
981
+ for (let i = entries.indexOf(family) + 1; i > 0 && i < entries.length; i += 1) {
982
+ const metrics = systemFontMetrics(entries[i]);
983
+ if (metrics) known.push(metrics);
984
+ }
985
+ if (!known.length) return null;
986
+
987
+ // Every measured family the stack names, in stack order, becomes a local()
988
+ // source of the ONE generated face: `local()` resolves only where that font
989
+ // is installed (Helvetica Neue is macOS-only, Arial and Georgia ship on
990
+ // Windows too), and a face with no resolvable source is skipped by font
991
+ // matching, which would hand the swap to an UNADJUSTED tail entry. The
992
+ // overrides are computed against the first family; the others differ from it
993
+ // by about a percent of size-adjust, which is still a swap that moves
994
+ // nothing visible, where no source at all is the whole 0.158 CLS back.
995
+ return { name: known[0].family, metrics: known[0], names: known.map((entry) => entry.family) };
996
+ }
997
+
998
+ /**
999
+ * The generated face. `size-adjust` is the width ratio; each vertical override
1000
+ * is the web face's own proportion divided by that ratio, because an override
1001
+ * is read against the already size-adjusted em. `descent-override` is a
1002
+ * non-negative percentage, so the sign the font stores is dropped here.
1003
+ * @param {string} family - the web family
1004
+ * @param {{name: string, metrics: object, names: string[]}} system - the fallback family, and every measured family the stack names after the web one
1005
+ * @param {object} metrics - the web family's metrics
1006
+ * @returns {string} one @font-face rule
1007
+ */
1008
+ function fallbackFace(family, system, metrics) {
1009
+ const sizeAdjust = (metrics.avgCharWidth / metrics.unitsPerEm)
1010
+ / (system.metrics.avgCharWidth / system.metrics.unitsPerEm);
1011
+ const percent = (value) => `${(value * 100).toFixed(3)}%`;
1012
+ const override = (value) => percent(Math.abs(value / metrics.unitsPerEm) / sizeAdjust);
1013
+
1014
+ const sources = system.names.map((name) => `local("${name}")`).join(',');
1015
+
1016
+ return `@font-face{font-family:"${family} Fallback";src:${sources};`
1017
+ + `size-adjust:${percent(sizeAdjust)};ascent-override:${override(metrics.ascent)};`
1018
+ + `descent-override:${override(metrics.descent)};line-gap-override:${override(metrics.lineGap)}}`;
1019
+ }
1020
+
1021
+ /**
1022
+ * Name each generated face in the stacks, immediately after its web family:
1023
+ * the fallback is what paints while the face loads, so it has to beat the
1024
+ * system tail. Idempotent: a stack that already names it is left alone.
1025
+ * @param {string} css - the compiled main bundle
1026
+ * @param {Map<string, string>} fallbacks - lowercased web family → fallback family
1027
+ * @returns {string}
1028
+ */
1029
+ function nameFallbacksInStacks(css, fallbacks) {
1030
+ return css.replace(FONT_TOKEN_DECLARATION, (declaration, value) => {
1031
+ const entries = value.split(',');
1032
+ const names = entries.map((entry) => unquoteFamily(entry));
1033
+ const rewritten = [];
1034
+ for (let i = 0; i < entries.length; i += 1) {
1035
+ rewritten.push(entries[i]);
1036
+ const fallback = fallbacks.get(names[i].toLowerCase());
1037
+ if (!fallback || names.includes(fallback)) continue;
1038
+
1039
+ // The neighbour's own spacing: a compiled sheet writes `a, b`, but a
1040
+ // consumer's stack may be written tight, and this edits in place.
1041
+ rewritten.push(`${/^\s*/.exec(entries[i + 1] ?? entries[i])[0]}"${fallback}"`);
1042
+ }
1043
+
1044
+ return declaration.slice(0, declaration.length - value.length) + rewritten.join(',');
1045
+ });
1046
+ }
1047
+
1048
+ /**
1049
+ * A family name as the stack means it: no quotes, no padding.
1050
+ * @param {string} name - one entry of a font stack, or a font-family descriptor
1051
+ * @returns {string}
1052
+ */
1053
+ function unquoteFamily(name) {
1054
+ return name.trim().replace(/^['"]|['"]$/g, '').trim();
1055
+ }
1056
+
1057
+ // The framework's own safelist — what no content scan can see (see purgeCss).
1058
+ // It is the DEFAULT, not the whole truth: a brand's `targets.web.purgecss`
1059
+ // safelist merges over it (#250).
1060
+ const PURGE_SAFELIST = { greedy: [/omega-/], standard: ['collapse', 'collapsing', 'show', 'showing', 'fade'] };
1061
+
1062
+ /**
1063
+ * Compile one configured pattern-lane entry. `new RegExp`'s own throw names
1064
+ * neither the lane nor the pattern, so a typo in omega.json5 (an unclosed
1065
+ * character class) surfaced as an anonymous SyntaxError mid-build — this
1066
+ * attributes it to the exact config key that carries it.
1067
+ * @param {string|RegExp} pattern - one safelist entry
1068
+ * @param {string} lane - the safelist lane (deep/greedy/keyframes)
1069
+ * @returns {RegExp}
1070
+ * @throws {Error} naming targets.web.purgecss.safelist.<lane> and the pattern
1071
+ */
1072
+ function toSafelistPattern(pattern, lane) {
1073
+ if (pattern instanceof RegExp) return pattern;
1074
+ try {
1075
+ return new RegExp(pattern);
1076
+ } catch (error) {
1077
+ throw new Error(
1078
+ `targets.web.purgecss.safelist.${lane}: "${pattern}" is not a valid regular expression `
1079
+ + `— ${error.message}`,
1080
+ );
1081
+ }
1082
+ }
1083
+
1084
+ /**
1085
+ * Merge a config-supplied PurgeCSS safelist over the framework defaults.
1086
+ * Config is JSON5, so its patterns are STRINGS — the pattern lanes
1087
+ * (greedy/deep/keyframes) take RegExp, so strings become one there; `standard`
1088
+ * takes both. The array form is PurgeCSS's own shorthand for `standard`.
1089
+ * @param {object|Array} [configured] - targets.web.purgecss.safelist
1090
+ * @returns {object} a PurgeCSS safelist object
1091
+ */
1092
+ function mergeSafelist(configured) {
1093
+ if (!configured) return PURGE_SAFELIST;
1094
+ const extra = Array.isArray(configured) ? { standard: configured } : configured;
1095
+ const merged = { ...PURGE_SAFELIST };
1096
+ for (const [lane, patterns] of Object.entries(extra)) {
1097
+ if (!Array.isArray(patterns) || !patterns.length) continue;
1098
+ const values = lane === 'standard'
1099
+ ? patterns
1100
+ : patterns.map((pattern) => toSafelistPattern(pattern, lane));
1101
+ merged[lane] = [...(merged[lane] || []), ...values];
1102
+ }
1103
+ return merged;
1104
+ }
1105
+
1106
+ /**
1107
+ * PurgeCSS post-pass: strip unused selectors from the MAIN css bundle using
1108
+ * the rendered HTML AND the built JS as content.
1109
+ * @param {object} options
1110
+ * @param {string} options.outDir
1111
+ * @param {object} options.manifest - from buildAssets()
1112
+ * @param {object} [options.purgecss] - the resolved config's `purgecss` section
1113
+ * (`targets.web.purgecss`): `{ safelist: { standard, deep, greedy, keyframes } }`
1114
+ * @returns {Promise<{ before: number, after: number }>}
1115
+ */
1116
+ async function purgeCss(options) {
1117
+ const { PurgeCSS } = require('purgecss');
1118
+ const cssFile = path.join(options.outDir, options.manifest.css.main.slice(1));
1119
+ const before = fs.statSync(cssFile).size;
1120
+
1121
+ const results = await new PurgeCSS().purge({
1122
+ // The built JS is content too: a client-rendered app's markup exists only
1123
+ // as strings in its bundle, so scanning HTML alone strips a consumer's own
1124
+ // classes at build time (#66 — they survive `omega dev`, which never purges).
1125
+ content: [path.join(options.outDir, '**/*.html'), path.join(options.outDir, '**/*.js')],
1126
+ css: [cssFile],
1127
+ // The omega namespace is runtime-driven — app-shell.js stamps data-shell-*
1128
+ // and the motion engine stamps data-omega-inview / data-omega-scrolled /
1129
+ // data-omega-active client-side — so the content scan can never see those
1130
+ // states. Keep every omega-namespaced rule.
1131
+ // greedy: the framework's own runtime-stamped namespace. standard:
1132
+ // Bootstrap's JS-toggled transition classes — added at runtime, absent
1133
+ // from the rendered HTML the content scan reads, so without the safelist
1134
+ // the collapse/fade transitions get purged and snap.
1135
+ // A brand adds its own on top through targets.web.purgecss.safelist.
1136
+ safelist: mergeSafelist(options.purgecss && options.purgecss.safelist),
1137
+ });
1138
+
1139
+ fs.writeFileSync(cssFile, results[0].css);
1140
+ return { before, after: Buffer.byteLength(results[0].css) };
1141
+ }
1142
+
1143
+ // The FIRST-PAINT template set (#750): the markup a visitor sees before
1144
+ // scrolling, per layer root — the root/base layout chrome, the marketing nav,
1145
+ // the app shell's topbar and sidebar, and every layer's hero section. The
1146
+ // subset is chosen by MARKUP rather than by a hand-kept selector list, so a
1147
+ // theme (or a consumer that overrides one of these files) gets ITS OWN nav and
1148
+ // hero rules inlined with no per-theme bookkeeping — and every class the nav
1149
+ // and hero really render carries its rules, which is what keeps CLS at zero.
1150
+ const CRITICAL_TEMPLATES = [
1151
+ path.join('_layouts', 'core', 'root.html'),
1152
+ path.join('_layouts', '**', 'core', 'base.html'),
1153
+ path.join('_includes', '**', 'sections', 'nav.html'),
1154
+ path.join('_includes', 'global', 'sections', 'app-topbar.html'),
1155
+ path.join('_includes', 'global', 'sections', 'app-sidebar.html'),
1156
+ path.join('_sections', '**', 'hero', 'section.html'),
1157
+ // The shared masthead cluster: about, contact, pricing, download and every
1158
+ // document page compose their opening band's copy through it (#467)
1159
+ path.join('_components', 'heading', 'masthead', 'component.html'),
1160
+ ];
1161
+
1162
+ // Every band a template stamps `data-omega-first-paint` (#763, #467): the SAME
1163
+ // attribute that starts a band's reveals at parse time selects its critical css,
1164
+ // so a template that opts a band in is never listed above by hand. Only the
1165
+ // band's own markup feeds the extractor, never the rest of its page: a whole
1166
+ // page layout drags its below-the-fold vocabulary (pricing's accordion) into
1167
+ // the inline block. A band that paints with the document needs its sizing in
1168
+ // the block, or the headline paints at the UA size and jumps.
1169
+ const FIRST_PAINT_BAND_GLOB = path.join('{_layouts,_sections,_components,_includes}', '**', '*.html');
1170
+ const FIRST_PAINT_BAND_REGEX = /<section\b[^>]*\bdata-omega-first-paint\b[^>]*>[\s\S]*?<\/section>/g;
1171
+
1172
+ /**
1173
+ * The first-paint band chunks across the template roots, as PurgeCSS raw content.
1174
+ * @param {string[]} roots
1175
+ * @returns {Array<{ raw: string, extension: string }>}
1176
+ */
1177
+ function firstPaintBands(roots) {
1178
+ return roots.flatMap((root) => fs.globSync(FIRST_PAINT_BAND_GLOB, { cwd: root }).flatMap((rel) => {
1179
+ const source = fs.readFileSync(path.join(root, rel), 'utf8');
1180
+ return [...source.matchAll(FIRST_PAINT_BAND_REGEX)].map((match) => ({ raw: match[0], extension: 'html' }));
1181
+ }));
1182
+ }
1183
+
1184
+ // The inline block's byte budget. It is a WIRE number: the packaged themes
1185
+ // measure 64 KB (neobrutalism) to 71 KB (newsflash, classy 68 KB), which gzip to
1186
+ // 12-15 KB — about one initial congestion window, arriving WITH the HTML
1187
+ // instead of costing the round trip this whole lane exists to remove. 72 KiB
1188
+ // sits just above the heaviest packaged theme, so the warning means a sheet
1189
+ // really has outgrown the mechanism. Over budget the build says so and still
1190
+ // ships it: a heavier theme is that theme's call, not a reason to fail a
1191
+ // consumer's build.
1192
+ const CRITICAL_MAX_BYTES = 72 * 1024;
1193
+
1194
+ // A page or layout sheet at or under this size ships INLINE instead of as a
1195
+ // link (#767). The cost of the link is the ROUND TRIP, not the bytes: the
1196
+ // request queues behind the HTML and the font preloads, so the #763 proof
1197
+ // measured /terms, which ships one 1 KB page sheet, first-painting a second
1198
+ // past the home page's, which ships none. 8 KB is the crossover: below it the css is
1199
+ // cheaper carried in the document than fetched, above it the document pays
1200
+ // for bytes every page repeats.
1201
+ const INLINE_MAX_BYTES = 8 * 1024;
1202
+
1203
+ /**
1204
+ * Make a stylesheet safe to print inside a <style> block: `</style` inside a
1205
+ * string literal (a `content:` value, an unencoded SVG data URI) would end the
1206
+ * block early and dump the rest of the sheet into the document as markup.
1207
+ * The css escape for `/` is legal exactly there, and the sequence cannot
1208
+ * appear anywhere else in a stylesheet. One home for the rule: the critical
1209
+ * block (#750) and the inlined page and layout sheets (#767) both print raw.
1210
+ *
1211
+ * @param {string} css
1212
+ * @returns {string}
1213
+ */
1214
+ function escapeStyleText(css) {
1215
+ return css.replace(/<\/(style)/gi, '<\\/$1');
1216
+ }
1217
+
1218
+ /**
1219
+ * Extract the first-paint subset of the compiled main bundle (#750) — the
1220
+ * block `head.html` inlines so the full sheet can load deferred. Same
1221
+ * extractor as the site-wide pass, pointed at the first-paint templates
1222
+ * instead of the rendered site.
1223
+ * @param {object} options
1224
+ * @param {string} options.css - the compiled main bundle (base path applied)
1225
+ * @param {string[]} options.roots - template roots (consumer dir + layer roots)
1226
+ * @param {function} [options.warn] - warning sink (default the devkit logger)
1227
+ * @returns {Promise<string>} the critical css ('' when no template matched)
1228
+ */
1229
+ async function extractCriticalCss(options) {
1230
+ const { PurgeCSS } = require('purgecss');
1231
+ const content = [
1232
+ ...options.roots.flatMap((root) => CRITICAL_TEMPLATES.map((rel) => path.join(root, rel))),
1233
+ ...firstPaintBands(options.roots),
1234
+ ];
1235
+ const results = await new PurgeCSS().purge({
1236
+ content,
1237
+ css: [{ raw: options.css, name: 'main.css' }],
1238
+ // Every @font-face stays: the kept rules name their family through
1239
+ // `var(--omega-font-*)`, which PurgeCSS cannot follow, so `fontFace: true`
1240
+ // pruned all of them to an empty shell and the preloaded faces went
1241
+ // undeclared until the deferred sheet landed, a swap-time reflow. The
1242
+ // eight declarations cost about a kilobyte. Keyframes still prune.
1243
+ fontFace: false,
1244
+ keyframes: true,
1245
+ // Only the JS-toggled lane of the framework safelist: the collapsed mobile
1246
+ // menu's `.collapse:not(.show)` guard is a first-paint rule (an expanded
1247
+ // link list pushes the hero down). The greedy `omega-` lane is deliberately
1248
+ // NOT here — it would pull every section's styles into the inline block.
1249
+ //
1250
+ // Plus the motion lane, and ONLY it (#763): every reveal rule is scoped
1251
+ // `html[data-omega-motion] …` and resolved by `data-omega-inview`, and
1252
+ // neither token is ever in rendered markup for the scan to find (the same
1253
+ // reason the site-wide safelist exists) — so the extractor purged the whole
1254
+ // lane. Inline, that meant the first-paint band's copy painted VISIBLE, the
1255
+ // head's starter stamped it, and the deferred sheet landed on an
1256
+ // already-stamped element: no fade ever ran. The hide rule IS the
1257
+ // transition's starting state, so it belongs in the block that paints.
1258
+ // Greedy: the pattern has to keep a rule the token only appears in one
1259
+ // compound of (`html[data-omega-motion] [data-omega-reveal][data-omega-inview]`).
1260
+ //
1261
+ // Plus two rules the markup scan cannot reach (#763 proof, playground home
1262
+ // at CLS 0.27 → 0.07):
1263
+ // - the skip link's utility class. body.html renders it BEFORE every band,
1264
+ // and unstyled it is a line of text that pushes the whole page down until
1265
+ // the deferred sheet hides it. The rest of body.html is the alert bars,
1266
+ // `hidden` until a script shows them, whose icon vocabulary would cost
1267
+ // 11 KB of block, so the file is not scanned: the one class is kept.
1268
+ // - the dotfield canvas, which its module CREATES at runtime. Without its
1269
+ // positioning rule the canvas lands in flow whenever the module beats the
1270
+ // deferred sheet, and the hero copy drops by the canvas height.
1271
+ safelist: {
1272
+ standard: PURGE_SAFELIST.standard,
1273
+ greedy: [/data-omega-(motion|reveal|inview)/, /visually-hidden-focusable/, /omega-dotfield__canvas/],
1274
+ },
1275
+ });
1276
+
1277
+ const css = escapeStyleText(results[0].css);
1278
+ if (Buffer.byteLength(css) > CRITICAL_MAX_BYTES) {
1279
+ const warn = options.warn || logger.warn.bind(logger);
1280
+ warn(
1281
+ `critical css is ${Buffer.byteLength(css)} bytes, over the ${CRITICAL_MAX_BYTES}-byte budget `
1282
+ + '— the inlined block now costs more than the request it defers',
1283
+ );
1284
+ }
1285
+
1286
+ return css;
1287
+ }
1288
+
1289
+ /**
1290
+ * Sass FileImporter resolving `omega:<name>` through the ordered layer roots
1291
+ * (first layer containing _<name>.scss / <name>.scss at its root or under
1292
+ * css/ wins). `omega:theme` → the active theme's _theme.scss. A candidate
1293
+ * that IS the requesting file is skipped, so a consumer main.scss can
1294
+ * `@use 'omega:main' with (…)` to configure and extend the main bundle from
1295
+ * the layers below it (the migrated form of UJM's
1296
+ * `@use 'ultimate-jekyll-manager' with (…)` theme customization).
1297
+ * @param {string[]} layers - layer roots
1298
+ * @returns {object}
1299
+ */
1300
+ /**
1301
+ * Sass importer synthesizing `omega:sections` — the §7 css lane. The module
1302
+ * body is a generated @use list over every layer-resolved section.scss /
1303
+ * component.scss / hero style.scss (deterministic kind+id order), so core main.scss pulls the
1304
+ * whole library with ONE line and PurgeCSS self-trims sections a site never
1305
+ * renders. Empty library → empty module (the @use is always safe).
1306
+ * @param {Array<{scss: string|null}>} sectionAssets - collectSectionAssets output
1307
+ * plus collectHeroAnimations' (#441 — one lane, same shape)
1308
+ * @returns {object}
1309
+ */
1310
+ function sectionsImporter(sectionAssets) {
1311
+ const { pathToFileURL } = require('node:url');
1312
+ return {
1313
+ canonicalize(url) {
1314
+ return url === 'omega:sections' ? new URL(url) : null;
1315
+ },
1316
+ load() {
1317
+ const contents = sectionAssets
1318
+ .filter((entry) => entry.scss)
1319
+ .map((entry, i) => `@use ${JSON.stringify(pathToFileURL(entry.scss).href)} as omega-section-${i};`)
1320
+ .join('\n');
1321
+ return { contents, syntax: 'scss' };
1322
+ },
1323
+ };
1324
+ }
1325
+
1326
+ function layeredFileImporter(layers) {
1327
+ const { pathToFileURL, fileURLToPath } = require('node:url');
1328
+ return {
1329
+ findFileUrl(url, context) {
1330
+ if (!url.startsWith('omega:')) return null;
1331
+ const name = url.slice('omega:'.length);
1332
+ const containing = context && context.containingUrl ? fileURLToPath(context.containingUrl) : null;
1333
+ for (const root of layers) {
1334
+ for (const candidate of [path.join(root, name), path.join(root, 'css', name)]) {
1335
+ const dir = path.dirname(candidate);
1336
+ const base = path.basename(candidate);
1337
+ for (const file of [path.join(dir, `_${base}.scss`), path.join(dir, `${base}.scss`)]) {
1338
+ if (!fs.existsSync(file)) continue;
1339
+ if (containing && path.resolve(file) === path.resolve(containing)) continue;
1340
+ return pathToFileURL(file);
1341
+ }
1342
+ }
1343
+ }
1344
+ return null;
1345
+ },
1346
+ };
1347
+ }
1348
+
1349
+ module.exports = { buildAssets, purgeCss, resolvePageAsset, isPageEntry, firstPaintFontFaces, layeredFileImporter, sectionsImporter };