first scripts
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Ballpark"updated = "2025-05-05"
|
title = "Ballpark"
|
||||||
date = 2022-03-01
|
date = 2022-03-01
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "A 3D Game Concept in Unity"
|
description = "A 3D Game Concept in Unity"
|
||||||
|
@ -25,7 +25,7 @@ banner = "/images/ballpark_menu.png"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
## Ballpark: 3D Environments in Unity
|
## Ballpark: 3D Environments in Unity
|
||||||
|
|
||||||
Implemented in Unity, Ballpark is a Concept work for a collaborative 2-Player Game, where one player is a navigator with a third-person perspective and another player is a copilot, responsible for interaction with the environment – featuring mostly working physics, intelligent enemies, a gun, a grappling hook system for traversing the map, a 2D Interface for navigation and a health bar system. On top of the meanest cyberpunk vibes my past self was able to conjure.
|
Implemented in Unity, Ballpark is a Concept work for a collaborative 2-Player Game, where one player is a navigator with a third-person perspective and another player is a copilot, responsible for interaction with the environment – featuring mostly working physics, intelligent enemies, a gun, a grappling hook system for traversing the map, a 2D Interface for navigation and a health bar system. On top of the meanest cyberpunk vibes my past self was able to conjure.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Critical Epistemology"updated = "2025-05-05"
|
title = "Critical Epistemology"
|
||||||
date = 2020-07-14
|
date = 2020-07-14
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "Forum entries from the Seminar: Critical Epistemologies"
|
description = "Forum entries from the Seminar: Critical Epistemologies"
|
||||||
|
@ -11,7 +11,6 @@ tags = ["alison jaggar", "elizabeth anderson", "epistemology", "ethics", "femini
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
## Forum entries from the Seminar: Critical Epistemologies
|
## Forum entries from the Seminar: Critical Epistemologies
|
||||||
|
|
||||||
### On Anderson: Institutions
|
### On Anderson: Institutions
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Philosophy"updated = "2025-05-05"
|
title = "Philosophy"
|
||||||
date = 2021-03-01
|
date = 2021-03-01
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "A selection of my weekly commentaries from philosophical seminars at the University of Osnabrück"
|
description = "A selection of my weekly commentaries from philosophical seminars at the University of Osnabrück"
|
||||||
|
@ -11,7 +11,6 @@ tags = ["alison jaggar", "elizabeth anderson", "elsa dorlin", "epistemology", "e
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
## Critical considerations during my studies
|
## Critical considerations during my studies
|
||||||
|
|
||||||
I have attended a fair share of philosophical seminars in my studies and consider it a core topic connected both to science and to digital environments.
|
I have attended a fair share of philosophical seminars in my studies and consider it a core topic connected both to science and to digital environments.
|
||||||
|
|
|
@ -25,8 +25,7 @@ tags = [
|
||||||
"university of osnabrück"
|
"university of osnabrück"
|
||||||
]
|
]
|
||||||
[extra]
|
[extra]
|
||||||
banner = "/images/rt_choice_corr_by_condition.png"
|
banner = "rt_choice_corr_by_condition.png"
|
||||||
|
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
|
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Political Violence"updated = "2025-05-05"
|
title = "Political Violence"
|
||||||
date = 2021-03-01
|
date = 2021-03-01
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "Forum entries from the Seminar: Is political violence justifiable? Reading Judith Butler and Elsa Dorlin"
|
description = "Forum entries from the Seminar: Is political violence justifiable? Reading Judith Butler and Elsa Dorlin"
|
||||||
|
@ -12,7 +12,6 @@ tags = ["alison jaggar", "elizabeth anderson", "elsa dorlin", "epistemology", "e
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
|
|
||||||
## Forum entries from the Seminar: Is political violence justifiable? Reading Judith Butler and Elsa Dorlin
|
## Forum entries from the Seminar: Is political violence justifiable? Reading Judith Butler and Elsa Dorlin
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Critical Philosophy of Subjectivity"updated = "2025-05-05"
|
title = "Critical Philosophy of Subjectivity"
|
||||||
date = 2021-03-01
|
date = 2021-03-01
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "Forum entries from the Seminar: Critical Philosophy of Subjectivity 1: Michel Foucault"
|
description = "Forum entries from the Seminar: Critical Philosophy of Subjectivity 1: Michel Foucault"
|
||||||
|
@ -11,7 +11,6 @@ tags = ["alison jaggar", "elizabeth anderson", "elsa dorlin", "epistemology", "e
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
## Forum entries from the Seminar: Critical Philosophy of Subjectivity 1: Michel Foucault
|
## Forum entries from the Seminar: Critical Philosophy of Subjectivity 1: Michel Foucault
|
||||||
|
|
||||||
### On Butler: Constituting norms =/= carrying normative responsibilities for their existence
|
### On Butler: Constituting norms =/= carrying normative responsibilities for their existence
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
+++
|
+++
|
||||||
title = "Master's Thesis"updated = "2025-05-05"
|
title = "Master's Thesis"
|
||||||
date = 2025-04-24
|
date = 2025-04-24
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "Human - Waste: A thesis examining interactive workshops"
|
description = "Human - Waste: A thesis examining interactive workshops"
|
||||||
|
@ -34,7 +34,6 @@ banner = "/images/masterthesis/puzzle.jpeg"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
## Master's Thesis: Human - Waste
|
## Master's Thesis: Human - Waste
|
||||||
|
|
||||||
Plastics offer significant material benefits, such as durability and versatility, yet their
|
Plastics offer significant material benefits, such as durability and versatility, yet their
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
+++
|
+++
|
||||||
title = "3D printing"updated = "2025-05-05"
|
title = "3D printing"
|
||||||
date = 2018-05-03
|
date = 2018-05-03
|
||||||
|
updated = 2025-05-05
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "My 3D Printing journey and the societal implications of the technology"
|
description = "My 3D Printing journey and the societal implications of the technology"
|
||||||
|
|
||||||
|
@ -25,27 +26,67 @@ tags = [
|
||||||
"university of osnabrück"
|
"university of osnabrück"
|
||||||
]
|
]
|
||||||
[extra]
|
[extra]
|
||||||
banner = "prusa.jpg"
|
banner = "prusa.jpg"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
{% gallery() %}
|
||||||
galleries = {
|
[
|
||||||
gallery = [
|
{
|
||||||
{ file = "cloning_station.jpg", title = "A plant propagation station now preparing our tomatoes for summer" },
|
"file": "cloning_station.jpg",
|
||||||
{ file = "elk.jpg", alt = "elk", title = "We use this to determine the flatmate of the month" },
|
"title": "A plant propagation station now preparing our tomatoes for summer",
|
||||||
{ file = "dragon_skull_1.jpg", alt = "dragon skull", title = "A dragon's head that was later treated to glow in the dark." },
|
"alt": "cloning station"
|
||||||
{ file = "ender2.jpg", alt = "ender 2", title = "This was my entry into a new world, the now 10 years old Ender 2" },
|
},
|
||||||
{ file = "lithophane.jpg", alt = "lithophane of my Grandparents", title = "I made some lithophanes, a process where the composition and thickness of the material are used for creating an image." },
|
{
|
||||||
{ file = "prusa.jpg", title = "This is my second printer, a Prusa i3 MK3s." },
|
"file": "elk.jpg",
|
||||||
{ file = "vulva_candle.jpg", alt = "vulva on a candle", title = "This candle is the result of a 3D printed plastic mold that I then poured wax into." },
|
"alt": "elk",
|
||||||
{ file = "pinecil.jpg", alt = "pinecil", title = "An enclosure for my portable soldering iron" },
|
"title": "We use this to determine the flatmate of the month"
|
||||||
{ file = "lamp.jpg", alt = "a lamp design", title = "A lamp screen design that particularly fascinated me, it effortlessly comes from a simple 2D spiral shape." },
|
},
|
||||||
{ file = "prusa_enclosure.jpg", alt = "Prusa enclosure", title = "A custom-built printer enclosure made up of 3 Ikea Lack tables and around 3 kgs of plastic." }
|
{
|
||||||
]
|
"file": "dragon_skull_1.jpg",
|
||||||
}
|
"alt": "dragon skull",
|
||||||
|
"title": "A dragon's head that was later treated to glow in the dark."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "ender2.jpg",
|
||||||
|
"alt": "ender 2",
|
||||||
|
"title": "This was my entry into a new world, the now 10 years old Ender 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "lithophane.jpg",
|
||||||
|
"alt": "lithophane of my Grandparents",
|
||||||
|
"title": "I made some lithophanes, a process where the composition and thickness of the material are used for creating an image."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "prusa.jpg",
|
||||||
|
"title": "This is my second printer, a Prusa i3 MK3s."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "vulva_candle.jpg",
|
||||||
|
"alt": "vulva on a candle",
|
||||||
|
"title": "This candle is the result of a 3D printed plastic mold that I then poured wax into."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "pinecil.jpg",
|
||||||
|
"alt": "pinecil",
|
||||||
|
"title": "An enclosure for my portable soldering iron"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "lamp.jpg",
|
||||||
|
"alt": "a lamp design",
|
||||||
|
"title": "A lamp screen design that particularly fascinated me, it effortlessly comes from a simple 2D spiral shape."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "prusa_enclosure.jpg",
|
||||||
|
"alt": "Prusa enclosure",
|
||||||
|
"title": "A custom-built printer enclosure made up of 3 Ikea Lack tables and around 3 kgs of plastic."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
{% end %}
|
||||||
|
|
||||||
## 3D Printing
|
## 3D Printing
|
||||||
|
|
||||||
{{ youtube(id="Yj_Pc357kEU") }}
|
{{ youtube(id="Yj_Pc357kEU") }}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
+++
|
+++
|
||||||
title = "About"updated = "2025-05-05"
|
title = "About"
|
||||||
description = "Welcome, let me introduce myself"
|
description = "Welcome, let me introduce myself"
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
date = 2023-07-26
|
date = 2023-07-26
|
||||||
+++
|
|
||||||
updated = "2025-05-05"
|
updated = "2025-05-05"
|
||||||
|
+++
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
I am Aron (pronouns: he/him), a 2022 graduate of the bachelor's program of Cognitive Science in Osnabrück.
|
I am Aron (pronouns: he/him), a 2022 graduate of the bachelor's program of Cognitive Science in Osnabrück.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
+++
|
+++
|
||||||
title = "Contact"updated = "2025-05-05"
|
title = "Contact"
|
||||||
description = "Reach me"
|
description = "Reach me"
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
date= 2025-05-01
|
date= 2025-05-01
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
For starters, mails are gold and probably still the best way to reach me.
|
For starters, mails are gold and probably still the best way to reach me.
|
||||||
[contact me](/mailto:aron@petau.net/)
|
[contact me](/mailto:aron@petau.net/)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
+++
|
+++
|
||||||
title = "Terms and Privacy Statement"updated = "2025-05-05"
|
title = "Terms and Privacy Statement"
|
||||||
date = 2025-05-01
|
date = 2025-05-01
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
+++
|
+++
|
||||||
updated = "2025-05-05"
|
|
||||||
My website address is: <https://aron.petau.net> .
|
My website address is: <https://aron.petau.net> .
|
||||||
|
|
||||||
## Location
|
## Location
|
||||||
|
|
BIN
public/404.gif
Before Width: | Height: | Size: 4 KiB |
BIN
public/404.png
Before Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 28 KiB |
1
public/auto-render.min.js
vendored
|
@ -1 +0,0 @@
|
||||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},n={};function r(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={exports:{}};return t[e](i,i.exports,r),i.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var o={};return function(){r.d(o,{default:function(){return d}});var e=r(771),t=r.n(e);const n=function(e,t,n){let r=n,o=0;const i=e.length;for(;r<t.length;){const n=t[r];if(o<=0&&t.slice(r,r+i)===e)return r;"\\"===n?r++:"{"===n?o++:"}"===n&&o--,r++}return-1},i=/^\\begin{/;var a=function(e,t){let r;const o=[],a=new RegExp("("+t.map((e=>e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"))).join("|")+")");for(;r=e.search(a),-1!==r;){r>0&&(o.push({type:"text",data:e.slice(0,r)}),e=e.slice(r));const a=t.findIndex((t=>e.startsWith(t.left)));if(r=n(t[a].right,e,t[a].left.length),-1===r)break;const l=e.slice(0,r+t[a].right.length),s=i.test(l)?l:e.slice(t[a].left.length,r);o.push({type:"math",data:s,rawData:l,display:t[a].display}),e=e.slice(r+t[a].right.length)}return""!==e&&o.push({type:"text",data:e}),o};const l=function(e,n){const r=a(e,n.delimiters);if(1===r.length&&"text"===r[0].type)return null;const o=document.createDocumentFragment();for(let e=0;e<r.length;e++)if("text"===r[e].type)o.appendChild(document.createTextNode(r[e].data));else{const i=document.createElement("span");let a=r[e].data;n.displayMode=r[e].display;try{n.preProcess&&(a=n.preProcess(a)),t().render(a,i,n)}catch(i){if(!(i instanceof t().ParseError))throw i;n.errorCallback("KaTeX auto-render: Failed to parse `"+r[e].data+"` with ",i),o.appendChild(document.createTextNode(r[e].rawData));continue}o.appendChild(i)}return o},s=function(e,t){for(let n=0;n<e.childNodes.length;n++){const r=e.childNodes[n];if(3===r.nodeType){let o=r.textContent,i=r.nextSibling,a=0;for(;i&&i.nodeType===Node.TEXT_NODE;)o+=i.textContent,i=i.nextSibling,a++;const s=l(o,t);if(s){for(let e=0;e<a;e++)r.nextSibling.remove();n+=s.childNodes.length-1,e.replaceChild(s,r)}else n+=a}else if(1===r.nodeType){const e=" "+r.className+" ";-1===t.ignoredTags.indexOf(r.nodeName.toLowerCase())&&t.ignoredClasses.every((t=>-1===e.indexOf(" "+t+" ")))&&s(r,t)}}};var d=function(e,t){if(!e)throw new Error("No element provided to render");const n={};for(const e in t)t.hasOwnProperty(e)&&(n[e]=t[e]);n.delimiters=n.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],n.ignoredTags=n.ignoredTags||["script","noscript","style","textarea","pre","code","option"],n.ignoredClasses=n.ignoredClasses||[],n.errorCallback=n.errorCallback||console.error,n.macros=n.macros||{},s(e,n)}}(),o=o.default}()}));
|
|
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 273 KiB |
|
@ -1,21 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Regex pattern for ISO date format filenames like 2019-06-01-something.md
|
|
||||||
pattern='^[0-9]{4}-[0-9]{2}-[0-9]{2}-.+\.md$'
|
|
||||||
|
|
||||||
echo "Processing markdown files in $(pwd)"
|
|
||||||
echo
|
|
||||||
|
|
||||||
for file in *.md; do
|
|
||||||
# Skip if no matching files (glob doesn't find anything)
|
|
||||||
[ -e "$file" ] || continue
|
|
||||||
|
|
||||||
if [[ "$file" =~ $pattern ]]; then
|
|
||||||
basename="${file%.md}"
|
|
||||||
mkdir -p "$basename"
|
|
||||||
mv "$file" "$basename/index.md"
|
|
||||||
echo "✔ Processed: $file → $basename/index.md"
|
|
||||||
else
|
|
||||||
echo "✘ Skipped (pattern mismatch): $file"
|
|
||||||
fi
|
|
||||||
done
|
|
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 328 KiB |
BIN
public/card.png
Before Width: | Height: | Size: 1.2 MiB |
|
@ -1,27 +0,0 @@
|
||||||
const closable = document.querySelectorAll("details.closable");
|
|
||||||
|
|
||||||
closable.forEach((detail) => {
|
|
||||||
detail.addEventListener("toggle", () => {
|
|
||||||
if (detail.open) setTargetDetail(detail);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function setTargetDetail(targetDetail) {
|
|
||||||
closable.forEach((detail) => {
|
|
||||||
if (detail !== targetDetail) {
|
|
||||||
detail.open = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("click", function (event) {
|
|
||||||
const isClickInsideDetail = [...closable].some((detail) =>
|
|
||||||
detail.contains(event.target)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isClickInsideDetail) {
|
|
||||||
closable.forEach((detail) => {
|
|
||||||
detail.open = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,406 +0,0 @@
|
||||||
// Taken from https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/
|
|
||||||
// Attachment, card, and spoiler code taken from https://github.com/cassidyjames/cassidyjames.github.io/blob/99782788a7e3ba3cc52d6803010873abd1b02b9e/_includes/comments.html#L251-L296
|
|
||||||
|
|
||||||
let blogPostAuthorText = document.getElementById("blog-post-author-text").textContent;
|
|
||||||
let boostsFromText = document.getElementById("boosts-from-text").textContent;
|
|
||||||
let dateLocale = document.getElementById("date-locale").textContent;
|
|
||||||
let favesFromText = document.getElementById("faves-from-text").textContent;
|
|
||||||
let host = document.getElementById("host").textContent;
|
|
||||||
let id = document.getElementById("id").textContent;
|
|
||||||
let lazyAsyncImage = document.getElementById("lazy-async-image").textContent;
|
|
||||||
let loadingText = document.getElementById("loading-text").textContent;
|
|
||||||
let noCommentsText = document.getElementById("no-comments-text").textContent;
|
|
||||||
let relAttributes = document.getElementById("rel-attributes").textContent;
|
|
||||||
let reloadText = document.getElementById("reload-text").textContent;
|
|
||||||
let sensitiveText = document.getElementById("sensitive-text").textContent;
|
|
||||||
let user = document.getElementById("user").textContent;
|
|
||||||
let viewCommentText = document.getElementById("view-comment-text").textContent;
|
|
||||||
let viewProfileText = document.getElementById("view-profile-text").textContent;
|
|
||||||
|
|
||||||
document.getElementById("load-comments").addEventListener("click", loadComments);
|
|
||||||
|
|
||||||
function escapeHtml(unsafe) {
|
|
||||||
return unsafe
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
}
|
|
||||||
function emojify(input, emojis) {
|
|
||||||
let output = input;
|
|
||||||
|
|
||||||
emojis.forEach((emoji) => {
|
|
||||||
let picture = document.createElement("picture");
|
|
||||||
|
|
||||||
let source = document.createElement("source");
|
|
||||||
source.setAttribute("srcset", escapeHtml(emoji.url));
|
|
||||||
source.setAttribute("media", "(prefers-reduced-motion: no-preference)");
|
|
||||||
|
|
||||||
let img = document.createElement("img");
|
|
||||||
img.className = "emoji";
|
|
||||||
img.setAttribute("src", escapeHtml(emoji.static_url));
|
|
||||||
img.setAttribute("alt", `:${emoji.shortcode}:`);
|
|
||||||
img.setAttribute("title", `:${emoji.shortcode}:`);
|
|
||||||
if (lazyAsyncImage == "true") {
|
|
||||||
img.setAttribute("decoding", "async");
|
|
||||||
img.setAttribute("loading", "lazy");
|
|
||||||
}
|
|
||||||
|
|
||||||
picture.appendChild(source);
|
|
||||||
picture.appendChild(img);
|
|
||||||
|
|
||||||
output = output.replace(`:${emoji.shortcode}:`, picture.outerHTML);
|
|
||||||
});
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadComments() {
|
|
||||||
let commentsWrapper = document.getElementById("comments-wrapper");
|
|
||||||
commentsWrapper.innerHTML = "";
|
|
||||||
|
|
||||||
let loadCommentsButton = document.getElementById("load-comments");
|
|
||||||
loadCommentsButton.innerHTML = loadingText;
|
|
||||||
loadCommentsButton.disabled = true;
|
|
||||||
|
|
||||||
fetch(`https://${host}/api/v1/statuses/${id}/context`)
|
|
||||||
.then(function (response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function (data) {
|
|
||||||
let descendants = data["descendants"];
|
|
||||||
if (
|
|
||||||
descendants &&
|
|
||||||
Array.isArray(descendants) &&
|
|
||||||
descendants.length > 0
|
|
||||||
) {
|
|
||||||
commentsWrapper.innerHTML = "";
|
|
||||||
|
|
||||||
descendants.forEach(function (status) {
|
|
||||||
console.log(descendants);
|
|
||||||
if (status.account.display_name.length > 0) {
|
|
||||||
status.account.display_name = escapeHtml(
|
|
||||||
status.account.display_name
|
|
||||||
);
|
|
||||||
status.account.display_name = emojify(
|
|
||||||
status.account.display_name,
|
|
||||||
status.account.emojis
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
status.account.display_name = status.account.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = "";
|
|
||||||
if (status.account.acct.includes("@")) {
|
|
||||||
instance = status.account.acct.split("@")[1];
|
|
||||||
} else {
|
|
||||||
instance = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isReply = status.in_reply_to_id !== id;
|
|
||||||
|
|
||||||
let op = false;
|
|
||||||
if (status.account.acct == user) {
|
|
||||||
op = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.content = emojify(status.content, status.emojis);
|
|
||||||
|
|
||||||
let comment = document.createElement("article");
|
|
||||||
comment.id = `comment-${status.id}`;
|
|
||||||
comment.className = isReply ? "comment comment-reply" : "comment";
|
|
||||||
comment.setAttribute("itemprop", "comment");
|
|
||||||
comment.setAttribute("itemtype", "http://schema.org/Comment");
|
|
||||||
|
|
||||||
let avatarSource = document.createElement("source");
|
|
||||||
avatarSource.setAttribute(
|
|
||||||
"srcset",
|
|
||||||
escapeHtml(status.account.avatar)
|
|
||||||
);
|
|
||||||
avatarSource.setAttribute(
|
|
||||||
"media",
|
|
||||||
"(prefers-reduced-motion: no-preference)"
|
|
||||||
);
|
|
||||||
|
|
||||||
let avatarImg = document.createElement("img");
|
|
||||||
avatarImg.className = "avatar";
|
|
||||||
avatarImg.setAttribute(
|
|
||||||
"src",
|
|
||||||
escapeHtml(status.account.avatar_static)
|
|
||||||
);
|
|
||||||
avatarImg.setAttribute(
|
|
||||||
"alt",
|
|
||||||
`@${status.account.username}@${instance} avatar`
|
|
||||||
);
|
|
||||||
if (lazyAsyncImage == "true") {
|
|
||||||
avatarImg.setAttribute("decoding", "async");
|
|
||||||
avatarImg.setAttribute("loading", "lazy");
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatarPicture = document.createElement("picture");
|
|
||||||
avatarPicture.appendChild(avatarSource);
|
|
||||||
avatarPicture.appendChild(avatarImg);
|
|
||||||
|
|
||||||
let avatar = document.createElement("a");
|
|
||||||
avatar.className = "avatar-link";
|
|
||||||
avatar.setAttribute("href", status.account.url);
|
|
||||||
avatar.setAttribute("rel", relAttributes);
|
|
||||||
avatar.setAttribute(
|
|
||||||
"title",
|
|
||||||
`${viewProfileText} @${status.account.username}@${instance}`
|
|
||||||
);
|
|
||||||
avatar.appendChild(avatarPicture);
|
|
||||||
comment.appendChild(avatar);
|
|
||||||
|
|
||||||
let instanceBadge = document.createElement("a");
|
|
||||||
instanceBadge.className = "instance";
|
|
||||||
instanceBadge.setAttribute("href", status.account.url);
|
|
||||||
instanceBadge.setAttribute(
|
|
||||||
"title",
|
|
||||||
`@${status.account.username}@${instance}`
|
|
||||||
);
|
|
||||||
instanceBadge.setAttribute("rel", relAttributes);
|
|
||||||
instanceBadge.textContent = instance;
|
|
||||||
|
|
||||||
let display = document.createElement("span");
|
|
||||||
display.className = "display";
|
|
||||||
display.setAttribute("itemprop", "author");
|
|
||||||
display.setAttribute("itemtype", "http://schema.org/Person");
|
|
||||||
display.innerHTML = status.account.display_name;
|
|
||||||
|
|
||||||
let header = document.createElement("header");
|
|
||||||
header.className = "author";
|
|
||||||
header.appendChild(display);
|
|
||||||
header.appendChild(instanceBadge);
|
|
||||||
comment.appendChild(header);
|
|
||||||
|
|
||||||
let permalink = document.createElement("a");
|
|
||||||
permalink.setAttribute("href", status.url);
|
|
||||||
permalink.setAttribute("itemprop", "url");
|
|
||||||
permalink.setAttribute("title", `${viewCommentText} ${instance}`);
|
|
||||||
permalink.setAttribute("rel", relAttributes);
|
|
||||||
permalink.textContent = new Date(
|
|
||||||
status.created_at
|
|
||||||
).toLocaleString(dateLocale, {
|
|
||||||
dateStyle: "long",
|
|
||||||
timeStyle: "short",
|
|
||||||
});
|
|
||||||
|
|
||||||
let timestamp = document.createElement("time");
|
|
||||||
timestamp.setAttribute("datetime", status.created_at);
|
|
||||||
timestamp.appendChild(permalink);
|
|
||||||
permalink.classList.add("external");
|
|
||||||
comment.appendChild(timestamp);
|
|
||||||
|
|
||||||
let main = document.createElement("main");
|
|
||||||
main.setAttribute("itemprop", "text");
|
|
||||||
|
|
||||||
if (status.sensitive == true || status.spoiler_text != "") {
|
|
||||||
let summary = document.createElement("summary");
|
|
||||||
if (status.spoiler_text == "") {
|
|
||||||
status.spoiler_text == sensitiveText;
|
|
||||||
}
|
|
||||||
summary.innerHTML = status.spoiler_text;
|
|
||||||
|
|
||||||
let spoiler = document.createElement("details");
|
|
||||||
spoiler.appendChild(summary);
|
|
||||||
spoiler.innerHTML += status.content;
|
|
||||||
|
|
||||||
main.appendChild(spoiler);
|
|
||||||
} else {
|
|
||||||
main.innerHTML = status.content;
|
|
||||||
}
|
|
||||||
comment.appendChild(main);
|
|
||||||
|
|
||||||
let attachments = status.media_attachments;
|
|
||||||
let SUPPORTED_MEDIA = ["image", "video", "gifv", "audio"];
|
|
||||||
let media = document.createElement("div");
|
|
||||||
media.className = "attachments";
|
|
||||||
if (
|
|
||||||
attachments &&
|
|
||||||
Array.isArray(attachments) &&
|
|
||||||
attachments.length > 0
|
|
||||||
) {
|
|
||||||
attachments.forEach((attachment) => {
|
|
||||||
if (SUPPORTED_MEDIA.includes(attachment.type)) {
|
|
||||||
|
|
||||||
let mediaElement;
|
|
||||||
switch (attachment.type) {
|
|
||||||
case "image":
|
|
||||||
mediaElement = document.createElement("img");
|
|
||||||
mediaElement.setAttribute("src", attachment.preview_url);
|
|
||||||
|
|
||||||
if (attachment.description != null) {
|
|
||||||
mediaElement.setAttribute("alt", attachment.description);
|
|
||||||
mediaElement.setAttribute("title", attachment.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lazyAsyncImage == "true") {
|
|
||||||
mediaElement.setAttribute("decoding", "async");
|
|
||||||
mediaElement.setAttribute("loading", "lazy");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.sensitive == true) {
|
|
||||||
mediaElement.classList.add("spoiler");
|
|
||||||
}
|
|
||||||
|
|
||||||
media.appendChild(mediaElement);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video":
|
|
||||||
mediaElement = document.createElement("video");
|
|
||||||
mediaElement.setAttribute("src", attachment.url);
|
|
||||||
mediaElement.setAttribute("controls", "");
|
|
||||||
|
|
||||||
if (attachment.description != null) {
|
|
||||||
mediaElement.setAttribute("aria-title", attachment.description);
|
|
||||||
mediaElement.setAttribute("title", attachment.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.sensitive == true) {
|
|
||||||
mediaElement.classList.add("spoiler");
|
|
||||||
}
|
|
||||||
|
|
||||||
media.appendChild(mediaElement);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "gifv":
|
|
||||||
mediaElement = document.createElement("video");
|
|
||||||
mediaElement.setAttribute("src", attachment.url);
|
|
||||||
mediaElement.setAttribute("autoplay", "");
|
|
||||||
mediaElement.setAttribute("playsinline", "");
|
|
||||||
mediaElement.setAttribute("loop", "");
|
|
||||||
|
|
||||||
if (attachment.description != null) {
|
|
||||||
mediaElement.setAttribute("aria-title", attachment.description);
|
|
||||||
mediaElement.setAttribute("title", attachment.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.sensitive == true) {
|
|
||||||
mediaElement.classList.add("spoiler");
|
|
||||||
}
|
|
||||||
|
|
||||||
media.appendChild(mediaElement);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "audio":
|
|
||||||
mediaElement = document.createElement("audio");
|
|
||||||
mediaElement.setAttribute("src", attachment.url);
|
|
||||||
mediaElement.setAttribute("controls", "");
|
|
||||||
|
|
||||||
if (attachment.description != null) {
|
|
||||||
mediaElement.setAttribute("aria-title", attachment.description);
|
|
||||||
mediaElement.setAttribute("title", attachment.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
media.appendChild(mediaElement);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mediaLink = document.createElement("a");
|
|
||||||
mediaLink.setAttribute("href", attachment.url);
|
|
||||||
mediaLink.setAttribute("rel", relAttributes);
|
|
||||||
mediaLink.appendChild(mediaElement);
|
|
||||||
|
|
||||||
media.appendChild(mediaLink);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
comment.appendChild(media);
|
|
||||||
}
|
|
||||||
|
|
||||||
let interactions = document.createElement("footer");
|
|
||||||
|
|
||||||
let boosts = document.createElement("a");
|
|
||||||
boosts.className = "boosts";
|
|
||||||
boosts.setAttribute("href", `${status.url}/reblogs`);
|
|
||||||
boosts.setAttribute("title", `${boostsFromText}`.replace("$INSTANCE", instance));
|
|
||||||
|
|
||||||
let boostsIcon = document.createElement("i");
|
|
||||||
boostsIcon.className = "icon";
|
|
||||||
boosts.appendChild(boostsIcon);
|
|
||||||
boosts.insertAdjacentHTML('beforeend', ` ${status.reblogs_count}`);
|
|
||||||
interactions.appendChild(boosts);
|
|
||||||
|
|
||||||
let faves = document.createElement("a");
|
|
||||||
faves.className = "faves";
|
|
||||||
faves.setAttribute("href", `${status.url}/favourites`);
|
|
||||||
faves.setAttribute("title", `${favesFromText}`.replace("$INSTANCE", instance));
|
|
||||||
|
|
||||||
let favesIcon = document.createElement("i");
|
|
||||||
favesIcon.className = "icon";
|
|
||||||
faves.appendChild(favesIcon);
|
|
||||||
faves.insertAdjacentHTML('beforeend', ` ${status.favourites_count}`);
|
|
||||||
interactions.appendChild(faves);
|
|
||||||
comment.appendChild(interactions);
|
|
||||||
|
|
||||||
if (status.card != null) {
|
|
||||||
let cardFigure = document.createElement("figure");
|
|
||||||
|
|
||||||
if (status.card.image != null) {
|
|
||||||
let cardImg = document.createElement("img");
|
|
||||||
cardImg.setAttribute("src", status.card.image);
|
|
||||||
cardImg.classList.add("no-hover");
|
|
||||||
cardFigure.appendChild(cardImg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cardCaption = document.createElement("figcaption");
|
|
||||||
|
|
||||||
let cardTitle = document.createElement("strong");
|
|
||||||
cardTitle.innerHTML = status.card.title;
|
|
||||||
cardCaption.appendChild(cardTitle);
|
|
||||||
|
|
||||||
if (status.card.description != null && status.card.description.length > 0) {
|
|
||||||
let cardDescription = document.createElement("p");
|
|
||||||
cardDescription.innerHTML = status.card.description;
|
|
||||||
cardCaption.appendChild(cardDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
cardFigure.appendChild(cardCaption);
|
|
||||||
|
|
||||||
let card = document.createElement("a");
|
|
||||||
card.className = "card";
|
|
||||||
card.setAttribute("href", status.card.url);
|
|
||||||
card.setAttribute("rel", relAttributes);
|
|
||||||
card.appendChild(cardFigure);
|
|
||||||
|
|
||||||
comment.appendChild(card);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op === true) {
|
|
||||||
comment.classList.add("op");
|
|
||||||
|
|
||||||
avatar.classList.add("op");
|
|
||||||
avatar.setAttribute(
|
|
||||||
"title",
|
|
||||||
`${blogPostAuthorText}: ` + avatar.getAttribute("title")
|
|
||||||
);
|
|
||||||
|
|
||||||
instanceBadge.classList.add("op");
|
|
||||||
instanceBadge.setAttribute(
|
|
||||||
"title",
|
|
||||||
`${blogPostAuthorText}: ` + instanceBadge.getAttribute("title")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
commentsWrapper.innerHTML += comment.outerHTML;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
var statusText = document.createElement("p");
|
|
||||||
statusText.innerHTML = noCommentsText;
|
|
||||||
statusText.setAttribute("id", "comments-status");
|
|
||||||
commentsWrapper.appendChild(statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadCommentsButton.innerHTML = reloadText;
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.error('Error loading comments:', error);
|
|
||||||
})
|
|
||||||
.finally(function () {
|
|
||||||
loadCommentsButton.disabled = false;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
// Based on https://www.roboleary.net/2022/01/13/copy-code-to-clipboard-blog.html
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
let blocks = document.querySelectorAll("pre[class^='language-']");
|
|
||||||
|
|
||||||
blocks.forEach((block) => {
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
// Code block header title
|
|
||||||
let title = document.createElement("span");
|
|
||||||
let lang = block.getAttribute("data-lang");
|
|
||||||
title.innerHTML = lang;
|
|
||||||
|
|
||||||
// Copy button icon
|
|
||||||
let icon = document.createElement("i");
|
|
||||||
icon.classList.add("icon");
|
|
||||||
|
|
||||||
// Copy button
|
|
||||||
let button = document.createElement("button");
|
|
||||||
let copyCodeText = document.getElementById("copy-code-text").textContent;
|
|
||||||
button.setAttribute("title", copyCodeText)
|
|
||||||
button.appendChild(icon);
|
|
||||||
|
|
||||||
// Code block header
|
|
||||||
let header = document.createElement("div");
|
|
||||||
header.classList.add("header");
|
|
||||||
header.appendChild(title);
|
|
||||||
header.appendChild(button);
|
|
||||||
|
|
||||||
// Container that holds header and the code block itself
|
|
||||||
let container = document.createElement("div");
|
|
||||||
container.classList.add("pre-container");
|
|
||||||
container.appendChild(header);
|
|
||||||
|
|
||||||
// Move code block into the container
|
|
||||||
block.parentNode.insertBefore(container, block);
|
|
||||||
container.appendChild(block);
|
|
||||||
|
|
||||||
button.addEventListener("click", async () => {
|
|
||||||
await copyCode(block, header, button); // Pass the button here
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function copyCode(block, header, button) {
|
|
||||||
let code = block.querySelector("code");
|
|
||||||
let text = code.innerText;
|
|
||||||
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
|
|
||||||
header.classList.add("active");
|
|
||||||
button.setAttribute("disabled", true);
|
|
||||||
|
|
||||||
header.addEventListener("animationend", () => {
|
|
||||||
header.classList.remove("active");
|
|
||||||
button.removeAttribute("disabled");
|
|
||||||
}, { once: true });
|
|
||||||
}
|
|
||||||
});
|
|
271
public/count.js
|
@ -1,271 +0,0 @@
|
||||||
// GoatCounter: https://www.goatcounter.com
|
|
||||||
// This file is released under the ISC license: https://opensource.org/licenses/ISC
|
|
||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (window.goatcounter && window.goatcounter.vars) // Compatibility with very old version; do not use.
|
|
||||||
window.goatcounter = window.goatcounter.vars
|
|
||||||
else
|
|
||||||
window.goatcounter = window.goatcounter || {}
|
|
||||||
|
|
||||||
// Load settings from data-goatcounter-settings.
|
|
||||||
var s = document.querySelector('script[data-goatcounter]')
|
|
||||||
if (s && s.dataset.goatcounterSettings) {
|
|
||||||
try { var set = JSON.parse(s.dataset.goatcounterSettings) }
|
|
||||||
catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) }
|
|
||||||
for (var k in set)
|
|
||||||
if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1)
|
|
||||||
window.goatcounter[k] = set[k]
|
|
||||||
}
|
|
||||||
|
|
||||||
var enc = encodeURIComponent
|
|
||||||
|
|
||||||
// Get all data we're going to send off to the counter endpoint.
|
|
||||||
var get_data = function(vars) {
|
|
||||||
var data = {
|
|
||||||
p: (vars.path === undefined ? goatcounter.path : vars.path),
|
|
||||||
r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer),
|
|
||||||
t: (vars.title === undefined ? goatcounter.title : vars.title),
|
|
||||||
e: !!(vars.event || goatcounter.event),
|
|
||||||
s: [window.screen.width, window.screen.height, (window.devicePixelRatio || 1)],
|
|
||||||
b: is_bot(),
|
|
||||||
q: location.search,
|
|
||||||
}
|
|
||||||
|
|
||||||
var rcb, pcb, tcb // Save callbacks to apply later.
|
|
||||||
if (typeof(data.r) === 'function') rcb = data.r
|
|
||||||
if (typeof(data.t) === 'function') tcb = data.t
|
|
||||||
if (typeof(data.p) === 'function') pcb = data.p
|
|
||||||
|
|
||||||
if (is_empty(data.r)) data.r = document.referrer
|
|
||||||
if (is_empty(data.t)) data.t = document.title
|
|
||||||
if (is_empty(data.p)) data.p = get_path()
|
|
||||||
|
|
||||||
if (rcb) data.r = rcb(data.r)
|
|
||||||
if (tcb) data.t = tcb(data.t)
|
|
||||||
if (pcb) data.p = pcb(data.p)
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a value is "empty" for the purpose of get_data().
|
|
||||||
var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' }
|
|
||||||
|
|
||||||
// See if this looks like a bot; there is some additional filtering on the
|
|
||||||
// backend, but these properties can't be fetched from there.
|
|
||||||
var is_bot = function() {
|
|
||||||
// Headless browsers are probably a bot.
|
|
||||||
var w = window, d = document
|
|
||||||
if (w.callPhantom || w._phantom || w.phantom)
|
|
||||||
return 150
|
|
||||||
if (w.__nightmare)
|
|
||||||
return 151
|
|
||||||
if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate)
|
|
||||||
return 152
|
|
||||||
if (navigator.webdriver)
|
|
||||||
return 153
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object to urlencoded string, starting with a ?.
|
|
||||||
var urlencode = function(obj) {
|
|
||||||
var p = []
|
|
||||||
for (var k in obj)
|
|
||||||
if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false)
|
|
||||||
p.push(enc(k) + '=' + enc(obj[k]))
|
|
||||||
return '?' + p.join('&')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a warning in the console.
|
|
||||||
var warn = function(msg) {
|
|
||||||
if (console && 'warn' in console)
|
|
||||||
console.warn('goatcounter: ' + msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the endpoint to send requests to.
|
|
||||||
var get_endpoint = function() {
|
|
||||||
var s = document.querySelector('script[data-goatcounter]')
|
|
||||||
if (s && s.dataset.goatcounter)
|
|
||||||
return s.dataset.goatcounter
|
|
||||||
return (goatcounter.endpoint || window.counter) // counter is for compat; don't use.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current path.
|
|
||||||
var get_path = function() {
|
|
||||||
var loc = location,
|
|
||||||
c = document.querySelector('link[rel="canonical"][href]')
|
|
||||||
if (c) { // May be relative or point to different domain.
|
|
||||||
var a = document.createElement('a')
|
|
||||||
a.href = c.href
|
|
||||||
if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, ''))
|
|
||||||
loc = a
|
|
||||||
}
|
|
||||||
return (loc.pathname + loc.search) || '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run function after DOM is loaded.
|
|
||||||
var on_load = function(f) {
|
|
||||||
if (document.body === null)
|
|
||||||
document.addEventListener('DOMContentLoaded', function() { f() }, false)
|
|
||||||
else
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter some requests that we (probably) don't want to count.
|
|
||||||
goatcounter.filter = function() {
|
|
||||||
if ('visibilityState' in document && document.visibilityState === 'prerender')
|
|
||||||
return 'visibilityState'
|
|
||||||
if (!goatcounter.allow_frame && location !== parent.location)
|
|
||||||
return 'frame'
|
|
||||||
if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/))
|
|
||||||
return 'localhost'
|
|
||||||
if (!goatcounter.allow_local && location.protocol === 'file:')
|
|
||||||
return 'localfile'
|
|
||||||
if (localStorage && localStorage.getItem('skipgc') === 't')
|
|
||||||
return 'disabled with #toggle-goatcounter'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get URL to send to GoatCounter.
|
|
||||||
window.goatcounter.url = function(vars) {
|
|
||||||
var data = get_data(vars || {})
|
|
||||||
if (data.p === null) // null from user callback.
|
|
||||||
return
|
|
||||||
data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control.
|
|
||||||
|
|
||||||
var endpoint = get_endpoint()
|
|
||||||
if (!endpoint)
|
|
||||||
return warn('no endpoint found')
|
|
||||||
|
|
||||||
return endpoint + urlencode(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count a hit.
|
|
||||||
window.goatcounter.count = function(vars) {
|
|
||||||
var f = goatcounter.filter()
|
|
||||||
if (f)
|
|
||||||
return warn('not counting because of: ' + f)
|
|
||||||
var url = goatcounter.url(vars)
|
|
||||||
if (!url)
|
|
||||||
return warn('not counting because path callback returned null')
|
|
||||||
|
|
||||||
if (!navigator.sendBeacon(url)) {
|
|
||||||
// This mostly fails due to being blocked by CSP; try again with an
|
|
||||||
// image-based fallback.
|
|
||||||
var img = document.createElement('img')
|
|
||||||
img.src = url
|
|
||||||
img.style.position = 'absolute' // Affect layout less.
|
|
||||||
img.style.bottom = '0px'
|
|
||||||
img.style.width = '1px'
|
|
||||||
img.style.height = '1px'
|
|
||||||
img.loading = 'eager'
|
|
||||||
img.setAttribute('alt', '')
|
|
||||||
img.setAttribute('aria-hidden', 'true')
|
|
||||||
|
|
||||||
var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) }
|
|
||||||
img.addEventListener('load', rm, false)
|
|
||||||
document.body.appendChild(img)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a query parameter.
|
|
||||||
window.goatcounter.get_query = function(name) {
|
|
||||||
var s = location.search.substr(1).split('&')
|
|
||||||
for (var i = 0; i < s.length; i++)
|
|
||||||
if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0)
|
|
||||||
return s[i].substr(name.length + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track click events.
|
|
||||||
window.goatcounter.bind_events = function() {
|
|
||||||
if (!document.querySelectorAll) // Just in case someone uses an ancient browser.
|
|
||||||
return
|
|
||||||
|
|
||||||
var send = function(elem) {
|
|
||||||
return function() {
|
|
||||||
goatcounter.count({
|
|
||||||
event: true,
|
|
||||||
path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''),
|
|
||||||
title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''),
|
|
||||||
referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) {
|
|
||||||
if (elem.dataset.goatcounterBound)
|
|
||||||
return
|
|
||||||
var f = send(elem)
|
|
||||||
elem.addEventListener('click', f, false)
|
|
||||||
elem.addEventListener('auxclick', f, false) // Middle click.
|
|
||||||
elem.dataset.goatcounterBound = 'true'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a "visitor counter" frame or image.
|
|
||||||
window.goatcounter.visit_count = function(opt) {
|
|
||||||
on_load(function() {
|
|
||||||
opt = opt || {}
|
|
||||||
opt.type = opt.type || 'html'
|
|
||||||
opt.append = opt.append || 'body'
|
|
||||||
opt.path = opt.path || get_path()
|
|
||||||
opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')}
|
|
||||||
|
|
||||||
opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?'
|
|
||||||
if (opt.no_branding) opt.attr['src'] += '&no_branding=1'
|
|
||||||
if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style)
|
|
||||||
if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start)
|
|
||||||
if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end)
|
|
||||||
|
|
||||||
var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type]
|
|
||||||
if (!tag)
|
|
||||||
return warn('visit_count: unknown type: ' + opt.type)
|
|
||||||
|
|
||||||
if (opt.type === 'html') {
|
|
||||||
opt.attr['frameborder'] = '0'
|
|
||||||
opt.attr['scrolling'] = 'no'
|
|
||||||
}
|
|
||||||
|
|
||||||
var d = document.createElement(tag)
|
|
||||||
for (var k in opt.attr)
|
|
||||||
d.setAttribute(k, opt.attr[k])
|
|
||||||
|
|
||||||
var p = document.querySelector(opt.append)
|
|
||||||
if (!p)
|
|
||||||
return warn('visit_count: append not found: ' + opt.append)
|
|
||||||
p.appendChild(d)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make it easy to skip your own views.
|
|
||||||
if (location.hash === '#toggle-goatcounter') {
|
|
||||||
if (localStorage.getItem('skipgc') === 't') {
|
|
||||||
localStorage.removeItem('skipgc', 't')
|
|
||||||
alert('GoatCounter tracking is now ENABLED in this browser.')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
localStorage.setItem('skipgc', 't')
|
|
||||||
alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!goatcounter.no_onload)
|
|
||||||
on_load(function() {
|
|
||||||
// 1. Page is visible, count request.
|
|
||||||
// 2. Page is not yet visible; wait until it switches to 'visible' and count.
|
|
||||||
// See #487
|
|
||||||
if (!('visibilityState' in document) || document.visibilityState === 'visible')
|
|
||||||
goatcounter.count()
|
|
||||||
else {
|
|
||||||
var f = function(e) {
|
|
||||||
if (document.visibilityState !== 'visible')
|
|
||||||
return
|
|
||||||
document.removeEventListener('visibilitychange', f)
|
|
||||||
goatcounter.count()
|
|
||||||
}
|
|
||||||
document.addEventListener('visibilitychange', f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!goatcounter.no_events)
|
|
||||||
goatcounter.bind_events()
|
|
||||||
})
|
|
||||||
})();
|
|
|
@ -1,22 +0,0 @@
|
||||||
.gallery {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
.mermaid {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 1.5em;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
padding: 1em;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
background-color: var(--code-bg);
|
|
||||||
font-family: var(--code-font, monospace);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid svg {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.mermaid {
|
|
||||||
background-color: var(--code-bg-dark, #2d2d2d);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
.skills {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-subtitle {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #444;
|
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-item {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-item i {
|
|
||||||
margin-right: 0.3rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
/* Basic Layout */
|
|
||||||
#timeline-content {
|
|
||||||
position: relative;
|
|
||||||
margin: 2rem 0;
|
|
||||||
padding-left: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#timeline-content ul.timeline {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#timeline-content ul.timeline::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: -30px;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 2px;
|
|
||||||
background: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Event List Item */
|
|
||||||
#timeline-content li.event {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Event Circle */
|
|
||||||
#timeline-content li.event::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: -39px;
|
|
||||||
top: 5px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--accent-color);
|
|
||||||
border: 2px solid white;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* From + To Label — styled exactly like old date label */
|
|
||||||
#timeline-content li.event::after {
|
|
||||||
content: attr(data-from) "\A" attr(data-to);
|
|
||||||
white-space: pre; /* ensures newline works */
|
|
||||||
position: absolute;
|
|
||||||
left: -160px;
|
|
||||||
width: 100px;
|
|
||||||
text-align: right;
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Event Heading */
|
|
||||||
#timeline-content li.event h3 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Event Description */
|
|
||||||
#timeline-content li.event p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Timeline Icon */
|
|
||||||
#timeline-content .timeline-icon {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
color: var(--accent-color);
|
|
||||||
font-size: 1.2rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hover Effects */
|
|
||||||
#timeline-content li.event:hover::before {
|
|
||||||
background: var(--accent-color-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
#timeline-content li.event:hover {
|
|
||||||
background-color: var(--accent-color-alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* From + To Label Hover Effect */
|
|
||||||
#timeline-content li.event:hover::after {
|
|
||||||
color: var(--fg-color);
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"grouping": "Languages, Operating Systems & Tools",
|
|
||||||
"skills": [
|
|
||||||
{ "name": "Cplusplus", "icon": "fa-solid fa-c" },
|
|
||||||
{ "name": "Rust", "icon": "fab fa-rust" },
|
|
||||||
{ "name": "Java", "icon": "fab fa-java" },
|
|
||||||
{ "name": "Python", "icon": "fab fa-python" },
|
|
||||||
{ "name": "git", "icon": "fas fa-code-branch" },
|
|
||||||
{ "name": "linux", "icon": "fab fa-linux" },
|
|
||||||
{ "name": "bash", "icon": "fas fa-terminal" },
|
|
||||||
{ "name": "javascript", "icon": "fab fa-js" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"grouping": "Platform Development & Administration",
|
|
||||||
"skills": [
|
|
||||||
{ "name": "NGINX", "icon": "fas fa-server" },
|
|
||||||
{ "name": "MySQL", "icon": "fas fa-database" },
|
|
||||||
{ "name": "Slurm", "icon": "fas fa-project-diagram" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"grouping": "Containers & Cloud",
|
|
||||||
"skills": [
|
|
||||||
{ "name": "Docker", "icon": "fab fa-docker" },
|
|
||||||
{ "name": "Aliyun", "icon": "fas fa-cloud" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
Before Width: | Height: | Size: 548 B |
|
@ -1 +0,0 @@
|
||||||
@font-face{font-style:normal;font-weight:100 900;src:url("fonts/InterVariable.woff2") format("woff2");font-family:"Inter Variable";font-display:swap}@font-face{font-style:italic;font-weight:100 900;src:url("fonts/InterVariable-Italic.woff2") format("woff2");font-family:"Inter Variable";font-display:swap}@font-face{font-style:normal;font-weight:100 900;src:url("fonts/JetBrainsMono.woff2") format("woff2");font-family:"JetBrains Mono";font-display:swap}@font-face{font-style:italic;font-weight:100 900;src:url("fonts/JetBrainsMono-Italic.woff2") format("woff2");font-family:"JetBrains Mono";font-display:swap}body{font-family:"Inter Variable",var(--font-system-ui),var(--font-emoji)}h1,h2,h3,h4,h5,h6{font-weight:bold;font-family:"Inter Variable",var(--font-system-ui),var(--font-emoji)}h1{font-weight:900}pre,code,kbd,samp{font-family:"JetBrains Mono",var(--font-monospace-code)}
|
|
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 3.1 MiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 4 MiB |
Before Width: | Height: | Size: 3.2 MiB |
Before Width: | Height: | Size: 2.6 MiB |
Before Width: | Height: | Size: 4.4 MiB |
Before Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 3.9 MiB |
Before Width: | Height: | Size: 3.4 MiB |
Before Width: | Height: | Size: 2.8 MiB |
Before Width: | Height: | Size: 3.8 MiB |
Before Width: | Height: | Size: 2.8 MiB |
Before Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 2 MiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 2.1 MiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.3 MiB |