the re-write begins...

This commit is contained in:
Michał Gdula 2025-04-25 20:56:42 +01:00
parent ae8bec6bf4
commit a96c318e83
93 changed files with 5 additions and 11574 deletions

View file

@ -1,13 +0,0 @@
version = 1
[[analyzers]]
name = "javascript"
[analyzers.meta]
environment = [
"browser",
"nodejs"
]
[[transformers]]
name = "prettier"

View file

@ -1,2 +0,0 @@
PUBLIC_ADDRESS="https://gay.leggy.dev"
PUBLIC_COMMENTS="false"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 987 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 KiB

31
.gitignore vendored
View file

@ -1,28 +1,5 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
# vscode settings folder
.vscode/
.idea
.vscode
_site
_cache

View file

@ -1,4 +0,0 @@
trailingComma: "all"
tabWidth: 4
semi: true
singleQuote: false

View file

@ -1,24 +0,0 @@
when:
- event: push
branch: main
steps:
- name: build
image: node:23-alpine3.20
commands:
- npm install
- npm run build
- name: deploy
image: alpine:latest
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- echo "$SSH_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh -o StrictHostKeyChecking=no uncertainty@192.168.0.42 "rm -r $COPY_TO || true"
- scp -o StrictHostKeyChecking=no -r $COPY_FROM uncertainty@192.168.0.42:$COPY_TO
environment:
COPY_FROM: dist
COPY_TO: /home/uncertainty/www/gay_leggy_dev
SSH_KEY:
from_secret: ssh_key

View file

@ -1,14 +1,3 @@
# website
Made in Astro because idk, but I hated it
Mostly used to show what I can do, kinda a portfolio I guess?
---
![Index.png](.github/screenshots/Index.png)
![Index.png](.github/screenshots/Blog.png)
---
aurgh
This branch is dedicated to the re-write in lume, yes, rewriting my site, again...

View file

@ -1,16 +0,0 @@
import { defineConfig } from "astro/config";
import syntaxTheme from "./syntax-theme.json";
import mdx from "@astrojs/mdx";
// https://astro.build/config
export default defineConfig({
output: "static",
markdown: {
syntaxHighlight: "shiki",
shikiConfig: {
theme: syntaxTheme,
},
},
integrations: [mdx()],
});

View file

@ -1,6 +0,0 @@
{
"origins": [
"https://gay.leggy.dev",
"https://portfolio.leggy.dev"
]
}

7344
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,22 +0,0 @@
{
"name": "website",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^3.1.9",
"astro": "4.16.10",
"typescript": "^5.6.3"
},
"devDependencies": {
"sass": "^1.80.6",
"sass-migrator": "^2.2.1"
}
}

View file

@ -1,159 +0,0 @@
/*!
* Modified from GitHub's Dark Dimmed theme, licensed under the MIT License
* Copyright (c) 2018 GitHub Inc.
* https://github.com/primer/primitives/blob/main/LICENSE
*/
main {
--color-prettylights-syntax-comment: #768390;
--color-prettylights-syntax-constant: #6cb6ff;
--color-prettylights-syntax-entity: #dcbdfb;
--color-prettylights-syntax-storage-modifier-import: #adbac7;
--color-prettylights-syntax-entity-tag: #8ddb8c;
--color-prettylights-syntax-keyword: #f47067;
--color-prettylights-syntax-string: #96d0ff;
--color-prettylights-syntax-variable: #f69d50;
--color-prettylights-syntax-brackethighlighter-unmatched: #e5534b;
--color-prettylights-syntax-invalid-illegal-text: #cdd9e5;
--color-prettylights-syntax-invalid-illegal-bg: #922323;
--color-prettylights-syntax-carriage-return-text: #cdd9e5;
--color-prettylights-syntax-carriage-return-bg: #ad2e2c;
--color-prettylights-syntax-string-regexp: #8ddb8c;
--color-prettylights-syntax-markup-list: #eac55f;
--color-prettylights-syntax-markup-heading: #316dca;
--color-prettylights-syntax-markup-italic: #adbac7;
--color-prettylights-syntax-markup-bold: #adbac7;
--color-prettylights-syntax-markup-deleted-text: #ffd8d3;
--color-prettylights-syntax-markup-deleted-bg: #78191b;
--color-prettylights-syntax-markup-inserted-text: #b4f1b4;
--color-prettylights-syntax-markup-inserted-bg: #1b4721;
--color-prettylights-syntax-markup-changed-text: #ffddb0;
--color-prettylights-syntax-markup-changed-bg: #682d0f;
--color-prettylights-syntax-markup-ignored-text: #adbac7;
--color-prettylights-syntax-markup-ignored-bg: #255ab2;
--color-prettylights-syntax-meta-diff-range: #dcbdfb;
--color-prettylights-syntax-brackethighlighter-angle: #768390;
--color-prettylights-syntax-sublimelinter-gutter-mark: #545d68;
--color-prettylights-syntax-constant-other-reference-link: #96d0ff;
--color-btn-text: #f0e9e4;
--color-btn-bg: #312620;
--color-btn-border: rgba(168, 99, 56, 0.1);
--color-btn-shadow: 0 0 transparent;
--color-btn-inset-shadow: 0 0 transparent;
--color-btn-hover-bg: #382e28;
--color-btn-hover-border: #a86338;
--color-btn-active-bg: hsl(25deg 27% 27% / 100%);
--color-btn-active-border: #a86338;
--color-btn-selected-bg: #312620;
--color-btn-primary-text: #f0e9e4;
--color-btn-primary-bg: #a86338;
--color-btn-primary-border: transparent;
--color-btn-primary-shadow: 0 0 transparent;
--color-btn-primary-inset-shadow: 0 0 transparent;
--color-btn-primary-hover-bg: #995a32;
--color-btn-primary-hover-border: transparent;
--color-btn-primary-selected-bg: #a86338;
--color-btn-primary-selected-shadow: 0 0 transparent;
--color-btn-primary-disabled-text: #bfbab6;
--color-btn-primary-disabled-bg: #804b2a;
--color-btn-primary-disabled-border: transparent;
--color-action-list-item-default-hover-bg: rgba(168, 99, 56, 0.12);
--color-segmented-control-bg: rgba(56, 46, 40, 0.1);
--color-segmented-control-button-bg: #312620;
--color-segmented-control-button-selected-border: #a86338;
--color-fg-default: #f0e9e4;
--color-fg-muted: #adbac7;
--color-fg-subtle: #545d68;
--color-canvas-default: #312620;
--color-canvas-overlay: #382e28;
--color-canvas-inset: rgba(49, 38, 32, 1);
--color-canvas-subtle: rgba(56, 46, 40, 1);
--color-border-default: #382e28;
--color-border-muted: #312620;
--color-neutral-muted: rgba(56, 46, 40, 0.4);
--color-accent-fg: #a86338;
--color-accent-emphasis: #a86338;
--color-accent-muted: rgba(168, 99, 56, 0.4);
--color-accent-subtle: rgba(168, 99, 56, 0.1);
--color-success-fg: #a86338;
--color-attention-fg: #a88538;
--color-attention-muted: #755b27;
--color-attention-subtle: #423315;
--color-danger-fg: #a83838;
--color-danger-muted: #752727;
--color-danger-subtle: #421616;
--color-primer-shadow-inset: 0 0 transparent;
--color-scale-gray-7: #382e28;
--color-scale-blue-8: #4b3b3a;
/*! Extensions from @primer/css/alerts/flash.scss */
--color-social-reaction-bg-hover: var(--color-scale-gray-7);
--color-social-reaction-bg-reacted-hover: var(--color-scale-blue-8);
}
main .pagination-loader-container {
background-image: url("https://github.com/images/modules/pulls/progressive-disclosure-line-dark.svg");
}
/*! Custom CSS */
.gsc-main {
gap: 0;
}
.gsc-reactions {
padding-bottom: 32px !important;
}
.gsc-reactions > div {
margin-top: 0 !important;
}
.gsc-reactions-count {
display: none;
}
.gsc-header {
padding-bottom: 32px;
}
.gsc-comment-box-tabs {
border-radius: 0 !important;
}
.gsc-comment-box-write {
/*border: 2px solid #382e28;*/
}
.gsc-comment-box:not(.gsc-comment-box-is-reply) {
border-width: 2px;
}
.gsc-comments {
gap: 0;
}
.gsc-comments > .gsc-comment-box,
.gsc-comments > .gsc-comment {
margin-bottom: 16px;
}
.gsc-comment > div {
border-width: 2px !important;
}
.btn {
border-radius: 9999px !important;
}
main .gsc-loading-image {
background-image: url("https://github.githubassets.com/images/mona-loading-dimmed.gif");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

View file

@ -1,121 +0,0 @@
---
import { getMonth, getDaySuffix } from "../utils";
interface Props {
post: any,
}
const { post } = Astro.props;
const date = new Date(post.data.pubDate);
---
<li class="link-card">
<a href=`/posts/${post.slug}`>
<h3>
{post.data.title}
</h3>
{post.data.pubDate ? (
<p>{date.getDate()}{getDaySuffix(date)} {getMonth(date)} {date.getFullYear()} • {post.data.description}</p>
) : (
<p>{post.data.description}</p>
)}
</a>
<div class="link-card-corner" />
</li>
<style lang="scss">
@import "../styles/vars.scss";
$corner-speed: 0.2s;
.link-card {
position: relative;
border-radius: $radius;
list-style: none;
overflow: hidden;
> a {
padding: 16px;
height: 100%;
min-height: 81px;
display: block;
text-decoration: none;
border-radius: $radius;
border: 2px solid $gray;
background-color: $dark;
color: $light;
transition: background-color $corner-speed ease-in-out;
}
.link-card-corner {
width: 40px;
height: 40px;
position: absolute;
bottom: -40px;
right: -40px;
border-top-left-radius: $radius;
border-top: 2px solid $gray;
border-left: 2px solid $gray;
background-image: linear-gradient(135deg, rgba($accent, 0.03), darken($dark, 1%));
background-color: $dark;
color: $light;
box-shadow: -4px -4px 0 rgba(#000, 0);
transition:
right $corner-speed ease-in-out,
bottom $corner-speed ease-in-out,
box-shadow $corner-speed ease-in-out;
pointer-events: none;
overflow: hidden;
z-index: +3;
&::after {
content: '';
height: 100px;
width: 100px;
position: absolute;
top: -16px;
left: -16px;
transform: rotate(-45deg);
border-top: 2px solid $gray;
background-color: $dark;
transition: left $corner-speed ease-in-out, top $corner-speed ease-in-out;
}
}
&:hover, &:focus-within {
> a {
background-color: rgba($accent, 0.03);
outline: none;
}
.link-card-corner {
bottom: 0;
right: 0;
box-shadow: -4px -4px 10px rgba(#000, 0.1);
&::after {
top: 3px;
left: 3px;
}
}
}
}
</style>

View file

@ -1,32 +0,0 @@
---
import { getMonth, getDaySuffix } from "../utils";
const { certificate } = Astro.props;
const date = new Date(certificate.data.achieved);
---
<div class="certificate">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none"/>
<path d="M168,157.94h0a44,44,0,1,1,56-67.88h0V56a8,8,0,0,0-8-8H40a8,8,0,0,0-8,8V184a8,8,0,0,0,8,8H168Z" opacity="0.2"/>
<line x1="72" y1="136" x2="120" y2="136" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
<line x1="72" y1="104" x2="120" y2="104" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
<circle cx="196" cy="124" r="44" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
<path d="M168,192H40a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H216a8,8,0,0,1,8,8V90.06" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="168 157.94 168 224 196 208 224 224 224 157.94" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
</svg>
<div>
<h3>{certificate.data.title}</h3>
<hr style="width: 100%">
<p>{date.getDate()}{getDaySuffix(date)} {getMonth(date)} {date.getFullYear()}</p>
<p>Presented by {certificate.data.provider}</p>
{certificate.data.skills && (
<ul class="pill-list">
{certificate.data.skills.map((skill: string) => ( <li class="pill">{skill}</li> ))}
</ul>
)}
{certificate.data.link && ( <a href={certificate.data.link} class="button">View Full</a> )}
</div>
</div>

View file

@ -1,33 +0,0 @@
---
---
<a class="button" href="/" id="home">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
<path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"></path>
</svg>
Homepage
</a>
<div />
<style lang="scss">
div {
height: calc(35px + 8px - 32px + 32px);
}
#home {
padding: 0 20px;
position: absolute;
top: 8px;
left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
&:before {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
</style>

View file

@ -1,41 +0,0 @@
---
import leg from "../assets/leg.webp";
---
<a class="button" id="music" href="https://www.last.fm/user/Fluffy_Bean_" target="_blank">
<img
src={leg.src}
width="64"
height="64"
loading="eager"
alt="Track cover art"
class="music-img music-bg"
/>
<img
src={leg.src}
width="64"
height="64"
loading="eager"
alt="Track cover art"
class="music-img music-cover"
/>
<ul>
<li id="music-title" style="font-weight: 600;">Track Name</li>
<li id="music-artist">by Artist</li>
<li id="music-album">on Album</li>
</ul>
</a>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const request = await fetch("https://lastfm-last-played.biancarosa.com.br/Fluffy_Bean_/latest-song");
const data = await request.json();
( document.querySelectorAll(".music-img") as NodeListOf<HTMLImageElement> ).forEach((img) => {
img.src = data["track"]["image"][2]["#text"];
});
( document.querySelector("#music-title") as HTMLParagraphElement ).innerText = `${data["track"]["name"]}`;
( document.querySelector("#music-artist") as HTMLParagraphElement ).innerText = `by ${data["track"]["artist"]["#text"]}`;
( document.querySelector("#music-album") as HTMLParagraphElement ).innerText = `on ${data["track"]["album"]["#text"]}`;
});
</script>

View file

@ -1,40 +0,0 @@
---
interface Props {
text: string,
}
const { text } = Astro.props;
---
<div class="note">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"></path></svg>
<span>{text}</span>
</div>
<style lang="scss">
@import "../styles/vars.scss";
.note {
margin: 16px 0;
padding: 10px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
font-weight: 500;
border-radius: $radius;
background-color: $accent;
color: $light;
> svg {
min-width: 24px;
}
> span {
padding-left: 8px;
}
}
</style>

View file

@ -1,7 +0,0 @@
{
"title": "Databases with SQL and Python",
"provider": "Hyperskill",
"achieved": "2023-07-01",
"skills": [ "SQL", "SQLAlchemy", "Python", "SQLite" ],
"link": ""
}

View file

@ -1,7 +0,0 @@
{
"title": "Introduction to Command Line and Unix Shell",
"provider": "Hyperskill",
"achieved": "2023-07-01",
"skills": [ "Bash", "Shell Scripting", "Linux", "Unix" ],
"link": ""
}

View file

@ -1,7 +0,0 @@
{
"title": "Introduction to Python",
"provider": "Hyperskill",
"achieved": "2023-07-01",
"skills": [ "Python" ],
"link": ""
}

View file

@ -1,7 +0,0 @@
{
"title": "Introduction to SQL",
"provider": "Hyperskill",
"achieved": "2023-07-01",
"skills": [ "SQL", "SQLite", "Databases" ],
"link": ""
}

View file

@ -1,36 +0,0 @@
import { z, defineCollection, reference } from "astro:content";
const posts = defineCollection({
type: "content",
schema: z.object({
draft: z.boolean().optional().default(false),
title: z.string(),
description: z.string(),
pubDate: z.string().transform((str) => new Date(str)),
tags: z.array(reference("tags")),
}),
});
const certificates = defineCollection({
type: "data",
schema: z.object({
title: z.string(),
provider: z.string(),
achieved: z.date(),
skills: z.array(z.string()).optional(),
link: z.string().optional(),
}),
});
const tags = defineCollection({
type: "content",
schema: z.object({
name: z.string(),
}),
});
export const collections = {
posts,
certificates,
tags,
};

View file

@ -1,53 +0,0 @@
---
title: Hello, Django!
description: Django is fun!
pubDate: 2023-06-19
tags:
- python
- django
- caddy
- networking
- webdev
---
import Note from "../../components/Note.astro";
<Note text="This is an older blog post imported from my old Django website" />
Wow, first ever blog! It's kind of ridiculous that somebody would think to start that only in 2023, since the cool thing to do now is to post on social media. Then again, the entire internet feels like it's falling apart now—fun!
Welp, where to start? Maybe how this page was made and the suffering I went through to get it running? sound good to me!
## Why Django????
This website runs on [Django](https://www.djangoproject.com/), a Python framework that I went with for a few reasons.
Firstly, I love Python and how fast and easy it is to make things in it. If you need to make something using it, most likely there is a package for it out there or a guide to making it yourself.
Secondly, I wanted to try something other than Flask. While Flask is great and all, I wanted to try something else, as Flask is terribly slow at serving static files, something that'll come to bite me later.
Lastly, I don't like JavaScript, why is this important? Someone out there will tell me to have used something like Vue to make this page. My answer is no. Not every single page out there needs to be made using an entire framework, you can get the exact same results with a static HTML file and a little bit of vanilla JS.
## On I love networking.
During the writing of this post, I actually experienced a lot of issues, none that Django itself caused but rather the terrible way I got everything setup. As I was setting this page up with Docker, Caddy was my proxy for my network, and it did its job for the most part. But it was very different from what I was used to with Nginx and lacked many features I wanted, such as RTMP, which it was not made for but Nginx comes with by default.
My network is also setup in a way that I have my websites running on a different container than my proxy, this is how I always had it, Nginx never complained, but oh boy, did Caddy give me a lot of issues that I'll get into in just a moment.
So, why is this an issue?
Django doesn't serve static files, the main reason being that the developers aren't interested in making a web server but a web framework. This means that Caddy had to handle the serving of static files. This is cool and all, but I've never done this before, so down a rabbit hole I went.
First I tried the obvious, getting Caddy to `root` the files on `webserver.ip/website/static` and `webserver.ip/website/media`. Nope! Apparently, Caddy can only serve static files from local files, not from a subdomain on a local IP. This would mean I have three options:
1. Move Caddy onto my web server.
2. SSH mount the files from my web server to my proxy server.
3. Use a different proxy server.
In the end, I went with the third option, I went back to `Nginx Proxy Manager` (amazing tool, by the way), and setup everything from scratch again. If you are reading this, then I got everything working, yey!
## Final thoughts
After all of that, switching proxies, dropping Docker, and a lot of suffering, I got this page working. Overall, would I go with Django again? Most likely! It was fast and easy to learn, and it has some really nice features. Though I wish Python was faster so I could use this for larger projects :c
While I continue to work out bugs on this page and my terrible server management, enjoy reading the slop in the blog 😋

View file

@ -1,159 +0,0 @@
---
title: Astro is hard....
description: Writing blogs is even harder
pubDate: 2024-05-28
tags:
- astro
- typescript
- webdev
---
Hello! Welcome to my new website, or at least as of writing this blog.
It's been written from scratch with Astro, Typescript and a lot of suffering, mostly due to bugs and random annoyances...
So where do I start, maybe _why_ I have chosen Astro over other existing options.
## Why Astro
TLDR: I don't know :3
I've been trying to learn Typescript for about a month now, mainly to broaden my skill set, but to also help me with job
searching. Firstly through Svelte for a college project (which I may write about), but now Astro.
I've chosen Astro for two main reasons
1. It's statically compiled, meaning that I don't ship any smelly Javascript to the browser, which I detest doing when not needed
2. It looked simple enough compared to other options
So, what was the experience like so far you may be asking, ehh...
## The problems
### `Image` and `Picture`
The first and biggest hurdle I faced was the `Image` and `Picture` elements from Astro. I could not for the life of me,
figure out a good solution for using both a file path, and a URL for the `frontmatter` data. I tried:
- `getImage()`
- Checking if the start of the string begins with `https://`
- Loading the image using `getImage()` on every page that passed the image data into the `Layout.astro` to set as the banner image
**NOTHING FUCKING WORKED.**
I spent a good few hours trying to get that working, until I came across the `image()` schema helper. I followed the
documentation, I followed videos, I copied code from existing repositories, nothing worked, it refused to load images
by file path, instead returning a string every time...
So I simply gave up, and I think that's for the best considering I want to keep my hair, and I had better things todo.
You win Astro, you win.
### PhotoSwipe
When working on the Refsheet part of this website, I wanted to use a library to be able to view images fullscreen. Maybe
I'm stupid, maybe I'm dumb, but I could not get [PhotoSwipe](https://photoswipe.com/) to work, at least when using
multiple sets of images on a page.
I tried stupid things such as creating unique IDs for each gallery element, but when
passing them into a `<script>` tag, it would break imports, as passing Astro variables into these for use on the user
side, it would put them _above_ the `import`s.
I know I'll figure this out, as I've already created a `plugins` section for my Layout, that looks as such.
```astro
---
// Layout.astro
interface Props {
title: string;
plugins?: {
katex?: boolean,
}
seo?: {
description?: string,
tags?: string[],
}
}
const { title, plugins, seo } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<title>{title}</title>
{plugins?.katex && (
<!-- Import Katex here -->
)}
</head>
<body>
<p>Balls</p>
</body>
</html>
```
```astro
---
// Markdown.astro
import Layout from "./Layout.astro";
// Get post data here
---
<Layout
plugins={{
katex: true,
}}
>
```
So I know it's possible...
### Broken `getEntries()`...
My last gripe I had with Astro was the broken `getEntries()` function, while it made it annoying to not get tag data for
posts, it wasn't hard to implement myself for the use of this blog. It's not that well optimised in my option, but it
does what it needs todo and doesn't run in the users browser anyway, thanks Astro.
```typescript
// utils.ts
export async function getTagsBySlug(
postTags: string[],
): Promise<CollectionEntry<"tags">[]> {
const allTags: CollectionEntry<"tags">[] = await getCollection("tags");
// Loop through all the tags in a post and the tags in the collections
// To see if they match, if they do we'll return them
const tags: CollectionEntry<"tags">[] = [];
postTags.forEach((postTag) => {
allTags.forEach((allTag) => {
if (allTag.slug === postTag) tags.push(allTag);
});
});
// Yeet
return tags;
}
```
## What I've learned
While Astro was a meh experience, I doubt all the problems I encountered where an Astro issue, most where probably a
skill issue tbh.
That said, trial by error (or fire when doing JavaScript/TypeScript) is a good way to learn, so I did learn much indeed,
both on Astro and TypeScript.
Other things I wasn't expecting to learn on the way was digging through Documentation more thoroughly and trying MDX. So
that's a win!
## Final words
I'm hoping to write more blogs in the future, mainly to practice my writing skills, and sharing my options publicly,
regardless if it's something political or programming related. But life's in a tangle right now
<div style="width: 200px">
![Maned Wolf art by Pulex](../../assets/posts/2024/05/pulex_leg.webp) [Art
by Pulex](https://www.pulexart.com/)
</div>

View file

@ -1,71 +0,0 @@
---
title: The Pebble Time!
description: '"Smart" watches are not smart at all!'
pubDate: 2024-06-06
tags:
- pebble
- impressions
---
Today I finally managed to get my hands on the Pebble Time! I've been looking for a watch to fulfil my three main needs,
without all the wacky account creation, data collection, and general annoyances sprinkled in for a while now, then I
found the Pebble Time. And now about a year later, here's my first impressions/review of the Pebble Time
([only 9 years late...](<https://en.wikipedia.org/wiki/Pebble_(watch)#Pebble_Time>))
## My Three Wishes
### Timekeeping
This seems quite stupid to put as a requirement for a watch "review" of sorts. But with all these watches coming out,
less and less of them can do what they were originally designed to do—show the time. Instead, they're packed with
features nobody will actually use that drain the life of the watch,
[barely get a day of battery life at times.](https://9to5google.com/2022/05/31/pixel-watch-battery-life/)
The Pebble Time, when released, was advertised to have 7 days of battery life,
[and it performed as advertised!](https://www.theverge.com/2015/5/27/8661863/pebble-time-review-wearable-smartwatch)
But nearly nine years later, does it still hold up? I can only answer after a few days of use, so watch out for an
update!
But how could Pebble do this? Through the smart optimisation of their PebbleOS and the use of an E-Ink display
(which I love)
### Notifications
This will make me sound old, but I hate having to pick up my phone constantly to check for notifications. It drives me
insane sometimes with the amount of stuff that tries to get my attention, even with most apps' notifications disabled.
Being able to quickly check if I should care to pull out my phone from my pocket is really handy, and something I missed
when my Samsung Gear S2 Classic finally kicked the bucket.
The Pebble Time does a great job at it, and I'd hope so since this was one of its main advertisement points. When do I
get a notification, it stays on my screen until I acknowledge it, which it can only do through the funky technology of
E-Ink.
### Quick Controls
Similarly, I don't want to have to take out my phone for some more simple things, like quickly changing the music I'm
listening to or toggling my desk lamp as I'm running for the train in the morning. Having access to
[Home Assistant](https://github.com/Willow-Systems/pebble-home-assistant) from my wrist is quite a lot more useful than
I thought at first. Though can be quite slow being written in JavaScript.
With the Samsung Gear S2 Classic, I didn't have such controls, due to the bloated TizenOS hogging down the limited 512MB
of ram and slow Qualcomm Snapdragon 400 CPU, along with the already dropped support at the time when I bought it.
## Annoyances
While there is many things I can say about the watch and the interface, like its funny little animations, cool design
and physical buttons to navigate. I have _already_ experienced crashes when trying to reply to notifications and "you
are not connected to the internet" block when trying to set up the watch,
[which Rebble does cover how todo](https://help.rebble.io/setup-android/#7), but still, it took me 3 attempts.
## What I miss from the Gear S2
While there isn't much here, I do want to point out just how useful the rotating crown of the Gear S2 was. It was a
really nice way of interacting with the operating system, similarly, I like the physical buttons on the Pebble Time. We
need less touchscreens in this world, not more!
## Final notes
I will probably post an update post for a more long-term review of the watch. I also want to try to write an app for the
Pebble, and I've been meaning to learn C, and with the extensive guides that are available for the watch, I think it's a
good time as ever to get into the land of low level!

View file

@ -1,84 +0,0 @@
---
draft: true
title: Urchin project
description: The game that maybe could be
pubDate: 2024-06-08
tags:
- python
- go
- raylib
- gamedev
- algorithms
---
'Tis I again.
Recently I've made quite a lot of progress on a project of mine and my brothers. I dubbed it "Urchin Project" because
urchin sounds funny, but also I have zero clue what to call the game.
## What was originally
This project actually started [around 10 months ago](https://www.youtube.com/watch?v=z9P-KlNKXAA), but this version was
abandoned for a few reasons. Main one being, I had zero fucking clue what I was doing, I didn't plan out a single part
of the project, I didn't even know what the story was going to be. Quickly, there was a lot of foot-guns in place and
horribly designed systems.
Urchin Project, though not called it back then, was written on PyGame, a Python wrapper for SDL with a few helpful
features. PyGame is more than suitable for most project and tests, as its quite simple to use and relatively fast to get
the hang of when first getting into writing games. One of the biggest gripes I have with it was the software based
rendering, but that shouldn't be an issu-
![Screenshot of the Urchin Project on a highDPI screen, causing everything to be really zoomed out](../../assets/posts/2024/06/photo_2023-08-07_10-22-00.jpg)
oh, yeah, that. While basically impossible to see on the screenshot, what ran at 300FPS on my 1080p monitor ran at barely 40fps on
a 5k one, this was going to be an issue, not only when it came to scaling things, but also the general design of the
game.
The only option I had was either to:
1. Ignore the issue and deal with it later
2. Use something like ModernGL to make an OpenGL context, and use PyGame as the input and audio manager
Guess which I've taken!
From the [previously linked video](https://www.youtube.com/watch?v=z9P-KlNKXAA), you can see I made quite a bit of
progress on the game. I even made a [world generator of some sorts](https://github.com/Fluffy-Bean/py_map_generation)!
But it was all horribly slow, terribly implemented and wasn't going to get far, and it didn't.
Eventually I gave up and moved onto other things, such as dealing with upcoming college.
## Go
Then around October 2023 I picked up Go. Go is a pretty neat little language, it has the simplicity of Python, and a
_pythonic_ sort of syntax in some ways, but the speeds of languages such as Java!
{/* I believe I read somewhere that Go can preform faster than Rust, but I cannot find the video/resource to back this up */}
Since then, I've used Go for most of my new projects, because since then I realised that Functional is the way to go for
me.
At one point I even tried to make something using a Go SDL wrapper, but that didn't really go anywhere and I gave up
even quicker with that.
## raylib
Now, here's where things get interesting!
- What have I been working on in the past week
- How the project started originally
- Why did I choose RayLib and Go over python
- Python was slow
- I didn't plan out the project well
- PyGame wasn't for me
- Gocurrency
- Physics system
- SAT
- QuadTrees
- Greedy Meshing
- What I want todo next
- World Generation
- Time/Temperature based systems

View file

@ -1,12 +0,0 @@
---
draft: true
title: ESPHome
description: Making little controllers do magic
pubDate: 2024-06-29
tags:
- esphome
- ha
- networking
---
ToDo

View file

@ -1,237 +0,0 @@
---
draft: false
title: umami
description: Goob bye Plausible
pubDate: 2024-11-07
tags:
- alpine
- linux
- webdev
- networking
---
import Note from "../../components/Note.astro";
## Introduction
This little blog post thang will be about umami, and why I went with it over fixing my Plausible install. While the
reason is pretty simple, really and can be summed up in two points.
- Its resource usage is low
- Has (nearly) all the features I used in Plausible
I want to talk a bit about how I got to the point where I chosen it, and the steps I've taken to actually install and
setup an Alpine CT with itttt
## Other options
Before I landed on umami, here's the other options I checked out, none of this is gonna really be "scientific", so do
some of your own research too, I'll provide links were ever I can, otherwise the information was brought to me in my
delusions.
### Matomo
I've seen Matomo get suggested in a few places before I went with Plausible originally, but I never checked it out till
now. While it looked solid, it also has _too_ much stuff... I didn't need or want a heatmap of interactions throughout
my website, nor session recording, or any ecommerce stuff.
Even with all that, I didn't particularly like the design, it was cluttered and confusing to go through the demo site.
### PostHog
PostHog was the second thing I've checked out, while it looked promising, it was just too much along with not solving my
problem of _having_ to use docker.
The requirements were also a bit insane for my needs, 8GBs of ram (recommended minimum) in comparison to the 256MB the
CT running umami currently has.
### Fathom
This was the last one I checked out before going with umami, I liked the design as it was similar to Plausible, and had
the feature set that I was looking for, with no complicated dashboards.
Buuuuuut, there is no self-hostable option, [kinda](https://github.com/usefathom/fathom). The lite version is no longer
maintained. Which is a shame, as it fathom looked promising and Go is a nice language to work with...
Maybe I could pick it up in the future, and bring it back to life again
### Services feature comparison
<Note text="Blank sections are due to me not being able to find information on the feature" />
| | Matomo | PostHog | umami | Fathom lite | Plausible |
|-------------------------------|--------|----------------------------|-----------------------------|------------------------------|----------------------------|
| Self-Hostable | Yes | Yes | Yes | Yes [^fathom-self-host] | Yes |
| Hosting method | PHP | Docker | Node or Docker | Go [^fathom-language] | Docker |
| GDPR compliant | Yes | Yes | Yes | No [^fathom-gdpr-compliance] | Yes |
| Email reports | Yes | Yes | | | Yes |
| Tracking events | Yes | Yes | Yes [^umami-event-tracking] | | No |
| Simple (and pretty) dashboard | No | Mostly | Yes | Yes | Yes |
| Minimum RAM | 1GB | 8GB | 256MB [^umamo-memory-usage] | | 1GB |
| Supported Databases | MySQL | N/A [^supported-databases] | Postgres or MySQL | SQLite | N/A [^supported-databases] |
## So what was wrong with Plausible?
Simply put, too heavy for my use-case. It was designed for websites that see thousands of people per week, maybe even a
day, but I haven't even reached a thousand views in a year! Secondly, docker, it has it's uses, but since all my
services already run in LXT/Proxmox containers, no point of putting them within another container.
All that I could've lived with fine, if not for trying to upgrade it. It broke, even in a docker container, it refused
to start after an update and wouldn't show any signs of life other that pushing my CPU usage to 100%, then freezing the
container!
Downgrading - worked. But since database migrations have been complete, a lot of the pages no longer worked, so I just
left it alone, until I was bothered with finding an alternative solution.
## umami
Finally, something I was looking for, it has all the features I need, with not too much extra stuff I'll never use. AND
it's self-hostable with a non-docker option!
Though, I did run into some issues compiling the project, specifically needing 2GBs of ram, lol
![Console, displaying an error from JS about running out of memory in the HEAP, then promptly exiting the process.](../../assets/posts/2024/11/js_oom.jpg)
And here's a dashboard comparison, before umami and Plausible
![Homepage of umami, showing a counter of how many people have visited my website, with a nearly empty graph of activity over the span of 1 day.](../../assets/posts/2024/11/umami.png)
![Dashboard of my page on Plausible, showing a line-graph of activity on my website over the past year, peaking in June of 2024 and a counter of 339 unique visitors.](../../assets/posts/2024/11/plausible.png)
## Now onto the fun stuff
<Note text="As mencioned before, this isn't supposed to be a full tutorial, but the steps I've taken to setup up umami" />
### Alpine
For this, I went with Alpine! It was my first time trying it, but it went great, and will probably be moving all my
services to it at some point, but that should be its own blog post. Main hurdle I had was understanding `OpenRC`, as
Alpine doesn't use `systemd`, but once I gotten used to `rc-update add <service>` and
`rc-service <service> start/stop/status` it went very smoothly.
Obviously firstly we should update all the available packages and install some useful tools
```bash
apk update
apk upgrade
apk add vim git
```
### Caddy
I didn't reaaaaaly need caddy for this, as it would mean the service would be behind two proxies, but I wanted to setup
caddy anyway. It was quite simple really
```bash
apk add caddy
rc-update add caddy
# Reboot
rc-service caddy start
# And make the required dirs and files
mkdir /var/www/html
```
Then, in `/etc/caddy`, we edit the `Caddyfile`, use your favorite editor for this, but I like vim, so for me I would run
`vim Caddyfile`. Then set the contents to the following
```caddyfile
:8080
reverse_proxy localhost:3000
```
<Note text="3000 is the default port for yarn, but adjust for your needs" />
And finally we just need to reload the service, still within `/etc/caddy`, with `caddy reload`!
### Postgres
It was slightly more manual than I'm used to on Ubuntu, but I found [a great guide by luppeng](https://luppeng.wordpress.com/2020/02/28/install-and-start-postgresql-on-alpine-linux)
that is still up-to-date as of writing this.
Then I just needed to get into the database with `psql -U postgres` and run `CREATE DATABASE umami;`.
<Note text="Creating a user specific to this service, along with setting correct network permissions, would be the safest, but you can figure that out" />
### Installing umami
The [official documentation](https://umami.is/docs/install) is the nicest, but there is some extra stuff you need todo
outside-of their tutorial
Firstly we make the required directory and clone the project `mkdir /var/www/html`
<Note text="Before you go any further, follow the official documentation, then come back :3" />
Now we're gonna set umami as a service! [They have a guide on doing that with PM2](https://umami.is/docs/install#running-umami),
but I prefer to use system native stuff, most likely for you this would be `systemd`, but if you're following along,
we'll be using `openrc`
Making a service on OpenRC is actually quite simple,[they have a guide for making services too](https://github.com/OpenRC/openrc/blob/master/service-script-guide.md). But first, make a
user for the service, such as `umami`. Next, we must make a file in `/etc/init.d`, such as `/etc/init.d/umami`. You can
do this by simply running `vim /etc/init.d/umami` that'll create and open the file for you. Then enter the following
```bash
#!/sbin/openrc-run
YARN_USER="umami"
PROJECT_DIR="/var/www/html/umami"
YARN_CMD="/usr/local/bin/yarn start"
APP_NAME="umami"
command="$YARN_CMD"
command_args="start"
command_user="$YARN_USER"
pidfile="/var/run/${APP_NAME}.pid"
output_log="/var/log/${APP_NAME}.log"
error_log="/var/log/${APP_NAME}_error.log"
depend() {
need net
}
start() {
ebegin "Starting ${APP_NAME}"
start-stop-daemon --start --user "$YARN_USER" \
--make-pidfile --pidfile "$pidfile" \
--chdir "$PROJECT_DIR" \
--exec "$command" -- $command_args >> "$output_log" 2>> "$error_log" &
eend $?
}
stop() {
ebegin "Stopping ${APP_NAME}"
start-stop-daemon --stop --pidfile "$pidfile"
eend $?
}
```
and run the following - same - commands as we did earlier for caddy
```bash
rc-update add umami
# Reboot
rc-service umami start
```
And we're done! Depending on your network setup, and if you used caddy or not, you should be able to visit the page from
something like `192.168.0.0:8000`, the default login is `admin`, `umami`.
## Some thoughts
You may also now notice new text on the bottom of the page
> This website tracks anonymous analytics. To see them in action visit umami.leggy.dev!
So far, I really enjoyed using umami, it's a nice project! But if you really don't like the idea of your visit being
counted, just paste the following into the JS console of your browser.
```js
localStorage.setItem("umami.disabled", 1);
```
All following visits will no-longer be counted. While I'd prefer your _not_ to remove yourself from the statistics, as
they're useful to me, you have the option to now.
[^fathom-self-host]: The paid version, that most people are probably know about, isn't self-hostable
[^fathom-language]: The self-hosted (lite) version uses Go, the paid version uses [Laravel](https://github.com/usefathom/fathom/issues/336#issuecomment-1079549690).
[^fathom-gdpr-compliance]: The paid version of Fathom [does comply, through their isolation layer](https://usefathom.com/features/eu-isolation), the lite version does not guarantee this.
[^umami-event-tracking]: unami doesn't track events automatically [and needs to be setup manually across the website](https://umami.is/docs/track-events).
[^umamo-memory-usage]: unami doesn't list minimum requirements, so far from my testing, this is about what I needed for all my websites. For installs that have higher page visit counts, I imagine more would be needed.
[^supported-databases]: The service is run within Docker containers, so I don't get to choose what database I can actually use :(

View file

@ -1,106 +0,0 @@
---
draft: true
title: "Code Examples"
description: "Aurghhhhhh"
pubDate: 2022-07-08
tags:
- "code"
---
```astro
---
import { getPosts } from "../../utils";
import Layout from "../../layouts/Layout.astro";
import Markdown from "../../layouts/Markdown.astro";
export async function getStaticPaths() {
const collection = await getPosts("projects");
return collection.map((post, i) => ({
params: { slug: post.slug },
props: {
post: post,
prev: i > 0 ? collection[i - 1] : undefined,
next: i < collection.length - 1 ? collection[i + 1] : undefined
}
}));
}
const { post, prev, next } = Astro.props;
---
<Layout title=`Leggy Land - ${post.data.title}` src={post.data.image.url} alt={post.data.image.alt}>
<Markdown {post} {prev} {next} base="/projects" />
</Layout>
```
```scss
.astro-code {
padding: 36px 8px 8px;
position: relative;
display: block;
font-size: 13px;
border-radius: $radius;
&::before {
content: "lang: " attr(data-language);
padding: 4px 8px;
width: 100%;
height: 28px;
position: absolute;
top: 0;
left: 0;
font-size: 13px;
background-color: $gray;
color: $light;
}
}
```
```go
func (p *penTool) Render() raylib.Texture2D {
offset := raylib.Vector2Scale(canvas.Offset, -1)
texture := raylib.LoadRenderTexture(int32(canvas.Size.X), int32(canvas.Size.Y))
raylib.BeginTextureMode(texture)
raylib.ClearBackground(raylib.Fade(raylib.Black, 0))
for i := 0; i < len(p.Points)-1; i++ {
startPointOffset := raylib.Vector2Add(p.Points[i], offset)
endPointOffset := raylib.Vector2Add(p.Points[i+1], offset)
raylib.DrawLineEx(startPointOffset, endPointOffset, p.Size, p.Color)
raylib.DrawCircle(int32(startPointOffset.X), int32(startPointOffset.Y), p.Size/2, p.Color)
}
if len(p.Points) > 0 {
endPointOffset := raylib.Vector2Add(p.Points[len(p.Points)-1], offset)
raylib.DrawCircle(int32(endPointOffset.X), int32(endPointOffset.Y), p.Size/2, p.Color)
}
raylib.EndTextureMode()
return texture.Texture
}
```
```python
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField()
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
thumb = models.ImageField(default="default.png", blank=True)
published = models.BooleanField(default=False)
def __str__(self):
return self.title
```

View file

@ -1,12 +0,0 @@
---
draft: true
title: "Image Examples"
description: "fug"
pubDate: 2024-07-08
tags:
- "code"
---
![Image](../../assets/cupcake.jpg)
critter time!!!!

View file

@ -1,130 +0,0 @@
---
draft: true
title: This is an example Post
description: "Cheat Sheet for Markdown"
pubDate: 2022-07-08
tags:
- "code"
---
# Markdown Cheat Sheet
Thanks for visiting [The Markdown Guide](https://www.markdownguide.org)!
This Markdown cheat sheet provides a quick overview of all the Markdown syntax elements. It cant cover every edge case, so if you need more information about any of these elements, refer to the reference guides for [basic syntax](https://www.markdownguide.org/basic-syntax/) and [extended syntax](https://www.markdownguide.org/extended-syntax/).
## Basic Syntax
These are the elements outlined in John Grubers original design document. All Markdown applications support these elements.
### Heading
# H1
## H2
### H3
### Bold
**bold text**
### Italic
_italicized text_
### Blockquote
> blockquote
### Ordered List
1. First item
2. Second item
3. Third item
### Unordered List
- First item
- Second item
- Third item
### Code
`code`
### Horizontal Rule
---
### Link
[Markdown Guide](https://www.markdownguide.org)
### Image
![alt text](https://www.markdownguide.org/assets/images/tux.png)
## Extended Syntax
These elements extend the basic syntax by adding additional features. Not all Markdown applications support these elements.
### Table
| Syntax | Description |
| --------- | ----------- |
| Header | Title |
| Paragraph | Text |
### Fenced Code Block
```json
{
"firstName": "John",
"lastName": "Smith",
"age": 25
}
```
### Footnote
Here's a sentence with a footnote. [^1]
[^1]: This is the footnote.
### Heading ID
### My Great Heading \{#custom-id}
### Definition List
term
: definition
### Strikethrough
~~The world is flat.~~
### Task List
- [x] Write the press release
- [ ] Update the website
- [ ] Contact the media
### Emoji
That is so funny! :joy:
(See also [Copying and Pasting Emoji](https://www.markdownguide.org/extended-syntax/#copying-and-pasting-emoji))
### Highlight
I need to highlight these ==very important words==.
### Subscript
H~2~O
### Superscript
X^2^

View file

@ -1,25 +0,0 @@
---
draft: true
title: "Math Examples"
description: "REEEEEE"
pubDate: 2024-06-08
tags:
- "code"
- "math"
---
Some simple mathematical expressions:
$$ \sqrt\{3x-1}+(1+x)^2 $$
$$\frac\{ax^2+bx+c}\{(a+b)^2}=0$$
$$f(x) = \pm A \sin\left(\frac\{2\pi}\{4} + \theta\right)$$
More complicated examples (from [KateX home page](https://katex.org)):
$$\displaystyle \frac\{1}\{\Bigl(\sqrt\{\phi \sqrt\{5}}-\phi\Bigr) e^\{\frac25 \pi}} = 1+\frac\{e^\{-2\pi}} \{1+\frac\{e^\{-4\pi}} \{1+\frac\{e^\{-6\pi}} \{1+\frac\{e^\{-8\pi}} \{1+\cdots} } } }$$
$$\displaystyle \left( \sum_\{k=1}^n a_k b_k \right)^2 \leq \left( \sum_\{k=1}^n a_k^2 \right) \left( \sum_\{k=1}^n b_k^2 \right)$$
$$\displaystyle \{1 + \frac\{q^2}\{(1-q)}+\frac\{q^6}\{(1-q)(1-q^2)}+\cdots }= \prod_\{j=0}^\{\infty}\frac\{1}\{(1-q^\{5j+2})(1-q^\{5j+3})}, \quad\quad \text\{for }\lvert q\rvert\<1. $$

View file

@ -1,3 +0,0 @@
---
name: Algorithms
---

View file

@ -1,3 +0,0 @@
---
name: Alpine
---

View file

@ -1,3 +0,0 @@
---
name: Astro
---

View file

@ -1,3 +0,0 @@
---
name: Caddy
---

View file

@ -1,3 +0,0 @@
---
name: Django
---

View file

@ -1,3 +0,0 @@
---
name: ESPHome
---

View file

@ -1,3 +0,0 @@
---
name: Game Development
---

View file

@ -1,3 +0,0 @@
---
name: Go
---

View file

@ -1,3 +0,0 @@
---
name: HomeAssistant
---

View file

@ -1,3 +0,0 @@
---
name: First Impressions
---

View file

@ -1,3 +0,0 @@
---
name: Linux/GNU
---

View file

@ -1,3 +0,0 @@
---
name: Networking
---

View file

@ -1,3 +0,0 @@
---
name: Pebble
---

View file

@ -1,3 +0,0 @@
---
name: Python
---

View file

@ -1,3 +0,0 @@
---
name: raylib
---

View file

@ -1,3 +0,0 @@
---
name: Review
---

View file

@ -1,3 +0,0 @@
---
name: Typescript
---

View file

@ -1,3 +0,0 @@
---
name: Web Development
---

2
src/env.d.ts vendored
View file

@ -1,2 +0,0 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View file

@ -1,179 +0,0 @@
---
import "../styles/styles.scss";
import Banner from "../assets/banner.png";
interface Props {
title: string;
plugins?: {
katex?: boolean,
giscus?: boolean,
}
seo?: {
description?: string,
tags?: string[],
}
}
const { title, plugins, seo } = Astro.props;
const address = import.meta.env.PUBLIC_ADDRESS;
---
<!doctype html>
<html lang="en">
<head>
<title>{title}</title>
<meta charset="UTF-8" />
<meta name="description" content={seo?.description ? seo?.description : "Premium Legs only"} />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<meta name="keywords" content={seo?.tags ? seo?.tags.join(', ') : "furry, gay, homosegs"} />
<meta property="og:title" content={title} />
<meta property="og:description" content={seo?.description ? seo?.description : "Premium Legs only"} />
<meta property="og:url" content="https://gay.leggy.dev" />
<meta property="og:type" content="website" />
<link
rel="icon"
type="image/webp"
href="/leg.webp"
/>
<link
rel="preconnect"
href="https://api.fontshare.com"
>
<link
rel="stylesheet"
href="https://api.fontshare.com/v2/css?f[]=jet-brains-mono@400&f[]=general-sans@1&display=swap"
/>
<!--
If you want to disable your visits from being counted,
run "localStorage.setItem('umami.disabled', 1);"
in the JS console (without the quotation marks).
-->
<script
is:inline
defer
src="https://umami.leggy.dev/script.js"
data-website-id="e8c4fb4f-6ff2-4179-8873-957d635c862c"
/>
{plugins?.katex && (
<link
rel="preconnect"
href="https://cdn.jsdelivr.net"
>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css"
integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww"
crossorigin="anonymous"
/>
<script
is:inline
defer
src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js"
integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd"
crossorigin="anonymous"
></script>
<script
is:inline
defer
src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js"
integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk"
crossorigin="anonymous"
onload="renderMathInElement(document.body);"
></script>
)}
{plugins?.giscus && (
<script
is:inline
src="https://giscus.app/client.js"
data-repo="Fluffy-Bean/website"
data-repo-id="R_kgDOL-t-KQ"
data-category="General"
data-category-id="DIC_kwDOL-t-Kc4CfxFh"
data-mapping="title"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="top"
data-theme=`${address}/custom/giscus.css`
data-lang="en"
data-loading="lazy"
crossorigin="anonymous"
async
></script>
)}
</head>
<body>
<a href="#content-skip" tabindex="0" id="content-skip-button">Skip to content</a>
<div class="banner">
<img
src={Banner.src}
alt="Stretch of road leading to cloudy hills with houses in the horizon"
width="1080"
height="700"
loading="eager"
decoding="async"
/>
</div>
<main>
<slot />
<p id="anal_notice">
This website tracks anonymous analytics. To see them in action visit
<a href="https://umami.leggy.dev/share/jNKQaN97seslziXY/gay.leggy.dev" target="_blank">umami.leggy.dev</a>!
</p>
</main>
</body>
<script>
function update(element: HTMLElement) { element.style.top = `${window.scrollY * 0.75 }px`; }
const img = document.querySelector(".banner > img") as HTMLImageElement;
document.addEventListener("scroll", () => update(img))
document.addEventListener("DOMContentLoaded", () => update(img))
</script>
</html>
<style lang="scss">
@import "../styles/vars.scss";
#content-skip-button {
padding: 16px 32px;
position: absolute;
top: -1000000px;
left: -1000000px;
z-index: 999999999;
font-weight: bolder;
text-decoration: none;
background-color: $accent;
color: $light;
&:focus {
top: 0;
left: 0;
outline: 0 solid transparent;
}
}
#anal_notice {
padding-top: 16px;
margin-bottom: -16px;
color: $light;
font-size: 11px;
text-align: center;
> a {
color: $accent;
&:hover {
color: $light;
}
}
}
</style>

View file

@ -1,327 +0,0 @@
---
import { getMonth, getDaySuffix, getTagsBySlug } from "../utils";
import Layout from "./Layout.astro";
import HomeButton from "../components/HomeButton.astro";
interface Props {
post: any,
prev?: any,
next?: any,
base: string,
}
const { post, prev, next } = Astro.props;
// 183 average w/p
const readTime = `${Math.ceil(post.body.split(" ").length / 183)} min read`;
const date = new Date(post.data.pubDate);
const tags = await getTagsBySlug(post.data.tags);
const comments = import.meta.env.PUBLIC_COMMENTS === "true";
---
<Layout
title=`Leggy Land - ${post.data.title}`
plugins={{
katex: true,
giscus: comments,
}}
seo={{
description: post.data.description,
tags: post.data.tags,
}}
>
<HomeButton />
<!-- If I ever move anything around, this will fucking break -->
<a href=`https://github.com/Fluffy-Bean/website/tree/main/src/content/posts/${post.id}` id="source" class="button" aria-label="Source Code">
<span>Source Code</span>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"></path></svg>
</a>
<!-- Sticky could be added, but it makes it a buit difficult to read things on mobile-->
<div class="header">
<h1>{post.data.title}</h1>
{post.data.pubDate ? (
<p>{date.getDate()}{getDaySuffix(date)} {getMonth(date)} {date.getFullYear()} • {readTime} • {post.data.description}</p>
) : (
<p>{readTime} • {post.data.description}</p>
)}
<ul id="tags" class="pill-list" role="list">
{tags.map((tag) => (
<li>
<a class="pill" href=`/search/${tag.slug}`>
<!--<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" viewBox="0 0 256 256"><path d="M216,152H168V104h48a8,8,0,0,0,0-16H168V40a8,8,0,0,0-16,0V88H104V40a8,8,0,0,0-16,0V88H40a8,8,0,0,0,0,16H88v48H40a8,8,0,0,0,0,16H88v48a8,8,0,0,0,16,0V168h48v48a8,8,0,0,0,16,0V168h48a8,8,0,0,0,0-16Zm-112,0V104h48v48Z"></path></svg>-->
{tag.data.name}
</a>
</li>
))}
</ul>
</div>
<span id="content-skip" />
<div id="markdown">
<div style="margin-bottom: 32px" />
<slot></slot>
<div style="margin-top: 32px" />
</div>
{(prev || next) && ( <hr> )}
<ul id="controls" role="list">
<li>
{prev && (
<a class="button" href=`/posts/${prev.slug}` id="prev">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"></path></svg>
Newer
</a>
)}
</li>
<li>
{next && (
<a class="button" href=`/posts/${next.slug}` id="next">
Older
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path></svg>
</a>
)}
</li>
</ul>
{comments && (
<hr>
<div class="giscus" id="giscus" />
)}
</Layout>
<style is:global lang="scss">
@import "../styles/vars";
#source {
padding: 0 10px;
position: absolute;
top: 8px;
right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
transition: padding 1s cubic-bezier(0, 1, 0, 1);
> span {
display: none;
}
&::before {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:hover, &:focus-visible {
padding: 0 20px;
> span {
display: block;
}
> svg {
display: none;
}
}
}
#controls {
display: flex;
flex-direction: row;
justify-content: space-between;
align-content: center;
> li > .button {
min-width: 35px;
height: 35px;
display: flex;
justify-content: center;
align-items: center;
}
}
#markdown {
margin: -32px 0;
display: block;
flex-grow: 1;
:target {
scroll-margin-block: 5ex;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 16px;
margin-bottom: 8px;
line-height: 1.1;
text-wrap: balance;
}
p {
margin-bottom: 8px;
}
a {
text-decoration: underline;
color: $accent;
&:hover, &:focus-visible {
color: $light;
}
&:focus-visible {
border-radius: $radius;
outline: 1px solid $light;
}
&:is([class]) {
text-decoration-skip-ink: auto;
text-decoration: none;
}
}
ol, ul {
margin: 16px 0;
padding-left: 32px;
}
hr {
margin: 16px 0;
border: 0;
border-top: 2px solid $gray;
}
// Style code only if it's not a child of .astro-code
:not(.astro-code) > code {
padding: 2px 4px;
font-size: 13px;
border-radius: $radius;
background-color: $gray;
color: $light;
overflow-x: auto
}
blockquote {
margin: 8px 0 8px 16px;
padding: 0 8px;
font-style: italic;
border-left: 2px solid $accent;
}
table {
margin: 16px 0;
width: 100%;
font-size: 13px;
border-collapse: collapse;
border-bottom: 2px solid $gray;
tr {
&:nth-child(even) td {
background-color: rgba($gray, 0.15);
}
&:last-of-type > td {
/*border-bottom: 2px solid darken($gray, 2%);*/
}
td {
padding: 8px 16px;
}
th {
padding: 8px 16px;
font-weight: bold;
text-align: left;
/*border-bottom: 2px solid darken($gray, 2%);*/
background-color: $gray;
&:first-child {
border-top-left-radius: $radius;
}
&:last-child {
border-top-right-radius: $radius;
}
}
}
}
img {
margin: 16px 0;
max-width: 100%;
height: auto;
border-radius: $radius;
}
.astro-code {
margin: 20px 0;
padding: 40px 8px 8px;
position: relative;
display: block;
font-size: 13px;
border-radius: $radius;
border: 2px solid rgba(#000, 0.1);
&::before {
content: attr(data-language);
padding: 4px 16px;
width: max-content;
height: 28px;
position: absolute;
top: 4px;
left: 0;
font-size: 13px;
text-transform: capitalize;
border-top-right-radius: 9999px;
border-bottom-right-radius: 9999px;
background-color: $gray;
color: $light;
z-index: +1;
}
&:focus-visible {
outline: 1px solid $light;
}
}
.footnotes {
margin-top: 32px;
padding: 16px;
border-radius: $radius;
background-color: $gray;
> h2 {
margin-top: 0;
}
}
}
</style>

View file

@ -1,112 +0,0 @@
---
import { getCollection } from "astro:content";
import { getPosts } from "../utils";
import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
import Certificate from "../components/Certificate.astro";
import Music from "../components/Music.astro";
const tools = ["Proxmox", "JetBrain IDEs", "Docker", "Linux", "SQLite", "Postgres", "MySQL"];
const languages = ["Go", "Python", "HTML", "CSS", "Sass", "TypeScript", "JavaScript", "Scratch", "PHP", "SQL", "Bash"];
const frameworks = ["Gin", "Echo", "Flask", "Svelte", "Astro", "raylib"];
const certificates = await getCollection("certificates");
const posts = await getPosts("posts");
---
<Layout title="Leggy Land">
<div class="header">
<h1>Leggy Land</h1>
<p>Made with Coffee, lots of it.</p>
<Music />
</div>
<span id="content-skip" />
<div class="section">
<h2>Who am I</h2>
<p>My name is Michał, I go by Fluffy, I'm 20 and I like computers</p>
<p>I mainly do website stuff, work with the front and backend, but I also like networking and breaking things</p>
<p>My favorite language currently is Go, but I also know a few other languages that are listed below!</p>
<p>In my free time, which isn't that often anymore, I enjoy playing games, such as The Witcher, Spin&nbsp;RhythmXD and other random stuff</p>
<p>I use Arch btw.</p>
</div>
<div class="section">
<h2>Stalk me here</h2>
<ul class="pill-list" role="list">
<li><a class="button" href="https://www.twitter.com/fluffybeanUwU" target="_blank">Twitter</a></li>
<li><a class="button" href="https://bsky.app/profile/leggy.dev" target="_blank">BlueSky</a></li>
<li><a class="button" href="https://t.me/Fluffy_Bean" target="_blank">Telegram</a></li>
<li><a class="button" href="https://github.com/Fluffy-Bean" target="_blank">GitHub</a></li>
</ul>
</div>
<div class="section">
<h2>Tools</h2>
<ul class="pill-list" role="list">
{tools.map(tool => (
<li class="pill large">
{tool}
</li>
))}
</ul>
</div>
<div class="section">
<h2>Languages</h2>
<ul class="pill-list" role="list">
{languages.map(language => (
<li class="pill large">
{language}
</li>
))}
</ul>
</div>
<div class="section">
<h2>Frameworks</h2>
<ul class="pill-list" role="list">
{frameworks.map(framework => (
<li class="pill large">
{framework}
</li>
))}
</ul>
</div>
<div class="section">
<h2>Recent Posts</h2>
{(posts.length > 0) ? (
<ul class="project-list" role="list">
{posts.slice(0, 2).map(post => (
<Card {post} />
))}
</ul>
<a class="button" id="see-all-posts" href="/posts">All Posts</a>
) : (
<p>No Posts yet made!</p>
)}
</div>
<div class="section">
<h2>Certificates</h2>
<ul class="project-list" role="list">
{certificates.map(certificate => (
<li>
<Certificate {certificate} />
</li>
))}
</ul>
</div>
</Layout>
<style lang="scss">
@import "../styles/vars.scss";
#see-all-projects,
#see-all-posts {
margin-top: 16px;
margin-left: auto;
}
</style>

View file

@ -1,26 +0,0 @@
---
import Layout from "../layouts/Layout.astro";
import HomeButton from "../components/HomeButton.astro";
import GwaGwa from "../assets/fumble.jpg";
---
<Layout title="LEG LEG LEG LEG LEG LEG LEG LEG LEG LEG LEG LEG LEG">
<HomeButton />
<div class="header">
<h1>Maned Wolf Jumpscare</h1>
<p>GwaGwa</p>
</div>
<span id="content-skip" />
<div class="secrtion">
<img
src={GwaGwa.src}
alt="Two Maned wolfs having a disagreement"
width="851"
height="575"
style="height: auto"
/>
</div>
</Layout>

View file

@ -1,25 +0,0 @@
---
import { getPosts } from "../../utils";
import Markdown from "../../layouts/Markdown.astro";
export async function getStaticPaths() {
const collection = await getPosts("posts");
return collection.map((post, i) => ({
params: { slug: post.slug },
props: {
post: post,
prev: i > 0 ? collection[i - 1] : undefined,
next: i < collection.length - 1 ? collection[i + 1] : undefined,
}
}));
}
const { post, prev, next } = Astro.props;
const { Content } = await post.render();
---
<Markdown post={post} prev={prev} next={next} base="posts">
<Content />
</Markdown>

View file

@ -1,53 +0,0 @@
---
import { getPosts } from "../../utils";
import Layout from "../../layouts/Layout.astro";
import HomeButton from "../../components/HomeButton.astro";
import Card from "../../components/Card.astro";
const posts = await getPosts("posts");
---
<Layout title="Leggy Land - All Posts">
<HomeButton />
<a href="/search" id="search" class="button" aria-label="Search">
Search
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M230.6,49.53A15.81,15.81,0,0,0,216,40H40A16,16,0,0,0,28.19,66.76l.08.09L96,139.17V216a16,16,0,0,0,24.87,13.32l32-21.34A16,16,0,0,0,160,194.66V139.17l67.74-72.32.08-.09A15.8,15.8,0,0,0,230.6,49.53ZM40,56h0Zm106.18,74.58A8,8,0,0,0,144,136v58.66L112,216V136a8,8,0,0,0-2.16-5.47L40,56H216Z"></path></svg>
</a>
<div class="header">
<h1>All Posts</h1>
<p>Books of egg</p>
</div>
<span id="content-skip" />
<ul role="list" class="project-list">
{posts.map(post => (
<Card {post}/>
))}
</ul>
</Layout>
<style is:global lang="scss">
@import "../../styles/vars";
#search {
padding: 0 20px;
position: absolute;
top: 8px;
right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
transition: padding 1s cubic-bezier(0, 1, 0, 1);
&::before {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
</style>

View file

@ -1,84 +0,0 @@
---
import Layout from "../layouts/Layout.astro";
import HomeButton from "../components/HomeButton.astro";
import Ref from "../assets/art/ref.png";
import Sneak from "../assets/art/sneak.png";
import Taidum from "../assets/art/taidum.png";
import Men from "../assets/art/kissing-men.png";
import Mood from "../assets/art/mood.png";
---
<Layout title="Leggy Land - Refsheet">
<HomeButton />
<div class="header">
<h1>Refsheet</h1>
<p>Maned Wolf moment</p>
</div>
<span id="content-skip" />
<div class="section">
<h2>Refsheet</h2>
<img src={Ref.src} alt="FluffyBean" class="art max" />
<ul class="pill-list" role="list">
<li class="pill size-button">mrHDash</li>
<li><a href="https://twitter.com/mrHDash" class="button">Twitter</a></li>
<li><a href="https://instagram.com/mrhdash_arts" class="button">Instagram</a></li>
</ul>
</div>
<div class="section">
<h2>Shep</h2>
<img src={Sneak.src} alt="FluffyBean" class="art" />
<ul class="pill-list" role="list">
<li class="pill size-button">Shep</li>
<li><a href="https://twitter.com/ShepGoesBlep" class="button">Twitter</a></li>
</ul>
</div>
<div class="section">
<h2>Zadok</h2>
<img src={Taidum.src} alt="FluffyBean" class="art" />
<ul class="pill-list" role="list">
<li class="pill size-button">Zadok</li>
<li><a href="https://twitter.com/Zadoktater" class="button">Twitter</a></li>
<li><button onclick="navigator.clipboard.writeText('zadoknchip'); return;" class="button">Discord</button></li>
</ul>
</div>
<div class="section">
<h2>LordPulex</h2>
<img src={Men.src} alt="FluffyBean" class="art" />
<ul class="pill-list" role="list">
<li class="pill size-button">LordPulex</li>
<li><a href="https://twitter.com/LordPulex" class="button">Twitter</a></li>
<li><a href="https://pulex.carrd.co/" class="button">Website</a></li>
</ul>
</div>
<div class="section">
<h2>OggyTheFox</h2>
<img src={Mood.src} alt="FluffyBean" class="art" />
<ul class="pill-list" role="list">
<li class="pill size-button">OggyTheFox</li>
<li><a href="https://twitter.com/OggyOsbourne" class="button">Twitter</a></li>
<li><a href="https://oggy123.eu/" class="button">Website</a></li>
</ul>
</div>
</Layout>
<style lang="scss">
@import "../styles/vars.scss";
.art {
margin-bottom: 8px;
/*padding: 8px;*/
max-width: 400px;
border-radius: $radius;
/*background: $gray;*/
&.max {
max-width: 100%;
}
}
</style>

View file

@ -1,69 +0,0 @@
---
import { getCollection } from "astro:content";
import { getPosts } from "../../utils";
import Layout from "../../layouts/Layout.astro";
import Card from "../../components/Card.astro";
import HomeButton from "../../components/HomeButton.astro";
export async function getStaticPaths() {
const collection = await getCollection("tags");
// Filter by file name, such as linux-kernel instead of "Linux Kernel"
return collection.map((tag) => ({
params: { filter: tag.slug },
props: { tag }
}));
}
const { tag } = Astro.props;
const allPosts = await getPosts("posts");
const filteredPosts = allPosts.filter((project) => project.data.tags.includes(tag.slug));
---
<Layout title=`Leggy Land - Searching for ${tag.data.name}`>
<HomeButton />
<a href="/search" id="reset-filters" class="button" aria-label="Reset Filters">
Reset Filters
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M227.82,66.76A16,16,0,0,0,216,40H40A16,16,0,0,0,28.19,66.76l.08.09L96,139.17V216a16,16,0,0,0,24.87,13.32l32-21.34A16,16,0,0,0,160,194.66V139.17l67.73-72.32ZM40,56h0Zm106.19,74.59A8,8,0,0,0,144,136v58.66L112,216V136a8,8,0,0,0-2.16-5.46L40,56H216Zm99.49,79.81a8,8,0,0,1-11.32,11.32L216,203.32l-18.34,18.35a8,8,0,0,1-11.31-11.32L204.69,192l-18.34-18.35a8,8,0,0,1,11.31-11.31L216,180.69l18.34-18.34a8,8,0,0,1,11.32,11.31L227.31,192Z"></path></svg>
</a>
<div class="header">
<h1>Search: {tag.data.name}</h1>
<p>Showing {filteredPosts.length}/{allPosts.length} posts</p>
</div>
<span id="content-skip" />
<div class="section">
<ul role="list" class="project-list">
{filteredPosts.map(post => (
<Card {post} />
))}
</ul>
</div>
</Layout>
<style is:global lang="scss">
@import "../../styles/vars";
#reset-filters {
padding: 0 20px;
position: absolute;
top: 8px;
right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
transition: padding 1s cubic-bezier(0, 1, 0, 1);
&::before {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
</style>

View file

@ -1,66 +0,0 @@
---
import { getCollection} from "astro:content";
import { getPosts } from "../../utils";
import Layout from "../../layouts/Layout.astro";
import HomeButton from "../../components/HomeButton.astro";
const tags = await getCollection("tags");
const posts = await getPosts("posts");
// Get post count for reach tag
tags.forEach((tag) => {
tag.data.postCount = posts.filter((project) => {
return project.data.tags.includes(tag.slug);
}).length;
})
// Dunno if Astro auto-sorts stuff
tags.sort((a, b) => {
return a.data.name.localeCompare(b.data.name);
});
---
<Layout title="Leggy Land - All Projects">
<HomeButton />
<div class="header">
<h1>Search</h1>
<p>Filter posts by tags</p>
</div>
<span id="content-skip" />
<ul role="list" class="pill-list">
{tags.map(tag => (
<li>
<a class="pill large" href=`/search/${tag.slug}`>
<!--<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 256 256"><path d="M216,152H168V104h48a8,8,0,0,0,0-16H168V40a8,8,0,0,0-16,0V88H104V40a8,8,0,0,0-16,0V88H40a8,8,0,0,0,0,16H88v48H40a8,8,0,0,0,0,16H88v48a8,8,0,0,0,16,0V168h48v48a8,8,0,0,0,16,0V168h48a8,8,0,0,0,0-16Zm-112,0V104h48v48Z"></path></svg>-->
{tag.data.name} <span class="blob">{tag.data.postCount}</span>
</a>
</li>
))}
</ul>
</Layout>
<style lang="scss">
@import "../../styles/vars.scss";
.blob {
margin-left: 8px;
min-width: 18px;
height: 18px;
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
font-size: 12px;
font-family: $font-mono;
border-radius: 9999px;
background-color: $accent;
color: $light;
}
</style>

View file

@ -1,27 +0,0 @@
.banner {
margin: 0;
width: 100%;
height: 100vh;
position: absolute;
top: 0;
left: 0;
box-shadow: 0 8px 8px rgba(#000, 0.3);
overflow: hidden;
> img {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: block;
object-fit: cover;
}
}

View file

@ -1,58 +0,0 @@
@use "vars";
.button {
padding: 0 20px;
width: max-content;
height: 35px;
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 500;
text-decoration: none;
border-radius: 9999px;
border: 0 solid transparent;
background-color: rgba(vars.$light, 0.04);
color: vars.$light;
overflow: hidden;
&:before {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border-radius: 9999px;
background-color: rgba(vars.$accent, 0.13);
opacity: 0;
transform: scaleX(0%);
pointer-events: none;
}
&:hover,
&:focus-visible {
outline: 0 solid transparent;
&:before {
opacity: 1;
transform: scaleX(100%);
transition:
opacity 0.5s cubic-bezier(0, 1, 0, 1),
transform 0.5s cubic-bezier(0, 1, 0, 1);
}
}
}

View file

@ -1,61 +0,0 @@
@use "vars";
.certificate {
padding: 16px;
height: 100%;
position: relative;
border-radius: vars.$radius;
border: 2px solid vars.$gray;
background-color: vars.$dark;
color: vars.$light;
overflow: hidden;
> svg {
width: 200px;
height: 200px;
position: absolute;
top: -37px;
left: -25px;
opacity: 0.03;
z-index: +1;
}
> div {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 8px;
text-decoration: none;
text-align: center;
z-index: +2;
> hr {
margin: calc(16px - 4px) 0;
border: 0 solid transparent;
border-bottom: 2px solid vars.$gray;
}
> .pill-list {
margin-top: 4px;
justify-content: center;
}
> .button {
margin-top: 4px;
padding: 0 32px;
}
}
}

View file

@ -1,51 +0,0 @@
@use "vars";
.header {
margin: -32px -32px 32px;
padding: 32px;
display: flex;
flex-direction: column;
gap: 16px;
border-bottom: 2px solid vars.$gray;
background-color: vars.$dark;
z-index: 999;
> h1 {
margin-bottom: calc((16px - 4px) * -1);
}
&.sticky {
position: sticky;
top: 0;
}
}
.header-follow {
margin: calc(-32px - 50px + 2px) 0 0;
height: 0;
position: sticky;
top: 0;
> div {
padding: 0 32px;
width: calc(100% + 64px);
height: 50px;
position: absolute;
left: -32px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
border-bottom: 2px solid vars.$gray;
background-color: vars.$dark;
color: vars.$light;
}
}

View file

@ -1,35 +0,0 @@
@use "vars";
main {
margin: 200px auto 0;
padding: 32px;
width: 100%;
max-width: 800px;
position: relative;
display: flex;
flex-direction: column;
flex-grow: 1;
border-top-left-radius: vars.$radius;
border-top-right-radius: vars.$radius;
border: 2px solid vars.$gray;
border-bottom: 0 solid transparent;
background-color: vars.$dark;
box-shadow: 0 8px 8px rgba(#000, 0.3);
z-index: 10;
> h1 {
padding-bottom: 8px;
}
> hr {
margin: 32px 0;
border: 0 solid transparent;
border-bottom: 2px solid vars.$gray;
}
}

View file

@ -1,72 +0,0 @@
@use "vars";
#music {
padding: 20px;
width: unset;
height: unset;
position: relative;
display: flex;
flex-direction: row;
justify-content: unset;
align-items: unset;
text-decoration: none;
font-size: 16px;
border-radius: vars.$radius;
&:before {
border-radius: vars.$radius;
}
.music-bg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
filter: blur(1px) saturate(110%);
mask-image: linear-gradient(to right, rgba(#fff, 0.4), rgba(#fff, 0));
object-fit: cover;
z-index: +1;
}
.music-cover {
margin-right: 4px;
width: 80px;
height: 80px;
border-radius: vars.$radius;
object-fit: cover;
z-index: +2;
}
> ul {
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 4px;
z-index: +2;
> li {
list-style: none;
}
}
}
@media only screen and (max-width: 500px) {
#music {
flex-direction: column;
}
}

View file

@ -1,51 +0,0 @@
@use "vars";
.pill {
padding: 0 10px;
width: max-content;
height: 30px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 13px;
border-radius: 99999px;
border: 2px solid rgba(vars.$light, 0.04);
background-color: vars.$dark;
color: vars.$light;
list-style: none;
> a {
color: inherit;
}
&.large {
padding: 0 16px;
height: 40px;
font-size: 16px;
}
&.size-button {
padding: 0 20px;
height: 35px;
font-size: 14px;
}
}
// If its a clickable element
a.pill,
button.pill {
text-decoration: none;
&:hover,
&:focus-visible {
border: 2px solid vars.$accent;
background-color: rgba(vars.$accent, 0.1);
outline: 0 solid transparent;
}
}

View file

@ -1,9 +0,0 @@
.pill-list {
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}

View file

@ -1,8 +0,0 @@
.project-list {
padding: 0;
display: flex;
flex-direction: column;
gap: 16px;
}

View file

@ -1,74 +0,0 @@
@use "vars";
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
scrollbar-color: vars.$accent transparent;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: vars.$accent;
}
&::-webkit-scrollbar-thumb:hover {
background: vars.$accent;
}
}
html {
min-height: 100vh;
font-size: 100%;
font-family: vars.$font-regular;
font-weight: 420;
line-height: 1.5;
text-size-adjust: none;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
}
body {
padding: 0 16px;
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #261f1b;
color: vars.$light;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
}
ul[role="list"],
ol[role="list"] {
list-style: none;
}
code *,
pre * {
font-family: vars.$font-mono !important;
}
img,
picture {
max-width: 100%;
display: block;
}

View file

@ -1,21 +0,0 @@
@use "vars";
.section {
padding-bottom: 32px;
> h2 {
padding-bottom: 10px;
}
> p {
padding-bottom: 8px;
}
> img {
border-radius: vars.$radius;
}
&:last-of-type {
padding-bottom: 0;
}
}

View file

@ -1,13 +0,0 @@
@use "vars";
@use "reset";
@use "main";
@use "banner";
@use "header";
@use "section";
@use "button";
@use "pill";
@use "pill_list";
@use "project_list";
@use "music";
@use "certificate";

View file

@ -1,9 +0,0 @@
$dark: #312620;
$gray: #382e28;
$light: #f0e9e4;
$accent: #a86338;
$radius: 4px;
$font-regular: "General Sans", sans-serif;
$font-mono: "JetBrains Mono", sans-serif;

View file

@ -1,62 +0,0 @@
import {
type CollectionEntry,
type ContentEntryMap,
getCollection,
} from "astro:content";
// https://github.com/hellotham/hello-astro/blob/e05706cf488bcec6e4c5494a622eedfc4e47d763/src/config.ts#L55C1-L62C2
export async function getPosts(collection: keyof ContentEntryMap) {
const posts = await getCollection(collection, ({ data }) => {
return data.draft !== true;
});
return posts.sort((a, b) =>
a.data.pubDate && b.data.pubDate
? Number(b.data.pubDate) - Number(a.data.pubDate)
: 0,
);
}
export function getMonth(date: Date): string {
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
return months[date.getMonth()];
}
export function getDaySuffix(date: Date): string {
let suffix = "th";
if (date.getDate() % 10 === 1 && date.getDate() !== 11) {
suffix = "st";
} else if (date.getDate() % 10 === 2 && date.getDate() !== 12) {
suffix = "nd";
} else if (date.getDate() % 10 === 3 && date.getDate() !== 13) {
suffix = "rd";
}
return suffix;
}
export async function getTagsBySlug(
postTags: string[],
): Promise<CollectionEntry<"tags">[]> {
const allTags: CollectionEntry<"tags">[] = await getCollection("tags");
const tags: CollectionEntry<"tags">[] = [];
postTags.forEach((postTag) => {
allTags.forEach((allTag) => {
if (allTag.slug === postTag) tags.push(allTag);
});
});
return tags;
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
{
"extends": "astro/tsconfigs/strict"
}