implement color workflow with palettes and fixed colors
This commit is contained in:
parent
bef0ab09c6
commit
7f28dd7299
12 changed files with 258 additions and 109 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -22,6 +22,7 @@
|
|||
"devDependencies": {
|
||||
"@toycode/markdown-it-class": "^1.2.4",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"colorjs.io": "^0.5.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^7.0.1",
|
||||
"dayjs": "^1.11.5",
|
||||
|
|
@ -2207,6 +2208,13 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorjs.io": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"scripts": {
|
||||
"clean": "rimraf dist src/_includes/css src/_includes/scripts",
|
||||
"favicons": "node ./src/_config/setup/generate-favicons.js",
|
||||
"colors": "node ./src/_config/setup/create-colors.js",
|
||||
"screenshots": "node ./src/_config/setup/generate-screenshots.js",
|
||||
"dev:clean": "rimraf src/assets/og-images",
|
||||
"dev:11ty": "cross-env ELEVENTY_ENV=development eleventy --serve --watch",
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
"devDependencies": {
|
||||
"@toycode/markdown-it-class": "^1.2.4",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"colorjs.io": "^0.5.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^7.0.1",
|
||||
"dayjs": "^1.11.5",
|
||||
|
|
|
|||
102
src/_config/setup/create-colors.js
Normal file
102
src/_config/setup/create-colors.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import fs from 'node:fs';
|
||||
import Color from 'colorjs.io';
|
||||
|
||||
const colorsBase = JSON.parse(fs.readFileSync('./src/_data/designTokens/colorsBase.json', 'utf-8'));
|
||||
|
||||
const generatePalette = (baseColorHex, steps) => {
|
||||
const baseColor = new Color(baseColorHex).to('oklch');
|
||||
|
||||
return steps.map(step => {
|
||||
const color = new Color('oklch', [step.lightness, baseColor.c * step.chromaFactor, baseColor.h]).to(
|
||||
'srgb'
|
||||
);
|
||||
|
||||
const [r, g, b] = color.coords.map(value => Math.round(Math.min(Math.max(value * 255, 0), 255)));
|
||||
|
||||
const hexValue = `#${r.toString(16).padStart(2, '0')}${g
|
||||
.toString(16)
|
||||
.padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||
|
||||
return {
|
||||
name: `${step.label}`,
|
||||
value: hexValue
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const vibrantSteps = [
|
||||
{label: '100', lightness: 0.96, chromaFactor: 0.19},
|
||||
{label: '200', lightness: 0.94, chromaFactor: 0.45},
|
||||
{label: '300', lightness: 0.86, chromaFactor: 0.78},
|
||||
{label: '400', lightness: 0.75, chromaFactor: 0.9},
|
||||
{label: '500', lightness: 0.62, chromaFactor: 1},
|
||||
{label: '600', lightness: 0.5, chromaFactor: 1},
|
||||
{label: '700', lightness: 0.42, chromaFactor: 1},
|
||||
{label: '800', lightness: 0.36, chromaFactor: 0.85},
|
||||
{label: '900', lightness: 0.2, chromaFactor: 0.55}
|
||||
];
|
||||
|
||||
const neutralSteps = [
|
||||
{label: '100', lightness: 0.98, chromaFactor: 0.12},
|
||||
{label: '200', lightness: 0.92, chromaFactor: 0.14},
|
||||
{label: '300', lightness: 0.85, chromaFactor: 0.14},
|
||||
{label: '400', lightness: 0.7, chromaFactor: 0.25},
|
||||
{label: '500', lightness: 0.6, chromaFactor: 0.3},
|
||||
{label: '600', lightness: 0.48, chromaFactor: 0.35},
|
||||
{label: '700', lightness: 0.4, chromaFactor: 0.3},
|
||||
{label: '800', lightness: 0.3, chromaFactor: 0.27},
|
||||
{label: '900', lightness: 0.2, chromaFactor: 0.25}
|
||||
];
|
||||
|
||||
const colorTokens = {
|
||||
title: colorsBase.title,
|
||||
description: colorsBase.description,
|
||||
items: []
|
||||
};
|
||||
|
||||
colorsBase.neutral.forEach(color => {
|
||||
const palette = generatePalette(color.value, neutralSteps);
|
||||
palette.forEach(shade => {
|
||||
colorTokens.items.push({
|
||||
name: `${color.name} ${shade.name}`,
|
||||
value: shade.value
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
colorsBase.vibrant.forEach(color => {
|
||||
const palette = generatePalette(color.value, vibrantSteps);
|
||||
palette.forEach(shade => {
|
||||
colorTokens.items.push({
|
||||
name: `${color.name} ${shade.name}`,
|
||||
value: shade.value
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
colorsBase.fixed.forEach(color => {
|
||||
colorTokens.items.push({
|
||||
name: color.name,
|
||||
value: color.value
|
||||
});
|
||||
|
||||
const fixedColor = new Color(color.value).to('oklch');
|
||||
const subduedColor = new Color('oklch', [
|
||||
fixedColor.l,
|
||||
fixedColor.c * 0.8, // reduce chroma by 20%
|
||||
fixedColor.h
|
||||
]).to('srgb');
|
||||
|
||||
const [r, g, b] = subduedColor.coords.map(value => Math.round(Math.min(Math.max(value * 255, 0), 255)));
|
||||
|
||||
const subduedHex = `#${r.toString(16).padStart(2, '0')}${g
|
||||
.toString(16)
|
||||
.padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||
|
||||
colorTokens.items.push({
|
||||
name: `${color.name} Subdued`,
|
||||
value: subduedHex
|
||||
});
|
||||
});
|
||||
|
||||
fs.writeFileSync('./src/_data/designTokens/colors.json', JSON.stringify(colorTokens, null, 2));
|
||||
|
|
@ -1,26 +1,66 @@
|
|||
{
|
||||
"title": "Colors",
|
||||
"description": "Hex color codes that can be shared, cross-platform. They can be converted at point of usage, such as HSL for web or CMYK for print. The first two colors are used by the theme toggle script to set the theme color.",
|
||||
"description": "Hex color codes that can be shared, cross-platform. They can be converted at point of usage, such as HSL for web or CMYK for print. neutral and vibrant colors are converted to color palettes, fixed colors are kept as they are",
|
||||
"items": [
|
||||
{
|
||||
"name": "Base Dark",
|
||||
"value": "#343434"
|
||||
"name": "Gray 100",
|
||||
"value": "#f8f8f8"
|
||||
},
|
||||
{
|
||||
"name": "Base Light",
|
||||
"value": "#FBFBFB"
|
||||
"name": "Gray 200",
|
||||
"value": "#e4e4e4"
|
||||
},
|
||||
{
|
||||
"name": "Primary Highlight",
|
||||
"name": "Gray 300",
|
||||
"value": "#cecece"
|
||||
},
|
||||
{
|
||||
"name": "Gray 400",
|
||||
"value": "#9e9e9e"
|
||||
},
|
||||
{
|
||||
"name": "Gray 500",
|
||||
"value": "#808080"
|
||||
},
|
||||
{
|
||||
"name": "Gray 600",
|
||||
"value": "#5d5d5d"
|
||||
},
|
||||
{
|
||||
"name": "Gray 700",
|
||||
"value": "#484848"
|
||||
},
|
||||
{
|
||||
"name": "Gray 800",
|
||||
"value": "#2e2e2e"
|
||||
},
|
||||
{
|
||||
"name": "Gray 900",
|
||||
"value": "#161616"
|
||||
},
|
||||
{
|
||||
"name": "Pink",
|
||||
"value": "#dd4462"
|
||||
},
|
||||
{
|
||||
"name": "Secondary Highlight",
|
||||
"name": "Pink Subdued",
|
||||
"value": "#ce5769"
|
||||
},
|
||||
{
|
||||
"name": "Blue",
|
||||
"value": "#4467dd"
|
||||
},
|
||||
{
|
||||
"name": "Tertiary Highlight",
|
||||
"name": "Blue Subdued",
|
||||
"value": "#4d6cc8"
|
||||
},
|
||||
{
|
||||
"name": "Gold",
|
||||
"value": "#fbbe25"
|
||||
},
|
||||
{
|
||||
"name": "Gold Subdued",
|
||||
"value": "#f1c15b"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
25
src/_data/designTokens/colorsBase.json
Normal file
25
src/_data/designTokens/colorsBase.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"title": "Colors",
|
||||
"description": "Hex color codes that can be shared, cross-platform. They can be converted at point of usage, such as HSL for web or CMYK for print. neutral and vibrant colors are converted to color palettes, fixed colors are kept as they are",
|
||||
"neutral": [
|
||||
{
|
||||
"name": "Gray",
|
||||
"value": "#888888"
|
||||
}
|
||||
],
|
||||
"vibrant": [],
|
||||
"fixed": [
|
||||
{
|
||||
"name": "Pink",
|
||||
"value": "#dd4462"
|
||||
},
|
||||
{
|
||||
"name": "Blue",
|
||||
"value": "#4467dd"
|
||||
},
|
||||
{
|
||||
"name": "Gold",
|
||||
"value": "#fbbe25"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -37,5 +37,5 @@
|
|||
.pagination li:has(a[aria-current='page']) {
|
||||
--pagination-bg: var(--color-secondary);
|
||||
--pagination-border: var(--color-secondary);
|
||||
--pagination-text: var(--color-base-light);
|
||||
--pagination-text: var(--color-light);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
/* Global variables. */
|
||||
/* This turned out a little too complex, and probably is overly specific. I'm still fiddling with this. Should work by itself though, once you define your design token colors.
|
||||
Adjust fallbacks if modern color syntax not supported for text and bg variants. */
|
||||
|
||||
/* Basic variable definitions for color schemes */
|
||||
:root {
|
||||
|
|
@ -20,108 +18,58 @@ Adjust fallbacks if modern color syntax not supported for text and bg variants.
|
|||
);
|
||||
--gradient-stripes: linear-gradient(
|
||||
45deg,
|
||||
var(--color-base-dark) 0 75%,
|
||||
var(--color-gray-900) 0 75%,
|
||||
var(--color-primary) 0 85%,
|
||||
var(--color-secondary) 0 92%,
|
||||
var(--color-tertiary) 0 100%
|
||||
);
|
||||
|
||||
--color-light: var(--color-gray-100);
|
||||
--color-dark: var(--color-gray-900);
|
||||
}
|
||||
|
||||
/* Default / Light theme specific variables */
|
||||
/* Light theme */
|
||||
:root,
|
||||
:root[data-theme='light'] {
|
||||
--color-dark: var(--color-base-dark);
|
||||
--color-light: var(--color-base-light);
|
||||
--color-text: var(--color-base-dark);
|
||||
--color-bg: var(--color-base-light);
|
||||
--color-base: var(--color-base-dark);
|
||||
--color-primary: var(--color-primary-highlight);
|
||||
--color-secondary: var(--color-secondary-highlight);
|
||||
--color-tertiary: var(--color-tertiary-highlight);
|
||||
/* Fallbacks if modern color syntax not supported */
|
||||
--color-text-accent: #505050;
|
||||
--color-bg-accent: #fdfdfd;
|
||||
--color-bg-accent-2: #f7f7f7;
|
||||
--color-text: var(--color-gray-800);
|
||||
--color-bg: var(--color-gray-100);
|
||||
--color-base: var(--color-gray-800);
|
||||
|
||||
--color-primary: var(--color-pink);
|
||||
--color-secondary: var(--color-blue);
|
||||
--color-tertiary: var(--color-gold);
|
||||
|
||||
--color-text-accent: var(--color-gray-700);
|
||||
--color-bg-accent: var(--color-gray-200);
|
||||
--color-bg-accent-2: var(--color-gray-300);
|
||||
}
|
||||
|
||||
/* Dark theme specific variables based on system preference */
|
||||
/* Dark theme */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-text: var(--color-base-light);
|
||||
--color-bg: var(--color-base-dark);
|
||||
--color-base: var(--color-base-light);
|
||||
--color-primary: var(--color-primary-highlight);
|
||||
--color-secondary: var(--color-secondary-highlight);
|
||||
--color-tertiary: var(--color-tertiary-highlight);
|
||||
/* Fallbacks if modern color syntax not supported */
|
||||
--color-text-accent: #a8a8a8;
|
||||
--color-bg-accent: #383838;
|
||||
--color-bg-accent-2: #404040;
|
||||
--color-text: var(--color-gray-100);
|
||||
--color-bg: var(--color-gray-800);
|
||||
--color-base: var(--color-gray-100);
|
||||
|
||||
--color-primary: var(--color-pink-subdued);
|
||||
--color-secondary: var(--color-blue-subdued);
|
||||
--color-tertiary: var(--color-gold-subdued);
|
||||
|
||||
--color-text-accent: var(--color-gray-300);
|
||||
--color-bg-accent: var(--color-gray-700);
|
||||
--color-bg-accent-2: var(--color-gray-600);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark theme specific variables based on manual override */
|
||||
:root[data-theme='dark'] {
|
||||
--color-text: var(--color-base-light);
|
||||
--color-bg: var(--color-base-dark);
|
||||
--color-base: var(--color-base-light);
|
||||
--color-primary: var(--color-primary-highlight);
|
||||
--color-secondary: var(--color-secondary-highlight);
|
||||
--color-tertiary: var(--color-tertiary-highlight);
|
||||
/* Fallbacks if modern color syntax not supported */
|
||||
--color-text-accent: #a8a8a8;
|
||||
--color-bg-accent: #383838;
|
||||
--color-bg-accent-2: #404040;
|
||||
}
|
||||
|
||||
/* Enhancements for browsers that support color-mix */
|
||||
@supports (background: color-mix(in srgb, red 50%, blue)) {
|
||||
:root,
|
||||
:root[data-theme='light'] {
|
||||
--color-text-accent: color-mix(in oklab, var(--color-base-dark) 80%, var(--color-bg));
|
||||
--color-bg-accent: color-mix(in oklab, var(--color-bg) 90%, var(--color-text));
|
||||
--color-bg-accent-2: color-mix(in oklab, var(--color-bg) 70%, var(--color-text));
|
||||
--color-primary-sub-20: color-mix(in oklab, var(--color-primary-highlight), var(--color-bg) 20%);
|
||||
--color-secondary-sub-20: color-mix(in oklab, var(--color-secondary-highlight), var(--color-bg) 20%);
|
||||
--color-tertiary-sub-20: color-mix(in oklab, var(--color-tertiary-highlight), var(--color-bg) 20%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-text-accent: color-mix(in oklab, var(--color-base-light) 70%, var(--color-bg));
|
||||
--color-bg-accent: color-mix(in oklab, var(--color-bg) 92%, var(--color-text));
|
||||
--color-bg-accent-2: color-mix(in oklab, var(--color-bg) 80%, var(--color-text));
|
||||
/* add opacity with color-mix */
|
||||
--color-primary: var(--color-primary-sub-20);
|
||||
--color-secondary: var(--color-secondary-sub-20);
|
||||
--color-tertiary: var(--color-tertiary-sub-20);
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
--color-text-accent: color-mix(in oklab, var(--color-base-light) 70%, var(--color-bg));
|
||||
--color-bg-accent: color-mix(in oklab, var(--color-bg) 92%, var(--color-text));
|
||||
--color-bg-accent-2: color-mix(in oklab, var(--color-bg) 80%, var(--color-text));
|
||||
/* add opacity with color-mix */
|
||||
--color-primary: var(--color-primary-sub-20);
|
||||
--color-secondary: var(--color-secondary-sub-20);
|
||||
--color-tertiary: var(--color-tertiary-sub-20);
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhancements for browsers that support relative color values */
|
||||
@supports (color: hsl(from red h s l)) {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-primary: hsl(from var(--color-primary-highlight) h calc(s / 1.2) l);
|
||||
--color-secondary: hsl(from var(--color-secondary-highlight) h calc(s / 1.2) l);
|
||||
--color-tertiary: hsl(from var(--color-tertiary-highlight) h calc(s / 1.2) l);
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
--color-primary: hsl(from var(--color-primary-highlight) h calc(s / 1.2) l);
|
||||
--color-secondary: hsl(from var(--color-secondary-highlight) h calc(s / 1.2) l);
|
||||
--color-tertiary: hsl(from var(--color-tertiary-highlight) h calc(s / 1.2) l);
|
||||
}
|
||||
--color-text: var(--color-gray-100);
|
||||
--color-bg: var(--color-gray-800);
|
||||
--color-base: var(--color-gray-100);
|
||||
|
||||
--color-primary: var(--color-pink-subdued);
|
||||
--color-secondary: var(--color-blue-subdued);
|
||||
--color-tertiary: var(--color-gold-subdued);
|
||||
|
||||
--color-text-accent: var(--color-gray-300);
|
||||
--color-bg-accent: var(--color-gray-700);
|
||||
--color-bg-accent-2: var(--color-gray-600);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
/* based on Andy Bell's article: https://piccalil.li/blog/how-i-build-a-button-component/ */
|
||||
|
||||
.button {
|
||||
--button-bg: var(--color-text);
|
||||
--button-color: color-mix(in oklab, var(--button-bg) 10%, var(--color-bg));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,26 @@ title: Design tokens
|
|||
|
||||
Edit all your preferences (colors, fluid text sizes etc.) in `src/_data/designTokens/*.json`.
|
||||
|
||||
Additional colors, variants and gradients for custom properties are automatically created in `src/assets/css/global/base/variables.css` based on the colors set in `colors.json`. If you change names you should edit `variables.css` as well and check if the names are used elsewhere in the template.
|
||||
Admittedly, I went a little bit crazy there with the new CSS color syntax stuff.
|
||||
Additional colors, variants and gradients for custom properties are automatically created in `src/assets/css/global/base/variables.css` based on the colors set in `colors.json`.
|
||||
|
||||
In the [style guide](/styleguide/) you can see how everything turns out.
|
||||
|
||||
**Special case: colors**
|
||||
|
||||
As of version 4.0, you can create colors dynamically. Run `npm run colors` after setting your color values in `src/_data/designTokens/colorsBase.json`. This will create / overwrite the required `colors.json` file in the same directory. These colors become custom properties (e.g. `--color-gray-100`) and utility classes similar to the Tailwind CSS syntax (for example `bg-gray-100`, `text-gray-900`).
|
||||
|
||||
If you want to adjust how the colors turn out, edit `src/_config/setup/create-colors.js`.
|
||||
|
||||
Placed under `neutral` or `vibrant`, colors are converted into scalable palettes. `neutral` is better for grayish / monochromatic colors, while `vibrant` is better for colorful palettes. Colors listed under `fixed` remain as standalone values without generating shades. Each color placed here also generates a "subdued" version for the dark theme.
|
||||
|
||||
```js
|
||||
// this creates a palette with shades of green, 100 to 900
|
||||
"vibrant": [
|
||||
{
|
||||
"name": "green",
|
||||
"value": "#008000"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
<strong class="text-pink">Important:</strong> If you change the color names, you must edit `src/assets/css/global/base/variables.css` with your color names. The rest of the CSS files should only reference custom properties set in `variables.css`.
|
||||
|
|
@ -11,7 +11,7 @@ permalink: 'blog/{% if pagination.pageNumber >=1 %}page-{{ pagination.pageNumber
|
|||
<article class="wrapper">
|
||||
<header class="full | section" style="--spot-color: var(--color-secondary)">
|
||||
<div class="section__inner flow region" style="--region-space-top: var(--space-xl-2xl)">
|
||||
<h1 class="text-center text-base-light">{{ title }}</h1>
|
||||
<h1 class="text-center" style="color: var(--color-light);">{{ title }}</h1>
|
||||
</div>
|
||||
|
||||
{% svg "divider/edgy", null, "seperator" %}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ blog:
|
|||
<div class="wrapper">
|
||||
<header class="full | section" style="--spot-color: var(--color-primary)">
|
||||
<div class="section__inner flow region">
|
||||
<h1 class="text-center text-base-light">{{ title }}</h1>
|
||||
<h1 class="text-center" style="color: var(--color-light);">{{ title }}</h1>
|
||||
</div>
|
||||
|
||||
{% svg "divider/waves", null, "seperator" %}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ customGradients:
|
|||
<a href="#" class="button">Default button</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="button" data-button-variant="primary">blue button</a>
|
||||
<a href="#" class="button" data-button-variant="primary">pink button</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="button" data-button-variant="secondary">red button</a>
|
||||
<a href="#" class="button" data-button-variant="secondary">blue button</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="button" data-button-variant="tertiary">yellow button</a>
|
||||
<a href="#" class="button" data-button-variant="tertiary">gold button</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
|
@ -70,7 +70,10 @@ customGradients:
|
|||
<div style="background-color: var(--color-{{ color.name | slugify }}"></div>
|
||||
|
||||
<p>
|
||||
<code>var(--color-{{ color.name | slugify }})</code>
|
||||
<code
|
||||
>#{{ color.value | slugify }}<br />
|
||||
var(--color-{{ color.name | slugify }})</code
|
||||
>
|
||||
</p>
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
|
|
|
|||
Loading…
Reference in a new issue