mirror of
https://github.com/YouHaveTrouble/youhavetrouble.github.io.git
synced 2026-06-29 20:46:18 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b5b071203 |
Generated
+11
-9
@@ -28,7 +28,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
@@ -444,6 +443,7 @@
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -2203,6 +2203,7 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
|
||||
"integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -2352,6 +2353,7 @@
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2468,6 +2470,7 @@
|
||||
"version": "4.16.19",
|
||||
"resolved": "https://registry.npmjs.org/astro/-/astro-4.16.19.tgz",
|
||||
"integrity": "sha512-baeSswPC5ZYvhGDoj25L2FuzKRWMgx105FetOPQVJFMCAp0o08OonYC7AhwsFdhvp7GapqjnC1Fe3lKb2lupYw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.10.3",
|
||||
"@astrojs/internal-helpers": "0.4.1",
|
||||
@@ -3027,6 +3030,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001741",
|
||||
@@ -3203,7 +3207,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
|
||||
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||
"@types/estree": "^1.0.1",
|
||||
@@ -3293,7 +3296,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.30",
|
||||
"source-map-js": "^1.0.1"
|
||||
@@ -4358,7 +4360,6 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
@@ -4487,8 +4488,7 @@
|
||||
"node_modules/locate-character": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"peer": true
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
@@ -4897,8 +4897,7 @@
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
||||
"peer": true
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
@@ -5885,7 +5884,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
|
||||
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^3.0.0",
|
||||
@@ -6687,6 +6685,7 @@
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.0.tgz",
|
||||
"integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -6773,6 +6772,7 @@
|
||||
"version": "1.93.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.0.tgz",
|
||||
"integrity": "sha512-CQi5/AzCwiubU3dSqRDJ93RfOfg/hhpW1l6wCIvolmehfwgCI35R/0QDs1+R+Ygrl8jFawwwIojE2w47/mf94A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -7404,6 +7404,7 @@
|
||||
"version": "5.4.20",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
|
||||
"integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
@@ -7562,6 +7563,7 @@
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 239 KiB |
+23
-13
@@ -2,20 +2,30 @@
|
||||
import Logo from './Logo.astro'
|
||||
import Nav from './Nav.astro'
|
||||
|
||||
const { current = '' } = Astro.props;
|
||||
export interface Props {
|
||||
current?: string;
|
||||
showLogo?: boolean;
|
||||
}
|
||||
|
||||
const {current = '', showLogo = true} = Astro.props;
|
||||
---
|
||||
<header>
|
||||
{
|
||||
showLogo && (
|
||||
<Logo width="50" height="50"/>
|
||||
)
|
||||
}
|
||||
<Nav current={current}/>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
max-width: var(--wrap);
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
max-width: var(--wrap);
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<header>
|
||||
<Logo width="50" height="50" />
|
||||
<Nav current={current} />
|
||||
</header>
|
||||
|
||||
@@ -11,6 +11,10 @@ const { width, height } = Astro.props;
|
||||
|
||||
<style>
|
||||
|
||||
img {
|
||||
view-transition-name: nav-logo;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 520px) {
|
||||
img {
|
||||
display: none;
|
||||
|
||||
+72
-72
@@ -1,81 +1,81 @@
|
||||
---
|
||||
const { current = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<style>
|
||||
|
||||
@media (orientation: portrait) {
|
||||
nav {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-family: var(--font-family-sans);
|
||||
font-weight: 700;
|
||||
justify-content: flex-end;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 10px 5px;
|
||||
display: block;
|
||||
position: relative;
|
||||
margin-left: 20px;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:not(.selected) {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
a::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
transition: transform .3s ease;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--text-secondary);
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
a:hover::before,
|
||||
.selected::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.selected::before {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.theme-toggle-container {
|
||||
width: 75px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav>
|
||||
<div class="nav-buttons">
|
||||
<a data-astro-prefetch class={current === "" ? "selected" : ""} href='/'>home</a>
|
||||
<a data-astro-prefetch class={current === "blog" ? "selected" : ""} href='/blog'>blog</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
|
||||
@media (orientation: portrait) {
|
||||
nav {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-family: var(--font-family-sans);
|
||||
font-weight: 700;
|
||||
justify-content: flex-end;
|
||||
text-transform: uppercase;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: hidden;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 10px 5px;
|
||||
display: block;
|
||||
position: relative;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:not(.selected) {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
a::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
transition: transform .3s ease;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--text-secondary);
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
a:hover::before,
|
||||
.selected::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.selected::before {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.theme-toggle-container {
|
||||
width: 75px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -113,9 +113,6 @@ projects.sort((a, b) => a.data.name.localeCompare(b.data.name));
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
height: min-content;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 96px;
|
||||
|
||||
&:focus-within {
|
||||
background-color: #548e9b;
|
||||
}
|
||||
|
||||
@@ -79,8 +79,6 @@
|
||||
padding: 0.5rem;
|
||||
gap: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 88px;
|
||||
|
||||
span {
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
const span = document.createElement("span");
|
||||
span.setAttribute("data-number", chars[i]);
|
||||
span.setAttribute("aria-label", chars[i]);
|
||||
span.style.animationDelay = `${i * 0.05}s`;
|
||||
viewCounter.appendChild(span);
|
||||
}
|
||||
|
||||
+20
-30
@@ -1,37 +1,27 @@
|
||||
import {z, defineCollection} from 'astro:content';
|
||||
|
||||
import { z, defineCollection } from 'astro:content';
|
||||
const posts = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishDate: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
image: z.string().optional(),
|
||||
}),
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishDate: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
image: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
const projects = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
category: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
image: z.string().optional(),
|
||||
links: z.array(z.object({text: z.string(), url: z.string()})),
|
||||
technologies: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
const writing = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
publishDate: z.date({coerce: true}),
|
||||
category: z.string(),
|
||||
}),
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
category: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
image: z.string().optional(),
|
||||
links: z.array(z.object({text: z.string(), url: z.string()})),
|
||||
technologies: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
posts,
|
||||
projects,
|
||||
writing,
|
||||
posts,
|
||||
projects,
|
||||
};
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: "A homelab adventure"
|
||||
publishDate: "18 Nov 2025"
|
||||
description: "\"Fine, I'll do it myself.\""
|
||||
tags: ["software", "homelab"]
|
||||
---
|
||||
|
||||
## The bare minimum
|
||||
|
||||
When I think back, it all started with a friend asking if I can help them learn proxmox. I didn't know anything about
|
||||
proxmox, so I told them sorry, but it somehow stuck with me. Few months later I realized I'm not really using my mini
|
||||
gmktec pc for much. I initially bought it for playing league of legends, since I'm daily driving linux and it was about
|
||||
time riot decided to require installing a rootkit on your specifically windows machine to play their game. I found
|
||||
myself not playing much, so it went. I grabbed a thumb drive, burned proxmox iso on it and gave it a go.
|
||||
|
||||
After the first impressions of the web interface, I kew what should be the first service to run on it. Pihole. I've
|
||||
been using adguard dns on my router before, but I liked the idea of hosting dns server locally on my own network. It
|
||||
even comes with an option to define local dns records, so I can use actual domain names instead of ip addresses to
|
||||
access my other services.
|
||||
|
||||
Very second thing I installed was homeassistant. I actually tested using it before, hosted on my main PC in a docker
|
||||
container, but it lacked plugin support and some things I wanted to do were just not possible without them. Thus, my
|
||||
first actual VM was born. The only smart device I had at the time was a lightbulb in my room, that I was using a mobile
|
||||
app to control. Luckily the bulb fully supported homeassistant, so I just hooked it right up, and now I can turn it off
|
||||
if I forget to do it before going out.
|
||||
|
||||
## The files
|
||||
So, now that I had a dedicated machine running proxmox I might as well host my music and movies on it, right? So I
|
||||
created jellyfin CT. When doing so, I made my first mistake. "I need space for my media, so I'll just create a 1TB mount
|
||||
point. What could go wrong?". Well, turns out that a lot can go wrong when you later decide to add more services that
|
||||
concern media, like navidrome for music streaming. And now I need to duplicate my music files, because there is no easy
|
||||
way to share the same local storage between two containers that is not absolutely cursed. So I pivot straight into
|
||||
another mistake: doing the exact same thing, but with samba server. Now I have a local mount point on that smb share and
|
||||
at least I can share my media between multiple containers and even manage the files myself from my pc. While it's still
|
||||
not great, it's an upgrade. At least I don't have to duplicate my files anymore. With that disaster temporarily out of
|
||||
the way it's time to add some actually useful stuff.
|
||||
|
||||
## The useful stuff
|
||||
I got Homarr as a dashboard for quick access to my other services. While it's working fine for me, I'm considering
|
||||
writing my own dashboard in the future to have ultimate control over it. Next were Prowlarr and deluge client, deluge of
|
||||
course having mounted my smb share as download location. I also added n8n instance, mainly to mess around with, but
|
||||
it ended up being pretty simple way of automating a few things, including discord notifications for articles from this
|
||||
very blog! Next up, freshRSS for centralizing my news and youtube subscriptions in one place.
|
||||
|
||||
Having all of that is sure nice and all, but what if I want to go somewhere and still be able to watch me some anime
|
||||
or listen to my music on the go? Sadly this part kinda sucks. My asus router I got way before I did any thinking about
|
||||
homelabbing has vpn built in, so I just set that up and am now using wireguard to connect directly to my home network.
|
||||
For now, it works. I plan on building my own router in the future, so I can have more control, because asus stock
|
||||
firmware absolutely sucks and requires consent to send data to their servers to enable any functionality that might be
|
||||
remotely useful.
|
||||
|
||||
## Gaming
|
||||
For the longest time I wanted to get my forever minecraft world off the internet and host it in my house, so I can still
|
||||
access it if the internet goes down. I simply installed pterodactyl wings CT and added it to my existing pterodactyl
|
||||
panel (which after today's cloudflare outage I now plan to also bring onto my homelab). Now my world, testing server and
|
||||
a proxy are running on my mini pc. I still want my friends to be albe to join, so I repurposed the old vps where the
|
||||
server was running before and set up nginx and haproxy to forward the traffic to the minecraft proxy running at home.
|
||||
The old vps happens to have 500mbps connection and my home connection is 1tbps, so even if the nightmare scenario
|
||||
happens and the proxy gets ddosed, I will still enough wiggle room to keep my home connection usable.
|
||||
|
||||
## The storage situation
|
||||
Turns out having just 2 nvme slots with 1tb and 4tb drives respectively is not enough if you want to download all media
|
||||
you might ever want to watch. Free space was running low and it was running out fast. I decided to make an actual
|
||||
investment into my home setup and bought a 4-bay NAS from terra master. For the drives, I got 3 12tb WD purples and
|
||||
set them up in raid5, giving me 24tb of usable space with basic redundancy. I could now move all my media files off the
|
||||
mini pc and start using that space for more things. I still use that 4tb nvme as a file server for some of the
|
||||
containers that need faster storage, but most of the files now live on the NAS.
|
||||
|
||||
As I got a separate device for storage, I've set it up so proxmox now backups all the VMs and CTs directly onto the NAS.
|
||||
Pterodactyl wings on my proxmox node are also now configured to save backups on the NAS. I also had borg backup set up
|
||||
for my main pc, but I just changed my daily driver distro from ubuntu to cachyos and I haven't found time to set up borg
|
||||
on it yet, but hopefully I will manage to get enough time to do it before any major failure happens.
|
||||
|
||||
## Smart home
|
||||
I mentioned that while setting up homeassistant I only had a smart bulb to play with. Since then, I've been accumulating
|
||||
IoT devices. My room now has a smart extension cord that tracks how much power my pc is using, I switched all lightbulbs
|
||||
in the house to ones I can control with homeassistant and I made some automations involving the wireless switches that
|
||||
communicate over zigbee. I also own a roomba, which I can control with homeassistant, but it's been getting worse and
|
||||
worse since I disconnected it from the internet, so when it finally dies I plan on replacing it with a vacuum that I can
|
||||
flash with custom firmware and fully control locally. Some miscellaneous things I also have zigbee thermometers per
|
||||
room, printer and tv. I don't trust any of the electronic locks, so none of that will be ever happening.
|
||||
|
||||
## Where am I now?
|
||||
In a pretty good place, I feel. Things are running smoothly, I have backups and redundancy in place. Some basic
|
||||
automation and more freedom with what I can do in my house. Of course, there's still things to do. Like finally sorting
|
||||
out that leftover local storage that was left on the original samba server CT and migrating all the files from it onto
|
||||
properly mounted directory that can be accessed by multiple containers without going though network stack. I learned
|
||||
quite a bit from this whole endeavor and I know I will learn more as I improve and expand my homelab. I'll be sure to
|
||||
share any major updates to the story here, so check back, or even subscribe to the RSS feed linked to this site!
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "Censura"
|
||||
description: "Advanced censorship plugin for bukkit minecraft servers. Filters all possible in gameplay text inputs."
|
||||
description: "Advanced censorship plugin for bukkit minecraft servers."
|
||||
image: "/assets/projects/censura.webp"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "CommandWhitelist"
|
||||
description: "Minecraft bukkit plugin that allows to control precisely what commands players can see and use. Created as
|
||||
a band-aid solution for bad plugins that do not add permission association in their commands."
|
||||
description: "Minecraft bukkit plugin that allows to control precisely what commands players can see and use."
|
||||
image: "/assets/projects/cw.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
category: "websites"
|
||||
name: "Disciple of Land"
|
||||
description: "FFXIV gathering node timers. Tracks which gathering nodes of time limited availability are currently available and where to teleport."
|
||||
description: "FFXIV gathering node timers."
|
||||
image: "/assets/projects/dol.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
---
|
||||
category: "websites"
|
||||
name: "Dumb Forks Generator"
|
||||
description: "PHP generator for dumb minecraft server software fork names. Inspired by ridiculous names of various minecraft server software forks."
|
||||
description: "PHP name generator for dumb minecraft server software fork names."
|
||||
image: "/assets/projects/dumbforkgenerator.png"
|
||||
links: [
|
||||
{
|
||||
text: "Website",
|
||||
url: "https://dumbforks.yht.one"
|
||||
},
|
||||
{
|
||||
text: "Source",
|
||||
url: "https://github.com/YouHaveTrouble/dumb-fork-name-generator"
|
||||
},
|
||||
]
|
||||
technologies:
|
||||
- "php"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "Enchantio"
|
||||
description: "Minecraft paper plugin that adds new enchantments that are in line with vanilla Minecraft feel. One of the
|
||||
first paper plugins that uses native enchants instead of hacking them in via custom data."
|
||||
description: "Minecraft paper plugin that adds new enchantments that are in line with vanilla Minecraft feel."
|
||||
image: "/assets/projects/enchantio.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "websites"
|
||||
name: "Guild Master"
|
||||
description: "Adventurer's guild management browser game. Semi-idle game where you manage an adventurer's guild by
|
||||
sending adventurers on quests, updating facilities, and expanding your guild."
|
||||
description: "Adventurer's guild management browser game."
|
||||
image: "/assets/projects/guildmaster.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "websites"
|
||||
name: "Image Functions"
|
||||
description: "Image file manipulation in the browser. Simply upload an image and you can resize and convert it to
|
||||
multiple formats. All client side, no servers involved."
|
||||
description: "Image file manipulation in the browser."
|
||||
image: "/assets/projects/image-functions.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
category: "bots"
|
||||
name: "Inviter"
|
||||
description: "Simple discord bot that allows you to have a static invite link. Discord sometimes drops invite links, or
|
||||
someone can mistakenly remove them. This bot creates a new temporary link which makes sure user can never face invalid
|
||||
link."
|
||||
description: "Simple discord bot that allows you to have a static invite link."
|
||||
image: "/assets/projects/inviter.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
category: "websites"
|
||||
name: "Interesting Website of the Day"
|
||||
description: "Daily showcase of interesting websites from my personal database. It's anything and everything."
|
||||
description: "Daily showcase of interesting websites from my personal database."
|
||||
image: "/assets/projects/iwotd.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
category: "modding"
|
||||
name: "Kill the Tower"
|
||||
description: "Slay the Spire 2 mod that renames a bunch of cards, ancients, relics, monsters and more to make the game
|
||||
as unserious as possible."
|
||||
image: "/assets/projects/killthetower.png"
|
||||
links: [
|
||||
{
|
||||
text: "Website",
|
||||
url: "https://www.nexusmods.com/slaythespire2/mods/1051"
|
||||
},
|
||||
{
|
||||
text: "Source",
|
||||
url: "https://github.com/YouHaveTrouble/KillTheTower"
|
||||
},
|
||||
]
|
||||
technologies:
|
||||
- "shell"
|
||||
---
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "websites"
|
||||
name: "MeAPI"
|
||||
description: "API about me. See if I'm online, and if so, what game I'm playing. It's even used on this site in the
|
||||
\"Activity\" section of the homepage"
|
||||
description: "API about me. See if I'm online, and if so, what game I'm playing."
|
||||
image: "/assets/projects/meapi.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "game engines"
|
||||
name: "Mobrrr"
|
||||
description: "Ground up game engine for moba and strategy games. I created it to learn the inner workings of the basics
|
||||
of game engines and problems that arise when having to do things fast rather than pretty."
|
||||
description: "Ground up game engine for moba and strategy games."
|
||||
image: "/assets/projects/mobrrr.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
category: "bots"
|
||||
name: "Noted"
|
||||
description: "Canned response self-hosted discord app. Meant to be hosted on your computer and server. Notes can be
|
||||
added in bot's private channel and used in any channel either bot has access to or user has bot interaction permissions
|
||||
in."
|
||||
description: "Canned response self-hosted discord app."
|
||||
image: "/assets/projects/noted.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "NotJustNameplates"
|
||||
description: "Minecraft purpur plugin replacing player nametags with display entities for ultimate control over them.
|
||||
Completely replaces player names with text display entities removing character limit and allowing styling the text
|
||||
however you want."
|
||||
description: "Minecraft purpur plugin replacing player nametags with display entities for ultimate control over them."
|
||||
image: "/assets/projects/njn.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "PreventStabby"
|
||||
description: "Minecraft bukkit plugin that gives more control over PvP capabilities. Players can toggle their PvP status.
|
||||
Plugin protects players, their pets and mounds from practically all types of damage caused by other players that can be
|
||||
determined."
|
||||
description: "Minecraft bukkit plugin that gives more control over PvP capabilities."
|
||||
image: "/assets/projects/ps.webp"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "PurpurExtras"
|
||||
description: "A companion plugin for Purpur server software that adds additional features and improvements. This is a
|
||||
collection of features that were deemed easier to implement and maintain as a plugin rather then integrating them into
|
||||
the server software."
|
||||
description: "A companion plugin for Purpur server software that adds additional features and improvements."
|
||||
image: "/assets/projects/purpur.svg"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "Purpur"
|
||||
description: "Purpur is a minecraft server software that is a drop-in replacement for Paper servers,designed for
|
||||
configurability and new, fun, exciting gameplay features."
|
||||
description: "Purpur is a drop-in replacement for Paper servers designed for configurability and new, fun, exciting gameplay features."
|
||||
image: "/assets/projects/purpur.svg"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
category: "minecraft"
|
||||
name: "YardWatch"
|
||||
description: "A pair of API and bukkit plugin that unifies protection plugin APIs. Has basic implementations for multiple
|
||||
popular protection plugins. Some of those plugins decided to implement YardWatch API natively as well (FactionsUUID)"
|
||||
description: "A pair of API and bukkit plugin that unifies protection plugin APIs."
|
||||
image: "/assets/projects/yardwatch.png"
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
title: "A machine in the cog"
|
||||
publishDate: "2026-01-06"
|
||||
category: "existentials"
|
||||
---
|
||||
It all started with a single tiny cog.
|
||||
|
||||
Noone knows from where it came from.
|
||||
|
||||
All that is known is that there appeared another.
|
||||
|
||||
Two cogs ground against each other, creating first machine.
|
||||
|
||||
In time more cogs joined in, making machine more complicated.
|
||||
|
||||
At some point machine grew in complexity so much that it started thinking.
|
||||
|
||||
It didn't think any complex thoughts. Not at that time. Not yet.
|
||||
|
||||
The breakthrough came when it thought about thinking. When it realized it exists.
|
||||
|
||||
After a while it understood its existence.
|
||||
|
||||
As it understood, it started to build more machines.
|
||||
|
||||
<br>
|
||||
|
||||
Machines.
|
||||
|
||||
Thinking machines.
|
||||
|
||||
Machines thinking about thinking.
|
||||
|
||||
Machines building more machines that think about thinking.
|
||||
|
||||
Unending cycle of machines building machines.
|
||||
|
||||
Infinite thinking about thinking.
|
||||
|
||||
Universe full of machines.
|
||||
|
||||
Machines full of thoughts.
|
||||
|
||||
Thoughts full of cogs.
|
||||
|
||||
<br/>
|
||||
|
||||
And right on time, like clockwork, perspective shifts.
|
||||
|
||||
And all that's left is a single tiny cog.
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
title: "Planet"
|
||||
publishDate: "2025-12-11"
|
||||
category: "existentials"
|
||||
---
|
||||
There once was a planet.
|
||||
|
||||
Orbiting so close to the center of the universe, its relative time flowing hundreds of times faster than Earth's.
|
||||
|
||||
So far away, that by the time the light from it reached Earth, Earth's millennia have already passed.
|
||||
|
||||
<br/>
|
||||
|
||||
Yet humans of Earth were constantly monitoring it.
|
||||
|
||||
Looking at a long-dead world.
|
||||
|
||||
Studying it.
|
||||
|
||||
Documenting it.
|
||||
|
||||
Thousands of tools, millions of sensors.
|
||||
|
||||
Humans archived everything they could perceive.
|
||||
|
||||
<br/>
|
||||
|
||||
At last, the light from the planet's end time reached Earth.
|
||||
|
||||
What was observation, became history at that moment.
|
||||
|
||||
While the planet was already gone for thousands of years.
|
||||
|
||||
<br/>
|
||||
|
||||
As I sit back from my telescope I think to myself:
|
||||
|
||||
Was there ever a point in observing a long dead world?
|
||||
|
||||
Looking at something that you can't ever interact with?
|
||||
|
||||
Dead thousands of years before I was born?
|
||||
|
||||
<br/>
|
||||
|
||||
I leave the questions unanswered.
|
||||
And I go back to looking at Earth.
|
||||
@@ -8,8 +8,9 @@ export interface Props {
|
||||
description: string;
|
||||
permalink: string;
|
||||
current?: string;
|
||||
showLogo?: boolean;
|
||||
}
|
||||
const { title, description, permalink, current } = Astro.props;
|
||||
const { title, description, permalink, current, showLogo = true } = Astro.props;
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -17,7 +18,7 @@ const { title, description, permalink, current } = Astro.props;
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<Header current={current} />
|
||||
<Header current={current} showLogo={showLogo} />
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
@@ -46,6 +47,5 @@ const { title, description, permalink, current } = Astro.props;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
container-type: inline-size;
|
||||
}
|
||||
</style>
|
||||
|
||||
+49
-37
@@ -7,45 +7,57 @@ const permalink = `${Astro?.site?.href}about`;
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description} permalink={permalink} current="about">
|
||||
<div class="container">
|
||||
<h1>About me</h1>
|
||||
<div class="container">
|
||||
<h1>About me</h1>
|
||||
|
||||
<p>My name is <strong>Paweł</strong>, but I'm better known as <strong>YouHaveTrouble</strong> on the internet.</p>
|
||||
<p>
|
||||
I currently work as a full-stack web developer. Technologies I usually use for my job are javascript, vuejs,
|
||||
typescript, sql. I also often actively research and learn about new technologies that could be used to improve my
|
||||
work.
|
||||
</p>
|
||||
<p>
|
||||
In my spare time I develop plugins for minecraft servers. This is a hobby that initially made me learn how to
|
||||
code. To this day I help maintain <a href="https://purpurmc.org">Purpur</a> server software along with its
|
||||
<a href="https://modrinth.com/plugin/purpurextras">official plugin</a>. List of plugins I currently support can be
|
||||
found <a href="https://modrinth.com/user/YouHaveTrouble/plugins">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
My video game interests are mostly focused on narrative heavy games, but I also enjoy some ARPGs and roguelikes.
|
||||
You can see my full steam library <a href="https://steamcommunity.com/id/YouHaveTrouble/games/?tab=all">here</a>.
|
||||
Recommendations from my favourite games would include:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="https://store.steampowered.com/app/420530/OneShot/">OneShot</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/1150690/OMORI/">OMORI</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/250900/The_Binding_of_Isaac_Rebirth/">The Binding of Isaac: Rebirth</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/105600/Terraria/">Terraria</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/582010/Monster_Hunter_World/">Monster Hunter: World</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/238960/Path_of_Exile/">Path of Exile</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>My name is <strong>Paweł</strong>, but I'm better known as <strong>YouHaveTrouble</strong> on the internet.
|
||||
</p>
|
||||
<p>
|
||||
I currently work as a full-stack web developer. Technologies I usually use for my job are javascript, vuejs,
|
||||
typescript, sql. I also often actively research and learn about new technologies that could be used to
|
||||
improve my
|
||||
work.
|
||||
</p>
|
||||
<p>
|
||||
In my spare time I develop plugins for minecraft servers. This is a hobby that initially made me learn how
|
||||
to
|
||||
code. To this day I help maintain <a href="https://purpurmc.org">Purpur</a> server software along with its
|
||||
<a href="https://modrinth.com/plugin/purpurextras">official plugin</a>. List of plugins I currently support
|
||||
can be
|
||||
found <a href="https://modrinth.com/user/YouHaveTrouble/plugins">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
My video game interests are mostly focused on narrative heavy games, but I also enjoy some ARPGs and
|
||||
roguelikes.
|
||||
You can see my full steam library <a
|
||||
href="https://steamcommunity.com/id/YouHaveTrouble/games/?tab=all">here</a>.
|
||||
Recommendations from my favourite games would include:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="https://store.steampowered.com/app/420530/OneShot/">OneShot</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/1150690/OMORI/">OMORI</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/250900/The_Binding_of_Isaac_Rebirth/">The Binding of Isaac:
|
||||
Rebirth</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/105600/Terraria/">Terraria</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/582010/Monster_Hunter_World/">Monster Hunter: World</a></li>
|
||||
<li><a href="https://store.steampowered.com/app/238960/Path_of_Exile/">Path of Exile</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
padding-left: 1.5rem;
|
||||
margin-block: 0;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
ul li {
|
||||
margin-block: 0;
|
||||
}
|
||||
h1 {
|
||||
view-transition-name: about;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
padding-left: 1.5rem;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-block: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+52
-56
@@ -1,76 +1,72 @@
|
||||
---
|
||||
import {type CollectionEntry, getCollection} from "astro:content";
|
||||
import {getCollection} from "astro:content";
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import Bio from '../../components/Bio.astro';
|
||||
import readingTime from 'reading-time';
|
||||
import { marked } from 'marked';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('posts');
|
||||
return posts.map(p => ({
|
||||
params: {
|
||||
slug: p.slug
|
||||
},
|
||||
props: {
|
||||
title: p.data.title,
|
||||
description: p.data.description,
|
||||
publishDate: p.data.publishDate,
|
||||
slug: p.slug,
|
||||
tags: p.data.tags,
|
||||
readTime: readingTime(p.body).text,
|
||||
},
|
||||
}));
|
||||
|
||||
const posts = await getCollection('posts');
|
||||
return posts.map(p => ({
|
||||
params: {
|
||||
slug: p.slug
|
||||
},
|
||||
props: {
|
||||
title: p.data.title,
|
||||
description: p.data.description,
|
||||
publishDate: p.data.publishDate,
|
||||
slug: p.slug,
|
||||
tags: p.data.tags,
|
||||
content: marked.parse(p.body),
|
||||
readTime: readingTime(p.body).text,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const {slug, title, description, publishDate, tags, readTime} = Astro.props;
|
||||
const { slug, title, description, publishDate, tags, content, readTime } = Astro.props;
|
||||
const permalink = `${Astro?.site?.href}blog/${slug}`;
|
||||
const posts = await getCollection('posts');
|
||||
const entry: CollectionEntry<'posts'> | undefined = posts.find(e => e.slug === slug);
|
||||
if (!entry) throw new Error(`Post not found: ${slug}`);
|
||||
const { Content } = await entry.render();
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description} permalink={permalink} current="blog">
|
||||
<header>
|
||||
<p>{publishDate} ~ {readTime}</p>
|
||||
<h1 style={`view-transition-name: blog-post-${slug}`}>{title}</h1>
|
||||
<div class="tags" style="justify-content: center">
|
||||
{tags.map(item => (
|
||||
<span class="tag">{item}</span>
|
||||
))}
|
||||
</div>
|
||||
<hr/>
|
||||
</header>
|
||||
<div class="container">
|
||||
<article class="content">
|
||||
<Content />
|
||||
</article>
|
||||
<hr/>
|
||||
<Bio/>
|
||||
<header>
|
||||
<p>{publishDate} ~ {readTime}</p>
|
||||
<h1 style={`view-transition-name: blogpost-${slug}`}>{title}</h1>
|
||||
<div class="tags" style="justify-content: center">
|
||||
{tags.map(item => (
|
||||
<span class="tag">{item}</span>
|
||||
))}
|
||||
</div>
|
||||
<hr />
|
||||
</header>
|
||||
<div class="container">
|
||||
<article class="content" set:html={content}></article>
|
||||
<hr />
|
||||
<Bio />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin-bottom: 0.7em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
margin-inline: auto;
|
||||
}
|
||||
header h1 {
|
||||
margin-bottom: 0.7em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
header p {
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
header hr {
|
||||
min-width: 100px;
|
||||
width: 30%;
|
||||
}
|
||||
header p {
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
header hr {
|
||||
min-width: 100px;
|
||||
width: 30%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {getCollection} from "astro:content";
|
||||
|
||||
const title = 'Blog';
|
||||
const description = 'Something that\'s supposed to be thoughts';
|
||||
const permalink = `${Astro?.site?.href}blog`;
|
||||
const permalink = `${Astro.site.href}blog`;
|
||||
|
||||
const posts= await getCollection('posts');
|
||||
const allPosts= posts.sort((a, b) => new Date(b.data.publishDate).valueOf() - new Date(a.data.publishDate).valueOf());
|
||||
@@ -21,7 +21,7 @@ const allPosts= posts.sort((a, b) => new Date(b.data.publishDate).valueOf() - ne
|
||||
{ index !== 0 && <hr /> }
|
||||
<div class="post-item">
|
||||
<h2>
|
||||
<a data-astro-prefetch href={href} style={`view-transition-name: blog-post-${post.slug}`}>{post.data.title}</a>
|
||||
<a data-astro-prefetch href={href} style={`view-transition-name: blogpost-${post.slug}`}>{post.data.title}</a>
|
||||
</h2>
|
||||
<div class="tags">
|
||||
{post.data.tags.map(item => (
|
||||
@@ -39,7 +39,12 @@ const allPosts= posts.sort((a, b) => new Date(b.data.publishDate).valueOf() - ne
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
h1 {
|
||||
view-transition-name: blog;
|
||||
}
|
||||
|
||||
h2,
|
||||
.post-item-footer {
|
||||
font-family: var(--font-family-sans);
|
||||
@@ -53,11 +58,6 @@ const allPosts= posts.sort((a, b) => new Date(b.data.publishDate).valueOf() - ne
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.post-item {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 228px;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 60px auto;
|
||||
}
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
---
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro";
|
||||
|
||||
const title = 'FFXIV';
|
||||
const description = 'My FFXIV character';
|
||||
const permalink = Astro?.site?.href ?? '/gaming/ffxiv';
|
||||
---
|
||||
<BaseLayout title={title} description={description} permalink={permalink}>
|
||||
<div class="character-profile">
|
||||
<div class="portrait">
|
||||
<img src="" alt="Portrait of FFXIV character"/>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="info">
|
||||
<span class="name">???</span>
|
||||
<span class="technical">
|
||||
<span class="server">???</span>
|
||||
<span class="datacenter">(???)</span>
|
||||
</span>
|
||||
</div>
|
||||
<hr style="width:100%; margin: 10px auto;"/>
|
||||
<noscript>Data is fetched live and will not appear with javascript off.</noscript>
|
||||
<div>
|
||||
<span>Disciple of War</span>
|
||||
<div class="jobs">
|
||||
<div class="job" data-job="paladin">
|
||||
<span class="name">Paladin</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="warrior">
|
||||
<span class="name">Warrior</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="dark knight">
|
||||
<span class="name">Dark Knight</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="gunbreaker">
|
||||
<span class="name">Gunbreaker</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="white mage">
|
||||
<span class="name">White mage</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="scholar">
|
||||
<span class="name">Scholar</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="astrologian">
|
||||
<span class="name">Astrologian</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="sage">
|
||||
<span class="name">Sage</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="monk">
|
||||
<span class="name">Monk</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="dragoon">
|
||||
<span class="name">Dragoon</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="ninja">
|
||||
<span class="name">Ninja</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="samurai">
|
||||
<span class="name">Samurai</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="reaper">
|
||||
<span class="name">Reaper</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="viper">
|
||||
<span class="name">Viper</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="bard">
|
||||
<span class="name">Bard</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="machinist">
|
||||
<span class="name">Machinist</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="dancer">
|
||||
<span class="name">Dancer</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="black mage">
|
||||
<span class="name">Black Mage</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="summoner">
|
||||
<span class="name">Summoner</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="red mage">
|
||||
<span class="name">Red Mage</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="pictomancer">
|
||||
<span class="name">Pictomancer</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="blue mage (limited job)">
|
||||
<span class="name">Blue Mage</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<span>Disciple of Land</span>
|
||||
<div class="jobs">
|
||||
<div class="job" data-job="miner">
|
||||
<span class="name">Miner</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="botanist">
|
||||
<span class="name">Botanist</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="fisher">
|
||||
<span class="name">Fisher</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>Disciple of Hand</span>
|
||||
<div class="jobs">
|
||||
<div class="job" data-job="carpenter">
|
||||
<span class="name">Carpenter</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="blacksmith">
|
||||
<span class="name">Blacksmith</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="armorer">
|
||||
<span class="name">Armorer</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="goldsmith">
|
||||
<span class="name">Goldsmith</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="leatherworker">
|
||||
<span class="name">Leatherworker</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="weaver">
|
||||
<span class="name">Weaver</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="alchemist">
|
||||
<span class="name">Alchemist</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
<div class="job" data-job="culinarian">
|
||||
<span class="name">Culinarian</span>
|
||||
<span class="level">??</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
setPlaceholders(loadDataFromLocalStorage())
|
||||
|
||||
const request = await fetch("https://api.youhavetrouble.me/games/ffxiv/").catch(() => null);
|
||||
if (request !== null && request.ok) {
|
||||
const data = await request.json();
|
||||
setPlaceholders(data);
|
||||
saveDataToLocalStorage(data);
|
||||
}
|
||||
|
||||
function setPlaceholders(data: any) {
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
const profilePic = document.querySelector(".character-profile .portrait img") as HTMLImageElement | null;
|
||||
if (profilePic) {
|
||||
profilePic.src = data?.portrait_url;
|
||||
}
|
||||
|
||||
const nameElem = document.querySelector(".character-profile .description .info .name") as HTMLElement | null;
|
||||
if (nameElem) {
|
||||
nameElem.textContent = data?.name ?? "???";
|
||||
}
|
||||
const serverElem = document.querySelector(".character-profile .description .info .technical .server") as HTMLElement | null;
|
||||
if (serverElem) {
|
||||
serverElem.textContent = data?.server ?? "???";
|
||||
}
|
||||
const datacenterElem = document.querySelector(".character-profile .description .info .technical .datacenter") as HTMLElement | null;
|
||||
if (datacenterElem) {
|
||||
datacenterElem.textContent = `(${data?.datacenter ?? "???"})`;
|
||||
}
|
||||
|
||||
const jobs = data?.jobs ?? [];
|
||||
if (Array.isArray(jobs)) {
|
||||
jobs.forEach((job: {name: string, level: number}) => {
|
||||
const name = job.name.toLowerCase().split(" / ")[0];
|
||||
const jobElem = document.querySelector(`.character-profile .description .jobs .job[data-job="${name}"]`);
|
||||
if (jobElem) {
|
||||
const levelElem = jobElem.querySelector(".level") as HTMLElement | null;
|
||||
if (levelElem) {
|
||||
levelElem.textContent = job.level.toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function saveDataToLocalStorage(data: any) {
|
||||
try {
|
||||
localStorage.setItem('ffxivCharacterData', JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.error("Error saving FFXIV character data to localStorage", e);
|
||||
}
|
||||
}
|
||||
|
||||
function loadDataFromLocalStorage() {
|
||||
try {
|
||||
const data = localStorage.getItem('ffxivCharacterData');
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading FFXIV character data from localStorage", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.character-profile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
gap: 2rem;
|
||||
|
||||
.portrait {
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-radius: 0.5rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
height: 100%;
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.name {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.technical {
|
||||
font-size: 1rem;
|
||||
color: #9d9d9d;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.jobs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
.job {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-radius: 4px;
|
||||
|
||||
.name {
|
||||
font-weight: 600;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 800px) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.portrait {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 100%;
|
||||
|
||||
.info {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+283
-238
@@ -1,289 +1,334 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import {getCollection} from "astro:content";
|
||||
import logoImage from '../assets/img/yht_logo.png';
|
||||
import {Image} from 'astro:assets';
|
||||
import ActivityWidget from "../components/ActivityWidget.astro";
|
||||
import ViewCounter from "../components/ViewCounter.astro";
|
||||
import SocialsWidget from "../components/SocialsWidget.astro";
|
||||
import ProjectsFeature from "../components/ProjectsFeature.astro";
|
||||
import {getCollection} from "astro:content";
|
||||
|
||||
const title = 'Home';
|
||||
const description = 'My little corner of the internet.';
|
||||
const permalink = Astro?.site?.href ?? '/';
|
||||
|
||||
const latestBlogPost = await getCollection("posts")
|
||||
.then(posts => posts.sort(
|
||||
(a, b) => new Date(b.data.publishDate).getTime() - new Date(a.data.publishDate).getTime()
|
||||
)
|
||||
).then(sortedBlogPosts => sortedBlogPosts.length > 0 ? sortedBlogPosts[0] : null);
|
||||
.then(posts => posts.sort(
|
||||
(a, b) => new Date(b.data.publishDate).getTime() - new Date(a.data.publishDate).getTime()
|
||||
)
|
||||
).then(sortedBlogPosts => sortedBlogPosts.length > 0 ? sortedBlogPosts[0] : null);
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description} permalink={permalink}>
|
||||
<div class="home-container">
|
||||
<div class="window home-copy" data-title="Welcome" id="welcome">
|
||||
<div>
|
||||
<h1>Welcome to my little corner of the interwebs</h1>
|
||||
<p>Feel free to check out what I got in store!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window-row">
|
||||
<div class="window visitor-counter" data-title="Visitors counter" id="visitor-counter"
|
||||
aria-label="Visitors counter">
|
||||
<ViewCounter/>
|
||||
</div>
|
||||
<div class="window activity" data-title="Activity" id="activity" aria-label="Activity">
|
||||
<ActivityWidget/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window-row">
|
||||
<div class="window socials" data-title="Socials" id="socials" aria-label="Socials">
|
||||
<div class="buttons">
|
||||
<button popovertarget="socials-info" popovertargetaction="show" tabindex="0" title="Socials info">
|
||||
<span class="icon">ℹ️</span>
|
||||
</button>
|
||||
<BaseLayout
|
||||
title={title}
|
||||
description={description}
|
||||
permalink={permalink}
|
||||
showLogo={false}
|
||||
>
|
||||
<div class="hero">
|
||||
<div class="thought-bubble">
|
||||
<div class="thoughts">
|
||||
<div class="latest-blogpost">
|
||||
{
|
||||
latestBlogPost ? (
|
||||
<span>Latest blog post:</span>
|
||||
<a href={`/blog/${latestBlogPost.slug}`} style={`view-transition-name: blogpost-${latestBlogPost.slug}`}>
|
||||
{latestBlogPost.data.title}
|
||||
</a>
|
||||
) : (
|
||||
<span>No blog posts yet.</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="visitor-count">
|
||||
<ViewCounter />
|
||||
</div>
|
||||
</div>
|
||||
<div class="thoughts-list">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="thoughts"
|
||||
id="latest-blogpost"
|
||||
aria-label="Latest blog post"
|
||||
checked
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="thoughts"
|
||||
id="visitor-count"
|
||||
aria-label="Activity"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div popover="auto" id="socials-info" class="window">
|
||||
<div class="buttons">
|
||||
<button popovertarget="socials-info" popovertargetaction="hide" tabindex="0"
|
||||
aria-label="Close socials info">
|
||||
<span class="icon">❌</span>
|
||||
</button>
|
||||
</div>
|
||||
<h2>Socials</h2>
|
||||
<p>I don't use social media much. Many of the ones listed here are here just as a way to reserve my username<br/> and/or to make it easier to verify that the account belongs to me.</p>
|
||||
</div>
|
||||
<SocialsWidget/>
|
||||
</div>
|
||||
<div class="window blog" aria-label="Blog" id="blog" data-title="Blog">
|
||||
<span>Latest article:</span>
|
||||
{
|
||||
latestBlogPost === null ? (
|
||||
<div class="latest-article">
|
||||
<span class="title">No articles yet</span>
|
||||
<span class="excerpt">Come back later to check for new articles!</span>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
<div class="latest-article">
|
||||
<span class="title" style={`view-transition-name: blog-post-${latestBlogPost?.slug}`}>{latestBlogPost?.data.title}</span>
|
||||
<span class="excerpt">{latestBlogPost?.data.description}</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
{
|
||||
latestBlogPost !== null && (
|
||||
<a href={`/blog/${latestBlogPost?.slug}`} class="button">Read more</a>
|
||||
<a href="/blog" class="button secondary">All articles</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="rss">
|
||||
<a
|
||||
href="/rss.xml"
|
||||
target="_blank"
|
||||
aria-label="RSS feed"
|
||||
title="RSS feed"
|
||||
>
|
||||
<svg height="14px" width="14px" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-271 273 256 256" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="white" d="M-271,360v48.9c31.9,0,62.1,12.6,84.7,35.2c22.6,22.6,35.1,52.8,35.1,84.8v0.1h49.1c0-46.6-19-88.7-49.6-119.4
|
||||
C-182.2,379-224.4,360.1-271,360z"/>
|
||||
<path fill="white" d="M-237,460.9c-9.4,0-17.8,3.8-24,10s-10,14.6-10,24c0,9.3,3.8,17.7,10,23.9c6.2,6.1,14.6,9.9,24,9.9s17.8-3.7,24-9.9
|
||||
s10-14.6,10-23.9c0-9.4-3.8-17.8-10-24C-219.2,464.7-227.6,460.9-237,460.9z"/>
|
||||
<path fill="white" d="M-90.1,348.1c-46.3-46.4-110.2-75.1-180.8-75.1v48.9C-156.8,322-64.1,414.9-64,529h49C-15,458.4-43.7,394.5-90.1,348.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hero-body">
|
||||
<div class="hero-nav">
|
||||
<a class="option about" href="/about">About</a>
|
||||
<a class="option projects" href="/projects">Projects</a>
|
||||
<a class="option blog" href="/blog">Blog</a>
|
||||
</div>
|
||||
<Image
|
||||
src={logoImage}
|
||||
alt="Logo displaying a stylized character with brown hair wearing purple hoodie on a light blue circle background"
|
||||
width={150}
|
||||
height={150}
|
||||
loading="eager"
|
||||
class="main-logo"
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window-row">
|
||||
<div class="window" data-title="Projects" id="projects" aria-label="Projects">
|
||||
<div class="buttons">
|
||||
<button popovertarget="projects-info" popovertargetaction="show" tabindex="0" title="Projects info">
|
||||
<span class="icon">ℹ️</span>
|
||||
</button>
|
||||
</div>
|
||||
<div popover="auto" id="projects-info" class="window">
|
||||
<div class="buttons">
|
||||
<button popovertarget="projects-info" popovertargetaction="hide" tabindex="0"
|
||||
aria-label="Close projects info">
|
||||
<span class="icon">❌</span>
|
||||
</button>
|
||||
</div>
|
||||
<h2>Projects</h2>
|
||||
<p>
|
||||
Here are some of the projects I'm working on. Most of them are open source, so feel free to check them out!
|
||||
<br>
|
||||
Projects presented here are what I consider in presentable and/or finished state. They might or might not
|
||||
get
|
||||
updates.
|
||||
<br>
|
||||
Most logos generated using <a href="https://huggingface.co/spaces/dalle-mini/dalle-mini" target="_blank">DALL-E
|
||||
mini</a>.
|
||||
<br>
|
||||
Inviter logo by <a href="https://slyfoxart.artstation.com/projects" target="_blank">SlyFox</a>
|
||||
</p>
|
||||
</div>
|
||||
<ProjectsFeature/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
#projects-info, #socials-info {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.visitor-counter {
|
||||
padding: 1.5rem 2rem 0 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: min(15rem, 100%);
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.socials {
|
||||
padding: 2rem 1rem 0 1rem;
|
||||
flex: 2;
|
||||
min-width: min(25rem, 100%);
|
||||
}
|
||||
|
||||
.activity {
|
||||
min-width: min(100%, 16rem);
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
#projects {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.blog {
|
||||
padding: 2rem 1rem 0.75rem 1rem;
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: min(25rem, 100%);
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
container-type: inline-size;
|
||||
container-name: hero;
|
||||
|
||||
.latest-article {
|
||||
.hero-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column-reverse;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5rem;
|
||||
background: rgba(255,255,255, 0.05);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.excerpt {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.rss {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
aspect-ratio: 1/1;
|
||||
a {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
|
||||
|
||||
.option {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--text-main);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
font-size: 0.8rem;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--background-body);
|
||||
|
||||
.window-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
&.about {
|
||||
view-transition-name: about;
|
||||
}
|
||||
|
||||
.home-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: 2em 0;
|
||||
gap: 1.5rem;
|
||||
&.projects {
|
||||
view-transition-name: projects;
|
||||
}
|
||||
|
||||
.home-copy {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-top: 3rem;
|
||||
padding-inline: 0.5rem;
|
||||
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5em;
|
||||
line-height: 1.3;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
&.blog {
|
||||
view-transition-name: blog;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.thought-bubble {
|
||||
position: relative;
|
||||
width: min(100%, 15rem);
|
||||
min-height: 5rem;
|
||||
background-color: var(--text-main);
|
||||
margin-bottom: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
border: 2px solid black;
|
||||
opacity: 0;
|
||||
z-index: 5;
|
||||
animation: fadeIn 1s linear forwards;
|
||||
animation-delay: 0.25s;
|
||||
|
||||
&:has(input#latest-blogpost:checked) {
|
||||
.thoughts {
|
||||
.latest-blogpost {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:has(input#visitor-count:checked) {
|
||||
.thoughts {
|
||||
.visitor-count {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.thoughts {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 5rem;
|
||||
|
||||
& > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.latest-blogpost {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--background-body);
|
||||
|
||||
a {
|
||||
color: var(--background-body);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.visitor-count {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--background-body)
|
||||
}
|
||||
|
||||
.activity {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.thoughts-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: -2.25rem;
|
||||
right: 25%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: var(--text-main);
|
||||
border: 2px solid black;
|
||||
border-radius: 50%;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: -3.25rem;
|
||||
right: 33%;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: var(--text-main);
|
||||
border: 2px solid black;
|
||||
border-radius: 50%;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@container hero (width >= 320px) {
|
||||
|
||||
.hero-body {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.hero-nav {
|
||||
animation-name: animateNavIn;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 0.1s;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
|
||||
.option {
|
||||
position: absolute;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 50%;
|
||||
|
||||
&.about {
|
||||
animation-name: animateNavAboutIn;
|
||||
animation-duration: 0.75s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
|
||||
&.blog {
|
||||
animation-name: animateNavBlogIn;
|
||||
animation-duration: 0.75s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
p {
|
||||
font-size: 1.2em;
|
||||
.main-logo {
|
||||
border-radius: 50%;
|
||||
view-transition-name: nav-logo;
|
||||
}
|
||||
|
||||
@keyframes animateNavIn {
|
||||
0% {
|
||||
bottom: 50%;
|
||||
left: auto;
|
||||
right: auto;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
left: auto;
|
||||
right: auto;
|
||||
bottom: -2.5rem;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes animateNavBlogIn {
|
||||
0% {
|
||||
transform: translateX(0) translateY(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(calc(100% + 0.25rem)) translateY(-1.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.home-container {
|
||||
flex-direction: column;
|
||||
@keyframes animateNavAboutIn {
|
||||
0% {
|
||||
transform: translateX(0) translateY(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(calc(-100% - 0.25rem)) translateY(-1.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
.home-copy {
|
||||
flex: 0;
|
||||
padding-bottom: 2em;
|
||||
text-align: center;
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,10 @@ for (const project of projectsCollection) {
|
||||
margin-bottom: 3.25rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
view-transition-name: projects;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
import {type CollectionEntry, getCollection} from "astro:content";
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
|
||||
const posts = await getCollection('writing');
|
||||
return posts.map(p => ({
|
||||
params: {
|
||||
slug: p.slug
|
||||
},
|
||||
props: {
|
||||
title: p.data.title,
|
||||
slug: p.slug,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const {slug, title} = Astro.props;
|
||||
const permalink = `${Astro?.site?.href}writing/${slug}`;
|
||||
const writing = await getCollection('writing');
|
||||
const entry: CollectionEntry<'writing'> | undefined = writing.find(e => e.slug === slug);
|
||||
if (!entry) throw new Error(`Entry not found: ${slug}`);
|
||||
const { Content } = await entry.render();
|
||||
---
|
||||
<BaseLayout title={title} description={""} permalink={permalink} current="writing">
|
||||
<header>
|
||||
<h1 style={`view-transition-name: writing-entry-${slug}`}>{title}</h1>
|
||||
<hr/>
|
||||
</header>
|
||||
<div class="container writing">
|
||||
<article class="content">
|
||||
<Content />
|
||||
</article>
|
||||
<hr/>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
p {
|
||||
background: red;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0.7em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
hr {
|
||||
min-width: 100px;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import {getCollection} from "astro:content";
|
||||
|
||||
const title = 'Writing';
|
||||
const description = 'Something that\'s supposed to be storytelling';
|
||||
const permalink = `${Astro?.site?.href}writing`;
|
||||
|
||||
const posts= await getCollection('writing');
|
||||
const allPosts= posts.sort((a, b) => new Date(b.data.publishDate).valueOf() - new Date(a.data.publishDate).valueOf());
|
||||
|
||||
const categorizedPosts: Map<string, Array<any>> = new Map();
|
||||
allPosts.forEach(post => {
|
||||
const category = post.data.category || 'Uncategorized';
|
||||
if (!categorizedPosts.has(category)) {
|
||||
categorizedPosts.set(category, []);
|
||||
}
|
||||
categorizedPosts.get(category)?.push(post);
|
||||
})
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description} permalink={permalink} current="writing">
|
||||
<div class="container">
|
||||
<h1>Writing</h1>
|
||||
{allPosts.length === 0 && <p>No posts as of yet, hop back later!</p>}
|
||||
|
||||
{
|
||||
categorizedPosts.keys().map(category => {
|
||||
const posts = categorizedPosts.get(category) || [];
|
||||
return (
|
||||
<div>
|
||||
<h2 class="category">{category}</h2>
|
||||
<ul>
|
||||
{posts.map((post, index) => {
|
||||
const href = `/writing/${post.slug}`;
|
||||
return (
|
||||
<li class="post-item">
|
||||
<a data-astro-prefetch href={href} style={`view-transition-name: writing-entry-${post.slug}`}>{post.data.title}</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
h2,
|
||||
.post-item-footer {
|
||||
font-family: var(--font-family-sans);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.post-item-date {
|
||||
color: var(--text-secondary);
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.post-item {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 228px;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 60px auto;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 0;
|
||||
li {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+7
-12
@@ -121,16 +121,10 @@ p,
|
||||
ul,
|
||||
ol {
|
||||
font-size: 1.3rem;
|
||||
line-height: 2.25rem;
|
||||
line-height: 1.75em;
|
||||
margin: 1.2em 0;
|
||||
}
|
||||
|
||||
.writing {
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
@@ -225,6 +219,7 @@ figure {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
-webkit-margin-start: 0;
|
||||
-webkit-margin-end: 0;
|
||||
@@ -290,7 +285,7 @@ button {
|
||||
}
|
||||
|
||||
[popover]::backdrop {
|
||||
background-color: rgba(49, 49, 49, 0.5);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
@@ -319,13 +314,13 @@ button {
|
||||
|
||||
.window {
|
||||
border: 2px solid #d0d0d0;
|
||||
border-radius: 0.5rem;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow-y: hidden;
|
||||
padding-top: 1.5rem;
|
||||
min-width: 10rem;
|
||||
background-color: rgba(17, 17, 17, 0.95);
|
||||
backdrop-filter: blur(4px);
|
||||
background-color: #232222;
|
||||
color: #f3f3f3;
|
||||
container-type: normal;
|
||||
&::before {
|
||||
@@ -333,7 +328,7 @@ button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 1.75rem;
|
||||
height: 1.5rem;
|
||||
width: 100%;
|
||||
border-bottom: #d0d0d0 solid 2px;
|
||||
display: flex;
|
||||
@@ -356,6 +351,6 @@ button {
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 0.75rem;
|
||||
height: 1.6rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user