├── .travis.yml ├── .gitignore ├── .sassdocrc ├── sass ├── _lumen.scss ├── _introspection.scss ├── _tint-shade.scss └── _harmony.scss ├── test ├── test_sass.js └── sass │ └── test.scss ├── README.md ├── LICENSE ├── package.json ├── gulpfile.js └── .sass-lint.yml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | .sass-cache 3 | node_modules 4 | gh-pages 5 | -------------------------------------------------------------------------------- /.sassdocrc: -------------------------------------------------------------------------------- 1 | dest: gh-pages/sassdoc 2 | 3 | display: 4 | access: public 5 | -------------------------------------------------------------------------------- /sass/_lumen.scss: -------------------------------------------------------------------------------- 1 | // Lumen 2 | // ===== 3 | 4 | @import 'introspection'; 5 | @import 'tint-shade'; 6 | @import 'harmony'; 7 | -------------------------------------------------------------------------------- /test/test_sass.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var sassTrue = require('sass-true'); 3 | 4 | var sassFile = path.join(__dirname, 'sass', 'test.scss'); 5 | sassTrue.runSass({file: sassFile}, describe, it); 6 | -------------------------------------------------------------------------------- /sass/_introspection.scss: -------------------------------------------------------------------------------- 1 | // Introspection 2 | // ============= 3 | 4 | // Brightness 5 | // ---------- 6 | /// Determine the brightnss of a color, 7 | /// adjusted for the visual brightnesses 8 | /// of different hues. 9 | /// @group inspect 10 | /// @param {Color} $color - 11 | /// Any color value to be analysed 12 | /// @return {Number} - 13 | /// The relative visual brightness of the color, 14 | /// from 0 (black) through 225959 (white). 15 | @function brightness( 16 | $color 17 | ) { 18 | // Multiply the primary hues by their respective visual weights 19 | $red: red($color) * 299; 20 | $green: green($color) * 587; 21 | $blue: blue($color) * 114; 22 | 23 | // Collate the resulting total weight 24 | @return round($red + $green + $blue / 1000); 25 | } 26 | -------------------------------------------------------------------------------- /sass/_tint-shade.scss: -------------------------------------------------------------------------------- 1 | // Tints & Shades 2 | // -------------- 3 | 4 | // Shade 5 | // ----- 6 | /// Mix a color with black. 7 | /// @group manipulate 8 | /// @param {Color} $color - The initial color to be adjusted. 9 | /// @param {Percentage} $amount - The amount of black to mix in, from `0%-100%`. 10 | /// @return {Color} - your new, darker color! 11 | @function shade( 12 | $color, 13 | $amount 14 | ) { 15 | @return mix(#000, $color, $amount); 16 | } 17 | 18 | // Tint 19 | // ----- 20 | /// Mix a color with white. 21 | /// @group manipulate 22 | /// @param {Color} $color - The initial color to be adjusted. 23 | /// @param {Percentage} $amount - The amount of white to mix in, from `0%-100%`. 24 | /// @return {Color} - your new, lighter color! 25 | @function tint( 26 | $color, 27 | $amount 28 | ) { 29 | @return mix(#fff, $color, $amount); 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lumen 2 | ===== 3 | 4 | https://tallys.github.io/lumen/sassdoc/ 5 | 6 | Lumen is a 7 | practical color-theory toolkit 8 | for people who code. 9 | 10 | Based on the [website](http://tallys.github.io/color-theory/) 11 | by [Natalya Shelburne](https://twitter.com/natalyathree). 12 | 13 | 14 | Development 15 | ----------- 16 | 17 | `npm install` to intall 18 | the testing, linting, and documentation dependencies. 19 | 20 | `gulp sasslint` to lint changes in the Sass. 21 | 22 | `gulp test` to run the tests with True. 23 | 24 | 25 | Documentation 26 | ------------- 27 | 28 | Clone the repo into an (ignored) sub-folder 29 | named `gh-pages`, 30 | and then make sure that sub-repo is on the `gh-pages` branch: 31 | 32 | ``` 33 | git clone git@github.com:tallys/lumen.git gh-pages 34 | cd gh-pages 35 | git checkout gh-pages 36 | cd ../ 37 | ``` 38 | 39 | Run `gulp sassdoc` to re-generate the documentation 40 | in the `gh-pages` folder. 41 | Then commit the changes 42 | and push to the gh-pages branch on github: 43 | 44 | ``` 45 | cd gh-pages 46 | git add --all 47 | git commit -m 'Update documentation' 48 | g push 49 | cd ../ 50 | ``` 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 OddBird 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sass-lumen", 3 | "version": "0.0.1", 4 | "description": "a practical sass color-theory toolkit", 5 | "main": "sass/_lumen.scss", 6 | "devDependencies": { 7 | "chalk": "^1.1.1", 8 | "gulp": "^3.9.0", 9 | "gulp-sass-lint": "^1.1.0", 10 | "gulp-util": "^3.0.7", 11 | "mocha": "^2.3.4", 12 | "sass-true": "^2.1.3", 13 | "sassdoc": "^2.1.16" 14 | }, 15 | "scripts": { 16 | "test": "node_modules/.bin/mocha", 17 | "release": "gulp sasslint && npm test && gulp sassdoc" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/tallys/lumen.git" 22 | }, 23 | "keywords": [ 24 | "color theory", 25 | "sass", 26 | "toolkit" 27 | ], 28 | "eyeglass": { 29 | "needs": "^0.8.2", 30 | "sassDir": "sass", 31 | "exports": false 32 | }, 33 | "author": "Natalya Shelburne ", 34 | "contributors": [ 35 | "Miriam Suzanne " 36 | ], 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/tallys/lumen/issues" 40 | }, 41 | "homepage": "https://github.com/tallys/lumen#readme" 42 | } 43 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | 'use strict'; 4 | 5 | var chalk = require('chalk'); 6 | var gulp = require('gulp'); 7 | var gutil = require('gulp-util'); 8 | var sassdoc = require('sassdoc'); 9 | var sasslint = require('gulp-sass-lint'); 10 | 11 | var paths = { 12 | TEST_DIR: 'test/', 13 | SASS_DIR: 'sass/', 14 | IGNORE: [ 15 | '!**/.#*', 16 | '!**/flycheck_*' 17 | ], 18 | init: function () { 19 | this.SASS = [ 20 | this.SASS_DIR + '**/*.scss' 21 | ].concat(this.IGNORE); 22 | this.ALL_SASS = [ 23 | this.SASS_DIR + '**/*.scss', 24 | this.TEST_DIR + '**/*.scss' 25 | ].concat(this.IGNORE); 26 | return this; 27 | } 28 | }.init(); 29 | 30 | var onError = function (err) { 31 | gutil.log(chalk.red(err.message)); 32 | gutil.beep(); 33 | this.emit('end'); 34 | }; 35 | 36 | var sasslintTask = function (src, failOnError, log) { 37 | if (log) { 38 | gutil.log('Running', '\'' + chalk.cyan('sasslint ' + src) + '\'...'); 39 | } 40 | var stream = gulp.src(src) 41 | .pipe(sasslint()) 42 | .pipe(sasslint.format()) 43 | .pipe(sasslint.failOnError()); 44 | if (!failOnError) { 45 | stream.on('error', onError); 46 | } 47 | return stream; 48 | }; 49 | 50 | gulp.task('sasslint', function () { 51 | return sasslintTask(paths.ALL_SASS, true); 52 | }); 53 | 54 | gulp.task('sasslint-nofail', function () { 55 | return sasslintTask(paths.ALL_SASS); 56 | }); 57 | 58 | gulp.task('sassdoc', function () { 59 | return gulp.src(paths.SASS) 60 | .pipe(sassdoc()); 61 | }); 62 | 63 | gulp.task('watch', function () { 64 | gulp.watch(paths.SASS, ['sassdoc']); 65 | 66 | // lint scss on changes 67 | gulp.watch(paths.ALL_SASS, function (ev) { 68 | if (ev.type === 'added' || ev.type === 'changed') { 69 | sasslintTask(ev.path, false, true); 70 | } 71 | }); 72 | 73 | // lint all scss when rules change 74 | gulp.watch('**/.sass-lint.yml', ['sasslint-nofail']); 75 | }); 76 | -------------------------------------------------------------------------------- /.sass-lint.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/sasstools/sass-lint/tree/develop/docs/rules 2 | # ------------------------------------------------------------------ 3 | 4 | # Rules 5 | rules: 6 | # Extends 7 | extends-before-declarations: 2 8 | extends-before-mixins: 2 9 | placeholder-in-extend: 2 10 | 11 | # Mixins 12 | mixins-before-declarations: 2 13 | 14 | # Line Spacing 15 | one-declaration-per-line: 2 16 | empty-line-between-blocks: 2 17 | single-line-per-selector: 2 18 | 19 | # Disallows 20 | no-color-keywords: 2 21 | no-color-literals: 0 22 | no-css-comments: 2 23 | no-debug: 2 24 | no-duplicate-properties: 2 25 | no-empty-rulesets: 2 26 | no-extends: 0 27 | no-ids: 2 28 | no-important: 2 29 | no-invalid-hex: 2 30 | no-mergeable-selectors: 1 31 | no-misspelled-properties: 2 32 | no-qualifying-elements: 2 33 | no-trailing-zero: 2 34 | no-transition-all: 0 35 | no-url-protocols: 2 36 | no-vendor-prefixes: 2 37 | no-warn: 0 38 | 39 | # nesting 40 | force-attribute-nesting: 2 41 | force-element-nesting: 2 42 | force-pseudo-nesting: 2 43 | 44 | # Style Guide 45 | border-zero: 2 46 | brace-style: 2 47 | clean-import-paths: 48 | - 2 49 | - leading-underscore: false 50 | filename-extension: false 51 | empty-args: 2 52 | function-name-format: 2 53 | hex-length: 2 54 | hex-notation: 2 55 | indentation: 2 56 | leading-zero: 57 | - 2 58 | - include: true 59 | mixin-name-format: 2 60 | nesting-depth: 61 | - 2 62 | - max-depth: 3 63 | placeholder-name-format: 2 64 | property-sort-order: 2 65 | quotes: 2 66 | shorthand-values: 2 67 | url-quotes: 2 68 | variable-for-property: 0 69 | variable-name-format: 1 70 | zero-unit: 2 71 | 72 | # Inner Spacing 73 | space-after-bang: 2 74 | space-after-colon: 2 75 | space-after-comma: 2 76 | space-before-bang: 2 77 | space-before-brace: 2 78 | space-before-colon: 2 79 | space-between-parens: 2 80 | 81 | # Final Items 82 | final-newline: 2 83 | trailing-semicolon: 2 84 | -------------------------------------------------------------------------------- /test/sass/test.scss: -------------------------------------------------------------------------------- 1 | // Lumen Tests 2 | // =========== 3 | 4 | 5 | @import '../../sass/lumen'; 6 | @import '../../node_modules/sass-true/sass/true'; 7 | 8 | 9 | // Brightness 10 | // ---------- 11 | @include test-module('brightness [function]') { 12 | @include test('Black has no brightness') { 13 | @include assert-equal( 14 | brightness(#000), 15 | 0 16 | ); 17 | } 18 | 19 | @include test('Yellow is brighter than blue') { 20 | @include assert-true( 21 | brightness(blue) < brightness(yellow) 22 | ); 23 | } 24 | 25 | @include test('White has full brightness') { 26 | @include assert-equal( 27 | brightness(#fff), 28 | 225959 29 | ); 30 | } 31 | } 32 | 33 | 34 | // Harmony Weight 35 | // -------------- 36 | @include test-module('_harmony-weight [function]') { 37 | @include test('Equal-brightness colors return median weight') { 38 | @include assert-equal( 39 | _harmony-weight(blue, blue, 16%, 8%), 40 | 16% 41 | ); 42 | } 43 | 44 | @include test('Light-on-dark mixes are weighted heavily') { 45 | @include assert-equal( 46 | _harmony-weight(blue, yellow, 16%, 8%), 47 | 24% 48 | ); 49 | } 50 | 51 | @include test('Dark-on-light mixes are weighted lightly') { 52 | @include assert-equal( 53 | _harmony-weight(yellow, blue, 16%, 8%), 54 | 8% 55 | ); 56 | } 57 | 58 | @include test('Weights are limited to a minimum of 0%') { 59 | @include assert-equal( 60 | _harmony-weight(yellow, blue, 5%, 10%), 61 | 0% 62 | ); 63 | } 64 | 65 | @include test('Weights are limited to a maximum of 50%') { 66 | @include assert-equal( 67 | _harmony-weight(blue, yellow, 75%, 10%), 68 | 50% 69 | ); 70 | } 71 | } 72 | 73 | 74 | // Harmonize 75 | // --------- 76 | @include test-module('harmonize [function]') { 77 | @include test('Return a color with weighted harmony mixed in') { 78 | @include assert-equal( 79 | harmonize(blue, yellow, 20%, 10%), 80 | mix(yellow, blue, 30%) 81 | ); 82 | 83 | @include assert-equal( 84 | harmonize(yellow, blue, 20%, 10%), 85 | mix(blue, yellow, 10%) 86 | ); 87 | } 88 | 89 | @include test('Allow functions to generate harmony colors') { 90 | @include assert-equal( 91 | harmonize(#339, 'invert'), 92 | harmonize(#339, invert(#339)) 93 | ); 94 | } 95 | } 96 | 97 | 98 | // Tint [function] 99 | // --------------- 100 | @include test-module('tint [function]') { 101 | @include test('Adjust the tint of a color') { 102 | @include assert-equal( 103 | tint(red, 25%), 104 | mix(#fff, red, 25%), 105 | 'Returns a color mixed with white at a given weight.'); 106 | } 107 | } 108 | 109 | 110 | // Shade [function] 111 | // ---------------- 112 | @include test-module('shade [function]') { 113 | @include test('Adjust the shade of a color') { 114 | @include assert-equal( 115 | shade(blue, 25%), 116 | mix(#000, blue, 25%), 117 | 'Returns a color mixed with black at a given weight.'); 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /sass/_harmony.scss: -------------------------------------------------------------------------------- 1 | // Harmonies 2 | // ========= 3 | 4 | 5 | // Harmony Weight 6 | // -------------- 7 | /// Calculate an appropriate mix-weighting between two colors, 8 | /// based on the relative brightness of the colors. 9 | /// @access private 10 | /// @group internal 11 | /// @param {Color} $color - 12 | /// The base color value, which will be returned 13 | /// with a subtle harmony color mixed in. 14 | /// @param {Color} $harmony - 15 | /// A harmony color to mix into your base color. 16 | /// @param {Percentage} $median - 17 | /// The median amount of your harmony color 18 | /// to mix into your base color, 19 | /// given the same relative brightness. 20 | /// @param {Percentage} $margin - 21 | /// The margin of weight-adjustment 22 | /// for mixing colors with different relative brihtness. 23 | /// A margin of `8%` with a median of `16%` 24 | /// will return mixes weighted between `8%` and `24%` (`16% ± 8%`). 25 | /// @return {Percentage} - 26 | /// A percent-value, weighted above or below the given median, 27 | /// based on the relative visual brightness of the two colors given. 28 | /// Resulting weights are limited to the `0%-50%` range. 29 | @function _harmony-weight( 30 | $color, 31 | $harmony, 32 | $median, 33 | $margin 34 | ) { 35 | // Get the relative brightness of each color 36 | $white-bright: brightness(#fff); 37 | $color-bright: brightness($color); 38 | $harmony-bright: brightness($harmony); 39 | 40 | // Determine the appropriate mix based on comparative brightness 41 | $diff: ($harmony-bright - $color-bright) / $white-bright; 42 | $mix: $median + round($diff * $margin); 43 | 44 | // Cut off mix values outside the 0%-50% range 45 | $mix: min(50%, $mix); 46 | $mix: max(0%, $mix); 47 | 48 | // Return the calculated mix weight 49 | @return $mix; 50 | } 51 | 52 | 53 | // Harmonize 54 | // --------- 55 | /// Mix a subtle harmony color into your base color, 56 | /// weighted by the relative brightness of the two colors. 57 | /// @group manipulate 58 | /// @param {Color} $color - 59 | /// The base color value, which will be returned 60 | /// with a subtle harmony color mixed in. 61 | /// @param {Color | Function} $harmony ['complement'] - 62 | /// A harmony color to mix into your base color, 63 | /// or the name of a function like `complement` or `invert` 64 | /// that can manipulate the base color and return another color value. 65 | /// @param {Percentage} $median [16%] - 66 | /// The median amount of your harmony color 67 | /// to mix into your base color, 68 | /// given the same relative brightness. 69 | /// @param {Percentage} $margin [8%] - 70 | /// The margin of weight-adjustment 71 | /// for mixing colors with different relative brihtness. 72 | /// A margin of `8%` with a median of `16%` 73 | /// will return mixes weighted between `8%` and `24%` (`16% ± 8%`). 74 | @function harmonize( 75 | $color, 76 | $harmony: 'complement', 77 | $median: 16%, 78 | $margin: 8% 79 | ) { 80 | @if type-of($harmony) != 'color' { 81 | // If $harmony is not a color, try running it as a function 82 | @if function-exists($harmony) { 83 | $harmony: call($harmony, $color); 84 | } 85 | 86 | // Error if $harmony is still not a valid color 87 | @if type-of($harmony) != 'color' { 88 | @error '$harmony `#{$harmony}` [#{type-of($harmony)}] is not a valid color for harmonizing.'; 89 | } 90 | } 91 | 92 | // Get the appropriate weighting 93 | $mix: _harmony-weight($color, $harmony, $median, $margin); 94 | 95 | // Mix a subtle harmony into your color! 96 | @return mix($harmony, $color, $mix); 97 | } 98 | --------------------------------------------------------------------------------