sort
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 212 KiB |
|
@ -30,10 +30,10 @@ banner = "prusa.jpg"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+++
|
+++
|
||||||
{% gallery() %}
|
|
||||||
|
```json
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"file": "cloning_station.jpg",
|
"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."
|
"title": "A custom-built printer enclosure made up of 3 Ikea Lack tables and around 3 kgs of plastic."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
{% end %}
|
```
|
||||||
|
|
||||||
## 3D Printing
|
## 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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
Before Width: | Height: | Size: 328 KiB After Width: | Height: | Size: 328 KiB |
Before Width: | Height: | Size: 1,008 KiB After Width: | Height: | Size: 1,008 KiB |
|
@ -20,7 +20,7 @@ tags = [
|
||||||
"university of osnabrück"
|
"university of osnabrück"
|
||||||
]
|
]
|
||||||
[extra]
|
[extra]
|
||||||
banner = "/images/ballpark_menu.png"
|
banner = "ballpark_menu.png"
|
||||||
|
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
|
|
BIN
content/blog/2020-07-15-chatbot/fulfillment-flow.png
Normal file
After Width: | Height: | Size: 54 KiB |
|
@ -22,17 +22,20 @@ tags = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
banner = "https://cloud.google.com/dialogflow/es/docs/images/fulfillment-flow.svg"
|
banner = "fulfillment-flow.png"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
|
|
||||||
## Guru to Go: a speech-controlled meditation assistant and sentiment tracker
|
## 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"
|
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.
|
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.
|
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
|
<div class="buttons">
|
||||||
){: .btn .btn--large}
|
<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
|
<div class="buttons">
|
||||||
){: .btn .btn--large}
|
<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.
|
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 %}
|
|
@ -25,13 +25,12 @@ tags = [
|
||||||
"university of the arts berlin"
|
"university of the arts berlin"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = 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.
|
On an Excursion to Lusatia, a project with the Working Title (De)Fences was born.
|
||||||
Here are the current materials.
|
Here are the current materials.
|
|
@ -17,7 +17,6 @@ tags = [
|
||||||
"university of the arts berlin"
|
"university of the arts berlin"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = 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.
|
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)
|
The original implementation is [here](https://dreamfusion3d.github.io)
|
||||||
|
|
||||||
{% include video id="shW_Jh728yg" provider="youtube" %}
|
{{ youtube(id="shW_Jh728yg") }}
|
||||||
|
|
||||||
## Gradio
|
## Gradio
|
||||||
|
|
||||||
|
@ -46,8 +45,13 @@ I used Mixamo to rig the model. It is a great tool for rigging and animating mod
|
||||||
|
|
||||||
## Unity
|
## 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.
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
@ -3,15 +3,7 @@ title = "Auraglow"
|
||||||
description = "Das Wesen der Dinge - Perspectives on Design"
|
description = "Das Wesen der Dinge - Perspectives on Design"
|
||||||
date = 2023-03-01
|
date = 2023-03-01
|
||||||
authors = ["Aron Petau", "Sebastian Paintner", "Milli Keil"]
|
authors = ["Aron Petau", "Sebastian Paintner", "Milli Keil"]
|
||||||
|
banner = "cage_closeup.jpeg"
|
||||||
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"
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = [
|
tags = [
|
||||||
"aruco",
|
"aruco",
|
||||||
|
@ -29,13 +21,13 @@ tags = [
|
||||||
"unity",
|
"unity",
|
||||||
"university of the arts berlin"
|
"university of the arts berlin"
|
||||||
]
|
]
|
||||||
|
[extra]
|
||||||
|
|
||||||
[extra]
|
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
What makes a room?\
|
What makes a room?\
|
||||||
How do moods and atmospheres emerge?\
|
How do moods and atmospheres emerge?\
|
||||||
Can we visualize them to make the experiences visible?
|
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).
|
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.
|
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}
|
[See the Project on GitHub](https://github.com/arontaupe/auraglow){: .btn .btn--large}
|
|
@ -1,26 +1,9 @@
|
||||||
+++
|
+++
|
||||||
title = "Ruminations"
|
title = "Ruminations"
|
||||||
excerpt: Perspectives on Engineering
|
description = "Perspectives on Engineering"
|
||||||
date: 2023-03-01 14:39:27 +0100
|
date = 2023-03-01
|
||||||
last_modified_at: 2023-03-01 14:39:27 +0100
|
|
||||||
authors = ["Aron Petau", "Niels Gercama"]
|
authors = ["Aron Petau", "Niels Gercama"]
|
||||||
|
banner = "ruminations1.jpeg"
|
||||||
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"
|
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = [
|
tags = [
|
||||||
|
@ -40,8 +23,6 @@ tags = [
|
||||||
"TODO, unfinished",
|
"TODO, unfinished",
|
||||||
"university of the arts berlin"
|
"university of the arts berlin"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = 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
|
# The Browser extension
|
||||||
|
|
||||||
TODO: add photo
|
gallery:
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
{% include gallery %}
|
|
||||||
|
|
||||||
### Find the code on GitHub
|
### Find the code on GitHub
|
||||||
|
|
||||||
Subvert a bit yourself, or just have a look at the code.
|
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
|
TODO: create video with live demo
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 398 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 680 KiB |
Before Width: | Height: | Size: 944 KiB After Width: | Height: | Size: 944 KiB |
Before Width: | Height: | Size: 853 KiB After Width: | Height: | Size: 853 KiB |
Before Width: | Height: | Size: 839 KiB After Width: | Height: | Size: 839 KiB |
Before Width: | Height: | Size: 768 KiB After Width: | Height: | Size: 768 KiB |
|
@ -16,32 +16,8 @@ tags = [
|
||||||
"suv",
|
"suv",
|
||||||
"university of the arts berlin"
|
"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]
|
[extra]
|
||||||
|
banner = "autoimmunitaet-1.jpg"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = 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.\
|
This dysfunction causes the immune system to stop accepting certain parts of itself and build antibodies instead.\
|
||||||
An invitation for a speculative playful interaction.
|
An invitation for a speculative playful interaction.
|
||||||
|
|
||||||
{% include gallery %}
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## The Process
|
## The Process
|
||||||
|
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
Before Width: | Height: | Size: 2 MiB After Width: | Height: | Size: 2 MiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
53
content/blog/2023-06-20-dreams-of-cars/index.md
Normal 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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?
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
Before Width: | Height: | Size: 874 KiB After Width: | Height: | Size: 874 KiB |
|
@ -2,6 +2,7 @@
|
||||||
title = "AIRASPI Build Log"
|
title = "AIRASPI Build Log"
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
description = "Utilizing an edge TPU to build an edge device for image recognition and object detection"
|
description = "Utilizing an edge TPU to build an edge device for image recognition and object detection"
|
||||||
|
date = 2024-01-30
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = [
|
tags = [
|
||||||
"coral",
|
"coral",
|
||||||
|
@ -14,8 +15,6 @@ tags = [
|
||||||
"raspberry pi",
|
"raspberry pi",
|
||||||
"surveillance"
|
"surveillance"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = 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)
|
Inspo from: [pose2art](https://github.com/MauiJerry/Pose2Art)
|
||||||
|
|
||||||
work in progress
|
|
||||||
{: .notice}
|
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
|
@ -17,7 +17,6 @@ tags = [
|
||||||
"university of the arts berlin"
|
"university of the arts berlin"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = 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!
|
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.
|
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
|
## 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.
|
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.
|
|
@ -1,12 +1,13 @@
|
||||||
+++
|
+++
|
||||||
title = "Sferics"
|
title = "Sferics"
|
||||||
description = "On a hunt for the Voice of the out there"
|
description = "On a hunt for the Voice of the out there"
|
||||||
date = 2023-06-20
|
date = 2024-06-20
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = [
|
tags = [
|
||||||
"antenna",
|
"antenna",
|
||||||
"electelectronicsromagnetism",
|
"electronics",
|
||||||
|
"magnetism",
|
||||||
"fm",
|
"fm",
|
||||||
"geosensing",
|
"geosensing",
|
||||||
"lightning",
|
"lightning",
|
||||||
|
@ -20,7 +21,6 @@ show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
|
||||||
## What the hell are Sferics?
|
## 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 Earth–ionosphere waveguide, and can be received thousands of kilometres from their source.
|
>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 Earth–ionosphere 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:
|
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!
|
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!
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
BIN
content/blog/2024-06-20-sferics/sferics1.jpg
Normal file
After Width: | Height: | Size: 999 KiB |
BIN
content/blog/2024-06-20-sferics/sferics2.jpg
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
content/blog/2024-06-20-sferics/sferics3.jpg
Normal file
After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
|
@ -3,19 +3,7 @@ title = "Käsewerkstatt"
|
||||||
description = "Building a Food trailer and selling my first Food"
|
description = "Building a Food trailer and selling my first Food"
|
||||||
date = 2024-07-05
|
date = 2024-07-05
|
||||||
authors = ["Aron Petau"]
|
authors = ["Aron Petau"]
|
||||||
banner = "/images/käsewerkstatt/cheese.jpeg"
|
banner = "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"
|
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = [
|
tags = [
|
||||||
"bruschetta",
|
"bruschetta",
|
||||||
|
@ -24,13 +12,11 @@ tags = [
|
||||||
"raclette",
|
"raclette",
|
||||||
"workshop"
|
"workshop"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
|
|
||||||
+++
|
+++
|
||||||
|
|
||||||
## Enter the Käsewerkstatt
|
## Enter the Käsewerkstatt
|
||||||
|
|
||||||
One day earlier this year I woke up and realized I had a space problem.
|
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.
|
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
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
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!
|
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)
|
Contact me at: [käsewerkstatt@petau.net](mailto:käsewerkstatt@petau.net)
|
||||||
{: .notice--info}
|
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
|
@ -30,7 +30,7 @@ tags = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
banner = "/images/masterthesis/puzzle.jpeg"
|
banner = "puzzle.jpeg"
|
||||||
show_copyright = true
|
show_copyright = true
|
||||||
show_shares = true
|
show_shares = true
|
||||||
+++
|
+++
|
||||||
|
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 283 KiB |
|
@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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
After Width: | Height: | Size: 4 KiB |
BIN
public/404.png
Normal file
After Width: | Height: | Size: 340 B |
BIN
public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 28 KiB |
1
public/auto-render.min.js
vendored
Normal 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/blog/auraglow/cage_closeup.jpeg
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
public/blog/auraglow/cage_closeup_2.jpeg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
public/blog/auraglow/cage_milli.jpeg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-1.jpg
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-2.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-3.jpg
Normal file
After Width: | Height: | Size: 680 KiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-5.jpg
Normal file
After Width: | Height: | Size: 944 KiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-6.jpg
Normal file
After Width: | Height: | Size: 853 KiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-7.jpg
Normal file
After Width: | Height: | Size: 839 KiB |
BIN
public/blog/autoimmunitaet/autoimmunitaet-8.jpg
Normal file
After Width: | Height: | Size: 768 KiB |
BIN
public/blog/ballpark/ballpark_menu.png
Normal file
After Width: | Height: | Size: 1,008 KiB |
BIN
public/blog/beacon/india_key_monastery.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
public/blog/cad/render_bike_holder.png
Normal file
After Width: | Height: | Size: 273 KiB |
BIN
public/blog/chatbot/fulfillment-flow.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-1.jpg
Normal file
After Width: | Height: | Size: 2.3 MiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-2.jpg
Normal file
After Width: | Height: | Size: 2 MiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-3.jpg
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-4.jpg
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-5.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-6.jpg
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
public/blog/dreams-of-cars/Dreams_of_Cars-7.jpg
Normal file
After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 874 KiB After Width: | Height: | Size: 874 KiB |
BIN
public/blog/käsewerkstatt/cheese.jpeg
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
public/blog/käsewerkstatt/combo_serve.jpeg
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
public/blog/käsewerkstatt/logo.jpeg
Normal file
After Width: | Height: | Size: 2.7 MiB |
BIN
public/blog/käsewerkstatt/product.jpeg
Normal file
After Width: | Height: | Size: 2.3 MiB |
BIN
public/blog/käsewerkstatt/trailer.jpeg
Normal file
After Width: | Height: | Size: 2.7 MiB |
BIN
public/blog/käsewerkstatt/welcome.jpeg
Normal file
After Width: | Height: | Size: 2.2 MiB |
BIN
public/blog/master-thesis/puzzle.jpeg
Normal file
After Width: | Height: | Size: 283 KiB |
BIN
public/blog/plastic-recycling/recycling_graphic.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/blog/printing/cloning_station.jpg
Normal file
After Width: | Height: | Size: 212 KiB |
BIN
public/blog/printing/prusa.jpg
Normal file
After Width: | Height: | Size: 328 KiB |
BIN
public/blog/ruminations/ruminations1.jpeg
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
public/blog/ruminations/ruminations2.jpeg
Normal file
After Width: | Height: | Size: 398 KiB |
BIN
public/blog/ruminations/ruminations3.jpeg
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
public/blog/sferics/sferics1.jpg
Normal file
After Width: | Height: | Size: 999 KiB |
BIN
public/blog/sferics/sferics2.jpg
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
public/blog/sferics/sferics3.jpg
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
public/blog/thesis/rt_choice_corr_by_condition.png
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
public/card.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
27
public/closable.js
Normal 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
|
@ -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, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
function emojify(input, emojis) {
|
||||||
|
let output = input;
|
||||||
|
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
let picture = document.createElement("picture");
|
||||||
|
|
||||||
|
let source = document.createElement("source");
|
||||||
|
source.setAttribute("srcset", escapeHtml(emoji.url));
|
||||||
|
source.setAttribute("media", "(prefers-reduced-motion: no-preference)");
|
||||||
|
|
||||||
|
let img = document.createElement("img");
|
||||||
|
img.className = "emoji";
|
||||||
|
img.setAttribute("src", escapeHtml(emoji.static_url));
|
||||||
|
img.setAttribute("alt", `:${emoji.shortcode}:`);
|
||||||
|
img.setAttribute("title", `:${emoji.shortcode}:`);
|
||||||
|
if (lazyAsyncImage == "true") {
|
||||||
|
img.setAttribute("decoding", "async");
|
||||||
|
img.setAttribute("loading", "lazy");
|
||||||
|
}
|
||||||
|
|
||||||
|
picture.appendChild(source);
|
||||||
|
picture.appendChild(img);
|
||||||
|
|
||||||
|
output = output.replace(`:${emoji.shortcode}:`, picture.outerHTML);
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadComments() {
|
||||||
|
let commentsWrapper = document.getElementById("comments-wrapper");
|
||||||
|
commentsWrapper.innerHTML = "";
|
||||||
|
|
||||||
|
let loadCommentsButton = document.getElementById("load-comments");
|
||||||
|
loadCommentsButton.innerHTML = loadingText;
|
||||||
|
loadCommentsButton.disabled = true;
|
||||||
|
|
||||||
|
fetch(`https://${host}/api/v1/statuses/${id}/context`)
|
||||||
|
.then(function (response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
let descendants = data["descendants"];
|
||||||
|
if (
|
||||||
|
descendants &&
|
||||||
|
Array.isArray(descendants) &&
|
||||||
|
descendants.length > 0
|
||||||
|
) {
|
||||||
|
commentsWrapper.innerHTML = "";
|
||||||
|
|
||||||
|
descendants.forEach(function (status) {
|
||||||
|
console.log(descendants);
|
||||||
|
if (status.account.display_name.length > 0) {
|
||||||
|
status.account.display_name = escapeHtml(
|
||||||
|
status.account.display_name
|
||||||
|
);
|
||||||
|
status.account.display_name = emojify(
|
||||||
|
status.account.display_name,
|
||||||
|
status.account.emojis
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
status.account.display_name = status.account.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = "";
|
||||||
|
if (status.account.acct.includes("@")) {
|
||||||
|
instance = status.account.acct.split("@")[1];
|
||||||
|
} else {
|
||||||
|
instance = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isReply = status.in_reply_to_id !== id;
|
||||||
|
|
||||||
|
let op = false;
|
||||||
|
if (status.account.acct == user) {
|
||||||
|
op = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.content = emojify(status.content, status.emojis);
|
||||||
|
|
||||||
|
let comment = document.createElement("article");
|
||||||
|
comment.id = `comment-${status.id}`;
|
||||||
|
comment.className = isReply ? "comment comment-reply" : "comment";
|
||||||
|
comment.setAttribute("itemprop", "comment");
|
||||||
|
comment.setAttribute("itemtype", "http://schema.org/Comment");
|
||||||
|
|
||||||
|
let avatarSource = document.createElement("source");
|
||||||
|
avatarSource.setAttribute(
|
||||||
|
"srcset",
|
||||||
|
escapeHtml(status.account.avatar)
|
||||||
|
);
|
||||||
|
avatarSource.setAttribute(
|
||||||
|
"media",
|
||||||
|
"(prefers-reduced-motion: no-preference)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let avatarImg = document.createElement("img");
|
||||||
|
avatarImg.className = "avatar";
|
||||||
|
avatarImg.setAttribute(
|
||||||
|
"src",
|
||||||
|
escapeHtml(status.account.avatar_static)
|
||||||
|
);
|
||||||
|
avatarImg.setAttribute(
|
||||||
|
"alt",
|
||||||
|
`@${status.account.username}@${instance} avatar`
|
||||||
|
);
|
||||||
|
if (lazyAsyncImage == "true") {
|
||||||
|
avatarImg.setAttribute("decoding", "async");
|
||||||
|
avatarImg.setAttribute("loading", "lazy");
|
||||||
|
}
|
||||||
|
|
||||||
|
let avatarPicture = document.createElement("picture");
|
||||||
|
avatarPicture.appendChild(avatarSource);
|
||||||
|
avatarPicture.appendChild(avatarImg);
|
||||||
|
|
||||||
|
let avatar = document.createElement("a");
|
||||||
|
avatar.className = "avatar-link";
|
||||||
|
avatar.setAttribute("href", status.account.url);
|
||||||
|
avatar.setAttribute("rel", relAttributes);
|
||||||
|
avatar.setAttribute(
|
||||||
|
"title",
|
||||||
|
`${viewProfileText} @${status.account.username}@${instance}`
|
||||||
|
);
|
||||||
|
avatar.appendChild(avatarPicture);
|
||||||
|
comment.appendChild(avatar);
|
||||||
|
|
||||||
|
let instanceBadge = document.createElement("a");
|
||||||
|
instanceBadge.className = "instance";
|
||||||
|
instanceBadge.setAttribute("href", status.account.url);
|
||||||
|
instanceBadge.setAttribute(
|
||||||
|
"title",
|
||||||
|
`@${status.account.username}@${instance}`
|
||||||
|
);
|
||||||
|
instanceBadge.setAttribute("rel", relAttributes);
|
||||||
|
instanceBadge.textContent = instance;
|
||||||
|
|
||||||
|
let display = document.createElement("span");
|
||||||
|
display.className = "display";
|
||||||
|
display.setAttribute("itemprop", "author");
|
||||||
|
display.setAttribute("itemtype", "http://schema.org/Person");
|
||||||
|
display.innerHTML = status.account.display_name;
|
||||||
|
|
||||||
|
let header = document.createElement("header");
|
||||||
|
header.className = "author";
|
||||||
|
header.appendChild(display);
|
||||||
|
header.appendChild(instanceBadge);
|
||||||
|
comment.appendChild(header);
|
||||||
|
|
||||||
|
let permalink = document.createElement("a");
|
||||||
|
permalink.setAttribute("href", status.url);
|
||||||
|
permalink.setAttribute("itemprop", "url");
|
||||||
|
permalink.setAttribute("title", `${viewCommentText} ${instance}`);
|
||||||
|
permalink.setAttribute("rel", relAttributes);
|
||||||
|
permalink.textContent = new Date(
|
||||||
|
status.created_at
|
||||||
|
).toLocaleString(dateLocale, {
|
||||||
|
dateStyle: "long",
|
||||||
|
timeStyle: "short",
|
||||||
|
});
|
||||||
|
|
||||||
|
let timestamp = document.createElement("time");
|
||||||
|
timestamp.setAttribute("datetime", status.created_at);
|
||||||
|
timestamp.appendChild(permalink);
|
||||||
|
permalink.classList.add("external");
|
||||||
|
comment.appendChild(timestamp);
|
||||||
|
|
||||||
|
let main = document.createElement("main");
|
||||||
|
main.setAttribute("itemprop", "text");
|
||||||
|
|
||||||
|
if (status.sensitive == true || status.spoiler_text != "") {
|
||||||
|
let summary = document.createElement("summary");
|
||||||
|
if (status.spoiler_text == "") {
|
||||||
|
status.spoiler_text == sensitiveText;
|
||||||
|
}
|
||||||
|
summary.innerHTML = status.spoiler_text;
|
||||||
|
|
||||||
|
let spoiler = document.createElement("details");
|
||||||
|
spoiler.appendChild(summary);
|
||||||
|
spoiler.innerHTML += status.content;
|
||||||
|
|
||||||
|
main.appendChild(spoiler);
|
||||||
|
} else {
|
||||||
|
main.innerHTML = status.content;
|
||||||
|
}
|
||||||
|
comment.appendChild(main);
|
||||||
|
|
||||||
|
let attachments = status.media_attachments;
|
||||||
|
let SUPPORTED_MEDIA = ["image", "video", "gifv", "audio"];
|
||||||
|
let media = document.createElement("div");
|
||||||
|
media.className = "attachments";
|
||||||
|
if (
|
||||||
|
attachments &&
|
||||||
|
Array.isArray(attachments) &&
|
||||||
|
attachments.length > 0
|
||||||
|
) {
|
||||||
|
attachments.forEach((attachment) => {
|
||||||
|
if (SUPPORTED_MEDIA.includes(attachment.type)) {
|
||||||
|
|
||||||
|
let mediaElement;
|
||||||
|
switch (attachment.type) {
|
||||||
|
case "image":
|
||||||
|
mediaElement = document.createElement("img");
|
||||||
|
mediaElement.setAttribute("src", attachment.preview_url);
|
||||||
|
|
||||||
|
if (attachment.description != null) {
|
||||||
|
mediaElement.setAttribute("alt", attachment.description);
|
||||||
|
mediaElement.setAttribute("title", attachment.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lazyAsyncImage == "true") {
|
||||||
|
mediaElement.setAttribute("decoding", "async");
|
||||||
|
mediaElement.setAttribute("loading", "lazy");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.sensitive == true) {
|
||||||
|
mediaElement.classList.add("spoiler");
|
||||||
|
}
|
||||||
|
|
||||||
|
media.appendChild(mediaElement);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "video":
|
||||||
|
mediaElement = document.createElement("video");
|
||||||
|
mediaElement.setAttribute("src", attachment.url);
|
||||||
|
mediaElement.setAttribute("controls", "");
|
||||||
|
|
||||||
|
if (attachment.description != null) {
|
||||||
|
mediaElement.setAttribute("aria-title", attachment.description);
|
||||||
|
mediaElement.setAttribute("title", attachment.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.sensitive == true) {
|
||||||
|
mediaElement.classList.add("spoiler");
|
||||||
|
}
|
||||||
|
|
||||||
|
media.appendChild(mediaElement);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "gifv":
|
||||||
|
mediaElement = document.createElement("video");
|
||||||
|
mediaElement.setAttribute("src", attachment.url);
|
||||||
|
mediaElement.setAttribute("autoplay", "");
|
||||||
|
mediaElement.setAttribute("playsinline", "");
|
||||||
|
mediaElement.setAttribute("loop", "");
|
||||||
|
|
||||||
|
if (attachment.description != null) {
|
||||||
|
mediaElement.setAttribute("aria-title", attachment.description);
|
||||||
|
mediaElement.setAttribute("title", attachment.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.sensitive == true) {
|
||||||
|
mediaElement.classList.add("spoiler");
|
||||||
|
}
|
||||||
|
|
||||||
|
media.appendChild(mediaElement);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "audio":
|
||||||
|
mediaElement = document.createElement("audio");
|
||||||
|
mediaElement.setAttribute("src", attachment.url);
|
||||||
|
mediaElement.setAttribute("controls", "");
|
||||||
|
|
||||||
|
if (attachment.description != null) {
|
||||||
|
mediaElement.setAttribute("aria-title", attachment.description);
|
||||||
|
mediaElement.setAttribute("title", attachment.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
media.appendChild(mediaElement);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaLink = document.createElement("a");
|
||||||
|
mediaLink.setAttribute("href", attachment.url);
|
||||||
|
mediaLink.setAttribute("rel", relAttributes);
|
||||||
|
mediaLink.appendChild(mediaElement);
|
||||||
|
|
||||||
|
media.appendChild(mediaLink);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
comment.appendChild(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
let interactions = document.createElement("footer");
|
||||||
|
|
||||||
|
let boosts = document.createElement("a");
|
||||||
|
boosts.className = "boosts";
|
||||||
|
boosts.setAttribute("href", `${status.url}/reblogs`);
|
||||||
|
boosts.setAttribute("title", `${boostsFromText}`.replace("$INSTANCE", instance));
|
||||||
|
|
||||||
|
let boostsIcon = document.createElement("i");
|
||||||
|
boostsIcon.className = "icon";
|
||||||
|
boosts.appendChild(boostsIcon);
|
||||||
|
boosts.insertAdjacentHTML('beforeend', ` ${status.reblogs_count}`);
|
||||||
|
interactions.appendChild(boosts);
|
||||||
|
|
||||||
|
let faves = document.createElement("a");
|
||||||
|
faves.className = "faves";
|
||||||
|
faves.setAttribute("href", `${status.url}/favourites`);
|
||||||
|
faves.setAttribute("title", `${favesFromText}`.replace("$INSTANCE", instance));
|
||||||
|
|
||||||
|
let favesIcon = document.createElement("i");
|
||||||
|
favesIcon.className = "icon";
|
||||||
|
faves.appendChild(favesIcon);
|
||||||
|
faves.insertAdjacentHTML('beforeend', ` ${status.favourites_count}`);
|
||||||
|
interactions.appendChild(faves);
|
||||||
|
comment.appendChild(interactions);
|
||||||
|
|
||||||
|
if (status.card != null) {
|
||||||
|
let cardFigure = document.createElement("figure");
|
||||||
|
|
||||||
|
if (status.card.image != null) {
|
||||||
|
let cardImg = document.createElement("img");
|
||||||
|
cardImg.setAttribute("src", status.card.image);
|
||||||
|
cardImg.classList.add("no-hover");
|
||||||
|
cardFigure.appendChild(cardImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardCaption = document.createElement("figcaption");
|
||||||
|
|
||||||
|
let cardTitle = document.createElement("strong");
|
||||||
|
cardTitle.innerHTML = status.card.title;
|
||||||
|
cardCaption.appendChild(cardTitle);
|
||||||
|
|
||||||
|
if (status.card.description != null && status.card.description.length > 0) {
|
||||||
|
let cardDescription = document.createElement("p");
|
||||||
|
cardDescription.innerHTML = status.card.description;
|
||||||
|
cardCaption.appendChild(cardDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
cardFigure.appendChild(cardCaption);
|
||||||
|
|
||||||
|
let card = document.createElement("a");
|
||||||
|
card.className = "card";
|
||||||
|
card.setAttribute("href", status.card.url);
|
||||||
|
card.setAttribute("rel", relAttributes);
|
||||||
|
card.appendChild(cardFigure);
|
||||||
|
|
||||||
|
comment.appendChild(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op === true) {
|
||||||
|
comment.classList.add("op");
|
||||||
|
|
||||||
|
avatar.classList.add("op");
|
||||||
|
avatar.setAttribute(
|
||||||
|
"title",
|
||||||
|
`${blogPostAuthorText}: ` + avatar.getAttribute("title")
|
||||||
|
);
|
||||||
|
|
||||||
|
instanceBadge.classList.add("op");
|
||||||
|
instanceBadge.setAttribute(
|
||||||
|
"title",
|
||||||
|
`${blogPostAuthorText}: ` + instanceBadge.getAttribute("title")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsWrapper.innerHTML += comment.outerHTML;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
var statusText = document.createElement("p");
|
||||||
|
statusText.innerHTML = noCommentsText;
|
||||||
|
statusText.setAttribute("id", "comments-status");
|
||||||
|
commentsWrapper.appendChild(statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCommentsButton.innerHTML = reloadText;
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.error('Error loading comments:', error);
|
||||||
|
})
|
||||||
|
.finally(function () {
|
||||||
|
loadCommentsButton.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
57
public/copy-button.js
Normal 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
|
@ -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
|
@ -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
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|