This commit is contained in:
2026-06-26 20:36:55 -04:00
commit b43a0306d2
174 changed files with 25684 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
*.zip filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
+29
View File
@@ -0,0 +1,29 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
stfolder/
# sub-projects
src/content/apps/disic
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
+4
View File
@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
"unwantedRecommendations": []
}
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}
+62
View File
@@ -0,0 +1,62 @@
# Astro Starter Kit: Blog
```sh
npm create astro@latest -- --template blog
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
Features:
- ✅ Minimal styling (make it your own!)
- ✅ 100/100 Lighthouse performance
- ✅ SEO-friendly with canonical URLs and OpenGraph data
- ✅ Sitemap support
- ✅ RSS Feed support
- ✅ Markdown & MDX support
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
├── public/
├── src/
│   ├── components/
│   ├── content/
│   ├── layouts/
│   └── pages/
├── astro.config.mjs
├── README.md
├── package.json
└── tsconfig.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
## Credit
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).
+10
View File
@@ -0,0 +1,10 @@
// @ts-check
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
site: "https://jackrabbits.gay",
integrations: [mdx(), sitemap()],
});
+14442
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
{
"name": "jackrabbits-gay",
"type": "module",
"version": "0.0.1",
"engines": {
"node": ">=24",
"npm": ">=11"
},
"scripts": {
"dev": "astro dev",
"build": "npm run astro:build",
"astro:build": "astro build",
"clean-dist": "rm -rf .astro dist",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^5.0.3",
"@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.2",
"astro": "^6.1.3",
"better-auth": "^1.5.5",
"decap-cms-app": "^3.10.1",
"sharp": "^0.34.3"
},
"devDependencies": {
"@types/node": "^25.2.1",
"astro-compress": "2.3.9"
}
}
+6
View File
@@ -0,0 +1,6 @@
This favicon was generated using the following font:
- Font Title: Puppies Play
- Font Author: undefined
- Font Source: https://fonts.gstatic.com/s/puppiesplay/v10/wlp2gwHZEV99rG6M3NR9uB9vaAJSA_JN3Q.ttf
- Font License: undefined)
+108
View File
@@ -0,0 +1,108 @@
# Use DecapBridge PKCE auth (required)
backend:
name: gitlab
repo: merrin2/my-website
branch: main
auth_type: pkce
base_url: https://auth.decapbridge.com
auth_endpoint: /sites/398abb39-6842-4b10-b57f-e69c47709e27/pkce
auth_token_endpoint: /sites/398abb39-6842-4b10-b57f-e69c47709e27/token
gateway_url: https://gateway.decapbridge.com
# See who did what (optional)
commit_messages:
create: Create {{collection}} “{{slug}}” - {{author-name}} <{{author-login}}> via DecapBridge
update: Update {{collection}} “{{slug}}” - {{author-name}} <{{author-login}}> via DecapBridge
delete: Delete {{collection}} “{{slug}}” - {{author-name}} <{{author-login}}> via DecapBridge
uploadMedia: Upload “{{path}}” - {{author-name}} <{{author-login}}> via DecapBridge
deleteMedia: Delete “{{path}}” - {{author-name}} <{{author-login}}> via DecapBridge
openAuthoring: Message {{message}} - {{author-name}} <{{author-login}}> via DecapBridge
# Enable PKCE fields (optional, recommended)
auth:
email_claim: email
first_name_claim: first_name
last_name_claim: last_name
avatar_url_claim: avatar_url
# Better Decap + Bridge logo (optional)
logo_url: https://decapbridge.com/decapcms-with-bridge.svg
# Add site links in Decap CMS (optional)
site_url: https://jackrabbits.gay
media_folder: static/media
public_folder: /media
collections:
- name: "apps"
label: "Apps"
folder: "src/content/apps"
create: true
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "pubDate", widget: "datetime" }
- { label: "Adult", name: "adult", widget: "boolean", required: false }
- { label: "Description", name: "description", widget: "text", required: false }
- { label: "Body", name: "body", widget: "markdown", required: false }
- name: "commissions"
label: "Commissions"
folder: "src/content/commissions"
create: true
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "pubDate", widget: "datetime" }
- { label: "Adult", name: "adult", widget: "boolean", required: false }
- { label: "Description", name: "description", widget: "text", required: false }
- { label: "Artist Name", name: "artistName", widget: "list", required: false, field: { label: "Name", name: "name", widget: "string" } }
- { label: "Artist URL", name: "artistUrl", widget: "list", required: false, field: { label: "URL", name: "url", widget: "string" } }
- { label: "Custom Source", name: "customSrc", widget: "string", required: false }
- { label: "Source Image", name: "src", widget: "image", required: false }
- name: "poems"
label: "Poems"
folder: "src/content/poems"
create: true
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "pubDate", widget: "datetime" }
- { label: "Adult", name: "adult", widget: "boolean", required: false }
- { label: "Description", name: "description", widget: "text", required: false }
- { label: "Body", name: "body", widget: "markdown", required: false }
- name: "songs"
label: "Songs"
folder: "src/content/songs"
create: true
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "pubDate", widget: "datetime" }
- { label: "Adult", name: "adult", widget: "boolean", required: false }
- { label: "Description", name: "description", widget: "text", required: false }
- { label: "Audio Source", name: "src", widget: "file", required: true }
- name: "stories"
label: "Stories"
folder: "src/content/stories"
create: true
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "pubDate", widget: "datetime" }
- { label: "Adult", name: "adult", widget: "boolean", required: false }
- { label: "Description", name: "description", widget: "text", required: false }
- { label: "Body", name: "body", widget: "markdown", required: false }
- name: "visuals"
label: "Visuals"
folder: "src/content/visuals"
create: true
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "pubDate", widget: "datetime" }
- { label: "Adult", name: "adult", widget: "boolean", required: false }
- { label: "Description", name: "description", widget: "text", required: false }
- { label: "Artist Name", name: "artistName", widget: "list", required: false, field: { label: "Name", name: "name", widget: "string" } }
- { label: "Artist URL", name: "artistUrl", widget: "list", required: false, field: { label: "URL", name: "url", widget: "string" } }
- { label: "Custom Source", name: "customSrc", widget: "string", required: false }
- { label: "Source Image", name: "src", widget: "image", required: false }
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
+36
View File
@@ -0,0 +1,36 @@
---
import { SITE_TITLE } from "const";
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import "../../styles/global.css";
interface Props {
title: string;
pageDescription?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, pageDescription } = Astro.props;
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link rel="sitemap" href="/sitemap-index.xml" />
<link
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={new URL("rss.xml", Astro.site)}
/>
<meta name="generator" content={Astro.generator} />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={pageDescription} />
@@ -0,0 +1,48 @@
---
import { LinkToPost } from "components";
import { type Post } from "types";
import type { getAllPostsByYear } from "utils";
import "./styles.css";
type Props = {
posts: Post[] | Awaited<ReturnType<typeof getAllPostsByYear>>;
title: string;
};
const { posts, title } = Astro.props as Props;
---
<div class={`feed-container`}>
<div class={`feed-header`}>
<h2>{title}</h2>
</div>
<div class="feed">
<ul class="post-links">
{
Array.isArray(posts)
? posts.map((post) => (
<li>
<LinkToPost includeCollection item={post} />
</li>
))
: Object.keys(posts)
.sort((a, b) => Number(b) - Number(a))
.map((year) => {
const yearPosts = posts[year];
return (
<li>
<h3>{year}</h3>
<ul>
{yearPosts.map((post) => (
<li>
<LinkToPost includeCollection item={post} />
</li>
))}
</ul>
</li>
);
})
}
</ul>
</div>
</div>
@@ -0,0 +1,29 @@
---
import { AdultFlag, FormattedDate } from "components";
import type { PostProps } from "content.config";
import { type Post } from "types";
import { pathNameFromTitle } from "utils";
import "./styles.css";
import { keyTypeNameMap } from "const";
export interface Props {
item: Post;
}
const { item } = Astro.props as Props;
const { collection } = item;
const { adult, customSrc, description, pubDate, src, title } =
item.data as PostProps;
const url = customSrc || src || `${collection}/${pathNameFromTitle(title)}/`;
---
<div class="latest-link-container">
<div class="latest-link">
<p>{keyTypeNameMap[collection]}</p>
<a href={url}>{title}</a>
{adult && <AdultFlag />}
</div>
<FormattedDate date={pubDate} />
{!!description && <p>{description}</p>}
</div>
+55
View File
@@ -0,0 +1,55 @@
.feed-container {
--headerHeight: 60px;
--sidePadding: 40px;
background-color: var(--darkBackground);
box-sizing: border-box;
color: var(--lightText);
position: relative;
z-index: 2;
.feed-header {
align-items: center;
background-color: #fff;
display: flex;
height: var(--headerHeight);
padding: 0px var(--sidePadding);
position: sticky;
top: 0;
z-index: 3;
h2 {
color: #000;
margin: 0;
}
}
.feed {
overflow-y: scroll;
ul {
box-sizing: border-box;
height: calc(100vh - var(--headerHeight));
line-height: 25px;
list-style: none;
margin: 0px;
padding: 15px var(--sidePadding);
li {
margin-bottom: 10px;
}
li:last-child {
padding-bottom: 50px;
}
a {
color: var(--lightLinkText);
}
ul {
height: auto;
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
---
import { allCollectionKeys, keyPageNameMap, keyUrlMap } from "const";
import "./styles.css";
---
<ul class="list-links">
{
allCollectionKeys.map((key) => (
<li>
<a href={keyUrlMap[key]}>{keyPageNameMap[key]}</a>
</li>
))
}
</ul>
+9
View File
@@ -0,0 +1,9 @@
ul.list-links {
margin: 0;
padding: 0;
li {
display: inline;
margin-right: 10px;
}
}
@@ -0,0 +1,5 @@
---
import "./styles.css";
---
<span class="adult-flag-container"><span class="adult-flag">18+</span></span>
@@ -0,0 +1,28 @@
.adult-flag-container {
.adult-flag {
color: red;
font-family: mono;
font-size: small;
}
}
ul.collection-links {
border: 1px solid red;
display: none;
position: absolute;
top: 0px;
}
.see-more {
position: relative;
}
.see-more:hover {
ul.collection-links {
display: block;
}
}
ul.collection-links:hover {
display: block;
}
@@ -0,0 +1,15 @@
---
import { getDateObj } from "utils";
import "./styles.css";
interface Props {
date: string | Date;
}
const { date } = Astro.props;
const dateObj = getDateObj(date);
---
<time class="formatted-date" datetime={dateObj.toISOString()}>
{dateObj.toLocaleDateString()}
</time>
@@ -0,0 +1,3 @@
time.formatted-date {
font-size: small;
}
+10
View File
@@ -0,0 +1,10 @@
---
export interface Props {
href: string;
text: string;
}
const { href, text } = Astro.props;
---
<a href={href} target="_blank" rel="noopener noreferrer">{text}</a>
@@ -0,0 +1,49 @@
---
import { AdultFlag, FormattedDate, NewFlag } from "components";
import type { PostProps } from "content.config";
import { keyTypeNameMap } from "const";
import { type Post } from "types";
import { getPostUrl, isNew } from "utils";
import "./styles.css";
type DatePosition = "top" | "bottom";
export interface Props {
datePosition?: DatePosition;
includeCollection?: boolean;
includeDescription?: boolean;
item: Post;
}
const {
datePosition: pDatePosition,
includeCollection,
includeDescription,
item,
} = Astro.props as Props;
const { collection } = item;
const { adult, description, pubDate, title } = item.data as PostProps;
const showNewFlag = await isNew(item);
const url = getPostUrl(collection, item);
const datePosition: DatePosition = pDatePosition || "top";
---
<div class="link-to-post-container">
{
includeCollection && (
<span class="collection-name">{keyTypeNameMap[collection]}</span>
)
}
{datePosition === "top" && <FormattedDate date={pubDate} />}
<div class="link-to-post">
{showNewFlag && <NewFlag post={item} />}
<a href={url}>{title}</a>
{adult && <AdultFlag />}
</div>
{datePosition === "bottom" && <FormattedDate date={pubDate} />}
{
!!description && includeDescription && (
<p class="item-description">{description}</p>
)
}
</div>
@@ -0,0 +1,24 @@
.link-to-post-container {
.collection-name {
font-weight: bold;
font-variant: all-small-caps;
margin-right: 10px;
text-transform: uppercase;
}
.link-to-post {
align-items: center;
border-radius: var(--br);
display: flex;
gap: 5px;
max-width: 300px;
a {
font-size: large;
}
}
.item-description {
max-width: 550px;
}
}
@@ -0,0 +1,5 @@
---
import "./styles.css";
---
<span class="new-flag-container"><span class="new-flag">New!</span></span>
+53
View File
@@ -0,0 +1,53 @@
.new-flag-container {
animation: bob 2s linear infinite;
.new-flag {
font-size: smaller;
text-transform: uppercase;
background: linear-gradient(
270deg,
#ff0000,
#ff9900,
#ffff00,
#33cc33,
#0066ff,
#6600cc,
#ff3399,
#ff0000
);
background-size: 200% 200%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
filter: brightness(2);
animation: rainbowMove 3s linear infinite;
}
}
@keyframes rainbowMove {
0% {
background-position: 0% 50%;
}
100% {
background-position: 200% 50%;
}
}
@keyframes bob {
0% {
transform: translateY(0px);
}
25% {
transform: translateY(-2px);
}
75% {
transform: translateY(2px);
}
100% {
transform: translateY(0px);
}
}
+5
View File
@@ -0,0 +1,5 @@
export { default as AdultFlag } from "./AdultFlag/AdultFlag.astro";
export { default as LinkToPost } from "./LinkToPost/LinkToPost.astro";
export { default as FormattedDate } from "./FormattedDate/FormattedDate.astro";
export { default as Link } from "./Link/Link.astro";
export { default as NewFlag } from "./NewFlag/NewFlag.astro";
@@ -0,0 +1,19 @@
---
import type { PostProps } from "content.config";
import type { Post } from "types";
import "./styles.css";
import { getFileExtension } from "utils";
export interface Props {
item: Post;
}
const { item } = Astro.props;
const { src, title } = item.data as PostProps;
const type = `audio/${getFileExtension(src!)}`;
---
<div class="audio-container">
<p>{title}</p>
<audio controls> <source src={src} type={type} /></audio>
</div>
@@ -0,0 +1,3 @@
.audio-container {
flex-basis: 100%;
}
@@ -0,0 +1,64 @@
---
import { Image } from "astro:assets";
import type { CollectionKey } from "astro:content";
import { Link } from "components";
import type { PostProps } from "content.config";
import type { Post } from "types";
import "./styles.css";
export interface Props {
item: Post;
collection?: CollectionKey;
}
const { item } = Astro.props;
const { artistName, artistUrl, customSrc, src, title } = item.data as PostProps;
const artistUrlPairs: [string, string][] = [];
if (Array.isArray(artistName)) {
artistName.forEach((artist, i) => {
artistUrlPairs.push([artist, (artistUrl as string[])[i]]);
});
} else if (artistName) {
artistUrlPairs.push([artistName, artistUrl as string]);
}
const images = import.meta.glob<{ default: ImageMetadata }>(
`/public/assets/**/*.{jpeg,jpg,png,gif}`,
);
---
<div class="gallery-image-container">
<a
href={customSrc || src}
target="_blank"
rel="noopener noreferrer"
aria-label={`Open full-sized image in new tab`}
>
<Image
src={images[`/public${src}`]()}
alt={title ?? "Gallery image"}
loading="lazy"
quality="medium"
height={150}
width={150}
/>
</a>
<div class="info-overlay">
<p>{title}</p>
{
artistUrlPairs.map(([name, url], i) => {
const separator = artistUrlPairs[i + 1] ? ", " : "";
if (url) {
return (
<span>
<Link href={url} text={name} />
{separator}
</span>
);
}
return <span>{`${name}${separator}`}</span>;
})
}
</div>
</div>
@@ -0,0 +1,49 @@
.gallery-image-container,
.gallery-image-container a {
height: var(--thumbnailWidth);
width: var(--thumbnailWidth);
}
.gallery-image-container {
position: relative;
transition: all 0.2s;
.info-overlay {
background-color: var(--darkBackground);
border-radius: 0 0 var(--br) var(--br);
color: var(--lightText);
font-size: small;
opacity: 0;
padding: 10px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
transform: translateY(5px);
transition: 0.3s all;
p {
margin: 0;
}
a {
color: var(--lightLinkText);
}
}
img {
border-radius: var(--br);
object-fit: scale-down;
width: 100%;
height: 100%;
}
}
.gallery-image-container:hover {
transform: scale(105%);
.info-overlay {
opacity: 1;
transform: translateY(0px);
}
}
@@ -0,0 +1,18 @@
---
import { LinkToPost } from "components";
import { type Post } from "types";
import "./styles.css";
export interface Props {
item: Post;
includeDescription?: boolean;
}
const { item, includeDescription } = Astro.props;
---
<LinkToPost
datePosition="top"
item={item}
includeDescription={includeDescription}
/>
+3
View File
@@ -0,0 +1,3 @@
export { default as GalleryAudioItem } from "./AudioItem/AudioItem.astro";
export { default as GalleryImageItem } from "./ImageItem/ImageItem.astro";
export { default as GalleryWritingItem } from "./WritingItem/WritingItem.astro";
+5
View File
@@ -0,0 +1,5 @@
export { default as BaseHead } from "./BaseHead/BaseHead.astro";
export * from "./common";
export * from "./gallery";
export { default as LatestFeed } from "./LatestFeed/LatestFeed.astro";
export { default as NavLinks } from "./NavLinks/NavLinks.astro";
+40
View File
@@ -0,0 +1,40 @@
import type { CollectionKey } from "astro:content";
import {
APPS_URL,
COMMISSIONS_URL,
POEMS_URL,
SONGS_URL,
STORIES_URL,
VISUAL_ART_URL,
} from "./urls";
export const keyUrlMap: { [key in CollectionKey]: string } = {
apps: APPS_URL,
commissions: COMMISSIONS_URL,
poems: POEMS_URL,
songs: SONGS_URL,
stories: STORIES_URL,
visuals: VISUAL_ART_URL,
};
export const keyPageNameMap: { [key in CollectionKey]: string } = {
apps: "Apps",
commissions: "Commissioned Art",
poems: "Poems",
songs: "Songs",
stories: "Stories",
visuals: "Visual Art",
};
export const keyTypeNameMap: { [key in CollectionKey]: string } = {
apps: "App",
commissions: "Commissioned Art",
poems: "Poem",
songs: "Song",
stories: "Story",
visuals: "Visual Art",
};
export const allCollectionKeys: CollectionKey[] = Object.keys(
keyPageNameMap,
).map((key) => key as CollectionKey);
+11
View File
@@ -0,0 +1,11 @@
import type { CollectionKey } from "astro:content";
export * from "./collectionKeyMaps";
export * from "./strings";
export * from "./urls";
export const noSubrouteCollections: CollectionKey[] = [
"commissions",
"songs",
"visuals",
];
+9
View File
@@ -0,0 +1,9 @@
import type { CollectionKey } from "astro:content";
import { keyPageNameMap } from "./collectionKeyMaps";
export const SITE_TITLE = "Jack of All Rabbits";
export const SITE_DESCRIPTION = "";
export const getListPageDescription = (collection: CollectionKey) => {
return `All ${keyPageNameMap[collection].toLocaleLowerCase()}, sorted by date descending.`;
};
+8
View File
@@ -0,0 +1,8 @@
export const APPS_URL = "/apps";
export const COMMISSIONS_URL = "/commissions";
export const POEMS_URL = "/poems";
export const SONGS_URL = "/songs";
export const STORIES_URL = "/stories";
export const VISUAL_ART_URL = "/visuals";
export const LIVE_APP_PATH = "/live";
+79
View File
@@ -0,0 +1,79 @@
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";
const commonProps = {
adult: z.boolean().optional(),
description: z.string().optional(),
pubDate: z.string(),
title: z.string(),
};
const imageProps = {
...commonProps,
artistName: z.union([z.string(), z.array(z.string())]).optional(),
artistUrl: z.union([z.string(), z.array(z.string())]).optional(),
customSrc: z.string().optional(), // used in cases where thumbnail is different from file
src: z.string().optional(),
};
const zodSchema = z.object({
...imageProps,
});
export type PostProps = z.infer<typeof zodSchema>;
export const collectionNames = [
"apps",
"commissions",
"poems",
"songs",
"stories",
"visuals",
];
const commonSchema = z.object({
...commonProps,
});
const imageSchema = z.object({ ...imageProps });
const appSchema = commonSchema;
const commissionSchema = imageSchema;
const poemSchema = commonSchema;
const songSchema = z.object({
...commonProps,
src: z.string(),
});
const storySchema = commonSchema;
const visualSchema = imageSchema;
const getGlob = (name: string) =>
glob({ pattern: "**/*.md", base: `./src/content/${name}` });
export const collections = {
apps: defineCollection({
loader: getGlob("apps"),
schema: appSchema,
}),
commissions: defineCollection({
loader: getGlob("commissions"),
schema: commissionSchema,
}),
poems: defineCollection({
loader: getGlob("poems"),
schema: poemSchema,
}),
songs: defineCollection({
loader: getGlob("songs"),
schema: songSchema,
}),
stories: defineCollection({
loader: getGlob("stories"),
schema: storySchema,
}),
visuals: defineCollection({
loader: getGlob("visuals"),
schema: visualSchema,
}),
};
+14
View File
@@ -0,0 +1,14 @@
---
title: "Disic"
pubDate: "Apr 19 2024"
---
Disic is an app that uses cellular automata to generate pretty animated patterns. Runs 7 games, including Conway's Game of Life, with many options for customizing colors and rulesets.
[Try it out.](https://disic.jackrabbits.gay)
![Disic screenshot 1](/assets/apps/disic1.png)
![Disic screenshot 2](/assets/apps/disic2.png)
![Disic screenshot 3](/assets/apps/disic3.png)
+42
View File
@@ -0,0 +1,42 @@
---
title: "Hare Trigger"
pubDate: "Sep 15 2024"
---
_A no-horse town in a shimmering valley. A mysterious crate. A bounty hunter with a twitching nose and an itchy trigger finger._
_Hare Trigger_ is a short exploration game featuring knowledge-based progression and shotgun-related mishaps. Its cozy setting is populated with twelve colorful characters, all of whom you have a random chance of blowing away as you talk to them.
Made in ten days for SuperNoVA Game Jam 2024.
[Download](/assets/apps/Hare%20Trigger.zip)
![Hare Trigger screenshot 1](/assets/apps/ht1.png)
![Hare Trigger screenshot 2](/assets/apps/ht2.png)
![Hare Trigger screenshot 3](/assets/apps/ht3.png)
**Credits**
_sakura Hz_
- "Vintage Radio Station"
- "jazzy"
- "watching anime (piano version)"
- "neon samurai"
_FSM Team_
- "Downtown Walk"
_Mixaund_
- "Motivate Me"
_Nine Inch Nails_
- "Ghosts II - 15"
_jsfxr_
- All generated sound effects
_Godot_
- Engine
+8
View File
@@ -0,0 +1,8 @@
---
title: "JOLB"
pubDate: "Jan 18 2026"
---
**JOLB is currently offline while I work on moving it to new hosting.**
JOLB is a poetry-writing game in which each player must add a line to a poem having only seen the line written by the previous player. It's packaged as a Telegram bot and can be played by messaging `/start` to **@jolb_bot**.
@@ -0,0 +1,7 @@
---
title: "Boxer 1.0 Ref Sheet"
artistName: "Me (using template)"
src: "/assets/commissions/1492415364.boxerbunny_boxer.png"
pubDate: "Jan 01 2017"
---
@@ -0,0 +1,8 @@
---
title: "PFP"
artistName: "PFP"
artistUrl: "https://www.furaffinity.net/user/etuix"
src: "/assets/commissions/1500346995.boxerbunny_icon_boxerbunny-both.png"
pubDate: "Jan 01 2017"
---
@@ -0,0 +1,8 @@
---
title: "Pride YCH"
artistName: "Fauxor"
artistUrl: "https://www.furaffinity.net/user/fauxor"
src: "/assets/commissions/1500347120.boxerbunny_pride17bb_copy.png"
pubDate: "Jan 01 2017"
---
@@ -0,0 +1,8 @@
---
title: "Buns!"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/1500347268.boxerbunny_boxerbuns2.0.png"
pubDate: "Jan 01 2017"
---
@@ -0,0 +1,8 @@
---
title: "Booty YCH"
artistName: "obliviousally"
artistUrl: "https://www.furaffinity.net/user/obliviousally"
src: "/assets/commissions/1503019784.boxerbunny_photo_2017-08-17_21-23-36.jpg"
pubDate: "Jan 01 2017"
---
@@ -0,0 +1,8 @@
---
title: "Boxer Hypno Icon"
artistName: "Rai"
artistUrl: "https://www.furaffinity.net/user/shibarin"
src: "/assets/commissions/1504452641.boxerbunny_1p1hoto_aug_19,_10_03_34_pm__1_.jpg"
pubDate: "Jan 01 2017"
---
@@ -0,0 +1,8 @@
---
title: "Karate Bunz"
artistName: "FayV"
artistUrl: "https://www.furaffinity.net/user/fayv"
src: "/assets/commissions/1515022119.boxerbunny_djyhkeavaaaptqz.jpg"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,8 @@
---
title: "Gameboy Icons"
artistName: "Jess CyanSorcery"
artistUrl: "https://www.furaffinity.net/user/cyanroxanne"
src: "/assets/commissions/1515022210.boxerbunny_photo_2017-11-30_18-35-12.jpg"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,7 @@
---
title: "Boxer 2.0 Ref Sheet"
artistName: "Me (using template)"
src: "/assets/commissions/Boxer2.0 ref.png"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,8 @@
---
title: "PFP"
artistName: "DragonessDrei"
artistUrl: "https://www.furaffinity.net/user/dragonessdrei"
src: "/assets/commissions/1520214373.boxerbunny_1517951027.dragonessdrei_boxer_icon.png"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,8 @@
---
title: "Boxer Badge"
artistName: "Gamibrii"
artistUrl: "https://www.deviantart.com/gamibrii"
src: "/assets/commissions/1521581447.boxerbunny_photo_2018-03-09_14-28-27.jpg"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,8 @@
---
title: "Spreader Bar YCH"
artistName: "Soft Mallow"
artistUrl: "https://x.com/softieMallow"
src: "/assets/commissions/1521581566.boxerbunny_spreadbar_boxer.png"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,8 @@
---
title: "Hypno Icon YCH"
artistName: "raccoonsnuggles"
artistUrl: "https://www.furaffinity.net/user/raccoonsnuggles"
src: "/assets/commissions/1535688982.boxerbunny_1533941385.raccoonsnuggles_boxer_bunny_hypno_ych.png"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,8 @@
---
title: "Snow Bun"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/1578748845.boxerbunny_1d1a5c8a-728d-4830-bfe8-8e65126dd3cc_jpeg.jpg"
pubDate: "Jan 01 2020"
---
@@ -0,0 +1,8 @@
---
title: "Boxer 3.0 Ref Sheet"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/1592572897.boxerbunny_ab81e558-47e0-4358-8c13-4e816ce4cb8a_jpeg.jpg"
pubDate: "Jan 01 2020"
---
@@ -0,0 +1,8 @@
---
title: "Boxer 3.0 Ref Sheet (Bust)"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/1594665478.boxerbunny_a3ebd4f5-cc8c-40c1-92ff-a51304e3172a_jpeg.jpg"
pubDate: "Jan 01 2020"
---
@@ -0,0 +1,8 @@
---
title: "Pretty in Pink"
artistName: "Snezz"
artistUrl: "https://www.furaffinity.net/user/snebzz"
src: "/assets/commissions/1672705093.boxerbunny__boxerbunny.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "All You Need"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/1672705143.boxerbunny_1631027204_slyasafoxibou_1583031570_vaynthefox_20200109_boxerserenade_v2__1_.jpg"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Sweater YCH 1"
artistName: "Dusty"
artistUrl: "t.me/dustydoodles"
src: "/assets/commissions/1672705271.boxerbunny_photo_2023-01-02_19-16-31.jpg"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Sweater YCH 2"
artistName: "Dusty"
artistUrl: "t.me/dustydoodles"
src: "/assets/commissions/1672705388.boxerbunny_sweatercommission2.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,7 @@
---
title: "Merrin/Val Ref Sheet"
artistName: "Me (using template)"
src: "/assets/commissions/1672705575.boxerbunny_merrin_ref_sheet.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Merrin Sketch Sheet"
artistName: "Hatch The Pigeon"
artistUrl: "https://ko-fi.com/hatchthepigeon"
src: "/assets/commissions/1672800799.boxerbunny_useruploads_1652be89-cbc2-48b2-94ff-b7af58a5f75d_sketchpage.jpg"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Merrin Aerial Yoga"
artistName: "Zavlekat."
artistUrl: "https://www.furaffinity.net/user/zavlekat."
src: "/assets/commissions/1673639819.boxerbunny_img_20230110_082652_272.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Stuck YCH"
artistName: "Cairo Steele"
artistUrl: "https://www.furaffinity.net/user/vesrin"
src: "/assets/commissions/1675300541.boxerbunny_stuck_v1_small.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Stuck YCH (chastity version)"
artistName: "Cairo Steele"
artistUrl: "https://www.furaffinity.net/user/vesrin"
src: "/assets/commissions/1675300601.boxerbunny_stuck_v2_small.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "PFP"
artistName: "Th3Geo"
artistUrl: "https://t.me/CrowParadeArt"
src: "/assets/commissions/1676735351.boxerbunny_lowres__60_.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "PFP"
artistName: "Hatch The Pigeon"
artistUrl: "https://ko-fi.com/hatchthepigeon"
src: "/assets/commissions/1676735498.boxerbunny_download.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Nautilus Friend!"
artistName: "Crow Parade"
artistUrl: "https://t.me/CrowParadeArt"
src: "/assets/commissions/1676735606.boxerbunny_img_20230213_225439_652.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Quizzical"
artistName: "Daud_Dalmata"
artistUrl: "https://www.furaffinity.net/user/dauddalmata"
src: "/assets/commissions/1677494199.boxerbunny_merrin__1_.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Quizzical"
artistName: "zangerdanger"
artistUrl: "https://zangerdanger.carrd.co/"
src: "/assets/commissions/1691172816.boxerbunny_img_20230804_141229_274.jpg"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Paws YCH"
artistName: "hita9"
artistUrl: "https://www.furaffinity.net/user/eugenyblack"
src: "/assets/commissions/1692198216.boxerbunny_37647440_paws_3.jpg"
pubDate: "Jan 01 2020"
---
@@ -0,0 +1,6 @@
---
title: "First Ever Commission"
artistName: "Unknown"
src: "/assets/commissions/IMG_0071.png"
pubDate: "Jan 01 2010"
---
@@ -0,0 +1,8 @@
---
title: "Boxer Sketch"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/IMG_4558.jpg"
pubDate: "Jan 01 2018"
---
@@ -0,0 +1,7 @@
---
title: "PFP"
artistName: "Unknown"
src: "/assets/commissions/photo_2020-03-25_09-36-18.jpg"
pubDate: "Jan 01 2021"
---
@@ -0,0 +1,7 @@
---
title: "YCH"
artistName: "Unknown"
src: "/assets/commissions/photo_2024-04-22_15-05-38.jpg"
pubDate: "Jan 01 2024"
---
@@ -0,0 +1,7 @@
---
title: "Kit and Kairo"
artistName: "Unknown"
src: "/assets/commissions/photo_2024-11-11_07-55-24.jpg"
pubDate: "Jan 01 2024"
---
@@ -0,0 +1,8 @@
---
title: "PFP"
artistName: "DivineFollyArt"
artistUrl: "https://www.furaffinity.net/user/divinefollyart/"
src: "/assets/commissions/-5168159333425023839_121.jpg"
pubDate: "Jan 01 2025"
---
@@ -0,0 +1,12 @@
---
title: "Orgy YCH"
artistName:
- "Dasher"
- "Chockloss"
artistUrl:
- "https://www.furaffinity.net/user/d4s3r/"
- "https://www.furaffinity.net/user/chockloss/"
src: "/assets/commissions/Orgycolor.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Shibari YCH"
artistName: "Soroka"
artistUrl: "https://www.furaffinity.net/user/soroka-ne-soroka/"
src: "/assets/commissions/1683349109.soroka-ne-soroka_юч_27_af.png"
pubDate: "Jan 01 2023"
---
@@ -0,0 +1,8 @@
---
title: "Kairo Pinup"
artistName: "KwiK"
artistUrl: "https://www.furaffinity.net/user/kwik/"
src: "/assets/commissions/photo_2024-04-29_15-18-42.jpg"
pubDate: "Jan 01 2024"
---
@@ -0,0 +1,8 @@
---
title: "Kairo Bondage Sketch"
artistName: "Sly"
artistUrl: "https://www.furaffinity.net/user/slyasafoxibou"
src: "/assets/commissions/photo_2023-10-16_00-47-08.jpg"
pubDate: "Jan 01 2024"
---
@@ -0,0 +1,8 @@
---
title: "Retro CGI Mystery Commission"
artistName: "zer0rei"
artistUrl: "https://www.zer0rei.com/store/mystery"
src: "/assets/commissions/Pink Jackrabbit Hypnotic 2160p Wallpaper.png"
pubDate: "Jan 01 2025"
---
+40
View File
@@ -0,0 +1,40 @@
---
title: "A fox drags the dusk"
pubDate: "Oct 13 2025"
---
from the clouds, jaws gripped round
the light that slots the rumpled birches'
just-unburdened boughs
trackless in the frowzled leaves,
she suddens over yearning
roots,
scarlet ember of the summer's
afterlife, tugging sunset
taut
along the eaves
skies groan grateful
as their skin stretches
thin
and their bones show through
no one hears her straining
breath
before the snap
no one sees her tumble to her
den in the hollow
-chested
night
bottomless hours on,
pushed in the barrow of a robin's
song,
dawn
overflows the dark
yet no one knows if or when
she'll wake again
to save the day from day
+14
View File
@@ -0,0 +1,14 @@
---
title: "Anchorite Autumn"
pubDate: "Oct 18 2025"
---
the rain-hooded monk of october
shades a stony cell
wafer-pale
lips tight
in
waning
votive twilight
thumbing beads of leaves
remembering sins of summer
+19
View File
@@ -0,0 +1,19 @@
---
title: "Caustics"
pubDate: "Oct 3 2025"
---
cracked glass wobble
leaky veins of sun
coral bed carousel
fizzled ribs of sea
sabre-fencing phantoms on
supermarket lobsters on
superyacht keels on
high school pool room tile
physics in the haunted sand
an ancient urgent transmission
a nebula-sized qr code
too close to scan
+10
View File
@@ -0,0 +1,10 @@
---
title: "Gender (noun)"
pubDate: "Sep 24 2025"
---
1. a correlation cluster of consumer preferences
2. a set of proscriptions surrounding expressions of anger
3. synesthesia. e.g. "harps are pink, saxes are blue."
4. any game whose players: cannot explain the rules; righteously enforce the rules; lose repeatedly but never win
5. the right or left handedness of shirt buttons
+26
View File
@@ -0,0 +1,26 @@
---
title: "Image18(Fixed).BMP"
pubDate: "Sep 21 2025"
---
xp
bliss
dazzles moonless
rooms like razor burn
dial
up
colics to a quiet
world
alt.consciousness.near-death-exp
re: please interpret...
re: please interpret...
re: please interpret...
> what recompense for sorrow?
only wind in the branches
you have been
online
-5000000000 second(s)
&
+29
View File
@@ -0,0 +1,29 @@
---
title: "Lostwave Girl"
pubDate: "Sep 10 2025"
---
Woken by umami-ripened sewage,
she greets another morning like "I can't do this."
Piss-puss mixture foams and glows golden
Cavatappi maggots swarm and grow swollen
Tumblrborne cognitohazard plague-rat girly
("Reblog to turn your brain to masticated slurry")
gets off to a turboprop voring an Accord
and adds a shadow person to her salvinorin stimboard
Darknet Sorceress in her Hello Kitty bed
reanimates to summon a glass menagerie of meds
Eyes made of dread, practice smile mildly upsetting,
morning flossing mostly acts as black bile bloodletting
Thesis on Deleuze decomposing on a USB
as she dapples caramel fraps with toasted sucralose debris
And every drive-thru order's immediate "Hello, sir!"
tends to get her wishing for a euthanasia coaster
She daydreams an Insta feed of last known photos:
CEOs reported missing from Palermo to Kyoto
Interpol too flustered to ever crack the case
because she's just too stunning wearing black leather and lace.
+18
View File
@@ -0,0 +1,18 @@
---
title: "MOON10.RAW"
pubDate: "Sep 21 2025"
---
moon like a searchbar
cradling a hesitating
cursor
flat clouds hemming the waiting
silence of the world
anyone can clip through concrete
no one can clip through the skybox
a neon sign once named the night
PSYCHIC
3am smothers it
@@ -0,0 +1,16 @@
---
title: "My old neighborhood is not that old."
pubDate: "Jan 9 2026"
---
Its body is still warm. But
its skies have thinned to treacherous
mountain roads. Its clouds seem just about
to burst the seams of the well-worn winter.
To the shapes un-peopling its misty windows,
I hope I've grown unrecognizable.
Every cradle eventually hollows. Abandoned
bars lay like thickets of ribs
in an elephant graveyard. Or, maybe,
as mile markers for the next ones
on their way.
@@ -0,0 +1,28 @@
---
title: "November 5th, Petworth"
pubDate: "Sep 29 2025"
---
Parked behind St. Paul's,
raindrops blither on the glass
like an ad read:
Honey, DraftKings, BetterHelp
6%
ABV King Cobra
in the cupholder
19%
draining phone battery
USB-C cable
Proudly Made in the USA
64%
turnout
There's something dull about a wildfire
About a country plunking a mining
rig in the tub, expecting
something beyond
electrocution
+18
View File
@@ -0,0 +1,18 @@
---
title: "Slushwave Girl"
pubDate: "Sep 29 2025"
---
she dreams you through cerulean curves of sky
and sighs you to her moth-soft bed
into a blanket of fog and whispering screens
which she vows you'll never want to leave
ascii angels haunt the ocean overhead
special characters circling like patient cumuli
she shivers you to airwaves where test patterns liquefy
station sign-offs flocking like gulls in infrared
haven't you always counted on sprouting wings?
or spindling delicate air on gossamer strings?
she glitters you to the web's last forum thread
and vows its gleaming vowels will never die
@@ -0,0 +1,20 @@
---
adult: true
title: "Sonnet for Your Werewolf Boyfriend"
pubDate: "Jul 12 2025"
---
He looms above you laden as a cloud
He beasts upon you furious as rain
Refusal smells to him like fertile ground,
like racing heat - and big breeds need to chase
He bites away your cries and marks you his
And every fevered thrust that runs you through
is made to stain your unclaimed scent with *his*
until his knot triumphs into you
You didn't used to know this feral quake
which bows your body down to pliant crawl
You didn't used to need his teeming weight;
you used to be afraid to need at all.
With sheepish thanks he licks your face and sighs,
and you feel like a giant in his eyes
@@ -0,0 +1,22 @@
---
title: "Sonnet for a Silent House"
pubDate: "Jul 5 2025"
---
A brittle bend of stone approached the house
A little stand of trees concealed the world
The space between was flushed with corpse-fed sprouts
Above, the bony gray of fall unfurled
Explorers trembled past the dreaming door
and showed their paper throats to spiral stairs
They found the thing that they'd been looking for,
but only after snapping in its snare
The ashy glare of dawn reveals the end
result of lusting for the shadow of
the world. A spine askew in jawbone bend,
a rusty stain upon a dusty rug
The house traverses folding maps of time
to welcome you the hour you arrive.
@@ -0,0 +1,20 @@
---
title: "Sonnet for the Abomination Currently Destroying Our Cathedral"
pubDate: "Sep 3 2025"
---
An embered sky insists upon the roof
Cathedral peaks trepan its clouded brain
The beast below still madders her refrain,
stampeding fraying twilight underhoof
Entramping shards of stained glass glutter, while
serrated winter wind affronts the saints
We ensconced in slattern rafters scorn her gait
which paces coarse as blood on marble tile
and rage.
Rage ever at the watchman's slip.
She snuffles at our holy tome to gnaw
and mangle in the glammer of her maw
She bursts the guarded silence from our lips,
our cries at last attract her gaze full vile,
but worst of all comes her beatific smile
+35
View File
@@ -0,0 +1,35 @@
---
title: "The Darkest Wild"
pubDate: "Aug 29 2025"
---
Come the roar of the darkest wild
Come the vilest kind of dream
Come the glut of the storm with a flood in its throat
Let all vicious things be freed
Come your trembling and sweating righteous
Come your regiment's jagged horn
Come your litany of galloping. Try us. Retreat
with less than no remains to mourn
We've long since flensed our leather shells,
the armor you rely on
Harried arrows carom off us
We wear the night like iron
Come the sun to conceal the shadow -
come to leer on the ones you felled
Come the dusk, we'll petition our pardons from death;
unlike you, we know him well
We've learned to drink the ink from paper
and feast on scarecrow bristles
Getting drunk on mountain fog
and moonlight from the river
We've learned the words that twist your fear
We watched you from our hold
Dancing when the cave winds sang
Thriving on each other's breath alone

Some files were not shown because too many files have changed in this diff Show More