Start bringing over a (curated) configuration for CSS and Eleventy

This commit is contained in:
Lewis Dale 2023-02-04 16:27:15 +00:00
commit 6c599aeaaf
30 changed files with 9618 additions and 0 deletions

24
.eleventy.js Normal file
View File

@ -0,0 +1,24 @@
module.exports = function(eleventyConfig) {
// Configure passthrough copies, file ops
eleventyConfig.addPlugin(require('./config/files'));
// Setup plugins
eleventyConfig.addPlugin(require('./config/plugins'));
// Custom filters and shortcodes
eleventyConfig.addPlugin(require('./config/filters'));
// Custom collections
eleventyConfig.addPlugin(require('./config/collections'));
return {
passthroughFileCopy: true,
dataTemplateEngine: false,
markdownTemplateEngine: "njk",
htmlTemplateEngine: "njk",
dir: {
input: "src",
includes: "_includes",
data: "_data",
output: "_site"
}
};
};

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
src/css/custom-props.css
_site

View File

@ -0,0 +1 @@
module.exports = function() {}

9
config/files/index.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = function(eleventyConfig) {
if (process.env.NODE_ENV !== "development") {
eleventyConfig.ignores.add("src/posts/drafts/*");
}
eleventyConfig.addPassthroughCopy("src/assets");
eleventyConfig.setDataDeepMerge(true);
eleventyConfig.setFrontMatterParsingOptions({ excerpt: true });
}

1
config/filters/index.js Normal file
View File

@ -0,0 +1 @@
module.exports = function() {}

54
config/plugins/image.js Normal file
View File

@ -0,0 +1,54 @@
const Image = require('@11ty/eleventy-img');
const defaultSizes = `
(max-width: 300px) 300px,
(max-width: 600px) 600px,
(max-width: 1000px) 1000px,
(max-width: 1440px) 1440px,
(max-width: 2500px) 2500px,
(min-width: 2500px) 3840px
`;
const imageShortcode = (src, alt, cls, sizes = defaultSizes, widths = [300, 600, 1000, 1440, 2500, 3840]) => {
let options = {
widths: widths,
formats: ['webp', 'jpeg'],
outputDir: "./_site/img",
};
// generate images, while this is async we dont wait
Image(src, options);
let imageAttributes = {
class: cls,
alt,
sizes,
decoding: "async",
};
// get metadata even the images are not fully generated
let metadata = Image.statsSync(src, options);
return Image.generateHTML(metadata, imageAttributes);
}
const remoteImageShortcode = async (src, alt, cls, sizes = defaultSizes, widths = [300, 600, 1000, 1440, 2500, 3840]) => {
let metadata = await Image(src, {
widths,
formats: ["webp", "avif", "jpeg", "png"],
outputDir: "./_site/img",
});
let imageAttributes = {
alt,
class: cls,
sizes,
loading: "lazy",
decoding: "async",
};
// You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay)
return Image.generateHTML(metadata, imageAttributes);
}
module.exports = function(eleventyConfig) {
eleventyConfig.addShortcode("image", imageShortcode);
eleventyConfig.addAsyncShortcode("remoteImage", remoteImageShortcode);
}

11
config/plugins/index.js Normal file
View File

@ -0,0 +1,11 @@
const markdownPlugin = require('./markdown');
const rss = require('@11ty/eleventy-plugin-rss');
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const imagePlugin = require('./image');
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(syntaxHighlight);
eleventyConfig.addPlugin(rss);
eleventyConfig.addPlugin(markdownPlugin);
eleventyConfig.addPlugin(imagePlugin);
}

View File

@ -0,0 +1,69 @@
const path = require("path");
const markdownIt = require('markdown-it');
const markdownItPrism = require('markdown-it-prism');
const markdownItAnchor = require('markdown-it-anchor');
const markdownItClass = require('@toycode/markdown-it-class');
const markdownItLinkAttributes = require('markdown-it-link-attributes');
const markdownItEmoji = require('markdown-it-emoji');
const markdownItFootnote = require('markdown-it-footnote');
const markdownitMark = require('markdown-it-mark');
const markdownitAbbr = require('markdown-it-abbr');
const markdownItEleventyImg = require('markdown-it-eleventy-img');
const { slugifyString } = require('../utils');
const markdownLib = markdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true
})
.disable('code')
.use(markdownItPrism, {
defaultLanguage: 'plaintext'
})
.use(markdownItAnchor, {
slugify: slugifyString,
tabIndex: false,
permalink: markdownItAnchor.permalink.headerLink({
class: 'heading-anchor'
})
})
.use(markdownItClass, {
ol: 'list',
ul: 'list'
})
.use(markdownItLinkAttributes, [
{
// match external links
matcher(href) {
return href.match(/^https?:\/\//);
},
attrs: {
target: '_blank',
rel: 'noreferrer noopener'
}
}
])
.use(markdownItEmoji)
.use(markdownItFootnote)
.use(markdownitMark)
.use(markdownitAbbr)
.use(markdownItEleventyImg, {
imgOptions: {
widths: [300, 600, 1000, 1440],
urlPath: "/img/",
outputDir: path.join("_site", "img"),
formats: ["avif", "webp", "jpeg"]
},
globalAttributes: {
sizes: `(max-width: 300px) 300px,
(max-width: 600px) 600px,
(max-width: 1000px) 1000px,
(max-width: 1440px) 1440px,
100%`
}
});
module.exports = function(eleventyConfig) {
eleventyConfig.setLibrary('md', markdownLib);
};

14
config/utils.js Normal file
View File

@ -0,0 +1,14 @@
const slugify = require('slugify');
/** Converts string to a slug form. */
const slugifyString = str => {
return slugify(str, {
replacement: '-',
remove: /[#,&,+()$~%.'":*?<>{}]/g,
lower: true
});
};
module.exports = {
slugifyString
}

9025
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "whimsy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"serve": "run-p \"serve:*\"",
"serve:props": "node scripts/custom-props.js",
"serve:css": "postcss src/css/styles.css --base src --dir _site/assets -w",
"serve:eleventy": "eleventy --serve",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@11ty/eleventy": "^2.0.0-beta.3",
"@11ty/eleventy-fetch": "^3.0.0",
"@11ty/eleventy-img": "^3.0.0",
"@11ty/eleventy-plugin-rss": "^1.2.0",
"@11ty/eleventy-plugin-syntaxhighlight": "^4.2.0",
"@toycode/markdown-it-class": "^1.2.4",
"autoprefixer": "^10.4.13",
"markdown-it-abbr": "^1.0.4",
"markdown-it-anchor": "^8.6.6",
"markdown-it-eleventy-img": "^0.9.0",
"markdown-it-emoji": "^2.0.2",
"markdown-it-footnote": "^3.0.3",
"markdown-it-link-attributes": "^4.0.1",
"markdown-it-mark": "^3.0.1",
"markdown-it-prism": "^2.3.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"postcss-cli": "^10.1.0",
"postcss-import": "^15.1.0",
"postcss-import-ext-glob": "^2.1.1",
"prettier": "^2.8.3",
"tailwindcss": "^3.2.4"
}
}

9
postcss.config.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
plugins: {
'postcss-import-ext-glob': {},
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
},
};

67
scripts/custom-props.js Normal file
View File

@ -0,0 +1,67 @@
const fs = require('node:fs');
const prettier = require('prettier');
const config = require('../tailwind.config.js');
const groupToPrefix = (group, prefix) => {
return Object.entries(group).map(([key, value]) => {
if (value instanceof Object && !(value instanceof Array)) {
return groupToPrefix(value, `${prefix}-${key}`);
} else {
return `--${prefix}-${key}: ${value};`
}
}).join('');
};
/*
Converts the tailwind config elements into custom props.
*/
const generateCSSProps = () => {
let result = '';
const groups = [
{key: 'colors', prefix: 'color'},
{key: 'spacing', prefix: 'space'},
{key: 'fontSize', prefix: 'text'},
{key: 'fontFamily', prefix: 'font-family'},
{key: 'screens', prefix: 'screen'},
{key: 'gap', prefix: 'gap'},
];
// Add a note that this is auto generated
result += `
/* VARIABLES GENERATED WITH TAILWIND CONFIG ON ${new Date().toLocaleDateString()}.
Tokens location: ./tailwind.config.js */
:root {
`;
// Loop each group's keys, use that and the associated
// property to define a :root custom prop
groups.forEach(({key, prefix}) => {
const group = config.theme[key];
if (!group) {
return;
}
// Object.keys(group).forEach(key => {
// result += `--${prefix}-${key}: ${group[key]};`;
// });
result += groupToPrefix(group, prefix);
});
// Close the :root block
result += `
}
`;
// Make the CSS readable to help people with auto-complete in their editors
result = prettier.format(result, {parser: 'scss'});
// Push this file into the CSS dir, ready to go
fs.writeFileSync('./src/css/custom-props.css', result);
};
generateCSSProps();
module.exports = generateCSSProps;

10
src/_data/metadata.json Normal file
View File

@ -0,0 +1,10 @@
{
"author": {
"name": "Lewis Dale"
},
"site": {
"name": "LewisDale dot Dev",
"domain": "lewisdale.dev",
"description": ""
}
}

11
src/_includes/base.njk Normal file
View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang={{data.page.lang}}>
<head>
<title>{{ title }} | {{ metadata.site.name }}</title>
<link rel="stylesheet" type="text/css" href="/assets/css/styles.css" />
</head>
<body>
{{ content | safe }}
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

6
src/css/cube.css Normal file
View File

@ -0,0 +1,6 @@
@import-glob "compositions/*";
@import-glob "utilities/*";
@import-glob "blocks/*";
@import-glob "exceptions/*";
@tailwind utilities;

View File

@ -0,0 +1,72 @@
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Bold.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Bold.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Medium.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Medium.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Light.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Regular.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Regular.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Light.woff2') format('woff2'),
url('/assets/fonts/Space_Grotesk/SpaceGrotesk-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}

4
src/css/globals.css Normal file
View File

@ -0,0 +1,4 @@
body {
font-family: var(--font-family-mono);
font-size: var(--font-size-s);
}

80
src/css/reset.css Normal file
View File

@ -0,0 +1,80 @@
/* Modern reset: https://piccalil.li/blog/a-modern-css-reset/ */
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
margin: 0;
}
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul,
ol,
[role='list'] {
list-style: none;
padding: 0;
margin: 0;
}
/* Set core root defaults */
html:focus-within {
scroll-behavior: smooth;
}
/* Set core body defaults */
body {
min-height: 100vh;
min-height: 100dvh; /* safari-specific */
text-rendering: optimizeSpeed;
line-height: 1.75;
}
/* A elements that don't have a class get default styles */
a:not([class]) {
text-decoration-skip-ink: auto;
}
/* Make images easier to work with */
img,
picture {
max-width: 100%;
display: block;
}
/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
font: inherit;
}
/* Remove all animations, transitions and smooth scroll for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
html:focus-within {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

11
src/css/styles.css Normal file
View File

@ -0,0 +1,11 @@
@import-glob "fonts/*";
@import "custom-props.css";
@import "reset.css";
@import "globals.css";
@import-glob "compositions/*";
@import-glob "utilities/*";
@import-glob "blocks/*";
@import-glob "exceptions/*";
@tailwind utilities;

6
src/index.html Normal file
View File

@ -0,0 +1,6 @@
---
title: Home
layout: base.njk
---
<h1>{{ metadata.site.name }}</h1>

92
tailwind.config.js Normal file
View File

@ -0,0 +1,92 @@
const defaultTheme = require('tailwindcss/defaultTheme');
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
content: ["./src/**/*.{md,njk,html}"],
theme: {
screens: {
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
'3xl': '1920px',
},
colors: defaultTheme.colors,
backgroundColor: ({theme}) => theme('colors'),
textColor: ({theme}) => theme('colors'),
spacing: {
'size-3xs': 'clamp(0.25rem, calc(0.24rem + 0.06vw), 0.31rem)',
'size-2xs': 'clamp(0.50rem, calc(0.48rem + 0.13vw), 0.63rem)',
'size-xs': 'clamp(0.75rem, calc(0.71rem + 0.19vw), 0.94rem)',
'size-s': 'clamp(1.00rem, calc(0.95rem + 0.25vw), 1.25rem)',
'size-m': 'clamp(1.50rem, calc(1.43rem + 0.38vw), 1.88rem)',
'size-l': 'clamp(2.00rem, calc(1.90rem + 0.50vw), 2.50rem)',
'size-xl': 'clamp(3.00rem, calc(2.85rem + 0.75vw), 3.75rem)',
'size-2xl': 'clamp(4.00rem, calc(3.80rem + 1.00vw), 5.00rem)',
'size-3xl': 'clamp(6.00rem, calc(5.70rem + 1.50vw), 7.50rem)',
},
fontSize: {
'size-2xs': 'clamp(0.56rem, calc(0.72rem + -0.14vw), 0.69rem)',
'size-xs': 'clamp(0.83rem, calc(0.83rem + 0.00vw), 0.83rem)',
'size-s': 'clamp(1.00rem, calc(0.95rem + 0.25vw), 1.25rem)',
'size-m': 'clamp(1.20rem, calc(1.07rem + 0.68vw), 1.88rem)',
'size-l': 'clamp(1.44rem, calc(1.17rem + 1.37vw), 2.81rem)',
'size-xl': 'clamp(1.73rem, calc(1.23rem + 2.49vw), 4.22rem)',
'size-2xl': 'clamp(2.07rem, calc(1.22rem + 4.25vw), 6.33rem)',
'size-3xl': 'clamp(2.49rem, calc(1.09rem + 7.00vw), 9.49rem)',
},
fontFamily: {
sans: defaultTheme.fontFamily.sans,
mono: ['"Space Grotesk"', ...defaultTheme.fontFamily.mono],
},
gap: ({theme}) => ({
none: '0px',
...theme('spacing'),
}),
margin: ({theme}) => ({
auto: 'auto',
none: '0',
...theme('spacing')
}),
padding: ({theme}) => ({
none: '0',
...theme('spacing')
}),
scrollMargin: ({theme}) => theme('spacing'),
textColor: ({theme}) => theme('colors'),
zIndex: {
auto: 'auto',
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
60: '60',
70: '70',
80: '80',
90: '90',
100: '100',
}
},
variantOrder: [
'first',
'last',
'odd',
'even',
'visited',
'checked',
'empty',
'read-only',
'group-hover',
'group-focus',
'focus-within',
'hover',
'focus',
'focus-visible',
'active',
'disabled',
],
}