├── .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 |
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 |
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 | {{ cart.item_count }}
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 |
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 |
17 |
18 | {{- localization.country.currency.iso_code }}
19 | {{ localization.country.currency.symbol }} | {{ localization.country.name -}}
20 |
21 | {% render 'icon-default', icon: 'caret' %}
22 |
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 |
28 | {% for list_item in log_list %}
29 | {% unless list_item contains '=' %}
30 | {{ list_item }}
31 | {% continue %}
32 | {% endunless %}
33 | {% assign kv_pair = list_item | split: '=' %}
34 |
35 | {{ kv_pair[0] }}: {{ kv_pair[1] }}
36 |
37 | {% endfor %}
38 |
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 |
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 |
17 | {{ localization.language.endonym_name | capitalize }}
18 | {% render 'icon-default', icon: 'caret' %}
19 |
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 |
17 |
20 | {% when 'dialog' %}
21 |
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 |
16 |
21 |
22 |
23 |
24 | {{- product.metafields.reviews.rating.value }} /
25 | {{ product.metafields.reviews.rating.value.scale_max -}}
26 |
27 |
28 |
29 | ({{ product.metafields.reviews.rating_count }})
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 |
17 | {% render 'icon-default', icon: 'share' %}
18 | {{ block.settings.share_label | escape }}
19 |
20 |
21 |
22 | {% render 'icon-default', icon: 'share' %}
23 | {{ block.settings.share_label | escape }}
24 |
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 |
48 |
52 |
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 |
--------------------------------------------------------------------------------