├── .github └── workflows │ ├── create-template.yml │ └── pages.yml ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── _bin └── pagefind ├── _config.yml ├── _includes ├── catalogue_item.html ├── comments.html ├── footer.html ├── head.html ├── navigation.html └── search.html ├── _layouts ├── default.html ├── home.html ├── page.html └── post.html ├── _pages ├── 404.html ├── about.md └── tags.html ├── _plugins ├── jekyll-last-modified.rb ├── no_date.rb ├── pagefind.rb ├── strip_footnotes.rb └── wiki-links.rb ├── _posts ├── bidirectional.md ├── example-content.md ├── footnotes.md ├── introducing-tail.md ├── managing-excerpt.md ├── pagination-post.md ├── posts-without-date.md ├── posts-without-filename.md ├── reply-comments.md ├── search.md ├── sticky-posts.md └── upgrades.md ├── assets ├── css │ ├── _sass │ │ ├── 404.scss │ │ ├── base.scss │ │ ├── catalogue.scss │ │ ├── code.scss │ │ ├── footnotes.scss │ │ ├── layout.scss │ │ ├── pagination.scss │ │ ├── post.scss │ │ ├── search.scss │ │ ├── tags.scss │ │ └── variables.scss │ ├── main.scss │ ├── sass-code-highlight │ │ ├── default.scss │ │ └── monokai.scss │ ├── syntax-default.scss │ └── syntax-monokai.scss ├── favicon │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ └── favicon-32x32.png └── js │ ├── disqusLoader.js │ ├── footnotes.js │ └── pagefind.js ├── favicon.ico └── index.html /.github/workflows/create-template.yml: -------------------------------------------------------------------------------- 1 | name: Create Template Branch 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | create-template-branch: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set git identity 18 | run: | 19 | git config user.name "github-actions[bot]" 20 | git config user.email "github-actions[bot]@users.noreply.github.com" 21 | 22 | - name: Create template branch 23 | run: | 24 | git checkout main 25 | git branch -D template || true 26 | git checkout -b template 27 | git rm -rf --cached --ignore-unmatch "_posts" "Gemfile.lock" 28 | git commit -m "Update template branch" 29 | git push origin template -f 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy to Github Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # Here source code branch is `master`, it could be other branch 7 | 8 | jobs: 9 | build_and_deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | # The checkout action doesn't provide a way to get all commit history for a single branch 15 | # So we use the magic number 2147483647 here which means infinite depth for git fetch 16 | # See https://github.com/actions/checkout/issues/520, https://stackoverflow.com/a/6802238 17 | fetch-depth: 2147483647 18 | 19 | # Use GitHub Actions' cache to cache dependencies on servers 20 | - uses: actions/cache@v4 21 | with: 22 | path: | 23 | .asdf/** 24 | vendor/bundle 25 | key: ${{ runner.os }}-cache-${{ hashFiles('**/cache.key') }} 26 | restore-keys: | 27 | ${{ runner.os }}-cache- 28 | 29 | 30 | # Use GitHub Deploy Action to build and deploy to Github 31 | - uses: jeffreytse/jekyll-deploy-action@v0.6.0 32 | with: 33 | provider: 'github' 34 | token: ${{ secrets.GITHUB_TOKEN }} # It's your Personal Access Token(PAT) 35 | repository: '' # Default is current repository 36 | branch: 'gh-pages' # Default is gh-pages for github provider 37 | jekyll_src: './' # Default is root directory 38 | jekyll_cfg: '_config.yml' # Default is _config.yml 39 | jekyll_baseurl: '' # Default is according to _config.yml 40 | bundler_ver: '>=0' # Default is latest bundler version 41 | cname: '' # Default is to not use a cname 42 | actor: '' # Default is the GITHUB_ACTOR 43 | pre_build_commands: '' # Installing additional dependencies (Arch Linux) 44 | 45 | # Step 2: Checkout gh-pages and run Pagefind 46 | - name: Checkout gh-pages and Run Pagefind 47 | uses: actions/checkout@v4 48 | with: 49 | ref: gh-pages 50 | path: gh-pages-dir 51 | 52 | - name: Run Pagefind on gh-pages 53 | run: | 54 | # Download Pagefind binary for Linux 55 | curl -L -o pagefind.tar.gz https://github.com/CloudCannon/pagefind/releases/download/v1.3.0/pagefind-v1.3.0-x86_64-unknown-linux-musl.tar.gz 56 | tar -xzf pagefind.tar.gz 57 | chmod +x pagefind 58 | 59 | # Run Pagefind against the gh-pages directory 60 | ./pagefind --site gh-pages-dir 61 | 62 | # Commit and push the Pagefind index 63 | cd gh-pages-dir 64 | git config user.name "github-actions[bot]" 65 | git config user.email "github-actions[bot]@users.noreply.github.com" 66 | git add . 67 | git commit -m "Add Pagefind index" 68 | git push origin gh-pages 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | .sass-cache/ 3 | .jekyll-metadata 4 | .jekyll-cache 5 | .DS_Store 6 | Gemfile.lock 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem 'jekyll', "~> 4.0" 6 | 7 | group :jekyll_plugins do 8 | #plugins in _config.yml 9 | gem 'jekyll-seo-tag' 10 | gem 'jekyll-paginate' 11 | gem 'jekyll-email-protect' 12 | gem 'jekyll-feed' 13 | 14 | #dependeny for custom footnote plugin 15 | gem 'nokogiri' 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.7) 5 | public_suffix (>= 2.0.2, < 7.0) 6 | base64 (0.2.0) 7 | bigdecimal (3.1.9) 8 | colorator (1.1.0) 9 | concurrent-ruby (1.3.5) 10 | csv (3.3.3) 11 | em-websocket (0.5.3) 12 | eventmachine (>= 0.12.9) 13 | http_parser.rb (~> 0) 14 | eventmachine (1.2.7) 15 | ffi (1.17.1-arm64-darwin) 16 | ffi (1.17.1-x86_64-darwin) 17 | ffi (1.17.1-x86_64-linux-gnu) 18 | forwardable-extended (2.6.0) 19 | google-protobuf (4.30.2-arm64-darwin) 20 | bigdecimal 21 | rake (>= 13) 22 | google-protobuf (4.30.2-x86_64-darwin) 23 | bigdecimal 24 | rake (>= 13) 25 | google-protobuf (4.30.2-x86_64-linux) 26 | bigdecimal 27 | rake (>= 13) 28 | http_parser.rb (0.8.0) 29 | i18n (1.14.7) 30 | concurrent-ruby (~> 1.0) 31 | jekyll (4.4.1) 32 | addressable (~> 2.4) 33 | base64 (~> 0.2) 34 | colorator (~> 1.0) 35 | csv (~> 3.0) 36 | em-websocket (~> 0.5) 37 | i18n (~> 1.0) 38 | jekyll-sass-converter (>= 2.0, < 4.0) 39 | jekyll-watch (~> 2.0) 40 | json (~> 2.6) 41 | kramdown (~> 2.3, >= 2.3.1) 42 | kramdown-parser-gfm (~> 1.0) 43 | liquid (~> 4.0) 44 | mercenary (~> 0.3, >= 0.3.6) 45 | pathutil (~> 0.9) 46 | rouge (>= 3.0, < 5.0) 47 | safe_yaml (~> 1.0) 48 | terminal-table (>= 1.8, < 4.0) 49 | webrick (~> 1.7) 50 | jekyll-email-protect (1.1.0) 51 | jekyll-feed (0.17.0) 52 | jekyll (>= 3.7, < 5.0) 53 | jekyll-paginate (1.1.0) 54 | jekyll-sass-converter (3.1.0) 55 | sass-embedded (~> 1.75) 56 | jekyll-seo-tag (2.8.0) 57 | jekyll (>= 3.8, < 5.0) 58 | jekyll-watch (2.2.1) 59 | listen (~> 3.0) 60 | json (2.10.2) 61 | kramdown (2.5.1) 62 | rexml (>= 3.3.9) 63 | kramdown-parser-gfm (1.1.0) 64 | kramdown (~> 2.0) 65 | liquid (4.0.4) 66 | listen (3.9.0) 67 | rb-fsevent (~> 0.10, >= 0.10.3) 68 | rb-inotify (~> 0.9, >= 0.9.10) 69 | mercenary (0.4.0) 70 | nokogiri (1.18.7-arm64-darwin) 71 | racc (~> 1.4) 72 | nokogiri (1.18.7-x86_64-darwin) 73 | racc (~> 1.4) 74 | nokogiri (1.18.7-x86_64-linux-gnu) 75 | racc (~> 1.4) 76 | pathutil (0.16.2) 77 | forwardable-extended (~> 2.6) 78 | public_suffix (6.0.1) 79 | racc (1.8.1) 80 | rake (13.2.1) 81 | rb-fsevent (0.11.2) 82 | rb-inotify (0.11.1) 83 | ffi (~> 1.0) 84 | rexml (3.4.1) 85 | rouge (4.5.1) 86 | safe_yaml (1.0.5) 87 | sass-embedded (1.86.3-arm64-darwin) 88 | google-protobuf (~> 4.30) 89 | sass-embedded (1.86.3-x86_64-darwin) 90 | google-protobuf (~> 4.30) 91 | sass-embedded (1.86.3-x86_64-linux-gnu) 92 | google-protobuf (~> 4.30) 93 | terminal-table (3.0.2) 94 | unicode-display_width (>= 1.1.1, < 3) 95 | unicode-display_width (2.6.0) 96 | webrick (1.9.1) 97 | 98 | PLATFORMS 99 | arm64-darwin-24 100 | x86_64-darwin-21 101 | x86_64-linux 102 | 103 | DEPENDENCIES 104 | jekyll (~> 4.0) 105 | jekyll-email-protect 106 | jekyll-feed 107 | jekyll-paginate 108 | jekyll-seo-tag 109 | nokogiri 110 | 111 | BUNDLED WITH 112 | 2.6.7 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chester How 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 | # Tail 2 | 3 | Tail is a minimal Jekyll blog theme heavily based on [Tale](https://github.com/chesterhow/tale) (and hence the name). Apart from the minor fixes to Tale and significant changes under the hood, this theme is designed to make it easier to maintain a blog. 4 | 5 | If you already have a bunch of files in `*.md` format, you can simply copy them to the `_posts` folder after forking this theme and making a few changes to `_config.yml`, your blog is ready to go! 6 | 7 | ## What's new in Tail 8 | - Dark Mode 9 | - Support for posts without the `YYYY-MM-DD` in the post's filename 10 | - Search (using Pagefind) 11 | - Popup Footnotes 12 | - Bi-directional Wiki-style linking support compatible with Obsidian 13 | - Comments via Email 14 | 15 | ## Other Features from Tale (with minor updates) 16 | - Easy installation 17 | - Compatible with GitHub Pages 18 | - Responsive design 19 | - Pagination of posts 20 | - Sticky posts 21 | - Tags 22 | - Excerpt management 23 | 24 | ## Contributing 25 | Found a bug or have a suggestion? Feel free to create an issue or make a pull request! 26 | 27 | ## License 28 | See [LICENSE](https://github.com/jitinnair1/tail/blob/master/LICENSE) 29 | 30 | PS: If you liked the theme, do star :star: it! Thanks! 31 | 32 | ### Also, check out: 33 | 34 | - [autoCV](https://github.com/jitinnair1/autocv) - a LaTeX template that builds and deploys the CV using GitHub Actions, so you will always have a ready link for your latest CV 35 | - [gradfolio](https://github.com/jitinnair1/gradfolio) - a minimal, quick-setup template for a personal website/portfolio 36 | - [snippet-book](https://github.com/jitinnair1/snippet-book) - terminal style, clean Jekyll blog theme with catppuccin colours 37 | -------------------------------------------------------------------------------- /_bin/pagefind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitinnair1/tail/0c2dbfca6315712f491b4454e74689c13c32641d/_bin/pagefind -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | title: Tail 3 | description: "Minimal Jekyll blog theme" 4 | baseurl: "/tail" 5 | url: 6 | 7 | # https://jekyllrb.com/docs/configuration/options/ 8 | timezone: Asia/Kolkata 9 | 10 | # Author 11 | # This is the default author name displayed on all posts, can be overridden by setting a different `author: Your Name` 12 | # in the frontmatter 13 | author: 14 | name: Your Name 15 | 16 | # Build settings 17 | markdown: kramdown 18 | highlighter: rouge 19 | 20 | 21 | # Comments (Reply via email) 22 | comments: true 23 | comment_email: "" #enter an email address where you want comment emails to be sent 24 | 25 | kramdown: 26 | input: GFM 27 | syntax_highlighter: rouge 28 | 29 | include: 30 | - _pages 31 | 32 | # Assets 33 | sass: 34 | sass_dir: /assets/css/_sass 35 | style: compressed 36 | 37 | # Gems 38 | plugins: 39 | - jekyll-feed 40 | - jekyll-paginate 41 | - jekyll-seo-tag 42 | 43 | # Permalinks 44 | permalink: /:title 45 | paginate: 5 46 | 47 | # Excludes 48 | exclude: 49 | - README.md 50 | - LICENSE.md 51 | 52 | # Disqus (Set to your disqus id) 53 | disqus: 54 | -------------------------------------------------------------------------------- /_includes/catalogue_item.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {% if include.sticky == 'true' %} 4 | 📌 · 5 | {% endif %} 6 | 7 |

{{ post.title }}

8 |
9 | 10 |

11 | {% if post.excerpt_separator %} 12 | {{ post.excerpt | strip_footnotes | strip_html}} 13 | {% else %} 14 | {{ post.content | strip_footnotes | strip_html | truncatewords: 30 }} 15 | {% endif %} 16 |

17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /_includes/comments.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Reply by 5 | email 6 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% seo %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% feed_meta %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /_includes/navigation.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /_includes/search.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | {% include navigation.html %} 7 | 8 | {% include search.html %} 9 | 10 | 11 | 12 |
13 | {{ content }} 14 |
15 | 16 | 17 | 18 | {% include footer.html %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /_layouts/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | {% for post in site.posts %} 7 | {% if post.sticky %} 8 | {% include catalogue_item.html sticky='true' %} 9 | {% endif %} 10 | {% endfor %} 11 | 12 | {% for post in paginator.posts %} 13 | {% include catalogue_item.html %} 14 | {% endfor %} 15 |
16 | 17 | 27 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include navigation.html %} 9 | 10 | {% include search.html %} 11 | 12 |
13 |
14 |

{{ page.title }}

15 |
16 |
17 | 18 |
19 | {{ content }} 20 |
21 | 22 | {% include footer.html %} 23 | 24 | 25 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | 30 | 31 |

{{ page.title }}

32 |
33 | 34 | {{ content }} 35 | 36 | {% if site.comments or page.comments %} 37 | {% include comments.html %} 38 | {% endif %} 39 | 40 |
41 | 42 | 43 |
44 | 45 | 55 | -------------------------------------------------------------------------------- /_pages/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: "404" 4 | permalink: /404.html 5 | --- 6 | 7 |
8 |

404: Page not found

9 |
10 |

11 | Oops! We can't seem to find the page you are looking for. Let's 12 | head back home. 13 |

14 |

15 |
16 | -------------------------------------------------------------------------------- /_pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "About" 4 | permalink: /about/ 5 | --- 6 | 7 | This theme builds upon an amazing theme called [Tale](https://github.com/chesterhow/tale). Apart from the minor fixes to Tale, this theme is designed to make it easier to maintain a blog. 8 | 9 | If you already have a bunch of files in `*.md` format, you can simply copy them to the `_posts` folder after forking this theme and making a few changes to `_config.yml`, your blog is ready to go! 10 | 11 | Check out the key features [here]({{ site.baseurl }}/introducing-tail). If you liked this theme, do star it on [GitHub](https://github.com/jitinnair1/tail). 12 | 13 | ### Also, check out: 14 | 15 | - [autoCV](https://github.com/jitinnair1/autocv) - a LaTeX template that builds and deploys the CV using GitHub Actions, so you will always have a ready link for your latest CV 16 | - [gradfolio](https://github.com/jitinnair1/gradfolio) - a minimal, quick-setup template for a personal website/portfolio 17 | -------------------------------------------------------------------------------- /_pages/tags.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "Tags" 4 | permalink: /tags/ 5 | --- 6 | 7 |
8 | {% for tag in site.tags %} 9 | {{ tag[0] }} 10 | {% endfor %} 11 |
12 | {% for tag in site.tags %} 13 |
14 | 27 | 30 | 31 | 32 |

{{ tag[0] }}

33 | {% for post in tag[1] %} 34 | 35 |
36 | {{ post.title }} 37 |
38 |
39 | 44 |
45 | {% endfor %} 46 |
47 | {% endfor %} 48 |
49 | -------------------------------------------------------------------------------- /_plugins/jekyll-last-modified.rb: -------------------------------------------------------------------------------- 1 | # Using https://github.com/michaelx/jekyll-last-modified 2 | 3 | module Jekyll 4 | class LastModifiedTag < Liquid::Tag 5 | 6 | def initialize(tag_name, path, tokens) 7 | super 8 | @path = path 9 | end 10 | 11 | def render(context) 12 | # Pipe parameter through Liquid to make additional replacements possible 13 | url = Liquid::Template.parse(@path).render context 14 | 15 | # Adds the site source, so that it also works with a custom one 16 | site_source = context.registers[:site].config['source'] 17 | file_path = site_source + '/' + url 18 | 19 | # Return last modified date 20 | File.mtime(file_path.strip!) 21 | end 22 | end 23 | end 24 | 25 | Liquid::Template.register_tag('last_modified', Jekyll::LastModifiedTag) -------------------------------------------------------------------------------- /_plugins/no_date.rb: -------------------------------------------------------------------------------- 1 | # From here: https://stackoverflow.com/a/68287682/9523246 2 | 3 | class Jekyll::PostReader 4 | # Don't use DATE_FILENAME_MATCHER so we don't need to put those stupid dates 5 | # in the filename. Also limit to just *.markdown, so it won't process binary 6 | # files from e.g. drags. 7 | def read_posts(dir) 8 | read_publishable(dir, "_posts", /.*\.md$/) 9 | end 10 | def read_drafts(dir) 11 | read_publishable(dir, "_drafts", /.*\.md$/) 12 | end 13 | end -------------------------------------------------------------------------------- /_plugins/pagefind.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | class PostCompileCommand < Jekyll::Generator 3 | safe true 4 | priority :lowest 5 | 6 | def generate(site) 7 | Jekyll::Hooks.register :site, :post_write do |_site| 8 | command = './_bin/pagefind --site _site' 9 | puts "Running: #{command}" 10 | system(command) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /_plugins/strip_footnotes.rb: -------------------------------------------------------------------------------- 1 | # From: https://battlepenguin.com/tech/removing-footnotes-from-excerpts-in-jekyll/ 2 | 3 | require 'nokogiri' 4 | 5 | module Jekyll 6 | module StripFootnotesFilter 7 | 8 | def strip_footnotes(raw) 9 | doc = Nokogiri::HTML.fragment(raw.encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '')) 10 | 11 | for block in ['div', 'sup', 'a'] do 12 | doc.css(block).each do |ele| 13 | ele.remove if (ele['class'] == 'footnotes' or ele['class'] == 'footnote') 14 | end 15 | end 16 | 17 | doc.inner_html 18 | 19 | end 20 | end 21 | end 22 | 23 | Liquid::Template.register_filter(Jekyll::StripFootnotesFilter) -------------------------------------------------------------------------------- /_plugins/wiki-links.rb: -------------------------------------------------------------------------------- 1 | # _plugins/wiki_links.rb 2 | 3 | Jekyll::Hooks.register [:posts], :pre_render do |post, payload| 4 | site = post.site 5 | 6 | #build a quick lookup of existing post slugs (without extensions) 7 | post_slugs = site.posts.docs.map { |p| File.basename(p.path, ".md") } 8 | 9 | #this regex finds [[wikilinks]] with optional custom titles 10 | post.content.gsub!(/\[\[([^\]\|]+)(\|([^\]]+))?\]\]/) do |_match| 11 | target_slug = Regexp.last_match(1).strip 12 | custom_title = Regexp.last_match(3)&.strip 13 | 14 | #convert target slug for URL (spaces to hyphens, lowercase) 15 | url_slug = target_slug.downcase.gsub(' ', '-') 16 | 17 | #validate if target post actually exists 18 | unless post_slugs.include?(url_slug) 19 | Jekyll.logger.warn "WikiLink Warning:", "Post '#{url_slug}' not found. Leaving raw link." 20 | next "[[#{target_slug}]]" #leave the link untouched 21 | end 22 | 23 | #build the URL 24 | url = "#{site.baseurl}/#{url_slug}" 25 | 26 | #use custom title if available, else the target slug 27 | link_text = custom_title || target_slug 28 | 29 | #return the HTML anchor tag 30 | "#{link_text}" 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /_posts/bidirectional.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Bidirectional Links 4 | date: 24th April 2025 5 | tags: 6 | - New 7 | --- 8 | You can use Obsidian-styte bidirectional links `[[ name_of_note ]]` and it will link to the correct post after the site is built.. 9 | 10 | Also, check out other features like [[search]], [[footnotes]] and [[reply-comments]]. 11 | -------------------------------------------------------------------------------- /_posts/example-content.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Example Content" 4 | date: 4th Oct 2022 5 | tags: Old 6 | excerpt_separator: 7 | --- 8 | 9 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tincidunt ornare nibh, non elementum augue tempus eget. Pellentesque tempus scelerisque iaculis. Nullam interdum ultricies nibh quis sollicitudin. Donec ornare fermentum facilisis. Ut at sem ac sem imperdiet varius a eget tortor. Nam eu augue eget orci semper maximus in eget augue. Mauris ornare, nisl ut suscipit consectetur, mi quam interdum tellus, at rutrum quam eros ultrices mi. 10 | 11 | # Headers 12 | ```markdown 13 | # H1 14 | ## H2 15 | ### H3 16 | #### H4 17 | ##### H5 18 | ###### H6 19 | ``` 20 | 21 | # H1 22 | ## H2 23 | ### H3 24 | #### H4 25 | ##### H5 26 | ###### H6 27 | 28 | # Text formatting 29 | ```markdown 30 | - **Bold** 31 | - _Italics_ 32 | - ~~Strikethrough~~ 33 | - Underline 34 | - Superscript 35 | - Subscript 36 | - Abbreviation: HTML 37 | - Citation: — Your Name 38 | ``` 39 | 40 | gives you: 41 | 42 | - **Bold** 43 | - _Italics_ 44 | - ~~Strikethrough~~ 45 | - Underline 46 | - Superscript 47 | - Subscript 48 | - Abbreviation: HTML 49 | - Citation: — Your Name 50 | 51 | # Lists 52 | 53 | ```markdown 54 | 1. Ordered list item 1 55 | 2. Ordered list item 2 56 | 3. Ordered list item 3 57 | 58 | * Unordered list item 1 59 | * Unordered list item 2 60 | * Unordered list item 3 61 | ``` 62 | 63 | look like this: 64 | 65 | 1. Ordered list item 1 66 | 2. Ordered list item 2 67 | 3. Ordered list item 3 68 | 69 | * Unordered list item 1 70 | * Unordered list item 2 71 | * Unordered list item 3 72 | 73 | # Links 74 | 75 | ```markdown 76 | Check out tail on [GitHub](https://github.com/jitinnair1/tail). 77 | ``` 78 | 79 | give you this: 80 | 81 | Check out tail on [GitHub](https://github.com/jitinnair1/tail). 82 | 83 | # Images 84 | 85 | ```markdown 86 | ![An image](url "Alt Text") 87 | 88 | ![Image with caption](url "Image with caption") 89 | _This is an image with a caption_ 90 | ``` 91 | 92 | ![An image](https://images.unsplash.com/photo-1664784805210-9fa665e2b7e9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80 "An image") 93 | 94 | ![Image with caption](https://images.unsplash.com/photo-1527697911963-20cb424e9608?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1486&q=80 "Image with caption") 95 | _This is an image with a caption_ 96 | 97 | # Code and Syntax Highlighting 98 | 99 | Use back-ticks for `inline code`. Multi-line code snippets are supported too through. Specify the language after the back-ticks for language specific syntax highlighting. 100 | 101 | ````` 102 | ```ruby 103 | require 'redcarpet' 104 | markdown = Redcarpet.new("Hello World!") 105 | puts markdown.to_html 106 | ``` 107 | ````` 108 | 109 | which will give you syntax highlighting like this: 110 | 111 | ```ruby 112 | require 'redcarpet' 113 | markdown = Redcarpet.new("Hello World!") 114 | puts markdown.to_html 115 | ``` 116 | 117 | ## To display line numbers in codeblocks, you can set them as an option in `_config.yml` 118 | 119 | ```yaml 120 | kramdown: 121 | syntax_highlighter: rouge 122 | syntax_highlighter_opts: 123 | block: 124 | line_numbers: true 125 | 126 | ``` 127 | 128 | 129 | # Blockquotes 130 | 131 | ```markdown 132 | > Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit. 133 | ``` 134 | 135 | > Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit. 136 | 137 | # Horizontal Rule & Line Break 138 | 139 | Use `
` for horizontal rules like this 140 | 141 | ```markdown 142 |
143 | ``` 144 | 145 | gives you 146 | 147 |
148 | 149 | and `
` for line breaks: 150 | 151 | ```markdown 152 | This
breaks the line 153 | ``` 154 | 155 | which will give you: 156 | 157 | This
breaks the line 158 | 159 | 160 | _The end_ 161 | -------------------------------------------------------------------------------- /_posts/footnotes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Better Footnotes" 4 | date: 10th April 2025 5 | tags: New 6 | --- 7 | 8 | When you write something that needs additional context or a citation, you can add a footnote[^1]. 9 | 10 | Footnotes are helpful for providing more information without disrupting the flow of your main content. They can be used for citations, clarifications, or just to add interesting asides[^2]. 11 | 12 | > The best footnote is one that adds context without demanding it be read. It should be there for the curious reader, not the casual one. 13 | 14 | You can use these for technical clarifications as well[^3]. And unlike traditional print footnotes, these are interactive – try hovering over or clicking on the footnote references! 15 | 16 | ## How it works 17 | 18 | The system uses HTML, CSS, and a bit of JavaScript to make the footnotes interactive. The key components are: 19 | 20 | 1. Footnote references in the text with proper IDs and roles 21 | 2. The footnotes section at the bottom with corresponding IDs 22 | 3. JavaScript that shows the footnote content when you interact with the reference 23 | 24 | The markup follows accessibility standards by using proper ARIA roles. You can even navigate to the footnotes and back using the links[^4]. 25 | 26 | ## Credits 27 | 28 | Lastly, this feature was adapted (read stolen) from [this post](https://tools.simonwillison.net/colophon#footnotes-experiment.html) by Simon Willison. Some of the changes I made are: 29 | 30 | - Clicking the footnote does not take you to the footnote listed at the end of the page, instead the interaction now works as hover to preview, click to toggle/hide.[^5] 31 | - The popup content now does not contain the back-link symbol. 32 | 33 | 34 | [^1]: This is the first footnote. It provides additional information that would otherwise clutter the main text. 35 | 36 | [^2]: This second footnote could be used for citation. For example: Smith, J. (2023). *The Art of Footnotes*. *Journal of Useless Knowledge*, 42(1), 123–145. 37 | 38 | [^3]: The footnote system uses the `:target` CSS selector and JavaScript to enhance the user experience. This technical approach allows for both progressive enhancement and accessibility. 39 | 40 | [^4]: The back-link (↩) takes you back to where you came from in the text, which is particularly useful in longer documents. 41 | 42 | [^5]: The footnotes are listed at the bottom of the page only so that the footnote references are visible say when the page is printed. 43 | 44 | -------------------------------------------------------------------------------- /_posts/introducing-tail.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Introducing Tail" 4 | date: 6th Oct 2022 5 | comments: false 6 | tags: Tail 7 | excerpt_separator: 8 | sticky: true 9 | hidden: true 10 | --- 11 | 12 | How do take an already amazing theme like [Tale](https://github.com/chesterhow/tale) and make it better? Dark-mode![^1] - well, that and many more changes under the hood is what led to this theme called Tail (as a hat tip to the original theme of course) 13 | 14 | 15 | ## What's new in Tail 16 | 17 | - Dark Mode 18 | - Popup [[footnotes|Footnotes]] 19 | - Search (via [Pagefind](https://pagefind.app)) 20 | - Comment via [[reply-comments | reply by email]] 21 | - Bi-directional Wiki-style linking support compatible with Obsidian 22 | - Support for posts without the `YYYY-MM-DD` in post's filename and `lastmod` dates 23 | 24 | ## Some great features from the original Tale (with minor updates) 25 | - Compatible with GitHub Pages 26 | - Responsive design (looks just as good on mobile) 27 | - Syntax highlighting 28 | - Markdown and HTML text formatting 29 | - Pagination of posts 30 | - Sticky posts 31 | - Tags 32 | - Excerpt management 33 | 34 | [^1]: For now, there is no toggle button for the dark mode. This is by design. I wanted the dark-mode to be on if the browser or system level dark-mode is enabled. Maybe, in a later version, I will add support for a dark/light toggle. Happy to accept a PR! 35 | -------------------------------------------------------------------------------- /_posts/managing-excerpt.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Managing Excerpt" 4 | date: 2nd Oct 2022 5 | tags: Old 6 | excerpt_separator: 7 | --- 8 | 9 | You can customise the excerpt (the text displayed below each post on the homepage) using the `excerpt-separator`. Here's how you can do so! 10 | 11 | ## Steps 12 | 13 | 1. Add `excerpt_separator: ` to the frontmatter of your blog post. 14 | 15 | 2. Insert this `` at where you would like the excerpt to cut off in your blog post. 16 | 17 | ### Note 18 | 19 | This follows [Jekyll's recommended way of managing excerpts](https://jekyllrb.com/docs/posts/#post-excerpts). 20 | 21 | -------------------------------------------------------------------------------- /_posts/pagination-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Pagination Post" 4 | date: 20th Sept 2022 5 | tags: Old 6 | --- 7 | 8 | Here we see **Tail's** pagination feature in action. It is set to 5 posts per page by default. Feel free to change this number in the `_config.yml` file! 9 | -------------------------------------------------------------------------------- /_posts/posts-without-date.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Posts Without Date in Frontmatter" 3 | layout: post 4 | date: 1st Sept 2022 5 | lastmod: 3rd Dec 2022 6 | comments: false 7 | tags: Tail 8 | excerpt_separator: 9 | hidden: false 10 | --- 11 | 12 | Typically, posts in Jekyll have a `date` entry in the post’s frontmatter. This date, if not specified will be taken to be the date when the post was last modified. 13 | 14 | 15 | 16 | This is done using the `jekyll-last-modified` custom plugin (check the `_plugins` directory in the source files). The last-modified date is only used, if a `date` in not specified in the front matter. Also, you can specify the `lastmod` date in the front matter using the `last_modified_at` keyword: 17 | 18 | ```yaml 19 | --- 20 | title: "Posts Without Date in Frontmatter" 21 | layout: post 22 | date: 1st Sept 2022 23 | showlastmod: yes 24 | lastmod: 3rd Dec 2022 25 | comments: false 26 | tags: Tail 27 | excerpt_separator: 28 | hidden: false 29 | --- 30 | ``` -------------------------------------------------------------------------------- /_posts/posts-without-filename.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Posts Without Date in Filename" 3 | date: 1st Oct 2022 4 | layout: post 5 | comments: false 6 | tags: New 7 | excerpt_separator: 8 | hidden: false 9 | --- 10 | 11 | Typically, posts in Jekyll have a `YYYY-MM-DD` portion in the post’s filename. The individual markdown posts can be freed from this naming convention to reduce the clutter in the filename. 12 | 13 | 14 | 15 | Also, it become easier to export a set of `*.md` files from any note-taking app and add them to the `_posts` folder without worrying about changing the filenames. 16 | 17 | Tail does this by using a [small plugin](https://stackoverflow.com/a/68287682/9523246) that changes the `DATE_FILENAME_MATCHER` for `_posts` and `_drafts` folders 18 | 19 | ```ruby 20 | class Jekyll::PostReader 21 | # Don't use DATE_FILENAME_MATCHER so we don't need to put those stupid dates 22 | # in the filename. Also limit to just *.markdown, so it won't process binary 23 | # files from e.g. drags. 24 | def read_posts(dir) 25 | read_publishable(dir, "_posts", /.*\.md$/) 26 | end 27 | def read_drafts(dir) 28 | read_publishable(dir, "_drafts", /.*\.md$/) 29 | end 30 | end 31 | ``` -------------------------------------------------------------------------------- /_posts/reply-comments.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Comments using Email" 4 | date: 25th April 2025 5 | tags: New 6 | --- 7 | 8 | To configure comments via on your blog, edit your `_config.yml` 9 | 10 | ```yaml 11 | # Comments (Reply via email) 12 | comments: true 13 | comment_email: "" #enter an email address where you want comment emails to be sent 14 | ``` 15 | This adds a `Reply by email` button at the end of your posts, like so: 16 | -------------------------------------------------------------------------------- /_posts/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Search" 4 | date: 25th April 2025 5 | tags: New 6 | --- 7 | 8 | You can search through all posts by using the search from the navigation bar. This can also be toggled using `Ctrl` or `Cmd` + 'K'. This functionality is built using [Pagefind](https://pagefind.app). 9 | 10 | The `pagefind` binary is a part of the repo under `_bin` and it is run after the site is built. 11 | -------------------------------------------------------------------------------- /_posts/sticky-posts.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Sticky Posts" 4 | date: 17th Sept 2022 5 | tags: Old 6 | excerpt_separator: 7 | --- 8 | 9 | Sticky, or pinned, posts are featured on the top of every page. Tale provides some flexibility when it comes to this feature. There is no limit on the number of sticky posts you can have. Although do note that each page will show all your sticky posts + the paginated posts. So if you have 4 sticky posts and 5 posts per page, each page can display up to 9 posts. 10 | 11 | ## Making a post "sticky" 12 | 13 | Add `sticky: true` to the frontmatter of your blog post. 14 | 15 | ### Exclude sticky post from paginated posts 16 | 17 | By default, sticky posts are still included in the paginated posts. To exclude a sticky post from paginated posts, add `hidden: true` to the frontmatter of that blog post. 18 | 19 | -------------------------------------------------------------------------------- /_posts/upgrades.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Cleaner Theme Upgrades" 4 | date: 25th April 2025 5 | tags: New 6 | --- 7 | 8 | If you want to apply the latest updates from `Tale` onto your fork, you can use the `template` branch on the [GitHub repo](https://github.com/jitinnair/tail). 9 | 10 | This branch will always point to the lastest `main` branch but without the `_posts` folder. This is not really a seamless upgrade experience but perhaps a slighly convenient one. 11 | -------------------------------------------------------------------------------- /assets/css/_sass/404.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | .notfound { 4 | position: relative; 5 | text-align: center; 6 | margin: 4rem 0; 7 | 8 | &-error { 9 | font-size: 4rem; 10 | margin: 1rem 0; 11 | } 12 | 13 | &-line { 14 | border-top: 0.4rem solid $default-shade; 15 | display: block; 16 | margin: 0 auto 3rem; 17 | width: 4rem; 18 | } 19 | 20 | &-message { 21 | max-width: 25rem; 22 | margin: 0 auto; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /assets/css/_sass/base.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | * { 4 | @include box-sizing; 5 | line-height: 1.5; 6 | } 7 | 8 | html, 9 | body { 10 | color: $default-color; 11 | margin: 0; 12 | padding: 0; 13 | 14 | @media (prefers-color-scheme: dark) { 15 | color: $default-color-dark; 16 | background-color: $body-bg-dark; 17 | } 18 | } 19 | 20 | html { 21 | font-family: $serif-primary; 22 | font-size: 16px; 23 | overflow-y: scroll; 24 | 25 | @media (min-width: $on-small-screen) { 26 | font-size: 18px; 27 | } 28 | } 29 | 30 | body { 31 | -webkit-text-size-adjust: 100%; 32 | } 33 | 34 | h1, 35 | h2, 36 | h3, 37 | h4, 38 | h5, 39 | h6 { 40 | color: $default-shade; 41 | font-family: $sans-serif; 42 | line-height: normal; 43 | 44 | @media (prefers-color-scheme: dark) { 45 | color: $default-tint; 46 | } 47 | } 48 | 49 | a { 50 | color: $blue; 51 | text-decoration: none; 52 | } 53 | 54 | blockquote { 55 | border-left: .25rem solid $grey-2; 56 | color: $grey-1; 57 | margin: .5rem 1.25rem 0.5; 58 | padding: .1rem 1.5rem .1rem 1.5rem; 59 | 60 | @media (min-width: $on-small-screen) { 61 | padding: .1rem 1.5rem .1rem 1.5rem; 62 | } 63 | 64 | p:last-child { 65 | margin-bottom: 0; 66 | } 67 | } 68 | 69 | img { 70 | display: block; 71 | margin: 0 0 1rem; 72 | max-width: 100%; 73 | } 74 | 75 | td { 76 | vertical-align: top; 77 | } 78 | -------------------------------------------------------------------------------- /assets/css/_sass/catalogue.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | .catalogue { 4 | &-item { 5 | border-bottom: 1px solid $grey-2; 6 | color: $default-color; 7 | display: block; 8 | padding: 2rem 0; 9 | 10 | @media (prefers-color-scheme: dark) { 11 | border-bottom: 1px solid $grey-2-dark; 12 | color: $default-color-dark; 13 | } 14 | 15 | &:hover .catalogue-line, 16 | &:focus .catalogue-line { 17 | width: 5rem; 18 | } 19 | 20 | &:last-child { 21 | border: 0; 22 | } 23 | } 24 | 25 | &-pinned { 26 | color: $default-tint; 27 | font-family: $serif-secondary; 28 | letter-spacing: .5px; 29 | } 30 | 31 | &-time { 32 | color: $default-tint; 33 | font-family: $serif-secondary; 34 | letter-spacing: .5px; 35 | } 36 | 37 | &-title { 38 | color: $default-shade; 39 | display: block; 40 | font-family: $sans-serif; 41 | font-size: 2rem; 42 | font-weight: 700; 43 | margin: .5rem 0; 44 | 45 | @media (prefers-color-scheme: dark) { 46 | color: $default-color-dark; 47 | } 48 | } 49 | 50 | &-line { 51 | @include transition(all .3s ease-out); 52 | border-top: .2rem solid $default-shade; 53 | display: block; 54 | width: 2rem; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/css/_sass/code.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | @use 'variables' as *; 3 | 4 | pre, 5 | code { 6 | font-family: $monospaced; 7 | } 8 | 9 | code { 10 | background-color: $grey-3; 11 | border-radius: 3px; 12 | color: $code-color; 13 | font-size: 85%; 14 | padding: .25em .5em; 15 | @media (prefers-color-scheme: dark) { 16 | background-color: color.adjust($body-bg-dark, $lightness: 10%); 17 | } 18 | } 19 | 20 | pre { 21 | margin: 0 0 1rem; 22 | white-space: pre-wrap; 23 | } 24 | 25 | pre code { 26 | background-color: transparent; 27 | color: inherit; 28 | font-size: 100%; 29 | padding: 0; 30 | } 31 | 32 | .highlight { 33 | background-color: $grey-3; 34 | border-radius: 3px; 35 | line-height: 1.4; 36 | margin: 0 0 1rem; 37 | padding: 1rem; 38 | @media (prefers-color-scheme: dark) { 39 | background-color: color.adjust($body-bg-dark, $lightness: 10%); 40 | } 41 | 42 | pre { 43 | margin-bottom: 0; 44 | overflow-x: auto; 45 | } 46 | 47 | .lineno { 48 | color: $default-tint; 49 | display: inline-block; // Ensures the null space also isn't selectable 50 | padding: 0 .75rem 0 .25rem; 51 | // Make sure numbers aren't selectable 52 | -webkit-user-select: none; 53 | -moz-user-select: none; 54 | user-select: none; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/css/_sass/footnotes.scss: -------------------------------------------------------------------------------- 1 | //_footnotes.scss 2 | 3 | @use 'variables' as *; 4 | 5 | .footnote { 6 | text-decoration: none; 7 | color: $blue; 8 | vertical-align: super; 9 | font-size: 0.75em; 10 | padding: 0 2px; 11 | } 12 | 13 | .footnote:hover { 14 | background-color: $default-shade-dark; 15 | @media (prefers-color-scheme: dark){ 16 | background-color: $default-shade; 17 | } 18 | } 19 | 20 | .footnotes { 21 | margin-top: 3rem; 22 | padding-top: 1rem; 23 | } 24 | 25 | .footnotes ol { 26 | padding-left: 1.5rem; 27 | } 28 | 29 | .footnotes li { 30 | margin-bottom: 1rem; 31 | font-size: 0.9em; 32 | } 33 | 34 | .reversefootnote { 35 | text-decoration: none; 36 | color: $blue; 37 | margin-left: 0.5rem; 38 | } 39 | 40 | #footnote-popup { 41 | position: fixed; 42 | display: none; 43 | width: 300px; 44 | background-color: $body-bg; 45 | border: 1px solid $grey-2; 46 | border-radius: 4px; 47 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 48 | padding: 15px; 49 | z-index: 1000; 50 | font-size: 0.9em; 51 | line-height: 1.5; 52 | 53 | @media (prefers-color-scheme: dark) { 54 | background-color: $body-bg-dark; 55 | border: 1px solid $grey-2-dark; 56 | } 57 | } 58 | 59 | #footnote-popup .reversefootnote { 60 | visibility: hidden; 61 | position: absolute; 62 | } 63 | -------------------------------------------------------------------------------- /assets/css/_sass/layout.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | .container { 4 | margin: 0 auto; 5 | max-width: 800px; 6 | width: 80%; 7 | } 8 | 9 | main, 10 | footer, 11 | .nav-container { 12 | display: block; 13 | margin: 0 auto; 14 | max-width: 800px; 15 | width: 80%; 16 | } 17 | 18 | .nav { 19 | box-shadow: 0 2px 2px -2px $shadow-color; 20 | overflow: auto; 21 | 22 | @media (prefers-color-scheme: dark) { 23 | box-shadow: 0 2px 2px -2px $shadow-color-dark; 24 | } 25 | 26 | &-container { 27 | margin: 1rem auto; 28 | position: relative; 29 | text-align: center; 30 | } 31 | 32 | &-title { 33 | @include transition(all .2s ease-out); 34 | color: $default-color; 35 | display: inline-block; 36 | margin: 0; 37 | padding-right: .2rem; 38 | 39 | @media (prefers-color-scheme: dark) { 40 | color: $default-color-dark; 41 | } 42 | 43 | &:hover, 44 | &:focus { 45 | opacity: .6; 46 | } 47 | } 48 | 49 | ul { 50 | list-style-type: none; 51 | margin: 1rem 0 0; 52 | padding: 0; 53 | text-align: center; 54 | } 55 | 56 | li { 57 | @include transition(all .2s ease-out); 58 | color: $default-color; 59 | display: inline-block; 60 | opacity: .6; 61 | padding: 0 2rem 0 0; 62 | 63 | @media (prefers-color-scheme: dark) { 64 | color: $default-color-dark; 65 | } 66 | 67 | &:last-child { 68 | padding-right: 0; 69 | } 70 | 71 | &:hover, 72 | &:focus { 73 | opacity: 1; 74 | } 75 | } 76 | 77 | a { 78 | color: $default-color; 79 | font-family: $sans-serif; 80 | 81 | @media (prefers-color-scheme: dark) { 82 | color: $default-color-dark; 83 | } 84 | } 85 | } 86 | 87 | @media (min-width: 600px) { 88 | .nav { 89 | &-container { 90 | text-align: left; 91 | } 92 | 93 | ul { 94 | bottom: 0; 95 | position: absolute; 96 | right: 0; 97 | } 98 | } 99 | } 100 | 101 | footer { 102 | font-family: $serif-secondary; 103 | padding: 2rem 0; 104 | text-align: center; 105 | 106 | span { 107 | color: $default-color; 108 | font-size: .8rem; 109 | 110 | @media (prefers-color-scheme: dark) { 111 | color: $default-color-dark; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /assets/css/_sass/pagination.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | .pagination { 4 | border-top: .5px solid #eee; 5 | font-family: $serif-secondary; 6 | padding-top: 2rem; 7 | position: relative; 8 | text-align: center; 9 | 10 | @media (prefers-color-scheme: dark) { 11 | border-top: .5px solid #353535; 12 | } 13 | 14 | span { 15 | color: $default-shade; 16 | font-size: 1.1rem; 17 | 18 | @media (prefers-color-scheme: dark) { 19 | color: $default-tint; 20 | } 21 | } 22 | 23 | .top { 24 | @include transition(all .3s ease-out); 25 | color: $default-color; 26 | font-family: $sans-serif; 27 | font-size: 1.1rem; 28 | opacity: .6; 29 | 30 | @media (prefers-color-scheme: dark) { 31 | color: $default-color-dark; 32 | } 33 | 34 | &:hover { 35 | opacity: 1; 36 | } 37 | } 38 | 39 | .arrow { 40 | @include transition(all .3s ease-out); 41 | color: $default-color; 42 | position: absolute; 43 | 44 | @media (prefers-color-scheme: dark) { 45 | color: $default-color-dark; 46 | } 47 | 48 | &:hover, 49 | &:focus { 50 | opacity: .6; 51 | text-decoration: none; 52 | } 53 | } 54 | 55 | .left { 56 | left: 0; 57 | } 58 | 59 | .right { 60 | right: 0; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /assets/css/_sass/post.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | .post { 4 | padding: 3rem 0; 5 | 6 | &-info { 7 | color: $default-tint; 8 | font-family: $serif-secondary; 9 | letter-spacing: 0.5px; 10 | text-align: center; 11 | 12 | span { 13 | font-style: italic; 14 | } 15 | } 16 | 17 | &-title { 18 | color: $default-shade; 19 | font-family: $sans-serif; 20 | font-size: 2.5rem; 21 | margin: 1rem 0; 22 | text-align: center; 23 | 24 | @media (prefers-color-scheme: dark) { 25 | color: $default-color-dark; 26 | } 27 | } 28 | 29 | &-line { 30 | border-top: 0.4rem solid $default-shade; 31 | display: block; 32 | margin: 0 auto 3rem; 33 | width: 4rem; 34 | } 35 | 36 | p { 37 | margin: 0 0 1rem; 38 | text-align: justify; 39 | } 40 | 41 | a:hover { 42 | text-decoration: underline; 43 | } 44 | 45 | img { 46 | margin: 0 auto 0.5rem; 47 | } 48 | 49 | img + em { 50 | color: $default-tint; 51 | display: block; 52 | font-family: $sans-serif; 53 | font-size: 0.9rem; 54 | font-style: normal; 55 | text-align: center; 56 | } 57 | 58 | // CSS for making emoji inline 59 | img.emoji { 60 | display: inline-block; 61 | left: 0; 62 | transform: none; 63 | width: 1rem; 64 | height: 1rem; 65 | vertical-align: text-top; 66 | padding: 0; 67 | margin: 0; 68 | } 69 | } 70 | 71 | .comments { 72 | display: flex; 73 | justify-content: center; 74 | align-items: center; 75 | margin-top: 40px; 76 | height: 80px; 77 | } 78 | 79 | .comments a { 80 | text-decoration: none; 81 | color: $blue; 82 | display: flex; 83 | align-items: center; 84 | gap: 8px; 85 | } 86 | 87 | .comments a:hover { 88 | opacity: 0.5; 89 | } 90 | -------------------------------------------------------------------------------- /assets/css/_sass/search.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | /* Modal hidden state */ 4 | #search-modal.hidden { 5 | display: none; 6 | } 7 | 8 | /* Modal positioning */ 9 | #search-modal { 10 | position: fixed; 11 | inset: 0; 12 | z-index: 999; 13 | font-family: $sans-serif; 14 | } 15 | 16 | /* Overlay */ 17 | .modal-overlay { 18 | position: absolute; 19 | inset: 0; 20 | background: rgba(0, 0, 0, 0.5); 21 | } 22 | 23 | /* Modal content */ 24 | .modal-content { 25 | position: relative; 26 | margin: 6vh auto; 27 | max-width: 600px; 28 | padding: 1.5rem; 29 | border-radius: 0; 30 | z-index: 1000; 31 | box-shadow: 0 1px 3px $shadow-color; 32 | background: $body-bg; 33 | color: $default-color; 34 | 35 | @media (max-width: $on-phone){ 36 | width: 100%; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | background: $body-bg-dark; 41 | color: $default-color-dark; 42 | box-shadow: 0 1px 3px $shadow-color-dark; 43 | } 44 | 45 | p { 46 | margin: 0 0 1rem; 47 | font-size: 0.875rem; 48 | color: $default-color; 49 | 50 | @media (prefers-color-scheme: dark) { 51 | color: $default-color-dark; 52 | } 53 | } 54 | } 55 | 56 | /* Close button */ 57 | #close-search { 58 | position: absolute; 59 | right: 1rem; 60 | top: 1.25rem; 61 | background: none; 62 | border: none; 63 | font-size: 1.25rem; 64 | color: $grey-1; 65 | cursor: pointer; 66 | line-height: 1; 67 | 68 | @media (prefers-color-scheme: dark) { 69 | color: $grey-1-dark; 70 | } 71 | 72 | &:hover { 73 | color: $default-shade; 74 | 75 | @media (prefers-color-scheme: dark) { 76 | color: $default-tint-dark; 77 | } 78 | } 79 | } 80 | 81 | /* Search toggle button */ 82 | #search-toggle { 83 | color: $default-color; 84 | margin-left: 2px; 85 | align-items: center; 86 | vertical-align: middle; 87 | 88 | @media (prefers-color-scheme: dark) { 89 | color: $default-color-dark; 90 | } 91 | } 92 | 93 | :root { 94 | --pagefind-ui-border-radius: 4px; 95 | --pagefind-ui-primary: #{$default-color}; 96 | --pagefind-ui-text: #{$default-shade}; 97 | --pagefind-ui-background: #{$body-bg}; 98 | --pagefind-ui-border: #{$grey-2}; 99 | --pagefind-ui-tag: #{$default-tint}; 100 | } 101 | 102 | @media (prefers-color-scheme: dark) { 103 | :root { 104 | --pagefind-ui-primary: #{$default-color-dark}; 105 | --pagefind-ui-text: #{$default-shade-dark}; 106 | --pagefind-ui-background: #{$body-bg-dark}; 107 | --pagefind-ui-border: #{$grey-2-dark}; 108 | --pagefind-ui-tag: #{$default-tint-dark}; 109 | } 110 | } 111 | 112 | #search input:focus { 113 | border-color: $blue; 114 | box-shadow: none; 115 | outline: none; 116 | } 117 | -------------------------------------------------------------------------------- /assets/css/_sass/tags.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | .tags { 4 | &-header { 5 | &-title { 6 | color: $default-shade; 7 | font-family: $sans-serif; 8 | font-size: 2.5rem; 9 | margin: 1rem 0; 10 | text-align: center; 11 | 12 | @media (prefers-color-scheme: dark) { 13 | color: $default-color-dark; 14 | } 15 | } 16 | 17 | &-line { 18 | border-top: 0.4rem solid $default-shade; 19 | display: block; 20 | margin: 0 auto 3rem; 21 | width: 4rem; 22 | } 23 | } 24 | 25 | &-clouds { 26 | text-align: center; 27 | font-family: $sans-serif; 28 | 29 | a { 30 | display: inline-block; 31 | margin: 0 0.1rem 0.2rem; 32 | padding: 0.2rem 0.5rem; 33 | background: rgba(0, 0, 0, 0.05); 34 | border-radius: 5px; 35 | color: $default-color; 36 | text-decoration: none; 37 | 38 | @media (prefers-color-scheme: dark) { 39 | color: $default-color-dark; 40 | } 41 | 42 | &:hover, 43 | &:active { 44 | background: rgba(0, 0, 0, 0.1); 45 | } 46 | } 47 | } 48 | 49 | &-item { 50 | &-icon { 51 | height: 1rem; 52 | } 53 | 54 | &-label { 55 | display: inline-block; 56 | margin: 2rem 0 0.5rem; 57 | font-family: $sans-serif; 58 | color: $default-color; 59 | 60 | @media (prefers-color-scheme: dark) { 61 | color: $default-color-dark; 62 | } 63 | } 64 | } 65 | 66 | &-post { 67 | display: flex; 68 | justify-content: space-between; 69 | padding: 5px 0; 70 | 71 | &-title { 72 | color: $default-color; 73 | 74 | @media (prefers-color-scheme: dark) { 75 | color: $default-color-dark; 76 | } 77 | 78 | @media (max-width: $on-phone) { 79 | width: 200px; 80 | display: block; 81 | text-decoration: none; 82 | white-space: nowrap; 83 | overflow: hidden; 84 | text-overflow: ellipsis; 85 | } 86 | } 87 | 88 | &-line { 89 | @include transition(all 0.3s ease-out); 90 | border-top: 0.1rem solid $default-shade; 91 | display: block; 92 | width: 0; 93 | 94 | @media (prefers-color-scheme: dark) { 95 | border-top: 0.1rem solid $default-tint; 96 | } 97 | } 98 | 99 | &-meta { 100 | color: $default-color; 101 | text-align: right; 102 | white-space: nowrap; 103 | font-family: $monospaced; 104 | 105 | @media (prefers-color-scheme: dark) { 106 | color: $default-color-dark; 107 | } 108 | } 109 | 110 | &:hover, 111 | &:active { 112 | .tags-post-line { 113 | width: 3rem; 114 | } 115 | 116 | .tags-post-meta { 117 | color: $default-shade; 118 | 119 | @media (prefers-color-scheme: dark) { 120 | color: $default-tint; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /assets/css/_sass/variables.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | 3 | // Standard 4 | $white: #fff !default; 5 | $blue: #4a9ae1 !default; 6 | 7 | // Light Colors 8 | $default-color: #555 !default; 9 | $default-shade: #353535 !default; 10 | $default-tint: #aaa !default; 11 | $grey-1: #979797 !default; 12 | $grey-2: #e5e5e5 !default; 13 | $grey-3: #f9f9f9 !default; 14 | $shadow-color: rgba(0, 0, 0, .2) !default; 15 | $code-color: #bf616a !default; 16 | $body-bg: #fff; 17 | 18 | 19 | // Dark 20 | $default-color-dark: #98a0ac; 21 | $default-shade-dark: #ccc; 22 | $default-tint-dark: #444; 23 | $grey-1-dark: #a0a0a0; 24 | $grey-2-dark: #333; 25 | $grey-3-dark: #1c1c1c; 26 | $shadow-color-dark: rgba(255, 255, 255, .2); 27 | $code-color-dark: #db1400; 28 | $body-bg-dark: #0F1216; 29 | 30 | 31 | // Fonts 32 | $serif-primary: 'Libre Baskerville', 'Times New Roman', 'Times', serif !default; 33 | $serif-secondary: 'Palatino', 'Palatino LT STD', 'Palatino Linotype', 'Book Antiqua', 'Georgia', serif !default; 34 | $sans-serif: 'Helvetica Neue', 'Segoe UI', 'Helvetica', 'Arial', sans-serif !default; 35 | $monospaced: 'IBM Plex Mono', 'Menlo', 'Monaco', monospace !default; 36 | 37 | //length variables for responsive design 38 | //only used in tags.scss for the time being. Todo: standardize useage of sizes across the theme 39 | $on-small-screen: 600px; 40 | $on-phone: 450px; 41 | 42 | @mixin box-sizing($type: border-box) { 43 | -webkit-box-sizing: $type; 44 | -moz-box-sizing: $type; 45 | box-sizing: $type; 46 | } 47 | 48 | @mixin transition($args...) { 49 | -webkit-transition: $args; 50 | -moz-transition: $args; 51 | transition: $args; 52 | } 53 | -------------------------------------------------------------------------------- /assets/css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Only the main Sass file needs front matter (the dashes are enough) 3 | --- 4 | 5 | @use 'variables' as *; 6 | @use 'base' as *; 7 | @use 'code' as *; 8 | @use 'post' as *; 9 | @use 'layout' as *; 10 | @use 'pagination' as *; 11 | @use 'catalogue' as *; 12 | @use '404' as *; 13 | @use 'tags' as *; 14 | @use 'footnotes' as *; 15 | @use 'search' as *; 16 | -------------------------------------------------------------------------------- /assets/css/sass-code-highlight/default.scss: -------------------------------------------------------------------------------- 1 | .highlight { 2 | .hll { 3 | background-color: #ffffcc; 4 | } 5 | background: #f8f8f8; 6 | .c { 7 | color: #408080; 8 | font-style: italic; 9 | } 10 | .err { 11 | border: 1px solid #FF0000; 12 | } 13 | .k { 14 | color: #008000; 15 | font-weight: bold; 16 | } 17 | .o { 18 | color: #666666; 19 | } 20 | .cm { 21 | color: #408080; 22 | font-style: italic; 23 | } 24 | .cp { 25 | color: #BC7A00; 26 | } 27 | .c1, .cs { 28 | color: #408080; 29 | font-style: italic; 30 | } 31 | .gd { 32 | color: #A00000; 33 | } 34 | .ge { 35 | font-style: italic; 36 | } 37 | .gr { 38 | color: #FF0000; 39 | } 40 | .gh { 41 | color: #000080; 42 | font-weight: bold; 43 | } 44 | .gi { 45 | color: #00A000; 46 | } 47 | .go { 48 | color: #808080; 49 | } 50 | .gp { 51 | color: #000080; 52 | font-weight: bold; 53 | } 54 | .gs { 55 | font-weight: bold; 56 | } 57 | .gu { 58 | color: #800080; 59 | font-weight: bold; 60 | } 61 | .gt { 62 | color: #0040D0; 63 | } 64 | .kc, .kd, .kn { 65 | color: #008000; 66 | font-weight: bold; 67 | } 68 | .kp { 69 | color: #008000; 70 | } 71 | .kr { 72 | color: #008000; 73 | font-weight: bold; 74 | } 75 | .kt { 76 | color: #B00040; 77 | } 78 | .m { 79 | color: #666666; 80 | } 81 | .s { 82 | color: #BA2121; 83 | } 84 | .na { 85 | color: #7D9029; 86 | } 87 | .nb { 88 | color: #008000; 89 | } 90 | .nc { 91 | color: #0000FF; 92 | font-weight: bold; 93 | } 94 | .no { 95 | color: #880000; 96 | } 97 | .nd { 98 | color: #AA22FF; 99 | } 100 | .ni { 101 | color: #999999; 102 | font-weight: bold; 103 | } 104 | .ne { 105 | color: #D2413A; 106 | font-weight: bold; 107 | } 108 | .nf { 109 | color: #0000FF; 110 | } 111 | .nl { 112 | color: #A0A000; 113 | } 114 | .nn { 115 | color: #0000FF; 116 | font-weight: bold; 117 | } 118 | .nt { 119 | color: #008000; 120 | font-weight: bold; 121 | } 122 | .nv { 123 | color: #19177C; 124 | } 125 | .ow { 126 | color: #AA22FF; 127 | font-weight: bold; 128 | } 129 | .w { 130 | color: #bbbbbb; 131 | } 132 | .mf, .mh, .mi, .mo { 133 | color: #666666; 134 | } 135 | .sb, .sc { 136 | color: #BA2121; 137 | } 138 | .sd { 139 | color: #BA2121; 140 | font-style: italic; 141 | } 142 | .s2 { 143 | color: #BA2121; 144 | } 145 | .se { 146 | color: #BB6622; 147 | font-weight: bold; 148 | } 149 | .sh { 150 | color: #BA2121; 151 | } 152 | .si { 153 | color: #BB6688; 154 | font-weight: bold; 155 | } 156 | .sx { 157 | color: #008000; 158 | } 159 | .sr { 160 | color: #BB6688; 161 | } 162 | .s1 { 163 | color: #BA2121; 164 | } 165 | .ss { 166 | color: #19177C; 167 | } 168 | .bp { 169 | color: #008000; 170 | } 171 | .vc, .vg, .vi { 172 | color: #19177C; 173 | } 174 | .il { 175 | color: #666666; 176 | } 177 | } -------------------------------------------------------------------------------- /assets/css/sass-code-highlight/monokai.scss: -------------------------------------------------------------------------------- 1 | $background: #272822; 2 | 3 | .highlight { 4 | background-color: $background; 5 | 6 | .hll { 7 | background-color: #49483e; 8 | } 9 | .c { 10 | color: #a8a38d; 11 | } 12 | .err { 13 | color: #960050; 14 | background-color: #1e0010; 15 | } 16 | .k { 17 | color: #66d9ef; 18 | } 19 | .l { 20 | color: #ae81ff; 21 | } 22 | .n { 23 | color: #f8f8f2; 24 | } 25 | .o { 26 | color: #f92672; 27 | } 28 | .p { 29 | color: #f8f8f2; 30 | } 31 | .cm, .cp, .c1, .cs { 32 | color: #a8a38d; 33 | } 34 | .ge { 35 | font-style: italic; 36 | } 37 | .gs { 38 | font-weight: bold; 39 | } 40 | .kc, .kd { 41 | color: #66d9ef; 42 | } 43 | .kn { 44 | color: #f92672; 45 | } 46 | .kp, .kr, .kt { 47 | color: #66d9ef; 48 | } 49 | .ld { 50 | color: #e6db74; 51 | } 52 | .m { 53 | color: #ae81ff; 54 | } 55 | .s { 56 | color: #e6db74; 57 | } 58 | .na { 59 | color: #a6e22e; 60 | } 61 | .nb { 62 | color: #f8f8f2; 63 | } 64 | .nc { 65 | color: #a6e22e; 66 | } 67 | .no { 68 | color: #66d9ef; 69 | } 70 | .nd { 71 | color: #a6e22e; 72 | } 73 | .ni { 74 | color: #f8f8f2; 75 | } 76 | .ne, .nf { 77 | color: #a6e22e; 78 | } 79 | .nl, .nn { 80 | color: #f8f8f2; 81 | } 82 | .nx { 83 | color: #a6e22e; 84 | } 85 | .py { 86 | color: #f8f8f2; 87 | } 88 | .nt { 89 | color: #f92672; 90 | } 91 | .nv { 92 | color: #f8f8f2; 93 | } 94 | .ow { 95 | color: #f92672; 96 | } 97 | .w { 98 | color: #f8f8f2; 99 | } 100 | .mf, .mh, .mi, .mo { 101 | color: #ae81ff; 102 | } 103 | .sb, .sc, .sd, .s2 { 104 | color: #e6db74; 105 | } 106 | .se { 107 | color: #ae81ff; 108 | } 109 | .sh, .si, .sx, .sr, .s1, .ss { 110 | color: #e6db74; 111 | } 112 | .bp, .vc, .vg, .vi { 113 | color: #f8f8f2; 114 | } 115 | .il { 116 | color: #ae81ff; 117 | } 118 | .gh {} 119 | .gu { 120 | color: #75715e; 121 | } 122 | .gd { 123 | color: #f92672; 124 | } 125 | .gi { 126 | color: #a6e22e; 127 | } 128 | } 129 | 130 | code{ 131 | background-color: "#282C34"; 132 | color: whitesmoke;} 133 | pre { 134 | background-color: "#282C34"; 135 | color: whitesmoke; 136 | } 137 | -------------------------------------------------------------------------------- /assets/css/syntax-default.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Front matter for Jekyll processing 3 | --- 4 | 5 | @use 'variables' as *; 6 | @use "sass-code-highlight/default"; 7 | -------------------------------------------------------------------------------- /assets/css/syntax-monokai.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Front matter for Jekyll processing 3 | --- 4 | 5 | @use 'variables' as *; 6 | @use "sass-code-highlight/monokai"; 7 | -------------------------------------------------------------------------------- /assets/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitinnair1/tail/0c2dbfca6315712f491b4454e74689c13c32641d/assets/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitinnair1/tail/0c2dbfca6315712f491b4454e74689c13c32641d/assets/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /assets/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitinnair1/tail/0c2dbfca6315712f491b4454e74689c13c32641d/assets/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /assets/js/disqusLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | disqusLoader.js v1.0 3 | A JavaScript plugin for lazy-loading Disqus comments widget. 4 | - 5 | By Osvaldas Valutis, www.osvaldas.info 6 | Available for use under the MIT License 7 | */ 8 | (function(b,f,l){var r=function(a){a=a.getBoundingClientRect();return{top:a.top+f.body.scrollTop,left:a.left+f.body.scrollLeft}},t=function(a,c){var d=f.createElement("script");d.src=a;d.async=!0;d.setAttribute("data-timestamp",+new Date);d.addEventListener("load",function(){"function"===typeof c&&c()});(f.head||f.body).appendChild(d)};l=function(a,c){var d,e;return function(){var g=this,f=arguments,b=+new Date;d&&bb.innerHeight*n||0 { 27 | // Clean up any existing event listeners 28 | const newLink = link.cloneNode(true); 29 | link.parentNode.replaceChild(newLink, link); 30 | 31 | // Add new event listeners 32 | newLink.addEventListener('click', handleFootnoteClick); 33 | newLink.addEventListener('mouseenter', handleFootnoteHover); 34 | newLink.addEventListener('mouseleave', handleFootnoteLeave); 35 | }); 36 | 37 | // Event handlers 38 | function handleFootnoteClick(event) { 39 | event.preventDefault(); 40 | 41 | const footnoteLink = event.currentTarget; 42 | if (currentFootnote === footnoteLink && isPopupVisible) { 43 | hidePopup(); 44 | } else { 45 | showPopup(footnoteLink); 46 | } 47 | } 48 | 49 | function handleFootnoteHover(event) { 50 | // Clear any existing timers 51 | if (hoverTimer) clearTimeout(hoverTimer); 52 | 53 | // Show the popup 54 | showPopup(event.currentTarget); 55 | } 56 | 57 | function handleFootnoteLeave() { 58 | // Delay hiding to allow moving to popup 59 | if (hoverTimer) clearTimeout(hoverTimer); 60 | hoverTimer = setTimeout(() => { 61 | if (!isMouseOverElement(popup)) { 62 | hidePopup(); 63 | } 64 | }, 200); 65 | } 66 | 67 | // Show popup with content from footnote 68 | function showPopup(footnoteLink) { 69 | // First, record which footnote we're showing 70 | currentFootnote = footnoteLink; 71 | 72 | // Get the target footnote content (without displaying yet) 73 | const footnoteId = footnoteLink.getAttribute('href').substring(1); 74 | const footnoteContent = document.getElementById(footnoteId); 75 | if (!footnoteContent) return; 76 | 77 | // Prepare popup content by extracting only the text content 78 | // This avoids the backlink issue completely 79 | let content = ''; 80 | 81 | // Clone all child nodes except the backlink 82 | const children = footnoteContent.childNodes; 83 | for (let i = 0; i < children.length; i++) { 84 | const child = children[i]; 85 | // Skip the backlink element 86 | if (child.nodeType === 1 && child.classList && child.classList.contains('reversefootnote')) { 87 | continue; 88 | } 89 | // Clone the node (whether it's text or element) 90 | content += child.nodeType === 3 ? child.textContent : child.outerHTML; 91 | } 92 | 93 | // Apply content to the hidden popup 94 | popup.style.display = 'none'; 95 | popup.innerHTML = content; 96 | 97 | // Position popup while still hidden 98 | const linkRect = footnoteLink.getBoundingClientRect(); 99 | positionPopup(linkRect); 100 | 101 | // Now show the fully prepared popup 102 | popup.style.display = 'block'; 103 | isPopupVisible = true; 104 | 105 | // Add popup event listeners 106 | popup.addEventListener('mouseleave', handlePopupLeave); 107 | } 108 | 109 | // Hide the popup 110 | function hidePopup() { 111 | popup.style.display = 'none'; 112 | isPopupVisible = false; 113 | currentFootnote = null; 114 | 115 | // Remove popup event listeners to prevent memory leaks 116 | popup.removeEventListener('mouseleave', handlePopupLeave); 117 | } 118 | 119 | function handlePopupLeave() { 120 | hidePopup(); 121 | } 122 | 123 | // Position popup optimally based on viewport 124 | function positionPopup(linkRect) { 125 | const viewportWidth = window.innerWidth; 126 | const viewportHeight = window.innerHeight; 127 | 128 | // Get popup dimensions 129 | // We need to temporarily make it visible but off-screen to measure it 130 | popup.style.visibility = 'hidden'; 131 | popup.style.display = 'block'; 132 | popup.style.left = '-9999px'; 133 | popup.style.top = '-9999px'; 134 | 135 | const popupWidth = popup.offsetWidth; 136 | const popupHeight = popup.offsetHeight; 137 | 138 | // Calculate optimal position 139 | const linkCenter = linkRect.left + (linkRect.width / 2); 140 | let left = linkCenter - (popupWidth / 2); 141 | let top = linkRect.bottom + 10; 142 | 143 | // Adjust if popup would go off screen 144 | if (left + popupWidth > viewportWidth - 20) { 145 | left = viewportWidth - popupWidth - 20; 146 | } 147 | if (left < 20) { 148 | left = 20; 149 | } 150 | 151 | // If popup would go below viewport, position it above the link 152 | if (top + popupHeight > viewportHeight - 20) { 153 | top = linkRect.top - popupHeight - 10; 154 | 155 | // If it would now go above the viewport, just position it at the top 156 | if (top < 20) { 157 | top = 20; 158 | } 159 | } 160 | 161 | // Apply position 162 | popup.style.left = `${left}px`; 163 | popup.style.top = `${top}px`; 164 | popup.style.visibility = 'visible'; 165 | } 166 | 167 | // Helper to detect if mouse is over an element 168 | function isMouseOverElement(el) { 169 | return el.matches(':hover'); 170 | } 171 | 172 | // Close popup when clicking outside 173 | document.addEventListener('pointerdown', (e) => { 174 | if (isPopupVisible && 175 | !e.target.closest('.footnote') && 176 | !e.target.closest('#footnote-popup')) { 177 | hidePopup(); 178 | } 179 | }); 180 | 181 | // Update position if window resizes 182 | window.addEventListener('resize', () => { 183 | if (currentFootnote && isPopupVisible) { 184 | const linkRect = currentFootnote.getBoundingClientRect(); 185 | positionPopup(linkRect); 186 | } 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /assets/js/pagefind.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("DOMContentLoaded", () => { 2 | new PagefindUI({ 3 | element: "#search", 4 | showImages: false 5 | }); 6 | 7 | const modal = document.getElementById("search-modal"); 8 | const toggle = document.getElementById("search-toggle"); 9 | const close = document.getElementById("close-search"); 10 | const overlay = modal.querySelector(".modal-overlay"); 11 | 12 | function toggleModal() { 13 | const isHidden = modal.classList.contains("hidden"); 14 | modal.classList.toggle("hidden"); 15 | if (isHidden) { 16 | modal.querySelector("input").focus(); // Focus input when opening 17 | } 18 | } 19 | 20 | // Toggle modal on button click 21 | toggle.addEventListener("click", (e) => { 22 | e.preventDefault(); 23 | toggleModal(); 24 | }); 25 | 26 | // Close modal on close button click 27 | close.addEventListener("click", toggleModal); 28 | 29 | // Close modal on overlay click 30 | overlay.addEventListener("click", toggleModal); 31 | 32 | // Toggle modal on Cmd/Ctrl + K, close on Escape 33 | document.addEventListener("keydown", (e) => { 34 | if ((e.metaKey || e.ctrlKey) && e.key === "k") { 35 | e.preventDefault(); 36 | toggleModal(); 37 | } else if (e.key === "Escape") { 38 | modal.classList.add("hidden"); 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitinnair1/tail/0c2dbfca6315712f491b4454e74689c13c32641d/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: Home 4 | --- 5 | --------------------------------------------------------------------------------