@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.
- package/LICENSE +98 -0
- package/README.md +640 -0
- package/bin/omega +2 -0
- package/bin/omega-web +2 -0
- package/bin/omg +2 -0
- package/core/_includes/admin/sections/sidebar.json +100 -0
- package/core/_includes/admin/sections/topbar.json +11 -0
- package/core/_includes/backend/sections/sidebar.json +39 -0
- package/core/_includes/backend/sections/topbar.json +16 -0
- package/core/_includes/core/body.html +102 -0
- package/core/_includes/core/foot.html +536 -0
- package/core/_includes/core/head.html +423 -0
- package/core/_includes/core/pricing/price-per-unit.html +33 -0
- package/core/_includes/core/showcase/entry-body.html +94 -0
- package/core/_includes/core/showcase/index-body.html +43 -0
- package/core/_includes/core/showcase/nav.html +60 -0
- package/core/_includes/frontend/sections/footer.json +74 -0
- package/core/_includes/frontend/sections/nav.json +70 -0
- package/core/_includes/global/sections/account.json +31 -0
- package/core/_includes/modules/engagement/giscus.html +26 -0
- package/core/_layouts/blueprint/404.html +10 -0
- package/core/_layouts/blueprint/about.html +10 -0
- package/core/_layouts/blueprint/account/index.html +11 -0
- package/core/_layouts/blueprint/admin/calendar/index.html +590 -0
- package/core/_layouts/blueprint/admin/dashboard/index.html +301 -0
- package/core/_layouts/blueprint/admin/firebase/index.html +214 -0
- package/core/_layouts/blueprint/admin/posts/editor.html +157 -0
- package/core/_layouts/blueprint/admin/posts/index.html +126 -0
- package/core/_layouts/blueprint/admin/stackblitz/index.html +30 -0
- package/core/_layouts/blueprint/admin/studio/index.html +85 -0
- package/core/_layouts/blueprint/admin/users/index.html +272 -0
- package/core/_layouts/blueprint/admin/users/new.html +82 -0
- package/core/_layouts/blueprint/admin/verts/index.html +204 -0
- package/core/_layouts/blueprint/alternatives/alternative.html +15 -0
- package/core/_layouts/blueprint/alternatives/index.html +10 -0
- package/core/_layouts/blueprint/app.html +10 -0
- package/core/_layouts/blueprint/auth/reset.html +19 -0
- package/core/_layouts/blueprint/auth/signin.html +19 -0
- package/core/_layouts/blueprint/auth/signup.html +19 -0
- package/core/_layouts/blueprint/auth/token.html +19 -0
- package/core/_layouts/blueprint/blog/categories/category.html +8 -0
- package/core/_layouts/blueprint/blog/categories/index.html +11 -0
- package/core/_layouts/blueprint/blog/index.html +10 -0
- package/core/_layouts/blueprint/blog/post.html +5 -0
- package/core/_layouts/blueprint/blog/tags/index.html +11 -0
- package/core/_layouts/blueprint/blog/tags/tag.html +8 -0
- package/core/_layouts/blueprint/careers.md +13 -0
- package/core/_layouts/blueprint/collection/category.html +9 -0
- package/core/_layouts/blueprint/collection/document.html +9 -0
- package/core/_layouts/blueprint/collection/index.html +9 -0
- package/core/_layouts/blueprint/connections/callback.html +11 -0
- package/core/_layouts/blueprint/contact.html +14 -0
- package/core/_layouts/blueprint/dashboard/index.html +10 -0
- package/core/_layouts/blueprint/download.html +20 -0
- package/core/_layouts/blueprint/extension/index.html +14 -0
- package/core/_layouts/blueprint/extension/installed.html +11 -0
- package/core/_layouts/blueprint/feedback.html +10 -0
- package/core/_layouts/blueprint/index.html +16 -0
- package/core/_layouts/blueprint/legal/cookies.md +57 -0
- package/core/_layouts/blueprint/legal/privacy.md +109 -0
- package/core/_layouts/blueprint/legal/terms.md +155 -0
- package/core/_layouts/blueprint/payment/checkout.html +15 -0
- package/core/_layouts/blueprint/payment/confirmation.html +14 -0
- package/core/_layouts/blueprint/portal/email-preferences.html +15 -0
- package/core/_layouts/blueprint/pricing.html +20 -0
- package/core/_layouts/blueprint/status.html +10 -0
- package/core/_layouts/blueprint/team/index.html +10 -0
- package/core/_layouts/blueprint/team/member.html +5 -0
- package/core/_layouts/blueprint/updates/index.html +10 -0
- package/core/_layouts/blueprint/updates/update.html +10 -0
- package/core/_layouts/core/root.html +45 -0
- package/core/_layouts/modules/utilities/redirect.html +129 -0
- package/core/css/_studio.scss +237 -0
- package/core/css/components/_index.scss +146 -0
- package/core/css/core/_.scss +0 -0
- package/core/css/core/_alert.scss +87 -0
- package/core/css/core/_animations.scss +343 -0
- package/core/css/core/_bindings.scss +116 -0
- package/core/css/core/_blog-search.scss +57 -0
- package/core/css/core/_chatsy.scss +61 -0
- package/core/css/core/_consent.scss +424 -0
- package/core/css/core/_custom-font-awesome.scss +146 -0
- package/core/css/core/_exit-popup.scss +42 -0
- package/core/css/core/_generated-badge.scss +33 -0
- package/core/css/core/_initialize.scss +29 -0
- package/core/css/core/_lazy-loading.scss +104 -0
- package/core/css/core/_social-sharing.scss +41 -0
- package/core/css/core/_tones.scss +32 -0
- package/core/css/core/_utilities.scss +157 -0
- package/core/css/main.scss +54 -0
- package/core/css/motion/_index.scss +368 -0
- package/core/css/pages/404/index.scss +0 -0
- package/core/css/pages/admin/calendar/index.scss +692 -0
- package/core/css/pages/admin/firebase/index.scss +33 -0
- package/core/css/pages/admin/index.scss +9 -0
- package/core/css/pages/admin/users/index.scss +9 -0
- package/core/css/pages/admin/verts/index.scss +9 -0
- package/core/css/pages/alternatives/[alternative]/index.scss +0 -0
- package/core/css/pages/blog/[slug].scss +55 -0
- package/core/css/pages/dashboard/account/index.scss +242 -0
- package/core/css/pages/download/index.scss +480 -0
- package/core/css/pages/extension/installed/index.scss +317 -0
- package/core/css/pages/feedback/index.scss +31 -0
- package/core/css/pages/payment/confirmation/index.scss +14 -0
- package/core/css/pages/pricing/index.scss +0 -0
- package/core/css/pages/status/index.scss +162 -0
- package/core/css/pages/test/libraries/layers/index.scss +28 -0
- package/core/css/pages/updates/[update].scss +46 -0
- package/core/css/pages/updates/index.scss +13 -0
- package/core/css/shell/_index.scss +207 -0
- package/core/css/tokens/_index.scss +166 -0
- package/core/icons/flags/ar.svg +1 -0
- package/core/icons/flags/bd.svg +1 -0
- package/core/icons/flags/ca.svg +1 -0
- package/core/icons/flags/ch.svg +1 -0
- package/core/icons/flags/cn.svg +1 -0
- package/core/icons/flags/co.svg +1 -0
- package/core/icons/flags/cw.svg +1 -0
- package/core/icons/flags/cz.svg +1 -0
- package/core/icons/flags/de.svg +1 -0
- package/core/icons/flags/dk.svg +1 -0
- package/core/icons/flags/eg.svg +1 -0
- package/core/icons/flags/es.svg +1 -0
- package/core/icons/flags/fi.svg +1 -0
- package/core/icons/flags/fr.svg +1 -0
- package/core/icons/flags/gb.svg +1 -0
- package/core/icons/flags/gq.svg +1 -0
- package/core/icons/flags/gr.svg +1 -0
- package/core/icons/flags/gt.svg +1 -0
- package/core/icons/flags/hn.svg +1 -0
- package/core/icons/flags/id.svg +1 -0
- package/core/icons/flags/ie.svg +1 -0
- package/core/icons/flags/in.svg +1 -0
- package/core/icons/flags/ir.svg +1 -0
- package/core/icons/flags/it.svg +1 -0
- package/core/icons/flags/jp.svg +1 -0
- package/core/icons/flags/kp.svg +1 -0
- package/core/icons/flags/kr.svg +1 -0
- package/core/icons/flags/ly.svg +1 -0
- package/core/icons/flags/ma.svg +1 -0
- package/core/icons/flags/mu.svg +1 -0
- package/core/icons/flags/mx.svg +1 -0
- package/core/icons/flags/ng.svg +1 -0
- package/core/icons/flags/pa.svg +1 -0
- package/core/icons/flags/ph.svg +1 -0
- package/core/icons/flags/pk.svg +1 -0
- package/core/icons/flags/pt.svg +1 -0
- package/core/icons/flags/ru.svg +1 -0
- package/core/icons/flags/sa.svg +1 -0
- package/core/icons/flags/sd.svg +1 -0
- package/core/icons/flags/se.svg +1 -0
- package/core/icons/flags/th.svg +1 -0
- package/core/icons/flags/tm.svg +1 -0
- package/core/icons/flags/tr.svg +1 -0
- package/core/icons/flags/us.svg +1 -0
- package/core/icons/flags/uy.svg +1 -0
- package/core/icons/flags/vn.svg +1 -0
- package/core/icons/flags/za.svg +1 -0
- package/core/images/exit-popup/subscriber-1.jpg +0 -0
- package/core/images/exit-popup/subscriber-2.jpg +0 -0
- package/core/images/exit-popup/subscriber-3.jpg +0 -0
- package/core/images/exit-popup/subscriber-4.jpg +0 -0
- package/core/images/placeholder/photo-1.jpg +0 -0
- package/core/images/placeholder/photo-2.jpg +0 -0
- package/core/images/placeholder/photo-3.jpg +0 -0
- package/core/js/core/analytics-loader.js +253 -0
- package/core/js/core/app-shell.js +136 -0
- package/core/js/core/appearance.js +195 -0
- package/core/js/core/auth.js +457 -0
- package/core/js/core/complete.js +72 -0
- package/core/js/core/consent.js +384 -0
- package/core/js/core/dev-icon-audit.js +63 -0
- package/core/js/core/dev-palette.js +670 -0
- package/core/js/core/dev-sections.js +53 -0
- package/core/js/core/exit-popup.js +276 -0
- package/core/js/core/initialize.js +8 -0
- package/core/js/core/language-switcher.js +167 -0
- package/core/js/core/lazy-loading.js +479 -0
- package/core/js/core/motion.js +28 -0
- package/core/js/core/query-strings.js +146 -0
- package/core/js/core/service-worker.js +28 -0
- package/core/js/core/social-sharing.js +293 -0
- package/core/js/first-paint.js +35 -0
- package/core/js/layouts/modules/utilities/redirect.js +122 -0
- package/core/js/libs/admin-helpers.js +62 -0
- package/core/js/libs/alert-dismiss.js +35 -0
- package/core/js/libs/analytics.js +368 -0
- package/core/js/libs/auth/email.js +144 -0
- package/core/js/libs/auth/errors.js +119 -0
- package/core/js/libs/auth/forms.js +206 -0
- package/core/js/libs/auth/index.js +181 -0
- package/core/js/libs/auth/oauth.js +394 -0
- package/core/js/libs/auth/orphan.js +110 -0
- package/core/js/libs/auth/password-toggle.js +44 -0
- package/core/js/libs/auth/session-params.js +266 -0
- package/core/js/libs/auth/tracking.js +41 -0
- package/core/js/libs/charts.js +516 -0
- package/core/js/libs/consent-region.js +127 -0
- package/core/js/libs/dev.js +150 -0
- package/core/js/libs/graph.js +201 -0
- package/core/js/libs/initialize-tooltips.js +31 -0
- package/core/js/libs/logger.js +38 -0
- package/core/js/libs/omega-copy.js +89 -0
- package/core/js/libs/path-prefix.js +44 -0
- package/core/js/libs/payment-config.js +76 -0
- package/core/js/libs/recaptcha.js +94 -0
- package/core/js/libs/sale-name.js +295 -0
- package/core/js/libs/showcase-frames.js +37 -0
- package/core/js/libs/srcset.js +69 -0
- package/core/js/libs/tracking-consent.js +161 -0
- package/core/js/main.js +144 -0
- package/core/js/pages/404/index.js +57 -0
- package/core/js/pages/admin/calendar/calendar-core.js +701 -0
- package/core/js/pages/admin/calendar/calendar-events.js +853 -0
- package/core/js/pages/admin/calendar/calendar-renderer.js +807 -0
- package/core/js/pages/admin/calendar/campaign-preview.js +105 -0
- package/core/js/pages/admin/calendar/index.js +40 -0
- package/core/js/pages/admin/firebase/index.js +727 -0
- package/core/js/pages/admin/index.js +531 -0
- package/core/js/pages/admin/posts/editor/index.js +230 -0
- package/core/js/pages/admin/posts/index.js +237 -0
- package/core/js/pages/admin/studio/index.js +538 -0
- package/core/js/pages/admin/users/index.js +669 -0
- package/core/js/pages/admin/users/new/index.js +65 -0
- package/core/js/pages/admin/verts/index.js +407 -0
- package/core/js/pages/alternatives/[alternative]/index.js +17 -0
- package/core/js/pages/app/index.js +135 -0
- package/core/js/pages/blog/[slug].js +132 -0
- package/core/js/pages/blog/_search.mjs +108 -0
- package/core/js/pages/blog/index.js +193 -0
- package/core/js/pages/blog/page/[num]/index.js +7 -0
- package/core/js/pages/connections/callback/index.js +148 -0
- package/core/js/pages/contact/index.js +170 -0
- package/core/js/pages/cookies.js +2 -0
- package/core/js/pages/dashboard/account/index.js +366 -0
- package/core/js/pages/dashboard/account/modules/orders.js +82 -0
- package/core/js/pages/dashboard/account/sections/api-keys.js +149 -0
- package/core/js/pages/dashboard/account/sections/billing.js +1667 -0
- package/core/js/pages/dashboard/account/sections/connections.js +333 -0
- package/core/js/pages/dashboard/account/sections/data-request.js +225 -0
- package/core/js/pages/dashboard/account/sections/delete.js +80 -0
- package/core/js/pages/dashboard/account/sections/notifications.js +167 -0
- package/core/js/pages/dashboard/account/sections/orders.js +151 -0
- package/core/js/pages/dashboard/account/sections/profile.js +200 -0
- package/core/js/pages/dashboard/account/sections/referrals.js +217 -0
- package/core/js/pages/dashboard/account/sections/refund.js +253 -0
- package/core/js/pages/dashboard/account/sections/security.js +726 -0
- package/core/js/pages/dashboard/account/sections/team.js +247 -0
- package/core/js/pages/download/index.js +418 -0
- package/core/js/pages/extension/index.js +164 -0
- package/core/js/pages/extension/installed/index.js +135 -0
- package/core/js/pages/feedback/index.js +193 -0
- package/core/js/pages/legal/_document.js +91 -0
- package/core/js/pages/payment/checkout/index.js +420 -0
- package/core/js/pages/payment/checkout/modules/api.js +139 -0
- package/core/js/pages/payment/checkout/modules/dev-section.js +175 -0
- package/core/js/pages/payment/checkout/modules/discount.js +69 -0
- package/core/js/pages/payment/checkout/modules/pricing.js +73 -0
- package/core/js/pages/payment/checkout/modules/state.js +339 -0
- package/core/js/pages/payment/checkout/modules/tracking.js +54 -0
- package/core/js/pages/payment/confirmation/index.js +91 -0
- package/core/js/pages/payment/confirmation/modules/celebration.js +45 -0
- package/core/js/pages/payment/confirmation/modules/state.js +112 -0
- package/core/js/pages/payment/confirmation/modules/tracking.js +133 -0
- package/core/js/pages/payment/confirmation/modules/verify.js +142 -0
- package/core/js/pages/portal/email-preferences/index.js +183 -0
- package/core/js/pages/pricing/index.js +425 -0
- package/core/js/pages/privacy.js +2 -0
- package/core/js/pages/reset/index.js +12 -0
- package/core/js/pages/signin/index.js +22 -0
- package/core/js/pages/signup/index.js +23 -0
- package/core/js/pages/status/index.js +687 -0
- package/core/js/pages/terms.js +2 -0
- package/core/js/pages/test/components/[category]/[name]/index.js +26 -0
- package/core/js/pages/test/libraries/appearance/index.js +220 -0
- package/core/js/pages/test/libraries/form-manager/index.js +328 -0
- package/core/js/pages/test/libraries/layers/index.js +16 -0
- package/core/js/pages/test/libraries/verts/index.js +68 -0
- package/core/js/pages/test/sections/[category]/[name]/index.js +28 -0
- package/core/js/pages/test/styleguide/index.js +35 -0
- package/core/js/pages/token/index.js +203 -0
- package/core/logos/brandmarks/original/apple-app-store.svg +1 -0
- package/core/logos/brandmarks/original/apple.svg +6 -0
- package/core/logos/brandmarks/original/bitbucket.svg +14 -0
- package/core/logos/brandmarks/original/bitcoin.svg +1 -0
- package/core/logos/brandmarks/original/bluesky.svg +7 -0
- package/core/logos/brandmarks/original/bootstrap.svg +43 -0
- package/core/logos/brandmarks/original/brave.svg +1 -0
- package/core/logos/brandmarks/original/chrome.svg +1 -0
- package/core/logos/brandmarks/original/discord.svg +1 -0
- package/core/logos/brandmarks/original/dropbox.svg +10 -0
- package/core/logos/brandmarks/original/edge.svg +1 -0
- package/core/logos/brandmarks/original/facebook-f.svg +1 -0
- package/core/logos/brandmarks/original/facebook.svg +8 -0
- package/core/logos/brandmarks/original/figma.svg +10 -0
- package/core/logos/brandmarks/original/firefox.svg +1 -0
- package/core/logos/brandmarks/original/github.svg +6 -0
- package/core/logos/brandmarks/original/gitlab.svg +17 -0
- package/core/logos/brandmarks/original/google-a-s.svg +1 -0
- package/core/logos/brandmarks/original/google-chrome-webstore.svg +49 -0
- package/core/logos/brandmarks/original/google-play-store.svg +1 -0
- package/core/logos/brandmarks/original/google-web-search.svg +1 -0
- package/core/logos/brandmarks/original/google.svg +1 -0
- package/core/logos/brandmarks/original/gulp.svg +4 -0
- package/core/logos/brandmarks/original/html-5.svg +1 -0
- package/core/logos/brandmarks/original/instagram.svg +1 -0
- package/core/logos/brandmarks/original/javascript.svg +1 -0
- package/core/logos/brandmarks/original/jira.svg +18 -0
- package/core/logos/brandmarks/original/kick.svg +7 -0
- package/core/logos/brandmarks/original/linkedin.svg +1 -0
- package/core/logos/brandmarks/original/linux.svg +28 -0
- package/core/logos/brandmarks/original/messenger.svg +16 -0
- package/core/logos/brandmarks/original/microsoft.svg +1 -0
- package/core/logos/brandmarks/original/npm.svg +8 -0
- package/core/logos/brandmarks/original/opera.svg +1 -0
- package/core/logos/brandmarks/original/patreon.svg +6 -0
- package/core/logos/brandmarks/original/pinterest.svg +6 -0
- package/core/logos/brandmarks/original/reddit.svg +1 -0
- package/core/logos/brandmarks/original/safari.svg +31 -0
- package/core/logos/brandmarks/original/sass.svg +3 -0
- package/core/logos/brandmarks/original/signal.svg +6 -0
- package/core/logos/brandmarks/original/skype.svg +47 -0
- package/core/logos/brandmarks/original/slack.svg +33 -0
- package/core/logos/brandmarks/original/soundcloud.svg +1 -0
- package/core/logos/brandmarks/original/spotify.svg +5 -0
- package/core/logos/brandmarks/original/steam.svg +6 -0
- package/core/logos/brandmarks/original/telegram.svg +14 -0
- package/core/logos/brandmarks/original/tiktok.svg +1 -0
- package/core/logos/brandmarks/original/trello.svg +16 -0
- package/core/logos/brandmarks/original/tumblr.svg +1 -0
- package/core/logos/brandmarks/original/twitch.svg +6 -0
- package/core/logos/brandmarks/original/twitter.svg +6 -0
- package/core/logos/brandmarks/original/unity.svg +6 -0
- package/core/logos/brandmarks/original/x.svg +7 -0
- package/core/logos/brandmarks/original/yahoo-y.svg +1 -0
- package/core/logos/brandmarks/original/youtube-square.svg +1 -0
- package/core/logos/brandmarks/original/youtube.svg +1 -0
- package/core/logos/combomarks/dark/airbnb.svg +30 -0
- package/core/logos/combomarks/dark/slack.svg +38 -0
- package/core/logos/combomarks/dark/spotify.svg +35 -0
- package/core/logos/combomarks/dark/twilio.svg +26 -0
- package/core/logos/combomarks/dark/weebly.svg +35 -0
- package/core/logos/combomarks/gray/airbnb.svg +33 -0
- package/core/logos/combomarks/gray/alphabet.svg +23 -0
- package/core/logos/combomarks/gray/amazon.svg +38 -0
- package/core/logos/combomarks/gray/boring-company.svg +91 -0
- package/core/logos/combomarks/gray/burberry.svg +239 -0
- package/core/logos/combomarks/gray/embark.svg +21 -0
- package/core/logos/combomarks/gray/fitbit.svg +61 -0
- package/core/logos/combomarks/gray/fred-perry.svg +94 -0
- package/core/logos/combomarks/gray/google.svg +27 -0
- package/core/logos/combomarks/gray/hollister.svg +34 -0
- package/core/logos/combomarks/gray/hubspot.svg +27 -0
- package/core/logos/combomarks/gray/kaplan.svg +33 -0
- package/core/logos/combomarks/gray/lenovo.svg +27 -0
- package/core/logos/combomarks/gray/levis.svg +25 -0
- package/core/logos/combomarks/gray/mapbox.svg +37 -0
- package/core/logos/combomarks/gray/netflix.svg +15 -0
- package/core/logos/combomarks/gray/new-balance.svg +32 -0
- package/core/logos/combomarks/gray/nike.svg +17 -0
- package/core/logos/combomarks/gray/paypal.svg +41 -0
- package/core/logos/combomarks/gray/puma.svg +82 -0
- package/core/logos/combomarks/gray/samsung.svg +26 -0
- package/core/logos/combomarks/gray/shopify.svg +36 -0
- package/core/logos/combomarks/gray/slack.svg +42 -0
- package/core/logos/combomarks/gray/spotify.svg +38 -0
- package/core/logos/combomarks/gray/stripe.svg +24 -0
- package/core/logos/combomarks/gray/tnf.svg +30 -0
- package/core/logos/combomarks/gray/twilio.svg +29 -0
- package/core/logos/combomarks/gray/uber.svg +24 -0
- package/core/logos/combomarks/gray/weebly.svg +38 -0
- package/core/logos/combomarks/original/airbnb.svg +33 -0
- package/core/logos/combomarks/original/alphabet.svg +23 -0
- package/core/logos/combomarks/original/amazon.svg +38 -0
- package/core/logos/combomarks/original/asana.svg +8 -0
- package/core/logos/combomarks/original/boring-company.svg +91 -0
- package/core/logos/combomarks/original/discord.svg +6 -0
- package/core/logos/combomarks/original/embark.svg +24 -0
- package/core/logos/combomarks/original/fitbit.svg +60 -0
- package/core/logos/combomarks/original/github.svg +6 -0
- package/core/logos/combomarks/original/gitlab.svg +20 -0
- package/core/logos/combomarks/original/google.svg +28 -0
- package/core/logos/combomarks/original/hubspot.svg +29 -0
- package/core/logos/combomarks/original/instagram.svg +7 -0
- package/core/logos/combomarks/original/kickstarter.svg +6 -0
- package/core/logos/combomarks/original/lenovo.svg +24 -0
- package/core/logos/combomarks/original/linkedin.svg +6 -0
- package/core/logos/combomarks/original/mapbox.svg +39 -0
- package/core/logos/combomarks/original/mastodon.svg +7 -0
- package/core/logos/combomarks/original/medium.svg +6 -0
- package/core/logos/combomarks/original/meta.svg +22 -0
- package/core/logos/combomarks/original/microsoft.svg +13 -0
- package/core/logos/combomarks/original/monday.svg +12 -0
- package/core/logos/combomarks/original/netflix.svg +15 -0
- package/core/logos/combomarks/original/notion.svg +9 -0
- package/core/logos/combomarks/original/paypal.svg +41 -0
- package/core/logos/combomarks/original/reddit.svg +13 -0
- package/core/logos/combomarks/original/samsung.svg +25 -0
- package/core/logos/combomarks/original/shopify.svg +35 -0
- package/core/logos/combomarks/original/slack.svg +47 -0
- package/core/logos/combomarks/original/spotify.svg +37 -0
- package/core/logos/combomarks/original/stackoverflow.svg +28 -0
- package/core/logos/combomarks/original/stripe.svg +24 -0
- package/core/logos/combomarks/original/tiktok.svg +12 -0
- package/core/logos/combomarks/original/tumblr.svg +6 -0
- package/core/logos/combomarks/original/uber.svg +24 -0
- package/core/logos/combomarks/original/weebly.svg +34 -0
- package/core/logos/combomarks/original/whatsapp.svg +20 -0
- package/core/logos/combomarks/original/youtube.svg +10 -0
- package/core/logos/combomarks/original/zoom.svg +7 -0
- package/core/logos/combomarks/white/airbnb.svg +33 -0
- package/core/logos/combomarks/white/alphabet.svg +23 -0
- package/core/logos/combomarks/white/amazon.svg +38 -0
- package/core/logos/combomarks/white/boring-company.svg +91 -0
- package/core/logos/combomarks/white/embark.svg +21 -0
- package/core/logos/combomarks/white/fitbit.svg +61 -0
- package/core/logos/combomarks/white/google.svg +27 -0
- package/core/logos/combomarks/white/hubspot.svg +27 -0
- package/core/logos/combomarks/white/kaplan.svg +33 -0
- package/core/logos/combomarks/white/lenovo.svg +27 -0
- package/core/logos/combomarks/white/mapbox.svg +37 -0
- package/core/logos/combomarks/white/netflix.svg +15 -0
- package/core/logos/combomarks/white/paypal.svg +40 -0
- package/core/logos/combomarks/white/samsung.svg +26 -0
- package/core/logos/combomarks/white/shopify.svg +36 -0
- package/core/logos/combomarks/white/slack.svg +42 -0
- package/core/logos/combomarks/white/spotify.svg +38 -0
- package/core/logos/combomarks/white/stripe.svg +24 -0
- package/core/logos/combomarks/white/twilio.svg +29 -0
- package/core/logos/combomarks/white/uber.svg +24 -0
- package/core/logos/combomarks/white/weebly.svg +38 -0
- package/defaults/pages/404.md +4 -0
- package/defaults/pages/about.md +4 -0
- package/defaults/pages/admin/calendar/index.html +4 -0
- package/defaults/pages/admin/firebase/index.html +4 -0
- package/defaults/pages/admin/index.md +4 -0
- package/defaults/pages/admin/posts/editor.html +4 -0
- package/defaults/pages/admin/posts/index.html +4 -0
- package/defaults/pages/admin/stackblitz/index.html +4 -0
- package/defaults/pages/admin/studio/index.html +4 -0
- package/defaults/pages/admin/users/index.html +4 -0
- package/defaults/pages/admin/users/new.html +4 -0
- package/defaults/pages/admin/verts/index.html +4 -0
- package/defaults/pages/ads-txt.html +11 -0
- package/defaults/pages/alternatives/index.md +4 -0
- package/defaults/pages/app.md +4 -0
- package/defaults/pages/auth/reset.md +4 -0
- package/defaults/pages/auth/signin.md +4 -0
- package/defaults/pages/auth/signup.md +4 -0
- package/defaults/pages/auth/token.md +4 -0
- package/defaults/pages/blog/categories/category.html +21 -0
- package/defaults/pages/blog/categories.md +4 -0
- package/defaults/pages/blog/index-json.html +27 -0
- package/defaults/pages/blog/tags/tag.html +21 -0
- package/defaults/pages/blog/tags.md +4 -0
- package/defaults/pages/blog.md +22 -0
- package/defaults/pages/careers.md +4 -0
- package/defaults/pages/connections/callback.md +4 -0
- package/defaults/pages/contact.md +4 -0
- package/defaults/pages/dashboard/account.md +4 -0
- package/defaults/pages/dashboard/index.md +12 -0
- package/defaults/pages/download.md +4 -0
- package/defaults/pages/extension/index.html +4 -0
- package/defaults/pages/extension/installed.html +4 -0
- package/defaults/pages/feedback.md +4 -0
- package/defaults/pages/feeds/posts-json.html +40 -0
- package/defaults/pages/feeds/posts-xml.html +38 -0
- package/defaults/pages/humans.html +25 -0
- package/defaults/pages/index.md +4 -0
- package/defaults/pages/legal/cookies.md +4 -0
- package/defaults/pages/legal/privacy.md +4 -0
- package/defaults/pages/legal/terms.md +4 -0
- package/defaults/pages/llms-txt.html +46 -0
- package/defaults/pages/opensearch.html +16 -0
- package/defaults/pages/pages-json.html +42 -0
- package/defaults/pages/payment/checkout.md +4 -0
- package/defaults/pages/payment/confirmation.md +4 -0
- package/defaults/pages/portal/email-preferences.md +4 -0
- package/defaults/pages/pricing.md +4 -0
- package/defaults/pages/redirects/account.md +12 -0
- package/defaults/pages/redirects/admin/dashboard.md +11 -0
- package/defaults/pages/redirects/auth/change-password.md +9 -0
- package/defaults/pages/redirects/auth/forgot.md +9 -0
- package/defaults/pages/redirects/auth/join.md +9 -0
- package/defaults/pages/redirects/auth/login.md +9 -0
- package/defaults/pages/redirects/auth/recover.md +9 -0
- package/defaults/pages/redirects/auth/register.md +9 -0
- package/defaults/pages/redirects/auth/reset-password.md +9 -0
- package/defaults/pages/redirects/billing/cancel.md +9 -0
- package/defaults/pages/redirects/billing/refund.md +9 -0
- package/defaults/pages/robots.html +36 -0
- package/defaults/pages/security-txt.html +11 -0
- package/defaults/pages/sitemap.html +37 -0
- package/defaults/pages/status.md +4 -0
- package/defaults/pages/team/index.md +4 -0
- package/defaults/pages/test/account/dashboard.html +16 -0
- package/defaults/pages/test/index.md +12 -0
- package/defaults/pages/test/libraries/appearance.html +268 -0
- package/defaults/pages/test/libraries/cover.html +50 -0
- package/defaults/pages/test/libraries/error.html +30 -0
- package/defaults/pages/test/libraries/firestore.html +202 -0
- package/defaults/pages/test/libraries/form-manager.html +374 -0
- package/defaults/pages/test/libraries/layers.html +52 -0
- package/defaults/pages/test/libraries/lazy-loading.html +450 -0
- package/defaults/pages/test/libraries/verts.html +149 -0
- package/defaults/pages/test/redirect/external.md +13 -0
- package/defaults/pages/test/redirect/internal.md +13 -0
- package/defaults/pages/test/styleguide/index.html +588 -0
- package/defaults/pages/test/translation/index.md +50 -0
- package/defaults/pages/updates/index.md +4 -0
- package/defaults/sample-posts/2026-01-12-welcome-to-the-blog.md +26 -0
- package/defaults/sample-posts/2026-02-03-a-field-guide-to-good-defaults.md +50 -0
- package/defaults/sample-posts/2026-02-24-shipping-week-notes.md +37 -0
- package/defaults/sample-posts/2026-03-17-the-case-for-one-config.md +36 -0
- package/defaults/sample-posts/2026-04-09-write-release-notes-people-read.md +34 -0
- package/defaults/sample-posts/2026-05-21-dark-mode-done-properly.md +38 -0
- package/defaults/sample-posts/2026-06-02-the-boring-deploy-is-the-good-deploy.md +30 -0
- package/defaults/sample-posts/2026-06-11-naming-things-a-survival-guide.md +30 -0
- package/defaults/sample-posts/2026-06-19-your-pricing-page-is-a-product.md +28 -0
- package/defaults/sample-posts/2026-06-30-email-is-an-api-with-feelings.md +28 -0
- package/defaults/sample-posts/2026-07-08-ship-the-docs-with-the-diff.md +30 -0
- package/defaults/sample-team/avery-quinn.md +22 -0
- package/defaults/sample-team/jordan-reyes.md +25 -0
- package/defaults/sample-team/riley-nakamura.md +22 -0
- package/defaults/sample-team/sam-okafor.md +22 -0
- package/defaults/sample-updates/v1.0.0.md +28 -0
- package/defaults/sample-updates/v1.1.0.md +27 -0
- package/defaults/sample-updates/v1.2.0.md +27 -0
- package/defaults/sample-updates/v1.3.0.md +28 -0
- package/defaults/showcase/components.html +62 -0
- package/defaults/showcase/entry.html +76 -0
- package/defaults/showcase/frame.html +62 -0
- package/defaults/showcase/index.html +59 -0
- package/dist/assets.js +1349 -0
- package/dist/brand-tokens.js +195 -0
- package/dist/build.js +192 -0
- package/dist/cachebreak-html.js +71 -0
- package/dist/cli-run.js +25 -0
- package/dist/cli.js +47 -0
- package/dist/collections.js +183 -0
- package/dist/commands/audit.js +470 -0
- package/dist/commands/build.js +197 -0
- package/dist/commands/clean.js +18 -0
- package/dist/commands/customize.js +125 -0
- package/dist/commands/deploy.js +441 -0
- package/dist/commands/dev.js +1067 -0
- package/dist/commands/install.js +31 -0
- package/dist/commands/lib/deploy-precheck.js +43 -0
- package/dist/commands/lib/ensure-target.js +115 -0
- package/dist/commands/migrate.js +86 -0
- package/dist/commands/purge.js +31 -0
- package/dist/commands/test.js +237 -0
- package/dist/commands/translate.js +57 -0
- package/dist/commands/update.js +22 -0
- package/dist/commands/version.js +11 -0
- package/dist/config-sections.js +245 -0
- package/dist/consumer-scan.js +118 -0
- package/dist/consumer.js +49 -0
- package/dist/customize.js +328 -0
- package/dist/decisions.js +153 -0
- package/dist/dist-audit.js +417 -0
- package/dist/dynamic-pages.js +357 -0
- package/dist/engine.js +1402 -0
- package/dist/firebase-auth-helpers.js +198 -0
- package/dist/font-metrics.js +420 -0
- package/dist/frontmatter-liquid.js +253 -0
- package/dist/github-secrets.js +80 -0
- package/dist/hero-animations.js +170 -0
- package/dist/imagemin.js +261 -0
- package/dist/index.js +41 -0
- package/dist/inline-icons.js +65 -0
- package/dist/language-flags.js +55 -0
- package/dist/layers.js +80 -0
- package/dist/layouts.js +44 -0
- package/dist/limit-collections.js +199 -0
- package/dist/link-resolver.js +303 -0
- package/dist/markdown-images.js +85 -0
- package/dist/merge.js +20 -0
- package/dist/migrate/codemod.js +172 -0
- package/dist/migrate/config-convert.js +448 -0
- package/dist/migrate/consumer-assets.js +130 -0
- package/dist/migrate/index.js +223 -0
- package/dist/migrate/lint.js +108 -0
- package/dist/migrate/rules.js +1929 -0
- package/dist/minify-html.js +148 -0
- package/dist/mode-helpers.js +96 -0
- package/dist/omega-bin.js +7 -0
- package/dist/overrides.js +190 -0
- package/dist/path-prefix.js +167 -0
- package/dist/paths.js +37 -0
- package/dist/pricing.js +301 -0
- package/dist/purge.js +95 -0
- package/dist/redirect-page.js +45 -0
- package/dist/sample-content.js +210 -0
- package/dist/scaffold.js +123 -0
- package/dist/sections.js +810 -0
- package/dist/service-worker.js +200 -0
- package/dist/social-pages.js +142 -0
- package/dist/srcset.js +67 -0
- package/dist/static-assets.js +154 -0
- package/dist/target-shortlinks.js +123 -0
- package/dist/theme-vocabulary.js +76 -0
- package/dist/translate/collect-text-nodes.js +93 -0
- package/dist/translate/default-routes.js +152 -0
- package/dist/translate/generate-defaults.js +297 -0
- package/dist/translate/index.js +568 -0
- package/dist/translate/packaged-defaults.js +127 -0
- package/dist/translate/sitemap.js +108 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/actions-secrets.js +145 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/bundle.js +292 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-precheck.js +54 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/env-watch.js +132 -0
- package/dist/vendor/devkit/framework-deps.js +46 -0
- package/dist/vendor/devkit/icons.js +178 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/reads.js +274 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/strip-dev-blocks-plugin.js +29 -0
- package/dist/vendor/devkit/strip-dev-blocks.js +32 -0
- package/dist/vendor/devkit/target-secrets.js +165 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/template-kit/data/language-flags.js +54 -0
- package/dist/vendor/template-kit/data/languages.js +195 -0
- package/dist/vendor/template-kit/data/social-urls.js +26 -0
- package/dist/vendor/template-kit/filters.js +227 -0
- package/dist/vendor/template-kit/index.js +28 -0
- package/dist/vendor/template-kit/jekyll-compat.js +268 -0
- package/dist/vendor/template-kit/register-liquid.js +0 -0
- package/dist/vendor/template-kit/tags/collections.js +191 -0
- package/dist/vendor/template-kit/tags/conditionals.js +101 -0
- package/dist/vendor/template-kit/tags/content.js +175 -0
- package/dist/vendor/template-kit/tags/index.js +33 -0
- package/dist/vendor/template-kit/tags/media.js +376 -0
- package/dist/vendor/template-kit/variable-resolver.js +163 -0
- package/docs/ads-system.md +109 -0
- package/docs/classy-v2/DIRECTION.md +92 -0
- package/docs/classy-v2/draft-1.html +1932 -0
- package/docs/classy-v2/draft-2.html +1956 -0
- package/docs/frontmatter.md +153 -0
- package/docs/index.md +254 -0
- package/docs/libs.md +298 -0
- package/docs/omega-sections-spec.md +159 -0
- package/docs/page-contract.md +112 -0
- package/docs/sections.md +845 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/template-kit.md +128 -0
- package/docs/test-framework.md +163 -0
- package/package.json +122 -0
- package/runtime/boot.js +149 -0
- package/runtime/icons.js +77 -0
- package/runtime/manager.js +36 -0
- package/scaffold/.github/workflows/build.yml +139 -0
- package/scaffold/.nvmrc +1 -0
- package/scaffold/AGENTS.md +55 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/_.gitattributes +4 -0
- package/scaffold/_.gitignore +27 -0
- package/scaffold/config/omega.json5 +20 -0
- package/scaffold/src/assets/css/pages/.gitkeep +0 -0
- package/scaffold/src/assets/images/.gitkeep +0 -0
- package/scaffold/src/assets/js/pages/.gitkeep +0 -0
- package/scaffold/src/pages/.gitkeep +0 -0
- package/scaffold/src/pages/example.md.txt +94 -0
- package/scaffold/src/service-worker.js +16 -0
- package/sw/entry.js +19 -0
- package/sw/manager.js +281 -0
- package/themes/_template/README.md +115 -0
- package/themes/_template/_config.scss +60 -0
- package/themes/_template/_theme.js +14 -0
- package/themes/_template/_theme.scss +28 -0
- package/themes/_template/css/base/_root.scss +19 -0
- package/themes/_template/css/components/_components.scss +23 -0
- package/themes/base/README.md +5 -0
- package/themes/base/_components/data/org-chart/component.html +33 -0
- package/themes/base/_components/data/org-chart/component.json5 +33 -0
- package/themes/base/_components/data/org-chart/component.scss +183 -0
- package/themes/base/_components/heading/masthead/component.html +22 -0
- package/themes/base/_components/heading/masthead/component.json5 +30 -0
- package/themes/base/_components/heading/section-head/component.html +18 -0
- package/themes/base/_components/heading/section-head/component.json5 +27 -0
- package/themes/base/_components/pricing/features/component.html +29 -0
- package/themes/base/_components/pricing/features/component.json5 +30 -0
- package/themes/base/_hero/orbit/index.html +30 -0
- package/themes/base/_hero/orbit/script.js +33 -0
- package/themes/base/_hero/orbit/style.scss +133 -0
- package/themes/base/_includes/admin/sections/sidebar.html +3 -0
- package/themes/base/_includes/admin/sections/topbar.html +3 -0
- package/themes/base/_includes/backend/sections/sidebar.html +3 -0
- package/themes/base/_includes/backend/sections/topbar.html +3 -0
- package/themes/base/_includes/frontend/components/connection-card.html +49 -0
- package/themes/base/_includes/frontend/components/post-card.html +54 -0
- package/themes/base/_includes/frontend/sections/account-connections.html +42 -0
- package/themes/base/_includes/frontend/sections/account-section-header.html +11 -0
- package/themes/base/_includes/frontend/sections/footer.html +154 -0
- package/themes/base/_includes/frontend/sections/nav.html +276 -0
- package/themes/base/_includes/global/sections/account.html +72 -0
- package/themes/base/_includes/global/sections/app-sidebar.html +248 -0
- package/themes/base/_includes/global/sections/app-topbar.html +228 -0
- package/themes/base/_includes/global/sections/page-header.html +99 -0
- package/themes/base/_layouts/admin/core/minimal-viewport-locked.html +22 -0
- package/themes/base/_layouts/admin/core/minimal.html +23 -0
- package/themes/base/_layouts/backend/core/base.html +57 -0
- package/themes/base/_layouts/backend/core/minimal-viewport-locked.html +32 -0
- package/themes/base/_layouts/backend/core/minimal.html +32 -0
- package/themes/base/_layouts/backend/pages/dashboard/index.html +230 -0
- package/themes/base/_layouts/frontend/core/base.html +31 -0
- package/themes/base/_layouts/frontend/core/cover.html +40 -0
- package/themes/base/_layouts/frontend/core/minimal.html +47 -0
- package/themes/base/_layouts/frontend/pages/404.html +54 -0
- package/themes/base/_layouts/frontend/pages/about.html +144 -0
- package/themes/base/_layouts/frontend/pages/account/index.html +2104 -0
- package/themes/base/_layouts/frontend/pages/alternatives/alternative.html +319 -0
- package/themes/base/_layouts/frontend/pages/alternatives/index.html +186 -0
- package/themes/base/_layouts/frontend/pages/app.html +142 -0
- package/themes/base/_layouts/frontend/pages/auth/reset.html +53 -0
- package/themes/base/_layouts/frontend/pages/auth/signin.html +90 -0
- package/themes/base/_layouts/frontend/pages/auth/signup.html +141 -0
- package/themes/base/_layouts/frontend/pages/auth/token.html +44 -0
- package/themes/base/_layouts/frontend/pages/blog/categories/category.html +88 -0
- package/themes/base/_layouts/frontend/pages/blog/categories/index.html +88 -0
- package/themes/base/_layouts/frontend/pages/blog/index.html +255 -0
- package/themes/base/_layouts/frontend/pages/blog/post.html +261 -0
- package/themes/base/_layouts/frontend/pages/blog/tags/index.html +84 -0
- package/themes/base/_layouts/frontend/pages/blog/tags/tag.html +88 -0
- package/themes/base/_layouts/frontend/pages/collection/category.html +72 -0
- package/themes/base/_layouts/frontend/pages/collection/document.html +54 -0
- package/themes/base/_layouts/frontend/pages/collection/index.html +84 -0
- package/themes/base/_layouts/frontend/pages/connections/callback.html +55 -0
- package/themes/base/_layouts/frontend/pages/contact.html +214 -0
- package/themes/base/_layouts/frontend/pages/download.html +793 -0
- package/themes/base/_layouts/frontend/pages/extension/index.html +253 -0
- package/themes/base/_layouts/frontend/pages/extension/installed.html +198 -0
- package/themes/base/_layouts/frontend/pages/feedback.html +171 -0
- package/themes/base/_layouts/frontend/pages/index.html +66 -0
- package/themes/base/_layouts/frontend/pages/legal/document.html +78 -0
- package/themes/base/_layouts/frontend/pages/payment/checkout.html +459 -0
- package/themes/base/_layouts/frontend/pages/payment/confirmation.html +131 -0
- package/themes/base/_layouts/frontend/pages/portal/email-preferences.html +104 -0
- package/themes/base/_layouts/frontend/pages/pricing.html +596 -0
- package/themes/base/_layouts/frontend/pages/status.html +361 -0
- package/themes/base/_layouts/frontend/pages/team/index.html +210 -0
- package/themes/base/_layouts/frontend/pages/team/member.html +125 -0
- package/themes/base/_layouts/frontend/pages/updates/index.html +119 -0
- package/themes/base/_layouts/frontend/pages/updates/update.html +118 -0
- package/themes/base/_sections/about/hero/section.html +68 -0
- package/themes/base/_sections/about/hero/section.json5 +58 -0
- package/themes/base/_sections/about/letter/section.html +60 -0
- package/themes/base/_sections/about/letter/section.json5 +56 -0
- package/themes/base/_sections/about/photo-band/section.html +24 -0
- package/themes/base/_sections/about/photo-band/section.json5 +23 -0
- package/themes/base/_sections/about/principles/section.html +17 -0
- package/themes/base/_sections/about/principles/section.json5 +34 -0
- package/themes/base/_sections/about/timeline/section.html +20 -0
- package/themes/base/_sections/about/timeline/section.json5 +34 -0
- package/themes/base/_sections/marketing/bento/section.html +91 -0
- package/themes/base/_sections/marketing/bento/section.json5 +152 -0
- package/themes/base/_sections/marketing/cta/section.html +44 -0
- package/themes/base/_sections/marketing/cta/section.json5 +47 -0
- package/themes/base/_sections/marketing/faq/section.html +52 -0
- package/themes/base/_sections/marketing/faq/section.json5 +68 -0
- package/themes/base/_sections/marketing/hero/section.html +349 -0
- package/themes/base/_sections/marketing/hero/section.json5 +335 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.html +38 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.js +86 -0
- package/themes/base/_sections/marketing/newsletter-cta/section.json5 +29 -0
- package/themes/base/_sections/marketing/pricing-cards/section.html +115 -0
- package/themes/base/_sections/marketing/pricing-cards/section.json5 +72 -0
- package/themes/base/_sections/marketing/product-demo/section.html +62 -0
- package/themes/base/_sections/marketing/product-demo/section.js +44 -0
- package/themes/base/_sections/marketing/product-demo/section.json5 +52 -0
- package/themes/base/_sections/marketing/prose/section.html +22 -0
- package/themes/base/_sections/marketing/prose/section.json5 +41 -0
- package/themes/base/_sections/marketing/showcase/section.html +35 -0
- package/themes/base/_sections/marketing/showcase/section.json5 +52 -0
- package/themes/base/_sections/marketing/stats/section.html +32 -0
- package/themes/base/_sections/marketing/stats/section.json5 +44 -0
- package/themes/base/_sections/marketing/testimonials/section.html +54 -0
- package/themes/base/_sections/marketing/testimonials/section.json5 +70 -0
- package/themes/base/_sections/marketing/trusted-by/section.html +30 -0
- package/themes/base/_sections/marketing/trusted-by/section.json5 +38 -0
- package/themes/base/_sections/verts/unit/section.html +7 -0
- package/themes/base/_sections/verts/unit/section.js +22 -0
- package/themes/base/_sections/verts/unit/section.json5 +24 -0
- package/themes/base/_sections/verts/unit/section.scss +24 -0
- package/themes/base/_theme.js +13 -0
- package/themes/base/_theme.scss +4 -0
- package/themes/bootstrap/_theme.js +16 -0
- package/themes/bootstrap/_theme.scss +11 -0
- package/themes/bootstrap/js/index.esm.js +19 -0
- package/themes/bootstrap/js/index.umd.js +34 -0
- package/themes/bootstrap/js/src/alert.js +87 -0
- package/themes/bootstrap/js/src/base-component.js +86 -0
- package/themes/bootstrap/js/src/button.js +72 -0
- package/themes/bootstrap/js/src/carousel.js +474 -0
- package/themes/bootstrap/js/src/collapse.js +297 -0
- package/themes/bootstrap/js/src/dom/data.js +55 -0
- package/themes/bootstrap/js/src/dom/event-handler.js +317 -0
- package/themes/bootstrap/js/src/dom/manipulator.js +71 -0
- package/themes/bootstrap/js/src/dom/selector-engine.js +126 -0
- package/themes/bootstrap/js/src/dropdown.js +458 -0
- package/themes/bootstrap/js/src/modal.js +378 -0
- package/themes/bootstrap/js/src/offcanvas.js +282 -0
- package/themes/bootstrap/js/src/popover.js +97 -0
- package/themes/bootstrap/js/src/scrollspy.js +296 -0
- package/themes/bootstrap/js/src/tab.js +315 -0
- package/themes/bootstrap/js/src/toast.js +224 -0
- package/themes/bootstrap/js/src/tooltip.js +632 -0
- package/themes/bootstrap/js/src/util/backdrop.js +151 -0
- package/themes/bootstrap/js/src/util/component-functions.js +35 -0
- package/themes/bootstrap/js/src/util/config.js +65 -0
- package/themes/bootstrap/js/src/util/focustrap.js +115 -0
- package/themes/bootstrap/js/src/util/index.js +306 -0
- package/themes/bootstrap/js/src/util/sanitizer.js +117 -0
- package/themes/bootstrap/js/src/util/scrollbar.js +114 -0
- package/themes/bootstrap/js/src/util/swipe.js +146 -0
- package/themes/bootstrap/js/src/util/template-factory.js +160 -0
- package/themes/bootstrap/overrides/_accordion.scss +21 -0
- package/themes/bootstrap/overrides/_avatars.scss +51 -0
- package/themes/bootstrap/overrides/_blog-images.scss +45 -0
- package/themes/bootstrap/overrides/_buttons-adaptive.scss +141 -0
- package/themes/bootstrap/overrides/_color-shades.scss +25 -0
- package/themes/bootstrap/overrides/_cursor-utilities.scss +13 -0
- package/themes/bootstrap/overrides/_index.scss +30 -0
- package/themes/bootstrap/overrides/_links.scss +26 -0
- package/themes/bootstrap/overrides/_soft-colors.scss +99 -0
- package/themes/bootstrap/overrides/_spacing-extended.scss +74 -0
- package/themes/bootstrap/overrides/_spinners.scss +22 -0
- package/themes/bootstrap/overrides/_typography-utilities.scss +40 -0
- package/themes/bootstrap/scss/_accordion.scss +153 -0
- package/themes/bootstrap/scss/_alert.scss +68 -0
- package/themes/bootstrap/scss/_badge.scss +38 -0
- package/themes/bootstrap/scss/_breadcrumb.scss +40 -0
- package/themes/bootstrap/scss/_button-group.scss +147 -0
- package/themes/bootstrap/scss/_buttons.scss +216 -0
- package/themes/bootstrap/scss/_card.scss +238 -0
- package/themes/bootstrap/scss/_carousel.scss +226 -0
- package/themes/bootstrap/scss/_close.scss +66 -0
- package/themes/bootstrap/scss/_containers.scss +41 -0
- package/themes/bootstrap/scss/_dropdown.scss +250 -0
- package/themes/bootstrap/scss/_forms.scss +9 -0
- package/themes/bootstrap/scss/_functions.scss +302 -0
- package/themes/bootstrap/scss/_grid.scss +39 -0
- package/themes/bootstrap/scss/_helpers.scss +12 -0
- package/themes/bootstrap/scss/_images.scss +42 -0
- package/themes/bootstrap/scss/_list-group.scss +199 -0
- package/themes/bootstrap/scss/_maps.scss +174 -0
- package/themes/bootstrap/scss/_mixins.scss +42 -0
- package/themes/bootstrap/scss/_modal.scss +240 -0
- package/themes/bootstrap/scss/_nav.scss +197 -0
- package/themes/bootstrap/scss/_navbar.scss +289 -0
- package/themes/bootstrap/scss/_offcanvas.scss +147 -0
- package/themes/bootstrap/scss/_pagination.scss +109 -0
- package/themes/bootstrap/scss/_placeholders.scss +51 -0
- package/themes/bootstrap/scss/_popover.scss +196 -0
- package/themes/bootstrap/scss/_progress.scss +68 -0
- package/themes/bootstrap/scss/_reboot.scss +611 -0
- package/themes/bootstrap/scss/_root.scss +187 -0
- package/themes/bootstrap/scss/_spinners.scss +85 -0
- package/themes/bootstrap/scss/_tables.scss +171 -0
- package/themes/bootstrap/scss/_toasts.scss +73 -0
- package/themes/bootstrap/scss/_tooltip.scss +119 -0
- package/themes/bootstrap/scss/_transitions.scss +27 -0
- package/themes/bootstrap/scss/_type.scss +106 -0
- package/themes/bootstrap/scss/_utilities.scss +806 -0
- package/themes/bootstrap/scss/_variables-dark.scss +102 -0
- package/themes/bootstrap/scss/_variables.scss +1753 -0
- package/themes/bootstrap/scss/bootstrap-grid.scss +62 -0
- package/themes/bootstrap/scss/bootstrap-reboot.scss +10 -0
- package/themes/bootstrap/scss/bootstrap-utilities.scss +19 -0
- package/themes/bootstrap/scss/bootstrap.scss +52 -0
- package/themes/bootstrap/scss/forms/_floating-labels.scss +97 -0
- package/themes/bootstrap/scss/forms/_form-check.scss +189 -0
- package/themes/bootstrap/scss/forms/_form-control.scss +214 -0
- package/themes/bootstrap/scss/forms/_form-range.scss +91 -0
- package/themes/bootstrap/scss/forms/_form-select.scss +80 -0
- package/themes/bootstrap/scss/forms/_form-text.scss +11 -0
- package/themes/bootstrap/scss/forms/_input-group.scss +132 -0
- package/themes/bootstrap/scss/forms/_labels.scss +36 -0
- package/themes/bootstrap/scss/forms/_validation.scss +12 -0
- package/themes/bootstrap/scss/helpers/_clearfix.scss +3 -0
- package/themes/bootstrap/scss/helpers/_color-bg.scss +7 -0
- package/themes/bootstrap/scss/helpers/_colored-links.scss +30 -0
- package/themes/bootstrap/scss/helpers/_focus-ring.scss +5 -0
- package/themes/bootstrap/scss/helpers/_icon-link.scss +25 -0
- package/themes/bootstrap/scss/helpers/_position.scss +36 -0
- package/themes/bootstrap/scss/helpers/_ratio.scss +26 -0
- package/themes/bootstrap/scss/helpers/_stacks.scss +15 -0
- package/themes/bootstrap/scss/helpers/_stretched-link.scss +15 -0
- package/themes/bootstrap/scss/helpers/_text-truncation.scss +7 -0
- package/themes/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
- package/themes/bootstrap/scss/helpers/_vr.scss +8 -0
- package/themes/bootstrap/scss/mixins/_alert.scss +18 -0
- package/themes/bootstrap/scss/mixins/_backdrop.scss +14 -0
- package/themes/bootstrap/scss/mixins/_banner.scss +7 -0
- package/themes/bootstrap/scss/mixins/_border-radius.scss +78 -0
- package/themes/bootstrap/scss/mixins/_box-shadow.scss +18 -0
- package/themes/bootstrap/scss/mixins/_breakpoints.scss +127 -0
- package/themes/bootstrap/scss/mixins/_buttons.scss +70 -0
- package/themes/bootstrap/scss/mixins/_caret.scss +69 -0
- package/themes/bootstrap/scss/mixins/_clearfix.scss +9 -0
- package/themes/bootstrap/scss/mixins/_color-mode.scss +21 -0
- package/themes/bootstrap/scss/mixins/_color-scheme.scss +7 -0
- package/themes/bootstrap/scss/mixins/_container.scss +11 -0
- package/themes/bootstrap/scss/mixins/_deprecate.scss +10 -0
- package/themes/bootstrap/scss/mixins/_forms.scss +163 -0
- package/themes/bootstrap/scss/mixins/_gradients.scss +47 -0
- package/themes/bootstrap/scss/mixins/_grid.scss +151 -0
- package/themes/bootstrap/scss/mixins/_image.scss +16 -0
- package/themes/bootstrap/scss/mixins/_list-group.scss +26 -0
- package/themes/bootstrap/scss/mixins/_lists.scss +7 -0
- package/themes/bootstrap/scss/mixins/_pagination.scss +10 -0
- package/themes/bootstrap/scss/mixins/_reset-text.scss +17 -0
- package/themes/bootstrap/scss/mixins/_resize.scss +6 -0
- package/themes/bootstrap/scss/mixins/_table-variants.scss +24 -0
- package/themes/bootstrap/scss/mixins/_text-truncate.scss +8 -0
- package/themes/bootstrap/scss/mixins/_transition.scss +26 -0
- package/themes/bootstrap/scss/mixins/_utilities.scss +97 -0
- package/themes/bootstrap/scss/mixins/_visually-hidden.scss +38 -0
- package/themes/bootstrap/scss/tests/jasmine.js +16 -0
- package/themes/bootstrap/scss/tests/mixins/_auto-import-of-variables-dark.test.scss +7 -0
- package/themes/bootstrap/scss/tests/mixins/_color-modes.test.scss +69 -0
- package/themes/bootstrap/scss/tests/mixins/_media-query-color-mode-full.test.scss +8 -0
- package/themes/bootstrap/scss/tests/mixins/_utilities.test.scss +393 -0
- package/themes/bootstrap/scss/tests/sass-true/register.js +14 -0
- package/themes/bootstrap/scss/tests/sass-true/runner.js +17 -0
- package/themes/bootstrap/scss/tests/utilities/_api.test.scss +75 -0
- package/themes/bootstrap/scss/utilities/_api.scss +47 -0
- package/themes/bootstrap/scss/vendor/_rfs.scss +348 -0
- package/themes/classy/README.md +89 -0
- package/themes/classy/_config.scss +245 -0
- package/themes/classy/_theme.js +31 -0
- package/themes/classy/_theme.scss +57 -0
- package/themes/classy/css/app/_panels.scss +183 -0
- package/themes/classy/css/base/_fonts.scss +79 -0
- package/themes/classy/css/base/_no-raise.scss +49 -0
- package/themes/classy/css/base/_root.scss +113 -0
- package/themes/classy/css/base/_typography.scss +154 -0
- package/themes/classy/css/base/_utilities.scss +284 -0
- package/themes/classy/css/components/_alerts.scss +51 -0
- package/themes/classy/css/components/_badges.scss +46 -0
- package/themes/classy/css/components/_buttons.scss +175 -0
- package/themes/classy/css/components/_cards.scss +41 -0
- package/themes/classy/css/components/_dropdowns.scss +48 -0
- package/themes/classy/css/components/_forms.scss +171 -0
- package/themes/classy/css/components/_receipt.scss +45 -0
- package/themes/classy/css/layout/_footer.scss +298 -0
- package/themes/classy/css/layout/_general.scss +209 -0
- package/themes/classy/css/layout/_nav.scss +154 -0
- package/themes/classy/css/layout/_shell.scss +458 -0
- package/themes/classy/css/marketing/_bento.scss +238 -0
- package/themes/classy/css/marketing/_content.scss +1328 -0
- package/themes/classy/css/marketing/_downloads.scss +246 -0
- package/themes/classy/css/marketing/_hero.scss +311 -0
- package/themes/classy/css/marketing/_sections.scss +649 -0
- package/themes/classy/css/pages/404/index.scss +67 -0
- package/themes/classy/css/pages/_auth.scss +235 -0
- package/themes/classy/css/pages/admin/firebase/index.scss +22 -0
- package/themes/classy/css/pages/blog/[slug].scss +56 -0
- package/themes/classy/css/pages/cookies.scss +2 -0
- package/themes/classy/css/pages/dashboard/account/index.scss +31 -0
- package/themes/classy/css/pages/feedback/index.scss +39 -0
- package/themes/classy/css/pages/legal/_document.scss +192 -0
- package/themes/classy/css/pages/payment/checkout/index.scss +585 -0
- package/themes/classy/css/pages/payment/confirmation/index.scss +9 -0
- package/themes/classy/css/pages/privacy.scss +2 -0
- package/themes/classy/css/pages/status/index.scss +164 -0
- package/themes/classy/css/pages/terms.scss +2 -0
- package/themes/classy/css/pages/updates/index.scss +128 -0
- package/themes/classy/fonts/inter-italic-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/inter-italic-latin.woff2 +0 -0
- package/themes/classy/fonts/inter-normal-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/inter-normal-latin.woff2 +0 -0
- package/themes/classy/fonts/newsreader-italic-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/newsreader-italic-latin.woff2 +0 -0
- package/themes/classy/fonts/newsreader-normal-latin-ext.woff2 +0 -0
- package/themes/classy/fonts/newsreader-normal-latin.woff2 +0 -0
- package/themes/classy/js/hero-demo-form.js +56 -0
- package/themes/neobrutalism/README.md +118 -0
- package/themes/neobrutalism/_config.scss +139 -0
- package/themes/neobrutalism/_layouts/frontend/core/base.html +30 -0
- package/themes/neobrutalism/_layouts/frontend/pages/index.html +275 -0
- package/themes/neobrutalism/_theme.js +24 -0
- package/themes/neobrutalism/_theme.scss +61 -0
- package/themes/neobrutalism/css/base/_mixins.scss +46 -0
- package/themes/neobrutalism/css/base/_root.scss +169 -0
- package/themes/neobrutalism/css/base/_typography.scss +61 -0
- package/themes/neobrutalism/css/base/_utilities.scss +89 -0
- package/themes/neobrutalism/css/components/_billing-toggle.scss +74 -0
- package/themes/neobrutalism/css/components/_buttons.scss +148 -0
- package/themes/neobrutalism/css/components/_cards.scss +47 -0
- package/themes/neobrutalism/css/components/_forms.scss +88 -0
- package/themes/neobrutalism/css/components/_marketing.scss +244 -0
- package/themes/neobrutalism/css/layout/_general.scss +213 -0
- package/themes/neobrutalism/css/layout/_navigation.scss +171 -0
- package/themes/neobrutalism/css/pages/index.scss +203 -0
- package/themes/neobrutalism/css/pages/pricing/index.scss +58 -0
- package/themes/neobrutalism/css/pages/test/libraries/layers/index.scss +7 -0
- package/themes/neobrutalism/js/pages/test/libraries/layers/index.js +9 -0
- package/themes/newsflash/README.md +117 -0
- package/themes/newsflash/_components/heading/lede/component.html +14 -0
- package/themes/newsflash/_components/heading/lede/component.json5 +23 -0
- package/themes/newsflash/_components/heading/rule-head/component.html +17 -0
- package/themes/newsflash/_components/heading/rule-head/component.json5 +23 -0
- package/themes/newsflash/_components/news/byline/component.html +12 -0
- package/themes/newsflash/_components/news/byline/component.json5 +18 -0
- package/themes/newsflash/_components/news/story-card/component.html +25 -0
- package/themes/newsflash/_components/news/story-card/component.json5 +21 -0
- package/themes/newsflash/_config.scss +147 -0
- package/themes/newsflash/_layouts/frontend/core/base.html +51 -0
- package/themes/newsflash/_layouts/frontend/pages/blog/index.html +299 -0
- package/themes/newsflash/_layouts/frontend/pages/blog/post.html +235 -0
- package/themes/newsflash/_layouts/frontend/pages/index.html +328 -0
- package/themes/newsflash/_sections/marketing/desks/section.html +39 -0
- package/themes/newsflash/_sections/marketing/desks/section.json5 +49 -0
- package/themes/newsflash/_sections/marketing/newsletter-cta/section.html +103 -0
- package/themes/newsflash/_sections/marketing/newsletter-cta/section.json5 +50 -0
- package/themes/newsflash/_sections/marketing/rundown/section.html +28 -0
- package/themes/newsflash/_sections/marketing/rundown/section.json5 +40 -0
- package/themes/newsflash/_theme.js +24 -0
- package/themes/newsflash/_theme.scss +70 -0
- package/themes/newsflash/css/base/_fonts.scss +80 -0
- package/themes/newsflash/css/base/_mixins.scss +51 -0
- package/themes/newsflash/css/base/_root.scss +257 -0
- package/themes/newsflash/css/base/_typography.scss +49 -0
- package/themes/newsflash/css/base/_utilities.scss +122 -0
- package/themes/newsflash/css/components/_badges.scss +65 -0
- package/themes/newsflash/css/components/_billing-toggle.scss +61 -0
- package/themes/newsflash/css/components/_buttons.scss +139 -0
- package/themes/newsflash/css/components/_cards.scss +47 -0
- package/themes/newsflash/css/components/_editorial.scss +173 -0
- package/themes/newsflash/css/components/_forms.scss +75 -0
- package/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
- package/themes/newsflash/css/components/_panels.scss +156 -0
- package/themes/newsflash/css/components/_ticker.scss +67 -0
- package/themes/newsflash/css/layout/_general.scss +330 -0
- package/themes/newsflash/css/layout/_navigation.scss +168 -0
- package/themes/newsflash/css/pages/404/index.scss +79 -0
- package/themes/newsflash/css/pages/blog/[slug].scss +186 -0
- package/themes/newsflash/css/pages/blog/index.scss +17 -0
- package/themes/newsflash/css/pages/index.scss +157 -0
- package/themes/newsflash/css/pages/pricing/index.scss +55 -0
- package/themes/newsflash/css/pages/test/libraries/layers/index.scss +7 -0
- package/themes/newsflash/fonts/fraunces-italic-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-italic-latin.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-normal-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/fraunces-normal-latin.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-italic-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-italic-latin.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-normal-latin-ext.woff2 +0 -0
- package/themes/newsflash/fonts/schibsted-grotesk-normal-latin.woff2 +0 -0
- package/themes/newsflash/js/pages/blog/[slug].js +29 -0
- package/themes/newsflash/js/pages/test/libraries/layers/index.js +9 -0
- package/translations/es/pages/404.json +86 -0
- package/translations/es/pages/account.json +36 -0
- package/translations/es/pages/app.json +44 -0
- package/translations/es/pages/cancel.json +36 -0
- package/translations/es/pages/change-password.json +36 -0
- package/translations/es/pages/connections/callback.json +41 -0
- package/translations/es/pages/dashboard/account.json +475 -0
- package/translations/es/pages/dashboard.json +36 -0
- package/translations/es/pages/forgot.json +36 -0
- package/translations/es/pages/join.json +36 -0
- package/translations/es/pages/login.json +36 -0
- package/translations/es/pages/payment/checkout.json +83 -0
- package/translations/es/pages/payment/confirmation.json +62 -0
- package/translations/es/pages/portal/email-preferences.json +46 -0
- package/translations/es/pages/recover.json +36 -0
- package/translations/es/pages/refund.json +36 -0
- package/translations/es/pages/register.json +36 -0
- package/translations/es/pages/reset-password.json +36 -0
- package/translations/es/pages/reset.json +42 -0
- package/translations/es/pages/signin.json +45 -0
- package/translations/es/pages/signup.json +60 -0
- package/translations/es/pages/token.json +39 -0
- package/translations/fa/pages/404.json +86 -0
- package/translations/fa/pages/account.json +36 -0
- package/translations/fa/pages/app.json +44 -0
- package/translations/fa/pages/cancel.json +36 -0
- package/translations/fa/pages/change-password.json +36 -0
- package/translations/fa/pages/connections/callback.json +41 -0
- package/translations/fa/pages/dashboard/account.json +475 -0
- package/translations/fa/pages/dashboard.json +36 -0
- package/translations/fa/pages/forgot.json +36 -0
- package/translations/fa/pages/join.json +36 -0
- package/translations/fa/pages/login.json +36 -0
- package/translations/fa/pages/payment/checkout.json +83 -0
- package/translations/fa/pages/payment/confirmation.json +62 -0
- package/translations/fa/pages/portal/email-preferences.json +46 -0
- package/translations/fa/pages/recover.json +36 -0
- package/translations/fa/pages/refund.json +36 -0
- package/translations/fa/pages/register.json +36 -0
- package/translations/fa/pages/reset-password.json +36 -0
- package/translations/fa/pages/reset.json +42 -0
- package/translations/fa/pages/signin.json +45 -0
- package/translations/fa/pages/signup.json +60 -0
- 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 };
|