Refactors site to use interactive terminal UI

Moves site from static HTML to a dynamic terminal interface.

This commit represents a major overhaul, introducing:
- A functional terminal emulator implemented in JavaScript.
- Command parsing and execution framework.
- Various terminal commands for navigation and utility functions.
- SASS conversion.

The old CSS and simple HTML are completely removed. A new Hugo theme is implemented.
This commit is contained in:
Dan 2025-12-08 13:46:35 +00:00
parent d22b4ec65a
commit 23369f4ace
46 changed files with 3524 additions and 860 deletions

View file

@ -1,121 +0,0 @@
{{- define "main" }}
{{- if (and site.Params.profileMode.enabled .IsHome) }}
{{- partial "index_profile.html" . }}
{{- else }} {{/* if not profileMode */}}
{{- if not .IsHome | and .Title }}
<header class="page-header">
{{- partial "breadcrumbs.html" . }}
<h1>
{{ .Title }}
{{- if and (or (eq .Kind `term`) (eq .Kind `section`)) (.Param "ShowRssButtonInSectionTermList") }}
{{- with .OutputFormats.Get "rss" }}
<a href="{{ .RelPermalink }}" title="RSS" aria-label="RSS">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" height="23">
<path d="M4 11a9 9 0 0 1 9 9" />
<path d="M4 4a16 16 0 0 1 16 16" />
<circle cx="5" cy="19" r="1" />
</svg>
</a>
{{- end }}
{{- end }}
</h1>
{{- if .Description }}
<div class="post-description">
{{ .Description | markdownify }}
</div>
{{- end }}
</header>
{{- end }}
{{- if .Content }}
<div class="post-content">
{{- if not (.Param "disableAnchoredHeadings") }}
{{- partial "anchored_headings.html" .Content -}}
{{- else }}{{ .Content }}{{ end }}
</div>
{{- end }}
{{- $pages := union .RegularPages .Sections }}
{{- if .IsHome }}
{{- $pages = where site.RegularPages "Type" "in" site.Params.mainSections }}
{{- $pages = where $pages "Params.hiddenInHomeList" "!=" "true" }}
{{- end }}
{{- $paginator := .Paginate $pages }}
{{- if and .IsHome site.Params.homeInfoParams (eq $paginator.PageNumber 1) }}
{{- partial "home_info.html" . }}
{{- end }}
{{- $term := .Data.Term }}
{{- range $index, $page := $paginator.Pages }}
{{- $class := "post-entry" }}
{{- $user_preferred := or site.Params.disableSpecial1stPost site.Params.homeInfoParams }}
{{- if (and $.IsHome (eq $paginator.PageNumber 1) (eq $index 0) (not $user_preferred)) }}
{{- $class = "first-entry" }}
{{- else if $term }}
{{- $class = "post-entry tag-entry" }}
{{- end }}
<article class="{{ $class }}">
{{- $isHidden := (.Param "cover.hiddenInList") | default (.Param "cover.hidden") | default false }}
{{- partial "cover.html" (dict "cxt" . "IsSingle" false "isHidden" $isHidden) }}
<header class="entry-header">
<h2 class="entry-hint-parent">
{{- .Title }}
{{- if .Draft }}
<span class="entry-hint" title="Draft">
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" fill="currentColor">
<path
d="M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" />
</svg>
</span>
{{- end }}
</h2>
</header>
{{- if (ne (.Param "hideSummary") true) }}
<div class="entry-content">
<p>{{ .Summary | plainify | htmlUnescape }}{{ if .Truncated }}...{{ end }}</p>
</div>
{{- end }}
{{- if not (.Param "hideMeta") }}
<footer class="entry-footer">
{{- partial "post_meta.html" . -}}
</footer>
{{- end }}
<a class="entry-link" aria-label="post link to {{ .Title | plainify }}" href="{{ .Permalink }}"></a>
</article>
{{- end }}
{{- if gt $paginator.TotalPages 1 }}
<footer class="page-footer">
<nav class="pagination">
{{- if $paginator.HasPrev }}
<a class="prev" href="{{ $paginator.Prev.URL | absURL }}">
«&nbsp;{{ i18n "prev_page" }}&nbsp;
{{- if (.Param "ShowPageNums") }}
{{- sub $paginator.PageNumber 1 }}/{{ $paginator.TotalPages }}
{{- end }}
</a>
{{- end }}
{{- if $paginator.HasNext }}
<a class="next" href="{{ $paginator.Next.URL | absURL }}">
{{- i18n "next_page" }}&nbsp;
{{- if (.Param "ShowPageNums") }}
{{- add 1 $paginator.PageNumber }}/{{ $paginator.TotalPages }}
{{- end }}&nbsp;»
</a>
{{- end }}
</nav>
</footer>
{{- end }}
{{- end }}{{/* end profileMode */}}
{{- end }}{{- /* end main */ -}}

View file

@ -1,69 +0,0 @@
{{- define "main" }}
<article class="post-single">
<header class="post-header">
{{ partial "breadcrumbs.html" . }}
<h1 class="post-title entry-hint-parent">
{{ .Title }}
{{- if .Draft }}
<span class="entry-hint" title="Draft">
<svg xmlns="http://www.w3.org/2000/svg" height="35" viewBox="0 -960 960 960" fill="currentColor">
<path
d="M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" />
</svg>
</span>
{{- end }}
</h1>
{{- if .Description }}
<div class="post-description">
{{ .Description }}
</div>
{{- end }}
{{- if not (.Param "hideMeta") }}
<div class="post-meta">
{{- partial "post_meta.html" . -}}
{{- partial "post_canonical.html" . -}}
</div>
{{- end }}
</header>
{{- $isHidden := (.Param "cover.hiddenInSingle") | default (.Param "cover.hidden") | default false }}
{{- partial "cover.html" (dict "cxt" . "IsSingle" true "isHidden" $isHidden) }}
{{- if (.Param "ShowToc") }}
{{- partial "toc.html" . }}
{{- end }}
{{- if .Content }}
<div class="post-content">
{{- if not (.Param "disableAnchoredHeadings") }}
{{- partial "anchored_headings.html" .Content -}}
{{- else }}{{ .Content }}{{ end }}
</div>
{{- end }}
<footer class="post-footer">
{{- $tags := .Language.Params.Taxonomies.tag | default "tags" }}
<ul class="post-tags">
{{- range ($.GetTerms $tags) }}
<li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
{{- end }}
</ul>
{{- if ne .Params.disableCoffee true }}
{{- partial "buy_me_a_coffee.html" . -}}
{{- end }}
{{- if (.Param "ShowPostNavLinks") }}
{{- partial "post_nav_links.html" . }}
{{- end }}
{{- if (and site.Params.ShowShareButtons (ne .Params.disableShare true)) }}
{{- partial "share_icons.html" . -}}
{{- end }}
</footer>
{{- if (.Param "comments") }}
{{- partial "comments.html" . }}
{{- end }}
</article>
{{- end }}{{/* end main */}}

401
layouts/index.html Normal file
View file

@ -0,0 +1,401 @@
{{ define "main" }}
<div class="wall"></div>
<!-- Neon sign above monitor -->
<div class="neon-sign">
<div class="neon-text">ritual.sh</div>
</div>
<!-- Sticky notes -->
<div class="sticky-note note1">fix bugs</div>
<div class="sticky-note note2">pwd:<br />puppies</div>
<div class="sticky-note note3">finish<br />coffee</div>
<div class="sticky-note note4">CALL<br />WIFE</div>
<!-- Papers pinned to wall -->
<div class="wall-paper paper1"></div>
<div class="wall-paper paper2"></div>
<!-- X-Files "I want to believe" poster -->
<div class="xfiles-poster">
<div class="xfiles-content">
<div class="ufo-illustration">
<div class="ufo-dome"></div>
<div class="ufo-body">
<div class="ufo-lights">
<div class="ufo-light"></div>
<div class="ufo-light"></div>
<div class="ufo-light"></div>
<div class="ufo-light"></div>
</div>
</div>
<div class="light-beam"></div>
</div>
<div class="believe-text">I WANT TO<br />BELIEVE</div>
</div>
</div>
<!-- Poster on wall -->
<div class="poster">
<div class="poster-image" id="posterImage">
<script src="/js/nowplaying.js"></script>
<div class="nowplayingcard">
<div class="nowplayingcontainer-inner">
<img id="trackart" src="" />
</div>
</div>
</div>
<div class="now-playing-note">Recently Played</div>
<div class="now-playing-artist">
<div class="trackInfo">
<span id="artist"></span>
<a id="tracktitle" style="display: none"></a>
<a id="album" style="display: none"></a>
</div>
</div>
</div>
<script>
setInterval(
getLastTrack("ritualplays", "3a4fef48fecc593d25e0f9a40df1fefe"),
10 * 1000,
);
</script>
<!-- Desk -->
<div class="desk"></div>
<!-- Curved cables on desk using SVG -->
<svg
class="cable-svg"
style="position: absolute; bottom: 8%; left: 15%; width: 200px; height: 150px"
>
<path
d="M 20 10 Q 40 60, 80 120"
stroke="#222"
stroke-width="3"
fill="none"
/>
</svg>
<svg
class="cable-svg"
style="
position: absolute;
bottom: 8%;
right: 20%;
width: 180px;
height: 140px;
"
>
<path
d="M 150 20 Q 100 70, 40 130"
stroke="#1a1a1a"
stroke-width="3"
fill="none"
/>
</svg>
<svg
class="cable-svg"
style="position: absolute; bottom: 8%; left: 28%; width: 220px; height: 160px"
>
<path
d="M 10 30 Q 80 40, 160 140"
stroke="#252525"
stroke-width="2.5"
fill="none"
/>
</svg>
<svg
class="cable-svg"
style="
position: absolute;
bottom: 8%;
right: 35%;
width: 150px;
height: 120px;
"
>
<path
d="M 130 15 Q 70 50, 20 110"
stroke="#2a2a2a"
stroke-width="2.5"
fill="none"
/>
</svg>
<svg
class="cable-svg"
style="position: absolute; bottom: 8%; left: 42%; width: 160px; height: 130px"
>
<path
d="M 30 20 Q 60 80, 120 125"
stroke="#1f1f1f"
stroke-width="2"
fill="none"
/>
</svg>
<div class="monitor-stand-small"></div>
<!-- Desk monitor -->
<div class="secondary-screen desk-monitor">
<div class="screen-display large crt">
SYSTEM LOAD<br />
CPU: 67%<br />
RAM: 4.2/8GB<br />
NET: 2.4MB/s<br />
DISK: 89%<br />
<span class="cursor-blink">_</span>
</div>
<div class="monitor-stand-small"></div>
</div>
<!-- Desk clutter -->
<div class="desk-item keyboard"></div>
<div class="desk-item mouse"></div>
<div class="desk-item coffee-mug"></div>
<!-- VU Meter on desk -->
<div class="vu-meter">
<div class="vu-meter-body">
<div class="vu-meter-screen">
<div class="vu-bars crt">
<div class="vu-bar" style="--delay: 0s; --height: 45%"></div>
<div class="vu-bar" style="--delay: 0.1s; --height: 65%"></div>
<div class="vu-bar" style="--delay: 0.2s; --height: 80%"></div>
<div class="vu-bar" style="--delay: 0.3s; --height: 55%"></div>
<div class="vu-bar" style="--delay: 0.4s; --height: 90%"></div>
<div class="vu-bar" style="--delay: 0.5s; --height: 70%"></div>
<div class="vu-bar" style="--delay: 0.6s; --height: 85%"></div>
<div class="vu-bar" style="--delay: 0.7s; --height: 60%"></div>
<div class="vu-bar" style="--delay: 0.8s; --height: 75%"></div>
<div class="vu-bar" style="--delay: 0.9s; --height: 50%"></div>
<div class="vu-bar" style="--delay: 1s; --height: 65%"></div>
<div class="vu-bar" style="--delay: 1.1s; --height: 40%"></div>
<div class="vu-bar" style="--delay: 1.2s; --height: 55%"></div>
<div class="vu-bar" style="--delay: 1.3s; --height: 70%"></div>
<div class="vu-bar" style="--delay: 1.4s; --height: 45%"></div>
<div class="vu-bar" style="--delay: 1.5s; --height: 35%"></div>
</div>
<!-- Peak indicator line -->
<div class="vu-peak-line"></div>
</div>
<!-- VU Meter LEDs -->
<div class="vu-leds">
<div class="vu-led green"></div>
<div class="vu-led green"></div>
<div class="vu-led yellow"></div>
<div class="vu-led red"></div>
</div>
</div>
</div>
<!-- iPod group - container keeps all elements positioned relative to each other -->
<div class="ipod-group">
<div class="ipod">
<div class="ipod-wheel"></div>
</div>
<!-- Earbud cables within the group container -->
<svg class="ipod-cables" viewBox="0 0 150 100" style="overflow: visible">
<defs>
<linearGradient id="cableGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color: #6b4fb3" />
<stop offset="100%" style="stop-color: #4169e1" />
</linearGradient>
</defs>
<!-- Main cable: plugs in at top RIGHT of iPod, makes a big loop to the LEFT, then hangs down -->
<!-- Start at x=85 (top right), arc way left to x=30, then come back down on the left side -->
<path
d="M 85,3 C 85,-12 75,-22 55,-22 C 35,-22 25,-15 25,0 C 25,15 28,35 32,50 L 35,65"
stroke="url(#cableGradient)"
stroke-width="2.5"
fill="none"
stroke-linecap="round"
/>
<!-- Left branch to left earbud -->
<path
d="M 35,65 C 28,72 20,82 5,88"
stroke="url(#cableGradient)"
stroke-width="1.8"
fill="none"
stroke-linecap="round"
/>
<!-- Right branch to right earbud -->
<path
d="M 35,65 C 55,73 95,82 125,83"
stroke="url(#cableGradient)"
stroke-width="1.8"
fill="none"
stroke-linecap="round"
/>
</svg>
<div class="earbud earbud-left"></div>
<div class="earbud earbud-right"></div>
</div>
<!-- Widgets and gadgets -->
<div class="widget router"></div>
<div class="widget hard-drive"></div>
{{ partial "lavalamp.html" . }}
<!-- CRT Monitor -->
<div class="crt-container">
<div class="crt-monitor">
<div class="crt-screen">
<div class="content crt">
<div id="terminal">
<div id="output"></div>
<div id="input-container" class="input-line hidden">
<span class="prompt">></span>
<input
type="text"
id="input"
autocomplete="off"
spellcheck="false"
/>
</div>
</div>
</div>
</div>
</div>
<div class="monitor-stand">
<div class="stand-neck"></div>
<div class="stand-base"></div>
</div>
<!-- Wall-mounted monitors -->
<div class="secondary-screen wall-monitor-1">
<div class="screen-display crt">
> netstat -an<br />
<pre>
tcp 0 0 127.0.0.1:33842 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:41267 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.54:53 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:52918 127.0.0.1:38471 ESTABLISHED
tcp 0 0 192.168.1.147:44321 52.143.67.201:443 ESTABLISHED
tcp 0 0 192.168.1.147:39854 104.26.15.88:80 TIME_WAIT
tcp 0 0 127.0.0.1:38471 127.0.0.1:52918 ESTABLISHED
tcp 0 0 127.0.0.1:56732 127.0.0.1:8080 ESTABLISHED
tcp 0 0 192.168.1.147:41209 13.107.42.16:443 ESTABLISHED
tcp 0 0 127.0.0.1:49563 127.0.0.1:8080 TIME_WAIT
Proto RefCnt Flags Type State I-Node Path
unix 3 [ ] STREAM CONNECTED 50896 /run/user/1000/bus
</pre
>
</div>
</div>
<div class="secondary-screen wall-monitor-2">
<div class="screen-display tiny cyan crt">
<div class="scroll-text">
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
[PKT] 172.16.0.1:22<br />
[PKT] 192.168.1.1:443<br />
[PKT] 10.0.0.15:8080<br />
</div>
</div>
</div>
<div class="secondary-screen wall-monitor-3">
<div class="screen-display crt">
PING 8.8.8.8<br />
64 bytes: 12ms<br />
64 bytes: 11ms<br />
64 bytes: 13ms<br />
<span class="cursor-blink">_</span>
</div>
</div>
<div class="secondary-screen wall-monitor-4">
<div class="screen-display tiny amber crt">
> tail -f /var/log<br />
[INFO] Process OK<br />
[WARN] High load detected - time for coffee break<br />
[INFO] Connected to database (it's in a relationship now)<br />
[ERROR] 404: Motivation not found<br />
[WARN] Firewall detected actual fire, calling emergency services<br />
[INFO] Successfully hacked into mainframe (jk it's just localhost)<br />
[ERROR] Keyboard not found. Press F1 to continue.<br />
[WARN] Too many open tabs. Browser having existential crisis.<br />
[INFO] Ping 127.0.0.1 - there's no place like home<br />
[ERROR] SQL injection attempt detected. Nice try, Bobby Tables.<br />
<span class="cursor-blink">_</span>
</div>
</div>
</div>
{{ end }}

View file

@ -1,3 +0,0 @@
<div class="buy-me-a-coffee">
If you found this article useful, consider <a href="https://ko-fi.com/ritual" target="_blank">buying me a coffee</a>. Every tip received makes my electricity bill seem less bad.
</div>

View file

@ -1,7 +0,0 @@
<script src="https://utteranc.es/client.js"
repo="unbolt/ritual.sh-comments"
issue-term="pathname"
theme="photon-dark"
crossorigin="anonymous"
async>
</script>

View file

@ -1,130 +0,0 @@
{{- if not (.Param "hideFooter") }}
<footer class="footer">
{{- if site.Copyright }}
<span>{{ site.Copyright | markdownify }}</span>
{{- else }}
<span>&copy; {{ now.Year }} <a href="{{ "" | absLangURL }}">Dan Baker</a></span>
{{- end }}
</footer>
{{- end }}
{{- if (not site.Params.disableScrollToTop) }}
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
<path d="M12 6H0l6-6z" />
</svg>
</a>
{{- end }}
{{- partial "extend_footer.html" . }}
<script>
let menu = document.getElementById('menu')
if (menu) {
menu.scrollLeft = localStorage.getItem("menu-scroll-position");
menu.onscroll = function () {
localStorage.setItem("menu-scroll-position", menu.scrollLeft);
}
}
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
var id = this.getAttribute("href").substr(1);
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
behavior: "smooth"
});
} else {
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
}
if (id === "top") {
history.replaceState(null, null, " ");
} else {
history.pushState(null, null, `#${id}`);
}
});
});
</script>
{{- if (not site.Params.disableScrollToTop) }}
<script>
var mybutton = document.getElementById("top-link");
window.onscroll = function () {
if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
mybutton.style.visibility = "visible";
mybutton.style.opacity = "1";
} else {
mybutton.style.visibility = "hidden";
mybutton.style.opacity = "0";
}
};
</script>
{{- end }}
{{- if (not site.Params.disableThemeToggle) }}
<script>
document.getElementById("theme-toggle").addEventListener("click", () => {
if (document.body.className.includes("dark")) {
document.body.classList.remove('dark');
localStorage.setItem("pref-theme", 'light');
} else {
document.body.classList.add('dark');
localStorage.setItem("pref-theme", 'dark');
}
})
</script>
{{- end }}
{{- if (and (eq .Kind "page") (ne .Layout "archives") (ne .Layout "search") (.Param "ShowCodeCopyButtons")) }}
<script>
document.querySelectorAll('pre > code').forEach((codeblock) => {
const container = codeblock.parentNode.parentNode;
const copybutton = document.createElement('button');
copybutton.classList.add('copy-code');
copybutton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
function copyingDone() {
copybutton.innerHTML = '{{- i18n "code_copied" | default "copied!" }}';
setTimeout(() => {
copybutton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
}, 2000);
}
copybutton.addEventListener('click', (cb) => {
if ('clipboard' in navigator) {
navigator.clipboard.writeText(codeblock.textContent);
copyingDone();
return;
}
const range = document.createRange();
range.selectNodeContents(codeblock);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
try {
document.execCommand('copy');
copyingDone();
} catch (e) { };
selection.removeRange(range);
});
if (container.classList.contains("highlight")) {
container.appendChild(copybutton);
} else if (container.parentNode.firstChild == container) {
// td containing LineNos
} else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
// table containing LineNos and code
codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
} else {
// code blocks not having highlight as parent class
codeblock.parentNode.appendChild(copybutton);
}
});
</script>
{{- end }}

View file

@ -1,157 +0,0 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{{- if hugo.IsProduction | or (eq site.Params.env "production") | and (ne .Params.robotsNoIndex true) }}
<meta name="robots" content="index, follow">
{{- else }}
<meta name="robots" content="noindex, nofollow">
{{- end }}
{{- /* Title */}}
<title>{{ if .IsHome }}{{ else }}{{ if .Title }}{{ .Title }} | {{ end }}{{ end }}{{ site.Title }}</title>
{{- /* Meta */}}
{{- if .IsHome }}
{{ with site.Params.keywords -}}<meta name="keywords" content="{{- range $i, $e := . }}{{ if $i }}, {{ end }}{{ $e }}{{ end }}">{{ end }}
{{- else }}
<meta name="keywords" content="{{ if .Params.keywords -}}
{{- range $i, $e := .Params.keywords }}{{ if $i }}, {{ end }}{{ $e }}{{ end }} {{- else }}
{{- range $i, $e := .Params.tags }}{{ if $i }}, {{ end }}{{ $e }}{{ end }} {{- end -}}">
{{- end }}
<meta name="description" content="{{- with .Description }}{{ . }}{{- else }}{{- if or .IsPage .IsSection}}
{{- .Summary | default (printf "%s - %s" .Title site.Title) }}{{- else }}
{{- with site.Params.description }}{{ . }}{{- end }}{{- end }}{{- end -}}">
<meta name="author" content="{{ (partial "author.html" . ) }}">
<link rel="canonical" href="{{ if .Params.canonicalURL -}} {{ trim .Params.canonicalURL " " }} {{- else -}} {{ .Permalink }} {{- end }}">
{{- if site.Params.analytics.google.SiteVerificationTag }}
<meta name="google-site-verification" content="{{ site.Params.analytics.google.SiteVerificationTag }}">
{{- end }}
{{- if site.Params.analytics.yandex.SiteVerificationTag }}
<meta name="yandex-verification" content="{{ site.Params.analytics.yandex.SiteVerificationTag }}">
{{- end }}
{{- if site.Params.analytics.bing.SiteVerificationTag }}
<meta name="msvalidate.01" content="{{ site.Params.analytics.bing.SiteVerificationTag }}">
{{- end }}
{{- if site.Params.analytics.naver.SiteVerificationTag }}
<meta name="naver-site-verification" content="{{ site.Params.analytics.naver.SiteVerificationTag }}">
{{- end }}
{{- /* Styles */}}
{{- /* includes */}}
{{- $includes := slice }}
{{- $includes = $includes | append (" " | resources.FromString "assets/css/includes-blank.css")}}
{{- if not (eq site.Params.assets.disableScrollBarStyle true) }}
{{- $ScrollStyle := (resources.Get "css/includes/scroll-bar.css") }}
{{- $includes = (append $ScrollStyle $includes) }}
{{- end }}
{{- $includes_all := $includes | resources.Concat "assets/css/includes.css" }}
{{- $theme_vars := (resources.Get "css/core/theme-vars.css") }}
{{- $reset := (resources.Get "css/core/reset.css") }}
{{- $media := (resources.Get "css/core/zmedia.css") }}
{{- $license_css := (resources.Get "css/core/license.css") }}
{{- $common := (resources.Match "css/common/*.css") | resources.Concat "assets/css/common.css" }}
{{- /* markup.highlight.noClasses should be set to `false` */}}
{{- $chroma_styles := (resources.Get "css/includes/chroma-styles.css") }}
{{- $chroma_mod := (resources.Get "css/includes/chroma-mod.css") }}
{{- /* order is important */}}
{{- $core := (slice $theme_vars $reset $common $chroma_styles $chroma_mod $includes_all $media) | resources.Concat "assets/css/core.css" | resources.Minify }}
{{- $extended := (resources.Match "css/extended/*.css") | resources.Concat "assets/css/extended.css" | resources.Minify }}
{{- /* bundle all required css */}}
{{- /* Add extended css after theme style */ -}}
{{- $stylesheet := (slice $license_css $core $extended) | resources.Concat "assets/css/stylesheet.css" }}
{{- if not site.Params.assets.disableFingerprinting }}
{{- $stylesheet := $stylesheet | fingerprint }}
<link crossorigin="anonymous" href="{{ $stylesheet.RelPermalink }}" integrity="{{ $stylesheet.Data.Integrity }}" rel="preload stylesheet" as="style">
{{- else }}
<link crossorigin="anonymous" href="{{ $stylesheet.RelPermalink }}" rel="preload stylesheet" as="style">
{{- end }}
{{- /* Search */}}
{{- if (eq .Layout `search`) -}}
<link crossorigin="anonymous" rel="preload" as="fetch" href="../index.json">
{{- $fastsearch := resources.Get "js/fastsearch.js" | js.Build (dict "params" (dict "fuseOpts" site.Params.fuseOpts)) | resources.Minify }}
{{- $fusejs := resources.Get "js/fuse.basic.min.js" }}
{{- $license_js := resources.Get "js/license.js" }}
{{- if not site.Params.assets.disableFingerprinting }}
{{- $search := (slice $fusejs $license_js $fastsearch ) | resources.Concat "assets/js/search.js" | fingerprint }}
<script defer crossorigin="anonymous" src="{{ $search.RelPermalink }}" integrity="{{ $search.Data.Integrity }}"></script>
{{- else }}
{{- $search := (slice $fusejs $fastsearch ) | resources.Concat "assets/js/search.js" }}
<script defer crossorigin="anonymous" src="{{ $search.RelPermalink }}"></script>
{{- end }}
{{- end -}}
{{- /* Favicons */}}
<link rel="icon" href="{{ site.Params.assets.favicon | default "favicon.ico" | absURL }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ site.Params.assets.favicon16x16 | default "favicon-16x16.png" | absURL }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ site.Params.assets.favicon32x32 | default "favicon-32x32.png" | absURL }}">
<link rel="apple-touch-icon" href="{{ site.Params.assets.apple_touch_icon | default "apple-touch-icon.png" | absURL }}">
<link rel="mask-icon" href="{{ site.Params.assets.safari_pinned_tab | default "safari-pinned-tab.svg" | absURL }}">
<meta name="theme-color" content="{{ site.Params.assets.theme_color | default "#2e2e33" }}">
<meta name="msapplication-TileColor" content="{{ site.Params.assets.msapplication_TileColor | default "#2e2e33" }}">
{{- /* RSS */}}
{{ range .AlternativeOutputFormats -}}
<link rel="{{ .Rel }}" type="{{ .MediaType.Type | html }}" href="{{ .Permalink | safeURL }}">
{{ end -}}
{{- range .AllTranslations -}}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end -}}
<noscript>
<style>
#theme-toggle,
.top-link {
display: none;
}
</style>
{{- if (and (ne site.Params.defaultTheme "light") (ne site.Params.defaultTheme "dark")) }}
<style>
@media (prefers-color-scheme: dark) {
:root {
--theme: rgb(29, 30, 32);
--entry: rgb(46, 46, 51);
--primary: rgb(218, 218, 219);
--secondary: rgb(155, 156, 157);
--tertiary: rgb(65, 66, 68);
--content: rgb(196, 196, 197);
--code-block-bg: rgb(46, 46, 51);
--code-bg: rgb(55, 56, 62);
--border: rgb(51, 51, 51);
}
.list {
background: var(--theme);
}
.list:not(.dark)::-webkit-scrollbar-track {
background: 0 0;
}
.list:not(.dark)::-webkit-scrollbar-thumb {
border-color: var(--theme);
}
}
</style>
{{- end }}
</noscript>
{{- partial "extend_head.html" . -}}
{{- /* Misc */}}
{{- if hugo.IsProduction | or (eq site.Params.env "production") }}
{{- template "_internal/google_analytics.html" . }}
{{- template "partials/templates/opengraph.html" . }}
{{- template "partials/templates/twitter_cards.html" . }}
{{- template "partials/templates/schema_json.html" . }}
{{- end -}}

View file

@ -1,149 +0,0 @@
{{- /* theme-toggle is enabled */}}
{{- if (not site.Params.disableThemeToggle) }}
{{- /* theme is light */}}
{{- if (eq site.Params.defaultTheme "light") }}
<script>
if (localStorage.getItem("pref-theme") === "dark") {
document.body.classList.add('dark');
}
</script>
{{- /* theme is dark */}}
{{- else if (eq site.Params.defaultTheme "dark") }}
<script>
if (localStorage.getItem("pref-theme") === "light") {
document.body.classList.remove('dark')
}
</script>
{{- else }}
{{- /* theme is auto */}}
<script>
if (localStorage.getItem("pref-theme") === "dark") {
document.body.classList.add('dark');
} else if (localStorage.getItem("pref-theme") === "light") {
document.body.classList.remove('dark')
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark');
}
</script>
{{- end }}
{{- /* theme-toggle is disabled and theme is auto */}}
{{- else if (and (ne site.Params.defaultTheme "light") (ne site.Params.defaultTheme "dark"))}}
<script>
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark');
}
</script>
{{- end }}
<header class="header">
<nav class="nav">
<div class="logo">
{{- $label_text := (site.Params.label.text | default site.Title) }}
{{- if site.Title }}
<a href="{{ "" | absLangURL }}" accesskey="h" title="{{ $label_text }} (Alt + H)">
{{- if site.Params.label.icon }}
{{- $img := resources.Get site.Params.label.icon }}
{{- if $img }}
{{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
{{- if hugo.IsExtended -}}
{{- $processableFormats = $processableFormats | append "webp" -}}
{{- end -}}
{{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
{{- if and (in $processableFormats $img.MediaType.SubType) (eq $prod true)}}
{{- if site.Params.label.iconHeight }}
{{- $img = $img.Resize (printf "x%d" site.Params.label.iconHeight) }}
{{ else }}
{{- $img = $img.Resize "x30" }}
{{- end }}
{{- end }}
<img src="{{ $img.Permalink }}" alt="" aria-label="logo"
height="{{- site.Params.label.iconImageHeight | default "30" -}}">
{{- else }}
<img src="{{- site.Params.label.icon | absURL -}}" alt="" aria-label="logo"
height="{{- site.Params.label.iconHeight | default "30" -}}">
{{- end -}}
{{- else if hasPrefix site.Params.label.iconSVG "<svg" }}
{{ site.Params.label.iconSVG | safeHTML }}
{{- end -}}
{{- $label_text -}}
</a>
{{- end }}
<div class="logo-switches">
{{- if (not site.Params.disableThemeToggle) }}
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</button>
{{- end }}
{{- $lang := .Lang}}
{{- $separator := or $label_text (not site.Params.disableThemeToggle)}}
{{- with site.Home.AllTranslations }}
<ul class="lang-switch">
{{- if $separator }}<li>|</li>{{ end }}
{{- range . -}}
{{- if ne $lang .Lang }}
<li>
<a href="{{- .Permalink -}}" title="{{ .Language.Params.languageAltTitle | default (.Language.LanguageName | emojify) | default (.Lang | title) }}"
aria-label="{{ .Language.LanguageName | default (.Lang | title) }}">
{{- if (and site.Params.displayFullLangName (.Language.LanguageName)) }}
{{- .Language.LanguageName | emojify -}}
{{- else }}
{{- .Lang | title -}}
{{- end -}}
</a>
</li>
{{- end -}}
{{- end}}
</ul>
{{- end }}
</div>
</div>
{{- $currentPage := . }}
<ul id="menu">
{{- range site.Menus.main }}
{{- $menu_item_url := (cond (strings.HasSuffix .URL "/") .URL (printf "%s/" .URL) ) | absLangURL }}
{{- $page_url:= $currentPage.Permalink | absLangURL }}
{{- $is_search := eq (site.GetPage .KeyName).Layout `search` }}
<li>
<a href="{{ .URL | absLangURL }}" title="{{ .Title | default .Name }} {{- cond $is_search (" (Alt + /)" | safeHTMLAttr) ("" | safeHTMLAttr ) }}"
{{- cond $is_search (" accesskey=/" | safeHTMLAttr) ("" | safeHTMLAttr ) }}>
<span {{- if eq $menu_item_url $page_url }} class="active" {{- end }}>
{{- .Pre }}
{{- .Name -}}
{{ .Post -}}
</span>
{{- if (findRE "://" .URL) }}&nbsp;
<svg fill="none" shape-rendering="geometricPrecision" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2.5" viewBox="0 0 24 24" height="12" width="12">
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"></path>
<path d="M15 3h6v6"></path>
<path d="M10 14L21 3"></path>
</svg>
{{- end }}
</a>
</li>
{{- end }}
</ul>
</nav>
</header>

View file

@ -1,34 +0,0 @@
{{- with site.Params.homeInfoParams }}
<article class="first-entry home-info">
<header class="entry-header">
<h1>{{ .Title | markdownify }}</h1>
</header>
<div class="entry-content">
<div class="row">
<div class="col photo">
{{- $me := resources.Get "/images/me.webp" }}
<img src="{{ $me.Permalink }}">
</div>
<div class="col intro-text">
{{ .Content | markdownify }}
<div class="entry-content">
{{- partial "nowplaying.html" . }}
</div>
</div>
</div>
<footer class="entry-footer">
{{ partial "social_icons.html" (dict "align" site.Params.homeInfoParams.AlignSocialIconsTo) }}
</footer>
</div>
</article>
{{- end -}}

View file

@ -0,0 +1,25 @@
<div class="container">
<svg style="position: absolute; width: 0; height: 0">
<defs>
<filter id="goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
<a href="/about/">
<div class="lava-lamp-container">
<div class="lamp-cap"></div>
<div class="lava-lamp" id="lavaLamp">
<div class="lamp-text">ABOUT</div>
</div>
<div class="lamp-base"></div>
</div>
</a>
</div>

View file

@ -5,7 +5,6 @@
<div class="nowplayingcard">
<div class="nowplayingcontainer-inner">
<div class="trackInfo">
<p><strong>Recently listening to:</strong></p>
🎵 <span id="artist"></span> - <a id="tracktitle"></a>
<a id="album"></a>
</div>

View file