├── .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 | 
11 |
12 |
13 |
14 |
15 | Light theme
16 |
17 | 
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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------
/static/icons/social/orcid.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 = ``;
4 | const errorIcon = ``;
7 | const copyIcon = ``;
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 |
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 |
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 |
48 |
49 | {% endfor -%}
50 |
51 | {% endmacro list_posts %}
52 |
53 | {% macro list_terms(terms) %}
54 |
55 | {%- for term in terms %}
56 |
61 |
62 | {% endfor -%}
63 |
64 | {% endmacro list_terms %}
65 |
66 | {% macro tags(page, short=false) %}
67 | {%- if page.taxonomies and page.taxonomies.tags %}
68 |
69 | {%- if short %}
70 | ::
71 | {%- set sep = "," -%}
72 | {% else %}
73 | :: tags:
74 | {%- set sep = " " -%}
75 | {% endif -%}
76 | {%- for tag in page.taxonomies.tags %}
77 | #{{ tag }}
79 | {%- if not loop.last %}{{ sep | safe }}
80 | {% endif -%}
81 | {% endfor -%}
82 |
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 |
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 |
 }})
214 | {% elif page.extra.remote_image %}
215 |

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 |
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 |
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 |
13 | {% else %}
14 |
15 | {% endif %}
16 | {{ body | markdown | safe }}
17 |
18 | {% else %}
19 |
26 |
{{ body | markdown | safe }}
27 | {% endif %}
28 |
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 |
--------------------------------------------------------------------------------