├── favicon.ico ├── img ├── og-img.png ├── intro-screenshot.png └── favicon │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-70x70.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── android-chrome-192x192-maskable.png │ ├── android-chrome-512x512-maskable.png │ ├── favicon.svg │ └── safari-pinned-tab.svg ├── .vscode └── settings.json ├── browserconfig.xml ├── css ├── _fonts.scss ├── _themes.scss ├── index.min.css ├── index.css └── index.scss ├── README.md ├── todo.txt ├── manifest.webmanifest ├── sw.js └── js └── index.js /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/og-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/og-img.png -------------------------------------------------------------------------------- /img/intro-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/intro-screenshot.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "easysass.excludeRegex": "^_", 3 | "easysass.compileAfterSave": true 4 | } -------------------------------------------------------------------------------- /img/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /img/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /img/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /img/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /img/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /img/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /img/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /img/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /img/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /img/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /img/favicon/android-chrome-192x192-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/android-chrome-192x192-maskable.png -------------------------------------------------------------------------------- /img/favicon/android-chrome-512x512-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theothertored/kode-guide/HEAD/img/favicon/android-chrome-512x512-maskable.png -------------------------------------------------------------------------------- /browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #101010 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /css/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inria+Sans:ital@0;1&family=Roboto+Mono&family=Source+Serif+Pro:wght@700&display=swap'); 2 | 3 | $font-family-heading: 'Source Serif Pro', serif; 4 | $font-family-body: 'Inria Sans', sans-serif; 5 | $font-family-mono: 'Roboto Mono', monospace; 6 | $font-size-base: 1rem; 7 | $font-size-code: 0.85em; 8 | $font-size-small: 0.75rem; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A guide to Kode basics 2 | 3 | (by Tored) 4 | 5 | ### [\[ Click here to read the guide \]](https://theothertored.github.io/kode-guide) 6 | 7 | This is an expanded and improved version of the old Kode guide I wrote in Google Docs. 8 | It comes with major rewrites, more sections and examples, a light & dark mode, and an interactive table of contents. 9 | 10 | It also works offline and can be installed like an app on your device. 11 | 12 | --- 13 | 14 | Shoutouts to Bunn-E, Grabster and especially robin for motivating me to work on this overhaul. -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | DONE: 2 | - write some kind of readme 3 | - section about basic lv() usage 4 | - add a service worker + PWA capability 5 | - favicon 6 | - "pick up where you left off" prompt 7 | - open/close left drawer with a swipe (I already tried opening it like that without thinking and it went back to my new tab screen) 8 | - expand the regex section a bit to maybe convince people to learn it 9 | - hamburger to arrow animation for fab 10 | - add some text explaining section links and copying examples 11 | - double tap to copy example 12 | - refactor the absolute bowl of spaghetti I have created (wasn't that bad lol) 13 | - smaller/larger list entries for mouse/touchscreen 14 | - mode switcher in left drawer so the mode can be changed from any position on the page 15 | - fix light mode heavy border being the same color as the light border -------------------------------------------------------------------------------- /manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "id": "kode-guide", 3 | "name": "Kode Guide", 4 | "short_name": "Kode Guide", 5 | "description": "An in-depth guide to Kustom formula basics (mainly KLWP and KWGT).", 6 | "icons": [ 7 | { 8 | "src": "img/favicon/android-chrome-192x192.png", 9 | "sizes": "192x192", 10 | "type": "image/png" 11 | }, 12 | { 13 | "src": "img/favicon/android-chrome-512x512.png", 14 | "sizes": "512x512", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "img/favicon/android-chrome-192x192-maskable.png", 19 | "sizes": "192x192", 20 | "type": "image/png", 21 | "purpose": "maskable" 22 | }, 23 | { 24 | "src": "img/favicon/android-chrome-512x512-maskable.png", 25 | "sizes": "512x512", 26 | "type": "image/png", 27 | "purpose": "maskable" 28 | } 29 | ], 30 | "start_url": "/kode-guide/", 31 | "theme_color": "#101010", 32 | "background_color": "#101010", 33 | "display": "standalone" 34 | } 35 | -------------------------------------------------------------------------------- /sw.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const cacheName = 'kode-guide'; 4 | 5 | self.addEventListener('install', ev => { 6 | 7 | ev.waitUntil( 8 | caches.open(cacheName).then(cache => { 9 | return cache.addAll([ 10 | "", 11 | "js/index.js", 12 | "css/index.css", 13 | ]); 14 | }) 15 | ); 16 | 17 | }); 18 | 19 | self.addEventListener('fetch', ev => { 20 | 21 | if (ev.request.method === 'GET') { 22 | 23 | ev.respondWith( 24 | caches.open(cacheName).then(cache => { 25 | return fetch(ev.request) 26 | .then(resp => { 27 | 28 | let reqUrl = ev.request.url.toString(); 29 | 30 | if (reqUrl.includes('v=')) { 31 | cache.put(getReqUrlWithoutVersion(reqUrl), resp.clone()); 32 | } else { 33 | cache.put(ev.request, resp.clone()); 34 | } 35 | 36 | return resp; 37 | 38 | }) 39 | .catch(err => { 40 | 41 | let reqUrl = ev.request.url.toString(); 42 | 43 | if (reqUrl.includes('v=')) { 44 | return cache.match(getReqUrlWithoutVersion(reqUrl)); 45 | } else { 46 | return cache.match(ev.request); 47 | } 48 | 49 | }); 50 | }) 51 | ); 52 | 53 | } 54 | 55 | }); 56 | 57 | function getReqUrlWithoutVersion(reqUrl){ 58 | return reqUrl.replace(/[?&]v=.*?([&#]|$)/g, ''); 59 | } -------------------------------------------------------------------------------- /img/favicon/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 35 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /img/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 19 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /css/_themes.scss: -------------------------------------------------------------------------------- 1 | // variables shared between themes 2 | $c-dark-bg: #101010; 3 | $c-dark-text: rgba(#fff, 0.9); 4 | $c-light-bg: #ffffff; 5 | $c-light-text: rgba(#000, 0.9); 6 | $c-accent: #ff00ff; 7 | 8 | // dark theme 9 | @media not print { 10 | :root.dark { 11 | --c-bg: $c-dark-bg; 12 | --c-bg-hover: rgba(#fff, 0.1); 13 | --c-bg-copied: $c-light-bg; 14 | --c-text-on-bg-pri: $c-dark-text; 15 | --c-text-on-bg-sec: rgba(#fff, 0.8); 16 | --c-text-on-bg-tet: rgba(#fff, 0.6); 17 | --c-text-on-bg-irr: rgba(#fff, 0.4); 18 | --c-text-link: #74c9ea; 19 | --c-text-error: #ed9090; 20 | --c-text-success: #84c666; 21 | --c-border-light: rgba(#fff, 0.2); 22 | --c-border-heavy: rgba(#fff, 0.5); 23 | --c-code-bg: rgba(#000, 0.01); 24 | --c-hl-1: #eacf7f; 25 | --c-hl-2: #c88afe; 26 | --c-hl-3: #a6ddfd; 27 | --c-hl-4: #c2e4a1; 28 | --c-hl-5: #f794d1; 29 | --c-hl-6: #ffb872; 30 | --c-hl-1-strong: #ffcc35; 31 | --c-hl-2-strong: #d19dff; 32 | --c-hl-3-strong: #74ccff; 33 | --c-hl-4-strong: #c4ff8a; 34 | --c-hl-5-strong: #f794d1; 35 | --c-hl-6-strong: #ffb872; 36 | --c-hl-gray: #f5f5f5; 37 | --c-hl-gray-strong: #d2d2d2; 38 | --c-hl-before: #ffac58; 39 | --c-hl-after: #ffac58; 40 | --c-hl-error: #ea9999; 41 | --c-hl-true: #9ecd88; 42 | --c-hl-false: #d98989; 43 | --c-hl-true-strong: #9dff6f; 44 | --c-hl-false-strong: #ff6f6f; 45 | color-scheme: dark; 46 | } 47 | } 48 | 49 | // light theme 50 | :root { 51 | --c-bg: $c-light-bg; 52 | --c-bg-hover: rgba(#000, 0.05); 53 | --c-bg-copied: $c-dark-bg; 54 | --c-text-on-bg-pri: $c-light-text; 55 | --c-text-on-bg-sec: rgba(#000, 0.75); 56 | --c-text-on-bg-tet: rgba(#000, 0.5); 57 | --c-text-on-bg-irr: rgba(#000, 0.35); 58 | --c-text-link: #0e7398; 59 | --c-text-error: #e41717; 60 | --c-text-success: #52a92a; 61 | --c-border-light: rgba(#000, 0.1); 62 | --c-border-heavy: rgba(#000, 0.25); 63 | --c-code-bg: rgba(#000, 0.01); 64 | --c-hl-1: #ffe599; 65 | --c-hl-2: #f0dfff; 66 | --c-hl-3: #d1eeff; 67 | --c-hl-4: #d9ead3; 68 | --c-hl-5: #f4cccc; 69 | --c-hl-6: #f9cb9c; 70 | --c-hl-1-strong: #f7ca42; 71 | --c-hl-2-strong: #d8bcfd; 72 | --c-hl-3-strong: #9fdcff; 73 | --c-hl-4-strong: #a2f484; 74 | --c-hl-5-strong: #ff7979; 75 | --c-hl-6-strong: #fba44e; 76 | --c-hl-gray: #f5f5f5; 77 | --c-hl-gray-strong: #d2d2d2; 78 | --c-hl-before: #ffd9b3; 79 | --c-hl-after: #ffd9b3; 80 | --c-hl-error: #ea9999; 81 | --c-hl-true: #ecfccf; 82 | --c-hl-false: #ffc7c7; 83 | --c-hl-true-strong: #ccff6d; 84 | --c-hl-false-strong: #ff9797; 85 | color-scheme: light; 86 | } 87 | 88 | // variables so it's easier to change things later just in case 89 | $c-bg: var(--c-bg); 90 | $c-bg-hover: var(--c-bg-hover); 91 | $c-bg-copied: var(--c-bg-copied); 92 | $c-text-on-bg-pri: var(--c-text-on-bg-pri); 93 | $c-text-on-bg-sec: var(--c-text-on-bg-sec); 94 | $c-text-on-bg-tet: var(--c-text-on-bg-tet); 95 | $c-text-on-bg-irr: var(--c-text-on-bg-irr); 96 | $c-text-link: var(--c-text-link); 97 | $c-text-error: var(--c-text-error); 98 | $c-text-success: var(--c-text-success); 99 | $c-border-light: var(--c-border-light); 100 | $c-border-heavy: var(--c-border-heavy); 101 | $c-code-bg: var(--c-code-bg); 102 | -------------------------------------------------------------------------------- /css/index.min.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inria+Sans:ital@0;1&family=Roboto+Mono&family=Source+Serif+Pro:wght@700&display=swap");@media not print{:root.dark{--c-bg:#101010;--c-bg-hover:rgba(255,255,255,0.1);--c-bg-copied:#fff;--c-text-on-bg-pri:rgba(255,255,255,0.9);--c-text-on-bg-sec:rgba(255,255,255,0.8);--c-text-on-bg-tet:rgba(255,255,255,0.6);--c-text-on-bg-irr:rgba(255,255,255,0.4);--c-text-link:#74c9ea;--c-text-error:#ed9090;--c-text-success:#84c666;--c-border-light:rgba(255,255,255,0.2);--c-border-heavy:rgba(255,255,255,0.5);--c-code-bg:rgba(0,0,0,0.01);--c-hl-1:#eacf7f;--c-hl-2:#c88afe;--c-hl-3:#a6ddfd;--c-hl-4:#c2e4a1;--c-hl-5:#f794d1;--c-hl-6:#ffb872;--c-hl-1-strong:#ffcc35;--c-hl-2-strong:#d19dff;--c-hl-3-strong:#74ccff;--c-hl-4-strong:#c4ff8a;--c-hl-5-strong:#f794d1;--c-hl-6-strong:#ffb872;--c-hl-gray:#f5f5f5;--c-hl-gray-strong:#d2d2d2;--c-hl-before:#ffac58;--c-hl-after:#ffac58;--c-hl-error:#ea9999;--c-hl-true:#9ecd88;--c-hl-false:#d98989;--c-hl-true-strong:#9dff6f;--c-hl-false-strong:#ff6f6f;color-scheme:dark}}:root{--c-bg:#fff;--c-bg-hover:rgba(0,0,0,0.05);--c-bg-copied:#101010;--c-text-on-bg-pri:rgba(0,0,0,0.9);--c-text-on-bg-sec:rgba(0,0,0,0.75);--c-text-on-bg-tet:rgba(0,0,0,0.5);--c-text-on-bg-irr:rgba(0,0,0,0.35);--c-text-link:#0e7398;--c-text-error:#e41717;--c-text-success:#52a92a;--c-border-light:rgba(0,0,0,0.1);--c-border-heavy:rgba(0,0,0,0.25);--c-code-bg:rgba(0,0,0,0.01);--c-hl-1:#ffe599;--c-hl-2:#f0dfff;--c-hl-3:#d1eeff;--c-hl-4:#d9ead3;--c-hl-5:#f4cccc;--c-hl-6:#f9cb9c;--c-hl-1-strong:#f7ca42;--c-hl-2-strong:#d8bcfd;--c-hl-3-strong:#9fdcff;--c-hl-4-strong:#a2f484;--c-hl-5-strong:#ff7979;--c-hl-6-strong:#fba44e;--c-hl-gray:#f5f5f5;--c-hl-gray-strong:#d2d2d2;--c-hl-before:#ffd9b3;--c-hl-after:#ffd9b3;--c-hl-error:#ea9999;--c-hl-true:#ecfccf;--c-hl-false:#ffc7c7;--c-hl-true-strong:#ccff6d;--c-hl-false-strong:#ff9797;color-scheme:light}@media (hover: hover){.toc>main>.toc-entry,.toc-fab:before,.scroll-restore-message>.btn-dismiss,.btn{transition:background-color cubic-bezier(0.4, 0, 0.2, 1) 0.15s}.toc>main>.toc-entry:hover,.toc-fab:hover:before,.scroll-restore-message>.btn-dismiss:hover,.btn:hover{background-color:var(--c-bg-hover)}}*{box-sizing:border-box}html,body{width:100%;height:100%;padding:0;margin:0}.guide{width:100%;max-width:50rem;padding:0 1rem;padding-bottom:15rem}h1{font-size:2.5rem;margin:2.5rem 0;margin-top:6rem}h1>.small{margin-top:1rem}h2{font-size:2rem;margin:0;padding-top:2rem}h3{font-size:1.5rem;margin:0;padding-top:2.5rem}h4{font-size:1.25rem;margin:0;padding-top:2rem}h2+h3{padding-top:1.5rem}h1+p,h1+.snippet,h2+p,h2+.snippet,h3+p,h3+.snippet,h4+p,h4+.snippet{margin-top:2rem}p,blockquote{margin-top:1.5rem;line-height:140%}.snippet{margin-top:0.5rem}ul,ol{line-height:140%}ul>li+li,ol>li+li{margin-top:0.5rem}.toc{position:fixed;top:0;left:0;bottom:0;z-index:2;width:24rem;max-width:calc(100vw - 3rem)}.toc.in{transform:translateX(0)}.toc:not(.in){transform:translateX(-100%)}.toc-fab{position:fixed;top:1rem;right:-4rem;z-index:2}.toc{display:flex;flex-direction:column;background-color:var(--c-bg);vertical-align:top;border-right:1px solid var(--c-border-light);text-align:left}.toc.transition{transition:transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s}.toc>header{display:flex;align-items:center;width:100%;border-bottom:1px solid var(--c-border-light);padding:1rem;font-family:"Source Serif Pro",serif;font-size:1.5rem}.toc>main{padding:0.5rem;flex:1;overflow-y:auto;overscroll-behavior-y:contain}.toc>main>.toc-entry{display:block;text-decoration:none;color:var(--c-text-on-bg-sec);padding:0.5rem;-webkit-tap-highlight-color:transparent;border-radius:0.25rem}.toc>main>.toc-entry>.number{color:var(--c-text-on-bg-irr)}.toc>main>.toc-1{padding-left:1rem}.toc>main>.toc-2{padding-left:2rem}.toc>footer{padding:1rem;padding-top:0.5rem;border-top:1px solid var(--c-border-light)}.toc>footer>.theme-switcher{margin-top:0}.toc-fab{width:3rem;height:3rem;font-size:1rem;border-radius:1.5rem;border:1px solid var(--c-border-light);color:var(--c-text-on-bg-pri);background-color:var(--c-bg);-webkit-tap-highlight-color:transparent;outline:none;cursor:pointer;overflow:hidden;transition:transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s}.toc-fab:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0}.toc-fab>.top,.toc-fab>.mid,.toc-fab>.bot{position:absolute;top:50%;left:50%;width:1rem;height:0.1rem;background-color:var(--c-text-on-bg-pri)}.toc-fab>.top{transform:translate(-50%, -50%) translateY(-.33333rem)}.toc-fab>.mid{transform:translate(-50%, -50%)}.toc-fab>.bot{transform:translate(-50%, -50%) translateY(.33333rem)}.toc.transition .toc-fab>.top,.toc.transition .toc-fab>.mid,.toc.transition .toc-fab>.bot{transition:transform cubic-bezier(0.4, 0, 0.2, 1) 0.15s}.toc.in .toc-fab>.top{transform:translate(-25%, -50%) translateY(-.33333rem) translate(-33%, .18182rem) rotate(-45deg) scaleX(0.5)}.toc.in .toc-fab>.mid{transform:translate(-50%, -50%) scaleX(0)}.toc.in .toc-fab>.bot{transform:translate(-25%, -50%) translateY(.33333rem) translate(-33%, -.18182rem) rotate(45deg) scaleX(0.5)}.toc-scrim{position:fixed;top:0;left:0;bottom:0;right:0;z-index:1;background-color:var(--c-bg);opacity:0;pointer-events:none;display:none;-webkit-tap-highlight-color:transparent}.toc.in ~ .toc-scrim{opacity:0.9;pointer-events:auto}body{font-family:"Inria Sans",sans-serif;font-size:1rem;color:var(--c-text-on-bg-sec);background-color:var(--c-bg);line-height:1.25}.guide{text-align:justify}h1,h2,h3,h4,h5,h6{font-family:"Source Serif Pro",serif;font-weight:700;color:var(--c-text-on-bg-pri);text-align:left}h1>a,h2>a,h3>a,h4>a,h5>a,h6>a{color:inherit;text-decoration:none}h1>a:hover,h2>a:hover,h3>a:hover,h4>a:hover,h5>a:hover,h6>a:hover{text-decoration:underline}.small{font-size:.75rem;font-weight:normal;color:var(--c-text-on-bg-tet);font-family:"Inria Sans",sans-serif}.guide>footer{margin-top:4rem;border-top:1px dashed var(--c-border-light)}.image-container{width:100%}.image-container img{display:block;max-width:100%;padding:1rem}.image-container label{margin-top:-0.5rem;display:block;text-align:center;font-size:.75rem;color:var(--c-text-on-bg-tet);font-style:italic}a{color:var(--c-text-link)}blockquote{font-style:italic;border-left:2px solid var(--c-border-light);margin-left:0;padding-left:1rem;padding-top:0.5rem;padding-bottom:0.5rem}.guide{counter-reset:h2 h3 h4}h2{counter-increment:h2;counter-reset:h3}h2:before{content:counter(h2) ". "}h3{counter-increment:h3;counter-reset:h4}h3:before{content:counter(h2) "." counter(h3) ". "}h4{counter-increment:h4}h4:before{content:counter(h2) "." counter(h3) "." counter(h4) ". "}code{display:inline-block;font-family:"Roboto Mono",monospace;font-size:.85em;border:1px solid var(--c-border-light);border-radius:0.25em;padding:0 0.35em;margin:0 0.2em;word-break:break-all;text-align:left;font-style:normal}code>pre{margin:0;white-space:pre-wrap}:root:not(.dark) .hl{position:relative;z-index:-1;line-height:1;white-space:pre-wrap;padding:0.1rem;margin:-0.1rem}:root:not(.dark) .hl+.hl{margin-left:0.1rem}:root:not(.dark) .hl-1{background-color:var(--c-hl-1)}:root:not(.dark) .hl-1.strong{background-color:var(--c-hl-1-strong)}:root:not(.dark) .hl-2{background-color:var(--c-hl-2)}:root:not(.dark) .hl-2.strong{background-color:var(--c-hl-2-strong)}:root:not(.dark) .hl-3{background-color:var(--c-hl-3)}:root:not(.dark) .hl-3.strong{background-color:var(--c-hl-3-strong)}:root:not(.dark) .hl-4{background-color:var(--c-hl-4)}:root:not(.dark) .hl-4.strong{background-color:var(--c-hl-4-strong)}:root:not(.dark) .hl-5{background-color:var(--c-hl-5)}:root:not(.dark) .hl-5.strong{background-color:var(--c-hl-5-strong)}:root:not(.dark) .hl-6{background-color:var(--c-hl-6)}:root:not(.dark) .hl-6.strong{background-color:var(--c-hl-6-strong)}:root:not(.dark) .hl-before{background-color:var(--c-hl-before)}:root:not(.dark) .hl-after{background-color:var(--c-hl-after)}:root:not(.dark) .hl-error{background-color:var(--c-hl-error)}:root:not(.dark) .hl-true{background-color:var(--c-hl-true)}:root:not(.dark) .hl-true.strong{background-color:var(--c-hl-true-strong)}:root:not(.dark) .hl-false{background-color:var(--c-hl-false)}:root:not(.dark) .hl-false.strong{background-color:var(--c-hl-false-strong)}:root:not(.dark) .hl-gray{background-color:var(--c-hl-gray)}:root:not(.dark) .hl-gray.strong{background-color:var(--c-hl-gray-strong)}:root.dark .hl-1{color:var(--c-hl-1)}:root.dark .hl-1.strong{color:var(--c-hl-1-strong)}:root.dark .hl-2{color:var(--c-hl-2)}:root.dark .hl-2.strong{color:var(--c-hl-2-strong)}:root.dark .hl-3{color:var(--c-hl-3)}:root.dark .hl-3.strong{color:var(--c-hl-3-strong)}:root.dark .hl-4{color:var(--c-hl-4)}:root.dark .hl-4.strong{color:var(--c-hl-4-strong)}:root.dark .hl-5{color:var(--c-hl-5)}:root.dark .hl-5.strong{color:var(--c-hl-5-strong)}:root.dark .hl-6{color:var(--c-hl-6)}:root.dark .hl-6.strong{color:var(--c-hl-6-strong)}:root.dark .hl-before{color:var(--c-hl-before)}:root.dark .hl-after{color:var(--c-hl-after)}:root.dark .hl-error{color:var(--c-hl-error)}:root.dark .hl-true{color:var(--c-hl-true)}:root.dark .hl-true.strong{color:var(--c-hl-true-strong)}:root.dark .hl-false{color:var(--c-hl-false)}:root.dark .hl-false.strong{color:var(--c-hl-false-strong)}:root.dark .hl-gray{color:var(--c-hl-gray)}:root.dark .hl-gray.strong{color:var(--c-hl-gray-strong)}:root.dark .hl.strong{font-weight:bold}.snippet{margin-top:1rem}.snippet label{display:block;position:sticky;left:0;font-size:.75rem;color:var(--c-text-on-bg-tet);line-height:1;padding:0.4rem 0.5rem}.snippet label>.error,.snippet label>.success{font-style:italic}.snippet label>.error:before,.snippet label>.success:before{content:"-";margin:0 0.25rem;color:var(--c-text-on-bg-tet)}.snippet label>.error{color:var(--c-text-error)}.snippet label>.success{color:var(--c-text-success)}.snippet label.output:before{content:"Output"}.snippet>code,.snippet .step>code{display:block;border:1px solid var(--c-border-light);padding:0.5rem 0.75rem;line-height:1.5;margin:0;overflow:hidden;position:relative}.snippet>code:empty,.snippet .step>code:empty{user-select:none}.snippet>code:empty:before,.snippet .step>code:empty:before{content:" ";white-space:pre}.snippet>code+code,.snippet .step>code+code{margin-top:0.2rem}.snippet>code+label,.snippet .step>code+label{margin-top:0.5rem}.snippet>code.copied:after,.snippet .step>code.copied:after{content:"";background-color:var(--c-bg-copied);position:absolute;top:50%;left:50%;z-index:1;width:50%;height:1000%;transform-origin:center;animation:0.5s 1 forwards copied;animation-timing-function:cubic-bezier(0, 0, 0.2, 1)}@keyframes copied{0%{transform:translateX(-50%) translateX(Min(25rem, 50vw)) translateX(80%) translateY(-50%) translateX(-50%) rotate(20deg)}100%{transform:translateX(-50%) translateX(Max(-25rem, -50vw)) translateX(-80%) translateY(-50%) translateX(-50%) rotate(20deg)}}.snippet>.step .arrow{display:flex;align-items:center;justify-content:start;padding:0 0.5rem;font-size:1.4rem;margin-top:-0.25em;margin-bottom:-0.25em;line-height:1}.snippet>.step .arrow::before{content:"↓";color:var(--c-text-on-bg-tet)}.snippet>.step+label{margin-top:0.5rem}.snippet>.note{font-size:.75rem;color:var(--c-text-on-bg-tet);margin-top:0.5rem;font-style:italic;padding:0 0.5rem}.snippet>.note+label{margin-top:0.5rem}.snippet+.snippet{margin-top:1.5rem}.table-container{width:calc(100% + 16px);padding:8px;margin:0 -8px;overflow-x:auto}table{border-spacing:0;border-collapse:separate;border-radius:0.25rem;border:1px solid var(--c-border-light);text-align:left}table thead>tr:last-child>th{border-bottom:1px solid var(--c-border-heavy)}table tr:first-child th,table tr:first-child td{border-top:none}table th,table td{padding:0.5rem 0.75rem;border-top:1px solid var(--c-border-light);border-left:1px solid var(--c-border-light)}table th:first-child,table td:first-child{border-left:none}table code{white-space:nowrap}#math-symbol-table td:first-child,#math-symbol-table th:first-child{text-align:center}#math-symbol-table td:nth-child(2),#math-symbol-table th:nth-child(2){width:100%}#string-mode-table td:nth-child(1),#string-mode-table th:nth-child(1){text-align:center}#string-mode-table td:nth-child(2),#string-mode-table th:nth-child(2){width:100%}#comparison-operator-table td:nth-child(1),#comparison-operator-table th:nth-child(1){text-align:center}#comparison-operator-table td:nth-child(2),#comparison-operator-table th:nth-child(2){width:100%}:root.light #comparison-operator-table td:nth-child(3),:root.light #comparison-operator-table th:nth-child(3){background-color:var(--c-hl-true)}:root.light #comparison-operator-table td:nth-child(4),:root.light #comparison-operator-table th:nth-child(4){background-color:var(--c-hl-false)}:root:not(.light) #comparison-operator-table td:nth-child(3),:root:not(.light) #comparison-operator-table th:nth-child(3){color:var(--c-hl-true)}:root:not(.light) #comparison-operator-table td:nth-child(4),:root:not(.light) #comparison-operator-table th:nth-child(4){color:var(--c-hl-false)}#logical-operator-table td,#logical-operator-table th{width:80px;text-align:center}#logical-operator-table td:nth-child(3),#logical-operator-table td:nth-child(4),#logical-operator-table th:nth-child(3),#logical-operator-table th:nth-child(4){border-left:1px solid var(--c-border-heavy)}#tf-token-table tr:nth-child(4) td{border-top:1px solid var(--c-border-heavy)}#tf-token-table td:nth-child(1),#tf-token-table th:nth-child(1){text-align:center}#tf-token-table td:nth-child(2),#tf-token-table th:nth-child(2){width:100%}.theme-switcher>label{display:block;font-size:.75rem;color:var(--c-text-on-bg-tet)}.theme-switcher>label+.buttons{margin-top:0.5rem}.theme-switcher>.buttons{display:flex;flex-direction:row;gap:0.5rem;margin-top:1rem;-webkit-tap-highlight-color:transparent}.theme-switcher>.buttons>label{flex:1;padding:0.5rem;text-align:center;position:relative;transition:background-color cubic-bezier(0.4, 0, 0.2, 1) 0.1s,transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s;cursor:pointer}.theme-switcher>.buttons>label>input{display:none}.theme-switcher>.buttons>label>span:before{content:"";position:absolute;top:0;left:0;bottom:0;right:0;border-radius:0.25rem;border:1px solid var(--c-border-light);transition:border cubic-bezier(0.87, 1.92, 0.48, 0.75) 0.15s}@media (hover: hover){.theme-switcher>.buttons>label:hover{background-color:var(--c-bg-hover)}}.theme-switcher>.buttons>label:active{transition-duration:0s;transform:scale(0.96)}.theme-switcher>.buttons>label>input:checked+span:before{border:0.15rem solid #f0f}.theme-switch-overlay{position:fixed;top:0;left:0;width:100vw;bottom:0;z-index:10;transform:translateY(-100%);opacity:1;transition:transform cubic-bezier(0.4, 0, 1, 1) 0.25s}.theme-switch-overlay.dark{background-color:#101010}.theme-switch-overlay.light{background-color:#fff}.theme-switch-overlay.in{transform:translateY(0)}.theme-switch-overlay.out{transform:translateY(100%);transition-timing-function:cubic-bezier(0, 0, 0.2, 1);pointer-events:none}.scroll-restore-message{position:fixed;bottom:1rem;left:1rem;right:1rem;max-width:50rem;margin:auto;display:flex;flex-direction:row;justify-content:space-between;flex-wrap:wrap;align-items:center;border:1px solid var(--c-border-heavy);background-color:var(--c-bg);border-radius:0.25rem;transform:translateY(100%) translateY(1rem);transition:transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s;-webkit-tap-highlight-color:transparent;cursor:pointer}.scroll-restore-message>span{display:inline-block;padding:1rem}.scroll-restore-message>.btn-dismiss{background:none;border:none;outline:none;margin-left:auto;font-family:"Inria Sans",sans-serif;font-size:1rem;color:var(--c-text-on-bg-tet);-webkit-tap-highlight-color:transparent;padding:1rem;cursor:pointer}.scroll-restore-message.in{transform:translateY(0)}.btn{background:none;border:1px solid var(--c-border-heavy);border-radius:0.25rem;outline:none;font-family:"Inria Sans",sans-serif;font-size:1rem;color:var(--c-text-on-bg-sec);-webkit-tap-highlight-color:transparent;padding:0.5rem 1rem;cursor:pointer}.nowrap{white-space:nowrap}.no-display{display:none}.text-right{text-align:right}.text-center{text-align:center}@media screen and (min-width: 100rem){.guide{margin:auto}}@media screen and (min-width: 79rem) and (max-width: 100rem){.toc.transition ~ .guide{transition:transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s}.guide{transform:translateX(50vw) translateX(-50%)}.scroll-restore-message{margin:0;left:0;transform:translateX(50vw) translateX(-50%) translateY(100%) translateY(1rem)}.scroll-restore-message.in{transform:translateX(50vw) translateX(-50%)}.toc.in ~ .guide,.toc.in ~ .scroll-restore-message.in{transform:translateX(29rem)}.toc.in ~ .scroll-restore-message{transform:translateX(29rem) translateY(100%) translateY(1rem)}}@media screen and (max-width: 79rem){.guide{margin:auto}.toc-scrim{display:block !important;transition:opacity cubic-bezier(0.4, 0, 1, 1) 0.2s}}@media screen and (max-width: 60rem){.toc-fab.scroll-out{transform:translateY(-5rem)}.toc.in .toc-fab.scroll-out{transform:translateX(0)}}@media screen and (max-width: 29rem){.toc-fab{right:-1.5rem;top:0.5rem}.toc:not(.in) .toc-fab.scroll-out{transform:translate(2.5rem, -5rem)}.toc:not(.in) .toc-fab:not(.scroll-out){transform:translateX(2.5rem)}}@media (pointer: coarse){.toc main>.toc-entry{padding-top:.75rem;padding-bottom:.75rem}.theme-switcher>.buttons>label{padding:.75rem !important}.btn{padding:.75rem 1.5rem}}@media print{.toc,.toc-fab,.scroll-restore-message,.toc-scrim,.theme-switch-overlay{display:none}h1,h2,h3,h4,h5,h6{page-break-after:avoid}} 2 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | 2 | let tocEl, guideEl; 3 | let isTOCOpen; 4 | let matchMedia, currentTheme; 5 | 6 | const mobileThreshold = rem2px(24 + 5 + 56); 7 | 8 | function rem2px(rem) 9 | { 10 | return rem * parseFloat(getComputedStyle(document.documentElement).fontSize); 11 | } 12 | 13 | // initialize before the page loads 14 | initThemeSwitching(); 15 | 16 | document.addEventListener('DOMContentLoaded', ev => 17 | { 18 | 19 | // make sure elements used by more than one function are initialized first 20 | guideEl = document.querySelector('.guide'); 21 | tocEl = document.querySelector('.toc'); 22 | 23 | initPWA(); 24 | initExampleNumbers(); 25 | initTOCAndHeaders(); 26 | initReactingToResize(); 27 | initDoubleTapToCopy(); 28 | initSwipeInteractionsForToc(); 29 | initScrollRestore(); 30 | 31 | }); 32 | 33 | function initPWA() 34 | { 35 | 36 | let prompt; 37 | 38 | if ('serviceWorker' in navigator) 39 | { 40 | navigator.serviceWorker.register('/kode-guide/sw.js', { scope: '/kode-guide/' }).then(reg => 41 | { 42 | 43 | let swCachingP = document.getElementById('sw-caching-available'); 44 | swCachingP.classList.remove('no-display'); 45 | 46 | }); 47 | 48 | } 49 | 50 | window.addEventListener('beforeinstallprompt', ev => 51 | { 52 | 53 | prompt = ev; 54 | let promptP = document.getElementById('pwa-install-prompt'); 55 | let promptBtnP = document.getElementById('pwa-install-button'); 56 | let promptBtn = promptBtnP.querySelector('button'); 57 | 58 | promptP.classList.remove('no-display'); 59 | promptBtnP.classList.remove('no-display'); 60 | 61 | promptBtn.addEventListener('click', ev => 62 | { 63 | prompt.prompt(); 64 | }); 65 | 66 | }); 67 | 68 | } 69 | 70 | function initReactingToResize() 71 | { 72 | 73 | let prevWindowWidth = window.innerWidth; 74 | 75 | // init auto-hiding TOC on window resize 76 | window.addEventListener('resize', ev => 77 | { 78 | 79 | // expand/collapse toc if we went from mobile to desktop or vice versa 80 | if (window.innerWidth < mobileThreshold != prevWindowWidth < mobileThreshold) 81 | { 82 | setTOCOpen(window.innerWidth >= mobileThreshold); 83 | } 84 | 85 | // save window width for next resize event 86 | prevWindowWidth = window.innerWidth; 87 | 88 | }); 89 | 90 | } 91 | 92 | function initExampleNumbers() 93 | { 94 | 95 | // ex. 1 and ex.1.1 counters 96 | let ex = 0, subex = 0; 97 | 98 | // go through each example number element 99 | document.querySelectorAll('.snippet .ex-no').forEach(el => 100 | { 101 | 102 | if (el.classList.contains('subex')) 103 | { 104 | 105 | // subexample 106 | 107 | if (el.classList.contains('first')) 108 | { 109 | // fisrt subexample should increment the main example counter and reset the subexample counter 110 | ex++; 111 | subex = 0; 112 | } 113 | 114 | // increment subexample counter and set the text 115 | subex++; 116 | el.prepend(`ex. ${ex}.${subex} `); 117 | 118 | } else 119 | { 120 | 121 | // example 122 | // increment example counter, set the text and reset the subexample counter 123 | ex++; 124 | el.prepend(`ex. ${ex} `); 125 | subex = 0; 126 | 127 | } 128 | 129 | }); 130 | 131 | } 132 | 133 | function initTOCAndHeaders() 134 | { 135 | 136 | // the element to append created list items to 137 | let tocListEl = tocEl.querySelector('main'); 138 | // section counters (1., 1.1., 1.1.1) 139 | let counters = [0, 0, 0]; 140 | 141 | // function to scroll to section linked to by the clicked link (ev.currentTarget) 142 | function sectionLinkEl_click(ev) 143 | { 144 | ev.preventDefault(); 145 | 146 | // show fragment in the address bar without triggering the default instant scroll 147 | window.history.replaceState(null, null, ev.currentTarget.href); 148 | 149 | // find element given by the href of the clicked link 150 | let scrollToEl = document.querySelector(ev.currentTarget.getAttribute('href')); 151 | 152 | // make sure the TOC fab won't overlap the header we're scrolling to 153 | let scrollTop; 154 | 155 | if (scrollToEl.offsetTop < window.scrollY && window.innerWidth < mobileThreshold) 156 | { 157 | // scrolling up and on mobile, fab will be in the way, offset by its height 158 | scrollTop = scrollToEl.offsetTop - tocFab.clientHeight; 159 | } else 160 | { 161 | // scrolling down or not on mobile, fab is not in the way 162 | scrollTop = scrollToEl.offsetTop; 163 | } 164 | 165 | // use native smooth scroll to scroll to the position 166 | window.scrollTo({ 167 | top: scrollTop, 168 | behavior: 'smooth' 169 | }); 170 | } 171 | 172 | // go through all headers in the guide 173 | guideEl.querySelectorAll('h2, h3, h4').forEach(el => 174 | { 175 | 176 | // store the text of the header for later 177 | let html = el.innerHTML; 178 | let text = el.innerText; 179 | 180 | // create the href that will link to this section 181 | let href = `#${el.id}`; 182 | 183 | 184 | // header link 185 | 186 | // create a link to the section for placing in the header 187 | let sectionLinkEl = document.createElement('a'); 188 | sectionLinkEl.href = href; 189 | sectionLinkEl.title = 'Link to this section'; 190 | sectionLinkEl.innerHTML = html; 191 | 192 | // listen to header clicks 193 | sectionLinkEl.addEventListener('click', sectionLinkEl_click); 194 | 195 | // replace header text with the link to the section 196 | el.innerText = ''; 197 | el.appendChild(sectionLinkEl); 198 | 199 | 200 | // toc link 201 | 202 | // create a link to the section for putting in the TOC 203 | let tocLinkEl = document.createElement('a'); 204 | 205 | // get which header level this is 206 | let entryIndex = parseInt(el.tagName[1]) - 2; 207 | 208 | // increment the counter for this header level 209 | counters[entryIndex]++; 210 | 211 | // reset counters for lower header levels 212 | for (let i = entryIndex + 1; i < counters.length; i++) 213 | { 214 | counters[i] = 0; 215 | } 216 | 217 | // give the entry a class for indenting 218 | tocLinkEl.classList.add('toc-entry', `toc-${entryIndex}`); 219 | 220 | // make a text for the entry from the counters and the header text 221 | tocLinkEl.innerHTML = `${counters.slice(0, entryIndex + 1).join('.')}. ${html}`; 222 | 223 | tocLinkEl.href = href; 224 | 225 | // listen to TOC entry clicks 226 | tocLinkEl.addEventListener('click', ev => 227 | { 228 | sectionLinkEl_click(ev) 229 | 230 | // if mobile, close TOC so the user doesn't have to do it manually 231 | if (window.innerWidth < mobileThreshold) 232 | setTOCOpen(false) 233 | }); 234 | 235 | // add created entry to the TOC 236 | tocListEl.append(tocLinkEl); 237 | 238 | }); 239 | 240 | 241 | let tocFab = document.querySelector('.toc-fab'); 242 | 243 | // toggle TOC on fab click 244 | tocFab.addEventListener('click', ev => setTOCOpen(!isTOCOpen)); 245 | 246 | 247 | // hide TOC fab on scroll down on mobile 248 | 249 | // init scroll Y to current scroll Y 250 | let prevScrollY = window.scrollY; 251 | let showingFab = true; 252 | 253 | // react to window scrolling 254 | window.addEventListener('scroll', ev => 255 | { 256 | 257 | // this code could maybe be changed to use requestAnimationFrame, but I don't think that's necessary 258 | 259 | if (window.scrollY > prevScrollY) 260 | { 261 | 262 | // scrolling down, hide fab if it's visible 263 | if (showingFab) 264 | { 265 | tocFab.classList.add('scroll-out'); 266 | showingFab = false; 267 | } 268 | 269 | } else 270 | { 271 | 272 | // scrolling up, show fab if it's hidden 273 | if (!showingFab) 274 | { 275 | tocFab.classList.remove('scroll-out'); 276 | showingFab = true; 277 | } 278 | 279 | } 280 | 281 | // store current scroll Y for next scroll event 282 | prevScrollY = window.scrollY; 283 | 284 | }); 285 | 286 | 287 | // hide TOC on scrim click 288 | let tocScrimEl = document.querySelector('.toc-scrim'); 289 | tocScrimEl.addEventListener('click', ev => setTOCOpen(false)); 290 | 291 | // show TOC initially on desktop, hide on mobile 292 | setTOCOpen(window.innerWidth >= mobileThreshold); 293 | 294 | // add transition class after setting TOC initial state (so further changes will be animated) 295 | setTimeout(() => tocEl.classList.add('transition'), 0); 296 | 297 | } 298 | 299 | function initThemeSwitching() 300 | { 301 | 302 | // reacts to the user's theme selection changing between 'light', 'dark' and 'system' 303 | // 1. synchronizes both theme switchers 304 | // 2. switches to the appropriate theme (resolves 'system' to 'light' or 'dark') 305 | // 3. saves the selection to localStorage 306 | function reactToSelectedThemeChange(theme, dontAnimate) 307 | { 308 | 309 | if (document.readyState === 'interactive') 310 | { 311 | setCheckedValue('theme', theme); 312 | setCheckedValue('theme2', theme); 313 | } 314 | 315 | if (theme === 'dark' || theme === 'light') 316 | { 317 | 318 | // switch to theme directly 319 | switchTo(theme, dontAnimate); 320 | 321 | } else 322 | { 323 | 324 | // theme is system 325 | if (window.matchMedia) 326 | { 327 | 328 | if (!matchMedia) 329 | { 330 | // this is the first time system was selected, check system theme and listen for further changes 331 | matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); 332 | matchMedia.addEventListener('change', systemDarkThemeListener); 333 | } 334 | 335 | if (matchMedia.matches) 336 | { 337 | // system set to dark theme 338 | switchTo('dark', dontAnimate); 339 | } else 340 | { 341 | // system set to light theme 342 | switchTo('light', dontAnimate); 343 | } 344 | 345 | } else 346 | { 347 | // switch to light as fallback 348 | switchTo('light', dontAnimate); 349 | } 350 | } 351 | 352 | // store the selection as the last selected theme that will be loaded on next visit 353 | localStorage.setItem('theme', theme); 354 | } 355 | 356 | // listens to system dark theme changes and switches to the appropriate theme if 'system' is selected 357 | function systemDarkThemeListener() 358 | { 359 | if (document.querySelector('[name="theme"]:checked').value === 'system') 360 | { 361 | switchTo(matchMedia.matches ? 'dark' : 'light'); 362 | } 363 | } 364 | 365 | // visually switches the document to light or dark theme (don't pass in 'system') 366 | // if dontAnimate is true: 367 | // 1. toggles the "light" theme class on 368 | // else 369 | // 1. creates a theme-switch-overlay 370 | // 2. shows the overlay and waits for animation finish 371 | // 3. toggles the "light" theme class on 372 | // 4. hides the overlay and waits for animation finish 373 | // 5. removes the overlay from the DOM 374 | function switchTo(toTheme, dontAnimate) 375 | { 376 | 377 | if (currentTheme === toTheme) 378 | return; 379 | 380 | document.documentElement.style.backgroundColor = null; 381 | 382 | if (dontAnimate) 383 | { 384 | 385 | document.documentElement.classList.toggle('dark', toTheme === 'dark'); 386 | 387 | } else 388 | { 389 | 390 | let overlay = document.createElement('div'); 391 | overlay.classList.add('theme-switch-overlay', toTheme); 392 | document.body.appendChild(overlay); 393 | 394 | setTimeout(() => 395 | { 396 | overlay.classList.add('in'); 397 | setTimeout(() => 398 | { 399 | document.documentElement.classList.toggle('dark', toTheme === 'dark'); 400 | overlay.classList.remove('in'); 401 | overlay.classList.add('out'); 402 | setTimeout(() => 403 | { 404 | overlay.remove(); 405 | }, 330); 406 | }, 330); 407 | }, 0); 408 | 409 | } 410 | 411 | currentTheme = toTheme; 412 | 413 | } 414 | 415 | // get initial theme from local storage, default to system 416 | let initialTheme = localStorage.getItem('theme') || 'system'; 417 | 418 | document.addEventListener('DOMContentLoaded', ev => 419 | { 420 | document.querySelectorAll('[name="theme"], [name="theme2"]').forEach(el => 421 | { 422 | 423 | // check appropriate radios 424 | el.checked = el.value === initialTheme; 425 | 426 | // react to radios of both theme switchers changing 427 | el.addEventListener('change', ev => reactToSelectedThemeChange(ev.currentTarget.value)); 428 | 429 | }); 430 | }) 431 | 432 | // set initial theme 433 | reactToSelectedThemeChange(initialTheme, true); 434 | 435 | } 436 | 437 | // helper function to set the checked value of a radio group given by name 438 | function setCheckedValue(name, value) 439 | { 440 | document.querySelectorAll(`[name="${name}"]`).forEach(el => 441 | { 442 | el.checked = el.value === value; 443 | }); 444 | } 445 | 446 | // initializes double-tapping on example boxes to copy to clipboard, with a fancy animation 447 | function initDoubleTapToCopy() 448 | { 449 | 450 | const doubleTapDelay = 500; 451 | 452 | document.querySelectorAll('.snippet > code, .snippet > .step > code').forEach(el => 453 | { 454 | 455 | let lastTapForThisEl = 0; 456 | 457 | el.addEventListener('click', ev => 458 | { 459 | 460 | let now = Date.now(); 461 | 462 | if (now - lastTapForThisEl < doubleTapDelay) 463 | { 464 | 465 | let noDollars = el.classList.contains('no-dollars') || el.previousElementSibling?.classList.contains('output'); 466 | 467 | // double tap detected, copy snippet text to clipboard 468 | navigator.clipboard.writeText(noDollars ? el.innerText : `$${el.innerText}$`).then(function () 469 | { 470 | 471 | // success, animate 472 | 473 | // after animation ends, remove the animation class and the listener 474 | const listener = ev => 475 | { 476 | el.classList.remove('copied'); 477 | el.removeEventListener('transitionend', listener); 478 | }; 479 | 480 | el.classList.add('copied'); 481 | el.addEventListener('animationend', listener); 482 | 483 | }, function (err) 484 | { 485 | alert('Error copying:' + err); 486 | }); 487 | 488 | 489 | } else 490 | { 491 | lastTapForThisEl = now; 492 | } 493 | 494 | }); 495 | 496 | }); 497 | 498 | } 499 | 500 | let swipeInProgress = false; 501 | let swipeStartX, swipeStartY; 502 | const swipeThreshold = 24; 503 | 504 | function initSwipeInteractionsForToc() 505 | { 506 | 507 | window.addEventListener('touchstart', ev => 508 | { 509 | 510 | if (ev.touches.length !== 1) 511 | return; 512 | 513 | let touch = ev.touches[0]; 514 | swipeStartX = touch.clientX; 515 | swipeStartY = touch.clientY; 516 | 517 | swipeInProgress = true; 518 | 519 | }, false); 520 | 521 | window.addEventListener('touchmove', ev => 522 | { 523 | 524 | if (!swipeInProgress) 525 | return; 526 | 527 | let touch = ev.touches[0]; 528 | 529 | let dx = touch.clientX - swipeStartX; 530 | let dy = touch.clientY - swipeStartY; 531 | 532 | if (Math.abs(dx) > Math.abs(dy)) 533 | { 534 | 535 | if (Math.abs(dx) >= swipeThreshold) 536 | { 537 | // horizontal swipe 538 | ev.preventDefault(); 539 | setTOCOpen(dx > 0); 540 | swipeInProgress = false; 541 | } 542 | 543 | } else 544 | { 545 | // vertical swipe 546 | swipeInProgress = false; 547 | } 548 | 549 | 550 | }, { passive: false }); 551 | 552 | window.addEventListener('touchend', ev => 553 | { 554 | swipeInProgress = false; 555 | }, false); 556 | 557 | } 558 | 559 | // opens/closes the TOC and remembers the state in isTOCOpen 560 | function setTOCOpen(open) 561 | { 562 | tocEl.classList.toggle('in', open); 563 | isTOCOpen = open; 564 | } 565 | 566 | function initScrollRestore() 567 | { 568 | 569 | const lastVisitScrollYKey = 'lastVisitScrollY'; 570 | const lastVisitFragmentKey = 'lastVisitFragment'; 571 | 572 | // restore scroll position on back/forward 573 | window.addEventListener('pageshow', ev => 574 | { 575 | 576 | let lastVisitScrollY = parseInt(localStorage.getItem(lastVisitScrollYKey)); 577 | let lastVisitFragment = localStorage.getItem(lastVisitFragmentKey); 578 | 579 | // ev.persisted is true when loading from bfcache, which preserves scroll pos on its own 580 | if (!ev.persisted) 581 | { 582 | 583 | // if (Math.abs(window.scrollY - lastVisitScrollY) >= 24 && (!window.location.hash || window.location.hash === lastVisitFragment)) 584 | if ((window.scrollY === 0 && lastVisitScrollY >= 24) || (window.location.fragment === lastVisitFragment && Math.abs(document.querySelector(lastVisitFragment).offsetTop - window.scrollY) >= 24)) 585 | { 586 | 587 | // offer to restore after hard reload/fresh visit 588 | 589 | console.log('adding notif el'); 590 | 591 | let notifEl = document.createElement('div'); 592 | notifEl.classList.add('scroll-restore-message'); 593 | 594 | let spanEl = document.createElement('span'); 595 | spanEl.innerText = 'Click here to pick up where you left off'; 596 | 597 | 598 | let dismissBtnEl = document.createElement('button'); 599 | dismissBtnEl.classList.add('btn-dismiss'); 600 | dismissBtnEl.innerText = 'DISMISS'; 601 | dismissBtnEl.addEventListener('click', ev => 602 | { 603 | hideNotifEl() 604 | // make sure the click doesn't reach the parent 605 | ev.stopPropagation(); 606 | }); 607 | 608 | 609 | notifEl.appendChild(spanEl); 610 | notifEl.appendChild(dismissBtnEl); 611 | 612 | notifEl.addEventListener('click', ev => 613 | { 614 | window.scrollTo({ 615 | top: lastVisitScrollY, 616 | behavior: 'smooth' 617 | }); 618 | hideNotifEl(); 619 | }); 620 | 621 | document.body.appendChild(notifEl); 622 | setTimeout(() => notifEl.classList.add('in'), 0); 623 | 624 | function hideNotifEl() 625 | { 626 | 627 | notifEl.addEventListener('transitionend', ev => notifEl.remove()); 628 | notifEl.classList.remove('in'); 629 | 630 | localStorage.removeItem(lastVisitScrollYKey); 631 | localStorage.removeItem(lastVisitFragmentKey); 632 | 633 | } 634 | 635 | } 636 | } 637 | 638 | }); 639 | 640 | let saveScrollTimeoutDuration = 100; 641 | let saveScrollTimeoutId = null; 642 | 643 | window.addEventListener('scroll', ev => 644 | { 645 | 646 | clearTimeout(saveScrollTimeoutId); 647 | 648 | saveScrollTimeoutId = setTimeout(() => 649 | { 650 | 651 | localStorage.setItem(lastVisitScrollYKey, window.scrollY); 652 | localStorage.setItem(lastVisitFragmentKey, window.location.hash); 653 | 654 | }, saveScrollTimeoutDuration); 655 | 656 | }); 657 | 658 | } -------------------------------------------------------------------------------- /css/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import url("https://fonts.googleapis.com/css2?family=Inria+Sans:ital@0;1&family=Roboto+Mono&family=Source+Serif+Pro:wght@700&display=swap"); 3 | @media not print { 4 | :root.dark { 5 | --c-bg: #101010; 6 | --c-bg-hover: rgba(255, 255, 255, 0.1); 7 | --c-bg-copied: #ffffff; 8 | --c-text-on-bg-pri: rgba(255, 255, 255, 0.9); 9 | --c-text-on-bg-sec: rgba(255, 255, 255, 0.8); 10 | --c-text-on-bg-tet: rgba(255, 255, 255, 0.6); 11 | --c-text-on-bg-irr: rgba(255, 255, 255, 0.4); 12 | --c-text-link: #74c9ea; 13 | --c-text-error: #ed9090; 14 | --c-text-success: #84c666; 15 | --c-border-light: rgba(255, 255, 255, 0.2); 16 | --c-border-heavy: rgba(255, 255, 255, 0.5); 17 | --c-code-bg: rgba(0, 0, 0, 0.01); 18 | --c-hl-1: #eacf7f; 19 | --c-hl-2: #c88afe; 20 | --c-hl-3: #a6ddfd; 21 | --c-hl-4: #c2e4a1; 22 | --c-hl-5: #f794d1; 23 | --c-hl-6: #ffb872; 24 | --c-hl-1-strong: #ffcc35; 25 | --c-hl-2-strong: #d19dff; 26 | --c-hl-3-strong: #74ccff; 27 | --c-hl-4-strong: #c4ff8a; 28 | --c-hl-5-strong: #f794d1; 29 | --c-hl-6-strong: #ffb872; 30 | --c-hl-gray: #f5f5f5; 31 | --c-hl-gray-strong: #d2d2d2; 32 | --c-hl-before: #ffac58; 33 | --c-hl-after: #ffac58; 34 | --c-hl-error: #ea9999; 35 | --c-hl-true: #9ecd88; 36 | --c-hl-false: #d98989; 37 | --c-hl-true-strong: #9dff6f; 38 | --c-hl-false-strong: #ff6f6f; 39 | color-scheme: dark; 40 | } 41 | } 42 | 43 | :root { 44 | --c-bg: #ffffff; 45 | --c-bg-hover: rgba(0, 0, 0, 0.05); 46 | --c-bg-copied: #101010; 47 | --c-text-on-bg-pri: rgba(0, 0, 0, 0.9); 48 | --c-text-on-bg-sec: rgba(0, 0, 0, 0.75); 49 | --c-text-on-bg-tet: rgba(0, 0, 0, 0.5); 50 | --c-text-on-bg-irr: rgba(0, 0, 0, 0.35); 51 | --c-text-link: #0e7398; 52 | --c-text-error: #e41717; 53 | --c-text-success: #52a92a; 54 | --c-border-light: rgba(0, 0, 0, 0.1); 55 | --c-border-heavy: rgba(0, 0, 0, 0.25); 56 | --c-code-bg: rgba(0, 0, 0, 0.01); 57 | --c-hl-1: #ffe599; 58 | --c-hl-2: #f0dfff; 59 | --c-hl-3: #d1eeff; 60 | --c-hl-4: #d9ead3; 61 | --c-hl-5: #f4cccc; 62 | --c-hl-6: #f9cb9c; 63 | --c-hl-1-strong: #f7ca42; 64 | --c-hl-2-strong: #d8bcfd; 65 | --c-hl-3-strong: #9fdcff; 66 | --c-hl-4-strong: #a2f484; 67 | --c-hl-5-strong: #ff7979; 68 | --c-hl-6-strong: #fba44e; 69 | --c-hl-gray: #f5f5f5; 70 | --c-hl-gray-strong: #d2d2d2; 71 | --c-hl-before: #ffd9b3; 72 | --c-hl-after: #ffd9b3; 73 | --c-hl-error: #ea9999; 74 | --c-hl-true: #ecfccf; 75 | --c-hl-false: #ffc7c7; 76 | --c-hl-true-strong: #ccff6d; 77 | --c-hl-false-strong: #ff9797; 78 | color-scheme: light; 79 | } 80 | 81 | @media (hover: hover) { 82 | .toc > main > .toc-entry, .toc-fab:before, .scroll-restore-message > .btn-dismiss, .btn { 83 | transition: background-color cubic-bezier(0.4, 0, 0.2, 1) 0.15s; 84 | } 85 | .toc > main > .toc-entry:hover, .toc-fab:hover:before, .scroll-restore-message > .btn-dismiss:hover, .btn:hover { 86 | background-color: var(--c-bg-hover); 87 | } 88 | } 89 | 90 | * { 91 | box-sizing: border-box; 92 | } 93 | 94 | html, 95 | body { 96 | width: 100%; 97 | height: 100%; 98 | padding: 0; 99 | margin: 0; 100 | } 101 | 102 | .guide { 103 | width: 100%; 104 | max-width: 50rem; 105 | padding: 0 1rem; 106 | padding-bottom: 15rem; 107 | } 108 | 109 | h1 { 110 | font-size: 2.5rem; 111 | margin: 2.5rem 0; 112 | margin-top: 6rem; 113 | } 114 | 115 | h1 > .small { 116 | margin-top: 1rem; 117 | } 118 | 119 | h2 { 120 | font-size: 2rem; 121 | margin: 0; 122 | padding-top: 2rem; 123 | } 124 | 125 | h3 { 126 | font-size: 1.5rem; 127 | margin: 0; 128 | padding-top: 2.5rem; 129 | } 130 | 131 | h4 { 132 | font-size: 1.25rem; 133 | margin: 0; 134 | padding-top: 2rem; 135 | } 136 | 137 | h2 + h3 { 138 | padding-top: 1.5rem; 139 | } 140 | 141 | h1 + p, h1 + .snippet, h2 + p, h2 + .snippet, h3 + p, h3 + .snippet, h4 + p, h4 + .snippet { 142 | margin-top: 2rem; 143 | } 144 | 145 | p, blockquote { 146 | margin-top: 1.5rem; 147 | line-height: 140%; 148 | } 149 | 150 | .snippet { 151 | margin-top: 0.5rem; 152 | } 153 | 154 | ul, ol { 155 | line-height: 140%; 156 | } 157 | 158 | ul > li + li, ol > li + li { 159 | margin-top: 0.5rem; 160 | } 161 | 162 | .toc { 163 | position: fixed; 164 | top: 0; 165 | left: 0; 166 | bottom: 0; 167 | z-index: 2; 168 | width: 24rem; 169 | max-width: calc(100vw - 3rem); 170 | } 171 | 172 | .toc.in { 173 | transform: translateX(0); 174 | } 175 | 176 | .toc:not(.in) { 177 | transform: translateX(-100%); 178 | } 179 | 180 | .toc-fab { 181 | position: fixed; 182 | top: 1rem; 183 | right: -4rem; 184 | z-index: 2; 185 | } 186 | 187 | .toc { 188 | display: flex; 189 | flex-direction: column; 190 | background-color: var(--c-bg); 191 | vertical-align: top; 192 | border-right: 1px solid var(--c-border-light); 193 | text-align: left; 194 | } 195 | 196 | .toc.transition { 197 | transition: transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s; 198 | } 199 | 200 | .toc > header { 201 | display: flex; 202 | align-items: center; 203 | width: 100%; 204 | border-bottom: 1px solid var(--c-border-light); 205 | padding: 1rem; 206 | font-family: "Source Serif Pro", serif; 207 | font-size: 1.5rem; 208 | } 209 | 210 | .toc > main { 211 | padding: 0.5rem; 212 | flex: 1; 213 | overflow-y: auto; 214 | overscroll-behavior-y: contain; 215 | } 216 | 217 | .toc > main > .toc-entry { 218 | display: block; 219 | text-decoration: none; 220 | color: var(--c-text-on-bg-sec); 221 | padding: 0.5rem; 222 | -webkit-tap-highlight-color: transparent; 223 | border-radius: 0.25rem; 224 | } 225 | 226 | .toc > main > .toc-entry > .number { 227 | color: var(--c-text-on-bg-irr); 228 | } 229 | 230 | .toc > main > .toc-1 { 231 | padding-left: 1rem; 232 | } 233 | 234 | .toc > main > .toc-2 { 235 | padding-left: 2rem; 236 | } 237 | 238 | .toc > footer { 239 | padding: 1rem; 240 | padding-top: 0.5rem; 241 | border-top: 1px solid var(--c-border-light); 242 | } 243 | 244 | .toc > footer > .theme-switcher { 245 | margin-top: 0; 246 | } 247 | 248 | .toc-fab { 249 | width: 3rem; 250 | height: 3rem; 251 | font-size: 1rem; 252 | border-radius: 1.5rem; 253 | border: 1px solid var(--c-border-light); 254 | color: var(--c-text-on-bg-pri); 255 | background-color: var(--c-bg); 256 | -webkit-tap-highlight-color: transparent; 257 | outline: none; 258 | cursor: pointer; 259 | overflow: hidden; 260 | transition: transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s; 261 | } 262 | 263 | .toc-fab:before { 264 | content: ""; 265 | position: absolute; 266 | top: 0; 267 | left: 0; 268 | right: 0; 269 | bottom: 0; 270 | } 271 | 272 | .toc-fab > .top, .toc-fab > .mid, .toc-fab > .bot { 273 | position: absolute; 274 | top: 50%; 275 | left: 50%; 276 | width: 1rem; 277 | height: 0.1rem; 278 | background-color: var(--c-text-on-bg-pri); 279 | } 280 | 281 | .toc-fab > .top { 282 | transform: translate(-50%, -50%) translateY(-0.33333rem); 283 | } 284 | 285 | .toc-fab > .mid { 286 | transform: translate(-50%, -50%); 287 | } 288 | 289 | .toc-fab > .bot { 290 | transform: translate(-50%, -50%) translateY(0.33333rem); 291 | } 292 | 293 | .toc.transition .toc-fab > .top, .toc.transition .toc-fab > .mid, .toc.transition .toc-fab > .bot { 294 | transition: transform cubic-bezier(0.4, 0, 0.2, 1) 0.15s; 295 | } 296 | 297 | .toc.in .toc-fab > .top { 298 | transform: translate(-25%, -50%) translateY(-0.33333rem) translate(-33%, 0.18182rem) rotate(-45deg) scaleX(0.5); 299 | } 300 | 301 | .toc.in .toc-fab > .mid { 302 | transform: translate(-50%, -50%) scaleX(0); 303 | } 304 | 305 | .toc.in .toc-fab > .bot { 306 | transform: translate(-25%, -50%) translateY(0.33333rem) translate(-33%, -0.18182rem) rotate(45deg) scaleX(0.5); 307 | } 308 | 309 | .toc-scrim { 310 | position: fixed; 311 | top: 0; 312 | left: 0; 313 | bottom: 0; 314 | right: 0; 315 | z-index: 1; 316 | background-color: var(--c-bg); 317 | opacity: 0; 318 | pointer-events: none; 319 | display: none; 320 | -webkit-tap-highlight-color: transparent; 321 | } 322 | 323 | .toc.in ~ .toc-scrim { 324 | opacity: 0.9; 325 | pointer-events: auto; 326 | } 327 | 328 | body { 329 | font-family: "Inria Sans", sans-serif; 330 | font-size: 1rem; 331 | color: var(--c-text-on-bg-sec); 332 | background-color: var(--c-bg); 333 | line-height: 1.25; 334 | } 335 | 336 | .guide { 337 | text-align: justify; 338 | } 339 | 340 | h1, h2, h3, h4, h5, h6 { 341 | font-family: "Source Serif Pro", serif; 342 | font-weight: 700; 343 | color: var(--c-text-on-bg-pri); 344 | text-align: left; 345 | } 346 | 347 | h1 > a, h2 > a, h3 > a, h4 > a, h5 > a, h6 > a { 348 | color: inherit; 349 | text-decoration: none; 350 | } 351 | 352 | h1 > a:hover, h2 > a:hover, h3 > a:hover, h4 > a:hover, h5 > a:hover, h6 > a:hover { 353 | text-decoration: underline; 354 | } 355 | 356 | .small { 357 | font-size: 0.75rem; 358 | font-weight: normal; 359 | color: var(--c-text-on-bg-tet); 360 | font-family: "Inria Sans", sans-serif; 361 | } 362 | 363 | .guide > footer { 364 | margin-top: 4rem; 365 | border-top: 1px dashed var(--c-border-light); 366 | } 367 | 368 | .image-container { 369 | width: 100%; 370 | } 371 | 372 | .image-container img { 373 | display: block; 374 | max-width: 100%; 375 | padding: 1rem; 376 | } 377 | 378 | .image-container label { 379 | margin-top: -0.5rem; 380 | display: block; 381 | text-align: center; 382 | font-size: 0.75rem; 383 | color: var(--c-text-on-bg-tet); 384 | font-style: italic; 385 | } 386 | 387 | a { 388 | color: var(--c-text-link); 389 | } 390 | 391 | blockquote { 392 | font-style: italic; 393 | border-left: 2px solid var(--c-border-light); 394 | margin-left: 0; 395 | padding-left: 1rem; 396 | padding-top: 0.5rem; 397 | padding-bottom: 0.5rem; 398 | } 399 | 400 | .guide { 401 | counter-reset: h2 h3 h4; 402 | } 403 | 404 | h2 { 405 | counter-increment: h2; 406 | counter-reset: h3; 407 | } 408 | 409 | h2:before { 410 | content: counter(h2) ". "; 411 | } 412 | 413 | h3 { 414 | counter-increment: h3; 415 | counter-reset: h4; 416 | } 417 | 418 | h3:before { 419 | content: counter(h2) "." counter(h3) ". "; 420 | } 421 | 422 | h4 { 423 | counter-increment: h4; 424 | } 425 | 426 | h4:before { 427 | content: counter(h2) "." counter(h3) "." counter(h4) ". "; 428 | } 429 | 430 | code { 431 | display: inline-block; 432 | font-family: "Roboto Mono", monospace; 433 | font-size: 0.85em; 434 | border: 1px solid var(--c-border-light); 435 | border-radius: 0.25em; 436 | padding: 0 0.35em; 437 | margin: 0 0.2em; 438 | word-break: break-all; 439 | text-align: left; 440 | font-style: normal; 441 | } 442 | 443 | code > pre { 444 | margin: 0; 445 | white-space: pre-wrap; 446 | } 447 | 448 | :root:not(.dark) .hl { 449 | position: relative; 450 | z-index: -1; 451 | line-height: 1; 452 | white-space: pre-wrap; 453 | padding: 0.1rem; 454 | margin: -0.1rem; 455 | } 456 | 457 | :root:not(.dark) .hl + .hl { 458 | margin-left: 0.1rem; 459 | } 460 | 461 | :root:not(.dark) .hl-1 { 462 | background-color: var(--c-hl-1); 463 | } 464 | 465 | :root:not(.dark) .hl-1.strong { 466 | background-color: var(--c-hl-1-strong); 467 | } 468 | 469 | :root:not(.dark) .hl-2 { 470 | background-color: var(--c-hl-2); 471 | } 472 | 473 | :root:not(.dark) .hl-2.strong { 474 | background-color: var(--c-hl-2-strong); 475 | } 476 | 477 | :root:not(.dark) .hl-3 { 478 | background-color: var(--c-hl-3); 479 | } 480 | 481 | :root:not(.dark) .hl-3.strong { 482 | background-color: var(--c-hl-3-strong); 483 | } 484 | 485 | :root:not(.dark) .hl-4 { 486 | background-color: var(--c-hl-4); 487 | } 488 | 489 | :root:not(.dark) .hl-4.strong { 490 | background-color: var(--c-hl-4-strong); 491 | } 492 | 493 | :root:not(.dark) .hl-5 { 494 | background-color: var(--c-hl-5); 495 | } 496 | 497 | :root:not(.dark) .hl-5.strong { 498 | background-color: var(--c-hl-5-strong); 499 | } 500 | 501 | :root:not(.dark) .hl-6 { 502 | background-color: var(--c-hl-6); 503 | } 504 | 505 | :root:not(.dark) .hl-6.strong { 506 | background-color: var(--c-hl-6-strong); 507 | } 508 | 509 | :root:not(.dark) .hl-before { 510 | background-color: var(--c-hl-before); 511 | } 512 | 513 | :root:not(.dark) .hl-after { 514 | background-color: var(--c-hl-after); 515 | } 516 | 517 | :root:not(.dark) .hl-error { 518 | background-color: var(--c-hl-error); 519 | } 520 | 521 | :root:not(.dark) .hl-true { 522 | background-color: var(--c-hl-true); 523 | } 524 | 525 | :root:not(.dark) .hl-true.strong { 526 | background-color: var(--c-hl-true-strong); 527 | } 528 | 529 | :root:not(.dark) .hl-false { 530 | background-color: var(--c-hl-false); 531 | } 532 | 533 | :root:not(.dark) .hl-false.strong { 534 | background-color: var(--c-hl-false-strong); 535 | } 536 | 537 | :root:not(.dark) .hl-gray { 538 | background-color: var(--c-hl-gray); 539 | } 540 | 541 | :root:not(.dark) .hl-gray.strong { 542 | background-color: var(--c-hl-gray-strong); 543 | } 544 | 545 | :root.dark .hl-1 { 546 | color: var(--c-hl-1); 547 | } 548 | 549 | :root.dark .hl-1.strong { 550 | color: var(--c-hl-1-strong); 551 | } 552 | 553 | :root.dark .hl-2 { 554 | color: var(--c-hl-2); 555 | } 556 | 557 | :root.dark .hl-2.strong { 558 | color: var(--c-hl-2-strong); 559 | } 560 | 561 | :root.dark .hl-3 { 562 | color: var(--c-hl-3); 563 | } 564 | 565 | :root.dark .hl-3.strong { 566 | color: var(--c-hl-3-strong); 567 | } 568 | 569 | :root.dark .hl-4 { 570 | color: var(--c-hl-4); 571 | } 572 | 573 | :root.dark .hl-4.strong { 574 | color: var(--c-hl-4-strong); 575 | } 576 | 577 | :root.dark .hl-5 { 578 | color: var(--c-hl-5); 579 | } 580 | 581 | :root.dark .hl-5.strong { 582 | color: var(--c-hl-5-strong); 583 | } 584 | 585 | :root.dark .hl-6 { 586 | color: var(--c-hl-6); 587 | } 588 | 589 | :root.dark .hl-6.strong { 590 | color: var(--c-hl-6-strong); 591 | } 592 | 593 | :root.dark .hl-before { 594 | color: var(--c-hl-before); 595 | } 596 | 597 | :root.dark .hl-after { 598 | color: var(--c-hl-after); 599 | } 600 | 601 | :root.dark .hl-error { 602 | color: var(--c-hl-error); 603 | } 604 | 605 | :root.dark .hl-true { 606 | color: var(--c-hl-true); 607 | } 608 | 609 | :root.dark .hl-true.strong { 610 | color: var(--c-hl-true-strong); 611 | } 612 | 613 | :root.dark .hl-false { 614 | color: var(--c-hl-false); 615 | } 616 | 617 | :root.dark .hl-false.strong { 618 | color: var(--c-hl-false-strong); 619 | } 620 | 621 | :root.dark .hl-gray { 622 | color: var(--c-hl-gray); 623 | } 624 | 625 | :root.dark .hl-gray.strong { 626 | color: var(--c-hl-gray-strong); 627 | } 628 | 629 | :root.dark .hl.strong { 630 | font-weight: bold; 631 | } 632 | 633 | .snippet { 634 | margin-top: 1rem; 635 | } 636 | 637 | .snippet label { 638 | display: block; 639 | position: sticky; 640 | left: 0; 641 | font-size: 0.75rem; 642 | color: var(--c-text-on-bg-tet); 643 | line-height: 1; 644 | padding: 0.4rem 0.5rem; 645 | } 646 | 647 | .snippet label > .error, 648 | .snippet label > .success { 649 | font-style: italic; 650 | } 651 | 652 | .snippet label > .error:before, 653 | .snippet label > .success:before { 654 | content: "-"; 655 | margin: 0 0.25rem; 656 | color: var(--c-text-on-bg-tet); 657 | } 658 | 659 | .snippet label > .error { 660 | color: var(--c-text-error); 661 | } 662 | 663 | .snippet label > .success { 664 | color: var(--c-text-success); 665 | } 666 | 667 | .snippet label.output:before { 668 | content: "Output"; 669 | } 670 | 671 | .snippet > code, .snippet .step > code { 672 | display: block; 673 | border: 1px solid var(--c-border-light); 674 | padding: 0.5rem 0.75rem; 675 | line-height: 1.5; 676 | margin: 0; 677 | overflow: hidden; 678 | position: relative; 679 | } 680 | 681 | .snippet > code:empty, .snippet .step > code:empty { 682 | user-select: none; 683 | } 684 | 685 | .snippet > code:empty:before, .snippet .step > code:empty:before { 686 | content: " "; 687 | white-space: pre; 688 | } 689 | 690 | .snippet > code + code, .snippet .step > code + code { 691 | margin-top: 0.2rem; 692 | } 693 | 694 | .snippet > code + label, .snippet .step > code + label { 695 | margin-top: 0.5rem; 696 | } 697 | 698 | .snippet > code.copied:after, .snippet .step > code.copied:after { 699 | content: ""; 700 | background-color: var(--c-bg-copied); 701 | position: absolute; 702 | top: 50%; 703 | left: 50%; 704 | z-index: 1; 705 | width: 50%; 706 | height: 1000%; 707 | transform-origin: center; 708 | animation: 0.5s 1 forwards copied; 709 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1); 710 | } 711 | 712 | @keyframes copied { 713 | 0% { 714 | transform: translateX(-50%) translateX(Min(25rem, 50vw)) translateX(80%) translateY(-50%) translateX(-50%) rotate(20deg); 715 | } 716 | 100% { 717 | transform: translateX(-50%) translateX(Max(-25rem, -50vw)) translateX(-80%) translateY(-50%) translateX(-50%) rotate(20deg); 718 | } 719 | } 720 | 721 | .snippet > .step .arrow { 722 | display: flex; 723 | align-items: center; 724 | justify-content: start; 725 | padding: 0 0.5rem; 726 | font-size: 1.4rem; 727 | margin-top: -0.25em; 728 | margin-bottom: -0.25em; 729 | line-height: 1; 730 | } 731 | 732 | .snippet > .step .arrow::before { 733 | content: "↓"; 734 | color: var(--c-text-on-bg-tet); 735 | } 736 | 737 | .snippet > .step + label { 738 | margin-top: 0.5rem; 739 | } 740 | 741 | .snippet > .note { 742 | font-size: 0.75rem; 743 | color: var(--c-text-on-bg-tet); 744 | margin-top: 0.5rem; 745 | font-style: italic; 746 | padding: 0 0.5rem; 747 | } 748 | 749 | .snippet > .note + label { 750 | margin-top: 0.5rem; 751 | } 752 | 753 | .snippet + .snippet { 754 | margin-top: 1.5rem; 755 | } 756 | 757 | .table-container { 758 | width: calc(100% + 16px); 759 | padding: 8px; 760 | margin: 0 -8px; 761 | overflow-x: auto; 762 | } 763 | 764 | table { 765 | border-spacing: 0; 766 | border-collapse: separate; 767 | border-radius: 0.25rem; 768 | border: 1px solid var(--c-border-light); 769 | text-align: left; 770 | } 771 | 772 | table thead > tr:last-child > th { 773 | border-bottom: 1px solid var(--c-border-heavy); 774 | } 775 | 776 | table tr:first-child th, 777 | table tr:first-child td { 778 | border-top: none; 779 | } 780 | 781 | table th, 782 | table td { 783 | padding: 0.5rem 0.75rem; 784 | border-top: 1px solid var(--c-border-light); 785 | border-left: 1px solid var(--c-border-light); 786 | } 787 | 788 | table th:first-child, 789 | table td:first-child { 790 | border-left: none; 791 | } 792 | 793 | table code { 794 | white-space: nowrap; 795 | } 796 | 797 | #math-symbol-table td:first-child, 798 | #math-symbol-table th:first-child { 799 | text-align: center; 800 | } 801 | 802 | #math-symbol-table td:nth-child(2), 803 | #math-symbol-table th:nth-child(2) { 804 | width: 100%; 805 | } 806 | 807 | #string-mode-table td:nth-child(1), 808 | #string-mode-table th:nth-child(1) { 809 | text-align: center; 810 | } 811 | 812 | #string-mode-table td:nth-child(2), 813 | #string-mode-table th:nth-child(2) { 814 | width: 100%; 815 | } 816 | 817 | #comparison-operator-table td:nth-child(1), 818 | #comparison-operator-table th:nth-child(1) { 819 | text-align: center; 820 | } 821 | 822 | #comparison-operator-table td:nth-child(2), 823 | #comparison-operator-table th:nth-child(2) { 824 | width: 100%; 825 | } 826 | 827 | :root.light #comparison-operator-table td:nth-child(3), 828 | :root.light #comparison-operator-table th:nth-child(3) { 829 | background-color: var(--c-hl-true); 830 | } 831 | 832 | :root.light #comparison-operator-table td:nth-child(4), 833 | :root.light #comparison-operator-table th:nth-child(4) { 834 | background-color: var(--c-hl-false); 835 | } 836 | 837 | :root:not(.light) #comparison-operator-table td:nth-child(3), 838 | :root:not(.light) #comparison-operator-table th:nth-child(3) { 839 | color: var(--c-hl-true); 840 | } 841 | 842 | :root:not(.light) #comparison-operator-table td:nth-child(4), 843 | :root:not(.light) #comparison-operator-table th:nth-child(4) { 844 | color: var(--c-hl-false); 845 | } 846 | 847 | #logical-operator-table td, 848 | #logical-operator-table th { 849 | width: 80px; 850 | text-align: center; 851 | } 852 | 853 | #logical-operator-table td:nth-child(3), #logical-operator-table td:nth-child(4), 854 | #logical-operator-table th:nth-child(3), 855 | #logical-operator-table th:nth-child(4) { 856 | border-left: 1px solid var(--c-border-heavy); 857 | } 858 | 859 | #tf-token-table tr:nth-child(4) td { 860 | border-top: 1px solid var(--c-border-heavy); 861 | } 862 | 863 | #tf-token-table td:nth-child(1), 864 | #tf-token-table th:nth-child(1) { 865 | text-align: center; 866 | } 867 | 868 | #tf-token-table td:nth-child(2), 869 | #tf-token-table th:nth-child(2) { 870 | width: 100%; 871 | } 872 | 873 | .theme-switcher > label { 874 | display: block; 875 | font-size: 0.75rem; 876 | color: var(--c-text-on-bg-tet); 877 | } 878 | 879 | .theme-switcher > label + .buttons { 880 | margin-top: 0.5rem; 881 | } 882 | 883 | .theme-switcher > .buttons { 884 | display: flex; 885 | flex-direction: row; 886 | gap: 0.5rem; 887 | margin-top: 1rem; 888 | -webkit-tap-highlight-color: transparent; 889 | } 890 | 891 | .theme-switcher > .buttons > label { 892 | flex: 1; 893 | padding: 0.5rem; 894 | text-align: center; 895 | position: relative; 896 | transition: background-color cubic-bezier(0.4, 0, 0.2, 1) 0.1s, transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s; 897 | cursor: pointer; 898 | } 899 | 900 | .theme-switcher > .buttons > label > input { 901 | display: none; 902 | } 903 | 904 | .theme-switcher > .buttons > label > span:before { 905 | content: ""; 906 | position: absolute; 907 | top: 0; 908 | left: 0; 909 | bottom: 0; 910 | right: 0; 911 | border-radius: 0.25rem; 912 | border: 1px solid var(--c-border-light); 913 | transition: border cubic-bezier(0.87, 1.92, 0.48, 0.75) 0.15s; 914 | } 915 | 916 | @media (hover: hover) { 917 | .theme-switcher > .buttons > label:hover { 918 | background-color: var(--c-bg-hover); 919 | } 920 | } 921 | 922 | .theme-switcher > .buttons > label:active { 923 | transition-duration: 0s; 924 | transform: scale(0.96); 925 | } 926 | 927 | .theme-switcher > .buttons > label > input:checked + span:before { 928 | border: 0.15rem solid #ff00ff; 929 | } 930 | 931 | .theme-switch-overlay { 932 | position: fixed; 933 | top: 0; 934 | left: 0; 935 | width: 100vw; 936 | bottom: 0; 937 | z-index: 10; 938 | transform: translateY(-100%); 939 | opacity: 1; 940 | transition: transform cubic-bezier(0.4, 0, 1, 1) 0.25s; 941 | } 942 | 943 | .theme-switch-overlay.dark { 944 | background-color: #101010; 945 | } 946 | 947 | .theme-switch-overlay.light { 948 | background-color: #ffffff; 949 | } 950 | 951 | .theme-switch-overlay.in { 952 | transform: translateY(0); 953 | } 954 | 955 | .theme-switch-overlay.out { 956 | transform: translateY(100%); 957 | transition-timing-function: cubic-bezier(0, 0, 0.2, 1); 958 | pointer-events: none; 959 | } 960 | 961 | .scroll-restore-message { 962 | position: fixed; 963 | bottom: 1rem; 964 | left: 1rem; 965 | right: 1rem; 966 | max-width: 50rem; 967 | margin: auto; 968 | display: flex; 969 | flex-direction: row; 970 | justify-content: space-between; 971 | flex-wrap: wrap; 972 | align-items: center; 973 | border: 1px solid var(--c-border-heavy); 974 | background-color: var(--c-bg); 975 | border-radius: 0.25rem; 976 | transform: translateY(100%) translateY(1rem); 977 | transition: transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s; 978 | -webkit-tap-highlight-color: transparent; 979 | cursor: pointer; 980 | } 981 | 982 | .scroll-restore-message > span { 983 | display: inline-block; 984 | padding: 1rem; 985 | } 986 | 987 | .scroll-restore-message > .btn-dismiss { 988 | background: none; 989 | border: none; 990 | outline: none; 991 | margin-left: auto; 992 | font-family: "Inria Sans", sans-serif; 993 | font-size: 1rem; 994 | color: var(--c-text-on-bg-tet); 995 | -webkit-tap-highlight-color: transparent; 996 | padding: 1rem; 997 | cursor: pointer; 998 | } 999 | 1000 | .scroll-restore-message.in { 1001 | transform: translateY(0); 1002 | } 1003 | 1004 | .btn { 1005 | background: none; 1006 | border: 1px solid var(--c-border-heavy); 1007 | border-radius: 0.25rem; 1008 | outline: none; 1009 | font-family: "Inria Sans", sans-serif; 1010 | font-size: 1rem; 1011 | color: var(--c-text-on-bg-sec); 1012 | -webkit-tap-highlight-color: transparent; 1013 | padding: 0.5rem 1rem; 1014 | cursor: pointer; 1015 | } 1016 | 1017 | .nowrap { 1018 | white-space: nowrap; 1019 | } 1020 | 1021 | .no-display { 1022 | display: none; 1023 | } 1024 | 1025 | .text-right { 1026 | text-align: right; 1027 | } 1028 | 1029 | .text-center { 1030 | text-align: center; 1031 | } 1032 | 1033 | @media screen and (min-width: 100rem) { 1034 | .guide { 1035 | margin: auto; 1036 | } 1037 | } 1038 | 1039 | @media screen and (min-width: 79rem) and (max-width: 100rem) { 1040 | .toc.transition ~ .guide { 1041 | transition: transform cubic-bezier(0.4, 0, 0.2, 1) 0.33s; 1042 | } 1043 | .guide { 1044 | transform: translateX(50vw) translateX(-50%); 1045 | } 1046 | .scroll-restore-message { 1047 | margin: 0; 1048 | left: 0; 1049 | transform: translateX(50vw) translateX(-50%) translateY(100%) translateY(1rem); 1050 | } 1051 | .scroll-restore-message.in { 1052 | transform: translateX(50vw) translateX(-50%); 1053 | } 1054 | .toc.in ~ .guide, .toc.in ~ .scroll-restore-message.in { 1055 | transform: translateX(29rem); 1056 | } 1057 | .toc.in ~ .scroll-restore-message { 1058 | transform: translateX(29rem) translateY(100%) translateY(1rem); 1059 | } 1060 | } 1061 | 1062 | @media screen and (max-width: 79rem) { 1063 | .guide { 1064 | margin: auto; 1065 | } 1066 | .toc-scrim { 1067 | display: block !important; 1068 | transition: opacity cubic-bezier(0.4, 0, 1, 1) 0.2s; 1069 | } 1070 | } 1071 | 1072 | @media screen and (max-width: 60rem) { 1073 | .toc-fab.scroll-out { 1074 | transform: translateY(-5rem); 1075 | } 1076 | .toc.in .toc-fab.scroll-out { 1077 | transform: translateX(0); 1078 | } 1079 | } 1080 | 1081 | @media screen and (max-width: 29rem) { 1082 | .toc-fab { 1083 | right: -1.5rem; 1084 | top: 0.5rem; 1085 | } 1086 | .toc:not(.in) .toc-fab.scroll-out { 1087 | transform: translate(2.5rem, -5rem); 1088 | } 1089 | .toc:not(.in) .toc-fab:not(.scroll-out) { 1090 | transform: translateX(2.5rem); 1091 | } 1092 | } 1093 | 1094 | @media (pointer: coarse) { 1095 | .toc main > .toc-entry { 1096 | padding-top: 0.75rem; 1097 | padding-bottom: 0.75rem; 1098 | } 1099 | .theme-switcher > .buttons > label { 1100 | padding: 0.75rem !important; 1101 | } 1102 | .btn { 1103 | padding: 0.75rem 1.5rem; 1104 | } 1105 | } 1106 | 1107 | @media print { 1108 | .toc, .toc-fab, .scroll-restore-message, .toc-scrim, .theme-switch-overlay { 1109 | display: none; 1110 | } 1111 | h1, h2, h3, h4, h5, h6 { 1112 | page-break-after: avoid; 1113 | } 1114 | } 1115 | -------------------------------------------------------------------------------- /css/index.scss: -------------------------------------------------------------------------------- 1 | @import "./_fonts.scss"; 2 | @import "./_themes.scss"; 3 | 4 | 5 | $ease-material-in-out: cubic-bezier(0.4, 0.0, 0.2, 1); 6 | $ease-material-in: cubic-bezier(0.4, 0.0, 1, 1); 7 | $ease-material-out: cubic-bezier(0.0, 0.0, 0.2, 1); 8 | 9 | 10 | // #region base 11 | 12 | // base class for things simply highlighting on hover, without sticking after touch 13 | @media (hover: hover) { 14 | %standard-hover { 15 | transition: background-color $ease-material-in-out 0.15s; 16 | 17 | &:hover { 18 | background-color: $c-bg-hover; 19 | } 20 | } 21 | } 22 | 23 | // #endregion 24 | 25 | 26 | 27 | // #region layout 28 | 29 | 30 | // layout variables 31 | $guide-max-w: 50rem; 32 | $toc-w: 24rem; 33 | $mobile-threshold: $toc-w + 5rem + $guide-max-w; 34 | $toc-fab-to-border-threshold: $toc-w + 5rem; 35 | $toc-fab-size: 3rem; 36 | $toc-fab-icon-size: 1rem; 37 | $toc-fab-scroll-out-y-offset: 5rem; 38 | 39 | * { 40 | box-sizing: border-box; 41 | } 42 | 43 | html, 44 | body { 45 | width: 100%; 46 | height: 100%; 47 | padding: 0; 48 | margin: 0; 49 | } 50 | 51 | .guide { 52 | width: 100%; 53 | max-width: $guide-max-w; 54 | padding: 0 1rem; 55 | padding-bottom: 15rem; 56 | } 57 | 58 | 59 | // #region header layout 60 | 61 | // headers use padding-top for spacing so scrolling to a section via offsetTop leaves a nice gap 62 | h1 { 63 | font-size: 2.5rem; 64 | margin: 2.5rem 0; 65 | margin-top: 6rem; 66 | 67 | > .small { 68 | margin-top: 1rem; 69 | } 70 | } 71 | 72 | h2 { 73 | font-size: 2rem; 74 | margin: 0; 75 | padding-top: 2rem; 76 | } 77 | 78 | h3 { 79 | font-size: 1.5rem; 80 | margin: 0; 81 | padding-top: 2.5rem; 82 | } 83 | 84 | h4 { 85 | font-size: 1.25rem; 86 | margin: 0; 87 | padding-top: 2rem; 88 | } 89 | 90 | // #endregion 91 | 92 | 93 | // #region spacing 94 | 95 | // spacing between 1. and 1.1. headings 96 | h2 + h3 { 97 | padding-top: 1.5rem; 98 | } 99 | 100 | // spacing between header and first section item 101 | h1, h2, h3, h4 { 102 | + p, + .snippet { 103 | margin-top: 2rem; 104 | } 105 | } 106 | 107 | // spacing between paragraphs 108 | p, blockquote { 109 | margin-top: 1.5rem; 110 | line-height: 140%; 111 | } 112 | 113 | // spacing before snippets 114 | .snippet { 115 | margin-top: 0.5rem; 116 | } 117 | 118 | // spacing between list items 119 | ul, ol { 120 | line-height: 140%; 121 | 122 | > li + li { 123 | margin-top: 0.5rem; 124 | } 125 | } 126 | 127 | // #endregion 128 | 129 | 130 | // #region toc 131 | 132 | .toc { 133 | position: fixed; 134 | top: 0; 135 | left: 0; 136 | bottom: 0; 137 | z-index: 2; 138 | width: $toc-w; 139 | max-width: calc(100vw - 3rem); 140 | 141 | } 142 | 143 | // toc position when expanded 144 | .toc.in { 145 | transform: translateX(0); 146 | } 147 | 148 | // toc position when collapsed 149 | .toc:not(.in) { 150 | transform: translateX(-100%); 151 | } 152 | 153 | // position fab 1rem from the top of the page and 1rem to the right of the guide 154 | .toc-fab { 155 | position: fixed; 156 | top: 1rem; 157 | right: -1rem - $toc-fab-size; 158 | z-index: 2; 159 | } 160 | 161 | // #endregion 162 | 163 | 164 | // #endregion 165 | 166 | 167 | 168 | // #region styling 169 | 170 | 171 | // #region toc 172 | 173 | .toc { 174 | display: flex; 175 | flex-direction: column; 176 | background-color: $c-bg; 177 | vertical-align: top; 178 | border-right: 1px solid $c-border-light; 179 | text-align: left; 180 | 181 | // class added by js to only animate after initial state was set 182 | &.transition { 183 | transition: transform $ease-material-in-out 0.33s; 184 | } 185 | 186 | > header { 187 | display: flex; 188 | align-items: center; 189 | width: 100%; 190 | border-bottom: 1px solid $c-border-light; 191 | padding: 1rem; 192 | font-family: $font-family-heading; 193 | font-size: 1.5rem; 194 | } 195 | 196 | > main { 197 | padding: 0.5rem; 198 | flex: 1; 199 | overflow-y: auto; 200 | overscroll-behavior-y: contain; 201 | 202 | > .toc-entry { 203 | @extend %standard-hover; 204 | display: block; 205 | text-decoration: none; 206 | color: $c-text-on-bg-sec; 207 | padding: 0.5rem; 208 | -webkit-tap-highlight-color: transparent; 209 | border-radius: 0.25rem; 210 | 211 | > .number { 212 | color: $c-text-on-bg-irr; 213 | } 214 | } 215 | 216 | > .toc-1 { 217 | padding-left: 1rem; 218 | } 219 | 220 | > .toc-2 { 221 | padding-left: 2rem; 222 | } 223 | } 224 | 225 | > footer { 226 | padding: 1rem; 227 | padding-top: 0.5rem; 228 | border-top: 1px solid $c-border-light; 229 | 230 | > .theme-switcher { 231 | margin-top: 0; 232 | } 233 | } 234 | } 235 | 236 | .toc-fab { 237 | width: $toc-fab-size; 238 | height: $toc-fab-size; 239 | font-size: $toc-fab-icon-size; 240 | border-radius: 1.5rem; 241 | border: 1px solid $c-border-light; 242 | color: $c-text-on-bg-pri; 243 | background-color: $c-bg; 244 | -webkit-tap-highlight-color: transparent; 245 | outline: none; 246 | cursor: pointer; 247 | overflow: hidden; 248 | transition: transform $ease-material-in-out 0.33s; 249 | 250 | // add a separate element responsible for just animating on hover 251 | // otherwise, switching the theme causes the fab bg colour to animate 252 | // and it looks very bad 253 | &:before { 254 | @extend %standard-hover; 255 | content: ""; 256 | position: absolute; 257 | top: 0; 258 | left: 0; 259 | right: 0; 260 | bottom: 0; 261 | } 262 | 263 | > .top, > .mid, > .bot { 264 | position: absolute; 265 | top: 50%; 266 | left: 50%; 267 | width: $toc-fab-icon-size; 268 | height: 0.1rem; 269 | background-color: $c-text-on-bg-pri; 270 | } 271 | 272 | > .top { 273 | transform: translate(-50%, -50%) translateY(-$toc-fab-icon-size / 3); 274 | } 275 | 276 | > .mid { 277 | transform: translate(-50%, -50%); 278 | } 279 | 280 | > .bot { 281 | transform: translate(-50%, -50%) translateY($toc-fab-icon-size / 3); 282 | } 283 | } 284 | 285 | .toc.transition .toc-fab { 286 | > .top, > .mid, > .bot { 287 | transition: transform $ease-material-in-out 0.15s; 288 | } 289 | } 290 | 291 | .toc.in .toc-fab { 292 | 293 | > .top { 294 | transform: translate(-25%, -50%) translateY(-$toc-fab-icon-size / 3) translate(-33%, $toc-fab-icon-size / 5.5) rotate(-45deg) scaleX(0.5); 295 | } 296 | 297 | > .mid { 298 | transform: translate(-50%, -50%) scaleX(0); 299 | } 300 | 301 | > .bot { 302 | transform: translate(-25%, -50%) translateY($toc-fab-icon-size / 3) translate(-33%, -$toc-fab-icon-size / 5.5) rotate(45deg) scaleX(0.5); 303 | } 304 | 305 | } 306 | 307 | .toc-scrim { 308 | position: fixed; 309 | top: 0; 310 | left: 0; 311 | bottom: 0; 312 | right: 0; 313 | z-index: 1; 314 | background-color: $c-bg; 315 | opacity: 0; 316 | pointer-events: none; 317 | display: none; 318 | -webkit-tap-highlight-color: transparent; 319 | } 320 | 321 | .toc.in ~ .toc-scrim { 322 | opacity: 0.9; 323 | pointer-events: auto; 324 | } 325 | 326 | // #endregion 327 | 328 | 329 | // #region basic component styling 330 | 331 | body { 332 | font-family: $font-family-body; 333 | font-size: $font-size-base; 334 | color: $c-text-on-bg-sec; 335 | background-color: $c-bg; 336 | line-height: 1.25; 337 | } 338 | 339 | .guide { 340 | text-align: justify; 341 | } 342 | 343 | 344 | // header styles 345 | h1, h2, h3, h4, h5, h6 { 346 | font-family: $font-family-heading; 347 | font-weight: 700; 348 | color: $c-text-on-bg-pri; 349 | text-align: left; 350 | 351 | > a { 352 | color: inherit; 353 | text-decoration: none; 354 | 355 | &:hover { 356 | text-decoration: underline; 357 | } 358 | } 359 | } 360 | 361 | // styling for that tiny text below the main header 362 | .small { 363 | font-size: $font-size-small; 364 | font-weight: normal; 365 | color: $c-text-on-bg-tet; 366 | font-family: $font-family-body; 367 | } 368 | 369 | .guide > footer { 370 | margin-top: 4rem; 371 | border-top: 1px dashed $c-border-light; 372 | } 373 | 374 | // there is only one image at the moment, at the very start 375 | .image-container { 376 | width: 100%; 377 | 378 | img { 379 | display: block; 380 | max-width: 100%; 381 | padding: 1rem; 382 | } 383 | 384 | label { 385 | margin-top: -0.5rem; 386 | display: block; 387 | text-align: center; 388 | font-size: $font-size-small; 389 | color: $c-text-on-bg-tet; 390 | font-style: italic; 391 | } 392 | } 393 | 394 | // recolour links 395 | a { 396 | color: $c-text-link; 397 | } 398 | 399 | blockquote { 400 | font-style: italic; 401 | border-left: 2px solid $c-border-light; 402 | margin-left: 0; 403 | padding-left: 1rem; 404 | padding-top: 0.5rem; 405 | padding-bottom: 0.5rem; 406 | } 407 | 408 | // #endregion 409 | 410 | 411 | // #region section counters 412 | 413 | .guide { 414 | counter-reset: h2 h3 h4; 415 | } 416 | 417 | h2 { 418 | counter-increment: h2; 419 | counter-reset: h3; 420 | 421 | &:before { 422 | content: counter(h2) ". "; 423 | } 424 | } 425 | 426 | h3 { 427 | counter-increment: h3; 428 | counter-reset: h4; 429 | 430 | &:before { 431 | content: counter(h2) "." counter(h3) ". "; 432 | } 433 | } 434 | 435 | h4 { 436 | counter-increment: h4; 437 | 438 | &:before { 439 | content: counter(h2) "." counter(h3) "." counter(h4) ". "; 440 | } 441 | } 442 | 443 | // #endregion 444 | 445 | 446 | // #region code & highlighting 447 | 448 | // inline code bits 449 | code { 450 | display: inline-block; 451 | font-family: $font-family-mono; 452 | font-size: $font-size-code; 453 | border: 1px solid $c-border-light; 454 | // background-color: $c-code-bg; 455 | border-radius: 0.25em; 456 | padding: 0 0.35em; 457 | margin: 0 0.2em; 458 | word-break: break-all; 459 | text-align: left; 460 | font-style: normal; 461 | 462 | > pre { 463 | margin: 0; 464 | white-space: pre-wrap; 465 | } 466 | } 467 | 468 | // light mode highlights using background color 469 | :root:not(.dark) { 470 | 471 | .hl { 472 | position: relative; 473 | z-index: -1; 474 | // display: inline-block; 475 | line-height: 1; 476 | white-space: pre-wrap; 477 | padding: 0.1rem; 478 | margin: -0.1rem; 479 | 480 | + .hl { 481 | margin-left: 0.1rem; 482 | } 483 | } 484 | 485 | @for $i from 1 to 7 { 486 | .hl-#{$i} { 487 | background-color: var(--c-hl-#{$i}); 488 | 489 | &.strong { 490 | background-color: var(--c-hl-#{$i}-strong); 491 | } 492 | } 493 | } 494 | 495 | .hl-before { 496 | background-color: var(--c-hl-before); 497 | } 498 | 499 | .hl-after { 500 | background-color: var(--c-hl-after); 501 | } 502 | 503 | .hl-error { 504 | background-color: var(--c-hl-error); 505 | } 506 | 507 | .hl-true { 508 | background-color: var(--c-hl-true); 509 | 510 | &.strong { 511 | background-color: var(--c-hl-true-strong); 512 | } 513 | } 514 | 515 | .hl-false { 516 | background-color: var(--c-hl-false); 517 | 518 | &.strong { 519 | background-color: var(--c-hl-false-strong); 520 | } 521 | } 522 | 523 | .hl-gray { 524 | background-color: var(--c-hl-gray); 525 | 526 | &.strong { 527 | background-color: var(--c-hl-gray-strong); 528 | } 529 | } 530 | } 531 | 532 | // dark mode highlights using text color 533 | :root.dark { 534 | 535 | @for $i from 1 to 7 { 536 | .hl-#{$i} { 537 | color: var(--c-hl-#{$i}); 538 | 539 | &.strong { 540 | color: var(--c-hl-#{$i}-strong); 541 | } 542 | } 543 | } 544 | 545 | .hl-before { 546 | color: var(--c-hl-before); 547 | } 548 | 549 | .hl-after { 550 | color: var(--c-hl-after); 551 | } 552 | 553 | .hl-error { 554 | color: var(--c-hl-error); 555 | } 556 | 557 | .hl-true { 558 | color: var(--c-hl-true); 559 | 560 | &.strong { 561 | color: var(--c-hl-true-strong); 562 | } 563 | } 564 | 565 | .hl-false { 566 | color: var(--c-hl-false); 567 | 568 | &.strong { 569 | color: var(--c-hl-false-strong); 570 | } 571 | } 572 | 573 | .hl-gray { 574 | color: var(--c-hl-gray); 575 | 576 | &.strong { 577 | color: var(--c-hl-gray-strong); 578 | } 579 | } 580 | 581 | .hl.strong { 582 | font-weight: bold; 583 | } 584 | 585 | } 586 | 587 | // #endregion 588 | 589 | 590 | // #region snippets 591 | 592 | .snippet { 593 | margin-top: 1rem; 594 | 595 | label { 596 | display: block; 597 | position: sticky; 598 | left: 0; 599 | font-size: $font-size-small; 600 | color: $c-text-on-bg-tet; 601 | line-height: 1; 602 | padding: 0.4rem 0.5rem; 603 | 604 | > .error, 605 | > .success { 606 | 607 | &:before { 608 | content: "-"; 609 | margin: 0 0.25rem; 610 | color: $c-text-on-bg-tet; 611 | } 612 | 613 | font-style: italic; 614 | } 615 | 616 | > .error { 617 | color: $c-text-error; 618 | } 619 | 620 | > .success { 621 | color: $c-text-success; 622 | } 623 | 624 | &.output { 625 | &:before { 626 | content: "Output"; 627 | } 628 | } 629 | } 630 | 631 | > code, .step > code { 632 | display: block; 633 | border: 1px solid $c-border-light; 634 | padding: 0.5rem 0.75rem; 635 | line-height: 1.5; 636 | margin: 0; 637 | overflow: hidden; 638 | position: relative; 639 | 640 | &:empty { 641 | user-select: none; 642 | 643 | &:before { 644 | content: " "; 645 | white-space: pre; 646 | } 647 | } 648 | 649 | + code { 650 | margin-top: 0.2rem; 651 | } 652 | 653 | + label { 654 | margin-top: 0.5rem; 655 | } 656 | 657 | &.copied { 658 | &:after { 659 | content: ""; 660 | background-color: $c-bg-copied; 661 | position: absolute; 662 | top: 50%; 663 | left: 50%; 664 | z-index: 1; 665 | width: 50%; 666 | height: 1000%; 667 | transform-origin: center; 668 | animation: 0.5s 1 forwards copied; 669 | animation-timing-function: $ease-material-out; 670 | 671 | @keyframes copied { 672 | $rot: 20deg; 673 | $tr: 80%; 674 | 675 | 0% { 676 | transform: translateX(-50%) translateX(Min($guide-max-w / 2, 50vw)) translateX($tr) translateY(-50%) translateX(-50%) rotate($rot); 677 | } 678 | 679 | 100% { 680 | transform: translateX(-50%) translateX(Max(-$guide-max-w / 2, -50vw)) translateX(-$tr) translateY(-50%) translateX(-50%) rotate($rot); 681 | } 682 | } 683 | } 684 | } 685 | } 686 | 687 | > .step { 688 | 689 | .arrow { 690 | display: flex; 691 | align-items: center; 692 | justify-content: start; 693 | padding: 0 0.5rem; 694 | font-size: 1.4rem; 695 | margin-top: -0.25em; 696 | margin-bottom: -0.25em; 697 | line-height: 1; 698 | 699 | &::before { 700 | content: "↓"; 701 | color: $c-text-on-bg-tet; 702 | } 703 | } 704 | 705 | + label { 706 | margin-top: 0.5rem; 707 | } 708 | } 709 | 710 | > .note { 711 | font-size: $font-size-small; 712 | color: $c-text-on-bg-tet; 713 | margin-top: 0.5rem; 714 | font-style: italic; 715 | padding: 0 0.5rem; 716 | 717 | + label { 718 | margin-top: 0.5rem; 719 | } 720 | } 721 | 722 | + .snippet { 723 | margin-top: 1.5rem; 724 | } 725 | } 726 | 727 | // #endregion 728 | 729 | 730 | // #region tables 731 | 732 | .table-container { 733 | width: calc(100% + 16px); 734 | padding: 8px; 735 | margin: 0 -8px; 736 | overflow-x: auto; 737 | } 738 | 739 | table { 740 | border-spacing: 0; 741 | border-collapse: separate; 742 | border-radius: 0.25rem; 743 | border: 1px solid $c-border-light; 744 | text-align: left; 745 | 746 | thead > tr:last-child > th { 747 | border-bottom: 1px solid $c-border-heavy; 748 | } 749 | 750 | tr:first-child { 751 | 752 | th, 753 | td { 754 | border-top: none; 755 | } 756 | } 757 | 758 | th, 759 | td { 760 | padding: 0.5rem 0.75rem; 761 | border-top: 1px solid $c-border-light; 762 | border-left: 1px solid $c-border-light; 763 | 764 | &:first-child { 765 | border-left: none; 766 | } 767 | } 768 | 769 | code { 770 | white-space: nowrap; 771 | } 772 | } 773 | 774 | #math-symbol-table { 775 | 776 | td, 777 | th { 778 | &:first-child { 779 | text-align: center; 780 | } 781 | 782 | &:nth-child(2) { 783 | width: 100%; 784 | } 785 | } 786 | } 787 | 788 | #string-mode-table { 789 | 790 | td, 791 | th { 792 | &:nth-child(1) { 793 | text-align: center; 794 | } 795 | 796 | &:nth-child(2) { 797 | width: 100%; 798 | } 799 | } 800 | } 801 | 802 | #comparison-operator-table { 803 | 804 | td, 805 | th { 806 | &:nth-child(1) { 807 | text-align: center; 808 | } 809 | 810 | &:nth-child(2) { 811 | width: 100%; 812 | } 813 | } 814 | } 815 | 816 | :root.light { 817 | 818 | #comparison-operator-table { 819 | 820 | td, 821 | th { 822 | &:nth-child(3) { 823 | background-color: var(--c-hl-true); 824 | } 825 | 826 | &:nth-child(4) { 827 | background-color: var(--c-hl-false); 828 | } 829 | } 830 | } 831 | 832 | } 833 | 834 | :root:not(.light) { 835 | #comparison-operator-table { 836 | 837 | td, 838 | th { 839 | &:nth-child(3) { 840 | color: var(--c-hl-true); 841 | } 842 | 843 | &:nth-child(4) { 844 | color: var(--c-hl-false); 845 | } 846 | } 847 | } 848 | } 849 | 850 | #logical-operator-table { 851 | 852 | td, 853 | th { 854 | width: 80px; 855 | text-align: center; 856 | 857 | &:nth-child(3), 858 | &:nth-child(4) { 859 | border-left: 1px solid $c-border-heavy; 860 | } 861 | } 862 | } 863 | 864 | #tf-token-table { 865 | tr { 866 | &:nth-child(4) { 867 | td { 868 | border-top: 1px solid $c-border-heavy; 869 | } 870 | } 871 | } 872 | 873 | td, 874 | th { 875 | &:nth-child(1) { 876 | text-align: center; 877 | } 878 | 879 | &:nth-child(2) { 880 | width: 100%; 881 | } 882 | } 883 | } 884 | 885 | // #endregion 886 | 887 | 888 | // #region theme switcher 889 | 890 | .theme-switcher { 891 | 892 | > label { 893 | display: block; 894 | font-size: $font-size-small; 895 | color: $c-text-on-bg-tet; 896 | 897 | + .buttons { 898 | margin-top: 0.5rem; 899 | } 900 | } 901 | 902 | > .buttons { 903 | display: flex; 904 | flex-direction: row; 905 | gap: 0.5rem; 906 | margin-top: 1rem; 907 | -webkit-tap-highlight-color: transparent; 908 | 909 | > label { 910 | flex: 1; 911 | padding: 0.5rem; 912 | text-align: center; 913 | position: relative; 914 | transition: background-color $ease-material-in-out 0.1s, transform $ease-material-in-out 0.33s; 915 | cursor: pointer; 916 | 917 | > input { 918 | display: none; 919 | } 920 | 921 | > span { 922 | &:before { 923 | content: ""; 924 | position: absolute; 925 | top: 0; 926 | left: 0; 927 | bottom: 0; 928 | right: 0; 929 | border-radius: 0.25rem; 930 | border: 1px solid $c-border-light; 931 | // overshoot 932 | transition: border cubic-bezier(0.87, 1.92, 0.48, 0.75) 0.15s; 933 | } 934 | } 935 | 936 | @media (hover: hover) { 937 | &:hover { 938 | background-color: $c-bg-hover; 939 | } 940 | } 941 | 942 | &:active { 943 | transition-duration: 0s; 944 | transform: scale(0.96); 945 | } 946 | 947 | > input:checked + span { 948 | &:before { 949 | border: 0.15rem solid $c-accent; 950 | } 951 | } 952 | } 953 | } 954 | } 955 | 956 | .theme-switch-overlay { 957 | position: fixed; 958 | top: 0; 959 | left: 0; 960 | width: 100vw; 961 | bottom: 0; 962 | z-index: 10; 963 | transform: translateY(-100%); 964 | opacity: 1; 965 | transition: transform $ease-material-in 0.25s; 966 | 967 | &.dark { 968 | background-color: $c-dark-bg; 969 | } 970 | 971 | &.light { 972 | background-color: $c-light-bg; 973 | } 974 | 975 | &.in { 976 | transform: translateY(0); 977 | } 978 | 979 | &.out { 980 | transform: translateY(100%); 981 | transition-timing-function: $ease-material-out; 982 | pointer-events: none; 983 | } 984 | } 985 | 986 | // #endregion 987 | 988 | 989 | // #region scroll restore 990 | 991 | .scroll-restore-message { 992 | position: fixed; 993 | bottom: 1rem; 994 | left: 1rem; 995 | right: 1rem; 996 | max-width: $guide-max-w; 997 | margin: auto; 998 | display: flex; 999 | flex-direction: row; 1000 | justify-content: space-between; 1001 | flex-wrap: wrap; 1002 | align-items: center; 1003 | border: 1px solid $c-border-heavy; 1004 | background-color: $c-bg; 1005 | border-radius: 0.25rem; 1006 | transform: translateY(100%) translateY(1rem); 1007 | transition: transform $ease-material-in-out 0.33s; 1008 | -webkit-tap-highlight-color: transparent; 1009 | cursor: pointer; 1010 | 1011 | > span { 1012 | display: inline-block; 1013 | padding: 1rem; 1014 | } 1015 | 1016 | > .btn-dismiss { 1017 | @extend %standard-hover; 1018 | background: none; 1019 | border: none; 1020 | outline: none; 1021 | margin-left: auto; 1022 | font-family: $font-family-body; 1023 | font-size: $font-size-base; 1024 | color: $c-text-on-bg-tet; 1025 | -webkit-tap-highlight-color: transparent; 1026 | padding: 1rem; 1027 | cursor: pointer; 1028 | } 1029 | 1030 | &.in { 1031 | transform: translateY(0); 1032 | } 1033 | } 1034 | 1035 | // #endregion 1036 | 1037 | 1038 | // #region buttons 1039 | 1040 | .btn { 1041 | @extend %standard-hover; 1042 | background: none; 1043 | border: 1px solid $c-border-heavy; 1044 | border-radius: 0.25rem; 1045 | outline: none; 1046 | font-family: $font-family-body; 1047 | font-size: $font-size-base; 1048 | color: $c-text-on-bg-sec; 1049 | -webkit-tap-highlight-color: transparent; 1050 | padding: 0.5rem 1rem; 1051 | cursor: pointer; 1052 | } 1053 | 1054 | // #endregion 1055 | 1056 | 1057 | // #endregion 1058 | 1059 | 1060 | 1061 | // #region misc 1062 | 1063 | .nowrap { 1064 | white-space: nowrap; 1065 | } 1066 | 1067 | .no-display { 1068 | display: none; 1069 | } 1070 | 1071 | .text-right { 1072 | text-align: right; 1073 | } 1074 | 1075 | .text-center { 1076 | text-align: center; 1077 | } 1078 | 1079 | // #endregion 1080 | 1081 | 1082 | // #region responsiveness 1083 | 1084 | // larger than 1600px at 16px font size (max desktop) 1085 | @media screen and (min-width: 100rem) { 1086 | 1087 | // center the guide on the screen 1088 | .guide { 1089 | margin: auto; 1090 | } 1091 | 1092 | } 1093 | 1094 | // less than 1600px at 16px, but can fit both toc and the guide side by side 1095 | @media screen and (min-width: $mobile-threshold) and (max-width: 100rem) { 1096 | 1097 | .toc.transition ~ .guide { 1098 | transition: transform $ease-material-in-out 0.33s; 1099 | } 1100 | 1101 | // center the guide on the screen by default 1102 | .guide { 1103 | transform: translateX(50vw) translateX(-50%); 1104 | } 1105 | 1106 | .scroll-restore-message { 1107 | margin: 0; 1108 | left: 0; 1109 | transform: translateX(50vw) translateX(-50%) translateY(100%) translateY(1rem); 1110 | 1111 | &.in { 1112 | transform: translateX(50vw) translateX(-50%); 1113 | } 1114 | } 1115 | 1116 | // position the guide to the right of the toc when it is expanded 1117 | .toc.in { 1118 | 1119 | ~ .guide, ~ .scroll-restore-message.in { 1120 | transform: translateX($toc-w + 5rem); 1121 | } 1122 | 1123 | ~ .scroll-restore-message { 1124 | transform: translateX($toc-w + 5rem) translateY(100%) translateY(1rem); 1125 | } 1126 | } 1127 | 1128 | } 1129 | 1130 | // mobile (toc is overlaid as a left drawer) 1131 | @media screen and (max-width: $mobile-threshold) { 1132 | 1133 | // center the guide on the screen 1134 | .guide { 1135 | margin: auto; 1136 | } 1137 | 1138 | // stop no-displaying the toc scrim 1139 | .toc-scrim { 1140 | display: block !important; 1141 | transition: opacity $ease-material-in 0.2s; 1142 | } 1143 | } 1144 | 1145 | // the fab will start to overlap the content at this point 1146 | @media screen and (max-width: ($guide-max-w + 10rem)) { 1147 | 1148 | // js applies this class to hide the fab when scrolling down 1149 | .toc-fab { 1150 | &.scroll-out { 1151 | transform: translateY(-5rem); 1152 | } 1153 | } 1154 | 1155 | // if the toc is expanded, override the fab being hidden due to scrolling down 1156 | .toc.in .toc-fab.scroll-out { 1157 | transform: translateX(0); 1158 | } 1159 | 1160 | } 1161 | 1162 | // the drawer will start to decrease in width at this point 1163 | @media screen and (max-width: $toc-fab-to-border-threshold) { 1164 | 1165 | // move fab so it overlaps the right border of the toc 1166 | .toc-fab { 1167 | right: -1.5rem; 1168 | top: 0.5rem; 1169 | } 1170 | 1171 | // if the toc is collapsed, 1172 | .toc:not(.in) .toc-fab { 1173 | 1174 | // hide the fab when scrolling up and also move it to the right so it no longer overlaps the border of the toc 1175 | &.scroll-out { 1176 | transform: translate(2.5rem, -$toc-fab-scroll-out-y-offset); 1177 | } 1178 | 1179 | // otherwise only move it to the right so it no longer overlaps the border of the toc 1180 | &:not(.scroll-out) { 1181 | transform: translateX(2.5rem); 1182 | } 1183 | } 1184 | 1185 | } 1186 | 1187 | // expand touch targets for touchscreens 1188 | @media (pointer: coarse) { 1189 | $p: 0.75rem; 1190 | 1191 | .toc main > .toc-entry { 1192 | padding-top: $p; 1193 | padding-bottom: $p; 1194 | } 1195 | 1196 | .theme-switcher { 1197 | > .buttons { 1198 | > label { 1199 | padding: $p !important; 1200 | } 1201 | } 1202 | } 1203 | 1204 | .btn { 1205 | padding: $p 1.5rem; 1206 | } 1207 | } 1208 | 1209 | @media print { 1210 | .toc, .toc-fab, .scroll-restore-message, .toc-scrim, .theme-switch-overlay { 1211 | display: none; 1212 | } 1213 | 1214 | h1, h2, h3, h4, h5, h6 { 1215 | page-break-after: avoid; 1216 | } 1217 | } 1218 | 1219 | // #endregion 1220 | --------------------------------------------------------------------------------