Compare commits

46 Commits

Author SHA1 Message Date
7b0acd007b Use vmax instead of vh
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 14s
Build Zola Website / build (pull_request) Successful in 20s
2024-12-18 13:03:39 +01:00
000bb3ea47 Fix visible content in navbar
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 15s
Build Zola Website / build (pull_request) Successful in 21s
2024-12-15 08:32:34 +01:00
297f96ab99 Added postbuild script to the Gitea Action
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
Build Zola Website / build (pull_request) Successful in 20s
2024-12-15 08:18:35 +01:00
3112316e7d Added execute permissions to the postbuild script 2024-12-15 08:18:10 +01:00
1ca991e4b9 Added: postbuild script 2024-12-15 08:14:54 +01:00
c827757fb8 Added: burger menu for mobile views
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 14s
Build Zola Website / build (pull_request) Successful in 21s
2024-12-15 08:09:10 +01:00
01c994aa13 Added auto-awning-close and walauncher projects
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
Build Zola Website / build (pull_request) Successful in 20s
2024-12-15 03:57:28 +01:00
1de4bf249b Added: hr under the h1 in posts and projects template
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 14s
Build Zola Website / build (pull_request) Successful in 20s
2024-12-15 03:52:41 +01:00
28c29f0d30 Fix code overflow
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
Build Zola Website / build (pull_request) Successful in 20s
2024-12-14 23:09:30 +01:00
8a832250a0 New post: Creating a Language-Specific Jellyfin Library
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 14s
Build Zola Website / build (pull_request) Successful in 20s
2024-12-14 19:52:23 +01:00
225f2af48b Tracking added
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
Build Zola Website / build (pull_request) Successful in 28s
2024-11-16 20:27:28 +01:00
2144038970 Rewrite title and description for jellyfin post
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 17s
Build Zola Website / build (pull_request) Successful in 24s
2024-10-17 14:03:35 +02:00
512a810122 New post: how i fixed cs2 4:3 resolution 2024-10-17 13:59:25 +02:00
2e5696f767 Added: copy text button
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 28s
Build Zola Website / build (pull_request) Successful in 35s
2024-10-15 11:11:08 +02:00
1751b590aa Text align left
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 16s
Build Zola Website / build (pull_request) Successful in 21s
2024-10-14 19:24:42 +02:00
a95f13943f CSS formatted
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 16s
Build Zola Website / build (pull_request) Successful in 23s
2024-10-14 17:15:08 +02:00
7f23f234cd Changed ancher color 2024-10-14 17:13:49 +02:00
b227a41a43 Zola workflow updated
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 30s
Build Zola Website / build (pull_request) Successful in 27s
2024-10-14 17:01:20 +02:00
bf5f265116 Design improvments 2024-10-14 16:52:45 +02:00
1cf12f91f0 Added border radius to code, meta description 2024-10-14 16:10:17 +02:00
00eec8be70 CSS redesign stuff, other things idk 2024-10-14 01:15:41 +02:00
542e432596 Jellyfin post 2024-10-12 22:45:43 +02:00
0791f417f3 Core for the posts section 2024-10-03 02:45:58 +02:00
2439dfa78d Added: biome for formatting, Makefile
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 36s
Build Zola Website / build (pull_request) Successful in 17s
2024-06-03 00:23:19 +02:00
e0248abb46 Revert "Changes for master"
This reverts commit b022a4ae37.
2024-06-03 00:10:28 +02:00
cc4a827331 Fix: source link - target blank
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 27s
2024-05-22 19:21:40 +02:00
e8ce3f9b75 Fix padding
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 17s
2024-03-25 17:17:18 +01:00
b022a4ae37 Changes for master
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 16s
2024-03-24 11:57:34 +01:00
736f17c442 Email address encoded
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 20s
Build Zola Website / build (pull_request) Successful in 15s
2024-03-23 17:11:13 +01:00
90ee057637 Disable minify html
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 20s
Build Zola Website / build (pull_request) Successful in 16s
2024-02-23 12:20:47 +01:00
77bc97087b Fix: url changed to dev from prod, readme updated
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 17s
Build Zola Website / build (pull_request) Successful in 15s
2024-02-23 03:14:06 +01:00
9c4bfc84ff Texts edited, projects redesigned, posts temporarily removed, pkmples project added and more
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 19s
Build Zola Website / build (pull_request) Successful in 15s
2024-02-23 03:06:08 +01:00
2e25a00637 Fixes: #2 changed gpg key
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 18s
Build Zola Website / build (pull_request) Successful in 15s
2024-02-08 09:36:14 +01:00
63d4122322 Added: posts page
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 17s
Build Zola Website / build (pull_request) Successful in 16s
2024-02-08 09:13:08 +01:00
5090214128 Fixes: #5 body padding 2024-02-08 09:12:18 +01:00
1e9475e806 Fixed: console on mobile
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 28s
2023-12-18 19:03:05 +01:00
47f26c96f6 console (#1)
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 18s
Reviewed-on: #1
2023-12-18 03:33:05 +01:00
f137aceb6e Removed old website
Some checks failed
Build and Deploy Zola Website / build_and_deploy (push) Failing after 17s
2023-12-07 23:01:29 +01:00
d3c49bcc78 Added Gitea Actions workflow files for CI/CD 2023-12-07 21:22:20 +01:00
dc68ded731 README.md updated 2023-12-07 21:15:50 +01:00
9d0a4d77b3 fixed some shits 2023-11-19 00:56:48 +01:00
7c0a463917 some updates almost ready for v1 2023-09-20 17:03:40 +02:00
35d081abc4 update 2023-08-29 10:08:14 +02:00
93895a5967 updated contacts and home page + cleaning 2023-06-21 16:26:18 +02:00
03d46cb73a bigger update 2023-06-17 16:04:09 +02:00
5c1910292e cleaning 2023-06-02 19:06:31 +02:00
63 changed files with 1657 additions and 588 deletions

View File

@ -0,0 +1,21 @@
name: Build Zola Website
on: pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set up Zola
uses: taiki-e/install-action@v2
with:
tool: zola@0.17.2
- name: Build Zola Website
run: |
zola build

View File

@ -0,0 +1,51 @@
name: Build and Deploy Zola Website
on:
push:
branches:
- master
- dev
env:
ZOLA_VERSION: "0.19.2"
HOST: ${{ secrets.SERVER_IP }}
SSH_USERNAME: ${{ secrets.USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Zola
run: |
wget https://github.com/getzola/zola/releases/download/v${ZOLA_VERSION}/zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz
tar -xvzf *.tar.gz
- name: Build Zola Website
run: |
./zola build
- name: Run postbuild script
run: bash ./postbuild.sh
- name: Set Destination Folder if MASTER
if: ${{ github.ref == 'refs/heads/master' }}
run: echo "DEST_FOLDER=/srv/www/cz/filiprojek/www" >> $GITHUB_ENV
- name: Set Destination Folder if DEV
if: ${{ github.ref == 'refs/heads/dev' }}
run: echo "DEST_FOLDER=/srv/www/cz/filiprojek/dev" >> $GITHUB_ENV
- name: Deploy
run: |
apt update -y && apt-get install -y --no-install-recommends rsync
eval "$(ssh-agent -s)"
ssh-add - <<< "${SSH_PRIVATE_KEY}"
mkdir -p ~/.ssh/
ssh-keyscan -H ${HOST} >> ~/.ssh/known_hosts
rsync -r --delete-after public/* "${SSH_USERNAME}@${HOST}:${{ env.DEST_FOLDER }}"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
public/

View File

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
all: clean format build
format:
biome format --write .
build:
zola build
clean:
rm -rf public/

View File

@ -1,17 +1,28 @@
# Website
- My personal website written in php, html, css and js
- My personal website https://www.filiprojek.cz
- Written in [Zola](https://getzola.org)
## To run development server
- `php -S localhost:8000`
- Then it can be access at http://localhost:8000/
## To run using Docker
- ToDo
- `zola serve`
- Then it can be access at http://127.0.0.1:1111/
## To Do
- [ ] Docker
- [ ] kopirovani PGP na click opravit
- [ ] projekty page css
- [ ] contact page odesilani mailu
- [ ] contact page pridat odkazy na dalsi soc site (github atd)
- [ ] Docker?
- [ ] redesign contact
- [ ] redesign about
- [ ] check about text content
- [ ] responsibility
# Projects
- nork
- website
- debrepo
- auto irrigation
- auto awning close
- FofrTasks
- FofrMess
- pkmples.cz
- deguapp?
- wpa_tui?
- dotfiles

15
biome.json Normal file
View File

@ -0,0 +1,15 @@
{
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
"files": {
"ignore": [".vscode/", "node_modules/", "public/"]
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": false,
"rules": {
"recommended": true
}
}
}

View File

@ -1,2 +0,0 @@
<p>(c) filiprojek.cz 2023<p>

View File

@ -1,11 +0,0 @@
<section class="nav">
<a class="logo" href="/">
<img src="img/fr_logo.webp" alt="logo" />
</a>
<div class="links">
<a href="/home">Úvod</a>
<a href="/projects">Projekty</a>
<a href="/contact">Kontakt</a>
</div>
</section>

24
config.toml Normal file
View File

@ -0,0 +1,24 @@
# The URL the site will be built for
base_url = "https://dev.filiprojek.cz"
title = "Filip Rojek"
compile_sass = true
minify_html = false
build_search_index = false
[markdown]
highlight_code = true
external_links_target_blank = true
external_links_no_follow = true
external_links_no_referrer = true
smart_punctuation = true
[extra]
git = "https://git.filiprojek.cz/fr/website"
nav_items = [
{name="Home", path="/"},
{name="About", path="/about"},
{name="Projects", path="/projects/"},
{name="Posts", path="/posts"}
]

27
content/about.md Normal file
View File

@ -0,0 +1,27 @@
+++
title = "About"
template = "about.html"
+++
## About
I am student of IT at Charles University in Prague (Faculty of Education).
My programming journey began in high school, where I developed a passion for `Linux` and co-founded [Fofrweb](https://fofrweb.com). Together with a classmate, I created web applications using `Node.js` and `Vue.js`, all hosted on my own Linux server.
As a member of [Microlab](https://microlab.space), the university's hacker space, I engage with fellow tech enthusiasts. Additionally, I contribute to the open-source community as a package maintainer for [Void Linux](https://voidlinux.org).
## Work
I currently work as a Linux engineer. Most of my work involves Debian based systems - desktops (Raspberry Pi, Intel NUC) and servers.
I also create custom websites from time to time.
## Projects
Most of my projects are hosted on Gitea and GitHub:
- Gitea: [git.filiprojek.cz](https://git.filiprojek.cz/fr)
- GitHub: [github.com/filiprojek](https://github.com/filiprojek)
## Contact
- <a href="mailto:&#102;&#105;&#108;&#105;&#112;&#064;&#102;&#105;&#108;&#105;&#112;&#114;&#111;&#106;&#101;&#107;&#046;&#099;&#122;">&#102;&#105;&#108;&#105;&#112;&#064;&#102;&#105;&#108;&#105;&#112;&#114;&#111;&#106;&#101;&#107;&#046;&#099;&#122;</a>
- [@filiprojek](https://t.me/filiprojek) on Telegram
- PGP: [0x7E65EA58C6075F09](https://keys.openpgp.org/vks/v1/by-fingerprint/CA3D9BE28315B49164130CD97E65EA58C6075F09)

7
content/posts/_index.md Normal file
View File

@ -0,0 +1,7 @@
+++
title = "Posts"
template = "post_list.html"
page_template = "post.html"
sort_by = "date"
+++

View File

@ -0,0 +1,66 @@
+++
title = "Fixing 4:3 Resolution in CS2 on Linux with NVIDIA GPU"
date = 2024-10-17
description = "How I fixed the 4:3 resolution in CS2 with an NVIDIA graphics card and Linux"
+++
I don't consider myself a gamer, but I've been playing the Counter-Strike series since CS 1.6. Every now and then, I enjoy staying up all night playing this broken game.
Ever since I started playing Counter-Strike, Ive preferred using a 4:3 stretched resolution on my 16:9 monitor. When I switched to Linux as my daily driver, the only game I really cared about was CS:GO.
CS:GO ran perfectly without any tweaks on my [Void Linux](https://voidlinux.org) system with a 1050ti laptop graphics card. I could play with the stretched resolution, and I even got more FPS than I did on Windows.
My problems started with the release of CS2. The 4:3 resolution didnt work at all, some resolutions were missing, and there didnt seem to be a solution (except for some Wayland fixes). Theres a [GitHub issue](https://github.com/ValveSoftware/csgo-osx-linux/issues/3264) about it.
My first idea was to set a custom resolution through `xrandr`. I followed [guides like this one](https://unix.stackexchange.com/questions/227876/how-to-set-custom-resolution-using-xrandr-when-the-resolution-is-not-available-i), but that didnt work for me.
For a while, I just stuck with the standard 16:9 `1920x1080` resolution. But today, I opened the `nvidia-settings` GUI. After tinkering with some advanced settings in the resolution section, I think I finally fixed my issue.
In the `X Server Display Configuration` section under advanced options, I adjusted the ViewPortIn and Panning settings. Im currently using a `2560x1440` resolution, so for 4:3 stretched, I set the resolution to `1440x1080`.
Here are the settings that worked for me:
- **ViewPortIn**: `1440x1080`
- **ViewPortOut**: `2560x1440+0+0`
- **Panning**: `1440x1080`
That fixed the issue, but I didnt want to manually open `nvidia-settings` every time I wanted to play CS2. After reading the [Arch Wiki article](https://wiki.archlinux.org/title/NVIDIA#Using_nvidia-settings) on `nvidia-settings`, I found that I could use this command to get the current resolution information:
```sh
$ nvidia-settings -q CurrentMetaMode
```
So ~I wrote~ ChatGPT wrote a small bash script to switch between my regular resolution and the CS2 resolution.
```bash
#!/bin/bash
# Define the commands for the two modes
MODE1="nvidia-settings --assign 'CurrentMetaMode=DPY-4: 2560x1440_144 @2560x1440 +1920+0 {ViewPortIn=2560x1440, ViewPortOut=2560x1440+0+0}, DPY-3: nvidia-auto-select @1920x1080 +0+180 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}'"
MODE2="nvidia-settings --assign 'CurrentMetaMode=DPY-4: 2560x1440_144 @1440x1080 +0+0 {ViewPortIn=1440x1080, ViewPortOut=2560x1440+0+0}'"
# File to store the current mode
STATE_FILE="/tmp/current_resolution_mode"
# Check if the state file exists
if [[ ! -f "$STATE_FILE" ]]; then
# If the state file doesn't exist, create it and set it to mode 1
echo "1" > "$STATE_FILE"
CURRENT_MODE=1
else
# Read the current mode from the state file
CURRENT_MODE=$(cat "$STATE_FILE")
fi
# Switch between the two modes
if [[ "$CURRENT_MODE" -eq 1 ]]; then
# Switch to mode 2
eval "$MODE2"
echo "2" > "$STATE_FILE"
echo "Switched to resolution mode 2"
else
# Switch to mode 1
eval "$MODE1"
echo "1" > "$STATE_FILE"
echo "Switched to resolution mode 1"
fi
```

View File

@ -0,0 +1,196 @@
+++
title = "Creating a Language-Specific Jellyfin Library"
date = 2024-12-14
description = "How to set up a Jellyfin library for language-specific content"
+++
Managing a multilingual media library can be a challenge, especially if you want to share specific language content with others. In my case, I have a large collection of movies and TV shows in both Czech and English. To help my parents enjoy only Czech-language content, I decided to create a dedicated Jellyfin library for it. Since Jellyfin doesn't natively support filtering libraries by language, I built a custom solution using Bash scripting and Docker.
## The Problem
My media library has the following folder structure for movies:
```
movies/
├── Movie name (Year of release)
│ ├── Movie name resolution.mkv
│ └── Movie name resolution.nfo
```
And for series:
```
series/
├── Series name
│ └── Season 1
│ ├── Series name - S01E01 - Pilot WEBRip-1080p.mkv
│ ├── Series name - S01E01 - Pilot WEBRip-1080p.cs.srt
│ └── Series name - S01E01 - Pilot WEBRip-1080p.en.srt
```
For movies, each film is stored in its own folder. For series, episodes are nested within season folders, under the parent series folder. Jellyfin requires each movie or series to reside in its own directory, which means symlinks must point to the parent folder rather than the media files themselves.
Additionally, I needed to differentiate between movies and series when creating symlinks. For movies, the parent directory is the movie folder, but for series, the symlink should point to the series' root folder (not the season folder). Here's how I solved it.
## Custom Script for Language Filtering
The following script processes the media directories to identify content with Czech audio tracks and creates symlinks to organize them into separate `cz_movies` and `cz_series` directories.
```bash
#!/bin/bash
# Define directories and language codes
SOURCE_DIR_MOVIES="/media/movies"
TARGET_DIR_MOVIES="/media/cz_movies"
SOURCE_DIR_SERIES="/media/series"
TARGET_DIR_SERIES="/media/cz_series"
LANGUAGES_TO_CHECK=("cze" "cz" "Czech" "czech" "česky" "cs" "ces")
FFPROBE="/usr/lib/jellyfin-ffmpeg/ffprobe"
# List of file extensions to skip
SKIP_EXTENSIONS=("nfo" "srt" "sh")
# Function to process a source directory
process_directory() {
local SOURCE_DIR="$1"
local TARGET_DIR="$2"
local IS_SERIES="$3"
# Loop through all files in SOURCE_DIR and subdirectories
find "$SOURCE_DIR" -type f | while read FILE; do
# Get the file extension
EXTENSION="${FILE##*.}"
# Check if the file extension is in the skip list
if [[ " ${SKIP_EXTENSIONS[@]} " =~ " $EXTENSION " ]]; then
# Skip the file if it's in the skip list
continue
fi
echo "Processing $FILE"
# Get the languages from the current file
LANGUAGES=($($FFPROBE -v error -show_entries stream=codec_type:stream_tags=language:stream=codec_name -of default=noprint_wrappers=1:nokey=1 "$FILE" | tr '\n' ' ' | grep -o 'audio [^ ]*' | awk '{print $2}'))
# Check if any of the languages in LANGUAGES_TO_CHECK exist in LANGUAGES
FOUND=false
for LANG_TO_CHECK in "${LANGUAGES_TO_CHECK[@]}"; do
if [[ " ${LANGUAGES[@]} " =~ " $LANG_TO_CHECK " ]]; then
FOUND=true
break
fi
done
if $FOUND; then
echo "Found a matching language (${LANGUAGES_TO_CHECK[@]}) in the list"
# Determine the symlink target based on whether this is a series or a movie
if [ "$IS_SERIES" = true ]; then
# For series, link the <Series name> directory
PARENT_DIR=$(dirname "$(dirname "$FILE")")
else
# For movies, link the <Movie name> directory
PARENT_DIR=$(dirname "$FILE")
fi
# Create a symlink to the parent directory in TARGET_DIR
ln -snf "$PARENT_DIR" "$TARGET_DIR/$(basename "$PARENT_DIR")"
echo "Symlink \"$PARENT_DIR\" \"$TARGET_DIR/$(basename "$PARENT_DIR")\""
echo "Symlink created for $PARENT_DIR"
else
echo "Skipping $FILE"
fi
done
}
# Process movies and series directories
process_directory "$SOURCE_DIR_MOVIES" "$TARGET_DIR_MOVIES" false
process_directory "$SOURCE_DIR_SERIES" "$TARGET_DIR_SERIES" true
```
## Automating the Script with Docker and Cron
To automate the script, I run it hourly using a minimal Docker container with cron installed. Instead of modifying the Jellyfin Docker image, I created a separate Alpine-based container to handle the job.
### Dockerfile for the Cron Container
```Dockerfile
FROM alpine:latest
RUN apk update && \
apk add --no-cache docker && \
rm -rf /var/cache/apk/*
CMD ["sh"]
```
### Docker-Compose Setup
Here's the updated `docker-compose.yml` that includes the Jellyfin service and the cron container:
```yaml
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
# ... your jellyfin docker compose ...
volumes:
- ./czech-lib-cron.sh:/czech-lib-cron.sh:ro # the custom library script
- /mnt/media/torrent:/media:ro # your media folder
- /mnt/media/torrent/cz_movies:/media/cz_movies # the newely created language specific library folder
- /mnt/media/torrent/cz_series:/media/cz_series # the newely created language specific library folder
jellyfin-cron:
build:
context: ../docker-cron
dockerfile: Dockerfile
container_name: jellyfin-cron
# Run the custom library script every hour at 45 minutes past
command: >
/bin/sh -c "
echo '45 * * * * docker exec -u root jellyfin bash -c \"/czech-lib-cron.sh\"' > /etc/crontabs/root &&
crond -f -l 2"
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Needed for `docker exec`
networks:
- default
```
## Bonus: Fix Missing Audio Language Metadata
Some files in my library lacked proper audio language metadata, causing them to be missed by the script. To fix this, I wrote another script to add the correct metadata:
```bash
#!/bin/bash
# Root directory (current working directory)
ROOT_DIR="$(pwd)"
# Function to process each video file
process_file() {
local input_file="$1"
local temp_file="${input_file%.avi}_temp.avi"
echo "Processing: $input_file"
# Add metadata to the audio track and overwrite the original file
ffmpeg -i "$input_file" -map 0 -c copy -metadata:s:a:0 language=cze "$temp_file"
if [[ $? -eq 0 ]]; then
mv "$temp_file" "$input_file"
echo "Successfully updated: $input_file"
else
echo "Error updating: $input_file"
rm -f "$temp_file" # Remove temporary file on failure
fi
}
# Export function for use in find's -exec
export -f process_file
# Find all .avi files and process them
find "$ROOT_DIR" -type f -name "*.avi" -exec bash -c 'process_file "$0"' {} \;
```
This script re-encodes files to include the missing language metadata. It defaults to `.avi` files, but you can adjust it as needed.
## Conclusion
With this setup, you can create language-specific libraries in Jellyfin. Make sure to add the /media/cz_movies and /media/cz_series folders as paths for new libraries in the Jellyfin dashboard settings. After adding these libraries, simply scan them to see your filtered content. Happy watching!

192
content/posts/jellyfin.md Normal file
View File

@ -0,0 +1,192 @@
+++
title = "Host Jellyfin with Docker and Docker Compose"
date = 2024-10-03
description = "Set up a Jellyfin media server with Docker, including hardware transcoding, media management, and companion services like Jellyseerr and Jellystat."
+++
Running your own media server is a great way to have complete control over your media library, and Jellyfin is one of the best open-source media server solutions available today. With Docker and Docker Compose, you can efficiently manage and scale your Jellyfin instance, along with useful companion services like Jellyseerr and Jellystat for enhanced functionality.
In this article, Ill walk through my setup of a Jellyfin media server, hosted in Docker using Docker Compose. This assumes you already have Docker and Docker Compose installed and are comfortable with Linux environments. Ill cover the configuration of Jellyfin, hardware transcoding, and the integration of supporting services like Jellyseerr and Jellystat to manage requests and track server statistics.
## Docker Compose Configuration Breakdown
Heres the Docker Compose file I use to deploy Jellyfin and its associated services.
```yaml
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: 1000:1000
restart: 'unless-stopped'
devices:
- /dev/dri:/dev/dri # for hardware transcoding
- /dev/dri/renderD128:/dev/dri/renderD128
#- /dev/kfd:/dev/kfd # Remove this device if you don't use the OpenCL tone-mapping
group_add:
- "103" # render gid
- "27" # video gid
networks:
- default
ports:
- 5100:8096
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media/torrent:/media:ro
- /dev/shm:/data/transcode # Offload transcoding to RAM if you have enough RAM
environment:
- JELLYFIN_PublishedServerUrl=https://your.host.com
jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: jellyseerr
environment:
- LOG_LEVEL=debug
- TZ=Europe/Prague
ports:
- 5280:5055
volumes:
- ./jellyseerr-config:/app/config
restart: unless-stopped
jellystat-db:
image: postgres:15.2
container_name: jellystat-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: <password>
volumes:
- ./jellystat-db:/var/lib/postgresql/data
jellystat:
image: cyfershepard/jellystat:latest
container_name: jellystat
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: <password>
POSTGRES_IP: jellystat-db
POSTGRES_PORT: 5432
JWT_SECRET: <jwt_secret>
TZ: Europe/Prague
volumes:
- ./jellystat-data:/app/backend/backup-data
ports:
- "5110:3000"
depends_on:
- jellystat-db
```
## Jellyfin Service
The core of this setup is the jellyfin service. This container runs the Jellyfin server itself, and the configuration is designed to optimize performance and security.
### Hardware Transcoding
For efficient media transcoding, Ive configured Jellyfin to leverage the hardware capabilities of the host machine. Specifically, Ive mounted the /dev/dri device to the container for Intel or AMD GPU transcoding:
```yaml
devices:
- /dev/dri:/dev/dri
- /dev/dri/renderD128:/dev/dri/renderD128
```
If you dont need OpenCL tone-mapping, you can skip the /dev/kfd device. By adding the necessary GIDs (103 for render and 27 for video), the container has access to GPU resources.
Additionally, Ive offloaded transcoding operations to the systems RAM by mounting /dev/shm as the transcode directory:
```yaml
volumes:
- /dev/shm:/data/transcode
```
This is particularly useful if you have sufficient RAM, as it improves transcoding performance by avoiding disk I/O overhead.
### Storage
In the `volumes` section, Ive mounted the following directories:
- `./config:/config`: Stores Jellyfins configuration data.
- `./cache:/cache`: Holds cache data to improve performance.
- `/mnt/media/torrent:/media:ro`: This is the read-only mount where Jellyfin accesses my media library. Its pointed to my torrent directory, ensuring that Jellyfin can index and serve files from there but not modify them.
### Network and Ports
The Jellyfin server is exposed on port `5100` (mapped to Jellyfins default 8096 internal port). I also set the JELLYFIN_PublishedServerUrl environment variable to make sure the correct public URL is used for generating media links and external access.
## Jellyseerr for Media Requests
Jellyseerr is a companion service to Jellyfin, allowing users to request new content directly from the web interface. Heres how Ive integrated it:
```yaml
jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: jellyseerr
environment:
- LOG_LEVEL=debug
- TZ=Europe/Prague
ports:
- 5280:5055
volumes:
- ./jellyseerr-config:/app/config
restart: unless-stopped
```
Ive exposed Jellyseerr on port `5280` and linked it to the default timezone (`Europe/Prague` in my case). Its important to store the Jellyseerr configuration separately in the `./jellyseerr-config` directory to persist user settings and requests.
## Jellystat for Tracking Server Stats
Jellystat adds another layer of utility, giving you the ability to track detailed statistics about your Jellyfin server usage, including playback metrics, user activity, and media insights.
It relies on a PostgreSQL database (`jellystat-db` service) to store all the data. Ive configured it like this:
```yaml
jellystat-db:
image: postgres:15.2
container_name: jellystat-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: <password>
volumes:
- ./jellystat-db:/var/lib/postgresql/data
```
Jellystat then communicates with the PostgreSQL instance:
```yaml
jellystat:
image: cyfershepard/jellystat:latest
container_name: jellystat
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: <password>
POSTGRES_IP: jellystat-db
POSTGRES_PORT: 5432
JWT_SECRET: <jwt_secret>
TZ: Europe/Prague
volumes:
- ./jellystat-data:/app/backend/backup-data
ports:
- "5110:3000"
depends_on:
- jellystat-db
```
In this configuration, Jellystat is exposed on port `5110`, and Ive configured the necessary environment variables to link it to the `jellystat-db` container. The JWT secret is used to authenticate requests between Jellystat and Jellyfin.
# Optimizing and Securing the Setup
## 1. Automatic Restart
Each service is configured with `restart: unless-stopped`, ensuring that the containers are restarted automatically if they crash or the host reboots. This adds reliability to the deployment, minimizing downtime.
## 2. Using RAM for Transcoding
Offloading transcoding operations to RAM using `/dev/shm` significantly boosts the performance of Jellyfin, especially when dealing with multiple simultaneous streams or high-bitrate media.
## 3. Resource Isolation and Security
By specifying the `user: 1000:1000` directive in the Jellyfin container, Ive ensured that the Jellyfin service runs as a non-root user on the host. This enhances security by limiting the container's privileges. Also, mounting `/media` in read-only (`ro`) mode ensures that the Jellyfin service cannot modify the media files directly, reducing the risk of accidental data corruption.
# Conclusion
This Docker Compose setup offers a robust Jellyfin media server that takes full advantage of hardware transcoding, secure media access, and useful companion services like Jellyseerr for content requests and Jellystat for usage tracking. Docker makes it easy to maintain, update, and scale this setup with minimal hassle, ensuring you have full control over your media experience.
With this configuration, you can efficiently manage and monitor your media server, adding a layer of automation and insight to your Jellyfin instance.

View File

@ -0,0 +1,9 @@
+++
title = "Nextcloud"
date = 2024-10-03
description = "My Nextcloud docker based instance"
+++
This is an post about my docker based Nextcloud instance

View File

@ -0,0 +1,7 @@
+++
title = "Projects"
template = "project_list.html"
page_template = "project.html"
sort_by = "date"
+++

View File

@ -0,0 +1,20 @@
+++
title = "Auto Awning Close"
date = 2021-04-12
description = "Automated awning control system using Arduino Nano and relays. Includes weather and time-based conditions for seamless operation."
[extra]
state = "done"
+++
The **Auto-Awning-Close** is a smart automation project that integrates seamlessly with an existing awning system. Leveraging an Arduino Nano and relays, it allows for effortless, automated control of your awning based on weather conditions, time schedules, or manual inputs via a dedicated remote.
This project showcases a sleek and functional approach to home automation, featuring PCB and breadboard schematics for a professional and reliable build. It prioritizes integration, ensuring the original remote controller remains operational while introducing new capabilities.
## **Key Features**
- **Weather-Based Automation:** Protect your awning from harsh elements like rain or wind with automatic closure when necessary.
- **Time-Based Automation:** Program your awning to open or close at specific times to suit your routine or maximize daylight exposure.
- **Seamless Remote Control:** Retain manual control with a secondary remote for added flexibility alongside automation.
If you're interested in robust, user-friendly home automation solutions, the Auto-Awning-Close project demonstrates how hardware, creativity, and practical design come together.

View File

@ -0,0 +1,18 @@
+++
title = "Debrepo"
date = 2023-05-01
description = "A Debian repository management tool"
[extra]
state = "stable"
+++
# Debrepo
A Debian repository management tool.
## About
`Debrepo` is a software tool designed for creating and managing Debian repositories for `*.deb` packages, providing a lightweight and user-friendly alternative to more complex tools like `reprepo` or `aptly`. While these alternatives may offer more advanced features, Debrepo focuses on providing essential functionality and ease of use for repository management, allowing users to easily add, remove, and update packages within their repositories. With Debrepo, users can efficiently manage their Debian repositories without the unnecessary complexity of more advanced tools.
## Links
- [Git](https://git.filiprojek.cz/fr/debrepo)

View File

@ -0,0 +1,16 @@
+++
title = "Dotfiles"
date = 2019-01-01
description = "Collection of my configuration dotfiles"
[extra]
state = "active"
+++
# Dotfiles
## About
- Collection of my configuration dotfiles.
## Links
- [Git](https://git.filiprojek.cz/fr/dotfiles)

View File

@ -0,0 +1,34 @@
+++
title = "Fofrweb"
date = 2019-11-27
description = "Custom websites and student projects"
[extra]
state = "active"
+++
# About
- Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
# Projects
## Websites
### date
### links
## FofrMess
### date
## FofrTasks
### date
## Fofrbazar
- Internetová bazarová platforma
### 04/2021
- Technologie
- PHP
- Laravel
- MySQL
- Linux
- Apache

15
content/projects/nork.md Normal file
View File

@ -0,0 +1,15 @@
+++
title = "Nork"
date = 2021-08-13
description = "Simple node.js tool that extends express projects"
[extra]
state = "done"
+++
# Nork
## About
- Simple node.js tool that extends express projects.
## Links
- [Git](https://github.com/filiprojek/nork)

View File

@ -0,0 +1,17 @@
+++
title = "pkmples.cz website"
date = 2023-12-18
description = "Website for PKM Ples written in Zola"
[extra]
state = "done"
+++
Website for PKM Ples written in [Zola](https://getzola.org).
I am using [Gitea Actions](https://docs.gitea.com/usage/actions/overview) for CI/CD.
It is available at [pkmples.cz](https://pkmples.cz).
Source is available on my [Gitea](https://git.filiprojek.cz/fofrweb/com_pkmples.cz).

View File

@ -0,0 +1,21 @@
+++
title = "Self Hosting"
date = 2024-10-14
description = "My selfhosting services"
[extra]
state = "pending"
+++
# Next Cloud
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
# Jellyfin
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
# Uptime Kuma
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
# Gitea
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.

View File

@ -0,0 +1,22 @@
+++
title = "WALauncher"
date = 2024-12-15
description = "A lightweight launcher for your web apps, an alternative to Muximux "
[extra]
state = "done"
+++
The **WALauncher** project is a lightweight, web-based application launcher designed as a modern and streamlined alternative to [Muximux](https://github.com/mescon/Muximux). Its perfect for managing web applications like Servarr apps but is highly customizable to support any web-based services.
WALauncher provides a sleek, user-friendly interface for consolidating your web services into a single, accessible dashboard, making it an efficient tool for home servers or personal projects.
#### **Key Features**
- **Minimalistic Design:** Intuitive layout for easy access to all your web apps.
- **Docker Support:** Deploy effortlessly using the provided Docker setup for consistent and reliable hosting.
- **Customizable:** Configure apps, icons, titles, and additional options to fit your preferences.
- **Open Source:** Contribute to or modify the project to suit your needs.
WALauncher combines simplicity and functionality for managing your web services with minimal overhead. Its a solution you can easily deploy and adapt to your workflow.
Visit the [GitHub](https://github.com/filiprojek/walauncher) repository for more details, screenshots, and support. Start managing your web apps with ease!

View File

@ -0,0 +1,16 @@
+++
title = "Website"
date = 2023-08-29
description = "My personal website"
[extra]
state = "in development"
+++
# Website
## About
- This website is built using the Zola static site generator.
## Links
- [Git](https://git.filiprojek.cz/fr/website)

View File

@ -1,37 +0,0 @@
.content {
display: flex;
flex-direction: column;
align-items: center;
}
.form_contact {
--base-padding: .5rem 1rem;
display: flex;
flex-direction: column;
width: 15rem;
margin-top: 2rem;
}
.form_contact input {
padding: var(--base-padding);
margin-bottom: 1rem;
}
.form_contact button {
align-self: center;
padding: var(--base-padding);
margin-top: 1.5rem;
cursor: pointer;
background-color: white;
color: black;
}
.form_contact textarea {
height: 8rem;
resize: none;
padding: var(--base-padding);
}
.form_contact input, textarea, button {
border-radius: 5px;
}

View File

@ -1,28 +0,0 @@
.content {
margin-block: auto;
}
.home {
font-size: 1.2rem;
}
.home section {
text-align: center;
}
.home a {
color: lightblue;
cursor: pointer;
text-decoration: underline;
}
#pgp {
cursor: pointer;
}
#pgpmobile {
display: none;
}
@media (max-width: 600px) {
#pgp {
display: none;
}
#pgpmobile {
display: inline-block;
}
}

View File

@ -1,52 +0,0 @@
.content h1 {
text-align: center;
margin-bottom: 2rem;
}
.projects {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.projects img {
max-width: 100%;
display: block;
}
.card {
display: flex;
border: 1px solid red;
border-radius: 15px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
width: min(40rem, calc(100% - 1rem));
}
.card .card_text > * {
margin: 1rem;
}
.card > * {
flex-basis: 100%;
}
.card .card_left {
border: 1px solid green;
}
.card .card_right {
border: 1px solid yellow;
}
.technologies {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.technologies span {
border: 1px solid white;
padding: .5rem;
border-radius: 5px;
}

View File

@ -1,77 +0,0 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
font-family: 'Source Code Pro', monospace;
background: var(--background);
color: var(--color);
}
html {
overflow-y: scroll !important;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
scroll-behavior: smooth;
}
*::-webkit-scrollbar {
width: 5px;
}
*::-webkit-scrollbar-thumb {
background-color: #ffffff;
border-radius: 20px;
}
*::-webkit-scrollbar-track {
background: #000000;
}
.nav {
position: relative;
display: flex;
padding: 1rem;
justify-content: space-between;
isolation: isolate;
}
.nav .logo img {
height: 3rem;
}
.nav .links {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
z-index: -1;
color: var(--color);
}
a { color: var(--color); }
a:visited { color: var(--color); }
a:hover { color: red; }
@media (min-width: 400px) {
.nav .links {
position: absolute;
inset: 0;
}
}
.content {
padding: 2rem 1rem;
}
footer {
display: flex;
width: 100vw;
justify-content: center;
align-items: center;
align-self: flex-end;
margin-top: auto;
margin-bottom: 2rem;
}

View File

@ -1,6 +0,0 @@
body {
--base-padding: .5rem 1rem;
--base-margin: 2rem;
--background: #111111;
--color: #ffffff;
}

View File

@ -1,88 +0,0 @@
<?php
$prod = false;
require_once("./libraries/router.php");
require_once("./libraries/console.php");
// Display Errors
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
error_reporting(-1);
// Display var_dump
#var_dump($_SESSION);
// Date Time Zone
date_default_timezone_set('Europe/Prague');
// site info
$keywords = "Filip Rojek, rojek, vyvojar, developer, backend vyvojar, nodejs vyvojar, php vyvojar, web vyvojar, programator, linux administrator";
$description = "Linux user, backend web developer and student.";
$locale = "cs_CZ";
$title = "Filip Rojek | Developer";
$site_name = "Filip Rojek | Developer";
$author = "Fofrweb | https://fofrweb.com";
$email = "webmaster(@)fofrweb.com";
$canonical = "https://www.filiprojek.cz/";
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1.0" />
<meta id="contentLanguage" http-equiv="Content-Language" content="<?= $locale ?>" />
<meta name="keywords" content="<?= $keywords ?>" />
<meta name="description" content="<?= $description ?>" />
<meta property="og:locale" content="<?= $locale ?>" />
<meta property="og:type" content="website" />
<meta property="og:title" content="<?= $title ?>" />
<meta property="og:description" content="<?= $description ?>" />
<meta property="og:site_name" content="<?= $site_name ?>" />
<meta name="author" content="<?= $author ?>" />
<meta name="email" content="<?= $email ?>" />
<meta name="copyright" content="<?= $copyright ?>" />
<meta name="expires" content="never" />
<?php
if($prod) { ?>
<meta name="robots" content="index,follow" />
<meta name="Seznambot" content="index,follow" />
<meta name="Googlebot" content="index,follow" />
<?php } else { ?>
<meta name="robots" content="noindex, nofollow" />
<meta name="Seznambot" content="noindex, nofollow" />
<meta name="Googlebot" content="noindex, nofollow" />
<?php } ?>
<title><?= $title ?></title>
<link rel="icon" type="image/x-icon" href="/img/fr.ico" />
<link rel="canonical" href="<?= $canonical ?>" />
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/variables.css">
</head>
<body>
<?php
require_once("./components/header.php");
?>
<section class="content">
<?php
$R = new Router();
$R->route('GET', '/', 'home');
$R->route('GET', '/home', 'home');
$R->route('GET', '/domu', 'home');
$R->route('POST', '/contact/send', 'contact');
$R = null;
?>
</section>
<footer>
<?php
require_once("./components/footer.php");
?>
</footer>
</body>
</html>

View File

@ -1,9 +0,0 @@
<?php
function console_log($data) {
$output = $data;
if (is_array($output))
$output = implode(',', $output);
echo "<script>console.log('Debug Objects: " . $output . "' );</script>";
}

View File

@ -1,31 +0,0 @@
<?php
class Mail {
public $from;
public $to;
public $subject;
public $message;
public $headers;
function __construct($from, $to, $subject, $message, $headers=null) {
$this->from = $from;
$this->to = $to;
$this->subject = $subject;
$this->headers = $headers;
}
public function send() {
// odeslání e-mailu
$headers = 'From: noreply@fofrweb.com' . "\r\n" .
'Reply-To: support@fofrweb.com' . "\r\n" .
'X-Mailer: PHP/' . phpversion();
$this->headers = $headers;
if (gettype($this->message) == 'string') {
if(mail($this->to, $this->subject, $this->message, $this->headers)) {
return true;
} else {
return false;
}
}
}
}

View File

@ -1,33 +0,0 @@
<?php
class Router {
public $returned = false;
function route($method, $url, $filename) {
$methods = ['GET', 'POST'];
if(in_array($method, $methods)) {
if($_SERVER['REQUEST_METHOD'] == $method) {
if ($_SERVER['REQUEST_URI'] == $url) {
require_once("./pages/$filename/$filename.php");
$this->returned = true;
return;
}
}
}
}
function __destruct() {
if($_SERVER['REQUEST_METHOD'] == 'GET') {
if(!$this->returned){
$url = explode("/", $_SERVER['REQUEST_URI']);
$url = $url[count($url)-1];
if (file_exists("./pages/$url/$url.php")) {
require_once("./pages/$url/$url.php");
} else {
require_once("./pages/errors/404.php");
}
}
}
}
}

View File

@ -1,61 +0,0 @@
<link rel="stylesheet" href="/css/contact.css">
<h1>Kontaktujte </h1>
<form class="form_contact" action="/contact/send" method="post" name="contact">
<label for="name">Vaše jméno:</label>
<input id="name" type="text" placeholder="Petr Novák" required autocomplete="on" />
<label for="email">Váš e-mail:</label>
<input id="email" type="email" placeholder="petr@novak.cz" required autocomplete="on" />
<label for="message">Zpráva:</label>
<textarea id="message" placeholder="Napište zprávu..." required></textarea>
<button type="button" id="submit">Odeslat zprávu</button>
</form>
<script>
const submit = document.querySelector("#submit")
submit.addEventListener("click", async (e) => {
e.preventDefault()
const formData = new FormData();
formData.append('name', document.querySelector("#name").value);
formData.append('email', document.querySelector("#email").value);
formData.append('message', document.querySelector("#message").value);
try {
await fetch('/contact/send', {
method: 'POST',
body: formData
});
console.log('Message sent successfully.')
} catch (err) {
console.error('Error:', error)
}
})
</script>
<?php
ini_set('log_errors', 1);
if(isset($_POST['email']) && isset($_POST['message'])) {
$email = $_POST['email'];
$message = $_POST['message'];
$name = $_POST['name'];
require_once(__DIR__ . "/../../libraries/mail.php");
$message = "Nová zpráva z webu filiprojek.cz\n\nOdesílatel: $email\nZpráva:\n$message";
$m = new Mail($_POST['email'], 'webmaster@filiprojek.cz', 'Nová zpráva z webu filiprojek.cz', $message);
if($m->send()) {
console_log("zprava odeslana");
header('Location: /?sent=true');
} else {
error_log("Message wasn't sent");
error_log(var_dump($m));
echo "<p>Něco se nepovedlo, zkuste to znovu... :(</p>";
echo "<a href='/'>Zpět</a>";
}
}

View File

@ -1,2 +0,0 @@
<h1>Error 404 - not found</h1>

View File

@ -1,33 +0,0 @@
<link rel="stylesheet" href="./css/home.css" >
<section class="home">
<section>
<p>Filip Rojek &ltfilip(at)filiprojek.cz&gt</p>
<br>
<p>Backend node.js & php developer <a href="https://www.fofrweb.com/" target="_blank">@fofrweb</a>,<br> Linux Gentoo user, student and coffee enthusiast<span class="underscore">_</span><p>
<br>
<p id="pgp" title="copy pgp to clipboard">PGP: fc37b989787acf8cbce7c0c2a56a345efe321161</p>
<br>
<p><a href="https://git.filiprojek.cz/fr" target="_blank">Git</a> <a href="https://t.me/filiprojek" target="_blank">Telegram</a> <a href="#" id="pgpmobile" title="copy pgp to clipboard">PGP</a></p>
</section>
<script>
const _ = document.querySelector(".underscore")
let b = false
setInterval(() => {
b ? _.style.visibility = "visible" : _.style.visibility = "hidden"
b = !b
}, 500)
const pgp = document.querySelector("#pgp")
const pgpmobile = document.querySelector("#pgpmobile")
const pgpcp = (e) => {
console.log(e)
navigator.clipboard.writeText("fc37b989787acf8cbce7c0c2a56a345efe321161")
alert("pgp has been copied to the clipboard")
}
pgp.addEventListener("click", pgpcp)
pgpmobile.addEventListener("click", pgpcp)
</script>
</section>

View File

@ -1,107 +0,0 @@
<link rel="stylesheet" href="/css/projects.css">
<h1>Projekty</h1>
<section class="projects">
<div class="card">
<div class="card_left card_text">
<p>pkmples.cz</p>
<p>Komerční webová stránka pro PKM PARTY PLES.</p>
<div class="technologies">
<span>PHP</span>
<span>Linux</span>
<span>Nginx</span>
</div>
<p>01/2023</p>
</div>
<div class="card_right">
<img src="https://www.fofrweb.com/images/pkm_two.png" alt="project screenshot">
</div>
</div>
<div class="card">
<div class="card_left">
<img src="https://www.fofrweb.com/images/rapidd.png" alt="project screenshot">
</div>
<div class="card_right card_text">
<p>rapiddglass.cz</p>
<p>Komerční webová stránka pro přípravek na ochranu a čištění krbového skla Rapidd Glass.</p>
<p>10/2022</p>
<div class="technologies">
<span>PHP</span>
<span>Linux</span>
<span>Nginx</span>
</div>
</div>
</div>
<div class="card">
<div class="card_left card_text">
<p>danielryslavy.cz</p>
<p>Komerční webová stránka osobního trenéra Daniela Ryšlavého.</p>
<p>06/2022</p>
<div class="technologies">
<span>PHP</span>
<span>Linux</span>
<span>Nginx</span>
</div>
</div>
<div class="card_right">
<img src="https://www.fofrweb.com/images/fitness.png" alt="project screenshot">
</div>
</div>
<div class="card">
<div class="card_left">
<img src="#" alt="project screenshot">
</div>
<div class="card_right card_text">
<p>Fofrmess</p>
<p>Chatovací realtime PWA aplikace.</p>
<p>02/2022</p>
<div class="technologies">
<span>Node.js</span>
<span>Express.js</span>
<span>PostgreSQL</span>
<span>Linux</span>
<span>Nginx</span>
</div>
</div>
</div>
<div class="card">
<div class="card_left card_text">
<p>Fofrtasks</p>
<p>Aplikace pro vytváření a správu poznámek a úkolů.</p>
<p>10/2021</p>
<div class="technologies">
<span>Node.js</span>
<span>Express.js</span>
<span>MongoDB</span>
<span>Linux</span>
<span>Nginx</span>
</div>
</div>
<div class="card_right">
<img src="#" alt="project screenshot">
</div>
</div>
<div class="card">
<div class="card_left">
<img src="#" alt="project screenshot">
</div>
<div class="card_right card_text">
<p>Fofrbazar</p>
<p>Internetová bazarová platforma.</p>
<p>04/2021</p>
<div class="technologies">
<span>PHP</span>
<span>Laravel</span>
<span>MySQL</span>
<span>Linux</span>
<span>Apache</span>
</div>
</div>
</div>
</section>

36
postbuild.sh Executable file
View File

@ -0,0 +1,36 @@
#!/bin/bash
public_dir="./public"
css_extension=".css"
timestamp=$(date +%s)
# Define an array of folders to skip
skip_folders=("img" "svg" "fonts")
# Function to add timestamp to CSS files and update HTML imports
update_css_references() {
# Find all CSS files in the root of the public directory
for css_file in "$public_dir"/*$css_extension; do
if [[ -f "$css_file" ]]; then
base_name=$(basename "$css_file" "$css_extension")
new_name="${base_name}-${timestamp}${css_extension}"
# Rename the CSS file
mv "$css_file" "$public_dir/$new_name"
echo "Renamed: $css_file -> $new_name"
# Update all HTML files in the root and 'archiv' folder
for html_file in "$public_dir"/*.html "$public_dir/archiv/"*.html; do
if [[ -f "$html_file" ]]; then
sed -i "s|$base_name$css_extension|$new_name|g" "$html_file"
echo "Updated references in: $html_file"
fi
done
fi
done
}
# Add timestamp to CSS files and update HTML references (including archiv/index.html)
update_css_references

25
sass/_fonts.scss Normal file
View File

@ -0,0 +1,25 @@
@font-face {
font-family: open-sans;
font-weight: normal;
font-style: normal;
src: url('fonts/open-sans-regular.woff2') format('woff2'),
url('fonts/open-sans-regular.woff') format('woff'),
url('fonts/open-sans-regular.ttf') format('truetype');
}
@font-face {
font-family: open-sans;
font-weight: normal;
font-style: italic;
src: url('fonts/open-sans-italic.woff2') format('woff2'),
url('fonts/open-sans-italic.woff') format('woff'),
url('fonts/open-sans-italic.ttf') format('truetype');
}
@font-face {
font-family: open-sans;
font-weight: bold;
font-style: normal;
src: url('fonts/open-sans-bold.woff2') format('woff2'),
url('fonts/open-sans-bold.woff') format('woff'),
url('fonts/open-sans-bold.ttf') format('truetype');
}

15
sass/about.scss Normal file
View File

@ -0,0 +1,15 @@
.about {
h2 {
margin-top: 1rem;
margin-bottom: .5rem;
}
h3, h4, h5, h6, p {
margin-top: .5rem;
margin-bottom: .5rem;
}
p {
text-align: left;
}
}

42
sass/content.scss Normal file
View File

@ -0,0 +1,42 @@
.content {
display: flex;
flex-direction: column;
margin: 2rem 30%;
}
.content {
@media (max-width: 690px) {
margin: 2rem 2rem;
}
.pgpwrap {
word-break: break-all;
}
}
.contact {
h1 {
margin-bottom: 1rem;
}
}
code {
font-size: 1rem;
}
ul {
list-style: none;
padding-left: 0;
li {
position: relative;
padding-left: 1rem;
}
li::before {
content: '';
position: absolute;
left: 0;
color:white;
}
}

18
sass/general.scss Normal file
View File

@ -0,0 +1,18 @@
.flex-col {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.flex-row {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
}
.disable-scroll {
overflow: hidden !important;
position: fixed;
}

35
sass/home.scss Normal file
View File

@ -0,0 +1,35 @@
.content {
margin-block: auto;
}
.home {
margin-top: 5rem;
font-size: 1.2rem;
padding: 0 5%;
section {
text-align: center;
}
a {
color: lightblue;
cursor: pointer;
text-decoration: underline;
}
#pgp {
cursor: pointer;
}
#pgpmobile {
display: none;
}
}
@media (max-width: 600px) {
#pgp {
display: none;
}
#pgpmobile {
display: inline-block;
}
}

78
sass/project.scss Normal file
View File

@ -0,0 +1,78 @@
//.language-yaml {
pre {
display: flex; /* Enables flexbox */
align-items: center; /* Vertically centers the content */
justify-content: space-between; /* Allows space for the copy button on the right */
max-width: 100%;
overflow-x: auto;
overflow-y: auto;
min-height: 3rem; /* Maintains a consistent height */
padding: 0.5rem;
margin: 0.5rem 0;
border-radius: 5px;
white-space: pre; /* Keeps code formatting */
box-sizing: border-box; /* Includes padding in width calculation */
}
code {
display: inline-block;
word-break: break-word; /* Prevents long words from breaking the layout */
white-space: pre-wrap; /* Allows wrapping of long strings */
}
.project-wrapper {
max-width: 900px;
margin: 0 auto;
padding: 20px;
h1 {
margin-top: 2.5rem;
margin-bottom: 1rem;
}
h2 {
margin-top: 1.5rem;
margin-bottom: 1rem;
}
h3, h4, h5, h6, p {
margin-top: 1rem;
margin-bottom: 1rem;
}
p {
text-align: left;
}
}
.link-back {
margin: 2rem 0;
}
.copy-code-wrapper {
position: relative;
}
.copy-code {
position: absolute;
top: .5rem;
right: .5rem;
cursor: pointer;
background-color: #1b1f26;
padding: .3rem ;
border-radius: 5px;
}
@media (max-width: 768px) {
.project-wrapper {
max-width: 100%;
padding: 1rem;
}
}
@media (max-width: 480px) {
.project-wrapper {
padding: 5px;
}
}

29
sass/project_list.scss Normal file
View File

@ -0,0 +1,29 @@
.project-list {
h1 {
margin-bottom: 2rem;
font-size: 2rem;
}
.project {
margin-bottom: 3rem;
.title {
text-decoration: none;
font-size: 1.2rem;
font-weight: bold;
}
.description {
font-style: italic;
margin-bottom: .5rem;
margin-top: .5rem;
}
hr {
margin-bottom: .5rem;
}
}
a {
color: var(--color);
}
}

119
sass/style.scss Normal file
View File

@ -0,0 +1,119 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
flex-direction: column;
min-height: 100dvh;
font-family: 'Source Code Pro', monospace;
background: var(--background);
color: var(--color);
}
html {
overflow-y: scroll !important;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
scroll-behavior: smooth;
}
*::-webkit-scrollbar {
width: 5px;
}
*::-webkit-scrollbar-thumb {
background-color: #ffffff;
border-radius: 20px;
}
*::-webkit-scrollbar-track {
background: #000000;
}
nav {
position: relative;
display: flex;
padding: 1rem 2rem;
justify-content: space-between;
isolation: isolate;
.logo img {
height: 3rem;
}
.links {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
z-index: -1;
color: var(--color);
font-size: 1.3rem;
}
a {
color: var(--color);
}
}
a {
color: lightblue;
cursor: pointer;
text-decoration: underline;
}
a:hover { color: var(--a-hover); }
@media (min-width: 400px) {
nav .links {
position: absolute;
inset: 0;
}
}
footer {
display: flex;
width: 100vw;
flex-direction: column;
justify-content: center;
align-items: center;
align-self: flex-end;
margin-top: auto;
margin-bottom: max(2rem, env(safe-area-inset-bottom)); /* Account for safe area */
}
.hamburger {
display: none;
}
@media only screen and (min-width: 0px) and (max-width: 1090px) {
header {
.hamburger {
display: block;
font-size: 3rem;
text-decoration: none;
cursor: pointer;
user-select: none;
z-index: 2000;
}
.logo {
z-index: 2000;
}
.hamburger:hover {
color: white;
}
.links {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
background: var(--c-gray);
flex-direction: column;
font-size: 2rem;
}
}
}

15
sass/vars.scss Normal file
View File

@ -0,0 +1,15 @@
* {
--base-padding: .5rem 1rem;
--base-margin: 2rem;
--c-white: #ffffff;
--c-gray: #111111;
--c-red: #FF3A20;
--c-darkblue: #02081e;
--c-blue: #5B85AA;
--a-hover: var(--c-red);
--color: var(--c-white);
--background: var(--c-gray);
}

1
static/img/at.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#ffffffff" d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm0-280q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Z"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 526 B

30
static/js/code-copy.js Normal file
View File

@ -0,0 +1,30 @@
let codes = document.querySelectorAll("pre")
codes.forEach(code => {
const elWrapper = document.createElement("div")
elWrapper.classList.add("copy-code-wrapper")
const el = document.createElement("span")
el.textContent = "Copy"
el.classList.add("copy-code")
elWrapper.appendChild(el)
code.parentNode.insertBefore(elWrapper, code)
let textContent = ""
code.childNodes.forEach(child => {
textContent += child.textContent;
})
elWrapper.addEventListener("click", (e) => {
e.preventDefault()
navigator.clipboard.writeText(textContent)
el.textContent = "Copied!"
setTimeout(() => {
el.textContent = "Copy"
}, 5000);
})
})

107
static/js/console.js Normal file
View File

@ -0,0 +1,107 @@
const c = document.querySelector(".console")
const ps1 = "[fr@website ~]$ "
const motd = "Welcome to my website!<br>You can use `help` for more informations :)"
let line = "";
function exec(command) {
switch (command[0]) {
case "help":
line = "help<br>author<br>contact<br>clear<br>echo"
break;
case "author":
line = "Filip Rojek, 2023"
break
case "contact":
line = "Filip Rojek &lt<a href='mailto: &#102;&#105;&#108;&#105;&#112;&#064;&#102;&#105;&#108;&#105;&#112;&#114;&#111;&#106;&#101;&#107;&#046;&#099;&#122;'>&#102;&#105;&#108;&#105;&#112;&#064;&#102;&#105;&#108;&#105;&#112;&#114;&#111;&#106;&#101;&#107;&#046;&#099;&#122;</a>&gt<br>web: <a href='https://filiprojek.cz' target='_blank'>www.filiprojek.cz</a><br>telegram: <a href='https://t.me/filiprojek' target='_blank'>@filiprojek</a>"
break
case "clear":
write("000ctrll")
return false
case "echo":
line = ""
command.forEach((cmd, i) => {
if (i === 0) return
line += cmd + " "
});
line = line.substring(0, line.length - 1) // remove last space
break
default:
line = "frsh: " + command[0] + ": command not found"
break;
}
return line
}
function write(key, payload) {
switch(key) {
case "Enter":
command = c.lastChild.textContent.replace(ps1, "")
command = command.split(" ");
let out = exec(command)
if(out !== false) {
line = document.createElement("p")
line.innerHTML += out
line.innerHTML += "<br>"
c.appendChild(line)
line = document.createElement("p")
line.innerHTML += ps1
c.appendChild(line)
}
command = ""
break
case "000ctrll":
c.innerHTML = ""
if(payload == motd) c.innerHTML = motd
line = document.createElement("p")
line.innerHTML += ps1
c.appendChild(line)
break
case "000backspace":
if(c.lastChild.textContent.slice(0, -1) !== ps1.slice(0, -1)) {
c.lastChild.innerHTML = c.lastChild.textContent.slice(0, -1)
}
break
default:
c.lastChild.innerHTML += key
}
}
function customCtrlShortcuts(plusKey) {
document.addEventListener("keydown", e => {
if(e.ctrlKey && e.key == plusKey) {
e.preventDefault()
write("000ctrl"+plusKey)
}
})
}
// On load init the terminal
window.addEventListener("load", () => {
write("000ctrll", motd)
if (navigator.userAgent.toLowerCase().includes("mobile")) {
const mi = document.querySelector(".mobile-input")
mi.style="opacity: 0; width: 0; height: 0"
c.addEventListener("click", e => {
mi.focus()
})
}
})
// Capture the keypress
window.addEventListener("keypress", e => {
e.preventDefault()
write(e.key)
})
window.addEventListener("keydown", e => {
if(e.key == "Backspace") {
e.preventDefault()
write("000backspace")
}
})
// Register custom ctrl shortcuts
customCtrlShortcuts("l") // ctrl + l
customCtrlShortcuts("c") // ctrl + c

View File

@ -0,0 +1,26 @@
const burger = document.querySelector(".hamburger")
const links = document.querySelector(".links")
const body = document.querySelector("body")
const main = document.querySelector("main")
let shown = false
burger.addEventListener("click", (e) => {
e.preventDefault()
if (!shown) {
links.style.display = "flex"
body.classList.add("disable-scroll")
burger.textContent = "x"
main.style.visibility = "hidden"
} else {
links.style.display = "none"
body.classList.remove("disable-scroll")
burger.textContent = "☰"
main.style.visibility = "visible"
}
shown = !shown
})

23
templates/404.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Filip Rojek | 404 Not found</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="/img/fr.ico">
<link rel="stylesheet" href="/general.css">
<link rel="stylesheet" href="/vars.css">
<link rel="stylesheet" href="/style.css">
<style>
.flex-col {
height: 100vh;
}
</style>
</head>
<body>
<div class="content flex-col">
<h2>404 - Not found</h2>
<a href="/">Home</a>
</div>
</body>
</html>

11
templates/about.html Normal file
View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block styles %}
<link rel="stylesheet" href="/about.css">
{% endblock styles %}
{% block content %}
<section class="about content">
{{ page.content | safe }}
</section>
{% endblock content %}

78
templates/base.html Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>
{% block title %}
{% if current_path != "/" %}
{{ config.title }} &ndash;
{% if section.title %}
{{ section.title }}
{% elif page.title %}
{{ page.title }}
{% endif %}
{% else %}
{{ config.title }}
{% endif %}
{% endblock title %}
</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Filip Rojek - Personal website">
<link rel="icon" type="image/x-icon" href="/img/fr.ico">
<link rel="stylesheet" href="/general.css">
<link rel="stylesheet" href="/vars.css">
<link rel="stylesheet" href="/content.css">
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="/home.css">
<script defer src="https://analytics.fofrweb.com/script.js" data-website-id="2b326fdd-6c87-4627-b1f1-d0afb40aeef6"></script>
<script defer src="/js/mobile-navbar.js"></script>
{% block styles %}
{% endblock styles %}
</head>
<body>
<header>
<nav>
<a href="/" class="logo">
<img src="/img/fr_logo.webp" alt="logo">
</a>
<div class="links">
{% for item in config.extra.nav_items %}
<a href="{{ item.path }}"
{% if item.path == current_path and item.path != "/" %}
class="active"
{% endif %}
>{{ item.name }}</a>
{% endfor %}
</div>
<div class="hamburger"></div>
</nav>
</header>
<main>
{% block content %}
<section>
{% if section %}
{{ section.content | safe }}
{% elif page %}
{{ page.content | safe }}
{% endif %}
</section>
{% endblock content %}
</main>
<footer>
<p>Build time: {{ now() | date(format="%Y-%m-%d %H:%M") }},
{% if config.extra.git %}
<a href="{{ config.extra.git }}" target="_blank">Source</a>
{% endif %}</p>
<p>&copy; filiprojek.cz 2022 - {{ now() | date(format="%Y")}}</p>
</footer>
<script>
console.log("I heard that a cool frontend developer works for https://fofrweb.com")
</script>
{% block scripts %}
{% endblock scripts %}
</body>
</html>

26
templates/index.html Normal file
View File

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block content %}
<section class="home">
<section>
<p>Filip Rojek &lt&#102;&#105;&#108;&#105;&#112;&#064;&#102;&#105;&#108;&#105;&#112;&#114;&#111;&#106;&#101;&#107;&#046;&#099;&#122;&gt</p>
<br>
<p>Linux sysadmin, backend node.js & php developer <a href="https://www.fofrweb.com/" target="_blank">@fofrweb</a>,<br> GNU/Linux <a href="http://voidlinux.org" target="_blank">Void</a> user, student and coffee enthusiast<span class="underscore">_</span><p>
<br>
<p>PGP: <a href="https://keys.openpgp.org/vks/v1/by-fingerprint/CA3D9BE28315B49164130CD97E65EA58C6075F09" target="_blank">0x7E65EA58C6075F09</a></p>
<br>
<p><a href="https://git.filiprojek.cz/fr" target="_blank">Git</a> <a href="https://t.me/filiprojek" target="_blank">Telegram</a> <a href="#" id="pgpmobile" title="copy pgp to clipboard">PGP</a></p>
</section>
<script>
const _ = document.querySelector(".underscore")
let b = false
setInterval(() => {
b ? _.style.visibility = "visible" : _.style.visibility = "hidden"
b = !b
}, 500)
</script>
</section>
{% endblock content %}

33
templates/post.html Normal file
View File

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block styles %}
<link rel="stylesheet" href="/project.css">
{% endblock styles %}
{% block scripts %}
<script src="/js/code-copy.js" defer></script>
{% endblock scripts %}
{% block content %}
<section class="project-wrapper flex-col">
<h2>{{ page.title }}</h2>
<!--
<section class="left-bar flex-col">
<h2>Projects</h2>
{% set section = get_section(path=page.ancestors | last) %}
{% for project in section.pages %}
<a href="{{ project.permalink }}">{{project.title}}</a>
{% endfor %}
</section>
-->
<!-- <h2>{{ page.title }}</h2> -->
<div>
<hr>
{{ page.content | safe }}
</div>
<a href="/posts" class="link-back">Back to list of posts</a>
</section>
{% endblock content %}

28
templates/post_list.html Normal file
View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block styles %}
<link rel="stylesheet" href="/project_list.css">
{% endblock styles %}
{% block content %}
<section class="project-list content">
<h1>My Posts</h1>
{% for post in section.pages %}
<div class="project">
<a class="title" href="{{ post.permalink }}">
{{ post.title }}
</a>
<p class="description">
{% if post.description %}
{{ post.description }}
{% else %}
&hellip;
{% endif %}
</p>
<hr>
<p>{{ post.date }}</p>
</div>
{% endfor %}
</section>
{% endblock content %}

27
templates/project.html Normal file
View File

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block styles %}
<link rel="stylesheet" href="/project.css">
{% endblock styles %}
{% block content %}
<section class="project-wrapper flex-col">
<!--
<section class="left-bar flex-col">
<h2>Projects</h2>
{% set section = get_section(path=page.ancestors | last) %}
{% for project in section.pages %}
<a href="{{ project.permalink }}">{{project.title}}</a>
{% endfor %}
</section>
-->
<h2>{{ page.title }}</h2>
<div>
<hr>
{{ page.content | safe }}
</div>
<a href="/projects" class="link-back">Back to list of projects</a>
</section>
{% endblock content %}

View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block styles %}
<link rel="stylesheet" href="/project_list.css">
{% endblock styles %}
{% block content %}
<section class="project-list content">
<h1>My Projects</h1>
{% for project in section.pages %}
<div class="project">
<a class="title" href="{{ project.permalink }}">
{{ project.title }}
</a>
<p class="description">
{% if project.description %}
{{ project.description }}
{% else %}
&hellip;
{% endif %}
</p>
<hr>
<p>{{ project.date }} | {{ project.extra.state}}</p>
</div>
{% endfor %}
</section>
{% endblock content %}