Compare commits

...

3 Commits

Author SHA1 Message Date
Anton Pogrebnjak
bd814e06ce Updated minor stuff 2026-02-23 02:10:34 +01:00
Anton Pogrebnjak
cd99d22174 Freiraum 2026-02-23 01:58:27 +01:00
Anton Pogrebnjak
128af8a7d5 Updated dependencies 2026-02-22 23:35:14 +01:00
17 changed files with 5170 additions and 602 deletions

View File

@@ -4,6 +4,7 @@ import mdx from '@astrojs/mdx';
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import sitemap from '@astrojs/sitemap';
import { typst } from 'astro-typst';
// https://astro.build/config
export default defineConfig({
@@ -15,5 +16,5 @@ export default defineConfig({
integrations: [mdx({
syntaxHighlight: 'shiki',
shikiConfig: { theme: "dracula" },
}), sitemap()],
}), sitemap(), typst()],
});

2233
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,12 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^4.3.5",
"@astrojs/rss": "^4.0.12",
"@astrojs/sitemap": "^3.5.1",
"@astrojs/vue": "^5.1.1",
"astro": "^5.13.7",
"@astrojs/mdx": "^4.3.13",
"@astrojs/rss": "^4.0.15",
"@astrojs/sitemap": "^3.7.0",
"@astrojs/vue": "^5.1.4",
"astro": "^5.17.3",
"astro-typst": "^0.12.1",
"rehype-katex": "^7.0.1",
"remark-math": "^6.0.0"
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View File

@@ -1,25 +1,11 @@
---
const today = new Date();
import Socials from "./Socials.astro";
---
<footer>
&copy; {today.getFullYear()} <a href="/about">Anton Pogrebnjak</a>. All rights reserved.
<div class="social-links">
<a href="https://github.com/Pantonius" target="_blank">
<span class="sr-only">Go to my GitHub repo</span>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
width="32"
height="32"
astro-icon="social/github"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg
>
</a>
</div>
&copy; {today.getFullYear()} <a href="/about">Anton</a>. All rights reserved.
<Socials />
</footer>
<style>
footer {
@@ -28,12 +14,6 @@ const today = new Date();
width: 100%;
}
.social-links {
display: flex;
justify-content: center;
gap: 1em;
margin-top: 1em;
}
.social-links a {
text-decoration: none;
color: var(--text);

View File

@@ -0,0 +1,25 @@
<div id="map" style="height: 400px;">
</div>
<script>
let map = L.map('map').setView([47.689749, 9.187857], 16);
L.tileLayer('https://sgx.geodatenzentrum.de/wmts_basemapde/tile/1.0.0/de_basemapde_web_raster_grau/default/GLOBAL_WEBMERCATOR/{z}/{y}/{x}.png', {
minZoom: 0,
maxZoom: 20,
attribution: 'Map data: &copy; <a href="http://www.govdata.de/dl-de/by-2-0">dl-de/by-2-0</a>',
ext: 'png'
}).addTo(map);
let geojson = await fetch('/geojson/freiraum/uni_konstanz.geojson', {
method: 'GET',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json());
let geoLayer = L.geoJson(geojson, {
style: { fillColor: 'rgba(18, 174, 225, .2)', fillOpacity: 1, stroke: true, color: 'rgba(18, 174, 225, .6)' },
}).addTo(map);
</script>

View File

@@ -1,6 +1,7 @@
---
import { ClientRouter } from "astro:transitions";
import HeaderLink from "./HeaderLink.astro";
import Socials from "./Socials.astro";
---
<header>
@@ -12,22 +13,8 @@ import HeaderLink from "./HeaderLink.astro";
<HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/projects">Projects</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink>
</div>
<div class="social-links">
<a href="https://github.com/Pantonius" target="_blank">
<span class="sr-only">Go to my GitHub repo</span>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
width="32"
height="32"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg
>
</a>
</div>
</div>
<Socials />
</nav>
</header>
@@ -72,10 +59,6 @@ import HeaderLink from "./HeaderLink.astro";
border-bottom-color: var(--primary);
}
.social-links,
.social-links a {
display: flex;
}
@media (max-width: 720px) {
header .logo img {

View File

@@ -28,6 +28,7 @@ import { ClientRouter } from "astro:transitions";
document.addEventListener("mousemove", (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
draw();
});
let distanceThreshold = .2;
@@ -66,7 +67,5 @@ import { ClientRouter } from "astro:transitions";
}
};
setInterval(() => {
draw();
}, 1000 / 60);
draw();
</script>

View File

@@ -78,6 +78,7 @@ const color = Astro.props.color || 'var(--text)';
margin: 0;
color: var(--color);
line-height: 1;
font-variant-caps: small-caps;
}
.date {
margin: 0;

View File

@@ -0,0 +1,28 @@
<div class="social-links">
<a href="https://github.com/Pantonius" target="_blank">
<span class="sr-only">Go to my GitHub repo</span>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
width="32"
height="32"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg>
</a>
<a href="https://mastodon.social/@pantonius" target="_blank">
<span class="sr-only">Go to my Mastodon profile</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="46" height="46"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill="currentColor" d="M529 243.1C529 145.9 465.3 117.4 465.3 117.4C402.8 88.7 236.7 89 174.8 117.4C174.8 117.4 111.1 145.9 111.1 243.1C111.1 358.8 104.5 502.5 216.7 532.2C257.2 542.9 292 545.2 320 543.6C370.8 540.8 399.3 525.5 399.3 525.5L397.6 488.6C397.6 488.6 361.3 500 320.5 498.7C280.1 497.3 237.5 494.3 230.9 444.7C230.3 440.1 230 435.4 230 430.8C315.6 451.7 388.7 439.9 408.7 437.5C464.8 430.8 513.7 396.2 519.9 364.6C529.7 314.8 528.9 243.1 528.9 243.1zM453.9 368.3L407.3 368.3L407.3 254.1C407.3 204.4 343.3 202.5 343.3 261L343.3 323.5L297 323.5L297 261C297 202.5 233 204.4 233 254.1L233 368.3L186.3 368.3C186.3 246.2 181.1 220.4 204.7 193.3C230.6 164.4 284.5 162.5 308.5 199.4L320.1 218.9L331.7 199.4C355.8 162.3 409.8 164.6 435.5 193.3C459.2 220.6 453.9 246.3 453.9 368.3L453.9 368.3z"/></svg>
</a>
</div>
<style>
.social-links,
.social-links a {
display: flex;
justify-content: center;
align-items: center;
color: var(--text);
}
</style>

View File

@@ -1,6 +1,21 @@
import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';
const bachelor = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/bachelor', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
part: z.number(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
@@ -16,7 +31,7 @@ const blog = defineCollection({
});
const projects = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
// Load Markdown and MDX files in the `src/content/projects/` directory.
loader: glob({
base: './src/content/projects', pattern: '**/*.{md,mdx}'
}),
@@ -31,4 +46,4 @@ const projects = defineCollection({
}),
});
export const collections = { blog, projects };
export const collections = { bachelor, blog, projects };

View File

@@ -0,0 +1,75 @@
---
title: Freiraum
pubDate: 2026-02-23T12:00:00Z
description: Freiraum is a room availability checker developed for the students of the University of Konstanz
heroImage: /images/posts/freiraum/homepage.png
---
import UKNMap from '../../components/Freiraum/UKNMap.astro';
Freiraum is a room availability checker developed for the students of the University of Konstanz. Much like the [Frag Wahltraut tool](../frag-wahltraut), it runs entirely on the client side. Meaning, the server has no real knowledge of any rooms in the university or their status. Instead, the client code fetches the data from a server, that is only available from within the university network.
# Background
During the lecture and exam periods, students are in need of viable working areas. Both for silent and group work. While the library is readily available for silent work, even its vast space can at times (in exam periods) reach full capacity. On the front of working areas for groups, the library is short on rooms that are sound isolated.
Thankfully, the university itself is much more than its library. Many rooms, that are used throughout the day will occassionally have some free spots, in which students can freely occupy the space and even use it effectively for group work without disturbing others.
While the university has been working on accommodating the students' need for working spaces, a real long-term solution hasn't left the planning phase (atleast that I am aware of).
However, anyone within the university network has access to a certain web service, providing the full planned occupancy of many rooms within the university.
# The Idea
The current occupancy tool is very inaccessible:
- it's only reachable via an IP-address
- its UI is very dense and hard to navigate
- if you are searching for any vacancies on a given time and day, you're out of luck -- you have to go through each room by hand and check its availability at that time
While a replacement is on its way, the student representation decided to leverage the current endpoint and build a tool that:
- is easy to navigate
- and provides the students with exactly the information they need:
<q>Which rooms are available throughout the day?</q>
# The Implementation
Freiraum mainly uses a timetable format with the rooms indicated by rows and the hours by columns. A vertical red indicator shows the current time over each row.
![](/images/posts/freiraum/homepage.png)
While lectures and other occupancies appear grayed-out, the computed free spots inbetween are rendered in a light blue shade, making current and future availabilities tangible at a single glance.
Each room and event can be clicked for a more detailed overview.
## Room Details
- the **kind of room** (auditorium, seminar, etc.),
- **its building**
- **its designation**
- **its capacity**
- other details like the availability of a projector, HDMI or VGA connection, blackboard, etc.
## Event Details
- the **kind of event** (lecture, seminar, etc.),
- **its title**,
- **the person that booked the slot**.
<br/>
The entire HTML of the timetable is generated via client-side javascript, which leads to pretty involved code, but gets the job done.
# Future Work / Other Ideas
An initial proof of concept also included a map view intended to give a visual queue of where the room is located. I later dismissed the idea for the time being because obtaining the shape and geo data for each room would have entaled a lot of manual work.
However, I learned that germany, and in fact the european union as well, provides some basic geo data for buildings, their area and roof shape, which is how I extracted this wonderful geojson shape data for each of the university buildings:
<UKNMap />
If anyone feels like it, some kind of navigation system might be fun to implement. Though I have heard that many have tried and failed miserably already.
Sticking with the timetable view, there is still room for filters and search. Possible queries could restrict the displayed rooms to only those of a certain size or kind (who actually wants to study in an auditorium when there is no lecture there?). Even the availability of a projector or blackboard could be filtered for or made directly visible through the use of icons (while trying not to overload the initial overview of the rooms).
# Final Notes
Apart from that, I am content with Freiraum in its current form. Part of its charme for me is its simplicity, though I am certain some people would disaggree. The code base is fairly easy to maintain, despite it being written in vanilla javascript (I seem to have a knack for that -- call me a masochist I guess). Often times I can quickly swoop in and change aspects of the UI or logic, without having to search all too long. Granted, I wrote the hecking thing. But the generation code for the actual HTML timetable is the only aspect that feels convoluted to me. All things considered, I feel certain that anyone can pick the code and adapt it to their will. In terms of performance and reliablity it seems to be robust enough that nobody has thrown tomatoes in my face -- yet.
The actual data endpoint for the room occupancy will move to the university's e-learning platform soon. As of now I have no clue wether the new version on the e-learning platform will provide a similarly accessible view of vacant rooms for students (I highly doubt it). Nor do I know if there is a similar API endpoint, that Freiraum can be attached to; Unless the IT department of the university provides one themselves, Freiraum will probably not be able to exist in its current way.
Whatever the future may hold, I am happy to have seen that it found use both by students and university employees. It turns out that the simplest of tools can make quite a difference :)

View File

@@ -32,6 +32,8 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05"
crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
article {
width: 100%;

View File

@@ -14,7 +14,7 @@ import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
<body>
<Header />
<Main>
<h1>Anton Pogrebnjak</h1>
<h1>Anton</h1>
<div style="max-width: 40rem">
<p>
Really, I don't have much to tell you about myself. Just look at the amazing stuff I've done and written about. That should suffice.

View File

@@ -23,8 +23,8 @@ const posts: Post[] = (await getCollection("projects"))
<Main>
<section id="welcome">
<h1>Hi &#128075;, my name is <a href="/about">Anton</a></h1>
<h2 class="typeout">
and I am passionate about a whole bunch of things
<h2 class="typeout">
Welcome to my realm!
</h2>
<h3 class="buttons">

View File

@@ -27,6 +27,7 @@
:root {
--text: #ffffff;
--text-soft: #ddd;
--text-softer: #aaa;
--background: #121212;
--background-soft: #212121;
--primary: #2df598;
@@ -182,6 +183,20 @@ blockquote {
font-size: 1.333em;
}
q {
display: block;
text-align: center;
font-weight: bold;
font-size: 1.7rem;
font-style: oblique;
margin: 2rem 0;
quotes: initial;
border: 2px solid var(--primary);
padding: 8px;
}
hr {
border: none;
border-top: 1px solid var(--primary);