From 6acf4ab874c58ee14f35da671029e56972745ce6 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Fri, 25 Apr 2025 12:09:21 +0200 Subject: feat(treewide): Migrate to zola --- .gitmodules | 4 + flake.lock | 24 +- flake.nix | 43 +- package.nix | 51 + src/.gitignore | 1 + src/config.toml | 68 + src/content/_index.md | 27 + src/content/blog/_index.md | 28 + src/content/blog/distributed-hooks.md | 105 ++ src/content/contact/_index.md | 31 + src/content/writings/_index.md | 28 + src/content/writings/kant-and-free-software.pdf.md | 7 + src/content/writings/raman-spectroscopy.pdf.md | 8 + src/highlight_themes | 1 + src/index.html | 13 - src/static/hl-dark.css | 72 + src/static/hl-light.css | 60 + src/static/icon/git-black.svg | 1 + src/static/icon/git-white.svg | 10 + src/static/icon/matrix-white.svg | 9 + src/static/icon/signal-black.svg | 1 + src/static/icon/signal-ultramarine.svg | 1 + src/static/icon/signal-white.svg | 1 + src/style.css | 50 - src/themes/serene/.gitignore | 8 + src/themes/serene/CHANGELOG.md | 347 ++++ src/themes/serene/LICENSE | 21 + src/themes/serene/README.md | 10 + src/themes/serene/USAGE.md | 536 ++++++ src/themes/serene/config.example.toml | 62 + .../serene/highlight_themes/serene-dark.tmTheme | 283 +++ .../serene/highlight_themes/serene-light.tmTheme | 239 +++ src/themes/serene/sass/giscus_dark.scss | 100 ++ src/themes/serene/sass/giscus_light.scss | 104 ++ src/themes/serene/sass/main.scss | 1839 ++++++++++++++++++++ src/themes/serene/screenshot.png | Bin 0 -> 188859 bytes src/themes/serene/static/icon/arrow-up.svg | 1 + src/themes/serene/static/icon/bluesky.svg | 1 + src/themes/serene/static/icon/caution.svg | 1 + src/themes/serene/static/icon/check.svg | 1 + src/themes/serene/static/icon/copy.svg | 1 + src/themes/serene/static/icon/email.svg | 1 + src/themes/serene/static/icon/facebook.svg | 1 + src/themes/serene/static/icon/fingerprint.svg | 1 + src/themes/serene/static/icon/github.svg | 1 + src/themes/serene/static/icon/important.svg | 1 + src/themes/serene/static/icon/instagram.svg | 1 + src/themes/serene/static/icon/linkedin.svg | 1 + src/themes/serene/static/icon/mastodon.svg | 1 + src/themes/serene/static/icon/moon.svg | 1 + src/themes/serene/static/icon/note.svg | 1 + src/themes/serene/static/icon/question.svg | 1 + src/themes/serene/static/icon/quote.svg | 8 + src/themes/serene/static/icon/sun.svg | 1 + src/themes/serene/static/icon/threads.svg | 1 + src/themes/serene/static/icon/tip.svg | 1 + src/themes/serene/static/icon/twitter.svg | 1 + src/themes/serene/static/icon/warning.svg | 1 + src/themes/serene/static/js/lightense.min.js | 2 + src/themes/serene/static/js/main.js | 259 +++ src/themes/serene/templates/404.html | 14 + src/themes/serene/templates/_base.html | 38 + src/themes/serene/templates/_custom_css.html | 59 + src/themes/serene/templates/_custom_font.html | 0 src/themes/serene/templates/_footer.html | 52 + src/themes/serene/templates/_giscus_script.html | 0 src/themes/serene/templates/_head_extend.html | 0 src/themes/serene/templates/_section_title.html | 10 + src/themes/serene/templates/anchor-link.html | 1 + src/themes/serene/templates/blog.html | 50 + src/themes/serene/templates/categories/list.html | 3 + src/themes/serene/templates/categories/single.html | 3 + src/themes/serene/templates/feed.xml | 32 + src/themes/serene/templates/home.html | 119 ++ src/themes/serene/templates/macros/collection.html | 153 ++ src/themes/serene/templates/macros/prose.html | 32 + src/themes/serene/templates/post.html | 151 ++ src/themes/serene/templates/prose.html | 82 + .../serene/templates/shortcodes/caution.html | 2 + .../serene/templates/shortcodes/collection.html | 23 + src/themes/serene/templates/shortcodes/detail.html | 4 + src/themes/serene/templates/shortcodes/figure.html | 8 + .../serene/templates/shortcodes/important.html | 2 + .../serene/templates/shortcodes/mermaid.html | 3 + src/themes/serene/templates/shortcodes/note.html | 2 + src/themes/serene/templates/shortcodes/quote.html | 10 + src/themes/serene/templates/shortcodes/tip.html | 2 + .../serene/templates/shortcodes/warning.html | 2 + src/themes/serene/templates/tags/list.html | 29 + src/themes/serene/templates/tags/single.html | 34 + src/themes/serene/theme.toml | 12 + src/writings/index.html | 9 - treefmt.nix | 11 +- update.sh | 3 + 94 files changed, 5326 insertions(+), 112 deletions(-) create mode 100644 .gitmodules create mode 100644 package.nix create mode 100644 src/.gitignore create mode 100644 src/config.toml create mode 100644 src/content/_index.md create mode 100644 src/content/blog/_index.md create mode 100644 src/content/blog/distributed-hooks.md create mode 100644 src/content/contact/_index.md create mode 100644 src/content/writings/_index.md create mode 100644 src/content/writings/kant-and-free-software.pdf.md create mode 100644 src/content/writings/raman-spectroscopy.pdf.md create mode 120000 src/highlight_themes delete mode 100644 src/index.html create mode 100644 src/static/hl-dark.css create mode 100644 src/static/hl-light.css create mode 100644 src/static/icon/git-black.svg create mode 100644 src/static/icon/git-white.svg create mode 100644 src/static/icon/matrix-white.svg create mode 100644 src/static/icon/signal-black.svg create mode 100644 src/static/icon/signal-ultramarine.svg create mode 100644 src/static/icon/signal-white.svg delete mode 100644 src/style.css create mode 100644 src/themes/serene/.gitignore create mode 100644 src/themes/serene/CHANGELOG.md create mode 100644 src/themes/serene/LICENSE create mode 100644 src/themes/serene/README.md create mode 100644 src/themes/serene/USAGE.md create mode 100644 src/themes/serene/config.example.toml create mode 100644 src/themes/serene/highlight_themes/serene-dark.tmTheme create mode 100644 src/themes/serene/highlight_themes/serene-light.tmTheme create mode 100644 src/themes/serene/sass/giscus_dark.scss create mode 100644 src/themes/serene/sass/giscus_light.scss create mode 100644 src/themes/serene/sass/main.scss create mode 100644 src/themes/serene/screenshot.png create mode 100644 src/themes/serene/static/icon/arrow-up.svg create mode 100644 src/themes/serene/static/icon/bluesky.svg create mode 100644 src/themes/serene/static/icon/caution.svg create mode 100644 src/themes/serene/static/icon/check.svg create mode 100644 src/themes/serene/static/icon/copy.svg create mode 100644 src/themes/serene/static/icon/email.svg create mode 100644 src/themes/serene/static/icon/facebook.svg create mode 100644 src/themes/serene/static/icon/fingerprint.svg create mode 100644 src/themes/serene/static/icon/github.svg create mode 100644 src/themes/serene/static/icon/important.svg create mode 100644 src/themes/serene/static/icon/instagram.svg create mode 100644 src/themes/serene/static/icon/linkedin.svg create mode 100644 src/themes/serene/static/icon/mastodon.svg create mode 100644 src/themes/serene/static/icon/moon.svg create mode 100644 src/themes/serene/static/icon/note.svg create mode 100644 src/themes/serene/static/icon/question.svg create mode 100644 src/themes/serene/static/icon/quote.svg create mode 100644 src/themes/serene/static/icon/sun.svg create mode 100644 src/themes/serene/static/icon/threads.svg create mode 100644 src/themes/serene/static/icon/tip.svg create mode 100644 src/themes/serene/static/icon/twitter.svg create mode 100644 src/themes/serene/static/icon/warning.svg create mode 100644 src/themes/serene/static/js/lightense.min.js create mode 100644 src/themes/serene/static/js/main.js create mode 100644 src/themes/serene/templates/404.html create mode 100644 src/themes/serene/templates/_base.html create mode 100644 src/themes/serene/templates/_custom_css.html create mode 100644 src/themes/serene/templates/_custom_font.html create mode 100644 src/themes/serene/templates/_footer.html create mode 100644 src/themes/serene/templates/_giscus_script.html create mode 100644 src/themes/serene/templates/_head_extend.html create mode 100644 src/themes/serene/templates/_section_title.html create mode 100644 src/themes/serene/templates/anchor-link.html create mode 100644 src/themes/serene/templates/blog.html create mode 100644 src/themes/serene/templates/categories/list.html create mode 100644 src/themes/serene/templates/categories/single.html create mode 100644 src/themes/serene/templates/feed.xml create mode 100644 src/themes/serene/templates/home.html create mode 100644 src/themes/serene/templates/macros/collection.html create mode 100644 src/themes/serene/templates/macros/prose.html create mode 100644 src/themes/serene/templates/post.html create mode 100644 src/themes/serene/templates/prose.html create mode 100644 src/themes/serene/templates/shortcodes/caution.html create mode 100644 src/themes/serene/templates/shortcodes/collection.html create mode 100644 src/themes/serene/templates/shortcodes/detail.html create mode 100644 src/themes/serene/templates/shortcodes/figure.html create mode 100644 src/themes/serene/templates/shortcodes/important.html create mode 100644 src/themes/serene/templates/shortcodes/mermaid.html create mode 100644 src/themes/serene/templates/shortcodes/note.html create mode 100644 src/themes/serene/templates/shortcodes/quote.html create mode 100644 src/themes/serene/templates/shortcodes/tip.html create mode 100644 src/themes/serene/templates/shortcodes/warning.html create mode 100644 src/themes/serene/templates/tags/list.html create mode 100644 src/themes/serene/templates/tags/single.html create mode 100644 src/themes/serene/theme.toml delete mode 100644 src/writings/index.html create mode 100755 update.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3ffb2b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "b-peetz.de/themes/serene"] + path = b-peetz.de/themes/serene + url = https://github.com/isunjn/serene.git + branch = latest diff --git a/flake.lock b/flake.lock index 2d03a28..5b636e4 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1738652123, - "narHash": "sha256-zdZek5FXK/k95J0vnLF0AMnYuZl4AjARq83blKuJBYY=", + "lastModified": 1745454774, + "narHash": "sha256-oLvmxOnsEKGtwczxp/CwhrfmQUG2ym24OMWowcoRhH8=", "owner": "ipetkov", "repo": "crane", - "rev": "c7e015a5fcefb070778c7d91734768680188a9cd", + "rev": "efd36682371678e2b6da3f108fdb5c613b3ec598", "type": "github" }, "original": { @@ -199,11 +199,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1738546358, - "narHash": "sha256-nLivjIygCiqLp5QcL7l56Tca/elVqM9FG1hGd9ZSsrg=", + "lastModified": 1745391562, + "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c6e957d81b96751a3d5967a0fd73694f303cc914", + "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7", "type": "github" }, "original": { @@ -235,11 +235,11 @@ ] }, "locked": { - "lastModified": 1738635966, - "narHash": "sha256-5MbJhh6nz7tx8FYVOJ0+ixMaEn0ibGzV/hScPMmqVTE=", + "lastModified": 1745548521, + "narHash": "sha256-xyliq8oS5OnzXjHRGr92RtmrtYI/dflf2gSEo0wMFjc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "1ff8663cd75a11e61f8046c62f4dbb05d1907b44", + "rev": "eb0afb4ac0720d55c29e88eb29432103d73ae11d", "type": "github" }, "original": { @@ -270,11 +270,11 @@ ] }, "locked": { - "lastModified": 1738680491, - "narHash": "sha256-8X7tR3kFGkE7WEF5EXVkt4apgaN85oHZdoTGutCFs6I=", + "lastModified": 1744961264, + "narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "64dbb922d51a42c0ced6a7668ca008dded61c483", + "rev": "8d404a69efe76146368885110f29a2ca3700bee6", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 34c0ef8..5e4e91b 100644 --- a/flake.nix +++ b/flake.nix @@ -7,9 +7,7 @@ crane = { url = "github:ipetkov/crane"; - inputs = { - nixpkgs.follows = "nixpkgs"; - }; + inputs = {}; }; rust-overlay = { url = "github:oxalica/rust-overlay"; @@ -98,33 +96,28 @@ system: let pkgs = nixpkgs.legacyPackages.${system}; treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;}; - build = pkgs.stdenv.mkDerivation { - pname = "b-peetz.de"; - version = "v1.0"; + + build = pkgs.callPackage ./package.nix {inherit essay facharbeit;}; + check = pkgs.stdenv.mkDerivation { + # Add the source name, to force this to rerun. + name = "zola-check-${./src}"; src = ./src; + # Allow network access for link checking + outputHash = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; + outputHashMode = "recursive"; + # Run local preferLocalBuild = true; allowSubstitutes = false; - nativeBuildInputs = []; - buildPhase = '' - mkdir --parents ./dead-trees{,/chemistry,/philosophy} - - install -D ${essay.outputs.packages."${system}".default}/philosophy/kant_and_free_software.pdf ./writings/philosophy/kant_and_free_software.pdf - # NOTE: This link is for backward compatibility, as I have given out links with that url <2024-07-13> - ln --symbolic --relative ./writings/philosophy/kant_and_free_software.pdf ./dead-trees/kant_and_free_software.pdf - ln --symbolic --relative ./writings/philosophy/kant_and_free_software.pdf ./dead-trees/philosophy/kant_and_free_software.pdf + nativeBuildInputs = [pkgs.zola pkgs.findutils]; - install -D ${facharbeit.outputs.packages."${system}".default}/chemistry/facharbeit.pdf ./writings/chemistry/facharbeit.pdf - # NOTE: This link is for backward compatibility, as I have given out links with that url <2024-07-13> - ln --symbolic --relative ./writings/chemistry/facharbeit.pdf ./dead-trees/raman_spectrometer.pdf - ln --symbolic --relative ./writings/chemistry/facharbeit.pdf ./dead-trees/chemistry/facharbeit.pdf - ''; - installPhase = '' - install -d $out/ - cp --recursive . $out/ + buildPhase = '' + zola check ''; + + installPhase = "touch $out"; }; in { packages = { @@ -132,7 +125,7 @@ }; checks = { - inherit build; + inherit build check; formatting = treefmtEval.config.build.check self; }; @@ -140,7 +133,9 @@ devShells = { default = pkgs.mkShell { - packages = []; + packages = [ + pkgs.zola + ]; }; }; } diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..7f52163 --- /dev/null +++ b/package.nix @@ -0,0 +1,51 @@ +{ + stdenv, + system, + # Deps + zola, + # Data + facharbeit, + essay, +}: +stdenv.mkDerivation { + pname = "b-peetz.de"; + version = "v1.0"; + src = ./src; + + # Run local + preferLocalBuild = true; + allowSubstitutes = false; + + nativeBuildInputs = [ + zola + ]; + + buildPhase = '' + zola build + + cd ./public || exit 1 + + mkdir --parents ./dead-trees{,/chemistry,/philosophy} + + install -D ${essay.outputs.packages."${system}".default}/philosophy/kant_and_free_software.pdf ./writings/philosophy/kant_and_free_software.pdf + base="./writings/philosophy/kant_and_free_software.pdf" + ln --symbolic --force --relative "$base" ./writings/kant-and-free-software.pdf + + # NOTE: This link is for backward compatibility, as I have given out links with that url <2024-07-13> + ln --symbolic --relative "$base" ./dead-trees/kant_and_free_software.pdf + ln --symbolic --relative "$base" ./dead-trees/philosophy/kant_and_free_software.pdf + + install -D ${facharbeit.outputs.packages."${system}".default}/chemistry/facharbeit.pdf ./writings/chemistry/facharbeit.pdf + base="./writings/chemistry/facharbeit.pdf" + ln --symbolic --force --relative "$base" ./writings/raman-spectroscopy.pdf + + # NOTE: This link is for backward compatibility, as I have given out links with that url <2024-07-13> + ln --symbolic --relative "$base" ./dead-trees/raman_spectrometer.pdf + ln --symbolic --relative "$base" ./dead-trees/chemistry/facharbeit.pdf + ''; + + installPhase = '' + install -d $out/ + cp --recursive . $out/ + ''; +} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..c75eecc --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +/public diff --git a/src/config.toml b/src/config.toml new file mode 100644 index 0000000..cabb9a9 --- /dev/null +++ b/src/config.toml @@ -0,0 +1,68 @@ +# serene v5.2.1 +# +# - docs: https://github.com/isunjn/serene/blob/latest/USAGE.md +# - check for updates: https://github.com/isunjn/serene/releases +# +#========================================================================================= + +base_url = "https://b-peetz.de" +title = "Benedikt Peetz" +description = "My website" +default_language = "en" +theme = "serene" +output_dir = "public" +compile_sass = true +minify_html = false # Keep this false, as it may cause issues with some styles +build_search_index = false # Keep this false, search is temporarily unsupported +generate_feeds = false # Whether to generate a feed file in root, read docs for more info about rss feed +feed_filenames = ["feed.xml"] # "feed.xml" | "atom.xml" | "rss.xml", read docs for more info + +taxonomies = [ + { name = "science", feed = true}, + { name = "tags" }, + { name = "categories" } +] + +[markdown] +highlight_code = true +highlight_theme = "css" +extra_syntaxes_and_themes = ["highlight_themes"] +highlight_themes_css = [ + { theme = "serene-light", filename = "hl-light.css" }, + { theme = "serene-dark", filename = "hl-dark.css" }, +] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= + +[extra] + +sections = [ + { name = "blog", path = "/blog", is_external = false }, + { name = "writings", path = "/writings", is_external = false }, + { name = "contact", path = "/contact", is_external = false }, + { name = "git", path = "https://git.foss-syndicate.org/bpeetz", is_external = true }, +] +blog_section_path = "/blog" + +back_link_text = "Back" # Text of the back button +force_theme = "dark" # false | "light" | "dark" + +footer_copyright = "© 2025 Benedikt Peetz" +footer_credits = true # Whether to show "Built with zola and serene" in footer + +not_found_error_text = "404 Not Found" +not_found_recover_text = "« back to home »" + +reaction = false # Whether to enable anonymous emoji reactions (Note: You need to set up a working api endpoint to enable this feature) +reaction_align = "right" # "left" | "center" | "right" +reaction_endpoint = "https://example.com/api/reaction" diff --git a/src/content/_index.md b/src/content/_index.md new file mode 100644 index 0000000..2916843 --- /dev/null +++ b/src/content/_index.md @@ -0,0 +1,27 @@ ++++ +template = 'home.html' + +[extra] +lang = 'en' + +# Show footer in home page +footer = true + +# If you don't want to display id/bio/avatar, simply comment out that line +name = "Benedikt Peetz" +id = "bpeetz" +bio = "he/him" +# avatar = "img/avatar.webp" +links = [ + { name = "Git", icon = "git-white", url = "https://git.foss-syndicate.org/bpeetz" }, + { name = "Email", icon = "email", url = "mailto:benedikt.peetz@b-peetz.de" }, + { name = "Matrix", icon = "matrix-white", url = "https://matrix.to/#/@soispha:matrix.vhack.eu" }, + { name = "Signal", icon = "signal-white", url = "https://signal.me/#eu/r03RJhrEynGMYVMhhpGuirRRMJYm_Yjgys3gfQPayVlQmb0Bv25Pt4_HT9QYpa7w" }, +] + +# Show a few recent posts in home page +recent = true +recent_max = 15 +recent_more_text = "more »" +date_format = "%b %-d, %Y" ++++ diff --git a/src/content/blog/_index.md b/src/content/blog/_index.md new file mode 100644 index 0000000..838b240 --- /dev/null +++ b/src/content/blog/_index.md @@ -0,0 +1,28 @@ ++++ +title = "List of blog posts" +description = "My blog posts." +sort_by = "date" +template = "blog.html" +page_template = "post.html" +insert_anchor_links = "right" +generate_feeds = true + +[extra] +lang = "en" + +title = "Blog" +# subtitle = "I write about ...." + +date_format = "%b %-d, %Y" + +categorized = false # posts can be categorized +back_to_top = true # show back-to-top button +toc = true # show table-of-contents +comment = true # enable comment +copy = true # show copy button in code block + +outdate_alert = true +outdate_alert_days = 90 +outdate_alert_text_before = "This article was last updated " +outdate_alert_text_after = " days ago and may be out of date." ++++ diff --git a/src/content/blog/distributed-hooks.md b/src/content/blog/distributed-hooks.md new file mode 100644 index 0000000..b32c743 --- /dev/null +++ b/src/content/blog/distributed-hooks.md @@ -0,0 +1,105 @@ ++++ +title = "An collection of my toughs regarding hooks in a fully distributed system" +date = 2025-04-25 ++++ + + +## Hooks in a distributed system + +We assume that our distributed system (system for short), contains in +total one task set. This task set is synced via multiple replicas and, +most importantly, not one replica owns it. As such a replica needs to +synchronize with every other replica to be able to claim, that they own +the full task set. + +We just assume, that each of the replicas can perfectly synchronize +itself. + +What if a user wanted to run a hook on ever new input to this task set +(i.e., on every new task)? + +Where would the hook execution happen? + + +### 1. The naive way (e.g. Taskwarrior or git) + +You could simply run the hook in the client that adds/modifies the task to the task set. +At first, this approach might look very promising: +It is easy, gives the user direct feedback about the hook return status (and thus allows to use the hook as a filter) and most importantly it is completely transparent for the user. + +The problem with this approach is unfortunately not fixable. +Take for example following setup: + +``` + | Desktop |---------------------------- | Smartphone | + | | + +--------------| Laptop |-----------------+ +``` + +And assume, that all of them have access to a client, that can add task to the task set. +Now assume, that I have a hook that connects to a server and starts a time there. + +If I were to start a task on my desktop, the hook would fire and tell the server to start time tracking. +If I later stop the task on my laptop, the hook would fire again and tell the server to stop tracking time. +This works flawlessly, as the server was already tracking time and as such can stop doing so. + +Let's imagine a different approach: +I start the task on my smartphone, which has a client that is not able to run hooks directly, as my smartphone lacks a full Linux system. +Now, trying to stop the task on my laptop, raises an error with the server, as the task was never started. + +In this case, we would need a way to track that the smartphone has not yet started the time on the server. +And that this should happen once the replica on the laptop got a hold of the task (i.e., it synchronized itself with the replica on the smartphone). + +### 2. Centralized approach (e.g. Git on servers) + +The “easy” way out is simply promoting on of the replicas to be our point of centralization. +This replica could then run the hook for every new input it receives via synchronization. + +After a full synchronization with every other replica out there, we know that the hook was run exactly once for each task. + +The problem with this approach is quite apparent: +We need to promote one of the replicas. +This means that the hook can only be run, _after_ this central replica synchronized itself. +As such, filter hooks, that prevent certain tasks to be inserted into the whole task set are only run after the fact. +This also makes it necessary that the other replicas wait for this central replica to advance before they advance themselves. + +This approach is quite similar to git's branches. +Our central replica would be the main branch, and all the other replicas would than rebase themselves regularly on the main branch. + +### 3. Distributed Tracking + +Having now explained why both running the task directly on the client, and running it in a centralized replica has downsides, I would like to point out my third idea. + +What if we combine these approaches? + +A client marks a task at replica addition time, with the hooks that it has already executed (in our example above the server timer start/stop hook). +If a replica synchronizes itself and receives a task, which has not yet recorded hook execution, it will execute them instead of the original client. + +Have you noticed the problem in this approach? + +Exactly! Hooks now need to be idempotent and can possibly be executed at an arbitrary time _after_ the original task was added: + +My smartphone client, marks the new task as having not run any hooks, as such _both_ my desktop and my laptop will run the server time tracking hook. +This will than fail on the later run, as the server cannot start time tracking for an already started task. +Additionally, the second hook run could also happen _after_ the task was already stopped (marking it started again)! + +As such we exchanged having no hook execution, for one prone to race conditions. + + +### 4. Hook execution on every client + +Having seen, that working around client hook execution does not really work (cf. approach 2 or 3), we could also go the other way and give all clients the possibility to execute hooks. + +This would require two things: +1. Hooks need to be somehow synchronized between clients (you cannot expect someone, to manually sync a hook script with a mobile client) +2. Hooks can no longer be undefined executable blobs. They need to be constricted, to a subset of executables (e.g., to Lua/python/web assembly). + +With these two requirements in place, a client could ship a Lua/python/web assembly runtime and thus guarantee that it can execute all possible hooks. + +There is a big problem with this approach. +It breaks probably most of the current hooks, because they are either written in a not-included language like POSIX shell (which cannot be included, because it probably hard-depends on binary dependencies (GNU `coreutils` as the most prevalent) which cannot be introspected from the outside) or are written in one of the included languages, but depend on external dependencies (many python hooks, for example, try to execute `task` to perform further task operations). + +In general, this approach would probably require a sandbox of some sort for hooks, so that hook authors know that their hook will also work on other platforms. +If we limit hooks to a subset of possible options, we should also enforce it on the platforms with more possibilities, so that hook authors can be confident that their hook actually works everywhere. + + diff --git a/src/content/contact/_index.md b/src/content/contact/_index.md new file mode 100644 index 0000000..3276460 --- /dev/null +++ b/src/content/contact/_index.md @@ -0,0 +1,31 @@ ++++ +title = "Contact" +description = "How to contact me" +template = "prose.html" +insert_anchor_links = "none" + +[extra] +lang = 'en' + +title = "Contact" +subtitle = "How to contact me" + + +math = false +mermaid = false +copy = false +comment = false +reaction = false ++++ + +Currently, you can reach me via the following methods (in order of preference): +- [Email – benedikt.peetz@b-peetz.de](mailto:benedikt.peetz@b-peetz.de) +- [Matrix – @soispha:vhack.eu](https://matrix.to/#/@soispha:matrix.vhack.eu) +- [Signal – benedikt.32](https://signal.me/#/eu/r03RJhrEynGMYVMhhpGuirRRMJYm_Yjgys3gfQPayVlQmb0Bv25Pt4_HT9QYpa7w) + +You can find my published PGP key via the web key directory: +``` +sq network wkd search benedikt.peetz@b-peetz.de --output - +``` + +Feel free to use that, if you want to use encrypted email. diff --git a/src/content/writings/_index.md b/src/content/writings/_index.md new file mode 100644 index 0000000..720c953 --- /dev/null +++ b/src/content/writings/_index.md @@ -0,0 +1,28 @@ ++++ +title = "Writings" +description = "Sort of academically written things" +sort_by = "date" +template = "blog.html" +page_template = "post.html" +insert_anchor_links = "right" +generate_feeds = true + +[extra] +lang = "en" + +title = "Writings" +subtitle = "Sort of academically written things." + +date_format = "%b %-d, %Y" + +categorized = true # posts can be categorized +back_to_top = true # show back-to-top button +toc = true # show table-of-contents +comment = true # enable comment +copy = true # show copy button in code block + +outdate_alert = true +outdate_alert_days = 90 +outdate_alert_text_before = "This article was last updated " +outdate_alert_text_after = " days ago and may be out of date." ++++ diff --git a/src/content/writings/kant-and-free-software.pdf.md b/src/content/writings/kant-and-free-software.pdf.md new file mode 100644 index 0000000..9c1ae1a --- /dev/null +++ b/src/content/writings/kant-and-free-software.pdf.md @@ -0,0 +1,7 @@ ++++ +title = "Kant and free software" +date = "2024-03-20" + +[taxonomies] +categories=["philosophy"] ++++ diff --git a/src/content/writings/raman-spectroscopy.pdf.md b/src/content/writings/raman-spectroscopy.pdf.md new file mode 100644 index 0000000..004a8a6 --- /dev/null +++ b/src/content/writings/raman-spectroscopy.pdf.md @@ -0,0 +1,8 @@ ++++ +title = "Bau und Evaluation eines günstigen Raman Spektrometers" +date = "2024-06-01" +language = "de" + +[taxonomies] +categories=["chemistry"] ++++ diff --git a/src/highlight_themes b/src/highlight_themes new file mode 120000 index 0000000..5109ab5 --- /dev/null +++ b/src/highlight_themes @@ -0,0 +1 @@ +themes/serene/highlight_themes \ No newline at end of file diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 63f1039..0000000 --- a/src/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Benedikt's Website - - - -

Hi!

-

For a list of my written things look here

- - diff --git a/src/static/hl-dark.css b/src/static/hl-dark.css new file mode 100644 index 0000000..da26983 --- /dev/null +++ b/src/static/hl-dark.css @@ -0,0 +1,72 @@ +/* + * theme "Tomorrow Night" generated by syntect + */ + +.z-code { + color: #c5c8c6; +} + +.z-comment, .z-string.z-quoted.z-double.z-block.z-python { + color: #999999; +} +.z-keyword.z-operator.z-class, .z-constant.z-other, .z-source.z-php.z-embedded.z-line { + color: #ced1cf; +} +.z-variable, .z-support.z-other.z-variable, .z-string.z-other.z-link, .z-string.z-regexp, .z-entity.z-name.z-tag, .z-entity.z-other.z-attribute-name, .z-meta.z-tag, .z-declaration.z-tag { + color: #a67878; +} +.z-constant.z-numeric, .z-constant.z-language, .z-support.z-constant, .z-constant.z-character, .z-variable.z-parameter, .z-punctuation.z-section.z-embedded, .z-keyword.z-other.z-unit { + color: #e08355; +} +.z-type, .z-entity.z-name.z-class, .z-entity.z-name.z-type.z-class, .z-support.z-type, .z-support.z-class { + color: #83aaa5; +} +.z-string, .z-constant.z-other.z-symbol, .z-entity.z-other.z-inherited-class, .z-markup.z-heading { + color: #85ad74; +} +.z-keyword.z-operator, .z-constant.z-other.z-color { + color: #83aaa5; +} +.z-entity.z-name.z-function, .z-meta.z-function-call, .z-support.z-function, .z-keyword.z-other.z-special-method, .z-meta.z-block-level { + color: #81a2be; +} +.z-keyword, .z-storage, .z-storage.z-type, .z-entity.z-name.z-tag.z-css { + color: #b294bb; +} +.z-invalid { + color: #ced2cf; + background-color: #df5f5f; +} +.z-meta.z-separator { + color: #ced2cf; + background-color: #82a3bf; +} +.z-invalid.z-deprecated { + color: #ced2cf; + background-color: #b798bf; +} +.z-markup.z-inserted.z-diff, .z-markup.z-deleted.z-diff, .z-meta.z-diff.z-header.z-to-file, .z-meta.z-diff.z-header.z-from-file { + color: #ffffff; +} +.z-markup.z-inserted.z-diff, .z-meta.z-diff.z-header.z-to-file { + color: #4baf5c; +} +.z-markup.z-deleted.z-diff, .z-meta.z-diff.z-header.z-from-file { + color: #d46565; +} +.z-meta.z-diff.z-header.z-from-file, .z-meta.z-diff.z-header.z-to-file { + color: #4271ae; +} +.z-meta.z-diff.z-range { + color: #3e999f; +font-style: italic; +} +.z-markup.z-deleted { + color: #f92672; +} +.z-markup.z-inserted { + color: #a6e22e; +} +.z-markup.z-changed { + color: #967efb; +} diff --git a/src/static/hl-light.css b/src/static/hl-light.css new file mode 100644 index 0000000..acb83c5 --- /dev/null +++ b/src/static/hl-light.css @@ -0,0 +1,60 @@ +/* + * theme "Tomorrow" generated by syntect + */ + +.z-code { + color: #4d4d4c; +} + +.z-comment, .z-string.z-quoted.z-double.z-block.z-python { + color: #999999; +} +.z-keyword.z-operator.z-class, .z-constant.z-other, .z-source.z-php.z-embedded.z-line { + color: #666969; +} +.z-variable, .z-support.z-other.z-variable, .z-string.z-other.z-link, .z-string.z-regexp, .z-entity.z-name.z-tag, .z-entity.z-other.z-attribute-name, .z-meta.z-tag, .z-declaration.z-tag { + color: #a67878; +} +.z-constant.z-numeric, .z-constant.z-language, .z-support.z-constant, .z-constant.z-character, .z-variable.z-parameter, .z-punctuation.z-section.z-embedded, .z-keyword.z-other.z-unit { + color: #e08355; +} +.z-type, .z-entity.z-name.z-class, .z-entity.z-name.z-type.z-class, .z-support.z-type, .z-support.z-class { + color: #568a8f; +} +.z-string, .z-constant.z-other.z-symbol, .z-entity.z-other.z-inherited-class, .z-markup.z-heading { + color: #85ad74; +} +.z-keyword.z-operator, .z-constant.z-other.z-color { + color: #568a8f; +} +.z-entity.z-name.z-function, .z-meta.z-function-call, .z-support.z-function, .z-keyword.z-other.z-special-method, .z-meta.z-block-level { + color: #4271ae; +} +.z-keyword, .z-storage, .z-storage.z-type { + color: #8959a8; +} +.z-invalid { + color: #ffffff; + background-color: #df5f5f; +} +.z-meta.z-separator { + color: #ffffff; + background-color: #4271ae; +} +.z-invalid.z-deprecated { + color: #ffffff; + background-color: #8959a8; +} +.z-markup.z-inserted.z-diff, .z-meta.z-diff.z-header.z-to-file { + color: #229545; +} +.z-markup.z-deleted.z-diff, .z-meta.z-diff.z-header.z-from-file { + color: #c82829; +} +.z-meta.z-diff.z-header.z-from-file, .z-meta.z-diff.z-header.z-to-file { + color: #4271ae; +} +.z-meta.z-diff.z-range { + color: #3e999f; +font-style: italic; +} diff --git a/src/static/icon/git-black.svg b/src/static/icon/git-black.svg new file mode 100644 index 0000000..a4704b9 --- /dev/null +++ b/src/static/icon/git-black.svg @@ -0,0 +1 @@ + diff --git a/src/static/icon/git-white.svg b/src/static/icon/git-white.svg new file mode 100644 index 0000000..6ada92c --- /dev/null +++ b/src/static/icon/git-white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/static/icon/matrix-white.svg b/src/static/icon/matrix-white.svg new file mode 100644 index 0000000..0d3cf54 --- /dev/null +++ b/src/static/icon/matrix-white.svg @@ -0,0 +1,9 @@ + + + Matrix (protocol) logo + + + + + + diff --git a/src/static/icon/signal-black.svg b/src/static/icon/signal-black.svg new file mode 100644 index 0000000..8f0fbd7 --- /dev/null +++ b/src/static/icon/signal-black.svg @@ -0,0 +1 @@ + diff --git a/src/static/icon/signal-ultramarine.svg b/src/static/icon/signal-ultramarine.svg new file mode 100644 index 0000000..87f7a77 --- /dev/null +++ b/src/static/icon/signal-ultramarine.svg @@ -0,0 +1 @@ + diff --git a/src/static/icon/signal-white.svg b/src/static/icon/signal-white.svg new file mode 100644 index 0000000..e869680 --- /dev/null +++ b/src/static/icon/signal-white.svg @@ -0,0 +1 @@ + diff --git a/src/style.css b/src/style.css deleted file mode 100644 index c245d20..0000000 --- a/src/style.css +++ /dev/null @@ -1,50 +0,0 @@ -a:link, a:visited { - color: #005386; -} - -ul a { - text-decoration: none; -} - -body { - margin:40px auto; - max-width:650px; - line-height:1.6; - font-size:18px; - padding:0 10px -} - -img { - max-width:100%; - display: block; - margin: 0 auto; -} - -@media (prefers-color-scheme: dark) { - body { - color: #ffffff; - background: #212121; - margin:40px auto; - max-width:650px; - line-height:1.6; - font-size:18px; - padding:0 10px - } - - h1, h2, h3 { - line-height:1.2 - } - - a:link, a:visited { - color: #58a6ff; - } - - ul a { - text-decoration: none; - } - - ul a:visited { - text-decoration: none; - color: #8e96f0; - } -} diff --git a/src/themes/serene/.gitignore b/src/themes/serene/.gitignore new file mode 100644 index 0000000..87760eb --- /dev/null +++ b/src/themes/serene/.gitignore @@ -0,0 +1,8 @@ +public/ +content/ +static/img/ +static/hl-light.css +static/hl-dark.css +config.toml + +.DS_Store diff --git a/src/themes/serene/CHANGELOG.md b/src/themes/serene/CHANGELOG.md new file mode 100644 index 0000000..872470b --- /dev/null +++ b/src/themes/serene/CHANGELOG.md @@ -0,0 +1,347 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [5.2.1] - 2025-04-19 + +- fix: homepage avatar style + +## [5.2.0] - 2025-04-10 + +- ui: a few tweaks + +## [5.1.0] - 2025-04-08 + +- feat: add support for zola v0.20.0 codeblock [name annotation](https://www.getzola.org/documentation/content/syntax-highlighting/#annotations), previous `codeblock` shortcode is deprecated +- refactor: remove `static/` prefix from icon paths, eliminates the need to copy icon files [@koyokr](https://github.com/koyokr) ([#76](https://github.com/isunjn/serene/pull/76)) + +## [5.0.1] - 2025-03-31 + +- fix: external links & recent posts on the homepage [@Hiramiya](https://github.com/Hiramiya) ([#74](https://github.com/isunjn/serene/pull/74)) + +## [5.0.0] - 2025-03-20 + +> **Warning** +> +> This version is a big redesign and contains lots of breaking changes. +> If you came from a previous version and want to upgrade, I suggest you start all over again. + +- New style: headerless, section title and subtitle, improved typography... +- Collections: special blocks for showcasing your list (a more general form of previous 'projects' page) +- Default icon size changed from 20 to 18 +- Some config options are moved from `config.toml` to specific `_index.md` +- Added options: `date_format` `back_link_text`, (section) `title` `subtitle`, (homepage) `footer` +- Removed options: `display_*` `nav_*` `blur_effect` `display_tags` `truncate_summary` `not_found_title` +- Callouts: `question` removed, `alert` renamed to `caution`, attribution `header` renamed to `title` +- Typst math rendering removed +- Added CSS variables: `--primary-decoration-color` `--text-decoration-color` `--highlight-mark-color` `--font-size` `--line-height` +- Removed CSS variables: `--homepage-font-size` `--homepage-line-height` `--paragraph-font-size` `--paragraph-line-height` `--aside-font-size` +- Lots of UI tweaks +- feat: Support subpath `base_url` [@b-d-e](https://github.com/b-d-e) ([#68](https://github.com/isunjn/serene/pull/68)) +- fix: `force_theme` option [@teh-banana](https://github.com/teh-banana) ([#71](https://github.com/isunjn/serene/pull/71)) +- fix: Add content-type header to reaction fetch [@sorokya](https://github.com/sorokya) ([#72](https://github.com/isunjn/serene/pull/72)) + +## [4.5.0] - 2024-11-03 + +### UI: + +- A few tweaks + +## [4.4.0] - 2024-11-02 + +### Add: + +- New feature: Anonymous emoji reactions +- New options: `display_bio` `display_avatar` `recent` + +### Remove: + +- Removed options: `homepage_layout` (use `recent` instead), `recent_more` +- Removed css variable: `--icon-size` + +### UI: + +- A few tweaks + +## [4.3.0] - 2024-10-13 + +### Add: + +- Add katex [copy-tex](https://github.com/KaTeX/KaTeX/tree/main/contrib/copy-tex) extension & bump katex version to 0.16.11 + +## [4.2.0] - 2024-10-04 + +### Fix: + +- Fix anchor link style issue, now `#` should no be present in the RSS file + +## [4.1.0] - 2024-09-16 + +### Add: + +- `force_theme` option to only use light or dark theme [@bruceoberg](https://github.com/bruceoberg) ([#62](https://github.com/isunjn/serene/issues/62)) +- A few more icons [@bruceoberg](https://github.com/bruceoberg) ([#63](https://github.com/isunjn/serene/issues/63)) + + +## [4.0.0] - 2024-08-11 + +- Deal with breaking changes of zola 19 config options: +> - Changed config options named `generate_feed` to `generate_feeds` (both in config.toml and in section front-matter) +> - Changed config option `feed_filename: String` to `feed_filenames: Vec` + +## [3.4.0] - 2024-04-25 + +### Add: + +- Math rending with [Typst](https://typst.app) [@Lambdaris](https://github.com/Lambdaris) ([#57](https://github.com/isunjn/serene/pull/57)) + +## [3.3.1] - 2024-03-10 + +### Fix: + +- Callout content overflow issue + +### UI: + +- Change highlight color of `diff` syntax +- A few tweaks + +## [3.3.0] - 2024-03-01 + +### Add: + +- New css variables: `--callout-border-radius` `--detail-border-radius` + +### Fix: + +- Overflow issue on mobile screens + +### UI: + +- Update quote icon +- A few tweaks + +## [3.2.0] - 2024-01-26 + +### Add: + +- Dark mode img/chart brightness option + +## [3.1.0] - 2024-01-20 + +### Add: + +- New shortcode: `quote` and `detail` + +### Fix: + +- Add `word-wrap: break-word` to inline code + + +## [3.0.0] - 2024-01-14 + +> **Warning** +> +> This version contains several breaking changes. +> If you came from a previous version and want to upgrade, I suggest you start all over again. + +### Add: + +- `recent` homepage layout +- `featured` mark +- Add title to ToC when it's too long +- A way to sort categories +- Project item image +- prerender/prefetch when hover, using `speculationrules` or `prefetch` +- RSS mask +- A few more css variables + +### Fix: + +- Theme init logic +- Mobile sidebar ui + +### UI: + +- A few tweaks +- Default icon size set to 20 (You should re-copy the `static/icon` folder) + + +## [2.3.0] - 2024-01-09 + +### Fix: + +- `z-index` of mobile sidebar + +### UI: + +- Color change and some small tweaks + +### Remove: + +- Default custom font removed + + +## [2.2.1] - 2024-01-02 + +### Fix: + +- Use `sessionStorage` for theme init in `_base.html` + +## [2.2.0] - 2023-12-29 + +### Fix: + +- Use `sessionStorage` for theme restore +- Fix an issue when initializing giscus theme +- Hide `#` anchor link in feed file + +## [2.1.2] - 2023-09-19 + +### Fix: + +- Outdate alert not 'hidden' ([#49](https://github.com/isunjn/serene/issues/49)) + +## [2.1.1] - 2023-09-16 + +### Add: + +- Custom 404 page + +## [2.0.1] - 2023-09-13 + +### Fix: + +- Min height of prose page & post page + +## [2.0.0] - 2023-09-01 + +> **Warning** +> +> This version contains several breaking changes. +> If you came from a previous version and want to upgrade, I suggest you start all over again. + +### UI: + +- Text selction now is styled +- Other minor tweaks +- Change defalut bg color of codeblock to transparent + +### Add: + +- Option `dispaly_tags` and `truncate_summary` [@woojiq](https://github.com/woojiq) ([#40](https://github.com/isunjn/serene/issues/40)) +- Support for footnote and backlink +- Active TOC indicator +- Generay `prose` section/page +- Config option `sections`, now you can rename `blog` to somthing else, e.g. `posts` +- Support for header nav fold/unfold +- Option for homepage layout, can be `about` or `list` +- A separate `_custom_css.html` for css customization + +### Fix: + +- Codeblock distance calculation +- Codeblock highlight style +- Add description tag only when it's available +- Post 3 column layout issue +- Inline code style in list item +- Link text-decoration style on mobile + + +## [1.2.0] - 2023-08-19 + +### UI: + +- Use noborder theme of giscus by default +- Post list item and callout styles changed +- Code block styles improved +- Default colors changed + +### Add: + +- Outline styles [@mrtnvgr](https://github.com/mrtnvgr) ([#26](https://github.com/isunjn/serene/pull/26)) +- Support self-host font ([#29](https://github.com/isunjn/serene/pull/29)) +- Copy button for code blocks ([#30](https://github.com/isunjn/serene/pull/30)) +- Support light/dark switch for code blocks ([#33](https://github.com/isunjn/serene/pull/33)) +- Support tags for project page +- Back-to-top button +- A shortcode for code block with file name: `codeblock` ([#39](https://github.com/isunjn/serene/pull/39)) + +### Fix: + +- Update theme toggle icon on page load [@mrtnvgr](https://github.com/mrtnvgr) ([#25](https://github.com/isunjn/serene/pull/25)) +- Layout shift problem on post page ([#27](https://github.com/isunjn/serene/pull/27)) + +## [1.1.1] - 2023-08-09 + +- Allow no tags in front matter [@mrtnvgr](https://github.com/mrtnvgr) +- Fix figcaption width issue + +## [1.1.0] - 2023-05-27 + +- Fix theme auto-toggle logic +- A few ui tweaks + +## [1.0.0] - 2023-05-24 + +> **Warning** +> The 1.0.0 version contains many breaking changes. +> If you came from a previous version and want to upgrade, I suggest you start all over again. + +### Breaking + +- `config.toml` restructured, config items are renamed +- All analytics configs removed, use `_head_extend.html` instead +- All comment-support configs removed, replace with [giscus](https://giscus.app) +- Icons now using svg files +- Callout renamed: `info -> note`, `caution -> warning`, `warning -> alert` +- Callout removed: `good`, `bad`, `happy`, `unhappy`, `check`, `wrong`, `flag`, `star` +- `cc_license` removed +- Reading-progress-bar removed +- Back-to-top button removed +- Many other tweaks + +## [0.2.0] - 2022-02-16 + +### Add: +- KaTeX support +- Mermaid support + +### Fix: +- Style issue of table-of-contents +- A few non-critical bugs + +## [0.1.0] - 2022-01-14 + +First release 🎉 + +[5.2.1]: https://github.com/isunjn/serene/compare/v5.2.0...v5.2.1 +[5.2.0]: https://github.com/isunjn/serene/compare/v5.1.0...v5.2.0 +[5.1.0]: https://github.com/isunjn/serene/compare/v5.0.1...v5.1.0 +[5.0.1]: https://github.com/isunjn/serene/compare/v5.0.0...v5.0.1 +[5.0.0]: https://github.com/isunjn/serene/compare/v4.5.0...v5.0.0 +[4.5.0]: https://github.com/isunjn/serene/compare/v4.4.0...v4.5.0 +[4.4.0]: https://github.com/isunjn/serene/compare/v4.3.0...v4.4.0 +[4.3.0]: https://github.com/isunjn/serene/compare/v4.2.0...v4.3.0 +[4.2.0]: https://github.com/isunjn/serene/compare/v4.1.0...v4.2.0 +[4.1.0]: https://github.com/isunjn/serene/compare/v4.0.0...v4.1.0 +[4.0.0]: https://github.com/isunjn/serene/compare/v3.4.0...v4.0.0 +[3.4.0]: https://github.com/isunjn/serene/compare/v3.3.1...v3.4.0 +[3.3.1]: https://github.com/isunjn/serene/compare/v3.3.0...v3.3.1 +[3.3.0]: https://github.com/isunjn/serene/compare/v3.2.0...v3.3.0 +[3.2.0]: https://github.com/isunjn/serene/compare/v3.1.0...v3.2.0 +[3.1.0]: https://github.com/isunjn/serene/compare/v3.0.0...v3.1.0 +[3.0.0]: https://github.com/isunjn/serene/compare/v2.3.0...v3.0.0 +[2.3.0]: https://github.com/isunjn/serene/compare/v2.2.1...v2.3.0 +[2.2.1]: https://github.com/isunjn/serene/compare/v2.2.0...v2.2.1 +[2.2.0]: https://github.com/isunjn/serene/compare/v2.1.2...v2.2.0 +[2.1.2]: https://github.com/isunjn/serene/compare/v2.1.1...v2.1.2 +[2.1.1]: https://github.com/isunjn/serene/compare/v2.0.1...v2.1.1 +[2.0.1]: https://github.com/isunjn/serene/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/isunjn/serene/compare/v1.2.0...v2.0.0 +[1.2.0]: https://github.com/isunjn/serene/compare/v1.1.1...v1.2.0 +[1.1.1]: https://github.com/isunjn/serene/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/isunjn/serene/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/isunjn/serene/compare/v0.2.0...v1.0.0 +[0.2.0]: https://github.com/isunjn/serene/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/isunjn/serene/releases/tag/v0.1.0 diff --git a/src/themes/serene/LICENSE b/src/themes/serene/LICENSE new file mode 100644 index 0000000..23f481c --- /dev/null +++ b/src/themes/serene/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-present isunjn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/themes/serene/README.md b/src/themes/serene/README.md new file mode 100644 index 0000000..4fad7a7 --- /dev/null +++ b/src/themes/serene/README.md @@ -0,0 +1,10 @@ +

+ A minimal blog theme for zola, well crafted +

+ +

+ Demo · + Usage +

+ +Screenshot diff --git a/src/themes/serene/USAGE.md b/src/themes/serene/USAGE.md new file mode 100644 index 0000000..1b91d88 --- /dev/null +++ b/src/themes/serene/USAGE.md @@ -0,0 +1,536 @@ +This is the detailed guide on how to use zola-theme-serene. You should also check zola's [documentation](https://www.getzola.org/documentation/getting-started/overview/). + +## Installation + +Create a zola site and add serene theme (assuming your site is called `myblog`): + +```sh +zola init myblog +cd myblog +git init +git submodule add -b latest https://github.com/isunjn/serene.git themes/serene +``` + +Copy the content of `myblog/themes/serene/config.example.toml` to `myblog/config.toml`. + +## Sections and Pages + +There is a `sections` config option in your `config.toml`, which enumerates the sections your site has. You should have at least one `blog` section. + +The name and path can be changed, be noticed that if you changed the blog section path (e.g. from `/posts` to `/blog`), then you should also change `blog_section_path` option. + +For home page, create `myblog/content/_index.md`: + +``` ++++ +template = 'home.html' + +[extra] +lang = 'en' + +# Show footer in home page +footer = false + +# If you don't want to display id/bio/avatar, simply comment out that line +name = "Serene Demo" +id = "serene" +bio = "programmer, he/him, we are young and life is fun" +avatar = "img/avatar.webp" +links = [ + { name = "GitHub", icon = "github", url = "https://github.com/" }, + { name = "Twitter", icon = "twitter", url = "https://twitter.com/" }, + { name = "Email", icon = "email", url = "mailto:" }, +] + +# Show a few recent posts in home page +recent = false +recent_max = 15 +recent_more_text = "more »" +date_format = "%b %-d, %Y" ++++ + +Hi, I'm ... +``` + +For blog section, create `myblog/content/posts/_index.md`: + +``` ++++ +title = "My Blog" +description = "My blog site." +sort_by = "date" +template = "blog.html" +page_template = "post.html" +insert_anchor_links = "right" +generate_feeds = true + +[extra] +lang = "en" + +title = "Posts" +subtitle = "I write about ...." + +date_format = "%b %-d, %Y" + +categorized = false # posts can be categorized +back_to_top = true # show back-to-top button +toc = true # show table-of-contents +comment = false # enable comment +copy = true # show copy button in code block + +outdate_alert = false +outdate_alert_days = 12 +outdate_alert_text_before = "This article was last updated " +outdate_alert_text_after = " days ago and may be out of date." ++++ +``` + +Blog section is defined by `blog.html` and `post.html`. Serene also has a special template called `prose.html`, it applies the same styles of blog post page. You can use it as a section template for a custom section page, for example if you want a separate `about` page, you can add a `{ name = "about", path = "/about", is_external = false }` to the `sections` and create a `myblog/content/about/_index.md`: + +``` ++++ +title = "About me" +description = "About page of ..." +template = "prose.html" +insert_anchor_links = "none" + +[extra] +lang = 'en' + +title = "Posts" +subtitle = "I write about ...." + +math = false +mermaid = false +copy = false +comment = false +reaction = false ++++ + +Hi, My name is .... +``` + +The default date format is "%b %-d, %Y", e.g. "Dec 13, 2025", check [this page](https://docs.rs/chrono/0.4.40/chrono/format/strftime/index.html) if you want to customize it, for example change to "%Y-%-m-%-d", e.g. "2025-2-13". + +Now the myblog directory may looks like this: + +``` +├── config.toml +├── content/ +│ ├── posts/ +│ │ └── _index.md +│ ├── about/ +│ │ └── _index.md +│ └── _index.md +├── sass/ +├── static/ +├── templates/ +└── themes/ + └── serene/ +``` + +## Favicon + +Create a new directory `img` under `myblog/static`, put favicon related files here, you can use tools like [favicon.io](https://favicon.io/favicon-converter/) to generate those files. If you want to display avatar in home page, also put your avatar picture file `avatar.webp` here, webp format is recommended. + +``` +... +├── static/ +│ └── img/ +│ ├── favicon-16x16.png +│ ├── favicon-32x32.png +│ ├── apple-touch-icon.png +│ └── avatar.webp +... +``` + +## Icon + +The default icons are placed in `myblog/themes/serene/static/icon`, the `icon` value in `links` of home section is the file name of the svg file. + +To customize, find the svg file you want, modify (in case you don't kown, a svg file is just a plian text file) its width and height to `18`, and the color to `currentColor`: + +`... width="18" height="18" ... fill="currentColor" ...` + +and then put it in `myblog/static/icon`, file in this folder with the same name will override the default one. + +The default icons mostly came from [Remix Icon](https://remixicon.com/). + +## Theme + +By default there is theme toggle button to switch between light and dark mode, you can set `force_theme` in `config.toml` to force a specific mode only. + +## Code Highlighting + +Copy `myblog/themes/serene/highlight_themes` directory to `myblog/highlight_themes`. + +By default serene use different highlight themes for light/dark mode, configured by `highlight_theme`, `extra_syntaxes_and_themes` and `highlight_themes_css`. The default highlight theme `serene-light` `serene-dark` is a modified version of [Tomorrow](https://github.com/ChrisKempson/Tomorrow-Theme) theme. + +If you set `highlight_theme` in `config.toml` to one of zola's [built-in highlight themes](https://www.getzola.org/documentation/getting-started/configuration/#syntax-highlighting), you will get that theme used in both light and dark mode. + +If you want a different theme, find the `.tmTheme` TextMate file of your theme, put it in `myblog/static/highlight_themes`, and then modify the `theme` value of `highlight_themes_css` to that file's name. This will generate a `hl-light.css` and a `hl-dark.css` file in `myblog/static/`, you may have to delete them first before you change the `theme` value, so zola can re-generate. You can find some TextMate themes on [this website](https://tmtheme-editor.glitch.me/). + +## RSS + +Zola's default feed file is located in the root directory of the site, set `generate_feeds = true` in `config.toml`, `feed_filenames` can be set to `["atom.xml"]` or `["rss.xml"] ` , corresponding to two different rss file standards, you should also set `generate_feeds = false` in `myblog/content/posts/_index.md` + +The serene theme looks more like a personal website, the posts are in the `/posts` directory, you may want the feed file to be in the `/posts` directory instead of the root directory, this requires you to set `generate_feeds = false ` `feed_filenames = ["feed.xml"]` in `config.toml`, and set `generate_feeds = true` in `myblog/content/posts/_index.md`. + +`feed.xml` uses `title` and `description` from `myblog/content/posts/_index.md`, the other two use `config.toml`'s. + +## Analytics + +To add scripts for analytics tools (such as Google Analytics, Umami, etc.), you can create `myblog/templates/_head_extend.html`. The content of this file will be added to the html head of each page. + +## Custom CSS + +Copy `myblog/themes/serene/templates/_custom_css.html` to `myblog/templates/_custom_css.html`, variables in this file are used to control styles, such as the theme color `--primary-color`, modify them as you want. + +If you want to customize more, you need to copy that file under the `templates`, `static`, `sass` directory in the corresponding `themes/serene` to the same name directory of `myblog`, and modify it. Be careful not to directly modify the files under the serene directory, because these modifications may cause conflicts if the theme is updated. + +If you want to use a custom font, create a new `myblog/templates/_custom_font.html` and put the font link tags (for eample, from [google fonts](https://fonts.google.com/)) into it, and then modify `--main-font` or `--code-font` in `myblog/sass/templates/_custom_css.html`. For performance reason, you may want to self-host font files, but it's optional: + +1. Open [google-webfonts-helper](https://gwfh.mranftl.com) and choose your font. +2. Modify `Customize folder prefix` of step 3 to `/font/` and then copy the css. +3. Replace the content of `myblog/templates/_custom_font.html` with a `` tag, with the css you just copied. +4. Download step 4 font files and put them in `myblog/static/font/` folder. + +## Front Matter + +The content inside the two `+++` at the top of the post markdown file is called front matter, used to provide metadata and config options of that post. Supported options: + +```md ++++ +title = "" +description = "" +date = 2022-01-01 +updated = 2025-01-01 +draft = true + +[taxonomies] +categories = ["one"] +tags = ["one", "two", "three"] + +[extra] +lang = "en" +toc = true +comment = false +copy = true +outdate_alert = true +outdate_alert_days = 120 +math = false +mermaid = false +featured = false +reaction = false ++++ + +new post about something... +``` + +Some of these options can also be configured in `myblog/content/posts/_index.md`, as the default value for all posts. + +If you set `blog_categorized = true`, posts will be sorted alphabetically by default, you can manually set the order by adding a prefix `__[0-9]{2}__` in front of the category name, for example, `categories = ["__01__CatXXX"]` + +## Table of Contents + +Set `toc = true` to display of table-of-contents. + +## Math & Chart + +Set `math = true` to enable formula rendering with KaTeX. + +Set `mermaid = true` to enable chart rendering with Mermaid. + +## Featured Mark + +Set `featured = true` to display an asterisk(*) mark in front of the title. + +## Outdate Alert + +If one of your posts has strong timeliness, you can display an outdate alert after certain days. + +Set `outdate_alert` and `outdate_alert_days` to enable the alert. + +In `myblog/content/posts/_index.md`, options `outdate_alert_text_before` and `outdate_alert_text_after` are the text content of the alert. + +## Comment + +You can use [giscus](https://giscus.app) as the comment system. + +To enable it, you need to create `myblog/templates/_giscus_script.html` and put the script configured on the giscus website into it, then change the value of `data-theme` to `https:///giscus_light.css`, replace `` with you domain name, same as `base_url` in `config.toml`, if you set `force_theme` to `dark`, replace `giscus_light.css` with `giscus_dark.css`. + +Then set `comment = true` to enable comment. + +## Reaction + +This theme supports a feature called anonymous emoji reaction, visitors of you site can react to your post with emojis, without the need to log in or register. + +You need to setup a backend api endpoint to enable it. Your endpoint should handle both `GET` and `POST` request: + +- `GET` + + Request query: + + `slug`: the slug of the post + + Response: + + ```jsonc + { + "👍": [123, true], // emoji: [count, reacted] + "👀": [456, false] + } + ``` + +- `POST` + + Request body: + + ```json + { + "slug": "post-slug", + "target": "👍", + "reacted": true + } + ``` + + Response: + + ```json + { + "success": true + } + ``` + +For conivence, you can use one template repo to to setup your own endpoint: + +- [isunjn/reaction](https://github.com/isunjn/reaction): All you need is a [Cloudflare](https://cloudflare.com) account. The free tier is good enough for a low-traffic personal blog. + +- [mildronize/reaction](https://github.com/mildronize/reaction): Specific to [Azure](https://azure.microsoft.com/) platform. +- [sorokya/reaction](https://github.com/sorokya/reaction): A self-contained one, you can run it with docker. + +After you setup your endpoint, set `reaction_endpoint = ""` and `reaction = true` to enable it. + +Giscus also support a reaction feature, but it requires visitors to log in to GitHub, you can disable it in giscus's settings. + +## Codeblock + +Zola supports some [annotations for code blocks](https://www.getzola.org/documentation/content/syntax-highlighting/#annotations). + +## Shortcodes + +[Shortcodes](https://www.getzola.org/documentation/content/shortcodes/) are some special templates. + +- Use `figure` to add caption to the image: + + ```md + {{ figure(src="/path/to/img", alt="alt text", caption="caption text") }} + ``` + + Adding attribution information to an image is very common, you can directly use the `via` attribute, which will display a link named 'via' below the image: + + ```md + {{ figure(src="/path/to/img", alt="some alt text", via="https://example.com") }} + ``` + +- Use `quote` to display a special quote block, `cite` is optional: + + ```md + {% quote(cite="") %} + // content... + {% end %} + ``` + +- Use `detail` to add an expandable detail block, `default_open` is optional: + + ```md + {% detail(title="", default_open=false) %} + // content... + {% end %} + ``` + +- As you can see in [this page](https://serene-demo.pages.dev/posts/callouts) of the demo site, callouts are special blockquote blocks, just like [github's](https://github.com/orgs/community/discussions/16925). There are currently 5 types: `note` `tip` `important` `warning` `caution`. + + `title` is optional: + + ```md + {% note(title="Note") %} + note text + {% end %} + ``` + +- Use `mermaid` to add a mermaid chart: + + ```md + {% mermaid() %} + flowchart LR + A[Hard] -->|Text| B(Round) + B --> C{Decision} + C -->|One| D[Result 1] + C -->|Two| E[Result 2] + {% end %} + ``` + +## Collection + +This theme has several special shortcodes for creating a collection of items. These collections can be used to showcase various types of your list, such as projects, publications, blogroll, bookmarks, etc. Check [this page](http://serene-demo.pages.dev/collections) on demo site to see some examples. + +Currently, there are 7 types of collection item: + +- `card` + + ```toml + [[collection]] + type = "card" + title = "Title" + subtitle = "Subtitle" # optional + date = "Date" # optional + link = "https://example.com" # optional + icon = "https://example.com/image.png" # optional + content = "Content" + tags = ["tag1", "tag2"] # optional + featured = false # optional + ``` + +- `card_simple` + + ```toml + [[collection]] + type = "card_simple" + title = "Title" + date = "Date" # optional + link = "https://example.com" # optional + icon = "https://example.com/image.png" # optional + content = "Content" + featured = false # optional + ``` + +- `entry` + + ```toml + [[collection]] + type = "entry" + title = "Title" # optional + subtitle = "Subtitle" # optional + link = "https://example.com" # optional + icon = "https://example.com/image.png" # optional + ``` + +- `box` + + ```toml + [[collection]] + type = "box" + title = "Title" + subtitle = "Subtitle" # optional + link = "https://example.com" # optional + img = "https://example.com/image.png" # optional + ``` + +- `art` + + ```toml + [[collection]] + type = "art" + title = "Title" + subtitle = "Subtitle" # optional + link = "https://example.com" # optional + img = "https://example.com/image.png" + content = "Content" # optional + footer = "Footer" # optional + ``` + +- `art_simple` + + ```toml + [[collection]] + type = "art_simple" + title = "Title" + subtitle = "Subtitle" # optional + link = "https://example.com" # optional + img = "https://example.com/image.png" + ``` + + +List your items in a toml file and then use a `collection` shortcode to render them. + +For example, to create a "projects" section page: + +1. Create `myblog/content/projects/projects.toml`: + + ```toml + [[collection]] + type = "card" + title = "Tokio" + link = "https://example.com" + content = "Tokio is an asynchronous runtime for the Rust programming language. It provides the building blocks needed for writing network applications. It gives the flexibility to target a wide range of systems, from large servers with dozens of cores to small embedded devices." + tags = ["rust", "async", "runtime"] + + [[collection]] + type = "card" + title = "Kubernetes" + link = "https://example.com" + content = "Kubernetes, also known as K8s, is an open source system for managing containerized applications across multiple hosts. It provides basic mechanisms for the deployment, maintenance, and scaling of applications." + tags = ["k8s", "golang"] + + [[collection]] + type = "card" + title = "Next.js" + link = "https://example.com" + content = "Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations." + tags = ["typescript", "react", "frontend"] + + ``` + +2. Create `myblog/content/projects/_index.md`: + + ``` + +++ + title = "My projects" + description = "Projects page of ..." + template = "prose.html" + + [extra] + title = "Projects" + subtitle = "Some cool projects I made" + +++ + + {{ collection(file="projects.toml") }} + ``` + +3. Add projects section in `sections` of `config.toml` + + ```toml + sections = [ + # ... + { name = "projects", path = "/projects", is_external = false }, + ] + ``` + +## Build & Deploy + +Local preview: + +```sh +zola serve +``` + +Build the site: + +```sh +zola build +``` + +To deploy a static site, refer to zola's [documentation about deployment](https://www.getzola.org/documentation/deployment/overview/). + +## Update + +Check the [CHANGELOG.md](https://github.com/isunjn/serene/blob/main/CHANGELOG.md) on github for breaking changes before you update. + +If you copied some files from `myblog/themes/serene` to `myblog/` for customization, such as `_custom_css.html` or `main.scss`, then you should record what you have modified before you update, re-copy those files and re-apply your modification after updating. The `config.toml` should be re-copied too. + +You can watch (`watch > custom > releases > apply`) this project on github to be reminded of a new release. + +```sh +git submodule update --remote themes/serene +``` diff --git a/src/themes/serene/config.example.toml b/src/themes/serene/config.example.toml new file mode 100644 index 0000000..e35d6c4 --- /dev/null +++ b/src/themes/serene/config.example.toml @@ -0,0 +1,62 @@ +# serene v5.2.1 +# +# - docs: https://github.com/isunjn/serene/blob/latest/USAGE.md +# - check for updates: https://github.com/isunjn/serene/releases +# +#========================================================================================= + +base_url = "https://example.com" +title = "xxxx" +description = "xxxx xxxx xxxx" +default_language = "en" +theme = "serene" +output_dir = "public" +compile_sass = true +minify_html = false # Keep this false, as it may cause issues with some styles +build_search_index = false # Keep this false, search is temporarily unsupported +generate_feeds = false # Whether to generate a feed file in root, read docs for more info about rss feed +feed_filenames = ["feed.xml"] # "feed.xml" | "atom.xml" | "rss.xml", read docs for more info +taxonomies = [{ name = "tags" }, { name = "categories" }] + +[markdown] +highlight_code = true +highlight_theme = "css" +extra_syntaxes_and_themes = ["highlight_themes"] +highlight_themes_css = [ + { theme = "serene-light", filename = "hl-light.css" }, + { theme = "serene-dark", filename = "hl-dark.css" }, +] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= + +[extra] + +sections = [ + { name = "posts", path = "/posts", is_external = false }, + # { name = "tags", path = "/tags", is_external = false }, + # { name = "github", path = "https://github.com/", is_external = true }, +] +blog_section_path = "/posts" + +back_link_text = "Back" # Text of the back button +force_theme = false # false | "light" | "dark" + +footer_copyright = "© 2025 " +footer_credits = true # Whether to show "Built with zola and serene" in footer + +not_found_error_text = "404 Not Found" +not_found_recover_text = "« back to home »" + +reaction = false # Whether to enable anonymous emoji reactions (Note: You need to set up a working api endpoint to enable this feature) +reaction_align = "right" # "left" | "center" | "right" +reaction_endpoint = "https://example.com/api/reaction" diff --git a/src/themes/serene/highlight_themes/serene-dark.tmTheme b/src/themes/serene/highlight_themes/serene-dark.tmTheme new file mode 100644 index 0000000..f4fd49c --- /dev/null +++ b/src/themes/serene/highlight_themes/serene-dark.tmTheme @@ -0,0 +1,283 @@ + + + + + + + + + comment + http://chriskempson.com + name + Tomorrow Night + settings + + + settings + + caret + #AEAFAD + foreground + #C5C8C6 + invisibles + #4B4E55 + lineHighlight + #282A2E + selection + #373B41 + + + + name + Comment + scope + comment, string.quoted.double.block.python + settings + + foreground + #999999 + + + + name + Foreground + scope + keyword.operator.class, constant.other, source.php.embedded.line + settings + + fontStyle + + foreground + #CED1CF + + + + name + Variable, String Link, Regular Expression, Tag Name + scope + variable, support.other.variable, string.other.link, string.regexp, entity.name.tag, entity.other.attribute-name, meta.tag, declaration.tag + settings + + foreground + #A67878 + + + + name + Number, Constant, Function Argument, Tag Attribute, Embedded + scope + constant.numeric, constant.language, support.constant, constant.character, variable.parameter, punctuation.section.embedded, keyword.other.unit + settings + + fontStyle + + foreground + #E08355 + + + + name + Class, Support + scope + type, +entity.name.class, entity.name.type.class, support.type, support.class + settings + + fontStyle + + foreground + #83aaa5 + + + + name + String, Symbols, Inherited Class, Markup Heading + scope + string, constant.other.symbol, entity.other.inherited-class, markup.heading + settings + + fontStyle + + foreground + #85AD74 + + + + name + Operator, Misc + scope + keyword.operator, constant.other.color + settings + + foreground + #83aaa5 + + + + name + Function, Special Method, Block Level + scope + entity.name.function, meta.function-call, support.function, keyword.other.special-method, meta.block-level + settings + + fontStyle + + foreground + #81A2BE + + + + name + Keyword, Storage + scope + keyword, storage, storage.type, entity.name.tag.css + settings + + fontStyle + + foreground + #B294BB + + + + name + Invalid + scope + invalid + settings + + background + #DF5F5F + fontStyle + + foreground + #CED2CF + + + + name + Separator + scope + meta.separator + settings + + background + #82A3BF + foreground + #CED2CF + + + + name + Deprecated + scope + invalid.deprecated + settings + + background + #B798BF + fontStyle + + foreground + #CED2CF + + + + name + Diff foreground + scope + markup.inserted.diff, markup.deleted.diff, meta.diff.header.to-file, meta.diff.header.from-file + settings + + foreground + #FFFFFF + + + + name + Diff insertion + scope + markup.inserted.diff, meta.diff.header.to-file + settings + + foreground + #4baf5c + + + + name + Diff deletion + scope + markup.deleted.diff, meta.diff.header.from-file + settings + + foreground + #D46565 + + + + name + Diff header + scope + meta.diff.header.from-file, meta.diff.header.to-file + settings + + foreground + #4271ae + + + + name + Diff range + scope + meta.diff.range + settings + + fontStyle + italic + foreground + #3e999f + + + + name + diff.deleted + scope + markup.deleted + settings + + foreground + #F92672 + + + + name + diff.inserted + scope + markup.inserted + settings + + foreground + #A6E22E + + + + name + diff.changed + scope + markup.changed + settings + + foreground + #967EFB + + + + uuid + F96223EB-1A60-4617-92F3-D24D4F13DB09 + colorSpaceName + sRGB + semanticClass + theme.dark.tomorrow_night + + \ No newline at end of file diff --git a/src/themes/serene/highlight_themes/serene-light.tmTheme b/src/themes/serene/highlight_themes/serene-light.tmTheme new file mode 100644 index 0000000..c4b8d1b --- /dev/null +++ b/src/themes/serene/highlight_themes/serene-light.tmTheme @@ -0,0 +1,239 @@ + + + + + + + + + comment + http://chriskempson.com + name + Tomorrow + settings + + + settings + + caret + #AEAFAD + foreground + #4D4D4C + invisibles + #D1D1D1 + lineHighlight + #EFEFEF + selection + #D6D6D6 + + + + name + Comment + scope + comment, string.quoted.double.block.python + settings + + foreground + #999999 + + + + name + Foreground + scope + keyword.operator.class, constant.other, source.php.embedded.line + settings + + fontStyle + + foreground + #666969 + + + + name + Variable, String Link, Regular Expression, Tag Name + scope + variable, support.other.variable, string.other.link, string.regexp, entity.name.tag, entity.other.attribute-name, meta.tag, declaration.tag + settings + + foreground + #A67878 + + + + name + Number, Constant, Function Argument, Tag Attribute, Embedded + scope + constant.numeric, constant.language, support.constant, constant.character, variable.parameter, punctuation.section.embedded, keyword.other.unit + settings + + fontStyle + + foreground + #E08355 + + + + name + Type, Class, Support + scope + type, +entity.name.class, entity.name.type.class, support.type, support.class + settings + + fontStyle + + foreground + #568A8F + + + + name + String, Symbols, Inherited Class, Markup Heading + scope + string, constant.other.symbol, entity.other.inherited-class, markup.heading + settings + + fontStyle + + foreground + #85AD74 + + + + name + Operator, Misc + scope + keyword.operator, constant.other.color + settings + + foreground + #568A8F + + + + name + Function, Special Method, Block Level + scope + entity.name.function, meta.function-call, support.function, keyword.other.special-method, meta.block-level + settings + + fontStyle + + foreground + #4271AE + + + + name + Keyword, Storage + scope + keyword, storage, storage.type + settings + + fontStyle + + foreground + #8959A8 + + + + name + Invalid + scope + invalid + settings + + background + #DF5F5F + fontStyle + + foreground + #FFFFFF + + + + name + Separator + scope + meta.separator + settings + + background + #4271AE + foreground + #FFFFFF + + + + name + Deprecated + scope + invalid.deprecated + settings + + background + #8959A8 + fontStyle + + foreground + #FFFFFF + + + + name + Diff insertion + scope + markup.inserted.diff, meta.diff.header.to-file + settings + + foreground + #229545 + + + + name + Diff deletion + scope + markup.deleted.diff, meta.diff.header.from-file + settings + + foreground + #C8282966 + + + + name + Diff header + scope + meta.diff.header.from-file, meta.diff.header.to-file + settings + + foreground + #4271ae + + + + name + Diff range + scope + meta.diff.range + settings + + fontStyle + italic + foreground + #3e999f + + + + uuid + 82CCD69C-F1B1-4529-B39E-780F91F07604 + colorSpaceName + sRGB + semanticClass + theme.light.tomorrow + + \ No newline at end of file diff --git a/src/themes/serene/sass/giscus_dark.scss b/src/themes/serene/sass/giscus_dark.scss new file mode 100644 index 0000000..ea0572f --- /dev/null +++ b/src/themes/serene/sass/giscus_dark.scss @@ -0,0 +1,100 @@ +main { + --primary-default: 88, 113, 162; + --bg-default: 22, 22, 24; + --color-prettylights-syntax-comment: #8b949e; + --color-prettylights-syntax-constant: #79c0ff; + --color-prettylights-syntax-entity: #d2a8ff; + --color-prettylights-syntax-storage-modifier-import: #c9d1d9; + --color-prettylights-syntax-entity-tag: #7ee787; + --color-prettylights-syntax-keyword: #ff7b72; + --color-prettylights-syntax-string: #a5d6ff; + --color-prettylights-syntax-variable: #ffa657; + --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; + --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; + --color-prettylights-syntax-invalid-illegal-bg: #8e1519; + --color-prettylights-syntax-carriage-return-text: #f0f6fc; + --color-prettylights-syntax-carriage-return-bg: #b62324; + --color-prettylights-syntax-string-regexp: #7ee787; + --color-prettylights-syntax-markup-list: #f2cc60; + --color-prettylights-syntax-markup-heading: #1f6feb; + --color-prettylights-syntax-markup-italic: #c9d1d9; + --color-prettylights-syntax-markup-bold: #c9d1d9; + --color-prettylights-syntax-markup-deleted-text: #ffdcd7; + --color-prettylights-syntax-markup-deleted-bg: #67060c; + --color-prettylights-syntax-markup-inserted-text: #aff5b4; + --color-prettylights-syntax-markup-inserted-bg: #033a16; + --color-prettylights-syntax-markup-changed-text: #ffdfb6; + --color-prettylights-syntax-markup-changed-bg: #5a1e02; + --color-prettylights-syntax-markup-ignored-text: #c9d1d9; + --color-prettylights-syntax-markup-ignored-bg: #1158c7; + --color-prettylights-syntax-meta-diff-range: #d2a8ff; + --color-prettylights-syntax-brackethighlighter-angle: #8b949e; + --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; + --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; + --color-btn-text: rgb(235 235 245 / 86%); + --color-btn-bg: rgba(var(--bg-default), 1); + --color-btn-border: rgba(var(--bg-default), 1); + --color-btn-shadow: 0 1px 0 rgba(var(--bg-default), 1); + --color-btn-inset-shadow: inset 0 1px 0 rgba(var(--bg-default), 1); + --color-btn-hover-bg: rgba(var(--bg-default), 0.5); + --color-btn-hover-border: rgba(var(--bg-default), 0.5); + --color-btn-active-bg: rgba(var(--primary-default), 0.2); + --color-btn-active-border: rgba(var(--primary-default), 1); + --color-btn-selected-bg: rgba(var(--primary-default), 0.15); + --color-btn-primary-text: rgb(255 255 255 / 100%); + --color-btn-primary-bg: rgba(var(--primary-default), 0.45); + --color-btn-primary-border: rgba(var(--primary-default), 0.5); + --color-btn-primary-shadow: 0 1px 0 rgb(27 31 36 / 10%); + --color-btn-primary-inset-shadow: inset 0 1px 0 hsl(0deg 0% 100% / 3%); + --color-btn-primary-hover-bg: rgba(var(--primary-default), 0.53); + --color-btn-primary-hover-border: rgba(var(--primary-default), 0.75); + --color-btn-primary-selected-bg: rgba(var(--primary-default), 0.45); + --color-btn-primary-selected-shadow: inset 0 1px 0 rgb(0 45 17 / 20%); + --color-btn-primary-disabled-text: rgb(255 255 255 / 80%); + --color-btn-primary-disabled-bg: rgba(var(--primary-default), 0.5); + --color-btn-primary-disabled-border: rgba(var(--primary-default), 0.5); + --color-action-list-item-default-hover-bg: rgb(177 186 196 / 12%); + --color-segmented-control-bg: rgb(110 118 129 / 10%); + --color-segmented-control-button-bg: #0d1117; + --color-segmented-control-button-selected-border: rgba(var(--bg-default), 0.85); + --color-fg-default: rgb(235 235 245 / 86%); + --color-fg-muted: rgb(235 235 245 / 60%); + --color-fg-subtle: rgb(235 235 245 / 50%); + --color-canvas-default: rgb(30 30 32 / 100%); + --color-canvas-overlay: rgb(30 30 32 / 100%); + --color-canvas-inset: rgba(var(--bg-default), 0.85); + --color-canvas-subtle: rgba(var(--bg-default), 1); + --color-border-default: rgba(var(--bg-default), 0.85); + --color-border-muted: rgb(175 184 193 / 20%); + --color-neutral-muted: rgb(175 184 193 / 20%); + --color-accent-fg: rgba(var(--primary-default), 0.85); + --color-accent-emphasis: rgba(var(--primary-default), 0.95); + --color-accent-muted: rgba(var(--primary-default), 0.4); + --color-accent-subtle: rgba(var(--primary-default), 0.1); + --color-success-fg: #3fb950; + --color-attention-fg: #d29922; + --color-attention-muted: rgb(187 128 9 / 40%); + --color-attention-subtle: rgb(187 128 9 / 15%); + --color-danger-fg: #f85149; + --color-danger-muted: rgb(248 81 73 / 40%); + --color-danger-subtle: rgb(248 81 73 / 10%); + --color-primer-shadow-inset: 0 1px 0 rgba(var(--bg-default), 1), inset 0 1px 0 rgba(var(--bg-default), 1); + --color-scale-gray-7: rgb(22 22 24 / 100%); + --color-scale-blue-8: rgb(16 185 129 / 15%); + + /*! Extensions from @primer/css/alerts/flash.scss */ + --color-social-reaction-bg-hover: var(--color-scale-gray-7); + --color-social-reaction-bg-reacted-hover: var(--color-scale-blue-8); +} + +main .pagination-loader-container { + background-image: url("https://github.com/images/modules/pulls/progressive-disclosure-line-dark.svg"); +} + +main .gsc-loading-image { + background-image: url("https://github.githubassets.com/images/mona-loading-dark.gif"); +} + +.gsc-comment-box-buttons a { + border-radius: 0.25rem !important; +} diff --git a/src/themes/serene/sass/giscus_light.scss b/src/themes/serene/sass/giscus_light.scss new file mode 100644 index 0000000..9433d60 --- /dev/null +++ b/src/themes/serene/sass/giscus_light.scss @@ -0,0 +1,104 @@ +main { + --primary-default: 88, 113, 162; + --bg-default: 246, 246, 247; + --color-prettylights-syntax-comment: #6e7781; + --color-prettylights-syntax-constant: #0550ae; + --color-prettylights-syntax-entity: #8250df; + --color-prettylights-syntax-storage-modifier-import: #24292f; + --color-prettylights-syntax-entity-tag: #116329; + --color-prettylights-syntax-keyword: #cf222e; + --color-prettylights-syntax-string: #0a3069; + --color-prettylights-syntax-variable: #953800; + --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; + --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; + --color-prettylights-syntax-invalid-illegal-bg: #82071e; + --color-prettylights-syntax-carriage-return-text: #f6f8fa; + --color-prettylights-syntax-carriage-return-bg: #cf222e; + --color-prettylights-syntax-string-regexp: #116329; + --color-prettylights-syntax-markup-list: #3b2300; + --color-prettylights-syntax-markup-heading: #0550ae; + --color-prettylights-syntax-markup-italic: #24292f; + --color-prettylights-syntax-markup-bold: #24292f; + --color-prettylights-syntax-markup-deleted-text: #82071e; + --color-prettylights-syntax-markup-deleted-bg: #ffebe9; + --color-prettylights-syntax-markup-inserted-text: #116329; + --color-prettylights-syntax-markup-inserted-bg: #dafbe1; + --color-prettylights-syntax-markup-changed-text: #953800; + --color-prettylights-syntax-markup-changed-bg: #ffd8b5; + --color-prettylights-syntax-markup-ignored-text: #eaeef2; + --color-prettylights-syntax-markup-ignored-bg: #0550ae; + --color-prettylights-syntax-meta-diff-range: #8250df; + --color-prettylights-syntax-brackethighlighter-angle: #57606a; + --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; + --color-prettylights-syntax-constant-other-reference-link: #0a3069; + --color-btn-text: #24292f; + --color-btn-bg: rgba(var(--bg-default), 1); + --color-btn-border: rgba(var(--bg-default), 1); + --color-btn-shadow: 0 1px 0 rgba(var(--bg-default), 1); + --color-btn-inset-shadow: inset 0 1px 0 rgba(var(--bg-default), 1); + --color-btn-hover-bg: rgba(var(--bg-default), 0.5); + --color-btn-hover-border: rgba(var(--bg-default), 0.5); + --color-btn-active-bg: rgba(var(--primary-default), 0.2); + --color-btn-active-border: rgba(var(--primary-default), 1); + --color-btn-selected-bg: rgba(var(--primary-default), 0.15); + --color-btn-primary-text: rgb(255 255 255 / 100%); + --color-btn-primary-bg: rgba(var(--primary-default), 1); + --color-btn-primary-border: rgba(var(--primary-default), 1); + --color-btn-primary-shadow: 0 1px 0 rgb(31 35 40 / 10%); + --color-btn-primary-inset-shadow: inset 0 1px 0 rgb(255 255 255 / 3%); + --color-btn-primary-hover-bg: rgba(var(--primary-default), 0.9); + --color-btn-primary-hover-border: rgba(var(--primary-default), 0.75); + --color-btn-primary-selected-bg: rgba(var(--primary-default), 1); + --color-btn-primary-selected-shadow: inset 0 1px 0 rgb(0 45 17 / 20%); + --color-btn-primary-disabled-text: rgb(255 255 255 / 80%); + --color-btn-primary-disabled-bg: rgba(var(--primary-default), 0.5); + --color-btn-primary-disabled-border: rgba(var(--primary-default), 0.5); + --color-action-list-item-default-hover-bg: rgb(208 215 222 / 32%); + --color-segmented-control-bg: #eaeef2; + --color-segmented-control-button-bg: #fff; + --color-segmented-control-button-selected-border: rgba(var(--bg-default), 0.85); + --color-fg-default: rgb(60 60 67); + --color-fg-muted: rgb(60 60 67 / 75%); + --color-fg-subtle: rgb(60 60 67 / 33%); + --color-canvas-default: rgb(255 255 255); + --color-canvas-overlay: rgb(255 255 255); + --color-canvas-inset: rgba(var(--bg-default), 0.85); + --color-canvas-subtle: rgba(var(--bg-default), 1); + --color-border-default: rgba(var(--bg-default), 0.85); + --color-border-muted: rgb(175 184 193 / 20%); + --color-neutral-muted: rgb(175 184 193 / 20%); + --color-accent-fg: rgba(var(--primary-default), 0.85); + --color-accent-emphasis: rgba(var(--primary-default), 0.95); + --color-accent-muted: rgba(var(--primary-default), 0.4); + --color-accent-subtle: rgba(var(--primary-default), 0.1); + --color-success-fg: #1a7f37; + --color-attention-fg: #9a6700; + --color-attention-muted: rgb(212 167 44 / 40%); + --color-attention-subtle: #fff8c5; + --color-danger-fg: #d1242f; + --color-danger-muted: rgb(255 129 130 / 40%); + --color-danger-subtle: #ffebe9; + --color-primer-shadow-inset: 0 1px 0 rgba(var(--bg-default), 1), inset 0 1px 0 rgba(var(--bg-default), 1); + --color-scale-gray-1: rgb(234 238 242 / 100%); + --color-scale-blue-1: rgb(16 185 129 / 15%); + + /*! Extensions from @primer/css/alerts/flash.scss */ + --color-social-reaction-bg-hover: var(--color-scale-gray-1); + --color-social-reaction-bg-reacted-hover: var(--color-scale-blue-1); +} + +main .pagination-loader-container { + background-image: url("https://github.com/images/modules/pulls/progressive-disclosure-line.svg"); +} + +main .gsc-loading-image { + background-image: url("https://github.githubassets.com/images/mona-loading-default.gif"); +} + +.gsc-comment:not(.gsc-reply-box) .gsc-replies { + border-radius: unset; +} + +.gsc-comment-box-buttons a { + border-radius: 0.25rem !important; +} diff --git a/src/themes/serene/sass/main.scss b/src/themes/serene/sass/main.scss new file mode 100644 index 0000000..f5628a2 --- /dev/null +++ b/src/themes/serene/sass/main.scss @@ -0,0 +1,1839 @@ +/* base + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + font-family: var(--main-font); + background-color: var(--bg-color); + color: var(--text-color); + font-size: var(--font-size); + line-height: var(--line-height); + overflow-y: scroll; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +button:focus-visible, +a:focus-visible { + outline: var(--primary-color) solid 3px; +} + +::selection { + background-color: var(--primary-color); + color: var(--bg-color); +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: transparent; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--text-decoration-color); + background-clip: padding-box; + border-radius: 4px; + border: 2px solid transparent; +} +@supports not selector(::-webkit-scrollbar) { + * { + scrollbar-width: thin; + scrollbar-color: var(--text-decoration-color) transparent; + } + body { + overflow-y: auto; + } +} + +/* prose + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.prose { + h1 { + font-size: 1.15em; + font-weight: bolder; + margin: 1.5em 0 0.75em; + } + + h2 { + font-size: 1.1em; + margin: 3em 0 1.5em; + } + + h3, h4, h5, h6 { + font-size: 1em; + margin: 2.5em 0 1.5em; + } + + .zola-anchor { + visibility: hidden; + margin-left: 0.75em; + border: none; + + &::after { + content: '#'; + } + } + + h1, h2, h3, h4, h5, h6 { + width: fit-content; + &:hover a.zola-anchor { + visibility: visible !important; + } + } + + p { + font-size: 1em; + line-height: inherit; + word-wrap: break-word; + margin: 1.5em 0; + } + + a { + border-bottom: 1.5px solid var(--primary-color); + color: var(--primary-color); + text-decoration: none; + overflow-wrap: anywhere; + + @media (hover: hover) { + &:hover { + opacity: 0.9; + } + } + } + + img { + max-width: 100%; + display: block; + margin: 0 auto; + border-radius: var(--img-border-radius); + } + + figure { + margin: 0 auto; + } + + figcaption { + width: 100%; + text-align: center; + margin: 1em auto 2em; + color: var(--text-pale-color); + font-size: 0.9em; + } + + blockquote { + border-left: 1.5px var(--text-decoration-color) solid; + padding-left: 16px; + margin: 1em 0; + color: var(--text-pale-color); + + p { + margin: 1em 0; + } + } + + ol, + ul { + padding-left: 1.5em; + } + + li { + margin: 1em 0; + p { + margin: 1em 0; + } + } + + li::marker { + color: var(--primary-color); + } + + hr { + border: none; + background-color: var(--text-decoration-color); + opacity: 0.25; + height: 2px; + margin: 3em 0; + } + + table { + width: 100%; + border-spacing: 0; + border-collapse: collapse; + margin: 1.5em 0; + } + + th, + td { + line-height: 2; + text-align: center; + border: 1px solid var(--primary-color); + padding: 1px 10px; + } + + :not(pre) > code { + font-family: var(--code-font); + font-size: 0.85em; + padding: 1px 6px; + color: var(--primary-color); + background-color: var(--inline-code-bg-color); + border-radius: var(--inline-code-border-radius); + word-wrap: break-word; + } + + pre { + font-size: 0.8em; + margin: 1.25em 0; + padding: 12px 48px 12px 16px; + line-height: 1.5; + border: 1.5px solid var(--primary-color); + border-color: var(--block-code-border-color); + border-radius: var(--block-code-border-radius); + overflow: auto; + + code { + font-family: var(--code-font); + } + + &[data-linenos] { + padding-left: 0px; + } + + table { + width: 100%; + margin: 0; + border-collapse: collapse; + border: none; + th, td { + line-height: 1.5; + } + } + + + table tr td:first-of-type { + color: var(--text-decoration-color); + } + + table td { + padding: 0; + padding-right: 48px; + text-align: initial; + border: initial; + } + + table td:nth-of-type(1) { + text-align: right; + user-select: none; + padding-right: 1em; + mark::before { + left: -8px; + width: calc(100% + 1em + 8px); + } + } + + mark { + display: block; + color: inherit; + background-color: transparent; + position: relative; + overflow: visible; + + &::before { + pointer-events: none; + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: calc(100% + 48px + 48px); + background-color: var(--highlight-mark-color); + } + } + + &.mermaid { + padding-right: 16px; + border: none; + svg { + display: block; + margin: 0 auto; + } + } + } + + pre > code > mark::before { + width: calc(100% + 48px + 16px); + left: -16px; + } + + .codeblock { + margin: 1.5em 0; + position: relative; + overflow: auto; + + pre { + margin: 0; + } + pre[data-name] { + padding-top: calc(36px + 1em * var(--line-height)); + &::before { + content: attr(data-name); + display: block; + position: absolute; + left: 0px; + top: 0px; + padding: 12px 16px; + color: var(--text-pale-color); + width: 100%; + border-bottom: 1px solid var(--primary-pale-color); + } + } + + .copy { + display: none; + z-index: 9; + position: absolute; + right: 0.6em; + top: 0.75em; + width: 24px; + height: 24px; + padding: 2px; + cursor: pointer; + background: transparent; + border: none; + color: var(--text-pale-color); + + &.copied, &:hover { + display: block; + color: var(--primary-color); + } + } + + @media (hover: hover) { + &:hover .copy { + display: block; + } + } + } + + .footnote-definition { + position: relative; + padding-left: 1.5em; + font-size: 0.9em; + margin: 3em 0 1em; + + .footnote-definition { + margin: 1em 0; + } + + .footnote-definition-label { + position: absolute; + top: 0; + left: 0; + font-size: 1em; + line-height: inherit; + vertical-align: auto; + &::after { + content: "."; + } + } + + p { + margin: 1em 0; + } + + button.backlink { + border: none; + background: none; + color: var(--primary-color); + line-height: inherit; + &:hover { + cursor: pointer; + text-decoration: underline; + } + } + } + + .callout { + margin: 1.5em 0; + + .icon { + height: 1.75em; + display: flex; + align-items: center; + } + + p { + margin: 0; + + p { + margin: 1em 0; + } + } + + &.has-title { + padding-left: 1em; + .title { + display: flex; + align-items: center; + gap: 0.5em; + margin-bottom: 0.5em; + } + } + + &.no-title { + padding-left: 0; + border: none; + display: flex; + align-items: start; + gap: 0.75em; + .content { + max-width: calc(100% - 30px); + } + } + + &.note { + color: var(--callout-note-color); + border-color: var(--callout-note-color); + } + + &.tip { + color: var(--callout-tip-color); + border-color: var(--callout-tip-color); + } + + &.important { + color: var(--callout-important-color); + border-color: var(--callout-important-color); + } + + &.warning { + color: var(--callout-warning-color); + border-color: var(--callout-warning-color); + } + + &.caution { + color: var(--callout-caution-color); + border-color: var(--callout-caution-color); + } + } + + .quote { + border: none; + position: relative; + margin: 2em 0; + padding: 1.5em 0 0 1.25em; + color: var(--text-pale-color); + + .icon { + color: var(--text-decoration-color); + display: block !important; + position: absolute; + left: 0; + top: 0; + } + + .content > p:first-of-type { + margin-top: 0; + } + .content > p:last-of-type { + margin-bottom: 0; + } + + .from { + display: flex; + justify-content: end; + align-items: center; + gap: 0.5em; + p { + margin: 0; + } + } + } + + details { + border: 1.5px solid var(--detail-border-color); + border-radius: var(--detail-border-radius); + margin: 1em 0; + padding: 0.5em 1em; + -webkit-tap-highlight-color: transparent; + + summary span { + margin-left: 0.25em; + } + } + + .mermaid { + background: #fff; + } +} + +body.dark .prose { + img { + filter: brightness(var(--dark-mode-img-brightness)); + } + .mermaid { + filter: brightness(var(--dark-mode-chart-brightness)); + } +} + +/* prose pages + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.prose-page { + main { + min-height: 100vh; + min-height: 100dvh; + margin: 0 auto; + max-width: var(--main-max-width); + display: flex; + flex-direction: column; + justify-content: space-between; + } + + article { + padding: 0 15px; + } + + .giscus { + padding: 0 15px; + } +} + +/* collections + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.prose { + .collection-wrapper { + margin: 1.5em 0; + } + + .collection { + border: none; + margin: 0; + padding: 0; + color: var(--text-color); + img { + margin: 0; + } + a, button { + -webkit-tap-highlight-color: transparent; + } + } + + .collection.card { + margin: 3em 0; + position: relative; + + &.featured::before { + content: '*'; + position: absolute; + top: 0; + bottom: 0; + left: 0; + transform: translateX(-200%); + line-height: 2; + color: var(--primary-color); + } + @media (max-width: 768px) { + &.featured::before { + transform: translateX(-150%); + } + } + + .meta { + display: flex; + gap: 0.5em; + .icon-wrapper { + height: calc(1em * var(--line-height)); + display: flex; + align-items: center; + flex-shrink: 0; + } + .icon { + height: 16px; + width: 16px; + } + .title { + color: var(--primary-color); + } + a.title { + text-decoration: none; + border-bottom: 1.5px solid transparent; + &:hover { + opacity: unset; + border-color: var(--primary-color); + } + } + .date { + margin-left: auto; + flex-shrink: 0; + font-size: 0.9em; + color: var(--text-pale-color); + } + } + + .subtitle { + font-size: 0.9em; + color: var(--text-pale-color); + margin: 0.75em 0; + } + + .content { + font-size: 1em; + p { + margin: 0.75em 0; + } + } + + .tags { + font-size: 0.9em; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.75em 1em; + color: var(--text-pale-color); + + div { + span { + font-size: 0.9em; + margin-right: 4px; + } + } + } + } + + .collection.card-simple { + margin: 0.5em 0; + position: relative; + + &.featured::before { + content: '*'; + position: absolute; + top: 0; + bottom: 0; + left: 0; + transform: translateX(-200%); + line-height: 2; + color: var(--primary-color); + } + @media (max-width: 768px) { + &.featured::before { + transform: translateX(-150%); + } + } + + .meta { + display: flex; + align-items: start; + gap: 0.5em; + } + + .icon-wrapper { + height: calc(1em * var(--line-height)); + display: flex; + align-items: center; + flex-shrink: 0; + } + .icon { + height: 16px; + width: 16px; + } + .title { + flex-shrink: 0; + max-width: 100%; + color: var(--primary-color); + margin-right: 0.5em; + } + a.title { + text-decoration: none; + border-bottom: 1.5px solid transparent; + &:hover { + opacity: unset; + border-color: var(--primary-color); + } + } + .content, + .content-narrow { + p { + margin: 0; + + p { + margin-top: 0.5em; + } + } + } + .content-narrow { + display: none; + margin: 0.25em 0 2em; + } + @media (max-width: 425px) { + .content { + display: none; + } + .content-narrow { + display: block; + } + } + .date { + margin-left: auto; + flex-shrink: 0; + font-size: 0.9em; + line-height: calc(var(--line-height) * 1.111); + color: var(--text-pale-color); + } + + } + + .collection.entry { + display: inline-flex; + width: fit-content; + gap: 0.5em; + padding: 4px 0; + margin: 0.25em 1.5em 0.25em 0; + + .icon-wrapper { + flex-shrink: 0; + display: flex; + align-items: center; + height: calc(1em * var(--line-height)); + } + .icon { + width: 16px; + height: 16px; + } + + .text div { + display: inline; + } + .title:has(+.subtitle) { + margin-right: 0.5em; + } + .subtitle { + font-size: 0.9em; + color: var(--text-pale-color); + } + } + a.collection.entry { + -webkit-tap-highlight-color: transparent; + color: var(--text-color); + border-bottom: 1.5px solid transparent; + &:hover { + border-color: var(--primary-color); + opacity: unset; + cursor: pointer; + } + } + + .collection.box { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + border: 1px solid var(--primary-color); + overflow: hidden; + + img { + width: 48px; + height: 48px; + &.rotate { + transition: transform 0.15s; + transform: rotate(-20deg) translate(-2px, 12px); + } + } + .placehold { + height: 48px; + } + + .text { + min-height: 48px; + line-height: 1.5; + padding: 0 1em; + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + + .title { + font-size: 0.9em; + color: var(--text-color); + } + + .subtitle { + font-size: 0.8em; + color: var(--text-pale-color); + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; + } + } + + @media (hover: hover) { + &:hover { + opacity: unset; + img.rotate { + transform: rotate(0deg) translate(0px, 0px); + } + } + } + } + a.collection.box { + -webkit-tap-highlight-color: transparent; + } + .collection-box-wrapper { + max-width: 100%; + width: fit-content; + display: inline-flex; + align-items: center; + justify-content: space-between; + border: 0.5px solid transparent; + overflow: hidden; + margin-right: 0.5em; + margin-bottom: 0.8em; + @media (hover: hover) { + &:has(a):hover { + border-color: var(--primary-color); + } + } + @media (max-width: 425px) { + & { + display: flex; + width: 100%; + } + } + } + + .collection.art { + --art-h: 160px; + --art-w: 115px; + + margin: 2em 0; + display: flex; + gap: 1.5em; + + .img-wrapper { + flex-shrink: 0; + width: var(--art-w); + height: var(--art-h); + img { + height: 100%; + width: 100%; + object-fit: contain; + } + } + .text { + height: var(--art-h); + display: flex; + flex-direction: column; + gap: 3px; + } + .title { + color: var(--primary-color); + } + a.title { + width: fit-content; + border-color: transparent; + &:hover { + opacity: unset; + border-color: var(--primary-color); + } + } + .subtitle, + .footer { + font-size: 0.8em; + color: var(--text-pale-color); + } + .content { + font-size: 0.9em; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + } + .footer { + margin-top: auto; + } + + @media (max-width: 425px) { + & { + --art-h: 120px; + --art-w: 85px; + gap: 0.75em; + .text { + gap: 2px; + } + .subtitle, + .footer { + font-size: 0.7em; + } + .content { + font-size: 0.8em; + -webkit-line-clamp: 2; + } + } + } + + } + + + .collection.art-simple { + --art-h: 160px; + --art-w: 115px; + + max-width: calc(50vw - 30px); + margin: 1.5em 0.75em; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 1em; + + .img-wrapper { + flex-shrink: 0; + width: var(--art-w); + height: var(--art-h); + img { + height: 100%; + width: 100%; + object-fit: contain; + } + } + .text { + max-width: calc(var(--art-w) * 1.5); + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; + } + .title { + font-size: 0.9em; + color: var(--primary-color); + } + a.title { + width: fit-content; + border-color: transparent; + &:hover { + opacity: unset; + border-color: var(--primary-color); + } + } + .subtitle { + font-size: 0.8em; + color: var(--text-pale-color); + } + + @media (max-width: 425px) { + & { + --art-h: 120px; + --art-w: 85px; + width: calc(50vw - 50px); + } + } + + } +} + +/* layout post list ( home / blog / tag ) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.layout-list { + .category { + font-size: 0.9em; + margin: 1em 15px; + font-weight: 400; + } + + .post-list { + padding: 0 15px; + + &.categorized { + margin: 1em 0 3em 0; + } + } + + .post { + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: 0.5em; + padding: 4px 0px; + margin: 6px 0; + line-height: 1.2; + text-decoration: none; + color: var(--primary-color); + + border-bottom: 1.5px solid transparent; + @media (hover: hover) { + &:hover { + border-bottom-color: var(--primary-color); + } + } + + .date { + white-space: nowrap; + } + + position: relative; + &.featured::before { + content: '*'; + position: absolute; + top: 0; + bottom: 0; + left: 0; + transform: translateX(-200%); + line-height: 2; + height: 100%; + } + @media (max-width: 768px) { + &.featured::before { + transform: translateX(-150%); + } + } + } + + .read-more { + display: flex; + justify-content: end; + margin: 1.5em 15px 0px; + + a { + color: var(--primary-color); + text-decoration: none; + border-bottom: 1.5px solid var(--primary-color); + } + } +} + +/* theme toggle + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#theme-toggle { + .sun-icon { + display: none; + } +} + +body.dark #theme-toggle { + .sun-icon { + display: initial; + } + .moon-icon { + display: none; + } +} + +/* homepage + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.homepage { + #wrapper { + min-height: 100vh; + min-height: 100dvh; + max-width: var(--homepage-max-width); + margin: 0 auto; + display: flex; + flex-direction: column; + justify-content: center; + } + + main { + width: 100%; + min-height: 75vh; + min-height: 75dvh; + padding: 4em 0; + @media (max-width: 425px) { + & { + padding: 2em 0; + } + } + } + + #info { + padding: 0 15px; + display: flex; + align-items: center; + gap: 1em; + + img { + height: var(--avatar-size); + width: var(--avatar-size); + border-radius: 50%; + } + + #text { + display: flex; + flex-direction: column; + justify-content: space-around; + line-height: 1.5; + gap: 1em; + } + + #id { + margin-left: 0.75em; + color: var(--primary-color); + } + + #bio { + color: var(--text-pale-color); + } + } + + #links { + padding: 0 15px; + margin: 2em 0 2.5em; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1em; + + a, + button { + -webkit-tap-highlight-color: transparent; + text-decoration: none; + line-height: 0; + color: var(--text-color); + + &:hover { + color: var(--primary-color); + } + } + + #left { + display: flex; + flex-wrap: wrap; + gap: 1em; + + a { + border-bottom: 1.5px solid var(--primary-color); + line-height: 1.5; + } + } + + #right { + display: flex; + gap: 0.7em; + + button { + padding: 0; + border: none; + background-color: transparent; + cursor: pointer; + } + } + + @media (max-width: 425px) { + #left { + gap: 0.75em; + } + #right { + gap: 0.5em; + } + } + } + + #brief { + padding: 0 15px; + } +} + +/* header + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +header { + font-size: 0.8em; + padding: 0 15px; + a { + -webkit-tap-highlight-color: transparent; + display: inline-block; + margin-top: 1em; + padding: 1em 1em 1em 0; + color: var(--text-pale-color); + text-decoration: none; + &:hover { + color: var(--primary-color); + } + } +} + +.section-title { + padding: 0 15px; + h1 { + font-size: 1.15em; + margin: 1.5em 0 0.75em; + } + p { + margin: 0; + margin-bottom: 1em; + font-size: 0.9em; + color: var(--text-pale-color); + } +} + +/* rss mask + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +dialog#rss-mask { + margin: 0 auto; + padding: 0px; + border: none; + overflow: visible; + background: transparent; + + &::backdrop { + background-color: initial; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + } + + div { + pointer-events: none; + height: 100vh; + height: 100dvh; + padding: 2em 0; + width: fit-content; + display: flex; + justify-content: center; + align-items: end; + flex-wrap: wrap; + gap: 1em; + font-size: 0.9em; + } + + a { + pointer-events: auto; + border-bottom: 1.5px solid var(--primary-color); + color: var(--primary-color); + text-decoration: none; + line-height: 1.5; + line-break: anywhere; + } + + button { + pointer-events: auto; + background: transparent; + border: none; + color: var(--text-pale-color); + padding: 0; + cursor: pointer; + &.copied, &:hover { + color: var(--primary-color); + } + } +} + +/* footer + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +footer { + font-size: 0.8em; + line-height: 18px; + margin-top: auto; + padding: 15px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0 0.5em; + color: var(--text-pale-color); + + .left { + margin: 15px 0; + margin-right: auto; + display: flex; + align-items: center; + span { + padding: 0 0.5em; + } + a { + color: var(--text-pale-color); + text-decoration: none; + text-underline-offset: 4px; + &:hover { + color: var(--text-pale-color); + text-decoration: underline; + } + } + } + + .right { + display: flex; + align-items: center; + gap: 4px; + margin: 11px 0; + + #rss-btn, + #theme-toggle { + -webkit-tap-highlight-color: transparent; + border: none; + background-color: transparent; + text-decoration: none; + cursor: pointer; + color: var(--text-pale-color); + &:hover { + color: var(--primary-color); + } + } + + #rss-btn { + padding: 4px 0; + font-size: 0.85em; + line-height: 18px; + } + #theme-toggle { + padding: 4px; + transform: translateX(6px); + svg { + scale: 0.7; + } + line-height: 0; + } + } +} + +/* blog page + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.blog { + #wrapper { + margin: 0 auto; + max-width: var(--main-max-width); + min-height: 100vh; + min-height: 100dvh; + display: flex; + flex-direction: column; + } + + main { + margin: 1em 0; + } +} + +/* post page + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.post { + #wrapper { + display: flex; + justify-content: space-between; + } + + #blank { + order: 1; + position: sticky; + width: calc((100% - var(--main-max-width)) / 2); + } + + main { + order: 2; + width: 100%; + margin: 0 auto; + max-width: var(--main-max-width); + min-height: 100vh; + min-height: 100dvh; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + article { + padding: 0 15px 2em; + + #post-info { + display: flex; + align-items: center; + flex-wrap: wrap; + margin-bottom: 1.5em; + font-size: 0.9em; + } + + #date { + color: var(--text-pale-color); + margin-bottom: 1em; + + #publish, + #updated { + margin-right: 1em; + } + } + + #tags { + margin-bottom: 1em; + display: flex; + gap: 1em; + flex-wrap: wrap; + + a { + color: var(--primary-color); + text-decoration: none; + line-height: 1.25; + span { + font-size: 0.95em; + margin-right: 2px; + } + + border-bottom: 1.5px solid transparent; + &:hover { + opacity: 1; + border-bottom-color: var(--primary-color); + } + } + } + + #outdate_alert { + font-style: italic; + &.hidden { + display: none; + } + } + } + + .mermaid { + background: #fff; + } + + .giscus { + padding: 0 15px; + } + + aside { + order: 3; + width: calc((100% - var(--main-max-width)) / 2); + position: sticky; + margin-top: 13em; + top: 0; + height: min-content; + font-size: 0.9em; + + nav { + padding: 10px 1em 10px 2em; + min-width: 60%; + overflow-y: auto; + max-height: calc(100vh - 6em); + scrollbar-width: none; + + &::-webkit-scrollbar { + width: 0; + } + } + + ul { + list-style-type: none; + padding: 0; + line-height: 2; + margin: 0; + } + + a { + text-decoration: none; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--text-pale-color); + position: relative; + padding: 0 1em; + + &.h3 { + padding-left: 2em; + } + + &::before { + display: block; + content: ""; + width: 1.5px; + position: absolute; + top: 0.7em; + bottom: 0.7em; + left: 0em; + background: transparent; + } + + &:hover { + color: var(--primary-color); + } + + &:hover::before { + background-color: var(--primary-color); + } + } + + #back-to-top { + line-height: 0; + z-index: 99; + position: fixed; + bottom: 15px; + margin-left: 1.5em; + color: var(--text-pale-color); + background: transparent; + border: none; + cursor: pointer; + padding: 15px; + transform: translateY(-5px) scale(0); + transition: transform 0.15s; + + svg { + scale: 0.8; + } + + &.shown { + transform: translateY(0px) scale(1); + } + + &:hover { + color: var(--primary-color); + } + } + } + + @media (max-width: 1024px) { + aside, #blank { + display: none; + } + } +} + +/* tag list page + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.tag-list { + #wrapper { + margin: 0 auto; + max-width: var(--main-max-width); + min-height: 100vh; + min-height: 100dvh; + display: flex; + flex-direction: column; + } + + main { + margin: 0 15px; + } + + .title { + font-size: 1.15em; + margin: 1.5em 0 0.75em; + } + + .tags { + margin: 2em 0; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 1em 2em; + + a { + color: var(--primary-color); + text-decoration: none; + line-height: 1.25; + + border-bottom: 1.5px solid transparent; + &:hover { + border-bottom-color: var(--primary-color); + } + } + } + +} + +/* tag single page + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.tag-single { + #wrapper { + margin: 0 auto; + max-width: var(--main-max-width); + min-height: 100vh; + min-height: 100dvh; + display: flex; + flex-direction: column; + } + + main { + width: 100%; + margin: 1em 0; + } + + .title { + font-size: 1.1em; + margin: 0.5em 15px 2em; + } +} + +/* 404 page + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +body.not-found { + height: 100vh; + height: 100dvh; + display: flex; + justify-content: center; + align-items: center; + + .wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 2em; + margin-bottom: 6em; + } + + .error { + margin: 0; + color: var(--text-pale-color); + } + + a { + color: var(--primary-color); + text-decoration: none; + border-bottom: 1.5px solid transparent; + &:hover { + border-color: var(--primary-color); + } + } +} + +/* reaction + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.reaction { + font-size: 0.8em; + padding: 15px 15px 30px; + min-height: 75px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + + &.left { + justify-content: start; + } + + &.right { + justify-content: end; + } + + &.center { + justify-content: center; + } + + &.error { + button { + color: var(--callout-caution-color) !important; + } + } + + button { + line-height: 1; + padding: 0.5em; + color: var(--text-pale-color); + display: flex; + justify-content: center; + align-items: center; + border: none; + gap: 4px; + background: transparent; + user-select: none; + cursor: pointer; + + span { + min-width: 16px; + } + + &:hover { + color: var(--primary-color); + } + + &.reacted { + color: var(--primary-color); + } + } +} + + +/* normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +html { + line-height: 1.15; + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +body { + margin: 0; +} + +main { + display: block; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +pre { + font-family: monospace, monospace; + font-size: 1em; +} + +a { + background-color: transparent; +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted; +} + +b, +strong { + font-weight: bolder; +} + +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +small { + font-size: 80%; +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +img { + border-style: none; +} + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; + appearance: button; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +textarea { + overflow: auto; +} + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + padding: 0; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + -webkit-appearance: textfield; + appearance: textfield; + outline-offset: -2px; +} + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} + +details { + display: block; +} + +summary { + display: list-item; +} + +template { + display: none; +} + +[hidden] { + display: none; +} diff --git a/src/themes/serene/screenshot.png b/src/themes/serene/screenshot.png new file mode 100644 index 0000000..b824cde Binary files /dev/null and b/src/themes/serene/screenshot.png differ diff --git a/src/themes/serene/static/icon/arrow-up.svg b/src/themes/serene/static/icon/arrow-up.svg new file mode 100644 index 0000000..f2430d4 --- /dev/null +++ b/src/themes/serene/static/icon/arrow-up.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/bluesky.svg b/src/themes/serene/static/icon/bluesky.svg new file mode 100644 index 0000000..faa3039 --- /dev/null +++ b/src/themes/serene/static/icon/bluesky.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/caution.svg b/src/themes/serene/static/icon/caution.svg new file mode 100644 index 0000000..c8bb5ee --- /dev/null +++ b/src/themes/serene/static/icon/caution.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/check.svg b/src/themes/serene/static/icon/check.svg new file mode 100644 index 0000000..9e030ce --- /dev/null +++ b/src/themes/serene/static/icon/check.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/copy.svg b/src/themes/serene/static/icon/copy.svg new file mode 100644 index 0000000..e5fb178 --- /dev/null +++ b/src/themes/serene/static/icon/copy.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/email.svg b/src/themes/serene/static/icon/email.svg new file mode 100644 index 0000000..1593667 --- /dev/null +++ b/src/themes/serene/static/icon/email.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/facebook.svg b/src/themes/serene/static/icon/facebook.svg new file mode 100644 index 0000000..991d924 --- /dev/null +++ b/src/themes/serene/static/icon/facebook.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/fingerprint.svg b/src/themes/serene/static/icon/fingerprint.svg new file mode 100644 index 0000000..a15985b --- /dev/null +++ b/src/themes/serene/static/icon/fingerprint.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/github.svg b/src/themes/serene/static/icon/github.svg new file mode 100644 index 0000000..e15be38 --- /dev/null +++ b/src/themes/serene/static/icon/github.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/important.svg b/src/themes/serene/static/icon/important.svg new file mode 100644 index 0000000..b3dcb13 --- /dev/null +++ b/src/themes/serene/static/icon/important.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/instagram.svg b/src/themes/serene/static/icon/instagram.svg new file mode 100644 index 0000000..6323fa8 --- /dev/null +++ b/src/themes/serene/static/icon/instagram.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/linkedin.svg b/src/themes/serene/static/icon/linkedin.svg new file mode 100644 index 0000000..dfb0d9f --- /dev/null +++ b/src/themes/serene/static/icon/linkedin.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/mastodon.svg b/src/themes/serene/static/icon/mastodon.svg new file mode 100644 index 0000000..601640f --- /dev/null +++ b/src/themes/serene/static/icon/mastodon.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/moon.svg b/src/themes/serene/static/icon/moon.svg new file mode 100644 index 0000000..fe7bdc8 --- /dev/null +++ b/src/themes/serene/static/icon/moon.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/note.svg b/src/themes/serene/static/icon/note.svg new file mode 100644 index 0000000..6afebac --- /dev/null +++ b/src/themes/serene/static/icon/note.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/question.svg b/src/themes/serene/static/icon/question.svg new file mode 100644 index 0000000..2aaafe7 --- /dev/null +++ b/src/themes/serene/static/icon/question.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/quote.svg b/src/themes/serene/static/icon/quote.svg new file mode 100644 index 0000000..24156f7 --- /dev/null +++ b/src/themes/serene/static/icon/quote.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/themes/serene/static/icon/sun.svg b/src/themes/serene/static/icon/sun.svg new file mode 100644 index 0000000..1025381 --- /dev/null +++ b/src/themes/serene/static/icon/sun.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/threads.svg b/src/themes/serene/static/icon/threads.svg new file mode 100644 index 0000000..deccc24 --- /dev/null +++ b/src/themes/serene/static/icon/threads.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/tip.svg b/src/themes/serene/static/icon/tip.svg new file mode 100644 index 0000000..21b107a --- /dev/null +++ b/src/themes/serene/static/icon/tip.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/twitter.svg b/src/themes/serene/static/icon/twitter.svg new file mode 100644 index 0000000..5ba5a47 --- /dev/null +++ b/src/themes/serene/static/icon/twitter.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/icon/warning.svg b/src/themes/serene/static/icon/warning.svg new file mode 100644 index 0000000..0d70434 --- /dev/null +++ b/src/themes/serene/static/icon/warning.svg @@ -0,0 +1 @@ + diff --git a/src/themes/serene/static/js/lightense.min.js b/src/themes/serene/static/js/lightense.min.js new file mode 100644 index 0000000..1f979a6 --- /dev/null +++ b/src/themes/serene/static/js/lightense.min.js @@ -0,0 +1,2 @@ +/*! lightense-images v1.0.17 | © Tunghsiao Liu | MIT */ +!function (e, t) { "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.Lightense = t() : e.Lightense = t() }(this, (function () { return e = { 352: e => { function t(e, t) { var n = Object.keys(e); if (Object.getOwnPropertySymbols) { var r = Object.getOwnPropertySymbols(e); t && (r = r.filter((function (t) { return Object.getOwnPropertyDescriptor(e, t).enumerable }))), n.push.apply(n, r) } return n } function n(e) { for (var n = 1; n < arguments.length; n++) { var i = null != arguments[n] ? arguments[n] : {}; n % 2 ? t(Object(i), !0).forEach((function (t) { r(e, t, i[t]) })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(i)) : t(Object(i)).forEach((function (t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(i, t)) })) } return e } function r(e, t, n) { return t in e ? Object.defineProperty(e, t, { value: n, enumerable: !0, configurable: !0, writable: !0 }) : e[t] = n, e } function i(e) { return (i = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (e) { return typeof e } : function (e) { return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e })(e) } var o = function () { "use strict"; var e, t = { time: 300, padding: 40, offset: 40, keyboard: !0, cubicBezier: "cubic-bezier(.2, 0, .1, 1)", background: "var(--bg-color-80, rgba(255, 255, 255, .98))", zIndex: 1e6, beforeShow: void 0, afterShow: void 0, beforeHide: void 0, afterHide: void 0 }, r = {}; function o(e) { var t = r[e]; if (t) { if ("function" != typeof t) throw "config.".concat(e, " must be a function!"); Reflect.apply(t, r, [r]) } } function a(e) { switch (i(e)) { case "undefined": throw "You need to pass an element!"; case "string": return document.querySelectorAll(e); case "object": return e } } function c(e) { var t = e.length; if (t) for (var n = 0; n < t; n++)s(e[n]); else s(e) } function s(e) { e.src && !e.classList.contains("lightense-target") && (e.classList.add("lightense-target"), e.addEventListener("click", (function (i) { if (r.keyboard && (i.metaKey || i.ctrlKey)) return window.open(e.src, "_blank"); !function (e) { if (r.target = e, r.target.classList.contains("lightense-open")) return g(); o("beforeShow"), r.scrollY = window.scrollY, function (e, t, n) { e.addEventListener(t, (function r(i) { Reflect.apply(n, this, i), e.removeEventListener(t, r) })) }(r.target, "transitionend", (function () { o("afterShow") })); var i = new Image; i.onload = function () { !function (e) { var n = e.width, i = e.height, o = window.pageYOffset || document.documentElement.scrollTop || 0, a = window.pageXOffset || document.documentElement.scrollLeft || 0, c = r.target.getBoundingClientRect(), s = n / c.width, d = window.innerWidth || document.documentElement.clientWidth || 0, l = window.innerHeight || document.documentElement.clientHeight || 0, u = r.target.getAttribute("data-lightense-padding") || r.target.getAttribute("data-padding") || r.padding, g = d > u ? d - u : d - t.padding, p = l > u ? l - u : l - t.padding, f = n / i, b = g / p; r.scaleFactor = n < g && i < p ? s : f < b ? p / i * s : g / n * s; var h = d / 2, m = o + l / 2, v = c.left + a + c.width / 2, y = c.top + o + c.height / 2; r.translateX = Math.round(h - v), r.translateY = Math.round(m - y) }(this), function () { r.target.classList.add("lightense-open"), r.wrap = document.createElement("div"), r.wrap.className = "lightense-wrap", setTimeout((function () { r.target.style.transform = "scale(" + r.scaleFactor + ")" }), 20), r.target.parentNode.insertBefore(r.wrap, r.target), r.wrap.appendChild(r.target), setTimeout((function () { r.wrap.style.transform = "translate3d(" + r.translateX + "px, " + r.translateY + "px, 0)" }), 20); var e = { cubicBezier: r.target.getAttribute("data-lightense-cubic-bezier") || r.cubicBezier, background: r.target.getAttribute("data-lightense-background") || r.target.getAttribute("data-background") || r.background, zIndex: r.target.getAttribute("data-lightense-z-index") || r.zIndex }, t = n(n({}, r), e); d("lightense-images-css-computed", "\n :root {\n --lightense-z-index: ".concat(t.zIndex - 1, ";\n --lightense-backdrop: ").concat(t.background, ";\n --lightense-duration: ").concat(t.time, "ms;\n --lightense-timing-func: ").concat(t.cubicBezier, ";\n }")), r.container.style.visibility = "visible", setTimeout((function () { r.container.style.opacity = "1" }), 20) }(), window.addEventListener("keyup", f, !1), window.addEventListener("scroll", p, !1), r.container.addEventListener("click", g, !1) }, i.src = r.target.src }(this) }), !1)) } function d(e, t) { var n = document.head || document.getElementsByTagName("head")[0]; document.getElementById(e) && document.getElementById(e).remove(); var r = document.createElement("style"); r.id = e, r.styleSheet ? r.styleSheet.cssText = t : r.appendChild(document.createTextNode(t)), n.appendChild(r) } function l() { d("lightense-images-css", "\n:root {\n --lightense-z-index: ".concat(r.zIndex - 1, ";\n --lightense-backdrop: ").concat(r.background, ";\n --lightense-duration: ").concat(r.time, "ms;\n --lightense-timing-func: ").concat(r.cubicBezier, ";\n}\n\n.lightense-backdrop {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n position: fixed;\n top: 0;\n left: 0;\n overflow: hidden;\n z-index: calc(var(--lightense-z-index) - 1);\n padding: 0;\n margin: 0;\n transition: opacity var(--lightense-duration) ease;\n cursor: zoom-out;\n opacity: 0;\n background-color: var(--lightense-backdrop);\n visibility: hidden;\n}\n\n@supports (-webkit-backdrop-filter: blur(30px)) {\n .lightense-backdrop {\n background-color: var(--lightense-backdrop);\n -webkit-backdrop-filter: blur(30px);\n }\n}\n\n@supports (backdrop-filter: blur(30px)) {\n .lightense-backdrop {\n background-color: var(--lightense-backdrop);\n backdrop-filter: blur(30px);\n }\n}\n\n.lightense-wrap {\n position: relative;\n transition: transform var(--lightense-duration) var(--lightense-timing-func);\n z-index: var(--lightense-z-index);\n pointer-events: none;\n}\n\n.lightense-target {\n cursor: zoom-in;\n transition: transform var(--lightense-duration) var(--lightense-timing-func);\n pointer-events: auto;\n}\n\n.lightense-open {\n cursor: zoom-out;\n}\n\n.lightense-transitioning {\n pointer-events: none;\n}")) } function u() { null === document.querySelector(".lightense-backdrop") ? (r.container = document.createElement("div"), r.container.className = "lightense-backdrop", document.body.appendChild(r.container)) : r.container = document.querySelector(".lightense-backdrop") } function g() { o("beforeHide"), window.removeEventListener("keyup", f, !1), window.removeEventListener("scroll", p, !1), r.container.removeEventListener("click", g, !1), r.target.classList.remove("lightense-open"), r.wrap.style.transform = "", r.target.style.transform = "", r.target.classList.add("lightense-transitioning"), r.container.style.opacity = "", setTimeout((function () { o("afterHide"), r.container.style.visibility = "", r.container.style.backgroundColor = "", r.wrap.parentNode.replaceChild(r.target, r.wrap), r.target.classList.remove("lightense-transitioning") }), r.time) } function p() { Math.abs(r.scrollY - window.scrollY) >= r.offset && g() } function f(e) { e.preventDefault(), 27 === e.keyCode && g() } return function (i) { var o = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; e = a(i), r = n(n({}, t), o), l(), u(), c(e) } }(); e.exports = o } }, t = {}, function n(r) { var i = t[r]; if (void 0 !== i) return i.exports; var o = t[r] = { exports: {} }; return e[r](o, o.exports, n), o.exports }(352); var e, t })); \ No newline at end of file diff --git a/src/themes/serene/static/js/main.js b/src/themes/serene/static/js/main.js new file mode 100644 index 0000000..b0aee0c --- /dev/null +++ b/src/themes/serene/static/js/main.js @@ -0,0 +1,259 @@ +function enableThemeToggle() { + const themeToggle = document.querySelector('#theme-toggle'); + if (!themeToggle) return; + const hlLink = document.querySelector('link#hl'); + const preferDark = window.matchMedia("(prefers-color-scheme: dark)"); + function toggleTheme(theme) { + if (theme == "dark") document.body.classList.add('dark'); else document.body.classList.remove('dark'); + if (hlLink) hlLink.href = `/hl-${theme}.css`; + sessionStorage.setItem("theme", theme); + toggleGiscusTheme(theme); + } + function toggleGiscusTheme(theme) { + const iframe = document.querySelector('iframe.giscus-frame'); + if (iframe) iframe.contentWindow.postMessage({ giscus: { setConfig: { theme: `${location.origin}/giscus_${theme}.css` } } }, 'https://giscus.app'); + } + function initGiscusTheme(evt) { + if (evt.origin !== 'https://giscus.app') return; + if (!(typeof evt.data === 'object' && evt.data.giscus)) return; + toggleGiscusTheme(sessionStorage.getItem("theme") || (preferDark.matches ? "dark" : "light")); + window.removeEventListener('message', initGiscusTheme); + } + window.addEventListener('message', initGiscusTheme); + themeToggle.addEventListener('click', () => toggleTheme(sessionStorage.getItem("theme") == "dark" ? "light" : "dark")); + preferDark.addEventListener("change", e => toggleTheme(e.matches ? "dark" : "light")); + if (!sessionStorage.getItem("theme") && preferDark.matches) toggleTheme("dark"); + if (sessionStorage.getItem("theme") == "dark") toggleTheme("dark"); +} + +function enablePrerender() { + const prerender = (a) => { + if (!a.classList.contains('instant')) return; + const script = document.createElement('script'); + script.type = 'speculationrules'; + script.textContent = JSON.stringify({ prerender: [{ source: 'list', urls: [a.href] }] }); + document.body.append(script); + a.classList.remove('instant'); + } + const prefetch = (a) => { + if (!a.classList.contains('instant')) return; + const link = document.createElement('link'); + link.rel = 'prefetch'; + link.href = a.href; + document.head.append(link); + a.classList.remove('instant'); + } + const support = HTMLScriptElement.supports && HTMLScriptElement.supports('speculationrules'); + const handle = support ? prerender : prefetch; + document.querySelectorAll('a.instant').forEach(a => { + if (a.href.endsWith(window.location.pathname)) return; + let timer; + a.addEventListener('mouseenter', () => { + timer = setTimeout(() => handle(a), 50); + }); + a.addEventListener('mouseleave', () => clearTimeout(timer)); + a.addEventListener('touchstart', () => handle(a), { passive: true }); + }); +} + +function enableRssMask() { + const rssBtn = document.querySelector('#rss-btn'); + const mask = document.querySelector('#rss-mask'); + const copyBtn = document.querySelector('#rss-mask button'); + if (!rssBtn || !mask) return; + rssBtn.addEventListener('click', (e) => { + e.preventDefault(); + mask.showModal(); + }); + const close = (e) => { + if (e.target == mask) mask.close(); + }; + mask.addEventListener('click', close); + const copy = () => { + navigator.clipboard.writeText(copyBtn.dataset.link).then(() => { + copyBtn.innerHTML = copyBtn.dataset.checkIcon; + copyBtn.classList.add('copied'); + copyBtn.removeEventListener('click', copy); + setTimeout(() => { + mask.close(); + copyBtn.innerHTML = copyBtn.dataset.copyIcon; + copyBtn.classList.remove('copied'); + copyBtn.addEventListener('click', copy); + }, 400); + }); + } + copyBtn.addEventListener('click', copy); +} + +function enableOutdateAlert() { + const alert = document.querySelector('#outdate_alert'); + if (!alert) return; + const publish = document.querySelector('#publish'); + const updated = document.querySelector('#updated'); + const updateDate = new Date(updated ? updated.textContent : publish.textContent); + const intervalDays = Math.floor((Date.now() - updateDate.getTime()) / (24 * 60 * 60 * 1000)); + const alertDays = parseInt(alert.dataset.days); + if (intervalDays >= alertDays) { + const msg = alert.dataset.alertTextBefore + intervalDays + alert.dataset.alertTextAfter; + alert.querySelector('.content').textContent = msg; + alert.classList.remove('hidden'); + } +} + +function enableTocTooltip() { + const anchors = document.querySelectorAll('aside nav a'); + if (anchors.length == 0) return; + const toggleTooltip = () => { + anchors.forEach(anchor => { + if (anchor.offsetWidth < anchor.scrollWidth) { + anchor.setAttribute('title', anchor.textContent); + } else { + anchor.removeAttribute('title'); + } + }); + }; + window.addEventListener('resize', toggleTooltip); + toggleTooltip(); +} + +function addCopyBtns() { + const cfg = document.querySelector('#copy-cfg'); + if (!cfg) return; + const copyIcon = cfg.dataset.copyIcon; + const checkIcon = cfg.dataset.checkIcon; + document.querySelectorAll('pre').forEach(block => { + if (block.classList.contains('mermaid')) return; + const wrapper = document.createElement('div'); + wrapper.className = 'codeblock'; + const btn = document.createElement('button'); + btn.className = 'copy'; + btn.ariaLabel = 'copy'; + btn.innerHTML = copyIcon; + const copy = () => { + navigator.clipboard.writeText(block.textContent).then(() => { + btn.innerHTML = checkIcon; + btn.classList.add('copied'); + btn.removeEventListener('click', copy); + setTimeout(() => { + btn.innerHTML = copyIcon; + btn.classList.remove('copied'); + btn.addEventListener('click', copy); + }, 1500); + }); + }; + btn.addEventListener('click', copy); + wrapper.appendChild(block.cloneNode(true)); + wrapper.appendChild(btn); + block.replaceWith(wrapper); + }); +} + +function addBackToTopBtn() { + const backBtn = document.querySelector('#back-to-top'); + if (!backBtn) return; + const toTop = () => window.scrollTo({ top: 0 }); + const toggle = () => { + const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + if (scrollTop > 200 && !backBtn.classList.contains('shown')) { + backBtn.classList.add('shown'); + backBtn.setAttribute('tabindex', 0); + backBtn.addEventListener('click', toTop); + } else if (scrollTop <= 200 && backBtn.classList.contains('shown')) { + backBtn.classList.remove('shown'); + backBtn.setAttribute('tabindex', -1); + backBtn.removeEventListener('click', toTop); + } + }; + window.addEventListener('scroll', toggle); + toggle(); +} + +function addFootnoteBacklink() { + const footnotes = document.querySelectorAll('.footnote-definition'); + footnotes.forEach(footnote => { + const backlink = document.createElement('button'); + backlink.className = 'backlink'; + backlink.ariaLabel = 'backlink'; + backlink.innerHTML = '↩︎'; + backlink.addEventListener('click', () => window.scrollTo({ + top: document.querySelector(`.footnote-reference a[href="#${footnote.id}"]`).getBoundingClientRect().top + window.scrollY, + })); + const lastEl = footnote.lastElementChild || footnote; + lastEl.appendChild(backlink); + }); +} + +function enableImgLightense() { + window.addEventListener("load", () => Lightense(".prose img:not(.no-lightense)", { background: 'rgba(43, 43, 43, 0.19)' })); +} + +function enableReaction() { + const container = document.querySelector('.reaction'); + if (!container) return; + const endpoint = container.dataset.endpoint; + const slug = location.pathname.split('/').filter(Boolean).pop(); + let state = { error: false, reaction: {} }; + const render = () => { + const btns = Object.entries(state.reaction).map(([emoji, [count, reacted]])=> { + const span = document.createElement('span'); + span.textContent = count; + const btn = document.createElement('button'); + if (reacted) btn.classList.add('reacted'); + btn.append(emoji, span); + btn.onclick = () => toggle(emoji); + return btn; + }); + if (state.error) { + container.classList.add('error'); + } else { + container.classList.remove('error'); + } + container.replaceChildren(...btns); + }; + const toggle = async (target) => { + const [count, reacted] = state.reaction[target]; + state.reaction[target] = reacted ? [count - 1, false] : [count + 1, true]; + render(); + try { + const resp = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ slug, target, reacted: !reacted }), + }); + if (resp.status === 200) { + error = false; + } else { + throw new Error(); + } + } catch (err) { + state.error = true; + state.reaction[target] = [count, reacted]; + render(); + } + }; + const init = async () => { + const resp = await fetch(`${endpoint}?slug=${slug}`); + if (resp.status === 200) { + state.reaction = await resp.json(); + render(); + } + }; + init(); +} + +enableThemeToggle(); +enablePrerender(); +enableRssMask(); +if (document.body.classList.contains('post')) { + enableOutdateAlert(); + addBackToTopBtn(); + enableTocTooltip(); +} +if (document.querySelector('.prose')) { + addCopyBtns(); + addFootnoteBacklink(); + enableImgLightense(); + enableReaction(); +} diff --git a/src/themes/serene/templates/404.html b/src/themes/serene/templates/404.html new file mode 100644 index 0000000..1e924de --- /dev/null +++ b/src/themes/serene/templates/404.html @@ -0,0 +1,14 @@ +{% extends "_base.html" %} + +{% block page %}not-found{% endblock page%} +{% block lang %}{{ config.default_language }}{% endblock lang %} +{% block title %}404{% endblock title %} + +{% block content %} +
+

+ {{ config.extra.not_found_error_text }} +

+ {{ config.extra.not_found_recover_text }} +
+{% endblock %} diff --git a/src/themes/serene/templates/_base.html b/src/themes/serene/templates/_base.html new file mode 100644 index 0000000..0bd7b3b --- /dev/null +++ b/src/themes/serene/templates/_base.html @@ -0,0 +1,38 @@ + + + + + + + + + {% block desc %}{% endblock desc %} + {% block title %}{% endblock title %} + + + + {% include "_custom_font.html" %} + {% include "_custom_css.html" %} + + {% block head %}{% endblock head %} + {% include "_head_extend.html" %} + + + + {% if not config.extra.force_theme %} + + {% endif %} + {% block content %}{% endblock content %} + {% block script %}{% endblock script %} + + + + diff --git a/src/themes/serene/templates/_custom_css.html b/src/themes/serene/templates/_custom_css.html new file mode 100644 index 0000000..f767a33 --- /dev/null +++ b/src/themes/serene/templates/_custom_css.html @@ -0,0 +1,59 @@ + diff --git a/src/themes/serene/templates/_custom_font.html b/src/themes/serene/templates/_custom_font.html new file mode 100644 index 0000000..e69de29 diff --git a/src/themes/serene/templates/_footer.html b/src/themes/serene/templates/_footer.html new file mode 100644 index 0000000..e9c1dd1 --- /dev/null +++ b/src/themes/serene/templates/_footer.html @@ -0,0 +1,52 @@ +{% if config.extra.blog_section_path is defined %} +{% set blog_section_path = config.extra.blog_section_path %} +{% endif %} +
+
+ +
+ +
+ {% if blog_section_path is defined and section.path is starting_with(blog_section_path) %} + {% if section.generate_feeds %} + {% set_global rss_path = blog_section_path ~ "/" ~ config.feed_filenames.0 %} + {% elif config.generate_feeds %} + {% set_global rss_path = "/" ~ config.feed_filenames.0 %} + {% endif %} + {% if section.generate_feeds or config.generate_feeds %} + RSS + {% endif %} + {% endif %} + + {% if not config.extra.force_theme %} + {% set moon_icon = load_data(path="icon/moon.svg") %} + {% set sun_icon = load_data(path="icon/sun.svg") %} + + {% endif %} +
+
+ +{% if blog_section_path is defined and section.path is starting_with(blog_section_path) %} +{% if section.generate_feeds or config.generate_feeds %} +{% set link = get_url(path=rss_path) %} + +
+ {{ link }} + {% set copy_icon = load_data(path="icon/copy.svg") %} + {% set check_icon = load_data(path="icon/check.svg") %} + +
+
+{% endif %} +{% endif %} diff --git a/src/themes/serene/templates/_giscus_script.html b/src/themes/serene/templates/_giscus_script.html new file mode 100644 index 0000000..e69de29 diff --git a/src/themes/serene/templates/_head_extend.html b/src/themes/serene/templates/_head_extend.html new file mode 100644 index 0000000..e69de29 diff --git a/src/themes/serene/templates/_section_title.html b/src/themes/serene/templates/_section_title.html new file mode 100644 index 0000000..191e079 --- /dev/null +++ b/src/themes/serene/templates/_section_title.html @@ -0,0 +1,10 @@ +{% if section.extra.title or section.extra.subtitle %} +
+ {% if section.extra.title %} +

{{ section.extra.title }}

+ {% endif %} + {% if section.extra.subtitle %} +

{{ section.extra.subtitle }}

+ {% endif %} +
+{% endif %} diff --git a/src/themes/serene/templates/anchor-link.html b/src/themes/serene/templates/anchor-link.html new file mode 100644 index 0000000..91e875b --- /dev/null +++ b/src/themes/serene/templates/anchor-link.html @@ -0,0 +1 @@ + diff --git a/src/themes/serene/templates/blog.html b/src/themes/serene/templates/blog.html new file mode 100644 index 0000000..c654e86 --- /dev/null +++ b/src/themes/serene/templates/blog.html @@ -0,0 +1,50 @@ +{% import "macros/prose.html" as macros %} +{% extends "_base.html" %} + +{% block page %}blog{% endblock page%} +{% block lang %}{% if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ section.lang }}{% endif %}{% endblock lang %} +{% block title %}{{ section.title }}{% endblock title %} +{% block desc %} + {% if section.description %} + {% set desc = section.description %} + {% else %} + {% set desc = config.description %} + {% endif %} + +{% endblock desc %} + +{% block content %} +
+ {{ macros::back_link(path = get_url(path="/")) }} + {% include "_section_title.html" %} +
+ {% if section.extra.categorized %} + {% for category,posts in section.pages | sort(attribute="taxonomies.categories.0") | group_by(attribute="taxonomies.categories.0") %} + {% set category_name = category %} + {% if category is matching("^__[0-9]{2}__") %} + {% set category_name = category | split(pat="") | slice(start=7) | join(sep="") %} + {% endif %} +

{{ category_name }}

+
+ {% for post in posts %} + + {{ post.title }} + {{ post.date | date(format=section.extra.date_format) }} + + {% endfor %} +
+ {% endfor %} + {% else %} +
+ {% for post in section.pages %} + + {{ post.title }} + {{ post.date | date(format=section.extra.date_format) }} + + {% endfor %} +
+ {% endif %} +
+ {% include "_footer.html" %} +
+{% endblock content %} diff --git a/src/themes/serene/templates/categories/list.html b/src/themes/serene/templates/categories/list.html new file mode 100644 index 0000000..9069f6b --- /dev/null +++ b/src/themes/serene/templates/categories/list.html @@ -0,0 +1,3 @@ + diff --git a/src/themes/serene/templates/categories/single.html b/src/themes/serene/templates/categories/single.html new file mode 100644 index 0000000..9069f6b --- /dev/null +++ b/src/themes/serene/templates/categories/single.html @@ -0,0 +1,3 @@ + diff --git a/src/themes/serene/templates/feed.xml b/src/themes/serene/templates/feed.xml new file mode 100644 index 0000000..fe1f139 --- /dev/null +++ b/src/themes/serene/templates/feed.xml @@ -0,0 +1,32 @@ + + + {% if section.title %}{{ section.title }}{% else %}{{ config.title }}{% endif %} + {%- if section.description %} + {{ section.description }} + {%- elif config.description %} + {{ config.description }} + {%- endif %} + + + {{ last_updated | date(format="%+") }} + {{ feed_url | safe }} + {%- for page in pages %} + + {{ page.title }} + {{ page.date | date(format="%+") }} + {{ page.updated | default(value=page.date) | date(format="%+") }} + {%- if page.summary %} + {{ page.summary }} + {%- endif %} + + {{ page.permalink | safe }} + {{ page.content }} + + {%- endfor %} + \ No newline at end of file diff --git a/src/themes/serene/templates/home.html b/src/themes/serene/templates/home.html new file mode 100644 index 0000000..6f4c3ac --- /dev/null +++ b/src/themes/serene/templates/home.html @@ -0,0 +1,119 @@ +{% extends "_base.html" %} + +{% block page %}homepage{% endblock page%} +{% block lang %}{% if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ section.lang }}{% endif %}{% endblock lang %} +{% block title %}{{ config.title }}{% endblock title %} +{% block desc %} + +{% endblock desc %} + +{% block head %} +{% if config.markdown.highlight_theme == "css" %} + {% if config.extra.force_theme == "dark" %} + + {% else %} + + {% endif %} +{% endif %} +{% if section.extra.math %} + + + + + +{% endif %} +{% endblock head %} + +{% block content %} +
+
+
+ {% if section.extra.avatar %} + avatar + {% endif %} +
+
+ {{ section.extra.name }} + {% if section.extra.id %} + @{{ section.extra.id }} + {% endif %} +
+ {% if section.extra.bio %} +
{{ section.extra.bio }}
+ {% endif %} +
+
+ +
+ {{ section.content | trim | safe }} +
+ {% if section.extra.recent %} + {% set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") %} + {% set section_md_path = blog_section_path ~ "/_index.md" %} + {% set blog_section = get_section(path=section_md_path) %} +
+
+ {% for post in blog_section.pages | slice(end=section.extra.recent_max) %} + + {{ post.title }} + + {{ post.date | date(format=section.extra.date_format) }} + + {% endfor %} +
+ +
+ {% endif %} +
+ {% if section.extra.footer %} + {% include "_footer.html" %} + {% endif %} +
+{% endblock content %} + +{% block script %} + +{% if section.extra.mermaid %} + +{% endif %} +{% endblock script %} diff --git a/src/themes/serene/templates/macros/collection.html b/src/themes/serene/templates/macros/collection.html new file mode 100644 index 0000000..796eae1 --- /dev/null +++ b/src/themes/serene/templates/macros/collection.html @@ -0,0 +1,153 @@ +{% macro card(item) %} +
+
+ {% if item.icon %} +
+ icon +
+ {% endif %} + {% if item.link %} + {{ item.title }} + {% else %} +
{{ item.title }}
+ {% endif %} + {% if item.date %} +
{{ item.date }}
+ {% endif %} +
+ {% if item.subtitle %} +
{{ item.subtitle }}
+ {% endif %} +
{{ item.content | trim | markdown | safe }}
+ {% if item.tags %} +
+ {% for tag in item.tags %} +
#{{ tag }}
+ {% endfor %} +
+ {% endif %} +
+{% endmacro %} + + +{% macro card_simple(item) %} +
+
+ {% if item.icon %} +
+ icon +
+ {% endif %} + {% if item.link %} + {{ item.title }} + {% else %} +
{{ item.title }}
+ {% endif %} +
{{ item.content | trim | markdown | safe }}
+ {% if item.date %} +
{{ item.date }}
+ {% endif %} +
+
{{ item.content | trim | markdown | safe }}
+
+{% endmacro %} + + +{% macro entry(item) %} +{% if item.link %} + +{% else %} +
+{% endif %} + {% if item.icon %} +
+ icon +
+ {% endif %} + {% if item.title or item.subtitle %} +
+ {% if item.title %} +
{{ item.title }}
+ {% endif %} + {% if item.subtitle %} +
{{ item.subtitle }}
+ {% endif %} +
+ {% endif %} +{% if item.link %} +
+{% else %} +
+{% endif %} +{% endmacro %} + + +{% macro box(item) %} + +{% endmacro %} + + +{% macro art(item) %} +
+
+ {{ item.title }} +
+
+ {% if item.link %} + {{ item.title }} + {% else %} +
{{ item.title }}
+ {% endif %} + {% if item.subtitle %} +
{{ item.subtitle }}
+ {% endif %} + {% if item.content %} +
{{ item.content }}
+ {% endif %} + {% if item.footer %} + + {% endif %} +
+
+{% endmacro %} + + +{% macro art_simple(item) %} +
+
+ {{ item.title }} +
+
+ {% if item.link %} + {{ item.title }} + {% else %} +
{{ item.title }}
+ {% endif %} + {% if item.subtitle %} +
{{ item.subtitle }}
+ {% endif %} +
+
+{% endmacro %} diff --git a/src/themes/serene/templates/macros/prose.html b/src/themes/serene/templates/macros/prose.html new file mode 100644 index 0000000..bd9a894 --- /dev/null +++ b/src/themes/serene/templates/macros/prose.html @@ -0,0 +1,32 @@ +{% macro back_link(path) %} +
+ +
+{% endmacro %} + + +{% macro callout(name) %} +
+ {% set icon = load_data(path="icon/" ~ name ~ ".svg") %} + {% if title %} +

+ + {{ icon | safe }} + + {{ title }} +

+
+ {{ body | markdown | safe }} +
+ {% else %} +
+ {{ icon | safe }} +
+
+ {{ body | markdown | safe }} +
+ {% endif %} +
+{% endmacro %} diff --git a/src/themes/serene/templates/post.html b/src/themes/serene/templates/post.html new file mode 100644 index 0000000..f31810a --- /dev/null +++ b/src/themes/serene/templates/post.html @@ -0,0 +1,151 @@ +{% import "macros/prose.html" as macros %} +{% extends "_base.html" %} + +{% block page %}post{% endblock page %} +{% block lang -%} +{%- set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") -%} +{%- set section_md_path = blog_section_path ~ "/_index.md" -%} +{%- set section = get_section(path=section_md_path, metadata_only=true) -%} +{%- if page.extra.lang %}{{page.extra.lang}}{% elif section.extra.lang %}{{section.extra.lang}}{% else %}{{page.lang}}{% endif -%} +{%- endblock lang %} +{% block title %}{{ page.title }}{% endblock title %} +{% block desc %} + {% if page.summary %} + {% set desc = page.summary %} + {% elif page.description %} + {% set desc = page.description %} + {% elif section.description %} + {% set desc = section.description %} + {% else %} + {% set desc = config.description %} + {% endif %} + +{% endblock desc %} + +{% block head %} +{% if config.markdown.highlight_theme == "css" %} + +{% endif %} +{% if page.extra.math %} + + + + + +{% endif %} +{% endblock head %} + +{% block content %} +
+
+ +
+ {{ macros::back_link(path = get_url(path=section.path)) }} + +
+ {% if page.extra.copy is defined %}{% set allow_copy = page.extra.copy %}{% else %}{% set allow_copy = section.extra.copy %}{% endif %} + {% if allow_copy %} + {% set copy_icon = load_data(path="icon/copy.svg") %} + {% set check_icon = load_data(path="icon/check.svg") %} + + {% endif %} +
+

{{ page.title }}

+
+
+ {{ page.date | date(format=section.extra.date_format) }} + {% if page.updated and page.updated != page.date -%} + Updated on {{ page.updated | date(format=section.extra.date_format) }} + {% endif -%} +
+ + {% if page.taxonomies.tags is defined %} +
+ {% for tag in page.taxonomies.tags -%} + {% set tag_slugify = tag | slugify -%} + #{{ tag }} + {%- endfor %} +
+ {% endif %} +
+ + {% if page.extra.outdate_alert is defined %}{% set show_outdate_alert = page.extra.outdate_alert %}{% else %}{% set show_outdate_alert = section.extra.outdate_alert %}{% endif %} + {% if page.extra.outdate_alert_days is defined %}{% set outdate_alert_days = page.extra.outdate_alert_days %}{% else %}{% set outdate_alert_days = section.extra.outdate_alert_days %}{% endif %} + + {% if show_outdate_alert -%} + + {% endif %} + + {{ page.content | safe }} +
+ + {% if page.extra.reaction is defined %}{% set show_reaction = page.extra.reaction %}{% else %}{% set show_reaction = config.extra.reaction %}{% endif %} + {% if show_reaction %} +
+ {% endif %} + + {% if page.extra.comment is defined %}{% set show_comment = page.extra.comment %}{% else %}{% set show_comment = section.extra.comment %}{% endif %} + {% if show_comment %} +
+ {% include "_giscus_script.html" %} + {% endif %} +
+ + {% include "_footer.html" %} +
+
+{% endblock content %} + +{% block script %} + +{% if page.extra.mermaid %} + +{% endif %} +{% endblock script %} diff --git a/src/themes/serene/templates/prose.html b/src/themes/serene/templates/prose.html new file mode 100644 index 0000000..661c408 --- /dev/null +++ b/src/themes/serene/templates/prose.html @@ -0,0 +1,82 @@ +{% import "macros/prose.html" as macros %} +{% extends "_base.html" %} + +{% block page %}prose-page{% endblock page %} +{% block lang -%} +{%- if section.extra.lang %}{{section.extra.lang}}{% else %}{{section.lang}}{% endif -%} +{%- endblock lang %} +{% block title %}{{ section.title }}{% endblock title %} +{% block desc %} + {% if section.description %} + {% set desc = section.description %} + {% else %} + {% set desc = config.description %} + {% endif %} + +{% endblock desc %} + +{% block head %} +{% if config.markdown.highlight_theme == "css" %} + +{% endif %} +{% if section.extra.math %} + + + + + +{% endif %} +{% endblock head %} + +{% block content %} +
+
+ {{ macros::back_link(path = get_url(path="/")) }} + {% include "_section_title.html" %} +
+ {% if section.extra.copy %} + {% set copy_icon = load_data(path="icon/copy.svg") %} + {% set check_icon = load_data(path="icon/check.svg") %} + + {% endif %} +
+ {{ section.content | safe }} +
+ + {% if section.extra.reaction is defined %}{% set show_reaction = section.extra.reaction %}{% else %}{% set show_reaction = config.extra.reaction %}{% endif %} + {% if show_reaction %} +
+ {% endif %} + + {% if section.extra.comment %} +
+ {% include "_giscus_script.html" %} + {% endif %} +
+ + {% include "_footer.html" %} +
+
+{% endblock content %} + +{% block script %} + +{% if section.extra.mermaid %} + +{% endif %} +{% endblock script %} diff --git a/src/themes/serene/templates/shortcodes/caution.html b/src/themes/serene/templates/shortcodes/caution.html new file mode 100644 index 0000000..7d1797f --- /dev/null +++ b/src/themes/serene/templates/shortcodes/caution.html @@ -0,0 +1,2 @@ +{% import "macros/prose.html" as macros %} +{{ macros::callout(name="caution") }} diff --git a/src/themes/serene/templates/shortcodes/collection.html b/src/themes/serene/templates/shortcodes/collection.html new file mode 100644 index 0000000..c217a0c --- /dev/null +++ b/src/themes/serene/templates/shortcodes/collection.html @@ -0,0 +1,23 @@ +{% import 'macros/collection.html' as marcos -%} + +{% set data = load_data(path="content" ~ section.path ~ file, format="toml") %} + +
+{% for item in data.collection %} + {% if item.type == "card" %} + {{ marcos::card(item=item) }} + {% elif item.type == "card_simple" %} + {{ marcos::card_simple(item=item) }} + {% elif item.type == "entry" %} + {{ marcos::entry(item=item) }} + {% elif item.type == "box" %} + {{ marcos::box(item=item) }} + {% elif item.type == "art" %} + {{ marcos::art(item=item) }} + {% elif item.type == "art_simple" %} + {{ marcos::art_simple(item=item) }} + {% elif item.type == "br" %} +
+ {% endif %} +{% endfor %} +
diff --git a/src/themes/serene/templates/shortcodes/detail.html b/src/themes/serene/templates/shortcodes/detail.html new file mode 100644 index 0000000..0d34a74 --- /dev/null +++ b/src/themes/serene/templates/shortcodes/detail.html @@ -0,0 +1,4 @@ +
+ {{ title }} + {{ body | markdown | safe }} +
diff --git a/src/themes/serene/templates/shortcodes/figure.html b/src/themes/serene/templates/shortcodes/figure.html new file mode 100644 index 0000000..8a4123f --- /dev/null +++ b/src/themes/serene/templates/shortcodes/figure.html @@ -0,0 +1,8 @@ +
+ + {% if via %} +
via
+ {% else %} +
{{ caption }}
+ {% endif %} +
\ No newline at end of file diff --git a/src/themes/serene/templates/shortcodes/important.html b/src/themes/serene/templates/shortcodes/important.html new file mode 100644 index 0000000..451c141 --- /dev/null +++ b/src/themes/serene/templates/shortcodes/important.html @@ -0,0 +1,2 @@ +{% import "macros/prose.html" as macros %} +{{ macros::callout(name="important") }} diff --git a/src/themes/serene/templates/shortcodes/mermaid.html b/src/themes/serene/templates/shortcodes/mermaid.html new file mode 100644 index 0000000..3d3725d --- /dev/null +++ b/src/themes/serene/templates/shortcodes/mermaid.html @@ -0,0 +1,3 @@ +
+  {{ body }}
+
\ No newline at end of file diff --git a/src/themes/serene/templates/shortcodes/note.html b/src/themes/serene/templates/shortcodes/note.html new file mode 100644 index 0000000..b16657b --- /dev/null +++ b/src/themes/serene/templates/shortcodes/note.html @@ -0,0 +1,2 @@ +{% import "macros/prose.html" as macros %} +{{ macros::callout(name="note") }} diff --git a/src/themes/serene/templates/shortcodes/quote.html b/src/themes/serene/templates/shortcodes/quote.html new file mode 100644 index 0000000..c7e3c37 --- /dev/null +++ b/src/themes/serene/templates/shortcodes/quote.html @@ -0,0 +1,10 @@ +
+ {% set icon = load_data(path="icon/quote.svg") %} + +
{{ body | markdown | safe }}
+ {% if cite %} +
+ {{ "— " ~ cite | markdown | safe }} +
+ {% endif %} +
\ No newline at end of file diff --git a/src/themes/serene/templates/shortcodes/tip.html b/src/themes/serene/templates/shortcodes/tip.html new file mode 100644 index 0000000..8904437 --- /dev/null +++ b/src/themes/serene/templates/shortcodes/tip.html @@ -0,0 +1,2 @@ +{% import "macros/prose.html" as macros %} +{{ macros::callout(name="tip") }} diff --git a/src/themes/serene/templates/shortcodes/warning.html b/src/themes/serene/templates/shortcodes/warning.html new file mode 100644 index 0000000..0964b15 --- /dev/null +++ b/src/themes/serene/templates/shortcodes/warning.html @@ -0,0 +1,2 @@ +{% import "macros/prose.html" as macros %} +{{ macros::callout(name="warning") }} diff --git a/src/themes/serene/templates/tags/list.html b/src/themes/serene/templates/tags/list.html new file mode 100644 index 0000000..ff41838 --- /dev/null +++ b/src/themes/serene/templates/tags/list.html @@ -0,0 +1,29 @@ +{% import "macros/prose.html" as macros %} +{% extends "_base.html" %} + +{% block page %}tag-list{% endblock page%} +{% block lang -%} +{% set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") %} +{% set section_md_path = blog_section_path ~ "/_index.md"%} +{% set section = get_section(path=section_md_path, metadata_only=true) %} +{%- if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ lang }}{% endif -%} +{%- endblock lang %} +{% block title %}Tags{% endblock title %} +{% block desc %} + +{% endblock desc %} + +{% block content %} +
+ {{ macros::back_link(path = get_url(path="/")) }} +
+

Tags

+
+ {% for tag in terms -%} + # {{ tag.name | lower }} + {% endfor %} +
+
+ {% include "_footer.html" %} +
+{% endblock content %} diff --git a/src/themes/serene/templates/tags/single.html b/src/themes/serene/templates/tags/single.html new file mode 100644 index 0000000..f8b9fc4 --- /dev/null +++ b/src/themes/serene/templates/tags/single.html @@ -0,0 +1,34 @@ +{% import "macros/prose.html" as macros %} +{% extends "_base.html" %} + +{% block page %}tag-single{% endblock page %} +{% block lang -%} +{% set blog_section_path = config.extra.blog_section_path | trim_start_matches(pat="/") %} +{% set section_md_path = blog_section_path ~ "/_index.md"%} +{% set section = get_section(path=section_md_path, metadata_only=true) %} +{%- if section.extra.lang %}{{ section.extra.lang }}{% else %}{{ lang }}{% endif -%} +{%- endblock lang %} +{% block title %}{{section.title}}{% endblock title %} +{% block desc %} + +{% endblock desc %} + +{% block content %} +
+ {{ macros::back_link(path = get_url(path="/tags")) }} +
+
+ # {{ term.name }} +
+
+ {% for post in term.pages %} + + {{ post.title }} + {{ post.date | date(format=section.extra.date_format) }} + + {% endfor %} +
+
+ {% include "_footer.html" %} +
+{% endblock content %} diff --git a/src/themes/serene/theme.toml b/src/themes/serene/theme.toml new file mode 100644 index 0000000..30efe2e --- /dev/null +++ b/src/themes/serene/theme.toml @@ -0,0 +1,12 @@ +name = "serene" +description = "A spiffy blog theme for zola" +license = "MIT" +homepage = "https://github.com/isunjn/serene" +min_version = "0.20.0" +demo = "https://serene-demo.pages.dev" + +[author] +name = "isunjn" +homepage = "https://github.com/isunjn" + +[extra] diff --git a/src/writings/index.html b/src/writings/index.html deleted file mode 100644 index 9ada54d..0000000 --- a/src/writings/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - -

My paper like things, provided in the portable document format (pdf)

- - - diff --git a/treefmt.nix b/treefmt.nix index 78a14d0..e6006d7 100644 --- a/treefmt.nix +++ b/treefmt.nix @@ -18,6 +18,7 @@ treefmt-nix.lib.evalModule pkgs ( }; shellcheck.enable = true; prettier = { + enable = true; settings = { arrowParens = "always"; bracketSameLine = false; @@ -41,10 +42,12 @@ treefmt-nix.lib.evalModule pkgs ( vueIndentScriptAndStyle = false; tabWidth = 4; - overrides = { - files = ["*.js"]; - options.tabwidth = 2; - }; + overrides = [ + { + files = ["*.js"]; + options.tabwidth = 2; + } + ]; }; }; diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..a69eaad --- /dev/null +++ b/update.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env sh + +nix flake update -- cgit 1.4.1