commit c01138a81c7052b87073395429500356ce4596f2
Author: jackyzha0 <j.zhao2k19@gmail.com>
Date:   Sun Jul 18 09:35:42 2021 -0400

    add base structure

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0de2938
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+public
+resources
+.idea
+content/.obsidian
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..147e2ca
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 jackyzha0
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0666d7b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# quartz
+Simple second brain and digital garden.
+
+```shell
+# Installation
+go install github.com/jackyzha0/hugo-obsidian
+
+# Run
+hugo-obsidian -input=content -output=data
+```
\ No newline at end of file
diff --git a/assets/darkmode.js b/assets/darkmode.js
new file mode 100644
index 0000000..93bf6da
--- /dev/null
+++ b/assets/darkmode.js
@@ -0,0 +1,26 @@
+// Darkmode toggle
+const toggleSwitch = document.querySelector('#darkmode-toggle')
+
+const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
+const currentTheme = localStorage.getItem('theme') ?? userPref
+
+if (currentTheme) {
+  document.documentElement.setAttribute('saved-theme', currentTheme);
+  if (currentTheme === 'dark') {
+    toggleSwitch.checked = true
+  }
+}
+
+const switchTheme = (e) => {
+  if (e.target.checked) {
+    document.documentElement.setAttribute('saved-theme', 'dark')
+    localStorage.setItem('theme', 'dark')
+  }
+  else {
+    document.documentElement.setAttribute('saved-theme', 'light')
+    localStorage.setItem('theme', 'light')
+  }
+}
+
+// listen for toggle
+toggleSwitch.addEventListener('change', switchTheme, false)
\ No newline at end of file
diff --git a/assets/darkmode.scss b/assets/darkmode.scss
new file mode 100644
index 0000000..dde5be6
--- /dev/null
+++ b/assets/darkmode.scss
@@ -0,0 +1,67 @@
+
+.darkmode {
+  text-align: right;
+
+  & > .toggle {
+    display: none;
+    box-sizing: border-box;
+
+    &:checked + .toggle-button:after {
+      left: 50%;
+    }
+
+    & + .toggle-button {
+      box-sizing: border-box;
+      outline: 0;
+      display: inline-block;
+      width: 3em;
+      height: 1.5em;
+      position: relative;
+      cursor: pointer;
+      border: 2px solid var(--gray);
+      user-select: none;
+      padding: 2px;
+      transition: all 0.2s ease;
+      border-radius: 2em;
+
+      &:after, &:before {
+        position: relative;
+        display: block;
+        box-sizing: border-box;
+        content: "";
+        width: 50%;
+        height: 100%;
+      }
+
+      &:before {
+        display: none;
+      }
+
+      &:after {
+        left: 0;
+        transition: all 0.2s ease;
+        background: var(--gray);
+        content: "";
+        border-radius: 1em;
+      }
+    }
+  }
+
+  & #dayIcon {
+    position: relative;
+    width: 20px;
+    height: 20px;
+    top: -1.5px;
+    margin: 0 7px;
+    fill: var(--gray);
+  }
+
+  & #nightIcon {
+    position: relative;
+    width: 18px;
+    height: 18px;
+    top: -2px;
+    margin: 0 7px;
+    fill: var(--gray);
+  }
+}
\ No newline at end of file
diff --git a/assets/syntax.scss b/assets/syntax.scss
new file mode 100644
index 0000000..27c37f4
--- /dev/null
+++ b/assets/syntax.scss
@@ -0,0 +1,99 @@
+/* Background */ .chroma { color: #f8f8f2; background-color: #282a36 }
+/* Other */ .chroma .x {  }
+/* Error */ .chroma .err {  }
+/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
+/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
+/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
+/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
+/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
+/* Keyword */ .chroma .k { color: #ff79c6 }
+/* KeywordConstant */ .chroma .kc { color: #ff79c6 }
+/* KeywordDeclaration */ .chroma .kd { color: #8be9fd; font-style: italic }
+/* KeywordNamespace */ .chroma .kn { color: #ff79c6 }
+/* KeywordPseudo */ .chroma .kp { color: #ff79c6 }
+/* KeywordReserved */ .chroma .kr { color: #ff79c6 }
+/* KeywordType */ .chroma .kt { color: #8be9fd }
+/* Name */ .chroma .n {  }
+/* NameAttribute */ .chroma .na { color: #50fa7b }
+/* NameBuiltin */ .chroma .nb { color: #8be9fd; font-style: italic }
+/* NameBuiltinPseudo */ .chroma .bp {  }
+/* NameClass */ .chroma .nc { color: #50fa7b }
+/* NameConstant */ .chroma .no {  }
+/* NameDecorator */ .chroma .nd {  }
+/* NameEntity */ .chroma .ni {  }
+/* NameException */ .chroma .ne {  }
+/* NameFunction */ .chroma .nf { color: #50fa7b }
+/* NameFunctionMagic */ .chroma .fm {  }
+/* NameLabel */ .chroma .nl { color: #8be9fd; font-style: italic }
+/* NameNamespace */ .chroma .nn {  }
+/* NameOther */ .chroma .nx {  }
+/* NameProperty */ .chroma .py {  }
+/* NameTag */ .chroma .nt { color: #ff79c6 }
+/* NameVariable */ .chroma .nv { color: #8be9fd; font-style: italic }
+/* NameVariableClass */ .chroma .vc { color: #8be9fd; font-style: italic }
+/* NameVariableGlobal */ .chroma .vg { color: #8be9fd; font-style: italic }
+/* NameVariableInstance */ .chroma .vi { color: #8be9fd; font-style: italic }
+/* NameVariableMagic */ .chroma .vm {  }
+/* Literal */ .chroma .l {  }
+/* LiteralDate */ .chroma .ld {  }
+/* LiteralString */ .chroma .s { color: #f1fa8c }
+/* LiteralStringAffix */ .chroma .sa { color: #f1fa8c }
+/* LiteralStringBacktick */ .chroma .sb { color: #f1fa8c }
+/* LiteralStringChar */ .chroma .sc { color: #f1fa8c }
+/* LiteralStringDelimiter */ .chroma .dl { color: #f1fa8c }
+/* LiteralStringDoc */ .chroma .sd { color: #f1fa8c }
+/* LiteralStringDouble */ .chroma .s2 { color: #f1fa8c }
+/* LiteralStringEscape */ .chroma .se { color: #f1fa8c }
+/* LiteralStringHeredoc */ .chroma .sh { color: #f1fa8c }
+/* LiteralStringInterpol */ .chroma .si { color: #f1fa8c }
+/* LiteralStringOther */ .chroma .sx { color: #f1fa8c }
+/* LiteralStringRegex */ .chroma .sr { color: #f1fa8c }
+/* LiteralStringSingle */ .chroma .s1 { color: #f1fa8c }
+/* LiteralStringSymbol */ .chroma .ss { color: #f1fa8c }
+/* LiteralNumber */ .chroma .m { color: #bd93f9 }
+/* LiteralNumberBin */ .chroma .mb { color: #bd93f9 }
+/* LiteralNumberFloat */ .chroma .mf { color: #bd93f9 }
+/* LiteralNumberHex */ .chroma .mh { color: #bd93f9 }
+/* LiteralNumberInteger */ .chroma .mi { color: #bd93f9 }
+/* LiteralNumberIntegerLong */ .chroma .il { color: #bd93f9 }
+/* LiteralNumberOct */ .chroma .mo { color: #bd93f9 }
+/* Operator */ .chroma .o { color: #ff79c6 }
+/* OperatorWord */ .chroma .ow { color: #ff79c6 }
+/* Punctuation */ .chroma .p {  }
+/* Comment */ .chroma .c { color: #6272a4 }
+/* CommentHashbang */ .chroma .ch { color: #6272a4 }
+/* CommentMultiline */ .chroma .cm { color: #6272a4 }
+/* CommentSingle */ .chroma .c1 { color: #6272a4 }
+/* CommentSpecial */ .chroma .cs { color: #6272a4 }
+/* CommentPreproc */ .chroma .cp { color: #ff79c6 }
+/* CommentPreprocFile */ .chroma .cpf { color: #ff79c6 }
+/* Generic */ .chroma .g {  }
+/* GenericDeleted */ .chroma .gd { color: #8b080b }
+/* GenericEmph */ .chroma .ge { text-decoration: underline }
+/* GenericError */ .chroma .gr {  }
+/* GenericHeading */ .chroma .gh { font-weight: bold }
+/* GenericInserted */ .chroma .gi { font-weight: bold }
+/* GenericOutput */ .chroma .go { color: #44475a }
+/* GenericPrompt */ .chroma .gp {  }
+/* GenericStrong */ .chroma .gs {  }
+/* GenericSubheading */ .chroma .gu { font-weight: bold }
+/* GenericTraceback */ .chroma .gt {  }
+/* GenericUnderline */ .chroma .gl { text-decoration: underline }
+/* TextWhitespace */ .chroma .w {  }
+
+.lntd:first-of-type > .chroma {
+  padding-right: 0;
+}
+
+.chroma code {
+  font-family: 'Fira Code' !important;
+  font-size: 0.85em;
+  line-height: 1em;
+  background: none;
+  padding: 0;
+}
+
+.chroma {
+  border-radius: 3px;
+  margin: 0;
+}
\ No newline at end of file
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..da90bbf
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,4 @@
+baseURL = "https://quartz.jzhao.xyz/"
+languageCode = "en-us"
+googleAnalytics = "UA-148413215-1"
+pygmentsUseClasses = true
\ No newline at end of file
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..e69de29
diff --git a/content/moc/directory.md b/content/moc/directory.md
new file mode 100644
index 0000000..e69de29
diff --git a/content/notes/config.md b/content/notes/config.md
new file mode 100644
index 0000000..e69de29
diff --git a/content/notes/setup.md b/content/notes/setup.md
new file mode 100644
index 0000000..e69de29
diff --git a/content/notes/troubleshooting.md b/content/notes/troubleshooting.md
new file mode 100644
index 0000000..e69de29
diff --git a/content/notes/welcome.md b/content/notes/welcome.md
new file mode 100644
index 0000000..e69de29
diff --git a/data/config.yaml b/data/config.yaml
new file mode 100644
index 0000000..8d0ae8a
--- /dev/null
+++ b/data/config.yaml
@@ -0,0 +1,11 @@
+name: Quartz Example Page
+description:
+  Here is the page description. This is an example Quartz site that details installation,
+  setup, customization, and troubleshooting for Quartz itself.
+page_title:
+  Quartz Example Page
+links:
+  - link_name: twitter
+    link: https://twitter.com/_jzhao
+  - link_name: github
+    link: https://github.com/jackyzha0
\ No newline at end of file
diff --git a/data/graphConfig.yaml b/data/graphConfig.yaml
new file mode 100644
index 0000000..089d5d3
--- /dev/null
+++ b/data/graphConfig.yaml
@@ -0,0 +1,11 @@
+enableLegend: false
+enableDrag: true
+enableZoom: false
+base:
+  node: "#284b63"
+  activeNode: "#f28482"
+  inactiveNode: "#a8b3bd"
+  link: "#babdbf"
+  activeLink: "#5a7282"
+paths:
+  - /moc: "#4388cc"
\ No newline at end of file
diff --git a/layouts/404.html b/layouts/404.html
new file mode 100644
index 0000000..e69de29
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
new file mode 100644
index 0000000..ccb3b93
--- /dev/null
+++ b/layouts/_default/baseof.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+{{ block "head" . }}
+{{ end }}
+
+<body>
+{{ block "main" . }}
+{{ end }}
+</body>
+</html>
\ No newline at end of file
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
new file mode 100644
index 0000000..c7ce881
--- /dev/null
+++ b/layouts/_default/single.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+{{ partial "head.html" . }}
+
+<body>
+<div class="singlePage">
+    <!-- Begin actual content -->
+    {{partial "darkmode.html" .}}
+    <article>
+        {{if .Title}}<h1>{{ .Title }}</h1>{{end}}
+        {{- .Content -}}
+    </article>
+    {{partial "footer.html" .}}
+</div>
+
+{{- with resources.Get "darkmode.js" | minify -}}
+<script>
+  {{.Content | safeJS }}
+</script>
+{{- end -}}
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/layouts/index.html b/layouts/index.html
new file mode 100644
index 0000000..e69de29
diff --git a/layouts/partials/backlinks.html b/layouts/partials/backlinks.html
new file mode 100644
index 0000000..2ae4976
--- /dev/null
+++ b/layouts/partials/backlinks.html
@@ -0,0 +1,9 @@
+<ol class="backlinks">
+    {{$curPage := strings.TrimRight "/" .Page.RelPermalink }}
+    {{$inbound := index $.Site.Data.linkIndex.index.backlinks $curPage}}
+    {{- range $inbound -}}
+    <li>
+        <a href="{{index . "source"}}">{{index . "source"}}</a>
+    </li>
+    {{- end -}}
+</ol>
\ No newline at end of file
diff --git a/layouts/partials/darkmode.html b/layouts/partials/darkmode.html
new file mode 100644
index 0000000..3d36d9a
--- /dev/null
+++ b/layouts/partials/darkmode.html
@@ -0,0 +1,16 @@
+<div class='darkmode'>
+    <label id="toggle-label-light" for='darkmode-toggle' tabindex="-1">
+        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="dayIcon" x="0px" y="0px" viewBox="0 0 35 35" style="enable-background:new 0 0 35 35;" xml:space="preserve">
+            <title>Light Mode</title>
+            <path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5    S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5    C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6    C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9    c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44    l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5    c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06    L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z     M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2    C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29    c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7    C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5    c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z" />
+        </svg>
+    </label>
+    <input class='toggle' id='darkmode-toggle' type='checkbox' tabindex="-1">
+    <label class='toggle-button' for='darkmode-toggle'></label>
+    <label id="toggle-label-dark" for='darkmode-toggle' tabindex="-1">
+        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="nightIcon" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background='new 0 0 100 100'" xml:space="preserve">
+            <title>Dark Mode</title>
+            <path d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571  C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23  c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369  c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65  c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z" />
+        </svg>
+    </label>
+</div>
\ No newline at end of file
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
new file mode 100644
index 0000000..3afeb8e
--- /dev/null
+++ b/layouts/partials/footer.html
@@ -0,0 +1,21 @@
+<div>
+    <hr/>
+    {{partial "graph.html" .}}
+    <ul id="sub-nav">
+        <li><a href="/">↳ Take me home</a></li>
+    </ul>
+</div>
+
+<!-- Contact Info -->
+<div id="contact_buttons" class="lt-centre">
+    <footer>
+        <p>made by {{ $.Site.Data.config.name }}, © {{ dateFormat "2006" now }}</p>
+        <a href="https://github.com/jackyzha0/quartz">source</a>
+        {{ if not .IsHome }}
+        <a href="/">home</a>
+        {{end}}
+        {{- range $.Site.Data.links.footer -}}
+        <a href="{{.link}}">{{.link_name}}</a>
+        {{- end -}}
+    </footer>
+</div>
\ No newline at end of file
diff --git a/layouts/partials/graph.html b/layouts/partials/graph.html
new file mode 100644
index 0000000..ea9cf1a
--- /dev/null
+++ b/layouts/partials/graph.html
@@ -0,0 +1,218 @@
+<script src="https://cdn.jsdelivr.net/npm/d3@6"></script>
+<div id="graph-container"></div>
+<style>
+    :root {
+        --g-node: {{.Site.Data.graphConfig.base.node}};
+        --g-node-active: {{.Site.Data.graphConfig.base.activeNode}};
+        --g-node-inactive: {{.Site.Data.graphConfig.base.inactiveNode}};
+        --g-link: {{.Site.Data.graphConfig.base.link}};
+        --g-link-active: {{.Site.Data.graphConfig.base.activeLink}};
+    }
+</style>
+<script>
+  const index = {{$.Site.Data.linkIndex.index}}
+  const links = {{$.Site.Data.linkIndex.links}}
+  const curPage = {{ strings.TrimRight "/" .Page.RelPermalink }}
+  const pathColors = {{$.Site.Data.graphConfig.paths}}
+
+  const parseIdsFromLinks = (links) => [...(new Set(links.flatMap(link => ([link.source, link.target]))))]
+
+  const data = {
+    nodes: parseIdsFromLinks(links).map(id => ({id})),
+    links,
+  }
+
+  const color = (d) => {
+    if (d.id === curPage) {
+      return "var(--g-node-active)"
+    }
+
+    for (const pathColor of pathColors) {
+      const path = Object.keys(pathColor)[0]
+      const colour = pathColor[path]
+      if (d.id.startsWith(path)) {
+        return colour
+      }
+    }
+
+    return "var(--g-node)"
+  }
+
+  const drag = simulation => {
+    function dragstarted(event, d) {
+      if (!event.active) simulation.alphaTarget(1).restart();
+      d.fx = d.x;
+      d.fy = d.y;
+    }
+
+    function dragged(event,d) {
+      d.fx = event.x;
+      d.fy = event.y;
+    }
+
+    function dragended(event,d) {
+      if (!event.active) simulation.alphaTarget(0);
+      d.fx = null;
+      d.fy = null;
+    }
+
+    const enableDrag = {{$.Site.Data.graphConfig.enableDrag}}
+    const noop = () => {}
+    return d3.drag()
+      .on("start", enableDrag ? dragstarted : noop)
+      .on("drag", enableDrag ? dragged : noop)
+      .on("end", enableDrag ? dragended : noop);
+  }
+
+  const height = 400
+  const width = document.getElementById("graph-container").offsetWidth
+
+  const simulation = d3.forceSimulation(data.nodes)
+    .force("charge", d3.forceManyBody().strength(-20))
+    .force("link", d3.forceLink(data.links)
+      .id(d => d.id)
+    )
+    .force("center", d3.forceCenter());
+
+  const svg = d3.select('#graph-container')
+    .append('svg')
+    .attr('width', width)
+    .attr('height', height)
+    .attr("viewBox", [-width / 2, -height / 2, width, height]);
+
+  // legend
+  const enableLegend = {{$.Site.Data.graphConfig.enableLegend}}
+  if (enableLegend) {
+    const legend = [
+      {"Current": "var(--g-node-active)"},
+      {"Note": "var(--g-node)"},
+      ...pathColors
+    ]
+    legend.forEach((legendEntry, i) => {
+      const key = Object.keys(legendEntry)[0]
+      const colour = legendEntry[key]
+      svg.append("circle").attr("cx", -width/2 + 20).attr("cy", height/2 - 30 * (i+1)).attr("r", 6).style("fill", colour)
+      svg.append("text").attr("x", -width/2 + 40).attr("y", height/2 - 30 * (i+1)).text(key).style("font-size", "15px").attr("alignment-baseline","middle")
+    })
+  }
+
+  // draw links between nodes
+  const link = svg.append("g")
+    .selectAll("line")
+    .data(data.links)
+    .join("line")
+    .attr("class", "link")
+    .attr("stroke", "var(--g-link)")
+    .attr("stroke-width", 2)
+    .attr("data-source", d => d.source.id)
+    .attr("data-target", d => d.target.id)
+
+  // svg groups
+  const graphNode = svg.append("g")
+    .selectAll("g")
+    .data(data.nodes)
+    .enter().append("g")
+
+  // draw individual nodes
+  const node = graphNode.append("circle")
+    .attr("class", "node")
+    .attr("id", (d) => d.id)
+    .attr("r", (d) => {
+      const numOut = index.links[d.id]?.length || 0
+      const numIn = index.backlinks[d.id]?.length || 0
+      return 3 + (numOut + numIn) / 4
+    })
+    .attr("fill", color)
+    .style("cursor", "pointer")
+    .on("click", (_, d) => {
+      window.location.href = d.id;
+    })
+    .on("mouseover", function (_, d) {
+      d3.selectAll(".node")
+        .transition()
+        .duration(100)
+        .attr("fill", "var(--g-node-inactive)")
+
+      const neighbours = parseIdsFromLinks([...(index.links[d.id] || []), ...(index.backlinks[d.id] || [])])
+      const neighbourNodes = d3.selectAll(".node").filter(d => neighbours.includes(d.id))
+      const currentId = d.id
+      const links = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId)
+
+      // highlight neighbour nodes
+      neighbourNodes
+        .transition()
+        .duration(200)
+        .attr("fill", color)
+
+      // highlight links
+      links
+        .transition()
+        .duration(200)
+        .attr("stroke", "var(--g-link-active)")
+
+      // show text for self
+      d3.select(this.parentNode)
+        .select("text")
+        .raise()
+        .transition()
+        .duration(200)
+        .style("opacity", 1)
+    }).on("mouseleave", function (_,d) {
+      d3.selectAll(".node")
+        .transition()
+        .duration(200)
+        .attr("fill", color)
+
+      const currentId = d.id
+      const links = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId)
+
+      links
+        .transition()
+        .duration(200)
+        .attr("stroke", "var(--g-link)")
+
+      d3.select(this.parentNode)
+        .select("text")
+        .transition()
+        .duration(200)
+        .style("opacity", 0)
+    })
+    .call(drag(simulation));
+
+  // draw labels
+  const labels = graphNode.append("text")
+    .attr("dx", 12)
+    .attr("dy", ".35em")
+    .text((d) => d.id)
+    .style("opacity", 0)
+    .style("pointer-events", "none")
+    .call(drag(simulation));
+
+  // set panning
+  const enableZoom = {{$.Site.Data.graphConfig.enableZoom}}
+  if (enableZoom) {
+    svg.call(d3.zoom()
+      .extent([[0, 0], [width, height]])
+      .scaleExtent([0.25, 4])
+      .on("zoom", ({transform}) => {
+        link.attr("transform", transform);
+        node.attr("transform", transform);
+        labels.attr("transform", transform);
+      }));
+  }
+
+  // progress the simulation
+  simulation.on("tick", () => {
+    link
+      .attr("x1", d => d.source.x)
+      .attr("y1", d => d.source.y)
+      .attr("x2", d => d.target.x)
+      .attr("y2", d => d.target.y);
+    node
+      .attr("cx", d => d.x)
+      .attr("cy", d => d.y);
+    labels
+      .attr("x", d => d.x)
+      .attr("y", d => d.y);
+  });
+</script>
\ No newline at end of file
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
new file mode 100644
index 0000000..5e42a2c
--- /dev/null
+++ b/layouts/partials/head.html
@@ -0,0 +1,24 @@
+<head>
+    <link rel="preconnect" href="https://www.googletagmanager.com">
+    <link crossorigin rel="preconnect" href="https://www.google-analytics.com">
+    {{ template "_internal/google_analytics_async.html" . }}
+
+    <!-- Meta tags -->
+    <meta charset="UTF-8">
+    <meta name="description" content="{{$.Site.Data.config.description}}">
+    <title>{{$.Site.Data.config.page_title}}</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="shortcut icon" type="image/png" href="/icon.png" />
+
+    <!-- CSS Stylesheets and Fonts -->
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Source+Sans+Pro:wght@400;700&family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
+    {{ $css := slice "darkmode.scss" "syntax.scss"}}
+    {{range $css}}
+    {{$sass := resources.Get . | resources.ToCSS }}
+    {{with $sass | minify}}
+    <style>
+        {{.Content | safeCSS}}
+    </style>
+    {{end}}
+    {{end}}
+</head>
\ No newline at end of file
diff --git a/layouts/partials/header.html b/layouts/partials/header.html
new file mode 100644
index 0000000..e69de29
diff --git a/static/icon.png b/static/icon.png
new file mode 100644
index 0000000..7294a8b
Binary files /dev/null and b/static/icon.png differ