├── .eslintrc.js
├── .gitignore
├── .prettierrc.yaml
├── .vscode
└── settings.json
├── README.md
├── package.json
├── sample.slater.config.js
└── src
├── assets
├── fonts.css.liquid
├── index.js
└── main.css
├── config
├── settings_data.json
└── settings_schema.json
├── layout
├── gift_card.liquid
├── password.liquid
└── theme.liquid
├── locales
└── en.default.json
├── scripts
├── app.js
├── components
│ ├── accountLogin.js
│ ├── cartDrawer.js
│ ├── cartDrawerItem.js
│ ├── header.js
│ ├── hero.js
│ ├── product-counter.js
│ ├── product-selection.js
│ ├── product.js
│ └── slater-welcome.js
├── index.js
└── lib
│ ├── cart.js
│ ├── choozy.js
│ ├── currency.js
│ ├── getProductJson.js
│ ├── images.js
│ ├── options.js
│ ├── product-selector.js
│ ├── radio.js
│ └── select.js
├── sections
├── footer.liquid
├── header.liquid
├── hero-collection.liquid
├── hero.liquid
└── slater-welcome.liquid
├── snippets
├── account-address-form.liquid
├── account-address.liquid
├── account-titles.liquid
├── cart-drawer.liquid
├── component-button.liquid
├── component-counter.liquid
├── component-image.liquid
├── component-input.liquid
├── component-radio.liquid
├── component-select.liquid
├── component-textarea.liquid
├── css-variables.liquid
├── head-meta.liquid
├── hero.liquid
├── pagination.liquid
├── product-head.liquid
├── product-options.liquid
├── search.liquid
└── social-sharing.liquid
├── styles
├── components
│ ├── buttons.scss
│ ├── cart-drawer.scss
│ ├── counter.scss
│ └── image.scss
├── global
│ ├── colors.scss
│ ├── containers.scss
│ ├── forms.scss
│ ├── global.scss
│ ├── lists.scss
│ ├── typography.scss
│ └── var.scss
├── main.scss
└── templates
│ └── .gitkeep
└── templates
├── 404.liquid
├── article.liquid
├── blog.liquid
├── cart.liquid
├── collection.liquid
├── customers
├── account.liquid
├── activate_account.liquid
├── addresses.liquid
├── login.liquid
├── order.liquid
├── register.liquid
└── reset_password.liquid
├── gift_card.liquid
├── index.liquid
├── list-collections.liquid
├── page.contact.liquid
├── page.liquid
├── password.liquid
├── product.liquid
├── product.neue.liquid
└── search.liquid
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | },
6 | extends: ['eslint:recommended', 'prettier', 'plugin:import/warnings'],
7 | globals: {
8 | Atomics: 'readonly',
9 | SharedArrayBuffer: 'readonly',
10 | window: 'writable',
11 | document: 'writable',
12 | slater: 'readable',
13 | },
14 | parser: 'babel-eslint',
15 | parserOptions: {
16 | ecmaVersion: 2018,
17 | sourceType: 'module',
18 | },
19 | extends: ['eslint:recommended', 'prettier', 'plugin:import/warnings'],
20 | plugins: ['prettier', 'import'],
21 | rules: {
22 | 'class-methods-use-this': 0,
23 | 'no-unused-expressions': [2, { allowTaggedTemplates: true }],
24 | 'no-underscore-dangle': 0,
25 | 'prettier/prettier': 2,
26 | 'consistent-return': 0,
27 | 'import/no-extraneous-dependencies': 0,
28 | 'max-len': [2, 120],
29 | 'object-curly-spacing': [2, 'always'],
30 | semi: [1, 'never'],
31 | 'no-console': 0,
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | config.yml
4 | build
5 | test.config.js
6 | *.zip
7 | slater.config.js
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | tabWidth: 4
2 | semi: false
3 | singleQuote: true
4 | arrowParens: 'always'
5 | trailingComma: 'es5'
6 | printWidth: 120
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "**/build": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Slater Base
2 |
3 | ## What is this?
4 |
5 | This is a base template based heavily on [Slater](https://github.com/the-couch/slater), an excellent Shopify development toolkit inspired by [Slate](https://github.com/Shopify/slate).
6 |
7 | ## Getting started
8 |
9 | ### Install dependencies
10 |
11 | ```
12 | npm i
13 | ```
14 |
15 | ### Edit configuration file
16 |
17 | 1. **Duplicate** `sample.slater.config.js`
18 | 2. **Rename** to `slater.config.js`
19 | 3. Change the `id`, `password` and `store` values to point to your Shopify environment
20 |
21 | ### Allow https locally
22 |
23 | In order for livereload to work properly, you have to allow https to run locally on port `4000`. To do so:
24 |
25 | - Run `npm run start` on the command line and then navigate to https://localhost:4000.
26 | - Then authorize the browser to view page despite the lack of an SSL certificate.
27 | - You should see the message "Slater running" on the subsequent screen.
28 |
29 | ### Setup your IDE
30 |
31 | We recommend that you use VS Code but most modern IDE's should do the job. This template comes configured with [eslint](https://eslint.org/) and [prettier](https://prettier.io/).
32 |
33 | In order take full advantage of these plugins, you should install the Prettier extension and update your preferences to auto format on save.
34 |
35 | ## Commands
36 |
37 | ### `npm run start`
38 |
39 | Watches for changes and auto uploads changed files to Shopify. When running this, the command line should print out the preview URL. Click on this to view your changes as your develop.
40 |
41 | ### `npm run deploy:development`
42 |
43 | Builds and compiles all assets and then uploads entire theme to Shopify environment specified in the configuration file. Use this if you would like to push up changes that were made prior to running `npm run start`.
44 |
45 | ## Shopify Theme Basics
46 |
47 | I would highly recommend reading the [Shopify Theme development documentation](https://help.shopify.com/en/themes/development) before diving into any Shopify project. Having a strong understanding of this system will save you a lot of time and confusion in the long run. The following is only intended to be a brief summary:
48 |
49 | ### Layouts
50 |
51 | A special type of template that wraps the entire site. A shopify site needs to have at least one of these. The layout typically contains the header, footer and main content area.
52 |
53 | ### Templates
54 |
55 | Think of templates like pages. There are templates for product pages, blog posts, blog listing pages etc.
56 |
57 | ### Sections
58 |
59 | Sections are pieces of content that need to be editable in the Shopify CMS. There are **dynamic** sections and **static** sections.
60 |
61 | #### Dynamic sections
62 |
63 | These are sections that can only exist on the homepage. The homepage is the only Shopify page that allows for an unlimited amount of sections that can be in whatever order the user wants. This allows the homepage to basically be completely editable in the CMS.
64 |
65 | #### Static sections
66 |
67 | Static sections are very similar to dynamic sections except they cannot be included on the homepage. There can only ever be on instance of these sections. A good example of a static section might be a hero that goes on the products listing page. This would allow a hero to be editable on the product listing pages but it cannot be used on any other pages. Adding to another page is technically possible but note that editing it's content in the CMS will result in updating the content for all instances of that section.
68 |
69 | ### Snippets
70 |
71 | Little re-usable bits of liquid.
72 |
73 | ## Working with JavaScript
74 |
75 | This base template utilizes a small library called [PicoApp](https://github.com/estrattonbailey/picoapp) to help organize your JS into components and easily maintain a global state. Please read through PicoApp's documentation before starting to work with the JavaScript.
76 |
77 | ## Working with SCSS
78 |
79 | This base template uses SCSS rather than plain CSS. The main scss file is appropriately called main.scss. It is imported into the index.js so that livereload can function properly. Feel free to add as many SCSS files as you like. Just import them into main.scss so that they get compiled correctly.
80 |
81 | ## Feedback
82 |
83 | We appreciate all feedback. Please contribute by creating pull requets or submitting issues.
84 |
85 | ---
86 |
87 | By [Use All Five](https://useallfive.com)
88 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@useallfive/slater-base",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "start": "slater watch",
6 | "build": "slater build",
7 | "deploy:development": "slater build && slater sync",
8 | "deploy:production": "slater build && slater sync --theme production",
9 | "test:start": "slater watch --config test.config.js",
10 | "test:build": "slater build --config test.config.js",
11 | "test:deploy": "slater build --config test.config.js && slater sync --config test.config.js",
12 | "test:deploy:production": "slater build --config test.config.js && slater sync --theme production --config test.config.js"
13 | },
14 | "author": "https://thecouch.nyc",
15 | "license": "MIT",
16 | "homepage": "https://github.com/the-couch/slater#readme",
17 | "devDependencies": {
18 | "babel-eslint": "^10.1.0",
19 | "eslint": "^6.8.0",
20 | "eslint-config-prettier": "^6.10.1",
21 | "eslint-plugin-import": "^2.20.2",
22 | "eslint-plugin-prettier": "^3.1.3",
23 | "prettier": "^2.0.4",
24 | "slater": "^1.8.0"
25 | },
26 | "dependencies": {
27 | "choozy": "0.0.6",
28 | "gridlex": "^2.7.1",
29 | "lazim": "^2.0.1",
30 | "mitt": "^1.2.0",
31 | "picoapp": "^3.6.1",
32 | "sliced": "^1.0.1",
33 | "unfetch": "^4.1.0"
34 | },
35 | "gitHead": "59009c48bf724a0f868261cc0154c757f82aea14"
36 | }
37 |
--------------------------------------------------------------------------------
/sample.slater.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | assets: {
5 | presets: ['sass'],
6 | },
7 | themes: {
8 | development: {
9 | id: 'ID',
10 | password: 'PASSWORD',
11 | store: 'NAME.myshopify.com',
12 | ignore: [
13 | 'settings_data.json', // leave this here to avoid overriding theme settings on sync
14 | ],
15 | },
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/assets/fonts.css.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Use this file for handling Custom Fonts, don't add fonts.
3 | We don't support importing fonts from the Shopify pipeline
4 | so you have to do it here.
5 | {% endcomment %}
--------------------------------------------------------------------------------
/src/assets/main.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | }
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
--------------------------------------------------------------------------------
/src/config/settings_data.json:
--------------------------------------------------------------------------------
1 | {
2 | "current": "Default",
3 | "presets": {
4 | "Default": {
5 | "sections": {
6 | "collection-list": {
7 | "type": "collection-list",
8 | "blocks": {
9 | "collection-list-0": {
10 | "type": "collection"
11 | },
12 | "collection-list-1": {
13 | "type": "collection"
14 | },
15 | "collection-list-2": {
16 | "type": "collection"
17 | }
18 | },
19 | "block_order": [
20 | "collection-list-0",
21 | "collection-list-1",
22 | "collection-list-2"
23 | ]
24 | },
25 | "featured-collection": {
26 | "type": "featured-collection",
27 | "settings": {
28 | "collection": "frontpage"
29 | }
30 | },
31 | "featured-product": {
32 | "type": "featured-product"
33 | }
34 | },
35 | "content_for_index": [
36 | "collection-list",
37 | "featured-collection",
38 | "featured-product"
39 | ]
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/config/settings_schema.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "theme_info",
4 | "theme_name": "Slater",
5 | "theme_version": "1.0.0",
6 | "theme_author": "The Couch",
7 | "theme_documentation_url": "https://github.com/the-couch/slater-theme",
8 | "theme_support_url": "https://github.com/the-couch/slater-theme"
9 | },
10 | {
11 | "name": "Typography",
12 | "settings": [
13 | {
14 | "type": "select",
15 | "id": "type_base_size",
16 | "label": "Base size",
17 | "default": "16px",
18 | "options": [
19 | {
20 | "value": "14px",
21 | "label": "14px"
22 | },
23 | {
24 | "value": "15px",
25 | "label": "15px"
26 | },
27 | {
28 | "value": "16px",
29 | "label": "16px"
30 | },
31 | {
32 | "value": "17px",
33 | "label": "17px"
34 | }
35 | ]
36 | }
37 | ]
38 | },
39 | {
40 | "name": "Colors",
41 | "settings": [
42 | {
43 | "type": "header",
44 | "content": "General colors"
45 | },
46 | {
47 | "type": "color",
48 | "id": "color_primary",
49 | "label": "Primary color",
50 | "default": "#000",
51 | "info": "Used for text links, and primary buttons"
52 | },
53 | {
54 | "type": "color",
55 | "id": "color_body_text",
56 | "label": "Text color",
57 | "default": "#000",
58 | "info": "Color used for content and paragraph text"
59 | }
60 | ]
61 | },
62 | {
63 | "name": "Social media",
64 | "settings": [
65 | {
66 | "type": "header",
67 | "content": "Social sharing options"
68 | },
69 | {
70 | "type": "checkbox",
71 | "id": "social_sharing_blog",
72 | "label": "Enable sharing for blog articles",
73 | "default": true
74 | },
75 | {
76 | "type": "checkbox",
77 | "id": "share_facebook",
78 | "label": "Share on Facebook",
79 | "default": true
80 | },
81 | {
82 | "type": "checkbox",
83 | "id": "share_twitter",
84 | "label": "Tweet on Twitter",
85 | "default": true
86 | },
87 | {
88 | "type": "checkbox",
89 | "id": "share_pinterest",
90 | "label": "Pin on Pinterest",
91 | "default": true
92 | },
93 | {
94 | "type": "header",
95 | "content": "Sharing links"
96 | },
97 | {
98 | "type": "text",
99 | "id": "social_twitter_link",
100 | "label": "Twitter link"
101 | },
102 | {
103 | "type": "text",
104 | "id": "social_facebook_link",
105 | "label": "Facebook link"
106 | },
107 | {
108 | "type": "text",
109 | "id": "social_pinterest_link",
110 | "label": "Pinterest link"
111 | },
112 | {
113 | "type": "text",
114 | "id": "social_instagram_link",
115 | "label": "Instagram link"
116 | },
117 | {
118 | "type": "text",
119 | "id": "social_snapchat_link",
120 | "label": "Snapchat link"
121 | },
122 | {
123 | "type": "text",
124 | "id": "social_tumblr_link",
125 | "label": "Tumblr link"
126 | },
127 | {
128 | "type": "text",
129 | "id": "social_youtube_link",
130 | "label": "Youtube link"
131 | },
132 | {
133 | "type": "text",
134 | "id": "social_vimeo_link",
135 | "label": "Vimeo link"
136 | }
137 | ]
138 | },
139 | {
140 | "name": "Cart",
141 | "settings": [
142 | {
143 | "type": "checkbox",
144 | "id": "cart_notes_enable",
145 | "label": "Enable cart notes",
146 | "default": true
147 | }
148 | ]
149 | },
150 | {
151 | "name": "Favicon",
152 | "settings": [
153 | {
154 | "type": "image_picker",
155 | "id": "favicon",
156 | "label": "Favicon image",
157 | "info": "Will be scaled down to 32 x 32px"
158 | }
159 | ]
160 | }
161 | ]
162 |
--------------------------------------------------------------------------------
/src/layout/gift_card.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {% if settings.favicon != blank %}
14 |
15 | {% endif %}
16 |
17 | {%- assign formatted_initial_value = gift_card.initial_value | money_without_trailing_zeros: gift_card.currency -%}
18 | {%- assign formatted_initial_value_stripped = formatted_initial_value | strip_html -%}
19 | {{ 'gift_cards.issued.title' | t: value: formatted_initial_value_stripped, shop: shop.name }}
20 |
21 |
22 |
23 | {{ 'main.css' | asset_url | stylesheet_tag }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
69 |
70 | {{ content_for_header }}
71 |
72 |
73 |
74 | {{ content_for_layout }}
75 |
76 |
77 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/layout/password.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {% if settings.favicon != blank %}
14 |
15 | {% endif %}
16 |
17 | {{ shop.name }}
18 |
19 | {% if page_description %}
20 |
21 | {% endif %}
22 |
23 | {% include 'social-meta-tags' %}
24 |
25 | {{ 'theme.scss.css' | asset_url | stylesheet_tag }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{ content_for_header }}
38 |
39 |
40 |
47 |
48 |
49 | {{ content_for_layout }}
50 |
51 |
52 |
61 |
62 |
63 |
{{ 'general.password_page.login_form_heading' | t }}
64 | {% form 'storefront_password' %}
65 | {{ form.errors | default_errors }}
66 |
{{ 'general.password_page.login_form_password_label' | t }}
67 |
68 |
69 | {{ 'general.password_page.login_form_submit' | t }}
70 |
71 | {% endform %}
72 |
{{ 'general.password_page.admin_link_html' | t }}
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/layout/theme.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {% capture seo_title %}
10 | {{ page_title }}
11 | {% if current_tags %}
12 | {%- assign meta_tags = current_tags | join: ', ' %}
13 | –
14 | {{ 'general.meta.tags' | t: tags: meta_tags -}}
15 | {% endif %}
16 | {% if current_page != 1 %}
17 | –
18 | {{ 'general.meta.page' | t: page: current_page }}
19 | {% endif %}
20 | {% unless page_title contains shop.name %}
21 | –
22 | {{ shop.name }}
23 | {% endunless %}
24 | {% endcapture %}
25 | {{ seo_title }}
26 |
27 | {% include 'head-meta' %}
28 |
29 | {% if template.directory == 'customers' %}
30 |
31 | {% endif %}
32 |
33 | {% include 'css-variables' %}
34 |
35 |
45 |
46 | {{ 'index.css' | asset_url | stylesheet_tag }}
47 |
48 | {{ content_for_header }}
49 |
50 |
51 |
52 | {% section 'header' %}
53 |
54 |
55 | {{ content_for_layout }}
56 | {% section 'footer' %}
57 |
58 |
59 |
60 |
61 | {% include 'cart-drawer' %}
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/locales/en.default.json:
--------------------------------------------------------------------------------
1 | {
2 | "general": {
3 | "accessibility": {
4 | "skip_to_content": "Skip to content"
5 | },
6 | "meta": {
7 | "tags": "Tagged \"{{ tags }}\"",
8 | "page": "Page {{ page }}"
9 | },
10 | "404": {
11 | "title": "404 Page Not Found",
12 | "subtext_html": "The page you requested does not exist. Click here to continue shopping."
13 | },
14 | "password_page": {
15 | "opening_soon": "Opening Soon",
16 | "spread_the_word": "Spread the word",
17 | "login_form_heading": "Enter store using password",
18 | "login_form_password_label": "Password",
19 | "login_form_password_placeholder": "Your password",
20 | "login_form_submit": "Enter",
21 | "signup_form_heading": "Find out when we open",
22 | "signup_form_email_label": "Email",
23 | "signup_form_email_placeholder": "Your email",
24 | "signup_form_submit": "Submit",
25 | "signup_form_success": "We will send you an email right before we open!",
26 | "admin_link_html": "Are you the store owner? Log in here ",
27 | "password_link": "Enter using password",
28 | "powered_by_shopify_html": "This shop will be powered by {{ shopify }}"
29 | },
30 | "social": {
31 | "share_on_facebook": "Share",
32 | "share_on_twitter": "Tweet",
33 | "share_on_pinterest": "Pin it",
34 | "alt_text": {
35 | "share_on_facebook": "Share on Facebook",
36 | "share_on_twitter": "Tweet on Twitter",
37 | "share_on_pinterest": "Pin on Pinterest"
38 | }
39 | },
40 | "search": {
41 | "no_results_html": "Your search for \"{{ terms }}\" did not yield any results.",
42 | "results_for_html": "Your search for \"{{ terms }}\" revealed the following:",
43 | "title": "Search for products on our site",
44 | "placeholder": "Search our store",
45 | "submit": "Search"
46 | }
47 | },
48 | "blogs": {
49 | "article": {
50 | "author_on_date_html": "Posted by {{ author }} on {{ date }}",
51 | "comment_meta_html": "{{ author }} on {{ date }}",
52 | "read_more": "Read more"
53 | },
54 | "comments": {
55 | "title": "Leave a comment",
56 | "name": "Name",
57 | "email": "Email",
58 | "message": "Message",
59 | "post": "Post comment",
60 | "moderated": "Please note, comments must be approved before they are published",
61 | "success_moderated": "Your comment was posted successfully. We will publish it in a little while, as our blog is moderated.",
62 | "success": "Your comment was posted successfully! Thank you!",
63 | "with_count": {
64 | "one": "{{ count }} comment",
65 | "other": "{{ count }} comments"
66 | }
67 | },
68 | "general": {
69 | "categories": "Categories"
70 | }
71 | },
72 | "cart": {
73 | "general": {
74 | "title": "Shopping Cart",
75 | "remove": "Remove",
76 | "note": "Special instructions for seller",
77 | "subtotal": "Subtotal",
78 | "shipping_at_checkout": "Shipping & taxes calculated at checkout",
79 | "update": "Update Cart",
80 | "checkout": "Check Out",
81 | "empty": "Your cart is currently empty.",
82 | "cookies_required": "Enable cookies to use the shopping cart",
83 | "continue_browsing_html": "Continue browsing here .",
84 | "item_quantity": "Item quantity",
85 | "savings": "You're saving"
86 | },
87 | "label": {
88 | "product": "Product",
89 | "price": "Price",
90 | "quantity": "Quantity",
91 | "total": "Total",
92 | "discounted_price": "Discounted price",
93 | "original_price": "Original price"
94 | }
95 | },
96 | "collections": {
97 | "general": {
98 | "no_matches": "Sorry, there are no products in this collection",
99 | "link_title": "Browse our {{ title }} collection"
100 | }
101 | },
102 | "contact": {
103 | "form": {
104 | "name": "Name",
105 | "email": "Email",
106 | "phone": "Phone Number",
107 | "message": "Message",
108 | "send": "Send",
109 | "post_success": "Thanks for contacting us. We'll get back to you as soon as possible."
110 | }
111 | },
112 | "customer": {
113 | "account": {
114 | "title": "My Account",
115 | "details": "Account Details",
116 | "view_addresses": "View Addresses",
117 | "return": "Return to Account Details"
118 | },
119 | "activate_account": {
120 | "title": "Activate Account",
121 | "subtext": "Create your password to activate your account.",
122 | "password": "Password",
123 | "password_confirm": "Confirm Password",
124 | "submit": "Activate Account",
125 | "cancel": "Decline Invitation"
126 | },
127 | "addresses": {
128 | "title": "Your Addresses",
129 | "default": "Default",
130 | "add_new": "Add a New Address",
131 | "edit_address": "Edit address",
132 | "first_name": "First Name",
133 | "last_name": "Last Name",
134 | "company": "Company",
135 | "address1": "Address1",
136 | "address2": "Address2",
137 | "city": "City",
138 | "country": "Country",
139 | "province": "Province",
140 | "zip": "Postal/Zip Code",
141 | "phone": "Phone",
142 | "set_default": "Set as default address",
143 | "add": "Add Address",
144 | "update": "Update Address",
145 | "cancel": "Cancel",
146 | "edit": "Edit",
147 | "delete": "Delete",
148 | "delete_confirm": "Are you sure you wish to delete this address?"
149 | },
150 | "login": {
151 | "title": "Login",
152 | "email": "Email",
153 | "password": "Password",
154 | "forgot_password": "Forgot your password?",
155 | "sign_in": "Sign In",
156 | "cancel": "Return to Store",
157 | "guest_title": "Continue as a guest",
158 | "guest_continue": "Continue"
159 | },
160 | "orders": {
161 | "title": "Order History",
162 | "order_number": "Order",
163 | "date": "Date",
164 | "payment_status": "Payment Status",
165 | "fulfillment_status": "Fulfillment Status",
166 | "total": "Total",
167 | "none": "You haven't placed any orders yet."
168 | },
169 | "order": {
170 | "title": "Order {{ name }}",
171 | "date": "Placed on {{ date }}",
172 | "cancelled": "Order Cancelled on {{ date }}",
173 | "cancelled_reason": "Reason: {{ reason }}",
174 | "billing_address": "Billing Address",
175 | "payment_status": "Payment Status",
176 | "shipping_address": "Shipping Address",
177 | "fulfillment_status": "Fulfillment Status",
178 | "discount": "Discount",
179 | "shipping": "Shipping",
180 | "tax": "Tax",
181 | "product": "Product",
182 | "sku": "SKU",
183 | "price": "Price",
184 | "quantity": "Quantity",
185 | "total": "Total",
186 | "fulfilled_at": "Fulfilled {{ date }}",
187 | "subtotal": "Subtotal"
188 | },
189 | "recover_password": {
190 | "title": "Reset your password",
191 | "email": "Email",
192 | "submit": "Submit",
193 | "cancel": "Cancel",
194 | "subtext": "We will send you an email to reset your password.",
195 | "success": "We've sent you an email with a link to update your password."
196 | },
197 | "reset_password": {
198 | "title": "Reset account password",
199 | "subtext": "Enter a new password for {{ email }}",
200 | "password": "Password",
201 | "password_confirm": "Confirm Password",
202 | "submit": "Reset Password"
203 | },
204 | "register": {
205 | "title": "Create Account",
206 | "first_name": "First Name",
207 | "last_name": "Last Name",
208 | "email": "Email",
209 | "password": "Password",
210 | "submit": "Create",
211 | "cancel": "Return to Store"
212 | }
213 | },
214 | "homepage": {
215 | "onboarding": {
216 | "product_title": "Example Product Title",
217 | "collection_title": "Example Collection Title",
218 | "no_content": "This section doesn’t currently include any content. Add content to this section using the sidebar."
219 | }
220 | },
221 | "layout": {
222 | "cart": {
223 | "title": "Cart",
224 | "items_count": {
225 | "one": "item",
226 | "other": "items"
227 | }
228 | },
229 | "customer": {
230 | "account": "Account",
231 | "logged_in_as_html": "Logged in as {{ first_name }}",
232 | "log_out": "Log out",
233 | "log_in": "Log in",
234 | "create_account": "Create account"
235 | },
236 | "footer": {
237 | "social_platform": "{{ name }} on {{ platform }}",
238 | "copyright": "Copyright",
239 | "payment_methods": "Payment methods accepted"
240 | }
241 | },
242 | "products": {
243 | "product": {
244 | "regular_price": "Regular price",
245 | "sold_out": "Sold Out",
246 | "unavailable": "Unavailable",
247 | "on_sale": "On Sale",
248 | "on_sale_from_html": "On Sale from {{ price }}",
249 | "from_text_html": "From {{ price }}",
250 | "quantity": "Quantity",
251 | "add_to_cart": "Add to Cart"
252 | }
253 | },
254 | "gift_cards": {
255 | "issued": {
256 | "title": "Here's your {{ value }} gift card for {{ shop }}!",
257 | "subtext": "Here's your gift card!",
258 | "disabled": "Disabled",
259 | "expired": "Expired on {{ expiry }}",
260 | "active": "Expires on {{ expiry }}",
261 | "redeem": "Use this code at checkout to redeem your gift card",
262 | "shop_link": "Start shopping",
263 | "print": "Print",
264 | "remaining_html": "{{ balance }} left",
265 | "add_to_apple_wallet": "Add to Apple Wallet"
266 | }
267 | },
268 | "date_formats":{
269 | "month_day_year": "%B %d, %Y"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/scripts/app.js:
--------------------------------------------------------------------------------
1 | import { picoapp } from 'picoapp'
2 |
3 | import slaterWelcome from '@/components/slater-welcome.js'
4 | import hero from '@/components/hero.js'
5 |
6 | import header from '@/components/header.js'
7 | import productSelection from '@/components/product-selection.js'
8 | import cartDrawer from '@/components/cartDrawer.js'
9 | import cartDrawerItem from '@/components/cartDrawerItem.js'
10 | import accountLogin from '@/components/accountLogin.js'
11 | import product from '@/components/product.js'
12 | import productCounter from '@/components/product-counter.js'
13 |
14 | const state = {
15 | cartOpen: false,
16 | }
17 |
18 | const components = {
19 | slaterWelcome,
20 | hero,
21 | accountLogin,
22 | header,
23 | productSelection,
24 | cartDrawer,
25 | cartDrawerItem,
26 | product,
27 | productCounter,
28 | }
29 |
30 | export default picoapp(components, state)
31 |
--------------------------------------------------------------------------------
/src/scripts/components/accountLogin.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 |
3 | export default component((node) => {
4 | const login = node.querySelector('.js-login-dialog')
5 | const recover = node.querySelector('.js-recover-dialog')
6 | const recoverLink = node.querySelector('.js-recover-trigger')
7 | const cancelRecoverLink = node.querySelector('.js-recover-cancel')
8 |
9 | /* eslint-disable */
10 | const recoverIsTarget = window.location.hash.match(/\#recover/) ? true : false
11 | /* eslint-enable */
12 | const successMessage = node.querySelector('.js-recover-success') !== null
13 |
14 | if (recoverIsTarget || successMessage) {
15 | login.style.display = 'none'
16 | recover.style.display = 'block'
17 | } else {
18 | login.style.display = 'block'
19 | }
20 |
21 | recoverLink.addEventListener('click', (e) => {
22 | e.preventDefault()
23 | login.style.display = 'none'
24 | recover.style.display = 'block'
25 | })
26 |
27 | cancelRecoverLink.addEventListener('click', (e) => {
28 | e.preventDefault()
29 | recover.style.display = 'none'
30 | login.style.display = 'block'
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/src/scripts/components/cartDrawer.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 | import { getSizedImageUrl, imageSize } from '@/lib/images.js'
3 | import { formatMoney } from '@/lib/currency.js'
4 | import app from '@/app.js'
5 |
6 | /* eslint-disable-next-line max-len */
7 | const X = ` `
8 |
9 | function createItem({
10 | variant_id: id,
11 | product_title: title,
12 | line_price: price,
13 | variant_title: color,
14 | image,
15 | url,
16 | quantity,
17 | ...item // eslint-disable-line
18 | }) {
19 | const img = image
20 | ? getSizedImageUrl(
21 | image.replace('.' + imageSize(image), ''),
22 | '200x' // TODO hacky af
23 | )
24 | : 'https://source.unsplash.com/R9OS29xJb-8/2000x1333'
25 |
26 | return `
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
${title}
35 |
${formatMoney(price)}
36 |
37 |
-
38 |
${quantity}
39 |
+
40 |
41 | ${color ? `
${color.split(':')[0]}
` : ``}
42 |
43 |
44 |
${X}
45 |
46 |
47 |
48 | `
49 | }
50 |
51 | function renderItems(items) {
52 | return items.length > 0
53 | ? items.reduce((markup, item) => {
54 | markup += createItem(item)
55 | return markup
56 | }, '')
57 | : ``
58 | }
59 |
60 | export default component((node, ctx) => {
61 | const overlay = node.querySelector('.js-overlay')
62 | const closeButton = node.querySelector('.js-close')
63 | const subtotal = node.querySelector('.js-subtotal')
64 | const itemsRoot = node.querySelector('.js-items')
65 | const loading = itemsRoot.innerHTML
66 |
67 | const render = (cart) => {
68 | itemsRoot.innerHTML = renderItems(cart.items)
69 | subtotal.innerHTML = formatMoney(cart.total_price)
70 | }
71 |
72 | const open = (cart) => {
73 | node.classList.add('is-active')
74 | itemsRoot.innerHTML = loading
75 | setTimeout(() => {
76 | node.classList.add('is-visible')
77 | setTimeout(render(cart), 10)
78 | app.mount()
79 | }, 50)
80 | }
81 |
82 | const close = () => {
83 | node.classList.remove('is-visible')
84 | setTimeout(() => {
85 | node.classList.remove('is-active')
86 | app.hydrate({ cartOpen: false })
87 | }, 400)
88 | }
89 |
90 | render(ctx.getState().cart)
91 |
92 | overlay.addEventListener('click', close)
93 | closeButton.addEventListener('click', close)
94 |
95 | ctx.on('cart:toggle', ({ cart, cartOpen }) => {
96 | if (cartOpen) open(cart)
97 | })
98 | ctx.on('cart:updated', () => {
99 | render(ctx.getState().cart)
100 | app.mount()
101 | })
102 | })
103 |
--------------------------------------------------------------------------------
/src/scripts/components/cartDrawerItem.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 | import { removeAddon, updateAddon } from '@/lib/cart.js'
3 |
4 | export default component((node) => {
5 | const button = node.querySelector('.js-remove-item')
6 | const decrease = node.querySelector('.js-remove-single')
7 | const increase = node.querySelector('.js-add-single')
8 | const currentQty = node.querySelector('.js-single-quantity').innerHTML
9 | const id = node.getAttribute('data-id')
10 |
11 | button.addEventListener('click', () => {
12 | removeAddon(id)
13 | })
14 |
15 | decrease.addEventListener('click', (e) => {
16 | e.preventDefault()
17 | updateAddon(id, parseInt(currentQty) - 1)
18 | })
19 |
20 | increase.addEventListener('click', (e) => {
21 | e.preventDefault()
22 | updateAddon(id, parseInt(currentQty) + 1)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/src/scripts/components/header.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 |
3 | export default component((node, ctx) => {
4 | const cartCount = node.querySelector('.js-cart-count')
5 | const cartToggles = node.querySelectorAll('.js-cart-drawer-toggle')
6 |
7 | for (let i = 0; i < cartToggles.length; i++) {
8 | cartToggles[i].addEventListener('click', (e) => {
9 | e.preventDefault()
10 |
11 | ctx.emit('cart:toggle', (state) => {
12 | return {
13 | cartOpen: !state.cartOpen,
14 | }
15 | })
16 | })
17 | }
18 |
19 | ctx.on('cart:updated', (state) => {
20 | cartCount.innerHTML = state.cart.item_count
21 | })
22 |
23 | cartCount.innerHTML = ctx.getState().cart.item_count
24 | })
25 |
--------------------------------------------------------------------------------
/src/scripts/components/hero.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 |
3 | export default component(() => {
4 | console.log('hero mounted')
5 |
6 | return () => {
7 | console.log('hero unmounted')
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/src/scripts/components/product-counter.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 |
3 | export default component((node) => {
4 | const decrease = node.querySelector('.js-counter-remove')
5 | const increase = node.querySelector('.js-counter-add')
6 | const quantity = node.querySelector('.js-counter-quantity')
7 |
8 | const min = parseInt(quantity.attributes.min.value)
9 | const max = parseInt(quantity.attributes.max.value)
10 |
11 | let count = parseInt(quantity.value)
12 |
13 | const set = (i) => {
14 | count = Math.max(min, Math.min(i, max || 10000))
15 | quantity.value = count
16 | }
17 |
18 | decrease.addEventListener('click', (e) => {
19 | e.preventDefault()
20 | set(--count)
21 | })
22 |
23 | increase.addEventListener('click', (e) => {
24 | e.preventDefault()
25 | set(++count)
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/scripts/components/product-selection.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 | import options from '@/lib/options.js'
3 | import getProductJson from '@/lib/getProductJson.js'
4 |
5 | export default component(({ node }) => {
6 | const opts = options(node)
7 |
8 | // cache
9 | getProductJson()
10 |
11 | opts.onUpdate((state) => {
12 | getProductJson().then((json) => {
13 | const variant = json.variants.filter((v) => v.id == state.id)[0]
14 | console.log(variant)
15 | })
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/scripts/components/product.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 | import { addVariant } from '@/lib/cart.js'
3 |
4 | export default component((node) => {
5 | const json = JSON.parse(node.querySelector('.js-product-json').innerHTML)
6 | const form = node.querySelector('form')
7 |
8 | const { selectedOrFirstAvailableVariant, product } = JSON.parse(
9 | document.querySelector('.js-product-json').innerHTML
10 | )
11 | let currentVariant = product.variants.filter((v) => v.id === selectedOrFirstAvailableVariant)[0]
12 |
13 | form.addEventListener('submit', (e) => {
14 | e.preventDefault()
15 | currentVariant = product.variants.filter((v) => v.id === parseInt(form.elements.id.value))[0]
16 |
17 | addVariant(currentVariant, form.elements.quantity.value)
18 | console.log(json)
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/src/scripts/components/slater-welcome.js:
--------------------------------------------------------------------------------
1 | import { component } from 'picoapp'
2 |
3 | export default component(() => {
4 | console.log('slater-welcome mounted')
5 |
6 | return () => {
7 | console.log('slater-welcome unmounted')
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/src/scripts/index.js:
--------------------------------------------------------------------------------
1 | import '../styles/main.scss'
2 | import '@/lib/select.js'
3 | import app from '@/app.js'
4 | import { fetchCart } from '@/lib/cart.js'
5 |
6 | /**
7 | * load any data that your site needs on fist load
8 | */
9 | Promise.all([fetchCart()]).then(([cart]) => {
10 | /**
11 | * add the cart data to the picoapp instance
12 | */
13 | app.hydrate({ cart })
14 |
15 | /**
16 | * mount any components defined on the page
17 | */
18 | app.mount()
19 | })
20 |
--------------------------------------------------------------------------------
/src/scripts/lib/cart.js:
--------------------------------------------------------------------------------
1 | import fetch from 'unfetch'
2 | import app from '@/app.js'
3 |
4 | export function addVariant(variant, quantity) {
5 | const numAvailable =
6 | variant.inventory_policy === 'deny' && variant.inventory_management === 'shopify'
7 | ? variant.inventory_quantity
8 | : null // null means they can add as many as they want
9 |
10 | return fetchCart().then(({ items }) => {
11 | const existing = items.filter((item) => item.id === variant.id)[0] || {}
12 | const numRequested = (existing.quantity || 0) + quantity
13 |
14 | if (numAvailable !== null && numRequested > numAvailable) {
15 | const err = `There are only ${numAvailable} of that product available, requested ${numRequested}.`
16 | app.emit('error', err)
17 | throw new Error(err)
18 | } else {
19 | return addItemById(variant.id, quantity)
20 | }
21 | })
22 | }
23 |
24 | export async function updateAddon(id, quantity) {
25 | const { items } = await fetchCart()
26 | for (let i = 0; i < items.length; i++) {
27 | if (items[i].variant_id === parseInt(id)) {
28 | return changeAddon(i + 1, quantity) // shopify cart is a 1-based index
29 | }
30 | }
31 | }
32 |
33 | export function removeAddon(id) {
34 | return updateAddon(id, 0)
35 | }
36 |
37 | function changeAddon(line, quantity) {
38 | app.emit('cart:updating')
39 |
40 | return fetch('/cart/change.js', {
41 | method: 'POST',
42 | credentials: 'include',
43 | headers: {
44 | 'Content-Type': 'application/json',
45 | },
46 | body: JSON.stringify({ line, quantity }),
47 | })
48 | .then((res) => res.json())
49 | .then((cart) => {
50 | app.hydrate({ cart: cart })
51 | app.emit('cart:updated', { cart: cart })
52 | return cart
53 | })
54 | }
55 |
56 | /**
57 | * Warning: this does not check available products first
58 | */
59 | export function addItemById(id, quantity) {
60 | app.emit('cart:updating')
61 |
62 | return fetch('/cart/add.js', {
63 | method: 'POST',
64 | credentials: 'include',
65 | headers: {
66 | 'Content-Type': 'application/json',
67 | },
68 | body: JSON.stringify({ id, quantity }),
69 | })
70 | .then((r) => r.json())
71 | .then((item) => {
72 | return fetchCart().then((cart) => {
73 | app.hydrate({ cart: cart })
74 | app.emit('cart:updated')
75 | app.emit('cart:toggle', (state) => {
76 | return {
77 | cartOpen: !state.cartOpen,
78 | }
79 | })
80 | // app.emit('updated', { item, cart })
81 | return { item, cart }
82 | })
83 | })
84 | }
85 |
86 | export function fetchCart() {
87 | return fetch('/cart.js', {
88 | method: 'GET',
89 | credentials: 'include',
90 | }).then((res) => res.json())
91 | }
92 |
--------------------------------------------------------------------------------
/src/scripts/lib/choozy.js:
--------------------------------------------------------------------------------
1 | import choozy from 'choozy'
2 |
3 | export default (node) => choozy(node)
4 |
--------------------------------------------------------------------------------
/src/scripts/lib/currency.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Currency Helpers
3 | * -----------------------------------------------------------------------------
4 | * A collection of useful functions that help with currency formatting
5 | *
6 | * Current contents
7 | * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value.
8 | *
9 | */
10 |
11 | /**
12 | * Format money values based on your shop currency settings
13 | * @param {Number|string} cents - value in cents or dollar amount e.g. 300 cents
14 | * or 3.00 dollars
15 | * @param {String} format - shop money_format setting
16 | * @return {String} value - formatted value
17 | */
18 |
19 | /* eslint-disable */
20 |
21 | export function formatMoney(cents, format = '${{amount}}') {
22 | if (typeof cents === 'string') {
23 | cents = cents.replace('.', '')
24 | }
25 |
26 | let value = ''
27 | const placeholderRegex = /\{\{\s*(\w+)\s*\}\}/
28 |
29 | function formatWithDelimiters(number, precision, thousands, decimal) {
30 | precision = precision || 2
31 | thousands = thousands || ','
32 | decimal = decimal || '.'
33 |
34 | if (isNaN(number) || number == null) {
35 | return 0
36 | }
37 |
38 | number = (number / 100.0).toFixed(precision)
39 |
40 | const parts = number.split('.')
41 | const dollarsAmount = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands)
42 | const centsAmount = parts[1] ? decimal + parts[1] : ''
43 |
44 | return dollarsAmount + centsAmount
45 | }
46 |
47 | switch (format.match(placeholderRegex)[1]) {
48 | case 'amount':
49 | value = formatWithDelimiters(cents, 2)
50 | break
51 | case 'amount_no_decimals':
52 | value = formatWithDelimiters(cents, 0)
53 | break
54 | case 'amount_with_space_separator':
55 | value = formatWithDelimiters(cents, 2, ' ', '.')
56 | break
57 | case 'amount_no_decimals_with_comma_separator':
58 | value = formatWithDelimiters(cents, 0, ',', '.')
59 | break
60 | case 'amount_no_decimals_with_space_separator':
61 | value = formatWithDelimiters(cents, 0, ' ')
62 | break
63 | }
64 |
65 | return format.replace(placeholderRegex, value)
66 | }
67 |
--------------------------------------------------------------------------------
/src/scripts/lib/getProductJson.js:
--------------------------------------------------------------------------------
1 | const cache = {}
2 |
3 | export default function getProductJson(slug = window.location.pathname.split('/').reverse()[0], opts = {}) {
4 | if (cache[slug] && !opts.refetch) return Promise.resolve(cache[slug])
5 |
6 | return fetch(window.location.origin + '/products/' + slug + '.json')
7 | .then((res) => res.json())
8 | .then(({ product }) => {
9 | cache[slug] = product
10 | return product
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/scripts/lib/images.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Image Helper Functions
3 | * -----------------------------------------------------------------------------
4 | * A collection of functions that help with basic image operations.
5 | *
6 | */
7 |
8 | /**
9 | * Preloads an image in memory and uses the browsers cache to store it until needed.
10 | *
11 | * @param {Array} images - A list of image urls
12 | * @param {String} size - A shopify image size attribute
13 | */
14 | export function preload(images, size) {
15 | if (typeof images === 'string') {
16 | images = [images]
17 | }
18 |
19 | for (var i = 0; i < images.length; i++) {
20 | var image = images[i]
21 | loadImage(getSizedImageUrl(image, size))
22 | }
23 | }
24 |
25 | /**
26 | * Loads and caches an image in the browsers cache.
27 | * @param {string} path - An image url
28 | */
29 | export function loadImage(path) {
30 | /* eslint-disable */
31 | new Image().src = path
32 | /* eslint-enable */
33 | }
34 |
35 | /**
36 | * Find the Shopify image attribute size
37 | *
38 | * @param {string} src
39 | * @returns {null}
40 | */
41 | export function imageSize(src) {
42 | /* eslint-disable */
43 | var match = src.match(/.+_((?:pico|icon|thumb|small|compact|medium|large|grande)|\d{1,4}x\d{0,4}|x\d{1,4})[_\.@]/)
44 | /* esling-enable */
45 |
46 | if (match) {
47 | return match[1]
48 | } else {
49 | return null
50 | }
51 | }
52 |
53 | /**
54 | * Adds a Shopify size attribute to a URL
55 | *
56 | * @param src
57 | * @param size
58 | * @returns {*}
59 | */
60 | export function getSizedImageUrl(src, size) {
61 | if (size === null) {
62 | return src
63 | }
64 |
65 | if (size === 'master') {
66 | return removeProtocol(src)
67 | }
68 |
69 | var match = src.match(/\.(jpg|jpeg|gif|png|bmp|bitmap|tiff|tif)(\?v=\d+)?$/i)
70 |
71 | if (match) {
72 | var prefix = src.split(match[0])
73 | var suffix = match[0]
74 |
75 | return removeProtocol(prefix[0] + '_' + size + suffix)
76 | } else {
77 | return null
78 | }
79 | }
80 |
81 | export function removeProtocol(path) {
82 | return path.replace(/http(s)?:/, '')
83 | }
84 |
--------------------------------------------------------------------------------
/src/scripts/lib/options.js:
--------------------------------------------------------------------------------
1 | import radio from '@/lib/radio.js'
2 |
3 | export default function productSelection(node, opts) {
4 | opts = Object.assign(
5 | {
6 | select: '[data-option-select]',
7 | radio: '[data-option-radio]',
8 | main: '[data-option-main]',
9 | },
10 | opts
11 | )
12 |
13 | const listeners = []
14 |
15 | const state = {
16 | id: null,
17 | options: [],
18 | }
19 |
20 | const selects = slater.qsa(opts.select)
21 | const radios = slater.qsa(opts.radio)
22 | const main = slater.qs(opts.main)
23 |
24 | if (!main || !main.length) throw 'data-option-main is missing'
25 | if (radios.length > 3) throw 'you have more than three radio groups'
26 | if (selects.length > 3) throw 'you have more than three select inputs'
27 |
28 | const variants = [].slice.call(main.children).reduce((variants, child) => {
29 | variants[child.innerHTML] = child.value
30 | return variants
31 | }, {})
32 |
33 | selects.forEach((select) => {
34 | if (select.nodeName !== 'SELECT')
35 | throw 'data-option-select should be defined on the individual option selectors'
36 |
37 | const index = parseInt(select.getAttribute('data-index'))
38 |
39 | // set initial value
40 | state.options[index] = select.value
41 |
42 | select.addEventListener('change', (e) => {
43 | state.options[index] = e.target.value
44 | updateSelection()
45 | })
46 | })
47 |
48 | radios.forEach((r) => {
49 | if (r.nodeName === 'INPUT')
50 | throw 'data-option-radio should be defined on a parent of the radio group, not the inputs themselves'
51 |
52 | const index = parseInt(r.getAttribute('data-index'))
53 | const inputs = [].slice.call(r.getElementsByTagName('input'))
54 |
55 | // set initial value
56 | inputs.forEach((r) => {
57 | if (r.checked) state.options[index] = r.value
58 | })
59 |
60 | radio(inputs, (value) => {
61 | state.options[index] = value
62 | updateSelection()
63 | })
64 | })
65 |
66 | updateSelection()
67 |
68 | function updateSelection() {
69 | state.id = variants[state.options.join(' / ')]
70 | main.value = state.id
71 | for (let fn of listeners) fn(state)
72 | }
73 |
74 | return {
75 | get state() {
76 | return state
77 | },
78 | onUpdate(fn) {
79 | if (listeners.indexOf(fn) < 0 && listeners.push(fn)) {
80 | return () => listeners.splice(listeners.indexOf(fn), 1)
81 | }
82 | },
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/scripts/lib/product-selector.js:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt'
2 |
3 | export default function ProductSelector(
4 | config = {
5 | main: '[data-product-select]',
6 | options: '[data-single-option-selector]',
7 | data: '[data-product-json]',
8 | }
9 | ) {
10 | const ev = mitt()
11 |
12 | const main = document.querySelector(config.main)
13 | const options = [].slice.call(document.querySelectorAll(config.options))
14 | const data = JSON.parse(document.querySelector(config.data).innerHTML)
15 |
16 | options.forEach((opt) =>
17 | opt.addEventListener('change', () => {
18 | const val = options.reduce((res, opt, i) => {
19 | res += i < options.length - 1 ? opt.value + ' / ' : opt.value
20 | return res
21 | }, '')
22 |
23 | for (let i = 0; i < main.options.length; i++) {
24 | if (main.options[i].text === val) {
25 | main.selectedIndex = i
26 | break
27 | }
28 | }
29 |
30 | ev.emit('update', data.variants.filter((v) => v.title === val)[0])
31 | })
32 | )
33 |
34 | return {
35 | on: ev.on,
36 | destroy() {
37 | options.forEach(() => {
38 | // opt.removeEventListener('change', updateSelect)
39 | })
40 | },
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/scripts/lib/radio.js:
--------------------------------------------------------------------------------
1 | export default function radio(radios, cb) {
2 | radios.map((r) => (r.onclick = (e) => cb(e.target.value)))
3 | }
4 |
--------------------------------------------------------------------------------
/src/scripts/lib/select.js:
--------------------------------------------------------------------------------
1 | import sliced from 'sliced'
2 |
3 | window.slater = Object.assign(window.slater || {}, {
4 | qs(q, ctx) {
5 | return (ctx || document).querySelector(q)
6 | },
7 | qsa(q, ctx) {
8 | return sliced((ctx || document).querySelectorAll(q))
9 | },
10 | gebtn(q, ctx) {
11 | return sliced((ctx || document).getElementsByTagName(q))
12 | },
13 | gebi(q) {
14 | return document.getElementById(q)
15 | },
16 | })
17 |
--------------------------------------------------------------------------------
/src/sections/footer.liquid:
--------------------------------------------------------------------------------
1 |
26 |
27 | {% schema %}
28 | {
29 | "name": "Footer",
30 | "settings": [
31 | {
32 | "type": "link_list",
33 | "id": "footer_menu",
34 | "label": "Menu",
35 | "default": "footer"
36 | }
37 | ]
38 | }
39 | {% endschema %}
40 |
--------------------------------------------------------------------------------
/src/sections/header.liquid:
--------------------------------------------------------------------------------
1 | {% comment %} Considerations for the header: 1. Schema.org tags -
2 |
3 | - itemprop [url, logo] etc {% endcomment %}
4 |
5 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/sections/hero-collection.liquid:
--------------------------------------------------------------------------------
1 | {% include 'hero' %}
2 |
3 | {% schema %}
4 | {
5 | "name": "Hero",
6 | "settings": [
7 | {
8 | "id": "title",
9 | "type": "text",
10 | "label": "Title",
11 | "default": "Find which products are right for you"
12 | }
13 | ]
14 | }
15 | {% endschema %}
16 |
--------------------------------------------------------------------------------
/src/sections/hero.liquid:
--------------------------------------------------------------------------------
1 | {% include 'hero' %}
2 |
3 | {% schema %}
4 | {
5 | "name": "Hero",
6 | "settings": [
7 | {
8 | "id": "title",
9 | "type": "text",
10 | "label": "Title"
11 | }
12 | ],
13 | "presets": [
14 | {
15 | "name": "Hero",
16 | "category": "hero"
17 | }
18 | ]
19 | }
20 | {% endschema %}
21 |
--------------------------------------------------------------------------------
/src/sections/slater-welcome.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
A Shopify theme development toolkit
5 |
A barebones theme and handy CLI for easy build and deployment.
6 |
npm i slater -g
7 |
8 |
12 |
13 |
14 |
15 |
16 | {% schema %}
17 | {
18 | "name": "Welcome",
19 | "settings": [],
20 | "presets": [
21 | {
22 | "name": "Welcome",
23 | "category": "slater"
24 | }
25 | ]
26 | }
27 | {% endschema %}
28 |
--------------------------------------------------------------------------------
/src/snippets/account-address-form.liquid:
--------------------------------------------------------------------------------
1 |
153 |
154 |
246 |
--------------------------------------------------------------------------------
/src/snippets/account-address.liquid:
--------------------------------------------------------------------------------
1 |
2 | {% if default %}
3 |
Default
4 | {% endif %}
5 |
6 |
7 | {{ address.name }}
8 | {% if address.company %}
9 | {{ address.company }}
10 | {% endif %}
11 | {{ address.street }}
12 | {{ address.city | capitalize }}
13 | {% if address.province %},
14 | {{ address.province | upcase }}
15 | {% endif %}
16 | {{ address.zip | upcase }}
17 | {{ address.country }}
18 | {% if address.phone != '' %}
19 | {{ address.phone }}
20 | {% endif %}
21 |
22 |
23 | {% if edit %}
24 |
28 | {% endif %}
29 |
30 |
--------------------------------------------------------------------------------
/src/snippets/account-titles.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ title }}
7 |
8 |
9 |
10 |
11 | {% if template contains 'account' %}
12 | Account Home
13 | {% else %}
14 | Account Home
15 | {% endif %}
16 |
17 |
18 |
19 | {% if template contains 'address' %}
20 | Addresses
21 | {% else %}
22 | Addresses
23 | {% endif %}
24 |
25 |
26 |
27 | Logout
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/snippets/cart-drawer.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Subtotal:
26 | $-.--
27 |
28 |
29 | {%
30 | include 'component-button' with
31 | tag: 'a',
32 | cta: 'Checkout',
33 | href: '/checkout',
34 | className: 'y no-ajax'
35 | %}
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/snippets/component-button.liquid:
--------------------------------------------------------------------------------
1 | {%- capture cx -%}
2 | button rel
3 | {{ className }}
4 | {% if outline == true %}button--outline
5 | {% endif %}
6 | {% if small == true %}button--small
7 | {% endif %}
8 | {%- endcapture -%}
9 |
10 | <{{tag|default:'a'}} {% if href %} href='{{ href }}' {% endif %} {% if name %} name='{{ name }}' {% endif %} {% if disabled %} disabled {% endif %} type='{{ type | default: 'button' }}' class='{{ cx | strip }}' role='button'>
11 | {{ cta | strip }}
12 | {{tag|default:'a'}}>
13 |
14 | {% assign cta = blank %}
15 | {% assign className = blank %}
16 | {% assign href = blank %}
17 | {% assign name = blank %}
18 | {% assign disabled = false %}
19 | {% assign type = 'button' %}
20 | {% assign cx = '' %}
21 |
--------------------------------------------------------------------------------
/src/snippets/component-counter.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | /components/counter.css
3 | {% endcomment %}
4 |
5 |
6 |
7 | -
8 |
9 | +
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/snippets/component-image.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% comment %}
6 |
7 | DEBUG image.liquid
8 |
9 | src:{{ src }}
10 | file:{{ file }}
11 | asset:{{ asset }}
12 | size:{{ size }}
13 | product:{{ product }}
14 | _src:{{ _src }}
15 |
16 | {% endcomment %}
17 |
18 | {% comment %}reset{% endcomment %}
19 | {% assign src = blank %}
20 | {% assign alt = blank %}
21 | {% assign className = blank %}
22 | {% assign fill = false %}
23 |
--------------------------------------------------------------------------------
/src/snippets/component-input.liquid:
--------------------------------------------------------------------------------
1 | {% if type == blank %}
2 | {% assign type = 'text' %}
3 | {% endif %}
4 | {% if autocomplete == blank %}
5 | {% assign autocomplete = 'off' %}
6 | {% endif %}
7 | {% if autocapitalize == blank %}
8 | {% assign autocapitalize = 'off' %}
9 | {% endif %}
10 | {% if spellcheck == blank %}
11 | {% assign spellcheck = 'false' %}
12 | {% endif %}
13 | {% if required == blank %}
14 | {% assign required = 'false' %}
15 | {% endif %}
16 |
17 | {%- capture id -%}
18 | {{type}}{{name}}{{ className | replace: ' ', '' }}
19 | {%- endcapture -%}
20 |
21 | {%- capture cx -%}
22 | input-wrapper x
23 | {{ className }}
24 | {%- endcapture -%}
25 |
26 |
27 |
28 | {{ label }}
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/snippets/component-radio.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{label}}
4 |
5 |
6 | {% assign label = blank %}
7 | {% assign name = blank %}
8 | {% assign value = blank %}
9 | {% assign attributes = blank %}
10 | {% assign checked = blank %}
11 |
--------------------------------------------------------------------------------
/src/snippets/component-select.liquid:
--------------------------------------------------------------------------------
1 | {%- capture uid -%}
2 | select{{name | default: label}}
3 | {%- endcapture -%}
4 |
5 | {%- capture cx -%}
6 | select-wrapper x rel
7 | {{ className }}
8 | {%- endcapture -%}
9 |
10 |
11 |
{{ label }}
12 |
13 |
14 |
15 | {% if placeholder %}
16 | {{ placeholder }}
17 | {% endif %}
18 | {{ options }}
19 |
20 |
21 |
22 |
23 |
24 |
25 | {% assign id = blank %}
26 | {% assign style = blank %}
27 | {% assign label = blank %}
28 | {% assign name = blank %}
29 | {% assign required = blank %}
30 | {% assign value = blank %}
31 | {% assign placeholder = blank %}
32 | {% assign options = blank %}
33 | {% assign attributes = blank %}
34 |
--------------------------------------------------------------------------------
/src/snippets/component-textarea.liquid:
--------------------------------------------------------------------------------
1 | {% if type == blank %}
2 | {% assign type = 'text' %}
3 | {% endif %}
4 | {% if autocomplete == blank %}
5 | {% assign autocomplete = 'off' %}
6 | {% endif %}
7 | {% if autocapitalize == blank %}
8 | {% assign autocapitalize = 'off' %}
9 | {% endif %}
10 | {% if spellcheck == blank %}
11 | {% assign spellcheck = 'false' %}
12 | {% endif %}
13 | {% if rows == blank %}
14 | {% assign rows = '3' %}
15 | {% endif %}
16 | {% if required == blank %}
17 | {% assign required = 'false' %}
18 | {% endif %}
19 |
20 | {%- capture cx -%}
21 | input-wrapper x
22 | {{ className }}
23 | {%- endcapture -%}
24 |
25 |
26 |
27 | {{ label }}
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/snippets/css-variables.liquid:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/snippets/head-meta.liquid:
--------------------------------------------------------------------------------
1 | {% assign social_type = 'website' %}
2 | {% assign social_title = page_title | strip_html | escape %}
3 | {% assign social_description = page_description | strip_html | escape %}
4 | {% assign social_image = 'social-image.jpg' | file_url %}
5 |
6 | {% if template contains 'product' %}
7 | {% assign social_type = 'product' %}
8 | {% assign social_title = product.title | strip_html | escape %}
9 | {% assign social_price = product.price | money_without_currency | strip_html | escape %}
10 | {% assign social_currency = shop.currency %}
11 | {% if product.featured_image %}
12 | {% assign social_product_img = current_variant.featured_image | default: product.featured_image %}
13 | {% assign social_image = social_product_img | img_url: '1024x' %}
14 | {% endif %}
15 | {% elsif template contains 'collection' %}
16 | {% if collection.description != blank %}
17 | {% assign social_description = collection.description | strip_html | escape %}
18 | {% endif %}
19 | {% if collection.image %}
20 | {% assign social_image = collection.image | img_url: 'master' %}
21 | {% endif %}
22 | {% elsif template contains 'blog' %}
23 | {% for article in blog.articles %}
24 | {% if article.image %}
25 | {% assign social_image = article.image | img_url: 'master' %}
26 | {% break %}
27 | {% endif %}
28 | {% endfor %}
29 | {% elsif template contains 'article' %}
30 | {% assign social_type = 'article' %}
31 | {% assign img_tag = '<' | append: 'img' %}
32 | {% if article.image %}
33 | {% assign social_image = article.image | img_url: 'master' %}
34 | {% elsif article.content contains img_tag %}
35 | {% assign src = article.content | split: 'src="' %}
36 | {% assign src = src[1] | split: '"' | first | remove: 'https:' | remove: 'http:' %}
37 | {% assign social_image = src %}
38 | {% endif %}
39 | {% endif %}
40 |
41 | {% comment %}
42 | Page-level Overrides
43 |
44 | Override variable assignments below this
45 | using `template contains` and similar operators.
46 | {% endcomment %}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {% if social_price %}
57 | {% endif %}
58 | {% if social_currency %}
59 | {% endif %}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/snippets/hero.liquid:
--------------------------------------------------------------------------------
1 |
2 | {{ section.settings.title }}
3 |
--------------------------------------------------------------------------------
/src/snippets/pagination.liquid:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/snippets/product-head.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
35 |
36 |
42 |
--------------------------------------------------------------------------------
/src/snippets/product-options.liquid:
--------------------------------------------------------------------------------
1 |
2 | {% unless product.has_only_default_variant %}
3 | {% comment %}
4 |
5 |
use selects
6 | {% for option in product.options_with_values %}
7 |
8 | {%- capture options -%}
9 | {% for value in option.values %}
10 |
12 | {{ value }}
13 |
14 | {% endfor %}
15 | {%- endcapture -%}
16 | {%- capture attributes -%}
17 | data-option-select
18 | data-index='{{ forloop.index0 }}'
19 | {%- endcapture -%}
20 | {%
21 | include 'component-select' with
22 | label: option.name,
23 | name: option.name,
24 | attributes: attributes
25 | options: options
26 | %}
27 |
28 | {% endfor %}
29 |
30 | {% endcomment %}
31 |
32 |
33 |
34 | or radios
35 |
36 | {% for option in product.options_with_values %}
37 |
38 |
{{ option.name }}
39 |
40 | {% for value in option.values %}
41 | {%
42 | include 'component-radio' with
43 | label: value,
44 | value: value,
45 | name: option.name,
46 | checked: option.selected_value == value
47 | %}
48 | {% endfor %}
49 |
50 |
51 | {% endfor %}
52 |
53 | {% endunless %}
54 |
55 | {%- capture options -%}
56 | {% for variant in product.variants %}
57 |
{{ variant.title }}
58 | {% endfor %}
59 | {%- endcapture -%}
60 |
61 | {%
62 | include 'component-select' with
63 | name: 'id'
64 | options: options
65 | placeholder: 'Select Option'
66 | className: 'js-variant'
67 | required: true
68 | attributes: 'data-option-main'
69 | %}
70 |
71 |
--------------------------------------------------------------------------------
/src/snippets/search.liquid:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/snippets/social-sharing.liquid:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/styles/components/buttons.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * /snippets/component-button.liquid
3 | */
4 | button {
5 | border: 0;
6 | border-radius: 0;
7 | display: inline-block;
8 | cursor: pointer;
9 | -webkit-appearance: none;
10 | }
11 | button.button,
12 | .button[role='button'],
13 | input.button[type='submit'] {
14 | background-color: var(--black);
15 | color: white;
16 | padding: 0.5em 1.5em;
17 | }
18 |
--------------------------------------------------------------------------------
/src/styles/components/cart-drawer.scss:
--------------------------------------------------------------------------------
1 | .cart-drawer-outer {
2 | display: none;
3 |
4 | &.is-active {
5 | display: block;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/styles/components/counter.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * /snippets/component-counter.liquid
3 | */
4 | .counter {
5 | button {
6 | height: 25px;
7 | width: 25px;
8 | background: black;
9 | color: white;
10 | line-height: 23px;
11 | text-align: center;
12 | }
13 | input {
14 | width: 4ch;
15 | padding: 0.3em;
16 | margin: 0 0.5em;
17 | text-align: center;
18 | height: auto;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/styles/components/image.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * /snippets/component-image.liquid
3 | */
4 | .image {
5 | background: var(--green);
6 | text-align: center;
7 | overflow: hidden;
8 | transition: opacity 200ms var(--cubic);
9 |
10 | &::before {
11 | content: attr(data-alt);
12 | display: block;
13 | position: absolute;
14 | top: 0;
15 | bottom: 0;
16 | left: 0;
17 | right: 0;
18 | margin: auto;
19 | font-size: 0.88rem;
20 | font-family: 'NB International Mono', monospace;
21 | opacity: 0.6;
22 | max-width: 200px;
23 | max-height: 50px;
24 | overflow: hidden;
25 | z-index: 0;
26 | transition: opacity 200ms var(--cubic);
27 | }
28 |
29 | img {
30 | object-fit: cover;
31 | opacity: 0;
32 | transition: opacity 200ms var(--cubic);
33 | }
34 |
35 | &.is-loaded {
36 | &::before {
37 | opacity: 0;
38 | }
39 | img {
40 | opacity: 1;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/styles/global/colors.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --black: #262c4d;
3 | --white: #fff;
4 | --green: #a4e1b9;
5 | --blue: #344fda;
6 | }
7 |
8 | .c-white {
9 | color: var(--white);
10 | }
11 | .bg-white {
12 | background-color: var(--white);
13 | }
14 |
15 | .c-black {
16 | color: var(--black);
17 | }
18 | .bg-black {
19 | background-color: var(--black);
20 | }
21 |
22 | .c-green {
23 | color: var(--green);
24 | }
25 | .bg-green {
26 | background-color: var(--green);
27 | }
28 |
29 | .c-blue {
30 | color: var(--blue);
31 | }
32 | .bg-blue {
33 | background-color: var(--blue);
34 | }
35 |
--------------------------------------------------------------------------------
/src/styles/global/containers.scss:
--------------------------------------------------------------------------------
1 | .outer {
2 | padding-left: 1.5em;
3 | padding-right: 1.5em;
4 | @media (--600) {
5 | padding-left: 3em;
6 | padding-right: 3em;
7 | }
8 | }
9 |
10 | .container--s {
11 | max-width: 600px;
12 | }
13 | .container--m {
14 | max-width: 1000px;
15 | }
16 | .container--l {
17 | max-width: 1400px;
18 | }
19 |
--------------------------------------------------------------------------------
/src/styles/global/forms.scss:
--------------------------------------------------------------------------------
1 | form {
2 | margin: 0;
3 | }
4 | input,
5 | textarea,
6 | select {
7 | display: block;
8 | outline: 0;
9 | border-radius: 0;
10 | border: 0;
11 | position: relative;
12 | font-size: inherit;
13 | background-color: transparent;
14 | padding: 0.75em 1em;
15 | color: var(--black);
16 |
17 | &:focus {
18 | border-color: var(--black);
19 | }
20 | &::-webkit-input-placeholder {
21 | color: var(--black);
22 | }
23 | /* Change the white to any color ;) */
24 | &:-webkit-autofill {
25 | -webkit-box-shadow: 0 0 0 30px white inset;
26 | }
27 | }
28 | textarea {
29 | max-width: 100%;
30 | overflow: auto;
31 | resize: vertical;
32 | min-height: 55px;
33 | padding-top: 0.5em;
34 | }
35 | .select-wrapper {
36 | display: block;
37 | position: relative;
38 |
39 | select {
40 | appearance: none;
41 | padding-right: 50px;
42 | width: 100%;
43 | border: 1px solid var(--black);
44 | }
45 |
46 | img {
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/styles/global/global.scss:
--------------------------------------------------------------------------------
1 | #root {
2 | transition: opacity 200ms var(--cubic);
3 | }
4 | .is-transitioning #root {
5 | opacity: 0;
6 | }
7 |
--------------------------------------------------------------------------------
/src/styles/global/lists.scss:
--------------------------------------------------------------------------------
1 | ol,
2 | ul {
3 | list-style: none;
4 | padding: 0;
5 | margin: 0;
6 | }
7 | ul.list,
8 | ol.list {
9 | padding-left: 2em;
10 | }
11 | ol.list {
12 | list-style: decimal;
13 | }
14 | ul.list {
15 | list-style: disc;
16 | }
17 |
--------------------------------------------------------------------------------
/src/styles/global/typography.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,700,700i');
2 |
3 | html {
4 | font-size: var(--base-font-size);
5 | }
6 |
7 | body {
8 | color: var(--color-body-text);
9 | font-size: 100%;
10 | font-family: 'Roboto Mono', -apple-system, system-ui, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
11 | line-height: 1.7;
12 | font-weight: 400;
13 | -webkit-font-smoothing: antialiased;
14 | }
15 |
16 | /**
17 | * @font-face definitions should be in /assets/fonts.css.liquid
18 | * and linked in of theme.liquid
19 | */
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6 {
26 | font-weight: 400;
27 | }
28 | .h0 {
29 | font-size: calc((54 / 16) * 1rem);
30 | }
31 | .s1,
32 | h1,
33 | .h1 {
34 | font-size: calc((32 / 16) * 1rem);
35 | }
36 | h1,
37 | .h1 {
38 | line-height: 1.1;
39 | }
40 | .s2,
41 | h2,
42 | .h2 {
43 | font-size: calc((24 / 16) * 1rem);
44 | }
45 | h2,
46 | .h2 {
47 | line-height: 1.2;
48 | }
49 | .s3,
50 | h3,
51 | .h3 {
52 | font-size: calc((22 / 16) * 1rem);
53 | }
54 | h3,
55 | .h3 {
56 | line-height: 1.3;
57 | }
58 | .s4,
59 | h4,
60 | .h4 {
61 | font-size: calc((18 / 16) * 1rem);
62 | }
63 | h4,
64 | .h4 {
65 | line-height: 1.4;
66 | }
67 | .s5,
68 | h5,
69 | .h5 {
70 | font-size: calc((16 / 16) * 1rem);
71 | }
72 | h5,
73 | .h5 {
74 | line-height: 1.5;
75 | }
76 | .s6,
77 | h6,
78 | .h6 {
79 | font-size: calc((14 / 16) * 1rem);
80 | }
81 | h6,
82 | .h6 {
83 | line-height: 1.5;
84 | }
85 | .s0,
86 | p,
87 | .p {
88 | font-size: 1rem;
89 | }
90 | p,
91 | .p {
92 | line-height: 1.6;
93 | }
94 | small,
95 | .small {
96 | font-size: calc((12 / 16) * 1rem);
97 | }
98 | .xsmall {
99 | font-size: calc((10 / 16) * 1rem);
100 | }
101 | blockquote {
102 | text-align: center;
103 | color: var(--blue);
104 | padding: 10px;
105 | margin: 0;
106 | p {
107 | font-family: 'freight-display-pro', serif;
108 | font-size: 26px;
109 | line-height: 1.4;
110 | }
111 | }
112 | a {
113 | color: inherit;
114 | text-decoration: none;
115 | }
116 | hr {
117 | display: block;
118 | border: 0;
119 | margin: 0;
120 | height: 1px;
121 | width: 100%;
122 | background-color: currentColor;
123 | color: inherit;
124 | }
125 | strong,
126 | .b {
127 | font-weight: bold;
128 | }
129 | em,
130 | .i {
131 | font-style: italic;
132 | }
133 | .caps {
134 | text-transform: uppercase;
135 | }
136 | .no-under {
137 | text-decoration: none;
138 | }
139 | .light {
140 | font-weight: 300;
141 | }
142 | .book,
143 | .regular,
144 | .medium {
145 | font-weight: 400;
146 | }
147 | .medium {
148 | font-weight: 500;
149 | }
150 | .semi {
151 | font-weight: 600;
152 | }
153 | .bold {
154 | font-weight: 700;
155 | }
156 | .track {
157 | letter-spacing: 0.1em;
158 | }
159 | .track--wide {
160 | letter-spacing: 0.3em;
161 | }
162 | .underline {
163 | position: relative;
164 | overflow: hidden;
165 | &:after {
166 | content: '';
167 | transition: all 0.25s;
168 | position: absolute;
169 | bottom: 0;
170 | left: 0;
171 | width: 100%;
172 | height: 1px;
173 | border-bottom: 1px solid var(--orange);
174 | }
175 | &--black {
176 | &:after {
177 | bottom: 4px;
178 | border-bottom-color: var(--black);
179 | }
180 | }
181 | &--white {
182 | &:after {
183 | bottom: -2px;
184 | border-bottom-color: white;
185 | border-bottom-width: 2px;
186 | }
187 | &:hover {
188 | &:after {
189 | transform: translateY(4px);
190 | }
191 | }
192 | }
193 | &:hover {
194 | &:after {
195 | transform: translateY(4px);
196 | }
197 | }
198 | }
199 | .link-hover {
200 | transition: all var(--fast) var(--ease);
201 | &:hover {
202 | color: var(--yellow);
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/styles/global/var.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --ease: ease-in-out;
3 | --cubic: cubic-bezier(0.12, 0.67, 0.53, 1);
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/main.scss:
--------------------------------------------------------------------------------
1 | @import 'node_modules/gridlex/src/gridlex';
2 |
3 | @import 'global/var';
4 | @import 'global/colors';
5 | @import 'global/typography';
6 | @import 'global/global';
7 | @import 'global/lists';
8 | @import 'global/forms';
9 | @import 'global/containers';
10 |
11 | @import 'components/buttons';
12 | @import 'components/image';
13 | @import 'components/counter';
14 | @import 'components/cart-drawer';
15 |
16 | #preview-bar-iframe,
17 | .none {
18 | display: none !important;
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/templates/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UseAllFive/slater-base/c280af36cb48e8a2ed8cf7346f1dbeac1e139aec/src/styles/templates/.gitkeep
--------------------------------------------------------------------------------
/src/templates/404.liquid:
--------------------------------------------------------------------------------
1 | {{ 'general.404.title' | t }}
2 | {{ 'general.404.subtext_html' | t }}
3 |
--------------------------------------------------------------------------------
/src/templates/article.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | When a comment is submitted, the browser is redirected to a page that includes
3 | the new comment id in its URL.
4 | #comments is a required ID and is used as an anchor link by Shopify.
5 | {% endcomment %}
6 |
7 | {%- assign number_of_comments = article.comments_count -%}
8 |
9 | {% comment %}
10 | If a comment was just submitted but requires moderation, we have an extra comment to count.
11 | {% endcomment %}
12 | {% if comment and comment.status != 'published' %}
13 | {% assign number_of_comments = article.comments_count | plus: 1 %}
14 | {% endif %}
15 |
16 |
17 |
18 | {% if article.image %}
19 | {{ article | img_url: '1024x1024' | img_tag: article.title }}
20 | {% endif %}
21 |
22 |
29 |
30 |
31 | {{ article.content }}
32 |
33 |
34 | {% if article.tags.size > 0 %}
35 |
36 | {% for tag in article.tags %}
37 |
38 | {{ tag }}
39 | {% unless forloop.last %},
40 | {% endunless %}
41 |
42 | {% endfor %}
43 |
44 | {% endif %}
45 |
46 | {% if settings.social_sharing_blog %}
47 | {% include 'social-sharing', share_title: article.title, share_permalink: article.url, share_image: article.image %}
48 | {% endif %}
49 |
50 | {% if blog.comments_enabled? %}
51 | {{ 'blogs.comments.with_count' | t: count: number_of_comments }}
52 |
53 | {% paginate article.comments by 5 %}
54 |
55 |
102 |
103 | {% endpaginate %}
104 |
105 | {% form 'new_comment', article %}
106 | {{ 'blogs.comments.title' | t }}
107 |
108 | {{ form.errors | default_errors }}
109 |
110 |
111 | {{ 'blogs.comments.name' | t }}
112 |
113 |
114 |
115 |
116 | {{ 'blogs.comments.email' | t }}
117 |
118 |
119 |
120 |
121 | {{ 'blogs.comments.message' | t }}
122 |
123 |
126 |
127 | {% if blog.moderated? %}
128 | {{ 'blogs.comments.moderated' | t }}
129 | {% endif %}
130 |
131 |
132 | {% endform %}
133 |
134 | {% endif %}
135 |
136 |
137 |
--------------------------------------------------------------------------------
/src/templates/blog.liquid:
--------------------------------------------------------------------------------
1 | {% paginate blog.articles by 5 %}
2 |
3 | {%- assign blog_title = blog.title -%}
4 |
5 | {% if current_tags %}
6 | {% capture blog_title %}
7 | {{ blog.title | link_to: blog.url }}
8 | —
9 | {{ current_tags.first }}
10 | {% endcapture %}
11 | {% endif %}
12 |
13 | {{ blog_title }}
14 |
15 | {% if blog.all_tags.size > 0 %}
16 | {{ 'blogs.general.categories' | t }}
17 |
18 |
19 | {% for tag in blog.all_tags %}
20 |
21 | {% if current_tags contains tag %}
22 | {{ tag }}
23 | {% else %}
24 | {{ tag | link_to_tag: tag }}
25 | {% endif %}
26 |
27 | {% endfor %}
28 |
29 | {% endif %}
30 |
31 | {% comment %}
32 |
33 | Article Previews
34 | ====================
35 | {% endcomment %}
36 |
37 | {% for article in blog.articles %}
38 |
41 |
42 | {% capture date %}
43 | {{ article.published_at | time_tag: format: 'month_day_year' }}
44 | {% endcapture %}
45 |
46 |
47 | {{ 'blogs.article.author_on_date_html' | t: author: article.author, date: date }}
48 |
49 |
50 | {% if article.image %}
51 |
52 | {{ article | img_url: '1024x1024' | img_tag: article.title }}
53 |
54 | {% endif %}
55 |
56 |
57 | {% if article.excerpt.size > 0 %}
58 | {{ article.excerpt }}
59 | {% else %}
60 |
61 | {{ article.content | strip_html | truncatewords: 100 }}
62 |
63 | {% endif %}
64 |
65 |
66 | {% if blog.comments_enabled? or article.tags.size > 0 %}
67 |
86 | {% endif %}
87 |
88 |
89 | {{ 'blogs.article.read_more' | t }}
90 | →
91 |
92 |
93 | {% endfor %}
94 |
95 | {% if paginate.pages > 1 %}
96 | {% include 'pagination' %}
97 | {% endif %}
98 |
99 | {% endpaginate %}
100 |
--------------------------------------------------------------------------------
/src/templates/cart.liquid:
--------------------------------------------------------------------------------
1 |
2 | {% if cart.item_count > 0 %}
3 |
{{ 'cart.general.title' | t }}
4 |
5 |
104 | {% else %}
105 |
{{ 'cart.general.title' | t }}
106 |
107 | {% comment %}
108 | Cart empty state
109 | {% endcomment %}
110 |
111 |
{{ 'cart.general.empty' | t }}
112 |
{{ 'cart.general.continue_browsing_html' | t }}
113 |
114 |
115 | {% comment %}
116 | Cart no cookies state
117 | ---------------------
118 | Browser cookies are required to use the cart. If cookies aren't enabled in the
119 | browser a message is displayed prompting the user to enable them.
120 | {% endcomment %}
121 |
122 |
{{ 'cart.general.cookies_required' | t }}
123 |
124 | {% endif %}
125 |
126 |
--------------------------------------------------------------------------------
/src/templates/collection.liquid:
--------------------------------------------------------------------------------
1 | {% paginate collection.products by 12 %}
2 |
3 | {% section 'hero-collection' %}
4 |
5 |
6 |
7 |
8 |
9 |
{{ collection.title }}
10 |
11 | {% if collection.description != blank %}
12 |
13 | {{ collection.description }}
14 |
15 | {% endif %}
16 |
17 | {% if collection.all_tags.size > 0 %}
18 |
19 | {% for tag in collection.all_tags %}
20 | {% if current_tags contains tag %}
21 |
22 | {{ tag | link_to_remove_tag: tag }}
23 |
24 | {% else %}
25 |
26 | {{ tag | link_to_tag: tag }}
27 |
28 | {% endif %}
29 | {% endfor %}
30 |
31 | {% endif %}
32 |
33 |
34 |
35 |
36 |
82 |
83 | {% if paginate.pages > 1 %}
84 | {{ paginate | default_pagination }}
85 | {% endif %}
86 |
87 |
88 | {% endpaginate %}
89 |
--------------------------------------------------------------------------------
/src/templates/customers/account.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 | {%
4 | include 'account-titles' with
5 | title: 'My Account'
6 | %}
7 |
8 |
9 |
10 |
Order History
11 |
12 |
13 |
14 | {% paginate customer.orders by 99 %}
15 |
16 | {% if customer.orders.size != 0 %}
17 |
18 |
47 |
48 | {% else %}
49 |
You don't have any orders yet!
50 | {% endif %}
51 |
52 | {% include 'pagination' %}
53 | {% endpaginate %}
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/templates/customers/activate_account.liquid:
--------------------------------------------------------------------------------
1 | {{ 'customer.activate_account.title' | t }}
2 | {{ 'customer.activate_account.subtext' | t }}
3 |
4 | {% form 'activate_customer_password' %}
5 | {{ form.errors | default_errors }}
6 |
7 |
8 |
9 | {{ 'customer.activate_account.password' | t }}
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ 'customer.activate_account.password_confirm' | t }}
17 |
18 |
19 |
20 |
21 |
22 |
23 | {% endform %}
24 |
--------------------------------------------------------------------------------
/src/templates/customers/addresses.liquid:
--------------------------------------------------------------------------------
1 | {% paginate customer.addresses by 100 %}
2 |
3 |
4 |
5 | {% include 'account-titles' with title: 'Addresses' %}
6 |
7 |
Create new address
8 |
9 | {%
10 | include 'account-address-form' with
11 | id: 'new',
12 | action: customer.new_address,
13 | title: 'Add New Address',
14 | cta: 'Add Address',
15 | formClassName: 'mb1 pb1'
16 | %}
17 |
18 | {% comment %}
19 | List all customer addresses with a unique edit form.
20 | Also add pagination in case they have a large number of addresses
21 | {% endcomment %}
22 | {% for address in customer.addresses %}
23 | {% if customer.default_address == address %}
24 | {% assign default_addy = true %}
25 | {% else %}
26 | {% assign default_addy = false %}
27 | {% endif %}
28 | {%
29 | include 'account-address' with
30 | address: address,
31 | default: default_addy,
32 | edit: true,
33 | className: 'mb1'
34 | %}
35 |
36 | {%
37 | include 'account-address-form' with
38 | id: address.id,
39 | action: address,
40 | title: 'Edit Address',
41 | cta: 'Update Address',
42 | formClassName: 'is-edit-form mb1'
43 | %}
44 | {% endfor %}
45 |
46 | {% if customer.addresses == empty %}
47 |
You have no saved address.
48 | {% endif %}
49 |
50 | {% include 'pagination' %}
51 |
52 |
53 |
54 |
55 |
86 |
87 | {% endpaginate %}
88 |
--------------------------------------------------------------------------------
/src/templates/customers/login.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Forgot your password?
5 |
Enter your email and we will send you a password reset link.
6 |
7 | {% form 'recover_customer_password', class: 'mha' %}
8 |
9 | {% if form.posted_successfully? %}
10 |
Success! Check your email for a link to reset your password.
11 | {% else %}
12 | {%
13 | include 'component-input' with
14 | label: 'Email',
15 | placeholder: 'Email',
16 | name: 'email',
17 | type: 'email'
18 | %}
19 |
20 | {% if form.errors %}
21 | {% for field in form.errors %}
22 | {% if field == 'form' %}
23 |
{{ form.errors.messages[field] }}
24 | {% else %}
25 |
{{ form.errors.translated_fields[field] | capitalize }}
26 | {{ form.errors.messages[field] }}.
27 | {% endif %}
28 | {% endfor %}
29 | {% endif %}
30 |
31 |
32 | {%
33 | include 'component-button' with
34 | tag: 'button',
35 | type: 'submit',
36 | cta: 'Reset password',
37 | className: 'btn btn__blue'
38 | %}
39 |
40 | {% endif %}
41 |
42 | {% endform %}
43 |
44 |
Cancel
45 |
46 |
47 |
48 |
Sign In
49 |
50 | {% form 'customer_login', class: 'ma' %}
51 |
52 | {%
53 | include 'component-input' with
54 | label: 'Email',
55 | placeholder: 'Email',
56 | name: 'customer[email]',
57 | className: 'mb1',
58 | type: 'email'
59 | %}
60 |
61 | {%
62 | include 'component-input' with
63 | label: 'Password',
64 | placeholder: 'Password',
65 | name: 'customer[password]',
66 | type: 'password',
67 | className: 'mb1'
68 | %}
69 |
70 |
71 | {% if form.errors %}
72 | {% for field in form.errors %}
73 | {% if field == 'form' %}
74 |
{{ form.errors.messages[field] }}
75 | {% else %}
76 |
{{ form.errors.translated_fields[field] | capitalize }}
77 | {{ form.errors.messages[field] }}
78 | {% endif %}
79 | {% endfor %}
80 | {% endif %}
81 |
82 |
83 | {%
84 | include 'component-button' with
85 | tag: 'button',
86 | type: 'submit',
87 | cta: 'Sign in',
88 | className: 'btn btn__blue caps bold'
89 | %}
90 |
91 | {% endform %}
92 |
93 |
100 |
101 | {% if shop.checkout.guest_login %}
102 |
103 |
104 |
105 |
106 | {% form 'guest_login' %}
107 | {%
108 | include 'button' with
109 | tag: 'button',
110 | type: 'submit',
111 | cta: 'Continue as a guest',
112 | className: 'btn btn__blue',
113 | outline: true
114 | %}
115 | {% endform %}
116 | {% endif %}
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/templates/customers/order.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | The data-label attributes on elements are mobile-friendly
3 | helpers used for responsive-table labels
4 | {% endcomment %}
5 |
6 | {{ 'customer.account.title' | t }}
7 |
8 | {{ 'customer.account.return' | t }}
9 |
10 | {{ 'customer.order.title' | t: name: order.name }}
11 |
12 | {{ 'customer.order.date' | t: date: order.created_at | date: "%B %d, %Y %I:%M%p" }}
13 |
14 | {% if order.cancelled %}
15 | {%- assign cancelled_at = order.cancelled_at | date: "%B %d, %Y %I:%M%p" -%}
16 | {{ 'customer.order.cancelled' | t: date: cancelled_at }}
17 | {{ 'customer.order.cancelled_reason' | t: reason: order.cancel_reason }}
18 | {% endif %}
19 |
20 |
21 |
22 |
23 | {{ 'customer.order.product' | t }}
24 | {{ 'customer.order.sku' | t }}
25 | {{ 'customer.order.price' | t }}
26 | {{ 'customer.order.quantity' | t }}
27 | {{ 'customer.order.total' | t }}
28 |
29 |
30 |
31 | {% for line_item in order.line_items %}
32 |
33 |
34 | {{ line_item.title | link_to: line_item.product.url }}
35 | {% if line_item.fulfillment %}
36 |
43 | {% endif %}
44 |
45 | {{ line_item.sku }}
46 | {{ line_item.price | money }}
47 | {{ line_item.quantity }}
48 | {{ line_item.quantity | times: line_item.price | money }}
49 |
50 | {% endfor %}
51 |
52 |
53 |
54 | {{ 'customer.order.subtotal' | t }}
55 | {{ order.subtotal_price | money }}
56 |
57 |
58 | {% for discount in order.discounts %}
59 |
60 | {{ discount.code }} {{ 'customer.order.discount' | t }}
61 | {{ discount.savings | money }}
62 |
63 | {% endfor %}
64 |
65 | {% for shipping_method in order.shipping_methods %}
66 |
67 | {{ 'customer.order.shipping' | t }} ({{ shipping_method.title }})
68 | {{ shipping_method.price | money }}
69 |
70 | {% endfor %}
71 |
72 | {% for tax_line in order.tax_lines %}
73 |
74 | {{ 'customer.order.tax' | t }} ({{ tax_line.title }} {{ tax_line.rate | times: 100 }}%)
75 | {{ tax_line.price | money }}
76 |
77 | {% endfor %}
78 |
79 |
80 | {{ 'customer.order.total' | t }}
81 | {{ order.total_price | money }} {{ order.currency }}
82 |
83 |
84 |
85 |
86 | {{ 'customer.order.billing_address' | t }}
87 |
88 | {{ 'customer.order.payment_status' | t }}: {{ order.financial_status_label }}
89 |
90 | {{ order.billing_address | format_address }}
91 |
92 | {{ 'customer.order.shipping_address' | t }}
93 |
94 | {{ 'customer.order.fulfillment_status' | t }}: {{ order.fulfillment_status_label }}
95 |
96 | {{ order.shipping_address | format_address }}
97 |
--------------------------------------------------------------------------------
/src/templates/customers/register.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ 'customer.register.title' | t }}
5 |
6 | {% form 'create_customer' %}
7 | {{ form.errors | default_errors }}
8 |
9 | {%
10 | include 'component-input' with
11 | placeholder: 'First Name',
12 | name: 'customer[first_name]',
13 | value: form.first_name,
14 | type: 'text',
15 | className: 'mb1'
16 | %}
17 | {%
18 | include 'component-input' with
19 | placeholder: 'Last Name',
20 | name: 'customer[last_name]',
21 | type: 'text',
22 | className: 'mb1'
23 | %}
24 | {%
25 | include 'component-input' with
26 | placeholder: 'Email',
27 | name: 'customer[email]',
28 | type: 'email',
29 | value: form.email,
30 | spellcheck: "off",
31 | autocapitalize: "off",
32 | className: 'mb1'
33 | %}
34 |
35 | {%
36 | include 'component-input' with
37 | placeholder: 'Password',
38 | name: 'customer[password]',
39 | type: 'password',
40 | className: 'mb1'
41 | %}
42 |
43 |
44 |
45 | {%
46 | include 'component-button' with
47 | tag: 'button',
48 | type: 'submit',
49 | cta: 'Sign Up',
50 | className: 'btn btn__blue'
51 | %}
52 |
53 |
54 |
61 | {% endform %}
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/templates/customers/reset_password.liquid:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/src/templates/gift_card.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | QR code is rendered in `#QrCode`
3 |
4 | `gift_card.pass_url` is true if apple wallet is enabled for the shop
5 | {% endcomment %}
6 |
7 | {% layout 'gift_card' %}
8 |
9 |
10 |
15 | {{ shop.url | escape }}
16 |
17 |
18 |
19 |
20 | {{ 'gift_cards.issued.subtext' | t }}
21 | {% unless gift_card.enabled %}
22 | {{ 'gift_cards.issued.disabled' | t }}
23 | {% endunless %}
24 |
25 | {%- assign gift_card_expiry_date = gift_card.expires_on | date: "%d/%m/%y" -%}
26 |
27 | {% if gift_card.expired and gift_card.enabled %}
28 | {{ 'gift_cards.issued.expired' | t: expiry: gift_card_expiry_date }}
29 | {% endif %}
30 |
31 | {% if gift_card.expired != true and gift_card.expires_on and gift_card.enabled %}
32 | {{ 'gift_cards.issued.active' | t: expiry: gift_card_expiry_date }}
33 | {% endif %}
34 |
35 |
36 |
37 | {%- assign initial_value_size = formatted_initial_value | size -%}
38 | {{ formatted_initial_value }}
39 |
40 | {% if gift_card.balance != gift_card.initial_value %}
41 | {{ 'gift_cards.issued.remaining_html' | t: balance: gift_card.balance | money }}
42 | {% endif %}
43 |
44 | {%- assign code_size = gift_card.code | format_code | size -%}
45 | {{ gift_card.code | format_code }}
46 |
47 | {{ 'gift_cards.issued.redeem' | t }}
48 |
49 | {{ 'gift_cards.issued.shop_link' | t }}
50 |
51 |
52 |
53 | {% if gift_card.pass_url %}
54 |
55 |
56 |
57 | {% endif %}
58 |
59 |
60 | {{ 'gift_cards.issued.print' | t }}
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/templates/index.liquid:
--------------------------------------------------------------------------------
1 | {{ content_for_index }}
2 |
--------------------------------------------------------------------------------
/src/templates/list-collections.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | This page represents the /collections and /products pages.
3 | {% endcomment %}
4 |
5 | {{ page_title }}
6 |
7 | {% for collection in collections %}
8 | {% unless collection.handle == 'frontpage' %}
9 |
10 | {% if collection.image != blank %}
11 | {{ collection | img_url: '480x480' | img_tag: collection.title }}
12 | {% elsif collection.products.first != blank %}
13 | {{ collection.products.first | img_url: '480x480' | img_tag: collection.title }}
14 | {% else %}
15 | {% capture current %}
16 | {% cycle 1, 2, 3, 4, 5, 6 %}
17 | {% endcapture %}
18 | {{ 'collection-' | append: current | placeholder_svg_tag: 'placeholder-svg placeholder-svg--small' }}
19 | {% endif %}
20 |
21 |
22 |
23 | {{ collection.title }}
24 |
25 | {% endunless %}
26 | {% endfor %}
27 |
--------------------------------------------------------------------------------
/src/templates/page.contact.liquid:
--------------------------------------------------------------------------------
1 | {{ page.title }}
2 |
3 |
4 | {{ page.content }}
5 |
6 |
7 | {% form 'contact' %}
8 |
9 | {% if form.posted_successfully? %}
10 |
11 | {{ 'contact.form.post_success' | t }}
12 |
13 | {% endif %}
14 |
15 | {{ form.errors | default_errors }}
16 |
17 | {{ 'contact.form.name' | t }}
18 |
19 |
20 | {{ 'contact.form.email' | t }}
21 |
22 |
23 | {{ 'contact.form.phone' | t }}
24 |
25 |
26 | {{ 'contact.form.message' | t }}
27 |
28 | {% if form.body %}
29 | {{ form.body }}
30 | {% endif %}
31 |
32 |
33 |
34 |
35 | {% endform %}
36 |
--------------------------------------------------------------------------------
/src/templates/page.liquid:
--------------------------------------------------------------------------------
1 | {{ page.title }}
2 |
3 |
4 | {{ page.content }}
5 |
6 |
--------------------------------------------------------------------------------
/src/templates/password.liquid:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | The share buttons share the home page URL. The share text is grabbed from the store
3 | meta description.
4 | {% endcomment %}
5 |
6 | {% layout 'password' %}
7 |
8 | {{ 'general.password_page.opening_soon' | t }}
9 |
10 | {% unless shop.password_message == blank %}
11 |
12 | {{ shop.password_message }}
13 |
14 | {% endunless %}
15 |
16 | {% form 'customer' %}
17 |
18 | {{ form.errors | default_errors }}
19 |
20 | {% if form.posted_successfully? %}
21 |
24 | {% else %}
25 | {{ 'general.password_page.signup_form_heading' | t }}
26 |
27 |
28 | {{ 'general.password_page.signup_form_email_label' | t }}
29 |
30 |
31 |
32 | asdfdfdf
33 | {{ 'general.password_page.signup_form_submit' | t }}
34 |
35 | {% endif %}
36 | {% endform %}
37 |
38 | {% if settings.share_facebook or settings.share_twitter %}
39 | {{ 'general.password_page.spread_the_word' | t }}
40 | {% include 'social-sharing' %}
41 | {% endif %}
42 |
43 |
44 | {% include 'icon-lock' %}
45 | {{ 'general.password_page.password_link' | t }}
46 | →
47 |
48 |
--------------------------------------------------------------------------------
/src/templates/product.liquid:
--------------------------------------------------------------------------------
1 |
2 | {%- assign current_variant = product.selected_or_first_available_variant -%}
3 | {%- assign featured_image = current_variant.featured_image | default: product.featured_image -%}
4 |
5 | {% include 'product-head' %}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {% for image in product.images %}
14 |
15 |
16 |
17 | {% endfor %}
18 |
19 |
20 |
21 |
22 |
{{ product.title }}
23 |
24 |
25 | {{ product.description }}
26 |
27 |
28 |
29 | {% include 'product-options' %}
30 |
31 |
32 | {% include 'component-counter' %}
33 |
34 |
35 |
36 | {{ current_variant.price | money }}
37 | {{ current_variant.compare_at_price | money }}
38 |
39 |
40 |
41 | {%- capture cta -%}
42 | {% if product.available %}
43 | Add to Cart
44 | {% else %}
45 | Out of Stock
46 | {% endif %}
47 | {%- endcapture -%}
48 | {%
49 | include 'component-button' with
50 | tag: 'button',
51 | name: 'add',
52 | type: 'submit',
53 | className: 'js-add-to-cart',
54 | cta: cta
55 | %}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/templates/product.neue.liquid:
--------------------------------------------------------------------------------
1 |
2 | {% assign current_variant = product.selected_or_first_available_variant %}
3 |
4 | {% comment %} {% unless product.has_only_default_variant %}{% endunless %} {% endcomment %}
5 | {% include 'product-head' %}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {% for image in product.images %}
14 |
15 |
16 |
17 | {% endfor %}
18 |
19 |
20 |
21 |
{{ product.title }}
22 |
23 |
24 | {{ product.description }}
25 |
26 |
27 |
28 |
29 | {% unless product.has_only_default_variant %}
30 |
31 |
32 | use selects
33 |
34 | {% for option in product.options_with_values %}
35 |
36 | {%- capture options -%}
37 | {% for value in option.values %}
38 |
39 | {{ value }}
40 |
41 | {% endfor %}
42 | {%- endcapture -%}
43 | {%- capture attributes -%}
44 | data-option-select
45 | data-index='{{ forloop.index0 }}'
46 | {%- endcapture -%}
47 | {%
48 | include 'component-select' with
49 | label: option.name,
50 | name: option.name,
51 | attributes: attributes
52 | options: options
53 | %}
54 |
55 | {% endfor %}
56 |
57 |
58 |
59 |
60 | or radios
61 |
62 | {% for option in product.options_with_values %}
63 |
64 |
{{ option.name }}
65 |
66 | {% for value in option.values %}
67 | {%
68 | include 'component-radio' with
69 | label: value,
70 | value: value,
71 | name: option.name,
72 | checked: option.selected_value == value
73 | %}
74 | {% endfor %}
75 |
76 |
77 | {% endfor %}
78 |
79 | {% endunless %}
80 |
81 | {%- capture options -%}
82 | {% for variant in product.variants %}
83 |
{{ variant.title }}
84 | {% endfor %}
85 | {%- endcapture -%}
86 |
87 | {%
88 | include 'component-select' with
89 | name: 'id',
90 | options: options,
91 | attributes: 'data-option-main',
92 | %}
93 |
94 |
95 |
96 | {% include 'component-counter' %}
97 |
98 |
99 |
100 | {{ current_variant.price | money }}
101 | {{ current_variant.compare_at_price | money }}
102 |
103 |
104 |
105 | {%- capture cta -%}
106 | {% if product.available %}
107 | Add to Cart
108 | {% else %}
109 | Out of Stock
110 | {% endif %}
111 | {%- endcapture -%}
112 | {%
113 | include 'component-button' with
114 | tag: 'button',
115 | name: 'add',
116 | type: 'submit',
117 | className: 'js-add-to-cart',
118 | cta: cta
119 | %}
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/templates/search.liquid:
--------------------------------------------------------------------------------
1 | {% paginate search.results by 10 %}
2 |
3 |
4 | {% if search.performed %}
5 | {% if search.results_count == 0 %}
6 | {{ 'general.search.no_results_html' | t: terms: search.terms }}
7 | {% else %}
8 | {{ 'general.search.results_for_html' | t: terms: search.terms }}
9 | {% endif %}
10 | {% else %}
11 | {{ 'general.search.title' | t }}
12 | {% endif %}
13 |
14 |
15 |
16 |
17 | {{ 'general.search.placeholder' | t }}
18 |
19 |
20 |
21 | {% include 'icon-search' %}
22 | {{ 'general.search.submit' | t }}
23 |
24 |
25 |
26 | {% if search.performed %}
27 |
28 | {% for item in search.results %}
29 |
30 | {% if item.featured_image %}
31 |
32 | {{ item.featured_image.src | img_url: '240x240' | img_tag: item.featured_image.alt }}
33 |
34 | {% endif %}
35 |
36 | {{ item.title | link_to: item.url }}
37 |
38 | {% if item.object_type == 'product' %}
39 |
40 | {% if item.compare_at_price > item.price %}
41 | {% if item.price_varies %}
42 | {% assign sale_price = item.price | money %}
43 | {{ 'products.product.on_sale_from_html' | t: price: sale_price }}
44 | {% else %}
45 | {{ 'products.product.on_sale' | t }}
46 | {{ item.price | money }}
47 | {% endif %}
48 | {{ 'products.product.regular_price' | t }}
49 | {{ item.compare_at_price | money }}
50 | {% else %}
51 | {% if item.price_varies %}
52 | {% assign price = item.price | money %}
53 | {{ 'products.product.from_text_html' | t: price: price }}
54 | {% else %}
55 | {{ item.price | money }}
56 | {% endif %}
57 | {% endif %}
58 | {% unless item.available %}
59 | {{ 'products.product.sold_out' | t }}
60 | {% endunless %}
61 |
62 | {% else %}
63 | {{ item.content | strip_html | truncatewords: 50 }}
64 | {% endif %}
65 |
66 |
67 | {% endfor %}
68 |
69 |
70 | {% if paginate.pages > 1 %}
71 | {% include 'pagination' %}
72 | {% endif %}
73 | {% endif %}
74 |
75 | {% endpaginate %}
76 |
--------------------------------------------------------------------------------
58 | {% if blog.moderated? and comment.status != 'published' %} 59 | {{ 'blogs.comments.success_moderated' | t }} 60 | {% else %} 61 | {{ 'blogs.comments.success' | t }} 62 | {% endif %} 63 |
64 | {% endif %} 65 | 66 | {% if number_of_comments > 0 %} 67 |68 | {% comment %} 69 | Display comment from URL parameters if it is waiting moderation 70 | {% endcomment %} 71 | {% if comment and comment.status != 'published' %} 72 |-
73 |
74 | {{ comment.content }}
75 |
76 | {% capture date %}
77 | {{ comment.created_at | time_tag: format: 'month_day_year' }}
78 | {% endcapture %}
79 |
81 | {% endif %}
82 |
83 | {% for comment in article.comments %}
84 | -
85 |
86 | {{ comment.content }}
87 |
88 | {% capture date %}
89 | {{ comment.created_at | time_tag: format: 'month_day_year' }}
90 | {% endcapture %}
91 |
93 |
94 | {% endfor %}
95 |
96 | 97 | {% if paginate.pages > 1 %} 98 | {% include 'pagination' %} 99 | {% endif %} 100 | {% endif %} 101 |{{ 'blogs.article.comment_meta_html' | t: author: comment.author, date: date }}
80 |{{ 'blogs.article.comment_meta_html' | t: author: comment.author, date: date }}
92 |