├── .editorconfig ├── .github └── workflows │ └── zola.yml ├── .gitignore ├── LICENSE ├── README.md ├── config.toml ├── content ├── _index.md ├── about.md ├── posts │ ├── _index.md │ ├── configuration.md │ ├── markdown.md │ ├── math-symbol.md │ ├── mermaid.md │ └── shortcode.md └── projects │ ├── _index.md │ ├── project-1.jpg │ ├── project_1.md │ ├── project_2.md │ ├── project_3.md │ ├── project_4.md │ └── project_5.md ├── sass ├── fonts.scss ├── main.scss ├── parts │ ├── _cards.scss │ ├── _code.scss │ ├── _header.scss │ ├── _image.scss │ ├── _mermaid.scss │ ├── _misc.scss │ ├── _note.scss │ ├── _search.scss │ ├── _table.scss │ ├── _tags.scss │ └── _toc.scss └── theme │ ├── dark.scss │ └── light.scss ├── screenshot-dark.png ├── screenshot.png ├── shell.nix ├── static ├── fonts │ ├── JetbrainsMono │ │ ├── JetBrainsMono-Bold.ttf │ │ ├── JetBrainsMono-BoldItalic.ttf │ │ ├── JetBrainsMono-ExtraBold.ttf │ │ ├── JetBrainsMono-ExtraBoldItalic.ttf │ │ ├── JetBrainsMono-ExtraLight.ttf │ │ ├── JetBrainsMono-ExtraLightItalic.ttf │ │ ├── JetBrainsMono-Italic.ttf │ │ ├── JetBrainsMono-Light.ttf │ │ ├── JetBrainsMono-LightItalic.ttf │ │ ├── JetBrainsMono-Medium.ttf │ │ ├── JetBrainsMono-MediumItalic.ttf │ │ ├── JetBrainsMono-Regular.ttf │ │ ├── JetBrainsMono-SemiBold.ttf │ │ ├── JetBrainsMono-SemiBoldItalic.ttf │ │ ├── JetBrainsMono-Thin.ttf │ │ └── JetBrainsMono-ThinItalic.ttf │ └── SpaceGrotesk │ │ ├── SpaceGrotesk-Bold.ttf │ │ ├── SpaceGrotesk-Light.ttf │ │ ├── SpaceGrotesk-Medium.ttf │ │ ├── SpaceGrotesk-Regular.ttf │ │ └── SpaceGrotesk-SemiBold.ttf ├── icons │ ├── moon.svg │ ├── search.svg │ ├── social │ │ ├── LICENSE │ │ ├── apple.svg │ │ ├── bitcoin.svg │ │ ├── bluesky.svg │ │ ├── deviantart.svg │ │ ├── diaspora.svg │ │ ├── discord.svg │ │ ├── discourse.svg │ │ ├── email.svg │ │ ├── ethereum.svg │ │ ├── etsy.svg │ │ ├── facebook.svg │ │ ├── fediverse.svg │ │ ├── github.svg │ │ ├── gitlab.svg │ │ ├── google-scholar.svg │ │ ├── google.svg │ │ ├── hacker-news.svg │ │ ├── instagram.svg │ │ ├── linkedin.svg │ │ ├── mastodon.svg │ │ ├── matrix.svg │ │ ├── orcid.svg │ │ ├── paypal.svg │ │ ├── pinterest.svg │ │ ├── quora.svg │ │ ├── reddit.svg │ │ ├── rss.svg │ │ ├── skype.svg │ │ ├── slack.svg │ │ ├── snapchat.svg │ │ ├── soundcloud.svg │ │ ├── spotify.svg │ │ ├── stack-exchange.svg │ │ ├── stack-overflow.svg │ │ ├── steam.svg │ │ ├── telegram.svg │ │ ├── twitter.svg │ │ ├── vimeo.svg │ │ ├── whatsapp.svg │ │ ├── x-twitter.svg │ │ └── youtube.svg │ └── sun.svg └── js │ ├── codeblock.js │ ├── count.js │ ├── imamu.js │ ├── main.js │ ├── mermaid.js │ ├── note.js │ ├── searchElasticlunr.js │ ├── searchElasticlunr.min.js │ ├── themetoggle.js │ └── toc.js ├── templates ├── 404.html ├── _giscus_script.html ├── base.html ├── cards.html ├── homepage.html ├── index.html ├── macros │ └── macros.html ├── page.html ├── partials │ ├── header.html │ └── nav.html ├── section.html ├── shortcodes │ ├── mermaid.html │ └── note.html ├── taxonomy_list.html └── taxonomy_single.html ├── theme.toml └── treefmt.toml /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.css] 2 | indent_style = space 3 | indent_size = 2 -------------------------------------------------------------------------------- /.github/workflows/zola.yml: -------------------------------------------------------------------------------- 1 | name: Zola 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | workflow_dispatch: 7 | 8 | # Allow one concurrent deployment 9 | concurrency: 10 | group: "pages" 11 | cancel-in-progress: true 12 | 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Build and deploy 22 | uses: shalzz/zola-deploy-action@master 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 not-matthias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apollo 2 | 3 | Modern and minimalistic blog theme powered by [Zola](https://getzola.org). See a live preview [here](https://not-matthias.github.io/apollo). 4 | 5 | Named after the greek god of knowledge, wisdom and intellect 6 | 7 |
8 | Dark theme 9 | 10 | ![blog-dark](./screenshot-dark.png) 11 | 12 |
13 | 14 |
15 | Light theme 16 | 17 | ![blog-light](./screenshot.png) 18 | 19 |
20 | 21 | ## Features 22 | 23 | - [x] Pagination 24 | - [x] Themes (light, dark, auto) 25 | - [x] Projects page 26 | - [x] Analytics using [GoatCounter](https://www.goatcounter.com/) / [Umami](https://umami.is/) 27 | - [x] Social Links 28 | - [x] MathJax Rendering 29 | - [x] Taxonomies 30 | - [x] Meta Tags For Individual Pages 31 | - [x] Custom homepage 32 | - [x] Comments 33 | - [x] Search 34 | 35 | ## Installation 36 | 37 | 1. Download the theme 38 | 39 | ``` 40 | git submodule add https://github.com/not-matthias/apollo themes/apollo 41 | ``` 42 | 43 | 2. Add the following to the top of your `config.toml` 44 | 45 | ```toml 46 | theme = "apollo" 47 | taxonomies = [{ name = "tags" }] 48 | 49 | [extra] 50 | theme = "auto" 51 | socials = [ 52 | # Configure socials here 53 | ] 54 | menu = [ 55 | # Configure menu bar here 56 | ] 57 | 58 | # See this for more options: https://github.com/not-matthias/apollo/blob/main/config.toml#L14 59 | ``` 60 | 61 | 3. Copy the example content 62 | 63 | ``` 64 | cp -r themes/apollo/content/* content/ 65 | ``` 66 | 67 | ## Configuration 68 | 69 | You can find all the configuration options [here](./content/posts/configuration.md) 70 | 71 | ## References 72 | 73 | This theme is based on [archie-zola](https://github.com/XXXMrG/archie-zola/). 74 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | base_url = "https://not-matthias.github.io/apollo/" 2 | title = "not-matthias" 3 | description = "This is an example description" 4 | build_search_index = true 5 | generate_feeds = true 6 | compile_sass = true 7 | minify_html = true 8 | taxonomies = [{ name = "tags" }] 9 | 10 | [search] 11 | include_title = true 12 | include_description = true 13 | include_path = true 14 | include_content = true 15 | index_format = "elasticlunr_json" 16 | 17 | [markdown] 18 | highlight_code = true 19 | highlight_theme = "ayu-light" 20 | 21 | [extra] 22 | toc = true 23 | use_cdn = false 24 | favicon = "/icon/favicon.png" 25 | theme = "toggle" # light, dark, auto, toggle 26 | fancy_code = true 27 | dynamic_note = true # a note that can be toggled 28 | mathjax = true 29 | mathjax_dollar_inline_enable = true 30 | repo_url = "https://github.com/not-matthias/apollo/tree/main/content/" 31 | 32 | menu = [ 33 | { name = "/posts", url = "/posts", weight = 1 }, 34 | { name = "/projects", url = "/projects", weight = 2 }, 35 | { name = "/about", url = "/about", weight = 3 }, 36 | { name = "/tags", url = "/tags", weight = 4 }, 37 | ] 38 | 39 | socials = [ 40 | { name = "twitter", url = "https://twitter.com/not_matthias", icon = "twitter" }, 41 | { name = "github", url = "https://github.com/not-matthias/", icon = "github" }, 42 | ] 43 | 44 | stylesheets = [ 45 | # "custom.css" # at /static/custom.css 46 | ] 47 | 48 | [extra.analytics] 49 | enabled = false 50 | 51 | [extra.analytics.goatcounter] 52 | user = "your_user" 53 | host = "example.com" # default= goatcounter.com 54 | 55 | [extra.analytics.umami] 56 | website_id = "43929cd1-1e83...." 57 | host_url = "https://stats.mywebsite.com" # default: https://api-gateway.umami.dev/ 58 | -------------------------------------------------------------------------------- /content/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title= "Apollo Theme" 3 | template = "homepage.html" 4 | +++ 5 | 6 | Thanks for checking out this theme! 7 | 8 | Checkout all the [options you can configure](./posts/configuration) and the [example pages](./tags/example/). 9 | -------------------------------------------------------------------------------- /content/about.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "About" 3 | path = "about" 4 | +++ 5 | -------------------------------------------------------------------------------- /content/posts/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | paginate_by = 7 3 | title = "Posts" 4 | sort_by = "date" 5 | 6 | insert_anchor_links = "heading" 7 | 8 | [extra] 9 | comment = true 10 | +++ 11 | -------------------------------------------------------------------------------- /content/posts/configuration.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Configuring Apollo" 3 | date = "2024-07-09" 4 | 5 | [taxonomies] 6 | tags=["documentation"] 7 | 8 | [extra] 9 | repo_view = true 10 | comment = true 11 | +++ 12 | 13 | # Site Configuration 14 | 15 | ## Search (`build_search_index`) 16 | 17 | Enables or disables the search functionality for your blog. 18 | 19 | - Type: Boolean 20 | - Default: false 21 | - Usage: `build_search_index = false` 22 | 23 | When enabled, a search index will be generated for your blog, allowing visitors to search for specific content. 24 | Additionally, a search button will be displayed in the navigation bar. 25 | 26 | Configure the search like this: 27 | 28 | ```toml 29 | build_search_index = true 30 | 31 | [search] 32 | include_title = true 33 | include_description = true 34 | include_path = true 35 | include_content = true 36 | index_format = "elasticlunr_json" 37 | ``` 38 | 39 | ## Theme Mode (`theme`) 40 | 41 | Sets the color theme for your blog. 42 | 43 | - Type: String 44 | - Options: "light", "dark", "auto", "toggle" 45 | - Default: "toggle" 46 | - Usage: `theme = "toggle"` 47 | 48 | The "toggle" option allows users to switch between light and dark modes, while "auto" typically follows the user's system preferences. 49 | 50 | ## Menu 51 | 52 | Defines the navigation menu items for your blog. 53 | 54 | - Type: Array of objects 55 | - Default: [] 56 | - Usage: 57 | ```toml 58 | menu = [ 59 | { name = "/posts", url = "/posts", weight = 1 }, 60 | { name = "/projects", url = "/projects", weight = 2 }, 61 | { name = "/about", url = "/about", weight = 3 }, 62 | { name = "/tags", url = "/tags", weight = 4 }, 63 | ] 64 | ``` 65 | 66 | ## Logo 67 | 68 | Defines the site logo image file. 69 | 70 | - Type: String 71 | - Usage: 72 | ```toml 73 | logo = "site_logo.svg" 74 | ``` 75 | 76 | ## Socials 77 | 78 | Defines the social media links. 79 | 80 | - Type: Array of objects 81 | - Default: [] 82 | - Usage: 83 | ```toml 84 | socials = [ 85 | { name = "twitter", url = "https://twitter.com/not_matthias", icon = "twitter" }, 86 | { name = "github", url = "https://github.com/not-matthias/", icon = "github" }, 87 | ] 88 | ``` 89 | 90 | ## Table of Contents (`toc`) 91 | 92 | Enables or disables the table of contents for posts. 93 | 94 | - Type: Boolean 95 | - Default: true 96 | - Usage: `toc = true` 97 | 98 | When enabled, a table of contents will be generated for posts, making it easier for readers to navigate through longer articles. 99 | 100 | Note: This feature adds additional JavaScript to your site. 101 | 102 | ## CDN Usage (`use_cdn`) 103 | 104 | Determines whether to use a Content Delivery Network (CDN) for assets. 105 | 106 | - Type: Boolean 107 | - Default: false 108 | - Usage: `use_cdn = false` 109 | 110 | When set to true, the theme will attempt to load assets from a CDN, which can improve loading times for visitors from different geographic locations. 111 | 112 | ## Favicon (`favicon`) 113 | 114 | Specifies the path to the favicon image for your blog. 115 | 116 | - Type: String 117 | - Default: "/icon/favicon.png" 118 | - Usage: `favicon = "/icon/favicon.png"` 119 | 120 | This sets the small icon that appears in the browser tab for your website. 121 | 122 | ## Custom Stylesheets (`stylesheets`) 123 | 124 | Allows you to add custom stylesheets to your blog. 125 | 126 | - Type: Array of files located in the `static` directory 127 | - Default: [] 128 | - Usage: 129 | ```toml 130 | stylesheets = [ 131 | "custom.css", # static/custom.css 132 | "/css/another.css" # static/css/another.css 133 | ] 134 | ``` 135 | 136 | ## Fancy Code Styling (`fancy_code`) 137 | 138 | Enables enhanced styling for code blocks. 139 | 140 | - Type: Boolean 141 | - Default: true 142 | - Usage: `fancy_code = true` 143 | 144 | This option adds the language label and a copy button. 145 | 146 | ## Dynamic Notes (`dynamic_note`) 147 | 148 | Allows for the creation of togglable note sections in your content. 149 | 150 | - Type: Boolean 151 | - Default: true 152 | - Usage: `dynamic_note = true` 153 | 154 | When enabled, you can create expandable/collapsible note sections in your blog posts. 155 | 156 | ## Anchor Links 157 | 158 | You can add anchor links by adding the following to your `_index.md`: 159 | 160 | ```toml 161 | insert_anchor_links = "heading" 162 | ``` 163 | 164 | ## Analytics 165 | 166 | Enable or disable analytics tracking: 167 | 168 | ```toml 169 | [extra.analytics] 170 | enabled = false 171 | ``` 172 | 173 | After enabling analytics, configure GoatCounter or Umami. 174 | 175 | ### GoatCounter 176 | 177 | Configure GoatCounter analytics: 178 | 179 | ```toml 180 | [extra.analytics.goatcounter] 181 | user = "your_user" # Your GoatCounter username 182 | host = "example.com" # Optional: Custom host 183 | ``` 184 | 185 | ### Umami Analytics 186 | 187 | Configure Umami analytics: 188 | 189 | ```toml 190 | [extra.analytics.umami] 191 | website_id = "43929cd1-1e83...." # Your Umami website ID 192 | host_url = "https://stats.mywebsite.com" # Optional: Custom host URL 193 | ``` 194 | 195 | --- 196 | 197 | # Page configuration 198 | 199 | ## Source code (`repo_view`) 200 | 201 | Do you want to link to the source code of your blog post? You can turn on the `repo_view` inside the `[extra]` section of your blog post. 202 | 203 | ```toml 204 | [extra] 205 | repo_view = true 206 | repo_url = "https://github.com/not-matthias/apollo/tree/main/content" # Alternatively add the repo here 207 | ``` 208 | 209 | The `repo_url` can be set in the `[extra]` section or in your `config.toml`. 210 | 211 | ## Comments (`comment`) 212 | 213 | Enables or disables the comment system for posts. 214 | 215 | - Type: Boolean 216 | - Default: false 217 | - Usage: `comment = false` 218 | 219 | After making `comment = true` in `[extra]` section of you post, save your script from [Giscus](https://giscus.app) to `templates/_giscus_script.html`. 220 | When enabled, this allows readers to leave comments on your blog posts. This feature has to be set for each individual post and is not supported at higher levels. 221 | 222 | Example configuration in [content/posts/configuration.md](https://github.com/not-matthias/apollo/blob/main/content/posts/configuration.md): 223 | 224 | ```toml 225 | +++ 226 | title = "Configuring Apollo" 227 | 228 | [extra] 229 | comment = true 230 | +++ 231 | ``` 232 | 233 | Comments via [utterances](https://utteranc.es) can be configured in `template/_giscus_script.html` like this: 234 | 235 | ```html 236 | 243 | ``` 244 | -------------------------------------------------------------------------------- /content/posts/markdown.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Markdown Test" 3 | date = "2022-01-01" 4 | updated = "2022-05-01" 5 | 6 | [taxonomies] 7 | tags=["example"] 8 | 9 | [extra] 10 | comment = true 11 | +++ 12 | 13 | # H1 14 | 15 | ## H2 16 | 17 | ### H3 18 | 19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet sagittis id consectetur purus ut. In pellentesque massa placerat duis ultricies. Neque laoreet suspendisse interdum consectetur libero id. Justo nec ultrices dui sapien eget mi proin. Nunc consequat interdum varius sit amet mattis vulputate. Sollicitudin tempor id eu nisl nunc mi ipsum. Non odio euismod lacinia at quis. Sit amet nisl suscipit adipiscing. Amet mattis vulputate enim nulla aliquet porttitor lacus luctus accumsan. Sit amet consectetur adipiscing elit pellentesque habitant. Ac placerat vestibulum lectus mauris. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. [Google](https://www.google.com) 20 | 21 | ![Markdown Logo](https://markdown-here.com/img/icon256.png) 22 | 23 | ## Code Block 24 | 25 | ```rust 26 | fn main() { 27 | println!("Hello World"); 28 | } 29 | ``` 30 | 31 | ```rust,hl_lines=2,linenos 32 | fn main() { 33 | println!("Hello World"); 34 | } 35 | ``` 36 | 37 | ## Ordered List 38 | 39 | 1. First item 40 | 2. Second item 41 | 3. Third item 42 | 43 | ## Unordered List 44 | 45 | - List item 46 | - Another item 47 | - And another item 48 | 49 | ## Nested list 50 | 51 | - Fruit 52 | - Apple 53 | - Orange 54 | - Banana 55 | - Dairy 56 | - Milk 57 | - Cheese 58 | 59 | ## Quote 60 | 61 | > Two things are infinite: the universe and human stupidity; and I'm not sure about the 62 | > universe.
63 | > — Albert Einstein 64 | 65 | ## Table Inline Markdown 66 | 67 | | Italics | Bold | Code | StrikeThrough | 68 | | --------- | -------- | ------ | ----------------- | 69 | | _italics_ | **bold** | `code` | ~~strikethrough~~ | 70 | 71 | ## Foldable Text 72 | 73 |
74 | Title 1 75 |

IT'S A SECRET TO EVERYBODY.

76 |
77 | 78 |
79 | Title 2 80 |

Stay awhile, and listen!

81 |
82 | 83 | ## Code tags 84 | 85 | Lorem ipsum `dolor` sit amet, `consectetur adipiscing` elit. 86 | `Lorem ipsum dolor sit amet, consectetur adipiscing elit.` 87 | -------------------------------------------------------------------------------- /content/posts/math-symbol.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Math Symbol Example" 3 | date = "2023-01-06" 4 | 5 | [taxonomies] 6 | tags=["example"] 7 | 8 | [extra] 9 | comment = true 10 | +++ 11 | 12 | Note: This requires the `mathjax` and `mathjax_dollar_inline_enable` option set to `true` in `[extra]` section. 13 | 14 | # Inline Math 15 | 16 | - $(a+b)^2$ = $a^2 + 2ab + b^2$ 17 | - A polynomial P of degree d over $\mathbb{F}_p$ is an expression of the form 18 | $P(s) = a_0 + a_1 . s + a_2 . s^2 + ... + a_d . s^d$ for some 19 | $a_0,..,a_d \in \mathbb{F}_p$ 20 | 21 | # Displayed Math 22 | 23 | $$ 24 | p := (\sum_{k∈I}{c_k.v_k} + \delta_v.t(x))·(\sum_{k∈I}{c_k.w_k} + \delta_w.t(x)) − (\sum_{k∈I}{c_k.y_k} + \delta_y.t(x)) 25 | $$ 26 | -------------------------------------------------------------------------------- /content/posts/mermaid.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Mermaid Example" 3 | date = "2024-12-26" 4 | 5 | [taxonomies] 6 | tags=["example"] 7 | 8 | [extra] 9 | comment = true 10 | +++ 11 | 12 | This Theme supports [mermaid](https://mermaid.js.org/) markdown diagram rendering. 13 | 14 | To use mermaid diagrams in your posts, see the example in the raw markdown code. 15 | https://raw.githubusercontent.com/not-matthias/apollo/refs/heads/main/content/posts/mermaid.md 16 | 17 | ## Rendered Example 18 | 19 | {% mermaid() %} 20 | graph LR 21 | A[Start] --> B[Initialize] 22 | B --> C[Processing] 23 | C --> D[Complete] 24 | D --> E[Success] 25 | 26 | style A fill:#f9f,stroke:#333 27 | style E fill:#9f9,stroke:#333 28 | {% end %} 29 | -------------------------------------------------------------------------------- /content/posts/shortcode.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Shortcode Example" 3 | date = "2024-06-14" 4 | 5 | [taxonomies] 6 | tags=["example"] 7 | 8 | [extra] 9 | comment = true 10 | +++ 11 | 12 | 13 | ## Note 14 | 15 | Here is an example of the `note` shortcode: 16 | 17 | This one is static! 18 | {{ note(header="Note!", body="This blog assumes basic terminal maturity") }} 19 | 20 | This one is clickable! 21 | {{ note(clickable=true, hidden = true, header="Quiz!", body="The answer to the quiz!") }} 22 | 23 | 24 | Syntax: 25 | ``` 26 | {{/* note(header="Note!", body="This blog assumes basic terminal maturity") */}} 27 | {{/* note(clickable=true, hidden = true, header="Quiz!", body="The answer to the quiz!") */}} 28 | ``` 29 | 30 | You can also use some HTML in the text: 31 | {{ note(header="Note!", body="

This blog assumes basic terminal maturity

") }} 32 | 33 | 34 | Literal shortcode: 35 | ``` 36 | {{/* note(header="Note!", body="

This blog assumes basic terminal maturity

") */}} 37 | ``` 38 | 39 | Pretty cool, right? 40 | 41 | Finally, you can do something like this (hopefully): 42 | 43 | {% note(clickable=true, header="Quiz!") %} 44 | 45 | # Hello this is markdown inside a note shortcode 46 | 47 | ```rust 48 | fn main() { 49 | println!("Hello World"); 50 | } 51 | ``` 52 | 53 | We can't call another shortcode inside a shortcode, but this is good enough. 54 | 55 | {% end %} 56 | 57 | Here is the raw markdown: 58 | 59 | ```markdown 60 | {{/* note(clickable=true, header="Quiz!") */}} 61 | 62 | # Hello this is markdown inside a note shortcode 63 | 64 | \`\`\`rust 65 | fn main() { 66 | println!("Hello World"); 67 | } 68 | \`\`\` 69 | 70 | We can't call another shortcode inside a shortcode, but this is good enough. 71 | 72 | {{/* end */}} 73 | ``` 74 | 75 | Finally, we have center 76 | {{ note(center=true, header="Centered Text", body="This is centered text") }} 77 | 78 | ```markdown 79 | {{/* note(center=true, header="Centered Text", body="This is centered text") */}} 80 | ``` 81 | It works good enough for me! 82 | -------------------------------------------------------------------------------- /content/projects/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Projects" 3 | sort_by = "weight" 4 | template = "cards.html" 5 | +++ 6 | -------------------------------------------------------------------------------- /content/projects/project-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/content/projects/project-1.jpg -------------------------------------------------------------------------------- /content/projects/project_1.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Apollo" 3 | description = "Modern and minimalistic blog theme." 4 | weight = 1 5 | 6 | [extra] 7 | local_image = "/projects/project-1.jpg" 8 | link_to = "https://github.com/not-matthias/apollo" 9 | +++ 10 | 11 | Example project page 12 | -------------------------------------------------------------------------------- /content/projects/project_2.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Project 2" 3 | description = "Example description" 4 | weight = 1 5 | 6 | [extra] 7 | # You can also crop the image in the url by adjusting w=/h= 8 | remote_image = "https://images.unsplash.com/photo-1523821741446-edb2b68bb7a0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80" 9 | +++ 10 | 11 | Example project page 12 | -------------------------------------------------------------------------------- /content/projects/project_3.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Project 3" 3 | description = "Example description" 4 | weight = 1 5 | 6 | [extra] 7 | remote_image = "https://images.unsplash.com/photo-1462556791646-c201b8241a94?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1465&q=80" 8 | +++ 9 | 10 | Example project page 11 | -------------------------------------------------------------------------------- /content/projects/project_4.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Project 4" 3 | description = "Example description with a lot of words but without any meaning. Why use lorem ipsum when you can just write a lot of text that has no underlying meaning?" 4 | weight = 1 5 | 6 | [extra] 7 | remote_image = "https://images.unsplash.com/photo-1620121692029-d088224ddc74?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80" 8 | +++ 9 | 10 | Example project page 11 | -------------------------------------------------------------------------------- /content/projects/project_5.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Project 4" 3 | description = "Example description" 4 | weight = 1 5 | +++ 6 | 7 | Example project page 8 | -------------------------------------------------------------------------------- /sass/fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Jetbrains Mono"; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url("fonts/JetbrainsMono/JetBrainsMono-Regular.ttf"), local("ttf"); 6 | font-display: swap; 7 | } 8 | 9 | @font-face { 10 | font-family: "Jetbrains Mono"; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: url("fonts/JetbrainsMono/JetBrainsMono-Bold.ttf"), local("ttf"); 14 | font-display: swap; 15 | } 16 | 17 | @font-face { 18 | font-family: "Space Grotesk"; 19 | font-style: normal; 20 | font-weight: 400; 21 | src: url("fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf"), local("ttf"); 22 | font-display: swap; 23 | } 24 | 25 | @font-face { 26 | font-family: "Space Grotesk"; 27 | font-style: normal; 28 | font-weight: 700; 29 | src: url("fonts/SpaceGrotesk/SpaceGrotesk-Bold.ttf"), local("ttf"); 30 | font-display: swap; 31 | } 32 | -------------------------------------------------------------------------------- /sass/main.scss: -------------------------------------------------------------------------------- 1 | @import "parts/_cards.scss"; 2 | @import "parts/_code.scss"; 3 | @import "parts/_header.scss"; 4 | @import "parts/_image.scss"; 5 | @import "parts/_toc.scss"; 6 | @import "parts/_note.scss"; 7 | @import "parts/_misc.scss"; 8 | @import "parts/_table.scss"; 9 | @import "parts/_tags.scss"; 10 | @import "parts/_mermaid.scss"; 11 | @import "parts/_search.scss"; 12 | 13 | :root { 14 | /* Used for: block comment, hr, ... */ 15 | --border-color: var(--border-color); 16 | 17 | /* Fonts */ 18 | --text-font: "Jetbrains Mono"; 19 | --header-font: "Space Grotesk", "Helvetica", sans-serif; 20 | --code-font: "Jetbrains Mono"; 21 | } 22 | 23 | html { 24 | background-color: var(--bg-0); 25 | color: var(--text-0); 26 | font-family: var(--text-font); 27 | line-height: 1.6em; 28 | } 29 | 30 | .content { 31 | max-width: 1000px; 32 | margin: 0 auto; 33 | padding: 0 24px; 34 | word-wrap: break-word; 35 | } 36 | 37 | @media all and (min-width: 640px) { 38 | html { 39 | font-size: 16.5px; 40 | } 41 | } 42 | 43 | @media all and (min-width: 720px) { 44 | html { 45 | font-size: 17px; 46 | } 47 | } 48 | 49 | @media all and (min-width: 960px) { 50 | html { 51 | font-size: 18px; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sass/parts/_cards.scss: -------------------------------------------------------------------------------- 1 | .cards { 2 | display: grid; 3 | grid-template-rows: auto; 4 | gap: 24px; 5 | padding: 12px 0; 6 | } 7 | 8 | @media all and (min-width: 640px) { 9 | .cards { 10 | grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); 11 | } 12 | } 13 | 14 | @media all and (max-width: 640px) { 15 | .cards { 16 | grid-template-columns: repeat(auto-fill, 1fr); 17 | } 18 | } 19 | 20 | .card { 21 | min-height: 100px; 22 | background: var(--bg-1); 23 | border: 2px solid var(--border-color); 24 | border-radius: 10px; 25 | overflow: hidden; 26 | } 27 | 28 | .card-info { 29 | padding: 0 24px 24px 24px; 30 | } 31 | 32 | .card-title { 33 | margin-top: 0.7em; 34 | } 35 | 36 | .card-image { 37 | border: unset; 38 | width: 100%; 39 | } 40 | 41 | .card-image-placeholder { 42 | height: 12px; 43 | width: 100%; 44 | } 45 | 46 | .card-description { 47 | margin-top: 0.5em; 48 | overflow: hidden; 49 | } 50 | 51 | @media all and (max-width: 720px) { 52 | .cards { 53 | gap: 18px; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sass/parts/_code.scss: -------------------------------------------------------------------------------- 1 | // Define base colors and fonts for light and dark themes 2 | :root { 3 | --code-font: "Courier New", monospace; 4 | --bg-primary: var(--bg-1); 5 | --text-color: var(--text-0); // Color of the code text 6 | --label-color: #f0f0f0; // Text color of the label 7 | 8 | --hightlight-color: #f0f0f0; 9 | } 10 | 11 | :root.dark { 12 | --hightlight-color: #204e8a; 13 | } 14 | 15 | // Define language colors map 16 | $language-colors: ( 17 | "js": ( 18 | #f7df1e, 19 | "JavaScript", 20 | ), 21 | "yaml": ( 22 | #f71e6a, 23 | "YAML", 24 | ), 25 | "shell": ( 26 | #4eaa25, 27 | "Shell", 28 | ), 29 | // Updated to a more specific green shade 30 | "json": ( 31 | dodgerblue, 32 | "JSON", 33 | ), 34 | "python": ( 35 | #3572a5, 36 | "Python", 37 | ), 38 | // Using the specific Python blue 39 | "css": ( 40 | #264de4, 41 | "CSS", 42 | ), 43 | "go": ( 44 | #00add8, 45 | "Go", 46 | ), 47 | // Official Go color 48 | "markdown": ( 49 | #0000ff, 50 | "Markdown", 51 | ), 52 | "rust": ( 53 | #ff4647, 54 | "Rust", 55 | ), 56 | // Adjusted to match Rust's branding 57 | "java": ( 58 | #f89820, 59 | "Java", 60 | ), 61 | // Oracle Java color 62 | "csharp": ( 63 | #178600, 64 | "C#", 65 | ), 66 | "ruby": ( 67 | #701516, 68 | "Ruby", 69 | ), 70 | "swift": ( 71 | #f05138, 72 | "Swift", 73 | ), 74 | "php": ( 75 | #777bb4, 76 | "PHP", 77 | ), 78 | "typescript": ( 79 | #3178c6, 80 | "TypeScript", 81 | ), 82 | "scala": ( 83 | #c22d40, 84 | "Scala", 85 | ), 86 | "kotlin": ( 87 | #f18e33, 88 | "Kotlin", 89 | ), 90 | "lua": ( 91 | #000080, 92 | "Lua", 93 | ), 94 | "perl": ( 95 | #0298c3, 96 | "Perl", 97 | ), 98 | "haskell": ( 99 | #5e5086, 100 | "Haskell", 101 | ), 102 | "r": ( 103 | #198ce7, 104 | "R", 105 | ), 106 | "dart": ( 107 | #00d2b8, 108 | "Dart", 109 | ), 110 | "elixir": ( 111 | #6e4a7e, 112 | "Elixir", 113 | ), 114 | "clojure": ( 115 | #5881d8, 116 | "Clojure", 117 | ), 118 | "bash": ( 119 | #4eaa25, 120 | "Bash", 121 | ), 122 | "default": ( 123 | #333, 124 | "Code", 125 | ), 126 | ); 127 | 128 | @mixin base-label-style($bg-color, $text-color: var(--label-color)) { 129 | background: $bg-color; 130 | color: $text-color; 131 | border-radius: 0 0 0.25rem 0.25rem; 132 | font-size: 12px; 133 | letter-spacing: 0.025rem; 134 | padding: 0.1rem 0.5rem; 135 | text-align: right; 136 | text-transform: uppercase; 137 | position: absolute; 138 | right: 0; 139 | top: 0; 140 | margin-top: 0.1rem; 141 | } 142 | 143 | // Example usage within a specific class for clarity 144 | .code-label { 145 | @include base-label-style(#333); // Default background color 146 | } 147 | 148 | @each $lang, $color-info in $language-colors { 149 | .label-#{$lang} { 150 | @include base-label-style(nth($color-info, 1)); 151 | } 152 | } 153 | 154 | code { 155 | background-color: var(--bg-primary); 156 | padding: 0.1em 0.2em; 157 | border-radius: 5px; 158 | border: 1px solid var(--border-color); 159 | font-family: var(--code-font); 160 | } 161 | 162 | pre { 163 | background-color: var(--bg-primary) !important; 164 | border-radius: 5px; 165 | border: 1px solid var(--border-color); 166 | line-height: 1.4; 167 | overflow-x: auto; 168 | padding: 1em; 169 | position: relative; 170 | 171 | mark { 172 | background-color: var( 173 | --hightlight-color 174 | ) !important; // Ensure mark uses the theme background 175 | padding: 0; 176 | border-radius: 0px; 177 | } 178 | 179 | code { 180 | background-color: transparent !important; 181 | color: var(--text-color); 182 | font-size: 100%; 183 | padding: 0; 184 | border: none; 185 | font-family: var(--code-font); 186 | 187 | table { 188 | margin: 0; 189 | border-collapse: collapse; 190 | font-family: var(--code-font); 191 | 192 | mark { 193 | display: block; 194 | color: unset; 195 | padding: 0; 196 | background-color: var(--hightlight-color) !important; 197 | filter: brightness(1.2); // Example to slightly increase brightness 198 | } 199 | } 200 | 201 | td, 202 | th, 203 | tr { 204 | padding: 0; 205 | border-bottom: none; 206 | border: none; // Ensure no borders around rows 207 | } 208 | 209 | tbody td:first-child { 210 | text-align: center; 211 | user-select: none; 212 | min-width: 60px; 213 | border-right: none; 214 | } 215 | 216 | tbody tr:nth-child(even), 217 | thead tr { 218 | background-color: unset; 219 | } 220 | } 221 | } 222 | 223 | .clipboard-button, 224 | .clipboard-button svg { 225 | all: unset; 226 | cursor: pointer; 227 | position: absolute; 228 | bottom: 5px; 229 | /* 5px from the bottom */ 230 | right: 5px; 231 | /* 5px from the right */ 232 | z-index: 10; 233 | background-color: transparent; 234 | border: none; 235 | fill: #ef5350; 236 | /* Sets the color of the SVG, assuming it's an SVG icon */ 237 | } 238 | -------------------------------------------------------------------------------- /sass/parts/_header.scss: -------------------------------------------------------------------------------- 1 | .page-header { 2 | font-size: 2.5em; 3 | line-height: 100%; 4 | font-family: var(--header-font); 5 | margin: 4rem 0px 1rem 0px; 6 | } 7 | 8 | .centered-header { 9 | font-family: var(--header-font); 10 | position: absolute; 11 | top: 40%; 12 | left: 50%; 13 | transform: translate(-50%, -50%); 14 | text-align: center; 15 | font-size: 4em; 16 | } 17 | 18 | .centered-header span { 19 | line-height: 100%; 20 | } 21 | 22 | header { 23 | display: flex; 24 | flex-direction: row; 25 | flex-wrap: wrap; 26 | justify-content: space-between; 27 | padding: 1em 0; 28 | } 29 | 30 | header .main { 31 | display: flex; 32 | flex-direction: row; 33 | flex-wrap: wrap; 34 | justify-content: space-between; 35 | align-items: flex-start; 36 | gap: 12px; 37 | font-size: 1.5rem; 38 | 39 | /* Otherwise header and menu is too close on small screens*/ 40 | margin-bottom: 5px; 41 | } 42 | 43 | header .social img, 44 | header #dark-mode-toggle img { 45 | width: 16px; 46 | height: 16px; 47 | } 48 | 49 | header .socials { 50 | margin-bottom: 10px; 51 | /* Space between social icons and menu items */ 52 | } 53 | 54 | #dark-mode-toggle { 55 | justify-content: center; 56 | } 57 | 58 | .logo { 59 | border-bottom: unset; 60 | background-image: unset; 61 | } 62 | 63 | .logo > img { 64 | border: unset; 65 | width: auto; 66 | height: 24px; 67 | vertical-align: middle; 68 | } 69 | 70 | .logo:hover { 71 | background-color: transparent; 72 | } 73 | 74 | .socials { 75 | /* flex-child */ 76 | flex-grow: 0; 77 | /* flex-container */ 78 | display: flex; 79 | flex-direction: row; 80 | flex-wrap: wrap; 81 | justify-content: flex-start; 82 | align-items: flex-end; 83 | gap: 6px; 84 | } 85 | 86 | .social { 87 | border-bottom: unset; 88 | background-image: unset; 89 | padding: 2px; 90 | } 91 | 92 | .social > img { 93 | border: unset; 94 | width: 24px; 95 | height: 24px; 96 | } 97 | 98 | /* Mobile-specific adjustments */ 99 | @media (max-width: 600px) { 100 | header { 101 | flex-direction: column; 102 | align-items: center; 103 | padding: 1em 0; 104 | } 105 | 106 | header .main a { 107 | font-size: 20px; 108 | } 109 | } 110 | 111 | .meta { 112 | color: #999; 113 | display: flexbox; 114 | /* This changes the meta class to use flexbox, which ensures inline display */ 115 | align-items: center; 116 | /* Aligns items vertically in the middle */ 117 | flex-wrap: wrap; 118 | /* Allows items to wrap as needed */ 119 | } 120 | 121 | #dark-mode-toggle > img { 122 | display: none; 123 | width: 15px; 124 | height: 15px; 125 | border: unset; 126 | } 127 | 128 | h1, 129 | h2, 130 | h3, 131 | h4, 132 | h5, 133 | h6 { 134 | font-size: 1.2rem; 135 | margin-top: 2em; 136 | } 137 | 138 | h1::before { 139 | color: var(--primary-color); 140 | content: "# "; 141 | } 142 | 143 | h2::before { 144 | color: var(--primary-color); 145 | content: "## "; 146 | } 147 | 148 | h3::before { 149 | color: var(--primary-color); 150 | content: "### "; 151 | } 152 | 153 | h4::before { 154 | color: var(--primary-color); 155 | content: "#### "; 156 | } 157 | 158 | h5::before { 159 | color: var(--primary-color); 160 | content: "##### "; 161 | } 162 | 163 | h6::before { 164 | color: var(--primary-color); 165 | content: "###### "; 166 | } 167 | -------------------------------------------------------------------------------- /sass/parts/_image.scss: -------------------------------------------------------------------------------- 1 | img { 2 | border: 3px solid #ececec; 3 | max-width: 100%; 4 | } 5 | 6 | figure { 7 | box-sizing: border-box; 8 | display: inline-block; 9 | margin: 0; 10 | max-width: 100%; 11 | } 12 | 13 | figure img { 14 | max-height: 500px; 15 | } 16 | 17 | @media screen and (min-width: 600px) { 18 | figure { 19 | padding: 0 40px; 20 | } 21 | } 22 | 23 | figure h4 { 24 | font-size: 1rem; 25 | margin: 0; 26 | margin-bottom: 1em; 27 | } 28 | 29 | figure h4::before { 30 | content: "↳ "; 31 | } 32 | -------------------------------------------------------------------------------- /sass/parts/_mermaid.scss: -------------------------------------------------------------------------------- 1 | .mermaid { 2 | text-align: center; 3 | margin-top: 1em; 4 | margin-bottom: 1em; 5 | 6 | strong { 7 | font-weight: bold; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sass/parts/_misc.scss: -------------------------------------------------------------------------------- 1 | .primary-color { 2 | color: var(--primary-color); 3 | } 4 | 5 | .draft-label { 6 | color: var(--hover-color); 7 | text-decoration: none; 8 | padding: 2px 4px; 9 | border-radius: 4px; 10 | margin-left: 6px; 11 | background-color: var(--primary-color); 12 | } 13 | 14 | ::-moz-selection { 15 | background: var(--primary-color); 16 | color: var(--hover-color); 17 | text-shadow: none; 18 | } 19 | 20 | ::selection { 21 | background: var(--primary-color); 22 | color: var(--hover-color); 23 | } 24 | 25 | p { 26 | line-height: 1.5; 27 | } 28 | 29 | hr { 30 | border: 0; 31 | border-top: 3px solid var(--border-color); 32 | margin: 1em 0; 33 | } 34 | 35 | blockquote { 36 | border-left: 3px solid var(--primary-color); 37 | color: #737373; 38 | margin: 0; 39 | padding-left: 1em; 40 | } 41 | 42 | a { 43 | border-bottom: 3px solid var(--primary-color); 44 | color: inherit; 45 | text-decoration: none; 46 | 47 | // Make sure the underline is at the top 48 | position: relative; // needed for z-index 49 | z-index: 1; 50 | } 51 | 52 | a.zola-anchor { 53 | border-bottom: none; 54 | } 55 | 56 | a:hover { 57 | background-color: var(--primary-color); 58 | color: var(--hover-color); 59 | } 60 | 61 | time { 62 | color: grey; 63 | } 64 | 65 | /* Remove post list padding */ 66 | .list > ul { 67 | margin: 0; 68 | padding: 1rem 0 0 0; 69 | } 70 | 71 | /* Post list */ 72 | .list-item { 73 | margin-bottom: 30px; 74 | list-style-type: none; 75 | } 76 | 77 | // change the line-through color 78 | del { 79 | text-decoration-color: var(--primary-color); 80 | text-decoration-thickness: 3px; 81 | } 82 | 83 | @media all and (max-width: 640px) { 84 | .post-header { 85 | display: grid; 86 | grid-template-rows: auto 1fr; 87 | 88 | h1 { 89 | margin-top: 0; 90 | // font-size: 130%; 91 | 92 | a { 93 | border-bottom: none; 94 | } 95 | } 96 | } 97 | } 98 | 99 | /* Post list */ 100 | @media all and (min-width: 640px) { 101 | .post-header { 102 | display: grid; 103 | gap: 1rem; 104 | grid-row-gap: 1.5rem; 105 | grid-template-columns: auto 1fr; 106 | 107 | h1 { 108 | margin: 0; 109 | font-size: 130%; 110 | 111 | a { 112 | border-bottom: none; 113 | } 114 | } 115 | } 116 | } 117 | 118 | /* Remove styling from theme toggle button */ 119 | #dark-mode-toggle { 120 | border-bottom: none; 121 | 122 | &:hover { 123 | background-color: transparent; 124 | } 125 | } 126 | 127 | .MathJax_Display, 128 | .MJXc-display, 129 | .MathJax_SVG_Display { 130 | overflow-x: auto; 131 | overflow-y: hidden; 132 | } 133 | -------------------------------------------------------------------------------- /sass/parts/_note.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --note-header-bg: var(--bg-2); 3 | --note-header-color: var(--text-0); 4 | --note-content-bg: var(--bg-1); 5 | } 6 | 7 | .note-container { 8 | border-radius: 4px; 9 | overflow: hidden; 10 | margin: 1em 0; 11 | position: relative; 12 | border-left: 3px solid var(--primary-color); 13 | font-family: var(--paragraph-font); 14 | } 15 | 16 | .note-toggle, 17 | .note-header { 18 | color: var(--note-header-color); 19 | background-color: var(--note-header-bg); 20 | padding: 10px 25px; 21 | text-align: left; 22 | border: none; 23 | width: 100%; 24 | position: relative; 25 | outline: none; 26 | font-size: 1.2em; 27 | transition: background-color 0.3s ease; 28 | 29 | p { 30 | margin: 0; 31 | } 32 | 33 | .note-center { 34 | text-align: center; 35 | padding-right: 50px; 36 | } 37 | 38 | .note-icon, 39 | .note-icon { 40 | padding-left: 25px; 41 | } 42 | } 43 | 44 | .note-toggle { 45 | font-family: inherit; 46 | padding: 10px 25px; /* Stop header intersecting with note-icon */ 47 | cursor: pointer; 48 | position: relative; 49 | } 50 | 51 | .note-toggle::before { 52 | content: "▼"; 53 | position: absolute; 54 | right: 20px; 55 | /* Position the arrow to the right */ 56 | top: 50%; 57 | /* Center vertically */ 58 | transform: translateY(-50%); 59 | /* Center vertically */ 60 | } 61 | 62 | .note-toggle:hover, 63 | .note-toggle:focus { 64 | color: var(--note-header-color); 65 | background-color: var(--note-header-bg); 66 | outline: none; 67 | } 68 | 69 | .note-content { 70 | padding: 10px 20px; 71 | background-color: var(--note-content-bg); 72 | } 73 | 74 | .note-icon::before { 75 | content: "✎"; 76 | color: var(--primary-color); 77 | position: absolute; 78 | left: 20px; 79 | top: 50%; 80 | transform: translateY(-50%); 81 | } 82 | 83 | summary { 84 | padding-left: 0.5em; 85 | 86 | &:hover { 87 | background-color: var(--primary-color); 88 | color: var(--hover-color); 89 | cursor: pointer; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /sass/parts/_search.scss: -------------------------------------------------------------------------------- 1 | $icon-size: 1.3rem; 2 | 3 | // Search button in navbar 4 | .search-button { 5 | background: none; 6 | border: none; 7 | padding: 2px; 8 | cursor: pointer; 9 | display: inline-flex; 10 | align-items: center; 11 | justify-content: center; 12 | margin-left: 0.25em; // Match your other nav items' margin 13 | 14 | img { 15 | border: none; 16 | } 17 | 18 | .search-icon { 19 | width: 16px; // Match your theme button size 20 | height: 16px; 21 | } 22 | 23 | &:hover { 24 | background-color: transparent; 25 | } 26 | } 27 | 28 | // Theme-specific styles for search icon 29 | html.dark .search-button .search-icon { 30 | filter: invert(1); 31 | } 32 | 33 | html.light .search-button .search-icon { 34 | filter: invert(0); 35 | } 36 | 37 | // Search modal 38 | .search-modal { 39 | display: none; 40 | position: fixed; 41 | top: 0; 42 | left: 0; 43 | width: 100%; 44 | height: 100%; 45 | z-index: 1000; 46 | background: rgba(0, 0, 0, 0.2); 47 | backdrop-filter: blur(8px); 48 | -webkit-backdrop-filter: blur(8px); 49 | 50 | #modal-content { 51 | position: relative; 52 | margin: 8% auto; 53 | width: 80%; 54 | max-width: 28rem; 55 | background-color: var(--bg-0); 56 | border: 1px solid var(--bg-1); 57 | border-radius: 8px; 58 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 59 | } 60 | 61 | #searchBar { 62 | display: flex; 63 | align-items: center; 64 | padding: 1rem; 65 | gap: 0.5rem; 66 | 67 | #searchInput { 68 | flex: 1; 69 | padding: 0.75rem 2.5rem; 70 | font-size: 1rem; 71 | color: var(--text-0); 72 | background: var(--bg-1); 73 | border: 1px solid var(--bg-1); 74 | border-radius: 20px; 75 | width: 100%; 76 | 77 | &:focus { 78 | outline: none; 79 | border-color: var(--primary-color); 80 | } 81 | 82 | &::placeholder { 83 | color: var(--text-1); 84 | } 85 | } 86 | 87 | .close-icon { 88 | position: absolute; 89 | right: 1.5rem; 90 | display: none; 91 | padding: 4px; 92 | cursor: pointer; 93 | 94 | svg { 95 | width: $icon-size; 96 | height: $icon-size; 97 | fill: var(--text-1); 98 | } 99 | } 100 | } 101 | 102 | #results-container { 103 | display: none; 104 | border-top: 1px solid var(--bg-1); 105 | 106 | #results-info { 107 | padding: 0.5rem; 108 | color: var(--text-1); 109 | font-size: 0.8rem; 110 | text-align: center; 111 | } 112 | 113 | #results { 114 | max-height: 50vh; 115 | overflow-y: auto; 116 | 117 | > div { 118 | padding: 0.75rem 1rem; 119 | cursor: pointer; 120 | 121 | &[aria-selected="true"] { 122 | background: var(--primary-color); 123 | 124 | * { 125 | color: var(--hover-color) !important; 126 | } 127 | } 128 | 129 | span:first-child { 130 | display: block; 131 | color: var(--text-0); 132 | font-weight: 500; 133 | margin-bottom: 0.25rem; 134 | } 135 | 136 | span:nth-child(2) { 137 | display: block; 138 | color: var(--text-1); 139 | font-size: 0.9rem; 140 | } 141 | 142 | &:hover:not([aria-selected="true"]) { 143 | background: var(--bg-1); 144 | } 145 | } 146 | } 147 | } 148 | 149 | #modal-content { 150 | position: relative; 151 | margin: 8% auto; 152 | width: 80%; 153 | max-width: 28rem; 154 | background-color: var(--bg-0); 155 | border: 1px solid var(--bg-1); 156 | border-radius: 8px; 157 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 158 | padding: 1rem; 159 | 160 | h1 { 161 | margin-top: 0; // Override the default 2em margin 162 | margin-bottom: 1rem; 163 | font-size: 1.2rem; 164 | 165 | &::before { 166 | color: var(--primary-color); 167 | content: "# "; 168 | } 169 | } 170 | } 171 | } 172 | 173 | #searchBar { 174 | position: relative; 175 | display: flex; 176 | align-items: center; 177 | padding: 1rem; 178 | 179 | // Clear button styling 180 | .clear-button { 181 | position: absolute; 182 | right: 1.5rem; 183 | background: none; 184 | border: none; 185 | padding: 4px; 186 | cursor: pointer; 187 | display: none; // Initially hidden, shown via JS when input has text 188 | width: 24px; 189 | height: 24px; 190 | 191 | svg { 192 | width: 100%; 193 | height: 100%; 194 | fill: var(--text-1); // Use your theme text color 195 | } 196 | 197 | &:hover { 198 | svg { 199 | fill: var(--primary-color); 200 | } 201 | } 202 | } 203 | 204 | // Make sure input accommodates the clear button 205 | #searchInput { 206 | padding-right: 2.5rem; // Give space for the clear button 207 | } 208 | } 209 | 210 | // Mobile adjustments 211 | @media only screen and (max-width: 600px) { 212 | .search-modal { 213 | #modal-content { 214 | margin: 4% auto; 215 | width: 92%; 216 | } 217 | 218 | #results { 219 | max-height: 70vh; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /sass/parts/_table.scss: -------------------------------------------------------------------------------- 1 | table { 2 | border-spacing: 0; 3 | border-collapse: collapse; 4 | } 5 | 6 | table th { 7 | padding: 6px 13px; 8 | border: 1px solid #dfe2e5; 9 | font-size: large; 10 | } 11 | 12 | table td { 13 | padding: 6px 13px; 14 | border: 1px solid #dfe2e5; 15 | } 16 | -------------------------------------------------------------------------------- /sass/parts/_tags.scss: -------------------------------------------------------------------------------- 1 | .tags { 2 | ul { 3 | margin-left: 0; 4 | padding-left: 0; 5 | } 6 | 7 | li { 8 | list-style-type: none; 9 | } 10 | 11 | a { 12 | border-bottom: 3px solid var(--primary-color); 13 | font-family: var(--text-font); 14 | } 15 | 16 | a:hover { 17 | color: var(--hover-color); 18 | background-color: var(--primary-color); 19 | } 20 | 21 | a::before { 22 | content: "🏷 "; /* Ensure there's a space after the emoji for spacing */ 23 | display: inline; 24 | white-space: nowrap !important; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sass/parts/_toc.scss: -------------------------------------------------------------------------------- 1 | .toc-container { 2 | .toc-title { 3 | cursor: pointer; 4 | position: relative; 5 | padding-left: 20px; // Space for the arrow 6 | 7 | &:before { 8 | content: "▼"; // Down arrow 9 | position: absolute; 10 | left: 0; 11 | transition: transform 0.3s ease; // Smooth transformation 12 | } 13 | 14 | &:hover:before, 15 | &.expanded:before { 16 | transform: rotate(180deg); // Arrow points up when expanded or on hover 17 | } 18 | } 19 | 20 | .toc-list { 21 | display: none; // ToC is hidden by default 22 | // Removed transition on display, not effective 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sass/theme/dark.scss: -------------------------------------------------------------------------------- 1 | $bg-0: #121212; 2 | 3 | // Dark theme needs a bigger contrast compared to the white theme. 4 | $bg-1: lighten($bg-0, 5%); 5 | $bg-2: lighten($bg-1, 10%); 6 | 7 | $text-0: lighten($bg-0, 87%); 8 | $text-1: lighten($bg-0, 60%); 9 | 10 | :root.dark { 11 | --text-0: #{$text-0}; 12 | --text-1: #{$text-1}; 13 | 14 | --bg-0: #{$bg-0}; 15 | --bg-1: #{$bg-1}; 16 | --bg-2: #{$bg-2}; 17 | 18 | --border-color: var(--bg-2); 19 | 20 | --primary-color: #ef5350; 21 | --hover-color: white; 22 | 23 | .social > img { 24 | filter: invert(1); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sass/theme/light.scss: -------------------------------------------------------------------------------- 1 | $bg-0: #fff; 2 | $bg-1: darken($bg-0, 2%); 3 | $bg-2: darken($bg-1, 8%); 4 | 5 | $text-0: darken($bg-0, 87%); 6 | $text-1: darken($bg-0, 60%); 7 | 8 | :root.light { 9 | --text-0: #{$text-0}; 10 | --text-1: #{$text-1}; 11 | 12 | --bg-0: #{$bg-0}; 13 | --bg-1: #{$bg-1}; 14 | --bg-2: #{$bg-2}; 15 | 16 | --border-color: var(--bg-2); 17 | 18 | --primary-color: #ef5350; 19 | --hover-color: white; 20 | 21 | // invert colour on hover for consistency 22 | .social :hover { 23 | filter: invert(1); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /screenshot-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/screenshot-dark.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/screenshot.png -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | {pkgs ? import {}}: 2 | pkgs.mkShell { 3 | nativeBuildInputs = with pkgs; [ 4 | zola 5 | 6 | # Formatters 7 | treefmt 8 | nodePackages.prettier 9 | alejandra 10 | djlint 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-Bold.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-BoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-ExtraBold.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-ExtraLight.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-Italic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-Light.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-LightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-Medium.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-MediumItalic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-SemiBold.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-Thin.ttf -------------------------------------------------------------------------------- /static/fonts/JetbrainsMono/JetBrainsMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/JetbrainsMono/JetBrainsMono-ThinItalic.ttf -------------------------------------------------------------------------------- /static/fonts/SpaceGrotesk/SpaceGrotesk-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/SpaceGrotesk/SpaceGrotesk-Bold.ttf -------------------------------------------------------------------------------- /static/fonts/SpaceGrotesk/SpaceGrotesk-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/SpaceGrotesk/SpaceGrotesk-Light.ttf -------------------------------------------------------------------------------- /static/fonts/SpaceGrotesk/SpaceGrotesk-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/SpaceGrotesk/SpaceGrotesk-Medium.ttf -------------------------------------------------------------------------------- /static/fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/SpaceGrotesk/SpaceGrotesk-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matthias/apollo/22d842d6dd1b723e5fb67ea0be538eb560a32b0e/static/fonts/SpaceGrotesk/SpaceGrotesk-SemiBold.ttf -------------------------------------------------------------------------------- /static/icons/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/LICENSE: -------------------------------------------------------------------------------- 1 | All icons in this directory are downloaded from [FontAwesome](https://fontawesome.com/). They are part of the [free offer](https://fontawesome.com/license/free) and are licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /static/icons/social/apple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/bitcoin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/bluesky.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/icons/social/deviantart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/diaspora.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/discourse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/ethereum.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/etsy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/fediverse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/gitlab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/google-scholar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /static/icons/social/google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/hacker-news.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/linkedin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/mastodon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/matrix.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/icons/social/orcid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /static/icons/social/paypal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/pinterest.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/quora.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/reddit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/rss.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/skype.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/slack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/snapchat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/soundcloud.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/spotify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/stack-exchange.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/stack-overflow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/steam.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/vimeo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/whatsapp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/x-twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/social/youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/sun.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/js/codeblock.js: -------------------------------------------------------------------------------- 1 | const successIcon = ` 2 | 3 | `; 4 | const errorIcon = ` 5 | 6 | `; 7 | const copyIcon = ` 8 | 9 | `; 10 | 11 | // Function to change icons after copying 12 | const changeIcon = (button, isSuccess) => { 13 | button.innerHTML = isSuccess ? successIcon : errorIcon; 14 | setTimeout(() => { 15 | button.innerHTML = copyIcon; // Reset to copy icon 16 | }, 2000); 17 | }; 18 | 19 | // Function to get code text from tables, skipping line numbers 20 | const getCodeFromTable = (codeBlock) => { 21 | return [...codeBlock.querySelectorAll('tr')] 22 | .map(row => row.querySelector('td:last-child')?.innerText ?? '') 23 | .join(''); 24 | }; 25 | 26 | // Function to get code text from non-table blocks 27 | const getNonTableCode = (codeBlock) => { 28 | return codeBlock.textContent.trim(); 29 | }; 30 | 31 | document.addEventListener('DOMContentLoaded', function () { 32 | // Select all `pre` elements containing `code` 33 | 34 | const observer = new IntersectionObserver((entries) => { 35 | entries.forEach(entry => { 36 | const pre = entry.target.parentNode; 37 | const clipboardBtn = pre.querySelector('.clipboard-button'); 38 | const label = pre.querySelector('.code-label'); 39 | 40 | if (clipboardBtn) { 41 | // Adjust the position of the clipboard button when the `code` is not fully visible 42 | clipboardBtn.style.right = entry.isIntersecting ? '5px' : `-${entry.boundingClientRect.right - pre.clientWidth + 5}px`; 43 | } 44 | 45 | if (label) { 46 | // Adjust the position of the label similarly 47 | label.style.right = entry.isIntersecting ? '0px' : `-${entry.boundingClientRect.right - pre.clientWidth}px`; 48 | } 49 | }); 50 | }, { 51 | root: null, // observing relative to viewport 52 | rootMargin: '0px', 53 | threshold: 1.0 // Adjust this to control when the callback fires 54 | }); 55 | 56 | document.querySelectorAll('pre code').forEach(codeBlock => { 57 | const pre = codeBlock.parentNode; 58 | pre.style.position = 'relative'; // Ensure parent `pre` can contain absolute elements 59 | 60 | // Create and append the copy button 61 | const copyBtn = document.createElement('button'); 62 | copyBtn.className = 'clipboard-button'; 63 | copyBtn.innerHTML = copyIcon; 64 | copyBtn.setAttribute('aria-label', 'Copy code to clipboard'); 65 | pre.appendChild(copyBtn); 66 | 67 | // Attach event listener to copy button 68 | copyBtn.addEventListener('click', async () => { 69 | // Determine if the code is in a table or not 70 | const isTable = codeBlock.querySelector('table'); 71 | const codeToCopy = isTable ? getCodeFromTable(codeBlock) : getNonTableCode(codeBlock); 72 | try { 73 | await navigator.clipboard.writeText(codeToCopy); 74 | changeIcon(copyBtn, true); // Show success icon 75 | } catch (error) { 76 | console.error('Failed to copy text: ', error); 77 | changeIcon(copyBtn, false); // Show error icon 78 | } 79 | }); 80 | 81 | const langClass = codeBlock.className.match(/language-(\w+)/); 82 | const lang = langClass ? langClass[1] : 'default'; 83 | 84 | // Create and append the label 85 | const label = document.createElement('span'); 86 | label.className = 'code-label label-' + lang; // Use the specific language class 87 | label.textContent = lang.toUpperCase(); // Display the language as label 88 | pre.appendChild(label); 89 | 90 | let ticking = false; 91 | pre.addEventListener('scroll', () => { 92 | if (!ticking) { 93 | window.requestAnimationFrame(() => { 94 | copyBtn.style.right = `-${pre.scrollLeft}px`; 95 | label.style.right = `-${pre.scrollLeft}px`; 96 | ticking = false; 97 | }); 98 | ticking = true; 99 | } 100 | }); 101 | 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /static/js/count.js: -------------------------------------------------------------------------------- 1 | // GoatCounter: https://www.goatcounter.com 2 | // This file (and *only* this file) is released under the ISC license: https://opensource.org/licenses/ISC 3 | ;(function() { 4 | 'use strict'; 5 | 6 | if (window.goatcounter && window.goatcounter.vars) // Compatibility with very old version; do not use. 7 | window.goatcounter = window.goatcounter.vars 8 | else 9 | window.goatcounter = window.goatcounter || {} 10 | 11 | // Load settings from data-goatcounter-settings. 12 | var s = document.querySelector('script[data-goatcounter]') 13 | if (s && s.dataset.goatcounterSettings) { 14 | try { var set = JSON.parse(s.dataset.goatcounterSettings) } 15 | catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) } 16 | for (var k in set) 17 | if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1) 18 | window.goatcounter[k] = set[k] 19 | } 20 | 21 | var enc = encodeURIComponent 22 | 23 | // Get all data we're going to send off to the counter endpoint. 24 | var get_data = function(vars) { 25 | var data = { 26 | p: (vars.path === undefined ? goatcounter.path : vars.path), 27 | r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer), 28 | t: (vars.title === undefined ? goatcounter.title : vars.title), 29 | e: !!(vars.event || goatcounter.event), 30 | s: [window.screen.width, window.screen.height, (window.devicePixelRatio || 1)], 31 | b: is_bot(), 32 | q: location.search, 33 | } 34 | 35 | var rcb, pcb, tcb // Save callbacks to apply later. 36 | if (typeof(data.r) === 'function') rcb = data.r 37 | if (typeof(data.t) === 'function') tcb = data.t 38 | if (typeof(data.p) === 'function') pcb = data.p 39 | 40 | if (is_empty(data.r)) data.r = document.referrer 41 | if (is_empty(data.t)) data.t = document.title 42 | if (is_empty(data.p)) data.p = get_path() 43 | 44 | if (rcb) data.r = rcb(data.r) 45 | if (tcb) data.t = tcb(data.t) 46 | if (pcb) data.p = pcb(data.p) 47 | return data 48 | } 49 | 50 | // Check if a value is "empty" for the purpose of get_data(). 51 | var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' } 52 | 53 | // See if this looks like a bot; there is some additional filtering on the 54 | // backend, but these properties can't be fetched from there. 55 | var is_bot = function() { 56 | // Headless browsers are probably a bot. 57 | var w = window, d = document 58 | if (w.callPhantom || w._phantom || w.phantom) 59 | return 150 60 | if (w.__nightmare) 61 | return 151 62 | if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate) 63 | return 152 64 | if (navigator.webdriver) 65 | return 153 66 | return 0 67 | } 68 | 69 | // Object to urlencoded string, starting with a ?. 70 | var urlencode = function(obj) { 71 | var p = [] 72 | for (var k in obj) 73 | if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false) 74 | p.push(enc(k) + '=' + enc(obj[k])) 75 | return '?' + p.join('&') 76 | } 77 | 78 | // Show a warning in the console. 79 | var warn = function(msg) { 80 | if (console && 'warn' in console) 81 | console.warn('goatcounter: ' + msg) 82 | } 83 | 84 | // Get the endpoint to send requests to. 85 | var get_endpoint = function() { 86 | var s = document.querySelector('script[data-goatcounter]') 87 | if (s && s.dataset.goatcounter) 88 | return s.dataset.goatcounter 89 | return (goatcounter.endpoint || window.counter) // counter is for compat; don't use. 90 | } 91 | 92 | // Get current path. 93 | var get_path = function() { 94 | var loc = location, 95 | c = document.querySelector('link[rel="canonical"][href]') 96 | if (c) { // May be relative or point to different domain. 97 | var a = document.createElement('a') 98 | a.href = c.href 99 | if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, '')) 100 | loc = a 101 | } 102 | return (loc.pathname + loc.search) || '/' 103 | } 104 | 105 | // Run function after DOM is loaded. 106 | var on_load = function(f) { 107 | if (document.body === null) 108 | document.addEventListener('DOMContentLoaded', function() { f() }, false) 109 | else 110 | f() 111 | } 112 | 113 | // Filter some requests that we (probably) don't want to count. 114 | goatcounter.filter = function() { 115 | if ('visibilityState' in document && document.visibilityState === 'prerender') 116 | return 'visibilityState' 117 | if (!goatcounter.allow_frame && location !== parent.location) 118 | return 'frame' 119 | if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/)) 120 | return 'localhost' 121 | if (!goatcounter.allow_local && location.protocol === 'file:') 122 | return 'localfile' 123 | if (localStorage && localStorage.getItem('skipgc') === 't') 124 | return 'disabled with #toggle-goatcounter' 125 | return false 126 | } 127 | 128 | // Get URL to send to GoatCounter. 129 | window.goatcounter.url = function(vars) { 130 | var data = get_data(vars || {}) 131 | if (data.p === null) // null from user callback. 132 | return 133 | data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control. 134 | 135 | var endpoint = get_endpoint() 136 | if (!endpoint) 137 | return warn('no endpoint found') 138 | 139 | return endpoint + urlencode(data) 140 | } 141 | 142 | // Count a hit. 143 | window.goatcounter.count = function(vars) { 144 | var f = goatcounter.filter() 145 | if (f) 146 | return warn('not counting because of: ' + f) 147 | var url = goatcounter.url(vars) 148 | if (!url) 149 | return warn('not counting because path callback returned null') 150 | 151 | if (!navigator.sendBeacon(url)) { 152 | // This mostly fails due to being blocked by CSP; try again with an 153 | // image-based fallback. 154 | var img = document.createElement('img') 155 | img.src = url 156 | img.style.position = 'absolute' // Affect layout less. 157 | img.style.bottom = '0px' 158 | img.style.width = '1px' 159 | img.style.height = '1px' 160 | img.loading = 'eager' 161 | img.setAttribute('alt', '') 162 | img.setAttribute('aria-hidden', 'true') 163 | 164 | var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) } 165 | img.addEventListener('load', rm, false) 166 | document.body.appendChild(img) 167 | } 168 | } 169 | 170 | // Get a query parameter. 171 | window.goatcounter.get_query = function(name) { 172 | var s = location.search.substr(1).split('&') 173 | for (var i = 0; i < s.length; i++) 174 | if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0) 175 | return s[i].substr(name.length + 1) 176 | } 177 | 178 | // Track click events. 179 | window.goatcounter.bind_events = function() { 180 | if (!document.querySelectorAll) // Just in case someone uses an ancient browser. 181 | return 182 | 183 | var send = function(elem) { 184 | return function() { 185 | goatcounter.count({ 186 | event: true, 187 | path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''), 188 | title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''), 189 | referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''), 190 | }) 191 | } 192 | } 193 | 194 | Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) { 195 | if (elem.dataset.goatcounterBound) 196 | return 197 | var f = send(elem) 198 | elem.addEventListener('click', f, false) 199 | elem.addEventListener('auxclick', f, false) // Middle click. 200 | elem.dataset.goatcounterBound = 'true' 201 | }) 202 | } 203 | 204 | // Add a "visitor counter" frame or image. 205 | window.goatcounter.visit_count = function(opt) { 206 | on_load(function() { 207 | opt = opt || {} 208 | opt.type = opt.type || 'html' 209 | opt.append = opt.append || 'body' 210 | opt.path = opt.path || get_path() 211 | opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')} 212 | 213 | opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?' 214 | if (opt.no_branding) opt.attr['src'] += '&no_branding=1' 215 | if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style) 216 | if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start) 217 | if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end) 218 | 219 | var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type] 220 | if (!tag) 221 | return warn('visit_count: unknown type: ' + opt.type) 222 | 223 | if (opt.type === 'html') { 224 | opt.attr['frameborder'] = '0' 225 | opt.attr['scrolling'] = 'no' 226 | } 227 | 228 | var d = document.createElement(tag) 229 | for (var k in opt.attr) 230 | d.setAttribute(k, opt.attr[k]) 231 | 232 | var p = document.querySelector(opt.append) 233 | if (!p) 234 | return warn('visit_count: append not found: ' + opt.append) 235 | p.appendChild(d) 236 | }) 237 | } 238 | 239 | // Make it easy to skip your own views. 240 | if (location.hash === '#toggle-goatcounter') { 241 | if (localStorage.getItem('skipgc') === 't') { 242 | localStorage.removeItem('skipgc', 't') 243 | alert('GoatCounter tracking is now ENABLED in this browser.') 244 | } 245 | else { 246 | localStorage.setItem('skipgc', 't') 247 | alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.') 248 | } 249 | } 250 | 251 | if (!goatcounter.no_onload) 252 | on_load(function() { 253 | // 1. Page is visible, count request. 254 | // 2. Page is not yet visible; wait until it switches to 'visible' and count. 255 | // See #487 256 | if (!('visibilityState' in document) || document.visibilityState === 'visible') 257 | goatcounter.count() 258 | else { 259 | var f = function(e) { 260 | if (document.visibilityState !== 'visible') 261 | return 262 | document.removeEventListener('visibilitychange', f) 263 | goatcounter.count() 264 | } 265 | document.addEventListener('visibilitychange', f) 266 | } 267 | 268 | if (!goatcounter.no_events) 269 | goatcounter.bind_events() 270 | }) 271 | })(); -------------------------------------------------------------------------------- /static/js/imamu.js: -------------------------------------------------------------------------------- 1 | // https://cloud.umami.is/script.js 2 | !function(){"use strict";(t=>{const{screen:{width:e,height:a},navigator:{language:r},location:n,document:i,history:c}=t,{hostname:s,href:o,origin:u}=n,{currentScript:l,referrer:d}=i,h=o.startsWith("data:")?void 0:t.localStorage;if(!l)return;const m="data-",f=l.getAttribute.bind(l),p=f(m+"website-id"),g=f(m+"host-url"),y=f(m+"tag"),b="false"!==f(m+"auto-track"),v="true"===f(m+"exclude-search"),w=f(m+"domains")||"",S=w.split(",").map((t=>t.trim())),N=`${(g||"https://api-gateway.umami.dev"||l.src.split("/").slice(0,-1).join("/")).replace(/\/$/,"")}/api/send`,T=`${e}x${a}`,A=/data-umami-event-([\w-_]+)/,x=m+"umami-event",O=300,U=t=>{if(t){try{const e=decodeURI(t);if(e!==t)return e}catch(e){return t}return encodeURI(t)}},j=t=>{try{const{pathname:e,search:a,hash:r}=new URL(t,n.href);t=e+a+r}catch(t){}return v?t.split("?")[0]:t},k=()=>({website:p,hostname:s,screen:T,language:r,title:U(q),url:U(W),referrer:U(_),tag:y||void 0}),E=(t,e,a)=>{a&&(_=W,W=j(a.toString()),W!==_&&setTimeout(K,O))},L=()=>!p||h&&h.getItem("umami.disabled")||w&&!S.includes(s),$=async(t,e="event")=>{if(L())return;const a={"Content-Type":"application/json"};void 0!==B&&(a["x-umami-cache"]=B);try{const r=await fetch(N,{method:"POST",body:JSON.stringify({type:e,payload:t}),headers:a}),n=await r.text();return B=n}catch(t){}},I=()=>{D||(K(),(()=>{const t=(t,e,a)=>{const r=t[e];return(...e)=>(a.apply(null,e),r.apply(t,e))};c.pushState=t(c,"pushState",E),c.replaceState=t(c,"replaceState",E)})(),(()=>{const t=new MutationObserver((([t])=>{q=t&&t.target?t.target.text:void 0})),e=i.querySelector("head > title");e&&t.observe(e,{subtree:!0,characterData:!0,childList:!0})})(),i.addEventListener("click",(async t=>{const e=t=>["BUTTON","A"].includes(t),a=async t=>{const e=t.getAttribute.bind(t),a=e(x);if(a){const r={};return t.getAttributeNames().forEach((t=>{const a=t.match(A);a&&(r[a[1]]=e(t))})),K(a,r)}},r=t.target,i=e(r.tagName)?r:((t,a)=>{let r=t;for(let t=0;t{s||(n.href=e)}))}else if("BUTTON"===i.tagName)return a(i)}}),!0),D=!0)},K=(t,e)=>$("string"==typeof t?{...k(),name:t,data:"object"==typeof e?e:void 0}:"object"==typeof t?t:"function"==typeof t?t(k()):k()),R=t=>$({...k(),data:t},"identify");t.umami||(t.umami={track:K,identify:R});let B,D,W=j(o),_=d.startsWith(u)?"":d,q=i.title;b&&!L()&&("complete"===i.readyState?I():i.addEventListener("readystatechange",I,!0))})(window)}(); -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | mmdElements = document.getElementsByClassName("mermaid"); 2 | const mmdHTML = []; 3 | for (let i = 0; i < mmdElements.length; i++) { 4 | mmdHTML[i] = mmdElements[i].innerHTML; 5 | } 6 | 7 | function mermaidRender(theme) { 8 | if (theme == "dark") { 9 | initOptions = { 10 | startOnLoad: false, 11 | theme: "dark", 12 | }; 13 | } else { 14 | initOptions = { 15 | startOnLoad: false, 16 | theme: "neutral", 17 | }; 18 | } 19 | for (let i = 0; i < mmdElements.length; i++) { 20 | delete mmdElements[i].dataset.processed; 21 | mmdElements[i].innerHTML = mmdHTML[i]; 22 | } 23 | mermaid.initialize(initOptions); 24 | mermaid.run(); 25 | } 26 | -------------------------------------------------------------------------------- /static/js/note.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | document.querySelectorAll('.note-toggle').forEach(function(toggleButton) { 3 | var content = toggleButton.nextElementSibling; 4 | var isHidden = content.style.display === 'none'; 5 | toggleButton.setAttribute('aria-expanded', !isHidden); 6 | 7 | toggleButton.addEventListener('click', function() { 8 | var expanded = this.getAttribute('aria-expanded') === 'true'; 9 | this.setAttribute('aria-expanded', !expanded); 10 | content.style.display = expanded ? 'none' : 'block'; 11 | }); 12 | }); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /static/js/searchElasticlunr.min.js: -------------------------------------------------------------------------------- 1 | !function(){function g(e){var t=new g.Index;return t.pipeline.add(g.trimmer,g.stopWordFilter,g.stemmer),e&&e.call(t,t),t}var t;g.version="0.9.5",(lunr=g).utils={},g.utils.warn=(t=this,function(e){t.console&&console.warn&&console.warn(e)}),g.utils.toString=function(e){return null==e?"":e.toString()},(g.EventEmitter=function(){this.events={}}).prototype.addListener=function(){var e=Array.prototype.slice.call(arguments);const t=e.pop();if("function"!=typeof t)throw new TypeError("last argument must be a function");e.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},g.EventEmitter.prototype.removeListener=function(e,t){this.hasHandler(e)&&-1!==(t=this.events[e].indexOf(t))&&(this.events[e].splice(t,1),0===this.events[e].length)&&delete this.events[e]},g.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){const t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},g.EventEmitter.prototype.hasHandler=function(e){return e in this.events},(g.tokenizer=function(n){if(!arguments.length||null==n)return[];if(Array.isArray(n)){let e=n.filter(function(e){return null!=e}),t=(e=e.map(function(e){return g.utils.toString(e).toLowerCase()}),[]);return e.forEach(function(e){e=e.split(g.tokenizer.seperator),t=t.concat(e)},this),t}return n.toString().trim().toLowerCase().split(g.tokenizer.seperator)}).defaultSeperator=/[\s-]+/,g.tokenizer.seperator=g.tokenizer.defaultSeperator,g.tokenizer.setSeperator=function(e){null!=e&&"object"==typeof e&&(g.tokenizer.seperator=e)},g.tokenizer.resetSeperator=function(){g.tokenizer.seperator=g.tokenizer.defaultSeperator},g.tokenizer.getSeperator=function(){return g.tokenizer.seperator},(g.Pipeline=function(){this._queue=[]}).registeredFunctions={},g.Pipeline.registerFunction=function(e,t){t in g.Pipeline.registeredFunctions&&g.utils.warn("Overwriting existing registered function: "+t),e.label=t,g.Pipeline.registeredFunctions[t]=e},g.Pipeline.getRegisteredFunction=function(e){return e in g.Pipeline.registeredFunctions!=1?null:g.Pipeline.registeredFunctions[e]},g.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||g.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},g.Pipeline.load=function(e){const n=new g.Pipeline;return e.forEach(function(e){var t=g.Pipeline.getRegisteredFunction(e);if(!t)throw new Error("Cannot load un-registered function: "+e);n.add(t)}),n},g.Pipeline.prototype.add=function(){Array.prototype.slice.call(arguments).forEach(function(e){g.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},g.Pipeline.prototype.after=function(e,t){if(g.Pipeline.warnIfFunctionNotRegistered(t),-1===(e=this._queue.indexOf(e)))throw new Error("Cannot find existingFn");this._queue.splice(e+1,0,t)},g.Pipeline.prototype.before=function(e,t){if(g.Pipeline.warnIfFunctionNotRegistered(t),-1===(e=this._queue.indexOf(e)))throw new Error("Cannot find existingFn");this._queue.splice(e,0,t)},g.Pipeline.prototype.remove=function(e){-1!==(e=this._queue.indexOf(e))&&this._queue.splice(e,1)},g.Pipeline.prototype.run=function(o){var e=[],t=o.length,i=this._queue.length;for(let n=0;ne&&(n=i),o=n-t,i=t+Math.floor(o/2),r=this.elements[i]}return r===e?i:-1},lunr.SortedSet.prototype.locationFor=function(e){let t=0,n=this.elements.length,o=n-t,i=t+Math.floor(o/2),r=this.elements[i];for(;1e&&(n=i),o=n-t,i=t+Math.floor(o/2),r=this.elements[i];return r>e?i:ri-1||o>r-1);)s[n]===l[o]?(t.add(s[n]),n++,o++):s[n]l[o]&&o++;return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){let t,n,o;n=this.length>=e.length?(t=this,e):(t=e,this),o=t.clone();for(let e=0,t=n.toArray();e{let t=n.getAttribute(e);t&&(t=t.replace("$SHORTCUT",v),n.setAttribute(e,t))})}s.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||s.click()});let e,r=(f.addEventListener("click",o),f.addEventListener("touchend",o,{passive:!0}),document.addEventListener("keydown",function(e){"Escape"===e.key&&u()}),p.addEventListener("click",function(){t(),h.focus()}),p.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||(t(),h.focus(),e.preventDefault())}),s.addEventListener("mouseover",i),s.addEventListener("click",l),s.addEventListener("touchstart",l,{passive:!0}),null);function l(){e=document.activeElement,i(),f.style.display="block",h.focus()}function u(){f.style.display="none",t(),e&&document.body.contains(e)&&e.focus()}function a(e){var t;"true"!==e.getAttribute("aria-selected")&&([t=null]=[e],y.querySelectorAll("#results > div").forEach(e=>{e!==t&&e.setAttribute("aria-selected","false")}),e.setAttribute("aria-selected","true")),h.setAttribute("aria-activedescendant",e.id)}function t(){h.value="",y.innerHTML="",g.style.display="none",h.removeAttribute("aria-activedescendant"),p.style.display="none"}function o(e){e.target===f&&u(),e.stopPropagation()}function i(){if(!r)if(window.searchIndex)r=Promise.resolve(elasticlunr.Index.load(window.searchIndex));else{var t=document.documentElement.getAttribute("lang").substring(0,2);let e=document.querySelector("meta[name='base']").getAttribute("content");e.endsWith("/")&&(e=e.slice(0,-1)),r=fetch(e+"/search_index."+t+".json").then(e=>e.json()).then(e=>elasticlunr.Index.load(e))}}function S(e){return e=parseInt(e,16).toString(2),[0,1,2,3,4][Math.ceil(e.length/8)]}function c(){a(this)}h.addEventListener("input",async function(){var e=this.value;const s=e.trim();var t=await r;y.innerHTML="",p.style.display=0{e&&(e.style.display="none")}),(o=m[o])&&(o.style.display="inline",o.textContent=o.textContent.replace("$NUMBER",t.toString()));let l=0;e.forEach(function(t){if(t.doc.title||t.doc.path||t.doc.id){var n=document.createElement("div"),o=(n.setAttribute("role","option"),n.id="result-"+l++,n.innerHTML="",n.querySelector("a")),i=n.querySelector("span:first-child"),r=n.querySelector("span:nth-child(2)"),i=(i.textContent=t.doc.title||t.doc.path||t.doc.id,t.doc.body?function(e,t){var n=t.map(function(e){return elasticlunr.stemmer(e.toLowerCase())});let o=0;var i=[];for(const m of e.toLowerCase().split(". ")){let t=!0;for(const v of m.split(/[\s\n]/)){if(0"),p[2]+p[0].length);!h.test(p[0])&&12<=p[0].length?(f=function(t){let n="",o=!1,i=0,r=0,s=0;for(let e=0;e"),d=g}c.push("…");var y=t=c.join("");return 150]+>/g,"").length?t.substring(0,150)+"…":y}(t.doc.body,s.split(/\s+/)):t.doc.description||"");r.innerHTML=i;let e=t.ref;t.doc.body&&(r=encodeURIComponent(s),e+="#:~:text="+r),o.href=e,y.appendChild(n)}}),h.setAttribute("aria-expanded",0e.split("#")[0].replace(/\/$/,""))(o)!==n(t)||e.ctrlKey||e.metaKey||u())}),document.querySelectorAll("#results > div").forEach(e=>{e.removeEventListener("touchstart",c),e.addEventListener("touchstart",c,{passive:!0})})},!0),document.addEventListener("keydown",function(t){var e=navigator.userAgent.toLowerCase().includes("mac")?t.metaKey:t.ctrlKey;if("k"===t.key&&e)t.preventDefault(),("block"===f.style.display?u:l)();else if(e=document.activeElement,"Tab"!==t.key||e!==h&&e!==p){if(0!==(r=y.querySelectorAll("#results > div")).length){var n,o,i=Array.from(r),r=y.querySelector('[aria-selected="true"]'),s=i.indexOf(r);if(["ArrowUp","ArrowDown","Home","End","PageUp","PageDown"].includes(t.key)){t.preventDefault();let e=s;switch(t.key){case"ArrowUp":e=Math.max(s-1,0);break;case"ArrowDown":e=Math.min(s+1,i.length-1);break;case"Home":e=0;break;case"End":e=i.length-1;break;case"PageUp":e=Math.max(s-3,0);break;case"PageDown":e=Math.min(s+3,i.length-1)}e!==s&&(a((o=i)[n=e]),o[n].scrollIntoView({block:"nearest",inline:"start"}))}"Enter"===t.key&&r&&(t.preventDefault(),t.stopImmediatePropagation(),(o=r.querySelector("a"))&&(window.location.href=o.getAttribute("href")),u())}}else t.preventDefault(),(e===h?p:h).focus()})}}; 2 | -------------------------------------------------------------------------------- /static/js/themetoggle.js: -------------------------------------------------------------------------------- 1 | function setTheme(mode) { 2 | localStorage.setItem("theme-storage", mode); 3 | } 4 | 5 | // Functions needed for the theme toggle 6 | // 7 | 8 | function toggleTheme() { 9 | if (localStorage.getItem("theme-storage") === "light") { 10 | setTheme("dark"); 11 | updateItemToggleTheme(); 12 | } else if (localStorage.getItem("theme-storage") === "dark") { 13 | setTheme("light"); 14 | updateItemToggleTheme(); 15 | } 16 | } 17 | 18 | function updateItemToggleTheme() { 19 | let mode = getSavedTheme(); 20 | 21 | const darkModeStyle = document.getElementById("darkModeStyle"); 22 | if (darkModeStyle) { 23 | darkModeStyle.disabled = (mode === "light"); 24 | } 25 | 26 | const sunIcon = document.getElementById("sun-icon"); 27 | const moonIcon = document.getElementById("moon-icon"); 28 | if (sunIcon && moonIcon) { 29 | sunIcon.style.display = (mode === "dark") ? "inline-block" : "none"; 30 | moonIcon.style.display = (mode === "light") ? "inline-block" : "none"; 31 | } 32 | 33 | let htmlElement = document.querySelector("html"); 34 | if (mode === "dark") { 35 | htmlElement.classList.remove("light") 36 | htmlElement.classList.add("dark") 37 | } else if (mode === "light") { 38 | htmlElement.classList.remove("dark") 39 | htmlElement.classList.add("light") 40 | } 41 | } 42 | 43 | function getSavedTheme() { 44 | let currentTheme = localStorage.getItem("theme-storage"); 45 | if(!currentTheme) { 46 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { 47 | currentTheme = "dark"; 48 | } else { 49 | currentTheme = "light"; 50 | } 51 | } 52 | 53 | return currentTheme; 54 | } 55 | 56 | // Update the toggle theme on page load 57 | updateItemToggleTheme(); 58 | -------------------------------------------------------------------------------- /static/js/toc.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | const tocTitle = document.querySelector('.toc-title'); 3 | const tocList = document.querySelector('.toc-list'); 4 | 5 | if (tocTitle && tocList) { 6 | const toggleToC = () => { 7 | const isExpanded = tocList.style.display === 'block' || window.getComputedStyle(tocList).display === 'block'; 8 | tocList.style.display = isExpanded ? 'none' : 'block'; 9 | tocTitle.classList.toggle('expanded', !isExpanded); 10 | }; 11 | 12 | tocTitle.addEventListener('click', toggleToC); 13 | } 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% block main_content %} 4 |
5 | {{ post_macros::page_header(title="404") }} 6 | Page not found :( 7 |
8 | {% endblock main_content %} 9 | -------------------------------------------------------------------------------- /templates/_giscus_script.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% import "macros/macros.html" as post_macros %} 2 | 3 | 4 | 5 | 6 | {% include "partials/header.html" %} 7 | 8 | 9 |
10 | {% include "partials/nav.html" %} 11 | 12 | {# Post page is the default #} 13 | {% block main_content %} 14 | Nothing here?! 15 | {% endblock main_content %} 16 | 17 | {% if page.extra.comment is defined %} 18 | {% set show_comment = page.extra.comment %} 19 | {% else %} 20 | {% set show_comment = false %} 21 | {% endif %} 22 | 23 | {% if show_comment %} 24 |
25 | {% include "_giscus_script.html" %} 26 | {% endif %} 27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/cards.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main_content %} 4 | {% if section.extra.section_path -%} 5 | {% set section = get_section(path=section.extra.section_path) %} 6 | {% endif -%} 7 | 8 | {{ post_macros::page_header(title=section.title) }} 9 | 10 |
11 | {%- if paginator %} 12 | {%- set show_pages = paginator.pages -%} 13 | {% else %} 14 | {%- set show_pages = section.pages -%} 15 | {% endif -%} 16 | 17 | {{ post_macros::cards_posts(pages=show_pages) }} 18 |
19 | 20 | {% if paginator %} 21 |
    22 | {% if paginator.previous %} 23 | 24 | 25 | 26 | {% endif %} 27 | 28 | {% if paginator.next %} 29 | 30 | 31 | 32 | {% endif %} 33 |
34 | {% endif %} 35 | {% endblock main_content %} 36 | -------------------------------------------------------------------------------- /templates/homepage.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main_content %} 4 | {{ post_macros::home_page(section=section) }} 5 | {% endblock main_content %} 6 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "section.html" %} 2 | -------------------------------------------------------------------------------- /templates/macros/macros.html: -------------------------------------------------------------------------------- 1 | {% macro list_tag_posts(pages, tag_name=false) %} 2 | {% if tag_name %} 3 | 6 | {% else %} 7 | 10 | {% endif %} 11 | 12 |
13 | {{ post_macros::list_posts(pages=pages) }} 14 |
15 | {% endmacro %} 16 | 17 | {% macro list_posts(pages) %} 18 |
    19 | {%- for page in pages %} 20 |
  • 21 |
    22 |
    23 | 24 | 25 |
    26 |

    27 | {{ page.title }} 28 | 29 | {% if page.draft %}DRAFT{% endif %} 30 |

    31 | 32 |
    33 |
    34 | {% if page.description %} 35 | {{ page.description }} 36 | {% elif page.summary %} 37 | {{ page.summary | safe }}… 38 | {% else %} 39 | {% set hide_read_more = true %} 40 | {% endif %} 41 |
    42 | 43 | {% if not hide_read_more %}Read more ⟶{% endif %} 44 |
    45 |
    46 |
    47 |
    48 |
  • 49 | {% endfor -%} 50 |
51 | {% endmacro list_posts %} 52 | 53 | {% macro list_terms(terms) %} 54 |
    55 | {%- for term in terms %} 56 |
    57 |

    58 | {{ term.name }} 59 |

    60 |
    61 | 62 | {% endfor -%} 63 |
64 | {% endmacro list_terms %} 65 | 66 | {% macro tags(page, short=false) %} 67 | {%- if page.taxonomies and page.taxonomies.tags %} 68 | 83 | {% endif -%} 84 | {% endmacro tags %} 85 | 86 | {% macro page_header(title) %} 87 | 90 | {% endmacro content %} 91 | 92 | {% macro home_page(section) %} 93 |
94 |
95 |
96 | {{ post_macros::page_header(title=section.title) }} 97 | {{ section.content | safe }} 98 |
99 |
100 |
101 | {% endmacro home_page %} 102 | 103 | {% macro content(page) %} 104 |
105 |
106 |
107 | {#

{{ page.title }}

#} 108 | {{ post_macros::page_header(title=page.title) }} 109 | 110 |
111 | {% if page.date %} 112 | Posted on 113 | {% endif %} 114 | 115 | {% if page.updated %} 116 | :: Updated on 117 | {% endif %} 118 | 119 | {% if page.extra.read_time %}:: Min Read{% endif %} 120 | 121 | {% if page.word_count %}:: {{ page.word_count }} Words{% endif %} 122 | 123 | {# Inline display of tags directly after the date #} 124 | {% if page.taxonomies and page.taxonomies.tags %} 125 | :: Tags: 126 | 127 | {%- for tag in page.taxonomies.tags %} 128 | 130 | {% if not loop.last %},{% endif %} 131 | {% endfor %} 132 | 133 | {% endif %} 134 | 135 | {# View the page on GitHub #} 136 | {% if page.extra.repo_view | default(value=config.extra.repo_view) | default(value=false) %} 137 | {# Use the page's repo_url if defined, otherwise use the global edit_repo_url #} 138 | {% if page.extra.repo_url is defined %} 139 | {% set repo_url = page.extra.repo_url %} 140 | {% elif config.extra.repo_url is defined %} 141 | {% set repo_url = config.extra.repo_url %} 142 | {% else %} 143 | {% set repo_url = false %} 144 | {% endif %} 145 | 146 | {% if repo_url %} 147 | {% set final_url = repo_url ~ page.relative_path %} 148 | :: Source Code 149 | {% endif %} 150 | {% endif %} 151 | 152 | {% if page.draft %}DRAFT{% endif %} 153 | 154 |
155 |
156 | 157 | {% if page.extra.tldr %} 158 |
159 | tl;dr: 160 | {{ page.extra.tldr }} 161 |
162 | {% endif %} 163 | 164 | {# Optional table of contents #} 165 | {% if config.extra.toc | default(value=false) %} 166 | {% if page.toc %} 167 |
168 |

Table of Contents

169 |
    170 | {% for h1 in page.toc %} 171 |
  • 172 | {{ h1.title }} 173 | {% if h1.children %} 174 |
      175 | {% for h2 in h1.children %} 176 |
    • 177 | {{ h2.title }} 178 |
    • 179 | 180 | {% if h2.children %} 181 |
        182 | {% for h3 in h2.children %} 183 |
      • 184 | {{ h3.title }} 185 |
      • 186 | {% endfor %} 187 |
      188 | {% endif %} 189 | {% endfor %} 190 |
    191 | {% endif %} 192 |
  • 193 | {% endfor %} 194 |
195 |
196 | {% endif %} 197 | {% endif %} 198 | 199 |
200 | {{ page.content | safe }} 201 |
202 |
203 |
204 | {% endmacro content %} 205 | 206 | {% macro cards_posts(pages) %} 207 |
208 | {%- for page in pages %} 209 |
210 | {% if page.extra.local_image %} 211 | {{ page.extra.local_image }} 214 | {% elif page.extra.remote_image %} 215 | {{ page.extra.remote_image }} 218 | {% else %} 219 |
220 | {% endif %} 221 | 222 |
223 |

224 | {% if page.extra.link_to %} 225 | {{ page.title }} 226 | {% else %} 227 | {{ page.title }} 228 | {% endif %} 229 |

230 | 231 |
232 | {%- if page.date %} 233 | 234 | {% endif -%} 235 | {% if page.draft %}DRAFT{% endif %} 236 |
237 | 238 |
239 | {% if page.description %}{{ page.description }}{% endif %} 240 |
241 |
242 |
243 | 244 | {% endfor -%} 245 |
246 | {% endmacro cards_posts %} 247 | -------------------------------------------------------------------------------- /templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main_content %} 4 | {{ post_macros::content(page=page) }} 5 | {% endblock main_content %} 6 | -------------------------------------------------------------------------------- /templates/partials/header.html: -------------------------------------------------------------------------------- 1 | {% import "macros/macros.html" as post_macros %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% if page.extra.meta %} 10 | 11 | {% for data in page.extra.meta %} 12 | 18 | {% endfor %} 19 | {% endif %} 20 | 21 | {# Site title #} 22 | {% set current_path = current_path | default(value="/") %} 23 | {% if current_path == "/" %} 24 | {{ config.title | default(value="Home") }} 25 | {% if not page_has_og_title %} 26 | 28 | {% endif %} 29 | 30 | {% else %} 31 | 32 | {% if page.title %} 33 | {{ page.title }} 34 | {% elif section.title %} 35 | {{ section.title }} 36 | {% elif config.title %} 37 | {{ config.title }} 38 | {% else %} 39 | Post 40 | {% endif %} 41 | 42 | 43 | {% if not page_has_og_title %} 44 | 46 | {% endif %} 47 | {% endif %} 48 | 49 | {% if not page_has_og_description %} 50 | {% if page.description %} 51 | 52 | {% elif config.description %} 53 | 54 | {% endif %} 55 | {% endif %} 56 | 57 | {% if not page_has_description %} 58 | {% if page.description %} 59 | 60 | {% elif config.description %} 61 | 62 | {% endif %} 63 | {% endif %} 64 | 65 | {# Favicon #} 66 | {% if config.extra.favicon %}{% endif %} 67 | 68 | {# Font from cdn or disk #} 69 | {% if config.extra.use_cdn | default(value=false) %} 70 | 72 | 74 | {% else %} 75 | 76 | {% endif %} 77 | 78 | {# Analytics #} 79 | {% if config.extra.analytics.enabled %} 80 | {% if config.extra.analytics.umami.website_id %} 81 | {% set website_id = config.extra.analytics.umami.website_id %} 82 | {% set host_url = config.extra.analytics.umami.host_url | default(value="https://api-gateway.umami.dev/") %} 83 | 84 | 88 | {% endif %} 89 | 90 | {% if config.extra.analytics.goatcounter.user %} 91 | {% set user = config.extra.analytics.goatcounter.user %} 92 | {% set host = config.extra.analytics.goatcounter.host | default(value="goatcounter.com") %} 93 | 94 | 97 | 101 | {% endif %} 102 | {% endif %} 103 | 104 | {# Fancy Codeblock #} 105 | {% if config.extra.fancy_code %}{% endif %} 106 | 107 | {# Table of contents #} 108 | {% if config.extra.toc | default(value=false) %}{% endif %} 109 | 110 | {# Dynamic Note #} 111 | {% if config.extra.dynamic_note | default(value=false) %} 112 | 113 | {% endif %} 114 | 115 | {% if config.extra.mathjax | default(value=false) %} 116 | {% if config.extra.mathjax_dollar_inline_enable | default(value=false) %} 117 | 124 | {% endif %} 125 | 127 | {% endif %} 128 | 129 | {# RSS #} 130 | 134 | 135 | 136 | {% set theme = config.extra.theme | default(value="toggle") %} 137 | {% if theme == "dark" %} 138 | 141 | {% elif theme == "light" %} 142 | 145 | {% elif theme == "auto" %} 146 | 149 | 153 | {% elif theme == "toggle" %} 154 | 157 | 161 | {% endif %} 162 | 163 | 164 | 165 | {% if theme == "auto" or theme == "toggle" %} 166 | 167 | 168 | {% if theme == "auto" %} 169 | 176 | {% else %} 177 | 178 | {% endif %} 179 | {% endif %} 180 | 181 | 182 | 186 | 187 | {% if config.extra.stylesheets %} 188 | {% for stylesheet in config.extra.stylesheets %} 189 | 190 | {% endfor %} 191 | {% endif %} 192 | 193 | {# Search #} 194 | {%- if config.build_search_index -%} 195 | {%- if config.search.index_format -%} 196 | {%- set search_index_format = config.search.index_format -%} 197 | {%- elif config.extra.index_format -%} 198 | {# Necessary to support Zola 0.17.X, as it doesn't have access to config.search.index_format #} 199 | {# See: https://github.com/getzola/zola/issues/2165 #} 200 | {%- set search_index_format = config.extra.index_format -%} 201 | {%- else -%} 202 | {%- set search_index_format = "elasticlunr_json" -%} 203 | {%- endif -%} 204 | 205 | {%- if search_index_format == "elasticlunr_javascript" -%} 206 | 208 | {%- endif -%} 209 | 210 | {# Main search script #} 211 | 213 | {%- endif -%} 214 | 215 | -------------------------------------------------------------------------------- /templates/partials/nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% if config.extra.logo %} 4 | 7 | {% else %} 8 | {{ config.title }} 9 | {% endif %} 10 | 11 | 12 |
13 | {% for social in config.extra.socials %} 14 | 18 | {% endfor %} 19 |
20 |
21 | 22 | 83 |
84 | -------------------------------------------------------------------------------- /templates/section.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main_content %} 4 | {% if section.extra.section_path -%} 5 | {% set section = get_section(path=section.extra.section_path) %} 6 | {% endif -%} 7 | 8 | {% block title %} 9 | {{ post_macros::page_header(title=section.title) }} 10 | {% endblock title %} 11 | 12 | {% block post_list %} 13 |
14 | {%- if paginator %} 15 | {%- set show_pages = paginator.pages -%} 16 | {% else %} 17 | {%- set show_pages = section.pages -%} 18 | {% endif -%} 19 | 20 | {{ post_macros::list_posts(pages=show_pages) }} 21 |
22 | {% endblock post_list %} 23 | 24 | {% if paginator %} 25 |
    26 | {% if paginator.previous %} 27 | 28 | 29 | 30 | {% endif %} 31 | 32 | {% if paginator.next %} 33 | 34 | 35 | 36 | {% endif %} 37 |
38 | {% endif %} 39 | {% endblock main_content %} 40 | -------------------------------------------------------------------------------- /templates/shortcodes/mermaid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |   {{ body | safe }}
5 | 
6 | -------------------------------------------------------------------------------- /templates/shortcodes/note.html: -------------------------------------------------------------------------------- 1 |
2 | {% if clickable | default(value=false) %} 3 | 10 | 11 | {% if hidden | default(value=false) %} 12 | 29 | -------------------------------------------------------------------------------- /templates/taxonomy_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main_content %} 4 |
5 | {% block title %} 6 | {{ post_macros::page_header(title="Tags") }} 7 | {% endblock title %} 8 | 9 |
10 | 19 |
20 |
21 | {% endblock main_content %} 22 | -------------------------------------------------------------------------------- /templates/taxonomy_single.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | 3 | {% block main_content %} 4 | 5 | {{ post_macros::list_tag_posts(pages=term.pages, tag_name=term.name) }} 6 | 7 | {% endblock main_content %} 8 | -------------------------------------------------------------------------------- /theme.toml: -------------------------------------------------------------------------------- 1 | name = "apollo" 2 | description = "Modern and minimalistic blog theme" 3 | min_version = "0.14.0" 4 | license = "MIT" 5 | homepage = "https://github.com/not-matthias/apollo" 6 | demo = "https://not-matthias.github.io/apollo" 7 | 8 | # Any variable there can be overridden in the end user `config.toml` 9 | # You don't need to prefix variables by the theme name but as this will 10 | # be merged with user data, some kind of prefix or nesting is preferable 11 | # Use snake_casing to be consistent with the rest of Zola 12 | [extra] 13 | 14 | [author] 15 | name = "not-matthias" 16 | homepage = "https://github.com/not-matthias" 17 | -------------------------------------------------------------------------------- /treefmt.toml: -------------------------------------------------------------------------------- 1 | walk = "git" 2 | excludes = ["static/*", "content/*"] 3 | 4 | [formatter.nix] 5 | command = "alejandra" 6 | includes = ["*.nix"] 7 | 8 | [formatter.djlint] 9 | command = "djlint" 10 | options = ["--preserve-blank-lines", "--reformat"] 11 | includes = ["*.html"] 12 | 13 | [formatter.scss] 14 | command = "prettier" 15 | includes = ["*.scss", "*.sass"] 16 | --------------------------------------------------------------------------------