├── .github └── logo.svg ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── demo ├── components │ ├── GithubLink.js │ ├── Logo.js │ └── Typography.js ├── fonts │ ├── Inter-italic-latin.var.woff2 │ ├── Inter-roman-latin.var.woff2 │ ├── SourceSansPro-Regular.otf │ └── Ubuntu-Mono-bold.woff2 ├── package-lock.json ├── package.json ├── pages │ ├── _app.js │ └── index.js ├── postcss.config.js ├── public │ └── favicon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest ├── styles │ ├── base.css │ ├── components.css │ ├── main.css │ └── utilities.css ├── tailwind.config.full.js └── tailwind.config.js ├── package-lock.json ├── package.json ├── prettier.config.js └── src └── index.js /.github/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | /demo/out 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | node_modules 3 | .next 4 | package-lock.json 5 | yarn.lock 6 | demo 7 | .github 8 | prettier.config.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mosaad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Tailwind CSS 3 |

4 | 5 | A Tailwind CSS plugin for automatically trimming the whitespace above and below text nodes. This is a port of [Capsize](https://github.com/seek-oss/capsize). 6 | 7 | Huge thanks to [Michael Taranto](https://github.com/michaeltaranto) and the [Seek](https://github.com/seek-oss) team behind it for figuring out the hard parts. 8 | 9 | [View live demo](https://tailwindcss-capsize.themosaad.com) 10 | 11 | ```html 12 |

Capsized Text

13 | ``` 14 | 15 | # Table of Contents 16 | 17 | - [Installation](#installation) 18 | - [Usage](#usage) 19 | - [Trim by default](#trim-by-default) 20 | - [Default line-height](#default-line-height) 21 | - [Define a default font-family](#define-a-default-font-family) 22 | - [Usage with @apply](#usage-with--apply) 23 | - [Root font-size](#root-font-size) 24 | - [Limitation and Customization](#limitation-and-customization) 25 | - [Behind the scenes](#behind-the-scenes) 26 | - [This port vs original Capsize](#this-port-vs-original-capsize) 27 | - [cap-height (since v0.4.0)](#cap-height--since-v040-) 28 | - [Default values](#default-values) 29 | - [line-gap (since v0.4.0)](#line-gap--since-v040-) 30 | - [Default values](#default-values-1) 31 | 32 | ## Installation 33 | 34 | Install the plugin from npm: 35 | 36 | ```sh 37 | # Using npm 38 | npm install @themosaad/tailwindcss-capsize 39 | ``` 40 | 41 | Then add the plugin to your `tailwind.config.js` file: 42 | 43 | ```js 44 | // tailwind.config.js 45 | module.exports = { 46 | theme: { 47 | fontFamily: { 48 | // define your custom font 49 | sans: ['Inter var', 'sans-serif'], 50 | }, 51 | capsize: { 52 | fontMetrics: { 53 | // define font metrics for your custom font from Capsize's website 54 | sans: { 55 | capHeight: 2048, 56 | ascent: 2728, 57 | descent: -680, 58 | lineGap: 0, 59 | unitsPerEm: 2816, 60 | }, 61 | }, 62 | // define the utility class for trimming (leave empty to trim all text nodes) 63 | className: 'capsize', 64 | }, 65 | }, 66 | plugins: [require('@themosaad/tailwindcss-capsize')], 67 | } 68 | ``` 69 | 70 | ## Usage 71 | 72 | Now you can use the `capsize` class to trim any text: 73 | 74 | ```html 75 |

Capsized Text

76 | ``` 77 | 78 | ### Trim by default 79 | 80 | if you prefer to trim text nodes by default, don't define a class name. This will move the trimming css to the font-size classes: 81 | 82 | ```js 83 | // tailwind.config.js 84 | module.exports = { 85 | theme: { 86 | capsize: { 87 | fontMetrics: { 88 | // ... 89 | }, 90 | // className: 'capsize', 91 | }, 92 | }, 93 | plugins: [require('@themosaad/tailwindcss-capsize')], 94 | } 95 | ``` 96 | 97 | this way, you can use: 98 | 99 | ```html 100 |

Capsized Text

101 | ``` 102 | 103 | ### Default line-height 104 | 105 | The root `line-height` is set to `1.5` by default. Which you can alter via: 106 | 107 | ```js 108 | // tailwind.config.js 109 | module.exports = { 110 | theme: { 111 | capsize: { 112 | rootLineHeightUnitless: 1.2, 113 | }, 114 | } 115 | ``` 116 | 117 | Therefore, if that's the line-height you want for a capsized element, you can ditch the leading class: 118 | 119 | ```html 120 |

Capsized Text

121 | ``` 122 | 123 | If you want better default line-height per font-size, you can enable the [defaultLineHeights experimental feature](https://github.com/tailwindlabs/tailwindcss/blob/v1.8.5/src/flagged/defaultLineHeights.js). Which will become the default in Tailwindcss v2. 124 | 125 | ```js 126 | // tailwind.config.js 127 | module.exports = { 128 | experimental: { 129 | defaultLineHeights: true, 130 | }, 131 | } 132 | ``` 133 | 134 | ### Define a default font-family 135 | 136 | In most cases, you'll have a default font-family that's used accross the website by default. 137 | 138 | If you added the `font-sans` class to the body or a parent element, you can use 139 | 140 | ```html 141 |

Capsized Text

142 | ``` 143 | 144 | ### Usage with @apply 145 | 146 | Since the plugin outputs pseudo-elements, you'll need to use the experimental applyComplexClasses feature: 147 | 148 | ```js 149 | // tailwind.config.js 150 | module.exports = { 151 | experimental: { 152 | applyComplexClasses: true, 153 | }, 154 | } 155 | ``` 156 | 157 | Which will allow you to use: 158 | 159 | ```css 160 | p { 161 | @apply capsize font-sans text-xl leading-7; 162 | } 163 | ``` 164 | 165 | ### Root font-size 166 | 167 | The plugin outputs the following inside the `@tailwind/base`: 168 | 169 | ```css 170 | html { 171 | font-size: 16px; 172 | --root-font-size-px: 16; 173 | } 174 | ``` 175 | 176 | So overriding it from you css might mess up the trimming calculations. 177 | 178 | To change the default root font-size: 179 | 180 | ```js 181 | // tailwind.config.js 182 | module.exports = { 183 | theme: { 184 | capsize: { 185 | rootFontSizePx: { 186 | default: 16, 187 | }, 188 | }, 189 | }, 190 | } 191 | ``` 192 | 193 | You can also change the rootFontSize for each screen: 194 | 195 | ```js 196 | // tailwind.config.js 197 | module.exports = { 198 | theme: { 199 | capsize: { 200 | rootFontSizePx: { 201 | default: 16, 202 | sm: 18, 203 | lg: 20, 204 | }, 205 | }, 206 | }, 207 | } 208 | ``` 209 | 210 | ## Limitation and Customization 211 | 212 | - Accepts `rem` and `px` for fontSize. 213 | - Accepts `rem`, `px`, and `unitless` for lineHeight. 214 | - Doesn't trim text on IE11 as it uses css variables for the trimming calculations. (will work on a JS polyfill or an average pre-calculation for all project fonts) 215 | - ~~Adds `padding: 0.05px 0` to capsized element which will override your padding utility classes.~~ No longer the case from v0.3.0. 216 | 217 | ## Behind the scenes 218 | 219 | The plugin adds the following to typography-related utility classes: 220 | 221 | To font-family classes, it adds css variables with font metrics: 222 | 223 | ```css 224 | .font-sans { 225 | font-family: Inter var, system-ui; 226 | --cap-height-scale: 0.7272; 227 | --descent-scale: 0.2414; 228 | --ascent-scale: 0.96875; 229 | --line-gap-scale: 0; 230 | --line-height-scale: 1.2102; 231 | } 232 | ``` 233 | 234 | To font-size classes, it adds css variables for calculating the font-size in pixels: 235 | 236 | ```css 237 | .text-6xl { 238 | font-size: 4rem; 239 | --font-size-rem: 4; 240 | --font-size-px: calc(var(--font-size-rem) * var(--root-font-size-px)); 241 | } 242 | ``` 243 | 244 | To line-height classes, it adds css variables for calculating the line-height in pixels: 245 | 246 | ```css 247 | .leading-tight { 248 | line-height: 1.25; 249 | --line-height-unitless: 1.25; 250 | --line-height-px: calc(var(--line-height-unitless) * var(--font-size-px)); 251 | } 252 | ``` 253 | 254 | To the capsized element's pseduo selectors, it adds the trimming calculation: 255 | 256 | ```css 257 | .capsize::before { 258 | content: ''; 259 | display: table; 260 | --line-height-normal: calc(var(--line-height-scale) * var(--font-size-px)); 261 | --specified-line-height-offset-double: calc(var(--line-height-normal) - var(--line-height-px)); 262 | --specified-line-height-offset: calc(var(--specified-line-height-offset-double) / 2); 263 | --specified-line-height-offset-to-scale: calc( 264 | var(--specified-line-height-offset) / var(--font-size-px) 265 | ); 266 | --line-gap-scale-half: calc(var(--line-gap-scale) / 2); 267 | --leading-trim-top: calc( 268 | var(--ascent-scale) - var(--cap-height-scale) + var(--line-gap-scale-half) - var(--specified-line-height-offset-to-scale) 269 | ); 270 | margin-bottom: calc(-1em * var(--leading-trim-top)); 271 | } 272 | 273 | .capsize::after { 274 | content: ''; 275 | display: table; 276 | --line-height-normal: calc(var(--line-height-scale) * var(--font-size-px)); 277 | --specified-line-height-offset-double: calc(var(--line-height-normal) - var(--line-height-px)); 278 | --specified-line-height-offset: calc(var(--specified-line-height-offset-double) / 2); 279 | --specified-line-height-offset-to-scale: calc( 280 | var(--specified-line-height-offset) / var(--font-size-px) 281 | ); 282 | --prevent-collapse-to-scale: calc(var(--prevent-collapse) / var(--font-size-px)); 283 | --line-gap-scale-half: calc(var(--line-gap-scale) / 2); 284 | --leading-trim-bottom: calc( 285 | var(--descent-scale) + var(--line-gap-scale-half) - var(--specified-line-height-offset-to-scale) 286 | ); 287 | margin-top: calc(-1em * var(--leading-trim-bottom)); 288 | } 289 | ``` 290 | 291 | ## This port vs original Capsize 292 | 293 | Aside from implementing the calculations via CSS variables to allow the usage of utility classes as illustrated above, I use a different method to prevent margin collapse that's not yet used in the original Capsize package (capsize@1.1.0 at the time). 294 | 295 | Capsize currently adds a `0.05` top and bottom padding to the capsized element to prevent margin collapse. 296 | 297 | From v0.3.0, I implemented a new way to prevent the collapsing based on @michaeltaranto's findings in [this issue](https://github.com/seek-oss/capsize/issues/26#issuecomment-686796155) that's not yet implemented in their Capsize. 298 | 299 | Now, you can use padding classes on the capsized element: 300 | 301 | ```html 302 | 303 | Capsized Link with Padding 304 | 305 | ``` 306 | 307 | If you encountered any crossbrowser issue with the new prevent collapse implementation, you can reverse back to the original implementation with padding: 308 | 309 | ```js 310 | // tailwind.config.js 311 | module.exports = { 312 | theme: { 313 | capsize: { 314 | keepPadding: true, 315 | }, 316 | }, 317 | } 318 | ``` 319 | 320 | ## cap-height (since v0.4.0) 321 | 322 | Instead of setting the `font-size`, you can use `cap-height` to declare the height of a the capital letters, and it'll automatically set the `font-size` based on the `fontMetrics`. 323 | 324 | This comes in handy when you have an icon next to the test and you want to visually balance their height. 325 | 326 | ```html 327 |

Capsized Text that's 1rem in height

328 | ``` 329 | 330 | ### Default values 331 | 332 | Default values are cut from the `extendedSpacingScale` feature flag. Found it unuseful to inclue the very small and very large values. 333 | 334 | You can override the default values via: 335 | 336 | ```js 337 | module.exports = { 338 | theme: { 339 | // ... 340 | capHeight: { 341 | 2: '0.5rem', 342 | 2.5: '0.625rem', 343 | 3: '0.75rem', 344 | 3.5: '0.875rem', 345 | 4: '1rem', 346 | 5: '1.25rem', 347 | 6: '1.5rem', 348 | 7: '1.75rem', 349 | 8: '2rem', 350 | 9: '2.25rem', 351 | 10: '2.5rem', 352 | 11: '2.75rem', 353 | 12: '3rem', 354 | 13: '3.25rem', 355 | 14: '3.5rem', 356 | 15: '3.75rem', 357 | }, 358 | }, 359 | } 360 | ``` 361 | 362 | ## line-gap (since v0.4.0) 363 | 364 | `line-gap` goes hand in hand with `cap-height` to have predectible height. 365 | 366 | If the following example wrapped to 2 lines. The total height of the element would be 3rem. 367 | 368 | ```html 369 |

370 | Capsized Text that's 1rem in height and has a 1rem line-gap 371 |

372 | ``` 373 | 374 | ### Default values 375 | 376 | `line-gap` has the same default values as `cap-height` which you can override via: 377 | 378 | ```js 379 | module.exports = { 380 | theme: { 381 | // ... 382 | lineGap: { 383 | 2: '0.5rem', 384 | 2.5: '0.625rem', 385 | 3: '0.75rem', 386 | 3.5: '0.875rem', 387 | 4: '1rem', 388 | 5: '1.25rem', 389 | 6: '1.5rem', 390 | 7: '1.75rem', 391 | 8: '2rem', 392 | 9: '2.25rem', 393 | 10: '2.5rem', 394 | 11: '2.75rem', 395 | 12: '3rem', 396 | 13: '3.25rem', 397 | 14: '3.5rem', 398 | 15: '3.75rem', 399 | }, 400 | }, 401 | } 402 | ``` 403 | 404 | You can also set default `line-gap` values for each `cap-height` similar to the [defaultLineHeights experimental feature](https://github.com/tailwindlabs/tailwindcss/blob/v1.8.5/src/flagged/defaultLineHeights.js) via: 405 | 406 | ```js 407 | module.exports = { 408 | theme: { 409 | // ... 410 | capHeight: { 411 | 2: ['0.5rem', { lineGap: '0.5rem' }], 412 | 3: ['0.75rem', { lineGap: '0.625rem' }], 413 | // ... 414 | }, 415 | }, 416 | } 417 | ``` 418 | -------------------------------------------------------------------------------- /demo/components/GithubLink.js: -------------------------------------------------------------------------------- 1 | export default function GithubLink() { 2 | return ( 3 |
4 | 8 | 9 | 14 | 15 | View on GitHub 16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /demo/components/Logo.js: -------------------------------------------------------------------------------- 1 | export default function Logo() { 2 | return ( 3 |
4 | 5 | 11 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 40 | 41 | 45 | 49 | 53 | 57 | 62 | 67 | 71 | 75 | 80 | 87 | 91 | 92 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /demo/components/Typography.js: -------------------------------------------------------------------------------- 1 | import resolveConfig from 'tailwindcss/resolveConfig' 2 | import tailwindConfig from '../tailwind.config' 3 | 4 | export default function Typography() { 5 | const fullConfig = resolveConfig(tailwindConfig) 6 | return ( 7 | <> 8 |
9 | {Object.keys(fullConfig.theme.capsize.fontMetrics).map((fontFamily, fontFamilyIndex) => ( 10 |
11 |

15 | font-{fontFamily} 16 |

17 |
18 |
19 |

20 | font-size & line-height 21 |

22 | {Object.keys(fullConfig.theme.fontSize) 23 | // .reverse() 24 | .map((fontSize, fontSizeIndex) => ( 25 |
26 |

{`text-${fontSize}`}

29 |

{`text-${fontSize} leading-none`}

32 |

{`text-${fontSize} leading-tight`}

35 |

{`text-${fontSize} leading-relaxed`}

38 |
39 | ))} 40 |
41 |
42 |

43 | cap-height & line-gap 44 |

45 |
46 | {Object.keys(fullConfig.theme.capHeight) 47 | .sort((a, b) => a - b) 48 | .map((capHeight, capHeightIndex) => ( 49 |
50 |

{`cap-height-${capHeight}`}

53 |

{`cap-height-${capHeight} line-gap-0`}

56 |

{`cap-height-${capHeight} line-gap-8`}

59 |

{`cap-height-${capHeight} line-gap-10`}

62 |
63 | ))} 64 |
65 |
66 |
67 |
68 | ))} 69 |
70 | 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /demo/fonts/Inter-italic-latin.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/fonts/Inter-italic-latin.var.woff2 -------------------------------------------------------------------------------- /demo/fonts/Inter-roman-latin.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/fonts/Inter-roman-latin.var.woff2 -------------------------------------------------------------------------------- /demo/fonts/SourceSansPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/fonts/SourceSansPro-Regular.otf -------------------------------------------------------------------------------- /demo/fonts/Ubuntu-Mono-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/fonts/Ubuntu-Mono-bold.woff2 -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-capsize-demo", 3 | "version": "1.0.0", 4 | "description": "A demo for testing @themosaad/tailwindcss-capsize plugin", 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "author": "Mosaad", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@themosaad/tailwindcss-capsize": "^1.0.0", 14 | "autoprefixer": "^10.2.4", 15 | "next": "^10.0.6", 16 | "postcss": "^8.2.4", 17 | "react": "^16.13.1", 18 | "react-dom": "^16.13.1", 19 | "tailwindcss": "^2.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/main.css' 2 | 3 | export default function MyApp({ Component, pageProps }) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /demo/pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Typography from '../components/Typography' 3 | import GithubLink from '../components/GithubLink' 4 | import Logo from '../components/Logo' 5 | 6 | export default function Index() { 7 | return ( 8 |
9 | 10 | Tailwind CSS and Capsize 11 | 12 |
13 |
14 |

Tailwind CSS and Capsize

15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /demo/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /demo/public/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /demo/public/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /demo/public/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /demo/public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #000000 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demo/public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /demo/public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /demo/public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/favicon.ico -------------------------------------------------------------------------------- /demo/public/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theMosaad/tailwindcss-capsize/8c334eef1a421e7d02ca92c9dc8ff21d3b2ef00a/demo/public/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /demo/public/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/public/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Next.js", 3 | "short_name": "Next.js", 4 | "icons": [ 5 | { 6 | "src": "/favicons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#000000", 17 | "background_color": "#000000", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /demo/styles/base.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Inter var'; 3 | font-weight: 100 900; 4 | font-display: swap; 5 | font-style: normal; 6 | font-named-instance: 'Regular'; 7 | src: url('../fonts/Inter-roman-latin.var.woff2') format('woff2'); 8 | } 9 | 10 | @font-face { 11 | font-family: 'Inter var'; 12 | font-weight: 100 900; 13 | font-display: swap; 14 | font-style: italic; 15 | font-named-instance: 'Italic'; 16 | src: url('../fonts/Inter-italic-latin.var.woff2') format('woff2'); 17 | } 18 | 19 | @font-face { 20 | font-family: 'Source Sans Pro'; 21 | font-style: normal; 22 | font-weight: 400; 23 | font-display: swap; 24 | src: url('../fonts/SourceSansPro-Regular.otf') format('opentype'); 25 | } 26 | 27 | @font-face { 28 | font-family: 'Ubuntu Mono'; 29 | font-weight: 700; 30 | font-style: normal; 31 | src: url('../fonts/Ubuntu-Mono-bold.woff2') format('woff2'); 32 | } 33 | 34 | @tailwind base; 35 | -------------------------------------------------------------------------------- /demo/styles/components.css: -------------------------------------------------------------------------------- 1 | @tailwind components; 2 | -------------------------------------------------------------------------------- /demo/styles/main.css: -------------------------------------------------------------------------------- 1 | @import 'base.css'; 2 | @import 'components.css'; 3 | 4 | @import 'utilities.css'; 5 | -------------------------------------------------------------------------------- /demo/styles/utilities.css: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | -------------------------------------------------------------------------------- /demo/tailwind.config.full.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | future: { 3 | // removeDeprecatedGapUtilities: true, 4 | // purgeLayersByDefault: true, 5 | }, 6 | purge: [], 7 | target: 'relaxed', 8 | prefix: '', 9 | important: false, 10 | separator: ':', 11 | theme: { 12 | screens: { 13 | sm: '640px', 14 | md: '768px', 15 | lg: '1024px', 16 | xl: '1280px', 17 | }, 18 | colors: { 19 | transparent: 'transparent', 20 | current: 'currentColor', 21 | 22 | black: '#000', 23 | white: '#fff', 24 | 25 | gray: { 26 | 100: '#f7fafc', 27 | 200: '#edf2f7', 28 | 300: '#e2e8f0', 29 | 400: '#cbd5e0', 30 | 500: '#a0aec0', 31 | 600: '#718096', 32 | 700: '#4a5568', 33 | 800: '#2d3748', 34 | 900: '#1a202c', 35 | }, 36 | red: { 37 | 100: '#fff5f5', 38 | 200: '#fed7d7', 39 | 300: '#feb2b2', 40 | 400: '#fc8181', 41 | 500: '#f56565', 42 | 600: '#e53e3e', 43 | 700: '#c53030', 44 | 800: '#9b2c2c', 45 | 900: '#742a2a', 46 | }, 47 | orange: { 48 | 100: '#fffaf0', 49 | 200: '#feebc8', 50 | 300: '#fbd38d', 51 | 400: '#f6ad55', 52 | 500: '#ed8936', 53 | 600: '#dd6b20', 54 | 700: '#c05621', 55 | 800: '#9c4221', 56 | 900: '#7b341e', 57 | }, 58 | yellow: { 59 | 100: '#fffff0', 60 | 200: '#fefcbf', 61 | 300: '#faf089', 62 | 400: '#f6e05e', 63 | 500: '#ecc94b', 64 | 600: '#d69e2e', 65 | 700: '#b7791f', 66 | 800: '#975a16', 67 | 900: '#744210', 68 | }, 69 | green: { 70 | 100: '#f0fff4', 71 | 200: '#c6f6d5', 72 | 300: '#9ae6b4', 73 | 400: '#68d391', 74 | 500: '#48bb78', 75 | 600: '#38a169', 76 | 700: '#2f855a', 77 | 800: '#276749', 78 | 900: '#22543d', 79 | }, 80 | teal: { 81 | 100: '#e6fffa', 82 | 200: '#b2f5ea', 83 | 300: '#81e6d9', 84 | 400: '#4fd1c5', 85 | 500: '#38b2ac', 86 | 600: '#319795', 87 | 700: '#2c7a7b', 88 | 800: '#285e61', 89 | 900: '#234e52', 90 | }, 91 | blue: { 92 | 100: '#ebf8ff', 93 | 200: '#bee3f8', 94 | 300: '#90cdf4', 95 | 400: '#63b3ed', 96 | 500: '#4299e1', 97 | 600: '#3182ce', 98 | 700: '#2b6cb0', 99 | 800: '#2c5282', 100 | 900: '#2a4365', 101 | }, 102 | indigo: { 103 | 100: '#ebf4ff', 104 | 200: '#c3dafe', 105 | 300: '#a3bffa', 106 | 400: '#7f9cf5', 107 | 500: '#667eea', 108 | 600: '#5a67d8', 109 | 700: '#4c51bf', 110 | 800: '#434190', 111 | 900: '#3c366b', 112 | }, 113 | purple: { 114 | 100: '#faf5ff', 115 | 200: '#e9d8fd', 116 | 300: '#d6bcfa', 117 | 400: '#b794f4', 118 | 500: '#9f7aea', 119 | 600: '#805ad5', 120 | 700: '#6b46c1', 121 | 800: '#553c9a', 122 | 900: '#44337a', 123 | }, 124 | pink: { 125 | 100: '#fff5f7', 126 | 200: '#fed7e2', 127 | 300: '#fbb6ce', 128 | 400: '#f687b3', 129 | 500: '#ed64a6', 130 | 600: '#d53f8c', 131 | 700: '#b83280', 132 | 800: '#97266d', 133 | 900: '#702459', 134 | }, 135 | }, 136 | spacing: { 137 | px: '1px', 138 | '0': '0', 139 | '1': '0.25rem', 140 | '2': '0.5rem', 141 | '3': '0.75rem', 142 | '4': '1rem', 143 | '5': '1.25rem', 144 | '6': '1.5rem', 145 | '8': '2rem', 146 | '10': '2.5rem', 147 | '12': '3rem', 148 | '16': '4rem', 149 | '20': '5rem', 150 | '24': '6rem', 151 | '32': '8rem', 152 | '40': '10rem', 153 | '48': '12rem', 154 | '56': '14rem', 155 | '64': '16rem', 156 | }, 157 | backgroundColor: (theme) => theme('colors'), 158 | backgroundImage: { 159 | none: 'none', 160 | 'gradient-to-t': 'linear-gradient(to top, var(--gradient-color-stops))', 161 | 'gradient-to-tr': 'linear-gradient(to top right, var(--gradient-color-stops))', 162 | 'gradient-to-r': 'linear-gradient(to right, var(--gradient-color-stops))', 163 | 'gradient-to-br': 'linear-gradient(to bottom right, var(--gradient-color-stops))', 164 | 'gradient-to-b': 'linear-gradient(to bottom, var(--gradient-color-stops))', 165 | 'gradient-to-bl': 'linear-gradient(to bottom left, var(--gradient-color-stops))', 166 | 'gradient-to-l': 'linear-gradient(to left, var(--gradient-color-stops))', 167 | 'gradient-to-tl': 'linear-gradient(to top left, var(--gradient-color-stops))', 168 | }, 169 | gradientColorStops: (theme) => theme('colors'), 170 | backgroundOpacity: (theme) => theme('opacity'), 171 | backgroundPosition: { 172 | bottom: 'bottom', 173 | center: 'center', 174 | left: 'left', 175 | 'left-bottom': 'left bottom', 176 | 'left-top': 'left top', 177 | right: 'right', 178 | 'right-bottom': 'right bottom', 179 | 'right-top': 'right top', 180 | top: 'top', 181 | }, 182 | backgroundSize: { 183 | auto: 'auto', 184 | cover: 'cover', 185 | contain: 'contain', 186 | }, 187 | borderColor: (theme) => ({ 188 | ...theme('colors'), 189 | default: theme('colors.gray.300', 'currentColor'), 190 | }), 191 | borderOpacity: (theme) => theme('opacity'), 192 | borderRadius: { 193 | none: '0', 194 | sm: '0.125rem', 195 | default: '0.25rem', 196 | md: '0.375rem', 197 | lg: '0.5rem', 198 | full: '9999px', 199 | }, 200 | borderWidth: { 201 | default: '1px', 202 | '0': '0', 203 | '2': '2px', 204 | '4': '4px', 205 | '8': '8px', 206 | }, 207 | boxShadow: { 208 | xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', 209 | sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', 210 | default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', 211 | md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', 212 | lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', 213 | xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', 214 | '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', 215 | inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', 216 | outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', 217 | none: 'none', 218 | }, 219 | container: {}, 220 | cursor: { 221 | auto: 'auto', 222 | default: 'default', 223 | pointer: 'pointer', 224 | wait: 'wait', 225 | text: 'text', 226 | move: 'move', 227 | 'not-allowed': 'not-allowed', 228 | }, 229 | divideColor: (theme) => theme('borderColor'), 230 | divideOpacity: (theme) => theme('borderOpacity'), 231 | divideWidth: (theme) => theme('borderWidth'), 232 | fill: { 233 | current: 'currentColor', 234 | }, 235 | flex: { 236 | '1': '1 1 0%', 237 | auto: '1 1 auto', 238 | initial: '0 1 auto', 239 | none: 'none', 240 | }, 241 | flexGrow: { 242 | '0': '0', 243 | default: '1', 244 | }, 245 | flexShrink: { 246 | '0': '0', 247 | default: '1', 248 | }, 249 | fontFamily: { 250 | sans: [ 251 | 'system-ui', 252 | '-apple-system', 253 | 'BlinkMacSystemFont', 254 | '"Segoe UI"', 255 | 'Roboto', 256 | '"Helvetica Neue"', 257 | 'Arial', 258 | '"Noto Sans"', 259 | 'sans-serif', 260 | '"Apple Color Emoji"', 261 | '"Segoe UI Emoji"', 262 | '"Segoe UI Symbol"', 263 | '"Noto Color Emoji"', 264 | ], 265 | serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], 266 | mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'], 267 | }, 268 | fontSize: { 269 | xs: '0.75rem', 270 | sm: '0.875rem', 271 | base: '1rem', 272 | lg: '1.125rem', 273 | xl: '1.25rem', 274 | '2xl': '1.5rem', 275 | '3xl': '1.875rem', 276 | '4xl': '2.25rem', 277 | '5xl': '3rem', 278 | '6xl': '4rem', 279 | }, 280 | fontWeight: { 281 | hairline: '100', 282 | thin: '200', 283 | light: '300', 284 | normal: '400', 285 | medium: '500', 286 | semibold: '600', 287 | bold: '700', 288 | extrabold: '800', 289 | black: '900', 290 | }, 291 | height: (theme) => ({ 292 | auto: 'auto', 293 | ...theme('spacing'), 294 | full: '100%', 295 | screen: '100vh', 296 | }), 297 | inset: { 298 | '0': '0', 299 | auto: 'auto', 300 | }, 301 | letterSpacing: { 302 | tighter: '-0.05em', 303 | tight: '-0.025em', 304 | normal: '0', 305 | wide: '0.025em', 306 | wider: '0.05em', 307 | widest: '0.1em', 308 | }, 309 | lineHeight: { 310 | none: '1', 311 | tight: '1.25', 312 | snug: '1.375', 313 | normal: '1.5', 314 | relaxed: '1.625', 315 | loose: '2', 316 | '3': '.75rem', 317 | '4': '1rem', 318 | '5': '1.25rem', 319 | '6': '1.5rem', 320 | '7': '1.75rem', 321 | '8': '2rem', 322 | '9': '2.25rem', 323 | '10': '2.5rem', 324 | }, 325 | listStyleType: { 326 | none: 'none', 327 | disc: 'disc', 328 | decimal: 'decimal', 329 | }, 330 | margin: (theme, { negative }) => ({ 331 | auto: 'auto', 332 | ...theme('spacing'), 333 | ...negative(theme('spacing')), 334 | }), 335 | maxHeight: { 336 | full: '100%', 337 | screen: '100vh', 338 | }, 339 | maxWidth: (theme, { breakpoints }) => ({ 340 | none: 'none', 341 | xs: '20rem', 342 | sm: '24rem', 343 | md: '28rem', 344 | lg: '32rem', 345 | xl: '36rem', 346 | '2xl': '42rem', 347 | '3xl': '48rem', 348 | '4xl': '56rem', 349 | '5xl': '64rem', 350 | '6xl': '72rem', 351 | full: '100%', 352 | ...breakpoints(theme('screens')), 353 | }), 354 | minHeight: { 355 | '0': '0', 356 | full: '100%', 357 | screen: '100vh', 358 | }, 359 | minWidth: { 360 | '0': '0', 361 | full: '100%', 362 | }, 363 | objectPosition: { 364 | bottom: 'bottom', 365 | center: 'center', 366 | left: 'left', 367 | 'left-bottom': 'left bottom', 368 | 'left-top': 'left top', 369 | right: 'right', 370 | 'right-bottom': 'right bottom', 371 | 'right-top': 'right top', 372 | top: 'top', 373 | }, 374 | opacity: { 375 | '0': '0', 376 | '25': '0.25', 377 | '50': '0.5', 378 | '75': '0.75', 379 | '100': '1', 380 | }, 381 | order: { 382 | first: '-9999', 383 | last: '9999', 384 | none: '0', 385 | '1': '1', 386 | '2': '2', 387 | '3': '3', 388 | '4': '4', 389 | '5': '5', 390 | '6': '6', 391 | '7': '7', 392 | '8': '8', 393 | '9': '9', 394 | '10': '10', 395 | '11': '11', 396 | '12': '12', 397 | }, 398 | padding: (theme) => theme('spacing'), 399 | placeholderColor: (theme) => theme('colors'), 400 | placeholderOpacity: (theme) => theme('opacity'), 401 | space: (theme, { negative }) => ({ 402 | ...theme('spacing'), 403 | ...negative(theme('spacing')), 404 | }), 405 | stroke: { 406 | current: 'currentColor', 407 | }, 408 | strokeWidth: { 409 | '0': '0', 410 | '1': '1', 411 | '2': '2', 412 | }, 413 | textColor: (theme) => theme('colors'), 414 | textOpacity: (theme) => theme('opacity'), 415 | width: (theme) => ({ 416 | auto: 'auto', 417 | ...theme('spacing'), 418 | '1/2': '50%', 419 | '1/3': '33.333333%', 420 | '2/3': '66.666667%', 421 | '1/4': '25%', 422 | '2/4': '50%', 423 | '3/4': '75%', 424 | '1/5': '20%', 425 | '2/5': '40%', 426 | '3/5': '60%', 427 | '4/5': '80%', 428 | '1/6': '16.666667%', 429 | '2/6': '33.333333%', 430 | '3/6': '50%', 431 | '4/6': '66.666667%', 432 | '5/6': '83.333333%', 433 | '1/12': '8.333333%', 434 | '2/12': '16.666667%', 435 | '3/12': '25%', 436 | '4/12': '33.333333%', 437 | '5/12': '41.666667%', 438 | '6/12': '50%', 439 | '7/12': '58.333333%', 440 | '8/12': '66.666667%', 441 | '9/12': '75%', 442 | '10/12': '83.333333%', 443 | '11/12': '91.666667%', 444 | full: '100%', 445 | screen: '100vw', 446 | }), 447 | zIndex: { 448 | auto: 'auto', 449 | '0': '0', 450 | '10': '10', 451 | '20': '20', 452 | '30': '30', 453 | '40': '40', 454 | '50': '50', 455 | }, 456 | gap: (theme) => theme('spacing'), 457 | gridTemplateColumns: { 458 | none: 'none', 459 | '1': 'repeat(1, minmax(0, 1fr))', 460 | '2': 'repeat(2, minmax(0, 1fr))', 461 | '3': 'repeat(3, minmax(0, 1fr))', 462 | '4': 'repeat(4, minmax(0, 1fr))', 463 | '5': 'repeat(5, minmax(0, 1fr))', 464 | '6': 'repeat(6, minmax(0, 1fr))', 465 | '7': 'repeat(7, minmax(0, 1fr))', 466 | '8': 'repeat(8, minmax(0, 1fr))', 467 | '9': 'repeat(9, minmax(0, 1fr))', 468 | '10': 'repeat(10, minmax(0, 1fr))', 469 | '11': 'repeat(11, minmax(0, 1fr))', 470 | '12': 'repeat(12, minmax(0, 1fr))', 471 | }, 472 | gridColumn: { 473 | auto: 'auto', 474 | 'span-1': 'span 1 / span 1', 475 | 'span-2': 'span 2 / span 2', 476 | 'span-3': 'span 3 / span 3', 477 | 'span-4': 'span 4 / span 4', 478 | 'span-5': 'span 5 / span 5', 479 | 'span-6': 'span 6 / span 6', 480 | 'span-7': 'span 7 / span 7', 481 | 'span-8': 'span 8 / span 8', 482 | 'span-9': 'span 9 / span 9', 483 | 'span-10': 'span 10 / span 10', 484 | 'span-11': 'span 11 / span 11', 485 | 'span-12': 'span 12 / span 12', 486 | }, 487 | gridColumnStart: { 488 | auto: 'auto', 489 | '1': '1', 490 | '2': '2', 491 | '3': '3', 492 | '4': '4', 493 | '5': '5', 494 | '6': '6', 495 | '7': '7', 496 | '8': '8', 497 | '9': '9', 498 | '10': '10', 499 | '11': '11', 500 | '12': '12', 501 | '13': '13', 502 | }, 503 | gridColumnEnd: { 504 | auto: 'auto', 505 | '1': '1', 506 | '2': '2', 507 | '3': '3', 508 | '4': '4', 509 | '5': '5', 510 | '6': '6', 511 | '7': '7', 512 | '8': '8', 513 | '9': '9', 514 | '10': '10', 515 | '11': '11', 516 | '12': '12', 517 | '13': '13', 518 | }, 519 | gridTemplateRows: { 520 | none: 'none', 521 | '1': 'repeat(1, minmax(0, 1fr))', 522 | '2': 'repeat(2, minmax(0, 1fr))', 523 | '3': 'repeat(3, minmax(0, 1fr))', 524 | '4': 'repeat(4, minmax(0, 1fr))', 525 | '5': 'repeat(5, minmax(0, 1fr))', 526 | '6': 'repeat(6, minmax(0, 1fr))', 527 | }, 528 | gridRow: { 529 | auto: 'auto', 530 | 'span-1': 'span 1 / span 1', 531 | 'span-2': 'span 2 / span 2', 532 | 'span-3': 'span 3 / span 3', 533 | 'span-4': 'span 4 / span 4', 534 | 'span-5': 'span 5 / span 5', 535 | 'span-6': 'span 6 / span 6', 536 | }, 537 | gridRowStart: { 538 | auto: 'auto', 539 | '1': '1', 540 | '2': '2', 541 | '3': '3', 542 | '4': '4', 543 | '5': '5', 544 | '6': '6', 545 | '7': '7', 546 | }, 547 | gridRowEnd: { 548 | auto: 'auto', 549 | '1': '1', 550 | '2': '2', 551 | '3': '3', 552 | '4': '4', 553 | '5': '5', 554 | '6': '6', 555 | '7': '7', 556 | }, 557 | transformOrigin: { 558 | center: 'center', 559 | top: 'top', 560 | 'top-right': 'top right', 561 | right: 'right', 562 | 'bottom-right': 'bottom right', 563 | bottom: 'bottom', 564 | 'bottom-left': 'bottom left', 565 | left: 'left', 566 | 'top-left': 'top left', 567 | }, 568 | scale: { 569 | '0': '0', 570 | '50': '.5', 571 | '75': '.75', 572 | '90': '.9', 573 | '95': '.95', 574 | '100': '1', 575 | '105': '1.05', 576 | '110': '1.1', 577 | '125': '1.25', 578 | '150': '1.5', 579 | }, 580 | rotate: { 581 | '-180': '-180deg', 582 | '-90': '-90deg', 583 | '-45': '-45deg', 584 | '0': '0', 585 | '45': '45deg', 586 | '90': '90deg', 587 | '180': '180deg', 588 | }, 589 | translate: (theme, { negative }) => ({ 590 | ...theme('spacing'), 591 | ...negative(theme('spacing')), 592 | '-full': '-100%', 593 | '-1/2': '-50%', 594 | '1/2': '50%', 595 | full: '100%', 596 | }), 597 | skew: { 598 | '-12': '-12deg', 599 | '-6': '-6deg', 600 | '-3': '-3deg', 601 | '0': '0', 602 | '3': '3deg', 603 | '6': '6deg', 604 | '12': '12deg', 605 | }, 606 | transitionProperty: { 607 | none: 'none', 608 | all: 'all', 609 | default: 610 | 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', 611 | colors: 'background-color, border-color, color, fill, stroke', 612 | opacity: 'opacity', 613 | shadow: 'box-shadow', 614 | transform: 'transform', 615 | }, 616 | transitionTimingFunction: { 617 | linear: 'linear', 618 | in: 'cubic-bezier(0.4, 0, 1, 1)', 619 | out: 'cubic-bezier(0, 0, 0.2, 1)', 620 | 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', 621 | }, 622 | transitionDuration: { 623 | '75': '75ms', 624 | '100': '100ms', 625 | '150': '150ms', 626 | '200': '200ms', 627 | '300': '300ms', 628 | '500': '500ms', 629 | '700': '700ms', 630 | '1000': '1000ms', 631 | }, 632 | transitionDelay: { 633 | '75': '75ms', 634 | '100': '100ms', 635 | '150': '150ms', 636 | '200': '200ms', 637 | '300': '300ms', 638 | '500': '500ms', 639 | '700': '700ms', 640 | '1000': '1000ms', 641 | }, 642 | animation: { 643 | none: 'none', 644 | spin: 'spin 1s linear infinite', 645 | ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', 646 | pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', 647 | bounce: 'bounce 1s infinite', 648 | }, 649 | keyframes: { 650 | spin: { 651 | to: { transform: 'rotate(360deg)' }, 652 | }, 653 | ping: { 654 | '75%, 100%': { transform: 'scale(2)', opacity: '0' }, 655 | }, 656 | pulse: { 657 | '50%': { opacity: '.5' }, 658 | }, 659 | bounce: { 660 | '0%, 100%': { 661 | transform: 'translateY(-25%)', 662 | animationTimingFunction: 'cubic-bezier(0.8,0,1,1)', 663 | }, 664 | '50%': { 665 | transform: 'none', 666 | animationTimingFunction: 'cubic-bezier(0,0,0.2,1)', 667 | }, 668 | }, 669 | }, 670 | }, 671 | variants: { 672 | accessibility: ['responsive', 'focus'], 673 | alignContent: ['responsive'], 674 | alignItems: ['responsive'], 675 | alignSelf: ['responsive'], 676 | appearance: ['responsive'], 677 | backgroundAttachment: ['responsive'], 678 | backgroundClip: ['responsive'], 679 | backgroundColor: ['responsive', 'hover', 'focus'], 680 | backgroundImage: ['responsive'], 681 | gradientColorStops: ['responsive', 'hover', 'focus'], 682 | backgroundOpacity: ['responsive', 'hover', 'focus'], 683 | backgroundPosition: ['responsive'], 684 | backgroundRepeat: ['responsive'], 685 | backgroundSize: ['responsive'], 686 | borderCollapse: ['responsive'], 687 | borderColor: ['responsive', 'hover', 'focus'], 688 | borderOpacity: ['responsive', 'hover', 'focus'], 689 | borderRadius: ['responsive'], 690 | borderStyle: ['responsive'], 691 | borderWidth: ['responsive'], 692 | boxShadow: ['responsive', 'hover', 'focus'], 693 | boxSizing: ['responsive'], 694 | container: ['responsive'], 695 | cursor: ['responsive'], 696 | display: ['responsive'], 697 | divideColor: ['responsive'], 698 | divideOpacity: ['responsive'], 699 | divideStyle: ['responsive'], 700 | divideWidth: ['responsive'], 701 | fill: ['responsive'], 702 | flex: ['responsive'], 703 | flexDirection: ['responsive'], 704 | flexGrow: ['responsive'], 705 | flexShrink: ['responsive'], 706 | flexWrap: ['responsive'], 707 | float: ['responsive'], 708 | clear: ['responsive'], 709 | fontFamily: ['responsive'], 710 | fontSize: ['responsive'], 711 | fontSmoothing: ['responsive'], 712 | fontVariantNumeric: ['responsive'], 713 | fontStyle: ['responsive'], 714 | fontWeight: ['responsive', 'hover', 'focus'], 715 | height: ['responsive'], 716 | inset: ['responsive'], 717 | justifyContent: ['responsive'], 718 | justifyItems: ['responsive'], 719 | justifySelf: ['responsive'], 720 | letterSpacing: ['responsive'], 721 | lineHeight: ['responsive'], 722 | listStylePosition: ['responsive'], 723 | listStyleType: ['responsive'], 724 | margin: ['responsive'], 725 | maxHeight: ['responsive'], 726 | maxWidth: ['responsive'], 727 | minHeight: ['responsive'], 728 | minWidth: ['responsive'], 729 | objectFit: ['responsive'], 730 | objectPosition: ['responsive'], 731 | opacity: ['responsive', 'hover', 'focus'], 732 | order: ['responsive'], 733 | outline: ['responsive', 'focus'], 734 | overflow: ['responsive'], 735 | overscrollBehavior: ['responsive'], 736 | padding: ['responsive'], 737 | placeContent: ['responsive'], 738 | placeItems: ['responsive'], 739 | placeSelf: ['responsive'], 740 | placeholderColor: ['responsive', 'focus'], 741 | placeholderOpacity: ['responsive', 'focus'], 742 | pointerEvents: ['responsive'], 743 | position: ['responsive'], 744 | resize: ['responsive'], 745 | space: ['responsive'], 746 | stroke: ['responsive'], 747 | strokeWidth: ['responsive'], 748 | tableLayout: ['responsive'], 749 | textAlign: ['responsive'], 750 | textColor: ['responsive', 'hover', 'focus'], 751 | textOpacity: ['responsive', 'hover', 'focus'], 752 | textDecoration: ['responsive', 'hover', 'focus'], 753 | textTransform: ['responsive'], 754 | userSelect: ['responsive'], 755 | verticalAlign: ['responsive'], 756 | visibility: ['responsive'], 757 | whitespace: ['responsive'], 758 | width: ['responsive'], 759 | wordBreak: ['responsive'], 760 | zIndex: ['responsive'], 761 | gap: ['responsive'], 762 | gridAutoFlow: ['responsive'], 763 | gridTemplateColumns: ['responsive'], 764 | gridColumn: ['responsive'], 765 | gridColumnStart: ['responsive'], 766 | gridColumnEnd: ['responsive'], 767 | gridTemplateRows: ['responsive'], 768 | gridRow: ['responsive'], 769 | gridRowStart: ['responsive'], 770 | gridRowEnd: ['responsive'], 771 | transform: ['responsive'], 772 | transformOrigin: ['responsive'], 773 | scale: ['responsive', 'hover', 'focus'], 774 | rotate: ['responsive', 'hover', 'focus'], 775 | translate: ['responsive', 'hover', 'focus'], 776 | skew: ['responsive', 'hover', 'focus'], 777 | transitionProperty: ['responsive'], 778 | transitionTimingFunction: ['responsive'], 779 | transitionDuration: ['responsive'], 780 | transitionDelay: ['responsive'], 781 | animation: ['responsive'], 782 | }, 783 | corePlugins: {}, 784 | plugins: [], 785 | } 786 | -------------------------------------------------------------------------------- /demo/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | let tailwindcssCapsize 4 | try { 5 | tailwindcssCapsize = require('../src/index.js') 6 | } catch (e) { 7 | if (e instanceof Error && e.code === 'MODULE_NOT_FOUND') { 8 | tailwindcssCapsize = require('@themosaad/tailwindcss-capsize') 9 | } else throw e 10 | } 11 | 12 | module.exports = { 13 | purge: { 14 | enabled: true, 15 | content: ['./**/*.js'], 16 | // These options are passed through directly to PurgeCSS 17 | options: { 18 | safelist: [ 19 | 'font-sans', 20 | 'font-source', 21 | 'font-ubuntu-mono', 22 | 'text-xs', 23 | 'text-sm', 24 | 'text-base', 25 | 'text-lg', 26 | 'text-xl', 27 | 'text-2xl', 28 | 'text-3xl', 29 | 'text-4xl', 30 | 'text-5xl', 31 | 'text-6xl', 32 | 'text-7xl', 33 | 'text-8xl', 34 | 'text-9xl', 35 | 'leading-3', 36 | 'leading-4', 37 | 'leading-5', 38 | 'leading-6', 39 | 'leading-7', 40 | 'leading-8', 41 | 'leading-9', 42 | 'leading-10', 43 | 'leading-none', 44 | 'leading-tight', 45 | 'leading-snug', 46 | 'leading-normal', 47 | 'leading-relaxed', 48 | 'leading-loose', 49 | 'cap-height-2', 50 | 'cap-height-2.5', 51 | 'cap-height-3', 52 | 'cap-height-3.5', 53 | 'cap-height-4', 54 | 'cap-height-5', 55 | 'cap-height-6', 56 | 'cap-height-7', 57 | 'cap-height-8', 58 | 'cap-height-9', 59 | 'cap-height-10', 60 | 'cap-height-11', 61 | 'cap-height-12', 62 | 'cap-height-13', 63 | 'cap-height-14', 64 | 'cap-height-15', 65 | 'line-gap-0', 66 | 'line-gap-2', 67 | 'line-gap-2.5', 68 | 'line-gap-3', 69 | 'line-gap-3.5', 70 | 'line-gap-4', 71 | 'line-gap-5', 72 | 'line-gap-6', 73 | 'line-gap-7', 74 | 'line-gap-8', 75 | 'line-gap-9', 76 | 'line-gap-10', 77 | 'line-gap-11', 78 | 'line-gap-12', 79 | ], 80 | }, 81 | }, 82 | theme: { 83 | fontFamily: { 84 | sans: ['Inter var', ...defaultTheme.fontFamily.sans], 85 | source: ['Source Sans Pro', ...defaultTheme.fontFamily.sans], 86 | 'ubuntu-mono': ['Ubuntu Mono', ...defaultTheme.fontFamily.mono], 87 | system: defaultTheme.fontFamily.sans, 88 | }, 89 | capsize: { 90 | fontMetrics: { 91 | sans: { 92 | capHeight: 2048, 93 | ascent: 2728, 94 | descent: -680, 95 | lineGap: 0, 96 | unitsPerEm: 2816, 97 | }, 98 | source: { 99 | capHeight: 710, 100 | ascent: 980, 101 | descent: -270, 102 | lineGap: 0, 103 | unitsPerEm: 1000, 104 | }, 105 | 'ubuntu-mono': { 106 | capHeight: 693, 107 | ascent: 830, 108 | descent: -170, 109 | lineGap: 0, 110 | unitsPerEm: 1000, 111 | }, 112 | }, 113 | rootFontSizePx: { 114 | default: 16, 115 | sm: 18, 116 | lg: 20, 117 | }, 118 | // rootLineHeightUnitless: 1.5, 119 | // rootLineGapUnitless: 0.5, 120 | // className: 'capsize', 121 | // keepPadding: true, 122 | }, 123 | capHeight: { 124 | 2: ['0.5rem', { lineGap: '0.5rem' }], 125 | 2.5: ['0.625rem', { lineGap: '0.625rem' }], 126 | 3: ['0.75rem', { lineGap: '0.75rem' }], 127 | 3.5: ['0.875rem', { lineGap: '0.875rem' }], 128 | 4: ['1rem', { lineGap: '1rem' }], 129 | 5: ['1.25rem', { lineGap: '1.25rem' }], 130 | 6: ['1.5rem', { lineGap: '1.5rem' }], 131 | 7: ['1.75rem', { lineGap: '1.75rem' }], 132 | 8: ['2rem', { lineGap: '2rem' }], 133 | 9: ['2.25rem', { lineGap: '2.25rem' }], 134 | 10: ['2.5rem', { lineGap: '2.5rem' }], 135 | 11: ['2.75rem', { lineGap: '2.75rem' }], 136 | 12: ['3rem', { lineGap: '3rem' }], 137 | 13: ['3.25rem', { lineGap: '3.25rem' }], 138 | 14: ['3.5rem', { lineGap: '3.5rem' }], 139 | 15: ['3.75rem', { lineGap: '3.55rem' }], 140 | }, 141 | lineGap: { 142 | 0: '0', 143 | 2: '0.5rem', 144 | 2.5: '0.625rem', 145 | 3: '0.75rem', 146 | 3.5: '0.875rem', 147 | 4: '1rem', 148 | 5: '1.25rem', 149 | 6: '1.5rem', 150 | 7: '1.75rem', 151 | 8: '2rem', 152 | 9: '2.25rem', 153 | 10: '2.5rem', 154 | 11: '2.75rem', 155 | 12: '3rem', 156 | }, 157 | }, 158 | variants: {}, 159 | plugins: [tailwindcssCapsize], 160 | } 161 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@themosaad/tailwindcss-capsize", 3 | "version": "1.0.0", 4 | "description": "A Tailwind CSS plugin for trimming the whitespace above and below text nodes. This is a port of Capsize.", 5 | "main": "src/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/theMosaad/tailwindcss-capsize.git" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "tailwindcss", 13 | "capsize", 14 | "leading trim", 15 | "leading", 16 | "typography", 17 | "baseline" 18 | ], 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "author": "Mosaad (https://themosaad.com)", 23 | "scripts": { 24 | "release": "np" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/theMosaad/tailwindcss-capsize/issues" 28 | }, 29 | "homepage": "https://github.com/theMosaad/tailwindcss-capsize#readme", 30 | "dependencies": { 31 | "lodash": "^4.17.20" 32 | }, 33 | "devDependencies": { 34 | "np": "^6.5.0", 35 | "tailwindcss": "^2.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | printWidth: 100, 5 | trailingComma: 'es5', 6 | arrowParens: 'always', 7 | tabWidth: 2, 8 | useTabs: false, 9 | quoteProps: 'as-needed', 10 | jsxSingleQuote: false, 11 | bracketSpacing: true, 12 | jsxBracketSameLine: false, 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const plugin = require('tailwindcss/plugin') 2 | const _ = require('lodash') 3 | 4 | module.exports = plugin( 5 | function ({ addBase, addUtilities, e, theme, variants }) { 6 | const screens = theme('screens', {}) 7 | const minWidths = extractMinWidths(screens) 8 | const rootSizes = mapMinWidthsToRootSize( 9 | minWidths, 10 | screens, 11 | theme('capsize.rootFontSizePx', { default: 16 }) 12 | ) 13 | 14 | const generateRootFontSizeFor = (minWidth) => { 15 | const fontSizeConfig = _.find( 16 | rootSizes, 17 | (rootSize) => `${rootSize.minWidth}` === `${minWidth}` 18 | ) 19 | 20 | if (!fontSizeConfig || !fontSizeConfig.rootSize) { 21 | return {} 22 | } 23 | 24 | return { 25 | 'font-size': fontSizeConfig.rootSize + 'px', 26 | '--root-font-size-px': fontSizeConfig.rootSize.toString(), 27 | } 28 | } 29 | const atRules = _(minWidths) 30 | .sortBy((minWidth) => parseInt(minWidth)) 31 | .sortedUniq() 32 | .map((minWidth) => { 33 | return { 34 | [`@media (min-width: ${minWidth})`]: { 35 | html: { 36 | ...generateRootFontSizeFor(minWidth), 37 | }, 38 | }, 39 | } 40 | }) 41 | .value() 42 | 43 | addBase([ 44 | { 45 | html: { 46 | ...generateRootFontSizeFor(0), 47 | 'line-height': theme('capsize.rootLineHeightUnitless', '1.5').toString(), 48 | '--line-height-unitless': theme('capsize.rootLineHeightUnitless', '1.5').toString(), 49 | '--line-gap-unitless': theme('capsize.rootLineGapUnitless', '0.5').toString(), 50 | ...(theme('capsize.keepPadding') 51 | ? { 52 | '--prevent-collapse': '0.05', 53 | } 54 | : {}), 55 | }, 56 | }, 57 | ...atRules, 58 | ]) 59 | 60 | const capsizeContent = { 61 | ...(theme('capsize.keepPadding') 62 | ? { 63 | 'padding-top': 'calc(1px * var(--prevent-collapse))', 64 | 'padding-bottom': 'calc(1px * var(--prevent-collapse))', 65 | 'padding-right': '0', 66 | 'padding-left': '0', 67 | } 68 | : {}), 69 | '&::before': { 70 | content: '""', 71 | ...(theme('capsize.keepPadding') 72 | ? { 73 | display: 'block', 74 | height: '0', 75 | } 76 | : { 77 | display: 'table', 78 | }), 79 | '--line-height-normal': 'calc(var(--line-height-scale) * var(--font-size-px))', 80 | '--specified-line-height-offset-double': 81 | 'calc(var(--line-height-normal) - var(--line-height-px))', 82 | '--specified-line-height-offset': 'calc(var(--specified-line-height-offset-double) / 2 )', 83 | '--specified-line-height-offset-to-scale': 84 | 'calc(var(--specified-line-height-offset) / var(--font-size-px))', 85 | '--line-gap-scale-half': 'calc(var(--line-gap-scale) / 2)', 86 | ...(theme('capsize.keepPadding') 87 | ? { 88 | '--prevent-collapse-to-scale': 'calc(var(--prevent-collapse) / var(--font-size-px))', 89 | '--leading-trim-top': 90 | 'calc( var(--ascent-scale) - var(--cap-height-scale) + var(--line-gap-scale-half) - var(--specified-line-height-offset-to-scale) + var(--prevent-collapse-to-scale) )', 91 | 'margin-top': 'calc(-1em * var(--leading-trim-top))', 92 | } 93 | : { 94 | '--leading-trim-top': 95 | 'calc( var(--ascent-scale) - var(--cap-height-scale) + var(--line-gap-scale-half) - var(--specified-line-height-offset-to-scale) )', 96 | 'margin-bottom': 'calc(-1em * var(--leading-trim-top))', 97 | }), 98 | }, 99 | '&::after': { 100 | content: '""', 101 | ...(theme('capsize.keepPadding') 102 | ? { 103 | display: 'block', 104 | height: '0', 105 | } 106 | : { 107 | display: 'table', 108 | }), 109 | '--line-height-normal': 'calc(var(--line-height-scale) * var(--font-size-px))', 110 | '--specified-line-height-offset-double': 111 | 'calc(var(--line-height-normal) - var(--line-height-px))', 112 | '--specified-line-height-offset': 'calc(var(--specified-line-height-offset-double) / 2 )', 113 | '--specified-line-height-offset-to-scale': 114 | 'calc(var(--specified-line-height-offset) / var(--font-size-px))', 115 | '--prevent-collapse-to-scale': 'calc(var(--prevent-collapse) / var(--font-size-px))', 116 | '--line-gap-scale-half': 'calc(var(--line-gap-scale) / 2)', 117 | ...(theme('capsize.keepPadding') 118 | ? { 119 | '--leading-trim-bottom': 120 | 'calc( var(--descent-scale) + var(--line-gap-scale-half) - var(--specified-line-height-offset-to-scale) + var(--prevent-collapse-to-scale) )', 121 | 'margin-bottom': 'calc(-1em * var(--leading-trim-bottom))', 122 | } 123 | : { 124 | '--leading-trim-bottom': 125 | 'calc( var(--descent-scale) + var(--line-gap-scale-half) - var(--specified-line-height-offset-to-scale) )', 126 | 'margin-top': 'calc(-1em * var(--leading-trim-bottom))', 127 | }), 128 | }, 129 | } 130 | 131 | if (theme('capsize.className')) { 132 | const capsizeUtilities = { 133 | [`.${e(theme('capsize.className'))}`]: { 134 | ...capsizeContent, 135 | }, 136 | } 137 | 138 | addUtilities(capsizeUtilities) 139 | } 140 | 141 | const fontFamilyUtilities = _.fromPairs( 142 | _.map(theme('fontFamily'), (value, modifier) => { 143 | return [ 144 | `.${e(`font-${modifier}`)}`, 145 | { 146 | 'font-family': Array.isArray(value) ? value.join(', ') : value, 147 | ...(theme(`capsize.fontMetrics.${modifier}`) 148 | ? { 149 | '--cap-height': theme(`capsize.fontMetrics.${modifier}.capHeight`).toString(), 150 | '--ascent': theme(`capsize.fontMetrics.${modifier}.ascent`).toString(), 151 | '--descent': theme(`capsize.fontMetrics.${modifier}.descent`).toString(), 152 | '--line-gap': theme(`capsize.fontMetrics.${modifier}.lineGap`).toString(), 153 | '--units-per-em': theme(`capsize.fontMetrics.${modifier}.unitsPerEm`).toString(), 154 | 155 | '--absolute-descent': Math.abs( 156 | theme(`capsize.fontMetrics.${modifier}.descent`) 157 | ).toString(), 158 | '--cap-height-scale': ( 159 | theme(`capsize.fontMetrics.${modifier}.capHeight`) / 160 | theme(`capsize.fontMetrics.${modifier}.unitsPerEm`) 161 | ).toString(), 162 | '--descent-scale': ( 163 | Math.abs(theme(`capsize.fontMetrics.${modifier}.descent`)) / 164 | theme(`capsize.fontMetrics.${modifier}.unitsPerEm`) 165 | ).toString(), 166 | '--ascent-scale': ( 167 | theme(`capsize.fontMetrics.${modifier}.ascent`) / 168 | theme(`capsize.fontMetrics.${modifier}.unitsPerEm`) 169 | ).toString(), 170 | '--line-gap-scale': ( 171 | theme(`capsize.fontMetrics.${modifier}.lineGap`) / 172 | theme(`capsize.fontMetrics.${modifier}.unitsPerEm`) 173 | ).toString(), 174 | '--line-height-scale': ( 175 | (theme(`capsize.fontMetrics.${modifier}.ascent`) + 176 | theme(`capsize.fontMetrics.${modifier}.lineGap`) + 177 | Math.abs(theme(`capsize.fontMetrics.${modifier}.descent`))) / 178 | theme(`capsize.fontMetrics.${modifier}.unitsPerEm`) 179 | ).toString(), 180 | } 181 | : {}), 182 | }, 183 | ] 184 | }) 185 | ) 186 | 187 | addUtilities(fontFamilyUtilities, variants('fontFamily')) 188 | 189 | const fontSizeUtilities = _.fromPairs( 190 | _.map(theme('fontSize'), (value, modifier) => { 191 | const [fontSize, options] = Array.isArray(value) ? value : [value] 192 | const { lineHeight, letterSpacing } = _.isPlainObject(options) 193 | ? options 194 | : { 195 | lineHeight: options, 196 | } 197 | 198 | return [ 199 | `.${e(`text-${modifier}`)}`, 200 | { 201 | 'font-size': fontSize, 202 | ...(fontSize.endsWith('rem') 203 | ? { 204 | '--font-size-rem': fontSize.replace('rem', ''), 205 | '--font-size-px': 'calc(var(--font-size-rem) * var(--root-font-size-px))', 206 | } 207 | : fontSize.endsWith('px') 208 | ? { '--font-size-px': fontSize.replace('px', '') } 209 | : {}), 210 | ...(lineHeight === undefined 211 | ? { 212 | '--line-height-px': 'calc(var(--line-height-unitless) * var(--font-size-px))', 213 | } 214 | : { 215 | 'line-height': lineHeight, 216 | ...(lineHeight.endsWith('rem') 217 | ? { 218 | '--line-height-rem': lineHeight.replace('rem', ''), 219 | '--line-height-px': 220 | 'calc(var(--line-height-rem) * var(--root-font-size-px))', 221 | } 222 | : lineHeight.endsWith('px') 223 | ? { '--line-height-px': lineHeight.replace('px', '') } 224 | : !isNaN(parseFloat(lineHeight)) && isFinite(lineHeight) 225 | ? { 226 | '--line-height-unitless': lineHeight, 227 | '--line-height-px': 228 | 'calc(var(--line-height-unitless) * var(--font-size-px))', 229 | } 230 | : {}), 231 | }), 232 | ...(theme('capsize.className') === undefined 233 | ? { 234 | ...capsizeContent, 235 | } 236 | : {}), 237 | ...(letterSpacing === undefined 238 | ? {} 239 | : { 240 | 'letter-spacing': letterSpacing, 241 | }), 242 | }, 243 | ] 244 | }) 245 | ) 246 | 247 | addUtilities(fontSizeUtilities, variants('fontSize')) 248 | 249 | const capHeightUtilities = _.fromPairs( 250 | _.map(theme('capHeight'), (value, modifier) => { 251 | const [capHeight, options] = Array.isArray(value) ? value : [value] 252 | const { lineGap, letterSpacing } = _.isPlainObject(options) 253 | ? options 254 | : { 255 | lineGap: options, 256 | } 257 | 258 | return [ 259 | `.${e(`cap-height-${modifier}`)}`, 260 | { 261 | 'font-size': 'calc(1px * var(--font-size-px))', 262 | ...(capHeight.endsWith('rem') 263 | ? { 264 | '--cap-height-rem': capHeight.replace('rem', ''), 265 | '--cap-height-px': 'calc(var(--cap-height-rem) * var(--root-font-size-px))', 266 | '--font-size-px': 'calc(var(--cap-height-px) / var(--cap-height-scale))', 267 | } 268 | : capHeight.endsWith('px') 269 | ? { 270 | '--cap-height-px': capHeight.replace('px', ''), 271 | '--font-size-px': 'calc(var(--cap-height-px) / var(--cap-height-scale))', 272 | } 273 | : {}), 274 | 'line-height': 'calc(1px * var(--line-height-px))', 275 | ...(lineGap === undefined 276 | ? { 277 | '--line-gap-px': 'calc(var(--cap-height-px) * var(--line-gap-unitless))', 278 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 279 | } 280 | : { 281 | ...(lineGap.endsWith('rem') 282 | ? { 283 | '--line-gap-rem': lineGap.replace('rem', ''), 284 | '--line-gap-px': 'calc(var(--line-gap-rem) * var(--root-font-size-px))', 285 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 286 | } 287 | : lineGap.endsWith('px') 288 | ? { 289 | '--line-gap-px': lineGap.replace('px', ''), 290 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 291 | } 292 | : !isNaN(parseFloat(lineGap)) && isFinite(lineGap) 293 | ? { 294 | '--line-gap-unitless': lineGap, 295 | '--line-gap-px': 'calc(var(--cap-height-px) * var(--line-gap-unitless))', 296 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 297 | } 298 | : {}), 299 | }), 300 | ...capsizeContent, 301 | }, 302 | ] 303 | }) 304 | ) 305 | 306 | addUtilities(capHeightUtilities, variants('capHeight')) 307 | 308 | const lineHeightUtilities = _.fromPairs( 309 | _.map(theme('lineHeight'), (lineHeight, modifier) => { 310 | return [ 311 | `.${e(`leading-${modifier}`)}`, 312 | { 313 | 'line-height': lineHeight, 314 | ...(lineHeight.endsWith('rem') 315 | ? { 316 | '--line-height-rem': lineHeight.replace('rem', ''), 317 | '--line-height-px': 'calc(var(--line-height-rem) * var(--root-font-size-px))', 318 | } 319 | : lineHeight.endsWith('px') 320 | ? { '--line-height-px': lineHeight.replace('px', '') } 321 | : !isNaN(parseFloat(lineHeight)) && isFinite(lineHeight) 322 | ? { 323 | '--line-height-unitless': lineHeight, 324 | '--line-height-px': 'calc(var(--line-height-unitless) * var(--font-size-px))', 325 | } 326 | : {}), 327 | }, 328 | ] 329 | }) 330 | ) 331 | 332 | addUtilities(lineHeightUtilities, variants('lineHeight')) 333 | 334 | const lineGapUtilities = _.fromPairs( 335 | _.map(theme('lineGap'), (lineGap, modifier) => { 336 | return [ 337 | `.${e(`line-gap-${modifier}`)}`, 338 | { 339 | 'line-height': 'calc(1px * var(--line-height-px))', 340 | ...(lineGap.endsWith('rem') 341 | ? { 342 | '--line-gap-rem': lineGap.replace('rem', ''), 343 | '--line-gap-px': 'calc(var(--line-gap-rem) * var(--root-font-size-px))', 344 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 345 | } 346 | : lineGap.endsWith('px') 347 | ? { 348 | '--line-gap-px': lineGap.replace('px', ''), 349 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 350 | } 351 | : !isNaN(parseFloat(lineGap)) && isFinite(lineGap) 352 | ? { 353 | '--line-gap-unitless': lineGap, 354 | '--line-gap-px': 'calc(var(--cap-height-px) * var(--line-gap-unitless))', 355 | '--line-height-px': 'calc(var(--cap-height-px) + var(--line-gap-px))', 356 | } 357 | : {}), 358 | }, 359 | ] 360 | }) 361 | ) 362 | 363 | addUtilities(lineGapUtilities, variants('lineGap')) 364 | }, 365 | { 366 | corePlugins: { 367 | fontFamily: false, 368 | fontSize: false, 369 | lineHeight: false, 370 | }, 371 | variants: { 372 | capHeight: ['responsive'], 373 | lineGap: ['responsive'], 374 | }, 375 | theme: { 376 | capHeight: { 377 | 2: ['0.5rem', { lineGap: '0.5rem' }], 378 | 2.5: ['0.625rem', { lineGap: '0.625rem' }], 379 | 3: ['0.75rem', { lineGap: '0.75rem' }], 380 | 3.5: ['0.875rem', { lineGap: '0.875rem' }], 381 | 4: ['1rem', { lineGap: '1rem' }], 382 | 5: ['1.25rem', { lineGap: '1.25rem' }], 383 | 6: ['1.5rem', { lineGap: '1.5rem' }], 384 | 7: ['1.75rem', { lineGap: '1.75rem' }], 385 | 8: ['2rem', { lineGap: '2rem' }], 386 | 9: ['2.25rem', { lineGap: '2.25rem' }], 387 | 10: ['2.5rem', { lineGap: '2.5rem' }], 388 | 11: ['2.75rem', { lineGap: '2.75rem' }], 389 | 12: ['3rem', { lineGap: '3rem' }], 390 | 13: ['3.25rem', { lineGap: '3.25rem' }], 391 | 14: ['3.5rem', { lineGap: '3.5rem' }], 392 | 15: ['3.75rem', { lineGap: '3.55rem' }], 393 | }, 394 | lineGap: { 395 | 0: '0', 396 | 2: '0.5rem', 397 | 2.5: '0.625rem', 398 | 3: '0.75rem', 399 | 3.5: '0.875rem', 400 | 4: '1rem', 401 | 5: '1.25rem', 402 | 6: '1.5rem', 403 | 7: '1.75rem', 404 | 8: '2rem', 405 | 9: '2.25rem', 406 | 10: '2.5rem', 407 | 11: '2.75rem', 408 | 12: '3rem', 409 | }, 410 | }, 411 | } 412 | ) 413 | 414 | function extractMinWidths(breakpoints) { 415 | return _.flatMap(breakpoints, (breakpoints) => { 416 | if (_.isString(breakpoints)) { 417 | breakpoints = { min: breakpoints } 418 | } 419 | 420 | if (!Array.isArray(breakpoints)) { 421 | breakpoints = [breakpoints] 422 | } 423 | 424 | return _(breakpoints) 425 | .filter((breakpoint) => { 426 | return _.has(breakpoint, 'min') || _.has(breakpoint, 'min-width') 427 | }) 428 | .map((breakpoint) => { 429 | return _.get(breakpoint, 'min-width', breakpoint.min) 430 | }) 431 | .value() 432 | }) 433 | } 434 | 435 | function mapMinWidthsToRootSize(minWidths, screens, rootSizes) { 436 | if (typeof rootSizes === 'undefined') { 437 | return [] 438 | } 439 | 440 | if (!_.isObject(rootSizes)) { 441 | return [ 442 | { 443 | screen: 'default', 444 | minWidth: 0, 445 | rootSize: rootSizes, 446 | }, 447 | ] 448 | } 449 | 450 | const mapping = [] 451 | 452 | if (rootSizes.default) { 453 | mapping.push({ 454 | screen: 'default', 455 | minWidth: 0, 456 | rootSize: rootSizes.default, 457 | }) 458 | } 459 | 460 | _.each(minWidths, (minWidth) => { 461 | Object.keys(screens).forEach((screen) => { 462 | if (`${screens[screen]}` === `${minWidth}`) { 463 | mapping.push({ 464 | screen, 465 | minWidth, 466 | rootSize: rootSizes[screen], 467 | }) 468 | } 469 | }) 470 | }) 471 | 472 | return mapping 473 | } 474 | --------------------------------------------------------------------------------