├── .editorconfig ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js └── src ├── apple-touch-icon.png ├── favicon-96x96.png ├── favicon.ico ├── favicon.svg ├── fonts ├── source-code-pro.woff2 └── work-sans.woff2 ├── images ├── progress-tracker-logo.svg ├── progress-tracker-social.png └── progress-tracker.png ├── index.html ├── scripts ├── main.js └── share-url-wc.js └── styles ├── main.css ├── progress-tracker.css ├── progress-tracker └── progress-tracker.css ├── share-url.css └── site ├── base.css ├── components.css ├── demo.css ├── layout.css ├── main.css ├── reset.css └── variables.css /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # Matches multiple files with brace expansion notation 14 | # Set default charset 15 | [*.{js,html}] 16 | charset = utf-8 17 | 18 | # 2 space indentation 19 | [*.{html,css,scss,js}] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | # Markdown files 24 | [*.md] 25 | trim_trailing_whitespace = false 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/actions/starter-workflows/blob/main/pages/static.yml + https://github.com/JamesIves/github-pages-deploy-action 2 | # Simple workflow for deploying static content to GitHub Pages 3 | name: Deploy static content to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the default branch 7 | push: 8 | branches: ["master"] 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 20 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: false 24 | 25 | jobs: 26 | # Single deploy job since we're just deploying 27 | deploy: 28 | environment: 29 | name: github-pages 30 | url: ${{ steps.deployment.outputs.page_url }} 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | # - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 36 | # run: | 37 | # npm ci 38 | # npm run build 39 | - name: Setup Pages 40 | uses: actions/configure-pages@v5 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v3 43 | with: 44 | # Upload entire repository 45 | path: 'src' 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .DS_Store 4 | Thumbs.db 5 | 6 | *.map 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Nigel O Toole 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 | # Progress Tracker 2 | ### Illustrate the steps in a multi step process like a form or a timeline. 3 | 4 | ### [Demo and documentation](http://nigelotoole.github.io/progress-tracker/) 5 | 6 | --- 7 | ## Quick start 8 | ```javascript 9 | $ npm install @nigelotoole/progress-tracker --save-dev 10 | ``` 11 | 12 | Import the CSS into your project and add the elements to your HTML. 13 | 14 | --- 15 | ### License 16 | MIT © Nigel O Toole 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "progress-tracker", 3 | "homepage": "https://nigelotoole.github.io/progress-tracker/", 4 | "author": "Nigel O Toole (http://www.purestructure.com)", 5 | "description": "Illustrate the steps in a multi step process like a form or a timeline.", 6 | "keywords": [ 7 | "progress", 8 | "tracker", 9 | "multi", 10 | "step", 11 | "multi-step", 12 | "stage", 13 | "multi-stage", 14 | "stepper", 15 | "javascript" 16 | ], 17 | "version": "3.0.1", 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">=4" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/NigelOToole/progress-tracker.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/NigelOToole/progress-tracker/issues" 28 | }, 29 | "browserslist": [ 30 | "defaults" 31 | ], 32 | "devDependencies": { 33 | "@11ty/eleventy-dev-server": "^2.0.6", 34 | "concurrently": "^8.2.1", 35 | "cross-env": "^7.0.3", 36 | "postcss": "^8.5.0", 37 | "postcss-cli": "^10.1.0", 38 | "postcss-custom-media": "^10.0.2", 39 | "postcss-import": "^15.1.0", 40 | "postcss-preset-env": "^9.1.4", 41 | "rimraf": "^5.0.5" 42 | }, 43 | "scripts": { 44 | "clean": "rimraf src/**/*.map", 45 | "dev": "cross-env NODE_ENV=development && concurrently \"npm:dev:*\"", 46 | "dev:server": "npx @11ty/eleventy-dev-server --dir=src", 47 | "dev:styles": "postcss src/styles/site/main.css src/styles/progress-tracker/progress-tracker.css --dir src/styles --watch", 48 | "build": "npm run clean && cross-env NODE_ENV=production concurrently \"npm:build:*\"", 49 | "build:styles": "postcss src/styles/site/main.css src/styles/progress-tracker/progress-tracker.css --dir src/styles", 50 | "publish:npm": "npm run build && npm publish --access public" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const isProduction = process.env.NODE_ENV === "production"; 2 | const plugins = [ 3 | require('postcss-import'), 4 | require('postcss-custom-media'), 5 | require('postcss-preset-env')({ 6 | stage: 1 7 | }), 8 | ]; 9 | 10 | module.exports = { 11 | map: isProduction ? false : { annotation: true, inline: false }, 12 | plugins 13 | } 14 | -------------------------------------------------------------------------------- /src/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/apple-touch-icon.png -------------------------------------------------------------------------------- /src/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/favicon-96x96.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/favicon.ico -------------------------------------------------------------------------------- /src/favicon.svg: -------------------------------------------------------------------------------- 1 | progress-tracker-logo -------------------------------------------------------------------------------- /src/fonts/source-code-pro.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/fonts/source-code-pro.woff2 -------------------------------------------------------------------------------- /src/fonts/work-sans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/fonts/work-sans.woff2 -------------------------------------------------------------------------------- /src/images/progress-tracker-logo.svg: -------------------------------------------------------------------------------- 1 | progress-tracker-logo -------------------------------------------------------------------------------- /src/images/progress-tracker-social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/images/progress-tracker-social.png -------------------------------------------------------------------------------- /src/images/progress-tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NigelOToole/progress-tracker/42fa35c22a86b0dadc5d34cd30394438f88547b0/src/images/progress-tracker.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Progress Tracker - Illustrate the steps in a multi step process like a form or a timeline 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 |

Progress Tracker

33 | 34 | 38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 | 46 |
47 |

Show the steps in a multi step process like a form or a timeline.

48 | 49 | Progress Tracker logo 50 |
51 | 52 | 59 | 60 |
61 |
62 | 63 | 64 |
65 |
66 | 67 |

Demos

68 | 69 | 70 |

Markers

71 | 72 |
73 |
    74 |
  1. 75 |
  2. 76 |
  3. 77 |
  4. 78 |
  5. 79 |
80 |
81 | 82 |
83 |
    84 |
  1. 85 |
  2. 86 |
  3. 87 |
  4. 88 |
  5. 89 |
90 |
91 | 92 |
93 |
    94 |
  1. 95 |
  2. 96 |
  3. 97 |
  4. 98 |
  5. 99 |
100 |
101 | 102 |
103 |
    104 |
  1. 105 |
  2. 106 |
  3. 107 |
  4. 108 |
  5. 109 |
110 |
111 | 112 | 113 |
114 |

Markers with text

115 | 116 |
117 | 118 | 128 |
129 |
130 | 131 | 132 |
133 |
    134 |
  1. 135 |
    136 |

    Step 1

    137 |

    Text explaining this step to the user.

    138 |
    139 |
  2. 140 | 141 |
  3. 142 |
    143 |

    Step 2

    144 |

    Text explaining this step to the user.

    145 |
    146 |
  4. 147 | 148 |
  5. 149 |
    150 |

    Step 3

    151 |

    Shorter text explanation.

    152 |
    153 |
  6. 154 |
155 |
156 | 157 |
158 |
    159 |
  1. 160 |
    161 |

    Step 1

    162 |

    Text explaining this step to the user.

    163 |
    164 |
  2. 165 | 166 |
  3. 167 |
    168 |

    Step 2

    169 |

    Text explaining this step to the user.

    170 |
    171 |
  4. 172 | 173 |
  5. 174 |
    175 |

    Step 3

    176 |

    Shorter text explanation.

    177 |
    178 |
  6. 179 |
180 |
181 | 182 | 183 |
184 |

Spaced

185 | 186 |
187 | 188 | 197 |
198 |
199 | 200 |
201 |
    202 |
  1. 203 |
    204 |

    Step 1

    205 |

    Text explaining this step to the user.

    206 |
    207 |
  2. 208 | 209 |
  3. 210 |
    211 |

    Step 2

    212 |

    Text explaining this step to the user.

    213 |
    214 |
  4. 215 | 216 |
  5. 217 |
    218 |

    Step 3

    219 |

    Shorter text explanation.

    220 |
    221 |
  6. 222 |
223 |
224 | 225 |
226 |
    227 |
  1. 228 |
    229 |

    Step 1

    230 |

    Text explaining this step to the user.

    231 |
    232 |
  2. 233 | 234 |
  3. 235 |
    236 |

    Step 2

    237 |

    Text explaining this step to the user.

    238 |
    239 |
  4. 240 | 241 |
  5. 242 |
    243 |

    Step 3

    244 |

    Shorter text explanation.

    245 |
    246 |
  6. 247 |
248 |
249 | 250 | 251 |

Animation

252 |
253 |
    254 |
  1. 255 | 259 |
  2. 260 | 261 |
  3. 262 | 266 |
  4. 267 | 268 |
  5. 269 | 273 |
  6. 274 |
275 |
276 | 277 | 278 |
279 |

Inline text

280 |
281 | 282 |
283 |
    284 |
  1. 285 |
    286 |

    Step 1

    287 |
    288 |
  2. 289 | 290 |
  3. 291 |
    292 |

    Step 2

    293 |
    294 |
  4. 295 | 296 |
  5. 297 |
    298 |

    Step 3

    299 |
    300 |
  6. 301 |
302 |
303 | 304 |
305 |
    306 |
  1. 307 |
    308 |

    Step 1

    309 |
    310 |
  2. 311 | 312 |
  3. 313 |
    314 |

    Step 2

    315 |
    316 |
  4. 317 | 318 |
  5. 319 |
    320 |

    Step 3

    321 |
    322 |
  6. 323 |
324 |
325 | 326 | 327 |
328 |

Vertical

329 | 330 |
331 | 332 | 337 |
338 |
339 | 340 |
341 |
    342 |
  1. 343 |
    344 |

    Step 1

    345 |

    Text explaining this step to the user.

    346 |
    347 |
  2. 348 | 349 |
  3. 350 |
    351 |

    Step 2

    352 |

    Text explaining this step to the user.

    353 |
    354 |
  4. 355 | 356 |
  5. 357 |
    358 |

    Step 3

    359 |

    Shorter text explanation.

    360 |
    361 |
  6. 362 |
363 |
364 | 365 | 366 |
367 |

Vertical spaced

368 | 369 |
370 | 371 | 376 |
377 |
378 | 379 |
380 |
    381 |
  1. 382 |
    383 |

    Step 1

    384 |

    Text explaining this step to the user.

    385 |
    386 |
  2. 387 | 388 |
  3. 389 |
    390 |

    Step 2

    391 |

    Text explaining this step to the user.

    392 |
    393 |
  4. 394 | 395 |
  5. 396 |
    397 |

    Step 3

    398 |

    Shorter text explanation.

    399 |
    400 |
  6. 401 |
402 |
403 | 404 | 405 |
406 |
407 | 408 | 409 |
410 |
411 | 412 |

Installation

413 | 414 |
$ npm install progress-tracker --save-dev
415 | 416 | 417 |

Usage

418 | 419 |

Add the CSS into your project and add the elements to your HTML.

420 | 421 | 422 |

Markup

423 | 424 |
<ol class="progress-tracker">
425 |   <li class="progress-step is-complete">
426 |     <div class="progress-text"> ... </div>
427 |   </li>
428 |   <li class="progress-step is-active">
429 |     <div class="progress-text"> ... </div>
430 |   </li>
431 |   <li class="progress-step">
432 |     <div class="progress-text"> ... </div>
433 |   </li>
434 | </ol>
435 | 
436 | 437 | 438 |

Options

439 | 440 |

Customize the appearance of the element by adding the variant classes as needed.

441 | 442 |
443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 |
ClassDescription
progress-trackerDefault layout.
progress-tracker--marker-counterAdds a numberic counter to the marker.
progress-tracker--marker-datasetAdds text to the marker from data-text on the step.
progress-tracker--marker-squareSquare marker shape.
progress-tracker--spacedSpace added around markers.
progress-tracker--inlineMarker text is in line with the steps.
progress-tracker--centerCenter aligned layout.
progress-tracker--rightRight aligned layout.
progress-tracker--reverseReversed layout, i.e. text on top.
progress-tracker--verticalVertically aligned layout.
progress-tracker--animAllows animation of path.
501 |
502 | 503 |

Compatibility

504 |

Supports all modern browsers at the time of release.

505 | 506 | 507 |

Demo site

508 |

Clone the repo from Github and run the commands below.

509 | 510 |
$ npm install
511 | $ npm run dev
512 | 
513 | 514 |
515 |
516 | 517 |
518 | 519 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | -------------------------------------------------------------------------------- /src/scripts/main.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('DOMContentLoaded', (event) => { 2 | 3 | // Demos 4 | 5 | const updateClasses = document.querySelectorAll('.demo-class-update'); 6 | 7 | for (const item of updateClasses) { 8 | let target = item.dataset['target']; 9 | if (!target) return; 10 | 11 | let targetElement = document.querySelectorAll(item.dataset['target']); 12 | for (const item of targetElement) { 13 | item.updateClass = {}; 14 | item.updateClass.currentClass = [...item.classList]; 15 | } 16 | 17 | 18 | item.addEventListener('change', (event) => { 19 | let oldClass = []; 20 | for (const option of event.target.options) { 21 | oldClass.push(option.value.split(' ')) 22 | } 23 | oldClass = oldClass.flat(); 24 | 25 | let newClass = event.target.value.split(' '); 26 | 27 | for (const demo of targetElement) { 28 | let oldClassToApply = oldClass.filter(item => !demo.updateClass.currentClass.includes(item)); 29 | let newClassToApply = newClass.filter(item => !demo.updateClass.currentClass.includes(item)); 30 | 31 | for (const item of oldClassToApply) { 32 | let cssClass = item; 33 | if (cssClass) demo.classList.remove(item); 34 | } 35 | 36 | for (const item of newClassToApply) { 37 | let cssClass = item; 38 | if (cssClass) demo.classList.add(item); 39 | } 40 | } 41 | }) 42 | 43 | }; 44 | 45 | const demoAnimButtons = document.querySelectorAll('.progress-tracker--anim button'); 46 | 47 | for (const item of demoAnimButtons) { 48 | item.addEventListener('click', (event) => { 49 | event.preventDefault(); 50 | 51 | for (const item of demoAnimButtons) { 52 | item.closest('.progress-step').classList.remove('is-active'); 53 | } 54 | 55 | let step = item.closest('.progress-step'); 56 | 57 | if(!step.classList.contains('is-complete')) { 58 | step.classList.add('is-complete'); 59 | if(step.nextElementSibling !== null) { 60 | step.nextElementSibling.classList.add('is-active'); 61 | } 62 | } 63 | else { 64 | step.classList.remove('is-complete'); 65 | if(step.previousElementSibling !== null) { 66 | step.previousElementSibling.classList.remove('is-active'); 67 | } 68 | } 69 | }); 70 | } 71 | 72 | 73 | 74 | 75 | // Site 76 | 77 | // Encoded text 78 | const encodeElements = document.querySelectorAll('.encode'); 79 | for (const item of encodeElements) { 80 | let decode = atob(item.dataset['encode']); 81 | 82 | if (item.dataset['encodeAttribute']) { 83 | item.setAttribute(`${item.dataset['encodeAttribute']}`, `${decode}`); 84 | } 85 | } 86 | 87 | // Observe header height 88 | const observeHeader = function() { 89 | let element = document.querySelector('.header'); 90 | let height = 0; 91 | 92 | const resizeObserver = new ResizeObserver((entries) => { 93 | for (const entry of entries) { 94 | let heightNew = entry.contentBoxSize[0].blockSize; 95 | 96 | if (height !== heightNew) { 97 | height = entry.contentBoxSize[0].blockSize; 98 | 99 | document.documentElement.style.setProperty(`--header-height`, `${height}px`); 100 | } 101 | } 102 | 103 | }); 104 | 105 | resizeObserver.observe(element); 106 | } 107 | 108 | observeHeader(); 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /src/scripts/share-url-wc.js: -------------------------------------------------------------------------------- 1 | class ShareUrl extends HTMLElement { 2 | 3 | // Setup 4 | constructor() { 5 | super(); 6 | 7 | this.options = { 8 | selector: '.share-url', 9 | activeClass: 'is-active', 10 | action: 'share', 11 | url: document.location.href, 12 | title: document.title, 13 | urlParameter: 'url', 14 | titleParameter: 'text', 15 | textSelector: null, 16 | textLabel: '', 17 | textSuccess: 'Shared', 18 | maintainSize: false, 19 | } 20 | 21 | this.platforms = [{ name: 'bluesky', url: 'https://bsky.app/intent/compose', urlParameter: '' }, { name: 'facebook', url: 'https://facebook.com/sharer/sharer.php', titleParameter: 't', urlParameter: 'u' }, { name: 'linkedin', url: 'https://www.linkedin.com/shareArticle?mini=true' }, { name: 'reddit', url: 'https://www.reddit.com/submit', titleParameter: 'title' }, { name: 'twitter', url: 'https://twitter.com/intent/tweet' }, { name: 'threads', url: 'https://www.threads.net/intent/post' }]; 22 | 23 | 24 | this.setup(); 25 | } 26 | 27 | connectedCallback() { 28 | this.setup(); 29 | } 30 | 31 | setup() { 32 | if (this._instantiated) return; 33 | 34 | this.element = this.querySelector('button, a'); 35 | if (!this.element) return; 36 | 37 | for (const item of this.getAttributeNames()) { 38 | let prop = this.camelCase(item); 39 | let value = this.checkBoolean(this.getAttribute(item)); 40 | this.options[prop] = value; 41 | } 42 | 43 | if (this.element.href && !this.getAttribute('action')) this.options.action = this.element.href; 44 | 45 | this.textElement = this.querySelector(this.options.textSelector); 46 | if (this.textElement === null) this.textElement = this.element; 47 | if (this.options.textLabel) this.textElement.innerText = this.options.textLabel; 48 | 49 | if (this.options.action === 'share' || this.options.action === 'clipboard') { 50 | navigator[this.options.action] ? this.element.addEventListener('click', () => this.shareEvent()) : this.setFallback(); 51 | } 52 | else { 53 | this.element.addEventListener('click', (event) => this.sharePlatform(event)); 54 | } 55 | 56 | this._instantiated = true; 57 | } 58 | 59 | 60 | // Utilities 61 | checkBoolean(string) { 62 | if (string.toLowerCase() === 'true') return true; 63 | if (string.toLowerCase() === 'false') return false; 64 | return string; 65 | } 66 | 67 | isValidUrl(string) { 68 | try { 69 | new URL(string); 70 | return true; 71 | } 72 | catch (error) { 73 | return false; 74 | } 75 | } 76 | 77 | setFallback() { 78 | if (this.element.querySelector('fallback') !== null) { 79 | this.element.classList.add('is-fallback'); 80 | } 81 | else { 82 | this.style.display = 'none'; 83 | } 84 | }; 85 | 86 | camelCase(text, delimiter = '-') { 87 | const pattern = new RegExp((`${delimiter}([a-z])`), 'g'); 88 | return text.replace(pattern, (match, replacement) => replacement.toUpperCase()); 89 | } 90 | 91 | 92 | // Methods 93 | async shareEvent() { 94 | try { 95 | if (this.options.action === 'share') await navigator.share({ title: this.options.title, text: this.options.title, url: this.options.url }); 96 | if (this.options.action === 'clipboard') await navigator.clipboard.writeText(this.options.url); 97 | 98 | this.shareSuccess(); 99 | } 100 | catch (error) { 101 | if (error.name !== 'AbortError') console.error(error.name, error.message); 102 | } 103 | } 104 | 105 | sharePlatform(event) { 106 | event.preventDefault(); 107 | 108 | let platformData = this.platforms.find((item) => item.name === this.options.action); 109 | if (platformData) { 110 | this.options.action = platformData.url; 111 | this.options.urlParameter = platformData.urlParameter ?? this.options.urlParameter; 112 | this.options.titleParameter = platformData.titleParameter ?? this.options.titleParameter; 113 | } 114 | 115 | if (this.options.action === 'mastodon') { 116 | let mastodonInstance = localStorage.getItem('mastodon-instance'); 117 | 118 | if (!mastodonInstance) { 119 | let mastodonPrompt = prompt('Enter your Mastodon instance'); 120 | if (mastodonPrompt === '' || mastodonPrompt === null) return; 121 | 122 | localStorage.setItem('mastodon-instance', mastodonPrompt); 123 | mastodonInstance = localStorage.getItem('mastodon-instance'); 124 | } 125 | 126 | this.options.action = `https://${mastodonInstance}/share`; 127 | } 128 | 129 | if (!this.isValidUrl(this.options.action)) return; 130 | 131 | const platformURL = new URL(this.options.action); 132 | 133 | if (this.options.urlParameter === '') { 134 | this.options.title += ` ${this.options.url}`; 135 | } 136 | else { 137 | platformURL.searchParams.append(this.options.urlParameter, this.options.url); 138 | } 139 | 140 | platformURL.searchParams.append(this.options.titleParameter, this.options.title); 141 | 142 | window.open(platformURL.href, '_blank', 'noreferrer,noopener'); 143 | this.shareSuccess(); 144 | } 145 | 146 | shareSuccess() { 147 | let textWidth = this.textElement.offsetWidth; 148 | this.textElement.innerText = this.options.textSuccess; 149 | if (this.options.maintainSize) this.textElement.style.width = `${Math.max(textWidth, this.textElement.offsetWidth)}px`; 150 | this.element.classList.add(this.options.activeClass); 151 | } 152 | 153 | } 154 | 155 | customElements.define('share-url', ShareUrl); 156 | export { ShareUrl }; 157 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | /* https://www.joshwcomeau.com/css/custom-css-reset/, https://andy-bell.co.uk/a-more-modern-css-reset/ */ 2 | *, *::before, *::after { 3 | box-sizing: border-box; 4 | } 5 | * { 6 | margin: 0; 7 | } 8 | html { 9 | -moz-text-size-adjust: none; 10 | -webkit-text-size-adjust: none; 11 | text-size-adjust: none; 12 | } 13 | body { 14 | line-height: 1.5; 15 | -webkit-font-smoothing: antialiased; 16 | 17 | min-height: 100vh; 18 | } 19 | img, picture, video, canvas, svg { 20 | display: block; 21 | max-width: 100%; 22 | } 23 | input, button, textarea, select { 24 | font: inherit; 25 | } 26 | p, h1, h2, h3, h4, h5, h6 { 27 | word-wrap: break-word; 28 | } 29 | /* Opinionated */ 30 | h1, h2, h3, h4, 31 | button, input, label { 32 | line-height: 1.2; 33 | } 34 | h1, h2, h3, h4 { 35 | text-wrap: balance; 36 | } 37 | @font-face { 38 | font-family: 'Source Code Pro'; 39 | src: url('../fonts/source-code-pro.woff2') format('woff2'); 40 | display: swap; 41 | } 42 | @font-face { 43 | font-family: 'Work Sans'; 44 | src: url('../fonts/work-sans.woff2') format('woff2'); 45 | display: swap; 46 | } 47 | :root { 48 | /* Colours */ 49 | /* https://oklch-palette.vercel.app/#53.67,0.257,262.51,100, https://oklch.com/#53.67,0.257,262.51,100 */ 50 | --color-50: rgb(245, 248, 255); 51 | --color-100: rgb(223, 234, 255); 52 | --color-200: rgb(153, 189, 255); 53 | --color-300: rgb(109, 159, 255); 54 | --color-400: rgb(63, 126, 255); 55 | --color-500: rgb(1, 87, 255); 56 | --color-600: rgb(0, 72, 215); 57 | --color-700: rgb(0, 52, 163); 58 | --color-800: rgb(0, 33, 112); 59 | --color-900: rgb(0, 17, 69); 60 | 61 | --color-accent-500: rgb(40, 209, 180); 62 | --color-accent-700: rgb(1, 119, 101); 63 | 64 | /* Type */ 65 | --sans-serif-font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, Arial, sans-serif; 66 | --serif-font-family: 'Times New Roman', Times, serif; 67 | /* --body-font-family: 'Work Sans', var(--sans-serif-font-family); 68 | --heading-font-family: 'Source Code Pro', var(--sans-serif-font-family); */ 69 | --body-font-family: 'Work Sans', sans-serif; 70 | --heading-font-family: 'Source Code Pro', monospace; 71 | 72 | --text-color: var(--color-700); 73 | --link-color: var(--color-500); 74 | --link-color-hover: var(--color-700); 75 | 76 | /* Layout */ 77 | --layout-breakpoint-xs: 0; 78 | --layout-breakpoint-sm: 576px; 79 | --layout-breakpoint-md: 768px; 80 | --layout-breakpoint-lg: 992px; 81 | --layout-breakpoint-xl: 1200px; 82 | --layout-breakpoint-xxl: 1400px; 83 | 84 | --layout-gutter-inline: 16px; 85 | --layout-gutter-block: 0px; 86 | 87 | --layout-space-xxs: 4px; 88 | --layout-space-xs: 8px; 89 | --layout-space-sm: 16px; 90 | --layout-space-md: 32px; 91 | --layout-space-lg: 48px; 92 | --layout-space-xl: 64px; 93 | --layout-space-xxl: 80px; 94 | 95 | --ease-out-cubic: cubic-bezier(.215, .610, .355, 1); 96 | --ease-in-out-cubic: cubic-bezier(.65, .05, .36, 1); 97 | 98 | --bg-grid-color: rgba(238, 238, 238, .75); 99 | --bg-grid-line: 2px; 100 | --bg-grid-box: 48px; 101 | } 102 | @supports (color: color(display-p3 0 0 0%)) { 103 | :root { 104 | --color-50: rgb(245, 248, 255); 105 | --color-100: rgb(223, 234, 255); 106 | --color-300: rgb(109, 159, 255); 107 | --color-700: rgb(0, 52, 163); 108 | --color-800: rgb(0, 33, 112); 109 | --color-900: rgb(0, 17, 69); 110 | } 111 | 112 | @media (color-gamut: p3) { 113 | :root { 114 | --color-50: color(display-p3 0.96281 0.97222 0.99781); 115 | --color-100: color(display-p3 0.8822 0.91623 0.99269); 116 | --color-300: color(display-p3 0.46998 0.61827 0.97252); 117 | --color-700: color(display-p3 0.07062 0.20014 0.6152); 118 | --color-800: color(display-p3 0.03454 0.12659 0.42217); 119 | --color-900: color(display-p3 0.01272 0.0648 0.25957); 120 | } 121 | } 122 | } 123 | @media (min-width: 768px) { 124 | :root { 125 | --layout-gutter-inline: 24px; 126 | } 127 | } 128 | @supports not (background-color: oklch(0%, 0, 0)) { 129 | :root { 130 | /* --color-50: #f0f5ff; */ 131 | --color-50: #f5f8ff; 132 | /* --color-100: #c5d9ff; */ 133 | --color-100: #dfeaff; 134 | --color-200: #99bdff; 135 | --color-300: #6d9fff; 136 | --color-400: #3f7eff; 137 | --color-500: #0157ff; 138 | --color-600: #0048d7; 139 | --color-700: #0034a3; 140 | --color-800: #002170; 141 | --color-900: #001145; 142 | 143 | --color-accent-500: #28d1b4; 144 | --color-accent-700: #007765; 145 | } 146 | } 147 | /* Custom media queries */ 148 | /* Type and background */ 149 | body { 150 | font-family: 'Work Sans', sans-serif; 151 | font-family: var(--body-font-family); 152 | color: rgb(0, 52, 163); 153 | color: color(display-p3 0.07062 0.20014 0.6152); 154 | color: var(--color-700); 155 | background-color: #fff; 156 | background-image: repeating-linear-gradient(90deg, rgba(238, 238, 238, .75) 0, rgba(238, 238, 238, .75) 2px, transparent 0, transparent 50%), repeating-linear-gradient(180deg, rgba(238, 238, 238, .75) 0, rgba(238, 238, 238, .75) 2px, transparent 0, transparent 50%); 157 | background-image: repeating-linear-gradient(90deg, var(--bg-grid-color) 0, var(--bg-grid-color) var(--bg-grid-line), transparent 0, transparent 50%), repeating-linear-gradient(180deg, var(--bg-grid-color) 0, var(--bg-grid-color) var(--bg-grid-line), transparent 0, transparent 50%); 158 | background-size: 48px 48px; 159 | background-size: var(--bg-grid-box) var(--bg-grid-box); 160 | background-position: calc(50% - (2px/2)) top; 161 | background-position: calc(50% - (var(--bg-grid-line)/2)) top; 162 | } 163 | .content { 164 | background-color: #fff; 165 | border-left: calc(2px/2) solid rgba(238, 238, 238, .75); 166 | border-right: calc(2px/2) solid rgba(238, 238, 238, .75); 167 | border-left: calc(var(--bg-grid-line)/2) solid var(--bg-grid-color); 168 | border-right: calc(var(--bg-grid-line)/2) solid var(--bg-grid-color); 169 | } 170 | ::-moz-selection { 171 | color: #fff; 172 | background-color: rgb(0, 52, 163); 173 | background-color: color(display-p3 0.07062 0.20014 0.6152); 174 | background-color: var(--color-700); 175 | } 176 | ::selection { 177 | color: #fff; 178 | background-color: rgb(0, 52, 163); 179 | background-color: color(display-p3 0.07062 0.20014 0.6152); 180 | background-color: var(--color-700); 181 | } 182 | h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 { 183 | font-family: 'Source Code Pro', monospace; 184 | font-family: var(--heading-font-family); 185 | font-weight: 400; 186 | word-spacing: -.5ch; 187 | } 188 | h1, h2, h3 { 189 | letter-spacing: -.025em; 190 | } 191 | h1, h2 { 192 | font-weight: 600; 193 | } 194 | /* Clamp from 375 - 768px */ 195 | h1 { font-size: 3rem; font-size: clamp(2.5rem, 2.0229rem + 2.0356vw, 3rem); } 196 | h2 { font-size: 2rem; font-size: clamp(1.5rem, 1.0229rem + 2.0356vw, 2rem); } 197 | h3 { font-size: 1.5rem; font-size: clamp(1.25rem, 1.0115rem + 1.0178vw, 1.5rem); } 198 | h4 { font-size: 1.25rem; font-size: clamp(1.125rem, 1.0057rem + 0.5089vw, 1.25rem); } 199 | h5 { font-size: 1rem; } 200 | h6 { font-size: 1rem; } 201 | p { 202 | 203 | } 204 | ul, ol { 205 | padding-left: 32px; 206 | padding-left: var(--layout-space-md); 207 | } 208 | pre, code { 209 | font-family: 'Source Code Pro', monospace; 210 | font-family: var(--heading-font-family); 211 | } 212 | a { 213 | color: rgb(1, 87, 255); 214 | color: var(--link-color); 215 | transition: color 0.3s; 216 | } 217 | a:hover, a:focus { 218 | color: rgb(0, 52, 163); 219 | color: color(display-p3 0.07062 0.20014 0.6152); 220 | color: var(--link-color-hover); 221 | } 222 | a:focus-within { 223 | outline: 2px solid rgb(1, 87, 255); 224 | outline: 2px solid var(--link-color); 225 | } 226 | /* Elements and utilities */ 227 | html { 228 | scroll-padding-block-start: var(--header-height); 229 | scroll-behavior: smooth; 230 | } 231 | [id]:not(.fullwidth) { 232 | scroll-margin-block-start: 1ex; 233 | } 234 | .img-fluid { 235 | max-width: 100%; 236 | height: auto; 237 | } 238 | .w-100 { 239 | width: 100%; 240 | } 241 | code { 242 | padding: .125rem; 243 | background-color: rgb(245, 248, 255); 244 | background-color: color(display-p3 0.96281 0.97222 0.99781); 245 | background-color: var(--color-50); 246 | } 247 | pre code { 248 | display: block; 249 | padding: 1rem; 250 | white-space: pre; 251 | overflow: auto; 252 | border: 1px solid rgb(1, 87, 255); 253 | border: 1px solid var(--color-500); 254 | } 255 | .table-outer { 256 | display: block; 257 | width: 100%; 258 | } 259 | .table { 260 | width: 100%; 261 | border-collapse: collapse; 262 | } 263 | .table th, .table td { 264 | padding: 16px; 265 | padding: var(--layout-space-sm); 266 | text-align: left; 267 | vertical-align: top; 268 | border: 1px solid rgb(1, 87, 255); 269 | border: 1px solid var(--color-500); 270 | } 271 | .table th { 272 | background-color: rgb(223, 234, 255); 273 | background-color: color(display-p3 0.8822 0.91623 0.99269); 274 | background-color: var(--color-100); 275 | } 276 | /* Scroll shadow for inline overflow */ 277 | /* Inspired by https://daverupert.com/2023/08/animation-timeline-scroll-shadows/ */ 278 | .scroll-shadow-inline { 279 | --shadow-color: rgba(0, 0, 0, 0.2); 280 | --shadow-size: 8px; 281 | --shadow-spread: calc(var(--shadow-size) * -.5); 282 | 283 | overflow-x: auto; 284 | overflow-inline: auto; 285 | 286 | animation: scroll-shadow-inset linear; 287 | scroll-timeline: --scroll-shadow-timeline inline; 288 | animation-timeline: --scroll-shadow-timeline; 289 | 290 | /* This is shorthand for the above using an anonymous timeline instead of a named one */ 291 | /* animation-timeline: scroll(self inline); */ 292 | 293 | /* Non-essential styles */ 294 | border: 1px solid rgb(1, 87, 255); 295 | border: 1px solid var(--color-500); 296 | 297 | /* Stops child elements with a background appearing above the shadow */ 298 | } 299 | .scroll-shadow-inline > * { 300 | mix-blend-mode: multiply; 301 | } 302 | /* Fallback */ 303 | @supports not (animation-timeline: scroll(self inline)) { 304 | .scroll-shadow-inline { 305 | /* Background color should be the same as the element */ 306 | --scroll-bg-color: #fff; 307 | background-image: 308 | linear-gradient(to right, #fff, #fff), linear-gradient(to right, #fff, #fff), 309 | linear-gradient(to right, var(--shadow-color), transparent), linear-gradient(to left, var(--shadow-color), transparent); 310 | background-image: 311 | linear-gradient(to right, var(--scroll-bg-color), var(--scroll-bg-color)), linear-gradient(to right, var(--scroll-bg-color), var(--scroll-bg-color)), 312 | linear-gradient(to right, var(--shadow-color), transparent), linear-gradient(to left, var(--shadow-color), transparent); 313 | background-size: 314 | calc(var(--shadow-size) * 4) 100%, calc(var(--shadow-size) * 4) 100%, 315 | calc(var(--shadow-size) * 2) 100%, calc(var(--shadow-size) * 2) 100%; 316 | background-position: left center, right center, left center, right center; 317 | background-attachment: local, local, scroll, scroll; 318 | background-repeat: no-repeat; 319 | } 320 | } 321 | .scroll-shadow-inline.table-outer { 322 | border-left: 2px solid rgb(1, 87, 255); 323 | border-right: 2px solid rgb(1, 87, 255); 324 | border-left: 2px solid oklch(53.67% 0.257 262.51); 325 | border-left: 2px solid var(--color-500); 326 | border-right: 2px solid oklch(53.67% 0.257 262.51); 327 | border-right: 2px solid var(--color-500); 328 | } 329 | .scroll-shadow-inline .table th:first-child, .scroll-shadow-inline .table td:first-child { 330 | border-left: none; 331 | } 332 | .scroll-shadow-inline .table th:last-child, .scroll-shadow-inline .table td:last-child { 333 | border-right: none; 334 | } 335 | code.scroll-shadow-inline { 336 | --scroll-bg-color: var(--color-50); 337 | } 338 | /* Shadow animations */ 339 | /* Right shadow, left shadow. Negative spread to prevent a shadow on the top and bottom of the element */ 340 | @keyframes scroll-shadow-inset { 341 | from { 342 | box-shadow: 343 | inset calc(var(--shadow-size) * -2) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color), 344 | inset calc(var(--shadow-size) * 0) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color); 345 | } 346 | 10%, 90% { 347 | box-shadow: 348 | inset calc(var(--shadow-size) * -1) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color), 349 | inset calc(var(--shadow-size) * 1) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color); 350 | } 351 | to { 352 | box-shadow: 353 | inset calc(var(--shadow-size) * 0) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color), 354 | inset calc(var(--shadow-size) * 2) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color); 355 | } 356 | } 357 | @keyframes scroll-shadow-inline-end { 358 | from { 359 | box-shadow: 360 | calc(var(--shadow-size) * 0) 0 var(--shadow-size) 0 var(--shadow-color); 361 | } 362 | 10%, 90% { 363 | box-shadow: 364 | calc(var(--shadow-size) * 1) 0 var(--shadow-size) 0 var(--shadow-color); 365 | } 366 | to { 367 | box-shadow: 368 | calc(var(--shadow-size) * 2) 0 var(--shadow-size) 0 var(--shadow-color); 369 | } 370 | } 371 | /* Browser support message */ 372 | .unsupported { 373 | display: none; 374 | } 375 | .unsupported.is-active { 376 | display: block; 377 | } 378 | /* Spacing of components */ 379 | .space, .fullwidth { 380 | --layout-space: var(--layout-space-md); 381 | } 382 | .space--zero { 383 | --layout-space: 0; 384 | } 385 | .space--xxs { 386 | --layout-space: var(--layout-space-xxs); 387 | } 388 | .space--xs { 389 | --layout-space: var(--layout-space-xs); 390 | } 391 | .space--sm { 392 | --layout-space: var(--layout-space-sm); 393 | } 394 | .space--md { 395 | --layout-space: var(--layout-space-md); 396 | } 397 | .space--lg { 398 | --layout-space: var(--layout-space-lg); 399 | } 400 | .space--xl { 401 | --layout-space: var(--layout-space-xl); 402 | } 403 | .space--xxl { 404 | --layout-space: var(--layout-space-xxl); 405 | } 406 | .space { 407 | padding: var(--layout-space); 408 | } 409 | .space--block { 410 | padding-left: 0; 411 | padding-right: 0; 412 | } 413 | .space--inline { 414 | padding-top: 0; 415 | padding-bottom: 0; 416 | } 417 | /* Fullwidth */ 418 | .fullwidth { 419 | } 420 | @media (min-width: 576px) { 421 | .main, .footer { 422 | padding-left: 16px; 423 | padding-right: 16px; 424 | padding-left: var(--layout-gutter-inline); 425 | padding-right: var(--layout-gutter-inline); 426 | } 427 | } 428 | .container { 429 | max-width: 768px; 430 | max-width: var(--layout-breakpoint-md); 431 | padding-left: 16px; 432 | padding-right: 16px; 433 | padding-left: var(--layout-gutter-inline); 434 | padding-right: var(--layout-gutter-inline); 435 | margin-left: auto; 436 | margin-right: auto; 437 | } 438 | .fullwidth > .container { 439 | padding-top: var(--layout-space); 440 | padding-bottom: var(--layout-space); 441 | } 442 | /* Grid */ 443 | .row { 444 | display: flex; 445 | flex-wrap: wrap; 446 | row-gap: 16px; 447 | row-gap: var(--layout-gutter-inline); 448 | margin-left: calc(-1 * 16px); 449 | margin-right: calc(-1 * 16px); 450 | margin-left: calc(-1 * var(--layout-gutter-inline)); 451 | margin-right: calc(-1 * var(--layout-gutter-inline)); 452 | } 453 | .row > * { 454 | flex-shrink: 0; 455 | width: 100%; 456 | max-width: 100%; 457 | padding-left: 16px; 458 | padding-right: 16px; 459 | padding-left: var(--layout-gutter-inline); 460 | padding-right: var(--layout-gutter-inline); 461 | } 462 | @media (min-width: 768px) { 463 | .col-md-4 { 464 | flex: 0 0 auto; 465 | width: 33.33333333%; 466 | } 467 | 468 | .col-md-8 { 469 | flex: 0 0 auto; 470 | width: 66.66666667%; 471 | } 472 | } 473 | @media (min-width: 992px) { 474 | .col-lg-4 { 475 | flex: 0 0 auto; 476 | width: 33.33333333%; 477 | } 478 | 479 | .col-lg-8 { 480 | flex: 0 0 auto; 481 | width: 66.66666667%; 482 | } 483 | } 484 | /* Flow */ 485 | .flow > * + * { 486 | margin-top: 1.5rem; 487 | margin-top: var(--flow-space, 1.5rem); 488 | } 489 | /* Large gap before headings and after h1 */ 490 | .flow .heading:has(h1) + * { 491 | --flow-space: var(--layout-space-lg); 492 | } 493 | .flow h1, .flow h2, .flow h3, .flow h4, .flow h1 + * { 494 | --flow-space: var(--layout-space-lg); 495 | } 496 | /* Medium gap if a heading follows a heading */ 497 | .flow h1 + h2, .flow h2 + h3, .flow h3 + h4, .flow .heading + h2, .flow .heading + h3, .flow .heading + h4 { 498 | --flow-space: var(--layout-space-md); 499 | } 500 | /* Small gap directly after heading and inside heading wrapper */ 501 | .flow h2:not(.does-not-exist) + *, .flow h3:not(.does-not-exist) + *, .flow h4:not(.does-not-exist) + *, .flow .heading + *, .flow h2:not(.does-not-exist) + p + *, .flow h3:not(.does-not-exist) + p + *, .flow h4:not(.does-not-exist) + p + *, .flow .heading + p + *, .flow.heading > * + * { 502 | --flow-space: var(--layout-space-sm); 503 | } 504 | /* Group */ 505 | .group, .nav { 506 | --layout-space: var(--layout-space-sm); 507 | 508 | display: flex; 509 | flex-wrap: wrap; 510 | gap: 16px; 511 | gap: var(--layout-space); 512 | } 513 | /* .group--min { 514 | > * > *:first-child { 515 | &, & > *:first-child { 516 | min-width: 144px; 517 | text-align: center; 518 | } 519 | } 520 | } */ 521 | /* Demos */ 522 | .demos .container { 523 | --flow-space: var(--layout-space-lg); 524 | } 525 | /* ----- Buttons ----- */ 526 | .btn { 527 | --padding-inline: var(--layout-space-sm); 528 | --padding-block: var(--layout-space-xs); 529 | 530 | --text-color: #fff; 531 | --bg-color: var(--color-500); 532 | --border-color: var(--color-500); 533 | --border-size: 0px; 534 | 535 | --text-color-hover: #fff; 536 | --bg-color-hover: var(--color-700); 537 | --border-color-hover: var(--color-700); 538 | 539 | display: inline-flex; 540 | gap: .25em; 541 | justify-content: center; 542 | align-items: center; 543 | padding: 8px 16px; 544 | padding: var(--padding-block) var(--padding-inline); 545 | border: 0px solid rgb(1, 87, 255); 546 | border: var(--border-size) solid var(--border-color); 547 | background-color: rgb(1, 87, 255); 548 | background-color: var(--bg-color); 549 | transition: all .25s; 550 | 551 | color: #fff; 552 | 553 | color: var(--text-color); 554 | font-size: 1rem; 555 | line-height: 1.5; 556 | -webkit-text-decoration: none; 557 | text-decoration: none; 558 | text-align: center; 559 | cursor: pointer; 560 | 561 | position: relative; 562 | overflow: hidden; 563 | } 564 | .btn > span:not(.does-not-exist), .btn > .icon { 565 | position: relative; 566 | z-index: 1; 567 | } 568 | .btn > span { 569 | position: relative; 570 | z-index: 1; 571 | 572 | display: inline-flex; 573 | gap: .25em; 574 | justify-content: center; 575 | align-items: center; 576 | } 577 | .btn::after { 578 | content: ""; 579 | position: absolute; 580 | top: 0; 581 | left: 0; 582 | width: 100%; 583 | height: 100%; 584 | z-index: 0; 585 | background-color: var(--bg-color-hover); 586 | scale: 0 1; 587 | transform-origin: 100% 50%; 588 | transition-property: scale; 589 | transition-duration: inherit; 590 | transition-timing-function: cubic-bezier(.215, .610, .355, 1); 591 | transition-timing-function: var(--ease-out-cubic); 592 | } 593 | .btn:hover, .btn:focus, .btn.is-active, a:hover .btn { 594 | color: var(--text-color-hover); 595 | border-color: var(--border-color-hover); 596 | } 597 | .btn:hover::after { 598 | scale: 1; 599 | transform-origin: 0 50%; 600 | } 601 | .btn:focus::after { 602 | scale: 1; 603 | transform-origin: 0 50%; 604 | } 605 | .btn.is-active::after { 606 | scale: 1; 607 | transform-origin: 0 50%; 608 | } 609 | a:hover .btn::after { 610 | scale: 1; 611 | transform-origin: 0 50%; 612 | } 613 | .btn:disabled { 614 | pointer-events: none; 615 | opacity: .75; 616 | } 617 | .btn.disabled { 618 | pointer-events: none; 619 | opacity: .75; 620 | } 621 | .btn .icon, .btn svg { 622 | pointer-events: none; 623 | } 624 | .btn.btn--outline { 625 | --text-color: var(--color-500); 626 | } 627 | .btn--white { 628 | --text-color: var(--color-500); 629 | --bg-color: var(--color-50); 630 | --border-color: #fff; 631 | 632 | --text-color-hover: var(--color-700); 633 | --bg-color-hover: #fff; 634 | --border-color-hover: #fff; 635 | } 636 | .btn--white.btn--outline { 637 | --text-color: #fff; 638 | } 639 | .btn--green { 640 | --bg-color: var(--color-accent-500); 641 | --border-color: var(--color-accent-500); 642 | 643 | --bg-color-hover: var(--color-accent-700); 644 | --border-color-hover: var(--color-accent-700); 645 | } 646 | .btn--green.btn--outline { 647 | --text-color: var(--color-accent-700); 648 | } 649 | .btn--outline, .btn--ghost { 650 | --bg-color: transparent; 651 | --border-size: 1px; 652 | } 653 | .btn--ghost { 654 | --text-color: #fff; 655 | --text-color-hover: var(--color-700); 656 | --bg-color-hover: #fff; 657 | --border-color-hover: #fff; 658 | } 659 | .btn--icon, .btn--round { 660 | --padding-inline: 1rem; 661 | --padding-block: 1rem; 662 | } 663 | .btn--round { 664 | align-items: center; 665 | justify-content: center; 666 | border-radius: 50%; 667 | aspect-ratio: 1 / 1; 668 | } 669 | .btn--icon-multi:not(.is-active) .icon:last-of-type, .btn--icon-multi.is-active .icon:first-of-type { 670 | display: none; 671 | } 672 | .icon { 673 | display: inline-flex; 674 | justify-content: center; 675 | align-items: center; 676 | width: 1em; 677 | height: 1em; 678 | fill: currentColor; 679 | stroke: currentColor; 680 | transition: inherit; 681 | } 682 | .icon use { 683 | transition: inherit; 684 | } 685 | a .icon, button .icon { 686 | pointer-events: none; 687 | } 688 | /* ----- Header and footer ----- */ 689 | .header { 690 | position: sticky; 691 | top: 0; 692 | z-index: 100; 693 | width: 100%; 694 | color: #fff; 695 | background-color: rgb(1, 87, 255); 696 | background-color: var(--color-500); 697 | border-bottom: 1px solid #fff; 698 | } 699 | @media (min-width: 768px) { 700 | .header { 701 | min-height: 80px; 702 | } 703 | } 704 | .header .container { 705 | display: flex; 706 | gap: 16px; 707 | gap: var(--layout-space-sm); 708 | flex-wrap: wrap; 709 | align-items: center; 710 | } 711 | .header a:not(.btn) { 712 | color: #fff; 713 | -webkit-text-decoration: none; 714 | text-decoration: none; 715 | } 716 | .header a:not(.btn):hover, .header a:not(.btn):focus { 717 | color: rgb(223, 234, 255); 718 | color: color(display-p3 0.8822 0.91623 0.99269); 719 | color: var(--color-100); 720 | } 721 | .logo-text { 722 | flex: 1 0 0%; 723 | margin: 0; 724 | font-family: 'Source Code Pro', monospace; 725 | font-family: var(--heading-font-family); 726 | color: #fff; 727 | 728 | /* font-size: 2.5rem; 729 | font-size: clamp(2rem, 1.5229rem + 2.0356vw, 2.5rem); */ 730 | 731 | font-size: 2rem; 732 | font-size: clamp(1.5rem, 1.0229rem + 2.0356vw, 2rem); 733 | } 734 | .header-nav { 735 | margin-left: auto; 736 | } 737 | .header-nav:not(:has(.btn)) { 738 | row-gap: 0; 739 | } 740 | @media (max-width: 575px) { 741 | .header-nav { 742 | width: 100%; 743 | } 744 | } 745 | .header-nav a:not(.btn) { 746 | position: relative; 747 | color: rgb(223, 234, 255); 748 | color: color(display-p3 0.8822 0.91623 0.99269); 749 | color: var(--color-100); 750 | text-transform: uppercase; 751 | 752 | } 753 | .header-nav a:not(.btn)::after { 754 | content: ""; 755 | position: absolute; 756 | bottom: 0; 757 | left: 0; 758 | width: 100%; 759 | height: 2px; 760 | transition: transform .2s ease-in-out; 761 | 762 | z-index: -1; 763 | background-color: currentColor; 764 | transform: scaleX(0); 765 | transform-origin: 100% 50%; 766 | transition-timing-function: cubic-bezier(.65, .05, .36, 1); 767 | transition-timing-function: var(--ease-in-out-cubic); 768 | } 769 | .header-nav a:not(.btn):hover, .header-nav a:not(.btn):focus, .header-nav a.is-active:not(.btn) { 770 | color: #fff; 771 | } 772 | .header-nav a:not(.btn):hover::after { 773 | transform: scaleX(1); 774 | transform-origin: 0 50%; 775 | } 776 | .header-nav a:not(.btn):focus::after { 777 | transform: scaleX(1); 778 | transform-origin: 0 50%; 779 | } 780 | .header-nav a.is-active:not(.btn)::after { 781 | transform: scaleX(1); 782 | transform-origin: 0 50%; 783 | } 784 | @media (max-width: 575px) { 785 | .header-nav .btn { 786 | width: calc(50% - (var(--layout-space) / 2)); 787 | --padding-block: var(--layout-space-xxs); 788 | } 789 | } 790 | .footer { 791 | text-align: center; 792 | } 793 | .footer .container > * { 794 | padding-top: var(--layout-space); 795 | border-top: 2px solid rgb(1, 87, 255); 796 | border-top: 2px solid oklch(53.67% 0.257 262.51); 797 | border-top: 2px solid var(--color-500); 798 | } 799 | .footer .container > *:not(:first-child) { 800 | margin-top: var(--layout-space); 801 | } 802 | .footer .group, .footer .nav { 803 | align-items: center; 804 | justify-content: center; 805 | } 806 | @media (max-width: 575px) { 807 | .footer .share-title { 808 | width: 100%; 809 | } 810 | } 811 | .footer-nav { 812 | row-gap: 8px; 813 | row-gap: var(--layout-space-xs); 814 | font-size: .875rem; 815 | } 816 | /* Page heading */ 817 | .page-intro { 818 | display: flex; 819 | gap: 32px; 820 | gap: var(--layout-space-md); 821 | align-items: center; 822 | } 823 | .page-heading { 824 | flex-grow: 1; 825 | text-wrap: pretty; 826 | } 827 | .page-intro-img { 828 | width: 80px; 829 | height: auto; 830 | border-radius: 50%; 831 | } 832 | @media (min-width: 576px) { 833 | .columns-sm-2 { 834 | -moz-column-count: 2; 835 | column-count: 2; 836 | -moz-column-gap: 32px; 837 | column-gap: 32px; 838 | -moz-column-gap: var(--layout-space-md); 839 | column-gap: var(--layout-space-md); 840 | } 841 | } 842 | .columns-sm-2 > * { 843 | page-break-inside: avoid; 844 | -moz-column-break-inside: avoid; 845 | break-inside: avoid; 846 | text-wrap: pretty; 847 | } 848 | /* Section heading */ 849 | .section-heading { 850 | display: flex; 851 | gap: 16px; 852 | gap: var(--layout-space-sm); 853 | margin-top: 64px; 854 | margin-top: var(--layout-space-xl); 855 | } 856 | .section-heading > *:first-child { 857 | flex-grow: 1; 858 | } 859 | /* Form elements */ 860 | .select { 861 | padding: .25rem 1rem .25rem .5rem; 862 | background: #fff; 863 | border: 1px solid rgb(1, 87, 255); 864 | border: 1px solid var(--color-500); 865 | } 866 | /* Demo */ 867 | /* Social image - 1200x630 */ 868 | /* body { 869 | scale: 1.5; 870 | transform-origin: top center; 871 | } 872 | 873 | .header { 874 | display: flex; 875 | border: none; 876 | 877 | .container { 878 | width: 100%; 879 | } 880 | } 881 | 882 | .header-nav { 883 | display: none; 884 | } 885 | 886 | .intro .container { 887 | display: flex; 888 | height: 340px; 889 | 890 | > *:not(.page-intro) { 891 | display: none; 892 | } 893 | } 894 | 895 | 896 | .page-heading { 897 | font-size: 3rem; 898 | } 899 | 900 | .page-intro-img { 901 | width: 160px; 902 | } */ 903 | /* Portfolio Image */ 904 | /* body { 905 | scale: 1.565; 906 | transform-origin: top center; 907 | } 908 | 909 | .header { 910 | display: none; 911 | } 912 | 913 | .progress-text p { 914 | display: none !important; 915 | } */ 916 | -------------------------------------------------------------------------------- /src/styles/progress-tracker.css: -------------------------------------------------------------------------------- 1 | .progress-tracker { 2 | --marker-size: 32px; 3 | --marker-size-block: 32px; 4 | --marker-size-inline: var(--marker-size-block); 5 | --marker-size-half: calc(var(--marker-size-block) * 0.5); 6 | --marker-spacing: -1px; /* Fix for rounding errors */ 7 | 8 | --marker-bg: #999; 9 | --marker-bg-active: #0034a3; 10 | --marker-bg-complete: #0157ff; 11 | --marker-bg-hover: #3f7eff; 12 | 13 | --marker-color: #fff; 14 | 15 | 16 | --path-size-block: 4px; 17 | --path-size-inline: calc(100% - (var(--marker-size-inline) + (var(--marker-spacing) * 2))); 18 | --path-position-block: calc(var(--marker-size-half) - (var(--path-size-block) * 0.5)); 19 | --path-position-inline: calc(var(--marker-size-inline) + var(--marker-spacing)); 20 | 21 | --path-bg: #999; 22 | --path-bg-active: #999; 23 | --path-bg-complete: #0157ff; 24 | 25 | 26 | --text-color: #0034a3; 27 | --text-color-hover: #0157ff; 28 | 29 | --animation-duration: 0.3s; 30 | 31 | display: flex; 32 | margin: 0; 33 | padding: 0; 34 | list-style: none; 35 | counter-reset: step; 36 | } 37 | 38 | .progress-step { 39 | position: relative; 40 | display: flex; 41 | flex-direction: column; 42 | flex: 1 1 0%; 43 | margin: 0; 44 | padding: 0; 45 | } 46 | 47 | .progress-step:last-child:not(:has(.progress-text)) { 48 | flex-grow: 0; 49 | } 50 | 51 | /* Marker shape */ 52 | 53 | .progress-step::before { 54 | --bg: var(--marker-bg); 55 | --color: var(--marker-color); 56 | 57 | content: ""; 58 | position: relative; 59 | z-index: 20; 60 | flex-shrink: 0; 61 | display: flex; 62 | justify-content: center; 63 | align-items: center; 64 | width: var(--marker-size-inline); 65 | height: var(--marker-size-block); 66 | border-radius: 50%; 67 | color: var(--color); 68 | background-color: var(--bg); 69 | 70 | transition: background-color, border-color; 71 | transition-duration: var(--animation-duration); 72 | } 73 | 74 | /* Marker path */ 75 | 76 | .progress-step::after { 77 | --bg: var(--path-bg); 78 | } 79 | 80 | .progress-step:not(:last-child)::after { 81 | content: ""; 82 | display: block; 83 | position: absolute; 84 | order: -1; 85 | top: var(--path-position-block); 86 | left: var(--path-position-inline); 87 | width: var(--path-size-inline); 88 | height: var(--path-size-block); 89 | background-color: var(--bg); 90 | transition: background-color, background-position; 91 | transition-duration: var(--animation-duration); 92 | } 93 | 94 | .progress-text { 95 | padding: 8px; 96 | overflow: hidden; 97 | color: var(--text-color); 98 | text-overflow: ellipsis; 99 | } 100 | 101 | .progress-text > * + * { 102 | margin-top: 4px; 103 | } 104 | 105 | .progress-text:where(a, button) { 106 | -webkit-text-decoration: none; 107 | text-decoration: none; 108 | text-align: inherit; 109 | line-height: inherit; 110 | color: inherit; 111 | background: none; 112 | border: none; 113 | } 114 | 115 | .progress-text:where(a, button):hover { 116 | cursor: pointer; 117 | color: var(--text-color-hover); 118 | } 119 | 120 | .progress-text:where(a, button):focus { 121 | cursor: pointer; 122 | color: var(--text-color-hover); 123 | } 124 | 125 | .progress-title { 126 | font-size: 1.5rem; 127 | } 128 | 129 | /* States */ 130 | 131 | .progress-step.is-active::before { 132 | --bg: var(--marker-bg-active); 133 | } 134 | 135 | .progress-step.is-active::after { 136 | --bg: var(--path-bg-active); 137 | } 138 | 139 | .progress-step.is-complete::before { 140 | --bg: var(--marker-bg-complete); 141 | } 142 | 143 | .progress-step.is-complete::after { 144 | --bg: var(--path-bg-complete); 145 | } 146 | 147 | .progress-step:not(.is-active):has(.progress-text:where(a, button)):hover::before { 148 | --bg: var(--marker-bg-hover); 149 | } 150 | 151 | .progress-step:not(.is-active):has(.progress-text:where(a, button)):focus::before { 152 | --bg: var(--marker-bg-hover); 153 | } 154 | 155 | /* Variants */ 156 | 157 | .progress-tracker--marker-counter .progress-step::before { 158 | content: counter(step); 159 | counter-increment: step; 160 | } 161 | 162 | .progress-tracker--marker-dataset .progress-step::before { 163 | content: attr(data-text); 164 | content: attr(data-text, ""); 165 | } 166 | 167 | .progress-tracker--marker-square { 168 | --marker-size-block: 24px; 169 | --marker-size-inline: 4px; 170 | --path-position-block: calc(var(--marker-size-block) - var(--path-size-block)); 171 | } 172 | 173 | .progress-tracker--marker-square .progress-step::before { 174 | border-radius: 0; 175 | } 176 | 177 | .progress-tracker--spaced { 178 | --marker-spacing: 8px; 179 | } 180 | 181 | .progress-tracker--inline { 182 | overflow: auto; 183 | } 184 | 185 | .progress-tracker--inline .progress-step { 186 | align-items: center; 187 | flex-direction: row; 188 | min-width: -moz-fit-content; 189 | min-width: fit-content; 190 | } 191 | 192 | .progress-tracker--inline .progress-step::before { 193 | flex-shrink: 0; 194 | order: 2; 195 | } 196 | 197 | .progress-tracker--inline .progress-step::after { 198 | position: relative; 199 | top: auto; 200 | left: 0; 201 | order: 3; 202 | } 203 | 204 | .progress-tracker--inline .progress-text { 205 | flex: 0 0 auto; 206 | padding: 8px 12px; 207 | } 208 | 209 | .progress-tracker--inline .progress-text:has(*:nth-child(2)) { 210 | flex-basis: min-content; 211 | } 212 | 213 | .progress-tracker--inline:not(.progress-tracker--inline-text-right) .progress-step:first-child .progress-text { 214 | padding-left: 0; 215 | } 216 | 217 | .progress-tracker--inline-text-right .progress-text { 218 | order: 2; 219 | } 220 | 221 | .progress-tracker--vertical { 222 | flex-direction: column; 223 | } 224 | 225 | .progress-tracker--vertical .progress-step { 226 | flex-direction: row; 227 | } 228 | 229 | .progress-tracker--vertical .progress-step::after { 230 | top: calc(var(--marker-size-block) + var(--marker-spacing)); 231 | left: var(--path-position-block); 232 | width: var(--path-size-block); 233 | height: var(--path-size-inline); 234 | } 235 | 236 | .progress-tracker--vertical .progress-text { 237 | flex-grow: 1; 238 | padding-top: 4px; 239 | padding-bottom: 12px; 240 | } 241 | 242 | .progress-tracker--center { 243 | --path-position-inline: calc(50% + (var(--marker-size-inline) / 2) + var(--marker-spacing)); 244 | 245 | text-align: center; 246 | } 247 | 248 | .progress-tracker--center:not(.progress-tracker--vertical) .progress-step::before { 249 | margin-left: auto; 250 | margin-right: auto; 251 | } 252 | 253 | .progress-tracker--center.progress-tracker--vertical { 254 | max-width: 240px; 255 | margin-left: auto; 256 | margin-right: auto; 257 | } 258 | 259 | .progress-tracker--right { 260 | --path-position-inline: calc(100% + var(--marker-spacing)); 261 | 262 | text-align: right; 263 | } 264 | 265 | .progress-tracker--right:not(.progress-tracker--vertical) .progress-step::before { 266 | margin-left: auto; 267 | } 268 | 269 | .progress-tracker--right.progress-tracker--vertical .progress-step { 270 | flex-direction: row-reverse; 271 | } 272 | 273 | .progress-tracker--right.progress-tracker--vertical .progress-step::after { 274 | left: auto; 275 | right: var(--path-position-block); 276 | } 277 | 278 | .progress-tracker--reverse:not(.progress-tracker--vertical) .progress-step::after { 279 | top: auto; 280 | bottom: var(--path-position-block); 281 | } 282 | 283 | .progress-tracker--reverse.progress-tracker--marker-square .progress-step::after { 284 | top: auto; 285 | bottom: 0; 286 | } 287 | 288 | .progress-tracker--reverse .progress-text { 289 | order: -1; 290 | flex-grow: 1; 291 | } 292 | 293 | .progress-tracker--anim .progress-step.is-active:not(:last-child)::after, .progress-tracker--anim .progress-step.is-complete:not(:last-child)::after { 294 | background-image: linear-gradient(to right, var(--path-bg-active) 50%, var(--path-bg-complete) 50%); 295 | background-size: 200% 100%; 296 | background-position: 0% 100%; 297 | } 298 | 299 | .progress-tracker--anim .progress-step.is-complete:not(:last-child)::after { 300 | background-position: -100% 100%; 301 | } 302 | 303 | .progress-tracker--anim.progress-tracker--vertical .progress-step.is-active:not(:last-child)::after, .progress-tracker--anim.progress-tracker--vertical .progress-step.is-complete:not(:last-child)::after { 304 | background-image: linear-gradient(to bottom, var(--path-bg-active) 50%, var(--path-bg-complete) 50%); 305 | background-size: 100% 200%; 306 | background-position: 100% 0%; 307 | } 308 | 309 | .progress-tracker--anim.progress-tracker--vertical .progress-step.is-complete:not(:last-child)::after { 310 | background-position: 100% -100%; 311 | } 312 | -------------------------------------------------------------------------------- /src/styles/progress-tracker/progress-tracker.css: -------------------------------------------------------------------------------- 1 | .progress-tracker { 2 | --marker-size: 32px; 3 | --marker-size-block: 32px; 4 | --marker-size-inline: var(--marker-size-block); 5 | --marker-size-half: calc(var(--marker-size-block) * 0.5); 6 | --marker-spacing: -1px; /* Fix for rounding errors */ 7 | 8 | --marker-bg: #999; 9 | --marker-bg-active: #0034a3; 10 | --marker-bg-complete: #0157ff; 11 | --marker-bg-hover: #3f7eff; 12 | 13 | --marker-color: #fff; 14 | 15 | 16 | --path-size-block: 4px; 17 | --path-size-inline: calc(100% - (var(--marker-size-inline) + (var(--marker-spacing) * 2))); 18 | --path-position-block: calc(var(--marker-size-half) - (var(--path-size-block) * 0.5)); 19 | --path-position-inline: calc(var(--marker-size-inline) + var(--marker-spacing)); 20 | 21 | --path-bg: #999; 22 | --path-bg-active: #999; 23 | --path-bg-complete: #0157ff; 24 | 25 | 26 | --text-color: #0034a3; 27 | --text-color-hover: #0157ff; 28 | 29 | --animation-duration: 0.3s; 30 | 31 | display: flex; 32 | margin: 0; 33 | padding: 0; 34 | list-style: none; 35 | counter-reset: step; 36 | } 37 | 38 | .progress-step { 39 | position: relative; 40 | display: flex; 41 | flex-direction: column; 42 | flex: 1 1 0%; 43 | margin: 0; 44 | padding: 0; 45 | 46 | &:last-child:not(:has(.progress-text)) { 47 | flex-grow: 0; 48 | } 49 | 50 | /* Marker shape */ 51 | &::before { 52 | --bg: var(--marker-bg); 53 | --color: var(--marker-color); 54 | 55 | content: ""; 56 | position: relative; 57 | z-index: 20; 58 | flex-shrink: 0; 59 | display: flex; 60 | justify-content: center; 61 | align-items: center; 62 | width: var(--marker-size-inline); 63 | height: var(--marker-size-block); 64 | border-radius: 50%; 65 | color: var(--color); 66 | background-color: var(--bg); 67 | 68 | transition: background-color, border-color; 69 | transition-duration: var(--animation-duration); 70 | } 71 | 72 | /* Marker path */ 73 | &::after { 74 | --bg: var(--path-bg); 75 | } 76 | 77 | &:not(:last-child)::after { 78 | content: ""; 79 | display: block; 80 | position: absolute; 81 | order: -1; 82 | inset-block-start: var(--path-position-block); 83 | inset-inline-start: var(--path-position-inline); 84 | width: var(--path-size-inline); 85 | height: var(--path-size-block); 86 | background-color: var(--bg); 87 | transition: background-color, background-position; 88 | transition-duration: var(--animation-duration); 89 | } 90 | } 91 | 92 | .progress-text { 93 | padding: 8px; 94 | overflow: hidden; 95 | color: var(--text-color); 96 | text-overflow: ellipsis; 97 | 98 | > * + * { 99 | margin-block-start: 4px; 100 | } 101 | 102 | &:where(a, button) { 103 | text-decoration: none; 104 | text-align: inherit; 105 | line-height: inherit; 106 | color: inherit; 107 | background: none; 108 | border: none; 109 | 110 | &:is(:hover, :focus) { 111 | cursor: pointer; 112 | color: var(--text-color-hover); 113 | } 114 | } 115 | } 116 | 117 | .progress-title { 118 | font-size: 1.5rem; 119 | } 120 | 121 | 122 | /* States */ 123 | 124 | .progress-step { 125 | 126 | &.is-active { 127 | &::before { 128 | --bg: var(--marker-bg-active); 129 | } 130 | 131 | &::after { 132 | --bg: var(--path-bg-active); 133 | } 134 | } 135 | 136 | &.is-complete { 137 | &::before { 138 | --bg: var(--marker-bg-complete); 139 | } 140 | 141 | &::after { 142 | --bg: var(--path-bg-complete); 143 | } 144 | } 145 | 146 | &:not(.is-active):has(.progress-text:where(a, button)) { 147 | &:is(:hover, :focus) { 148 | &::before { 149 | --bg: var(--marker-bg-hover); 150 | } 151 | } 152 | } 153 | 154 | } 155 | 156 | 157 | 158 | /* Variants */ 159 | .progress-tracker--marker-counter { 160 | .progress-step { 161 | &::before { 162 | content: counter(step); 163 | counter-increment: step; 164 | } 165 | } 166 | } 167 | 168 | .progress-tracker--marker-dataset { 169 | .progress-step { 170 | &::before { 171 | content: attr(data-text); 172 | content: attr(data-text, ""); 173 | } 174 | } 175 | } 176 | 177 | .progress-tracker--marker-square { 178 | --marker-size-block: 24px; 179 | --marker-size-inline: 4px; 180 | --path-position-block: calc(var(--marker-size-block) - var(--path-size-block)); 181 | 182 | .progress-step { 183 | &::before { 184 | border-radius: 0; 185 | } 186 | } 187 | } 188 | 189 | 190 | .progress-tracker--spaced { 191 | --marker-spacing: 8px; 192 | } 193 | 194 | 195 | .progress-tracker--inline { 196 | overflow: auto; 197 | 198 | .progress-step { 199 | align-items: center; 200 | flex-direction: row; 201 | min-width: fit-content; 202 | 203 | &::before { 204 | flex-shrink: 0; 205 | order: 2; 206 | } 207 | 208 | &::after { 209 | position: relative; 210 | inset-block-start: auto; 211 | inset-inline-start: 0; 212 | order: 3; 213 | } 214 | } 215 | 216 | .progress-text { 217 | flex: 0 0 auto; 218 | padding: 8px 12px; 219 | 220 | &:has(*:nth-child(2)) { 221 | flex-basis: min-content; 222 | } 223 | } 224 | 225 | &:not(.progress-tracker--inline-text-right) .progress-step:first-child { 226 | .progress-text { 227 | padding-inline-start: 0; 228 | } 229 | } 230 | } 231 | 232 | .progress-tracker--inline-text-right { 233 | .progress-text { 234 | order: 2; 235 | } 236 | } 237 | 238 | 239 | .progress-tracker--vertical { 240 | flex-direction: column; 241 | 242 | .progress-step { 243 | flex-direction: row; 244 | 245 | &::after { 246 | inset-block-start: calc(var(--marker-size-block) + var(--marker-spacing)); 247 | inset-inline-start: var(--path-position-block); 248 | width: var(--path-size-block); 249 | height: var(--path-size-inline); 250 | } 251 | } 252 | 253 | .progress-text { 254 | flex-grow: 1; 255 | padding-block: 4px 12px; 256 | } 257 | } 258 | 259 | 260 | .progress-tracker--center { 261 | --path-position-inline: calc(50% + (var(--marker-size-inline) / 2) + var(--marker-spacing)); 262 | 263 | text-align: center; 264 | 265 | &:not(.progress-tracker--vertical) .progress-step { 266 | &::before { 267 | margin-inline: auto; 268 | } 269 | } 270 | 271 | &.progress-tracker--vertical { 272 | max-width: 240px; 273 | margin-inline: auto; 274 | } 275 | } 276 | 277 | .progress-tracker--right { 278 | --path-position-inline: calc(100% + var(--marker-spacing)); 279 | 280 | text-align: right; 281 | 282 | &:not(.progress-tracker--vertical) .progress-step { 283 | &::before { 284 | margin-inline-start: auto; 285 | } 286 | } 287 | 288 | &.progress-tracker--vertical .progress-step { 289 | flex-direction: row-reverse; 290 | 291 | &::after { 292 | inset-inline-start: auto; 293 | inset-inline-end: var(--path-position-block); 294 | } 295 | } 296 | } 297 | 298 | 299 | .progress-tracker--reverse { 300 | 301 | &:not(.progress-tracker--vertical) .progress-step { 302 | &::after { 303 | inset-block-start: auto; 304 | inset-block-end: var(--path-position-block); 305 | } 306 | } 307 | 308 | &.progress-tracker--marker-square .progress-step { 309 | &::after { 310 | inset-block: auto 0; 311 | } 312 | } 313 | 314 | .progress-text { 315 | order: -1; 316 | flex-grow: 1; 317 | } 318 | } 319 | 320 | 321 | .progress-tracker--anim { 322 | .progress-step { 323 | &.is-active, &.is-complete { 324 | &:not(:last-child)::after { 325 | background-image: linear-gradient(to right, var(--path-bg-active) 50%, var(--path-bg-complete) 50%); 326 | background-size: 200% 100%; 327 | background-position: 0% 100%; 328 | } 329 | } 330 | &.is-complete { 331 | &:not(:last-child)::after { 332 | background-position: -100% 100%; 333 | } 334 | } 335 | } 336 | 337 | &.progress-tracker--vertical .progress-step { 338 | &.is-active, &.is-complete { 339 | &:not(:last-child)::after { 340 | background-image: linear-gradient(to bottom, var(--path-bg-active) 50%, var(--path-bg-complete) 50%); 341 | background-size: 100% 200%; 342 | background-position: 100% 0%; 343 | } 344 | } 345 | &.is-complete { 346 | &:not(:last-child)::after { 347 | background-position: 100% -100%; 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/styles/share-url.css: -------------------------------------------------------------------------------- 1 | .share-url, share-url button { 2 | display: inline-flex; 3 | gap: .25em; 4 | justify-content: center; 5 | align-items: center; 6 | padding: 8px 16px; 7 | border: none; 8 | background-color: #0157ff; 9 | transition: all .25s; 10 | 11 | color: #fff; 12 | font-size: 1rem; 13 | line-height: 1.5; 14 | -webkit-text-decoration: none; 15 | text-decoration: none; 16 | text-align: center; 17 | cursor: pointer; 18 | } 19 | 20 | .share-url:hover, .share-url:focus, .share-url.is-active, share-url button:hover, share-url button:focus, share-url button.is-active { 21 | color: #fff; 22 | background-color: #0034a3; 23 | } 24 | 25 | .share-url svg, share-url button svg { 26 | width: 1em; 27 | height: 1em; 28 | fill: currentColor; 29 | stroke: currentColor; 30 | } 31 | 32 | .share-url:not(.is-active) svg:last-of-type, share-url button:not(.is-active) svg:last-of-type, .share-url.is-active svg:first-of-type, share-url button.is-active svg:first-of-type { 33 | display: none; 34 | } 35 | 36 | /* Fallback - If JS is unavailable or action is not available */ 37 | 38 | .share-url fallback a, share-url button fallback a, .share-url fallback a:hover, share-url button fallback a:hover { 39 | color: #fff; 40 | } 41 | 42 | @media (scripting: enabled) { 43 | .share-url:not(.is-fallback) fallback, share-url button:not(.is-fallback) fallback { 44 | display: none; 45 | } 46 | .share-url.is-fallback:has(fallback) > *:not(fallback), share-url button.is-fallback:has(fallback) > *:not(fallback) { 47 | display: none; 48 | } 49 | } 50 | 51 | @media (scripting: none) { 52 | .share-url:has(fallback) > *:not(fallback), share-url button:has(fallback) > *:not(fallback) { 53 | display: none; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/styles/site/base.css: -------------------------------------------------------------------------------- 1 | /* Type and background */ 2 | body { 3 | font-family: var(--body-font-family); 4 | color: var(--color-700); 5 | background-color: #fff; 6 | background-image: repeating-linear-gradient(90deg, var(--bg-grid-color) 0, var(--bg-grid-color) var(--bg-grid-line), transparent 0, transparent 50%), repeating-linear-gradient(180deg, var(--bg-grid-color) 0, var(--bg-grid-color) var(--bg-grid-line), transparent 0, transparent 50%); 7 | background-size: var(--bg-grid-box) var(--bg-grid-box); 8 | background-position: calc(50% - (var(--bg-grid-line)/2)) top; 9 | } 10 | 11 | .content { 12 | background-color: #fff; 13 | border-inline: calc(var(--bg-grid-line)/2) solid var(--bg-grid-color); 14 | } 15 | 16 | ::selection { 17 | color: #fff; 18 | background-color: var(--color-700); 19 | } 20 | 21 | h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 { 22 | font-family: var(--heading-font-family); 23 | font-weight: 400; 24 | word-spacing: -.5ch; 25 | } 26 | 27 | h1, h2, h3 { 28 | letter-spacing: -.025em; 29 | } 30 | 31 | h1, h2 { 32 | font-weight: 600; 33 | } 34 | 35 | /* Clamp from 375 - 768px */ 36 | h1 { font-size: 3rem; font-size: clamp(2.5rem, 2.0229rem + 2.0356vw, 3rem); } 37 | h2 { font-size: 2rem; font-size: clamp(1.5rem, 1.0229rem + 2.0356vw, 2rem); } 38 | h3 { font-size: 1.5rem; font-size: clamp(1.25rem, 1.0115rem + 1.0178vw, 1.5rem); } 39 | h4 { font-size: 1.25rem; font-size: clamp(1.125rem, 1.0057rem + 0.5089vw, 1.25rem); } 40 | h5 { font-size: 1rem; } 41 | h6 { font-size: 1rem; } 42 | 43 | p { 44 | 45 | } 46 | 47 | ul, ol { 48 | padding-inline-start: var(--layout-space-md); 49 | } 50 | 51 | pre, code { 52 | font-family: var(--heading-font-family); 53 | } 54 | 55 | a { 56 | color: var(--link-color); 57 | transition: color 0.3s; 58 | 59 | &:hover, &:focus { 60 | color: var(--link-color-hover); 61 | } 62 | 63 | &:focus-within { 64 | outline: 2px solid var(--link-color); 65 | } 66 | } 67 | 68 | 69 | /* Elements and utilities */ 70 | html { 71 | scroll-padding-block-start: var(--header-height); 72 | scroll-behavior: smooth; 73 | } 74 | 75 | [id]:not(.fullwidth) { 76 | scroll-margin-block-start: 1ex; 77 | } 78 | 79 | 80 | .img-fluid { 81 | max-width: 100%; 82 | height: auto; 83 | } 84 | 85 | .w-100 { 86 | width: 100%; 87 | } 88 | 89 | 90 | code { 91 | padding: .125rem; 92 | background-color: var(--color-50); 93 | 94 | pre & { 95 | display: block; 96 | padding: 1rem; 97 | white-space: pre; 98 | overflow: auto; 99 | border: 1px solid var(--color-500); 100 | } 101 | } 102 | 103 | 104 | .table-outer { 105 | display: block; 106 | width: 100%; 107 | } 108 | 109 | .table { 110 | width: 100%; 111 | border-collapse: collapse; 112 | 113 | th, td { 114 | padding: var(--layout-space-sm); 115 | text-align: left; 116 | vertical-align: top; 117 | border: 1px solid var(--color-500); 118 | } 119 | 120 | th { 121 | background-color: var(--color-100); 122 | } 123 | } 124 | 125 | 126 | /* Scroll shadow for inline overflow */ 127 | /* Inspired by https://daverupert.com/2023/08/animation-timeline-scroll-shadows/ */ 128 | .scroll-shadow-inline { 129 | --shadow-color: rgb(0 0 0 / .2); 130 | --shadow-size: 8px; 131 | --shadow-spread: calc(var(--shadow-size) * -.5); 132 | 133 | overflow-x: auto; 134 | overflow-inline: auto; 135 | 136 | animation: scroll-shadow-inset linear; 137 | scroll-timeline: --scroll-shadow-timeline inline; 138 | animation-timeline: --scroll-shadow-timeline; 139 | 140 | /* This is shorthand for the above using an anonymous timeline instead of a named one */ 141 | /* animation-timeline: scroll(self inline); */ 142 | 143 | /* Non-essential styles */ 144 | border: 1px solid var(--color-500); 145 | 146 | /* Stops child elements with a background appearing above the shadow */ 147 | > * { 148 | mix-blend-mode: multiply; 149 | } 150 | 151 | /* Fallback */ 152 | @supports not (animation-timeline: scroll(self inline)) { 153 | /* Background color should be the same as the element */ 154 | --scroll-bg-color: #fff; 155 | background-image: 156 | linear-gradient(to right, var(--scroll-bg-color), var(--scroll-bg-color)), linear-gradient(to right, var(--scroll-bg-color), var(--scroll-bg-color)), 157 | linear-gradient(to right, var(--shadow-color), transparent), linear-gradient(to left, var(--shadow-color), transparent); 158 | background-size: 159 | calc(var(--shadow-size) * 4) 100%, calc(var(--shadow-size) * 4) 100%, 160 | calc(var(--shadow-size) * 2) 100%, calc(var(--shadow-size) * 2) 100%; 161 | background-position: left center, right center, left center, right center; 162 | background-attachment: local, local, scroll, scroll; 163 | background-repeat: no-repeat; 164 | } 165 | } 166 | 167 | .scroll-shadow-inline { 168 | &.table-outer { 169 | border-inline: 2px solid var(--color-500); 170 | } 171 | 172 | .table { 173 | th, td { 174 | &:first-child { 175 | border-inline-start: none; 176 | } 177 | 178 | &:last-child { 179 | border-inline-end: none; 180 | } 181 | } 182 | } 183 | 184 | code& { 185 | --scroll-bg-color: var(--color-50); 186 | } 187 | } 188 | 189 | /* Shadow animations */ 190 | /* Right shadow, left shadow. Negative spread to prevent a shadow on the top and bottom of the element */ 191 | @keyframes scroll-shadow-inset { 192 | from { 193 | box-shadow: 194 | inset calc(var(--shadow-size) * -2) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color), 195 | inset calc(var(--shadow-size) * 0) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color); 196 | } 197 | 10%, 90% { 198 | box-shadow: 199 | inset calc(var(--shadow-size) * -1) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color), 200 | inset calc(var(--shadow-size) * 1) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color); 201 | } 202 | to { 203 | box-shadow: 204 | inset calc(var(--shadow-size) * 0) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color), 205 | inset calc(var(--shadow-size) * 2) 0 var(--shadow-size) var(--shadow-spread) var(--shadow-color); 206 | } 207 | } 208 | 209 | @keyframes scroll-shadow-inline-end { 210 | from { 211 | box-shadow: 212 | calc(var(--shadow-size) * 0) 0 var(--shadow-size) 0 var(--shadow-color); 213 | } 214 | 10%, 90% { 215 | box-shadow: 216 | calc(var(--shadow-size) * 1) 0 var(--shadow-size) 0 var(--shadow-color); 217 | } 218 | to { 219 | box-shadow: 220 | calc(var(--shadow-size) * 2) 0 var(--shadow-size) 0 var(--shadow-color); 221 | } 222 | } 223 | 224 | 225 | /* Browser support message */ 226 | .unsupported { 227 | display: none; 228 | 229 | &.is-active { 230 | display: block; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/styles/site/components.css: -------------------------------------------------------------------------------- 1 | /* ----- Buttons ----- */ 2 | .btn { 3 | --padding-inline: var(--layout-space-sm); 4 | --padding-block: var(--layout-space-xs); 5 | 6 | --text-color: #fff; 7 | --bg-color: var(--color-500); 8 | --border-color: var(--color-500); 9 | --border-size: 0px; 10 | 11 | --text-color-hover: #fff; 12 | --bg-color-hover: var(--color-700); 13 | --border-color-hover: var(--color-700); 14 | 15 | display: inline-flex; 16 | gap: .25em; 17 | justify-content: center; 18 | align-items: center; 19 | padding: var(--padding-block) var(--padding-inline); 20 | border: var(--border-size) solid var(--border-color); 21 | background-color: var(--bg-color); 22 | transition: all .25s; 23 | 24 | color: var(--text-color); 25 | font-size: 1rem; 26 | line-height: 1.5; 27 | text-decoration: none; 28 | text-align: center; 29 | cursor: pointer; 30 | 31 | position: relative; 32 | overflow: hidden; 33 | 34 | > :is(span, .icon) { 35 | position: relative; 36 | z-index: 1; 37 | } 38 | 39 | > span { 40 | position: relative; 41 | z-index: 1; 42 | 43 | display: inline-flex; 44 | gap: .25em; 45 | justify-content: center; 46 | align-items: center; 47 | } 48 | 49 | &::after { 50 | content: ""; 51 | position: absolute; 52 | top: 0; 53 | left: 0; 54 | width: 100%; 55 | height: 100%; 56 | z-index: 0; 57 | background-color: var(--bg-color-hover); 58 | scale: 0 1; 59 | transform-origin: 100% 50%; 60 | transition-property: scale; 61 | transition-duration: inherit; 62 | transition-timing-function: var(--ease-out-cubic); 63 | } 64 | 65 | &:is(:hover, :focus, .is-active), a:hover & { 66 | color: var(--text-color-hover); 67 | border-color: var(--border-color-hover); 68 | 69 | &::after { 70 | scale: 1; 71 | transform-origin: 0 50%; 72 | } 73 | } 74 | 75 | &:is(.disabled, :disabled) { 76 | pointer-events: none; 77 | opacity: .75; 78 | } 79 | 80 | & .icon, & svg { 81 | pointer-events: none; 82 | } 83 | 84 | &.btn--outline { 85 | --text-color: var(--color-500); 86 | } 87 | } 88 | 89 | 90 | .btn--white { 91 | --text-color: var(--color-500); 92 | --bg-color: var(--color-50); 93 | --border-color: #fff; 94 | 95 | --text-color-hover: var(--color-700); 96 | --bg-color-hover: #fff; 97 | --border-color-hover: #fff; 98 | 99 | &.btn--outline { 100 | --text-color: #fff; 101 | } 102 | } 103 | 104 | .btn--green { 105 | --bg-color: var(--color-accent-500); 106 | --border-color: var(--color-accent-500); 107 | 108 | --bg-color-hover: var(--color-accent-700); 109 | --border-color-hover: var(--color-accent-700); 110 | 111 | &.btn--outline { 112 | --text-color: var(--color-accent-700); 113 | } 114 | } 115 | 116 | 117 | .btn--outline, .btn--ghost { 118 | --bg-color: transparent; 119 | --border-size: 1px; 120 | } 121 | 122 | .btn--ghost { 123 | --text-color: #fff; 124 | --text-color-hover: var(--color-700); 125 | --bg-color-hover: #fff; 126 | --border-color-hover: #fff; 127 | } 128 | 129 | 130 | .btn--icon, .btn--round { 131 | --padding-inline: 1rem; 132 | --padding-block: 1rem; 133 | } 134 | 135 | .btn--round { 136 | align-items: center; 137 | justify-content: center; 138 | border-radius: 50%; 139 | aspect-ratio: 1 / 1; 140 | } 141 | 142 | .btn--icon-multi { 143 | &:not(.is-active) .icon:last-of-type, &.is-active .icon:first-of-type { 144 | display: none; 145 | } 146 | } 147 | 148 | 149 | .icon { 150 | display: inline-flex; 151 | justify-content: center; 152 | align-items: center; 153 | width: 1em; 154 | height: 1em; 155 | fill: currentColor; 156 | stroke: currentColor; 157 | transition: inherit; 158 | 159 | & use { 160 | transition: inherit; 161 | } 162 | } 163 | 164 | a, button { 165 | & .icon { 166 | pointer-events: none; 167 | } 168 | } 169 | 170 | 171 | 172 | /* ----- Header and footer ----- */ 173 | .header { 174 | position: sticky; 175 | top: 0; 176 | z-index: 100; 177 | width: 100%; 178 | color: #fff; 179 | background-color: var(--color-500); 180 | border-bottom: 1px solid #fff; 181 | 182 | @media (--viewport-md-up) { 183 | min-height: 80px; 184 | } 185 | 186 | .container { 187 | display: flex; 188 | gap: var(--layout-space-sm); 189 | flex-wrap: wrap; 190 | align-items: center; 191 | } 192 | 193 | a:not(.btn) { 194 | color: #fff; 195 | text-decoration: none; 196 | 197 | &:is(:hover, :focus) { 198 | color: var(--color-100); 199 | } 200 | } 201 | } 202 | 203 | .logo-text { 204 | flex: 1 0 0%; 205 | margin: 0; 206 | font-family: var(--heading-font-family); 207 | color: #fff; 208 | 209 | /* font-size: 2.5rem; 210 | font-size: clamp(2rem, 1.5229rem + 2.0356vw, 2.5rem); */ 211 | 212 | font-size: 2rem; 213 | font-size: clamp(1.5rem, 1.0229rem + 2.0356vw, 2rem); 214 | } 215 | 216 | 217 | .header-nav { 218 | margin-inline-start: auto; 219 | 220 | &:not(:has(.btn)) { 221 | row-gap: 0; 222 | } 223 | 224 | @media (--viewport-sm-down) { 225 | width: 100%; 226 | } 227 | 228 | a:not(.btn) { 229 | position: relative; 230 | color: var(--color-100); 231 | text-transform: uppercase; 232 | 233 | &::after { 234 | content: ""; 235 | position: absolute; 236 | bottom: 0; 237 | left: 0; 238 | width: 100%; 239 | height: 2px; 240 | transition: transform .2s ease-in-out; 241 | 242 | z-index: -1; 243 | background-color: currentColor; 244 | transform: scaleX(0); 245 | transform-origin: 100% 50%; 246 | transition-timing-function: var(--ease-in-out-cubic); 247 | } 248 | 249 | &:is(:hover, :focus, .is-active) { 250 | color: #fff; 251 | 252 | &::after { 253 | transform: scaleX(1); 254 | transform-origin: 0 50%; 255 | } 256 | } 257 | 258 | } 259 | 260 | .btn { 261 | @media (--viewport-sm-down) { 262 | width: calc(50% - (var(--layout-space) / 2)); 263 | --padding-block: var(--layout-space-xxs); 264 | } 265 | } 266 | } 267 | 268 | 269 | .footer { 270 | text-align: center; 271 | 272 | .container > * { 273 | &:not(:first-child) { 274 | margin-block-start: var(--layout-space); 275 | } 276 | padding-block-start: var(--layout-space); 277 | border-block-start: 2px solid var(--color-500); 278 | } 279 | 280 | .group, .nav { 281 | align-items: center; 282 | justify-content: center; 283 | } 284 | 285 | .share-title { 286 | @media (--viewport-sm-down) { 287 | width: 100%; 288 | } 289 | } 290 | } 291 | 292 | .footer-nav { 293 | row-gap: var(--layout-space-xs); 294 | font-size: .875rem; 295 | } 296 | 297 | 298 | /* Page heading */ 299 | .page-intro { 300 | display: flex; 301 | gap: var(--layout-space-md); 302 | align-items: center; 303 | } 304 | 305 | .page-heading { 306 | flex-grow: 1; 307 | text-wrap: pretty; 308 | } 309 | 310 | .page-intro-img { 311 | width: 80px; 312 | height: auto; 313 | border-radius: 50%; 314 | } 315 | 316 | .columns-sm-2 { 317 | @media (--viewport-sm-up) { 318 | column-count: 2; 319 | column-gap: var(--layout-space-md); 320 | } 321 | 322 | > * { 323 | break-inside: avoid; 324 | text-wrap: pretty; 325 | } 326 | } 327 | 328 | /* Section heading */ 329 | .section-heading { 330 | display: flex; 331 | gap: var(--layout-space-sm); 332 | margin-block-start: var(--layout-space-xl); 333 | 334 | > *:first-child { 335 | flex-grow: 1; 336 | } 337 | } 338 | 339 | /* Form elements */ 340 | .select { 341 | padding: .25rem 1rem .25rem .5rem; 342 | background: #fff; 343 | border: 1px solid var(--color-500); 344 | } -------------------------------------------------------------------------------- /src/styles/site/demo.css: -------------------------------------------------------------------------------- 1 | /* Demo */ -------------------------------------------------------------------------------- /src/styles/site/layout.css: -------------------------------------------------------------------------------- 1 | /* Spacing of components */ 2 | .space, .fullwidth { 3 | --layout-space: var(--layout-space-md); 4 | } 5 | 6 | .space--zero { 7 | --layout-space: 0; 8 | } 9 | .space--xxs { 10 | --layout-space: var(--layout-space-xxs); 11 | } 12 | .space--xs { 13 | --layout-space: var(--layout-space-xs); 14 | } 15 | .space--sm { 16 | --layout-space: var(--layout-space-sm); 17 | } 18 | .space--md { 19 | --layout-space: var(--layout-space-md); 20 | } 21 | .space--lg { 22 | --layout-space: var(--layout-space-lg); 23 | } 24 | .space--xl { 25 | --layout-space: var(--layout-space-xl); 26 | } 27 | .space--xxl { 28 | --layout-space: var(--layout-space-xxl); 29 | } 30 | 31 | .space { 32 | padding: var(--layout-space); 33 | } 34 | 35 | .space--block { 36 | padding-inline: 0; 37 | } 38 | 39 | .space--inline { 40 | padding-block: 0; 41 | } 42 | 43 | 44 | 45 | /* Fullwidth */ 46 | .fullwidth { 47 | } 48 | 49 | .main, .footer { 50 | @media (--viewport-sm-up) { 51 | padding-inline: var(--layout-gutter-inline); 52 | } 53 | } 54 | 55 | .container { 56 | max-width: var(--layout-breakpoint-md); 57 | padding-inline: var(--layout-gutter-inline); 58 | margin-inline: auto; 59 | } 60 | 61 | .fullwidth > .container { 62 | padding-block: var(--layout-space); 63 | } 64 | 65 | 66 | /* Grid */ 67 | .row { 68 | display: flex; 69 | flex-wrap: wrap; 70 | row-gap: var(--layout-gutter-inline); 71 | margin-inline: calc(-1 * var(--layout-gutter-inline)); 72 | 73 | & > * { 74 | flex-shrink: 0; 75 | width: 100%; 76 | max-width: 100%; 77 | padding-inline: var(--layout-gutter-inline); 78 | } 79 | } 80 | 81 | @media (--viewport-md-up) { 82 | .col-md-4 { 83 | flex: 0 0 auto; 84 | width: 33.33333333%; 85 | } 86 | 87 | .col-md-8 { 88 | flex: 0 0 auto; 89 | width: 66.66666667%; 90 | } 91 | } 92 | 93 | @media (--viewport-lg-up) { 94 | .col-lg-4 { 95 | flex: 0 0 auto; 96 | width: 33.33333333%; 97 | } 98 | 99 | .col-lg-8 { 100 | flex: 0 0 auto; 101 | width: 66.66666667%; 102 | } 103 | } 104 | 105 | 106 | /* Flow */ 107 | .flow > * + * { 108 | margin-block-start: var(--flow-space, 1.5rem); 109 | } 110 | 111 | .flow { 112 | /* Large gap before headings and after h1 */ 113 | h1, h2, h3, h4, h1 + *, :is(.heading:has(h1)) + * { 114 | --flow-space: var(--layout-space-lg); 115 | } 116 | 117 | /* Medium gap if a heading follows a heading */ 118 | h1 + h2, h2 + h3, h3 + h4, .heading + :is(h2, h3, h4) { 119 | --flow-space: var(--layout-space-md); 120 | } 121 | 122 | /* Small gap directly after heading and inside heading wrapper */ 123 | :is(h2, h3, h4, .heading) + *, :is(h2, h3, h4, .heading) + p + *, &.heading > * + * { 124 | --flow-space: var(--layout-space-sm); 125 | } 126 | } 127 | 128 | 129 | 130 | /* Group */ 131 | .group, .nav { 132 | --layout-space: var(--layout-space-sm); 133 | 134 | display: flex; 135 | flex-wrap: wrap; 136 | gap: var(--layout-space); 137 | } 138 | 139 | /* .group--min { 140 | > * > *:first-child { 141 | &, & > *:first-child { 142 | min-width: 144px; 143 | text-align: center; 144 | } 145 | } 146 | } */ 147 | 148 | 149 | /* Demos */ 150 | .demos .container { 151 | --flow-space: var(--layout-space-lg); 152 | } 153 | -------------------------------------------------------------------------------- /src/styles/site/main.css: -------------------------------------------------------------------------------- 1 | @import "reset.css"; 2 | @import "variables.css"; 3 | @import "base.css"; 4 | @import "layout.css"; 5 | 6 | @import "components.css"; 7 | @import "demo.css"; 8 | 9 | 10 | /* Social image - 1200x630 */ 11 | /* body { 12 | scale: 1.5; 13 | transform-origin: top center; 14 | } 15 | 16 | .header { 17 | display: flex; 18 | border: none; 19 | 20 | .container { 21 | width: 100%; 22 | } 23 | } 24 | 25 | .header-nav { 26 | display: none; 27 | } 28 | 29 | .intro .container { 30 | display: flex; 31 | height: 340px; 32 | 33 | > *:not(.page-intro) { 34 | display: none; 35 | } 36 | } 37 | 38 | 39 | .page-heading { 40 | font-size: 3rem; 41 | } 42 | 43 | .page-intro-img { 44 | width: 160px; 45 | } */ 46 | 47 | 48 | /* Portfolio Image */ 49 | /* body { 50 | scale: 1.565; 51 | transform-origin: top center; 52 | } 53 | 54 | .header { 55 | display: none; 56 | } 57 | 58 | .progress-text p { 59 | display: none !important; 60 | } */ 61 | -------------------------------------------------------------------------------- /src/styles/site/reset.css: -------------------------------------------------------------------------------- 1 | /* https://www.joshwcomeau.com/css/custom-css-reset/, https://andy-bell.co.uk/a-more-modern-css-reset/ */ 2 | *, *::before, *::after { 3 | box-sizing: border-box; 4 | } 5 | * { 6 | margin: 0; 7 | } 8 | 9 | html { 10 | -moz-text-size-adjust: none; 11 | -webkit-text-size-adjust: none; 12 | text-size-adjust: none; 13 | } 14 | 15 | body { 16 | line-height: 1.5; 17 | -webkit-font-smoothing: antialiased; 18 | 19 | min-height: 100vh; 20 | } 21 | img, picture, video, canvas, svg { 22 | display: block; 23 | max-width: 100%; 24 | } 25 | input, button, textarea, select { 26 | font: inherit; 27 | } 28 | p, h1, h2, h3, h4, h5, h6 { 29 | overflow-wrap: break-word; 30 | } 31 | 32 | /* Opinionated */ 33 | h1, h2, h3, h4, 34 | button, input, label { 35 | line-height: 1.2; 36 | } 37 | 38 | h1, h2, h3, h4 { 39 | text-wrap: balance; 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/site/variables.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Code Pro'; 3 | src: url('../fonts/source-code-pro.woff2') format('woff2'); 4 | display: swap; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Work Sans'; 9 | src: url('../fonts/work-sans.woff2') format('woff2'); 10 | display: swap; 11 | } 12 | 13 | 14 | :root { 15 | /* Colours */ 16 | /* https://oklch-palette.vercel.app/#53.67,0.257,262.51,100, https://oklch.com/#53.67,0.257,262.51,100 */ 17 | --color-50: oklch(97.89% 0.01 267.36); 18 | --color-100: oklch(93.51% 0.031 263.52); 19 | --color-200: oklch(79.69% 0.102 262.19); 20 | --color-300: oklch(71% 0.151 262.38); 21 | --color-400: oklch(62.26% 0.203 262.51); 22 | --color-500: oklch(53.67% 0.257 262.51); 23 | --color-600: oklch(47.18% 0.226 262.46); 24 | --color-700: oklch(38.31% 0.185 262.52); 25 | --color-800: oklch(29.19% 0.141 262.52); 26 | --color-900: oklch(20.92% 0.101 262.51); 27 | 28 | --color-accent-500: oklch(77.31% 0.136 177.29); 29 | --color-accent-700: oklch(50.9% 0.094 177.27); 30 | 31 | /* Type */ 32 | --sans-serif-font-family: system-ui, Arial, sans-serif; 33 | --serif-font-family: 'Times New Roman', Times, serif; 34 | /* --body-font-family: 'Work Sans', var(--sans-serif-font-family); 35 | --heading-font-family: 'Source Code Pro', var(--sans-serif-font-family); */ 36 | --body-font-family: 'Work Sans', sans-serif; 37 | --heading-font-family: 'Source Code Pro', monospace; 38 | 39 | --text-color: var(--color-700); 40 | --link-color: var(--color-500); 41 | --link-color-hover: var(--color-700); 42 | 43 | /* Layout */ 44 | --layout-breakpoint-xs: 0; 45 | --layout-breakpoint-sm: 576px; 46 | --layout-breakpoint-md: 768px; 47 | --layout-breakpoint-lg: 992px; 48 | --layout-breakpoint-xl: 1200px; 49 | --layout-breakpoint-xxl: 1400px; 50 | 51 | --layout-gutter-inline: 16px; 52 | --layout-gutter-block: 0px; 53 | 54 | --layout-space-xxs: 4px; 55 | --layout-space-xs: 8px; 56 | --layout-space-sm: 16px; 57 | --layout-space-md: 32px; 58 | --layout-space-lg: 48px; 59 | --layout-space-xl: 64px; 60 | --layout-space-xxl: 80px; 61 | 62 | --ease-out-cubic: cubic-bezier(.215, .610, .355, 1); 63 | --ease-in-out-cubic: cubic-bezier(.65, .05, .36, 1); 64 | 65 | --bg-grid-color: rgba(238, 238, 238, .75); 66 | --bg-grid-line: 2px; 67 | --bg-grid-box: 48px; 68 | 69 | @media (--viewport-md-up) { 70 | --layout-gutter-inline: 24px; 71 | } 72 | } 73 | 74 | @supports not (background-color: oklch(0%, 0, 0)) { 75 | :root { 76 | /* --color-50: #f0f5ff; */ 77 | --color-50: #f5f8ff; 78 | /* --color-100: #c5d9ff; */ 79 | --color-100: #dfeaff; 80 | --color-200: #99bdff; 81 | --color-300: #6d9fff; 82 | --color-400: #3f7eff; 83 | --color-500: #0157ff; 84 | --color-600: #0048d7; 85 | --color-700: #0034a3; 86 | --color-800: #002170; 87 | --color-900: #001145; 88 | 89 | --color-accent-500: #28d1b4; 90 | --color-accent-700: #007765; 91 | } 92 | } 93 | 94 | /* Custom media queries */ 95 | @custom-media --viewport-xs-up (min-width: 480px); 96 | @custom-media --viewport-xs-down (max-width: 479px); 97 | @custom-media --viewport-sm-up (min-width: 576px); 98 | @custom-media --viewport-sm-down (max-width: 575px); 99 | @custom-media --viewport-md-up (min-width: 768px); 100 | @custom-media --viewport-md-down (max-width: 767px); 101 | @custom-media --viewport-lg-up (min-width: 992px); 102 | @custom-media --viewport-lg-down (max-width: 991px); 103 | @custom-media --viewport-xl-up (min-width: 1200px); 104 | @custom-media --viewport-xl-down (max-width: 1199px); 105 | @custom-media --viewport-xxl-up (min-width: 1400px); 106 | @custom-media --viewport-xxl-down (max-width: 1399px); 107 | --------------------------------------------------------------------------------