löten leuchten

This commit is contained in:
Aron Petau 2025-05-17 17:09:41 +02:00
parent 98d803929a
commit 88c564f467
2662 changed files with 195168 additions and 12 deletions

View file

@ -2,6 +2,7 @@
title = "How to miet Ulli"
date = 2025-05-01
authors = ["Aron Petau"]
draft=true
[extra]
show_copyright = true
@ -9,8 +10,9 @@ show_shares = true
featured = true
+++
{% alert(note=true) %}
This is a work in Progress. Informations on here are subject to change.
{: .notice--danger}
{% end %}
<iframe width="100%" height="800" src="https://vrm.victronenergy.com/installation/167009/embed/f61b11f2"></iframe>
@ -21,8 +23,9 @@ That means roughly 80 PS. No mountains and offroading for you in there.
## Propellant
{% alert(note=true) %}
The car takes either **Super** (95), **Super Plus** (98) or **E10**.
{: .notice--danger}
{% end %}
The Car uses about 10-12l/100km, when never going beyond 90 km/h.
Otherwise it uses about 12-15l/100km.
@ -31,8 +34,9 @@ The tank is 85l, so you can go about 750km on one tank.
The tank needle is broken, so you have to use the trip meter to know how much fuel is left.
Always refill completely, otherwise you will have no clue about your range.
{% alert(note=true) %}
Please remember to reset the meter after filling up.
{: .notice--danger}
{% end %}
The car is a manual, so you need to know how to drive stick.
There is no Servolenkung (power steering), so it is a bit harder to steer.
@ -52,33 +56,38 @@ The solar system functions automatically and has an automatic shut-off, so you c
For the Analogue people, there is a battery monitor in the car, that shows the current battery voltage. It is right next to the driver seat and can read the front starter battery and the support back battery.
Anything below 11.5V is considered empty and you should start the car to recharge the battery. There is a battery booster, which will charge both batteries while driving.
{% alert(note=true) %}
For increased solar capacity, take out the folded panels, plug their cable into the adapter at the rear under the exhaust pipe and place the panels in the sun. The solar system will automatically use the additional power.
{: .notice--info}
{% end %}
## Kitchen
The kitchen is equipped with a 2 flame gas stove and a sink with running water.
The water is stored in a 15l tank, which is filled from the outside. Using it awarely, it can last for about 3 days (2 people).
{% alert(note=true) %}
Gas is standard 5kg grey gas bottles, which can be exchanged at any gas station. There is a bottle in the car, which is usually enough for more than a month of cooking. If you run out, you can exchange it at any gas station.
{: .notice--info}
{% end %}
Kitchen is fully equipped for 2 People.
{% alert(note=true) %}
Always roll the top window down while driving. You will loose the window and its roughly 300 to replace.
{: .notice--danger}
{% end %}
There is a powerful 12v fridge which holds about 30l. It is powered by the solar system and can be used while driving. With enough sun it can run 24/7.
{% alert(note=true) %}
Anything above the setting 3 will freeze your food. Level 2 works for me.
{: .notice--danger}
{% end %}
## Sleeping
The bed is in the high roof and is 1.20m wide and 1.90m long.
{% alert(note=true) %}
There is climbing involved to get up there.
{: .notice--info}
{% end %}
## Heating
@ -105,8 +114,10 @@ Then we can arrange a time for you to pick up the car.
The car costs 30€ per day, plus 0.10€ per km.
This factors in my insurance and the taxes I have to pay.
Any damages to the car will be charged to you. A total damage would cost you somewhere around 10.000 Euro, so please be careful.
{% alert(note=true) %}
Any damages to the car will be charged to you.
A total damage would cost you somewhere around 10.000 Euro, so please be careful.
Check your Haftpflichtversicherung (private liability insurance) to see whether it covers rented cars.
{: .notice--danger}
{% end %}
[Benzinrechner](https://benzinrechner.info/en)

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View file

@ -0,0 +1,102 @@
+++
title = "einszwovier: löten und leuchten"
date = 2025-05-16
authors = ["Aron Petau", "Friedrich Weber"]
description = "The first course format in einszwovier: löten und leuchten"
draft = false
[taxonomies]
tags = [
"making",
"education",
"democratic",
"engineering",
"experiment",
"work",
"3D printing",
"soldering",
"electronics",
"einszwovier",
]
[extra]
banner = "eins zwo vier logo.png"
show_copyright = true
show_shares = true
featured = false
+++
{% gallery() %}
[
{
"file": "cables.jpeg",
"alt": "",
"title": "All the led Lamps together"
},
{
"file": "feedback.jpeg",
"alt": "",
"title": "The Guestbook: a quick Feedback mechanism we use"
},
{
"file": "hand.jpeg",
"alt": "",
"title": "Tinkereing with only simple shapes"
},
{
"file": "lights.jpeg",
"alt": "",
"title": "More Lights"
},
{
"file": "prints.jpeg",
"alt": "",
"title": "Some overmight prints"
},
{
"file": "ski.jpeg",
"alt": "",
"title": "A completely self-designed skier"
}
]
{% end %}
**Ein praxisnaher Kurs zu Löten, Elektronik und Lampendesign für junge Tüftler*innen**
*Löten und Leuchten* fand inzwischen in drei erfolgreichen Durchläufen statt — jeweils als Angebot für Schüler*innen der 5. und 6. Klasse. Der Kurs bietet einen spielerischen und begleiteten Einstieg in die Welt der Elektronik, des Lötens und der digitalen Gestaltung. Im Mittelpunkt steht das **Verstehen durch eigenes Machen**: Technologien begreifen, indem man sie selbst gestaltet.
## Das Projekt
Über drei Sitzungen hinweg (jeweils drei Stunden) entwickelten und bauten die Kinder ihre eigene USB-betriebene LED-Leuchte. Sie löteten elektronische Bauteile, modellierten Gehäuse in 3D, beschäftigten sich mit Lichtstreuung und lernten dabei ganz selbstverständlich, technische Probleme kreativ zu lösen. Jede Leuchte wurde von Grund auf gebaut, funktional und transportabel ganz ohne Batterien, dafür mit echten Kabeln, Werkzeug und einem großen Schuss Eigenverantwortung.
Zum Einstieg lernten die Teilnehmer*innen die Grundlagen der Elektrizität mit den wunderbar zugänglichen **Makey Makey**-Boards kennen. Damit konnten wir spielerisch Stromkreise, Leitfähigkeit und Steuerung erklären ein Einstieg, der sofort Neugier und Begeisterung weckte.
Anschließend folgte das Herzstück des Projekts: **USB-Kabel aufschneiden, 5V-LEDs anlöten** und eigene Gehäuse entwerfen. Das Löten geschah unter Aufsicht, aber jede*r lötete selbst und das mit sichtbarem Stolz. Wenn die eigene LED zum ersten Mal leuchtet, ist das ein magischer Moment.
## Gestaltung mit Werkzeug und mit Einschränkungen
Für die 3D-Gestaltung nutzten wir **Tinkercad** auf iPads. Die Oberfläche war für viele der erste Berührungspunkt mit CAD-Software und erwies sich als zugänglich und intuitiv allerdings nicht ohne technische Stolpersteine. Tinkercad stürzte gelegentlich ab, und Synchronisationsprobleme führten manchmal zu Verwirrung. Trotz dieser Hürden ermöglichte es einen **niedrigschwelligen Einstieg in die digitale Gestaltung**.
Die entworfenen Lampenschirme mussten nicht nur schön aussehen, sondern auch die Elektronik sinnvoll aufnehmen. Dadurch ergaben sich ganz reale Designherausforderungen: Passt das Kabel? Wie weit darf die LED vom Gehäuse entfernt sein? Wie verändert sich das Licht?
Gedruckt wurde mit weißem PLA-Filament ideal für die Lichtstreuung. Im Kurs entwickelten sich dadurch ganz organisch Gespräche über **Materialeigenschaften, Lichtdurchlässigkeit und die physikalischen Grenzen des 3D-Drucks**.
## Echte Herausforderungen, echtes Denken
Das Projekt traf genau die richtige Balance: **anspruchsvoll genug, um ernst genommen zu werden**, aber machbar genug, damit alle ein Erfolgserlebnis hatten. Jedes Kind nahm am Ende eine funktionierende, selbstgebaute Lampe mit nach Hause und keine glich der anderen.
Dabei gab es viele kleine Hürden: USB-Kabel, die zu viel Spiel hatten, Gehäuse, die nicht sofort passten, LEDs, die nachjustiert werden mussten. Wir wichen diesen Herausforderungen nicht aus im Gegenteil: Wir nutzten sie als Anlässe, um gemeinsam nach Lösungen zu suchen. Gerade diese Momente führten zu den besten Gesprächen über Technik, Entwurf und Fehlerkultur.
## Bonus-Runde: Tischkicker-Prototypen
Zum Abschluss durfte jede Gruppe ihren eigenen **Mini-Tischkicker** entwerfen mit den Materialien und Ideen, die sie zur Verfügung hatten. Diese kreative Extra-Aufgabe förderte **Teamarbeit, Improvisation und erste Design-Thinking-Schritte**. Und ganz nebenbei entstanden viele lustige, kluge und überraschende Lösungen.
## Rückblick
Alle drei Durchgänge des Workshops wurden mit **großem Interesse, Konzentration und Freude** aufgenommen. Die Kinder waren über die gesamte Zeit engagiert, nicht nur beim Basteln, sondern auch im Denken: Wie funktioniert das? Was kann ich anders machen? Was ist möglich?
Sie gingen nicht nur mit einer leuchtenden Lampe nach Hause sondern mit dem Gefühl, etwas **selbst geschaffen** zu haben. Und mit der Erkenntnis, dass Technik keine Zauberei ist, sondern etwas, das man verstehen und gestalten kann.
Auch für uns als Kursleitung war *Löten und Leuchten* ein bestärkendes Erlebnis. Die Kombination aus digitalen Werkzeugen, praktischer Arbeit und offener Aufgabenstellung schuf einen Raum, in dem Lernen ganz selbstverständlich und mit echter Neugier geschah.
*Löten und Leuchten* wird sich weiterentwickeln doch das Ziel bleibt dasselbe: **Kinder stärken, selbstbestimmt mit Technik umzugehen, und ihnen zeigen, dass sie mehr können, als sie denken.**

View file

@ -0,0 +1,101 @@
+++
title = "einszwovier: löten und leuchten"
date = 2025-05-16
authors = ["Aron Petau", "Friedrich Weber"]
description = "The first course format in einszwovier: löten und leuchten"
draft = false
[taxonomies]
tags = [
"making",
"education",
"democratic",
"engineering",
"experiment",
"work",
"3D printing",
"soldering",
"electronics",
"einszwovier",
]
[extra]
banner = "eins zwo vier logo.png"
show_copyright = true
show_shares = true
featured = false
+++
{% gallery() %}
[
{
"file": "cables.jpeg",
"alt": "",
"title": "All the led Lamps together"
},
{
"file": "feedback.jpeg",
"alt": "",
"title": "The Guestbook: a quick Feedback mechanism we use"
},
{
"file": "hand.jpeg",
"alt": "",
"title": "Tinkereing with only simple shapes"
},
{
"file": "lights.jpeg",
"alt": "",
"title": "More Lights"
},
{
"file": "prints.jpeg",
"alt": "",
"title": "Some overmight prints"
},
{
"file": "ski.jpeg",
"alt": "",
"title": "A completely self-designed skier"
}
]
{% end %}
# Löten und Leuchten
**A hands-on course in soldering, electronics, and lamp design for young creators**
*Löten und Leuchten* has now run in three successful iterations — each time offering 5th and 6th graders a guided yet exploratory dive into the worlds of electronics, making, and digital design. At its core, the course is about **understanding through creating**: introducing young learners to tangible technologies and encouraging them to shape the outcome with their own ideas and hands.
## The Project
Over three sessions (each lasting three hours), participants designed and built their own USB-powered LED lamp. Along the way, they soldered electronic components, modeled lamp housings in 3D, learned about light diffusion, and got a direct introduction to real-world problem solving. Every lamp was built from scratch, powered via USB — no batteries, no glue kits, just wire, plastic, and a bit of courage.
The children began by learning the basics of electricity through interactive experiments using the excellent **Makey Makey** boards. These allowed us to demonstrate concepts like conductivity, input/output, and circuitry in a playful and intuitive way. The enthusiasm was immediate and contagious.
From there, we moved to the heart of the project: **cutting open USB cables, preparing and soldering 5V LEDs**, and designing enclosures for them. The soldering was always supervised, but each child did their own work — and it showed. There's something deeply satisfying about holding a working circuit you assembled yourself, and many kids expressed how proud they were to see their light turn on.
## Designing with Tools — and Constraints
For 3D modeling, we used **Tinkercad** on iPads. While the interface proved very accessible, we also encountered its limits: the app occasionally crashed or froze under load, and file syncing sometimes led to confusion. Nonetheless, it provided a **gentle, well-mediated entry point** to CAD. Most kids had never touched 3D design software before, but quickly began exploring shapes, tolerances, and fitting dimensions. The lamps they created werent just decorative — they had to **functionally hold the electronics**, which added a very real-world layer of complexity.
The printed shades were all done in white PLA to support light diffusion. This led to organic conversations around **material properties, translucency, and light behavior**, which the kids quickly absorbed and applied in their designs.
## Real Challenges, Real Thinking
The project hit a sweet spot: **it was challenging enough to be meaningful**, but achievable enough to allow for success. Every child managed to finish a working lamp — and each one was different. Along the way, they encountered plenty of design hurdles: USB cables that needed reinforcement, cases that didnt fit on the first try, LEDs that had to be repositioned for optimal glow.
We didnt avoid these issues — we embraced them. Instead of simplifying the process to a formula, we treated every obstacle as an opportunity for discussion. Why didnt this fit? What could we change? How do you fix it? These moments turned into some of the richest learning experiences in the course.
## Bonus Round: Tabletop Foosball
As a closing challenge, each group designed their own **mini foosball table**, using whatever materials and approaches they liked. This final task was light-hearted, but not without its own design challenges — and it served as a great entry into **collaborative thinking and prototyping**. It also reinforced our goal of learning through play, iteration, and autonomy.
## Reflections
Across all three runs, the workshop was met with **enthusiasm, curiosity, and real focus**. The kids were engaged from start to finish, not just with the tools, but with the ideas behind them. They walked away with more than just a glowing lamp — they gained an understanding of how things work, and a confidence that they can build things themselves.
For us as facilitators, the course reaffirmed how powerful hands-on, self-directed learning can be. The combination of digital and physical tools, real constraints, and open-ended outcomes created an environment where creativity thrived.
*Löten und Leuchten* will continue to evolve, but its core will remain: empowering kids to build things they care about, and helping them realize that technology isnt magic — its something they can shape.

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View file

@ -14,6 +14,7 @@ tags = [
"experiment",
"work",
"3D printing",
"einszwovier",
]
[extra]
banner = "eins zwo vier logo.png"

BIN
public/404.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

2
public/404.html Normal file

File diff suppressed because one or more lines are too long

BIN
public/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

3931
public/atom.xml Normal file

File diff suppressed because it is too large Load diff

1
public/auto-render.min.js vendored Normal file
View file

@ -0,0 +1 @@
!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}()}));

BIN
public/card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

27
public/closable.js Normal file
View file

@ -0,0 +1,27 @@
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;
});
}
});

406
public/comments.js Normal file
View file

@ -0,0 +1,406 @@
// 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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
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;
});
}

57
public/copy-button.js Normal file
View file

@ -0,0 +1,57 @@
// 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 Normal file
View file

@ -0,0 +1,271 @@
// 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()
})
})();

48
public/css/gallery.css Normal file
View file

@ -0,0 +1,48 @@
#image-gallery {
margin: 2rem 0;
}
.gallery {
column-count: 3;
column-gap: 1rem;
}
.gallery-item {
break-inside: avoid;
margin-bottom: 1rem;
text-align: center;
list-style: none; /* ← important! */
}
.gallery-item img {
width: 100%;
height: auto;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transition: transform 0.2s ease;
}
.gallery-item img:hover {
transform: scale(1.6);
}
.caption {
font-size: 0.9rem;
margin-top: 0.5rem;
margin-bottom: 0; /* ← just in case */
color: var(--fg-color);
list-style: none; /* ← extra-safe */
}
@media (max-width: 768px) {
.gallery {
column-count: 2;
}
}
@media (max-width: 480px) {
.gallery {
column-count: 1;
}
}

28
public/css/mermaid.css Normal file
View file

@ -0,0 +1,28 @@
.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);
}
}

72
public/css/skills.css Normal file
View file

@ -0,0 +1,72 @@
/* Basic Layout for Skills List */
#skills-content {
margin: 2rem 0;
}
#skills-content .category {
margin-bottom: 3rem;
}
#skills-content .category h3 {
font-size: 1.5rem;
font-weight: regular;
margin-bottom: 1rem;
}
/* Skills container for single-line display */
#skills-content .skills-list {
list-style: none;
padding: 0;
margin: 0;
display: flex; /* Use flexbox for single-line display */
flex-wrap: wrap; /* Allow skills to wrap if needed */
gap: 1rem; /* Spacing between skills */
}
/* Skill List Item */
#skills-content .skills-list .skill {
display: inline-flex; /* Ensures each skill is aligned inline */
align-items: center;
position: relative;
cursor: pointer; /* Show pointer cursor on hover */
text-decoration: none; /* Remove underline from links */
color: inherit; /* Inherit color from parent */
transition: background-color 0.3s, color 0.3s;
}
/* Skill Name */
#skills-content .skills-list .skill span {
display: inline-block;
font-size: 1.2rem;
margin-left: 0.5rem;
line-height: 1.4;
}
/* Skill Icon */
#skills-content .skills-list .skill .skill-icon {
margin-right: 0.5rem;
color: var(--accent-color);
font-size: 1.4rem;
vertical-align: middle;
}
/* Hover Effects for Skills */
#skills-content .skills-list .skill:hover::before {
background: var(--accent-color-dark);
}
#skills-content .skills-list .skill:hover {
background-color: var(--accent-color-alpha);
color: var(--fg-color); /* Change text color on hover */
}
/* Skill Category Hover Effect */
#skills-content .skills-list .skill:hover span {
color: var(--fg-color);
}
/* Optional: Customize Icon Color on Hover */
#skills-content .skills-list .skill:hover .skill-icon {
color: var(--fg-color);
}

91
public/css/timeline.css Normal file
View file

@ -0,0 +1,91 @@
/* 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(--bg-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: var(--fg-color);
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);
}
#timeline-content li.event:hover {
background-color: var(--accent-color-alpha);
}
/* From + To Label Hover Effect */
#timeline-content li.event:hover::after {
color: var(--accent-color);
}

30
public/data/skills.json Normal file
View file

@ -0,0 +1,30 @@
[
{
"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" }
]
}
]

3904
public/de/atom.xml Normal file

File diff suppressed because it is too large Load diff

45
public/de/index.html Normal file
View file

@ -0,0 +1,45 @@
<!doctype html><html data-theme=light lang=de xmlns=http://www.w3.org/1999/xhtml><head><meta charset=UTF-8><meta content="Mein Portfolio, Blog und allgemeine Präsenz online" name=description><meta content="width=device-width,initial-scale=1" name=viewport><meta content=#FF7E3C name=theme-color><meta content=#FF7E3C media=(prefers-color-scheme:dark) name=theme-color><title>Aron Petau</title><link href=https://aron.petau.net/de/ rel=canonical><link href=https://mastodon.online/@reprintedAron rel=me><meta content=@reprintedAron@mastodon.online name=fediverse:creator><link href=https://aron.petau.net/favicon.png rel=icon type=image/png><link href=https://aron.petau.net/apple-touch-icon.png rel=apple-touch-icon sizes=180x180 type=image/png><link crossorigin href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css integrity=sha512-... referrerpolicy=no-referrer rel=stylesheet><link title="Aron Petau - Atom Feed" href=https://aron.petau.net/atom.xml rel=alternate type=application/atom+xml><style>:root,[data-theme=dark]{--accent-color:#ff7e3c}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--accent-color:#ff7e3c}}</style><link href=https://aron.petau.net/style.css rel=stylesheet><link href=https://aron.petau.net/css/timeline.css rel=stylesheet><link href=https://aron.petau.net/css/mermaid.css rel=stylesheet><link href=https://aron.petau.net/css/skills.css rel=stylesheet><link href=https://aron.petau.net/css/gallery.css rel=stylesheet><script defer src=https://aron.petau.net/closable.js></script><script defer src=https://aron.petau.net/copy-button.js></script><script data-goatcounter=https://duckquill.goatcounter.com/count defer src=https://aron.petau.net/count.js></script><script defer src=https://aron.petau.net/fuse.js></script><script defer src=https://aron.petau.net/search-fuse.js></script><script defer src=https://aron.petau.net/theme-switcher.js></script><script type=module>import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });</script><meta content="Aron Petau" property=og:site_name><meta content="Aron Petau" property=og:title><meta content=https://aron.petau.net/de/ property=og:url><meta content="Mein Portfolio, Blog und allgemeine Präsenz online" property=og:description><meta content=https://aron.petau.net/card.png property=og:image><meta content=de_DE property=og:locale><body><header id=site-nav><nav><a href=#main-content tabindex=0> Zum Hauptinhalt springen </a><ul><li id=home><a class=active href=https://aron.petau.net/de/> <i class=icon></i>Aron Petau</a><li class=divider><li><a href=https://aron.petau.net/de/project/>Projekte</a><li><a href=https://aron.petau.net/de/pages/contact/>Kontakt</a><li><a href=https://aron.petau.net/de/pages/cv/>Vita</a><li><a href=https://aron.petau.net/de/pages/about/>Über mich</a><li id=search><button class=circle id=search-toggle title=Suche><i class=icon></i></button><li id=language-switcher><details class=closable><summary class=circle title=Sprache><i class=icon></i></summary> <ul><li><a href=https://aron.petau.net/ lang=en>English</a></ul></details><li id=theme-switcher><details class=closable><summary class=circle title=Thema><i class=icon></i></summary> <ul><li><button title="Zum hellen Thema wechseln" class=circle id=theme-light><i class=icon></i></button><li><button title="Zum dunklen Thema wechseln" class=circle id=theme-dark><i class=icon></i></button><li><button title="Systemthema nutzen" class=circle id=theme-system><i class=icon></i></button></ul></details><li id=feed><a class=circle href=https://aron.petau.net/de/atom.xml title=Feed> <i class=icon></i> </a><li id=repo><a class=circle href=https://forgejo.petau.net/aron/awebsite title=Repository> <i class=icon></i> </a></ul></nav><div id=search-container><label class=visually-hidden for=search-bar>Suche</label><input placeholder="Suche nach…" autocomplete=off disabled id=search-bar type=search><div id=search-results-container><div id=search-results></div></div></div></header><main id=main-content><h2 id=Willkommen><a aria-label="Anchor link for: Willkommen" class=zola-anchor href=#Willkommen><i class=icon></i></a> Willkommen</h2><p>auf der Online-Präsenz von Aron Petau.<aside><p><img title="Aron Petau" alt=logo src=/images/logo.png></aside><p>Ich verwende die Pronomen er/ihm und lebe in Berlin, Deutschland.<p>Ich bin Tüftler, Designer, Softwareentwickler und arbeite in der Forschung zu digitaler Bildung.<p>Diese Seite ist eine Sammlung meiner Gedanken und Erfahrungen.<p>Ich hoffe, du findest hier etwas Interessantes.<p><blockquote class=note><p class=alert-title><i class=icon></i>Anmerkung<p>Diese Seite befindet sich derzeit im aktiven Umbau. Fehlende oder kaputte Links sind zu erwarten.<p>Solange der Umzug bzw. das Redesign nicht vollständig abgeschlossen ist, ist die alte Seite weiterhin hier erreichbar: <a href=https://old.aron.petau.net>old.aron.petau.net</a></blockquote> Fortschritt des Umbaus: <progress max=100 value=90></progress><p><blockquote class=note><p class=alert-title><i class=icon></i>Anmerkung<p>Außerdem gibt es erste Bemühungen, diese Website zu übersetzen. Das ist ein ziemlich aufwändiger Prozess und wird einige Zeit dauern.</blockquote> Fortschritt der Übersetzung: <progress max=100 value=15></progress><blockquote class=important><p class=alert-title><i class=icon></i>Wichtig<p>Zuletzt aktualisiert: 2025-05-14</blockquote><div class="crt scanlines" aria-hidden=true><pre><code>➜ content git:(main) ✗ tree -L 2
.
├── _index.md
├── blog
│   ├── _index.md
│   ├── 2018-05-03-printing
│   ├── 2018-07-05-cad
│   ├── 2018-09-01-beacon
│   ├── 2019-03-19-plastic-recycling
│   ├── 2019-06-01-ballpark
│   ├── 2020-03-01-homebrew
│   ├── 2020-07-14-critical-epistemologies
│   ├── 2020-07-15-chatbot
│   ├── 2021-03-01-coding
│   ├── 2021-03-01-philosophy
│   ├── 2021-04-13-thesis
│   ├── 2021-08-01-iron-smelting
│   ├── 2021-12-05-political-violence
│   ├── 2022-01-22-critical-philosophy-subjectivity
│   ├── 2022-04-01-allei
│   ├── 2022-04-30-lusatia
│   ├── 2022-12-03-stable-dreamfusion
│   ├── 2022-12-04-lampshades
│   ├── 2023-01-03-auraglow
│   ├── 2023-03-01-ruminations
│   ├── 2023-06-16-ascendancy
│   ├── 2023-06-20-autoimmunitaet
│   ├── 2023-06-20-dreams-of-cars
│   ├── 2023-12-06-postmaster
│   ├── 2023-12-07-commoning-cars
│   ├── 2024-01-30-airaspi-build-log
│   ├── 2024-03-25-aethercomms
│   ├── 2024-04-11-local-diffusion
│   ├── 2024-04-25-echoing-dimensions
│   ├── 2024-06-20-sferics
│   ├── 2024-07-05-käsewerkstatt
│   └── 2025-04-15-master-thesis
└── pages
├── about.md
├── contact.md
├── cv.md
├── privacy.md
└── rent-ulli.md
</code></pre></div><span class=hidden id=copy-code-text>Code kopieren</span><span class=hidden id=search-index>https://aron.petau.net/de//search_index.en.json</span><span class=hidden id=more-matches-text>$MATCHES mehr Treffer</span></main><footer id=site-footer><div class=carbonbadge id=wcb></div><script defer src=https://unpkg.com/website-carbon-badges@1.1.3/b.min.js></script><nav><ul><li><a href=https://aron.petau.net/de/project/>Projekte</a><li><a href=https://aron.petau.net/de/pages/privacy/>Privacy</a><li><a class=external href=https://kaesewerkstatt.petau.net>Käsewerkstatt</a><li><a class=external href=https://www.newpractice.net/author/aron-petau>New Practice Network</a></ul></nav><p>© Aron Petau, 2025<ul id=socials><li><a rel=" me" href=https://github.com/arontaupe title=GitHub> <i style="--icon:url(&#34data:image/svg+xml,%3Csvg role='img' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ctitle%3EGitHub%3C/title%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E&#34)" class=icon></i> <span>GitHub</span> </a><li><a rel=" me" href=https://www.printables.com/@arontaupe title=Printables> <i class=icon style=--icon:url(data:image/svg+xml,%3Csvg%20role%3D%22img%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ctitle%3EPrintables%3C%2Ftitle%3E%3Cpath%20d%3D%22M3.678%204.8%2012%209.6v9.6l8.322-4.8V4.8L12%200ZM12%2019.2l-8.322-4.8V24Z%22%2F%3E%3C%2Fsvg%3E)></i> <span>Printables</span> </a><li><a rel=" me" href=https://www.etsy.com/de-en/shop/reprintedservices title=Etsy> <i class=icon style=--icon:url(data:image/svg+xml,%3Csvg%20role%3D%22img%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ctitle%3EEtsy%3C%2Ftitle%3E%3Cpath%20d%3D%22M8.559%202.445c0-.325.033-.52.59-.52h7.465c1.3%200%202.02%201.11%202.54%203.193l.42%201.666h1.27c.23-4.728.43-6.784.43-6.784s-3.196.36-5.09.36H6.635L1.521.196v1.37l1.725.326c1.21.24%201.5.496%201.6%201.606%200%200%20.11%203.27.11%208.64%200%205.385-.09%208.61-.09%208.61%200%20.973-.39%201.333-1.59%201.573l-1.722.33V24l5.13-.165h8.55c1.935%200%206.39.165%206.39.165.105-1.17.75-6.48.855-7.064h-1.2l-1.284%202.91c-1.005%202.28-2.476%202.445-4.11%202.445h-4.906c-1.63%200-2.415-.64-2.415-2.05V12.8s3.62%200%204.79.096c.912.064%201.463.325%201.76%201.598l.39%201.695h1.41l-.09-4.278.192-4.305h-1.391l-.45%201.89c-.283%201.244-.48%201.47-1.754%201.6-1.666.17-4.815.14-4.815.14V2.45h-.05z%22%2F%3E%3C%2Fsvg%3E)></i> <span>Etsy</span> </a><li><a rel=" me" href=https://mastodon.online/@reprintedAron title=Mastodon> <i style="--icon:url(&#34data:image/svg+xml,%3Csvg role='img' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ctitle%3EMastodon%3C/title%3E%3Cpath d='M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z'/%3E%3C/svg%3E&#34)" class=icon></i> <span>Mastodon</span> </a></ul></footer>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Some files were not shown because too many files have changed in this diff Show more