├── .editorconfig ├── .gitignore ├── config ├── rollup.js ├── template.js └── uglify.js ├── license ├── package.json ├── readme.md ├── src ├── colors │ ├── convert.js │ ├── index.js │ ├── material.js │ └── open.js ├── index.html ├── index.js ├── index.sass ├── static │ ├── favicon.ico │ ├── icon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── mstile-152x152.png │ ├── img │ │ ├── .gitkeep │ │ └── colors.svg │ └── manifest.json ├── styles │ ├── all.sass │ ├── base │ │ ├── doc.sass │ │ ├── layout.sass │ │ └── loader.sass │ ├── config │ │ ├── extns.sass │ │ ├── mixins.sass │ │ └── vars.sass │ └── tags │ │ ├── color.sass │ │ ├── modal.sass │ │ ├── radio.sass │ │ ├── side.sass │ │ ├── toast.sass │ │ └── top.sass ├── unused │ ├── app.js │ ├── burst.js │ ├── shared.js │ ├── sw.js │ ├── tag │ │ ├── color.js │ │ ├── copy-animation.js │ │ ├── mojs-copy-animation.js │ │ ├── shade.js │ │ ├── side.js │ │ ├── toast.js │ │ └── top.js │ └── tweens.js └── views │ ├── anim.js │ ├── index.js │ ├── radio.js │ ├── shade.js │ ├── shade2.js │ ├── shared.js │ ├── switcher.js │ ├── toast.js │ └── top.js ├── taskfile.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,yml,md}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | dist 5 | release 6 | -------------------------------------------------------------------------------- /config/rollup.js: -------------------------------------------------------------------------------- 1 | const buble = require('rollup-plugin-buble'); 2 | const replace = require('rollup-plugin-replace'); 3 | const resolve = require('rollup-plugin-node-resolve'); 4 | 5 | module.exports = isDev => ({ 6 | rollup: { 7 | plugins: [ 8 | buble({ 9 | jsx: 'h', 10 | transforms: { 11 | modules: false 12 | } 13 | }), 14 | replace({'process.env.NODE_ENV': JSON.stringify(isDev || 'production')}), 15 | resolve({browser: true, jsnext: true}) 16 | ] 17 | }, 18 | bundle: { 19 | format: 'iife', 20 | sourceMap: isDev && true 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /config/template.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a `dist/package.json` 3 | * @param {Number} semver Current semver 4 | */ 5 | module.exports = semver => ({ 6 | name: 'colors', 7 | version: semver, 8 | repository: 'lukeed/colors-app', 9 | author: { 10 | name: 'Luke Edwards', 11 | email: 'luke.edwards05@gmail.com', 12 | url: 'https://lukeed.com' 13 | }, 14 | scripts: { 15 | start: 'serve --single' 16 | }, 17 | dependencies: { 18 | 'serve': '^4.0.0' 19 | }, 20 | now: { 21 | name: 'colors', 22 | alias: 'colors.now.sh' 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /config/uglify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | compress: { 5 | conditionals: 1, 6 | drop_console: 1, 7 | comparisons: 1, 8 | join_vars: 1, 9 | booleans: 1, 10 | loops: 1 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Luke Edwards (https://lukeed.com) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colors", 3 | "version": "2.0.2", 4 | "description": "Copy color values from popular palettes. Supports HEX, RGB, and HSL formats.", 5 | "license": "MIT", 6 | "repository": "lukeed/colors-app", 7 | "author": { 8 | "name": "Luke Edwards", 9 | "email": "luke.edwards05@gmail.com", 10 | "url": "https://lukeed.com" 11 | }, 12 | "scripts": { 13 | "build": "taskr build release", 14 | "deploy": "npm run build && now release", 15 | "start": "serve release --single", 16 | "watch": "taskr build watch" 17 | }, 18 | "engines": { 19 | "node": ">= 4.6.0" 20 | }, 21 | "dependencies": { 22 | "animejs": "^2.0.2", 23 | "ganalytics": "^2.0.1", 24 | "md-colors": "^1.0.0", 25 | "preact": "^8.1.0", 26 | "preact-router": "^2.5.1" 27 | }, 28 | "devDependencies": { 29 | "@taskr/clear": "^1.0.6", 30 | "@taskr/concat": "^1.0.6", 31 | "@taskr/esnext": "^1.0.0", 32 | "@taskr/htmlmin": "^1.0.6", 33 | "@taskr/rev": "^1.0.6", 34 | "@taskr/sass": "^1.0.6", 35 | "@taskr/uglify": "^1.0.6", 36 | "@taskr/watch": "^1.0.6", 37 | "browser-sync": "^2.18.8", 38 | "connect-history-api-fallback": "^1.3.0", 39 | "fly-precache": "^2.1.0", 40 | "fly-rollup": "^2.1.0", 41 | "rollup-plugin-buble": "^0.15.0", 42 | "rollup-plugin-node-resolve": "^3.0.0", 43 | "rollup-plugin-replace": "^1.1.1", 44 | "serve": "^5.1.4", 45 | "taskr": "^1.0.6", 46 | "taskr-autoprefixer": "^1.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # colors-app 2 | 3 | > Copy color values from popular palettes. Supports HEX, RGB, and HSL formats. 4 | 5 | ##### Current Palettes 6 | - [Material Design](https://material.io/guidelines/style/color.html) 7 | - [Open Color](https://yeun.github.io/open-color/) 8 | - Please suggest more! 9 | 10 | ##### Why? 11 | 12 | One night I asked myself, "Self, what can I build in **two hours** using [Preact](https://github.com/developit/preact)?" :thinking:
13 | The [initial version](https://github.com/lukeed/colors-app/tree/433ab81727b136da7bd7f8d3f5ca9c9a42ad3d15) took _less than 2 hours_ and [I was pretty happy](https://twitter.com/lukeed05/status/812088705171107840) with it.* :smile: 14 | 15 | Additional features (and other changes) have and will continue to improve this web app. 16 | 17 | > ***** I got a head start by using [`fly-kit-preact`](https://github.com/lukeed/fly-kit-preact). Webpack users may want to see [`preact-starter`](https://github.com/lukeed/preact-starter) instead! 18 | 19 | ## Install 20 | 21 | ```sh 22 | git clone https://github.com/lukeed/colors-app 23 | npm install 24 | npm start 25 | ``` 26 | 27 | > :exclamation: **Pro Tip:** Use [Yarn](https://yarnpkg.com/) to install dependencies 3x faster than NPM! 28 | 29 | ## Development 30 | 31 | ### Commands 32 | 33 | Any of the following commands can (and should :wink:) be run from the command line. 34 | 35 | > If using [Yarn](https://yarnpkg.com/), all instances of `npm` can be replaced with `yarn`. :ok_hand: 36 | 37 | #### build 38 | 39 | ``` 40 | $ npm run build 41 | ``` 42 | 43 | Compiles all files. Output is sent to the `dist` directory. 44 | 45 | #### start 46 | 47 | ``` 48 | $ npm start 49 | ``` 50 | 51 | Executes [`build`](#build) and runs your application (from the `dist` directory) in the browser. 52 | 53 | #### watch 54 | 55 | ``` 56 | $ npm run watch 57 | ``` 58 | 59 | Like [`start`](#start), but will auto-compile & auto-reload the server after any file changes within the `src` directory. 60 | 61 | 62 | ## License 63 | 64 | MIT © [Luke Edwards](https://lukeed.com) 65 | -------------------------------------------------------------------------------- /src/colors/convert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `hex-rgb` 3 | * @see https://github.com/sindresorhus/hex-rgb 4 | */ 5 | export function hex2rgb(hex) { 6 | hex = hex.replace(/^#/, ''); 7 | 8 | if (hex.length === 3) { 9 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; 10 | } 11 | 12 | const num = parseInt(hex, 16); 13 | return [num >> 16, num >> 8 & 255, num & 255]; 14 | } 15 | 16 | export function isDark(rgb) { 17 | return Math.round( 18 | ((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000 19 | ) <= 140; 20 | } 21 | 22 | /** 23 | * `colr-convert` 24 | * @see https://github.com/stayradiated/colr-convert 25 | */ 26 | export function rgb2hsl (rgb) { 27 | const r = rgb[0] / 255; 28 | const g = rgb[1] / 255; 29 | const b = rgb[2] / 255; 30 | 31 | const min = Math.min(r, g, b); 32 | const max = Math.max(r, g, b); 33 | const dif = max - min; 34 | 35 | let h = Math.min(( 36 | max === min ? 0 : 37 | r === max ? (g - b) / dif : 38 | g === max ? 2 + (b - r) / dif : 39 | b === max ? 4 + (r - g) / dif : 0 40 | ) * 60, 360); 41 | 42 | if (h < 0) { 43 | h += 360; 44 | } 45 | 46 | const l = (min + max) / 2 * 100; 47 | 48 | const s = 100 * ( 49 | max === min ? 0 : 50 | l <= 0.5 ? dif / (max + min) : 51 | dif / (2 - max - min) 52 | ); 53 | 54 | return [h, s, l].map(Math.round); 55 | } 56 | -------------------------------------------------------------------------------- /src/colors/index.js: -------------------------------------------------------------------------------- 1 | import * as MD from './material'; 2 | import * as OC from './open'; 3 | 4 | /** 5 | * Pair Color values with Key names. 6 | * - (color list length is variable) 7 | */ 8 | const pair = (colors, keys) => { 9 | let i, k, len, out = {}; 10 | for (k in colors) { 11 | out[k] = {}; 12 | len = colors[k].length; 13 | for (i = 0; i < len; i++) { 14 | out[k][keys[i]] = colors[k][i]; 15 | } 16 | } 17 | return out; 18 | }; 19 | 20 | const format = obj => ({ 21 | base: obj.base, 22 | names: obj.names, 23 | colors: pair(obj.all, obj.keys) 24 | }); 25 | 26 | export default { 27 | material: format(MD), 28 | open: format(OC) 29 | } 30 | -------------------------------------------------------------------------------- /src/colors/material.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Material Design Palette 3 | * @see https://material.io/guidelines/style/color.html 4 | */ 5 | 6 | export const base = '500'; 7 | 8 | export const keys = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', 'A100', 'A200', 'A400', 'A700']; 9 | 10 | export const all = { 11 | red: ['FFEBEE', 'FFCDD2', 'EF9A9A', 'E57373', 'EF5350', 'F44336', 'E53935', 'D32F2F', 'C62828', 'B71C1C', 'FF8A80', 'FF5252', 'FF1744', 'D50000'], 12 | pink: ['FCE4EC', 'F8BBD0', 'F48FB1', 'F06292', 'EC407A', 'E91E63', 'D81B60', 'C2185B', 'AD1457', '880E4F', 'FF80AB', 'FF4081', 'F50057', 'C51162'], 13 | purple: ['F3E5F5', 'E1BEE7', 'CE93D8', 'BA68C8', 'AB47BC', '9C27B0', '8E24AA', '7B1FA2', '6A1B9A', '4A148C', 'EA80FC', 'E040FB', 'D500F9', 'AA00FF'], 14 | 'deep-purple': ['EDE7F6', 'D1C4E9', 'B39DDB', '9575CD', '7E57C2', '673AB7', '5E35B1', '512DA8', '4527A0', '311B92', 'B388FF', '7C4DFF', '651FFF', '6200EA'], 15 | indigo: ['E8EAF6', 'C5CAE9', '9FA8DA', '7986CB', '5C6BC0', '3F51B5', '3949AB', '303F9F', '283593', '1A237E', '8C9EFF', '536DFE', '3D5AFE', '304FFE'], 16 | blue: ['E3F2FD', 'BBDEFB', '90CAF9', '64B5F6', '42A5F5', '2196F3', '1E88E5', '1976D2', '1565C0', '0D47A1', '82B1FF', '448AFF', '2979FF', '2962FF'], 17 | 'light-blue': ['E1F5FE', 'B3E5FC', '81D4FA', '4FC3F7', '29B6F6', '03A9F4', '039BE5', '0288D1', '0277BD', '01579B', '80D8FF', '40C4FF', '00B0FF', '0091EA'], 18 | cyan: ['E0F7FA', 'B2EBF2', '80DEEA', '4DD0E1', '26C6DA', '00BCD4', '00ACC1', '0097A7', '00838F', '006064', '84FFFF', '18FFFF', '00E5FF', '00B8D4'], 19 | teal: ['E0F2F1', 'B2DFDB', '80CBC4', '4DB6AC', '26A69A', '009688', '00897B', '00796B', '00695C', '004D40', 'A7FFEB', '64FFDA', '1DE9B6', '00BFA5'], 20 | green: ['E8F5E9', 'C8E6C9', 'A5D6A7', '81C784', '66BB6A', '4CAF50', '43A047', '388E3C', '2E7D32', '1B5E20', 'B9F6CA', '69F0AE', '00E676', '00C853'], 21 | 'light-green': ['F1F8E9', 'DCEDC8', 'C5E1A5', 'AED581', '9CCC65', '8BC34A', '7CB342', '689F38', '558B2F', '33691E', 'CCFF90', 'B2FF59', '76FF03', '64DD17'], 22 | lime: ['F9FBE7', 'F0F4C3', 'E6EE9C', 'DCE775', 'D4E157', 'CDDC39', 'C0CA33', 'AFB42B', '9E9D24', '827717', 'F4FF81', 'EEFF41', 'C6FF00', 'AEEA00'], 23 | yellow: ['FFFDE7', 'FFF9C4', 'FFF59D', 'FFF176', 'FFEE58', 'FFEB3B', 'FDD835', 'FBC02D', 'F9A825', 'F57F17', 'FFFF8D', 'FFFF00', 'FFEA00', 'FFD600'], 24 | amber: ['FFF8E1', 'FFECB3', 'FFE082', 'FFD54F', 'FFCA28', 'FFC107', 'FFB300', 'FFA000', 'FF8F00', 'FF6F00', 'FFE57F', 'FFD740', 'FFC400', 'FFAB00'], 25 | orange: ['FFF3E0', 'FFE0B2', 'FFCC80', 'FFB74D', 'FFA726', 'FF9800', 'FB8C00', 'F57C00', 'EF6C00', 'E65100', 'FFD180', 'FFAB40', 'FF9100', 'FF6D00'], 26 | 'deep-orange': ['FBE9E7', 'FFCCBC', 'FFAB91', 'FF8A65', 'FF7043', 'FF5722', 'F4511E', 'E64A19', 'D84315', 'BF360C', 'FF9E80', 'FF6E40', 'FF3D00', 'DD2C00'], 27 | brown: ['EFEBE9', 'D7CCC8', 'BCAAA4', 'A1887F', '8D6E63', '795548', '6D4C41', '5D4037', '4E342E', '3E2723'], 28 | grey: ['FAFAFA', 'F5F5F5', 'EEEEEE', 'E0E0E0', 'BDBDBD', '9E9E9E', '757575', '616161', '424242', '212121'], 29 | 'blue-grey': ['ECEFF1', 'CFD8DC', 'B0BEC5', '90A4AE', '78909C', '607D8B', '546E7A', '455A64', '37474F', '263238'] 30 | } 31 | 32 | export const names = Object.keys(all); 33 | -------------------------------------------------------------------------------- /src/colors/open.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Open Color Palette 3 | * @see https://yeun.github.io/open-color/ 4 | */ 5 | 6 | export const base = '6'; 7 | 8 | export const keys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; 9 | 10 | export const all = { 11 | red: ['FFF5F5', 'FFE3E3', 'FFC9C9', 'FFA8A8', 'FF8787', 'FF6B6B', 'FA5252', 'F03E3E', 'E03131', 'C92A2A'], 12 | pink: ['FFF0F6', 'FFDEEB', 'FCC2D7', 'FAA2C1', 'F783AC', 'F06595', 'E64980', 'D6336C', 'C2255C', 'A61E4D'], 13 | grape: ['F8F0FC', 'F3D9FA', 'EEBEFA', 'E599F7', 'DA77F2', 'CC5DE8', 'BE4BDB', 'AE3EC9', '9C36B5', '862E9C'], 14 | violet: ['F3F0FF', 'E5DBFF', 'D0BFFF', 'B197FC', '9775FA', '845EF7', '7950F2', '7048E8', '6741D9', '5F3DC4'], 15 | indigo: ['EDF2FF', 'DBE4FF', 'BAC8FF', '91A7FF', '748FFC', '5C7CFA', '4C6EF5', '4263EB', '3B5BDB', '364FC7'], 16 | blue: ['E8F7FF', 'CCEDFF', 'A3DAFF', '72C3FC', '4DADF7', '329AF0', '228AE6', '1C7CD6', '1B6EC2', '1862AB'], 17 | cyan: ['E3FAFC', 'C5F6FA', '99E9F2', '66D9E8', '3BC9DB', '22B8CF', '15AABF', '1098AD', '0C8599', '0B7285'], 18 | teal: ['E6FCF5', 'C3FAE8', '96F2D7', '63E6BE', '38D9A9', '20C997', '12B886', '0CA678', '099268', '087F5B'], 19 | green: ['EBFBEE', 'D3F9D8', 'B2F2BB', '8CE99A', '69DB7C', '51CF66', '40C057', '37B24D', '2F9E44', '2B8A3E'], 20 | lime: ['F4FCE3', 'E9FAC8', 'D8F5A2', 'C0EB75', 'A9E34B', '94D82D', '82C91E', '74B816', '66A80F', '5C940D'], 21 | yellow: ['FFF9DB', 'FFF3BF', 'FFEC99', 'FFE066', 'FFD43B', 'FCC419', 'FAB005', 'F59F00', 'F08C00', 'E67700'], 22 | orange: ['FFF4E6', 'FFE8CC', 'FFD8A8', 'FFC078', 'FFA94D', 'FF922B', 'FD7E14', 'F76707', 'E8590C', 'D9480F'], 23 | gray: ['F8F9FA', 'F1F3F5', 'E9ECEF', 'DEE2E6', 'CED4DA', 'ADB5BD', '868E96', '495057', '343A40', '212529'] 24 | }; 25 | 26 | export const names = Object.keys(all); 27 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Material Colors 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

ʕ•ᴥ•ʔ

37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { h, render } from 'preact'; 2 | import GAnalytics from 'ganalytics'; 3 | import { doc } from './views/shared'; 4 | import App from './views'; 5 | 6 | render(, doc.body); 7 | 8 | if (process.env.NODE_ENV === 'production') { 9 | // cache all assets if browser supports serviceworker 10 | if ('serviceWorker' in navigator && location.protocol === 'https:') { 11 | navigator.serviceWorker.register('/service-worker.js'); 12 | } 13 | 14 | // add Google Analytics 15 | window.ga = new GAnalytics('UA-41153115-6'); 16 | } 17 | -------------------------------------------------------------------------------- /src/index.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Main Stylesheet 3 | */ 4 | 5 | @import 'md-colors' 6 | @import 'styles/all' 7 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/icon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/icon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/static/icon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/icon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/static/icon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/icon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/static/icon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/icon/favicon-16x16.png -------------------------------------------------------------------------------- /src/static/icon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/icon/favicon-32x32.png -------------------------------------------------------------------------------- /src/static/icon/mstile-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/icon/mstile-152x152.png -------------------------------------------------------------------------------- /src/static/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/colors-app/a06fd176ac98e22ba9f16aff16aeb74b0cc75b67/src/static/img/.gitkeep -------------------------------------------------------------------------------- /src/static/img/colors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 15 | 17 | 21 | 22 | 27 | 28 | 29 | 31 | 32 | 34 | 35 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 70 | 72 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Colors", 3 | "short_name": "colors", 4 | "start_url": "/material/red", 5 | "display": "fullscreen", 6 | "orientation": "portrait", 7 | "background_color": "#ffffff", 8 | "theme_color": "#fafafa", 9 | "icons": [{ 10 | "src": "icon/android-chrome-192x192.png", 11 | "type": "image/png", 12 | "sizes": "192x192" 13 | }, 14 | { 15 | "src": "icon/android-chrome-512x512.png", 16 | "type": "image/png", 17 | "sizes": "512x512" 18 | }] 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/all.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Styles/Partials 3 | */ 4 | 5 | @import 'config/vars' 6 | @import 'config/extns' 7 | @import 'config/mixins' 8 | 9 | @import 'base/doc' 10 | // @import 'base/loader' 11 | @import 'base/layout' 12 | 13 | @import 'tags/top' 14 | @import 'tags/side' 15 | @import 'tags/color' 16 | @import 'tags/toast' 17 | @import 'tags/radio' 18 | @import 'tags/modal' 19 | -------------------------------------------------------------------------------- /src/styles/base/doc.sass: -------------------------------------------------------------------------------- 1 | 2 | * 3 | margin: 0 4 | padding: 0 5 | box-sizing: border-box 6 | 7 | body 8 | // color: $gray-dark 9 | font-size: $base-font-size 10 | font-family: $base-font-family 11 | -webkit-tap-highlight-color: rgba(0,0,0,0) 12 | -webkit-touch-callout: none 13 | // +selection($yellow, 0.65) 14 | overflow-x: hidden 15 | +font-smoothing() 16 | // +MQ(tablet) 17 | // font-size: $base-font-size * 0.9 18 | 19 | a 20 | text-decoration: none 21 | outline: none 22 | 23 | // img 24 | // pointer-events: none 25 | // user-select: none 26 | -------------------------------------------------------------------------------- /src/styles/base/layout.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Layout 3 | */ 4 | 5 | #app 6 | width: 100vw 7 | height: 100vh 8 | 9 | #content 10 | @extend %dflex 11 | background-color: md-color('grey', '100') 12 | top: $top-height 13 | position: fixed 14 | bottom: 0 15 | right: 0 16 | left: 0 17 | 18 | #nojs 19 | position: absolute 20 | text-align: center 21 | font-size: 85% 22 | bottom: 1em 23 | width: 100% 24 | 25 | #burst 26 | position: absolute 27 | -------------------------------------------------------------------------------- /src/styles/base/loader.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Loader / Splash 3 | */ 4 | 5 | /** 6 |
7 | Material Colors 8 |

Material Colors

9 |

A Material Design color browser and picker. Supports HEX, RGB, and HSL formats.

10 |
11 | */ 12 | 13 | #loader 14 | @extend #content 15 | @extend %column 16 | @extend %jcenter 17 | @extend %acenter 18 | background: $white 19 | visibility: visible 20 | text-align: center 21 | @extend %fader 22 | top: 0 23 | 24 | * 25 | width: 85% 26 | max-width: 20em 27 | 28 | img 29 | margin-top: -2.5em 30 | max-width: 18em 31 | 32 | p 33 | margin-top: 0.5em 34 | font-style: italic 35 | font-size: 90% 36 | 37 | #app 38 | @extend %fader 39 | opacity: 0 40 | 41 | .loaded 42 | #loader 43 | opacity: 0 44 | #app 45 | opacity: 1 46 | -------------------------------------------------------------------------------- /src/styles/config/extns.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Flex extensions 3 | */ 4 | 5 | %flex 6 | flex: 1 7 | 8 | %dflex 9 | display: block 10 | display: flex 11 | 12 | %column 13 | flex-direction: column 14 | 15 | %acenter 16 | align-items: center 17 | 18 | %jcenter 19 | justify-content: center 20 | -------------------------------------------------------------------------------- /src/styles/config/mixins.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Mixins / Helpers 3 | */ 4 | 5 | %scroller 6 | height: 100% 7 | overflow: auto 8 | overflow-x: hidden 9 | padding: $gutter $gutter 0 10 | -webkit-overflow-scrolling: touch 11 | will-change: transform 12 | 13 | %scroll-item 14 | width: 100% 15 | display: block 16 | border-radius: $radius 17 | margin-bottom: $gutter 18 | 19 | %fader 20 | transition: opacity $base-timing $base-curve 21 | 22 | =font-smoothing 23 | -moz-osx-font-smoothing: grayscale 24 | -webkit-font-smoothing: antialiased 25 | text-rendering: optimizeLegibility 26 | 27 | =selection($color, $amount) 28 | ::-moz-selection 29 | background-color: rgba($color, $amount) 30 | ::selection 31 | background-color: rgba($color, $amount) 32 | 33 | =centerX 34 | left: 50% 35 | transform: translateX(-50%) 36 | -------------------------------------------------------------------------------- /src/styles/config/vars.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic Vars 3 | */ 4 | 5 | $radius: 4px 6 | $gutter: 4px 7 | 8 | $white: #FFF 9 | $black: md-color('grey', '900') 10 | 11 | $base-font-size: 16px 12 | $base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif 13 | 14 | $base-timing: 0.3s 15 | $base-curve: cubic-bezier(0.4, 0, 0.2, 1) 16 | 17 | $top-height: 2.5em 18 | -------------------------------------------------------------------------------- /src/styles/tags/color.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Color Shades 3 | */ 4 | 5 | $color-height: 5em // 80 6 | $color-text-xy: 0.5em 7 | 8 | #color 9 | @extend %flex 10 | @extend %scroller 11 | padding-left: 0 12 | 13 | li 14 | position: relative 15 | @extend %scroll-item 16 | @extend %dflex 17 | @extend %acenter 18 | @extend %jcenter 19 | min-height: $color-height 20 | 21 | * 22 | pointer-events: none 23 | user-select: none 24 | 25 | label, span 26 | position: absolute 27 | font-weight: 700 28 | font-size: 80% 29 | 30 | label 31 | top: $color-text-xy 32 | left: $color-text-xy 33 | 34 | span 35 | font-style: italic 36 | right: $color-text-xy 37 | bottom: $color-text-xy 38 | -------------------------------------------------------------------------------- /src/styles/tags/modal.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Palette Switcher Modal 3 | */ 4 | 5 | .modal 6 | position: relative 7 | visibility: hidden 8 | transition: all $base-timing ease $base-timing 9 | opacity: 0 10 | z-index: 5 // top zindex 11 | 12 | &__overlay 13 | @extend #app 14 | position: absolute 15 | background: rgba($black,0.35) 16 | top: 0 17 | 18 | &__content 19 | position: absolute 20 | contain: strict 21 | min-height: 8em 22 | height: 30vh 23 | width: 80vw 24 | top: 50vh 25 | left: 50% 26 | transition: all $base-timing ease 27 | transform: translate(-50%, -35%) 28 | box-shadow: 0 3px 6px rgba($black, 0.16), 0 3px 6px rgba($black, 0.23) 29 | will-change: opacity, transform 30 | background: $white 31 | padding: 1.5em 0 32 | opacity: 0 33 | 34 | .md-radio 35 | margin-left: 1.5em 36 | 37 | h3 38 | text-align: center 39 | margin-bottom: 1em 40 | 41 | &.open 42 | transition-delay: 0s 43 | visibility: visible 44 | opacity: 1 45 | 46 | .modal__content 47 | transition-delay: $base-timing 48 | transform: translate(-50%, -50%) 49 | opacity: 1 50 | -------------------------------------------------------------------------------- /src/styles/tags/radio.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://github.com/lukeed/md-radio 3 | */ 4 | 5 | $radio-inner-size: 1em 6 | $radio-outer-size: 1.5em 7 | 8 | $radio-font-size: 1em 9 | 10 | $radio-text-color: $black 11 | $radio-inactive-color: lighten(md-color('grey', '400'), 10) 12 | $radio-active-color: md-color('blue', '500') 13 | $radio-inner-color: $white 14 | 15 | $radio-transition: all $base-timing ease-in-out 16 | 17 | =size($val) 18 | width: $val 19 | height: $val 20 | 21 | .md-radio 22 | position: relative 23 | display: inline-block 24 | font-size: $radio-font-size 25 | line-height: 2.5 * $radio-font-size 26 | cursor: pointer 27 | 28 | &:hover .md-radio__fake > span 29 | transform: scale(0.5) 30 | opacity: .5 31 | 32 | input 33 | +size(1px) 34 | opacity: 0 // Hide input, but leave it available for tabbing 35 | // display: none // jk 36 | 37 | &:checked + .md-radio__fake 38 | border-color: $radio-active-color 39 | transform: scale(1) 40 | > span 41 | transform: scale(1) 42 | opacity: 1 43 | &:focus + .md-radio__fake > span 44 | background-color: darken($radio-active-color, 10) 45 | transform: scale(1) 46 | opacity: 1 47 | 48 | &__fake, 49 | &__fake > span 50 | transition: $radio-transition 51 | 52 | // basic text label & text wrapper 53 | > div 54 | line-height: $radio-font-size 55 | display: inline-block 56 | 57 | // Native input control and checked events 58 | // Outer ring of custom radio 59 | &__fake 60 | position: relative 61 | display: block 62 | float: left 63 | clear: left 64 | margin: $radio-outer-size - $radio-inner-size 65 | +size($radio-outer-size) 66 | border: 2px solid $radio-inactive-color 67 | border-radius: 50% 68 | background-color: $radio-inner-color 69 | transform: scale(0.75) 70 | 71 | // Inner ring of custom radio 72 | > span 73 | +size($radio-inner-size) 74 | transform: scale(0) 75 | display: block 76 | margin: 2px 77 | border-radius: 50% 78 | background-color: $radio-active-color 79 | opacity: 0 80 | -------------------------------------------------------------------------------- /src/styles/tags/side.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Color Nav 3 | */ 4 | 5 | $side-height: 2rem // 32 6 | $side-width: 4rem // 64 7 | 8 | #side 9 | @extend %dflex 10 | @extend %column 11 | @extend %scroller 12 | width: $side-width 13 | 14 | a 15 | @extend %flex 16 | @extend %scroll-item 17 | min-height: $side-height 18 | 19 | // width: 100% 20 | // flex-direction: row 21 | // padding: 4px 0 4px 4px 22 | // height: 4rem 23 | 24 | // a 25 | // margin-bottom: 0 26 | // margin-right: 4px 27 | // 28 | -------------------------------------------------------------------------------- /src/styles/tags/toast.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Toast / "Copied!" 3 | */ 4 | 5 | $toast-movement: 1.75 * $top-height 6 | 7 | #toast 8 | position: fixed 9 | background-color: $white 10 | box-shadow: 0 1px 3px rgba($black, 0.12), 0 1px 2px rgba($black, 0.24) 11 | transition: transform $base-timing $base-curve 0.2s 12 | will-change: transform 13 | border-radius: $radius/2 14 | padding: 0.75em 1.5em 15 | font-weight: 700 16 | top: -1em 17 | +centerX() 18 | z-index: 1 19 | &.open 20 | transform: translate(-50%, $toast-movement) 21 | -------------------------------------------------------------------------------- /src/styles/tags/top.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Title Header 3 | */ 4 | 5 | $top-padding: 0.75em 6 | 7 | #top 8 | position: fixed 9 | background: $white 10 | height: $top-height 11 | line-height: $top-height 12 | text-transform: capitalize 13 | border-bottom: 1px solid md-color('grey', '300') 14 | text-align: center 15 | z-index: 5 16 | right: 0 17 | left: 0 18 | top: 0 19 | 20 | h1 21 | font-size: 1.125em 22 | @media screen and (max-width: 321px) 23 | font-size: 1em 24 | 25 | #modes 26 | position: absolute 27 | text-transform: uppercase 28 | right: $top-padding 29 | font-weight: 600 30 | cursor: pointer 31 | padding: 0 1em 32 | font-size: 75% 33 | opacity: 0.65 34 | top: 0 35 | 36 | #logo 37 | position: absolute 38 | width: $top-height 39 | background: transparent url(/img/colors.svg) no-repeat 40 | left: $top-padding 41 | cursor: pointer 42 | height: 90% 43 | top: 5% 44 | -------------------------------------------------------------------------------- /src/unused/app.js: -------------------------------------------------------------------------------- 1 | import { h, render } from 'preact'; 2 | import { isOk, onSuccess, onError } from './sw'; 3 | import { on, modes, nav, id, loc, win, doc } from './shared'; 4 | import { names } from './schemes/md'; 5 | import Toast from './tag/toast'; 6 | import Color from './tag/color'; 7 | import Side from './tag/side'; 8 | import Top from './tag/top'; 9 | 10 | const read = () => loc.hash.split('/').pop(); 11 | 12 | const App = ({ color, format }) => ( 13 |
14 | 18 | 19 |
20 | 21 | 22 |
23 | 24 | 25 |
26 | ); 27 | 28 | let mode, idx = 0; 29 | function toggleMode() { 30 | idx++; 31 | (idx >= modes.length) && (idx = 0); 32 | draw({mode: modes[idx]}); 33 | } 34 | 35 | function openSchemes(e) { 36 | e.preventDefault(); 37 | console.log('open schemes modal'); 38 | } 39 | 40 | let elem, clor, name; 41 | function draw(obj) { 42 | name = obj.name || name; 43 | mode = obj.mode || modes[idx]; 44 | elem = render(, doc.body, elem); 45 | clor = clor || id('color'); 46 | } 47 | 48 | let rID, pos; 49 | function scrollUp() { 50 | pos = clor.scrollTop; 51 | if (pos <= 0) { 52 | pos = 0; 53 | cancelAnimationFrame(rID); 54 | } else { 55 | clor.scrollTop -= Math.min(60, pos * 0.28125); 56 | rID = requestAnimationFrame(scrollUp); 57 | } 58 | } 59 | 60 | function handler() { 61 | const hash = read(); 62 | const name = names.indexOf(hash) > -1 ? hash : names[0]; 63 | draw({name}); 64 | scrollUp(); 65 | ga && ga('send', 'pageview', name); 66 | } 67 | 68 | // init && redraw 69 | win.onhashchange = handler; 70 | on('DOMContentLoaded', handler); 71 | 72 | // cache all assets if browser supports serviceworker 73 | if (isOk && process.env.NODE_ENV === 'production') { 74 | nav.serviceWorker.register('/service-worker.js').then(onSuccess).catch(onError); 75 | } 76 | -------------------------------------------------------------------------------- /src/unused/burst.js: -------------------------------------------------------------------------------- 1 | /* global: mojs */ 2 | import tweens from './tweens'; 3 | import { burst, win } from './shared'; 4 | 5 | let timeline; 6 | 7 | function init () { 8 | const anims = tweens(); 9 | timeline = new mojs.Timeline(); 10 | // add each to timeline 11 | for (var k = 0; k < anims.length; k++) { 12 | timeline.add(anims[k]); 13 | } 14 | console.log(anims); 15 | console.log(timeline); 16 | // single play 17 | timeline.play(); 18 | } 19 | 20 | // read & set dimensions; do once per resize 21 | function getDimensions(node) { 22 | return node.getBoundingClientRect(); 23 | } 24 | 25 | // move to item position 26 | export function move(node) { 27 | const dims = getDimensions(node); 28 | 29 | for (const k in dims) { 30 | burst.style[k] = `${dims[k]}px`; 31 | } 32 | } 33 | 34 | export function animate(node) { 35 | if (!win.mojs) return; 36 | burst.style.zIndex = 9; 37 | return timeline ? timeline.replay() : init(); 38 | } 39 | -------------------------------------------------------------------------------- /src/unused/shared.js: -------------------------------------------------------------------------------- 1 | export const win = window; 2 | export const doc = document; 3 | export const loc = win.location; 4 | export const nav = win.navigator; 5 | 6 | export const bg = str => `background-color: #${str};`; 7 | 8 | export function id(str) { 9 | return doc.getElementById(str); 10 | } 11 | 12 | export const burst = id('burst'); 13 | 14 | export const modes = ['hex', 'rgb', 'hsl']; 15 | 16 | export function on(ev, handler) { 17 | doc.addEventListener(ev, handler); 18 | } 19 | 20 | export function off(ev, handler) { 21 | doc.removeEventListener(ev, handler); 22 | } 23 | 24 | export function emit(ev, detail) { 25 | const evt = detail ? new CustomEvent(ev, { detail }) : new Event(ev); 26 | doc.dispatchEvent(evt); 27 | } 28 | -------------------------------------------------------------------------------- /src/unused/sw.js: -------------------------------------------------------------------------------- 1 | import { win, loc, nav } from './shared'; 2 | 3 | // Check to make sure service workers are supported in the current browser, 4 | // and that the current page is accessed from a secure origin. 5 | // Using a service worker from an insecure origin will trigger JS console errors 6 | // @docs: http://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features 7 | 8 | 9 | const isLocalhost = Boolean(loc.hostname === 'localhost' || 10 | // [::1] is the IPv6 localhost address. 11 | loc.hostname === '[::1]' || 12 | // 127.0.0.1/8 is considered localhost for IPv4. 13 | loc.hostname.match( 14 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 15 | ) 16 | ); 17 | 18 | // If serviceWorkers are supported & should be initialized here 19 | export const isOk = ('serviceWorker' in nav && (loc.protocol === 'https:' || isLocalhost)); 20 | 21 | // If successefully registered, continue with caching 22 | export function onSuccess(registration) { 23 | // Check to see if there's an updated version of service-worker.js with new files to cache: 24 | // @docs: https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-registration-update-method 25 | if (typeof registration.update === 'function') { 26 | registration.update(); 27 | } 28 | 29 | // updatefound is fired if 'service-worker.js' changes. 30 | registration.onupdatefound = function () { 31 | // updatefound is also fired the very first time the SW is installed, 32 | // and there's no need to prompt for a reload at that point. 33 | // So check here to see if the page is already controlled, 34 | // i.e. whether there's an existing service worker. 35 | if (nav.serviceWorker.controller) { 36 | // The updatefound event implies that registration.installing is set: 37 | // @docs: https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event 38 | const installing = registration.installing; 39 | 40 | installing.onstatechange = function () { 41 | switch (installing.state) { 42 | case 'installed': 43 | // At this point, the old content will have been purged and the 44 | // fresh content will have been added to the cache. 45 | // It's the perfect time to display a 46 | // "New content is available; please refresh." 47 | // message in the page's interface. 48 | break; 49 | 50 | case 'redundant': 51 | throw new Error('The installing service worker became redundant.'); 52 | 53 | default: 54 | // Ignore 55 | } 56 | }; 57 | } 58 | }; 59 | } 60 | 61 | // Something went wrong! 62 | export function onError(e) { 63 | console.error('Error during service worker registration:', e); 64 | } 65 | -------------------------------------------------------------------------------- /src/unused/tag/color.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { all } from '../schemes/md'; 3 | import Shade from './shade'; 4 | 5 | export default ({ color, format }) => { 6 | const obj = all[color]; 7 | 8 | return ( 9 |
    10 | 11 | { 12 | Object.keys(obj).map(k => ( 13 | 14 | )) 15 | } 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/unused/tag/copy-animation.js: -------------------------------------------------------------------------------- 1 | /* global: mojs */ 2 | import { h, Component } from 'preact'; 3 | import {addModules, tuneModules} from './mojs-copy-animation'; 4 | 5 | class CopyAnimation extends Component { 6 | shouldComponentUpdate(nextProps) { 7 | const {x, y} = nextProps; 8 | tuneModules(this._modules, x, y); 9 | 10 | return false; 11 | } 12 | 13 | componentDidMount() { 14 | this._modules = addModules(this.props.timeline, this._el); 15 | } 16 | 17 | render() { 18 | const style = { 19 | position: 'absolute', 20 | left: 0, 21 | top: 0, 22 | zIndex: 2, 23 | width: '100%', 24 | height: '100%', 25 | overflow: 'hidden', 26 | cursor: 'pointer' 27 | }; 28 | 29 | return ( 30 |
this._el = el } style={style} /> 31 | ); 32 | } 33 | } 34 | 35 | export default CopyAnimation; 36 | -------------------------------------------------------------------------------- /src/unused/tag/mojs-copy-animation.js: -------------------------------------------------------------------------------- 1 | /* global: mojs */ 2 | 3 | // add custom `copy` shape 4 | class Copy extends mojs.CustomShape { 5 | getShape() { 6 | return ''; 7 | } 8 | } 9 | mojs.addShape('copy', Copy); 10 | 11 | export function addModules(timeline, el) { 12 | const ShapeStagger = mojs.stagger( mojs.Shape ); 13 | 14 | const ripplesOut = new ShapeStagger({ 15 | parent: el, 16 | left: 0, top: 0, 17 | quantifier: 3, 18 | fill: 'none', 19 | stroke: ['white', '#999', '#f1f1f1'], 20 | opacity: { [.2] : 0 }, 21 | scale: { 0: 5 }, 22 | delay: 'stagger(150, 75)', 23 | duration: 1000 24 | }); 25 | 26 | const copyShape = new mojs.Shape({ 27 | shape: 'copy', 28 | parent: el, 29 | left: 0, top: 0, 30 | radius: 30, 31 | scale: { 0: 1 }, 32 | opacity: { 1.5: -.1, easing: 'quad.inout' }, 33 | fill: 'white', 34 | duration: 500, 35 | delay: 350, 36 | }); 37 | 38 | const ripplesIn = new ShapeStagger({ 39 | parent: el, 40 | left: 0, top: 0, 41 | quantifier: 3, 42 | delay: 'stagger(100)', 43 | fill: ['hotpink', 'cyan', 'yellow'], 44 | // fill: ['white', '#999', '#f1f1f1'], 45 | opacity: { [.75] : 0 }, 46 | scale: { 'stagger(20, -5)': 0 } 47 | }); 48 | 49 | timeline.add(ripplesOut, ripplesIn, copyShape); 50 | return [ripplesOut, ripplesIn, copyShape]; 51 | } 52 | 53 | export function tuneModules(modules, x, y) { 54 | for (let i = 0; i < modules.length; i++) { 55 | modules[i].tune({ x, y }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/unused/tag/shade.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { animate, move } from '../burst'; 3 | import { bgc, doc, emit } from '../shared'; 4 | import { hex2rgb, rgb2hsl, isDark } from '../convert'; 5 | import CopyAnimation from './copy-animation'; 6 | 7 | function copy(text) { 8 | const el = doc.createElement('input'); 9 | el.value = text; 10 | doc.body.appendChild(el); 11 | el.select(); 12 | doc.execCommand('copy'); 13 | el.remove(); 14 | } 15 | 16 | function handle(e) { 17 | const elm = e.target; 18 | copy(elm.lastChild.textContent); 19 | // show toast 20 | emit('copied'); 21 | 22 | this.setState({ x: e.layerX, y: e.layerY }); 23 | // defer 24 | setTimeout(()=> { this._timeline.replay(); }, 10 ); 25 | } 26 | 27 | class Shade extends Component { 28 | constructor() { 29 | super(); 30 | this._timeline = new mojs.Timeline; 31 | this.state = { x: 0, y: 0 }; 32 | } 33 | 34 | render() { 35 | const {idx, hex, format} = this.props; 36 | 37 | const rgb = hex2rgb(hex); 38 | let style = bgc(hex); 39 | isDark(rgb) && (style += 'color:white;'); 40 | 41 | let text; 42 | switch (format) { 43 | case 'hsl': 44 | const hsl = rgb2hsl(rgb); 45 | text = `hsl(${ hsl[0] }, ${ hsl[1] }%, ${ hsl[2] }%)`; 46 | break; 47 | case 'rgb': 48 | text = `rgb(${ rgb.join(', ') })`; 49 | break; 50 | default: 51 | text = `#${ hex }`; 52 | break; 53 | } 54 | 55 | const {x, y} = this.state; 56 | 57 | return ( 58 |
  • 59 | 60 | 61 | { text } 62 |
  • 63 | ); 64 | } 65 | } 66 | 67 | export default Shade; 68 | -------------------------------------------------------------------------------- /src/unused/tag/side.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { all, names } from '../schemes/md'; 3 | import { bgc } from '../shared'; 4 | 5 | export default ({ color }) => 6 | h('nav', {id: 'side'}, [ 7 | names.map(name => h('a', { 8 | href: `#/${name}`, 9 | className: (name === color) ? 'active' : '', 10 | style: bgc( all[name]['500'] ) 11 | })) 12 | ]); 13 | -------------------------------------------------------------------------------- /src/unused/tag/toast.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { on } from '../shared'; 3 | 4 | export default class Toast extends Component { 5 | constructor() { 6 | this.state = { open:false }; 7 | 8 | this.hideToast = () => this.setState({ open:false }); 9 | 10 | this.showToast = () => { 11 | this.setState({ open:true }); 12 | setTimeout(this.hideToast, 3000); 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | on('copied', this.showToast); 18 | } 19 | 20 | shouldComponentUpdate(_, state) { 21 | return state.open !== this.state.open; 22 | } 23 | 24 | render(_, { open }) { 25 | return ( 26 |
    Copied!
    27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/unused/tag/top.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | 3 | export default ({ color, format, onMode, onLogo }) => ( 4 |
    5 |
    95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/views/radio.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | 3 | export default props => ( 4 | 12 | ) 13 | -------------------------------------------------------------------------------- /src/views/shade.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | // import { animate, move } from '../burst'; 3 | import { hex2rgb, rgb2hsl, isDark } from '../colors/convert'; 4 | import { bg, doc, emit } from './shared'; 5 | // import CopyAnimation from './copy-animation'; 6 | 7 | function copy(text) { 8 | const el = doc.createElement('input'); 9 | el.value = text; 10 | doc.body.appendChild(el); 11 | el.select(); 12 | doc.execCommand('copy'); 13 | el.remove(); 14 | } 15 | 16 | function handle(e) { 17 | const elm = e.target; 18 | copy(elm.lastChild.textContent); 19 | // show toast 20 | emit('copied'); 21 | 22 | this.setState({ x: e.layerX, y: e.layerY }); 23 | // defer 24 | setTimeout(()=> { this._timeline.replay(); }, 10 ); 25 | } 26 | 27 | class Shade extends Component { 28 | constructor() { 29 | super(); 30 | this._timeline = new mojs.Timeline; 31 | this.state = { x: 0, y: 0 }; 32 | } 33 | 34 | render() { 35 | const {idx, hex, format} = this.props; 36 | 37 | const rgb = hex2rgb(hex); 38 | let style = bg(hex); 39 | isDark(rgb) && (style += 'color:white;'); 40 | 41 | let text; 42 | switch (format) { 43 | case 'hsl': 44 | const hsl = rgb2hsl(rgb); 45 | text = `hsl(${ hsl[0] }, ${ hsl[1] }%, ${ hsl[2] }%)`; 46 | break; 47 | case 'rgb': 48 | text = `rgb(${ rgb.join(', ') })`; 49 | break; 50 | default: 51 | text = `#${ hex }`; 52 | break; 53 | } 54 | 55 | const {x, y} = this.state; 56 | 57 | return ( 58 |
  • 59 | 60 | { text } 61 |
  • 62 | ); 63 | } 64 | // 65 | } 66 | 67 | export default Shade; 68 | -------------------------------------------------------------------------------- /src/views/shade2.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { bg, doc, emit } from './shared'; 3 | import { hex2rgb, rgb2hsl, isDark } from '../colors/convert'; 4 | 5 | function copy(text) { 6 | const el = doc.createElement('input'); 7 | el.value = text; 8 | doc.body.appendChild(el); 9 | el.select(); 10 | doc.execCommand('copy'); 11 | el.remove(); 12 | } 13 | 14 | function handle(e) { 15 | const color = e.target.lastChild.textContent; 16 | emit('copied', { color, x:e.x, y:e.y }); 17 | copy(color); 18 | } 19 | 20 | export default ({ idx, hex, format }) => { 21 | let style = bg(hex); 22 | const rgb = hex2rgb(hex); 23 | isDark(rgb) && (style += 'color:white;'); 24 | 25 | let text; 26 | switch (format) { 27 | case 'hsl': 28 | const hsl = rgb2hsl(rgb); 29 | text = `hsl(${ hsl[0] }, ${ hsl[1] }%, ${ hsl[2] }%)`; 30 | break; 31 | case 'rgb': 32 | text = `rgb(${ rgb.join(', ') })`; 33 | break; 34 | default: 35 | text = `#${ hex }`; 36 | break; 37 | } 38 | 39 | return ( 40 |
  • 41 | 42 | { text } 43 |
  • 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/views/shared.js: -------------------------------------------------------------------------------- 1 | export const doc = document; 2 | 3 | export const bg = str => `background-color: #${str};`; 4 | 5 | export function on(ev, handler) { 6 | doc.addEventListener(ev, handler); 7 | } 8 | 9 | export function off(ev, handler) { 10 | doc.removeEventListener(ev, handler); 11 | } 12 | 13 | export function emit(ev, detail) { 14 | const evt = detail ? new CustomEvent(ev, { detail }) : new Event(ev); 15 | doc.dispatchEvent(evt); 16 | } 17 | -------------------------------------------------------------------------------- /src/views/switcher.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { on } from './shared'; 3 | import Radio from './radio'; 4 | 5 | const options = [ 6 | {label: 'Material Design', value: 'material'}, 7 | {label: 'Open Color', value: 'open'} 8 | ]; 9 | 10 | export default class Switcher extends Component { 11 | constructor(props) { 12 | this.state = { open:false }; 13 | 14 | this.show = () => this.setState({ open:true }); 15 | this.hide = cb => this.setState({ open:false }, cb && cb.call && cb); 16 | 17 | this.onChange = e => { 18 | const cb = () => props.onSelect(e.target.value); 19 | setTimeout(() => this.hide(cb), 300 * 1.1); // $base-timing 20 | }; 21 | } 22 | 23 | componentDidMount() { 24 | on('popup', this.show); 25 | } 26 | 27 | shouldComponentUpdate(props, state) { 28 | return state.open !== this.state.open || props.selected !== this.props.selected; 29 | } 30 | 31 | render({ selected }, { open }) { 32 | let cls = 'modal'; 33 | open && (cls += ' open'); 34 | return ( 35 |
    36 |
    37 | 38 |
    39 |

    Select Palette

    40 | { options.map(obj => 41 | 44 | ) } 45 |
    46 |
    47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/views/toast.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { on } from './shared'; 3 | 4 | export default class Toast extends Component { 5 | constructor() { 6 | this.state = { open:false }; 7 | 8 | this.hide = () => this.setState({ open:false }); 9 | 10 | this.show = () => { 11 | this.setState({ open:true }); 12 | setTimeout(this.hide, 3000); 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | on('toast', this.show); 18 | } 19 | 20 | shouldComponentUpdate(_, state) { 21 | return state.open !== this.state.open; 22 | } 23 | 24 | render(_, { open }) { 25 | return ( 26 |
    Copied!
    27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/views/top.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | 3 | export default ({ color, format, onMode, onLogo }) => ( 4 |
    5 |