├── .gitignore
├── babel.config.json
├── src
├── site
│ ├── _data
│ │ ├── env.js
│ │ ├── settings
│ │ │ ├── analytics.js
│ │ │ ├── favicons.js
│ │ │ ├── seo.js
│ │ │ └── social.js
│ │ ├── sections
│ │ │ ├── footer.js
│ │ │ └── header.js
│ │ ├── articles
│ │ │ ├── categories.js
│ │ │ └── all.js
│ │ └── pages.js
│ ├── robots.njk
│ ├── htaccess.njk
│ ├── _includes
│ │ ├── layouts
│ │ │ ├── page.njk
│ │ │ ├── default.njk
│ │ │ └── base.njk
│ │ └── partials
│ │ │ ├── pages
│ │ │ ├── pageSections.njk
│ │ │ ├── richTextSection.njk
│ │ │ └── latestArticles.njk
│ │ │ ├── articles
│ │ │ └── preview.njk
│ │ │ └── global
│ │ │ ├── header.njk
│ │ │ ├── menuOverlay.njk
│ │ │ ├── footer.njk
│ │ │ └── meta.njk
│ ├── 404.njk
│ ├── page.njk
│ ├── articles
│ │ ├── articleCategories.njk
│ │ └── article.njk
│ ├── sitemap.njk
│ ├── feed.njk
│ └── articles.njk
└── assets
│ ├── images
│ └── .gitkeep
│ ├── scss
│ ├── 5-components
│ │ └── .gitkeep
│ ├── 3-vendors
│ │ ├── _lazysizes.scss
│ │ └── _s2r.scss
│ ├── 2-utilities
│ │ ├── _layout.scss
│ │ └── _type.scss
│ ├── 4-objects
│ │ └── _button.scss
│ ├── 6-misc
│ │ ├── _hacks.scss
│ │ └── _print.scss
│ ├── 1-setup
│ │ ├── _base.scss
│ │ └── _type.scss
│ └── main.scss
│ ├── fonts
│ ├── montserrat-bold-webfont.woff
│ ├── montserrat-black-webfont.woff
│ ├── montserrat-black-webfont.woff2
│ ├── montserrat-bold-webfont.woff2
│ ├── montserrat-light-webfont.woff
│ ├── montserrat-light-webfont.woff2
│ ├── montserrat-regular-webfont.woff
│ └── montserrat-regular-webfont.woff2
│ ├── svgs
│ ├── icon-facebook.svg
│ ├── icon-twitter.svg
│ ├── icon-youtube.svg
│ ├── icon-bymattlee.svg
│ └── icon-instagram.svg
│ └── js
│ ├── store
│ └── menuOverlay.js
│ ├── components
│ ├── global.js
│ └── taxi.js
│ ├── utilities
│ ├── helper.js
│ └── check.js
│ ├── transitions
│ └── Default.js
│ ├── main.js
│ └── vendors
│ └── Scroll2Reveal.js
├── netlify.toml
├── .eslintrc
├── eleventy
├── filters
│ ├── cacheBust.js
│ ├── rssDate.js
│ ├── articleUrl.js
│ ├── htmlDate.js
│ ├── readableDate.js
│ ├── articleCategoryUrl.js
│ ├── absoluteUrl.js
│ ├── rssLastUpdatedDate.js
│ └── blocksToHtml.js
├── shortcodes
│ ├── currentYear.js
│ ├── imageUrl.js
│ ├── svg.js
│ ├── isSamePageOrSection.js
│ └── imageSrcset.js
├── utilities
│ ├── sanityClient.js
│ └── getInternalUrl.js
└── transforms
│ ├── addHeaderCredit.js
│ ├── minifyHtml.js
│ └── parseContent.js
├── config.js
├── gulpfile.babel.js
├── tasks
│ ├── default.js
│ ├── clean.js
│ ├── browserSync.js
│ ├── copy.js
│ ├── build.js
│ ├── eleventy.js
│ ├── svgs.js
│ ├── watch.js
│ ├── scripts.js
│ ├── styles.js
│ └── images.js
├── index.js
└── config.js
├── tailwindUtilities.js
├── LICENSE.md
├── .stylelintrc
├── .eleventy.js
├── package.json
├── tailwind.config.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"]
3 | }
--------------------------------------------------------------------------------
/src/site/_data/env.js:
--------------------------------------------------------------------------------
1 | module.exports = process.env.NODE_ENV
2 |
--------------------------------------------------------------------------------
/src/assets/images/.gitkeep:
--------------------------------------------------------------------------------
1 | User-specific packages can be placed here
2 |
--------------------------------------------------------------------------------
/src/assets/scss/5-components/.gitkeep:
--------------------------------------------------------------------------------
1 | User-specific packages can be placed here
2 |
--------------------------------------------------------------------------------
/src/site/robots.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: robots.txt
3 | ---
4 | User-agent: *
5 | Disallow:
6 | Sitemap: {{ '/sitemap.xml' | absoluteUrl }}
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "yarn prod"
3 | publish = "dist"
4 |
5 | [[redirects]]
6 | from = "/*"
7 | to = "/404/"
8 | status = 404
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-bold-webfont.woff
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-black-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-black-webfont.woff
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-black-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-black-webfont.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-bold-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-bold-webfont.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-light-webfont.woff
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-light-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-light-webfont.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-regular-webfont.woff
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bymattlee/bymattlee-11ty-starter/HEAD/src/assets/fonts/montserrat-regular-webfont.woff2
--------------------------------------------------------------------------------
/src/site/htaccess.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: .htaccess
3 | ---
4 | RewriteEngine On
5 | RewriteBase /
6 | RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
7 | RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
8 |
9 | ErrorDocument 404 /404/
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@babel/eslint-parser",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "requireConfigFile": false,
7 | },
8 | "extends": "eslint:recommended",
9 | "env": {
10 | "browser": true,
11 | "es6": true
12 | }
13 | }
--------------------------------------------------------------------------------
/src/site/_includes/layouts/page.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 |
16 | {% for article in category.articles %}
17 | {% include 'partials/articles/preview.njk' %}
18 | {% endfor %}
19 |
20 |
--------------------------------------------------------------------------------
/src/assets/js/transitions/Default.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | ** ***** Default JS
3 | ** ***** ----------------------------------------------- ***** */
4 |
5 | import { Transition } from '@unseenco/taxi'
6 | import gsap from 'gsap'
7 |
8 | export default class Default extends Transition {
9 | onLeave({ from, done }) {
10 | gsap.fromTo(from, 0.3, { opacity: 1 }, { opacity: 0, onComplete: done })
11 | }
12 |
13 | onEnter({ to, done }) {
14 | // Reset Scroll
15 | window.scrollTo(0, 0)
16 |
17 | // Animation
18 | gsap.fromTo(to, 0.3, { opacity: 0 }, { opacity: 1, onComplete: done })
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/site/_data/articles/categories.js:
--------------------------------------------------------------------------------
1 | const client = require('../../../../eleventy/utilities/sanityClient.js')
2 |
3 | const filter = `
4 | *[_type == "articleCategory" && !(_id in path('drafts.**'))] {
5 | description,
6 | 'slug': slug.current,
7 | title,
8 | 'articles': *[_type == "article" && references(^._id)]{
9 | categories[]->{
10 | title,
11 | 'slug': slug.current
12 | },
13 | excerpt,
14 | publishedAt,
15 | 'slug': slug.current,
16 | title
17 | } | order(publishedAt desc)
18 | } | order(title asc)
19 | `
20 |
21 | module.exports = async () => {
22 | return await client.fetch(filter).catch((err) => console.error(err))
23 | }
24 |
--------------------------------------------------------------------------------
/eleventy/transforms/addHeaderCredit.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Add Header Credit Transform
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | const markupHeader = [
6 | '',
7 | '\n',
23 | ]
24 |
25 | module.exports = (content, outputPath) => {
26 | if (outputPath.endsWith('.html')) {
27 | return markupHeader.join('\n') + content
28 | }
29 | return content
30 | }
31 |
--------------------------------------------------------------------------------
/gulpfile.babel.js/tasks/build.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Gulp - Build
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | import clean from './clean.js'
6 | import copy from './copy.js'
7 | import gulp from 'gulp'
8 | import { eleventyBuild } from './eleventy.js'
9 | import images from './images.js'
10 | import scripts from './scripts.js'
11 | import styles from './styles.js'
12 | import svgs from './svgs.js'
13 |
14 | /*
15 | ** -- Clean dist directory
16 | ** -- Run all tasks to rebuild project
17 | */
18 | const build = gulp.series(
19 | clean,
20 | gulp.parallel(eleventyBuild, scripts, images, svgs, copy),
21 | styles
22 | )
23 |
24 | export default build
25 |
--------------------------------------------------------------------------------
/src/assets/svgs/icon-bymattlee.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 | {% for article in pagination.items %}
15 | {% include 'partials/articles/preview.njk' %}
16 | {% endfor %}
17 |
18 |
19 | {% if pagination.href.previous or pagination.href.next %}
20 |
21 |
22 | {% if pagination.href.previous %}
23 |
« Previous Page
24 | {% endif %}
25 |
26 |
27 | {% if pagination.href.next %}
28 |
Next Page »
29 | {% endif %}
30 |
31 |
32 | {% endif %}
33 |
--------------------------------------------------------------------------------
/src/assets/scss/6-misc/_print.scss:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | ** ***** Misc/Print (from HTML5 Boilerplate)
3 | ** ***** ----------------------------------------------- ***** */
4 |
5 | @media print {
6 | *,
7 | *::before,
8 | *::after,
9 | *::first-letter,
10 | *::first-line {
11 | background: transparent !important;
12 | box-shadow: none !important;
13 | color: get-color('black') !important;
14 | text-shadow: none !important;
15 | }
16 |
17 | a,
18 | a::visited {
19 | text-decoration: underline;
20 | }
21 |
22 | a[href]::after {
23 | content: ' (' attr(href) ')';
24 | }
25 |
26 | abbr[title]::after {
27 | content: ' (' attr(title) ')';
28 | }
29 |
30 | a[href^='#']::after,
31 | a[href^='javascript:']::after {
32 | content: '';
33 | }
34 |
35 | pre,
36 | blockquote {
37 | border: 1px solid #666;
38 | page-break-inside: avoid;
39 | }
40 |
41 | thead {
42 | display: table-header-group;
43 | }
44 |
45 | tr,
46 | img {
47 | page-break-inside: avoid;
48 | }
49 |
50 | img {
51 | max-width: 100% !important;
52 | }
53 |
54 | p,
55 | h2,
56 | h3 {
57 | orphans: 3;
58 | widows: 3;
59 | }
60 |
61 | h2,
62 | h3 {
63 | page-break-after: avoid;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/assets/svgs/icon-instagram.svg:
--------------------------------------------------------------------------------
1 | ')) {
76 | return html.slice(5).slice(0, -6)
77 | } else {
78 | return html
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/assets/scss/2-utilities/_type.scss:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | ** ***** Utilities/Type
3 | ** ***** ----------------------------------------------- ***** */
4 |
5 | @layer utilities {
6 | h1,
7 | h2,
8 | h3,
9 | h4,
10 | h5,
11 | h6,
12 | .u-h1,
13 | .u-h2,
14 | .u-h3,
15 | .u-h4,
16 | .u-h5,
17 | .u-h6 {
18 | @apply u-font-heading u-font-bold;
19 | text-rendering: optimizeLegibility;
20 | }
21 |
22 | .u-h1 {
23 | @apply u-text-36 sm:u-text-48;
24 | }
25 |
26 | .u-h2 {
27 | @apply u-text-30 sm:u-text-36;
28 | }
29 |
30 | .u-h3 {
31 | @apply u-text-24 sm:u-text-28;
32 | }
33 |
34 | .u-h4 {
35 | @apply u-text-18 sm:u-text-24;
36 | }
37 |
38 | .u-h5 {
39 | @apply u-text-14 sm:u-text-18;
40 | }
41 |
42 | .u-h6 {
43 | @apply u-text-12 sm:u-text-14;
44 | }
45 |
46 | .u-text-body {
47 | @apply u-font-body u-leading-normal u-text-14 sm:u-text-16;
48 | }
49 |
50 | .u-rich-text {
51 | > * {
52 | &:first-child {
53 | @apply u-mt-0;
54 | }
55 |
56 | &:last-child {
57 | @apply u-mb-0;
58 | }
59 | }
60 |
61 | h1,
62 | h2,
63 | h3,
64 | h4,
65 | h5,
66 | h6 {
67 | @apply u-mb-10;
68 | }
69 |
70 | h1 {
71 | @apply u-text-36 sm:u-text-48;
72 | }
73 |
74 | h2 {
75 | @apply u-text-30 sm:u-text-36;
76 | }
77 |
78 | h3 {
79 | @apply u-text-24 sm:u-text-28;
80 | }
81 |
82 | h4 {
83 | @apply u-text-18 sm:u-text-24;
84 | }
85 |
86 | * + h1,
87 | * + h2,
88 | * + h3,
89 | * + h4,
90 | * + h5,
91 | * + h6 {
92 | @apply u-mt-30 sm:u-mt-35;
93 | }
94 |
95 | a {
96 | @apply u-underline;
97 | }
98 |
99 | p {
100 | @apply u-leading-loose u-my-20 sm:u-my-30;
101 | }
102 |
103 | ul,
104 | ol {
105 | @apply u-leading-loose u-mt-20 u-mr-0 u-mb-20 u-ml-30 sm:u-mt-25 sm:u-mb-25 sm:u-ml-35;
106 |
107 | ul,
108 | ol {
109 | @apply u-mt-5 u-mr-0 u-mb-5 u-ml-35;
110 | }
111 | }
112 |
113 | ul {
114 | @apply u-list-disc;
115 | }
116 |
117 | ol {
118 | @apply u-list-decimal;
119 | }
120 |
121 | blockquote {
122 | @apply u-border-l-2 u-my-30 u-mx-20 u-pl-30;
123 | }
124 |
125 | .o-button {
126 | @apply u-no-underline;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/site/_includes/partials/global/footer.njk:
--------------------------------------------------------------------------------
1 |
47 |
--------------------------------------------------------------------------------
/gulpfile.babel.js/tasks/scripts.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Gulp - Scripts
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | import addSrc from 'gulp-add-src'
6 | import babel from 'rollup-plugin-babel'
7 | import browserSync from 'browser-sync'
8 | import concat from 'gulp-concat'
9 | import commonjs from 'rollup-plugin-commonjs'
10 | import config from '../config'
11 | import eslint from 'gulp-eslint'
12 | import gif from 'gulp-if'
13 | import gulp from 'gulp'
14 | import header from 'gulp-header'
15 | import json from 'rollup-plugin-json'
16 | import nodeResolve from 'rollup-plugin-node-resolve'
17 | import plumber from 'gulp-plumber'
18 | import rename from 'gulp-rename'
19 | import rollup from 'gulp-better-rollup'
20 | import size from 'gulp-size'
21 | import sourcemaps from 'gulp-sourcemaps'
22 | import terser from 'gulp-terser'
23 |
24 | // Environment variables
25 | const isDevelopment = process.env.NODE_ENV === 'development'
26 |
27 | // Lint files with ESLint
28 | const scriptsLint = () => {
29 | return gulp.src(config.scripts.watchSrc).pipe(eslint()).pipe(eslint.format())
30 | }
31 |
32 | /*
33 | ** -- Create sourcemaps if in development mode (use gulp --production or gulp --staging to disable soucemaps)
34 | ** -- Bundle all files
35 | ** -- Minify all files
36 | ** -- Add ByMattLee header to bundled file
37 | ** -- Print bundled file size
38 | ** -- Reload browser
39 | */
40 | const scriptsMain = () => {
41 | return gulp
42 | .src(config.scripts.src)
43 | .pipe(plumber())
44 | .pipe(gif(isDevelopment, sourcemaps.init()))
45 | .pipe(
46 | rollup(
47 | {
48 | plugins: [
49 | babel({
50 | exclude: 'node_modules/**',
51 | }),
52 | nodeResolve(),
53 | commonjs(),
54 | json(),
55 | ],
56 | },
57 | 'umd'
58 | )
59 | )
60 | .pipe(
61 | terser({
62 | format: {
63 | comments: false,
64 | },
65 | })
66 | )
67 | .pipe(concat('main.js'))
68 | .pipe(
69 | rename({
70 | suffix: '.min',
71 | })
72 | )
73 | .pipe(header(config.fileHeader.join('\n')))
74 | .pipe(
75 | size({
76 | title: 'Compressed File Size:',
77 | showFiles: true,
78 | })
79 | )
80 | .pipe(gif(isDevelopment, sourcemaps.write('./')))
81 | .pipe(gulp.dest(config.scripts.dest))
82 | .pipe(browserSync.stream())
83 | }
84 |
85 | const scripts = gulp.series(scriptsLint, scriptsMain)
86 |
87 | export default scripts
88 |
--------------------------------------------------------------------------------
/src/site/articles/article.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | bodyClasses: p-article
4 | viewName: article
5 | pagination:
6 | data: articles.all
7 | size: 1
8 | alias: article
9 | permalink: /articles/{{ article.slug }}/
10 | eleventyComputed:
11 | title: "{{ article.title | safe }}"
12 | featuredImage: "{% if article.featuredImage %}{% imageUrl article.featuredImage, 1200 %}{% else %}{% endif %}"
13 | pageDescription: "{{ article.excerpt | safe }}"
14 | metaTitle: "{{ article.articleMetaData.pageTitle | safe }}"
15 | metaDescription: "{{ article.articleMetaData.pageDescription | safe }}"
16 | metaShareImage: "{% if article.articleMetaData.pageShareImage %}{% imageUrl article.articleMetaData.pageShareImage, 1200 %}{% else %}{% endif %}"
17 | ---
18 |
19 |
20 |
21 |
22 | {% if article.featuredImage %}
23 |
24 |
25 |
26 | {% endif %}
27 |
28 |
47 |
48 | {% if article.mainContent %}
49 |
50 | {{ article.mainContent | blocksToHtml | safe }}
51 |
52 | {% endif %}
53 |
54 |
55 |
56 | {% if article.previousArticle or article.nextArticle %}
57 |
58 |
63 |
64 | {% if article.nextArticle %}
65 |
Next Article »
66 | {% endif %}
67 |
68 |
69 | {% endif %}
70 |
71 |
--------------------------------------------------------------------------------
/src/assets/scss/1-setup/_type.scss:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | ** ***** Setup/Type
3 | ** ***** ----------------------------------------------- ***** */
4 |
5 | /* ***** Mixins
6 | ** ***** ----------------------------------------------- ***** */
7 |
8 | // Font face
9 | @mixin font-face($name, $file, $weight: 400, $style: normal) {
10 | @font-face {
11 | font-display: swap;
12 | font-family: $name;
13 | font-style: $style;
14 | font-weight: $weight;
15 | src: url('../fonts/#{$file}.woff2') format('woff2'),
16 | url('../fonts/#{$file}.woff') format('woff');
17 | }
18 | }
19 |
20 | // Placeholder text
21 | @mixin placeholder($color) {
22 | &::-webkit-input-placeholder {
23 | @apply opacity-100 u-transition-opacity u-duration-slow;
24 | color: $color;
25 | }
26 |
27 | &::-moz-placeholder {
28 | @apply opacity-100 u-transition-opacity u-duration-slow;
29 | color: $color;
30 | }
31 |
32 | &:-ms-input-placeholder {
33 | @apply opacity-100 u-transition-opacity u-duration-slow;
34 | color: $color;
35 | }
36 |
37 | &:-moz-placeholder {
38 | @apply opacity-100 u-transition-opacity u-duration-slow;
39 | color: $color;
40 | }
41 |
42 | &:focus {
43 | &::-webkit-input-placeholder {
44 | @apply opacity-50;
45 | }
46 |
47 | &::-moz-placeholder {
48 | @apply opacity-50;
49 | }
50 |
51 | &:-ms-input-placeholder {
52 | @apply opacity-50;
53 | }
54 |
55 | &:-moz-placeholder {
56 | @apply opacity-50;
57 | }
58 | }
59 | }
60 |
61 | // Input autofill
62 | @mixin autofill($border-color, $font-color, $background-color) {
63 | &:-webkit-autofill,
64 | &:-webkit-autofill:hover,
65 | &:-webkit-autofill:focus {
66 | border: 1px solid $border-color;
67 | -webkit-box-shadow: 0 0 0 1000px $background-color inset;
68 | -webkit-text-fill-color: $font-color;
69 | transition: background-color 5000s ease-in-out 0s;
70 | }
71 | }
72 |
73 | // Text Underline
74 | @mixin text-underline() {
75 | background: linear-gradient(currentColor, currentColor);
76 | background-position: 0 100%;
77 | background-repeat: no-repeat;
78 | background-size: 100% 0.1rem;
79 | }
80 |
81 | /* ***** Includes
82 | ** ***** ----------------------------------------------- ***** */
83 |
84 | @layer base {
85 | // Montserrat
86 | @include font-face('Montserrat', 'montserrat-light-webfont', 200);
87 | @include font-face('Montserrat', 'montserrat-regular-webfont');
88 | @include font-face('Montserrat', 'montserrat-bold-webfont', 700);
89 | @include font-face('Montserrat', 'montserrat-black-webfont', 800);
90 |
91 | // Open Sans
92 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap');
93 | }
94 |
--------------------------------------------------------------------------------
/.eleventy.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Eleventy Config
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | // Import transforms
6 | const parseContent = require('./eleventy/transforms/parseContent.js')
7 | const minifyHtml = require('./eleventy/transforms/minifyHtml.js')
8 | const addHeaderCredit = require('./eleventy/transforms/addHeaderCredit.js')
9 |
10 | // Import filters
11 | const absoluteUrl = require('./eleventy/filters/absoluteUrl.js')
12 | const cacheBust = require('./eleventy/filters/cacheBust.js')
13 | const htmlDate = require('./eleventy/filters/htmlDate.js')
14 | const readableDate = require('./eleventy/filters/readableDate.js')
15 | const rssLastUpdatedDate = require('./eleventy/filters/rssLastUpdatedDate.js')
16 | const rssDate = require('./eleventy/filters/rssDate.js')
17 | const articleUrl = require('./eleventy/filters/articleUrl.js')
18 | const articleCategoryUrl = require('./eleventy/filters/articleCategoryUrl.js')
19 | const blocksToHtml = require('./eleventy/filters/blocksToHtml.js')
20 |
21 | // Import shortcodes
22 | const imageUrl = require('./eleventy/shortcodes/imageUrl.js')
23 | const imageSrcset = require('./eleventy/shortcodes/imageSrcset.js')
24 | const isSamePageOrSection = require('./eleventy/shortcodes/isSamePageOrSection.js')
25 | const svg = require('./eleventy/shortcodes/svg.js')
26 | const currentYear = require('./eleventy/shortcodes/currentYear.js')
27 |
28 | module.exports = function (config) {
29 | // Transforms
30 | config.addTransform('parseContent', parseContent)
31 | if (process.env.NODE_ENV !== 'development')
32 | config.addTransform('minifyHtml', minifyHtml)
33 | config.addTransform('addHeaderCredit', addHeaderCredit)
34 |
35 | // Filters
36 | config.addFilter('absoluteUrl', absoluteUrl)
37 | config.addFilter('cacheBust', cacheBust)
38 | config.addFilter('htmlDate', htmlDate)
39 | config.addFilter('readableDate', readableDate)
40 | config.addFilter('rssLastUpdatedDate', rssLastUpdatedDate)
41 | config.addFilter('rssDate', rssDate)
42 | config.addFilter('articleUrl', articleUrl)
43 | config.addFilter('articleCategoryUrl', articleCategoryUrl)
44 | config.addFilter('blocksToHtml', blocksToHtml)
45 |
46 | // Shortcodes
47 | config.addShortcode('imageUrl', imageUrl)
48 | config.addShortcode('imageSrcset', imageSrcset)
49 | config.addShortcode('isSamePageOrSection', isSamePageOrSection)
50 | config.addShortcode('svg', svg)
51 | config.addShortcode('currentYear', currentYear)
52 |
53 | // Layout aliases
54 | config.addLayoutAlias('base', 'layouts/base.njk')
55 | config.addLayoutAlias('default', 'layouts/default.njk')
56 | config.addLayoutAlias('page', 'layouts/page.njk')
57 |
58 | return {
59 | dir: {
60 | input: 'src/site',
61 | output: 'dist',
62 | },
63 | htmlTemplateEngine: 'njk',
64 | markdownTemplateEngine: 'njk',
65 | dataTemplateEngine: 'njk',
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/gulpfile.babel.js/tasks/styles.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Gulp - Styles
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | import autoprefixer from 'gulp-autoprefixer'
6 | import browserSync from 'browser-sync'
7 | import cleanCSS from 'gulp-clean-css'
8 | import concat from 'gulp-concat'
9 | import config from '../config'
10 | import dartSass from 'sass'
11 | import gif from 'gulp-if'
12 | import gulp from 'gulp'
13 | import gulpSass from 'gulp-sass'
14 | import header from 'gulp-header'
15 | import plumber from 'gulp-plumber'
16 | import postcss from 'gulp-postcss'
17 | import rename from 'gulp-rename'
18 | import reporter from 'postcss-reporter'
19 | import scss from 'postcss-scss'
20 | import size from 'gulp-size'
21 | import sourcemaps from 'gulp-sourcemaps'
22 | import stylelint from 'stylelint'
23 | import tailwindcss from 'tailwindcss'
24 |
25 | // Set default sass compiler
26 | const sass = gulpSass(dartSass)
27 |
28 | // Environment variables
29 | const isDevelopment = process.env.NODE_ENV === 'development'
30 |
31 | /*
32 | ** -- Lint scss files with Stylelint
33 | ** -- Create sourcemaps if in development mode (use gulp --production or gulp --staging to disable soucemaps)
34 | ** -- Compile scss files
35 | ** -- Apply Tailwind styles
36 | ** -- Autoprefix necessary properties
37 | ** -- Minify
38 | ** -- Add ByMattLee header to bundled file
39 | ** -- Print bundled file size
40 | ** -- Inject styles into page
41 | */
42 | const styles = () => {
43 | return gulp
44 | .src(config.styles.src)
45 | .pipe(plumber())
46 | .pipe(
47 | postcss(
48 | [
49 | stylelint(),
50 | reporter({
51 | clearMessages: true,
52 | }),
53 | ],
54 | {
55 | syntax: scss,
56 | }
57 | )
58 | )
59 | .pipe(gif(isDevelopment, sourcemaps.init()))
60 | .pipe(sass().on('error', sass.logError))
61 | .pipe(
62 | postcss(
63 | [
64 | tailwindcss(config.styles.tailwindConfig),
65 | reporter({
66 | clearMessages: true,
67 | }),
68 | ],
69 | {
70 | syntax: scss,
71 | }
72 | )
73 | )
74 | .pipe(autoprefixer())
75 | .pipe(
76 | cleanCSS({
77 | inline: ['all'],
78 | rebase: false,
79 | })
80 | )
81 | .pipe(concat('main.css'))
82 | .pipe(
83 | rename({
84 | suffix: '.min',
85 | })
86 | )
87 | .pipe(header(config.fileHeader.join('\n')))
88 | .pipe(
89 | size({
90 | title: 'Compressed File Size:',
91 | showFiles: true,
92 | })
93 | )
94 | .pipe(gif(isDevelopment, sourcemaps.write('./')))
95 | .pipe(gulp.dest(config.styles.dest))
96 | .pipe(
97 | browserSync.stream({
98 | match: '**/*.css',
99 | })
100 | )
101 | }
102 |
103 | export default styles
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bymattlee-11ty-starter",
3 | "version": "1.0.0",
4 | "description": "ByMattLee 11ty Starter",
5 | "homepage": "http://bymattlee.com",
6 | "author": "Matt Lee
",
7 | "engines": {
8 | "node": "16.17.1"
9 | },
10 | "scripts": {
11 | "start": "yarn dev",
12 | "dev": "NODE_ENV=development gulp",
13 | "stage": "NODE_ENV=staging gulp build",
14 | "prod": "NODE_ENV=production gulp build"
15 | },
16 | "keywords": [
17 | "ByMattLee",
18 | "11ty",
19 | "Starter"
20 | ],
21 | "license": "MIT",
22 | "repository": {
23 | "type": "git",
24 | "url": "git://github.com/bymattlee/bymattlee-11ty-starter.git"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/bymattlee/bymattlee-11ty-starter/issues"
28 | },
29 | "devDependencies": {
30 | "@11ty/eleventy": "^2.0.1",
31 | "@babel/core": "^7.22.9",
32 | "@babel/eslint-parser": "^7.22.9",
33 | "@babel/preset-env": "^7.22.9",
34 | "@babel/register": "^7.22.5",
35 | "@sanity/block-content-to-html": "^2.0.0",
36 | "@sanity/client": "^3.4.1",
37 | "@sanity/image-url": "^1.0.2",
38 | "@tailwindcss/aspect-ratio": "^0.4.2",
39 | "browser-sync": "^2.29.3",
40 | "del": "^6.0.0",
41 | "eslint": "^8.44.0",
42 | "flat-map": "^1.0.0",
43 | "get-video-id": "^3.6.5",
44 | "get-youtube-id": "^1.0.1",
45 | "gulp": "^4.0.2",
46 | "gulp-add-src": "^1.0.0",
47 | "gulp-autoprefixer": "^8.0.0",
48 | "gulp-better-rollup": "^4.0.1",
49 | "gulp-changed": "^4.0.3",
50 | "gulp-clean-css": "^4.3.0",
51 | "gulp-concat": "^2.6.1",
52 | "gulp-eslint": "^6.0.0",
53 | "gulp-header": "^2.0.9",
54 | "gulp-if": "^3.0.0",
55 | "gulp-imagemin": "^7.1.0",
56 | "gulp-plumber": "^1.2.1",
57 | "gulp-postcss": "^9.0.1",
58 | "gulp-rename": "^2.0.0",
59 | "gulp-run-command": "^0.0.10",
60 | "gulp-sass": "^5.1.0",
61 | "gulp-scale-images": "^3.0.2",
62 | "gulp-size": "^4.0.1",
63 | "gulp-sourcemaps": "^3.0.0",
64 | "gulp-svg-sprite": "^2.0.3",
65 | "gulp-terser": "^2.1.0",
66 | "html-minifier": "^4.0.0",
67 | "image-size": "^1.0.2",
68 | "imagemin-jpeg-recompress": "^7.1.0",
69 | "jsdom": "^22.1.0",
70 | "luxon": "^3.3.0",
71 | "path": "^0.12.7",
72 | "postcss": "^8.4.26",
73 | "postcss-reporter": "^7.0.5",
74 | "postcss-scss": "^4.0.6",
75 | "regenerator-runtime": "^0.13.11",
76 | "rollup": "^3.26.2",
77 | "rollup-plugin-babel": "^4.4.0",
78 | "rollup-plugin-commonjs": "^10.1.0",
79 | "rollup-plugin-json": "^4.0.0",
80 | "rollup-plugin-node-resolve": "^5.2.0",
81 | "sass": "^1.63.6",
82 | "stylelint": "^15.10.1",
83 | "stylelint-config-standard": "^34.0.0",
84 | "stylelint-order": "^6.0.3",
85 | "tailwindcss": "^3.3.3",
86 | "through2": "^4.0.2"
87 | },
88 | "dependencies": {
89 | "@unseenco/taxi": "^1.3.0",
90 | "alpinejs": "^3.12.3",
91 | "gsap": "^3.12.2",
92 | "lazysizes": "^5.3.2",
93 | "throttle-debounce": "^5.0.0"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/assets/scss/main.scss:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Main
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | /*
6 | ** All CSS classes use namespaces to further improve organization.
7 | **
8 | ** -- (u-) Utility classes have a very specific role and usually contain one declaration.
9 | ** - Example: A class that hides the element on mobile, a class that bolds the text, etc.
10 | **
11 | ** -- (l-) Layout classes provides structure that hold the site elements together.
12 | ** - Example: Containers, grids, etc.
13 | **
14 | ** -- (o-) Objects are the smallest of all elements. Should be reused as much as possible.
15 | ** They are either used multiple times on a page and/or in multiple contexts on a site.
16 | ** - Example: social icons, news posts, buttons, section titles, etc.
17 | **
18 | ** -- (c-) Components are also known as modules that are made up of smaller, related elements.
19 | ** They are used at most once per page in one type of context.
20 | ** - Example: A header is a component and is made up of smaller elements such as the
21 | ** site's logo, navigation, social icons, etc.
22 | **
23 | ** -- (js-) JavaScript classes do not have any styling attached to them. They are used to
24 | ** signify that there is some behavior attached to it.
25 | ** - Example: A button that toggles the menu, a social icon that opens a popup window, etc.
26 | **
27 | ** -- (t-) Theme classes modify the color palette of the site. Usually used on the body element.
28 | ** - Example: A dark theme that utilizes a lighter type color on a dark background.
29 | **
30 | ** -- (s-) State classes signify the current state of an element.
31 | ** - Example: A mobile menu that is open when the menu button is clicked.
32 | **
33 | ** -- (p-) Page classes identifies the current page.
34 | ** - Example: The home page will use the class '.p-home'.
35 | */
36 |
37 | /* ***** Tailwind
38 | ** ***** ----------------------------------------------- ***** */
39 |
40 | @tailwind base;
41 | @tailwind components;
42 | @tailwind utilities;
43 |
44 | /* ***** Setup
45 | ** ***** ----------------------------------------------- ***** */
46 |
47 | @import '1-setup/type';
48 | @import '1-setup/base';
49 |
50 | /* ***** Utilities
51 | ** ***** ----------------------------------------------- ***** */
52 |
53 | @import '2-utilities/layout';
54 | @import '2-utilities/type';
55 |
56 | /* ***** Vendors
57 | ** ***** ----------------------------------------------- ***** */
58 |
59 | @import '3-vendors/lazysizes';
60 | @import '3-vendors/s2r';
61 |
62 | /* ***** Objects
63 | ** ***** ----------------------------------------------- ***** */
64 |
65 | @import '4-objects/button';
66 |
67 | /* ***** Components
68 | ** ***** ----------------------------------------------- ***** */
69 |
70 | // @import '5-components/component';
71 |
72 | /* ***** Misc
73 | ** ***** ----------------------------------------------- ***** */
74 |
75 | @import '6-misc/hacks';
76 | @import '6-misc/print';
77 |
--------------------------------------------------------------------------------
/src/site/_includes/partials/global/meta.njk:
--------------------------------------------------------------------------------
1 | {% set pageTitle = title + ' | ' + settings.seo.siteName %}
2 | {% set shareTitle = title %}
3 | {% set pageDescription = pageDescription or settings.seo.siteDescription %}
4 | {% set shareImage %}{% imageUrl settings.seo.siteShareImage, 1200 %}{% endset %}
5 |
6 | {% if metaTitle %}
7 | {% set pageTitle = metaTitle %}
8 | {% set shareTitle = metaTitle %}
9 | {% endif %}
10 |
11 | {% if metaDescription %}
12 | {% set pageDescription = metaDescription %}
13 | {% endif %}
14 |
15 | {% if featuredImage %}
16 | {% set shareImage = featuredImage %}
17 | {% endif %}
18 |
19 | {% if metaShareImage %}
20 | {% set shareImage = metaShareImage %}
21 | {% endif %}
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ pageTitle }}
29 |
30 | {% if settings.favicons.favicon %}
31 |
32 |
33 | {% endif %}
34 |
35 | {% if settings.favicons.appleTouchIcon %}
36 |
37 |
38 | {% endif %}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | {% if settings.social.twitterHandle %}
62 |
63 |
64 | {% endif %}
65 |
66 | {% if 'p-home' in bodyClasses %}
67 |
86 | {% endif %}
87 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const config = require('./gulpfile.babel.js/config.js')
2 | const { pxToEm, fontFallbacks } = require('./tailwindUtilities.js')
3 |
4 | module.exports = {
5 | mode: 'jit',
6 | prefix: 'u-',
7 | theme: {
8 | screens: {
9 | xs: pxToEm(480),
10 | sm: pxToEm(768),
11 | md: pxToEm(1024),
12 | lg: pxToEm(1280),
13 | xl: pxToEm(1440),
14 | xxl: pxToEm(1800),
15 | },
16 | colors: {
17 | transparent: 'transparent',
18 | current: 'currentColor',
19 | black: '#000',
20 | white: '#fff',
21 | 'grey-dark': {
22 | 1: '#111',
23 | 2: '#222',
24 | 3: '#333',
25 | 4: '#444',
26 | 5: '#555',
27 | 6: '#666',
28 | },
29 | 'grey-light': {
30 | a: '#aaa',
31 | c: '#ccc',
32 | },
33 | },
34 | fontFamily: {
35 | body: ['Open Sans', ...fontFallbacks],
36 | heading: ['Montserrat', ...fontFallbacks],
37 | },
38 | fontSize: {
39 | 0: '0',
40 | 10: '1rem',
41 | 12: '1.2rem',
42 | 14: '1.4rem',
43 | 16: '1.6rem',
44 | 18: '1.8rem',
45 | 22: '2.2rem',
46 | 24: '2.4rem',
47 | 28: '2.8rem',
48 | 30: '3rem',
49 | 36: '3.6rem',
50 | 48: '4.8rem',
51 | },
52 | lineHeight: {
53 | none: '1',
54 | tight: '1.2',
55 | normal: '1.4',
56 | loose: '1.8',
57 | },
58 | letterSpacing: {
59 | tightest: '-0.1rem',
60 | tighter: '-0.05rem',
61 | tight: '-0.025rem',
62 | normal: '0',
63 | wide: '0.025rem',
64 | wider: '0.05rem',
65 | widest: '0.1rem',
66 | },
67 | spacing: {
68 | 0: '0',
69 | 1: '0.1rem',
70 | 2: '0.2rem',
71 | 3: '0.3rem',
72 | 4: '0.4rem',
73 | 5: '0.5rem',
74 | 6: '0.6rem',
75 | 7: '0.7rem',
76 | 8: '0.8rem',
77 | 9: '0.9rem',
78 | 10: '1rem',
79 | 15: '1.5rem',
80 | 20: '2rem',
81 | 25: '2.5rem',
82 | 30: '3rem',
83 | 35: '3.5rem',
84 | 40: '4rem',
85 | 45: '4.5rem',
86 | 50: '5rem',
87 | 55: '5.5rem',
88 | 60: '6rem',
89 | 65: '6.5rem',
90 | 70: '7rem',
91 | 75: '7.5rem',
92 | 80: '8rem',
93 | 85: '8.5rem',
94 | 90: '9rem',
95 | 95: '9.5rem',
96 | 100: '10rem',
97 | 110: '11rem',
98 | 120: '12rem',
99 | 130: '13rem',
100 | 140: '14rem',
101 | 150: '15rem',
102 | 160: '16rem',
103 | 170: '17rem',
104 | 180: '18rem',
105 | 190: '19rem',
106 | 200: '20rem',
107 | 210: '21rem',
108 | 220: '22rem',
109 | 230: '23rem',
110 | 240: '24rem',
111 | 250: '25rem',
112 | },
113 | borderWidth: {
114 | DEFAULT: '0.1rem',
115 | 0: '0',
116 | 1: '0.1rem',
117 | 2: '0.2rem',
118 | 4: '0.3rem',
119 | 6: '0.4rem',
120 | 8: '0.8rem',
121 | },
122 | borderRadius: {
123 | DEFAULT: '1rem',
124 | none: '0',
125 | full: '9999rem',
126 | },
127 | zIndex: {
128 | auto: 'auto',
129 | below: '-1',
130 | 0: '0',
131 | 1: '10',
132 | 2: '20',
133 | 3: '30',
134 | 4: '40',
135 | 5: '50',
136 | 6: '60',
137 | 7: '70',
138 | 8: '80',
139 | 9: '90',
140 | 10: '100',
141 | header: '500',
142 | 'menu-overlay': '400',
143 | },
144 | transitionDuration: {
145 | fast: '150ms',
146 | normal: '300ms',
147 | slow: '500ms',
148 | },
149 | transitionTimingFunction: {
150 | linear: 'linear',
151 | in: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)',
152 | out: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
153 | 'in-out': 'cubic-bezier(0.645, 0.045, 0.355, 1)',
154 | },
155 | },
156 | corePlugins: {
157 | container: false,
158 | aspectRatio: false,
159 | },
160 | plugins: [require('@tailwindcss/aspect-ratio')],
161 | content: config.styles.purgeContent,
162 | future: {
163 | hoverOnlyWhenSupported: true,
164 | },
165 | }
166 |
--------------------------------------------------------------------------------
/gulpfile.babel.js/tasks/images.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | /* ***** Gulp - Images
3 | /* ***** ----------------------------------------------- ***** */
4 |
5 | import changed from 'gulp-changed'
6 | import config from '../config'
7 | import flatMap from 'flat-map'
8 | import gulp from 'gulp'
9 | import imagemin from 'gulp-imagemin'
10 | import imageminJpegRecompress from 'imagemin-jpeg-recompress'
11 | import path from 'path'
12 | import readMetadata from 'gulp-scale-images/read-metadata'
13 | import scaleImages from 'gulp-scale-images'
14 | import size from 'gulp-size'
15 | import through from 'through2'
16 |
17 | /*
18 | ** -- Check if image is already in dist directory and has changed
19 | ** -- Optimize image for production
20 | */
21 | const imagesOptimize = () => {
22 | return gulp
23 | .src(config.images.optimizeSrc)
24 | .pipe(changed(config.images.dest))
25 | .pipe(
26 | imagemin([
27 | imagemin.gifsicle({ interlaced: true }),
28 | imageminJpegRecompress(),
29 | imagemin.optipng({ optimizationLevel: 5 }),
30 | imagemin.svgo(),
31 | ])
32 | )
33 | .pipe(
34 | size({
35 | title: 'Optimized File Size:',
36 | showFiles: true,
37 | })
38 | )
39 | .pipe(gulp.dest(config.images.dest))
40 | }
41 |
42 | /*
43 | ** -- Check if image is already in dist directory and has changed
44 | ** -- Generate responsive images by resizing them to various sizes
45 | */
46 | const getFileWidth = function (file, _, callback) {
47 | readMetadata(file, function (error, meta) {
48 | if (error) return callback(error)
49 | file.width = meta.width
50 | callback(null, file)
51 | })
52 | }
53 |
54 | const imageVariants = function (file, callback) {
55 | const imageSizes = []
56 |
57 | if (file.width >= 200) {
58 | const img200 = file.clone()
59 | img200.scale = { maxWidth: 200 }
60 | imageSizes.push(img200)
61 | }
62 |
63 | if (file.width >= 400) {
64 | const img400 = file.clone()
65 | img400.scale = { maxWidth: 400 }
66 | imageSizes.push(img400)
67 | }
68 |
69 | if (file.width >= 600) {
70 | const img600 = file.clone()
71 | img600.scale = { maxWidth: 600 }
72 | imageSizes.push(img600)
73 | }
74 |
75 | if (file.width >= 800) {
76 | const img800 = file.clone()
77 | img800.scale = { maxWidth: 800 }
78 | imageSizes.push(img800)
79 | }
80 |
81 | if (file.width >= 1000) {
82 | const img1000 = file.clone()
83 | img1000.scale = { maxWidth: 1000 }
84 | imageSizes.push(img1000)
85 | }
86 |
87 | if (file.width >= 1200) {
88 | const img1200 = file.clone()
89 | img1200.scale = { maxWidth: 1200 }
90 | imageSizes.push(img1200)
91 | }
92 |
93 | if (file.width >= 1400) {
94 | const img1400 = file.clone()
95 | img1400.scale = { maxWidth: 1400 }
96 | imageSizes.push(img1400)
97 | }
98 |
99 | if (file.width >= 1600) {
100 | const img1600 = file.clone()
101 | img1600.scale = { maxWidth: 1600 }
102 | imageSizes.push(img1600)
103 | }
104 |
105 | if (file.width >= 1800) {
106 | const img1800 = file.clone()
107 | img1800.scale = { maxWidth: 1800 }
108 | imageSizes.push(img1800)
109 | }
110 |
111 | callback(null, imageSizes)
112 | }
113 |
114 | const newFileName = function (output, scale, callback) {
115 | const fileName =
116 | path.basename(output.path, output.extname) +
117 | '-' +
118 | scale.maxWidth +
119 | 'w' +
120 | output.extname
121 | callback(null, fileName)
122 | }
123 |
124 | function imagesGenerateResponsive() {
125 | return gulp
126 | .src(config.images.responsiveSrc)
127 | .pipe(changed(config.images.dest))
128 | .pipe(through.obj(getFileWidth))
129 | .pipe(flatMap(imageVariants))
130 | .pipe(scaleImages(newFileName))
131 | .pipe(
132 | imagemin([
133 | imagemin.gifsicle({ interlaced: true }),
134 | imageminJpegRecompress(),
135 | imagemin.optipng({ optimizationLevel: 5 }),
136 | imagemin.svgo(),
137 | ])
138 | )
139 | .pipe(
140 | size({
141 | title: 'Optimized File Size:',
142 | showFiles: true,
143 | })
144 | )
145 | .pipe(gulp.dest(config.images.dest))
146 | }
147 |
148 | const images = gulp.parallel(imagesOptimize, imagesGenerateResponsive)
149 |
150 | export default images
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # ByMattLee 11ty Starter
4 |
5 | A starter boilerplate powered by [11ty](https://www.11ty.dev/), [Sanity](https://www.sanity.io/), [Gulp](https://gulpjs.com/), [Tailwind CSS](https://tailwindcss.com/), [rollup.js](https://rollupjs.org/), [Alpine.js](https://alpinejs.dev/) and [Taxi.js](https://taxi.js.org/).
6 |
7 | [VIEW DEMO](https://bymattlee-11ty-starter.netlify.app/)
8 |
9 | ---
10 |
11 | ## Installation
12 |
13 | ##### 1. Install Node 16.17.1 LTS:
14 |
15 | ##### 2. Install Yarn
16 |
17 | ```
18 | $ npm i -g yarn
19 | ```
20 |
21 | ##### 3. Install all dependencies
22 |
23 | ```
24 | $ yarn
25 | ```
26 |
27 | ---
28 |
29 | ## Configuration
30 |
31 | In `config.js`, update Sanity options and environment URLs for project compilation.
32 |
33 | ---
34 |
35 | ## Use
36 |
37 | ##### Build Files For Development And Initialize Watch
38 |
39 | ```
40 | $ yarn dev
41 | ```
42 |
43 | ##### Build Files For Staging
44 |
45 | ```
46 | $ yarn stage
47 | ```
48 |
49 | ##### Build Files For Production
50 |
51 | ```
52 | $ yarn prod
53 | ```
54 |
55 | ---
56 |
57 | ## General Features
58 |
59 | - Features [Gulp](https://gulpjs.com/) as the build pipeline that renders HTML, compiles styles and scripts, and optimizes assets.
60 | - Data is coming from the Sanity Cloud API. This pairs perfectly with the [ByMattLee Sanity Studio Starter](https://github.com/bymattlee/bymattlee-sanity-studio-starter)
61 | - Spins up a local development environment through Browsersync
62 | - Browser reload when NJK, JS, image, SVG or asset files are updated
63 | - Styles are injected when SCSS files are updated
64 |
65 | ##### Markup
66 |
67 | - Features [11ty](https://www.11ty.dev/) as the static site generator
68 | - Markup is minified in staging and production environments
69 | - Site data can be set in `src/site/_data`
70 | - Pages in `src/site` will be compiled to `dist`
71 | - `src/site/_includes/layouts/base.njk` serves as the base template for the site
72 | - `src/site/_includes/partials` contains site partials and components that are reused across the site (modular HTML)
73 |
74 | ##### Styles
75 |
76 | - Features [Tailwind CSS](https://tailwindcss.com/), a utility-first framework
77 | - Includes linter (Stylelint), autoprefixer, minification and sourcemap creation
78 | - SCSS files are located in `src/assets/scss`
79 | - `main.scss` in `src/assets/scss` serves as the base that includes the other dependent SASS files
80 | - `main.scss` gets compiled to `dist/assets/css/main.min.css`
81 | - `.stylelintrc` contains the settings for Stylelint
82 | - Info about class namespacing can be found in `src/assets/scss/main.scss`
83 | - Unused CSS (PurgeCSS) will be removed in staging and production builds
84 |
85 | ##### Scripts
86 |
87 | - Features [rollup.js](https://rollupjs.org/guide/en/) as the module bundler
88 | - Includes linting (ESLint), concatenation, minification and sourcemap creation
89 | - Includes [Taxi.js](https://taxi.js.org/) for seamless page transitions
90 | - Includes a custom framework for reuseable content animations
91 | - Includes [Alpine.js](https://alpinejs.dev/) for declarative DOM manipulation
92 | - `main.js` in `src/assets/js` serves as the main JS file that includes and runs all components and will be compiled to `dist/assets/js/main.min.js`
93 | - All local components should be placed in `src/assets/js/components`
94 | - All vendor JS can be manually added to `src/assets/js/vendors` if not found on Yarn
95 | - `.eslintrc` contains the settings for ESLint
96 |
97 | ##### Images
98 |
99 | - Place all unoptimized images in the `src/assets/images` directory
100 | - They will then be optimized and placed in `dist/assets/images`
101 | - Responsive images will be generated at the following widths (if larger): 200w, 400w, 600w, 800w, 1000w, 1200w, 1400w, 1600w, 1800w
102 | - Use `imageSrc` shortcode to render responsive `srcset` in markup
103 |
104 | ##### SVGS
105 |
106 | - Place all SVG files in the `src/assets/svg` directory
107 | - They will then be optimized and added to a sprite at `dist/assets/svg/sprite.svg`
108 |
109 | ##### Other Assets
110 |
111 | - All assets (fonts, videos, swfs, etc) under `src/assets` will be copied to `dist/assets` on build
112 |
113 | ##### Sitemap
114 |
115 | - Generates a sitemap based on the HTML files in `dist`
116 |
117 | ##### Robots.txt
118 |
119 | - Generates a robots.txt file
120 | - Configuration can be found in `src/site/robots.njk`
121 |
122 | ---
123 |
124 | ## Tailwind CSS Notes
125 |
126 | - All Tailwind settings can be found in `./tailwind.config.js`
127 | - Most styles should be written as utility classes in the template markup but custom SCSS can be used for unique properties and/or magic numbers
128 | - Utilize the [`@apply`](https://tailwindcss.com/docs/functions-and-directives#apply) directive when writing custom SCSS for efficiency:
129 |
130 | ```scss
131 | svg {
132 | @apply u-inline-block u-fill-current;
133 | }
134 | ```
135 |
136 | - Tailwind settings can be accessed with the [`theme()`](https://tailwindcss.com/docs/functions-and-directives#theme) function:
137 |
138 | ```scss
139 | .button {
140 | animation-duration: theme('transitionDuration.normal');
141 | }
142 | ```
143 |
144 | - Media queries can be used with the shorthand `screen()` function:
145 |
146 | ```scss
147 | .button {
148 | margin-right: 2.8rem;
149 | @screen sm {
150 | margin-right: 4.2rem;
151 | }
152 | }
153 | ```
154 |
155 | ---
156 |
157 | ## Contact
158 |
159 | - Matt Lee - `@bymattlee` on most platforms
160 | - Visit my website at [bymattlee.com](https://bymattlee.com)
161 |
162 | [](https://twitter.com/bymattlee)
163 |
--------------------------------------------------------------------------------
/src/assets/js/vendors/Scroll2Reveal.js:
--------------------------------------------------------------------------------
1 | /* ***** ----------------------------------------------- ***** **
2 | ** ***** Scroll2Reveal JS
3 | ** ***** ----------------------------------------------- ***** */
4 |
5 | /*
6 |
7 | Scroll2Reveal - Display elements are they come into view
8 |
9 | Requires: GSAP 3+
10 |
11 | Two implementation approaches:
12 |
13 | [1] Group Reveal
14 | - Details
15 | - Once a group container comes into view, the elements in the group animate one-by-one
16 | - Each element each have a different animation style
17 | - Usage
18 | - Add this selector to the group container - [data-s2r="group"]
19 | - Add this selector to the child elements that will animate - [data-s2r-el="XXX"]
20 | - Replace "XXX" with the animation style. Choose from:
21 | - "block-fade-up" - Animates the element by fading up
22 | - "block-fade-in" - Animtes the element by fading in
23 | - "stagger-fade-up" - Stagger animate child elements by fading up. Automatically selects child elements
24 | - "stagger-fade-in" - Stagger animate child elements by fading in. Automatically selects child elements
25 | - Use the following selectors for custom options
26 | - Delay - [data-s2r-delay="0.1"]
27 | - Duration - [data-s2r-duration="0.6"]
28 |
29 | [2] Single Reveal
30 | - Details
31 | - Once an element comes into view, the element animates in
32 | - Usage
33 | - Add this selector to the element - [data-s2r="single"]
34 | - Add this selector to the element to specify the animation type - [data-s2r-type="XXX"]
35 | - Replace "XXX" with the animation style. Choose from:
36 | - "block-fade-up" - Animates the element by fading up
37 | - "block-fade-in" - Animates the element by fading in
38 | - "stagger-fade-up" - Stagger animate child elements by fading up. Add this selector to each child element - [data-s2r-el]
39 | - "stagger-fade-in" - Stagger animate child elements by fading in. Add this selector to each child element - [data-s2r-el]
40 | - "stagger-fade-up-children" - Stagger animate child elements by fading up. Automatically selects child elements
41 | - "stagger-fade-in-children" - Stagger animate child elements by fading in. Automatically selects child elements
42 | - Use the following selectors for custom options
43 | - Delay - [data-s2r-delay="0.1"]
44 | - Duration - [data-s2r-duration="0.6"]
45 | - Stagger (works for "stagger-fade-up" and "stagger-fade-in") - [data-s2r-stagger="0.1"]
46 |
47 | */
48 |
49 | import gsap from 'gsap'
50 |
51 | class Scroll2Reveal {
52 | constructor() {
53 | // Check if s2r elements are present. If so, do nothing
54 | this.s2r = document.querySelectorAll('[data-s2r]')
55 | if (!this.s2r.length) return
56 |
57 | // Define global, reusable vars
58 | this.s2rActive
59 | this.observer
60 | this.initDelay = 100
61 |
62 | // Init
63 | window.addEventListener('DOMContentLoaded', () => {
64 | this.init()
65 | })
66 | }
67 |
68 | init() {
69 | // Add init delay to allow page to load before animating
70 | setTimeout(() => {
71 | this.reveal()
72 | }, this.initDelay)
73 | }
74 |
75 | reveal() {
76 | // Check if element has already animated, if so, do nothing
77 | this.s2rActive = document.querySelectorAll('[data-s2r]:not(.s2r-in-view)')
78 | if (!this.s2rActive.length) return
79 |
80 | this.observer = new IntersectionObserver(
81 | (entries) => {
82 | entries.forEach((entry) => {
83 | if (entry.isIntersecting) {
84 | const thisEl = entry.target
85 |
86 | if (thisEl.classList.contains('s2r-in-view')) return
87 |
88 | thisEl.classList.add('s2r-in-view')
89 |
90 | if (thisEl.dataset.s2r === 'group') {
91 | this.groupReveal(thisEl)
92 | } else if (thisEl.dataset.s2r === 'single') {
93 | this.singleReveal(thisEl)
94 | }
95 | }
96 | })
97 | },
98 | { rootMargin: `0px 0px ${window.innerHeight * -0.2}px 0px` }
99 | )
100 |
101 | this.s2rActive.forEach((el) => {
102 | this.observer.observe(el)
103 | })
104 | }
105 |
106 | groupReveal(el) {
107 | const childEl = el.querySelectorAll('[data-s2r-el]')
108 |
109 | // Select each child element and animate
110 | childEl.forEach((el) => {
111 | const thisElType = el.dataset.s2rEl
112 | const delay = el.dataset.s2rDelay || 0
113 | const duration = el.dataset.s2rDuration || 0.8
114 |
115 | if (thisElType === 'block-fade-up') {
116 | gsap.fromTo(
117 | el,
118 | { opacity: 0, y: '40px' },
119 | {
120 | opacity: 1,
121 | y: '0px',
122 | duration: duration,
123 | delay: delay,
124 | ease: 'power2.out',
125 | }
126 | )
127 | } else if (thisElType === 'block-fade-in') {
128 | gsap.fromTo(
129 | el,
130 | { opacity: 0 },
131 | { opacity: 1, duration: duration, delay: delay, ease: 'power2.out' }
132 | )
133 | } else if (thisElType === 'stagger-fade-up') {
134 | const stagger = el.dataset.s2rStagger || 0.1
135 | const staggerEl = el.children
136 |
137 | gsap.fromTo(
138 | staggerEl,
139 | { opacity: 0, y: '40px' },
140 | {
141 | opacity: 1,
142 | y: '0px',
143 | stagger: stagger,
144 | duration: duration,
145 | delay: delay,
146 | ease: 'power2.out',
147 | }
148 | )
149 | } else if (thisElType === 'stagger-fade-in') {
150 | const stagger = el.dataset.s2rStagger || 0.1
151 | const staggerEl = el.children
152 |
153 | gsap.fromTo(
154 | staggerEl,
155 | { opacity: 0 },
156 | {
157 | opacity: 1,
158 | stagger: stagger,
159 | duration: duration,
160 | delay: delay,
161 | ease: 'power2.out',
162 | }
163 | )
164 | }
165 | })
166 | }
167 |
168 | singleReveal(el) {
169 | const childEl = el.querySelectorAll('[data-s2r-el]')
170 | const thisElType = el.dataset.s2rType
171 | const delay = el.dataset.s2rDelay || 0
172 | const duration = el.dataset.s2rDuration || 0.8
173 | const stagger = el.dataset.s2rStagger || 0.1
174 |
175 | // Play the animation on the single element
176 | if (thisElType === 'block-fade-up') {
177 | gsap.fromTo(
178 | el,
179 | { opacity: 0, y: '40px' },
180 | {
181 | opacity: 1,
182 | y: '0px',
183 | duration: duration,
184 | delay: delay,
185 | ease: 'power2.out',
186 | }
187 | )
188 | } else if (thisElType === 'block-fade-in') {
189 | gsap.fromTo(
190 | el,
191 | { opacity: 0 },
192 | { opacity: 1, duration: duration, delay: delay, ease: 'power2.out' }
193 | )
194 | } else if (thisElType === 'stagger-fade-up') {
195 | gsap.fromTo(
196 | childEl,
197 | { opacity: 0, y: '40px' },
198 | {
199 | opacity: 1,
200 | y: '0px',
201 | stagger: stagger,
202 | duration: duration,
203 | delay: delay,
204 | ease: 'power2.out',
205 | }
206 | )
207 | } else if (thisElType === 'stagger-fade-in') {
208 | gsap.fromTo(
209 | childEl,
210 | { opacity: 0 },
211 | {
212 | opacity: 1,
213 | stagger: stagger,
214 | duration: duration,
215 | delay: delay,
216 | ease: 'power2.out',
217 | }
218 | )
219 | } else if (thisElType === 'stagger-fade-up-children') {
220 | const staggerEl = el.children
221 | gsap.fromTo(
222 | staggerEl,
223 | { opacity: 0, y: '40px' },
224 | {
225 | opacity: 1,
226 | y: '0px',
227 | stagger: stagger,
228 | duration: duration,
229 | delay: delay,
230 | ease: 'power2.out',
231 | }
232 | )
233 | } else if (thisElType === 'stagger-fade-in-children') {
234 | const staggerEl = el.children
235 | gsap.fromTo(
236 | staggerEl,
237 | { opacity: 0 },
238 | {
239 | opacity: 1,
240 | stagger: stagger,
241 | duration: duration,
242 | delay: delay,
243 | ease: 'power2.out',
244 | }
245 | )
246 | }
247 | }
248 |
249 | reInit() {
250 | // If s2r is undefined on reInit, init again
251 | // This can happen if s2r was initialized on an empty page and reInit was called on new page load with Highway
252 | if (typeof this.observer === 'undefined') {
253 | this.init()
254 | } else {
255 | this.s2rActive = document.querySelectorAll('[data-s2r]:not(.s2r-in-view)')
256 | this.s2rActive.forEach((el) => {
257 | // Add init delay to allow page to load before animating
258 | setTimeout(() => {
259 | this.observer.observe(el)
260 | }, this.initDelay)
261 | })
262 | }
263 | }
264 | }
265 |
266 | export default Scroll2Reveal
267 |
--------------------------------------------------------------------------------