├── .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 | [](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 |
36 |
37 | {% include "svg/logo.svg" %}
38 | Feo.css
39 | v{{ pkg.version }}
40 |
41 | {% include "partials/navigation.njk" %}
42 |
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 |
2 | home
3 | {% if key !== "home" %}/ {% endif %}
4 | {% if key !== "home" %}{{key}} {% endif %}
5 | {% if subkey %}/ {% endif %}
6 | {% if subkey %}{{title}} {% endif %}
7 |
--------------------------------------------------------------------------------
/site/_includes/partials/footer.njk:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/site/_includes/partials/navigation.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% for itemKey, item in collections.all | navigation %}
4 |
5 | {{ item.title }}
6 | {% if item.sub.length > 0 and key == itemKey %}
7 |
12 | {% endif %}
13 |
14 | {% endfor %}
15 |
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 | Custom property
41 | Description
42 |
43 |
44 |
45 |
46 | --accordion-border
47 |
48 | Sets the border-color
property.
49 |
50 |
51 |
52 | --accordion-surface
53 |
54 | Sets background-color
property of the header (also known as the summary
).
55 |
56 |
57 |
58 | --accordion-radius
59 |
60 | Sets border-radius
property.
61 |
62 |
63 |
64 |
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 |
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 |
61 | [label text]
62 | <[element] ... />
63 |
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 | Custom property
76 | Description
77 |
78 |
79 |
80 |
81 | --form-radius
82 |
83 | Sets the border-radius
of the fields.
84 |
85 |
86 |
87 | --form-border
88 |
89 | Sets the border-color
of the fields.
90 |
91 |
92 |
93 | --form-focus
94 |
95 | Sets the border-radius
of the fields, on hover and focus.
96 |
97 |
98 |
99 | --form-disabled-surface
100 |
101 | Sets the background-color
of disabled fields.
102 |
103 |
104 |
105 | --form-disabled-border
106 |
107 | Sets the border-color
of disabled fields.
108 |
109 |
110 |
111 | --form-disabled-padding
112 |
113 | Sets the padding-left
of disabled fields.
114 |
115 |
116 |
117 |
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 | Name
17 | Length (m)
18 | Weight (kg)
19 |
20 |
21 |
22 |
23 | John Doe
24 | 1.72
25 | 68
26 |
27 |
28 | John Doe
29 | 1.72
30 | 68
31 |
32 |
33 | John Doe
34 | 1.72
35 | 68
36 |
37 |
38 |
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 | Custom property
71 | Description
72 |
73 |
74 |
75 |
76 | --table-radius
77 | Sets the radius of the corners
78 |
79 |
80 | --table-color
81 | Sets the color of the borders and header
82 |
83 |
84 |
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 |
15 |
16 | My toggle
17 |
18 |
19 |
20 | ## Implementation
21 |
22 | ```
23 |
24 |
25 | [label text]
26 |
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 | Custom property
39 | Description
40 |
41 |
42 |
43 |
44 | --toggle-checked-color
45 |
46 | Sets the background color of the toggle on
47 | :checked
. Default is green
.
48 |
49 |
50 |
51 | --toggle-dot-color
52 |
53 | Sets the dot color.
54 |
55 |
56 |
57 | --toggle-size
58 |
59 | Sets the size of the white "dot" of the toggle.
60 |
61 |
62 |
63 | --toggle-space
64 |
65 | Sets spacing around the white "dot" of the toggle.
66 |
67 |
68 |
69 |
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 | Custom property
35 | Description
36 |
37 |
38 |
39 |
40 | --tooltip-surface
41 |
42 | Sets the background-color
property.
43 |
44 |
45 |
46 | --tooltip-color
47 |
48 | Sets color
property of the text.
49 |
50 |
51 |
52 | --tooltip-decoration
53 |
54 | Sets text-decoration
on the hover element.
55 |
56 |
57 |
58 |
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 | Custom property Default Description
26 |
27 |
28 | --layout-threshold
100%
Sets the max-width
of the targeted element
29 |
30 |
31 |
32 |
33 | ## Utility classes
34 |
35 |
36 |
37 |
38 | Class name Required? Description
39 |
40 |
41 | --threshold-{z}
Required Controls the --layout-threshold
API
42 |
43 |
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 | Custom property Default Description
27 |
28 |
29 | --layout-gap
0
Sets the gap
of the targeted element
30 | --layout-align
center
Sets the align-items
of the targeted element
31 |
32 |
33 |
34 |
35 | ## Utility classes
36 |
37 |
38 |
39 |
40 | Class name Required? Description
41 |
42 |
43 | --gap-{z}
Controls the --layout-gap
API
44 | --start/--end/--center/--stretch
Controls the --layout-align
API
45 |
46 |
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 | Custom property Default Description
24 |
25 |
26 | --layout-gap
0
Sets the gap
of the targeted element
27 | --layout-align
center
Sets the align-items
of the targeted element
28 | --layout-direction
row
Sets the flex-direction
of the targeted element
29 |
30 |
31 |
32 |
33 | ## Utility classes
34 |
35 |
36 |
37 |
38 | Class name Required? Description
39 |
40 |
41 | --gap-{z}
Controls the --layout-gap
API
42 | --column/--row
Controls the --layout-direction
API
43 | --start/--end/--center/--stretch
Controls the --layout-align
API
44 |
45 |
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 | Custom property Default Description
24 |
25 |
26 | --layout-threshold
0
Sets the max-width
of the child elements
27 | --layout-gap
0
Sets the gap
of the targeted element
28 |
29 |
30 |
31 |
32 | ## Utility classes
33 |
34 |
35 |
36 |
37 | Class name Required? Description
38 |
39 |
40 | --threshold-{z}
Required Controls the --layout-threshold
API
41 | --gap-{z}
Controls the --layout-gap
API
42 |
43 |
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 | Custom property Default Description
24 |
25 |
26 | --layout-gap
0
Sets the max-width
of the targeted element
27 | --layout-direction
row
Sets the flex-direction
of the targeted element
28 | --layout-align
center
Sets the align-items
of the targeted element
29 |
30 |
31 |
32 |
33 | ## Utility classes
34 |
35 |
36 |
37 |
38 | Class name Required? Description
39 |
40 |
41 | --gap-{z}
Controls the --layout-gap
API
42 | --column/--row
Controls the --layout-direction
API
43 | --start/--end/--center/--stretch
Controls the --layout-align
API
44 | --grow
sets flex-grow: 1;
on the targeted element.
45 | --self-start/end/stretch/center
sets the align-self
property on the targeted element.
46 |
47 |
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 | Custom property Default Description
24 |
25 |
26 | --layout-gap
0
Sets the gap
of the targeted element
27 | --layout-amount
2
Sets the number of colums
28 |
29 |
30 |
31 |
32 | ## Utility classes
33 |
34 |
35 |
36 |
37 | Class name Required? Description
38 |
39 |
40 | --gap-{z}
Controls the --layout-gap
API
41 | --amount-{z}
Controls the --layout-amount
API
42 |
43 |
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 | Custom property Default Description
26 |
27 |
28 | --layout-gap
0
Sets the gap
of the targeted element
29 |
30 |
31 |
32 |
33 | ## Utility classes
34 |
35 |
36 |
37 |
38 | Class name Required? Description
39 |
40 |
41 | --gap-{z}
Controls the --layout-gap
API
42 |
43 |
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 | Custom property Default Description
25 |
26 |
27 | --layout-ratio
Sets the aspect-ratio
of the targeted element
28 |
29 |
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 | Custom property Default Description
26 |
27 |
28 | --layout-gap
0
Sets the gap
of the targeted element
29 | --layout-direction
row
Sets the flex-direction
of the targeted element
30 | --layout-align
center
Sets the align-items
of the targeted element
31 |
32 |
33 |
34 |
35 | ## Utility classes
36 |
37 |
38 |
39 |
40 | Class name Required? Description
41 |
42 |
43 | --gap-{z}
Controls the --layout-gap
API
44 | --column/--row
Controls the --layout-direction
API
45 | --start/--end/--center/--stretch
Controls the --layout-align
API
46 |
47 |
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 | Custom property Default Description
32 |
33 |
34 | --layout-threshold
0
Sets the "fixed width" of the sidebar child element
35 | --layout-gap
0
Sets the gap
of the targeted element
36 | --layout-inline-size
60%
Sets the min-width
of the flexible content child element
37 |
38 |
39 |
40 |
41 | ## Utility classes
42 |
43 |
44 |
45 |
46 | Class name Required? Description
47 |
48 |
49 | --threshold-{z}
Required Controls the --layout-threshold
API
50 | --gap-{z}
Controls the --layout-gap
API
51 | --left/--right
Required Sets which element is the fixed width sidebar
52 |
53 |
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 | Custom property Default Description
31 |
32 |
33 | --layout-threshold
0
Sets the threshold when the targeted element switches orientation
34 | --layout-gap
0
Sets the gap
of the targeted element
35 | --layout-direction
row
Sets the flex-direction
of the targeted element
36 |
37 |
38 |
39 |
40 | ## Utility classes
41 |
42 |
43 |
44 |
45 | Class name Required? Description
46 |
47 |
48 | --threshold-{z}
Required Controls the --layout-threshold
API
49 | --gap-{z}
Controls the --layout-threshold
API
50 | --column/--row
Controls the --layout-direction
API
51 |
52 |
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 | Custom property Default Description
26 |
27 |
28 | --layout-threshold
0
Sets the min-width
of the child elements
29 | --layout-gap
0
Sets the gap
of the targeted element
30 |
31 |
32 |
33 |
34 | ## Utility classes
35 |
36 |
37 |
38 |
39 | Class name Required? Description
40 |
41 |
42 | --threshold-{z}
Required Controls the --layout-threshold
API
43 | --gap-{z}
Controls the --layout-gap
API
44 |
45 |
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 | Token name
34 | Value at 320px
35 | Value at 1240px
36 |
37 |
38 |
39 |
40 | --token-size-000
41 | 0.65rem
42 | 0.65rem + 3.25px
43 |
44 |
45 | --token-size-00
46 | 0.8125rem
47 | 0.8125rem + 4px
48 |
49 |
50 | --token-size-0
51 | 1rem
52 | 1rem + 5px
53 |
54 |
55 | --token-size-1
56 | 1.33rem
57 | 1.33rem + 6.65px
58 |
59 |
60 | --token-size-2
61 | 1.78rem
62 | 1.78rem + 8.9px
63 |
64 |
65 | --token-size-3
66 | 2.37rem
67 | 2.37rem + 11.85px
68 |
69 |
70 | --token-size-4
71 | 3.16rem
72 | 3.16rem + 15.7px
73 |
74 |
75 | --token-size-5
76 | 4.21rem
77 | 4.21rem + 21.05px
78 |
79 |
80 |
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 | Token name
94 | Value
95 |
96 |
97 |
98 |
99 | --token-bp-000
100 | 11.31rem
101 |
102 |
103 | --token-bp-00
104 | 15.04rem
105 |
106 |
107 | --token-bp-0
108 | 20rem
109 |
110 |
111 | --token-bp-1
112 | 20rem
113 |
114 |
115 | --token-bp-2
116 | 26.6rem
117 |
118 |
119 | --token-bp-3
120 | 35.38rem
121 |
122 |
123 | --token-bp-4
124 | 47.05rem
125 |
126 |
127 | --token-bp-5
128 | 62.58rem
129 |
130 |
131 |
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 | Token name
144 | RGB value
145 | HSL value
146 |
147 |
148 |
149 |
150 | --token-neutral-0
151 | #0E131B
152 | hsl(215, 30%, 8%)
153 |
154 |
155 | --token-neutral-1
156 | #2B3A50
157 | hsl(215, 30%, 24%)
158 |
159 |
160 | --token-neutral-2
161 | #476185
162 | hsl(215, 30%, 40%)
163 |
164 |
165 | --token-neutral-3
166 | #C8D4E5
167 | hsl(215, 36%, 78%)
168 |
169 |
170 | --token-neutral-4
171 | #E3E9F2
172 | hsl(215, 36%, 92%)
173 |
174 |
175 | --token-neutral-5
176 | #F7F9FB
177 | hsl(215, 36%, 98%)
178 |
179 |
180 |
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 | Color
192 | Design token
193 | Description
194 |
195 |
196 |
197 |
198 | --surface-0
199 | --token-neutral-0
200 | Main background color
201 |
202 |
203 | --surface-1
204 | --token-neutral-1
205 | Background color for surfaces that need to stand out a little on the main background (e.g. cards, code-blocks)
206 |
207 |
208 | --surface-2
209 | --token-neutral-2
210 | Surface color for specific elements or properties that need a little more emphasize (e.g. border colors)
211 |
212 |
213 | --text-0
214 | --token-neutral-5
215 | Main text color
216 |
217 |
218 | --text-1
219 | --token-neutral-4
220 | Text color for elements that need a little less high-light
221 |
222 |
223 | --text-2
224 | --token-neutral-3
225 | Text color for elements with less emphasize
226 |
227 |
228 |
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 | Class
13 | Design token
14 | CSS property
15 |
16 |
17 |
18 |
19 | m-{z}
20 | --token-size-{z}
21 | margin
22 |
23 |
24 | mt-{z}
25 | --token-size-{z}
26 | margin-top
27 |
28 |
29 | mr-{z}
30 | --token-size-{z}
31 | margin-right
32 |
33 |
34 | mb-{z}
35 | --token-size-{z}
36 | margin-bottom
37 |
38 |
39 | ml-{z}
40 | --token-size-{z}
41 | margin-left
42 |
43 |
44 |
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 | Custom property Default Description
16 |
17 |
18 | --line-count
2
Sets the -webkit-line-clamp
of the targeted element
19 |
20 |
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 | Class
13 | Design token
14 | CSS property
15 |
16 |
17 |
18 |
19 | size-{z}
20 | --token-size-{z}
21 | font-size
22 |
23 |
24 | bold/regular/italic
25 | -
26 | Various
27 |
28 |
29 | text-center/text-start/text-end
30 | -
31 | Sets the text-align
property.
32 |
33 |
34 |
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 |
--------------------------------------------------------------------------------