├── .babelrc ├── .browserslistrc ├── .eslintignore ├── .eslintrc ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── Bug_issue.md │ └── Feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── cla.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .shopifyignore ├── .stylelintrc.json ├── .theme-check.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE.md ├── README.md ├── config ├── settings_data.json └── settings_schema.json ├── eslintrc.json ├── layout ├── password.liquid └── theme.liquid ├── locales ├── bg-BG.json ├── cs.json ├── cs.schema.json ├── da.json ├── da.schema.json ├── de.json ├── de.schema.json ├── el.json ├── en.default.json ├── en.default.schema.json ├── es.json ├── es.schema.json ├── fi.json ├── fi.schema.json ├── fr.json ├── fr.schema.json ├── hr-HR.json ├── hu.json ├── id.json ├── it.json ├── it.schema.json ├── ja.json ├── ja.schema.json ├── ko.json ├── ko.schema.json ├── lt-LT.json ├── nb.json ├── nb.schema.json ├── nl.json ├── nl.schema.json ├── pl.json ├── pl.schema.json ├── pt-BR.json ├── pt-BR.schema.json ├── pt-PT.json ├── pt-PT.schema.json ├── ro-RO.json ├── ru.json ├── sk-SK.json ├── sl-SI.json ├── sv.json ├── sv.schema.json ├── th.json ├── th.schema.json ├── tr.json ├── tr.schema.json ├── vi.json ├── vi.schema.json ├── zh-CN.json ├── zh-CN.schema.json ├── zh-TW.json └── zh-TW.schema.json ├── package.json ├── pnpm-lock.yaml ├── public └── sparkle.gif ├── release-notes.md ├── sections ├── announcement-bar.liquid ├── apps.liquid ├── cart-drawer-items.liquid ├── cart-drawer.liquid ├── cart-icon-bubble.liquid ├── cart-live-region-text.liquid ├── cart-notification-button.liquid ├── cart-notification-product.liquid ├── cart-update-instructions.liquid ├── collage.liquid ├── collapsible-content.liquid ├── collection-list.liquid ├── contact-form.liquid ├── custom-liquid.liquid ├── custom-section.liquid ├── dynamic-cart-footer.liquid ├── dynamic-product-list.liquid ├── dynamic-product-slider.liquid ├── dynamic-progress-bar.liquid ├── email-signup-banner.liquid ├── featured-blog.liquid ├── featured-collection.liquid ├── featured-product.liquid ├── footer-group.json ├── footer.liquid ├── header-group.json ├── header.liquid ├── hero.liquid ├── image-banner.liquid ├── image-with-text.liquid ├── main-404.liquid ├── main-account.liquid ├── main-activate-account.liquid ├── main-addresses.liquid ├── main-article.liquid ├── main-blog.liquid ├── main-cart-footer.liquid ├── main-cart-items.liquid ├── main-collection-banner.liquid ├── main-collection-product-grid.liquid ├── main-list-collections.liquid ├── main-login.liquid ├── main-order.liquid ├── main-page.liquid ├── main-password-footer.liquid ├── main-password-header.liquid ├── main-product.liquid ├── main-register.liquid ├── main-reset-password.liquid ├── main-search.liquid ├── multicolumn.liquid ├── multirow.liquid ├── newsletter.liquid ├── page.liquid ├── pickup-availability.liquid ├── predictive-search.liquid ├── related-products.liquid ├── rich-text.liquid ├── slideshow.liquid ├── test.liquid └── video.liquid ├── shopify.theme.toml ├── snippets ├── art-direction.liquid ├── article-card.liquid ├── banner-content.liquid ├── brand-font-body.liquid ├── brand-font-header.liquid ├── brand-icon.liquid ├── card-collection.liquid ├── card-product.liquid ├── cart-drawer-items.liquid ├── cart-drawer.liquid ├── cart-gwp.liquid ├── cart-icon-bubble.liquid ├── cart-notification.liquid ├── cart-update-instructions.liquid ├── content-variables.liquid ├── country-localization.liquid ├── critical-css.liquid ├── critical-js.liquid ├── css-variables.liquid ├── display-log-lines.liquid ├── dynamic-cart-footer.liquid ├── dynamic-product-list.liquid ├── dynamic-product-slider.liquid ├── dynamic-progress-bar.liquid ├── email-signup-banner-background-mobile.liquid ├── email-signup-banner-background.liquid ├── facets-desktop.liquid ├── facets-drawer-only.liquid ├── facets-drawer.liquid ├── facets.liquid ├── gift-card-recipient-form.liquid ├── header-drawer.liquid ├── header-dropdown-menu.liquid ├── header-mega-menu-blocks.liquid ├── header-mega-menu.liquid ├── header-search.liquid ├── icon-accordion.liquid ├── icon-default.liquid ├── id-type-switch.liquid ├── klaviyo-form.liquid ├── language-localization.liquid ├── mask-arch.liquid ├── meta-tags.liquid ├── object-position-vars.liquid ├── overlay-variables.liquid ├── pagination.liquid ├── price.liquid ├── product-block-buy-buttons.liquid ├── product-block-collapsible-tab.liquid ├── product-block-complementary.liquid ├── product-block-icon-with-text.liquid ├── product-block-inventory.liquid ├── product-block-popup.liquid ├── product-block-price.liquid ├── product-block-quantity-selector.liquid ├── product-block-rating.liquid ├── product-media-gallery.liquid ├── product-media-modal.liquid ├── product-media.liquid ├── product-thumbnail.liquid ├── product-variant-options.liquid ├── product-variant-picker.liquid ├── remove-unused-css.liquid ├── return-condition-eval-cart.liquid ├── return-condition-eval-customer.liquid ├── return-conditions-eval.liquid ├── return-gwp-claimed-offers.liquid ├── return-gwp-main.liquid ├── return-gwp-offer-claimed.liquid ├── return-gwp-offer-eval.liquid ├── return-gwp-offer-found.liquid ├── return-gwp-offer-next.liquid ├── return-gwp-offers-to-remove.liquid ├── return-log-line.liquid ├── return-without-logs.liquid ├── schema-article.liquid ├── schema-organization.liquid ├── schema-product.liquid ├── search-results-desktop.liquid ├── share-button.liquid ├── social-icons.liquid ├── srcset-from-widths.liquid ├── ucoast-animate.liquid ├── ucoast-ax.liquid ├── ucoast-defer-style.liquid ├── ucoast-image.liquid ├── ucoast-media.liquid ├── ucoast-scheme-classes.liquid ├── ucoast-spacer.liquid ├── ucoast-spacing.liquid ├── ucoast-strip-src.liquid ├── ucoast-style-tag.liquid ├── ucoast-video.liquid ├── variant-availability.liquid ├── vite.liquid ├── waitlist.liquid └── welcome-popup.liquid ├── src ├── entry │ ├── _critical-css.scss │ ├── _critical.ts │ ├── css-dir-article-defer.scss │ ├── css-dir-article-prio.scss │ ├── css-dir-base-defer.scss │ ├── css-dir-base-prio.scss │ ├── css-dir-blog-defer.scss │ ├── css-dir-blog-prio.scss │ ├── css-dir-collection-defer.scss │ ├── css-dir-collection-prio.scss │ ├── css-dir-customer-defer.scss │ ├── css-dir-customer-prio.scss │ ├── css-dir-page-defer.scss │ ├── css-dir-page-prio.scss │ ├── css-dir-product-defer.scss │ ├── css-dir-product-prio.scss │ ├── css-optional-component-localization-form.scss │ ├── css-optional-component-mega-menu.scss │ ├── css-optional-component-model-viewer-ui.scss │ ├── css-optional-component-pagination.scss │ ├── css-optional-component-pickup-availability.scss │ ├── css-optional-component-product-model.scss │ ├── css-optional-component-recipient-form.scss │ ├── css-optional-mask-blobs.scss │ ├── css-optional-quick-add.scss │ ├── css-optional-section-contact-form.scss │ ├── css-optional-section-password.scss │ ├── css-optional-template-giftcard.scss │ ├── js-dir-cart-drawer.ts │ ├── js-dir-catalog.ts │ ├── js-dir-customer.ts │ ├── js-dir-product.ts │ ├── js-dir-theme.ts │ ├── js-optional-animations.ts │ ├── js-optional-localization-form.ts │ ├── js-optional-magnify.ts │ ├── js-optional-password-modal.ts │ ├── js-optional-pickup-availability.ts │ ├── js-optional-product-model.ts │ ├── js-optional-quick-add.ts │ ├── js-optional-recipient-form.ts │ ├── js-optional-share-button.ts │ └── js-optional-theme-editor.ts ├── scripts │ ├── cart │ │ ├── cart-drawer-items.ts │ │ ├── cart-drawer.ts │ │ ├── cart-items.ts │ │ ├── cart-note.ts │ │ ├── cart-remove-button.ts │ │ └── dynamic-progress-bar.ts │ ├── catalog │ │ ├── facet-filters-form.ts │ │ ├── facet-remove.ts │ │ ├── main-search.ts │ │ ├── price-range.ts │ │ └── show-more-button.ts │ ├── core │ │ ├── TsDOM.ts │ │ ├── UcoastEl.ts │ │ ├── art-direction.ts │ │ ├── cart-functions.ts │ │ ├── global.ts │ │ ├── media.ts │ │ └── ucoast-video.ts │ ├── customer │ │ └── customer.ts │ ├── global.d.ts │ ├── optional │ │ ├── localization-form.ts │ │ ├── magnify.ts │ │ ├── password-modal.ts │ │ ├── pickup-availability-drawer.ts │ │ ├── pickup-availability.ts │ │ ├── predictive-search.ts │ │ ├── product-model.ts │ │ ├── quick-add.ts │ │ ├── recipient-form.ts │ │ ├── share-button.ts │ │ └── theme-editor.ts │ ├── product │ │ ├── media-gallery.ts │ │ ├── product-form.ts │ │ ├── product-info.ts │ │ └── product-modal.ts │ ├── shopify.d.ts │ ├── theme │ │ ├── animations.ts │ │ ├── cart-notification.ts │ │ ├── constants.ts │ │ ├── deferred-media.ts │ │ ├── details-disclosure.ts │ │ ├── details-modal.ts │ │ ├── header-drawer.ts │ │ ├── header-menu.ts │ │ ├── klaviyo-form.ts │ │ ├── menu-drawer.ts │ │ ├── modal-dialog.ts │ │ ├── modal-opener.ts │ │ ├── multi-product-slider.ts │ │ ├── product-recommendations.ts │ │ ├── product-slider.ts │ │ ├── quantity-input.ts │ │ ├── search-form.ts │ │ ├── slider-component.ts │ │ ├── slideshow-component.ts │ │ ├── sticky-header.ts │ │ ├── variant-radios.ts │ │ ├── variant-selects.ts │ │ ├── waitlist-form.ts │ │ └── welcome-popup.ts │ └── types │ │ ├── events.d.ts │ │ ├── responses.d.ts │ │ └── theme.d.ts └── styles │ ├── article-prio │ └── section-blog-post.scss │ ├── base-defer │ ├── collage.scss │ ├── component-article-card.scss │ ├── component-cart-drawer.scss │ ├── component-cart-gwp.scss │ ├── component-cart-items.scss │ ├── component-cart-notification.scss │ ├── component-cart.scss │ ├── component-dynamic-progress-bar.scss │ ├── component-facets.scss │ ├── component-list-payment.scss │ ├── component-list-social.scss │ ├── component-loading-overlay.scss │ ├── component-menu-drawer.scss │ ├── component-modal-video.scss │ ├── component-newsletter.scss │ ├── component-predictive-search.scss │ ├── component-product-popup-modal.scss │ ├── component-welcome-popup.scss │ ├── newsletter-section.scss │ ├── section-collection-list.scss │ ├── section-email-signup-banner.scss │ ├── section-featured-blog.scss │ ├── section-featured-product.scss │ ├── section-footer.scss │ ├── section-multicolumn.scss │ ├── section-rich-text.scss │ └── video-section.scss │ ├── base-prio │ ├── base.scss │ ├── collapsible-content.scss │ ├── component-accordion.scss │ ├── component-banner-content.scss │ ├── component-card.scss │ ├── component-content-settings.scss │ ├── component-deferred-media.scss │ ├── component-discounts.scss │ ├── component-image-with-text.scss │ ├── component-list-menu.scss │ ├── component-price.scss │ ├── component-product-slider.scss │ ├── component-quantity.scss │ ├── component-rating.scss │ ├── component-search.scss │ ├── component-slider.scss │ ├── component-slideshow.scss │ ├── component-totals.scss │ ├── section-hero.scss │ ├── section-image-banner.scss │ └── section-multi-product-slider.scss │ ├── blog-prio │ └── section-main-blog.scss │ ├── collection-defer │ └── component-show-more.scss │ ├── collection-prio │ ├── component-collection-hero.scss │ └── template-collection.scss │ ├── customer-prio │ └── customer.scss │ ├── global │ ├── critical-css.scss │ ├── functions.scss │ ├── typography.scss │ └── variables.scss │ ├── modules │ ├── article-defer.scss │ ├── article-prio.scss │ ├── base-defer.scss │ ├── base-prio.scss │ ├── blog-defer.scss │ ├── blog-prio.scss │ ├── collection-defer.scss │ ├── collection-prio.scss │ ├── customer-defer.scss │ ├── customer-prio.scss │ ├── page-defer.scss │ ├── page-prio.scss │ ├── product-defer.scss │ └── product-prio.scss │ ├── optional │ ├── component-localization-form.scss │ ├── component-mega-menu.scss │ ├── component-model-viewer-ui.scss │ ├── component-pagination.scss │ ├── component-pickup-availability.scss │ ├── component-product-model.scss │ ├── component-recipient-form.scss │ ├── mask-blobs.scss │ ├── quick-add.scss │ ├── section-contact-form.scss │ ├── section-password.scss │ └── template-giftcard.scss │ ├── page-prio │ └── section-main-page.scss │ ├── product-defer │ ├── component-complementary-products.scss │ └── section-related-products.scss │ └── product-prio │ └── section-main-product.scss ├── templates ├── 404.json ├── article.json ├── blog.json ├── cart.liquid ├── collection.json ├── customers │ ├── account.json │ ├── activate_account.json │ ├── addresses.json │ ├── login.json │ ├── order.json │ ├── register.json │ └── reset_password.json ├── gift_card.liquid ├── index.json ├── list-collections.json ├── page.contact.json ├── page.json ├── password.json ├── product.hidden.liquid ├── product.json └── search.json ├── translation.yml ├── tsconfig.json └── vite.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": "3.0.0" 8 | } 9 | ] 10 | ], 11 | "plugins": ["@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties"] 12 | } 13 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production staging] 2 | >5% 3 | last 2 versions 4 | Firefox ESR 5 | not ie < 11 6 | 7 | [development] 8 | last 1 chrome version 9 | last 1 firefox version 10 | last 1 edge version 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | assets 2 | node_modules!/snippets/critical-css.liquid 3 | /snippets/critical-css.liquid 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | "extends": ["eslint:recommended"], 4 | "env": { 5 | "browser": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": 8, 10 | "sourceType": "module", 11 | "requireConfigFile": false 12 | }, 13 | "rules": { 14 | "semi": 2 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Use this template for reporting a bug 4 | labels: bug 5 | --- 6 | 7 | ## Describe the current behavior 8 | 9 | 10 | ## Describe the expected behavior 11 | 12 | 13 | ## Version information (Dawn, browsers and operating systems) 14 | 15 | - Dawn Version: 7.0.1 16 | - Chrome Version 108.0.5359.124 17 | - macOS Version 13.1 18 | 19 | ## Possible solution 20 | 21 | 22 | ## Additional context/screenshots 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Use this template for suggesting a Dawn enhancement 4 | labels: enhancement 5 | --- 6 | 7 | ## Describe the enhancement you'd like 8 | 9 | 10 | 11 | ## Describe alternatives you've considered 12 | 13 | 14 | 15 | ## Additional context/screenshots 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### PR Summary: 2 | 3 | 4 | 5 | 6 | ### Why are these changes introduced? 7 | 8 | Fixes #0. 9 | 10 | ### What approach did you take? 11 | 12 | ### Other considerations 13 | 14 | ### Decision log 15 | 16 | | # | Decision | Alternatives | Rationale | Downsides | 17 | |---|---|---|---|---| 18 | | 1 | | | | | 19 | 20 | 21 | ### Visual impact on existing themes 22 | 23 | 24 | 25 | ### Testing steps/scenarios 26 | 27 | - [ ] Step 1 28 | 29 | ### Demo links 30 | 31 | 32 | - [Store](url) 33 | - [Editor](url) 34 | 35 | ### Checklist 36 | - [ ] Added PR summary for [release notes](https://themes.shopify.com/themes/dawn/styles/default#ReleaseNotes) 37 | - [ ] Requested review from UX (Only for changes that are affecting the experience or perceivable visual details) 38 | - [ ] Created a ticket for the [help.shopify.com](https://help.shopify.com) documentation team about updates to theme settings. (Internal-only task) 39 | - [ ] Followed [theme code principles](https://github.com/Shopify/dawn/blob/main/.github/CONTRIBUTING.md#theme-code-principles) 40 | - [ ] Linted with [Theme Check](https://github.com/Shopify/theme-check) 41 | - [ ] Tested on [mobile](https://shopify.dev/themes/store/requirements#mobile-browser-requirements) 42 | - [ ] Tested on [multiple browsers](https://shopify.dev/themes/store/requirements#desktop-browser-requirements) 43 | - [ ] Tested for [accessibility](https://shopify.dev/themes/best-practices/accessibility) 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | lhci: 5 | name: Lighthouse 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - name: Lighthouse 10 | uses: shopify/lighthouse-ci-action@v1.1.1 11 | with: 12 | store: ${{ secrets.SHOP_STORE_OS2 }} 13 | password: ${{ secrets.SHOP_PASSWORD_OS2 }} 14 | access_token: ${{ secrets.SHOP_ACCESS_TOKEN }} 15 | collection_handle: all 16 | lhci_github_app_token: ${{ secrets.LHCI_GITHUB_TOKEN }} 17 | pull_theme: ${{ secrets.SHOP_PULL_THEME }} 18 | theme-check: 19 | name: Theme Check 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Theme Check 24 | uses: shopify/theme-check-action@v1 25 | with: 26 | token: ${{ github.token }} 27 | -------------------------------------------------------------------------------- /.github/workflows/cla.yml: -------------------------------------------------------------------------------- 1 | name: Contributor License Agreement (CLA) 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, synchronize, reopened] 6 | issue_comment: 7 | types: [created] 8 | 9 | jobs: 10 | cla: 11 | runs-on: ubuntu-latest 12 | if: | 13 | (github.event.issue.pull_request 14 | && !github.event.issue.pull_request.merged_at 15 | && contains(github.event.comment.body, 'signed') 16 | ) 17 | || (github.event.pull_request && !github.event.pull_request.merged) 18 | steps: 19 | - uses: Shopify/shopify-cla-action@v1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | cla-token: ${{ secrets.CLA_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | build 6 | 7 | # misc 8 | .DS_Store 9 | 10 | npm-debug.log 11 | yarn-error.log 12 | yarn.lock 13 | .yarnclean 14 | .vscode 15 | .idea 16 | .idea/ 17 | .idea/* 18 | node_modules/ 19 | node_modules/* 20 | /assets/ 21 | /snippets/vite.liquid 22 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | build 6 | 7 | # misc 8 | .DS_Store 9 | 10 | npm-debug.log 11 | yarn-error.log 12 | yarn.lock 13 | .yarnclean 14 | .vscode 15 | .idea 16 | /assets/app.min.css 17 | /assets/app.min.js 18 | .idea/ 19 | .idea/* 20 | node_modules/ 21 | node_modules/* 22 | # OS generated files 23 | .DS_Store 24 | .DS_Store? 25 | ._* 26 | .Spotlight-V100 27 | .Trashes 28 | ehthumbs.db 29 | Thumbs.db 30 | 31 | # Dependency directories 32 | node_modules/ 33 | jspm_packages/ 34 | 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | package-lock.json 42 | 43 | # dotenv environment variables file 44 | .env 45 | 46 | # yarn (using Zero-Installs) 47 | .yarn/* 48 | !.yarn/cache 49 | !.yarn/patches 50 | !.yarn/plugins 51 | !.yarn/releases 52 | !.yarn/sdks 53 | !.yarn/versions 54 | 55 | assets/runtime 56 | package.json 57 | release-notes.md 58 | LICENSE.md 59 | 60 | eslintrc.json 61 | .eslintrc 62 | tsconfig.json 63 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 80, 4 | "tabWidth": 4, 5 | "useTabs": true, 6 | "singleQuote": true, 7 | "semi": false, 8 | "indentSchema": true, 9 | "overrides": [ 10 | { 11 | "files": "*.liquid", 12 | "options": { 13 | "singleQuote": false 14 | } 15 | } 16 | ], 17 | "plugins": ["@shopify/prettier-plugin-liquid"] 18 | } 19 | -------------------------------------------------------------------------------- /.shopifyignore: -------------------------------------------------------------------------------- 1 | eslintrc.json 2 | package.json 3 | tsconfig.json 4 | assets/.vite/manifest.json 5 | app/* 6 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /.theme-check.yml: -------------------------------------------------------------------------------- 1 | MatchingTranslations: 2 | enabled: false 3 | TemplateLength: 4 | enabled: false 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["shopify.theme-check-vscode", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "[javascript]": { 4 | "editor.formatOnSave": true 5 | }, 6 | "[css]": { 7 | "editor.formatOnSave": true 8 | }, 9 | "[liquid]": { 10 | "editor.formatOnSave": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-present Shopify Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, sell and/or create derivative works of the Software or any part thereof, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The rights granted above may only be exercised to develop themes that integrate or interoperate with Shopify software or services, and, if applicable, to distribute, offer for sale or otherwise make available any such themes via the Shopify Theme Store. All other uses of the Software are strictly prohibited. 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard-with-typescript", 3 | "parserOptions": { 4 | "project": "./tsconfig.json" 5 | } 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ucoast-vite-shopify", 3 | "license": "UNLICENSED", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "cross-env DEBUG=vite-plugin-shopify:* vite", 8 | "build": "cross-env DEBUG=vite-plugin-shopify:* vite build", 9 | "lint": "eslint .", 10 | "push": "shopify theme push -x templates/*.json -x sections/*.json -x config/settings_data.json", 11 | "pull-json": "shopify theme pull -o templates/*.json -o sections/*.json -o config/settings_data.json", 12 | "push-assets": "shopify theme push -o assets/* -o snippets/vite.liquid" 13 | }, 14 | "devDependencies": { 15 | "@shopify/prettier-plugin-liquid": "^1.5.0", 16 | "@vitejs/plugin-basic-ssl": "^1.1.0", 17 | "cross-env": "^7.0.3", 18 | "cssnano": "^5.1.15", 19 | "cssnano-preset-advanced": "^5.3.10", 20 | "dom-event-types": "^1.1.0", 21 | "hls.js": "^1.5.7", 22 | "prettier": "^3.2.5", 23 | "sass": "^1.77.1", 24 | "typescript": "^5.4.5", 25 | "vite": "^5.2.11", 26 | "vite-plugin-mkcert": "^1.17.5", 27 | "vite-plugin-shopify": "^3.0.1" 28 | }, 29 | "dependencies": { 30 | "@webcomponents/webcomponentsjs": "^2.8.0", 31 | "embla-carousel": "8.0.4", 32 | "embla-carousel-auto-height": "8.0.4", 33 | "embla-carousel-autoplay": "8.0.4", 34 | "embla-carousel-class-names": "8.0.4", 35 | "embla-carousel-wheel-gestures": "8.0.0-rc05", 36 | "install": "^0.13.0", 37 | "lodash-es": "^4.17.21" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/sparkle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadsciencee/shopify-dawn-typescript/ed6df28a01c86c5a7011b612627dad237a92735f/public/sparkle.gif -------------------------------------------------------------------------------- /sections/apps.liquid: -------------------------------------------------------------------------------- 1 |
2 | {%- for block in section.blocks -%} 3 | {% render block %} 4 | {%- endfor -%} 5 |
6 | 7 | {% schema %} 8 | { 9 | "name": "t:sections.apps.name", 10 | "tag": "section", 11 | "class": "section", 12 | "settings": [ 13 | { 14 | "type": "checkbox", 15 | "id": "include_margins", 16 | "default": true, 17 | "label": "t:sections.apps.settings.include_margins.label" 18 | } 19 | ], 20 | "blocks": [ 21 | { 22 | "type": "@app" 23 | } 24 | ], 25 | "presets": [ 26 | { 27 | "name": "t:sections.apps.presets.name" 28 | } 29 | ] 30 | } 31 | {% endschema %} 32 | -------------------------------------------------------------------------------- /sections/cart-drawer-items.liquid: -------------------------------------------------------------------------------- 1 | {% render 'cart-drawer-items', cart: cart %} 2 | -------------------------------------------------------------------------------- /sections/cart-drawer.liquid: -------------------------------------------------------------------------------- 1 | {%- render 'cart-drawer' -%} 2 | -------------------------------------------------------------------------------- /sections/cart-icon-bubble.liquid: -------------------------------------------------------------------------------- 1 | {% render 'cart-icon-bubble' %} 2 | -------------------------------------------------------------------------------- /sections/cart-live-region-text.liquid: -------------------------------------------------------------------------------- 1 | {{ 'sections.cart.new_subtotal' | t }}: {{ cart.total_price | money_with_currency }} 2 | -------------------------------------------------------------------------------- /sections/cart-notification-button.liquid: -------------------------------------------------------------------------------- 1 | 6 | {{- 'general.cart.view_empty_cart' | t -}} 7 | 8 | -------------------------------------------------------------------------------- /sections/cart-notification-product.liquid: -------------------------------------------------------------------------------- 1 | {%- if cart != empty -%} 2 | {%- for item in cart.items -%} 3 |
4 | {%- if item.image -%} 5 |
6 | {% render 'ucoast-media', media: item.image, wrapper_class: 'w-100', loading: 'eager' %} 7 |
8 | {%- endif -%} 9 |
10 | {%- if settings.show_vendor -%} 11 |

{{ item.product.vendor }}

12 | {%- endif -%} 13 |

14 | {{ item.product.title | escape }} 15 |

16 |
17 | {%- unless item.product.has_only_default_variant -%} 18 | {%- for option in item.options_with_values -%} 19 |
20 |
{{ option.name }}:
21 |
{{ option.value }}
22 |
23 | {%- endfor -%} 24 | {%- endunless -%} 25 | {%- for property in item.properties -%} 26 | {%- assign property_first_char = property.first | slice: 0 -%} 27 | {%- if property.last != blank and property_first_char != '_' -%} 28 |
29 |
{{ property.first }}:
30 |
31 | {%- if property.last contains '/uploads/' -%} 32 | 33 | {{ property.last | split: '/' | last }} 34 | 35 | {%- else -%} 36 | {{ property.last }} 37 | {%- endif -%} 38 |
39 |
40 | {%- endif -%} 41 | {%- endfor -%} 42 |
43 | {%- if item.selling_plan_allocation != null -%} 44 |

45 | {{ item.selling_plan_allocation.selling_plan.name }} 46 |

47 | {%- endif -%} 48 |
49 |
50 | {% break %} 51 | {%- endfor -%} 52 | {%- endif -%} 53 | -------------------------------------------------------------------------------- /sections/cart-update-instructions.liquid: -------------------------------------------------------------------------------- 1 | {% render 'cart-update-instructions', cart: cart %} 2 | -------------------------------------------------------------------------------- /sections/custom-liquid.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | capture spacing 3 | render 'ucoast-spacing', settings: section.settings 4 | endcapture 5 | if section.index > 2 6 | assign loading = 'lazy' 7 | else 8 | assign loading = 'eager' 9 | endif 10 | capture color_scheme 11 | render 'ucoast-scheme-classes', color_scheme: section.settings.color_scheme 12 | endcapture 13 | -%} 14 | 15 |
16 |
17 | {{ section.settings.custom_liquid }} 18 |
19 |
20 | 21 | {% schema %} 22 | { 23 | "name": "t:sections.custom-liquid.name", 24 | "tag": "section", 25 | "class": "section", 26 | "settings": [ 27 | { 28 | "type": "liquid", 29 | "id": "custom_liquid", 30 | "label": "t:sections.custom-liquid.settings.custom_liquid.label", 31 | "info": "t:sections.custom-liquid.settings.custom_liquid.info" 32 | }, 33 | { 34 | "type": "color_scheme", 35 | "id": "color_scheme", 36 | "label": "t:sections.all.colors.label", 37 | "default": "background-1" 38 | }, 39 | { 40 | "type": "header", 41 | "content": "t:sections.all.spacing" 42 | }, 43 | { 44 | "type": "range", 45 | "id": "space_above", 46 | "label": "Space Above", 47 | "info": "Set to -1 to disable space above", 48 | "min": 0, 49 | "max": 10, 50 | "default": 4, 51 | "unit": "spc", 52 | "step": 1 53 | }, 54 | { 55 | "type": "range", 56 | "id": "mobile_space_above", 57 | "label": "Mobile Space Above", 58 | "min": -1, 59 | "max": 10, 60 | "step": 1, 61 | "default": -1, 62 | "info": "Leave as '-1' to use the same value as the desktop setting." 63 | } 64 | ], 65 | "presets": [ 66 | { 67 | "name": "t:sections.custom-liquid.presets.name" 68 | } 69 | ] 70 | } 71 | {% endschema %} 72 | -------------------------------------------------------------------------------- /sections/custom-section.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% content_for 'blocks' %} 3 |
4 | 5 | {% schema %} 6 | { 7 | "name": "Custom section", 8 | "blocks": [{ "type": "_group-text-column" }, { "type": "@app" }], 9 | "settings": [ 10 | { 11 | "type": "header", 12 | "content": "Color" 13 | }, 14 | { 15 | "type": "color_scheme", 16 | "id": "color_scheme", 17 | "label": "Color scheme", 18 | "default": "scheme-1" 19 | } 20 | ], 21 | "presets": [ 22 | { 23 | "name": "Custom section" 24 | } 25 | ] 26 | } 27 | {% endschema %} 28 | -------------------------------------------------------------------------------- /sections/dynamic-cart-footer.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | # constants setup 3 | assign result_delimiter = '::' 4 | # call main gwp logic function. necessary copy paste, used in cart-drawer.liquid snippet and dynamic cart sections 5 | capture gwp_result 6 | render 'return-gwp-main' 7 | endcapture 8 | assign gwp_result = gwp_result | split: result_delimiter 9 | assign gwp_offer_handle = gwp_result[0] 10 | assign gwp_offer_type = gwp_result[1] 11 | assign gwp_offer = metaobjects.gwp_offer[gwp_offer_handle] 12 | if gwp_offer == empty or gwp_offer == blank 13 | assign gwp_offer_type = 'none' 14 | endif 15 | %} 16 | 17 | {% render 'dynamic-cart-footer', 18 | cart: cart, 19 | gwp_offer: gwp_offer, 20 | gwp_offer_type: gwp_offer_type 21 | %} 22 | -------------------------------------------------------------------------------- /sections/dynamic-product-list.liquid: -------------------------------------------------------------------------------- 1 | {% render 'dynamic-product-list', collection: collection, play_on_event: true %} 2 | -------------------------------------------------------------------------------- /sections/dynamic-product-slider.liquid: -------------------------------------------------------------------------------- 1 | {% render 'dynamic-product-slider', collection: collection %} 2 | -------------------------------------------------------------------------------- /sections/dynamic-progress-bar.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | # constants setup 3 | assign result_delimiter = '::' 4 | # call main gwp logic function. necessary copy paste, used in cart-drawer.liquid snippet and dynamic cart sections 5 | capture gwp_result 6 | render 'return-gwp-main' 7 | endcapture 8 | assign gwp_result = gwp_result | split: result_delimiter 9 | assign gwp_offer_handle = gwp_result[0] 10 | assign gwp_offer_type = gwp_result[1] 11 | assign gwp_offer = metaobjects.gwp_offer[gwp_offer_handle] 12 | if gwp_offer == empty or gwp_offer == blank 13 | assign gwp_offer_type = 'none' 14 | endif 15 | %} 16 | 17 | {% render 'dynamic-progress-bar', 18 | cart: cart, 19 | gwp_offer: gwp_offer, 20 | gwp_offer_type: gwp_offer_type 21 | %} 22 | -------------------------------------------------------------------------------- /sections/footer-group.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "t:sections.footer.name", 3 | "type": "footer", 4 | "sections": { 5 | "footer": { 6 | "type": "footer", 7 | "blocks": { 8 | "footer-0": { 9 | "type": "link_list", 10 | "settings": { 11 | "heading": "Quick links", 12 | "menu": "footer" 13 | } 14 | }, 15 | "footer-1": { 16 | "type": "link_list", 17 | "settings": { 18 | "heading": "Info", 19 | "menu": "footer" 20 | } 21 | }, 22 | "footer-2": { 23 | "type": "text", 24 | "settings": { 25 | "heading": "Our mission", 26 | "subtext": "

Share contact information, store details, and brand content with your customers.<\/p>" 27 | } 28 | } 29 | }, 30 | "block_order": [ 31 | "footer-0", 32 | "footer-1", 33 | "footer-2" 34 | ], 35 | "settings": { 36 | "color_scheme": "background-1", 37 | "newsletter_enable": true, 38 | "newsletter_heading": "Subscribe to our emails", 39 | "show_social": true, 40 | "enable_country_selector": false, 41 | "enable_language_selector": false, 42 | "payment_enable": true, 43 | "show_policy": false 44 | } 45 | } 46 | }, 47 | "order": [ 48 | "footer" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /sections/header-group.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "t:sections.header.name", 3 | "type": "header", 4 | "sections": { 5 | "announcement-bar": { 6 | "type": "announcement-bar", 7 | "blocks": { 8 | "announcement-bar-0": { 9 | "type": "announcement", 10 | "settings": { 11 | "text": "Welcome to our store", 12 | "link": "" 13 | } 14 | } 15 | }, 16 | "block_order": [ 17 | "announcement-bar-0" 18 | ], 19 | "settings": { 20 | "color_scheme": "", 21 | "show_line_separator": true, 22 | "show_social": false, 23 | "auto_rotate": false, 24 | "change_slides_speed": 3 25 | } 26 | }, 27 | "header": { 28 | "type": "header", 29 | "settings": { 30 | "logo_position": "middle-left", 31 | "menu": "main-menu", 32 | "menu_type_desktop": "mega", 33 | "sticky_header_type": "on-scroll-up", 34 | "show_line_separator": true, 35 | "color_scheme": "background-1", 36 | "menu_color_scheme": "", 37 | "enable_country_selector": false, 38 | "enable_language_selector": false, 39 | "mobile_logo_position": "center", 40 | "margin_bottom": 0, 41 | "padding_top": 20, 42 | "padding_bottom": 20 43 | } 44 | } 45 | }, 46 | "order": [ 47 | "announcement-bar", 48 | "header" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /sections/main-404.liquid: -------------------------------------------------------------------------------- 1 | 12 | 13 |

14 |

15 | {{ 'templates.404.subtext' | t }} 16 |

17 |

18 | {{ 'templates.404.title' | t }} 19 |

20 | 21 | {{ 'general.continue_shopping' | t }} 22 | 23 |
24 | -------------------------------------------------------------------------------- /sections/main-collection-banner.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | capture color_scheme 3 | render 'ucoast-scheme-classes', color_scheme: section.settings.color_scheme 4 | endcapture 5 | %} 6 | 7 | {% assign offset = settings.media_shadow_vertical_offset | at_least: 0 %} 8 | {%- style -%} 9 | @media screen and (max-width: 749px) { 10 | .collection-hero--with-image .collection-hero__inner { 11 | padding-bottom: calc({% render 'ucoast-ax', value: offset %} + {% render 'ucoast-ax', value: 20 %}); 12 | } 13 | } 14 | {%- endstyle -%} 15 | 16 |
17 |
18 |
19 |

20 | 21 | {{- 'sections.collection_template.title' | t }}: 22 | 23 | {{- collection.title | escape -}} 24 |

25 | 26 | {%- if section.settings.show_collection_description -%} 27 |
{{ collection.description }}
28 | {%- endif -%} 29 |
30 | 31 | {%- if section.settings.show_collection_image and collection.image -%} 32 | {% render 'ucoast-media', 33 | media: collection.image, 34 | loading: 'eager', 35 | wrapper_class: 'collection-hero__image-container gradient' 36 | %} 37 | {%- endif -%} 38 |
39 |
40 | 41 | {% schema %} 42 | { 43 | "name": "t:sections.main-collection-banner.name", 44 | "class": "section", 45 | "settings": [ 46 | { 47 | "type": "paragraph", 48 | "content": "t:sections.main-collection-banner.settings.paragraph.content" 49 | }, 50 | { 51 | "type": "checkbox", 52 | "id": "show_collection_description", 53 | "default": true, 54 | "label": "t:sections.main-collection-banner.settings.show_collection_description.label" 55 | }, 56 | { 57 | "type": "checkbox", 58 | "id": "show_collection_image", 59 | "default": false, 60 | "label": "t:sections.main-collection-banner.settings.show_collection_image.label", 61 | "info": "t:sections.main-collection-banner.settings.show_collection_image.info" 62 | }, 63 | { 64 | "type": "color_scheme", 65 | "id": "color_scheme", 66 | "label": "t:sections.all.colors.label", 67 | "default": "background-1" 68 | } 69 | ] 70 | } 71 | {% endschema %} 72 | -------------------------------------------------------------------------------- /sections/main-page.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | capture spacing 3 | render 'ucoast-spacing', settings: section.settings 4 | endcapture 5 | capture color_scheme 6 | render 'ucoast-scheme-classes', color_scheme: section.settings.color_scheme 7 | endcapture 8 | if section.index > 2 9 | assign loading = 'lazy' 10 | else 11 | assign loading = 'eager' 12 | endif 13 | -%} 14 | 15 |
16 |

17 | {{ page.title | escape }} 18 |

19 |
20 | {{ page.content }} 21 |
22 |
23 | 24 | {% schema %} 25 | { 26 | "name": "t:sections.main-page.name", 27 | "tag": "section", 28 | "class": "section", 29 | "settings": [ 30 | { 31 | "type": "color_scheme", 32 | "id": "color_scheme", 33 | "label": "t:sections.all.colors.label", 34 | "default": "background-1" 35 | }, 36 | { 37 | "type": "header", 38 | "content": "t:sections.all.spacing" 39 | }, 40 | { 41 | "type": "range", 42 | "id": "space_above", 43 | "label": "Space Above", 44 | "info": "Set to -1 to disable space above", 45 | "min": 0, 46 | "max": 10, 47 | "default": 4, 48 | "unit": "spc", 49 | "step": 1 50 | }, 51 | { 52 | "type": "range", 53 | "id": "mobile_space_above", 54 | "label": "Mobile Space Above", 55 | "min": -1, 56 | "max": 10, 57 | "step": 1, 58 | "default": -1, 59 | "info": "Leave as '-1' to use the same value as the desktop setting." 60 | } 61 | ] 62 | } 63 | {% endschema %} 64 | -------------------------------------------------------------------------------- /sections/test.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | assign collect_logs = section.settings.collect_logs 3 | capture offers 4 | render 'return-gwp-offer-next', collect_logs: collect_logs 5 | endcapture 6 | -%} 7 | 8 |
9 | {% if collect_logs %} 10 | {% render 'display-log-lines', source: offers, index: 2 %} 11 | {% assign split = offers | split: '::' %} 12 | {% assign offers = split[0] | append: '::' | append: split[1] %} 13 | {% endif %} 14 | {{ offers }} 15 |

cart update instructions in json

16 | {% render 'cart-update-instructions', cart: cart %} 17 |
18 | 19 | {% schema %} 20 | { 21 | "name": "Test Section", 22 | "tag": "section", 23 | "class": "section", 24 | "disabled_on": { 25 | "groups": ["header", "footer"] 26 | }, 27 | "settings": [ 28 | { 29 | "type": "checkbox", 30 | "id": "collect_logs", 31 | "label": "Enable Log Collection", 32 | "default": true 33 | } 34 | ], 35 | "presets": [ 36 | { 37 | "name": "Test Section" 38 | } 39 | ] 40 | } 41 | {% endschema %} 42 | -------------------------------------------------------------------------------- /shopify.theme.toml: -------------------------------------------------------------------------------- 1 | [environments.dev] 2 | store = "store-handle-here" 3 | live-reload = 'off' 4 | -------------------------------------------------------------------------------- /snippets/brand-font-body.liquid: -------------------------------------------------------------------------------- 1 | {% if preload %} 2 | {{ 'BODY_FONT_FILE_NAME.woff2' | asset_url | preload_tag: as: 'font', type: 'font/woff2' }} 3 | {% else %} 4 | @font-face { 5 | font-display: block; 6 | font-family: brandbody; 7 | src: url("{{ 'BODY_FONT_FILE_NAME.woff2' | asset_url }}") format("woff2"); 8 | font-style: normal; 9 | font-weight: 400; 10 | } 11 | @font-face { 12 | font-display: block; 13 | font-family: brandbody; 14 | src: url("{{ 'BODY_ITALIC_FONT_FILE_NAME.woff2' | asset_url }}") format("woff2"); 15 | font-style: italic; 16 | font-weight: 400; 17 | } 18 | @font-face { 19 | font-display: block; 20 | font-family: brandbody; 21 | src: url("{{ 'BODY_BOLD_FONT_FILE_NAME.woff2' | asset_url }}") format("woff2"); 22 | font-style: italic; 23 | font-weight: 700; 24 | } 25 | {% comment %} 26 | template is based on a font with an extra-bold weight, but this isn't necessary or used in any default styles 27 | if you don't need this, remove the below and make sure to remove from css-variables.liquid 28 | and variables.scss 29 | {% endcomment %} 30 | @font-face { 31 | font-display: block; 32 | font-family: body; 33 | src: url("{{ 'BODY_EXTRA_BOLD_FONT_FILE_NAME.woff2' | asset_url }}") format("woff2"); 34 | font-style: italic; 35 | font-weight: 800; 36 | } 37 | {% endif %} 38 | -------------------------------------------------------------------------------- /snippets/brand-font-header.liquid: -------------------------------------------------------------------------------- 1 | {% if preload %} 2 | {{ 'HEADER_FONT_FILE_NAME.woff2' | asset_url | preload_tag: as: 'font', type: 'font/woff2' }} 3 | {% else %} 4 | @font-face { 5 | font-display: block; 6 | font-family: brandheading; 7 | src: url("{{ 'HEADER_FONT_FILE_NAME.woff2' | asset_url }}") format("woff2"); 8 | font-style: normal; 9 | font-weight: 400; 10 | } 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /snippets/brand-icon.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | -- this is for custom builds, this should be used for all brand specific icons that replace defaults 3 | {% endcomment %} 4 | -------------------------------------------------------------------------------- /snippets/cart-gwp.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | # renders a gwp selection UI if an active offer is found 3 | 4 | # params: 5 | # gwp_offer_type (string, 'none' | 'active' | 'partial') 6 | # gwp_offer (gwp_offer) 7 | 8 | # set params 9 | assign gwp_offer_type = gwp_offer_type | default: 'none' 10 | 11 | if gwp_offer_type == 'active' 12 | assign product_list = gwp_offer.product_offer.value.products.value 13 | assign free_gift_text = gwp_offer.display_cta.value 14 | endif 15 | %} 16 | 17 | {% if gwp_offer_type == 'active' %} 18 | 19 |
20 |
21 |

Choose Your Gift

22 |
23 |
24 |
25 | 45 |
46 |
47 | {% endif %} 48 | -------------------------------------------------------------------------------- /snippets/cart-icon-bubble.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | if cart == empty 3 | render 'icon-default', icon: 'cart-empty' 4 | else 5 | render 'icon-default', icon: 'cart' 6 | endif 7 | -%} 8 | 9 | {{ 'templates.cart.cart' | t }} 10 | {%- if cart != empty -%} 11 |
12 | {%- if cart.item_count < 100 -%} 13 | 14 | {%- endif -%} 15 | 16 | {{- 'sections.header.cart_count' | t: count: cart.item_count -}} 17 | 18 |
19 | {%- endif -%} 20 |
21 | -------------------------------------------------------------------------------- /snippets/cart-notification.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Renders cart notification 3 | 4 | Accepts: 5 | - color_scheme: {String} sets the color scheme of the notification (optional) 6 | - desktop_menu_type: {String} passes the desktop menu type which allows us to use the right css class (optional) 7 | 8 | Usage: 9 | {% render 'cart-notification' %} 10 | {% endcomment %} 11 | 12 | 13 |
14 | 57 |
58 |
59 | {% style %} 60 | .cart-notification { 61 | display: none; 62 | } 63 | {% endstyle %} 64 | -------------------------------------------------------------------------------- /snippets/cart-update-instructions.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | # output a json object with instructions to potentially update the cart. should be retrieved via section api 3 | 4 | # consts 5 | assign result_delimiter = '::' 6 | assign list_delimiter = ':::' 7 | 8 | 9 | # set params 10 | assign line_id_type = 'key' 11 | 12 | # set output vars 13 | assign update_required = false 14 | assign line_updates = '' 15 | 16 | # TODO: bundle logic here. bundle logic could potentially change line_id_type 17 | 18 | # add any invalid gwp offers to the update list 19 | capture gwp_offers_to_remove 20 | render 'return-gwp-offers-to-remove', line_id_type: line_id_type 21 | endcapture 22 | echo gwp_offers_to_remove 23 | assign line_updates = line_updates | append: gwp_offers_to_remove 24 | 25 | # now that we have both the gwp removal and potential bundle updates, we can split the list 26 | assign line_updates = line_updates | split: list_delimiter 27 | 28 | # format line updates so they will be a valid json object to pass into cart ajax api 29 | assign line_updates_json = "" 30 | for line in line_updates 31 | assign line_split = line | split: result_delimiter 32 | assign line_id = line_split[0] 33 | assign line_quantity = line_split[1] 34 | if line_id_type == 'key' 35 | assign line_id = '"' | append: line_id | append:'"' 36 | endif 37 | assign line_json = line_id | append: ':' | append: line_quantity 38 | unless forloop.last 39 | assign line_json = line_json | append: ',' 40 | endunless 41 | assign line_updates_json = line_updates_json | append: line_json 42 | endfor 43 | 44 | if line_updates.size > 0 45 | assign update_required = true 46 | endif 47 | 48 | 49 | assign json = '{' 50 | assign json = json | append: '"updates": {' | append: line_updates_json | append: '},' 51 | assign json = json | append: '"update_required": ' | append: update_required 52 | 53 | assign json = json | append: '}' 54 | %} 55 | 56 | -------------------------------------------------------------------------------- /snippets/content-variables.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | required vars (passed in settings obj) 3 | 4 | position: 'start' | 'center' 5 | wrap_width: int 220-720 6 | mobile_wrap_percent: int 51-150 7 | {% endcomment %} 8 | --component-text-align: {{- position -}};--component-wrap-width: {%- render 'ucoast-ax', value: wrap_width -%};--component-mobile-wrap-percent: {{- mobile_wrap_percent -}};--component-flex-position:{%- if position == 'start' -%}flex-start{%- else -%}center{%- endif -%}; 9 | -------------------------------------------------------------------------------- /snippets/country-localization.liquid: -------------------------------------------------------------------------------- 1 | {%- comment -%} 2 | Renders the country picker for the localization form 3 | 4 | Accepts: 5 | - localPosition: pass in the position in which the form is coming up to create specific IDs 6 | {%- endcomment -%} 7 | 8 |
9 | 23 | 50 |
51 | 52 | -------------------------------------------------------------------------------- /snippets/critical-js.liquid: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /snippets/display-log-lines.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # params: source (string), line_delimiter (string, default '::'), index (number, default 1) 3 | # log_lines should be a string array delimited by '___LOG___' 4 | # individual log lines can be delimited by the ',' character to display logs in a list form 5 | # if an '=' is found in a log line list item, the log line will be split into a key-value pair 6 | # if '_title_' is found in a log line, it will be converted to an

element with _title_ removed 7 | assign line_delimiter = line_delimiter | default: '::' 8 | assign log_delimiter = log_delimiter | default: '___LOG___' 9 | assign split_source = source | split: line_delimiter 10 | assign log_lines = split_source[index] 11 | assign log_lines = log_lines | split: log_delimiter | compact 12 | 13 | -%} 14 | {% if log_lines.size > 0 %} 15 |
16 |

Logs:

17 | {% for log_line in log_lines %} 18 | {% if log_line contains '_title_' %} 19 |

{{ log_line | replace: '_title_', '' }}

20 | {% continue %} 21 | {% endif %} 22 | {% unless log_line contains ',' %} 23 |

{{ log_line }}

24 | {% continue %} 25 | {% endunless %} 26 | {% assign log_list = log_line | split: ',' %} 27 | 39 | {% endfor %} 40 |
41 | {% endif %} 42 | -------------------------------------------------------------------------------- /snippets/dynamic-product-list.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | unless section_id 3 | assign section_id = section.id 4 | endunless 5 | if search_products 6 | assign product_list = search_products 7 | else 8 | assign product_list = collection.products 9 | endif 10 | %} 11 | {%- for product in product_list -%} 12 | {%- if forloop.index == 4 -%} 13 | {% assign loading = 'lazy' %} 14 | {%- endif -%} 15 |
  • 20 | {% render 'card-product', 21 | card_product: product, 22 | section_id: section_id, 23 | allow_upsell: true, 24 | show_secondary_image: true, 25 | loading: loading, 26 | play_on_event: play_on_event 27 | %} 28 |
  • 29 | {% if forloop.index0 == insert_after %} 30 |
  • 31 | {{ insert_markup }} 32 |
  • 33 | {% endif %} 34 | {%- endfor -%} 35 | -------------------------------------------------------------------------------- /snippets/dynamic-product-slider.liquid: -------------------------------------------------------------------------------- 1 | {% assign unique_id = section.id | append: collection.id %} 2 | 11 |
    12 |
      16 | {% unless defer == true %} 17 | {% render 'dynamic-product-list', 18 | collection: collection, 19 | enable_defer: false, 20 | slider: true, 21 | section_id: unique_id 22 | %} 23 | {% endunless %} 24 |
    25 |
    26 |
    27 | -------------------------------------------------------------------------------- /snippets/facets-drawer-only.liquid: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 60 |
    61 |
    62 | -------------------------------------------------------------------------------- /snippets/facets.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Renders facets (filtering and sorting) 3 | 4 | Accepts: 5 | - results: {Object} Collection or Search object 6 | - enable_filtering: {Boolean} Show filtering when true 7 | - enable_sorting: {Boolean} Show sorting when true 8 | - filter_type: {String} Type of filter 9 | - paginate: {Object} 10 | 11 | Usage: 12 | {% render 'facets', results: collection, enable_filtering: true, enable_sorting: true, filter_type: 'vertical', paginate: paginate %} 13 | {% endcomment %} 14 | 15 |
    16 | {% liquid 17 | assign sort_by = results.sort_by | default: results.default_sort_by 18 | assign total_active_values = 0 19 | if results.url 20 | assign results_url = results.url 21 | else 22 | assign terms = results.terms | escape 23 | assign results_url = '?q=' | append: terms | append: '&options%5Bprefix%5D=last&sort_by=' | append: sort_by 24 | endif 25 | 26 | assign currencies_using_comma_decimals = 'ANG,ARS,BRL,BYN,BYR,CLF,CLP,COP,CRC,CZK,DKK,EUR,HRK,HUF,IDR,ISK,MZN,NOK,PLN,RON,RUB,SEK,TRY,UYU,VES,VND' | split: ',' 27 | assign uses_comma_decimals = false 28 | if currencies_using_comma_decimals contains cart.currency.iso_code 29 | assign uses_comma_decimals = true 30 | endif 31 | 32 | if filter_type == 'vertical' or filter_type == 'horizontal' 33 | render 'facets-desktop', filter_type: filter_type, results: results, enable_filtering: enable_filtering,results_url: results_url, uses_comma_decimals: uses_comma_decimals, sort_by: sort_by 34 | endif 35 | render 'facets-drawer', filter_type: filter_type, results: results, enable_filtering: enable_filtering,results_url: results_url, uses_comma_decimals: uses_comma_decimals, sort_by: sort_by 36 | 37 | if filter_type == 'drawer' 38 | render 'facets-drawer-only', filter_type: filter_type, results: results, enable_filtering: enable_filtering,results_url: results_url, uses_comma_decimals: uses_comma_decimals, sort_by: sort_by 39 | endif 40 | %} 41 |
    42 | -------------------------------------------------------------------------------- /snippets/id-type-switch.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # check for up to 3 types from a block id, and echo the type if found 3 | # for use with theme blocks since the type is always @theme... 4 | if id == '' or id == blank or id == empty 5 | if default_type 6 | echo default_type 7 | endif 8 | else 9 | if id contains type_1 10 | echo type_1 11 | elsif type_2 and id contains type_2 12 | echo type_2 13 | elsif type_3 and id contains type_3 14 | echo type_3 15 | elsif default_type 16 | echo default_type 17 | endif 18 | endif 19 | -%} 20 | -------------------------------------------------------------------------------- /snippets/language-localization.liquid: -------------------------------------------------------------------------------- 1 | {%- comment -%} 2 | Renders the language picker for the localization form 3 | 4 | Accepts: 5 | - localPosition: pass in the position in which the form is coming up to create specific IDs 6 | {%- endcomment -%} 7 | 8 |
    9 | 20 | 45 |
    46 | 47 | -------------------------------------------------------------------------------- /snippets/mask-arch.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /snippets/meta-tags.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | assign og_title = page_title | default: shop.name 3 | assign og_url = canonical_url | default: request.origin 4 | assign og_type = 'website' 5 | assign og_description = page_description | default: shop.description | default: shop.name 6 | 7 | if request.page_type == 'product' 8 | assign og_type = 'product' 9 | elsif request.page_type == 'article' 10 | assign og_type = 'article' 11 | elsif request.page_type == 'password' 12 | assign og_url = request.origin 13 | endif 14 | %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {%- if page_image -%} 23 | 24 | 25 | 26 | 27 | {%- endif -%} 28 | 29 | {%- if request.page_type == 'product' -%} 30 | 31 | 32 | {%- endif -%} 33 | 34 | {%- if settings.social_twitter_link != blank -%} 35 | 36 | {%- endif -%} 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /snippets/object-position-vars.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | use like this: 3 | render 'object-position-vars', settings: block.settings 4 | 5 | ex: input: 6 | override_shopify_position_desktop: block.settings.override_shopify_position_desktop, 7 | override_shopify_position_mobile: block.settings.override_shopify_position_mobile, 8 | y_position_desktop: block.settings.y_position_desktop, 9 | x_position_desktop: block.settings.x_position_desktop, 10 | y_position_mobile: block.settings.y_position_mobile, 11 | x_position_mobile: block.settings.x_position_mobile, 12 | {% endcomment %} 13 | {% liquid 14 | if settings.override_shopify_position_desktop 15 | echo '--object-position-desktop: ' 16 | echo settings.x_position_desktop 17 | echo '% ' 18 | echo settings.y_position_desktop 19 | echo '%;' 20 | endif 21 | if settings.override_shopify_position_mobile 22 | echo '--object-position-mobile: ' 23 | echo settings.x_position_mobile | default: settings.x_position_desktop 24 | echo '% ' 25 | echo settings.y_position_mobile | default: settings.y_position_desktop 26 | echo '%;' 27 | endif 28 | %} 29 | -------------------------------------------------------------------------------- /snippets/overlay-variables.liquid: -------------------------------------------------------------------------------- 1 | --opacity:{{- overlay_opacity | divided_by: 100.0 | default: 0.1 -}}; 2 | -------------------------------------------------------------------------------- /snippets/product-block-collapsible-tab.liquid: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | {% render 'icon-accordion', icon: block.settings.icon %} 6 |

    7 | {{ block.settings.heading | default: block.settings.page.title }} 8 |

    9 |
    10 | {% render 'icon-default', icon: 'caret' %} 11 |
    12 |
    14 | {{ block.settings.content }} 15 | {{ block.settings.page.content }} 16 |
    17 |
    18 |
    19 | -------------------------------------------------------------------------------- /snippets/product-block-popup.liquid: -------------------------------------------------------------------------------- 1 | {% case element %} 2 | {% when 'opener' %} 3 | 8 | 16 | 17 | 18 | {{- block.settings.text -}} 19 | 20 | {% when 'dialog' %} 21 | 26 | 46 | 47 | {% endcase %} 48 | -------------------------------------------------------------------------------- /snippets/product-block-price.liquid: -------------------------------------------------------------------------------- 1 |
    3 | {%- render 'price', 4 | product: product, 5 | use_variant: true, 6 | show_badges: true, 7 | price_class: 'price--large' 8 | -%} 9 |
    10 | {%- if cart.taxes_included or shop.shipping_policy.body != blank -%} 11 |
    12 | {%- if cart.taxes_included -%} 13 | {{ 'products.product.include_taxes' | t }} 14 | {%- endif -%} 15 | {%- if shop.shipping_policy.body != blank -%} 16 | {{ 'products.product.shipping_policy_html' | t: link: shop.shipping_policy.url }} 17 | {%- endif -%} 18 |
    19 | {%- endif -%} 20 |
    21 | {%- assign product_form_installment_id = 'product-form-installment-' | append: section.id -%} 22 | {%- form 'product', product, id: product_form_installment_id, class: 'installment caption-large' -%} 23 | 25 | {{ form | payment_terms }} 26 | {%- endform -%} 27 |
    28 | -------------------------------------------------------------------------------- /snippets/product-block-rating.liquid: -------------------------------------------------------------------------------- 1 | {%- if product.metafields.reviews.rating.value != blank -%} 2 | {% liquid 3 | assign rating_decimal = 0 4 | assign decimal = product.metafields.reviews.rating.value.rating | modulo: 1 5 | if decimal >= 0.3 and decimal <= 0.7 6 | assign rating_decimal = 0.5 7 | elsif decimal > 0.7 8 | assign rating_decimal = 1 9 | endif 10 | %} 11 | 22 |

    23 | 27 |

    28 |

    29 | 30 | 31 | {{- product.metafields.reviews.rating_count }} 32 | {{ 'accessibility.total_reviews' | t -}} 33 | 34 |

    35 | {%- endif -%} 36 | -------------------------------------------------------------------------------- /snippets/product-media-modal.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Renders a product media modal. Also see 'product-media-gallery' 3 | 4 | Accepts: 5 | - product: {Object} Product liquid object 6 | - variant_images: {Array} Product images associated with a variant 7 | 8 | Usage: 9 | {% render 'product-media-modal' %} 10 | {% endcomment %} 11 | 12 | 13 | 58 | 59 | -------------------------------------------------------------------------------- /snippets/remove-unused-css.liquid: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /snippets/return-gwp-claimed-offers.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # find all gwp offers that have been claimed in the current cart state 3 | 4 | # params: collect_logs (boolean, optional) 5 | # returns an array in string form. 'handle::claimed_ids(string[])::log_lines' separated by ':::' 6 | 7 | # note: the claimed_ids string[] is comma separated with a trailing comma 8 | 9 | # consts 10 | assign result_delimiter = '::' 11 | assign list_delimiter = ':::' 12 | assign log_delimiter = '___LOG___' 13 | assign not_found_result = '_not_found_' 14 | 15 | # set params 16 | assign collect_logs = collect_logs | default: false 17 | 18 | # set output vars 19 | assign results = '' 20 | 21 | # loop offer metaobjects in order specified by theme settings 22 | for offer in settings.gwp_offers 23 | # capture result in form of 'offer_handle::claimed(boolean)::claimed_ids(string[])::log_lines' 24 | capture offer_result 25 | render 'return-gwp-offer-claimed', offer: offer, collect_logs: collect_logs 26 | endcapture 27 | assign result_split = offer_result | split: result_delimiter 28 | assign claimed = result_split[1] 29 | assign claimed_product_ids = result_split[2] 30 | if collect_logs 31 | assign offer_logs = result_split[3] | default: 'log' 32 | endif 33 | if claimed == 'false' 34 | if collect_logs 35 | assign offer_logs = offer_logs | append: 'Offer ' | append: handle | append: ' is not claimed' | append: log_delimiter 36 | endif 37 | continue 38 | else 39 | if collect_logs 40 | assign offer_logs = offer_logs | append: 'Offer ' | append: handle | append: ' is claimed' | append: log_delimiter 41 | assign success_result = offer.system.handle | append: result_delimiter | append: claimed_product_ids | append: result_delimiter | append: offer_logs 42 | else 43 | assign success_result = offer.system.handle | append: result_delimiter | append: claimed_product_ids 44 | endif 45 | assign results = results | append: success_result | append: list_delimiter 46 | endif 47 | # add the result to the list if valid 48 | endfor 49 | 50 | # clean up null results 51 | assign results = results | split: list_delimiter | compact | uniq | join: list_delimiter 52 | 53 | # return result to caller 54 | echo results 55 | -%} 56 | -------------------------------------------------------------------------------- /snippets/return-gwp-main.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # top-level gwp logic 3 | 4 | # params: collect_logs (boolean, optional) 5 | 6 | # returns string (offer_handle(string)::offer_type('none' | 'active' | 'partial')) 7 | 8 | # note: an 'active offer' is a ready-to-claim offer, while a partial offer just means that it's something the customer can work towards 9 | # note: this is the only logic file that should be called from normal template code 10 | 11 | # consts 12 | assign result_delimiter = '::' 13 | 14 | # set params 15 | assign collect_logs = collect_logs | default: false 16 | 17 | # set output vars 18 | assign offer_type = 'none' 19 | assign offer_handle = 'false' 20 | 21 | # start by checking for an active offer 22 | capture active_offer_result 23 | render 'return-gwp-offer-next', collect_logs: collect_logs 24 | endcapture 25 | 26 | # call function to get the handle of the active offer or 'false' string 27 | capture maybe_active_offer_handle 28 | render 'return-gwp-offer-found', result: active_offer_result, collect_logs: collect_logs 29 | endcapture 30 | 31 | if maybe_active_offer_handle != 'false' 32 | assign offer_handle = maybe_active_offer_handle 33 | assign offer_type = 'active' 34 | else 35 | # if there's no active offer, check for a partial offer 36 | capture partial_offer_result 37 | render 'return-gwp-offer-next', soft_cart_threshold: true, collect_logs: collect_logs 38 | endcapture 39 | 40 | # call the same function to get the handle of the potential partial offer 41 | capture maybe_partial_offer_handle 42 | render 'return-gwp-offer-found', result: partial_offer_result, collect_logs: collect_logs 43 | endcapture 44 | 45 | # if a partial offer is found it should have a 'offer_condition_cart_threshold' metaobject in the the 'enable_conditions' list 46 | if maybe_partial_offer_handle != 'false' 47 | assign offer_handle = maybe_partial_offer_handle 48 | assign offer_type = 'partial' 49 | endif 50 | endif 51 | 52 | echo offer_handle | append: result_delimiter | append: offer_type 53 | -%} 54 | -------------------------------------------------------------------------------- /snippets/return-gwp-offer-found.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # helper to parse a gwp_offer result string and return the offer handle if valid 3 | 4 | # params 5 | # result (string type::handle::log_lines) 6 | 7 | # returns string (gwp_handle | 'false') 8 | 9 | # consts 10 | assign result_delimiter = '::' 11 | assign not_found_result = '_not_found_' 12 | 13 | # parse result input 14 | assign result_split = result | split: result_delimiter 15 | assign result_type = result_split[0] 16 | assign result_handle = result_split[1] 17 | 18 | # return result to caller 19 | if result_type != 'gwp_offer' or result_handle == not_found_result or result_handle == empty or result_handle == blank 20 | echo 'false' 21 | else 22 | echo result_handle 23 | endif 24 | -%} 25 | -------------------------------------------------------------------------------- /snippets/return-log-line.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # extract a log line from a response. 3 | 4 | # params: 5 | # source (string) 6 | # index (number: default 1) 7 | # line_delimiter (string: default '::') 8 | # log_delimiter (string: default '___LOG___') 9 | 10 | # note: specify an index to identify which index the log line can be found at when delimited by 'line_delimiter' 11 | 12 | assign line_delimiter = line_delimiter | default: '::' 13 | assign log_delimiter = log_delimiter | default: '___LOG___' 14 | assign index = index | default: 1 15 | assign split_source = source | split: line_delimiter 16 | echo split_source[index] | append: '___LOG___' 17 | -%} 18 | -------------------------------------------------------------------------------- /snippets/return-without-logs.liquid: -------------------------------------------------------------------------------- 1 | {%- liquid 2 | # take a string array and strip logs, returning the same string array without logs 3 | 4 | # params: 5 | # source (string[]) 6 | # index (number: default 1) 7 | # line_delimiter (string: default '::') 8 | # list_delimiter (string: default ':::') 9 | 10 | # note: for string 'result::log message here', index 1 would return 'log message here' 11 | 12 | # set params 13 | assign list_delimiter = list_delimiter | default: ':::' 14 | assign line_delimiter = line_delimiter | default: '::' 15 | assign index = index | default: 1 16 | assign last_item_index = index | minus: 1 17 | 18 | # set output vars 19 | assign final_result = '' 20 | 21 | # loop through source array 22 | assign result_list = source | split: list_delimiter 23 | for list_item in result_list 24 | assign stripped_item = '' 25 | assign split_source = list_item | split: line_delimiter 26 | for inner_item in split_source 27 | if forloop.index0 >= index 28 | break 29 | endif 30 | assign stripped_item = stripped_item | append: inner_item 31 | if forloop.index0 != last_item_index 32 | assign stripped_item = stripped_item | append: line_delimiter 33 | endif 34 | endfor 35 | if stripped_item != '' 36 | assign final_result = final_result | append: stripped_item | append: list_delimiter 37 | endif 38 | endfor 39 | 40 | # return result to caller 41 | echo final_result 42 | -%} 43 | -------------------------------------------------------------------------------- /snippets/schema-article.liquid: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /snippets/schema-organization.liquid: -------------------------------------------------------------------------------- 1 | 23 | {%- if request.page_type == 'index' -%} 24 | {% assign potential_action_target = request.origin 25 | | append: routes.search_url 26 | | append: '?q={search_term_string}' 27 | %} 28 | 41 | {%- endif -%} 42 | -------------------------------------------------------------------------------- /snippets/schema-product.liquid: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /snippets/share-button.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Renders share button. 3 | Accepts: 4 | - block: {Object} passes in the block information. 5 | - share_link: {String} url to be added to the input the user will get/copy. 6 | 7 | Usage: 8 | {% render 'share-button', 9 | block: block, 10 | share_link: share_url 11 | %} 12 | {% endcomment %} 13 | {% render 'vite', vite: 'js-optional-share-button.ts' %} 14 | 15 | 16 | 20 |
    21 | 25 | 48 |
    49 |
    50 | -------------------------------------------------------------------------------- /snippets/srcset-from-widths.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | comment 3 | input: image, widths 4 | endcomment 5 | assign arr = widths | split: ',' 6 | for width in arr 7 | assign width_as_int = width | strip | plus: 0 8 | echo image | image_url: width: width_as_int 9 | echo ' ' 10 | echo width 11 | echo 'w' 12 | unless forloop.last 13 | echo ', ' 14 | endunless 15 | endfor 16 | %} 17 | -------------------------------------------------------------------------------- /snippets/ucoast-animate.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | case animation 3 | when 'fade-in' 4 | echo ' scroll-trigger animate--fade-in ' 5 | endcase 6 | %} 7 | -------------------------------------------------------------------------------- /snippets/ucoast-ax.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | echo 'calc(' | append: value | append: ' * var(--ax))' 3 | %} 4 | -------------------------------------------------------------------------------- /snippets/ucoast-defer-style.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | 'url' should already be formatted as asset url 3 | 'media' represents the media query to shift to after the page is loaded 4 | - the only value that should ever be passed here is '(min-width: 750px)' 5 | {% endcomment %} 6 | {% liquid 7 | unless media 8 | assign media = 'all' 9 | endunless 10 | %} 11 | 17 | -------------------------------------------------------------------------------- /snippets/ucoast-image.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | this should only be called from ucoast-media 3 | {% endcomment %} 4 | {% liquid 5 | unless image 6 | assign image = settings.placeholder_image 7 | endunless 8 | unless id 9 | assign id = image.id 10 | endunless 11 | if x_position and y_position 12 | assign wrapper_id = 'image-wrapper-' | append: id | append: x_position | append: y_position 13 | endif 14 | %} 15 | 28 | {% liquid 29 | unless image 30 | assign image = settings.placeholder_image 31 | endunless 32 | unless starting_width 33 | assign starting_width = 1 34 | endunless 35 | unless alt 36 | assign alt = image.alt 37 | endunless 38 | unless alt 39 | assign alt = '' 40 | endunless 41 | 42 | if media_child 43 | echo media_child 44 | endif 45 | capture input 46 | echo image | image_url: width: starting_width | image_tag: loading: loading, class: image_class, id: id, style: style, preload: preload, alt: alt 47 | endcapture 48 | assign starting_src = image | image_url: width: starting_width 49 | assign replace_src = ' src="' | append: starting_src | append: '" data-src=' 50 | if defer 51 | assign replace_src = ' data-uc-defer ' | append: replace_src 52 | endif 53 | if load_on_event 54 | assign replace_src = ' data-uc-load-on-event ' | append: replace_src 55 | endif 56 | if multiplier != 1 57 | assign replace_src = ' data-uc-multiplier="' | append: multiplier | append: '"' | append: replace_src 58 | endif 59 | if custom_attributes 60 | assign replace_src = ' ' | append: custom_attributes | append: ' ' | append: replace_src 61 | endif 62 | assign output = input | replace: 'srcset="', 'data-srcset="' | replace: ' src=', replace_src 63 | echo output 64 | %} 65 | {% if wrapper_id %} 66 | {% style %} 67 | [data-wrapper-id="{{ wrapper_id }}"] img, 68 | [data-wrapper-id="{{ wrapper_id }}"] video{ 69 | object-position: {{ x_position }}% {{ y_position }}% !important; 70 | } 71 | {% endstyle %} 72 | {% endif %} 73 | 74 | -------------------------------------------------------------------------------- /snippets/ucoast-media.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | unless defer 3 | assign defer = false 4 | endunless 5 | unless load_on_event 6 | assign load_on_event = false 7 | endunless 8 | 9 | if media.duration > 0 10 | render 'ucoast-video', wrapper_class: wrapper_class, video_class: media_class, video: media, defer: defer, load_on_event: load_on_event, aspect_ratio: aspect_ratio, starting_width: starting_width, id: media.id, y_position: y_position, x_position: x_position 11 | else 12 | render 'ucoast-image', wrapper_class: wrapper_class, image_class: media_class, image: media, defer: defer, load_on_event: load_on_event, aspect_ratio: aspect_ratio, starting_width: starting_width, id: media.id, y_position: y_position, x_position: x_position 13 | endif 14 | %} 15 | -------------------------------------------------------------------------------- /snippets/ucoast-scheme-classes.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | assign scheme1 = settings.color_schemes | first 3 | if mobile_color_scheme and desktop_color_scheme 4 | assign mobile_color_scheme = mobile_color_scheme | lstrip | rstrip 5 | assign desktop_color_scheme = desktop_color_scheme | lstrip | rstrip 6 | unless mobile_color_scheme == 'background-1' or disable_padding == true 7 | echo ' p-section-background--s-down ' 8 | endunless 9 | echo 'color-' | append: mobile_color_scheme | append: '--s-down ' 10 | unless desktop_color_scheme == 'background-1' or disable_padding == true 11 | echo ' p-section-background--s-up ' 12 | endunless 13 | echo 'color-' | append: desktop_color_scheme | append: '--s-up ' 14 | else 15 | assign color_scheme = color_scheme | lstrip | rstrip 16 | unless color_scheme == 'background-1' or disable_padding == true 17 | echo ' p-section-background ' 18 | endunless 19 | echo 'color-' | append: color_scheme | append: ' ' 20 | endif 21 | %} 22 | -------------------------------------------------------------------------------- /snippets/ucoast-spacer.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | capture spacer_class 3 | echo ' sa-' | append: settings.space_above | append: ' ' 4 | if settings.mobile_space_above != -1 5 | echo ' sa-' | append: settings.mobile_space_above | append: '--m-down ' 6 | endif 7 | endcapture 8 | echo '
    ' 9 | %} 10 | -------------------------------------------------------------------------------- /snippets/ucoast-spacing.liquid: -------------------------------------------------------------------------------- 1 | {% liquid 2 | if settings.mobile_space_above == -1 3 | echo ' sa-' | append: settings.space_above | append: ' ' 4 | else 5 | echo ' sa-' | append: settings.space_above | append: ' ' 6 | echo ' sa-' | append: settings.mobile_space_above | append: '--m-down ' 7 | endif 8 | %} 9 | -------------------------------------------------------------------------------- /snippets/ucoast-strip-src.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} string replace "src" with "data-src" to prevent google from yelling at me{% endcomment %} 2 | {% liquid 3 | assign output = input | replace: 'src=', 'data-src=' 4 | echo output 5 | %} 6 | -------------------------------------------------------------------------------- /snippets/ucoast-style-tag.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | file: feeds into 'ucoast' param in the vite plugin 3 | settings [bool]: 4 | preload_stylesheet: if true, will preload the stylesheet 5 | {% endcomment %} 6 | 7 | {% liquid 8 | assign base_file = file | append: '.scss' 9 | capture stylesheet 10 | if preload_stylesheet 11 | render 'vite' with base_file, preload_stylesheet: true 12 | else 13 | render 'vite' with base_file 14 | endif 15 | endcapture 16 | if stylesheet contains '@vite/client' 17 | echo stylesheet 18 | elsif file contains 'defer' 19 | assign stylesheet = stylesheet | replace: 'type="text/css"', 'type="text/css" defer="defer"' 20 | echo stylesheet 21 | else 22 | echo stylesheet 23 | endif 24 | %} 25 | -------------------------------------------------------------------------------- /snippets/ucoast-video.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | this should only be called from ucoast-media 3 | {% endcomment %} 4 | {% if video %} 5 | 27 | {% liquid 28 | for source in video.sources 29 | if source.mime_type == 'video/mp4' 30 | assign mp4_source = 'data-mp4-src="' | append: source.url | append: '" ' 31 | assign mp4_source_obj = source 32 | elsif source.mime_type == 'application/x-mpegURL' 33 | assign hls_source = 'data-hls-src="' | append: source.url | append: '" ' 34 | assign hls_source_obj = source 35 | endif 36 | endfor 37 | %} 38 | 53 | 54 | {% endif %} 55 | -------------------------------------------------------------------------------- /snippets/vite.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | IMPORTANT: This snippet is automatically generated by vite-plugin-shopify. 3 | Do not attempt to modify this file directly, as any changes will be overwritten by the next build. 4 | {% endcomment %} 5 | {% assign path = vite | replace: '@js/', '../scripts/' | replace: '@scss/', '../styles/' | replace: '~/', '../' | replace: '@/', '../' %} 6 | {% liquid 7 | assign path_prefix = path | slice: 0 8 | if path_prefix == '/' 9 | assign file_url_prefix = 'https://localhost:3000' 10 | else 11 | assign file_url_prefix = 'https://localhost:3000/src/entry/' 12 | endif 13 | assign file_url = path | prepend: file_url_prefix 14 | assign file_name = path | split: '/' | last 15 | if file_name contains '.' 16 | assign file_extension = file_name | split: '.' | last 17 | endif 18 | assign css_extensions = 'css|less|sass|scss|styl|stylus|pcss|postcss' | split: '|' 19 | assign is_css = false 20 | if css_extensions contains file_extension 21 | assign is_css = true 22 | endif 23 | %} 24 | 25 | {% if is_css == true %} 26 | {{ file_url | stylesheet_tag }} 27 | {% else %} 28 | 29 | {% endif %} 30 | -------------------------------------------------------------------------------- /src/entry/_critical-css.scss: -------------------------------------------------------------------------------- 1 | @import '@scss/global/critical-css.scss'; 2 | -------------------------------------------------------------------------------- /src/entry/_critical.ts: -------------------------------------------------------------------------------- 1 | import * as global from '@/scripts/core/global' 2 | const { ON_CHANGE_DEBOUNCE_TIMER, PUB_SUB_EVENTS, ATTRIBUTES, SELECTORS } = global 3 | export { ON_CHANGE_DEBOUNCE_TIMER, PUB_SUB_EVENTS, ATTRIBUTES, SELECTORS } 4 | global.globalSetup() 5 | -------------------------------------------------------------------------------- /src/entry/css-dir-article-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | -------------------------------------------------------------------------------- /src/entry/css-dir-article-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | @import '../styles/modules/article-prio'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-base-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | -------------------------------------------------------------------------------- /src/entry/css-dir-base-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | -------------------------------------------------------------------------------- /src/entry/css-dir-blog-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | -------------------------------------------------------------------------------- /src/entry/css-dir-blog-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | @import '../styles/modules/blog-prio'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-collection-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | @import '../styles/base-defer/component-facets'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-collection-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | @import '../styles/modules/collection-prio'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-customer-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | -------------------------------------------------------------------------------- /src/entry/css-dir-customer-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | @import '../styles/modules/customer-prio'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-page-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | -------------------------------------------------------------------------------- /src/entry/css-dir-page-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | @import '../styles/modules/page-prio'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-product-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-defer'; 2 | @import '../styles/modules/product-defer'; 3 | -------------------------------------------------------------------------------- /src/entry/css-dir-product-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/modules/base-prio'; 2 | @import "../styles/modules/product-prio"; 3 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-localization-form.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-localization-form'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-mega-menu.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-mega-menu'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-model-viewer-ui.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-model-viewer-ui'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-pagination.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-pagination'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-pickup-availability.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-pickup-availability'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-product-model.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-product-model'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-component-recipient-form.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/component-recipient-form'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-mask-blobs.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/mask-blobs'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-quick-add.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/quick-add'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-section-contact-form.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/section-contact-form'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-section-password.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/section-password'; 2 | -------------------------------------------------------------------------------- /src/entry/css-optional-template-giftcard.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/optional/template-giftcard'; 2 | -------------------------------------------------------------------------------- /src/entry/js-dir-cart-drawer.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q} from '@/scripts/core/TsDOM' 2 | import { CartDrawer } from '@/scripts/cart/cart-drawer' 3 | import { CartNote } from '@/scripts/cart/cart-note' 4 | import { CartDrawerItems } from '@/scripts/cart/cart-drawer-items' 5 | import { CartRemoveButton } from '@/scripts/cart/cart-remove-button' 6 | import { CartItems } from '@/scripts/cart/cart-items'; 7 | import { PredictiveSearch } from '@/scripts/optional/predictive-search'; 8 | import { QuantityInput } from '@/scripts/theme/quantity-input'; 9 | import { SearchForm } from '@/scripts/theme/search-form'; 10 | import { CartNotification } from '@/scripts/theme/cart-notification'; 11 | import { HeaderMenu } from '@/scripts/theme/header-menu'; 12 | import { DetailsModal } from '@/scripts/theme/details-modal'; 13 | import { MenuDrawer } from '@/scripts/theme/menu-drawer'; 14 | import { HeaderDrawer } from '@/scripts/theme/header-drawer'; 15 | import { ModalDialog } from '@/scripts/theme/modal-dialog'; 16 | import { ModalOpener } from '@/scripts/theme/modal-opener'; 17 | import { DynamicProgressBar } from '@/scripts/cart/dynamic-progress-bar' 18 | 19 | q.safeDefineElement(QuantityInput) 20 | q.safeDefineElement(CartNotification) 21 | q.safeDefineElement(CartDrawer) 22 | q.safeDefineElement(CartNote) 23 | q.safeDefineElement(CartItems) 24 | q.safeDefineElement(CartDrawerItems) 25 | q.safeDefineElement(CartRemoveButton) 26 | q.safeDefineElement(SearchForm) 27 | q.safeDefineElement(PredictiveSearch) 28 | q.safeDefineElement(HeaderMenu) 29 | q.safeDefineElement(DetailsModal) 30 | q.safeDefineElement(MenuDrawer) 31 | q.safeDefineElement(HeaderDrawer) 32 | q.safeDefineElement(ModalDialog) 33 | q.safeDefineElement(ModalOpener) 34 | q.safeDefineElement(DynamicProgressBar) 35 | -------------------------------------------------------------------------------- /src/entry/js-dir-catalog.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q} from '@/scripts/core/TsDOM' 2 | 3 | import { FacetFiltersForm, beforeFacetFiltersForm, afterFacetFiltersForm } from '@/scripts/catalog/facet-filters-form' 4 | import { PriceRange } from '@/scripts/catalog/price-range' 5 | import { FacetRemove } from '@/scripts/catalog/facet-remove' 6 | import { MainSearch } from '@/scripts/catalog/main-search' 7 | import { ShowMoreButton } from '@/scripts/catalog/show-more-button'; 8 | 9 | q.safeDefineElement(FacetFiltersForm, beforeFacetFiltersForm, afterFacetFiltersForm) 10 | q.safeDefineElement(PriceRange) 11 | q.safeDefineElement(FacetRemove) 12 | q.safeDefineElement(MainSearch) 13 | q.safeDefineElement(ShowMoreButton) 14 | -------------------------------------------------------------------------------- /src/entry/js-dir-customer.ts: -------------------------------------------------------------------------------- 1 | import { initializeCustomerAddresses } from '@/scripts/customer/customer'; 2 | 3 | initializeCustomerAddresses(); 4 | -------------------------------------------------------------------------------- /src/entry/js-dir-product.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q} from '@/scripts/core/TsDOM' 2 | import { ProductForm } from '@/scripts/product/product-form' 3 | import { ProductInfo } from '@/scripts/product/product-info' 4 | import { ProductModal } from '@/scripts/product/product-modal' 5 | import { MediaGallery } from '@/scripts/product/media-gallery' 6 | import { VariantSelects } from '@/scripts/theme/variant-selects'; 7 | import { VariantRadios } from '@/scripts/theme/variant-radios'; 8 | import { ProductRecommendations } from '@/scripts/theme/product-recommendations'; 9 | 10 | q.safeDefineElement(ProductInfo) 11 | q.safeDefineElement(ProductForm) 12 | q.safeDefineElement(ProductModal) 13 | q.safeDefineElement(MediaGallery) 14 | q.safeDefineElement(VariantSelects) 15 | q.safeDefineElement(VariantRadios) 16 | q.safeDefineElement(ProductRecommendations) 17 | -------------------------------------------------------------------------------- /src/entry/js-dir-theme.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q} from '@/scripts/core/TsDOM' 2 | import { DeferredMedia } from '@/scripts/theme/deferred-media' 3 | import { DetailsDisclosure } from '@/scripts/theme/details-disclosure' 4 | import { SliderComponent } from '@/scripts/theme/slider-component' 5 | import { SlideshowComponent } from '@/scripts/theme/slideshow-component' 6 | import { StickyHeader } from '@/scripts/theme/sticky-header' 7 | import { UcoastVideo } from '@/scripts/core/ucoast-video'; 8 | import { ProductSlider } from '@/scripts/theme/product-slider' 9 | import { MultiProductSlider } from '@/scripts/theme/multi-product-slider' 10 | import { WaitlistForm } from '@/scripts/theme/waitlist-form' 11 | import { KlaviyoForm } from '@/scripts/theme/klaviyo-form' 12 | import { WelcomePopup } from '@/scripts/theme/welcome-popup' 13 | q.safeDefineElement(UcoastVideo) 14 | q.safeDefineElement(DeferredMedia) 15 | q.safeDefineElement(SliderComponent) 16 | q.safeDefineElement(SlideshowComponent) 17 | q.safeDefineElement(StickyHeader) 18 | q.safeDefineElement(DetailsDisclosure) 19 | q.safeDefineElement(ProductSlider) 20 | q.safeDefineElement(MultiProductSlider) 21 | q.safeDefineElement(KlaviyoForm) 22 | q.safeDefineElement(WelcomePopup) 23 | q.safeDefineElement(WaitlistForm) 24 | -------------------------------------------------------------------------------- /src/entry/js-optional-animations.ts: -------------------------------------------------------------------------------- 1 | import { initializeScrollAnimationTrigger } from '@/scripts/theme/animations'; 2 | import { TsDOM as q } from '@/scripts/core/TsDOM'; 3 | 4 | window.addEventListener('DOMContentLoaded', () => initializeScrollAnimationTrigger()) 5 | 6 | document.addEventListener('shopify:section:load', (event: Event) => { 7 | initializeScrollAnimationTrigger(q.rt(event)) 8 | }) 9 | -------------------------------------------------------------------------------- /src/entry/js-optional-localization-form.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q} from '@/scripts/core/TsDOM' 2 | import { LocalizationForm } from '@/scripts/optional/localization-form'; 3 | 4 | q.safeDefineElement(LocalizationForm) 5 | -------------------------------------------------------------------------------- /src/entry/js-optional-magnify.ts: -------------------------------------------------------------------------------- 1 | import { enableZoomOnHover } from '@/scripts/optional/magnify'; 2 | 3 | enableZoomOnHover(2); 4 | -------------------------------------------------------------------------------- /src/entry/js-optional-password-modal.ts: -------------------------------------------------------------------------------- 1 | import { PasswordModal } from '@/scripts/optional/password-modal' 2 | import { TsDOM as q} from '@/scripts/core/TsDOM' 3 | q.safeDefineElement(PasswordModal) 4 | -------------------------------------------------------------------------------- /src/entry/js-optional-pickup-availability.ts: -------------------------------------------------------------------------------- 1 | import { PickupAvailability } from '@/scripts/optional/pickup-availability' 2 | import { PickupAvailabilityDrawer } from '@/scripts/optional/pickup-availability-drawer' 3 | import { TsDOM as q} from '@/scripts/core/TsDOM' 4 | q.safeDefineElement(PickupAvailabilityDrawer) 5 | q.safeDefineElement(PickupAvailability) 6 | 7 | -------------------------------------------------------------------------------- /src/entry/js-optional-product-model.ts: -------------------------------------------------------------------------------- 1 | import { ProductModel } from '@/scripts/optional/product-model' 2 | import { TsDOM as q} from '@/scripts/core/TsDOM' 3 | q.safeDefineElement(ProductModel) 4 | -------------------------------------------------------------------------------- /src/entry/js-optional-quick-add.ts: -------------------------------------------------------------------------------- 1 | import {QuickAddModal} from '@/scripts/optional/quick-add'; 2 | import { TsDOM as q} from '@/scripts/core/TsDOM' 3 | import { ProductForm } from '@/scripts/product/product-form'; 4 | q.safeDefineElement(QuickAddModal); 5 | q.safeDefineElement(ProductForm) 6 | -------------------------------------------------------------------------------- /src/entry/js-optional-recipient-form.ts: -------------------------------------------------------------------------------- 1 | import {RecipientForm} from '@/scripts/optional/recipient-form'; 2 | import { TsDOM as q} from '@/scripts/core/TsDOM' 3 | q.safeDefineElement(RecipientForm); 4 | -------------------------------------------------------------------------------- /src/entry/js-optional-share-button.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q} from '@/scripts/core/TsDOM' 2 | import { ShareButton } from '@/scripts/optional/share-button'; 3 | 4 | q.safeDefineElement(ShareButton) 5 | -------------------------------------------------------------------------------- /src/entry/js-optional-theme-editor.ts: -------------------------------------------------------------------------------- 1 | import {initializeThemeEditor} from '@/scripts/optional/theme-editor' 2 | initializeThemeEditor() 3 | -------------------------------------------------------------------------------- /src/scripts/cart/cart-drawer-items.ts: -------------------------------------------------------------------------------- 1 | import { CartItems } from '@/scripts/cart/cart-items' 2 | import { SELECTORS } from '@/scripts/core/global'; 3 | 4 | export class CartDrawerItems extends CartItems { 5 | static override htmlSelector = 'cart-drawer-items' 6 | static override selectors = { 7 | ...CartItems.selectors, 8 | element: 'cart-drawer-items', 9 | lineItemStatus: '[data-uc-cart-drawer-status]', 10 | errors: '[data-uc-cart-drawer-errors]', 11 | liveRegionText: '[data-uc-cart-drawer-live-region-text]', 12 | main: '[data-uc-cart-drawer-main]', 13 | // the following selectors are partial - they will be concatenated with the line ID 14 | line: '#CartDrawer-Item', // ex: `${this.instanceSelectors.line}-${line}` 15 | lineQuantity: '#CartDrawer-Quantity', // ex: `${this.instanceSelectors.lineQuantity}-${line}` 16 | lineError: '#CartDrawer-LineItemError', // ex: `${this.instanceSelectors.lineError}-${line}` 17 | } 18 | constructor() { 19 | super(); 20 | 21 | } 22 | override setInstanceSelectors() { 23 | this.instanceSelectors = CartDrawerItems.selectors 24 | } 25 | override getSectionsToRender() { 26 | return [ 27 | { 28 | id: 'CartDrawer', 29 | section: 'cart-drawer-items', 30 | selector: 'cart-drawer-items', 31 | }, 32 | { 33 | id: 'CartIconBubble', 34 | section: 'cart-icon-bubble', 35 | selector: SELECTORS.cartLink, 36 | }, 37 | { 38 | id: 'CartDrawer', 39 | section: 'dynamic-progress-bar', 40 | selector: 'dynamic-progress-bar', 41 | }, 42 | { 43 | id: 'CartDrawer', 44 | section: 'dynamic-cart-footer', 45 | selector: '.drawer__footer', 46 | }, 47 | { 48 | id: 'CartDrawer', 49 | section: 'cart-update-instructions', 50 | selector: '[data-cart-update-instructions]', 51 | }, 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/scripts/cart/cart-note.ts: -------------------------------------------------------------------------------- 1 | import { ON_CHANGE_DEBOUNCE_TIMER } from '@/scripts/core/global' 2 | import { fetchConfig } from '@/scripts/core/global'; 3 | import { TsDOM as q, debounce } from '@/scripts/core/TsDOM' 4 | import { UcoastEl } from '@/scripts/core/UcoastEl'; 5 | 6 | export class CartNote extends UcoastEl { 7 | static htmlSelector = 'cart-note' 8 | input: HTMLTextAreaElement 9 | constructor() { 10 | super() 11 | this.input = q.rs('textarea', this) 12 | 13 | this.input.addEventListener( 14 | 'change', 15 | debounce((event: Event) => { 16 | const target = q.rt(event) 17 | const body = JSON.stringify({ note: target.value }) 18 | void fetch(`${window.routes.cart_update_url}`, { ...fetchConfig(), ...{ body } }) 19 | }, ON_CHANGE_DEBOUNCE_TIMER) 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/scripts/cart/cart-remove-button.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | import { type CartItems } from '@/scripts/cart/cart-items' 3 | import { type CartDrawerItems } from '@/scripts/cart/cart-drawer-items' 4 | import { UcoastEl } from '@/scripts/core/UcoastEl' 5 | 6 | export class CartRemoveButton extends UcoastEl { 7 | static htmlSelector = 'cart-remove-button' 8 | constructor() { 9 | super() 10 | 11 | this.addEventListener('click', (event) => { 12 | event.preventDefault() 13 | const cartItems = 14 | q.oc(this, 'cart-items') || 15 | q.rc(this, 'cart-drawer-items') 16 | cartItems.updateQuantity(q.ra(this, 'data-index'), '0') 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/scripts/cart/dynamic-progress-bar.ts: -------------------------------------------------------------------------------- 1 | import { UcoastEl } from '@/scripts/core/UcoastEl' 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | 4 | export class DynamicProgressBar extends UcoastEl { 5 | static htmlSelector = 'dynamic-progress-bar' 6 | statusEl: HTMLElement 7 | constructor() { 8 | super() 9 | this.statusEl = this.getStatusEl(this) 10 | } 11 | getStatusEl(el: DynamicProgressBar) { 12 | return q.rs('[data-shipping-bar-status]', el) 13 | } 14 | animateFromRawHTML(rawHTML: string) { 15 | const newDocument = new DOMParser().parseFromString( 16 | rawHTML, 17 | 'text/html' 18 | ) 19 | const newShippingBar = q.rs( 20 | DynamicProgressBar.htmlSelector, 21 | newDocument 22 | ) 23 | Array.from(newShippingBar.attributes).forEach(attr => { 24 | if (attr.name === 'class' || attr.name === 'style') return 25 | this.setAttribute(attr.name, attr.value); 26 | }); 27 | const newPercent = parseInt( 28 | q.ra(newShippingBar, 'data-percent') 29 | ) 30 | const newStatusEl = this.getStatusEl(newShippingBar) 31 | this.statusEl.innerHTML = newStatusEl.innerHTML 32 | this.style.setProperty('--progress-percent', `${newPercent}%`); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/catalog/facet-remove.ts: -------------------------------------------------------------------------------- 1 | import { UcoastEl } from '@/scripts/core/UcoastEl'; 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | import { type FacetFiltersForm } from '@/scripts/catalog/facet-filters-form'; 4 | 5 | export class FacetRemove extends UcoastEl { 6 | static htmlSelector = 'facet-remove' 7 | constructor() { 8 | super() 9 | const facetLink = q.rs('a', this) 10 | facetLink.setAttribute('role', 'button') 11 | facetLink.addEventListener('click', this.closeFilter.bind(this)) 12 | facetLink.addEventListener('keyup', (event) => { 13 | event.preventDefault() 14 | if (event.code?.toUpperCase() === 'SPACE') this.closeFilter(event) 15 | }) 16 | } 17 | 18 | closeFilter(event: Event) { 19 | event.preventDefault() 20 | const form = 21 | q.oc(this, 'facet-filters-form') || 22 | q.rs('facet-filters-form') 23 | form.onActiveFilterClick(event) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/scripts/catalog/main-search.ts: -------------------------------------------------------------------------------- 1 | import { SearchForm } from '@/scripts/theme/search-form'; 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | 4 | export class MainSearch extends SearchForm { 5 | static override htmlSelector = 'main-search'; 6 | allSearchInputs: NodeListOf; 7 | constructor() { 8 | super(); 9 | this.allSearchInputs = q.rl('input[type="search"]') 10 | this.setupEventListeners(); 11 | } 12 | 13 | setupEventListeners() { 14 | let allSearchForms:HTMLFormElement[] = []; 15 | this.allSearchInputs.forEach((input) => { 16 | if (input.form) { 17 | allSearchForms.push(input.form) 18 | } else { 19 | throw new Error('input in allSearchInputs is not in a form element') 20 | } 21 | }); 22 | this.input.addEventListener('focus', this.onInputFocus.bind(this)); 23 | if (allSearchForms.length < 2) return; 24 | allSearchForms.forEach((form) => form.addEventListener('reset', this.onFormReset.bind(this))); 25 | this.allSearchInputs.forEach((input) => input.addEventListener('input', this.onInput.bind(this))); 26 | } 27 | 28 | override onFormReset(event:Event) { 29 | super.onFormReset(event); 30 | if (super.shouldResetForm()) { 31 | this.keepInSync('', this.input); 32 | } 33 | } 34 | 35 | onInput(event:Event) { 36 | const target = q.rt(event); 37 | this.keepInSync(target.value, target); 38 | } 39 | 40 | onInputFocus() { 41 | const isSmallScreen = window.innerWidth < 750; 42 | if (isSmallScreen) { 43 | this.scrollIntoView({ behavior: 'smooth' }); 44 | } 45 | } 46 | 47 | keepInSync(value:string, target:HTMLInputElement) { 48 | this.allSearchInputs.forEach((input) => { 49 | if (input !== target) { 50 | input.value = value; 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/scripts/catalog/price-range.ts: -------------------------------------------------------------------------------- 1 | import { UcoastEl } from '@/scripts/core/UcoastEl' 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | 4 | export class PriceRange extends UcoastEl { 5 | static htmlSelector = 'price-range' 6 | static selectors = { 7 | input: '[data-uc-price-range-input]', 8 | max: '[data-uc-price-range-input="max"]', 9 | min: '[data-uc-price-range-input="min"]', 10 | } 11 | inputs: NodeListOf 12 | maxInput: HTMLInputElement 13 | minInput: HTMLInputElement 14 | constructor() { 15 | super() 16 | this.maxInput = q.rs(PriceRange.selectors.max, this) 17 | this.minInput = q.rs(PriceRange.selectors.min, this) 18 | this.inputs = q.rl(PriceRange.selectors.input, this) 19 | this.inputs.forEach((element) => 20 | element.addEventListener('change', this.onRangeChange.bind(this)) 21 | ) 22 | this.setMinAndMaxValues() 23 | } 24 | 25 | onRangeChange(event: Event) { 26 | const currentTarget = q.rct(event) 27 | this.adjustToValidValues(currentTarget) 28 | this.setMinAndMaxValues() 29 | } 30 | 31 | setMinAndMaxValues() { 32 | if (this.maxInput.value) this.minInput.setAttribute('max', this.maxInput.value) 33 | if (this.minInput.value) this.maxInput.setAttribute('min', this.minInput.value) 34 | if (this.minInput.value === '') this.maxInput.setAttribute('min', '0') 35 | if (this.maxInput.value === '') 36 | this.minInput.setAttribute('max', q.ra(this.maxInput, 'max')) 37 | } 38 | 39 | adjustToValidValues(input: HTMLInputElement) { 40 | const value = Number(input.value) 41 | const min = Number(q.ra(input, 'min')) 42 | const max = Number(q.ra(input, 'max')) 43 | 44 | if (value < min) input.value = `${min}` 45 | if (value > max) input.value = `${max}` 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/scripts/catalog/show-more-button.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | import { UcoastEl } from '@/scripts/core/UcoastEl' 3 | 4 | export class ShowMoreButton extends UcoastEl { 5 | static htmlSelector = 'show-more-button' 6 | static selectors = { 7 | button: '[data-uc-show-more]', 8 | item: '[data-uc-show-more-item]', 9 | display: '[data-uc-show-more-display]', 10 | wrap: '[data-uc-show-more-wrap]', 11 | label: '[data-uc-show-more-label]' 12 | } 13 | index: string 14 | constructor() { 15 | super() 16 | const button = q.rs(ShowMoreButton.selectors.button, this) 17 | this.index = q.ra(button, 'data-uc-show-more') 18 | button.addEventListener('click', (event) => { 19 | this.expandShowMore(event) 20 | 21 | const nextElementToFocus = q.os( 22 | `[data-uc-show-more-item${this.index}]:not('.hidden)'` 23 | ) 24 | if (!nextElementToFocus) return 25 | const input = q.os('input', nextElementToFocus) 26 | if (!input) return 27 | input.focus() 28 | }) 29 | } 30 | expandShowMore(_event: Event) { 31 | this.querySelectorAll(ShowMoreButton.selectors.label).forEach((element) => 32 | element.classList.toggle('hidden') 33 | ) 34 | const items = q.ol(`[data-uc-show-more-item="${this.index}"]`) 35 | items?.forEach((item) => item.classList.toggle('hidden')) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/scripts/core/UcoastEl.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | 3 | export class UcoastEl extends HTMLElement { 4 | animateSelf: boolean 5 | animationObserver: IntersectionObserver | null 6 | animateableElements?: NodeListOf 7 | constructor() { 8 | super() 9 | this.animateSelf = this.hasAttribute('data-uc-animate') 10 | this.animateableElements = q.ol('[data-uc-animate]', this) 11 | this.animationObserver = null 12 | } 13 | connectedCallback() { 14 | if (this.animateSelf || this.animateableElements) { 15 | this.animateOut() 16 | this.observeVisibility() 17 | } 18 | } 19 | animateIn() { 20 | if (this.animateSelf) { 21 | this.setAttribute('data-uc-animate', 'in') 22 | } else if (this.animateableElements) { 23 | this.animateableElements.forEach((el) => { 24 | el.setAttribute('data-uc-animate', 'in') 25 | }) 26 | } 27 | } 28 | animateOut() { 29 | if (this.animateSelf) { 30 | this.setAttribute('data-uc-animate', 'ready') 31 | } else if (this.animateableElements) { 32 | this.animateableElements.forEach((el) => { 33 | el.setAttribute('data-uc-animate', 'ready') 34 | }) 35 | } 36 | } 37 | observeVisibility() { 38 | const animateArea = this.getAttribute('data-uc-animate-area') 39 | const threshold = animateArea ? parseFloat(animateArea) / 100 : 1.0 40 | 41 | const options = { 42 | root: null, 43 | rootMargin: '0px', 44 | threshold: threshold, 45 | } 46 | 47 | this.animationObserver = new IntersectionObserver((entries) => { 48 | entries.forEach((entry) => { 49 | if (entry.isIntersecting && entry.intersectionRatio >= threshold) { 50 | this.animateIn() 51 | } else { 52 | this.animateOut() 53 | } 54 | }) 55 | }, options) 56 | 57 | if (this.animateSelf) { 58 | this.animationObserver.observe(this) 59 | } else if (this.animateableElements) { 60 | this.animateableElements.forEach((el) => this.animationObserver?.observe(el)) 61 | } 62 | } 63 | disconnectedCallback() { 64 | if (this.animationObserver) { 65 | this.animationObserver.disconnect() 66 | } 67 | } 68 | validate(el: HTMLElement) { 69 | return el instanceof this.constructor 70 | } 71 | validated(el: HTMLElement, required: boolean) { 72 | const isValid = this.validate(el) 73 | if (isValid) return el 74 | if (required) throw new Error('Class Instance Not Valid') 75 | return undefined 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/scripts/core/art-direction.ts: -------------------------------------------------------------------------------- 1 | import { UcoastEl } from '@/scripts/core/UcoastEl' 2 | 3 | export class ArtDirection extends UcoastEl { 4 | static htmlSelector = 'art-direction' 5 | constructor() { 6 | super() 7 | } 8 | 9 | override async connectedCallback() { 10 | super.connectedCallback() 11 | } 12 | onMediaLoad() { 13 | this.classList.add('loaded') 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/scripts/optional/password-modal.ts: -------------------------------------------------------------------------------- 1 | import { DetailsModal } from '@/scripts/theme/details-modal' 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | 4 | export class PasswordModal extends DetailsModal { 5 | static override htmlSelector = 'password-modal' 6 | constructor() { 7 | super() 8 | 9 | if (this.querySelector('input[aria-invalid="true"]')) 10 | this.open({ target: q.rs('details', this) }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/scripts/optional/pickup-availability-drawer.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | import { UcoastEl } from '@/scripts/core/UcoastEl' 3 | 4 | export class PickupAvailabilityDrawer extends UcoastEl { 5 | static htmlSelector = 'pickup-availability-drawer' 6 | onBodyClick: (event: MouseEvent) => void 7 | opener: HTMLButtonElement 8 | focusElement?: HTMLElement 9 | constructor() { 10 | super() 11 | 12 | this.onBodyClick = this.handleBodyClick.bind(this) 13 | this.opener = q.rs('button', this) 14 | 15 | this.opener.addEventListener('click', () => { 16 | this.hide() 17 | }) 18 | 19 | this.addEventListener('keyup', (event) => { 20 | if (event.code?.toUpperCase() === 'ESCAPE') this.hide() 21 | }) 22 | } 23 | 24 | handleBodyClick(event: MouseEvent) { 25 | const target = q.rt(event) 26 | if ( 27 | target != this && 28 | !target.closest('pickup-availability-drawer') && 29 | target.id != 'ShowPickupAvailabilityDrawer' 30 | ) { 31 | this.hide() 32 | } 33 | } 34 | 35 | hide() { 36 | this.removeAttribute('open') 37 | document.body.removeEventListener('click', this.onBodyClick) 38 | document.body.classList.remove('overflow-hidden') 39 | window.TsDOM.removeTrapFocus(this.focusElement) 40 | } 41 | 42 | show(focusElement: HTMLElement) { 43 | this.focusElement = focusElement 44 | this.setAttribute('open', '') 45 | document.body.addEventListener('click', this.onBodyClick) 46 | document.body.classList.add('overflow-hidden') 47 | window.TsDOM.trapFocus(this) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/scripts/product/product-modal.ts: -------------------------------------------------------------------------------- 1 | import { ModalDialog } from '@/scripts/theme/modal-dialog' 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | import { DeferredMedia } from '@/scripts/theme/deferred-media' 4 | 5 | export class ProductModal extends ModalDialog { 6 | static override htmlSelector = 'product-modal' 7 | constructor() { 8 | super() 9 | } 10 | static selectors = { 11 | container: '[data-uc-product-modal-container]', 12 | } 13 | 14 | override hide() { 15 | super.hide() 16 | } 17 | 18 | override show(opener: HTMLElement) { 19 | super.show(opener) 20 | this.showActiveMedia() 21 | } 22 | 23 | showActiveMedia() { 24 | const openedById = this.openedBy ? this.openedBy.getAttribute('data-media-id') : 'NO_OPENER' 25 | this.querySelectorAll(`[data-media-id]:not([data-media-id="${openedById}"])`).forEach( 26 | (element) => { 27 | element.classList.remove('active') 28 | } 29 | ) 30 | const activeMedia = q.os< 31 | HTMLImageElement | HTMLVideoElement | HTMLIFrameElement | DeferredMedia 32 | >(`[data-media-id="${openedById}"]`, this) 33 | if (!activeMedia) { 34 | console.error('no active media found') 35 | return 36 | } 37 | const activeMediaTemplate = activeMedia.querySelector('template') 38 | const activeMediaContent = activeMediaTemplate ? activeMediaTemplate.content : null 39 | activeMedia.classList.add('active') 40 | activeMedia.scrollIntoView() 41 | void window.Ucoast.mediaManager.playAllInContainer(this) 42 | 43 | const container = q.rs(ProductModal.selectors.container, this) 44 | const activeMediaWidth = activeMedia.width 45 | ? parseInt(`${activeMedia.width}`) 46 | : activeMedia.clientWidth 47 | container.scrollLeft = (activeMediaWidth - container.clientWidth) / 2 48 | 49 | if ( 50 | activeMedia instanceof DeferredMedia && 51 | activeMediaContent && 52 | activeMediaContent.querySelector('.js-youtube') 53 | ) 54 | activeMedia.loadContent() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/scripts/theme/constants.ts: -------------------------------------------------------------------------------- 1 | export const ON_CHANGE_DEBOUNCE_TIMER = 300 2 | 3 | export const PUB_SUB_EVENTS = { 4 | cartUpdate: 'cart-update', 5 | quantityUpdate: 'quantity-update', 6 | variantChange: 'variant-change', 7 | cartError: 'cart-error', 8 | } 9 | 10 | // common attributes. sometimes these are used as elements selectors, if the attribute is added/removed 11 | export const ATTRIBUTES = { 12 | cartEmpty: 'data-uc-cart-empty', 13 | loading: 'data-uc-loading', 14 | submenu: 'data-uc-submenu', 15 | menuOpening: 'data-uc-menu-opening' 16 | } 17 | 18 | // selectors for common elements where the selector won't be added/removed 19 | export const SELECTORS = { 20 | cartLink: '[data-uc-cart-icon-bubble]', 21 | loadingOverlay: '[data-uc-loading-overlay]', 22 | loadingOverlaySpinner: '[data-uc-loading-overlay-spinner]', 23 | sectionHeader: '.section-header', 24 | headerWrapper: '[data-uc-header-wrapper]', 25 | } 26 | -------------------------------------------------------------------------------- /src/scripts/theme/deferred-media.ts: -------------------------------------------------------------------------------- 1 | import { pauseAllMedia } from '@/scripts/core/global' 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | import { UcoastEl } from '@/scripts/core/UcoastEl' 4 | 5 | export class DeferredMedia extends UcoastEl { 6 | static htmlSelector = 'deferred-media' 7 | width?: number 8 | constructor() { 9 | super() 10 | const poster = this.querySelector('[id^="Deferred-Poster-"]') 11 | if (!poster) return 12 | poster.addEventListener('click', this.loadContent.bind(this)) 13 | } 14 | 15 | getTemplate() { 16 | return q.rs('template', this) 17 | } 18 | 19 | loadContent(focus = true) { 20 | pauseAllMedia() 21 | if (!this.getAttribute('loaded')) { 22 | const content = document.createElement('div') 23 | const template = this.getTemplate() 24 | if (!template || !template.content || !template.content.firstElementChild) { 25 | throw new Error('No template content found') 26 | } 27 | content.appendChild(template.content.firstElementChild.cloneNode(true)) 28 | void window.Ucoast.mediaManager.playAllInContainer(this) 29 | 30 | this.setAttribute('loaded', 'true') 31 | const deferredElement = this.appendChild( 32 | q.rs('video, model-viewer, iframe', content) 33 | ) 34 | if (focus) deferredElement.focus() 35 | if ( 36 | deferredElement instanceof HTMLVideoElement && 37 | deferredElement.getAttribute('autoplay') 38 | ) { 39 | // force autoplay for safari 40 | void deferredElement.play() 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/scripts/theme/details-disclosure.ts: -------------------------------------------------------------------------------- 1 | import { UcoastEl } from '@/scripts/core/UcoastEl'; 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | 4 | export class DetailsDisclosure extends UcoastEl { 5 | static htmlSelector = 'details-disclosure' 6 | mainDetailsToggle: HTMLDetailsElement 7 | content: HTMLElement 8 | animations?: Animation[] 9 | 10 | constructor() { 11 | super() 12 | this.mainDetailsToggle = q.rs('details', this) 13 | this.content = q.rs('summary', this.mainDetailsToggle, 'nextElementSibling') 14 | 15 | this.mainDetailsToggle.addEventListener('focusout', this.onFocusOut.bind(this)) 16 | this.mainDetailsToggle.addEventListener('toggle', this.onToggle.bind(this)) 17 | } 18 | 19 | 20 | 21 | onFocusOut() { 22 | setTimeout(() => { 23 | if (!this.contains(document.activeElement)) this.close() 24 | }) 25 | } 26 | 27 | onToggle() { 28 | if (!this.animations) this.animations = this.content.getAnimations() 29 | 30 | if (this.mainDetailsToggle.hasAttribute('open')) { 31 | this.animations.forEach((animation) => animation.play()) 32 | } else { 33 | this.animations.forEach((animation) => animation.cancel()) 34 | } 35 | } 36 | 37 | close() { 38 | this.mainDetailsToggle.removeAttribute('open') 39 | q.rs('summary', this.mainDetailsToggle).setAttribute('aria-expanded', 'false') 40 | window.Ucoast.openMenuId = undefined 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/scripts/theme/header-drawer.ts: -------------------------------------------------------------------------------- 1 | import { MenuDrawer } from '@/scripts/theme/menu-drawer' 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | import { ATTRIBUTES, SELECTORS } from '@/scripts/core/global' 4 | 5 | export class HeaderDrawer extends MenuDrawer { 6 | static override htmlSelector = 'header-drawer' 7 | static override selectors = { 8 | ...MenuDrawer.selectors, 9 | } 10 | header?: HTMLElement 11 | borderOffset?: number 12 | constructor() { 13 | super() 14 | } 15 | 16 | override getInstanceSelectors() { 17 | this.instanceSelectors = HeaderDrawer.selectors 18 | } 19 | 20 | setHeader() { 21 | return this.header || q.rs(SELECTORS.sectionHeader) 22 | } 23 | 24 | setBorderOffset() { 25 | return this.borderOffset || 26 | q.rs(SELECTORS.headerWrapper).dataset.ucHeaderWrapper == 'border-bottom' 27 | ? 1 28 | : 0 29 | } 30 | 31 | override openMenuDrawer(summaryElement: HTMLElement) { 32 | this.header = this.setHeader() 33 | this.borderOffset = this.setBorderOffset() 34 | document.documentElement.style.setProperty( 35 | '--header-bottom-position', 36 | `${Math.round(this.header.getBoundingClientRect().bottom - this.borderOffset)}px` 37 | ) 38 | this.header.setAttribute('data-uc-header-menu-open', '') 39 | 40 | setTimeout(() => { 41 | this.mainDetails.setAttribute(ATTRIBUTES.menuOpening, '') 42 | }) 43 | 44 | summaryElement.setAttribute('aria-expanded', 'true') 45 | window.addEventListener('resize', this.onResize) 46 | window.TsDOM.trapFocus(this.mainDetails, summaryElement) 47 | document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`) 48 | } 49 | 50 | override closeMenuDrawer( 51 | event: Event | undefined, 52 | elementToFocus: HTMLElement | undefined = undefined 53 | ) { 54 | if (!elementToFocus) return 55 | this.header = this.setHeader() 56 | super.closeMenuDrawer(event, elementToFocus) 57 | this.header.removeAttribute('data-uc-header-menu-open') 58 | window.removeEventListener('resize', this.onResize) 59 | } 60 | 61 | onResize = () => { 62 | if (!this.header) return 63 | this.borderOffset = this.setBorderOffset() 64 | document.documentElement.style.setProperty( 65 | '--header-bottom-position', 66 | `${Math.round(this.header.getBoundingClientRect().bottom - this.borderOffset)}px` 67 | ) 68 | document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/scripts/theme/header-menu.ts: -------------------------------------------------------------------------------- 1 | import { DetailsDisclosure } from '@/scripts/theme/details-disclosure'; 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | import { 4 | scaleValue, 5 | } from '@/scripts/core/global' 6 | import {type StickyHeader } from '@/scripts/theme/sticky-header'; 7 | import { SELECTORS } from '@/scripts/core/global'; 8 | 9 | export class HeaderMenu extends DetailsDisclosure { 10 | static override htmlSelector = 'header-menu' 11 | header: StickyHeader | null 12 | hoverSummary?: HTMLElement 13 | detailsId?: string 14 | constructor() { 15 | super() 16 | this.header = q.rs(SELECTORS.headerWrapper) 17 | this.hoverSummary = q.os('summary[data-summary-hover="on"]', this) 18 | this.detailsId = q.oa(this.mainDetailsToggle, 'id') 19 | this.initHoverSummary() 20 | } 21 | 22 | initHoverSummary() { 23 | if (!this.hoverSummary || !this.detailsId) return 24 | 25 | this.hoverSummary.addEventListener('click', (_) => { 26 | void window.Ucoast.mediaManager.playAllInContainer(this.mainDetailsToggle) 27 | window.setTimeout(() => { 28 | void window.Ucoast.mediaManager.playAllInContainer(this.mainDetailsToggle) 29 | }, 3) 30 | }) 31 | 32 | this.hoverSummary.addEventListener('mouseenter', (_) => { 33 | void window.Ucoast.mediaManager.playAllInContainer(this.mainDetailsToggle) 34 | const openMenuId = window.Ucoast.openMenuId 35 | if (openMenuId && openMenuId === this.detailsId) return 36 | window.Ucoast.openMenuId = this.detailsId 37 | this.mainDetailsToggle.setAttribute('open', '') 38 | this.animations?.forEach((animation) => animation.play()) 39 | this.hoverSummary?.setAttribute('aria-expanded', 'true') 40 | void window.Ucoast.mediaManager.playAllInContainer(this.mainDetailsToggle) 41 | }) 42 | 43 | this.mainDetailsToggle.addEventListener('keyup', q.onKeyUpEscape) 44 | } 45 | 46 | override onToggle() { 47 | if (!this.header) return 48 | this.header.preventHide = this.mainDetailsToggle.open 49 | 50 | if ( 51 | document.documentElement.style.getPropertyValue('--header-bottom-position-desktop') !== 52 | '' 53 | ) 54 | return 55 | const viewport = window.innerWidth >= 750 ? 'desktop' : 'mobile' 56 | document.documentElement.style.setProperty( 57 | '--header-bottom-position-desktop', 58 | `calc(${scaleValue(this.header.getBoundingClientRect().bottom, viewport)} * var(--ax))` 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/scripts/theme/modal-opener.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | import { type ModalDialog } from '@/scripts/theme/modal-dialog'; 3 | import { UcoastEl } from '@/scripts/core/UcoastEl'; 4 | 5 | export class ModalOpener extends UcoastEl { 6 | static htmlSelector = 'modal-opener'; 7 | constructor() { 8 | super(); 9 | 10 | const button = this.querySelector('button'); 11 | 12 | if (!button) return; 13 | button.addEventListener('click', (e) => { 14 | e.preventDefault() 15 | const modalSelector = this.getAttribute('data-modal') 16 | if (!modalSelector) return; 17 | const modal = q.os(modalSelector); 18 | console.log('modalSelector', modalSelector) 19 | console.log({modal}) 20 | if (modal) modal.show(button); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/scripts/theme/multi-product-slider.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | import { UcoastEl } from '@/scripts/core/UcoastEl' 3 | import { ProductSlider } from '@/scripts/theme/product-slider' 4 | 5 | export class MultiProductSlider extends UcoastEl { 6 | static htmlSelector = 'multi-product-slider' 7 | navButtons: NodeListOf 8 | sliderWrappers: NodeListOf 9 | sliders: NodeListOf 10 | constructor() { 11 | super() 12 | this.navButtons = q.rl('button[data-nav]', this) 13 | this.sliderWrappers = q.rl('[data-slider]', this) 14 | this.sliders = q.rl(ProductSlider.htmlSelector, this) 15 | this.addListeners() 16 | } 17 | 18 | addListeners() { 19 | this.navButtons.forEach((button) => { 20 | button.addEventListener('click', this.activateSlider.bind(this)) 21 | }) 22 | } 23 | 24 | activateSlider(event: Event) { 25 | const currentTarget = q.rct(event) 26 | const blockId = q.ra(currentTarget, 'data-nav') 27 | this.navButtons.forEach((navButton) => { 28 | const navBlockId = q.ra(navButton, 'data-nav') 29 | if (blockId === navBlockId) { 30 | navButton.classList.add('active') 31 | navButton.classList.remove('underline-on-hover') 32 | navButton.classList.add('underline-always') 33 | } else { 34 | navButton.classList.remove('active') 35 | navButton.classList.add('underline-on-hover') 36 | navButton.classList.remove('underline-always') 37 | } 38 | }) 39 | this.sliderWrappers.forEach((sliderWrapper) => { 40 | const sliderBlockId = q.ra(sliderWrapper, 'data-slider') 41 | if (blockId === sliderBlockId) { 42 | sliderWrapper.classList.add('active') 43 | void window.Ucoast.mediaManager.playAllInContainer( 44 | sliderWrapper 45 | ) 46 | } else { 47 | sliderWrapper.classList.remove('active') 48 | void window.Ucoast.mediaManager.pauseAllInContainer( 49 | sliderWrapper 50 | ) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/scripts/theme/product-recommendations.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q } from '@/scripts/core/TsDOM' 2 | import { UcoastEl } from '@/scripts/core/UcoastEl' 3 | 4 | export class ProductRecommendations extends UcoastEl { 5 | static htmlSelector = 'product-recommendations' 6 | constructor() { 7 | super() 8 | } 9 | 10 | override connectedCallback() { 11 | const handleIntersection = ( 12 | entries: IntersectionObserverEntry[], 13 | observer: IntersectionObserver 14 | ) => { 15 | if (!entries[0].isIntersecting) return 16 | observer.unobserve(this) 17 | 18 | const fetchUrl = q.ra(this, 'data-url') 19 | 20 | fetch(fetchUrl) 21 | .then((response) => response.text()) 22 | .then((text) => { 23 | const html = document.createElement('div') 24 | html.innerHTML = text 25 | const recommendations = q.os( 26 | 'product-recommendations', 27 | html 28 | ) 29 | 30 | if ( 31 | recommendations && 32 | recommendations.innerHTML.trim().length 33 | ) { 34 | this.innerHTML = recommendations.innerHTML 35 | } 36 | 37 | if ( 38 | !this.querySelector('slideshow-component') && 39 | this.classList.contains('complementary-products') 40 | ) { 41 | this.remove() 42 | } 43 | 44 | if (html.querySelector('.grid__item')) { 45 | this.classList.add('product-recommendations--loaded') 46 | } 47 | }) 48 | .catch((e) => { 49 | console.error(e) 50 | }) 51 | } 52 | 53 | new IntersectionObserver(handleIntersection.bind(this), { 54 | rootMargin: '0px 0px 400px 0px', 55 | }).observe(this) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/scripts/theme/quantity-input.ts: -------------------------------------------------------------------------------- 1 | import { PUB_SUB_EVENTS } from '@/scripts/core/global' 2 | import { subscribe, type SubscriberCallback } from '@/scripts/core/global' 3 | import { TsDOM as q } from '@/scripts/core/TsDOM' 4 | import { UcoastEl } from '@/scripts/core/UcoastEl'; 5 | 6 | export class QuantityInput extends UcoastEl { 7 | static htmlSelector = 'quantity-input' 8 | input: HTMLInputElement 9 | changeEvent: Event 10 | quantityUpdateUnsubscriber: SubscriberCallback | undefined = undefined 11 | constructor() { 12 | super() 13 | this.input = q.rs('input', this) 14 | this.changeEvent = new Event('change', { bubbles: true }) 15 | 16 | this.input.addEventListener('change', this.onInputChange.bind(this)) 17 | this.querySelectorAll('button').forEach((button) => 18 | button.addEventListener('click', this.onButtonClick.bind(this)) 19 | ) 20 | } 21 | 22 | override connectedCallback() { 23 | this.validateQtyRules() 24 | this.quantityUpdateUnsubscriber = subscribe( 25 | PUB_SUB_EVENTS.quantityUpdate, 26 | this.validateQtyRules.bind(this) 27 | ) 28 | } 29 | 30 | override disconnectedCallback() { 31 | if (this.quantityUpdateUnsubscriber) { 32 | this.quantityUpdateUnsubscriber(undefined) 33 | } 34 | } 35 | 36 | onInputChange(_event: Event) { 37 | this.validateQtyRules() 38 | } 39 | 40 | onButtonClick(event: Event) { 41 | event.preventDefault() 42 | const previousValue = this.input.value 43 | const target = q.rt(event) 44 | 45 | target.name === 'plus' ? this.input.stepUp() : this.input.stepDown() 46 | if (previousValue !== this.input.value) this.input.dispatchEvent(this.changeEvent) 47 | } 48 | 49 | validateQtyRules() { 50 | const value = parseInt(this.input.value) 51 | if (this.input.min) { 52 | const min = parseInt(this.input.min) 53 | const buttonMinus = q.rs(".quantity__button[name='minus']", this) 54 | buttonMinus.classList.toggle('disabled', value <= min) 55 | } 56 | if (this.input.max) { 57 | const max = parseInt(this.input.max) 58 | const buttonPlus = q.rs(".quantity__button[name='plus']", this) 59 | buttonPlus.classList.toggle('disabled', value >= max) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/scripts/theme/search-form.ts: -------------------------------------------------------------------------------- 1 | import { TsDOM as q, debounce } from '@/scripts/core/TsDOM' 2 | import { UcoastEl } from '@/scripts/core/UcoastEl' 3 | 4 | export class SearchForm extends UcoastEl { 5 | static htmlSelector = 'search-form' 6 | input: HTMLInputElement 7 | resetButton: HTMLButtonElement 8 | form: HTMLFormElement 9 | constructor() { 10 | super() 11 | this.input = q.rs('input[type="search"]', this) 12 | const form = this.input.form 13 | if (!form) throw new Error('input is not in a form element') 14 | this.form = form 15 | this.resetButton = q.rs('button[type="reset"]', this) 16 | 17 | this.form.addEventListener('reset', this.onFormReset.bind(this)) 18 | this.input.addEventListener( 19 | 'input', 20 | debounce((_event: Event) => { 21 | this.onChange() 22 | }, 300).bind(this) 23 | ) 24 | } 25 | 26 | toggleResetButton() { 27 | const resetIsHidden = this.resetButton.classList.contains('hidden') 28 | if (this.input.value.length > 0 && resetIsHidden) { 29 | this.resetButton.classList.remove('hidden') 30 | } else if (this.input.value.length === 0 && !resetIsHidden) { 31 | this.resetButton.classList.add('hidden') 32 | } 33 | } 34 | 35 | onChange() { 36 | this.toggleResetButton() 37 | } 38 | 39 | shouldResetForm() { 40 | return !document.querySelector('[aria-selected="true"] a') 41 | } 42 | 43 | onFormReset(event: Event) { 44 | // Prevent default so the form reset doesn't set the value gotten from the url on page load 45 | event.preventDefault() 46 | // Don't reset if the user has selected an element on the predictive search dropdown 47 | if (this.shouldResetForm()) { 48 | this.input.value = '' 49 | this.input.focus() 50 | this.toggleResetButton() 51 | } 52 | } 53 | } 54 | 55 | customElements.define('search-form', SearchForm) 56 | -------------------------------------------------------------------------------- /src/scripts/theme/variant-radios.ts: -------------------------------------------------------------------------------- 1 | import { VariantSelects } from '@/scripts/theme/variant-selects'; 2 | import { TsDOM as q } from '@/scripts/core/TsDOM' 3 | 4 | export class VariantRadios extends VariantSelects { 5 | static override htmlSelector = 'variant-radios'; 6 | static override selectors = { 7 | ...VariantSelects.selectors, 8 | } 9 | constructor() { 10 | super(); 11 | } 12 | 13 | override setInstanceSelectors() { 14 | this.instanceSelectors = VariantRadios.selectors 15 | } 16 | 17 | override setInputAvailability(listOfOptions:(HTMLInputElement|HTMLOptionElement)[], listOfAvailableOptions:(string|null)[]) { 18 | listOfOptions.forEach((input) => { 19 | if (listOfAvailableOptions.includes(input.getAttribute('value'))) { 20 | input.classList.remove('disabled'); 21 | } else { 22 | input.classList.add('disabled'); 23 | } 24 | }); 25 | } 26 | 27 | override updateOptions() { 28 | const fieldsetNodes = q.ol('fieldset', this); 29 | if (!fieldsetNodes) throw new Error('cannot do updateOptions in variant-radios, no fieldset nodes found'); 30 | const fieldsets = Array.from(fieldsetNodes); 31 | this.options = fieldsets.map((fieldset) => { 32 | const inputNodes = q.ol('input', fieldset); 33 | if (!inputNodes) throw new Error('cannot do updateOptions in variant-radios, no input nodes found'); 34 | const inputs = Array.from(inputNodes); 35 | const checkedInput = inputs.find((radio) => radio.checked) 36 | if (!checkedInput) throw new Error('cannot do updateOptions in variant-radios, no checked input found'); 37 | return checkedInput.value; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/scripts/types/events.d.ts: -------------------------------------------------------------------------------- 1 | import { ProductVariant } from '@/scripts/types/api' 2 | 3 | // some of these might actually be pubsub events - which aren't *real* javascript events 4 | 5 | interface VariantChange extends Event { 6 | data: { 7 | sectionId: string 8 | html: Document 9 | variant: ProductVariant 10 | } 11 | } 12 | 13 | interface CartUpdate extends Event { 14 | source: string 15 | productVariantId: number 16 | } 17 | 18 | interface QuantityUpdate extends Event { 19 | undefined 20 | } 21 | 22 | interface CartError extends Event { 23 | source: string 24 | productVariantId: number 25 | message: string 26 | errors: string | Record 27 | } 28 | 29 | interface SlideChangedEvent extends Event { 30 | detail: { 31 | currentPage: number 32 | currentElement: HTMLElement 33 | } 34 | } 35 | 36 | interface BlockSelectEvent extends Event {} 37 | interface BlockDeselectEvent extends Event {} 38 | 39 | export { 40 | VariantChange, 41 | CartUpdate, 42 | QuantityUpdate, 43 | CartError, 44 | SlideChangedEvent, 45 | BlockSelectEvent, 46 | BlockDeselectEvent, 47 | } 48 | -------------------------------------------------------------------------------- /src/scripts/types/responses.d.ts: -------------------------------------------------------------------------------- 1 | import {Image, Product} from "@/scripts/types/api"; 2 | 3 | export interface JsonApiResponse { 4 | intent: string 5 | products: JsonProduct[] | Product[] 6 | } 7 | 8 | export interface JsonProduct { 9 | id: number 10 | title: string 11 | handle: string 12 | description: string | null 13 | published_at: string 14 | created_at: string 15 | vendor: string 16 | type: string 17 | tags: string[] 18 | price: number 19 | price_min: number 20 | price_max: number 21 | available: boolean 22 | price_varies: boolean 23 | compare_at_price: number | null 24 | compare_at_price_min: number 25 | compare_at_price_max: number 26 | compare_at_price_varies: boolean 27 | variants: JsonProductVariant[] 28 | images: string[] | null 29 | featured_image: string | null 30 | options: JsonOption[] 31 | url: string 32 | } 33 | 34 | interface JsonProductVariant { 35 | id: number 36 | title: string 37 | option1: string 38 | option2: string | null 39 | option3: string | null 40 | sku: string | null 41 | requires_shipping: boolean 42 | taxable: boolean 43 | featured_image: string | null 44 | available: boolean 45 | name: string 46 | public_title: string 47 | options: string[] 48 | price: number 49 | weight: number 50 | compare_at_price: number | null 51 | inventory_management: string 52 | barcode: string | null 53 | } 54 | 55 | interface JsonOption { 56 | name: string 57 | position: number 58 | values: string[] 59 | } 60 | -------------------------------------------------------------------------------- /src/scripts/types/theme.d.ts: -------------------------------------------------------------------------------- 1 | export type ShopifySectionRenderingSchema = { id: string; section?: string; selector: string } 2 | export type DebounceCallback = (...args: unknown[]) => unknown 3 | export type FocusableHTMLElement = 4 | | HTMLAnchorElement 5 | | HTMLButtonElement 6 | | HTMLInputElement 7 | | HTMLSelectElement 8 | | HTMLTextAreaElement 9 | | HTMLIFrameElement 10 | | HTMLObjectElement 11 | | HTMLAreaElement 12 | export type EventWithRelatedTarget = MouseEvent | FocusEvent | DragEvent | PointerEvent 13 | 14 | export interface VariantChangeEvent extends Event { 15 | data: { 16 | sectionId: string 17 | html: Document 18 | currentVariant: JSON 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-cart-gwp.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | 4 | $block: 'cart-gwp'; 5 | $desktopGrid: var(--desktop-grid); 6 | 7 | .#{$block} { 8 | display: block; 9 | width: 100%; 10 | margin-bottom: $ax15; 11 | padding-bottom: $ax15; 12 | border-bottom: var(--ax1) solid rgba(var(--c-foreground),.2); 13 | 14 | &__title { 15 | padding: $ax8; 16 | margin-bottom: $ax7; 17 | } 18 | 19 | &__track { 20 | display: block; 21 | overflow-x: auto; 22 | overflow-y: hidden; 23 | } 24 | 25 | &__products { 26 | width: max-content; 27 | display: flex; 28 | gap: $ax15; 29 | margin-left: $ax15; 30 | margin-right: $ax15; 31 | min-width: 100%; 32 | } 33 | 34 | &__item { 35 | width: ax(120); 36 | max-width: 33vw; 37 | 38 | @include media-query($m-up) { 39 | width: calc(33.33% - #{$ax20}); 40 | } 41 | } 42 | 43 | .card__information { 44 | display: none; 45 | } 46 | 47 | // overrides 48 | 49 | .card--standard .card__media { 50 | background: #efeceb; 51 | } 52 | 53 | .card--standard { 54 | .quick-add { 55 | 56 | margin-bottom: 0; 57 | z-index: 3; 58 | 59 | 60 | .button { 61 | padding: $ax10; 62 | min-height: unset; 63 | 64 | 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-dynamic-progress-bar.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | $block: 'dynamic-progress-bar'; 4 | .#{$block} { 5 | padding: 0 $ax15; 6 | height: $ax24; 7 | &__indicator { 8 | background-color: #90D3EE; 9 | // border-radius: $ax10; 10 | overflow: hidden; 11 | width: var(--progress-percent); 12 | transition: width 0.3s $bezier; 13 | left: 0; 14 | right: 0; 15 | max-width: 100%; 16 | } 17 | &__background { 18 | background-color: rgb(#{$schemaColorBackground}); 19 | border: $ax1 solid rgb(#{$schemaColorForeground}); 20 | border-left: 0; 21 | border-right: 0; 22 | // border-radius: $ax10; 23 | left: 0; 24 | right: 0; 25 | width: 100%; 26 | } 27 | &__status { 28 | &__text { 29 | margin: 0; 30 | padding: $ax4 $ax10 $ax5; 31 | font-weight: 500; 32 | letter-spacing: -0.03em; 33 | @include clampSize(12, 13); 34 | } 35 | &__container { 36 | //border-radius: $ax10; 37 | background-color: rgb(#{$schemaColorBackground}); 38 | overflow: hidden; 39 | width: 100%; 40 | left: 0; 41 | } 42 | } 43 | &[data-gwp-offer-type="none"] { 44 | display: none !important; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-list-payment.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .list-payment { 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: center; 7 | margin: ax(-5) 0; 8 | padding-top: #{$ax10}; 9 | padding-left: 0; 10 | } 11 | 12 | .list-payment__item { 13 | align-items: center; 14 | display: flex; 15 | padding: #{$ax5}; 16 | } 17 | @include media-query($s-up) { 18 | .list-payment { 19 | justify-content: flex-end; 20 | margin: ax(-5); 21 | padding-top: 0; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-list-social.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .list-social { 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: flex-end; 7 | } 8 | 9 | @include media-query($s-down) { 10 | .list-social { 11 | justify-content: center; 12 | } 13 | } 14 | 15 | .list-social__item .icon { 16 | height: #{$ax18}; 17 | width: #{$ax18}; 18 | } 19 | 20 | .list-social__link { 21 | align-items: center; 22 | display: flex; 23 | padding: #{$ax13}; 24 | color: rgb(#{$schemaColorForeground}); 25 | } 26 | 27 | .list-social__link:hover .icon { 28 | transform: scale(1.07); 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-loading-overlay.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | [data-uc-loading-overlay] { 4 | position: absolute; 5 | z-index: 1; 6 | width: #{$ax18}; 7 | } 8 | 9 | @include media-query($s-down) { 10 | [data-uc-loading-overlay] { 11 | top: 0; 12 | right: 0; 13 | } 14 | } 15 | 16 | [data-uc-loading-overlay-spinner] { 17 | width: #{$ax18}; 18 | display: inline-block; 19 | } 20 | 21 | .spinner { 22 | animation: rotator 1.4s linear infinite; 23 | } 24 | 25 | @keyframes rotator { 26 | 0% { 27 | transform: rotate(0deg); 28 | } 29 | 100% { 30 | transform: rotate(270deg); 31 | } 32 | } 33 | 34 | .path { 35 | stroke-dasharray: 280; 36 | stroke-dashoffset: 0; 37 | transform-origin: center; 38 | stroke: rgb(#{$schemaColorForeground}); 39 | animation: dash 1.4s ease-in-out infinite; 40 | } 41 | 42 | @media screen and (forced-colors: active) { 43 | .path { 44 | stroke: CanvasText; 45 | } 46 | } 47 | 48 | @keyframes dash { 49 | 0% { 50 | stroke-dashoffset: 280; 51 | } 52 | 50% { 53 | stroke-dashoffset: 75; 54 | transform: rotate(135deg); 55 | } 56 | 100% { 57 | stroke-dashoffset: 280; 58 | transform: rotate(450deg); 59 | } 60 | } 61 | 62 | [data-uc-loading-overlay]:not(.hidden) + .cart-item__price-wrapper, 63 | [data-uc-loading-overlay]:not(.hidden) ~ cart-remove-button { 64 | opacity: 50%; 65 | } 66 | 67 | [data-uc-loading-overlay]:not(.hidden) ~ cart-remove-button { 68 | pointer-events: none; 69 | cursor: default; 70 | } 71 | @include media-query($s-up) { 72 | [data-uc-loading-overlay] { 73 | left: 0; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-modal-video.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .modal-video { 4 | background: rgba(#{$schemaColorForeground}, 0.2); 5 | box-sizing: border-box; 6 | height: 100%; 7 | left: 0; 8 | margin: 0 auto; 9 | opacity: 0; 10 | overflow: auto; 11 | position: fixed; 12 | top: 0; 13 | visibility: hidden; 14 | width: 100%; 15 | z-index: -1; 16 | } 17 | 18 | .modal-video[open] { 19 | opacity: 1; 20 | visibility: visible; 21 | z-index: 101; 22 | } 23 | 24 | .modal-video__content { 25 | background-color: rgb(#{$schemaColorBackground}); 26 | height: 100%; 27 | margin: 0; 28 | overflow: auto; 29 | padding: 0; 30 | position: absolute; 31 | width: 100%; 32 | } 33 | 34 | .modal-video__toggle { 35 | align-items: center; 36 | background-color: rgb(#{$schemaColorBackground}); 37 | border-radius: 50%; 38 | border: #{$ax1} solid rgba(#{$schemaColorForeground}, 0.1); 39 | color: rgba(#{$schemaColorForeground}, 0.55); 40 | cursor: pointer; 41 | display: flex; 42 | justify-content: center; 43 | margin: 0 0 0 auto; 44 | padding: #{$ax12}; 45 | position: fixed; 46 | right: #{$ax5}; 47 | top: #{$ax20}; 48 | width: #{$ax40}; 49 | z-index: 2; 50 | } 51 | 52 | .modal-video__toggle .icon { 53 | height: auto; 54 | margin: 0; 55 | width: #{$ax22}; 56 | } 57 | 58 | .modal-video__content-info { 59 | height: calc(100% - #{ax(60)}); 60 | margin: 0 auto; 61 | padding-top: #{ax(80)}; 62 | width: calc(100% - #{$ax10}); 63 | } 64 | 65 | .modal-video__video, 66 | .modal-video__video iframe { 67 | height: 100%; 68 | width: 100%; 69 | } 70 | 71 | .modal-video__video iframe { 72 | position: static; 73 | border: 0; 74 | } 75 | @include media-query($s-up) { 76 | .modal-video__toggle { 77 | right: #{$ax48}; 78 | top: #{$ax35}; 79 | } 80 | .modal-video__content-info { 81 | height: calc(100% - #{ax(75)}); 82 | padding-top: #{ax(95)}; 83 | width: calc(100% - #{ax(96)}); 84 | } 85 | } 86 | @include media-query($m-up) { 87 | .modal-video__toggle { 88 | right: $ax43; 89 | top: #{$ax30}; 90 | } 91 | .modal-video__content-info { 92 | height: calc(100% - #{ax(70)}); 93 | padding-top: #{ax(90)}; 94 | width: calc(100% - #{ax(86)}); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-newsletter.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .newsletter-form { 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | width: 100%; 9 | position: relative; 10 | } 11 | 12 | .newsletter-form__field-wrapper { 13 | width: 100%; 14 | } 15 | 16 | .newsletter-form__field-wrapper .field__input { 17 | padding-right: #{$ax50}; 18 | } 19 | 20 | .newsletter-form__field-wrapper .field { 21 | z-index: 0; 22 | } 23 | 24 | .newsletter-form__message { 25 | justify-content: center; 26 | margin-bottom: 0; 27 | } 28 | 29 | .newsletter-form__message--success { 30 | margin-top: #{$ax20}; 31 | } 32 | 33 | .newsletter-form__button { 34 | width: #{$ax44}; 35 | margin: 0; 36 | right: #{$inputsBorderWidth}; 37 | top: 0; 38 | height: 100%; 39 | z-index: 2; 40 | } 41 | 42 | .newsletter-form__button:focus-visible { 43 | box-shadow: 0 0 0 #{$ax3} rgb(#{$schemaColorBackground}), 0 0 0 #{$ax4} rgba(#{$schemaColorForeground}); 44 | background-color: rgb(#{$schemaColorBackground}); 45 | } 46 | 47 | .newsletter-form__button:focus { 48 | box-shadow: 0 0 0 #{$ax3} rgb(#{$schemaColorBackground}), 0 0 0 #{$ax4} rgba(#{$schemaColorForeground}); 49 | background-color: rgb(#{$schemaColorBackground}); 50 | } 51 | 52 | .newsletter-form__button:not(:focus-visible):not(.focused) { 53 | box-shadow: inherit; 54 | background-color: inherit; 55 | } 56 | 57 | .newsletter-form__button .icon { 58 | width: #{$ax15}; 59 | } 60 | @include media-query($s-up) { 61 | .newsletter-form { 62 | align-items: flex-start; 63 | margin: 0 auto; 64 | max-width: ax(360); 65 | } 66 | .newsletter-form__message { 67 | justify-content: flex-start; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/styles/base-defer/component-welcome-popup.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | $block: 'welcome-popup'; 4 | .#{$block} { 5 | opacity: 0; 6 | transition: opacity 0.5s $bezier 0.02s; 7 | visibility: hidden; 8 | z-index: -1; 9 | box-shadow: none !important; 10 | &[open] { 11 | transition: opacity 0.5s $bezier 0.02s; 12 | opacity: 1; 13 | visibility: visible; 14 | z-index: 101; 15 | } 16 | &__main { 17 | left: $ax20; 18 | bottom: $ax20; 19 | padding: $ax5; 20 | width: calc(100% - #{$ax40}); 21 | max-width: ax(420); 22 | border: $ax1 solid rgb(#{$schemaColorForeground}); 23 | box-shadow: none !important; 24 | 25 | @include media-query($s-up) { 26 | left: $ax30; 27 | bottom: $ax30; 28 | padding: $ax5; 29 | width: ax(310); 30 | } 31 | } 32 | &__media { 33 | border: $ax1 solid rgb(#{$schemaColorForeground}); 34 | aspect-ratio: 1.6; 35 | } 36 | &__content { 37 | padding: $ax18 $ax15; 38 | @include media-query($s-down) { 39 | margin-top: $ax25; 40 | } 41 | } 42 | &__title { 43 | } 44 | &__text { 45 | margin-top: $ax13; 46 | } 47 | .form { 48 | display: flex; 49 | flex-wrap: wrap; 50 | margin-top: $ax22; 51 | column-gap: calc(#{$xSpacing} / 2); 52 | row-gap: calc(#{$ySpacing} / 2); 53 | @include media-query($s-up) { 54 | margin-top: $ax12; 55 | } 56 | } 57 | &__close-button { 58 | padding: $ax15; 59 | svg { 60 | width: var(--ax9); 61 | } 62 | @include media-query($s-down) { 63 | padding: $ax20; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/styles/base-defer/newsletter-section.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .newsletter__wrapper { 4 | padding-right: calc(#{$ax40} / #{$fontBodyScale}); 5 | padding-left: calc(#{$ax40} / #{$fontBodyScale}); 6 | } 7 | 8 | .newsletter__wrapper > * { 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | } 12 | 13 | .newsletter__wrapper > * + * { 14 | margin-top: #{$ax20}; 15 | } 16 | 17 | .newsletter__wrapper > * + .newsletter-form { 18 | margin-top: #{$ax30}; 19 | } 20 | 21 | .newsletter__subheading { 22 | max-width: ax(700); 23 | } 24 | 25 | .newsletter__wrapper .newsletter-form__field-wrapper { 26 | max-width: ax(360); 27 | } 28 | 29 | .newsletter-form__field-wrapper .newsletter-form__message { 30 | margin-top: #{$ax15}; 31 | } 32 | 33 | .newsletter__button { 34 | margin-top: #{$ax30}; 35 | width: fit-content; 36 | } 37 | @include media-query($s-up) { 38 | .newsletter__wrapper { 39 | padding-right: #{ax(90)}; 40 | padding-left: #{ax(90)}; 41 | } 42 | .newsletter__button { 43 | flex-shrink: 0; 44 | margin: 0 0 0 #{$ax10}; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/styles/base-defer/section-collection-list.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | 4 | .collection-list__item:only-child { 5 | max-width: 100%; 6 | width: 100%; 7 | } 8 | 9 | @include media-query($s-down) { 10 | .collection-list:not(.slider) { 11 | padding-left: 0; 12 | padding-right: 0; 13 | } 14 | 15 | .section-collection-list .page-width { 16 | padding-left: 0; 17 | padding-right: 0; 18 | } 19 | 20 | .section-collection-list .collection-list:not(.slider) { 21 | padding-left: #{$ax15}; 22 | padding-right: #{$ax15}; 23 | } 24 | .slider.collection-list--1-items { 25 | padding-bottom: 0; 26 | } 27 | } 28 | 29 | @include media-query($m-down) { 30 | .collection-list.slider .collection-list__item { 31 | max-width: 100%; 32 | } 33 | } 34 | 35 | .collection-list-view-all { 36 | margin-top: #{$ax20}; 37 | } 38 | @include media-query($m-only) { 39 | .slider.collection-list--1-items, 40 | .slider.collection-list--2-items, 41 | .slider.collection-list--3-items, 42 | .slider.collection-list--4-items { 43 | padding-bottom: 0; 44 | } 45 | } 46 | 47 | @include media-query($s-up) { 48 | .collection-list__item a:hover { 49 | box-shadow: none; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/styles/base-defer/section-featured-blog.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .blog-placeholder-svg { 4 | height: 100%; 5 | } 6 | 7 | .blog__posts.articles-wrapper { 8 | margin-bottom: #{$ax10}; 9 | } 10 | 11 | .blog__posts.articles-wrapper .article { 12 | scroll-snap-align: start; 13 | } 14 | 15 | @include media-query($s-down) { 16 | .blog__post.article { 17 | width: calc(100% - #{$ax30} - #{$xSpacingMobile}); 18 | } 19 | } 20 | 21 | .background-secondary .blog-placeholder__content { 22 | background-color: rgb(#{$schemaColorBackground}); 23 | } 24 | 25 | .blog__button { 26 | margin-top: #{$ax30}; 27 | } 28 | @include media-query($s-up) { 29 | .blog__button { 30 | margin-top: #{$ax50}; 31 | } 32 | } 33 | /* check for flexbox gap in older Safari versions */ 34 | @supports not (inset: 10px) { 35 | @include media-query($s-up) { 36 | .blog__posts .article + .article { 37 | margin-left: #{$xSpacingDesktop}; 38 | } 39 | } 40 | } 41 | @include media-query($m-up) { 42 | .grid--1-col-desktop .article-card .card__content { 43 | text-align: center; 44 | } 45 | } 46 | @include media-query($m-up) { 47 | .blog__posts.articles-wrapper { 48 | margin-bottom: 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/base-defer/section-featured-product.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .featured-product .product__media-list { 4 | width: 100%; 5 | margin: 0; 6 | padding-bottom: 0; 7 | } 8 | 9 | .featured-product .product-media-container { 10 | margin-bottom: #{$mediaShadowVerticalOffset}; 11 | max-width: 100%; 12 | } 13 | 14 | .featured-product .product__media-item { 15 | padding-left: 0; 16 | width: 100%; 17 | } 18 | 19 | .featured-product .product__media-item:not(:first-child) { 20 | display: none; 21 | } 22 | 23 | .featured-product .placeholder-svg { 24 | display: block; 25 | height: auto; 26 | width: 100%; 27 | } 28 | 29 | .background-secondary .featured-product { 30 | padding: #{$ax25}; 31 | } 32 | 33 | .featured-product .share-button:nth-last-child(2) { 34 | display: inline-flex; 35 | } 36 | 37 | .share-button + .product__view-details { 38 | display: inline-flex; 39 | float: right; 40 | align-items: center; 41 | min-height: #{$ax44}; 42 | } 43 | 44 | .share-button + .product__view-details::after { 45 | content: ''; 46 | clear: both; 47 | display: table; 48 | } 49 | @include media-query($s-up) { 50 | .featured-product .product__media-item { 51 | padding-bottom: 0; 52 | } 53 | 54 | .background-secondary .featured-product { 55 | padding: #{$ax50}; 56 | } 57 | } 58 | @include media-query($m-up) { 59 | .background-secondary .featured-product:not(.product--no-media) > .product__info-wrapper { 60 | padding: 0 0 0 #{$ax50}; 61 | } 62 | 63 | .background-secondary 64 | .featured-product:not(.product--no-media).product--right 65 | > .product__info-wrapper { 66 | padding: 0 #{$ax50} 0 0; 67 | } 68 | 69 | .featured-product:not(.product--no-media) > .product__info-wrapper { 70 | padding: 0 #{ax(70)}; 71 | } 72 | 73 | .background-secondary .featured-product { 74 | padding: #{ax(60)} #{ax(70)}; 75 | position: relative; 76 | z-index: 1; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/styles/base-defer/section-rich-text.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | 4 | .rich-text__wrapper { 5 | width: calc(100% - #{$ax40} / #{$fontBodyScale}); 6 | } 7 | 8 | .rich-text:not(.rich-text--full-width) .rich-text__wrapper { 9 | margin: auto; 10 | width: calc(100% - #{ax(80)} / #{$fontBodyScale}); 11 | } 12 | 13 | .rich-text__blocks * { 14 | overflow-wrap: break-word; 15 | } 16 | 17 | .rich-text__blocks > * { 18 | margin-top: 0; 19 | margin-bottom: 0; 20 | } 21 | 22 | .rich-text__blocks > * + * { 23 | margin-top: #{$ax20}; 24 | } 25 | 26 | .rich-text__blocks > * + a { 27 | margin-top: #{$ax30}; 28 | } 29 | 30 | .rich-text__buttons { 31 | justify-content: center; 32 | gap: #{$ax10}; 33 | max-width: ax(450); 34 | word-break: break-word; 35 | } 36 | 37 | .rich-text__buttons--multiple > * { 38 | flex-grow: 1; 39 | min-width: #{ax(220)}; 40 | } 41 | 42 | .rich-text__buttons + .rich-text__buttons { 43 | margin-top: #{$ax10}; 44 | } 45 | 46 | .rich-text__blocks.left .rich-text__buttons { 47 | justify-content: flex-start; 48 | } 49 | 50 | .rich-text__blocks.right .rich-text__buttons { 51 | justify-content: flex-end; 52 | } 53 | @include media-query($s-up) { 54 | .rich-text__wrapper { 55 | width: 100%; 56 | } 57 | 58 | .rich-text__wrapper--left { 59 | justify-content: flex-start; 60 | } 61 | 62 | .rich-text__wrapper--right { 63 | justify-content: flex-end; 64 | } 65 | 66 | .rich-text__blocks { 67 | max-width: ax(500); 68 | } 69 | } 70 | 71 | @include media-query($m-up) { 72 | .rich-text__blocks { 73 | max-width: ax(780); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/styles/base-defer/video-section.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .video-section__media { 4 | --ratio-percent: 56.25%; 5 | padding-bottom: calc(var(--ratio-percent) - #{$mediaBorderWidth}); 6 | } 7 | 8 | /* Needed for gradient continuity with or without animation so that transparent PNG images come up as we would expect */ 9 | .scroll-trigger:where(.gradient.video-section__media) { 10 | background: transparent; 11 | } 12 | 13 | .video-section__media.global-media-settings--full-width { 14 | padding-bottom: var(--ratio-percent); 15 | } 16 | 17 | .video-section__media.deferred-media { 18 | box-shadow: #{$mediaShadowHorizontalOffset} #{$mediaShadowVerticalOffset} #{$mediaShadowBlurRadius} 19 | rgba(#{$schemaColorShadow}, #{$mediaShadowOpacity}); 20 | } 21 | 22 | .video-section__media.deferred-media:after { 23 | content: none; 24 | } 25 | 26 | .video-section__poster.deferred-media__poster:focus { 27 | outline-offset: #{$ax3}; 28 | } 29 | 30 | .video-section__media iframe { 31 | background-color: rgba(#{$schemaColorForeground}, 0.03); 32 | border: 0; 33 | } 34 | 35 | .video-section__poster, 36 | .video-section__media iframe, 37 | .video-section__media video { 38 | position: absolute; 39 | width: 100%; 40 | height: 100%; 41 | } 42 | 43 | .video-section__media video { 44 | background: #000000; 45 | } 46 | 47 | .video-section__media.media-fit-cover video { 48 | object-fit: cover; 49 | } 50 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-accordion.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .accordion summary { 4 | display: flex; 5 | position: relative; 6 | line-height: 1; 7 | padding: #{$ax15} 0; 8 | } 9 | 10 | .accordion .summary__title { 11 | display: flex; 12 | flex: 1; 13 | } 14 | 15 | .accordion .summary__title + .icon-caret { 16 | height: calc(#{$fontHeadingScale} * #{$ax6}); 17 | } 18 | 19 | .accordion + .accordion { 20 | margin-top: 0; 21 | border-top: none; 22 | } 23 | 24 | .accordion { 25 | margin-top: #{$ax25}; 26 | margin-bottom: 0; 27 | border-top: #{$ax1} solid rgba(#{$schemaColorForeground}, 0.08); 28 | border-bottom: #{$ax1} solid rgba(#{$schemaColorForeground}, 0.08); 29 | } 30 | 31 | .accordion__title { 32 | display: inline-block; 33 | max-width: calc(100% - #{ax(60)}); 34 | min-height: #{$ax16}; 35 | margin: 0; 36 | word-break: break-word; 37 | } 38 | 39 | .accordion .icon-accordion { 40 | align-self: center; 41 | fill: rgb(#{$schemaColorForeground}); 42 | height: calc(#{$fontHeadingScale} * #{$ax20}); 43 | margin-right: calc(#{$fontHeadingScale} * #{$ax10}); 44 | width: calc(#{$fontHeadingScale} * #{$ax20}); 45 | } 46 | 47 | .accordion details[open] > summary .icon-caret { 48 | transform: rotate(180deg); 49 | } 50 | 51 | .accordion__content { 52 | margin-bottom: #{$ax15}; 53 | word-break: break-word; 54 | overflow-x: auto; 55 | padding: 0 #{$ax6}; 56 | } 57 | 58 | .accordion__content img { 59 | max-width: 100%; 60 | } 61 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-banner-content.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | $block: 'banner-content'; 4 | .#{$block} { 5 | &__eyebrow { 6 | & + * { 7 | margin-top: $ax14; 8 | } 9 | } 10 | &__buttons { 11 | display: flex; 12 | flex-wrap: wrap; 13 | gap: $ax15; 14 | @include media-query($m-up) { 15 | gap: $ax30; 16 | } 17 | } 18 | &__title { 19 | & + .#{$block}__button, 20 | & + .#{$block}__buttons { 21 | margin-top: $ax28; 22 | } 23 | & + .#{$block}__text { 24 | margin-top: $ax14; 25 | } 26 | & + .#{$block}__title { 27 | margin-top: $ax4; 28 | } 29 | } 30 | &__text { 31 | & + .#{$block}__button, 32 | & + .#{$block}__buttons { 33 | margin-top: $ax30; 34 | } 35 | } 36 | padding: $ax15; 37 | @include media-query($m-up) { 38 | padding: $ax40; 39 | &__title { 40 | & + .#{$block}__button, 41 | & + .#{$block}__buttons { 42 | margin-top: $ax38; 43 | } 44 | & + .#{$block}__text { 45 | margin-top: $ax23; 46 | } 47 | } 48 | &__text { 49 | & + .#{$block}__button, 50 | & + .#{$block}__buttons { 51 | margin-top: $ax30; 52 | } 53 | } 54 | &__button { 55 | min-width: ax(150); 56 | max-width: 100%; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-content-settings.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | $block: 'content-settings'; 4 | .#{$block} { 5 | text-align: $componentTextAlign; 6 | align-items: $componentFlexPosition; 7 | max-width: calc(#{$componentWrapWidth} * $componentMobileWrapPercent * 0.01); 8 | @include media-query($m-up) { 9 | max-width: $componentWrapWidth; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-discounts.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .discounts { 4 | font-size: #{$ax12}; 5 | } 6 | 7 | .discounts__discount { 8 | display: flex; 9 | align-items: center; 10 | line-height: calc(1 + 0.5 / #{$fontBodyScale}); 11 | } 12 | 13 | .discounts__discount svg { 14 | color: rgba(#{$schemaColorButton}, var(--alpha-button-background)); 15 | } 16 | 17 | .discounts__discount--position { 18 | justify-content: center; 19 | } 20 | 21 | 22 | 23 | .discounts__discount > .icon { 24 | color: rgb(#{$schemaColorForeground}); 25 | width: #{$ax12}; 26 | height: #{$ax12}; 27 | margin-right: #{$ax7}; 28 | } 29 | @include media-query($s-up) { 30 | .discounts__discount--position { 31 | justify-content: flex-end; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-list-menu.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .list-menu--right { 4 | right: 0; 5 | } 6 | 7 | .list-menu--disclosure { 8 | position: absolute; 9 | min-width: 100%; 10 | width: #{ax(200)}; 11 | border: #{$ax1} solid rgba(#{$schemaColorForeground}, 0.2); 12 | } 13 | 14 | .list-menu--disclosure:focus { 15 | outline: none; 16 | } 17 | 18 | .list-menu--disclosure.localization-selector { 19 | max-height: #{ax(180)}; 20 | overflow: auto; 21 | width: #{ax(100)}; 22 | padding: #{$ax5}; 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-price.scss: -------------------------------------------------------------------------------- 1 | 2 | @import '../global/variables'; 3 | @import '../global/functions'; 4 | .price { 5 | font-size: #{$ax16}; 6 | letter-spacing: .0625em; 7 | line-height: calc(1 + 0.5 / #{$fontBodyScale}); 8 | color: rgb(#{$schemaColorForeground}); 9 | } 10 | 11 | .price > * { 12 | display: inline-block; 13 | vertical-align: top; 14 | } 15 | 16 | .price.price--unavailable { 17 | visibility: hidden; 18 | } 19 | 20 | .price--end { 21 | text-align: right; 22 | } 23 | 24 | .price .price-item { 25 | display: inline-block; 26 | margin: 0 #{$ax10} 0 0; 27 | } 28 | 29 | .price__regular .price-item--regular { 30 | margin-right: 0; 31 | } 32 | 33 | .price:not(.price--show-badge) .price-item--last:last-of-type { 34 | margin: 0; 35 | } 36 | 37 | .price--large { 38 | font-size: #{$ax16}; 39 | line-height: calc(1 + 0.5 / #{$fontBodyScale}); 40 | letter-spacing: 0.08125em; 41 | } 42 | 43 | .price--sold-out .price__availability, 44 | .price__regular { 45 | display: block; 46 | } 47 | 48 | .price__sale, 49 | .price__availability, 50 | .price .price__badge-sale, 51 | .price .price__badge-sold-out, 52 | .price--on-sale .price__regular, 53 | .price--on-sale .price__availability { 54 | display: none; 55 | } 56 | 57 | .price--sold-out .price__badge-sold-out, 58 | .price--on-sale .price__badge-sale { 59 | display: inline-block; 60 | } 61 | 62 | .price--on-sale .price__sale { 63 | display: initial; 64 | flex-direction: row; 65 | flex-wrap: wrap; 66 | } 67 | 68 | .price--center { 69 | display: initial; 70 | justify-content: center; 71 | } 72 | 73 | .price--on-sale .price-item--regular { 74 | text-decoration: line-through; 75 | color: rgba(#{$schemaColorForeground}, 0.75); 76 | font-size: #{$ax13}; 77 | } 78 | 79 | .unit-price { 80 | display: block; 81 | font-size: #{$ax11}; 82 | letter-spacing: .036em; 83 | line-height: calc(1 + 0.2 / #{$fontBodyScale}); 84 | margin-top: #{$ax2}; 85 | text-transform: uppercase; 86 | color: rgba(#{$schemaColorForeground}, 0.7); 87 | } 88 | 89 | @include media-query($s-up) { 90 | .price { 91 | margin-bottom: 0; 92 | } 93 | .price--large { 94 | font-size: #{$ax18}; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-product-slider.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | $block: 'product-slider'; 4 | .#{$block} { 5 | --slide-spacing: #{$ax12}; 6 | @include media-query($m-up) { 7 | --slide-spacing: #{$ax25}; 8 | } 9 | .embla { 10 | } 11 | .embla__viewport { 12 | overflow: hidden; 13 | padding-bottom: $ax1; 14 | } 15 | .embla__container { 16 | backface-visibility: hidden; 17 | display: flex; 18 | touch-action: pan-y; 19 | margin-left: calc(var(--slide-spacing) * -1); 20 | @include media-query($s-down) { 21 | margin-left: 0; 22 | } 23 | } 24 | .embla__slide, 25 | .grid__item { 26 | flex-grow: 1; 27 | width: ax(270); 28 | max-width: ax(270); 29 | min-width: 0; 30 | padding-left: var(--slide-spacing); 31 | position: relative; 32 | &:first-of-type { 33 | margin-left: var(--slide-spacing); 34 | } 35 | @include media-query($s-down) { 36 | &:first-of-type { 37 | margin-left: $ax10; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-rating.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .rating { 4 | display: inline-block; 5 | margin: 0; 6 | } 7 | 8 | .product .rating-star { 9 | --letter-spacing: 0.8; 10 | --font-size: 1.7; 11 | } 12 | 13 | .card-wrapper .rating-star { 14 | --letter-spacing: 0.7; 15 | --font-size: 1.4; 16 | } 17 | 18 | .rating-star { 19 | --color-rating-star: rgb(#{$schemaColorForeground}); 20 | --percent: calc( 21 | ( 22 | var(--rating) / var(--rating-max) + var(--rating-decimal) * var(--font-size) / 23 | (var(--rating-max) * (var(--letter-spacing) + var(--font-size))) 24 | ) * 100% 25 | ); 26 | letter-spacing: calc(var(--letter-spacing) * #{$ax10}); 27 | font-size: calc(var(--font-size) * #{$ax10}); 28 | line-height: 1; 29 | display: inline-block; 30 | font-family: Times; 31 | margin: 0; 32 | } 33 | 34 | .rating-star::before { 35 | content: '★★★★★'; 36 | background: linear-gradient( 37 | 90deg, 38 | var(--color-rating-star) var(--percent), 39 | rgba(#{$schemaColorForeground}, 0.15) var(--percent) 40 | ); 41 | -webkit-background-clip: text; 42 | -webkit-text-fill-color: transparent; 43 | } 44 | 45 | .rating-text { 46 | display: none; 47 | } 48 | 49 | .rating-count { 50 | display: inline-block; 51 | margin: 0; 52 | } 53 | 54 | @media (forced-colors: active) { 55 | .rating { 56 | display: none; 57 | } 58 | 59 | .rating-text { 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-search.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .search__input.field__input { 4 | padding-right: ax(98); 5 | } 6 | 7 | .search__button { 8 | right: #{$inputsBorderWidth}; 9 | top: #{$inputsBorderWidth}; 10 | } 11 | 12 | .reset__button { 13 | right: calc(#{$inputsBorderWidth} + #{$ax44}); 14 | top: #{$inputsBorderWidth}; 15 | } 16 | 17 | .reset__button:not(:focus-visible)::after { 18 | border-right: #{$ax1} solid rgba(#{$schemaColorForeground}, 0.08); 19 | display: block; 20 | height: calc(100% - #{$ax16}); 21 | content: ''; 22 | position: absolute; 23 | right: 0; 24 | } 25 | 26 | .reset__button:not(:focus)::after { 27 | border-right: #{$ax1} solid rgba(#{$schemaColorForeground}, 0.08); 28 | display: block; 29 | height: calc(100% - #{$ax18}); 30 | content: ''; 31 | position: absolute; 32 | right: 0; 33 | } 34 | 35 | .search__button:focus-visible, 36 | .reset__button:focus-visible { 37 | background-color: rgb(#{$schemaColorBackground}); 38 | z-index: 4; 39 | } 40 | 41 | .search__button:focus, 42 | .reset__button:focus { 43 | background-color: rgb(#{$schemaColorBackground}); 44 | z-index: 4; 45 | } 46 | 47 | .search__button:not(:focus-visible):not(.focused), 48 | .reset__button:not(:focus-visible):not(.focused) { 49 | box-shadow: inherit; 50 | background-color: inherit; 51 | } 52 | 53 | .search__button:hover .icon, 54 | .reset__button:hover .icon { 55 | transform: scale(1.07); 56 | } 57 | 58 | .search__button .icon { 59 | height: #{$ax18}; 60 | width: #{$ax18}; 61 | } 62 | 63 | .reset__button .icon.icon-close { 64 | height: #{$ax18}; 65 | width: #{$ax18}; 66 | stroke-width: #{$ax1}; 67 | } 68 | 69 | /* Remove extra spacing for search inputs in Safari */ 70 | input::-webkit-search-decoration { 71 | -webkit-appearance: none; 72 | } 73 | -------------------------------------------------------------------------------- /src/styles/base-prio/component-totals.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .totals { 4 | display: flex; 5 | justify-content: center; 6 | align-items: flex-end; 7 | } 8 | 9 | .totals > * { 10 | font-size: #{$ax16}; 11 | margin: 0; 12 | } 13 | 14 | .totals > h2 { 15 | font-size: calc(#{$fontHeadingScale} * #{$ax16}); 16 | } 17 | 18 | .totals * { 19 | line-height: 1; 20 | } 21 | 22 | .totals > * + * { 23 | margin-left: #{$ax20}; 24 | } 25 | 26 | .totals__subtotal-value { 27 | font-size: #{$ax18}; 28 | } 29 | 30 | .cart__ctas + .totals { 31 | margin-top: #{$ax20}; 32 | } 33 | @include media-query($s-up) { 34 | .totals { 35 | justify-content: flex-end; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/base-prio/section-hero.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | 4 | $block: 'hero'; 5 | .#{$block} { 6 | @include media-query($m-down) { 7 | &--height-viewport { 8 | height: 100svh; 9 | min-height: ax(550); 10 | } 11 | &--height-large { 12 | .grid__item { 13 | aspect-ratio: 0.83334; 14 | } 15 | } 16 | &--height-medium { 17 | .grid__item { 18 | height: 100svh; 19 | min-height: ax(550); 20 | } 21 | } 22 | } 23 | @include media-query($m-up) { 24 | &--height-viewport { 25 | aspect-ratio: 1.7142857143; 26 | max-height: calc( 27 | 100svh - #{$headerHeight} - #{$announcementHeight} 28 | ); 29 | min-height: ax(550); 30 | } 31 | &--height-large { 32 | aspect-ratio: 1.8; 33 | } 34 | &--height-medium { 35 | aspect-ratio: 2.12; 36 | max-height: calc( 37 | 100svh - (#{$sectionSpacingDesktop} * 2) 38 | ); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/base-prio/section-multi-product-slider.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | $block: 'multi-product-slider'; 4 | .#{$block} { 5 | max-width: 100vw; 6 | overflow: hidden; 7 | &__nav-wrapper { 8 | width: 100%; 9 | max-width: 100vw; 10 | overflow-x: auto; 11 | overflow-y: hidden; 12 | &::-webkit-scrollbar { 13 | display: none; 14 | } 15 | -ms-overflow-style: none; /* IE and Edge */ 16 | scrollbar-width: none; /* Firefox */ 17 | } 18 | &__nav { 19 | padding: 0 $ax20; 20 | column-gap: $ax20; 21 | width: max-content; 22 | @include media-query($s-up) { 23 | padding: 0 $ax30; 24 | } 25 | } 26 | &__button { 27 | opacity: 0.5; 28 | transition: opacity 0.3s $bezier; 29 | 30 | &.active { 31 | opacity: 1; 32 | transition: opacity 0.3s $bezier; 33 | &:before { 34 | display: block; 35 | } 36 | } 37 | } 38 | &__container { 39 | margin-top: $ax30; 40 | height: ax(550); 41 | } 42 | &__slider-wrapper { 43 | opacity: 0; 44 | pointer-events: none; 45 | &.active { 46 | opacity: 1; 47 | pointer-events: unset; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/blog-prio/section-main-blog.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .blog-articles { 4 | display: grid; 5 | grid-gap: #{$ax10}; 6 | column-gap: #{$xSpacingMobile}; 7 | row-gap: #{$ySpacingMobile}; 8 | } 9 | 10 | .blog-articles .card-wrapper { 11 | width: 100%; 12 | } 13 | 14 | @include media-query($s-up) { 15 | .blog-articles { 16 | grid-template-columns: 1fr 1fr; 17 | column-gap: #{$xSpacingDesktop}; 18 | row-gap: #{$ySpacingDesktop}; 19 | } 20 | 21 | .blog-articles--collage > *:nth-child(3n + 1), 22 | .blog-articles--collage > *:nth-child(3n + 2):last-child { 23 | grid-column: span 2; 24 | text-align: center; 25 | } 26 | 27 | .blog-articles--collage > *:nth-child(3n + 1) .card, 28 | .blog-articles--collage > *:nth-child(3n + 2):last-child .card { 29 | text-align: center; 30 | } 31 | 32 | .blog-articles--collage > *:nth-child(3n + 1) .article-card__image--small .ratio::before, 33 | .blog-articles--collage > *:nth-child(3n + 2):last-child .article-card__image--small .ratio::before { 34 | padding-bottom: #{ax(220)}; 35 | } 36 | 37 | .blog-articles--collage > *:nth-child(3n + 1) .article-card__image--medium .ratio::before, 38 | .blog-articles--collage > *:nth-child(3n + 2):last-child .article-card__image--medium .ratio::before { 39 | padding-bottom: #{ax(440)}; 40 | } 41 | 42 | .blog-articles--collage > *:nth-child(3n + 1) .article-card__image--large .ratio::before, 43 | .blog-articles--collage > *:nth-child(3n + 2):last-child .article-card__image--large .ratio::before { 44 | padding-bottom: ax(660); 45 | } 46 | } 47 | @include media-query($m-up) { 48 | .blog-articles--collage > *:nth-child(3n + 1) .article-card__image--small .ratio .ratio::before, 49 | .blog-articles--collage > *:nth-child(3n + 2):last-child .article-card__image--small .ratio .ratio::before { 50 | padding-bottom: #{ax(275)}; 51 | } 52 | 53 | .blog-articles--collage > *:nth-child(3n + 1) .article-card__image--medium .ratio::before, 54 | .blog-articles--collage > *:nth-child(3n + 2):last-child .article-card__image--medium .ratio::before { 55 | padding-bottom: #{ax(550)}; 56 | } 57 | 58 | .blog-articles--collage > *:nth-child(3n + 1) .article-card__image--large .ratio::before, 59 | .blog-articles--collage > *:nth-child(3n + 2):last-child .article-card__image--large .ratio::before { 60 | padding-bottom: #{ax(825)}; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/styles/collection-defer/component-show-more.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | [data-uc-show-more] { 4 | padding-left: 0; 5 | justify-content: flex-start; 6 | padding-bottom: #{$ax11}; 7 | } 8 | 9 | [data-uc-show-more], 10 | .button-show-less { 11 | margin-top: #{$ax15}; 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/collection-prio/component-collection-hero.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .collection-hero__inner { 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .collection-hero--with-image .collection-hero__inner { 9 | margin-bottom: 0; 10 | padding-bottom: #{$ax20}; 11 | } 12 | 13 | .collection-hero__text-wrapper { 14 | flex-basis: 100%; 15 | } 16 | 17 | .collection-hero__title { 18 | margin: #{$ax25} 0; 19 | } 20 | 21 | .collection-hero__title + .collection-hero__description { 22 | margin-top: #{$ax15}; 23 | margin-bottom: #{$ax15}; 24 | font-size: #{$ax16}; 25 | line-height: calc(1 + 0.5 / #{$fontBodyScale}); 26 | } 27 | 28 | .collection-hero--with-image .collection-hero__title { 29 | margin: 0; 30 | } 31 | 32 | .collection-hero--with-image .collection-hero__text-wrapper { 33 | padding: #{$ax50} 0 #{$ax40}; 34 | } 35 | 36 | .collection-hero__image-container { 37 | border: #{$mediaBorderWidth} solid 38 | rgba(#{$schemaColorForeground}, #{$mediaBorderOpacity}); 39 | border-radius: #{$mediaRadius}; 40 | box-shadow: #{$mediaShadowHorizontalOffset} #{$mediaShadowVerticalOffset} 41 | #{$mediaShadowBlurRadius} rgba(#{$schemaColorShadow}, #{$mediaShadowOpacity}); 42 | } 43 | 44 | @include media-query($s-down) { 45 | .collection-hero__image-container { 46 | height: #{ax(200)}; 47 | } 48 | } 49 | @include media-query($s-up) { 50 | .collection-hero.collection-hero--with-image { 51 | padding: calc(#{$ax40} + #{$pageWidthMargin}) 0 calc(#{$ax40} + #{$pageWidthMargin}); 52 | overflow: hidden; 53 | } 54 | 55 | .collection-hero--with-image .collection-hero__inner { 56 | padding-bottom: 0; 57 | } 58 | 59 | .collection-hero { 60 | padding: 0; 61 | } 62 | 63 | .collection-hero__inner { 64 | align-items: center; 65 | flex-direction: row; 66 | padding-bottom: 0; 67 | } 68 | 69 | .collection-hero__title + .collection-hero__description { 70 | font-size: #{$ax18}; 71 | margin-top: #{$ax20}; 72 | margin-bottom: #{$ax20}; 73 | } 74 | 75 | .collection-hero__description { 76 | max-width: 66.67%; 77 | } 78 | 79 | .collection-hero--with-image .collection-hero__description { 80 | max-width: 100%; 81 | } 82 | 83 | .collection-hero--with-image .collection-hero__text-wrapper { 84 | padding: #{$ax40} #{$ax20} #{$ax40} 0; 85 | flex-basis: 50%; 86 | } 87 | 88 | .collection-hero__image-container { 89 | align-self: stretch; 90 | flex: 1 0 50%; 91 | margin-left: #{$ax30}; 92 | min-height: #{ax(200)}; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/styles/collection-prio/template-collection.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | @include media-query($s-down) { 4 | .collection .grid__item:only-child { 5 | flex: 0 0 100%; 6 | max-width: 100%; 7 | } 8 | } 9 | 10 | @include media-query($m-down) { 11 | .collection .slider.slider--tablet { 12 | margin-bottom: #{$ax15}; 13 | } 14 | } 15 | 16 | .collection [data-uc-loading-overlay] { 17 | top: 0; 18 | right: 0; 19 | bottom: 0; 20 | left: 0; 21 | display: none; 22 | width: 100%; 23 | padding: 0 #{$ax15}; 24 | opacity: 0.7; 25 | } 26 | 27 | @include media-query($s-up) { 28 | .collection [data-uc-loading-overlay] { 29 | padding-left: #{$ax50}; 30 | padding-right: #{$ax50}; 31 | } 32 | } 33 | 34 | .collection.loading [data-uc-loading-overlay] { 35 | display: block; 36 | } 37 | 38 | .collection--empty .title-wrapper { 39 | margin-top: #{ax(100)}; 40 | margin-bottom: #{ax(150)}; 41 | } 42 | 43 | @include media-query($m-down) { 44 | .collection .slider--tablet.product-grid { 45 | scroll-padding-left: #{$ax15}; 46 | } 47 | } 48 | 49 | .collection__description > * { 50 | margin: 0; 51 | } 52 | 53 | .collection__title.title-wrapper { 54 | margin-bottom: #{$ax25}; 55 | } 56 | 57 | .collection__title .title:not(:only-child) { 58 | margin-bottom: #{$ax10}; 59 | } 60 | 61 | @include media-query($m-up) { 62 | .collection__title--desktop-slider .title { 63 | margin-bottom: #{$ax25}; 64 | } 65 | 66 | .collection__title.title-wrapper--self-padded-tablet-down { 67 | padding: 0 #{$ax50}; 68 | } 69 | 70 | .collection slider-component:not(.page-width-desktop) { 71 | padding: 0; 72 | } 73 | 74 | .collection--full-width slider-component:not(.slider-component-desktop) { 75 | padding: 0 #{$ax15}; 76 | max-width: none; 77 | } 78 | } 79 | 80 | .collection__view-all a:not(.link) { 81 | margin-top: #{$ax10}; 82 | } 83 | -------------------------------------------------------------------------------- /src/styles/modules/article-defer.scss: -------------------------------------------------------------------------------- 1 | //@import '../article-defer.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/modules/article-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../article-prio/section-blog-post.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/modules/base-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../base-defer/component-cart-items.scss'; 2 | @import '../base-defer/component-list-payment.scss'; 3 | @import '../base-defer/component-list-social.scss'; 4 | @import '../base-defer/component-loading-overlay.scss'; 5 | @import '../base-defer/component-menu-drawer.scss'; 6 | @import '../base-defer/component-modal-video.scss'; 7 | @import '../base-defer/component-newsletter.scss'; 8 | @import '../base-defer/newsletter-section.scss'; 9 | @import '../base-defer/section-email-signup-banner.scss'; 10 | @import '../base-defer/section-footer.scss'; 11 | @import '../base-defer/component-cart.scss'; 12 | @import '../base-defer/component-cart-drawer.scss'; 13 | @import '../base-defer/component-predictive-search.scss'; 14 | @import '../base-defer/section-collection-list.scss'; 15 | @import '../base-defer/section-featured-blog.scss'; 16 | @import '../base-defer/section-featured-product.scss'; 17 | @import '../base-defer/section-multicolumn.scss'; 18 | @import '../base-defer/section-rich-text.scss'; 19 | @import '../base-defer/video-section.scss'; 20 | @import '../base-defer/component-cart-notification.scss'; 21 | @import '../base-defer/collage.scss'; 22 | @import '../base-defer/component-article-card.scss'; 23 | @import '../base-defer/component-welcome-popup.scss'; 24 | @import '../base-defer/component-product-popup-modal.scss'; 25 | @import '../base-defer/component-dynamic-progress-bar.scss'; 26 | @import '../base-defer/component-cart-gwp.scss'; 27 | -------------------------------------------------------------------------------- /src/styles/modules/base-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../base-prio/base.scss'; 2 | @import '../base-prio/component-quantity.scss'; 3 | @import '../global/typography.scss'; 4 | @import '../base-prio/collapsible-content.scss'; 5 | @import '../base-prio/component-accordion.scss'; 6 | @import '../base-prio/component-card.scss'; 7 | @import '../base-prio/component-deferred-media.scss'; 8 | @import '../base-prio/component-discounts.scss'; 9 | @import '../base-prio/component-image-with-text.scss'; 10 | @import '../base-prio/component-list-menu.scss'; 11 | @import '../base-prio/component-price.scss'; 12 | @import '../base-prio/component-rating.scss'; 13 | @import '../base-prio/component-search.scss'; 14 | @import '../base-prio/component-slider.scss'; 15 | @import '../base-prio/component-slideshow.scss'; 16 | @import '../base-prio/component-totals.scss'; 17 | @import '../base-prio/section-image-banner.scss'; 18 | @import '../base-prio/section-multi-product-slider.scss'; 19 | @import '../base-prio/component-product-slider.scss'; 20 | @import '../base-prio/section-hero.scss'; 21 | @import '../base-prio/component-banner-content.scss'; 22 | @import '../base-prio/component-content-settings.scss'; 23 | -------------------------------------------------------------------------------- /src/styles/modules/blog-defer.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadsciencee/shopify-dawn-typescript/ed6df28a01c86c5a7011b612627dad237a92735f/src/styles/modules/blog-defer.scss -------------------------------------------------------------------------------- /src/styles/modules/blog-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../blog-prio/section-main-blog.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/modules/collection-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../base-defer/component-facets.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/modules/collection-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../collection-prio/component-collection-hero.scss'; 2 | @import '../collection-prio/template-collection.scss'; 3 | @import '../collection-defer/component-show-more.scss'; 4 | -------------------------------------------------------------------------------- /src/styles/modules/customer-defer.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadsciencee/shopify-dawn-typescript/ed6df28a01c86c5a7011b612627dad237a92735f/src/styles/modules/customer-defer.scss -------------------------------------------------------------------------------- /src/styles/modules/customer-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../customer-prio/customer.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/modules/page-defer.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadsciencee/shopify-dawn-typescript/ed6df28a01c86c5a7011b612627dad237a92735f/src/styles/modules/page-defer.scss -------------------------------------------------------------------------------- /src/styles/modules/page-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../page-prio/section-main-page.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/modules/product-defer.scss: -------------------------------------------------------------------------------- 1 | @import '../product-defer/component-complementary-products.scss'; 2 | @import '../product-defer/section-related-products.scss'; 3 | -------------------------------------------------------------------------------- /src/styles/modules/product-prio.scss: -------------------------------------------------------------------------------- 1 | @import '../product-prio/section-main-product.scss'; 2 | -------------------------------------------------------------------------------- /src/styles/optional/component-mega-menu.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .mega-menu { 4 | position: static; 5 | } 6 | 7 | .mega-menu__content { 8 | background-color: rgb(#{$schemaColorBackground}); 9 | border-left: 0; 10 | border-radius: 0; 11 | border-right: 0; 12 | overflow-y: auto; 13 | padding-bottom: #{$ax30}; 14 | padding-top: #{$ax30}; 15 | top: 100%; 16 | } 17 | 18 | .shopify-section-header-sticky .mega-menu__content { 19 | max-height: calc(100vh - var(--header-bottom-position-desktop) - #{$ax40}); 20 | } 21 | 22 | [data-uc-header-wrapper='border-bottom'] .mega-menu__content { 23 | border-top: 0; 24 | } 25 | 26 | .js .mega-menu__content { 27 | opacity: 0; 28 | transform: translateY(#{ax(-15)}); 29 | &--with-media { 30 | padding: $ySpacing $xSpacing; 31 | } 32 | } 33 | 34 | .mega-menu[open] .mega-menu__content { 35 | opacity: 1; 36 | transform: translateY(0); 37 | } 38 | 39 | .mega-menu__list { 40 | display: grid; 41 | gap: #{$ax18} #{$ax40}; 42 | grid-template-columns: repeat(6, minmax(0, 1fr)); 43 | list-style: none; 44 | &--with-media { 45 | padding-left: 0; 46 | margin-left: 0; 47 | } 48 | } 49 | 50 | .mega-menu__link { 51 | color: rgba(#{$schemaColorForeground}, 0.75); 52 | display: block; 53 | line-height: calc(1 + 0.3 / #{$fontBodyScale}); 54 | padding-bottom: #{$ax6}; 55 | padding-top: #{$ax6}; 56 | text-decoration: none; 57 | word-wrap: break-word; 58 | } 59 | 60 | .mega-menu__link--level-2 { 61 | font-weight: bold; 62 | } 63 | 64 | .header--top-center .mega-menu__list { 65 | display: flex; 66 | justify-content: center; 67 | flex-wrap: wrap; 68 | column-gap: 0; 69 | } 70 | 71 | .header--top-center .mega-menu__list > li { 72 | width: 16%; 73 | padding-right: #{$ax24}; 74 | } 75 | 76 | .mega-menu .mega-menu__list--condensed { 77 | display: block; 78 | } 79 | 80 | .mega-menu__list--condensed .mega-menu__link { 81 | font-weight: normal; 82 | } 83 | 84 | .mega-menu__media { 85 | aspect-ratio: 1.5; 86 | height: auto; 87 | max-width: ax(320); 88 | @include media-query($s-up) { 89 | margin-left: auto; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/styles/optional/component-model-viewer-ui.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .shopify-model-viewer-ui .shopify-model-viewer-ui__controls-area { 4 | background: rgb(#{$schemaColorBackground}); 5 | border-color: rgba(#{$schemaColorForeground}, 0.04); 6 | } 7 | 8 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button { 9 | color: rgba(#{$schemaColorForeground}, 0.75); 10 | } 11 | 12 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control:hover { 13 | color: rgba(#{$schemaColorForeground}, 0.55); 14 | } 15 | 16 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control:active, 17 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control.focus-visible:focus { 18 | color: rgba(#{$schemaColorForeground}, 0.55); 19 | background: rgba(#{$schemaColorForeground}, 0.04); 20 | } 21 | 22 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control:not(:last-child):after { 23 | border-color: rgba(#{$schemaColorForeground}, 0.04); 24 | } 25 | 26 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--poster { 27 | border-radius: 50%; 28 | color: rgb(#{$schemaColorForeground}); 29 | background: rgb(#{$schemaColorBackground}); 30 | border-color: rgba(#{$schemaColorForeground}, 0.1); 31 | transform: translate(-50%, -50%) scale(1); 32 | transition: transform var(--duration-short) ease, color var(--duration-short) ease; 33 | } 34 | 35 | .shopify-model-viewer-ui .shopify-model-viewer-ui__poster-control-icon { 36 | width: #{$ax48}; 37 | height: #{$ax48}; 38 | margin-top: #{$ax3}; 39 | } 40 | 41 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--poster:hover, 42 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--poster:focus { 43 | transform: translate(-50%, -50%) scale(1.1); 44 | } 45 | @include media-query($m-up) { 46 | .pagination-wrapper { 47 | margin-top: #{$ax50}; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/styles/optional/component-pagination.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .pagination-wrapper { 4 | margin-top: #{$ax40}; 5 | } 6 | 7 | 8 | .pagination__list > li { 9 | width: $ax24; 10 | height: $ax24; 11 | } 12 | 13 | .pagination__list > li:not(:last-child) { 14 | margin-right: #{$ax10}; 15 | } 16 | 17 | .pagination__item { 18 | color: rgb(#{$schemaColorForeground}); 19 | text-decoration: none; 20 | } 21 | 22 | .pagination__item .icon-caret { 23 | height: #{$ax7}; 24 | transform: translateY(-10%); 25 | } 26 | 27 | 28 | 29 | .pagination__item--next .icon { 30 | margin-left: #{ax(-2)}; 31 | transform: rotate(90deg); 32 | } 33 | 34 | .pagination__item--prev .icon { 35 | margin-right: #{ax(-2)}; 36 | transform: rotate(-90deg); 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/optional/component-product-model.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .product__xr-button { 4 | background: rgba(#{$schemaColorForeground}, 0.08); 5 | color: rgb(#{$schemaColorForeground}); 6 | margin: #{$ax10} auto; 7 | box-shadow: none; 8 | display: flex; 9 | } 10 | 11 | .button.product__xr-button:hover { 12 | box-shadow: none; 13 | } 14 | 15 | .product__xr-button[data-shopify-xr-hidden] { 16 | visibility: hidden; 17 | } 18 | 19 | .shopify-design-mode .product__xr-button[data-shopify-xr-hidden] { 20 | display: none; 21 | } 22 | 23 | @include media-query($s-down) { 24 | slider-component .product__xr-button { 25 | display: none; 26 | } 27 | 28 | .active .product__xr-button:not([data-shopify-xr-hidden]) { 29 | display: block; 30 | } 31 | } 32 | 33 | 34 | .product__xr-button .icon { 35 | width: #{$ax14}; 36 | margin-right: #{$ax10}; 37 | } 38 | @include media-query($s-up) { 39 | slider-component + .button.product__xr-button { 40 | display: none; 41 | } 42 | 43 | .product__xr-button[data-shopify-xr-hidden] { 44 | display: none; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/styles/optional/section-contact-form.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .contact img { 4 | max-width: 100%; 5 | } 6 | 7 | .contact .form__message { 8 | align-items: flex-start; 9 | } 10 | 11 | .contact .icon-success { 12 | margin-top: #{$ax2}; 13 | } 14 | 15 | .contact .field { 16 | margin-bottom: #{$ax15}; 17 | } 18 | 19 | .contact__button { 20 | margin-top: #{$ax30}; 21 | } 22 | 23 | @include media-query($s-up) { 24 | .contact .field { 25 | margin-bottom: #{$ax20}; 26 | } 27 | 28 | .contact__button { 29 | margin-top: #{$ax40}; 30 | } 31 | 32 | .contact__fields { 33 | display: grid; 34 | grid-template-columns: repeat(2, 1fr); 35 | grid-column-gap: #{$ax20}; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/page-prio/section-main-page.scss: -------------------------------------------------------------------------------- 1 | @import '../global/variables'; 2 | @import '../global/functions'; 3 | .page-title { 4 | margin-top: 0; 5 | } 6 | 7 | .main-page-title { 8 | margin-bottom: #{$ax30}; 9 | } 10 | 11 | .page-placeholder-wrapper { 12 | display: flex; 13 | justify-content: center; 14 | } 15 | 16 | .page-placeholder { 17 | width: ax(525); 18 | height: ax(525); 19 | } 20 | 21 | @include media-query($s-up) { 22 | .main-page-title { 23 | margin-bottom: #{$ax40}; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/styles/product-defer/section-related-products.scss: -------------------------------------------------------------------------------- 1 | @import "../global/variables"; 2 | @import "../global/functions"; 3 | .related-products { 4 | display: block; 5 | } 6 | 7 | .related-products__heading { 8 | margin: 0 0 #{$ax30}; 9 | } 10 | -------------------------------------------------------------------------------- /templates/404.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-404" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/article.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-article", 5 | "blocks": { 6 | "featured_image": { 7 | "type": "featured_image", 8 | "settings": { 9 | "image_height": "adapt" 10 | } 11 | }, 12 | "title": { 13 | "type": "title", 14 | "settings": { 15 | "blog_show_date": true, 16 | "blog_show_author": false 17 | } 18 | }, 19 | "share": { 20 | "type": "share", 21 | "settings": { 22 | "share_label": "Share" 23 | } 24 | }, 25 | "content": { 26 | "type": "content" 27 | } 28 | }, 29 | "block_order": [ 30 | "featured_image", 31 | "title", 32 | "share", 33 | "content" 34 | ] 35 | } 36 | }, 37 | "order": [ 38 | "main" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /templates/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-blog", 5 | "settings": { 6 | "layout": "collage", 7 | "show_image": true, 8 | "image_height": "medium", 9 | "show_date": true, 10 | "show_author": false 11 | } 12 | } 13 | }, 14 | "order": [ 15 | "main" 16 | ] 17 | } -------------------------------------------------------------------------------- /templates/cart.liquid: -------------------------------------------------------------------------------- 1 | {% layout 'none' %} 2 | {% comment %} 3 | No one really uses the cart page. This auto redirects the cart page to the home page 4 | and opens the cart drawer. If you need the cart page for some reason, you can remove this file 5 | and replace it with cart.json from the default dawn template 6 | {% endcomment %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /templates/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main-banner": { 4 | "type": "main-collection-banner" 5 | }, 6 | "main-product-grid": { 7 | "type": "main-collection-product-grid" 8 | } 9 | }, 10 | "order": ["main-banner", "main-product-grid"] 11 | } 12 | -------------------------------------------------------------------------------- /templates/customers/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-account" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/customers/activate_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-activate-account" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/customers/addresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-addresses" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/customers/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-login" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/customers/order.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-order" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/customers/register.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-register" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/customers/reset_password.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-reset-password" 5 | } 6 | }, 7 | "order": [ 8 | "main" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /templates/list-collections.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-list-collections", 5 | "settings": { 6 | "title": "Collections", 7 | "sort": "alphabetical", 8 | "image_ratio": "square" 9 | } 10 | } 11 | }, 12 | "order": [ 13 | "main" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /templates/page.contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-page", 5 | "settings": { 6 | } 7 | }, 8 | "form": { 9 | "type": "contact-form", 10 | "settings": { 11 | "heading": "", 12 | "heading_size": "h1", 13 | "color_scheme": "background-1" 14 | } 15 | } 16 | }, 17 | "order": [ 18 | "main", 19 | "form" 20 | ] 21 | } -------------------------------------------------------------------------------- /templates/page.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-page", 5 | "settings": { 6 | } 7 | } 8 | }, 9 | "order": [ 10 | "main" 11 | ] 12 | } -------------------------------------------------------------------------------- /templates/password.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "password", 3 | "sections": { 4 | "main": { 5 | "type": "email-signup-banner", 6 | "blocks": { 7 | "heading": { 8 | "type": "heading", 9 | "settings": { 10 | "heading": "Opening soon", 11 | "heading_size": "h1" 12 | } 13 | }, 14 | "paragraph": { 15 | "type": "paragraph", 16 | "settings": { 17 | "text": "

    Be the first to know when we launch.<\/p>", 18 | "text_style": "body" 19 | } 20 | }, 21 | "email_form": { 22 | "type": "email_form" 23 | } 24 | }, 25 | "block_order": [ 26 | "heading", 27 | "paragraph", 28 | "email_form" 29 | ], 30 | "settings": { 31 | "image_overlay_opacity": 0, 32 | "show_background_image": true, 33 | "image_height": "medium", 34 | "desktop_content_position": "middle-center", 35 | "show_text_box": true, 36 | "desktop_content_alignment": "center", 37 | "color_scheme": "background-1", 38 | "mobile_content_alignment": "center", 39 | "show_text_below": true 40 | } 41 | } 42 | }, 43 | "order": [ 44 | "main" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /templates/product.hidden.liquid: -------------------------------------------------------------------------------- 1 | {% layout 'none' %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": { 3 | "main": { 4 | "type": "main-search", 5 | "settings": { 6 | "columns_desktop": 4, 7 | "image_ratio": "adapt", 8 | "show_secondary_image": false, 9 | "show_vendor": false, 10 | "show_rating": false, 11 | "enable_filtering": true, 12 | "enable_sorting": true, 13 | "article_show_date": true, 14 | "article_show_author": false, 15 | "columns_mobile": "2" 16 | } 17 | } 18 | }, 19 | "order": [ 20 | "main" 21 | ] 22 | } -------------------------------------------------------------------------------- /translation.yml: -------------------------------------------------------------------------------- 1 | source_language: en 2 | target_languages: [bg-BG, cs, da, de, el, es, fi, fr, hr-HR, hu, id, it, ja, ko, lt-LT, nb, nl, pl, pt-BR, pt-PT, ro-RO, ru, sk-SK, sl-SI, sv, th, tr, vi, zh-CN, zh-TW] 3 | non_blocking_languages: [] 4 | async_pr_mode: per_pr 5 | components: 6 | - name: buyer 7 | audience: buyer 8 | scheme: 'online-store-theme' 9 | owners: ['@Shopify/themes-translations'] 10 | paths: 11 | - locales/{{language}}.json 12 | - name: merchant 13 | target_languages: [cs, da, de, es, fi, fr, it, ja, ko, nb, nl, pl, pt-BR, pt-PT, sv, th, tr, vi, zh-CN, zh-TW] 14 | audience: merchant 15 | scheme: 'online-store-theme' 16 | owners: ['@Shopify/themes-translations'] 17 | paths: 18 | - locales/{{language}}.schema.json 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "." 4 | ], 5 | "exclude": [ 6 | "dist", 7 | "build", 8 | "node_modules" 9 | ], 10 | "compilerOptions": { 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/*": [ 14 | "src/*" 15 | ], 16 | "~/*": [ 17 | "src/*", 18 | ] 19 | }, 20 | "rootDir": ".", 21 | "outDir": "../built/local", 22 | "pretty": true, 23 | "lib": [ 24 | "es2022", 25 | "dom.iterable", 26 | "dom" 27 | ], 28 | "target": "es2022", 29 | "module": "ES2022", 30 | "moduleResolution": "node", 31 | "declaration": true, 32 | "declarationMap": true, 33 | "sourceMap": true, 34 | "composite": true, 35 | "noEmitOnError": true, 36 | "emitDeclarationOnly": true, 37 | "strict": true, 38 | "strictBindCallApply": false, 39 | "useUnknownInCatchVariables": false, 40 | "noImplicitOverride": true, 41 | "noUnusedLocals": true, 42 | "noUnusedParameters": true, 43 | "allowUnusedLabels": false, 44 | "skipLibCheck": true, 45 | "alwaysStrict": true, 46 | "preserveConstEnums": true, 47 | "newLine": "lf", 48 | "types": [ 49 | 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import shopify from 'vite-plugin-shopify' 2 | import { resolve } from 'node:path' 3 | import cssnano from 'cssnano' 4 | import advancedPreset from 'cssnano-preset-advanced' 5 | import basicSsl from '@vitejs/plugin-basic-ssl' 6 | 7 | export default { 8 | css: { 9 | transformer: 'postcss', 10 | postcss: { 11 | plugins: [ 12 | cssnano( 13 | advancedPreset({ 14 | autoprefix: false, 15 | mergeRules: true, 16 | discardDuplicates: true, 17 | reduceIdents: false, 18 | zindex: false, 19 | }) 20 | ), 21 | ], 22 | } 23 | }, 24 | server: { 25 | secure: false, 26 | host: 'localhost', 27 | https: true, 28 | port: 3000, 29 | } , 30 | publicDir: 'public', 31 | resolve: { 32 | alias: { 33 | '@js': resolve('src/scripts'), 34 | '@scss': resolve('src/styles'), 35 | }, 36 | }, 37 | plugins: [ 38 | basicSsl(), 39 | shopify({ 40 | themeRoot: './', 41 | sourceCodeDir: 'src', 42 | entrypointsDir: 'src/entry', 43 | additionalEntrypoints: [], 44 | snippetFile: 'vite.liquid', 45 | versionNumbers: true 46 | }), 47 | ], 48 | build: { 49 | sourcemap: true, 50 | minify: 'esbuild', 51 | cssMinify: 'postcss', 52 | rollupOptions: { 53 | output: { 54 | output: { 55 | manualChunks: { 56 | 'core-chunk': (id) => id.includes('/src/core/'), 57 | 'theme-chunk': (id) => id.includes('/src/theme/'), 58 | 'catalog-chunk': (id) => id.includes('/src/catalog/'), 59 | 'product-chunk': (id) => id.includes('/src/product/'), 60 | 'cart-chunk': (id) => id.includes('/src/cart/'), 61 | 'customer-chunk': (id) => id.includes('/src/customer/'), 62 | }, 63 | } 64 | } 65 | } 66 | }, 67 | } 68 | --------------------------------------------------------------------------------