summaryrefslogtreecommitdiff
path: root/publish
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-03-25 13:48:52 -0300
committerGabriel Schneider <[email protected]>2026-03-25 13:49:41 -0300
commitffb30c53b76adbee64fc6b0df59ab7599dec3d9e (patch)
tree344b5c1424e3099ac0110fafcabd2c67d51f864f /publish
download0x4200.cafe-ffb30c53b76adbee64fc6b0df59ab7599dec3d9e.tar.gz
0x4200.cafe-ffb30c53b76adbee64fc6b0df59ab7599dec3d9e.zip
initial kodama site
Diffstat (limited to 'publish')
-rw-r--r--publish/index.html141
-rw-r--r--publish/kodama.graph.json1
-rw-r--r--publish/kodama.json1
-rw-r--r--publish/main.css697
-rw-r--r--publish/main.js362
5 files changed, 1202 insertions, 0 deletions
diff --git a/publish/index.html b/publish/index.html
new file mode 100644
index 0000000..1ef99a4
--- /dev/null
+++ b/publish/index.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html lang="en-US"><head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<meta name="viewport" content="width=device-width"><title>index</title><link rel="icon" href="/assets/favicon.ico" /><template id="theme-option-template"><input type="radio" name="theme" /><label></label></template><script src="/main.js"></script><!-- Also see: https://katex.org/docs/autorender -->
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css" integrity="sha384-WcoG4HRXMzYzfCgiyfrySxx90XSl2rxY5mnVY5TwtWE6KLrArNKn0T/mOgNL0Mmi" crossorigin="anonymous">
+<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js" integrity="sha384-J+9dG2KMoiR9hqcFao0IBLwxt6zpcyN68IgwzsCSkbreXUjmNVRhPFTssqdSGjwQ" crossorigin="anonymous"></script>
+<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js" integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh" crossorigin="anonymous"></script>
+<!-- Also see: https://github.com/KaTeX/KaTeX/tree/main/contrib/copy-tex -->
+<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/copy-tex.min.js" integrity="sha384-HORx6nWi8j5/mYA+y57/9/CZc5z8HnEw4WUZWy5yOn9ToKBv1l58vJaufFAn9Zzi" 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 },
+ ],
+ trust: true,
+ strict: false,
+ throwOnError: false,
+ minRuleThickness: 0.05,
+ });
+ });
+</script>
+<link rel="stylesheet" href="/main.css"><style>
+@media only screen and (min-width: 1000px) {
+ #grid-wrapper { grid-template-columns: var(--article-max-width) var(--toc-max-width); }
+ nav#toc { max-width: 45ex; }
+}
+</style><link rel="preconnect" href="https://fonts.googleapis.com" />
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+<link href="https://fonts.googleapis.com/css2?family=Inria+Sans:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet" />
+<link href="https://fonts.googleapis.com/css2?family=LXGW+WenKai+TC:wght@400;700&display=swap" rel="stylesheet">
+</head><body><div id="grid-wrapper" style="grid-template-areas: 'article toc';" data-base-url="/"><nav id="toc" class="sticky-nav mobile-sticky-nav"><div id="theme-options"><!-- Copyright (c) 2025 Kodama Project. All rights reserved. -->
+<!-- Released under the Apache-2.0 license as described in the file LICENSE. -->
+<!-- Authors: Kokic (@kokic) -->
+<!-- Inspired by Pink Floyd - The Dark Side of the Moon -->
+
+<style>
+ body:has(input[value="moon"]:checked) {
+ --hover-primary-color: 181, 140, 114;
+
+ --text-color: rgb(32, 28, 48);
+ --toc-link-color: #555;
+ --background-color: rgb(255, 238, 228);
+
+ --hover-color-block: rgba(var(--hover-primary-color), 0.1);
+ --hover-color-link: rgba(var(--hover-primary-color), 0.2);
+
+ --link-color: rgb(227, 66, 114);
+ --slug-color: rgb(184, 170, 161);
+ --logo-color: #666;
+ --logo-hover-color: #aaa;
+ --span-taxon-color: black;
+ --article-taxon-color: #888;
+ --mark-color: rgb(255, 255, 151);
+ --em-color: rgb(220, 121, 113);
+ }
+
+ @media (prefers-color-scheme: dark) {
+ body:has(input[value="moon"]:checked) {
+ --hover-primary-color: 196, 136, 193;
+
+ --text-color: rgb(187 176 169);
+ --toc-link-color: rgb(187 176 169);
+ --background-color: rgb(0, 0, 0);
+
+ --background-color-pre: rgba(100, 100, 100, 0.2);
+ --background-color-code: rgba(150, 150, 150, 0.2);
+
+ --hover-color-block: rgba(var(--hover-primary-color), 0.06);
+ --hover-color-link: rgba(var(--hover-primary-color), 0.2);
+ --target-color: rgb(255, 255, 151);
+
+ --link-color: rgb(255, 128, 165);
+ --slug-color: #aeaeae;
+ --logo-color: #999;
+ --logo-hover-color: #eee;
+ --span-taxon-color: rgb(236, 230, 226);
+ --em-color: rgb(255, 219, 230);
+
+ .block:has(.block:hover) {
+ background-color: var(--background-color);
+ }
+ }
+ }
+
+ body:has(input[value="moon-light"]:checked) {
+ --hover-primary-color: 181, 140, 114;
+
+ --text-color: rgb(32, 28, 48);
+ --toc-link-color: #555;
+ --background-color: rgb(255, 238, 228);
+
+ --hover-color-block: rgba(var(--hover-primary-color), 0.1);
+ --hover-color-link: rgba(var(--hover-primary-color), 0.2);
+
+ --link-color: rgb(227, 66, 114);
+ --slug-color: rgb(184, 170, 161);
+ --logo-color: #666;
+ --logo-hover-color: #aaa;
+ --span-taxon-color: black;
+ --article-taxon-color: #888;
+ --mark-color: rgb(255, 255, 151);
+ --em-color: rgb(220, 121, 113);
+ }
+
+ body:has(input[value="moon-dark"]:checked) {
+ --hover-primary-color: 196, 136, 193;
+
+ --text-color: rgb(187 176 169);
+ --toc-link-color: rgb(187 176 169);
+ --background-color: rgb(32, 28, 48);
+
+ --background-color-pre: rgba(100, 100, 100, 0.2);
+ --background-color-code: rgba(150, 150, 150, 0.2);
+
+ --hover-color-block: rgba(var(--hover-primary-color), 0.06);
+ --hover-color-link: rgba(var(--hover-primary-color), 0.2);
+ --target-color: rgb(255, 255, 151);
+
+ --link-color: rgb(255, 128, 165);
+ --slug-color: #aeaeae;
+ --logo-color: #999;
+ --logo-hover-color: #eee;
+ --span-taxon-color: rgb(236, 230, 226);
+ --em-color: rgb(255, 219, 230);
+
+ .block:has(.block:hover) {
+ background-color: var(--background-color);
+ }
+ }
+</style>
+
+<theme-option name="moon">dark</theme-option>
+<theme-option name="moon-light">light</theme-option>
+</div></nav>
+
+<article><section class="block" data-taxon=""><details open><summary id="index"><header><h1><span class="taxon"></span>index <a class="slug" href="/index.html">[index]</a></h1><div class="metadata"><ul></ul></div></header></summary></details></section><footer></footer></article></div></body></html> \ No newline at end of file
diff --git a/publish/kodama.graph.json b/publish/kodama.graph.json
new file mode 100644
index 0000000..0f6304d
--- /dev/null
+++ b/publish/kodama.graph.json
@@ -0,0 +1 @@
+{"sections":{"index":{"parent":"index","parent_specified":false,"references":[],"backlinks":[]}}} \ No newline at end of file
diff --git a/publish/kodama.json b/publish/kodama.json
new file mode 100644
index 0000000..5315150
--- /dev/null
+++ b/publish/kodama.json
@@ -0,0 +1 @@
+{"index":{"slug":{"Plain":"index"},"ext":{"Plain":"md"},"title":{"Plain":"index"},"page-title":{"Plain":"index"}}} \ No newline at end of file
diff --git a/publish/main.css b/publish/main.css
new file mode 100644
index 0000000..f3ca6cb
--- /dev/null
+++ b/publish/main.css
@@ -0,0 +1,697 @@
+html {
+ font-size: 18px;
+}
+
+:root body {
+ color-scheme: light dark;
+ --content-gap: 15px;
+ --radius: 5px;
+ --article-max-width: 90ex;
+ --toc-max-width: 90ex;
+ --text-font-family: "Inria Sans", sans-serif;
+ --kaiti-font-family: "LXGW WenKai TC", "FandolKai", "KaiTi", "SimKai", "STKaiti", "Kaiti SC", "Kaiti TC", "华文楷体", "楷体", serif;
+
+ /* root font size: 16px */
+ --pre-font-size: 1rem;
+ --code-font-size: 1rem;
+ --cjk-kaiti-font-size: 1rem;
+ /* If this value is not 1rem, the CJK text in Typst will not match the size of the CJK text in KaTeX. */
+ --katex-font-size: 1em;
+ --katex-frac-line-font-size: 1.25em;
+ --details-h1-font-size: 1.2rem;
+ /* 13pt */
+ --section-details-h1-font-size: 1.083rem;
+ --article-details-h1-font-size: 1.5rem;
+ /* 0.9em */
+ --article-details-h1-taxon-font-size: 1.35rem;
+ /* 24px; */
+ --logo-font-size: 1.5rem;
+ /* 14pt; */
+ --footer-h2-font-size: 1.167rem;
+ --p-line-height: 1.55rem;
+
+ --text-color: black;
+ --toc-link-color: #555;
+ --background-color: white;
+
+ --background-color-pre: rgba(0, 100, 100, 0.04);
+ --background-color-code: rgba(0, 100, 100, 0.04);
+
+ --hover-color-block: rgba(0, 100, 255, 0.04);
+ --hover-color-link: rgba(0, 100, 255, 0.1);
+ --target-color: rgb(67, 92, 255);
+
+ --link-color: black;
+ --slug-color: gray;
+ --logo-color: #666;
+ --logo-hover-color: #aaa;
+ --span-taxon-color: #444;
+ --article-taxon-color: #888;
+ --mark-color: rgb(255, 255, 151);
+ --em-color: var(--text-color);
+
+ --alert-border-color: gray;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root body {
+ --text-color: white;
+ --toc-link-color: white;
+ --background-color: #2f2f2f;
+
+ --background-color-pre: rgba(100, 100, 100, 0.2);
+ --background-color-code: rgba(150, 150, 150, 0.2);
+
+ --hover-color-block: rgba(100, 162, 255, 0.06);
+ --hover-color-link: rgba(100, 162, 255, 0.2);
+ --target-color: rgb(255, 255, 151);
+
+ --link-color: white;
+ --slug-color: #aeaeae;
+ --logo-color: #999;
+ --logo-hover-color: #eee;
+ --span-taxon-color: #bbb;
+ }
+}
+
+@media print {
+ html {
+ font-size: 16px;
+ }
+
+ .edit,
+ #theme-options {
+ display: none;
+ }
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+theme-option {
+ display: inline-block;
+}
+
+theme-option>input {
+ display: none;
+}
+
+theme-option>label {
+ color: var(--slug-color);
+}
+
+theme-option>label:hover {
+ background-color: var(--hover-color-link);
+}
+
+theme-option:has(input:checked)>label {
+ color: var(--link-color);
+}
+
+theme-option>label::before {
+ content: "[";
+ color: rgba(0, 0, 0, 0);
+}
+
+theme-option>label::after {
+ content: "]";
+ color: rgba(0, 0, 0, 0);
+}
+
+theme-option:has(input:checked)>label::before {
+ color: var(--link-color);
+}
+
+theme-option:has(input:checked)>label::after {
+ color: var(--link-color);
+}
+
+#theme-options::before {
+ content: "(";
+}
+
+#theme-options::after {
+ content: ")";
+}
+
+body {
+ color: var(--text-color);
+ font-optical-sizing: auto;
+ font-size: 1rem;
+ font-family: var(--text-font-family);
+ hyphens: auto;
+ background-color: var(--background-color);
+}
+
+pre,
+code {
+ font-optical-sizing: auto;
+}
+
+p,
+pre {
+ line-height: var(--p-line-height);
+}
+
+pre {
+ border-radius: var(--radius);
+ background-color: var(--background-color-pre);
+ padding: 0.5rem;
+ font-size: var(--pre-font-size);
+ margin-top: 0rem;
+ overflow-x: auto;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+}
+
+code {
+ border-radius: var(--radius);
+ background-color: var(--background-color-code);
+ padding: 0.1rem;
+ font-size: var(--code-font-size);
+}
+
+pre,
+code {
+ font-family: monospace;
+}
+
+table {
+ border-collapse: collapse;
+ margin-bottom: 1rem;
+}
+
+/* !forest */
+th {
+ font-weight: normal;
+ text-align: left;
+}
+
+th,
+td {
+ padding: 0 15px;
+ vertical-align: top;
+}
+
+/* !forest */
+pre>code {
+ border-radius: 0;
+ background-color: transparent;
+ padding: 0;
+}
+
+/* !forest */
+.footnote-definition:last-child {
+ margin-bottom: 1rem;
+}
+
+/* !forest */
+.footnote-definition>p {
+ display: inline;
+}
+
+/* !forest */
+.footnote-definition-label {
+ margin-right: 4px;
+}
+
+/* !forest */
+.footnote-reference:target {
+ border: 1px solid var(--target-color);
+}
+
+.footnote-definition:target>.footnote-definition-label {
+ border: 1px solid var(--target-color);
+}
+
+.footnote-reference::before,
+.footnote-definition-label::before {
+ content: "[";
+}
+
+.footnote-reference::after,
+.footnote-definition-label::after {
+ content: "]";
+}
+
+.katex {
+ font: normal var(--katex-font-size) KaTeX_Main, Times New Roman, serif;
+ line-height: 1rem;
+ text-indent: 0;
+ text-rendering: auto;
+}
+
+.katex .mfrac .frac-line {
+ font-size: var(--katex-frac-line-font-size);
+}
+
+/* Generated by KaTeX */
+.cjk_fallback {
+ font-size: 1rem;
+ font-family: var(--text-font-family);
+}
+
+p:has(span.katex-html:empty) {
+ display: none;
+}
+
+section+p:empty {
+ display: none;
+}
+
+.katex-display {
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+.katex-display .katex {
+ /* Inconsistent bounding box at different font sizes. */
+ /* Also see: https://github.com/KaTeX/KaTeX/issues/3965 */
+ padding-top: 1px;
+}
+
+.display-none {
+ display: none;
+}
+
+em {
+ color: var(--em-color);
+ font-style: italic;
+}
+
+em span:is([lang=zh], [lang=ja], [lang=ko]) {
+ font-style: normal;
+ font-family: var(--kaiti-font-family);
+ font-size: var(--cjk-kaiti-font-size);
+}
+
+blockquote {
+ font-style: italic;
+ --blockquote-cjk-font-family: var(--kaiti-font-family);
+}
+
+blockquote span:is([lang=zh], [lang=ja], [lang=ko]) {
+ font-style: normal;
+ font-family: var(--blockquote-cjk-font-family);
+ font-size: var(--cjk-kaiti-font-size);
+}
+
+/* GFM Blockquote tags: https://github.com/orgs/community/discussions/16925 */
+
+blockquote:is(.markdown-alert-note, .markdown-alert-tip, .markdown-alert-important, .markdown-alert-warning, .markdown-alert-caution) {
+ --blockquote-cjk-font-family: var(--text-font-family);
+ margin: 0.5rem 0.5rem;
+ padding: 0rem 0.5rem;
+ font-style: normal;
+ border-left: .25rem double var(--alert-border-color);
+}
+
+blockquote:is(.markdown-alert-note, .markdown-alert-tip, .markdown-alert-important, .markdown-alert-warning, .markdown-alert-caution)>p:first-child::before {
+ font-size: var(--section-details-h1-font-size);
+ font-family: monospace;
+ font-style: normal;
+ color: var(--span-taxon-color);
+ margin-right: 0.5rem;
+ display: block;
+ content: var(--alert-label);
+}
+
+blockquote.markdown-alert-note {
+ --alert-label: "[# NOTE]";
+}
+
+blockquote.markdown-alert-tip {
+ --alert-label: "[# TIP]";
+}
+
+blockquote.markdown-alert-important {
+ --alert-label: "[# IMPORTANT]";
+}
+
+blockquote.markdown-alert-warning {
+ --alert-label: "[# WARNING]";
+}
+
+blockquote.markdown-alert-caution {
+ --alert-label: "[# CAUTION]";
+}
+
+h1,
+h2,
+h3,
+h4 {
+ margin-top: 0.5em;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: 500;
+ margin-bottom: 0;
+}
+
+h5,
+h6,
+p {
+ margin-top: 0;
+}
+
+details>summary {
+ list-style-type: none;
+ outline: none;
+}
+
+details>summary>header {
+ display: inline;
+}
+
+details>summary::marker,
+details>summary::-webkit-details-marker {
+ display: none;
+}
+
+details h1 {
+ font-size: var(--details-h1-font-size);
+ display: inline;
+}
+
+span.taxon {
+ color: var(--span-taxon-color);
+ font-weight: 500;
+}
+
+article>section>details>summary>header>h1 {
+ font-size: var(--article-details-h1-font-size);
+}
+
+article>section>details>h2 {
+ font-size: 1.25rem;
+}
+
+article>section>details>h3 {
+ font-size: 1.125rem;
+}
+
+article>section>details>h4 {
+ font-size: 1rem;
+}
+
+article>section>details>h5,
+article>section>details>h6 {
+ font-size: 0.875rem;
+}
+
+article>section>details>summary>header {
+ display: block;
+ margin-bottom: 0.5em;
+}
+
+article>section>details>summary>header>h1>.taxon {
+ display: block;
+ font-size: var(--article-details-h1-taxon-font-size);
+ color: var(--article-taxon-color);
+ padding-bottom: 5pt;
+}
+
+article>section>details>summary>header>h1>.taxon:empty {
+ display: none;
+}
+
+section.block>details {
+ margin-bottom: 0.4em;
+}
+
+section.block>details[open] {
+ margin-bottom: 1em;
+}
+
+section.block.link-list>details {
+ margin-bottom: 0em;
+}
+
+section.block.link-list>details>section.block>details {
+ margin-bottom: 0.25em;
+}
+
+/* class */
+.inline-typst {
+ display: inline;
+ margin: 0 0;
+ font-size: var(--katex-font-size);
+}
+
+.block {
+ padding-left: 5px;
+ padding-right: 10px;
+ padding-bottom: 2px;
+ border-radius: var(--radius);
+}
+
+.block:hover {
+ background-color: var(--hover-color-block);
+}
+
+.block.hide-metadata>details>summary>header>.metadata {
+ display: none;
+}
+
+img {
+ object-fit: cover;
+ max-width: 100%;
+}
+
+figure {
+ text-align: center;
+}
+
+figcaption {
+ font-style: italic;
+ padding: 3px;
+}
+
+mark {
+ background-color: var(--mark-color);
+}
+
+hr {
+ margin-top: 10px;
+ margin-bottom: 20px;
+ background-color: gray;
+ border: 0 none;
+ width: 100%;
+ height: 1pt;
+}
+
+ul,
+ol {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.logo>span {
+ color: var(--logo-color);
+ text-decoration: none;
+}
+
+.logo>span:hover {
+ color: var(--logo-hover-color);
+}
+
+.logo {
+ font-weight: 600;
+ font-size: var(--logo-font-size);
+}
+
+section section[data-taxon="Reference"]>details>summary>header>h1>.taxon,
+section section[data-taxon="Person"]>details>summary>header>h1>.taxon {
+ display: none;
+}
+
+section .block[data-taxon]>header>h1,
+section .block[data-taxon] details>summary>header>h1 {
+ font-size: var(--section-details-h1-font-size);
+}
+
+footer>section {
+ margin-bottom: 1em;
+}
+
+footer h2 {
+ font-size: var(--footer-h2-font-size);
+}
+
+.metadata ul {
+ padding-left: 0;
+ display: inline;
+}
+
+.metadata li::after {
+ content: " · ";
+}
+
+.metadata li:last-child::after {
+ content: "";
+}
+
+.metadata * {
+ display: inline;
+}
+
+.link {
+ cursor: pointer;
+}
+
+a {
+ color: var(--link-color);
+ text-decoration: inherit;
+}
+
+a.slug:hover,
+a.edit:hover,
+a.hash:hover,
+a.bullet:hover,
+.link:hover {
+ background-color: var(--hover-color-link);
+}
+
+.link.external,
+.link.asset {
+ text-decoration: underline;
+}
+
+.link.asset::before {
+ content: "[F] ";
+ /* file */
+}
+
+a.link.local,
+.link.local a,
+a.slug,
+a.edit,
+a.hash {
+ box-shadow: none;
+ text-decoration-line: underline;
+ text-decoration-style: dotted;
+ overflow-wrap: break-word;
+}
+
+.slug,
+.edit,
+.doi,
+.orcid {
+ color: var(--slug-color);
+ font-weight: 200;
+}
+
+body>header {
+ margin-bottom: 0.5em;
+}
+
+#grid-wrapper>article {
+ max-width: var(--article-max-width);
+ margin-right: auto;
+ grid-area: article;
+}
+
+@media only screen and (max-width: 1000px) {
+ :root body {
+ --code-font-size: 0.9rem;
+ }
+
+ figure {
+ margin: 0rem;
+ }
+
+ .block {
+ padding-left: 2px;
+ padding-right: 2px;
+ padding-bottom: 2px;
+ border-radius: 5px;
+ }
+
+ #grid-wrapper>nav {
+ transition: ease all 0.2s;
+ }
+
+ .mobile-sticky-nav {
+ position: sticky;
+ top: 0px;
+ max-height: calc(100vh - 0px);
+ overflow-y: auto;
+ scrollbar-width: thin;
+
+ background-color: var(--background-color);
+ z-index: 7;
+ border-bottom: solid var(--text-color);
+ }
+}
+
+@media only screen and (min-width: 1000px) {
+ body {
+ margin-top: 2rem;
+ margin-left: 2rem;
+ transition: ease all 0.2s;
+ }
+
+ #grid-wrapper {
+ display: grid;
+ grid-auto-flow: column;
+ }
+
+ .sticky-nav {
+ position: sticky;
+ top: 0px;
+ max-height: calc(100vh - 0px);
+ overflow-y: auto;
+ scrollbar-width: thin;
+ }
+}
+
+nav#toc li.item-summary li {
+ display: none;
+}
+
+nav#toc ul {
+ list-style-type: none;
+}
+
+nav#toc li>ul {
+ margin: 0;
+ padding-left: 1rem;
+}
+
+nav#toc,
+nav#toc a {
+ color: var(--toc-link-color);
+}
+
+nav#toc {
+ grid-area: toc;
+}
+
+nav {
+ font-optical-sizing: auto;
+}
+
+nav#toc a.bullet {
+ opacity: 0.7;
+ margin-left: 0.4rem;
+ margin-right: 0.3rem;
+ padding-left: 0.2rem;
+ padding-right: 0.2rem;
+ text-decoration: none;
+}
+
+.typst-text use[fill="#000000"],
+.typst-group use[fill="#000000"],
+path.typst-shape[fill="#000000"] {
+ fill: var(--text-color);
+}
+
+path.typst-shape[stroke="#000000"] {
+ stroke: var(--text-color);
+} \ No newline at end of file
diff --git a/publish/main.js b/publish/main.js
new file mode 100644
index 0000000..5caefb6
--- /dev/null
+++ b/publish/main.js
@@ -0,0 +1,362 @@
+// Mobile TOC: responsive open / close behavior for the table of contents.
+function toggleDetailsOpen() {
+ const details = document.querySelector("#toc>div>details");
+ if (!details) return;
+
+ if (window.matchMedia("(max-width: 1000px)").matches) {
+ details.removeAttribute("open");
+ } else {
+ details.setAttribute("open", "");
+ }
+}
+
+document.addEventListener("DOMContentLoaded", toggleDetailsOpen);
+window.addEventListener("resize", toggleDetailsOpen);
+
+// Theme: option selection, persistence, and dynamic color invert logic.
+const KODAMA_THEME_KEY = `kodama-theme`;
+
+function storeSelectedTheme(name) {
+ localStorage.setItem(KODAMA_THEME_KEY, name);
+}
+
+function getCurrentTheme() {
+ return localStorage.getItem(KODAMA_THEME_KEY) || window.primaryTheme;
+}
+
+function selectTheme(themeName) {
+ storeSelectedTheme(themeName);
+ requestAnimationFrame(applyDynamicColorInvert);
+}
+
+function applyFavorTheme() {
+ const favoredTheme = getCurrentTheme();
+ if (favoredTheme) {
+ const selector = `input[type='radio'][id='${favoredTheme}']`;
+ const favoredOption = window.themeOptions.querySelector(selector);
+
+ if (favoredOption) {
+ favoredOption.checked = true;
+ } else if (window.primaryOption) {
+ // Fallback to primary theme, if the favored theme is not found
+ // e.g., when the theme options have changed, and the stored theme is no longer available.
+ window.primaryOption.checked = true;
+ }
+ }
+ applyDynamicColorInvert();
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+ window.themeOptions = document.getElementById("theme-options");
+ const templateContent = document.getElementById("theme-option-template").content;
+
+ customElements.define(
+ "theme-option",
+ class extends HTMLElement {
+ constructor() {
+ super();
+
+ const node = templateContent.cloneNode(true);
+ const themeName = this.getAttribute("name");
+
+ const input = node.querySelector("input");
+ input.setAttribute("id", themeName);
+ input.setAttribute("value", themeName);
+
+ const label = node.querySelector("label");
+ label.setAttribute("for", themeName);
+ label.addEventListener("click", () => selectTheme(themeName));
+
+ while (this.firstChild) {
+ label.appendChild(this.firstChild);
+ }
+ this.appendChild(node);
+ }
+ },
+ );
+
+ window.primaryOption = window.themeOptions.querySelector("input[type='radio']");
+ window.primaryTheme = primaryOption?.value;
+
+ applyFavorTheme();
+});
+
+/// SPSA filter solver
+///
+/// Also see:
+/// - https://en.wikipedia.org/wiki/Simultaneous_perturbation_stochastic_approximation
+/// - https://github.com/pranjalworm/css-filter-generator
+///
+/// Principle
+///
+/// invert → sepia → saturate → hue-rotate → brightness → contrast
+/// loss = |ΔR| + |ΔG| + |ΔB| + |ΔH| + |ΔS| + |ΔL|
+///
+
+const clamp = (value) => Math.max(0, Math.min(255, value));
+
+const multiply = ([r, g, b], matrix) => {
+ const nr = clamp(r * matrix[0] + g * matrix[1] + b * matrix[2]);
+ const ng = clamp(r * matrix[3] + g * matrix[4] + b * matrix[5]);
+ const nb = clamp(r * matrix[6] + g * matrix[7] + b * matrix[8]);
+ return [nr, ng, nb];
+};
+
+const hueRotate = ([r, g, b], angle = 0) => {
+ const rad = (angle / 180) * Math.PI;
+ const sin = Math.sin(rad);
+ const cos = Math.cos(rad);
+ const matrix = [
+ 0.213 + cos * 0.787 - sin * 0.213,
+ 0.715 - cos * 0.715 - sin * 0.715,
+ 0.072 - cos * 0.072 + sin * 0.928,
+ 0.213 - cos * 0.213 + sin * 0.143,
+ 0.715 + cos * 0.285 + sin * 0.140,
+ 0.072 - cos * 0.072 - sin * 0.283,
+ 0.213 - cos * 0.213 - sin * 0.787,
+ 0.715 - cos * 0.715 + sin * 0.715,
+ 0.072 + cos * 0.928 + sin * 0.072,
+ ];
+ return multiply([r, g, b], matrix);
+};
+
+const grayscale = ([r, g, b], value = 1) => {
+ const matrix = [
+ 0.2126 + 0.7874 * (1 - value),
+ 0.7152 - 0.7152 * (1 - value),
+ 0.0722 - 0.0722 * (1 - value),
+ 0.2126 - 0.2126 * (1 - value),
+ 0.7152 + 0.2848 * (1 - value),
+ 0.0722 - 0.0722 * (1 - value),
+ 0.2126 - 0.2126 * (1 - value),
+ 0.7152 - 0.7152 * (1 - value),
+ 0.0722 + 0.9278 * (1 - value),
+ ];
+ return multiply([r, g, b], matrix);
+};
+
+const sepia = ([r, g, b], value = 1) => {
+ const matrix = [
+ 0.393 + 0.607 * (1 - value),
+ 0.769 - 0.769 * (1 - value),
+ 0.189 - 0.189 * (1 - value),
+ 0.349 - 0.349 * (1 - value),
+ 0.686 + 0.314 * (1 - value),
+ 0.168 - 0.168 * (1 - value),
+ 0.272 - 0.272 * (1 - value),
+ 0.534 - 0.534 * (1 - value),
+ 0.131 + 0.869 * (1 - value),
+ ];
+ return multiply([r, g, b], matrix);
+};
+
+const saturate = ([r, g, b], value = 1) => {
+ const matrix = [
+ 0.213 + 0.787 * value,
+ 0.715 - 0.715 * value,
+ 0.072 - 0.072 * value,
+ 0.213 - 0.213 * value,
+ 0.715 + 0.285 * value,
+ 0.072 - 0.072 * value,
+ 0.213 - 0.213 * value,
+ 0.715 - 0.715 * value,
+ 0.072 + 0.928 * value,
+ ];
+ return multiply([r, g, b], matrix);
+};
+
+const linear = ([r, g, b], slope = 1, intercept = 0) => {
+ const scale = 255;
+ return [
+ clamp(r * slope + intercept * scale),
+ clamp(g * slope + intercept * scale),
+ clamp(b * slope + intercept * scale),
+ ];
+};
+
+const brightness = (rgb, value = 1) => linear(rgb, value);
+const contrast = (rgb, value = 1) => linear(rgb, value, -0.5 * value + 0.5);
+
+const invert = ([r, g, b], value = 1) => {
+ const nr = (value + r / 255 * (1 - 2 * value)) * 255;
+ const ng = (value + g / 255 * (1 - 2 * value)) * 255;
+ const nb = (value + b / 255 * (1 - 2 * value)) * 255;
+ return [clamp(nr), clamp(ng), clamp(nb)];
+};
+
+// Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
+const rgbToHsl = (r, g, b) => {
+ r /= 255; g /= 255; b /= 255;
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ let h, s, l = (max + min) / 2;
+ if (max === min) {
+ h = s = 0;
+ } else {
+ const d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+ return { h: h * 100, s: s * 100, l: l * 100 };
+};
+
+const applyFilters = (rgb, [inv, sep, sat, hue, bri, con]) => {
+ let color = [...rgb];
+ color = invert(color, inv / 100);
+ color = sepia(color, sep / 100);
+ color = saturate(color, sat / 100);
+ color = hueRotate(color, hue * 3.6);
+ color = brightness(color, bri / 100);
+ color = contrast(color, con / 100);
+ return color;
+};
+
+const lossFn = (targetRgb, filters) => {
+ const resultRgb = applyFilters([0, 0, 0], filters);
+ const targetHsl = rgbToHsl(...targetRgb);
+ const resultHsl = rgbToHsl(...resultRgb);
+ return (
+ Math.abs(resultRgb[0] - targetRgb[0]) +
+ Math.abs(resultRgb[1] - targetRgb[1]) +
+ Math.abs(resultRgb[2] - targetRgb[2]) +
+ Math.abs(resultHsl.h - targetHsl.h) +
+ Math.abs(resultHsl.s - targetHsl.s) +
+ Math.abs(resultHsl.l - targetHsl.l)
+ );
+};
+
+const fix = (value, idx) => {
+ let max = 100;
+ if (idx === 2) max = 7500; // saturate
+ else if (idx === 4 || idx === 5) max = 200; // brightness, contrast
+ if (idx === 3) { // hue-rotate
+ if (value > max) return value % max;
+ if (value < 0) return max + (value % max);
+ return value;
+ }
+ return Math.max(0, Math.min(max, value));
+};
+
+const spsa = (targetRgb, A, a, c, initial, iters) => {
+ const alpha = 1;
+ const gamma = 1 / 6;
+ let values = [...initial];
+ let best = [...values];
+ let bestLoss = Infinity;
+
+ for (let k = 0; k < iters; k++) {
+ const ck = c / Math.pow(k + 1, gamma);
+ const deltas = Array(6).fill().map(() => (Math.random() > 0.5 ? 1 : -1));
+ const highArgs = values.map((v, i) => v + ck * deltas[i]);
+ const lowArgs = values.map((v, i) => v - ck * deltas[i]);
+ const lossDiff = lossFn(targetRgb, highArgs) - lossFn(targetRgb, lowArgs);
+
+ values = values.map((v, i) => {
+ const g = (lossDiff / (2 * ck)) * deltas[i];
+ const ak = a[i] / Math.pow(A + k + 1, alpha);
+ return fix(v - ak * g, i);
+ });
+
+ const loss = lossFn(targetRgb, values);
+ if (loss < bestLoss) {
+ best = [...values];
+ bestLoss = loss;
+ }
+ }
+ return { values: best, loss: bestLoss };
+};
+
+// Threshold for acceptable color loss. This value determines when the SPSA algorithm stops early.
+// A loss below this value is considered visually indistinguishable for most use cases.
+// The value 25 was determined empirically to balance performance and color accuracy.
+const ACCEPTABLE_COLOR_LOSS = 25;
+
+const solveWide = (targetRgb) => {
+ const A = 5;
+ const c = 15;
+ const a = [60, 180, 18000, 600, 1.2, 1.2];
+ let best = { loss: Infinity, values: null };
+ for (let i = 0; best.loss > ACCEPTABLE_COLOR_LOSS && i < 3; i++) {
+ const initial = [50, 20, 3750, 50, 100, 100];
+ const result = spsa(targetRgb, A, a, c, initial, 1000);
+ if (result.loss < best.loss) best = result;
+ }
+ return best;
+};
+
+const solveNarrow = (targetRgb, wideResult) => {
+ const A = wideResult.loss;
+ const c = 2;
+ const A1 = A + 1;
+ const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
+ return spsa(targetRgb, A, a, c, wideResult.values, 500);
+};
+
+const cssFilterString = (filters) => {
+ const [inv, sep, sat, hue, bri, con] = filters.map(Math.round);
+ const hueDeg = Math.round(hue * 3.6);
+ return `filter: invert(${inv}%) sepia(${sep}%) saturate(${sat}%) hue-rotate(${hueDeg}deg) brightness(${bri}%) contrast(${con}%);`;
+};
+
+const solveColor = (targetRgb) => {
+ const wide = solveWide(targetRgb);
+ const narrow = solveNarrow(targetRgb, wide);
+ return {
+ values: narrow.values,
+ loss: narrow.loss,
+ filter: cssFilterString(narrow.values),
+ };
+};
+
+// Reuse a single canvas / context to avoid memory leaks
+const colorParseCanvas = document.createElement('canvas');
+colorParseCanvas.width = 1;
+colorParseCanvas.height = 1;
+const colorParseContext = colorParseCanvas.getContext('2d');
+
+const toRGBArray = (cssColor) => {
+ colorParseContext.fillStyle = cssColor;
+ colorParseContext.fillRect(0, 0, 1, 1);
+
+ // The `getImageData().data` returns a Uint8ClampedArray with 4 values per pixel (RGBA).
+ // also see: https://developer.mozilla.org/zh-CN/docs/Web/API/ImageData/data
+ const rgba = colorParseContext.getImageData(0, 0, 1, 1).data;
+ return Array.from(rgba.slice(0, 3));
+};
+
+const DYNAMIC_STYLE_ELEMENT_ID = 'dynamic-color-invert-style';
+
+function memorizedSolvedFilter(color) {
+ const colorKey = `solved-${color}`;
+ const storedFilterOption = localStorage.getItem(colorKey);
+ if (storedFilterOption) {
+ return storedFilterOption;
+ } else {
+ const filter = solveColor(color).filter;
+ localStorage.setItem(colorKey, filter);
+ return filter;
+ }
+}
+
+function applyDynamicColorInvert() {
+ const textColor = getComputedStyle(document.body).getPropertyValue('--text-color');
+ if (textColor && textColor.length > 0) {
+ const rgb = toRGBArray(textColor);
+ const filter = memorizedSolvedFilter(rgb);
+
+ const existingStyle = document.getElementById(DYNAMIC_STYLE_ELEMENT_ID);
+ if (existingStyle) {
+ existingStyle.textContent = `.color-invert {${filter}}`;
+ } else {
+ const style = document.createElement('style');
+ style.id = DYNAMIC_STYLE_ELEMENT_ID;
+ style.textContent = `.color-invert {${filter}}`;
+ document.head.appendChild(style);
+ }
+ }
+}