├── .babelrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .huskyrc
├── .stylelintrc
├── Readme.md
├── TODO
├── build-utils
├── addons
│ └── webpack.bundleanalyzer.js
├── common-path.js
├── common-plugins
│ ├── copy-plugin.js
│ └── replace-in-file-plugin.js
├── markup
│ ├── className.js
│ ├── html.js
│ └── index.js
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
├── commitlint.config.js
├── docs
├── assets
│ ├── css
│ │ └── main.css
│ ├── images
│ │ ├── icons.png
│ │ ├── icons@2x.png
│ │ ├── widgets.png
│ │ └── widgets@2x.png
│ └── js
│ │ ├── main.js
│ │ └── search.js
├── index.html
├── interfaces
│ ├── helpers_lazyload.ilazyglobalconfig.html
│ ├── types_classname_swiper_type.iswiperclassname.html
│ ├── types_shopify_cart_type.icart.html
│ ├── types_shopify_cart_type.iitemsresponse.html
│ ├── types_shopify_cart_type.iproperty.html
│ ├── types_shopify_common_type.ilineitem.html
│ ├── types_shopify_common_type.ioption.html
│ ├── types_shopify_common_type.ivariant.html
│ ├── types_shopify_product_type.imedia.html
│ ├── types_shopify_product_type.ipreviewobject.html
│ ├── types_shopify_product_type.iproduct.html
│ └── types_shopify_theme_type.itheme.html
├── modules.html
└── modules
│ ├── helpers.html
│ ├── helpers_cart_cart.html
│ ├── helpers_dom_common.html
│ ├── helpers_dom_dom.html
│ ├── helpers_dom_event.html
│ ├── helpers_lazyload.html
│ ├── helpers_sections.html
│ ├── helpers_swiper.html
│ ├── helpers_utils.html
│ ├── helpers_vueconfig.html
│ ├── types_classname_swiper_type.html
│ ├── types_shopify_cart_type.html
│ ├── types_shopify_common_type.html
│ ├── types_shopify_product_type.html
│ └── types_shopify_theme_type.html
├── jest.config.js
├── jest.setup.ts
├── package-lock.json
├── package.json
├── shopify
├── assets
│ ├── base.css
│ ├── cart-notification.js
│ ├── cart.js
│ ├── collage.css
│ ├── collection-filters-form.js
│ ├── component-accordion.css
│ ├── component-article-card.css
│ ├── component-badge.css
│ ├── component-card.css
│ ├── component-cart-items.css
│ ├── component-cart-notification.css
│ ├── component-cart.css
│ ├── component-collection-hero.css
│ ├── component-deferred-media.css
│ ├── component-discounts.css
│ ├── component-image-with-text.css
│ ├── component-list-menu.css
│ ├── component-list-payment.css
│ ├── component-list-social.css
│ ├── component-loading-overlay.css
│ ├── component-menu-drawer.css
│ ├── component-model-viewer-ui.css
│ ├── component-newsletter.css
│ ├── component-pagination.css
│ ├── component-pickup-availability.css
│ ├── component-price.css
│ ├── component-product-model.css
│ ├── component-rte.css
│ ├── component-search.css
│ ├── component-slider.css
│ ├── component-totals.css
│ ├── customer.css
│ ├── customer.js
│ ├── details-disclosure.js
│ ├── details-modal.js
│ ├── disclosure.css
│ ├── global.js
│ ├── newsletter-section.css
│ ├── password-modal.js
│ ├── pickup-availability.js
│ ├── product-form.js
│ ├── product-model.js
│ ├── section-blog-post.css
│ ├── section-collection-list.css
│ ├── section-contact-form.css
│ ├── section-featured-blog.css
│ ├── section-footer.css
│ ├── section-image-banner.css
│ ├── section-main-blog.css
│ ├── section-main-page.css
│ ├── section-main-product.css
│ ├── section-multicolumn.css
│ ├── section-password.css
│ ├── section-product-recommendations.css
│ ├── section-rich-text.css
│ ├── share.js
│ ├── slider.js
│ ├── template-collection.css
│ ├── template-giftcard.css
│ └── variants.js
├── config
│ ├── settings_data.json
│ └── settings_schema.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
├── sections
│ ├── announcement-bar.liquid
│ ├── apps.liquid
│ ├── cart-icon-bubble.liquid
│ ├── cart-live-region-text.liquid
│ ├── cart-notification-button.liquid
│ ├── cart-notification-product.liquid
│ ├── collage.liquid
│ ├── collection-list.liquid
│ ├── contact-form.liquid
│ ├── custom-liquid.liquid
│ ├── featured-blog.liquid
│ ├── featured-collection.liquid
│ ├── footer.liquid
│ ├── header.liquid
│ ├── image-banner.liquid
│ ├── image-with-text.liquid
│ ├── main-404.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-page.liquid
│ ├── main-password-footer.liquid
│ ├── main-password-header.liquid
│ ├── main-product.liquid
│ ├── main-search.liquid
│ ├── multicolumn.liquid
│ ├── newsletter.liquid
│ ├── page.liquid
│ ├── pickup-availability.liquid
│ ├── product-recommendations.liquid
│ ├── rich-text.liquid
│ └── test
│ │ ├── Test.ts
│ │ ├── _test.scss
│ │ └── test.liquid
├── snippets
│ ├── article-card.liquid
│ ├── cart-notification.liquid
│ ├── icon-3d-model.liquid
│ ├── icon-accordion.liquid
│ ├── icon-account.liquid
│ ├── icon-arrow.liquid
│ ├── icon-caret.liquid
│ ├── icon-cart-empty.liquid
│ ├── icon-cart.liquid
│ ├── icon-checkmark.liquid
│ ├── icon-clipboard.liquid
│ ├── icon-close-small.liquid
│ ├── icon-close.liquid
│ ├── icon-discount.liquid
│ ├── icon-error.liquid
│ ├── icon-facebook.liquid
│ ├── icon-filter.liquid
│ ├── icon-hamburger.liquid
│ ├── icon-instagram.liquid
│ ├── icon-minus.liquid
│ ├── icon-padlock.liquid
│ ├── icon-pinterest.liquid
│ ├── icon-play.liquid
│ ├── icon-plus.liquid
│ ├── icon-remove.liquid
│ ├── icon-share.liquid
│ ├── icon-snapchat.liquid
│ ├── icon-success.liquid
│ ├── icon-tick.liquid
│ ├── icon-tiktok.liquid
│ ├── icon-tumblr.liquid
│ ├── icon-twitter.liquid
│ ├── icon-unavailable.liquid
│ ├── icon-vimeo.liquid
│ ├── icon-youtube.liquid
│ ├── icon-zoom.liquid
│ ├── meta-tags.liquid
│ ├── pagination.liquid
│ ├── price.liquid
│ ├── product-card-placeholder.liquid
│ ├── product-card.liquid
│ ├── product-thumbnail.liquid
│ └── social-sharing.liquid
└── templates
│ ├── 404.json
│ ├── article.json
│ ├── blog.json
│ ├── cart.json
│ ├── collection.json
│ ├── customers
│ ├── account.liquid
│ ├── activate_account.liquid
│ ├── addresses.liquid
│ ├── login.liquid
│ ├── order.liquid
│ ├── register.liquid
│ └── reset_password.liquid
│ ├── gift_card.liquid
│ ├── index.json
│ ├── list-collections.json
│ ├── page.contact.json
│ ├── page.json
│ ├── password.json
│ ├── product.json
│ └── search.json
├── src
├── __test__
│ ├── cart
│ │ └── cart.spec.ts
│ ├── dom
│ │ └── dom.spec.ts
│ └── test
│ │ └── app.spec.ts
├── helpers
│ ├── cart
│ │ └── cart.ts
│ ├── dom
│ │ ├── common.ts
│ │ ├── dom.ts
│ │ └── event.ts
│ ├── index.ts
│ └── sections
│ │ └── index.ts
├── index.ts
├── styles
│ ├── _general.scss
│ ├── _vueGeneral.scss
│ ├── base
│ │ ├── _base-color.scss
│ │ ├── _base-dir.scss
│ │ └── _heading-base.scss
│ ├── components
│ │ ├── _components-dir.scss
│ │ ├── button
│ │ │ └── _btn.scss
│ │ ├── loading
│ │ │ └── _loading-ui.scss
│ │ └── toast
│ │ │ └── _toast.scss
│ ├── helpers
│ │ └── _helpers-dir.scss
│ ├── layout
│ │ └── _layout-dir.scss
│ ├── main.scss
│ ├── pages
│ │ ├── _body.scss
│ │ ├── _pages-dir.scss
│ │ ├── collection
│ │ │ ├── _all-collection.scss
│ │ │ ├── _normal-collection.scss
│ │ │ └── _page-alles.scss
│ │ └── customer
│ │ │ └── _account.scss
│ ├── sections
│ │ └── _sections-dir.scss
│ ├── snippet
│ │ └── _background-image.scss
│ ├── utils
│ │ ├── _utils-dir.scss
│ │ ├── functions
│ │ │ └── _get-variable-css.scss
│ │ └── mixins
│ │ │ ├── _center.scss
│ │ │ ├── _position.scss
│ │ │ ├── _prefix.scss
│ │ │ ├── _responsive.scss
│ │ │ ├── _size.scss
│ │ │ └── _three-dots.scss
│ └── vendors
│ │ ├── _grid.scss
│ │ ├── _normalize.scss
│ │ ├── _reset.scss
│ │ └── _variants.scss
├── types
│ ├── shopify
│ │ ├── cart.type.ts
│ │ ├── collection.type.ts
│ │ ├── common.type.ts
│ │ ├── product.type.ts
│ │ └── theme.type.ts
│ └── vue-shims.d.ts
└── vue
│ ├── components
│ ├── entry
│ │ ├── cart
│ │ │ └── .gitkeep
│ │ ├── index.ts
│ │ ├── product
│ │ │ └── .gitkeep
│ │ ├── readme.md
│ │ └── search
│ │ │ └── .gitkeep
│ └── globals
│ │ ├── .gitkeep
│ │ ├── XoButton.vue
│ │ └── readme.md
│ ├── config
│ └── index.ts
│ ├── filters
│ ├── hugMoneyFormat.ts
│ ├── hugUppercase.ts
│ ├── imgURL.ts
│ └── index.ts
│ ├── mixins
│ ├── CollectionCard.ts
│ └── SplitingVariant.ts
│ └── store
│ ├── index.ts
│ ├── modules
│ ├── cart
│ │ └── index.ts
│ └── collection
│ │ └── index.ts
│ └── type.ts
├── tsconfig.json
├── typedoc.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ],
5 | "plugins": [
6 | ["@babel/proposal-decorators", { "legacy": true }],
7 | ["@babel/proposal-class-properties", { "loose": true }]
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: "@typescript-eslint/parser",
3 | plugins: [
4 | "@typescript-eslint",
5 | // "eslint-comments",
6 | // "promise",
7 | // "unicorn",
8 | ],
9 | extends: [
10 | "airbnb-typescript/base",
11 | "plugin:@typescript-eslint/recommended",
12 | "plugin:eslint-comments/recommended",
13 | 'plugin:vue/vue3-recommended'
14 | ],
15 | parserOptions: {
16 | project: './tsconfig.json',
17 | },
18 | overrides: [
19 | {
20 | // enable the rule specifically for TypeScript files
21 | "files": ["*.ts", "*.vue"],
22 | "rules": {
23 | "@typescript-eslint/explicit-function-return-type": ["error"]
24 | }
25 | }
26 | ],
27 | rules: {
28 | "no-underscore-dangle": 'off',
29 | 'max-len': 'off',
30 | 'import/no-cycle': 'off',
31 |
32 | /**
33 | * Lỗi ngoại trừ :
34 | * a || b
35 | * a && b()
36 | * a() || (b = c)
37 | * a ? b() : c()
38 | * a ? b() || (c = d) : e()
39 | */
40 | "no-unused-expressions": "off",
41 | "@typescript-eslint/no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }],
42 |
43 | /**
44 | * Cho phép ngắt dòng ( string dom )
45 | */
46 | "operator-linebreak": "off",
47 |
48 | /**
49 | * For mutations VueX
50 | *
51 | * setCart(state, payload) {
52 | * state.errorMessage = false;
53 | * state.shoppingCart = payload;
54 | * return state;
55 | },
56 | */
57 | "no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["state"] }],
58 | "no-shadow": "off",
59 | /**
60 | * Ignore this vue lifecycle
61 | */
62 | "class-methods-use-this": [
63 | "error",
64 | { "exceptMethods": [
65 | "beforeCreate",
66 | "created",
67 | "beforeMount",
68 | "mounted",
69 | "beforeUpdate",
70 | "updated",
71 | "beforeDestroy",
72 | "destroyed"
73 | ]
74 | }
75 | ],
76 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
77 | },
78 | };
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | thumb.db
3 | *.zip
4 | *.rar
5 | .DS_Store
6 | npm-debug.log
7 | debug.log
8 | .env
9 | dist/
10 | dist/config.yml
11 | dist/assets/app.js
12 | dist/assets/main.css
13 | .vscode
14 |
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | {
2 | "husky": {
3 | "hooks": {
4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ignoreFiles": [
3 | "src/styles/vendors/*.scss",
4 | ],
5 | "rules": {
6 | "max-nesting-depth": 2,
7 | "max-empty-lines": 2,
8 | "color-hex-case": "lower",
9 | "comment-empty-line-before": ["always", {
10 | "except": ["first-nested"],
11 | "ignore": ["after-comment", "stylelint-commands"]
12 | }]
13 | }
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | - Tạo Global component không cần import + chạy được ở design mode
2 |
--------------------------------------------------------------------------------
/build-utils/addons/webpack.bundleanalyzer.js:
--------------------------------------------------------------------------------
1 | const WebpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
2 |
3 | module.exports = {
4 | plugins: [
5 | new WebpackBundleAnalyzer(),
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/build-utils/common-path.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | /**
5 | * Input path
6 | */
7 | srcPath: path.resolve(__dirname, '../src'),
8 |
9 | /**
10 | * Folder path
11 | */
12 | componentsPath: path.resolve(__dirname, '../src/components'),
13 | helpersPath: path.resolve(__dirname, '../src/helpers'),
14 | stylesPath: path.resolve(__dirname, '../src/styles'),
15 | typesPath: path.resolve(__dirname, '../src/types'),
16 | vuePath: path.resolve(__dirname, '../src/vue'),
17 | themeDevPath: path.resolve(__dirname, '../shopify'), // Shopify structure
18 |
19 | /**
20 | * Output path
21 | */
22 | outputPath: path.resolve(__dirname, '../dist'),
23 |
24 | };
25 |
--------------------------------------------------------------------------------
/build-utils/common-plugins/copy-plugin.js:
--------------------------------------------------------------------------------
1 | const CopyPlugin = require("copy-webpack-plugin");
2 | const path = require('path');
3 | const commonPath = require('../common-path');
4 |
5 | module.exports = {
6 | huwngCopyPlugin: new CopyPlugin({
7 | patterns: [
8 | /**
9 | * Các folder có json, không lấy folder con
10 | */
11 | {
12 | from: path.resolve(__dirname, commonPath.themeDevPath, 'config/*.json'),
13 | to: path.resolve(__dirname, commonPath.outputPath, 'config/[name].[ext]'),
14 | },
15 | {
16 | from: path.resolve(__dirname, commonPath.themeDevPath, 'locales/*.json'),
17 | to: path.resolve(__dirname, commonPath.outputPath, 'locales/[name].[ext]'),
18 | },
19 | /**
20 | * Các folder có liquid, có lấy folder con
21 | */
22 | {
23 |
24 | from: path.resolve(__dirname, commonPath.themeDevPath, 'layout/**/*.liquid'),
25 | to: path.resolve(__dirname, commonPath.outputPath, 'layout/[name].[ext]'),
26 | },
27 | {
28 | from: path.resolve(__dirname, commonPath.themeDevPath, 'sections/**/*.liquid'),
29 | to: path.resolve(__dirname, commonPath.outputPath, 'sections/[name].[ext]'),
30 | },
31 | {
32 | from: path.resolve(__dirname, commonPath.themeDevPath, 'snippets/**/*.liquid'),
33 | to: path.resolve(__dirname, commonPath.outputPath, 'snippets/[name].[ext]'),
34 | },
35 | /**
36 | * Folder này cứ để nguyên xi
37 | */
38 | {
39 | from: path.resolve(__dirname, commonPath.themeDevPath, 'templates'),
40 | to: path.resolve(__dirname, commonPath.outputPath, 'templates'),
41 | },
42 | {
43 | from: path.resolve(__dirname, commonPath.themeDevPath, 'assets'),
44 | to: path.resolve(__dirname, commonPath.outputPath, 'assets'),
45 | }
46 | // ,
47 | // /**
48 | // * SCSS in section ( inside ./theme folder )
49 | // */
50 | // {
51 | // from: path.resolve(__dirname, commonPath.themeDevPath, '**/*.scss'),
52 | // to: path.resolve(__dirname, commonPath.themeDevPath, 'sections/scss/[name].[ext]'),
53 | // },
54 | // /**
55 | // * TS in section ( inside ./theme folder )
56 | // */
57 | // {
58 | // from: path.resolve(__dirname, commonPath.themeDevPath, '**/*.ts'),
59 | // to: path.resolve(__dirname, commonPath.themeDevPath, 'sections/ts/[name].[ext]'),
60 | // }
61 | ],
62 | }),
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/build-utils/common-plugins/replace-in-file-plugin.js:
--------------------------------------------------------------------------------
1 | const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
2 | const path = require('path');
3 | const commonPath = require('../common-path');
4 | const all = require('../markup/index');
5 |
6 | module.exports = {
7 | huwngReplacePlugin: new ReplaceInFileWebpackPlugin(all.allReplace),
8 | };
9 |
10 |
--------------------------------------------------------------------------------
/build-utils/markup/className.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Classname common markup
3 | *
4 | */
5 | module.exports = {
6 | className: [{
7 | dir: 'dist',
8 | test: [/\.css$/, /\.liquid/],
9 | rules: [{
10 | search: '@IMG_HOVER_ZOOM',
11 | replace: 'xo-img--is-zoom'
12 | },
13 | {
14 | search: '@EFFECT_ROTATE',
15 | replace: 'xo-effect--is-rotate'
16 | },
17 | {
18 | search: '@EFFECT_MOVE_TO',
19 | replace: 'xo-effect--moveto'
20 | },
21 | ]
22 | }],
23 | }
24 |
--------------------------------------------------------------------------------
/build-utils/markup/html.js:
--------------------------------------------------------------------------------
1 | /**
2 | * HTML ( base on Liquid ) common markup
3 | *
4 | */
5 | // test: [/\.css$/, /\.liquid/],
6 |
7 | const FULL_HEADING = {
8 | liquid: `
9 | {% case section.settings.align_header %}
10 | {% when 'left' %}
11 | {% assign align_class = 'text-left' %}
12 | {% when 'center' %}
13 | {% assign align_class = 'text-center' %}
14 | {% when 'right' %}
15 | {% assign align_class = 'text-right' %}
16 | {% else %}
17 |
18 | {% endcase %}
19 |
20 |
31 | `,
32 |
33 | jsonSchemaSetting: `
34 | {
35 | "type" : "header",
36 | "content": "Section header"
37 | },
38 | {
39 | "type": "text",
40 | "id": "heading",
41 | "label": "Heading",
42 | "default": "Heading of section"
43 | },
44 | {
45 | "type": "textarea",
46 | "id": "subheading",
47 | "label": "Sub heading",
48 | "default": "Sub heading of section"
49 | },
50 | {
51 | "type": "select",
52 | "id": "align_header",
53 | "label": "Align Header",
54 | "options": [
55 | { "value": "left", "label": "Left" },
56 | { "value": "center", "label": "Center" },
57 | { "value": "right", "label": "Right" }
58 | ],
59 | "default": "center"
60 | }
61 | `
62 | };
63 |
64 | module.exports = {
65 | html: [{
66 | dir: 'dist',
67 | test: [/\.liquid/],
68 | rules: [{
69 | search: '@HTML_FULL_HEADING',
70 | replace: FULL_HEADING.liquid
71 | }, {
72 | search: '@SETTING_FULL_HEADING',
73 | replace: FULL_HEADING.jsonSchemaSetting
74 | }]
75 | }]
76 | }
77 |
--------------------------------------------------------------------------------
/build-utils/markup/index.js:
--------------------------------------------------------------------------------
1 | const htmlReplace = require('./html');
2 | const classReplace = require('./className');
3 |
4 | let boutiqueReplace = [
5 | ...classReplace.className,
6 | ...htmlReplace.html,
7 | ];
8 |
9 | module.exports = {
10 | allReplace: [...boutiqueReplace]
11 | }
12 |
--------------------------------------------------------------------------------
/build-utils/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const commonPath = require('./common-path');
3 | const TerserJSPlugin = require('terser-webpack-plugin');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
6 | const { VueLoaderPlugin } = require('vue-loader')
7 | const StyleLintPlugin = require('stylelint-webpack-plugin');
8 | const copyPlugin = require('./common-plugins/copy-plugin');
9 | const replacePlugin = require('./common-plugins/replace-in-file-plugin');
10 |
11 | const hugCommonConfig = {
12 | name: 'ShopiyThemeStarter',
13 | entry: './src/index.ts',
14 | output: {
15 | path: commonPath.outputPath,
16 | filename: 'assets/app.js',
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.js$/,
22 | exclude: /node_modules/,
23 | use: [
24 | {
25 | loader: 'babel-loader',
26 | },
27 | 'webpack-import-glob-loader' /** @see https://www.npmjs.com/package/import-glob-loader */
28 | ]
29 | },
30 | {
31 | test: /\.ts?$/,
32 | loader: 'ts-loader',
33 | exclude: /node_modules/,
34 | options: {
35 | appendTsSuffixTo: [/\.vue$/],
36 | },
37 | },
38 | {
39 | test: /\.vue$/,
40 | loader: 'vue-loader',
41 | options: {
42 | loaders: {
43 | 'scss': 'vue-style-loader!css-loader!sass-loader',
44 | 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
45 | },
46 | },
47 | },
48 | {
49 | test: /\.(s*)css$/,
50 | use: [
51 | MiniCssExtractPlugin.loader,
52 | {
53 | loader: 'css-loader',
54 | options: {
55 | importLoaders: 2,
56 | sourceMap: false,
57 | },
58 | },
59 | {
60 | loader: 'postcss-loader',
61 | options: {
62 | plugins: () => [require('autoprefixer')],
63 | sourceMap: false,
64 | },
65 | },
66 | {
67 | loader: 'sass-loader',
68 | options: {
69 | sourceMap: false,
70 | },
71 | },
72 | 'webpack-import-glob-loader' /** @see https://www.npmjs.com/package/import-glob-loader */
73 | ],
74 | },
75 | // {
76 | // test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg)(\?[a-z0-9=.]+)?$/,
77 | // loader: 'url-loader?limit=100000'
78 | // }
79 | ],
80 | },
81 | optimization: {
82 | minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
83 | },
84 | plugins: [
85 | new MiniCssExtractPlugin({
86 | filename: 'assets/main.css',
87 | }),
88 | new VueLoaderPlugin(),
89 | new StyleLintPlugin({
90 | configFile: '.stylelintrc',
91 | context: 'src',
92 | files: '**/*.(s(c|a)ss|css)',
93 | failOnError: false,
94 | quiet: false,
95 | emitErrors: true
96 | }),
97 | copyPlugin.huwngCopyPlugin,
98 | replacePlugin.huwngReplacePlugin,
99 | ],
100 | resolve: {
101 | extensions: ['.vue', '.ts', '.js', '.json'],
102 | alias: {
103 | vue: 'vue/dist/vue.esm-bundler.js',
104 | Components: commonPath.componentsPath,
105 | Helpers: commonPath.helpersPath,
106 | Styles: commonPath.stylesPath,
107 | Shopify: commonPath.themeDevPath,
108 | Types: commonPath.typesPath,
109 | Vue: commonPath.vuePath,
110 | }
111 | },
112 | stats: {
113 | entrypoints: false,
114 | children: false,
115 | }
116 | };
117 |
118 | module.exports = { hugCommonConfig }
119 |
--------------------------------------------------------------------------------
/build-utils/webpack.dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'development',
3 | devtool: 'inline-source-map',
4 | devServer: {
5 | contentBase: './dist',
6 | hot: false,
7 | watchContentBase: true,
8 | },
9 | watch: true,
10 | /**
11 | * Tells stats whether to add information about the built modules.
12 | * @see {@link https://webpack.js.org/configuration/stats/}
13 | */
14 | stats: {
15 | excludeAssets: [
16 | /.liquid/,
17 | /.json/,
18 | /.svg/,
19 | /.min.*/,
20 | /.png/,
21 | /.gif/
22 | ],
23 | modules: false
24 | },
25 | };
26 |
27 |
--------------------------------------------------------------------------------
/build-utils/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | optimization: {
6 | minimizer: [
7 | new CssMinimizerPlugin(),
8 | ],
9 | },
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {extends: ['@commitlint/config-conventional']}
2 |
--------------------------------------------------------------------------------
/docs/assets/images/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/docs/assets/images/icons.png
--------------------------------------------------------------------------------
/docs/assets/images/icons@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/docs/assets/images/icons@2x.png
--------------------------------------------------------------------------------
/docs/assets/images/widgets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/docs/assets/images/widgets.png
--------------------------------------------------------------------------------
/docs/assets/images/widgets@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/docs/assets/images/widgets@2x.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "roots": [
3 | "/src"
4 | ],
5 |
6 | "setupFilesAfterEnv": ['/jest.setup.ts'],
7 |
8 | /**
9 | * Xác định nơi bỏ các file testing
10 | * Thông thuòng ra sẽ bỏ các file typescript vào hết thư mục src
11 | */
12 | "testMatch": [
13 | "**/__tests__/**/*.+(ts|tsx|js)",
14 | "**/?(*.)+(spec|test).+(ts|tsx|js)"
15 | ],
16 |
17 | /**
18 | * Jest sẽ dựa định dạng này để phát hiện các file cần được testing
19 | */
20 | "transform": {
21 | "^.+\\.(ts|tsx|vue)$": "ts-jest"
22 | },
23 |
24 | /**
25 | * Thằng ts-jest sẽ xác định các file có dạng này
26 | * Sau đó sẽ biến đổi về dạng nó có thể hiểu được để chạy jest
27 | */
28 | "verbose": true,
29 |
30 | /**
31 | * Báo cáo các bài test lúc đang chạy
32 | */
33 | "globals": {
34 | "ts-jest": {
35 | diagnostics: false
36 | }
37 | }
38 | /**
39 | * Cái này để các hàm của thằng jest trở thành globals
40 | * không cần phải require hay import khi dùng nữa
41 | */
42 | }
43 |
--------------------------------------------------------------------------------
/jest.setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom'
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xo-boutique",
3 | "version": "1.0.0",
4 | "description": "HungDz",
5 | "main": "webpack.common.js",
6 | "scripts": {
7 | "dev": "npm run build -- --env.env=dev",
8 | "build:prod": "npm run build -- --env.env=prod",
9 | "build": "webpack",
10 | "build:dev:bundleanalyzer": "npm run build -- --env.env=dev --env.addons=bundleanalyzer",
11 | "build:prod:bundleanalyzer": "npm run build -- --env.env=prod --env.addons=bundleanalyzer",
12 | "docs": "npx typedoc",
13 | "test": "jest --detectOpenHandles",
14 | "lint:js": "eslint --ext \".ts,.vue\" --ignore-path .gitignore .",
15 | "lint:style": "stylelint \"**/*.{vue,css}\" --ignore-path .gitignore",
16 | "lint": "npm run lint:js && npm run lint:style"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git@gitlab.com:xopify-themes/xo-boutique.git"
21 | },
22 | "husky": {
23 | "hooks": {
24 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
25 | }
26 | },
27 | "_moduleAliases": {
28 | "@components": "dist",
29 | "@helpers": "dist"
30 | },
31 | "keywords": [],
32 | "author": "",
33 | "license": "ISC",
34 | "bugs": {
35 | "url": "https://gitlab.com/xopify-themes/xo-boutique/-/issues"
36 | },
37 | "homepage": "https://gitlab.com/xopify-themes/xo-boutique",
38 | "devDependencies": {
39 | "@babel/core": "^7.5.5",
40 | "@babel/plugin-proposal-class-properties": "^7.12.1",
41 | "@babel/plugin-proposal-decorators": "^7.12.12",
42 | "@babel/preset-env": "^7.5.5",
43 | "@commitlint/cli": "^11.0.0",
44 | "@commitlint/config-conventional": "^11.0.0",
45 | "@shopify/theme-currency": "^4.1.1",
46 | "@shopify/theme-predictive-search": "^4.1.1",
47 | "@testing-library/jest-dom": "^5.11.9",
48 | "@types/google.maps": "^3.44.2",
49 | "@types/googlemaps": "^3.43.3",
50 | "@types/jest": "^26.0.20",
51 | "@types/swiper": "^5.4.2",
52 | "@typescript-eslint/eslint-plugin": "^4.14.1",
53 | "@typescript-eslint/parser": "^4.14.1",
54 | "@vue/compiler-sfc": "^3.1.2",
55 | "autoprefixer": "^9.7.1",
56 | "babel-loader": "^8.0.6",
57 | "babel-polyfill": "^6.26.0",
58 | "copy-webpack-plugin": "^6.2.1",
59 | "css-loader": "^3.2.0",
60 | "css-minimizer-webpack-plugin": "^1.2.0",
61 | "eslint": "^6.8.0",
62 | "eslint-config-airbnb": "^18.0.1",
63 | "eslint-config-airbnb-typescript": "^12.0.0",
64 | "eslint-import-resolver-alias": "^1.1.2",
65 | "eslint-plugin-eslint-comments": "^3.2.0",
66 | "eslint-plugin-import": "^2.22.1",
67 | "eslint-plugin-vue": "^7.12.1",
68 | "husky": "^4.3.8",
69 | "jest": "^26.6.3",
70 | "jsdoc": "^3.6.6",
71 | "mini-css-extract-plugin": "^0.9.0",
72 | "module-alias": "^2.2.2",
73 | "node-sass": "^4.12.0",
74 | "optimize-css-assets-webpack-plugin": "^5.0.3",
75 | "postcss-loader": "^3.0.0",
76 | "replace-in-file-webpack-plugin": "^1.0.6",
77 | "sass-loader": "^7.1.0",
78 | "style-loader": "^1.0.0",
79 | "stylelint": "^13.9.0",
80 | "stylelint-config-sass-guidelines": "^7.1.0",
81 | "stylelint-config-standard": "^20.0.0",
82 | "stylelint-webpack-plugin": "^2.1.1",
83 | "terser-webpack-plugin": "^2.2.1",
84 | "ts-jest": "^26.5.0",
85 | "ts-loader": "^7.0.5",
86 | "typedoc": "^0.20.32",
87 | "typescript": "^3.9.7",
88 | "url-loader": "^2.1.0",
89 | "vue-debounce-decorator": "^1.0.1",
90 | "vue-loader": "^16.3.0",
91 | "vue-style-loader": "^4.1.2",
92 | "webpack": "^4.38.0",
93 | "webpack-bundle-analyzer": "^4.4.0",
94 | "webpack-cli": "^3.3.6",
95 | "webpack-import-glob-loader": "^1.6.3",
96 | "webpack-merge": "^4.2.2"
97 | },
98 | "dependencies": {
99 | "swiper": "^6.5.1",
100 | "vue": "^3.1.2",
101 | "vue-class-component": "^7.2.6",
102 | "vue-property-decorator": "^9.1.2",
103 | "vuex": "^3.6.2",
104 | "vuex-module-decorators": "^1.0.1"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/shopify/assets/cart-notification.js:
--------------------------------------------------------------------------------
1 | class CartNotification extends HTMLElement {
2 | constructor() {
3 | super();
4 |
5 | this.notification = document.getElementById('cart-notification');
6 | this.header = document.querySelector('sticky-header');
7 | this.onBodyClick = this.handleBodyClick.bind(this);
8 |
9 | this.notification.addEventListener('keyup', (evt) => evt.code === 'Escape' && this.close());
10 | this.querySelectorAll('button[type="button"]').forEach((closeButton) =>
11 | closeButton.addEventListener('click', this.close.bind(this))
12 | );
13 | }
14 |
15 | open() {
16 | this.notification.classList.add('animate', 'active');
17 |
18 | this.notification.addEventListener('transitionend', () => {
19 | this.notification.focus();
20 | trapFocus(this.notification);
21 | }, { once: true });
22 |
23 | document.body.addEventListener('click', this.onBodyClick);
24 | }
25 |
26 | close() {
27 | this.notification.classList.remove('active');
28 |
29 | document.body.removeEventListener('click', this.onBodyClick);
30 |
31 | removeTrapFocus(this.activeElement);
32 | }
33 |
34 | renderContents(parsedState) {
35 | this.productId = parsedState.id;
36 | this.getSectionsToRender().forEach((section => {
37 | document.getElementById(section.id).innerHTML =
38 | this.getSectionInnerHTML(parsedState.sections[section.id], section.selector);
39 | }));
40 |
41 | this.header?.reveal();
42 | this.open();
43 | }
44 |
45 | getSectionsToRender() {
46 | return [
47 | {
48 | id: 'cart-notification-product',
49 | selector: `#cart-notification-product-${this.productId}`,
50 | },
51 | {
52 | id: 'cart-notification-button'
53 | },
54 | {
55 | id: 'cart-icon-bubble'
56 | }
57 | ];
58 | }
59 |
60 | getSectionInnerHTML(html, selector = '.shopify-section') {
61 | return new DOMParser()
62 | .parseFromString(html, 'text/html')
63 | .querySelector(selector).innerHTML;
64 | }
65 |
66 | handleBodyClick(evt) {
67 | const target = evt.target;
68 | if (target !== this.notification && !target.closest('cart-notification')) {
69 | const disclosure = target.closest('details-disclosure');
70 | this.activeElement = disclosure ? disclosure.querySelector('summary') : null;
71 | this.close();
72 | }
73 | }
74 |
75 | setActiveElement(element) {
76 | this.activeElement = element;
77 | }
78 | }
79 |
80 | customElements.define('cart-notification', CartNotification);
81 |
--------------------------------------------------------------------------------
/shopify/assets/component-accordion.css:
--------------------------------------------------------------------------------
1 | .accordion summary {
2 | display: flex;
3 | position: relative;
4 | line-height: 1;
5 | padding: 1.5rem 0;
6 | }
7 |
8 | .accordion .summary__title {
9 | display: flex;
10 | flex: 1;
11 | }
12 |
13 | .accordion + .accordion {
14 | margin-top: 0;
15 | border-top: none;
16 | }
17 |
18 | .accordion {
19 | margin-top: 2.5rem;
20 | margin-bottom: 0;
21 | border-top: 0.1rem solid var(--color-foreground-20);
22 | border-bottom: 0.1rem solid var(--color-foreground-20);
23 | }
24 |
25 | .accordion__title {
26 | display: inline-block;
27 | max-width: calc(100% - 6rem);
28 | min-height: 1.6rem;
29 | margin: 0;
30 | word-break: break-word;
31 | }
32 |
33 | .accordion .icon-accordion {
34 | align-self: center;
35 | min-width: 1.6rem;
36 | margin-right: 1rem;
37 | fill: var(--color-foreground);
38 | }
39 |
40 | .accordion details[open] > summary .icon-caret {
41 | transform: rotate(180deg);
42 | }
43 |
44 | .accordion__content {
45 | margin-bottom: 1.5rem;
46 | word-break: break-word;
47 | }
48 |
49 | .accordion__content img {
50 | max-width: 100%;
51 | }
52 |
--------------------------------------------------------------------------------
/shopify/assets/component-article-card.css:
--------------------------------------------------------------------------------
1 | .articles-wrapper.grid {
2 | margin: 0 0 5rem 0;
3 | }
4 |
5 | @media screen and (min-width: 750px) {
6 | .articles-wrapper.grid {
7 | margin-bottom: 7rem;
8 | }
9 | }
10 |
11 | .articles-wrapper .article {
12 | max-width: 100%;
13 | }
14 |
15 | @media screen and (max-width: 749px) {
16 | .articles-wrapper .article {
17 | width: 100%;
18 | }
19 | }
20 |
21 | .article {
22 | display: flex;
23 | align-items: center;
24 | }
25 |
26 | .article.grid__item {
27 | padding: 0;
28 | }
29 |
30 | .article-card {
31 | background-color: var(--color-foreground-4);
32 | align-self: flex-start;
33 | flex: 0 1 100%;
34 | display: flex;
35 | align-items: flex-start;
36 | height: 100%;
37 | }
38 |
39 | .grid--peek .article-card {
40 | box-sizing: border-box;
41 | }
42 |
43 | .article-card__info {
44 | padding: 2.5rem 2.5rem 3rem;
45 | display: flex;
46 | flex-direction: column;
47 | flex-grow: 1;
48 | }
49 |
50 | @media screen and (min-width: 750px) {
51 | .article-card__info {
52 | padding: 4rem 5rem;
53 | }
54 | }
55 |
56 | .article-content {
57 | width: 100%;
58 | height: 100%;
59 | display: flex;
60 | flex-direction: column;
61 | text-decoration: none;
62 | color: inherit;
63 | }
64 |
65 | .article-content:hover .article-card__title {
66 | text-decoration: underline;
67 | text-underline-offset: 0.3rem;
68 | }
69 |
70 | .article-card__image {
71 | overflow: hidden;
72 | }
73 |
74 | .article-content img {
75 | transition: transform var(--duration-default) ease;
76 | }
77 |
78 | .article-content:hover img {
79 | transform: scale(1.07);
80 | }
81 |
82 | .article-card__image-wrapper > a {
83 | display: block;
84 | }
85 |
86 | .article-card__title {
87 | text-decoration: none;
88 | word-break: break-word;
89 | }
90 |
91 | .article-card__link.link {
92 | padding: 0;
93 | }
94 |
95 | .article-card__link {
96 | text-underline-offset: 0.3rem;
97 | }
98 |
99 | .article-content:hover .article-card__link {
100 | text-decoration-thickness: 0.2rem;
101 | }
102 |
103 | .article-card__header h2 {
104 | margin: 0;
105 | }
106 |
107 | .article-card__header h2:not(:first-child) {
108 | margin-top: 1rem;
109 | }
110 |
111 | .article-card__footer {
112 | letter-spacing: 0.1rem;
113 | font-size: 1.4rem;
114 | }
115 |
116 | .article-card__footer:not(:last-child) {
117 | margin-bottom: 1rem;
118 | }
119 |
120 | .article-card__footer:last-child {
121 | margin-top: auto;
122 | }
123 |
124 | .article-card__link:not(:only-child) {
125 | margin-right: 3rem;
126 | }
127 |
128 | @media screen and (min-width: 990px) {
129 | .article-card__link:not(:only-child) {
130 | margin-right: 4rem;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/shopify/assets/component-badge.css:
--------------------------------------------------------------------------------
1 | .badge {
2 | border: 1px solid transparent;
3 | border-radius: 4rem;
4 | display: inline-block;
5 | font-size: 1.2rem;
6 | letter-spacing: 0.1rem;
7 | line-height: 1;
8 | padding: 0.6rem 1.6rem;
9 | text-align: center;
10 | background-color: var(--color-badge-background);
11 | border-color: var(--color-badge-border);
12 | color: var(--color-foreground);
13 | word-break: break-word;
14 | }
15 |
--------------------------------------------------------------------------------
/shopify/assets/component-cart-notification.css:
--------------------------------------------------------------------------------
1 | .cart-notification-wrapper {
2 | position: relative;
3 | }
4 |
5 | .cart-notification-wrapper .cart-notification {
6 | display: block;
7 | }
8 |
9 | .cart-notification {
10 | background-color: var(--color-background);
11 | border-color: var(--color-foreground-20);
12 | border-style: solid;
13 | border-width: 0 0 0.1rem;
14 | padding: 2.5rem 3.5rem;
15 | position: absolute;
16 | right: 0;
17 | transform: translateY(-100%);
18 | visibility: hidden;
19 | width: 100%;
20 | z-index: -1;
21 | }
22 |
23 | @media screen and (min-width: 750px) {
24 | .cart-notification {
25 | border-width: 0 0.1rem 0.1rem;
26 | max-width: 36.8rem;
27 | right: 4rem;
28 | }
29 | }
30 |
31 | .cart-notification.animate {
32 | transition: transform var(--duration-short) ease,
33 | visibility 0s var(--duration-short) ease;
34 | }
35 |
36 | .cart-notification.active {
37 | transform: translateY(0);
38 | transition: transform var(--duration-default) ease, visibility 0s;
39 | visibility: visible;
40 | }
41 |
42 | .cart-notification__header {
43 | align-items: flex-start;
44 | display: flex;
45 | }
46 |
47 | .cart-notification__heading {
48 | align-items: center;
49 | display: flex;
50 | flex-grow: 1;
51 | margin-bottom: 0;
52 | margin-top: 0;
53 | }
54 |
55 | .cart-notification__heading .icon-checkmark {
56 | color: var(--color-foreground);
57 | margin-right: 1rem;
58 | width: 1.3rem;
59 | }
60 |
61 | .cart-notification__close {
62 | margin-top: -2rem;
63 | margin-right: -3rem;
64 | }
65 |
66 | .cart-notification__links {
67 | text-align: center;
68 | }
69 |
70 | .cart-notification__links > * {
71 | margin-top: 1rem;
72 | }
73 |
74 | .cart-notification-product {
75 | align-items: flex-start;
76 | display: flex;
77 | padding-bottom: 3rem;
78 | padding-top: 2rem;
79 | }
80 |
81 | .cart-notification-product dl {
82 | margin-bottom: 0;
83 | margin-top: 0;
84 | }
85 |
86 | .cart-notification-product__image {
87 | border: 0.1rem solid var(--color-foreground-3);
88 | margin-right: 1.5rem;
89 | }
90 |
91 | .cart-notification-product__name {
92 | margin-bottom: 0;
93 | margin-top: 0;
94 | }
95 |
96 | .cart-notification-product__option {
97 | color: var(--color-foreground-70);
98 | margin-top: 1rem;
99 | }
100 |
101 | .cart-notification-product__option + .cart-notification-product__option {
102 | margin-top: 0.5rem;
103 | }
104 |
105 | .cart-notification-product__option > * {
106 | display: inline-block;
107 | margin: 0;
108 | }
109 |
--------------------------------------------------------------------------------
/shopify/assets/component-cart.css:
--------------------------------------------------------------------------------
1 | .cart {
2 | position: relative;
3 | display: block;
4 | }
5 |
6 | .cart__empty-text,
7 | .is-empty .cart__contents,
8 | cart-items.is-empty .title-wrapper-with-link,
9 | .is-empty .cart__footer {
10 | display: none;
11 | }
12 |
13 | .is-empty .cart__empty-text,
14 | .is-empty .cart__warnings {
15 | display: block;
16 | }
17 |
18 | .cart__warnings {
19 | display: none;
20 | text-align: center;
21 | padding: 7rem 0;
22 | }
23 |
24 | .cart__empty-text {
25 | margin: 4.5rem 0 5.5rem;
26 | }
27 |
28 | .cart__contents > * + * {
29 | margin-top: 2.5rem;
30 | }
31 |
32 | @media screen and (min-width: 990px) {
33 | .cart__warnings {
34 | padding: 10rem 0 15rem;
35 | }
36 |
37 | .cart__empty-text {
38 | margin: 5rem 0 6rem;
39 | }
40 | }
41 |
42 | cart-items {
43 | display: block;
44 | }
45 |
46 | .cart__items {
47 | position: relative;
48 | padding-bottom: 3rem;
49 | border-bottom: 0.1rem solid var(--color-foreground-20);
50 | }
51 |
52 | .cart__items--disabled {
53 | pointer-events: none;
54 | }
55 |
56 | .cart__footer {
57 | padding: 4rem 0 0;
58 | }
59 |
60 | .cart__footer-wrapper:last-child .cart__footer {
61 | padding-bottom: 5rem;
62 | }
63 |
64 | .cart__footer > div:only-child {
65 | margin-left: auto;
66 | }
67 |
68 | .cart__footer > * + * {
69 | margin-top: 4rem;
70 | }
71 |
72 | .cart__footer .discounts {
73 | margin-top: 1rem;
74 | }
75 |
76 | .cart__note {
77 | display: block;
78 | }
79 |
80 | .cart__note label {
81 | display: flex;
82 | align-items: flex-end;
83 | line-height: 1;
84 | height: 1.8rem;
85 | margin-bottom: 2rem;
86 | color: var(--color-foreground-75);
87 | }
88 |
89 | .cart__note .field__input {
90 | padding: 1rem;
91 | }
92 |
93 | @media screen and (min-width: 750px) {
94 | .cart__items {
95 | grid-column-start: 1;
96 | grid-column-end: 3;
97 | padding-bottom: 4rem;
98 | margin-bottom: 4rem;
99 | }
100 |
101 | .cart__contents > * + * {
102 | margin-top: 0;
103 | }
104 |
105 | .cart__items + .cart__footer {
106 | grid-column: 2;
107 | }
108 |
109 | .cart__footer {
110 | display: flex;
111 | justify-content: space-between;
112 | border: 0;
113 | }
114 |
115 | .cart__footer-wrapper:last-child {
116 | padding-top: 0;
117 | }
118 |
119 | .cart__footer > * {
120 | width: 35rem;
121 | }
122 |
123 | .cart__footer > * + * {
124 | margin-left: 4rem;
125 | margin-top: 0;
126 | }
127 | }
128 |
129 | .cart__ctas button {
130 | width: 100%;
131 | }
132 |
133 | .cart__ctas > *:not(noscript:first-child) + * {
134 | margin-top: 1rem;
135 | }
136 |
137 | .cart__update-button {
138 | margin-bottom: 1rem;
139 | }
140 |
141 | .cart__dynamic-checkout-buttons {
142 | margin-top: 0;
143 | }
144 |
145 | .cart__dynamic-checkout-buttons div[role='button'] {
146 | border-radius: 0 !important;
147 | }
148 |
149 | .cart-note__label {
150 | display: inline-block;
151 | margin-bottom: 1rem;
152 | line-height: 2;
153 | }
154 |
155 | .tax-note {
156 | margin: 2.2rem 0 1.6rem auto;
157 | text-align: center;
158 | display: block;
159 | }
160 |
161 | .cart__checkout-button {
162 | max-width: 36rem;
163 | }
164 |
165 | .cart__ctas {
166 | text-align: center;
167 | }
168 |
169 | @media screen and (min-width: 750px) {
170 | .cart-note {
171 | max-width: 35rem;
172 | }
173 |
174 | .cart__update-button {
175 | margin-bottom: 0;
176 | margin-right: 0.8rem;
177 | }
178 |
179 | .cart__dynamic-checkout-buttons {
180 | margin-top: 1rem;
181 | }
182 |
183 | .tax-note {
184 | margin-bottom: 2.2rem;
185 | text-align: right;
186 | }
187 |
188 | [data-shopify-buttoncontainer] {
189 | justify-content: flex-end;
190 | }
191 |
192 | .cart__ctas {
193 | display: flex;
194 | gap: 1rem;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/shopify/assets/component-collection-hero.css:
--------------------------------------------------------------------------------
1 | .collection-hero {
2 | margin-bottom: 2rem;
3 | }
4 |
5 | .collection-hero--with-image {
6 | background-color: var(--color-foreground-4);
7 | }
8 |
9 | .collection-hero__inner {
10 | display: flex;
11 | flex-direction: column;
12 | padding-bottom: 2rem;
13 | margin-bottom: 2rem;
14 | }
15 |
16 | @media screen and (min-width: 750px) {
17 | .collection-hero.collection-hero--with-image {
18 | padding: 4rem 0 4rem;
19 | }
20 | }
21 |
22 | .collection-hero__text-wrapper {
23 | flex-basis: 100%;
24 | }
25 |
26 | .collection-hero--with-image .collection-hero__inner {
27 | margin-bottom: 4rem;
28 | }
29 |
30 | @media screen and (min-width: 750px) {
31 | .collection-hero {
32 | padding: 0 0 2rem;
33 | margin-bottom: 0;
34 | }
35 |
36 | .collection-hero--with-image {
37 | margin-bottom: 4.5rem;
38 | }
39 |
40 | .collection-hero__inner {
41 | align-items: center;
42 | flex-direction: row;
43 | padding-bottom: 0;
44 | margin-bottom: 0;
45 | }
46 |
47 | .collection-hero--with-image .collection-hero__inner {
48 | margin-bottom: 0;
49 | }
50 | }
51 |
52 | .collection-hero__title {
53 | margin: 5rem 0 0;
54 | }
55 |
56 | .collection-hero__title + .collection-hero__description {
57 | margin-top: 1.5rem;
58 | font-size: 1.6rem;
59 | line-height: 1.5;
60 | }
61 |
62 | @media screen and (min-width: 750px) {
63 | .collection-hero__title + .collection-hero__description {
64 | font-size: 1.8rem;
65 | margin-top: 2rem;
66 | }
67 |
68 | .collection-hero__description {
69 | max-width: 66.67%;
70 | }
71 |
72 | .collection-hero--with-image .collection-hero__description {
73 | max-width: 100%;
74 | }
75 | }
76 |
77 | .collection-hero--with-image .collection-hero__title {
78 | margin: 0;
79 | }
80 |
81 | .collection-hero--with-image .collection-hero__text-wrapper {
82 | padding: 5rem 0 4rem;
83 | }
84 |
85 | @media screen and (max-width: 749px) {
86 | .collection-hero__image-container {
87 | height: 20rem;
88 | }
89 | }
90 |
91 | @media screen and (min-width: 750px) {
92 | .collection-hero--with-image .collection-hero__text-wrapper {
93 | padding: 4rem 2rem 4rem 0;
94 | flex-basis: 50%;
95 | }
96 |
97 | .collection-hero__image-container {
98 | align-self: stretch;
99 | flex: 1 0 50%;
100 | margin-left: 3rem;
101 | min-height: 20rem;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/shopify/assets/component-deferred-media.css:
--------------------------------------------------------------------------------
1 | .deferred-media__poster {
2 | background-color: transparent;
3 | border: none;
4 | cursor: pointer;
5 | margin: 0;
6 | padding: 0;
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | .media > .deferred-media__poster {
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | }
16 |
17 | .deferred-media__poster img {
18 | width: auto;
19 | height: 100%;
20 | }
21 |
22 | .deferred-media {
23 | overflow: hidden;
24 | }
25 |
26 | .deferred-media:not([loaded]) template {
27 | z-index: -1;
28 | }
29 |
30 | .deferred-media[loaded] > .deferred-media__poster {
31 | display: none;
32 | }
33 |
34 | .deferred-media__poster:focus {
35 | outline-offset: -0.3rem;
36 | }
37 |
38 | .deferred-media__poster-button {
39 | background-color: var(--color-background);
40 | border: 0.1rem solid var(--color-foreground-10);
41 | border-radius: 50%;
42 | color: var(--color-foreground);
43 | display: flex;
44 | align-items: center;
45 | justify-content: center;
46 | height: 6.2rem;
47 | width: 6.2rem;
48 | position: absolute;
49 | left: 50%;
50 | top: 50%;
51 | transform: translate(-50%, -50%) scale(1);
52 | transition: transform var(--duration-short) ease, color var(--duration-short) ease;
53 | z-index: 1;
54 | }
55 |
56 | .deferred-media__poster-button:hover {
57 | transform: translate(-50%, -50%) scale(1.1);
58 | }
59 |
60 | .deferred-media__poster-button .icon {
61 | width: 2rem;
62 | height: 2rem;
63 | }
64 |
65 | .deferred-media__poster-button .icon-play {
66 | margin-left: 0.2rem;
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/shopify/assets/component-discounts.css:
--------------------------------------------------------------------------------
1 | .discounts {
2 | font-size: 1.2rem;
3 | }
4 |
5 | .discounts__discount {
6 | display: flex;
7 | align-items: center;
8 | line-height: 1.5;
9 | }
10 |
11 | .discounts__discount svg {
12 | color: var(--color-button-background);
13 | }
14 |
15 | .discounts__discount--end {
16 | justify-content: flex-end;
17 | }
18 |
19 | .discounts__discount > .icon {
20 | color: var(--color-foreground);
21 | width: 1.2rem;
22 | height: 1.2rem;
23 | margin-right: 0.7rem;
24 | }
25 |
--------------------------------------------------------------------------------
/shopify/assets/component-image-with-text.css:
--------------------------------------------------------------------------------
1 | .image-with-text {
2 | margin-top: 5rem;
3 | }
4 |
5 | .image-with-text:not(.color-scheme-background-1) {
6 | margin-bottom: 5rem;
7 | }
8 |
9 | @media screen and (min-width: 750px) {
10 | .image-with-text {
11 | margin-bottom: 5rem;
12 | }
13 | }
14 |
15 | .image-with-text .grid {
16 | margin-left: 0;
17 | margin-bottom: 0;
18 | }
19 |
20 | .image-with-text__grid {
21 | overflow: hidden;
22 | }
23 |
24 | @media screen and (min-width: 750px) {
25 | .image-with-text__grid--reverse {
26 | flex-direction: row-reverse;
27 | }
28 | }
29 |
30 | .image-with-text__media {
31 | background-color: transparent;
32 | min-height: 100%;
33 | }
34 |
35 | .image-with-text__media--small {
36 | height: 19.4rem;
37 | }
38 |
39 | .image-with-text__media--large {
40 | height: 43.5rem;
41 | }
42 |
43 | @media screen and (min-width: 750px) {
44 | .image-with-text__media--small {
45 | height: 31.4rem;
46 | }
47 |
48 | .image-with-text__media--large {
49 | height: 69.5rem;
50 | }
51 | }
52 |
53 | .image-with-text__media--placeholder {
54 | background-color: var(--color-foreground-4);
55 | position: relative;
56 | overflow: hidden;
57 | }
58 |
59 | .image-with-text__media--placeholder.image-with-text__media--adapt {
60 | height: 20rem;
61 | }
62 |
63 | @media screen and (min-width: 750px) {
64 | .image-with-text__media--placeholder.image-with-text__media--adapt {
65 | height: 30rem;
66 | }
67 | }
68 |
69 | .image-with-text__media--placeholder > svg {
70 | position: absolute;
71 | left: 50%;
72 | max-width: 80rem;
73 | top: 50%;
74 | transform: translate(-50%, -50%);
75 | width: 100%;
76 | fill: currentColor;
77 | }
78 |
79 | .image-with-text__content {
80 | display: flex;
81 | flex-direction: column;
82 | align-items: flex-start;
83 | height: 100%;
84 | justify-content: center;
85 | padding: 4rem 4rem 5rem;
86 | }
87 |
88 | @media screen and (min-width: 750px) {
89 | .image-with-text__grid--reverse .image-with-text__content {
90 | margin-left: auto;
91 | }
92 | }
93 |
94 | @media screen and (min-width: 990px) {
95 | .image-with-text__content {
96 | padding: 6rem 7rem 7rem;
97 | }
98 | }
99 |
100 | .image-with-text__content > * + * {
101 | margin-top: 1rem;
102 | }
103 |
104 | .image-with-text__content > .image-with-text__text:empty ~ a {
105 | margin-top: 2rem;
106 | }
107 |
108 | .image-with-text__content > :first-child:is(.image-with-text__heading) {
109 | margin-top: 0;
110 | }
111 |
112 | .image-with-text__content :last-child:is(.image-with-text__heading) {
113 | margin-bottom: 0;
114 | }
115 |
116 | .image-with-text__content :last-child:is(.button) {
117 | margin-top: 2rem;
118 | }
119 |
120 | .image-with-text__content .button + .image-with-text__text {
121 | margin-top: 2rem;
122 | }
123 |
124 | .image-with-text__heading {
125 | margin-bottom: 0;
126 | }
127 |
128 | .image-with-text__text p {
129 | margin-top: 0;
130 | margin-bottom: 1rem;
131 | }
132 |
--------------------------------------------------------------------------------
/shopify/assets/component-list-menu.css:
--------------------------------------------------------------------------------
1 | .list-menu--right {
2 | right: 0;
3 | }
4 |
5 | .list-menu--disclosure {
6 | position: absolute;
7 | min-width: 100%;
8 | width: 20rem;
9 | border: 1px solid var(--color-foreground-20);
10 | background-color: var(--color-background);
11 | }
12 |
13 | .list-menu--disclosure:focus {
14 | outline: none;
15 | }
16 |
17 | .list-menu__item--active {
18 | text-decoration: underline;
19 | text-underline-offset: 0.3rem;
20 | }
21 |
22 | .list-menu--disclosure.localization-selector {
23 | max-height: 18rem;
24 | overflow: auto;
25 | width: 10rem;
26 | padding: 0.5rem;
27 | }
28 |
--------------------------------------------------------------------------------
/shopify/assets/component-list-payment.css:
--------------------------------------------------------------------------------
1 | .list-payment {
2 | display: flex;
3 | flex-wrap: wrap;
4 | justify-content: center;
5 | margin: -0.5rem 0;
6 | padding-top: 1rem;
7 | padding-left: 0;
8 | }
9 |
10 | @media screen and (min-width: 750px) {
11 | .list-payment {
12 | justify-content: flex-end;
13 | margin: -0.5rem;
14 | padding-top: 0;
15 | }
16 | }
17 |
18 | .list-payment__item {
19 | align-items: center;
20 | display: flex;
21 | padding: 0.5rem;
22 | }
23 |
--------------------------------------------------------------------------------
/shopify/assets/component-list-social.css:
--------------------------------------------------------------------------------
1 | .list-social {
2 | display: flex;
3 | flex-wrap: wrap;
4 | justify-content: flex-end;
5 | }
6 |
7 | @media only screen and (max-width: 749px) {
8 | .list-social {
9 | justify-content: center;
10 | }
11 | }
12 |
13 | .list-social__item .icon {
14 | height: 1.8rem;
15 | width: 1.8rem;
16 | }
17 |
18 | .list-social__link {
19 | align-items: center;
20 | display: flex;
21 | padding: 1.3rem;
22 | }
23 |
24 | .list-social__link:hover .icon {
25 | transform: scale(1.07);
26 | }
27 |
--------------------------------------------------------------------------------
/shopify/assets/component-loading-overlay.css:
--------------------------------------------------------------------------------
1 | .loading-overlay {
2 | position: absolute;
3 | z-index: 1;
4 | width: 3rem;
5 | }
6 |
7 | @media screen and (max-width: 749px) {
8 | .loading-overlay {
9 | top: 0;
10 | right: 0;
11 | }
12 | }
13 |
14 | @media screen and (min-width: 750px) {
15 | .loading-overlay {
16 | left: 0;
17 | }
18 | }
19 |
20 | .loading-overlay__spinner {
21 | width: 3rem;
22 | display: inline-block;
23 | }
24 |
25 | .spinner {
26 | animation: rotator 1.4s linear infinite;
27 | }
28 |
29 | @keyframes rotator {
30 | 0% {
31 | transform: rotate(0deg);
32 | }
33 | 100% {
34 | transform: rotate(270deg);
35 | }
36 | }
37 |
38 | .path {
39 | stroke-dasharray: 280;
40 | stroke-dashoffset: 0;
41 | transform-origin: center;
42 | stroke: var(--color-foreground);
43 | animation: dash 1.4s ease-in-out infinite;
44 | }
45 |
46 | @keyframes dash {
47 | 0% {
48 | stroke-dashoffset: 280;
49 | }
50 | 50% {
51 | stroke-dashoffset: 75;
52 | transform: rotate(135deg);
53 | }
54 | 100% {
55 | stroke-dashoffset: 280;
56 | transform: rotate(450deg);
57 | }
58 | }
59 |
60 | .loading-overlay:not(.hidden) + .cart-item__price-wrapper,
61 | .loading-overlay:not(.hidden) ~ cart-remove-button {
62 | opacity: 50%;
63 | }
64 |
65 | .loading-overlay:not(.hidden) ~ cart-remove-button {
66 | pointer-events: none;
67 | cursor: default;
68 | }
69 |
--------------------------------------------------------------------------------
/shopify/assets/component-model-viewer-ui.css:
--------------------------------------------------------------------------------
1 | .shopify-model-viewer-ui .shopify-model-viewer-ui__controls-area {
2 | background: var(--color-background);
3 | border-color: var(--color-foreground-4);
4 | }
5 |
6 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button {
7 | color: var(--color-foreground-75);
8 | }
9 |
10 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control:hover {
11 | color: var(--color-foreground-55);
12 | }
13 |
14 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control:active,
15 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control.focus-visible:focus {
16 | color: var(--color-foreground-55);
17 | background: var(--color-foreground-4);
18 | }
19 |
20 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--control:not(:last-child):after {
21 | border-color: var(--color-foreground-4);
22 | }
23 |
24 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--poster {
25 | border-radius: 50%;
26 | color: var(--color-foreground);
27 | background: var(--color-background);
28 | border-color: var(--color-foreground-10);
29 | transform: translate(-50%, -50%) scale(1);
30 | transition: transform var(--duration-short) ease, color var(--duration-short) ease;
31 | }
32 |
33 | .shopify-model-viewer-ui .shopify-model-viewer-ui__poster-control-icon {
34 | width: 4.8rem;
35 | height: 4.8rem;
36 | margin-top: .3rem;
37 | }
38 |
39 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--poster:hover,
40 | .shopify-model-viewer-ui .shopify-model-viewer-ui__button--poster:focus {
41 | transform: translate(-50%, -50%) scale(1.1);
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/shopify/assets/component-newsletter.css:
--------------------------------------------------------------------------------
1 | .newsletter-form {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | width: 100%;
7 | position: relative;
8 | }
9 |
10 | @media screen and (min-width: 750px) {
11 | .newsletter-form {
12 | flex-direction: row;
13 | align-items: flex-start;
14 | margin: 0 auto;
15 | max-width: 50rem;
16 | }
17 | }
18 |
19 | .newsletter-form__field-wrapper {
20 | width: 100%;
21 | }
22 |
23 | .newsletter-form__message {
24 | justify-content: center;
25 | margin-bottom: 0;
26 | }
27 |
28 | .newsletter-form__message--success {
29 | margin-top: 2rem;
30 | }
31 |
32 | @media screen and (min-width: 750px) {
33 | .newsletter-form__message {
34 | justify-content: flex-start;
35 | }
36 |
37 | .newsletter-form__message--success {
38 | position: absolute;
39 | left: 0;
40 | bottom: -65%;
41 | }
42 | }
43 |
44 | .newsletter-form__button {
45 | margin-left: 1.4rem;
46 | }
47 |
48 | @media screen and (max-width: 989px) {
49 | .newsletter-form__button {
50 | width: 100%;
51 | margin: 1.4rem 0 0 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/shopify/assets/component-pagination.css:
--------------------------------------------------------------------------------
1 | .pagination-wrapper {
2 | margin-top: 4rem;
3 | margin-bottom: 7rem;
4 | }
5 |
6 | .pagination-wrapper-small {
7 | margin-top: 1rem;
8 | margin-bottom: 7rem;
9 | }
10 |
11 | @media screen and (min-width: 990px) {
12 | .pagination-wrapper {
13 | margin-top: 5rem;
14 | margin-bottom: 10rem;
15 | }
16 | }
17 |
18 | .pagination__list {
19 | display: flex;
20 | flex-wrap: wrap;
21 | justify-content: center;
22 | }
23 |
24 | .pagination__list > li {
25 | flex: 1 0 4.4rem;
26 | max-width: 4.4rem;
27 | }
28 |
29 | .pagination__list > li:not(:last-child) {
30 | margin-right: 1rem;
31 | }
32 |
33 | .pagination__item {
34 | color: var(--color-foreground);
35 | display: inline-flex;
36 | justify-content: center;
37 | align-items: center;
38 | position: relative;
39 | height: 4.4rem;
40 | width: 100%;
41 | padding: 0;
42 | text-decoration: none;
43 | }
44 |
45 | .pagination__item:hover {
46 | color: var(--color-foreground);
47 | }
48 |
49 | a.pagination__item:hover::after {
50 | height: 0.2rem;
51 | }
52 |
53 | .pagination__item .icon-caret {
54 | height: 0.6rem;
55 | }
56 |
57 | .pagination__item--current {
58 | font-weight: 600;
59 | }
60 |
61 | .pagination__item--current::after {
62 | height: 0.1rem;
63 | }
64 |
65 | .pagination__item--current::after,
66 | .pagination__item:hover::after {
67 | content: '';
68 | display: block;
69 | width: 2rem;
70 | position: absolute;
71 | bottom: 8px;
72 | left: 50%;
73 | transform: translateX(-50%);
74 | background-color: currentColor;
75 | }
76 |
77 | .pagination__item--next .icon {
78 | margin-left: -0.2rem;
79 | transform: rotate(90deg);
80 | }
81 |
82 | .pagination__item--next:hover .icon {
83 | transform: rotate(90deg) scale(1.07);
84 | }
85 |
86 | .pagination__item--prev .icon {
87 | margin-right: -0.2rem;
88 | transform: rotate(-90deg);
89 | }
90 |
91 | .pagination__item--prev:hover .icon {
92 | transform: rotate(-90deg) scale(1.07);
93 | }
94 |
95 | .pagination__item-arrow {
96 | color: var(--color-foreground-75);
97 | }
98 |
99 | .pagination__item-arrow:hover .icon {
100 | color: var(--color-foreground);
101 | }
102 |
103 | .pagination__item-arrow:hover::after {
104 | display: none;
105 | }
106 |
--------------------------------------------------------------------------------
/shopify/assets/component-pickup-availability.css:
--------------------------------------------------------------------------------
1 | pickup-availability {
2 | display: block;
3 | }
4 |
5 | pickup-availability[available] {
6 | min-height: 12rem;
7 | }
8 |
9 | .pickup-availability-preview {
10 | align-items: flex-start;
11 | display: flex;
12 | gap: 0.2rem;
13 | }
14 |
15 | @media screen and (min-width: 750px) {
16 | .pickup-availability-preview {
17 | padding: 0 2rem 0 0;
18 | }
19 | }
20 |
21 | .pickup-availability-preview .icon {
22 | flex-shrink: 0;
23 | height: 1.8rem;
24 | }
25 |
26 | .pickup-availability-preview .icon-unavailable {
27 | height: 1.6rem;
28 | margin-top: 0.1rem;
29 | }
30 |
31 | .pickup-availability-button {
32 | background-color: transparent;
33 | color: var(--color-foreground-75);
34 | letter-spacing: 0.06rem;
35 | padding: 0 0 0.2rem;
36 | text-decoration: underline;
37 | }
38 |
39 | .pickup-availability-button:hover {
40 | color: var(--color-foreground);
41 | }
42 |
43 | .pickup-availability-info * {
44 | margin: 0 0 0.6rem;
45 | }
46 |
47 | pickup-availability-drawer {
48 | background-color: var(--color-background);
49 | border: 0.1rem solid var(--color-foreground-20);
50 | height: 100%;
51 | opacity: 0;
52 | overflow-y: auto;
53 | padding: 2rem;
54 | position: fixed;
55 | top: 0;
56 | right: 0;
57 | z-index: 4;
58 | transition: opacity var(--duration-default) ease,
59 | transform var(--duration-default) ease;
60 | transform: translateX(100%);
61 | width: 100%;
62 | }
63 |
64 | pickup-availability-drawer[open] {
65 | transform: translateX(0);
66 | opacity: 1;
67 | }
68 |
69 | @media screen and (min-width: 750px) {
70 | pickup-availability-drawer {
71 | transform: translateX(100%);
72 | width: 37.5rem;
73 | }
74 |
75 | pickup-availability-drawer[open] {
76 | opacity: 1;
77 | transform: translateX(0);
78 | animation: animateDrawerOpen var(--duration-default) ease;
79 | }
80 | }
81 |
82 | .pickup-availability-header {
83 | align-items: flex-start;
84 | display: flex;
85 | justify-content: space-between;
86 | margin-bottom: 1.2rem;
87 | }
88 |
89 | .pickup-availability-drawer-title {
90 | margin: 0.5rem 0 0;
91 | }
92 |
93 | .pickup-availability-header .icon {
94 | width: 2rem;
95 | }
96 |
97 | .pickup-availability-drawer-button {
98 | background-color: transparent;
99 | border: none;
100 | color: var(--color-base-text);
101 | cursor: pointer;
102 | display: block;
103 | height: 4.4rem;
104 | padding: 1.2rem;
105 | width: 4.4rem;
106 | }
107 |
108 | .pickup-availability-drawer-button:hover {
109 | color: var(--color-foreground-75);
110 | }
111 |
112 | .pickup-availability-variant {
113 | font-size: 1.3rem;
114 | line-height: 1.2;
115 | margin: 0 0 1.2rem;
116 | text-transform: capitalize;
117 | }
118 |
119 | .pickup-availability-variant > * + strong {
120 | margin-left: 1rem;
121 | }
122 |
123 | .pickup-availability-list__item {
124 | border-bottom: 0.1rem solid var(--color-foreground-20);
125 | padding: 2rem 0;
126 | }
127 |
128 | .pickup-availability-list__item:first-child {
129 | border-top: 0.1rem solid var(--color-foreground-20);
130 | }
131 |
132 | .pickup-availability-list__item > * {
133 | margin: 0;
134 | }
135 |
136 | .pickup-availability-list__item > * + * {
137 | margin-top: 1rem;
138 | }
139 |
140 | .pickup-availability-address {
141 | font-style: normal;
142 | font-size: 1.2rem;
143 | line-height: 1.5;
144 | }
145 |
146 | .pickup-availability-address p {
147 | margin: 0;
148 | }
149 |
150 | @keyframes animateDrawerOpen {
151 | @media screen and (max-width: 749px) {
152 | 0% {
153 | opacity: 0;
154 | transform: translateX(100%);
155 | }
156 |
157 | 100% {
158 | opacity: 1;
159 | transform: translateX(0);
160 | }
161 | }
162 |
163 | @media screen and (min-width: 750px) {
164 | 0% {
165 | opacity: 0;
166 | transform: translateX(100%);
167 | }
168 |
169 | 100% {
170 | opacity: 1;
171 | transform: translateX(0);
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/shopify/assets/component-price.css:
--------------------------------------------------------------------------------
1 | .price {
2 | align-items: center;
3 | display: flex;
4 | flex-direction: row;
5 | flex-wrap: wrap;
6 | font-size: 1.6rem;
7 | letter-spacing: 0.1rem;
8 | line-height: 1.5;
9 | color: var(--color-foreground);
10 | }
11 |
12 | .price.price--unavailable {
13 | visibility: hidden;
14 | }
15 |
16 | .price--end {
17 | justify-content: flex-end;
18 | }
19 |
20 | .price dl {
21 | margin: 0;
22 | display: flex;
23 | flex-direction: column;
24 | }
25 |
26 | .price dd {
27 | margin: 0 1rem 0 0;
28 | }
29 |
30 | .price .price__last:last-of-type {
31 | margin: 0;
32 | }
33 |
34 | @media screen and (min-width: 750px) {
35 | .price {
36 | margin-bottom: 0;
37 | }
38 | }
39 |
40 | .price--large {
41 | font-size: 1.6rem;
42 | line-height: 1.5;
43 | letter-spacing: 0.13rem;
44 | }
45 |
46 | @media screen and (min-width: 750px) {
47 | .price--large {
48 | font-size: 1.8rem;
49 | }
50 | }
51 |
52 | .price--sold-out .price__availability,
53 | .price__regular {
54 | display: block;
55 | }
56 |
57 | .price__sale,
58 | .price__availability,
59 | .price .price__badge-sale,
60 | .price .price__badge-sold-out,
61 | .price--on-sale .price__regular,
62 | .price--on-sale .price__availability,
63 | .price--no-compare .price__compare {
64 | display: none;
65 | }
66 |
67 | .price--sold-out .price__badge-sold-out,
68 | .price--on-sale .price__badge-sale {
69 | display: inline-flex;
70 | }
71 |
72 | .price--on-sale .price__sale {
73 | display: flex;
74 | flex-direction: row;
75 | flex-wrap: wrap;
76 | }
77 |
78 | .price--center {
79 | display: flex;
80 | justify-content: center;
81 | }
82 |
83 | .price--on-sale .price-item--regular {
84 | text-decoration: line-through;
85 | color: var(--color-foreground-75);
86 | }
87 |
88 | .unit-price {
89 | font-size: 1.1rem;
90 | letter-spacing: 0.04rem;
91 | line-height: 1.2;
92 | margin-top: 0.2rem;
93 | text-transform: uppercase;
94 | color: var(--color-foreground-70);
95 | }
96 |
--------------------------------------------------------------------------------
/shopify/assets/component-product-model.css:
--------------------------------------------------------------------------------
1 | .button.product__xr-button {
2 | background: var(--color-foreground-8);
3 | color: var(--color-foreground);
4 | margin: 1rem auto;
5 | box-shadow: none;
6 | }
7 |
8 | .button.product__xr-button:hover {
9 | box-shadow: none;
10 | }
11 |
12 | .product__xr-button[data-shopify-xr-hidden] {
13 | visibility: hidden;
14 | }
15 |
16 | @media screen and (max-width: 749px) {
17 | slider-component .product__xr-button:not([data-shopify-xr-hidden]) {
18 | display: none;
19 | }
20 |
21 | .active .product__xr-button:not([data-shopify-xr-hidden]) {
22 | display: block;
23 | }
24 | }
25 |
26 | @media screen and (min-width: 750px) {
27 | .product__media-wrapper > .button.product__xr-button {
28 | display: none;
29 | }
30 |
31 | .product__xr-button[data-shopify-xr-hidden] {
32 | display: none;
33 | }
34 | }
35 |
36 | .product__xr-button .icon {
37 | width: 1.4rem;
38 | margin-right: 1rem;
39 | }
40 |
--------------------------------------------------------------------------------
/shopify/assets/component-rte.css:
--------------------------------------------------------------------------------
1 | .rte > p:first-child {
2 | margin-top: 0;
3 | }
4 |
5 | .rte > p:last-child {
6 | margin-bottom: 0;
7 | }
8 |
9 | .rte table {
10 | table-layout: fixed;
11 | }
12 |
13 | @media screen and (min-width: 750px) {
14 | .rte table td {
15 | padding-left: 1.2rem;
16 | padding-right: 1.2rem;
17 | }
18 | }
19 |
20 | .rte img {
21 | height: auto;
22 | max-width: 100%;
23 | }
24 |
25 | .rte ul {
26 | padding-left: 2rem;
27 | }
28 |
29 | .rte li {
30 | list-style: inherit;
31 | }
32 |
33 | .rte li:last-child {
34 | margin-bottom: 0;
35 | }
36 |
37 | .rte a {
38 | color: var(--color-link-hover);
39 | text-underline-offset: 0.3rem;
40 | text-decoration-thickness: 0.1rem;
41 | transition: text-decoration-thickness var(--duration-short) ease;
42 | }
43 |
44 | .rte a:hover {
45 | color: var(--color-link);
46 | text-decoration-thickness: 0.2rem;
47 | }
48 |
49 | .rte blockquote {
50 | display: inline-flex;
51 | }
52 |
53 | .rte blockquote > * {
54 | margin: -0.5rem 0 -0.5rem 0;
55 | }
56 |
--------------------------------------------------------------------------------
/shopify/assets/component-search.css:
--------------------------------------------------------------------------------
1 | .search__input.field__input {
2 | padding-right: 5rem;
3 | }
4 |
5 | .search__button .icon {
6 | height: 1.8rem;
7 | }
8 |
9 | /* Remove extra spacing for search inputs in Safari */
10 | input::-webkit-search-decoration {
11 | -webkit-appearance: none;
12 | }
13 |
--------------------------------------------------------------------------------
/shopify/assets/component-totals.css:
--------------------------------------------------------------------------------
1 | .totals {
2 | display: flex;
3 | justify-content: center;
4 | align-items: flex-end;
5 | }
6 |
7 | .totals > * {
8 | font-size: 1.6rem;
9 | margin: 0;
10 | }
11 |
12 | .totals * {
13 | line-height: 1;
14 | }
15 |
16 | .totals > * + * {
17 | margin-left: 2rem;
18 | }
19 |
20 | .totals__subtotal-value {
21 | font-size: 1.8rem;
22 | }
23 |
24 | .cart__ctas + .totals {
25 | margin-top: 2rem;
26 | }
27 |
28 | @media all and (min-width: 750px) {
29 | .totals {
30 | justify-content: flex-end;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/shopify/assets/customer.js:
--------------------------------------------------------------------------------
1 | const selectors = {
2 | customerAddresses: '[data-customer-addresses]',
3 | addressCountrySelect: '[data-address-country-select]',
4 | addressContainer: '[data-address]',
5 | toggleAddressButton: 'button[aria-expanded]',
6 | cancelAddressButton: 'button[type="reset"]',
7 | deleteAddressButton: 'button[data-confirm-message]'
8 | };
9 |
10 | const attributes = {
11 | expanded: 'aria-expanded',
12 | confirmMessage: 'data-confirm-message'
13 | };
14 |
15 | class CustomerAddresses {
16 | constructor() {
17 | this.elements = this._getElements();
18 | if (Object.keys(this.elements).length === 0) return;
19 | this._setupCountries();
20 | this._setupEventListeners();
21 | }
22 |
23 | _getElements() {
24 | const container = document.querySelector(selectors.customerAddresses);
25 | return container ? {
26 | container,
27 | addressContainer: container.querySelector(selectors.addressContainer),
28 | toggleButtons: document.querySelectorAll(selectors.toggleAddressButton),
29 | cancelButtons: container.querySelectorAll(selectors.cancelAddressButton),
30 | deleteButtons: container.querySelectorAll(selectors.deleteAddressButton),
31 | countrySelects: container.querySelectorAll(selectors.addressCountrySelect)
32 | } : {};
33 | }
34 |
35 | _setupCountries() {
36 | if (Shopify && Shopify.CountryProvinceSelector) {
37 | // eslint-disable-next-line no-new
38 | new Shopify.CountryProvinceSelector('AddressCountryNew', 'AddressProvinceNew', {
39 | hideElement: 'AddressProvinceContainerNew'
40 | });
41 | this.elements.countrySelects.forEach((select) => {
42 | const formId = select.dataset.formId;
43 | // eslint-disable-next-line no-new
44 | new Shopify.CountryProvinceSelector(`AddressCountry_${formId}`, `AddressProvince_${formId}`, {
45 | hideElement: `AddressProvinceContainer_${formId}`
46 | });
47 | });
48 | }
49 | }
50 |
51 | _setupEventListeners() {
52 | this.elements.toggleButtons.forEach((element) => {
53 | element.addEventListener('click', this._handleAddEditButtonClick);
54 | });
55 | this.elements.cancelButtons.forEach((element) => {
56 | element.addEventListener('click', this._handleCancelButtonClick);
57 | });
58 | this.elements.deleteButtons.forEach((element) => {
59 | element.addEventListener('click', this._handleDeleteButtonClick);
60 | });
61 | }
62 |
63 | _toggleExpanded(target) {
64 | target.setAttribute(
65 | attributes.expanded,
66 | (target.getAttribute(attributes.expanded) === 'false').toString()
67 | );
68 | }
69 |
70 | _handleAddEditButtonClick = ({ currentTarget }) => {
71 | this._toggleExpanded(currentTarget);
72 | }
73 |
74 | _handleCancelButtonClick = ({ currentTarget }) => {
75 | this._toggleExpanded(
76 | currentTarget
77 | .closest(selectors.addressContainer)
78 | .querySelector(`[${attributes.expanded}]`)
79 | )
80 | }
81 |
82 | _handleDeleteButtonClick = ({ currentTarget }) => {
83 | // eslint-disable-next-line no-alert
84 | if (confirm(currentTarget.getAttribute(attributes.confirmMessage))) {
85 | Shopify.postLink(currentTarget.dataset.target, {
86 | parameters: { _method: 'delete' },
87 | });
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/shopify/assets/details-disclosure.js:
--------------------------------------------------------------------------------
1 | class DetailsDisclosure extends HTMLElement {
2 | constructor() {
3 | super();
4 | this.mainDetailsToggle = this.querySelector('details');
5 |
6 | this.addEventListener('keyup', this.onKeyUp);
7 | this.mainDetailsToggle.addEventListener('focusout', this.onFocusOut.bind(this));
8 | }
9 |
10 | onKeyUp(event) {
11 | if(event.code.toUpperCase() !== 'ESCAPE') return;
12 |
13 | const openDetailsElement = event.target.closest('details[open]');
14 | if (!openDetailsElement) return;
15 |
16 | const summaryElement = openDetailsElement.querySelector('summary');
17 | openDetailsElement.removeAttribute('open');
18 | summaryElement.focus();
19 | }
20 |
21 | onFocusOut() {
22 | setTimeout(() => {
23 | if (!this.contains(document.activeElement)) this.close();
24 | })
25 | }
26 |
27 | close() {
28 | this.mainDetailsToggle.removeAttribute('open')
29 | }
30 | }
31 |
32 | customElements.define('details-disclosure', DetailsDisclosure);
33 |
--------------------------------------------------------------------------------
/shopify/assets/details-modal.js:
--------------------------------------------------------------------------------
1 | class DetailsModal extends HTMLElement {
2 | constructor() {
3 | super();
4 | this.detailsContainer = this.querySelector('details');
5 | this.summaryToggle = this.querySelector('summary');
6 |
7 | this.detailsContainer.addEventListener(
8 | 'keyup',
9 | (event) => event.code.toUpperCase() === 'ESCAPE' && this.close()
10 | );
11 | this.summaryToggle.addEventListener(
12 | 'click',
13 | this.onSummaryClick.bind(this)
14 | );
15 | this.querySelector('button[type="button"]').addEventListener(
16 | 'click',
17 | this.close.bind(this)
18 | );
19 |
20 | this.summaryToggle.setAttribute('role', 'button');
21 | this.summaryToggle.setAttribute('aria-expanded', 'false');
22 | }
23 |
24 | isOpen() {
25 | return this.detailsContainer.hasAttribute('open');
26 | }
27 |
28 | onSummaryClick(event) {
29 | event.preventDefault();
30 | event.target.closest('details').hasAttribute('open')
31 | ? this.close()
32 | : this.open(event);
33 | }
34 |
35 | onBodyClick(event) {
36 | if (!this.contains(event.target)) this.close(false);
37 | }
38 |
39 | open(event) {
40 | this.onBodyClickEvent =
41 | this.onBodyClickEvent || this.onBodyClick.bind(this);
42 | event.target.closest('details').setAttribute('open', true);
43 | document.body.addEventListener('click', this.onBodyClickEvent);
44 |
45 | trapFocus(
46 | this.detailsContainer.querySelector('[tabindex="-1"]'),
47 | this.detailsContainer.querySelector('input:not([type="hidden"])')
48 | );
49 | }
50 |
51 | close(focusToggle = true) {
52 | removeTrapFocus(focusToggle ? this.summaryToggle : null);
53 | this.detailsContainer.removeAttribute('open');
54 | document.body.removeEventListener('click', this.onBodyClickEvent);
55 | }
56 | }
57 |
58 | customElements.define('details-modal', DetailsModal);
59 |
--------------------------------------------------------------------------------
/shopify/assets/disclosure.css:
--------------------------------------------------------------------------------
1 | .disclosure {
2 | position: relative;
3 | }
4 |
5 | .disclosure__button {
6 | align-items: center;
7 | cursor: pointer;
8 | display: flex;
9 | height: 4rem;
10 | padding: 0 1.5rem 0 1.5rem;
11 | font-size: 1.3rem;
12 | background-color: transparent;
13 | }
14 |
15 | .disclosure__list {
16 | border: 1px solid var(--color-foreground-20);
17 | font-size: 1.4rem;
18 | margin-top: -0.5rem;
19 | min-height: 8.2rem;
20 | max-height: 19rem;
21 | max-width: 22rem;
22 | min-width: 12rem;
23 | width: max-content;
24 | overflow-y: auto;
25 | padding-bottom: 0.5rem;
26 | padding-top: 0.5rem;
27 | position: absolute;
28 | bottom: 100%;
29 | transform: translateY(-1rem);
30 | z-index: 2;
31 | background-color: var(--color-background);
32 | }
33 |
34 | .disclosure__item {
35 | position: relative;
36 | }
37 |
38 | .disclosure__link {
39 | display: block;
40 | padding: 0.5rem 2.2rem;
41 | text-decoration: none;
42 | line-height: 1.8;
43 | }
44 |
--------------------------------------------------------------------------------
/shopify/assets/newsletter-section.css:
--------------------------------------------------------------------------------
1 | .newsletter--narrow .newsletter__wrapper,
2 | .newsletter:not(.newsletter--narrow) .newsletter__wrapper.color-background-1 {
3 | margin-top: 5rem;
4 | margin-bottom: 5rem;
5 | }
6 |
7 | .newsletter__wrapper:not(.color-background-1) {
8 | padding-top: 5rem;
9 | padding-bottom: 5rem;
10 | }
11 |
12 | .newsletter__wrapper {
13 | padding-right: 4rem;
14 | padding-left: 4rem;
15 | }
16 |
17 | @media screen and (min-width: 750px) {
18 | .newsletter__wrapper {
19 | padding-right: 9rem;
20 | padding-left: 9rem;
21 | }
22 | }
23 |
24 | .newsletter__wrapper > * {
25 | margin-top: 0;
26 | margin-bottom: 0;
27 | }
28 |
29 | .newsletter__wrapper > * + * {
30 | margin-top: 2rem;
31 | }
32 |
33 | .newsletter__wrapper > * + .newsletter-form {
34 | margin-top: 3rem;
35 | }
36 |
37 | .newsletter__subheading {
38 | max-width: 70rem;
39 | margin-left: auto;
40 | margin-right: auto;
41 | }
42 |
43 | .newsletter__wrapper .newsletter-form__field-wrapper {
44 | max-width: 36rem;
45 | }
46 |
47 | .newsletter-form__field-wrapper .newsletter-form__message {
48 | margin-top: 1.5rem;
49 | }
50 |
51 | .newsletter__button {
52 | margin-top: 3rem;
53 | width: fit-content;
54 | }
55 |
56 | @media screen and (min-width: 750px) {
57 | .newsletter__button {
58 | flex-shrink: 0;
59 | margin: 0 0 0 1rem;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/shopify/assets/password-modal.js:
--------------------------------------------------------------------------------
1 | class PasswordModal extends DetailsModal {
2 | constructor() {
3 | super();
4 |
5 | if (this.querySelector('input[aria-invalid="true"]')) this.open({target: this.querySelector('details')});
6 | }
7 | }
8 |
9 | customElements.define('password-modal', PasswordModal);
10 |
--------------------------------------------------------------------------------
/shopify/assets/pickup-availability.js:
--------------------------------------------------------------------------------
1 | class PickupAvailability extends HTMLElement {
2 | constructor() {
3 | super();
4 |
5 | if(!this.hasAttribute('available')) return;
6 |
7 | this.errorHtml = this.querySelector('template').content.firstElementChild.cloneNode(true);
8 | this.onClickRefreshList = this.onClickRefreshList.bind(this);
9 | this.fetchAvailability(this.dataset.variantId);
10 | }
11 |
12 | fetchAvailability(variantId) {
13 | const variantSectionUrl = `${this.dataset.baseUrl}variants/${variantId}/?section_id=pickup-availability`;
14 |
15 | fetch(variantSectionUrl)
16 | .then(response => response.text())
17 | .then(text => {
18 | const sectionInnerHTML = new DOMParser()
19 | .parseFromString(text, 'text/html')
20 | .querySelector('.shopify-section');
21 | this.renderPreview(sectionInnerHTML);
22 | })
23 | .catch(e => {
24 | this.querySelector('button')?.removeEventListener('click', this.onClickRefreshList);
25 | this.renderError();
26 | });
27 | }
28 |
29 | onClickRefreshList(evt) {
30 | this.fetchAvailability(this.dataset.variantId);
31 | }
32 |
33 | renderError() {
34 | this.innerHTML = '';
35 | this.appendChild(this.errorHtml);
36 |
37 | this.querySelector('button').addEventListener('click', this.onClickRefreshList);
38 | }
39 |
40 | renderPreview(sectionInnerHTML) {
41 | const drawer = document.querySelector('pickup-availability-drawer');
42 | if (drawer) drawer.remove();
43 | if (!sectionInnerHTML.querySelector('pickup-availability-preview')) {
44 | this.innerHTML = "";
45 | this.removeAttribute('available');
46 | return;
47 | }
48 |
49 | this.innerHTML = sectionInnerHTML.querySelector('pickup-availability-preview').outerHTML;
50 | this.setAttribute('available', '');
51 |
52 | document.body.appendChild(sectionInnerHTML.querySelector('pickup-availability-drawer'));
53 |
54 | this.querySelector('button').addEventListener('click', (evt) => {
55 | document.querySelector('pickup-availability-drawer').show(evt.target);
56 | });
57 | }
58 | }
59 |
60 | customElements.define('pickup-availability', PickupAvailability);
61 |
62 | class PickupAvailabilityDrawer extends HTMLElement {
63 | constructor() {
64 | super();
65 |
66 | this.onBodyClick = this.handleBodyClick.bind(this);
67 |
68 | this.querySelector('button').addEventListener('click', () => {
69 | this.hide();
70 | });
71 |
72 | this.addEventListener('keyup', () => {
73 | if(event.code.toUpperCase() === 'ESCAPE') this.hide();
74 | });
75 | }
76 |
77 | handleBodyClick(evt) {
78 | const target = evt.target;
79 | if (target != this && !target.closest('pickup-availability-drawer') && target.id != 'ShowPickupAvailabilityDrawer') {
80 | this.hide();
81 | }
82 | }
83 |
84 | hide() {
85 | this.removeAttribute('open');
86 | document.body.removeEventListener('click', this.onBodyClick);
87 | document.body.classList.remove('overflow-hidden');
88 | removeTrapFocus(this.focusElement);
89 | }
90 |
91 | show(focusElement) {
92 | this.focusElement = focusElement;
93 | this.setAttribute('open', '');
94 | document.body.addEventListener('click', this.onBodyClick);
95 | document.body.classList.add('overflow-hidden');
96 | trapFocus(this);
97 | }
98 | }
99 |
100 | customElements.define('pickup-availability-drawer', PickupAvailabilityDrawer);
101 |
--------------------------------------------------------------------------------
/shopify/assets/product-form.js:
--------------------------------------------------------------------------------
1 | class ProductForm extends HTMLElement {
2 | constructor() {
3 | super();
4 |
5 | this.form = this.querySelector('form');
6 | this.form.addEventListener('submit', this.onSubmitHandler.bind(this));
7 | this.cartNotification = document.querySelector('cart-notification');
8 | }
9 |
10 | onSubmitHandler(evt) {
11 | evt.preventDefault();
12 | this.cartNotification.setActiveElement(document.activeElement);
13 |
14 | const submitButton = this.querySelector('[type="submit"]');
15 |
16 | submitButton.setAttribute('disabled', true);
17 | submitButton.classList.add('loading');
18 |
19 | const body = JSON.stringify({
20 | ...JSON.parse(serializeForm(this.form)),
21 | sections: this.cartNotification.getSectionsToRender().map((section) => section.id),
22 | sections_url: window.location.pathname
23 | });
24 |
25 | fetch(`${routes.cart_add_url}`, { ...fetchConfig('javascript'), body })
26 | .then((response) => response.json())
27 | .then((parsedState) => {
28 | this.cartNotification.renderContents(parsedState);
29 | })
30 | .catch((e) => {
31 | console.error(e);
32 | })
33 | .finally(() => {
34 | submitButton.classList.remove('loading');
35 | submitButton.removeAttribute('disabled');
36 | });
37 | }
38 | }
39 |
40 | customElements.define('product-form', ProductForm);
41 |
--------------------------------------------------------------------------------
/shopify/assets/product-model.js:
--------------------------------------------------------------------------------
1 | class ProductModel extends DeferredMedia {
2 | constructor() {
3 | super();
4 | }
5 |
6 | loadContent() {
7 | super.loadContent();
8 |
9 | Shopify.loadFeatures([
10 | {
11 | name: 'model-viewer-ui',
12 | version: '1.0',
13 | onLoad: this.setupModelViewerUI.bind(this),
14 | },
15 | ]);
16 | }
17 |
18 | setupModelViewerUI(errors) {
19 | if (errors) return;
20 |
21 | this.modelViewerUI = new Shopify.ModelViewerUI(this.querySelector('model-viewer'));
22 | }
23 | }
24 | customElements.define('product-model', ProductModel);
25 |
26 | window.ProductModel = {
27 | loadShopifyXR() {
28 | Shopify.loadFeatures([
29 | {
30 | name: 'shopify-xr',
31 | version: '1.0',
32 | onLoad: this.setupShopifyXR.bind(this),
33 | },
34 | ]);
35 | },
36 |
37 | setupShopifyXR(errors) {
38 | if (errors) return;
39 |
40 | if (!window.ShopifyXR) {
41 | document.addEventListener('shopify_xr_initialized', () =>
42 | this.setupShopifyXR()
43 | );
44 | return;
45 | }
46 |
47 | document.querySelectorAll('[id^="ProductJSON-"]').forEach((modelJSON) => {
48 | window.ShopifyXR.addModels(JSON.parse(modelJSON.textContent));
49 | modelJSON.remove();
50 | });
51 | window.ShopifyXR.setupXRElements();
52 | },
53 | };
54 |
55 | window.addEventListener('DOMContentLoaded', () => { window.ProductModel?.loadShopifyXR(); });
56 |
--------------------------------------------------------------------------------
/shopify/assets/section-blog-post.css:
--------------------------------------------------------------------------------
1 | .article-template > *:first-child:not(.article-template__hero-container) {
2 | margin-top: 5rem;
3 | }
4 |
5 | .article-template__hero-container {
6 | max-width: 130rem;
7 | margin: 0 auto;
8 | }
9 |
10 | @media screen and (min-width: 1320px) {
11 | .article-template__hero-container:first-child {
12 | margin-top: 5rem;
13 | }
14 | }
15 |
16 | .article-template__hero-medium {
17 | height: 15.6rem;
18 | }
19 |
20 | .article-template__hero-large {
21 | height: 19rem;
22 | }
23 |
24 | @media screen and (min-width: 750px) and (max-width: 989px) {
25 | .article-template__hero-medium {
26 | height: 34.9rem;
27 | }
28 |
29 | .article-template__hero-large {
30 | height: 42.3rem;
31 | }
32 | }
33 |
34 | @media screen and (min-width: 990px) {
35 | .article-template__hero-medium {
36 | height: 54.5rem;
37 | }
38 |
39 | .article-template__hero-large {
40 | height: 66rem;
41 | }
42 | }
43 |
44 | .article-template header {
45 | margin-top: 4.4rem;
46 | margin-bottom: 2rem;
47 | }
48 |
49 | @media screen and (min-width: 750px) {
50 | .article-template header {
51 | margin-top: 5rem;
52 | }
53 | }
54 |
55 | .article-template__title {
56 | margin: 0;
57 | }
58 |
59 | .article-template__title:not(:only-child) {
60 | margin-bottom: 1rem;
61 | }
62 |
63 | .article-template__link {
64 | font-size: 1.8rem;
65 | display: flex;
66 | justify-content: center;
67 | align-items: center;
68 | text-underline-offset: 0.3rem;
69 | }
70 |
71 | .article-template__link:hover {
72 | text-decoration-thickness: 0.2rem;
73 | }
74 |
75 | .article-template__link svg {
76 | width: 1.5rem;
77 | transform: rotate(180deg);
78 | margin-right: 1rem;
79 | }
80 |
81 | .article-template__content {
82 | margin-top: 3rem;
83 | margin-bottom: 3rem;
84 | }
85 |
86 | .article-template__social-sharing {
87 | display: flex;
88 | flex-direction: column;
89 | align-items: self-end;
90 | margin-top: 3rem;
91 | }
92 |
93 | .article-template__social-sharing .social-sharing {
94 | margin-left: -1.3rem;
95 | }
96 |
97 | .article-template__comment-wrapper {
98 | margin-top: 5rem;
99 | }
100 |
101 | @media screen and (min-width: 750px) {
102 | .article-template__comment-wrapper {
103 | margin-top: 6rem;
104 | }
105 | }
106 |
107 | .article-template__comment-wrapper h2 {
108 | margin-top: 0;
109 | }
110 |
111 | .article-template__comments {
112 | margin-bottom: 5rem;
113 | }
114 |
115 | @media screen and (min-width: 750px) {
116 | .article-template__comments {
117 | margin-bottom: 7rem;
118 | }
119 | }
120 |
121 | .article-template__comments-fields {
122 | margin-bottom: 4rem;
123 | }
124 |
125 | .article-template__comments-comment {
126 | color: var(--color-foreground-75);
127 | background-color: var(--color-background);
128 | margin-bottom: 1.5rem;
129 | padding: 2rem 2rem 1.5rem;
130 | }
131 |
132 | @media screen and (min-width: 750px) {
133 | .article-template__comments-comment {
134 | padding: 2rem 2.5rem;
135 | }
136 | }
137 |
138 | .article-template__comments-comment p {
139 | margin: 0 0 1rem;
140 | }
141 |
142 | .article-template__comment-fields > * {
143 | margin-bottom: 3rem;
144 | }
145 |
146 | @media screen and (min-width: 750px) {
147 | .article-template__comment-fields {
148 | display: grid;
149 | grid-template-columns: repeat(2, 1fr);
150 | grid-column-gap: 4rem;
151 | }
152 | }
153 |
154 | .article-template__comment-warning {
155 | margin: 2rem 0 2.5rem;
156 | }
157 |
158 | @media screen and (min-width: 990px) {
159 | .article-template__comments .pagination-wrapper {
160 | margin: 5rem 0 8rem;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/shopify/assets/section-collection-list.css:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 749px) {
2 | .collage-section + .collection-list-section .no-heading.no-mobile-link {
3 | margin-top: -7rem;
4 | }
5 | .collage-section + .collection-list-section .no-heading:not(.no-mobile-link) {
6 | margin-top: -1rem;
7 | }
8 | }
9 |
10 | @media screen and (min-width: 749px) {
11 | .collage-section + .collection-list-section .no-heading {
12 | margin-top: -4rem;
13 | }
14 | }
15 |
16 | .collection-list-title {
17 | margin: 0;
18 | }
19 |
20 | @media screen and (max-width: 749px) {
21 | .collection-list-wrapper.page-width {
22 | padding: 0;
23 | }
24 |
25 | .collection-list:not(.slider) {
26 | padding-left: 0;
27 | padding-right: 0;
28 | }
29 |
30 | .collection-list-section .collection-list:not(.slider) {
31 | padding-left: 1.5rem;
32 | padding-right: 1.5rem;
33 | }
34 | }
35 |
36 | @media screen and (max-width: 749px) {
37 | .collection-list-wrapper:not(.no-heading) .title-wrapper-with-link {
38 | margin-top: -1rem;
39 | }
40 | }
41 |
42 | @media screen and (min-width: 750px) {
43 | .collection-list-wrapper.no-heading {
44 | margin-top: 6rem;
45 | }
46 | }
47 |
48 | .collection-list__item:only-child {
49 | max-width: 100%;
50 | width: 100%;
51 | }
52 |
53 | .collection-list__item .card--light-border:hover {
54 | border: 0.1rem solid var(--color-foreground-4);
55 | }
56 |
57 | .collection-list__item:only-child .media {
58 | height: 35rem;
59 | }
60 |
61 | @media screen and (max-width: 749px) {
62 | .collection-list .collection-list__item {
63 | width: calc(100% - 3rem);
64 | }
65 |
66 | .collection-list__item.grid__item {
67 | padding-bottom: 1rem;
68 | }
69 |
70 | .slider.collection-list--1-items {
71 | padding-bottom: 0;
72 | }
73 | }
74 |
75 | .collection-list.negative-margin--small {
76 | margin-bottom: -1rem;
77 | }
78 |
79 | @media screen and (min-width: 750px) and (max-width: 989px) {
80 | .slider.collection-list--1-items,
81 | .slider.collection-list--2-items,
82 | .slider.collection-list--3-items,
83 | .slider.collection-list--4-items {
84 | padding-bottom: 0;
85 | }
86 | }
87 |
88 | @media screen and (min-width: 750px) {
89 | .collection-list__item:only-child > *:not(.card--media) {
90 | height: 320px;
91 | }
92 |
93 | .collection-list__item:only-child .media {
94 | height: 47rem;
95 | }
96 |
97 | .collection-list__item a:hover {
98 | box-shadow: none;
99 | }
100 |
101 | .collection-list.grid--3-col-tablet .grid__item {
102 | max-width: 33.33%;
103 | }
104 |
105 | .collection-list--4-items .grid__item,
106 | .collection-list--7-items .grid__item:nth-child(n + 4),
107 | .collection-list--10-items .grid__item:nth-child(n + 7) {
108 | width: 50%;
109 | }
110 | }
111 |
112 | @media screen and (max-width: 989px) {
113 | .collection-list.slider .collection-list__item {
114 | max-width: 100%;
115 | }
116 | }
117 |
118 | .collection-list__item .card__text,
119 | .collection-list__item .card-colored {
120 | position: relative;
121 | }
122 |
--------------------------------------------------------------------------------
/shopify/assets/section-contact-form.css:
--------------------------------------------------------------------------------
1 | .contact img {
2 | max-width: 100%;
3 | }
4 |
5 | .contact .field {
6 | margin-bottom: 1.5rem;
7 | }
8 |
9 | @media screen and (min-width: 750px) {
10 | .contact .field {
11 | margin-bottom: 2rem;
12 | }
13 | }
14 |
15 | .contact__button {
16 | margin-top: 3rem;
17 | }
18 |
19 | @media screen and (min-width: 750px) {
20 | .contact__button {
21 | margin-top: 4rem;
22 | }
23 | }
24 |
25 | @media screen and (min-width: 750px) {
26 | .contact__fields {
27 | display: grid;
28 | grid-template-columns: repeat(2, 1fr);
29 | grid-column-gap: 2rem;
30 | }
31 | }
32 |
33 | .grecaptcha-badge {
34 | visibility: hidden;
35 | }
36 |
--------------------------------------------------------------------------------
/shopify/assets/section-featured-blog.css:
--------------------------------------------------------------------------------
1 | .blog:not(.background-secondary) {
2 | margin: 5rem 0;
3 | }
4 |
5 | .blog.background-secondary {
6 | padding: 4rem 0 5rem;
7 | }
8 |
9 | .blog .placeholder {
10 | display: flex;
11 | flex-direction: column;
12 | align-items: center;
13 | height: 22rem;
14 | text-align: center;
15 | padding: 4rem 2rem 5rem;
16 | margin: 0 2rem;
17 | }
18 |
19 | @media screen and (min-width: 750px) {
20 | .blog .placeholder {
21 | margin: 0;
22 | }
23 | }
24 |
25 | @media screen and (max-width: 749px) {
26 | .blog:not(.no-heading) {
27 | margin-top: -1rem;
28 | }
29 | }
30 |
31 | @media screen and (min-width: 750px) {
32 | .blog.no-heading {
33 | margin-top: 6rem;
34 | }
35 | }
36 |
37 | .background-secondary .title-wrapper-with-link {
38 | margin-top: 0;
39 | }
40 |
41 | .blog__title {
42 | margin: 0;
43 | }
44 |
45 | .blog__posts.articles-wrapper {
46 | margin-bottom: 0;
47 | }
48 |
49 | @media screen and (min-width: 750px) {
50 | .blog__post:only-child {
51 | text-align: center;
52 | }
53 | }
54 |
55 | @media screen and (min-width: 990px) {
56 | .blog__posts.articles-wrapper {
57 | padding-bottom: 0;
58 | }
59 | }
60 |
61 | .blog__posts.articles-wrapper .article {
62 | scroll-snap-align: start;
63 | }
64 |
65 | @media screen and (min-width: 750px) {
66 | .blog__posts .article + .article {
67 | margin-left: 1rem;
68 | }
69 | }
70 |
71 | @media screen and (max-width: 749px) {
72 | .blog__post.article {
73 | width: calc(100% - 3rem);
74 | padding-left: 0.5rem;
75 | }
76 | }
77 |
78 | .background-secondary .article-card {
79 | background-color: var(--color-background);
80 | }
81 |
82 | .blog__button {
83 | margin-top: 3rem;
84 | }
85 |
86 | @media screen and (min-width: 750px) {
87 | .blog__button {
88 | margin-top: 5rem;
89 | }
90 | }
91 |
92 | @media screen and (max-width: 749px) {
93 | .slider.blog__posts--1-items {
94 | padding-bottom: 0;
95 | }
96 | }
97 |
98 | @media screen and (min-width: 750px) and (max-width: 989px) {
99 | .slider.blog__posts--1-items,
100 | .slider.blog__posts--2-items {
101 | padding-bottom: 0;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/shopify/assets/section-image-banner.css:
--------------------------------------------------------------------------------
1 | .banner {
2 | display: flex;
3 | position: relative;
4 | flex-direction: column;
5 | min-height: initial;
6 | }
7 |
8 | @media screen and (max-width: 749px) {
9 | .banner:not(.banner--stacked) {
10 | flex-direction: row;
11 | flex-wrap: wrap;
12 | }
13 | }
14 |
15 | @media screen and (min-width: 750px) {
16 | .banner {
17 | min-height: 72rem;
18 | flex-direction: row;
19 | }
20 | }
21 |
22 | @media screen and (max-width: 749px) {
23 | .banner--stacked {
24 | height: auto;
25 | }
26 |
27 | .banner--stacked .banner__media {
28 | flex-direction: column;
29 | }
30 | }
31 |
32 | .banner__media {
33 | height: 100%;
34 | left: 0;
35 | top: 0;
36 | width: 100%;
37 | position: relative;
38 | }
39 |
40 | .banner__media-half {
41 | width: 50%;
42 | }
43 |
44 | .banner__media-half + .banner__media-half {
45 | right: 0;
46 | left: auto;
47 | }
48 |
49 | @media screen and (max-width: 749px) {
50 | .banner--stacked .banner__media-half {
51 | width: 100%;
52 | }
53 |
54 | .banner--stacked .banner__media-half + .banner__media-half {
55 | order: 1;
56 | }
57 |
58 | .banner:not(.banner--adapt):not(.banner--stacked) > .banner__media {
59 | height: 39rem;
60 | }
61 | }
62 |
63 | @media screen and (min-width: 750px) {
64 | .banner__media {
65 | position: absolute;
66 | height: 100%;
67 | }
68 | }
69 |
70 | .banner--adapt {
71 | height: auto;
72 | }
73 |
74 | @media screen and (max-width: 749px) {
75 | .banner--stacked:not(.banner--adapt) .banner__media {
76 | height: 39rem;
77 | }
78 |
79 | .banner::before {
80 | display: none !important;
81 | }
82 |
83 | .banner--stacked .banner__media-image-half {
84 | width: 100%;
85 | }
86 | }
87 |
88 | .banner__media .placeholder-svg {
89 | position: absolute;
90 | left: 0;
91 | top: 0;
92 | height: 100%;
93 | width: 100%;
94 | }
95 |
96 | .banner__content {
97 | padding: 0;
98 | display: flex;
99 | position: relative;
100 | width: 100%;
101 | justify-content: center;
102 | }
103 |
104 | @media screen and (min-width: 750px) {
105 | .banner__content {
106 | padding-bottom: 5rem;
107 | padding-top: 5rem;
108 | }
109 | }
110 |
111 | .banner__box {
112 | border: 0;
113 | padding: 4rem 3.5rem;
114 | position: relative;
115 | height: fit-content;
116 | align-items: center;
117 | text-align: center;
118 | width: 100%;
119 | }
120 |
121 | .banner__box > * + .banner__buttons {
122 | margin: 0 auto;
123 | margin-top: 2.3rem;
124 | transform: translateX(1rem);
125 | }
126 |
127 | .banner__box > * + .banner__buttons--multiple {
128 | display: flex;
129 | max-width: 45rem;
130 | flex-wrap: wrap;
131 | align-items: baseline;
132 | justify-content: center;
133 | }
134 |
135 | @media screen and (min-width: 750px) {
136 | .banner__box > * + .banner__buttons {
137 | margin-top: 2rem;
138 | }
139 | }
140 |
141 | .banner__content .button + .button {
142 | margin-top: 1.5rem;
143 | }
144 |
145 | .banner__content .button {
146 | height: auto;
147 | margin-right: 2rem;
148 | }
149 |
150 | .banner__box > * + .banner__text {
151 | margin-top: 1.5rem;
152 | }
153 |
154 | @media screen and (min-width: 750px) {
155 | .banner__box > * + .banner__text {
156 | margin-top: 2rem;
157 | }
158 | }
159 |
160 | .banner__box > * + * {
161 | margin-top: 1rem;
162 | }
163 |
164 | .banner__box > *:first-child {
165 | margin-top: 0;
166 | }
167 |
168 | @media screen and (max-width: 749px) {
169 | .banner__content .button {
170 | flex-grow: 1;
171 | }
172 |
173 | .banner--stacked .banner__box {
174 | width: 100%;
175 | }
176 | }
177 |
178 | @media screen and (min-width: 750px) {
179 | .banner__box {
180 | padding: 4rem;
181 | width: 54.8rem;
182 | }
183 |
184 | .banner__box > .banner__buttons:only-child .button {
185 | margin-top: 0;
186 | }
187 | }
188 |
189 | .banner__heading > *,
190 | .banner__text > * {
191 | word-wrap: break-word;
192 | }
193 |
194 | .banner__heading {
195 | margin-bottom: 0;
196 | }
197 |
--------------------------------------------------------------------------------
/shopify/assets/section-main-blog.css:
--------------------------------------------------------------------------------
1 | .blog-articles {
2 | display: grid;
3 | grid-gap: 1rem;
4 | }
5 |
6 | @media screen and (min-width: 750px) {
7 | .blog-articles {
8 | grid-template-columns: 1fr 1fr;
9 | }
10 |
11 | .blog-articles > *:first-child,
12 | .blog-articles > *:nth-child(4),
13 | .blog-articles > *:last-child:nth-child(2),
14 | .blog-articles > *:last-child:nth-child(5) {
15 | grid-column: span 2;
16 | text-align: center;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/shopify/assets/section-main-page.css:
--------------------------------------------------------------------------------
1 | .page-title {
2 | margin-top: 0;
3 | }
4 |
5 | .main-page-title {
6 | margin-bottom: 3rem;
7 | }
8 |
9 | @media screen and (min-width: 750px) {
10 | .main-page-title {
11 | margin-bottom: 4rem;
12 | }
13 | }
14 |
15 | .page-placeholder-wrapper {
16 | display: flex;
17 | justify-content: center;
18 | }
19 |
20 | .page-placeholder {
21 | width: 52.5rem;
22 | height: 52.5rem;
23 | }
24 |
--------------------------------------------------------------------------------
/shopify/assets/section-product-recommendations.css:
--------------------------------------------------------------------------------
1 | .product-recommendations {
2 | display: block;
3 | }
4 |
5 | .product-recommendations__heading {
6 | margin: 0;
7 | margin-bottom: 3rem;
8 | }
9 |
10 | .product-recommendations .grid__item {
11 | padding-bottom: 0;
12 | }
13 |
--------------------------------------------------------------------------------
/shopify/assets/section-rich-text.css:
--------------------------------------------------------------------------------
1 | .rich-text {
2 | margin: auto;
3 | max-width: 110rem;
4 | text-align: center;
5 | /* 1.5rem margin on left & right */
6 | width: calc(100% - 3rem);
7 | }
8 |
9 | .rich-text.rich-text--full-width {
10 | max-width: initial;
11 | width: 100%;
12 | }
13 |
14 | .rich-text__blocks {
15 | margin: auto;
16 | /* 2.5rem margin on left & right */
17 | width: calc(100% - 5rem);
18 | }
19 |
20 | .rich-text__blocks * {
21 | overflow-wrap: break-word;
22 | }
23 |
24 | .rich-text--full-width .rich-text__blocks {
25 | /* 4rem (1.5rem + 2.5rem) margin on left & right */
26 | width: calc(100% - 8rem);
27 | }
28 |
29 | .rich-text:not(.rich-text--full-width),
30 | .rich-text--full-width.color-background-1 {
31 | margin-top: 5rem;
32 | margin-bottom: 5rem;
33 | }
34 |
35 | .rich-text:not(.color-background-1) {
36 | padding-top: 5rem;
37 | padding-bottom: 5rem;
38 | }
39 |
40 | @media screen and (min-width: 750px) {
41 | .rich-text {
42 | /* 5rem margin on left & right */
43 | width: calc(100% - 10rem);
44 | }
45 |
46 | .rich-text__blocks {
47 | max-width: 50rem;
48 | }
49 |
50 | .rich-text--full-width .rich-text__blocks {
51 | /* 7.5rem (5rem + 2.5rem) margin on left & right */
52 | width: calc(100% - 15rem);
53 | }
54 | }
55 |
56 | @media screen and (min-width: 990px) {
57 | .rich-text__blocks {
58 | max-width: 78rem;
59 | }
60 | }
61 |
62 | /* Blocks */
63 |
64 | .rich-text__blocks > * {
65 | margin-top: 0;
66 | margin-bottom: 0;
67 | }
68 |
69 | .rich-text__blocks > * + * {
70 | margin-top: 2rem;
71 | }
72 |
73 | .rich-text__blocks > * + a {
74 | margin-top: 3rem;
75 | }
76 |
--------------------------------------------------------------------------------
/shopify/assets/share.js:
--------------------------------------------------------------------------------
1 | class ShareButton extends DetailsDisclosure {
2 | constructor() {
3 | super();
4 |
5 | this.elements = {
6 | shareButton: this.querySelector('button'),
7 | successMessage: this.querySelector('[id^="ShareMessage"]'),
8 | urlInput: this.querySelector('input')
9 | }
10 | if (navigator.share) {
11 | this.mainDetailsToggle.setAttribute('hidden', '');
12 | this.elements.shareButton.classList.remove('hidden');
13 | this.elements.shareButton.addEventListener('click', () => { navigator.share({ url: document.location.href, title: document.title }) });
14 | } else {
15 | this.mainDetailsToggle.addEventListener('toggle', this.toggleDetails.bind(this));
16 | this.mainDetailsToggle.querySelector('button').addEventListener('click', this.copyToClipboard.bind(this));
17 | }
18 | }
19 |
20 | toggleDetails() {
21 | if (!this.mainDetailsToggle.open)
22 | this.elements.successMessage.classList.add('hidden');
23 | }
24 |
25 | copyToClipboard() {
26 | navigator.clipboard.writeText(this.elements.urlInput.value).then(() => {
27 | this.elements.successMessage.classList.remove('hidden');
28 | this.elements.successMessage.setAttribute('aria-hidden', false);
29 |
30 | setTimeout(() => {
31 | this.elements.successMessage.setAttribute('aria-hidden', true);
32 | }, 6000);
33 | });
34 | }
35 | }
36 |
37 | customElements.define('share-button', ShareButton);
38 |
--------------------------------------------------------------------------------
/shopify/assets/slider.js:
--------------------------------------------------------------------------------
1 | class SliderComponent extends HTMLElement {
2 | constructor() {
3 | super();
4 | this.slider = this.querySelector('ul');
5 | this.sliderItems = this.querySelectorAll('li');
6 | this.pageCount = this.querySelector('.slider-counter--current');
7 | this.pageTotal = this.querySelector('.slider-counter--total');
8 | this.prevButton = this.querySelector('button[name="previous"]');
9 | this.nextButton = this.querySelector('button[name="next"]');
10 |
11 | if (!this.slider || !this.nextButton) return;
12 |
13 | const resizeObserver = new ResizeObserver(entries => this.initPages());
14 | resizeObserver.observe(this.slider);
15 |
16 | this.slider.addEventListener('scroll', this.update.bind(this));
17 | this.prevButton.addEventListener('click', this.onButtonClick.bind(this));
18 | this.nextButton.addEventListener('click', this.onButtonClick.bind(this));
19 | }
20 |
21 | initPages() {
22 | if (!this.sliderItems.length === 0) return;
23 | this.slidesPerPage = Math.floor(this.slider.clientWidth / this.sliderItems[0].clientWidth);
24 | this.totalPages = this.sliderItems.length - this.slidesPerPage + 1;
25 | this.update();
26 | }
27 |
28 | update() {
29 | if (!this.pageCount || !this.pageTotal) return;
30 | this.currentPage = Math.round(this.slider.scrollLeft / this.sliderItems[0].clientWidth) + 1;
31 |
32 | if (this.currentPage === 1) {
33 | this.prevButton.setAttribute('disabled', true);
34 | } else {
35 | this.prevButton.removeAttribute('disabled');
36 | }
37 |
38 | if (this.currentPage === this.totalPages) {
39 | this.nextButton.setAttribute('disabled', true);
40 | } else {
41 | this.nextButton.removeAttribute('disabled');
42 | }
43 |
44 | this.pageCount.textContent = this.currentPage;
45 | this.pageTotal.textContent = this.totalPages;
46 | }
47 |
48 | onButtonClick(event) {
49 | event.preventDefault();
50 | const slideScrollPosition = event.currentTarget.name === 'next' ? this.slider.scrollLeft + this.sliderItems[0].clientWidth : this.slider.scrollLeft - this.sliderItems[0].clientWidth;
51 | this.slider.scrollTo({
52 | left: slideScrollPosition
53 | });
54 | }
55 | }
56 |
57 | customElements.define('slider-component', SliderComponent);
58 |
--------------------------------------------------------------------------------
/shopify/config/settings_data.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/shopify/sections/announcement-bar.liquid:
--------------------------------------------------------------------------------
1 | {%- for block in section.blocks -%}
2 | {%- case block.type -%}
3 | {%- when 'announcement' -%}
4 |
20 | {%- endcase -%}
21 | {%- endfor -%}
22 |
23 | {% schema %}
24 | {
25 | "name": "t:sections.announcement-bar.name",
26 | "max_blocks": 12,
27 | "blocks": [
28 | {
29 | "type": "announcement",
30 | "name": "t:sections.announcement-bar.blocks.announcement.name",
31 | "settings": [
32 | {
33 | "type": "text",
34 | "id": "text",
35 | "default": "Welcome to our store",
36 | "label": "t:sections.announcement-bar.blocks.announcement.settings.text.label"
37 | },
38 | {
39 | "type": "select",
40 | "id": "color_scheme",
41 | "options": [
42 | {
43 | "value": "background-1",
44 | "label": "t:sections.announcement-bar.blocks.announcement.settings.color_scheme.options__1.label"
45 | },
46 | {
47 | "value": "background-2",
48 | "label": "t:sections.announcement-bar.blocks.announcement.settings.color_scheme.options__2.label"
49 | },
50 | {
51 | "value": "inverse",
52 | "label": "t:sections.announcement-bar.blocks.announcement.settings.color_scheme.options__3.label"
53 | },
54 | {
55 | "value": "accent-1",
56 | "label": "t:sections.announcement-bar.blocks.announcement.settings.color_scheme.options__4.label"
57 | },
58 | {
59 | "value": "accent-2",
60 | "label": "t:sections.announcement-bar.blocks.announcement.settings.color_scheme.options__5.label"
61 | }
62 | ],
63 | "default": "accent-1",
64 | "label": "t:sections.announcement-bar.blocks.announcement.settings.color_scheme.label"
65 | },
66 | {
67 | "type": "url",
68 | "id": "link",
69 | "label": "t:sections.announcement-bar.blocks.announcement.settings.link.label"
70 | }
71 | ]
72 | }
73 | ],
74 | "default": {
75 | "blocks": [
76 | {
77 | "type": "announcement"
78 | }
79 | ]
80 | }
81 | }
82 | {% endschema %}
83 |
--------------------------------------------------------------------------------
/shopify/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": "spaced-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 |
--------------------------------------------------------------------------------
/shopify/sections/cart-icon-bubble.liquid:
--------------------------------------------------------------------------------
1 | {%- liquid
2 | if cart == empty
3 | render 'icon-cart-empty'
4 | else
5 | render 'icon-cart'
6 | endif
7 | -%}
8 | {{ 'templates.cart.cart' | t }}
9 | {%- if cart != empty -%}
10 |
11 | {%- if cart.item_count < 100 -%}
12 | {{ cart.item_count }}
13 | {%- endif -%}
14 | {{ 'sections.header.cart_count' | t: count: cart.item_count }}
15 |
16 | {%- endif -%}
17 |
--------------------------------------------------------------------------------
/shopify/sections/cart-live-region-text.liquid:
--------------------------------------------------------------------------------
1 | {{ 'sections.cart.new_subtotal' | t }}: {{ cart.total_price | money_with_currency }}
2 |
--------------------------------------------------------------------------------
/shopify/sections/cart-notification-button.liquid:
--------------------------------------------------------------------------------
1 | {{ 'general.cart.view' | t: count: cart.item_count }}
2 |
--------------------------------------------------------------------------------
/shopify/sections/cart-notification-product.liquid:
--------------------------------------------------------------------------------
1 | {%- if cart != empty -%}
2 | {%- for item in cart.items -%}
3 |
4 | {% if item.image %}
5 |
12 | {% endif %}
13 |
14 |
{{ item.product.title | escape }}
15 | {%- unless item.product.has_only_default_variant -%}
16 |
17 | {%- for option in item.options_with_values -%}
18 |
19 |
{{ option.name }}:
20 | {{ option.value }}
21 |
22 | {%- endfor -%}
23 |
24 | {%- endunless -%}
25 |
26 |
27 | {%- endfor -%}
28 | {%- endif -%}
29 |
--------------------------------------------------------------------------------
/shopify/sections/contact-form.liquid:
--------------------------------------------------------------------------------
1 | {{ 'section-contact-form.css' | asset_url | stylesheet_tag }}
2 |
3 |
4 | {%- form 'contact', id: 'ContactForm' -%}
5 | {%- if form.posted_successfully? -%}
6 |
{% render 'icon-success' %} {{ 'templates.contact.form.post_success' | t }}
7 | {%- elsif form.errors -%}
8 |
9 |
10 |
11 |
18 | {%- endif -%}
19 |
50 |
51 |
52 | {{ 'templates.contact.form.phone' | t }}
53 |
54 |
55 |
64 | {{ 'templates.contact.form.comment' | t }}
65 |
66 |
67 |
68 | {{ 'templates.contact.form.send' | t }}
69 |
70 |
71 | {%- endform -%}
72 |
73 |
74 | {% schema %}
75 | {
76 | "name": "t:sections.contact-form.name",
77 | "tag": "section",
78 | "class": "spaced-section",
79 | "presets": [
80 | {
81 | "name": "t:sections.contact-form.presets.name"
82 | }
83 | ]
84 | }
85 | {% endschema %}
86 |
--------------------------------------------------------------------------------
/shopify/sections/custom-liquid.liquid:
--------------------------------------------------------------------------------
1 | {{ section.settings.custom_liquid }}
2 |
3 | {% schema %}
4 | {
5 | "name": "t:sections.custom-liquid.name",
6 | "tag": "section",
7 | "class": "spaced-section",
8 | "settings": [
9 | {
10 | "type": "liquid",
11 | "id": "custom_liquid",
12 | "label": "t:sections.custom-liquid.settings.custom_liquid.label"
13 | }
14 | ],
15 | "presets": [
16 | {
17 | "name": "t:sections.custom-liquid.presets.name"
18 | }
19 | ]
20 | }
21 | {% endschema %}
22 |
--------------------------------------------------------------------------------
/shopify/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 |
--------------------------------------------------------------------------------
/shopify/sections/main-blog.liquid:
--------------------------------------------------------------------------------
1 | {{ 'component-article-card.css' | asset_url | stylesheet_tag }}
2 | {{ 'component-card.css' | asset_url | stylesheet_tag }}
3 | {{ 'section-main-blog.css' | asset_url | stylesheet_tag }}
4 |
5 |
6 | {%- paginate blog.articles by 6 -%}
7 |
8 |
9 |
{{ blog.title | escape }}
10 |
11 |
12 | {%- for article in blog.articles -%}
13 |
14 | {%- render 'article-card', article: article, show_image: section.settings.show_image -%}
15 |
16 | {%- endfor -%}
17 |
18 |
19 | {%- if paginate.pages > 1 -%}
20 | {%- render 'pagination', paginate: paginate -%}
21 | {%- endif -%}
22 |
23 | {%- endpaginate -%}
24 |
25 | {% schema %}
26 | {
27 | "name": "t:sections.main-blog.name",
28 | "tag": "section",
29 | "class": "spaced-section",
30 | "settings": [
31 | {
32 | "type": "header",
33 | "content": "t:sections.main-blog.settings.header.content"
34 | },
35 | {
36 | "type": "checkbox",
37 | "id": "show_image",
38 | "default": true,
39 | "label": "t:sections.main-blog.settings.show_image.label",
40 | "info": "t:sections.main-blog.settings.show_image.info"
41 | },
42 | {
43 | "type": "paragraph",
44 | "content": "t:sections.main-blog.settings.paragraph.content"
45 | }
46 | ],
47 | "blocks": [
48 | {
49 | "type": "title",
50 | "name": "t:sections.main-blog.blocks.title.name",
51 | "limit": 1,
52 | "settings": [
53 | {
54 | "type": "checkbox",
55 | "id": "show_date",
56 | "default": true,
57 | "label": "t:sections.main-blog.blocks.title.settings.show_date.label"
58 | },
59 | {
60 | "type": "checkbox",
61 | "id": "show_author",
62 | "default": false,
63 | "label": "t:sections.main-blog.blocks.title.settings.show_author.label"
64 | }
65 | ]
66 | },
67 | {
68 | "type": "summary",
69 | "name": "t:sections.main-blog.blocks.summary.name",
70 | "limit": 1
71 | },
72 | {
73 | "type": "link",
74 | "name": "t:sections.main-blog.blocks.link.name",
75 | "limit": 1
76 | }
77 | ]
78 | }
79 | {% endschema %}
80 |
--------------------------------------------------------------------------------
/shopify/sections/main-collection-banner.liquid:
--------------------------------------------------------------------------------
1 | {{ 'component-collection-hero.css' | asset_url | stylesheet_tag }}
2 |
3 |
4 |
5 |
6 |
7 | {{ 'sections.collection_template.title' | t }}:
8 | {{- collection.title | escape -}}
9 |
10 |
11 | {%- if section.settings.show_collection_description -%}
12 |
{{ collection.description }}
13 | {%- endif -%}
14 |
15 |
16 | {%- if section.settings.show_collection_image and collection.image -%}
17 |
32 | {%- endif -%}
33 |
34 |
35 |
36 | {% schema %}
37 | {
38 | "name": "t:sections.main-collection-banner.name",
39 | "class": "spaced-section spaced-section--full-width",
40 | "settings": [
41 | {
42 | "type": "paragraph",
43 | "content": "t:sections.main-collection-banner.settings.paragraph.content"
44 | },
45 | {
46 | "type": "checkbox",
47 | "id": "show_collection_description",
48 | "default": false,
49 | "label": "t:sections.main-collection-banner.settings.show_collection_description.label"
50 | },
51 | {
52 | "type": "checkbox",
53 | "id": "show_collection_image",
54 | "default": false,
55 | "label": "t:sections.main-collection-banner.settings.show_collection_image.label",
56 | "info": "t:sections.main-collection-banner.settings.show_collection_image.info"
57 | }
58 | ]
59 | }
60 | {% endschema %}
61 |
--------------------------------------------------------------------------------
/shopify/sections/main-page.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'section-main-page.css' | asset_url | stylesheet_tag }}
5 | {{ 'component-rte.css' | asset_url | stylesheet_tag }}
6 |
7 |
8 |
9 | {{ page.title | escape }}
10 |
11 |
12 | {{ page.content }}
13 |
14 |
15 |
16 | {% schema %}
17 | {
18 | "name": "t:sections.main-page.name",
19 | "tag": "section",
20 | "class": "spaced-section"
21 | }
22 | {% endschema %}
23 |
--------------------------------------------------------------------------------
/shopify/sections/page.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'component-rte.css' | asset_url | stylesheet_tag }}
5 | {{ 'section-main-page.css' | asset_url | stylesheet_tag }}
6 |
7 |
8 |
9 | {%- if section.settings.page.title != blank -%}
10 | {{ section.settings.page.title | escape }}
11 | {%- else -%}
12 | Page title
13 | {%- endif -%}
14 |
15 |
16 | {%- if section.settings.page.content != blank -%}
17 | {{ section.settings.page.content }}
18 | {%- else -%}
19 |
20 | {{ 'page' | placeholder_svg_tag: 'page-placeholder' }}
21 |
22 | {%- endif -%}
23 |
24 |
25 |
26 | {% schema %}
27 | {
28 | "name": "t:sections.page.name",
29 | "tag": "section",
30 | "class": "spaced-section",
31 | "settings": [
32 | {
33 | "type": "page",
34 | "id": "page",
35 | "label": "t:sections.page.settings.page.label"
36 | }
37 | ],
38 | "presets": [
39 | {
40 | "name": "t:sections.page.presets.name"
41 | }
42 | ]
43 | }
44 | {% endschema %}
45 |
--------------------------------------------------------------------------------
/shopify/sections/pickup-availability.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}theme-check-disable UndefinedObject{% endcomment %}
2 | {%- assign pick_up_availabilities = product_variant.store_availabilities | where: 'pick_up_enabled', true -%}
3 |
4 | {%- if pick_up_availabilities.size > 0 -%}
5 |
6 | {%- liquid
7 | assign closest_location = pick_up_availabilities.first
8 |
9 | if closest_location.available
10 | render 'icon-tick'
11 | endif
12 | -%}
13 |
14 |
15 | {%- if closest_location.available -%}
16 |
{{ 'products.product.pickup_availability.pick_up_available_at_html' | t: location_name: closest_location.location.name }}
17 |
{{ closest_location.pick_up_time }}
18 |
19 | {%- if pick_up_availabilities.size == 1 -%}
20 | {{ 'products.product.pickup_availability.view_store_info' | t }}
21 | {%- else -%}
22 | {{ 'products.product.pickup_availability.check_other_stores' | t }}
23 | {%- endif -%}
24 |
25 | {%- else -%}
26 |
{{ 'products.product.pickup_availability.pick_up_unavailable_at_html' | t: location_name: closest_location.location.name }}
27 | {%- if pick_up_availabilities.size > 1 -%}
28 |
{{ 'products.product.pickup_availability.check_other_stores' | t }}
29 | {%- endif -%}
30 | {%- endif -%}
31 |
32 |
33 |
34 |
35 |
39 |
40 | {%- unless product_variant.product.has_only_default_variant -%}
41 |
42 | {%- for product_option in product_variant.product.options_with_values -%}
43 | {{ product_option.name | escape }}:
44 | {%- for value in product_option.values -%}
45 | {%- if product_option.selected_value == value -%}
46 | {{ value | escape }}
47 | {%- endif -%}
48 | {%- endfor -%}
49 | {%- unless forloop.last -%}, {%- endunless forloop.last -%}
50 | {%- endfor -%}
51 |
52 | {%- endunless -%}
53 |
54 |
75 |
76 | {%- endif -%}
77 |
--------------------------------------------------------------------------------
/shopify/sections/test/Test.ts:
--------------------------------------------------------------------------------
1 | // Something
2 |
--------------------------------------------------------------------------------
/shopify/sections/test/_test.scss:
--------------------------------------------------------------------------------
1 | // Something
2 |
--------------------------------------------------------------------------------
/shopify/snippets/article-card.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Renders an article card for a given blog with settings to either show the image or not.
3 |
4 | Accepts:
5 | - blog: {Object} Blog object
6 | - article: {Object} Article object
7 | - show_image: {String} The setting either show the article image or not. If it's not included it will show the image by default
8 |
9 | Usage:
10 | {% render 'article-card' blog: blog, article: article, show_image: section.settings.show_image %}
11 | {% endcomment %}
12 |
13 |
14 |
15 | {%- if show_image == true and article.image -%}
16 |
17 |
18 |
33 |
34 |
35 | {%- endif -%}
36 |
37 |
38 | {%- for block in section.blocks -%}
39 | {%- case block.type -%}
40 | {%- when 'title'-%}
41 |
42 |
43 | {{ article.title | escape }}
44 |
45 | {%- if block.settings.show_date -%}
46 |
47 | {{- article.published_at | time_tag: format: 'month_year' -}}
48 |
49 | {%- endif -%}
50 | {%- if block.settings.show_author -%}
51 | {{ article.author -}}
52 | {%- endif -%}
53 |
54 |
55 | {%- when 'summary'-%}
56 | {%- if article.excerpt.size > 0 or article.content.size > 0 -%}
57 |
58 | {%- if article.excerpt.size > 0 -%}
59 | {{ article.excerpt | strip_html | truncatewords: 30 }}
60 | {%- else -%}
61 | {{ article.content | strip_html | truncatewords: 30 }}
62 | {%- endif -%}
63 |
64 | {%- endif -%}
65 |
66 | {%- when 'link'-%}
67 |
68 |
69 | {{ 'blogs.article.read_more' | t }}
70 |
71 |
72 | {%- if article.comments_count > 0 and blog.comments_enabled? -%}
73 | {{ 'blogs.article.comments' | t: count: article.comments_count }}
74 | {%- endif -%}
75 |
76 | {%- endcase -%}
77 | {%- endfor -%}
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/shopify/snippets/cart-notification.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
16 |
{{ 'general.continue_shopping' | t }}
17 |
18 |
19 |
20 |
21 | {% style %}
22 | .cart-notification {
23 | display: none;
24 | }
25 | {% endstyle %}
26 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-3d-model.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-account.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-arrow.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-caret.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-cart-empty.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-cart.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-checkmark.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-clipboard.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-close-small.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-close.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-discount.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-error.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-facebook.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-filter.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-hamburger.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-instagram.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-minus.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-padlock.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-pinterest.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-play.liquid:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-plus.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-remove.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-share.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-snapchat.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-success.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-tick.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-tiktok.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-tumblr.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-twitter.liquid:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-unavailable.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-vimeo.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-youtube.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shopify/snippets/icon-zoom.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/shopify/snippets/meta-tags.liquid:
--------------------------------------------------------------------------------
1 | {%- liquid
2 | assign og_title = page_title | default: shop.name
3 | assign og_url = canonical_url | default: shop.url
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 == 'collection'
12 | assign og_type = 'product.group'
13 | elsif request.page_type == 'password'
14 | assign og_url = shop.url
15 | endif
16 | %}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {%- if page_image -%}
25 |
26 |
27 |
28 |
29 | {%- endif -%}
30 |
31 | {%- if request.page_type == 'product' -%}
32 |
33 |
34 | {%- endif -%}
35 |
36 | {%- if settings.social_twitter_link != blank -%}
37 |
38 | {%- endif -%}
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/shopify/snippets/pagination.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Renders a set of links for paginated results. Must be used within paginate tags.
3 |
4 | Usage:
5 | {% paginate results by 2 %}
6 | {% render 'pagination', paginate: paginate, anchor: '#yourID' %}
7 | {% endpaginate %}
8 |
9 | Accepts:
10 | - paginate: {Object}
11 | - anchor: {String} (optional) This can be added so that on page reload it takes you to wherever you've placed your anchor tag.
12 | - class: {String} (optional) Appended to container element's class attribute
13 | {% endcomment %}
14 |
15 |
16 | {{ 'component-pagination.css' | asset_url | stylesheet_tag }}
17 |
18 | {%- if paginate.parts.size > 0 -%}
19 |
54 | {%- endif -%}
55 |
--------------------------------------------------------------------------------
/shopify/snippets/price.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Renders a list of product's price (regular, sale)
3 |
4 | Accepts:
5 | - product: {Object} Product Liquid object (optional)
6 | - use_variant: {Boolean} Renders selected or first variant price instead of overall product pricing (optional)
7 | - show_badges: {Boolean} Renders 'Sale' and 'Sold Out' tags if the product matches the condition (optional)
8 | - price_class: {String} Adds a price class to the price element (optional)
9 |
10 | Usage:
11 | {% render 'price', product: product %}
12 | {% endcomment %}
13 | {%- liquid
14 | if use_variant
15 | assign target = product.selected_or_first_available_variant
16 | else
17 | assign target = product
18 | endif
19 |
20 | assign compare_at_price = target.compare_at_price
21 | assign price = target.price | default: 1999
22 | assign available = target.available | default: false
23 | assign money_price = price | money
24 |
25 | if target == product and product.price_varies
26 | assign money_price = 'products.product.price.from_price_html' | t: price: money_price
27 | endif
28 | -%}
29 |
30 |
35 |
36 | {%- comment -%}
37 | Explanation of description list:
38 | - div.price__regular: Displayed when there are no variants on sale
39 | - div.price__sale: Displayed when a variant is a sale
40 | - div.price__availability: Displayed when the product is sold out
41 | {%- endcomment -%}
42 |
43 |
44 | {{ 'products.product.price.regular_price' | t }}
45 |
46 |
47 |
48 | {{ money_price }}
49 |
50 |
51 |
52 |
53 |
54 | {{ 'products.product.price.regular_price' | t }}
55 |
56 |
57 |
58 | {{ compare_at_price | money }}
59 |
60 |
61 |
62 | {{ 'products.product.price.sale_price' | t }}
63 |
64 |
65 |
66 | {{ money_price }}
67 |
68 |
69 |
70 |
71 | {{ 'products.product.price.unit_price' | t }}
72 |
73 | {{- product.selected_or_first_available_variant.unit_price | money -}}
74 | /
75 | {{ 'accessibility.unit_price_separator' | t }}
76 |
77 | {%- if product.selected_or_first_available_variant.unit_price_measurement.reference_value != 1 -%}
78 | {{- product.selected_or_first_available_variant.unit_price_measurement.reference_value -}}
79 | {%- endif -%}
80 | {{ product.selected_or_first_available_variant.unit_price_measurement.reference_unit }}
81 |
82 |
83 |
84 |
85 | {%- if show_badges -%}
86 |
87 | {{ 'products.product.on_sale' | t }}
88 |
89 |
90 |
91 | {{ 'products.product.sold_out' | t }}
92 |
93 | {%- endif -%}
94 |
95 |
--------------------------------------------------------------------------------
/shopify/snippets/product-card-placeholder.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Renders a product card placeholder
3 |
4 | Usage:
5 | {% render 'product-card-placeholder' %}
6 | {% endcomment %}
7 |
8 |
23 |
--------------------------------------------------------------------------------
/shopify/snippets/social-sharing.liquid:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/shopify/templates/404.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-404"
5 | }
6 | },
7 | "order": [
8 | "main"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/shopify/templates/article.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-article",
5 | "blocks": {
6 | "featured_image": {
7 | "type": "featured_image"
8 | },
9 | "title": {
10 | "type": "title"
11 | },
12 | "content": {
13 | "type": "content"
14 | },
15 | "social_sharing": {
16 | "type": "social_sharing"
17 | }
18 | },
19 | "block_order": [
20 | "featured_image",
21 | "title",
22 | "content",
23 | "social_sharing"
24 | ]
25 | }
26 | },
27 | "order": ["main"]
28 | }
29 |
--------------------------------------------------------------------------------
/shopify/templates/blog.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-blog",
5 | "blocks": {
6 | "title": {
7 | "type": "title"
8 | },
9 | "summary": {
10 | "type": "summary"
11 | },
12 | "link": {
13 | "type": "link"
14 | }
15 | },
16 | "block_order": [
17 | "title",
18 | "summary",
19 | "link"
20 | ]
21 | }
22 | },
23 | "order": [
24 | "main"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/shopify/templates/cart.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "cart-items": {
4 | "type": "main-cart-items"
5 | },
6 | "cart-footer": {
7 | "type": "main-cart-footer",
8 | "blocks": {
9 | "subtotal": {
10 | "type": "subtotal"
11 | },
12 | "buttons": {
13 | "type": "buttons"
14 | }
15 | },
16 | "block_order": [
17 | "subtotal",
18 | "buttons"
19 | ]
20 | }
21 | },
22 | "order": [
23 | "cart-items",
24 | "cart-footer"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/shopify/templates/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "banner": {
4 | "type": "main-collection-banner"
5 | },
6 | "product-grid": {
7 | "type": "main-collection-product-grid"
8 | }
9 | },
10 | "order": [
11 | "banner",
12 | "product-grid"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/shopify/templates/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "image_banner": {
4 | "type": "image-banner",
5 | "settings": {
6 | "desktop_text_box_position": "flex-end"
7 | },
8 | "blocks": {
9 | "heading": {
10 | "type": "heading",
11 | "settings": {
12 | "heading": "Talk about your brand"
13 | }
14 | },
15 | "button": {
16 | "type": "buttons",
17 | "settings": {
18 | "button_label_1": "Shop all",
19 | "button_link_1": "shopify://collections/all",
20 | "button_label_2": ""
21 | }
22 | }
23 | },
24 | "block_order": ["heading", "button"]
25 | },
26 | "featured_products": {
27 | "type": "featured-collection",
28 | "settings": {
29 | "title": "Featured products"
30 | }
31 | },
32 | "image_text": {
33 | "type": "image-with-text",
34 | "blocks": {
35 | "heading": {
36 | "type": "heading"
37 | },
38 | "text": {
39 | "type": "text"
40 | },
41 | "button": {
42 | "type": "button"
43 | }
44 | },
45 | "block_order": [
46 | "heading",
47 | "text",
48 | "button"
49 | ]
50 | }
51 | },
52 | "order": [
53 | "image_banner",
54 | "featured_products",
55 | "image_text"
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/shopify/templates/list-collections.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-list-collections"
5 | }
6 | },
7 | "order": [
8 | "main"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/shopify/templates/page.contact.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-page"
5 | },
6 | "form": {
7 | "type": "contact-form"
8 | }
9 | },
10 | "order": [
11 | "main",
12 | "form"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/shopify/templates/page.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-page"
5 | }
6 | },
7 | "order": [
8 | "main"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/shopify/templates/password.json:
--------------------------------------------------------------------------------
1 | {
2 | "layout": "password",
3 | "sections": {
4 | "main": {
5 | "type": "newsletter",
6 | "settings": {
7 | "full_width": false
8 | },
9 | "blocks": {
10 | "heading": {
11 | "type": "heading",
12 | "settings": {
13 | "heading": "Opening soon"
14 | }
15 | },
16 | "paragraph": {
17 | "type": "paragraph",
18 | "settings": {
19 | "paragraph": "Be the first to know when we launch.
"
20 | }
21 | },
22 | "email_form": {
23 | "type": "email_form"
24 | }
25 | },
26 | "block_order": [
27 | "heading",
28 | "paragraph",
29 | "email_form"
30 | ]
31 | }
32 | },
33 | "order": [
34 | "main"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/shopify/templates/product.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-product",
5 | "blocks": {
6 | "vendor": {
7 | "type": "text",
8 | "settings": {
9 | "text_style": "uppercase",
10 | "text": "{{ product.vendor }}"
11 | }
12 | },
13 | "title": {
14 | "type": "title"
15 | },
16 | "subtitle": {
17 | "type": "text",
18 | "settings": {
19 | "text": "{{ product.metafields.descriptors.subtitle.value }}",
20 | "text_style": "subtitle"
21 | }
22 | },
23 | "price": {
24 | "type": "price"
25 | },
26 | "variant_picker": {
27 | "type": "variant_picker"
28 | },
29 | "quantity_selector": {
30 | "type": "quantity_selector"
31 | },
32 | "buy_buttons": {
33 | "type": "buy_buttons"
34 | },
35 | "description": {
36 | "type": "description"
37 | },
38 | "share": {
39 | "type": "share"
40 | }
41 | },
42 | "block_order": [
43 | "vendor",
44 | "title",
45 | "subtitle",
46 | "price",
47 | "variant_picker",
48 | "quantity_selector",
49 | "buy_buttons",
50 | "description",
51 | "share"
52 | ]
53 | },
54 | "product-recommendations": {
55 | "type": "product-recommendations"
56 | }
57 | },
58 | "order": [
59 | "main",
60 | "product-recommendations"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/shopify/templates/search.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": {
3 | "main": {
4 | "type": "main-search"
5 | }
6 | },
7 | "order": [
8 | "main"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/__test__/cart/cart.spec.ts:
--------------------------------------------------------------------------------
1 | // import * as Cart from '../../helpers/cart/cart'
2 |
3 | // describe("Cart API", () => {
4 | // // test("it should filter by a search term (link)", () => {
5 | // // Cart.addItem()
6 |
7 | // // });
8 | // });
9 |
--------------------------------------------------------------------------------
/src/__test__/dom/dom.spec.ts:
--------------------------------------------------------------------------------
1 | import { addClass, qs, qsa } from '../../helpers/dom/dom';
2 |
3 | /**
4 | * @see https://noriste.github.io/reactjsday-2019-testing-course/book/intro-to-react-testing/jest-dom.html
5 | */
6 | describe("DOM helpers test kmacoders", () => {
7 | test("Test qsa", () => {
8 | document.body.innerHTML = `
9 |
13 | `;
14 |
15 | const output = 1;
16 | const dataTestValue = qsa('.xo-item')[0].getAttribute('data-test');
17 |
18 | expect(qsa('.xo-item').length).toBe(1);
19 | expect(dataTestValue).toEqual('1');
20 | expect(qsa('.xo-item2')[0]).toBeEmptyDOMElement;
21 | });
22 |
23 | test("test qs", () => {
24 | document.body.innerHTML = `
25 |
28 | `;
29 |
30 | const output = true;
31 | const vanillaDOM = document.querySelector('.xo-item');
32 | expect(qs('.xo-item') === vanillaDOM).toBe(output);
33 | });
34 |
35 | test("test addClass", () => {
36 | document.body.innerHTML = `
37 |
40 | `;
41 |
42 | const output = true;
43 | const xoItem = document.querySelector('.xo-item') as HTMLElement;
44 | addClass(xoItem, 'new-class');
45 |
46 | expect(xoItem.classList.contains('new-class')).toBe(output);
47 | });
48 | test("Test qsa", () => {
49 | document.body.innerHTML = `
50 |
54 | `;
55 |
56 | const output = 1;
57 | const dataTestValue = qsa('.xo-item')[0].getAttribute('data-test');
58 |
59 | expect(qsa('.xo-item').length).toBe(1);
60 | expect(dataTestValue).toEqual('1');
61 | expect(qsa('.xo-item2')[0]).toBeEmptyDOMElement;
62 | });
63 |
64 | test("test qs", () => {
65 | document.body.innerHTML = `
66 |
69 | `;
70 |
71 | const output = true;
72 | const vanillaDOM = document.querySelector('.xo-item');
73 | expect(qs('.xo-item') === vanillaDOM).toBe(output);
74 | });
75 |
76 | test("test addClass", () => {
77 | document.body.innerHTML = `
78 |
81 | `;
82 |
83 | const output = true;
84 | const xoItem = document.querySelector('.xo-item') as HTMLElement;
85 | addClass(xoItem, 'new-class');
86 |
87 | expect(xoItem.classList.contains('new-class')).toBe(output);
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/src/__test__/test/app.spec.ts:
--------------------------------------------------------------------------------
1 | import { filterByTerm } from '../../app'
2 |
3 | describe("Filter function", () => {
4 | test("it should filter by a search term (link)", () => {
5 | const input = [
6 | { id: 1, url: "https://www.url1.dev" },
7 | { id: 2, url: "https://www.url2.dev" },
8 | { id: 3, url: "https://www.link3.dev" }
9 | ];
10 |
11 | const output = [{ id: 3, url: "https://www.link3.dev" }];
12 |
13 | expect(filterByTerm(input, "link")).toEqual(output);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/helpers/dom/common.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @example
3 | *
4 | * ```
5 | * const styleEl = `
6 | *
11 | * `;
12 | *
13 | * appendToHead({
14 | * type: 'html',
15 | * childEl: styleEl,
16 | * });
17 | * ```
18 | * @param config
19 | */
20 | const appendToHead = (config: {
21 | type: 'html' | 'element',
22 | childEl: string | HTMLStyleElement | HTMLScriptElement
23 | }): void => {
24 | const { type, childEl } = config;
25 | const headHTML = document.head || document.getElementsByTagName('head')[0];
26 |
27 | type === 'html' && headHTML.insertAdjacentHTML('beforeend', childEl as string);
28 | type === 'element' && headHTML.insertAdjacentElement('beforeend', childEl as HTMLStyleElement | HTMLScriptElement);
29 | };
30 |
31 | /**
32 | *
33 | * @example
34 | * ```
35 | * const previewElement = document.querySelector('.preview-el');
36 | *
37 | * css(previewElement, {
38 | * display: 'none',
39 | * color: '#f00';
40 | * font-size: '2rem';
41 | * });
42 | * ```
43 | *
44 | * @param el
45 | * @param styleObj
46 | */
47 | const css = (el: HTMLElement, styleObj: any): void => {
48 | Object.keys(styleObj).forEach((key: any) => {
49 | // eslint-disable-next-line no-param-reassign
50 | el.style[key] = styleObj[key];
51 | });
52 | };
53 |
54 | /**
55 | * Active only classname in ctx
56 | * @example
57 | *
58 | * ```
59 | * const btn = document.getElementById('btn');
60 | * const wrapper = document.querySelector('.xo-wrapper');
61 | *
62 | * btn.addEventListener('click', () => {
63 | * activeClass(btn, 'xo--active', wrapper);
64 | * })
65 | * ```
66 | *
67 | * @param el - Target element
68 | * @param className - Class to toggle to target element
69 | * @param ctx - Context document
70 | */
71 | const activeClassInCtx = (el: HTMLElement, className: string, ctx: T | Document = document): void => {
72 | ctx.querySelector(`.${className}`)!.classList.remove(className);
73 | el.classList.add(className);
74 | };
75 |
76 | export {
77 | css,
78 | activeClassInCtx,
79 | appendToHead,
80 | };
81 |
--------------------------------------------------------------------------------
/src/helpers/dom/event.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Add new event to element
3 | *
4 | * #### Example
5 | *
6 | * ```
7 | * const btn = byId('submit');
8 | * const handler = (e) => { ... }
9 | * events.on(btn, 'click', handler);
10 | * ```
11 | *
12 | */
13 | const $on = (target: Document | T, type: string, handler: EventListener): void => {
14 | target.addEventListener(type, handler);
15 | };
16 |
17 | /**
18 | * turn off event from element
19 | * #### Example
20 | *
21 | * ```
22 | * const btn = byId('submit');
23 | * const handler = (e) => { ... }
24 | * events.on(btn, 'click', handler);
25 | * events.off(btn, 'click', handler);
26 | * ```
27 | *
28 | */
29 |
30 | const $off = (target: Document, type: string, handler: EventListener): void => {
31 | target.removeEventListener(type, handler);
32 | };
33 |
34 | export {
35 | $on,
36 | $off,
37 | };
38 |
--------------------------------------------------------------------------------
/src/helpers/index.ts:
--------------------------------------------------------------------------------
1 | // Helper nào cần import ở index mới dùng được thì import ở đây
2 | // Helper dạng require lúc code trong các components thì thôi :v
3 |
--------------------------------------------------------------------------------
/src/helpers/sections/index.ts:
--------------------------------------------------------------------------------
1 | import { qs } from '../dom/dom';
2 |
3 | /**
4 | * Run script callback when a section is selected
5 | * @param {string} className: Root classname section
6 | * @param {Function} cb: Callback when this section is selected
7 | */
8 | export const onSectionSelected = (className: string, cb: () => void): void => {
9 | qs(className) && cb();
10 | };
11 |
12 | /**
13 | * Debounce function
14 | * @param {Function} func : Function want to delay
15 | * @param {number} waitFor : Time to delay
16 | * @returns {Function}
17 | */
18 |
19 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
20 | // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
21 | export const debounce = any>(func: F, waitFor: number) => {
22 | let timeout = 0;
23 |
24 | const debounced = (...args: any): void => {
25 | clearTimeout(timeout);
26 | timeout = setTimeout(() => func(...args), waitFor);
27 | };
28 |
29 | return debounced as (...args: Parameters) => ReturnType;
30 | };
31 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2 | // @ts-nocheck
3 | import Vue, { createApp } from 'vue';
4 | import 'Vue/config';
5 | import 'Vue/filters';
6 | import store from './vue/store';
7 |
8 | /**
9 | * SCSS
10 | */
11 | import 'swiper/swiper-bundle.css';
12 | import './styles/main.scss';
13 |
14 | /**
15 | * TS
16 | */
17 | import './helpers';
18 | import './vue/components/entry';
19 |
20 | /**
21 | * Auto find and import all .ts file in Shopify folder
22 | */
23 | const tsFiles = require.context('Shopify/', true, /\.ts$/);
24 | tsFiles.keys().forEach(tsFiles);
25 |
26 | /**
27 | * vue components
28 | * auto-import all vue components
29 | */
30 | const vueComponents = require.context('./vue/components/globals/', true, /\.vue$/);
31 | // vueComponents.keys().forEach((key) => {
32 | // const component = vueComponents(key).default;
33 | // Vue.component(component.name, component);
34 | // });
35 |
36 | /**
37 | * All SECTION is vue instance ( template vue )
38 | *
39 | * Properly render vue components inside sections on user insert in the theme editor
40 | * add the 'vue' keyword to the section's wrapper classes e.g.:
41 | *
42 | * {% schema %}
43 | * {
44 | * "class": "vue-section"
45 | * }
46 | * {% endschema %}
47 | */
48 |
49 | /* If merchant in designMode */
50 | Shopify.designMode && document.addEventListener('shopify:section:load', (event) => {
51 | if (event.target.classList.value.includes('vue-section')) {
52 | const app = createApp({
53 | delimiters: ['${', '}'],
54 | store,
55 | });
56 |
57 | vueComponents.keys().forEach((key) => {
58 | const component = vueComponents(key).default;
59 | app.component(component.name, component);
60 | });
61 |
62 | app.mount(event.target);
63 | }
64 | });
65 |
66 | /* If merchant in normalMode ( is Section ) */
67 | document.querySelectorAll('.shopify-section').forEach((section) => {
68 | if (section.classList.value.includes('vue-section')) {
69 | const app = createApp({
70 | delimiters: ['${', '}'],
71 | store,
72 | });
73 |
74 | vueComponents.keys().forEach((key) => {
75 | const component = vueComponents(key).default;
76 | app.component(component.name, component);
77 | });
78 |
79 | app.mount(section);
80 | }
81 | });
82 |
83 | /** If vue instace != section */
84 | document.querySelectorAll('[data-vue-instance]').forEach((element) => {
85 | const newInstanceVue = createApp({
86 | el: element,
87 | store,
88 | });
89 | vueComponents.keys().forEach((key) => {
90 | const component = vueComponents(key).default;
91 | newInstanceVue.component(component.name, component);
92 | });
93 | });
94 |
95 | console.log('kmacoders developing..');
96 |
--------------------------------------------------------------------------------
/src/styles/_general.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/_general.scss
--------------------------------------------------------------------------------
/src/styles/_vueGeneral.scss:
--------------------------------------------------------------------------------
1 | [v-cloak] {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/src/styles/base/_base-color.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/base/_base-color.scss
--------------------------------------------------------------------------------
/src/styles/base/_base-dir.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/base/_base-dir.scss
--------------------------------------------------------------------------------
/src/styles/base/_heading-base.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/base/_heading-base.scss
--------------------------------------------------------------------------------
/src/styles/components/_components-dir.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/components/_components-dir.scss
--------------------------------------------------------------------------------
/src/styles/components/button/_btn.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/components/button/_btn.scss
--------------------------------------------------------------------------------
/src/styles/components/loading/_loading-ui.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/components/loading/_loading-ui.scss
--------------------------------------------------------------------------------
/src/styles/components/toast/_toast.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/components/toast/_toast.scss
--------------------------------------------------------------------------------
/src/styles/helpers/_helpers-dir.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/helpers/_helpers-dir.scss
--------------------------------------------------------------------------------
/src/styles/layout/_layout-dir.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/layout/_layout-dir.scss
--------------------------------------------------------------------------------
/src/styles/main.scss:
--------------------------------------------------------------------------------
1 | /* All import */
2 |
3 | /*
4 | * Vendor
5 | */
6 | @import './vendors/reset';
7 | @import './vendors/variants'; // File chứa variable cho file grid bên dứoi
8 | @import './vendors/grid'; // Grid BS4
9 | @import './vendors/normalize'; // Reset CSS
10 |
11 | /*
12 | * Utils
13 | */
14 | @import './utils/utils-dir';
15 |
16 | /*
17 | * Pages
18 | */
19 | @import './pages/pages-dir';
20 |
21 | /*
22 | * Layout
23 | */
24 | @import './layout/layout-dir';
25 |
26 | /*
27 | * Components
28 | */
29 | @import './components/components-dir';
30 |
31 | /*
32 | * Snippet
33 | */
34 | @import './snippet/background-image';
35 |
36 | /*
37 | * Sections
38 | */
39 | @import './sections/sections-dir';
40 |
41 | /*
42 | * Base
43 | */
44 | @import './base/base-dir';
45 |
46 | /*
47 | * Helpers
48 | */
49 | @import './helpers/helpers-dir';
50 |
51 | /* Auto impo@import all scss file from Theme folder */
52 | /* Using @see https://www.npmjs.com/package/impo@import-glob-loader */
53 | @import "../../shopify/**/*.scss";
54 |
55 | /*
56 | * Code chung
57 | */
58 | @import './general';
59 | @import './vueGeneral';
60 |
--------------------------------------------------------------------------------
/src/styles/pages/_body.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/pages/_body.scss
--------------------------------------------------------------------------------
/src/styles/pages/_pages-dir.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/pages/_pages-dir.scss
--------------------------------------------------------------------------------
/src/styles/pages/collection/_all-collection.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/pages/collection/_all-collection.scss
--------------------------------------------------------------------------------
/src/styles/pages/collection/_normal-collection.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/pages/collection/_normal-collection.scss
--------------------------------------------------------------------------------
/src/styles/pages/collection/_page-alles.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/pages/collection/_page-alles.scss
--------------------------------------------------------------------------------
/src/styles/pages/customer/_account.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/pages/customer/_account.scss
--------------------------------------------------------------------------------
/src/styles/sections/_sections-dir.scss:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/styles/snippet/_background-image.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/styles/snippet/_background-image.scss
--------------------------------------------------------------------------------
/src/styles/utils/_utils-dir.scss:
--------------------------------------------------------------------------------
1 | @import './mixins/prefix';
2 | @import './mixins/center';
3 | @import './mixins/position';
4 | @import './mixins/responsive';
5 | @import './mixins/size';
6 | @import './functions/get-variable-css';
7 | @import './mixins/three-dots';
8 |
--------------------------------------------------------------------------------
/src/styles/utils/functions/_get-variable-css.scss:
--------------------------------------------------------------------------------
1 | /// Get color variable from css to scss
2 | /// @param $color-props: color variable : --color-text-rgb, --color-body-text
3 | ///
4 | @function color($color-props) {
5 | @return var(--color-#{$color-props});
6 | }
7 |
8 | /// Get font variable from css to scss
9 | /// @param $font-props: font variable : --font-size-header, --font-size-base
10 | ///
11 | @function font($font-props) {
12 | @return var(--font-#{$font-props});
13 | }
14 |
15 | /// Get all general variable from css to scss
16 | @function v($props) {
17 | @return var(--#{$props});
18 | }
19 |
20 | /// Example :
21 | // :root {
22 | // --color-background: #FFFFFF;
23 | // }
24 |
25 | // body {
26 | // color: color(primary);
27 | // }
28 |
29 | // compiled sass code is:
30 |
31 | // body {
32 | // color: var(--color-primary);
33 | // }
34 |
35 | // .kmacoders {
36 | // color: v(--color-background);
37 | // }
38 |
39 | // compiled sass code is:
40 |
41 | // .kmacoders {
42 | // color: var(----color-background);
43 | // }
44 |
--------------------------------------------------------------------------------
/src/styles/utils/mixins/_center.scss:
--------------------------------------------------------------------------------
1 | /* Self center vertical */
2 | @mixin vertical-align-center {
3 | position: relative;
4 | top: 50%;
5 | @include prefix(transform, translateY(-50%));
6 | }
7 |
8 | /* Self center horizontal */
9 | @mixin horizontal-align-center {
10 | position: relative;
11 | left: 50%;
12 | @include prefix(transform, translateX(-50%));
13 | }
14 |
15 | /* Self center both direction */
16 | @mixin both-align-center {
17 | position: relative;
18 | left: 50%;
19 | top: 50%;
20 | @include prefix(transform, translate(-50%, -50%));
21 | }
22 |
--------------------------------------------------------------------------------
/src/styles/utils/mixins/_position.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Add position property
3 | *
4 | * @param {String} $position - relative | absolute | fixed
5 | * @param {Length} $args - List direction property and value
6 | *
7 | * @example
8 | * - Usage:
9 | * .foo {
10 | * @include position(relative, top 0 left 1em);
11 | * }
12 | * - Output:
13 | * .foo {
14 | position: relative;
15 | top: 0;
16 | left: 1em;
17 | * }
18 | * ...or other way :
19 | * .foo {
20 | * @include position(relative, '');
21 | * }
22 | */
23 | @mixin position($position, $args) {
24 | @each $o in top right bottom left {
25 | $i: index($args, $o);
26 |
27 | @if $i and $i + 1<= length($args) and type-of(nth($args, $i + 1)) == number {
28 | #{$o}: nth($args, $i + 1);
29 | }
30 | }
31 |
32 | position: $position;
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/utils/mixins/_prefix.scss:
--------------------------------------------------------------------------------
1 | /* VERSION 1 ***********************************************/
2 | /*
3 | * Add prefix for property css
4 | *
5 | * @param $name : name of property
6 | * @param $value : value of property
7 | *
8 | * @example
9 | * - Usage:
10 | * .foo {
11 | * @inlucde prefix(transform, translateY(-50%));
12 | * }
13 | */
14 | @mixin prefix($name, $value) {
15 | -webkit-#{$name}: $value;
16 | -moz-#{$name}: $value;
17 | -ms-#{$name}: $value;
18 | -o-#{$name}: $value;
19 | #{$name}: $value;
20 | }
21 |
22 | /* VERSION 2 : Avanced version ****************************************/
23 | /*
24 | * Mixin to prefix several properties at once
25 | *
26 | * @param {List} $prefixes (()) - List of prefixes to print
27 | * @example
28 | * - Usage:
29 | * .foo {
30 | * @include prefix((
31 | * column-count: 3,
32 | * column-gap: 1.5em,
33 | * column-rule: 2px solid hotpink
34 | * ), webkit moz);
35 | * }
36 | * - Output:
37 | * .foo {
38 | * -webkit-column-count: 3;
39 | * -moz-column-count: 3;
40 | * column-count: 3;
41 | * -webkit-column-gap: 1.5em;
42 | * -moz-column-gap: 1.5em;
43 | * column-gap: 1.5em;
44 | * -webkit-column-rule: 2px solid hotpink;
45 | * -moz-column-rule: 2px solid hotpink;
46 | * column-rule: 2px solid hotpink;
47 | *}
48 | */
49 | @mixin prefixServeral($declarations, $prefixes: ()) {
50 | @each $property, $value in $declarations {
51 | @each $prefix in $prefixes {
52 | #{'-' + $prefix + '-' + $property}: $value;
53 | }
54 |
55 | // Output standard non-prefixed declaration
56 | #{$property}: $value;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/styles/utils/mixins/_responsive.scss:
--------------------------------------------------------------------------------
1 | /* Screen size ( Size này cùng với Theme Config global cho JS) */
2 | $SM: 576px;
3 | $MD: 768px;
4 | $LG: 992px;
5 | $XL: 1200px;
6 |
7 | /*
8 | * Responsive element follow by screen size
9 | *
10 | * @param $screen-size: screen size
11 | *
12 | * @example
13 | * .foo {
14 | * font-size: 10px;
15 | * @include responsive(MD) {
16 | * font-size : 12px;
17 | * }
18 | * @include responsive(XL) {
19 | * font-size : 14px;
20 | * }
21 | */
22 | @mixin responsive($screen-size) {
23 | @if $screen-size == SM {
24 | @media only screen and (min-width: $SM) {
25 | @content;
26 | }
27 | } @else if $screen-size == MD {
28 | @media only screen and (min-width: $MD) {
29 | @content;
30 | }
31 | } @else if $screen-size == LG {
32 | @media only screen and (min-width: $LG) {
33 | @content;
34 | }
35 | } @else if $screen-size == XL {
36 | @media only screen and (min-width: $XL) {
37 | @content;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/utils/mixins/_size.scss:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Set size for a element
4 | *
5 | * @param $width: width property
6 | * @param $height: height property
7 | *
8 | * @example
9 | * .foo {
10 | * @include box(10px, 5px);
11 | * }
12 | */
13 | @mixin box($width, $height) {
14 | height: $height;
15 | width: $width;
16 | }
17 |
--------------------------------------------------------------------------------
/src/styles/utils/mixins/_three-dots.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Set size for a element
3 | *
4 | * @param $width: width property
5 | * @param $line: line property
6 | *
7 | * @example
8 | * .foo {
9 | * @include three-dots(200px, 3);
10 | * }
11 | */
12 |
13 | @mixin three-dots($width, $line) {
14 | overflow: hidden;
15 | text-overflow: ellipsis;
16 | display: -webkit-box;
17 | -webkit-box-orient: vertical;
18 | -webkit-line-clamp: $line;
19 |
20 | width: $width;
21 | }
22 |
--------------------------------------------------------------------------------
/src/styles/vendors/_variants.scss:
--------------------------------------------------------------------------------
1 | $width-site: 1200px;
2 | $gutter-site: 15px;
3 | $gutter-site-mobile: 15px;
4 | $section-padding-top: 80px;
5 | $section-padding-bottom: 80px;
6 | $section-spacing-top: 10px;
7 | $section-spacing-bottom: 10px;
8 | $section-spacing-sm-top: 20px;
9 | $section-spacing-sm-bottom: 20px;
10 | $section-spacing-md-top: 30px;
11 | $section-spacing-md-bottom: 30px;
12 | $section-spacing-lg-top: 40px;
13 | $section-spacing-lg-bottom: 40px;
14 | $section-spacing-xl-top: 50px;
15 | $section-spacing-xl-bottom: 50px;
16 | $swiper-color: #007aff;
17 |
--------------------------------------------------------------------------------
/src/types/shopify/cart.type.ts:
--------------------------------------------------------------------------------
1 | import { ILineItem } from './common.type';
2 |
3 | export interface ICart {
4 | token?: string;
5 | note?: string;
6 | attributes?: any;
7 | original_total_price?: number;
8 | total_price?: number;
9 | total_discount?: number;
10 | total_weight?: number;
11 | item_count?: number;
12 | requires_shipping?: boolean;
13 | cart_level_discount_applications: any[];
14 | currency: string;
15 | items: ILineItem[];
16 | items_subtotal_price: number;
17 | }
18 |
19 | /**
20 | * Properties in formData request '/cart/add.js'
21 | */
22 | export interface IProperty {
23 | [propsKey: string]: string|number;
24 | }
25 |
26 | export interface IItemsResponse {
27 | items: ILineItem[];
28 | }
29 |
--------------------------------------------------------------------------------
/src/types/shopify/collection.type.ts:
--------------------------------------------------------------------------------
1 | export interface ICollection {
2 | id: number;
3 | title: string;
4 | handle: string;
5 | description: string;
6 | pushlished_at: string;
7 | update_at: string;
8 | image: string;
9 | products_count: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/types/shopify/common.type.ts:
--------------------------------------------------------------------------------
1 | export interface ILineItem {
2 | id?: number;
3 | properties?: any;
4 | quantity?: number;
5 | variant_id?: number;
6 | key?: string;
7 | title?: string;
8 | price?: number;
9 | line_price?: number;
10 | original_line_price?: number;
11 | total_discount?: number;
12 | discounts?: any[];
13 | discounted_price?: number;
14 | featured_image?: {
15 | alt: string,
16 | aspect_ratio: number;
17 | height: number;
18 | url: string;
19 | width: number;
20 | };
21 | sku?: any;
22 | grams?: number;
23 | vendor?: string;
24 | product_id?: number;
25 | gift_card?: boolean;
26 | url?: string;
27 | image?: string;
28 | handle?: string;
29 | requires_shipping?: boolean;
30 | product_type?: string;
31 | product_title?: string;
32 | product_description?: string;
33 | variant_title?: string;
34 | variant_options?: string[];
35 | final_line_price?: number;
36 | line_level_discount_allocations?: any[];
37 | line_level_total_discount?: number;
38 | options_with_values?: {
39 | name: string;
40 | value: number;
41 | }[];
42 | original_price?: number;
43 | product_has_only_default_variant?: boolean;
44 | taxable: boolean;
45 | }
46 |
47 | export interface IVariant {
48 | price: number;
49 | id: number;
50 | title?: string;
51 | option1?: string;
52 | option2?: any;
53 | option3?: any;
54 | sku?: any;
55 | requires_shipping?: boolean;
56 | taxable?: boolean;
57 | featured_image?: any;
58 | available?: boolean;
59 | name?: string;
60 | options?: string[];
61 | weight?: number;
62 | compare_at_price?: any;
63 | inventory_quantity?: number;
64 | inventory_management?: string;
65 | inventory_policy?: string;
66 | barcode?: string;
67 | }
68 |
69 | export interface IOption {
70 | name?: string;
71 | position?: number;
72 | values?: string[];
73 | }
74 |
--------------------------------------------------------------------------------
/src/types/shopify/product.type.ts:
--------------------------------------------------------------------------------
1 | import { IOption, IVariant } from './common.type';
2 |
3 | export interface IProduct {
4 | id: number;
5 | title: string;
6 | handle: string;
7 | variants: IVariant[];
8 | description?: string;
9 | published_at: Date;
10 | created_at: Date;
11 | vendor?: string;
12 | type?: string;
13 | tags?: any[];
14 | price: number;
15 | price_min?: number;
16 | price_max?: number;
17 | available?: boolean;
18 | price_varies?: boolean;
19 | compare_at_price?: any;
20 | compare_at_price_min?: number;
21 | compare_at_price_max?: number;
22 | compare_at_price_varies?: boolean;
23 | images?: string[];
24 | featured_image?: string;
25 | options?: IOption[];
26 | url?: string;
27 | media?: IMedia[];
28 | }
29 |
30 | export interface IProductSearch {
31 | available: boolean;
32 | body: string;
33 | compare_at_price_max: string;
34 | compare_at_price_min: string;
35 | featured_image: {
36 | alt: string;
37 | aspect_ratio: number;
38 | height: number;
39 | url: string;
40 | width: number;
41 | };
42 | handle: string;
43 | id: number;
44 | image: string;
45 | price: string;
46 | price_max: string;
47 | price_min: string;
48 | tags: string[];
49 | title: string;
50 | type: string;
51 | url: string;
52 | variants: IVariant[];
53 | vendor: string;
54 | }
55 |
56 | export interface IMedia {
57 | alt: string;
58 | aspect_ratio: number;
59 | height: number;
60 | id: number;
61 | media_type: 'video' | 'image' | 'external_video'| 'model';
62 | position: number;
63 | preview_image: IPreviewObject;
64 | src: string;
65 | width: number;
66 | }
67 |
68 | export interface IPreviewObject {
69 | aspect_ratio: number;
70 | height: number;
71 | src: string;
72 | width: number;
73 | }
74 |
--------------------------------------------------------------------------------
/src/types/shopify/theme.type.ts:
--------------------------------------------------------------------------------
1 | export interface ITheme {
2 | id: number;
3 | name: string;
4 | role: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/types/vue-shims.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * Import Vue component to .ts file in ./Shopify folder
4 | */
5 |
6 | declare module "*.vue" {
7 | import Vue from 'vue';
8 | export default Vue;
9 | }
10 |
--------------------------------------------------------------------------------
/src/vue/components/entry/cart/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/vue/components/entry/cart/.gitkeep
--------------------------------------------------------------------------------
/src/vue/components/entry/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Import all entry
3 | * Mount Vue SFC vào DOM
4 | */
5 |
6 | // import { onSectionSelected } from 'Helpers/sections';
7 | // import SearchBar from './search/SearchBar.vue';
8 | // import PageAlles from './alles/PageAlles.vue';
9 | // import CartDrawer from './cart/CartDrawer.vue';
10 | // import CartItemCount from '../globals/CartItemCount.vue';
11 | // import ProductReview from './product-review/ProductReview.vue';
12 | // import './alles/AllesContainer';
13 |
14 | // onSectionSelected('#search-bar', () => {
15 | // const seachBar = new SearchBar().$mount('#search-bar');
16 | // });
17 |
18 | // onSectionSelected('#page-alles', () => {
19 | // const pageAlles = new PageAlles().$mount('#page-alles');
20 | // });
21 |
22 | // onSectionSelected('#product-review', () => {
23 | // const productReview = new ProductReview().$mount('#product-review');
24 | // });
25 |
26 | // const cartDrawer = new CartDrawer().$mount('#cart-drawer');
27 |
28 | // onSectionSelected('#cart-item-count', () => {
29 | // const cartItemCount = new CartItemCount().$mount('#cart-item-count');
30 | // });
31 |
--------------------------------------------------------------------------------
/src/vue/components/entry/product/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/vue/components/entry/product/.gitkeep
--------------------------------------------------------------------------------
/src/vue/components/entry/readme.md:
--------------------------------------------------------------------------------
1 | Chứa các Component Vue của từng Page
2 |
--------------------------------------------------------------------------------
/src/vue/components/entry/search/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/vue/components/entry/search/.gitkeep
--------------------------------------------------------------------------------
/src/vue/components/globals/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmacoders/Shopify-Theme-Starter-Vue3/0bdc5d64a3a1a62c7f62a333c52ee066c7e2ad19/src/vue/components/globals/.gitkeep
--------------------------------------------------------------------------------
/src/vue/components/globals/XoButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 |
7 |
20 |
21 |
32 |
--------------------------------------------------------------------------------
/src/vue/components/globals/readme.md:
--------------------------------------------------------------------------------
1 | Chứa các Components Vue được sử dụng ở nhiều page
2 |
--------------------------------------------------------------------------------
/src/vue/config/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Config vue global
3 | */
4 |
5 | // import Vue from 'vue';
6 |
7 | // Vue.config.ignoredElements = ['model-viewer'];
8 |
--------------------------------------------------------------------------------
/src/vue/filters/hugMoneyFormat.ts:
--------------------------------------------------------------------------------
1 | import { formatMoney } from '@shopify/theme-currency';
2 |
3 | declare let theme: any;
4 |
5 | /**
6 | * Format money from Shopify theo money_format
7 | */
8 |
9 | const hugMoneyFormat = (value: number | string): string => {
10 | if (!value) return '';
11 | return formatMoney(Number(value), theme.moneyFormat);
12 | };
13 |
14 | export default hugMoneyFormat;
15 |
--------------------------------------------------------------------------------
/src/vue/filters/hugUppercase.ts:
--------------------------------------------------------------------------------
1 | const hugUppercase = (value: string): string => value.toUpperCase();
2 |
3 | export default hugUppercase;
4 |
--------------------------------------------------------------------------------
/src/vue/filters/imgURL.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Same as img_url shopify theme
3 | */
4 |
5 | const imgURL = (src: string, size: string, crop: string): string => src
6 | .replace(/_(pico|icon|thumb|small|compact|medium|large|grande|original|500x500|768x768|1024x1024|2048x2048|master)+\./g, '.')
7 | .replace(/\.jpg|\.png|\.gif|\.jpeg/g, (match: string) => `_${size}_crop_${crop}${match}`);
8 |
9 | export default imgURL;
10 |
--------------------------------------------------------------------------------
/src/vue/filters/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Import Vue Filter global
3 | */
4 | // import Vue from 'vue';
5 | // import hugMonedyFormat from './hugMoneyFormat';
6 | // import imgURL from './imgURL';
7 | // import hugUppercase from './hugUppercase';
8 |
9 | // Vue.filter('hugMoneyFormat', hugMonedyFormat);
10 | // Vue.filter('imgURL', imgURL);
11 | // Vue.filter('hugUppercase', hugUppercase);
12 |
--------------------------------------------------------------------------------
/src/vue/mixins/CollectionCard.ts:
--------------------------------------------------------------------------------
1 | // import Vue from 'vue';
2 | // import Component from 'vue-class-component';
3 | // import store from 'Vue/store/index';
4 | // import { updateTime } from 'Helpers/utils';
5 | // import { IProduct } from 'Types/shopify/product.type';
6 | // import { mapActions, mapGetters } from 'vuex';
7 | // /**
8 | // * Description:
9 | // * Mixins Vue create list products card from 1 collection ( with collection id)
10 | // */
11 | // declare let collectionId: string;
12 |
13 | // const getProductsFromCollection = async (): Promise => {
14 | // if (collectionId === 'NO_CHOOSEN_COLLECTION') return [];
15 |
16 | // const res = await fetch(`https://cdn.xopify.com/custom-app/upfrontreep/collections-${collectionId}.json?${updateTime()}`);
17 | // if (!res.ok) throw new Error('Bad response from server');
18 | // const listProducts: IProduct[] = await res.json();
19 |
20 | // return listProducts;
21 | // };
22 |
23 | // @Component({
24 | // store,
25 | // computed: {
26 | // ...mapGetters('CollectionStore', {
27 | // isCollectionLoading: 'getIsCollectionLoading',
28 | // }),
29 | // },
30 | // methods: {
31 | // ...mapActions('CollectionStore', [
32 | // 'onDisabledLoading',
33 | // ]),
34 | // },
35 | // })
36 | // export default class MixinCollectionCard extends Vue {
37 | // isCollectionLoading!: boolean;
38 |
39 | // onDisabledLoading!: () => void;
40 |
41 | // allProducts: IProduct[] = [];
42 |
43 | // async mounted(): Promise {
44 | // const products = await getProductsFromCollection();
45 | // products.forEach((product) => {
46 | // this.allProducts.push(product);
47 | // });
48 |
49 | // this.onDisabledLoading();
50 | // }
51 | // }
52 |
--------------------------------------------------------------------------------
/src/vue/mixins/SplitingVariant.ts:
--------------------------------------------------------------------------------
1 | // import { VariantsEntity } from 'Types/product-information/information';
2 | // import Vue from 'vue';
3 | // import Component from 'vue-class-component';
4 |
5 | // /**
6 | // * Spliting variant title || count package variant
7 | // */
8 | // @Component
9 | // export default class SplitingVariant extends Vue {
10 | // hoverVariant: VariantsEntity = {} as VariantsEntity;
11 |
12 | // get metaVariant(): string {
13 | // const res = (this.hoverVariant.title || '').split(' ');
14 | // let metaVariant = '';
15 | // if (res.length > 1) {
16 | // (res.slice(0, -1)).forEach((title: string) => {
17 | // metaVariant += `${title} `;
18 | // });
19 | // } else {
20 | // [metaVariant] = res;
21 | // }
22 | // return metaVariant || '';
23 | // }
24 |
25 | // get quantityVariant(): string {
26 | // const res = (this.hoverVariant.title || '').split(' ');
27 | // const quantityVariant = (res.length > 1) ? res[Number(res.length - 1)] : '';
28 | // return quantityVariant || '';
29 | // }
30 | // }
31 |
--------------------------------------------------------------------------------
/src/vue/store/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * All store
3 | */
4 | // import Vue from 'vue';
5 | // import Vuex from 'vuex';
6 | // import CartStore from './modules/cart';
7 | // import CollectionStore from './modules/collection';
8 | // import ReviewStore from './modules/productReview';
9 | // import SearchStore from './modules/header';
10 |
11 | // Vue.use(Vuex);
12 | // const store = new Vuex.Store({
13 | // modules: {
14 | // CartStore,
15 | // CollectionStore,
16 | // ReviewStore,
17 | // SearchStore,
18 | // },
19 | // });
20 |
21 | // export default store;
22 |
--------------------------------------------------------------------------------
/src/vue/store/modules/cart/index.ts:
--------------------------------------------------------------------------------
1 | // import { getCartState, updateItemByKey } from 'Helpers/cart/cart';
2 | // import { ICart, IProperty } from 'Types/shopify/cart.type';
3 | // import {
4 | // Action,
5 | // Module,
6 | // Mutation,
7 | // VuexModule,
8 | // } from 'vuex-module-decorators';
9 |
10 | // enum MutationType {
11 | // setCartState = 'SET_CART_STATE',
12 | // setIsExpand = 'SET_IS_EXPAND',
13 | // setCartItemCount = 'SET_CART_ITEM_COUNT',
14 | // }
15 |
16 | // declare let cartInitial: ICart;
17 | // declare let cartItemCountInitial: number;
18 | // @Module({
19 | // namespaced: true,
20 | // })
21 | // export default class CartStore extends VuexModule {
22 | // /**
23 | // * Toggle cart drawer
24 | // */
25 | // isExpand = false;
26 |
27 | // /**
28 | // * Initital cart state ( page reload )
29 | // */
30 | // cartState: ICart = cartInitial;
31 |
32 | // /**
33 | // * Exist item in cart
34 | // */
35 | // cartItemCount = cartItemCountInitial;
36 |
37 | // get getCartState(): ICart {
38 | // return this.cartState;
39 | // }
40 |
41 | // get getIsExpand(): boolean {
42 | // return this.isExpand;
43 | // }
44 |
45 | // get getCartItemCount(): number {
46 | // return this.cartItemCount;
47 | // }
48 |
49 | // @Mutation
50 | // [MutationType.setCartState](newCartState: ICart): void {
51 | // this.cartState = newCartState;
52 | // }
53 |
54 | // @Mutation
55 | // [MutationType.setCartItemCount](newCount: number): void {
56 | // this.cartItemCount = newCount || Number(this.cartState.item_count);
57 | // }
58 |
59 | // @Mutation
60 | // [MutationType.setIsExpand](): void {
61 | // this.isExpand = !this.isExpand;
62 | // }
63 |
64 | // @Action
65 | // async fetchCartState(): Promise {
66 | // const newCartState = await getCartState();
67 | // this.context.commit(MutationType.setCartState, newCartState);
68 | // this.context.commit(MutationType.setCartItemCount, Number(newCartState.item_count));
69 | // }
70 |
71 | // @Action
72 | // async updateItemByKey(config: {
73 | // key: string,
74 | // quantity?: number,
75 | // properties: IProperty,
76 | // onSuccess?: (cartState: ICart) => void;
77 | // onError?: (err: Error) => void;
78 | // }): Promise {
79 | // const {
80 | // key,
81 | // quantity,
82 | // properties,
83 | // onSuccess,
84 | // onError,
85 | // } = config;
86 |
87 | // const formData = {
88 | // id: key,
89 | // quantity,
90 | // properties,
91 | // };
92 |
93 | // try {
94 | // const res = await fetch('/cart/change.js', {
95 | // method: 'POST',
96 | // headers: {
97 | // 'Content-Type': 'application/json',
98 | // },
99 | // body: JSON.stringify(formData),
100 | // });
101 | // if (!res.ok) throw new Error('Bad response from server');
102 | // const cart: ICart = await res.json();
103 | // this.context.commit(MutationType.setCartState, cart);
104 | // this.context.commit(MutationType.setCartItemCount, Number(cart.item_count));
105 | // onSuccess && onSuccess(cart);
106 | // } catch (error) {
107 | // onError && onError(error);
108 | // }
109 | // }
110 |
111 | // @Action
112 | // toggleExpand(): void {
113 | // this.context.commit(MutationType.setIsExpand);
114 | // }
115 | // }
116 |
--------------------------------------------------------------------------------
/src/vue/store/modules/collection/index.ts:
--------------------------------------------------------------------------------
1 | // import {
2 | // Action,
3 | // Module,
4 | // Mutation,
5 | // VuexModule,
6 | // } from 'vuex-module-decorators';
7 |
8 | // enum MutationType {
9 | // setIsCollectionLoading = 'SET_IS_COLLECTION_LOADING',
10 | // }
11 |
12 | // @Module({
13 | // namespaced: true,
14 | // })
15 | // export default class CollectionStore extends VuexModule {
16 | // /**
17 | // * State of loading ui in Alles page
18 | // */
19 | // isCollectionLoading = true;
20 |
21 | // get getIsCollectionLoading(): boolean {
22 | // return this.isCollectionLoading;
23 | // }
24 |
25 | // @Mutation
26 | // [MutationType.setIsCollectionLoading](): void {
27 | // this.isCollectionLoading = false;
28 | // }
29 |
30 | // @Action
31 | // onDisabledLoading(): void {
32 | // this.context.commit(MutationType.setIsCollectionLoading);
33 | // }
34 | // }
35 |
--------------------------------------------------------------------------------
/src/vue/store/type.ts:
--------------------------------------------------------------------------------
1 | // export interface RootState {
2 | // cartCount: number,
3 | // isOpen: boolean,
4 | // }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "lib": [
5 | "es6",
6 | "dom"
7 | ],
8 | "outDir": "./dist",
9 | // "rootDir": "./src",
10 | "allowJs": true,
11 | "module": "commonjs",
12 | "target": "es5",
13 | "strictPropertyInitialization": false,
14 | "sourceMap": false,
15 | "moduleResolution": "node",
16 | "strict": true,
17 | "experimentalDecorators": true,
18 | "types": ["google.maps"],
19 |
20 | /* Config alias for ts */
21 | "baseUrl": ".",
22 | "paths": {
23 | "Shopify/*": [ "./shopify/*" ], // Shopify structure
24 | "Components/*": [ "./src/components/*" ],
25 | "Helpers/*": [ "./src/helpers/*" ],
26 | "Styles/*": [ "./src/styles/*" ],
27 | "Types/*": [ "./src/types/*" ],
28 | "Vue/*": [ "src/vue/*" ],
29 | },
30 | },
31 | "exclude": [
32 | "node_modules",
33 | "**/*.spec.ts",
34 | "**/*.test.ts"
35 | ],
36 | "include": [
37 | "./src/**/*.ts",
38 | "./src/**/*.vue",
39 | "./shopify/**/*.ts",
40 | "./shopify/**/*.vue",
41 | "./jest.setup.ts"
42 | ],
43 | }
44 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["src/helpers/", "src/types"],
3 | "out": "docs"
4 | }
5 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const { hugCommonConfig } = require('./build-utils/webpack.common');
3 |
4 | const addons = (addonsArg) => {
5 | console.log(addonsArg);
6 | const addons2 = []
7 | .concat.apply([], [addonsArg])
8 | .filter(Boolean);
9 |
10 | return addons2.map((addonName) => require(`./build-utils/addons/webpack.${addonName}.js`));
11 | };
12 |
13 | const allConfigs = (env) => {
14 | console.log(env)
15 | console.log(env.addons)
16 |
17 | const envConfig = require(`./build-utils/webpack.${env.env}.js`);
18 | const allConfig = merge(hugCommonConfig, envConfig, ...addons(env.addons));
19 |
20 | return allConfig;
21 | }
22 | module.exports = allConfigs;
23 |
--------------------------------------------------------------------------------