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-img: invert(98%) sepia(1%) saturate(4643%) hue-rotate(297deg) brightness(115%) contrast(76%);
|
||||
--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-dark: color-mix(in srgb, var(--color-highlight) 60%, black);
|
||||
--color-highlight-alt: #d03b4a;
|
||||
@@ -105,8 +109,9 @@
|
||||
|
||||
--color-background: #111111;
|
||||
--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-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-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">
|
||||
import Banner2 from "$lib/banner2.svelte";
|
||||
import Content from "$lib/viewport/content.svelte";
|
||||
import Gallery, { type GalleryEntry } from "$lib/lists/gallery.svelte";
|
||||
import { posts, type BlogPostLink } from "./posts";
|
||||
import { BlogPostTag, 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 {
|
||||
let banner = "";
|
||||
if (entry.post.banner && entry.post.banner !== "") {
|
||||
banner = `/blog/${entry.key}/${entry.post.banner}`;
|
||||
function setFilter(tag: BlogPostTag) {
|
||||
filter = tag;
|
||||
console.log(filter);
|
||||
}
|
||||
|
||||
return {
|
||||
title: `${entry.post.title}`,
|
||||
subtitle: `#${(posts.length - index).toString().padStart(2, '0')} // ${entry.post.date}, ${entry.post.time}`,
|
||||
img: banner,
|
||||
link: `/blog/${entry.key}/`,
|
||||
imgAlt: `Preview image for ${entry.post.title}`,
|
||||
description: entry.post.description,
|
||||
};
|
||||
function filterPosts(): BlogPostLink[] {
|
||||
if (filter == BlogPostTag.NULL) {
|
||||
return posts;
|
||||
}
|
||||
|
||||
let a: BlogPostLink[] = posts.filter((post) => post.post.tags.includes(filter))
|
||||
console.log(a);
|
||||
return a;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,5 +32,63 @@
|
||||
banner="robert.webp"
|
||||
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>
|
||||
|
||||
<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: string;
|
||||
|
||||
tags: BlogPostTag[];
|
||||
}
|
||||
|
||||
export interface BlogPostLink {
|
||||
@@ -21,6 +23,15 @@ export interface BlogPostLink {
|
||||
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[] = [
|
||||
{
|
||||
@@ -32,6 +43,9 @@ export const posts: BlogPostLink[] = [
|
||||
bannerAlt: "White light blurs on a darker background.",
|
||||
title: "Moving On",
|
||||
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.",
|
||||
title: "I made a LIGHTYEARS font",
|
||||
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.",
|
||||
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.",
|
||||
tags: [
|
||||
BlogPostTag.TECH_TIP,
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -62,9 +82,13 @@ export const posts: BlogPostLink[] = [
|
||||
date: "2026-02-14",
|
||||
time: "19:46",
|
||||
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",
|
||||
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.",
|
||||
title: "Am I doing too much?",
|
||||
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.",
|
||||
title: "Drawing Challenge",
|
||||
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",
|
||||
title: "Lessons Learned",
|
||||
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.",
|
||||
title: "Limitations",
|
||||
description: "Something about how boundaries can foster creativity.",
|
||||
tags: [
|
||||
BlogPostTag.ART,
|
||||
BlogPostTag.DRAWING,
|
||||
BlogPostTag.IMADETHIS,
|
||||
],
|
||||
}
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user