diff --git a/README.md b/README.md index 0eec5dc..0ae035c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Inspired by [jsonresume-theme-flat](https://github.com/erming/jsonresume-theme-f - 💄 Markdown support - 📐 CSS grid layout - 🌗 Light and dark modes +- 🎨 Customizable colors - 🧩 Standalone CLI - 📦 ESM and CommonJS builds @@ -50,3 +51,25 @@ _Even_ comes with a barebones CLI that reads resumes from `stdin` and outputs HT ```console npx jsonresume-theme-even < resume.json > resume.html ``` + +## Options + +### Colors + +You can override theme colors via the `.meta.colors` resume field. Each entry defines a tuple of light and (optional) dark color values. If only one array value is defined, it will be used in both light and dark modes. + +Here's an example using the default theme colors: + +```json +{ + "meta": { + "colors": { + "background": ["#ffffff", "#191e23"], + "dimmed": ["#f3f4f5", "#23282d"], + "primary": ["#191e23", "#fbfbfc"], + "secondary": ["#6c7781", "#ccd0d4"], + "accent": ["#0073aa", "#00a0d2"] + } + } +} +``` diff --git a/resume.js b/resume.js index 2515b99..9c2e9d8 100644 --- a/resume.js +++ b/resume.js @@ -11,11 +11,12 @@ import References from './components/references.js' import Skills from './components/skills.js' import Volunteer from './components/volunteer.js' import Work from './components/work.js' +import colors from './utils/colors.js' import html from './utils/html.js' export default function Resume(resume, css) { return html` - + ${Meta(resume.basics)} diff --git a/style.css b/style.css index 74b2816..9295dde 100644 --- a/style.css +++ b/style.css @@ -1,11 +1,23 @@ :root { color-scheme: light dark; - --color-background: #ffffff; /* White */ - --color-muted: #f3f4f5; /* Light Gray 200 */ - --color-primary: #191e23; /* Dark Gray 900 */ - --color-secondary: #6c7781; /* Dark Gray 300 */ - --color-accent: #0073aa; /* WordPress Blue */ + --color-background-light: #ffffff; /* White */ + --color-dimmed-light: #f3f4f5; /* Light Gray 200 */ + --color-primary-light: #191e23; /* Dark Gray 900 */ + --color-secondary-light: #6c7781; /* Dark Gray 300 */ + --color-accent-light: #0073aa; /* WordPress Blue */ + + --color-background-dark: #191e23; /* Dark Gray 900 */ + --color-dimmed-dark: #23282d; /* Dark Gray 800 */ + --color-primary-dark: #fbfbfc; /* Light Gray 100 */ + --color-secondary-dark: #ccd0d4; /* Light Gray 700 */ + --color-accent-dark: #00a0d2; /* Medium Blue */ + + --color-background: var(--color-background-light); + --color-dimmed: var(--color-dimmed-light); + --color-primary: var(--color-primary-light); + --color-secondary: var(--color-secondary-light); + --color-accent: var(--color-accent-light); --scale-ratio: 1.25; --scale0: 1rem; @@ -18,11 +30,11 @@ @media (prefers-color-scheme: dark) { :root { - --color-background: #191e23; /* Dark Gray 900 */ - --color-muted: #23282d; /* Dark Gray 800 */ - --color-primary: #fbfbfc; /* Light Gray 100 */ - --color-secondary: #ccd0d4; /* Light Gray 700 */ - --color-accent: #00a0d2; /* Medium Blue */ + --color-background: var(--color-background-dark); + --color-dimmed: var(--color-dimmed-dark); + --color-primary: var(--color-primary-dark); + --color-secondary: var(--color-secondary-dark); + --color-accent: var(--color-accent-dark); } } @@ -128,7 +140,7 @@ h6 { } blockquote { - border-left: 0.2em solid var(--color-muted); + border-left: 0.2em solid var(--color-dimmed); padding-left: 1em; } @@ -147,7 +159,7 @@ svg { } .masthead { - background: var(--color-muted); + background: var(--color-dimmed); display: inherit; gap: inherit; grid-column: full; @@ -206,7 +218,7 @@ blockquote > * + *, } .tag-list > li { - background: var(--color-muted); + background: var(--color-dimmed); border-radius: 0.2em; padding: 0.2em 0.6em; } diff --git a/test/__snapshots__/index.test.js.snap b/test/__snapshots__/index.test.js.snap index f1837b4..85a41bf 100644 --- a/test/__snapshots__/index.test.js.snap +++ b/test/__snapshots__/index.test.js.snap @@ -2,7 +2,7 @@ exports[`renders a resume 1`] = ` " - + diff --git a/test/index.test.js b/test/index.test.js index 8924cab..bc3df07 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -2,10 +2,21 @@ import { HtmlValidate } from 'html-validate' import { expect, it } from 'vitest' import { render } from '../index.js' -import resume from 'resume-schema/sample.resume.json' assert { type: 'json' } +import sampleResume from 'resume-schema/sample.resume.json' assert { type: 'json' } -// Overwrite empty sample resume values -resume.basics.image = 'image.jpg' +const resume = { + ...sampleResume, + meta: { + ...sampleResume.meta, + colors: { + background: ['lightgray', 'darkgray'], + }, + }, + basics: { + ...sampleResume.basics, + image: 'image.jpg', + }, +} it('renders a resume', () => { expect(render(resume)).toMatchSnapshot() @@ -15,6 +26,7 @@ it('renders valid HTML', async () => { const htmlvalidate = new HtmlValidate({ extends: ['html-validate:recommended'], rules: { + 'no-inline-style': 'off', 'no-trailing-whitespace': 'off', 'tel-non-breaking': 'off', }, diff --git a/utils/colors.js b/utils/colors.js new file mode 100644 index 0000000..269db7f --- /dev/null +++ b/utils/colors.js @@ -0,0 +1,6 @@ +export default function colors(meta = {}) { + const { colors } = meta + return colors && Object.entries(colors) + .map(([name, [light, dark = light]]) => `--color-${name}-light:${light}; --color-${name}-dark:${dark};`) + .join(' ') +}