├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── robots.txt ├── shaper-editing.gif ├── shot.png └── shot2.png ├── src ├── App.js ├── App.test.js ├── common.css ├── components │ ├── demo-email │ │ ├── demo-email.css │ │ └── demo-email.js │ ├── setting.js │ ├── settings.js │ └── specs │ │ ├── specs.css │ │ └── specs.js ├── demo.css ├── icons │ ├── frame-ratio.svg │ ├── frame-y.svg │ ├── space.svg │ ├── text-increment.svg │ └── unit.svg ├── index.css ├── index.js ├── logo.svg ├── serviceWorker.js ├── settings.css ├── setupTests.js ├── slider.css ├── utilities.css ├── utilities.js └── variables.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [SHAPER](https://hihayk.github.io/shaper/) — interface styles shaper 2 | 3 | ![](https://github.com/hihayk/shaper/blob/master/public/shaper-editing.gif?raw=true) 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://hihayk.github.io/shaper", 3 | "name": "shaper", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "gh-pages": "^3.1.0", 11 | "react": "^16.13.1", 12 | "react-dom": "^16.13.1", 13 | "react-scripts": "3.4.1", 14 | "tinycolor2": "^1.4.1" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "predeploy": "npm run build", 22 | "deploy": "gh-pages -d build" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | shaper — interface styles shaper 29 | 30 | 31 | 38 | 39 | 40 | 41 |
42 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/shaper-editing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/shaper-editing.gif -------------------------------------------------------------------------------- /public/shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/shot.png -------------------------------------------------------------------------------- /public/shot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/shot2.png -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useLayoutEffect } from 'react'; 2 | import './utilities.css'; 3 | import './common.css'; 4 | import Specs from './components/specs/specs'; 5 | import { getRandomObject, setProperty, numberToUnit, fonts } from './utilities'; 6 | import Settings from './components/settings' 7 | import DemoEmail from './components/demo-email/demo-email'; 8 | import { getVariables, darkModeStyles } from './variables' 9 | 10 | const defaultState = { 11 | fontFamily: fonts[0], 12 | textSizeIncrement: 1.23, 13 | baseTextSize: 0.9, 14 | textFrameRatio: 2.37, 15 | textFrameY: 0.8, 16 | spaceIncrement: 1.65, 17 | unit: 0.5, 18 | accentHue: 254, 19 | accentSaturation: 31, 20 | accentLightness: 50, 21 | greySaturation: 5, 22 | radius: 0.4, 23 | fieldBorderWidth: 2, 24 | buttonRound: false, 25 | } 26 | 27 | function App() { 28 | 29 | const getHash = () => { 30 | const hash = decodeURI(window.location.hash) 31 | 32 | if (hash) { 33 | const stateKeysArray = Object.keys(defaultState) 34 | const hashValuesArray = hash.substr(1, hash.length).split(['/']) 35 | 36 | const getHashObject = () => { 37 | var hashObject = {} 38 | stateKeysArray.forEach((key, i) => { 39 | const valueWithBoolean = () => { 40 | if(hashValuesArray[i] === 'true') { return true } 41 | if(hashValuesArray[i] === 'false') { return false } 42 | return hashValuesArray[i] 43 | } 44 | 45 | return hashObject[key] = valueWithBoolean() 46 | }) 47 | 48 | return hashObject 49 | } 50 | 51 | return getHashObject() 52 | } 53 | 54 | return null 55 | } 56 | 57 | const initialState = getHash() || defaultState 58 | 59 | const [fontFamily, setFontFamily] = useState(initialState.fontFamily) 60 | const [textSizeIncrement, setTextSizeIncrement] = useState(initialState.textSizeIncrement) 61 | const [baseTextSize, setBaseTextSize] = useState(initialState.baseTextSize) 62 | const [textFrameRatio, setTextFrameRatio] = useState(initialState.textFrameRatio) 63 | const [textFrameY, setTextFrameY] = useState(initialState.textFrameY) 64 | const [spaceIncrement, setSpaceIncrement] = useState(initialState.spaceIncrement) 65 | const [unit, setUnit] = useState(0.5) 66 | const [accentHue, setAccentHue] = useState(initialState.accentHue) 67 | const [accentSaturation, setAccentSaturation] = useState(initialState.accentSaturation) 68 | const [accentLightness, setAccentLightness] = useState(initialState.accentLightness) 69 | const [greySaturation, setGreySaturation] = useState(initialState.greySaturation) 70 | const [radius, setRadius] = useState(initialState.radius) 71 | const [fieldBorderWidth, setFieldBorderWidth] = useState(initialState.fieldBorderWidth) 72 | const [buttonRound, setButtonRound] = useState(initialState.buttonRound) 73 | const [darkMode, setDarkMode] = useState(false) 74 | const [preview, setPreview] = useState('demo') 75 | 76 | const variables = getVariables({ 77 | baseTextSize, 78 | textSizeIncrement, 79 | fontFamily, 80 | unit, 81 | spaceIncrement, 82 | textFrameRatio, 83 | textFrameY, 84 | accentHue, 85 | accentSaturation, 86 | accentLightness, 87 | greySaturation, 88 | radius, 89 | fieldBorderWidth, 90 | }) 91 | 92 | const styleRef = useRef() 93 | 94 | useLayoutEffect(() => { 95 | // Only create style element once 96 | if (!styleRef.current) { 97 | const style = document.createElement('style') 98 | document.head.appendChild(style) 99 | styleRef.current = style; 100 | } 101 | 102 | styleRef.current.innerHTML = ` 103 | :root{ 104 | ${Object.values(variables).join('')} 105 | } 106 | ${darkModeStyles} 107 | ` 108 | }, [variables]) 109 | 110 | const handleRandomize = () => { 111 | setFontFamily(getRandomObject().fontFamily) 112 | setTextSizeIncrement(getRandomObject().textSizeIncrement) 113 | setBaseTextSize(getRandomObject().baseTextSize) 114 | setTextFrameRatio(getRandomObject().textFrameRatio) 115 | setTextFrameY(getRandomObject().textFrameY) 116 | setSpaceIncrement(getRandomObject().spaceIncrement) 117 | setAccentHue(getRandomObject().accentHue) 118 | setAccentSaturation(getRandomObject().accentSaturation) 119 | setAccentLightness(getRandomObject().accentLightness) 120 | setGreySaturation(getRandomObject().greySaturation) 121 | setRadius(getRandomObject().radius) 122 | setFieldBorderWidth(getRandomObject().fieldBorderWidth) 123 | setButtonRound(getRandomObject().buttonRound) 124 | } 125 | 126 | const bodyClassList = document.querySelector('body').classList 127 | 128 | useEffect(() => { 129 | setProperty('fontFamily', fontFamily) 130 | setProperty('textSizeIncrement', textSizeIncrement) 131 | setProperty('baseTextSize', numberToUnit(baseTextSize, 'rem')) 132 | setProperty('textFrameRatio', textFrameRatio) 133 | setProperty('textFrameY', numberToUnit(textFrameY, 'em')) 134 | setProperty('spaceIncrement', spaceIncrement) 135 | setProperty('unit', numberToUnit(unit, 'rem')) 136 | setProperty('accentH', accentHue) 137 | setProperty('accentS', numberToUnit(accentSaturation, '%')) 138 | setProperty('accentL', numberToUnit(accentLightness, '%')) 139 | setProperty('greyS', numberToUnit(greySaturation, '%')) 140 | setProperty('radius', numberToUnit(radius, 'rem')) 141 | setProperty('fieldBorderWidth', numberToUnit(fieldBorderWidth, 'px')) 142 | 143 | if(buttonRound) { 144 | bodyClassList.add('roundButtons'); 145 | } else { 146 | bodyClassList.remove('roundButtons'); 147 | } 148 | 149 | if(darkMode) { 150 | bodyClassList.add('darkMode'); 151 | } else { 152 | bodyClassList.remove('darkMode'); 153 | } 154 | 155 | }, [accentHue, accentLightness, accentSaturation, baseTextSize, bodyClassList, buttonRound, darkMode, fieldBorderWidth, fontFamily, greySaturation, radius, spaceIncrement, textFrameRatio, textFrameY, textSizeIncrement, unit, variables.type]) 156 | 157 | const currentState = { 158 | fontFamily, 159 | textSizeIncrement, 160 | baseTextSize, 161 | textFrameRatio, 162 | textFrameY, 163 | spaceIncrement, 164 | unit, 165 | accentHue, 166 | accentSaturation, 167 | accentLightness, 168 | greySaturation, 169 | radius, 170 | fieldBorderWidth, 171 | buttonRound, 172 | } 173 | 174 | if(getHash()) { 175 | window.location.hash = encodeURI(Object.values(getHash()).join('/')) 176 | } 177 | 178 | const updateHash = () => { 179 | window.location.hash = encodeURI(Object.values(currentState).join('/')) 180 | } 181 | 182 | const isInitialMount = useRef(true); 183 | 184 | useEffect(() => { 185 | if (isInitialMount.current) { 186 | isInitialMount.current = false; 187 | } else { 188 | updateHash() 189 | } 190 | }); 191 | 192 | return ( 193 |
194 | 230 | {preview === 'specs' ? ( 231 | 242 | ) : ( 243 | 244 | )} 245 |
246 | ); 247 | } 248 | 249 | export default App; 250 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/common.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | --settingsBg: #1B1B1B; 9 | 10 | margin: 0; 11 | background-color: var(--settingsBg); 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .textInput { 17 | padding: calc(var(--textFrameY) - var(--fieldBorderWidth)) var(--textFrameY); 18 | font: inherit; 19 | color: inherit; 20 | border: var(--fieldBorderWidth) solid var(--c-fieldBorder); 21 | appearance: none; 22 | border-radius: var(--radius); 23 | width: 100%; 24 | background-color: transparent; 25 | } 26 | 27 | .button { 28 | padding: var(--textFrameY) var(--textFrameX); 29 | font: inherit; 30 | border: none; 31 | background-color: var(--c-buttonBg); 32 | appearance: none; 33 | border-radius: var(--radius); 34 | font-weight: 500; 35 | cursor: pointer; 36 | color: var(--c-body); 37 | letter-spacing: 0.03em; 38 | transition: 0.2s filter; 39 | } 40 | 41 | .button:hover { 42 | filter: brightness(1.1); 43 | } 44 | 45 | .button:active { 46 | filter: brightness(0.9); 47 | } 48 | 49 | .buttonSmall { 50 | font-size: var(--text-xs); 51 | } 52 | 53 | .roundButtons .button { 54 | border-radius: 1000rem; 55 | } 56 | 57 | .button-accent { 58 | background-color: var(--c-accent); 59 | color: var(--c-accentContrasted); 60 | } 61 | 62 | .button-accent span { 63 | color: black; 64 | mix-blend-mode: exclusion; 65 | } 66 | 67 | .card { 68 | padding: var(--space-l); 69 | background-color: white; 70 | border-radius: var(--radius); 71 | } 72 | 73 | .detailsActions { 74 | display: flex; 75 | } -------------------------------------------------------------------------------- /src/components/demo-email/demo-email.css: -------------------------------------------------------------------------------- 1 | .emailWrapper, .emailWrapper * { 2 | line-height: var(--globalLineHeight); 3 | } 4 | 5 | .emailWrapper { 6 | font-size: var(--text-s); 7 | color: var(--c-body); 8 | font-family: var(--fontFamily); 9 | display: grid; 10 | grid-template-columns: 17% 52% calc(100% - 17% - 52%); 11 | min-height: 100vh; 12 | background: var(--c-background); 13 | } 14 | 15 | .sidebar { 16 | border-right: 1px solid var(--c-border); 17 | padding: var(--space-l); 18 | } 19 | 20 | .detailsPanel { 21 | border-left: 1px solid var(--c-border); 22 | padding: var(--space-xl); 23 | background-color: var(--c-overlay); 24 | } 25 | 26 | main { 27 | padding: var(--space-xl); 28 | } 29 | 30 | .emails { 31 | border-top: 1px solid var(--c-border); 32 | } 33 | 34 | .email { 35 | display: grid; 36 | grid-template-columns: 1fr 4fr; 37 | padding: var(--space-m) 0; 38 | border-bottom: 1px solid var(--c-border); 39 | min-width: 0; 40 | min-width: 100%; 41 | } 42 | 43 | .listCell + .listCell { 44 | padding-left: var(--space-l); 45 | } 46 | 47 | .listCell { 48 | overflow: hidden; 49 | text-overflow: ellipsis; 50 | width: 100%; 51 | white-space: nowrap; 52 | min-width: 0; 53 | } 54 | 55 | .searchSection { 56 | margin-bottom: var(--space-xl); 57 | } 58 | 59 | .logo { 60 | width: 2rem; 61 | height: 2rem; 62 | border-radius: 100%; 63 | background-color: var(--c-accent); 64 | } 65 | 66 | .postAvatar { 67 | width: 3rem; 68 | height: 3rem; 69 | border-radius: 100%; 70 | background-color: var(--c-accent); 71 | } 72 | 73 | .post { 74 | display: grid; 75 | grid-template-columns: 3rem 1fr; 76 | grid-gap: var(--space-l); 77 | padding: var(--space-l); 78 | border-radius: var(--radius); 79 | background-color: var(--c-overlay); 80 | } 81 | 82 | .detailsCard { 83 | padding: var(--space-l); 84 | border-radius: var(--radius); 85 | background-color: var(--c-background); 86 | } 87 | 88 | .menuItem { 89 | padding: var(--space-s); 90 | border-radius: var(--radius); 91 | } 92 | 93 | .menuItem.active { 94 | background-color: var(--c-overlay); 95 | font-weight: bold; 96 | } 97 | 98 | .menuItem:not(.active) { 99 | cursor: pointer; 100 | color: var(--c-bodyDimmed); 101 | } 102 | 103 | .userCard { 104 | display: grid; 105 | grid-template-columns: auto 1fr auto; 106 | grid-gap: var(--space-m); 107 | padding: var(--space-m); 108 | border-radius: var(--radius); 109 | background-color: var(--c-background); 110 | align-items: center; 111 | } 112 | 113 | .userCardAvatar { 114 | width: 2rem; 115 | height: 2rem; 116 | border-radius: 100%; 117 | background-color: var(--c-accent); 118 | } 119 | 120 | .formGrid { 121 | display: grid; 122 | grid-template-columns: 1fr 1fr auto; 123 | grid-gap: var(--space-s); 124 | align-items: end; 125 | } 126 | 127 | .tabs { 128 | display: flex; 129 | } 130 | 131 | .tab { 132 | font-size: var(--text-m); 133 | border-bottom: 2px solid transparent; 134 | padding-bottom: 0.2em; 135 | } 136 | 137 | .tab:not(.active) { 138 | cursor: pointer; 139 | color: var(--c-bodyDimmed); 140 | } 141 | 142 | .tab + .tab { 143 | margin-left: var(--space-l); 144 | } 145 | 146 | .tab.active { 147 | border-color: var(--c-accent); 148 | } 149 | 150 | .messageBox { 151 | padding: var(--space-m); 152 | border-radius: var(--radius); 153 | background-color: var(--c-primary-3xlight); 154 | color: var(--c-primary-xdark); 155 | } 156 | 157 | .chartCard { 158 | display: grid; 159 | grid-template-columns: 50% 50%; 160 | padding: var(--space-l); 161 | border-radius: var(--radius); 162 | background-color: var(--c-background); 163 | align-items: center; 164 | } 165 | 166 | .chartCard .chart:first-child { 167 | padding-right: var(--space-m); 168 | } 169 | 170 | .chartCard .chart + .chart { 171 | padding-left: var(--space-m); 172 | border-left: 1px solid var(--c-border); 173 | } 174 | 175 | .chartCard svg { 176 | display: block; 177 | } 178 | 179 | .highlightCard { 180 | background-color: var(--c-accent); 181 | display: grid; 182 | grid-template-columns: 1fr auto; 183 | grid-gap: var(--space-m); 184 | padding: var(--space-l); 185 | border-radius: var(--radius); 186 | align-items: center; 187 | color: var(--c-accentContrasted); 188 | } 189 | 190 | .highlightCardAvatar { 191 | background-color: white; 192 | width: 3rem; 193 | height: 3rem; 194 | border-radius: 100%; 195 | } 196 | 197 | .highlightCardAvatars { 198 | display: flex; 199 | } 200 | 201 | .highlightCardAvatar + .highlightCardAvatar { 202 | margin-left: -2.2rem; 203 | } 204 | 205 | .highlightCardAvatar:first-child { 206 | z-index: 1; 207 | opacity: 0.2; 208 | } 209 | 210 | .highlightCardAvatar:nth-child(2) { 211 | z-index: 2; 212 | opacity: 0.5; 213 | } 214 | 215 | .highlightCardAvatar:nth-child(3) { 216 | z-index: 3; 217 | } 218 | 219 | @media (max-width: 1100px) { 220 | .emailWrapper { 221 | grid-template-columns: 1fr; 222 | } 223 | .sidebar { 224 | display: none; 225 | } 226 | .detailsPanel { 227 | width: 100%; 228 | } 229 | body { 230 | overflow-x: hidden; 231 | } 232 | .mainLogoSection { 233 | margin-right: 2rem; 234 | } 235 | 236 | .snippetSection { 237 | display: block; 238 | } 239 | 240 | .previewSection, .codeSection { 241 | width: 100%; 242 | } 243 | } 244 | 245 | .dataNumber { 246 | text-overflow: ellipsis; 247 | overflow: hidden; 248 | } 249 | -------------------------------------------------------------------------------- /src/components/demo-email/demo-email.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './demo-email.css' 3 | 4 | const contents = [ 5 | `Nylon is a chord generator that emulates chord strokes of a guitar. Play up to 6 strings per chord, add dynamics by controlling stroke speed, acceleration and velocity, and humanize each stroke by adding randomness.`, 6 | `Alternative graphical interface for Robert Henke's ml.Distance but with a twist. Distance and panning values are interpolated, for avoiding clicks in the sound. The added smooth parameter controls the interpolation time.`, 7 | `As suggested this version should keep correct timings when changing looping lengths.`, 8 | `The Interpolation Time`, 9 | `Added a preset system; now you can store/recall settings within a live set`, 10 | ] 11 | 12 | const emails = [ 13 | { name: 'Koji Akane', handle: '@koji', content: contents[0] }, 14 | { name: 'Kiyoko Riku', handle: '@kiyoko', content: contents[1] }, 15 | { name: 'Ichirō Nori', handle: '@nori', content: contents[2] }, 16 | ] 17 | const menuItems = [ 18 | 'First Section', 'Second Section', 'Third Section', 'Fourth Section' 19 | ] 20 | 21 | const DemoEmail = () => ( 22 |
23 | 32 |
33 |
34 | {contents[3]} 35 |
36 |
37 | First 38 |
Second
39 |
Third
40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 |
{emails[2].name}
50 |
{emails[2].content}
51 |
6:51 p. m. · 11 jun. 2020
52 |
53 |
54 |
55 | {emails.map((email, index) => ( 56 |
57 |
{email.name}
58 |
{email.content}
59 |
60 | ))} 61 |
62 |
63 | 64 |
65 |
66 | 67 | {}} type="text" className="textInput mt-s" value="email@example.com" /> 68 |
69 |
70 | 71 | {}} type="password" className="textInput mt-s" value="123456" /> 72 |
73 | 74 |
75 | 76 |
77 |
78 | 146 |
147 | ) 148 | 149 | export default DemoEmail -------------------------------------------------------------------------------- /src/components/setting.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Setting = ({ 4 | label, 5 | id, 6 | illustration, 7 | settingType, 8 | inputType, 9 | value, 10 | onChange, 11 | valueUnit, 12 | step, 13 | children, 14 | switchIsActive, 15 | ...props 16 | }) => { 17 | return ( 18 |
23 | {/* {illustration && ( 24 |
25 | 26 |
27 | )} */} 28 | 29 | {settingType === "input" && ( 30 | <> 31 | 34 | 43 | {inputType === "range" && ( 44 |
45 | 53 |
54 | {valueUnit} 55 |
56 |
57 | )} 58 | 59 | )} 60 | {settingType === "switch" && ( 61 |
62 | 66 | 69 |
70 | )} 71 |
72 | ) 73 | } 74 | 75 | export default Setting -------------------------------------------------------------------------------- /src/components/settings.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Setting from './setting'; 3 | import '../settings.css'; 4 | import '../slider.css'; 5 | import space from '../icons/space.svg' 6 | import frameRatio from '../icons/frame-ratio.svg' 7 | import frameY from '../icons/frame-y.svg' 8 | import textIncrement from '../icons/text-increment.svg' 9 | import unitSvg from '../icons/unit.svg' 10 | 11 | const Settings = ({ 12 | fontFamily, 13 | setFontFamily, 14 | textSizeIncrement, 15 | setTextSizeIncrement, 16 | baseTextSize, 17 | setBaseTextSize, 18 | textFrameRatio, 19 | setTextFrameRatio, 20 | textFrameY, 21 | setTextFrameY, 22 | spaceIncrement, 23 | unit, 24 | setUnit, 25 | setSpaceIncrement, 26 | accentHue, 27 | setAccentHue, 28 | accentSaturation, 29 | setAccentSaturation, 30 | accentLightness, 31 | setAccentLightness, 32 | greySaturation, 33 | setGreySaturation, 34 | radius, 35 | setRadius, 36 | fieldBorderWidth, 37 | setFieldBorderWidth, 38 | buttonRound, 39 | setButtonRound, 40 | darkMode, 41 | setDarkMode, 42 | preview, 43 | setPreview, 44 | 45 | handleRandomize, 46 | }) => { 47 | 48 | return ( 49 |
50 |
51 |
52 |

53 | Typography 54 |

55 | setTextSizeIncrement(e.target.value)} 63 | min={1} 64 | max={2} 65 | step={0.01} 66 | valueUnit="ratio" 67 | /> 68 | setBaseTextSize(e.target.value)} 75 | min={0.8} 76 | max={2} 77 | step={0.01} 78 | valueUnit="rem" 79 | /> 80 | setFontFamily(e.target.value)} 87 | /> 88 |
89 |
90 |

91 | Spacing 92 |

93 | setUnit(e.target.value)} 101 | min={0.4} 102 | max={0.8} 103 | step={0.01} 104 | valueUnit="rem" 105 | /> 106 | setSpaceIncrement(e.target.value)} 114 | min={1} 115 | max={2} 116 | step={0.01} 117 | valueUnit="ratio" 118 | /> 119 |
120 |
121 |

122 | - 123 |

124 | setTextFrameY(e.target.value)} 132 | min={0} 133 | max={1} 134 | step={0.01} 135 | valueUnit="em" 136 | /> 137 | setTextFrameRatio(e.target.value)} 145 | min={1} 146 | max={3} 147 | step={0.01} 148 | valueUnit="ratio" 149 | /> 150 |
151 |
152 |

153 | Color 154 |

155 | setAccentHue(e.target.value)} 162 | min={0} 163 | max={360} 164 | step={1} 165 | valueUnit="°" 166 | /> 167 | setAccentSaturation(e.target.value)} 174 | min={0} 175 | max={100} 176 | step={1} 177 | valueUnit="%" 178 | /> 179 | setAccentLightness(e.target.value)} 186 | min={0} 187 | max={100} 188 | step={1} 189 | valueUnit="%" 190 | /> 191 |
192 |
193 |

194 | - 195 |

196 | setGreySaturation(e.target.value)} 203 | min={0} 204 | max={100} 205 | step={1} 206 | valueUnit="%" 207 | /> 208 |
209 |
210 |

211 | Layer 212 |

213 | setRadius(e.target.value)} 220 | min={0} 221 | max={1} 222 | step={0.01} 223 | valueUnit="rem" 224 | /> 225 | setFieldBorderWidth(e.target.value)} 232 | min={1} 233 | max={3} 234 | step={1} 235 | valueUnit="px" 236 | /> 237 | setButtonRound(!buttonRound)} 243 | /> 244 | 245 |
246 |
247 |
248 | 254 |
255 | setDarkMode(!darkMode)} 261 | /> 262 |
263 |
264 | 270 | 276 |
277 |
278 |
279 | 280 |
281 |
282 | S 283 | H 284 | A 285 | P 286 | E 287 | R 288 |
289 | 290 | 291 | 292 | 293 |
294 |
295 |
296 | ) 297 | } 298 | 299 | export default Settings -------------------------------------------------------------------------------- /src/components/specs/specs.css: -------------------------------------------------------------------------------- 1 | .specsWrapper { 2 | padding: 2rem; 3 | background-color: var(--c-background); 4 | color: var(--c-body); 5 | font-family: var(--fontFamily); 6 | } 7 | 8 | .snippetsSection { 9 | display: grid; 10 | grid-gap: var(--space-l); 11 | } 12 | 13 | .snippetSection { 14 | display: flex; 15 | margin-bottom: var(--space-xl); 16 | } 17 | 18 | .previewSection { 19 | border: 1px solid var(--c-border); 20 | padding: var(--space-l); 21 | width: 50%; 22 | } 23 | 24 | .codeSection { 25 | background-color: var(--c-overlay); 26 | padding: var(--space-l); 27 | overflow: auto; 28 | width: 100%; 29 | } 30 | 31 | .snippetSection.withPreview .codeSection { 32 | width: 50%; 33 | } 34 | 35 | .codeSection code { 36 | font-family: 'Roboto Mono'; 37 | } 38 | 39 | .demoSpace { 40 | background-color: currentColor; 41 | } 42 | 43 | .demoSpace + .demoSpace { 44 | margin-top: var(--space-s); 45 | } 46 | 47 | .demoSpace-s { width: var(--space-s); height: var(--space-s); } 48 | .demoSpace-m { width: var(--space-m); height: var(--space-m); } 49 | .demoSpace-l { width: var(--space-l); height: var(--space-l); } 50 | .demoSpace-xl { width: var(--space-xl); height: var(--space-xl); } 51 | .demoSpace-2xl { width: var(--space-2xl); height: var(--space-2xl); } 52 | .demoSpace-3xl { width: var(--space-3xl); height: var(--space-3xl); } 53 | .demoSpace-4xl { width: var(--space-4xl); height: var(--space-4xl); } 54 | 55 | .colorBox { 56 | width: 2rem; 57 | height: 2rem; 58 | border-radius: 100%; 59 | display: block; 60 | border: 1px solid hsla(0, 0%, 100%, 0.2); 61 | } 62 | 63 | .colorsRow { 64 | display: grid; 65 | grid-gap: var(--space-s); 66 | } 67 | 68 | .colorBoxWrapper { 69 | display: inline-grid; 70 | grid-template-columns: auto 1fr; 71 | grid-gap: var(--space-s); 72 | align-items: center; 73 | } -------------------------------------------------------------------------------- /src/components/specs/specs.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './specs.css'; 3 | 4 | const Snippet = ({preview, code}) => ( 5 |
6 | {preview && ( 7 |
8 | {preview} 9 |
10 | )} 11 | 12 |
13 |
 14 |         {code}
 15 |       
16 |
17 |
18 | ) 19 | 20 | const Specs = ({ 21 | variables, 22 | darkModeStyles, 23 | }) => { 24 | return ( 25 |
26 |
27 |
Typography
28 | 31 | {['xl','l','m','s','xs'].map((item, index) => ( 32 |
33 |
Aa123 — {item}
34 |
Handgloves
35 |
36 | ))} 37 | 38 | )} 39 | code={variables.type} 40 | /> 41 |
Spacing
42 | 45 | {['s','m','l','xl','2xl','3xl','4xl'].map((item, index) => ( 46 |
47 | ))} 48 | 49 | )} 50 | code={variables.space} 51 | /> 52 |
53 |
Text frame
54 |
Use textFrame variables to set the padding of buttons and text inputs.
55 |
56 | 59 |
padding: var(--textFrameY)
66 |
padding: var(--textFrameY) var(--textFrameX)
72 | 73 | )} 74 | code={variables.textFrame} 75 | /> 76 |
Color
77 | 80 |
81 | {[1,2,3,4,5,6,7,8].map((item, index) => ( 82 |
83 |
84 |
grey{item}
85 |
86 | ))} 87 |
88 |
89 |
90 | {['accent','accentContrasted'].map((item, index) => ( 91 |
92 |
93 |
{item}
94 |
95 | ))} 96 |
97 | 98 | )} 99 | code={variables.color} 100 | /> 101 |
Layer
102 | 110 | )} 111 | code={variables.layer} 112 | /> 113 |
Dark mode styles
114 | 117 |
118 |
119 | ) 120 | } 121 | 122 | export default Specs -------------------------------------------------------------------------------- /src/demo.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/src/demo.css -------------------------------------------------------------------------------- /src/icons/frame-ratio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/frame-y.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/space.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/icons/text-increment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/unit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/src/index.css -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/settings.css: -------------------------------------------------------------------------------- 1 | input::-webkit-outer-spin-button, 2 | input::-webkit-inner-spin-button { 3 | -webkit-appearance: none; 4 | margin: 0; 5 | } 6 | 7 | input[type=number] { 8 | -moz-appearance: textfield; 9 | } 10 | 11 | .settingsWrapper { 12 | color: white; 13 | padding: 2rem; 14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 15 | overflow: auto; 16 | } 17 | 18 | .settingsGrid { 19 | min-width: 1280px; 20 | display: grid; 21 | grid-gap: 2rem; 22 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr auto; 23 | } 24 | 25 | .settingsGridColumnDouble { 26 | grid-column: 2 / span 2; 27 | } 28 | 29 | .settingTextInput { 30 | background-color: transparent; 31 | border: none; 32 | color: inherit; 33 | padding: 0.25rem; 34 | left: -0.25rem; 35 | position: relative; 36 | font: inherit; 37 | width: 100%; 38 | transition: 0.1s 0.2s; 39 | } 40 | 41 | .settingTextInput:hover { 42 | background-color: hsla(0, 0%, 100%, 0.1); 43 | } 44 | 45 | .settingTextInputSmall { 46 | max-width: 4rem; 47 | } 48 | 49 | .settingsGridColumnTitle { 50 | margin: 0; 51 | font-size: 1rem; 52 | } 53 | 54 | .mainLogoSection { 55 | display: flex; 56 | flex-direction: column; 57 | align-items: center; 58 | justify-content: space-between; 59 | } 60 | 61 | .mainLogo { 62 | font-size: 0.8rem; 63 | font-weight: bold; 64 | display: flex; 65 | flex-direction: column; 66 | justify-content: center; 67 | align-content: center; 68 | text-align: center; 69 | } 70 | 71 | .mainLogo span { 72 | margin-bottom: 0.7rem; 73 | } 74 | 75 | .ghIcon { 76 | width: 1.5rem; 77 | height: 1.5rem; 78 | cursor: pointer; 79 | text-decoration: none; 80 | color: inherit; 81 | opacity: 0.5; 82 | } 83 | 84 | .ghIcon svg { 85 | display: block; 86 | width: 100%; 87 | } 88 | 89 | .aboutSection { 90 | font-size: 0.8rem; 91 | } 92 | 93 | .aboutSection a, .aboutSection a:visited { 94 | color: inherit; 95 | text-underline-position: under; 96 | opacity: 0.5; 97 | text-decoration-color: hsla(0, 0%, 100%, 0.5); 98 | } 99 | 100 | .triggersColumn { 101 | border-left: 1px solid hsla(0, 0%, 100%, 0.1); 102 | border-right: 1px solid hsla(0, 0%, 100%, 0.1); 103 | display: grid; 104 | justify-items: center; 105 | align-items: center; 106 | padding: 0 3.5rem; 107 | } 108 | 109 | .randomButtonSection { 110 | border-bottom: 1px solid hsla(0, 0%, 100%, 0.1); 111 | } 112 | 113 | .triggerButton { 114 | background-color: white; 115 | border: none; 116 | font: inherit; 117 | color: var(--settingsBg); 118 | padding: 0.8em 1em; 119 | cursor: pointer; 120 | } 121 | 122 | .xBorder { 123 | height: 1px; 124 | width: 100%; 125 | background-color: hsla(0, 0%, 100%, 0.1); 126 | } 127 | 128 | .settingLabel { 129 | display: block; 130 | } 131 | 132 | .settingLabel, .settingNumberUnit { 133 | opacity: 0.5; 134 | } 135 | 136 | .settingWrapper { 137 | margin: 1.2rem 0 0 0; 138 | } 139 | 140 | .settingNumberInputWrapper { 141 | display: flex; 142 | justify-content: space-between; 143 | align-items: center; 144 | } 145 | 146 | .settingIllustration { 147 | display: block; 148 | } 149 | 150 | input[type=range] { 151 | display: block; 152 | } 153 | 154 | .settingIllustrationSection { 155 | height: 35px; 156 | display: flex; 157 | align-items: flex-end; 158 | margin-bottom: 0.5rem; 159 | } 160 | 161 | .switchSettingWrapper { 162 | display: flex; 163 | align-items: center; 164 | } 165 | 166 | .switchSettingWrapper .settingLabel { 167 | margin-bottom: 0; 168 | padding-left: 0.5rem; 169 | } 170 | 171 | .switchTrack { 172 | width: 2rem; 173 | height: 1rem; 174 | display: block; 175 | border-radius: 1rem; 176 | cursor: pointer; 177 | box-shadow: inset 0 0 0 1px hsla(0, 0%, 100%, 0.5);; 178 | transition: 0.2s; 179 | overflow: hidden; 180 | } 181 | 182 | .switchTrack input { 183 | opacity: 0; 184 | position: absolute; 185 | cursor: pointer; 186 | } 187 | 188 | .switchKnob { 189 | cursor: pointer; 190 | width: 1rem; 191 | height: 1rem; 192 | border-radius: 100%; 193 | display: block; 194 | box-shadow: inset 0 0 0 1px white, -1rem 0 0 10px white; 195 | transition: 0.2s; 196 | background-color: var(--settingsBg); 197 | position: relative; 198 | } 199 | 200 | .switchTrack input:checked + .switchKnob { 201 | transform: translate(1rem); 202 | } 203 | 204 | .settingButtonGroup { 205 | display: flex; 206 | font-size: 0.8rem; 207 | border: 1px solid hsla(0, 0%, 100%, 0.2); 208 | padding: 0.25rem; 209 | } 210 | 211 | .settingButton { 212 | appearance: none; 213 | border-radius: 0; 214 | border: none; 215 | font: inherit; 216 | font-size: 0.8rem; 217 | color: hsla(0, 0%, 100%, 0.5); 218 | padding: 0.4em 0.8em; 219 | cursor: pointer; 220 | background-color: transparent; 221 | } 222 | 223 | .settingButton.active { 224 | color: var(--settingsBg); 225 | background-color: white; 226 | } -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/slider.css: -------------------------------------------------------------------------------- 1 | input[type=range] { 2 | -webkit-appearance: none; 3 | width: 100%; 4 | background: transparent; 5 | height: 1.5rem; 6 | cursor: pointer; 7 | } 8 | 9 | input[type=range]::-webkit-slider-thumb { 10 | -webkit-appearance: none; 11 | } 12 | 13 | input[type=range]:focus { 14 | outline: none; 15 | } 16 | 17 | input[type=range]:hover::-webkit-slider-thumb, 18 | input[type=range]:focus::-webkit-slider-thumb { 19 | transform: scale(1.6); 20 | } 21 | input[type=range]:hover::-moz-range-thumb, 22 | input[type=range]:focus::-moz-range-thumb { 23 | transform: scale(1.6); 24 | } 25 | 26 | input[type=range]::-webkit-slider-thumb { 27 | -webkit-appearance: none; 28 | margin-top: -3px; 29 | height: 0.5rem; 30 | width: 0.5rem; 31 | background: white; 32 | border-radius: 100%; 33 | transition: 0.2s; 34 | } 35 | 36 | input[type=range]::-moz-range-thumb { 37 | margin-top: -3px; 38 | height: 0.5rem; 39 | width: 0.5rem; 40 | background: white; 41 | border-radius: 100%; 42 | transition: 0.2s; 43 | } 44 | 45 | input[type=range]::-webkit-slider-runnable-track { 46 | width: 100%; 47 | height: 2px; 48 | cursor: pointer; 49 | background: linear-gradient(90deg, hsla(0,0%,100%,1) 0%, hsla(0,0%,100%,1) var(--value), hsla(0,0%,100%,0.2) var(--value));; 50 | border: none; 51 | } 52 | 53 | input[type=range]::-moz-range-track { 54 | width: 100%; 55 | height: 2px; 56 | cursor: pointer; 57 | background: hsla(0,0%,100%,0.2); 58 | border: none; 59 | } -------------------------------------------------------------------------------- /src/utilities.css: -------------------------------------------------------------------------------- 1 | .limitLineLength { 2 | max-width: 30em; 3 | } 4 | 5 | .c-bodyDimmed { 6 | color: var(--c-bodyDimmed); 7 | } 8 | 9 | .bgc-grey1 { background-color: var(--c-grey1) } 10 | .bgc-grey2 { background-color: var(--c-grey2) } 11 | .bgc-grey3 { background-color: var(--c-grey3) } 12 | .bgc-grey4 { background-color: var(--c-grey4) } 13 | .bgc-grey5 { background-color: var(--c-grey5) } 14 | .bgc-grey6 { background-color: var(--c-grey6) } 15 | .bgc-grey7 { background-color: var(--c-grey7) } 16 | .bgc-grey8 { background-color: var(--c-grey8) } 17 | 18 | .bgc-accent { background-color: var(--c-accent) } 19 | .bgc-accentContrasted { background-color: var(--c-accentContrasted) } 20 | 21 | .text-xs { font-size: var(--text-xs) } 22 | .text-s { font-size: var(--text-s) } 23 | .text-m { font-size: var(--text-m) } 24 | .text-l { font-size: var(--text-l) } 25 | .text-xl { font-size: var(--text-xl) } 26 | 27 | .mt-s { margin-top: var(--space-s) } 28 | .mt-m { margin-top: var(--space-m) } 29 | .mt-l { margin-top: var(--space-l) } 30 | .mt-xl { margin-top: var(--space-xl) } 31 | .mt-2xl { margin-top: var(--space-2xl) } 32 | .mt-3xl { margin-top: var(--space-3xl) } 33 | .mt-4xl { margin-top: var(--space-4xl) } 34 | 35 | .mb-s { margin-bottom: var(--space-s) } 36 | .mb-m { margin-bottom: var(--space-m) } 37 | .mb-l { margin-bottom: var(--space-l) } 38 | .mb-xl { margin-bottom: var(--space-xl) } 39 | .mb-2xl { margin-bottom: var(--space-2xl) } 40 | .mb-3xl { margin-bottom: var(--space-3xl) } 41 | .mb-4xl { margin-bottom: var(--space-4xl) } 42 | 43 | .ml-s { margin-left: var(--space-s) } 44 | .ml-m { margin-left: var(--space-m) } 45 | .ml-l { margin-left: var(--space-l) } 46 | .ml-xl { margin-left: var(--space-xl) } 47 | .ml-2xl { margin-left: var(--space-2xl) } 48 | .ml-3xl { margin-left: var(--space-3xl) } 49 | .ml-4xl { margin-left: var(--space-4xl) } 50 | 51 | .mr-s { margin-right: var(--space-s) } 52 | .mr-m { margin-right: var(--space-m) } 53 | .mr-l { margin-right: var(--space-l) } 54 | .mr-xl { margin-right: var(--space-xl) } 55 | .mr-2xl { margin-right: var(--space-2xl) } 56 | .mr-3xl { margin-right: var(--space-3xl) } 57 | .mr-4xl { margin-right: var(--space-4xl) } 58 | 59 | .pt-s { padding-top: var(--space-s) } 60 | .pt-m { padding-top: var(--space-m) } 61 | .pt-l { padding-top: var(--space-l) } 62 | .pt-xl { padding-top: var(--space-xl) } 63 | .pt-2xl { padding-top: var(--space-2xl) } 64 | .pt-3xl { padding-top: var(--space-3xl) } 65 | .pt-4xl { padding-top: var(--space-4xl) } 66 | 67 | .pb-s { padding-bottom: var(--space-s) } 68 | .pb-m { padding-bottom: var(--space-m) } 69 | .pb-l { padding-bottom: var(--space-l) } 70 | .pb-xl { padding-bottom: var(--space-xl) } 71 | .pb-2xl { padding-bottom: var(--space-2xl) } 72 | .pb-3xl { padding-bottom: var(--space-3xl) } 73 | .pb-4xl { padding-bottom: var(--space-4xl) } 74 | 75 | .pl-s { padding-left: var(--space-s) } 76 | .pl-m { padding-left: var(--space-m) } 77 | .pl-l { padding-left: var(--space-l) } 78 | .pl-xl { padding-left: var(--space-xl) } 79 | .pl-2xl { padding-left: var(--space-2xl) } 80 | .pl-3xl { padding-left: var(--space-3xl) } 81 | .pl-4xl { padding-left: var(--space-4xl) } 82 | 83 | .pr-s { padding-right: var(--space-s) } 84 | .pr-m { padding-right: var(--space-m) } 85 | .pr-l { padding-right: var(--space-l) } 86 | .pr-xl { padding-right: var(--space-xl) } 87 | .pr-2xl { padding-right: var(--space-2xl) } 88 | .pr-3xl { padding-right: var(--space-3xl) } 89 | .pr-4xl { padding-right: var(--space-4xl) } -------------------------------------------------------------------------------- /src/utilities.js: -------------------------------------------------------------------------------- 1 | export const randomNumber = (min, max, full) => { 2 | const num = Math.random() * (max - min) + min 3 | let rand = min + Math.random() * (max - min); 4 | if (full === 'full') { 5 | return Math.round(rand) 6 | } 7 | return Math.round((num + Number.EPSILON) * 100) / 100 8 | } 9 | 10 | export const setProperty = (property, value) => { 11 | document.documentElement.style.setProperty(`--${property}`, value); 12 | } 13 | 14 | export const getProperty = (property) => { 15 | getComputedStyle(document.documentElement).getPropertyValue(`--${property}`); 16 | } 17 | 18 | export const fonts = [ 19 | 'system-ui, sans-serif', 20 | 'IBM Plex Sans', 21 | 'Futura, sans-serif', 22 | 'Roboto Mono', 23 | 'Nunito', 24 | 'Helvetica, sans-serif', 25 | 'Merriweather', 26 | 'Work Sans', 27 | ] 28 | 29 | const buttonIsRound = [true, false, false, false] 30 | 31 | export const getRandomObject = () => { 32 | const randomFontsPosition = randomNumber(0, fonts.length-1, 'full') 33 | return { 34 | fontFamily: fonts[randomFontsPosition], 35 | textSizeIncrement: randomNumber(1.2, 1.4), 36 | baseTextSize: randomNumber(0.875, 1.1), 37 | textFrameRatio: randomNumber(1.2, 3), 38 | textFrameY: randomNumber(0.4, 1), 39 | unit: randomNumber(0.4, 0.8), 40 | spaceIncrement: randomNumber(1.5, 1.8), 41 | accentHue: randomNumber(0, 360, 'full'), 42 | accentSaturation: randomNumber(0, 100, 'full'), 43 | accentLightness: randomNumber(30, 60, 'full'), 44 | greySaturation: randomNumber(0, 10, 'full'), 45 | radius: randomNumber(0, 0.4), 46 | fieldBorderWidth: randomNumber(1, 3, 'full'), 47 | buttonRound: buttonIsRound[randomNumber(0, 3, 'full')], 48 | } 49 | } 50 | 51 | export const numberToUnit = (number, unit) => `${number}${unit}` -------------------------------------------------------------------------------- /src/variables.js: -------------------------------------------------------------------------------- 1 | import tinycolor from 'tinycolor2' 2 | 3 | export const getVariables = ({ 4 | baseTextSize, 5 | textSizeIncrement, 6 | fontFamily, 7 | unit, 8 | spaceIncrement, 9 | textFrameRatio, 10 | textFrameY, 11 | accentHue, 12 | accentSaturation, 13 | accentLightness, 14 | greySaturation, 15 | radius, 16 | fieldBorderWidth, 17 | }) => { 18 | const getAccentButtonColor = () => { 19 | const accentButtonBgLuminance = tinycolor(`hsl(${accentHue} ${accentSaturation} ${accentLightness})`).getLuminance() 20 | const lightness = accentButtonBgLuminance < 0.3 ? 95 : 15 21 | 22 | return `hsl(var(--accentH), var(--accentS), ${lightness}%)` 23 | } 24 | 25 | return { 26 | type: 27 | `--baseTextSize: ${baseTextSize}rem; 28 | --textSizeIncrement: ${textSizeIncrement}; 29 | 30 | --text-xs: calc(var(--baseTextSize) / var(--textSizeIncrement)); 31 | --text-s: var(--baseTextSize); 32 | --text-m: calc(var(--text-s) * var(--textSizeIncrement)); 33 | --text-l: calc(var(--text-m) * var(--textSizeIncrement)); 34 | --text-xl: calc(var(--text-l) * var(--textSizeIncrement)); 35 | 36 | --lineHeightFixedAmount: 0.35rem; 37 | --lineHeightRelativeAmount: 1.1em; 38 | --globalLineHeight: calc(var(--lineHeightFixedAmount) + var(--lineHeightRelativeAmount)); 39 | 40 | --fontFamily: ${fontFamily};` 41 | , 42 | space: 43 | `--unit: ${unit}rem; 44 | --spaceIncrement: ${spaceIncrement}; 45 | 46 | --space-s: var(--unit); 47 | --space-m: calc(var(--space-s) * var(--spaceIncrement)); 48 | --space-l: calc(var(--space-m) * var(--spaceIncrement)); 49 | --space-xl: calc(var(--space-l) * var(--spaceIncrement)); 50 | --space-2xl: calc(var(--space-xl) * var(--spaceIncrement)); 51 | --space-3xl: calc(var(--space-2xl) * var(--spaceIncrement)); 52 | --space-4xl: calc(var(--space-3xl) * var(--spaceIncrement));` 53 | , 54 | textFrame: 55 | `--textFrameRatio: ${textFrameRatio}; 56 | --textFrameY: ${textFrameY}em; 57 | --textFrameX: calc(var(--textFrameY) * var(--textFrameRatio));` 58 | , 59 | color: 60 | `--accentH: ${accentHue}; 61 | --accentS: ${accentSaturation}%; 62 | --accentL: ${accentLightness}%; 63 | --c-accent: hsl(var(--accentH), var(--accentS), var(--accentL)); 64 | --c-accentContrasted: ${getAccentButtonColor()}; 65 | 66 | --greyH: var(--accentH); 67 | --greyS: ${greySaturation}%; 68 | --initialGreyLightness: 93%; 69 | --greyscaleLightnessIncrement: 11.3%; 70 | 71 | --grey1L: var(--initialGreyLightness); 72 | --grey2L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 1); 73 | --grey3L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 2); 74 | --grey4L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 3); 75 | --grey5L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 4); 76 | --grey6L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 5); 77 | --grey7L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 6); 78 | --grey8L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 7); 79 | 80 | --c-grey1: hsl(var(--greyH), var(--greyS), var(--grey1L)); 81 | --c-grey2: hsl(var(--greyH), var(--greyS), var(--grey2L)); 82 | --c-grey3: hsl(var(--greyH), var(--greyS), var(--grey3L)); 83 | --c-grey4: hsl(var(--greyH), var(--greyS), var(--grey4L)); 84 | --c-grey5: hsl(var(--greyH), var(--greyS), var(--grey5L)); 85 | --c-grey6: hsl(var(--greyH), var(--greyS), var(--grey6L)); 86 | --c-grey7: hsl(var(--greyH), var(--greyS), var(--grey7L)); 87 | --c-grey8: hsl(var(--greyH), var(--greyS), var(--grey8L)); 88 | 89 | --c-border: hsla(var(--greyH), var(--greyS), var(--grey8L), 0.1); 90 | --c-overlay: hsla(var(--greyH), var(--greyS), var(--grey8L), 0.07); 91 | --c-background: white; 92 | --c-body: var(--c-grey8); 93 | --c-bodyDimmed: hsla(var(--greyH), var(--greyS), var(--grey8L), 0.5); 94 | --c-fieldBorder: var(--c-grey2); 95 | --c-buttonBg: var(--c-grey2);` 96 | , 97 | layer: 98 | `--radius: ${radius}rem; 99 | 100 | --fieldBorderWidth: ${fieldBorderWidth}px;` 101 | , 102 | } 103 | } 104 | 105 | export const darkModeStyles = 106 | `body.darkMode { 107 | --c-border: hsla(var(--greyH), var(--greyS), var(--grey1L), 0.1); 108 | --c-overlay: hsla(var(--greyH), var(--greyS), var(--grey1L), 0.07); 109 | --c-background: var(--c-grey8); 110 | --c-body: var(--c-grey1); 111 | --c-bodyDimmed: hsla(var(--greyH), var(--greyS), var(--grey1L), 0.5); 112 | --c-fieldBorder: var(--c-grey6); 113 | --c-buttonBg: var(--c-grey6); 114 | }` --------------------------------------------------------------------------------