Compare commits

..

32 Commits

Author SHA1 Message Date
d9045f23c2 added blog post about changing domains 2026-03-26 20:51:13 +01:00
816f9cefec replaced links to this website with natconf.dev 2026-03-26 20:21:39 +01:00
94416f5779 edited about page 2026-03-26 11:11:55 +01:00
7ab4a9a173 edited the button corner on the main page 2026-03-25 22:33:04 +01:00
2d052acf91 added warning banner to outdated postgresql blog post 2026-03-25 22:29:26 +01:00
5dee4fccc1 added new blog post 2026-03-25 22:18:57 +01:00
d0d74becc5 added subtitle to main page 2026-03-25 21:44:49 +01:00
0911a8892f added update about blog post 2026-03-17 17:10:56 +01:00
c95fd2a101 edited styling of code blocks to improve legibility 2026-03-17 17:06:59 +01:00
e118a71f76 added blog post 0317 2026-03-17 17:03:47 +01:00
3c47cbe581 added 88x31 buttons to main page 2026-03-14 22:22:39 +01:00
8c00b77d4e slight text edits to blog post 2026-03-14 17:54:25 +01:00
070e50e718 updated page title on Homesick page where it previously announced the page as Project N5 2026-03-14 10:45:47 +01:00
757510744f updated links to copyparty public share 2026-03-14 09:32:36 +01:00
c63aeabb6c added that homesick was previously called project n5 to the project page. should have done this before 2026-03-11 19:48:06 +01:00
4ff75057da renamed project n5 to homesick on main page 2026-03-11 19:33:44 +01:00
5558e3cf3b added update to link to drawing page 2026-03-11 19:22:34 +01:00
a186a0e4bc changed Project N5 references in some places to Homesick 2026-03-11 18:57:19 +01:00
e094d68dfa removed project n5 link from header; added homesick and drawing page links to footer 2026-03-11 18:54:33 +01:00
72b9d15381 adjusted gallery to scale down on mobile devices 2026-03-11 18:38:32 +01:00
013c6e38c7 added drawing to drawing gallery; added alt texts to all drawings 2026-03-10 23:17:24 +01:00
4683233272 removed redundant png screenshot from devlog files 2026-03-09 22:07:13 +01:00
b88908d7bf added devlog 2026/0309 2026-03-09 22:06:49 +01:00
a9756725da added link to art page to main page; added appropriate banner to art page; changed banner of project n5 page on main page to match the actual page banner 2026-03-09 17:40:30 +01:00
71010be02c added art page link to header and footer; removed unrelevant banner from art page 2026-03-09 17:21:54 +01:00
1f67da8996 added art page that links to its subpages 2026-03-09 17:17:07 +01:00
e1d8b1b5a4 made drawing gallery mobile-friendlier 2026-03-09 16:47:06 +01:00
7ecc34b762 finished adding content to drawing gallery for now 2026-03-09 16:43:26 +01:00
f2511470c5 moved discography page to art folder and added blurb to drawing gallery page 2026-03-09 16:13:08 +01:00
72a7515120 added drawing gallery page, not linked anywhere yet 2026-03-09 16:06:56 +01:00
6b42760737 removed redundant reverseTextOrder property from gallery 2026-03-07 20:37:37 +01:00
103b153dc0 show overflow on gallery entries to prevent text cutoff on hover 2026-03-07 15:01:57 +01:00
50 changed files with 1005 additions and 151 deletions

View File

@@ -1,5 +1,5 @@
# 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)

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { type IndieButton } from "./indie-button";
let {
button
}: {
button: IndieButton;
} = $props();
</script>
<a class="button" href="{button.link}">
<img src="/common/buttons/{button.img}" alt="{button.alt}">
</a>
<style>
.button {
width: 88px;
height: 31px;
transition: scale var(--duration-animation) var(--anim-curve);
margin: 0;
}
.button:hover {
scale: 1.2;
z-index: 10;
}
.button img {
height: 100%;
width: 100%;
}
</style>

View File

@@ -0,0 +1,26 @@
export interface IndieButton {
img: string;
alt: string;
link?: string;
}
export let buttons: IndieButton[] = [
{
img: "iso8601.png",
alt: "A button with the text 'ISO 8601 YYYY-MM-DD'.",
link: "https://www.iso8601.com/",
},
{
img: "queercoded.png",
alt: "A button with the text \"you're telling me a queer coded this\" on a rainbow background.",
},
{
img: "madewithsveltekit.gif",
alt: "A red button with the text 'MADE WITH SVELTEKIT'.",
link: "https://svelte.dev",
},
{
img: "trans-rights-now.png",
alt: "A button with the text 'TRANS RIGHTS NOW!' next to a trans flag.",
},
];

View File

@@ -9,6 +9,7 @@
alignment,
video,
pixelated,
smaller,
}: {
image: string;
altText?: string;
@@ -16,6 +17,7 @@
alignment?: string;
video?: boolean;
pixelated?: boolean;
smaller?: boolean;
} = $props();
</script>
@@ -39,18 +41,37 @@
{/if}
{/snippet}
<!-- this structure is ugly as fuck. there must be a better way of doing this -->
{#if alignment && alignment == "left"}
<a class="subtitled-img-container subtitled-img-container-left" href="{image}">
{@render subtitledImageContent()}
</a>
{#if smaller}
<a class="subtitled-img-container subtitled-img-container-left subtitled-img-reduced-size" href="{image}">
{@render subtitledImageContent()}
</a>
{:else}
<a class="subtitled-img-container subtitled-img-container-left" href="{image}">
{@render subtitledImageContent()}
</a>
{/if}
{:else if alignment && alignment == "right"}
<a class="subtitled-img-container subtitled-img-container-right" href="{image}">
{@render subtitledImageContent()}
</a>
{#if smaller}
<a class="subtitled-img-container subtitled-img-container-right subtitled-img-reduced-size" href="{image}">
{@render subtitledImageContent()}
</a>
{:else}
<a class="subtitled-img-container subtitled-img-container-right" href="{image}">
{@render subtitledImageContent()}
</a>
{/if}
{:else}
<a class="subtitled-img-container subtitled-img-container-centred" href="{image}">
{@render subtitledImageContent()}
</a>
{#if smaller}
<a class="subtitled-img-container subtitled-img-container-centred subtitled-img-reduced-size" href="{image}">
{@render subtitledImageContent()}
</a>
{:else}
<a class="subtitled-img-container subtitled-img-container-centred" href="{image}">
{@render subtitledImageContent()}
</a>
{/if}
{/if}
<style>
@@ -66,6 +87,7 @@
.subtitled-img-container-centred {
width: var(--media-width);
/* width: fit-content; */
margin-left: auto;
margin-right: auto;
}
@@ -112,6 +134,10 @@
margin-left: auto;
margin-right: auto;
}
.subtitled-img-reduced-size {
max-width: initial;
}
}
@media screen and (min-width: 600px) {
@@ -119,6 +145,10 @@
width: 34%;
}
.subtitled-img-reduced-size {
max-width: 20%;
}
/* Don't limit height of images set to the side because text flows around them */
.subtitled-img-container-left img, .subtitled-img-container-right img {
max-height: initial;

View File

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

View File

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

View File

@@ -10,21 +10,18 @@
let {
entries,
reverseTextOrder,
}: {
entries: GalleryEntry[];
reverseTextOrder?: boolean;
} = $props();
</script>
<div class="post-list">
{#each entries as entry}
{@render galleryEntry({entry, reverseTextOrder})}
{@render galleryEntry({entry})}
{/each}
</div>
{#snippet galleryEntry({entry, reverseTextOrder}: {entry: GalleryEntry, reverseTextOrder?: boolean})}
<!-- {#snippet galleryEntry({key, post, index}: {key: string, post: DevlogPost, index: number})} -->
{#snippet galleryEntry({entry}: {entry: GalleryEntry})}
<a class="gallery-container" href="{entry.link}">
{#if entry.img && entry.img !== ""}
<img class="gallery-img" src="{entry.img}" alt="{entry.imgAlt}">
@@ -32,15 +29,9 @@
<div class="gallery-img-placeholder"></div>
{/if}
<div class="gallery-text-container">
{#if reverseTextOrder}
<p class="gallery-subtitle">{@html entry.subtitle}</p>
<p class="gallery-title">{entry.title}</p>
<p class="gallery-description">{entry.description}</p>
{:else}
<p class="gallery-title">{entry.title}</p>
<p class="gallery-subtitle">{@html entry.subtitle}</p>
<p class="gallery-description">{entry.description}</p>
{/if}
<p class="gallery-subtitle">{@html entry.subtitle}</p>
<p class="gallery-title">{entry.title}</p>
<p class="gallery-description">{entry.description}</p>
</div>
</a>
{/snippet}
@@ -88,7 +79,6 @@
grid-auto-columns: 1fr;
grid-template-rows: 1fr 1fr 0fr;
align-items: center;
overflow: hidden;
flex-grow: 1;
padding: 0 16px;
transition: background-color var(--duration-animation) var(--anim-curve),
@@ -105,8 +95,6 @@
.gallery-title {
font-family: var(--font-mono);
font-weight: 700;
font-size: 1.4rem;
line-height: 2.0rem;
}
.gallery-subtitle, .gallery-description {
@@ -125,43 +113,43 @@
background-color: var(--color-background-highlight);
backdrop-filter: blur(var(--blur-radius-background));
}
.gallery-container:hover .gallery-img, .gallery-container:hover .gallery-img-placeholder {
/* filter: grayscale(0%); */
/* margin-left: 8px; */
width: 260px;
}
.gallery-container:hover .gallery-text-container {
/* padding-right: 8px; */
grid-template-rows: 0fr 1fr 1fr;
}
.gallery-container:hover p {
color: var(--color-highlight);
}
.gallery-container:hover .gallery-subtitle {
opacity: 0;
}
.gallery-container:hover .gallery-description {
opacity: 1;
}
@media screen and (max-width: 700px) {
/* .gallery-title {
font-size: 1.0rem;
line-height: 1.1rem;
@media screen and (min-width: 800px) {
.gallery-title {
font-size: 1.4rem;
line-height: 2.0rem;
}
.gallery-subtitle {
font-size: 0.9rem;
line-height: 1.1rem;
} */
}
@media screen and (max-width: 500px) {
.gallery-container:hover .gallery-img, .gallery-container:hover .gallery-img-placeholder {
width: 260px;
}
.gallery-container:hover .gallery-text-container {
grid-template-rows: 0fr 1fr 1fr;
}
.gallery-container:hover .gallery-subtitle {
opacity: 0;
}
.gallery-container:hover .gallery-description {
opacity: 1;
}
}
@media screen and (max-width: 800px) {
.gallery-title {
font-size: 1.0rem;
line-height: 1.1rem;
}
.gallery-description {
display: none;
}
.gallery-subtitle {
font-size: 0.8rem;
line-height: 1rem;
@@ -169,7 +157,7 @@
}
.gallery-container {
height: 64px;
height: 72px;
}
.gallery-text-container {
@@ -177,8 +165,8 @@
}
.gallery-img, .gallery-img-placeholder {
width: 100px;
min-width: 100px;
width: 110px;
min-width: 110px;
}
}
</style>

View File

@@ -20,19 +20,22 @@
<div class="content-container">
<div class="content-box center-box">
<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 class="content-box">
<h6>Content</h6>
<a href="/projects">Projects</a>
<a class="link-level-2" href="/projects/projectn5">Homesick</a>
<a href="/blog">Blog</a>
<a href="/art">Art</a>
<a class="link-level-2" href="/art/drawings">Drawings</a>
</div>
<div class="content-box">
<h6>Meta</h6>
<a href="/meta/about">About</a>
<a href="/meta/feeds">Feeds</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>
</div>
</div>
@@ -66,6 +69,14 @@
margin: 0;
}
.link-level-2 {
padding-left: 16px;
}
.link-level-2::before {
content: '└ ';
}
footer a:hover {
font-weight: 700;
text-decoration: var(--border-style) var(--underline-dash-size) var(--color-highlight) underline;

View File

@@ -1,9 +1,8 @@
{#snippet headerLinks()}
<a href="/">Home</a>
<a href="/projects">Projects</a>
<a href="/projects/projectn5">Project N5</a>
<a href="/blog">Blog</a>
<!-- <a href="/garden">Garden</a> -->
<a href="/art">Art</a>
<a href="/meta/about">About</a>
{/snippet}

View File

@@ -97,12 +97,19 @@
font-style: normal;
}
@font-face {
font-family: "LIGHTYEARS";
src: url("/fonts/lightyears.woff2");
font-weight: 400;
font-style: normal;
}
/* OpenMoji */
@font-face {
font-family: "OpenMoji";
src: url("/fonts/openmoji/openmoji.woff2") format("woff2");
unicode-range: U+23,U+2A,U+2D,U+30-39,U+A9,U+AE,U+200D,U+203C,U+2049,U+20E3,U+2117,U+2120,U+2122,U+2139,U+2194-2199,U+21A9,U+21AA,U+229C,U+231A,U+231B,U+2328,U+23CF,U+23E9-23F3,U+23F8-23FE,U+24C2,U+25A1,U+25AA-25AE,U+25B6,U+25C0,U+25C9,U+25D0,U+25D1,U+25E7-25EA,U+25ED,U+25EE,U+25FB-25FE,U+2600-2605,U+260E,U+2611,U+2614,U+2615,U+2618,U+261D,U+2620,U+2622,U+2623,U+2626,U+262A,U+262E,U+262F,U+2638-263A,U+2640,U+2642,U+2648-2653,U+265F,U+2660,U+2663,U+2665,U+2666,U+2668,U+267B,U+267E,U+267F,U+2691-2697,U+2699,U+269B,U+269C,U+26A0,U+26A1,U+26A7,U+26AA,U+26AB,U+26B0,U+26B1,U+26BD,U+26BE,U+26C4,U+26C5,U+26C8,U+26CE,U+26CF,U+26D1,U+26D3,U+26D4,U+26E9,U+26EA,U+26F0-26F5,U+26F7-26FA,U+26FD,U+2702,U+2705,U+2708-270D,U+270F,U+2712,U+2714,U+2716,U+271D,U+2721,U+2728,U+2733,U+2734,U+2744,U+2747,U+274C,U+274E,U+2753-2755,U+2757,U+2763,U+2764,U+2795-2797,U+27A1,U+27B0,U+27BF,U+2934,U+2935,U+2B05-2B07,U+2B0C,U+2B0D,U+2B1B,U+2B1C,U+2B1F-2B24,U+2B2E,U+2B2F,U+2B50,U+2B55,U+2B58,U+2B8F,U+2BBA-2BBC,U+2BC3,U+2BC4,U+2BEA,U+2BEB,U+3030,U+303D,U+3297,U+3299,U+E000-E009,U+E010,U+E011,U+E040-E06D,U+E080-E0B4,U+E0C0-E0CC,U+E0FF-E10D,U+E140-E14A,U+E150-E157,U+E181-E189,U+E1C0-E1C4,U+E1C6-E1D9,U+E200-E216,U+E240-E269,U+E280-E283,U+E2C0-E2C4,U+E2C6-E2DA,U+E300-E303,U+E305-E30F,U+E312-E316,U+E318-E322,U+E324-E329,U+E32B,U+E340-E348,U+E380,U+E381,U+F000,U+F77A,U+F8FF,U+FE0F,U+1F004,U+1F0CF,U+1F10D-1F10F,U+1F12F,U+1F16D-1F171,U+1F17E,U+1F17F,U+1F18E,U+1F191-1F19A,U+1F1E6-1F1FF,U+1F201,U+1F202,U+1F21A,U+1F22F,U+1F232-1F23A,U+1F250,U+1F251,U+1F260-1F265,U+1F300-1F321,U+1F324-1F393,U+1F396,U+1F397,U+1F399-1F39B,U+1F39E-1F3F0,U+1F3F3-1F3F5,U+1F3F7-1F4FD,U+1F4FF-1F53D,U+1F549-1F54E,U+1F550-1F567,U+1F56F,U+1F570,U+1F573-1F57A,U+1F587,U+1F58A-1F58D,U+1F590,U+1F595,U+1F596,U+1F5A4,U+1F5A5,U+1F5A8,U+1F5B1,U+1F5B2,U+1F5BC,U+1F5C2-1F5C4,U+1F5D1-1F5D3,U+1F5DC-1F5DE,U+1F5E1,U+1F5E3,U+1F5E8,U+1F5EF,U+1F5F3,U+1F5FA-1F64F,U+1F680-1F6C5,U+1F6CB-1F6D2,U+1F6D5-1F6D7,U+1F6DC-1F6E5,U+1F6E9,U+1F6EB,U+1F6EC,U+1F6F0,U+1F6F3-1F6FC,U+1F7E0-1F7EB,U+1F7F0,U+1F90C-1F93A,U+1F93C-1F945,U+1F947-1F9FF,U+1FA70-1FA7C,U+1FA80-1FA89,U+1FA8F-1FAC6,U+1FACE-1FADC,U+1FADF-1FAE9,U+1FAF0-1FAF8,U+1FBC5-1FBC9,U+E0061-E0067,U+E0069,U+E006C-E0079,U+E007F;
}
font-family: "OpenMoji";
src: url("/fonts/openmoji/openmoji.woff2") format("woff2");
unicode-range: U+23,U+2A,U+2D,U+30-39,U+A9,U+AE,U+200D,U+203C,U+2049,U+20E3,U+2117,U+2120,U+2122,U+2139,U+2194-2199,U+21A9,U+21AA,U+229C,U+231A,U+231B,U+2328,U+23CF,U+23E9-23F3,U+23F8-23FE,U+24C2,U+25A1,U+25AA-25AE,U+25B6,U+25C0,U+25C9,U+25D0,U+25D1,U+25E7-25EA,U+25ED,U+25EE,U+25FB-25FE,U+2600-2605,U+260E,U+2611,U+2614,U+2615,U+2618,U+261D,U+2620,U+2622,U+2623,U+2626,U+262A,U+262E,U+262F,U+2638-263A,U+2640,U+2642,U+2648-2653,U+265F,U+2660,U+2663,U+2665,U+2666,U+2668,U+267B,U+267E,U+267F,U+2691-2697,U+2699,U+269B,U+269C,U+26A0,U+26A1,U+26A7,U+26AA,U+26AB,U+26B0,U+26B1,U+26BD,U+26BE,U+26C4,U+26C5,U+26C8,U+26CE,U+26CF,U+26D1,U+26D3,U+26D4,U+26E9,U+26EA,U+26F0-26F5,U+26F7-26FA,U+26FD,U+2702,U+2705,U+2708-270D,U+270F,U+2712,U+2714,U+2716,U+271D,U+2721,U+2728,U+2733,U+2734,U+2744,U+2747,U+274C,U+274E,U+2753-2755,U+2757,U+2763,U+2764,U+2795-2797,U+27A1,U+27B0,U+27BF,U+2934,U+2935,U+2B05-2B07,U+2B0C,U+2B0D,U+2B1B,U+2B1C,U+2B1F-2B24,U+2B2E,U+2B2F,U+2B50,U+2B55,U+2B58,U+2B8F,U+2BBA-2BBC,U+2BC3,U+2BC4,U+2BEA,U+2BEB,U+3030,U+303D,U+3297,U+3299,U+E000-E009,U+E010,U+E011,U+E040-E06D,U+E080-E0B4,U+E0C0-E0CC,U+E0FF-E10D,U+E140-E14A,U+E150-E157,U+E181-E189,U+E1C0-E1C4,U+E1C6-E1D9,U+E200-E216,U+E240-E269,U+E280-E283,U+E2C0-E2C4,U+E2C6-E2DA,U+E300-E303,U+E305-E30F,U+E312-E316,U+E318-E322,U+E324-E329,U+E32B,U+E340-E348,U+E380,U+E381,U+F000,U+F77A,U+F8FF,U+FE0F,U+1F004,U+1F0CF,U+1F10D-1F10F,U+1F12F,U+1F16D-1F171,U+1F17E,U+1F17F,U+1F18E,U+1F191-1F19A,U+1F1E6-1F1FF,U+1F201,U+1F202,U+1F21A,U+1F22F,U+1F232-1F23A,U+1F250,U+1F251,U+1F260-1F265,U+1F300-1F321,U+1F324-1F393,U+1F396,U+1F397,U+1F399-1F39B,U+1F39E-1F3F0,U+1F3F3-1F3F5,U+1F3F7-1F4FD,U+1F4FF-1F53D,U+1F549-1F54E,U+1F550-1F567,U+1F56F,U+1F570,U+1F573-1F57A,U+1F587,U+1F58A-1F58D,U+1F590,U+1F595,U+1F596,U+1F5A4,U+1F5A5,U+1F5A8,U+1F5B1,U+1F5B2,U+1F5BC,U+1F5C2-1F5C4,U+1F5D1-1F5D3,U+1F5DC-1F5DE,U+1F5E1,U+1F5E3,U+1F5E8,U+1F5EF,U+1F5F3,U+1F5FA-1F64F,U+1F680-1F6C5,U+1F6CB-1F6D2,U+1F6D5-1F6D7,U+1F6DC-1F6E5,U+1F6E9,U+1F6EB,U+1F6EC,U+1F6F0,U+1F6F3-1F6FC,U+1F7E0-1F7EB,U+1F7F0,U+1F90C-1F93A,U+1F93C-1F945,U+1F947-1F9FF,U+1FA70-1FA7C,U+1FA80-1FA89,U+1FA8F-1FAC6,U+1FACE-1FADC,U+1FADF-1FAE9,U+1FAF0-1FAF8,U+1FBC5-1FBC9,U+E0061-E0067,U+E0069,U+E006C-E0079,U+E007F;
}
/* #endregion */
@@ -120,7 +127,8 @@
--color-header-highlight: color-mix(in srgb, #6d1e26 80%, transparent);
--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-dark: color-mix(in srgb, var(--color-highlight-dark) 60%, transparent);
@@ -154,6 +162,8 @@
--font-mono: 'Kode Mono', 'OpenMoji', monospace;
--font-size-mono: 0.9em;
--font-lightyears: 'LIGHTYEARS', sans-serif;
--font-size-h1: 2.0rem;
--font-size-h2: 1.5rem;
--font-size-h3: 1.3rem;
@@ -164,6 +174,7 @@
/* sizing */
--media-width: 80%;
--width-toc: 650px;
/* page sizing */
--page-width: 1200px;
@@ -234,9 +245,16 @@
color: var(--color-highlight);
margin-top: var(--margin-header-top);
margin-bottom: var(--margin-header-bottom);
}
h1 {
width: fit-content;
}
h2, h3, h4, h5, h6 {
width: 100%;
}
h2 + h3, h3 + h4, h4 + h5, h5 + h6 {
/* Remove top margin when a header immediately preceds another header */
margin-top: 0;
@@ -298,6 +316,11 @@
padding-right: 6px;
}
.code-block {
width: fit-content;
padding: 16px;
}
img, video {
margin-left: auto;
margin-right: auto;
@@ -319,6 +342,10 @@
border: var(--border-dash-size) var(--border-style) var(--color-highlight); */
}
.lightyears-text {
font-family: var(--font-lightyears);
}
.horizontally-centre-aligned {
width: var(--media-width);
display: flex;
@@ -448,5 +475,15 @@
font-family: var(--font-sans-serif);
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>

View File

@@ -1,12 +1,13 @@
<script lang="ts">
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 { posts as devlogPosts } from "./projects/projectn5/devlog/posts";
import { posts as blogPosts } from "./blog/posts";
import { entries as updateEntries } from "./meta/updates/updates";
import UpdateEntry from "$lib/components/update-entry.svelte";
import IndieButton from "$lib/components/indie-button.svelte";
import { buttons } from "$lib/components/indie-button";
let latestDevlogDate = devlogPosts[0].post.date;
let latestBlogDate = blogPosts[0].post.date;
@@ -15,10 +16,10 @@
const galleryTopRow: GalleryRowEntry[] = [
{
title: "Project N5 devlog",
title: "Homesick devlog",
description: `My active Godot game project about finding yourself in an unfamiliar future. <i>latest update: ${latestDevlogDate}</i>`,
img: "projects/projectn5/devlog/2025/0523/birds_eye.webp",
altText: "Project N5 screenshot of Laura looking down at two cuboids.",
img: "projects/projectn5/banner2.webp",
altText: "The protagonist Laura standing on a floating platform in the purple test level. Ziplines are all around her and the text 'When this text is spinning, the game is not paused' is frozen in the sky.",
link: "projects/projectn5",
},
{
@@ -33,24 +34,31 @@
const galleryBottomRow: GalleryRowEntry[] = [
{
title: "Projects",
description: "An overview of what I do and have done",
description: "An overview of my more technical projects",
img: "projects/banner.webp",
altText: "An upside-down New 3DS XL lying open on a desk with a small USB-C breakout board attached to it, and a USB-C cable plugged in. The 3DS is glowing to indicate that it is charging.",
link: "projects",
},
{
title: "Art",
description: "My creative side lives here",
img: "art/banner.webp",
altText: "A rainbow-like holographic effect produced by bending a reflective sheet of cardboard.",
link: "art",
},
{
title: "Files",
description: "Find things I've put for download on my Copyparty instance",
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.",
link: "https://files.denizk0461.dev/",
link: "https://files.natconf.dev/public/",
},
{
title: "Gitea",
description: "I now also self-host a Gitea instance where I am likely migrating all my projects to",
img: "main/magic.webp",
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>
@@ -62,10 +70,12 @@
<Content>
<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>
<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!">
<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>
@@ -91,34 +101,45 @@
<GalleryRow entries={galleryTopRow} />
<GalleryRow entries={galleryBottomRow} />
<hr>
<div class="split-container">
<div class="webring-container">
<h4 class="update-header">webrings</h4>
<div class="webring">
<iframe title="bucket webring" id="bucket-webring" style="width: 100%; height: 3rem; border: none;" src="https://webring.bucketfish.me/embed.html?name=denizk0461"></iframe>
</div>
<div class="webring-container">
<div class="webring">
<iframe title="bucket webring" id="bucket-webring" style="width: 100%; height: 3rem; border: none;" src="https://webring.bucketfish.me/embed.html?name=denizk0461"></iframe>
<div class="webring">
<a href="https://www.rainbowcemetery.com/devring/prev.php?id=18">
<img src="/webrings/gamedev/ringprev.png" alt="previous">
</a>
<a href="https://www.rainbowcemetery.com/devring/list.php?id=18">
<img src="/webrings/gamedev/88x31.png" alt="list">
</a>
<a href="https://www.rainbowcemetery.com/devring/next.php?id=18">
<img src="/webrings/gamedev/ringnext.png" alt="next">
</a>
</div>
<map name="w95widget">
<area href="https://baccyflap.com/noai" target="_blank" shape="rect" coords="0,0,308,22" alt="no ai webring" title="no ai webring">
<area href="https://baccyflap.com/noai/?prv&s=dzk" target="_top" shape="rect" coords="56,36,130,58" alt="previous" title="previous">
<area href="https://baccyflap.com/noai/?rnd" target="_top" shape="rect" coords="137,36,211,58" alt="random" title="random">
<area href="https://baccyflap.com/noai/?nxt&s=dzk" target="_top" shape="rect" coords="218,36,292,58" alt="next" title="next">
</map>
<img class="webring-img" usemap="#w95widget" src="/webrings/noai/w95widget.webp" alt="a gray Windows 95 style dialog box titled 'The No AI Webring' with a little icon showing a computer chip in a rubbish bin. beside it are three clickable buttons, labeled Previous, Random... and Next">
</div>
<div class="webring">
<a href="https://www.rainbowcemetery.com/devring/prev.php?id=18">
<img src="/webrings/gamedev/ringprev.png" alt="previous">
</a>
<a href="https://www.rainbowcemetery.com/devring/list.php?id=18">
<img src="/webrings/gamedev/88x31.png" alt="list">
</a>
<a href="https://www.rainbowcemetery.com/devring/next.php?id=18">
<img src="/webrings/gamedev/ringnext.png" alt="next">
</a>
<div class="button-container">
<h4 class="update-header">button corner</h4>
<div class="button-subcontainer">
{#each buttons as button}
<IndieButton button={button} />
{/each}
</div>
<p>to be expanded!</p>
<p class="small-supertext">my own 88x31 button is in the making. ETA: ???</p>
</div>
<map name="w95widget">
<area href="https://baccyflap.com/noai" target="_blank" shape="rect" coords="0,0,308,22" alt="no ai webring" title="no ai webring">
<area href="https://baccyflap.com/noai/?prv&s=dzk" target="_top" shape="rect" coords="56,36,130,58" alt="previous" title="previous">
<area href="https://baccyflap.com/noai/?rnd" target="_top" shape="rect" coords="137,36,211,58" alt="random" title="random">
<area href="https://baccyflap.com/noai/?nxt&s=dzk" target="_top" shape="rect" coords="218,36,292,58" alt="next" title="next">
</map>
<img class="webring-img" usemap="#w95widget" src="/webrings/noai/w95widget.webp" alt="a gray Windows 95 style dialog box titled 'The No AI Webring' with a little icon showing a computer chip in a rubbish bin. beside it are three clickable buttons, labeled Previous, Random... and Next">
</div>
</Content>
<style>
@@ -127,7 +148,7 @@
display: flex;
flex-direction: row;
gap: 16px;
margin-bottom: 16px;
margin: 16px 0;
}
@media screen and (max-width: 900px) {
@@ -137,6 +158,18 @@
}
}
.button-container {
flex: 2;
}
.button-subcontainer {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
align-content: center;
}
.webring {
display: flex;
flex-direction: row;
@@ -155,21 +188,23 @@
margin-right: 12px;
}
.update-container, .info-container {
.update-container, .info-container, .button-container, .webring-container {
padding: 8px 24px;
backdrop-filter: blur(var(--blur-radius-background));
flex: 2;
}
.update-container {
.update-container, .button-container, .webring-container {
border: var(--border-dash-size) var(--color-highlight) var(--border-style);
}
.info-container {
.info-container, .button-container {
border: var(--border-dash-size) var(--color-highlight-alt) var(--border-style);
}
.info-container {
flex: 1;
}
.info-container > h4 {
.info-container > h4, .button-container > h4 {
color: var(--color-highlight-alt);
}
.info-container p, .info-container a {
@@ -183,6 +218,7 @@
flex-wrap: wrap;
align-items: center;
justify-content: center;
flex: 3;
/* flex-wrap: wrap; */
/* gap: 8px; */
padding-top: 16px;
@@ -209,6 +245,12 @@
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
padding-bottom: 12px;
}
.page-subtitle {
/* padding-bottom: 12px; */
width: fit-content;
margin: 4px 0 12px 0;
display: block;
}
</style>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import Banner2 from "$lib/banner2.svelte";
import Content from "$lib/viewport/content.svelte";
import GalleryRow, { type GalleryRowEntry } from "$lib/lists/gallery-row.svelte";
const subpages: GalleryRowEntry[] = [
{
title: "Drawing Gallery",
description: "Some cool things I've drawn!",
img: "drawings/banner.webp",
altText: "Several Faber-Castell Polychromos colour pencils lined up with markings next to them in the same colour on a sheet of paper.",
link: "drawings",
},
{
title: "Discography",
description: "Small stories about my past music",
img: "/main/hypertext.webp",
altText: "",
link: "music",
},
];
</script>
<svelte:head>
<title>Art | denizk0461</title>
</svelte:head>
<Content>
<Banner2
title="Art"
banner="banner.webp"
bannerAlt="A rainbow-like holographic effect produced by bending a reflective sheet of cardboard."
subtitle="my creative side" />
<p>Here I have collected the products of some of my creative endeavours. Check them out below!</p>
<GalleryRow entries={subpages} />
</Content>

View File

@@ -0,0 +1,137 @@
<script lang="ts">
import Banner2 from "$lib/banner2.svelte";
import Content from "$lib/viewport/content.svelte";
import { type Drawing, drawings } from "./drawings";
</script>
<svelte:head>
<title>Drawing Gallery | denizk0461</title>
</svelte:head>
{#snippet drawingGalleryEntry({d}: {d: Drawing})}
<div class="gallery-entry">
<div class="gallery-entry-img-container">
<img src="{d.img}" alt="{d.imgAlt}">
</div>
<div class="gallery-entry-info">
<p class="gallery-entry-title">{d.title} <span>{d.date}</span></p>
{#each d.notes as note}
<p class="gallery-entry-note">{note}</p>
{/each}
<a href="{d.img}">view full-size</a>
</div>
</div>
{/snippet}
<Content>
<Banner2
title="Drawing Gallery"
subtitle=""
banner="banner.webp"
bannerAlt="Several Faber-Castell Polychromos colour pencils lined up with markings next to them in the same colour on a sheet of paper." />
<p>I started drawing at the start of 2026 and this is my page to show off what I make! I've mostly drawn on paper so far (I like the feel and resistance of pens on paper as well as the <a href="/blog/2026/0129">limitations</a> it imposes), but I got into digital art with Krita recently!</p>
<p>Why have I created this page, you may wonder? to pressure myself to draw more</p>
<p>If you're interested, here's a post about me <a href="/blog/2026/0205">drawing every day for 28 days</a> to learn to draw. You may recognise some of the drawings there; I picked out my favourite drawings and added them here!</p>
<div class="drawing-container">
{#each drawings as d}
{@render drawingGalleryEntry({d})}
{/each}
</div>
</Content>
<style>
.drawing-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 8px;
}
.gallery-entry {
position: relative;
height: 340px;
}
.gallery-entry-img-container {
overflow: hidden;
}
.gallery-entry-img-container, .gallery-entry-info {
border-radius: 16px;
}
.gallery-entry-img-container, .gallery-entry-info {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 0;
height: 100%;
width: 100%;
}
.gallery-entry img {
width: 100%;
height: 100%;
object-fit: cover;
transition: scale var(--duration-animation) var(--anim-curve);
}
.gallery-entry:hover img {
scale: 1.2;
}
.gallery-entry:hover .gallery-entry-info {
opacity: 1;
}
.gallery-entry-info {
opacity: 0;
display: flex;
flex-direction: column;
transition: opacity var(--duration-animation) var(--anim-curve);
background-color: var(--color-header-highlight);
padding: 12px 8px;
gap: 4px;
box-sizing: border-box;
justify-content: center;
outline: var(--border-style) var(--border-dash-size) var(--color-highlight-alt);
}
.gallery-entry-info * {
margin: 0;
width: fit-content;
}
.gallery-entry-title {
font-family: var(--font-mono);
font-weight: 700;
}
.gallery-entry-title span {
font-size: 0.8rem;
line-height: 0.9rem;
font-weight: 500;
}
.gallery-entry-note, .gallery-entry-info a {
font-size: 1.0rem;
line-height: 1.3rem;
}
@media screen and (max-width: 1000px) {
.drawing-container {
grid-template-columns: 1fr 1fr;
}
}
@media screen and (max-width: 600px) {
.drawing-container {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -0,0 +1,90 @@
export interface Drawing {
title: string;
date: string;
notes: string[];
img: string;
imgAlt: string;
}
export let drawings: Drawing[] = [
{
title: "Krita #1",
date: "2026-03-10",
notes: [
"ok i changed my mind on digital art. it's awesome.",
"My first drawing using Krita! I went with my usual methods but tried refining some things and adding (hopefully not overly misplaced) shadows too. I ended up really liking the ability to use layers, and colour in digital art just pops so nicely.",
"Initially, I drew the left arm in front of her body but later changed this to avoid drawing the hand.",
],
img: "2026/0310.webp",
imgAlt: "A digital drawing of a girl with long brown hair in a ponytail. She has green eyes and is wearing a cropped shirt with stripes, an orange spaghetti top underneath, and dark trousers. She is holding her hands behind her back.",
},
{
title: "SMILE! :D",
date: "2026-03-04",
notes: [
"This is actually the construction sketch of a drawing I later went over with a fineliner and coloured pencils. However, I kind of prefer the pencil sketch.",
"This was my first attempt at a head-on perspective. I had fun drawing details like the scrunchie, the jeans, and the smile!",
],
img: "/blog/2026/0205/27-1.webp",
imgAlt: "A drawing of a girl with her head tilted towards her right shoulder. She is smiling with her eyes closed and is holding up a victory sign with her left hand. She has her hair in a ponytail and is wearing jeans with shoulder straps, and there is a scrunchie on her left wrist as well.",
},
{
title: "Cyborg Arm",
date: "2026-02-27",
notes: [
"Possibly my favourite sketch from the drawing challenge, because she looks cool, but also because her design deviates from the other characters a bit.",
],
img: "/blog/2026/0205/22.webp",
imgAlt: "A pencil sketch of a girl with a ponytail, crop top, and track pants with a slightly shocked look on her face. She is looking at her right arm, which is a cyborg part.",
},
{
title: "Porter Robinson fanart",
date: "2026-02-26",
notes: [
"I drew the Worlds hand for practice and then decided to draw Po-Uta's head as well. I realised then that learning to draw gave me the ability to draw fanart.",
"I had never considered that possibility before.",
],
img: "/blog/2026/0205/21.webp",
imgAlt: "Two pencil sketches traced over with a black fineliner. The left one is of a hand with a cube in its palm, sketched after the hand on the cover of Porter Robinson's album Worlds. Beneath it is an emoticon used on the same cover. To the right is a manga-style head with green eyes and wavy hair, meant to resemble Porter Robinson's Vocaloid mascot Po-Uta.",
},
{
title: "Emilia",
date: "2026-02-23",
notes: [
"My first character with the new style of drawing eyes I picked up from a manga drawing book!",
"I named her Emilia because she looked like a more nice and caring character.",
],
img: "/blog/2026/0205/18.webp",
imgAlt: "A pencil sketch of a girl holding up a V sign with her left arm. She is wearing a long-sleeve shirt, jeans, and her hair is tied up in a ponytail. She is winking, the other eye is coloured green. Her body is tilted towards the right side of the paper. In the top right corner is a lightly-drawn sketch of the girl's pose.",
},
{
title: "Elizabeth",
date: "2026-02-18",
notes: [
"She's the product of me trying to re-draw the character I drew on day 1 of my drawing challenge, and I was really glad to see that I had actually improved!",
],
img: "/blog/2026/0205/13-2.webp",
imgAlt: "A pencil drawing of a girl looking to the left. She is wearing a cropped loose tee and jeans, while her right hand is hinted to rest on her hip.",
},
{
title: "bread girl",
date: "2026-01-30",
notes: [
"I drew her during a game of Wizard. I initially wanted to make her chew on a whole loaf but I didn't know how to draw that.",
"Wasn't really sure how to convey that her mouth is full either, but in retrospect, I could have exaggerated the bow in her lower eyelids to do so.",
"I like her eyes. Her head could be taller, actually.",
],
img: "2026/breadgirl.webp",
imgAlt: "An anime-style girl chewing on a piece of bread. She wears a ponytail and a sleeveless top.",
},
{
title: "test",
date: "2026-01-29",
notes: [
"A small sketch (only like 4cm wide) that I drew with a ballpoint pen on pink paper. The fact that I was able to sketch this, without any prior practice, plus an intrinsic want to be able to draw made me seriously consider learning to draw.",
"Having learned just a little bit about drawing, I can say now (a month and a half later) that this isn't great, but it served its purpose of making me start to draw!",
],
img: "/blog/2026/0129/girl.webp",
imgAlt: "A small drawing of an anime-style girl's head. She has a ponytail and is looking towards the left with a concentrated gaze.",
},
];

View File

@@ -33,5 +33,5 @@
banner="robert.webp"
bannerAlt="View at a tram bridge rising and then curving to the left." />
<Gallery entries={entries} reverseTextOrder />
<Gallery entries={entries} />
</Content>

View File

@@ -112,7 +112,7 @@ I didn't have any kind of clear goal for today, but I did enjoy the sketching. A
For the most part, I was just testing thing without sticking to just faces. Most importantly perhaps: no perfectionism! I didn't use my eraser *once!* The focus was on trying more rather than perfecting one single drawing. It did work to my benefit, in a way; I drew bodies, heads, even hands.
I talked to a friend of mine earlier today about drawing. Thing is, she's a pro at it. She's been donig it for ages, she's studying art too, and she does insanely cool paintings. She did give me quite a few tips when we talked, but I did notice that a bit of it was way above my level. At one point, she started sketching a realistic eye, telling me about minute details like the positioning of eyelashes, and while I didn't see a direct connection between her advice and my drawings, I listened and watched in awe as she drew a really good-looking eye from what I assume is just experience and possibly my eyes as a partial reference.
I talked to a friend of mine earlier today about drawing. Thing is, she's a pro at it. She's been doing it for ages, she's studying art too, and she does insanely cool paintings. She did give me quite a few tips when we talked, but I did notice that a bit of it was way above my level. At one point, she started sketching a realistic eye, telling me about minute details like the positioning of eyelashes, and while I didn't see a direct connection between her advice and my drawings, I listened and watched in awe as she drew a really good-looking eye from what I assume is just experience and possibly my eyes as a partial reference.
We also talked about perfectionism and how it can be beneficial to draw without erasing constantly like how I drew [my drawing from two weeks ago](/blog/2026/0129/) using a ballpoint pen instead of a pencil. No going back using that. I did intend to use a gel pen today, but I kind of shied away from it for now. Thinking about it now, a ballpoint pen may be better actually, because it draws finer and less bold lines.
@@ -482,13 +482,7 @@ Hand 1 has pretty thick fingers, but it was my first try so whatever. Hand 4's f
Went pretty well! No idea what I'll do tomorrow, but it's going well. If I'm really daring, I'll do another character (that'll hopefully take less time...) and try to add some cool hands. Jazz hands would be funny ngl
Oh a bit of a tangent: I do have to say that I felt a little down today. There were a lot of good things today: I'm virtually sickness-free, I wore a cool new outfit and felt very gen-Z-internety in it so I listened to I Love My Computer by Ninajirachi on the bus and felt even more in my element, and the weather was super nice today as well. I even met up with friends!
But I wanted to show one of my friends, the artsy one, my drawings today. I don't see her super often so I wanted to use the chance. I was actually really excited to do so because obviously I've put quite a lot of time into these drawings and I'm quite proud of especially what I've managed to accomplish in this short time, and I like sharing that. But she wanted me to show them off while we were busy doing something else entirely, to which I told her something along the lines of *"I'll show you when we have the time for it"* because I wanted to take my time with that rather than rush through the sketches. I ended up not being able to show her my drawings, but she still gave me the advice to *study anatomy*.
That felt pretty discouraging. She hadn't even seen my drawings, doesn't know what or how I am drawing, and gave me pretty boilerplate advice as a result. I guess it's not too dramatic, but it still didn't feel great.
But I'm fine now. In fact, I just looked at my drawing from [two days ago](#day-18) again, and that made me feel really good. I'm *so happy* about that drawing in particular and my progress in general. *I created these drawings!!* How cool is that?? AI images could never evoke the joy in me that I am feeling when I look at my sketches.
Oh a bit of a tangent: I just looked at my drawing from [two days ago](#day-18) again, and that made me feel really good. I'm *so happy* about that drawing in particular and my progress in general. *I created these drawings!!* How cool is that?? AI images could never evoke the joy in me that I am feeling when I look at my sketches.
## Day 21: we'll see creation come undone

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">
<channel>
<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()}
</channel>
</rss>`;
@@ -16,8 +16,8 @@ function getEntries(): String {
<item>
<title><![CDATA[${entry.post.title}]]></title>
<description><![CDATA[${entry.post.description}]]></description>
<link>https://denizk0461.dev/blog/${entry.key}</link>
<guid isPermaLink="true">https://denizk0461.dev/blog/${entry.key}</guid>
<link>https://natconf.dev/blog/${entry.key}</link>
<guid isPermaLink="true">https://natconf.dev/blog/${entry.key}</guid>
<pubDate><![CDATA[${new Date(`${entry.post.date}T${entry.post.time}`).toUTCString()}]]></pubDate>
</item>`)
entries.forEach(entry => {

View File

@@ -23,6 +23,39 @@ export interface 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",
post: {

View File

@@ -78,7 +78,7 @@
},
{
text: "Bluesky",
link: "https://bsky.app/profile/denizk0461.dev",
link: "https://bsky.app/profile/natconf.dev",
},
{
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">
<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>
@@ -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 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>
@@ -177,7 +177,7 @@
<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>
@@ -201,7 +201,7 @@
<hr>
<p><i>Last updated: 2026-02-25</i></p>
<p><i>Last updated: 2026-03-26</i></p>
</Content>
<style>

View File

@@ -9,7 +9,7 @@
link: "/blog/feed.xml",
},
{
text: "Project N5 devlog",
text: "Homesick devlog",
link: "/projects/projectn5/devlog/feed.xml",
},
]

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>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>
</Content>

View File

@@ -1,6 +1,30 @@
import { type UpdateEntry } from "$lib/components/update-entry.svelte";
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",
time: "19:21",
content: "new page: my drawings!",
link: "/art/drawings",
},
{
date: "2026-03-07",
time: "14:12",
@@ -51,7 +75,7 @@ export const entries: UpdateEntry[] = [
date: "2026-02-03",
time: "15:48",
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",

View File

@@ -19,7 +19,7 @@
description: "Small stories about my past music",
img: "/main/hypertext.webp",
altText: "",
link: "my-tracks",
link: "/art/music",
},
];
</script>

View File

@@ -29,19 +29,20 @@
<svelte:head>
<title>Project N5 | denizk0461</title>
<title>Homesick | denizk0461</title>
</svelte:head>
<Content>
<Banner2
title="Project N5"
banner="/projects/projectn5/banner2.webp" />
title="Homesick"
banner="/projects/projectn5/banner2.webp"
bannerAlt="The protagonist Laura standing on a floating platform in the purple test level. Ziplines are all around her and the text 'When this text is spinning, the game is not paused' is frozen in the sky." />
<p>I am currently working on a game under the working title <b>Project N5</b>! I'm aiming for it to be an action-adventure jump-and-run game inspired by games such as Ratchet & Clank. Development started on <b>2023-09-16</b> and rebooted on <b>2025-05-16</b>.</p>
<p>I am currently working on a game under the working title <b>Homesick</b> (fka Project N5)! I'm aiming for it to be an action-adventure jump-and-run game inspired by games such as Ratchet & Clank. Development started on <b>2023-09-16</b> and rebooted on <b>2025-05-16</b>.</p>
<h2 id="devlog">Development Log</h2>
<p>Development log entries in reverse chronological order (newest to oldest).</p>
<Gallery {entries} reverseTextOrder />
<Gallery {entries} />
</Content>

View File

@@ -4,19 +4,19 @@
let builds: LinkEntry[] = [
{
text: "2023-10-07 (Protagonist #1)",
link: "https://files.denizk0461.dev/projectn5/2023-10-07.zip",
link: "https://files.natconf.dev/public/projectn5/2023-10-07.zip",
},
{
text: "2023-12-23 (Protagonist #2)",
link: "https://files.denizk0461.dev/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)",
link: "https://files.denizk0461.dev/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]",
link: "https://files.denizk0461.dev/projectn5/2025-08-16.zip",
link: "https://files.natconf.dev/public/projectn5/2025-08-16.zip",
},
];
</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.
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/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} />

View File

@@ -0,0 +1,75 @@
<script lang="ts">
import SubtitledImage from "$lib/components/subtitled-image.svelte";
</script>
Been a while. Again.
<SubtitledImage
image="habitkit.webp"
altText="A screenshot of the app HabitKit zoomed in on an entry labelled 'Project Laura' and subtitled '2025 IS THE YEAR OF LAURA'. On the left half are red dots scattered, marking days of progress, whereas the right half is entirely blank."
subtitle="aaaaaaaaaaaaaa"
alignment="right" />
## What's Been Going On
Not a lot. Well actually, that's not true. I returned to Germany after a four-month abroad semester in the UK. I deep-cleaned my apartment. I switched to Linux. I worked on my website a bunch. I [learned to draw](/blog/2026/0205). But there's not been any hard progress on Project N5.
I use this habit tracking app, [HabitKit](https://www.habitkit.app/), and it (unfortunately) quite clearly displays the lack of work on the game. But this doesn't mean I've given up on the game. Maybe I'm just saying this because I'm too stubborn though.
## What I've Realised
<SubtitledImage
image="papertracker.webp"
altText="A calendar with the title 'HOMESICK Abroad Progress Calendar'. Close to half the days are marked with a cross, with no discernible pattern visible."
subtitle="proof of progress"
alignment="left"
smaller />
In and especially after my drawing challenge, I've realised something I wasn't 100% sure applied across the board for me: I struggle to get started even on the things I enjoy most. I knew before that I have a hard time starting work on the game but enjoying it once I do begin, but I found that the very same applies to drawing.
There's just something inside of me that hinders me from doing what I love.
I tried combating this when I lived in the UK; I had a physical version of the habit tracker printed out (on uni credit) that served as a looming reminder day and night. The fact I needed this to begin with is ridiculous, I gotta say. But it did help, in a way, I think? Judging by the way my progress looks on the tracker app, there was at least a small increase in productive days.
## What's There to Look Forward To
Something I was really looking forward to was [Godot 4.6](https://godotengine.org/releases/4.6/), which finally released a while back!
The change that'll probably impact me the most is the (re-)introduction of inverse kinematics! That's something I've been longing to be able to implement for quite a while. But there are more cool changes that'll have a more minor but still interesting impact, like those on tonemapping, glow, LOD generation, and also the added support for controller lights and the possibility to use the DualSense's adaptive triggers in a future update.
I've also been keeping my eyes open for resources, and I found a couple that piqued my interest; namely, two shaders, a [toon shader by Binbun](https://binbun3d.itch.io/godot-ultimate-toon-shader) that seems more customisable and a lot more visually interesting than the one I've been using so far, and [Sky3D by TokisanGames](https://github.com/TokisanGames/Sky3D) for very pretty skies.
I am also planning on potentially looking into root motion for animation, once I get to that. It feels like a larger undertaking, and I may not end up pursuing that after all, but it seems like a cool way of getting better and more fluid-feeling motion out of a character.
## What's Changing
Possibly the biggest change I can announce right now is the name of the project. I've been calling it by this name for a while, but to make it somewhat official: the game is now called ***Homesick***.
The old 'Project N5' hasn't felt right for a long time. It's a relic of a time when the game was meant to be more of a shooter, Ratchet: Gladiator-style, with a robot protagonist fighting other robots while travelling across different planets. This isn't what I had in mind for the project for a very long time now. ***Homesick*** is meant to represent Laura's feelings, Laura's struggles when she learns what has happened to the world she once knew.
I'll probably change the URL structure soon to read `/projects/homesick`, but I'll set up redirects so old URLs won't be dead ends.
## What I've Done
Even in my state of absence from the project itself, I kept it in mind I never forgot about it! Still, the actual progress I made is rather minor. I did implement metro stations back in December, which allow the player to travel between levels and even to set locations within a level. Aside from that, most other changes are pretty basic and not really worth mentioning, like tweaking parameters or simplifying code blocks.
Possibly the most significant progress I made was on starting to model Laura v3. Wait, what?
## What I'm Planning to Do
Something I started but made very little progress with is Laura's redesign. The *next* redesign. I think I learned a lot from the v2 model (or v4 model, as I had called it before), but there's more to improve mesh-wise. However, I also want to make bigger stylistic changes. I feel that a lot of what I criticised about Laura v1 still applies in some way: she just doesn't look that interesting as a character. There's so much more I can do with her, I think, and the v2 model is just iteration on the v1 design. I made a low-poly mockup back in December to dump my brain onto the 3D canvas:
<SubtitledImage
image="laura3-preview.webp"
altText="A low-poly model of a human with red hair in a ponytail wearing a black mask, dark grey shirt with long sleeves, green trousers, and white shoes."
subtitle="an idea for where laura may be headed" />
Some pretty drastic colour palette changes. Red top, brown hair, and black/white everything else is just *boring*. That's like the look I wear irl except red instead of green. I wanted to mix up things.
I also believe that my recent efforts in learning to draw gave me a better perspective at modelling human bodies. There are details I had to pay attention to that I think just aren't reflected very well in the current model. It's small things like not making her arms and legs straight as matches. Or the proportions. Or her face; I think I'll be able to improve her eyes in particular. I struggled with that on v2, but ESPECIALLY on v1. And maybe I'll do a little better at drawing textures too, who knows?
It feels weird to design Laura for a *third* time now, but I guess this would be no different in an actual game studio's production. There's no way you'll land on the perfect character on your first try, right? It's just that a game studio may do this in a week or two, and I'm much slower at this.
Something else I need to revisit is the amalgamation I built a while back to manage Laura's different movement states. I think the way I built it is somehow both completely overengineered and also insufficient for use as a foundation for Laura's movement. It works *now*, don't get me wrong, but it's so bad to maintain. I'm not even gonna show it lol
Something I also *finally* need to do is just sit down and write story bits. It's something I've just not done (enough), and the story obviously isn't gonna magically appear out of thin air. There are ideas floating around in my head and I need to capture, connect, and build upon them. It's just something I've not done much before, so I'm worried and I keep postponing it, but just like with all other hobbies of mine: *I just need to get started!*

View File

@@ -16,7 +16,7 @@
<Banner2
title="{data.title}"
subtitle="Project N5 Devlog"
subtitle="Homesick Devlog"
date="{data.date}"
banner="preview.webp"
bannerAlt="{data.bannerAlt}"

View File

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

View File

@@ -11,6 +11,15 @@ export interface DevlogPostLink {
}
export const posts: DevlogPostLink[] = [
{
key: "2026/0309",
post: {
title: "The Overdue Update",
date: "2026-03-09",
bannerAlt: "GDScript code zoomed in on a function called '_take_damage()' that checks whether the player has died and then reads: 'if is_dead: _die()'.",
description: "An excuse, a fresh character, and a new name.",
}
},
{
key: "2025/1207",
post: {

View File

@@ -65,10 +65,10 @@ export const games: Project[] = [
banner: "/projects/projectn5/banner2.webp",
icon: "",
date: "September 2023 now",
title: "Project N5",
title: "Homesick",
subtitle: "",
paragraphs: [
"I'm currently working on a game developed using Godot, entitled Project N5! It's aiming to be an action-adventure 3D jump & run heavily inspired by games such as <a href='https://en.wikipedia.org/wiki/Ratchet_%26_Clank'>Ratchet & Clank</a>.",
"I'm currently working on a game developed using Godot, entitled Homesick! It's aiming to be an action-adventure 3D jump & run heavily inspired by games such as <a href='https://en.wikipedia.org/wiki/Ratchet_%26_Clank'>Ratchet & Clank</a>.",
"I maintain a development log, feel free to check it out if you're curious! Or play some of the old builds available for download below.",
],
links: [
@@ -77,12 +77,12 @@ export const games: Project[] = [
link: "/projects/projectn5",
},
{
text: "Play an <b>old build</b> (developed until 2025-05-16)",
link: "https://apps.denizk0461.dev/projectn5",
text: "Play an <b>old web build</b> (developed until 2025-05-16)",
link: "https://apps.natconf.dev/projectn5",
},
{
text: "Download the <b>old Windows builds</b>",
link: "https://files.denizk0461.dev/projectn5",
link: "https://files.natconf.dev/public/projectn5",
},
],
status: ProjectStatus.ACTIVE,
@@ -102,7 +102,7 @@ export const games: Project[] = [
links: [
{
text: "View the latest <b>Magician</b> build",
link: "https://apps.denizk0461.dev/magician",
link: "https://apps.natconf.dev/magician",
},
],
status: ProjectStatus.ABANDONED,
@@ -139,7 +139,7 @@ export const games: Project[] = [
links: [
{
text: "Play <b>Swords & Stuff</b>",
link: "https://apps.denizk0461.dev/swordsnstuff",
link: "https://apps.natconf.dev/swordsnstuff",
},
],
status: ProjectStatus.ABANDONED,
@@ -159,11 +159,11 @@ export const games: Project[] = [
links: [
{
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>",
link: "https://apps.denizk0461.dev/tads/2",
link: "https://apps.natconf.dev/tads/2",
},
],
status: ProjectStatus.FINISHED,
@@ -189,7 +189,7 @@ export const hardware: Project[] = [
},
{
text: "Get the <b>PCB and STL files</b>",
link: "https://files.denizk0461.dev/daisyfm/",
link: "https://files.natconf.dev/public/daisyfm/",
},
{
text: "View the code files on <b>Codeberg</b>",
@@ -317,7 +317,7 @@ export const music: Project[] = [
links: [
{
text: "Listen & download on my <b>copyparty</b> instance",
link: "https://files.denizk0461.dev/my_tracks/Dreamworld/",
link: "https://files.natconf.dev/public/my_tracks/Dreamworld/",
},
],
status: ProjectStatus.FINISHED,
@@ -337,7 +337,7 @@ export const music: Project[] = [
links: [
{
text: "Listen & download on my <b>copyparty</b> instance",
link: "https://files.denizk0461.dev/my_tracks/A%20New%20Beginning/",
link: "https://files.natconf.dev/public/my_tracks/A%20New%20Beginning/",
},
],
status: ProjectStatus.FINISHED,

BIN
static/art/banner.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

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.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB