Compare commits

...

10 Commits

26 changed files with 419 additions and 60 deletions

View File

@@ -1,5 +1,5 @@
# pages-svelte # pages-svelte
SvelteKit repository for my website hosted at [https://denizk0461.dev/](https://denizk0461.dev/) SvelteKit repository for my website hosted at [https://natconf.dev/](https://natconf.dev/)
![Screenshot of the website's main page](website-screenshot.webp) ![Screenshot of the website's main page](website-screenshot.webp)

View File

@@ -5,23 +5,11 @@ export interface IndieButton {
} }
export let buttons: IndieButton[] = [ export let buttons: IndieButton[] = [
{
img: "flag-progress.png",
alt: "A button showing the progress pride flag.",
},
{
img: "gnu-linux.gif",
alt: "A button with the Linux penguin and the text 'Made on GNU/Linux'.",
},
{ {
img: "iso8601.png", img: "iso8601.png",
alt: "A button with the text 'ISO 8601 YYYY-MM-DD'.", alt: "A button with the text 'ISO 8601 YYYY-MM-DD'.",
link: "https://www.iso8601.com/", link: "https://www.iso8601.com/",
}, },
{
img: "notbyai.png",
alt: "A button with the text 'created by a human, not by AI' and a smiley next to it.",
},
{ {
img: "queercoded.png", img: "queercoded.png",
alt: "A button with the text \"you're telling me a queer coded this\" on a rainbow background.", alt: "A button with the text \"you're telling me a queer coded this\" on a rainbow background.",
@@ -31,11 +19,6 @@ export let buttons: IndieButton[] = [
alt: "A red button with the text 'MADE WITH SVELTEKIT'.", alt: "A red button with the text 'MADE WITH SVELTEKIT'.",
link: "https://svelte.dev", link: "https://svelte.dev",
}, },
{
img: "sexno.png",
alt: "A button with the text 'SEX? NO.' on a white background with a blue border.",
link: "https://youtu.be/J4i0tuoYBG0?t=176",
},
{ {
img: "trans-rights-now.png", img: "trans-rights-now.png",
alt: "A button with the text 'TRANS RIGHTS NOW!' next to a trans flag.", alt: "A button with the text 'TRANS RIGHTS NOW!' next to a trans flag.",

View File

@@ -80,10 +80,11 @@
} }
.toc-container { .toc-container {
max-width: 650px; max-width: var(--width-toc);
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-top: 12px; margin-top: 12px;
box-sizing: border-box;
background-color: var(--color-background-highlight); background-color: var(--color-background-highlight);
padding: 16px 0; padding: 16px 0;
border: var(--border-style) var(--border-dash-size) var(--color-highlight); border: var(--border-style) var(--border-dash-size) var(--color-highlight);

View File

@@ -22,7 +22,7 @@
</div> </div>
<span class="update-entry-timestamp-divider">::</span> <span class="update-entry-timestamp-divider">::</span>
<p> <p>
{entry.content} {@html entry.content}
{#if entry.link} {#if entry.link}
<a class="update-entry-link" href="{entry.link}">»</a> <a class="update-entry-link" href="{entry.link}">»</a>
{/if} {/if}

View File

@@ -20,7 +20,7 @@
<div class="content-container"> <div class="content-container">
<div class="content-box center-box"> <div class="content-box center-box">
<p> 20232026 denizk0461</p> <p> 20232026 denizk0461</p>
<p>Built from commit <a class="commit" href="https://code.denizk0461.dev/denizk0461/pages/src/commit/{version}">{version.substring(0, 6)}</a></p> <p>Built from commit <a class="commit" href="https://code.natconf.dev/denizk0461/pages/src/commit/{version}">{version.substring(0, 6)}</a></p>
</div> </div>
<div class="content-box"> <div class="content-box">
<h6>Content</h6> <h6>Content</h6>
@@ -35,7 +35,7 @@
<a href="/meta/about">About</a> <a href="/meta/about">About</a>
<a href="/meta/feeds">Feeds</a> <a href="/meta/feeds">Feeds</a>
<a href="/meta/updates">Updates</a> <a href="/meta/updates">Updates</a>
<a href="https://code.denizk0461.dev/denizk0461/pages">Page Source</a> <a href="https://code.natconf.dev/denizk0461/pages">Page Source</a>
<a href="/meta/privacy">Privacy & Cookies</a> <a href="/meta/privacy">Privacy & Cookies</a>
</div> </div>
</div> </div>

View File

@@ -97,6 +97,13 @@
font-style: normal; font-style: normal;
} }
@font-face {
font-family: "LIGHTYEARS";
src: url("/fonts/lightyears.woff2");
font-weight: 400;
font-style: normal;
}
/* OpenMoji */ /* OpenMoji */
@font-face { @font-face {
font-family: "OpenMoji"; font-family: "OpenMoji";
@@ -120,7 +127,8 @@
--color-header-highlight: color-mix(in srgb, #6d1e26 80%, transparent); --color-header-highlight: color-mix(in srgb, #6d1e26 80%, transparent);
--color-background: #111111; --color-background: #111111;
--color-background-highlight: color-mix(in srgb, var(--color-highlight) 10%, transparent); --color-background-highlight: color-mix(in srgb, var(--color-highlight) 20%, transparent);
--color-background-highlight-alt: color-mix(in srgb, var(--color-highlight-alt) 20%, transparent);
--color-background-highlight-hover: color-mix(in srgb, var(--color-highlight) 60%, transparent); --color-background-highlight-hover: color-mix(in srgb, var(--color-highlight) 60%, transparent);
--color-background-highlight-hover-dark: color-mix(in srgb, var(--color-highlight-dark) 60%, transparent); --color-background-highlight-hover-dark: color-mix(in srgb, var(--color-highlight-dark) 60%, transparent);
@@ -154,6 +162,8 @@
--font-mono: 'Kode Mono', 'OpenMoji', monospace; --font-mono: 'Kode Mono', 'OpenMoji', monospace;
--font-size-mono: 0.9em; --font-size-mono: 0.9em;
--font-lightyears: 'LIGHTYEARS', sans-serif;
--font-size-h1: 2.0rem; --font-size-h1: 2.0rem;
--font-size-h2: 1.5rem; --font-size-h2: 1.5rem;
--font-size-h3: 1.3rem; --font-size-h3: 1.3rem;
@@ -164,6 +174,7 @@
/* sizing */ /* sizing */
--media-width: 80%; --media-width: 80%;
--width-toc: 650px;
/* page sizing */ /* page sizing */
--page-width: 1200px; --page-width: 1200px;
@@ -305,6 +316,11 @@
padding-right: 6px; padding-right: 6px;
} }
.code-block {
width: fit-content;
padding: 16px;
}
img, video { img, video {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@@ -326,6 +342,10 @@
border: var(--border-dash-size) var(--border-style) var(--color-highlight); */ border: var(--border-dash-size) var(--border-style) var(--color-highlight); */
} }
.lightyears-text {
font-family: var(--font-lightyears);
}
.horizontally-centre-aligned { .horizontally-centre-aligned {
width: var(--media-width); width: var(--media-width);
display: flex; display: flex;
@@ -455,5 +475,15 @@
font-family: var(--font-sans-serif); font-family: var(--font-sans-serif);
padding: 4px; padding: 4px;
} }
.callout-warning {
margin: 12px auto;
max-width: var(--width-toc);
padding: 12px 20px;
box-sizing: border-box;
backdrop-filter: blur(var(--blur-radius-background));
background-color: var(--color-background-highlight-alt);
border: var(--border-dash-size) var(--border-style) var(--color-highlight-alt);
}
} }
</style> </style>

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import Content from "$lib/viewport/content.svelte"; import Content from "$lib/viewport/content.svelte";
import SubtitledImage from "$lib/components/subtitled-image.svelte";
import GalleryRow, { type GalleryRowEntry } from "$lib/lists/gallery-row.svelte"; import GalleryRow, { type GalleryRowEntry } from "$lib/lists/gallery-row.svelte";
import { posts as devlogPosts } from "./projects/projectn5/devlog/posts"; import { posts as devlogPosts } from "./projects/projectn5/devlog/posts";
@@ -52,14 +51,14 @@
description: "Find things I've put for download on my Copyparty instance", description: "Find things I've put for download on my Copyparty instance",
img: "main/hypertext.webp", img: "main/hypertext.webp",
altText: "Screenshot of Hypertext Unity level. Crates are strewn across the floor, Waluigi is flying in front of the camera, and text such as 'COME AND TRY OUR ALL-NEW BLENDER' and 'omg! it's the brandenburg er tor!' is displayed.", altText: "Screenshot of Hypertext Unity level. Crates are strewn across the floor, Waluigi is flying in front of the camera, and text such as 'COME AND TRY OUR ALL-NEW BLENDER' and 'omg! it's the brandenburg er tor!' is displayed.",
link: "https://files.denizk0461.dev/public/", link: "https://files.natconf.dev/public/",
}, },
{ {
title: "Gitea", title: "Gitea",
description: "I now also self-host a Gitea instance where I am likely migrating all my projects to", description: "I now also self-host a Gitea instance where I am likely migrating all my projects to",
img: "main/magic.webp", img: "main/magic.webp",
altText: "A 'magic' command written in Java. The command shuts down the computer when ran.", altText: "A 'magic' command written in Java. The command shuts down the computer when ran.",
link: "https://code.denizk0461.dev/", link: "https://code.natconf.dev/",
}, },
]; ];
</script> </script>
@@ -71,10 +70,12 @@
<Content> <Content>
<h1 class="gradient-title"><i>Moin!</i> ~ welcome to my website :)</h1> <h1 class="gradient-title"><i>Moin!</i> ~ welcome to my website :)</h1>
<a href="/blog/2026/0325" class="page-subtitle gradient-title lightyears-text">you can change the world from your bedroom!</a>
<hr> <hr>
<div> <div>
<img class="me-img pixelated-img" src="me.webp" alt="Pixelated mirror selfie of the website creator wearing a green shirt, fitting the website theme. The face is obscured." title="hi!"> <img class="me-img pixelated-img" src="me.webp" alt="Pixelated mirror selfie of the website creator wearing a green shirt, fitting the website theme. The face is obscured." title="hi!">
<p>Hi! I'm Deniz. Welcome to my website! I keep rewriting this introduction but I'm REALLY bad at this type of stuff.</p> <p>Hi! I'm Deniz. Welcome to my website! I keep rewriting this introduction but I'm REALLY bad at this type of stuff.</p>
@@ -129,12 +130,13 @@
</div> </div>
<div class="button-container"> <div class="button-container">
<h4 class="update-header">88x31 buttons</h4> <h4 class="update-header">button corner</h4>
<div class="button-subcontainer"> <div class="button-subcontainer">
{#each buttons as button} {#each buttons as button}
<IndieButton button={button} /> <IndieButton button={button} />
{/each} {/each}
</div> </div>
<p>to be expanded!</p>
<p class="small-supertext">my own 88x31 button is in the making. ETA: ???</p> <p class="small-supertext">my own 88x31 button is in the making. ETA: ???</p>
</div> </div>
</div> </div>
@@ -243,6 +245,12 @@
background-clip: text; background-clip: text;
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
padding-bottom: 12px; }
.page-subtitle {
/* padding-bottom: 12px; */
width: fit-content;
margin: 4px 0 12px 0;
display: block;
} }
</style> </style>

View File

@@ -0,0 +1,247 @@
<div class="callout-warning">
<p><b>Note</b>: as I'm constantly learning new things, this blog post is sort of outdated now. The information is still correct, but it feels inefficient and leads into a dead-end. I dislike this, as I wanted to write a guide that provides an expandable base. Therefore, I'll likely either update or replace this article and trim it down to focus on the important bits.</p>
<p>Expect a Go API.</p>
</div>
*Hey!*
Do you want to develop a web application in SvelteKit? Do you want this application to access a PostgreSQL database on a remote server? Do you struggle with CORS (cross-origin resource sharing) and CSRF (cross-site request forgery) and keep receiving error 403? Well, I did, and I couldn't find ***ANYTHING*** on the internet going beyond a test running locally. This is why I'm writing this guide.
Let me preface this by saying: what I did isn't perfect. It's also likely incomplete; I would give bad advice in a complete guide for sure. What this is, is a guide covering the key aspects for setting up a relatively minimal setup to get your application online successfully, upon which you can expand by adding more features. The important distinction from other guides I've found online is that this guide doesn't deploy the backend locally, but instead sets it up online, ready to use.
## The Guide
### What You Need
- a device, such as a laptop or a desktop, with the following installed:
- an IDE
- `npm`
- `ssh` keys for the server, if you're accessing it remotely
- a remote server instance set up with some software:
- Nginx
- Docker Compose
- an idea for what kind of app you wanna make
### Getting Started
#### SvelteKit
For creating a SvelteKit app, I recommend the guide [Getting Started](https://svelte.dev/docs/svelte/getting-started) by the Svelte team. You won't need more than the first four commands to create the app and get it up and running. During the setup, I recommend you pick the `adapter-node`.
#### Backend
For the backend, which will run on your remote server, it's very easy to use Docker Compose to get started. Once you're `ssh`'d into the server (or physically sitting at the terminal), create a folder where the data will live in, then create a file named `compose.yml` (for instance, by typing `nano compose.yml` into the terminal) and paste this into the file, then use `docker compose up -d` (or if that doesn't work, try `docker-compose up -d` with a hyphen) to start the container:
<pre class="code-block">
version: '3'
services:
api:
image: postgrest/postgrest
restart: always
ports:
- 3100:3000
environment:
PGRST_SERVER_HOST: '0.0.0.0'
PGRST_DB_URI: 'postgres://authenticator:password@database:5432/postgres'
PGRST_DB_SCHEMAS: 'api'
PGRST_DB_ANON_ROLE: 'web_anon'
PGRST_SERVER_CORS_ALLOWED_ORIGINS: "https://app.natconf.dev"
depends_on:
- database
database:
image: postgres
restart: always
ports:
- 5432:5432
environment:
POSTGRES_USER: 'username'
POSTGRES_PASSWORD: 'password'
POSTGRES_DB: 'database_name'
volumes:
- ./postgresql:/var/lib/postgresql
</pre>
Important notes:
This `compose.yml` covers both the PostgreSQL database itself as well as a program called [PostgREST](https://docs.postgrest.org/), which connects to a PostgreSQL database and acts as a [REST](https://developer.mozilla.org/en-US/docs/Glossary/REST) api. This is advantageous, as it means you won't need modules such as [Postgres.js](https://github.com/porsager/postgres) to send raw SQL queries from SvelteKit to the database. With PostgREST, you can use a regular URL to access and modify database data using HTTP verbs such as GET (for getting data) and POST (for sending new data).
Some environment variables need to be set: `PGRST_SERVER_CORS_ALLOWED_ORIGINS` decides from which domains requests are allowed. Setting it to `"*"` allows all domains. Change this to the domain your application is running on. Set `POSTGRES_USER` and `POSTGRES_PASSWORD` to the data you'll use to log in as your admin account. Also, `PGRST_DB_ANON_ROLE` is the user that will be used when making requests to the database without credentials. This user's permissions will decide whether GET, POST, etc. requests will go through. You don't need to change this, but if you do, you'll also have to change `web_anon` later in this guide.
In `PGRST_DB_URI`, the link uses the username `authenticator` and a password. Replace the word 'password' with the actual password you'll use for the `authenticator` role. You will set up this role [later](#configure-the-database). This is *not* the same password you'll set for `POSTGRES_PASSWORD`!
The ports may differ on your machine. The `database` service runs on port 5432. You only need to change this if this port is already used by something on your server. Keep in mind to change the **left** number only! The left number is the port that faces outwards, the right port is the one used internally by the container. Since PostgreSQL is running on port 5432 by default, and since there is nothing else running inside the container that's also on port 5432, we don't need to change the right side at all. If you change the left port, be sure to change the port used in the `api` service's `PGRST_DB_URI` as well, as this URI is used by PostgREST to connect to the database.
I did change the port for the `api` service, however. PostgREST runs on port 3000 by default. This is problematic, as Node (which we will use for the SvelteKit application later) also runs on port 3000 by default. I chose to change PostgREST's port to 3100 here to avoid conflict, but you can pick any non-privileged port (any between 1024 and 65535 is good).
Keep in mind that you will not need to open up any of these ports on your firewall. Why? This is why:
#### Nginx
I'm using Nginx as a reverse proxy here. What does this mean? It means that we can send a human-readable URL to Nginx, such as this: `https://files.natconf.dev`, and Nginx will *internally* route us to the place the service is actually running on: `http://localhost:3923`. This has several advantages: as the end user, we won't need to remember the IP address or the port of the services we connect to. As the server administrator, we will only need to expose a minimal number of ports. In this example, port 3923 is actually closed off from the public, meaning that bypassing Nginx and connecting to the service directly, e.g. by typing `https://natconf.dev:3923`, is impossible. The only open port is 443, which is a standard port for websites served over encrypted HTTPS. Your browser will always try to connect to a website starting with `https://` on port 443, unless you specify another port. Port 80 is actually also open on my server, as it accepts requests through the unsafe `http://`, but it only redirects to `https://` on 443.
I configured Nginx to run both the SvelteKit app as well as the PostgREST backend from the same subdomain. You don't *need* to do this, and I *think* it should even work if you put, for example, the database on a different subdomain, but I can't guarantee it.
<!-- &#123; == {. if this is not encoded, sveltekit will try to interpret it as code and throw an error -->
<pre class="code-block">
server &#123;
listen 443;
server_name app.natconf.dev;
location / &#123;
proxy_pass http://localhost:3000;
}
location /customers &#123;
proxy_pass http://localhost:3100/customers;
}
}
</pre>
This `server` block serves two functions:
- it serves our SvelteKit app, which by default runs on port 3000, from the root directory of the subdomain (e.g. `https://app.natconf.dev`), and
- it serves the table `customers` from our PostgreSQL database, which is accessible through PostgREST on port 3100, on the subpage `/customers` (`https://app.natconf.dev/customers`).
- If your table has a different name, replace `customers` with that table's name.
- If you want multiple tables to be accessible, copy the entire `location /[tablename]` block and paste it in as often as you need, replacing `[tablename]` with the names of the tables.
Be sure to change `natconf.dev` to your own domain, as this will otherwise not work.
### Configure the Database
Once you have the database running, we need to create a table to store data in. You can log into the database on your server by running this command to enter the PostgreSQL console: `docker exec -it [service-name] psql -d postgres -U [username]`. `[container-name]` is the name of the PostgreSQL service, which you will see popping up in the terminal after running `docker compose up -d`. It will likely look something like `foldername_database`. `[username]` is the name of the privileged user defined in `compose.yml` under the environment variable `POSTGRES_USER`. This is basically your admin account.
You're best off following [the official guide for PostgREST](https://docs.postgrest.org/en/v14/tutorials/tut0.html#step-3-create-database-for-api). Important steps: create the schema, name it `api` (that's the publicly-accessible schema because we set `PGRST_DB_SCHEMAS: 'api'` in the `compose.yml`), create a table (name has to start with `api.` but can be anything after that, e.g. `api.customers`), insert test data, create the roles `web_anon` and `authenticator` and then quit by typing `\q`. That's all. Welcome back!
You can now access the database data on `https://app.natconf.dev` (if you replace my domain with yours). Keep in mind that the PostgREST guide only grants read access (GET) to the database; in order to POST data to the database, you need to grant `INSERT` privileges to `web_anon`: `grant insert on api.customers to web_anon;`.
If you want a kind voice to guide you through this process, I found [Ian Wootten's video on setting up PostgREST](https://youtu.be/RxuofiZNhtU) to be very nice to follow along.
### POST Data From SvelteKit
The difficult part is done. Sending data from SvelteKit to be inserted into the database is pretty easy, fortunately. You can use `fetch` to do this:
<pre class="code-block">
const response = await fetch(`https://$&#123;API_HOST}/$&#123;API_DB}`, &#123;
method: "POST",
headers: &#123;
"Content-Type": "application/json",
},
body: JSON.stringify(customer),
});
if (response.ok) &#123;
console.log("success");
} else &#123;
console.log("an error occurred: " + response.statusText);
}
</pre>
In this example, I set up two environment variables: `API_HOST` and `API_DB`. Those are secrets that won't be published to your Git provider when you push your changes (if your .gitignore is set up correctly!). `API_HOST` is the fully-qualified domain, e.g. `https://app.natconf.dev`, and `API_DB` is the name of the table, e.g. `customers`. Doing this is not necessary, but it's good practice to keep secrets, especially once you get to the stage of needing passwords or authentication keys. If you want to use environment variables, create a file named `.env` in the root directory of your application, add the variables as such:
<pre class="code-block">
API_HOST="https://app.natconf.dev"
API_DB="customers"
</pre>
...and then import them into your script:
<pre class="code-block">
import &#123;
API_HOST,
API_DB,
} from '$env/static/private';
</pre>
Keep in mind that the `fetch` call is async, meaning that if you wrap this in a function, you will have to declare the function as such, e.g. `async function insertCustomer(...)` and call it using await: `await insertUser(...)`.
Also, the `customer` variable I declared is basically one database entry with the exact same properties as you set it up in PostgreSQL, except for the primary key (likely named `id`). You shouldn't send this in a POST request, as `id` will be auto-generated by the database to be a unique value. That is, unless you *want* to generate these values yourself, in which case, go ahead.
This should now work fine to POST data to the database...
### Fix Error 403 On SvelteKit
...except in production. Once you deploy the application on your server, you may face error *'403: Cross-site POST form submissions are forbidden'*. This is an issue caused by a security measure implemented in the browser that forbids accepting data unless the server sends the correct headers. You will only see this issue in a production build as SvelteKit disables these security measures when you're running a development build. The way I fixed this in production is by adding the domain of my backend to the `trustedOrigins` inside the `kit` block of my `svelte.config.js`:
<pre class="code-block">
kit: &#123;
adapter: adapter(),
csrf: &#123;
trustedOrigins: [
"https://app.natconf.dev"
],
},
},
</pre>
**This is the crucial bit.** Only by adding the URL of my backend to the `trustedOrigins` was I able to fix the 403 errors. Is this the correct way of doing it? Frankly, I don't know. But I do know that you should definitely not put `"*"` as one of the trusted origins, as this would mean your app accepts data from anywhere, and that is *definitely* unsafe.
If you just came here to get a solution for the 403 error that's it! We're done :) But read on for an extra piece of advice as well as my struggle to get this working.
### Your Data Will Be Public
**Important:** the contents of the database will be publicly visible, as we've granted GET (and possibly INSERT) permissions to `web_anon`. You will need to do a little more work to create a safe API that only exposes required data. Use either [PostgREST's guide](https://docs.postgrest.org/en/v14/tutorials/tut1.html) or [Ian Wootten's video](https://youtu.be/RxuofiZNhtU) for guidance on how to restrict and grant access safely.
## The Rant
If you only came for the guide, you've finished it. What follows is me ranting about how difficult it was to get this information.
### Why Did I Write This?
I was building a small SvelteKit application for a project I'm doing together with a friend. I figured it'd be easy, as it's just like building my website, except I'll also make some requests to a backend on my server. I was right... kind of. I have spent *two days* trying to figure out how to do this successfully.
The requests were working fine when I was running the development build of the SvelteKit app on my PC. Once I deployed it though, I was facing *'403: Cross-site POST form submissions are forbidden'* errors for every single request. I couldn't figure out why. I scoured the internet for answers, trying anything I could. I configured `Access-Control-Allow-*` headers in Nginx. I set the `ORIGIN` parameter in the app's `.env`. I ran the app and the database on the same subdomain. Nothing worked. When I tried looking for tutorials, there was *nothing* explaining how I could set up what I had in mind.
How could this be? I'm doing web development 101 here! This isn't some advanced toolchain I'm using, it's the bare minimum for a working application with a backend! Somehow, no resource I could find covered this. Any website I read and any video I watched only ever ran those services locally. Docker containers were spun up on the development client, the client apps were run in development mode (which disables CORS/CSRF security features), and there was never a *live* example given.
### Stupid Gaslighting AI Slop
These days, when looking up tech problems in particular, most of the online resources are AI-generated slop. It was genuinely extremely difficult to avoid them, as they were not just prevalent, but almost entirely dominating the search results. There was this one page that I particularly despised. The worst part? It was actually several pages.
If you stumble upon a page such as `w3tutorials.net` or `tutorialpedia.org`, *run*. Do not take in their information. Avoid at all costs. In fact, these pages both use the very same CSS styling with their dark blue background and fade-in title animation upon page load, and you will find even more domains using this exact same template.
But why run? Is it really that bad? Maybe the information is useful?
I'm going to prove why you should avoid generative AI at all costs. Let's go through the article I read: "How to Fix 'Cross-site POST Form Submissions Are Forbidden' Error in SvelteKit: Dev Server Works, Build Fails". Extremely long title, but it in fact describes my exact problem. Four 'fixes' are given:
1. Use Relative Form Actions
This 'fix' describes using relative paths to access form URLs. Instead of `https://app.natconf.dev/customers`, I should just use `/customers`.
This doesn't work.
2. Include the CSRF Token
This 'fix' explains that the issue may be caused by the lack of a CSRF token included in the form that would verify the validity of the submission. The solution is to import the token into the app using `import { csrfToken } from '$app/stores';` and then including the token in a hidden `<input>` named `_csrf`.
*What in the hell* did this AI smoke to hallucinate this? There is no such thing as an importable CSRF token, and it wouldn't make sense for the solution to just be 'send the token along with the request'. Who's validating the token? How would they know it's valid? You would need server-side validation of the token, which is a real thing, but you'd need to code that yourself or use a library for it. You can't just send a random string and say "this request is real, here's some random text to prove it".
3. Configure `ORIGIN` and Environment Variables
Ignoring the fact that their markdown styling doesn't work correctly and shows the \` marks on text that is supposed to be monospaced, this 'fix' alleges that setting the `ORIGIN` environment variable needs to be set to the domain the application is running on so that SvelteKit is aware of this domain.
This *may* work? SvelteKit documentation does [make mention of an `ORIGIN` variable](https://svelte.dev/docs/kit/adapter-node#Environment-variables-ORIGIN-PROTOCOL_HEADER-HOST_HEADER-and-PORT_HEADER) used in the Node adapter. However, as far as I could tell, this had no effect on my app.
4. Check Server Hooks (Handle Function)
This 'fix' accuses you, the developer, of explicitly deleting the required headers for CSRF validation using code such as: `delete event.request.headers.origin;`. *Excuse me?* You little piece of shit. How dare you accuse me of something like this? No, of course I haven't explicitly deleted the headers from my request!
Actually, now that I think about it, this is a very odd thing for an AI to produce. Usually, they are overly positive towards the user. I guess this one was still positively phrased and only indirectly accusing the reader of the mistake. Still, the implication is ridiculous.
### No Surprise Stack Overflow is Dying
Alas, I couldn't find anything helping my cause. In my most desperate moment, I came up with an idea I never thought I'd pursue: *asking a question on Stack Overflow*. **THE** Stack Overflow! The place where arrogant people get high on the most minuscule amounts of power or so I've heard. It can't be that bad.
I wasn't entirely sure whether my problem lied in my SvelteKit, Nginx, or PostgreSQL setup, so I documented them briefly but with the necessary detail in my question. I described my plan and my previous attempts at fixing the issue at hand, including links to the resources I had used. I formatted the entire thing and structured it so that it would be relatively simple to digest. It also had a concluding question to summarise my problem at the end. I read their entire stupid article on 'writing a good question'. What a ridiculous article.
I submitted it to the Staging Ground, which is apparently a Stack Overflow-exclusive thing where new users are forced (?) to submit their questions for 'experienced' users to judge them (which is such a fucking weird concept) and give advice. Fair enough, I thought, I have a fairly open mind and I'd gladly receive constructive feedback so that others could answer my question better.
What instead happened was that within less than 10 minutes of posting my question, two users deemed my question 'off-topic' with the reason 'this question is not about programming or software development' and closed it before it went public. wow. One of them at least had the decency of giving me the advice to post in either Server Fault or Database Administrators. Seeing that this *definitely* wasn't a database problem, I deemed the latter suggestion misguided at best, but I did try Server Fault instead. I copy-pasted my question, submitted it directly (they had no such thing as a Staging Ground), and proceeded to receive no answers.
The best part: once I figured out the solution to my problem on my own, it became abundantly clear that my problem was caused by SvelteKit. This means:
- the question was actually off-topic on Server Fault
- the question was indeed correct to be posted on Stack Overflow
- whoever judged my question to be 'off-topic' likely only wanted to exercise their virtual 'power' and didn't even bother reading and understanding my question while expecting me to read paragraphs on 'how to ask a good question'
You know what? Stack Overflow can burn in hell. No wonder its user base is continually shrinking. What a horrible, toxic place. I'm saying this mostly because of the things I've *heard* about Stack Overflow, but my short first-hand experience clearly showed that those things are true.

View File

@@ -0,0 +1,32 @@
<p class="lightyears-text">The world looks so different now. The world looks so different now. The world looks so different now.</p>
## Background
One of my favourite music artists, [Jaron](https://youtu.be/GXvqQ5-P82I), released his album LIGHTYEARS a little over a year ago. For his visuals, he uses a variety of symbols in place of Latin letters, and there's a converter on [his website](https://jaronsteele.com/) too. Only problem is that my browser can't seem to display most of the characters because the characters aren't included in most fonts.
That's why I made a font! It allows you to type Latin characters from `A-Z` as well as numbers `0-9` and `!?` in the LIGHTYEARS style. Like this:
<p class="lightyears-text">trans rights!</p>
The font exclusively uses characters from the Noto font family. Many of the Noto varieties have been stitched together to recreate the whole LIGHTYEARS alphabet.
To create the font, I used [FontForge](https://fontforge.org). Finding this tool was both a blessing and a curse, as it was exactly what I needed, but it kept. crashing. all. the. time. I tried both the AppImage as well as the release on `dnf` and both had the same issues. I managed to make it work, but it took a lot of patience. Eventually I figured out that importing Noto Maths gave me a 3-8 second window before the editor crashed. The project file would forget the imported font, but if I had copied any glyphs it would keep those.
## Download & use
[Download the font here](https://files.natconf.dev/public/lightyears.woff2). It's in the web-optimised `woff2` format and has most characters stripped to minimise its file size it's less than 20 kilobytes in size! Uppercase and lowercase letters are the same.
For use on your website, put the font into your resources/static/similar folder and then add this block of code to your CSS file:
<pre class="code-block">
@font-face &#123;
font-family: "LIGHTYEARS";
src: url("/fonts/lightyears.woff2");
font-weight: 400;
font-style: normal;
}
</pre>
Then you can change any element's font by setting `font-family: 'LIGHTYEARS', sans-serif;`. Because it's the Latin characters and Arabic numbers that have been changed, you can type text in regular English and people can 'decrypt' the messages by copy-pasting the text somewhere else!
You do **not** need Jaron's converter to type stylised text! The converter returns the actual symbols from other scripts, whereas this font only changes how letters look.

View File

@@ -0,0 +1,7 @@
I'm switching my website domain from `denizk0461.dev` to `natconf.dev`!
All I can say for now about this change is that I came up with this domain name a while back and I liked it more so than `denizk0461.dev` , so I've been thinking about migrating.
I'm updating things as I go. All links on this website now point to the new domain, and all services have been moved over too. As of right now, they're also still accessible via the old domain, but I will be disabling that soon. Instead, I'll set up redirects, which will be in place until the domain expires on 2026-06-04.
I *may* change up some visual elements in the process, but it won't be a major redesign.

View File

@@ -4,7 +4,7 @@ import { posts } from "../posts";
const xml = () => `<rss version="2.0"> const xml = () => `<rss version="2.0">
<channel> <channel>
<title>denizk0461's Blog</title> <title>denizk0461's Blog</title>
<link>https://denizk0461.dev/blog/</link> <link>https://natconf.dev/blog/</link>
<description><![CDATA[denizk0461 blogs about stuff here]]></description>${getEntries()} <description><![CDATA[denizk0461 blogs about stuff here]]></description>${getEntries()}
</channel> </channel>
</rss>`; </rss>`;
@@ -16,8 +16,8 @@ function getEntries(): String {
<item> <item>
<title><![CDATA[${entry.post.title}]]></title> <title><![CDATA[${entry.post.title}]]></title>
<description><![CDATA[${entry.post.description}]]></description> <description><![CDATA[${entry.post.description}]]></description>
<link>https://denizk0461.dev/blog/${entry.key}</link> <link>https://natconf.dev/blog/${entry.key}</link>
<guid isPermaLink="true">https://denizk0461.dev/blog/${entry.key}</guid> <guid isPermaLink="true">https://natconf.dev/blog/${entry.key}</guid>
<pubDate><![CDATA[${new Date(`${entry.post.date}T${entry.post.time}`).toUTCString()}]]></pubDate> <pubDate><![CDATA[${new Date(`${entry.post.date}T${entry.post.time}`).toUTCString()}]]></pubDate>
</item>`) </item>`)
entries.forEach(entry => { entries.forEach(entry => {

View File

@@ -23,6 +23,39 @@ export interface BlogPostLink {
export const posts: BlogPostLink[] = [ export const posts: BlogPostLink[] = [
{
key: "2026/0326",
post: {
date: "2026-03-26",
time: "20:50",
banner: "banner.webp",
bannerAlt: "White light blurs on a darker background.",
title: "Moving On",
description: "It's time to switch domains.",
}
},
{
key: "2026/0325",
post: {
date: "2026-03-25",
time: "22:22",
banner: "banner.webp",
bannerAlt: "A sunset captured from an Autobahn exit.",
title: "I made a LIGHTYEARS font",
description: "I feel electric and it's only getting brighter!",
}
},
{
key: "2026/0317",
post: {
date: "2026-03-17",
time: "17:00",
banner: "banner.webp",
bannerAlt: "A Microsoft Surface Pro 8 displaying a Blue Screen of Death.",
title: "How To: Set Up SvelteKit Frontend + PostgreSQL Backend",
description: "How to set up a web application with a backend on a remote server without getting error 403.",
}
},
{ {
key: "2026/0214", key: "2026/0214",
post: { post: {

View File

@@ -78,7 +78,7 @@
}, },
{ {
text: "Bluesky", text: "Bluesky",
link: "https://bsky.app/profile/denizk0461.dev", link: "https://bsky.app/profile/natconf.dev",
}, },
{ {
text: "Codeberg", text: "Codeberg",
@@ -151,7 +151,7 @@
<img alt="Screenshot of the taskbar of a Fedora KDE setup. There are multiple icons. From left to right: Clank as the application launcher icon, Firefox, fooyin music player, Dolphin file explorer." src="taskbar.webp"> <img alt="Screenshot of the taskbar of a Fedora KDE setup. There are multiple icons. From left to right: Clank as the application launcher icon, Firefox, fooyin music player, Dolphin file explorer." src="taskbar.webp">
<p>As for the server infrastructure: the website is hosted on a Hetzner server instance I am renting. It's a relatively cheap CPX22 node that costs me 7,72€ a month, and besides my website, it's also hosting things such as a Nextcloud instance and a Minecraft server. In order to host and update the website, I wrote a small bash script that pulls the changes from the <a href="https://code.denizk0461.dev/denizk0461/pages">Git repository</a>, builds the website as a Node server, and then exposes it via Nginx.</p> <p>As for the server infrastructure: the website is hosted on a Hetzner server instance I am renting. It's a relatively cheap CPX22 node that costs me 7,72€ a month, and besides my website, it's also hosting things such as a Nextcloud instance and a Minecraft server. In order to host and update the website, I wrote a small bash script that pulls the changes from the <a href="https://code.natconf.dev/denizk0461/pages">Git repository</a>, builds the website as a Node server, and then exposes it via Nginx.</p>
<h4 id="software">Tools & Software</h4> <h4 id="software">Tools & Software</h4>
@@ -169,7 +169,7 @@
<p>The <a href="https://ratchetandclank.fandom.com/wiki/Ratchet">rat</a> in the bottom right of the screen is property of <a href="https://insomniac.games/">Insomniac Games</a>. Clicking it will bring you good fortune.</p> <p>The <a href="https://ratchetandclank.fandom.com/wiki/Ratchet">rat</a> in the bottom right of the screen is property of <a href="https://insomniac.games/">Insomniac Games</a>. Clicking it will bring you good fortune.</p>
<p>The style of the webring elements is adapted from a template provided by Rainbow Cemetery for the <a href="https://www.rainbowcemetery.com/devring/">Gamedev webring</a>. I adapted it into <a href="https://code.denizk0461.dev/denizk0461/pages/src/branch/master/src/lib/components/ring.svelte">a Svelte component</a> that allows setting the links, emojis, and arrows more easily.</p> <p>The style of the webring elements is adapted from a template provided by Rainbow Cemetery for the <a href="https://www.rainbowcemetery.com/devring/">Gamedev webring</a>. I adapted it into <a href="https://code.natconf.dev/denizk0461/pages/src/branch/master/src/lib/components/ring.svelte">a Svelte component</a> that allows setting the links, emojis, and arrows more easily.</p>
<h2 id="contact">Contact / Where to find me</h2> <h2 id="contact">Contact / Where to find me</h2>
@@ -177,7 +177,7 @@
<LinkList entries={links} /> <LinkList entries={links} />
<p>I removed my Discord username because I don't want to use Discord anymore, now that they <a href="https://www.zdnet.com/article/discord-age-verification-requirement/">ask for facial data and/or government ID that they force to be scanned by third-party AI</a> and then <a href="https://arstechnica.com/tech-policy/2026/02/discord-faces-backlash-over-age-checks-after-data-breach-exposed-70000-ids/">leak it...</a> Apparently they are also about to be <a href="https://www.zdnet.com/article/microsoft-may-be-poised-to-buy-its-next-community-discord/">bought up by Micro$lop</a>?? Discord is as good as dead to me.</p> <p>I removed my Discord username because I don't want to use Discord anymore, now that they <a href="https://www.zdnet.com/article/discord-age-verification-requirement/">ask for facial data and/or government ID that they force to be scanned by third-party AI</a> and then <a href="https://arstechnica.com/tech-policy/2026/02/discord-faces-backlash-over-age-checks-after-data-breach-exposed-70000-ids/">leak it...</a> Apparently they are also about to be <a href="https://www.zdnet.com/article/microsoft-may-be-poised-to-buy-its-next-community-discord/">bought up by Micro$lop</a>?? I prefer not to use Discord anymore, although I will admit I don't have any good alternative at the moment.</p>
<h2>Anything else?</h2> <h2>Anything else?</h2>
@@ -201,7 +201,7 @@
<hr> <hr>
<p><i>Last updated: 2026-02-25</i></p> <p><i>Last updated: 2026-03-26</i></p>
</Content> </Content>
<style> <style>

View File

@@ -13,7 +13,7 @@
<p>This page uses <b>no cookies</b> as of now. No data will be stored on your device while browsing this website. <b>No trackers</b> are used either <b>no analytics</b>, not even a visit counter of any kind. Not by a third-party, and currently, none I built myself either.</p> <p>This page uses <b>no cookies</b> as of now. No data will be stored on your device while browsing this website. <b>No trackers</b> are used either <b>no analytics</b>, not even a visit counter of any kind. Not by a third-party, and currently, none I built myself either.</p>
<p>The Godot and Unity projects on the <code>apps.denizk0461.dev</code> subdomain <i>may</i> cache compiled shaders on your device, I'm not sure. These files would only be used by your GPU to render visual effects for the game they were compiled for.</p> <p>The Godot and Unity projects on the <code>apps.natconf.dev</code> subdomain <i>may</i> cache compiled shaders on your device, I'm not sure. These files would only be used by your GPU to render visual effects for the game they were compiled for.</p>
<p>Last updated: 2025-09-10</p> <p>Last updated: 2025-09-10</p>
</Content> </Content>

View File

@@ -1,6 +1,24 @@
import { type UpdateEntry } from "$lib/components/update-entry.svelte"; import { type UpdateEntry } from "$lib/components/update-entry.svelte";
export const entries: UpdateEntry[] = [ export const entries: UpdateEntry[] = [
{
date: "2026-03-26",
time: "20:50",
content: "<b>Important</b>: my website is changing domains.",
link: "/blog/2026/0326",
},
{
date: "2026-03-25",
time: "22:22",
content: "I made a LIGHTYEARS font!",
link: "/blog/2026/0325",
},
{
date: "2026-03-17",
time: "17:10",
content: "a bit uncharacteristic, but I wrote a guide on setting up a SvelteKit app + backend because I found NOTHING of the sort online.",
link: "/blog/2026/0317",
},
{ {
date: "2026-03-11", date: "2026-03-11",
time: "19:21", time: "19:21",
@@ -57,7 +75,7 @@ export const entries: UpdateEntry[] = [
date: "2026-02-03", date: "2026-02-03",
time: "15:48", time: "15:48",
content: "Now running my own Gitea instance! It now also hosts my website repository.", content: "Now running my own Gitea instance! It now also hosts my website repository.",
link: "https://code.denizk0461.dev/denizk0461/pages", link: "https://code.natconf.dev/denizk0461/pages",
}, },
{ {
date: "2026-02-02", date: "2026-02-02",

View File

@@ -4,19 +4,19 @@
let builds: LinkEntry[] = [ let builds: LinkEntry[] = [
{ {
text: "2023-10-07 (Protagonist #1)", text: "2023-10-07 (Protagonist #1)",
link: "https://files.denizk0461.dev/public/projectn5/2023-10-07.zip", link: "https://files.natconf.dev/public/projectn5/2023-10-07.zip",
}, },
{ {
text: "2023-12-23 (Protagonist #2)", text: "2023-12-23 (Protagonist #2)",
link: "https://files.denizk0461.dev/public/projectn5/2023-12-23.zip", link: "https://files.natconf.dev/public/projectn5/2023-12-23.zip",
}, },
{ {
text: "2024-03-25 (Protagonist #3 with jump animations)", text: "2024-03-25 (Protagonist #3 with jump animations)",
link: "https://files.denizk0461.dev/public/projectn5/2024-03-25.zip", link: "https://files.natconf.dev/public/projectn5/2024-03-25.zip",
}, },
{ {
text: "2025-08-16 (Laura era) [same build as the web version]", text: "2025-08-16 (Laura era) [same build as the web version]",
link: "https://files.denizk0461.dev/public/projectn5/2025-08-16.zip", link: "https://files.natconf.dev/public/projectn5/2025-08-16.zip",
}, },
]; ];
</script> </script>
@@ -33,9 +33,9 @@ The good news is that I'm done with all of this! The bachelor's thesis in partic
I decided to upload some playable builds of **Project N5**! Now that the website is running on my own server instead of being hosted by GitHub or Codeberg, I have a lot more freedom here. I decided to upload some playable builds of **Project N5**! Now that the website is running on my own server instead of being hosted by GitHub or Codeberg, I have a lot more freedom here.
The game, in its state from 2025-05-16 (before the reboot), is available to play in-browser [right here!](https://apps.denizk0461.dev/projectn5) It's not a terribly great experience, though. Loading times are significantly longer and shader compilation regularly freezes the game for longer than in a locally-saved copy. Some shaders are also not functioning as intended, though this only has a minor visual impact. The game was never optimised to work on the web, after all. The game, in its state from 2025-05-16 (before the reboot), is available to play in-browser [right here!](https://apps.natconf.dev/projectn5) It's not a terribly great experience, though. Loading times are significantly longer and shader compilation regularly freezes the game for longer than in a locally-saved copy. Some shaders are also not functioning as intended, though this only has a minor visual impact. The game was never optimised to work on the web, after all.
I've also uploaded old builds of the game [here](https://files.denizk0461.dev/public/projectn5). You'll find the following builds, one for each protagonist: I've also uploaded old builds of the game [here](https://files.natconf.dev/public/projectn5). You'll find the following builds, one for each protagonist:
<LinkList entries={builds} /> <LinkList entries={builds} />

View File

@@ -4,7 +4,7 @@ import { posts } from "../posts";
const xml = () => `<rss version="2.0"> const xml = () => `<rss version="2.0">
<channel> <channel>
<title>Project N5 Devlog</title> <title>Project N5 Devlog</title>
<link>https://denizk0461.dev/projects/projectn5/devlog/</link> <link>https://natconf.dev/projects/projectn5/devlog/</link>
<description><![CDATA[Development log for the game Project N5 by denizk0461]]></description>${getEntries()} <description><![CDATA[Development log for the game Project N5 by denizk0461]]></description>${getEntries()}
</channel> </channel>
</rss>`; </rss>`;
@@ -16,8 +16,8 @@ function getEntries(): String {
<item> <item>
<title><![CDATA[${entry.post.title}]]></title> <title><![CDATA[${entry.post.title}]]></title>
<description><![CDATA[${entry.post.description}]]></description> <description><![CDATA[${entry.post.description}]]></description>
<link>https://denizk0461.dev/projects/projectn5/devlog/${entry.key}</link> <link>https://natconf.dev/projects/projectn5/devlog/${entry.key}</link>
<guid isPermaLink="true">https://denizk0461.dev/projects/projectn5/devlog/${entry.key}</guid> <guid isPermaLink="true">https://natconf.dev/projects/projectn5/devlog/${entry.key}</guid>
<pubDate><![CDATA[${new Date(entry.post.date).toUTCString()}]]></pubDate> <pubDate><![CDATA[${new Date(entry.post.date).toUTCString()}]]></pubDate>
</item>`) </item>`)
entries.forEach(entry => { entries.forEach(entry => {

View File

@@ -78,11 +78,11 @@ export const games: Project[] = [
}, },
{ {
text: "Play an <b>old web build</b> (developed until 2025-05-16)", text: "Play an <b>old web build</b> (developed until 2025-05-16)",
link: "https://apps.denizk0461.dev/projectn5", link: "https://apps.natconf.dev/projectn5",
}, },
{ {
text: "Download the <b>old Windows builds</b>", text: "Download the <b>old Windows builds</b>",
link: "https://files.denizk0461.dev/public/projectn5", link: "https://files.natconf.dev/public/projectn5",
}, },
], ],
status: ProjectStatus.ACTIVE, status: ProjectStatus.ACTIVE,
@@ -102,7 +102,7 @@ export const games: Project[] = [
links: [ links: [
{ {
text: "View the latest <b>Magician</b> build", text: "View the latest <b>Magician</b> build",
link: "https://apps.denizk0461.dev/magician", link: "https://apps.natconf.dev/magician",
}, },
], ],
status: ProjectStatus.ABANDONED, status: ProjectStatus.ABANDONED,
@@ -139,7 +139,7 @@ export const games: Project[] = [
links: [ links: [
{ {
text: "Play <b>Swords & Stuff</b>", text: "Play <b>Swords & Stuff</b>",
link: "https://apps.denizk0461.dev/swordsnstuff", link: "https://apps.natconf.dev/swordsnstuff",
}, },
], ],
status: ProjectStatus.ABANDONED, status: ProjectStatus.ABANDONED,
@@ -159,11 +159,11 @@ export const games: Project[] = [
links: [ links: [
{ {
text: "Play <b>TADS 1</b>", text: "Play <b>TADS 1</b>",
link: "https://apps.denizk0461.dev/tads/1", link: "https://apps.natconf.dev/tads/1",
}, },
{ {
text: "Play <b>TADS 2</b>", text: "Play <b>TADS 2</b>",
link: "https://apps.denizk0461.dev/tads/2", link: "https://apps.natconf.dev/tads/2",
}, },
], ],
status: ProjectStatus.FINISHED, status: ProjectStatus.FINISHED,
@@ -189,7 +189,7 @@ export const hardware: Project[] = [
}, },
{ {
text: "Get the <b>PCB and STL files</b>", text: "Get the <b>PCB and STL files</b>",
link: "https://files.denizk0461.dev/public/daisyfm/", link: "https://files.natconf.dev/public/daisyfm/",
}, },
{ {
text: "View the code files on <b>Codeberg</b>", text: "View the code files on <b>Codeberg</b>",
@@ -317,7 +317,7 @@ export const music: Project[] = [
links: [ links: [
{ {
text: "Listen & download on my <b>copyparty</b> instance", text: "Listen & download on my <b>copyparty</b> instance",
link: "https://files.denizk0461.dev/public/my_tracks/Dreamworld/", link: "https://files.natconf.dev/public/my_tracks/Dreamworld/",
}, },
], ],
status: ProjectStatus.FINISHED, status: ProjectStatus.FINISHED,
@@ -337,7 +337,7 @@ export const music: Project[] = [
links: [ links: [
{ {
text: "Listen & download on my <b>copyparty</b> instance", text: "Listen & download on my <b>copyparty</b> instance",
link: "https://files.denizk0461.dev/public/my_tracks/A%20New%20Beginning/", link: "https://files.natconf.dev/public/my_tracks/A%20New%20Beginning/",
}, },
], ],
status: ProjectStatus.FINISHED, status: ProjectStatus.FINISHED,

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.