diff options
Diffstat (limited to 'src')
76 files changed, 5162 insertions, 72 deletions
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..3b10361 --- /dev/null +++ b/src/config.toml @@ -0,0 +1,96 @@ +# 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 = "Soispha 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] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false + +[markdown.highlighting] +error_on_missing_language = true + +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" + + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= +[extra] +nav = [ + { 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 }, +] + +# Your info on the home page, if you don't want to display handle/bio/avatar, simply comment out that line +name = "Soispha Peetz" +handle = "soispha" +# bio = "dog person, killer" +# avatar = "img/avatar.webp" + +links = [ + { name = "Git", icon = "git", url = "https://git.foss-syndicate.org/bpeetz" }, + { name = "Email", icon = "email", url = "mailto:soispha@b-peetz.de" }, + { name = "Matrix", icon = "matrix", url = "https://matrix.to/#/@soispha:matrix.vhack.eu" }, + { name = "Signal", icon = "signal", url = "https://signal.me/#eu/0VgltiXf19Tu_tRdpucNYYHXo26LGd2az_w_SHPD_qrUe2xOlMZHybrLzM_-NoKu" }, +] +blog_section_path = "/blog" + +back_link_text = "Back" # Text of the back button +color_scheme = "auto" # "auto" | "light" | "dark" + +# og_image = "img/og.png" # The default image for social media sharing (Open Graph), a path in static folder or a full URL + +footer_copyright = "© 2025 Soispha 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 »" + +# Site-wide default values of display options, can be overridden in a section's `_index.md`, and again in a post's front-matter +date_format = "%b %-d, %Y" +toc = true # show table-of-contents +code_copy = true # show copy button in code block +comment = false # enable comment +math = false # enable formula rendering with KaTeX +mermaid = false # enable chart rendering with Mermaid +outdated_alert = true # show outdate alert after certain days +outdated_alert_days = 256 +outdated_alert_text_before = "This article was last updated " +outdated_alert_text_after = " days ago and may be out of date." + +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..60f5368 --- /dev/null +++ b/src/content/_index.md @@ -0,0 +1,25 @@ ++++ +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 = "Soispha Peetz" +id = "soispha" +bio = "They/Them" + +# avatar = "img/avatar.webp" + +# 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..07b1a5e --- /dev/null +++ b/src/content/blog/_index.md @@ -0,0 +1,29 @@ ++++ +title = "List of blog posts" +description = "My blog posts." +sort_by = "date" +template = "home.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..211ea25 --- /dev/null +++ b/src/content/blog/distributed-hooks.md @@ -0,0 +1,132 @@ ++++ +title = "An collection of my toughs regarding hooks in a fully distributed system" +date = 2025-04-25 ++++ + +<!-- LTeX: language=en-GB --> + +## 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) +1. 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..d9010c5 --- /dev/null +++ b/src/content/contact/_index.md @@ -0,0 +1,32 @@ ++++ +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: + +- [Email – soispha@b-peetz.de](mailto:soispha@b-peetz.de) +- [Matrix – @soispha:vhack.eu](https://matrix.to/#/@soispha:matrix.vhack.eu) +- [Signal – soispha.87](https://signal.me/#eu/0VgltiXf19Tu_tRdpucNYYHXo26LGd2az_w_SHPD_qrUe2xOlMZHybrLzM_-NoKu) + +<!-- 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..c7555e9 --- /dev/null +++ b/src/content/writings/_index.md @@ -0,0 +1,28 @@ ++++ +title = "Writings" +description = "Sort of academically written things" +sort_by = "date" +template = "prose.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.md b/src/content/writings/kant-and-free-software.md new file mode 100644 index 0000000..ec491fa --- /dev/null +++ b/src/content/writings/kant-and-free-software.md @@ -0,0 +1,9 @@ ++++ +title = "Kant and free software" +date = "2024-03-20" + +path = "writings/kant-and-free-software.pdf" + +[taxonomies] +categories=["philosophy"] ++++ diff --git a/src/content/writings/kant-and-free-software.pdf.base b/src/content/writings/kant-and-free-software.pdf.base Binary files differnew file mode 100644 index 0000000..d13de60 --- /dev/null +++ b/src/content/writings/kant-and-free-software.pdf.base diff --git a/src/content/writings/raman-spectroscopy.md b/src/content/writings/raman-spectroscopy.md new file mode 100644 index 0000000..2d05da4 --- /dev/null +++ b/src/content/writings/raman-spectroscopy.md @@ -0,0 +1,10 @@ ++++ +title = "Bau und Evaluation eines günstigen Raman Spektrometers" +date = "2024-06-01" +lang = "de" + +path = "writings/raman-spectroscopy.pdf" + +[taxonomies] +categories=["chemistry"] ++++ diff --git a/src/content/writings/raman-spectroscopy.pdf.base b/src/content/writings/raman-spectroscopy.pdf.base Binary files differnew file mode 100644 index 0000000..c87b99c --- /dev/null +++ b/src/content/writings/raman-spectroscopy.pdf.base 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 @@ -<!doctype html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width"/> - <title>Benedikt's Website</title> - <link rel="stylesheet" href="/style.css"> -</head> -<body> - <h1>Hi!</h1> - <p>For a list of my written things look <a href="./writings/index.html">here</a></p> -</body> -</html> diff --git a/src/static/hl-dark.css b/src/static/hl-dark.css new file mode 100644 index 0000000..abfd5c1 --- /dev/null +++ b/src/static/hl-dark.css @@ -0,0 +1,109 @@ +/* + * 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..d983d43 --- /dev/null +++ b/src/static/hl-light.css @@ -0,0 +1,93 @@ +/* + * 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.svg b/src/static/icon/git.svg new file mode 100644 index 0000000..b35fb62 --- /dev/null +++ b/src/static/icon/git.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 92 92"> + <defs> + <clipPath id="a"> + <path d="M0 .113h91.887V92H0Zm0 0"/> + </clipPath> + </defs> + <g clip-path="url(#a)"> + <path style="stroke:none;fill-rule:nonzero;fill-opacity:1" fill="currentColor" d="M90.156 41.965 50.036 1.848a5.918 5.918 0 0 0-8.372 0l-8.328 8.332 10.566 10.566a7.03 7.03 0 0 1 7.23 1.684 7.034 7.034 0 0 1 1.669 7.277l10.187 10.184a7.028 7.028 0 0 1 7.278 1.672 7.04 7.04 0 0 1 0 9.957 7.05 7.05 0 0 1-9.965 0 7.044 7.044 0 0 1-1.528-7.66l-9.5-9.497V59.36a7.04 7.04 0 0 1 1.86 11.29 7.04 7.04 0 0 1-9.957 0 7.04 7.04 0 0 1 0-9.958 7.06 7.06 0 0 1 2.304-1.539V33.926a7.049 7.049 0 0 1-3.82-9.234L29.242 14.272 1.73 41.777a5.925 5.925 0 0 0 0 8.371L41.852 90.27a5.925 5.925 0 0 0 8.37 0l39.934-39.934a5.925 5.925 0 0 0 0-8.371"/> + </g> +</svg> diff --git a/src/static/icon/matrix.svg b/src/static/icon/matrix.svg new file mode 100644 index 0000000..31eb014 --- /dev/null +++ b/src/static/icon/matrix.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" width="18" height="18" viewBox="0 0 27.9 32" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <title>Matrix (protocol) logo</title> + <g transform="translate(-.095 .005)" fill="currentColor"> + <path d="m27.1 31.2v-30.5h-2.19v-0.732h3.04v32h-3.04v-0.732z"/> + <path d="m8.23 10.4v1.54h0.044c0.385-0.564 0.893-1.03 1.49-1.37 0.58-0.323 1.25-0.485 1.99-0.485 0.72 0 1.38 0.14 1.97 0.42 0.595 0.279 1.05 0.771 1.36 1.48 0.338-0.5 0.796-0.941 1.38-1.32 0.58-0.383 1.27-0.574 2.06-0.574 0.602 0 1.16 0.074 1.67 0.22 0.514 0.148 0.954 0.383 1.32 0.707 0.366 0.323 0.653 0.746 0.859 1.27 0.205 0.522 0.308 1.15 0.308 1.89v7.63h-3.13v-6.46c0-0.383-0.015-0.743-0.044-1.08-0.0209-0.307-0.103-0.607-0.242-0.882-0.133-0.251-0.336-0.458-0.584-0.596-0.257-0.146-0.606-0.22-1.05-0.22-0.44 0-0.796 0.085-1.07 0.253-0.272 0.17-0.485 0.39-0.639 0.662-0.159 0.287-0.264 0.602-0.308 0.927-0.052 0.347-0.078 0.697-0.078 1.05v6.35h-3.13v-6.4c0-0.338-7e-3 -0.673-0.021-1-0.0114-0.314-0.0749-0.623-0.188-0.916-0.108-0.277-0.3-0.512-0.55-0.673-0.258-0.168-0.636-0.253-1.14-0.253-0.198 0.0083-0.394 0.042-0.584 0.1-0.258 0.0745-0.498 0.202-0.705 0.374-0.228 0.184-0.422 0.449-0.584 0.794-0.161 0.346-0.242 0.798-0.242 1.36v6.62h-3.13v-11.4z"/> + <path d="m0.936 0.732v30.5h2.19v0.732h-3.04v-32h3.03v0.732z"/> + </g> +</svg> diff --git a/src/static/icon/signal.svg b/src/static/icon/signal.svg new file mode 100644 index 0000000..2538fdc --- /dev/null +++ b/src/static/icon/signal.svg @@ -0,0 +1,21 @@ +<svg fill="none" height="18" viewBox="0 0 160 160" width="18" xmlns="http://www.w3.org/2000/svg"> + <g fill="currentColor"> + <path d="m80 0c4.1505 0 8.2271.31607 12.2072.925452l-1.1444 7.413248c-3.6069-.55226-7.3014-.8387-11.0628-.8387-3.7612 0-7.4555.28641-11.0623.83862l-1.1444-7.413245c3.9799-.609332 8.0564-.925375 12.2067-.925375z"/> + <path d="m98.9849 2.26619-1.7798 7.28755c7.3099 1.77896 14.1849 4.66606 20.4389 8.47306l3.895-6.411c-6.901-4.20091-14.488-7.38658-22.5541-9.34961z"/> + <path d="m127.279 15.4591-4.432 6.0507c5.977 4.3861 11.257 9.6664 15.643 15.6437l6.051-4.4324c-4.84-6.5957-10.666-12.4222-17.262-17.262z"/> + <path d="m148.384 38.4618-6.411 3.8942c3.807 6.2541 6.694 13.1299 8.473 20.4395l7.288-1.7798c-1.963-8.0657-5.149-15.6528-9.35-22.5539z"/> + <path d="m159.075 67.7934-7.414 1.1444c.553 3.6067.839 7.301.839 11.0622 0 3.7614-.286 7.4559-.839 11.0628l7.414 1.1444c.609-3.9801.925-8.0567.925-12.2072 0-4.1503-.316-8.2267-.925-12.2066z"/> + <path d="m141.973 117.645c3.807-6.255 6.694-13.13 8.473-20.44l7.288 1.7798c-1.963 8.0662-5.149 15.6532-9.35 22.5542z"/> + <path d="m138.49 122.847 6.051 4.432c-4.84 6.596-10.666 12.422-17.262 17.262l-4.433-6.051c5.978-4.386 11.258-9.666 15.644-15.643z"/> + <path d="m117.644 141.973 3.894 6.411c-6.901 4.201-14.488 7.387-22.5537 9.35l-1.7798-7.288c7.3095-1.779 14.1855-4.666 20.4395-8.473z"/> + <path d="m91.0622 151.661 1.1445 7.414c-3.9799.609-8.0564.925-12.2067.925-4.1505 0-8.2272-.316-12.2073-.925l1.1442-7.413c3.6054.552 7.2997.838 11.0631.838 3.7612 0 7.4555-.286 11.0622-.839z"/> + <path d="m62.7945 150.448-1.7794 7.286c-6.0589-1.475-11.8477-3.639-17.2785-6.406l-7.5927 1.772-1.7042-7.304 10.2604-2.394 2.4408 1.243c4.9187 2.506 10.1623 4.467 15.6536 5.803z"/> + <path d="m28.1097 147.273 1.7042 7.304-13.0145 3.036c-8.66079 2.021-16.433718-5.752-14.41286-14.412l3.03673-13.015 7.30383 1.704-3.03675 13.015c-.75782 3.248 2.15705 6.162 5.40485 5.405z"/> + <path d="m14.2041 125.56-7.30383-1.704 1.77163-7.593c-2.76664-5.431-4.93123-11.22-6.40585-17.2787l7.28586-1.7794c1.33599 5.4911 3.29709 10.7351 5.80279 15.6541l1.2435 2.441z"/> + <path d="m8.33759 91.0624-7.412228 1.1442c-.609324-3.9799-.925362-8.0563-.925362-12.2066 0-4.1505.316067-8.2271.925446-12.2072l7.413244 1.1444c-.55225 3.607-.83869 7.3014-.83869 11.0628 0 3.7631.28613 7.4572.83759 11.0624z"/> + <path d="m9.55373 62.795-7.28755-1.7798c1.96302-8.0657 5.1487-15.6528 9.34962-22.5539l6.411 3.8942c-3.807 6.2541-6.6941 13.1299-8.47307 20.4395z"/> + <path d="m21.5098 37.1531-6.0507-4.4324c4.8398-6.5957 10.6663-12.4221 17.262-17.2619l4.4324 6.0507c-5.9773 4.3861-11.2576 9.6663-15.6437 15.6436z"/> + <path d="m42.356 18.0266-3.8943-6.4111c6.9011-4.20082 14.4882-7.38645 22.554-9.34944l1.7798 7.28755c-7.3096 1.77899-14.1854 4.66589-20.4395 8.47299z"/> + <path d="m145 80c0 35.899-29.101 65-65 65-11.3866 0-22.0893-2.928-31.3965-8.072-.8961-.495-1.9417-.658-2.9389-.426l-28.9134 6.747 6.7465-28.914c.2326-.997.0692-2.043-.426-2.939-5.1439-9.307-8.0717-20.0095-8.0717-31.396 0-35.8985 29.1015-65 65-65 35.899 0 65 29.1015 65 65z"/> + </g> +</svg> 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..b3fa3a8 --- /dev/null +++ b/src/themes/serene/.gitignore @@ -0,0 +1,9 @@ +.zed/ +.claude/ + +public/ +content/ +static/img/ +static/assets/ + +.DS_Store diff --git a/src/themes/serene/CHANGELOG.md b/src/themes/serene/CHANGELOG.md new file mode 100644 index 0000000..77a1b42 --- /dev/null +++ b/src/themes/serene/CHANGELOG.md @@ -0,0 +1,512 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [6.0.0] - 2026-09-06 + +- feat: migrate to zola v0.23 / Tera v2, the minimum zola version required is now `v0.23.4`, shortcodes are rewritten as Tera components +- feat: tag links are now generated via zola's taxonomy API instead of hardcoded `/tags/` paths, so the `taxonomy_root` config option (e.g. `taxonomy_root = "blog"` for `/blog/tags/xxx` URLs) is respected +- feat: support multiple blog-like list sections — post pages now read config from their own parent section instead of the `blog_section_path` one, and each list section can have its own feed (`generate_feeds = true` in its `_index.md`); `blog_section_path` now only designates the main section used by the home page's recent posts and the tags pages +- refactor: `blog.html` template is renamed to `posts.html`, change `template = "blog.html"` to `template = "posts.html"` in your blog section's `_index.md` +- feat: display options (`toc` / `code_copy` / `comment` / `math` / `mermaid` / `reaction` / `outdated_alert` / `date_format`, etc.) now follow a unified fallback chain: post front-matter → section `_index.md` → `[extra]` of `zola.toml`, the closest one wins — notably `math` / `mermaid` can now be enabled for a whole section, and site-wide defaults can be set in `zola.toml` +- refactor: several options are renamed for clarity: `force_theme` (`false | "light" | "dark"`) → `color_scheme` (`"auto" | "light" | "dark"`), `copy` → `code_copy`, `outdate_alert*` → `outdated_alert*`, and `id` of the home section → `handle` +- refactor: the `sections` config option is renamed to `nav`, and its `is_external` field is removed — a `path` starting with `/` is an internal link, anything else (e.g. an `https://` URL) is treated as an external link automatically; external nav links now show a `ne-resize` cursor +- refactor: `name` / `handle` / `bio` / `avatar` / `links` of the home page are moved from the home section's `_index.md` to `[extra]` of `zola.toml` +- refactor: the example config file is renamed from `config.example.toml` to `zola.toml.example`, following zola v0.23's new default config file name `zola.toml` (the old `config.toml` name still works) +- refactor: the callout components (`note` / `tip` / `important` / `warning` / `caution`) are removed in favor of zola's native [GitHub alert syntax](https://github.com/orgs/community/discussions/16925) (`> [!NOTE]`), styled with icon and title by the theme; the title texts can be customized via css variables (`--callout-note-title`, etc.) +- feat: collections are redesigned — item appearance (`layout = "card" | "row" | "tile" | "gallery"`) and arrangement (`flow = "stack" | "inline" | "grid"`, the grid adapts its column count to the available width) are now declared at the collection level, and all layouts share one unified set of item fields (`title` / `subtitle` / `content` / `icon` / `image` / `link` / `badge` / `tags` / `featured`) with graceful degradation; item `link`s are auto-detected as internal or external, and `image` supports section-colocated files; `card` items can additionally show a picture on the left via `image` (the small `icon` before the title is a separate field) +- chore: upgrade Mermaid to 11.17.2 and KaTeX to 0.18.6, refreshing KaTeX resource integrity hashes + +### Migrate from zola v0.22 + +Zola v0.23 removed shortcodes and Tera macros in favor of [Tera components](https://www.getzola.org/documentation/content/overview/#templating-your-content), and your markdown content is now itself a Tera template. Once you update zola and this theme, you need to update your content accordingly: + +- Shortcode calls in your markdown must be rewritten in component syntax. Arguments other than strings are wrapped in `{...}`: + - `{{ figure(src="...", width="600") }}` → `{{ <figure src="..." width="600" /> }}` + - `{% quote(cite="...") %} ... {% end %}` → `{% <quote cite="..."> %} ... {% </quote> %}` + - `{{ youtube(id="...", autoplay=true) }}` → `{{ <youtube id="..." autoplay={true} /> }}` + - `{{ collection(file="projects.toml") }}` → `{{ <collection file="projects.toml" section /> }}` (note the extra `section`) +- The callout shortcodes are removed, rewrite them with the GitHub alert syntax (custom titles are not supported, the title is always the type name): + + ```md + {% note(title="Note") %} + note text + {% end %} + ``` + + becomes: + + ```md + > [!NOTE] + > note text + ``` + +- For `figure` with a colocated image, pass the page context: `{{ <figure src="img.png" page /> }}` (or `section` instead of `page` when used in a section's `_index.md`) +- `width` / `height` of `figure` must be strings: `width="600"`, not `width=600` +- Literal `{{` or `{%` in your content (e.g. code blocks showing template syntax, GitHub Actions `${{ ... }}`) must be wrapped with `{% raw %}` ... `{% endraw %}`, otherwise zola will try to interpret them as Tera syntax. For files full of such content, you can instead list them in the `skip_content_templating` config option (glob patterns) to opt them out of templating entirely (components won't work in those files) +- Component calls must be at the top level of your markdown content — nesting them inside a list item is no longer supported (the component's HTML output breaks the list's indentation rules and produces broken HTML). If you had e.g. a callout inside a list item, move it out of the list +- The `date` filter is now backed by [jiff](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) instead of chrono. Common `date_format` values like `"%b %-d, %Y"` still work, but chrono-specific specifiers (e.g. `%+`) are no longer supported and will fail the build — check your `date_format` options against the jiff docs +- Syntax highlighting CSS files (`giallo-light.css` / `giallo-dark.css`) are now generated directly into the output directory instead of `static/`, so you can remove them from your `static/` folder and `.gitignore` +- If you have custom templates that override serene's, they must be migrated to Tera v2 as well, see the [Tera migration guide](https://github.com/Keats/tera/blob/master/MIGRATION.md) +- The `blog.html` template is renamed to `posts.html`: change `template = "blog.html"` to `template = "posts.html"` in your blog section's `_index.md` +- Zola's default config file name is now `zola.toml` — you can rename your `config.toml` to `zola.toml` (optional, the old name still works) +- Several options are renamed, update them in your `zola.toml` / section `_index.md` / post front-matter: + - `force_theme = false | "light" | "dark"` → `color_scheme = "auto" | "light" | "dark"` (`false` becomes `"auto"`) + - `copy` → `code_copy` + - `outdate_alert` / `outdate_alert_days` / `outdate_alert_text_before` / `outdate_alert_text_after` → `outdated_alert` / `outdated_alert_days` / `outdated_alert_text_before` / `outdated_alert_text_after` + - `id` in the home section's `_index.md` → `handle` + - `sections` in `zola.toml` → `nav`, and remove the `is_external` field from its entries (external links are now detected automatically from the `path`) + - `name` / `handle` / `bio` / `avatar` / `links` move from the home section's `_index.md` to `[extra]` of `zola.toml` +- Collection toml files use a new schema, update them as follows: + - each `[[collection]]` becomes `[[item]]`, and the per-item `type` moves to a single collection-level `layout` at the top of the file: `card` → `layout = "card"`, `card_simple` → `layout = "row"` plus `flow = "stack"` (rename its `content` to `subtitle`), `entry` → `layout = "row"`, `box` → `layout = "tile"`, `art` → `layout = "gallery"`, `art_simple` → `layout = "gallery"` plus `flow = "grid"` + - rename item fields: `img` → `image` (`icon` keeps its name), and both `date` and `footer` → `badge` (a short mark rendered as-is: a year, a date range, a rating, a status...) + - remove `type = "br"` items — arrangement is now controlled by the collection-level `flow` (`"stack"` / `"inline"` / `"grid"`) option + +## [5.7.0] - 2026-08-09 + +- feat: add open graph & twitter card meta tags and canonical link +- fix: resolve colocated figure image URLs [@uchouT](https://github.com/uchouT) ([#109](https://github.com/isunjn/serene/pull/109)) +- fix: art collection content margin + +## [5.6.3] - 2026-02-14 + +- fix: back link behavior on new tab + +## [5.6.2] - 2026-02-13 + +- feat: add markdown support for collection's content and subtitle [@paulhdk](https://github.com/paulhdk) ([#104](https://github.com/isunjn/serene/pull/104)) + +## [5.6.1] - 2026-02-01 + +- fix: codeblock with filename + +## [5.6.0] - 2026-02-01 + +- feat: adapt zola v0.22 new code highlighting, custom highlight themes (`serene-light`/`serene-dark`) are removed in favor of built-in themes +- feat: add `github_alerts` color styling (zola v0.21+ feature) +- feat: add `rel_me` option for links (useful for Mastodon verification) +- feat: add `youtube` shortcode [@gloomydumber](https://github.com/gloomydumber) ([#92](https://github.com/isunjn/serene/pull/92)) +- fix: input element color scheme should be changed along with light/dark mode change + +### Migrate from zola v0.21 + +Since this version, the minimum zola version required has changed to `v0.22.1`. + +Replace the old `[markdown]` section in your `config.toml`: + +```toml +# Old (remove these) +[markdown] +highlight_code = true +highlight_theme = "css" +extra_syntaxes_and_themes = ["themes/serene/highlight_themes"] +highlight_themes_css = [ + { theme = "serene-light", filename = "hl-light.css" }, + { theme = "serene-dark", filename = "hl-dark.css" }, +] +``` + +With the new format: + +```toml +# New +[markdown] +github_alerts = true # optional, enables GitHub-style callout syntax + +[markdown.highlighting] +style = "class" +light_theme = "github-light" # or any zola built-in theme +dark_theme = "github-dark" # or any zola built-in theme +``` + +You should also delete the generated `hl-light.css` and `hl-dark.css` files from your `static/` directory if you have them. +The new generated css files for code highlighting will be `giallo-light.css` and `giallo-dark.css`. (giallo is zola's new syntax highlighter) + +## [5.5.0] - 2025-10-18 + +- feat: shortcode `figure`'s `caption` can use markdown now, the `via` prop is removed, use `caption="[via](https://example.com)"` instead +- fix: shortcode `figure` now will keep img's ratio on small screens when `width` and `height` is set +- ui: a few tweaks + +## [5.4.3] - 2025-09-29 + +- fix: correct back link button behavior when there is a hash in url + +## [5.4.2] - 2025-09-27 + +- fix: feed.xml template build error when there is no post + +## [5.4.1] - 2025-09-21 + +- feat: add overflow scroll indicator for table-of-contents + +## [5.4.0] - 2025-09-06 + +- ui: a few tweaks +- feat: back link button now do `history.back()` if appropriate +- feat: add custom height and width options for `figure` shortcode [@znxftw](https://github.com/znxftw) ([#83](https://github.com/isunjn/serene/pull/83)) + +## [5.3.1] - 2025-05-22 + +- fix: `extra_syntaxes_and_themes` defaults to theme's, so you don't need to copy that path [@makai410](https://github.com/makai410) ([#80](https://github.com/isunjn/serene/pull/80)) + +## [5.3.0] - 2025-05-07 + +- ui: a few tweaks + +## [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<String>` + +## [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 🎉 + +[6.0.0]: https://github.com/isunjn/serene/compare/v5.7.0...v6.0.0 +[5.7.0]: https://github.com/isunjn/serene/compare/v5.6.3...v5.7.0 +[5.6.3]: https://github.com/isunjn/serene/compare/v5.6.2...v5.6.3 +[5.6.2]: https://github.com/isunjn/serene/compare/v5.6.1...v5.6.2 +[5.6.1]: https://github.com/isunjn/serene/compare/v5.6.0...v5.6.1 +[5.6.0]: https://github.com/isunjn/serene/compare/v5.5.0...v5.6.0 +[5.5.0]: https://github.com/isunjn/serene/compare/v5.4.3...v5.5.0 +[5.4.3]: https://github.com/isunjn/serene/compare/v5.4.2...v5.4.3 +[5.4.2]: https://github.com/isunjn/serene/compare/v5.4.1...v5.4.2 +[5.4.1]: https://github.com/isunjn/serene/compare/v5.4.0...v5.4.1 +[5.4.0]: https://github.com/isunjn/serene/compare/v5.3.1...v5.4.0 +[5.3.1]: https://github.com/isunjn/serene/compare/v5.3.0...v5.3.1 +[5.3.0]: https://github.com/isunjn/serene/compare/v5.2.1...v5.3.0 +[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..7620101 --- /dev/null +++ b/src/themes/serene/README.md @@ -0,0 +1,5 @@ +A minimal blog theme for [Zola](https://www.getzola.org), well crafted. + +[Demo](https://serene-demo.pages.dev) | [Usage](https://github.com/isunjn/serene/blob/latest/USAGE.md) + + diff --git a/src/themes/serene/USAGE.md b/src/themes/serene/USAGE.md new file mode 100644 index 0000000..88e73a1 --- /dev/null +++ b/src/themes/serene/USAGE.md @@ -0,0 +1,511 @@ +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/). + +Serene requires zola `0.23.4` or later. + +## 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/zola.toml.example` to `myblog/zola.toml`. + +## Sections and Pages + +There is a `nav` config option in your `zola.toml`, which enumerates the navigation entries of the home page. An entry whose `path` starts with `/` links to a section of your site, any other path (e.g. an `https://` URL) is treated as an external link and opens in a new tab. You should have at least one `blog` section. + +The name and path can be changed, note 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 + +# Show a few recent posts in home page +recent = false +recent_max = 15 +recent_more_text = "more »" ++++ + +Hi, I'm ... +``` + +For blog section, create `myblog/content/posts/_index.md`: + +``` ++++ +title = "My Blog" +description = "My blog site." +sort_by = "date" +template = "posts.html" +page_template = "post.html" +insert_anchor_links = "right" +generate_feeds = true + +[extra] +lang = "en" + +title = "Posts" +subtitle = "I write about ...." + +categorized = false # posts can be categorized +back_to_top = true # show back-to-top button ++++ +``` + +Display options like `toc` / `code_copy` / `comment` / `date_format` have site-wide defaults in `zola.toml`, you can override them here for this section (e.g. `toc = false`), see [Front Matter](#front-matter) for details. + +Blog section is defined by `posts.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" }` to the `nav` 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 = "About" +subtitle = "About this site" ++++ + +Hi, My name is .... +``` + +The default date format is "%b %-d, %Y", e.g. "Dec 13, 2025", check [this page](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) if you want to customize it, for example change to "%Y-%-m-%-d", e.g. "2025-2-13". + +### Multiple list sections + +You can have more than one blog-like list section, e.g. a `series` section alongside `posts`. Just create `myblog/content/series/_index.md` the same way as the blog section (with `template = "posts.html"` and `page_template = "post.html"`), and add it to `nav` in `zola.toml`. Each list section has its own options in `[extra]` (`date_format`, `toc`, `categorized`, etc.), and can have its own feed by setting `generate_feeds = true` in its `_index.md` (available at e.g. `/series/feed.xml`). + +The `blog_section_path` option in `zola.toml` points to your *main* blog section, it is used by the recent posts list of the home page and by the tags pages (tags are site-wide: posts from all list sections that share a tag will be listed together). + +Now the myblog directory may look like this: + +``` +├── zola.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 `zola.toml` is the file name of the svg file. + +To customize, find the svg file you want, modify (in case you don't know, a svg file is just a plain 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 + +The `color_scheme` option in `zola.toml` controls the light/dark mode behavior: `"auto"` (default) follows the visitor's system preference and shows a theme toggle button, while `"light"` / `"dark"` locks the site to a single mode and hides the button. + +## RSS + +There are two ways to provide feeds: + +- **Per-section feeds** (recommended): set `generate_feeds = false` in `zola.toml`, and `generate_feeds = true` in the `_index.md` of your list sections. Each of these sections gets its own feed (e.g. `/posts/feed.xml`, `/series/feed.xml`), using the `title` and `description` of that section. The RSS button in the footer links to the feed of the section the current page belongs to. +- **A single site-wide feed**: set `generate_feeds = true` in `zola.toml`, and `generate_feeds = false` in section `_index.md` files. The feed is located in the root directory (e.g. `/feed.xml`), contains posts from all sections, and uses the `title` and `description` of `zola.toml`. The RSS button in the footer links to it on all pages. + +`feed_filenames` can be set to `["feed.xml"]` (serene's own atom template), or `["atom.xml"]` / `["rss.xml"]` (zola's built-in templates), corresponding to different feed standards. + +## Open Graph + +Each page has [Open Graph](https://ogp.me/) and Twitter Card meta tags (plus a canonical link), so links shared to social media and chat apps can show a rich preview card with title, description and image. + +Title and description come from the same sources as the page's `<title>` and meta description. The preview image is resolved as follows: + +- Post pages use `og_image` in the `[extra]` section of front matter, if set. It can be a full URL, a path in the `static` folder (starting with `/`), or the filename of a [colocated asset](https://www.getzola.org/documentation/content/overview/#asset-colocation) of the post. +- Otherwise, the site-wide default `og_image` in the `[extra]` section of `zola.toml` is used, if set. It can be a full URL or a path in the `static` folder, e.g. `og_image = "img/og.png"`. +- If neither is set, image related tags are omitted. + +An image around 1200x630 is recommended for the best display on most platforms. + +## 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, copy the file you want to change from `themes/serene`'s `templates` / `static` / `sass` directory to the same-named directory of `myblog`, and modify it there. 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 example, from [google fonts](https://fonts.google.com/)) into it, and then modify `--main-font` or `--code-font` in `myblog/templates/_custom_css.html`. For performance reasons, 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 `<style> </style>` 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 +code_copy = true +outdated_alert = true +outdated_alert_days = 120 +math = false +mermaid = false +featured = false +reaction = false +og_image = "cover.png" ++++ + +new post about something... +``` + +Display options follow a unified fallback chain: **post front-matter → the post's section `_index.md` → `[extra]` of `zola.toml`**, the closest one wins. This applies to `toc`, `code_copy`, `comment`, `math`, `mermaid`, `reaction`, `outdated_alert` and `outdated_alert_days` (`date_format` and `outdated_alert_text_before/after` follow a section → config chain). So you can set site-wide defaults in `zola.toml`, override them per section, and override again per post. + +If you set `categorized = true`, posts are grouped by category, and categories are 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 the 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. + +## Outdated Alert + +If one of your posts has strong timeliness, you can display an outdated alert after certain days. + +Set `outdated_alert` and `outdated_alert_days` to enable the alert. + +Options `outdated_alert_text_before` and `outdated_alert_text_after` are the text content of the alert, they can be set in `[extra]` of `zola.toml`, or per section in its `_index.md`. + +## 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://<your-domain-name>/giscus_light.css`, replace `<your-domain-name>` with you domain name, same as `base_url` in `zola.toml`, if you set `color_scheme` 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 convenience, you can use one template repo 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 = "<your-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). + +## Callouts + +Callouts use the [GitHub alert syntax](https://github.com/orgs/community/discussions/16925), there are 5 types: `NOTE` `TIP` `IMPORTANT` `WARNING` `CAUTION`: + +```md +> [!NOTE] +> note text +``` + +Serene styles them with an icon and a title. The title texts default to "Note" / "Tip" / "Important" / "Warning" / "Caution", you can change them (e.g. for a non-English site) by setting css variables in your `_custom_css.html`: + +```css +--callout-note-title: "注意"; +--callout-tip-title: "提示"; +--callout-important-title: "重要"; +--callout-warning-title: "警告"; +--callout-caution-title: "当心"; +``` + +## Components + +Since zola `0.23`, your markdown content is itself a [Tera](https://keats.github.io/tera/) template, and shortcodes were replaced by [Tera components](https://www.getzola.org/documentation/content/overview/#templating-your-content). Serene provides some built-in components. + +Note that component arguments other than strings are wrapped in `{...}`, e.g. `autoplay={true}`. + +- Use `figure` to add caption or width/height to an image, `alt` `caption` `width` `height` are all optional (`width` and `height` take strings): + + ```md + {{ <figure src="/path/to/img" alt="alt text" caption="caption text" width="600" height="400" /> }} + ``` + + If `src` is the filename of a [colocated asset](https://www.getzola.org/documentation/content/overview/#asset-colocation), pass `page` (or `section` when used in a section's `_index.md`) so the image URL can be resolved: + + ```md + {{ <figure src="colocated-img.png" caption="caption text" page /> }} + ``` + + The caption is parsed as markdown so you can use bold / italic / link, for example `caption="[via](https://example.com)"` + + Adding height to an image is always recommended, as this can avoid page layout shift. When you use ``, browser cannot determine the image's dimensions before it loads. + +- Use `quote` to display a special quote block, `cite` is optional: + + ```md + {% <quote cite=""> %} + // content... + {% </quote> %} + ``` + +- Use `detail` to add an expandable detail block, `default_open` is optional: + + ```md + {% <detail title="" default_open={false}> %} + // content... + {% </detail> %} + ``` + +- 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] + {% </mermaid> %} + ``` + +- Use `youtube` to embed a youtube video, `autoplay` is optional, default to `false`: + + ```md + {{ <youtube id="<youtube-video-id>" autoplay={true} /> }} + ``` + +Since your markdown content is now a Tera template, if you want to write literal `{{` or `{%` in your content (e.g. in a code block), wrap it with `{% raw %}` and `{% endraw %}`. + +Note that component calls must be at the top level of your content — don't nest them inside a list item, as the component's HTML output would break the list's indentation rules and produce broken HTML. + +## Collection + +This theme has a special component for creating a collection of items. Collections can be used to showcase various types of lists, such as projects, publications, blogroll, bookmarks, books, etc. Check [this page](https://serene-demo.pages.dev/collections) on demo site to see some examples. + +A collection is described by a toml file. Two collection-level options decide how it looks: + +- `layout`: the appearance of each item + - `card`: rich block item, with title / subtitle / content / tags etc. + - `row`: compact one-line item + - `tile`: small bordered block, text on the left and a 48x48 image on the right + - `gallery`: item with a poster image +- `flow`: how items are arranged in the container + - `stack`: vertically stacked, one item per line (default for `card` and `gallery`); a stacked `row` takes the full width with its `badge` aligned to the right + - `inline`: items take their content width and wrap horizontally (default for `row` and `tile`) + - `grid`: an even grid, the number of columns adapts to the available width automatically + +Not every combination makes sense: `card` doesn't work with `flow = "inline"` (it is treated as `grid`), and an unknown `layout` / `flow` value falls back to the default. + +All layouts share the same set of item fields, every field except `title` is optional and simply omitted from rendering when absent. Each layout renders the fields that fit its density and ignores the rest: + +| field | card | row | tile | gallery | +| ---------- | ------------ | ---- | --------- | -------- | +| `title` | ✓ | ✓ | ✓ | ✓ | +| `subtitle` | ✓ | ✓ | ✓ | ✓ | +| `content` | ✓ | - | - | ✓ | +| `icon` | ✓ | ✓ | - | - | +| `image` | ✓ (left) | - | ✓ (48x48) | ✓ (poster) | +| `link` | title | title | whole item | title | +| `badge` | ✓ (right) | ✓ | - | ✓ (bottom) | +| `tags` | ✓ | - | - | - | +| `featured` | ✓ | ✓ | - | - | +| `rotate` | - | - | ✓ | - | + +```toml +layout = "card" # "card" | "row" | "tile" | "gallery" +# flow = "grid" # "stack" | "inline" | "grid" + +[[item]] +title = "Title" +subtitle = "Subtitle" # supports inline markdown +content = "Content" # supports markdown +icon = "https://example.com/icon.png" # a small 16x16 icon shown before the title (card and row layouts) +image = "https://example.com/image.png" # a picture: shown on the left for card, as the 48x48 block for tile, as the poster for gallery +link = "https://example.com" # makes the title clickable (or the whole item for tile), external links open in a new tab automatically +# `icon` and `image` can be a full URL, a path in `static` (starting with `/`), or a file colocated with the section +badge = "2025" # a short mark rendered as-is: a year, a date range, a rating, a status... +tags = ["tag1", "tag2"] +featured = true # show an asterisk mark (card and row layouts) +rotate = true # playfully rotate the image (tile layout) +``` + +Note that `gallery` changes its look with the flow: `stack` puts the poster on the left with text on the right, while `grid` / `inline` puts the poster on top with text centered below. + +For example, to create a "projects" section page: + +1. Create `myblog/content/projects/projects.toml`: + + ```toml + layout = "card" + + [[item]] + title = "Tokio" + link = "https://example.com" + content = "Tokio is an asynchronous runtime for the Rust programming language." + tags = ["rust", "async", "runtime"] + + [[item]] + title = "Kubernetes" + link = "https://example.com" + content = "Kubernetes, also known as K8s, is an open source system for managing containerized applications." + tags = ["k8s", "golang"] + ``` + +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" section /> }} + ``` + +3. Add projects section in `nav` of `zola.toml` + + ```toml + nav = [ + # ... + { name = "projects", path = "/projects" }, + ] + ``` + +A page can have multiple collections: put several toml files in the section directory and call the component for each, with markdown headings in between to group them. + +## 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 `zola.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/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..79eb9ac --- /dev/null +++ b/src/themes/serene/sass/main.scss @@ -0,0 +1,1857 @@ +/* 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: 6px; + 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%; + height: auto; + 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; + } + + &[class*="markdown-alert-"] { + margin: 1.5em 0; + display: grid; + grid-template-columns: 18px 1fr; + column-gap: 0.5em; + + &::before { + // the icon, colored via mask so it follows currentColor + content: ""; + grid-row: 1; + grid-column: 1; + align-self: center; + width: 18px; + height: 18px; + background-color: currentColor; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + } + + &::after { + // the title + grid-row: 1; + grid-column: 2; + align-self: center; + font-weight: 600; + } + + > * { + grid-column: 1 / -1; + } + + > p:first-of-type { + margin-top: 0.5em; + } + > p:last-of-type { + margin-bottom: 0; + } + } + + &.markdown-alert-note { + color: var(--callout-note-color); + border-color: var(--callout-note-color); + &::before { + mask-image: url("icon/note.svg"); + -webkit-mask-image: url("icon/note.svg"); + } + &::after { + content: var(--callout-note-title, "Note"); + } + } + + &.markdown-alert-tip { + color: var(--callout-tip-color); + border-color: var(--callout-tip-color); + &::before { + mask-image: url("icon/tip.svg"); + -webkit-mask-image: url("icon/tip.svg"); + } + &::after { + content: var(--callout-tip-title, "Tip"); + } + } + + &.markdown-alert-important { + color: var(--callout-important-color); + border-color: var(--callout-important-color); + &::before { + mask-image: url("icon/important.svg"); + -webkit-mask-image: url("icon/important.svg"); + } + &::after { + content: var(--callout-important-title, "Important"); + } + } + + &.markdown-alert-warning { + color: var(--callout-warning-color); + border-color: var(--callout-warning-color); + &::before { + mask-image: url("icon/warning.svg"); + -webkit-mask-image: url("icon/warning.svg"); + } + &::after { + content: var(--callout-warning-title, "Warning"); + } + } + + &.markdown-alert-caution { + color: var(--callout-caution-color); + border-color: var(--callout-caution-color); + &::before { + mask-image: url("icon/caution.svg"); + -webkit-mask-image: url("icon/caution.svg"); + } + &::after { + content: var(--callout-caution-title, "Caution"); + } + } + } + + ol, ul { + padding-left: 1.4em; + } + + li { + margin: 0.5em 0; + p { + margin: 0.5em 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; + border: 1.5px solid var(--primary-color); + } + + 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.85em; + 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); + } + + .giallo-ln { + padding-right: 12px; + user-select: none; + } + + &.z-l-code, + &.z-d-code { + background-color: var(--bg-color); + } + + &.mermaid { + padding-right: 16px; + border: none; + svg { + display: block; + margin: 0 auto; + } + } + } + + .codeblock { + margin: 1.5em 0; + position: relative; + overflow: auto; + + pre { + margin: 0; + } + code[data-name]::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); + } + pre:has(code[data-name]) { + padding-top: calc(36px + 1em * var(--line-height)); + } + + .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; + } + } + } + + .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; + } + + .youtube { + position: relative; + height: 0; + padding-bottom: 56.25%; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } + } + + + input { + color-scheme: light; + } +} + +body.dark .prose { + img { + filter: brightness(var(--dark-mode-img-brightness)); + } + .mermaid { + filter: brightness(var(--dark-mode-chart-brightness)); + } + input { + color-scheme: dark; + } +} + +/* 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 { + margin: 1.5em 0; + + &.flow-inline { + display: flex; + flex-wrap: wrap; + align-items: center; + } + + &.flow-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); + gap: 1.5em; + } + + a { + -webkit-tap-highlight-color: transparent; + } + + .image img { + margin: 0; + } + + span.title { + color: var(--text-color); + } + + a.title { + border-bottom: none; + > span { + border-bottom: 1.5px solid var(--primary-color); + } + } + + .subtitle, + .badge { + font-size: 0.9em; + color: var(--text-pale-color); + } + } + + /* card: rich block item */ + .layout-card { + .item { + margin: 3em 0; + position: relative; + display: flex; + align-items: center; + gap: 1.5em; + + > .image { + flex-shrink: 0; + width: 160px; + img { + width: 100%; + height: auto; + border-radius: var(--img-border-radius); + } + } + + @media (max-width: 425px) { + flex-direction: column; + align-items: stretch; + gap: 1em; + + > .image { + width: 100%; + } + } + + .body { + flex: 1; + min-width: 0; + } + + &.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 { + height: calc(1em * var(--line-height)); + display: flex; + align-items: center; + flex-shrink: 0; + img { + height: 16px; + width: 16px; + } + } + .badge { + margin-left: auto; + flex-shrink: 0; + } + } + + .subtitle { + display: block; + 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); + + .tag span { + font-size: 0.9em; + margin-right: 4px; + } + } + } + + &.flow-grid .item, + &.flow-inline .item { + margin: 0; + } + &.flow-grid { + grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); + } + } + + /* row: compact one-line item */ + .layout-row { + &.flow-inline { + gap: 0.5em 1.5em; + } + + .item { + display: inline-flex; + width: fit-content; + max-width: 100%; + gap: 0.5em; + padding: 4px 0; + color: var(--text-color); + 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%); + } + } + + .icon { + flex-shrink: 0; + display: flex; + align-items: center; + height: calc(1em * var(--line-height)); + img { + width: 16px; + height: 16px; + } + } + + .main { + display: inline-flex; + gap: 0.5em; + min-width: 0; + } + + .main:has(+ .subtitle) { + margin-right: 0.5em; + } + } + + &.flow-stack .item { + display: flex; + width: auto; + margin: 0.25em 0; + + .badge { + margin-left: auto; + padding-left: 1em; + flex-shrink: 0; + } + } + + @media (max-width: 425px) { + .item:has(.subtitle) { + display: flex; + width: 100%; + flex-wrap: wrap; + } + + .item:has(.badge) .subtitle { + order: 10; + flex-basis: 100%; + } + } + + } + + /* tile: small bordered block, text left + image right */ + .layout-tile { + &.flow-inline { + gap: 0.8em 0.5em; + } + + .item { + max-width: 100%; + display: inline-flex; + align-items: center; + justify-content: space-between; + border: 1px solid var(--primary-color); + overflow: hidden; + text-decoration: none; + + img { + width: 48px; + height: 48px; + margin: 0; + &.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; + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; + } + } + + @media (hover: hover) { + &:hover { + opacity: unset; + img.rotate { + transform: rotate(0deg) translate(0px, 0px); + } + } + } + } + + a.item { + @media (hover: hover) { + &:hover { + box-shadow: 0 0 0 1px var(--primary-color); + } + } + } + + &.flow-stack .item { + display: flex; + width: 100%; + margin: 0.5em 0; + } + + @media (max-width: 425px) { + .item { + display: flex; + width: 100%; + } + } + } + + /* gallery: item with a poster image */ + .layout-gallery { + --poster-h: 160px; + --poster-w: 115px; + + &.flow-grid { + grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); + } + + a.title { + width: fit-content; + } + + .image { + flex-shrink: 0; + width: var(--poster-w); + height: var(--poster-h); + img { + height: 100%; + width: 100%; + object-fit: contain; + } + } + + .content { + font-size: 0.9em; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + p { + margin: 0; + } + } + + .subtitle, + .badge { + font-size: 0.8em; + } + + /* stack: poster left, text right */ + &.flow-stack .item { + margin: 2em 0; + display: flex; + gap: 1.5em; + + .text { + height: var(--poster-h); + display: flex; + flex-direction: column; + gap: 3px; + } + .badge { + margin-top: auto; + } + + @media (max-width: 425px) { + --poster-h: 120px; + --poster-w: 85px; + gap: 0.75em; + .text { + gap: 2px; + } + .subtitle, + .badge { + font-size: 0.7em; + } + .content { + font-size: 0.8em; + -webkit-line-clamp: 2; + } + } + } + + /* grid / inline: poster on top, text centered below */ + &.flow-grid .item, + &.flow-inline .item { + display: flex; + flex-direction: column; + align-items: center; + gap: 1em; + + .title { + font-size: 0.9em; + } + .text { + max-width: calc(var(--poster-w) * 1.5); + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 3px; + } + + @media (max-width: 425px) { + --poster-h: 120px; + --poster-w: 85px; + } + } + &.flow-inline { + gap: 1.5em 2em; + align-items: start; + } + } +} + +/* 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: 2em; + padding: 0.25em 0px; + /* margin: 0.25em 0; */ + 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: translate(-200%, 6px); + height: calc(var(--line-height) * 1em); + } + @media (max-width: 768px) { + &.featured::before { + transform: translate(-150%, 6px); + } + } + + @media (max-width: 425px) { + flex-direction: column-reverse; + align-items: start; + gap: 0.25em; + margin: 1em 0; + .date { + margin-left: initial; + color: var(--text-pale-color); + font-size: 0.8em; + } + &.featured::before { + transform: translate(-150%, 115%); + } + } + } + + .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; + } + + img + #text { + gap: 0.75em; + } + + #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; + + &.external { + cursor: ne-resize; + } + } + } + + #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 { + &.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; + } + + background: + linear-gradient(var(--bg-color) 50%, transparent) center top, + linear-gradient(transparent, var(--bg-color) 50%) center bottom, + radial-gradient(farthest-side at 50% 0, var(--primary-decoration-color), var(--bg-color)) center top, + radial-gradient(farthest-side at 50% 100%, var(--primary-decoration-color), var(--bg-color)) center bottom; + background-repeat: no-repeat; + background-size: 100% 4em, 100% 4em, 100% 1.5em, 100% 1.5em; + background-attachment: local, local, scroll, scroll; + } + + 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 { + margin-left: 1.5em; + border-left: 1px solid var(--text-decoration-color); + } + + &:hover { + 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 Binary files differnew file mode 100644 index 0000000..2856540 --- /dev/null +++ b/src/themes/serene/screenshot.png 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M4.90727 3.43929C5.61594 3.56009 6.44016 3.94139 7.48302 4.68685C9.27977 5.97119 10.7687 7.73907 12 9.56361C13.2313 7.73907 14.7202 5.97119 16.517 4.68685C17.5598 3.94139 18.3841 3.56009 19.0927 3.43929C19.8605 3.3084 20.3825 3.50358 20.7082 3.63931C21.7166 4.05956 22 5.22508 22 6.21461C22 6.41649 21.9144 7.5166 21.8148 8.57508C21.7634 9.12088 21.7057 9.68306 21.6486 10.1515C21.5963 10.5804 21.5337 11.0321 21.4587 11.2849C21.1161 12.4395 20.3965 13.2618 19.508 13.8021C20.4453 14.5092 20.7854 15.6583 20.4359 16.7856C19.8393 18.71 17.6991 21.1833 15.6005 21.4037C13.8281 21.5898 12.6662 20.0794 12 18.6449C11.3338 20.0794 10.1719 21.5898 8.39954 21.4037C6.30095 21.1833 4.1607 18.71 3.56408 16.7856C3.21457 15.6583 3.55466 14.5092 4.49197 13.8021C3.60345 13.2618 2.88394 12.4395 2.54132 11.2849C2.46631 11.0321 2.40367 10.5804 2.35139 10.1515C2.29429 9.68306 2.23658 9.12088 2.18521 8.57508C2.08559 7.5166 2 6.41649 2 6.21461C2 5.22508 2.28343 4.05956 3.29182 3.63931C3.61753 3.50358 4.13949 3.3084 4.90727 3.43929ZM4.04911 6.91709C4.11331 7.73486 4.22889 9.02507 4.33669 9.90947C4.36927 10.1767 4.39214 10.4536 4.45863 10.7156C4.85637 12.056 6.38779 12.7978 8.14506 12.603C8.68686 12.5429 9.17695 12.9278 9.24697 13.4684C9.31698 14.009 8.94113 14.5061 8.40191 14.586C7.64608 14.6981 5.08656 14.9425 5.47438 16.1934C5.8312 17.3443 7.32212 19.2796 8.60842 19.4146C9.53606 19.5121 10.1084 18.0211 10.3741 17.3697C10.6489 16.6958 10.8622 15.9903 11.0417 15.3885C11.1681 14.9648 11.5578 14.6744 12 14.6744C12.4422 14.6744 12.8319 14.9648 12.9583 15.3885C13.1378 15.9903 13.3511 16.6958 13.6259 17.3697C13.8916 18.0211 14.4639 19.5121 15.3916 19.4146C16.6779 19.2796 18.1688 17.3443 18.5256 16.1934C18.9134 14.9425 16.3539 14.6981 15.5981 14.586C15.0589 14.5061 14.683 14.009 14.753 13.4684C14.8231 12.9278 15.3131 12.5429 15.8549 12.603C17.6122 12.7978 19.1436 12.0563 19.5413 10.7159C19.61 10.45 19.63 10.18 19.6633 9.90948C19.7711 9.02507 19.8867 7.73486 19.9509 6.91709C19.9876 6.44922 20.1985 5.27964 19.4288 5.41084C19.1429 5.45959 18.6059 5.65205 17.68 6.31391C15.7374 7.70252 13.9749 9.82666 12.891 11.954C12.7203 12.289 12.376 12.5 12 12.5C11.624 12.5 11.2797 12.289 11.109 11.954C10.0251 9.82666 8.26258 7.70252 6.31998 6.31391C5.39406 5.65205 4.85713 5.45959 4.57117 5.41084C3.7874 5.27724 4.01205 6.44504 4.04911 6.91709Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M4.00098 20V14C4.00098 9.58172 7.5827 6 12.001 6C16.4193 6 20.001 9.58172 20.001 14V20H21.001V22H3.00098V20H4.00098ZM6.00098 20H18.001V14C18.001 10.6863 15.3147 8 12.001 8C8.68727 8 6.00098 10.6863 6.00098 14V20ZM11.001 2H13.001V5H11.001V2ZM19.7792 4.80761L21.1934 6.22183L19.0721 8.34315L17.6578 6.92893L19.7792 4.80761ZM2.80859 6.22183L4.22281 4.80761L6.34413 6.92893L4.92991 8.34315L2.80859 6.22183ZM7.00098 14C7.00098 11.2386 9.23956 9 12.001 9V11C10.3441 11 9.00098 12.3431 9.00098 14H7.00098Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M10.0007 15.1709L19.1931 5.97852L20.6073 7.39273L10.0007 17.9993L3.63672 11.6354L5.05093 10.2212L10.0007 15.1709Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M6.9998 6V3C6.9998 2.44772 7.44752 2 7.9998 2H19.9998C20.5521 2 20.9998 2.44772 20.9998 3V17C20.9998 17.5523 20.5521 18 19.9998 18H16.9998V20.9991C16.9998 21.5519 16.5499 22 15.993 22H4.00666C3.45059 22 3 21.5554 3 20.9991L3.0026 7.00087C3.0027 6.44811 3.45264 6 4.00942 6H6.9998ZM5.00242 8L5.00019 20H14.9998V8H5.00242ZM8.9998 6H16.9998V16H18.9998V4H8.9998V6Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3ZM20 7.23792L12.0718 14.338L4 7.21594V19H20V7.23792ZM4.51146 5L12.0619 11.662L19.501 5H4.51146Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M14 19H19V5H5V19H12V14H10V12H12V10.3458C12 9.00855 12.1392 8.52362 12.4007 8.03473C12.6622 7.54584 13.0458 7.16216 13.5347 6.9007C13.9174 6.69604 14.3922 6.57252 15.2217 6.51954C15.551 6.49851 15.9771 6.52533 16.5 6.6V8.5H16C15.0827 8.5 14.7042 8.54332 14.4779 8.66433C14.3376 8.73939 14.2394 8.83758 14.1643 8.97793C14.0433 9.20418 14 9.42853 14 10.3458V12H16.5L16 14H14V19ZM4 3H20C20.5523 3 21 3.44772 21 4V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V4C3 3.44772 3.44772 3 4 3Z"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M17.0003 13V14C17.0003 16.7696 16.3364 19.445 15.0853 21.8455L14.8585 22.2663L13.1116 21.2924C14.2716 19.2115 14.9211 16.8817 14.9935 14.4559L15.0003 14V13H17.0003ZM11.0003 10H13.0003V14L12.9948 14.3787C12.9153 17.1495 11.9645 19.7731 10.3038 21.928L10.073 22.2189L8.52406 20.9536C10.0408 19.0969 10.9145 16.8017 10.9943 14.3663L11.0003 14V10ZM12.0003 6C14.7617 6 17.0003 8.23858 17.0003 11H15.0003C15.0003 9.34315 13.6571 8 12.0003 8C10.3434 8 9.00025 9.34315 9.00025 11V14C9.00025 16.2354 8.1806 18.3444 6.72928 19.9768L6.51767 20.2067L5.06955 18.8273C6.23328 17.6056 6.92099 16.0118 6.99381 14.3027L7.00025 14V11C7.00025 8.23858 9.23883 6 12.0003 6ZM12.0003 2C16.9708 2 21.0003 6.02944 21.0003 11V14C21.0003 15.6979 20.7985 17.3699 20.4035 18.9903L20.2647 19.5285L18.3349 19.0032C18.726 17.5662 18.9475 16.0808 18.9919 14.5684L19.0003 14V11C19.0003 7.13401 15.8662 4 12.0003 4C10.4279 4 8.97663 4.51841 7.80805 5.39364L6.38308 3.96769C7.92267 2.73631 9.87547 2 12.0003 2ZM4.96794 5.38282L6.39389 6.8078C5.5635 7.91652 5.0543 9.27971 5.00431 10.7593L4.99961 10.999L5.00378 13C5.00378 14.1195 4.73991 15.2026 4.24263 16.1772L4.08648 16.4663L2.34961 15.4747C2.72889 14.8103 2.95077 14.0681 2.99539 13.2924L3.00378 13L3.00361 11C3.00025 8.87522 3.73656 6.92242 4.96794 5.38282Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M5.88401 18.6531C5.58404 18.4523 5.32587 18.1972 5.0239 17.8366C4.91473 17.7062 4.47283 17.1521 4.55811 17.258C4.09533 16.6831 3.80296 16.4168 3.50156 16.3087C2.9817 16.1223 2.7114 15.5497 2.89784 15.0298C3.08428 14.51 3.65685 14.2397 4.17672 14.4261C4.92936 14.696 5.43847 15.1609 6.12425 16.0141C6.03025 15.8972 6.46364 16.4408 6.55731 16.5526C6.74784 16.7802 6.88732 16.918 6.99629 16.9909C7.20118 17.128 7.58451 17.1871 8.14709 17.1308C8.17065 16.7487 8.24136 16.378 8.34919 16.0355C5.38097 15.3101 3.70116 13.3949 3.70116 9.63947C3.70116 8.4006 4.0704 7.28368 4.75917 6.34756C4.5415 5.45368 4.57433 4.37259 5.06092 3.15611C5.1725 2.87715 5.40361 2.66314 5.69031 2.57328C5.77242 2.54949 5.81791 2.5389 5.89878 2.52648C6.70167 2.40319 7.83573 2.69681 9.31449 3.62311C10.181 3.41855 11.0885 3.31476 12.0012 3.31476C12.9129 3.31476 13.8196 3.41835 14.6854 3.62253C16.1619 2.68976 17.2986 2.39625 18.1072 2.52627C18.1919 2.53988 18.2645 2.55758 18.3249 2.57741C18.6059 2.66967 18.8316 2.88155 18.9414 3.15611C19.4279 4.37232 19.4608 5.45319 19.2433 6.34695C19.9342 7.28313 20.3012 8.39184 20.3012 9.63947C20.3012 13.3966 18.627 15.3046 15.6588 16.0318C15.7837 16.4467 15.8496 16.9103 15.8496 17.4118C15.8496 18.0763 15.8471 18.7108 15.8424 19.4223C15.8412 19.6124 15.8397 19.8156 15.8375 20.1279C16.2129 20.2107 16.5229 20.5074 16.6031 20.9086C16.7114 21.4502 16.3602 21.977 15.8186 22.0853C14.6794 22.3132 13.8353 21.5535 13.8353 20.5608C13.8353 20.4705 13.836 20.3414 13.8375 20.1142C13.8398 19.8012 13.8412 19.5987 13.8425 19.4092C13.8471 18.7017 13.8496 18.0714 13.8496 17.4118C13.8496 16.7145 13.6664 16.26 13.4237 16.0508C12.7627 15.481 13.0977 14.3971 13.965 14.2996C16.9314 13.9663 18.3012 12.8174 18.3012 9.63947C18.3012 8.68484 17.9893 7.89547 17.3881 7.23534C17.1301 6.95209 17.0567 6.54634 17.199 6.19062C17.3647 5.77639 17.4354 5.2336 17.2941 4.57678L17.2847 4.57944C16.7928 4.71861 16.1744 5.01956 15.4261 5.52826C15.182 5.69413 14.8772 5.74377 14.5932 5.66388C13.7729 5.43319 12.8913 5.31476 12.0012 5.31476C11.111 5.31476 10.2294 5.43319 9.40916 5.66388C9.12662 5.74335 8.82344 5.69468 8.57997 5.53077C7.8274 5.02414 7.2056 4.72355 6.71079 4.58352C6.56735 5.23672 6.63814 5.77758 6.80336 6.19062C6.94565 6.54634 6.87219 6.95209 6.61423 7.23534C6.01715 7.89096 5.70116 8.69352 5.70116 9.63947C5.70116 12.8114 7.07225 13.9681 10.023 14.2996C10.8883 14.3969 11.2246 15.4767 10.5675 16.048C10.3751 16.2153 10.1384 16.7799 10.1384 17.4118V20.5608C10.1384 21.5472 9.30356 22.2866 8.17878 22.0898C7.63476 21.9946 7.27093 21.4764 7.36613 20.9324C7.43827 20.5201 7.75331 20.2114 8.13841 20.1274V19.1379C7.22829 19.1991 6.47656 19.0496 5.88401 18.6531Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12.001 9C10.3436 9 9.00098 10.3431 9.00098 12C9.00098 13.6573 10.3441 15 12.001 15C13.6583 15 15.001 13.6569 15.001 12C15.001 10.3427 13.6579 9 12.001 9ZM12.001 7C14.7614 7 17.001 9.2371 17.001 12C17.001 14.7605 14.7639 17 12.001 17C9.24051 17 7.00098 14.7629 7.00098 12C7.00098 9.23953 9.23808 7 12.001 7ZM18.501 6.74915C18.501 7.43926 17.9402 7.99917 17.251 7.99917C16.5609 7.99917 16.001 7.4384 16.001 6.74915C16.001 6.0599 16.5617 5.5 17.251 5.5C17.9393 5.49913 18.501 6.0599 18.501 6.74915ZM12.001 4C9.5265 4 9.12318 4.00655 7.97227 4.0578C7.18815 4.09461 6.66253 4.20007 6.17416 4.38967C5.74016 4.55799 5.42709 4.75898 5.09352 5.09255C4.75867 5.4274 4.55804 5.73963 4.3904 6.17383C4.20036 6.66332 4.09493 7.18811 4.05878 7.97115C4.00703 9.0752 4.00098 9.46105 4.00098 12C4.00098 14.4745 4.00753 14.8778 4.05877 16.0286C4.0956 16.8124 4.2012 17.3388 4.39034 17.826C4.5591 18.2606 4.7605 18.5744 5.09246 18.9064C5.42863 19.2421 5.74179 19.4434 6.17187 19.6094C6.66619 19.8005 7.19148 19.9061 7.97212 19.9422C9.07618 19.9939 9.46203 20 12.001 20C14.4755 20 14.8788 19.9934 16.0296 19.9422C16.8117 19.9055 17.3385 19.7996 17.827 19.6106C18.2604 19.4423 18.5752 19.2402 18.9074 18.9085C19.2436 18.5718 19.4445 18.2594 19.6107 17.8283C19.8013 17.3358 19.9071 16.8098 19.9432 16.0289C19.9949 14.9248 20.001 14.5389 20.001 12C20.001 9.52552 19.9944 9.12221 19.9432 7.97137C19.9064 7.18906 19.8005 6.66149 19.6113 6.17318C19.4434 5.74038 19.2417 5.42635 18.9084 5.09255C18.573 4.75715 18.2616 4.55693 17.8271 4.38942C17.338 4.19954 16.8124 4.09396 16.0298 4.05781C14.9258 4.00605 14.5399 4 12.001 4ZM12.001 2C14.7176 2 15.0568 2.01 16.1235 2.06C17.1876 2.10917 17.9135 2.2775 18.551 2.525C19.2101 2.77917 19.7668 3.1225 20.3226 3.67833C20.8776 4.23417 21.221 4.7925 21.476 5.45C21.7226 6.08667 21.891 6.81333 21.941 7.8775C21.9885 8.94417 22.001 9.28333 22.001 12C22.001 14.7167 21.991 15.0558 21.941 16.1225C21.8918 17.1867 21.7226 17.9125 21.476 18.55C21.2218 19.2092 20.8776 19.7658 20.3226 20.3217C19.7668 20.8767 19.2076 21.22 18.551 21.475C17.9135 21.7217 17.1876 21.89 16.1235 21.94C15.0568 21.9875 14.7176 22 12.001 22C9.28431 22 8.94514 21.99 7.87848 21.94C6.81431 21.8908 6.08931 21.7217 5.45098 21.475C4.79264 21.2208 4.23514 20.8767 3.67931 20.3217C3.12348 19.7658 2.78098 19.2067 2.52598 18.55C2.27848 17.9125 2.11098 17.1867 2.06098 16.1225C2.01348 15.0558 2.00098 14.7167 2.00098 12C2.00098 9.28333 2.01098 8.94417 2.06098 7.8775C2.11014 6.8125 2.27848 6.0875 2.52598 5.45C2.78014 4.79167 3.12348 4.23417 3.67931 3.67833C4.23514 3.1225 4.79348 2.78 5.45098 2.525C6.08848 2.2775 6.81348 2.11 7.87848 2.06C8.94514 2.0125 9.28431 2 12.001 2Z"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M4.00098 3H20.001C20.5533 3 21.001 3.44772 21.001 4V20C21.001 20.5523 20.5533 21 20.001 21H4.00098C3.44869 21 3.00098 20.5523 3.00098 20V4C3.00098 3.44772 3.44869 3 4.00098 3ZM5.00098 5V19H19.001V5H5.00098ZM7.50098 9C6.67255 9 6.00098 8.32843 6.00098 7.5C6.00098 6.67157 6.67255 6 7.50098 6C8.3294 6 9.00098 6.67157 9.00098 7.5C9.00098 8.32843 8.3294 9 7.50098 9ZM6.50098 10H8.50098V17.5H6.50098V10ZM12.001 10.4295C12.5854 9.86534 13.2665 9.5 14.001 9.5C16.072 9.5 17.501 11.1789 17.501 13.25V17.5H15.501V13.25C15.501 12.2835 14.7175 11.5 13.751 11.5C12.7845 11.5 12.001 12.2835 12.001 13.25V17.5H10.001V10H12.001V10.4295Z"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3.019 12.0075C2.98744 10.7478 3.00692 9.5598 3.00692 8.56644C3.00692 4.22767 5.84954 2.95597 5.84954 2.95597C7.28286 2.29767 9.74238 2.0209 12.2993 2H12.3621C14.919 2.0209 17.3801 2.29767 18.8134 2.95597C18.8134 2.95597 21.656 4.22767 21.656 8.56644C21.656 8.56644 21.6916 11.7674 21.2596 13.9898C20.9852 15.4007 18.8034 16.9446 16.2974 17.2438C14.9906 17.3999 13.7042 17.5431 12.3322 17.4802C10.0885 17.3775 8.31815 16.9446 8.31815 16.9446C8.31815 17.1631 8.33166 17.3711 8.35853 17.5655C8.44182 18.1978 8.65659 18.6604 8.96296 19C9.72944 19.8497 11.0692 19.9301 12.3577 19.9743C14.178 20.0366 15.7986 19.5254 15.7986 19.5254L15.8735 21.1712C15.8735 21.1712 14.6003 21.8548 12.3322 21.9805C11.0815 22.0493 9.52858 21.9491 7.71969 21.4704C6.18802 21.065 5.15153 20.1804 4.45091 19C3.35714 17.1573 3.08191 14.5938 3.019 12.0075ZM6.31815 16.9446V14.3967L8.79316 15.0018C8.8405 15.0134 8.95098 15.0383 9.11692 15.0723C9.40521 15.1313 9.73416 15.1908 10.0959 15.2467C10.8485 15.3628 11.6341 15.4462 12.4237 15.4823C13.4425 15.529 14.3249 15.4652 16.0603 15.2579C17.7233 15.0594 19.208 14.0622 19.2963 13.6082C19.3783 13.1861 19.4472 12.6858 19.5021 12.1261C19.5714 11.4205 19.6155 10.6558 19.6388 9.88068C19.654 9.37026 19.6582 8.93648 19.6564 8.62452L19.656 8.56644C19.656 7.1368 19.2873 6.12756 18.6928 5.40793C18.5008 5.17553 18.3004 4.99408 18.1087 4.85958C18.0183 4.79617 17.9737 4.77136 17.9787 4.77345C16.9662 4.30844 14.8859 4.02069 12.3621 3.99993H12.3156C9.77596 4.02069 7.6969 4.30836 6.66627 4.78161C6.68919 4.77136 6.64459 4.79617 6.55423 4.85958C6.36257 4.99408 6.16214 5.17553 5.97016 5.40793C5.37568 6.12756 5.00692 7.1368 5.00692 8.56644C5.00692 8.7976 5.00628 8.96339 5.00392 9.44137C4.9981 10.6238 5.00004 11.2256 5.01841 11.9589C5.07185 14.156 5.2822 15.7941 5.71797 17C5.93023 17.5874 6.19005 18.0709 6.49741 18.4507C6.37791 18.0162 6.31815 17.5142 6.31815 16.9446ZM8.08576 6.37135C8.71735 6.37135 9.22924 6.88324 9.22924 7.51482C9.22924 8.14626 8.71735 8.6583 8.08576 8.6583C7.45432 8.6583 6.94229 8.14626 6.94229 7.51482C6.94229 6.88324 7.45432 6.37135 8.08576 6.37135Z"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 7H13V9H11V7ZM11 11H13V17H11V11Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM13 13.3551V14H11V12.5C11 11.9477 11.4477 11.5 12 11.5C12.8284 11.5 13.5 10.8284 13.5 10C13.5 9.17157 12.8284 8.5 12 8.5C11.2723 8.5 10.6656 9.01823 10.5288 9.70577L8.56731 9.31346C8.88637 7.70919 10.302 6.5 12 6.5C13.933 6.5 15.5 8.067 15.5 10C15.5 11.5855 14.4457 12.9248 13 13.3551Z" fill="currentColor"></path></svg> 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 @@ +<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="796 698 200 200"> +<g> + <path d="M885.208,749.739v-40.948C836.019,708.791,796,748.81,796,798v89.209h89.208V798h-48.26 + C836.948,771.39,858.598,749.739,885.208,749.739z"/> + <path d="M996,749.739v-40.948c-49.19,0-89.209,40.019-89.209,89.209v89.209H996V798h-48.26 + C947.74,771.39,969.39,749.739,996,749.739z"/> +</g> +</svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12.1835 1.41016L12.1822 1.41016C9.09012 1.43158 6.70036 2.47326 5.09369 4.51569C3.66581 6.33087 2.93472 8.86436 2.91016 12.0068V12.0082C2.93472 15.1508 3.66586 17.6696 5.09369 19.4847C6.70043 21.5271 9.10257 22.5688 12.1946 22.5902H12.1958C14.944 22.5711 16.8929 21.8504 18.4985 20.2463C20.6034 18.1434 20.5408 15.5048 19.8456 13.8832C19.3163 12.6493 18.2709 11.6618 16.8701 11.0477C16.6891 8.06345 15.0097 6.32178 12.2496 6.30415C10.6191 6.29409 9.14792 7.02378 8.24685 8.39104L9.90238 9.5267C10.4353 8.71818 11.2789 8.32815 12.2371 8.33701C13.6244 8.34586 14.5362 9.11128 14.7921 10.4541C14.02 10.3333 13.1902 10.2982 12.3076 10.3488C9.66843 10.5008 7.9399 12.061 8.05516 14.2244C8.17571 16.4862 10.367 17.7186 12.4476 17.605C14.9399 17.4684 16.4209 15.6292 16.7722 13.2836C17.3493 13.6575 17.7751 14.1344 18.0163 14.6969C18.4559 15.7222 18.4838 17.4132 17.1006 18.7952C15.8838 20.0108 14.4211 20.5407 12.1891 20.5572C9.71428 20.5388 7.85698 19.746 6.65154 18.2136C5.51973 16.7748 4.92843 14.6882 4.90627 12.0002C4.92843 9.31211 5.51973 7.22549 6.65154 5.78673C7.85698 4.25433 9.71424 3.46156 12.189 3.44303C14.6819 3.4617 16.5728 4.25837 17.8254 5.79937C18.5162 6.64934 18.949 7.66539 19.2379 8.71407L21.1776 8.19656C20.8148 6.85917 20.2414 5.58371 19.363 4.50305C17.7098 2.46918 15.2816 1.43166 12.1835 1.41016ZM12.4204 12.3782C13.3044 12.3272 14.1239 12.3834 14.8521 12.5345C14.7114 14.1116 14.0589 15.4806 12.3401 15.575C11.2282 15.6376 10.1031 15.1413 10.0484 14.114C10.0077 13.3503 10.5726 12.4847 12.4204 12.3782Z"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M9.97308 18H11V13H13V18H14.0269C14.1589 16.7984 14.7721 15.8065 15.7676 14.7226C15.8797 14.6006 16.5988 13.8564 16.6841 13.7501C17.5318 12.6931 18 11.385 18 10C18 6.68629 15.3137 4 12 4C8.68629 4 6 6.68629 6 10C6 11.3843 6.46774 12.6917 7.31462 13.7484C7.40004 13.855 8.12081 14.6012 8.23154 14.7218C9.22766 15.8064 9.84103 16.7984 9.97308 18ZM10 20V21H14V20H10ZM5.75395 14.9992C4.65645 13.6297 4 11.8915 4 10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10C20 11.8925 19.3428 13.6315 18.2443 15.0014C17.624 15.7748 16 17 16 18.5V21C16 22.1046 15.1046 23 14 23H10C8.89543 23 8 22.1046 8 21V18.5C8 17 6.37458 15.7736 5.75395 14.9992Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M15.3499 5.5498C13.7681 5.5498 12.4786 6.81785 12.4504 8.39633L12.4223 9.97138C12.4164 10.3027 12.143 10.5665 11.8117 10.5606C11.7881 10.5602 11.7646 10.5584 11.7413 10.5552L10.1805 10.3423C8.12699 10.0623 6.15883 9.11711 4.27072 7.54387C3.67275 10.8535 4.84 13.147 7.65342 14.9157L9.40041 16.014C9.68095 16.1904 9.7654 16.5608 9.58903 16.8413C9.54861 16.9056 9.49636 16.9616 9.43504 17.0064L7.84338 18.1693C8.78973 18.2288 9.68938 18.1873 10.435 18.0385C15.1526 17.097 18.2897 13.5468 18.2897 7.69084C18.2897 7.21275 17.2774 5.5498 15.3499 5.5498ZM10.4507 8.36066C10.4983 5.69559 12.6735 3.5498 15.3499 3.5498C16.7132 3.5498 17.9465 4.10658 18.8348 5.00515C19.5462 4.9998 20.1514 5.17966 21.5035 4.35943C21.1693 5.9998 21.0034 6.71177 20.2897 7.69084C20.2897 15.3324 15.5926 19.0487 10.8264 19.9998C7.5587 20.6519 2.80646 19.5812 1.44531 18.1584C2.13874 18.1051 4.95928 17.8018 6.58895 16.6089C5.20994 15.6984 -0.278631 12.4679 3.32772 3.78617C5.02119 5.76283 6.73797 7.10831 8.47807 7.82262C9.63548 8.29774 9.91978 8.28825 10.4507 8.36066Z" fill="currentColor"></path></svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path d="M12.865 3.00017L22.3912 19.5002C22.6674 19.9785 22.5035 20.5901 22.0252 20.8662C21.8732 20.954 21.7008 21.0002 21.5252 21.0002H2.47266C1.92037 21.0002 1.47266 20.5525 1.47266 20.0002C1.47266 19.8246 1.51886 19.6522 1.60663 19.5002L11.1329 3.00017C11.4091 2.52187 12.0206 2.358 12.4989 2.63414C12.651 2.72191 12.7772 2.84815 12.865 3.00017ZM4.20471 19.0002H19.7932L11.9989 5.50017L4.20471 19.0002ZM10.9989 16.0002H12.9989V18.0002H10.9989V16.0002ZM10.9989 9.00017H12.9989V14.0002H10.9989V9.00017Z" fill="currentColor"></path></svg> 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..df2ef33 --- /dev/null +++ b/src/themes/serene/static/js/main.js @@ -0,0 +1,271 @@ +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 = `/giallo-${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(); +} + +function enableBackLink() { + const backLink = document.querySelector('#back-link'); + if (!backLink) return; + backLink.addEventListener('click', (e) => { + if (document.referrer && location.href.startsWith(document.referrer) && !location.hash && history.length > 1) { + e.preventDefault(); + history.back(); + } + }); +} + +enableThemeToggle(); +enablePrerender(); +enableRssMask(); +enableBackLink(); +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 %} +<div class="wrapper"> + <p class="error"> + {{ config.extra.not_found_error_text }} + </p> + <a class="instant" href="{{ get_url(path='/') }}">{{ config.extra.not_found_recover_text }}</a> +</div> +{% endblock %} diff --git a/src/themes/serene/templates/_base.html b/src/themes/serene/templates/_base.html new file mode 100644 index 0000000..d1f2d48 --- /dev/null +++ b/src/themes/serene/templates/_base.html @@ -0,0 +1,39 @@ +{% set color_scheme = config.extra.color_scheme if config.extra.color_scheme is defined else "auto" %} +<!DOCTYPE html> + +<html lang="{% block lang %}{% endblock lang %}"> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="color-scheme" content="light dark"> + {% block desc %}{% endblock desc %} + <title>{% block title %}{% endblock title %}</title> + <link rel="icon" type="image/png" sizes="32x32" href="{{ get_url(path='img/favicon-32x32.png') }}"> + <link rel="icon" type="image/png" sizes="16x16" href="{{ get_url(path='img/favicon-16x16.png') }}"> + <link rel="apple-touch-icon" sizes="180x180" href="{{ get_url(path='img/apple-touch-icon.png') }}"> + {% include "_custom_font.html" %} + {% include "_custom_css.html" %} + <link rel="stylesheet" href="{{ get_url(path='main.css') }}"> + {% block head %}{% endblock head %} + {% include "_head_extend.html" %} +</head> + +<body class="{% block page %}{% endblock page%}{% if color_scheme == "dark" %} dark{% endif %}"> + {% if color_scheme == "auto" %} + <script> + const theme = sessionStorage.getItem('theme'); + const match = window.matchMedia("(prefers-color-scheme: dark)").matches; + if ((theme && theme == 'dark') || (!theme && match)) { + document.body.classList.add('dark'); + const hl = document.querySelector('link#hl'); + if (hl) hl.href = '{{ get_url(path="giallo-dark.css") }}'; + } + </script> + {% endif %} + {% block content %}{% endblock content %} + {% block script %}{% endblock script %} + <script src="{{ get_url(path='js/main.js') }}"></script> +</body> + +</html> diff --git a/src/themes/serene/templates/_components/collection.html b/src/themes/serene/templates/_components/collection.html new file mode 100644 index 0000000..70a1d31 --- /dev/null +++ b/src/themes/serene/templates/_components/collection.html @@ -0,0 +1,129 @@ +{# + This component is used inside markdown content, which is rendered as a Tera + template first and *then* processed as markdown (zola >= 0.23). Its HTML + output must therefore stay markdown-safe: no blank lines in the middle of + an HTML block, and no lines indented by 4+ spaces. + + Collection file schema: + layout = "card" | "row" | "tile" | "gallery" (default "card") + flow = "stack" | "inline" | "grid" (default: card/gallery -> "stack", row/tile -> "inline") + [[item]] + title / subtitle / content / icon / image / link / badge / tags / featured / rotate +#} + +{% component collection(file: string, section) -%} +{%- set data = load_data(path="content" ~ section.path ~ file, format="toml") -%} +{%- set layout = data.layout if data.layout is defined else "card" -%} +{%- if layout not in ["card", "row", "tile", "gallery"] -%} +{%- set layout = "card" -%} +{%- endif -%} +{%- if data.flow is defined and data.flow in ["stack", "inline", "grid"] -%} +{%- set flow = data.flow -%} +{%- elif layout == "row" or layout == "tile" -%} +{%- set flow = "inline" -%} +{%- else -%} +{%- set flow = "stack" -%} +{%- endif -%} +{%- if layout == "card" and flow == "inline" -%} +{%- set flow = "grid" -%} +{%- endif -%} +<div class="collection layout-{{ layout }} flow-{{ flow }}"> +{%- for item in data.item -%} +{%- set img_src = "" -%} +{%- if item.image is defined -%} +{%- if item.image is starting_with(pat="http") or item.image is starting_with(pat="/") or item.image is starting_with(pat="data:") -%} +{%- set img_src = item.image -%} +{%- else -%} +{%- set img_src = get_url(path=section.colocated_path ~ item.image) -%} +{%- endif -%} +{%- endif -%} +{%- set icon_src = "" -%} +{%- if item.icon is defined -%} +{%- if item.icon is starting_with(pat="http") or item.icon is starting_with(pat="/") or item.icon is starting_with(pat="data:") -%} +{%- set icon_src = item.icon -%} +{%- else -%} +{%- set icon_src = get_url(path=section.colocated_path ~ item.icon) -%} +{%- endif -%} +{%- endif -%} +{%- set external = item.link is defined and not item.link is starting_with(pat="/") -%} +{%- if layout == "card" -%} +<div class="item{% if item.featured %} featured{% endif %}"> +{%- if img_src -%}<div class="image"><img src="{{ img_src }}" alt="{{ item.title }}"></div>{%- endif -%} +<div class="body"> +<div class="meta"> +{%- if icon_src -%}<span class="icon"><img class="no-lightense" src="{{ icon_src }}" alt="" width="16" height="16"></span>{%- endif -%} +{%- if item.link -%} +<a class="title" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}><span>{{ item.title }}</span></a> +{%- else -%} +<span class="title">{{ item.title }}</span> +{%- endif -%} +{%- if item.badge -%}<span class="badge">{{ item.badge }}</span>{%- endif -%} +</div> +{%- if item.subtitle -%} +<div class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</div> +{%- endif -%} +{%- if item.content -%} +<div class="content"> +{{ item.content | trim | markdown | trim | safe }} +</div> +{%- endif -%} +{%- if item.tags -%} +<div class="tags"> +{%- for tag in item.tags -%} +<span class="tag"><span>#</span>{{ tag }}</span> +{%- endfor -%} +</div> +{%- endif -%} +</div> +</div> +{%- elif layout == "row" -%} +<span class="item{% if item.featured %} featured{% endif %}"> +<span class="main"> +{%- if icon_src -%}<span class="icon"><img class="no-lightense" src="{{ icon_src }}" alt="" width="16" height="16"></span>{%- endif -%} +{%- if item.link -%} +<a class="title" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}><span>{{ item.title }}</span></a> +{%- else -%} +<span class="title">{{ item.title }}</span> +{%- endif -%} +</span> +{%- if item.subtitle -%}<span class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</span>{%- endif -%} +{%- if item.badge -%}<span class="badge">{{ item.badge }}</span>{%- endif -%} +</span> +{%- elif layout == "tile" -%} +{%- if item.link -%} +<a class="item" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}> +{%- else -%} +<span class="item"> +{%- endif -%} +<span class="text"> +<span class="title">{{ item.title }}</span> +{%- if item.subtitle -%}<span class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</span>{%- endif -%} +</span> +{%- if img_src -%} +<img class="no-lightense{% if item.rotate %} rotate{% endif %}" src="{{ img_src }}" alt="{{ item.title }}" width="48" height="48"> +{%- else -%} +<span class="placehold"></span> +{%- endif -%} +{%- if item.link -%}</a>{%- else -%}</span>{%- endif -%} +{%- elif layout == "gallery" -%} +<div class="item"> +{%- if img_src -%}<div class="image"><img src="{{ img_src }}" alt="{{ item.title }}"></div>{%- endif -%} +<div class="text"> +{%- if item.link -%} +<a class="title" href="{{ item.link }}"{% if external %} target="_blank" rel="noreferrer noopener"{% endif %}><span>{{ item.title }}</span></a> +{%- else -%} +<span class="title">{{ item.title }}</span> +{%- endif -%} +{%- if item.subtitle -%}<span class="subtitle">{{ item.subtitle | trim | markdown(inline=true) | trim | safe }}</span>{%- endif -%} +{%- if item.content -%} +<div class="content"> +{{ item.content | trim | markdown | trim | safe }} +</div> +{%- endif -%} +{%- if item.badge -%}<span class="badge">{{ item.badge }}</span>{%- endif -%} +</div> +</div> +{%- endif -%} +{%- endfor -%} +</div> +{%- endcomponent collection %} diff --git a/src/themes/serene/templates/_components/prose.html b/src/themes/serene/templates/_components/prose.html new file mode 100644 index 0000000..c478a58 --- /dev/null +++ b/src/themes/serene/templates/_components/prose.html @@ -0,0 +1,72 @@ +{# + Components in this file are used inside markdown content, which is rendered + as a Tera template first and *then* processed as markdown (zola >= 0.23). + Their HTML output must therefore stay markdown-safe: no blank lines in the + middle of an HTML block, and no lines indented by 4+ spaces. +#} + +{% component back_link(path: string, config) %} +<header> + <nav> + <a id="back-link" href="{{ path }}">← {{ config.extra.back_link_text }}</a> + </nav> +</header> +{% endcomponent back_link %} + + +{% component quote(cite: string = "") -%} +{%- set icon = load_data(path="icon/quote.svg") -%} +<blockquote class="quote"> +<div class="icon" style="display: none;">{{ icon | trim | safe }}</div> +<div class="content"> +{{ body | markdown | trim | safe }} +</div> +{%- if cite -%} +{%- set from_text = "— " ~ cite -%} +<div class="from"> +{{ from_text | markdown | trim | safe }} +</div> +{%- endif -%} +</blockquote> +{%- endcomponent quote %} + + +{% component detail(title: string = "", default_open: bool = false) -%} +<details{% if default_open %} open{% endif %}> +<summary><span>{{ title }}</span></summary> +{{ body | markdown | trim | safe }} +</details> +{%- endcomponent detail %} + + +{% component figure(src: string, alt: string = "", caption: string = "", width: string = "", height: string = "", page = {}, section = {}) -%} +{%- if src is starting_with(pat="/") or src is starting_with(pat="http://") or src is starting_with(pat="https://") or src is starting_with(pat="//") or src is starting_with(pat="data:") -%} +{%- set image_src = src -%} +{%- elif page?.colocated_path -%} +{%- set image_src = get_url(path=page.colocated_path ~ src) -%} +{%- elif section?.colocated_path -%} +{%- set image_src = get_url(path=section.colocated_path ~ src) -%} +{%- else -%} +{%- set image_src = src -%} +{%- endif -%} +<figure> +<img src="{{ image_src }}"{% if alt %} alt="{{ alt }}"{% endif %}{% if height %} height="{{ height }}"{% endif %}{% if width %} width="{{ width }}"{% endif %}> +{%- if caption -%} +<figcaption>{{ caption | trim | markdown | trim | safe }}</figcaption> +{%- endif -%} +</figure> +{%- endcomponent figure %} + + +{% component mermaid() -%} +<pre class="mermaid"> +{{ body }} +</pre> +{%- endcomponent mermaid %} + + +{% component youtube(id: string, autoplay: bool = false) -%} +<div class="youtube"> +<iframe src="https://www.youtube.com/embed/{{ id }}{% if autoplay %}?autoplay=1&mute=1{% endif %}" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> +</div> +{%- endcomponent youtube %} diff --git a/src/themes/serene/templates/_components/seo.html b/src/themes/serene/templates/_components/seo.html new file mode 100644 index 0000000..361d7d2 --- /dev/null +++ b/src/themes/serene/templates/_components/seo.html @@ -0,0 +1,39 @@ +{% component seo(title, desc, config, url = "", page_type = "website", image = "", colocated_path = "", published = "", updated = "", tags = [], lang = "") %} +{%- set og_image = "" -%} +{%- if image -%} + {%- if image is starting_with(pat="http://") or image is starting_with(pat="https://") or image is starting_with(pat="//") -%} + {%- set og_image = image -%} + {%- elif image is starting_with(pat="/") -%} + {%- set og_image = get_url(path=image) -%} + {%- elif colocated_path -%} + {%- set og_image = get_url(path=colocated_path ~ image) -%} + {%- else -%} + {%- set og_image = get_url(path=image) -%} + {%- endif -%} +{%- elif config.extra.og_image -%} + {%- if config.extra.og_image is starting_with(pat="http://") or config.extra.og_image is starting_with(pat="https://") or config.extra.og_image is starting_with(pat="//") -%} + {%- set og_image = config.extra.og_image -%} + {%- else -%} + {%- set og_image = get_url(path=config.extra.og_image) -%} + {%- endif -%} +{%- endif -%} +<meta name="description" content="{{ desc | striptags | trim }}"> +{% if url %}<link rel="canonical" href="{{ url | safe }}">{% endif %} +<meta property="og:site_name" content="{{ config.title }}"> +<meta property="og:type" content="{{ page_type }}"> +<meta property="og:title" content="{{ title }}"> +<meta property="og:description" content="{{ desc | striptags | trim }}"> +{% if url %}<meta property="og:url" content="{{ url | safe }}">{% endif %} +{% if lang %}<meta property="og:locale" content="{{ lang | replace(from="-", to="_") }}">{% endif %} +{% if og_image %}<meta property="og:image" content="{{ og_image | safe }}">{% endif %} +{% if page_type == "article" %} +{% if published %}<meta property="article:published_time" content="{{ published }}">{% endif %} +{% if updated %}<meta property="article:modified_time" content="{{ updated }}">{% endif %} +{% if tags %}{% for tag in tags %}<meta property="article:tag" content="{{ tag }}"> +{% endfor %}{% endif %} +{% endif %} +<meta name="twitter:card" content="{% if og_image %}summary_large_image{% else %}summary{% endif %}"> +<meta name="twitter:title" content="{{ title }}"> +<meta name="twitter:description" content="{{ desc | striptags | trim }}"> +{% if og_image %}<meta name="twitter:image" content="{{ og_image | safe }}">{% endif %} +{% endcomponent seo %} 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 @@ +<style> + + /* light mode colors */ + body { + --primary-color: #5871a2; + --primary-pale-color: #5871a233; + --primary-decoration-color: #5871a210; + --bg-color: #ffffff; + --text-color: #2f3030; + --text-pale-color: #767676; + --text-decoration-color: #a9a9a9; + --highlight-mark-color: #5f75b020; + + --callout-note-color: #5871a2; + --callout-tip-color: #268556; + --callout-important-color: #885fc9; + --callout-warning-color: #ab6632; + --callout-caution-color: #c64e4e; + } + + /* dark mode colors */ + body.dark { + --primary-color: #6f8fd1; + --primary-pale-color: #6f8fd166; + --primary-decoration-color: #6f8fd112; + --bg-color: #1c1c1c; + --text-color: #c1c1c1; + --text-pale-color: #848484; + --text-decoration-color: #5f5f5f; + --highlight-mark-color: #8296cb3b; + + --callout-note-color: #6f8fd1; + --callout-tip-color: #47976f; + --callout-important-color: #9776cd; + --callout-warning-color: #ad7a52; + --callout-caution-color: #d06161; + } + + /* typography */ + body { + --main-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --code-font: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + --homepage-max-width: 768px; + --main-max-width: 768px; + --avatar-size: 60px; + --font-size: 16px; + --line-height: 1.75; + --img-border-radius: 0px; + --detail-border-radius: 0px; + --dark-mode-img-brightness: 0.75; + --dark-mode-chart-brightness: 0.75; + --inline-code-border-radius: 2px; + --inline-code-bg-color: var(--primary-decoration-color); + --block-code-border-radius: 0px; + --block-code-border-color: var(--primary-color); + --detail-border-color: var(--primary-color); + } + +</style> 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 --- /dev/null +++ b/src/themes/serene/templates/_custom_font.html diff --git a/src/themes/serene/templates/_footer.html b/src/themes/serene/templates/_footer.html new file mode 100644 index 0000000..78d62c8 --- /dev/null +++ b/src/themes/serene/templates/_footer.html @@ -0,0 +1,48 @@ +{% set show_rss = false %} +{% if section is defined and section.generate_feeds %} + {% set rss_path = section.path ~ config.feed_filenames[0] %} + {% set show_rss = true %} +{% elif config.generate_feeds %} + {% set rss_path = "/" ~ config.feed_filenames[0] %} + {% set show_rss = true %} +{% endif %} +<footer> + <div class="left"> + <div class="copyright"> + {{ config.extra.footer_copyright }} + {% if config.extra.footer_credits %} + <span>|</span> + Built with <a href="https://www.getzola.org" rel="noreferrer" target="_blank">zola</a> and <a href="https://github.com/isunjn/serene" rel="noreferrer" target="_blank">serene</a> + {% endif %} + </div> + </div> + + <div class="right"> + {% if show_rss %} + <a id="rss-btn" href="{{ get_url(path=rss_path) }}">RSS</a> + {% endif %} + + {% if color_scheme == "auto" %} + {% set moon_icon = load_data(path="icon/moon.svg") %} + {% set sun_icon = load_data(path="icon/sun.svg") %} + <button id="theme-toggle" aria-label="theme switch"> + <span class="moon-icon">{{ moon_icon | safe }}</span> + <span class="sun-icon">{{ sun_icon | safe }}</span> + </button> + {% endif %} + </div> +</footer> + +{% if show_rss %} +{% set link = get_url(path=rss_path) %} +<dialog id="rss-mask"> + <div> + <a href="{{ link }}">{{ link }}</a> + {% set copy_icon = load_data(path="icon/copy.svg") %} + {% set check_icon = load_data(path="icon/check.svg") %} + <button autofocus aria-label="copy" data-link="{{ link }}" data-copy-icon="{{ copy_icon }}" data-check-icon="{{ check_icon }}" > + {{ copy_icon | safe }} + </button> + </div> +</dialog> +{% 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 --- /dev/null +++ b/src/themes/serene/templates/_giscus_script.html 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 --- /dev/null +++ b/src/themes/serene/templates/_head_extend.html 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 %} +<div class="section-title"> + {% if section.extra.title %} + <h1>{{ section.extra.title }}</h1> + {% endif %} + {% if section.extra.subtitle %} + <p>{{ section.extra.subtitle }}</p> + {% endif %} +</div> +{% 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 @@ +<a class="zola-anchor" href="#{{ id }}" aria-label="Anchor link for: {{ id }}" style="visibility: hidden;"></a> 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 @@ +<script> + window.location.assign('{{ get_url(path="/") }}'); +</script> 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 @@ +<script> + window.location.assign('{{ get_url(path="/") }}'); +</script> diff --git a/src/themes/serene/templates/feed.xml b/src/themes/serene/templates/feed.xml new file mode 100644 index 0000000..00aebe5 --- /dev/null +++ b/src/themes/serene/templates/feed.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ lang }}"> + <title>{% if section is defined and section.title %}{{ section.title }}{% else %}{{ config.title }}{% endif %}</title> + {%- if section is defined and section.description %} + <subtitle>{{ section.description }}</subtitle> + {%- elif config.description %} + <subtitle>{{ config.description }}</subtitle> + {%- endif %} + <link rel="self" type="application/atom+xml" href="{{ feed_url | safe }}"/> + <link rel="alternate" type="text/html" href=" + {%- if section is defined -%} + {{ section.permalink | escape_xml | safe }} + {%- else -%} + {{ config.base_url | escape_xml | safe }} + {%- endif -%} + "/> + {% if last_updated is defined %} + <updated>{{ last_updated | date(format="%Y-%m-%dT%H:%M:%S%:z") }}</updated> + {% endif %} + <id>{{ feed_url | safe }}</id> + {%- for page in pages %} + <entry xml:lang="{{ page.lang }}"> + <title>{{ page.title }}</title> + <published>{{ page.date | date(format="%Y-%m-%dT%H:%M:%S%:z") }}</published> + <updated>{{ (page.updated or page.date) | date(format="%Y-%m-%dT%H:%M:%S%:z") }}</updated> + <link rel="alternate" type="text/html" href="{{ page.permalink | safe }}"/> + <id>{{ page.permalink | safe }}</id> + {% if page.summary %} + <summary>{{ page.summary }}</summary> + {% endif %} + <content type="html" xml:base="{{ page.permalink | escape_xml | safe }}">{{ page.content }}</content> + </entry> + {%- endfor %} +</feed> diff --git a/src/themes/serene/templates/home.html b/src/themes/serene/templates/home.html new file mode 100644 index 0000000..e074268 --- /dev/null +++ b/src/themes/serene/templates/home.html @@ -0,0 +1,130 @@ +{% 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 %} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = section.lang %}{% endif -%} + {{ <seo config + title={config.title} + desc={config.description} + url={get_url(path="/")} + lang={og_lang} /> }} +{% endblock desc %} + +{% block head %} +{% if config.markdown.highlighting.style == "class" %} + {% if color_scheme == "dark" %} + <link id="hl" rel="stylesheet" type="text/css" href="{{ get_url(path='giallo-dark.css') }}" /> + {% else %} + <link id="hl" rel="stylesheet" type="text/css" href="{{ get_url(path='giallo-light.css') }}" /> + {% endif %} +{% endif %} +{% set enable_math = section.extra.math if section.extra.math is defined else (config.extra.math if config.extra.math is defined else false) %} +{% if enable_math %} +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.css" integrity="sha384-M59ezskvvpvT+a+C1x088YJ3DVmK+wZdX0UkVKalOI4Qi5Nwv0WrvpqHcfa2HQqB" crossorigin="anonymous"> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.js" integrity="sha384-7jGyG5zFwmEamqNWdCbpsPn+GTWEis3lnV7X/jXHyhFpJG7ExABLyMapabg8F4+p" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/copy-tex.min.js" integrity="sha384-Pe2JWbaShhSRT0SESJ9XLPeAsi4yrNi7r/CiH0Coq7giBjK5a6Ae7XcybE8rNlQP" crossorigin="anonymous"></script> +<script> + document.addEventListener("DOMContentLoaded", function () { + renderMathInElement(document.body, { + delimiters: [ + { left: '$$', right: '$$', display: true }, + { left: '$', right: '$', display: false }, + { left: '\\(', right: '\\)', display: false }, + { left: '\\[', right: '\\]', display: true } + ], + throwOnError: false + }); + }); +</script> +{% endif %} +{% endblock head %} + +{% block content %} +<div id="wrapper"> + <main> + <section id="info"> + {% if config.extra.avatar %} + <img src="{{ get_url(path=config.extra.avatar) }}" alt="avatar"> + {% endif %} + <div id="text"> + <div> + <span id="name">{{ config.extra.name }}</span> + {% if config.extra.handle %} + <span id="id">@{{ config.extra.handle }}</span> + {% endif %} + </div> + {% if config.extra.bio %} + <div id="bio"> {{ config.extra.bio }} </div> + {% endif %} + </div> + </section> + <section id="links"> + <div id="left"> + {% for item in config.extra.nav %} + {% if item.path is starting_with(pat="/") and item.path is not starting_with(pat="//") %} + <a href="{{ get_url(path=item.path) }}" class="instant">{{ item.name }}</a> + {% else %} + <a href="{{ item.path }}" class="external" target="_blank" rel='noreferrer noopener'>{{ item.name }}</a> + {% endif %} + {% endfor %} + </div> + <div id="right"> + {% for link in config.extra.links %} + <a href="{{ link.url }}" aria-label="{{ link.name }}" target="_blank" rel='noreferrer noopener{% if link.rel_me%} me{% endif %}'> + {% set icon_path = "icon/" ~ link.icon ~ ".svg" %} + {% set icon = load_data(path=icon_path) %} + {{ icon | safe }} + </a> + {% endfor %} + {% if not section.extra.footer and color_scheme == "auto" %} + {% set moon_icon = load_data(path="icon/moon.svg") %} + {% set sun_icon = load_data(path="icon/sun.svg") %} + <button id="theme-toggle" aria-label="theme switch"> + <span class="moon-icon">{{ moon_icon | safe }}</span> + <span class="sun-icon">{{ sun_icon | safe }}</span> + </button> + {% endif %} + </div> + </section> + <section id="brief" class="prose"> + {{ section.content | trim | safe }} + </section> + {% if section.extra.recent %} + {% set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") %} + {% set section_md_path = blog_section_path ~ "/_index.md" %} + {% set blog_section = get_section(path=section_md_path) %} + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} + <section class="layout-list"> + <div class="post-list"> + {% for post in blog_section.pages[:section.extra.recent_max] %} + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> + <span>{{ post.title }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> + </a> + {% endfor %} + </div> + <div class="read-more"> + <a class="instant" href="{{ config.extra.blog_section_path }}">{{ section.extra.recent_more_text }}</a> + </div> + </section> + {% endif %} + </main> + {% if section.extra.footer %} + {% include "_footer.html" %} + {% endif %} +</div> +{% endblock content %} + +{% block script %} +<script src="{{ get_url(path='js/lightense.min.js') }}"></script> +{% set enable_mermaid = section.extra.mermaid if section.extra.mermaid is defined else (config.extra.mermaid if config.extra.mermaid is defined else false) %} +{% if enable_mermaid %} +<script type="module"> + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.esm.min.mjs'; + mermaid.initialize({ startOnLoad: true }); +</script> +{% endif %} +{% endblock script %} diff --git a/src/themes/serene/templates/post.html b/src/themes/serene/templates/post.html new file mode 100644 index 0000000..07f9782 --- /dev/null +++ b/src/themes/serene/templates/post.html @@ -0,0 +1,164 @@ +{% extends "_base.html" %} + +{% block page %}post{% endblock page %} +{% block lang -%} +{%- set section = get_section(path=page.ancestors | last, 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 %} + {%- set section = get_section(path=page.ancestors | last, metadata_only=true) -%} + {% 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 %} + {%- if page.extra.lang %}{% set og_lang = page.extra.lang %}{% elif section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = page.lang %}{% endif -%} + {{ <seo config + title={page.title} + desc={desc} + url={page.permalink} + page_type="article" + image={page.extra?.og_image or ""} + colocated_path={page.colocated_path or ""} + published={page.date or ""} + updated={page.updated or ""} + tags={page.taxonomies?.tags or []} + lang={og_lang} /> }} +{% endblock desc %} + +{% block head %} +{% if config.markdown.highlighting.style == "class" %} +<link id="hl" rel="stylesheet" type="text/css" href="/giallo-{% if color_scheme == "dark" %}dark{% else %}light{% endif %}.css" /> +{% endif %} +{% set enable_math = page.extra.math if page.extra.math is defined else (section.extra.math if section.extra.math is defined else (config.extra.math if config.extra.math is defined else false)) %} +{% if enable_math %} +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.css" integrity="sha384-M59ezskvvpvT+a+C1x088YJ3DVmK+wZdX0UkVKalOI4Qi5Nwv0WrvpqHcfa2HQqB" crossorigin="anonymous"> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.js" integrity="sha384-7jGyG5zFwmEamqNWdCbpsPn+GTWEis3lnV7X/jXHyhFpJG7ExABLyMapabg8F4+p" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/copy-tex.min.js" integrity="sha384-Pe2JWbaShhSRT0SESJ9XLPeAsi4yrNi7r/CiH0Coq7giBjK5a6Ae7XcybE8rNlQP" crossorigin="anonymous"></script> +<script> + document.addEventListener("DOMContentLoaded", function () { + renderMathInElement(document.body, { + delimiters: [ + { left: '$$', right: '$$', display: true }, + { left: '$', right: '$', display: false }, + { left: '\\(', right: '\\)', display: false }, + { left: '\\[', right: '\\]', display: true } + ], + throwOnError: false + }); + }); +</script> +{% endif %} +{% endblock head %} + +{% block content %} +<div id="wrapper"> + <div id="blank"></div> + <aside> + {% set show_toc = page.extra.toc if page.extra.toc is defined else (section.extra.toc if section.extra.toc is defined else (config.extra.toc if config.extra.toc is defined else false)) %} + {% if show_toc and page.toc %} + <nav> + <ul> + {% for h2 in page.toc %} + <li> + <a class="h2" href="#{{ h2.id | safe }}">{{ h2.title }}</a> + {% if h2.children %} + <ul> + {% for h3 in h2.children %} + <li> + <a class="h3" href="#{{ h3.id | safe }}">{{ h3.title }}</a> + </li> + {% endfor %} + </ul> + {% endif %} + </li> + {% endfor %} + </ul> + </nav> + {% endif %} + {% if section.extra.back_to_top %} + <button id="back-to-top" aria-label="back to top"> + {% set icon = load_data(path="icon/arrow-up.svg") %} + {{ icon | safe }} + </button> + {% endif %} + </aside> + <main> + {{ <back_link path={get_url(path=section.path)} config /> }} + + <div> + {% set allow_copy = page.extra.code_copy if page.extra.code_copy is defined else (section.extra.code_copy if section.extra.code_copy is defined else (config.extra.code_copy if config.extra.code_copy is defined else false)) %} + {% if allow_copy %} + {% set copy_icon = load_data(path="icon/copy.svg") %} + {% set check_icon = load_data(path="icon/check.svg") %} + <div id="copy-cfg" style="display: none;" data-copy-icon="{{ copy_icon }}" data-check-icon="{{ check_icon }}"></div> + {% endif %} + <article class="prose"> + <h1>{{ page.title }}</h1> + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} + <div id="post-info"> + <div id="date"> + <span id="publish">{{ page.date | date(format=date_format) }}</span> + {% if page.updated and page.updated != page.date -%} + <span>Updated on <span id="updated">{{ page.updated | date(format=date_format) }}</span></span> + {% endif -%} + </div> + + {% if page.taxonomies.tags is defined %} + <div id="tags"> + {% for tag in page.taxonomies.tags -%} + <a class="instant" href="{{ get_taxonomy_url(kind="tags", term=tag) }}"><span>#</span>{{ tag }}</a> + {%- endfor %} + </div> + {% endif %} + </div> + + {% set show_outdated_alert = page.extra.outdated_alert if page.extra.outdated_alert is defined else (section.extra.outdated_alert if section.extra.outdated_alert is defined else (config.extra.outdated_alert if config.extra.outdated_alert is defined else false)) %} + {% set outdated_alert_days = page.extra.outdated_alert_days if page.extra.outdated_alert_days is defined else (section.extra.outdated_alert_days if section.extra.outdated_alert_days is defined else (config.extra.outdated_alert_days if config.extra.outdated_alert_days is defined else 120)) %} + + {% if show_outdated_alert -%} + {% set outdated_alert_text_before = section.extra.outdated_alert_text_before if section.extra.outdated_alert_text_before is defined else (config.extra.outdated_alert_text_before if config.extra.outdated_alert_text_before is defined else "") %} + {% set outdated_alert_text_after = section.extra.outdated_alert_text_after if section.extra.outdated_alert_text_after is defined else (config.extra.outdated_alert_text_after if config.extra.outdated_alert_text_after is defined else "") %} + <blockquote id="outdate_alert" class="markdown-alert-caution hidden" data-days="{{ outdated_alert_days }}" + data-alert-text-before="{{ outdated_alert_text_before }}" + data-alert-text-after="{{ outdated_alert_text_after }}"> + <div class="content"></div> + </blockquote> + {% endif %} + + {{ page.content | safe }} + </article> + + {% set show_reaction = page.extra.reaction if page.extra.reaction is defined else (section.extra.reaction if section.extra.reaction is defined else (config.extra.reaction if config.extra.reaction is defined else false)) %} + {% if show_reaction %} + <div class="reaction {{ config.extra.reaction_align }}" data-endpoint="{{ config.extra.reaction_endpoint }}"></div> + {% endif %} + + {% set show_comment = page.extra.comment if page.extra.comment is defined else (section.extra.comment if section.extra.comment is defined else (config.extra.comment if config.extra.comment is defined else false)) %} + {% if show_comment %} + <div class="giscus"></div> + {% include "_giscus_script.html" %} + {% endif %} + </div> + + {% include "_footer.html" %} + </main> +</div> +{% endblock content %} + +{% block script %} +<script src="/js/lightense.min.js"></script> +{% set enable_mermaid = page.extra.mermaid if page.extra.mermaid is defined else (section.extra.mermaid if section.extra.mermaid is defined else (config.extra.mermaid if config.extra.mermaid is defined else false)) %} +{% if enable_mermaid %} +<script type="module"> + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.esm.min.mjs'; + mermaid.initialize({ startOnLoad: true }); +</script> +{% endif %} +{% endblock script %} diff --git a/src/themes/serene/templates/posts.html b/src/themes/serene/templates/posts.html new file mode 100644 index 0000000..9b3dec1 --- /dev/null +++ b/src/themes/serene/templates/posts.html @@ -0,0 +1,58 @@ +{% 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 %} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = section.lang %}{% endif -%} + {{ <seo config + title={section.title} + desc={desc} + url={section.permalink} + lang={og_lang} /> }} +{% endblock desc %} + +{% block content %} +<div id="wrapper"> + {{ <back_link path={get_url(path="/")} config /> }} + {% include "_section_title.html" %} + <main class="layout-list"> + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} + {% if section.extra.categorized %} + {% set categorized_posts = [post for post in section.pages if post.taxonomies?.categories] %} + {% set categories = [post.taxonomies.categories[0] for post in categorized_posts] | unique | sort %} + {% for category in categories %} + {% set posts = [post for post in categorized_posts if post.taxonomies.categories[0] == category] %} + {% set category_name = category %} + {% if category is matching(pat="^__[0-9]{2}__") %} + {% set category_name = category[6:] %} + {% endif %} + <h2 class="category">{{ category_name }}</h2> + <div class="post-list categorized"> + {% for post in posts %} + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> + <span>{{ post.title }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> + </a> + {% endfor %} + </div> + {% endfor %} + {% else %} + <div class="post-list"> + {% for post in section.pages %} + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> + <span>{{ post.title }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> + </a> + {% endfor %} + </div> + {% endif %} + </main> + {% include "_footer.html" %} +</div> +{% endblock content %} diff --git a/src/themes/serene/templates/prose.html b/src/themes/serene/templates/prose.html new file mode 100644 index 0000000..114ff16 --- /dev/null +++ b/src/themes/serene/templates/prose.html @@ -0,0 +1,90 @@ +{% 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 %} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = section.lang %}{% endif -%} + {{ <seo config + title={section.title} + desc={desc} + url={section.permalink} + lang={og_lang} /> }} +{% endblock desc %} + +{% block head %} +{% if config.markdown.highlighting.style == "class" %} +<link id="hl" rel="stylesheet" type="text/css" href="/giallo-{% if color_scheme == "dark" %}dark{% else %}light{% endif %}.css" /> +{% endif %} +{% set enable_math = section.extra.math if section.extra.math is defined else (config.extra.math if config.extra.math is defined else false) %} +{% if enable_math %} +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.css" integrity="sha384-M59ezskvvpvT+a+C1x088YJ3DVmK+wZdX0UkVKalOI4Qi5Nwv0WrvpqHcfa2HQqB" crossorigin="anonymous"> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/katex.min.js" integrity="sha384-7jGyG5zFwmEamqNWdCbpsPn+GTWEis3lnV7X/jXHyhFpJG7ExABLyMapabg8F4+p" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/auto-render.min.js" integrity="sha384-bjyGPfbij8/NDKJhSGZNP/khQVgtHUE5exjm4Ydllo42FwIgYsdLO2lXGmRBf5Mz" crossorigin="anonymous"></script> +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.18.6/dist/contrib/copy-tex.min.js" integrity="sha384-Pe2JWbaShhSRT0SESJ9XLPeAsi4yrNi7r/CiH0Coq7giBjK5a6Ae7XcybE8rNlQP" crossorigin="anonymous"></script> +<script> + document.addEventListener("DOMContentLoaded", function () { + renderMathInElement(document.body, { + delimiters: [ + { left: '$$', right: '$$', display: true }, + { left: '$', right: '$', display: false }, + { left: '\\(', right: '\\)', display: false }, + { left: '\\[', right: '\\]', display: true } + ], + throwOnError: false + }); + }); +</script> +{% endif %} +{% endblock head %} + +{% block content %} +<div id="wrapper"> + <main> + {{ <back_link path={get_url(path="/")} config /> }} + {% include "_section_title.html" %} + <div> + {% set allow_copy = section.extra.code_copy if section.extra.code_copy is defined else (config.extra.code_copy if config.extra.code_copy is defined else false) %} + {% if allow_copy %} + {% set copy_icon = load_data(path="icon/copy.svg") %} + {% set check_icon = load_data(path="icon/check.svg") %} + <div id="copy-cfg" style="display: none;" data-copy-icon="{{ copy_icon }}" data-check-icon="{{ check_icon }}"></div> + {% endif %} + <article class="prose"> + {{ section.content | safe }} + </article> + + {% set show_reaction = section.extra.reaction if section.extra.reaction is defined else (config.extra.reaction if config.extra.reaction is defined else false) %} + {% if show_reaction %} + <div class="reaction {{ config.extra.reaction_align }}" data-endpoint="{{ config.extra.reaction_endpoint }}"></div> + {% endif %} + + {% set show_comment = section.extra.comment if section.extra.comment is defined else (config.extra.comment if config.extra.comment is defined else false) %} + {% if show_comment %} + <div class="giscus"></div> + {% include "_giscus_script.html" %} + {% endif %} + </div> + + {% include "_footer.html" %} + </main> +</div> +{% endblock content %} + +{% block script %} +<script src="/js/lightense.min.js"></script> +{% set enable_mermaid = section.extra.mermaid if section.extra.mermaid is defined else (config.extra.mermaid if config.extra.mermaid is defined else false) %} +{% if enable_mermaid %} +<script type="module"> + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.esm.min.mjs'; + mermaid.initialize({ startOnLoad: true }); +</script> +{% endif %} +{% endblock script %} diff --git a/src/themes/serene/templates/tags/list.html b/src/themes/serene/templates/tags/list.html new file mode 100644 index 0000000..e2d48d7 --- /dev/null +++ b/src/themes/serene/templates/tags/list.html @@ -0,0 +1,35 @@ +{% extends "_base.html" %} + +{% block page %}tag-list{% endblock page%} +{% block lang -%} +{% set blog_section_path = config.extra.blog_section_path | trim_start(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 %} + {%- set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") -%} + {%- set section = get_section(path=blog_section_path ~ "/_index.md", metadata_only=true) -%} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = lang %}{% endif -%} + {{ <seo config + title="Tags" + desc="Tags" + url={current_url or ""} + lang={og_lang} /> }} +{% endblock desc %} + +{% block content %} +<div id="wrapper"> + {{ <back_link path={get_url(path="/")} config /> }} + <main> + <h1 class="title">Tags</h1> + <div class="tags"> + {% for tag in terms -%} + <a class="instant" href="{{ tag.permalink }}"># {{ tag.name | lower }}</a> + {% endfor %} + </div> + </main> + {% include "_footer.html" %} +</div> +{% 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..ea9f360 --- /dev/null +++ b/src/themes/serene/templates/tags/single.html @@ -0,0 +1,42 @@ +{% extends "_base.html" %} + +{% block page %}tag-single{% endblock page %} +{% block lang -%} +{% set blog_section_path = config.extra.blog_section_path | trim_start(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 %} + {%- set blog_section_path = config.extra.blog_section_path | trim_start(pat="/") -%} + {%- set section = get_section(path=blog_section_path ~ "/_index.md", metadata_only=true) -%} + {%- if section.extra.lang %}{% set og_lang = section.extra.lang %}{% else %}{% set og_lang = lang %}{% endif -%} + {{ <seo config + title={section.title} + desc={term.name} + url={term.permalink} + lang={og_lang} /> }} +{% endblock desc %} + +{% block content %} +<div id="wrapper"> + {% set tags_taxonomy = get_taxonomy(kind="tags") %} + {{ <back_link path={tags_taxonomy.permalink} config /> }} + <main class="layout-list"> + <div class="title"> + <span># {{ term.name }}</span> + </div> + <div class="post-list"> + {% set date_format = section.extra.date_format if section.extra.date_format is defined else (config.extra.date_format if config.extra.date_format is defined else "%b %-d, %Y") %} + {% for post in term.pages %} + <a class="post instant {% if post.extra.featured %}featured{% endif %}" href="{{ post.permalink }}"> + <span>{{ post.title }}</span> + <span class="date">{{ post.date | date(format=date_format) }}</span> + </a> + {% endfor %} + </div> + </main> + {% include "_footer.html" %} +</div> +{% endblock content %} diff --git a/src/themes/serene/theme.toml b/src/themes/serene/theme.toml new file mode 100644 index 0000000..2d2b84a --- /dev/null +++ b/src/themes/serene/theme.toml @@ -0,0 +1,12 @@ +name = "serene" +description = "A minimal blog theme for zola, well crafted" +license = "MIT" +homepage = "https://github.com/isunjn/serene" +min_version = "0.23.4" +demo = "https://serene-demo.pages.dev" + +[author] +name = "isunjn" +homepage = "https://github.com/isunjn" + +[extra] diff --git a/src/themes/serene/zola.toml b/src/themes/serene/zola.toml new file mode 100644 index 0000000..1c3a8e3 --- /dev/null +++ b/src/themes/serene/zola.toml @@ -0,0 +1,75 @@ +base_url = "https://serene-demo.pages.dev" +title = "Serene" +description = "" +default_language = "en" +# theme = "serene" +output_dir = "public" +compile_sass = true +minify_html = false +build_search_index = false +generate_feeds = false +feed_filenames = ["feed.xml"] +taxonomies = [{ name = "tags" }, { name = "categories" }] + +[markdown] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false +github_alerts = true + +[markdown.highlighting] +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= +[extra] + +nav = [ + { name = "posts", path = "/posts" }, + { name = "collections", path = "/collections" }, + { name = "about", path = "/about" }, +] + +name = "Serene" +handle = "serene" +bio = "A minimal blog theme for Zola, well crafted" +# avatar = "img/avatar.webp" +links = [ + { name = "GitHub", icon = "github", url = "https://github.com/isunjn/serene" }, + { name = "Email", icon = "email", url = "mailto:<your-email-address>" }, +] +blog_section_path = "/posts" + +back_link_text = "Back" +color_scheme = "auto" # "auto" | "light" | "dark" + +# og_image = "https://github.com/isunjn/serene/blob/main/screenshot.png?raw=true" + +footer_copyright = "© 2025 Serene Theme" +footer_credits = true + +not_found_error_text = "404 Not Found" +not_found_recover_text = "« back to home »" + +date_format = "%b %-d, %Y" +toc = true +code_copy = true +comment = false +math = false +mermaid = false +outdated_alert = false +outdated_alert_days = 120 +outdated_alert_text_before = "This article was last updated " +outdated_alert_text_after = " days ago and may be out of date." + +reaction = false +reaction_align = "right" +reaction_endpoint = "https://example.com/api/reaction" diff --git a/src/themes/serene/zola.toml.example b/src/themes/serene/zola.toml.example new file mode 100644 index 0000000..02f00fd --- /dev/null +++ b/src/themes/serene/zola.toml.example @@ -0,0 +1,80 @@ +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] +render_emoji = false +external_links_target_blank = false +external_links_no_follow = true +external_links_no_referrer = true +smart_punctuation = false +github_alerts = true + +[markdown.highlighting] +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" + +#========================================================================================= + +[extra] + +nav = [ + { name = "posts", path = "/posts" }, + # { name = "tags", path = "/tags" }, + # { name = "github", path = "https://github.com/<your-username>" }, +] + +# Your info on the home page, if you don't want to display handle/bio/avatar, simply comment out that line +name = "Jhon Wick" +handle = "jhonwick" +bio = "dog person, killer" +avatar = "img/avatar.webp" +links = [ + { name = "GitHub", icon = "github", url = "https://github.com/<your-username>" }, + { name = "Email", icon = "email", url = "mailto:<your-email-address>" }, + { name = "Twitter", icon = "twitter", url = "https://twitter.com/<your-username>" }, + { name = "Mastodon", icon = "mastodon", url = "https://mastodon.social/<your-username>", rel_me = true }, +] +blog_section_path = "/posts" # Path of the main blog section, used by home page's recent posts and tags pages + +back_link_text = "Back" # Text of the back button +color_scheme = "auto" # "auto" | "light" | "dark" + +# og_image = "img/og.png" # The default image for social media sharing (Open Graph), a path in static folder or a full URL + +footer_copyright = "© 2025 <your-name>" +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 »" + +# Site-wide default values of display options, can be overridden in a section's `_index.md`, and again in a post's front-matter +date_format = "%b %-d, %Y" +toc = true # show table-of-contents +code_copy = true # show copy button in code block +comment = false # enable comment +math = false # enable formula rendering with KaTeX +mermaid = false # enable chart rendering with Mermaid +outdated_alert = false # show outdate alert after certain days +outdated_alert_days = 120 +outdated_alert_text_before = "This article was last updated " +outdated_alert_text_after = " days ago and may be out of date." + +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/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 @@ -<html> - <body> - <h1>My paper like things, provided in the portable document format (pdf)</h1> - <ul> - <li><a href="./philosophy/kant_and_free_software.pdf">My essay on free software and Kantian ethics</a></li> - <li><a href="./chemistry/facharbeit.pdf">My 'Facharbeit' on Raman Spektrometrie</a></li> - </ul> - </body> -</html> |
