🏗 Restructure project, change font to Maple Mono

This commit is contained in:
2025-08-29 12:37:17 -04:00
parent 0733e44d8b
commit b8445f4a1b
63 changed files with 1522 additions and 842 deletions
-32
View File
@@ -1,32 +0,0 @@
{
"$ref": "#/definitions/blog",
"definitions": {
"blog": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"pubDate": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"pubDate",
"tags"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
+10 -1
View File
@@ -1 +1,10 @@
export default new Map();
import __ASTRO_IMAGE_IMPORT_3KcDr from "src/assets/blog/my-ai-portrait.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fcreating-a-dating-profile-with-ai.md";
import __ASTRO_IMAGE_IMPORT_1G57ng from "src/assets/blog/ileopard/mac-os-10-1.png?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fileopard-a-retrospective.md";
import __ASTRO_IMAGE_IMPORT_Z1ESWoO from "src/assets/blog/ileopard/itunes-7.gif?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fileopard-a-retrospective.md";
import __ASTRO_IMAGE_IMPORT_rrnp from "src/assets/blog/ileopard/ileopard-2-0-1.png?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fileopard-a-retrospective.md";
import __ASTRO_IMAGE_IMPORT_Zi2DqH from "src/assets/blog/gunpla/box.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2023%2Fmy-gunpla-adventure.md";
import __ASTRO_IMAGE_IMPORT_1OkzEl from "src/assets/blog/gunpla/all-the-parts.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2023%2Fmy-gunpla-adventure.md";
import __ASTRO_IMAGE_IMPORT_FYQiW from "src/assets/blog/gunpla/final.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2023%2Fmy-gunpla-adventure.md";
export default new Map([["src/assets/blog/my-ai-portrait.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fcreating-a-dating-profile-with-ai.md", __ASTRO_IMAGE_IMPORT_3KcDr], ["src/assets/blog/ileopard/mac-os-10-1.png?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fileopard-a-retrospective.md", __ASTRO_IMAGE_IMPORT_1G57ng], ["src/assets/blog/ileopard/itunes-7.gif?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fileopard-a-retrospective.md", __ASTRO_IMAGE_IMPORT_Z1ESWoO], ["src/assets/blog/ileopard/ileopard-2-0-1.png?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2022%2Fileopard-a-retrospective.md", __ASTRO_IMAGE_IMPORT_rrnp], ["src/assets/blog/gunpla/box.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2023%2Fmy-gunpla-adventure.md", __ASTRO_IMAGE_IMPORT_Zi2DqH], ["src/assets/blog/gunpla/all-the-parts.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2023%2Fmy-gunpla-adventure.md", __ASTRO_IMAGE_IMPORT_1OkzEl], ["src/assets/blog/gunpla/final.jpg?astroContentImageFlag=&importer=src%2Fcontent%2Fblog%2F2023%2Fmy-gunpla-adventure.md", __ASTRO_IMAGE_IMPORT_FYQiW]]);
-184
View File
@@ -1,184 +0,0 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
components: import('astro').MDXInstance<{}>['components'];
}>;
}
}
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(entry: {
collection: C;
slug: E;
}): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(entry: {
collection: C;
id: E;
}): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: {
collection: C;
slug: ValidContentEntrySlug<C>;
}[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: {
collection: C;
id: keyof DataEntryMap[C];
}[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? {
collection: C;
slug: ValidContentEntrySlug<C>;
}
: {
collection: C;
id: keyof DataEntryMap[C];
}
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
"blog": Record<string, {
id: string;
render(): Render[".md"];
slug: string;
body: string;
collection: "blog";
data: InferEntrySchema<"blog">;
rendered?: RenderedContent;
filePath?: string;
}>;
"vinyl": Record<string, {
id: string;
body?: string;
collection: "vinyl";
data: any;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
export type ContentConfig = typeof import("../src/content/config.js");
}
File diff suppressed because one or more lines are too long
-10
View File
@@ -1,10 +0,0 @@
// Automatically generated by astro-icon
// 001a1ccebb4bfa874cce071296b7167baf197ac1b43baf1f725af5a705835151
declare module 'virtual:astro-icon' {
export type Icon =
| "calendar"
| "clock"
| "pen"
| "person";
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1753720357171
"lastUpdateCheck": 1756244081510
}
}
-1
View File
@@ -1,2 +1 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />
-3
View File
@@ -10,9 +10,6 @@ export default defineConfig({
site: 'https://ghall.space',
output: 'static',
integrations: [mdx(), alpinejs()],
experimental: {
svg: true,
},
redirects: {
'/posts/[...slug]': '/blog/[...slug]',
},
+1386 -486
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -12,13 +12,13 @@
"lint": "eslint --ext .js --ext .ts --ext .astro"
},
"dependencies": {
"@astrojs/alpinejs": "^0.4.3",
"@astrojs/mdx": "4.0.2",
"@astrojs/rss": "4.0.10",
"@astrojs/alpinejs": "^0.4.8",
"@astrojs/mdx": "^4.3.4",
"@astrojs/rss": "^4.0.12",
"@types/alpinejs": "^3.13.11",
"@types/markdown-it": "^14.1.2",
"alpinejs": "^3.14.8",
"astro": "^5.1.8",
"astro": "^5.13.3",
"astro-pagefind": "^1.7.0",
"date-fns": "^4.1.0",
"markdown-it": "^14.1.0",

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before

Width:  |  Height:  |  Size: 721 KiB

After

Width:  |  Height:  |  Size: 721 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before

Width:  |  Height:  |  Size: 566 KiB

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

Before

Width:  |  Height:  |  Size: 619 B

After

Width:  |  Height:  |  Size: 619 B

Before

Width:  |  Height:  |  Size: 630 B

After

Width:  |  Height:  |  Size: 630 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

+1 -1
View File
@@ -1,7 +1,7 @@
---
import { Image } from 'astro:assets';
import portrait from '../img/portrait.jpg';
import portrait from '../assets/portrait.jpg';
interface Props {
size: number;
+2 -2
View File
@@ -1,7 +1,7 @@
---
import { format, add } from 'date-fns';
import CalendarIcon from '../img/svg/calendar.svg';
import CalendarIcon from '../assets/svg/calendar.svg';
interface Props {
title: string;
@@ -17,7 +17,7 @@ const { title, date, slug } = Astro.props;
{slug ? <a href={`/blog/${slug}`}>{title}</a> : title}
</h2>
<div>
<CalendarIcon class="calendar-icon" size={24} />
<CalendarIcon class="calendar-icon" width={24} height={24} />
{format(add(new Date(date), { hours: 6 }), 'MMM do, y')}
</div>
</div>
+7 -2
View File
@@ -1,5 +1,5 @@
---
import RssIcon from '../img/svg/rss.svg';
import RssIcon from '../assets/svg/rss.svg';
const year = new Date().getFullYear();
---
@@ -13,12 +13,17 @@ const year = new Date().getFullYear();
<p>
<a href="/rss.xml"
><RssIcon class="rss-icon" size={20} /> Subscribe with RSS</a
><RssIcon class="rss-icon" width={20} height={20} /> Subscribe with RSS</a
>
</p>
</footer>
<style>
footer {
margin: 4rem 0;
text-align: center;
}
.rss-icon {
transform: translateY(0.18rem);
}
+34 -12
View File
@@ -20,15 +20,10 @@ const pathComponents = pathname.split('/').slice(1);
---
<header>
<div>
{
pathComponents[0] !== '' ? (
<a href="/" transition:name="my-avatar">
<Avatar size={60} />
</a>
) : null
}
</div>
<a href="/">
<Avatar size={60} />
</a>
<nav>
{
navLinks.map((link) => (
@@ -36,9 +31,7 @@ const pathComponents = pathname.split('/').slice(1);
<a href={`/${link.path}`}>
<span>{link.label}</span>
</a>
{pathComponents[0] === link.path ? (
<div class="underline" transition:name="menu-selection-indicator" />
) : null}
{pathComponents[0] === link.path ? <div class="underline" /> : null}
</li>
))
}
@@ -46,6 +39,35 @@ const pathComponents = pathname.split('/').slice(1);
</header>
<style>
header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
height: 120px;
}
header > h1 > a {
color: var(--text);
}
header > nav {
display: flex;
flex-direction: row;
list-style: none;
gap: 20px;
font-size: 1.15rem;
}
header > nav > li > a:hover {
text-decoration: none;
}
header > nav > li > .selected {
font-weight: bold;
}
.underline {
height: 2px;
width: 100%;
+1 -1
View File
@@ -19,7 +19,7 @@ const { post } = Astro.props;
const { data, slug } = post;
---
<article transition:name={slug}>
<article>
<BlogHeader title={post.data.title} date={data.pubDate} slug={slug} />
<Tags tags={data.tags} />
</article>
+6 -3
View File
@@ -9,9 +9,12 @@ const { tags } = Astro.props;
<span>
{
tags.sort().map((tag: string, index: number) => (
<a class="tag" href={`/blog/archive/${tag}`}>
{`${tag}${index < tags.length - 1 ? ', ' : ''}`}
</a>
<>
<a class="tag" href={`/blog/archive/${tag}`}>
{tag}
</a>
{index < tags.length - 1 ? ' | ' : ''}
</>
))
}
</span>
@@ -12,7 +12,7 @@ I decided to use [OpenAI](https://openai.com/) for the written portion, and [Eve
I decided against using AI generated images because, even though Im letting an AI create a dating profile for me, I want it to be somewhat genuine. Also Im not looking to catfish anybody with this image I generated just to see how good AI could make me look:
<img src="/media/my-ai-portrait.jpg" width="260" alt="an AI-generated portrait of me" />
![An AI-generated portrait of me](src/assets/blog/my-ai-portrait.jpg)
Originally this was going to be one post Id write over the course of a week, but as I was writing I decided I should split it into 2 parts: Part 1, this part, about setting up the dating profile, and part 2, coming at the end of the experiment, about the experience and how it went.
@@ -10,13 +10,13 @@ But I did find a rather salty post I wrote about a Mac theme I created back when
If you were around the Mac customization scenespecifically on the now-defunct MacThemes.netaround 2007, you probably remember a little project called iLeopard. Admittedly, the chance that youre in that extremely specific niché is incredibly small. The best data I could find is this [Ars Technica article](https://arstechnica.com/gadgets/2007/03/7296/) from March 2007 saying the Mac hit about 6% marketshare. And only a tiny subset of those users even had the thought that customizing the look of Mac OS X was something they wanted to do.
<img src="/media/ileopard/mac-os-10-1.png" width="90%" alt="a screenshot of Mac OS X 10.1 with various apps open" />
![a screenshot of Mac OS X 10.1 with various apps open](src/assets/blog/ileopard/mac-os-10-1.png)
*Mac OS X 10.1 and the Aqua Interface, from* [_GUIdebook_](https://guidebookgallery.org/screenshots/macosx101)
I was one of the few that had that had that idea pop into my head. I was super into finding and downloading cool themes, including one I distinctly remember that looked like Windows Vista for some reason. It was 2007, the Aqua Interface (the playful, plastic-looking interface style Apple used for about a decade) was already feeling a little passé to me. I wanted something new, and weirdly enough that new thing came from Apple, in the form of iTunes 7.
<img src="/media/ileopard/itunes-7.gif" width="80%" alt="a screenshot iTunes 7 with the iTunes Music Store open" />
![a screenshot iTunes 7 with the iTunes Music Store open](src/assets/blog/ileopard/itunes-7.gif)
_iTunes 7 screenshot, from [AppleInsider](https://appleinsider.com/articles/06/09/12/apple_introduces_itunes_7_previews_itv_device)_
@@ -40,7 +40,7 @@ Being that I was a bored teenager in high school with way too much time on my ha
After dozens of restarts, and a couple corrupted OS installs, I had figured out a good chunk of the building blocks making up Leopards UI. Even still, after weeks of this, there was still so much to be done. Bear in mind, there was no documentation on any of this stuff. I was about to create, as far as I know, the first theme for Mac OS X Leopard.
<img src="/media/ileopard/ileopard-2-0-1.png" width="75%" alt="a screenshot of the Mac OS X Appearance preference pane showing off the modifications made by iLeopard" />
![a screenshot of the Mac OS X Appearance preference pane showing off the modifications made by iLeopard](src/assets/blog/ileopard/ileopard-2-0-1.png)
_iLeopard 2.0.1 screenshot, from [AmazingHenry on MacRumors](https://forums.macrumors.com/threads/ileopard-theme.2045553/), released by fellow MacThemes.net user gcamp' after I essentially handed off the project_
+3 -3
View File
@@ -12,17 +12,17 @@ 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:
<img src="/media/gunpla/box.jpg" width="90%" alt="a box sitting on a table with an image of a mech and text that reads 'M9 Gernsback ver IV (Agressor Squadron)'" />
![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.
<img src="/media/gunpla/all-the-parts.jpg" width="90%" alt="neatly piled plastic sprues sorted by color, an instruction booklet, and a model of R2-D2 lurking in the corner" />
![neatly piled plastic sprues sorted by color, an instruction booklet, and a model of R2-D2 lurking in the corner](src/assets/blog/gunpla/all-the-parts.jpg)
Separating the individual pieces from each sprue was pretty painless. They came off very cleanly and I barely had to use my handy hobby knife to clean up bits of excess plastic, and when I did it cut through like butter. Not a drop of glue was needed, everything snapped together perfectly. I struggled a bit with the smaller parts (of which there were plenty), and there were a couple seemingly microscopic stickers I had to apply, so my iFixIt tweezers came in handy quite a few times.
After a couple hours, I had a pretty sweet looking miniature mecha.
<img src="/media/gunpla/final.jpg" width="90%" alt="the finished model, standing tall, and holding a scary looking gun" />
![the finished model, standing tall, and holding a scary looking gun](src/assets/blog/gunpla/final.jpg)
Putting it together, I was just so amazed by the level of engineering that went into this kit. Putting it all together, I could tell someone put a lot of care and attention into designing this thing, rivaling some of the Lego kits I've built, from the near-seamless fit of all the pieces, to the various types of joints enabling a quite frankly insane level of pose-ability for something this size.
@@ -32,11 +32,11 @@ Once both MUI Base and Tailwind are installed, we next need to run `yarn tailwin
First thing's first, open up the project folder in your code editor of choice and at the root of the project you should see the 'tailwind.config.js' we created in step 2. Open that, and under `module.exports` you should see an empty array named 'content'. Add the following to the array: `'./src/**/*.{js,jsx,ts,tsx}'` Your config file should look like this:
<img src="/media/mui-plus-tailwind/tailwind-config.png" width="80%" alt="module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {}, }, plugins: [],};">
<img src="src/assets/blog/mui-plus-tailwind/tailwind-config.png" width="80%" alt="module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {}, }, plugins: [],};">
Next, we need to import Tailwind into our 'globals.css' file, which is located in 'src/styles/'. We can delete everything in the file as we won't be needing any of it. Then, add the following:
<img src="/media/mui-plus-tailwind/global-css.png" width="80%" alt="@tailwind base; @tailwind components; @tailwind utilities;">
<img src="src/assets/blog/mui-plus-tailwind/global-css.png" width="80%" alt="@tailwind base; @tailwind components; @tailwind utilities;">
And that's it! We're ready to start creating something!
@@ -46,7 +46,7 @@ Ideally, we'd want to create smaller, reusable component files, but that's beyon
First, we should delete everything we don't need so that our 'index.js' file looks like this:
<img src="/media/mui-plus-tailwind/index-js.png" width="80%" alt="export default function Home() { return() }">
<img src="src/assets/blog/mui-plus-tailwind/index-js.png" width="80%" alt="export default function Home() { return() }">
We're going to create a simple button, nothing too fancy. So first we need to import the MUI Unstyled Button component by adding `import { ButtonUnstyled as Button } from '@mui/base'` on the first line of our file.
@@ -54,21 +54,21 @@ _Note: The official MUI docs say to import as `import ButtonUnstyled from '@mui/
Now we can create our button. Inside the return statement, add the following:
<img src="/media/mui-plus-tailwind/unstyled-button-code.png" width="80%" alt="<Button>Click Me!</Button>" />
<img src="src/assets/blog/mui-plus-tailwind/unstyled-button-code.png" width="80%" alt="<Button>Click Me!</Button>" />
Next, go back to your terminal and run `yarn dev` to start up the dev server, and click the 'localhost' link that appears (most likely 'localhost:3000'). We'll be greeted by what looks like just a bit of text (it's a button, I promise), which isn't what we want, but that's because we haven't added any styles yet!
<img src="/media/mui-plus-tailwind/unstyled-button.png" width="40%" alt="A plain old 'button' that reads 'Click Me!'" />
<img src="src/assets/blog/mui-plus-tailwind/unstyled-button.png" width="40%" alt="A plain old 'button' that reads 'Click Me!'" />
All we need to do is add the `className` prop with some nice Tailwind utility classes:
<img src="/media/mui-plus-tailwind/styled-button-code.png" width="80%" alt="<Button className='rounded-lg border-2 border-sky-500 bg-sky-600 py-2 px-10 font-medium text-slate-300 shadow hover:bg-sky-700 active:translate-y-0.5'>
<img src="src/assets/blog/mui-plus-tailwind/styled-button-code.png" width="80%" alt="<Button className='rounded-lg border-2 border-sky-500 bg-sky-600 py-2 px-10 font-medium text-slate-300 shadow hover:bg-sky-700 active:translate-y-0.5'>
Click Me!
</Button>" />
Now, when we go back to the browser, we should see the following:
<img src="/media/mui-plus-tailwind/styled-button.png" width="40%" alt="A styled blue button that reads 'Click Me!'" />
<img src="src/assets/blog/mui-plus-tailwind/styled-button.png" width="40%" alt="A styled blue button that reads 'Click Me!'" />
Nice! That looks a lot more like a button.
+13 -63
View File
@@ -1,20 +1,21 @@
@font-face {
font-family: Manrope;
src: url(./fonts/Manrope-Regular.ttf) format('truetype');
font-family: Maple Mono;
src: url(./assets/fonts/MapleMono-Regular.ttf) format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: Manrope;
src: url(./fonts/Manrope-Bold.ttf) format('truetype');
font-family: Maple Mono;
src: url(./assets/fonts/MapleMono-Bold.ttf) format('truetype');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: JetBrainsMono;
src: url(./fonts/JetBrainsMono-VariableFont_wght.ttf) format('truetype');
src: url(./assets/fonts/JetBrainsMono-VariableFont_wght.ttf)
format('truetype');
font-style: normal;
font-display: block;
}
@@ -25,7 +26,7 @@
--red: #d20f39;
--orange: #fe640b;
--text: #4c4f69;
--background: white;
--background: #fff8fa;
--radius: 5px;
--shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
--border: 1px solid #8c8fa1;
@@ -46,7 +47,7 @@
html {
color: var(--text);
background-color: var(--background);
font-family: 'Manrope', sans-serif;
font-family: 'Maple Mono', monospace;
font-size: 1.05rem;
}
@@ -65,38 +66,9 @@ a:hover {
opacity: 0.75;
}
header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
height: 120px;
}
header > h1 > a {
color: var(--text);
}
header > nav {
display: flex;
flex-direction: row;
list-style: none;
gap: 20px;
font-size: 1.15rem;
}
header > nav > li > a:hover {
text-decoration: none;
}
header > nav > li > .selected {
font-weight: bold;
}
footer {
margin: 4rem 0;
text-align: center;
main p {
text-align: justify;
line-height: 1.4;
}
article img {
@@ -104,6 +76,8 @@ article img {
box-shadow: var(--shadow);
display: block;
margin: auto;
max-width: 100%;
height: auto;
}
.no-shadow {
@@ -124,12 +98,6 @@ article img {
margin: 0;
}
.more-posts {
text-align: center;
padding: 1rem;
font-size: 1.2rem;
}
blockquote {
border-left: 4px solid;
border-color: var(--text);
@@ -159,24 +127,6 @@ kbd {
white-space: nowrap;
}
/* Not By AI badge */
.not-by-ai {
display: block;
width: 134px;
height: 45px;
background-image: url(svg/Written-By-Human-Not-By-AI-Badge-white.svg);
background-repeat: no-repeat;
background-position: center;
margin: 1rem 0;
}
@media (prefers-color-scheme: dark) {
.not-by-ai {
background-image: url(svg/Written-By-Human-Not-By-AI-Badge-black.svg);
}
}
@media (max-width: 590px) {
header {
flex-direction: column;
+3 -5
View File
@@ -1,6 +1,5 @@
---
import '@styles/global.css';
import { ClientRouter } from 'astro:transitions';
import '../global.css';
import Header from '@components/Header.astro';
import Footer from '@components/Footer.astro';
@@ -23,16 +22,15 @@ const title = Astro.props.title || Astro.props.frontmatter?.title || 'Unknown';
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="robots" content="noindex" /><meta
name="description"
content="My personal blog about life, gaming, tech, and whatever else I feel like writing about."
content="My little space on the World Wide Web."
/><link
rel="alternate"
type="application/rss+xml"
title="ghall.space - RSS"
href={`${Astro.site}rss.xml`}
/><title>{`ghall.space - ${title}`}</title>
<ClientRouter />
</head>
<body class="layout-simple" transition:animate="fade">
<body class="layout-simple">
<Header />
<main>
<slot />
+1 -1
View File
@@ -2,7 +2,7 @@
import Layout from '@layouts/Layout.astro';
import { Image } from 'astro:assets';
import DataGif from '../img/it-does-not-exist.gif';
import DataGif from '../assets/it-does-not-exist.gif';
---
<Layout title="Not Found">
+20 -2
View File
@@ -15,13 +15,13 @@ export async function getStaticPaths() {
const { post } = Astro.props;
const { data, slug } = post;
const { data } = post;
const { Content } = await post.render();
---
<Layout title={data.title}>
<article transition:name={slug}>
<article>
<BlogHeader title={data.title} date={data.pubDate} />
<Content />
<a href="https://notbyai.fyi/">
@@ -30,3 +30,21 @@ const { Content } = await post.render();
<Tags tags={data.tags} />
</article>
</Layout>
<style>
.not-by-ai {
display: block;
width: 134px;
height: 45px;
background-image: url(../../assets/svg/Written-By-Human-Not-By-AI-Badge-white.svg);
background-repeat: no-repeat;
background-position: center;
margin: 1rem 0;
}
@media (prefers-color-scheme: dark) {
.not-by-ai {
background-image: url(../../assets/svg/Written-By-Human-Not-By-AI-Badge-black.svg);
}
}
</style>
+1 -1
View File
@@ -50,7 +50,7 @@ posts.sort(
<ul>
{
posts.map(({ slug, data }) => (
<li transition:name={slug}>
<li>
<a href={`/posts/${slug}`}>{data.title}</a> -
<span>
{format(add(new Date(data.pubDate), { hours: 6 }), 'MMM do, y')}
+8
View File
@@ -26,3 +26,11 @@ const posts = await getCollection('blog');
)
}
</Layout>
<style>
.more-posts {
text-align: center;
padding: 1rem;
font-size: 1.2rem;
}
</style>
+6 -7
View File
@@ -6,8 +6,8 @@ import { getCollection } from 'astro:content';
import Layout from '@layouts/Layout.astro';
import Avatar from '@components/Avatar.astro';
import MastodonIcon from '../img/svg/mastodon.svg';
import BlueskyIcon from '../img/svg/bluesky.svg';
import MastodonIcon from '../assets/svg/mastodon.svg';
import BlueskyIcon from '../assets/svg/bluesky.svg';
const iconSize = 16;
const posts = await getCollection('blog');
@@ -19,9 +19,6 @@ const latestPost = posts.sort(
---
<Layout title="Welcome">
<div transition:name="my-avatar">
<Avatar size={200} />
</div>
<p>
My name is <strong>Graham</strong> (he/him), a full-stack web developer, and
tech enthusiast.
@@ -50,10 +47,12 @@ const latestPost = posts.sort(
If you want to get in touch, I'm on <a
rel="me"
href="https://mastodon.social/@ghalldev"
target="_blank"><MastodonIcon size={iconSize} />Mastodon</a
target="_blank"
><MastodonIcon width={iconSize} height={iconSize} />Mastodon</a
> and <a
href="https://bsky.app/profile/ghalldev.bsky.social"
target="_blank"><BlueskyIcon size={iconSize} />Bluesky</a
target="_blank"
><BlueskyIcon width={iconSize} height={iconSize} />Bluesky</a
>.
</p>
<p>
+3 -5
View File
@@ -5,19 +5,17 @@ title: Now
Hey there, this is my [/now page](https://nownownow.com/about)!
_Last updated: July 28, 2025_
_Last updated: August 29, 2025_
## 🎧 Listening
- [Tunnel Vision - Beach Bunny](https://album.link/i/1794412465)
- [Flood - They Might Be Giants](https://album.link/us/i/298111036)
- [Video Game Legends - Celestial Aeon Project, Deck Hard & Stellar Conflux](https://album.link/i/1814286119)
- [Ego Death at a Bachelorette Party - Hayley Williams](https://album.link/i/1833006180)
## 🎮 Playing
- [Batman Arkham Asylum](https://www.igdb.com/games/batman-arkham-asylum)
- [Assassin's Creed Shadows](https://www.igdb.com/games/assassins-creed-shadows)
- [Baldur's Gate 3](https://www.igdb.com/games/baldurs-gate-3)
- [Rune Factory: Guardians of Azuma](https://www.igdb.com/games/rune-factory-guardians-of-azuma--1)
## 📺 Watching
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.