Compare commits

...

9 Commits

Author SHA1 Message Date
Anton Pogrebnjak
7e4f1acbf1 Delete .github/workflows directory 2026-04-06 23:03:48 +02:00
Anton Pogrebnjak
fcb52ebdc9 Fixed gallery link 2026-04-06 23:00:56 +02:00
Anton Pogrebnjak
c0a7f193db Also added photo types 2026-04-06 22:56:43 +02:00
Anton Pogrebnjak
da9d256b71 Added phtography section 2026-04-06 22:56:12 +02:00
Anton Pogrebnjak
d03478f81a Fixed showcase and home page 2026-04-06 20:37:32 +02:00
Anton Pogrebnjak
120ce7726f Updated dependencies 2026-04-06 19:56:05 +02:00
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
31 changed files with 4902 additions and 2979 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
public/images/photos/*.jpg filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,39 +0,0 @@
name: Deploy to GitHub Pages
on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branchs name
push:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:
# Allow this job to clone the repo and create a page deployment
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout your repository using git
uses: actions/checkout@v4
- name: Install, build, and upload your site
uses: withastro/action@v3
# with:
# path: . # The root location of your Astro project inside the repository. (optional)
# node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional)
# package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

3988
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,10 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.3.5", "@astrojs/mdx": "^5.0.3",
"@astrojs/rss": "^4.0.12", "@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.5.1", "@astrojs/sitemap": "^3.7.2",
"@astrojs/vue": "^5.1.1", "astro": "^6.1.4",
"astro": "^5.13.7",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-math": "^6.0.0" "remark-math": "^6.0.0"
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View File

@@ -1,25 +1,11 @@
--- ---
const today = new Date(); const today = new Date();
import Socials from "./Socials.astro";
--- ---
<footer> <footer>
&copy; {today.getFullYear()} <a href="/about">Anton Pogrebnjak</a>. All rights reserved. &copy; {today.getFullYear()} <a href="/about">Anton</a>. All rights reserved.
<div class="social-links"> <Socials />
<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>
</footer> </footer>
<style> <style>
footer { footer {
@@ -28,12 +14,6 @@ const today = new Date();
width: 100%; width: 100%;
} }
.social-links {
display: flex;
justify-content: center;
gap: 1em;
margin-top: 1em;
}
.social-links a { .social-links a {
text-decoration: none; text-decoration: none;
color: var(--text); 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

@@ -0,0 +1,49 @@
---
import type { Photo } from '../models/Photo.ts';
const photos: Photo[] = Astro.props.photos;
const collection: string = Astro.props.collection;
const color = Astro.props.color || 'var(--text)';
---
<section>
{
photos.map((photo) => (
<a href={"/photography/" + photo.id}>
<img src={photo.src} />
</a>
))
}
</section>
<style define:vars={{ color }}>
section {
width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 2px;
}
a {
overflow: hidden;
min-height: 200px;
max-height: 12.5rem;
min-width: 200px;
max-width: 12.5rem;
flex: 1;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0;
transition: transform .2s;
}
img:hover {
transform: scale(1.2);
cursor: pointer;
}
</style>

View File

@@ -1,6 +1,7 @@
--- ---
import { ClientRouter } from "astro:transitions"; import { ClientRouter } from "astro:transitions";
import HeaderLink from "./HeaderLink.astro"; import HeaderLink from "./HeaderLink.astro";
import Socials from "./Socials.astro";
--- ---
<header> <header>
@@ -9,25 +10,11 @@ import HeaderLink from "./HeaderLink.astro";
<img src="/logo.svg" alt="ಠ_ಠ" /> <img src="/logo.svg" alt="ಠ_ಠ" />
</a> </a>
<div class="internal-links"> <div class="internal-links">
<HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/projects">Projects</HeaderLink> <HeaderLink href="/projects">Projects</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink> <HeaderLink href="/photography">Photography</HeaderLink>
</div> <HeaderLink href="/blog">Blog</HeaderLink>
<div class="social-links"> </div>
<a href="https://github.com/Pantonius" target="_blank"> <Socials />
<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>
</nav> </nav>
</header> </header>
@@ -72,10 +59,6 @@ import HeaderLink from "./HeaderLink.astro";
border-bottom-color: var(--primary); border-bottom-color: var(--primary);
} }
.social-links,
.social-links a {
display: flex;
}
@media (max-width: 720px) { @media (max-width: 720px) {
header .logo img { header .logo img {

View File

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

View File

@@ -78,6 +78,8 @@ const color = Astro.props.color || 'var(--text)';
margin: 0; margin: 0;
color: var(--color); color: var(--color);
line-height: 1; line-height: 1;
font-size: 1em;
font-variant-caps: small-caps;
} }
.date { .date {
margin: 0; margin: 0;
@@ -101,8 +103,8 @@ const color = Astro.props.color || 'var(--text)';
ul li:first-child { ul li:first-child {
margin-bottom: 0; margin-bottom: 0;
} }
ul li:first-child .title { ul li:first-child .title, ul li .title {
font-size: 1.563em; font-size: 1.2em;
} }
} }
</style> </style>

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 { glob, file } from 'astro/loaders';
import { defineCollection, z } from 'astro:content'; 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({ const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory. // Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
@@ -13,10 +28,26 @@ const blog = defineCollection({
updatedDate: z.coerce.date().optional(), updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(), heroImage: z.string().optional(),
}), }),
}
);
const photography = defineCollection({
// Load Markdown and MDX files in the `src/content/projects/` directory.
loader: file(
'./src/content/photography/index.json'
),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
src: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
}),
}); });
const projects = 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({ loader: glob({
base: './src/content/projects', pattern: '**/*.{md,mdx}' base: './src/content/projects', pattern: '**/*.{md,mdx}'
}), }),
@@ -31,4 +62,4 @@ const projects = defineCollection({
}), }),
}); });
export const collections = { blog, projects }; export const collections = { bachelor, blog, photography, projects };

View File

@@ -0,0 +1,44 @@
[
{
"id": "DSC05932",
"src": "DSC05932.jpg",
"title": "DSC05932",
"pubDate": "2026-04-06",
"description": ""
},
{
"id": "DSC06124",
"src": "DSC06124.jpg",
"title": "DSC06124",
"pubDate": "2026-04-06",
"description": ""
},
{
"id": "DSC06196",
"src": "DSC06196.jpg",
"title": "DSC06196",
"pubDate": "2026-04-06",
"description": ""
},
{
"id": "UKN_Topo_01",
"src": "UKN_Topo_01.jpg",
"title": "UKN_Topo_01",
"pubDate": "2026-04-06",
"description": ""
},
{
"id": "UKN_Topo_02",
"src": "UKN_Topo_02.jpg",
"title": "UKN_Topo_02",
"pubDate": "2026-04-06",
"description": ""
},
{
"id": "UKN_Topo_03",
"src": "UKN_Topo_03.jpg",
"title": "UKN_Topo_03",
"pubDate": "2026-04-06",
"description": ""
}
]

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" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05"
crossorigin="anonymous" crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script> 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> <style>
article { article {
width: 100%; width: 100%;

View File

@@ -0,0 +1,103 @@
---
import type { CollectionEntry } from "astro:content";
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Main from "../components/Main.astro";
import Footer from "../components/Footer.astro";
import FormattedDate from "../components/FormattedDate.astro";
type Props = CollectionEntry<"photography">["data"];
const { title, description, pubDate, src } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} image={src} />
<style>
article {
width: 100%;
}
h1::after {
content: "";
display: block;
/* width: 100%; */
height: 2px;
background-color: var(--primary);
margin: 0.5rem auto;
}
h2::after {
content: "";
display: block;
/* width: 100%; */
height: 2px;
background-color: var(--primary);
margin: 0.5rem auto;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
}
.prose {
width: 720px;
max-width: 100%;
margin: auto;
padding: 1em;
color: var(--text);
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: var(--text);
}
</style>
</head>
<body>
<Header />
<Main>
<article>
<div class="hero-image">
<img
width={1020}
height={510}
src={src}
alt=""
/>
</div>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
</div>
<h1>{title}</h1>
</div>
<p>
{description}
</p>
</div>
</article>
</Main>
<Footer />
</body>
</html>

32
src/models/Photo.ts Normal file
View File

@@ -0,0 +1,32 @@
export type CollectionPhoto = {
id: string;
collection: "photography";
data: {
title: string,
src: string,
pubDate: Date,
description: string
};
filePath?: string;
};
export type Photo = {
id: string,
title: string;
src: string;
pubDate: Date;
description: string;
collection: "photography";
};
export const convertPhoto = (collectionPhoto: CollectionPhoto): Photo => {
return {
id: collectionPhoto.id,
title: collectionPhoto.data.title,
src: "/images/photography/" + collectionPhoto.data.src,
pubDate: collectionPhoto.data.pubDate,
description: collectionPhoto.data.description,
collection: collectionPhoto.collection,
};
}

View File

@@ -14,7 +14,7 @@ import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
<body> <body>
<Header /> <Header />
<Main> <Main>
<h1>Anton Pogrebnjak</h1> <h1>Anton</h1>
<div style="max-width: 40rem"> <div style="max-width: 40rem">
<p> <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. 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,15 +23,16 @@ const posts: Post[] = (await getCollection("projects"))
<Main> <Main>
<section id="welcome"> <section id="welcome">
<h1>Hi &#128075;, my name is <a href="/about">Anton</a></h1> <h1>Hi &#128075;, my name is <a href="/about">Anton</a></h1>
<h2 class="typeout"> <h2 class="typeout">
and I am passionate about a whole bunch of things Welcome to my realm!
</h2> </h2>
<h3 class="buttons"> <h4 class="buttons">
<a href="/blog">Blog</a>
<a href="/projects">Projects</a> <a href="/projects">Projects</a>
<a href="/photography">Photography</a>
<a href="/blog">Blog</a>
<a href="/about">About</a> <a href="/about">About</a>
</h3> </h4>
</section> </section>
<section id="projects"> <section id="projects">
@@ -108,14 +109,6 @@ const posts: Post[] = (await getCollection("projects"))
section { section {
padding: 1rem; padding: 1rem;
} }
#welcome h1 {
font-size: 1.5rem;
}
#welcome h2 {
font-size: 1rem;
}
} }
</style> </style>
</html> </html>

View File

@@ -0,0 +1,18 @@
---
import { getCollection } from 'astro:content';
import { render } from 'astro:content';
import GalleryPhoto from '../../layouts/GalleryPhoto.astro';
import { convertCollectionPhoto, convertPhoto, type Photo } from '../../models/Photo';
export async function getStaticPaths() {
const photos: Photo[] = (await getCollection('photography')).map(convertPhoto);
return photos.map((photo) => ({
params: { slug: photo.id },
props: photo,
}));
}
type Props = Photo;
const photo: Photo = Astro.props;
---
<GalleryPhoto {...photo} />

View File

@@ -0,0 +1,29 @@
---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Main from '../../components/Main.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import Gallery from '../../components/Gallery.astro';
import { convertPhoto } from '../../models/Photo';
const photos: Photo[] = (await getCollection('photography')).map(convertPhoto).sort((a, b) => b.pubDate.valueOf() - a.pubDate.valueOf());
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<Main>
{photos.length === 0 ? (
<h1 style="opacity: .5; user-select: none;">No photos yet &#128539;</h1>
) : ( <Gallery photos={photos} /> )}
</Main>
<Footer />
</body>
</html>

View File

@@ -27,6 +27,7 @@
:root { :root {
--text: #ffffff; --text: #ffffff;
--text-soft: #ddd; --text-soft: #ddd;
--text-softer: #aaa;
--background: #121212; --background: #121212;
--background-soft: #212121; --background-soft: #212121;
--primary: #2df598; --primary: #2df598;
@@ -182,6 +183,20 @@ blockquote {
font-size: 1.333em; 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 { hr {
border: none; border: none;
border-top: 1px solid var(--primary); border-top: 1px solid var(--primary);