efficient-storefront-blocks 0.57.0 → 0.58.1
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/CHANGELOG.md +50 -0
- package/dist/chunks/{EditorPreviewCheckout-7Y7WL5BM.js → EditorPreviewCheckout-4NGRJA3T.js} +1 -1
- package/dist/chunks/{SiteEditorProvider-5MWJMLLI.js → SiteEditorProvider-V5FE5UD6.js} +1 -1
- package/dist/chunks/{chunk-BPVXTVPM.js → chunk-4AKN63X5.js} +1 -1
- package/dist/chunks/chunk-K4AZ5SUU.js +1 -0
- package/dist/chunks/chunk-PHH7L5FJ.js +255 -0
- package/dist/chunks/{chunk-FSSCNRRE.js → chunk-SJQTKWV3.js} +1 -1
- package/dist/chunks/{fitment-data-UVMEA7WW.js → fitment-data-OIWU6HGJ.js} +1 -1
- package/dist/editor/components/BlogSource.d.ts +11 -0
- package/dist/editor/components/BlogSource.js +1 -0
- package/dist/editor/components/CategorySource.d.ts +7 -0
- package/dist/editor/components/CategorySource.js +1 -0
- package/dist/editor/components/CollectionPicker.d.ts +7 -0
- package/dist/editor/components/CollectionPicker.js +1 -0
- package/dist/editor/components/ConsentBannerLayoutTiles.d.ts +3 -0
- package/dist/editor/components/ConsentBannerLayoutTiles.js +1 -0
- package/dist/editor/components/CountryShowAsTiles.d.ts +5 -0
- package/dist/editor/components/CountryShowAsTiles.js +1 -0
- package/dist/editor/components/EditProductLink.d.ts +1 -0
- package/dist/editor/components/EditProductLink.js +1 -0
- package/dist/editor/components/EditorCurrencyProvider.d.ts +7 -0
- package/dist/editor/components/EditorCurrencyProvider.js +1 -0
- package/dist/editor/components/EditorPreviewChat.d.ts +1 -0
- package/dist/editor/components/EditorPreviewChat.js +11 -0
- package/dist/editor/components/EditorPreviewCheckout.d.ts +1 -0
- package/dist/editor/components/EditorPreviewCheckout.js +1 -0
- package/dist/editor/components/EditorPreviewSurface.d.ts +5 -0
- package/dist/editor/components/EditorPreviewSurface.js +1 -0
- package/dist/editor/components/FinishPicker.d.ts +6 -0
- package/dist/editor/components/FinishPicker.js +1 -0
- package/dist/editor/components/FooterColumnsField.d.ts +6 -0
- package/dist/editor/components/FooterColumnsField.js +1 -0
- package/dist/editor/components/FooterLogoFillField.d.ts +6 -0
- package/dist/editor/components/FooterLogoFillField.js +1 -0
- package/dist/editor/components/FooterLogoRowFillField.d.ts +6 -0
- package/dist/editor/components/FooterLogoRowFillField.js +1 -0
- package/dist/editor/components/FormPicker.d.ts +33 -0
- package/dist/editor/components/FormPicker.js +1 -0
- package/dist/editor/components/GalleryProductBinding.d.ts +7 -0
- package/dist/editor/components/GalleryProductBinding.js +1 -0
- package/dist/editor/components/GalleryShowcaseSource.d.ts +7 -0
- package/dist/editor/components/GalleryShowcaseSource.js +1 -0
- package/dist/editor/components/HeaderSection.d.ts +3 -0
- package/dist/editor/components/HeaderSection.js +1 -0
- package/dist/editor/components/HeaderSettingsPanel.d.ts +132 -0
- package/dist/editor/components/HeaderSettingsPanel.js +1 -0
- package/dist/editor/components/MenuLinkRangeField.d.ts +5 -0
- package/dist/editor/components/MenuLinkRangeField.js +1 -0
- package/dist/editor/components/MenuPicker.d.ts +7 -0
- package/dist/editor/components/MenuPicker.js +1 -0
- package/dist/editor/components/PerViewStepperField.d.ts +13 -0
- package/dist/editor/components/PerViewStepperField.js +1 -0
- package/dist/editor/components/PreviewHeader.d.ts +2 -0
- package/dist/editor/components/PreviewHeader.js +11 -0
- package/dist/editor/components/PreviewHeaderPrimer.d.ts +4 -0
- package/dist/editor/components/PreviewHeaderPrimer.js +1 -0
- package/dist/editor/components/PreviewMenusPrimer.d.ts +3 -0
- package/dist/editor/components/PreviewMenusPrimer.js +1 -0
- package/dist/editor/components/PreviewProductPicker.d.ts +20 -0
- package/dist/editor/components/PreviewProductPicker.js +1 -0
- package/dist/editor/components/ProductHeroSource.d.ts +19 -0
- package/dist/editor/components/ProductHeroSource.js +1 -0
- package/dist/editor/components/ProductPicker.d.ts +6 -0
- package/dist/editor/components/ProductPicker.js +1 -0
- package/dist/editor/components/SocialLinksDoor.d.ts +1 -0
- package/dist/editor/components/SocialLinksDoor.js +1 -0
- package/dist/editor/components/SpecTableSource.d.ts +7 -0
- package/dist/editor/components/SpecTableSource.js +1 -0
- package/dist/editor/components/StorefrontEditorFurniture.d.ts +6 -0
- package/dist/editor/components/StorefrontEditorFurniture.js +1 -0
- package/dist/editor/components/StorefrontHeaderPanel.d.ts +2 -0
- package/dist/editor/components/StorefrontHeaderPanel.js +1 -0
- package/dist/editor/components/StorefrontLinkRows.d.ts +5 -0
- package/dist/editor/components/StorefrontLinkRows.js +1 -0
- package/dist/editor/components/StorefrontShopRows.d.ts +2 -0
- package/dist/editor/components/StorefrontShopRows.js +1 -0
- package/dist/editor/components/SwitchToBoxHeader.d.ts +1 -0
- package/dist/editor/components/SwitchToBoxHeader.js +1 -0
- package/dist/editor/components/WheelsPerRowField.d.ts +6 -0
- package/dist/editor/components/WheelsPerRowField.js +1 -0
- package/dist/editor/components/useCategoryFilter.d.ts +11 -0
- package/dist/editor/components/useCategoryFilter.js +1 -0
- package/dist/editor/components/useGridSourceLock.d.ts +1 -0
- package/dist/editor/components/useGridSourceLock.js +1 -0
- package/dist/editor/components/useStorefrontCategories.d.ts +2 -0
- package/dist/editor/components/useStorefrontCategories.js +1 -0
- package/dist/editor/components/useStorefrontForms.d.ts +2 -0
- package/dist/editor/components/useStorefrontForms.js +1 -0
- package/dist/editor/components/useWheelMediaGroups.d.ts +2 -0
- package/dist/editor/components/useWheelMediaGroups.js +1 -0
- package/dist/editor/lib/api/section-templates.d.ts +43 -0
- package/dist/editor/lib/api/section-templates.js +1 -0
- package/dist/editor/lib/editor-currency-source.d.ts +11 -0
- package/dist/editor/lib/editor-currency-source.js +1 -0
- package/dist/editor/lib/efficient-backend.d.ts +21 -0
- package/dist/editor/lib/efficient-backend.js +1 -0
- package/dist/editor/lib/header-settings-source.d.ts +7 -0
- package/dist/editor/lib/header-settings-source.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/AccountBlock/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/AccountBlock/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/AlloyWheelsListing/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/AlloyWheelsListing/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/BundleOffer/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/BundleOffer/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/CartBlock/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/CartBlock/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/CatalogFilterBar/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/CatalogFilterBar/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/CheckoutCTA/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/CheckoutCTA/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/CollectionCard/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/CollectionCard/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/Collections/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/Collections/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/CompatibleTyres/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/CompatibleTyres/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/CountrySwitchBlock/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/CountrySwitchBlock/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/DeliveryOptions/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/DeliveryOptions/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/DetailRows/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/DetailRows/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/EuTyreLabel/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/EuTyreLabel/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/FitmentFinder/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/FitmentFinder/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/FitmentTable/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/FitmentTable/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/Gallery/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/Gallery/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/GalleryB/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/GalleryB/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/GalleryItem/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/GalleryItem/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/Header/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/Header/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/ProductGrid/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/ProductGrid/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/ProductHero/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/ProductHero/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/ProductRef/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/ProductRef/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/RelatedProducts/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/RelatedProducts/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/SearchBlock/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/SearchBlock/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/SpecTable/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/SpecTable/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/ThemeSwitchBlock/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/ThemeSwitchBlock/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/TyreFitment/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/TyreFitment/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/TyreSizes/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/commerce/TyreSizes/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/controls.d.ts +15 -0
- package/dist/editor/lib/puck/blocks/commerce/controls.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/index.d.ts +1 -0
- package/dist/editor/lib/puck/blocks/commerce/index.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/page-rules.d.ts +8 -0
- package/dist/editor/lib/puck/blocks/commerce/page-rules.js +1 -0
- package/dist/editor/lib/puck/blocks/commerce/shared-fields.d.ts +5 -0
- package/dist/editor/lib/puck/blocks/commerce/shared-fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/AppointmentBooking/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/AppointmentBooking/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/BookingBox/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/BookingBox/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/ContactRouting/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/ContactRouting/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/Events/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/Events/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/Footer/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/Footer/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterDivider/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterDivider/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterEmailSignup/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterEmailSignup/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterGroup/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterGroup/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterLogo/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterLogo/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterMenu/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterMenu/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterSocial/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterSocial/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/FooterText/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/FooterText/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/Form/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/Form/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/LegalLineBlock/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/LegalLineBlock/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/ReviewForm/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/ReviewForm/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/Reviews/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/Reviews/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/SiteFooter/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/SiteFooter/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/SiteHeader/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/SiteHeader/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/SubmitBox/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/SubmitBox/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/SubmitBuild/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/SubmitBuild/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/Subscribe/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/Subscribe/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/SubscribeBlogs/fields.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/SubscribeBlogs/fields.js +1 -0
- package/dist/editor/lib/puck/blocks/service/controls.d.ts +2 -0
- package/dist/editor/lib/puck/blocks/service/controls.js +1 -0
- package/dist/editor/lib/puck/blocks/service/index.d.ts +1 -0
- package/dist/editor/lib/puck/blocks/service/index.js +1 -0
- package/dist/editor/lib/puck/blocks/service/placeholders.d.ts +1 -0
- package/dist/editor/lib/puck/blocks/service/placeholders.js +1 -0
- package/dist/editor/lib/puck/box-header-for-site.d.ts +9 -0
- package/dist/editor/lib/puck/box-header-for-site.js +1 -0
- package/dist/editor/lib/puck/box-header.d.ts +52 -0
- package/dist/editor/lib/puck/box-header.js +1 -0
- package/dist/editor/lib/puck/config.d.ts +73 -0
- package/dist/editor/lib/puck/config.js +1 -0
- package/dist/editor/lib/say-with.d.ts +2 -0
- package/dist/editor/lib/say-with.js +1 -0
- package/dist/editor/lib/storefront-document.d.ts +2 -0
- package/dist/editor/lib/storefront-document.js +1 -0
- package/dist/editor/lib/storefront-editor-words.d.ts +693 -0
- package/dist/editor/lib/storefront-editor-words.js +1 -0
- package/dist/editor/lib/storefront-editor.d.ts +9 -0
- package/dist/editor/lib/storefront-editor.js +1 -0
- package/dist/editor/lib/storefront-shop-surfaces.d.ts +8 -0
- package/dist/editor/lib/storefront-shop-surfaces.js +1 -0
- package/dist/editor/lib/use-prime-and-refresh.d.ts +16 -0
- package/dist/editor/lib/use-prime-and-refresh.js +1 -0
- package/dist/editor.css +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.js +69 -52
- package/dist/render/app/fonts/archivo/OFL.txt +93 -0
- package/dist/render/app/fonts/archivo/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/archivo/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/archivo/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/archivo/latin.woff2 +0 -0
- package/dist/render/app/fonts/archivo/vietnamese-italic.woff2 +0 -0
- package/dist/render/app/fonts/archivo/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/assistant/OFL.txt +95 -0
- package/dist/render/app/fonts/assistant/hebrew.woff2 +0 -0
- package/dist/render/app/fonts/assistant/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/assistant/latin.woff2 +0 -0
- package/dist/render/app/fonts/big-shoulders/OFL.txt +93 -0
- package/dist/render/app/fonts/big-shoulders/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/big-shoulders/latin.woff2 +0 -0
- package/dist/render/app/fonts/big-shoulders/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/OFL.txt +93 -0
- package/dist/render/app/fonts/cormorant/cyrillic-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/cyrillic-ext.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/cyrillic-italic.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/cyrillic.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/latin.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/vietnamese-italic.woff2 +0 -0
- package/dist/render/app/fonts/cormorant/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/dm-sans/OFL.txt +93 -0
- package/dist/render/app/fonts/dm-sans/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/dm-sans/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/dm-sans/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/dm-sans/latin.woff2 +0 -0
- package/dist/render/app/fonts/fallbacks.css +133 -0
- package/dist/render/app/fonts/fraunces/OFL.txt +93 -0
- package/dist/render/app/fonts/fraunces/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/fraunces/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/fraunces/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/fraunces/latin.woff2 +0 -0
- package/dist/render/app/fonts/fraunces/vietnamese-italic.woff2 +0 -0
- package/dist/render/app/fonts/fraunces/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/inter/OFL.txt +93 -0
- package/dist/render/app/fonts/inter/cyrillic-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/cyrillic-ext.woff2 +0 -0
- package/dist/render/app/fonts/inter/cyrillic-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/cyrillic.woff2 +0 -0
- package/dist/render/app/fonts/inter/greek-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/greek-ext.woff2 +0 -0
- package/dist/render/app/fonts/inter/greek-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/greek.woff2 +0 -0
- package/dist/render/app/fonts/inter/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/inter/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/latin.woff2 +0 -0
- package/dist/render/app/fonts/inter/vietnamese-italic.woff2 +0 -0
- package/dist/render/app/fonts/inter/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/OFL.txt +93 -0
- package/dist/render/app/fonts/jetbrains-mono/cyrillic-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/cyrillic-ext.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/cyrillic-italic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/cyrillic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/greek-italic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/greek.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/latin.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/vietnamese-italic.woff2 +0 -0
- package/dist/render/app/fonts/jetbrains-mono/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/manrope/OFL.txt +93 -0
- package/dist/render/app/fonts/manrope/cyrillic-ext.woff2 +0 -0
- package/dist/render/app/fonts/manrope/cyrillic.woff2 +0 -0
- package/dist/render/app/fonts/manrope/greek.woff2 +0 -0
- package/dist/render/app/fonts/manrope/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/manrope/latin.woff2 +0 -0
- package/dist/render/app/fonts/manrope/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/michroma/OFL.txt +93 -0
- package/dist/render/app/fonts/michroma/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/michroma/latin.woff2 +0 -0
- package/dist/render/app/fonts/oswald/OFL.txt +93 -0
- package/dist/render/app/fonts/oswald/cyrillic-ext.woff2 +0 -0
- package/dist/render/app/fonts/oswald/cyrillic.woff2 +0 -0
- package/dist/render/app/fonts/oswald/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/oswald/latin.woff2 +0 -0
- package/dist/render/app/fonts/oswald/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/outfit/OFL.txt +93 -0
- package/dist/render/app/fonts/outfit/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/outfit/latin.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/OFL.txt +93 -0
- package/dist/render/app/fonts/playfair-display/cyrillic-italic.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/cyrillic.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/latin-ext-italic.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/latin-italic.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/latin.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/vietnamese-italic.woff2 +0 -0
- package/dist/render/app/fonts/playfair-display/vietnamese.woff2 +0 -0
- package/dist/render/app/fonts/poppins/OFL.txt +93 -0
- package/dist/render/app/fonts/poppins/devanagari-400.woff2 +0 -0
- package/dist/render/app/fonts/poppins/devanagari-500.woff2 +0 -0
- package/dist/render/app/fonts/poppins/devanagari-600.woff2 +0 -0
- package/dist/render/app/fonts/poppins/devanagari-italic-400.woff2 +0 -0
- package/dist/render/app/fonts/poppins/devanagari-italic-500.woff2 +0 -0
- package/dist/render/app/fonts/poppins/devanagari-italic-600.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-400.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-500.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-600.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-ext-400.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-ext-500.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-ext-600.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-ext-italic-400.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-ext-italic-500.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-ext-italic-600.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-italic-400.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-italic-500.woff2 +0 -0
- package/dist/render/app/fonts/poppins/latin-italic-600.woff2 +0 -0
- package/dist/render/app/fonts/sora/OFL.txt +93 -0
- package/dist/render/app/fonts/sora/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/sora/latin.woff2 +0 -0
- package/dist/render/app/fonts/space-grotesk/OFL.txt +93 -0
- package/dist/render/app/fonts/space-grotesk/latin-ext.woff2 +0 -0
- package/dist/render/app/fonts/space-grotesk/latin.woff2 +0 -0
- package/dist/render/app/fonts/space-grotesk/vietnamese.woff2 +0 -0
- package/dist/render/app/globals.css +3264 -0
- package/dist/render/built-with.json +3 -0
- package/dist/render/components/AppointmentBooking.d.ts +14 -0
- package/dist/render/components/AppointmentBooking.js +1 -0
- package/dist/render/components/AssistantChatMount.d.ts +8 -0
- package/dist/render/components/AssistantChatMount.js +1 -0
- package/dist/render/components/AssistantChatPanel.d.ts +7 -0
- package/dist/render/components/AssistantChatPanel.js +1 -0
- package/dist/render/components/BoxHeaderBar.d.ts +10 -0
- package/dist/render/components/BoxHeaderBar.js +1 -0
- package/dist/render/components/BrandPlate.d.ts +4 -0
- package/dist/render/components/BrandPlate.js +1 -0
- package/dist/render/components/BundleCard.d.ts +6 -0
- package/dist/render/components/BundleCard.js +1 -0
- package/dist/render/components/CartContext.d.ts +91 -0
- package/dist/render/components/CartContext.js +1 -0
- package/dist/render/components/CartDrawer.d.ts +12 -0
- package/dist/render/components/CartDrawer.js +4 -0
- package/dist/render/components/CartLineThumbnail.d.ts +8 -0
- package/dist/render/components/CartLineThumbnail.js +1 -0
- package/dist/render/components/CatalogFilterBarSection.d.ts +12 -0
- package/dist/render/components/CatalogFilterBarSection.js +1 -0
- package/dist/render/components/ChatContext.d.ts +23 -0
- package/dist/render/components/ChatContext.js +1 -0
- package/dist/render/components/ChatLauncher.d.ts +18 -0
- package/dist/render/components/ChatLauncher.js +1 -0
- package/dist/render/components/ChatWidget.d.ts +12 -0
- package/dist/render/components/ChatWidget.js +1 -0
- package/dist/render/components/CheckoutCTA.d.ts +12 -0
- package/dist/render/components/CheckoutCTA.js +1 -0
- package/dist/render/components/CollectionCard.d.ts +27 -0
- package/dist/render/components/CollectionCard.js +1 -0
- package/dist/render/components/Collections.d.ts +17 -0
- package/dist/render/components/Collections.js +1 -0
- package/dist/render/components/CompatibleTyres.d.ts +23 -0
- package/dist/render/components/CompatibleTyres.js +1 -0
- package/dist/render/components/ConsentBanner.d.ts +20 -0
- package/dist/render/components/ConsentBanner.js +1 -0
- package/dist/render/components/ConsentContext.d.ts +22 -0
- package/dist/render/components/ConsentContext.js +1 -0
- package/dist/render/components/ConsentPanel.d.ts +15 -0
- package/dist/render/components/ConsentPanel.js +1 -0
- package/dist/render/components/ConsentProvider.d.ts +7 -0
- package/dist/render/components/ConsentProvider.js +1 -0
- package/dist/render/components/ContactRouting.d.ts +33 -0
- package/dist/render/components/ContactRouting.js +1 -0
- package/dist/render/components/CountrySelect.d.ts +7 -0
- package/dist/render/components/CountrySelect.js +1 -0
- package/dist/render/components/CouponField.d.ts +3 -0
- package/dist/render/components/CouponField.js +1 -0
- package/dist/render/components/CurrencyContext.d.ts +27 -0
- package/dist/render/components/CurrencyContext.js +1 -0
- package/dist/render/components/DeliveryOptions.d.ts +18 -0
- package/dist/render/components/DeliveryOptions.js +1 -0
- package/dist/render/components/DeliveryOptionsSource.d.ts +8 -0
- package/dist/render/components/DeliveryOptionsSource.js +1 -0
- package/dist/render/components/DetailRows.d.ts +22 -0
- package/dist/render/components/DetailRows.js +1 -0
- package/dist/render/components/DimensionArrowMarkers.d.ts +3 -0
- package/dist/render/components/DimensionArrowMarkers.js +1 -0
- package/dist/render/components/EditorCurrencyNoticeContext.d.ts +7 -0
- package/dist/render/components/EditorCurrencyNoticeContext.js +1 -0
- package/dist/render/components/EmailCaptureForm.d.ts +15 -0
- package/dist/render/components/EmailCaptureForm.js +1 -0
- package/dist/render/components/EnglishVariant.d.ts +7 -0
- package/dist/render/components/EnglishVariant.js +1 -0
- package/dist/render/components/EuTyreLabelCard.d.ts +4 -0
- package/dist/render/components/EuTyreLabelCard.js +1 -0
- package/dist/render/components/EuTyreLabels.d.ts +7 -0
- package/dist/render/components/EuTyreLabels.js +1 -0
- package/dist/render/components/Events.d.ts +62 -0
- package/dist/render/components/Events.js +1 -0
- package/dist/render/components/FinishSwatches.d.ts +14 -0
- package/dist/render/components/FinishSwatches.js +1 -0
- package/dist/render/components/FitmentTable.d.ts +17 -0
- package/dist/render/components/FitmentTable.js +1 -0
- package/dist/render/components/FooterDivider.d.ts +6 -0
- package/dist/render/components/FooterDivider.js +1 -0
- package/dist/render/components/FooterEmailSignup.d.ts +6 -0
- package/dist/render/components/FooterEmailSignup.js +1 -0
- package/dist/render/components/FooterGroup.d.ts +8 -0
- package/dist/render/components/FooterGroup.js +1 -0
- package/dist/render/components/FooterLogo.d.ts +13 -0
- package/dist/render/components/FooterLogo.js +1 -0
- package/dist/render/components/FooterMenu.d.ts +25 -0
- package/dist/render/components/FooterMenu.js +1 -0
- package/dist/render/components/FooterSection.d.ts +11 -0
- package/dist/render/components/FooterSection.js +1 -0
- package/dist/render/components/FooterSocial.d.ts +16 -0
- package/dist/render/components/FooterSocial.js +1 -0
- package/dist/render/components/FooterText.d.ts +7 -0
- package/dist/render/components/FooterText.js +1 -0
- package/dist/render/components/FormBlock.d.ts +19 -0
- package/dist/render/components/FormBlock.js +1 -0
- package/dist/render/components/GalleryCard.d.ts +28 -0
- package/dist/render/components/GalleryCard.js +1 -0
- package/dist/render/components/GalleryGrid.d.ts +20 -0
- package/dist/render/components/GalleryGrid.js +1 -0
- package/dist/render/components/GalleryItem.d.ts +41 -0
- package/dist/render/components/GalleryItem.js +1 -0
- package/dist/render/components/GalleryShowcase.d.ts +51 -0
- package/dist/render/components/GalleryShowcase.js +1 -0
- package/dist/render/components/Header.d.ts +13 -0
- package/dist/render/components/Header.js +1 -0
- package/dist/render/components/HeaderNavItem.d.ts +17 -0
- package/dist/render/components/HeaderNavItem.js +1 -0
- package/dist/render/components/HeaderToolBlocks.d.ts +18 -0
- package/dist/render/components/HeaderToolBlocks.js +1 -0
- package/dist/render/components/HeaderTools.d.ts +21 -0
- package/dist/render/components/HeaderTools.js +1 -0
- package/dist/render/components/LegalLine.d.ts +4 -0
- package/dist/render/components/LegalLine.js +1 -0
- package/dist/render/components/Lightbox.d.ts +19 -0
- package/dist/render/components/Lightbox.js +1 -0
- package/dist/render/components/LinkedProductHero.d.ts +34 -0
- package/dist/render/components/LinkedProductHero.js +1 -0
- package/dist/render/components/LiveSiteMapsProvider.d.ts +6 -0
- package/dist/render/components/LiveSiteMapsProvider.js +1 -0
- package/dist/render/components/MenuPlaceholderLabel.d.ts +1 -0
- package/dist/render/components/MenuPlaceholderLabel.js +1 -0
- package/dist/render/components/NoCurrencyNote.d.ts +4 -0
- package/dist/render/components/NoCurrencyNote.js +1 -0
- package/dist/render/components/PreOrderPanel.d.ts +5 -0
- package/dist/render/components/PreOrderPanel.js +1 -0
- package/dist/render/components/PreviewMenusContext.d.ts +13 -0
- package/dist/render/components/PreviewMenusContext.js +1 -0
- package/dist/render/components/ProductCard.d.ts +6 -0
- package/dist/render/components/ProductCard.js +1 -0
- package/dist/render/components/ProductCardChrome.d.ts +37 -0
- package/dist/render/components/ProductCardChrome.js +1 -0
- package/dist/render/components/ProductCardEyebrow.d.ts +14 -0
- package/dist/render/components/ProductCardEyebrow.js +1 -0
- package/dist/render/components/ProductGridSection.d.ts +65 -0
- package/dist/render/components/ProductGridSection.js +1 -0
- package/dist/render/components/ProductHeroPanel.d.ts +22 -0
- package/dist/render/components/ProductHeroPanel.js +1 -0
- package/dist/render/components/ProductLightbox.d.ts +5 -0
- package/dist/render/components/ProductLightbox.js +1 -0
- package/dist/render/components/ProductRef.d.ts +16 -0
- package/dist/render/components/ProductRef.js +1 -0
- package/dist/render/components/PublishedFooter.d.ts +16 -0
- package/dist/render/components/PublishedFooter.js +1 -0
- package/dist/render/components/PublishedHeader.d.ts +4 -0
- package/dist/render/components/PublishedHeader.js +1 -0
- package/dist/render/components/QuantityStepper.d.ts +7 -0
- package/dist/render/components/QuantityStepper.js +1 -0
- package/dist/render/components/QuoteLink.d.ts +4 -0
- package/dist/render/components/QuoteLink.js +1 -0
- package/dist/render/components/ReviewForm.d.ts +8 -0
- package/dist/render/components/ReviewForm.js +1 -0
- package/dist/render/components/Reviews.d.ts +12 -0
- package/dist/render/components/Reviews.js +1 -0
- package/dist/render/components/SearchContext.d.ts +13 -0
- package/dist/render/components/SearchContext.js +1 -0
- package/dist/render/components/SectionBand.d.ts +9 -0
- package/dist/render/components/SectionBand.js +1 -0
- package/dist/render/components/SectionFootnote.d.ts +5 -0
- package/dist/render/components/SectionFootnote.js +1 -0
- package/dist/render/components/SectionGroupBody.d.ts +9 -0
- package/dist/render/components/SectionGroupBody.js +1 -0
- package/dist/render/components/SectionNotice.d.ts +7 -0
- package/dist/render/components/SectionNotice.js +1 -0
- package/dist/render/components/SiteRecordContext.d.ts +17 -0
- package/dist/render/components/SiteRecordContext.js +1 -0
- package/dist/render/components/SiteSwitch.d.ts +10 -0
- package/dist/render/components/SiteSwitch.js +1 -0
- package/dist/render/components/SpecTable.d.ts +18 -0
- package/dist/render/components/SpecTable.js +1 -0
- package/dist/render/components/SpecTablePartBridge.d.ts +5 -0
- package/dist/render/components/SpecTablePartBridge.js +1 -0
- package/dist/render/components/StorefrontProviders.d.ts +15 -0
- package/dist/render/components/StorefrontProviders.js +1 -0
- package/dist/render/components/SubmitBuild.d.ts +23 -0
- package/dist/render/components/SubmitBuild.js +1 -0
- package/dist/render/components/Subscribe.d.ts +27 -0
- package/dist/render/components/Subscribe.js +1 -0
- package/dist/render/components/SubscribePopup.d.ts +19 -0
- package/dist/render/components/SubscribePopup.js +7 -0
- package/dist/render/components/SubscribePopupContext.d.ts +33 -0
- package/dist/render/components/SubscribePopupContext.js +1 -0
- package/dist/render/components/ThemeToggle.d.ts +1 -0
- package/dist/render/components/ThemeToggle.js +1 -0
- package/dist/render/components/ThemedSelect.d.ts +59 -0
- package/dist/render/components/ThemedSelect.js +1 -0
- package/dist/render/components/TyreCard.d.ts +19 -0
- package/dist/render/components/TyreCard.js +1 -0
- package/dist/render/components/TyreFitVerdicts.d.ts +7 -0
- package/dist/render/components/TyreFitVerdicts.js +1 -0
- package/dist/render/components/TyreImage.d.ts +10 -0
- package/dist/render/components/TyreImage.js +1 -0
- package/dist/render/components/TyrePlpCard.d.ts +19 -0
- package/dist/render/components/TyrePlpCard.js +1 -0
- package/dist/render/components/TyreRenderSvg.d.ts +7 -0
- package/dist/render/components/TyreRenderSvg.js +1 -0
- package/dist/render/components/TyreSummaryCard.d.ts +11 -0
- package/dist/render/components/TyreSummaryCard.js +1 -0
- package/dist/render/components/TyreVehicleFitment.d.ts +18 -0
- package/dist/render/components/TyreVehicleFitment.js +1 -0
- package/dist/render/components/ViewMoreLink.d.ts +4 -0
- package/dist/render/components/ViewMoreLink.js +1 -0
- package/dist/render/components/WebFormField.d.ts +17 -0
- package/dist/render/components/WebFormField.js +1 -0
- package/dist/render/components/WheelCardFrame.d.ts +67 -0
- package/dist/render/components/WheelCardFrame.js +1 -0
- package/dist/render/components/WheelImage.d.ts +20 -0
- package/dist/render/components/WheelImage.js +1 -0
- package/dist/render/components/WheelPlaceholder.d.ts +7 -0
- package/dist/render/components/WheelPlaceholder.js +1 -0
- package/dist/render/components/WheelSvg.d.ts +7 -0
- package/dist/render/components/WheelSvg.js +1 -0
- package/dist/render/components/WishlistContext.d.ts +13 -0
- package/dist/render/components/WishlistContext.js +1 -0
- package/dist/render/components/booking/BookingBox.d.ts +16 -0
- package/dist/render/components/booking/BookingBox.js +1 -0
- package/dist/render/components/booking/BookingFormState.d.ts +46 -0
- package/dist/render/components/booking/BookingFormState.js +1 -0
- package/dist/render/components/booking/BookingRegions.d.ts +16 -0
- package/dist/render/components/booking/BookingRegions.js +1 -0
- package/dist/render/components/buy-row.d.ts +1 -0
- package/dist/render/components/buy-row.js +1 -0
- package/dist/render/components/catalog/CatalogFilterBar.d.ts +55 -0
- package/dist/render/components/catalog/CatalogFilterBar.js +1 -0
- package/dist/render/components/catalog/CatalogFilterContext.d.ts +39 -0
- package/dist/render/components/catalog/CatalogFilterContext.js +1 -0
- package/dist/render/components/catalog/CatalogFilterLabels.d.ts +6 -0
- package/dist/render/components/catalog/CatalogFilterLabels.js +1 -0
- package/dist/render/components/catalog/CatalogVehiclePicker.d.ts +22 -0
- package/dist/render/components/catalog/CatalogVehiclePicker.js +1 -0
- package/dist/render/components/catalog/FilterPill.d.ts +25 -0
- package/dist/render/components/catalog/FilterPill.js +1 -0
- package/dist/render/components/catalog/SiteMenuLinks.d.ts +6 -0
- package/dist/render/components/catalog/SiteMenuLinks.js +1 -0
- package/dist/render/components/checkout/CheckoutPageClient.d.ts +25 -0
- package/dist/render/components/checkout/CheckoutPageClient.js +3 -0
- package/dist/render/components/fitment/FitmentFinder.d.ts +15 -0
- package/dist/render/components/fitment/FitmentFinder.js +1 -0
- package/dist/render/components/fitment/FitmentPickerGrid.d.ts +6 -0
- package/dist/render/components/fitment/FitmentPickerGrid.js +1 -0
- package/dist/render/components/fitment/FitmentRowFit.d.ts +4 -0
- package/dist/render/components/fitment/FitmentRowFit.js +1 -0
- package/dist/render/components/fitment/PickerField.d.ts +16 -0
- package/dist/render/components/fitment/PickerField.js +1 -0
- package/dist/render/components/fitment/StockFitmentCard.d.ts +8 -0
- package/dist/render/components/fitment/StockFitmentCard.js +1 -0
- package/dist/render/components/fitment/useFitmentCascade.d.ts +31 -0
- package/dist/render/components/fitment/useFitmentCascade.js +1 -0
- package/dist/render/components/fitment/useVehicleFit.d.ts +29 -0
- package/dist/render/components/fitment/useVehicleFit.js +1 -0
- package/dist/render/components/fitment/vehicle-control.d.ts +10 -0
- package/dist/render/components/fitment/vehicle-control.js +0 -0
- package/dist/render/components/fitment/vehicle-level-state.d.ts +14 -0
- package/dist/render/components/fitment/vehicle-level-state.js +1 -0
- package/dist/render/components/formSubmission.d.ts +32 -0
- package/dist/render/components/formSubmission.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicBundleOffer.d.ts +13 -0
- package/dist/render/components/pdp-dynamic/DynamicBundleOffer.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicDetailRows.d.ts +14 -0
- package/dist/render/components/pdp-dynamic/DynamicDetailRows.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicEuTyreLabel.d.ts +8 -0
- package/dist/render/components/pdp-dynamic/DynamicEuTyreLabel.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicFitmentTable.d.ts +16 -0
- package/dist/render/components/pdp-dynamic/DynamicFitmentTable.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicGalleryGrid.d.ts +18 -0
- package/dist/render/components/pdp-dynamic/DynamicGalleryGrid.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicProductHero.d.ts +25 -0
- package/dist/render/components/pdp-dynamic/DynamicProductHero.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicRelatedProducts.d.ts +31 -0
- package/dist/render/components/pdp-dynamic/DynamicRelatedProducts.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicSpecTable.d.ts +13 -0
- package/dist/render/components/pdp-dynamic/DynamicSpecTable.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicTyreFitment.d.ts +13 -0
- package/dist/render/components/pdp-dynamic/DynamicTyreFitment.js +1 -0
- package/dist/render/components/pdp-dynamic/DynamicTyreSizes.d.ts +11 -0
- package/dist/render/components/pdp-dynamic/DynamicTyreSizes.js +1 -0
- package/dist/render/components/pdp-dynamic/FitmentTableBlock.d.ts +7 -0
- package/dist/render/components/pdp-dynamic/FitmentTableBlock.js +1 -0
- package/dist/render/components/pdp-dynamic/KindGate.d.ts +8 -0
- package/dist/render/components/pdp-dynamic/KindGate.js +1 -0
- package/dist/render/components/pdp-dynamic/useSampleProduct.d.ts +5 -0
- package/dist/render/components/pdp-dynamic/useSampleProduct.js +1 -0
- package/dist/render/components/product-hero/TyreHeroFitment.d.ts +17 -0
- package/dist/render/components/product-hero/TyreHeroFitment.js +1 -0
- package/dist/render/components/product-hero/VehicleQuestion.d.ts +18 -0
- package/dist/render/components/product-hero/VehicleQuestion.js +1 -0
- package/dist/render/components/product-hero/WheelHeroFitment.d.ts +9 -0
- package/dist/render/components/product-hero/WheelHeroFitment.js +1 -0
- package/dist/render/components/product-hero/hero-content.d.ts +7 -0
- package/dist/render/components/product-hero/hero-content.js +1 -0
- package/dist/render/components/product-hero/hero-view.d.ts +37 -0
- package/dist/render/components/product-hero/hero-view.js +1 -0
- package/dist/render/components/product-hero/stage-photo.d.ts +10 -0
- package/dist/render/components/product-hero/stage-photo.js +3 -0
- package/dist/render/components/submit/SubmitBox.d.ts +19 -0
- package/dist/render/components/submit/SubmitBox.js +1 -0
- package/dist/render/components/submit/SubmitRegions.d.ts +17 -0
- package/dist/render/components/submit/SubmitRegions.js +1 -0
- package/dist/render/components/useEuTyreLabels.d.ts +3 -0
- package/dist/render/components/useEuTyreLabels.js +1 -0
- package/dist/render/components/useFinishSelection.d.ts +17 -0
- package/dist/render/components/useFinishSelection.js +1 -0
- package/dist/render/components/useHeaderBar.d.ts +9 -0
- package/dist/render/components/useHeaderBar.js +1 -0
- package/dist/render/components/useLightbox.d.ts +10 -0
- package/dist/render/components/useLightbox.js +1 -0
- package/dist/render/components/useModalDismiss.d.ts +8 -0
- package/dist/render/components/useModalDismiss.js +1 -0
- package/dist/render/components/useNewsletterSignup.d.ts +12 -0
- package/dist/render/components/useNewsletterSignup.js +1 -0
- package/dist/render/components/usePreOrderIntent.d.ts +14 -0
- package/dist/render/components/usePreOrderIntent.js +1 -0
- package/dist/render/components/useProductCatalog.d.ts +54 -0
- package/dist/render/components/useProductCatalog.js +1 -0
- package/dist/render/components/useProductSelection.d.ts +39 -0
- package/dist/render/components/useProductSelection.js +1 -0
- package/dist/render/components/useStockAlertSignup.d.ts +14 -0
- package/dist/render/components/useStockAlertSignup.js +1 -0
- package/dist/render/components/useStorefrontReviews.d.ts +2 -0
- package/dist/render/components/useStorefrontReviews.js +1 -0
- package/dist/render/components/useTyreCatalog.d.ts +2 -0
- package/dist/render/components/useTyreCatalog.js +1 -0
- package/dist/render/components/useTyreDetail.d.ts +6 -0
- package/dist/render/components/useTyreDetail.js +1 -0
- package/dist/render/components/useTyreFitAnswer.d.ts +5 -0
- package/dist/render/components/useTyreFitAnswer.js +1 -0
- package/dist/render/components/useWheelDetail.d.ts +6 -0
- package/dist/render/components/useWheelDetail.js +1 -0
- package/dist/render/components/wheels-plp/AlloyWheelsListing.d.ts +42 -0
- package/dist/render/components/wheels-plp/AlloyWheelsListing.js +1 -0
- package/dist/render/components/wheels-plp/PlpActiveChips.d.ts +9 -0
- package/dist/render/components/wheels-plp/PlpActiveChips.js +1 -0
- package/dist/render/components/wheels-plp/PlpCard.d.ts +27 -0
- package/dist/render/components/wheels-plp/PlpCard.js +1 -0
- package/dist/render/components/wheels-plp/PlpInStockToggle.d.ts +6 -0
- package/dist/render/components/wheels-plp/PlpInStockToggle.js +1 -0
- package/dist/render/components/wheels-plp/PlpNotifyMeDialog.d.ts +10 -0
- package/dist/render/components/wheels-plp/PlpNotifyMeDialog.js +1 -0
- package/dist/render/components/wheels-plp/PlpPagination.d.ts +5 -0
- package/dist/render/components/wheels-plp/PlpPagination.js +1 -0
- package/dist/render/components/wheels-plp/PlpSidebar.d.ts +25 -0
- package/dist/render/components/wheels-plp/PlpSidebar.js +1 -0
- package/dist/render/components/wheels-plp/PlpTypePicker.d.ts +7 -0
- package/dist/render/components/wheels-plp/PlpTypePicker.js +1 -0
- package/dist/render/components/wheels-plp/PlpTyreSizeBoxes.d.ts +9 -0
- package/dist/render/components/wheels-plp/PlpTyreSizeBoxes.js +1 -0
- package/dist/render/components/wheels-plp/PlpVehicleBar.d.ts +15 -0
- package/dist/render/components/wheels-plp/PlpVehicleBar.js +1 -0
- package/dist/render/components/wheels-plp/PlpVehicleGate.d.ts +10 -0
- package/dist/render/components/wheels-plp/PlpVehicleGate.js +1 -0
- package/dist/render/components/wheels-plp/usePlpCardSelection.d.ts +18 -0
- package/dist/render/components/wheels-plp/usePlpCardSelection.js +1 -0
- package/dist/render/components/wheels-plp/usePlpFiltersInUrl.d.ts +10 -0
- package/dist/render/components/wheels-plp/usePlpFiltersInUrl.js +1 -0
- package/dist/render/components/wheels-plp/useVehicleCatalog.d.ts +10 -0
- package/dist/render/components/wheels-plp/useVehicleCatalog.js +1 -0
- package/dist/render/components/wheels-plp/useWheelCardBottom.d.ts +20 -0
- package/dist/render/components/wheels-plp/useWheelCardBottom.js +1 -0
- package/dist/render/lib/api/chat.d.ts +31 -0
- package/dist/render/lib/api/chat.js +1 -0
- package/dist/render/lib/api/fitment-data.d.ts +18 -0
- package/dist/render/lib/api/fitment-data.js +1 -0
- package/dist/render/lib/api/gallery-builds-data.d.ts +4 -0
- package/dist/render/lib/api/gallery-builds-data.js +1 -0
- package/dist/render/lib/api/generated-upload-limits.d.ts +1292 -0
- package/dist/render/lib/api/generated-upload-limits.js +1 -0
- package/dist/render/lib/api/index.d.ts +806 -0
- package/dist/render/lib/api/index.js +1 -0
- package/dist/render/lib/api/origins.d.ts +2 -0
- package/dist/render/lib/api/origins.js +1 -0
- package/dist/render/lib/api/types.d.ts +3928 -0
- package/dist/render/lib/api/types.js +0 -0
- package/dist/render/lib/api/upload-limits.d.ts +10 -0
- package/dist/render/lib/api/upload-limits.js +1 -0
- package/dist/render/lib/assistant-chat.d.ts +35 -0
- package/dist/render/lib/assistant-chat.js +1 -0
- package/dist/render/lib/auth.d.ts +6 -0
- package/dist/render/lib/auth.js +1 -0
- package/dist/render/lib/booking/text-kinds.d.ts +11 -0
- package/dist/render/lib/booking/text-kinds.js +1 -0
- package/dist/render/lib/booking-dates.d.ts +23 -0
- package/dist/render/lib/booking-dates.js +1 -0
- package/dist/render/lib/cache-evict.d.ts +4 -0
- package/dist/render/lib/cache-evict.js +1 -0
- package/dist/render/lib/cart/coupon-preview.d.ts +12 -0
- package/dist/render/lib/cart/coupon-preview.js +1 -0
- package/dist/render/lib/cart/shipping-status.d.ts +12 -0
- package/dist/render/lib/cart/shipping-status.js +1 -0
- package/dist/render/lib/cart/snapshot-lines.d.ts +3 -0
- package/dist/render/lib/cart/snapshot-lines.js +1 -0
- package/dist/render/lib/catalog/from-tyre.d.ts +3 -0
- package/dist/render/lib/catalog/from-tyre.js +1 -0
- package/dist/render/lib/catalog/from-wheel.d.ts +7 -0
- package/dist/render/lib/catalog/from-wheel.js +1 -0
- package/dist/render/lib/catalog/listing-links.d.ts +11 -0
- package/dist/render/lib/catalog/listing-links.js +1 -0
- package/dist/render/lib/catalog/plp-item.d.ts +39 -0
- package/dist/render/lib/catalog/plp-item.js +1 -0
- package/dist/render/lib/catalog/plp-vehicle-copy.d.ts +20 -0
- package/dist/render/lib/catalog/plp-vehicle-copy.js +1 -0
- package/dist/render/lib/catalog/plp-vehicle-slice.d.ts +13 -0
- package/dist/render/lib/catalog/plp-vehicle-slice.js +1 -0
- package/dist/render/lib/catalog/product-options.d.ts +27 -0
- package/dist/render/lib/catalog/product-options.js +1 -0
- package/dist/render/lib/catalog/purchase.d.ts +46 -0
- package/dist/render/lib/catalog/purchase.js +1 -0
- package/dist/render/lib/catalog/related-cards.d.ts +12 -0
- package/dist/render/lib/catalog/related-cards.js +1 -0
- package/dist/render/lib/catalog/relationships.d.ts +28 -0
- package/dist/render/lib/catalog/relationships.js +1 -0
- package/dist/render/lib/catalog/store-product.d.ts +56 -0
- package/dist/render/lib/catalog/store-product.js +1 -0
- package/dist/render/lib/chat-socket.d.ts +8 -0
- package/dist/render/lib/chat-socket.js +1 -0
- package/dist/render/lib/checkout/wallet-return.d.ts +52 -0
- package/dist/render/lib/checkout/wallet-return.js +1 -0
- package/dist/render/lib/chrome-cache.d.ts +6 -0
- package/dist/render/lib/chrome-cache.js +1 -0
- package/dist/render/lib/chrome-load.d.ts +28 -0
- package/dist/render/lib/chrome-load.js +1 -0
- package/dist/render/lib/click-ids.d.ts +8 -0
- package/dist/render/lib/click-ids.js +1 -0
- package/dist/render/lib/commerce.d.ts +42 -0
- package/dist/render/lib/commerce.js +1 -0
- package/dist/render/lib/consent/choice.d.ts +35 -0
- package/dist/render/lib/consent/choice.js +1 -0
- package/dist/render/lib/consent/rules.d.ts +60 -0
- package/dist/render/lib/consent/rules.js +1 -0
- package/dist/render/lib/content/catalog-filter-labels.d.ts +6 -0
- package/dist/render/lib/content/catalog-filter-labels.js +1 -0
- package/dist/render/lib/content/country-show-as.d.ts +9 -0
- package/dist/render/lib/content/country-show-as.js +1 -0
- package/dist/render/lib/content/english-variant.d.ts +4 -0
- package/dist/render/lib/content/english-variant.js +1 -0
- package/dist/render/lib/content/field-usage.d.ts +35 -0
- package/dist/render/lib/content/field-usage.js +1 -0
- package/dist/render/lib/content/header-bar.d.ts +8 -0
- package/dist/render/lib/content/header-bar.js +1 -0
- package/dist/render/lib/content/header-theme.d.ts +7 -0
- package/dist/render/lib/content/header-theme.js +1 -0
- package/dist/render/lib/content/menu-placeholder.d.ts +8 -0
- package/dist/render/lib/content/menu-placeholder.js +1 -0
- package/dist/render/lib/content/menu-row.d.ts +9 -0
- package/dist/render/lib/content/menu-row.js +1 -0
- package/dist/render/lib/content/product-hero.d.ts +13 -0
- package/dist/render/lib/content/product-hero.js +1 -0
- package/dist/render/lib/content/site.d.ts +62 -0
- package/dist/render/lib/content/site.js +1 -0
- package/dist/render/lib/content/source-values.d.ts +43 -0
- package/dist/render/lib/content/source-values.js +0 -0
- package/dist/render/lib/content/stock-words.d.ts +10 -0
- package/dist/render/lib/content/stock-words.js +1 -0
- package/dist/render/lib/countries.d.ts +5 -0
- package/dist/render/lib/countries.js +1 -0
- package/dist/render/lib/editor-route.d.ts +3 -0
- package/dist/render/lib/editor-route.js +1 -0
- package/dist/render/lib/email-shape.d.ts +1 -0
- package/dist/render/lib/email-shape.js +1 -0
- package/dist/render/lib/font-table.json +2588 -0
- package/dist/render/lib/forwarded-for.d.ts +6 -0
- package/dist/render/lib/forwarded-for.js +1 -0
- package/dist/render/lib/google-tag.d.ts +37 -0
- package/dist/render/lib/google-tag.js +1 -0
- package/dist/render/lib/header-settings-fetch.d.ts +5 -0
- package/dist/render/lib/header-settings-fetch.js +1 -0
- package/dist/render/lib/links/link-rules.d.ts +80 -0
- package/dist/render/lib/links/link-rules.js +1 -0
- package/dist/render/lib/logo/surface-is-light.d.ts +1 -0
- package/dist/render/lib/logo/surface-is-light.js +1 -0
- package/dist/render/lib/menu-link.d.ts +1 -0
- package/dist/render/lib/menu-link.js +1 -0
- package/dist/render/lib/menu-source.d.ts +23 -0
- package/dist/render/lib/menu-source.js +1 -0
- package/dist/render/lib/meta-pixel.d.ts +19 -0
- package/dist/render/lib/meta-pixel.js +1 -0
- package/dist/render/lib/newsletter-signup.d.ts +16 -0
- package/dist/render/lib/newsletter-signup.js +1 -0
- package/dist/render/lib/next-fonts.d.ts +3 -0
- package/dist/render/lib/next-fonts.js +1 -0
- package/dist/render/lib/popup/popup-rules.d.ts +25 -0
- package/dist/render/lib/popup/popup-rules.js +1 -0
- package/dist/render/lib/popup/text-kind-style.d.ts +3 -0
- package/dist/render/lib/popup/text-kind-style.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/AccountBlock/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/AccountBlock/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/AlloyWheelsListing/render.d.ts +6 -0
- package/dist/render/lib/puck/blocks/commerce/AlloyWheelsListing/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/BundleOffer/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/BundleOffer/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/CartBlock/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/CartBlock/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/CatalogFilterBar/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/CatalogFilterBar/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/CheckoutCTA/render.d.ts +6 -0
- package/dist/render/lib/puck/blocks/commerce/CheckoutCTA/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/CollectionCard/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/CollectionCard/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/Collections/render.d.ts +7 -0
- package/dist/render/lib/puck/blocks/commerce/Collections/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/CompatibleTyres/render.d.ts +9 -0
- package/dist/render/lib/puck/blocks/commerce/CompatibleTyres/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/CountrySwitchBlock/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/CountrySwitchBlock/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/DeliveryOptions/render.d.ts +6 -0
- package/dist/render/lib/puck/blocks/commerce/DeliveryOptions/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/DetailRows/render.d.ts +9 -0
- package/dist/render/lib/puck/blocks/commerce/DetailRows/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/EuTyreLabel/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/EuTyreLabel/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/FitmentFinder/render.d.ts +8 -0
- package/dist/render/lib/puck/blocks/commerce/FitmentFinder/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/FitmentTable/render.d.ts +9 -0
- package/dist/render/lib/puck/blocks/commerce/FitmentTable/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/Gallery/render.d.ts +14 -0
- package/dist/render/lib/puck/blocks/commerce/Gallery/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/GalleryB/render.d.ts +13 -0
- package/dist/render/lib/puck/blocks/commerce/GalleryB/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/GalleryItem/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/GalleryItem/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/Header/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/Header/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/ProductGrid/render.d.ts +7 -0
- package/dist/render/lib/puck/blocks/commerce/ProductGrid/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/ProductHero/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/ProductHero/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/ProductRef/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/ProductRef/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/RelatedProducts/render.d.ts +6 -0
- package/dist/render/lib/puck/blocks/commerce/RelatedProducts/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/SearchBlock/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/SearchBlock/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/SpecTable/render.d.ts +7 -0
- package/dist/render/lib/puck/blocks/commerce/SpecTable/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/ThemeSwitchBlock/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/ThemeSwitchBlock/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/TyreFitment/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/TyreFitment/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/TyreSizes/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/commerce/TyreSizes/render.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/placeholders.d.ts +4 -0
- package/dist/render/lib/puck/blocks/commerce/placeholders.js +1 -0
- package/dist/render/lib/puck/blocks/commerce/renders.d.ts +32 -0
- package/dist/render/lib/puck/blocks/commerce/renders.js +1 -0
- package/dist/render/lib/puck/blocks/service/AppointmentBooking/render.d.ts +8 -0
- package/dist/render/lib/puck/blocks/service/AppointmentBooking/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/BookingBox/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/BookingBox/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/ContactRouting/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/ContactRouting/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/Events/render.d.ts +7 -0
- package/dist/render/lib/puck/blocks/service/Events/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/Footer/render.d.ts +8 -0
- package/dist/render/lib/puck/blocks/service/Footer/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterDivider/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/FooterDivider/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterEmailSignup/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/FooterEmailSignup/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterGroup/render.d.ts +8 -0
- package/dist/render/lib/puck/blocks/service/FooterGroup/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterLogo/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/FooterLogo/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterMenu/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/FooterMenu/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterSocial/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/FooterSocial/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/FooterText/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/FooterText/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/Form/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/Form/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/LegalLineBlock/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/LegalLineBlock/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/ReviewForm/render.d.ts +6 -0
- package/dist/render/lib/puck/blocks/service/ReviewForm/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/Reviews/render.d.ts +6 -0
- package/dist/render/lib/puck/blocks/service/Reviews/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/SiteFooter/render.d.ts +7 -0
- package/dist/render/lib/puck/blocks/service/SiteFooter/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/SiteHeader/render.d.ts +7 -0
- package/dist/render/lib/puck/blocks/service/SiteHeader/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/SubmitBox/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/SubmitBox/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/SubmitBuild/render.d.ts +8 -0
- package/dist/render/lib/puck/blocks/service/SubmitBuild/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/Subscribe/render.d.ts +2 -0
- package/dist/render/lib/puck/blocks/service/Subscribe/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/SubscribeBlogs/render.d.ts +17 -0
- package/dist/render/lib/puck/blocks/service/SubscribeBlogs/render.js +1 -0
- package/dist/render/lib/puck/blocks/service/renders.d.ts +26 -0
- package/dist/render/lib/puck/blocks/service/renders.js +1 -0
- package/dist/render/lib/puck/blocks/service/subscribe.d.ts +33 -0
- package/dist/render/lib/puck/blocks/service/subscribe.js +1 -0
- package/dist/render/lib/puck/blocks/text-part-shapes.json +1649 -0
- package/dist/render/lib/puck/current-product.d.ts +19 -0
- package/dist/render/lib/puck/current-product.js +1 -0
- package/dist/render/lib/puck/current-record.d.ts +44 -0
- package/dist/render/lib/puck/current-record.js +1 -0
- package/dist/render/lib/puck/fitment-table-dynamic.d.ts +4 -0
- package/dist/render/lib/puck/fitment-table-dynamic.js +1 -0
- package/dist/render/lib/puck/footer-page-blocks.d.ts +7 -0
- package/dist/render/lib/puck/footer-page-blocks.js +1 -0
- package/dist/render/lib/puck/preview-pick.d.ts +7 -0
- package/dist/render/lib/puck/preview-pick.js +1 -0
- package/dist/render/lib/puck/preview-product.d.ts +6 -0
- package/dist/render/lib/puck/preview-product.js +1 -0
- package/dist/render/lib/puck/render-client.d.ts +5 -0
- package/dist/render/lib/puck/render-client.js +1 -0
- package/dist/render/lib/puck/render-config.d.ts +4 -0
- package/dist/render/lib/puck/render-config.js +1 -0
- package/dist/render/lib/puck/slot-seeds.d.ts +3 -0
- package/dist/render/lib/puck/slot-seeds.js +1 -0
- package/dist/render/lib/puck/storefront-root.d.ts +18 -0
- package/dist/render/lib/puck/storefront-root.js +1 -0
- package/dist/render/lib/puck/subscribe-content-type.d.ts +10 -0
- package/dist/render/lib/puck/subscribe-content-type.js +1 -0
- package/dist/render/lib/puck/template-product-kind.d.ts +7 -0
- package/dist/render/lib/puck/template-product-kind.js +1 -0
- package/dist/render/lib/sections/card-image-sizes.d.ts +12 -0
- package/dist/render/lib/sections/card-image-sizes.js +1 -0
- package/dist/render/lib/sections/card-show-defaults.d.ts +10 -0
- package/dist/render/lib/sections/card-show-defaults.js +1 -0
- package/dist/render/lib/sections/catalog-filter-layout.d.ts +7 -0
- package/dist/render/lib/sections/catalog-filter-layout.js +1 -0
- package/dist/render/lib/sections/fill-row.d.ts +16 -0
- package/dist/render/lib/sections/fill-row.js +1 -0
- package/dist/render/lib/sections/hero-arrangement.d.ts +11 -0
- package/dist/render/lib/sections/hero-arrangement.js +1 -0
- package/dist/render/lib/sections/hero-gallery.d.ts +14 -0
- package/dist/render/lib/sections/hero-gallery.js +1 -0
- package/dist/render/lib/sections/hero-price-placement.d.ts +7 -0
- package/dist/render/lib/sections/hero-price-placement.js +1 -0
- package/dist/render/lib/select-keys.d.ts +18 -0
- package/dist/render/lib/select-keys.js +1 -0
- package/dist/render/lib/site-headers.d.ts +3 -0
- package/dist/render/lib/site-headers.js +1 -0
- package/dist/render/lib/storefront-page-groups.d.ts +16 -0
- package/dist/render/lib/storefront-page-groups.js +1 -0
- package/dist/render/lib/storefront-page-order.d.ts +3 -0
- package/dist/render/lib/storefront-page-order.js +1 -0
- package/dist/render/lib/submit/text-kinds.d.ts +11 -0
- package/dist/render/lib/submit/text-kinds.js +1 -0
- package/dist/render/lib/tyres/eu-tyre-label.d.ts +29 -0
- package/dist/render/lib/tyres/eu-tyre-label.js +1 -0
- package/dist/render/lib/tyres/placeholder-tyre.d.ts +2 -0
- package/dist/render/lib/tyres/placeholder-tyre.js +1 -0
- package/dist/render/lib/tyres/spec-sheet.d.ts +12 -0
- package/dist/render/lib/tyres/spec-sheet.js +1 -0
- package/dist/render/lib/tyres/tyre-brand-name.d.ts +2 -0
- package/dist/render/lib/tyres/tyre-brand-name.js +1 -0
- package/dist/render/lib/tyres/tyre-fit-copy.d.ts +3 -0
- package/dist/render/lib/tyres/tyre-fit-copy.js +1 -0
- package/dist/render/lib/tyres/tyre-page-path.d.ts +2 -0
- package/dist/render/lib/tyres/tyre-page-path.js +1 -0
- package/dist/render/lib/tyres/tyre-purchase.d.ts +12 -0
- package/dist/render/lib/tyres/tyre-purchase.js +1 -0
- package/dist/render/lib/tyres/tyre-size-fit.d.ts +12 -0
- package/dist/render/lib/tyres/tyre-size-fit.js +1 -0
- package/dist/render/lib/tyres/tyre-size.d.ts +11 -0
- package/dist/render/lib/tyres/tyre-size.js +1 -0
- package/dist/render/lib/tyres/tyre-vehicle-fitment.d.ts +17 -0
- package/dist/render/lib/tyres/tyre-vehicle-fitment.js +1 -0
- package/dist/render/lib/uuid.d.ts +1 -0
- package/dist/render/lib/uuid.js +1 -0
- package/dist/render/lib/wheels/applied-vehicle.d.ts +1 -0
- package/dist/render/lib/wheels/applied-vehicle.js +1 -0
- package/dist/render/lib/wheels/catalog-default-layout.d.ts +14 -0
- package/dist/render/lib/wheels/catalog-default-layout.js +1 -0
- package/dist/render/lib/wheels/catalog-filters-to-listing.d.ts +9 -0
- package/dist/render/lib/wheels/catalog-filters-to-listing.js +1 -0
- package/dist/render/lib/wheels/catalog-filters.d.ts +19 -0
- package/dist/render/lib/wheels/catalog-filters.js +1 -0
- package/dist/render/lib/wheels/collections.d.ts +22 -0
- package/dist/render/lib/wheels/collections.js +1 -0
- package/dist/render/lib/wheels/cover-rendering.d.ts +23 -0
- package/dist/render/lib/wheels/cover-rendering.js +1 -0
- package/dist/render/lib/wheels/detail-shots.d.ts +10 -0
- package/dist/render/lib/wheels/detail-shots.js +1 -0
- package/dist/render/lib/wheels/finish-colour-match.d.ts +4 -0
- package/dist/render/lib/wheels/finish-colour-match.js +1 -0
- package/dist/render/lib/wheels/finish-defaults.d.ts +16 -0
- package/dist/render/lib/wheels/finish-defaults.js +1 -0
- package/dist/render/lib/wheels/finish-photos.d.ts +2 -0
- package/dist/render/lib/wheels/finish-photos.js +1 -0
- package/dist/render/lib/wheels/fitment-columns.d.ts +9 -0
- package/dist/render/lib/wheels/fitment-columns.js +1 -0
- package/dist/render/lib/wheels/fitment-options.d.ts +18 -0
- package/dist/render/lib/wheels/fitment-options.js +1 -0
- package/dist/render/lib/wheels/fitment-row-fit.d.ts +17 -0
- package/dist/render/lib/wheels/fitment-row-fit.js +1 -0
- package/dist/render/lib/wheels/fitments.d.ts +13 -0
- package/dist/render/lib/wheels/fitments.js +1 -0
- package/dist/render/lib/wheels/grid-source.d.ts +12 -0
- package/dist/render/lib/wheels/grid-source.js +1 -0
- package/dist/render/lib/wheels/listable-wheels.d.ts +3 -0
- package/dist/render/lib/wheels/listable-wheels.js +1 -0
- package/dist/render/lib/wheels/lug-count.d.ts +10 -0
- package/dist/render/lib/wheels/lug-count.js +1 -0
- package/dist/render/lib/wheels/pdp-fit-verdict.d.ts +27 -0
- package/dist/render/lib/wheels/pdp-fit-verdict.js +1 -0
- package/dist/render/lib/wheels/picker-goes-to-the-shop.d.ts +10 -0
- package/dist/render/lib/wheels/picker-goes-to-the-shop.js +1 -0
- package/dist/render/lib/wheels/placeholder-wheel.d.ts +3 -0
- package/dist/render/lib/wheels/placeholder-wheel.js +1 -0
- package/dist/render/lib/wheels/plp-filters.d.ts +100 -0
- package/dist/render/lib/wheels/plp-filters.js +1 -0
- package/dist/render/lib/wheels/plp-fitment-match.d.ts +61 -0
- package/dist/render/lib/wheels/plp-fitment-match.js +1 -0
- package/dist/render/lib/wheels/plp-grid.d.ts +18 -0
- package/dist/render/lib/wheels/plp-grid.js +1 -0
- package/dist/render/lib/wheels/plp-hydrate.d.ts +21 -0
- package/dist/render/lib/wheels/plp-hydrate.js +1 -0
- package/dist/render/lib/wheels/plp-vehicle-params.d.ts +10 -0
- package/dist/render/lib/wheels/plp-vehicle-params.js +1 -0
- package/dist/render/lib/wheels/pre-order-intent.d.ts +3 -0
- package/dist/render/lib/wheels/pre-order-intent.js +1 -0
- package/dist/render/lib/wheels/pre-order.d.ts +7 -0
- package/dist/render/lib/wheels/pre-order.js +1 -0
- package/dist/render/lib/wheels/price-display.d.ts +47 -0
- package/dist/render/lib/wheels/price-display.js +1 -0
- package/dist/render/lib/wheels/render-fit.d.ts +3 -0
- package/dist/render/lib/wheels/render-fit.js +1 -0
- package/dist/render/lib/wheels/renderable-finishes.d.ts +6 -0
- package/dist/render/lib/wheels/renderable-finishes.js +1 -0
- package/dist/render/lib/wheels/selected-studs.d.ts +9 -0
- package/dist/render/lib/wheels/selected-studs.js +1 -0
- package/dist/render/lib/wheels/set-mode.d.ts +34 -0
- package/dist/render/lib/wheels/set-mode.js +1 -0
- package/dist/render/lib/wheels/size-range.d.ts +10 -0
- package/dist/render/lib/wheels/size-range.js +1 -0
- package/dist/render/lib/wheels/spec-format.d.ts +7 -0
- package/dist/render/lib/wheels/spec-format.js +1 -0
- package/dist/render/lib/wheels/spec-sheet.d.ts +58 -0
- package/dist/render/lib/wheels/spec-sheet.js +1 -0
- package/dist/render/lib/wheels/tyre-render.d.ts +34 -0
- package/dist/render/lib/wheels/tyre-render.js +1 -0
- package/dist/render/lib/wheels/variant-spec.d.ts +9 -0
- package/dist/render/lib/wheels/variant-spec.js +1 -0
- package/dist/render/lib/wheels/variants.d.ts +8 -0
- package/dist/render/lib/wheels/variants.js +1 -0
- package/dist/render/lib/wheels/vehicle-field-focus.d.ts +7 -0
- package/dist/render/lib/wheels/vehicle-field-focus.js +1 -0
- package/dist/render/lib/wheels/vehicle-memory.d.ts +25 -0
- package/dist/render/lib/wheels/vehicle-memory.js +1 -0
- package/dist/render/lib/wheels/wheel-name.d.ts +4 -0
- package/dist/render/lib/wheels/wheel-name.js +1 -0
- package/dist/render/lib/wheels/wheel-specs.d.ts +14 -0
- package/dist/render/lib/wheels/wheel-specs.js +1 -0
- package/dist/render/lib/wheels/wheel-summary-casing.d.ts +4 -0
- package/dist/render/lib/wheels/wheel-summary-casing.js +1 -0
- package/dist/render/lib/wheels/wheels.d.ts +38 -0
- package/dist/render/lib/wheels/wheels.js +1 -0
- package/dist/render/mocks/fitment-snapshot.json +3911 -0
- package/dist/render/mocks/gallery-builds.json +213 -0
- package/dist/words.d.ts +68 -7
- package/dist/words.js +5 -5
- package/package.json +37 -5
- package/dist/chunks/chunk-BDITPYS5.js +0 -1
- package/dist/chunks/chunk-EHKAKDFN.js +0 -255
- /package/dist/chunks/{chunk-4NM2MDGN.js → chunk-76HTJBKU.js} +0 -0
- /package/dist/chunks/{chunk-EHKAKDFN.js.LEGAL.txt → chunk-PHH7L5FJ.js.LEGAL.txt} +0 -0
|
@@ -0,0 +1,3264 @@
|
|
|
1
|
+
/* THE SITE'S STYLESHEET — the design tokens, the theme-derived surfaces, every
|
|
2
|
+
section's and card's own rules, the word styles, the motion. It is the blocks'
|
|
3
|
+
look, so it lives with the blocks (efficient-storefront doc/adr/0009).
|
|
4
|
+
|
|
5
|
+
It carries no `@import "tailwindcss"` and no `@source`: those are the HOST's,
|
|
6
|
+
because only the host knows which files its pages are built from. The
|
|
7
|
+
storefront's entry is its `app/tailwind.css` (`@import "tailwindcss"`, then
|
|
8
|
+
this file, then the storefront's `@source` exclusions); the package's canvas
|
|
9
|
+
is built from this repository's own. Everything below is the storefront's
|
|
10
|
+
`app/globals.css` from line 79 on, as it stood on 2026-10-10. */
|
|
11
|
+
|
|
12
|
+
/* ─── Storefront design tokens ───────────────────────────────────── */
|
|
13
|
+
/* ⚠️ THE SURFACE, LINE AND RADIUS TOKENS ARE `--site-*`, AND THAT IS A NAME
|
|
14
|
+
COLLISION FIXED, NOT A STYLE. efficient-shell's UI kit declares `--surface`,
|
|
15
|
+
`--line` and `--radius-sm…2xl` as ITS tokens, and this sheet publishes the
|
|
16
|
+
site's on <body> (the surface mapping below, the corner tokens in
|
|
17
|
+
app/layout.tsx). Where both are on one page — the storefront's editor harnesses — the kit's
|
|
18
|
+
dialogs, popovers and colour picker are portalled to <body> and inherited the
|
|
19
|
+
SITE'S values: light-on-light in dark mode, square menus on a site whose
|
|
20
|
+
Corners is 0. Harness UI-24 says editor chrome resolves to the kit's tokens,
|
|
21
|
+
so the site's own carry a prefix the kit never reads. Tailwind's class names
|
|
22
|
+
are unchanged: `bg-surface`, `border-line` and `rounded-*` reach the prefixed
|
|
23
|
+
properties through the theme block (`inline`) with the named colours below.
|
|
24
|
+
|
|
25
|
+
The `-base` inputs and `--bg` / `--fg` / `--accent` keep their names: the kit
|
|
26
|
+
declares none of them. A STORED fill that names the old spelling —
|
|
27
|
+
`var(--surface)` in a published section's background — is translated where it
|
|
28
|
+
is painted (`paintedFill` in lib/content/section-background), not here. */
|
|
29
|
+
/* Dark theme (default) */
|
|
30
|
+
:root {
|
|
31
|
+
/* Surface BASES — the light/dark page surfaces. Split out from the live token
|
|
32
|
+
for the same reason --fg-base is (below): the colour Theme's per-site
|
|
33
|
+
surfaces (--theme-bg / --theme-surface / --theme-line / --theme-muted) remap
|
|
34
|
+
these on `.sf-theme-surfaces`, in EVERY mode since 66238, and an un-themed
|
|
35
|
+
site falls back to the base. A base is a plain literal; the live token is
|
|
36
|
+
always `var(<base>)`.
|
|
37
|
+
|
|
38
|
+
⏳ THIS USED TO SAY "in DARK MODE ONLY … so a near-black themed background
|
|
39
|
+
can never end up under the light theme's near-black ink". That protection
|
|
40
|
+
went when a theme became one flat palette and the surfaces started applying
|
|
41
|
+
in both modes. The sentence stayed, and the gap it described re-opened in
|
|
42
|
+
the CHROME, which was still taking its ink from the mode. Fixed by mapping
|
|
43
|
+
--fg beside --bg on `.sf-theme-surfaces`; see the note there. */
|
|
44
|
+
--bg-base: #0a0a0a;
|
|
45
|
+
--bg: var(--bg-base);
|
|
46
|
+
--surface-base: #131313;
|
|
47
|
+
--site-surface: var(--surface-base);
|
|
48
|
+
--surface-2-base: #1c1c1c;
|
|
49
|
+
--site-surface-2: var(--surface-2-base);
|
|
50
|
+
/* Foreground BASE — the light/dark page ink. Split out from --fg so the colour
|
|
51
|
+
Theme's per-site ink (--theme-fg) can remap --fg on `.sf-theme-surfaces` and
|
|
52
|
+
`.sf-themed-content`, in EVERY mode; a site that has chosen no theme falls
|
|
53
|
+
back to this base.
|
|
54
|
+
|
|
55
|
+
⏳ THIS USED TO SAY "in DARK mode only … so a dark-mode cream text can't land
|
|
56
|
+
on the cream light-mode page". True when a preset carried an ink and no
|
|
57
|
+
background of its own. A flat palette (66238) carries both, so the ink can
|
|
58
|
+
only ever land on that palette's own background — the case this guarded
|
|
59
|
+
against cannot arise, and the guard itself became the bug in the chrome. */
|
|
60
|
+
--fg-base: #f5f1e8;
|
|
61
|
+
--fg: var(--fg-base);
|
|
62
|
+
--fg-dim-base: #9a9388;
|
|
63
|
+
--fg-dim: var(--fg-dim-base);
|
|
64
|
+
--line-base: #2a2a2a;
|
|
65
|
+
--site-line: var(--line-base);
|
|
66
|
+
/* Theme-colour BASES. Split out from the live token for exactly the reason
|
|
67
|
+
--fg-base and the surface bases are: the merchant's theme colour is now TWO
|
|
68
|
+
colours, one per mode, and .sf-theme-accent below maps whichever the mode
|
|
69
|
+
calls for onto --accent. A base is a plain literal per mode; the live token is
|
|
70
|
+
always `var(<base>)`, so a site with no theme of its own resolves to
|
|
71
|
+
precisely the values it uses today. */
|
|
72
|
+
--accent-base: #c9a461;
|
|
73
|
+
--accent: var(--accent-base);
|
|
74
|
+
--accent-2-base: #b08a44;
|
|
75
|
+
--accent-2: var(--accent-2-base);
|
|
76
|
+
|
|
77
|
+
/* ⛔ THE FOUR MODE-PINNED accent bases (--accent-base-on-dark / -on-light and
|
|
78
|
+
their -2 pair, card 66401.g) WERE DELETED HERE on 2026-09-04. They existed
|
|
79
|
+
for one reader — the `.sf-section-surface-*` blocks, which pinned a mode and
|
|
80
|
+
so could not read the page-following --accent-base. Nothing pins a mode any
|
|
81
|
+
more (see the note above `.sf-section-accent` below), so nothing read them.
|
|
82
|
+
Adding them back without a mode-pinned block to read them is dead weight. */
|
|
83
|
+
/* The ink a filled control puts ON the accent. A BASE like the others, and a
|
|
84
|
+
literal here because :root has no theme to derive from: it is accentInk()'s
|
|
85
|
+
answer for the house gold above (black, 8.97:1). A themed site overrides it
|
|
86
|
+
from the carrier; see .sf-theme-accent. */
|
|
87
|
+
--accent-ink-base: #000000;
|
|
88
|
+
--accent-ink: var(--accent-ink-base);
|
|
89
|
+
/* THE BUTTON'S FILL, and by default it IS the accent (card 66504). A theme
|
|
90
|
+
may carry its own button colour; one that does not — every theme that
|
|
91
|
+
exists today — resolves straight back to the accent triple beside this, so
|
|
92
|
+
the button is byte-identical to what it painted before this token existed.
|
|
93
|
+
|
|
94
|
+
⚠️ DECLARED IN EVERY SCOPE THAT REBINDS --accent, not only here. A custom
|
|
95
|
+
property is resolved where it is DECLARED, so a single `--btn: var(--accent)`
|
|
96
|
+
on the carrier would freeze the site accent and a section that pinned its own
|
|
97
|
+
accent would stop moving its buttons. Same rule, same reason, as --accent-ink
|
|
98
|
+
next door; scripts/test-button-ink.ts holds it for that one. */
|
|
99
|
+
--btn: var(--theme-btn, var(--accent));
|
|
100
|
+
--btn-2: var(--theme-btn-2, var(--accent-2));
|
|
101
|
+
--btn-ink: var(--theme-btn-ink, var(--accent-ink));
|
|
102
|
+
/* Discount / "savings" + "fits perfect" success accent. Tuned to
|
|
103
|
+
read on the dark page bg without becoming a neon highlight. */
|
|
104
|
+
--green: #7aa672;
|
|
105
|
+
/* Foreground text that sits over dark media — video posters, photo
|
|
106
|
+
overlays — and must stay cream in BOTH themes (unlike --fg, which
|
|
107
|
+
flips to near-black in light mode). Defined once here, deliberately
|
|
108
|
+
not overridden in the [data-theme="light"] block below. */
|
|
109
|
+
--overlay-fg: #f5f1e8;
|
|
110
|
+
/* Its mirror: foreground text that sits over a LIGHT surface and must stay
|
|
111
|
+
near-black in BOTH themes. Same contract as --overlay-fg above — declared
|
|
112
|
+
once here and deliberately NOT overridden in the [data-theme="light"] block
|
|
113
|
+
below. Its one consumer is the site header's "Text colour at the top =
|
|
114
|
+
Dark" override, which is a merchant saying "the thing under my nav is
|
|
115
|
+
light" about a page in either theme, so it cannot be var(--fg). The value
|
|
116
|
+
is the same near-black the deleted light surface class used to paint. */
|
|
117
|
+
--overlay-fg-on-light: #0a0a0a;
|
|
118
|
+
/* Alert / destructive accent — a muted terracotta. Used for inline
|
|
119
|
+
error messages and the cart "remove" hover. Theme-independent. */
|
|
120
|
+
--alert: #c97a6a;
|
|
121
|
+
/* Deep warm tint used as the far stop of the decorative card-panel
|
|
122
|
+
gradients (gallery card hover panel, PDP process panel). */
|
|
123
|
+
--panel-tint: #181410;
|
|
124
|
+
/* Pure black / white reserved for chrome composited over media —
|
|
125
|
+
video letterbox scrims and the controls + borders layered on top.
|
|
126
|
+
Theme-independent: media renders dark regardless of page theme. */
|
|
127
|
+
--overlay-scrim: #000;
|
|
128
|
+
--overlay-chrome: #fff;
|
|
129
|
+
/* Tells the browser to paint OS chrome (select dropdown panels,
|
|
130
|
+
scrollbars, native checkboxes) in dark mode so they match the
|
|
131
|
+
site palette instead of flashing white. */
|
|
132
|
+
color-scheme: dark;
|
|
133
|
+
/* ─── Card-surface tokens ─────────────────────────────────────
|
|
134
|
+
Used by the home page ProductCard, the wheels CatalogCard, and
|
|
135
|
+
the journal ArticleCard / Cover article. Dark mode lifts each
|
|
136
|
+
card to a medium-dark grey (~#4d4b48 — a clear step lighter than
|
|
137
|
+
the page bg #0a0a0a so cards don't melt into the page). The
|
|
138
|
+
hover variant nudges another 10pp toward the foreground for a
|
|
139
|
+
visible "lifted" state. Light mode keeps the originals so the
|
|
140
|
+
cream-on-cream card system stays untouched.
|
|
141
|
+
The journal "Recent issues" panel uses --journal-panel-bg, which
|
|
142
|
+
dark mode collapses to --bg (no visible grey rectangle in the
|
|
143
|
+
bottom-right) while light mode keeps the original --site-surface fill.
|
|
144
|
+
The wheels catalog grid container uses --catalog-grid-bg, which
|
|
145
|
+
dark mode collapses to --bg (so empty grid cells in partial last
|
|
146
|
+
rows match the page) while light mode keeps the original --site-line
|
|
147
|
+
fill (which paints the hairline-divider effect through the 2px
|
|
148
|
+
gap between cards).
|
|
149
|
+
Card mix ratios pushed down again per request — another 20%
|
|
150
|
+
darker step. Now 92/8 rest, 82/18 hover (was 85/15 / 75/25),
|
|
151
|
+
resolving to roughly ~#2d2d2c rest / ~#3c3a37 hover. Each step
|
|
152
|
+
shaves more cream/foreground out of the mix and pulls the result
|
|
153
|
+
closer to the underlying --site-surface-2 #1c1c1c base, while still
|
|
154
|
+
keeping enough lift to read as a clear card surface against the
|
|
155
|
+
#0a0a0a page background.
|
|
156
|
+
The five recipes themselves live in the "theme-derived" block
|
|
157
|
+
below rather than here — they read --site-surface-2 / --bg / --fg,
|
|
158
|
+
and a custom property substitutes its var()s where it is
|
|
159
|
+
DECLARED, so declaring them at :root alone would freeze the
|
|
160
|
+
built-in surfaces and ignore the merchant's palette. */
|
|
161
|
+
/* ─── Component-scoped tokens ──────────────────────────────────
|
|
162
|
+
Used by single components but defined here so the underlying
|
|
163
|
+
colour stays a one-place change. Theme-independent unless the
|
|
164
|
+
light block overrides. */
|
|
165
|
+
/* Muted accent tint used by the PDP fitment-diagram outline. */
|
|
166
|
+
--accent-dim: #9a8050;
|
|
167
|
+
/* PhotoPlate's plate band (card 029) — the spec strip docked over the
|
|
168
|
+
plate photograph's bottom edge (or below it on phones). PHOTOGRAPHY
|
|
169
|
+
FURNITURE: like --overlay-fg it stays dark on BOTH page themes, so it
|
|
170
|
+
is deliberately NOT overridden in the light block below — a light-theme
|
|
171
|
+
tenant (XM) still gets the dark band the design draws. A tenant theme
|
|
172
|
+
may override all three. Values are the XM draft's: band #131313, ink
|
|
173
|
+
#C9C9C5, dim ink #6A6A64 (the mobile artboard's second line). */
|
|
174
|
+
--plate-band-bg: #131313;
|
|
175
|
+
--plate-band-fg: #c9c9c5;
|
|
176
|
+
--plate-band-fg-dim: #6a6a64;
|
|
177
|
+
/* Always-dark tooltip surface — overlaid on light + dark pages. */
|
|
178
|
+
--tooltip-bg: #3a3a3a;
|
|
179
|
+
/* The stage a wheel render sits on inside the product cards — the shared
|
|
180
|
+
components/WheelCardFrame (Product Grid + Alloy Wheels Listing) and the
|
|
181
|
+
/wheels CatalogCard. This is the DARK value; light mode overrides it in
|
|
182
|
+
the theme-derived light block below.
|
|
183
|
+
🔁 REVERSAL (66035, 2026-08-09) — do NOT "fix" this back. Between
|
|
184
|
+
1.25.1 and now this token was deliberately always-black, so the render
|
|
185
|
+
sat on one consistent backdrop in both themes. The owner has reversed
|
|
186
|
+
that: in light mode the stage now FOLLOWS THE LIGHT THEME (cream) and
|
|
187
|
+
only dark mode keeps the black. Dark mode is unchanged. */
|
|
188
|
+
--wheel-stage: #0a0a0a;
|
|
189
|
+
/* Radial gradient backing the Subscribe popup's left panel. The popup
|
|
190
|
+
now follows the page theme, so the DARK value lives here and the cream
|
|
191
|
+
value is overridden in the [data-theme="light"] block below. Warm
|
|
192
|
+
near-black tones echo the cream panel's warmth; the gold rings +
|
|
193
|
+
crosshatch layered on top read stronger against this dark ground. */
|
|
194
|
+
--subscribe-panel-bg: radial-gradient(80% 100% at 50% 40%, #221d15 0%, #151210 65%, #0a0908 100%);
|
|
195
|
+
/* ─── Font roles ───────────────────────────────────────────────
|
|
196
|
+
The swappable faces behind the font utilities below. Each defaults
|
|
197
|
+
to the site's built-in next/font face, so an un-themed page is
|
|
198
|
+
unchanged. The editor's "Fonts" control (Website settings)
|
|
199
|
+
overrides these tokens on the SITE-WIDE accent carrier — see
|
|
200
|
+
fontRoleStyle() in lib/fonts.ts and root.render in lib/puck/config
|
|
201
|
+
— so picking a font re-sets every section AND the header/footer
|
|
202
|
+
chrome around them without touching the loaded @font-face vars.
|
|
203
|
+
Published on <main> alone until card 8824, which is why a themed
|
|
204
|
+
site used to render in three typefaces. Theme-independent (no
|
|
205
|
+
light override). */
|
|
206
|
+
--font-role-heading: var(--font-display);
|
|
207
|
+
/* Heading 2 and 3 are styles of their own (Site styles lists six peers), and
|
|
208
|
+
their default IS Heading 1's face: unset means "the same as Heading 1", so a
|
|
209
|
+
site that has never opened those rows keeps one typeface across all three.
|
|
210
|
+
`fontRoleStyle` overrides these on the carrier when a face is chosen. */
|
|
211
|
+
--font-role-heading2: var(--font-role-heading);
|
|
212
|
+
--font-role-heading3: var(--font-role-heading);
|
|
213
|
+
--font-role-body: var(--font-body);
|
|
214
|
+
--font-role-accent: var(--font-serif);
|
|
215
|
+
/* The small-caps eyebrow/label face behind the `font-mono` utility below.
|
|
216
|
+
Defaults to the built-in JetBrains Mono, swappable via the Appearance
|
|
217
|
+
panel's "Labels" font (fontRoleStyle → --font-role-label). */
|
|
218
|
+
--font-role-label: var(--font-mono-loaded);
|
|
219
|
+
/* The OS-monospace tail that sits BEHIND whichever face the label role names.
|
|
220
|
+
Split out of --font-mono so a rule can put the LIVE --font-role-label in
|
|
221
|
+
front of it (see .mono-eyebrow) without restating eight family names.
|
|
222
|
+
Deliberately contains no var() of its own: a custom property's var() is
|
|
223
|
+
substituted where the property is DECLARED, so anything referencing
|
|
224
|
+
--font-role-label from here would freeze to the :root face — which is the
|
|
225
|
+
whole reason --font-mono cannot be used for this (card 8824). */
|
|
226
|
+
--font-mono-fallback: ui-monospace, "SF Mono", "Cascadia Mono", "Cascadia Code",
|
|
227
|
+
"Consolas", "DejaVu Sans Mono", Menlo, monospace;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Light theme — applied when <html data-theme="light"> */
|
|
231
|
+
[data-theme="light"] {
|
|
232
|
+
/* Light-mode surfaces + ink. Only the BASES flip here — the live tokens
|
|
233
|
+
inherit the `var(<base>)` mapping declared at :root, so every surface has
|
|
234
|
+
exactly one light value and one dark value, in one place each. */
|
|
235
|
+
--bg-base: #f5f1e8;
|
|
236
|
+
--surface-base: #ffffff;
|
|
237
|
+
--surface-2-base: #ebe6d9;
|
|
238
|
+
--fg-base: #0a0a0a;
|
|
239
|
+
--fg-dim-base: #6b6357;
|
|
240
|
+
--line-base: #d8d2c2;
|
|
241
|
+
/* Only the BASES flip here — the live tokens inherit the `var(<base>)`
|
|
242
|
+
mapping declared at :root. This pair is what an UN-themed light page uses,
|
|
243
|
+
and what a themed one falls back to when its preset carries no light
|
|
244
|
+
colour. */
|
|
245
|
+
--accent-base: #8a6a2a;
|
|
246
|
+
--accent-2-base: #c9a461;
|
|
247
|
+
/* accentInk() for the light-page accent above — WHITE, not the :root black.
|
|
248
|
+
#8a6a2a scores 4.18:1 under black and 5.03:1 under white, so the ink flips
|
|
249
|
+
with the accent even between two shades of the house gold. */
|
|
250
|
+
--accent-ink-base: #ffffff;
|
|
251
|
+
/* Same role as the dark-theme --green, but darkened for the cream
|
|
252
|
+
bg. The dark-theme #7aa672 reads as pastel-bright on cream — this
|
|
253
|
+
#4a6b42 is the same hue, lower lightness. */
|
|
254
|
+
--green: #4a6b42;
|
|
255
|
+
/* Switch OS chrome back to light when the user toggles light mode. */
|
|
256
|
+
color-scheme: light;
|
|
257
|
+
/* Light-mode card overrides — preserve the original behaviour the
|
|
258
|
+
light theme shipped with: card body = --site-surface (the white surface
|
|
259
|
+
the design used pre-cleanup), hover = --site-surface-2 (the warm tan
|
|
260
|
+
step). The dark-mode lifted-grey treatment does NOT apply here.
|
|
261
|
+
The journal panel reverts to --site-surface (its original cream-white
|
|
262
|
+
fill).
|
|
263
|
+
The catalog grid container uses --bg in BOTH themes now — the
|
|
264
|
+
original --site-line fill was painting a visible warm-tan rectangle
|
|
265
|
+
in the bottom-right empty grid cell on partial last rows (e.g.
|
|
266
|
+
Monoblock Cast = 7 wheels in 4 cols leaves 1 empty track). With
|
|
267
|
+
--bg, that empty cell now matches the page background. The
|
|
268
|
+
side-effect is that the inter-card hairline dividers (line color
|
|
269
|
+
painting through the 2px gap) are gone in light mode too, but
|
|
270
|
+
the white card surface (--site-surface) on the cream page bg already
|
|
271
|
+
gives cards clear visual separation.
|
|
272
|
+
These five recipes, and the light --tooltip-bg, live in the
|
|
273
|
+
theme-derived block below for the reason given there. */
|
|
274
|
+
/* Muted accent for the fitment-diagram outline — slightly deeper than
|
|
275
|
+
the light-theme --accent (#8a6a2a) so the silhouette still reads
|
|
276
|
+
against the cream page background. */
|
|
277
|
+
--accent-dim: #7a5d23;
|
|
278
|
+
/* Cream radial gradient backing the Subscribe popup's left panel in
|
|
279
|
+
light mode — the original design value (dark override lives in :root). */
|
|
280
|
+
--subscribe-panel-bg: radial-gradient(80% 100% at 50% 40%, #f5edd8 0%, #ebe6d9 65%, #ddd5c0 100%);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* ─── Theme-derived surface tokens ──────────────────────────────────────
|
|
284
|
+
The card / panel / section fills, which are all recipes over --site-surface-2,
|
|
285
|
+
--site-surface, --bg and --fg rather than colours of their own.
|
|
286
|
+
|
|
287
|
+
Declared BOTH at the root and on `.sf-theme-surfaces` — the element that
|
|
288
|
+
carries the merchant's palette (<body> on the live site, the preview wrapper
|
|
289
|
+
in the editor). This is not a second copy: a custom property has its var()s
|
|
290
|
+
substituted where it is DECLARED, so a recipe declared only at :root would
|
|
291
|
+
bake in the built-in surfaces and quietly ignore the theme. Re-declaring the
|
|
292
|
+
same recipes on the themed element is what lets a merchant's surface colour
|
|
293
|
+
actually reach the cards, the journal panel and the section bands.
|
|
294
|
+
|
|
295
|
+
The dark block must stay ABOVE the light one: `:root` and
|
|
296
|
+
`[data-theme="light"]` have equal specificity, so source order decides. */
|
|
297
|
+
:root,
|
|
298
|
+
.sf-theme-surfaces {
|
|
299
|
+
--card-bg: color-mix(in oklab, var(--site-surface-2) 92%, var(--fg));
|
|
300
|
+
--card-bg-hover: color-mix(in oklab, var(--site-surface-2) 82%, var(--fg));
|
|
301
|
+
--journal-panel-bg: var(--bg);
|
|
302
|
+
--catalog-grid-bg: var(--bg);
|
|
303
|
+
/* ─── Section-level "highlighted" background ────────────────
|
|
304
|
+
Used by alternating sections across the contact / about / fitment
|
|
305
|
+
/ PDP pages to break up long dark-on-dark stretches with a tonal
|
|
306
|
+
band. Resolves to --card-bg (medium-grey ~#2d2d2c) in dark mode.
|
|
307
|
+
Tweak in this one place to update every section at once. */
|
|
308
|
+
--section-bg: var(--card-bg);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
[data-theme="light"],
|
|
312
|
+
[data-theme="light"] .sf-theme-surfaces {
|
|
313
|
+
--card-bg: var(--site-surface);
|
|
314
|
+
--card-bg-hover: var(--site-surface-2);
|
|
315
|
+
--journal-panel-bg: var(--site-surface);
|
|
316
|
+
--catalog-grid-bg: var(--bg);
|
|
317
|
+
/* Light mode skips the gold-band — sections fall back to the cream
|
|
318
|
+
page bg so the dark-mode gold treatment doesn't leak through. */
|
|
319
|
+
--section-bg: var(--bg);
|
|
320
|
+
/* Tooltip surface follows the cream palette in light mode (was the
|
|
321
|
+
always-dark #3a3a3a, which read as a hard black highlight when it
|
|
322
|
+
popped over a hovered fitment row). Uses --site-surface so the tooltip
|
|
323
|
+
contrasts with the row's --site-surface-2 hover state. */
|
|
324
|
+
--tooltip-bg: var(--site-surface);
|
|
325
|
+
/* The wheel image well follows the light theme instead of staying black
|
|
326
|
+
(66035). --bg, not --site-surface-2: --site-surface-2 is exactly --card-bg-hover
|
|
327
|
+
in light, so a surface-2 stage would vanish into the card the moment
|
|
328
|
+
the shopper hovered it. --bg keeps the well readable in BOTH the rest
|
|
329
|
+
and hover states, and matches what the COMPACT ProductCard variant has
|
|
330
|
+
always used for its own image well (`bg-bg`), so the two wells finally
|
|
331
|
+
agree. Lives in the theme-derived block, not the plain light block,
|
|
332
|
+
because it is a var() recipe: declared only at :root it would bake in
|
|
333
|
+
the built-in cream and ignore the merchant's palette. */
|
|
334
|
+
--wheel-stage: var(--bg);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* Colour-Theme content text. themeColorStyle() sets --theme-fg on the page
|
|
338
|
+
CONTENT wrapper (the live <main> in LayoutChrome, the editor preview body in
|
|
339
|
+
lib/puck/config) — the merchant's per-site text colour. Mapped onto --fg in
|
|
340
|
+
EVERY mode: a theme is one flat palette (66238), so the preset states one ink
|
|
341
|
+
and it paints wherever the theme paints.
|
|
342
|
+
|
|
343
|
+
It used to map in DARK mode only, with a parallel light-page ink token,
|
|
344
|
+
because --theme-fg was dark-mode ink and mapping it unconditionally put
|
|
345
|
+
cream text on the cream light page — the bug where the Product Grid header
|
|
346
|
+
vanished. A flat preset carries its background as well as its ink, so the two
|
|
347
|
+
travel together and there is no mode left for them to disagree across.
|
|
348
|
+
|
|
349
|
+
The chrome (Header / Footer) sits OUTSIDE this wrapper and keeps the plain
|
|
350
|
+
light/dark --fg. */
|
|
351
|
+
.sf-themed-content {
|
|
352
|
+
--fg: var(--theme-fg, var(--fg-base));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Colour-Theme SURFACES. themeSurfaceStyle() sets --theme-bg / --theme-surface /
|
|
356
|
+
--theme-surface-2 / --theme-line / --theme-muted on the element carrying this
|
|
357
|
+
class: <body> on the live site, the preview wrapper inside the editor.
|
|
358
|
+
|
|
359
|
+
Why <body> and not the accent carrier: <body> is the element that PAINTS the
|
|
360
|
+
page canvas (`bg-bg`), and the carrier is one of its children — custom
|
|
361
|
+
properties inherit downwards, so a background set on the carrier could never
|
|
362
|
+
reach the canvas behind the content. The values are server-computed from the
|
|
363
|
+
site's theme row and identical on both renders, so they add no hydration
|
|
364
|
+
surface beyond the --sf-header-h length var already set there.
|
|
365
|
+
|
|
366
|
+
The mapping reads --theme-*, exactly as --theme-fg does, and applies in
|
|
367
|
+
EVERY mode. Each fallback is the token's own base, which is why an un-themed
|
|
368
|
+
site — and any preset stored before these colours existed — resolves to
|
|
369
|
+
precisely the values it uses today.
|
|
370
|
+
|
|
371
|
+
There used to be a SECOND set of names for light pages, and two routes that
|
|
372
|
+
emitted them (a per-surface light colour, card 22014; a whole-palette opt-in,
|
|
373
|
+
card 11039). Both existed because a preset's surfaces were dark-mode colours
|
|
374
|
+
and the light page had to be protected from them — a near-black background
|
|
375
|
+
under the light theme's near-black ink is a site nobody can read. A theme is
|
|
376
|
+
one flat palette now (66238): the preset states its ink and its background
|
|
377
|
+
together, so they are readable or unreadable as a pair and there is no half
|
|
378
|
+
to protect the other from. One set of names, one rule. */
|
|
379
|
+
.sf-theme-surfaces {
|
|
380
|
+
--bg: var(--theme-bg, var(--bg-base));
|
|
381
|
+
--site-surface: var(--theme-surface, var(--surface-base));
|
|
382
|
+
--site-surface-2: var(--theme-surface-2, var(--surface-2-base));
|
|
383
|
+
--site-line: var(--theme-line, var(--line-base));
|
|
384
|
+
--fg-dim: var(--theme-muted, var(--fg-dim-base));
|
|
385
|
+
/* THE INK TRAVELS WITH THE BACKGROUND, and it has to be on THIS element.
|
|
386
|
+
Lewis, 2026-09-08: on SHARPEN he scrolled past the hero and the header text
|
|
387
|
+
was white on a white bar. Measured on the live page, header solid, at rest:
|
|
388
|
+
a light preset in dark mode gave a bar of #f4f4f6 under ink of #f5f1e8 —
|
|
389
|
+
dLum 3. The mirror broke too: a dark preset in light mode, #0a0a0a on
|
|
390
|
+
#2e2e2e, dLum 36.
|
|
391
|
+
|
|
392
|
+
Both had one cause. This element is <body>, so everything above maps the
|
|
393
|
+
merchant's SURFACES onto the whole page, chrome included. The merchant's
|
|
394
|
+
INK was mapped only on `.sf-themed-content` (<main>) below, and the Header
|
|
395
|
+
and Footer are that element's SIBLINGS, not its children. So the chrome
|
|
396
|
+
took its background from the palette and its ink from whichever light/dark
|
|
397
|
+
mode was on, and the two halves were free to disagree.
|
|
398
|
+
|
|
399
|
+
A theme is one flat palette (66238): the preset states its background and
|
|
400
|
+
its ink together, so they are readable or unreadable AS A PAIR. That is
|
|
401
|
+
only true while both halves reach the same elements. Declaring it here is
|
|
402
|
+
what makes it true for the chrome.
|
|
403
|
+
|
|
404
|
+
⚠️ This is the line the old "chrome keeps the plain light/dark --fg" rule
|
|
405
|
+
used to be, and that rule was SAFE ONLY WHILE THE SURFACES WERE DARK-MODE
|
|
406
|
+
ONLY. 66238 made them apply in every mode; the protection went with it and
|
|
407
|
+
nobody noticed, because the comments around here still said "dark mode
|
|
408
|
+
only" for another year. They are corrected now — do not restore the split.
|
|
409
|
+
`scripts/test-chrome-theme-pair.ts` §2 fails if one half ever travels
|
|
410
|
+
without the other.
|
|
411
|
+
|
|
412
|
+
`.sf-themed-content` still re-declares --fg on <main>. That is NOT dead:
|
|
413
|
+
a custom property has its var()s substituted where it is DECLARED, so the
|
|
414
|
+
value resolved here freezes against THIS element's --theme-fg. Re-declaring
|
|
415
|
+
it down there is what lets a section pin its own ink and have it resolve
|
|
416
|
+
against the section's value instead of the page's. */
|
|
417
|
+
--fg: var(--theme-fg, var(--fg-base));
|
|
418
|
+
}
|
|
419
|
+
/* Colour-Theme ACCENT — the merchant's theme colour.
|
|
420
|
+
themeAccentStyle() sets --theme-accent (+ its companion shade) on the element
|
|
421
|
+
carrying this class: the boxless accent carrier in app/layout.tsx on the live
|
|
422
|
+
site, the preview wrapper inside the editor.
|
|
423
|
+
|
|
424
|
+
Why the indirection rather than setting --accent inline: a custom property set
|
|
425
|
+
in a style attribute beats every stylesheet rule on that same element, so an
|
|
426
|
+
inline --accent could never be overridden — and the per-SECTION accent
|
|
427
|
+
override below has to be able to win. The colour arrives as data and lets CSS
|
|
428
|
+
pick, the same reason --theme-fg and the themed surfaces exist.
|
|
429
|
+
|
|
430
|
+
It used to carry a second colour for light pages, with a two-step fallback to
|
|
431
|
+
the dark one. A theme is one flat palette now (66238): one accent, one rule. */
|
|
432
|
+
.sf-theme-accent {
|
|
433
|
+
--accent: var(--theme-accent, var(--accent-base));
|
|
434
|
+
--accent-2: var(--theme-accent-2, var(--accent-2-base));
|
|
435
|
+
--accent-ink: var(--theme-accent-ink, var(--accent-ink-base));
|
|
436
|
+
--btn: var(--theme-btn, var(--accent));
|
|
437
|
+
--btn-2: var(--theme-btn-2, var(--accent-2));
|
|
438
|
+
--btn-ink: var(--theme-btn-ink, var(--accent-ink));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* ── A SECTION'S THEME PAINTS IT (card ss.section-theme-picker) ─────────────
|
|
442
|
+
Lewis, 2026-09-03: he set a Box to Theme 6, a blue preset, and nothing moved.
|
|
443
|
+
Nothing could — only a preset's ACCENT was ever published per preset, so the
|
|
444
|
+
wrapper could reach one of six colours, and an empty Box draws nothing that
|
|
445
|
+
uses an accent. `ThemeColors.background` had always said what this should be:
|
|
446
|
+
"THE PAGE when the page is on this theme, THE SECTION when a section is."
|
|
447
|
+
|
|
448
|
+
IT USED TO SIT BEFORE THE SURFACE CLASSES AND THE POSITION WAS LOAD-BEARING:
|
|
449
|
+
`.sf-section-surface-*` and `.sf-section-accent` were both (0,1,0), so source
|
|
450
|
+
order decided --bg between them, and this block after them cost a pinned
|
|
451
|
+
accent bar its fill (`scripts/test-accent-colour-parity` caught it). The
|
|
452
|
+
surface classes were deleted on 2026-09-04, so there is nothing left at equal
|
|
453
|
+
specificity to order against — but do not move this block on the strength of
|
|
454
|
+
that: a section's theme is what paints everything, and a new class in this
|
|
455
|
+
family would bring the whole question back.
|
|
456
|
+
|
|
457
|
+
The fallbacks live here rather than in the emitted style attribute so the
|
|
458
|
+
wrapper stays a pure reference with no hex in it (DD's rule, 2026-08-25). */
|
|
459
|
+
/* ⚠️ TWO SELECTORS, ONE COPY OF THE MAPPING — the map/paint split the
|
|
460
|
+
`[ADD] The Header Can Take Its Own Space, And Pick A Theme` card owed an
|
|
461
|
+
answer for, and it is a SELECTOR LIST rather than a second block on purpose.
|
|
462
|
+
|
|
463
|
+
`.sf-section-accent` both MAPS a preset's tokens onto --bg/--fg/… and PAINTS
|
|
464
|
+
with them (`background: var(--bg); color: var(--fg)`, at the end of this
|
|
465
|
+
block). A SECTION wants both. The HEADER wants only the first half: its bar
|
|
466
|
+
must stay see-through at the top of a `Shared` page, so a rule that painted
|
|
467
|
+
unconditionally would fill it in. Copying the nine references into a second
|
|
468
|
+
block would be the duplication DD's rule and card 66152 both exist to stop —
|
|
469
|
+
a copy is cheap the day it is made and permanent afterwards.
|
|
470
|
+
|
|
471
|
+
So the mapping is declared ONCE, for both, and only the paint is
|
|
472
|
+
`.sf-section-accent`'s alone below. `scripts/test-header-theme.ts` §3 fails if
|
|
473
|
+
the header class ever picks up a `background` or `color` here. */
|
|
474
|
+
.sf-section-accent,
|
|
475
|
+
.sf-header-theme {
|
|
476
|
+
/* ⛔ THE SURFACES BELOW ARE CARD ss.section-theme-picker, AND WITHOUT THEM THE
|
|
477
|
+
CONTROL DOES ALMOST NOTHING. Lewis, 2026-09-03: he set a Box to Theme 6, a
|
|
478
|
+
blue preset, and nothing moved. Only the accent triple above existed, so a
|
|
479
|
+
section could reach one of a preset's six colours — and an empty Box uses no
|
|
480
|
+
accent at all, which is why every theme looked identical on it.
|
|
481
|
+
|
|
482
|
+
`ThemeColors.background` has always said what this should be: "THE PAGE when
|
|
483
|
+
the page is on this theme, THE SECTION when a section is." This is the
|
|
484
|
+
section half, finally wired.
|
|
485
|
+
|
|
486
|
+
THE FALLBACKS ARE IN THE REFERENCE, NOT HERE — `sectionAccentVars` builds
|
|
487
|
+
each `--sf-sec-*` as `var(--theme-preset-<i>-x, var(--theme-x, <base>))`, so
|
|
488
|
+
a stale preset index lands on the page's own token rather than on nothing. A
|
|
489
|
+
bare `var(--sf-sec-bg)` here would be the cycle-free half of that chain; the
|
|
490
|
+
second value is kept only for a wrapper written before this card. */
|
|
491
|
+
--bg: var(--sf-sec-bg, var(--theme-bg, #0a0a0a));
|
|
492
|
+
--fg: var(--sf-sec-fg, var(--theme-fg, #f5f1e8));
|
|
493
|
+
--site-surface: var(--sf-sec-surface, var(--theme-surface, #131313));
|
|
494
|
+
--site-surface-2: var(--sf-sec-surface-2, var(--theme-surface-2, #1c1c1c));
|
|
495
|
+
--site-line: var(--sf-sec-line, var(--theme-line, #2a2a2a));
|
|
496
|
+
--fg-dim: var(--sf-sec-muted, var(--theme-muted, #9a9388));
|
|
497
|
+
/* The derived fills that read --bg / --site-surface-2, restated because a custom
|
|
498
|
+
property resolves where it is DECLARED: the :root copies froze against the
|
|
499
|
+
PAGE's tokens and would keep the page's card colour inside a themed band.
|
|
500
|
+
Same rule, same reason, as --btn above. */
|
|
501
|
+
--card-bg: color-mix(in oklab, var(--site-surface-2) 92%, var(--fg));
|
|
502
|
+
--card-bg-hover: color-mix(in oklab, var(--site-surface-2) 82%, var(--fg));
|
|
503
|
+
--journal-panel-bg: var(--bg);
|
|
504
|
+
--catalog-grid-bg: var(--bg);
|
|
505
|
+
--section-bg: var(--card-bg);
|
|
506
|
+
}
|
|
507
|
+
/* THE PAINT HALF, and a SECTION's alone. The header takes the mapping above and
|
|
508
|
+
paints from its own branches instead: `<Header/>` sets background and colour
|
|
509
|
+
inline once the bar is solid, and the `data-top-space="own"` rule further up
|
|
510
|
+
paints a page that takes its own space. Both read the very --bg / --fg the
|
|
511
|
+
list above just re-pointed, which is how picking a theme moves the bar without
|
|
512
|
+
this declaration ever reaching it. */
|
|
513
|
+
.sf-section-accent {
|
|
514
|
+
background: var(--bg);
|
|
515
|
+
color: var(--fg);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* ⛔ THERE ARE NO `.sf-section-surface-*` CLASSES ANY MORE, AND THAT IS A
|
|
519
|
+
DECISION — do not write a new one here.
|
|
520
|
+
Card 22018 gave a SECTION a pinned dark / light / accent surface;
|
|
521
|
+
ss.section-theme-picker took that away on 2026-09-03 (a section is on a THEME
|
|
522
|
+
and nothing else), leaving the HEADER as the only element that wore one; and
|
|
523
|
+
the header's own Background control went on 2026-09-04 (Lewis: the header
|
|
524
|
+
follows the theme). With no element left to wear them, the three surface
|
|
525
|
+
blocks, their three `.sf-section-accent` pairings and the four
|
|
526
|
+
`--accent-*-base-on-{dark,light}` tokens they alone read were removed.
|
|
527
|
+
NOTHING ON ANY SITE MOVED: all 9 stored header `surface` values were empty.
|
|
528
|
+
Bringing one back means giving some element two colour rules again, with no
|
|
529
|
+
panel able to say which of them paints. */
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
/* Per-SECTION accent. The wrapper stores only a preset INDEX and emits
|
|
533
|
+
--sf-sec-accent* as `var(--theme-preset-<i>-accent…)` REFERENCES into the
|
|
534
|
+
per-preset accents the carrier publishes (themePresetAccentVars in
|
|
535
|
+
lib/theme.ts) — no copied colour anywhere, so editing the palette re-paints
|
|
536
|
+
every overridden section. A stale index resolves nothing and every chain
|
|
537
|
+
below falls back to the page accent: degraded, readable, never garbage.
|
|
538
|
+
|
|
539
|
+
Mode: the section follows the PAGE's mode, always. There used to be a second
|
|
540
|
+
case — a PINNED surface picked the accent's mode itself, through three
|
|
541
|
+
`.sf-section-surface-*.sf-section-accent` rules sitting last at (0,2,0). Those
|
|
542
|
+
went with the surface classes on 2026-09-04; nothing pins a mode now, so this
|
|
543
|
+
one rule is the whole story. */
|
|
544
|
+
/* The header joins this one too: a themed bar's buttons and links must come out
|
|
545
|
+
of the SAME preset as its background, or the bar is half on one theme. Pure
|
|
546
|
+
mapping, no paint, so there is nothing here to keep off the header. */
|
|
547
|
+
.sf-section-accent,
|
|
548
|
+
.sf-header-theme {
|
|
549
|
+
--accent: var(--sf-sec-accent, var(--accent-base));
|
|
550
|
+
--accent-2: var(--sf-sec-accent-2, var(--accent-2-base));
|
|
551
|
+
--accent-ink: var(--sf-sec-accent-ink, var(--accent-ink-base));
|
|
552
|
+
--btn: var(--theme-btn, var(--accent));
|
|
553
|
+
--btn-2: var(--theme-btn-2, var(--accent-2));
|
|
554
|
+
--btn-ink: var(--theme-btn-ink, var(--accent-ink));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
/* ─── Corners ──────────────────────────────────────────────────────────
|
|
559
|
+
The Appearance panel's site-wide "Corners" control drives the site's radius
|
|
560
|
+
scale (cornerStyleTokens in lib/theme.ts sets --site-radius-* on the theme
|
|
561
|
+
carrier), which reaches every `rounded-sm|md|lg|xl|2xl` on the site because
|
|
562
|
+
the site's `inline` theme block (with the named colours, further down)
|
|
563
|
+
compiles those to `border-radius: var(--site-radius-<size>)`.
|
|
564
|
+
|
|
565
|
+
⚠️ NOT TAILWIND'S OWN `--radius-*`. Until 2026-10-11 the setting overrode
|
|
566
|
+
Tailwind's scale directly, and efficient-shell's UI kit reads that same scale
|
|
567
|
+
for its chrome — so a site at 0 squared the kit's dialogs wherever the two
|
|
568
|
+
shared a page (see the note on the design tokens above). `inline` makes each
|
|
569
|
+
utility name the site's property itself, so nothing the site draws reads
|
|
570
|
+
Tailwind's `--radius-*` any more. The values on :root are Tailwind 4.3's own
|
|
571
|
+
scale, which is what the utilities drew before; the bare `rounded` keeps its
|
|
572
|
+
quarter-rem the same way.
|
|
573
|
+
|
|
574
|
+
The bare `rounded` utility does NOT follow the scale: it compiles to a literal
|
|
575
|
+
`0.25rem`, so it would sit out the setting and leave a site with square cards
|
|
576
|
+
and rounded inputs. Re-point it at a variable whose default is that same
|
|
577
|
+
0.25rem — the utility keeps its exact value until the merchant chooses
|
|
578
|
+
otherwise.
|
|
579
|
+
|
|
580
|
+
`rounded-full` is deliberately NOT re-pointed. It compiles to
|
|
581
|
+
`calc(infinity * 1px)` and is what makes a thing CIRCULAR — toggle knobs,
|
|
582
|
+
status dots, finish swatches, the wheel placeholder's rings. A circle is a
|
|
583
|
+
shape, not a corner, and squaring those would be a regression with no upside;
|
|
584
|
+
the design that motivated this setting has no rounded panels but still has
|
|
585
|
+
round dots. */
|
|
586
|
+
:root {
|
|
587
|
+
--site-radius: 0.25rem;
|
|
588
|
+
--site-radius-xs: 0.125rem;
|
|
589
|
+
--site-radius-sm: 0.25rem;
|
|
590
|
+
--site-radius-md: 0.375rem;
|
|
591
|
+
--site-radius-lg: 0.5rem;
|
|
592
|
+
--site-radius-xl: 0.75rem;
|
|
593
|
+
--site-radius-2xl: 1rem;
|
|
594
|
+
--site-radius-3xl: 1.5rem;
|
|
595
|
+
--site-radius-4xl: 2rem;
|
|
596
|
+
}
|
|
597
|
+
@layer utilities {
|
|
598
|
+
.rounded {
|
|
599
|
+
border-radius: var(--site-radius);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* The BUTTON's corners, and the one place the `Corners` setting can reach them.
|
|
604
|
+
Card `[FIX] The Corners Setting Says Buttons And Skips Them`.
|
|
605
|
+
|
|
606
|
+
The same hole as `.sf-card` further down, found from the other end. `Corners`
|
|
607
|
+
reaches an element by re-pointing the `--site-radius-*` scale above, which only
|
|
608
|
+
works if that element writes a `rounded-*` utility — and `components/Btn.tsx`,
|
|
609
|
+
the shared button every section draws, writes NONE. So the row's hint said
|
|
610
|
+
"Applies to panels, cards, inputs and buttons" while a button was square at 0,
|
|
611
|
+
at 9 and at 40. `--sf-button-radius` is the property `cornerRadiusTokens`
|
|
612
|
+
publishes for buttons, at half the card's ratio — the amount the inputs and
|
|
613
|
+
panels beside it already take — and it is UNSET at the identity, so the `0`
|
|
614
|
+
here is exactly what a button draws today: square, on every site that never
|
|
615
|
+
opened the panel. No migration, and the hint becomes true.
|
|
616
|
+
|
|
617
|
+
⚠️ IT LIVES HERE, WITH `Corners`, AND NOT IN THE CARD-LOOK BLOCK BELOW. It was
|
|
618
|
+
written there first and `scripts/test-card-style-settings.tsx` §1a caught it:
|
|
619
|
+
every rule between those two markers must be scoped to `.sf-card` /
|
|
620
|
+
`.sf-card-grid`, because a card-look rule that is not would reach a colour
|
|
621
|
+
swatch or a toggle knob (Lewis, 2026-09-01). This is not a card-look rule —
|
|
622
|
+
it is a corner rule — and the guard reading its neighbourhood by position is
|
|
623
|
+
the reason position matters.
|
|
624
|
+
|
|
625
|
+
⚠️ NO `overflow: clip` HERE, unlike `.sf-card`. A button's only children are
|
|
626
|
+
its label and its arrow glyph, and the arrow TRANSLATES 3px right on hover —
|
|
627
|
+
clipping the box would shave that arrow at the moment it moves. Nothing in a
|
|
628
|
+
button bleeds past its border, so there is nothing to clip. */
|
|
629
|
+
.sf-button { border-radius: var(--sf-button-radius, 0); }
|
|
630
|
+
|
|
631
|
+
/* ─── Stacking layers ──────────────────────────────────────────────────
|
|
632
|
+
The site's layers above the page, by NAME (SSOT-12), in steps of 100 (the
|
|
633
|
+
owner's layer rule, 2026-10-12). Markup takes a layer as `z-(--sf-layer-…)`.
|
|
634
|
+
|
|
635
|
+
`--sf-layer-lightbox` is every lightbox (components/Lightbox.tsx). Its value
|
|
636
|
+
is the `z-[100]` the three lightboxes wrote by hand, so nothing re-orders
|
|
637
|
+
against them. It is above the fixed header (`z-50`), the drawers (60 / 61)
|
|
638
|
+
and the dialogs (90) — but only because the lightbox is portalled out of its
|
|
639
|
+
section: a layer orders things inside ONE stacking context, and the owner
|
|
640
|
+
photos' dialog, drawn inside a `relative z-[2]` section, sat under the header
|
|
641
|
+
at any number (hartesmetal.com, 2026-10-15).
|
|
642
|
+
|
|
643
|
+
⏳ The other site layers are still numbers in their components (the header
|
|
644
|
+
and its menu 40 / 50, drawers and menus 60 / 61, dialogs 90, the select list
|
|
645
|
+
200). They move here, onto steps of 100, as their own change. */
|
|
646
|
+
:root {
|
|
647
|
+
--sf-layer-lightbox: 100;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/* ─── Floating launchers step aside for an overlay ─────────────────────
|
|
651
|
+
A floating launcher (`data-sf-floating`: the chat bubble and its panel, the
|
|
652
|
+
classic one and the assistant) is fixed to the bottom-right corner above
|
|
653
|
+
the page. A blocking overlay — the phone menu, the cart drawer, search, the
|
|
654
|
+
footer menu, a popup — marks <html> `data-sf-overlay` while it is open
|
|
655
|
+
(components/useModalDismiss.ts), and under that mark the launcher is hidden:
|
|
656
|
+
in the phone menu the bubble sat on the Region button and on "Your account"
|
|
657
|
+
(inovit.com.au at 390, 2026-10-17, card [FIX] Region Picker On Phone).
|
|
658
|
+
A rule rather than a layer number: it holds whatever the overlay's own
|
|
659
|
+
z-index, and the chat comes back, as it was, when the overlay closes.
|
|
660
|
+
Guard: scripts/test-phone-menu-region-picker.tsx §3-§4. */
|
|
661
|
+
:root[data-sf-overlay] [data-sf-floating] { visibility: hidden; }
|
|
662
|
+
|
|
663
|
+
/* ─── Card look (card 66332) ───────────────────────────────────────────
|
|
664
|
+
Site styles ▸ Basics ▸ Card shadow / Shadow colour / Card gap / Hover —
|
|
665
|
+
three site-level settings beside `Corners`, for the reason Corners is one
|
|
666
|
+
decision: a card appears in a product grid and a filter cell,
|
|
667
|
+
and one set per section is one idea with seven answers.
|
|
668
|
+
|
|
669
|
+
⚠️ EVERY RULE HERE IS SCOPED TO `.sf-card` OR `.sf-card-grid`, AND THAT IS
|
|
670
|
+
THE EXEMPTION. Lewis, 2026-09-01: corners, shadow and hover must not leak
|
|
671
|
+
onto swatches, switches and status dots. `Corners` exempts them structurally
|
|
672
|
+
— `rounded-full` compiles to a literal that reads no token, so the radius
|
|
673
|
+
override cannot reach it (see the Corners block above) — and this is that
|
|
674
|
+
same rule in the only vocabulary a shadow can use: a thing that does not
|
|
675
|
+
carry the class is out of reach by construction, not by a list of exceptions
|
|
676
|
+
somebody has to maintain. `scripts/test-card-style-settings.tsx` §1 holds
|
|
677
|
+
both halves — the rules stay scoped, and no round control wears the class.
|
|
678
|
+
|
|
679
|
+
⚠️ EVERY `:hover` CARRIES THE SAME `:active`. Lewis, 2026-09-01: a phone has
|
|
680
|
+
no hover, so the hover effect IS the press feedback. A rule with no `:active`
|
|
681
|
+
is a setting a shopper on a phone can never see, and it looks correct on
|
|
682
|
+
every desktop it is tested on. §2 keeps it that way. (The selector lists are
|
|
683
|
+
written on ONE line each because that guard reads a rule's whole selector;
|
|
684
|
+
splitting them over two lines would let a `:hover` sneak past it.)
|
|
685
|
+
|
|
686
|
+
⚠️ NO SELECTOR NAMES A PAGE, A SECTION OR A FILTER. A filter cell is a card
|
|
687
|
+
and takes these values automatically — it gets no settings of its own
|
|
688
|
+
(settled). That is only true while these rules key off the site carrier and
|
|
689
|
+
the class alone; a page or filter-bar ancestor here would produce a card that
|
|
690
|
+
follows the site in one place and not in another. §3.
|
|
691
|
+
|
|
692
|
+
UNLAYERED, like the heading-compression rules below: a card writes its hover
|
|
693
|
+
as a Tailwind `hover:bg-[…]` utility inside `@layer utilities`, and a custom
|
|
694
|
+
property cannot override a declaration that does not read it. An unlayered
|
|
695
|
+
rule can.
|
|
696
|
+
|
|
697
|
+
An untouched site carries NO `data-sf-card-*` attribute (the identity value
|
|
698
|
+
of each setting emits none — cardShadowAttr / cardGapAttr / hoverEffectAttr
|
|
699
|
+
in lib/theme.ts), so not one rule below can match and the site renders
|
|
700
|
+
byte-identically. */
|
|
701
|
+
|
|
702
|
+
/* The shadow colour reaches the shadows through `--sf-shadow-color`, whose
|
|
703
|
+
BUILT-IN VALUE LIVES HERE rather than in the carrier: a site that never
|
|
704
|
+
touched the colour then publishes no declaration at all, the way `small`
|
|
705
|
+
corners publish none. Lewis's default, 2026-09-01. */
|
|
706
|
+
[data-sf-card-shadow="soft"] { --sf-card-shadow: 0 1px 2px color-mix(in srgb, var(--sf-shadow-color, #141416) 7%, transparent), 0 4px 12px -4px color-mix(in srgb, var(--sf-shadow-color, #141416) 10%, transparent); }
|
|
707
|
+
[data-sf-card-shadow="lifted"] { --sf-card-shadow: 0 2px 4px color-mix(in srgb, var(--sf-shadow-color, #141416) 6%, transparent), 0 14px 30px -10px color-mix(in srgb, var(--sf-shadow-color, #141416) 22%, transparent); }
|
|
708
|
+
|
|
709
|
+
/* The raised state a `lift` hover moves to, and the distance it moves. Both
|
|
710
|
+
read the merchant's shadow colour for the reason the resting shadow does —
|
|
711
|
+
a black lift on a near-black page is a setting that visibly does nothing. */
|
|
712
|
+
[data-sf-card-hover="lift"] { --sf-card-shadow-hover: 0 6px 10px color-mix(in srgb, var(--sf-shadow-color, #141416) 8%, transparent), 0 20px 40px -14px color-mix(in srgb, var(--sf-shadow-color, #141416) 26%, transparent); --sf-card-lift: translateY(-3px); }
|
|
713
|
+
|
|
714
|
+
/* The gutter between cards. A LENGTH rather than an attribute, because the
|
|
715
|
+
wheels listing SUBTRACTS its gutter when working out how wide one of N
|
|
716
|
+
columns would be (lib/wheels/plp-grid) — a visual gap and an arithmetic gap
|
|
717
|
+
that were two values would drift, which is the one thing that file exists to
|
|
718
|
+
prevent. Root-relative, like the 1rem it replaces: the gutter has always
|
|
719
|
+
scaled with the visitor's own browser text size, and a px figure here would
|
|
720
|
+
shrink it for anyone who raised theirs. */
|
|
721
|
+
[data-sf-card-gap="tight"] { --sf-card-gap: 0.5rem; }
|
|
722
|
+
[data-sf-card-gap="roomy"] { --sf-card-gap: 1.625rem; }
|
|
723
|
+
|
|
724
|
+
/* The resting card, and the state it moves to. Both read a custom property that
|
|
725
|
+
is UNSET on an untouched site, so both fall back to what a card draws today —
|
|
726
|
+
no shadow, and no transform. */
|
|
727
|
+
/* The card's CORNERS, and the one place the `Corners` setting can reach them.
|
|
728
|
+
A card writes no `rounded-*` utility — not one of the four card components
|
|
729
|
+
does — so overriding the `--site-radius-*` scale moved buttons and panels
|
|
730
|
+
and left every card square, whatever the panel said. `--sf-card-radius` is
|
|
731
|
+
the property `cornerStyleTokens` publishes for cards alone, and it is UNSET
|
|
732
|
+
at the identity, so the `0` here is what an untouched site keeps: square,
|
|
733
|
+
which is what a card draws today. No migration, and `Round` works.
|
|
734
|
+
|
|
735
|
+
`overflow: clip` rather than `hidden`: a rounded card whose photo runs to its
|
|
736
|
+
own edge (Latest Posts, the journal index) would otherwise wear a square
|
|
737
|
+
image over a round corner. `clip` does that without making a scroll
|
|
738
|
+
container, so the catalog card keeps its `sticky` behaviour. Nothing bleeds
|
|
739
|
+
outside a card today — no negative offset in any of the four — so at the
|
|
740
|
+
identity this clips nothing and the site renders byte-identically. */
|
|
741
|
+
/* ⚠️ THE RADIUS IS A CHAIN, READ CARD FIRST (`[ADD] A Card Sets Its Own
|
|
742
|
+
Corners`): this card's own answer, then the section's, then the site's, then
|
|
743
|
+
square. `--sf-card-box-radius` is published ONLY by a card that has been given
|
|
744
|
+
a number of its own, on a `display: contents` carrier the injection pass in
|
|
745
|
+
lib/puck/config.tsx wraps it in — so a card nobody has touched declares
|
|
746
|
+
nothing, falls straight through to `--sf-card-radius` and renders exactly as
|
|
747
|
+
it did before this rule grew a level. lib/content/card-corners.ts says why the
|
|
748
|
+
card tier needs a name of its own rather than overriding the inherited token.
|
|
749
|
+
The final `0` is unchanged and is still today's corner for a site that has set
|
|
750
|
+
none. */
|
|
751
|
+
.sf-card { box-shadow: var(--sf-card-shadow, none); border-radius: var(--sf-card-box-radius, var(--sf-card-radius, 0)); overflow: clip; }
|
|
752
|
+
|
|
753
|
+
/* The carrier that publishes it. `display: contents` generates no box at all, so
|
|
754
|
+
a card inside a grid stays the grid item and the markup gains nothing that can
|
|
755
|
+
be laid out — the same reason `.sf-section-corners` is `display: contents` one
|
|
756
|
+
tier up. Declared here rather than left to the inline style alone so no class
|
|
757
|
+
is rendered that the stylesheet never styles. */
|
|
758
|
+
.sf-card-corners { display: contents; }
|
|
759
|
+
|
|
760
|
+
/* The PICTURE inside a card, and the one place a section's `Card image` setting
|
|
761
|
+
can reach it. The card's own corner (`--sf-card-radius`, above) rounds the
|
|
762
|
+
card's OUTER edge and, through `overflow: clip`, any picture that runs all the
|
|
763
|
+
way to that edge — Latest Posts and the journal index both do. It cannot reach
|
|
764
|
+
an INSET picture, and that is the card Lewis actually saw on 2026-09-04: every
|
|
765
|
+
wheel card pads its image well by 5–30px, so the photo sits inside the card
|
|
766
|
+
and stays square whatever `Corners` says.
|
|
767
|
+
|
|
768
|
+
Hence a second property, NOT a second setting: `--sf-card-image-radius` is
|
|
769
|
+
published per SECTION by `cardImageShapeTokens` (lib/content/card-image-shape.ts)
|
|
770
|
+
and inherits down to the pictures in that section alone. Site-wide was the
|
|
771
|
+
first proposal and Lewis rejected it — 「有些是要分开的, 有些是要文字在图片上的」
|
|
772
|
+
— because one value cannot let the Collections card keep its overlay caption
|
|
773
|
+
while a product grid rounds its photos.
|
|
774
|
+
|
|
775
|
+
`0` is the fallback, so a section that has chosen nothing declares nothing and
|
|
776
|
+
the picture keeps the square cut it draws today. No migration, and the whole
|
|
777
|
+
setting is provably inert until used.
|
|
778
|
+
|
|
779
|
+
`overflow: clip` for the same reason `.sf-card` uses it rather than `hidden`:
|
|
780
|
+
these wells hold absolutely-positioned siblings (the badge, the gradient, the
|
|
781
|
+
hover crossfade) and `clip` rounds them off without making a scroll container,
|
|
782
|
+
so the catalog card keeps its `sticky` behaviour. */
|
|
783
|
+
.sf-card-image { border-radius: var(--sf-card-image-radius, 0); overflow: clip; }
|
|
784
|
+
.sf-card:hover, .sf-card:active { box-shadow: var(--sf-card-shadow-hover, var(--sf-card-shadow, none)); transform: var(--sf-card-lift, none); }
|
|
785
|
+
|
|
786
|
+
/* Only a site that has chosen gets the extra transitions. `transition-property`
|
|
787
|
+
ALONE — the duration and the easing stay with the card's own
|
|
788
|
+
`transition-colors duration-200|300` utility, so a card keeps the timing it
|
|
789
|
+
has always had and only gains two more properties to animate. */
|
|
790
|
+
[data-sf-card-shadow] .sf-card, [data-sf-card-hover] .sf-card { transition-property: color, background-color, border-color, box-shadow, transform; }
|
|
791
|
+
|
|
792
|
+
/* `None` and `Lift` must also STOP the fill lightening every card already does
|
|
793
|
+
— a row that says the hover is None while the card still changes colour is a
|
|
794
|
+
row that lies. `Highlight` is the name of that existing effect, so it emits
|
|
795
|
+
no attribute and matches nothing here. `var(--card-bg)` resolves on the CARD,
|
|
796
|
+
which is what lets a card inside a themed section band keep that band's own
|
|
797
|
+
surface instead of the page's. */
|
|
798
|
+
/* ⚠️ THIS ONE RULE IS KEYED ON `.sf-card-fill`, NOT `.sf-card`, AND THAT IS WHAT
|
|
799
|
+
LETS EVERY SECTION BE A CARD. Lewis, 2026-09-03: "所有的 section 也都可以加入
|
|
800
|
+
corner. 但是默认是直角的." — every section takes Corners, and square stays the
|
|
801
|
+
default.
|
|
802
|
+
|
|
803
|
+
The blocker was this rule. It cancels a card's own fill-lightening by setting
|
|
804
|
+
the card back to `var(--card-bg)`, which is right for a card that lightens and
|
|
805
|
+
WRONG for one that does not: a section drawing `bg-surface` would jump to a
|
|
806
|
+
different colour the moment a merchant chose "Hover: None", the one option
|
|
807
|
+
whose whole job is to hold the colour still. Measured before this change: of
|
|
808
|
+
the 36 unmarked card sections, exactly ZERO lightened their own card fill —
|
|
809
|
+
every `hover:bg-*` in them was on a BUTTON inside the section.
|
|
810
|
+
|
|
811
|
+
So the vocabulary splits, one grammar with one modifier rather than two
|
|
812
|
+
grammars:
|
|
813
|
+
`.sf-card` a card SURFACE — corners, shadow, lift, gap. Colour-free,
|
|
814
|
+
so wearing it is safe for any section and is one line.
|
|
815
|
+
`.sf-card-fill` …and this card lightens its fill on hover, so the None and
|
|
816
|
+
Lift options have something to cancel. Worn only by the
|
|
817
|
+
cards that actually do it.
|
|
818
|
+
`scripts/test-card-style-settings.tsx` §8 holds both halves. */
|
|
819
|
+
[data-sf-card-hover="none"] .sf-card-fill:hover, [data-sf-card-hover="none"] .sf-card-fill:active, [data-sf-card-hover="lift"] .sf-card-fill:hover, [data-sf-card-hover="lift"] .sf-card-fill:active { background-color: var(--card-bg); }
|
|
820
|
+
|
|
821
|
+
/* ⚠️ THE RULE THAT USED TO LIVE HERE IS GONE — [ADD] Gap Inherits From Site.
|
|
822
|
+
It read `[data-sf-card-gap] .sf-card-grid { gap: var(--sf-card-gap); }`, and it
|
|
823
|
+
is removed rather than left beside the new one because it would WIN: its
|
|
824
|
+
specificity is (0,2,0) against `.sf-gap-zone`'s (0,1,0), so on precisely the
|
|
825
|
+
sites that stored `tight` or `roomy` it would have pinned the old gutter and
|
|
826
|
+
the site's new Block gap would have done nothing — on those sites only, which
|
|
827
|
+
is the hardest kind of bug to see.
|
|
828
|
+
|
|
829
|
+
The two words themselves are NOT retired: `--sf-card-gap` is still defined
|
|
830
|
+
above, and every card grid now reads it as the MIDDLE rung of
|
|
831
|
+
`var(--sf-block-gap, var(--sf-card-gap, <own built-in>))`. So a site that
|
|
832
|
+
stored one keeps drawing it, and keeps drawing it ONLY on the card grids it
|
|
833
|
+
ever governed — see cardGridGapProps for why that middle rung has to exist. */
|
|
834
|
+
|
|
835
|
+
/* The brand plate is not a card — it is the strip laid over the lower quarter of
|
|
836
|
+
one — but it follows the card's fill, so `None` and `Lift` have to stop its
|
|
837
|
+
lightening too or the card stops changing colour and the plate inside it does
|
|
838
|
+
not. Same pair of states, same reason. */
|
|
839
|
+
[data-sf-card-hover="none"] .sf-card:hover .sf-card-plate, [data-sf-card-hover="none"] .sf-card:active .sf-card-plate, [data-sf-card-hover="lift"] .sf-card:hover .sf-card-plate, [data-sf-card-hover="lift"] .sf-card:active .sf-card-plate { background-color: var(--card-bg); }
|
|
840
|
+
|
|
841
|
+
/* ─── End card look (card 66332) ─────────────────────────────────────── */
|
|
842
|
+
|
|
843
|
+
/* ─── Heading compression ──────────────────────────────────────────────
|
|
844
|
+
The Appearance panel's "Headings" compression rung, applied to real headings
|
|
845
|
+
(h1–h4) under the theme carrier. Delivered as a data attribute rather than a
|
|
846
|
+
custom property because every heading in the codebase writes its own
|
|
847
|
+
`leading-*` / `tracking-*` utility, and a variable cannot override a
|
|
848
|
+
declaration that does not read it. These rules are UNLAYERED, so they win
|
|
849
|
+
over Tailwind's `@layer utilities` regardless of specificity.
|
|
850
|
+
|
|
851
|
+
`normal` emits no attribute at all, so neither rule below can match and the
|
|
852
|
+
default is byte-identical to today — the reason this is an attribute the
|
|
853
|
+
carrier omits rather than one it always sets.
|
|
854
|
+
|
|
855
|
+
Metrics are the display-type values from the Apex design system: closed
|
|
856
|
+
leading with slightly negative tracking.
|
|
857
|
+
|
|
858
|
+
⚠️ THE TOP RUNG USED TO ADD `text-transform: uppercase`, AND THAT WAS THE BUG
|
|
859
|
+
CARD 66162 REMOVED. The Appearance panel also has a `Capitals` setting, which
|
|
860
|
+
emits `--sf-heading-case` and is read by a Tailwind arbitrary utility inside
|
|
861
|
+
`@layer utilities`. Unlayered beats layered whatever the specificity, so these
|
|
862
|
+
rules won every time: a merchant who chose "As typed" was shown their choice
|
|
863
|
+
selected and served capitals anyway. Measured on production before the fix
|
|
864
|
+
(inovit.com.au/wheels, the real `div.sf-theme-accent` carrier, a real <h2>):
|
|
865
|
+
|
|
866
|
+
Spacing=Normal + Capitals=As typed -> text-transform: none obeyed
|
|
867
|
+
Spacing=Compressed + Capitals=As typed -> text-transform: uppercase IGNORED
|
|
868
|
+
Spacing=Tight + Capitals=As typed -> text-transform: none obeyed
|
|
869
|
+
|
|
870
|
+
Capitalisation is not a spacing metric. `Capitals` is now the only thing on
|
|
871
|
+
the platform that writes a heading's case, which is what lets these two rungs
|
|
872
|
+
be split into a line gap and a text gap without either of them lying. DO NOT
|
|
873
|
+
ADD IT BACK: scripts/test-text-styles-panel.tsx §2 reads this very block and
|
|
874
|
+
fails on any `text-transform` in it, and it fails on a case in
|
|
875
|
+
lib/theme's `compressionMetrics` too, because the panel can tell the same lie
|
|
876
|
+
from the other end. A design that wants capitals has `Capitals: As designed`,
|
|
877
|
+
which is the default and is what every site is already on. */
|
|
878
|
+
[data-sf-headings="tight"] :is(h1, h2, h3, h4) {
|
|
879
|
+
line-height: 0.95;
|
|
880
|
+
letter-spacing: -0.01em;
|
|
881
|
+
}
|
|
882
|
+
[data-sf-headings="compressed"] :is(h1, h2, h3, h4) {
|
|
883
|
+
line-height: 0.86;
|
|
884
|
+
letter-spacing: -0.01em;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/* A part's own PARAGRAPH spacing on the editor canvas (plan S2). The cell
|
|
888
|
+
writes `--sf-part-para` on the part and flags it with `data-sf-part-para`; an
|
|
889
|
+
inline style cannot reach the part's line breaks, so these rules do. The gap
|
|
890
|
+
goes after each line except the last, list rows too; a blank line keeps its
|
|
891
|
+
height. The published page gets the same three rules from `paragraphRules`
|
|
892
|
+
in lib/puck/part-styles.ts — keep the two the same. A part without the flag
|
|
893
|
+
is untouched. */
|
|
894
|
+
[data-sf-part-para] br:not(br + br) {
|
|
895
|
+
display: block;
|
|
896
|
+
content: "";
|
|
897
|
+
margin-top: var(--sf-part-para);
|
|
898
|
+
}
|
|
899
|
+
[data-sf-part-para] :is([data-sf-bullet], [data-sf-number]):not(:first-child) {
|
|
900
|
+
margin-top: var(--sf-part-para);
|
|
901
|
+
}
|
|
902
|
+
[data-sf-part-para] :is([data-sf-bullet], [data-sf-number]):not(:last-child) {
|
|
903
|
+
margin-bottom: var(--sf-part-para);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/* ─── Header at-top (transparent) text colour ─────────────────────────
|
|
907
|
+
Before it goes solid on scroll, the fixed site Header paints its nav in
|
|
908
|
+
--sf-header-overlay-fg. Default = the PALETTE's ink, in both modes.
|
|
909
|
+
|
|
910
|
+
⚠️ IT USED TO BE THE MODE'S INK — cream `--overlay-fg` in dark, `--fg` in
|
|
911
|
+
light — and that split is the one the solid bar lost on 2026-09-08 (see
|
|
912
|
+
`.sf-theme-surfaces`). A theme is one flat palette (66238): its background
|
|
913
|
+
reaches the page in every mode, so a light palette in dark mode put cream
|
|
914
|
+
nav on a white page. Found 2026-10-07 on INOVIT /journal, and on SHARPEN's
|
|
915
|
+
404, where Next's error shell never runs the theme boot script, so even a
|
|
916
|
+
light-mode site has no `data-theme="light"` there. A page with no marker
|
|
917
|
+
leaves the body canvas under the bar, and the palette's ink is what reads on
|
|
918
|
+
it. `--fg` on <body> IS that ink; an unthemed site resolves it to the mode
|
|
919
|
+
base exactly as before (dark `--fg-base` is the same #f5f1e8 as
|
|
920
|
+
`--overlay-fg`). `scripts/test-chrome-theme-pair.ts` §6 holds it.
|
|
921
|
+
|
|
922
|
+
AUTO. A page publishes the ink its FIRST section DECLARES it needs, as
|
|
923
|
+
`data-lead-ink` on the page body (lib/puck/header-ink, read by both
|
|
924
|
+
StorefrontPageView and the editor preview). Cream over a band the section
|
|
925
|
+
itself paints cream on — a `dark` or `accent` surface, or a full-bleed hero
|
|
926
|
+
with its own dark legibility gradient — and nothing at all otherwise, so a
|
|
927
|
+
light content page keeps its near-black ink and cream-on-cream stays
|
|
928
|
+
impossible. A DECLARATION, never a sampled colour: see the module.
|
|
929
|
+
|
|
930
|
+
This replaced a hand-maintained list of route names plus a "does this page
|
|
931
|
+
open with a hero?" marker. Neither asked whether the thing under the header
|
|
932
|
+
was dark, so a dark section that was not a hero got ink-on-ink and no control
|
|
933
|
+
could fix it (card 66216).
|
|
934
|
+
|
|
935
|
+
`:has()` lets the header — a body child rendered ABOVE the page content —
|
|
936
|
+
react to a marker set DEEP inside the page body, so it stays pure CSS:
|
|
937
|
+
resolved at first paint, re-resolved on client navigation, with no boot
|
|
938
|
+
script and no React state driving the colour.
|
|
939
|
+
|
|
940
|
+
EVERY INK HAS ITS GROUND. Each rule that sets --sf-header-overlay-fg also
|
|
941
|
+
sets --sf-header-overlay-ground: the colour that ink was picked to sit ON
|
|
942
|
+
(cream ink -> near-black ground, near-black ink -> cream, --fg -> --bg). The
|
|
943
|
+
see-through bar has no background of its own, so this pair is the only
|
|
944
|
+
place its "background colour" exists. The cart badge draws its number in
|
|
945
|
+
it (components/HeaderTools, via --sf-header-ground, which Header and
|
|
946
|
+
BoxHeaderBar set inline). Add an ink rule without its ground and
|
|
947
|
+
scripts/test-cart-count-badge.tsx fails. */
|
|
948
|
+
.sf-site-header {
|
|
949
|
+
--sf-header-overlay-fg: var(--fg);
|
|
950
|
+
--sf-header-overlay-ground: var(--bg);
|
|
951
|
+
}
|
|
952
|
+
body:has([data-lead-ink="light"]) .sf-site-header {
|
|
953
|
+
--sf-header-overlay-fg: var(--overlay-fg);
|
|
954
|
+
--sf-header-overlay-ground: var(--overlay-fg-on-light);
|
|
955
|
+
}
|
|
956
|
+
body:has([data-lead-ink="dark"]) .sf-site-header {
|
|
957
|
+
--sf-header-overlay-fg: var(--overlay-fg-on-light);
|
|
958
|
+
--sf-header-overlay-ground: var(--overlay-fg);
|
|
959
|
+
}
|
|
960
|
+
/* THE MERCHANT'S OVERRIDE — Header → Look → "Text colour at the top". Auto
|
|
961
|
+
reads 99% of pages; this is the merchant taking over on the one it reads
|
|
962
|
+
wrong, so it has to beat Auto in BOTH directions, including the direction
|
|
963
|
+
Auto is confident about.
|
|
964
|
+
|
|
965
|
+
The leading `body ` is there for SPECIFICITY alone, and it is load-bearing:
|
|
966
|
+
the Auto rules above score (0,2,1) — one attribute inside :has(), one class,
|
|
967
|
+
one type — so a bare `.sf-site-header[data-header-ink]` at (0,2,0) would lose
|
|
968
|
+
to them and the control would silently do nothing.
|
|
969
|
+
`scripts/test-header-lead-ink.ts` §3b computes both and pins the winner. */
|
|
970
|
+
body .sf-site-header[data-header-ink="light"] {
|
|
971
|
+
--sf-header-overlay-fg: var(--overlay-fg);
|
|
972
|
+
--sf-header-overlay-ground: var(--overlay-fg-on-light);
|
|
973
|
+
}
|
|
974
|
+
body .sf-site-header[data-header-ink="dark"] {
|
|
975
|
+
--sf-header-overlay-fg: var(--overlay-fg-on-light);
|
|
976
|
+
--sf-header-overlay-ground: var(--overlay-fg);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/* ─── THE BOX HEADER ON A PHONE ───────────────────────────────────────
|
|
980
|
+
Blueprint step H.5, as the approved Box header mockup draws it. Under 801px
|
|
981
|
+
the row holding the site header's one-row Menu shows the logo at the left
|
|
982
|
+
and the rest together at the right, the menu button last; Search and the
|
|
983
|
+
region picker move into the menu's panel (components/PhoneMenuTools).
|
|
984
|
+
|
|
985
|
+
Only a row in the site header (`[data-section-group="header"]`) that holds a
|
|
986
|
+
one-row Menu (`[data-menu-row]`, components/FooterMenu). A page's Boxes, and
|
|
987
|
+
a header without such a Menu, keep their own `Columns on phone`.
|
|
988
|
+
|
|
989
|
+
⚠️ UNLAYERED ON PURPOSE. A Box's phone cells are Tailwind utilities
|
|
990
|
+
(lib/content/box-layout `PHONE_ROW_CLASS`) in `@layer utilities`, and
|
|
991
|
+
unlayered CSS beats layered whatever the specificity. */
|
|
992
|
+
@media (max-width: 800px) {
|
|
993
|
+
/* The row, and the rows inside it: one line, each block its own width. A
|
|
994
|
+
block is the row's own child on the site and sits inside the editor's
|
|
995
|
+
frame (`[data-puck-component]`) in the editor, so each rule reads both. */
|
|
996
|
+
[data-section-group="header"] .flex-row:has([data-menu-row]),
|
|
997
|
+
[data-section-group="header"] .flex-row:has([data-menu-row]) .flex-row {
|
|
998
|
+
flex-direction: row;
|
|
999
|
+
flex-wrap: nowrap;
|
|
1000
|
+
}
|
|
1001
|
+
[data-section-group="header"] .flex-row:has([data-menu-row]) > *,
|
|
1002
|
+
[data-section-group="header"] .flex-row:has([data-menu-row]) .flex-row > * {
|
|
1003
|
+
flex: 0 0 auto;
|
|
1004
|
+
/* A Box's frame is `w-full`; here it is as wide as what it holds. */
|
|
1005
|
+
width: auto;
|
|
1006
|
+
min-width: 0;
|
|
1007
|
+
}
|
|
1008
|
+
/* The menu button last, beside the cart. */
|
|
1009
|
+
[data-section-group="header"] .flex-row:has([data-menu-row]) > :is([data-menu-row], :has([data-menu-row])) {
|
|
1010
|
+
order: 99;
|
|
1011
|
+
}
|
|
1012
|
+
/* The first block (the logo) takes the room left over, and is the query
|
|
1013
|
+
container a long store name shrinks to fit: components/Logo
|
|
1014
|
+
`fitToContainer`, which the Logo block sets in the site header. */
|
|
1015
|
+
[data-section-group="header"] .flex-row:has([data-menu-row]) > :first-child:not([data-menu-row], :has([data-menu-row])) {
|
|
1016
|
+
flex: 1 1 0;
|
|
1017
|
+
container-type: inline-size;
|
|
1018
|
+
overflow-x: clip;
|
|
1019
|
+
}
|
|
1020
|
+
/* Search and the region picker are in the menu's panel instead: the block,
|
|
1021
|
+
and in the editor the frame around it, so no gap is left. Only the frame
|
|
1022
|
+
whose block IS the tool, never a Box frame that merely holds one. */
|
|
1023
|
+
[data-section-group="header"]:has([data-menu-row]) [data-phone-menu-tool],
|
|
1024
|
+
[data-section-group="header"]:has([data-menu-row]) [data-puck-component]:has(> * > [data-phone-menu-tool]) {
|
|
1025
|
+
display: none;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/* ─── THE BOX HEADER AT MID WIDTHS ────────────────────────────────────
|
|
1030
|
+
801px to 1500px: TWO ROWS, as the old header draws them from 801px
|
|
1031
|
+
(components/Header, its medium top bar). The logo and the tools on the top
|
|
1032
|
+
line, the one-row Menu's links on a second line under a hairline. Ruled by
|
|
1033
|
+
Victor Mau 2026-10-01: the approved mockup draws only 1440px (one row) and
|
|
1034
|
+
390px (the phone bar above), and a row that ran out of room folded the
|
|
1035
|
+
links onto two lines inside it. Same scope as the phone rules: only the
|
|
1036
|
+
site header's row that holds a one-row Menu. Unlayered for the same reason.
|
|
1037
|
+
The gap and the hairline are today's header's own: `gap-3`, `border-line/60`
|
|
1038
|
+
and `pt-2.5`.
|
|
1039
|
+
NOT for a group on `Same row` (the Site Header's Menu row, the old header's
|
|
1040
|
+
`menu_row: inline`, lib/content/header-bar): its links stay on the logo's
|
|
1041
|
+
row at every width above a phone.
|
|
1042
|
+
UP TO 1500px, NOT 1100px: Victor's rule ended at 1100px (the approved
|
|
1043
|
+
mockup is one row at 1440px). Lewis, 2026-10-12, card [ADD] Every Old
|
|
1044
|
+
Header Setting To The Box Header: `Own row` stacks exactly as the old
|
|
1045
|
+
header did, to 1500px, so a switched site looks the same at every width. */
|
|
1046
|
+
@media (min-width: 801px) and (max-width: 1500px) {
|
|
1047
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row:has([data-menu-row]) {
|
|
1048
|
+
flex-wrap: wrap;
|
|
1049
|
+
row-gap: 12px;
|
|
1050
|
+
}
|
|
1051
|
+
/* The links: their own line, last, under the hairline. */
|
|
1052
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row:has([data-menu-row]) > :is([data-menu-row], :has([data-menu-row])) {
|
|
1053
|
+
order: 99;
|
|
1054
|
+
flex: 1 0 100%;
|
|
1055
|
+
border-top: 1px solid color-mix(in oklab, var(--site-line) 60%, transparent);
|
|
1056
|
+
padding-top: 10px;
|
|
1057
|
+
}
|
|
1058
|
+
/* The logo takes the top line's room, so the tools sit at its end. */
|
|
1059
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row:has([data-menu-row]) > :first-child:not([data-menu-row], :has([data-menu-row])) {
|
|
1060
|
+
flex: 1 1 0;
|
|
1061
|
+
container-type: inline-size;
|
|
1062
|
+
overflow-x: clip;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/* ─── THE BOX HEADER'S LETTERS ────────────────────────────────────────
|
|
1067
|
+
The Site Header's `Letters` (the old header's `nav_text_case`,
|
|
1068
|
+
lib/content/header-bar): ONE value for the menu's links AND the tool words,
|
|
1069
|
+
which sit in one bar — Lewis asked for them controlled together
|
|
1070
|
+
(2026-09-03). Uppercase, the default, is the menu's own capitals and 0.18em
|
|
1071
|
+
tracking, worn by the tool words too, so a bar is never half capitalised.
|
|
1072
|
+
Capitalised takes both off and lets the words read as they are written —
|
|
1073
|
+
a removed transform, never `capitalize`, which would rewrite a merchant's
|
|
1074
|
+
`FusionForge`. The phone panel's links are inside the menu, so they follow.
|
|
1075
|
+
Unlayered: the menu's capitals are Tailwind utilities in `@layer
|
|
1076
|
+
utilities`, and unlayered CSS beats layered whatever the specificity. */
|
|
1077
|
+
[data-section-group="header"] .mono-eyebrow-ctl {
|
|
1078
|
+
/* The menu's own type (components/FooterMenu, its row: `font-mono`
|
|
1079
|
+
`text-[13px]`), so a word tool reads as part of the same strip, as the
|
|
1080
|
+
old bar's tools did. An icon tool draws no text, so nothing moves. */
|
|
1081
|
+
font-family: var(--font-role-label), var(--font-mono-fallback);
|
|
1082
|
+
font-size: 13px;
|
|
1083
|
+
text-transform: uppercase;
|
|
1084
|
+
letter-spacing: 0.18em;
|
|
1085
|
+
}
|
|
1086
|
+
[data-section-group="header"][data-header-nav-case="capitalised"] [data-menu-row] ul,
|
|
1087
|
+
[data-section-group="header"][data-header-nav-case="capitalised"] .mono-eyebrow-ctl {
|
|
1088
|
+
text-transform: none;
|
|
1089
|
+
letter-spacing: normal;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/* ─── THE BOX HEADER'S MENU ALIGNMENT ─────────────────────────────────
|
|
1093
|
+
Where the old header put its menu, as the page Menu's `Link alignment` and
|
|
1094
|
+
`Show links` (lib/content/menu-row; Lewis 2026-10-12, Q2 and Q4 option B).
|
|
1095
|
+
The bar stays ONE FLAT ROW — Logo, Menu, tools — so the phone and mid-width
|
|
1096
|
+
rules above keep working; these only place the menu where the bar is one
|
|
1097
|
+
row, and step aside where it stacks.
|
|
1098
|
+
|
|
1099
|
+
One row (801px up): a Menu with `Link alignment` takes the bar's free space
|
|
1100
|
+
and puts its links at its start, centre or end. `Center` gives the cells
|
|
1101
|
+
either side equal shares, so the links sit on the bar's centre (the old
|
|
1102
|
+
1fr auto 1fr). A link outside a Menu's `Show links` range is hidden.
|
|
1103
|
+
|
|
1104
|
+
A LEAD part — a ranged Menu that stops before the last link, left of a
|
|
1105
|
+
centred logo — makes the bar three cells: the lead and the cell after the
|
|
1106
|
+
logo share the bar equally, the logo keeps its width. Where the bar stacks
|
|
1107
|
+
(a phone, and a laptop on Own row) the lead steps aside and the Menu after
|
|
1108
|
+
the logo shows EVERY link, its Box opened (`display: contents`) so its Menu
|
|
1109
|
+
and tools are the bar's own: one menu, one button, as the old header.
|
|
1110
|
+
|
|
1111
|
+
Unlayered, like the rules above, and after them where they meet: the
|
|
1112
|
+
mid-width rules are more specific and win the cells they place. */
|
|
1113
|
+
@media (min-width: 801px) {
|
|
1114
|
+
[data-section-group="header"] .flex-row > :is([data-menu-align], :has([data-menu-align])) {
|
|
1115
|
+
flex: 1 1 0%;
|
|
1116
|
+
min-width: 0;
|
|
1117
|
+
}
|
|
1118
|
+
/* Start and End fill the cell they grow into; a centred menu is as wide as
|
|
1119
|
+
its links, so the links sit on the bar's centre. */
|
|
1120
|
+
[data-section-group="header"] [data-menu-row]:is([data-menu-align="start"], [data-menu-align="end"]) {
|
|
1121
|
+
width: 100%;
|
|
1122
|
+
}
|
|
1123
|
+
[data-section-group="header"] [data-menu-row][data-menu-align="start"] > ul {
|
|
1124
|
+
justify-content: flex-start;
|
|
1125
|
+
}
|
|
1126
|
+
[data-section-group="header"] [data-menu-row][data-menu-align="center"] > ul {
|
|
1127
|
+
justify-content: center;
|
|
1128
|
+
}
|
|
1129
|
+
[data-section-group="header"] [data-menu-row][data-menu-align="end"] > ul {
|
|
1130
|
+
justify-content: flex-end;
|
|
1131
|
+
}
|
|
1132
|
+
/* Centred links: the cells either side share the rest equally. */
|
|
1133
|
+
/* NOT `min-width: 0`: a side cell never shrinks below what it holds, as
|
|
1134
|
+
the old grid's `1fr` columns did not, so a wide logo pushes the links
|
|
1135
|
+
over rather than running under them. */
|
|
1136
|
+
[data-section-group="header"] .flex-row:has([data-menu-align="center"]) > :not([data-menu-align]):not(:has([data-menu-align])) {
|
|
1137
|
+
flex: 1 1 0%;
|
|
1138
|
+
}
|
|
1139
|
+
[data-section-group="header"] .flex-row > :is([data-menu-align="center"], :has([data-menu-align="center"])) {
|
|
1140
|
+
flex: 0 1 auto;
|
|
1141
|
+
}
|
|
1142
|
+
[data-section-group="header"] [data-menu-row] li[data-menu-out] {
|
|
1143
|
+
display: none;
|
|
1144
|
+
}
|
|
1145
|
+
/* A centred logo: the cell after the lead part keeps its own width. */
|
|
1146
|
+
[data-section-group="header"] .flex-row > :is([data-menu-part="lead"], :has([data-menu-part="lead"])) + * {
|
|
1147
|
+
flex: 0 0 auto;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
@media (max-width: 800px) {
|
|
1151
|
+
[data-section-group="header"] .flex-row > :is([data-menu-part="lead"], :has([data-menu-part="lead"])) {
|
|
1152
|
+
display: none;
|
|
1153
|
+
}
|
|
1154
|
+
[data-section-group="header"] .flex-row > :is([data-menu-part="lead"], :has([data-menu-part="lead"])) + * {
|
|
1155
|
+
flex: 1 1 0;
|
|
1156
|
+
container-type: inline-size;
|
|
1157
|
+
overflow-x: clip;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
@media (min-width: 801px) and (max-width: 1500px) {
|
|
1161
|
+
/* Own row stacks: the links start their own line, as the old second row. */
|
|
1162
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) [data-menu-row][data-menu-align] > ul {
|
|
1163
|
+
justify-content: flex-start;
|
|
1164
|
+
}
|
|
1165
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) [data-menu-row] li[data-menu-out] {
|
|
1166
|
+
display: list-item;
|
|
1167
|
+
}
|
|
1168
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row > :is([data-menu-part="lead"], :has([data-menu-part="lead"])) {
|
|
1169
|
+
display: none;
|
|
1170
|
+
}
|
|
1171
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row > :is([data-menu-part="lead"], :has([data-menu-part="lead"])) + * {
|
|
1172
|
+
flex: 1 1 0;
|
|
1173
|
+
container-type: inline-size;
|
|
1174
|
+
overflow-x: clip;
|
|
1175
|
+
}
|
|
1176
|
+
/* The old stacked bar drew Search FIRST among its tools, whatever its
|
|
1177
|
+
Search position (components/Header, the medium band); the position only
|
|
1178
|
+
ever applied to the one-row bar. The logo stays ahead of it where a
|
|
1179
|
+
centred logo's Box is opened below. */
|
|
1180
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) [data-phone-menu-tool="search"] > *,
|
|
1181
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) [data-puck-component]:has(> * > [data-phone-menu-tool="search"]) {
|
|
1182
|
+
order: -1;
|
|
1183
|
+
}
|
|
1184
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row > :is([data-menu-part="lead"], :has([data-menu-part="lead"])) + * {
|
|
1185
|
+
order: -2;
|
|
1186
|
+
}
|
|
1187
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row:has([data-menu-part="lead"]) > :has([data-menu-row]):not([data-menu-row]):not(:has([data-menu-part="lead"])),
|
|
1188
|
+
[data-section-group="header"]:not([data-header-menu-row="inline"]) .flex-row:has([data-menu-part="lead"]) > :has([data-menu-row]):not([data-menu-row]):not(:has([data-menu-part="lead"])) :has([data-menu-row]):not([data-menu-row]) {
|
|
1189
|
+
display: contents;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/* ─── Header TOP SPACE ────────────────────────────────────────────────
|
|
1194
|
+
The page's own "Top space" (Page settings → `Top space`; the resolver is
|
|
1195
|
+
lib/puck/header-clearance). A page that takes its own space publishes
|
|
1196
|
+
`data-top-space="own"` on its body, and the bar above it paints a real
|
|
1197
|
+
background from the first pixel instead of being see-through.
|
|
1198
|
+
|
|
1199
|
+
⚠️ THIS IS THE MAP/PAINT SPLIT THIS CARD OWED AN ANSWER FOR, and the answer
|
|
1200
|
+
is that the header does NOT wear `.sf-section-accent`. That class both MAPS
|
|
1201
|
+
the theme tokens and PAINTS with them (`background: var(--bg)` at its end),
|
|
1202
|
+
and a bar that must stay see-through at the top of most pages cannot wear a
|
|
1203
|
+
rule that paints unconditionally. So the MAPPING stays where it already is —
|
|
1204
|
+
the theme tokens the header inherits from the carrier — and the PAINT is
|
|
1205
|
+
these three declarations, which apply only to a page that asked for them.
|
|
1206
|
+
|
|
1207
|
+
WHY THE INK GOES THROUGH THE VARIABLE. <Header/> sets `color:
|
|
1208
|
+
var(--sf-header-overlay-fg)` INLINE while it is not solid, and an inline
|
|
1209
|
+
style cannot be beaten from a stylesheet. Re-pointing the variable — the same
|
|
1210
|
+
indirection the Auto/override rules above already use — lands the theme ink
|
|
1211
|
+
without a specificity fight and without touching the component.
|
|
1212
|
+
|
|
1213
|
+
The border is the one declaration that IS a fight: the bar carries Tailwind's
|
|
1214
|
+
`border-transparent` at (0,1,0) while not solid, and this selector scores
|
|
1215
|
+
(0,2,1) — `body` type + the attribute inside `:has()` + the class. That is
|
|
1216
|
+
the same arithmetic `scripts/test-header-lead-ink.ts` §3b pins for the ink
|
|
1217
|
+
override, and `scripts/test-header-top-space.ts` pins it for this rule.
|
|
1218
|
+
|
|
1219
|
+
ON SCROLL NOTHING HERE APPLIES ANY MORE and that is correct: <Header/> goes
|
|
1220
|
+
solid and sets background and colour inline, which beat this block. Solid is
|
|
1221
|
+
solid either way, so the bar does not change appearance as the reader
|
|
1222
|
+
scrolls — it was already painted. */
|
|
1223
|
+
body:has([data-top-space="own"]) .sf-site-header {
|
|
1224
|
+
--sf-header-overlay-fg: var(--fg);
|
|
1225
|
+
--sf-header-overlay-ground: var(--bg);
|
|
1226
|
+
background: var(--bg);
|
|
1227
|
+
border-bottom-color: var(--site-line);
|
|
1228
|
+
backdrop-filter: blur(12px);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/* ─── The page's WIDTH reaching the chrome ────────────────────────────────
|
|
1232
|
+
A page set to `Full width` publishes `data-page-width="full"` on its own
|
|
1233
|
+
wrapper, and the bar above it plus the footer below it stop capping — so
|
|
1234
|
+
the three sit on one column, or run edge to edge, together.
|
|
1235
|
+
|
|
1236
|
+
⚠️ WHY `:has()` AND NOT A VARIABLE INHERITED DOWN THE TREE. The header is a
|
|
1237
|
+
SIBLING of the page body, not an ancestor of it — measured in the browser on
|
|
1238
|
+
2026-09-09: a custom property published on the page's wrapper resolves to
|
|
1239
|
+
`none` inside the header, every time. The carrier that DOES wrap all three
|
|
1240
|
+
(`.sf-theme-accent`) is rendered in app/layout.tsx, and a Next layout cannot
|
|
1241
|
+
read the pathname (see the note above `isEditorShellRequest`), so it cannot
|
|
1242
|
+
know which page it is wrapping. `:has()` is the one thing that reaches back
|
|
1243
|
+
up and across, and card 66514 already proved it here for the header's ink.
|
|
1244
|
+
|
|
1245
|
+
⚠️ AND WHY IT RE-POINTS A VARIABLE RATHER THAN SETTING `max-width`. The cap is
|
|
1246
|
+
an INLINE style (`siteWidthCapStyle`), which no stylesheet can beat. Setting
|
|
1247
|
+
the variable it reads is not a specificity trick, it is the only way in.
|
|
1248
|
+
|
|
1249
|
+
`none` rather than a wider number: `Full width` means no cap at all, and a
|
|
1250
|
+
number would be a second opinion about how wide "no cap" is.
|
|
1251
|
+
|
|
1252
|
+
⛔ A BAR THAT ANSWERED FOR ITSELF IS NOT LISTENING. Header and Footer each
|
|
1253
|
+
have their own Width row; when one is set to `Content width` the component
|
|
1254
|
+
writes the cap literally instead of through the variable, so this rule
|
|
1255
|
+
cannot move it. That is the three-level rule made mechanical: the level that
|
|
1256
|
+
spoke wins, and silence means follow. */
|
|
1257
|
+
body:has([data-page-width="full"]) .sf-site-header,
|
|
1258
|
+
body:has([data-page-width="full"]) footer {
|
|
1259
|
+
--sf-chrome-cap: none;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
@theme inline {
|
|
1263
|
+
--color-bg: var(--bg);
|
|
1264
|
+
--color-surface: var(--site-surface);
|
|
1265
|
+
--color-surface-2: var(--site-surface-2);
|
|
1266
|
+
--color-fg: var(--fg);
|
|
1267
|
+
--color-fg-dim: var(--fg-dim);
|
|
1268
|
+
--color-line: var(--site-line);
|
|
1269
|
+
--color-accent: var(--accent);
|
|
1270
|
+
--color-accent-2: var(--accent-2);
|
|
1271
|
+
--color-accent-ink: var(--accent-ink);
|
|
1272
|
+
/* The button's own fill/ink trio (card 66504), so `bg-btn` / `text-btn-ink`
|
|
1273
|
+
compile. Each resolves to the accent unless a theme carries a button
|
|
1274
|
+
colour — the chain is in `.sf-theme-accent` and its siblings above. */
|
|
1275
|
+
--color-btn: var(--btn);
|
|
1276
|
+
--color-btn-2: var(--btn-2);
|
|
1277
|
+
--color-btn-ink: var(--btn-ink);
|
|
1278
|
+
--color-overlay-fg: var(--overlay-fg);
|
|
1279
|
+
--color-green: var(--green);
|
|
1280
|
+
--color-alert: var(--alert);
|
|
1281
|
+
--color-overlay-scrim: var(--overlay-scrim);
|
|
1282
|
+
--color-overlay-chrome: var(--overlay-chrome);
|
|
1283
|
+
--color-plate-band: var(--plate-band-bg);
|
|
1284
|
+
--color-plate-band-fg: var(--plate-band-fg);
|
|
1285
|
+
--color-plate-band-fg-dim: var(--plate-band-fg-dim);
|
|
1286
|
+
|
|
1287
|
+
/* font families — resolve through the swappable --font-role-* tokens
|
|
1288
|
+
(defined on :root above, default to the built-in faces) so the
|
|
1289
|
+
editor's Fonts control can remap them per site. */
|
|
1290
|
+
--font-display: var(--font-role-heading), "Inter", system-ui, sans-serif;
|
|
1291
|
+
/* Heading 2 and Heading 3 each get a utility of their own, so a section can
|
|
1292
|
+
DRAW the face those rows set. Until this existed they could not: every
|
|
1293
|
+
heading on the site wrote `font-display`, which resolves
|
|
1294
|
+
`--font-role-heading` — Heading 1's face — while its size token said
|
|
1295
|
+
`--size-headingMedium`. Measured on main before this change, 43 lines took
|
|
1296
|
+
their size from Heading 2 and their face from Heading 1, and 30 did the
|
|
1297
|
+
same for Heading 3, so BOTH Font controls in Site styles saved a value and
|
|
1298
|
+
moved zero pixels.
|
|
1299
|
+
|
|
1300
|
+
⚠️ THE FALLBACK TAIL IS THE SAME AS `--font-display`'s ON PURPOSE. These
|
|
1301
|
+
are the same typographic slot at a different rank, not a different family,
|
|
1302
|
+
and a heading that fell back to a different stack while the webfont loaded
|
|
1303
|
+
would flash a face nobody chose.
|
|
1304
|
+
|
|
1305
|
+
⚠️ SWAPPING `font-display` FOR THESE MOVES NO PIXELS TODAY.
|
|
1306
|
+
`--font-role-heading2` and `--font-role-heading3` are declared on :root as
|
|
1307
|
+
`var(--font-role-heading)`, and `normalizeFonts` resolves an unset
|
|
1308
|
+
heading2/heading3 back to Heading 1's key, so a site that has never opened
|
|
1309
|
+
those two rows renders exactly as it did. What changes is that opening
|
|
1310
|
+
them now does something. */
|
|
1311
|
+
--font-heading2: var(--font-role-heading2), "Inter", system-ui, sans-serif;
|
|
1312
|
+
--font-heading3: var(--font-role-heading3), "Inter", system-ui, sans-serif;
|
|
1313
|
+
--font-body: var(--font-role-body), system-ui, sans-serif;
|
|
1314
|
+
/* Fraunces (default) — editorial italic serif, used by the Journal
|
|
1315
|
+
page for gold-italic emphasis in headlines + pull-quotes. Falls
|
|
1316
|
+
back to Georgia so the cascade never bare-metals to Times New
|
|
1317
|
+
Roman. The accent role is swappable via the Fonts control. */
|
|
1318
|
+
--font-serif: var(--font-role-accent), Georgia, "Iowan Old Style", serif;
|
|
1319
|
+
/* Space Mono web font (loaded by next/font in layout.tsx) is the
|
|
1320
|
+
primary face — same family lineage as Space Grotesk, identical
|
|
1321
|
+
look on every OS. The OS-mono cascade sits behind it as graceful
|
|
1322
|
+
fallback while the webfont is loading or if it fails to load. */
|
|
1323
|
+
--font-mono: var(--font-role-label), var(--font-mono-fallback);
|
|
1324
|
+
|
|
1325
|
+
/* Tailwind's radius scale, pointed at the SITE'S corners — the `Corners`
|
|
1326
|
+
block above says why it is `--site-radius-*` and where the defaults live. */
|
|
1327
|
+
--radius-xs: var(--site-radius-xs);
|
|
1328
|
+
--radius-sm: var(--site-radius-sm);
|
|
1329
|
+
--radius-md: var(--site-radius-md);
|
|
1330
|
+
--radius-lg: var(--site-radius-lg);
|
|
1331
|
+
--radius-xl: var(--site-radius-xl);
|
|
1332
|
+
--radius-2xl: var(--site-radius-2xl);
|
|
1333
|
+
--radius-3xl: var(--site-radius-3xl);
|
|
1334
|
+
--radius-4xl: var(--site-radius-4xl);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
* {
|
|
1338
|
+
box-sizing: border-box;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/* Layered — see the element-reset block below for why every bare-element
|
|
1342
|
+
rule in this file must sit inside `@layer base`. */
|
|
1343
|
+
@layer base {
|
|
1344
|
+
html,
|
|
1345
|
+
body {
|
|
1346
|
+
background: var(--bg);
|
|
1347
|
+
color: var(--fg);
|
|
1348
|
+
/* Body copy face — via the swappable body role (defaults to Inter). The
|
|
1349
|
+
editor's Fonts control re-declares font-family on the page wrapper so
|
|
1350
|
+
content re-resolves to the chosen face; this keeps chrome outside the
|
|
1351
|
+
wrapper (e.g. the header) on the default body face. */
|
|
1352
|
+
font-family: var(--font-role-body);
|
|
1353
|
+
-webkit-font-smoothing: antialiased;
|
|
1354
|
+
/* Theme switch should feel instant — the long 0.4s fade read as lag. Keep a
|
|
1355
|
+
hair of easing so it's not a hard cut. */
|
|
1356
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/* Smooth-scroll for in-page hash anchors (header nav: /wheels#fusionforge etc.) */
|
|
1360
|
+
html {
|
|
1361
|
+
scroll-behavior: smooth;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/* The header's DRAWN height in px — what the bar actually measures on screen,
|
|
1366
|
+
which is the merchant's "Header height" OR the bar's own content, whichever is
|
|
1367
|
+
taller. <Header/> publishes it from a ResizeObserver on the bar. The
|
|
1368
|
+
body-clearance, the section-anchor offset, the bars that pin under the header
|
|
1369
|
+
and every `calc(100svh - header)` all read it, so page content never slides
|
|
1370
|
+
under a taller — or floats below a shorter — bar.
|
|
1371
|
+
|
|
1372
|
+
⚠️ NOT the slider value. That is `--sf-header-set-h` below, and only the bar's
|
|
1373
|
+
own min-height may read it. The two were one var until 2026-09-16, and a bar
|
|
1374
|
+
that had grown past the slider (a tall logo, or the stacked two-row layout
|
|
1375
|
+
between 801px and 1500px) hid the top of the page under itself by the
|
|
1376
|
+
difference. See lib/puck/header-clearance.ts.
|
|
1377
|
+
|
|
1378
|
+
Default = the historical fixed header height, so the first paint of a site
|
|
1379
|
+
whose content fits is byte-identical to what it has always been. Keep it in
|
|
1380
|
+
step with HEADER_HEIGHT_PX.default (lib/puck/header-clearance.ts). */
|
|
1381
|
+
:root {
|
|
1382
|
+
--sf-header-h: 88px;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/* The merchant's CHOSEN header height — the Appearance → "Header height" slider,
|
|
1386
|
+
published per site by app/layout.tsx (on <body>) and by <Header/> (on <html>).
|
|
1387
|
+
Read by the bar's own `min-height` and by NOTHING else: a taller bar must still
|
|
1388
|
+
be allowed to grow, or the stacked two-row header would clip its own menu.
|
|
1389
|
+
Keep this default in step with HEADER_HEIGHT_PX.default. */
|
|
1390
|
+
:root {
|
|
1391
|
+
--sf-header-set-h: 88px;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/* The site's content column, in px — how wide this whole website is. Default =
|
|
1395
|
+
the width every storefront rendered at while it was hard-coded; app/layout.tsx
|
|
1396
|
+
overrides it per site (on the page content wrapper) from the site's theme row,
|
|
1397
|
+
and every page-width cap reads it as `max-w-[var(--sf-site-width,1600px)]`. A
|
|
1398
|
+
site that has set no width publishes nothing, so the cap takes its own
|
|
1399
|
+
fallback and lands right back on this number. Keep this default in step with
|
|
1400
|
+
PAGE_WIDTH.default (lib/content/page-width.ts) — scripts/test-site-width.ts
|
|
1401
|
+
reads it out of this file and fails if the two drift. NOT the same value as
|
|
1402
|
+
the per-PAGE `--sf-page-width`, which the page builder's own Width control
|
|
1403
|
+
publishes lower down for the page it is on. */
|
|
1404
|
+
:root {
|
|
1405
|
+
--sf-site-width: 1600px;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/* Offset a page body below the fixed Header (HEADER_CLEARANCE_CLASS). Reads the
|
|
1409
|
+
live header height so the gap tracks the merchant's chosen "Header height"
|
|
1410
|
+
instead of a fixed 88px; the fallback keeps SSR's first paint correct before
|
|
1411
|
+
<Header/>'s effect publishes the real value. */
|
|
1412
|
+
.sf-header-clearance {
|
|
1413
|
+
padding-top: var(--sf-header-h, 88px);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/* Bars that stick directly under the site header (STICKY_UNDERHEADER_CLASS — the
|
|
1417
|
+
Product Grid section header, the Journal category bar, the document-tabs bar).
|
|
1418
|
+
Each pins at the live header height on its own (`top-[var(--sf-header-h,88px)]`);
|
|
1419
|
+
this rule handles WHEN it should pin. By default they pin while the page scrolls,
|
|
1420
|
+
which only reads right when the site header itself stays pinned. <Header/>
|
|
1421
|
+
publishes its sticky mode as `data-header-sticky` on <html>; when the header is
|
|
1422
|
+
set to scroll away ("none") or to hide on scroll-down ("on_scroll_up"), a bar
|
|
1423
|
+
pinned at the top would hang over the gap the departed/hidden header left — so
|
|
1424
|
+
un-pin it and let it scroll away with the content. Absent attribute (first paint,
|
|
1425
|
+
or the default "always") keeps the pin, so the common case is unchanged. */
|
|
1426
|
+
:root[data-header-sticky="none"] .sf-sticky-underheader,
|
|
1427
|
+
:root[data-header-sticky="on_scroll_up"] .sf-sticky-underheader {
|
|
1428
|
+
position: static;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/* Stick to top — the pin any section or card can be given (efficient-web-builder
|
|
1432
|
+
lib/content/section-sticky.ts, card [ADD] Every Section Can Stick To The Top).
|
|
1433
|
+
The pass wraps a node that sticks in `.sf-section-stick` and publishes the
|
|
1434
|
+
distance below the header as `--sf-stick-distance` (and `-phone`); nothing
|
|
1435
|
+
is emitted for a node that does not stick, so these rules match nothing on
|
|
1436
|
+
a page that never used it.
|
|
1437
|
+
|
|
1438
|
+
It stops under the live header height, the same `--sf-header-h` the
|
|
1439
|
+
under-header bars use. When the header does not stay pinned — the header
|
|
1440
|
+
publishes `data-header-sticky` "none" or "on_scroll_up" on <html> — there
|
|
1441
|
+
is no header to stop under, so the stop is the screen top. */
|
|
1442
|
+
.sf-section-stick {
|
|
1443
|
+
position: sticky;
|
|
1444
|
+
top: calc(var(--sf-header-h, 88px) + var(--sf-stick-distance, 0px));
|
|
1445
|
+
}
|
|
1446
|
+
.sf-section-stick.sf-section-stick-desktop-off {
|
|
1447
|
+
position: static;
|
|
1448
|
+
}
|
|
1449
|
+
:root[data-header-sticky="none"] .sf-section-stick,
|
|
1450
|
+
:root[data-header-sticky="on_scroll_up"] .sf-section-stick {
|
|
1451
|
+
top: var(--sf-stick-distance, 0px);
|
|
1452
|
+
}
|
|
1453
|
+
/* What comes after a pinned node slides OVER it. A sticky box is positioned,
|
|
1454
|
+
and a positioned box paints above the plain boxes that follow it, so the
|
|
1455
|
+
next section would slide UNDER the pinned one. Giving the followers a
|
|
1456
|
+
position of their own puts them back in page order. Only siblings of a pin
|
|
1457
|
+
are touched, so a page with no pin is unchanged. `:where()` makes the rule
|
|
1458
|
+
weigh nothing: a follower that sets its own position (the Category bar's
|
|
1459
|
+
own `sticky`, an `absolute` layer) keeps it. */
|
|
1460
|
+
:where(.sf-section-stick ~ :not(.sf-section-stick)) {
|
|
1461
|
+
position: relative;
|
|
1462
|
+
}
|
|
1463
|
+
/* The phone's own answer (below 720px, VIEW_TABLET_MIN, written as in the
|
|
1464
|
+
phone padding block below). */
|
|
1465
|
+
@media not all and (min-width: 720px) {
|
|
1466
|
+
.sf-section-stick.sf-section-stick-phone-on {
|
|
1467
|
+
position: sticky;
|
|
1468
|
+
top: calc(var(--sf-header-h, 88px) + var(--sf-stick-distance-phone, 0px));
|
|
1469
|
+
}
|
|
1470
|
+
.sf-section-stick.sf-section-stick-phone-off {
|
|
1471
|
+
position: static;
|
|
1472
|
+
}
|
|
1473
|
+
:root[data-header-sticky="none"] .sf-section-stick.sf-section-stick-phone-on,
|
|
1474
|
+
:root[data-header-sticky="on_scroll_up"] .sf-section-stick.sf-section-stick-phone-on {
|
|
1475
|
+
top: var(--sf-stick-distance-phone, 0px);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/* Section height — the "Height" field on any Puck section (card 66215). The
|
|
1480
|
+
injected pass wraps a section that sets one in this class and publishes the
|
|
1481
|
+
length as a custom property; the property is what varies per section, so the
|
|
1482
|
+
rule itself lives here once.
|
|
1483
|
+
|
|
1484
|
+
MIN-height, never height, and no overflow anywhere in this block: content
|
|
1485
|
+
taller than the number the merchant chose makes the section GROW (the owner's
|
|
1486
|
+
ruling — clipping would hide a merchant's own words with no sign of it in the
|
|
1487
|
+
editor). `display:grid` on the wrapper (set inline, beside the property) is
|
|
1488
|
+
what makes the section STRETCH into the height rather than sit at the top of
|
|
1489
|
+
an empty box; a grid row still grows past its minimum when the content is
|
|
1490
|
+
taller, so stretching costs nothing at the clip end.
|
|
1491
|
+
|
|
1492
|
+
TWO DECLARATIONS, because one React style object cannot carry `min-height`
|
|
1493
|
+
twice. The first is in `vh` and every browser understands it. The second is in
|
|
1494
|
+
`svh` — the SMALL viewport height, which accounts for the mobile address bar
|
|
1495
|
+
the old `vh` ignores — and only browsers that have the unit enter the
|
|
1496
|
+
@supports block to take it. A section whose length carries no viewport unit at
|
|
1497
|
+
all (Fixed in px) publishes only the base property, so the inner var() names
|
|
1498
|
+
the base as its own fallback rather than resolving to nothing. */
|
|
1499
|
+
.sf-section-minh {
|
|
1500
|
+
min-height: var(--sf-section-minh);
|
|
1501
|
+
}
|
|
1502
|
+
@supports (min-height: 1svh) {
|
|
1503
|
+
.sf-section-minh {
|
|
1504
|
+
min-height: var(--sf-section-minh-s, var(--sf-section-minh));
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/* Section anchors — the "Anchor" field on any Puck section. When set,
|
|
1509
|
+
the section renders an invisible, zero-height <span id> as its first child
|
|
1510
|
+
(see SectionAnchor in lib/puck/config.tsx). `display:block; height:0` makes it
|
|
1511
|
+
a real scroll target that adds no layout box; `scroll-margin-top` clears the
|
|
1512
|
+
fixed header — now the live --sf-header-h (fallback 88px) — so a
|
|
1513
|
+
/<page>#<anchor> deep-link lands with the section below the header, not under
|
|
1514
|
+
it — the same JS-free path as the /wheels collection anchors above. */
|
|
1515
|
+
.sf-section-anchor {
|
|
1516
|
+
display: block;
|
|
1517
|
+
height: 0;
|
|
1518
|
+
scroll-margin-top: var(--sf-header-h, 88px);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/* Hero slider — never show the horizontal scroll-snap scrollbar. Covers the
|
|
1522
|
+
track itself AND any descendant, because in the page-builder Puck wraps the
|
|
1523
|
+
slot children, so the actual scroll container can be a nested element the
|
|
1524
|
+
utility classes on `.hero-track` don't reach. */
|
|
1525
|
+
.hero-track,
|
|
1526
|
+
.hero-track * {
|
|
1527
|
+
scrollbar-width: none; /* Firefox */
|
|
1528
|
+
-ms-overflow-style: none; /* legacy Edge */
|
|
1529
|
+
}
|
|
1530
|
+
.hero-track::-webkit-scrollbar,
|
|
1531
|
+
.hero-track *::-webkit-scrollbar {
|
|
1532
|
+
display: none; /* Chrome / Safari */
|
|
1533
|
+
width: 0;
|
|
1534
|
+
height: 0;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
/* ─── Element resets ─────────────────────────────────────────────────
|
|
1538
|
+
EVERY bare-element reset in this file lives inside `@layer base`, and
|
|
1539
|
+
new ones must too. An UNLAYERED rule beats every layered one no matter
|
|
1540
|
+
how specific, and Tailwind's utilities live in `@layer utilities` — so
|
|
1541
|
+
a top-level `a { color: inherit }` silently outranks `text-accent`,
|
|
1542
|
+
`underline`, `hover:text-bg`… on that element. The class is still in
|
|
1543
|
+
the markup, still in the generated CSS, and still does nothing: the
|
|
1544
|
+
failure is invisible until someone measures a computed colour.
|
|
1545
|
+
|
|
1546
|
+
That is not hypothetical. It cost <button> its font/background/border
|
|
1547
|
+
once (fixed by layering it, below), and it had swallowed the colour on
|
|
1548
|
+
every <a> in the storefront — 74 links across 44 files carrying a
|
|
1549
|
+
`text-*` / `underline` utility that never applied.
|
|
1550
|
+
|
|
1551
|
+
`scripts/test-css-layers.ts` fails the build if a bare-element reset
|
|
1552
|
+
reappears at top level. */
|
|
1553
|
+
@layer base {
|
|
1554
|
+
a {
|
|
1555
|
+
color: inherit;
|
|
1556
|
+
text-decoration: none;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
img {
|
|
1560
|
+
display: block;
|
|
1561
|
+
max-width: 100%;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
button {
|
|
1565
|
+
font: inherit;
|
|
1566
|
+
color: inherit;
|
|
1567
|
+
background: none;
|
|
1568
|
+
border: 0;
|
|
1569
|
+
cursor: pointer;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/* A HEADING MAY ALWAYS SHRINK TO THE BOX IT IS IN, AND BREAK A WORD THAT IS
|
|
1573
|
+
WIDER THAN THAT BOX. (Card [FIX] Big Text Spills Out Of A Section.)
|
|
1574
|
+
|
|
1575
|
+
A flex item defaults to `min-width: auto`: it refuses to be narrower than
|
|
1576
|
+
its own longest WORD. That is invisible while a section draws a heading at
|
|
1577
|
+
the size it was designed at, and it is the whole defect the moment a
|
|
1578
|
+
merchant raises that size — which they can, from Site styles, from a part's
|
|
1579
|
+
own Text style, and (since the Booking Form's four text kinds) from a
|
|
1580
|
+
section's own control. Measured on the Booking Form, 2026-09-10: its step
|
|
1581
|
+
label is an `<h3>` beside a numbered circle in `flex items-center gap-3`,
|
|
1582
|
+
and pointed at Heading 1 it laid out 470px wide in a 384px row and pushed
|
|
1583
|
+
the page's scroll width from 992 to 1081. Nothing clipped it with
|
|
1584
|
+
`overflow` — the page simply grew wider than the viewport and the tail was
|
|
1585
|
+
off-screen, which reads to a merchant as "cut off at the card's edge".
|
|
1586
|
+
|
|
1587
|
+
⚠️ WRAP AND GROW, NEVER CAP. Capping how far a run may grow would undo the
|
|
1588
|
+
owner's 2026-09-05 ruling ("following a style brings the size too") one
|
|
1589
|
+
section at a time. A cap already exists and is the merchant's to set —
|
|
1590
|
+
`PartStyle.width`, "how far the part may run, in px". This makes the
|
|
1591
|
+
DEFAULT honest: the words wrap, the section gets taller, nothing is lost.
|
|
1592
|
+
|
|
1593
|
+
⚠️ BOTH DECLARATIONS OR NEITHER. Once the item may shrink, a single word
|
|
1594
|
+
wider than the box still runs out of it; `break-word` breaks a word only
|
|
1595
|
+
when it cannot fit a line of its own, so every heading that fits today is
|
|
1596
|
+
drawn exactly as it is today.
|
|
1597
|
+
|
|
1598
|
+
⚠️ HEADINGS ONLY, AND THAT IS A SCOPE RATHER THAN AN OVERSIGHT. A blanket
|
|
1599
|
+
`.flex > * { min-width: 0 }` would reach 346 flex rows and let icons and
|
|
1600
|
+
fixed-size boxes squash under pressure they currently resist. A run that is
|
|
1601
|
+
NOT a heading is reached by the other half of this fix, which is keyed to
|
|
1602
|
+
the thing that actually makes it big: `partStyleDeclarations` in
|
|
1603
|
+
`lib/puck/part-styles.ts` writes the same two properties whenever it states
|
|
1604
|
+
a size. Together they cover both ways a run can outgrow its box.
|
|
1605
|
+
|
|
1606
|
+
Layered, like every bare-element rule in this file — so a section that
|
|
1607
|
+
really does want `min-w-*` or `whitespace-nowrap` on a heading still wins
|
|
1608
|
+
with a utility. `scripts/test-css-layers.ts` fails the build if it is not. */
|
|
1609
|
+
h1,
|
|
1610
|
+
h2,
|
|
1611
|
+
h3,
|
|
1612
|
+
h4,
|
|
1613
|
+
h5,
|
|
1614
|
+
h6 {
|
|
1615
|
+
min-width: 0;
|
|
1616
|
+
overflow-wrap: break-word;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/* Visible focus ring on dark + light themes */
|
|
1621
|
+
:focus-visible {
|
|
1622
|
+
outline: 2px solid var(--accent);
|
|
1623
|
+
outline-offset: 2px;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
/* Native <select> dropdown options + popup panel.
|
|
1627
|
+
Theme-aware:
|
|
1628
|
+
dark theme → option panel = black bg, white text
|
|
1629
|
+
light theme → option panel = white bg, black text
|
|
1630
|
+
`color-scheme` on the select itself tells the browser which OS
|
|
1631
|
+
chrome to render (Chrome/Edge/Safari respect this for the popup
|
|
1632
|
+
panel background). The explicit option {bg,color} pair covers
|
|
1633
|
+
Firefox + the option rows themselves so contrast stays correct in
|
|
1634
|
+
both themes. */
|
|
1635
|
+
/* Layered — see the element-reset block above. The `option:hover/:checked`
|
|
1636
|
+
and `[data-theme="light"]` rules below stay unlayered on purpose: they are
|
|
1637
|
+
state/theme OVERRIDES of these defaults, not resets. */
|
|
1638
|
+
@layer base {
|
|
1639
|
+
select {
|
|
1640
|
+
color-scheme: dark;
|
|
1641
|
+
accent-color: var(--accent);
|
|
1642
|
+
}
|
|
1643
|
+
option {
|
|
1644
|
+
background-color: #0a0a0a;
|
|
1645
|
+
color: #f5f1e8;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
option:hover,
|
|
1649
|
+
option:focus,
|
|
1650
|
+
option:checked,
|
|
1651
|
+
option:active {
|
|
1652
|
+
background: var(--accent) linear-gradient(0deg, var(--accent), var(--accent));
|
|
1653
|
+
color: #0a0a0a;
|
|
1654
|
+
}
|
|
1655
|
+
[data-theme="light"] select {
|
|
1656
|
+
color-scheme: light;
|
|
1657
|
+
}
|
|
1658
|
+
[data-theme="light"] option {
|
|
1659
|
+
background-color: #ffffff;
|
|
1660
|
+
color: #0a0a0a;
|
|
1661
|
+
}
|
|
1662
|
+
[data-theme="light"] option:hover,
|
|
1663
|
+
[data-theme="light"] option:focus,
|
|
1664
|
+
[data-theme="light"] option:checked,
|
|
1665
|
+
[data-theme="light"] option:active {
|
|
1666
|
+
background: var(--accent) linear-gradient(0deg, var(--accent), var(--accent));
|
|
1667
|
+
color: #0a0a0a;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/* Mono caps eyebrow utility — the header's nav, wordmark strip and utility
|
|
1671
|
+
links (components/Header.tsx is its only user).
|
|
1672
|
+
|
|
1673
|
+
The face reads --font-role-label DIRECTLY, never `var(--font-mono)`. A custom
|
|
1674
|
+
property's var() is substituted on the element that DECLARES it, and
|
|
1675
|
+
--font-mono is declared on :root — so it computes to the house JetBrains Mono
|
|
1676
|
+
there and inherits that literal everywhere, whatever a site publishes further
|
|
1677
|
+
down. Measured on one element with the site's Labels font set to Outfit:
|
|
1678
|
+
`var(--font-mono)` -> JetBrains Mono, `var(--font-role-label)` -> Outfit. That
|
|
1679
|
+
is why the header nav stayed monospace on both demo sites even once the roles
|
|
1680
|
+
reached it (card 8824). The Tailwind `font-mono` utility is unaffected — it
|
|
1681
|
+
inlines the theme value, so its var() resolves per element already.
|
|
1682
|
+
|
|
1683
|
+
The size and tracking below stay hard-coded: making them per-site is a
|
|
1684
|
+
separate card. */
|
|
1685
|
+
.mono-eyebrow {
|
|
1686
|
+
font-family: var(--font-role-label), var(--font-mono-fallback);
|
|
1687
|
+
font-size: 14px;
|
|
1688
|
+
letter-spacing: 0.18em;
|
|
1689
|
+
text-transform: uppercase;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/* ── Site styles ▸ Accent ▸ Size ────────────────────────────────────────────
|
|
1693
|
+
The size a gold `==phrase==` takes. The row was added on 2026-09-05 and read
|
|
1694
|
+
by NOTHING until 2026-09-08: `typeSizesStyle` published `--size-accent` and
|
|
1695
|
+
zero lines in `components/` or `app/` looked at it, so the control saved a
|
|
1696
|
+
number and moved not one pixel.
|
|
1697
|
+
|
|
1698
|
+
⚠️ A RULE, NOT A CLASS ON THE ELEMENT, and the reason is measured rather than
|
|
1699
|
+
stylistic. `goldPhrases` renders every accent run, and its output is
|
|
1700
|
+
byte-frozen: `scripts/test-markup-byte-identity.tsx` compares it against the
|
|
1701
|
+
parser that shipped, and `scripts/test-word-styles.tsx` compares 900 PUBLISHED
|
|
1702
|
+
strings against a named commit. Adding one utility class there moved all 900 —
|
|
1703
|
+
a real diff on every live page, to deliver a size the cascade delivers with no
|
|
1704
|
+
markup at all.
|
|
1705
|
+
|
|
1706
|
+
It also reaches the accent runs written by hand in CartDrawer, GalleryItem,
|
|
1707
|
+
checkout and the PDPs, which `goldPhrases` never sees and which are the same
|
|
1708
|
+
style. Scoped to `em` so it cannot catch the many other elements that wear
|
|
1709
|
+
`text-accent` for its COLOUR alone.
|
|
1710
|
+
|
|
1711
|
+
⚠️ THE FALLBACK IS `inherit`, NOT A NUMBER. An accent is a phrase inside a
|
|
1712
|
+
line, so with no size stated it must take that line's size — which is what an
|
|
1713
|
+
`<em>` already did. A site that has never opened the row publishes no
|
|
1714
|
+
`--size-accent` and renders byte for byte as it did. The tokens are published
|
|
1715
|
+
on the page content wrapper, so chrome outside `<main>` keeps inheriting too.
|
|
1716
|
+
`scripts/test-style-face-matches-size.tsx` §4 pins both halves. */
|
|
1717
|
+
em.text-accent {
|
|
1718
|
+
font-size: var(--size-accent, inherit);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/* Letters: Capitalised — the header's "Letters" control turning the caps OFF
|
|
1722
|
+
(card 66518).
|
|
1723
|
+
|
|
1724
|
+
IT HAS TO LIVE HERE, beside `.mono-eyebrow`, and it cannot be a Tailwind
|
|
1725
|
+
utility. `.mono-eyebrow` above is declared OUTSIDE every `@layer`, and
|
|
1726
|
+
unlayered CSS beats layered CSS whatever the specificity — measured in the
|
|
1727
|
+
built stylesheet: `.mono-eyebrow` at byte 143878 unlayered, `.normal-case`
|
|
1728
|
+
at 77347 inside `@layer utilities`. So `normal-case` landed in the DOM and
|
|
1729
|
+
changed nothing on screen, which is exactly how it reached Lewis.
|
|
1730
|
+
|
|
1731
|
+
Same specificity as `.mono-eyebrow`, same (un)layer, declared AFTER it — so
|
|
1732
|
+
the cascade picks this one, and it keeps working if either rule moves as
|
|
1733
|
+
long as this one stays below. The 0.18em tracking goes with the caps: that
|
|
1734
|
+
spacing is drawn for capitals and reads as a gap between lowercase letters. */
|
|
1735
|
+
.mono-eyebrow-plain {
|
|
1736
|
+
letter-spacing: normal;
|
|
1737
|
+
text-transform: none;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
/* The header's three tool controls — search, account, cart — taking the case
|
|
1741
|
+
from the strip they sit in (card 66518).
|
|
1742
|
+
|
|
1743
|
+
A <button> does NOT inherit `text-transform`: every UA stylesheet sets
|
|
1744
|
+
`text-transform: none` on form controls, and that beats the inherited value.
|
|
1745
|
+
So the nav strip said `uppercase`, the account <a> obeyed, and the cart and
|
|
1746
|
+
search <button>s did not. Measured in Chrome on the real storefront:
|
|
1747
|
+
|
|
1748
|
+
nav text-transform: uppercase
|
|
1749
|
+
a Account uppercase <- inherits
|
|
1750
|
+
button Bag none <- does not
|
|
1751
|
+
button Search none <- does not
|
|
1752
|
+
|
|
1753
|
+
Lewis saw it as "ACCOUNT Bag Search" in one bar. It was always true; the
|
|
1754
|
+
glyphs just had no letters to show it with.
|
|
1755
|
+
|
|
1756
|
+
SCOPED to the three controls rather than to `.mono-eyebrow button`, so the
|
|
1757
|
+
region picker and the theme toggle in the same strip keep the case they draw
|
|
1758
|
+
today. Unlayered, like the two rules above, for the same cascade reason. */
|
|
1759
|
+
.mono-eyebrow-ctl {
|
|
1760
|
+
letter-spacing: inherit;
|
|
1761
|
+
text-transform: inherit;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/* Finish-swatch border — a neutral mid-grey perimeter. Each swatch
|
|
1765
|
+
already carries its own hex fill; this border just provides a clean
|
|
1766
|
+
edge line so the colour disc reads as a deliberately-bordered chip.
|
|
1767
|
+
Theme-independent: the mid-grey is quiet enough on the dark page bg
|
|
1768
|
+
and visible enough on the light card surface, so it is not flipped
|
|
1769
|
+
in the [data-theme="light"] block. Consumed by ProductCard and the
|
|
1770
|
+
wheels CatalogCard swatches. */
|
|
1771
|
+
:root {
|
|
1772
|
+
--swatch-border: #888;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
/* Ticker keyframes — track is duplicated and translates -50% to loop seamlessly.
|
|
1776
|
+
FOUR of them, one per answer of the Scrolling Bar's `Scroll direction` row.
|
|
1777
|
+
|
|
1778
|
+
⚠️ `storefront-ticker` KEEPS ITS BARE NAME AND IS THE LEFTWARD ONE. It is the
|
|
1779
|
+
travel every published marquee already has, and the section-style baseline
|
|
1780
|
+
pins the ticker's default markup down to this exact animation string — so
|
|
1781
|
+
renaming it would move a fixture for a strip whose pixels had not changed,
|
|
1782
|
+
which costs a normaliser or a CHANGED_SECTIONS row to say nothing at all.
|
|
1783
|
+
The three new directions are the ones that get new names. */
|
|
1784
|
+
@keyframes storefront-ticker {
|
|
1785
|
+
from {
|
|
1786
|
+
transform: translateX(0);
|
|
1787
|
+
}
|
|
1788
|
+
to {
|
|
1789
|
+
transform: translateX(-50%);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
/* Rightward is the same travel read backwards, NOT a negative duration or an
|
|
1793
|
+
`animation-direction: reverse` on the shared name: both would need a second
|
|
1794
|
+
property on the element, and the element already says everything it says in
|
|
1795
|
+
one `animation` shorthand. */
|
|
1796
|
+
@keyframes storefront-ticker-right {
|
|
1797
|
+
from {
|
|
1798
|
+
transform: translateX(-50%);
|
|
1799
|
+
}
|
|
1800
|
+
to {
|
|
1801
|
+
transform: translateX(0);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
/* The vertical pair. Same -50% loop on the other axis — the track is stacked
|
|
1805
|
+
rather than laid across, so the duplicated half tiles downwards instead. */
|
|
1806
|
+
@keyframes storefront-ticker-up {
|
|
1807
|
+
from {
|
|
1808
|
+
transform: translateY(0);
|
|
1809
|
+
}
|
|
1810
|
+
to {
|
|
1811
|
+
transform: translateY(-50%);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
@keyframes storefront-ticker-down {
|
|
1815
|
+
from {
|
|
1816
|
+
transform: translateY(-50%);
|
|
1817
|
+
}
|
|
1818
|
+
to {
|
|
1819
|
+
transform: translateY(0);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/* `.hero-poster` (two radial gradients over a dark ramp, plus a 45° crosshatch
|
|
1824
|
+
on ::before) was deleted by card 66237. The Hero's z-1 layer now paints one
|
|
1825
|
+
flat `var(--site-surface)` inline, like every other picture slot. Nothing else
|
|
1826
|
+
used the class, and the hardcoded #2a2218 / #1a1410 / #0a0a0a in it were
|
|
1827
|
+
dark-theme literals that never followed a merchant's palette. Do not
|
|
1828
|
+
reintroduce it: `scripts/test-placeholder-copy.ts` §3b fails on the name. */
|
|
1829
|
+
|
|
1830
|
+
/* Studio-map editorial overrides moved inline to ContactPageClient.tsx
|
|
1831
|
+
so they reliably ship with the component (Turbopack dev mode doesn't
|
|
1832
|
+
always hot-reload mid-session edits to globals.css). */
|
|
1833
|
+
|
|
1834
|
+
/* Footer — the first block (the brand logo) fills its whole column so no menu
|
|
1835
|
+
ever wraps beneath it. --footer-logo-rows is measured from the live block and
|
|
1836
|
+
column counts in FooterSection and set on <footer>; the logo cell spans that
|
|
1837
|
+
many rows and pins to column 1, pushing every other block into the columns to
|
|
1838
|
+
its right. The per-tier toggles (logoFill{Desktop,Tablet,Mobile}) live in that
|
|
1839
|
+
measurement: a disabled tier — or a single-column layout — resolves the var to
|
|
1840
|
+
1, so this same rule collapses to a normal cell and the logo just stacks.
|
|
1841
|
+
|
|
1842
|
+
The logo can instead fill its whole ROW: --footer-logo-col (also set on
|
|
1843
|
+
<footer> per tier) is `1 / -1` when that view's row-fill is on, making the
|
|
1844
|
+
logo span every column as its own top row while the other blocks flow beneath.
|
|
1845
|
+
Row-fill and column-fill can't both apply, so when row-fill is on FooterSection
|
|
1846
|
+
forces --footer-logo-rows back to 1; off, the var defaults to `1` (a normal
|
|
1847
|
+
single-column cell) and the row span above takes over. */
|
|
1848
|
+
.footer-block-grid > :first-child {
|
|
1849
|
+
grid-column: var(--footer-logo-col, 1);
|
|
1850
|
+
grid-row: 1 / span var(--footer-logo-rows, 1);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
/* Alloy Wheels Listing — how many wheel cards sit in one row, authored PER VIEW
|
|
1854
|
+
("Wheels per row" in the section's Results settings). The three track lists
|
|
1855
|
+
are computed in lib/wheels/plp-grid and set on the grid element as custom
|
|
1856
|
+
properties; this rule is only the media-query switch that picks one, because a
|
|
1857
|
+
published page has no JS device to ask. The editor sets all three to the
|
|
1858
|
+
previewed view's list instead — its iframe width is not the device being
|
|
1859
|
+
previewed — so the same rule serves both without branching.
|
|
1860
|
+
|
|
1861
|
+
No fallback values on purpose: an element that carries the class always
|
|
1862
|
+
carries the vars, and a silent fallback here would be a second, invisible
|
|
1863
|
+
source of truth for the default the grid module already owns.
|
|
1864
|
+
|
|
1865
|
+
The breakpoints ARE lib/content/view-tiers' VIEW_TABLET_MIN / VIEW_DESKTOP_MIN
|
|
1866
|
+
— CSS cannot read those constants, so scripts/test-view-tier-breakpoints.ts
|
|
1867
|
+
fails if this file and that module ever disagree. */
|
|
1868
|
+
.plp-card-grid {
|
|
1869
|
+
display: grid;
|
|
1870
|
+
gap: var(--plp-grid-gap);
|
|
1871
|
+
grid-template-columns: var(--plp-grid-cols-mobile);
|
|
1872
|
+
}
|
|
1873
|
+
@media (min-width: 720px) {
|
|
1874
|
+
.plp-card-grid {
|
|
1875
|
+
grid-template-columns: var(--plp-grid-cols-tablet);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
@media (min-width: 1100px) {
|
|
1879
|
+
.plp-card-grid {
|
|
1880
|
+
grid-template-columns: var(--plp-grid-cols-desktop);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
/* Integrations wall marquee (components/IntegrationsSection.tsx): the chip
|
|
1885
|
+
track is rendered twice, so -50% is exactly one full list — the loop is
|
|
1886
|
+
seamless at any width. Reduced-motion users get a static wall (the
|
|
1887
|
+
component sets [animation:none] via motion-reduce). */
|
|
1888
|
+
@keyframes sf-marquee {
|
|
1889
|
+
from { transform: translateX(0); }
|
|
1890
|
+
to { transform: translateX(-50%); }
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/* ── Word styles — Highlight · Bold · Quiet (card 66119) ────────────────────
|
|
1894
|
+
A selected word wears a NAMED style, defined once for the whole site: change
|
|
1895
|
+
the definition and every word wearing it follows. Three NEW marks, one each:
|
|
1896
|
+
|
|
1897
|
+
@@phrase@@ Highlight ++phrase++ Bold ^^phrase^^ Quiet
|
|
1898
|
+
|
|
1899
|
+
⚠️ `**` AND `==` ARE NOT INVOLVED AND MUST NOT BE. `**` is bold and `==` is
|
|
1900
|
+
the brand-colour highlight, on this site and in the email campaign designer,
|
|
1901
|
+
which parses the same authored string through the same shared grammar. See
|
|
1902
|
+
lib/word-styles.ts for why re-pointing them was considered and rejected.
|
|
1903
|
+
|
|
1904
|
+
EACH MARK'S BUILT-IN LOOK LIVES HERE, ONCE. `wordStyleTokens` emits a
|
|
1905
|
+
`--sf-ws-*` custom property only for a facet the merchant has actually set,
|
|
1906
|
+
so the fallback in each `var()` below IS the default — there is no second
|
|
1907
|
+
copy of these values in TypeScript to drift from. A `var(--x, inherit)` reads
|
|
1908
|
+
as "the merchant said nothing about this facet, so leave it alone".
|
|
1909
|
+
|
|
1910
|
+
NOT in @layer utilities, so these beat any Tailwind utility on the same
|
|
1911
|
+
element. Nothing sets one today — a word-style span carries no class at all —
|
|
1912
|
+
but a section that later wrapped one would otherwise silently win over a
|
|
1913
|
+
merchant's explicit choice.
|
|
1914
|
+
|
|
1915
|
+
The three differ only in their DEFAULT colour and weight; every other facet
|
|
1916
|
+
starts unset, because a word style should change as little as it must. */
|
|
1917
|
+
[data-sf-ws] {
|
|
1918
|
+
font-family: var(--sf-ws-font, inherit);
|
|
1919
|
+
font-size: var(--sf-ws-size, inherit);
|
|
1920
|
+
font-style: var(--sf-ws-style, inherit);
|
|
1921
|
+
line-height: var(--sf-ws-line, inherit);
|
|
1922
|
+
letter-spacing: var(--sf-ws-track, inherit);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/* Highlight — the site's brand colour, the same ink `==` paints in. They are
|
|
1926
|
+
two different marks on purpose: `==` is a fixed brand-colour highlight shared
|
|
1927
|
+
with email, this one is a style a merchant can redefine.
|
|
1928
|
+
|
|
1929
|
+
THE SELECTOR AND EVERY TOKEN BELOW SPELL THE KEY, NOT THE LABEL. A merchant
|
|
1930
|
+
read Emphasis from card 66172 until 2026-10-04 and reads Highlight again now;
|
|
1931
|
+
`highlight` is what every published page and the backend column already
|
|
1932
|
+
store, and it does not move for a copy change. */
|
|
1933
|
+
[data-sf-ws="highlight"] {
|
|
1934
|
+
--sf-ws-font: var(--sf-ws-highlight-font, inherit);
|
|
1935
|
+
--sf-ws-size: var(--sf-ws-highlight-size, inherit);
|
|
1936
|
+
--sf-ws-style: var(--sf-ws-highlight-style, inherit);
|
|
1937
|
+
--sf-ws-line: var(--sf-ws-highlight-line, inherit);
|
|
1938
|
+
--sf-ws-track: var(--sf-ws-highlight-track, inherit);
|
|
1939
|
+
font-weight: var(--sf-ws-highlight-weight, var(--sf-ws-highlight-weight-bold, inherit));
|
|
1940
|
+
color: var(--sf-ws-highlight-color, var(--accent));
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/* Strong — heavier than its sentence, in the sentence's own ink. 600 rather
|
|
1944
|
+
than 700 so it matches the weight `**` already renders at: a merchant reaching
|
|
1945
|
+
for "the strong one" should not find it louder than the bold beside it. */
|
|
1946
|
+
[data-sf-ws="strong"] {
|
|
1947
|
+
--sf-ws-font: var(--sf-ws-strong-font, inherit);
|
|
1948
|
+
--sf-ws-size: var(--sf-ws-strong-size, inherit);
|
|
1949
|
+
--sf-ws-style: var(--sf-ws-strong-style, inherit);
|
|
1950
|
+
--sf-ws-line: var(--sf-ws-strong-line, inherit);
|
|
1951
|
+
--sf-ws-track: var(--sf-ws-strong-track, inherit);
|
|
1952
|
+
font-weight: var(--sf-ws-strong-weight, var(--sf-ws-strong-weight-bold, 600));
|
|
1953
|
+
color: var(--sf-ws-strong-color, inherit);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/* Quiet — the site's Labels ink, the dim tone eyebrows and captions already
|
|
1957
|
+
use, so a quiet word recedes by the same amount everywhere. */
|
|
1958
|
+
[data-sf-ws="quiet"] {
|
|
1959
|
+
--sf-ws-font: var(--sf-ws-quiet-font, inherit);
|
|
1960
|
+
--sf-ws-size: var(--sf-ws-quiet-size, inherit);
|
|
1961
|
+
--sf-ws-style: var(--sf-ws-quiet-style, inherit);
|
|
1962
|
+
--sf-ws-line: var(--sf-ws-quiet-line, inherit);
|
|
1963
|
+
--sf-ws-track: var(--sf-ws-quiet-track, inherit);
|
|
1964
|
+
font-weight: var(--sf-ws-quiet-weight, var(--sf-ws-quiet-weight-bold, inherit));
|
|
1965
|
+
color: var(--sf-ws-quiet-color, var(--fg-dim));
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/* ── A Hero slide's GROUPS, and the anchor each one sits on (card 66189) ───────
|
|
1969
|
+
|
|
1970
|
+
A group carries an ANCHOR, never a pair of coordinates. The owner's reason is
|
|
1971
|
+
the design: the hero's height is the merchant's, a phone breaks every stored
|
|
1972
|
+
coordinate, and "bottom left" means the same thing at any size.
|
|
1973
|
+
|
|
1974
|
+
⚠️ WHY THIS IS A GRID AND NOT NINE ABSOLUTE CORNERS. Puck 0.20 hands a slot's
|
|
1975
|
+
children to ONE container as a flat list — there is no per-child wrapper — so
|
|
1976
|
+
`HeroSlide` cannot sort its groups into nine corner boxes, and nine absolute
|
|
1977
|
+
siblings cannot stack by content height without measuring in JS (which means
|
|
1978
|
+
the published page moving after it paints). One grid solves both: three bands
|
|
1979
|
+
of four rows with a `1fr` spacer between them, the vertical anchor picking the
|
|
1980
|
+
band's first row and the horizontal anchor riding `justify-self`.
|
|
1981
|
+
|
|
1982
|
+
That last part is load-bearing and easy to lose: three anchors in ONE band
|
|
1983
|
+
share a row, so `bottom-left` and `bottom-right` sit SIDE BY SIDE — which is
|
|
1984
|
+
what today's hero is (heading left, body right, one line). A design that gave
|
|
1985
|
+
each band a flex column would stack them and quietly break it.
|
|
1986
|
+
|
|
1987
|
+
Empty `auto` rows collapse to nothing, so a band holding one group hugs its
|
|
1988
|
+
edge exactly as if the other three rows were not there.
|
|
1989
|
+
|
|
1990
|
+
⚠️ THE STACK RULES BELOW WIN ON SPECIFICITY, NOT ON SOURCE ORDER, AND THAT IS
|
|
1991
|
+
DELIBERATE. `~` is the GENERAL sibling combinator, so `.g[a] ~ .g[a]` matches
|
|
1992
|
+
the 2nd group on that anchor AND the 3rd AND the 4th — every one of them. The
|
|
1993
|
+
right row still wins because each longer chain is a more specific selector
|
|
1994
|
+
(three compound selectors beat two), and specificity is settled before source
|
|
1995
|
+
order ever comes into it. So these rules may be written in any order and may
|
|
1996
|
+
be reordered safely — but do NOT "tidy" them by collapsing the chains or by
|
|
1997
|
+
assuming the last one wins, because the 3rd and 4th rows would silently fall
|
|
1998
|
+
back to the 2nd row's rule and two groups would draw on top of each other.
|
|
1999
|
+
|
|
2000
|
+
THE STACK IS CAPPED AT FOUR, and the cap is enforced in the EDITOR as well as
|
|
2001
|
+
here — a fifth group would land on the fourth's row, and a guard is not in the
|
|
2002
|
+
room when a merchant presses Add. See `heroGroupAddBlocked` and
|
|
2003
|
+
`placementPoints` in lib/hero-group-placement.ts: the Add row and the full
|
|
2004
|
+
anchor's square both go unavailable, with a sentence saying why.
|
|
2005
|
+
|
|
2006
|
+
The whole model — the row maths, the depth, the nudge limit — lives in
|
|
2007
|
+
lib/hero-group-placement.ts, and scripts/test-hero-group-placement.tsx pins
|
|
2008
|
+
this file against it (a tenth anchor with no rule here fails there).
|
|
2009
|
+
|
|
2010
|
+
NOT in @layer utilities, like the word-style rules above and for the same
|
|
2011
|
+
reason: a group's row must beat any utility a later section puts on it. */
|
|
2012
|
+
.sf-hero-group-layer {
|
|
2013
|
+
display: grid;
|
|
2014
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2015
|
+
/* FIVE rows per band, not four: the first row of each band belongs to the
|
|
2016
|
+
slide's OWN copy (card 66213) and the four after it are the groups' stack.
|
|
2017
|
+
Keep in step with HERO_GROUP_BAND_ROWS — lib/hero-group-placement carries
|
|
2018
|
+
the whole reason, and a band that disagrees with this line puts a group on
|
|
2019
|
+
a row the grid does not have, which is an implicit row BELOW the slide
|
|
2020
|
+
rather than an error anybody sees. */
|
|
2021
|
+
grid-template-rows: repeat(5, auto) 1fr repeat(5, auto) 1fr repeat(5, auto);
|
|
2022
|
+
/* The layer covers the whole slide, so it would swallow every click meant for
|
|
2023
|
+
what is underneath it. Only the copy and the groups are hit targets. */
|
|
2024
|
+
pointer-events: none;
|
|
2025
|
+
}
|
|
2026
|
+
/* …except in the editor, where the empty drop zone IS the drop target. Card
|
|
2027
|
+
66213 moved this off the LAYER and onto the zone: the layer now also holds the
|
|
2028
|
+
slide's own copy, and making the whole layer a target would put the drop
|
|
2029
|
+
surface on top of the merchant's own heading. */
|
|
2030
|
+
.sf-hero-group-layer-edit .sf-hero-group-zone { pointer-events: auto; }
|
|
2031
|
+
|
|
2032
|
+
/* Puck's own drop zone, laid over the SAME rows as the copy beside it (card
|
|
2033
|
+
66213). This is the one layer, and this element is what makes it one:
|
|
2034
|
+
`subgrid` means a group's `--sf-hg-row` counts the layer's tracks, not a
|
|
2035
|
+
private set of its own, so a group and the copy on one square STACK instead of
|
|
2036
|
+
overlapping — which two stacked layers can never do.
|
|
2037
|
+
|
|
2038
|
+
⚠️ A REAL BOX, DELIBERATELY, AND NOT `display: contents`. Puck measures this
|
|
2039
|
+
element to decide where a drag lands; `display: contents` removes the box and
|
|
2040
|
+
takes the measurement with it. Spanning every row costs nothing because the
|
|
2041
|
+
zone is not a hit target (above) and its rows are the layer's own.
|
|
2042
|
+
|
|
2043
|
+
Measured in a real browser before it was written (900×500 slide, 40px inset):
|
|
2044
|
+
copy 406→424 at the left edge, group one 424→442 directly beneath it, group
|
|
2045
|
+
two 442→460, a top-right group at 40→58 against the right edge, and ZERO
|
|
2046
|
+
overlapping pairs. scripts/harness/hero-copy-grid-probe.html is that probe. */
|
|
2047
|
+
.sf-hero-group-zone {
|
|
2048
|
+
grid-column: 1;
|
|
2049
|
+
grid-row: 1 / -1;
|
|
2050
|
+
display: grid;
|
|
2051
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2052
|
+
grid-template-rows: subgrid;
|
|
2053
|
+
pointer-events: none;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
.sf-hero-group {
|
|
2057
|
+
/* The BAND's own row, stated by the component as a custom property so the
|
|
2058
|
+
sibling rules below can add to it. An inline `grid-row` would beat them. */
|
|
2059
|
+
grid-row: var(--sf-hg-row);
|
|
2060
|
+
/* EXPLICIT, and it is not decoration: the zone above spans every row of
|
|
2061
|
+
column 1, so auto-placement has no free cell left and would flow the copy
|
|
2062
|
+
into an implicit SECOND column — which is exactly what the browser probe
|
|
2063
|
+
did before this line existed, parking the copy off to the right of a
|
|
2064
|
+
narrowed zone. */
|
|
2065
|
+
grid-column: 1;
|
|
2066
|
+
min-width: 0;
|
|
2067
|
+
pointer-events: auto;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/* A group's own two numbers, applied from CUSTOM PROPERTIES the component
|
|
2071
|
+
renders rather than from the inline `max-width` / `translate` it used to
|
|
2072
|
+
write directly (card 66255).
|
|
2073
|
+
|
|
2074
|
+
⚠️ THIS IS NOT A TIDY-UP, AND PUTTING THEM BACK INLINE BREAKS THE PHONE. An
|
|
2075
|
+
inline style beats every stylesheet rule that is not `!important`, so the
|
|
2076
|
+
phone block below could not reach either of them — and both have to be
|
|
2077
|
+
reached: a `width` is a % of the SLIDE (60% of 375px is 201px, a number no
|
|
2078
|
+
merchant chose) and a nudge is a ±40px fine adjustment against an ANCHOR,
|
|
2079
|
+
which a stack does not have. Free to change, because no stored document holds
|
|
2080
|
+
a HeroGroup at all: measured 2026-08-29 across every schema, in both `layout`
|
|
2081
|
+
and `published_layout`, soft-deleted rows included.
|
|
2082
|
+
|
|
2083
|
+
An UNTOUCHED group still renders nothing for the nudge — the component omits
|
|
2084
|
+
the property entirely — so the fallback here is what carries the "no nudge"
|
|
2085
|
+
case, exactly as the inline style's absence used to. */
|
|
2086
|
+
.sf-hero-group {
|
|
2087
|
+
max-width: var(--sf-hg-w, 100%);
|
|
2088
|
+
translate: var(--sf-hg-nudge, none);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/* A box's WIDTH BUILT-IN, and it is two different numbers because the two
|
|
2092
|
+
positions are two different questions (owner 2026-09-04: "是需要在那个百分之
|
|
2093
|
+
60 的盒子里, 100% 的出现").
|
|
2094
|
+
|
|
2095
|
+
A box parked on the SLIDE is a column of copy over a photograph, and 60% of
|
|
2096
|
+
the slide is the shape that reads — so the zone's own children carry 60%
|
|
2097
|
+
here. A box parked INSIDE another box was handed the same 60% and it was
|
|
2098
|
+
simply wrong: it is not measuring the slide, it is measuring the box its
|
|
2099
|
+
merchant just drew, and 60% of that is a second unasked-for indent. It takes
|
|
2100
|
+
the `var()` fallback one rule up — 100%, fill the box you are in.
|
|
2101
|
+
|
|
2102
|
+
⚠️ THE BUILT-IN LIVES HERE AND NOT IN `defaultProps` — doc/editor-ui.md E11,
|
|
2103
|
+
the same call `lib/content/container-gap.ts` and `container-direction.ts`
|
|
2104
|
+
already made for this very box. A seeded default is a second copy of a
|
|
2105
|
+
number, free to drift from the one the render uses; worse here than for
|
|
2106
|
+
either of those, because this number depends on WHERE the node sits and
|
|
2107
|
+
`defaultProps` is stamped before the node has a position.
|
|
2108
|
+
|
|
2109
|
+
⚠️ AND IT IS A CUSTOM PROPERTY ON A SELECTOR, NOT A `max-width`. The rule
|
|
2110
|
+
above is the one place a box's width is applied, so a merchant's stored
|
|
2111
|
+
width still beats this (it arrives inline) and the phone block below still
|
|
2112
|
+
overrides both. A `max-width` here would be a second application of one
|
|
2113
|
+
idea, which is the defect the inline-vs-stylesheet note above is about. */
|
|
2114
|
+
.sf-hero-group-zone > .sf-hero-group {
|
|
2115
|
+
--sf-hg-w: 60%;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
/* The second group on ONE anchor drops 1 row, so a stack never overlaps.
|
|
2119
|
+
Document order is Layer-panel order, so the tree order IS the stack order. */
|
|
2120
|
+
.sf-hero-group[data-hg="top-left"] ~ .sf-hero-group[data-hg="top-left"],
|
|
2121
|
+
.sf-hero-group[data-hg="top-center"] ~ .sf-hero-group[data-hg="top-center"],
|
|
2122
|
+
.sf-hero-group[data-hg="top-right"] ~ .sf-hero-group[data-hg="top-right"],
|
|
2123
|
+
.sf-hero-group[data-hg="left"] ~ .sf-hero-group[data-hg="left"],
|
|
2124
|
+
.sf-hero-group[data-hg="center"] ~ .sf-hero-group[data-hg="center"],
|
|
2125
|
+
.sf-hero-group[data-hg="right"] ~ .sf-hero-group[data-hg="right"],
|
|
2126
|
+
.sf-hero-group[data-hg="bottom-left"] ~ .sf-hero-group[data-hg="bottom-left"],
|
|
2127
|
+
.sf-hero-group[data-hg="bottom-center"] ~ .sf-hero-group[data-hg="bottom-center"],
|
|
2128
|
+
.sf-hero-group[data-hg="bottom-right"] ~ .sf-hero-group[data-hg="bottom-right"] {
|
|
2129
|
+
grid-row: calc(var(--sf-hg-row) + 1);
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/* The third group on ONE anchor drops 2 rows, so a stack never overlaps.
|
|
2133
|
+
Document order is Layer-panel order, so the tree order IS the stack order. */
|
|
2134
|
+
.sf-hero-group[data-hg="top-left"] ~ .sf-hero-group[data-hg="top-left"] ~ .sf-hero-group[data-hg="top-left"],
|
|
2135
|
+
.sf-hero-group[data-hg="top-center"] ~ .sf-hero-group[data-hg="top-center"] ~ .sf-hero-group[data-hg="top-center"],
|
|
2136
|
+
.sf-hero-group[data-hg="top-right"] ~ .sf-hero-group[data-hg="top-right"] ~ .sf-hero-group[data-hg="top-right"],
|
|
2137
|
+
.sf-hero-group[data-hg="left"] ~ .sf-hero-group[data-hg="left"] ~ .sf-hero-group[data-hg="left"],
|
|
2138
|
+
.sf-hero-group[data-hg="center"] ~ .sf-hero-group[data-hg="center"] ~ .sf-hero-group[data-hg="center"],
|
|
2139
|
+
.sf-hero-group[data-hg="right"] ~ .sf-hero-group[data-hg="right"] ~ .sf-hero-group[data-hg="right"],
|
|
2140
|
+
.sf-hero-group[data-hg="bottom-left"] ~ .sf-hero-group[data-hg="bottom-left"] ~ .sf-hero-group[data-hg="bottom-left"],
|
|
2141
|
+
.sf-hero-group[data-hg="bottom-center"] ~ .sf-hero-group[data-hg="bottom-center"] ~ .sf-hero-group[data-hg="bottom-center"],
|
|
2142
|
+
.sf-hero-group[data-hg="bottom-right"] ~ .sf-hero-group[data-hg="bottom-right"] ~ .sf-hero-group[data-hg="bottom-right"] {
|
|
2143
|
+
grid-row: calc(var(--sf-hg-row) + 2);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/* The fourth group on ONE anchor drops 3 rows, so a stack never overlaps.
|
|
2147
|
+
Document order is Layer-panel order, so the tree order IS the stack order. */
|
|
2148
|
+
.sf-hero-group[data-hg="top-left"] ~ .sf-hero-group[data-hg="top-left"] ~ .sf-hero-group[data-hg="top-left"] ~ .sf-hero-group[data-hg="top-left"],
|
|
2149
|
+
.sf-hero-group[data-hg="top-center"] ~ .sf-hero-group[data-hg="top-center"] ~ .sf-hero-group[data-hg="top-center"] ~ .sf-hero-group[data-hg="top-center"],
|
|
2150
|
+
.sf-hero-group[data-hg="top-right"] ~ .sf-hero-group[data-hg="top-right"] ~ .sf-hero-group[data-hg="top-right"] ~ .sf-hero-group[data-hg="top-right"],
|
|
2151
|
+
.sf-hero-group[data-hg="left"] ~ .sf-hero-group[data-hg="left"] ~ .sf-hero-group[data-hg="left"] ~ .sf-hero-group[data-hg="left"],
|
|
2152
|
+
.sf-hero-group[data-hg="center"] ~ .sf-hero-group[data-hg="center"] ~ .sf-hero-group[data-hg="center"] ~ .sf-hero-group[data-hg="center"],
|
|
2153
|
+
.sf-hero-group[data-hg="right"] ~ .sf-hero-group[data-hg="right"] ~ .sf-hero-group[data-hg="right"] ~ .sf-hero-group[data-hg="right"],
|
|
2154
|
+
.sf-hero-group[data-hg="bottom-left"] ~ .sf-hero-group[data-hg="bottom-left"] ~ .sf-hero-group[data-hg="bottom-left"] ~ .sf-hero-group[data-hg="bottom-left"],
|
|
2155
|
+
.sf-hero-group[data-hg="bottom-center"] ~ .sf-hero-group[data-hg="bottom-center"] ~ .sf-hero-group[data-hg="bottom-center"] ~ .sf-hero-group[data-hg="bottom-center"],
|
|
2156
|
+
.sf-hero-group[data-hg="bottom-right"] ~ .sf-hero-group[data-hg="bottom-right"] ~ .sf-hero-group[data-hg="bottom-right"] ~ .sf-hero-group[data-hg="bottom-right"] {
|
|
2157
|
+
grid-row: calc(var(--sf-hg-row) + 3);
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
/* ── A BUTTON IN A SLIDE BOX SET TO `One row` DROPS ITS FULL-WIDTH FRAME ─────
|
|
2161
|
+
Card `[FIX] Two Buttons Side By Side In A Hero`.
|
|
2162
|
+
|
|
2163
|
+
THE DEFECT. `components/ButtonBlock.tsx` draws a frame that asks for the
|
|
2164
|
+
whole line — `w-full`, or the site frame, which is `w-full` inside a cap.
|
|
2165
|
+
That is right wherever a Button block owns its own row, and it is exactly
|
|
2166
|
+
wrong in the one place a merchant puts TWO of them on one line. Measured in a
|
|
2167
|
+
real browser on this stylesheet before this rule was written, two Buttons in
|
|
2168
|
+
a 1200px Slide box set to `One row`:
|
|
2169
|
+
|
|
2170
|
+
each block 1200px wide, stacked one under the other — two lines, not one.
|
|
2171
|
+
|
|
2172
|
+
Card 66262 had already drawn the right answer and called it THE HERO LOOK:
|
|
2173
|
+
"the button alone, with no frame and no row of its own, because the hero puts
|
|
2174
|
+
BOTH its buttons in one row". It reached the component as a `hero` prop set
|
|
2175
|
+
by the slide's own built-in button — and that button was deleted when the
|
|
2176
|
+
hero moved onto Boxes (#1280), so the prop had no caller left and the look
|
|
2177
|
+
was unreachable. This is that look, restored where it can actually be reached.
|
|
2178
|
+
|
|
2179
|
+
⚠️ IT IS A RULE AND NOT A PROP, AND THAT IS THE WHOLE MECHANISM. The question
|
|
2180
|
+
is "what is holding this block?", and the block cannot answer it: it renders
|
|
2181
|
+
on the published site, where there is no document to walk, and every block in
|
|
2182
|
+
a hero is a server component, so there is no React context to read either.
|
|
2183
|
+
`components/HeroGroup.tsx` makes the identical call one tier up for
|
|
2184
|
+
`--sf-hg-w` and states the reason there in full — CSS already knows, so the
|
|
2185
|
+
answer is asked where it is free. `lib/puck/config.tsx`'s cell-width pass is
|
|
2186
|
+
the same shape for the EDITOR half of a position question.
|
|
2187
|
+
|
|
2188
|
+
⚠️ TWO SELECTORS, BECAUSE THE EDITOR ADDS A LAYER. Puck 0.20 wraps every slot
|
|
2189
|
+
child in a drag element unless the component declares `inline`; the published
|
|
2190
|
+
page has no wrapper (HeroGroup's own docblock carries that measurement, card
|
|
2191
|
+
66290). So the first line is the page and the second is the canvas.
|
|
2192
|
+
|
|
2193
|
+
⛔ `:not(.sf-hero-group)` IS LOAD-BEARING. A Slide box may hold a Slide box,
|
|
2194
|
+
and that inner box answers this question FOR ITSELF — it carries its own
|
|
2195
|
+
`Direction`, so it matches this rule on its own when it is a row and must not
|
|
2196
|
+
be reached through its parent when it is `Stacked`. Without it, a button in a
|
|
2197
|
+
stacked box inside a one-row box would silently lose its width.
|
|
2198
|
+
|
|
2199
|
+
⚠️ NOT GATED ON THE DESKTOP. Below 800px the block above turns this container
|
|
2200
|
+
into a column while the `flex-row` class stays on it, so this rule still
|
|
2201
|
+
applies — and it must: `align-items` is unset on a Slide box, so a flex item
|
|
2202
|
+
with `width: auto` is STRETCHED to the column, which is the full-width button
|
|
2203
|
+
a phone already draws. A `min-width: 801px` gate here would give the phone
|
|
2204
|
+
two shrink-wrapped buttons instead, which nobody asked for.
|
|
2205
|
+
|
|
2206
|
+
UNLAYERED, like every rule in this section, and here that is load-bearing
|
|
2207
|
+
rather than incidental: `w-full`, `max-w-*`, `mx-auto` and `px-*` are all
|
|
2208
|
+
Tailwind utilities inside `@layer utilities`, and an unlayered rule beats a
|
|
2209
|
+
layered one whatever their specificity. */
|
|
2210
|
+
.sf-hero-group-blocks.flex-row > .sf-button-block,
|
|
2211
|
+
.sf-hero-group-blocks.flex-row > *:not(.sf-hero-group) > .sf-button-block {
|
|
2212
|
+
width: auto;
|
|
2213
|
+
max-width: none;
|
|
2214
|
+
margin-inline: 0;
|
|
2215
|
+
padding-inline: 0;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/* ── ON A PHONE THE NINE SQUARES BECOME ONE COLUMN (card 66255) ──────────────
|
|
2219
|
+
|
|
2220
|
+
THE DEFECT THIS FIXES IS A COLLISION, NOT A MISSING FEATURE. The layer above
|
|
2221
|
+
is ONE grid column and an anchor resolves to a grid ROW, so a band's three
|
|
2222
|
+
anchors deliberately SHARE a row and are pulled apart by `justify-self` —
|
|
2223
|
+
correct on a wide screen, and what makes today's hero expressible. The
|
|
2224
|
+
sibling chains above only drop a group that shares an ANCHOR with the one
|
|
2225
|
+
before it, so a `left` group and a `right` group sit in ONE cell at every
|
|
2226
|
+
width. At the default 60% each, that is 120% of the slide.
|
|
2227
|
+
|
|
2228
|
+
Measured in a real browser on this stylesheet before the block was written,
|
|
2229
|
+
two groups anchored left and right with a 28px heading in each:
|
|
2230
|
+
|
|
2231
|
+
375px left 20 → 221 right 154 → 355 both top 242 → 355
|
|
2232
|
+
OVERLAPPING BY 67px, on identical vertical bounds.
|
|
2233
|
+
1280px left 40 → 259 right 989 → 1240 both top 298 → 372
|
|
2234
|
+
side by side, 730px of clear air. Correct, and untouched below.
|
|
2235
|
+
|
|
2236
|
+
⚠️ THE CONTAINER CHANGES, THE CHILDREN DO NOT. `grid-row` is INERT on a flex
|
|
2237
|
+
item, so nothing here has to out-specify the ~60 lines of sibling chains
|
|
2238
|
+
above — and a `grid-row: auto` override could not have: those chains are four
|
|
2239
|
+
classes and four attributes deep and a media query adds no specificity. Turn
|
|
2240
|
+
the container into a single-line column and every box gets a line of its own
|
|
2241
|
+
by the flexbox model itself. The chains stay exactly as they are and come
|
|
2242
|
+
back untouched at 801px.
|
|
2243
|
+
|
|
2244
|
+
Both boxes are laid out here: `.sf-hero-group-layer` holds the slide's own
|
|
2245
|
+
copy and the zone, `.sf-hero-group-zone` holds the groups. Two containers,
|
|
2246
|
+
one column — the copy first because it is first in the document, and the
|
|
2247
|
+
document order is the reading order. There is deliberately no `order:` here:
|
|
2248
|
+
sorting the column by band would move the box without moving the markup, so
|
|
2249
|
+
a screen reader and the tab key would disagree with the screen.
|
|
2250
|
+
|
|
2251
|
+
THE PHONE LINE IS 800px, WHICH IS THE HERO'S OWN. The picker hides below it,
|
|
2252
|
+
the arrows hide below it, the slideshow's bottom dots appear below it and the
|
|
2253
|
+
slide's type and padding step down at it — see lib/hero-phone-stack.ts, which
|
|
2254
|
+
carries the number for the guard to read (a media query cannot take a custom
|
|
2255
|
+
property, so it is spelled out here too). */
|
|
2256
|
+
@media (max-width: 800px) {
|
|
2257
|
+
.sf-hero-group-layer {
|
|
2258
|
+
display: flex;
|
|
2259
|
+
flex-direction: column;
|
|
2260
|
+
flex-wrap: nowrap;
|
|
2261
|
+
/* The slide's own padding steps down HERE rather than on the element (card
|
|
2262
|
+
66265). It used to be `max-[800px]:px-5 max-[800px]:pt-[100px]
|
|
2263
|
+
max-[800px]:pb-[90px]`, which Tailwind compiles to
|
|
2264
|
+
`@media (width < 800px)` — one pixel short of this block's own line — so
|
|
2265
|
+
at exactly 800px THIS DIV was a phone column with desktop padding on it.
|
|
2266
|
+
The values are the ones those three utilities emitted, `--spacing` and
|
|
2267
|
+
all, so a phone renders the pixels it rendered before the move. */
|
|
2268
|
+
padding-inline: calc(var(--spacing) * 5);
|
|
2269
|
+
padding-top: 100px;
|
|
2270
|
+
padding-bottom: 90px;
|
|
2271
|
+
/* The two rows the Hero asks, as the merchant's own answer or the default
|
|
2272
|
+
every published hero already draws: bottom, left. The FALLBACKS carry the
|
|
2273
|
+
default rather than the component writing it, so an unasked hero renders
|
|
2274
|
+
the same style attribute it rendered before this card — see
|
|
2275
|
+
`heroPhoneStackStyle`, and keep the two in step. */
|
|
2276
|
+
justify-content: var(--sf-hero-phone-y, flex-end);
|
|
2277
|
+
align-items: var(--sf-hero-phone-x, flex-start);
|
|
2278
|
+
}
|
|
2279
|
+
.sf-hero-group-zone {
|
|
2280
|
+
display: flex;
|
|
2281
|
+
flex-direction: column;
|
|
2282
|
+
flex-wrap: nowrap;
|
|
2283
|
+
/* The zone is the column's full width even when the column hugs a side, so
|
|
2284
|
+
a group is aligned against the SLIDE and not against the widest group
|
|
2285
|
+
beside it. Without this the layer's `align-items` shrink-wraps the zone
|
|
2286
|
+
and `Sideways` would move the whole block instead of each box. */
|
|
2287
|
+
align-self: stretch;
|
|
2288
|
+
align-items: var(--sf-hero-phone-x, flex-start);
|
|
2289
|
+
}
|
|
2290
|
+
.sf-hero-group {
|
|
2291
|
+
/* A group's stored width is a % of the SLIDE. 60% of 375px is 201px — a
|
|
2292
|
+
number nobody chose — so in the stack a group may use the whole column
|
|
2293
|
+
and its box is as wide as its own words, which is what leaves `Sideways`
|
|
2294
|
+
something to move. */
|
|
2295
|
+
max-width: 100%;
|
|
2296
|
+
/* ±40px is a fine adjustment against an ANCHOR. There is no anchor in a
|
|
2297
|
+
stack, and 40px of a 375px screen is 11% of the picture — enough to push
|
|
2298
|
+
a box under the slide's own `overflow: hidden`. */
|
|
2299
|
+
translate: none;
|
|
2300
|
+
}
|
|
2301
|
+
/* …and the blocks INSIDE a group stack too (card 66264). A group may be set to
|
|
2302
|
+
`One row`, which is a wide-screen answer: on a phone a heading beside a
|
|
2303
|
+
button is the same collision this whole block exists to prevent, one tier
|
|
2304
|
+
down. The stored answer is untouched and comes straight back at 801px.
|
|
2305
|
+
|
|
2306
|
+
IT LIVES HERE, IN THIS BLOCK, RATHER THAN AS A `max-[800px]:flex-col` ON THE
|
|
2307
|
+
ELEMENT — which is how components/Flex.tsx spells the same idea, and how the
|
|
2308
|
+
rest of the shop spells every phone rule it has. Tailwind compiles that
|
|
2309
|
+
variant to `@media (width < 800px)`, which stops at 799; this block is
|
|
2310
|
+
`max-width: 800px`, which does not. A group's blocks would therefore have
|
|
2311
|
+
flipped one pixel away from the group's own stack. One line in the block
|
|
2312
|
+
that already owns the hero's phone line keeps the two together, and keeps
|
|
2313
|
+
the number in exactly one place.
|
|
2314
|
+
|
|
2315
|
+
UNLAYERED, like every rule in this section, and here that is load-bearing
|
|
2316
|
+
rather than incidental: `flex-row` is a Tailwind utility inside
|
|
2317
|
+
`@layer utilities`, and an unlayered rule beats a layered one whatever their
|
|
2318
|
+
specificity. */
|
|
2319
|
+
.sf-hero-group-blocks {
|
|
2320
|
+
flex-direction: column;
|
|
2321
|
+
}
|
|
2322
|
+
/* ── AND THE REST OF THE HERO, MOVED HERE BY CARD 66265 ──────────────────
|
|
2323
|
+
Everything below drew its phone answer from a `max-[800px]:` variant on the
|
|
2324
|
+
element until that card. Tailwind v4 compiles that variant to
|
|
2325
|
+
`@media (width < 800px)`, which STOPS AT 799, while this block is
|
|
2326
|
+
`@media (max-width: 800px)`, which does not — so at a viewport of exactly
|
|
2327
|
+
800px the hero was in two modes at once: the layer, the zone and the groups
|
|
2328
|
+
above had already collapsed to the phone column while these four still drew
|
|
2329
|
+
the wide layout. Card 66264 found that and stepped around it; this is the
|
|
2330
|
+
same move it made for `.sf-hero-group-blocks`, applied to the rest.
|
|
2331
|
+
|
|
2332
|
+
`max-[801px]:` would also have worked and is refused: it leaves a number in
|
|
2333
|
+
the source that reads wrong to everyone who meets it afterwards. The line is
|
|
2334
|
+
800 on both sides, declared once, above.
|
|
2335
|
+
|
|
2336
|
+
UNLAYERED, like every rule in this section, and here that is load-bearing
|
|
2337
|
+
rather than incidental: each of these elements still carries the Tailwind
|
|
2338
|
+
utility for its WIDE value (`py-3.5`, `left-6`), those live in
|
|
2339
|
+
`@layer utilities`, and an unlayered rule beats a layered one whatever their
|
|
2340
|
+
specificity. (Card 66288 took the copy grid and the copy button row out of
|
|
2341
|
+
this run with the rest of the slide's built-in copy; the two that remain are
|
|
2342
|
+
the ticker and the editor's video control.) The values are the ones the removed
|
|
2343
|
+
utilities emitted, so a phone renders exactly what it rendered before.
|
|
2344
|
+
`scripts/test-hero-800-line.tsx` resolves all four at 799, 800 and 801. */
|
|
2345
|
+
.sf-hero-ticker {
|
|
2346
|
+
padding-block: calc(var(--spacing) * 3);
|
|
2347
|
+
}
|
|
2348
|
+
/* Editor-only chrome, and it moves for the same reason as the rest: a control
|
|
2349
|
+
that stepped in at 799 while the slide under it stacked at 800 is the same
|
|
2350
|
+
disagreement, just one a shopper never sees. */
|
|
2351
|
+
.sf-hero-video-control {
|
|
2352
|
+
left: calc(var(--spacing) * 4);
|
|
2353
|
+
}
|
|
2354
|
+
/* ── AND THE HERO'S OVER-MEDIA CHROME, MOVED HERE BY CARD 66270 ──────────
|
|
2355
|
+
The MIRROR HALF of the block above. Those declarations arrived because
|
|
2356
|
+
`max-[800px]:` compiles to `@media (width < 800px)`, which stops one pixel
|
|
2357
|
+
SHORT of this block's line. These two arrive because `min-[800px]:`
|
|
2358
|
+
compiles to `@media (width >= 800px)`, which INCLUDES 800 — exactly as this
|
|
2359
|
+
block does. So at a viewport of exactly 800px the slide had already
|
|
2360
|
+
collapsed into its phone column, above, while the slideshow still drew its
|
|
2361
|
+
DESKTOP chrome over the top of it: the slide picker and the prev / next
|
|
2362
|
+
arrows shown, and the phone's own dot row — the control that is meant to
|
|
2363
|
+
REPLACE them down here — hidden. No width had both.
|
|
2364
|
+
|
|
2365
|
+
`min-[801px]:` would also have worked and is refused, for the same reason
|
|
2366
|
+
`max-[801px]:` was: it leaves a number in the source that reads wrong to
|
|
2367
|
+
everyone who meets it afterwards. The line is 800 on both sides, declared
|
|
2368
|
+
once, at the top of this block.
|
|
2369
|
+
|
|
2370
|
+
UNLAYERED, and load-bearing rather than incidental: each element still
|
|
2371
|
+
carries a Tailwind utility for its OTHER answer (`flex` on the rail,
|
|
2372
|
+
`hidden` on the dots), those live in `@layer utilities`, and an unlayered
|
|
2373
|
+
rule beats a layered one whatever their specificity. Same fact card 66265
|
|
2374
|
+
leans on. `scripts/test-hero-800-chrome.tsx` resolves all three elements at
|
|
2375
|
+
799, 800 and 801, and proves these two rules are unlayered from the source.
|
|
2376
|
+
|
|
2377
|
+
THE RAIL IS BOTH FEATURES AT ONCE. `heroOverlayRailClass` in
|
|
2378
|
+
lib/hero-overlay-anchor.ts is the one desktop gate, written once, and the
|
|
2379
|
+
picker and the arrows both read it — so this single selector is why "the
|
|
2380
|
+
arrows follow whatever the picker does on a phone" stays true by
|
|
2381
|
+
construction. Do not give either of them a rule of its own. */
|
|
2382
|
+
.sf-hero-overlay-rail {
|
|
2383
|
+
display: none;
|
|
2384
|
+
}
|
|
2385
|
+
/* The dots are NOT a caller of that gate — they are the phone's own control,
|
|
2386
|
+
drawn inline by components/HeroSection.tsx, and they had to move with it:
|
|
2387
|
+
hiding the picker and the arrows at 800 while these stayed hidden too would
|
|
2388
|
+
leave an 800px visitor no way to change slide at all. */
|
|
2389
|
+
.sf-hero-phone-dots {
|
|
2390
|
+
display: flex;
|
|
2391
|
+
}
|
|
2392
|
+
/* The air between stacked boxes, put on the GROUPS alone and never as a `gap`
|
|
2393
|
+
on the layer. An empty groups zone is still a flex item, so a `gap` would
|
|
2394
|
+
push the copy of every published slide — none of which has a group — 28px
|
|
2395
|
+
off the bottom for no reason anyone asked for. A margin on a box that does
|
|
2396
|
+
not exist costs nothing. */
|
|
2397
|
+
.sf-hero-group-zone .sf-hero-group {
|
|
2398
|
+
margin-top: 28px;
|
|
2399
|
+
}
|
|
2400
|
+
/* ── ON A PHONE THE HERO GROWS TO FIT ITS WORDS (card [FIX] Hero Grows On
|
|
2401
|
+
Phone, GG 2026-10-16) ─────────────────────────────────────────────────
|
|
2402
|
+
The phone column above is bottom-aligned (`justify-content: flex-end`) in
|
|
2403
|
+
a layer that covered a hero of FIXED height (`absolute inset-0` on a
|
|
2404
|
+
`h-[…]` / `h-screen min-h-[680px]` section). Words taller than the hero
|
|
2405
|
+
therefore rose off the TOP of the page, under the site's header:
|
|
2406
|
+
hartesmetal.com/dealers' heading began 181px above the page at 390x844, and
|
|
2407
|
+
hartesmetal.com.au's home slideshow 100px above it at 375x667.
|
|
2408
|
+
|
|
2409
|
+
So on a phone the hero's height is a MINIMUM and its words size it:
|
|
2410
|
+
· the hero (HeroSingle's section, the slideshow's viewport) is a column
|
|
2411
|
+
at least as tall as the height that applies at this width: the set
|
|
2412
|
+
phone height (`--sf-hero-h-m`, which the builder writes on the
|
|
2413
|
+
element), from 720px the set desktop height (`--sf-hero-h-d`; 720 is
|
|
2414
|
+
the height control's own line and picks WHICH height, nothing else —
|
|
2415
|
+
the rule just below this block), or, with no set height,
|
|
2416
|
+
`max(100vh, 680px)`: the pixels `h-screen min-h-[680px]` drew;
|
|
2417
|
+
· the slideshow's track fills that column and is as tall as its tallest
|
|
2418
|
+
slide (a flex row stretches its slides), and each slide is a column;
|
|
2419
|
+
· the word layer joins the flow, fills its hero or slide, and is its
|
|
2420
|
+
width. `width: 100%` and not stretch: the layer keeps `mx-auto` for the
|
|
2421
|
+
wide screen, and in a flex column auto side margins switch stretching
|
|
2422
|
+
off, so it would shrink to its widest word and `lib/fit-words` would
|
|
2423
|
+
never see that word overflow.
|
|
2424
|
+
Words that already fit stay exactly where the bottom-aligned stack put
|
|
2425
|
+
them. 801px and up is untouched.
|
|
2426
|
+
|
|
2427
|
+
HERE, on the hero's one phone line, and not as `max-[720px]:` utilities on
|
|
2428
|
+
the components (17.0.1 did that, and drew the layout on a second line —
|
|
2429
|
+
between 720 and 800 the old overflow stayed). UNLAYERED, like the rest of
|
|
2430
|
+
this block: it must beat the height and position utilities the elements
|
|
2431
|
+
carry for their wide layout. Selected by the hooks the hero already has.
|
|
2432
|
+
`scripts/test-hero-grows-on-phone.ts` resolves every line at 799, 800 and
|
|
2433
|
+
801. */
|
|
2434
|
+
section:not([data-slide-name]):has(> .sf-hero-group-layer),
|
|
2435
|
+
div:has(> .hero-track) {
|
|
2436
|
+
display: flex;
|
|
2437
|
+
flex-direction: column;
|
|
2438
|
+
height: auto;
|
|
2439
|
+
min-height: var(--sf-hero-h-m, max(100vh, 680px));
|
|
2440
|
+
}
|
|
2441
|
+
.hero-track {
|
|
2442
|
+
height: auto;
|
|
2443
|
+
flex: 1 1 auto;
|
|
2444
|
+
}
|
|
2445
|
+
.hero-track > * {
|
|
2446
|
+
height: auto;
|
|
2447
|
+
}
|
|
2448
|
+
section[data-slide-name] {
|
|
2449
|
+
display: flex;
|
|
2450
|
+
flex-direction: column;
|
|
2451
|
+
height: auto;
|
|
2452
|
+
}
|
|
2453
|
+
.sf-hero-group-layer {
|
|
2454
|
+
position: relative;
|
|
2455
|
+
flex: 1 1 auto;
|
|
2456
|
+
width: 100%;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
/* …and WHICH height is the minimum between 720 and 800: the desktop one, as
|
|
2460
|
+
the builder's own `min-[720px]:h-[var(--sf-hero-h-d)]` says (VIEW_TABLET_MIN,
|
|
2461
|
+
the line the Height control switches on). Only the height is chosen here;
|
|
2462
|
+
the layout above is on the hero's one line, 800. */
|
|
2463
|
+
@media (min-width: 720px) and (max-width: 800px) {
|
|
2464
|
+
section:not([data-slide-name]):has(> .sf-hero-group-layer),
|
|
2465
|
+
div:has(> .hero-track) {
|
|
2466
|
+
min-height: var(--sf-hero-h-d, max(100vh, 680px));
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/* ── NEGATIVE GAP = THE BLOCKS OVERLAP (card ss.negative-gap) ────────────────
|
|
2471
|
+
The other half of `containerGapProps` in lib/content/container-gap.ts, which
|
|
2472
|
+
is where the whole argument for this shape is written down. In short: CSS
|
|
2473
|
+
`gap` rejects a negative length outright — `gap: -12px` is an invalid
|
|
2474
|
+
declaration, the browser drops it, and the page lays out exactly as it does at
|
|
2475
|
+
`gap: 0` (measured in Chromium 2026-09-03). So below zero the render stops
|
|
2476
|
+
asking `gap` and pulls the blocks together with a margin instead.
|
|
2477
|
+
|
|
2478
|
+
HALF THE OVERLAP ON EVERY CHILD, and the same half given back to the slot as
|
|
2479
|
+
padding. Two neighbours each move half way, so they meet the full overlap
|
|
2480
|
+
inside each other, and the group's own outer edge does not move at all. The
|
|
2481
|
+
half arrives as `--sf-gap-overlap`, because it is a runtime number and a class
|
|
2482
|
+
cannot carry one.
|
|
2483
|
+
|
|
2484
|
+
⚠️ NOT `> * + *`, which is one rule shorter and wrong twice: on a wrapping row
|
|
2485
|
+
it indents the first block of every line after the first (CSS cannot see that
|
|
2486
|
+
the line broke), and between grid rows it does nothing at all, because grid
|
|
2487
|
+
rows are tracks rather than siblings. Both measured failing first.
|
|
2488
|
+
|
|
2489
|
+
ONLY REACHED BELOW ZERO. Nothing on this page is emitted for a gap of 0 or
|
|
2490
|
+
more, which is what makes this card unable to move a single live site: the
|
|
2491
|
+
floor was 0 until today, so no stored document holds a negative.
|
|
2492
|
+
|
|
2493
|
+
UNLAYERED, like the hero rules above, so these beat the Tailwind utilities the
|
|
2494
|
+
same elements carry. */
|
|
2495
|
+
/* ── The gap between the blocks inside a container ────────────────────────────
|
|
2496
|
+
The LENGTH arrives as `--sf-gap-len` from lib/content/container-gap.ts, which
|
|
2497
|
+
sets it to either a stored number (`24px`) or, when the container is
|
|
2498
|
+
inheriting, the site token with that container's own built-in as the fallback
|
|
2499
|
+
(`var(--sf-block-gap, 24px)`). So the cascade does the inheriting and no site
|
|
2500
|
+
value is ever plumbed through React to get here.
|
|
2501
|
+
|
|
2502
|
+
⚠️ HALVED BELOW 800px, UNCONDITIONALLY — owner's ruling 2026-09-03, chosen
|
|
2503
|
+
over "only when the merchant set a value" with the cost stated. Every live
|
|
2504
|
+
page's PHONE spacing therefore changes the day this ships: Grid and Box 24→12,
|
|
2505
|
+
Footer box 16→8, Events 24→12, Product range 8→4, the wheels listing 1rem→0.5rem.
|
|
2506
|
+
DESKTOP does not move by a pixel. This is the ruling, not a bug — anyone
|
|
2507
|
+
reading a "the gaps got smaller on mobile" report should stop here.
|
|
2508
|
+
|
|
2509
|
+
800px is the breakpoint the Gallery already shipped a 2:1 step at
|
|
2510
|
+
(`gap-2 max-[800px]:gap-1`), so the one number every container now halves at
|
|
2511
|
+
is the one number one of them already used, rather than a sixth opinion.
|
|
2512
|
+
|
|
2513
|
+
`calc(… / 2)` rather than a second property from the render: the halving is a
|
|
2514
|
+
RULE, and a rule belongs in the stylesheet where it is stated once, not in
|
|
2515
|
+
four call sites that could each forget it.
|
|
2516
|
+
|
|
2517
|
+
UNLAYERED, like the overlap rules below, so these beat the Tailwind gap
|
|
2518
|
+
utilities the same elements still carry. */
|
|
2519
|
+
.sf-gap-zone {
|
|
2520
|
+
gap: var(--sf-gap-len);
|
|
2521
|
+
}
|
|
2522
|
+
@media (max-width: 800px) {
|
|
2523
|
+
.sf-gap-zone {
|
|
2524
|
+
gap: calc(var(--sf-gap-len) / 2);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
/* The overlap below zero halves on the same screen, for the same reason and by
|
|
2529
|
+
the same ruling. It needs its OWN derived property rather than redefining
|
|
2530
|
+
`--sf-gap-overlap`: a custom property cannot be defined in terms of itself,
|
|
2531
|
+
so `--sf-gap-overlap: calc(var(--sf-gap-overlap) / 2)` is a cycle and CSS
|
|
2532
|
+
throws the whole declaration away — silently, which is the shape of bug this
|
|
2533
|
+
repo keeps writing guards for. One extra name, and the four rules below read
|
|
2534
|
+
it instead. */
|
|
2535
|
+
.sf-gap-overlap {
|
|
2536
|
+
--sf-gap-overlap-eff: var(--sf-gap-overlap);
|
|
2537
|
+
}
|
|
2538
|
+
@media (max-width: 800px) {
|
|
2539
|
+
.sf-gap-overlap {
|
|
2540
|
+
--sf-gap-overlap-eff: calc(var(--sf-gap-overlap) / 2);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
.sf-gap-overlap > * {
|
|
2544
|
+
/* WHICH BLOCK SITS ON TOP, pinned rather than left to chance — owner's pick
|
|
2545
|
+
2026-09-03: the block LOWER in the Layers list paints over the one above it,
|
|
2546
|
+
and a merchant changes it by dragging in Layers.
|
|
2547
|
+
|
|
2548
|
+
That is already what CSS does between plain siblings, but only while every
|
|
2549
|
+
one of them is unpositioned: measured 2026-09-03, a column of two where the
|
|
2550
|
+
FIRST block alone sets `position: relative` paints the first ON TOP, which
|
|
2551
|
+
would make the answer depend on which block a merchant happened to drop.
|
|
2552
|
+
Positioning them all puts every child in the same class, where tree order
|
|
2553
|
+
decides again — measured restoring later-on-top. A block that sets its own
|
|
2554
|
+
`z-index` still wins, and that is left alone deliberately: it is a block
|
|
2555
|
+
saying something louder than a container default. */
|
|
2556
|
+
position: relative;
|
|
2557
|
+
}
|
|
2558
|
+
.sf-gap-overlap-block,
|
|
2559
|
+
.sf-gap-overlap-both {
|
|
2560
|
+
padding-block: var(--sf-gap-overlap-eff);
|
|
2561
|
+
}
|
|
2562
|
+
.sf-gap-overlap-block > *,
|
|
2563
|
+
.sf-gap-overlap-both > * {
|
|
2564
|
+
margin-block: calc(-1 * var(--sf-gap-overlap-eff));
|
|
2565
|
+
}
|
|
2566
|
+
.sf-gap-overlap-inline,
|
|
2567
|
+
.sf-gap-overlap-both {
|
|
2568
|
+
padding-inline: var(--sf-gap-overlap-eff);
|
|
2569
|
+
}
|
|
2570
|
+
.sf-gap-overlap-inline > *,
|
|
2571
|
+
.sf-gap-overlap-both > * {
|
|
2572
|
+
margin-inline: calc(-1 * var(--sf-gap-overlap-eff));
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/* ── Grid cell width (the cell-width card) ──────────────────────────────────
|
|
2576
|
+
How many of a Grid row's positions ONE block takes.
|
|
2577
|
+
|
|
2578
|
+
⚠️ THE RULE IS WRITTEN TWICE ON PURPOSE, AND DELETING EITHER HALF BREAKS
|
|
2579
|
+
EXACTLY ONE OF THE TWO PLACES THIS RUNS. Puck 0.20 wraps every slot child in
|
|
2580
|
+
a drag element of its own unless the component declares `inline`
|
|
2581
|
+
(components/HeroGroup.tsx carries the measurement, card 66290). So:
|
|
2582
|
+
|
|
2583
|
+
published page .sf-grid-zone > [data-sf-span] the block itself is
|
|
2584
|
+
the grid item
|
|
2585
|
+
editor canvas .sf-grid-zone > *:has(> [data-sf-span]) Puck's wrapper is
|
|
2586
|
+
the grid item, and
|
|
2587
|
+
the attribute is one
|
|
2588
|
+
level inside it
|
|
2589
|
+
|
|
2590
|
+
A rule written only the first way is the defect the hero anchor shipped with
|
|
2591
|
+
for weeks: right on the live site, silently ignored in the editor, and it
|
|
2592
|
+
reads as a styling question rather than a wrapper one. `:has()` is how the
|
|
2593
|
+
width reaches the wrapper — it is the only direction CSS travels upward, and
|
|
2594
|
+
it is already this stylesheet's own idiom (see the lead-ink rules above and
|
|
2595
|
+
the overlay rules in storefront-editor.css).
|
|
2596
|
+
|
|
2597
|
+
NOT `display: contents` on the wrapper, which is the other way to make the
|
|
2598
|
+
block the grid item: Puck MEASURES that element to place a drop and to draw
|
|
2599
|
+
its selection overlay, and `display: contents` removes the box and takes the
|
|
2600
|
+
measurement with it. The same warning is written out at .sf-hero-group-zone.
|
|
2601
|
+
|
|
2602
|
+
A block with NO span stamps no attribute and matches nothing here, so it
|
|
2603
|
+
auto-places into one position exactly as it did before this card. */
|
|
2604
|
+
.sf-grid-zone {
|
|
2605
|
+
grid-template-columns: repeat(var(--sf-grid-cols, 3), minmax(0, 1fr));
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
.sf-grid-zone > [data-sf-span="1"],
|
|
2609
|
+
.sf-grid-zone > *:has(> [data-sf-span="1"]) { grid-column: span 1; }
|
|
2610
|
+
.sf-grid-zone > [data-sf-span="2"],
|
|
2611
|
+
.sf-grid-zone > *:has(> [data-sf-span="2"]) { grid-column: span 2; }
|
|
2612
|
+
.sf-grid-zone > [data-sf-span="3"],
|
|
2613
|
+
.sf-grid-zone > *:has(> [data-sf-span="3"]) { grid-column: span 3; }
|
|
2614
|
+
.sf-grid-zone > [data-sf-span="4"],
|
|
2615
|
+
.sf-grid-zone > *:has(> [data-sf-span="4"]) { grid-column: span 4; }
|
|
2616
|
+
.sf-grid-zone > [data-sf-span="5"],
|
|
2617
|
+
.sf-grid-zone > *:has(> [data-sf-span="5"]) { grid-column: span 5; }
|
|
2618
|
+
.sf-grid-zone > [data-sf-span="6"],
|
|
2619
|
+
.sf-grid-zone > *:has(> [data-sf-span="6"]) { grid-column: span 6; }
|
|
2620
|
+
.sf-grid-zone > [data-sf-span="7"],
|
|
2621
|
+
.sf-grid-zone > *:has(> [data-sf-span="7"]) { grid-column: span 7; }
|
|
2622
|
+
.sf-grid-zone > [data-sf-span="8"],
|
|
2623
|
+
.sf-grid-zone > *:has(> [data-sf-span="8"]) { grid-column: span 8; }
|
|
2624
|
+
.sf-grid-zone > [data-sf-span="9"],
|
|
2625
|
+
.sf-grid-zone > *:has(> [data-sf-span="9"]) { grid-column: span 9; }
|
|
2626
|
+
.sf-grid-zone > [data-sf-span="10"],
|
|
2627
|
+
.sf-grid-zone > *:has(> [data-sf-span="10"]) { grid-column: span 10; }
|
|
2628
|
+
.sf-grid-zone > [data-sf-span="11"],
|
|
2629
|
+
.sf-grid-zone > *:has(> [data-sf-span="11"]) { grid-column: span 11; }
|
|
2630
|
+
.sf-grid-zone > [data-sf-span="12"],
|
|
2631
|
+
.sf-grid-zone > *:has(> [data-sf-span="12"]) { grid-column: span 12; }
|
|
2632
|
+
|
|
2633
|
+
/* The phone. A span is a DESKTOP statement — twelve positions across a phone is
|
|
2634
|
+
unreadable — so below the breakpoint every cell takes one position of a
|
|
2635
|
+
1–3 column row. `grid-column: auto` beats the span rules above on order, not
|
|
2636
|
+
on specificity, which is why this block sits after them. */
|
|
2637
|
+
@media (max-width: 800px) {
|
|
2638
|
+
.sf-grid-zone {
|
|
2639
|
+
grid-template-columns: repeat(var(--sf-grid-cols-phone, 1), minmax(0, 1fr));
|
|
2640
|
+
}
|
|
2641
|
+
.sf-grid-zone > [data-sf-span],
|
|
2642
|
+
.sf-grid-zone > *:has(> [data-sf-span]) { grid-column: auto; }
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
/* A cell's HEIGHT — the same wrapper as the span rules above, and the same two
|
|
2646
|
+
halves ([FIX] Grid Cell Card Stretches In The Editor):
|
|
2647
|
+
|
|
2648
|
+
published page .sf-grid-zone > * the block is the grid
|
|
2649
|
+
item, and the grid's
|
|
2650
|
+
own default stretch
|
|
2651
|
+
makes it the row height.
|
|
2652
|
+
No rule: there is
|
|
2653
|
+
nothing to add.
|
|
2654
|
+
editor canvas .sf-grid-zone > [data-puck-component] the editor's drag
|
|
2655
|
+
wrapper is the grid
|
|
2656
|
+
item. IT stretches;
|
|
2657
|
+
the block inside it
|
|
2658
|
+
is a plain block, as
|
|
2659
|
+
tall as its words.
|
|
2660
|
+
|
|
2661
|
+
Measured on the Hartes homepage (first Grid, 1440): the white card was 774px
|
|
2662
|
+
live and 638px in the canvas, so the black page showed under it.
|
|
2663
|
+
|
|
2664
|
+
The editor half makes the wrapper a grid of ONE column. Its child — or, through
|
|
2665
|
+
a `display: contents` host the editor adds, its grandchild — is then a grid
|
|
2666
|
+
item, and a grid item stretches to its area, so the block is the cell's height
|
|
2667
|
+
exactly as it is live. `minmax(0, 1fr)` keeps the width what a block child had:
|
|
2668
|
+
the wrapper's own width, allowed to shrink below its content.
|
|
2669
|
+
|
|
2670
|
+
Matched on the attribute, not with `:has()` as the span rules are: a span is
|
|
2671
|
+
a property of the BLOCK, so the rule has to look inside; a stretch is a
|
|
2672
|
+
property of EVERY wrapper in this zone. The published page never carries the
|
|
2673
|
+
attribute, so this cannot touch a live layout. A block declared `inline`
|
|
2674
|
+
carries the attribute on its own root and would become a grid here — none is
|
|
2675
|
+
droppable into a Grid, and scripts/test-cell-stretch.ts holds that.
|
|
2676
|
+
|
|
2677
|
+
Still NOT `display: contents` on the wrapper — see the warning on the span
|
|
2678
|
+
rules above: the editor measures that element for drops and the outline. */
|
|
2679
|
+
.sf-grid-zone > [data-puck-component] {
|
|
2680
|
+
display: grid;
|
|
2681
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
/* …and the same HEIGHT in a Box laid out in one ROW, and in a Slide box set to
|
|
2685
|
+
One row ([FIX] Box Row Children Stretch In The Editor). The same wrapper, the
|
|
2686
|
+
same two halves, one container over:
|
|
2687
|
+
|
|
2688
|
+
published page row > * the block is the flex
|
|
2689
|
+
item, and the row's
|
|
2690
|
+
stretch makes it the
|
|
2691
|
+
row height. No rule.
|
|
2692
|
+
editor canvas row > [data-puck-component] the drag wrapper is
|
|
2693
|
+
the flex item. IT
|
|
2694
|
+
stretches; the block
|
|
2695
|
+
inside it is as tall
|
|
2696
|
+
as its words.
|
|
2697
|
+
|
|
2698
|
+
Measured with scripts/probe-row-stretch.tsx on the real Box markup: a block
|
|
2699
|
+
beside a 774px block was 774px live and 638px in the canvas, for a Box row
|
|
2700
|
+
with Align unset or Stretch and for a Slide box row.
|
|
2701
|
+
|
|
2702
|
+
WHICH ROWS. A row stretches unless it was told where to sit, so the selector
|
|
2703
|
+
is "a row with no Start, Center or End on it". That covers Stretch, and an
|
|
2704
|
+
Align never stored (every older Box, every Slide box — the flex default is
|
|
2705
|
+
stretch). A Box with a picture always carries an anchor, so it never matches.
|
|
2706
|
+
The row class is a general one, so the reach was counted in the live config
|
|
2707
|
+
(every block with a slot, rendered): only three zones ever wear it — the Box,
|
|
2708
|
+
the Slide box, and a Footer group set to Horizontal, whose panel always
|
|
2709
|
+
stores Start, Center or End.
|
|
2710
|
+
A Stacked box has no row class, so a column zone never matches — and that is
|
|
2711
|
+
on purpose: there the wrapper already fills the width, and a grid would also
|
|
2712
|
+
stop the block's margins collapsing out of it.
|
|
2713
|
+
|
|
2714
|
+
⚠️ UNDER 800px A BOX ROW BECOMES A COLUMN AND KEEPS ITS ROW CLASS, so the rule
|
|
2715
|
+
still applies there (and in a Slide box row at 800px and under). That is
|
|
2716
|
+
measured, not assumed: at 390 every height and width is unchanged except one
|
|
2717
|
+
— a block whose words carry a margin was 618px in the canvas and 638px live,
|
|
2718
|
+
because the margin collapsed out of the block into the wrapper. The grid
|
|
2719
|
+
wrapper keeps it inside, so the canvas now draws 638 as live does. A Stacked
|
|
2720
|
+
box still draws 618 in the canvas; that is the column case above, left alone.
|
|
2721
|
+
|
|
2722
|
+
The class names are the ones lib/content/container-direction and
|
|
2723
|
+
lib/content/box-layout emit; scripts/test-cell-stretch.ts §4 reads them from
|
|
2724
|
+
there, so a renamed class goes red there instead of silently matching nothing
|
|
2725
|
+
here.
|
|
2726
|
+
|
|
2727
|
+
`100%` AND NOT THE CELL'S `minmax(0, 1fr)`, and the reason is the width. A
|
|
2728
|
+
grid cell is sized by the grid, so its wrapper may shrink below its words. A
|
|
2729
|
+
flex item is not: its smallest size is its content's, unless the row says
|
|
2730
|
+
otherwise. A `minmax(0, 1fr)` column makes the wrapper's smallest size zero,
|
|
2731
|
+
so a word wider than the row was squeezed in the canvas where live it runs
|
|
2732
|
+
out. A `100%` column is the content's size while the row is measuring, and
|
|
2733
|
+
exactly the wrapper's width once it is laid out — which is what a plain
|
|
2734
|
+
block child was. The probe holds both: every width is unchanged.
|
|
2735
|
+
|
|
2736
|
+
`:not([class])` — ONLY EASEL'S WRAPPER, NEVER A BLOCK'S OWN ROOT. A block
|
|
2737
|
+
declared `inline` (the Slide box) has no wrapper: Easel puts the SAME two
|
|
2738
|
+
attributes on the block's own root, which is already the flex item and
|
|
2739
|
+
already stretched, and must not become a grid. No attribute tells the two
|
|
2740
|
+
apart. Their shape does: Easel draws its wrapper as a bare div with no class
|
|
2741
|
+
(efficient-easel/easel), and every block root draws one. §4 holds both.
|
|
2742
|
+
|
|
2743
|
+
⚠️ THREE `:not()`s, NOT ONE WITH A LIST, and no comma anywhere in the
|
|
2744
|
+
selector. scripts/css-rules.ts — the reader the hero guards resolve real
|
|
2745
|
+
classes with — splits a rule on its commas, so `:not(.a, .b)` reached it as a
|
|
2746
|
+
bare `.b`, and it handed `display: grid` to the hero's arrows and dot row. A
|
|
2747
|
+
browser reads both spellings the same; §4 holds the reader too.
|
|
2748
|
+
|
|
2749
|
+
Still NOT `display: contents` on the wrapper, for the reason above. */
|
|
2750
|
+
.flex-row:not(.items-start):not(.items-center):not(.items-end) > [data-puck-component]:not([class]) {
|
|
2751
|
+
display: grid;
|
|
2752
|
+
grid-template-columns: 100%;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
/* ── THE PHOTO WALL'S LAYOUT ────────────────────────────────────────────────
|
|
2756
|
+
The Customer Photo Grid draws one of `lib/content/gallery-layout.ts`'s
|
|
2757
|
+
patterns. THE PER-LAYOUT RULES ARE NOT HERE: nineteen layouts over two tracks
|
|
2758
|
+
is four hundred-odd nth-child rules, and every page would carry all of them.
|
|
2759
|
+
`galleryLayoutCss()` emits the chosen one beside the wall, which is this
|
|
2760
|
+
repo's own inline-<style> idiom (ProductGridSection, ContactLocations, FAQ).
|
|
2761
|
+
What lives here is only what every layout shares.
|
|
2762
|
+
|
|
2763
|
+
⚠️ NOTHING BELOW APPLIES UNTIL A LAYOUT IS CHOSEN. The class is stamped only
|
|
2764
|
+
by the chosen-layout branch, so every published gallery — all of them today —
|
|
2765
|
+
renders the three-step wall it always has, byte for byte.
|
|
2766
|
+
|
|
2767
|
+
⚠️ THE ROW HEIGHT IS THE WHOLE REASON THIS BLOCK EXISTS. A tile sizes itself
|
|
2768
|
+
from its WIDTH (`galleryCardRatioClass`, square by default), so a wide photo
|
|
2769
|
+
is automatically a tall photo and "span two rows" would mean nothing. Inside a
|
|
2770
|
+
wall the CELL decides instead: the rows are a fixed height, a two-row photo is
|
|
2771
|
+
exactly twice a one-row photo, and the card stretches to fill whatever it was
|
|
2772
|
+
given. That is what makes a feature photo actually big rather than merely
|
|
2773
|
+
wide.
|
|
2774
|
+
|
|
2775
|
+
The card's own ratio is overridden by specificity, not by `!important` —
|
|
2776
|
+
`.sf-gallery-zone .sf-card` is two classes against Tailwind's one. */
|
|
2777
|
+
.sf-gallery-zone {
|
|
2778
|
+
display: grid;
|
|
2779
|
+
grid-auto-flow: row dense;
|
|
2780
|
+
/* One row of the wall. Scales with the viewport so a wall is not a strip of
|
|
2781
|
+
stamps on a wide screen nor a stack of billboards on a small one. */
|
|
2782
|
+
grid-auto-rows: clamp(140px, 15vw, 260px);
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
/* A cell may be narrower than its content wants; without this a long caption
|
|
2786
|
+
pushes the column wider and the whole wall drifts off the pattern. */
|
|
2787
|
+
.sf-gallery-zone > * {
|
|
2788
|
+
min-width: 0;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
/* The tile fills the cell it was given, through Puck's wrapper in the editor and
|
|
2792
|
+
directly on the published page — the same "both are the grid item" fact the
|
|
2793
|
+
nth-child rules rely on. */
|
|
2794
|
+
.sf-gallery-zone > *,
|
|
2795
|
+
.sf-gallery-zone > * > * {
|
|
2796
|
+
height: 100%;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
/* …and the card stops deciding its own shape, because the layout decided it. */
|
|
2800
|
+
.sf-gallery-zone .sf-card {
|
|
2801
|
+
aspect-ratio: auto;
|
|
2802
|
+
height: 100%;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
@media (max-width: 800px) {
|
|
2806
|
+
.sf-gallery-zone {
|
|
2807
|
+
grid-auto-rows: clamp(90px, 26vw, 150px);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
/* ── A SECTION'S OWN BOX, ROUNDED ───────────────────────────────────────────
|
|
2812
|
+
Lewis, 2026-09-04: the corner setting is for the BOX. Before this it was not:
|
|
2813
|
+
`sectionCornerStyle` publishes the radius as custom properties onto a carrier
|
|
2814
|
+
that is `display: contents`, so the value reached anything INSIDE the section
|
|
2815
|
+
that already read a radius — a card, a `rounded-*` utility — and never the
|
|
2816
|
+
section's own edge. Measured the same day on origin/main: of the 71 sections
|
|
2817
|
+
carrying the Shape group, only 20 wear `.sf-card` and had anything inside to
|
|
2818
|
+
catch the value at all. The other 51 include Hero Slideshow, Collections, Stats
|
|
2819
|
+
and Gallery, and he found it by setting 27px on a Hero and watching nothing
|
|
2820
|
+
happen. (Re-measured 2026-09-05; an earlier 22/49 was taken before main moved.)
|
|
2821
|
+
|
|
2822
|
+
⚠️ THE CARRIER STAYS `display: contents`, AND THIS IS WHY IT CAN. The obvious
|
|
2823
|
+
fix — make the carrier a real box and put the radius on it — is the one thing
|
|
2824
|
+
`lib/puck/config.tsx` says not to do: a block box between a section and its
|
|
2825
|
+
parent breaks a full-viewport cover's `absolute inset-0` and any sticky child.
|
|
2826
|
+
A `display: contents` element generates no box but IS still in the DOM for
|
|
2827
|
+
selectors, and its child is the section's own root — measured 2026-09-05 by
|
|
2828
|
+
walking the rendered React tree of every one: ALL 71 carriers hold exactly one
|
|
2829
|
+
element, none holds more, and none failed to render. (An earlier count of 64
|
|
2830
|
+
was a limit of the measurement, not of the sections.) So this paints the box the section already had rather than adding one,
|
|
2831
|
+
and the layout is untouched at every value.
|
|
2832
|
+
|
|
2833
|
+
⚠️ AND IT ONLY EXISTS WHEN THE SECTION HAS CHOSEN. The carrier is emitted only
|
|
2834
|
+
for a section with its own corner (an inheriting one returns the render
|
|
2835
|
+
untouched), so this selector matches nothing on an untouched page and the
|
|
2836
|
+
zero-regression claim `test-section-corners` makes is unchanged.
|
|
2837
|
+
|
|
2838
|
+
`overflow: clip` for the reason `.sf-card` uses it rather than `hidden`: a
|
|
2839
|
+
rounded box whose photo runs to its own edge would otherwise wear a square
|
|
2840
|
+
image over a round corner, and `clip` does that without making a scroll
|
|
2841
|
+
container — so a sticky child inside a section keeps working.
|
|
2842
|
+
|
|
2843
|
+
⚠️ IT READS `--sf-section-radius`, NOT THE CARD PROPERTY, AND THAT IS THE
|
|
2844
|
+
WHOLE OF THE TWO-TIER SPLIT. It read `--sf-card-radius` for one day, which
|
|
2845
|
+
meant the section's own edge and every card inside it were one number and
|
|
2846
|
+
could never differ — Lewis asked for a Product Grid rounded one way with its
|
|
2847
|
+
product cards rounded another (2026-09-04). The two properties are named and
|
|
2848
|
+
explained in lib/content/section-corners.ts. That control now exists — a row on
|
|
2849
|
+
the CARD BLOCK, where Lewis ruled on 2026-09-05 it belongs — and it writes a
|
|
2850
|
+
THIRD property, `--sf-card-box-radius`, so this rule is still the section's own
|
|
2851
|
+
edge and nothing else. The fallback `0` costs nothing today and is what stops
|
|
2852
|
+
this rule painting a box on any future carrier that arrives without a section
|
|
2853
|
+
radius. */
|
|
2854
|
+
.sf-section-corners > * { border-radius: var(--sf-section-radius, 0); overflow: clip; }
|
|
2855
|
+
|
|
2856
|
+
/* ── A SECTION'S (AND A BOX'S) OWN BORDER ────────────────────────────────────
|
|
2857
|
+
Box Border, approved by Lewis 2026-10-15. efficient-web-builder 17.0.0's one
|
|
2858
|
+
resolver (`lib/content/borders.ts`) publishes the Border a section or a Box
|
|
2859
|
+
sets as custom properties on a `display: contents` carrier,
|
|
2860
|
+
`.sf-section-border`, emitted ONLY when the section has a border of its own —
|
|
2861
|
+
so this selector matches nothing on an untouched page. The line is drawn on
|
|
2862
|
+
the carrier's CHILD, the section's own box, as the radius above is.
|
|
2863
|
+
|
|
2864
|
+
Only what was set is published, so each `var()` falls back to "nothing set":
|
|
2865
|
+
an unset side is 0 (no line), an unset style is solid, an unset colour is the
|
|
2866
|
+
site's Borders colour (`--site-line`, the theme's `line`). A Box inside a
|
|
2867
|
+
section that has a border inherits the section's properties for whatever it
|
|
2868
|
+
did not set itself: the Box follows the tier above, as Corners does.
|
|
2869
|
+
|
|
2870
|
+
On a phone the `-phone` properties come first and the desktop ones are the
|
|
2871
|
+
fallback, so an unset phone value follows the desktop. The boundary is the
|
|
2872
|
+
phone padding's below (`not all and (min-width: 720px)`, lib/content/
|
|
2873
|
+
view-tiers' VIEW_TABLET_MIN). scripts/test-section-border-css.ts.
|
|
2874
|
+
|
|
2875
|
+
`.sf-section-border-self` is the same line drawn on the block's OWN root:
|
|
2876
|
+
efficient-web-builder 18.2.0 puts it there for a Box inside a row or another
|
|
2877
|
+
Box, where a carrier would take the row's place and break its layout. The
|
|
2878
|
+
properties sit on that root itself, so the rule reads them the same way. */
|
|
2879
|
+
.sf-section-border > *,
|
|
2880
|
+
.sf-section-border-self {
|
|
2881
|
+
border-style: var(--sf-border-style, solid);
|
|
2882
|
+
border-color: var(--sf-border-color, var(--site-line));
|
|
2883
|
+
border-width: var(--sf-border-top-width, 0) var(--sf-border-right-width, 0) var(--sf-border-bottom-width, 0) var(--sf-border-left-width, 0);
|
|
2884
|
+
}
|
|
2885
|
+
@media not all and (min-width: 720px) {
|
|
2886
|
+
.sf-section-border > *,
|
|
2887
|
+
.sf-section-border-self {
|
|
2888
|
+
border-style: var(--sf-border-style-phone, var(--sf-border-style, solid));
|
|
2889
|
+
border-color: var(--sf-border-color-phone, var(--sf-border-color, var(--site-line)));
|
|
2890
|
+
border-width: var(--sf-border-top-width-phone, var(--sf-border-top-width, 0)) var(--sf-border-right-width-phone, var(--sf-border-right-width, 0)) var(--sf-border-bottom-width-phone, var(--sf-border-bottom-width, 0)) var(--sf-border-left-width-phone, var(--sf-border-left-width, 0));
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
/* ── A SECTION THAT RUNS EDGE TO EDGE HANDS ITS CARDS BACK TO THE SITE ────────
|
|
2895
|
+
Lewis, 2026-09-10: a card stops following its section when the section
|
|
2896
|
+
ACTUALLY RENDERS EDGE TO EDGE. Such a section has no visible edges, so its
|
|
2897
|
+
corner is off the page and passing that number down would round the cards by
|
|
2898
|
+
a value whose source the merchant cannot see. The class is put on the section's
|
|
2899
|
+
own corners carrier by the injection pass in lib/puck/config.tsx, off
|
|
2900
|
+
`sectionRunsFullWidth` — NEVER off a bare `mode === "full"`, which reads every
|
|
2901
|
+
untouched section as full. lib/content/card-corners.ts carries the whole
|
|
2902
|
+
ruling, including why the site's Hero image switch does not enter it.
|
|
2903
|
+
|
|
2904
|
+
⚠️ IT LEADS WITH THE CARD'S OWN PROPERTY, and that is what makes source order
|
|
2905
|
+
irrelevant: a card that answered for itself wins here exactly as it wins in
|
|
2906
|
+
`.sf-card` above, whichever rule the cascade happens to pick.
|
|
2907
|
+
|
|
2908
|
+
⚠️ AND THE FALLBACK IS `--sf-site-card-radius`, NOT `--sf-card-radius`. Inside
|
|
2909
|
+
a section that has its own corner, `--sf-card-radius` IS the section's answer —
|
|
2910
|
+
that is the whole two-tier split — so reaching past it needs a name only the
|
|
2911
|
+
two roots publish. */
|
|
2912
|
+
.sf-section-bleeds .sf-card { border-radius: var(--sf-card-box-radius, var(--sf-site-card-radius, 0)); }
|
|
2913
|
+
|
|
2914
|
+
/* ── A Scrolling Marquee that sits INSIDE the section next to it ──────────────
|
|
2915
|
+
`lib/sections/ticker-join.ts` carries the why. The short of it: a full-screen
|
|
2916
|
+
section is 100vh, so a marquee under it lands below the fold; joined, the
|
|
2917
|
+
strip takes NO page height and is pinned to the boundary instead, which puts
|
|
2918
|
+
it inside that first screen. The Hero's own strip has always done this with
|
|
2919
|
+
`absolute inset-x-0 bottom-0` (components/HeroSection.tsx) — the box below is
|
|
2920
|
+
what lets a SIBLING section do the same, by putting a containing block on the
|
|
2921
|
+
seam for it to hang off.
|
|
2922
|
+
|
|
2923
|
+
⚠️ THE `z-index` IS ON THE BOX, NOT ON THE STRIP, and moving it back breaks
|
|
2924
|
+
the thing it protects. The strip's own `z-[2]` (PR #515) lifts it over a
|
|
2925
|
+
pinned Scroll-Over Intro, but a z-index inside a positioned box only orders
|
|
2926
|
+
the box's own children — it cannot lift the pair past a SIBLING section. So
|
|
2927
|
+
the pair's stacking order has to be declared here, on the element that is
|
|
2928
|
+
actually the sibling, and it is the same 2 for the same reason.
|
|
2929
|
+
|
|
2930
|
+
⚠️ `height: 0` MUST NOT BECOME `height: 0; overflow: hidden`. The strip is
|
|
2931
|
+
entirely outside its box on purpose — that is what "inside the section next
|
|
2932
|
+
to it" means — so any clipping on this element erases the marquee. See the
|
|
2933
|
+
corners rule directly below, which exists because the section-corners carrier
|
|
2934
|
+
would have done exactly that. */
|
|
2935
|
+
.sf-ticker-join { position: relative; height: 0; z-index: 2; }
|
|
2936
|
+
.sf-ticker-join-above { bottom: 0; }
|
|
2937
|
+
.sf-ticker-join-below { top: 0; }
|
|
2938
|
+
|
|
2939
|
+
/* ⛔ THE CORNERS CARRIER WOULD HAVE CLIPPED THE STRIP AWAY, and this is the two
|
|
2940
|
+
lines that stop it. `.sf-section-corners > *` (above) rounds and CLIPS a
|
|
2941
|
+
section's outermost box; for a joined marquee that box is the zero-height one,
|
|
2942
|
+
so `overflow: clip` on a 0px-tall element deleted the whole marquee from the
|
|
2943
|
+
page while the panel still reported a corner radius. Lewis asked on 2026-09-07
|
|
2944
|
+
that the border and the corners stay the marquee's own settings, so the fix is
|
|
2945
|
+
to move the rounding one level in — onto the strip, which is the box a
|
|
2946
|
+
merchant is actually looking at — rather than to exclude Ticker from the
|
|
2947
|
+
corners pass and take the control away.
|
|
2948
|
+
|
|
2949
|
+
The specificity is (0,2,0) against the (0,2,0) of the rule above, so these
|
|
2950
|
+
come LAST on purpose: source order is what decides, exactly as the
|
|
2951
|
+
`.sf-section-surface-*` pairs in this file are ordered. */
|
|
2952
|
+
.sf-section-corners > .sf-ticker-join { border-radius: 0; overflow: visible; }
|
|
2953
|
+
.sf-section-corners > .sf-ticker-join > * { border-radius: var(--sf-section-radius, 0); overflow: clip; }
|
|
2954
|
+
|
|
2955
|
+
/* ── A section's phone padding ────────────────────────────────────────────────
|
|
2956
|
+
The phone half of the override ruled by the owner on 2026-09-07: ONE value
|
|
2957
|
+
used on both screens, and a setting MAY also carry a phone value. Set, the
|
|
2958
|
+
phone uses it; unset, the phone follows the desktop value.
|
|
2959
|
+
|
|
2960
|
+
"Unset follows the desktop" is expressed by the ABSENCE of these classes, not
|
|
2961
|
+
by a fallback inside `var()`. lib/content/section-padding.ts emits a class
|
|
2962
|
+
only for the axis that actually has a phone value, so a section with no phone
|
|
2963
|
+
value carries no class, matches no rule here, and renders byte-identically to
|
|
2964
|
+
before this block existed.
|
|
2965
|
+
|
|
2966
|
+
⚠️ `!important` IS LOAD-BEARING, and it is the reason for the class. The
|
|
2967
|
+
desktop padding is an INLINE style on the same element (sectionPadStyle), and
|
|
2968
|
+
an inline style beats any stylesheet declaration that is not `!important`.
|
|
2969
|
+
|
|
2970
|
+
⚠️ NO FALLBACK IN THE `var()`, for the same reason the grid block above gives:
|
|
2971
|
+
the class and the property are emitted together, so a fallback here would be a
|
|
2972
|
+
second, invisible answer to a question the render already answered.
|
|
2973
|
+
|
|
2974
|
+
The boundary IS lib/content/view-tiers' VIEW_TABLET_MIN. Written as `not all
|
|
2975
|
+
and (min-width: 720px)` rather than a `max-width` so the literal is the SAME
|
|
2976
|
+
720 the two blocks above use, with no fractional-pixel gap to get wrong. */
|
|
2977
|
+
@media not all and (min-width: 720px) {
|
|
2978
|
+
.sf-pad-top-phone {
|
|
2979
|
+
padding-top: var(--sf-pad-t-phone) !important;
|
|
2980
|
+
}
|
|
2981
|
+
.sf-pad-bottom-phone {
|
|
2982
|
+
padding-bottom: var(--sf-pad-b-phone) !important;
|
|
2983
|
+
}
|
|
2984
|
+
/* The sideways pair, and ONLY a Box standing on a picture ever wears these.
|
|
2985
|
+
Owner, 2026-09-09: the card's left and right inner padding has to be
|
|
2986
|
+
settable. No section has a sideways padding and these classes do not give
|
|
2987
|
+
it one — nothing emits them but `components/Flex` for a card. */
|
|
2988
|
+
.sf-pad-left-phone {
|
|
2989
|
+
padding-left: var(--sf-pad-l-phone) !important;
|
|
2990
|
+
}
|
|
2991
|
+
.sf-pad-right-phone {
|
|
2992
|
+
padding-right: var(--sf-pad-r-phone) !important;
|
|
2993
|
+
}
|
|
2994
|
+
/* A picture's PHONE focus (Lewis 2026-10-19: one picture, one dot; the view
|
|
2995
|
+
you drag in sets that view's point). The same split as the padding above:
|
|
2996
|
+
the desktop point is the picture's INLINE `object-position`, and the
|
|
2997
|
+
builder's `phoneFocus` (efficient-web-builder lib/content/phone-focus)
|
|
2998
|
+
adds this class and its variable only when a phone point is set, so a
|
|
2999
|
+
picture without one matches nothing here and draws the bytes it drew. */
|
|
3000
|
+
.sf-focus-phone {
|
|
3001
|
+
object-position: var(--sf-focus-phone) !important;
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
/* ── A card owns the room at its own sides ────────────────────────────────────
|
|
3006
|
+
Owner, 2026-09-09, choosing A over B when both were drawn: the card owns it,
|
|
3007
|
+
and the number a merchant sets is the only one.
|
|
3008
|
+
|
|
3009
|
+
⛔ WHAT THIS UNDOES, AND WHY IT IS NOT A HACK. A block inside a Box still
|
|
3010
|
+
renders its own BlockFrame, and that frame draws the SITE's Side space — so
|
|
3011
|
+
the words on a card sat 40px in from the picture on a wide screen and 24px in
|
|
3012
|
+
on a phone, from a frame that belongs to the block rather than to the card.
|
|
3013
|
+
Stacked under the Box's own frame gutter it read as 80px at the default Width,
|
|
3014
|
+
and none of it was reachable from any panel.
|
|
3015
|
+
|
|
3016
|
+
It is the same defect the owner already ruled on for the axis next door
|
|
3017
|
+
(2026-09-04: a block inside a box does not answer width, the box does). That
|
|
3018
|
+
ruling took the CONTROL away and left the BEHAVIOUR, which is why the gutter
|
|
3019
|
+
was still being drawn.
|
|
3020
|
+
|
|
3021
|
+
⚠️ SCOPED TO A CARD'S OWN CONTENT LAYER, not to every Box. The class is
|
|
3022
|
+
emitted only where there is a background picture, so an ordinary layout Box
|
|
3023
|
+
and every block on every page are untouched.
|
|
3024
|
+
|
|
3025
|
+
⚠️ AND IT ZEROES ONLY THE SIDES. `padding-top`/`bottom` on a block are its own
|
|
3026
|
+
spacing control and are left alone; blocks have no sideways spacing control at
|
|
3027
|
+
all, so there is nothing here a merchant could have set. */
|
|
3028
|
+
.sf-box-sides > * {
|
|
3029
|
+
padding-left: 0;
|
|
3030
|
+
padding-right: 0;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
/* ── A section's height on a phone ────────────────────────────────────────────
|
|
3034
|
+
The phone half of the Height group. Lewis chose 甲 on 2026-09-08: the phone
|
|
3035
|
+
overrides the group AS ONE UNIT, so a section either follows the desktop
|
|
3036
|
+
completely or holds its own mode + number + unit.
|
|
3037
|
+
|
|
3038
|
+
"Follows the desktop" is the ABSENCE of this class, not a fallback inside
|
|
3039
|
+
`var()` — lib/content/section-min-height.ts emits the class only when the node
|
|
3040
|
+
stores a phone MODE, so a section without one matches no rule here and renders
|
|
3041
|
+
byte-identically to before this block existed.
|
|
3042
|
+
|
|
3043
|
+
⚠️ `display: grid` LIVES HERE, NOT IN THE INLINE STYLE, whenever the phone is
|
|
3044
|
+
the only screen with a height. It is what makes a short section stretch to its
|
|
3045
|
+
minimum, and an inline copy would apply at every width — turning a section the
|
|
3046
|
+
merchant left on `Auto` for the desktop from a block into a grid there. Here it
|
|
3047
|
+
reaches the phone alone.
|
|
3048
|
+
|
|
3049
|
+
⚠️ `!important` because the desktop's height is an INLINE style on the same
|
|
3050
|
+
element, and an inline style beats any stylesheet rule without it.
|
|
3051
|
+
|
|
3052
|
+
The boundary IS lib/content/view-tiers' VIEW_TABLET_MIN — written as `not all
|
|
3053
|
+
and (min-width: 720px)` so the literal is the same 720 the tier blocks above
|
|
3054
|
+
use. The `@supports` leg mirrors the desktop pair: `svh` where the browser has
|
|
3055
|
+
it, the plain unit as the fallback. */
|
|
3056
|
+
@media not all and (min-width: 720px) {
|
|
3057
|
+
.sf-section-minh-phone {
|
|
3058
|
+
display: grid !important;
|
|
3059
|
+
min-height: var(--sf-section-minh-p) !important;
|
|
3060
|
+
}
|
|
3061
|
+
@supports (min-height: 1svh) {
|
|
3062
|
+
.sf-section-minh-phone {
|
|
3063
|
+
min-height: var(--sf-section-minh-ps, var(--sf-section-minh-p)) !important;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
/* ── A BOX WHOSE GAP IS THE ONLY SPACING ────────────────────────────────────
|
|
3069
|
+
Owner, 2026-09-11: 「为什么 eyebrow 没有padding, 别的text有?」 Every block frames
|
|
3070
|
+
itself with its own vertical padding and no two agree (none / 16 / 24 / 48),
|
|
3071
|
+
so the space between two blocks in a Box has three sources — the block above,
|
|
3072
|
+
the Gap, and the block below. This is the Box saying its Gap is the only one.
|
|
3073
|
+
|
|
3074
|
+
⚠️ UNLAYERED ON PURPOSE. Written as `[&>*]:py-0` it would be a Tailwind
|
|
3075
|
+
utility, landing in `@layer utilities` beside the `py-4` it must beat, at the
|
|
3076
|
+
same specificity — decided by emission order, with the larger value later. It
|
|
3077
|
+
would do nothing, silently. Unlayered CSS beats every layered rule whatever
|
|
3078
|
+
its specificity, which is why this is here and not a class in the markup.
|
|
3079
|
+
|
|
3080
|
+
`padding-block`, not `padding`: a block's own SIDE padding is its business and
|
|
3081
|
+
several blocks read the site gutter through it. */
|
|
3082
|
+
.sf-box-gap-only > * {
|
|
3083
|
+
padding-block: 0;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
/* …and a block with its OWN padding is still a block of the Box. Lewis
|
|
3087
|
+
2026-10-16: a Heading or Text has its own padding (Left/Right/Top/Bottom),
|
|
3088
|
+
drawn by the builder's wrapper, `div.sf-section-pad`, AROUND the block's
|
|
3089
|
+
frame. That wrapper is then the Box's direct child, so the rule above zeroes
|
|
3090
|
+
the wrapper and the frame's own `py-4` came back: +32px on the Hartes US
|
|
3091
|
+
footer tagline given 20px on its right. This reaches the frame inside it. The
|
|
3092
|
+
wrapper's own padding is an INLINE style, which this never beats, so what the
|
|
3093
|
+
merchant typed still draws. Unlayered, like the rule above, for its reason. */
|
|
3094
|
+
.sf-box-gap-only > .sf-section-pad > * {
|
|
3095
|
+
padding-block: 0;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
/* ── A SECTION ANIMATES IN ─────────────────────────────────────────────────────
|
|
3099
|
+
card [ADD] A Section Can Animate In — step two of three. Step one taught the
|
|
3100
|
+
site to answer `prefers-reduced-motion` (#1364, the rule directly below this
|
|
3101
|
+
one); step three draws the Animation tab. This is the motion itself.
|
|
3102
|
+
|
|
3103
|
+
THREE MOTIONS, and the three ideas behind them are written in
|
|
3104
|
+
`lib/content/section-animation.ts` rather than repeated here. What belongs
|
|
3105
|
+
HERE, and nowhere else, is the timing: CSS is what runs it, so the duration,
|
|
3106
|
+
the curve and the distance live in exactly one place and no TypeScript
|
|
3107
|
+
constant restates them. Nothing times this animation from JS — the wrapper
|
|
3108
|
+
ends on the element's own `animationend`.
|
|
3109
|
+
|
|
3110
|
+
⚠️ EVERY MOTION DECLARES A `from` AND NO `to`, AND THAT IS LOAD-BEARING.
|
|
3111
|
+
A keyframe list with no `to` animates to the element's OWN computed style, so
|
|
3112
|
+
the resting appearance of a section is never described twice. The alternative
|
|
3113
|
+
— `to { opacity: 1; transform: none }` — is a second statement of how the
|
|
3114
|
+
section looks, and it is the statement that would be wrong the first time a
|
|
3115
|
+
section arrives already carrying a transform of its own.
|
|
3116
|
+
|
|
3117
|
+
⚠️ AND IT IS WHY A DROPPED `animationend` COSTS NOTHING. With
|
|
3118
|
+
`animation-fill-mode: both` the element rests on the last keyframe, and the
|
|
3119
|
+
last keyframe IS its natural appearance. The wrapper clears the state to drop
|
|
3120
|
+
the compositor layer, not to make the content visible.
|
|
3121
|
+
|
|
3122
|
+
── HOW THE REDUCE-MOTION RULE BELOW REACHES THIS ────────────────────────────
|
|
3123
|
+
It is not asked about anywhere. The rule below sets
|
|
3124
|
+
`animation-duration: 1ms !important` on everything, so an armed section plays
|
|
3125
|
+
its one pass in a millisecond and lands at rest — i.e. it is simply there,
|
|
3126
|
+
which is what "reduce motion" means. `!important` is why order does not
|
|
3127
|
+
matter and why these rules may sit above it. `scripts/probe-section-animation.mjs`
|
|
3128
|
+
reads that back out of a real Chrome with the media feature emulated instead
|
|
3129
|
+
of trusting this paragraph.
|
|
3130
|
+
|
|
3131
|
+
⛔ AND THAT IS ALSO WHY THE HIDDEN STATE IS NOT A KEYFRAME'S JOB. `armed` is
|
|
3132
|
+
a plain `opacity: 0` written by JS, never by the server, and only ever onto a
|
|
3133
|
+
section that is below the fold with an observer already watching it. See
|
|
3134
|
+
`SECTION_ANIM_STATE` in lib/content/section-animation.ts. */
|
|
3135
|
+
@keyframes sf-section-fade {
|
|
3136
|
+
from { opacity: 0; }
|
|
3137
|
+
}
|
|
3138
|
+
@keyframes sf-section-rise {
|
|
3139
|
+
from { opacity: 0; transform: translateY(24px); }
|
|
3140
|
+
}
|
|
3141
|
+
@keyframes sf-section-scale {
|
|
3142
|
+
from { opacity: 0; transform: scale(0.96); }
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
[data-sf-anim][data-sf-anim-state="armed"] {
|
|
3146
|
+
opacity: 0;
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
[data-sf-anim][data-sf-anim-state="play"] {
|
|
3150
|
+
animation-duration: 600ms;
|
|
3151
|
+
/* Ease-out. A section arrives quickly and settles — the shape every entrance
|
|
3152
|
+
in the house drawings has, and the inverse of the ease-in a thing leaving
|
|
3153
|
+
would take. */
|
|
3154
|
+
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
|
3155
|
+
animation-fill-mode: both;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
[data-sf-anim="fade"][data-sf-anim-state="play"] {
|
|
3159
|
+
animation-name: sf-section-fade;
|
|
3160
|
+
}
|
|
3161
|
+
[data-sf-anim="rise"][data-sf-anim-state="play"] {
|
|
3162
|
+
animation-name: sf-section-rise;
|
|
3163
|
+
}
|
|
3164
|
+
[data-sf-anim="scale"][data-sf-anim-state="play"] {
|
|
3165
|
+
animation-name: sf-section-scale;
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
/* ── THE VISITOR'S OWN "REDUCE MOTION" SETTING ──────────────────────────────
|
|
3169
|
+
[ADD] The Site Respects Reduce Motion.
|
|
3170
|
+
|
|
3171
|
+
"Reduce motion" is a switch on every phone and laptop. People turn it on
|
|
3172
|
+
because movement on a screen makes them ill — vestibular disorders, migraine,
|
|
3173
|
+
motion sickness — not because they dislike animation. The browser hands the
|
|
3174
|
+
answer to CSS for free and, until this rule, this site threw it away.
|
|
3175
|
+
|
|
3176
|
+
Measured on main, 2026-09-11: 95 files under app/ components/ lib/ carry a
|
|
3177
|
+
`transition-` utility and 3 of them guard it. So 92 files moved for everyone,
|
|
3178
|
+
always. `components/useReducedMotion.ts` is the JS half of this answer and
|
|
3179
|
+
has been here for a while; this is the CSS half it was always missing.
|
|
3180
|
+
|
|
3181
|
+
── WHAT "REDUCE" MEANS HERE, AND WHY IT IS NOT "KILL EVERYTHING" ──────────
|
|
3182
|
+
The crude rule — every duration to zero — is the one most sites ship, and it
|
|
3183
|
+
is wrong in a way that is easy to miss: it also kills the fades. A focus ring
|
|
3184
|
+
that snaps into place, a menu whose background flicks from one colour to
|
|
3185
|
+
another, a dialog that appears with no fade at all — those transitions carry
|
|
3186
|
+
no movement, so they harm nobody, and removing them makes the interface
|
|
3187
|
+
harder to follow rather than kinder.
|
|
3188
|
+
|
|
3189
|
+
So this rule sorts transitions by WHAT THEY ANIMATE, not by how long they
|
|
3190
|
+
take. `transition-property` is replaced with a fixed list of properties that
|
|
3191
|
+
change only ink and light. Everything the author asked for that is not on the
|
|
3192
|
+
list — transform, translate, height, top, margin, background-position, the
|
|
3193
|
+
whole family of movement — is simply no longer a transitioned property, so it
|
|
3194
|
+
changes instantly. The author's own duration and easing survive on the fades.
|
|
3195
|
+
|
|
3196
|
+
A disclosure that animated its height now opens in one step. That is the
|
|
3197
|
+
intended result and not a regression: a growing box pushes every word below
|
|
3198
|
+
it down the screen, which is exactly the movement being asked about.
|
|
3199
|
+
|
|
3200
|
+
── ANIMATIONS ────────────────────────────────────────────────────────────
|
|
3201
|
+
A keyframe list cannot be sorted this way — CSS cannot see inside one — so
|
|
3202
|
+
every animation is collapsed to a single 1ms pass. Two decisions in that:
|
|
3203
|
+
|
|
3204
|
+
· NOT `animation: none`. An element whose static styles are invisible and
|
|
3205
|
+
which becomes visible through a `forwards` fill would then never appear.
|
|
3206
|
+
Nothing in this repo is built that way today, but a one-line rule that
|
|
3207
|
+
can hide content later is the wrong default. Running the animation to its
|
|
3208
|
+
end instead always lands where the author meant it to land.
|
|
3209
|
+
· 1ms rather than 0s, so a JS handler waiting on `animationend` still gets
|
|
3210
|
+
its event and does not hang a component half-open.
|
|
3211
|
+
|
|
3212
|
+
The six looping marquees here (the four ticker directions, the integrations
|
|
3213
|
+
wall, the editor's thumbnail strip) each end one iteration exactly one list
|
|
3214
|
+
width along, which is the seam — so they stop where they started and read as
|
|
3215
|
+
a still image, which is the point.
|
|
3216
|
+
|
|
3217
|
+
── ⚠️ UNLAYERED AND `!important`, BOTH LOAD-BEARING ───────────────────────
|
|
3218
|
+
The card that opened this work asked for the rule inside `@layer base`, on
|
|
3219
|
+
the reasoning that a component needing to keep a motion could then win with a
|
|
3220
|
+
utility. That is the right instinct about escape hatches and the wrong
|
|
3221
|
+
mechanism, and it would have shipped a rule that does nothing at all:
|
|
3222
|
+
|
|
3223
|
+
· `@import "tailwindcss"` orders the layers base → components → utilities,
|
|
3224
|
+
so EVERY Tailwind utility outranks everything in `@layer base` whatever
|
|
3225
|
+
its specificity. All 92 unguarded files move via utilities. Layered here,
|
|
3226
|
+
this rule would lose to every single one of them, silently, and the only
|
|
3227
|
+
way to find out would be to read a computed style.
|
|
3228
|
+
· `@layer utilities` is no better: `*` is specificity 0,0,0 and loses to the
|
|
3229
|
+
`.transition-all` beside it.
|
|
3230
|
+
· Unlayered, it beats all of them — the same cascade fact `.sf-box-gap-only`
|
|
3231
|
+
above relies on, and the inverse of the one `scripts/test-css-layers.ts`
|
|
3232
|
+
guards. That guard is about bare-element RESETS and does not reach `*`;
|
|
3233
|
+
it would not have caught this being layered. `test-reduced-motion.ts`
|
|
3234
|
+
does.
|
|
3235
|
+
|
|
3236
|
+
`!important` then covers the two things being unlayered does not reach: the
|
|
3237
|
+
unlayered class rules in this very file, and inline styles. Three components
|
|
3238
|
+
set an animation inline — the search dialog, the cart drawer, the subscribe
|
|
3239
|
+
popup — and an inline style beats any stylesheet rule that is not important.
|
|
3240
|
+
|
|
3241
|
+
── THE ESCAPE HATCH ──────────────────────────────────────────────────────
|
|
3242
|
+
`data-motion-keep` on an element exempts it and its subtree. It is written
|
|
3243
|
+
into the selector rather than as a second rule that reverts, so there is no
|
|
3244
|
+
cascade puzzle to solve when reading it, and one grep finds every exception.
|
|
3245
|
+
Nothing carries it today. A motion that genuinely must survive this setting
|
|
3246
|
+
is rare enough to be argued for in review, which is what the attribute makes
|
|
3247
|
+
someone do. */
|
|
3248
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3249
|
+
*:not([data-motion-keep], [data-motion-keep] *),
|
|
3250
|
+
*:not([data-motion-keep], [data-motion-keep] *)::before,
|
|
3251
|
+
*:not([data-motion-keep], [data-motion-keep] *)::after {
|
|
3252
|
+
/* Ink and light only. Nothing on this list moves anything on the screen. */
|
|
3253
|
+
transition-property:
|
|
3254
|
+
opacity, color, background-color, border-color, outline-color,
|
|
3255
|
+
text-decoration-color, caret-color, fill, stroke, box-shadow, filter,
|
|
3256
|
+
backdrop-filter !important;
|
|
3257
|
+
animation-duration: 1ms !important;
|
|
3258
|
+
animation-delay: 0ms !important;
|
|
3259
|
+
animation-iteration-count: 1 !important;
|
|
3260
|
+
/* Beats the `html { scroll-behavior: smooth }` reset in @layer base. A
|
|
3261
|
+
scroll the visitor did not ask for is movement like any other. */
|
|
3262
|
+
scroll-behavior: auto !important;
|
|
3263
|
+
}
|
|
3264
|
+
}
|