jekyll-theme-doctored 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4fc26b437cc62595c064e308b8b371341f45b4f5e2b7c6fa15073a58b60eb2c0
4
+ data.tar.gz: 070d0a9332e3c6efdab9e8dcbf8ab287a26a7022484f817d40a42ad4e2504fd2
5
+ SHA512:
6
+ metadata.gz: 119185681985c9740c9ab3d7ca417aba336cd4e43de2e8eac649e9efd6e652061f92496cb3d4755763cd472f50071eacc95b16a44ab63078c3c5c9fe5cf74034
7
+ data.tar.gz: f35ead47ceb577af4d3e4c540a0541a7163af921991de6e06a04bb941a3cea6358d2f589aa6b34d333df691e140936e4d32a024600688e3bc58d8be6b11f2d80
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
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.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # 🎩 Doctored
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/gemspec.svg)](https://rubygems.org/gems/doctored)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![wakatime](https://wakatime.com/badge/user/7482ea9d-3085-4e9b-95ad-1ca78a14d948/project/f57b75b4-8209-4d96-bb52-b673574bed86.svg)](https://wakatime.com/badge/user/7482ea9d-3085-4e9b-95ad-1ca78a14d948/project/f57b75b4-8209-4d96-bb52-b673574bed86)
6
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/matmanna/doctored/jekyll.yml)
7
+
8
+
9
+ A blog theme prioritizing uniqueness, technical complexity, and sophistication.
10
+
11
+ **Demo Deployment:** https://matmanna.github.io/doctored
12
+
13
+ ## 🌟 Overview
14
+
15
+ Made using Jekyll and Tailwind following the [Tonic](https://tonic.hackclub.com) starter guide and [template](https://github.com/hackclub/tonic-starter). No other jekyll theme really felt like it matched what I would have wanted so I created my own!
16
+
17
+ **💫 Feature highlights:**
18
+
19
+ - 20+ color schemes & dark/light/system modes
20
+ - Projects, blog, now, and contact pages
21
+ - GFM support (tables, admonitions, etc.)
22
+ - Git metadata access
23
+ - Floating Table of Contents
24
+ - Side & foot notes
25
+ - RSS feed & sitemap
26
+ - Responsive nav, footer sections
27
+
28
+ ## 📸 Demo Images:
29
+
30
+ | <img width="1919" height="1020" alt="image" src="https://github.com/user-attachments/assets/a130fd59-c673-4f4a-a676-3fd0bfb2473b" /> | <img width="1914" height="1018" alt="image" src="https://github.com/user-attachments/assets/2eeb6c33-fe38-46a4-8d54-c1fad985ed3b" /> |
31
+ | ----- | ---- |
32
+ | <img width="1919" height="1019" alt="image" src="https://github.com/user-attachments/assets/0919fd1d-2ed2-4ded-829a-6b504f939515" /> | <img width="1917" height="1014" alt="image" src="https://github.com/user-attachments/assets/dadb5548-a2c0-4f22-a2e1-6b2f67bd29f9" /> |
33
+
34
+ ## ⌨️ Usage
35
+
36
+ > [!IMPORTANT]
37
+ > Developing a site with doctored requires having Ruby, RubyGems, GCC, Make Bundle, Python, Node.js, and npm installed on your system.
38
+
39
+ ### ▶️ Running
40
+
41
+ Run `npm install` and `npm run dev` to develop. Run `npm run build` to build. Postinstall and other scripts have been configured to use `bundle install`, `bundle exec jekyll serve --watch`, and `bundle exec jekyll build` respectively.
42
+
43
+ ### 🖼️ Formatting posts
44
+
45
+ To post a new post, run `npm run post markdown_file_path_in__drafts`, which formats it with side/foot notes then moves it to `_posts/` (or root if page!)
46
+
47
+ Based on [@JacobU](https://github.com/jacobu)'s `postMarkdown` python script, doctored supports writing your posts/pages in a simple markdown file within `_drafts` before running the `post` script to format it in the following ways:
48
+
49
+ - **Foot/sidenotes:**
50
+
51
+ To add a foot/side note, include the following in your draft markdown:
52
+ ```html
53
+ <small>Richly-formatted (html) content of note</small>
54
+ ```
55
+
56
+ ## 🙌 Contributing
57
+
58
+ Please create Issues and PRs for bugs/feature requests and updated code respectively.
59
+
60
+ ### 📜 License
61
+
62
+ Doctored is licensed under the MIT, which means you can do (almost) anything with it!!
data/_config.yml ADDED
@@ -0,0 +1,50 @@
1
+ title: Doctored
2
+ description: "A blog theme prioritizing uniqueness, complexity, and sophistication."
3
+ github: "https://github.com/matmanna/doctored"
4
+ url: "https://matmanna.github.io/doctored"
5
+ encoding: utf-8
6
+
7
+ plugins:
8
+ - jekyll-postcss
9
+ - jekyll-git_metadata
10
+ - kramdown-parser-gfm
11
+ - jekyll-gfm-admonitions
12
+ - jekyll-sitemap
13
+ - jekyll-feed
14
+
15
+ feed:
16
+ icon: /assets/images/favicon.png
17
+
18
+ redcarpet:
19
+ extensions: ["with_toc_data"]
20
+
21
+ collections:
22
+ projects:
23
+ output: true
24
+ pages:
25
+ permalink: /:title/
26
+ output: true
27
+
28
+ exclude:
29
+ - README.md
30
+ - LICENSE
31
+ - "*.gem"
32
+ - "*.gemspec"
33
+ - vendor
34
+ - Gemfile.lock
35
+ - .gitignore
36
+ - bin
37
+ - node_modules
38
+ - package-lock.json
39
+ - postcss.config.js
40
+
41
+ sass:
42
+ sourcemap: never
43
+
44
+ postcss:
45
+ cache: false
46
+
47
+ keep_files:
48
+ - assets/css/tailwind.css
49
+ - assetss/css/tailwind.css.map
50
+ - assets/js
@@ -0,0 +1,174 @@
1
+ {% capture headingsWorkspace %}
2
+ {% comment %}
3
+ Copyright (c) 2018 Vladimir "allejo" Jimenez
4
+
5
+ Permission is hereby granted, free of charge, to any person
6
+ obtaining a copy of this software and associated documentation
7
+ files (the "Software"), to deal in the Software without
8
+ restriction, including without limitation the rights to use,
9
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the
11
+ Software is furnished to do so, subject to the following
12
+ conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ OTHER DEALINGS IN THE SOFTWARE.
25
+ {% endcomment %}
26
+ {% comment %}
27
+ Version 1.0.13
28
+ https://github.com/allejo/jekyll-anchor-headings
29
+
30
+ "Be the pull request you wish to see in the world." ~Ben Balter
31
+
32
+ Usage:
33
+ {% include anchor_headings.html html=content anchorBody="#" %}
34
+
35
+ Parameters:
36
+ * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
37
+
38
+ Optional Parameters:
39
+ * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
40
+ * headerAttrs (string) : '' - Any custom HTML attributes that will be added to the heading tag; you may NOT use `id`;
41
+ the `%heading%` and `%html_id%` placeholders are available
42
+ * anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`;
43
+ the `%heading%` and `%html_id%` placeholders are available
44
+ * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
45
+ * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
46
+ * anchorTitle (string) : '' - The `title` attribute that will be used for anchors
47
+ * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
48
+ * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
49
+ * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
50
+ * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
51
+ * generateId (true) : false - Set to true if a header without id should generate an id to use.
52
+
53
+ Output:
54
+ The original HTML with the addition of anchors inside of all of the h1-h6 headings.
55
+ {% endcomment %}
56
+
57
+ {% assign minHeader = include.h_min | default: 1 %}
58
+ {% assign maxHeader = include.h_max | default: 6 %}
59
+ {% assign beforeHeading = include.beforeHeading %}
60
+ {% assign headerAttrs = include.headerAttrs %}
61
+ {% assign nodes = include.html | split: '<h' %}
62
+
63
+ {% capture edited_headings %}{% endcapture %}
64
+
65
+ {% for _node in nodes %}
66
+ {% capture node %}{{ _node | strip }}{% endcapture %}
67
+
68
+ {% if node == "" %}
69
+ {% continue %}
70
+ {% endif %}
71
+
72
+ {% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
73
+ {% assign headerLevel = nextChar | times: 1 %}
74
+
75
+ <!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it -->
76
+ {% if headerLevel == 0 %}
77
+ <!-- Split up the node based on closing angle brackets and get the first one. -->
78
+ {% assign firstChunk = node | split: '>' | first %}
79
+
80
+ <!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' -->
81
+ {% unless firstChunk contains '<' %}
82
+ {% capture node %}<h{{ node }}{% endcapture %}
83
+ {% endunless %}
84
+
85
+ {% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
86
+ {% continue %}
87
+ {% endif %}
88
+
89
+ {% capture _closingTag %}</h{{ headerLevel }}>{% endcapture %}
90
+ {% assign _workspace = node | split: _closingTag %}
91
+ {% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
92
+ {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
93
+ {% assign escaped_header = header | strip_html | strip %}
94
+
95
+ {% assign _classWorkspace = _workspace[0] | split: 'class="' %}
96
+ {% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
97
+ {% assign _html_class = _classWorkspace[0] %}
98
+
99
+ {% if _html_class contains "no_anchor" %}
100
+ {% assign skip_anchor = true %}
101
+ {% else %}
102
+ {% assign skip_anchor = false %}
103
+ {% endif %}
104
+
105
+ {% assign _idWorkspace = _workspace[0] | split: 'id="' %}
106
+ {% if _idWorkspace[1] %}
107
+ {% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
108
+ {% assign html_id = _idWorkspace[0] %}
109
+ {% assign h_attrs = headerAttrs %}
110
+ {% elsif include.generateId %}
111
+ <!-- If the header did not have an id we create one. -->
112
+ {% assign html_id = escaped_header | slugify %}
113
+ {% if html_id == "" %}
114
+ {% assign html_id = false %}
115
+ {% endif %}
116
+ <!-- Append the generated id to other potential header attributes. -->
117
+ {% capture h_attrs %}{{ headerAttrs }} id="%html_id%"{% endcapture %}
118
+ {% endif %}
119
+
120
+ <!-- Build the anchor to inject for our heading -->
121
+ {% capture anchor %}{% endcapture %}
122
+
123
+ {% if skip_anchor == false and html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
124
+ {% if h_attrs %}
125
+ {% capture _hAttrToStrip %}{{ _hAttrToStrip | split: '>' | first }} {{ h_attrs | strip | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}>{% endcapture %}
126
+ {% endif %}
127
+
128
+ {% capture anchor %}href="#{{ html_id }}"{% endcapture %}
129
+
130
+ {% if include.anchorClass %}
131
+ {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
132
+ {% endif %}
133
+
134
+ {% if include.anchorTitle %}
135
+ {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', escaped_header }}"{% endcapture %}
136
+ {% endif %}
137
+
138
+ {% if include.anchorAttrs %}
139
+ {% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}{% endcapture %}
140
+ {% endif %}
141
+
142
+ {% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', escaped_header | default: '' }}</a>{% endcapture %}
143
+
144
+ <!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it -->
145
+ {% if beforeHeading %}
146
+ {% capture anchor %}{{ anchor }} {% endcapture %}
147
+ {% else %}
148
+ {% capture anchor %} {{ anchor }}{% endcapture %}
149
+ {% endif %}
150
+ {% endif %}
151
+
152
+ {% capture new_heading %}
153
+ <h{{ _hAttrToStrip }}
154
+ {{ include.bodyPrefix }}
155
+ {% if beforeHeading %}
156
+ {{ anchor }}{{ header }}
157
+ {% else %}
158
+ {{ header }}{{ anchor }}
159
+ {% endif %}
160
+ {{ include.bodySuffix }}
161
+ </h{{ headerLevel }}>
162
+ {% endcapture %}
163
+
164
+ <!--
165
+ If we have content after the `</hX>` tag, then we'll want to append that here so we don't lost any content.
166
+ -->
167
+ {% assign chunkCount = _workspace | size %}
168
+ {% if chunkCount > 1 %}
169
+ {% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %}
170
+ {% endif %}
171
+
172
+ {% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
173
+ {% endfor %}
174
+ {% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
@@ -0,0 +1,33 @@
1
+ <div class="flex flex-row justify-between w-full px-5 md:px-10 gap-8">
2
+ <div class="flex flex-col space-y-4 sm:flex-grow max-w-[50%] font-sans">
3
+ <h3 class="block">{{ site.data.foot.emoji }} {{ site.data.foot.title }} | <span class="dark:text-primary-600 text-primary-700">{{ site.description }}</span></h3>
4
+ <div class="flex flex-row flex-wrap gap-4 dark:text-primary-600">
5
+ <label for="theme">Color mode:</label>
6
+
7
+ <select name="theme" id="theme-dropdown" class="flex flex-shrink w-fit bg-transparent">
8
+ <option value="dark">Dark</option>
9
+ <option value="light">Light</option>
10
+ <option value="system">System</option>
11
+ </select>
12
+ </div>
13
+ <p>Site built on {{ site.git.last_commit.commit_date }} from
14
+ <a
15
+ href="{{ site.github }}/commit/{{ site.git.last_commit.long_sha }}"><code>{{ site.git.last_commit.short_sha }}</code></a>
16
+ </p>
17
+ </div>
18
+ <div class="flex flex-row flex-wrap justify-between flex-grow gap-4">
19
+ <ul class="flex flex-col space-y-4">
20
+ <li class="font-bold">quick links</li>
21
+ {% for entry in site.data.nav.pages %}
22
+ <li><a href="{{ entry.url | relative_url }}">/{{ entry.title }}</a></li>
23
+ {% endfor %}
24
+ </ul>
25
+ <ul class="flex flex-col space-y-4">
26
+ <li class="font-bold" id="contact">socials</li>
27
+
28
+ {% for social in site.data.foot.socials %}
29
+ <li><a href="{{ social.url }}">:{{ social.title }}</a></li>
30
+ {% endfor %}
31
+ </ul>
32
+ </div>
33
+ </div>
@@ -0,0 +1,33 @@
1
+ <head>
2
+ <title>{{ page.title }} | {{ site.title }}</title>
3
+
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <script>
7
+ function applyTheme(newTheme, triggeredByDropdown = false) {
8
+ const isDarkMode = newTheme === 'dark' || (localStorage.theme == 'dark' && !(newTheme === 'light')) || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
9
+ document.documentElement.classList.toggle(
10
+ 'dark',
11
+ isDarkMode
12
+ )
13
+
14
+ const toggle = document.getElementById('theme-toggle');
15
+ const dropdown = document.getElementById('theme-dropdown');
16
+
17
+ if (toggle) {
18
+ toggle.innerHTML =
19
+ localStorage.theme == "dark" ? '<i class="fas fa-moon"></i>' : !localStorage.theme ? '<i class="fas fa-laptop"></i>' : '<i class="fas fa-sun"></i>';
20
+ }
21
+ if (dropdown) {
22
+ if (!triggeredByDropdown) {
23
+ dropdown.dataset.changed = new Date().toISOString();
24
+ }
25
+ dropdown.value = localStorage.theme || "system";
26
+ }
27
+ }
28
+ applyTheme();
29
+ </script>
30
+ <link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
31
+ <link rel="icon" href="{{ '/assets/images/favicon.ico' | relative_url }}" type="image/x-icon">
32
+ <script src="https://kit.fontawesome.com/a7b73c75c9.js" crossorigin="anonymous"></script>
33
+ </head>
@@ -0,0 +1,18 @@
1
+ <div class="flex flex-row flex-wrap justify-between w-full gap-4">
2
+ <div class="flex flex-row gap-1">
3
+ {{site.data.nav.emoji}}
4
+ <a href="{{ '/' | relative_url }}"><h3 class="font-sans">{{ site.data.nav.title }}</h3></a>
5
+ </div>
6
+ <ul class="flex flex-row flex-wrap gap-3 sm:lips:gap-4 text-sm sm:text-base">
7
+ {% for entry in site.data.nav.pages %}
8
+ <li>
9
+ <a href="{{ entry.url | relative_url }}"
10
+ {% if page.url == entry.url or (page.url == '/' and entry.url == '/') %}
11
+ class="font-bold"
12
+ {% endif %}
13
+ >/{{ entry.title }}</a>
14
+ </li>
15
+ {% endfor %}
16
+ <li id="theme-toggle" class="dark:text-primary-400 text-primary-800 "></li>
17
+ </ul>
18
+ </div>
@@ -0,0 +1,177 @@
1
+ <div id="toc-container" class="relative pt-6">
2
+ <div id="toc-placeholder" class="blur-sm my-4 rounded-md w-full bg-primary-200 dark:bg-primary-800"></div>
3
+ <div id="table-of-contents" class="bg-primary-200 dark:bg-primary-800 rounded-md p-2 w-full transition-all duration-100 ease-in-out">
4
+ <h4 class="font-bold my-0 mt-2 ml-3"> {{ page.title }} </h4>
5
+ {% include toc.html html=content anchor_class="toc-link" %}
6
+ </div>
7
+
8
+ <div id="toc-navbar" class="hidden bg-primary-300 dark:bg-primary-950 rounded-t-md p-2 w-full flex justify-between items-center opacity-0 transition-all duration-100 ease-in-out transform -translate-y-4">
9
+ <div class="flex items-center gap-2">
10
+ <span class="text-lg">{{ site.data.nav.emoji }}</span>
11
+ <span class="font-bold">{{ site.data.nav.title }}</span>
12
+ </div>
13
+ <div class="relative group">
14
+ <button class="flex items-center gap-1 px-2 py-1 rounded hover:bg-primary-400 dark:hover:bg-primary-700 transition-colors">
15
+ Navigate <i class="fa-solid fa-chevron-down text-xs"></i>
16
+ </button>
17
+ <div class="absolute right-0 mt-0 w-40 bg-white dark:bg-primary-900 rounded-md shadow-lg overflow-hidden z-10 hidden group-hover:block">
18
+ <div class="py-1">
19
+ {% for page in site.data.nav.pages %}
20
+ <a href="{{ page.url | relative_url }}" class="block px-4 py-2 text-sm hover:bg-primary-200 dark:hover:bg-primary-700">
21
+ /{{ page.title }}
22
+ </a>
23
+ {% endfor %}
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <script>
31
+ document.addEventListener('DOMContentLoaded', function() {
32
+ const toc = document.getElementById('table-of-contents');
33
+ const tocNavbar = document.getElementById('toc-navbar');
34
+ const placeholder = document.getElementById('toc-placeholder');
35
+ const tocContainer = document.getElementById('toc-container');
36
+ let originalRect = null;
37
+ let isFixed = false;
38
+ let targetPosition = { x: 0, y: 0, scale: 1 };
39
+
40
+ function updateOriginalPosition() {
41
+ toc.style.position = 'static';
42
+ toc.querySelector('h4').innerHTML = 'Table of Contents:';
43
+ toc.style.transform = 'none';
44
+ placeholder.style.display = 'none';
45
+ tocNavbar.style.display = 'none';
46
+
47
+ void toc.offsetWidth;
48
+
49
+ originalRect = toc.getBoundingClientRect();
50
+ placeholder.style.height = originalRect.height + 'px';
51
+
52
+ const fixedTop = 80;
53
+
54
+ targetPosition = {
55
+ x: 0,
56
+ y: fixedTop,
57
+ width: originalRect.width
58
+ };
59
+ }
60
+
61
+ function toggleFixedPosition() {
62
+ if (!originalRect) {
63
+ updateOriginalPosition();
64
+ }
65
+
66
+ if (window.innerWidth >= 1280) {
67
+ const shouldBeFixed = window.scrollY > (tocContainer.offsetTop - 5);
68
+
69
+ if (shouldBeFixed !== isFixed) {
70
+ isFixed = shouldBeFixed;
71
+
72
+ if (isFixed) {
73
+ const viewportWidth = window.innerWidth;
74
+ let fixedLeft, maxWidth;
75
+
76
+ if (viewportWidth > 1600) {
77
+ fixedLeft = viewportWidth * 0.03;
78
+ maxWidth = Math.min(450, viewportWidth * 0.25);
79
+ } else if (viewportWidth > 1280) {
80
+ fixedLeft = viewportWidth * 0.025;
81
+ maxWidth = Math.min(400, viewportWidth * 0.22);
82
+ } else {
83
+ fixedLeft = viewportWidth * 0.03;
84
+ maxWidth = Math.min(350, viewportWidth * 0.2);
85
+ }
86
+ toc.querySelector('h4').innerHTML = '{{ page.title }}';
87
+
88
+ targetPosition.x = fixedLeft;
89
+ targetPosition.width = maxWidth;
90
+
91
+ toc.style.position = 'fixed';
92
+ toc.style.top = targetPosition.y + 'px';
93
+ toc.style.left = targetPosition.x + 'px';
94
+ toc.style.width = targetPosition.width + 'px';
95
+ toc.style.borderTopLeftRadius = '0';
96
+ toc.style.borderTopRightRadius = '0';
97
+ placeholder.style.display = 'block';
98
+
99
+ tocNavbar.style.display = 'flex';
100
+ tocNavbar.style.position = 'fixed';
101
+ tocNavbar.style.top = (targetPosition.y - tocNavbar.offsetHeight) + 'px';
102
+ tocNavbar.style.left = targetPosition.x + 'px';
103
+ tocNavbar.style.width = targetPosition.width + 'px';
104
+ tocNavbar.style.opacity = '0';
105
+ tocNavbar.style.transform = 'translateY(-16px)';
106
+
107
+ void tocNavbar.offsetWidth;
108
+
109
+ requestAnimationFrame(() => {
110
+ tocNavbar.style.opacity = '1';
111
+ tocNavbar.style.transform = 'translateY(0)';
112
+ });
113
+
114
+ } else {
115
+ tocNavbar.style.opacity = '0';
116
+ tocNavbar.style.transform = 'translateY(-16px)';
117
+
118
+
119
+ setTimeout(() => {
120
+ toc.style.position = 'absolute';
121
+ toc.style.top = placeholder.offsetTop + 'px';
122
+ toc.style.left = '';
123
+ toc.style.width = '';
124
+ toc.style.maxWidth = '';
125
+ toc.querySelector('h4').innerHTML = 'Table of Contents:';
126
+ toc.style.borderTopLeftRadius = '';
127
+ toc.style.borderTopRightRadius = '';
128
+
129
+ tocNavbar.style.display = 'none';
130
+
131
+ void toc.offsetWidth;
132
+
133
+ setTimeout(() => {
134
+ placeholder.style.display = 'none';
135
+ toc.style.position = 'static';
136
+ }, 50);
137
+ }, 100);
138
+ }
139
+ }
140
+ } else if (isFixed) {
141
+ isFixed = false;
142
+ toc.style.position = 'static';
143
+ toc.style.width = 'auto';
144
+ toc.style.transform = 'none';
145
+ placeholder.style.display = 'none';
146
+ tocNavbar.style.display = 'none';
147
+ }
148
+ }
149
+
150
+ let ticking = false;
151
+ window.addEventListener('scroll', function() {
152
+ if (!ticking) {
153
+ window.requestAnimationFrame(function() {
154
+ toggleFixedPosition();
155
+ ticking = false;
156
+ });
157
+ ticking = true;
158
+ }
159
+ });
160
+
161
+ let scrollTimeout;
162
+ window.addEventListener('scroll', () => {
163
+ clearTimeout(scrollTimeout);
164
+ scrollTimeout = setTimeout(() => {
165
+ toggleFixedPosition();
166
+ }, 50);
167
+ }, { passive: true });
168
+
169
+ window.addEventListener('resize', function() {
170
+ updateOriginalPosition();
171
+ toggleFixedPosition();
172
+ });
173
+
174
+ updateOriginalPosition();
175
+ toggleFixedPosition();
176
+ });
177
+ </script>
@@ -0,0 +1,189 @@
1
+ {% capture tocWorkspace %}
2
+ {% comment %}
3
+ Copyright (c) 2017 Vladimir "allejo" Jimenez
4
+
5
+ Permission is hereby granted, free of charge, to any person
6
+ obtaining a copy of this software and associated documentation
7
+ files (the "Software"), to deal in the Software without
8
+ restriction, including without limitation the rights to use,
9
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the
11
+ Software is furnished to do so, subject to the following
12
+ conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ OTHER DEALINGS IN THE SOFTWARE.
25
+ {% endcomment %}
26
+ {% comment %}
27
+ Version 1.2.1
28
+ https://github.com/allejo/jekyll-toc
29
+
30
+ "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe
31
+
32
+ Usage:
33
+ {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %}
34
+
35
+ Parameters:
36
+ * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
37
+
38
+ Optional Parameters:
39
+ * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC
40
+ * class (string) : '' - a CSS class assigned to the TOC
41
+ * id (string) : '' - an ID to assigned to the TOC
42
+ * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored
43
+ * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored
44
+ * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list
45
+ * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level
46
+ * submenu_class (string) : '' - add custom class(es) for each child group of headings; has support for '%level%' placeholder which is the current "submenu" heading level
47
+ * base_url (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content
48
+ * anchor_class (string) : '' - add custom class(es) for each anchor element
49
+ * skip_no_ids (bool) : false - skip headers that do not have an `id` attribute
50
+ * flat_toc (bool) : false - when set to true, the TOC will be a single level list
51
+
52
+ Output:
53
+ An ordered or unordered list representing the table of contents of a markdown block. This snippet will only
54
+ generate the table of contents and will NOT output the markdown given to it
55
+ {% endcomment %}
56
+
57
+ {% capture newline %}
58
+ {% endcapture %}
59
+ {% assign newline = newline | rstrip %} <!-- Remove the extra spacing but preserve the newline -->
60
+
61
+ {% capture deprecation_warnings %}{% endcapture %}
62
+
63
+ {% if include.baseurl %}
64
+ {% capture deprecation_warnings %}{{ deprecation_warnings }}<!-- jekyll-toc :: "baseurl" has been deprecated, use "base_url" instead -->{{ newline }}{% endcapture %}
65
+ {% endif %}
66
+
67
+ {% if include.skipNoIDs %}
68
+ {% capture deprecation_warnings %}{{ deprecation_warnings }}<!-- jekyll-toc :: "skipNoIDs" has been deprecated, use "skip_no_ids" instead -->{{ newline }}{% endcapture %}
69
+ {% endif %}
70
+
71
+ {% capture jekyll_toc %}{% endcapture %}
72
+ {% assign orderedList = include.ordered | default: false %}
73
+ {% assign flatToc = include.flat_toc | default: false %}
74
+ {% assign baseURL = include.base_url | default: include.baseurl | default: '' %}
75
+ {% assign skipNoIDs = include.skip_no_ids | default: include.skipNoIDs | default: false %}
76
+ {% assign minHeader = include.h_min | default: 1 %}
77
+ {% assign maxHeader = include.h_max | default: 6 %}
78
+ {% assign nodes = include.html | strip | split: '<h' %}
79
+
80
+ {% assign firstHeader = true %}
81
+ {% assign currLevel = 0 %}
82
+ {% assign lastLevel = 0 %}
83
+
84
+ {% capture listModifier %}{% if orderedList %}ol{% else %}ul{% endif %}{% endcapture %}
85
+
86
+ {% for node in nodes %}
87
+ {% if node == "" %}
88
+ {% continue %}
89
+ {% endif %}
90
+
91
+ {% assign currLevel = node | replace: '"', '' | slice: 0, 1 | times: 1 %}
92
+
93
+ {% if currLevel < minHeader or currLevel > maxHeader %}
94
+ {% continue %}
95
+ {% endif %}
96
+
97
+ {% assign _workspace = node | split: '</h' %}
98
+
99
+ {% assign _idWorkspace = _workspace[0] | split: 'id="' %}
100
+ {% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
101
+ {% assign htmlID = _idWorkspace[0] %}
102
+
103
+ {% assign _classWorkspace = _workspace[0] | split: 'class="' %}
104
+ {% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
105
+ {% assign htmlClass = _classWorkspace[0] %}
106
+
107
+ {% if htmlClass contains "no_toc" %}
108
+ {% continue %}
109
+ {% endif %}
110
+
111
+ {% if firstHeader %}
112
+ {% assign minHeader = currLevel %}
113
+ {% endif %}
114
+
115
+ {% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
116
+ {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
117
+
118
+ {% if include.item_class and include.item_class != blank %}
119
+ {% capture listItemClass %} class="{{ include.item_class | replace: '%level%', currLevel | split: '.' | join: ' ' }}"{% endcapture %}
120
+ {% endif %}
121
+
122
+ {% if include.submenu_class and include.submenu_class != blank %}
123
+ {% assign subMenuLevel = currLevel | minus: 1 %}
124
+ {% capture subMenuClass %} class="{{ include.submenu_class | replace: '%level%', subMenuLevel | split: '.' | join: ' ' }}"{% endcapture %}
125
+ {% endif %}
126
+
127
+ {% capture anchorBody %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %}
128
+
129
+ {% if htmlID %}
130
+ {% capture anchorAttributes %} href="{% if baseURL %}{{ baseURL }}{% endif %}#{{ htmlID }}"{% endcapture %}
131
+
132
+ {% if include.anchor_class %}
133
+ {% capture anchorAttributes %}{{ anchorAttributes }} class="{{ include.anchor_class | split: '.' | join: ' ' }}"{% endcapture %}
134
+ {% endif %}
135
+
136
+ {% capture listItem %}<a{{ anchorAttributes }}>{{ anchorBody }}</a>{% endcapture %}
137
+ {% elsif skipNoIDs == true %}
138
+ {% continue %}
139
+ {% else %}
140
+ {% capture listItem %}{{ anchorBody }}{% endcapture %}
141
+ {% endif %}
142
+
143
+ {% if currLevel > lastLevel and flatToc == false %}
144
+ {% capture jekyll_toc %}{{ jekyll_toc }}<{{ listModifier }}{{ subMenuClass }}>{% endcapture %}
145
+ {% elsif currLevel < lastLevel and flatToc == false %}
146
+ {% assign repeatCount = lastLevel | minus: currLevel %}
147
+
148
+ {% for i in (1..repeatCount) %}
149
+ {% capture jekyll_toc %}{{ jekyll_toc }}</li></{{ listModifier }}>{% endcapture %}
150
+ {% endfor %}
151
+
152
+ {% capture jekyll_toc %}{{ jekyll_toc }}</li>{% endcapture %}
153
+ {% else %}
154
+ {% capture jekyll_toc %}{{ jekyll_toc }}</li>{% endcapture %}
155
+ {% endif %}
156
+
157
+ {% capture jekyll_toc %}{{ jekyll_toc }}<li{{ listItemClass }}>{{ listItem }}{% endcapture %}
158
+
159
+ {% assign lastLevel = currLevel %}
160
+ {% assign firstHeader = false %}
161
+ {% endfor %}
162
+
163
+ {% if flatToc == true %}
164
+ {% assign repeatCount = 1 %}
165
+ {% else %}
166
+ {% assign repeatCount = minHeader | minus: 1 %}
167
+ {% assign repeatCount = lastLevel | minus: repeatCount %}
168
+ {% endif %}
169
+
170
+ {% for i in (1..repeatCount) %}
171
+ {% capture jekyll_toc %}{{ jekyll_toc }}</li></{{ listModifier }}>{% endcapture %}
172
+ {% endfor %}
173
+
174
+ {% if jekyll_toc != '' %}
175
+ {% assign rootAttributes = '' %}
176
+ {% if include.class and include.class != blank %}
177
+ {% capture rootAttributes %} class="{{ include.class | split: '.' | join: ' ' }}"{% endcapture %}
178
+ {% endif %}
179
+
180
+ {% if include.id and include.id != blank %}
181
+ {% capture rootAttributes %}{{ rootAttributes }} id="{{ include.id }}"{% endcapture %}
182
+ {% endif %}
183
+
184
+ {% if rootAttributes %}
185
+ {% assign nodes = jekyll_toc | split: '>' %}
186
+ {% capture jekyll_toc %}<{{ listModifier }}{{ rootAttributes }}>{{ nodes | shift | join: '>' }}>{% endcapture %}
187
+ {% endif %}
188
+ {% endif %}
189
+ {% endcapture %}{% assign tocWorkspace = '' %}{{ deprecation_warnings }}{{ jekyll_toc -}}
@@ -0,0 +1,24 @@
1
+ <html>
2
+ {% include head.html %}
3
+
4
+ <body
5
+ class="dark:text-primary-300 text-primary-700 dark:bg-primary-900 bg-primary-100 font-display flex flex-col items-center md:items-center lg:items-center">
6
+ <header
7
+ class="w-full dark:text-primary-700 text-primary-800 dark:bg-primary-950 bg-primary-300 flex flex-col items-center py-4 px-4 border-t-[6px] border-solid border-primary-500">
8
+ <div class="max-w-[65ch] w-full">
9
+ {% include nav.html %}
10
+ </div>
11
+ </header>
12
+ <div class="prose dark:prose-invert my-8 px-4 max-w-[65ch] w-full space-y-5">
13
+ {% include anchor_headings.html html=content anchorBody="<i class=\"fas fa-anchor fa-2xs hover:fa-bounce\"></i>" anchorClass="content-center" %}
14
+ </div>
15
+ <footer
16
+ class="w-full dark:text-primary-400 text-primary-800 dark:bg-primary-950 bg-primary-200 py-6 flex flex-col items-center">
17
+ <div class="max-w-[70ch] w-full">
18
+ {% include foot.html %}
19
+ </div>
20
+ </footer>
21
+ <script src="{{ '/assets/js/toggle-theme.js' | relative_url }}"></script>
22
+ </body>
23
+
24
+ </html>
@@ -0,0 +1,15 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <h1 class="mb-3">/{{ page.name | replace: '.md', '' | replace: '.html', '' }}</h1>
6
+
7
+ <div class="mb-6">
8
+ {% if page.toc == true %}
9
+ {% include toc-card.html html=content %}
10
+ {% endif %}
11
+ </div>
12
+
13
+ {{ content }}
14
+
15
+ <script src="{{ '/assets/js/scrollspy.js' | relative_url }}"></script>
@@ -0,0 +1,24 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <h1>{{ page.title }}</h1>
6
+ <p>{{ page.description }}</p>
7
+
8
+ <div class="flex flex-row gap-4">
9
+ <p class="my-0">On {{ page.date | date_to_string }}, by</p>
10
+ <a href="https://github.com/{{ page.author }}" class="flex flex-row items-center gap-2">
11
+ <img src="https://github.com/{{ page.author }}.png?size=30" class="my-0 w-8 h-8 rounded-full" />
12
+ @{{ page.author }}
13
+ </a>
14
+ </div>
15
+ {% if page.toc != false %}
16
+ {% include toc-card.html html=content %}
17
+ {% endif %}
18
+ <div>
19
+
20
+ </div>
21
+
22
+ {{ content }}
23
+
24
+ <script src="{{ '/assets/js/scrollspy.js' | relative_url }}"></script>
@@ -0,0 +1,33 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <h1>{{ page.title }}</h1>
6
+ <p>{{ page.description }}</p>
7
+ <div class="rounded-md bg-primary-300 dark:bg-primary-800 p-4 mb-4">
8
+ <ul class="my-0">
9
+ <li>
10
+ <div class="flex flex-row gap-2 items-center">
11
+ <p class="my-0 mr-1">Skills: </p>
12
+ {% for skill in page.skills %}
13
+ <div
14
+ class="bg-primary-500 dark:bg-primary-900 text-sm py-1 text-primary-200 dark:text-primary-400 items-center px-2 rounded-md flex flex-row gap-2 h-fit">
15
+ <i class="{{ skill.icon }}"></i>{{ skill.name }}
16
+ </div>
17
+ {% endfor %}
18
+ </div>
19
+ </li>
20
+
21
+ <li>
22
+ <div class="flex flex-row items-center gap-3"><i class="fas fa-link"></i><a href="{{ page.website }}">Visit
23
+ the website</a></div>
24
+ </li>
25
+
26
+ <li>
27
+ <div class="flex flex-row items-center gap-3"><i class="fa-brands fa-youtube"></i><a
28
+ href="{{ page.video }}">Watch the recap</a></div>
29
+ </li>
30
+ </ul>
31
+ </div>
32
+
33
+ {{ content }}
@@ -0,0 +1,76 @@
1
+ ---
2
+ ---
3
+
4
+ @import "tailwindcss/base";
5
+ @import "tailwindcss/components";
6
+ @import "tailwindcss/utilities";
7
+
8
+ @layer base {
9
+ @font-face {
10
+ font-family: 'LibertinusMonoMono';
11
+ src: url('../fonts/LibertinusMonoMono.otf') format('opentype');
12
+ font-weight: normal;
13
+ font-style: normal;
14
+ }
15
+ }
16
+
17
+ @layer components {
18
+ a {
19
+ @apply dark:!text-primary-400 !text-primary-800 !underline !decoration-dotted;
20
+ }
21
+
22
+ h1 {
23
+ font-family: sans-serif
24
+ }
25
+
26
+ a[href^="http://"]:after,
27
+ a[href^="https://"]:after {
28
+ content: "\f08e";
29
+ font-family: FontAwesome;
30
+ font-weight: normal;
31
+ font-style: normal;
32
+ display: inline-block;
33
+ margin-left: 3px;
34
+ text-decoration: none;
35
+ padding-left: 3px;
36
+ }
37
+
38
+ body {
39
+ text-underline-offset: 4px;
40
+ counter-reset: sidenote-counter;
41
+ }
42
+
43
+ small {
44
+ @apply hidden mr-[-35%] xl:mr-[-50%] lg:block;
45
+ counter-increment: sidenote-counter;
46
+ float: right;
47
+ clear: right;
48
+ width: 40%;
49
+ margin-top: 1em;
50
+ margin-bottom: 1em;
51
+ font-size: 1.0rem;
52
+ line-height: 1.3;
53
+ vertical-align: baseline;
54
+ position: relative;
55
+ padding: 1em;
56
+ border-top: 1px dotted #CCCCCC;
57
+ border-bottom: 1px dotted #CCCCCC;
58
+ }
59
+
60
+ small::before {
61
+ @apply border-primary-500 rounded-full mr-2 mt-1 p-1 w-8 h-8 items-center text-center;
62
+ content: counter(sidenote-counter);
63
+ float: left;
64
+ border-style: solid;
65
+ border-width: 2px;
66
+ }
67
+
68
+ .prose>h2,
69
+ h3 {
70
+ gap: 8px;
71
+ display: flex;
72
+ }
73
+ li::marker {
74
+ @apply !text-primary-500;
75
+ }
76
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,62 @@
1
+ /**
2
+ * TOC ScrollSpy
3
+ * Highlights the current section in the table of contents
4
+ */
5
+ function initTocScrollSpy() {
6
+ const headings = document.querySelectorAll('h2[id], h3[id], h4[id], h5[id], h6[id]');
7
+ const tocLinks = document.querySelectorAll('.toc-link');
8
+
9
+ if (headings.length === 0 || tocLinks.length === 0) return;
10
+
11
+ const idToTocLink = {};
12
+ tocLinks.forEach(link => {
13
+ const href = link.getAttribute('href');
14
+ if (href && href.startsWith('#')) {
15
+ idToTocLink[href.substring(1)] = link;
16
+ }
17
+ });
18
+
19
+ const headingPositions = Array.from(headings).map(heading => {
20
+ return {
21
+ id: heading.id,
22
+ top: heading.getBoundingClientRect().top + window.pageYOffset - 100
23
+ };
24
+ });
25
+
26
+ function updateToc() {
27
+ const scrollPosition = window.pageYOffset;
28
+
29
+ let currentHeadingIndex = headingPositions.findIndex(heading =>
30
+ heading.top > scrollPosition
31
+ ) - 1;
32
+
33
+ if (currentHeadingIndex === -2) {
34
+ currentHeadingIndex = headingPositions.length - 1;
35
+ } else if (currentHeadingIndex < 0) {
36
+ currentHeadingIndex = 0;
37
+ }
38
+
39
+ tocLinks.forEach(link => link.classList.remove('font-bold', 'text-primary-800', 'dark:text-primary-300'));
40
+
41
+ const currentId = headingPositions[currentHeadingIndex]?.id;
42
+ if (currentId && idToTocLink[currentId]) {
43
+ idToTocLink[currentId].classList.add('font-bold', 'text-primary-800', 'dark:text-primary-300');
44
+ }
45
+ }
46
+
47
+ updateToc();
48
+
49
+ let ticking = false;
50
+ window.addEventListener('scroll', function() {
51
+ if (!ticking) {
52
+ window.requestAnimationFrame(function() {
53
+ updateToc();
54
+ ticking = false;
55
+ });
56
+ ticking = true;
57
+ }
58
+ });
59
+ }
60
+
61
+ document.addEventListener('DOMContentLoaded', initTocScrollSpy);
62
+
@@ -0,0 +1,33 @@
1
+ document.getElementById('theme-toggle').addEventListener('click', (event) => {
2
+ let newTheme = undefined;
3
+ if (localStorage.theme === "dark") {
4
+ localStorage.removeItem('theme')
5
+ } else if (!localStorage.theme) {
6
+ newTheme = 'light';
7
+ localStorage.theme = 'light';
8
+ } else {
9
+ newTheme = 'dark';
10
+ localStorage.theme = 'dark';
11
+ }
12
+
13
+ applyTheme(newTheme);
14
+ });
15
+
16
+ document.getElementById('theme-dropdown').addEventListener('change', (event) => {
17
+ if (event.target.dataset.changed ) {
18
+ event.target.dataset.changed = undefined;
19
+ if (new Date(event.target.dataset.changed) - new Date() < 1000) {
20
+ return;
21
+ }
22
+ }
23
+ const selectedTheme = event.target.value;
24
+ if (selectedTheme === 'light' || selectedTheme === 'dark') {
25
+ localStorage.theme = selectedTheme;
26
+ } else {
27
+ selectedTheme = undefined;
28
+ localStorage.removeItem('theme');
29
+ }
30
+ applyTheme(selectedTheme, true);
31
+ });
32
+
33
+ applyTheme();
data/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "dependencies": {
3
+ "autoprefixer": "^10.4.21",
4
+ "postcss": "^8.5.3",
5
+ "postcss-import": "^14.1.0"
6
+ },
7
+ "devDependencies": {
8
+ "@tailwindcss/typography": "^0.5.16",
9
+ "cssnano": "^5.1.15",
10
+ "tailwindcss": "^3.4.17"
11
+ },
12
+ "scripts": {
13
+ "postinstall": "bundle install",
14
+ "dev": "bundle exec jekyll serve --watch",
15
+ "build": "python utils/postMarkdown.py && bundle exec jekyll build",
16
+ "clean": "bundle exec jekyll clean",
17
+ "doctor": "bundle exec jekyll doctor",
18
+ "post": "python utils/postMarkdown.py"
19
+ }
20
+ }
data/postcss.config.js ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ plugins: [
3
+ require('postcss-import'),
4
+ require('tailwindcss'),
5
+ require("autoprefixer"),
6
+ ...(process.env.JEKYLL_ENV == "production"
7
+ ? [require("cssnano")({ preset: "default" })]
8
+ : [])
9
+ ]
10
+ };
@@ -0,0 +1,43 @@
1
+ import colors from 'tailwindcss/colors'
2
+
3
+ /** @type {import('tailwindcss').Config} */
4
+ module.exports = {
5
+ darkMode: 'selector',
6
+ content: [
7
+ './_includes/**/*.html',
8
+ './_layouts/**/*.html',
9
+ './_posts/*.md',
10
+ './*.html',
11
+ ],
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ primary: colors.stone,
16
+ },
17
+ typography: {
18
+ DEFAULT: {
19
+ css: (theme) => ({
20
+ a: {
21
+ color: theme('colors.primary.200') + ' !important',
22
+ 'text-decoration': 'underline !important',
23
+ 'text-decoration-style': 'dotted !important',
24
+ '&:hover': {
25
+ color: theme('colors.primary.400') + ' !important',
26
+ },
27
+ '@screen dark': {
28
+ color: theme('colors.primary.400') + ' !important',
29
+ },
30
+ }
31
+ }),
32
+ },
33
+ },
34
+ },
35
+ fontFamily: {
36
+ display: ['LibertinusMonoMono'],
37
+ sans: ['sans-serif'],
38
+ icon: ['FontAwesome'],
39
+ },
40
+ },
41
+ variants: {},
42
+ plugins: [require('@tailwindcss/typography')],
43
+ }
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-theme-doctored
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - matmanna
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-11-06 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jekyll
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 3.9.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 3.9.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: jekyll-postcss
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.5.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.5.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: bundler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ description: " Doctored is a jekyll theme built with tailwind CSS which enables the
55
+ ceration of static, yet highly advanced, blogs, digital gardens, documentation,
56
+ and portfolio websites."
57
+ email:
58
+ - ''
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE
64
+ - README.md
65
+ - _config.yml
66
+ - _includes/anchor_headings.html
67
+ - _includes/foot.html
68
+ - _includes/head.html
69
+ - _includes/nav.html
70
+ - _includes/toc-card.html
71
+ - _includes/toc.html
72
+ - _layouts/default.html
73
+ - _layouts/page.html
74
+ - _layouts/post.html
75
+ - _layouts/project.html
76
+ - assets/css/main.css
77
+ - assets/fonts/LibertinusMonoMono.otf
78
+ - assets/images/favicon.ico
79
+ - assets/images/favicon.png
80
+ - assets/js/scrollspy.js
81
+ - assets/js/toggle-theme.js
82
+ - package.json
83
+ - postcss.config.js
84
+ - tailwind.config.js
85
+ homepage: https://matmanna.github.io/doctored
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ homepage_uri: https://matmanna.github.io/doctored
90
+ source_code_uri: https://github.com/https://github.com/matmanna/doctored
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.6.2
106
+ specification_version: 4
107
+ summary: "\U0001F3A9 A blog theme prioritizing uniqueness, complexity, and sophistication."
108
+ test_files: []