├── .eleventy.js ├── .github ├── labeler.yml ├── launch.json └── workflows │ ├── labeler.yml │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── feo.css ├── feo.min.css ├── netlify.toml ├── package.json ├── public ├── demo.css ├── favicon.ico └── feo.min.css ├── site ├── _includes │ ├── base.njk │ ├── partials │ │ ├── breadcrumb.njk │ │ ├── footer.njk │ │ ├── navigation.njk │ │ └── sublist.njk │ └── svg │ │ ├── center.svg │ │ ├── cluster.svg │ │ ├── logo.svg │ │ ├── pancake.svg │ │ ├── repel.svg │ │ ├── sidebar.svg │ │ ├── switcher.svg │ │ └── tiles.svg ├── blocks.md ├── blocks │ ├── accordion.md │ ├── forms.md │ ├── table.md │ ├── toggle.md │ └── tooltip.md ├── index.md ├── layouts.md ├── layouts │ ├── center.md │ ├── cluster.md │ ├── equal.md │ ├── fifty.md │ ├── flexbox.md │ ├── grid.md │ ├── pancake.md │ ├── pile.md │ ├── repel.md │ ├── sidebar.md │ ├── switcher.md │ └── tiles.md ├── tokens.md ├── utilities.md └── utilities │ ├── click-area.md │ ├── contrast.md │ ├── counted.md │ ├── hover-group.md │ ├── indexed.md │ ├── margins.md │ ├── max-width.md │ ├── read-more.md │ ├── scroll-container.md │ ├── typography.md │ └── visually-hidden.md ├── src ├── blocks │ ├── accordion.css │ ├── form.css │ ├── table.css │ ├── toggle.css │ └── tooltip.css ├── global │ ├── global.css │ ├── reset.css │ └── tokens.css ├── index.css ├── layout │ ├── center.css │ ├── cluster.css │ ├── equal.css │ ├── fifty.css │ ├── flex.css │ ├── grid.css │ ├── pancake.css │ ├── pile.css │ ├── repel.css │ ├── sidebar.css │ ├── stack.css │ ├── switcher.css │ ├── tiles.css │ └── utilities.css └── utilities │ ├── animations.css │ ├── click-area.css │ ├── contrast.css │ ├── counted.css │ ├── darken.css │ ├── hover-group.css │ ├── indexed.css │ ├── loading.css │ ├── margin.css │ ├── read-more.css │ ├── scroll-container.css │ ├── typography.css │ ├── visually-hidden.css │ ├── width.css │ └── z-index.css └── yarn.lock /.eleventy.js: -------------------------------------------------------------------------------- 1 | function sort(key) { 2 | return function (a, b) { 3 | return a.data[key] < b.data[key] ? -1 : 1; 4 | }; 5 | } 6 | 7 | function getNavigation(collection) { 8 | const items = {}; 9 | 10 | // main navigation items 11 | let _c = collection.filter((c) => !c.data.subkey); 12 | for (let i in _c) { 13 | const item = _c[i].data; 14 | items[item.key] = { 15 | title: item.title, 16 | order: item.order, 17 | url: item.page.url, 18 | sub: [], 19 | }; 20 | } 21 | 22 | // sub items 23 | _c = collection.filter((c) => c.data.subkey).sort(sort("title")); 24 | for (let i in _c) { 25 | const item = _c[i].data; 26 | items[item.key].sub.push({ 27 | title: item.title, 28 | url: item.page.url, 29 | key: item.subkey, 30 | }); 31 | } 32 | return Object.entries(items).sort((a, b) => 33 | Math.sign(a[1].order - b[1].order) 34 | ); 35 | } 36 | 37 | function getSubitems(collection, key) { 38 | return collection 39 | .filter((c) => c.data.key === key && c.data.subkey) 40 | .sort(sort("title")); 41 | } 42 | 43 | module.exports = (config) => { 44 | config.addPassthroughCopy({ "./public/": "/" }); 45 | config.addFilter("navigation", getNavigation); 46 | config.addFilter("subitems", getSubitems); 47 | 48 | return { 49 | markdownTemplateEngine: "njk", 50 | dataTemplateEngine: "njk", 51 | htmlTemplateEngine: "njk", 52 | dir: { 53 | input: "site", 54 | output: "_site", 55 | }, 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | documentation: 2 | - any: ['docs/**/*'] 3 | 4 | enhancement: 5 | - any: ['src/**/*'] -------------------------------------------------------------------------------- /.github/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Jest Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeArgs": [ 9 | "--inspect-brk", 10 | "${workspaceRoot}/node_modules/.bin/jest", 11 | "--runInBand", 12 | "--coverage", 13 | "false" 14 | ], 15 | "console": "integratedTerminal", 16 | "internalConsoleOptions": "neverOpen" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | triage: 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/labeler@v4 13 | with: 14 | repo-token: "${{ secrets.GITHUB_TOKEN }}" -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-npm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | registry-url: https://registry.npmjs.org/ 16 | - name: yarn publish 17 | run: | 18 | yarn config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN 19 | npm publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.* 3 | *.scssc 4 | *.log 5 | *.swp 6 | .DS_Store 7 | .sass-cache 8 | node_modules 9 | 10 | _site 11 | yarn-error.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kevin Pennekamp 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 | # Feo CSS: A tiny CSS framework 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | Feo.css is a small CSS library that gives you a good starting point on any project. It provides you with sensible defaults for standard HTML elements, and some CSS classes around layout patterns and simple utility classes. 6 | 7 | Find out more [here](https://feo.crinkles.dev). 8 | -------------------------------------------------------------------------------- /feo.min.css: -------------------------------------------------------------------------------- 1 | @layer global{*,:before,:after{box-sizing:border-box}:where(:not(:-webkit-any(iframe,canvas,img,svg,video)):not(svg *)){border:0;margin:0}:where(:not(:is(iframe,canvas,img,svg,video)):not(svg *)){border:0;margin:0}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;height:100%;interpolate-size:allow-keywords;height:-webkit-fill-available;overflow-x:hidden}html:focus-within{scroll-behavior:smooth}body{text-rendering:optimizespeed;min-height:100vh;min-height:-webkit-fill-available;line-height:1.4}:where(ul[role],ol[role]){padding-left:0;list-style:none}:where(ul,ol):not([role]) li{padding-left:.35em}:where(h1,h2,h3,h4,button,input,label){line-height:1.1}:where(h1,h2,h3,h4){text-wrap:balance}:where(a:not([class])){color:currentColor;-webkit-text-decoration-color:inherit;text-decoration-color:inherit;touch-action:manipulation;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;text-underline-offset:.1em;text-decoration-thickness:1px}:where(a:not([class]):visited){color:currentColor;-webkit-text-decoration-color:inherit;text-decoration-color:inherit;touch-action:manipulation;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;text-underline-offset:.1em;text-decoration-thickness:1px}:where(img,picture,svg){max-width:100%;display:block}:where(input,button,textarea,select){font-family:inherit;font-size:inherit}:target{scroll-margin-block:5ex}:active:not(:focus-visible){outline:none}:focus:not(:focus-visible){outline:none}:focus-visible{outline:1px solid}[hidden]{display:none}:disabled,[aria-disabled=true]{cursor:not-allowed}[disabled=true]{cursor:not-allowed}[aria-controls]{cursor:pointer}body:has(dialog[open]){overflow:hidden}@view-transition{navigation: auto;}@media (prefers-reduced-motion:reduce){html:focus-within{scroll-behavior:auto!important}@view-transition{navigation: none;}*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;-webkit-animation-duration:.01ms!important;animation-duration:.01ms!important;-webkit-animation-iteration-count:1!important;animation-iteration-count:1!important}}:root{--token-neutral-0:#1b1e22;--token-neutral-1:#31373f;--token-neutral-2:#515a67;--token-neutral-3:#b9c5d4;--token-neutral-4:#e6eaef;--token-neutral-5:#f9fafb;--token-bp-0:20rem;--token-bp-000:calc(var(--token-bp-00)/1.33);--token-bp-00:calc(var(--token-bp-0)/1.33);--token-bp-1:calc(var(--token-bp-0)*1.33);--token-bp-2:calc(var(--token-bp-1)*1.33);--token-bp-3:calc(var(--token-bp-2)*1.33);--token-bp-4:calc(var(--token-bp-3)*1.33);--token-bp-5:calc(var(--token-bp-4)*1.33);--feo-scale:calc(5*(min(100vw,1240px) - 320px)/920);--token-size-000:calc(.65rem + .65*var(--feo-scale));--token-size-00:calc(.8125rem + .8125*var(--feo-scale));--token-size-0:calc(1rem + var(--feo-scale));--token-size-1:calc(1.33rem + 1.33*var(--feo-scale));--token-size-2:calc(1.78rem + 1.78*var(--feo-scale));--token-size-3:calc(2.37rem + 2.37*var(--feo-scale));--token-size-4:calc(3.16rem + 3.16*var(--feo-scale));--token-size-5:calc(4.21rem + 4.21*var(--feo-scale));--monospace:ui-monospace,"Cascadia Code","Source Code Pro",Menlo,Consolas,"DejaVu Sans Mono",monospace;--serif:Charter,"Bitstream Charter","Sitka Text",Cambria,serif;--sans-serif:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";color-scheme:light;--text-0:var(--token-neutral-0);--text-1:var(--token-neutral-1);--text-2:var(--token-neutral-2);--surface-0:var(--token-neutral-5);--surface-1:var(--token-neutral-4);--surface-2:var(--token-neutral-3);--icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E");--icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E")}body{min-width:var(--token-bp-0);font-size:var(--token-size-0);font-family:var(--sans-serif);color:var(--text-0);background-color:var(--surface-0);max-width:100vw;position:relative}:where(a:not([class])){-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;color:currentColor}:where(h1,h2,h3){font-weight:600}h1{font-size:var(--token-size-3)}h2{font-size:var(--token-size-2)}h3{font-size:var(--token-size-1)}code{font-family:var(--monospace);-webkit-hyphens:none;hyphens:none;font-size:.85em}:not(pre)>code{border:1px solid var(--surface-2);background-color:var(--surface-1);white-space:nowrap;word-break:break-all;border-radius:4px;padding:2px}pre{background-color:var(--surface-1);word-wrap:normal;word-break:normal;word-spacing:normal;border-radius:8px;width:100%;max-width:100%;padding:0;position:relative}pre>code{padding:var(--token-size-000)var(--token-size-00);font-size:var(--token-size-00);white-space:pre;tab-size:2;-webkit-text-size-adjust:none;display:block;overflow-x:auto}::selection{color:var(--surface-0)!important;background:var(--text-0)!important}}@layer layout{.center{--layout-threshold:100%;max-width:min(100%,var(--layout-threshold));width:100%;margin-left:auto;margin-right:auto}.cluster{--layout-gap:0;--layout-align:center;align-items:var(--layout-align);justify-content:flex-start;gap:var(--layout-gap);flex-wrap:wrap;display:flex}.equal{--layout-gap:0;--layout-align:center;--layout-direction:row;flex-direction:var(--layout-direction);align-items:var(--layout-align);gap:var(--layout-gap);flex-wrap:nowrap;width:100%;display:flex}.equal>*{flex:1 1 0}.fifty{--layout-threshold:0;--layout-gap:0;gap:var(--layout-gap);flex-flow:wrap;display:flex}.fifty>*{flex-grow:1;flex-basis:var(--layout-threshold)}.flex{--layout-gap:0;--layout-direction:row;--layout-align:center;flex-direction:var(--layout-direction);align-items:var(--layout-align);gap:var(--layout-gap);display:flex}.grow{flex-grow:1}.self-start{align-self:self-start}.self-center{align-self:center}.self-stretch{align-self:stretch}.self-end{align-self:self-end}.grid{--layout-amount:2;--layout-gap:0;grid-template-columns:repeat(var(--layout-amount),1fr);gap:var(--layout-gap);display:grid}.pancake{--layout-gap:0;gap:var(--layout-gap);grid-template-rows:auto 1fr auto;display:grid}.pile{aspect-ratio:var(--layout-ratio);grid:[pile]1fr/[pile]1fr;align-items:center;justify-items:center;display:grid}.pile>*{grid-area:pile}.repel{--layout-gap:0;--layout-direction:row;--layout-align:center;flex-direction:var(--layout-direction);align-items:var(--layout-align);justify-content:space-between;gap:var(--layout-gap);flex-wrap:wrap;display:flex}.sidebar{--layout-gap:0;--layout-threshold:0;--layout-inline-size:60%;gap:var(--layout-gap);flex-wrap:wrap;align-items:stretch;display:flex}.sidebar>*{flex-basis:var(--layout-threshold);min-width:min(100%,var(--layout-threshold));flex-grow:1}.sidebar.--left>:last-child,.sidebar.--right>:first-child{min-width:var(--layout-inline-size);flex-grow:999;flex-basis:0}.stack>*+*{--layout-gap:0;margin-top:var(--layout-gap,1em)}.switcher{--layout-gap:0;--layout-threshold:0;--layout-direction:row;flex-direction:var(--layout-direction);gap:var(--layout-gap);flex-wrap:wrap;display:flex}.switcher>*{flex-grow:1;flex-basis:calc((var(--layout-threshold) - 100%)*999)}.tiles{--layout-threshold:0;--layout-gap:0;grid-template-columns:repeat(auto-fit,minmax(min(var(--layout-threshold),100%),1fr));gap:var(--layout-gap);display:grid}.--column{--layout-direction:column}.--row{--layout-direction:row}.--start{--layout-align:flex-start}.--end{--layout-align:flex-end}.--center{--layout-align:center}.--stretch{--layout-align:stretch}.--amount-1{--layout-amount:1}.--amount-2{--layout-amount:2}.--amount-3{--layout-amount:3}.--amount-4{--layout-amount:4}.--amount-5{--layout-amount:5}.--amount-6{--layout-amount:6}.--amount-7{--layout-amount:7}.--amount-8{--layout-amount:8}.--amount-9{--layout-amount:9}.--amount-10{--layout-amount:10}.--amount-11{--layout-amount:11}.--amount-12{--layout-amount:12}.--gap-none{--layout-gap:none}.--gap-000{--layout-gap:var(--token-size-000)}.--gap-00{--layout-gap:var(--token-size-00)}.--gap-0{--layout-gap:var(--token-size-0)}.--gap-1{--layout-gap:var(--token-size-1)}.--gap-2{--layout-gap:var(--token-size-2)}.--gap-3{--layout-gap:var(--token-size-3)}.--gap-4{--layout-gap:var(--token-size-4)}.--gap-5{--layout-gap:var(--token-size-5)}.--threshold-000{--layout-threshold:var(--token-bp-000)}.--threshold-00{--layout-threshold:var(--token-bp-00)}.--threshold-0{--layout-threshold:var(--token-bp-0)}.--threshold-1{--layout-threshold:var(--token-bp-1)}.--threshold-2{--layout-threshold:var(--token-bp-2)}.--threshold-3{--layout-threshold:var(--token-bp-3)}.--threshold-4{--layout-threshold:var(--token-bp-4)}.--threshold-5{--layout-threshold:var(--token-bp-5)}}@layer blocks{details.accordion{--accordion-border:var(--surface-1);--accordion-radius:8px;--accordion-surface:var(--surface-1);border:3px solid var(--accordion-border);border-radius:var(--accordion-radius)}details.accordion>*{padding:2px 12px}details.accordion summary{background-color:var(--accordion-surface);border-radius:calc(var(--accordion-radius) - 3px);font-weight:700}details.accordion[open] summary{border-bottom:1px solid var(--accordion-border);border-bottom-right-radius:0;border-bottom-left-radius:0}@-webkit-keyframes details-show{0%{opacity:0;-webkit-transform:translateY(-.5em);transform:translateY(-.5em)}}@keyframes details-show{0%{opacity:0;-webkit-transform:translateY(-.5em);transform:translateY(-.5em)}}details.accordion[open]>:not(summary){padding:var(--token-size-00);-webkit-animation:.25s ease-in-out slide-down-1;animation:.25s ease-in-out slide-down-1}form{--form-radius:8px;--form-border:var(--text-2);--form-focus:var(--text-0);--form-disabled-surface:var(--surface-1);--form-disabled-padding:var(--token-size-000);--form-disabled-border:var(--text-2)}label{color:var(--text-2);font-size:var(--token-size-0);flex-direction:column;gap:4px;display:flex}label span{font-size:var(--token-size-00);margin-left:var(--form-radius)}select,textarea{padding:6px var(--token-size-000);background-color:var(--token-surface-0);font-size:var(--token-size-00);border:1px solid var(--form-border);border-radius:var(--form-radius,4px);color:var(--text-0);width:100%;transition:all .1s}input:not([type=checkbox]){padding:6px var(--token-size-000);background-color:var(--token-surface-0);font-size:var(--token-size-00);border:1px solid var(--form-border);border-radius:var(--form-radius,4px);color:var(--text-0);width:100%;transition:all .1s}input:not(:disabled):not([type=checkbox]):where(:hover,:focus){border:1px solid var(--form-focus);outline:1px solid var(--form-focus)}select:not(:disabled):where(:hover,:focus){border:1px solid var(--form-focus);outline:1px solid var(--form-focus)}textarea:not(:disabled):where(:hover,:focus){border:1px solid var(--form-focus);outline:1px solid var(--form-focus)}input:disabled,textarea:disabled,select:disabled{background-color:var(--form-disabled-surface);border:1px solid var(--form-disabled-border);padding-left:var(--form-disabled-padding)}textarea{min-height:8em}select,input[list]{--arrow-icon:url("data:image/svg+xml,%3Csvg width='14' height='9' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cpath id='Path' fill='%232B3A50' d='M7 9L13.0622 0H0.937822L7 9Z'/%3E%3C/svg%3E");-webkit-appearance:none;appearance:none;background-image:var(--arrow-icon);background-repeat:no-repeat,repeat;background-position:right var(--token-size-000)top 50%,0 0;background-size:.8em,100%;display:block}label:has(.toggle){align-items:center;gap:var(--token-size-00);flex-direction:row}input[type=checkbox].toggle{--toggle-dot-color:white;--toggle-checked-color:green;--toggle-size:var(--token-size-0);--toggle-space:3px;-webkit-appearance:none;appearance:none;background:var(--surface-2);height:calc(var(--toggle-size) + 2*var(--toggle-space));width:calc(2*var(--toggle-size) + 2*var(--toggle-space));vertical-align:middle;border-radius:var(--toggle-size);transition:background .25s linear;display:inline-block;position:relative;box-shadow:inset 0 1px 3px rgba(0,0,0,.2)}input[type=checkbox].toggle:before{content:"";width:var(--toggle-size);height:var(--toggle-size);background:var(--toggle-dot-color);border-radius:calc(var(--toggle-size)*.5);top:var(--toggle-space);left:var(--toggle-space);transition:-webkit-transform .25s linear,transform .25s linear;display:block;position:absolute;-webkit-transform:translate(0);transform:translate(0);box-shadow:0 1px 3px rgba(0,0,0,.2)}input[type=checkbox].toggle:checked{background:var(--toggle-checked-color)}input[type=checkbox].toggle:checked:before{-webkit-transform:translateX(var(--toggle-size));transform:translateX(var(--toggle-size))}div:has(>table:only-child){width:100%;overflow-x:auto}table{--table-radius:6px;--table-color:var(--surface-1);border-collapse:collapse;border-radius:calc(var(--table-radius)*1.3);width:100%}thead{background:var(--table-color)}table tr:last-child td:first-child{border-bottom-left-radius:var(--table-radius)}table tr:last-child td:last-child{border-bottom-right-radius:var(--table-radius)}table tr:first-child th:first-child{border-top-left-radius:var(--table-radius)}table tr:first-child th:last-child{border-top-right-radius:var(--table-radius)}td{background-color:var(--surface-0)}td,th{text-align:left;padding:4px var(--token-size-000)}tbody tr{border-top:1px solid var(--table-color)}table tr:hover td{background-color:var(--table-color)}[data-tooltip]{--tooltip-pointer-color:var(--text-2);--tooltip-surface:var(--token-neutral-1);--tooltip-color:var(--token-neutral-5);--tooltip-slide-to:translate(-50%,-.25rem);--tooltip-caret-slide-to:translate(-50%,0rem);position:relative}[data-tooltip]:not(:-webkit-any(a,button,input,[role=button])){border-bottom:1px dotted var(--tooltip-pointer-color);cursor:help;text-decoration:none}[data-tooltip]:not(:is(a,button,input,[role=button])){border-bottom:1px dotted var(--tooltip-pointer-color);cursor:help;text-decoration:none}[data-tooltip]:before,[data-tooltip]:after{z-index:99;background:var(--tooltip-surface);content:attr(data-tooltip);color:var(--tooltip-color);text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none;max-width:var(--token-bp-00);border-radius:4px;padding:.25rem .5rem;font-size:.875rem;font-style:normal;font-weight:400;text-decoration:none;display:block;position:absolute;bottom:100%;left:50%;overflow:hidden;-webkit-transform:translate(-50%,-.25rem);transform:translate(-50%,-.25rem)}[data-tooltip]:after{content:"";color:var(--tooltip-surface);background-color:transparent;border-top:.3rem solid;border-left:.3rem solid transparent;border-right:.3rem solid transparent;border-radius:0;padding:0;-webkit-transform:translate(-50%);transform:translate(-50%)}[data-tooltip]:focus:before,[data-tooltip]:hover:before,[data-tooltip]:focus:after,[data-tooltip]:hover:after{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus:before,[data-tooltip]:focus:after,[data-tooltip]:hover:before,[data-tooltip]:hover:after{opacity:0;-webkit-animation-name:tooltip-slide;animation-name:tooltip-slide;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-transform:translate(-50%,.75rem);transform:translate(-50%,.75rem)}[data-tooltip]:focus:after,[data-tooltip]:hover:after{-webkit-animation-name:tooltip-caret-slide;animation-name:tooltip-caret-slide;-webkit-transform:translate(-50%,-.25rem);transform:translate(-50%,-.25rem)}@-webkit-keyframes tooltip-slide{to{-webkit-transform:var(--tooltip-slide-to);transform:var(--tooltip-slide-to);opacity:1}}@keyframes tooltip-slide{to{-webkit-transform:var(--tooltip-slide-to);transform:var(--tooltip-slide-to);opacity:1}}@-webkit-keyframes tooltip-caret-slide{50%{opacity:0}to{-webkit-transform:var(--tooltip-caret-slide-to);transform:var(--tooltip-caret-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{-webkit-transform:var(--tooltip-caret-slide-to);transform:var(--tooltip-caret-slide-to);opacity:1}}}[aria-busy=true]:not(:-webkit-any(input,select,textarea,html,form)){white-space:nowrap}[aria-busy=true]:not(:is(input,select,textarea,html,form)){white-space:nowrap}[aria-busy=true]:not(:-webkit-any(input,select,textarea,html,form)):not(:empty):not(:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))):before{margin-right:calc(var(--token-size-000)/2)}[aria-busy=true]:not(:is(input,select,textarea,html,form)):not(:empty):not(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))):before{margin-right:calc(var(--token-size-000)/2)}[aria-busy=true]:not(:-webkit-any(input,select,textarea,html,form)):not(:empty):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)):before{margin-left:calc(var(--token-size-000)/2)}[aria-busy=true]:not(:is(input,select,textarea,html,form)):not(:empty):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)):before{margin-left:calc(var(--token-size-000)/2)}[aria-busy=true]:before{content:"";vertical-align:-.125em;background-image:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E");background-repeat:no-repeat;background-size:1em;width:1em;height:1em;display:inline-block}}@layer utilities{@-webkit-keyframes wiggle{0%{-webkit-transform:rotate(0);transform:rotate(0)}30%{-webkit-transform:rotate(60deg);transform:rotate(60deg)}60%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}80%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}to{-webkit-transform:rotate(0);transform:rotate(0)}}@keyframes wiggle{0%{-webkit-transform:rotate(0);transform:rotate(0)}30%{-webkit-transform:rotate(60deg);transform:rotate(60deg)}60%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}80%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}to{-webkit-transform:rotate(0);transform:rotate(0)}}@-webkit-keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes slide-up-1{0%{-webkit-transform:translateY(1rem);transform:translateY(1rem)}}@keyframes slide-up-1{0%{-webkit-transform:translateY(1rem);transform:translateY(1rem)}}@-webkit-keyframes slide-up-2{0%{-webkit-transform:translateY(2rem);transform:translateY(2rem)}}@keyframes slide-up-2{0%{-webkit-transform:translateY(2rem);transform:translateY(2rem)}}@-webkit-keyframes slide-down-1{0%{-webkit-transform:translateY(-1rem);transform:translateY(-1rem)}}@keyframes slide-down-1{0%{-webkit-transform:translateY(-1rem);transform:translateY(-1rem)}}.click-area{position:relative}.click-area a{cursor:pointer}.click-area a:after{content:"";position:absolute;top:0;bottom:0;left:0;right:0}.contrast{background-color:var(--contrast-bg);color:lch(from var(--contrast-bg)calc((49.44 - l)*infinity)0 0)}.counted:has(>:first-child){--count:1}.counted:has(>:nth-child(2)){--count:2}.counted:has(>:nth-child(3)){--count:3}.counted:has(>:nth-child(4)){--count:4}.counted:has(>:nth-child(5)){--count:5}.counted:has(>:nth-child(6)){--count:6}.counted:has(>:nth-child(7)){--count:7}.counted:has(>:nth-child(8)){--count:8}.counted:has(>:nth-child(9)){--count:9}.counted:has(>:nth-child(10)){--count:10}.hover-group{--opacity:.4}.hover-group>*{transition:all .25s}@media (hover:hover){.hover-group:hover>:not(:hover){opacity:var(--opacity)}}.indexed>:first-child{--index:1}.indexed>:nth-child(2){--index:2}.indexed>:nth-child(3){--index:3}.indexed>:nth-child(4){--index:4}.indexed>:nth-child(5){--index:5}.indexed>:nth-child(6){--index:6}.indexed>:nth-child(7){--index:7}.indexed>:nth-child(8){--index:8}.indexed>:nth-child(9){--index:9}.indexed>:nth-child(10){--index:10}.m-000{margin:var(--token-size-000)}.m-00{margin:var(--token-size-00)}.m-0{margin:var(--token-size-0)}.m-1{margin:var(--token-size-1)}.m-2{margin:var(--token-size-2)}.m-3{margin:var(--token-size-3)}.m-4{margin:var(--token-size-4)}.m-5{margin:var(--token-size-5)}.mb-000{margin-bottom:var(--token-size-000)}.mb-00{margin-bottom:var(--token-size-00)}.mb-0{margin-bottom:var(--token-size-0)}.mb-1{margin-bottom:var(--token-size-1)}.mb-2{margin-bottom:var(--token-size-2)}.mb-3{margin-bottom:var(--token-size-3)}.mb-4{margin-bottom:var(--token-size-4)}.mb-5{margin-bottom:var(--token-size-5)}.mt-000{margin-top:var(--token-size-000)}.mt-00{margin-top:var(--token-size-00)}.mt-0{margin-top:var(--token-size-0)}.mt-1{margin-top:var(--token-size-1)}.mt-2{margin-top:var(--token-size-2)}.mt-3{margin-top:var(--token-size-3)}.mt-4{margin-top:var(--token-size-4)}.mt-5{margin-top:var(--token-size-5)}.ml-000{margin-left:var(--token-size-000)}.ml-00{margin-left:var(--token-size-00)}.ml-0{margin-left:var(--token-size-0)}.ml-1{margin-left:var(--token-size-1)}.ml-2{margin-left:var(--token-size-2)}.ml-3{margin-left:var(--token-size-3)}.ml-4{margin-left:var(--token-size-4)}.ml-5{margin-left:var(--token-size-5)}.mr-000{margin-right:var(--token-size-000)}.mr-00{margin-right:var(--token-size-00)}.mr-0{margin-right:var(--token-size-0)}.mr-1{margin-right:var(--token-size-1)}.mr-2{margin-right:var(--token-size-2)}.mr-3{margin-right:var(--token-size-3)}.mr-4{margin-right:var(--token-size-4)}.mr-5{margin-right:var(--token-size-5)}.read-more{--line-count:2;-webkit-line-clamp:var(--line-count);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scroll-container{contain:size;flex-wrap:nowrap;overflow-y:auto}.size-000{font-size:var(--token-size-000)}.size-00{font-size:var(--token-size-00)}.size-0{font-size:var(--token-size-0)}.size-1{font-size:var(--token-size-1)}.size-2{font-size:var(--token-size-2)}.size-3{font-size:var(--token-size-3)}.size-4{font-size:var(--token-size-4)}.size-5{font-size:var(--token-size-5)}.bold{font-weight:600}.regular{font-weight:400}.italic{font-style:italic}.text-center{text-align:center}.text-end:not(:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:right}.text-end:not(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:right}.text-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:left}.text-end:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:left}.text-start:not(:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:left}.text-start:not(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:left}.text-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}.text-start:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}.visually-hidden,.sr-only{clip:rect(0,0,0,0);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.maxw-000{max-width:var(--token-bp-000)}.maxw-00{max-width:var(--token-bp-00)}.maxw-0{max-width:var(--token-bp-0)}.maxw-1{max-width:var(--token-bp-1)}.maxw-2{max-width:var(--token-bp-2)}.maxw-3{max-width:var(--token-bp-3)}.maxw-4{max-width:var(--token-bp-4)}.maxw-5{max-width:var(--token-bp-5)}.z-indexed{z-index:calc(infinity)}} -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn build:11ty" 3 | publish = "_site/" 4 | functions = "functions/" 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {}, 3 | "scripts": { 4 | "css:bundle": "lightningcss --bundle --nesting --targets '> 0.25%, not IE 11' src/index.css -o feo.css", 5 | "css:minified": "lightningcss --bundle --nesting --minify --targets '> 0.25%, not IE 11' src/index.css -o feo.min.css", 6 | "css:docs": "lightningcss --bundle --nesting --minify --targets '> 0.25%, not IE 11' src/index.css -o public/feo.min.css", 7 | "build": "yarn css:bundle && yarn css:minified && yarn css:docs", 8 | "start": "eleventy --serve & yarn css:docs & onchange 'src/**/*.css' -- yarn build", 9 | "build:11ty": "npx @11ty/eleventy && yarn css:docs" 10 | }, 11 | "name": "feo-css", 12 | "version": "6.0.0", 13 | "main": "feo.css", 14 | "description": "A tiny CSS framework", 15 | "repository": "https://github.com/vyckes/feo-css.git", 16 | "keywords": [ 17 | "cube css", 18 | "css" 19 | ], 20 | "author": "Kevin Pennekamp ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/vyckes/feo-css/issues" 24 | }, 25 | "homepage": "https://github.com/vyckes/feo-css#readme", 26 | "devDependencies": { 27 | "@11ty/eleventy": "^2.0.1", 28 | "lightningcss-cli": "^1.18.0", 29 | "onchange": "^7.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/demo.css: -------------------------------------------------------------------------------- 1 | /* Themes */ 2 | @media (prefers-color-scheme: dark) { 3 | :root { 4 | color-scheme: dark; 5 | --text-0: var(--token-neutral-5); 6 | --text-1: var(--token-neutral-4); 7 | --text-2: var(--token-neutral-3); 8 | --surface-0: var(--token-neutral-0); 9 | --surface-1: var(--token-neutral-1); 10 | --surface-2: var(--token-neutral-2); 11 | } 12 | } 13 | 14 | a { 15 | transition: all 200ms; 16 | } 17 | 18 | a:visited { 19 | } 20 | 21 | a:hover { 22 | text-decoration-thickness: 2px; 23 | color: blueviolet; 24 | text-decoration-color: blueviolet; 25 | } 26 | 27 | /* Structure */ 28 | main { 29 | position: relative; 30 | } 31 | article { 32 | padding: var(--token-size-0); 33 | width: 100%; 34 | max-width: 68ch; 35 | } 36 | footer { 37 | padding: var(--token-size-0); 38 | } 39 | 40 | /* Main navigation */ 41 | nav { 42 | --contrast-bg: var(--token-neutral-1); 43 | 44 | z-index: 100; 45 | /* background-color: var(--token-neutral-1); */ 46 | /* color: var(--token-neutral-4); */ 47 | padding: var(--token-size-0); 48 | outline: 1px solid var(--token-neutral-2); 49 | } 50 | 51 | nav a, 52 | nav a:visited { 53 | text-decoration: none; 54 | } 55 | nav a:hover { 56 | color: inherit; 57 | font-weight: bold; 58 | } 59 | nav a[data-selected="true"] { 60 | font-weight: bold; 61 | } 62 | 63 | /* ensure text does not go on multiline in horizontal orientaiton */ 64 | nav li { 65 | white-space: nowrap; 66 | } 67 | 68 | /* small changes for responsiveness */ 69 | @media (max-width: 646px) { 70 | nav ul ul { 71 | display: none; 72 | } 73 | /* Overwrites scroll behavior and ensures growth in case main as little content */ 74 | main { 75 | contain: none; 76 | min-height: calc(100vh - 165px - var(--token-size-2)); 77 | } 78 | } 79 | 80 | /* Breadcrumbs */ 81 | .breadcrumbs { 82 | color: var(--text-2); 83 | text-transform: uppercase; 84 | } 85 | 86 | .breadcrumbs .divider { 87 | color: var(--surface-2); 88 | } 89 | 90 | .breadcrumbs li:last-child a { 91 | text-decoration: none; 92 | } 93 | 94 | h2 { 95 | margin-top: var(--token-size-2); 96 | font-size: var(--token-size-0); 97 | } 98 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vycke/feo-css/2d65a6ccd3487a85597ec2fec07de99361a195ef/public/favicon.ico -------------------------------------------------------------------------------- /public/feo.min.css: -------------------------------------------------------------------------------- 1 | @layer global{*,:before,:after{box-sizing:border-box}:where(:not(:-webkit-any(iframe,canvas,img,svg,video)):not(svg *)){border:0;margin:0}:where(:not(:is(iframe,canvas,img,svg,video)):not(svg *)){border:0;margin:0}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;height:100%;interpolate-size:allow-keywords;height:-webkit-fill-available;overflow-x:hidden}html:focus-within{scroll-behavior:smooth}body{text-rendering:optimizespeed;min-height:100vh;min-height:-webkit-fill-available;line-height:1.4}:where(ul[role],ol[role]){padding-left:0;list-style:none}:where(ul,ol):not([role]) li{padding-left:.35em}:where(h1,h2,h3,h4,button,input,label){line-height:1.1}:where(h1,h2,h3,h4){text-wrap:balance}:where(a:not([class])){color:currentColor;-webkit-text-decoration-color:inherit;text-decoration-color:inherit;touch-action:manipulation;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;text-underline-offset:.1em;text-decoration-thickness:1px}:where(a:not([class]):visited){color:currentColor;-webkit-text-decoration-color:inherit;text-decoration-color:inherit;touch-action:manipulation;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;text-underline-offset:.1em;text-decoration-thickness:1px}:where(img,picture,svg){max-width:100%;display:block}:where(input,button,textarea,select){font-family:inherit;font-size:inherit}:target{scroll-margin-block:5ex}:active:not(:focus-visible){outline:none}:focus:not(:focus-visible){outline:none}:focus-visible{outline:1px solid}[hidden]{display:none}:disabled,[aria-disabled=true]{cursor:not-allowed}[disabled=true]{cursor:not-allowed}[aria-controls]{cursor:pointer}body:has(dialog[open]){overflow:hidden}@view-transition{navigation: auto;}@media (prefers-reduced-motion:reduce){html:focus-within{scroll-behavior:auto!important}@view-transition{navigation: none;}*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;-webkit-animation-duration:.01ms!important;animation-duration:.01ms!important;-webkit-animation-iteration-count:1!important;animation-iteration-count:1!important}}:root{--token-neutral-0:#1b1e22;--token-neutral-1:#31373f;--token-neutral-2:#515a67;--token-neutral-3:#b9c5d4;--token-neutral-4:#e6eaef;--token-neutral-5:#f9fafb;--token-bp-0:20rem;--token-bp-000:calc(var(--token-bp-00)/1.33);--token-bp-00:calc(var(--token-bp-0)/1.33);--token-bp-1:calc(var(--token-bp-0)*1.33);--token-bp-2:calc(var(--token-bp-1)*1.33);--token-bp-3:calc(var(--token-bp-2)*1.33);--token-bp-4:calc(var(--token-bp-3)*1.33);--token-bp-5:calc(var(--token-bp-4)*1.33);--feo-scale:calc(5*(min(100vw,1240px) - 320px)/920);--token-size-000:calc(.65rem + .65*var(--feo-scale));--token-size-00:calc(.8125rem + .8125*var(--feo-scale));--token-size-0:calc(1rem + var(--feo-scale));--token-size-1:calc(1.33rem + 1.33*var(--feo-scale));--token-size-2:calc(1.78rem + 1.78*var(--feo-scale));--token-size-3:calc(2.37rem + 2.37*var(--feo-scale));--token-size-4:calc(3.16rem + 3.16*var(--feo-scale));--token-size-5:calc(4.21rem + 4.21*var(--feo-scale));--monospace:ui-monospace,"Cascadia Code","Source Code Pro",Menlo,Consolas,"DejaVu Sans Mono",monospace;--serif:Charter,"Bitstream Charter","Sitka Text",Cambria,serif;--sans-serif:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";color-scheme:light;--text-0:var(--token-neutral-0);--text-1:var(--token-neutral-1);--text-2:var(--token-neutral-2);--surface-0:var(--token-neutral-5);--surface-1:var(--token-neutral-4);--surface-2:var(--token-neutral-3);--icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E");--icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E")}body{min-width:var(--token-bp-0);font-size:var(--token-size-0);font-family:var(--sans-serif);color:var(--text-0);background-color:var(--surface-0);max-width:100vw;position:relative}:where(a:not([class])){-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;color:currentColor}:where(h1,h2,h3){font-weight:600}h1{font-size:var(--token-size-3)}h2{font-size:var(--token-size-2)}h3{font-size:var(--token-size-1)}code{font-family:var(--monospace);-webkit-hyphens:none;hyphens:none;font-size:.85em}:not(pre)>code{border:1px solid var(--surface-2);background-color:var(--surface-1);white-space:nowrap;word-break:break-all;border-radius:4px;padding:2px}pre{background-color:var(--surface-1);word-wrap:normal;word-break:normal;word-spacing:normal;border-radius:8px;width:100%;max-width:100%;padding:0;position:relative}pre>code{padding:var(--token-size-000)var(--token-size-00);font-size:var(--token-size-00);white-space:pre;tab-size:2;-webkit-text-size-adjust:none;display:block;overflow-x:auto}::selection{color:var(--surface-0)!important;background:var(--text-0)!important}}@layer layout{.center{--layout-threshold:100%;max-width:min(100%,var(--layout-threshold));width:100%;margin-left:auto;margin-right:auto}.cluster{--layout-gap:0;--layout-align:center;align-items:var(--layout-align);justify-content:flex-start;gap:var(--layout-gap);flex-wrap:wrap;display:flex}.equal{--layout-gap:0;--layout-align:center;--layout-direction:row;flex-direction:var(--layout-direction);align-items:var(--layout-align);gap:var(--layout-gap);flex-wrap:nowrap;width:100%;display:flex}.equal>*{flex:1 1 0}.fifty{--layout-threshold:0;--layout-gap:0;gap:var(--layout-gap);flex-flow:wrap;display:flex}.fifty>*{flex-grow:1;flex-basis:var(--layout-threshold)}.flex{--layout-gap:0;--layout-direction:row;--layout-align:center;flex-direction:var(--layout-direction);align-items:var(--layout-align);gap:var(--layout-gap);display:flex}.grow{flex-grow:1}.self-start{align-self:self-start}.self-center{align-self:center}.self-stretch{align-self:stretch}.self-end{align-self:self-end}.grid{--layout-amount:2;--layout-gap:0;grid-template-columns:repeat(var(--layout-amount),1fr);gap:var(--layout-gap);display:grid}.pancake{--layout-gap:0;gap:var(--layout-gap);grid-template-rows:auto 1fr auto;display:grid}.pile{aspect-ratio:var(--layout-ratio);grid:[pile]1fr/[pile]1fr;align-items:center;justify-items:center;display:grid}.pile>*{grid-area:pile}.repel{--layout-gap:0;--layout-direction:row;--layout-align:center;flex-direction:var(--layout-direction);align-items:var(--layout-align);justify-content:space-between;gap:var(--layout-gap);flex-wrap:wrap;display:flex}.sidebar{--layout-gap:0;--layout-threshold:0;--layout-inline-size:60%;gap:var(--layout-gap);flex-wrap:wrap;align-items:stretch;display:flex}.sidebar>*{flex-basis:var(--layout-threshold);min-width:min(100%,var(--layout-threshold));flex-grow:1}.sidebar.--left>:last-child,.sidebar.--right>:first-child{min-width:var(--layout-inline-size);flex-grow:999;flex-basis:0}.stack>*+*{--layout-gap:0;margin-top:var(--layout-gap,1em)}.switcher{--layout-gap:0;--layout-threshold:0;--layout-direction:row;flex-direction:var(--layout-direction);gap:var(--layout-gap);flex-wrap:wrap;display:flex}.switcher>*{flex-grow:1;flex-basis:calc((var(--layout-threshold) - 100%)*999)}.tiles{--layout-threshold:0;--layout-gap:0;grid-template-columns:repeat(auto-fit,minmax(min(var(--layout-threshold),100%),1fr));gap:var(--layout-gap);display:grid}.--column{--layout-direction:column}.--row{--layout-direction:row}.--start{--layout-align:flex-start}.--end{--layout-align:flex-end}.--center{--layout-align:center}.--stretch{--layout-align:stretch}.--amount-1{--layout-amount:1}.--amount-2{--layout-amount:2}.--amount-3{--layout-amount:3}.--amount-4{--layout-amount:4}.--amount-5{--layout-amount:5}.--amount-6{--layout-amount:6}.--amount-7{--layout-amount:7}.--amount-8{--layout-amount:8}.--amount-9{--layout-amount:9}.--amount-10{--layout-amount:10}.--amount-11{--layout-amount:11}.--amount-12{--layout-amount:12}.--gap-none{--layout-gap:none}.--gap-000{--layout-gap:var(--token-size-000)}.--gap-00{--layout-gap:var(--token-size-00)}.--gap-0{--layout-gap:var(--token-size-0)}.--gap-1{--layout-gap:var(--token-size-1)}.--gap-2{--layout-gap:var(--token-size-2)}.--gap-3{--layout-gap:var(--token-size-3)}.--gap-4{--layout-gap:var(--token-size-4)}.--gap-5{--layout-gap:var(--token-size-5)}.--threshold-000{--layout-threshold:var(--token-bp-000)}.--threshold-00{--layout-threshold:var(--token-bp-00)}.--threshold-0{--layout-threshold:var(--token-bp-0)}.--threshold-1{--layout-threshold:var(--token-bp-1)}.--threshold-2{--layout-threshold:var(--token-bp-2)}.--threshold-3{--layout-threshold:var(--token-bp-3)}.--threshold-4{--layout-threshold:var(--token-bp-4)}.--threshold-5{--layout-threshold:var(--token-bp-5)}}@layer blocks{details.accordion{--accordion-border:var(--surface-1);--accordion-radius:8px;--accordion-surface:var(--surface-1);border:3px solid var(--accordion-border);border-radius:var(--accordion-radius)}details.accordion>*{padding:2px 12px}details.accordion summary{background-color:var(--accordion-surface);border-radius:calc(var(--accordion-radius) - 3px);font-weight:700}details.accordion[open] summary{border-bottom:1px solid var(--accordion-border);border-bottom-right-radius:0;border-bottom-left-radius:0}@-webkit-keyframes details-show{0%{opacity:0;-webkit-transform:translateY(-.5em);transform:translateY(-.5em)}}@keyframes details-show{0%{opacity:0;-webkit-transform:translateY(-.5em);transform:translateY(-.5em)}}details.accordion[open]>:not(summary){padding:var(--token-size-00);-webkit-animation:.25s ease-in-out slide-down-1;animation:.25s ease-in-out slide-down-1}form{--form-radius:8px;--form-border:var(--text-2);--form-focus:var(--text-0);--form-disabled-surface:var(--surface-1);--form-disabled-padding:var(--token-size-000);--form-disabled-border:var(--text-2)}label{color:var(--text-2);font-size:var(--token-size-0);flex-direction:column;gap:4px;display:flex}label span{font-size:var(--token-size-00);margin-left:var(--form-radius)}select,textarea{padding:6px var(--token-size-000);background-color:var(--token-surface-0);font-size:var(--token-size-00);border:1px solid var(--form-border);border-radius:var(--form-radius,4px);color:var(--text-0);width:100%;transition:all .1s}input:not([type=checkbox]){padding:6px var(--token-size-000);background-color:var(--token-surface-0);font-size:var(--token-size-00);border:1px solid var(--form-border);border-radius:var(--form-radius,4px);color:var(--text-0);width:100%;transition:all .1s}input:not(:disabled):not([type=checkbox]):where(:hover,:focus){border:1px solid var(--form-focus);outline:1px solid var(--form-focus)}select:not(:disabled):where(:hover,:focus){border:1px solid var(--form-focus);outline:1px solid var(--form-focus)}textarea:not(:disabled):where(:hover,:focus){border:1px solid var(--form-focus);outline:1px solid var(--form-focus)}input:disabled,textarea:disabled,select:disabled{background-color:var(--form-disabled-surface);border:1px solid var(--form-disabled-border);padding-left:var(--form-disabled-padding)}textarea{min-height:8em}select,input[list]{--arrow-icon:url("data:image/svg+xml,%3Csvg width='14' height='9' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cpath id='Path' fill='%232B3A50' d='M7 9L13.0622 0H0.937822L7 9Z'/%3E%3C/svg%3E");-webkit-appearance:none;appearance:none;background-image:var(--arrow-icon);background-repeat:no-repeat,repeat;background-position:right var(--token-size-000)top 50%,0 0;background-size:.8em,100%;display:block}label:has(.toggle){align-items:center;gap:var(--token-size-00);flex-direction:row}input[type=checkbox].toggle{--toggle-dot-color:white;--toggle-checked-color:green;--toggle-size:var(--token-size-0);--toggle-space:3px;-webkit-appearance:none;appearance:none;background:var(--surface-2);height:calc(var(--toggle-size) + 2*var(--toggle-space));width:calc(2*var(--toggle-size) + 2*var(--toggle-space));vertical-align:middle;border-radius:var(--toggle-size);transition:background .25s linear;display:inline-block;position:relative;box-shadow:inset 0 1px 3px rgba(0,0,0,.2)}input[type=checkbox].toggle:before{content:"";width:var(--toggle-size);height:var(--toggle-size);background:var(--toggle-dot-color);border-radius:calc(var(--toggle-size)*.5);top:var(--toggle-space);left:var(--toggle-space);transition:-webkit-transform .25s linear,transform .25s linear;display:block;position:absolute;-webkit-transform:translate(0);transform:translate(0);box-shadow:0 1px 3px rgba(0,0,0,.2)}input[type=checkbox].toggle:checked{background:var(--toggle-checked-color)}input[type=checkbox].toggle:checked:before{-webkit-transform:translateX(var(--toggle-size));transform:translateX(var(--toggle-size))}div:has(>table:only-child){width:100%;overflow-x:auto}table{--table-radius:6px;--table-color:var(--surface-1);border-collapse:collapse;border-radius:calc(var(--table-radius)*1.3);width:100%}thead{background:var(--table-color)}table tr:last-child td:first-child{border-bottom-left-radius:var(--table-radius)}table tr:last-child td:last-child{border-bottom-right-radius:var(--table-radius)}table tr:first-child th:first-child{border-top-left-radius:var(--table-radius)}table tr:first-child th:last-child{border-top-right-radius:var(--table-radius)}td{background-color:var(--surface-0)}td,th{text-align:left;padding:4px var(--token-size-000)}tbody tr{border-top:1px solid var(--table-color)}table tr:hover td{background-color:var(--table-color)}[data-tooltip]{--tooltip-pointer-color:var(--text-2);--tooltip-surface:var(--token-neutral-1);--tooltip-color:var(--token-neutral-5);--tooltip-slide-to:translate(-50%,-.25rem);--tooltip-caret-slide-to:translate(-50%,0rem);position:relative}[data-tooltip]:not(:-webkit-any(a,button,input,[role=button])){border-bottom:1px dotted var(--tooltip-pointer-color);cursor:help;text-decoration:none}[data-tooltip]:not(:is(a,button,input,[role=button])){border-bottom:1px dotted var(--tooltip-pointer-color);cursor:help;text-decoration:none}[data-tooltip]:before,[data-tooltip]:after{z-index:99;background:var(--tooltip-surface);content:attr(data-tooltip);color:var(--tooltip-color);text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none;max-width:var(--token-bp-00);border-radius:4px;padding:.25rem .5rem;font-size:.875rem;font-style:normal;font-weight:400;text-decoration:none;display:block;position:absolute;bottom:100%;left:50%;overflow:hidden;-webkit-transform:translate(-50%,-.25rem);transform:translate(-50%,-.25rem)}[data-tooltip]:after{content:"";color:var(--tooltip-surface);background-color:transparent;border-top:.3rem solid;border-left:.3rem solid transparent;border-right:.3rem solid transparent;border-radius:0;padding:0;-webkit-transform:translate(-50%);transform:translate(-50%)}[data-tooltip]:focus:before,[data-tooltip]:hover:before,[data-tooltip]:focus:after,[data-tooltip]:hover:after{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus:before,[data-tooltip]:focus:after,[data-tooltip]:hover:before,[data-tooltip]:hover:after{opacity:0;-webkit-animation-name:tooltip-slide;animation-name:tooltip-slide;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-transform:translate(-50%,.75rem);transform:translate(-50%,.75rem)}[data-tooltip]:focus:after,[data-tooltip]:hover:after{-webkit-animation-name:tooltip-caret-slide;animation-name:tooltip-caret-slide;-webkit-transform:translate(-50%,-.25rem);transform:translate(-50%,-.25rem)}@-webkit-keyframes tooltip-slide{to{-webkit-transform:var(--tooltip-slide-to);transform:var(--tooltip-slide-to);opacity:1}}@keyframes tooltip-slide{to{-webkit-transform:var(--tooltip-slide-to);transform:var(--tooltip-slide-to);opacity:1}}@-webkit-keyframes tooltip-caret-slide{50%{opacity:0}to{-webkit-transform:var(--tooltip-caret-slide-to);transform:var(--tooltip-caret-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{-webkit-transform:var(--tooltip-caret-slide-to);transform:var(--tooltip-caret-slide-to);opacity:1}}}[aria-busy=true]:not(:-webkit-any(input,select,textarea,html,form)){white-space:nowrap}[aria-busy=true]:not(:is(input,select,textarea,html,form)){white-space:nowrap}[aria-busy=true]:not(:-webkit-any(input,select,textarea,html,form)):not(:empty):not(:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))):before{margin-right:calc(var(--token-size-000)/2)}[aria-busy=true]:not(:is(input,select,textarea,html,form)):not(:empty):not(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))):before{margin-right:calc(var(--token-size-000)/2)}[aria-busy=true]:not(:-webkit-any(input,select,textarea,html,form)):not(:empty):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)):before{margin-left:calc(var(--token-size-000)/2)}[aria-busy=true]:not(:is(input,select,textarea,html,form)):not(:empty):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)):before{margin-left:calc(var(--token-size-000)/2)}[aria-busy=true]:before{content:"";vertical-align:-.125em;background-image:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E");background-repeat:no-repeat;background-size:1em;width:1em;height:1em;display:inline-block}}@layer utilities{@-webkit-keyframes wiggle{0%{-webkit-transform:rotate(0);transform:rotate(0)}30%{-webkit-transform:rotate(60deg);transform:rotate(60deg)}60%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}80%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}to{-webkit-transform:rotate(0);transform:rotate(0)}}@keyframes wiggle{0%{-webkit-transform:rotate(0);transform:rotate(0)}30%{-webkit-transform:rotate(60deg);transform:rotate(60deg)}60%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}80%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}to{-webkit-transform:rotate(0);transform:rotate(0)}}@-webkit-keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes slide-up-1{0%{-webkit-transform:translateY(1rem);transform:translateY(1rem)}}@keyframes slide-up-1{0%{-webkit-transform:translateY(1rem);transform:translateY(1rem)}}@-webkit-keyframes slide-up-2{0%{-webkit-transform:translateY(2rem);transform:translateY(2rem)}}@keyframes slide-up-2{0%{-webkit-transform:translateY(2rem);transform:translateY(2rem)}}@-webkit-keyframes slide-down-1{0%{-webkit-transform:translateY(-1rem);transform:translateY(-1rem)}}@keyframes slide-down-1{0%{-webkit-transform:translateY(-1rem);transform:translateY(-1rem)}}.click-area{position:relative}.click-area a{cursor:pointer}.click-area a:after{content:"";position:absolute;top:0;bottom:0;left:0;right:0}.contrast{background-color:var(--contrast-bg);color:lch(from var(--contrast-bg)calc((49.44 - l)*infinity)0 0)}.counted:has(>:first-child){--count:1}.counted:has(>:nth-child(2)){--count:2}.counted:has(>:nth-child(3)){--count:3}.counted:has(>:nth-child(4)){--count:4}.counted:has(>:nth-child(5)){--count:5}.counted:has(>:nth-child(6)){--count:6}.counted:has(>:nth-child(7)){--count:7}.counted:has(>:nth-child(8)){--count:8}.counted:has(>:nth-child(9)){--count:9}.counted:has(>:nth-child(10)){--count:10}.hover-group{--opacity:.4}.hover-group>*{transition:all .25s}@media (hover:hover){.hover-group:hover>:not(:hover){opacity:var(--opacity)}}.indexed>:first-child{--index:1}.indexed>:nth-child(2){--index:2}.indexed>:nth-child(3){--index:3}.indexed>:nth-child(4){--index:4}.indexed>:nth-child(5){--index:5}.indexed>:nth-child(6){--index:6}.indexed>:nth-child(7){--index:7}.indexed>:nth-child(8){--index:8}.indexed>:nth-child(9){--index:9}.indexed>:nth-child(10){--index:10}.m-000{margin:var(--token-size-000)}.m-00{margin:var(--token-size-00)}.m-0{margin:var(--token-size-0)}.m-1{margin:var(--token-size-1)}.m-2{margin:var(--token-size-2)}.m-3{margin:var(--token-size-3)}.m-4{margin:var(--token-size-4)}.m-5{margin:var(--token-size-5)}.mb-000{margin-bottom:var(--token-size-000)}.mb-00{margin-bottom:var(--token-size-00)}.mb-0{margin-bottom:var(--token-size-0)}.mb-1{margin-bottom:var(--token-size-1)}.mb-2{margin-bottom:var(--token-size-2)}.mb-3{margin-bottom:var(--token-size-3)}.mb-4{margin-bottom:var(--token-size-4)}.mb-5{margin-bottom:var(--token-size-5)}.mt-000{margin-top:var(--token-size-000)}.mt-00{margin-top:var(--token-size-00)}.mt-0{margin-top:var(--token-size-0)}.mt-1{margin-top:var(--token-size-1)}.mt-2{margin-top:var(--token-size-2)}.mt-3{margin-top:var(--token-size-3)}.mt-4{margin-top:var(--token-size-4)}.mt-5{margin-top:var(--token-size-5)}.ml-000{margin-left:var(--token-size-000)}.ml-00{margin-left:var(--token-size-00)}.ml-0{margin-left:var(--token-size-0)}.ml-1{margin-left:var(--token-size-1)}.ml-2{margin-left:var(--token-size-2)}.ml-3{margin-left:var(--token-size-3)}.ml-4{margin-left:var(--token-size-4)}.ml-5{margin-left:var(--token-size-5)}.mr-000{margin-right:var(--token-size-000)}.mr-00{margin-right:var(--token-size-00)}.mr-0{margin-right:var(--token-size-0)}.mr-1{margin-right:var(--token-size-1)}.mr-2{margin-right:var(--token-size-2)}.mr-3{margin-right:var(--token-size-3)}.mr-4{margin-right:var(--token-size-4)}.mr-5{margin-right:var(--token-size-5)}.read-more{--line-count:2;-webkit-line-clamp:var(--line-count);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scroll-container{contain:size;flex-wrap:nowrap;overflow-y:auto}.size-000{font-size:var(--token-size-000)}.size-00{font-size:var(--token-size-00)}.size-0{font-size:var(--token-size-0)}.size-1{font-size:var(--token-size-1)}.size-2{font-size:var(--token-size-2)}.size-3{font-size:var(--token-size-3)}.size-4{font-size:var(--token-size-4)}.size-5{font-size:var(--token-size-5)}.bold{font-weight:600}.regular{font-weight:400}.italic{font-style:italic}.text-center{text-align:center}.text-end:not(:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:right}.text-end:not(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:right}.text-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:left}.text-end:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:left}.text-start:not(:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:left}.text-start:not(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))){text-align:left}.text-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}.text-start:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}.visually-hidden,.sr-only{clip:rect(0,0,0,0);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.maxw-000{max-width:var(--token-bp-000)}.maxw-00{max-width:var(--token-bp-00)}.maxw-0{max-width:var(--token-bp-0)}.maxw-1{max-width:var(--token-bp-1)}.maxw-2{max-width:var(--token-bp-2)}.maxw-3{max-width:var(--token-bp-3)}.maxw-4{max-width:var(--token-bp-4)}.maxw-5{max-width:var(--token-bp-5)}.z-indexed{z-index:calc(infinity)}} -------------------------------------------------------------------------------- /site/_includes/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Feo.css - {{ title }} 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 43 | 44 | 45 |
46 |
47 | {% include "partials/breadcrumb.njk" %} 48 |

{{title}}

49 | {{ content | safe }} 50 |
51 | {% include "partials/footer.njk" %} 52 |
53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /site/_includes/partials/breadcrumb.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/_includes/partials/footer.njk: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/_includes/partials/navigation.njk: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /site/_includes/partials/sublist.njk: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /site/_includes/svg/center.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /site/_includes/svg/cluster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /site/_includes/svg/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /site/_includes/svg/pancake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /site/_includes/svg/repel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /site/_includes/svg/switcher.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /site/blocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Blocks 4 | order: 4 5 | key: blocks 6 | --- 7 | 8 | A limited set of blocks (mostly based on basic HTML elements, 9 | but are more complex in their implementation). 10 | 11 | Several of the below components have an **API**. These are defined CSS custom properties specified for these components. By only altering these properties in your own CSS, you can change some of the looks of the components. 12 | 13 | The available component classes in Feo.css are listed below. 14 | 15 | {% set items = collections.all | subitems("blocks") %} 16 | {% include "partials/sublist.njk" %} 17 | -------------------------------------------------------------------------------- /site/blocks/accordion.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Accordion 4 | key: blocks 5 | subkey: accordion 6 | --- 7 | 8 | An accordion or alert box that can be styled and used to provide more information to users. 9 | 10 | ## Example 11 | 12 |
13 | Accordion title 14 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis porttitor mauris et nisl lobortis, nec efficitur lectus placerat. Nunc ultricies libero quis justo feugiat, at dapibus ex egestas. Donec cursus euismod mauris, ut pellentesque est scelerisque quis. Vestibulum pellentesque dui ut congue tempor. Morbi sit amet elit nec sapien auctor fringilla.
15 |
16 | 17 | ## Implementation 18 | 19 | ``` 20 |
21 | Title 22 |
...
23 |
24 | ``` 25 | 26 |
27 | About the wrapping div 28 |
You should wrap the content that doess not to the header/summary, in an HTML tag (e.g. p, div) for the opening animation to work.
29 |
30 | 31 | ## Custom properties 32 | 33 | There are several custom properties available that can be 34 | overwritten to control the looks of the table. You can change these properties on the `details` selector. 35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 62 | 63 | 64 |
Custom propertyDescription
--accordion-border 48 | Sets the border-color property. 49 |
--accordion-surface 54 | Sets background-color property of the header (also known as the summary). 55 |
--accordion-radius 60 | Sets border-radius property. 61 |
65 |
66 | -------------------------------------------------------------------------------- /site/blocks/forms.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Forms 4 | key: blocks 5 | subkey: forms 6 | --- 7 | 8 | Generic form fields useful for all forms. 9 | 10 | ## Example 11 | 12 |
13 | 17 | 21 | 32 | 44 | 48 |
49 | 50 | Consistent styling is available for the most common form elements: 51 | input fields, text-areas, and two kinds of dropdowns. In addition, 52 | various states like `:hover` and `:disabled` are covered. 53 | 54 | ## Implementation 55 | 56 | All the fields in the form follow a specific implementation for 57 | the styling. 58 | 59 | ``` 60 | 64 | ``` 65 | 66 | ## Custom properties 67 | 68 | There are several custom properties available that can be 69 | overwritten to control the looks of the table. You can change these properties on the `details` selector. 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 91 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 109 | 110 | 111 | 112 | 115 | 116 | 117 |
Custom propertyDescription
--form-radius 83 | Sets the border-radius of the fields. 84 |
--form-border 89 | Sets the border-color of the fields. 90 |
--form-focus 95 | Sets the border-radius of the fields, on hover and focus. 96 |
--form-disabled-surface 101 | Sets the background-color of disabled fields. 102 |
--form-disabled-border 107 | Sets the border-color of disabled fields. 108 |
--form-disabled-padding 113 | Sets the padding-left of disabled fields. 114 |
118 |
119 | -------------------------------------------------------------------------------- /site/blocks/table.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Table 4 | key: blocks 5 | subkey: table 6 | --- 7 | 8 | Generic table component. 9 | 10 | ## Example 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
NameLength (m)Weight (kg)
John Doe1.7268
John Doe1.7268
John Doe1.7268
39 |
40 | 41 | ## Implementation 42 | 43 | ``` 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
...
...
53 |
54 | ``` 55 | 56 |
57 | About the wrapping div 58 |
the additional div wrapping the table in the below implementation is required if you want the table to horizontal scroll on smaller screens. The table needs to be the :only-child of this div.
59 |
60 | 61 | ## Custom properties 62 | 63 | There are several custom properties available that can be 64 | overwritten to control the looks of the table. You can change these properties on the `table` selector. 65 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
Custom propertyDescription
--table-radiusSets the radius of the corners
--table-colorSets the color of the borders and header
85 |
86 | -------------------------------------------------------------------------------- /site/blocks/toggle.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Toggle 4 | key: blocks 5 | subkey: toggle 6 | --- 7 | 8 | A toggle is a specific component that can be used as a solitary 9 | checkbox, or for something else. 10 | 11 | ## Example 12 | 13 |
14 | 18 |
19 | 20 | ## Implementation 21 | 22 | ``` 23 | 27 | ``` 28 | 29 | ## Custom properties 30 | 31 | The implementation is a slightly different from the form elements, 32 | as you will put the label text after the toggle in most cases. You can change these properties on the `input[type="checkbox"]` selector. 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 67 | 68 | 69 |
Custom propertyDescription
--toggle-checked-color 46 | Sets the background color of the toggle on 47 | :checked. Default is green. 48 |
--toggle-dot-color 53 | Sets the dot color. 54 |
--toggle-size 59 | Sets the size of the white "dot" of the toggle. 60 |
--toggle-space 65 | Sets spacing around the white "dot" of the toggle. 66 |
70 |
71 | -------------------------------------------------------------------------------- /site/blocks/tooltip.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Tooltip 4 | key: blocks 5 | subkey: tooltip 6 | --- 7 | 8 | A simple `data-*` attribute that allows for a hover-triggered tooltip with plain text in it. 9 | 10 | ## Example 11 | 12 | Hover me for a tooltip at the top! 13 | 14 | Hover me for a tooltip at the bottom! 15 | 16 | ## Implementation 17 | 18 | ``` 19 | 20 | hover element 21 | 22 | ``` 23 | 24 | By default the tooltip is positioned at the top. If you want it at the bottom, add `data-tooltip-bottom` to the element. 25 | 26 | ## Custom properties 27 | 28 | There are several custom properties available that can be overwritten to control the looks of the tooltip. 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 56 | 57 | 58 |
Custom propertyDescription
--tooltip-surface 42 | Sets the background-color property. 43 |
--tooltip-color 48 | Sets color property of the text. 49 |
--tooltip-decoration 54 | Sets text-decoration on the hover element. 55 |
59 |
60 | -------------------------------------------------------------------------------- /site/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Home 4 | order: 1 5 | key: home 6 | --- 7 | 8 | Feo.css is a small CSS library (<5kB) that gives you a good starting point on any project. It provides you with sensible defaults for standard HTML elements, and some CSS classes around layout patterns and simple utility classes. A solid CSS foundation 9 | and architecture speeds up everything. Feo.css provides that. 10 | 11 |
12 | Did you know? 13 |

1. The name Feo means "front-end optimized". It also happens to mean "ugly" in Spanish. Happy coincidence, don't you think?

14 |

2. Everything on this site, is purely based on the library. Ok, almost everything (less than 100 lines of CSS required).

15 |
16 | 17 | ## How to use it 18 | 19 | Use the _unpkg.com_ CDN directly in your `head` of your HTML page: 20 | 21 | ``` 22 | 23 | ``` 24 | 25 | Import it in your (S)CSS: 26 | 27 | ``` 28 | @import "https://unpkg.com/feo-css/feo.min.css"; 29 | ``` 30 | 31 | Or install it via NPM using your package manager of choice: 32 | 33 | ``` 34 | npm install feo-css 35 | yarn add feo-css 36 | ``` 37 | 38 | ## Setup and architecture 39 | 40 | The architecture of Feo.css follows the principles outlined [here](https://github.com/vyckes/css-architecture). 41 | 42 | ``` 43 | @layer global, layout, blocks, utilities; 44 | ``` 45 | 46 | The layers indicate the level of importance, meaning: try to solve 47 | things with HTML elements first (based on a simple [reset & global css](https://github.com/vyckes/feo-css/blob/main/src/global/_global.css)). If that is not enough, use 48 | generic layout patterns, and some utilities. In more complex 49 | settings, use components. 50 | 51 | 1. [Design tokens](/tokens) (part of the `global` layer) 52 | 2. [Layout patterns](/layouts) 53 | 3. [Blocks](/blocks) 54 | 4. [Utility](/utilities) 55 | 56 | Because Feo.css is build using `@layer`, you can easily 57 | extend and avoid cascading issues, by including your own CSS in 58 | the proper layer. 59 | 60 | ## Naming conventions 61 | 62 | Feo.css has some particular naming conventions that are important to know and understand. In particular two naming patterns. 63 | 64 | ## Design tokens 65 | 66 | Tokens like sizing and breakpoints are considered to have a "baseline". The most common value for the design token. Those tokens always have a `-0` as the post-fix in their naming (e.g. `--token-size-0`). For each step higher, the number is increased (e.g. `--token-size-2`). In case of lowering steps, we _add_ a `0` to the token name (e.g. `--token-size-000`). This convention is chosen as it is seen to be more readable compared to `--token-size--1` (note the double dash). 67 | 68 | ## Class utilities 69 | 70 | [Utility classes](/utilities) are classes that do one thing, and one thing well. _Class utilities_ are classes that that allow you to control one aspect from a different CSS class, like a layout class. Class utilities on their own have no impact whatsoever, in contract to utility classes. Class utilities have a `--` post-fix, to make them easily spottable (as in most cases they alter interal custom properties). 71 | -------------------------------------------------------------------------------- /site/layouts.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Layouts 4 | order: 3 5 | key: layouts 6 | --- 7 | 8 | Feo.css offers classes for standardized layout patterns that you see on almost every website or application. 9 | 10 |
11 | Class utilities 12 |

Utility classes are classes that do one thing, and one thing well. Class utilities are classes that that allow you to control one aspect from a different CSS class, like a layout class. Class utilities on their own have no impact whatsoever, in contract to utility classes. Class utilities have a -- post-fix, to make them easily spottable (as in most cases they alter interal custom properties).

13 |

All class utilities alter internal custom properties labeled --layout-*. These custom properties are shared between the layout classes.

14 |
15 | 16 | The available layout classes in Feo.css are listed below. 17 | 18 | {% set items = collections.all | subitems("layouts") %} 19 | {% include "partials/sublist.njk" %} 20 | -------------------------------------------------------------------------------- /site/layouts/center.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Center 4 | key: layouts 5 | subkey: center 6 | --- 7 | 8 | Places the targeted element in the horizontal center. It takes the entire available horizontal space, until it hits its set `max-width` set through the `--threshold-{z}` class utilities. 9 | 10 | {% include "svg/center.svg" %} 11 | 12 | ## Implementation 13 | 14 | ``` 15 |
16 | ... 17 |
18 | ``` 19 | 20 | ## API 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Custom propertyDefaultDescription
--layout-threshold100%Sets the max-width of the targeted element
31 |
32 | 33 | ## Utility classes 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
Class nameRequired?Description
--threshold-{z}RequiredControls the --layout-threshold API
44 |
45 | -------------------------------------------------------------------------------- /site/layouts/cluster.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Cluster 4 | key: layouts 5 | subkey: cluster 6 | --- 7 | 8 | Groups items in such a way that a 'cluster' is created that automatically 9 | determines how many items can be on a single row (e.g. tag cloud). 10 | 11 | {% include "svg/cluster.svg" %} 12 | 13 | ## Implementation 14 | 15 | ``` 16 |
17 | ... 18 |
19 | ``` 20 | 21 | ## API 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Custom propertyDefaultDescription
--layout-gap0Sets the gap of the targeted element
--layout-aligncenterSets the align-items of the targeted element
33 |
34 | 35 | ## Utility classes 36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Class nameRequired?Description
--gap-{z}Controls the --layout-gap API
--start/--end/--center/--stretchControls the --layout-align API
47 |
48 | -------------------------------------------------------------------------------- /site/layouts/equal.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Equal 4 | key: layouts 5 | subkey: equal 6 | --- 7 | 8 | Makes a wrapper fill the available space and makes all the children equal size within that space. Does not wrap. 9 | 10 | ## Implementation 11 | 12 | ``` 13 |
14 | ... 15 |
16 | ``` 17 | 18 | ## API 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Custom propertyDefaultDescription
--layout-gap0Sets the gap of the targeted element
--layout-aligncenterSets the align-items of the targeted element
--layout-directionrowSets the flex-direction of the targeted element
31 |
32 | 33 | ## Utility classes 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
Class nameRequired?Description
--gap-{z}Controls the --layout-gap API
--column/--rowControls the --layout-direction API
--start/--end/--center/--stretchControls the --layout-align API
46 |
47 | -------------------------------------------------------------------------------- /site/layouts/fifty.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Fifty-fifty 4 | key: layouts 5 | subkey: fifty-fifty 6 | --- 7 | 8 | A simple layout pattern that makes two elements of equal width next to eachother, until a threshold is met. When the available space is lower than the threshold, items are positioned below eachother. Can be used with more than 2 items, but does not act the same as the [switcher](/layouts/switcher), as items just wrap. 9 | 10 | ## Implementation 11 | 12 | ``` 13 |
14 | ... 15 |
16 | ``` 17 | 18 | ## API 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Custom propertyDefaultDescription
--layout-threshold0Sets the max-width of the child elements
--layout-gap0Sets the gap of the targeted element
30 |
31 | 32 | ## Utility classes 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
Class nameRequired?Description
--threshold-{z}RequiredControls the --layout-threshold API
--gap-{z}Controls the --layout-gap API
44 |
45 | -------------------------------------------------------------------------------- /site/layouts/flexbox.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Flex 4 | key: layouts 5 | subkey: flex 6 | --- 7 | 8 | Implements a flex container that makes use of the common layout APIs defined in Feo.css. 9 | 10 | ## Implementation 11 | 12 | ``` 13 |
14 | ... 15 |
16 | ``` 17 | 18 | ## API 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Custom propertyDefaultDescription
--layout-gap0Sets the max-width of the targeted element
--layout-directionrowSets the flex-direction of the targeted element
--layout-aligncenterSets the align-items of the targeted element
31 |
32 | 33 | ## Utility classes 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
Class nameRequired?Description
--gap-{z}Controls the --layout-gap API
--column/--rowControls the --layout-direction API
--start/--end/--center/--stretchControls the --layout-align API
--growsets flex-grow: 1; on the targeted element.
--self-start/end/stretch/centersets the align-self property on the targeted element.
48 |
49 | -------------------------------------------------------------------------------- /site/layouts/grid.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Grids 4 | key: layouts 5 | subkey: grids 6 | --- 7 | 8 | A layout pattern to easily create a grid of equal columns. 9 | 10 | ## Implementation 11 | 12 | ``` 13 |
14 | ... 15 |
16 | ``` 17 | 18 | ## API 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Custom propertyDefaultDescription
--layout-gap0Sets the gap of the targeted element
--layout-amount2Sets the number of colums
30 |
31 | 32 | ## Utility classes 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
Class nameRequired?Description
--gap-{z}Controls the --layout-gap API
--amount-{z}Controls the --layout-amount API
44 |
45 | -------------------------------------------------------------------------------- /site/layouts/pancake.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Pancake 4 | key: layouts 5 | subkey: pancake 6 | --- 7 | 8 | A common vertical pattern where the center content should stretch the available space, pushing the top and bottom to, well, the top and bottom. 9 | 10 | {% include "svg/pancake.svg" %} 11 | 12 | ## Implementation 13 | 14 | ``` 15 |
16 | ... 17 |
18 | ``` 19 | 20 | ## API 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Custom propertyDefaultDescription
--layout-gap0Sets the gap of the targeted element
31 |
32 | 33 | ## Utility classes 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
Class nameRequired?Description
--gap-{z}Controls the --layout-gap API
44 |
45 | -------------------------------------------------------------------------------- /site/layouts/pile.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Pile 4 | key: layouts 5 | subkey: pile 6 | --- 7 | 8 | A simple layout pattern to place items on top eachother, in a depth sense. The lower items in the DOM-tree are positioned more on top. 9 | 10 | ## Implementation 11 | 12 | ```html 13 |
14 | 15 |

text

16 |
17 | ``` 18 | 19 | ## API 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Custom propertyDefaultDescription
--layout-ratioSets the aspect-ratio of the targeted element
30 |
31 | -------------------------------------------------------------------------------- /site/layouts/repel.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Repel 4 | key: layouts 5 | subkey: repel 6 | --- 7 | 8 | A simple layout pattern that pushes elements away from eachother, given the available space. Effectively implementing the `.justify-between`, `.--gap-{z}`, `.flex`, and `.--row` (as the default is horizontal orientation) classes. 9 | 10 | {% include "svg/repel.svg" %} 11 | 12 | ## Implementation 13 | 14 | ``` 15 |
16 | ... 17 |
18 | ``` 19 | 20 | ## API 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Custom propertyDefaultDescription
--layout-gap0Sets the gap of the targeted element
--layout-directionrowSets the flex-direction of the targeted element
--layout-aligncenterSets the align-items of the targeted element
33 |
34 | 35 | ## Utility classes 36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
Class nameRequired?Description
--gap-{z}Controls the --layout-gap API
--column/--rowControls the --layout-direction API
--start/--end/--center/--stretchControls the --layout-align API
48 |
49 | -------------------------------------------------------------------------------- /site/layouts/sidebar.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Sidebar 4 | key: layouts 5 | subkey: sidebar 6 | --- 7 | 8 | A common responsive layout in which there is a "sidebar" of a _fixed_ width, and a content area that is flexible. This implementation switches to a vertical layout the moment the flexible content gets too little space left within the _targeted (wrapper) element_. By default, items are stretch vertically within the parent wrapper. 9 | 10 | {% include "svg/sidebar.svg" %} 11 | 12 | ## Implementation 13 | 14 | ``` 15 | 18 | ``` 19 | 20 |
21 | Implementation tip(s)! 22 |

1. The sidebar layout pattern does not have to be applied to an entire page. You can even apply it to a "searchbar". The input bar is the flexible content, but the search button is of a fixed content. If there is not enough room, they switch to a vertical layout.

23 |

2. There is a custom property called --layout-inline-size, set to 60% to calculate the breaking point of this layout. If you want a different breaking point, you can overwrite this custom property. There are class utilities available.

24 |
25 | 26 | ## API 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
Custom propertyDefaultDescription
--layout-threshold0Sets the "fixed width" of the sidebar child element
--layout-gap0Sets the gap of the targeted element
--layout-inline-size60%Sets the min-width of the flexible content child element
39 |
40 | 41 | ## Utility classes 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
Class nameRequired?Description
--threshold-{z}RequiredControls the --layout-threshold API
--gap-{z}Controls the --layout-gap API
--left/--rightRequiredSets which element is the fixed width sidebar
54 |
55 | -------------------------------------------------------------------------------- /site/layouts/switcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Switcher 4 | key: layouts 5 | subkey: switcher 6 | --- 7 | 8 | A _responsive_ layout pattern that helps switching the orientation from horizontal to vertical if the _available width for the targeted (wrapper) element_ becomes less than the set width. Useful when the targeted element has >= 2 child elements. 9 | 10 | {% include "svg/switcher.svg" %} 11 | 12 | ## Implementation 13 | 14 | ``` 15 |
16 | ... 17 |
18 | ``` 19 | 20 |
21 | Implementation tip! 22 | When you apply the switcher pattern to the "fixed" content of sidebar pattern, you can get an impressive layout. On larger available width, the content in the "fixed" sidebar has a vertical orientation. Once the available width decreases, the sidebar transforms into a vertical alignment. But the switcher does the opposite, as it now has more available width. To achieve this, ensure the --threshold-{z} class utility of the switcher has a z+1 compared to the sidebar.

23 |
24 | 25 | ## API 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
Custom propertyDefaultDescription
--layout-threshold0Sets the threshold when the targeted element switches orientation
--layout-gap0Sets the gap of the targeted element
--layout-directionrowSets the flex-direction of the targeted element
38 |
39 | 40 | ## Utility classes 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
Class nameRequired?Description
--threshold-{z}RequiredControls the --layout-threshold API
--gap-{z}Controls the --layout-threshold API
--column/--rowControls the --layout-direction API
53 |
54 | -------------------------------------------------------------------------------- /site/layouts/tiles.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Tiles 4 | key: layouts 5 | subkey: tiles 6 | --- 7 | 8 | Also known as a RAM (repeat, auto, minmax) layout. It is a tile system in which the browser determines how many tiles fit in the avaiable space. It rounds down the number of tiles on a single row, and stretches the tiles to fit the space. When the screen shrinks, the amount of tiles on a row decreases automatically. 9 | 10 | {% include "svg/tiles.svg" %} 11 | 12 | ## Implementation 13 | 14 | ``` 15 |
16 | ... 17 |
18 | ``` 19 | 20 | ## API 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Custom propertyDefaultDescription
--layout-threshold0Sets the min-width of the child elements
--layout-gap0Sets the gap of the targeted element
32 |
33 | 34 | ## Utility classes 35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
Class nameRequired?Description
--threshold-{z}RequiredControls the --layout-threshold API
--gap-{z}Controls the --layout-gap API
46 |
47 | -------------------------------------------------------------------------------- /site/tokens.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Tokens 4 | order: 2 5 | key: tokens 6 | --- 7 | 8 | Feo.css offers a limited set of design tokens, implemented in [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) (`--*`). These design tokens are used throughout all the different layers of Feo.css. For example, several layout 9 | patterns allow you to set the `gap` between columns and rows, through _class ulilities_. These classes directly use the defined tokens. 10 | 11 |
12 | Naming convention 13 |

Tokens like sizing and breakpoints are considered to have a "baseline". The most common value for the design token. Those tokens always have a -0 as the post-fix in their naming (e.g. --token-size-0). For each step higher, the number is increased (e.g. --token-size-2). In case of lowering steps, we add a 0 to the token name (e.g. --token-size-000). This convention is chosen as it is seen to be more readable compared to --token-size--1 (note the double dash).

14 |
15 | 16 | ## Sizing 17 | 18 | Feo.css offers design tokens on _sizing_ that can be used for spacing (e.g. margin and padding), font-sizes, or anything you can think of. The values are based on a combination of a few key principles: 19 | 20 | - [Fluid scaling](https://crinkles.dev/writing/different-approaches-to-fluid-typography-and-layouts/) based on screen size. 21 | - A ratio of `1.33` between two consecutive token values. 22 | - The mentioned naming convention outlined above. `1rem` is taken as the base value (`--size-0`). 23 | 24 |
25 | Note on fluid scaling 26 |

If you do not want to use a fluid scaling of the --size-{z} tokens, you can overwrite the --feo-scale Custom Property in your own code, and set it to 0.

27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Token nameValue at 320pxValue at 1240px
--token-size-0000.65rem0.65rem + 3.25px
--token-size-000.8125rem0.8125rem + 4px
--token-size-01rem1rem + 5px
--token-size-11.33rem1.33rem + 6.65px
--token-size-21.78rem1.78rem + 8.9px
--token-size-32.37rem2.37rem + 11.85px
--token-size-43.16rem3.16rem + 15.7px
--token-size-54.21rem4.21rem + 21.05px
81 |
82 | 83 | ## Breakpoints 84 | 85 | Tokens used as points that can be used, whenever your UI is 86 | breaking. Scaling between the values is based on 87 | `1.33`. 88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
Token nameValue
--token-bp-00011.31rem
--token-bp-0015.04rem
--token-bp-020rem
--token-bp-120rem
--token-bp-226.6rem
--token-bp-335.38rem
--token-bp-447.05rem
--token-bp-562.58rem
132 |
133 | 134 | ## Colors 135 | 136 | A set of dark and light colors are present in Feo.css, that can be 137 | used as a base for your application in greyscale. 138 | 139 |
140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |
Token nameRGB valueHSL value
--token-neutral-0#0E131Bhsl(215, 30%, 8%)
--token-neutral-1#2B3A50hsl(215, 30%, 24%)
--token-neutral-2#476185hsl(215, 30%, 40%)
--token-neutral-3#C8D4E5hsl(215, 36%, 78%)
--token-neutral-4#E3E9F2hsl(215, 36%, 92%)
--token-neutral-5#F7F9FBhsl(215, 36%, 98%)
181 |
182 | 183 | ## Themes 184 | 185 | Feo.css does not offer multiple themes out of the box, but offers a basic _light_, as shown below. The tokens defined in the light theme are used throughout Feo.css (e.g. forms-component). Feo.css does not offer auto-themes out of the box, as these should be _opt-in_ for developers. By offering one theme, consistent application of theme related tokens (through custom properties) can be achieved. 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 |
ColorDesign tokenDescription
--surface-0--token-neutral-0Main background color
--surface-1--token-neutral-1Background color for surfaces that need to stand out a little on the main background (e.g. cards, code-blocks)
--surface-2--token-neutral-2Surface color for specific elements or properties that need a little more emphasize (e.g. border colors)
--text-0--token-neutral-5Main text color
--text-1--token-neutral-4Text color for elements that need a little less high-light
--text-2--token-neutral-3Text color for elements with less emphasize
229 |
230 | 231 |
232 | About contrasts 233 |

The selected theme colors, if combined with --surface-{z} and --text-{z} have a contrast score of AAA (> 7) and can be used freely. Except when you combine --surface-2 and --text-2. That combination has a AA+ (4.25) score. So it should only be used with larger text.

234 |
235 | -------------------------------------------------------------------------------- /site/utilities.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Utilities 4 | order: 5 5 | key: utilities 6 | --- 7 | 8 | Utility classes are classes that do one thing, and one thing well. They are different _class utilities_, like the one used in 9 | [layouts layer](/layouts). 10 | 11 |
12 | Limited offering 13 |

Feo.css offers a very limited set of utility classes. The expectation is that most design tokens will not remain, especially colors. Therefor, only common utility classes unrelated to design tokens are given, and a limited set of utility classes based on the --token-size-{z} and --token-bp-{z} tokens.

14 |
15 | 16 | {% set items = collections.all | subitems("utilities") %} 17 | {% include "partials/sublist.njk" %} 18 | -------------------------------------------------------------------------------- /site/utilities/click-area.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Click-area 4 | key: utilities 5 | subkey: click-area 6 | --- 7 | 8 | Makes the entire element with this class clickable, based on the 9 | first `` it can find in its (sub-)DOM. It does not 10 | have to be a direct descender. 11 | 12 |
13 | A word of caution 14 |

not all elements support ::after in all browsers, (e.g. tr) in at least Safari. This means that .click-area will expand to the next parent (which could well be the entire page). Always test your work in multiple browsers!

15 |
16 | -------------------------------------------------------------------------------- /site/utilities/contrast.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Auto-contrast 4 | key: utilities 5 | subkey: contrast 6 | --- 7 | 8 | Based on some ['magic'](https://til.jakelazaroff.com/css/swap-between-black-and-white-text-based-on-background-color/), the `.contrast` makes it possible to automatically determine the correct `color` based on a background color (set via the `--contrast-bg` custom property). 9 | 10 |
11 | Did you know? 12 |

This technique is applied to the navigation of this site!

13 |
14 | -------------------------------------------------------------------------------- /site/utilities/counted.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Counted 4 | key: utilities 5 | subkey: counted 6 | --- 7 | 8 | Sets a `--count` on the element with the value of the number of direct children the element has. Up to `10` is facilitated. 9 | -------------------------------------------------------------------------------- /site/utilities/hover-group.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Hover-group 4 | key: utilities 5 | subkey: hover-group 6 | --- 7 | 8 | Makes an entire group more interactive on hover, by making the non-hover times dissappear a bit (lowering their opacity). For an example, look at the navigation of this document website (on desktop). 9 | -------------------------------------------------------------------------------- /site/utilities/indexed.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Indexed 4 | key: utilities 5 | subkey: indexed 6 | --- 7 | 8 | Sets a `--index` property on the direct children, corresponding to the index of the child. Can be used for internal calculations based on this number. Up to `10` are faciliated. 9 | -------------------------------------------------------------------------------- /site/utilities/margins.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Margins 4 | key: utilities 5 | subkey: margins 6 | --- 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
ClassDesign tokenCSS property
m-{z}--token-size-{z}margin
mt-{z}--token-size-{z}margin-top
mr-{z}--token-size-{z}margin-right
mb-{z}--token-size-{z}margin-bottom
ml-{z}--token-size-{z}margin-left
45 |
46 | 47 |
48 | Were is my padding?! 49 |

You might be wondering, where are the padding classes? Well Feo.css is a little opinionated. The layers are build with 'layout' being the most important layer. Layout is about how elements are positioned in relation to eachother. Margin has an impact on that, padding, does not. If you want padding, copy over the src/utilities/margin.css and replace margin with padding.

50 |
51 | -------------------------------------------------------------------------------- /site/utilities/max-width.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Max-width 4 | key: utilities 5 | subkey: max-width 6 | --- 7 | 8 | Based on the `--token-bp-{z}` [tokens](/tokens), control the `max-width` property. 9 | -------------------------------------------------------------------------------- /site/utilities/read-more.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Read more 4 | key: utilities 5 | subkey: read-more 6 | --- 7 | 8 | A simple class that makes works like an "elipsis" for long, multi-line text. 9 | 10 | ## API 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Custom propertyDefaultDescription
--line-count2Sets the -webkit-line-clamp of the targeted element
21 |
22 | -------------------------------------------------------------------------------- /site/utilities/scroll-container.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Scroll container 4 | key: utilities 5 | subkey: scroll-container 6 | --- 7 | 8 | Allows for vertical scroll within the element, enabled by the `contain: size` property/value. This allows it to be easily used in flex containers (e.g. stretched items across the height of the parent). 9 | 10 |
11 | Note 12 |

always test the implementation in various use cases. For instance, on this site, the main content uses this class. However, on smaller screens, the `contain: size` needs to be replaced with `contain: none`, as the height would otherwise not fill the available space (due to flex-wrapping).

13 |
14 | -------------------------------------------------------------------------------- /site/utilities/typography.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Typography 4 | key: utilities 5 | subkey: typography 6 | --- 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
ClassDesign tokenCSS property
size-{z}--token-size-{z}font-size
bold/regular/italic-Various
text-center/text-start/text-end-Sets the text-align property.
35 |
36 | -------------------------------------------------------------------------------- /site/utilities/visually-hidden.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base.njk 3 | title: Visually-hidden 4 | key: utilities 5 | subkey: visually-hidden 6 | --- 7 | 8 | Makes the element visually hidden for users, but accessible for screen-readers, etc. 9 | -------------------------------------------------------------------------------- /src/blocks/accordion.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Accordion component 3 | */ 4 | details.accordion { 5 | --accordion-border: var(--surface-1); 6 | --accordion-radius: 8px; 7 | --accordion-surface: var(--surface-1); 8 | 9 | border: 3px solid var(--accordion-border); 10 | border-radius: var(--accordion-radius); 11 | } 12 | 13 | /* Assumes only and one other div */ 14 | details.accordion > * { 15 | padding: 2px 12px; 16 | } 17 | 18 | details.accordion summary { 19 | font-weight: bold; 20 | background-color: var(--accordion-surface); 21 | /* Offset needed to make the borders play nice with the details */ 22 | border-radius: calc(var(--accordion-radius) - 3px); 23 | } 24 | 25 | /* Changes in styling when opened */ 26 | details.accordion[open] summary { 27 | border-bottom-right-radius: 0px; 28 | border-bottom-left-radius: 0px; 29 | border-bottom: 1px solid var(--accordion-border); 30 | } 31 | 32 | /** 33 | * Animation when opening the accordion 34 | */ 35 | @keyframes details-show { 36 | from { 37 | opacity: 0; 38 | transform: translateY(-0.5em); 39 | } 40 | } 41 | details.accordion[open] > *:not(summary) { 42 | animation: slide-down-1 250ms ease-in-out; 43 | padding: var(--token-size-00); 44 | } 45 | -------------------------------------------------------------------------------- /src/blocks/form.css: -------------------------------------------------------------------------------- 1 | form { 2 | --form-radius: 8px; 3 | --form-border: var(--text-2); 4 | --form-focus: var(--text-0); 5 | --form-disabled-surface: var(--surface-1); 6 | --form-disabled-padding: var(--token-size-000); 7 | --form-disabled-border: var(--text-2); 8 | } 9 | 10 | label { 11 | display: flex; 12 | flex-direction: column; 13 | color: var(--text-2); 14 | font-size: var(--token-size-0); 15 | gap: 4px; 16 | } 17 | 18 | label span { 19 | font-size: var(--token-size-00); 20 | margin-left: var(--form-radius); 21 | } 22 | 23 | /** 24 | * Base styling for form elements 25 | */ 26 | input:not([type="checkbox"]), 27 | select, 28 | textarea { 29 | width: 100%; 30 | padding: 6px var(--token-size-000); 31 | background-color: var(--token-surface-0); 32 | font-size: var(--token-size-00); 33 | border: 1px solid var(--form-border); 34 | border-radius: var(--form-radius, 4px); 35 | transition: all 100ms; 36 | color: var(--text-0); 37 | } 38 | 39 | input:not(:disabled):not([type="checkbox"]):where(:hover, :focus), 40 | select:not(:disabled):where(:hover, :focus), 41 | textarea:not(:disabled):where(:hover, :focus) { 42 | border: 1px solid var(--form-focus); 43 | outline: 1px solid var(--form-focus); 44 | } 45 | 46 | input:disabled, 47 | textarea:disabled, 48 | select:disabled { 49 | background-color: var(--form-disabled-surface); 50 | border: 1px solid var(--form-disabled-border); 51 | padding-left: var(--form-disabled-padding); 52 | } 53 | 54 | /** 55 | * Adjustments for textarea 56 | */ 57 | textarea { 58 | min-height: 8em; 59 | } 60 | 61 | /** 62 | * Select and datalist 63 | */ 64 | select, 65 | input[list] { 66 | --arrow-icon: url("data:image/svg+xml,%3Csvg width='14' height='9' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cpath id='Path' fill='%232B3A50' d='M7 9L13.0622 0H0.937822L7 9Z'/%3E%3C/svg%3E"); 67 | 68 | appearance: none; 69 | display: block; 70 | background-image: var(--arrow-icon); 71 | background-repeat: no-repeat, repeat; 72 | background-position: 73 | right var(--token-size-000) top 50%, 74 | 0 0; 75 | background-size: 76 | 0.8em auto, 77 | 100%; 78 | } 79 | -------------------------------------------------------------------------------- /src/blocks/table.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Make a table horizontal scroll, in case there is a wrapper div, 3 | * without the need for a custom class. Does nothing in browsers not 4 | * supporting :has 5 | */ 6 | div:has(> table:only-child) { 7 | overflow-x: auto; 8 | /* Ensure that even in flex-start on the parent, tables will be full width and scrollable */ 9 | width: 100%; 10 | } 11 | 12 | /** 13 | * Table component 14 | */ 15 | table { 16 | --table-radius: 6px; 17 | --table-color: var(--surface-1); 18 | 19 | /* Table fills always the available space */ 20 | width: 100%; 21 | border-collapse: collapse; 22 | border-radius: calc(var(--table-radius) * 1.3); 23 | } 24 | 25 | thead { 26 | background: var(--table-color); 27 | } 28 | 29 | table tr:last-child td:first-child { 30 | border-bottom-left-radius: var(--table-radius); 31 | } 32 | 33 | table tr:last-child td:last-child { 34 | border-bottom-right-radius: var(--table-radius); 35 | } 36 | 37 | table tr:first-child th:first-child { 38 | border-top-left-radius: var(--table-radius); 39 | } 40 | 41 | table tr:first-child th:last-child { 42 | border-top-right-radius: var(--table-radius); 43 | } 44 | 45 | td { 46 | background-color: var(--surface-0); 47 | } 48 | 49 | td, 50 | th { 51 | text-align: left; 52 | padding: 4px var(--token-size-000); 53 | } 54 | 55 | tbody tr { 56 | border-top: 1px solid var(--table-color); 57 | } 58 | 59 | table tr:hover td { 60 | background-color: var(--table-color); 61 | } 62 | -------------------------------------------------------------------------------- /src/blocks/toggle.css: -------------------------------------------------------------------------------- 1 | /** 2 | * A toggle (because, who needs checkboxes?) 3 | */ 4 | label:has(.toggle) { 5 | flex-direction: row; 6 | align-items: center; 7 | gap: var(--token-size-00); 8 | } 9 | 10 | input[type="checkbox"].toggle { 11 | --toggle-dot-color: white; 12 | --toggle-checked-color: green; 13 | --toggle-size: var(--token-size-0); 14 | --toggle-space: 3px; 15 | 16 | appearance: none; 17 | position: relative; 18 | display: inline-block; 19 | background: var(--surface-2); 20 | /* Calculate the width and height based on the dot + spacing */ 21 | height: calc(var(--toggle-size) + 2 * var(--toggle-space)); 22 | width: calc(2 * var(--toggle-size) + 2 * var(--toggle-space)); 23 | vertical-align: middle; 24 | border-radius: var(--toggle-size); 25 | box-shadow: 0px 1px 3px #0003 inset; 26 | transition: 0.25s linear background; 27 | } 28 | 29 | input[type="checkbox"].toggle::before { 30 | content: ""; 31 | display: block; 32 | width: var(--toggle-size); 33 | height: var(--toggle-size); 34 | background: var(--toggle-dot-color); 35 | border-radius: calc(var(--toggle-size) * 0.5); 36 | position: absolute; 37 | top: var(--toggle-space); 38 | left: var(--toggle-space); 39 | box-shadow: 0px 1px 3px #0003; 40 | transition: 0.25s linear transform; 41 | transform: translateX(0rem); 42 | } 43 | 44 | input[type="checkbox"].toggle:checked { 45 | background: var(--toggle-checked-color); 46 | } 47 | 48 | /* Assumes the dot moves the same amount as its own size */ 49 | input[type="checkbox"].toggle:checked::before { 50 | transform: translateX(var(--toggle-size)); 51 | } 52 | -------------------------------------------------------------------------------- /src/blocks/tooltip.css: -------------------------------------------------------------------------------- 1 | [data-tooltip] { 2 | --tooltip-pointer-color: var(--text-2); 3 | --tooltip-surface: var(--token-neutral-1); 4 | --tooltip-color: var(--token-neutral-5); 5 | --tooltip-slide-to: translate(-50%, -0.25rem); 6 | --tooltip-caret-slide-to: translate(-50%, 0rem); 7 | 8 | position: relative; 9 | } 10 | 11 | [data-tooltip]:not(a, button, input, [role="button"]) { 12 | border-bottom: 1px dotted var(--tooltip-pointer-color); 13 | text-decoration: none; 14 | cursor: help; 15 | } 16 | 17 | [data-tooltip]::before, 18 | [data-tooltip]::after { 19 | display: block; 20 | z-index: 99; 21 | position: absolute; 22 | bottom: 100%; 23 | left: 50%; 24 | padding: 0.25rem 0.5rem; 25 | overflow: hidden; 26 | transform: translate(-50%, -0.25rem); 27 | border-radius: 4px; 28 | background: var(--tooltip-surface); 29 | content: attr(data-tooltip); 30 | color: var(--tooltip-color); 31 | font-style: normal; 32 | font-weight: normal; 33 | font-size: 0.875rem; 34 | text-decoration: none; 35 | text-overflow: ellipsis; 36 | white-space: nowrap; 37 | opacity: 0; 38 | pointer-events: none; 39 | max-width: var(--token-bp-00); 40 | } 41 | 42 | [data-tooltip]::after { 43 | padding: 0; 44 | transform: translate(-50%, 0rem); 45 | border-top: 0.3rem solid; 46 | border-right: 0.3rem solid transparent; 47 | border-left: 0.3rem solid transparent; 48 | border-radius: 0; 49 | background-color: transparent; 50 | content: ""; 51 | color: var(--tooltip-surface); 52 | } 53 | 54 | [data-tooltip]:focus::before, 55 | [data-tooltip]:hover::before, 56 | [data-tooltip]:focus::after, 57 | [data-tooltip]:hover::after { 58 | opacity: 1; 59 | } 60 | 61 | @media (hover: hover) and (pointer: fine) { 62 | [data-tooltip]:focus::before, 63 | [data-tooltip]:focus::after, 64 | [data-tooltip]:hover::before, 65 | [data-tooltip]:hover::after { 66 | transform: translate(-50%, 0.75rem); 67 | animation-duration: 0.2s; 68 | animation-fill-mode: forwards; 69 | animation-name: tooltip-slide; 70 | opacity: 0; 71 | } 72 | [data-tooltip]:focus::after, 73 | [data-tooltip]:hover::after { 74 | transform: translate(-50%, -0.25rem); 75 | animation-name: tooltip-caret-slide; 76 | } 77 | 78 | @keyframes tooltip-slide { 79 | to { 80 | transform: var(--tooltip-slide-to); 81 | opacity: 1; 82 | } 83 | } 84 | @keyframes tooltip-caret-slide { 85 | 50% { 86 | opacity: 0; 87 | } 88 | to { 89 | transform: var(--tooltip-caret-slide-to); 90 | opacity: 1; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/global/global.css: -------------------------------------------------------------------------------- 1 | /** 2 | * First import the reset and tokens 3 | */ 4 | @import "reset.css"; 5 | @import "tokens.css"; 6 | 7 | /** 8 | * Now do everything else 9 | */ 10 | body { 11 | min-width: var(--token-bp-0); 12 | max-width: 100vw; 13 | font-size: var(--token-size-0); 14 | font-family: var(--sans-serif); 15 | position: relative; 16 | color: var(--text-0); 17 | background-color: var(--surface-0); 18 | } 19 | 20 | :where(a:not([class])) { 21 | text-decoration-skip-ink: auto; 22 | color: currentColor; 23 | } 24 | 25 | /** 26 | * HEADERS 27 | */ 28 | :where(h1, h2, h3) { 29 | font-weight: 600; 30 | } 31 | 32 | h1 { 33 | font-size: var(--token-size-3); 34 | } 35 | h2 { 36 | font-size: var(--token-size-2); 37 | } 38 | h3 { 39 | font-size: var(--token-size-1); 40 | } 41 | 42 | /** 43 | * CODE 44 | */ 45 | code { 46 | font-family: var(--monospace); 47 | font-size: 0.85em; 48 | hyphens: none; 49 | } 50 | 51 | :not(pre) > code { 52 | border: 1px solid var(--surface-2); 53 | background-color: var(--surface-1); 54 | border-radius: 4px; 55 | padding: 2px; 56 | white-space: nowrap; 57 | word-break: break-all; 58 | } 59 | 60 | pre { 61 | border-radius: 8px; 62 | background-color: var(--surface-1); 63 | position: relative; 64 | padding: 0; 65 | width: 100%; 66 | max-width: 100%; 67 | word-wrap: normal; 68 | word-break: normal; 69 | word-spacing: normal; 70 | } 71 | 72 | pre > code { 73 | padding: var(--token-size-000) var(--token-size-00); 74 | font-size: var(--token-size-00); 75 | display: block; 76 | white-space: pre; 77 | overflow-x: auto; 78 | tab-size: 2; 79 | /* fix for increased font-size safari iOS */ 80 | -webkit-text-size-adjust: none; 81 | } 82 | 83 | /** 84 | * SELECTION 85 | */ 86 | ::selection { 87 | color: var(--surface-0) !important; 88 | background: var(--text-0) !important; 89 | } 90 | -------------------------------------------------------------------------------- /src/global/reset.css: -------------------------------------------------------------------------------- 1 | /* Box sizing rules */ 2 | *, 3 | *::before, 4 | *::after { 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Remove all default margin/border styles of the "User-Agent-Stylesheet" */ 9 | *:where(:not(iframe, canvas, img, svg, video):not(svg *)) { 10 | margin: 0; 11 | border: 0; 12 | } 13 | 14 | html { 15 | height: 100%; 16 | /* avoid scaling on mobile */ 17 | -moz-text-size-adjust: none; 18 | -webkit-text-size-adjust: none; 19 | text-size-adjust: none; 20 | /* iOS webkit fix */ 21 | height: -webkit-fill-available; 22 | /* allow of transitions on height */ 23 | interpolate-size: allow-keywords; 24 | /* as WCAG dictates scroll in only one direction */ 25 | overflow-x: hidden; 26 | } 27 | 28 | /* Set core root defaults */ 29 | html:focus-within { 30 | scroll-behavior: smooth; 31 | } 32 | 33 | /* Set core body defaults */ 34 | body { 35 | text-rendering: optimizeSpeed; 36 | min-height: 100vh; 37 | /* iOS webkit fix */ 38 | min-height: -webkit-fill-available; 39 | line-height: 1.4; 40 | } 41 | 42 | /* Remove list styles on ul, ol elements, if they have any other class */ 43 | :where(ul[role], ol[role]) { 44 | list-style: none; 45 | padding-left: 0; 46 | } 47 | 48 | :where(ul, ol):not([role]) li { 49 | padding-left: 0.35em; 50 | } 51 | 52 | /* Set shorter line heights on headings and interactive elements */ 53 | :where(h1, h2, h3, h4, button, input, label) { 54 | line-height: 1.1; 55 | } 56 | 57 | /* Balance text wrapping on headings */ 58 | :where(h1, h2, h3, h4) { 59 | text-wrap: balance; 60 | } 61 | 62 | /* Baseline for default links */ 63 | :where(a:not([class])), 64 | :where(a:not([class]):visited) { 65 | color: currentColor; 66 | text-decoration-color: inherit; 67 | touch-action: manipulation; 68 | text-decoration-thickness: 1px; 69 | text-decoration-skip-ink: auto; 70 | text-underline-offset: 0.1em; 71 | } 72 | 73 | /* Make images easier to work with */ 74 | :where(img, picture, svg) { 75 | display: block; 76 | max-width: 100%; 77 | } 78 | 79 | /* default input style */ 80 | :where(input, button, textarea, select) { 81 | font-family: inherit; 82 | font-size: inherit; 83 | } 84 | 85 | /* Anything that has been anchored to should have extra scroll margin */ 86 | :target { 87 | scroll-margin-block: 5ex; 88 | } 89 | 90 | /* Removes outlines for mouse users */ 91 | :active:not(:focus-visible) { 92 | outline: none; 93 | } 94 | 95 | /* Removes outlines for mouse users */ 96 | :focus:not(:focus-visible) { 97 | outline: none; 98 | } 99 | 100 | /* Makes focus through keyboard better */ 101 | :focus-visible { 102 | outline: 1px solid currentColor; 103 | } 104 | /* Hide elements */ 105 | [hidden] { 106 | display: none; 107 | } 108 | 109 | /* Set the correct cursor */ 110 | :disabled, 111 | [aria-disabled="true"], 112 | [disabled="true"] { 113 | cursor: not-allowed; 114 | } 115 | 116 | [aria-controls] { 117 | cursor: pointer; 118 | } 119 | 120 | /* disallows scroll on background */ 121 | body:has(dialog[open]) { 122 | overflow: hidden; 123 | } 124 | 125 | /** Basic smooth view transitions, when supported */ 126 | @view-transition { 127 | navigation: auto; 128 | } 129 | 130 | /* Remove all animations and transitions for 131 | people that prefer not to see them */ 132 | @media (prefers-reduced-motion: reduce) { 133 | html:focus-within { 134 | scroll-behavior: auto !important; 135 | } 136 | 137 | @view-transition { 138 | navigation: none; 139 | } 140 | 141 | *, 142 | *::before, 143 | *::after { 144 | transition-duration: 0.01ms !important; 145 | animation-duration: 0.01ms !important; 146 | animation-iteration-count: 1 !important; 147 | scroll-behavior: auto !important; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/global/tokens.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /** 3 | * COLORS 4 | */ 5 | /** NEUTRALS */ 6 | --token-neutral-0: hsl(215, 12%, 12%); 7 | --token-neutral-1: hsl(215, 12%, 22%); 8 | --token-neutral-2: hsl(215, 12%, 36%); 9 | --token-neutral-3: hsl(215, 24%, 78%); 10 | --token-neutral-4: hsl(215, 24%, 92%); 11 | --token-neutral-5: hsl(215, 24%, 98%); 12 | 13 | /** 14 | * BREAK POINTS 15 | */ 16 | --token-bp-0: 20rem; 17 | --token-bp-000: calc(var(--token-bp-00) / 1.33); 18 | --token-bp-00: calc(var(--token-bp-0) / 1.33); 19 | --token-bp-1: calc(var(--token-bp-0) * 1.33); 20 | --token-bp-2: calc(var(--token-bp-1) * 1.33); 21 | --token-bp-3: calc(var(--token-bp-2) * 1.33); 22 | --token-bp-4: calc(var(--token-bp-3) * 1.33); 23 | --token-bp-5: calc(var(--token-bp-4) * 1.33); 24 | 25 | /** 26 | * SPACING & SIZING 27 | */ 28 | 29 | /* Value between 0px and 5px, equal to 20% increase of 1rem */ 30 | --feo-scale: calc(5 * (min(100vw, 1240px) - 320px) / 920); 31 | 32 | /* Scale of 1.333 */ 33 | --token-size-000: calc(0.65rem + 0.65 * var(--feo-scale)); 34 | --token-size-00: calc(0.8125rem + 0.8125 * var(--feo-scale)); 35 | --token-size-0: calc(1rem + var(--feo-scale)); 36 | --token-size-1: calc(1.33rem + 1.33 * var(--feo-scale)); 37 | --token-size-2: calc(1.78rem + 1.78 * var(--feo-scale)); 38 | --token-size-3: calc(2.37rem + 2.37 * var(--feo-scale)); 39 | --token-size-4: calc(3.16rem + 3.16 * var(--feo-scale)); 40 | --token-size-5: calc(4.21rem + 4.21 * var(--feo-scale)); 41 | 42 | /** 43 | * FONT FAMILY 44 | */ 45 | --monospace: 46 | ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, 47 | "DejaVu Sans Mono", monospace; 48 | --serif: Charter, "Bitstream Charter", "Sitka Text", Cambria, serif; 49 | --sans-serif: 50 | system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, 51 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 52 | 53 | /** 54 | * THEME 55 | */ 56 | color-scheme: light; 57 | --text-0: var(--token-neutral-0); 58 | --text-1: var(--token-neutral-1); 59 | --text-2: var(--token-neutral-2); 60 | --surface-0: var(--token-neutral-5); 61 | --surface-1: var(--token-neutral-4); 62 | --surface-2: var(--token-neutral-3); 63 | 64 | /** 65 | * ICONS 66 | */ 67 | --icon-loading: url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E"); 68 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 69 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); 70 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); 71 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); 72 | } 73 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @layer global, layout, blocks, utilities; 2 | 3 | /** global */ 4 | @import "global/reset.css" layer(global); 5 | @import "global/tokens.css" layer(global); 6 | @import "global/global.css" layer(global); 7 | 8 | /** layout */ 9 | @import "layout/center.css" layer(layout); 10 | @import "layout/cluster.css" layer(layout); 11 | @import "layout/equal.css" layer(layout); 12 | @import "layout/fifty.css" layer(layout); 13 | @import "layout/flex.css" layer(layout); 14 | @import "layout/grid.css" layer(layout); 15 | @import "layout/pancake.css" layer(layout); 16 | @import "layout/pile.css" layer(layout); 17 | @import "layout/repel.css" layer(layout); 18 | @import "layout/sidebar.css" layer(layout); 19 | @import "layout/stack.css" layer(layout); 20 | @import "layout/switcher.css" layer(layout); 21 | @import "layout/tiles.css" layer(layout); 22 | @import "layout/utilities.css" layer(layout); 23 | 24 | /** blocks */ 25 | @import "blocks/accordion.css" layer(blocks); 26 | @import "blocks/form.css" layer(blocks); 27 | @import "blocks/toggle.css" layer(blocks); 28 | @import "blocks/table.css" layer(blocks); 29 | @import "blocks/tooltip.css" layer(blocks); 30 | 31 | /** utilities */ 32 | @import "utilities/animations.css" layer(utilities); 33 | @import "utilities/click-area.css" layer(utilities); 34 | @import "utilities/contrast.css" layer(utilities); 35 | @import "utilities/counted.css" layer(utilities); 36 | @import "utilities/hover-group.css" layer(utilities); 37 | @import "utilities/indexed.css" layer(utilities); 38 | @import "utilities/loading.css" layer(blocks); 39 | @import "utilities/margin.css" layer(utilities); 40 | @import "utilities/read-more.css" layer(utilities); 41 | @import "utilities/scroll-container.css" layer(utilities); 42 | @import "utilities/typography.css" layer(utilities); 43 | @import "utilities/visually-hidden.css" layer(utilities); 44 | @import "utilities/width.css" layer(utilities); 45 | @import "utilities/z-index.css" layer(utilities); 46 | -------------------------------------------------------------------------------- /src/layout/center.css: -------------------------------------------------------------------------------- 1 | /** 2 | * horizontal center of layout elements 3 | * 4 | * --layout-threshold: sets the width of the element that is centered 5 | * 6 | * NOTE: exceptions in child elements can be made width: 7 | * [class~="center"] > * { 8 | * margin-left: 50%; 9 | * transform: translateX(-50%); 10 | * width: 100vw; 11 | * max-width: X; 12 | * } 13 | */ 14 | .center { 15 | --layout-threshold: 100%; 16 | 17 | margin-inline: auto; 18 | width: 100%; 19 | /* required to ensure certain elements that should be side-scrollable don't push out the layout at 100% */ 20 | max-width: min(100%, var(--layout-threshold)); 21 | } 22 | -------------------------------------------------------------------------------- /src/layout/cluster.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Groups items in such a way that a 'cluster' is created that automatically 3 | determines how many items can be on a single row (e.g. tag cloud) 4 | * 5 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 6 | */ 7 | .cluster { 8 | --layout-gap: 0; 9 | --layout-align: center; 10 | 11 | display: flex; 12 | flex-wrap: wrap; 13 | align-items: var(--layout-align); 14 | justify-content: flex-start; 15 | gap: var(--layout-gap); 16 | } 17 | -------------------------------------------------------------------------------- /src/layout/equal.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Makes a wrapper fill the available space 3 | * Makes all children equal size in the available space 4 | */ 5 | 6 | .equal { 7 | --layout-gap: 0; 8 | --layout-align: center; 9 | --layout-direction: row; 10 | 11 | display: flex; 12 | flex-direction: var(--layout-direction); 13 | flex-wrap: nowrap; 14 | align-items: var(--layout-align); 15 | gap: var(--layout-gap); 16 | width: 100%; 17 | } 18 | 19 | .equal > * { 20 | flex: 1 1 0px; 21 | } 22 | -------------------------------------------------------------------------------- /src/layout/fifty.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Makes a fifty-fifty layout in case there are two elements. 3 | * 4 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 5 | * --layout-threshold: set the min-width of child elements 6 | */ 7 | .fifty { 8 | --layout-threshold: 0; 9 | --layout-gap: 0; 10 | 11 | display: flex; 12 | flex-direction: row; 13 | flex-wrap: wrap; 14 | gap: var(--layout-gap); 15 | } 16 | 17 | .fifty > * { 18 | flex-grow: 1; 19 | flex-basis: var(--layout-threshold); 20 | } 21 | -------------------------------------------------------------------------------- /src/layout/flex.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Default flexbox utility classes to control layout 3 | * 4 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 5 | * --layout-direction: sets flex-direction 6 | * --layout-align: sets align-items 7 | */ 8 | .flex { 9 | --layout-gap: 0; 10 | --layout-direction: row; 11 | --layout-align: center; 12 | 13 | display: flex; 14 | flex-direction: var(--layout-direction); 15 | align-items: var(--layout-align); 16 | gap: var(--layout-gap); 17 | } 18 | 19 | /** 20 | * Utility functions for flex containers 21 | */ 22 | .grow { 23 | flex-grow: 1; 24 | } 25 | 26 | .self-start { 27 | align-self: self-start; 28 | } 29 | 30 | .self-center { 31 | align-self: center; 32 | } 33 | 34 | .self-stretch { 35 | align-self: stretch; 36 | } 37 | 38 | .self-end { 39 | align-self: self-end; 40 | } 41 | -------------------------------------------------------------------------------- /src/layout/grid.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Default grid layout with equal columns. 3 | * 4 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 5 | * --layout-amount: sets a fixed number of columns 6 | */ 7 | .grid { 8 | --layout-amount: 2; 9 | --layout-gap: 0; 10 | 11 | display: grid; 12 | grid-template-columns: repeat(var(--layout-amount), 1fr); 13 | gap: var(--layout-gap); 14 | } 15 | -------------------------------------------------------------------------------- /src/layout/pancake.css: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple pattern on a three (or more) layer layout in which the second layer fills the available space 3 | * 4 | * --layout-gap: sets gap between elements 5 | */ 6 | .pancake { 7 | --layout-gap: 0; 8 | 9 | display: grid; 10 | grid-template-rows: auto 1fr auto; 11 | gap: var(--layout-gap); 12 | } 13 | -------------------------------------------------------------------------------- /src/layout/pile.css: -------------------------------------------------------------------------------- 1 | /** 2 | * A stack of position items on top of eachother (depth) 3 | * 4 | * --layout-ratio: can be used to set a ratio, but empty by default 5 | */ 6 | .pile { 7 | display: grid; 8 | grid: [pile] 1fr / [pile] 1fr; 9 | place-items: center; 10 | aspect-ratio: var(--layout-ratio); 11 | } 12 | 13 | .pile > * { 14 | grid-area: pile; 15 | } 16 | -------------------------------------------------------------------------------- /src/layout/repel.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout to stretch items across the main axis and have the space in between 3 | * 4 | * --layout-gap: sets gap between child elements 5 | * --layout-direction: sets the flex-direction 6 | */ 7 | .repel { 8 | --layout-gap: 0; 9 | --layout-direction: row; 10 | --layout-align: center; 11 | 12 | display: flex; 13 | flex-direction: var(--layout-direction); 14 | flex-wrap: wrap; 15 | align-items: var(--layout-align); 16 | justify-content: space-between; 17 | gap: var(--layout-gap); 18 | } 19 | -------------------------------------------------------------------------------- /src/layout/sidebar.css: -------------------------------------------------------------------------------- 1 | /** 2 | * A responsive 2-column layout with one fixed width column, and one flexible 3 | * column. It switches from vertical to horizontal when the flexible column 4 | * becomes less than --content of the total (i.e. a bit more than 5 | * ) 6 | * 7 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 8 | * --layout-threshold: sets the absolute value of the fixed-width column 9 | * --layout-inline-size: the minimum size of the responsive column 10 | * 11 | * NOTE: use the --left and --right classes to set which column is the fixed bar 12 | */ 13 | .sidebar { 14 | --layout-gap: 0; 15 | --layout-threshold: 0; 16 | --layout-inline-size: 60%; 17 | 18 | display: flex; 19 | flex-wrap: wrap; 20 | gap: var(--layout-gap); 21 | align-items: stretch; 22 | } 23 | 24 | .sidebar > * { 25 | flex-basis: var(--layout-threshold); 26 | flex-grow: 1; 27 | min-width: min(100%, var(--layout-threshold)); 28 | } 29 | 30 | .sidebar.--left > :last-child, 31 | .sidebar.--right > :first-child { 32 | flex-basis: 0; 33 | flex-grow: 999; 34 | 35 | /* wraps when content becomes less than X */ 36 | /* similar to min-width in horizontal reading direction */ 37 | min-inline-size: var(--layout-inline-size); 38 | } 39 | -------------------------------------------------------------------------------- /src/layout/stack.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Stack Layout Utility 3 | * ------------------- 4 | * Originally inspired by: https://piccalil.li/blog/my-favourite-3-lines-of-css/ 5 | * 6 | * The stack utility creates consistent vertical spacing between elements. 7 | * It follows the "lobotomized owl" selector pattern (* + *) to add 8 | * margin between consecutive sibling elements. 9 | * 10 | * Usage: 11 | *
12 | *

First element

13 | *

Second element (gets margin-top)

14 | *

Third element (gets margin-top)

15 | *
16 | * 17 | * You can customize the gap by setting --layout-gap on the .stack element: 18 | *
19 | * ... 20 | *
21 | */ 22 | 23 | .stack > * + * { 24 | --layout-gap: 0; 25 | 26 | margin-block-start: var(--layout-gap, 1em); 27 | } -------------------------------------------------------------------------------- /src/layout/switcher.css: -------------------------------------------------------------------------------- 1 | /** 2 | * A layout of X columns that switches from horizontal to vertical the moment 3 | * the width of parent becomes less than a set value. 4 | * 5 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 6 | * --layout-threshold: the min-width of a single child elements 7 | * --layout-direction: sets flex-direction 8 | */ 9 | 10 | .switcher { 11 | --layout-gap: 0; 12 | --layout-threshold: 0; 13 | --layout-direction: row; 14 | 15 | display: flex; 16 | flex-direction: var(--layout-direction); 17 | flex-wrap: wrap; 18 | gap: var(--layout-gap); 19 | } 20 | 21 | .switcher > * { 22 | flex-grow: 1; 23 | flex-basis: calc((var(--layout-threshold) - 100%) * 999); 24 | } 25 | -------------------------------------------------------------------------------- /src/layout/tiles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * tile-based sytem that determines the amount of columns based on a min-width 3 | * for each tile. The wrapper fills the entire available horizonal space 4 | * 5 | * --layout-threshold: sets the minimum width of each tile 6 | * --layout-gap: set the gap between the child elements (horizontal & vertical) 7 | */ 8 | .tiles { 9 | --layout-threshold: 0; 10 | --layout-gap: 0; 11 | 12 | display: grid; 13 | grid-template-columns: repeat( 14 | auto-fit, 15 | minmax(min(var(--layout-threshold), 100%), 1fr) 16 | ); 17 | gap: var(--layout-gap); 18 | } 19 | -------------------------------------------------------------------------------- /src/layout/utilities.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility classes to control the shared --layout-X properties amongst the 3 | * layout classes 4 | */ 5 | 6 | /* Flex direction */ 7 | .--column { 8 | --layout-direction: column; 9 | } 10 | .--row { 11 | --layout-direction: row; 12 | } 13 | /* helpers around the cross axis */ 14 | .--start { 15 | --layout-align: flex-start; 16 | } 17 | .--end { 18 | --layout-align: flex-end; 19 | } 20 | .--center { 21 | --layout-align: center; 22 | } 23 | .--stretch { 24 | --layout-align: stretch; 25 | } 26 | /* Control the amount API */ 27 | .--amount-1 { 28 | --layout-amount: 1; 29 | } 30 | .--amount-2 { 31 | --layout-amount: 2; 32 | } 33 | .--amount-3 { 34 | --layout-amount: 3; 35 | } 36 | .--amount-4 { 37 | --layout-amount: 4; 38 | } 39 | .--amount-5 { 40 | --layout-amount: 5; 41 | } 42 | .--amount-6 { 43 | --layout-amount: 6; 44 | } 45 | .--amount-7 { 46 | --layout-amount: 7; 47 | } 48 | .--amount-8 { 49 | --layout-amount: 8; 50 | } 51 | .--amount-9 { 52 | --layout-amount: 9; 53 | } 54 | .--amount-10 { 55 | --layout-amount: 10; 56 | } 57 | .--amount-11 { 58 | --layout-amount: 11; 59 | } 60 | .--amount-12 { 61 | --layout-amount: 12; 62 | } 63 | /* Control the gap API */ 64 | .--gap-none { 65 | --layout-gap: none; 66 | } 67 | .--gap-000 { 68 | --layout-gap: var(--token-size-000); 69 | } 70 | .--gap-00 { 71 | --layout-gap: var(--token-size-00); 72 | } 73 | .--gap-0 { 74 | --layout-gap: var(--token-size-0); 75 | } 76 | .--gap-1 { 77 | --layout-gap: var(--token-size-1); 78 | } 79 | .--gap-2 { 80 | --layout-gap: var(--token-size-2); 81 | } 82 | .--gap-3 { 83 | --layout-gap: var(--token-size-3); 84 | } 85 | .--gap-4 { 86 | --layout-gap: var(--token-size-4); 87 | } 88 | .--gap-5 { 89 | --layout-gap: var(--token-size-5); 90 | } 91 | /* Control the threshold API */ 92 | .--threshold-000 { 93 | --layout-threshold: var(--token-bp-000); 94 | } 95 | .--threshold-00 { 96 | --layout-threshold: var(--token-bp-00); 97 | } 98 | .--threshold-0 { 99 | --layout-threshold: var(--token-bp-0); 100 | } 101 | .--threshold-1 { 102 | --layout-threshold: var(--token-bp-1); 103 | } 104 | .--threshold-2 { 105 | --layout-threshold: var(--token-bp-2); 106 | } 107 | .--threshold-3 { 108 | --layout-threshold: var(--token-bp-3); 109 | } 110 | .--threshold-4 { 111 | --layout-threshold: var(--token-bp-4); 112 | } 113 | .--threshold-5 { 114 | --layout-threshold: var(--token-bp-5); 115 | } 116 | -------------------------------------------------------------------------------- /src/utilities/animations.css: -------------------------------------------------------------------------------- 1 | @keyframes wiggle { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 30% { 6 | transform: rotate(60deg); 7 | } 8 | 60% { 9 | transform: rotate(-45deg); 10 | } 11 | 80% { 12 | transform: rotate(15deg); 13 | } 14 | 100% { 15 | transform: rotate(0deg); 16 | } 17 | } 18 | 19 | @keyframes fade-in { 20 | from { 21 | opacity: 0; 22 | } 23 | to { 24 | opacity: 1; 25 | } 26 | } 27 | 28 | @keyframes slide-up-1 { 29 | from { 30 | transform: translateY(1rem); 31 | } 32 | } 33 | 34 | @keyframes slide-up-2 { 35 | from { 36 | transform: translateY(2rem); 37 | } 38 | } 39 | 40 | @keyframes slide-down-1 { 41 | from { 42 | transform: translateY(-1rem); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utilities/click-area.css: -------------------------------------------------------------------------------- 1 | /* Makes an entire box clickable based on a single child anchor */ 2 | /* NOTE: not usable on table rows, as ::after is not supported on Safari & Chrome */ 3 | 4 | .click-area { 5 | position: relative; 6 | } 7 | 8 | .click-area a { 9 | cursor: pointer; 10 | } 11 | 12 | .click-area a::after { 13 | position: absolute; 14 | inset: 0; 15 | content: ""; 16 | } 17 | -------------------------------------------------------------------------------- /src/utilities/contrast.css: -------------------------------------------------------------------------------- 1 | /* https://til.jakelazaroff.com/css/swap-between-black-and-white-text-based-on-background-color/ */ 2 | 3 | .contrast { 4 | background-color: var(--contrast-bg); 5 | /* This swaps between white or black based on the background color. */ 6 | /* After testing against all RGB background colors, 49.44 minimizes the number of WCAG 4.5:1 contrast failures. */ 7 | color: lch(from var(--contrast-bg) calc((49.44 - l) * infinity) 0 0); 8 | } 9 | -------------------------------------------------------------------------------- /src/utilities/counted.css: -------------------------------------------------------------------------------- 1 | .counted:has(> *:nth-child(1)) { 2 | --count: 1; 3 | } 4 | .counted:has(> *:nth-child(2)) { 5 | --count: 2; 6 | } 7 | .counted:has(> *:nth-child(3)) { 8 | --count: 3; 9 | } 10 | .counted:has(> *:nth-child(4)) { 11 | --count: 4; 12 | } 13 | .counted:has(> *:nth-child(5)) { 14 | --count: 5; 15 | } 16 | .counted:has(> *:nth-child(6)) { 17 | --count: 6; 18 | } 19 | .counted:has(> *:nth-child(7)) { 20 | --count: 7; 21 | } 22 | .counted:has(> *:nth-child(8)) { 23 | --count: 8; 24 | } 25 | .counted:has(> *:nth-child(9)) { 26 | --count: 9; 27 | } 28 | .counted:has(> *:nth-child(10)) { 29 | --count: 10; 30 | } 31 | -------------------------------------------------------------------------------- /src/utilities/darken.css: -------------------------------------------------------------------------------- 1 | /* bg-darken auto based on hover */ 2 | .hover\:bg-darken-5 { 3 | /* darken for 5% */ 4 | } 5 | -------------------------------------------------------------------------------- /src/utilities/hover-group.css: -------------------------------------------------------------------------------- 1 | /** 2 | * HOVER GROUP 3 | * add interactive elements on hover. partly hides non-hover elements and 4 | * enlarges hover element 5 | */ 6 | .hover-group { 7 | --opacity: 0.4; 8 | } 9 | 10 | .hover-group > * { 11 | transition: all 0.25s; 12 | } 13 | 14 | /** check if hover is available or not, is not available on mobile */ 15 | @media (hover: hover) { 16 | .hover-group:hover > *:not(:hover) { 17 | opacity: var(--opacity); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utilities/indexed.css: -------------------------------------------------------------------------------- 1 | .indexed > *:nth-child(1) { 2 | --index: 1; 3 | } 4 | .indexed > *:nth-child(2) { 5 | --index: 2; 6 | } 7 | .indexed > *:nth-child(3) { 8 | --index: 3; 9 | } 10 | .indexed > *:nth-child(4) { 11 | --index: 4; 12 | } 13 | .indexed > *:nth-child(5) { 14 | --index: 5; 15 | } 16 | .indexed > *:nth-child(6) { 17 | --index: 6; 18 | } 19 | .indexed > *:nth-child(7) { 20 | --index: 7; 21 | } 22 | .indexed > *:nth-child(8) { 23 | --index: 8; 24 | } 25 | .indexed > *:nth-child(9) { 26 | --index: 9; 27 | } 28 | .indexed > *:nth-child(10) { 29 | --index: 10; 30 | } 31 | -------------------------------------------------------------------------------- /src/utilities/loading.css: -------------------------------------------------------------------------------- 1 | [aria-busy="true"]:not(input, select, textarea, html, form) { 2 | white-space: nowrap; 3 | } 4 | 5 | [aria-busy="true"]:not(input, select, textarea, html, form):not( 6 | :empty 7 | )::before { 8 | margin-inline-end: calc(var(--token-size-000) / 2); 9 | } 10 | 11 | [aria-busy="true"]::before { 12 | display: inline-block; 13 | width: 1em; 14 | height: 1em; 15 | background-image: url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E"); 16 | background-size: 1em auto; 17 | background-repeat: no-repeat; 18 | content: ""; 19 | vertical-align: -0.125em; 20 | } 21 | -------------------------------------------------------------------------------- /src/utilities/margin.css: -------------------------------------------------------------------------------- 1 | .m-000 { 2 | margin: var(--token-size-000); 3 | } 4 | .m-00 { 5 | margin: var(--token-size-00); 6 | } 7 | .m-0 { 8 | margin: var(--token-size-0); 9 | } 10 | .m-1 { 11 | margin: var(--token-size-1); 12 | } 13 | .m-2 { 14 | margin: var(--token-size-2); 15 | } 16 | .m-3 { 17 | margin: var(--token-size-3); 18 | } 19 | .m-4 { 20 | margin: var(--token-size-4); 21 | } 22 | .m-5 { 23 | margin: var(--token-size-5); 24 | } 25 | .mb-000 { 26 | margin-bottom: var(--token-size-000); 27 | } 28 | .mb-00 { 29 | margin-bottom: var(--token-size-00); 30 | } 31 | .mb-0 { 32 | margin-bottom: var(--token-size-0); 33 | } 34 | .mb-1 { 35 | margin-bottom: var(--token-size-1); 36 | } 37 | .mb-2 { 38 | margin-bottom: var(--token-size-2); 39 | } 40 | .mb-3 { 41 | margin-bottom: var(--token-size-3); 42 | } 43 | .mb-4 { 44 | margin-bottom: var(--token-size-4); 45 | } 46 | .mb-5 { 47 | margin-bottom: var(--token-size-5); 48 | } 49 | .mt-000 { 50 | margin-top: var(--token-size-000); 51 | } 52 | .mt-00 { 53 | margin-top: var(--token-size-00); 54 | } 55 | .mt-0 { 56 | margin-top: var(--token-size-0); 57 | } 58 | .mt-1 { 59 | margin-top: var(--token-size-1); 60 | } 61 | .mt-2 { 62 | margin-top: var(--token-size-2); 63 | } 64 | .mt-3 { 65 | margin-top: var(--token-size-3); 66 | } 67 | .mt-4 { 68 | margin-top: var(--token-size-4); 69 | } 70 | .mt-5 { 71 | margin-top: var(--token-size-5); 72 | } 73 | .ml-000 { 74 | margin-left: var(--token-size-000); 75 | } 76 | .ml-00 { 77 | margin-left: var(--token-size-00); 78 | } 79 | .ml-0 { 80 | margin-left: var(--token-size-0); 81 | } 82 | .ml-1 { 83 | margin-left: var(--token-size-1); 84 | } 85 | .ml-2 { 86 | margin-left: var(--token-size-2); 87 | } 88 | .ml-3 { 89 | margin-left: var(--token-size-3); 90 | } 91 | .ml-4 { 92 | margin-left: var(--token-size-4); 93 | } 94 | .ml-5 { 95 | margin-left: var(--token-size-5); 96 | } 97 | .mr-000 { 98 | margin-right: var(--token-size-000); 99 | } 100 | .mr-00 { 101 | margin-right: var(--token-size-00); 102 | } 103 | .mr-0 { 104 | margin-right: var(--token-size-0); 105 | } 106 | .mr-1 { 107 | margin-right: var(--token-size-1); 108 | } 109 | .mr-2 { 110 | margin-right: var(--token-size-2); 111 | } 112 | .mr-3 { 113 | margin-right: var(--token-size-3); 114 | } 115 | .mr-4 { 116 | margin-right: var(--token-size-4); 117 | } 118 | .mr-5 { 119 | margin-right: var(--token-size-5); 120 | } 121 | -------------------------------------------------------------------------------- /src/utilities/read-more.css: -------------------------------------------------------------------------------- 1 | .read-more { 2 | --line-count: 2; 3 | 4 | display: -webkit-box; 5 | -webkit-line-clamp: var(--line-count); 6 | -webkit-box-orient: vertical; 7 | overflow: hidden; 8 | } 9 | -------------------------------------------------------------------------------- /src/utilities/scroll-container.css: -------------------------------------------------------------------------------- 1 | .scroll-container { 2 | contain: size; 3 | overflow-y: auto; 4 | /* required to ensure flex column layouts do not wrap to the side */ 5 | flex-wrap: nowrap; 6 | } 7 | -------------------------------------------------------------------------------- /src/utilities/typography.css: -------------------------------------------------------------------------------- 1 | /** Font-sizes */ 2 | .size-000 { 3 | font-size: var(--token-size-000); 4 | } 5 | .size-00 { 6 | font-size: var(--token-size-00); 7 | } 8 | .size-0 { 9 | font-size: var(--token-size-0); 10 | } 11 | .size-1 { 12 | font-size: var(--token-size-1); 13 | } 14 | .size-2 { 15 | font-size: var(--token-size-2); 16 | } 17 | .size-3 { 18 | font-size: var(--token-size-3); 19 | } 20 | .size-4 { 21 | font-size: var(--token-size-4); 22 | } 23 | .size-5 { 24 | font-size: var(--token-size-5); 25 | } 26 | /** Styles */ 27 | .bold { 28 | font-weight: 600; 29 | } 30 | .regular { 31 | font-weight: 400; 32 | } 33 | .italic { 34 | font-style: italic; 35 | } 36 | .text-center { 37 | text-align: center; 38 | } 39 | .text-end { 40 | text-align: end; 41 | } 42 | .text-start { 43 | text-align: start; 44 | } 45 | -------------------------------------------------------------------------------- /src/utilities/visually-hidden.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Visually hides element from users 3 | */ 4 | .visually-hidden, 5 | .sr-only { 6 | position: absolute; 7 | width: 1px; 8 | height: 1px; 9 | padding: 0; 10 | margin: -1px; 11 | overflow: hidden; 12 | clip: rect(0, 0, 0, 0); 13 | border: 0; 14 | } 15 | -------------------------------------------------------------------------------- /src/utilities/width.css: -------------------------------------------------------------------------------- 1 | .maxw-000 { 2 | max-width: var(--token-bp-000); 3 | } 4 | 5 | .maxw-00 { 6 | max-width: var(--token-bp-00); 7 | } 8 | 9 | .maxw-0 { 10 | max-width: var(--token-bp-0); 11 | } 12 | 13 | .maxw-1 { 14 | max-width: var(--token-bp-1); 15 | } 16 | 17 | .maxw-2 { 18 | max-width: var(--token-bp-2); 19 | } 20 | 21 | .maxw-3 { 22 | max-width: var(--token-bp-3); 23 | } 24 | 25 | .maxw-4 { 26 | max-width: var(--token-bp-4); 27 | } 28 | 29 | .maxw-5 { 30 | max-width: var(--token-bp-5); 31 | } 32 | -------------------------------------------------------------------------------- /src/utilities/z-index.css: -------------------------------------------------------------------------------- 1 | .z-indexed { 2 | z-index: calc(infinity); 3 | } 4 | --------------------------------------------------------------------------------