├── src ├── _data │ ├── layout.js │ └── meta.js ├── _includes │ ├── default.liquid │ ├── footer.html │ └── header.html ├── index.html ├── css │ └── global.css ├── js │ └── dots.js └── images │ └── learn-with-jason-dark.svg ├── .gitignore ├── netlify.toml ├── .eleventy.js ├── package.json ├── LICENSE └── README.md /src/_data/layout.js: -------------------------------------------------------------------------------- 1 | module.exports = 'default'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | _site 3 | 4 | # Local Netlify folder 5 | .netlify -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "_site" 4 | functions = "functions" 5 | 6 | [dev] 7 | command = "npm run dev" 8 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | module.exports = (config) => { 2 | config.addPassthroughCopy('src/css'); 3 | config.addPassthroughCopy('src/images'); 4 | config.addPassthroughCopy('src/js'); 5 | 6 | return { 7 | dir: { 8 | input: 'src', 9 | }, 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "1.0.0", 4 | "name": "learnwithjason-demo-base", 5 | "author": "Jason Lengstorf ", 6 | "scripts": { 7 | "dev": "eleventy --serve", 8 | "build": "eleventy" 9 | }, 10 | "devDependencies": { 11 | "@11ty/eleventy": "^0.11.0" 12 | }, 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /src/_data/meta.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // keep it short! shown in the header 3 | title: 'Demo of the Demo', 4 | 5 | // these are all optional and add links to the footer 6 | repo: 'learnwithjason/demo-base', 7 | episode: 8 | 'https://www.learnwithjason.dev/creating-css-variable-font-text-effects', 9 | tutorial: 'https://codepen.io/jlengstorf/pen/QWbdLjb', 10 | }; 11 | -------------------------------------------------------------------------------- /src/_includes/default.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ meta.title }} · Learn With Jason 7 | 8 | 9 | 10 | 11 | {% include header.html %} 12 | 13 |
14 | {{ content }} 15 |
16 | 17 | {% include footer.html %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Jason Lengstorf 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 39 | 40 |

This Is a Demo of the Demo

41 | 42 |
43 |

boop.

44 |
45 | -------------------------------------------------------------------------------- /src/css/global.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --pink: #d459ab; 3 | --pink-text: #dd78bc; /* lightened up to meet a11y guidelines */ 4 | --yellow: #ffdf37; 5 | --blue: #a6fffa; 6 | --light-gray: #e0e0e0; 7 | --dark-gray: #1a2a3b; 8 | --gray: #3f4f61; 9 | --black: #011627; 10 | --black-transparent: #01162700; 11 | --white: white; 12 | --lwj-gradient: linear-gradient( 13 | 90deg, 14 | var(--pink) 0%, 15 | var(--yellow) 25%, 16 | var(--blue) 50%, 17 | var(--yellow) 75%, 18 | var(--pink) 100% 19 | ); 20 | --text: var(--gray); 21 | --text-muted: #687889; 22 | --font-family: mallory, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 23 | Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 24 | 'Segoe UI Symbol'; 25 | } 26 | 27 | * { 28 | box-sizing: border-box; 29 | } 30 | 31 | html, 32 | body { 33 | background: var(--white); 34 | color: var(--text); 35 | font-family: mallory, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 36 | Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 37 | 'Segoe UI Symbol'; 38 | font-weight: 400; 39 | margin: 0; 40 | } 41 | 42 | body { 43 | min-height: 100vh; 44 | } 45 | 46 | h1, 47 | h2, 48 | h3, 49 | h4, 50 | h5, 51 | h6 { 52 | color: var(--black); 53 | font-weight: 900; 54 | } 55 | 56 | main { 57 | margin: 4rem auto; 58 | max-width: 640px; 59 | min-height: calc(100vh - 242px); 60 | width: 90vw; 61 | } 62 | 63 | @keyframes gradient-roll { 64 | 0% { 65 | background-position: -100vw 0; 66 | } 67 | 68 | 100% { 69 | background-position: 0 0; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Learn With Jason 4 | 5 |

6 |

7 | Learn With Jason Demo Template 8 |

9 |

10 | This is a base template to use as a starting point for Learn With Jason projects. 11 |

12 |

13 | Helpful links: 14 | see the demo · 15 | start a new project · 16 | see upcoming episodes of Learn With Jason 17 |

18 | 19 |   20 | 21 | This is a template repo intended to provide some default structure and styles for demo projects. In a nutshell, this provides: 22 | 23 | * a styled header 24 | * a footer with configurable links 25 | * a main content area that’s centered 26 | * some generic global defaults (e.g. `box-sizing` reset, CSS variables for fonts, colors, and a few other things) 27 | 28 | It’s created with [11ty](https://11ty.dev), but we can always generate the wrapper and copy the built HTML over for use with whatever framework. 29 | 30 |   31 | 32 | ## More Information 33 | 34 | - [Follow _Learn With Jason_ on Twitch][twitch] to watch future episodes live 35 | - [Add the _Learn With Jason_ schedule to your Google Calendar][cal] 36 | 37 |   38 |

39 | 40 | Deploy this project to Netlify 41 | 42 |

43 | 44 | [twitch]: https://jason.af/twitch 45 | [cal]: https://jason.af/lwj/cal -------------------------------------------------------------------------------- /src/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 70 | 71 | 87 | -------------------------------------------------------------------------------- /src/js/dots.js: -------------------------------------------------------------------------------- 1 | const CANVAS_WIDTH = 300; 2 | const CANVAS_HEIGHT = 56; 3 | const X_SPACING = 28; 4 | const Y_SPACING = 14; 5 | const MAX_WIDTH = CANVAS_WIDTH + X_SPACING * 2; 6 | const MAX_HEIGHT = CANVAS_HEIGHT + Y_SPACING * 2; 7 | const COLUMN_COUNT = MAX_WIDTH / X_SPACING; 8 | const ROW_COUNT = MAX_HEIGHT / Y_SPACING; 9 | const OFFSET_DISTANCE = 0.05; 10 | 11 | /* 12 | * To keep track of which circle has what opacity, we need to give each one a 13 | * unique key. Fortunately, we have row and column numbers, which combine to 14 | * make a unique value for each circle! 15 | * 16 | * This function starts each circle out at a random opacity, then increments 17 | * the value by 0.01 going either up or down depending on its current value. 18 | * 19 | * This causes a slow flicker from almost completely transparent to almost 20 | * completely opaque that’s randomized enough to look interesting. 21 | */ 22 | const opacityMap = new Map(); 23 | function getCircleOpacity(row, col) { 24 | const key = `${row},${col}`; 25 | const { opacity, direction } = opacityMap.has(key) 26 | ? opacityMap.get(key) 27 | : { opacity: Math.random(), direction: (col + row) % 2 ? 1 : -1 }; 28 | 29 | const nextValue = opacity + 0.01 * direction; 30 | 31 | // make sure the value is always between 0 and 1 32 | const nextOpacity = Math.max(0, Math.min(1, nextValue)); 33 | 34 | // if we went out of bounds, reverse the direction of the animation. 35 | const nextDirection = nextValue > 1 || nextValue < 0 ? -direction : direction; 36 | 37 | opacityMap.set(key, { opacity: nextOpacity, direction: nextDirection }); 38 | 39 | return nextOpacity; 40 | } 41 | 42 | let offset = 0; 43 | function drawCircles() { 44 | const ctx = document.querySelector('#canvas').getContext('2d'); 45 | ctx.save(); 46 | 47 | // for each loop, remove any existing shapes 48 | ctx.clearRect(0, 0, MAX_WIDTH, MAX_HEIGHT); 49 | 50 | // bump the canvas back by the spacing amount keep our math a bit cleaner 51 | ctx.translate(-X_SPACING, -Y_SPACING); 52 | 53 | // update the offset to move everything one tick over from its previous spot 54 | offset += OFFSET_DISTANCE; 55 | 56 | for (let row = 1; row <= ROW_COUNT; row++) { 57 | // this gives the pattern its diagonal look 58 | const rowBump = row % 2 ? Y_SPACING : 0; 59 | 60 | // figure out how far from the top this row should be 61 | const newPosition = (row * Y_SPACING - offset) % MAX_HEIGHT; 62 | const y = newPosition < 0 ? newPosition + MAX_HEIGHT : newPosition; 63 | 64 | for (let col = 0; col <= COLUMN_COUNT; col++) { 65 | const opacity = getCircleOpacity(row, col); 66 | 67 | // figure out the horizontal position for this circle 68 | const x = (col * X_SPACING + offset * 2 + rowBump) % MAX_WIDTH; 69 | 70 | // draw the circle on the canvas 71 | ctx.beginPath(); 72 | ctx.arc(x, y, 4, 0, Math.PI * 2, true); 73 | ctx.fillStyle = `rgba(26, 42, 59, ${opacity})`; 74 | ctx.fill(); 75 | } 76 | } 77 | 78 | ctx.restore(); 79 | } 80 | 81 | export const startAnimation = () => { 82 | // use an interval because this doesn’t need to run every frame 83 | setInterval(drawCircles, 50); 84 | }; 85 | -------------------------------------------------------------------------------- /src/_includes/header.html: -------------------------------------------------------------------------------- 1 | 126 | 127 |
128 |
129 | 130 | 133 | {{ meta.title }} 134 |
135 | 136 |
137 |
138 | 139 | 144 | -------------------------------------------------------------------------------- /src/images/learn-with-jason-dark.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------