fix formatting

This commit is contained in:
2025-11-24 10:22:06 -05:00
parent e181c735b8
commit 321aa058ed
41 changed files with 776 additions and 767 deletions
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -1,29 +1,29 @@
import alpinejs from "@astrojs/alpinejs"; import alpinejs from '@astrojs/alpinejs';
// https://astro.build/config // https://astro.build/config
import mdx from "@astrojs/mdx"; import mdx from '@astrojs/mdx';
import { defineConfig } from "astro/config"; import { defineConfig } from 'astro/config';
import pagefind from "astro-pagefind"; import pagefind from 'astro-pagefind';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
site: "https://ghall.space", site: 'https://ghall.space',
output: "static", output: 'static',
markdown: { markdown: {
shikiConfig: { shikiConfig: {
themes: { themes: {
light: "github-light", light: 'github-light',
dark: "github-dark", dark: 'github-dark',
}, },
}, },
}, },
integrations: [ integrations: [
mdx(), mdx(),
alpinejs({ entrypoint: "/src/entrypoint" }), alpinejs({ entrypoint: '/src/entrypoint' }),
pagefind(), pagefind(),
], ],
redirects: { redirects: {
"/posts/[...slug]": "/blog/[...slug]", '/posts/[...slug]': '/blog/[...slug]',
"/blog": "/blog/page/1", '/blog': '/blog/page/1',
}, },
}); });
+3 -1
View File
@@ -9,7 +9,9 @@
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"lint": "eslint --ext .js --ext .ts --ext .astro" "check:lint": "eslint --ext .js --ext .ts --ext .astro",
"check:format": "prettier . --check",
"format": "prettier . --write"
}, },
"dependencies": { "dependencies": {
"@alpinejs/persist": "^3.15.1", "@alpinejs/persist": "^3.15.1",
+13 -13
View File
@@ -1,27 +1,27 @@
--- ---
import { Image } from "astro:assets"; import { Image } from 'astro:assets';
import portrait from "../assets/portrait.jpg"; import portrait from '../assets/portrait.jpg';
interface Props { interface Props {
size: number; size: number;
} }
const { size } = Astro.props; const { size } = Astro.props;
--- ---
<Image <Image
src={portrait} src={portrait}
alt="Illustrated portrait of a person with glasses and a beard, wearing a red and black plaid shirt. The background is a solid green color." alt="Illustrated portrait of a person with glasses and a beard, wearing a red and black plaid shirt. The background is a solid green color."
class="portrait" class="portrait"
width={`${size}`} width={`${size}`}
height={`${size}`} height={`${size}`}
/> />
<style lang="scss"> <style lang="scss">
.portrait { .portrait {
display: block; display: block;
margin: auto; margin: auto;
border-radius: 200px; border-radius: 200px;
} }
</style> </style>
+25 -25
View File
@@ -1,43 +1,43 @@
--- ---
import { add, format } from "date-fns"; import { add, format } from 'date-fns';
import CalendarIcon from "@assets/svg/calendar.svg"; import CalendarIcon from '@assets/svg/calendar.svg';
interface Props { interface Props {
title: string; title: string;
date: Date; date: Date;
slug?: string; slug?: string;
} }
const { title, date, slug } = Astro.props; const { title, date, slug } = Astro.props;
--- ---
<div class="blog-header"> <div class="blog-header">
{slug ? <a href={`/blog/${slug}`}>{title}</a> : <h1>{title}</h1>} {slug ? <a href={`/blog/${slug}`}>{title}</a> : <h1>{title}</h1>}
<div> <div>
<CalendarIcon class="calendar-icon" width={24} height={24} /> <CalendarIcon class="calendar-icon" width={24} height={24} />
<strong>{format(add(new Date(date), { hours: 6 }), "MMM do, y")}</strong> <strong>{format(add(new Date(date), { hours: 6 }), 'MMM do, y')}</strong>
</div> </div>
</div> </div>
<style lang="scss"> <style lang="scss">
a { a {
font-size: 1.3rem; font-size: 1.3rem;
} }
strong { strong {
font-weight: bolder; font-weight: bolder;
} }
.blog-header { .blog-header {
margin: 1rem 0; margin: 1rem 0;
& h2 { & h2 {
margin: 0; margin: 0;
} }
} }
.calendar-icon { .calendar-icon {
transform: translateY(0.3rem); transform: translateY(0.3rem);
} }
</style> </style>
+16 -16
View File
@@ -3,23 +3,23 @@
--- ---
<div class="sidebar"> <div class="sidebar">
<section> <section>
<h2>Tags</h2> <h2>Tags</h2>
<ul> <ul>
<li>Hello</li> <li>Hello</li>
<li>Hello</li> <li>Hello</li>
<li>Hello</li> <li>Hello</li>
<li>Hello</li> <li>Hello</li>
</ul> </ul>
</section> </section>
</div> </div>
<style lang="scss"> <style lang="scss">
.sidebar { .sidebar {
padding: 16px; padding: 16px;
border: var(--border); border: var(--border);
min-width: 140px; min-width: 140px;
margin-left: 14px; margin-left: 14px;
height: fit-content; height: fit-content;
} }
</style> </style>
+12 -12
View File
@@ -1,26 +1,26 @@
--- ---
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from 'astro:content';
import BlogHeader from "./BlogHeader.astro"; import BlogHeader from './BlogHeader.astro';
import Tags from "./Tags.astro"; import Tags from './Tags.astro';
interface Props { interface Props {
post: CollectionEntry<"blog">; post: CollectionEntry<'blog'>;
} }
const { post } = Astro.props; const { post } = Astro.props;
--- ---
<article> <article>
<BlogHeader title={post.data.title} date={post.data.pubDate} slug={post.id} /> <BlogHeader title={post.data.title} date={post.data.pubDate} slug={post.id} />
<Tags tags={post.data.tags} /> <Tags tags={post.data.tags} />
</article> </article>
<style lang="scss"> <style lang="scss">
article { article {
padding-bottom: 20px; padding-bottom: 20px;
&:not(:first-child) { &:not(:first-child) {
border-top: var(--border); border-top: var(--border);
} }
} }
</style> </style>
+11 -11
View File
@@ -1,20 +1,20 @@
--- ---
interface Props { interface Props {
tags: string[]; tags: string[];
} }
const { tags } = Astro.props; const { tags } = Astro.props;
--- ---
<span> <span>
{ {
tags.sort().map((tag: string, index: number) => ( tags.sort().map((tag: string, index: number) => (
<> <>
<a class="tag" href={`/blog/tag/${tag}`}> <a class="tag" href={`/blog/tag/${tag}`}>
{tag} {tag}
</a> </a>
{index < tags.length - 1 ? " | " : ""} {index < tags.length - 1 ? ' | ' : ''}
</> </>
)) ))
} }
</span> </span>
+50 -50
View File
@@ -1,68 +1,68 @@
--- ---
import MastodonIcon from "../assets/svg/mastodon.svg"; import MastodonIcon from '../assets/svg/mastodon.svg';
import RssIcon from "../assets/svg/rss.svg"; import RssIcon from '../assets/svg/rss.svg';
import ThemeToggle from "./ThemeToggle.astro"; import ThemeToggle from './ThemeToggle.astro';
const year = new Date().getFullYear(); const year = new Date().getFullYear();
--- ---
<footer> <footer>
<ThemeToggle /> <ThemeToggle />
<div class="socials"> <div class="socials">
<a <a
href="https://mastodon.social/@ghalldev" href="https://mastodon.social/@ghalldev"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
><MastodonIcon class="mastodon-icon" width={20} height={20} /> Follow me on ><MastodonIcon class="mastodon-icon" width={20} height={20} /> Follow me on
Mastodon</a Mastodon</a
> >
<a href="/rss.xml" <a href="/rss.xml"
><RssIcon class="rss-icon" width={20} height={20} /> Subscribe with RSS</a ><RssIcon class="rss-icon" width={20} height={20} /> Subscribe with RSS</a
> >
</div> </div>
<p> <p>
Copyright 2022 - {year}, Graham Hall Copyright 2022 - {year}, Graham Hall
<br /> <br />
Built with <a href="https://astro.build">Astro</a> Built with <a href="https://astro.build">Astro</a>
</p> </p>
</footer> </footer>
<style lang="scss"> <style lang="scss">
footer { footer {
margin: 2rem 0; margin: 2rem 0;
text-align: center; text-align: center;
border-top: var(--border); border-top: var(--border);
padding-top: 24px; padding-top: 24px;
} }
.rss-icon { .rss-icon {
transform: translateY(0.18rem); transform: translateY(0.18rem);
} }
.socials { .socials {
display: flex; display: flex;
gap: 24px; gap: 24px;
justify-content: center; justify-content: center;
& a { & a {
color: var(--text); color: var(--text);
& .rss-icon { & .rss-icon {
color: var(--orange); color: var(--orange);
} }
& .mastodon-icon { & .mastodon-icon {
color: var(--blue); color: var(--blue);
transform: translateY(4px); transform: translateY(4px);
} }
} }
} }
@media (max-width: 516px) { @media (max-width: 516px) {
.socials { .socials {
flex-direction: column; flex-direction: column;
} }
} }
</style> </style>
+80 -80
View File
@@ -1,97 +1,97 @@
--- ---
import BarsIcon from "../../assets/svg/bars.svg"; import BarsIcon from '../../assets/svg/bars.svg';
import CloseIcon from "../../assets/svg/xmark.svg"; import CloseIcon from '../../assets/svg/xmark.svg';
import { navLinks } from "../../data/nav-links"; import { navLinks } from '../../data/nav-links';
--- ---
<!-- drawerOpened is defined in /src/layouts/Layout.astro --> <!-- drawerOpened is defined in /src/layouts/Layout.astro -->
<div class="drawer-container" @keydown.escape="drawerOpen = false"> <div class="drawer-container" @keydown.escape="drawerOpen = false">
<button class="icon-button" @click="drawerOpen = !drawerOpen"> <button class="icon-button" @click="drawerOpen = !drawerOpen">
<BarsIcon width={24} height={24} /> <BarsIcon width={24} height={24} />
</button> </button>
<div <div
class="overlay" class="overlay"
@click="drawerOpen = !drawerOpen" @click="drawerOpen = !drawerOpen"
x-show="drawerOpen" x-show="drawerOpen"
x-transition:enter-start="hidden-overlay" x-transition:enter-start="hidden-overlay"
x-transition:enter-end="visible-overlay" x-transition:enter-end="visible-overlay"
x-transition:leave-start="visible-overlay" x-transition:leave-start="visible-overlay"
x-transition:leave-end="hidden-overlay" x-transition:leave-end="hidden-overlay"
> >
</div> </div>
<div <div
class="drawer" class="drawer"
x-show="drawerOpen" x-show="drawerOpen"
x-transition:enter-start="hidden-drawer" x-transition:enter-start="hidden-drawer"
x-transition:enter-end="visible-drawer" x-transition:enter-end="visible-drawer"
x-transition:leave-start="visible-drawer" x-transition:leave-start="visible-drawer"
x-transition:leave-end="hidden-drawer" x-transition:leave-end="hidden-drawer"
> >
<div> <div>
<button class="icon-button" @click="drawerOpen = !drawerOpen"> <button class="icon-button" @click="drawerOpen = !drawerOpen">
<CloseIcon width={24} height={24} /></button <CloseIcon width={24} height={24} /></button
> >
</div> </div>
<nav> <nav>
{navLinks.map((link) => <a href={`/${link.path}`}>{link.label}</a>)} {navLinks.map((link) => <a href={`/${link.path}`}>{link.label}</a>)}
</nav> </nav>
</div> </div>
</div> </div>
<style lang="scss"> <style lang="scss">
@use "../../styles/variables.scss" as *; @use '../../styles/variables.scss' as *;
.drawer-container { .drawer-container {
display: none; display: none;
} }
.overlay { .overlay {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
z-index: 2; z-index: 2;
transition: $transition; transition: $transition;
} }
.hidden-overlay { .hidden-overlay {
opacity: 0; opacity: 0;
} }
.drawer { .drawer {
right: 0; right: 0;
top: 0; top: 0;
background-color: var(--background); background-color: var(--background);
position: absolute; position: absolute;
width: 75vw; width: 75vw;
height: 100vh; height: 100vh;
z-index: 3; z-index: 3;
transition: $transition; transition: $transition;
& div { & div {
padding: 8px 6px 4px; padding: 8px 6px 4px;
border-bottom: var(--border); border-bottom: var(--border);
} }
} }
nav { nav {
text-align: left; text-align: left;
padding: 12px; padding: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
font-size: 1.2rem; font-size: 1.2rem;
} }
.hidden-drawer { .hidden-drawer {
transform: translateX(75vw); transform: translateX(75vw);
} }
@media screen and (max-width: $max-mobile-width) { @media screen and (max-width: $max-mobile-width) {
.drawer-container { .drawer-container {
display: block; display: block;
} }
} }
</style> </style>
+40 -40
View File
@@ -1,53 +1,53 @@
--- ---
import Drawer from "./Drawer.astro"; import Drawer from './Drawer.astro';
import Nav from "./Nav.astro"; import Nav from './Nav.astro';
--- ---
<header> <header>
<div class="container"> <div class="container">
<a href="/">ghall.space</a> <a href="/">ghall.space</a>
<div> <div>
<Nav /> <Nav />
<Drawer /> <Drawer />
</div> </div>
</div> </div>
</header> </header>
<style lang="scss"> <style lang="scss">
@use "../../styles/variables.scss" as *; @use '../../styles/variables.scss' as *;
header { header {
margin-bottom: 20px; margin-bottom: 20px;
height: 70px; height: 70px;
background-color: var(--background); background-color: var(--background);
width: 100%; width: 100%;
top: 0; top: 0;
position: fixed; position: fixed;
z-index: 1; z-index: 1;
border-bottom: var(--border); border-bottom: var(--border);
& .container { & .container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
max-width: $max-page-width; max-width: $max-page-width;
margin: auto; margin: auto;
padding: 0 16px; padding: 0 16px;
height: 100%; height: 100%;
& div { & div {
display: flex; display: flex;
gap: 20px; gap: 20px;
align-items: center; align-items: center;
} }
} }
} }
a { a {
font-size: 1.6rem; font-size: 1.6rem;
color: var(--text); color: var(--text);
font-weight: bold; font-weight: bold;
} }
</style> </style>
+29 -29
View File
@@ -1,40 +1,40 @@
--- ---
import { navLinks } from "../../data/nav-links"; import { navLinks } from '../../data/nav-links';
--- ---
<nav> <nav>
<ul> <ul>
{ {
navLinks.map((link) => ( navLinks.map((link) => (
<li> <li>
<a href={`/${link.path}`}>{link.label}</a> <a href={`/${link.path}`}>{link.label}</a>
</li> </li>
)) ))
} }
</ul> </ul>
</nav> </nav>
<style lang="scss"> <style lang="scss">
@use "../../styles/variables.scss" as *; @use '../../styles/variables.scss' as *;
ul { ul {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
list-style: none; list-style: none;
gap: 10px; gap: 10px;
font-size: 1.15rem; font-size: 1.15rem;
font-weight: 600; font-weight: 600;
align-items: center; align-items: center;
& a { & a {
color: var(--text); color: var(--text);
padding: 5px 10px; padding: 5px 10px;
} }
} }
@media screen and (max-width: $max-mobile-width) { @media screen and (max-width: $max-mobile-width) {
nav { nav {
display: none; display: none;
} }
} }
</style> </style>
+25 -25
View File
@@ -1,5 +1,5 @@
<div <div
x-data="{ x-data="{
latestPost: null, latestPost: null,
fetchPost() { fetchPost() {
fetch('https://mastodon.social/@ghalldev.rss') fetch('https://mastodon.social/@ghalldev.rss')
@@ -21,34 +21,34 @@
}); });
}, },
}" }"
x-init="fetchPost()" x-init="fetchPost()"
> >
<template x-if="latestPost"> <template x-if="latestPost">
<div class="mastodon-post"> <div class="mastodon-post">
<h2>Latest Mastodon Post</h2> <h2>Latest Mastodon Post</h2>
<div x-html="latestPost.body"></div> <div x-html="latestPost.body"></div>
<a x-bind:href="latestPost.link" target="_blank" rel="noopener noreferrer" <a x-bind:href="latestPost.link" target="_blank" rel="noopener noreferrer"
>View Post</a >View Post</a
> >
</div> </div>
</template> </template>
<template x-if="!latestPost"> <template x-if="!latestPost">
<p>Loading...</p> <p>Loading...</p>
</template> </template>
</div> </div>
<style lang="scss"> <style lang="scss">
@use "../styles/variables.scss" as *; @use '../styles/variables.scss' as *;
.mastodon-post { .mastodon-post {
margin: 2rem auto; margin: 2rem auto;
padding: 1rem; padding: 1rem;
max-width: 600px; max-width: 600px;
border-radius: $radius; border-radius: $radius;
border: var(--border); border: var(--border);
} }
.mastodon-post > h2 { .mastodon-post > h2 {
margin-top: 0; margin-top: 0;
} }
</style> </style>
+3 -3
View File
@@ -1,8 +1,8 @@
import { defineCollection, z } from "astro:content"; import { defineCollection, z } from 'astro:content';
import { glob } from "astro/loaders"; import { glob } from 'astro/loaders';
const blogCollection = defineCollection({ const blogCollection = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }), loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
pubDate: z.string().transform((str: string) => new Date(str)), pubDate: z.string().transform((str: string) => new Date(str)),
+1 -1
View File
@@ -12,7 +12,7 @@ I've never been a Gundam fan, the genre just never really appealed to me. But I
So, there was a little anime I watched back in high school called [Full Metal Panic](https://en.wikipedia.org/wiki/Full_Metal_Panic!) which, like Gundam, features people piloting giant mecha. As luck would have it, Bandai, the makers of Gunpla kits, have/had the license to make kits for the series. Not-so-luckily, the kit I wanted based on the main character's mech is impossible to find at a reasonable price at the moment, so I settled for my second pick: So, there was a little anime I watched back in high school called [Full Metal Panic](https://en.wikipedia.org/wiki/Full_Metal_Panic!) which, like Gundam, features people piloting giant mecha. As luck would have it, Bandai, the makers of Gunpla kits, have/had the license to make kits for the series. Not-so-luckily, the kit I wanted based on the main character's mech is impossible to find at a reasonable price at the moment, so I settled for my second pick:
![a box sitting on a table with an image of a mech and text that reads 'M9 Gernsback ver IV \(Agressor Squadron)'](src/assets/blog/gunpla/box.jpg) ![a box sitting on a table with an image of a mech and text that reads 'M9 Gernsback ver IV (Agressor Squadron)'](src/assets/blog/gunpla/box.jpg)
So, with the kit in hand, I opened the box and was greeted by a handful of plastic pouches containing what seemed like a thousand parts connected on plastic sprues in a variety of blues and grays. I took out the contents and perused through the included instructions, which read very much like it was designed by Ikea, if Ikea sold miniature giant robots. The writing was all in Japanese, but the illustrations were enough to guide one through the process no matter what language they can read. So, with the kit in hand, I opened the box and was greeted by a handful of plastic pouches containing what seemed like a thousand parts connected on plastic sprues in a variety of blues and grays. I took out the contents and perused through the included instructions, which read very much like it was designed by Ikea, if Ikea sold miniature giant robots. The writing was all in Japanese, but the illustrations were enough to guide one through the process no matter what language they can read.
@@ -3,6 +3,7 @@ title: 'AI & Creativity'
pubDate: '11/11/24' pubDate: '11/11/24'
tags: ['Tech', 'Creativity'] tags: ['Tech', 'Creativity']
--- ---
Recently, Ive been pondering generative AI and its effects. Not on jobs, the economy, or even the damage its done to our ability to trust information we see online (whether its something generated by a malicious actor looking to mislead, or a simple “hallucination” causing an innocent mistake). No, Ive been pondering its effects on human creativity, and our capacity to learn and grow as creative creatures. This is something Ive had in the back of my mind for a quite a while, and I think Ive finally reached a point where I can put my feelings into words. Recently, Ive been pondering generative AI and its effects. Not on jobs, the economy, or even the damage its done to our ability to trust information we see online (whether its something generated by a malicious actor looking to mislead, or a simple “hallucination” causing an innocent mistake). No, Ive been pondering its effects on human creativity, and our capacity to learn and grow as creative creatures. This is something Ive had in the back of my mind for a quite a while, and I think Ive finally reached a point where I can put my feelings into words.
My experience with generative AI started way back at the end of 2022 with [an AI-assisted dating profile](https://ghall.blog/posts/2022/creating-a-dating-profile-with-ai/). Just over a month later, I posted a [follow up](https://ghall.blog/posts/2023/an-update-on-my-ai-dating-profile/), in which I said the following: My experience with generative AI started way back at the end of 2022 with [an AI-assisted dating profile](https://ghall.blog/posts/2022/creating-a-dating-profile-with-ai/). Just over a month later, I posted a [follow up](https://ghall.blog/posts/2023/an-update-on-my-ai-dating-profile/), in which I said the following:
@@ -25,4 +25,5 @@ While any code getting shipped to the browser will still need to be compiled to
I started off being annoyed with using types in my code because, to be quite frank, they are annoying. They'll make your code throw errors whenever you try to multiply a string, or something silly like that. But that's part of the beauty I've come to appreciate. They're there to help you write better code, and to point you in the right direction when something inevitably goes wrong. I started off being annoyed with using types in my code because, to be quite frank, they are annoying. They'll make your code throw errors whenever you try to multiply a string, or something silly like that. But that's part of the beauty I've come to appreciate. They're there to help you write better code, and to point you in the right direction when something inevitably goes wrong.
[^1]: [Here](https://stackoverflow.com/questions/1517582/what-is-the-difference-between-statically-typed-and-dynamically-typed-languages) is a StackOverflow thread on the difference between dynamic and static typing put far better than I ever could explain it [^1]: [Here](https://stackoverflow.com/questions/1517582/what-is-the-difference-between-statically-typed-and-dynamically-typed-languages) is a StackOverflow thread on the difference between dynamic and static typing put far better than I ever could explain it
[^2]: As an example, I rewrote [my CLI clone of Wordle](https://github.com/ghall89/wordle-cli) in TypeScript [^2]: As an example, I rewrote [my CLI clone of Wordle](https://github.com/ghall89/wordle-cli) in TypeScript
@@ -3,6 +3,7 @@ title: 'Announcing AutoDock'
pubDate: '1/17/25' pubDate: '1/17/25'
tags: ['MacOS', 'Apps', 'Programming'] tags: ['MacOS', 'Apps', 'Programming']
--- ---
I'm a MacBook Air user who often uses a larger external display, and I have a, perhaps, idiosyncratic desire to have the dock always visible on the larger display at my desk, but prefer it hidden when using the smaller, built-in one. I'm a MacBook Air user who often uses a larger external display, and I have a, perhaps, idiosyncratic desire to have the dock always visible on the larger display at my desk, but prefer it hidden when using the smaller, built-in one.
While it's not all that inconvenient to manually swap the dock with a quick <kbd>opt</kbd> + <kbd>cmd</kbd> + <kbd>D</kbd>, I thought it would be cool to automate the process based on display size. I looked around for an app that would do this, and couldn't find one that worked reliably. While it's not all that inconvenient to manually swap the dock with a quick <kbd>opt</kbd> + <kbd>cmd</kbd> + <kbd>D</kbd>, I thought it would be cool to automate the process based on display size. I looked around for an app that would do this, and couldn't find one that worked reliably.
@@ -3,6 +3,7 @@ title: 'Skip the Algorithm: Be Your Own Online Curator'
pubDate: '3/1/25' pubDate: '3/1/25'
tags: ['Tech', 'Mindfulness', 'Apps'] tags: ['Tech', 'Mindfulness', 'Apps']
--- ---
Ever since I started using Mastodon, I've become a lot more interested in reducing the presence of algorithmic content in my digital life. I've never been particularly thrilled with algorithmic feeds of content. I was among the people who was quite furious when Instagram ditched the chronological timeline, and then started inserting content from accounts I didn't even follow. Ever since I started using Mastodon, I've become a lot more interested in reducing the presence of algorithmic content in my digital life. I've never been particularly thrilled with algorithmic feeds of content. I was among the people who was quite furious when Instagram ditched the chronological timeline, and then started inserting content from accounts I didn't even follow.
Somewhere along the line, I think I just started accepting what I assumed was inevitable. I think I was probably naive, and didn't consider the ills both social and psychological that resulted algorithmically curated content. It was just an annoying thing that I just had to accept. Somewhere along the line, I think I just started accepting what I assumed was inevitable. I think I was probably naive, and didn't consider the ills both social and psychological that resulted algorithmically curated content. It was just an annoying thing that I just had to accept.
@@ -4,7 +4,7 @@ pubDate: '5/11/25'
tags: ['Gaming'] tags: ['Gaming']
--- ---
My latest board gaming obsession is quite an unlikely one for me. Im not particularly fond of the viking aesthetic in general, and, while I dont dislike engine-builder games, theyre not generally the type of game Id go out of my way to play. My latest board gaming obsession is quite an unlikely one for me. Im not particularly fond of the viking aesthetic in general, and, while I dont dislike engine-builder games, theyre not generally the type of game Id go out of my way to play.
But a little game called [Knarr](https://boardgamegeek.com/boardgame/379629) struck a chord with me. Someone in my gaming group introduced me, and I enjoyed it so much I ended up picking up my own copy. Something that has only happened once before with [Akropolis](https://boardgamegeek.com/boardgame/357563), another favorite of mine. But a little game called [Knarr](https://boardgamegeek.com/boardgame/379629) struck a chord with me. Someone in my gaming group introduced me, and I enjoyed it so much I ended up picking up my own copy. Something that has only happened once before with [Akropolis](https://boardgamegeek.com/boardgame/357563), another favorite of mine.
+20 -20
View File
@@ -10,26 +10,26 @@ The Switch is also the console Ive played the most games on, so I thought it
Also, I listed them in alphabetical order partly because I couldnt decide on a ranking, but mostly because it makes my brain happy. 🧠 Also, I listed them in alphabetical order partly because I couldnt decide on a ranking, but mostly because it makes my brain happy. 🧠
* A Short Hike - A Short Hike
* Animal Crossing: New Horizons - Animal Crossing: New Horizons
* Fire Emblem Engage - Fire Emblem Engage
* Hades - Hades
* Kirby and the Forgotten Land - Kirby and the Forgotten Land
* The Legend of Zelda: Tears of the Kingdom - The Legend of Zelda: Tears of the Kingdom
* Metroid Dread - Metroid Dread
* Persona 5 Royal - Persona 5 Royal
* Pokemon Legends: Arceus - Pokemon Legends: Arceus
* Sea of Stars - Sea of Stars
* Shantae and the Seven Sirens - Shantae and the Seven Sirens
* Splatoon 3 - Splatoon 3
* Super Mario Odyssey - Super Mario Odyssey
* Super Mario Wonder - Super Mario Wonder
* Super Smash Bros Ultimate - Super Smash Bros Ultimate
* Tetris 99 - Tetris 99
* Unicorn Overlord - Unicorn Overlord
* The Witcher 3: Wild Hunt - The Witcher 3: Wild Hunt
* Xenoblade Chronicles 3 - Xenoblade Chronicles 3
* Yooka-Laylee and the Impossible Lair - Yooka-Laylee and the Impossible Lair
I opted to stay away from remasters/remakes, but as a bonus here are the ones that stood out to me… I opted to stay away from remasters/remakes, but as a bonus here are the ones that stood out to me…
@@ -14,7 +14,7 @@ Unlike Arc, and indeed unlike most browsers these days, Zen doesnt use Chromi
On the web dev side, Ive found the developer tools to be…fine. Ive used the Firefox dev tools in the past, so Im comfortable with them, and it didnt take me long to adjust. While I much prefer the Chromium dev tools (which is about the only thing I _like_ about Chromium), I dont find the Gecko dev tools that much worse. Which is more than I can say about Safaris dev tools. 😬 On the web dev side, Ive found the developer tools to be…fine. Ive used the Firefox dev tools in the past, so Im comfortable with them, and it didnt take me long to adjust. While I much prefer the Chromium dev tools (which is about the only thing I _like_ about Chromium), I dont find the Gecko dev tools that much worse. Which is more than I can say about Safaris dev tools. 😬
Another major positive is battery life. Id become somewhat used to the less-than-ideal battery life I was getting with Arc, and slightly less so than Vivaldi. But after switching to Zen, I started to notice my battery was lasting quite a bit longer. As I write this (with Zen open in the background), my M2 MacBook Air is at about 88% battery, with 11 hours remaining. I know in practice it wont *actually* last 11 hours, but thats higher than any estimation that I can recall ever getting using a Chromium-based browser. In practice, it means that, generally, depending on which Node environments I have to run locally that day, I can make it through an entire 8 hour workday, with a bit to spare. Another major positive is battery life. Id become somewhat used to the less-than-ideal battery life I was getting with Arc, and slightly less so than Vivaldi. But after switching to Zen, I started to notice my battery was lasting quite a bit longer. As I write this (with Zen open in the background), my M2 MacBook Air is at about 88% battery, with 11 hours remaining. I know in practice it wont _actually_ last 11 hours, but thats higher than any estimation that I can recall ever getting using a Chromium-based browser. In practice, it means that, generally, depending on which Node environments I have to run locally that day, I can make it through an entire 8 hour workday, with a bit to spare.
The biggest downsides are the inability to access DRMd content online, specifically on streaming services (Ive been swapping to Safari to get my Andor and Doctor Who fix), and the fact that the iCloud Keychain extension doesn't work (though I've heard through the internet grapevine a fix is coming). Also, the fun and I will admit, relatively obnoxious animations on this site do not work, but that should be remedied whenever Firefox fully implements the View Transition API. The biggest downsides are the inability to access DRMd content online, specifically on streaming services (Ive been swapping to Safari to get my Andor and Doctor Who fix), and the fact that the iCloud Keychain extension doesn't work (though I've heard through the internet grapevine a fix is coming). Also, the fun and I will admit, relatively obnoxious animations on this site do not work, but that should be remedied whenever Firefox fully implements the View Transition API.
@@ -3,6 +3,7 @@ title: 'The New ghall.space'
pubDate: '9/1/25' pubDate: '9/1/25'
tags: ['Meta', 'Programming', 'Web Dev'] tags: ['Meta', 'Programming', 'Web Dev']
--- ---
It's been a while since I originally built this website, which started life as a little project to learn the, at the time, brand new web framework Astro. My skills in web development have improved greatly in that time, as has the functionality of the Astro framework. Therefore, I thought it would be a fun project to make some improvements to the site, both visually and in how the codebase is structured. It's been a while since I originally built this website, which started life as a little project to learn the, at the time, brand new web framework Astro. My skills in web development have improved greatly in that time, as has the functionality of the Astro framework. Therefore, I thought it would be a fun project to make some improvements to the site, both visually and in how the codebase is structured.
One of the biggest front-facing changes I've made, aside from new typography and some layout adjustments, is a mobile-responsive navigation bar. Mobile is always something I've been conscious of when building this site, but I opted for a simplified approach by ensuring the site was usable on mobile without introducing many responsive elements. But as the site morphed from a simple blog to a more generalized space on the internet (hence the ".space" TLD), I've discovered the limits of that design philosophy. One of the biggest front-facing changes I've made, aside from new typography and some layout adjustments, is a mobile-responsive navigation bar. Mobile is always something I've been conscious of when building this site, but I opted for a simplified approach by ensuring the site was usable on mobile without introducing many responsive elements. But as the site morphed from a simple blog to a more generalized space on the internet (hence the ".space" TLD), I've discovered the limits of that design philosophy.
@@ -1,7 +1,7 @@
--- ---
title: 'Tinkering With Linux' title: 'Tinkering With Linux'
pubDate: '10/27/25' pubDate: '10/27/25'
tags: ['Tech', 'Linux' ] tags: ['Tech', 'Linux']
--- ---
For the last 8 or 9 months, I've been experimenting on and off with Linux. Specifically Fedora (via Asahi Linux) for those who are curious. I installed it on an M1 MacBook Air, which I keep around as an emergency backup, and I've been mostly pleased with how well it's been running. For the last 8 or 9 months, I've been experimenting on and off with Linux. Specifically Fedora (via Asahi Linux) for those who are curious. I installed it on an M1 MacBook Air, which I keep around as an emergency backup, and I've been mostly pleased with how well it's been running.
@@ -41,5 +41,7 @@ I do, however, have some questions for the Linux community if any of you haven't
Feel free to reach out to me on [Mastodon](https://mastodon.social/@ghalldev). Feel free to reach out to me on [Mastodon](https://mastodon.social/@ghalldev).
[^1]: However, to continue my series of gimicky posts, like when I [wrote about the Mac using Mac OS 7](/blog/2024/ramblings-on-the-macintosh), and [wrote about taking intentional analog time with pen and paper](/blog/2025/intentional-analog-time), I'm writing this blog post in Linux. [^1]: However, to continue my series of gimicky posts, like when I [wrote about the Mac using Mac OS 7](/blog/2024/ramblings-on-the-macintosh), and [wrote about taking intentional analog time with pen and paper](/blog/2025/intentional-analog-time), I'm writing this blog post in Linux.
[^2]: This annoyingly does not work for all apps, but I'm probably missing something obvious. [^2]: This annoyingly does not work for all apps, but I'm probably missing something obvious.
[^3]: Of note, the Plasma shell itself seems to be the most memory intensive process, using about 500MB of RAM, which seems more than reasonable. [^3]: Of note, the Plasma shell itself seems to be the most memory intensive process, using about 500MB of RAM, which seems more than reasonable.
+4 -4
View File
@@ -4,8 +4,8 @@ export interface NavLink {
} }
export const navLinks: NavLink[] = [ export const navLinks: NavLink[] = [
{ label: "Blog", path: "blog/page/1" }, { label: 'Blog', path: 'blog/page/1' },
{ label: "Now", path: "now" }, { label: 'Now', path: 'now' },
{ label: "Projects", path: "projects" }, { label: 'Projects', path: 'projects' },
{ label: "Search", path: "search" }, { label: 'Search', path: 'search' },
]; ];
+12 -12
View File
@@ -1,6 +1,6 @@
import autodockImg from "@assets/projects/autodock.png"; import autodockImg from '@assets/projects/autodock.png';
import bggClientImg from "@assets/projects/bgg-client.png"; import bggClientImg from '@assets/projects/bgg-client.png';
import keystashImg from "@assets/projects/keystash.png"; import keystashImg from '@assets/projects/keystash.png';
export interface Project { export interface Project {
title: string; title: string;
@@ -11,24 +11,24 @@ export interface Project {
export const projects: Project[] = [ export const projects: Project[] = [
{ {
title: "AutoDock", title: 'AutoDock',
description: description:
"A MacOS menubar utility for automatically hiding and showing the Dock based on the screen size of the connected displays.", 'A MacOS menubar utility for automatically hiding and showing the Dock based on the screen size of the connected displays.',
image: autodockImg, image: autodockImg,
link: "https://github.com/ghall89/AutoDock", link: 'https://github.com/ghall89/AutoDock',
}, },
{ {
title: "KeyStash", title: 'KeyStash',
description: description:
"A MacOS application for managing software license keys for software purchased outside the Mac App Store.", 'A MacOS application for managing software license keys for software purchased outside the Mac App Store.',
image: keystashImg, image: keystashImg,
link: "https://github.com/ghall89/KeyStash", link: 'https://github.com/ghall89/KeyStash',
}, },
{ {
title: "bgg-client", title: 'bgg-client',
description: description:
"A TypeScript client for working with the BoardGameGeek.com API.", 'A TypeScript client for working with the BoardGameGeek.com API.',
image: bggClientImg, image: bggClientImg,
link: "https://www.npmjs.com/package/bgg-client", link: 'https://www.npmjs.com/package/bgg-client',
}, },
]; ];
+2 -2
View File
@@ -1,5 +1,5 @@
import persist from "@alpinejs/persist"; import persist from '@alpinejs/persist';
import type { Alpine } from "alpinejs"; import type { Alpine } from 'alpinejs';
export default (Alpine: Alpine) => { export default (Alpine: Alpine) => {
Alpine.plugin(persist); Alpine.plugin(persist);
+1 -1
View File
@@ -1,5 +1,5 @@
/// <reference path="../.astro/types.d.ts" /> /// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" /> /// <reference types="astro/client" />
interface Window { interface Window {
Alpine: import("alpinejs").Alpine; Alpine: import('alpinejs').Alpine;
} }
+39 -39
View File
@@ -1,58 +1,58 @@
--- ---
import { ClientRouter } from "astro:transitions"; import { ClientRouter } from 'astro:transitions';
import "../styles/global.scss"; import '../styles/global.scss';
import Footer from "@components/Footer.astro"; import Footer from '@components/Footer.astro';
import Header from "@components/Header/Header.astro"; import Header from '@components/Header/Header.astro';
export interface Props { export interface Props {
title: string; title: string;
frontmatter?: { frontmatter?: {
title: string; title: string;
}; };
} }
const title = Astro.props.title || Astro.props.frontmatter?.title || "Unknown"; const title = Astro.props.title || Astro.props.frontmatter?.title || 'Unknown';
--- ---
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="robots" content="noindex" /><meta <meta name="robots" content="noindex" /><meta
name="description" name="description"
content="My little space on the World Wide Web." content="My little space on the World Wide Web."
/><link /><link
rel="alternate" rel="alternate"
type="application/rss+xml" type="application/rss+xml"
title="ghall.space - RSS" title="ghall.space - RSS"
href={`${Astro.site}rss.xml`} href={`${Astro.site}rss.xml`}
/><title>{`ghall.space - ${title}`}</title> /><title>{`ghall.space - ${title}`}</title>
<ClientRouter /> <ClientRouter />
</head> </head>
<body <body
class="layout-simple" class="layout-simple"
x-data="{ x-data="{
drawerOpen: false, drawerOpen: false,
darkMode: $persist(false), darkMode: $persist(false),
serifs: $persist(true) serifs: $persist(true)
}" }"
:class="{ 'lock-scroll': drawerOpen, 'theme-dark': darkMode, 'serif-text': serifs}" :class="{ 'lock-scroll': drawerOpen, 'theme-dark': darkMode, 'serif-text': serifs}"
> >
<Header /> <Header />
<main transition:animate="initial"> <main transition:animate="initial">
<slot /> <slot />
</main> </main>
<Footer /> <Footer />
</body> </body>
</html> </html>
<style lang="scss"> <style lang="scss">
/* Prevent scrolling when drawer (located in Header) is open */ /* Prevent scrolling when drawer (located in Header) is open */
.lock-scroll { .lock-scroll {
overflow: hidden; overflow: hidden;
} }
</style> </style>
+19 -19
View File
@@ -1,30 +1,30 @@
--- ---
import { Image } from "astro:assets"; import { Image } from 'astro:assets';
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
import DataGif from "../assets/it-does-not-exist.gif"; import DataGif from '../assets/it-does-not-exist.gif';
--- ---
<Layout title="Not Found"> <Layout title="Not Found">
<h2>404 - Page Not Found</h2> <h2>404 - Page Not Found</h2>
<Image <Image
class="gif" class="gif"
src={DataGif} src={DataGif}
alt="Data from Star Trek with the caption 'It does not exist'" alt="Data from Star Trek with the caption 'It does not exist'"
/> />
</Layout> </Layout>
<style lang="scss"> <style lang="scss">
@use "../styles/variables.scss" as *; @use '../styles/variables.scss' as *;
h2 { h2 {
text-align: center; text-align: center;
} }
.gif { .gif {
display: block; display: block;
margin: auto; margin: auto;
border-radius: $radius; border-radius: $radius;
box-shadow: var(--shadow); box-shadow: var(--shadow);
} }
</style> </style>
+26 -26
View File
@@ -1,15 +1,15 @@
--- ---
import { getCollection, render } from "astro:content"; import { getCollection, render } from 'astro:content';
import BlogHeader from "@components/Blog/BlogHeader.astro"; import BlogHeader from '@components/Blog/BlogHeader.astro';
import Tags from "@components/Blog/Tags.astro"; import Tags from '@components/Blog/Tags.astro';
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection("blog"); const posts = await getCollection('blog');
return posts.map((post) => ({ return posts.map((post) => ({
params: { slug: post.id }, params: { slug: post.id },
props: { post }, props: { post },
})); }));
} }
const { post } = Astro.props; const { post } = Astro.props;
@@ -20,24 +20,24 @@ const { Content } = await render(post);
--- ---
<Layout title={data.title}> <Layout title={data.title}>
<article> <article>
<BlogHeader title={data.title} date={data.pubDate} /> <BlogHeader title={data.title} date={data.pubDate} />
<Tags tags={data.tags} /> <Tags tags={data.tags} />
<Content /> <Content />
<a href="https://notbyai.fyi/" target="_blank"> <a href="https://notbyai.fyi/" target="_blank">
<i class="not-by-ai"></i> <i class="not-by-ai"></i>
</a> </a>
</article> </article>
</Layout> </Layout>
<style lang="scss"> <style lang="scss">
.not-by-ai { .not-by-ai {
display: block; display: block;
width: 134px; width: 134px;
height: 45px; height: 45px;
background-image: url(../../assets/svg/Written-By-Human-Not-By-AI-Badge-white.svg); background-image: url(../../assets/svg/Written-By-Human-Not-By-AI-Badge-white.svg);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
margin: 1rem 0; margin: 1rem 0;
} }
</style> </style>
+42 -42
View File
@@ -1,64 +1,64 @@
--- ---
import { type CollectionEntry, getCollection } from "astro:content"; import { type CollectionEntry, getCollection } from 'astro:content';
import PostPreview from "@components/Blog/PostPreview.astro"; import PostPreview from '@components/Blog/PostPreview.astro';
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
import type { Page } from "astro"; import type { Page } from 'astro';
export async function getStaticPaths({ paginate }) { export async function getStaticPaths({ paginate }) {
const allPosts = await getCollection("blog"); const allPosts = await getCollection('blog');
allPosts.sort( allPosts.sort(
(a, b) => (a, b) =>
Date.parse(String(b.data.pubDate)) - Date.parse(String(a.data.pubDate)), Date.parse(String(b.data.pubDate)) - Date.parse(String(a.data.pubDate))
); );
return paginate(allPosts, { pageSize: 10 }); return paginate(allPosts, { pageSize: 10 });
} }
interface Props { interface Props {
page: Page<CollectionEntry<"blog">>; page: Page<CollectionEntry<'blog'>>;
} }
const { page } = Astro.props; const { page } = Astro.props;
--- ---
<Layout title="Blog Archive"> <Layout title="Blog Archive">
{page.data.map((post) => <PostPreview post={post} />)} {page.data.map((post) => <PostPreview post={post} />)}
<div class="pagination"> <div class="pagination">
{ {
page.currentPage !== 1 ? ( page.currentPage !== 1 ? (
<a href={`/blog/page/${page.currentPage - 1}`}>Previous</a> <a href={`/blog/page/${page.currentPage - 1}`}>Previous</a>
) : ( ) : (
<span class="disabled">Previous</span> <span class="disabled">Previous</span>
) )
} }
{ {
page.currentPage !== page.lastPage ? ( page.currentPage !== page.lastPage ? (
<a href={`/blog/page/${page.currentPage + 1}`}>Next</a> <a href={`/blog/page/${page.currentPage + 1}`}>Next</a>
) : ( ) : (
<span class="disabled">Next</span> <span class="disabled">Next</span>
) )
} }
</div> </div>
</Layout> </Layout>
<style lang="scss"> <style lang="scss">
ul { ul {
list-style: none; list-style: none;
} }
.pagination { .pagination {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin: auto; margin: auto;
margin-top: 2rem; margin-top: 2rem;
gap: 1rem; gap: 1rem;
} }
.disabled { .disabled {
color: var(--text); color: var(--text);
opacity: 0.5; opacity: 0.5;
} }
</style> </style>
+46 -46
View File
@@ -1,72 +1,72 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from 'astro:content';
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
import { add, format } from "date-fns"; import { add, format } from 'date-fns';
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection("blog"); const posts = await getCollection('blog');
const tags = ["all"]; const tags = ['all'];
posts.forEach((post) => { posts.forEach((post) => {
post.data.tags.forEach((tag) => { post.data.tags.forEach((tag) => {
if (!tags.includes(tag)) { if (!tags.includes(tag)) {
tags.push(tag); tags.push(tag);
} }
}); });
}); });
return tags.map((tag) => ({ return tags.map((tag) => ({
params: { tag }, params: { tag },
props: { tag }, props: { tag },
})); }));
} }
const { tag }: { tag?: string } = Astro.params; const { tag }: { tag?: string } = Astro.params;
const posts = await getCollection("blog", ({ data }) => { const posts = await getCollection('blog', ({ data }) => {
if (!tag) { if (!tag) {
return false; return false;
} }
if (tag === "all") { if (tag === 'all') {
return true; return true;
} }
return data.tags.includes(tag); return data.tags.includes(tag);
}); });
if (posts.length === 0) { if (posts.length === 0) {
return Astro.redirect("/404"); return Astro.redirect('/404');
} }
posts.sort( posts.sort(
(a, b) => (a, b) =>
Date.parse(String(b.data.pubDate)) - Date.parse(String(a.data.pubDate)), Date.parse(String(b.data.pubDate)) - Date.parse(String(a.data.pubDate))
); );
--- ---
<Layout title={`Blog - ${tag}`}> <Layout title={`Blog - ${tag}`}>
<h1>All posts tagged: {tag}</h1> <h1>All posts tagged: {tag}</h1>
<ul> <ul>
{ {
posts.map((post) => ( posts.map((post) => (
<li> <li>
<a href={`/posts/${post.id}`}>{post.data.title}</a> - <a href={`/posts/${post.id}`}>{post.data.title}</a> -
<span> <span>
{format( {format(
add(new Date(post.data.pubDate), { hours: 6 }), add(new Date(post.data.pubDate), { hours: 6 }),
"MMM do, y", 'MMM do, y'
)} )}
</span> </span>
</li> </li>
)) ))
} }
</ul> </ul>
</Layout> </Layout>
<style lang="scss"> <style lang="scss">
ul { ul {
list-style: none; list-style: none;
} }
</style> </style>
+60 -60
View File
@@ -1,75 +1,75 @@
--- ---
export const prerender = true; export const prerender = true;
import { getCollection } from "astro:content"; import { getCollection } from 'astro:content';
import Avatar from "@components/Avatar.astro"; import Avatar from '@components/Avatar.astro';
import LatestPost from "@components/LatestPost.astro"; import LatestPost from '@components/LatestPost.astro';
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
import BlueskyIcon from "../assets/svg/bluesky.svg"; import BlueskyIcon from '../assets/svg/bluesky.svg';
import MastodonIcon from "../assets/svg/mastodon.svg"; import MastodonIcon from '../assets/svg/mastodon.svg';
const iconSize = 16; const iconSize = 16;
const posts = await getCollection("blog"); const posts = await getCollection('blog');
const latestPost = posts.sort( const latestPost = posts.sort(
(a, b) => (a, b) =>
new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf(), new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf()
)[0]; )[0];
--- ---
<Layout title="Welcome"> <Layout title="Welcome">
<Avatar size={200} /> <Avatar size={200} />
<p> <p>
My name is <strong>Graham</strong> (he/him), a full-stack web developer, and My name is <strong>Graham</strong> (he/him), a full-stack web developer, and
tech enthusiast. tech enthusiast.
</p> </p>
<p> <p>
When I'm not writing code, I'm usually enjoying one of my other hobbies; When I'm not writing code, I'm usually enjoying one of my other hobbies;
video games, board games, music, photography, art, the list goes on... video games, board games, music, photography, art, the list goes on...
</p> </p>
<p> <p>
I also like writing about stuff, so I put together this site to share I also like writing about stuff, so I put together this site to share
whatever's on my mind about the world of tech, gaming, life, web whatever's on my mind about the world of tech, gaming, life, web
development, and whatever else strikes my fancy. development, and whatever else strikes my fancy.
</p> </p>
<p> <p>
Read my latest blog post, <a href={`blog/${latestPost.id}`} Read my latest blog post, <a href={`blog/${latestPost.id}`}
>{latestPost.data.title}</a >{latestPost.data.title}</a
>, posted on {new Date(latestPost.data.pubDate).toLocaleDateString()}. >, posted on {new Date(latestPost.data.pubDate).toLocaleDateString()}.
</p> </p>
<p> <p>
If your interested in checking out my web dev projects, check out <a If your interested in checking out my web dev projects, check out <a
href="https://ghall.dev/" href="https://ghall.dev/"
target="_blank" target="_blank"
rel="noopener noreferrer">my portfolio</a rel="noopener noreferrer">my portfolio</a
>. >.
</p> </p>
<p> <p>
If you want to get in touch, I'm on <a If you want to get in touch, I'm on <a
rel="me" rel="me"
href="https://mastodon.social/@ghalldev" href="https://mastodon.social/@ghalldev"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
><MastodonIcon width={iconSize} height={iconSize} />Mastodon</a ><MastodonIcon width={iconSize} height={iconSize} />Mastodon</a
> and <a > and <a
href="https://bsky.app/profile/ghalldev.bsky.social" href="https://bsky.app/profile/ghalldev.bsky.social"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
><BlueskyIcon width={iconSize} height={iconSize} />Bluesky</a ><BlueskyIcon width={iconSize} height={iconSize} />Bluesky</a
>. >.
</p> </p>
<p> <p>
My portrait was drawn by <a My portrait was drawn by <a
href="https://www.nataliavazquezgarcia.com" href="https://www.nataliavazquezgarcia.com"
target="_blank" target="_blank"
rel="noopener noreferrer">Natalia Vazquez</a rel="noopener noreferrer">Natalia Vazquez</a
>. >.
</p> </p>
<LatestPost /> <LatestPost />
</Layout> </Layout>
<style lang="scss"> <style lang="scss">
a > svg { a > svg {
transform: translateY(0.1rem); transform: translateY(0.1rem);
} }
</style> </style>
+37 -37
View File
@@ -1,47 +1,47 @@
--- ---
import Card from "@components/Card.astro"; import Card from '@components/Card.astro';
import { projects } from "@data/projects"; import { projects } from '@data/projects';
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
const title = "Projects"; const title = 'Projects';
--- ---
<Layout title={title}> <Layout title={title}>
<h1>{title}</h1> <h1>{title}</h1>
<div class="projects-grid"> <div class="projects-grid">
{ {
projects.map((project) => ( projects.map((project) => (
<Card <Card
title={project.title} title={project.title}
image={project.image} image={project.image}
links={[ links={[
{ {
label: "More...", label: 'More...',
href: project.link, href: project.link,
newWindow: true, newWindow: true,
}, },
]} ]}
> >
<div class="project-content"> <div class="project-content">
<p>{project.description}</p> <p>{project.description}</p>
</div> </div>
</Card> </Card>
)) ))
} }
</div> </div>
</Layout> </Layout>
<style lang="scss"> <style lang="scss">
.projects-grid { .projects-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem; gap: 1rem;
} }
.project-content { .project-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 8rem; height: 8rem;
} }
</style> </style>
+6 -6
View File
@@ -1,15 +1,15 @@
import { getCollection } from "astro:content"; import { getCollection } from 'astro:content';
import rss from "@astrojs/rss"; import rss from '@astrojs/rss';
import MarkdownIt from "markdown-it"; import MarkdownIt from 'markdown-it';
const parser = new MarkdownIt({ html: true }); const parser = new MarkdownIt({ html: true });
export async function GET(context) { export async function GET(context) {
const blog = await getCollection("blog"); const blog = await getCollection('blog');
return rss({ return rss({
title: "ghall.space", title: 'ghall.space',
description: description:
"My personal blog about life, gaming, tech, and whatever else I feel like writing about.", 'My personal blog about life, gaming, tech, and whatever else I feel like writing about.',
site: context.site, site: context.site,
trailingSlash: false, trailingSlash: false,
items: blog items: blog
+47 -47
View File
@@ -1,64 +1,64 @@
--- ---
import Layout from "@layouts/Layout.astro"; import Layout from '@layouts/Layout.astro';
import SearchField from "astro-pagefind/components/Search"; import SearchField from 'astro-pagefind/components/Search';
--- ---
<Layout title="Search"> <Layout title="Search">
<div class="search-container"> <div class="search-container">
<h2>Looking for something?</h2> <h2>Looking for something?</h2>
<SearchField <SearchField
id="search" id="search"
className="pagefind-ui" className="pagefind-ui"
uiOptions={{ showImages: false }} uiOptions={{ showImages: false }}
/> />
</div> </div>
</Layout> </Layout>
<style is:global> <style is:global>
.search-container { .search-container {
min-height: 60vh; min-height: 60vh;
} }
.pagefind-ui { .pagefind-ui {
font-family: var(--font) !important; font-family: var(--font) !important;
color: var(--text) !important; color: var(--text) !important;
} }
.pagefind-ui__form::before { .pagefind-ui__form::before {
background-color: var(--text) !important; background-color: var(--text) !important;
} }
.pagefind-ui__search-input, .pagefind-ui__search-input,
.pagefind-ui__button { .pagefind-ui__button {
color: var(--text) !important; color: var(--text) !important;
border: var(--border) !important; border: var(--border) !important;
background-color: var(--background) !important; background-color: var(--background) !important;
} }
.pagefind-ui__button:hover { .pagefind-ui__button:hover {
opacity: 0.75; opacity: 0.75;
} }
.pagefind-ui__search-clear { .pagefind-ui__search-clear {
color: var(--red) !important; color: var(--red) !important;
background-color: var(--background) !important; background-color: var(--background) !important;
} }
.pagefind-ui__result-excerpt > mark { .pagefind-ui__result-excerpt > mark {
background-color: var(--highlight); background-color: var(--highlight);
} }
.pagefind-ui__result-link { .pagefind-ui__result-link {
color: var(--blue) !important; color: var(--blue) !important;
text-decoration: none !important; text-decoration: none !important;
} }
.pagefind-ui__result-link:hover { .pagefind-ui__result-link:hover {
opacity: 0.75; opacity: 0.75;
} }
.pagefind-ui__result { .pagefind-ui__result {
border: none !important; border: none !important;
border-bottom: var(--border) !important; border-bottom: var(--border) !important;
} }
</style> </style>
+6 -6
View File
@@ -1,14 +1,14 @@
@font-face { @font-face {
font-family: "Noto Sans"; font-family: 'Noto Sans';
src: url("./fonts/NotoSans-VariableFont_wdth,wght.ttf") format("truetype"); src: url('./fonts/NotoSans-VariableFont_wdth,wght.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "Noto Serif"; font-family: 'Noto Serif';
src: url("./fonts/NotoSerif-VariableFont_wdth,wght.ttf") format("truetype"); src: url('./fonts/NotoSerif-VariableFont_wdth,wght.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "JetBrainsMono"; font-family: 'JetBrainsMono';
src: url("./fonts/JetBrainsMono-VariableFont_wght.ttf") format("truetype"); src: url('./fonts/JetBrainsMono-VariableFont_wght.ttf') format('truetype');
} }
+75 -75
View File
@@ -1,139 +1,139 @@
@use "./variables.scss" as *; @use './variables.scss' as *;
@use "fonts.scss"; @use 'fonts.scss';
:root { :root {
--blue: #1e66f5; --blue: #1e66f5;
--sky: #04a5e5; --sky: #04a5e5;
--red: #d20f39; --red: #d20f39;
--orange: #fe640b; --orange: #fe640b;
--text: #40360e; --text: #40360e;
--highlight: #ffeebd; --highlight: #ffeebd;
--background: #fffdf5; --background: #fffdf5;
--border: #{$border-style} #8f7a20; --border: #{$border-style} #8f7a20;
--font: "Noto Sans", sans-serif; --font: 'Noto Sans', sans-serif;
} }
.theme-dark { .theme-dark {
--blue: #5da7fb; --blue: #5da7fb;
--sky: #00b0f5; --sky: #00b0f5;
--red: #f38ba8; --red: #f38ba8;
--orange: #fab387; --orange: #fab387;
--text: #c4caed; --text: #c4caed;
--background: #2a2c32; --background: #2a2c32;
--border: #{$border-style} #505160; --border: #{$border-style} #505160;
} }
.serif-text { .serif-text {
--font: "Noto Serif", serif; --font: 'Noto Serif', serif;
} }
body { body {
color: var(--text); color: var(--text);
background-color: var(--background); background-color: var(--background);
font-family: var(--font); font-family: var(--font);
font-size: 1.1rem; font-size: 1.1rem;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
main { main {
max-width: 800px; max-width: 800px;
margin: auto; margin: auto;
padding: 0 1rem; padding: 0 1rem;
margin-top: 100px; margin-top: 100px;
} }
a { a {
color: var(--blue); color: var(--blue);
text-decoration: none; text-decoration: none;
&:hover { &:hover {
opacity: 0.75; opacity: 0.75;
} }
} }
main p { main p {
line-height: 1.5; line-height: 1.5;
} }
/* Article image style */ /* Article image style */
article img { article img {
border-radius: $radius; border-radius: $radius;
display: block; display: block;
margin: auto; margin: auto;
max-width: $max-page-width; max-width: $max-page-width;
height: auto; height: auto;
} }
/* Typography */ /* Typography */
.footnotes { .footnotes {
font-size: 1rem; font-size: 1rem;
} }
.footnotes p { .footnotes p {
display: inline; display: inline;
} }
blockquote { blockquote {
border-left: 4px solid; border-left: 4px solid;
border-color: var(--text); border-color: var(--text);
margin-left: 1rem; margin-left: 1rem;
padding-left: 2rem; padding-left: 2rem;
font-style: italic; font-style: italic;
} }
code { code {
color: var(--orange); color: var(--orange);
padding: 1px 4px; padding: 1px 4px;
margin: 0 2px; margin: 0 2px;
user-select: all; user-select: all;
} }
kbd { kbd {
border: var(--border); border: var(--border);
border-radius: $radius; border-radius: $radius;
padding: 1px 4px; padding: 1px 4px;
white-space: nowrap; white-space: nowrap;
} }
code, code,
kbd { kbd {
font-size: 0.85rem; font-size: 0.85rem;
} }
/* Icon button */ /* Icon button */
.icon-button { .icon-button {
background: none; background: none;
padding: 2px 2px -2px; padding: 2px 2px -2px;
border: none; border: none;
line-height: 1; line-height: 1;
aspect-ratio: square; aspect-ratio: square;
height: 24px; height: 24px;
width: 24px; width: 24px;
&:active { &:active {
opacity: 0.3; opacity: 0.3;
} }
& svg { & svg {
color: var(--text); color: var(--text);
} }
} }
/* shiki */ /* shiki */
.astro-code, .astro-code,
.astro-code span { .astro-code span {
background-color: var(--background) !important; background-color: var(--background) !important;
/* Optional, if you also want font styles */ /* Optional, if you also want font styles */
font-style: var(--shiki-light-font-style); font-style: var(--shiki-light-font-style);
font-weight: var(--shiki-light-font-weight); font-weight: var(--shiki-light-font-weight);
text-decoration: var(--shiki-light-text-decoration); text-decoration: var(--shiki-light-text-decoration);
} }
.theme-dark .astro-code, .theme-dark .astro-code,
.theme-dark .astro-code span { .theme-dark .astro-code span {
color: var(--shiki-dark) !important; color: var(--shiki-dark) !important;
} }