This commit is contained in:
Aron Petau 2025-05-11 15:26:20 +02:00
parent 3bd579da3c
commit 4291b1bf48
327 changed files with 3334 additions and 191 deletions

View file

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Before After
Before After

View file

@ -30,10 +30,10 @@ banner = "prusa.jpg"
show_copyright = true
show_shares = true
+++
{% gallery() %}
```json
[
{
"file": "cloning_station.jpg",
@ -85,7 +85,7 @@ show_shares = true
"title": "A custom-built printer enclosure made up of 3 Ikea Lack tables and around 3 kgs of plastic."
}
]
{% end %}
```
## 3D Printing
@ -108,8 +108,6 @@ I built both of them from kits and heavily modified them. I control them via oct
Through it, I felt more at home using Linux, programming, soldering, incorporating electronics, and iteratively designing.
I love the abilities a 3D Printer gives me and plan on using it for the [recycling](/plastic-recycling/) project.
{{ gallery(name="gallery") }}
During the last half year, I also worked in a university context with 3D printers.
We conceptualized and established a "Digitallabor", an open space to enable all people to get into contact with innovative technologies. The idea was to create some form of Makerspace while emphasizing digital media.
The project is young, it started in August last year and so most of my tasks were in Workgroups, deciding on the type of machines and types of content such a project can provide value with.

View file

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 328 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1,008 KiB

After

Width:  |  Height:  |  Size: 1,008 KiB

Before After
Before After

View file

@ -20,7 +20,7 @@ tags = [
"university of osnabrück"
]
[extra]
banner = "/images/ballpark_menu.png"
banner = "ballpark_menu.png"
show_copyright = true
show_shares = true

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -22,17 +22,20 @@ tags = [
]
[extra]
banner = "https://cloud.google.com/dialogflow/es/docs/images/fulfillment-flow.svg"
banner = "fulfillment-flow.png"
show_copyright = true
show_shares = true
+++
## Guru to Go: a speech-controlled meditation assistant and sentiment tracker
{% include video id="R73vAH37TC0" provider="youtube" %}
{{ youtube(id="R73vAH37TC0") }}
Here, you see a Demo video of a voice-controlled meditation assistant that we worked on in the course "Conversational Agents and speech interfaces"
[Course Description](https://w3o.ikw.uni-osnabrueck.de/scheinmaker/export/details/76/
){: .btn .btn--large}
<div class="buttons">
<a class="colored external" href="https://w3o.ikw.uni-osnabrueck.de/scheinmaker/export/details/76/67">Course Description</a>
</div>
The central goal of the entire project was to make the Assistant be entirely speech controlled, such that the phone needn't be touched while immersing yourself in meditation.
@ -45,11 +48,14 @@ Nevertheless, the whole Chatbot functioned as a meditation player and was able t
Attached below you can also find our final report with details on the programming and thought process.
[Read the full report](https://acrobat.adobe.com/link/track?uri=urn:aaid:scds:US:23118565-e24e-4586-b0e0-c0ef7550a067
){: .btn .btn--large}
<div class="buttons">
<a class="colored external" href="https://acrobat.adobe.com/link/track?uri=urn:aaid:scds:US:23118565-e24e-4586-b0e0-c0ef7550a067">Read the full report</a>
</div>
[Look at the Project on GitHub](https://github.com/cstenkamp/medibot_pythonbackend
){: .btn .btn--large}
<div class="buttons">
<a class="colored external" href="https://github.com/cstenkamp/medibot_pythonbackend">Look at the Project on GitHub</a>
</div>
{% alert(note=true) %}
After this being my first dip into using the Google Framework for the creation of a speech assistant and encountering many problems along the way that partly found their way also into the final report, now I managed to utilize these explorations and am currently working to create [Ällei](/allei/), another chatbot with a different focus, which is not realized within Actions on google, but will rather be getting its own react app on a website.
{: .notice}
{% end %}

View file

@ -25,13 +25,12 @@ tags = [
"university of the arts berlin"
]
[extra]
show_copyright = true
show_shares = true
+++
{% include video id="kx6amt2jY7U" provider="youtube" %}
{{ youtube(id="kx6amt2jY7U") }}
On an Excursion to Lusatia, a project with the Working Title (De)Fences was born.
Here are the current materials.

View file

@ -17,7 +17,6 @@ tags = [
"university of the arts berlin"
]
[extra]
show_copyright = true
show_shares = true
@ -34,7 +33,7 @@ The implementation I forked is [here](https://github.com/arontaupe/stable-dreamf
This one is running on stable-diffusion as a bas process, which means we are are expected to have worse results than google.
The original implementation is [here](https://dreamfusion3d.github.io)
{% include video id="shW_Jh728yg" provider="youtube" %}
{{ youtube(id="shW_Jh728yg") }}
## Gradio
@ -46,8 +45,13 @@ I used Mixamo to rig the model. It is a great tool for rigging and animating mod
## Unity
I used Unity to render the model to the magic leap 1. THrough this, i could create an interactive and immersive environment with the generated models.
I used Unity to render the model to the magic leap 1.
Through this, i could create an interactive and immersive environment with the generated models.
The dream was, to build a AI- Chamber of wishes. You pick up the glasses, state your desires and then the algorithm will present to you an almost-real object in AR.
The dream was, to build a AI- Chamber of wishes.
You pick up the glasses, state your desires and then the algorithm will present to you an almost-real object in AR.
Due to not having access to the proprietary sources from google and the beefy, but still not quite machine-learning ready computers we have at the studio, the results are not quite as good as i hoped. But still, the results are quite interesting and i am happy with the outcome. A single generated object in the Box takes roughly 20 minutes to generate. Even then, the algorithm is quite particular and oftentimes will not generate anything coherent at all.
Due to not having access to the proprietary sources from google and the beefy, but still not quite machine-learning ready computers we have at the studio, the results are not quite as good as i hoped.
But still, the results are quite interesting and i am happy with the outcome.
A single generated object in the Box takes roughly 20 minutes to generate.
Even then, the algorithm is quite particular and oftentimes will not generate anything coherent at all.

View file

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Before After
Before After

View file

@ -3,15 +3,7 @@ title = "Auraglow"
description = "Das Wesen der Dinge - Perspectives on Design"
date = 2023-03-01
authors = ["Aron Petau", "Sebastian Paintner", "Milli Keil"]
banner = "/images/cage_closeup.jpeg"
gallery:
- url: /assets/images/cage_closeup_2.jpeg
image_path: /assets/images/cage_closeup_2.jpeg
title: "The AR set that we used"
alt: "An AR Headset lying in a cage"
banner = "cage_closeup.jpeg"
[taxonomies]
tags = [
"aruco",
@ -29,13 +21,13 @@ tags = [
"unity",
"university of the arts berlin"
]
[extra]
show_copyright = true
show_shares = true
+++
![The AR set that we used](cage_closeup_2.jpeg)
What makes a room?\
How do moods and atmospheres emerge?\
Can we visualize them to make the experiences visible?
@ -49,6 +41,4 @@ Here, we question the conservative, purely physical concept of space and address
Space will have transformed: from a simple "object on which interest, thought, action is directed" (definition object Duden), to a "creature that is endowed with consciousness, thinking, sensing, acting" (definition subject Duden).
This metamorphosis of subject formation on objects enables the space to undergo changes influenced, or, more precisely a shaping, reshaping, deformation -such that the space can finally be perceived differently and multiangular.
{% include gallery %}
[See the Project on GitHub](https://github.com/arontaupe/auraglow){: .btn .btn--large}

View file

@ -1,26 +1,9 @@
+++
title = "Ruminations"
excerpt: Perspectives on Engineering
date: 2023-03-01 14:39:27 +0100
last_modified_at: 2023-03-01 14:39:27 +0100
description = "Perspectives on Engineering"
date = 2023-03-01
authors = ["Aron Petau", "Niels Gercama"]
banner = "/images/ruminations/ruminations1.jpeg"
gallery:
- url: /assets/images/ruminations/ruminations1.jpeg
image_path: /assets/images/ruminations/ruminations1.jpeg
alt: ""
title: "The projects installation"
- url: /assets/images/ruminations/ruminations2.jpeg
image_path: /assets/images/ruminations/ruminations2.jpeg
alt: ""
title: "The projects installation"
- url: /assets/images/ruminations/ruminations3.jpeg
image_path: /assets/images/ruminations/ruminations3.jpeg
alt: ""
title: "The projects installation"
banner = "ruminations1.jpeg"
[taxonomies]
tags = [
@ -40,8 +23,6 @@ tags = [
"TODO, unfinished",
"university of the arts berlin"
]
[extra]
show_copyright = true
show_shares = true
@ -70,13 +51,16 @@ A second part of the project is a low-tech installation consisting of a camera (
# The Browser extension
TODO: add photo
gallery:
![The project installation](ruminations1.jpeg)
![The project installation](ruminations2.jpeg)
![The project installation](ruminations3.jpeg)
{% include gallery %}
### Find the code on GitHub
Subvert a bit yourself, or just have a look at the code.
[The code of the Project on GitHub](https://github.com/arontaupe/ruminations){: .btn .btn--large}
[The code of the Project on GitHub](https://github.com/arontaupe/ruminations)
TODO: create video with live demo

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 398 KiB

After

Width:  |  Height:  |  Size: 398 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 680 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 944 KiB

After

Width:  |  Height:  |  Size: 944 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 853 KiB

After

Width:  |  Height:  |  Size: 853 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 839 KiB

After

Width:  |  Height:  |  Size: 839 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 768 KiB

After

Width:  |  Height:  |  Size: 768 KiB

Before After
Before After

View file

@ -16,32 +16,8 @@ tags = [
"suv",
"university of the arts berlin"
]
banner = "/images/autoimmunitaet/autoimmunitaet-1.jpg"
gallery:
- url: /assets/images/autoimmunitaet/autoimmunitaet-1.jpg
image_path: /assets/images/autoimmunitaet/autoimmunitaet-1.jpg
title: "Our action figures in action"
- url: /assets/images/autoimmunitaet/autoimmunitaet-3.jpg
image_path: /assets/images/autoimmunitaet/autoimmunitaet-3.jpg
title: "Our action figures in action"
- url: /assets/images/autoimmunitaet/autoimmunitaet-5.jpg
image_path: /assets/images/autoimmunitaet/autoimmunitaet-5.jpg
title: "Our action figures in action"
- url: /assets/images/autoimmunitaet/autoimmunitaet-6.jpg
image_path: /assets/images/autoimmunitaet/autoimmunitaet-6.jpg
title: "Our action figures in action"
- url: /assets/images/autoimmunitaet/autoimmunitaet-7.jpg
image_path: /assets/images/autoimmunitaet/autoimmunitaet-7.jpg
title: "Our action figures in action"
- url: /assets/images/autoimmunitaet/autoimmunitaet-8.jpg
image_path: /assets/images/autoimmunitaet/autoimmunitaet-8.jpg
title: "Our action figures in action"
[extra]
banner = "autoimmunitaet-1.jpg"
show_copyright = true
show_shares = true
+++
@ -70,7 +46,12 @@ Autoimmunity is a term for defects, that are produced by a dysfunctional self-to
This dysfunction causes the immune system to stop accepting certain parts of itself and build antibodies instead.\
An invitation for a speculative playful interaction.
{% include gallery %}
![Our action figures in action](autoimmunitaet-1.jpg)
![Our action figures in action](autoimmunitaet-3.jpg)
![Our action figures in action](autoimmunitaet-5.jpg)
![Our action figures in action](autoimmunitaet-6.jpg)
![Our action figures in action](autoimmunitaet-7.jpg)
![Our action figures in action](autoimmunitaet-8.jpg)
## The Process

View file

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2 MiB

After

Width:  |  Height:  |  Size: 2 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

View file

@ -0,0 +1,53 @@
+++
title = "Dreams of Cars"
description = "A subversive urban intervention"
date = 2023-06-20
authors = ["Aron Petau"]
[taxonomies]
tags = [
"ads",
"cars",
"dreams",
"greenscreen",
"imaginaries",
"lightroom",
"photoshop",
"photography",
"studio d+c",
"suv",
"university of the arts berlin",
"urban intervention"
]
[extra]
banner = "suv_door-1.jpg"
show_copyright = true
show_shares = true
+++
## Photography
In the context of the course "Fotografie Elementar" with Sebastian Herold I developed a small concept of urban intervention.\
The results were exhibited at the UdK Rundgang 2023 and are also visible here.
![The gallery piece](suv_door-1.jpg)
## Dreams of Cars
These are not just cars.\
They are Sport Utility Vehicles.\
What might they have had as hopes and dreams on the production line?\
Do they dream of drifting in dusty deserts?\
Climbing steep rocky canyon roads?\
Sliding down sun-drenched dunes?\
Discovering remote pathways in natural grasslands?\
Nevertheless, they did end up in the parking spots here in Berlin.
What drove them here?
![Dreams of Cars 1](Dreams_of_Cars-1.jpg)
![Dreams of Cars 2](Dreams_of_Cars-2.jpg)
![Dreams of Cars 3](Dreams_of_Cars-3.jpg)
![Dreams of Cars 4](Dreams_of_Cars-4.jpg)
![Dreams of Cars 5](Dreams_of_Cars-5.jpg)
![Dreams of Cars 6](Dreams_of_Cars-6.jpg)
![Dreams of Cars 7](Dreams_of_Cars-7.jpg)

View file

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 874 KiB

Before After
Before After

View file

@ -2,6 +2,7 @@
title = "AIRASPI Build Log"
authors = ["Aron Petau"]
description = "Utilizing an edge TPU to build an edge device for image recognition and object detection"
date = 2024-01-30
[taxonomies]
tags = [
"coral",
@ -14,8 +15,6 @@ tags = [
"raspberry pi",
"surveillance"
]
[extra]
show_copyright = true
show_shares = true
@ -32,8 +31,6 @@ It would be a real Edge Device, with no computation happening in the cloud.
Inspo from: [pose2art](https://github.com/MauiJerry/Pose2Art)
work in progress
{: .notice}
## Hardware

View file

@ -17,7 +17,6 @@ tags = [
"university of the arts berlin"
]
[extra]
show_copyright = true
show_shares = true
@ -36,7 +35,6 @@ With AI becoming more and more democratised and GPT-like Structures increasingly
Empower yourself against readymade technology!
Do not let others decide on what your best practices are. Get involved in the modification of the algorithm and get surprised by endless creative possibilities. Through creating a short graphic novel with 4-8 panels, participants will be able to utilise multiple flavours of the Stable Diffusion algorithm, and will have a non-mathematical understanding of the parameters and their effects on the output within some common GUIs. They will be able to apply several post-processing techniques to their generated images, such as upscaling, masking, inpainting and pose redrawing. Further, participants will be able to understand the structure of a good text prompt, be able to utilise online reference databases and manipulate parameters and directives of the Image to optimise desired qualities. Participants will also be introduced to ControlNet, enabling them to direct Pose and Image composition in detail.
## Workshop Evaluation
Over the course of 3 hours, I gave an introductory workshop in local stable diffusion processing and introduced participants to the server available to UdK Students for fast remote computation that circumvents the unethicality of continuously using a proprietary cloud service for similar outputs. There is not much we can do on the data production side and many ethical dilemmas surrounding digital colonialism remain, but local computation takes one step towards a critical and transparent use of AI tools by Artists.

View file

@ -1,12 +1,13 @@
+++
title = "Sferics"
description = "On a hunt for the Voice of the out there"
date = 2023-06-20
date = 2024-06-20
authors = ["Aron Petau"]
[taxonomies]
tags = [
"antenna",
"electelectronicsromagnetism",
"electronics",
"magnetism",
"fm",
"geosensing",
"lightning",
@ -20,7 +21,6 @@ show_copyright = true
show_shares = true
+++
## What the hell are Sferics?
>A radio atmospheric signal or sferic (sometimes also spelled "spheric") is a broadband electromagnetic impulse that occurs as a result of natural atmospheric lightning discharges. Sferics may propagate from their lightning source without major attenuation in the Earthionosphere waveguide, and can be received thousands of kilometres from their source.
@ -48,7 +48,12 @@ We have several hour-long recordings of the Sferics, which we are currently inve
Have a listen to a recording of the Sferics here:
{% include video id="2YYPg_K3dI4" provider="youtube" %}
{{ youtube(id="2YYPg_K3dI4") }}
As you can hear, there is quite a bit of 60 hz ground buzz in the recording. This is either due to the fact that the antenna was not properly grounded or we simply were still too close to the bustling city.
As you can hear, there is quite a bit of 60 hz ground buzz in the recording.
This is either due to the fact that the antenna was not properly grounded or we simply were still too close to the bustling city.
I think it is already surprising that we got such a clear impression so close to Berlin. Let's see what we can get in the countryside!
![Listening at night](sferics1.jpeg)
![The Drachenberg](sferics2.jpeg)
![The Antenna](sferics3.jpeg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View file

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

View file

@ -3,19 +3,7 @@ title = "Käsewerkstatt"
description = "Building a Food trailer and selling my first Food"
date = 2024-07-05
authors = ["Aron Petau"]
banner = "/images/käsewerkstatt/cheese.jpeg"
gallery:
- url: /assets/images/käsewerkstatt/cheese.jpeg
image_path: /assets/images/käsewerkstatt/cheese.jpeg
title: "Scraping the cheese"
- url: /assets/images/käsewerkstatt/combo_serve.jpeg
image_path: /assets/images/käsewerkstatt/combo_serve.jpeg
title: "The Recommended Combo from the Käsewerkstatt"
- url: assets/images/käsewerkstatt/logo.jpeg
image_path: /assets/images/käsewerkstatt/logo.jpeg
title: "The Logo of the Käsewerkstatt, done with the Shaper Origin"
banner = "cheese.jpeg"
[taxonomies]
tags = [
"bruschetta",
@ -24,13 +12,11 @@ tags = [
"raclette",
"workshop"
]
[extra]
show_copyright = true
show_shares = true
+++
## Enter the Käsewerkstatt
One day earlier this year I woke up and realized I had a space problem.
@ -57,9 +43,12 @@ For the future, the trailer is supposed to tend more towards vegan dishes, as a
The event itself was great, and, in part at least, started paying off the trailer.
{% include gallery caption="Some photos of the opeing event @ Bergfest in Brandenburg an der Havel" %}
Some photos of the opeing event @ Bergfest in Brandenburg an der Havel
![Scraping the cheese](cheese.jpeg)
![The Recommended Combo from the Käsewerkstatt](combo_serve.jpeg)
![The Logo of the Käsewerkstatt, done with the Shaper Origin](logo.jpeg)
We encountered lots of positive feedback and I am looking forward to the next event. So, in case you want to have a foodtruck at your event, hit me up!
Contact me at: [käsewerkstatt@petau.net](mailto:käsewerkstatt@petau.net)
{: .notice--info}

View file

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Before After
Before After

View file

@ -30,7 +30,7 @@ tags = [
]
[extra]
banner = "/images/masterthesis/puzzle.jpeg"
banner = "puzzle.jpeg"
show_copyright = true
show_shares = true
+++

View file

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

Before After
Before After

View file

@ -1,73 +0,0 @@
+++
title = "Dreams of Cars"
description = "A subversive urban intervention"
date = 2023-06-20
authors = ["Aron Petau"]
[taxonomies]
tags = [
"ads",
"cars",
"dreams",
"greenscreen",
"imaginaries",
"lightroom",
"photoshop",
"photography",
"studio d+c",
"suv",
"university of the arts berlin",
"urban intervention"
]
banner = "/images/suv/suv_door-1.jpg"
gallery:
- url: /assets/images/suv/Dreams_of_Cars-1.jpg
image_path: /assets/images/suv/Dreams_of_Cars-1.jpg
title: "Dreams of Cars 1"
- url: /assets/images/suv/Dreams_of_Cars-2.jpg
image_path: /assets/images/suv/Dreams_of_Cars-2.jpg
title: "Dreams of Cars 2"
- url: /assets/images/suv/Dreams_of_Cars-3.jpg
image_path: /assets/images/suv/Dreams_of_Cars-3.jpg
title: "Dreams of Cars 3"
- url: /assets/images/suv/Dreams_of_Cars-4.jpg
image_path: /assets/images/suv/Dreams_of_Cars-4.jpg
title: "Dreams of Cars 4"
- url: /assets/images/suv/Dreams_of_Cars-5.jpg
image_path: /assets/images/suv/Dreams_of_Cars-5.jpg
title: "Dreams of Cars 5"
- url: /assets/images/suv/Dreams_of_Cars-6.jpg
image_path: /assets/images/suv/Dreams_of_Cars-6.jpg
title: "Dreams of Cars 6"
- url: /assets/images/suv/Dreams_of_Cars-7.jpg
image_path: /assets/images/suv/Dreams_of_Cars-7.jpg
title: "Dreams of Cars 7"
[extra]
show_copyright = true
show_shares = true
+++
## Photography
In the context of the course "Fotografie Elementar" with Sebastian Herold I developed a small concept of urban intervention.\
The results were exhibited at the UdK Rundgang 2023 and are also visible here.
![The gallery piece](/assets/images/suv/suv_door-1.jpg)
## Dreams of Cars
These are not just cars.\
They are Sport Utility Vehicles.\
What might they have had as hopes and dreams on the production line?\
Do they dream of drifting in dusty deserts?\
Climbing steep rocky canyon roads?\
Sliding down sun-drenched dunes?\
Discovering remote pathways in natural grasslands?\
Nevertheless, they did end up in the parking spots here in Berlin.
What drove them here?
{% include gallery %}

BIN
public/404.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

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

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}()}));

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

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: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,008 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 874 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

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()
})
})();

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

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

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);
}
}

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