the chunkiest update

This commit is contained in:
haetae 2025-04-17 03:09:27 -04:00
parent 3c6fd519ff
commit dc3e9aa226
46 changed files with 238 additions and 116 deletions

View File

@ -1,6 +1,6 @@
// @ts-check
import { defineConfig } from 'astro/config';
import { modifiedTime } from './src/utils/last-modified.mjs';
import { modifiedTime } from './src/utils/lastModified.mjs';
import alpinejs from '@astrojs/alpinejs';
// https://astro.build/config
@ -8,6 +8,7 @@ export default defineConfig({
site: "https://haetae.32-b.it",
markdown: {
remarkPlugins: [modifiedTime],
smartypants: false,
},
integrations: [alpinejs()],
});

View File

@ -12,6 +12,7 @@
"astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.13",
"markdown-it": "^14.1.0",
"node-html-parser": "^7.0.1",
"sanitize-html": "^2.15.0",
},
"devDependencies": {
@ -252,6 +253,8 @@
"base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
@ -292,6 +295,10 @@
"crossws": ["crossws@0.3.4", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw=="],
"css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="],
"css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
@ -382,6 +389,8 @@
"hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
@ -522,10 +531,14 @@
"node-fetch-native": ["node-fetch-native@1.6.6", "", {}, "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ=="],
"node-html-parser": ["node-html-parser@7.0.1", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA=="],
"node-mock-http": ["node-mock-http@1.0.0", "", {}, "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"ofetch": ["ofetch@1.4.1", "", { "dependencies": { "destr": "^2.0.3", "node-fetch-native": "^1.6.4", "ufo": "^1.5.4" } }, "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw=="],
"ohash": ["ohash@1.1.4", "", {}, "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="],

View File

@ -17,6 +17,7 @@
"astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.13",
"markdown-it": "^14.1.0",
"node-html-parser": "^7.0.1",
"sanitize-html": "^2.15.0"
},
"devDependencies": {

View File

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 411 B

View File

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 689 B

BIN
src/assets/gallery/cian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 169 B

View File

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 731 B

View File

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 254 B

After

Width:  |  Height:  |  Size: 254 B

View File

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

View File

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

View File

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

View File

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

View File

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

View File

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 302 B

View File

@ -5,14 +5,14 @@ interface Props {
imagePath: string;
alt: string;
caption?: string;
className?: string[] | string;
class?: string | string[];
}
const { imagePath, alt, caption, className }: Props = Astro.props;
const { imagePath, alt, caption, class: className }: Props = Astro.props;
const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/**/*.{jpeg,jpg,png,webp,gif}");
if (!images[imagePath]) throw new Error(`"${imagePath}" does not exist in glob: "src/assets/**/*.{jpeg,jpg,png,webp,gif}"`);
---
<figure class:list={className}>
<figure class:list={[className]}>
<Image src={images[imagePath]()} {alt} />
{caption && <figcaption>{caption}</figcaption>}
</figure>

View File

@ -0,0 +1,8 @@
---
title: birthday
pubDate: 2024-05-01
---
it's my birthday today! i'm turning older two times each year because i technically have two ages... it's becoming harder to come to terms with this the older i get. orz
oh well! at least cake is eternal.

View File

@ -0,0 +1,10 @@
---
title: first post!
pubDate: 2024-04-29
---
i've remade my personal website again, this time with the sole purpose of creating profiles of my rp characters and maybe yap about my interests more.
i think my time on the internet has made me intensely private and introverted, so it's a little hard for me to really put myself out there both irl and digitally. there's just a lot that feels scary about the world, but i'd never be able to make any connections with people if i let my fears hold me back.
this is my attempt to share a part of myself, however small and trivial it may be.

View File

@ -1,19 +0,0 @@
---
title: hey girl hey
pubDate: 2024-02-03
currently:
mood: happy
reading: hella
watching: stuff
playing: balatro
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pretium augue elit, eget interdum massa lobortis ut. Praesent facilisis ornare aliquam. Donec sit amet volutpat ipsum, id ultricies urna. Donec vestibulum sagittis felis, tempor fermentum urna posuere at. Vestibulum cursus mauris eget bibendum blandit. Aenean at augue porttitor, bibendum massa ut, laoreet lacus. Phasellus fermentum tincidunt lectus vel volutpat. Proin sagittis vel sem sit amet consequat. Vestibulum ac laoreet quam. Mauris eu purus sit amet odio maximus dictum. Mauris quam tellus, tempus eu faucibus in, mollis quis velit. Phasellus nisl mauris, congue vel magna a, rutrum aliquet ante. Proin in ante pharetra, vestibulum nisi vel, fringilla tortor. Etiam mattis, mauris et mattis sagittis, orci eros ornare risus, vitae consequat ligula nulla eget quam.
Fusce malesuada sed risus eget elementum. Quisque porttitor finibus libero, et semper magna pretium ac. Donec gravida erat iaculis ante cursus, in laoreet ligula dapibus. Vestibulum gravida lorem eleifend mollis hendrerit. Nulla et velit dapibus, aliquet lectus at, mattis nulla. Pellentesque lacus eros, sagittis quis dignissim a, malesuada ut eros. Aliquam fringilla a leo sed commodo. Sed eu ligula malesuada, ultrices orci eget, suscipit ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras interdum libero sed leo eleifend tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
Vivamus scelerisque ac quam sed interdum. Mauris pulvinar aliquet est in luctus. Donec convallis dui aliquam urna pellentesque, eget maximus tellus scelerisque. Integer posuere commodo justo in finibus. Vivamus at molestie nisl, sit amet ultrices ex. Ut elementum dignissim dui a lacinia. Etiam nec purus ac felis congue tristique ut eget leo. Aenean id augue molestie, auctor quam ac, aliquam magna. Maecenas condimentum sem mauris, in sodales nunc suscipit non. Sed iaculis ut magna vel sollicitudin. Etiam commodo lacinia lorem, quis tincidunt sem laoreet et. Sed interdum elit ac erat blandit cursus. Pellentesque imperdiet placerat lacus id sagittis. In vitae efficitur ante, sit amet feugiat nunc. Praesent erat mi, hendrerit molestie maximus sed, tempus eu massa.
Fusce convallis ultricies orci, vulputate laoreet magna. Proin in aliquet diam. Etiam placerat ante eget lacus ultrices fermentum. Phasellus interdum facilisis ex mattis blandit. Quisque vitae convallis velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sodales tincidunt lorem. Ut bibendum posuere elementum. Phasellus arcu lacus, porta sit amet convallis a, finibus et nisi. Sed id justo dapibus, faucibus elit eu, tincidunt ante.
Donec vehicula ultrices egestas. Maecenas non magna tortor. Curabitur metus sapien, ultricies porta urna vitae, pulvinar blandit tellus. In aliquet risus sed libero vulputate, in volutpat ligula malesuada. Phasellus viverra pretium turpis, quis congue dolor fringilla vitae. Morbi orci lacus, mollis non dolor nec, euismod aliquet risus. Ut massa nibh, maximus sit amet turpis et, dapibus ullamcorper turpis. Aenean ut tellus ac nisi mollis tempus. Duis at libero quis felis ornare viverra ac sit amet justo. Sed gravida magna ut nibh tincidunt, consectetur lobortis est consequat. Phasellus ex velit, tincidunt sit amet consectetur id, vulputate eget tellus. Donec ligula elit, vestibulum non tincidunt eu, ullamcorper eget massa. Nulla venenatis maximus metus, sed pulvinar dolor ullamcorper at. Vestibulum volutpat tortor ante, a ornare justo porttitor tempus. Aliquam sit amet sem porta, hendrerit dolor sed, tristique neque.

View File

@ -1,9 +0,0 @@
---
title: hmhmhm!
pubDate: 2024-02-04
currently:
mood: sad
reading: sad fanfic
---
hey there this is a test

View File

@ -0,0 +1,16 @@
---
title: minor updates
pubDate: 2024-12-12
---
it's been a while since i last touched this website and 11ty went thru some updates and i thought i could also do some renovations on my end too. i had a lot of trouble trying to redo the guestbook to make it cleaner in terms of scripting. it gets real mad if i try to introduce a new script file and i can't make it play nice with 11ty filters, so i'll just have to copy-paste some stuff to make it happy.
though what reminded me of this website and why i wanted to update again was because of this hilarious mssg i got (shout out to morpho)
![screenshot of morpho's comment: "I DIDNT THINK IT WOULC SEND THREE TIMES OMG IM SORRY"]($/images/the-of-all-time.png)
i wanted to save this before i cleaned up my guestbook entries bcs i thought it was the funniest thing i've seen all month. ilu so much, i hope ur having a wonderful day LMAO
i've been back at the grind with webdev and coding stuff, making little games where i can. i also tried to make a pomodoro game, but fell short right as i got all the mechanics working bcs i couldn't make a functioning save/load system in godot without it crashing on me. it definitely taught me not to overlook an important feature from the beginning.
right now i'm working on a mini turn-based rpg, and hopefully it'll be less groan-inducing than the last project.

View File

@ -0,0 +1,10 @@
---
title: nuzlocke
pubDate: 2024-05-24
---
i'm thinking about starting a nuzlocke run in the gs chronicle hack. i've always had a fond spot for johto ever since soulsilver came out - i remember the pokewalker and suicune encounter vividly. i just wish i was in a place to also get all the event pokemon, but i missed out on a lot of them.
in any case, i think gs chronicle would be a good start because one, it's a hack, two, it's johto, and three, i can (probably) make a little story out of it. apparently in the hack the story is slightly altered from soulsilver's and crystal's original, so that'll be fun to play around with.
i'm also toying around with the idea of starting a nuzlocke in pokemon platinum, since i've never played that game before and my second and most memorable pokemon game i've ever had was pokemon pearl. hmm, decisions decisions.

14
src/content/blog/t-log.md Normal file
View File

@ -0,0 +1,14 @@
---
title: t log number ??1
pubDate: 2024-05-19
---
it's been about two - now almost three - years since i started testosterone. this past week, i went to go get my hormone levels checked, but without getting into a lot of details i hashed out some issues with my doctor and now *hopefully* everything should be sorted out. i just hate miscommunication between my doctor, insurance, and pharmacy. the fact that insurance is essentially a middleman makes it unnecessarily 10x more difficult for me to get meds.
i found it hilarious that the nurse who took my blood sample also played stardew valley and final fantasy xiv, and fps games otherwise. she told me she mained ninja in ffxiv at the moment and didn't like modding in stardew valley. which fair! updating mods and getting them all organized without a good mod manager is a goddamn hassle lmao.
tips for any trans dudes thinking about transitioning with hormones: note that injections are (usually) cheaper and more effective. you can also increase dosage, but it has to be slowly with your doctor's supervision and advice. you can't increase dosage while on gels, you just take them as is. this next tidbit is more US-oriented, but i've found that my state's medicaid covers gels for free. check your state and insurance to see if they cover them, because they're expensive otherwise.
please feel free to ~~send me an email at xhaetae@proton.me~~ reach out to me on the [32-bit cafe discourse forum](https://discourse.32bit.cafe/u/haetae) if you have any questions. i'm not a doctor, but i've had some experience navigating transitioning and the systems surrounding the process.
(edit: unfortunately [proton is sketchy](https://news.ycombinator.com/item?id=42837181), so that email's nuked)

View File

@ -4,8 +4,8 @@ import Layout from "./Layout.astro";
import Navbar from "~/Navbar.astro";
import Figure from "~/Figure.astro";
import border from "$/images/border.png";
import frame from "$/images/frame.png";
import border from "$/border.png";
import frame from "$/frame.png";
type Props = MarkdownLayoutProps<{
avatar?: string;
@ -19,7 +19,7 @@ const { frontmatter } = Astro.props;
<main>
{frontmatter.avatar && frontmatter.avatarText && (
<Figure className="avatar" imagePath={frontmatter.avatar} alt={frontmatter.avatarText} />
<Figure class="avatar" imagePath={frontmatter.avatar} alt={frontmatter.avatarText} />
)}
<article>
<slot />

View File

@ -1,15 +1,14 @@
---
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import Layout from "./Layout.astro";
import Navbar from "~/Navbar.astro";
import moods from "@/utils/moods";
import outerBBS from "$/images/guild-bbs.png";
import innerBBS from "$/images/guild-bbs-content.png";
import sideBBS from "$/images/guild-bbs-list.png";
import outerBBS from "$/guild-bbs.png";
import innerBBS from "$/guild-bbs-content.png";
import sideBBS from "$/guild-bbs-list.png";
import formatDate from "@/utils/formatDate";
interface Props {
title: string;
@ -26,11 +25,11 @@ interface Props {
const { title, date, currently } = Astro.props;
const blog = await getCollection("blog");
blog.length = Math.min(blog.length, 5);
blog.length = Math.min(blog.length, 12);
blog.sort((a, b) => a.data.pubDate!.valueOf() - b.data.pubDate!.valueOf());
dayjs.extend(utc);
---
<Layout {title}>
<slot slot="head" name="head" />
<Navbar />
<main>
@ -42,9 +41,12 @@ dayjs.extend(utc);
{blog.map(entry => (
<li>
<span class="item">
<a href={`/blog/${entry.id}`}>{entry.data.title}</a>
<time datetime={dayjs(entry.data.pubDate!).utc(true).toISOString()}>
{dayjs(entry.data.pubDate).utc(true).format("M/D/YY")}
<a href={`/blog/${entry.id}`} class="entry-title" aria-labelledby={entry.id}>
<span style="display: none">{entry.data.title}</span>
</a>
<span id={entry.id} class="link" aria-hidden="true">{entry.data.title}</span>
<time datetime={formatDate(entry.data.pubDate!, true)}>
{formatDate(entry.data.pubDate!, false, "MM/DD/YY")}
</time>
</span>
</li>
@ -54,8 +56,8 @@ dayjs.extend(utc);
<div class="nav-section">
<h1><span class="title">other links</span></h1>
<ul>
<li><span class="item"><a href="/blog">archive</a></span></li>
<li><span class="item"><a href="/blog/rss.xml">rss feed</a></span></li>
<li><span class="item"><a href="/blog" class="link">archive</a></span></li>
<li><span class="item"><a href="/blog/rss.xml" class="link">rss feed</a></span></li>
</ul>
</div>
</div>
@ -67,9 +69,9 @@ dayjs.extend(utc);
<h1>{title}</h1>
<hr />
<div class="info">
<time datetime={dayjs(date).utc(true).toISOString()}>
<time datetime={formatDate(date, true)}>
<span class="title">Date</span>
<span class="desc">{dayjs(date).utc(true).format("MMMM DD, YYYY")}</span>
<span class="desc">{formatDate(date)}</span>
</time>
</div>
</header>
@ -208,13 +210,25 @@ dayjs.extend(utc);
border-bottom: 2px solid var(--bg-4);
.item {
position: relative;
font-family: var(--mplus-10-font);
font-size: 1.254rem;
font-size: 1.25rem; /* this should be 20px */
display: flex;
justify-content: space-between;
align-items: center;
a {
&.entry-title {
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
.link {
flex: 1;
background-color: var(--bg-1);
padding: 6px;
@ -228,7 +242,7 @@ dayjs.extend(utc);
}
&:hover {
a { background-color: var(--bg-7); }
.link { background-color: var(--bg-7); }
time { background-color: var(--border-6); }
}
}
@ -254,6 +268,7 @@ dayjs.extend(utc);
}
article {
display: flex;
letter-spacing: 1px;
border: 2px solid var(--border-0);
border-image: var(--outerBorder) 19 9 38 11 fill / 38px 18px 76px 22px;
@ -261,6 +276,8 @@ dayjs.extend(utc);
padding: 36px 16px 74px 20px;
.inner {
display: flex;
flex-direction: column;
border: 2px solid var(--border-2);
border-image: var(--innerBorder) 3 2 2 fill / 6px 4px 4px;
margin: 14px 4px 8px;
@ -313,7 +330,8 @@ dayjs.extend(utc);
}
.content {
position: relative;
/* position: relative; */
flex: 1;
background-attachment: local;
background-image: linear-gradient(to right, var(--bg-0) 5px, transparent 2px), linear-gradient(var(--bg-4) 2px, transparent 2px);
background-size: 10px 1lh;
@ -326,6 +344,7 @@ dayjs.extend(utc);
margin-trim: block;
p { margin-block: 1lh; }
a, del, s { text-decoration-thickness: 2px; }
@supports not (margin-trim: block) {
:first-child { margin-block-start: 0; }

View File

@ -1,7 +1,6 @@
---
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import Layout from "./Layout.astro";
import formatDate from "@/utils/formatDate";
interface Props {
title: string;
@ -12,20 +11,22 @@ interface Props {
}
const { title, ficTitle, date, notes, lastModified }: Props = Astro.props;
dayjs.extend(utc);
---
<Layout title={`${ficTitle}, ${title}`}>
<Fragment slot="head">
<meta name="description" content={notes} />
</Fragment>
<slot name="breadcrumbs" />
<main>
<header>
<h1>{title}</h1>
<time datetime={dayjs(date).utc(true).toISOString()}>
Published on {dayjs(date).utc(true).format("MMMM DD, YYYY")}
<time datetime={formatDate(date, true)}>
Published on {formatDate(date)}
</time>
{lastModified && (
<time datetime={dayjs(lastModified).utc(true).toISOString()}>
Last edited on {dayjs(lastModified).utc(true).format("MMMM DD, YYYY")}
<time datetime={formatDate(lastModified, true)}>
Last edited on {formatDate(lastModified)}
</time>
)}
{notes && (

View File

@ -10,10 +10,10 @@ const { title = "haetae" }: Props = Astro.props;
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<meta name="pinterest" content="nopin nohover" />
<slot name="header" />
<meta name="robots" content="noai, noimageai" />
<slot name="head" />
<title>{title}</title>
<script is:inline>
localStorage.getItem("theme")

View File

@ -15,7 +15,7 @@ i used a bunch of assets that require attribution but i also wanted to link peop
## images
- [Pikachu portrait](https://sprites.pmdcollab.org/#/0025?form=0) by SPIKE CHUNSOFT, curated by the PMD Collab project
- [This about page's frame images](https://www.spriters-resource.com/ds_dsi/pokemonmysterydungeonexplorersoftimedarkness/sheet/5986/) by redblueyellow
- The blog's frame images and additional assets (such as the mood emoticons) were graciously provided by AymShade on the MapleLegends forums! I can't directly link because I'm scared of a Certain Company™ being evil
- the blog's frame images and additional assets (such as the mood emoticons) were graciously provided by AymShade on the MapleLegends forums! i can't directly link because i'm scared of a Certain Company™ being evil
- [The guestbook's speech bubble images](https://www.spriters-resource.com/game_boy_advance/mlss/sheet/7573/) by MajinPiccolo
## fonts
@ -25,4 +25,4 @@ i used a bunch of assets that require attribution but i also wanted to link peop
- [PixelMplus](https://itouhiro.hatenablog.com/entry/20130602/font) by itouhiro is licensed under [M+ font license](https://web.archive.org/web/20221024231351/http://mplus-fonts.osdn.jp/mplus-bitmap-fonts/#license)
- [Redaction](https://www.redaction.us/) by [Forest Young](https://www.moma.org/interactives/exhibitions/2011/talktome/objects/140027/) and [Jeremy Mickel](https://mckltype.com/) is dual-licensed under [LGPL 2.1](https://www.tldrlegal.com/license/gnu-lesser-general-public-license-v2-1-lgpl-2-1) and [OFL 1.1](https://www.tldrlegal.com/license/open-font-license-ofl-explained)
- [sq](https://github.com/leahneukirchen/sq) by [Leah Neukirchen](https://leahneukirchen.org/) is in public domain
- [Wonder Mail](https://www.dafont.com/wonder-mail.font) by ShinxHijinx is free for personal use (as far as I know at the time of this writing: April 14, 2025)
- [Wonder Mail](https://www.dafont.com/wonder-mail.font) by ShinxHijinx is free for personal use (as far as i know at the time of this writing: April 14, 2025)

View File

@ -2,12 +2,13 @@
import Blog from '@/layouts/Blog.astro';
import type { GetStaticPaths } from 'astro';
import { getCollection, render } from 'astro:content';
import leftNormal from "$/images/left-normal.png";
import leftHover from "$/images/left-hover.png";
import leftActive from "$/images/left-active.png";
import rightNormal from "$/images/right-normal.png";
import rightHover from "$/images/right-hover.png";
import rightActive from "$/images/right-active.png";
import leftNormal from "$/left-normal.png";
import leftHover from "$/left-hover.png";
import leftActive from "$/left-active.png";
import rightNormal from "$/right-normal.png";
import rightHover from "$/right-hover.png";
import rightActive from "$/right-active.png";
export const getStaticPaths = (async () => {
const blog = await getCollection("blog");
@ -27,6 +28,9 @@ const previous = current + 1 === blog.length ? undefined : blog[current + 1];
const next = current === 0 ? undefined : blog[current - 1];
---
<Blog title={entry.data.title!} date={entry.data.pubDate!} currently={entry.data.currently}>
<Fragment slot="head">
<meta name="description" content={entry.body?.substring(0, 150) + "…"}>
</Fragment>
<Content />
{(previous || next) && (

View File

@ -16,7 +16,9 @@ const getMonth = (id: number) => {
}
---
<Layout>
<Fragment slot="header" set:html={`<link rel="alternate" type="application/rss+xml" title="haetae's blog" href=${new URL("/blog/rss.xml", Astro.site)} />`} />
<Fragment slot="head">
<link rel="alternate" type="application/rss+xml" title="haetae's blog" href={new URL("/blog/rss.xml", Astro.site)} />
</Fragment>
<h1>blog</h1>
<ul>
{Object.entries(sorted).map(entry => (

View File

@ -1,22 +1,37 @@
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import rss from "@astrojs/rss";
import rss, { type RSSFeedItem } from "@astrojs/rss";
import MarkdownIt from "markdown-it";
import { parse as htmlParser } from "node-html-parser";
import sanitize from "sanitize-html";
import fixRssImages from "@/utils/fixRssImages";
const parser = new MarkdownIt();
export const GET: APIRoute = async (context) => {
const blog = await getCollection("blog");
const feed: RSSFeedItem[] = [];
for (const entry of blog) {
const content = parser.render(entry.body!);
const html = htmlParser.parse(content);
const images = html.querySelectorAll("img");
await fixRssImages(images, context);
feed.push({
link: `/blog/${entry.id}`,
content: sanitize(html.toString(), {
allowedTags: sanitize.defaults.allowedTags.concat(["img"]),
}),
...entry.data,
});
}
return rss({
title: "haetae's blog",
description: "a blog about a weirdo who likes coding",
site: context.site!,
items: blog.map(entry => ({
link: `/blog/${entry.id}`,
content: sanitize(parser.render(entry.body!), {
allowedTags: sanitize.defaults.allowedTags.concat(["img"]),
}),
...entry.data,
})),
items: feed,
});
}

View File

@ -3,8 +3,6 @@ import Chapter from "@/layouts/Chapter.astro";
import type { GetStaticPaths } from "astro";
import { getCollection, render } from "astro:content";
import { Breadcrumbs } from "astro-breadcrumbs";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
export const getStaticPaths = (async () => {
const chapters = await getCollection("chapters");
@ -21,7 +19,6 @@ const { ficId, chapterId } = Astro.params;
const { chapter } = Astro.props;
const { Content, remarkPluginFrontmatter } = await render(chapter);
dayjs.extend(utc);
const chapters = await getCollection("chapters", ({ id }) => {
return id.split("/")[0] === ficId;
});

View File

@ -2,8 +2,7 @@
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import Layout from "@/layouts/Layout.astro";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import formatDate from "@/utils/formatDate";
export const getStaticPaths = (async () => {
const fics = await getCollection("fics");
@ -19,10 +18,11 @@ const chapters = await getCollection("chapters", ({ id }) => {
});
chapters.length = Math.min(chapters.length, 5);
chapters.sort((a, b) => a.data.sortOrder - b.data.sortOrder);
dayjs.extend(utc);
---
<Layout title={fic.data.title}>
<Fragment slot="header" set:html={`<link rel="alternate" type="application/rss+xml" title="${fic.data.title}" href=${new URL(`/fics/${Astro.params.ficId}/rss.xml`, Astro.site)} />`} />
<Fragment slot="head">
<link rel="alternate" type="application/rss+xml" title={fic.data.title} href={new URL(`/fics/${Astro.params.ficId}/rss.xml`, Astro.site)} />
</Fragment>
<main>
<section>
<h1>{fic.data.title}</h1>
@ -31,9 +31,9 @@ dayjs.extend(utc);
<span class="title">Fandom</span>
<span class="data">{fic.data.series.join(", ")}</span>
</p>
<time datetime={dayjs(fic.data.publishedAt).utc(true).toISOString()}>
<time datetime={formatDate(fic.data.publishedAt, true)}>
<span class="title">Date</span>
<span class="data">{dayjs(fic.data.publishedAt).utc(true).format("MMMM DD, YYYY")}</span>
<span class="data">{formatDate(fic.data.publishedAt)}</span>
</time>
<div>
<span class="title">Summary</span>

View File

@ -1,10 +1,12 @@
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import rss from "@astrojs/rss";
import rss, { type RSSFeedItem } from "@astrojs/rss";
import MarkdownIt from "markdown-it";
import { parse as htmlParser } from "node-html-parser";
import sanitize from "sanitize-html";
const parser = new MarkdownIt();
import fixRssImages from "@/utils/fixRssImages";
const parser = new MarkdownIt();
const fics = await getCollection("fics");
export const GET: APIRoute = async (context) => {
@ -12,21 +14,33 @@ export const GET: APIRoute = async (context) => {
return id.split("/")[0] === context.params.ficId;
});
const fic = fics.find(({ id }) => id === context.params.ficId);
const feed: RSSFeedItem[] = [];
for (const entry of chapters) {
const content = parser.render(entry.body!);
const html = htmlParser.parse(content);
const images = html.querySelectorAll("img");
await fixRssImages(images, context);
feed.push({
link: `/fics/${entry.id}`,
title: entry.data.title,
pubDate: entry.data.publishedAt,
content: sanitize(html.toString(), {
allowedTags: sanitize.defaults.allowedTags.concat(["img"]),
}),
categories: fic?.data.series.concat(fic.data.title),
});
}
return rss({
title: `${fic?.data.title}`,
description: sanitize(parser.render(fic?.data.summary!), {
allowedTags: sanitize.defaults.allowedTags.concat(["br"]),
}),
site: context.site!,
items: chapters.map(chapter => ({
link: `/fics/${chapter.id}`,
title: chapter.data.title,
pubDate: chapter.data.publishedAt,
content: sanitize(parser.render(chapter.body!), {
allowedTags: sanitize.defaults.allowedTags.concat(["img"]),
}),
categories: fic?.data.series.concat(fic.data.title),
})),
items: feed,
});
};

View File

@ -1,9 +1,8 @@
---
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import Layout from "@/layouts/Layout.astro";
import formatDate from "@/utils/formatDate";
import ThemeSwitch from "~/ThemeSwitch.astro";
import speech from "$/images/speech.png";
import speech from "$/speech.png";
type GuestBookEntry = {
ID: number;
@ -18,7 +17,6 @@ type GuestBookEntry = {
Guestbook: object;
};
dayjs.extend(utc);
const response = await fetch("https://guestbooks.meadow.cafe/api/v1/get-guestbook-messages/500");
const data: GuestBookEntry[] = await response.json();
data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valueOf());
@ -69,7 +67,7 @@ data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valu
</h1>
<time
:datetime="formatDate(entry.CreatedAt, true)"
x-text="formatDate(entry.CreatedAt)"
x-text="formatDate(entry.CreatedAt, false, 'MMMM D, YYYY on dddd, hh:mm a')"
>
</time>
</header>
@ -97,8 +95,8 @@ data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valu
: <span>{entry.Name}</span>
}
</h1>
<time datetime={dayjs(entry.CreatedAt).utc(true).toISOString()}>
{dayjs(entry.CreatedAt).utc(true).format("MMMM DD, YYYY")}
<time datetime={formatDate(entry.CreatedAt, true)}>
{formatDate(entry.CreatedAt, false, 'MMMM D, YYYY on dddd, hh:mm a')}
</time>
</header>
@ -108,7 +106,6 @@ data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valu
</noscript>
</section>
<button onclick="document.getElementById('notification').showModal()">open popup</button>
<dialog id="notification">
<form method="dialog">
<menu>
@ -174,6 +171,8 @@ data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valu
border-image: var(--border) 7 / 7px / 7px repeat;
margin: 2rem 0;
h1 { font-size: 2rem; }
&.error {
max-width: max-content;
padding: 1rem;
@ -248,9 +247,7 @@ data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valu
</style>
<script>
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
import formatDate from "@/utils/formatDate";
const form = document.forms[0];
const challenge = form.elements["challengeQuestionAnswer" as any];
@ -294,12 +291,6 @@ data.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valu
this.errors = "Whoops, couldn't load any guestbook entries! :(";
}
},
formatDate(date: string, iso = false) {
if (iso) {
return dayjs(date).utc(true).toISOString();
} else {
return dayjs(date).utc(true).format("MMMM DD, YYYY");
}
},
formatDate: formatDate,
}));
</script>

21
src/utils/fixRssImages.ts Normal file
View File

@ -0,0 +1,21 @@
import type { APIContext } from "astro";
import { getImage } from "astro:assets";
const imagesGlob = import.meta.glob<{ default: ImageMetadata }>("/src/assets/**/*.{jpeg,jpg,png,webp,gif}");
export default async function (images: any[], context: APIContext) {
for (const img of images) {
const src = img.getAttribute("src");
if (src?.startsWith("$/")) {
const cleanedSrc = src.replace("$/", "");
const imagePathPrefix = `/src/assets/${cleanedSrc}`;
const imagePath = await imagesGlob[imagePathPrefix]();
if (imagePath) {
const optimizedImage = await getImage({ src: imagePath.default });
img.setAttribute("src", context.site + optimizedImage.src.replace("/", ""));
} else {
throw Error("couldn't find image for rss feed!");
}
}
}
}

13
src/utils/formatDate.ts Normal file
View File

@ -0,0 +1,13 @@
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
export default function (date: Date | string, iso = false, format?: string) {
if (iso) {
return dayjs(date).utc(true).toISOString();
} else {
// use this as reference: https://day.js.org/docs/en/display/format
return dayjs(date).utc(true).format(format ?? "MMMM DD, YYYY");
}
}