├── .gitignore ├── .prettierrc ├── gorko.scss ├── package-lock.json ├── package.json ├── readme.md ├── src ├── _default-config.scss ├── functions │ ├── _functions.scss │ ├── _get-color.scss │ ├── _get-size.scss │ └── _get-utility-value.scss ├── generator │ ├── _generator.scss │ └── workers │ │ ├── _cycle.scss │ │ ├── _generate-css-vars.scss │ │ ├── _generate-css.scss │ │ ├── _get-config-value.scss │ │ ├── _get-namespace.scss │ │ ├── _process-collection.scss │ │ └── _process-vars.scss └── mixins │ ├── _apply-utility.scss │ ├── _generate-utility-classes.scss │ ├── _media-query.scss │ └── _mixins.scss └── test ├── _config.scss └── test.scss /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.* 3 | *.scssc 4 | *.log 5 | *.swp 6 | .DS_Store 7 | .sass-cache 8 | node_modules 9 | test.html 10 | tmp 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 90, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "bracketSpacing": false, 6 | "quoteProps": "consistent", 7 | "trailingComma": "none", 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /gorko.scss: -------------------------------------------------------------------------------- 1 | /// Import the default config as the fallback for if 2 | /// the user hasn't yet set a config for their project 3 | @import './src/default-config'; 4 | 5 | /// Set default feature flags 6 | $generate-css-vars: true !default; 7 | $generate-utility-classes: true !default; 8 | 9 | // Support old features 10 | @if (variable-exists(outputTokenCSS)) { 11 | @warn '$outputTokenCSS is deprecated. Please use $generate-utility-classes instead'; 12 | $generate-utility-classes: $outputTokenCSS; 13 | } 14 | 15 | @import './src/functions/functions'; 16 | @import './src/mixins/mixins'; 17 | @import './src/generator/generator'; 18 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gorko", 3 | "version": "0.8.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "gorko", 9 | "version": "0.8.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "sass": "^1.26.5" 13 | }, 14 | "devDependencies": { 15 | "prettier": "^2.0.5" 16 | } 17 | }, 18 | "node_modules/anymatch": { 19 | "version": "3.1.1", 20 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 21 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 22 | "dependencies": { 23 | "normalize-path": "^3.0.0", 24 | "picomatch": "^2.0.4" 25 | }, 26 | "engines": { 27 | "node": ">= 8" 28 | } 29 | }, 30 | "node_modules/binary-extensions": { 31 | "version": "2.2.0", 32 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 33 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 34 | "engines": { 35 | "node": ">=8" 36 | } 37 | }, 38 | "node_modules/braces": { 39 | "version": "3.0.2", 40 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 41 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 42 | "dependencies": { 43 | "fill-range": "^7.0.1" 44 | }, 45 | "engines": { 46 | "node": ">=8" 47 | } 48 | }, 49 | "node_modules/chokidar": { 50 | "version": "3.5.1", 51 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", 52 | "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", 53 | "dependencies": { 54 | "anymatch": "~3.1.1", 55 | "braces": "~3.0.2", 56 | "glob-parent": "~5.1.0", 57 | "is-binary-path": "~2.1.0", 58 | "is-glob": "~4.0.1", 59 | "normalize-path": "~3.0.0", 60 | "readdirp": "~3.5.0" 61 | }, 62 | "engines": { 63 | "node": ">= 8.10.0" 64 | }, 65 | "optionalDependencies": { 66 | "fsevents": "~2.3.1" 67 | } 68 | }, 69 | "node_modules/fill-range": { 70 | "version": "7.0.1", 71 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 72 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 73 | "dependencies": { 74 | "to-regex-range": "^5.0.1" 75 | }, 76 | "engines": { 77 | "node": ">=8" 78 | } 79 | }, 80 | "node_modules/fsevents": { 81 | "version": "2.3.2", 82 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 83 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 84 | "hasInstallScript": true, 85 | "optional": true, 86 | "os": [ 87 | "darwin" 88 | ], 89 | "engines": { 90 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 91 | } 92 | }, 93 | "node_modules/glob-parent": { 94 | "version": "5.1.2", 95 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 96 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 97 | "dependencies": { 98 | "is-glob": "^4.0.1" 99 | }, 100 | "engines": { 101 | "node": ">= 6" 102 | } 103 | }, 104 | "node_modules/is-binary-path": { 105 | "version": "2.1.0", 106 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 107 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 108 | "dependencies": { 109 | "binary-extensions": "^2.0.0" 110 | }, 111 | "engines": { 112 | "node": ">=8" 113 | } 114 | }, 115 | "node_modules/is-extglob": { 116 | "version": "2.1.1", 117 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 118 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 119 | "engines": { 120 | "node": ">=0.10.0" 121 | } 122 | }, 123 | "node_modules/is-glob": { 124 | "version": "4.0.1", 125 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 126 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 127 | "dependencies": { 128 | "is-extglob": "^2.1.1" 129 | }, 130 | "engines": { 131 | "node": ">=0.10.0" 132 | } 133 | }, 134 | "node_modules/is-number": { 135 | "version": "7.0.0", 136 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 137 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 138 | "engines": { 139 | "node": ">=0.12.0" 140 | } 141 | }, 142 | "node_modules/normalize-path": { 143 | "version": "3.0.0", 144 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 145 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 146 | "engines": { 147 | "node": ">=0.10.0" 148 | } 149 | }, 150 | "node_modules/picomatch": { 151 | "version": "2.2.2", 152 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", 153 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", 154 | "engines": { 155 | "node": ">=8.6" 156 | }, 157 | "funding": { 158 | "url": "https://github.com/sponsors/jonschlinkert" 159 | } 160 | }, 161 | "node_modules/prettier": { 162 | "version": "2.0.5", 163 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", 164 | "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", 165 | "dev": true, 166 | "bin": { 167 | "prettier": "bin-prettier.js" 168 | }, 169 | "engines": { 170 | "node": ">=10.13.0" 171 | } 172 | }, 173 | "node_modules/readdirp": { 174 | "version": "3.5.0", 175 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", 176 | "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", 177 | "dependencies": { 178 | "picomatch": "^2.2.1" 179 | }, 180 | "engines": { 181 | "node": ">=8.10.0" 182 | } 183 | }, 184 | "node_modules/sass": { 185 | "version": "1.32.8", 186 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz", 187 | "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==", 188 | "dependencies": { 189 | "chokidar": ">=2.0.0 <4.0.0" 190 | }, 191 | "bin": { 192 | "sass": "sass.js" 193 | }, 194 | "engines": { 195 | "node": ">=8.9.0" 196 | } 197 | }, 198 | "node_modules/to-regex-range": { 199 | "version": "5.0.1", 200 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 201 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 202 | "dependencies": { 203 | "is-number": "^7.0.0" 204 | }, 205 | "engines": { 206 | "node": ">=8.0" 207 | } 208 | } 209 | }, 210 | "dependencies": { 211 | "anymatch": { 212 | "version": "3.1.1", 213 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 214 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 215 | "requires": { 216 | "normalize-path": "^3.0.0", 217 | "picomatch": "^2.0.4" 218 | } 219 | }, 220 | "binary-extensions": { 221 | "version": "2.2.0", 222 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 223 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" 224 | }, 225 | "braces": { 226 | "version": "3.0.2", 227 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 228 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 229 | "requires": { 230 | "fill-range": "^7.0.1" 231 | } 232 | }, 233 | "chokidar": { 234 | "version": "3.5.1", 235 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", 236 | "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", 237 | "requires": { 238 | "anymatch": "~3.1.1", 239 | "braces": "~3.0.2", 240 | "fsevents": "~2.3.1", 241 | "glob-parent": "~5.1.0", 242 | "is-binary-path": "~2.1.0", 243 | "is-glob": "~4.0.1", 244 | "normalize-path": "~3.0.0", 245 | "readdirp": "~3.5.0" 246 | } 247 | }, 248 | "fill-range": { 249 | "version": "7.0.1", 250 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 251 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 252 | "requires": { 253 | "to-regex-range": "^5.0.1" 254 | } 255 | }, 256 | "fsevents": { 257 | "version": "2.3.2", 258 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 259 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 260 | "optional": true 261 | }, 262 | "glob-parent": { 263 | "version": "5.1.2", 264 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 265 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 266 | "requires": { 267 | "is-glob": "^4.0.1" 268 | } 269 | }, 270 | "is-binary-path": { 271 | "version": "2.1.0", 272 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 273 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 274 | "requires": { 275 | "binary-extensions": "^2.0.0" 276 | } 277 | }, 278 | "is-extglob": { 279 | "version": "2.1.1", 280 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 281 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 282 | }, 283 | "is-glob": { 284 | "version": "4.0.1", 285 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 286 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 287 | "requires": { 288 | "is-extglob": "^2.1.1" 289 | } 290 | }, 291 | "is-number": { 292 | "version": "7.0.0", 293 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 294 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 295 | }, 296 | "normalize-path": { 297 | "version": "3.0.0", 298 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 299 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 300 | }, 301 | "picomatch": { 302 | "version": "2.2.2", 303 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", 304 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" 305 | }, 306 | "prettier": { 307 | "version": "2.0.5", 308 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", 309 | "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", 310 | "dev": true 311 | }, 312 | "readdirp": { 313 | "version": "3.5.0", 314 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", 315 | "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", 316 | "requires": { 317 | "picomatch": "^2.2.1" 318 | } 319 | }, 320 | "sass": { 321 | "version": "1.32.8", 322 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz", 323 | "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==", 324 | "requires": { 325 | "chokidar": ">=2.0.0 <4.0.0" 326 | } 327 | }, 328 | "to-regex-range": { 329 | "version": "5.0.1", 330 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 331 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 332 | "requires": { 333 | "is-number": "^7.0.0" 334 | } 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gorko", 3 | "version": "0.9.1", 4 | "description": "A tiny Sass token class generator.", 5 | "main": "gorko.scss", 6 | "dependencies": { 7 | "sass": "^1.26.5" 8 | }, 9 | "scripts": { 10 | "test": "npx sass gorko.scss tmp/gorko.css && npx sass test/test.scss tmp/test.css" 11 | }, 12 | "devDependencies": { 13 | "prettier": "^2.0.5" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/hankchizljaw/gorko.git" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/hankchizljaw/gorko/issues" 24 | }, 25 | "homepage": "https://github.com/hankchizljaw/gorko#readme" 26 | } 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Gorko 2 | 3 | A tiny, Sass-powered design-token led utility class generator, with handy helpers, that helps you to power your front-ends with a single source of truth. 4 | 5 | ## Table of contents 6 | 7 | - [Gorko](#gorko) 8 | - [Getting started](#getting-started) 9 | - [Configuration](#configuration) 10 | - [Base size (optional)](#base-size--optional-) 11 | - [Size scale (optional)](#size-scale--optional-) 12 | - [Colors (optional)](#colors--optional-) 13 | - [Gorko config (required)](#gorko-config--required-) 14 | - [Breakpoints](#breakpoints) 15 | - [Utility Class Generator](#utility-class-generator) 16 | - [Example outputs](#example-outputs) 17 | - [Generating Utility Classes On Demand](#generating-utility-classes-on-demand) 18 | - [Using CSS Custom Properties](#using-css-custom-properties) 19 | - [Using themes](#using-themes) 20 | - [Sass functions](#sass-functions) 21 | - [Get color](#get-color) 22 | - [Example](#example) 23 | - [Get utility value](#get-utility-value) 24 | - [Example](#example-1) 25 | - [Get size](#get-size) 26 | - [Example](#example-2) 27 | - [Sass mixins](#sass-mixins) 28 | - [Apply utility](#apply-utility) 29 | - [Example](#example-3) 30 | - [Media query](#media-query) 31 | - [Example](#example-4) 32 | - [Contributing](#contributing) 33 | - [Pull Request Process](#pull-request-process) 34 | - [Code of Conduct](#code-of-conduct) 35 | - [Our Pledge](#our-pledge) 36 | - [Our Standards](#our-standards) 37 | - [Our Responsibilities](#our-responsibilities) 38 | - [Scope](#scope) 39 | - [Enforcement](#enforcement) 40 | - [Attribution](#attribution) 41 | 42 | ## Getting started 43 | 44 | First up, install Gorko: 45 | 46 | ```bash 47 | npm install gorko 48 | ``` 49 | 50 | In your Sass (SCSS in this case), import Gorko like so: 51 | 52 | ```scss 53 | @import '../path/to/your/node_modules/gorko/gorko.scss'; 54 | ``` 55 | 56 | This will generate utility classes based on the default configuration. To configure it for yourself, take this default, and create your own. Once it is created **import your config before Gorko**, like this: 57 | 58 | ```bash 59 | @import 'config'; 60 | ``` 61 | 62 | ## Configuration 63 | 64 | This is the default configuration. It is recommended that you use it as your base for your own configuration. 65 | 66 | ```scss 67 | /// BASE SIZE 68 | /// All calculations are based on this. It’s recommended that 69 | /// you keep it at 1rem because that is the root font size. You 70 | /// can set it to whatever you like and whatever unit you like. 71 | /// 72 | $gorko-base-size: 1rem; 73 | 74 | /// SIZE SCALE 75 | /// This is a Major Third scale that powers all the utilities that 76 | /// it is relevant for (font-size, margin, padding). All items are 77 | /// calcuated off the base size, so change that and cascade across 78 | /// your whole project. 79 | /// 80 | $gorko-size-scale: ( 81 | '300': $gorko-base-size * 0.8, 82 | '400': $gorko-base-size, 83 | '500': $gorko-base-size * 1.25, 84 | '600': $gorko-base-size * 1.6, 85 | '700': $gorko-base-size * 2, 86 | '900': $gorko-base-size * 3 87 | ); 88 | 89 | /// COLORS 90 | /// Colors are shared between backgrounds and text by default. 91 | /// You can also use them to power borders, fills or shadows, for example. 92 | /// 93 | $gorko-colors: ( 94 | 'dark': #1a1a1a, 95 | 'light': #f3f3f3 96 | ); 97 | 98 | /// CORE CONFIG 99 | /// This powers everything from utility class generation to breakpoints 100 | /// to enabling/disabling pre-built components/utilities. 101 | /// 102 | $gorko-config: ( 103 | 'bg': ( 104 | 'items': $gorko-colors, 105 | 'output': 'standard', 106 | 'property': 'background' 107 | ), 108 | 'color': ( 109 | 'items': $gorko-colors, 110 | 'output': 'standard', 111 | 'property': 'color' 112 | ), 113 | 'box': ( 114 | 'items': ( 115 | 'block': 'block', 116 | 'flex': 'flex', 117 | 'hide': 'none', 118 | 'show': 'inherit' 119 | ), 120 | 'output': 'responsive', 121 | 'property': 'display' 122 | ), 123 | 'font': ( 124 | 'items': ( 125 | 'base': 'Helvetica, Arial, sans-serif' 126 | ), 127 | 'output': 'standard', 128 | 'property': 'font-family' 129 | ), 130 | 'gap-top': ( 131 | 'items': $gorko-size-scale, 132 | 'output': 'standard', 133 | 'property': 'margin-top' 134 | ), 135 | 'gap-right': ( 136 | 'items': $gorko-size-scale, 137 | 'output': 'standard', 138 | 'property': 'margin-right' 139 | ), 140 | 'gap-bottom': ( 141 | 'items': $gorko-size-scale, 142 | 'output': 'standard', 143 | 'property': 'margin-bottom' 144 | ), 145 | 'gap-left': ( 146 | 'items': $gorko-size-scale, 147 | 'output': 'standard', 148 | 'property': 'margin-left' 149 | ), 150 | 'pad-top': ( 151 | 'items': $gorko-size-scale, 152 | 'output': 'standard', 153 | 'property': 'padding-top' 154 | ), 155 | 'pad-right': ( 156 | 'items': $gorko-size-scale, 157 | 'output': 'standard', 158 | 'property': 'padding-right' 159 | ), 160 | 'pad-bottom': ( 161 | 'items': $gorko-size-scale, 162 | 'output': 'standard', 163 | 'property': 'padding-bottom' 164 | ), 165 | 'pad-left': ( 166 | 'items': $gorko-size-scale, 167 | 'output': 'standard', 168 | 'property': 'padding-left' 169 | ), 170 | 'stack': ( 171 | 'items': ( 172 | '300': 0, 173 | '400': 10, 174 | '500': 20, 175 | '600': 30, 176 | '700': 40 177 | ), 178 | 'output': 'standard', 179 | 'property': 'z-index' 180 | ), 181 | 'text': ( 182 | 'items': $gorko-size-scale, 183 | 'output': 'responsive', 184 | 'property': 'font-size' 185 | ), 186 | 'weight': ( 187 | 'items': ( 188 | 'light': '300', 189 | 'regular': '400', 190 | 'bold': '700' 191 | ), 192 | 'output': 'standard', 193 | 'property': 'font-weight' 194 | ), 195 | 'width': ( 196 | 'items': ( 197 | 'full': '100%', 198 | 'half': percentage(1/2), 199 | 'quarter': percentage(1/4), 200 | 'third': percentage(1/3) 201 | ), 202 | 'output': 'responsive', 203 | 'property': 'width' 204 | ), 205 | 'breakpoints': ( 206 | 'sm': '(min-width: 36em)', 207 | 'md': '(min-width: 48em)', 208 | 'lg': '(min-width: 62em)' 209 | ) 210 | ); 211 | ``` 212 | 213 | ### Base size (optional) 214 | 215 | `$gorko-base-size` 216 | 217 | The base size for the size ratio calculations. It is only required for the default configuration. 218 | 219 | ### Size scale (optional) 220 | 221 | `$gorko-size-scale` 222 | 223 | This takes the base size and by default, generates a **major third** size scale. This can be set to whatever scale you like. 224 | 225 | If this is not set, the `get-size` function will use the default configuration. 226 | 227 | ### Colors (optional) 228 | 229 | `$gorko-colors` 230 | 231 | A collection of key/value pairs that by default, generate text and background colour utilities. 232 | 233 | If this is not set, the `get-color` function will use the default configuration. 234 | 235 | ### Gorko config (required) 236 | 237 | `$gorko-config` 238 | 239 | 🚨 Without this set, Gorko won’t work. 🚨 240 | 241 | It contains all of the utility class definitions and breakpoint definitions that the generator and mixins use. 242 | 243 | You can add as many or as little utility class definitions as you like—likewise for breakpoint definitions. 244 | 245 | ### Breakpoints 246 | 247 | The `breakpoints` map in `$gorko-config` defines media queries for the utility class generator. By default, the are set as follows: 248 | 249 | ```scss 250 | 'breakpoints': ( 251 | 'sm': '(min-width: 36em)', 252 | 'md': '(min-width: 48em)', 253 | 'lg': '(min-width: 62em)' 254 | ) 255 | ``` 256 | 257 | You can add as many or as little of these as you like and call them whatever you like. The only requirement is that the value is a valid media query. 258 | 259 | ## Utility Class Generator 260 | 261 | The utility class generator loops through `$gorko-config` looking for items that have a valid utility class structure. The following structure is required to generate a utility class: 262 | 263 | ```scss 264 | 'width':('items':('full':'100%','half': '50%' 265 | ), 266 | 'output': 'standard', 267 | 'property': 'width' 268 | ),; 269 | ``` 270 | 271 | The first key is the name of the utility and that contains a Sass map. Inside that map, you need to have the following: 272 | 273 | - `items`: a map of key/value pairs which link a utility class to a CSS property’s value. If you want to use CSS Custom Properties, this should be the string key, referencing the `'css-vars'` `$gorko-config` group that you want to use 274 | - `output`: this must be `responsive` or `standard`. If you set it to `responsive`, it will generate the same utility class for **every breakpoint that is defined**. 275 | - `property`: the [CSS property](https://css-tricks.com/almanac/properties/) that this utility controls. 276 | 277 | ### Example outputs 278 | 279 | The above structure would output the following utility classes: 280 | 281 | ```css 282 | .width-full { 283 | width: 100%; 284 | } 285 | 286 | .width-half { 287 | width: 50%; 288 | } 289 | ``` 290 | 291 | If we set the `output` to be `responsive`, with the default `breakpoints` defined, the output would be as follows: 292 | 293 | ```css 294 | .width-full { 295 | width: 100%; 296 | } 297 | 298 | .width-half { 299 | width: 50%; 300 | } 301 | 302 | @media (min-width: 36em) { 303 | .sm\:width-full { 304 | width: 100%; 305 | } 306 | 307 | .sm\:width-half { 308 | width: 50%; 309 | } 310 | } 311 | 312 | @media (min-width: 48em) { 313 | .md\:width-full { 314 | width: 100%; 315 | } 316 | 317 | .md\:width-half { 318 | width: 50%; 319 | } 320 | } 321 | 322 | @media (min-width: 62em) { 323 | .lg\:width-full { 324 | width: 100%; 325 | } 326 | 327 | .lg\:width-half { 328 | width: 50%; 329 | } 330 | } 331 | ``` 332 | 333 | ## Generating Utility Classes On Demand 334 | 335 | The default behaviour of Gorko is to generate utility classes, but in the spirit of being as flexible as possible, you can stop it doing that by setting `$generate-utility-classes` to `false` when you pull Gorko into your project, like this: 336 | 337 | ```scss 338 | $generate-utility-classes: false; 339 | @import 'config'; 340 | @import '../path/to/your/node_modules/gorko/gorko.scss'; 341 | ``` 342 | 343 | We might want to generate those utility classes later on in the CSS, though, so we use the `generate-utility-classes()` mixin anywhere **after** Gorko has been pulled in. 344 | 345 | ```scss 346 | $generate-utility-classes: false; 347 | @import 'config'; 348 | @import '../path/to/your/node_modules/gorko/gorko.scss'; 349 | 350 | // Standard authored CSS 351 | body { 352 | display: grid; 353 | place-items: center; 354 | } 355 | 356 | // Generate utilities after everything else 357 | @include generate-utility-classes(); 358 | ``` 359 | 360 | ## Using CSS Custom Properties 361 | 362 | **[See a demo repo](https://github.com/andy-piccalilli/gorko-custom-props-demo)** 363 | 364 | You might want to use CSS Custom Properties instead of static references to tokens. To do so with Gorko, you need to make a couple of adjustments to your `$gorko-config`. 365 | 366 | Firstly, at the top, you need to add a `css-vars` group which has a **key** and a value, which should be a map of tokens. 367 | 368 | ```scss 369 | $gorko-config: ( 370 | 'css-vars': ( 371 | 'color': $gorko-colors, 372 | 'weight': ( 373 | 'bold': 700, 374 | 'black': 900 375 | ) 376 | ) 377 | ); 378 | ``` 379 | 380 | In this example, we have defined a `'color'` group which uses `$gorko-colors`, but also a `'weight'` group where we have defined key value pairs, just like we do in the utility class generator. 381 | 382 | This will now generate a collection of CSS Custom properties like this: 383 | 384 | ```css 385 | :root { 386 | --color-dark: #1a1a1a; 387 | --color-light: #f3f3f3; 388 | --weight-bold: 700; 389 | --weight-black: 900; 390 | } 391 | ``` 392 | 393 | To use CSS Custom Properties in a utility class, we need to first, switch `'items'` to be a reference to the `'css-vars'` group we want, then set `'css-vars'` to be `true`. 394 | 395 | ```scss 396 | 'bg': ( 397 | 'items': 'color', 398 | 'css-vars': true, 399 | 'output': 'standard', 400 | 'property': 'background' 401 | ) 402 | ``` 403 | 404 | Now, the background utility classes will look like this: 405 | 406 | ```css 407 | .bg-dark { 408 | background: var(--color-dark); 409 | } 410 | 411 | .bg-light { 412 | background: var(--color-light); 413 | } 414 | ``` 415 | 416 | **Note**: You can use a combination of CSS Custom Properties _and_ static references to tokens for different utility classes. Gorko is flexible enough to let you do what works for you and your team. 417 | 418 | When you enable CSS custom properties, Gorko will generate the `:root` blocks for you, but sometimes, you might want those `:root` blocks not to be rendered. This is common if you are generating more than one CSS bundle. **To disable the generation of CSS Custom Property blocks, set `$generate-css-vars = false;`, before you import Gorko, just like [generating utility classes on demand](#generating-utility-classes-on-demand)**. 419 | 420 | ### Using themes 421 | 422 | **This feature requires Custom Properties** 423 | 424 | A handy part of the Custom Property support with Gorko is the ability to generate multiple themes. These themes can power dark mode with `@media (prefers-color-scheme: dark)` or be prefixed with whatever scheme you like. 425 | 426 | Let’s say you want a dark mode that [both honours the user’s preference, via a media query, and also, can be toggled](https://piccalil.li/tutorial/create-a-user-controlled-dark-or-light-mode/). The toggle version could use `[data-theme="dark"]` as its prefix. We’ll generate a default light theme too. 427 | 428 | First, we set some values. 429 | 430 | ```scss 431 | $gorko-colors: ( 432 | 'dark': #1a1a1a, 433 | 'light': #f3f3f3 434 | ); 435 | 436 | $light-colors: ( 437 | 'text': map-get($gorko-colors, 'dark'), 438 | 'bg': map-get($gorko-colors, 'light') 439 | ); 440 | 441 | $dark-colors: ( 442 | 'text': map-get($gorko-colors, 'light'), 443 | 'bg': map-get($gorko-colors, 'dark') 444 | ); 445 | ``` 446 | 447 | Then, we tweak `$gorko-config`. 448 | 449 | ```scss 450 | $gorko-config: ( 451 | 'css-vars': ( 452 | 'themes': ( 453 | 'default': ( 454 | 'tokens': ( 455 | 'color': $light-colors 456 | ) 457 | ), 458 | 'dark': ( 459 | 'prefers-color-scheme': 'dark', 460 | 'tokens': ( 461 | 'color': $dark-colors 462 | ) 463 | ), 464 | 'dark-toggle': ( 465 | 'prefix': '[data-theme="dark"]', 466 | 'tokens': ( 467 | 'color': $dark-colors 468 | ) 469 | ) 470 | ) 471 | ), 472 | /// the rest of your config 473 | ); 474 | ``` 475 | 476 | This then generates the following Custom Properties: 477 | 478 | ```css 479 | :root { 480 | --color-text: #1a1a1a; 481 | --color-bg: #f3f3f3; 482 | } 483 | 484 | @media (prefers-color-scheme: dark) { 485 | :root { 486 | --color-text: #f3f3f3; 487 | --color-bg: #1a1a1a; 488 | } 489 | } 490 | 491 | [data-theme='dark'] { 492 | --color-text: #f3f3f3; 493 | --color-bg: #1a1a1a; 494 | } 495 | ``` 496 | 497 | Now, we can style elements like so and regardless of what theme is selected, we won’t have to change our CSS: 498 | 499 | ```css 500 | .my-element { 501 | background: var(--color-bg); 502 | text: var(--color-text); 503 | } 504 | ``` 505 | 506 | A complete theme map looks like this: 507 | 508 | ```scss 509 | 'dark-toggle': ( // Name required 510 | 'prefix': '[data-theme="dark"]', // Optional. Will be :root if not set 511 | 'prefers-color-scheme': 'dark', // Optional. Will generate @media rule if set 512 | 'tokens': ( // Required. Map of key value pairs 513 | 'color': ( 514 | 'primary': #1a1a1a, 515 | 'secondary': #f3f3f 516 | ) 517 | ) 518 | ) 519 | ``` 520 | 521 | You can generate as many themes with whatever prefix you can think up! 522 | 523 | You could also [generate color utility classes using the generator](#utility-class-generator) that use these custom properties. 524 | 525 | ## Namespaces 526 | Gorko supports 'namespacing' both the generated class and variable names by allowing you to specify a prefix. This is done using the `namespace` map within `$gorko-config`. The default `namespace` config looks like this: 527 | 528 | ````scss 529 | $gorko-config: ( 530 | 'namespace': ( 531 | 'prefix': '', // string 532 | 'classes': true, // boolean or string 533 | 'css-vars': false // boolean or string 534 | ) 535 | ) 536 | ```` 537 | ### Namespace Settings 538 | #### prefix 539 | Specifying a value for `prefix` will append that value to classes (by default) and css variables (opt-in). You are responsible for appending any separating character, such as a dash or underscore. For example, if your namespace is `my-app-`, you need to add the trailing `-` character in the `'prefix'` section. 540 | 541 | #### classes 542 | Accepts either a boolean value indicating that the `prefix` should be applied to generated utility classes OR a string, which allows you to override the global `prefix` 543 | 544 | #### css-vars 545 | Accepts either a boolean value indicating that the `prefix` should be applied to css-vars OR a string, which allows you to override the global `prefix` 546 | 547 | ### Examples 548 | 549 | Minimal Configuration: This configuration would prepend `my-` to the beginning of generated utility classes, but would not modify css variable names: 550 | 551 | ````scss 552 | $gorko-config: ( 553 | 'namespace': ( 554 | 'prefix': 'my-' 555 | ) 556 | ) 557 | ```` 558 | 559 | Everything prefixed: This configuration applies the prefix to both utility classes and css variables: 560 | 561 | ````scss 562 | $gorko-config: ( 563 | 'namespace': ( 564 | 'prefix': 'my-', 565 | 'css-vars': true 566 | ) 567 | ) 568 | ```` 569 | 570 | Separate prefixes: This configuration gives you the ability to provide different prefixes for utility classes and css variables: 571 | 572 | ````scss 573 | $gorko-config: ( 574 | 'namespace': ( 575 | 'classes': 'my-class-', 576 | 'css-vars': 'my-var-' 577 | ) 578 | ) 579 | ```` 580 | 581 | ## Sass functions 582 | 583 | There are a couple of handy functions that give you access to configuration settings. 584 | 585 | ### Get color 586 | 587 | `get-color($key: string)` 588 | 589 | Takes the passed `$key` and attempts to retrieve a match from `$gorko-colors`. 590 | 591 | #### Example 592 | 593 | Using the default config: 594 | 595 | ```scss 596 | $dark = get-color('dark'); // #1a1a1a 597 | ``` 598 | 599 | ### Get utility value 600 | 601 | `get-utility-value($key: string, $value-key: string)` 602 | 603 | Returns back the value for a utility class so you can use it directly. 604 | 605 | #### Example 606 | 607 | Using the default config: 608 | 609 | ```scss 610 | font-weight: get-utility-value('weight', 'light'); // 300 611 | ``` 612 | 613 | ### Get size 614 | 615 | `get-size($ratio-key: string)` 616 | 617 | Tries to match the passed `$ratio-key` with the `$gorko-size-scale`. Returns null if it can’t find a match. 618 | 619 | #### Example 620 | 621 | Using the default config: 622 | 623 | ```scss 624 | $my-size: get-size('500'); // 1.25rem 625 | ``` 626 | 627 | ## Sass mixins 628 | 629 | ### Apply utility 630 | 631 | `apply-utility($key: string, $value-key: string)` 632 | 633 | Grab the property and value of one of the `$gorko-config` utilities that the generator will generate a class for. 634 | 635 | #### Example 636 | 637 | Using the default config: 638 | 639 | ```scss 640 | .my-element { 641 | @include apply-utility('weight', 'bold'); // font-weight: bold; 642 | } 643 | ``` 644 | 645 | ### Media query 646 | 647 | `media-query($key: string)` 648 | 649 | Pass in the key of one of your breakpoints set in `$gorko-config['breakpoints']` and this mixin will generate the media query with your configured value. 650 | 651 | #### Example 652 | 653 | Using the default config: 654 | 655 | ```scss 656 | .my-element { 657 | @include media-query('md') { 658 | background: goldenrod; 659 | } 660 | } 661 | ``` 662 | 663 | Output: 664 | 665 | ```css 666 | @media (min-width: 48em) { 667 | .my-element { 668 | background: goldenrod; 669 | } 670 | } 671 | ``` 672 | 673 | ## Contributing 674 | 675 | When contributing to this repository, please first discuss the change you wish to make via issue, 676 | email, or any other method with the owners of this repository before making a change. 677 | 678 | Please note we have a code of conduct, please follow it in all your interactions with the project. 679 | 680 | ## Pull Request Process 681 | 682 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 683 | build. 684 | 2. Ensure your work is thoroughly tested, to the best of your abilities 685 | 3. You may merge the Pull Request in once you have the sign-off from a maintainer 686 | 687 | ## Code of Conduct 688 | 689 | ### Our Pledge 690 | 691 | In the interest of fostering an open and welcoming environment, we as 692 | contributors and maintainers pledge to making participation in our project and 693 | our community a harassment-free experience for everyone, regardless of age, body 694 | size, disability, ethnicity, gender identity and expression, level of experience, 695 | nationality, personal appearance, race, religion, or sexual identity and 696 | orientation. 697 | 698 | ### Our Standards 699 | 700 | Examples of behavior that contributes to creating a positive environment 701 | include: 702 | 703 | - Using welcoming and inclusive language 704 | - Being respectful of differing viewpoints and experiences 705 | - Gracefully accepting constructive criticism 706 | - Focusing on what is best for the community 707 | - Showing empathy towards other community members 708 | 709 | Examples of unacceptable behavior by participants include: 710 | 711 | - The use of sexualized language or imagery and unwelcome sexual attention or 712 | advances 713 | - Trolling, insulting/derogatory comments, and personal or political attacks 714 | - Public or private harassment 715 | - Publishing others' private information, such as a physical or electronic 716 | address, without explicit permission 717 | - Other conduct which could reasonably be considered inappropriate in a 718 | professional setting 719 | 720 | ### Our Responsibilities 721 | 722 | Project maintainers are responsible for clarifying the standards of acceptable 723 | behavior and are expected to take appropriate and fair corrective action in 724 | response to any instances of unacceptable behavior. 725 | 726 | Project maintainers have the right and responsibility to remove, edit, or 727 | reject comments, commits, code, wiki edits, issues, and other contributions 728 | that are not aligned to this Code of Conduct, or to ban temporarily or 729 | permanently any contributor for other behaviors that they deem inappropriate, 730 | threatening, offensive, or harmful. 731 | 732 | ### Scope 733 | 734 | This Code of Conduct applies both within project spaces and in public spaces 735 | when an individual is representing the project or its community. Examples of 736 | representing a project or community include using an official project e-mail 737 | address, posting via an official social media account, or acting as an appointed 738 | representative at an online or offline event. Representation of a project may be 739 | further defined and clarified by project maintainers. 740 | 741 | ### Enforcement 742 | 743 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 744 | reported by contacting the project team at me@andy-bell.design. All 745 | complaints will be reviewed and investigated and will result in a response that 746 | is deemed necessary and appropriate to the circumstances. The project team is 747 | obligated to maintain confidentiality with regard to the reporter of an incident. 748 | Further details of specific enforcement policies may be posted separately. 749 | 750 | Project maintainers who do not follow or enforce the Code of Conduct in good 751 | faith may face temporary or permanent repercussions as determined by other 752 | members of the project's leadership. 753 | 754 | ### Attribution 755 | 756 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 757 | available at [http://contributor-covenant.org/version/1/4][version] 758 | 759 | [homepage]: http://contributor-covenant.org 760 | [version]: http://contributor-covenant.org/version/1/4/ 761 | -------------------------------------------------------------------------------- /src/_default-config.scss: -------------------------------------------------------------------------------- 1 | /// BASE SIZE 2 | /// All calculations are based on this. It’s recommended that 3 | /// you keep it at 1rem because that is the root font size. You 4 | /// can set it to whatever you like and whatever unit you like. 5 | /// 6 | $gorko-base-size: 1rem !default; 7 | 8 | /// SIZE SCALE 9 | /// This is a Major Third scale that powers all the utilities that 10 | /// it is relevant for (font-size, margin, padding). All items are 11 | /// calcuated off the base size, so change that and cascade across 12 | /// your whole project. 13 | /// 14 | $gorko-size-scale: ( 15 | '300': $gorko-base-size * 0.8, 16 | '400': $gorko-base-size, 17 | '500': $gorko-base-size * 1.25, 18 | '600': $gorko-base-size * 1.6, 19 | '700': $gorko-base-size * 2, 20 | '900': $gorko-base-size * 3 21 | ) !default; 22 | 23 | /// COLORS 24 | /// Colors are shared between backgrounds and text by default. 25 | /// You can also use them to power borders, fills or shadows, for example. 26 | /// 27 | $gorko-colors: ( 28 | 'dark': #1a1a1a, 29 | 'light': #f3f3f3 30 | ) !default; 31 | 32 | /// CORE CONFIG 33 | /// This powers everything from utility class generation to breakpoints 34 | /// to enabling/disabling pre-built components/utilities. 35 | /// 36 | $gorko-config: ( 37 | 'namespace': ( 38 | 'prefix': '', 39 | 'classes': true, 40 | 'css-vars': false, 41 | ), 42 | 'bg': ( 43 | 'items': $gorko-colors, 44 | 'output': 'standard', 45 | 'property': 'background' 46 | ), 47 | 'color': ( 48 | 'items': $gorko-colors, 49 | 'output': 'standard', 50 | 'property': 'color' 51 | ), 52 | 'box': ( 53 | 'items': ( 54 | 'block': 'block', 55 | 'flex': 'flex', 56 | 'hide': 'none', 57 | 'show': 'inherit' 58 | ), 59 | 'output': 'responsive', 60 | 'property': 'display' 61 | ), 62 | 'font': ( 63 | 'items': ( 64 | 'base': 'Helvetica, Arial, sans-serif' 65 | ), 66 | 'output': 'standard', 67 | 'property': 'font-family' 68 | ), 69 | 'gap-top': ( 70 | 'items': $gorko-size-scale, 71 | 'output': 'standard', 72 | 'property': 'margin-top' 73 | ), 74 | 'gap-right': ( 75 | 'items': $gorko-size-scale, 76 | 'output': 'standard', 77 | 'property': 'margin-right' 78 | ), 79 | 'gap-bottom': ( 80 | 'items': $gorko-size-scale, 81 | 'output': 'standard', 82 | 'property': 'margin-bottom' 83 | ), 84 | 'gap-left': ( 85 | 'items': $gorko-size-scale, 86 | 'output': 'standard', 87 | 'property': 'margin-left' 88 | ), 89 | 'pad-top': ( 90 | 'items': $gorko-size-scale, 91 | 'output': 'standard', 92 | 'property': 'padding-top' 93 | ), 94 | 'pad-right': ( 95 | 'items': $gorko-size-scale, 96 | 'output': 'standard', 97 | 'property': 'padding-right' 98 | ), 99 | 'pad-bottom': ( 100 | 'items': $gorko-size-scale, 101 | 'output': 'standard', 102 | 'property': 'padding-bottom' 103 | ), 104 | 'pad-left': ( 105 | 'items': $gorko-size-scale, 106 | 'output': 'standard', 107 | 'property': 'padding-left' 108 | ), 109 | 'stack': ( 110 | 'items': ( 111 | '300': 0, 112 | '400': 10, 113 | '500': 20, 114 | '600': 30, 115 | '700': 40 116 | ), 117 | 'output': 'standard', 118 | 'property': 'z-index' 119 | ), 120 | 'text': ( 121 | 'items': $gorko-size-scale, 122 | 'output': 'responsive', 123 | 'property': 'font-size' 124 | ), 125 | 'weight': ( 126 | 'items': ( 127 | 'light': '300', 128 | 'regular': '400', 129 | 'bold': '700' 130 | ), 131 | 'output': 'standard', 132 | 'property': 'font-weight' 133 | ), 134 | 'width': ( 135 | 'items': ( 136 | 'full': '100%', 137 | 'half': percentage(1/2), 138 | 'quarter': percentage(1/4), 139 | 'third': percentage(1/3) 140 | ), 141 | 'output': 'responsive', 142 | 'property': 'width' 143 | ), 144 | 'breakpoints': ( 145 | 'sm': '(min-width: 36em)', 146 | 'md': '(min-width: 48em)', 147 | 'lg': '(min-width: 62em)' 148 | ) 149 | ) !default; 150 | -------------------------------------------------------------------------------- /src/functions/_functions.scss: -------------------------------------------------------------------------------- 1 | @import 'get-color'; 2 | @import 'get-size'; 3 | @import 'get-utility-value'; 4 | -------------------------------------------------------------------------------- /src/functions/_get-color.scss: -------------------------------------------------------------------------------- 1 | /// GET COLOR FUNCTION 2 | /// Function tries to match the passed $key with the $gorko-colors map. Returns null 3 | /// if it can’t find a match. 4 | /// 5 | /// @param {string} $key - The color that you want to get 6 | /// 7 | @function get-color($key) { 8 | $response: map-get($gorko-colors, $key); 9 | 10 | @if ($response) { 11 | @return $response; 12 | } 13 | 14 | @warn #{'Color "' + $key + '" not found in $gorko-colors'}; 15 | 16 | @return null; 17 | } 18 | -------------------------------------------------------------------------------- /src/functions/_get-size.scss: -------------------------------------------------------------------------------- 1 | /// GET SIZE FUNCTION 2 | /// 3 | /// Function tries to match the passed $ratio-key with the $gorko-size-scale. Returns null 4 | /// if it can’t find a match. 5 | /// 6 | /// @param {string} $ratio-key - The size ratio that you want to get 7 | /// 8 | @function get-size($ratio-key) { 9 | $response: map-get($gorko-size-scale, $ratio-key); 10 | 11 | @if ($response) { 12 | @return $response; 13 | } 14 | 15 | @warn #{'Size "' + $ratio-key + '" not found in $gorko-size-scale'}; 16 | 17 | @return null; 18 | } 19 | -------------------------------------------------------------------------------- /src/functions/_get-utility-value.scss: -------------------------------------------------------------------------------- 1 | @import '../generator/workers/get-config-value'; 2 | 3 | /// GET UTILITY VALUE FUNCTION 4 | /// Grab the value of one of the $gorko-config utilities 5 | /// that the generator will generate a class for. 6 | /// 7 | /// @param {string} $key - The configured utility’s key 8 | /// @param {string} $value-key - The value key that you are looking for within the utilty 9 | /// 10 | @function get-utility-value($key, $value-key) { 11 | $values: get-config-value($key, $value-key); 12 | 13 | @if ($values) { 14 | @return map-get($values, 'value'); 15 | } @else { 16 | @return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/generator/_generator.scss: -------------------------------------------------------------------------------- 1 | @import 'workers/cycle'; 2 | @import 'workers/generate-css-vars'; 3 | @import '../mixins/generate-utility-classes'; 4 | 5 | $css-vars: map-get($gorko-config, 'css-vars'); 6 | 7 | /// Run the vars generator, regardless of whether they are defined, 8 | /// but only if they are enabled 9 | @if ($generate-css-vars == true) { 10 | @include generate-css-vars($css-vars); 11 | } 12 | 13 | /// Only run if there should be an output 14 | @if ($generate-utility-classes == true) { 15 | // Generate utility classes 16 | @include generate-utility-classes(); 17 | } 18 | -------------------------------------------------------------------------------- /src/generator/workers/_cycle.scss: -------------------------------------------------------------------------------- 1 | /// CYCLE MIXIN 2 | /// A simple worker that loops every element in the config 3 | /// and pushes it into the collection processor 4 | /// 5 | /// @param {string} $prefix - An optional prefix that is stuck to the front of selectors 6 | /// @param {bool} $is-breakpoint - A flag for the responsive generation to use to determine wether to run or not 7 | /// 8 | @import 'process-collection'; 9 | 10 | @mixin cycle($prefix, $is-breakpoint) { 11 | @each $selector, $props in $gorko-config { 12 | @if(type-of($props) == 'map') { 13 | @include process-collection($props, $prefix, $selector, $is-breakpoint); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/generator/workers/_generate-css-vars.scss: -------------------------------------------------------------------------------- 1 | @import 'process-vars'; 2 | 3 | /// GENERATE CSS VARS MIXIN 4 | /// Will either generate a simple collection of CSS Custom Properties 5 | /// or if `themes` are defined, it’ll loop each theme and generate Custom Properties 6 | /// 7 | /// @param {map} $vars - A collection of keys and values 8 | /// 9 | @mixin generate-css-vars($vars) { 10 | @if ($vars) { 11 | /// First, we look for themes to see if we need to do extra work 12 | $themes: map-get($vars, 'themes'); 13 | 14 | /// Still generate the custom properties but remove the themes from the $vars 15 | /// so we don't confuse the generator 16 | :root { 17 | @include process-vars(map-remove($vars, 'themes')); 18 | } 19 | 20 | @if ($themes) { 21 | @each $name, $theme in $themes { 22 | $prefix: map-get($theme, 'prefix'); 23 | $tokens: map-get($theme, 'tokens'); 24 | $prefers-color-scheme: map-get($theme, 'prefers-color-scheme'); 25 | 26 | /// A theme can be defined without a prefix, but we need one to create 27 | /// a valid CSS rule, so we’ll set it as :root 28 | @if not($prefix) { 29 | $prefix: ':root'; 30 | } 31 | 32 | /// If a prefers-color-scheme is set, that needs to be a media query 33 | @if ($prefers-color-scheme) { 34 | @media (prefers-color-scheme: #{$prefers-color-scheme}) { 35 | #{$prefix} { 36 | @include process-vars($tokens); 37 | } 38 | } 39 | } @else { 40 | /// If not, we can generate the vars within the prefix only instead 41 | #{$prefix} { 42 | @include process-vars($tokens); 43 | } 44 | } 45 | } 46 | } @else { 47 | /// No themes, so just generate custom properties for each CSS var 48 | :root { 49 | @include process-vars($vars); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/generator/workers/_generate-css.scss: -------------------------------------------------------------------------------- 1 | /// GENERATE CSS MIXIN 2 | /// The final CSS generator that takes the process params and generates 3 | /// a CSS utility. 4 | /// 5 | /// @param {string} $selector - The CSS selector that should be generated 6 | /// @param {string} $property - The CSS property that this utility affects 7 | /// @param {map} $items - The collection of utility items to generate classes for 8 | /// @param {boolean} $use-css-vars - this is to tell generate-css to use either a value or a CSS Variable 9 | /// @param {string} $item-key - the key for items which is used for tying each CSS var up to the item value 10 | /// 11 | @import 'get-namespace'; 12 | 13 | @mixin generate-css($selector, $property, $items, $use-css-vars, $item-key) { 14 | 15 | $var-namespace: get-namespace('css-vars'); 16 | 17 | @each $key, $value in $items { 18 | #{'.' + $selector + '-' + $key} { 19 | @if ($use-css-vars) { 20 | #{ $property }: var(--#{$var-namespace + $item-key + '-' + $key}); 21 | } @else { 22 | #{ $property }: #{$value}; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/generator/workers/_get-config-value.scss: -------------------------------------------------------------------------------- 1 | /// Grab the property and value of one of the $gorko-config utilities 2 | /// that the generator will generate a class for. 3 | /// 4 | /// @param {string} $key - The configured utility’s key 5 | /// @param {string} $value-key - The value key that you are looking for within the utilty 6 | /// 7 | @import 'get-namespace'; 8 | 9 | @function get-config-value($key, $value-key) { 10 | 11 | $utility: map-get($gorko-config, $key); 12 | $property: map-get($utility, 'property'); 13 | $use-css-vars: map-get($utility, 'css-vars'); 14 | $items: map-get($utility, 'items'); 15 | $vars-key: ''; 16 | 17 | @if ($use-css-vars) { 18 | 19 | /// If this utility class is using vars, we need to extract which var it is using 20 | $vars: map-get($gorko-config, 'css-vars'); 21 | /// The standard $items expression will have returned a string in this context 22 | $vars-key: $items; 23 | 24 | // Re-assign the items to be custom properties instead 25 | $items: map-get($vars, $vars-key); 26 | 27 | // Validate this is going to return a value 28 | @if not(map-get($items, $value-key)) { 29 | @warn #{'Value: ‘' + $value-key + '’ doesn’t exist in ‘' + $vars-key + '’' }; 30 | @return false; 31 | } 32 | 33 | $namespace: get-namespace('css-vars'); 34 | 35 | // We've got all we need so generate that custom property reference with var 36 | @return ('property': #{$property}, 'value': var(--#{$namespace + $vars-key + '-' + $value-key})); 37 | 38 | } @else { 39 | // No CSS vars so we need to find a property and key match 40 | $item-value: map-get($items, $value-key); 41 | 42 | @if ($property and $item-value) { 43 | @return ('property': #{$property}, 'value': #{$item-value}); 44 | } @else { 45 | @if ($property) { 46 | @warn #{'Value: ‘' + $value-key + '’ doesn’t exist in ‘' + $key + '’' }; 47 | } 48 | 49 | @if ($item-value) { 50 | @warn #{'Utility ‘' + $key + '’ doesn’t exist'}; 51 | } 52 | 53 | @return false; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/generator/workers/_get-namespace.scss: -------------------------------------------------------------------------------- 1 | /// Grab the property and value of one of the $gorko-config utilities 2 | /// that the generator will generate a class for. 3 | /// 4 | /// @param {string} $namespace-type - Which type of object needs namespaced. One of 'classes' or 'vars' 5 | /// 6 | @function get-namespace($namespace-type) { 7 | 8 | $namespace-defaults: ( 9 | 'prefix': '', 10 | 'classes': true, 11 | 'css-vars': false 12 | ); 13 | 14 | $namespace-settings: map-get($gorko-config, 'namespace'); 15 | 16 | @if($namespace-settings == null){ 17 | @return ''; 18 | } 19 | 20 | $namespace-settings-merged: map-merge($namespace-defaults, $namespace-settings); 21 | $prefix: map-get($namespace-settings-merged, 'prefix'); 22 | 23 | $should-display-prefix: map-get($namespace-settings-merged, $namespace-type); 24 | 25 | $is-setting-boolean: type-of($should-display-prefix) == bool; 26 | @if($should-display-prefix and $is-setting-boolean) { 27 | // If the specified setting is boolean true, return the global prefix 28 | @return $prefix; 29 | } @else if($should-display-prefix and $is-setting-boolean == false){ 30 | // If the specified setting is something other than a boolean, use that as the prefix for this setting 31 | @return $should-display-prefix; 32 | } @else { 33 | @return ''; 34 | } 35 | } -------------------------------------------------------------------------------- /src/generator/workers/_process-collection.scss: -------------------------------------------------------------------------------- 1 | /// PROCESS COLLECTION MIXIN 2 | /// Takes a passed collection and finds utility classes to generate. 3 | /// It’ll loop through breakpoints, too and generate responsive utilities 4 | /// where required 5 | /// 6 | /// @param {map} $collection - The '$gorko-config' config item 7 | /// @param {string} $prefix - An optional prefix that is stuck to the front of selectors 8 | /// @param {string} $selector - The CSS selector that should be generated 9 | /// @param {bool} $is-breakpoint - A flag for the responsive generation to use to determine wether to run or not 10 | /// 11 | @import 'generate-css'; 12 | @import 'get-namespace'; 13 | 14 | @mixin process-collection($collection, $prefix, $selector, $is-breakpoint) { 15 | $items: map-get($collection, 'items'); 16 | $output: map-get($collection, 'output'); 17 | $property: map-get($collection, 'property'); 18 | $use-css-vars: map-get($collection, 'css-vars'); 19 | $vars-key: ''; 20 | 21 | $selector: get-namespace('classes') + $selector; 22 | 23 | /// If this collection is using CSS vars, the items come from the 24 | /// 'css-vars' map in $gorko-config 25 | @if ($use-css-vars) { 26 | $vars: map-get($gorko-config, 'css-vars'); 27 | $vars-key: map-get($collection, 'items'); 28 | $items: map-get($vars, $vars-key); 29 | } 30 | 31 | /// It'll only run if $items and $property aren't null. This means it'll ignore the breakpoints and design tokens, for example. 32 | @if ($property and $items) { 33 | @if ($output == 'responsive') { 34 | 35 | @include generate-css( 36 | #{$prefix + $selector}, 37 | $property, 38 | $items, 39 | $use-css-vars, 40 | $vars-key 41 | ); 42 | } 43 | 44 | @if ($output == 'standard') { 45 | @if ($is-breakpoint != true) { 46 | @include generate-css( 47 | #{$prefix + $selector}, 48 | $property, 49 | $items, 50 | $use-css-vars, 51 | $vars-key 52 | ); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/generator/workers/_process-vars.scss: -------------------------------------------------------------------------------- 1 | /// PROCESS VARS MIXIN 2 | /// A reusable looper that generates custom property rules 3 | /// 4 | /// @param {map} $passed-vars - A collection of keys and values 5 | /// 6 | @import 'get-namespace'; 7 | 8 | @mixin process-vars($passed-vars) { 9 | 10 | $namespace: get-namespace('css-vars'); 11 | 12 | @each $var, $items in $passed-vars { 13 | @each $key, $value in $items { 14 | --#{$namespace + $var + '-' + $key}: #{$value}; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/mixins/_apply-utility.scss: -------------------------------------------------------------------------------- 1 | @import '../generator/workers/get-config-value'; 2 | 3 | /// APPLY UTILITY MIXIN 4 | /// Grab the property and value of one of the $gorko-config utilities 5 | /// that the generator will generate a class for. 6 | /// 7 | /// @param {string} $key - The configured utility’s key 8 | /// @param {string} $value-key - The value key that you are looking for within the utilty 9 | /// 10 | @mixin apply-utility($key, $value-key) { 11 | $values: get-config-value($key, $value-key); 12 | 13 | @if ($values) { 14 | #{map-get($values, 'property')}: map-get($values, 'value'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/mixins/_generate-utility-classes.scss: -------------------------------------------------------------------------------- 1 | @import '../generator/workers/cycle'; 2 | 3 | /// GENERATE UTILITY CLASSES MIXIN 4 | /// Runs the utility class generator on demand 5 | /// 6 | @mixin generate-utility-classes() { 7 | /* GORKO: auto-generated utility classes start */ 8 | 9 | /// Run the standard cycle first 10 | @include cycle('', false); 11 | 12 | /// For each breakpoint, generate a prefix and run the cycle 13 | @each $key, $value in map-get($gorko-config, 'breakpoints') { 14 | $prefix: #{$key + '\\:'}; 15 | $is-breakpoint: true; 16 | 17 | @media #{$value} { 18 | @include cycle($prefix, $is-breakpoint); 19 | } 20 | } 21 | 22 | /* GORKO: auto-generated utility classes end */ 23 | } 24 | -------------------------------------------------------------------------------- /src/mixins/_media-query.scss: -------------------------------------------------------------------------------- 1 | /// MEDIA QUERY MIXIN 2 | /// Pass in the key of one of your breakpoints set in `$gorko-config['breakpoints']` 3 | /// and this mixin will generate the @media query with your configured value. 4 | /// 5 | /// @param {string} $key - The key of your configured breakpoint 6 | /// 7 | @mixin media-query($key) { 8 | $breakpoints: map-get($gorko-config, 'breakpoints'); 9 | $matched-breakpoint: map-get($breakpoints, $key); 10 | 11 | @if not $matched-breakpoint { 12 | @warn #{ 'Breakpoint, ‘' + $key + '’ not found in `$gorko-config`. Make sure it’s set in the ‘breakpoints’ section.' }; 13 | } @else { 14 | @media #{ $matched-breakpoint } { 15 | @content; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/mixins/_mixins.scss: -------------------------------------------------------------------------------- 1 | @import 'apply-utility'; 2 | @import 'media-query'; 3 | -------------------------------------------------------------------------------- /test/_config.scss: -------------------------------------------------------------------------------- 1 | /// BASE SIZE 2 | /// All calculations are based on this. It’s recommended that 3 | /// you keep it at 1rem because that is the root font size. You 4 | /// can set it to whatever you like and whatever unit you like. 5 | /// 6 | $gorko-base-size: 1rem; 7 | 8 | /// SIZE SCALE 9 | /// This is a Major Third scale that powers all the utilities that 10 | /// it is relevant for (font-size, margin, padding). All items are 11 | /// calcuated off the base size, so change that and cascade across 12 | /// your whole project. 13 | /// 14 | $gorko-size-scale: ( 15 | '300': $gorko-base-size * 0.8, 16 | '400': $gorko-base-size, 17 | '500': $gorko-base-size * 1.25, 18 | '600': $gorko-base-size * 1.6, 19 | '700': $gorko-base-size * 2, 20 | '900': $gorko-base-size * 3 21 | ); 22 | 23 | /// COLORS 24 | /// Colors are shared between backgrounds and text by default. 25 | /// You can also use them to power borders, fills or shadows, for example. 26 | /// 27 | $gorko-colors: ( 28 | 'dark': #1a1a1a, 29 | 'light': #f3f3f3, 30 | 'blush': pink 31 | ); 32 | 33 | $light-colors: ( 34 | 'text': map-get($gorko-colors, 'dark'), 35 | 'bg': map-get($gorko-colors, 'light') 36 | ); 37 | 38 | $dark-colors: ( 39 | 'text': map-get($gorko-colors, 'light'), 40 | 'bg': map-get($gorko-colors, 'dark') 41 | ); 42 | 43 | 44 | /// CORE CONFIG 45 | /// This powers everything from utility class generation to breakpoints 46 | /// to enabling/disabling pre-built components/utilities. 47 | /// 48 | $gorko-config: ( 49 | 'namespace': ( 50 | 'prefix': 'my-prefix-', 51 | 'classes': true, 52 | 'css-vars': true 53 | ), 54 | 'css-vars': ( 55 | 'color': $gorko-colors, 56 | 'themes': ( 57 | 'default': ( 58 | 'tokens': ( 59 | 'color': $light-colors 60 | ) 61 | ), 62 | 'dark': ( 63 | 'prefers-color-scheme': 'dark', 64 | 'tokens': ( 65 | 'color': $dark-colors 66 | ) 67 | ), 68 | 'dark-toggle': ( 69 | 'prefix': '[data-theme="dark"]', 70 | 'tokens': ( 71 | 'color': $dark-colors 72 | ) 73 | ) 74 | ) 75 | ), 76 | 'bg': ( 77 | 'items': 'color', 78 | 'css-vars': true, 79 | 'output': 'standard', 80 | 'property': 'background-color' 81 | ), 82 | 'color': ( 83 | 'items': 'color', 84 | 'css-vars': true, 85 | 'output': 'standard', 86 | 'property': 'color' 87 | ), 88 | 'box': ( 89 | 'items': ( 90 | 'block': 'block', 91 | 'flex': 'flex', 92 | 'hide': 'none', 93 | 'show': 'inherit' 94 | ), 95 | 'output': 'responsive', 96 | 'property': 'display' 97 | ), 98 | 'font': ( 99 | 'items': ( 100 | 'base': 'Helvetica, Arial, sans-serif' 101 | ), 102 | 'output': 'standard', 103 | 'property': 'font-family' 104 | ), 105 | 'gap-top': ( 106 | 'items': $gorko-size-scale, 107 | 'output': 'standard', 108 | 'property': 'margin-top' 109 | ), 110 | 'gap-right': ( 111 | 'items': $gorko-size-scale, 112 | 'output': 'standard', 113 | 'property': 'margin-right' 114 | ), 115 | 'gap-bottom': ( 116 | 'items': $gorko-size-scale, 117 | 'output': 'standard', 118 | 'property': 'margin-bottom' 119 | ), 120 | 'gap-left': ( 121 | 'items': $gorko-size-scale, 122 | 'output': 'standard', 123 | 'property': 'margin-left' 124 | ), 125 | 'pad-top': ( 126 | 'items': $gorko-size-scale, 127 | 'output': 'standard', 128 | 'property': 'padding-top' 129 | ), 130 | 'pad-right': ( 131 | 'items': $gorko-size-scale, 132 | 'output': 'standard', 133 | 'property': 'padding-right' 134 | ), 135 | 'pad-bottom': ( 136 | 'items': $gorko-size-scale, 137 | 'output': 'standard', 138 | 'property': 'padding-bottom' 139 | ), 140 | 'pad-left': ( 141 | 'items': $gorko-size-scale, 142 | 'output': 'standard', 143 | 'property': 'padding-left' 144 | ), 145 | 'stack': ( 146 | 'items': ( 147 | '300': 0, 148 | '400': 10, 149 | '500': 20, 150 | '600': 30, 151 | '700': 40 152 | ), 153 | 'output': 'standard', 154 | 'property': 'z-index' 155 | ), 156 | 'text': ( 157 | 'items': $gorko-size-scale, 158 | 'output': 'responsive', 159 | 'property': 'font-size' 160 | ), 161 | 'weight': ( 162 | 'items': ( 163 | 'light': '300', 164 | 'regular': '400', 165 | 'bold': '700' 166 | ), 167 | 'output': 'standard', 168 | 'property': 'font-weight' 169 | ), 170 | 'width': ( 171 | 'items': ( 172 | 'full': '100%', 173 | 'half': percentage(1/2), 174 | 'quarter': percentage(1/4), 175 | 'third': percentage(1/3) 176 | ), 177 | 'output': 'responsive', 178 | 'property': 'width' 179 | ), 180 | 'breakpoints': ( 181 | 'sm': '(min-width: 36em)', 182 | 'md': '(min-width: 48em)', 183 | 'lg': '(min-width: 62em)' 184 | ) 185 | ); 186 | -------------------------------------------------------------------------------- /test/test.scss: -------------------------------------------------------------------------------- 1 | @import 'config'; 2 | 3 | @import '../gorko'; 4 | 5 | /// A TESTING GROUND FOR FEATURES. MAKE SURE IT'S CLEAR AFTER HERE BEFORE PUSHING 6 | 7 | body { 8 | font-size: get-utility-value('text', '900'); 9 | // background: get-config-value('color', 'blush'); 10 | @include apply-utility('color', 'blush'); 11 | } 12 | --------------------------------------------------------------------------------