├── .dependabot └── config.yml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── _headers ├── gatsby-config.js ├── gatsby-node.js ├── netlify.toml ├── netlify └── functions │ └── hello.js ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── cms │ ├── cms.js │ └── preview-templates │ │ ├── AboutPagePreview.js │ │ ├── BlogPostPreview.js │ │ ├── IndexPagePreview.js │ │ └── ProductPagePreview.js ├── components │ ├── BlogRoll.js │ ├── Content.js │ ├── Features.js │ ├── Footer.js │ ├── FullWidthImage.js │ ├── Layout.js │ ├── Navbar.js │ ├── PreviewCompatibleImage.js │ ├── Pricing.js │ ├── SiteMetadata.js │ ├── Testimonials.js │ └── all.sass ├── img │ ├── github-icon.svg │ ├── logo.svg │ └── social │ │ ├── facebook.svg │ │ ├── instagram.svg │ │ ├── twitter.svg │ │ └── vimeo.svg ├── pages │ ├── 404.js │ ├── about │ │ └── index.md │ ├── blog │ │ ├── 2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md │ │ ├── 2017-01-04-a-beginners-guide-to-brewing-with-chemex.md │ │ ├── 2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md │ │ └── index.js │ ├── contact │ │ ├── examples.js │ │ ├── file-upload.js │ │ ├── index.js │ │ └── thanks.js │ ├── index.md │ ├── products │ │ └── index.md │ └── tags │ │ └── index.js └── templates │ ├── about-page.js │ ├── blog-post.js │ ├── index-page.js │ ├── product-page.js │ └── tags.js └── static ├── admin └── config.yml └── img ├── apple-touch-icon.png ├── blog-index.jpg ├── chemex.jpg ├── coffee-gear.png ├── coffee.png ├── favicon-16x16.png ├── favicon-32x32.png ├── flavor_wheel.jpg ├── home-jumbotron.jpg ├── jumbotron.jpg ├── logo.svg ├── meeting-space.png ├── og-image.jpg ├── products-full-width.jpg ├── products-grid1.jpg ├── products-grid2.jpg ├── products-grid3.jpg ├── safari-pinned-tab.svg └── tutorials.png /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | update_configs: 4 | - package_manager: javascript 5 | directory: / 6 | update_schedule: live 7 | allowed_updates: 8 | - match: 9 | update_type: security 10 | automerged_updates: 11 | - match: 12 | dependency_type: all 13 | update_type: in_range 14 | version_requirement_updates: widen_ranges -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | --- 11 | name: Bug report 12 | about: Create a report to help us improve 13 | --- 14 | 15 | 16 | 17 | 18 | # Bug report 19 | 20 | 21 | 22 | 23 | 24 | 25 | **What is the current behavior?** 26 | 27 | 28 | **If the current behavior is a bug, please provide the steps to reproduce.** 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | **What is the expected behavior?** 38 | 39 | 40 | 41 | 42 | 43 | **Other relevant information:** 44 | 45 | 46 | 47 | Node.js version: 48 | NPM/Yarn version 49 | Operating System: 50 | Additional tools: 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | --- 11 | name: Other 12 | about: Something else 13 | 14 | --- 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | --- 11 | name: Feature request 12 | about: Suggest an idea for this project 13 | 14 | --- 15 | 16 | 17 | 18 | ## Feature request 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | **What is the expected behavior?** 27 | 28 | 29 | **What is motivation or use case for adding/changing the behavior?** 30 | 31 | 32 | **How should this be implemented in your opinion?** 33 | 34 | 35 | **Are you willing to work on this yourself?** 36 | yes 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | public/ 7 | static/admin/*.bundle.* 8 | .DS_Store 9 | yarn-error.log 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at david@netlify.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Contributions are always welcome, no matter how large or small. Before contributing, 4 | please read the [code of conduct](CODE_OF_CONDUCT.md). 5 | 6 | ## Setup 7 | 8 | > Install yarn on your system: [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install) 9 | 10 | ### Install dependencies 11 | 12 | > Only required on the first run, subsequent runs can use `yarn` to both 13 | bootstrap and run the development server using `yarn develop`. 14 | 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. 15 | 16 | ```sh 17 | $ git clone https://github.com/netlify-templates/gatsby-starter-netlify-cms 18 | $ yarn 19 | ``` 20 | 21 | ## Available scripts 22 | 23 | 24 | ### `build` 25 | 26 | Build the static files into the `public` folder, turns lambda functions into a deployable form. 27 | 28 | #### Usage 29 | 30 | ```sh 31 | $ yarn build 32 | ``` 33 | 34 | ### `clean` 35 | 36 | Runs `gatsby clean` command. 37 | 38 | #### Usage 39 | 40 | ```sh 41 | yarn clean 42 | ``` 43 | 44 | ### `netlify dev` 45 | 46 | Starts the netlify dev environment, including the gatsby dev environment. 47 | For more infor check the [Netlify Dev Docs](https://github.com/netlify/cli/blob/master/docs/netlify-dev.md) 48 | 49 | ```sh 50 | netlify dev 51 | ``` 52 | 53 | ### `develop` or `start` 54 | 55 | 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 56 | 57 | #### Usage 58 | 59 | ```sh 60 | yarn develop 61 | ``` 62 | ### `test` 63 | 64 | Not implmented yet 65 | 66 | #### Usage 67 | 68 | ```sh 69 | yarn test 70 | ``` 71 | 72 | ### `format` 73 | 74 | Formats code and docs according to our style guidelines using `prettier` 75 | 76 | #### Usage 77 | 78 | ```sh 79 | yarn format 80 | ``` 81 | 82 | 83 | ## Pull Requests 84 | 85 | We actively welcome your pull requests! 86 | 87 | 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! 88 | 89 | 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. 90 | 91 | 1. Fork the repo. 92 | 2. Create a branch from `master`. If you're addressing a specific issue, prefix your branch name with the issue number. 93 | 2. If you've added code that should be tested, add tests. 94 | 3. If you've changed APIs, update the documentation. 95 | 4. Run `yarn test` and ensure the test suite passes. (Not applicable yet) 96 | 5. Use `yarn format` to format and lint your code. 97 | 6. PR's must be rebased before merge (feel free to ask for help). 98 | 7. PR should be reviewed by two maintainers prior to merging. 99 | 100 | ## License 101 | 102 | By contributing to the Gatsby - Netlify CMS starter, you agree that your contributions will be licensed 103 | under its [MIT license](LICENSE). 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | **What kind of change does this PR introduce?** 9 | 10 | 11 | 12 | **Does this PR introduce a breaking change?** 13 | 14 | 15 | 16 | **What needs to be documented once your changes are merged?** 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gatsby + Netlify CMS Starter 2 | 3 | [![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) 4 | 5 | **Note:** This starter uses [Gatsby v4](https://www.gatsbyjs.com/gatsby-4/). 6 | 7 | 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/)**. 8 | 9 | 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. 10 | 11 | ## Features 12 | 13 | - A simple landing page with blog functionality built with Netlify CMS 14 | - Editable Pages: Landing, About, Product, Blog-Collection and Contact page with Netlify Form support 15 | - Create Blog posts from Netlify CMS 16 | - Tags: Separate page for posts under each tag 17 | - Basic directory organization 18 | - Uses Bulma for styling, but size is reduced by `gatsy-plugin-purgecss` 19 | - Blazing fast loading times thanks to pre-rendered HTML and automatic chunk loading of JS files 20 | - Uses `gatsby-plugin-image` with Netlify-CMS preview support 21 | - Separate components for everything 22 | - Netlify deploy configuration 23 | - Netlify function support, see `netlify/functions` folder 24 | - Perfect score on Lighthouse for SEO, Accessibility and Performance (wip:PWA) 25 | - ..and more 26 | 27 | ## Prerequisites 28 | 29 | - Minimal Node.js version 14.15.0 30 | - [Gatsby CLI](https://www.gatsbyjs.com/docs/reference/gatsby-cli/) 31 | - [Netlify CLI](https://github.com/netlify/cli) 32 | 33 | ## Getting Started (Recommended) 34 | 35 | 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: 36 | 37 | Deploy to Netlify 38 | 39 | After clicking that button, you’ll 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, you’ll need to set up Netlify’s Identity service to authorize users to log in to the CMS. 40 | 41 | ### Access Locally 42 | 43 | Pulldown a local copy of the Github repository Netlify created for you, with the name you specified in the previous step 44 | 45 | ``` 46 | $ git clone https://github.com/[GITHUB_USERNAME]/[REPO_NAME].git 47 | $ cd [REPO_NAME] 48 | $ yarn 49 | $ netlify dev # or ntl dev 50 | ``` 51 | 52 | This uses [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 `netlify/functions` folder. 53 | 54 | To test the CMS locally, you'll need to run a production build of the site: 55 | 56 | ``` 57 | $ npm run build 58 | $ netlify dev # or ntl dev 59 | ``` 60 | 61 | ### Media Libraries (installed, but optional) 62 | 63 | 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. 64 | 65 | ```javascript 66 | import CMS from "netlify-cms-app"; 67 | // import uploadcare from 'netlify-cms-media-library-uploadcare' 68 | // import cloudinary from 'netlify-cms-media-library-cloudinary' 69 | 70 | import AboutPagePreview from "./preview-templates/AboutPagePreview"; 71 | import BlogPostPreview from "./preview-templates/BlogPostPreview"; 72 | import ProductPagePreview from "./preview-templates/ProductPagePreview"; 73 | import IndexPagePreview from "./preview-templates/IndexPagePreview"; 74 | 75 | // CMS.registerMediaLibrary(uploadcare); 76 | // CMS.registerMediaLibrary(cloudinary); 77 | 78 | CMS.registerPreviewTemplate("index", IndexPagePreview); 79 | CMS.registerPreviewTemplate("about", AboutPagePreview); 80 | CMS.registerPreviewTemplate("products", ProductPagePreview); 81 | CMS.registerPreviewTemplate("blog", BlogPostPreview); 82 | ``` 83 | 84 | 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. 85 | Example: 86 | 87 | ``` 88 | yarn remove netlify-cms-media-library-uploadcare 89 | ``` 90 | 91 | OR 92 | 93 | ``` 94 | yarn remove netlify-cms-media-library-cloudinary 95 | ``` 96 | 97 | ## Getting Started (Without Netlify) 98 | 99 | ``` 100 | $ gatsby new [SITE_DIRECTORY_NAME] https://github.com/netlify-templates/gatsby-starter-netlify-cms/ 101 | $ cd [SITE_DIRECTORY_NAME] 102 | $ npm run build 103 | $ npm run start 104 | ``` 105 | 106 | ### Setting up the CMS 107 | 108 | Follow the [Netlify CMS Quick Start Guide](https://www.netlifycms.org/docs/quick-start/#authentication) to set up authentication, and hosting for production. 109 | 110 | If you want use Netlify CMS locally, run the site in one terminal with `npm run start` and in another 111 | Terminal you can use `npx netlify-cms-proxy-server` which proxy requests so you'll be automatically logged 112 | in as a user on [http:localhost:3000/admin](http:localhost:3000/admin). 113 | 114 | ## Debugging 115 | 116 | Windows users, who aren't using [WSL](https://docs.microsoft.com/en-us/windows/wsl/about), might encounter `node-gyp` errors when trying to npm install. 117 | To resolve, make sure that you have both Python 2.7 and the Visual C++ build environment installed. 118 | 119 | ``` 120 | npm config set python python2.7 121 | npm install --global --production windows-build-tools 122 | ``` 123 | 124 | [Full details here](https://www.npmjs.com/package/node-gyp "NPM node-gyp page"). 125 | 126 | MacOS and WSL users who might also encounter some errors, check [node-gyp](https://github.com/nodejs/node-gyp) for more info. We recommend using the latest stable node version. 127 | 128 | ## Purgecss 129 | 130 | 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. 131 | 132 | # CONTRIBUTING 133 | 134 | Contributions are always welcome, no matter how large or small. Before contributing, 135 | please read the [code of conduct](CODE_OF_CONDUCT.md). 136 | -------------------------------------------------------------------------------- /_headers: -------------------------------------------------------------------------------- 1 | /static/* 2 | Cache-Control: "public, max-age=360000" 3 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: "Gatsby + Netlify CMS Starter", 4 | description: 5 | "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.", 6 | }, 7 | plugins: [ 8 | "gatsby-plugin-react-helmet", 9 | { 10 | resolve: "gatsby-plugin-sass", 11 | options: { 12 | sassOptions: { 13 | indentedSyntax: true, 14 | }, 15 | }, 16 | }, 17 | { 18 | // keep as first gatsby-source-filesystem plugin for gatsby image support 19 | resolve: "gatsby-source-filesystem", 20 | options: { 21 | path: `${__dirname}/static/img`, 22 | name: "uploads", 23 | }, 24 | }, 25 | { 26 | resolve: "gatsby-source-filesystem", 27 | options: { 28 | path: `${__dirname}/src/pages`, 29 | name: "pages", 30 | }, 31 | }, 32 | { 33 | resolve: "gatsby-source-filesystem", 34 | options: { 35 | path: `${__dirname}/src/img`, 36 | name: "images", 37 | }, 38 | }, 39 | `gatsby-plugin-image`, 40 | "gatsby-plugin-sharp", 41 | "gatsby-transformer-sharp", 42 | { 43 | resolve: "gatsby-transformer-remark", 44 | options: { 45 | plugins: [ 46 | 'gatsby-remark-relative-images', 47 | { 48 | resolve: "gatsby-remark-images", 49 | options: { 50 | // It's important to specify the maxWidth (in pixels) of 51 | // the content container as this plugin uses this as the 52 | // base for generating different widths of each image. 53 | maxWidth: 2048, 54 | }, 55 | }, 56 | { 57 | resolve: "gatsby-remark-copy-linked-files", 58 | options: { 59 | destinationDir: "static", 60 | }, 61 | }, 62 | ], 63 | }, 64 | }, 65 | { 66 | resolve: "gatsby-plugin-netlify-cms", 67 | options: { 68 | modulePath: `${__dirname}/src/cms/cms.js`, 69 | }, 70 | }, 71 | { 72 | resolve: "gatsby-plugin-purgecss", // purges all unused/unreferenced css rules 73 | options: { 74 | develop: true, // Activates purging in npm run develop 75 | purgeOnly: ["/all.sass"], // applies purging only on the bulma css file 76 | }, 77 | }, // must be after other CSS plugins 78 | "gatsby-plugin-netlify", // make sure to keep it last in the array 79 | ], 80 | }; 81 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const path = require('path') 3 | const { createFilePath } = require('gatsby-source-filesystem') 4 | 5 | exports.createPages = ({ actions, graphql }) => { 6 | const { createPage } = actions 7 | 8 | return graphql(` 9 | { 10 | allMarkdownRemark(limit: 1000) { 11 | edges { 12 | node { 13 | id 14 | fields { 15 | slug 16 | } 17 | frontmatter { 18 | tags 19 | templateKey 20 | } 21 | } 22 | } 23 | } 24 | } 25 | `).then((result) => { 26 | if (result.errors) { 27 | result.errors.forEach((e) => console.error(e.toString())) 28 | return Promise.reject(result.errors) 29 | } 30 | 31 | const posts = result.data.allMarkdownRemark.edges 32 | 33 | posts.forEach((edge) => { 34 | const id = edge.node.id 35 | createPage({ 36 | path: edge.node.fields.slug, 37 | tags: edge.node.frontmatter.tags, 38 | component: path.resolve( 39 | `src/templates/${String(edge.node.frontmatter.templateKey)}.js` 40 | ), 41 | // additional data can be passed via context 42 | context: { 43 | id, 44 | }, 45 | }) 46 | }) 47 | 48 | // Tag pages: 49 | let tags = [] 50 | // Iterate through each post, putting all found tags into `tags` 51 | posts.forEach((edge) => { 52 | if (_.get(edge, `node.frontmatter.tags`)) { 53 | tags = tags.concat(edge.node.frontmatter.tags) 54 | } 55 | }) 56 | // Eliminate duplicate tags 57 | tags = _.uniq(tags) 58 | 59 | // Make tag pages 60 | tags.forEach((tag) => { 61 | const tagPath = `/tags/${_.kebabCase(tag)}/` 62 | 63 | createPage({ 64 | path: tagPath, 65 | component: path.resolve(`src/templates/tags.js`), 66 | context: { 67 | tag, 68 | }, 69 | }) 70 | }) 71 | }) 72 | } 73 | 74 | exports.onCreateNode = ({ node, actions, getNode }) => { 75 | const { createNodeField } = actions 76 | 77 | if (node.internal.type === `MarkdownRemark`) { 78 | const value = createFilePath({ node, getNode }) 79 | createNodeField({ 80 | name: `slug`, 81 | node, 82 | value, 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | [build.environment] 5 | NODE_VERSION = "14.15.0" 6 | YARN_VERSION = "1.22.4" 7 | YARN_FLAGS = "--no-ignore-optional" 8 | 9 | 10 | -------------------------------------------------------------------------------- /netlify/functions/hello.js: -------------------------------------------------------------------------------- 1 | // For more info, check https://docs.netlify.com/functions/build-with-javascript 2 | module.exports.handler = async function(event, context) { 3 | console.log("queryStringParameters", event.queryStringParameters) 4 | return { 5 | // return null to show no errors 6 | statusCode: 200, // http status code 7 | body: JSON.stringify({ 8 | msg: "Hello, World! This is better " + Math.round(Math.random() * 10) 9 | }) 10 | } 11 | } 12 | 13 | // Now you are ready to access this API from anywhere in your Gatsby app! For example, in any event handler or lifecycle method, insert: 14 | // fetch("/.netlify/functions/hello") 15 | // .then(response => response.json()) 16 | // .then(console.log) 17 | // For more info see: https://www.gatsbyjs.org/blog/2018-12-17-turning-the-static-dynamic/#static-dynamic-is-a-spectrum 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-netlify-cms", 3 | "description": "Example Gatsby, and Netlify CMS project", 4 | "version": "1.1.3", 5 | "author": "Austin Green", 6 | "dependencies": { 7 | "bulma": "^0.9.0", 8 | "gatsby": "^4.0.0", 9 | "gatsby-plugin-image": "^2.0.0", 10 | "gatsby-plugin-netlify": "^5.0.0", 11 | "gatsby-plugin-netlify-cms": "^6.0.0", 12 | "gatsby-plugin-purgecss": "^6.0.0", 13 | "gatsby-plugin-react-helmet": "^5.0.0", 14 | "gatsby-plugin-sass": "^5.0.0", 15 | "gatsby-plugin-sharp": "^4.0.0", 16 | "gatsby-remark-copy-linked-files": "^5.0.0", 17 | "gatsby-remark-images": "^6.0.0", 18 | "gatsby-remark-relative-images": "^2.0.2", 19 | "gatsby-source-filesystem": "^4.0.0", 20 | "gatsby-transformer-remark": "^5.0.0", 21 | "gatsby-transformer-sharp": "^4.0.0", 22 | "lodash": "^4.17.15", 23 | "lodash-webpack-plugin": "^0.11.4", 24 | "netlify-cms-app": "^2.15.72", 25 | "netlify-cms-media-library-cloudinary": "^1.3.10", 26 | "netlify-cms-media-library-uploadcare": "^0.5.10", 27 | "prop-types": "^15.6.0", 28 | "react": "^17.0.0", 29 | "react-dom": "^17.0.0", 30 | "react-helmet": "^6.0.0", 31 | "sass": "^1.43.2", 32 | "uuid": "^8.0.0" 33 | }, 34 | "keywords": [ 35 | "gatsby" 36 | ], 37 | "license": "MIT", 38 | "main": "n/a", 39 | "scripts": { 40 | "clean": "gatsby clean", 41 | "start": "npm run develop", 42 | "build": "npm run clean && gatsby build", 43 | "develop": "npm run clean && gatsby develop", 44 | "serve": "gatsby serve", 45 | "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"", 46 | "test": "echo \"Error: no test specified\" && exit 1" 47 | }, 48 | "devDependencies": { 49 | "prettier": "^2.0.5" 50 | }, 51 | "engines": { 52 | "node": ">= 14.15.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>netlify/renovate-config:netlify-cms-starter"] 3 | } 4 | -------------------------------------------------------------------------------- /src/cms/cms.js: -------------------------------------------------------------------------------- 1 | import CMS from 'netlify-cms-app' 2 | import uploadcare from 'netlify-cms-media-library-uploadcare' 3 | import cloudinary from 'netlify-cms-media-library-cloudinary' 4 | 5 | import AboutPagePreview from './preview-templates/AboutPagePreview' 6 | import BlogPostPreview from './preview-templates/BlogPostPreview' 7 | import ProductPagePreview from './preview-templates/ProductPagePreview' 8 | import IndexPagePreview from './preview-templates/IndexPagePreview' 9 | 10 | CMS.registerMediaLibrary(uploadcare) 11 | CMS.registerMediaLibrary(cloudinary) 12 | 13 | CMS.registerPreviewTemplate('index', IndexPagePreview) 14 | CMS.registerPreviewTemplate('about', AboutPagePreview) 15 | CMS.registerPreviewTemplate('products', ProductPagePreview) 16 | CMS.registerPreviewTemplate('blog', BlogPostPreview) 17 | -------------------------------------------------------------------------------- /src/cms/preview-templates/AboutPagePreview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { AboutPageTemplate } from '../../templates/about-page' 4 | 5 | const AboutPagePreview = ({ entry, widgetFor }) => ( 6 | 10 | ) 11 | 12 | AboutPagePreview.propTypes = { 13 | entry: PropTypes.shape({ 14 | getIn: PropTypes.func, 15 | }), 16 | widgetFor: PropTypes.func, 17 | } 18 | 19 | export default AboutPagePreview 20 | -------------------------------------------------------------------------------- /src/cms/preview-templates/BlogPostPreview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { BlogPostTemplate } from '../../templates/blog-post' 4 | 5 | const BlogPostPreview = ({ entry, widgetFor }) => { 6 | const tags = entry.getIn(['data', 'tags']) 7 | return ( 8 | 14 | ) 15 | } 16 | 17 | BlogPostPreview.propTypes = { 18 | entry: PropTypes.shape({ 19 | getIn: PropTypes.func, 20 | }), 21 | widgetFor: PropTypes.func, 22 | } 23 | 24 | export default BlogPostPreview 25 | -------------------------------------------------------------------------------- /src/cms/preview-templates/IndexPagePreview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { IndexPageTemplate } from '../../templates/index-page' 4 | 5 | const IndexPagePreview = ({ entry, getAsset }) => { 6 | const data = entry.getIn(['data']).toJS() 7 | 8 | if (data) { 9 | return ( 10 | 19 | ) 20 | } else { 21 | return
Loading...
22 | } 23 | } 24 | 25 | IndexPagePreview.propTypes = { 26 | entry: PropTypes.shape({ 27 | getIn: PropTypes.func, 28 | }), 29 | getAsset: PropTypes.func, 30 | } 31 | 32 | export default IndexPagePreview 33 | -------------------------------------------------------------------------------- /src/cms/preview-templates/ProductPagePreview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ProductPageTemplate } from '../../templates/product-page' 4 | 5 | const ProductPagePreview = ({ entry, getAsset }) => { 6 | const entryBlurbs = entry.getIn(['data', 'intro', 'blurbs']) 7 | const blurbs = entryBlurbs ? entryBlurbs.toJS() : [] 8 | 9 | const entryTestimonials = entry.getIn(['data', 'testimonials']) 10 | const testimonials = entryTestimonials ? entryTestimonials.toJS() : [] 11 | 12 | const entryPricingPlans = entry.getIn(['data', 'pricing', 'plans']) 13 | const pricingPlans = entryPricingPlans ? entryPricingPlans.toJS() : [] 14 | 15 | return ( 16 | 46 | ) 47 | } 48 | 49 | ProductPagePreview.propTypes = { 50 | entry: PropTypes.shape({ 51 | getIn: PropTypes.func, 52 | }), 53 | getAsset: PropTypes.func, 54 | } 55 | 56 | export default ProductPagePreview 57 | -------------------------------------------------------------------------------- /src/components/BlogRoll.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Link, graphql, StaticQuery } from 'gatsby' 4 | import PreviewCompatibleImage from './PreviewCompatibleImage' 5 | 6 | 7 | const BlogRollTemplate = (props) => { 8 | 9 | const { edges: posts } = props.data.allMarkdownRemark; 10 | 11 | return ( 12 |
13 | {posts && 14 | posts.map(({ node: post }) => ( 15 |
16 |
21 |
22 | {post?.frontmatter?.featuredimage && ( 23 |
24 | 36 |
37 | ) } 38 |

39 | 43 | {post.frontmatter.title} 44 | 45 | 46 | 47 | {post.frontmatter.date} 48 | 49 |

50 |
51 |

52 | {post.excerpt} 53 |
54 |
55 | 56 | Keep Reading → 57 | 58 |

59 |
60 |
61 | ))} 62 |
63 | ) 64 | } 65 | 66 | BlogRoll.propTypes = { 67 | data: PropTypes.shape({ 68 | allMarkdownRemark: PropTypes.shape({ 69 | edges: PropTypes.array, 70 | }), 71 | }), 72 | } 73 | 74 | 75 | export default function BlogRoll() { 76 | return ( 77 | } 113 | /> 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /src/components/Content.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | export const HTMLContent = ({ content, className }) => ( 5 |
6 | ); 7 | 8 | const Content = ({ content, className }) => ( 9 |
{content}
10 | ); 11 | 12 | Content.propTypes = { 13 | content: PropTypes.node, 14 | className: PropTypes.string, 15 | }; 16 | 17 | HTMLContent.propTypes = Content.propTypes; 18 | 19 | export default Content; 20 | -------------------------------------------------------------------------------- /src/components/Features.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import PreviewCompatibleImage from "../components/PreviewCompatibleImage"; 4 | 5 | const FeatureGrid = ({ gridItems }) => ( 6 |
7 | {gridItems.map((item) => ( 8 |
9 |
10 |
11 |
17 | 18 |
19 |
20 |

{item.text}

21 |
22 |
23 | ))} 24 |
25 | ); 26 | 27 | FeatureGrid.propTypes = { 28 | gridItems: PropTypes.arrayOf( 29 | PropTypes.shape({ 30 | image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), 31 | text: PropTypes.string, 32 | }) 33 | ), 34 | }; 35 | 36 | export default FeatureGrid; 37 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link } from "gatsby"; 3 | 4 | import logo from "../img/logo.svg"; 5 | import facebook from "../img/social/facebook.svg"; 6 | import instagram from "../img/social/instagram.svg"; 7 | import twitter from "../img/social/twitter.svg"; 8 | import vimeo from "../img/social/vimeo.svg"; 9 | 10 | const Footer = () => { 11 | 12 | return ( 13 |
14 |
15 | Kaldi 20 |
21 |
22 |
23 |
24 |
25 |
26 |
    27 |
  • 28 | 29 | Home 30 | 31 |
  • 32 |
  • 33 | 34 | About 35 | 36 |
  • 37 |
  • 38 | 39 | Products 40 | 41 |
  • 42 |
  • 43 | 44 | Form Examples 45 | 46 |
  • 47 |
  • 48 | 54 | Admin 55 | 56 |
  • 57 |
58 |
59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | Latest Stories 66 | 67 |
  • 68 |
  • 69 | 70 | Contact 71 | 72 |
  • 73 |
74 |
75 |
76 | 107 |
108 |
109 |
110 |
111 | ); 112 | }; 113 | 114 | export default Footer; 115 | -------------------------------------------------------------------------------- /src/components/FullWidthImage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { GatsbyImage } from "gatsby-plugin-image"; 4 | 5 | export default function FullWidthImage(props) { 6 | const { 7 | height = 400, 8 | img, 9 | title, 10 | subheading, 11 | imgPosition = "top left", 12 | } = props; 13 | 14 | return ( 15 | 16 |
23 | {img?.url ? ( 24 | 37 | ) : ( 38 | 54 | )} 55 | {(title || subheading) && ( 56 |
66 | {/* Any content here will be centered in the component */} 67 | {title && ( 68 |

79 | {title} 80 |

81 | )} 82 | {subheading && ( 83 |

95 | {subheading} 96 |

97 | )} 98 |
99 | )} 100 |
101 |
102 | ); 103 | } 104 | 105 | FullWidthImage.propTypes = { 106 | img: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), 107 | title: PropTypes.string, 108 | height: PropTypes.number, 109 | subheading: PropTypes.string, 110 | }; 111 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Helmet } from "react-helmet"; 3 | import Footer from "../components/Footer"; 4 | import Navbar from "../components/Navbar"; 5 | import "./all.sass"; 6 | import useSiteMetadata from "./SiteMetadata"; 7 | import { withPrefix } from "gatsby"; 8 | 9 | const TemplateWrapper = ({ children }) => { 10 | const { title, description } = useSiteMetadata(); 11 | return ( 12 |
13 | 14 | 15 | {title} 16 | 17 | 18 | 23 | 29 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 |
{children}
53 |
54 |
55 | ); 56 | }; 57 | 58 | export default TemplateWrapper; 59 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "gatsby"; 3 | import github from "../img/github-icon.svg"; 4 | import logo from "../img/logo.svg"; 5 | 6 | const Navbar = () => { 7 | const [isActive, setIsActive] = useState(false); 8 | 9 | return ( 10 | 76 | ); 77 | }; 78 | 79 | export default Navbar; 80 | -------------------------------------------------------------------------------- /src/components/PreviewCompatibleImage.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { GatsbyImage } from "gatsby-plugin-image"; 4 | 5 | const PreviewCompatibleImage = ({ imageInfo }) => { 6 | const imageStyle = { borderRadius: "5px" }; 7 | 8 | const { alt = "", childImageSharp, image } = imageInfo; 9 | 10 | if (!!image && !!image.childImageSharp) { 11 | return ( 12 | 17 | ); 18 | } else if (!!childImageSharp) { 19 | return ( 20 | 25 | ); 26 | // for Netlify CMS 27 | } else if (image) { 28 | return {alt}; 29 | } else { 30 | return null 31 | } 32 | }; 33 | 34 | PreviewCompatibleImage.propTypes = { 35 | imageInfo: PropTypes.shape({ 36 | alt: PropTypes.string, 37 | childImageSharp: PropTypes.object, 38 | image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired, 39 | style: PropTypes.object, 40 | }).isRequired, 41 | }; 42 | 43 | export default PreviewCompatibleImage; 44 | -------------------------------------------------------------------------------- /src/components/Pricing.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const Pricing = ({ data }) => ( 5 |
6 | {data.map((price) => ( 7 |
8 |
9 |

10 | {price.plan} 11 |

12 |

13 | ${price.price} 14 |

15 |

{price.description}

16 |
    17 | {price.items.map((item) => ( 18 |
  • 19 | {item} 20 |
  • 21 | ))} 22 |
23 |
24 |
25 | ))} 26 |
27 | ); 28 | 29 | Pricing.propTypes = { 30 | data: PropTypes.arrayOf( 31 | PropTypes.shape({ 32 | plan: PropTypes.string, 33 | price: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 34 | description: PropTypes.string, 35 | items: PropTypes.array, 36 | }) 37 | ), 38 | }; 39 | 40 | export default Pricing; 41 | -------------------------------------------------------------------------------- /src/components/SiteMetadata.js: -------------------------------------------------------------------------------- 1 | import { graphql, useStaticQuery } from 'gatsby' 2 | 3 | const useSiteMetadata = () => { 4 | const { site } = useStaticQuery( 5 | graphql` 6 | query SITE_METADATA_QUERY { 7 | site { 8 | siteMetadata { 9 | title 10 | description 11 | } 12 | } 13 | } 14 | ` 15 | ) 16 | return site.siteMetadata 17 | } 18 | 19 | export default useSiteMetadata 20 | -------------------------------------------------------------------------------- /src/components/Testimonials.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { v4 } from "uuid"; 4 | 5 | const Testimonials = ({ testimonials }) => ( 6 |
7 | {testimonials.map((testimonial) => ( 8 |
9 |
10 | {testimonial.quote} 11 |
12 | – {testimonial.author} 13 |
14 |
15 | ))} 16 |
17 | ); 18 | 19 | Testimonials.propTypes = { 20 | testimonials: PropTypes.arrayOf( 21 | PropTypes.shape({ 22 | quote: PropTypes.string, 23 | author: PropTypes.string, 24 | }) 25 | ), 26 | }; 27 | 28 | export default Testimonials; 29 | -------------------------------------------------------------------------------- /src/components/all.sass: -------------------------------------------------------------------------------- 1 | @import "~bulma/sass/utilities/initial-variables" 2 | 3 | // Config vars 4 | // Kaldi logo brand color: #FD461E 5 | $kaldi-red: #D64000 6 | $kaldi-red-invert: #fff 7 | 8 | $primary: $kaldi-red 9 | $primary-invert: $kaldi-red-invert 10 | $body-color: #333 11 | $black: #2b2523 12 | 13 | .navbar .navbar-menu 14 | box-shadow: none !important 15 | 16 | .content .taglist 17 | list-style: none 18 | margin-bottom: 0 19 | margin-left: 0 20 | margin-right: 1.5rem 21 | margin-top: 1.5rem 22 | display: flex 23 | flex-wrap: wrap 24 | justify-content: left 25 | align-items: center 26 | li 27 | padding: 0 2rem 1rem 0 28 | margin-bottom: 1.5rem 29 | margin-top: 0 30 | 31 | // Helper Classes 32 | .full-width-image-container 33 | width: 100vw 34 | height: 400px 35 | position: relative 36 | left: 50% 37 | right: 50% 38 | margin: 2em -50vw 39 | background-size: cover 40 | background-position: bottom 41 | display: flex 42 | justify-content: center 43 | align-items: center 44 | 45 | .full-width-image 46 | width: 100vw 47 | height: 400px 48 | background-size: cover 49 | background-position: bottom 50 | display: flex 51 | justify-content: center 52 | align-items: center 53 | 54 | .btn 55 | display: inline-block 56 | padding: 12px 16px 10px 57 | font-size: 18px 58 | font-size: 1rem 59 | line-height: 1.25 60 | background-color: #fff 61 | border-radius: .25rem 62 | text-decoration: none 63 | font-weight: 700 64 | color: #CC3700 65 | text-align: center 66 | -webkit-box-shadow: inset 0 0 0 2px #CC3700 67 | box-shadow: inset 0 0 0 2px #f40 68 | -webkit-transition: all .15s ease 69 | transition: all .15s ease 70 | 71 | .margin-top-0 72 | margin-top: 0 !important 73 | 74 | .navbar-item .icon 75 | color: $primary 76 | // Override for use of svg's from https://simpleicons.org/ 77 | .icon svg 78 | width: 1.5rem 79 | height: 1.5rem 80 | fill: currentColor 81 | .navbar-brand .navbar-item.logo 82 | padding: 0 1rem 83 | footer.footer 84 | padding: 3rem 0rem 0rem 85 | background-color: transparent 86 | 87 | //Menu overrides 88 | $menu-item-color: $white-ter 89 | $menu-item-hover-color: $black !important 90 | $menu-item-hover-background-color: $black 91 | $menu-item-active-color: $kaldi-red-invert 92 | $menu-item-active-background-color: $kaldi-red-invert 93 | $menu-list-border-left: 1px solid $kaldi-red-invert 94 | $menu-label-color: $white-ter 95 | 96 | 97 | .menu-label 98 | font-size: 1em !important 99 | text-align: left 100 | .menu-list 101 | list-style: none !important 102 | text-align: left 103 | .social 104 | padding: 2em 105 | .social a 106 | padding: .5em .5em .3em .5em 107 | border-radius: 1em 108 | background-color: $white-ter 109 | margin: .5em 110 | width: 1em 111 | height: 1em 112 | vertical-align: middle 113 | display: inline 114 | 115 | // blog roll 116 | .blog-list-item.is-featured 117 | background-color: #d6400033 118 | .blog-list-item header 119 | display: flex 120 | margin-bottom: 1em 121 | .blog-list-item .featured-thumbnail 122 | flex-basis: 35% 123 | margin: 0 1.5em 0 0 124 | 125 | 126 | @import "~bulma" 127 | 128 | 129 | // responsiveness 130 | +tablet-only 131 | .blog-list-item .featured-thumbnail 132 | flex-basis: 50% 133 | 134 | +mobile 135 | .blog-list-item header 136 | display: block 137 | .blog-list-item .featured-thumbnail 138 | text-align: center 139 | max-width: 70% 140 | margin: 0 0 1em -------------------------------------------------------------------------------- /src/img/github-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |