Compare commits
11 Commits
60246f7fd6
...
7d0a51a4d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d0a51a4d9 | |||
| d3e1aeb33d | |||
| 78f05fd5f3 | |||
| 0699b7e08d | |||
| 0c403c82e7 | |||
| 4641d2357a | |||
| 4ea6d168a6 | |||
| 9dcb9823a8 | |||
| 6440a098bf | |||
| 61875ab08e | |||
| 159ba59500 |
131
src/lib/lists/blog-gallery.svelte
Normal file
131
src/lib/lists/blog-gallery.svelte
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BlogPostTag, type BlogPostLink } from "../../routes/blog/posts";
|
||||||
|
|
||||||
|
let {
|
||||||
|
posts,
|
||||||
|
}: {
|
||||||
|
posts: BlogPostLink[];
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="entry-container">
|
||||||
|
{#each posts as post}
|
||||||
|
<a class="entry" href="{post.key}">
|
||||||
|
<div class="entry-banner-container">
|
||||||
|
<img class="entry-banner" src="{post.key}/{post.post.banner}" alt="{post.post.bannerAlt}">
|
||||||
|
</div>
|
||||||
|
<div class="entry-text-container">
|
||||||
|
<p class="entry-title">{post.post.title}</p>
|
||||||
|
<p class="entry-date">:: {post.post.date} ::</p>
|
||||||
|
<p class="entry-description">{post.post.description}</p>
|
||||||
|
<div class="entry-tag-container">
|
||||||
|
{#each post.post.tags as tag}
|
||||||
|
<span class="post-tag">{tag}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.entry-container {
|
||||||
|
display: grid;
|
||||||
|
/* gap: 8px; */
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
transition: background-color var(--duration-animation) var(--anim-curve),
|
||||||
|
border-color var(--duration-animation) var(--anim-curve),
|
||||||
|
backdrop-filter var(--duration-blur) var(--anim-curve),
|
||||||
|
border-radius var(--duration-animation) var(--anim-curve);
|
||||||
|
border: var(--border-dash-size) var(--border-style) transparent;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry:hover {
|
||||||
|
background-color: var(--color-background-highlight-alt);
|
||||||
|
border-color: var(--color-highlight-alt);
|
||||||
|
backdrop-filter: blur(var(--blur-radius-background));
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry:hover .entry-banner {
|
||||||
|
scale: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry:hover .entry-banner-container {
|
||||||
|
/* border-radius: 24px 24px 0 0; */
|
||||||
|
border-top-left-radius: 16px;
|
||||||
|
border-top-right-radius: 16px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-banner-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
transition: border-radius var(--duration-animation) var(--anim-curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-banner {
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: scale var(--duration-animation) var(--anim-curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-text-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 16px 2px 2px;
|
||||||
|
/* gap: 8px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-title {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.4rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-date {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--color-text-highlight-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-description {
|
||||||
|
font-size: 1.0rem;
|
||||||
|
line-height: 1.2rem;
|
||||||
|
margin: 4px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-tag-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
.entry-container {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.entry-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -97,6 +97,10 @@
|
|||||||
--color-text-secondary: #b0b0b0;
|
--color-text-secondary: #b0b0b0;
|
||||||
--color-text-img: invert(98%) sepia(1%) saturate(4643%) hue-rotate(297deg) brightness(115%) contrast(76%);
|
--color-text-img: invert(98%) sepia(1%) saturate(4643%) hue-rotate(297deg) brightness(115%) contrast(76%);
|
||||||
--color-text-dark: #1e1e1e;
|
--color-text-dark: #1e1e1e;
|
||||||
|
|
||||||
|
--color-text-highlight: color-mix(in srgb, var(--color-highlight) 70%, var(--color-text));
|
||||||
|
--color-text-highlight-alt: color-mix(in srgb, var(--color-highlight-alt) 70%, var(--color-text));
|
||||||
|
|
||||||
--color-highlight: #51B86B;
|
--color-highlight: #51B86B;
|
||||||
--color-highlight-dark: color-mix(in srgb, var(--color-highlight) 60%, black);
|
--color-highlight-dark: color-mix(in srgb, var(--color-highlight) 60%, black);
|
||||||
--color-highlight-alt: #d03b4a;
|
--color-highlight-alt: #d03b4a;
|
||||||
@@ -105,8 +109,9 @@
|
|||||||
|
|
||||||
--color-background: #111111;
|
--color-background: #111111;
|
||||||
--color-background-highlight: color-mix(in srgb, var(--color-highlight) 20%, 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-alt: color-mix(in srgb, var(--color-highlight-alt) 30%, 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-alt: color-mix(in srgb, var(--color-highlight-alt) 66%, 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);
|
||||||
|
|
||||||
--color-waters: #242424;
|
--color-waters: #242424;
|
||||||
|
|||||||
20
src/routes/blog/+layout.svelte
Normal file
20
src/routes/blog/+layout.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@render children()}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global {
|
||||||
|
.post-tag {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
background-color: var(--color-background-highlight-alt);
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Banner2 from "$lib/banner2.svelte";
|
import Banner2 from "$lib/banner2.svelte";
|
||||||
import Content from "$lib/viewport/content.svelte";
|
import Content from "$lib/viewport/content.svelte";
|
||||||
import Gallery, { type GalleryEntry } from "$lib/lists/gallery.svelte";
|
import { BlogPostTag, posts, type BlogPostLink } from "./posts";
|
||||||
import { posts, type BlogPostLink } from "./posts";
|
import BlogGallery from "$lib/lists/blog-gallery.svelte";
|
||||||
|
|
||||||
let entries: GalleryEntry[] = posts.map(mapEntries);
|
let filter = $state(BlogPostTag.NULL);
|
||||||
|
|
||||||
function mapEntries(entry: BlogPostLink, index: number): GalleryEntry {
|
function setFilter(tag: BlogPostTag) {
|
||||||
let banner = "";
|
filter = tag;
|
||||||
if (entry.post.banner && entry.post.banner !== "") {
|
console.log(filter);
|
||||||
banner = `/blog/${entry.key}/${entry.post.banner}`;
|
}
|
||||||
|
|
||||||
|
function filterPosts(): BlogPostLink[] {
|
||||||
|
if (filter == BlogPostTag.NULL) {
|
||||||
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
let a: BlogPostLink[] = posts.filter((post) => post.post.tags.includes(filter))
|
||||||
title: `${entry.post.title}`,
|
console.log(a);
|
||||||
subtitle: `#${(posts.length - index).toString().padStart(2, '0')} // ${entry.post.date}, ${entry.post.time}`,
|
return a;
|
||||||
img: banner,
|
|
||||||
link: `/blog/${entry.key}/`,
|
|
||||||
imgAlt: `Preview image for ${entry.post.title}`,
|
|
||||||
description: entry.post.description,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -33,5 +32,63 @@
|
|||||||
banner="robert.webp"
|
banner="robert.webp"
|
||||||
bannerAlt="View at a tram bridge rising and then curving to the left." />
|
bannerAlt="View at a tram bridge rising and then curving to the left." />
|
||||||
|
|
||||||
<Gallery entries={entries} />
|
<!-- TODO descriptions on filter click -->
|
||||||
|
<p class="tag-filter-header"># filter posts by tag:</p>
|
||||||
|
<div class="tag-filter-container">
|
||||||
|
{#each Object.values(BlogPostTag) as tag}
|
||||||
|
{#if tag == filter}
|
||||||
|
<button class="post-tag tag-filter tag-filter-selected" onclick={() => { setFilter(tag) }}>{tag}</button>
|
||||||
|
{:else}
|
||||||
|
<button class="post-tag tag-filter" onclick={() => { setFilter(tag) }}>{tag}</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BlogGallery posts={filterPosts()} />
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tag-filter-header {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
/* margin-left: 10px;
|
||||||
|
margin-right: 10px; */
|
||||||
|
margin: 12px 10px 4px;
|
||||||
|
color: var(--color-text-highlight-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px 12px;
|
||||||
|
margin: 0 10px 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter {
|
||||||
|
width: fit-content;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color var(--duration-animation) var(--anim-curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter-selected {
|
||||||
|
background-color: var(--color-highlight-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter:hover {
|
||||||
|
background-color: var(--color-background-highlight-hover-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.tag-filter-container {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,8 @@ export interface BlogPostDetails {
|
|||||||
* Description to be used in page's metadata.
|
* Description to be used in page's metadata.
|
||||||
*/
|
*/
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
tags: BlogPostTag[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlogPostLink {
|
export interface BlogPostLink {
|
||||||
@@ -21,6 +23,15 @@ export interface BlogPostLink {
|
|||||||
post: BlogPostDetails;
|
post: BlogPostDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum BlogPostTag {
|
||||||
|
NULL = "all", // placeholder when a 'no tag' is needed. if in doubt, do not use this
|
||||||
|
ART = "art-stuff", // ramblings to do with art
|
||||||
|
DRAWING = "drawing", // self-explanatory
|
||||||
|
IMADETHIS = "i-made-this", // stuff i made
|
||||||
|
META = "natconf-meta", // about the website itself
|
||||||
|
TECH_TIP = "tech-tip", // tech guides
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const posts: BlogPostLink[] = [
|
export const posts: BlogPostLink[] = [
|
||||||
{
|
{
|
||||||
@@ -32,6 +43,9 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "White light blurs on a darker background.",
|
bannerAlt: "White light blurs on a darker background.",
|
||||||
title: "Moving On",
|
title: "Moving On",
|
||||||
description: "It's time to switch domains.",
|
description: "It's time to switch domains.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.META,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -43,6 +57,9 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "A sunset captured from an Autobahn exit.",
|
bannerAlt: "A sunset captured from an Autobahn exit.",
|
||||||
title: "I made a LIGHTYEARS font",
|
title: "I made a LIGHTYEARS font",
|
||||||
description: "I feel electric and it's only getting brighter!",
|
description: "I feel electric and it's only getting brighter!",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.IMADETHIS,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -54,6 +71,9 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "A Microsoft Surface Pro 8 displaying a Blue Screen of Death.",
|
bannerAlt: "A Microsoft Surface Pro 8 displaying a Blue Screen of Death.",
|
||||||
title: "How To: Set Up SvelteKit Frontend + PostgreSQL Backend",
|
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.",
|
description: "How to set up a web application with a backend on a remote server without getting error 403.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.TECH_TIP,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,9 +82,13 @@ export const posts: BlogPostLink[] = [
|
|||||||
date: "2026-02-14",
|
date: "2026-02-14",
|
||||||
time: "19:46",
|
time: "19:46",
|
||||||
banner: "logins.webp",
|
banner: "logins.webp",
|
||||||
bannerAlt: "A curved stick from a tree with some dry leaves attached. Its form resembles an entity with two legs, a spine, and no arms, leaning over and looking sad.",
|
bannerAlt: "A screenshot of a terminal emulator logged onto a remote server displaying log messages. The messages all display different IP addresses unsuccessfully attempting to log in with different usernames. There are over a dozen requests within a single minute on February 14, 2026.",
|
||||||
title: "SSH Woes",
|
title: "SSH Woes",
|
||||||
description: "About how I was shocked to learn that my server was open for attacks for well over a year.",
|
description: "About how I was shocked to learn that my server was open for attacks for well over a year.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.META,
|
||||||
|
BlogPostTag.TECH_TIP,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,6 +100,9 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "A curved stick from a tree with some dry leaves attached. Its form resembles an entity with two legs, a spine, and no arms, leaning over and looking sad.",
|
bannerAlt: "A curved stick from a tree with some dry leaves attached. Its form resembles an entity with two legs, a spine, and no arms, leaning over and looking sad.",
|
||||||
title: "Am I doing too much?",
|
title: "Am I doing too much?",
|
||||||
description: "I'm trying to pursue too many hobbies all at once and it's a struggle.",
|
description: "I'm trying to pursue too many hobbies all at once and it's a struggle.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.ART,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,6 +114,11 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "A Leuchtturm-branded notebook with a copper-coloured cover. An eraser, a pencil sharpener, and a Faber-Castell pencil are lying on top.",
|
bannerAlt: "A Leuchtturm-branded notebook with a copper-coloured cover. An eraser, a pencil sharpener, and a Faber-Castell pencil are lying on top.",
|
||||||
title: "Drawing Challenge",
|
title: "Drawing Challenge",
|
||||||
description: "Challenging myself to draw something every day for 4 weeks.",
|
description: "Challenging myself to draw something every day for 4 weeks.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.ART,
|
||||||
|
BlogPostTag.DRAWING,
|
||||||
|
BlogPostTag.IMADETHIS,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -98,6 +130,9 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "Colossus standing in the National Museum of Computing in Bletchley, UK",
|
bannerAlt: "Colossus standing in the National Museum of Computing in Bletchley, UK",
|
||||||
title: "Lessons Learned",
|
title: "Lessons Learned",
|
||||||
description: "A small note about how you should always check whether your finished work works as intended.",
|
description: "A small note about how you should always check whether your finished work works as intended.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.META,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -109,6 +144,11 @@ export const posts: BlogPostLink[] = [
|
|||||||
bannerAlt: "A small drawing of an anime-style girl's head.",
|
bannerAlt: "A small drawing of an anime-style girl's head.",
|
||||||
title: "Limitations",
|
title: "Limitations",
|
||||||
description: "Something about how boundaries can foster creativity.",
|
description: "Something about how boundaries can foster creativity.",
|
||||||
|
tags: [
|
||||||
|
BlogPostTag.ART,
|
||||||
|
BlogPostTag.DRAWING,
|
||||||
|
BlogPostTag.IMADETHIS,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
Reference in New Issue
Block a user