├── .babelrc ├── .codeclimate.yml ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── slater.png ├── src ├── assets │ ├── index.js │ ├── index.js.map │ └── main.css ├── config-sample.yml ├── config │ ├── settings_data.json │ └── settings_schema.json ├── icons │ ├── icon-amazon_payments.svg │ ├── icon-american_express.svg │ ├── icon-apple_pay.svg │ ├── icon-arrow-down.svg │ ├── icon-bitcoin.svg │ ├── icon-cart.svg │ ├── icon-cirrus.svg │ ├── icon-close.svg │ ├── icon-dankort.svg │ ├── icon-diners_club.svg │ ├── icon-discover.svg │ ├── icon-dogecoin.svg │ ├── icon-dwolla.svg │ ├── icon-facebook.svg │ ├── icon-forbrugsforeningen.svg │ ├── icon-hamburger.svg │ ├── icon-instagram.svg │ ├── icon-interac.svg │ ├── icon-jcb.svg │ ├── icon-litecoin.svg │ ├── icon-lock.svg │ ├── icon-maestro.svg │ ├── icon-master.svg │ ├── icon-minus.svg │ ├── icon-paypal.svg │ ├── icon-pinterest.svg │ ├── icon-plus.svg │ ├── icon-rss.svg │ ├── icon-search.svg │ ├── icon-shopify-logo.svg │ ├── icon-snapchat.svg │ ├── icon-tumblr.svg │ ├── icon-twitter.svg │ ├── icon-vimeo.svg │ ├── icon-visa.svg │ └── icon-youtube.svg ├── layout │ ├── gift_card.liquid │ ├── password.liquid │ └── theme.liquid ├── locales │ └── en.default.json ├── scripts │ ├── app.js │ ├── compiled.bundle.js │ ├── components │ │ ├── accountLogin.js │ │ ├── cartDrawer.js │ │ ├── cartDrawerItem.js │ │ ├── header.js │ │ ├── hero.js │ │ ├── input-text.js │ │ └── product.js │ ├── index.js │ ├── lib │ │ └── router.js │ ├── pages │ │ └── product.js │ ├── sections │ │ └── product.js │ ├── slate │ │ ├── sections.js │ │ ├── utils.js │ │ └── variants.js │ ├── slater │ │ ├── cart.js │ │ ├── currency.js │ │ ├── images.js │ │ ├── product-selector.js │ │ └── utils.js │ ├── templates │ │ ├── customers-addresses.js │ │ └── customers-login.js │ └── util │ │ └── theme-provider.js ├── sections │ ├── collection-list.liquid │ ├── featured-collection.liquid │ ├── featured-product.liquid │ ├── footer.liquid │ ├── header.liquid │ ├── product.liquid │ └── s-cart.liquid ├── snippets │ ├── account-address-form.liquid │ ├── account-address.liquid │ ├── account-titles.liquid │ ├── add-to-cart-form.liquid │ ├── button.liquid │ ├── cart-drawer.liquid │ ├── form-input-select.liquid │ ├── form-input-text.liquid │ ├── form-input-textarea.liquid │ ├── head-meta.liquid │ ├── icon-chevron.liquid │ ├── navigation.liquid │ ├── no-blocks.liquid │ ├── pagination.liquid │ ├── search.liquid │ ├── social-sharing.liquid │ └── theme-provider.liquid ├── styles │ ├── main.css │ └── slater │ │ ├── components │ │ ├── accounts.css │ │ ├── buttons.css │ │ ├── cart-drawer.css │ │ └── page-transition.css │ │ ├── lib │ │ ├── breakpoint.css │ │ ├── colors.css │ │ ├── containers.css │ │ ├── forms.css │ │ ├── lists.css │ │ ├── spacing.css │ │ ├── typography.css │ │ ├── var.css │ │ └── visibilty.css │ │ └── theme.css └── 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 │ ├── page.styles.liquid │ ├── password.liquid │ ├── product.liquid │ └── search.liquid ├── theme.lock ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-class-properties", 4 | "transform-object-rest-spread", 5 | "lodash", 6 | "syntax-dynamic-import", 7 | ['module-resolver', { 8 | 'root': ['.'], 9 | 'alias': { 10 | 'slater': './src/scripts/slater', 11 | 'components': './src/scripts/components', 12 | 'lib': './src/scripts/lib', 13 | 'pages': './src/scripts/pages', 14 | 'templates': './src/scripts/templates' 15 | } 16 | }] 17 | ], 18 | "presets": [ 19 | "es2015" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | # other configuration excluded from example... 2 | exclude_patterns: 3 | - "src/assets/*.js" 4 | - "src/assets/*.css" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file contains a list of files and folders to be ignored when 2 | # committing to a git repository. Ignored files are both Slate project 3 | # specific files as well as commonly ignored files on any project. 4 | 5 | # For more information on this .gitignore files, see GitHub's 6 | # documentation: https://help.github.com/articles/ignoring-files/ 7 | 8 | # Project # 9 | ################### 10 | node_modules 11 | dist 12 | upload 13 | deploy.log 14 | build/* 15 | build 16 | config.yml 17 | src/assets/index.js 18 | src/assets/main.css 19 | 20 | # Compiled source # 21 | ################### 22 | *.com 23 | *.class 24 | *.dll 25 | *.exe 26 | *.o 27 | *.so 28 | 29 | # Packages # 30 | ############ 31 | # it's better to unpack these files and commit the raw source 32 | # git has its own built in compression methods 33 | *.7z 34 | *.dmg 35 | *.gz 36 | *.iso 37 | *.jar 38 | *.rar 39 | *.tar 40 | *.zip 41 | 42 | # Logs and databases # 43 | ###################### 44 | *.log 45 | *.sql 46 | *.sqlite 47 | 48 | # OS generated files # 49 | ###################### 50 | .DS_Store 51 | .DS_Store? 52 | ._* 53 | .Spotlight-V100 54 | .Trashes 55 | ehthumbs.db 56 | Thumbs.db 57 | 58 | # Custom # 59 | ###################### 60 | *.sass-cache* 61 | _site 62 | /.idea 63 | secrets.json 64 | .jekyll-metadata 65 | .ruby-version 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## slater theme is deprected and now maintained in slater 2 | 3 | Note, the slater-theme is now bundled with Slater-CLI 4 | 5 | [slater](https://github.com/the-couch/slater) 6 | 7 | This theme is no longer maintained here 8 | 9 | 10 | ----------- 11 | 12 | 13 | # SLATER 14 | Slater than [Slate](https://github.com/Shopify/slate). Minimaler than minimal. 15 | 16 | 17 | 18 | ## Up and Running 19 | 20 | Clone the repo to your local environment 21 | 22 | `git clone git@github.com:the-couch/slater-theme.git theme` 23 | 24 | `cd theme` 25 | 26 | `npm install` or `yarn install` 27 | 28 | ----- 29 | 30 | ### Initial build 31 | 32 | We need to upload our initial build of the theme, we can do this by running `npm run build` to compile the theme one time, from there we can zip the `src` archive and upload it to our Shopify environment. 33 | 34 | ----- 35 | 36 | ### Development 37 | 38 | Duplicate the `config-sample.yml` and grab the creds from your shopify theme. 39 | 40 | In two separate windows run the watch command (this is to allow us to use our own build process on top of Shopify Slate). 41 | 42 | `npm run watch:assets` - watches js and css 43 | 44 | `npm run start` - manages theme refresh/upload 45 | 46 | ## Custom Routing 47 | 48 | We're using operator to extend pjax style routing throughout the theme. This can be used to fetch/cache/handle transitions between routes. This can also help us init/code split specific javascript to specific routes (for example a flickity slideshow). 49 | 50 | The code for handling this is found in `lib/router` 51 | 52 | We use the `afterRender` and `beforeRender` methods for handling page transitions 53 | 54 | To block a custom route add the `no-ajax` class to the url element. 55 | 56 | ## Why 57 | I think slate is awesome, but I don't like that it's a closed box, I love the flexibility of things like react-create-app because it let's you actually eject the project and extend it. So this is a bit of a hack but I think y'all enjoy it. If you don't like it though, you don't have to use it ;) 58 | 59 | ## Why why 60 | Slate is a really big step forward to Shopify theme development. However, we generally prefer Webpack to Gulp, vanilla js to jQuery, CSS to SCSS, functional to object-oriented, etc. We've removed the boilerplate in favor of *nothing*, and will be releasing packages to replace the functionality of Slate in the near future. 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slater", 3 | "version": "0.3.1", 4 | "scripts": { 5 | "start": "slater watch", 6 | "deploy:production": "slater deploy --env production", 7 | "dev": "concurrently 'slater watch' 'npm run watch:js' 'npm run watch:css'", 8 | "build": "npm run build:js && npm run build:css", 9 | "watch:assets": "concurrently 'npm run watch:js' 'npm run watch:css'", 10 | "build:theme": "theme upload", 11 | "watch:theme": "theme watch", 12 | "build:js": "NODE_ENV=production webpack --progress", 13 | "watch:js": "NODE_ENV=development webpack -w", 14 | "build:css": "NODE_ENV=production postcss styles/main.css -o src/assets/main.css", 15 | "watch:css": "NODE_ENV=development postcss styles/main.css -w -o src/assets/main.css" 16 | }, 17 | "author": "iamkevingreen", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "@slater/cli": "^0.6.2", 21 | "babel-core": "^6.25.0", 22 | "babel-eslint": "^7.2.3", 23 | "babel-loader": "^7.0.0", 24 | "babel-plugin-lodash": "^3.2.11", 25 | "babel-plugin-module-resolver": "^2.7.1", 26 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 27 | "babel-plugin-transform-class-properties": "^6.24.1", 28 | "babel-plugin-transform-object-assign": "^6.22.0", 29 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 30 | "babel-preset-es2015": "^6.24.1", 31 | "concurrently": "^3.5.0", 32 | "cssnano": "^3.10.0", 33 | "lodash-webpack-plugin": "^0.11.4", 34 | "postcss": "^6.0.2", 35 | "postcss-calc": "^6.0.0", 36 | "postcss-cli": "^4.1.0", 37 | "postcss-cssnext": "^2.11.0", 38 | "postcss-discard-comments": "^2.0.4", 39 | "postcss-import": "^10.0.0", 40 | "postcss-nested": "^2.0.2", 41 | "standard": "^10.0.2", 42 | "standard-loader": "^6.0.1", 43 | "uglifyjs-webpack-plugin": "^1.2.2", 44 | "webpack": "^3.0.0", 45 | "webpack-dashboard": "^1.0.0-5" 46 | }, 47 | "dependencies": { 48 | "form-serialize": "^0.7.2", 49 | "mitt": "^1.1.2", 50 | "operator": "^1.2.0", 51 | "picoapp": "^2.0.2", 52 | "svbstrate": "^4.1.0", 53 | "unfetch": "^3.0.0", 54 | "w2t": "0.0.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const p = process.env.NODE_ENV === 'production' 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('postcss-import'), 6 | require('postcss-nested'), 7 | require('postcss-cssnext'), 8 | require('postcss-calc'), 9 | require('postcss-discard-comments'), 10 | require('postcss-reporter'), 11 | p ? require('cssnano') : '' 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /slater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-couch/slater-theme/d2cce8b040ab7bf5dfd79a248e950b85f9074fc5/slater.png -------------------------------------------------------------------------------- /src/config-sample.yml: -------------------------------------------------------------------------------- 1 | # This file contains the information needed for Shopify to authenticate 2 | # requests and edit/update your remote theme files. 3 | # 4 | # 1. Set up a private app (https://help.shopify.com/api/guides/api-credentials#generate-private-app-credentials) 5 | # with "Read and write" permissions for "Theme templates and theme assets". 6 | # 2. Replace the required variables for each environment below. 7 | # 8 | # password, theme_id, and store variables are required. 9 | # 10 | # For more information on this config file: 11 | # Configuration variables | http://shopify.github.io/themekit/configuration/ 12 | # Ignore patterns | http://shopify.github.io/themekit/ignores/ 13 | 14 | --- 15 | 16 | development: 17 | password: 18 | theme_id: 19 | store: your-shop.myshopify.com 20 | ignore_files: 21 | #- settings_data.json # Uncomment this line to avoid resetting theme settings 22 | 23 | production: 24 | password: 25 | theme_id: "live" 26 | store: your-shop.myshopify.com 27 | ignore_files: 28 | #- settings_data.json # Uncomment this line to avoid resetting theme settings 29 | -------------------------------------------------------------------------------- /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/icons/icon-amazon_payments.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-american_express.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-apple_pay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-bitcoin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-cart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-cirrus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-dankort.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-diners_club.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-discover.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-dogecoin.svg: -------------------------------------------------------------------------------- 1 | icon-dogecoin -------------------------------------------------------------------------------- /src/icons/icon-dwolla.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/icon-facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-forbrugsforeningen.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/icon-hamburger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-interac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-jcb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-litecoin.svg: -------------------------------------------------------------------------------- 1 | icon-litecoin -------------------------------------------------------------------------------- /src/icons/icon-lock.svg: -------------------------------------------------------------------------------- 1 | Lock icon -------------------------------------------------------------------------------- /src/icons/icon-maestro.svg: -------------------------------------------------------------------------------- 1 | Shape -------------------------------------------------------------------------------- /src/icons/icon-master.svg: -------------------------------------------------------------------------------- 1 | Shape -------------------------------------------------------------------------------- /src/icons/icon-minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-paypal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-pinterest.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-rss.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-shopify-logo.svg: -------------------------------------------------------------------------------- 1 | Shopify logo -------------------------------------------------------------------------------- /src/icons/icon-snapchat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-tumblr.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-vimeo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-visa.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/gift_card.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% if settings.favicon != blank %} 12 | 13 | {% endif %} 14 | 15 | {%- assign formatted_initial_value = gift_card.initial_value | money_without_trailing_zeros: gift_card.currency -%} 16 | {%- assign formatted_initial_value_stripped = formatted_initial_value | strip_html -%} 17 | {{ 'gift_cards.issued.title' | t: value: formatted_initial_value_stripped, shop: shop.name }} 18 | 19 | 20 | 21 | {{ 'main.css' | asset_url | stylesheet_tag }} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 62 | 63 | {{ content_for_header }} 64 | 65 | 66 | 67 |
68 | {{ content_for_layout }} 69 |
70 | 71 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/layout/password.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% if settings.favicon != blank %} 12 | 13 | {% endif %} 14 | 15 | {{ shop.name }} 16 | 17 | {% if page_description %} 18 | 19 | {% endif %} 20 | 21 | {% include 'social-meta-tags' %} 22 | 23 | {{ 'theme.scss.css' | asset_url | stylesheet_tag }} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {{ content_for_header }} 32 | 33 | 34 | 35 |
36 |

37 | 40 |

41 |
42 | 43 |
44 | {{ content_for_layout }} 45 |
46 | 47 | 56 | 57 |
58 |

{{ 'general.password_page.login_form_heading' | t }}

59 | {% form 'storefront_password' %} 60 | {{ form.errors | default_errors }} 61 | 62 | 66 | 69 | {% endform %} 70 |

{{ 'general.password_page.admin_link_html' | t }}

71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /src/layout/theme.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% if settings.favicon != blank %} 10 | 11 | {% endif %} 12 | 13 | {% capture seo_title %} 14 | {{ page_title }} 15 | {% if current_tags %} 16 | {%- assign meta_tags = current_tags | join: ', ' %} – {{ 'general.meta.tags' | t: tags: meta_tags -}} 17 | {% endif %} 18 | {% if current_page != 1 %} 19 | – {{ 'general.meta.page' | t: page: current_page }} 20 | {% endif %} 21 | {% unless page_title contains shop.name %} 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 | 43 | 44 | {{ 'index.css' | asset_url | stylesheet_tag }} 45 | 46 | {{ content_for_header }} 47 | 48 | 49 | 50 |
51 |

Download this starter theme

52 |
53 | 54 | 55 | {% section 'header' %} 56 | 57 |
58 | {{ content_for_layout }} 59 | {% section 'footer' %} 60 |
61 | 62 |
63 | 64 | {% include 'cart-drawer' %} 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/scripts/app.js: -------------------------------------------------------------------------------- 1 | import picoapp from 'picoapp' 2 | 3 | import header from './components/header.js' 4 | import product from './components/product.js' 5 | import cartDrawer from './components/cartDrawer.js' 6 | import cartDrawerItem from './components/cartDrawerItem.js' 7 | import accountLogin from './components/accountLogin.js' 8 | 9 | const state = { 10 | cartOpen: false 11 | } 12 | 13 | const actions = { 14 | toggleCart: open => state => ({ cartOpen: !state.cartOpen }) 15 | } 16 | 17 | const components = { 18 | header, 19 | product, 20 | cartDrawer, 21 | cartDrawerItem, 22 | accountLogin 23 | } 24 | 25 | export default picoapp(components, state, actions) 26 | -------------------------------------------------------------------------------- /src/scripts/components/accountLogin.js: -------------------------------------------------------------------------------- 1 | import { component } from 'picoapp' 2 | 3 | export default component(({ node: outer, state }) => { 4 | const login = outer.querySelector('.js-login-dialog') 5 | const recover = outer.querySelector('.js-recover-dialog') 6 | const recoverLink = outer.querySelector('.js-recover-trigger') 7 | const cancelRecoverLink = outer.querySelector('.js-recover-cancel') 8 | 9 | /* eslint-disable */ 10 | const recoverIsTarget = window.location.hash.match(/\#recover/) ? true : false 11 | /* eslint-enable */ 12 | 13 | const successMessage = outer.querySelector('.js-recover-success') !== null 14 | 15 | if (recoverIsTarget || successMessage) { 16 | login.style.display = 'none' 17 | recover.style.display = 'block' 18 | } else { 19 | login.style.display = 'block' 20 | } 21 | 22 | recoverLink.addEventListener('click', (e) => { 23 | e.preventDefault() 24 | login.style.display = 'none' 25 | recover.style.display = 'block' 26 | }) 27 | 28 | cancelRecoverLink.addEventListener('click', (e) => { 29 | e.preventDefault() 30 | recover.style.display = 'none' 31 | login.style.display = 'block' 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/scripts/components/cartDrawer.js: -------------------------------------------------------------------------------- 1 | import { fetchCart } from '../slater/cart' 2 | import { getSizedImageUrl, imageSize } from '../slater/images' 3 | import { formatMoney } from '../slater/currency' 4 | import { component } from 'picoapp' 5 | import app from '../app.js' 6 | 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 18 | }) { 19 | const img = image ? getSizedImageUrl( 20 | image.replace('_' + imageSize(image), ''), '200x' // TODO hacky af 21 | ) : 'https://source.unsplash.com/R9OS29xJb-8/2000x1333' 22 | 23 | return ` 24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 | ${title} 32 |
${formatMoney(price)}
33 |
34 |
-
35 |
${quantity}
36 |
+
37 |
38 | ${color ? `
${color.split(':')[0]}
` : ``} 39 |
40 | 41 | 42 |
43 |
44 |
45 | ` 46 | } 47 | 48 | function renderItems (items) { 49 | return items.length > 0 ? ( 50 | items.reduce((markup, item) => { 51 | markup += createItem(item) 52 | return markup 53 | }, '') 54 | ) : ( 55 | `

Your cart is empty

` 56 | ) 57 | } 58 | 59 | export default component(({ node, state, actions }) => { 60 | const overlay = node.querySelector('.js-overlay') 61 | const closeButton = node.querySelector('.js-close') 62 | const subtotal = node.querySelector('.js-subtotal') 63 | const itemsRoot = node.querySelector('.js-items') 64 | const loading = itemsRoot.innerHTML 65 | 66 | const render = (cart) => { 67 | itemsRoot.innerHTML = renderItems(cart.items) 68 | subtotal.innerHTML = formatMoney(cart.total_price) 69 | } 70 | 71 | const open = (cart) => { 72 | node.classList.add('is-active') 73 | itemsRoot.innerHTML = loading 74 | setTimeout(() => { 75 | node.classList.add('is-visible') 76 | setTimeout(render(cart), 10) 77 | app.mount() 78 | }, 50) 79 | } 80 | 81 | const close = () => { 82 | node.classList.remove('is-visible') 83 | setTimeout(() => { 84 | node.classList.remove('is-active') 85 | app.hydrate({cartOpen: false}) 86 | }, 400) 87 | } 88 | 89 | render(state.cart) 90 | 91 | overlay.addEventListener('click', close) 92 | closeButton.addEventListener('click', close) 93 | 94 | return { 95 | onStateChange ({cart, cartOpen}) { 96 | console.log('state is changing?', cartOpen) 97 | cartOpen ? open(cart) : null 98 | } 99 | } 100 | }) 101 | 102 | // 103 | // export default outer => { 104 | // let isOpen = false 105 | // 106 | // const overlay = outer.querySelector('.js-overlay') 107 | // const closeButton = outer.querySelector('.js-close') 108 | // const subtotal = outer.querySelector('.js-subtotal') 109 | // const itemsRoot = outer.querySelector('.js-items') 110 | // const loading = itemsRoot.innerHTML 111 | // 112 | // function render () { 113 | // fetchCart().then(cart => { 114 | // itemsRoot.innerHTML = renderItems(cart.items) 115 | // subtotal.innerHTML = formatMoney(cart.total_price) 116 | // setTimeout(() => { 117 | // scripts.mount() 118 | // }, 0) 119 | // }) 120 | // } 121 | // 122 | // function open () { 123 | // outer.classList.add('is-active') 124 | // 125 | // itemsRoot.innerHTML = loading 126 | // 127 | // setTimeout(() => { 128 | // outer.classList.add('is-visible') 129 | // isOpen = true 130 | // setTimeout(render, 10) 131 | // }, 50) 132 | // } 133 | // 134 | // function close () { 135 | // outer.classList.remove('is-visible') 136 | // 137 | // setTimeout(() => { 138 | // outer.classList.remove('is-active') 139 | // isOpen = false 140 | // }, 400) 141 | // } 142 | // 143 | // on('updated', ({ cart }) => { 144 | // isOpen ? render() : open() 145 | // }) 146 | // on('addon', ({ cart }) => { 147 | // isOpen ? render() : open() 148 | // }) 149 | // overlay.addEventListener('click', close) 150 | // closeButton.addEventListener('click', close) 151 | // 152 | // return { 153 | // open, 154 | // close: close 155 | // } 156 | // } 157 | -------------------------------------------------------------------------------- /src/scripts/components/cartDrawerItem.js: -------------------------------------------------------------------------------- 1 | import { removeAddon, updateAddon } from '../slater/cart.js' 2 | import { component } from 'picoapp' 3 | 4 | export default component(({ node: item, state }) => { 5 | const button = item.getElementsByTagName('button')[0] 6 | const decrease = item.querySelector('.js-remove-single') 7 | const increase = item.querySelector('.js-add-single') 8 | const currentQty = item.querySelector('.js-single-quantity').innerHTML 9 | const id = item.getAttribute('data-id') 10 | 11 | button.addEventListener('click', e => { 12 | e.preventDefault() 13 | removeAddon(id) 14 | }) 15 | 16 | decrease.addEventListener('click', e => { 17 | e.preventDefault() 18 | updateAddon(id, parseInt(currentQty) - 1) 19 | }) 20 | 21 | increase.addEventListener('click', e => { 22 | e.preventDefault() 23 | updateAddon(id, parseInt(currentQty) + 1) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/scripts/components/header.js: -------------------------------------------------------------------------------- 1 | // import { on, fetchCart } from '../slater/cart' 2 | import { component } from 'picoapp' 3 | // import Cart from './cart-drawer.js' 4 | 5 | export default component(({ node: header, state, actions }) => { 6 | const cartCount = header.querySelector('.js-cart-count') 7 | const cartToggles = header.querySelectorAll('.js-cart-drawer-toggle') 8 | cartCount.innerHTML = state.cart.items.length >= 1 ? state.cart.item_count : null 9 | 10 | for (let toggle of cartToggles) { 11 | toggle.addEventListener('click', e => { 12 | console.log('yo click', state) 13 | e.preventDefault() 14 | actions.toggleCart(true) 15 | console.log('yo click', state) 16 | }) 17 | } 18 | 19 | return { 20 | onStateChange (state) { 21 | cartCount.innerHTML = state.cart.item_count 22 | } 23 | } 24 | // const cartCount = header.querySelector('.js-cart-count') 25 | // const cart = fetchCart() 26 | // cart.then(res => { 27 | // /* eslint-disable */ 28 | // res ? cartCount.innerHTML = res.item_count : null 29 | // /* eslint-enable */ 30 | // }) 31 | // on('updated', ({ cart }) => { 32 | // cartCount.innerHTML = cart.item_count 33 | // }) 34 | // on('addon', ({ cart }) => { 35 | // cartCount.innerHTML = cart.item_count 36 | // }) 37 | // /** 38 | // // * Cart opening 39 | // // */ 40 | // const cartToggles = header.querySelectorAll('.js-cart-drawer-toggle') 41 | // for (let toggle of cartToggles) { 42 | // toggle.addEventListener('click', e => { 43 | // console.log('clicked?') 44 | // e.preventDefault() 45 | // Cart.open() 46 | // // const cartDrawer = scripts.cache.get('cart-drawer') 47 | // // cartDrawer.open() 48 | // }) 49 | // } 50 | }) 51 | -------------------------------------------------------------------------------- /src/scripts/components/hero.js: -------------------------------------------------------------------------------- 1 | export default props => { 2 | console.log('hero fired', props) 3 | } 4 | -------------------------------------------------------------------------------- /src/scripts/components/input-text.js: -------------------------------------------------------------------------------- 1 | export default wrapper => { 2 | const input = wrapper.getElementsByTagName('input')[0] 3 | 4 | function handleAddRemove (e) { 5 | e.target.value ? add() : remove() 6 | } 7 | 8 | function add () { 9 | wrapper.classList.add('has-value') 10 | } 11 | 12 | function remove () { 13 | wrapper.classList.remove('has-value') 14 | } 15 | 16 | input.addEventListener('change', handleAddRemove) 17 | input.addEventListener('blur', handleAddRemove) 18 | input.addEventListener('focus', add) 19 | 20 | return { 21 | unmount () { 22 | input.removeEventListener('change', handleAddRemove) 23 | input.removeEventListener('blur', handleAddRemove) 24 | input.removeEventListener('focus', add) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/scripts/components/product.js: -------------------------------------------------------------------------------- 1 | import { addVariant } from '../slater/cart.js' 2 | import { component } from 'picoapp' 3 | import wait from 'w2t' 4 | 5 | export default component(({ node: el, state }) => { 6 | const { selectedOrFirstAvailableVariant, product } = JSON.parse(el.querySelector('.js-product-json').innerHTML) 7 | let currentVariant = product.variants.filter(v => v.id === selectedOrFirstAvailableVariant)[0] 8 | 9 | const form = el.getElementsByTagName('form')[0] 10 | const submit = form.querySelector('.js-submit-cart') 11 | const quantity = form.querySelector('.js-quantity').value 12 | console.log('subby', form) 13 | 14 | form.addEventListener('submit', e => { 15 | e.preventDefault() 16 | submit.children[0].innerHTML = 'Adding..' 17 | wait(1000, [ 18 | addVariant(currentVariant, quantity).then(({ item, cart }) => { 19 | submit.children[0].innerHTML = 'Add to Cart' 20 | }).catch(e => { 21 | alert(e) 22 | }) 23 | ]) 24 | }) 25 | }) 26 | 27 | // 28 | // export default el => { 29 | // const { selectedOrFirstAvailableVariant, product } = JSON.parse(el.querySelector('.js-product-json').innerHTML) 30 | // 31 | // let currentVariant = product.variants.filter(v => v.id === selectedOrFirstAvailableVariant)[0] 32 | // 33 | // /** 34 | // * Adding products to cart 35 | // */ 36 | // const form = el.getElementsByTagName('form')[0] 37 | // const submit = form.querySelector('.js-submit-cart') 38 | // const quantity = form.querySelector('.js-quantity').value 39 | // 40 | // form.addEventListener('submit', e => { 41 | // e.preventDefault() 42 | // console.log('add to cart') 43 | // 44 | // submit.disabled = true 45 | // addVariant(currentVariant, quantity).then(({ item, cart }) => { 46 | // submit.disabled = false 47 | // }).catch(e => { 48 | // submit.disabled = false 49 | // /* eslint-disable */ 50 | // alert(e) 51 | // /* eslint-enable */ 52 | // }) 53 | // }) 54 | // } 55 | -------------------------------------------------------------------------------- /src/scripts/index.js: -------------------------------------------------------------------------------- 1 | import operator from 'operator' 2 | import app from './app.js' 3 | import wait from 'w2t' 4 | import { fetchCart } from './slater/cart' 5 | 6 | import '../styles/main.css' 7 | 8 | let root = document.getElementById('pageTransition') 9 | 10 | const animateRoute = () => { 11 | return new Promise(res => { 12 | root.classList.add('cover') 13 | setTimeout(() => { 14 | root.classList.remove('cover') 15 | res() 16 | }, 600) 17 | }) 18 | } 19 | 20 | const router = operator('#root', [ 21 | state => { 22 | return wait(20, [ 23 | animateRoute() 24 | ]) 25 | } 26 | ]) 27 | 28 | router.on('before', state => { 29 | // const pageTransition = document.getElementById('pageTransition') 30 | // pageTransition.classList.add('cover') 31 | return Promise.all([ 32 | app.unmount(), 33 | new Promise(r => { 34 | document.body.classList.add('moving') 35 | setTimeout(r, 800) 36 | }) 37 | ]) 38 | }) 39 | 40 | router.on('after', ({ title, pathname }) => { 41 | document.title = title 42 | window.history.pushState({}, '', pathname) 43 | }) 44 | 45 | document.addEventListener('DOMContentLoaded', e => { 46 | Promise.all([ 47 | fetchCart() 48 | // checkout.hydrate() 49 | ]).then(([ cart ]) => { 50 | app.hydrate({ cart: cart }) 51 | app.mount() 52 | }) 53 | }) 54 | 55 | console.groupCollapsed('Slater credits 🍝 taco') 56 | console.log('Development by The Couch https://thecouch.nyc') 57 | console.groupEnd() 58 | -------------------------------------------------------------------------------- /src/scripts/lib/router.js: -------------------------------------------------------------------------------- 1 | import operator from 'operator.js' 2 | // import * as scripts from 'micromanager' 3 | 4 | const router = operator({ 5 | transitionSpeed: 400, 6 | routes: {} 7 | }) 8 | 9 | // router.addRoute('*', () => { 10 | // const cache = scripts.cache.dump() 11 | // let modules = [] 12 | // 13 | // for (let key in cache) { 14 | // modules.push(cache[key]) 15 | // } 16 | // 17 | // return Promise.all(modules.map(mod => { 18 | // return mod.leave ? mod.leave() : mod 19 | // })) 20 | // }) 21 | 22 | router.on('afterRender', requestedRoute => { 23 | // const cartDrawer = scripts.cache.get('cart-drawer') 24 | // cartDrawer && cartDrawer.close() 25 | 26 | const pageTransition = document.getElementById('pageTransition') 27 | setTimeout(() => { 28 | pageTransition.classList.remove('cover') 29 | }, 600) 30 | }) 31 | 32 | router.on('beforeRender', requestedRoute => { 33 | const root = document.getElementById('root') 34 | const pageTransition = document.getElementById('pageTransition') 35 | 36 | root.classList.add('moving') 37 | pageTransition.classList.add('cover') 38 | setTimeout(() => { 39 | console.log('waiting game') 40 | }, 700) 41 | }) 42 | 43 | export default router 44 | -------------------------------------------------------------------------------- /src/scripts/pages/product.js: -------------------------------------------------------------------------------- 1 | import ProductSelector from 'slater/product-selector.js' 2 | 3 | export default el => { 4 | const selector = ProductSelector() 5 | 6 | selector.on('update', variant => { 7 | console.log(variant) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/scripts/sections/product.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * Product Template Script 3 | // * ------------------------------------------------------------------------------ 4 | // * A file that contains scripts highly couple code to the Product template. 5 | // * 6 | // * @namespace product 7 | // */ 8 | // 9 | /* eslint-disable */ 10 | 11 | console.log('hi') 12 | 13 | export default props => { 14 | console.log('product woo', props) 15 | } 16 | 17 | // theme.Product = (function() { 18 | // 19 | // var selectors = { 20 | // addToCart: '[data-add-to-cart]', 21 | // addToCartText: '[data-add-to-cart-text]', 22 | // comparePrice: '[data-compare-price]', 23 | // comparePriceText: '[data-compare-text]', 24 | // originalSelectorId: '[data-product-select]', 25 | // priceWrapper: '[data-price-wrapper]', 26 | // productFeaturedImage: '[data-product-featured-image]', 27 | // productJson: '[data-product-json]', 28 | // productPrice: '[data-product-price]', 29 | // productThumbs: '[data-product-single-thumbnail]', 30 | // singleOptionSelector: '[data-single-option-selector]' 31 | // }; 32 | // 33 | // /** 34 | // * Product section constructor. Runs on page load as well as Theme Editor 35 | // * `section:load` events. 36 | // * @param {string} container - selector for the section container DOM element 37 | // */ 38 | // function Product(container) { 39 | // this.$container = $(container); 40 | // var sectionId = this.$container.attr('data-section-id'); 41 | // 42 | // this.settings = {}; 43 | // this.namespace = '.product'; 44 | // 45 | // // Stop parsing if we don't have the product json script tag when loading 46 | // // section in the Theme Editor 47 | // if (!$(selectors.productJson, this.$container).html()) { 48 | // return; 49 | // } 50 | // 51 | // this.productSingleObject = JSON.parse($(selectors.productJson, this.$container).html()); 52 | // this.settings.imageSize = slate.Image.imageSize($(selectors.productFeaturedImage, this.$container).attr('src')); 53 | // 54 | // slate.Image.preload(this.productSingleObject.images, this.settings.imageSize); 55 | // 56 | // this.initVariants(); 57 | // } 58 | // 59 | // Product.prototype = $.extend({}, Product.prototype, { 60 | // 61 | // /** 62 | // * Handles change events from the variant inputs 63 | // */ 64 | // initVariants: function() { 65 | // var options = { 66 | // $container: this.$container, 67 | // enableHistoryState: this.$container.data('enable-history-state') || false, 68 | // singleOptionSelector: selectors.singleOptionSelector, 69 | // originalSelectorId: selectors.originalSelectorId, 70 | // product: this.productSingleObject 71 | // }; 72 | // 73 | // this.variants = new slate.Variants(options); 74 | // 75 | // this.$container.on('variantChange' + this.namespace, this.updateAddToCartState.bind(this)); 76 | // this.$container.on('variantImageChange' + this.namespace, this.updateProductImage.bind(this)); 77 | // this.$container.on('variantPriceChange' + this.namespace, this.updateProductPrices.bind(this)); 78 | // }, 79 | // 80 | // /** 81 | // * Updates the DOM state of the add to cart button 82 | // * 83 | // * @param {boolean} enabled - Decides whether cart is enabled or disabled 84 | // * @param {string} text - Updates the text notification content of the cart 85 | // */ 86 | // updateAddToCartState: function(evt) { 87 | // var variant = evt.variant; 88 | // 89 | // if (variant) { 90 | // $(selectors.priceWrapper, this.$container).removeClass('hide'); 91 | // } else { 92 | // $(selectors.addToCart, this.$container).prop('disabled', true); 93 | // $(selectors.addToCartText, this.$container).html(theme.strings.unavailable); 94 | // $(selectors.priceWrapper, this.$container).addClass('hide'); 95 | // return; 96 | // } 97 | // 98 | // if (variant.available) { 99 | // $(selectors.addToCart, this.$container).prop('disabled', false); 100 | // $(selectors.addToCartText, this.$container).html(theme.strings.addToCart); 101 | // } else { 102 | // $(selectors.addToCart, this.$container).prop('disabled', true); 103 | // $(selectors.addToCartText, this.$container).html(theme.strings.soldOut); 104 | // } 105 | // }, 106 | // 107 | // /** 108 | // * Updates the DOM with specified prices 109 | // * 110 | // * @param {string} productPrice - The current price of the product 111 | // * @param {string} comparePrice - The original price of the product 112 | // */ 113 | // updateProductPrices: function(evt) { 114 | // var variant = evt.variant; 115 | // var $comparePrice = $(selectors.comparePrice, this.$container); 116 | // var $compareEls = $comparePrice.add(selectors.comparePriceText, this.$container); 117 | // 118 | // $(selectors.productPrice, this.$container) 119 | // .html(slate.Currency.formatMoney(variant.price, theme.moneyFormat)); 120 | // 121 | // if (variant.compare_at_price > variant.price) { 122 | // $comparePrice.html(slate.Currency.formatMoney(variant.compare_at_price, theme.moneyFormat)); 123 | // $compareEls.removeClass('hide'); 124 | // } else { 125 | // $comparePrice.html(''); 126 | // $compareEls.addClass('hide'); 127 | // } 128 | // }, 129 | // 130 | // /** 131 | // * Updates the DOM with the specified image URL 132 | // * 133 | // * @param {string} src - Image src URL 134 | // */ 135 | // updateProductImage: function(evt) { 136 | // var variant = evt.variant; 137 | // var sizedImgUrl = slate.Image.getSizedImageUrl(variant.featured_image.src, this.settings.imageSize); 138 | // 139 | // $(selectors.productFeaturedImage, this.$container).attr('src', sizedImgUrl); 140 | // }, 141 | // 142 | // /** 143 | // * Event callback for Theme Editor `section:unload` event 144 | // */ 145 | // onUnload: function() { 146 | // this.$container.off(this.namespace); 147 | // } 148 | // }); 149 | // 150 | // return Product; 151 | // })(); 152 | -------------------------------------------------------------------------------- /src/scripts/slate/sections.js: -------------------------------------------------------------------------------- 1 | 2 | /* eslint-disable */ 3 | 4 | slate.Sections = function Sections() { 5 | this.constructors = {}; 6 | this.instances = []; 7 | 8 | $(document) 9 | .on('shopify:section:load', this._onSectionLoad.bind(this)) 10 | .on('shopify:section:unload', this._onSectionUnload.bind(this)) 11 | .on('shopify:section:select', this._onSelect.bind(this)) 12 | .on('shopify:section:deselect', this._onDeselect.bind(this)) 13 | .on('shopify:section:reorder', this._onReorder.bind(this)) 14 | .on('shopify:block:select', this._onBlockSelect.bind(this)) 15 | .on('shopify:block:deselect', this._onBlockDeselect.bind(this)); 16 | }; 17 | 18 | slate.Sections.prototype = $.extend({}, slate.Sections.prototype, { 19 | _createInstance: function(container, constructor) { 20 | var $container = $(container); 21 | var id = $container.attr('data-section-id'); 22 | var type = $container.attr('data-section-type'); 23 | 24 | constructor = constructor || this.constructors[type]; 25 | 26 | if (typeof constructor === 'undefined') { 27 | return; 28 | } 29 | 30 | var instance = $.extend(new constructor(container), { 31 | id: id, 32 | type: type, 33 | container: container 34 | }); 35 | 36 | this.instances.push(instance); 37 | }, 38 | 39 | _onSectionLoad: function(evt) { 40 | var container = $('[data-section-id]', evt.target)[0]; 41 | if (container) { 42 | this._createInstance(container); 43 | } 44 | }, 45 | 46 | _onSectionUnload: function(evt) { 47 | var instance = slate.utils.findInstance(this.instances, 'id', evt.detail.sectionId); 48 | 49 | if (!instance) { 50 | return; 51 | } 52 | 53 | if (typeof instance.onUnload === 'function') { 54 | instance.onUnload(evt); 55 | } 56 | 57 | this.instances = slate.utils.removeInstance(this.instances, 'id', evt.detail.sectionId); 58 | }, 59 | 60 | _onSelect: function(evt) { 61 | var instance = slate.utils.findInstance(this.instances, 'id', evt.detail.sectionId); 62 | 63 | if (instance && typeof instance.onSelect === 'function') { 64 | instance.onSelect(evt); 65 | } 66 | }, 67 | 68 | _onDeselect: function(evt) { 69 | var instance = slate.utils.findInstance(this.instances, 'id', evt.detail.sectionId); 70 | 71 | if (instance && typeof instance.onDeselect === 'function') { 72 | instance.onDeselect(evt); 73 | } 74 | }, 75 | 76 | _onReorder: function(evt) { 77 | var instance = slate.utils.findInstance(this.instances, 'id', evt.detail.sectionId); 78 | 79 | if (instance && typeof instance.onReorder === 'function') { 80 | instance.onReorder(evt); 81 | } 82 | }, 83 | 84 | _onBlockSelect: function(evt) { 85 | var instance = slate.utils.findInstance(this.instances, 'id', evt.detail.sectionId); 86 | 87 | if (instance && typeof instance.onBlockSelect === 'function') { 88 | instance.onBlockSelect(evt); 89 | } 90 | }, 91 | 92 | _onBlockDeselect: function(evt) { 93 | var instance = slate.utils.findInstance(this.instances, 'id', evt.detail.sectionId); 94 | 95 | if (instance && typeof instance.onBlockDeselect === 'function') { 96 | instance.onBlockDeselect(evt); 97 | } 98 | }, 99 | 100 | register: function(type, constructor) { 101 | this.constructors[type] = constructor; 102 | 103 | $('[data-section-type=' + type + ']').each(function(index, container) { 104 | this._createInstance(container, constructor); 105 | }.bind(this)); 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /src/scripts/slate/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility helpers 3 | * ----------------------------------------------------------------------------- 4 | * A collection of useful functions for dealing with arrays and objects 5 | * 6 | * @namespace utils 7 | */ 8 | 9 | /* eslint-disable */ 10 | 11 | slate.utils = { 12 | 13 | /** 14 | * Return an object from an array of objects that matches the provided key and value 15 | * 16 | * @param {array} array - Array of objects 17 | * @param {string} key - Key to match the value against 18 | * @param {string} value - Value to get match of 19 | */ 20 | findInstance: function(array, key, value) { 21 | for (var i = 0; i < array.length; i++) { 22 | if (array[i][key] === value) { 23 | return array[i]; 24 | } 25 | } 26 | }, 27 | 28 | /** 29 | * Remove an object from an array of objects by matching the provided key and value 30 | * 31 | * @param {array} array - Array of objects 32 | * @param {string} key - Key to match the value against 33 | * @param {string} value - Value to get match of 34 | */ 35 | removeInstance: function(array, key, value) { 36 | var i = array.length; 37 | while(i--) { 38 | if (array[i][key] === value) { 39 | array.splice(i, 1); 40 | break; 41 | } 42 | } 43 | 44 | return array; 45 | }, 46 | 47 | /** 48 | * _.compact from lodash 49 | * Remove empty/false items from array 50 | * Source: https://github.com/lodash/lodash/blob/master/compact.js 51 | * 52 | * @param {array} array 53 | */ 54 | compact: function(array) { 55 | var index = -1; 56 | var length = array == null ? 0 : array.length; 57 | var resIndex = 0; 58 | var result = []; 59 | 60 | while (++index < length) { 61 | var value = array[index]; 62 | if (value) { 63 | result[resIndex++] = value; 64 | } 65 | } 66 | return result; 67 | }, 68 | 69 | /** 70 | * _.defaultTo from lodash 71 | * Checks `value` to determine whether a default value should be returned in 72 | * its place. The `defaultValue` is returned if `value` is `NaN`, `null`, 73 | * or `undefined`. 74 | * Source: https://github.com/lodash/lodash/blob/master/defaultTo.js 75 | * 76 | * @param {*} value - Value to check 77 | * @param {*} defaultValue - Default value 78 | * @returns {*} - Returns the resolved value 79 | */ 80 | defaultTo: function(value, defaultValue) { 81 | return (value == null || value !== value) ? defaultValue : value 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /src/scripts/slate/variants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Variant Selection scripts 3 | * ------------------------------------------------------------------------------ 4 | * 5 | * Handles change events from the variant inputs in any `cart/add` forms that may 6 | * exist. Also updates the master select and triggers updates when the variants 7 | * price or image changes. 8 | * 9 | * @namespace variants 10 | */ 11 | 12 | /* eslint-disable */ 13 | 14 | slate.Variants = (function() { 15 | 16 | /** 17 | * Variant constructor 18 | * 19 | * @param {object} options - Settings from `product.js` 20 | */ 21 | function Variants(options) { 22 | this.$container = options.$container; 23 | this.product = options.product; 24 | this.singleOptionSelector = options.singleOptionSelector; 25 | this.originalSelectorId = options.originalSelectorId; 26 | this.enableHistoryState = options.enableHistoryState; 27 | this.currentVariant = this._getVariantFromOptions(); 28 | 29 | $(this.singleOptionSelector, this.$container).on('change', this._onSelectChange.bind(this)); 30 | } 31 | 32 | Variants.prototype = $.extend({}, Variants.prototype, { 33 | 34 | /** 35 | * Get the currently selected options from add-to-cart form. Works with all 36 | * form input elements. 37 | * 38 | * @return {array} options - Values of currently selected variants 39 | */ 40 | _getCurrentOptions: function() { 41 | var currentOptions = $.map($(this.singleOptionSelector, this.$container), function(element) { 42 | var $element = $(element); 43 | var type = $element.attr('type'); 44 | var currentOption = {}; 45 | 46 | if (type === 'radio' || type === 'checkbox') { 47 | if ($element[0].checked) { 48 | currentOption.value = $element.val(); 49 | currentOption.index = $element.data('index'); 50 | 51 | return currentOption; 52 | } else { 53 | return false; 54 | } 55 | } else { 56 | currentOption.value = $element.val(); 57 | currentOption.index = $element.data('index'); 58 | 59 | return currentOption; 60 | } 61 | }); 62 | 63 | // remove any unchecked input values if using radio buttons or checkboxes 64 | currentOptions = slate.utils.compact(currentOptions); 65 | 66 | return currentOptions; 67 | }, 68 | 69 | /** 70 | * Find variant based on selected values. 71 | * 72 | * @param {array} selectedValues - Values of variant inputs 73 | * @return {object || undefined} found - Variant object from product.variants 74 | */ 75 | _getVariantFromOptions: function() { 76 | var selectedValues = this._getCurrentOptions(); 77 | var variants = this.product.variants; 78 | var found = false; 79 | 80 | variants.forEach(function(variant) { 81 | var satisfied = true; 82 | 83 | selectedValues.forEach(function(option) { 84 | if (satisfied) { 85 | satisfied = (option.value === variant[option.index]); 86 | } 87 | }); 88 | 89 | if (satisfied) { 90 | found = variant; 91 | } 92 | }); 93 | 94 | return found || null; 95 | }, 96 | 97 | /** 98 | * Event handler for when a variant input changes. 99 | */ 100 | _onSelectChange: function() { 101 | var variant = this._getVariantFromOptions(); 102 | 103 | this.$container.trigger({ 104 | type: 'variantChange', 105 | variant: variant 106 | }); 107 | 108 | if (!variant) { 109 | return; 110 | } 111 | 112 | this._updateMasterSelect(variant); 113 | this._updateImages(variant); 114 | this._updatePrice(variant); 115 | this.currentVariant = variant; 116 | 117 | if (this.enableHistoryState) { 118 | this._updateHistoryState(variant); 119 | } 120 | }, 121 | 122 | /** 123 | * Trigger event when variant image changes 124 | * 125 | * @param {object} variant - Currently selected variant 126 | * @return {event} variantImageChange 127 | */ 128 | _updateImages: function(variant) { 129 | var variantImage = variant.featured_image || {}; 130 | var currentVariantImage = this.currentVariant.featured_image || {}; 131 | 132 | if (!variant.featured_image || variantImage.src === currentVariantImage.src) { 133 | return; 134 | } 135 | 136 | this.$container.trigger({ 137 | type: 'variantImageChange', 138 | variant: variant 139 | }); 140 | }, 141 | 142 | /** 143 | * Trigger event when variant price changes. 144 | * 145 | * @param {object} variant - Currently selected variant 146 | * @return {event} variantPriceChange 147 | */ 148 | _updatePrice: function(variant) { 149 | if (variant.price === this.currentVariant.price && variant.compare_at_price === this.currentVariant.compare_at_price) { 150 | return; 151 | } 152 | 153 | this.$container.trigger({ 154 | type: 'variantPriceChange', 155 | variant: variant 156 | }); 157 | }, 158 | 159 | /** 160 | * Update history state for product deeplinking 161 | * 162 | * @param {variant} variant - Currently selected variant 163 | * @return {k} [description] 164 | */ 165 | _updateHistoryState: function(variant) { 166 | if (!history.replaceState || !variant) { 167 | return; 168 | } 169 | 170 | var newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?variant=' + variant.id; 171 | window.history.replaceState({path: newurl}, '', newurl); 172 | }, 173 | 174 | /** 175 | * Update hidden master select of variant change 176 | * 177 | * @param {variant} variant - Currently selected variant 178 | */ 179 | _updateMasterSelect: function(variant) { 180 | $(this.originalSelectorId, this.$container)[0].value = variant.id; 181 | } 182 | }); 183 | 184 | return Variants; 185 | })(); 186 | -------------------------------------------------------------------------------- /src/scripts/slater/cart.js: -------------------------------------------------------------------------------- 1 | import fetch from 'unfetch' 2 | import mitt from 'mitt' 3 | import app from '../app.js' 4 | 5 | const ev = mitt() 6 | 7 | export const on = ev.on 8 | 9 | export function addVariant (variant, quantity) { 10 | const numAvailable = variant.inventory_policy === 'deny' && variant.inventory_management === 'shopify' ? ( 11 | variant.inventory_quantity 12 | ) : null // null means they can add as many as they want 13 | 14 | return fetchCart().then(({ items }) => { 15 | const existing = items.filter(item => item.id === variant.id)[0] || {} 16 | const numRequested = (existing.quantity || 0) + quantity 17 | 18 | if (numAvailable !== null && numRequested > numAvailable) { 19 | const err = `There are only ${numAvailable} of that product available, requested ${numRequested}.` 20 | ev.emit('error', err) 21 | throw new Error(err) 22 | } else { 23 | return addItemById(variant.id, quantity) 24 | } 25 | }) 26 | } 27 | 28 | export function updateAddon (id, quantity) { 29 | return fetchCart().then(({ items }) => { 30 | for (let i = 0; i < items.length; i++) { 31 | if (items[i].variant_id === parseInt(id)) { 32 | return changeAddon(i + 1, quantity) // shopify cart is a 1-based index 33 | } 34 | } 35 | }) 36 | } 37 | 38 | export function removeAddon (id) { 39 | return updateAddon(id, 0) 40 | } 41 | 42 | function changeAddon (line, quantity) { 43 | ev.emit('updating') 44 | 45 | return fetch('/cart/change.js', { 46 | method: 'POST', 47 | credentials: 'include', 48 | headers: { 49 | 'Content-Type': 'application/json' 50 | }, 51 | body: JSON.stringify({ line, quantity }) 52 | }).then(res => res.json()).then(cart => { 53 | ev.emit('addon', { item: null, cart }) 54 | app.hydrate({ cart: cart })(() => console.log('updated') ) 55 | return cart 56 | }) 57 | } 58 | 59 | /** 60 | * Warning: this does not check available products first 61 | */ 62 | export function addItemById (id, quantity) { 63 | ev.emit('updating') 64 | 65 | console.log('yo adddy') 66 | 67 | return fetch('/cart/add.js', { 68 | method: 'POST', 69 | credentials: 'include', 70 | headers: { 71 | 'Content-Type': 'application/json' 72 | }, 73 | body: JSON.stringify({ id, quantity }) 74 | }).then(r => r.json()).then(item => { 75 | return fetchCart().then(cart => { 76 | app.hydrate({ cart: cart })(() => console.log('updated')) 77 | app.actions.toggleCart() 78 | // ev.emit('updated', { item, cart }) 79 | return { item, cart } 80 | }) 81 | }) 82 | } 83 | 84 | export function fetchCart () { 85 | return fetch('/cart.js', { 86 | method: 'GET', 87 | credentials: 'include' 88 | }).then(res => res.json()) 89 | } 90 | -------------------------------------------------------------------------------- /src/scripts/slater/currency.js: -------------------------------------------------------------------------------- 1 | import { defaultTo } from './utils.js' 2 | 3 | /** 4 | * Currency Helpers 5 | * ----------------------------------------------------------------------------- 6 | * A collection of useful functions that help with currency formatting 7 | * 8 | * Current contents 9 | * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value. 10 | * 11 | */ 12 | 13 | /** 14 | * Format money values based on your shop currency settings 15 | * @param {Number|string} cents - value in cents or dollar amount e.g. 300 cents 16 | * or 3.00 dollars 17 | * @param {String} format - shop money_format setting 18 | * @return {String} value - formatted value 19 | */ 20 | 21 | /* eslint-disable */ 22 | 23 | export function formatMoney (cents, format = '${{amount}}') { 24 | if (typeof cents === 'string') { 25 | cents = cents.replace('.', '') 26 | } 27 | 28 | let value = '' 29 | const placeholderRegex = /\{\{\s*(\w+)\s*\}\}/ 30 | 31 | function formatWithDelimiters (number, precision, thousands, decimal) { 32 | precision = defaultTo(precision, 2) 33 | thousands = defaultTo(thousands, ',') 34 | decimal = defaultTo(decimal, '.') 35 | 36 | if (isNaN(number) || number == null) { 37 | return 0 38 | } 39 | 40 | number = (number / 100.0).toFixed(precision) 41 | 42 | const parts = number.split('.') 43 | const dollarsAmount = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands) 44 | const centsAmount = parts[1] ? (decimal + parts[1]) : '' 45 | 46 | return dollarsAmount + centsAmount 47 | } 48 | 49 | switch (format.match(placeholderRegex)[1]) { 50 | case 'amount': 51 | value = formatWithDelimiters(cents, 2) 52 | break 53 | case 'amount_no_decimals': 54 | value = formatWithDelimiters(cents, 0) 55 | break 56 | case 'amount_with_space_separator': 57 | value = formatWithDelimiters(cents, 2, ' ', '.') 58 | break 59 | case 'amount_no_decimals_with_comma_separator': 60 | value = formatWithDelimiters(cents, 0, ',', '.') 61 | break 62 | case 'amount_no_decimals_with_space_separator': 63 | value = formatWithDelimiters(cents, 0, ' ') 64 | break 65 | } 66 | 67 | return format.replace(placeholderRegex, value) 68 | } 69 | -------------------------------------------------------------------------------- /src/scripts/slater/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/slater/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( 14 | document.querySelectorAll(config.options) 15 | ) 16 | const data = JSON.parse( 17 | document.querySelector(config.data).innerHTML 18 | ) 19 | 20 | options.forEach(opt => opt.addEventListener('change', e => { 21 | const val = options.reduce((res, opt, i) => { 22 | res += i < options.length - 1 ? opt.value + ' / ' : opt.value 23 | return res 24 | }, '') 25 | 26 | for (let i = 0; i < main.options.length; i++) { 27 | if (main.options[i].text === val) { 28 | main.selectedIndex = i 29 | break 30 | } 31 | } 32 | 33 | ev.emit('update', data.variants.filter(v => v.title === val)[0]) 34 | })) 35 | 36 | return { 37 | on: ev.on, 38 | destroy () { 39 | options.forEach(opt => { 40 | // opt.removeEventListener('change', updateSelect) 41 | }) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/scripts/slater/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility helpers 3 | * ----------------------------------------------------------------------------- 4 | * A collection of useful functions for dealing with arrays and objects 5 | * 6 | * @namespace utils 7 | */ 8 | 9 | /** 10 | * Return an object from an array of objects that matches the provided key and value 11 | * 12 | * @param {array} array - Array of objects 13 | * @param {string} key - Key to match the value against 14 | * @param {string} value - Value to get match of 15 | */ 16 | export function findInstance (array, key, value) { 17 | for (let i = 0; i < array.length; i++) { 18 | if (array[i][key] === value) { 19 | return array[i] 20 | } 21 | } 22 | } 23 | 24 | /** 25 | * Remove an object from an array of objects by matching the provided key and value 26 | * 27 | * @param {array} array - Array of objects 28 | * @param {string} key - Key to match the value against 29 | * @param {string} value - Value to get match of 30 | */ 31 | export function removeInstance (array, key, value) { 32 | let i = array.length 33 | 34 | while (i--) { 35 | if (array[i][key] === value) { 36 | array.splice(i, 1) 37 | break 38 | } 39 | } 40 | 41 | return array 42 | } 43 | 44 | /** 45 | * _.compact from lodash 46 | * Remove empty/false items from array 47 | * Source: https://github.com/lodash/lodash/blob/master/compact.js 48 | * 49 | * @param {array} array 50 | */ 51 | export function compact (array) { 52 | let index = -1 53 | let resIndex = 0 54 | const length = array == null ? 0 : array.length 55 | const result = [] 56 | 57 | while (++index < length) { 58 | const value = array[index] 59 | 60 | if (value) { 61 | result[resIndex++] = value 62 | } 63 | } 64 | 65 | return result 66 | } 67 | 68 | /** 69 | * _.defaultTo from lodash 70 | * Checks `value` to determine whether a default value should be returned in 71 | * its place. The `defaultValue` is returned if `value` is `NaN`, `null`, 72 | * or `undefined`. 73 | * Source: https://github.com/lodash/lodash/blob/master/defaultTo.js 74 | * 75 | * @param {*} value - Value to check 76 | * @param {*} defaultValue - Default value 77 | * @returns {*} - Returns the resolved value 78 | */ 79 | export function defaultTo (value, defaultValue) { 80 | return (value == null) ? defaultValue : value 81 | } 82 | -------------------------------------------------------------------------------- /src/scripts/templates/customers-addresses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Customer Addresses Script 3 | * ------------------------------------------------------------------------------ 4 | * A file that contains scripts highly couple code to the Customer Addresses 5 | * template. 6 | * 7 | * @namespace customerAddresses 8 | */ 9 | 10 | /* eslint-disable */ 11 | 12 | theme.customerAddresses = (function() { 13 | var $newAddressForm = $('#AddressNewForm'); 14 | 15 | if (!$newAddressForm.length) { 16 | return; 17 | } 18 | 19 | // Initialize observers on address selectors, defined in shopify_common.js 20 | if (Shopify) { 21 | new Shopify.CountryProvinceSelector('AddressCountryNew', 'AddressProvinceNew', { 22 | hideElement: 'AddressProvinceContainerNew' 23 | }); 24 | } 25 | 26 | // Initialize each edit form's country/province selector 27 | $('.address-country-option').each(function() { 28 | var formId = $(this).data('form-id'); 29 | var countrySelector = 'AddressCountry_' + formId; 30 | var provinceSelector = 'AddressProvince_' + formId; 31 | var containerSelector = 'AddressProvinceContainer_' + formId; 32 | 33 | new Shopify.CountryProvinceSelector(countrySelector, provinceSelector, { 34 | hideElement: containerSelector 35 | }); 36 | }); 37 | 38 | // Toggle new/edit address forms 39 | $('.address-new-toggle').on('click', function() { 40 | $newAddressForm.toggleClass('hide'); 41 | }); 42 | 43 | $('.address-edit-toggle').on('click', function() { 44 | var formId = $(this).data('form-id'); 45 | $('#EditAddress_' + formId).toggleClass('hide'); 46 | }); 47 | 48 | $('.address-delete').on('click', function() { 49 | var $el = $(this); 50 | var formId = $el.data('form-id'); 51 | var confirmMessage = $el.data('confirm-message'); 52 | if (confirm(confirmMessage || 'Are you sure you wish to delete this address?')) { 53 | Shopify.postLink('/account/addresses/' + formId, {parameters: {_method: 'delete'}}); 54 | } 55 | }); 56 | })(); 57 | -------------------------------------------------------------------------------- /src/scripts/templates/customers-login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Password Template Script 3 | * ------------------------------------------------------------------------------ 4 | * A file that contains scripts highly couple code to the Password template. 5 | * 6 | * @namespace password 7 | */ 8 | 9 | /* eslint-disable */ 10 | 11 | theme.customerLogin = (function() { 12 | var config = { 13 | recoverPasswordForm: '#RecoverPassword', 14 | hideRecoverPasswordLink: '#HideRecoverPasswordLink' 15 | }; 16 | 17 | if (!$(config.recoverPasswordForm).length) { 18 | return; 19 | } 20 | 21 | checkUrlHash(); 22 | resetPasswordSuccess(); 23 | 24 | $(config.recoverPasswordForm).on('click', onShowHidePasswordForm); 25 | $(config.hideRecoverPasswordLink).on('click', onShowHidePasswordForm); 26 | 27 | function onShowHidePasswordForm(evt) { 28 | evt.preventDefault(); 29 | toggleRecoverPasswordForm(); 30 | } 31 | 32 | function checkUrlHash() { 33 | var hash = window.location.hash; 34 | 35 | // Allow deep linking to recover password form 36 | if (hash === '#recover') { 37 | toggleRecoverPasswordForm(); 38 | } 39 | } 40 | 41 | /** 42 | * Show/Hide recover password form 43 | */ 44 | function toggleRecoverPasswordForm() { 45 | $('#RecoverPasswordForm').toggleClass('hide'); 46 | $('#CustomerLoginForm').toggleClass('hide'); 47 | } 48 | 49 | /** 50 | * Show reset password success message 51 | */ 52 | function resetPasswordSuccess() { 53 | var $formState = $('.reset-password-success'); 54 | 55 | // check if reset password form was successfully submited. 56 | if (!$formState.length) { 57 | return; 58 | } 59 | 60 | // show success message 61 | $('#ResetSuccess').removeClass('hide'); 62 | } 63 | })(); 64 | -------------------------------------------------------------------------------- /src/scripts/util/theme-provider.js: -------------------------------------------------------------------------------- 1 | export default div => { 2 | const theme = div.getAttribute('data-theme') 3 | const header = document.getElementById('header') 4 | 5 | if (!/light|dark/.test(theme)) { 6 | return console.warn(`Header theme must be either 'light' or 'dark', not ${theme}.`) 7 | } 8 | 9 | if (theme === 'light') { 10 | header.classList.add('header--light') 11 | } else { 12 | header.classList.remove('header--light') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/sections/collection-list.liquid: -------------------------------------------------------------------------------- 1 | {% if section.settings.title != blank %} 2 |

{{ section.settings.title | escape }}

3 | {% endif %} 4 | 5 | {% for block in section.blocks limit: section.blocks.size %} 6 |
7 | {%- assign collection = collections[block.settings.collection] -%} 8 | 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 | {% elsif collection == empty %} 15 | {% capture current %}{% cycle 1, 2, 3, 4, 5, 6 %}{% endcapture %} 16 | {{ 'collection-' | append: current | placeholder_svg_tag: 'placeholder-svg placeholder-svg--small' }} 17 | {% endif %} 18 | 19 |

20 | {% if collection == empty %} 21 | {{ 'homepage.onboarding.collection_title' | t }} 22 | {% else %} 23 | {{ collection.title }} 24 | {% endif %} 25 |

26 |
27 |
28 | {% endfor %} 29 | 30 | {% if section.blocks.size == 0 %} 31 | {% include 'no-blocks' %} 32 | {% endif %} 33 | 34 | {% schema %} 35 | { 36 | "name": "Collection list", 37 | "max_blocks": 12, 38 | "settings": [ 39 | { 40 | "type": "text", 41 | "id": "title", 42 | "label": "Heading", 43 | "default": "Collection list" 44 | } 45 | ], 46 | "blocks": [ 47 | { 48 | "type": "collection", 49 | "name": "Collection", 50 | "settings": [ 51 | { 52 | "label": "Collection", 53 | "id": "collection", 54 | "type": "collection" 55 | } 56 | ] 57 | } 58 | ], 59 | "presets": [ 60 | { 61 | "name": "Collection list", 62 | "category": "Collection", 63 | "blocks": [ 64 | { 65 | "type": "collection" 66 | }, 67 | { 68 | "type": "collection" 69 | }, 70 | { 71 | "type": "collection" 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | {% endschema %} 78 | -------------------------------------------------------------------------------- /src/sections/featured-collection.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | {% if section.settings.title != blank %} 6 |

{{ section.settings.title | escape }}

7 | {% endif %} 8 | 9 | {%- assign collection = collections[section.settings.collection] -%} 10 | 11 | 37 |
38 |
39 | 40 | 41 | {% schema %} 42 | { 43 | "name": "Featured collection", 44 | "settings": [ 45 | { 46 | "type": "text", 47 | "id": "title", 48 | "label": "Heading", 49 | "default": "Featured collection" 50 | }, 51 | { 52 | "id": "collection", 53 | "type": "collection", 54 | "label": "Collection" 55 | } 56 | ], 57 | "presets": [ 58 | { 59 | "name": "Featured collection", 60 | "category": "Collection" 61 | } 62 | ] 63 | } 64 | 65 | {% endschema %} 66 | -------------------------------------------------------------------------------- /src/sections/featured-product.liquid: -------------------------------------------------------------------------------- 1 | {%- assign product = all_products[section.settings.product] -%} 2 | {%- assign current_variant = product.selected_or_first_available_variant -%} 3 | 4 | {% if product == empty %} 5 | {%- assign section_onboarding = true -%} 6 | {%- assign onboarding_title = 'homepage.onboarding.product_title' | t -%} 7 | {% endif %} 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | {% if product.featured_image.src != blank %} 25 | {{ featured_image.alt | escape }} 26 | {% else %} 27 | {{ 'product-1' | placeholder_svg_tag: 'placeholder-svg placeholder-svg--small' }} 28 | {% endif %} 29 | 30 | {% if product.images.size > 1 %} 31 | 40 | {% endif %} 41 | 42 |

{{ product.title | default: onboarding_title }}

43 | {% if product.vendor != blank %} 44 |

{{ product.vendor }}

45 | {% endif %} 46 | 47 |
48 | 49 | 50 | 51 | 52 | {% include 'add-to-cart-form' %} 53 | 54 |
55 | 56 |
57 | {{ product.description }} 58 |
59 | 60 | {% if section.settings.show_share_buttons %} 61 | {% include 'social-sharing', share_title: product.title, share_permalink: product.url, share_image: product %} 62 | {% endif %} 63 | 64 | {% unless product == empty %} 65 | 68 | {% endunless %} 69 |
70 | 71 | {% schema %} 72 | { 73 | "name": "Featured product", 74 | "settings": [ 75 | { 76 | "type": "product", 77 | "id": "product", 78 | "label": "Product" 79 | }, 80 | { 81 | "type": "checkbox", 82 | "id": "show_share_buttons", 83 | "label": "Show social sharing buttons" 84 | } 85 | ], 86 | "presets": [ 87 | { 88 | "name": "Featured product", 89 | "category": "Product" 90 | } 91 | ] 92 | } 93 | {% endschema %} 94 | -------------------------------------------------------------------------------- /src/sections/footer.liquid: -------------------------------------------------------------------------------- 1 | 16 | 17 | {% schema %} 18 | { 19 | "name": "Footer", 20 | "settings": [ 21 | { 22 | "type": "link_list", 23 | "id": "footer_menu", 24 | "label": "First menu", 25 | "default": "footer" 26 | } 27 | ] 28 | } 29 | {% endschema %} 30 | -------------------------------------------------------------------------------- /src/sections/header.liquid: -------------------------------------------------------------------------------- 1 | 7 |
8 | 53 |
-------------------------------------------------------------------------------- /src/sections/product.liquid: -------------------------------------------------------------------------------- 1 |
2 | 3 | {%- assign current_variant = product.selected_or_first_available_variant -%} 4 | {%- assign featured_image = current_variant.featured_image | default: product.featured_image -%} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 | 21 | {{ featured_image.alt | escape }} 22 | {% if product.images.size > 1 %} 23 | 32 | {% endif %} 33 | 34 |

{{ product.title }}

35 |

{{ product.vendor }}

36 | 37 |
38 | 39 | 40 | 41 | {% include 'add-to-cart-form' %} 42 |
43 | 44 |
45 | {{ product.description }} 46 |
47 | 48 | {% comment %} 49 | 50 | Commenting out social sharing for now 51 | 52 | {% if section.settings.show_share_buttons %} 53 | {% include 'social-sharing', share_title: product.title, share_permalink: product.url, share_image: product %} 54 | {% endif %} 55 | 56 | {% endcomment %} 57 | 58 | {% unless product == empty %} 59 | 62 | {% endunless %} 63 |
64 | 65 | {% schema %} 66 | { 67 | "name": "Product pages", 68 | "settings": [ 69 | { 70 | "type": "checkbox", 71 | "id": "show_share_buttons", 72 | "label": "Show social sharing buttons", 73 | "default": true 74 | } 75 | ] 76 | } 77 | {% endschema %} 78 | -------------------------------------------------------------------------------- /src/sections/s-cart.liquid: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Your Bag
4 |
5 | Close 6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /src/snippets/account-address.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% if default %} 3 |
Default
4 | {% endif %} 5 | 6 |
7 |
{{ address.name }}
8 | {% if address.company %}{{ address.company }}
{% endif %} 9 | {{ address.street }}
10 | {{ address.city | capitalize }}{% if address.province %}, {{ address.province | upcase }} {% endif %} {{ address.zip | upcase }}
11 | {{ address.country }}
12 | {% if address.phone != '' %}{{ address.phone }}{% endif %} 13 |
14 | 15 | {% if edit %} 16 | 20 | {% endif %} 21 |
22 | -------------------------------------------------------------------------------- /src/snippets/account-titles.liquid: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

{{ title }}

7 | 8 | 31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /src/snippets/add-to-cart-form.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% unless product.has_only_default_variant %} 3 | {% for option in product.options_with_values %} 4 |
5 | 8 | 9 | 21 |
22 | {% endfor %} 23 | {% endunless %} 24 | 25 | 35 | 36 | 37 | 38 | 39 |
40 | 41 | {{ current_variant.price | default: '1999' | money }} 42 | 43 | 44 | {% if product.compare_at_price_max > product.price %} 45 | {{ 'products.product.regular_price' | t }} 46 | 47 | {% if current_variant.compare_at_price > current_variant.price %} 48 | {{ current_variant.compare_at_price | money }} 49 | {% endif %} 50 | 51 | {% endif %} 52 |
53 | 54 | 70 |
-------------------------------------------------------------------------------- /src/snippets/button.liquid: -------------------------------------------------------------------------------- 1 | {% assign tag = tag | default: 'a' %} 2 | 3 | {% capture cx %} 4 | button rel sans caps track 5 | {{ className }} 6 | {% if outline == true %}button--outline{% endif %} 7 | {% if small == true %}button--small{% endif %} 8 | {% if light == true %}button--light{% endif %} 9 | {% if inherit == true %}button--inherit{% endif %} 10 | {% endcapture %} 11 | 12 | <{{ tag }} 13 | {% if href %}href='{{ href }}'{% endif %} 14 | {% if type %}type='{{ type }}'{% endif %} 15 | {% if name %}name='{{ name }}'{% endif %} 16 | class='{% include 'u-cx' with cx %}' 17 | role='button'> 18 | 19 | {{ cta }} 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/snippets/cart-drawer.liquid: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 9 | 10 |

Your Cart

11 |
12 |
13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /src/snippets/form-input-select.liquid: -------------------------------------------------------------------------------- 1 | {% if required == blank %} 2 | {% assign required = 'false' %} 3 | {% endif %} 4 | 5 | {%- capture id -%} 6 | {{type}}{{name}}{{ className | replace: ' ', '' }} 7 | {%- endcapture -%} 8 | 9 | {%- capture cx -%} 10 | select-wrapper x rel 11 | {{ className }} 12 | {% if boxed %}is-boxed{% endif %} 13 | {%- endcapture -%} 14 | 15 |
16 | 17 | 26 | 27 | 28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /src/snippets/form-input-text.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 | 39 | 40 | 41 |
42 | -------------------------------------------------------------------------------- /src/snippets/form-input-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 | 37 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /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 %}{% endif %} 57 | {% if social_currency %}{% endif %} 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/snippets/icon-chevron.liquid: -------------------------------------------------------------------------------- 1 | {% capture cx %} 2 | chevron 3 | {% if left == true %}is-left{% endif %} 4 | {% if down == true %}is-down{% endif %} 5 | {% endcapture %} 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/snippets/navigation.liquid: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/snippets/no-blocks.liquid: -------------------------------------------------------------------------------- 1 |
2 | {{ 'homepage.onboarding.no_content' | t }} 3 |
4 | -------------------------------------------------------------------------------- /src/snippets/pagination.liquid: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/snippets/search.liquid: -------------------------------------------------------------------------------- 1 |
2 | 5 | 10 | 13 |
-------------------------------------------------------------------------------- /src/snippets/social-sharing.liquid: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | {% if settings.share_facebook %} 5 | 6 | {% include 'icon-facebook' %} 7 | 8 | {{ 'general.social.alt_text.share_on_facebook' | t }} 9 | 10 | {% endif %} 11 | 12 | {% if settings.share_twitter %} 13 | 14 | {% include 'icon-twitter' %} 15 | 16 | {{ 'general.social.alt_text.share_on_twitter' | t }} 17 | 18 | {% endif %} 19 | 20 | {% if settings.share_pinterest %} 21 | 22 | {% include 'icon-pinterest' %} 23 | 24 | {{ 'general.social.alt_text.share_on_pinterest' | t }} 25 | 26 | {% endif %} 27 | 28 |
29 | -------------------------------------------------------------------------------- /src/snippets/theme-provider.liquid: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | @import 'svbstrate/src/lib/reset.css'; 2 | @import 'svbstrate/src/lib/display.css'; 3 | @import 'svbstrate/src/lib/positioning.css'; 4 | @import 'svbstrate/src/lib/flexbox.css'; 5 | @import 'svbstrate/src/lib/align.css'; 6 | @import 'svbstrate/src/lib/spacing.css'; 7 | @import 'svbstrate/src/lib/buttons.css'; 8 | @import 'svbstrate/src/lib/forms.css'; 9 | @import 'svbstrate/src/lib/z-index.css'; 10 | 11 | /* Default Slater Theme */ 12 | @import 'slater/lib/var'; 13 | @import 'slater/lib/colors'; 14 | @import 'slater/lib/breakpoint'; 15 | @import 'slater/lib/spacing'; 16 | @import 'slater/lib/containers'; 17 | @import 'slater/lib/forms'; 18 | @import 'slater/lib/lists'; 19 | @import 'slater/lib/visibilty'; 20 | 21 | @import 'slater/components/cart-drawer'; 22 | @import 'slater/components/page-transition'; 23 | @import 'slater/components/accounts'; 24 | @import 'slater/components/buttons'; 25 | 26 | body { 27 | background-color: pink; 28 | color: red; 29 | } -------------------------------------------------------------------------------- /src/styles/slater/components/buttons.css: -------------------------------------------------------------------------------- 1 | button { 2 | border: 0; 3 | padding: 1.1rem 1.3rem; 4 | } -------------------------------------------------------------------------------- /src/styles/slater/components/cart-drawer.css: -------------------------------------------------------------------------------- 1 | .cart-quantity { 2 | cursor: pointer; 3 | } 4 | 5 | .cart-drawer-outer { 6 | display: none; 7 | 8 | &.is-active { 9 | display: block; 10 | } 11 | &.is-visible { 12 | .cart-drawer { 13 | transform: translateX(0); 14 | } 15 | .cart-drawer-overlay { 16 | opacity: 0.8; 17 | } 18 | } 19 | } 20 | .cart-drawer-overlay { 21 | opacity: 0; 22 | transition: all var(--slow) var(--ease); 23 | } 24 | .cart-drawer { 25 | max-width: 500px; 26 | transition: all var(--slow) var(--ease); 27 | transform: translateX(100%); 28 | flex-direction: column; 29 | padding-bottom: 160px; /* make room for footer */ 30 | 31 | header { 32 | border-bottom: 1px solid var(--gray); 33 | height: 75px; 34 | 35 | @media (--m) { 36 | height: 100px; 37 | } 38 | 39 | button { 40 | height: 30px; 41 | width: 30px; 42 | 43 | &:focus { 44 | outline: 1px dashed var(--gray); 45 | } 46 | 47 | svg { 48 | height: 15px; 49 | width: 15px; 50 | } 51 | } 52 | } 53 | } 54 | .cart-drawer__items { 55 | overflow: auto; 56 | padding-top: 75px; 57 | 58 | @media (--m) { 59 | padding-top: 100px; 60 | } 61 | 62 | svg.loader { 63 | width: 100px; 64 | height: 100px; 65 | animation-duration: 1.5s; 66 | animation-name: pulse; 67 | animation-iteration-count: infinite; 68 | } 69 | } 70 | 71 | .cart-drawer__item { 72 | padding: 2em 0 1.5em; 73 | border-top: 1px solid var(--gray); 74 | 75 | &:first-child { 76 | border-top: 0; 77 | } 78 | 79 | img { 80 | width: 90px; 81 | } 82 | 83 | button { 84 | top: 1.5em; 85 | 86 | svg { 87 | width: 10px; 88 | height: 10px; 89 | } 90 | } 91 | } 92 | .cart-drawer__item__remove { 93 | top: 1.5em; 94 | 95 | svg { 96 | width: 10px; 97 | height: 10px; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/styles/slater/components/page-transition.css: -------------------------------------------------------------------------------- 1 | .page-transition { 2 | transition: all var(--slow) var(--ease); 3 | height: 100vh; 4 | width: 100vw; 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | transform: translateY(100%); 9 | &.cover { 10 | transform: translateY(0); 11 | } 12 | } -------------------------------------------------------------------------------- /src/styles/slater/lib/breakpoint.css: -------------------------------------------------------------------------------- 1 | @custom-media --s (min-width: 37.5em); /* 600 */ 2 | @custom-media --ms (max-width: 37.5em); /* 600 */ 3 | @custom-media --m (min-width: 56.26em); /* 900 */ 4 | @custom-media --mm (max-width: 56.26em); /* 900 */ 5 | @custom-media --l (min-width: 75em); /* 1200 */ 6 | @custom-media --xl (min-width: 81.25em); /* 1300 */ 7 | -------------------------------------------------------------------------------- /src/styles/slater/lib/colors.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --white: #F4F4F4; 3 | --gray-medium: #EAEAEA; 4 | --gray: #EAEAEA; 5 | --ruby: #BB3136; 6 | } 7 | 8 | .cw { 9 | color: var(--white); 10 | } 11 | 12 | .cgm { 13 | color: var(--white); 14 | } 15 | 16 | .bcw { 17 | background-color: var(--white); 18 | } 19 | 20 | .bcgm { 21 | background-color: var(--gray-medium); 22 | } 23 | 24 | .bcr { 25 | background-color: var(--ruby); 26 | } -------------------------------------------------------------------------------- /src/styles/slater/lib/containers.css: -------------------------------------------------------------------------------- 1 | .container--c { 2 | margin-right: auto; 3 | margin-left: auto; 4 | } 5 | 6 | .container--l { 7 | max-width: 1280px; 8 | } 9 | 10 | .container--xl { 11 | max-width: 1480px; 12 | } 13 | 14 | .outer { 15 | width: 100%; 16 | padding-left: 1.5em; 17 | padding-right: 1.5em; 18 | @media (--m) { 19 | padding-left: 3em; 20 | padding-right: 3em; 21 | } 22 | } 23 | .outer--v { 24 | padding-top: 1.5em; 25 | padding-bottom: 1.5em; 26 | } 27 | .container { 28 | margin-left: auto; 29 | margin-right: auto; 30 | } 31 | 32 | .container--s { 33 | max-width: 480px; 34 | width: 100%; 35 | } 36 | 37 | .container--a { 38 | max-width: 630px; 39 | } 40 | 41 | .container--m { 42 | max-width: 700px; 43 | } 44 | 45 | .container--mm { 46 | max-width: 890px; 47 | } -------------------------------------------------------------------------------- /src/styles/slater/lib/forms.css: -------------------------------------------------------------------------------- 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; 15 | color: var(--ruby); 16 | border-bottom: 1px solid var(--ruby); 17 | 18 | &:focus { 19 | /* border-color: black; */ 20 | } 21 | &::-webkit-input-placeholder { 22 | color: var(--ruby); 23 | } 24 | /* Change the white to any color ;) */ 25 | &:-webkit-autofill { 26 | -webkit-box-shadow: 0 0 0 30px white inset; 27 | } 28 | } 29 | input, 30 | select { 31 | height: 55px; 32 | } 33 | input[type='checkbox'] { 34 | height: auto; 35 | } 36 | textarea { 37 | max-width: 100%; 38 | overflow: auto; 39 | resize: vertical; 40 | min-height: 55px; 41 | padding-top: 0.5em; 42 | } 43 | .input--reset { 44 | height: auto; 45 | padding: 0; 46 | } 47 | .select-wrapper { 48 | display: block; 49 | position: relative; 50 | 51 | svg { 52 | } 53 | 54 | select { 55 | appearance: none; 56 | padding-right: 50px; 57 | width: 100%; 58 | } 59 | 60 | &.is-boxed { 61 | svg { 62 | right: 2em; 63 | } 64 | select { 65 | height: 75px; 66 | padding-left: 2em; 67 | padding-right: 2em; 68 | 69 | &:focus { 70 | border-color: black; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/styles/slater/lib/lists.css: -------------------------------------------------------------------------------- 1 | ol, ul { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | ul.list, 7 | ol.list { 8 | padding-left: 2em; 9 | } 10 | ol.list { 11 | list-style: decimal; 12 | } 13 | ul.list { 14 | list-style: disc; 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/slater/lib/spacing.css: -------------------------------------------------------------------------------- 1 | .mt15, .my15, .m15 { margin-top: 1.5em } 2 | .mb15, .my15, .m15 { margin-bottom: 1.5em } 3 | .ml15, .mx15, .m15 { margin-left: 1.5em } 4 | .mr15, .mx15, .m15 { margin-right: 1.5em } 5 | .pt15, .pv15, .p15 { padding-top: 1.5em } 6 | .pb15, .pv15, .p15 { padding-bottom: 1.5em } 7 | .pl15, .py15, .p15 { padding-left: 1.5em } 8 | .pr15, .py15, .p15 { padding-right: 1.5em } 9 | 10 | .mt2, .my2, .m2 { margin-top: 2em } 11 | .mb2, .my2, .m2 { margin-bottom: 2em } 12 | .ml2, .mx2, .m2 { margin-left: 2em } 13 | .mr2, .mx2, .m2 { margin-right: 2em } 14 | .pt2, .pv2, .p2 { padding-top: 2em } 15 | .pb2, .pv2, .p2 { padding-bottom: 2em } 16 | .pl2, .py2, .p2 { padding-left: 2em } 17 | .pr2, .py2, .p2 { padding-right: 2em } 18 | -------------------------------------------------------------------------------- /src/styles/slater/lib/typography.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | color: var(--black); 4 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif; 5 | font-size: 100%; 6 | line-height: 1.7; 7 | font-weight: 500; 8 | -webkit-font-smoothing: antialiased; 9 | } 10 | 11 | /** 12 | * @font-face definitions in /assets/fonts.css 13 | * and linked in of theme.liquid 14 | */ 15 | body, .text { 16 | font-family: 'sofia-pro'; 17 | } 18 | .serif { 19 | font-family: 'freight-display-pro', serif; 20 | } 21 | .sans { 22 | font-family: 'sofia-pro'; 23 | letter-spacing: 0.1em; 24 | } 25 | .light { 26 | font-weight: 400; 27 | } 28 | .medium { 29 | font-weight: 500; 30 | } 31 | h1, h2, h3, h4, h5, h6 { 32 | font-weight: 400; 33 | } 34 | .h0 { 35 | font-size: calc((54 /16) * 1rem); 36 | @media (--m) { 37 | font-size: calc((96 /16) * 1rem); 38 | } 39 | } 40 | .s1, 41 | h1, .h1 { 42 | font-size: calc((34 /16) * 1rem); 43 | @media (--s) { 44 | font-size: calc((54 /16) * 1rem); 45 | } 46 | @media (--m) { 47 | font-size: calc((110 /16) * 1rem); 48 | } 49 | } 50 | 51 | h1.hero-h1 { 52 | @media (--s) { 53 | font-size: calc((70/16) * 1rem); 54 | } 55 | @media (--m) { 56 | font-size: calc((90/16) * 1rem); 57 | } 58 | @media (--l) { 59 | font-size: calc((110/16) * 1rem); 60 | } 61 | } 62 | 63 | h1, .h1 { 64 | line-height: 1.1; 65 | } 66 | .s2, 67 | h2, .h2 { 68 | font-size: calc((32 /16) * 1rem); 69 | @media (--m) { 70 | font-size: calc((54 /16) * 1rem); 71 | } 72 | @media (--l) { 73 | font-size: calc((72 /16) * 1rem); 74 | } 75 | } 76 | h2, .h2 { 77 | line-height: 1.3; 78 | } 79 | .s3, 80 | h3, .h3 { 81 | font-size: calc((28 /16) * 1rem); 82 | @media (--m) { 83 | font-size: calc((32 /16) * 1rem); 84 | } 85 | @media (--l) { 86 | font-size: calc((60 /16) * 1rem); 87 | } 88 | } 89 | h3, .h3 { 90 | line-height: 1.3; 91 | @media (--l) { 92 | line-height: 1.4; 93 | } 94 | } 95 | .s4, 96 | h4, .h4 { 97 | font-size: calc((26 /16) * 1rem); 98 | @media (--m) { 99 | font-size: calc((35 /16) * 1rem); 100 | } 101 | @media (--l) { 102 | font-size: calc((48 /16) * 1rem); 103 | } 104 | } 105 | h4, .h4 { 106 | line-height: 1.5; 107 | @media (--l) { 108 | line-height: 1.1; 109 | } 110 | } 111 | .s5, 112 | h5, .h5 { 113 | font-size: calc((15 /16) * 1rem); 114 | letter-spacing: 0.05rem; 115 | } 116 | h5, .h5 { 117 | line-height: 1.5; 118 | } 119 | .s6, 120 | h6, .h6 { 121 | font-size: calc((26 /16) * 1rem); 122 | } 123 | h6, .h6 { 124 | line-height: 1.2; 125 | } 126 | .s0, 127 | p, .p { 128 | font-size: calc((16/16) * 1rem); 129 | @media (--m) { 130 | font-size: calc((18/16) * 1rem); 131 | } 132 | } 133 | .s1, 134 | p .p1 { 135 | font-size: calc((14/16) * 1rem); 136 | letter-spacing: 0.05rem; 137 | } 138 | .s5, 139 | p .p5 { 140 | font-size: calc((12/16) * 1rem); 141 | letter-spacing: 0.1rem; 142 | } 143 | .s5 { 144 | line-height: 1.5; 145 | } 146 | .s16, 147 | .s16 p { 148 | font-size: calc((16/16) * 1rem); 149 | } 150 | p, .p { 151 | line-height: 1.7; 152 | } 153 | p { 154 | margin: 1em 0; 155 | } 156 | 157 | blockquote { 158 | text-align: center; 159 | color: var(--blue); 160 | padding: 10px; 161 | margin: 0; 162 | p { 163 | font-family: 'freight-display-pro', serif; 164 | font-size: 26px; 165 | line-height: 1.4; 166 | @media (--m) { 167 | font-size: 36px; 168 | line-height: 1.3; 169 | } 170 | } 171 | } 172 | 173 | .p2, .p2 p { 174 | font-size: calc((18 /16) * 1rem); 175 | @media (--m) { 176 | font-size: calc((22 /16) * 1rem); 177 | } 178 | } 179 | 180 | .p4 { 181 | font-size: calc((28 /16) * 1rem); 182 | @media (--m) { 183 | font-size: calc((35 /16) * 1rem); 184 | line-height: 1.3; 185 | } 186 | } 187 | small, .small { 188 | font-size: calc((12 /16) * 1rem); 189 | } 190 | .xsmall { 191 | font-size: calc((10 /16) * 1rem); 192 | } 193 | a { 194 | color: inherit; 195 | text-decoration: none; 196 | outline: none; 197 | &:hover { 198 | outline: none; 199 | } 200 | } 201 | hr { 202 | display: block; 203 | border: 0; 204 | margin: 0; 205 | height: 1px; 206 | width: 100%; 207 | background-color: currentColor; 208 | color: inherit; 209 | } 210 | strong, .b { 211 | font-weight: bold; 212 | } 213 | em, .i { 214 | font-style: italic; 215 | } 216 | .caps { 217 | text-transform: uppercase; 218 | } 219 | .normal { 220 | text-transform: none; 221 | } 222 | .nw { 223 | font-weight: normal; 224 | } 225 | .no-under { 226 | text-decoration: none; 227 | } 228 | .light { 229 | font-weight: 300; 230 | } 231 | .book, 232 | .medium { 233 | font-weight: 400; 234 | } 235 | .demi { 236 | font-weight: 500; 237 | } 238 | .bold { 239 | font-weight: 700; 240 | } 241 | .track { 242 | letter-spacing: 0.2em; 243 | } 244 | .track--l { 245 | letter-spacing: 0.3em; 246 | } 247 | .underline { 248 | position: relative; 249 | overflow: hidden; 250 | &:after { 251 | content: ''; 252 | transition: all .25s; 253 | position: absolute; 254 | bottom: 0; 255 | left: 0; 256 | width: 100%; 257 | height: 1px; 258 | border-bottom: 1px solid var(--orange); 259 | } 260 | &--black { 261 | &:after { 262 | bottom: 4px; 263 | border-bottom-color: var(--black); 264 | } 265 | } 266 | &--white { 267 | &:after { 268 | bottom: -2px; 269 | border-bottom-color: white; 270 | border-bottom-width: 2px; 271 | } 272 | &:hover { 273 | &:after { 274 | transform: translateY(4px); 275 | } 276 | } 277 | } 278 | &:hover { 279 | &:after { 280 | transform: translateY(4px); 281 | } 282 | } 283 | } 284 | .link-hover { 285 | transition: all var(--fast) var(--ease); 286 | &:hover { 287 | color: var(--yellow); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/styles/slater/lib/var.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --fast: 200ms; 3 | --slow: 400ms; 4 | --ease: ease-in-out; 5 | --cubic: cubic-bezier(.01,.57,.13,1); 6 | --shadow: 0 15px 35px -10px rgba(0,0,0,0.15); 7 | --video-shadow: 0 25px 50px -20px rgba(0,0,0,0.15); 8 | } 9 | -------------------------------------------------------------------------------- /src/styles/slater/lib/visibilty.css: -------------------------------------------------------------------------------- 1 | .hide { 2 | display: none; 3 | } 4 | .hide--s { 5 | @media (--s) { 6 | display: none; 7 | } 8 | } 9 | .hide--m { 10 | @media (--m) { 11 | display: none; 12 | } 13 | } 14 | .hide--l { 15 | @media (--l) { 16 | display: none; 17 | } 18 | } 19 | .show--s { 20 | display: none; 21 | @media (--s) { 22 | display: block; 23 | } 24 | } 25 | .show--m { 26 | display: none; 27 | @media (--m) { 28 | display: block; 29 | } 30 | } 31 | .show--l { 32 | display: none; 33 | @media (--l) { 34 | display: block; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/slater/theme.css: -------------------------------------------------------------------------------- 1 | /* Default Slater Theme */ 2 | @import 'lib/reset'; 3 | @import 'lib/var'; 4 | @import 'lib/colors'; 5 | @import 'lib/breakpoint'; 6 | @import 'lib/align'; 7 | @import 'lib/display'; 8 | @import 'lib/containers'; 9 | @import 'lib/flexbox'; 10 | @import 'lib/lists'; 11 | @import 'lib/visibilty'; 12 | @import 'lib/spacing'; 13 | @import 'lib/z-index'; 14 | @import 'lib/positioning'; 15 | 16 | @import 'components/cart-drawer'; 17 | @import 'components/page-transition'; 18 | @import 'components/buttons'; 19 | 20 | body { 21 | background-color: pink; 22 | color: red; 23 | } -------------------------------------------------------------------------------- /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 |
23 |

{{ article.title }}

24 | {% capture date %}{{ article.published_at | time_tag: format: 'month_day_year' }}{% endcapture %} 25 |

{{ 'blogs.article.author_on_date_html' | t: author: article.author, date: date }}

26 |
27 | 28 |
29 | {{ article.content }} 30 |
31 | 32 | {% if article.tags.size > 0 %} 33 | 40 | {% endif %} 41 | 42 | {% if settings.social_sharing_blog %} 43 | {% include 'social-sharing', share_title: article.title, share_permalink: article.url, share_image: article.image %} 44 | {% endif %} 45 | 46 | {% if blog.comments_enabled? %} 47 |

{{ 'blogs.comments.with_count' | t: count: number_of_comments }}

48 | 49 | {% paginate article.comments by 5 %} 50 | 51 |
52 | {% if comment and comment.status and paginate.current_page == 1 %} 53 |

54 | {% if blog.moderated? and comment.status != 'published' %} 55 | {{ 'blogs.comments.success_moderated' | t }} 56 | {% else %} 57 | {{ 'blogs.comments.success' | t }} 58 | {% endif %} 59 |

60 | {% endif %} 61 | 62 | {% if number_of_comments > 0 %} 63 | 88 | 89 | {% if paginate.pages > 1 %} 90 | {% include 'pagination' %} 91 | {% endif %} 92 | {% endif %} 93 |
94 | 95 | {% endpaginate %} 96 | 97 | {% form 'new_comment', article %} 98 |

{{ 'blogs.comments.title' | t }}

99 | 100 | {{ form.errors | default_errors }} 101 | 102 | 105 | 112 | 113 | 116 | 125 | 126 | 129 | 136 | 137 | {% if blog.moderated? %} 138 |

{{ 'blogs.comments.moderated' | t }}

139 | {% endif %} 140 | 141 | 142 | {% endform %} 143 | 144 | {% endif %} 145 | 146 |
147 | -------------------------------------------------------------------------------- /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 %}{{ blog.title | link_to: blog.url }} — {{ current_tags.first }}{% endcapture %} 7 | {% endif %} 8 | 9 |

{{ blog_title }}

10 | 11 | {% if blog.all_tags.size > 0 %} 12 |

{{ 'blogs.general.categories' | t }}

13 | 14 | 25 | {% endif %} 26 | 27 | 28 | {% comment %} 29 | 30 | Article Previews 31 | ==================== 32 | {% endcomment %} 33 | 34 | {% for article in blog.articles %} 35 |

36 | {{ article.title }} 37 |

38 | 39 | {% capture date %}{{ article.published_at | time_tag: format: 'month_day_year' }}{% endcapture %} 40 | 41 |

42 | {{ 'blogs.article.author_on_date_html' | t: author: article.author, date: date }} 43 |

44 | 45 | {% if article.image %} 46 | 47 | {{ article | img_url: '1024x1024' | img_tag: article.title }} 48 | 49 | {% endif %} 50 | 51 |
52 | {% if article.excerpt.size > 0 %} 53 | {{ article.excerpt }} 54 | {% else %} 55 |

56 | {{ article.content | strip_html | truncatewords: 100 }} 57 |

58 | {% endif %} 59 |
60 | 61 | {% if blog.comments_enabled? or article.tags.size > 0 %} 62 | 80 | {% endif %} 81 | 82 |

83 | {{ 'blogs.article.read_more' | t }} → 84 |

85 | 86 | {% endfor %} 87 | 88 | {% if paginate.pages > 1 %} 89 | {% include 'pagination' %} 90 | {% endif %} 91 | 92 | {% endpaginate %} 93 | -------------------------------------------------------------------------------- /src/templates/cart.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% if cart.item_count > 0 %} 3 |

{{ 'cart.general.title' | t }}

4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for item in cart.items %} 18 | 19 | {% comment %} 20 | 21 | Cart Item Template 22 | ===================== 23 | The data-label attributes on 28 | 33 | 63 | 73 | 81 | 84 | 85 | 86 | {% endfor %} 87 | 88 |
{{ 'cart.label.product' | t }}{{ 'cart.label.price' | t }}{{ 'cart.label.quantity' | t }}{{ 'cart.label.total' | t }}
elements are mobile-friendly 24 | helpers used for responsive-table labels 25 | {% endcomment %} 26 | 27 |
29 | 30 | {{ item.title | escape }} 31 | 32 | 34 | {{ item.product.title }} 35 | 36 | {% unless item.product.has_only_default_variant %} 37 |

{{ item.variant.title }}

38 | {% endunless %} 39 | 40 |

{{ item.vendor }}

41 | 42 | {%- assign property_size = item.properties | size -%} 43 | 44 | {% if property_size > 0 %} 45 | {% for p in item.properties %} 46 | {% unless p.last == blank %} 47 | {{ p.first }}: 48 | 49 | {% if p.last contains '/uploads/' %} 50 | {{ p.last | split: '/' | last }} 51 | {% else %} 52 | {{ p.last }} 53 | {% endif %} 54 | 55 | {% endunless %} 56 | {% endfor %} 57 | {% endif %} 58 | 59 | 60 | {{ 'cart.general.remove' | t }} 61 | 62 |
64 | {% if item.original_line_price != item.line_price %} 65 | {{ 'cart.label.discounted_price' | t }} 66 | {{ item.price | money }} 67 | {{ 'cart.label.original_price' | t }} 68 | {{ item.original_price | money }} 69 | {% else %} 70 | {{ item.price | money }} 71 | {% endif %} 72 | 74 | 80 | 82 | {{ item.line_price | money }} 83 |
89 | 90 | {% if settings.cart_notes_enable %} 91 | 92 | 93 | {% endif %} 94 | 95 |

{{ 'cart.general.subtotal' | t }}

96 |

{{ cart.total_price | money }}

97 | {% if cart.total_discounts > 0 %} 98 |

{{ 'cart.general.savings' | t }} {{ cart.total_discounts | money }}

99 | {% endif %} 100 |

{{ 'cart.general.shipping_at_checkout' | t }}

101 | 102 | 103 |
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 | 26 | 27 |
28 | {% for product in collection.products %} 29 |
30 | 31 | {{ product.featured_image.alt | escape }} 32 | 33 | 34 |

35 | {{ product.title }} 36 |

37 |

38 | {% if product.compare_at_price > product.price %} 39 | 40 | {% if product.price_varies %} 41 | {%- assign sale_price = product.price | money -%} 42 | {{ 'products.product.on_sale_from_html' | t: price: sale_price }} 43 | {% else %} 44 | {{ 'products.product.on_sale' | t }} 45 | {{ product.price | money }} 46 | {% endif %} 47 | 48 | {% else %} 49 | 50 | {% if product.price_varies %} 51 | {%- assign price = product.price | money -%} 52 | {{ 'products.product.from_text_html' | t: price: price }} 53 | {% else %} 54 | {{ product.price | money }} 55 | {% endif %} 56 | 57 | {% endif %} 58 | 59 | {% if product.compare_at_price > product.price %} 60 | {{ 'products.product.regular_price' | t }} 61 | {{ product.compare_at_price | money }} 62 | {% endif %} 63 | 64 | {% unless product.available %} 65 | {{ 'products.product.sold_out' | t }} 66 | {% endunless %} 67 |

68 |
69 | 70 | {% else %} 71 | {% if collection.handle == 'all' and collection.all_vendors.size == 0 and collection.all_types.size == 0 %} 72 | {% for i in (1..8) %} 73 | 74 | {% capture current %}{% cycle 1, 2, 3, 4, 5, 6 %}{% endcapture %} 75 | {{ 'product-' | append: current | placeholder_svg_tag: 'placeholder-svg placeholder-svg--small' }} 76 | 77 | 78 |

79 | {{ 'homepage.onboarding.product_title' | t }} 80 |

81 | 82 |

83 | {{ 1999 | money }} 84 |

85 | {% endfor %} 86 | {% else %} 87 |

{{ 'collections.general.no_matches' | t }}

88 | {% endif %} 89 | 90 | {% endfor %} 91 |
92 | 93 | {% if paginate.pages > 1 %} 94 | {% include 'pagination' %} 95 | {% endif %} 96 | {% endpaginate %} 97 | -------------------------------------------------------------------------------- /src/templates/customers/account.liquid: -------------------------------------------------------------------------------- 1 | {% include 'theme-provider' with theme: 'dark' %} 2 | 3 |
4 |
5 | {% 6 | include 'account-titles' with 7 | title: 'My Account' 8 | %} 9 | 10 | 55 |
56 | 57 |
58 | -------------------------------------------------------------------------------- /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 | 11 | 15 |

16 | 17 |

18 | 21 | 25 |

26 | 27 | 28 | 29 | {% endform %} 30 | -------------------------------------------------------------------------------- /src/templates/customers/addresses.liquid: -------------------------------------------------------------------------------- 1 | {% include 'theme-provider' with theme: 'dark' %} 2 | 3 | {% include 'account-titles' with title: 'Addresses' %} 4 | 5 | {% paginate customer.addresses by 100 %} 6 | 7 |
8 |
9 |
10 | 11 | 12 | {% 13 | include 'account-address-form' with 14 | id: 'new', 15 | action: customer.new_address, 16 | title: 'Add New Address', 17 | cta: 'Add Address', 18 | formClassName: 'mb1 pb1' 19 | %} 20 | 21 | {% comment %} 22 | List all customer addresses with a unique edit form. 23 | Also add pagination in case they have a large number of addresses 24 | {% endcomment %} 25 | {% for address in customer.addresses %} 26 | {% if customer.default_address == address %} 27 | {% assign default_addy = true %} 28 | {% else %} 29 | {% assign default_addy = false %} 30 | {% endif %} 31 | {% 32 | include 'account-address' with 33 | address: address, 34 | default: default_addy, 35 | edit: true, 36 | className: 'mb1' 37 | %} 38 | 39 | {% 40 | include 'account-address-form' with 41 | id: address.id, 42 | action: address, 43 | title: 'Edit Address', 44 | cta: 'Update Address', 45 | formClassName: 'is-edit-form mb1' 46 | %} 47 | {% endfor %} 48 | 49 | {% if customer.addresses == empty %} 50 |
You have no saved address.
51 | {% endif %} 52 | 53 | {% include 'pagination' %} 54 |
55 |
56 |
57 | 58 | 89 | 90 | {% endpaginate %} 91 | 92 | -------------------------------------------------------------------------------- /src/templates/customers/login.liquid: -------------------------------------------------------------------------------- 1 | {% include 'theme-provider' with theme: 'dark' %} 2 | 3 |
4 |
5 | 47 | 48 | 117 |
118 |
119 | -------------------------------------------------------------------------------- /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 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% for line_item in order.line_items %} 32 | 33 | 45 | 46 | 47 | 48 | 49 | 50 | {% endfor %} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {% for discount in order.discounts %} 59 | 60 | 61 | 62 | 63 | {% endfor %} 64 | 65 | {% for shipping_method in order.shipping_methods %} 66 | 67 | 68 | 69 | 70 | {% endfor %} 71 | 72 | {% for tax_line in order.tax_lines %} 73 | 74 | 75 | 76 | 77 | {% endfor %} 78 | 79 | 80 | 81 | 82 | 83 | 84 |
{{ 'customer.order.product' | t }}{{ 'customer.order.sku' | t }}{{ 'customer.order.price' | t }}{{ 'customer.order.quantity' | t }}{{ 'customer.order.total' | t }}
34 | {{ line_item.title | link_to: line_item.product.url }} 35 | {% if line_item.fulfillment %} 36 |
37 | {%- assign created_at = line_item.fulfillment.created_at | date: format: 'month_day_year' -%} 38 | {{ 'customer.order.fulfilled_at' | t: date: created_at }} 39 | {% if line_item.fulfillment.tracking_number %} 40 | {{ line_item.fulfillment.tracking_company }} #{{ line_item.fulfillment.tracking_number}} 41 | {% endif %} 42 |
43 | {% endif %} 44 |
{{ line_item.sku }}{{ line_item.price | money }}{{ line_item.quantity }}{{ line_item.quantity | times: line_item.price | money }}
{{ 'customer.order.subtotal' | t }}{{ order.subtotal_price | money }}
{{ discount.code }} {{ 'customer.order.discount' | t }}{{ discount.savings | money }}
{{ 'customer.order.shipping' | t }} ({{ shipping_method.title }}){{ shipping_method.price | money }}
{{ 'customer.order.tax' | t }} ({{ tax_line.title }} {{ tax_line.rate | times: 100 }}%){{ tax_line.price | money }}
{{ 'customer.order.total' | t }}{{ order.total_price | money }} {{ order.currency }}
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 | 54 |
55 |
56 | -------------------------------------------------------------------------------- /src/templates/customers/reset_password.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% form 'reset_customer_password' %} 3 | 4 |

{{ 'customer.reset_password.title' | t }}

5 | 6 |

{{ 'customer.reset_password.subtext' | t: email: email }}

7 | 8 | {{ form.errors | default_errors }} 9 | 10 | 13 | 18 | 19 | 22 | 27 | 28 | 29 | {% endform %} 30 |
31 | -------------------------------------------------------------------------------- /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 |
11 | 14 |
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 | Gift card illustration 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 | {{ 'gift_cards.issued.add_to_apple_wallet' | t }} 56 | 57 | {% endif %} 58 | 59 | 60 | {{ 'gift_cards.issued.print' | t }} 61 | 62 | 63 |
64 | -------------------------------------------------------------------------------- /src/templates/index.liquid: -------------------------------------------------------------------------------- 1 |
2 | {{ content_for_index }} 3 |
-------------------------------------------------------------------------------- /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 %}{% cycle 1, 2, 3, 4, 5, 6 %}{% endcapture %} 16 | {{ 'collection-' | append: current | placeholder_svg_tag: 'placeholder-svg placeholder-svg--small' }} 17 | {% endif %} 18 | 19 | 20 |

21 | {{ collection.title }} 22 |

23 | {% endunless %} 24 | {% endfor %} 25 | -------------------------------------------------------------------------------- /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 | 18 | 23 | 24 | 25 | 33 | 34 | 35 | 41 | 42 | 43 | 51 | 52 | 53 | 54 | {% endform %} 55 | -------------------------------------------------------------------------------- /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 | 30 | 37 | 41 | {% endif %} 42 | {% endform %} 43 | 44 | {% if settings.share_facebook or settings.share_twitter %} 45 |

{{ 'general.password_page.spread_the_word' | t }}

46 | {% include 'social-sharing' %} 47 | {% endif %} 48 | 49 | 50 | {% include 'icon-lock' %} 51 | {{ 'general.password_page.password_link' | t }} → 52 | 53 | -------------------------------------------------------------------------------- /src/templates/product.liquid: -------------------------------------------------------------------------------- 1 | {% section 'product' %} 2 | -------------------------------------------------------------------------------- /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 | 19 | 24 | 28 |
29 | 30 | 31 | {% if search.performed %} 32 | 74 | 75 | {% if paginate.pages > 1 %} 76 | {% include 'pagination' %} 77 | {% endif %} 78 | {% endif %} 79 | 80 | {% endpaginate %} 81 | -------------------------------------------------------------------------------- /theme.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | {} 3 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 5 | const p = process.env.NODE_ENV === 'production' 6 | 7 | module.exports = { 8 | target: 'web', 9 | devtool: 'source-map', 10 | entry: path.join(__dirname, 'src/scripts/index.js'), 11 | output: { 12 | path: path.join(__dirname, 'src/assets'), 13 | filename: 'index.js' 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | enforce: 'pre', 19 | test: /\.js?$/, 20 | loader: 'standard-loader', 21 | exclude: /node_modules/, 22 | options: { 23 | parser: 'babel-eslint' 24 | } 25 | }, 26 | { 27 | test: /\.js$/, 28 | exclude: /node_modules/, 29 | include: path.join(__dirname, 'src/scripts'), 30 | loaders: ['babel-loader'] 31 | } 32 | ] 33 | }, 34 | resolve: { 35 | alias: { 36 | slater: path.join(__dirname, 'src/scripts/', 'slater'), 37 | micromanagerRoot: path.join(__dirname, 'src/scripts'), 38 | components: path.join(__dirname, 'src/scripts/', 'components'), 39 | pages: path.join(__dirname, 'src/scripts/', 'pages'), 40 | templates: path.join(__dirname, 'src/scripts/', 'templates'), 41 | lib: path.join(__dirname, 'src/scripts/', 'lib') 42 | } 43 | }, 44 | plugins: p ? [ 45 | new webpack.NoEmitOnErrorsPlugin(), 46 | new LodashModuleReplacementPlugin(), 47 | new UglifyJsPlugin(), 48 | new webpack.optimize.OccurrenceOrderPlugin() 49 | ] : [] 50 | }; 51 | --------------------------------------------------------------------------------