├── .babelrc ├── .gitignore ├── README.md ├── package.json ├── src ├── AppContext.js ├── AppHeader.js ├── FontContext.js ├── Main.js ├── TextBaseline.js ├── app.js ├── fonts │ ├── Averta-Bold.otf │ ├── AvertaPE-Regular.otf │ ├── Cera-Regular.ttf │ ├── FiraCode-Regular.otf │ └── Inter.otf ├── index.html ├── ratios.js └── scales.js ├── static ├── _redirects ├── favicon.ico └── preview.png └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/proposal-class-properties"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | # cache 9 | .cache 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /dist 16 | 17 | /notes 18 | 19 | # misc 20 | .DS_Store 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Styled Baseline 2 | 3 | Just my playground 4 | 5 | Try the Tailwindcss version: https://www.npmjs.com/package/tailwind-compositor 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "baseline", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "Apostolos Christodoulou ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "blob-to-buffer": "^1.2.6", 9 | "emotion": "^10.0.17", 10 | "fontkit": "^1.8.0", 11 | "lodash": "^4.17.4", 12 | "parcel-plugin-static-files-copy": "^2.4.3", 13 | "preact": "^10.0.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.7.7", 17 | "@babel/plugin-proposal-class-properties": "^7.7.4", 18 | "parcel": "^1.12.4" 19 | }, 20 | "browserslist": "> 1%, not ie 11, not op_mini all", 21 | "scripts": { 22 | "start": "parcel src/index.html", 23 | "build": "parcel build src/index.html" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AppContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'preact'; 2 | 3 | export default createContext({ 4 | baseline: 8, 5 | setBaseline: () => null, 6 | size: 8, 7 | setSize: () => null, 8 | lead: 1, 9 | setLead: () => null, 10 | }); 11 | -------------------------------------------------------------------------------- /src/AppHeader.js: -------------------------------------------------------------------------------- 1 | import { Component, h } from "preact"; 2 | import { useContext, useEffect, useState } from "preact/hooks"; 3 | import { css, injectGlobal } from "emotion"; 4 | import fontkit from "fontkit"; 5 | 6 | import blobToBuffer from "blob-to-buffer"; 7 | 8 | import FontContext from "./FontContext"; 9 | import AppContext from "./AppContext"; 10 | import ratios from "./ratios"; 11 | 12 | import Inter from "./fonts/Inter.otf"; 13 | 14 | const defaultFontUrl = Inter; 15 | 16 | const selectClass = css` 17 | -webkit-appearance: none; 18 | -moz-appearance: none; 19 | height: 30px; 20 | text-align: left; 21 | color: #2b2b2b; 22 | width: 100%; 23 | padding: 0 8px 0 8px; 24 | background-color: white; 25 | opacity: 0; 26 | position: absolute; 27 | top: 0; 28 | left: 0; 29 | width: 100%; 30 | height: 100%; 31 | z-index: 1; 32 | `; 33 | 34 | const inputClass = css` 35 | height: 30px; 36 | text-align: left; 37 | color: #2b2b2b; 38 | width: 100%; 39 | padding: 0 8px 0 8px; 40 | background-color: white; 41 | &.selected { 42 | background-color: #2b2b2b; 43 | color: white; 44 | } 45 | `; 46 | 47 | const gridBtn = css` 48 | display: block; 49 | height: 30px; 50 | width: 100%; 51 | display: flex; 52 | align-items: center; 53 | text-align: left; 54 | color: #2b2b2b; 55 | padding: 0 0 0 8px; 56 | background-color: white; 57 | &:disabled { 58 | color: #ccc; 59 | } 60 | `; 61 | 62 | export default () => { 63 | // 64 | const { font, setFont } = useContext(FontContext); 65 | const { 66 | dark, 67 | setDark, 68 | baseline, 69 | setBaseline, 70 | size, 71 | setSize, 72 | snap, 73 | setSnap, 74 | lead, 75 | setLead, 76 | flow, 77 | setFlow, 78 | ratio, 79 | setRatio, 80 | length, 81 | setLength, 82 | grid, 83 | setGrid, 84 | debug, 85 | setDebug, 86 | } = useContext(AppContext); 87 | // 88 | useEffect(() => { 89 | loadURL(defaultFontUrl); 90 | }, [defaultFontUrl]); 91 | 92 | const onChange = (e) => { 93 | let file = e.target.files && e.target.files[0]; 94 | if (file) { 95 | loadBlob(file); 96 | } 97 | }; 98 | 99 | const loadURL = (url) => { 100 | fetch(url) 101 | .then((res) => res.blob()) 102 | .then(loadBlob, console.error); 103 | }; 104 | 105 | const loadBlob = (blob) => { 106 | blobToBuffer(blob, (err, buffer) => { 107 | if (err) { 108 | throw err; 109 | } 110 | 111 | var reader = new FileReader(); 112 | reader.onload = function (e) { 113 | const font = fontkit.create(buffer); 114 | useFont({ fontData: reader.result, font }); 115 | }; 116 | reader.readAsDataURL(blob); 117 | }); 118 | }; 119 | 120 | const useFont = ({ fontData, font }) => { 121 | if (!font) return; 122 | setFont(font); 123 | 124 | injectGlobal` 125 | @font-face { 126 | font-family: '${font.familyName}'; 127 | font-style: normal; 128 | font-weight: ${font["OS/2"].usWeightClass}; 129 | src: url('${fontData}') 130 | format('opentype'); 131 | } 132 | `; 133 | }; 134 | 135 | return ( 136 |
147 |
* { 152 | text-align: left; 153 | font-size: 9px; 154 | padding: 3px 0 0 8px; 155 | } 156 | `} 157 | > 158 |
DARK
159 |
FONT
160 |
{snap ? "BASELINE GRID" : "LINE GAP"}
161 |
BASELINE
162 |
LEADING
163 |
ROOT SIZE
164 |
SCALE
165 |
LENGTH
166 |
V-RHYTHM
167 |
RULERS
168 |
169 |
170 |
177 | 180 |
190 | 203 | {font ? font.familyName : "F"} 204 |
205 | 213 |
218 | setBaseline(e.target.value)} 226 | /> 227 |
228 |
233 | setLead(e.target.value)} 241 | /> 242 |
243 |
248 | setSize(e.target.value)} 256 | /> 257 |
258 |
264 | 273 |
{ratio}
274 |
275 |
280 | setLength(parseInt(e.target.value))} 288 | /> 289 |
290 |
295 | setFlow(e.target.value)} 303 | /> 304 |
305 | 306 | 309 | 310 | setDark(!dark)} 313 | href="https://github.com/a7sc11u/styled-baseline" 314 | target="_blank" 315 | > 316 | GH 317 | 318 |
319 |
320 | ); 321 | }; 322 | -------------------------------------------------------------------------------- /src/FontContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'preact'; 2 | 3 | export default createContext({ 4 | font: null, 5 | features: null, 6 | setFont: () => 1, 7 | setFeatures: () => 1 8 | }); 9 | -------------------------------------------------------------------------------- /src/Main.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { useContext, useMemo } from "preact/hooks"; 3 | import { css } from "emotion"; 4 | 5 | import FontContext from "./FontContext"; 6 | import AppContext from "./AppContext"; 7 | import TextBaseline from "./TextBaseline"; 8 | 9 | import { modularScale, carbonScale } from "./scales"; 10 | 11 | export default () => { 12 | const { font } = useContext(FontContext); 13 | const { 14 | dark, 15 | baseline, 16 | size, 17 | snap, 18 | lead, 19 | flow, 20 | ratio, 21 | length, 22 | grid, 23 | debug, 24 | } = useContext(AppContext); 25 | 26 | if (!font) return null; 27 | 28 | const minHeight = Math.ceil(30 / baseline); 29 | 30 | let bg = css` 31 | background-color: ${dark ? `#060606` : `#FFFFFF`}; 32 | color: ${dark ? `#FFFFFF` : `#060606`}; 33 | min-height: 100vh; 34 | padding-top: ${baseline * 1 + baseline * flow + minHeight * baseline}px; 35 | padding-bottom: ${baseline * flow + minHeight * baseline}px; 36 | padding-left: 5vw; 37 | padding-right: 5vw; 38 | background-repeat: repeat; 39 | background-size: 100% ${baseline}px; 40 | ${grid && 41 | `background-image: linear-gradient( 42 | rgba(255, 0, 107, ${dark ? 0.2 : 0.2}) 1px, 43 | transparent 0 44 | );`} 45 | `; 46 | 47 | const scale = useMemo(() => { 48 | return ratio === "IBM Carbon" 49 | ? carbonScale({ base: parseInt(size), length: length }) 50 | : modularScale({ base: size, ratio }); 51 | }, [size, length, ratio, baseline]); 52 | 53 | return ( 54 |
55 | {Array.from(new Array(length)).map((v, i) => ( 56 | 67 | Lorem Ipsum is simply dummy text of the printing and typesetting 68 | industry. Lorem Ipsum has been the industry's standard dummy text ever 69 | since the 1500s, when an unknown printer took a galley of type and 70 | scrambled it to make a type specimen book. It has survived not only 71 | five centuries, but also the leap into electronic typesetting, 72 | remaining essentially unchanged. It was popularised in the 1960s with 73 | the release of Letraset sheets containing Lorem Ipsum passages, and 74 | more recently with desktop publishing software like Aldus PageMaker 75 | including versions of Lorem Ipsum. 76 | 77 | ))} 78 |
79 | ); 80 | }; 81 | -------------------------------------------------------------------------------- /src/TextBaseline.js: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { css } from "emotion"; 3 | 4 | const precision = 4; 5 | const toPrecision = (input, precision) => { 6 | return input.toPrecision(precision); 7 | }; 8 | 9 | export default ({ 10 | children, 11 | font, 12 | fontSize, 13 | baseline = 8, 14 | leading = null, 15 | flow = 0, 16 | snap = true, 17 | dark = true, 18 | debug = false, 19 | }) => { 20 | const toBaseline = (num) => { 21 | // const factor = num / baseline; 22 | // const remainer = factor - Math.floor(factor); 23 | 24 | // console.log(remainer, ratio); 25 | 26 | // const round = remainer < ratio ? Math.floor : Math.ceil; 27 | 28 | return Math.ceil(num / baseline) * baseline; 29 | }; 30 | 31 | const descentAbsolute = Math.abs(font.descent); 32 | const contentArea = font.ascent + font.lineGap + descentAbsolute; 33 | 34 | const contentAreaRatio = contentArea / font.unitsPerEm; 35 | const descentRatio = descentAbsolute / font.unitsPerEm; 36 | const ascentRatio = font.ascent / font.unitsPerEm; 37 | const lineGapRatio = font.lineGap / font.unitsPerEm; 38 | const lineGapRatioHalf = lineGapRatio / 2; 39 | const capHeightRatio = font.capHeight / font.unitsPerEm; 40 | const xHeightRatio = font.xHeight / font.unitsPerEm; 41 | const capHeight = fontSize * capHeightRatio; 42 | 43 | const roundPoint = xHeightRatio; 44 | const rowHeight = snap ? toBaseline(capHeight) : capHeight; 45 | const rowHeightRatio = rowHeight / fontSize; 46 | 47 | const lineHeight = rowHeight + leading * baseline; 48 | const contentAreaHeight = contentAreaRatio * fontSize; 49 | const lineHeightOffset = contentAreaHeight - lineHeight; 50 | const lineHeightOffsetHalf = lineHeightOffset / 2; 51 | const lineHeightOffsetHalfRatio = lineHeightOffsetHalf / fontSize; 52 | 53 | const trimTop = 54 | (ascentRatio - 55 | rowHeightRatio + 56 | lineGapRatioHalf - 57 | lineHeightOffsetHalfRatio) * 58 | -1; 59 | 60 | const trimBottom = 61 | (descentRatio + lineGapRatioHalf - lineHeightOffsetHalfRatio) * -1; 62 | 63 | const lineHeightValue = lineHeight / fontSize; 64 | const trimTopValue = toPrecision(trimTop, precision); 65 | const trimBottomValue = toPrecision(trimBottom, precision); 66 | 67 | return ( 68 | 99 | {children} 100 | 101 | ); 102 | }; 103 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import { Component, h, render } from "preact"; 2 | import { useState } from "preact/hooks"; 3 | 4 | import FontContext from "./FontContext"; 5 | import AppContext from "./AppContext"; 6 | import AppHeader from "./AppHeader"; 7 | import Main from "./Main"; 8 | 9 | const App = () => { 10 | const [font, setFont] = useState(); 11 | const [baseline, setBaseline] = useState(8); 12 | const [size, setSize] = useState(16); 13 | const [lead, setLead] = useState(2); 14 | const [flow, setFlow] = useState(6); 15 | const [ratio, setRatio] = useState("1.2"); 16 | const [length, setLength] = useState(12); 17 | const [snap, setSnap] = useState(true); 18 | const [grid, setGrid] = useState(false); 19 | const [debug, setDebug] = useState(false); 20 | const [dark, setDark] = useState(true); 21 | 22 | const handleSnapChange = (snap) => { 23 | if (snap) { 24 | setLead(Math.round(lead)); 25 | } 26 | 27 | setSnap(snap); 28 | }; 29 | 30 | return ( 31 | 32 | 56 | 57 | {font &&
} 58 | 59 | 60 | ); 61 | }; 62 | 63 | render(, document.body); 64 | -------------------------------------------------------------------------------- /src/fonts/Averta-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/src/fonts/Averta-Bold.otf -------------------------------------------------------------------------------- /src/fonts/AvertaPE-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/src/fonts/AvertaPE-Regular.otf -------------------------------------------------------------------------------- /src/fonts/Cera-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/src/fonts/Cera-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/FiraCode-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/src/fonts/FiraCode-Regular.otf -------------------------------------------------------------------------------- /src/fonts/Inter.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/src/fonts/Inter.otf -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Styled Baseline 5 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | 41 | 77 | 78 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/ratios.js: -------------------------------------------------------------------------------- 1 | export const RATIOS = { 2 | IBM_CARBON: "carbon", 3 | OCTAVE: 2, 4 | DOUBLE_OCTAVE: 4, 5 | GOLDEN_RATIO: 1.618034, 6 | PHI: 1.618034, 7 | PERFECT_FIFTH: 1.5, 8 | PERFECT_FOURTH: 1.333333333, 9 | AUGMENTED_FOURTH: 1.41421, 10 | MAJOR_ELEVENTH: 2.666666667, 11 | MAJOR_SECOND: 1.125, 12 | MAJOR_SEVENTH: 1.875, 13 | MAJOR_SIXTH: 1.666666667, 14 | MAJOR_TENTH: 2.5, 15 | MAJOR_THIRD: 1.25, 16 | MAJOR_TWELFTH: 3, 17 | MINOR_SECOND: 1.066666667, 18 | MINOR_SEVENTH: 1.777777778, 19 | MINOR_THIRD: 1.2, 20 | }; 21 | 22 | export default RATIOS; 23 | -------------------------------------------------------------------------------- /src/scales.js: -------------------------------------------------------------------------------- 1 | import { RATIOS } from "./ratios"; 2 | 3 | export const modularScale = ({ 4 | base = 16, 5 | ratio = RATIOS.PERFECT_FOURTH, 6 | interval = 1, 7 | }) => { 8 | return (step) => { 9 | const v = Math.floor(base * Math.pow(ratio, step / interval)); 10 | 11 | return v; 12 | }; 13 | }; 14 | 15 | export const carbonScale = (params = {}) => { 16 | const { base = 8, intervals = 4, increment = 2 } = params; 17 | 18 | function getStep(step) { 19 | if (step <= 1) { 20 | return base; 21 | } 22 | 23 | return ( 24 | getStep(step - 1) + Math.floor((step - 2) / intervals + 1) * increment 25 | ); 26 | } 27 | 28 | return (v) => getStep(v + 1); 29 | }; 30 | -------------------------------------------------------------------------------- /static/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/static/favicon.ico -------------------------------------------------------------------------------- /static/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tol-is/styled-baseline/70b0b181b104f2f38e9bb121e6e77e61bd40c4d1/static/preview.png --------------------------------------------------------------------------------