forked from thi-ng/umbrella
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): add color themes generator example
- Loading branch information
1 parent
b8ceed6
commit 0675d89
Showing
9 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
build | ||
dev | ||
node_modules | ||
yarn.lock | ||
!snowpack.config.js | ||
!*.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# color-themes | ||
|
||
![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/color-themes.png) | ||
|
||
[Live demo](http://demo.thi.ng/umbrella/color-themes/) | ||
|
||
Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. | ||
|
||
## Authors | ||
|
||
- Karsten Schmidt | ||
|
||
## License | ||
|
||
© 2021 Karsten Schmidt // Apache Software License 2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "color-themes", | ||
"version": "0.0.1", | ||
"description": "Probabilistic color theme generator", | ||
"repository": "https://github.com/thi-ng/umbrella", | ||
"author": "Karsten Schmidt <[email protected]>", | ||
"license": "Apache-2.0", | ||
"scripts": { | ||
"clean": "../../node_modules/.bin/rimraf build node_modules/.cache", | ||
"start": "../../node_modules/.bin/snowpack dev --reload", | ||
"build": "../../node_modules/.bin/snowpack build" | ||
}, | ||
"devDependencies": { | ||
"@thi.ng/snowpack-env": "^2.3.3" | ||
}, | ||
"dependencies": { | ||
"@thi.ng/checks": "latest", | ||
"@thi.ng/color": "latest", | ||
"@thi.ng/hiccup-html": "latest", | ||
"@thi.ng/hiccup-svg": "latest", | ||
"@thi.ng/random": "latest", | ||
"@thi.ng/rdom": "latest", | ||
"@thi.ng/rdom-components": "latest", | ||
"@thi.ng/rstream": "latest" | ||
}, | ||
"browserslist": [ | ||
"last 3 Chrome versions" | ||
], | ||
"browser": { | ||
"process": false | ||
}, | ||
"thi.ng": { | ||
"readme": [ | ||
"color", | ||
"hiccup-html", | ||
"hiccup-svg", | ||
"random", | ||
"rdom", | ||
"rdom-components", | ||
"rstream" | ||
], | ||
"screenshot": "examples/color-themes.png" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> | ||
<title>color-themes</title> | ||
<link | ||
href="https://unpkg.com/tachyons@4/css/tachyons.min.css" | ||
rel="stylesheet" | ||
/> | ||
<style> | ||
.grid { | ||
display: grid; | ||
grid-template-columns: 2fr 1fr 3fr; | ||
gap: 1rem; | ||
} | ||
|
||
.grid2 { | ||
display: grid; | ||
grid-template-columns: 1fr 1fr; | ||
gap: 2rem; | ||
} | ||
</style> | ||
<script | ||
async | ||
defer | ||
data-domain="demo.thi.ng" | ||
src="https://plausible.io/js/plausible.js" | ||
></script> | ||
</head> | ||
<body class="sans-serif ma0 overflow-y-hidden"> | ||
<div id="app"></div> | ||
<!-- <div> | ||
<a | ||
class="link" | ||
href="https://github.com/thi-ng/umbrella/tree/develop/examples/color-themes" | ||
>Source code</a | ||
> | ||
</div> --> | ||
<script type="module" src="/_dist_/index.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** @type {import("snowpack").SnowpackUserConfig } */ | ||
module.exports = { | ||
mount: { | ||
public: "/", | ||
src: "/_dist_", | ||
}, | ||
plugins: [ | ||
"@snowpack/plugin-typescript", | ||
[ | ||
"@snowpack/plugin-webpack", | ||
{ | ||
extendConfig: (config) => { | ||
config.node = { | ||
process: false, | ||
setImmediate: false, | ||
util: "empty", | ||
}; | ||
return config; | ||
}, | ||
}, | ||
], | ||
], | ||
installOptions: { | ||
installTypes: true, | ||
}, | ||
buildOptions: { | ||
baseUrl: "/umbrella/color-themes", | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
import { isMobile, isString } from "@thi.ng/checks"; | ||
import { | ||
ColorRangePreset, | ||
colorsFromTheme, | ||
ColorThemePart, | ||
COLOR_RANGES, | ||
css, | ||
CSSColorName, | ||
distCIEDE2000, | ||
lch, | ||
LCH, | ||
proximity, | ||
sort, | ||
swatchesH, | ||
} from "@thi.ng/color"; | ||
import { | ||
button, | ||
checkbox, | ||
div, | ||
inputColor, | ||
inputRange, | ||
span, | ||
} from "@thi.ng/hiccup-html"; | ||
import { svg } from "@thi.ng/hiccup-svg"; | ||
import { SYSTEM, XsAdd } from "@thi.ng/random"; | ||
import { | ||
$compile, | ||
$inputNum, | ||
$list, | ||
$refresh, | ||
ComponentLike, | ||
} from "@thi.ng/rdom"; | ||
import { staticDropdown } from "@thi.ng/rdom-components"; | ||
import { debounce, reactive, Stream, sync, SyncTuple } from "@thi.ng/rstream"; | ||
|
||
// pre-sort range preset IDs for dropdown menus | ||
const RANGE_IDs = <ColorRangePreset[]>Object.keys(COLOR_RANGES).sort(); | ||
|
||
///////////////////////// UI widgets | ||
|
||
const themePartControls = ([id, part]: [string, ColorThemePart]) => { | ||
const stream = <Stream<ColorThemePart>>parts.getSourceForID(id); | ||
return div( | ||
".grid.mb3", | ||
{}, | ||
staticDropdown(RANGE_IDs, reactive(<string>part.range), { | ||
attribs: { | ||
title: "color range preset", | ||
oninput: (e) => | ||
stream.next({ | ||
...part, | ||
range: <ColorRangePreset>( | ||
(<HTMLInputElement>e.target).value | ||
), | ||
}), | ||
}, | ||
}), | ||
inputColor({ | ||
value: css(<LCH>part.base), | ||
title: "base color", | ||
onchange: (e) => | ||
stream.next({ | ||
...part, | ||
base: lch((<HTMLInputElement>e.target).value), | ||
}), | ||
}), | ||
inputRange({ | ||
min: 0, | ||
max: 1, | ||
step: 0.01, | ||
value: part.weight, | ||
title: "weight", | ||
onchange: (e) => | ||
stream.next({ | ||
...part, | ||
weight: parseFloat((<HTMLInputElement>e.target).value), | ||
}), | ||
}) | ||
); | ||
}; | ||
|
||
const control = (label: string, body: ComponentLike) => | ||
div(".grid2.mb3", {}, span({}, label), body); | ||
|
||
const themeSwatches = async ({ | ||
parts, | ||
num, | ||
variance, | ||
seed, | ||
sorted, | ||
}: SyncTuple<typeof mainInputs>) => { | ||
const colors = [ | ||
...colorsFromTheme(Object.values(parts), { | ||
num, | ||
variance, | ||
rnd: new XsAdd(seed), | ||
}), | ||
]; | ||
if (sorted) { | ||
sort(colors, proximity(lch(1, 0, 0), distCIEDE2000())); | ||
} | ||
return <ComponentLike>svg( | ||
{ | ||
width: "100vw", | ||
height: "100vh", | ||
viewBox: `0 0 ${num * 5} 100`, | ||
preserveAspectRatio: "none", | ||
convert: true, | ||
}, | ||
swatchesH(colors, 5, 100) | ||
); | ||
}; | ||
|
||
///////////////////////// streams / app state | ||
|
||
const themePart = ( | ||
range: ColorRangePreset, | ||
base: LCH | CSSColorName, | ||
weight = 1 | ||
) => | ||
reactive<ColorThemePart>({ | ||
range, | ||
base: isString(base) ? lch(base) : base, | ||
weight, | ||
}); | ||
|
||
const randomizeThemeParts = () => { | ||
for (let part of Object.values(parts.getSources())) { | ||
part.next({ | ||
range: RANGE_IDs[SYSTEM.int() % RANGE_IDs.length], | ||
base: lch.random(), | ||
weight: SYSTEM.float(), | ||
}); | ||
} | ||
}; | ||
|
||
const parts = sync({ | ||
src: { | ||
0: themePart("bright", "goldenrod"), | ||
1: themePart("hard", "turquoise", 0.33), | ||
2: themePart("cool", "fuchsia", 0.5), | ||
3: themePart("warm", "seagreen", 0.1), | ||
}, | ||
}); | ||
|
||
// debounce needed for batch update via randomizeTheme() | ||
const debouncedParts = parts.subscribe(debounce(16)); | ||
|
||
const num = reactive(isMobile() ? 100 : 200); | ||
const variance = reactive(0.05); | ||
const sorted = reactive(false); | ||
const seed = reactive(0xdecafbad); | ||
const mainInputs = <const>{ | ||
parts: debouncedParts, | ||
num, | ||
variance, | ||
seed, | ||
sorted, | ||
}; | ||
const main = sync({ src: mainInputs }); | ||
|
||
///////////////////////// UI components | ||
|
||
$compile( | ||
div( | ||
{}, | ||
// color swatches | ||
$refresh<SyncTuple<typeof mainInputs>>(main, themeSwatches), | ||
// theme controls in HUD UI | ||
div( | ||
".z-1.fixed.top-0.left-0.bg-white-80.ma3-m.ma3-l.pa3.w-100.w-50-m.w-33-l", | ||
{}, | ||
// list of controls for each theme part | ||
$list<[string, ColorThemePart]>( | ||
debouncedParts.map((parts) => Object.entries(parts)), | ||
"div", | ||
{}, | ||
themePartControls | ||
), | ||
// global controls: variance, random seed, sorting | ||
control( | ||
"variance", | ||
inputRange({ | ||
min: 0, | ||
max: 0.2, | ||
step: 0.005, | ||
value: variance, | ||
oninput: $inputNum(variance), | ||
}) | ||
), | ||
control( | ||
"random seed", | ||
inputRange({ | ||
min: 0, | ||
max: 1 << 30, | ||
value: seed, | ||
oninput: $inputNum(seed), | ||
}) | ||
), | ||
control( | ||
"sort", | ||
checkbox({ | ||
checked: sorted, | ||
onchange: (e) => | ||
sorted.next( | ||
Boolean((<HTMLInputElement>e.target).checked) | ||
), | ||
}) | ||
), | ||
button( | ||
".bg-black.white.w4", | ||
{ onclick: randomizeThemeParts }, | ||
"randomize" | ||
) | ||
) | ||
) | ||
).mount(document.getElementById("app")!); |
Oops, something went wrong.