This commit is contained in:
Tony Tkáčik 2021-03-06 12:06:10 +01:00
commit 9ef04b8f7e
78 changed files with 26668 additions and 0 deletions

14
.dependabot/config.yml Normal file
View file

@ -0,0 +1,14 @@
version: 1
update_configs:
- package_manager: javascript
directory: /
update_schedule: live
allowed_updates:
- match:
update_type: security
automerged_updates:
- match:
dependency_type: all
update_type: in_range
version_requirement_updates: widen_ranges

50
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,50 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
---
name: Bug report
about: Create a report to help us improve
---
<!-- Please don't delete this template because we'll close your issue -->
<!-- Before creating an issue please make sure you are using the latest version of the starter. -->
<!-- This project is starter project using Gatsby and NetlifyCMS in it, if you think the issue can originate from upstream then please report it-->
# Bug report
<!-- Please ask questions on Spectrum for Gatsby questions or the Gitter channel for NetlifyCMS. -->
<!-- https://spectrum.chat/?t=da07ec65-96f9-41be-baf0-0271b5b772ef -->
<!-- https://gitter.im/netlify/NetlifyCMS -->
<!-- Issues which contain questions or support requests will be closed. -->
**What is the current behavior?**
**If the current behavior is a bug, please provide the steps to reproduce.**
<!-- A great way to do this is to provide your configuration via a GitHub repository -->
<!-- The most helpful is a minimal reproduction with instructions on how to reproduce -->
<!-- Please only add small code snippets directly into this issue -->
<!-- https://gist.github.com is a good place for longer code snippets -->
<!-- If your issue is caused by a plugin or loader, please create an issue on the loader/plugin repository instead -->
**What is the expected behavior?**
<!-- "It should work" is not a helpful explanation -->
<!-- Explain exactly how it should behave -->
**Other relevant information:**
<!--Run `gatsby info --clipboard` in your project directory and paste the output here. Not working? You may need to update your global gatsby-cli - `npm install -g gatsby-cli` -->
Node.js version:
NPM/Yarn version
Operating System:
Additional tools:

18
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View file

@ -0,0 +1,18 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---
---
name: Other
about: Something else
---
<!-- Bug reports and Feature requests must use other templates, or will be closed -->
<!-- Please ask questions on the NetlifyCMS Gitter channel (https://gitter.im/netlify/NetlifyCMS). -->
<!-- Issues which contain questions or support requests will be closed. -->

View file

@ -0,0 +1,36 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
---
name: Feature request
about: Suggest an idea for this project
---
<!-- Please don't delete this template or we'll close your issue -->
## Feature request
<!-- Issues which contain questions or support requests will be closed. -->
<!-- Before creating an issue please make sure you are using the latest version of webpack. -->
<!-- Check if this feature need to be implemented in a plugin or loader instead -->
<!-- If yes: file the issue on the plugin/loader repo -->
<!-- Features related to the development server should be filed on this repo instead -->
**What is the expected behavior?**
**What is motivation or use case for adding/changing the behavior?**
**How should this be implemented in your opinion?**
**Are you willing to work on this yourself?**
yes

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# Project dependencies
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
.cache/
# Build directory
public/
static/admin/*.bundle.*
.DS_Store
yarn-error.log

76
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at david@netlify.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

103
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,103 @@
# CONTRIBUTING
Contributions are always welcome, no matter how large or small. Before contributing,
please read the [code of conduct](CODE_OF_CONDUCT.md).
## Setup
> Install yarn on your system: [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install)
### Install dependencies
> Only required on the first run, subsequent runs can use `yarn` to both
bootstrap and run the development server using `yarn develop`.
Since this starter using the [netlify-dev](https://www.netlify.com/products/dev/#how-it-works), there could be further issues you, please check the [netlify-dev](https://github.com/netlify/netlify-dev) repository for further information and set up questions.
```sh
$ git clone https://github.com/netlify-templates/gatsby-starter-netlify-cms
$ yarn
```
## Available scripts
### `build`
Build the static files into the `public` folder, turns lambda functions into a deployable form.
#### Usage
```sh
$ yarn build
```
### `clean`
Runs `gatsby clean` command.
#### Usage
```sh
yarn clean
```
### `netlify dev`
Starts the netlify dev environment, including the gatsby dev environment.
For more infor check the [Netlify Dev Docs](https://github.com/netlify/cli/blob/master/docs/netlify-dev.md)
```sh
netlify dev
```
### `develop` or `start`
Runs the `clean` script and starts the gatsby develop server using the command `gatsby develop`. We recomend using this command when you don't need Netlify specific features
#### Usage
```sh
yarn develop
```
### `test`
Not implmented yet
#### Usage
```sh
yarn test
```
### `format`
Formats code and docs according to our style guidelines using `prettier`
#### Usage
```sh
yarn format
```
## Pull Requests
We actively welcome your pull requests!
If you need help with Git or our workflow, please ask on [Gitter.im](https://gitter.im/netlify/NetlifyCMS). We want your contributions even if you're just learning Git. Our maintainers are happy to help!
Netlify CMS uses the [Forking Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) + [Feature Branches](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow). Additionally, PR's should be [rebased](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) on master when opened, and again before merging.
1. Fork the repo.
2. Create a branch from `master`. If you're addressing a specific issue, prefix your branch name with the issue number.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Run `yarn test` and ensure the test suite passes. (Not applicable yet)
5. Use `yarn format` to format and lint your code.
6. PR's must be rebased before merge (feel free to ask for help).
7. PR should be reviewed by two maintainers prior to merging.
## License
By contributing to the Gatsby - Netlify CMS starter, you agree that your contributions will be licensed
under its [MIT license](LICENSE).

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 gatsbyjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

19
PULL_REQUEST_TEMPLATE.md Normal file
View file

@ -0,0 +1,19 @@
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
<!-- Try to link to an open issue for more information. -->
<!-- In addition to that please answer these questions: -->
**What kind of change does this PR introduce?**
<!-- E.g. a bugfix, feature, refactoring, build related change, etc… -->
**Does this PR introduce a breaking change?**
<!-- If this PR introduces a breaking change, please describe the impact and a migration path for existing applications. -->
**What needs to be documented once your changes are merged?**
<!-- List all the information that needs to be added to the documentation after merge -->
<!-- When your changes are merged you will be asked to contribute this to the documentation -->

126
README.md Normal file
View file

@ -0,0 +1,126 @@
# Gatsby + Netlify CMS Starter
[![Netlify Status](https://api.netlify.com/api/v1/badges/b654c94e-08a6-4b79-b443-7837581b1d8d/deploy-status)](https://app.netlify.com/sites/gatsby-starter-netlify-cms-ci/deploys)
**Note:** This starter uses [Gatsby v2](https://www.gatsbyjs.org/blog/2018-09-17-gatsby-v2/).
This repo contains an example business website that is built with [Gatsby](https://www.gatsbyjs.org/), and [Netlify CMS](https://www.netlifycms.org): **[Demo Link](https://gatsby-netlify-cms.netlify.com/)**.
It follows the [JAMstack architecture](https://jamstack.org) by using Git as a single source of truth, and [Netlify](https://www.netlify.com) for continuous deployment, and CDN distribution.
## Features
- A simple landing page with blog functionality built with Netlify CMS
- Editable Pages: Landing, About, Product, Blog-Collection and Contact page with Netlify Form support
- Create Blog posts from Netlify CMS
- Tags: Separate page for posts under each tag
- Basic directory organization
- Uses Bulma for styling, but size is reduced by `purge-css-plugin`
- Blazing fast loading times thanks to pre-rendered HTML and automatic chunk loading of JS files
- Uses `gatsby-image` with Netlify-CMS preview support
- Separate components for everything
- Netlify deploy configuration
- Netlify function support, see `lambda` folder
- Perfect score on Lighthouse for SEO, Accessibility and Performance (wip:PWA)
- ..and more
## Prerequisites
- Node (I recommend using v8.2.0 or higher)
- [Gatsby CLI](https://www.gatsbyjs.org/docs/)
- [Netlify CLI](https://github.com/netlify/cli)
## Getting Started (Recommended)
Netlify CMS can run in any frontend web environment, but the quickest way to try it out is by running it on a pre-configured starter site with Netlify. The example here is the Kaldi coffee company template (adapted from [One Click Hugo CMS](https://github.com/netlify-templates/one-click-hugo-cms)). Use the button below to build and deploy your own copy of the repository:
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/netlify-templates/gatsby-starter-netlify-cms&amp;stack=cms"><img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify"></a>
After clicking that button, youll authenticate with GitHub and choose a repository name. Netlify will then automatically create a repository in your GitHub account with a copy of the files from the template. Next, it will build and deploy the new site on Netlify, bringing you to the site dashboard when the build is complete. Next, youll need to set up Netlifys Identity service to authorize users to log in to the CMS.
### Access Locally
Pulldown a local copy of the Github repository Netlify created for you, with the name you specified in the previous step
```
$ git clone https://github.com/[GITHUB_USERNAME]/[REPO_NAME].git
$ cd [REPO_NAME]
$ yarn
$ netlify dev # or ntl dev
```
This uses the new [Netlify Dev](https://www.netlify.com/products/dev/?utm_source=blog&utm_medium=netlifycms&utm_campaign=devex) CLI feature to serve any functions you have in the `lambda` folder.
To test the CMS locally, you'll need to run a production build of the site:
```
$ npm run build
$ netlify dev # or ntl dev
```
### Media Libraries (installed, but optional)
Media Libraries have been included in this starter as a default. If you are not planning to use `Uploadcare` or `Cloudinary` in your project, you **can** remove them from module import and registration in `src/cms/cms.js`. Here is an example of the lines to comment or remove them your project.
```javascript
import CMS from 'netlify-cms-app'
// import uploadcare from 'netlify-cms-media-library-uploadcare'
// import cloudinary from 'netlify-cms-media-library-cloudinary'
import AboutPagePreview from './preview-templates/AboutPagePreview'
import BlogPostPreview from './preview-templates/BlogPostPreview'
import ProductPagePreview from './preview-templates/ProductPagePreview'
import IndexPagePreview from './preview-templates/IndexPagePreview'
// CMS.registerMediaLibrary(uploadcare);
// CMS.registerMediaLibrary(cloudinary);
CMS.registerPreviewTemplate('index', IndexPagePreview)
CMS.registerPreviewTemplate('about', AboutPagePreview)
CMS.registerPreviewTemplate('products', ProductPagePreview)
CMS.registerPreviewTemplate('blog', BlogPostPreview)
```
Note: Don't forget to also remove them from `package.json` and `yarn.lock` / `package-lock.json` using `yarn` or `npm`. During the build netlify-cms-app will bundle the media libraries as well, having them removed will save you build time.
Example:
```
yarn remove netlify-cms-media-library-uploadcare
```
OR
```
yarn remove netlify-cms-media-library-cloudinary
```
## Getting Started (Without Netlify)
```
$ gatsby new [SITE_DIRECTORY_NAME] https://github.com/netlify-templates/gatsby-starter-netlify-cms/
$ cd [SITE_DIRECTORY_NAME]
$ npm run build
$ npm run serve
```
### Setting up the CMS
Follow the [Netlify CMS Quick Start Guide](https://www.netlifycms.org/docs/quick-start/#authentication) to set up authentication, and hosting.
## Debugging
Windows users might encounter `node-gyp` errors when trying to npm install.
To resolve, make sure that you have both Python 2.7 and the Visual C++ build environment installed.
```
npm config set python python2.7
npm install --global --production windows-build-tools
```
[Full details here](https://www.npmjs.com/package/node-gyp 'NPM node-gyp page')
MacOS users might also encounter some errors, for more info check [node-gyp](https://github.com/nodejs/node-gyp). We recommend using the latest stable node version.
## Purgecss
This plugin uses [gatsby-plugin-purgecss](https://www.gatsbyjs.org/packages/gatsby-plugin-purgecss/) and [bulma](https://bulma.io/). The bulma builds are usually ~170K but reduced 90% by purgecss.
# CONTRIBUTING
Contributions are always welcome, no matter how large or small. Before contributing,
please read the [code of conduct](CODE_OF_CONDUCT.md).

2
_headers Normal file
View file

@ -0,0 +1,2 @@
/static/*
Cache-Control: "public, max-age=360000"

77
gatsby-config.js Normal file
View file

@ -0,0 +1,77 @@
module.exports = {
siteMetadata: {
title: 'Gatsby + Netlify CMS Starter',
description:
'This repo contains an example business website that is built with Gatsby, and Netlify CMS.It follows the JAMstack architecture by using Git as a single source of truth, and Netlify for continuous deployment, and CDN distribution.',
},
plugins: [
'gatsby-plugin-react-helmet',
'gatsby-plugin-sass',
{
// keep as first gatsby-source-filesystem plugin for gatsby image support
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/static/img`,
name: 'uploads',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/pages`,
name: 'pages',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/img`,
name: 'images',
},
},
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
{
resolve: 'gatsby-remark-relative-images',
options: {
name: 'uploads',
},
},
{
resolve: 'gatsby-remark-images',
options: {
// It's important to specify the maxWidth (in pixels) of
// the content container as this plugin uses this as the
// base for generating different widths of each image.
maxWidth: 2048,
},
},
{
resolve: 'gatsby-remark-copy-linked-files',
options: {
destinationDir: 'static',
},
},
],
},
},
{
resolve: 'gatsby-plugin-netlify-cms',
options: {
modulePath: `${__dirname}/src/cms/cms.js`,
},
},
{
resolve: 'gatsby-plugin-purgecss', // purges all unused/unreferenced css rules
options: {
develop: true, // Activates purging in npm run develop
purgeOnly: ['/all.sass'], // applies purging only on the bulma css file
},
}, // must be after other CSS plugins
'gatsby-plugin-netlify', // make sure to keep it last in the array
],
}

87
gatsby-node.js Normal file
View file

@ -0,0 +1,87 @@
const _ = require('lodash')
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const { fmImagesToRelative } = require('gatsby-remark-relative-images')
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions
return graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
id
fields {
slug
}
frontmatter {
tags
templateKey
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()))
return Promise.reject(result.errors)
}
const posts = result.data.allMarkdownRemark.edges
posts.forEach((edge) => {
const id = edge.node.id
createPage({
path: edge.node.fields.slug,
tags: edge.node.frontmatter.tags,
component: path.resolve(
`src/templates/${String(edge.node.frontmatter.templateKey)}.js`
),
// additional data can be passed via context
context: {
id,
},
})
})
// Tag pages:
let tags = []
// Iterate through each post, putting all found tags into `tags`
posts.forEach((edge) => {
if (_.get(edge, `node.frontmatter.tags`)) {
tags = tags.concat(edge.node.frontmatter.tags)
}
})
// Eliminate duplicate tags
tags = _.uniq(tags)
// Make tag pages
tags.forEach((tag) => {
const tagPath = `/tags/${_.kebabCase(tag)}/`
createPage({
path: tagPath,
component: path.resolve(`src/templates/tags.js`),
context: {
tag,
},
})
})
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
fmImagesToRelative(node) // convert image paths for gatsby images
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}

17
lambda/hello.js Normal file
View file

@ -0,0 +1,17 @@
// For more info, check https://docs.netlify.com/functions/build-with-javascript
module.exports.handler = async function(event, context) {
console.log("queryStringParameters", event.queryStringParameters)
return {
// return null to show no errors
statusCode: 200, // http status code
body: JSON.stringify({
msg: "Hello, World! This is better " + Math.round(Math.random() * 10)
})
}
}
// Now you are ready to access this API from anywhere in your Gatsby app! For example, in any event handler or lifecycle method, insert:
// fetch("/.netlify/functions/hello")
// .then(response => response.json())
// .then(console.log)
// For more info see: https://www.gatsbyjs.org/blog/2018-12-17-turning-the-static-dynamic/#static-dynamic-is-a-spectrum

8
netlify.toml Normal file
View file

@ -0,0 +1,8 @@
[build]
publish = "public"
command = "npm run build"
functions = "lambda"
[build.environment]
NODE_VERSION = "12"
YARN_VERSION = "1.22.4"
YARN_FLAGS = "--no-ignore-optional"

23646
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

50
package.json Normal file
View file

@ -0,0 +1,50 @@
{
"name": "gatsby-starter-netlify-cms",
"description": "Example Gatsby, and Netlify CMS project",
"version": "1.1.3",
"author": "Austin Green",
"dependencies": {
"bulma": "^0.9.0",
"gatsby": "^2.20.35",
"gatsby-image": "^2.3.5",
"gatsby-plugin-netlify": "^2.2.4",
"gatsby-plugin-netlify-cms": "^4.2.5",
"gatsby-plugin-purgecss": "^5.0.0",
"gatsby-plugin-react-helmet": "^3.2.5",
"gatsby-plugin-sass": "^2.2.4",
"gatsby-plugin-sharp": "^2.5.7",
"gatsby-remark-copy-linked-files": "^2.2.4",
"gatsby-remark-images": "^3.2.6",
"gatsby-remark-relative-images": "^0.3.0",
"gatsby-source-filesystem": "^2.2.5",
"gatsby-transformer-remark": "^2.7.5",
"gatsby-transformer-sharp": "^2.4.7",
"lodash": "^4.17.15",
"lodash-webpack-plugin": "^0.11.4",
"netlify-cms-app": "^2.14.26",
"netlify-cms-media-library-cloudinary": "^1.3.10",
"netlify-cms-media-library-uploadcare": "^0.5.10",
"node-sass": "^4.14.0",
"prop-types": "^15.6.0",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-helmet": "^6.0.0",
"uuid": "^7.0.0"
},
"keywords": [
"gatsby"
],
"license": "MIT",
"main": "n/a",
"scripts": {
"clean": "gatsby clean",
"start": "npm run develop",
"build": "npm run clean && gatsby build",
"develop": "npm run clean && gatsby develop",
"format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"prettier": "^2.0.5"
}
}

3
renovate.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": ["github>netlify/renovate-config:netlify-cms-starter"]
}

16
src/cms/cms.js Normal file
View file

@ -0,0 +1,16 @@
import CMS from 'netlify-cms-app'
import uploadcare from 'netlify-cms-media-library-uploadcare'
import cloudinary from 'netlify-cms-media-library-cloudinary'
import AboutPagePreview from './preview-templates/AboutPagePreview'
import BlogPostPreview from './preview-templates/BlogPostPreview'
import ProductPagePreview from './preview-templates/ProductPagePreview'
import IndexPagePreview from './preview-templates/IndexPagePreview'
CMS.registerMediaLibrary(uploadcare)
CMS.registerMediaLibrary(cloudinary)
CMS.registerPreviewTemplate('index', IndexPagePreview)
CMS.registerPreviewTemplate('about', AboutPagePreview)
CMS.registerPreviewTemplate('products', ProductPagePreview)
CMS.registerPreviewTemplate('blog', BlogPostPreview)

View file

@ -0,0 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { AboutPageTemplate } from '../../templates/about-page'
const AboutPagePreview = ({ entry, widgetFor }) => (
<AboutPageTemplate
title={entry.getIn(['data', 'title'])}
content={widgetFor('body')}
/>
)
AboutPagePreview.propTypes = {
entry: PropTypes.shape({
getIn: PropTypes.func,
}),
widgetFor: PropTypes.func,
}
export default AboutPagePreview

View file

@ -0,0 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import { BlogPostTemplate } from '../../templates/blog-post'
const BlogPostPreview = ({ entry, widgetFor }) => {
const tags = entry.getIn(['data', 'tags'])
return (
<BlogPostTemplate
content={widgetFor('body')}
description={entry.getIn(['data', 'description'])}
tags={tags && tags.toJS()}
title={entry.getIn(['data', 'title'])}
/>
)
}
BlogPostPreview.propTypes = {
entry: PropTypes.shape({
getIn: PropTypes.func,
}),
widgetFor: PropTypes.func,
}
export default BlogPostPreview

View file

@ -0,0 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import { IndexPageTemplate } from '../../templates/index-page'
const IndexPagePreview = ({ entry, getAsset }) => {
const data = entry.getIn(['data']).toJS()
if (data) {
return (
<IndexPageTemplate
image={getAsset(data.image)}
title={data.title}
heading={data.heading}
subheading={data.subheading}
description={data.description}
intro={data.intro || { blurbs: [] }}
mainpitch={data.mainpitch || {}}
/>
)
} else {
return <div>Loading...</div>
}
}
IndexPagePreview.propTypes = {
entry: PropTypes.shape({
getIn: PropTypes.func,
}),
getAsset: PropTypes.func,
}
export default IndexPagePreview

View file

@ -0,0 +1,56 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ProductPageTemplate } from '../../templates/product-page'
const ProductPagePreview = ({ entry, getAsset }) => {
const entryBlurbs = entry.getIn(['data', 'intro', 'blurbs'])
const blurbs = entryBlurbs ? entryBlurbs.toJS() : []
const entryTestimonials = entry.getIn(['data', 'testimonials'])
const testimonials = entryTestimonials ? entryTestimonials.toJS() : []
const entryPricingPlans = entry.getIn(['data', 'pricing', 'plans'])
const pricingPlans = entryPricingPlans ? entryPricingPlans.toJS() : []
return (
<ProductPageTemplate
image={getAsset(entry.getIn(['data', 'image']))}
title={entry.getIn(['data', 'title'])}
heading={entry.getIn(['data', 'heading'])}
description={entry.getIn(['data', 'description'])}
intro={{ blurbs }}
main={{
heading: entry.getIn(['data', 'main', 'heading']),
description: entry.getIn(['data', 'main', 'description']),
image1: {
image: getAsset(entry.getIn(['data', 'main', 'image1', 'image'])),
alt: entry.getIn(['data', 'main', 'image1', 'alt']),
},
image2: {
image: getAsset(entry.getIn(['data', 'main', 'image2', 'image'])),
alt: entry.getIn(['data', 'main', 'image2', 'alt']),
},
image3: {
image: getAsset(entry.getIn(['data', 'main', 'image3', 'image'])),
alt: entry.getIn(['data', 'main', 'image3', 'alt']),
},
}}
fullImage={entry.getIn(['data', 'full_image'])}
testimonials={testimonials}
pricing={{
heading: entry.getIn(['data', 'pricing', 'heading']),
description: entry.getIn(['data', 'pricing', 'description']),
plans: pricingPlans,
}}
/>
)
}
ProductPagePreview.propTypes = {
entry: PropTypes.shape({
getIn: PropTypes.func,
}),
getAsset: PropTypes.func,
}
export default ProductPagePreview

104
src/components/BlogRoll.js Normal file
View file

@ -0,0 +1,104 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Link, graphql, StaticQuery } from 'gatsby'
import PreviewCompatibleImage from './PreviewCompatibleImage'
class BlogRoll extends React.Component {
render() {
const { data } = this.props
const { edges: posts } = data.allMarkdownRemark
return (
<div className="columns is-multiline">
{posts &&
posts.map(({ node: post }) => (
<div className="is-parent column is-6" key={post.id}>
<article
className={`blog-list-item tile is-child box notification ${
post.frontmatter.featuredpost ? 'is-featured' : ''
}`}
>
<header>
{post.frontmatter.featuredimage ? (
<div className="featured-thumbnail">
<PreviewCompatibleImage
imageInfo={{
image: post.frontmatter.featuredimage,
alt: `featured image thumbnail for post ${post.frontmatter.title}`,
}}
/>
</div>
) : null}
<p className="post-meta">
<Link
className="title has-text-primary is-size-4"
to={post.fields.slug}
>
{post.frontmatter.title}
</Link>
<span> &bull; </span>
<span className="subtitle is-size-5 is-block">
{post.frontmatter.date}
</span>
</p>
</header>
<p>
{post.excerpt}
<br />
<br />
<Link className="button" to={post.fields.slug}>
Keep Reading
</Link>
</p>
</article>
</div>
))}
</div>
)
}
}
BlogRoll.propTypes = {
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
edges: PropTypes.array,
}),
}),
}
export default () => (
<StaticQuery
query={graphql`
query BlogRollQuery {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
filter: { frontmatter: { templateKey: { eq: "blog-post" } } }
) {
edges {
node {
excerpt(pruneLength: 400)
id
fields {
slug
}
frontmatter {
title
templateKey
date(formatString: "MMMM DD, YYYY")
featuredpost
featuredimage {
childImageSharp {
fluid(maxWidth: 120, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
}
}
`}
render={(data, count) => <BlogRoll data={data} count={count} />}
/>
)

19
src/components/Content.js Normal file
View file

@ -0,0 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
export const HTMLContent = ({ content, className }) => (
<div className={className} dangerouslySetInnerHTML={{ __html: content }} />
)
const Content = ({ content, className }) => (
<div className={className}>{content}</div>
)
Content.propTypes = {
content: PropTypes.node,
className: PropTypes.string,
}
HTMLContent.propTypes = Content.propTypes
export default Content

View file

@ -0,0 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import PreviewCompatibleImage from '../components/PreviewCompatibleImage'
const FeatureGrid = ({ gridItems }) => (
<div className="columns is-multiline">
{gridItems.map((item) => (
<div key={item.text} className="column is-6">
<section className="section">
<div className="has-text-centered">
<div
style={{
width: '240px',
display: 'inline-block',
}}
>
<PreviewCompatibleImage imageInfo={item} />
</div>
</div>
<p>{item.text}</p>
</section>
</div>
))}
</div>
)
FeatureGrid.propTypes = {
gridItems: PropTypes.arrayOf(
PropTypes.shape({
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
text: PropTypes.string,
})
),
}
export default FeatureGrid

115
src/components/Footer.js Normal file
View file

@ -0,0 +1,115 @@
import React from 'react'
import { Link } from 'gatsby'
import logo from '../img/logo.svg'
import facebook from '../img/social/facebook.svg'
import instagram from '../img/social/instagram.svg'
import twitter from '../img/social/twitter.svg'
import vimeo from '../img/social/vimeo.svg'
const Footer = class extends React.Component {
render() {
return (
<footer className="footer has-background-black has-text-white-ter">
<div className="content has-text-centered">
<img
src={logo}
alt="Kaldi"
style={{ width: '14em', height: '10em' }}
/>
</div>
<div className="content has-text-centered has-background-black has-text-white-ter">
<div className="container has-background-black has-text-white-ter">
<div style={{ maxWidth: '100vw' }} className="columns">
<div className="column is-4">
<section className="menu">
<ul className="menu-list">
<li>
<Link to="/" className="navbar-item">
Home
</Link>
</li>
<li>
<Link className="navbar-item" to="/about">
About
</Link>
</li>
<li>
<Link className="navbar-item" to="/products">
Products
</Link>
</li>
<li>
<Link className="navbar-item" to="/contact/examples">
Form Examples
</Link>
</li>
<li>
<a
className="navbar-item"
href="/admin/"
target="_blank"
rel="noopener noreferrer"
>
Admin
</a>
</li>
</ul>
</section>
</div>
<div className="column is-4">
<section>
<ul className="menu-list">
<li>
<Link className="navbar-item" to="/blog">
Latest Stories
</Link>
</li>
<li>
<Link className="navbar-item" to="/contact">
Contact
</Link>
</li>
</ul>
</section>
</div>
<div className="column is-4 social">
<a title="facebook" href="https://facebook.com">
<img
src={facebook}
alt="Facebook"
style={{ width: '1em', height: '1em' }}
/>
</a>
<a title="twitter" href="https://twitter.com">
<img
className="fas fa-lg"
src={twitter}
alt="Twitter"
style={{ width: '1em', height: '1em' }}
/>
</a>
<a title="instagram" href="https://instagram.com">
<img
src={instagram}
alt="Instagram"
style={{ width: '1em', height: '1em' }}
/>
</a>
<a title="vimeo" href="https://vimeo.com">
<img
src={vimeo}
alt="Vimeo"
style={{ width: '1em', height: '1em' }}
/>
</a>
</div>
</div>
</div>
</div>
</footer>
)
}
}
export default Footer

58
src/components/Layout.js Normal file
View file

@ -0,0 +1,58 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import Footer from '../components/Footer'
import Navbar from '../components/Navbar'
import './all.sass'
import useSiteMetadata from './SiteMetadata'
import { withPrefix } from 'gatsby'
const TemplateWrapper = ({ children }) => {
const { title, description } = useSiteMetadata()
return (
<div>
<Helmet>
<html lang="en" />
<title>{title}</title>
<meta name="description" content={description} />
<link
rel="apple-touch-icon"
sizes="180x180"
href={`${withPrefix('/')}img/apple-touch-icon.png`}
/>
<link
rel="icon"
type="image/png"
href={`${withPrefix('/')}img/favicon-32x32.png`}
sizes="32x32"
/>
<link
rel="icon"
type="image/png"
href={`${withPrefix('/')}img/favicon-16x16.png`}
sizes="16x16"
/>
<link
rel="mask-icon"
href={`${withPrefix('/')}img/safari-pinned-tab.svg`}
color="#ff4400"
/>
<meta name="theme-color" content="#fff" />
<meta property="og:type" content="business.business" />
<meta property="og:title" content={title} />
<meta property="og:url" content="/" />
<meta
property="og:image"
content={`${withPrefix('/')}img/og-image.jpg`}
/>
</Helmet>
<Navbar />
<div>{children}</div>
<Footer />
</div>
)
}
export default TemplateWrapper

98
src/components/Navbar.js Normal file
View file

@ -0,0 +1,98 @@
import React from 'react'
import { Link } from 'gatsby'
import github from '../img/github-icon.svg'
import logo from '../img/logo.svg'
const Navbar = class extends React.Component {
constructor(props) {
super(props)
this.state = {
active: false,
navBarActiveClass: '',
}
}
toggleHamburger = () => {
// toggle the active boolean in the state
this.setState(
{
active: !this.state.active,
},
// after state has been updated,
() => {
// set the class in state for the navbar accordingly
this.state.active
? this.setState({
navBarActiveClass: 'is-active',
})
: this.setState({
navBarActiveClass: '',
})
}
)
}
render() {
return (
<nav
className="navbar is-transparent"
role="navigation"
aria-label="main-navigation"
>
<div className="container">
<div className="navbar-brand">
<Link to="/" className="navbar-item" title="Logo">
<img src={logo} alt="Kaldi" style={{ width: '88px' }} />
</Link>
{/* Hamburger menu */}
<div
className={`navbar-burger burger ${this.state.navBarActiveClass}`}
data-target="navMenu"
onClick={() => this.toggleHamburger()}
>
<span />
<span />
<span />
</div>
</div>
<div
id="navMenu"
className={`navbar-menu ${this.state.navBarActiveClass}`}
>
<div className="navbar-start has-text-centered">
<Link className="navbar-item" to="/about">
About
</Link>
<Link className="navbar-item" to="/products">
Products
</Link>
<Link className="navbar-item" to="/blog">
Blog
</Link>
<Link className="navbar-item" to="/contact">
Contact
</Link>
<Link className="navbar-item" to="/contact/examples">
Form Examples
</Link>
</div>
<div className="navbar-end has-text-centered">
<a
className="navbar-item"
href="https://github.com/netlify-templates/gatsby-starter-netlify-cms"
target="_blank"
rel="noopener noreferrer"
>
<span className="icon">
<img src={github} alt="Github" />
</span>
</a>
</div>
</div>
</div>
</nav>
)
}
}
export default Navbar

View file

@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'
import Img from 'gatsby-image'
const PreviewCompatibleImage = ({ imageInfo }) => {
const imageStyle = { borderRadius: '5px' }
const { alt = '', childImageSharp, image } = imageInfo
if (!!image && !!image.childImageSharp) {
return (
<Img style={imageStyle} fluid={image.childImageSharp.fluid} alt={alt} />
)
}
if (!!childImageSharp) {
return <Img style={imageStyle} fluid={childImageSharp.fluid} alt={alt} />
}
if (!!image && typeof image === 'string')
return <img style={imageStyle} src={image} alt={alt} />
return null
}
PreviewCompatibleImage.propTypes = {
imageInfo: PropTypes.shape({
alt: PropTypes.string,
childImageSharp: PropTypes.object,
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
style: PropTypes.object,
}).isRequired,
}
export default PreviewCompatibleImage

40
src/components/Pricing.js Normal file
View file

@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'
const Pricing = ({ data }) => (
<div className="columns">
{data.map((price) => (
<div key={price.plan} className="column">
<section className="section">
<h4 className="has-text-centered has-text-weight-semibold">
{price.plan}
</h4>
<h2 className="is-size-1 has-text-weight-bold has-text-primary has-text-centered">
${price.price}
</h2>
<p className="has-text-weight-semibold">{price.description}</p>
<ul>
{price.items.map((item) => (
<li key={item} className="is-size-5">
{item}
</li>
))}
</ul>
</section>
</div>
))}
</div>
)
Pricing.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
plan: PropTypes.string,
price: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
description: PropTypes.string,
items: PropTypes.array,
})
),
}
export default Pricing

View file

@ -0,0 +1,19 @@
import { graphql, useStaticQuery } from 'gatsby'
const useSiteMetadata = () => {
const { site } = useStaticQuery(
graphql`
query SITE_METADATA_QUERY {
site {
siteMetadata {
title
description
}
}
}
`
)
return site.siteMetadata
}
export default useSiteMetadata

View file

@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import { v4 } from 'uuid'
const Testimonials = ({ testimonials }) => (
<div>
{testimonials.map((testimonial) => (
<article key={v4()} className="message">
<div className="message-body">
{testimonial.quote}
<br />
<cite> {testimonial.author}</cite>
</div>
</article>
))}
</div>
)
Testimonials.propTypes = {
testimonials: PropTypes.arrayOf(
PropTypes.shape({
quote: PropTypes.string,
author: PropTypes.string,
})
),
}
export default Testimonials

140
src/components/all.sass Normal file
View file

@ -0,0 +1,140 @@
@import "~bulma/sass/utilities/initial-variables"
// Config vars
// Kaldi logo brand color: #FD461E
$kaldi-red: #D64000
$kaldi-red-invert: #fff
$primary: $kaldi-red
$primary-invert: $kaldi-red-invert
$body-color: #333
$black: #2b2523
.navbar .navbar-menu
box-shadow:none !important
.content .taglist
list-style: none
margin-bottom: 0
margin-left: 0
margin-right: 1.5rem
margin-top: 1.5rem
display: flex
flex-wrap: wrap
justify-content: left
align-items: center
li
padding: 0 2rem 1rem 0
margin-bottom: 1.5rem
margin-top: 0
// Helper Classes
.full-width-image-container
width: 100vw
height: 400px
position: relative
left: 50%
right: 50%
margin: 2em -50vw
background-size: cover
background-position: bottom
display: flex
justify-content: center
align-items: center
.full-width-image
width: 100vw
height: 400px
background-size: cover
background-position: bottom
display: flex
justify-content: center
align-items: center
.btn
display: inline-block
padding: 12px 16px 10px
font-size: 18px
font-size: 1rem
line-height: 1.25
background-color: #fff
border-radius: .25rem
text-decoration: none
font-weight: 700
color: #CC3700
text-align: center
-webkit-box-shadow: inset 0 0 0 2px #CC3700
box-shadow: inset 0 0 0 2px #f40
-webkit-transition: all .15s ease
transition: all .15s ease
.margin-top-0
margin-top: 0 !important
.navbar-item .icon
color: $primary
// Override for use of svg's from https://simpleicons.org/
.icon svg
width: 1.5rem
height: 1.5rem
fill: currentColor
.navbar-brand .navbar-item.logo
padding: 0 1rem
footer.footer
padding: 3rem 0rem 0rem
background-color: transparent
//Menu overrides
$menu-item-color: $white-ter
$menu-item-hover-color: $black !important
$menu-item-hover-background-color: $black
$menu-item-active-color: $kaldi-red-invert
$menu-item-active-background-color: $kaldi-red-invert
$menu-list-border-left: 1px solid $kaldi-red-invert
$menu-label-color: $white-ter
.menu-label
font-size: 1em !important
text-align: left
.menu-list
list-style: none !important
text-align: left
.social
padding: 2em
.social a
padding: .5em .5em .3em .5em
border-radius: 1em
background-color: $white-ter
margin: .5em
width: 1em
height: 1em
vertical-align: middle
display: inline
// blog roll
.blog-list-item.is-featured
background-color: #d6400033;
.blog-list-item header
display: flex;
margin-bottom: 1em;
.blog-list-item .featured-thumbnail
flex-basis: 35%;
margin: 0 1.5em 0 0;
@import "~bulma"
// responsiveness
+tablet-only
.blog-list-item .featured-thumbnail
flex-basis: 50%;
+mobile
.blog-list-item header
display: block
.blog-list-item .featured-thumbnail
text-align: center;
max-width: 70%;
margin: 0 0 1em;

5
src/img/github-icon.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="438.549px" height="438.549px" viewBox="0 0 438.549 438.549" style="enable-background:new 0 0 438.549 438.549;" xml:space="preserve">
<g>
<path d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365 c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63 c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996 c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136 c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559 c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559 c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997 c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851 c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136 c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41 c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126 c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817 c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994 c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849 c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24 c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979 c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146 c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995 c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906 C438.536,184.851,428.728,148.168,409.132,114.573z"/>
</g>
<div xmlns="" id="divScriptsUsed" style="display: none"/><script xmlns="" id="globalVarsDetection" src="chrome-extension://cmkdbmfndkfgebldhnkbfhlneefdaaip/js/wrs_env.js"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

1
src/img/logo.svg Normal file
View file

@ -0,0 +1 @@
<svg viewBox="0 0 109 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:figma="http://www.figma.com/figma/ns"><title>Logo</title><g transform="translate(-1470)" figma:type="canvas"><g style="mix-blend-mode:normal" figma:type="vector" transform="translate(1470)" fill="#f40"><use xlink:href="#b" style="mix-blend-mode:normal"/><use xlink:href="#c" style="mix-blend-mode:normal"/><use xlink:href="#d" style="mix-blend-mode:normal"/><use xlink:href="#e" style="mix-blend-mode:normal"/><use xlink:href="#f" style="mix-blend-mode:normal"/></g></g><defs><path id="b" d="M22.735 23.171c.283.323.053.829-.376.829h-5.907c-.285 0-.556-.121-.745-.333l-9.414-10.526v10.36c0 .276-.224.5-.5.5h-5.293c-.276 0-.5-.224-.5-.5v-23c0-.276.224-.5.5-.5h5.293c.276 0 .5.224.5.5v9.815l9.141-9.99c.19-.207.457-.325.738-.325h5.762c.437 0 .664.521.366.841l-9.851 10.563 10.287 11.767z"/><path id="c" d="M45.991 24c-.199 0-.38-.118-.459-.301l-2.024-4.669h-10.67l-2.024 4.669c-.079.183-.259.301-.459.301h-5.212c-.366 0-.608-.381-.453-.712l10.782-23c.082-.176.259-.288.453-.288h4.358c.194 0 .37.112.453.287l10.815 23c.156.332-.086.713-.452.713h-5.108zm-11.135-9.668h6.635l-3.317-7.694-3.317 7.694z"/><path id="d" d="M55.525 24c-.276 0-.5-.224-.5-.5v-23c0-.276.224-.5.5-.5h5.293c.276 0 .5.224.5.5v18.428h9.759c.276 0 .5.224.5.5v4.072c0 .276-.224.5-.5.5h-15.552z"/><path id="e" d="M75.279.5c0-.276.224-.5.5-.5h9.315c2.667 0 4.959.477 6.874 1.43 1.938.953 3.42 2.338 4.446 4.153 1.026 1.793 1.539 3.926 1.539 6.4 0 2.496-.513 4.652-1.539 6.468-1.003 1.793-2.474 3.166-4.412 4.119-1.915.953-4.218 1.43-6.908 1.43h-9.315c-.276 0-.5-.224-.5-.5v-23zm9.37 18.462c2.371 0 4.138-.579 5.301-1.736 1.163-1.157 1.744-2.905 1.744-5.242 0-2.338-.581-4.074-1.744-5.209-1.163-1.157-2.93-1.736-5.301-1.736h-3.078v13.923h3.078z"/><path id="f" d="M102.913 24c-.276 0-.5-.224-.5-.5v-23c0-.276.224-.5.5-.5h5.293c.276 0 .5.224.5.5v23c0 .276-.224.5-.5.5h-5.293z"/></defs></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Facebook icon</title><path d="M22.676 0H1.324C.593 0 0 .593 0 1.324v21.352C0 23.408.593 24 1.324 24h11.494v-9.294H9.689v-3.621h3.129V8.41c0-3.099 1.894-4.785 4.659-4.785 1.325 0 2.464.097 2.796.141v3.24h-1.921c-1.5 0-1.792.721-1.792 1.771v2.311h3.584l-.465 3.63H16.56V24h6.115c.733 0 1.325-.592 1.325-1.324V1.324C24 .593 23.408 0 22.676 0"/></svg>

After

Width:  |  Height:  |  Size: 425 B

View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Instagram icon</title><path d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913.306.788.717 1.459 1.384 2.126.667.666 1.336 1.079 2.126 1.384.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558.788-.306 1.459-.718 2.126-1.384.666-.667 1.079-1.335 1.384-2.126.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913-.306-.789-.718-1.459-1.384-2.126C21.319 1.347 20.651.935 19.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227-.224.562-.479.96-.899 1.382-.419.419-.824.679-1.38.896-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421-.569-.224-.96-.479-1.379-.899-.421-.419-.69-.824-.9-1.38-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678c-3.405 0-6.162 2.76-6.162 6.162 0 3.405 2.76 6.162 6.162 6.162 3.405 0 6.162-2.76 6.162-6.162 0-3.405-2.76-6.162-6.162-6.162zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405c0 .795-.646 1.44-1.44 1.44-.795 0-1.44-.646-1.44-1.44 0-.794.646-1.439 1.44-1.439.793-.001 1.44.645 1.44 1.439z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter icon</title><path d="M23.954 4.569c-.885.389-1.83.654-2.825.775 1.014-.611 1.794-1.574 2.163-2.723-.951.555-2.005.959-3.127 1.184-.896-.959-2.173-1.559-3.591-1.559-2.717 0-4.92 2.203-4.92 4.917 0 .39.045.765.127 1.124C7.691 8.094 4.066 6.13 1.64 3.161c-.427.722-.666 1.561-.666 2.475 0 1.71.87 3.213 2.188 4.096-.807-.026-1.566-.248-2.228-.616v.061c0 2.385 1.693 4.374 3.946 4.827-.413.111-.849.171-1.296.171-.314 0-.615-.03-.916-.086.631 1.953 2.445 3.377 4.604 3.417-1.68 1.319-3.809 2.105-6.102 2.105-.39 0-.779-.023-1.17-.067 2.189 1.394 4.768 2.209 7.557 2.209 9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63.961-.689 1.8-1.56 2.46-2.548l-.047-.02z"/></svg>

After

Width:  |  Height:  |  Size: 757 B

1
src/img/social/vimeo.svg Normal file
View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Vimeo icon</title><path d="M23.977 6.416c-.105 2.338-1.739 5.543-4.894 9.609-3.268 4.247-6.026 6.37-8.29 6.37-1.409 0-2.578-1.294-3.553-3.881L5.322 11.4C4.603 8.816 3.834 7.522 3.01 7.522c-.179 0-.806.378-1.881 1.132L0 7.197c1.185-1.044 2.351-2.084 3.501-3.128C5.08 2.701 6.266 1.984 7.055 1.91c1.867-.18 3.016 1.1 3.447 3.838.465 2.953.789 4.789.971 5.507.539 2.45 1.131 3.674 1.776 3.674.502 0 1.256-.796 2.265-2.385 1.004-1.589 1.54-2.797 1.612-3.628.144-1.371-.395-2.061-1.614-2.061-.574 0-1.167.121-1.777.391 1.186-3.868 3.434-5.757 6.762-5.637 2.473.06 3.628 1.664 3.493 4.797l-.013.01z"/></svg>

After

Width:  |  Height:  |  Size: 679 B

13
src/pages/404.js Normal file
View file

@ -0,0 +1,13 @@
import React from 'react'
import Layout from '../components/Layout'
const NotFoundPage = () => (
<Layout>
<div>
<h1>NOT FOUND</h1>
<p>You just hit a route that doesn&#39;t exist... the sadness.</p>
</div>
</Layout>
)
export default NotFoundPage

19
src/pages/about/index.md Normal file
View file

@ -0,0 +1,19 @@
---
templateKey: 'about-page'
path: /about
title: About our values
---
### Shade-grown coffee
Coffee is a small tree or shrub that grows in the forest understory in its wild form, and traditionally was grown commercially under other trees that provided shade. The forest-like structure of shade coffee farms provides habitat for a great number of migratory and resident species.
### Single origin
Single-origin coffee is coffee grown within a single known geographic origin. Sometimes, this is a single farm or a specific collection of beans from a single country. The name of the coffee is then usually the place it was grown to whatever degree available.
### Sustainable farming
Sustainable agriculture is farming in sustainable ways based on an understanding of ecosystem services, the study of relationships between organisms and their environment. What grows where and how it is grown are a matter of choice and careful consideration for nature and communities.
### Direct sourcing
Direct trade is a form of sourcing practiced by some coffee roasters. Advocates of direct trade practices promote direct communication and price negotiation between buyer and farmer, along with systems that encourage and incentivize quality.
### Reinvest profits
We want to truly empower the communities that bring amazing coffee to you. Thats why we reinvest 20% of our profits into farms, local businesses and schools everywhere our coffee is grown. You can see the communities grow and learn more about coffee farming on our blog.

View file

@ -0,0 +1,33 @@
---
templateKey: blog-post
title: Making sense of the SCAAs new Flavor Wheel
date: 2016-12-17T15:04:10.000Z
featuredpost: false
featuredimage: /img/flavor_wheel.jpg
description: The Coffee Tasters Flavor Wheel, the official resource used by coffee tasters, has been revised for the first time this year.
tags:
- flavor
- tasting
---
![flavor wheel](/img/flavor_wheel.jpg)
The SCAA updated the wheel to reflect the finer nuances needed to describe flavors more precisely. The new descriptions are more detailed and hence allow cuppers to distinguish between more flavors.
While this is going to be a big change for professional coffee tasters, it means a lot to you as a consumer as well. Well explain how the wheel came to be, how pros use it and what the flavors actually mean.
## What the updates mean to you
The Specialty Coffee Association of America (SCAA), founded in 1982, is a non-profit trade organization for the specialty coffee industry. With members located in more than 40 countries, SCAA represents every segment of the specialty coffee industry, including:
* producers
* roasters
* importers/exporters
* retailers
* manufacturers
* baristas
For over 30 years, SCAA has been dedicated to creating a vibrant specialty coffee community by recognizing, developing and promoting specialty coffee. SCAA sets and maintains quality standards for the industry, conducts market research, and provides education, training, resources, and business services for its members.
Coffee cupping, or coffee tasting, is the practice of observing the tastes and aromas of brewed coffee. It is a professional practice but can be done informally by anyone or by professionals known as "Q Graders". A standard coffee cupping procedure involves deeply sniffing the coffee, then loudly slurping the coffee so it spreads to the back of the tongue.
The coffee taster attempts to measure aspects of the coffee's taste, specifically the body (the texture or mouthfeel, such as oiliness), sweetness, acidity (a sharp and tangy feeling, like when biting into an orange), flavour (the characters in the cup), and aftertaste. Since coffee beans embody telltale flavours from the region where they were grown, cuppers may attempt to identify the coffee's origin.

View file

@ -0,0 +1,29 @@
---
templateKey: blog-post
title: A beginners guide to brewing with Chemex
date: 2017-01-04T15:04:10.000Z
featuredpost: false
featuredimage: /img/chemex.jpg
description: Brewing with a Chemex probably seems like a complicated, time-consuming ordeal, but once you get used to the process, it becomes a soothing ritual that's worth the effort every time.
tags:
- brewing
- chemex
---
![chemex](/img/chemex.jpg)
This week well **take** a look at all the steps required to make astonishing coffee with a Chemex at home. The Chemex Coffeemaker is a manual, pour-over style glass-container coffeemaker that Peter Schlumbohm invented in 1941, and which continues to be manufactured by the Chemex Corporation in Chicopee, Massachusetts.
In 1958, designers at the [Illinois Institute of Technology](https://www.spacefarm.digital) said that the Chemex Coffeemaker is _"one of the best-designed products of modern times"_, and so is included in the collection of the Museum of Modern Art in New York City.
## The little secrets of Chemex brewing
The Chemex Coffeemaker consists of an hourglass-shaped glass flask with a conical funnel-like neck (rather than the cylindrical neck of an Erlenmeyer flask) and uses proprietary filters, made of bonded paper (thicker-gauge paper than the standard paper filters for a drip-method coffeemaker) that removes most of the coffee oils, brewing coffee with a taste that is different than coffee brewed in other coffee-making systems; also, the thicker paper of the Chemex coffee filters may assist in removing cafestol, a cholesterol-containing compound found in coffee oils. Heres three important tips newbies forget about:
1. Always buy dedicated Chemex filters.
2. Use a scale, dont try to eyeball it.
3. Never skip preheating the glass.
4. Timing is key, dont forget the clock.
The most visually distinctive feature of the Chemex is the heatproof wooden collar around the neck, allowing it to be handled and poured when full of hot water. This is turned, then split in two to allow it to fit around the glass neck. The two pieces are held loosely in place by a tied leather thong. The pieces are not tied tightly and can still move slightly, retained by the shape of the conical glass.
For a design piece that became popular post-war at a time of Modernism and precision manufacture, this juxtaposition of natural wood and the organic nature of a hand-tied knot with the laboratory nature of glassware was a distinctive feature of its appearance.

View file

@ -0,0 +1,34 @@
---
templateKey: 'blog-post'
title: 'Just in: small batch of Jamaican Blue Mountain in store next week'
date: 2017-01-04T15:04:10.000Z
featuredpost: true
description: >-
Were proud to announce that well be offering a small batch of Jamaica Blue
Mountain coffee beans in our store next week.
tags:
- jamaica
- green beans
- flavor
- tasting
---
We expect the shipment of a limited quantity of green beans next Monday. Well be offering the roasted beans from Tuesday, but quantities are limited, so be quick.
Blue Mountain Peak is the highest mountain in Jamaica and one of the highest peaks in the Caribbean at 7,402 ft. It is the home of Blue Mountain coffee and their famous tours. It is located on the border of the Portland and Saint Thomas parishes of Jamaica.
## A little history
The Blue Mountains are considered by many to be a hiker's and camper's paradise. The traditional Blue Mountain trek is a 7-mile hike to the peak and consists of a 3,000-foot increase in elevation. Jamaicans prefer to reach the peak at sunrise, thus the 34 hour hike is usually undertaken in darkness. Since the sky is usually very clear in the mornings, Cuba can be seen in the distance.
>Some of the plants found on the Blue Mountain cannot be found anywhere else in the world and they are often of a dwarfed sort.
This is mainly due to the cold climate which inhibits growth. The small coffee farming communities of Claverty Cottage and Hagley Gap are located near the peak.
## What you need to know before trying
Jamaican Blue Mountain Coffee or Jamaica Blue Mountain Coffee is a classification of coffee grown in the Blue Mountains of Jamaica. The best lots of Blue Mountain coffee are noted for their mild flavor and lack of bitterness. Over the past few decades, this coffee has developed a reputation that has made it one of the most expensive and sought-after coffees in the world. Over 80% of all Jamaican Blue Mountain Coffee is exported to Japan. In addition to its use for brewed coffee, the beans are the flavor base of Tia Maria coffee liqueur.
Jamaican Blue Mountain Coffee is a globally protected certification mark, meaning only coffee certified by the Coffee Industry Board of Jamaica can be labeled as such. It comes from a recognized growing region in the Blue Mountain region of Jamaica, and its cultivation is monitored by the Coffee Industry Board of Jamaica.
The Blue Mountains are generally located between Kingston to the south and Port Antonio to the north. Rising 7,402 ft, they are some of the highest mountains in the Caribbean. The climate of the region is cool and misty with high rainfall. The soil is rich, with excellent drainage. This combination of climate and soil is considered ideal for coffee.

38
src/pages/blog/index.js Normal file
View file

@ -0,0 +1,38 @@
import React from 'react'
import Layout from '../../components/Layout'
import BlogRoll from '../../components/BlogRoll'
export default class BlogIndexPage extends React.Component {
render() {
return (
<Layout>
<div
className="full-width-image-container margin-top-0"
style={{
backgroundImage: `url('/img/blog-index.jpg')`,
}}
>
<h1
className="has-text-weight-bold is-size-1"
style={{
boxShadow: '0.5rem 0 0 #f40, -0.5rem 0 0 #f40',
backgroundColor: '#f40',
color: 'white',
padding: '1rem',
}}
>
Latest Stories
</h1>
</div>
<section className="section">
<div className="container">
<div className="content">
<BlogRoll />
</div>
</div>
</section>
</Layout>
)
}
}

View file

@ -0,0 +1,50 @@
import React from 'react'
import Link from 'gatsby-link'
import Layout from '../../components/Layout'
export default class Index extends React.Component {
render() {
return (
<Layout>
<section className="section">
<div className="container">
<div className="content">
<h1>Hi people</h1>
<p>
This is an example site integrating Netlifys form handling with
Gatsby
</p>
<ul>
<li>
<Link to="/contact">Basic contact form</Link>
</li>
<li>
<Link to="/contact/file-upload/">Form with file upload</Link>
</li>
</ul>
<h2>Troubleshooting</h2>
<h3>Forms stop working after upgrading to Gatsby v2</h3>
<p>
This can be caused by the offline-plugin.{' '}
<a href="https://github.com/gatsbyjs/gatsby/issues/7997#issuecomment-419749232">
Workaround
</a>{' '}
is to use <code>?no-cache=1</code> in the POST url to prevent
the service worker from handling form submissions
</p>
<h3>Adding reCAPTCHA</h3>
<p>
If you are planning to add reCAPTCHA please go to{' '}
<a href="https://github.com/imorente/gatsby-netlify-form-example">
imorente/gatsby-netlify-form-example
</a>{' '}
for a working example.
</p>
</div>
</div>
</section>
</Layout>
)
}
}

View file

@ -0,0 +1,108 @@
import React from 'react'
import { navigate } from 'gatsby-link'
import Layout from '../../components/Layout'
function encode(data) {
const formData = new FormData()
for (const key of Object.keys(data)) {
formData.append(key, data[key])
}
return formData
}
export default class Contact extends React.Component {
constructor(props) {
super(props)
this.state = {}
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
handleAttachment = (e) => {
this.setState({ [e.target.name]: e.target.files[0] })
}
handleSubmit = (e) => {
e.preventDefault()
const form = e.target
fetch('/', {
method: 'POST',
body: encode({
'form-name': form.getAttribute('name'),
...this.state,
}),
})
.then(() => navigate(form.getAttribute('action')))
.catch((error) => alert(error))
}
render() {
return (
<Layout>
<section className="section">
<div className="container">
<div className="content">
<h1>File Upload</h1>
<form
name="file-upload"
method="post"
action="/contact/thanks/"
data-netlify="true"
data-netlify-honeypot="bot-field"
onSubmit={this.handleSubmit}
>
{/* The `form-name` hidden field is required to support form submissions without JavaScript */}
<input type="hidden" name="form-name" value="file-upload" />
<div hidden>
<label>
Dont fill this out:{' '}
<input name="bot-field" onChange={this.handleChange} />
</label>
</div>
<div className="field">
<label className="label" htmlFor={'name'}>
Your name
</label>
<div className="control">
<input
className="input"
type={'text'}
name={'name'}
onChange={this.handleChange}
id={'name'}
required={true}
/>
</div>
</div>
<div className="field">
<div className="file">
<label className="file-label">
<input
className="file-input"
type="file"
name="attachment"
onChange={this.handleAttachment}
/>
<span className="file-cta">
<span className="file-label">Choose a file</span>
</span>
</label>
</div>
</div>
<div className="field">
<button className="button is-link" type="submit">
Send
</button>
</div>
</form>
</div>
</div>
</section>
</Layout>
)
}
}

115
src/pages/contact/index.js Normal file
View file

@ -0,0 +1,115 @@
import React from 'react'
import { navigate } from 'gatsby-link'
import Layout from '../../components/Layout'
function encode(data) {
return Object.keys(data)
.map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
.join('&')
}
export default class Index extends React.Component {
constructor(props) {
super(props)
this.state = { isValidated: false }
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
handleSubmit = (e) => {
e.preventDefault()
const form = e.target
fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: encode({
'form-name': form.getAttribute('name'),
...this.state,
}),
})
.then(() => navigate(form.getAttribute('action')))
.catch((error) => alert(error))
}
render() {
return (
<Layout>
<section className="section">
<div className="container">
<div className="content">
<h1>Contact</h1>
<form
name="contact"
method="post"
action="/contact/thanks/"
data-netlify="true"
data-netlify-honeypot="bot-field"
onSubmit={this.handleSubmit}
>
{/* The `form-name` hidden field is required to support form submissions without JavaScript */}
<input type="hidden" name="form-name" value="contact" />
<div hidden>
<label>
Dont fill this out:{' '}
<input name="bot-field" onChange={this.handleChange} />
</label>
</div>
<div className="field">
<label className="label" htmlFor={'name'}>
Your name
</label>
<div className="control">
<input
className="input"
type={'text'}
name={'name'}
onChange={this.handleChange}
id={'name'}
required={true}
/>
</div>
</div>
<div className="field">
<label className="label" htmlFor={'email'}>
Email
</label>
<div className="control">
<input
className="input"
type={'email'}
name={'email'}
onChange={this.handleChange}
id={'email'}
required={true}
/>
</div>
</div>
<div className="field">
<label className="label" htmlFor={'message'}>
Message
</label>
<div className="control">
<textarea
className="textarea"
name={'message'}
onChange={this.handleChange}
id={'message'}
required={true}
/>
</div>
</div>
<div className="field">
<button className="button is-link" type="submit">
Send
</button>
</div>
</form>
</div>
</div>
</section>
</Layout>
)
}
}

View file

@ -0,0 +1,15 @@
import React from 'react'
import Layout from '../../components/Layout'
export default () => (
<Layout>
<section className="section">
<div className="container">
<div className="content">
<h1>Thank you!</h1>
<p>This is a custom thank you page for form submissions</p>
</div>
</div>
</section>
</Layout>
)

70
src/pages/index.md Normal file
View file

@ -0,0 +1,70 @@
---
templateKey: index-page
title: Great coffee with a conscience
image: /img/home-jumbotron.jpg
heading: Great coffee with a conscience
subheading: Support sustainable farming while enjoying a cup
mainpitch:
title: Why Kaldi
description: >
Kaldi is the coffee store for everyone who believes that great coffee
shouldn't just taste good, it should do good too. We source all of our beans
directly from small scale sustainable farmers and make sure part of the
profits are reinvested in their communities.
description: >-
Kaldi is the ultimate spot for coffee lovers who want to learn about their
javas origin and support the farmers that grew it. We take coffee production,
roasting and brewing seriously and were glad to pass that knowledge to
anyone.
intro:
blurbs:
- image: /img/coffee.png
text: >
We sell green and roasted coffee beans that are sourced directly from
independent farmers and farm cooperatives. Were proud to offer a
variety of coffee beans grown with great care for the environment and
local communities. Check our post or contact us directly for current
availability.
- image: /img/coffee-gear.png
text: >
We offer a small, but carefully curated selection of brewing gear and
tools for every taste and experience level. No matter if you roast your
own beans or just bought your first french press, youll find a gadget
to fall in love with in our shop.
- image: /img/tutorials.png
text: >
Love a great cup of coffee, but never knew how to make one? Bought a
fancy new Chemex but have no clue how to use it? Don't worry, were here
to help. You can schedule a custom 1-on-1 consultation with our baristas
to learn anything you want to know about coffee roasting and brewing.
Email us or call the store for details.
- image: /img/meeting-space.png
text: >
We believe that good coffee has the power to bring people together.
Thats why we decided to turn a corner of our shop into a cozy meeting
space where you can hang out with fellow coffee lovers and learn about
coffee making techniques. All of the artwork on display there is for
sale. The full price you pay goes to the artist.
heading: What we offer
description: >
Kaldi is the ultimate spot for coffee lovers who want to learn about their
javas origin and support the farmers that grew it. We take coffee
production, roasting and brewing seriously and were glad to pass that
knowledge to anyone. This is an edit via identity...
main:
heading: Great coffee with no compromises
description: >
We hold our coffee to the highest standards from the shrub to the cup.
Thats why were meticulous and transparent about each step of the coffees
journey. We personally visit each farm to make sure the conditions are
optimal for the plants, farmers and the local environment.
image1:
alt: A close-up of a paper filter filled with ground coffee
image: /img/products-grid3.jpg
image2:
alt: A green cup of a coffee on a wooden table
image: /img/products-grid2.jpg
image3:
alt: Coffee beans
image: /img/products-grid1.jpg
---

101
src/pages/products/index.md Normal file
View file

@ -0,0 +1,101 @@
---
templateKey: 'product-page'
path: /products
title: Our Coffee
image: /img/jumbotron.jpg
heading: Great coffee with a conscience
description: >-
Kaldi is the ultimate spot for coffee lovers who want to learn about their
javas origin and support the farmers that grew it. We take coffee production,
roasting and brewing seriously and were glad to pass that knowledge to
anyone.
intro:
blurbs:
- image: /img/coffee.png
text: >
We sell green and roasted coffee beans that are sourced directly from
independent farmers and farm cooperatives. Were proud to offer a
variety of coffee beans grown with great care for the environment and
local communities. Check our post or contact us directly for current
availability.
- image: /img/coffee-gear.png
text: >
We offer a small, but carefully curated selection of brewing gear and
tools for every taste and experience level. No matter if you roast your
own beans or just bought your first french press, youll find a gadget
to fall in love with in our shop.
- image: /img/tutorials.png
text: >
Love a great cup of coffee, but never knew how to make one? Bought a
fancy new Chemex but have no clue how to use it? Don't worry, were here
to help. You can schedule a custom 1-on-1 consultation with our baristas
to learn anything you want to know about coffee roasting and brewing.
Email us or call the store for details.
- image: /img/meeting-space.png
text: >
We believe that good coffee has the power to bring people together.
Thats why we decided to turn a corner of our shop into a cozy meeting
space where you can hang out with fellow coffee lovers and learn about
coffee making techniques. All of the artwork on display there is for
sale. The full price you pay goes to the artist.
heading: What we offer
description: >
Kaldi is the ultimate spot for coffee lovers who want to learn about their
javas origin and support the farmers that grew it. We take coffee
production, roasting and brewing seriously and were glad to pass that
knowledge to anyone. This is an edit via identity...
main:
heading: Great coffee with no compromises
description: >
We hold our coffee to the highest standards from the shrub to the cup.
Thats why were meticulous and transparent about each step of the coffees
journey. We personally visit each farm to make sure the conditions are
optimal for the plants, farmers and the local environment.
image1:
alt: A close-up of a paper filter filled with ground coffee
image: /img/products-grid3.jpg
image2:
alt: A green cup of a coffee on a wooden table
image: /img/products-grid2.jpg
image3:
alt: Coffee beans
image: /img/products-grid1.jpg
testimonials:
- author: Elisabeth Kaurismäki
quote: >-
The first time I tried Kaldis coffee, I couldnt even believe that was
the same thing Ive been drinking every morning.
- author: Philipp Trommler
quote: >-
Kaldi is the place to go if you want the best quality coffee. I love their
stance on empowering farmers and transparency.
full_image: /img/products-full-width.jpg
pricing:
heading: Monthly subscriptions
description: >-
We make it easy to make great coffee a part of your life. Choose one of our
monthly subscription plans to receive great coffee at your doorstep each
month. Contact us about more details and payment info.
plans:
- description: Perfect for the drinker who likes to enjoy 1-2 cups per day.
items:
- 3 lbs of coffee per month
- Green or roasted beans"
- One or two varieties of beans"
plan: Small
price: '50'
- description: 'Great for avid drinkers, java-loving couples and bigger crowds'
items:
- 6 lbs of coffee per month
- Green or roasted beans
- Up to 4 different varieties of beans
plan: Big
price: '80'
- description: Want a few tiny batches from different varieties? Try our custom plan
items:
- Whatever you need
- Green or roasted beans
- Unlimited varieties
plan: Custom
price: '??'
---

57
src/pages/tags/index.js Normal file
View file

@ -0,0 +1,57 @@
import React from 'react'
import { kebabCase } from 'lodash'
import { Helmet } from 'react-helmet'
import { Link, graphql } from 'gatsby'
import Layout from '../../components/Layout'
const TagsPage = ({
data: {
allMarkdownRemark: { group },
site: {
siteMetadata: { title },
},
},
}) => (
<Layout>
<section className="section">
<Helmet title={`Tags | ${title}`} />
<div className="container content">
<div className="columns">
<div
className="column is-10 is-offset-1"
style={{ marginBottom: '6rem' }}
>
<h1 className="title is-size-2 is-bold-light">Tags</h1>
<ul className="taglist">
{group.map((tag) => (
<li key={tag.fieldValue}>
<Link to={`/tags/${kebabCase(tag.fieldValue)}/`}>
{tag.fieldValue} ({tag.totalCount})
</Link>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
</Layout>
)
export default TagsPage
export const tagPageQuery = graphql`
query TagsQuery {
site {
siteMetadata {
title
}
}
allMarkdownRemark(limit: 1000) {
group(field: frontmatter___tags) {
fieldValue
totalCount
}
}
}
`

View file

@ -0,0 +1,63 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import Content, { HTMLContent } from '../components/Content'
export const AboutPageTemplate = ({ title, content, contentComponent }) => {
const PageContent = contentComponent || Content
return (
<section className="section section--gradient">
<div className="container">
<div className="columns">
<div className="column is-10 is-offset-1">
<div className="section">
<h2 className="title is-size-3 has-text-weight-bold is-bold-light">
{title}
</h2>
<PageContent className="content" content={content} />
</div>
</div>
</div>
</div>
</section>
)
}
AboutPageTemplate.propTypes = {
title: PropTypes.string.isRequired,
content: PropTypes.string,
contentComponent: PropTypes.func,
}
const AboutPage = ({ data }) => {
const { markdownRemark: post } = data
return (
<Layout>
<AboutPageTemplate
contentComponent={HTMLContent}
title={post.frontmatter.title}
content={post.html}
/>
</Layout>
)
}
AboutPage.propTypes = {
data: PropTypes.object.isRequired,
}
export default AboutPage
export const aboutPageQuery = graphql`
query AboutPage($id: String!) {
markdownRemark(id: { eq: $id }) {
html
frontmatter {
title
}
}
}
`

103
src/templates/blog-post.js Normal file
View file

@ -0,0 +1,103 @@
import React from 'react'
import PropTypes from 'prop-types'
import { kebabCase } from 'lodash'
import { Helmet } from 'react-helmet'
import { graphql, Link } from 'gatsby'
import Layout from '../components/Layout'
import Content, { HTMLContent } from '../components/Content'
export const BlogPostTemplate = ({
content,
contentComponent,
description,
tags,
title,
helmet,
}) => {
const PostContent = contentComponent || Content
return (
<section className="section">
{helmet || ''}
<div className="container content">
<div className="columns">
<div className="column is-10 is-offset-1">
<h1 className="title is-size-2 has-text-weight-bold is-bold-light">
{title}
</h1>
<p>{description}</p>
<PostContent content={content} />
{tags && tags.length ? (
<div style={{ marginTop: `4rem` }}>
<h4>Tags</h4>
<ul className="taglist">
{tags.map((tag) => (
<li key={tag + `tag`}>
<Link to={`/tags/${kebabCase(tag)}/`}>{tag}</Link>
</li>
))}
</ul>
</div>
) : null}
</div>
</div>
</div>
</section>
)
}
BlogPostTemplate.propTypes = {
content: PropTypes.node.isRequired,
contentComponent: PropTypes.func,
description: PropTypes.string,
title: PropTypes.string,
helmet: PropTypes.object,
}
const BlogPost = ({ data }) => {
const { markdownRemark: post } = data
return (
<Layout>
<BlogPostTemplate
content={post.html}
contentComponent={HTMLContent}
description={post.frontmatter.description}
helmet={
<Helmet titleTemplate="%s | Blog">
<title>{`${post.frontmatter.title}`}</title>
<meta
name="description"
content={`${post.frontmatter.description}`}
/>
</Helmet>
}
tags={post.frontmatter.tags}
title={post.frontmatter.title}
/>
</Layout>
)
}
BlogPost.propTypes = {
data: PropTypes.shape({
markdownRemark: PropTypes.object,
}),
}
export default BlogPost
export const pageQuery = graphql`
query BlogPostByID($id: String!) {
markdownRemark(id: { eq: $id }) {
id
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
title
description
tags
}
}
}
`

193
src/templates/index-page.js Normal file
View file

@ -0,0 +1,193 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Link, graphql } from 'gatsby'
import Layout from '../components/Layout'
import Features from '../components/Features'
import BlogRoll from '../components/BlogRoll'
export const IndexPageTemplate = ({
image,
title,
heading,
subheading,
mainpitch,
description,
intro,
}) => (
<div>
<div
className="full-width-image margin-top-0"
style={{
backgroundImage: `url(${
!!image.childImageSharp ? image.childImageSharp.fluid.src : image
})`,
backgroundPosition: `top left`,
backgroundAttachment: `fixed`,
}}
>
<div
style={{
display: 'flex',
height: '150px',
lineHeight: '1',
justifyContent: 'space-around',
alignItems: 'left',
flexDirection: 'column',
}}
>
<h1
className="has-text-weight-bold is-size-3-mobile is-size-2-tablet is-size-1-widescreen"
style={{
boxShadow:
'rgb(255, 68, 0) 0.5rem 0px 0px, rgb(255, 68, 0) -0.5rem 0px 0px',
backgroundColor: 'rgb(255, 68, 0)',
color: 'white',
lineHeight: '1',
padding: '0.25em',
}}
>
{title}
</h1>
<h3
className="has-text-weight-bold is-size-5-mobile is-size-5-tablet is-size-4-widescreen"
style={{
boxShadow:
'rgb(255, 68, 0) 0.5rem 0px 0px, rgb(255, 68, 0) -0.5rem 0px 0px',
backgroundColor: 'rgb(255, 68, 0)',
color: 'white',
lineHeight: '1',
padding: '0.25em',
}}
>
{subheading}
</h3>
</div>
</div>
<section className="section section--gradient">
<div className="container">
<div className="section">
<div className="columns">
<div className="column is-10 is-offset-1">
<div className="content">
<div className="content">
<div className="tile">
<h1 className="title">{mainpitch.title}</h1>
</div>
<div className="tile">
<h3 className="subtitle">{mainpitch.description}</h3>
</div>
</div>
<div className="columns">
<div className="column is-12">
<h3 className="has-text-weight-semibold is-size-2">
{heading}
</h3>
<p>{description}</p>
</div>
</div>
<Features gridItems={intro.blurbs} />
<div className="columns">
<div className="column is-12 has-text-centered">
<Link className="btn" to="/products">
See all products
</Link>
</div>
</div>
<div className="column is-12">
<h3 className="has-text-weight-semibold is-size-2">
Latest stories
</h3>
<BlogRoll />
<div className="column is-12 has-text-centered">
<Link className="btn" to="/blog">
Read more
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
)
IndexPageTemplate.propTypes = {
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
title: PropTypes.string,
heading: PropTypes.string,
subheading: PropTypes.string,
mainpitch: PropTypes.object,
description: PropTypes.string,
intro: PropTypes.shape({
blurbs: PropTypes.array,
}),
}
const IndexPage = ({ data }) => {
const { frontmatter } = data.markdownRemark
return (
<Layout>
<IndexPageTemplate
image={frontmatter.image}
title={frontmatter.title}
heading={frontmatter.heading}
subheading={frontmatter.subheading}
mainpitch={frontmatter.mainpitch}
description={frontmatter.description}
intro={frontmatter.intro}
/>
</Layout>
)
}
IndexPage.propTypes = {
data: PropTypes.shape({
markdownRemark: PropTypes.shape({
frontmatter: PropTypes.object,
}),
}),
}
export default IndexPage
export const pageQuery = graphql`
query IndexPageTemplate {
markdownRemark(frontmatter: { templateKey: { eq: "index-page" } }) {
frontmatter {
title
image {
childImageSharp {
fluid(maxWidth: 2048, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
heading
subheading
mainpitch {
title
description
}
description
intro {
blurbs {
image {
childImageSharp {
fluid(maxWidth: 240, quality: 64) {
...GatsbyImageSharpFluid
}
}
}
text
}
heading
description
}
}
}
}
`

View file

@ -0,0 +1,247 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import Features from '../components/Features'
import Testimonials from '../components/Testimonials'
import Pricing from '../components/Pricing'
import PreviewCompatibleImage from '../components/PreviewCompatibleImage'
export const ProductPageTemplate = ({
image,
title,
heading,
description,
intro,
main,
testimonials,
fullImage,
pricing,
}) => (
<div className="content">
<div
className="full-width-image-container margin-top-0"
style={{
backgroundImage: `url(${
!!image.childImageSharp ? image.childImageSharp.fluid.src : image
})`,
}}
>
<h2
className="has-text-weight-bold is-size-1"
style={{
boxShadow: '0.5rem 0 0 #f40, -0.5rem 0 0 #f40',
backgroundColor: '#f40',
color: 'white',
padding: '1rem',
}}
>
{title}
</h2>
</div>
<section className="section section--gradient">
<div className="container">
<div className="section">
<div className="columns">
<div className="column is-7 is-offset-1">
<h3 className="has-text-weight-semibold is-size-2">{heading}</h3>
<p>{description}</p>
</div>
</div>
<div className="columns">
<div className="column is-10 is-offset-1">
<Features gridItems={intro.blurbs} />
<div className="columns">
<div className="column is-7">
<h3 className="has-text-weight-semibold is-size-3">
{main.heading}
</h3>
<p>{main.description}</p>
</div>
</div>
<div className="tile is-ancestor">
<div className="tile is-vertical">
<div className="tile">
<div className="tile is-parent is-vertical">
<article className="tile is-child">
<PreviewCompatibleImage imageInfo={main.image1} />
</article>
</div>
<div className="tile is-parent">
<article className="tile is-child">
<PreviewCompatibleImage imageInfo={main.image2} />
</article>
</div>
</div>
<div className="tile is-parent">
<article className="tile is-child">
<PreviewCompatibleImage imageInfo={main.image3} />
</article>
</div>
</div>
</div>
<Testimonials testimonials={testimonials} />
<div
className="full-width-image-container"
style={{
backgroundImage: `url(${
fullImage.childImageSharp
? fullImage.childImageSharp.fluid.src
: fullImage
})`,
}}
/>
<h2 className="has-text-weight-semibold is-size-2">
{pricing.heading}
</h2>
<p className="is-size-5">{pricing.description}</p>
<Pricing data={pricing.plans} />
</div>
</div>
</div>
</div>
</section>
</div>
)
ProductPageTemplate.propTypes = {
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
title: PropTypes.string,
heading: PropTypes.string,
description: PropTypes.string,
intro: PropTypes.shape({
blurbs: PropTypes.array,
}),
main: PropTypes.shape({
heading: PropTypes.string,
description: PropTypes.string,
image1: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
image2: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
image3: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
}),
testimonials: PropTypes.array,
fullImage: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
pricing: PropTypes.shape({
heading: PropTypes.string,
description: PropTypes.string,
plans: PropTypes.array,
}),
}
const ProductPage = ({ data }) => {
const { frontmatter } = data.markdownRemark
return (
<Layout>
<ProductPageTemplate
image={frontmatter.image}
title={frontmatter.title}
heading={frontmatter.heading}
description={frontmatter.description}
intro={frontmatter.intro}
main={frontmatter.main}
testimonials={frontmatter.testimonials}
fullImage={frontmatter.full_image}
pricing={frontmatter.pricing}
/>
</Layout>
)
}
ProductPage.propTypes = {
data: PropTypes.shape({
markdownRemark: PropTypes.shape({
frontmatter: PropTypes.object,
}),
}),
}
export default ProductPage
export const productPageQuery = graphql`
query ProductPage($id: String!) {
markdownRemark(id: { eq: $id }) {
frontmatter {
title
image {
childImageSharp {
fluid(maxWidth: 2048, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
heading
description
intro {
blurbs {
image {
childImageSharp {
fluid(maxWidth: 240, quality: 64) {
...GatsbyImageSharpFluid
}
}
}
text
}
heading
description
}
main {
heading
description
image1 {
alt
image {
childImageSharp {
fluid(maxWidth: 526, quality: 92) {
...GatsbyImageSharpFluid
}
}
}
}
image2 {
alt
image {
childImageSharp {
fluid(maxWidth: 526, quality: 92) {
...GatsbyImageSharpFluid
}
}
}
}
image3 {
alt
image {
childImageSharp {
fluid(maxWidth: 1075, quality: 72) {
...GatsbyImageSharpFluid
}
}
}
}
}
testimonials {
author
quote
}
full_image {
childImageSharp {
fluid(maxWidth: 2048, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
pricing {
heading
description
plans {
description
items
plan
price
}
}
}
}
}
`

74
src/templates/tags.js Normal file
View file

@ -0,0 +1,74 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import { Link, graphql } from 'gatsby'
import Layout from '../components/Layout'
class TagRoute extends React.Component {
render() {
const posts = this.props.data.allMarkdownRemark.edges
const postLinks = posts.map((post) => (
<li key={post.node.fields.slug}>
<Link to={post.node.fields.slug}>
<h2 className="is-size-2">{post.node.frontmatter.title}</h2>
</Link>
</li>
))
const tag = this.props.pageContext.tag
const title = this.props.data.site.siteMetadata.title
const totalCount = this.props.data.allMarkdownRemark.totalCount
const tagHeader = `${totalCount} post${
totalCount === 1 ? '' : 's'
} tagged with ${tag}`
return (
<Layout>
<section className="section">
<Helmet title={`${tag} | ${title}`} />
<div className="container content">
<div className="columns">
<div
className="column is-10 is-offset-1"
style={{ marginBottom: '6rem' }}
>
<h3 className="title is-size-4 is-bold-light">{tagHeader}</h3>
<ul className="taglist">{postLinks}</ul>
<p>
<Link to="/tags/">Browse all tags</Link>
</p>
</div>
</div>
</div>
</section>
</Layout>
)
}
}
export default TagRoute
export const tagPageQuery = graphql`
query TagPage($tag: String) {
site {
siteMetadata {
title
}
}
allMarkdownRemark(
limit: 1000
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { tags: { in: [$tag] } } }
) {
totalCount
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`

66
static/admin/config.yml Normal file
View file

@ -0,0 +1,66 @@
backend:
name: git-gateway
branch: master
commit_messages:
create: 'Create {{collection}} “{{slug}}”'
update: 'Update {{collection}} “{{slug}}”'
delete: 'Delete {{collection}} “{{slug}}”'
uploadMedia: '[skip ci] Upload “{{path}}”'
deleteMedia: '[skip ci] Delete “{{path}}”'
media_folder: static/img
public_folder: /img
collections:
- name: "blog"
label: "Blog"
folder: "src/pages/blog"
create: true
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
fields:
- {label: "Template Key", name: "templateKey", widget: "hidden", default: "blog-post"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Description", name: "description", widget: "text"}
- {label: "Featured Post", name: "featuredpost", widget: "boolean"}
- {label: "Featured Image", name: "featuredimage", widget: image}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Tags", name: "tags", widget: "list"}
- name: "pages"
label: "Pages"
files:
- file: "src/pages/index.md"
label: "Landing Page"
name: "index"
fields:
- {label: "Template Key", name: "templateKey", widget: "hidden", default: "index-page"}
- {label: Title, name: title, widget: string}
- {label: Image, name: image, widget: image}
- {label: Heading, name: heading, widget: string}
- {label: Subheading, name: subheading, widget: string}
- {label: Mainpitch, name: mainpitch, widget: object, fields: [{label: Title, name: title, widget: string}, {label: Description, name: description, widget: text}]}
- {label: Description, name: description, widget: string}
- {label: Intro, name: intro, widget: object, fields: [{label: Heading, name: heading, widget: string}, {label: Description, name: description, widget: text}, {label: Blurbs, name: blurbs, widget: list, fields: [{label: Image, name: image, widget: image}, {label: Text, name: text, widget: text}]}]}
- {label: Main, name: main, widget: object, fields: [{label: Heading, name: heading, widget: string}, {label: Description, name: description, widget: text}, {label: Image1, name: image1, widget: object, fields: [{label: Image, name: image, widget: image}, {label: Alt, name: alt, widget: string}]}, {label: Image2, name: image2, widget: object, fields: [{label: Image, name: image, widget: image}, {label: Alt, name: alt, widget: string}]}, {label: Image3, name: image3, widget: object, fields: [{label: Image, name: image, widget: image}, {label: Alt, name: alt, widget: string}]}]}
- file: "src/pages/about/index.md"
label: "About"
name: "about"
fields:
- {label: "Template Key", name: "templateKey", widget: "hidden", default: "about-page"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Body", name: "body", widget: "markdown"}
- file: "src/pages/products/index.md"
label: "Products Page"
name: "products"
fields:
- {label: "Template Key", name: "templateKey", widget: "hidden", default: "product-page"}
- {label: Title, name: title, widget: string}
- {label: Image, name: image, widget: image}
- {label: Heading, name: heading, widget: string}
- {label: Description, name: description, widget: string}
- {label: Intro, name: intro, widget: object, fields: [{label: Heading, name: heading, widget: string}, {label: Description, name: description, widget: text}, {label: Blurbs, name: blurbs, widget: list, fields: [{label: Image, name: image, widget: image}, {label: Text, name: text, widget: text}]}]}
- {label: Main, name: main, widget: object, fields: [{label: Heading, name: heading, widget: string}, {label: Description, name: description, widget: text}, {label: Image1, name: image1, widget: object, fields: [{label: Image, name: image, widget: image}, {label: Alt, name: alt, widget: string}]}, {label: Image2, name: image2, widget: object, fields: [{label: Image, name: image, widget: image}, {label: Alt, name: alt, widget: string}]}, {label: Image3, name: image3, widget: object, fields: [{label: Image, name: image, widget: image}, {label: Alt, name: alt, widget: string}]}]}
- {label: Testimonials, name: testimonials, widget: list, fields: [{label: Quote, name: quote, widget: string}, {label: Author, name: author, widget: string}]}
- {label: Full_image, name: full_image, widget: image}
- {label: Pricing, name: pricing, widget: object, fields: [{label: Heading, name: heading, widget: string}, {label: Description, name: description, widget: string}, {label: Plans, name: plans, widget: list, fields: [{label: Plan, name: plan, widget: string}, {label: Price, name: price, widget: string}, {label: Description, name: description, widget: string}, {label: Items, name: items, widget: list}]}]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
static/img/blog-index.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
static/img/chemex.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
static/img/coffee-gear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
static/img/coffee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

BIN
static/img/flavor_wheel.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
static/img/jumbotron.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

1
static/img/logo.svg Normal file
View file

@ -0,0 +1 @@
<svg viewBox="0 0 109 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:figma="http://www.figma.com/figma/ns"><title>Logo</title><g transform="translate(-1470)" figma:type="canvas"><g style="mix-blend-mode:normal" figma:type="vector" transform="translate(1470)" fill="#f40"><use xlink:href="#b" style="mix-blend-mode:normal"/><use xlink:href="#c" style="mix-blend-mode:normal"/><use xlink:href="#d" style="mix-blend-mode:normal"/><use xlink:href="#e" style="mix-blend-mode:normal"/><use xlink:href="#f" style="mix-blend-mode:normal"/></g></g><defs><path id="b" d="M22.735 23.171c.283.323.053.829-.376.829h-5.907c-.285 0-.556-.121-.745-.333l-9.414-10.526v10.36c0 .276-.224.5-.5.5h-5.293c-.276 0-.5-.224-.5-.5v-23c0-.276.224-.5.5-.5h5.293c.276 0 .5.224.5.5v9.815l9.141-9.99c.19-.207.457-.325.738-.325h5.762c.437 0 .664.521.366.841l-9.851 10.563 10.287 11.767z"/><path id="c" d="M45.991 24c-.199 0-.38-.118-.459-.301l-2.024-4.669h-10.67l-2.024 4.669c-.079.183-.259.301-.459.301h-5.212c-.366 0-.608-.381-.453-.712l10.782-23c.082-.176.259-.288.453-.288h4.358c.194 0 .37.112.453.287l10.815 23c.156.332-.086.713-.452.713h-5.108zm-11.135-9.668h6.635l-3.317-7.694-3.317 7.694z"/><path id="d" d="M55.525 24c-.276 0-.5-.224-.5-.5v-23c0-.276.224-.5.5-.5h5.293c.276 0 .5.224.5.5v18.428h9.759c.276 0 .5.224.5.5v4.072c0 .276-.224.5-.5.5h-15.552z"/><path id="e" d="M75.279.5c0-.276.224-.5.5-.5h9.315c2.667 0 4.959.477 6.874 1.43 1.938.953 3.42 2.338 4.446 4.153 1.026 1.793 1.539 3.926 1.539 6.4 0 2.496-.513 4.652-1.539 6.468-1.003 1.793-2.474 3.166-4.412 4.119-1.915.953-4.218 1.43-6.908 1.43h-9.315c-.276 0-.5-.224-.5-.5v-23zm9.37 18.462c2.371 0 4.138-.579 5.301-1.736 1.163-1.157 1.744-2.905 1.744-5.242 0-2.338-.581-4.074-1.744-5.209-1.163-1.157-2.93-1.736-5.301-1.736h-3.078v13.923h3.078z"/><path id="f" d="M102.913 24c-.276 0-.5-.224-.5-.5v-23c0-.276.224-.5.5-.5h5.293c.276 0 .5.224.5.5v23c0 .276-.224.5-.5.5h-5.293z"/></defs></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
static/img/og-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M387 5763 c-1 -1 -24 -3 -52 -4 -155 -9 -269 -96 -319 -244 -15 -46
-16 -145 -13 -1225 2 -646 6 -1188 10 -1205 4 -16 12 -59 18 -95 24 -136 69
-300 112 -405 180 -439 480 -792 869 -1024 275 -164 565 -258 888 -288 97 -9
1416 -9 1535 0 119 9 189 19 272 38 27 6 55 12 63 14 164 34 384 123 565 228
177 103 448 336 555 477 19 25 37 47 40 50 17 15 104 148 151 230 97 169 175
359 209 510 7 30 16 65 21 77 l8 22 59 -23 c84 -33 149 -48 272 -63 119 -15
341 -3 420 22 10 3 28 8 41 10 82 16 294 118 387 188 79 58 181 155 228 213
59 75 151 222 159 254 1 3 11 26 23 52 63 140 92 307 87 498 -2 69 -6 136 -9
150 -3 14 -9 41 -12 60 -20 119 -96 297 -182 425 -143 212 -311 346 -572 459
-44 19 -171 53 -225 60 -22 3 -60 8 -85 12 -140 19 -350 -5 -497 -56 l-53 -18
-1 26 c0 15 -1 90 -3 167 -2 126 -5 146 -27 195 -33 71 -71 115 -134 155 -54
34 -77 42 -141 51 -35 5 -4663 12 -4667 7z m5552 -1236 c80 -23 186 -92 241
-155 210 -242 147 -625 -130 -786 -198 -115 -453 -86 -614 71 -83 80 -79 64
-78 341 0 136 1 267 1 291 1 40 6 49 66 107 73 71 119 99 215 130 86 28 209
28 299 1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/img/tutorials.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB