├── src ├── fonts │ └── .gitkeep ├── images │ └── .gitkeep ├── styles │ ├── components │ │ ├── forms.css │ │ ├── header.css │ │ ├── images.css │ │ └── buttons.css │ ├── base │ │ ├── theme.css │ │ ├── helpers.css │ │ ├── layout.css │ │ ├── print.css │ │ ├── root.css │ │ ├── typography.css │ │ └── foundation.css │ └── style.css ├── js │ ├── main.js │ ├── vendor │ │ └── vendor.js │ └── modules │ │ ├── breakpoint.js │ │ └── debounce.js ├── static │ ├── social.jpg │ ├── favicon.ico │ └── favicon.png ├── data │ └── data.json └── html │ ├── index.html │ └── layout │ └── main.njk ├── gulpfile.js ├── lib │ ├── webpackConfig.js │ ├── env.js │ └── handleErrors.js ├── tasks │ ├── clean.js │ ├── scripts.js │ ├── report.js │ ├── assets.js │ ├── server.js │ ├── styles.js │ ├── images.js │ └── html.js ├── index.js └── config.js ├── .eslintignore ├── .babelrc ├── logo.jpg ├── .prettierignore ├── .browserslistrc ├── .prettierrc.js ├── postcss.config.js ├── .editorconfig ├── .gitignore ├── .eslintrc ├── .gitattributes ├── LICENSE ├── package.json └── README.md /src/fonts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gulpfile.js/lib/webpackConfig.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/components/forms.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.min.js 2 | public/ 3 | vendor/ -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | console.log('main.js has loaded'); 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davshoward/tux/HEAD/logo.jpg -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | package-lock.json 4 | public -------------------------------------------------------------------------------- /src/static/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davshoward/tux/HEAD/src/static/social.jpg -------------------------------------------------------------------------------- /src/js/vendor/vendor.js: -------------------------------------------------------------------------------- 1 | import 'focus-visible'; 2 | 3 | console.log('vendor.js has loaded'); 4 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davshoward/tux/HEAD/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davshoward/tux/HEAD/src/static/favicon.png -------------------------------------------------------------------------------- /gulpfile.js/lib/env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prod: process.env.NODE_ENV === 'production', 3 | }; 4 | -------------------------------------------------------------------------------- /src/styles/base/theme.css: -------------------------------------------------------------------------------- 1 | [class*='background-'] { 2 | background: var(--background-color); 3 | } 4 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers we support 2 | 3 | > 1% 4 | last 2 versions 5 | ie 11 6 | ios >= 7 7 | android >= 5 8 | safari >= 9 -------------------------------------------------------------------------------- /src/styles/components/header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | display: block; 4 | position: relative; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/clean.js: -------------------------------------------------------------------------------- 1 | const del = require('del'); 2 | const config = require('../config').dest; 3 | 4 | const clean = () => del(config); 5 | 6 | module.exports = clean; 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | tabWidth: 4, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | jsxBracketSameLine: true, 7 | endOfLine: 'auto', 8 | }; 9 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-preset-env')({ 5 | stage: 1, 6 | }), 7 | require('postcss-nested'), 8 | require('postcss-color-mod-function'), 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /src/data/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": { 3 | "url": "", 4 | "imageurl": "/social.png", 5 | "googleplus": "[googleplus username goes here]", 6 | "facebookapp": "[facebookapp id goes here]", 7 | "twitterid": "[twitter id goes here]" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 1 8 | indent_style = tab 9 | max_line_length = 80 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /gulpfile.js/lib/handleErrors.js: -------------------------------------------------------------------------------- 1 | var notify = require('gulp-notify'); 2 | 3 | module.exports = function (errorObject, callback) { 4 | notify 5 | .onError(errorObject.toString().split(': ').join(':\n')) 6 | .apply(this, arguments); 7 | 8 | // Keep gulp from hanging on this task 9 | if (typeof this.emit === 'function') this.emit('end'); 10 | }; 11 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/scripts.js: -------------------------------------------------------------------------------- 1 | const { src, dest } = require('gulp'); 2 | const config = require('../config').scripts; 3 | const webpack = require('webpack'); 4 | const webpackStream = require('webpack-stream'); 5 | 6 | const scripts = () => 7 | src(config.src) 8 | .pipe(webpackStream(config.webpack, webpack)) 9 | .pipe(dest(config.dest)); 10 | 11 | module.exports = scripts; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## General 2 | *.bak 3 | *.diff 4 | *.err 5 | *.log 6 | *.orig 7 | *.rej 8 | *.swo 9 | *.swp 10 | *.vi 11 | *.zip 12 | *~ 13 | .htaccess 14 | 15 | # OS or Editor folders 16 | ._* 17 | .cache 18 | .DS_Store 19 | .idea 20 | .project 21 | .settings 22 | .tmproj 23 | *.esproj 24 | nbproject 25 | Thumbs.db 26 | 27 | ## Development 28 | node_modules 29 | build 30 | dist 31 | site 32 | public 33 | 34 | package-lock.json -------------------------------------------------------------------------------- /src/styles/style.css: -------------------------------------------------------------------------------- 1 | @import 'base/root.css'; 2 | @import 'base/foundation.css'; 3 | @import 'base/helpers.css'; 4 | @import 'base/typography.css'; 5 | @import 'base/layout.css'; 6 | @import 'base/theme.css'; 7 | 8 | @import 'components/buttons.css'; 9 | @import 'components/header.css'; 10 | @import 'components/images.css'; 11 | 12 | @import 'base/print.css'; 13 | 14 | html { 15 | visibility: visible; 16 | opacity: 1; 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2018, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "browser": true, 8 | "commonjs": true, 9 | "es6": true 10 | }, 11 | "parser": "babel-eslint", 12 | "rules": { 13 | "indent": ["warn", "tab"], 14 | "semi": ["error", "always"], 15 | "no-unused-vars": [ 16 | "warn", 17 | { "vars": "all", "args": "none", "ignoreRestSiblings": false } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/js/modules/breakpoint.js: -------------------------------------------------------------------------------- 1 | export const breakpoint = () => { 2 | // Setup the breakpoint variable 3 | let breakpoint; 4 | 5 | // Get the current breakpoint 6 | const getBreakpoint = function () { 7 | return window 8 | .getComputedStyle(document.body, ':before') 9 | .content.replace(/\"/g, ''); 10 | }; 11 | 12 | // Calculate breakpoint on page load 13 | breakpoint = getBreakpoint(); 14 | 15 | return breakpoint; 16 | }; 17 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/report.js: -------------------------------------------------------------------------------- 1 | const { src } = require('gulp'); 2 | const config = require('../config'); 3 | const sizereport = require('gulp-sizereport'); 4 | 5 | const report = () => { 6 | let srcs = config.report.src; 7 | 8 | config.assets && 9 | config.assets.length && 10 | config.assets.forEach((a) => srcs.push[a.dest]); 11 | 12 | srcs = srcs.map((x) => `${x}/**/*`); 13 | 14 | return src(srcs).pipe(sizereport({ gzip: true })); 15 | }; 16 | 17 | module.exports = report; 18 | -------------------------------------------------------------------------------- /src/styles/components/images.css: -------------------------------------------------------------------------------- 1 | .image-background, 2 | .image-contain { 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | 9 | &.compat-object-fit { 10 | background-position: center center; 11 | background-size: cover; 12 | 13 | img { 14 | display: none; 15 | } 16 | } 17 | 18 | img { 19 | margin: 0; 20 | width: 100%; 21 | height: 100%; 22 | object-fit: cover; 23 | } 24 | } 25 | 26 | .image-contain { 27 | &.compat-object-fit { 28 | background-size: contain; 29 | } 30 | 31 | img { 32 | object-fit: contain; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/assets.js: -------------------------------------------------------------------------------- 1 | const { src, dest } = require('gulp'); 2 | const changed = require('gulp-changed'); 3 | const config = require('../config').assets; 4 | const handleErrors = require('../lib/handleErrors'); 5 | const browserSync = require('browser-sync'); 6 | 7 | const assets = (callback) => { 8 | if (config.length <= 0) { 9 | return callback(); 10 | } 11 | 12 | config.forEach((entry) => { 13 | src(entry.src) 14 | .pipe(changed(entry.dest)) 15 | .on('error', handleErrors) 16 | .pipe(dest(entry.dest)) 17 | .pipe(browserSync.stream()); 18 | }); 19 | 20 | return callback(); 21 | }; 22 | 23 | module.exports = assets; 24 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/server.js: -------------------------------------------------------------------------------- 1 | const browserSync = require('browser-sync'); 2 | const config = require('../config').server; 3 | const webpackConfig = require('../config').scripts.webpack; 4 | const webpack = require('webpack'); 5 | const webpackDevMiddleware = require('webpack-dev-middleware'); 6 | const webpackHotMiddleware = require('webpack-hot-middleware'); 7 | const bundler = webpack(webpackConfig); 8 | 9 | const server = () => { 10 | config.server.middleware = [ 11 | webpackDevMiddleware(bundler, { 12 | stats: 'minimal', 13 | }), 14 | webpackHotMiddleware(bundler), 15 | ]; 16 | 17 | browserSync.init(config); 18 | }; 19 | 20 | module.exports = server; 21 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'layout/main.njk' %} {% set page_title = "Tux 🤵" %} {% set 3 | page_description = "A baseline toolkit to ease the building of static HTML sites 4 | or templated CMS builds. Using Webpack 4, Gulp, PostCSS, Nunjunks and 5 | BrowserSync." %} {% set page_slug = "" %} {%set body_classes = "page-homepage" 6 | %} {% block main %} 7 |
17 |

Tux 🤵

18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /src/js/modules/debounce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Debounce functions for better performance 3 | * (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com 4 | * @param {Function} fn The function to debounce 5 | */ 6 | export const debounce = function (fn) { 7 | // Setup a timer 8 | var timeout; 9 | 10 | // Return a function to run debounced 11 | return function () { 12 | // Setup the arguments 13 | var context = this; 14 | var args = arguments; 15 | 16 | // If there's a timer, cancel it 17 | if (timeout) { 18 | window.cancelAnimationFrame(timeout); 19 | } 20 | 21 | // Setup the new requestAnimationFrame() 22 | timeout = window.requestAnimationFrame(function () { 23 | fn.apply(context, args); 24 | }); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/styles/base/helpers.css: -------------------------------------------------------------------------------- 1 | .visuallyhidden { 2 | border: 0; 3 | clip: rect(0 0 0 0); 4 | height: 1px; 5 | margin: -1px; 6 | overflow: hidden; 7 | padding: 0; 8 | position: absolute; 9 | width: 1px; 10 | 11 | &.is-focusable { 12 | &:active, 13 | &:focus { 14 | clip: auto; 15 | height: auto; 16 | margin: 0; 17 | overflow: visible; 18 | position: static; 19 | width: auto; 20 | } 21 | } 22 | } 23 | 24 | .coverlink { 25 | position: relative; 26 | text-decoration: none; 27 | 28 | &::after { 29 | content: ''; 30 | position: absolute; 31 | left: 0; 32 | top: 0; 33 | right: 0; 34 | bottom: 0; 35 | z-index: 1; 36 | cursor: pointer; 37 | } 38 | } 39 | 40 | .full-bleed { 41 | width: 100vw; 42 | margin-left: 50%; 43 | transform: translateX(-50%); 44 | } 45 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/styles.js: -------------------------------------------------------------------------------- 1 | const { src, dest } = require("gulp"); 2 | const gulpif = require("gulp-if"); 3 | const config = require("../config").styles; 4 | const env = require("../lib/env"); 5 | const handleErrors = require("../lib/handleErrors"); 6 | const browserSync = require("browser-sync"); 7 | 8 | const sourcemaps = require("gulp-sourcemaps"); 9 | const postcss = require("gulp-postcss"); 10 | const cssnano = require("gulp-cssnano"); 11 | 12 | const styles = () => 13 | src(config.src) 14 | .pipe(gulpif(!env.prod, sourcemaps.init())) 15 | .pipe(postcss()) 16 | .pipe(gulpif(env.prod, cssnano({ autoprefixer: false }))) 17 | .pipe(gulpif(!env.prod, sourcemaps.write("./"))) 18 | .on("error", handleErrors) 19 | .pipe(dest(config.dest)) 20 | .pipe(browserSync.stream()); 21 | 22 | module.exports = styles; 23 | -------------------------------------------------------------------------------- /src/styles/components/buttons.css: -------------------------------------------------------------------------------- 1 | .button { 2 | appearance: none; 3 | position: relative; 4 | display: inline-flex; 5 | align-items: center; 6 | justify-content: center; 7 | min-width: 6rem; 8 | text-align: center; 9 | white-space: normal; 10 | margin: 0; 11 | padding: var(--flow-sm) var(--flow-md); 12 | font-size: 1rem; 13 | font-family: inherit; 14 | border: 1px solid transparent; 15 | background-color: var(--color-purple-500); 16 | color: white; 17 | transition: 0.2s ease-out; 18 | cursor: pointer; 19 | 20 | &, 21 | &:hover, 22 | &:focus { 23 | text-decoration: none; 24 | outline: none; 25 | } 26 | 27 | &:hover:not([disabled]) { 28 | border-color: white; 29 | } 30 | 31 | &:active { 32 | top: 1px; 33 | } 34 | 35 | &[disabled] { 36 | cursor: not-allowed; 37 | opacity: 0.45; 38 | } 39 | 40 | .icon { 41 | color: inherit; 42 | margin-left: var(--flow-sm); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/images.js: -------------------------------------------------------------------------------- 1 | const { src, dest } = require('gulp'); 2 | const gulpif = require('gulp-if'); 3 | const changed = require('gulp-changed'); 4 | const config = require('../config').images; 5 | const env = require('../lib/env'); 6 | const handleErrors = require('../lib/handleErrors'); 7 | const browserSync = require('browser-sync'); 8 | 9 | const imagemin = require('gulp-imagemin'); 10 | 11 | const imageminConfig = [ 12 | imagemin.gifsicle({ interlaced: true }), 13 | imagemin.mozjpeg({ progressive: true }), 14 | imagemin.optipng({ optimizationLevel: 5 }), 15 | imagemin.svgo({ 16 | plugins: [{ removeViewBox: true }, { cleanupIDs: false }], 17 | }), 18 | ]; 19 | 20 | const images = () => 21 | src(`${config.src}.{${config.ext}}`) 22 | .pipe(changed(config.dest)) 23 | .on('error', handleErrors) 24 | .pipe(gulpif(env.prod, imagemin(imageminConfig))) 25 | .pipe(dest(config.dest)) 26 | .pipe(browserSync.stream()); 27 | 28 | module.exports = images; 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # .gitattributes 2 | 3 | # Auto detect text files and perform LF normalization 4 | * text=auto 5 | 6 | # These files are text and should be normalized 7 | *.php text 8 | *.css text 9 | *.js text 10 | *.htm text 11 | *.html text 12 | *.xml text 13 | *.txt text 14 | *.ini text 15 | *.inc text 16 | .htaccess text 17 | 18 | # Documents 19 | *.doc diff=astextplain 20 | *.DOC diff=astextplain 21 | *.docx diff=astextplain 22 | *.DOCX diff=astextplain 23 | *.dot diff=astextplain 24 | *.DOT diff=astextplain 25 | *.pdf diff=astextplain 26 | *.PDF diff=astextplain 27 | *.rtf diff=astextplain 28 | *.RTF diff=astextplain 29 | 30 | # These files are binary and should be left untouched 31 | # (binary is a macro for -text -diff) 32 | *.png binary 33 | *.jpg binary 34 | *.jpeg binary 35 | *.gif binary 36 | *.ico binary 37 | *.mov binary 38 | *.mp4 binary 39 | *.mp3 binary 40 | *.flv binary 41 | *.fla binary 42 | *.swf binary 43 | *.gz binary 44 | *.zip binary 45 | *.7z binary 46 | *.ttf binary -------------------------------------------------------------------------------- /src/styles/base/layout.css: -------------------------------------------------------------------------------- 1 | body::before { 2 | content: 'xs'; 3 | display: none; 4 | visibility: hidden; 5 | 6 | @media (--sm) { 7 | content: 'sm'; 8 | } 9 | @media (--md) { 10 | content: 'md'; 11 | } 12 | @media (--lg) { 13 | content: 'lg'; 14 | } 15 | @media (--xl) { 16 | content: 'xl'; 17 | } 18 | } 19 | 20 | body, 21 | html { 22 | min-height: 100vh; 23 | overflow-x: hidden; 24 | } 25 | 26 | .flow { 27 | * + * { 28 | margin-top: var(--flow-md); 29 | } 30 | 31 | *:not(:--heading) + :--heading:not(.no-bump) { 32 | margin-top: var(--flow-lg); 33 | } 34 | 35 | ul ul, 36 | ol ol, 37 | li + li, 38 | dt, 39 | dd { 40 | margin-top: var(--flow-xs); 41 | } 42 | } 43 | 44 | .site-container { 45 | max-width: 120rem; 46 | margin: 0 auto; 47 | } 48 | 49 | .container { 50 | margin: 0 auto; 51 | padding: 0 var(--flow-md); 52 | max-width: 36rem; 53 | 54 | @media (--lg) { 55 | max-width: 62rem; 56 | } 57 | 58 | @media (--xl) { 59 | max-width: 68rem; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gulpfile.js/index.js: -------------------------------------------------------------------------------- 1 | const { series, parallel, watch } = require('gulp'); 2 | const browserSync = require('browser-sync'); 3 | 4 | const config = require('./config'); 5 | const env = require('./lib/env'); 6 | 7 | const clean = require('./tasks/clean'); 8 | const styles = require('./tasks/styles'); 9 | const html = require('./tasks/html'); 10 | const images = require('./tasks/images'); 11 | const assets = require('./tasks/assets'); 12 | const report = require('./tasks/report'); 13 | const scripts = require('./tasks/scripts'); 14 | const server = require('./tasks/server'); 15 | 16 | const watching = (callback) => { 17 | watch(config.styles.watch, styles); 18 | watch(config.images.watch, images); 19 | watch(config.html.src, html); 20 | watch(config.html.data, html); 21 | 22 | callback(); 23 | }; 24 | 25 | const tasks = [styles, images, assets, html, scripts]; 26 | 27 | module.exports = { 28 | default: env.prod 29 | ? series(clean, parallel(...tasks), report) 30 | : series(clean, parallel(...tasks), watching, server), 31 | }; 32 | -------------------------------------------------------------------------------- /src/styles/base/print.css: -------------------------------------------------------------------------------- 1 | @media print { 2 | *, 3 | *::before, 4 | *::after { 5 | background: transparent !important; 6 | color: #000 !important; 7 | box-shadow: none !important; 8 | text-shadow: none !important; 9 | } 10 | 11 | a, 12 | a:visited { 13 | text-decoration: underline; 14 | } 15 | 16 | a[href]::after { 17 | content: ' (' attr(href) ')'; 18 | } 19 | 20 | abbr[title]::after { 21 | content: ' (' attr(title) ')'; 22 | } 23 | 24 | .ir a::after, 25 | a[href^='javascript:']::after, 26 | a[href^='#']::after { 27 | content: ''; 28 | } 29 | 30 | pre, 31 | blockquote { 32 | border: 1px solid #999; 33 | page-break-inside: avoid; 34 | } 35 | 36 | thead { 37 | display: table-header-group; 38 | } 39 | 40 | tr, 41 | img { 42 | page-break-inside: avoid; 43 | } 44 | 45 | img { 46 | max-width: 100% !important; 47 | } 48 | 49 | @page { 50 | margin: 0.5cm; 51 | } 52 | 53 | p, 54 | h2, 55 | h3 { 56 | orphans: 3; 57 | widows: 3; 58 | } 59 | 60 | h2, 61 | h3 { 62 | page-break-after: avoid; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Davs Howard 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 | -------------------------------------------------------------------------------- /gulpfile.js/tasks/html.js: -------------------------------------------------------------------------------- 1 | const { src, dest } = require('gulp'); 2 | const gulpif = require('gulp-if'); 3 | const config = require('../config').html; 4 | const env = require('../lib/env'); 5 | const handleErrors = require('../lib/handleErrors'); 6 | 7 | const browserSync = require('browser-sync'); 8 | 9 | const data = require('gulp-data'); 10 | const htmlmin = require('gulp-htmlmin'); 11 | const nunjucksRender = require('gulp-nunjucks-render'); 12 | const strip = require('gulp-strip-comments'); 13 | const fs = require('fs'); 14 | 15 | const html = () => { 16 | const exclude = `${config.src}/**/{${config.excludeFolders.join(',')}}/**`; 17 | 18 | const parseData = () => JSON.parse(fs.readFileSync(config.data)); 19 | 20 | const nunjucksRenderOptions = {}; 21 | nunjucksRenderOptions.path = config.render.path; 22 | nunjucksRenderOptions.envOptions = config.render.envOptions; 23 | 24 | return src([`${config.src}/**/*.{${config.ext}}`, `!${exclude}`]) 25 | .pipe(data(parseData)) 26 | .on('error', handleErrors) 27 | .pipe(nunjucksRender(nunjucksRenderOptions)) 28 | .on('error', handleErrors) 29 | .pipe(gulpif(env.prod, strip())) 30 | .pipe(gulpif(env.prod, htmlmin(config.htmlmin))) 31 | .pipe(dest(config.dest)) 32 | .pipe(browserSync.stream()); 33 | }; 34 | 35 | module.exports = html; 36 | -------------------------------------------------------------------------------- /src/styles/base/root.css: -------------------------------------------------------------------------------- 1 | @custom-media --sm (width >= 25em); 2 | @custom-media --md (width >= 48em); 3 | @custom-media --lg (width >= 70em); 4 | @custom-media --xl (width >= 96em); 5 | @custom-media --xxl (width >= 120em); 6 | @custom-selector :--heading 7 | h1, 8 | h2, 9 | h3, 10 | h4, 11 | h5, 12 | h6, 13 | .h1, 14 | .h2, 15 | .h3, 16 | .h4, 17 | .h5, 18 | .h6; 19 | 20 | :root { 21 | --flow-xxs: 0.375rem; 22 | --flow-xs: 0.5rem; 23 | --flow-sm: 0.75rem; 24 | --flow-md: 1rem; 25 | --flow-lg: 1.25rem; 26 | --flow-xl: 1.875rem; 27 | --flow-xxl: 2.5rem; 28 | 29 | --fluid-type-min-width: 25; 30 | --fluid-type-max-width: 120; 31 | 32 | --transition-smooth: cubic-bezier(0.765, 0, 0.175, 1); 33 | 34 | --font-base: system-ui; 35 | --font-header: system-ui; 36 | 37 | --color-purple-500: rebeccapurple; 38 | --color-orange-500: orange; 39 | 40 | --color-neutral-900: #1c1c1c; 41 | --color-neutral-800: #2d2d2d; 42 | --color-neutral-700: #595959; 43 | --color-neutral-600: #646464; 44 | --color-neutral-500: #9e9e9e; 45 | --color-neutral-400: #bbbbbb; 46 | --color-neutral-300: #d8d8d8; 47 | --color-neutral-200: #ececec; 48 | --color-neutral-100: #f5f5f5; 49 | 50 | --font-color: #333333; 51 | --heading-color: white; 52 | --link-color: white; 53 | 54 | --outline-color: lightgreen; 55 | --selection-color: lightgreen; 56 | } 57 | -------------------------------------------------------------------------------- /src/styles/base/typography.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | --heading-min-size: 2; 3 | --heading-max-size: 2.5; 4 | font-size: 2rem; 5 | } 6 | 7 | h2 { 8 | --heading-min-size: 1.75; 9 | --heading-max-size: 2; 10 | font-size: 1.75rem; 11 | } 12 | 13 | h3 { 14 | --heading-min-size: 1.4; 15 | --heading-max-size: 1.6; 16 | font-size: 1.4rem; 17 | } 18 | 19 | h4 { 20 | --heading-min-size: 1.2; 21 | --heading-max-size: 1.4; 22 | font-size: 1.2rem; 23 | } 24 | 25 | h5 { 26 | --heading-min-size: 1; 27 | --heading-max-size: 1.2; 28 | font-size: 1rem; 29 | } 30 | 31 | h6 { 32 | --heading-min-size: 1; 33 | --heading-max-size: 1.2; 34 | font-size: 1rem; 35 | } 36 | 37 | :--heading { 38 | display: inline-block; 39 | width: 100%; 40 | color: var(--heading-color); 41 | font-size: calc( 42 | (var(--heading-min-size) * 1rem) + 43 | (var(--heading-max-size) - var(--heading-min-size)) * 44 | (100vw - (var(--fluid-type-min-width) * 1rem)) / 45 | (var(--fluid-type-max-width) - var(--fluid-type-min-width)) 46 | ); 47 | font-family: var(--font-header); 48 | line-height: 1.2; 49 | } 50 | 51 | a { 52 | color: var(--link-color); 53 | } 54 | 55 | p, 56 | ul, 57 | ol { 58 | font-size: 1rem; 59 | line-height: 1.4; 60 | 61 | &:empty { 62 | display: none; 63 | } 64 | } 65 | 66 | ul, 67 | ol { 68 | padding-left: var(--flow-md); 69 | 70 | ul, 71 | ol { 72 | padding-left: var(--flow-md); 73 | } 74 | } 75 | 76 | blockquote { 77 | position: relative; 78 | font-style: italic; 79 | padding-left: var(--flow-md); 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tux", 3 | "version": "2.1.1", 4 | "description": "A baseline toolkit to ease the building of static HTML sites or templated CMS builds.", 5 | "license": "MIT", 6 | "author": "Davs Howard <@davshoward> (https://davshoward.com)", 7 | "keywords": [ 8 | "gulp", 9 | "webpack", 10 | "browsersync", 11 | "tux", 12 | "postcss" 13 | ], 14 | "main": "gulpfile.js/index.js", 15 | "scripts": { 16 | "start": "gulp", 17 | "build": "cross-env NODE_ENV=production gulp" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.14.6", 21 | "@babel/preset-env": "^7.14.7", 22 | "babel-eslint": "^10.0.3", 23 | "babel-loader": "^8.2.2", 24 | "browser-sync": "^2.27.4", 25 | "cross-env": "^7.0.3", 26 | "del": "^6.0.0", 27 | "eslint": "^7.29.0", 28 | "glob": "^7.1.7", 29 | "gulp": "^4.0.2", 30 | "gulp-changed": "^4.0.3", 31 | "gulp-cssnano": "^2.1.3", 32 | "gulp-data": "^1.3.1", 33 | "gulp-htmlmin": "^5.0.1", 34 | "gulp-if": "^3.0.0", 35 | "gulp-imagemin": "^7.1.0", 36 | "gulp-notify": "^4.0.0", 37 | "gulp-nunjucks-render": "^2.2.3", 38 | "gulp-postcss": "^9.0.0", 39 | "gulp-sequence": "^1.0.0", 40 | "gulp-sizereport": "^1.2.1", 41 | "gulp-sourcemaps": "^3.0.0", 42 | "gulp-strip-comments": "^2.5.2", 43 | "gulp-watch": "^5.0.1", 44 | "path": "^0.12.7", 45 | "postcss": "^8.3.5", 46 | "postcss-color-mod-function": "^3.0.3", 47 | "postcss-import": "^14.0.2", 48 | "postcss-nested": "^5.0.5", 49 | "postcss-preset-env": "^6.7.0", 50 | "postcss-strip-inline-comments": "^0.1.5", 51 | "require-dir": "^1.2.0", 52 | "webpack": "^5.42.0", 53 | "webpack-dev-middleware": "^5.0.0", 54 | "webpack-hot-middleware": "^2.25.0", 55 | "webpack-stream": "^6.1.2" 56 | }, 57 | "dependencies": { 58 | "focus-visible": "^5.2.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Tux](/logo.jpg) 2 | 3 | A baseline toolkit to ease the building of static HTML sites or templated CMS builds. Using Webpack 4, Gulp 4, PostCSS, Nunjunks and BrowserSync. 4 | 5 | ## Features 💪 6 | 7 | - HTML - Build templates with Nunjucks 8 | - CSS - PostCSS with autoprefixing, nesting, custom media queries and more - "Use tomorrow’s CSS syntax, today" 9 | - JS - Bundle and transpile ES6 JavaScript with Webpack and Babel 10 | - Assets - Automatically optimise images, manage fonts and static assets 11 | - Development - Live reload with BrowserSync and Webpack's HMR. 12 | 13 | ## Getting started 📖 14 | 15 | ### Requirements 16 | 17 | - Node.js 18 | - npm 19 | 20 | ### Off you go 21 | 22 | #### Clone 23 | 24 | ```bash 25 | git clone https://github.com/davshoward/tux 26 | cd 27 | ``` 28 | 29 | #### Install with npm 30 | 31 | ```bash 32 | npm install 33 | ``` 34 | 35 | #### Start 36 | 37 | ```bash 38 | npm start 39 | ``` 40 | 41 | #### Build 42 | 43 | ```bash 44 | npm run build 45 | ``` 46 | 47 | #### Configure 48 | 49 | Customise your own file paths within gulpfile.js/config.js 50 | 51 | ## Contributing 52 | 53 | Welcome any improvements or suggestions :-) 54 | 55 | ## Changelog 56 | 57 | ### 2.1.1 58 | 59 | - Bug fix for images not copying correctly 60 | 61 | ### 2.1.0 62 | 63 | - Updated all dependencies 64 | - Renamed rhythm spacing class to flow (and associated to custom properties) 65 | - Added custom :--heading property 66 | - Added Prettier config 67 | 68 | ### 2.0.0 69 | 70 | - Updated to Gulp 4 and introduced some adjustments to the build process 71 | - Restructured the CSS components 72 | - Introduced new CSS elements including theme file and prefers-reduced-motion override 73 | 74 | ### 1.2.1 75 | 76 | - Added JS-free FOUC fix 77 | - Minor CSS update 78 | - Remove Aria role=document (from PR #1) 79 | 80 | ### 1.2.0 81 | 82 | - Updated foundation css 83 | - Introduce spacing rhythm and fluid headers 84 | - Allow urls to not require .html 85 | - Added css-to-JS breakpoint sync 86 | - Updated dependencies - including adding postcss color-mod 87 | 88 | ### 1.1.1 89 | 90 | - Added focus-visible usage for better baseline accessibility 91 | 92 | ### 1.1.0 93 | 94 | - Fully migrate from SASS to PostCSS 95 | - Updated dependencies 96 | 97 | ### 1.0.0 98 | 99 | - Initial commit 100 | -------------------------------------------------------------------------------- /src/html/layout/main.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ page_title }} 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 |
55 | 56 |
57 | 58 | 61 | 62 | 63 |
64 | {% block main %} {% endblock %} 65 |
66 | 67 | 68 |
69 | {% block footer %} {% endblock %} 70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /gulpfile.js/config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const glob = require('glob'); 3 | const path = require('path'); 4 | const env = require('./lib/env'); 5 | 6 | const src = 'src'; 7 | const dest = 'public'; 8 | 9 | const assets = [ 10 | { 11 | src: `${src}/fonts/*`, 12 | dest: `${dest}/fonts`, 13 | }, 14 | { 15 | src: `${src}/static/*`, 16 | dest: `${dest}/`, 17 | }, 18 | ]; 19 | 20 | const server = { 21 | files: [], 22 | server: { 23 | baseDir: dest, 24 | middleware: [], 25 | serveStaticOptions: { 26 | extensions: ['html'], 27 | }, 28 | }, 29 | watchOptions: { 30 | debounceDelay: 2000, 31 | }, 32 | }; 33 | 34 | const html = { 35 | src: `${src}/html`, 36 | dest: `${dest}/`, 37 | data: `${src}/data/data.json`, 38 | render: { 39 | path: `${src}/html`, 40 | envOptions: { 41 | watch: false, 42 | }, 43 | }, 44 | htmlmin: { 45 | collapseWhitespace: true, 46 | }, 47 | excludeFolders: ['layout', 'components', 'macros'], 48 | ext: ['html', 'njk', 'json'], 49 | }; 50 | 51 | const images = { 52 | src: `${src}/images/*`, 53 | dest: `${dest}/`, 54 | watch: `${src}/images/*`, 55 | ext: ['jpg', 'png', 'svg', 'gif', 'webp'], 56 | }; 57 | 58 | const styles = { 59 | src: `${src}/styles/*.css`, 60 | dest: `${dest}/styles`, 61 | watch: `${src}/styles/**/*.css`, 62 | }; 63 | 64 | const scripts = { 65 | src: `${src}/js/**`, 66 | dest: `${dest}/js`, 67 | webpack: { 68 | mode: env.prod ? 'production' : 'development', 69 | context: path.resolve(__dirname, `../${src}/js`), 70 | entry: { 71 | main: !env.prod 72 | ? ['webpack-hot-middleware/client?reload=true', './main.js'] 73 | : ['./main.js'], 74 | vendor: glob.sync( 75 | path.resolve(__dirname, `../${src}/js/vendor/**/*.js`) 76 | ), 77 | }, 78 | output: { 79 | path: path.resolve(__dirname, `../${dest}/js`), 80 | filename: '[name].bundle.js', 81 | publicPath: '/js', 82 | }, 83 | resolve: { 84 | alias: { 85 | utils: `${src}/js/utils`, 86 | modules: `${src}/js/modules`, 87 | }, 88 | }, 89 | plugins: !env.prod ? [new webpack.HotModuleReplacementPlugin()] : [], 90 | module: { 91 | rules: [ 92 | { 93 | test: /\.js$/, 94 | exclude: /node_modules/, 95 | use: ['babel-loader'], 96 | }, 97 | ], 98 | }, 99 | }, 100 | }; 101 | 102 | const report = { 103 | src: [styles.dest, scripts.dest, images.dest], 104 | }; 105 | 106 | module.exports = { 107 | src, 108 | dest, 109 | server, 110 | assets, 111 | images, 112 | html, 113 | styles, 114 | scripts, 115 | report, 116 | }; 117 | -------------------------------------------------------------------------------- /src/styles/base/foundation.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | 5 | &, 6 | &::before, 7 | &::after { 8 | box-sizing: inherit; 9 | } 10 | } 11 | 12 | html { 13 | box-sizing: border-box; 14 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 15 | Noto Sans, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 16 | 'Segoe UI Symbol', 'Noto Color Emoji'; 17 | line-height: 1.15; 18 | -ms-text-size-adjust: 100%; 19 | -webkit-text-size-adjust: 100%; 20 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 21 | -ms-overflow-style: scrollbar; 22 | word-break: break-word; 23 | scroll-behavior: smooth; 24 | } 25 | 26 | body { 27 | min-height: 100vh; 28 | color: var(--font-color); 29 | font-family: var(--font-base); 30 | font-size: 1rem; 31 | overflow-wrap: break-word; 32 | } 33 | 34 | main { 35 | display: block; 36 | } 37 | 38 | hr { 39 | height: 0; 40 | overflow: visible; 41 | } 42 | 43 | nav ol, 44 | nav ul { 45 | list-style: none; 46 | } 47 | 48 | [hidden], 49 | .hidden { 50 | display: none !important; 51 | } 52 | 53 | a { 54 | background-color: transparent; 55 | -webkit-text-decoration-skip: objects; 56 | 57 | &:active, 58 | &:hover { 59 | outline: 0; 60 | } 61 | } 62 | 63 | b, 64 | strong { 65 | font-weight: bold; 66 | } 67 | 68 | small { 69 | font-size: 80%; 70 | } 71 | 72 | ::-moz-selection, 73 | ::selection { 74 | background-color: var(--selection-color); 75 | color: var(--font-color); 76 | text-shadow: none; 77 | } 78 | 79 | img { 80 | max-width: 100%; 81 | height: auto; 82 | -ms-interpolation-mode: bicubic; 83 | display: inline-block; 84 | vertical-align: middle; 85 | border: 0; 86 | border-style: none; 87 | } 88 | 89 | button, 90 | input, 91 | select, 92 | textarea { 93 | color: inherit; 94 | font: inherit; 95 | } 96 | 97 | button, 98 | input, 99 | select { 100 | overflow: visible; 101 | } 102 | 103 | button { 104 | background: transparent; 105 | border: 0; 106 | padding: 0; 107 | cursor: pointer; 108 | -webkit-font-smoothing: inherit; 109 | letter-spacing: inherit; 110 | } 111 | 112 | button, 113 | [type='button'], 114 | [type='reset'], 115 | [type='submit'] { 116 | -webkit-appearance: button; 117 | } 118 | 119 | input { 120 | overflow: visible; 121 | } 122 | 123 | textarea { 124 | resize: vertical; 125 | overflow: auto; 126 | } 127 | 128 | select { 129 | text-transform: none; 130 | } 131 | 132 | ::-moz-focus-inner { 133 | border-style: none; 134 | padding: 0; 135 | } 136 | 137 | table { 138 | border-collapse: collapse; 139 | border-spacing: 0; 140 | } 141 | 142 | audio, 143 | canvas, 144 | iframe, 145 | svg, 146 | video { 147 | vertical-align: middle; 148 | } 149 | 150 | audio:not([controls]) { 151 | display: none; 152 | height: 0; 153 | } 154 | 155 | svg:not([fill]) { 156 | fill: currentColor; 157 | } 158 | 159 | svg:not(:root) { 160 | overflow: hidden; 161 | } 162 | 163 | .layout-svgs { 164 | position: absolute; 165 | left: -200vw; 166 | } 167 | 168 | canvas { 169 | display: inline-block; 170 | } 171 | 172 | template { 173 | display: none; 174 | } 175 | 176 | a, 177 | area, 178 | button, 179 | input, 180 | label, 181 | select, 182 | summary, 183 | textarea, 184 | [tabindex] { 185 | touch-action: manipulation; 186 | } 187 | 188 | [aria-busy='true'] { 189 | cursor: progress; 190 | } 191 | 192 | [aria-controls] { 193 | cursor: pointer; 194 | } 195 | 196 | [aria-disabled='true'], 197 | [disabled] { 198 | cursor: not-allowed; 199 | } 200 | 201 | [aria-hidden='false'][hidden]:not(:focus) { 202 | clip: rect(0, 0, 0, 0); 203 | display: inherit; 204 | position: absolute; 205 | } 206 | 207 | .no-js *:focus { 208 | box-shadow: 0 0 0 2px var(--outline-color); 209 | } 210 | 211 | *:focus:not(.focus-visible) { 212 | outline: none; 213 | } 214 | 215 | .focus-visible { 216 | box-shadow: 0 0 0 2px var(--outline-color); 217 | } 218 | 219 | @media (prefers-reduced-motion: reduce) { 220 | html { 221 | scroll-behavior: auto; 222 | } 223 | 224 | * { 225 | transition: none !important; 226 | } 227 | } 228 | --------------------------------------------------------------------------------