Finish adding blog posts
This commit is contained in:
parent
4a3ee88f77
commit
cf180442c3
21 changed files with 178 additions and 40 deletions
src
assets
components/icons
content
blog
games
stories
tag-categories
i18n
layouts
pages
Binary file not shown.
Before ![]() (image error) Size: 9.3 KiB After ![]() (image error) Size: 25 KiB ![]() ![]() |
BIN
src/assets/thumbnails/other/crossing_over_retrospective.png
Normal file
BIN
src/assets/thumbnails/other/crossing_over_retrospective.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 124 KiB |
BIN
src/assets/thumbnails/other/taken_in_breakdown.png
Normal file
BIN
src/assets/thumbnails/other/taken_in_breakdown.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 45 KiB |
15
src/components/icons/IconBlog.astro
Normal file
15
src/components/icons/IconBlog.astro
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
import SVGIcon from "./SVGIcon.astro";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
width: string;
|
||||||
|
height: string;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<SVGIcon {...Astro.props} viewBox="0 0 512 512">
|
||||||
|
<path
|
||||||
|
d="M192 32c0 17.7 14.3 32 32 32c123.7 0 224 100.3 224 224c0 17.7 14.3 32 32 32s32-14.3 32-32C512 128.9 383.1 0 224 0c-17.7 0-32 14.3-32 32zm0 96c0 17.7 14.3 32 32 32c70.7 0 128 57.3 128 128c0 17.7 14.3 32 32 32s32-14.3 32-32c0-106-86-192-192-192c-17.7 0-32 14.3-32 32zM96 144c0-26.5-21.5-48-48-48S0 117.5 0 144L0 368c0 79.5 64.5 144 144 144s144-64.5 144-144s-64.5-144-144-144l-16 0 0 96 16 0c26.5 0 48 21.5 48 48s-21.5 48-48 48s-48-21.5-48-48l0-224z"
|
||||||
|
></path>
|
||||||
|
</SVGIcon>
|
|
@ -1,5 +1,6 @@
|
||||||
export { default as IconArrowBack } from "./IconArrowBack.astro";
|
export { default as IconArrowBack } from "./IconArrowBack.astro";
|
||||||
export { default as IconArrowUp } from "./IconArrowUp.astro";
|
export { default as IconArrowUp } from "./IconArrowUp.astro";
|
||||||
|
export { default as IconBlog } from "./IconBlog.astro";
|
||||||
export { default as IconBook } from "./IconBook.astro";
|
export { default as IconBook } from "./IconBook.astro";
|
||||||
export { default as IconBriefcase } from "./IconBriefcase.astro";
|
export { default as IconBriefcase } from "./IconBriefcase.astro";
|
||||||
export { default as IconChevronLeft } from "./IconChevronLeft.astro";
|
export { default as IconChevronLeft } from "./IconChevronLeft.astro";
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
---
|
---
|
||||||
title: Jamming Over
|
title: "Jamming Over: A Postmortem"
|
||||||
pubDate: 2024-03-26
|
pubDate: 2024-03-26
|
||||||
isDraft: true
|
|
||||||
isAgeRestricted: true
|
isAgeRestricted: true
|
||||||
authors: bad-manners
|
authors: bad-manners
|
||||||
# thumbnail: /src/assets/thumbnails/story_thumbnail.png
|
thumbnail: /src/assets/thumbnails/other/crossing_over_retrospective.png
|
||||||
description: |
|
description: |
|
||||||
Postmortem about my first vore game, [Crossing Over](/games/crossing-over) – albeit more of an assortment of random thoughts. **Spoilers for Crossing Over ahead!**
|
A retrospective about my first vore game, [Crossing Over](/games/crossing-over) – albeit more of an assortment of random thoughts than an actual postmortem. **Spoilers for Crossing Over ahead!**
|
||||||
tags: []
|
tags:
|
||||||
|
- behind the scenes
|
||||||
|
- retrospective
|
||||||
|
- oral vore
|
||||||
|
- anthro predator
|
||||||
|
- willing predator
|
||||||
|
- willing prey
|
||||||
|
- male predator
|
||||||
|
- non-binary prey
|
||||||
|
- micro prey
|
||||||
|
- soul vore
|
||||||
|
- long-term endo
|
||||||
relatedGames:
|
relatedGames:
|
||||||
- crossing-over
|
- crossing-over
|
||||||
---
|
---
|
||||||
|
@ -280,3 +290,7 @@ Nonetheless, I can't deny that I feel this way. I've been trying to write more s
|
||||||
Well, I managed to finish one other thing, at least: this postmortem! And despite this current slump, I still want to make stuff – more stories and, maybe, even more games. I know that these negative feelings will fade from memory, and that I'll remember this project fondly for months and years to come. I want to make more art – not just for myself, but for others. I want to improve my skills, and I want to bring people joy.
|
Well, I managed to finish one other thing, at least: this postmortem! And despite this current slump, I still want to make stuff – more stories and, maybe, even more games. I know that these negative feelings will fade from memory, and that I'll remember this project fondly for months and years to come. I want to make more art – not just for myself, but for others. I want to improve my skills, and I want to bring people joy.
|
||||||
|
|
||||||
At the end of the day, I would be happy to become even a fraction of who Marco was for Bard in their darkest hour.
|
At the end of the day, I would be happy to become even a fraction of who Marco was for Bard in their darkest hour.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Oh, hey, I didn't expect you to read all of this! I hope it was enjoyable. By the way, since I first wrote this retrospective-slash-postmortem, I've [opened the source code for this game](https://gitgud.io/BadMannersXYZ/CrossingOver)! If you are interested in an even more hands-on look at how it was made, or want to make your own visual novel in Godot, feel free to peruse it to your heart's content.
|
|
@ -1,15 +1,26 @@
|
||||||
---
|
---
|
||||||
title: "Story Breakdown: Taken In"
|
title: "Taken In: Story Breakdown!"
|
||||||
pubDate: 2024-01-23
|
pubDate: 2024-01-23
|
||||||
isDraft: true
|
|
||||||
isAgeRestricted: true
|
isAgeRestricted: true
|
||||||
authors: bad-manners
|
authors: bad-manners
|
||||||
# thumbnail: /src/assets/thumbnails/story_thumbnail.png
|
thumbnail: /src/assets/thumbnails/other/taken_in_breakdown.png
|
||||||
description: |
|
description: |
|
||||||
First time annotating a vore story; in this case, [Taken In](/stories/taken-in). Here, I go over my writing process while offering additional tidbits of information.
|
First time annotating a vore story; in this case, [Taken In](/stories/taken-in). Here, I go over my writing process while offering additional tidbits of information.
|
||||||
tags: []
|
tags:
|
||||||
relatedGames:
|
- behind the scenes
|
||||||
- crossing-over
|
- Sam Brendan
|
||||||
|
- feral predator
|
||||||
|
- anthro prey
|
||||||
|
- male predator
|
||||||
|
- ambiguous gender prey
|
||||||
|
- willing predator
|
||||||
|
- unwilling prey
|
||||||
|
- oral vore
|
||||||
|
- same size
|
||||||
|
- full tour
|
||||||
|
- point of view
|
||||||
|
relatedStories:
|
||||||
|
- taken-in
|
||||||
---
|
---
|
||||||
|
|
||||||
All in all, going over the story and breaking it down was a fun process. This was originally a text document, which I had to completely refit into this blog post that you're reading (oof...!). But for the sake of posteriority, I think it was worth the effort.
|
All in all, going over the story and breaking it down was a fun process. This was originally a text document, which I had to completely refit into this blog post that you're reading (oof...!). But for the sake of posteriority, I think it was worth the effort.
|
|
@ -41,7 +41,7 @@ tags:
|
||||||
- soul vore
|
- soul vore
|
||||||
- long-term endo
|
- long-term endo
|
||||||
relatedBlogPosts:
|
relatedBlogPosts:
|
||||||
- crossing-over-postmortem
|
- crossing-over-retrospective
|
||||||
---
|
---
|
||||||
|
|
||||||
<iframe
|
<iframe
|
||||||
|
|
|
@ -29,6 +29,8 @@ tags:
|
||||||
- point of view
|
- point of view
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
"Sam Brendan": bad-manners
|
"Sam Brendan": bad-manners
|
||||||
|
relatedBlogPosts:
|
||||||
|
- taken-in-breakdown
|
||||||
---
|
---
|
||||||
|
|
||||||
Clank! Shuffle! Crunch! The sounds outside are too loud to be stopped by the walls in your room, and you jolt awake.
|
Clank! Shuffle! Crunch! The sounds outside are too loud to be stopped by the walls in your room, and you jolt awake.
|
||||||
|
|
|
@ -9,3 +9,7 @@ tags:
|
||||||
description: Short-format stories with no more than 2,500 words.
|
description: Short-format stories with no more than 2,500 words.
|
||||||
- name: toki pona
|
- name: toki pona
|
||||||
description: Stories written in toki pona, the language of good.
|
description: Stories written in toki pona, the language of good.
|
||||||
|
- name: behind the scenes
|
||||||
|
description: Content where I go over the process of making other content.
|
||||||
|
- name: retrospective
|
||||||
|
description: Documents detailing the good and bad parts of the process of creation of a certain project.
|
||||||
|
|
|
@ -298,7 +298,7 @@ const UI_STRINGS = {
|
||||||
},
|
},
|
||||||
// Tag-related strings
|
// Tag-related strings
|
||||||
"tag/total_works_with_tag": {
|
"tag/total_works_with_tag": {
|
||||||
en: (tag: string, storiesCount: number, gamesCount: number) => {
|
en: (tag: string, storiesCount: number, gamesCount: number, blogPostsCount: number) => {
|
||||||
const content = [];
|
const content = [];
|
||||||
if (storiesCount > 0) {
|
if (storiesCount > 0) {
|
||||||
content.push(UI_STRINGS["util/enumerate"].en(storiesCount, "story", "stories"));
|
content.push(UI_STRINGS["util/enumerate"].en(storiesCount, "story", "stories"));
|
||||||
|
@ -306,6 +306,9 @@ const UI_STRINGS = {
|
||||||
if (gamesCount > 0) {
|
if (gamesCount > 0) {
|
||||||
content.push(UI_STRINGS["util/enumerate"].en(gamesCount, "game", "games"));
|
content.push(UI_STRINGS["util/enumerate"].en(gamesCount, "game", "games"));
|
||||||
}
|
}
|
||||||
|
if (blogPostsCount > 0) {
|
||||||
|
content.push(UI_STRINGS["util/enumerate"].en(gamesCount, "blog post", "blog posts"));
|
||||||
|
}
|
||||||
if (content.length === 0) {
|
if (content.length === 0) {
|
||||||
return `No works tagged with "${tag}".`;
|
return `No works tagged with "${tag}".`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ const series = props.series && (await getEntry(props.series));
|
||||||
const authorsList = await getEntries(props.authors);
|
const authorsList = await getEntries(props.authors);
|
||||||
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
||||||
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
||||||
|
const relatedBlogPosts = (await getEntries(props.relatedBlogPosts)).filter((post) => !post.data.isDraft);
|
||||||
---
|
---
|
||||||
|
|
||||||
<PublishedContentLayout
|
<PublishedContentLayout
|
||||||
|
@ -44,6 +45,7 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
|
||||||
: undefined}
|
: undefined}
|
||||||
relatedStories={relatedStories}
|
relatedStories={relatedStories}
|
||||||
relatedGames={relatedGames}
|
relatedGames={relatedGames}
|
||||||
|
relatedBlogPosts={relatedBlogPosts}
|
||||||
posts={props.posts}
|
posts={props.posts}
|
||||||
labelReturnTo={{ title: t(props.lang, "blog/return_to_blog_posts"), link: "/blog" }}
|
labelReturnTo={{ title: t(props.lang, "blog/return_to_blog_posts"), link: "/blog" }}
|
||||||
labelPreviousContent={t(props.lang, "blog/previous_post_aria_label")}
|
labelPreviousContent={t(props.lang, "blog/previous_post_aria_label")}
|
|
@ -4,15 +4,16 @@ import BaseLayout from "./BaseLayout.astro";
|
||||||
import logoBM from "../assets/images/logo_bm.png";
|
import logoBM from "../assets/images/logo_bm.png";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import {
|
import {
|
||||||
IconHome,
|
IconBlog,
|
||||||
|
IconBook,
|
||||||
IconBriefcase,
|
IconBriefcase,
|
||||||
|
IconGamepad,
|
||||||
|
IconHome,
|
||||||
|
IconMagnifyingGlass,
|
||||||
|
IconMoon,
|
||||||
IconSquareRSS,
|
IconSquareRSS,
|
||||||
IconSun,
|
IconSun,
|
||||||
IconMoon,
|
|
||||||
IconMagnifyingGlass,
|
|
||||||
IconTags,
|
IconTags,
|
||||||
IconGamepad,
|
|
||||||
IconBook,
|
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -80,6 +81,12 @@ const isCurrentRoute = (path: string) =>
|
||||||
<span class="order-3 group-hover:underline group-focus:underline">Games</span>
|
<span class="order-3 group-hover:underline group-focus:underline">Games</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="u-url text-link group" href="/blog" aria-current={isCurrentRoute("/blog") ? "page" : undefined}>
|
||||||
|
<IconBlog width="1.25rem" height="1.25rem" class="order-1 inline align-text-top" />
|
||||||
|
<span class="order-3 group-hover:underline group-focus:underline">Blog posts</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="u-url text-link group" href="/tags" aria-current={isCurrentRoute("/tags") ? "page" : undefined}>
|
<a class="u-url text-link group" href="/tags" aria-current={isCurrentRoute("/tags") ? "page" : undefined}>
|
||||||
<IconTags width="1.25rem" height="1.25rem" class="order-1 inline align-text-top" />
|
<IconTags width="1.25rem" height="1.25rem" class="order-1 inline align-text-top" />
|
||||||
|
|
|
@ -15,6 +15,7 @@ const series = props.series && (await getEntry(props.series));
|
||||||
const authorsList = await getEntries(props.authors);
|
const authorsList = await getEntries(props.authors);
|
||||||
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
||||||
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
||||||
|
const relatedBlogPosts = (await getEntries(props.relatedBlogPosts)).filter((post) => !post.data.isDraft);
|
||||||
---
|
---
|
||||||
|
|
||||||
<PublishedContentLayout
|
<PublishedContentLayout
|
||||||
|
@ -40,6 +41,7 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
|
||||||
: undefined}
|
: undefined}
|
||||||
relatedStories={relatedStories}
|
relatedStories={relatedStories}
|
||||||
relatedGames={relatedGames}
|
relatedGames={relatedGames}
|
||||||
|
relatedBlogPosts={relatedBlogPosts}
|
||||||
posts={props.posts}
|
posts={props.posts}
|
||||||
labelReturnTo={{ title: t(props.lang, "game/return_to_games"), link: "/games" }}
|
labelReturnTo={{ title: t(props.lang, "game/return_to_games"), link: "/games" }}
|
||||||
labelPreviousContent={t(props.lang, "game/previous_game_aria_label")}
|
labelPreviousContent={t(props.lang, "game/previous_game_aria_label")}
|
||||||
|
|
|
@ -19,6 +19,7 @@ const commissionersList = props.commissioners && (await getEntries(props.commiss
|
||||||
const requestersList = props.requesters && (await getEntries(props.requesters));
|
const requestersList = props.requesters && (await getEntries(props.requesters));
|
||||||
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
||||||
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
||||||
|
const relatedBlogPosts = (await getEntries(props.relatedBlogPosts)).filter((post) => !post.data.isDraft);
|
||||||
const wordCount = props.wordCount?.toString();
|
const wordCount = props.wordCount?.toString();
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ const wordCount = props.wordCount?.toString();
|
||||||
: undefined}
|
: undefined}
|
||||||
relatedStories={relatedStories}
|
relatedStories={relatedStories}
|
||||||
relatedGames={relatedGames}
|
relatedGames={relatedGames}
|
||||||
|
relatedBlogPosts={relatedBlogPosts}
|
||||||
posts={props.posts}
|
posts={props.posts}
|
||||||
labelReturnTo={{ title: t(props.lang, "story/return_to_stories"), link: "/stories/1" }}
|
labelReturnTo={{ title: t(props.lang, "story/return_to_stories"), link: "/stories/1" }}
|
||||||
labelPreviousContent={t(props.lang, "story/previous_story_aria_label")}
|
labelPreviousContent={t(props.lang, "story/previous_story_aria_label")}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
|
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
|
||||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||||
import { t } from "../i18n";
|
|
||||||
import UserComponent from "../components/UserComponent.astro";
|
import UserComponent from "../components/UserComponent.astro";
|
||||||
import { markdownToPlaintext } from "../utils/markdown_to_plaintext";
|
import { markdownToPlaintext } from "../utils/markdown_to_plaintext";
|
||||||
|
|
||||||
|
@ -34,13 +33,13 @@ const posts = await Promise.all(
|
||||||
data-tooltip
|
data-tooltip
|
||||||
>
|
>
|
||||||
{post.data.thumbnail ? (
|
{post.data.thumbnail ? (
|
||||||
<div class="flex aspect-[630/500] max-w-[288px] justify-center">
|
<div class="flex aspect-square max-w-[288px] justify-center">
|
||||||
<Image
|
<Image
|
||||||
loading={i < 10 ? "eager" : "lazy"}
|
loading={i < 10 ? "eager" : "lazy"}
|
||||||
class="u-photo m-auto"
|
class="u-photo m-auto"
|
||||||
src={post.data.thumbnail}
|
src={post.data.thumbnail}
|
||||||
alt={`Thumbnail for ${post.data.title}`}
|
alt={`Thumbnail for ${post.data.title}`}
|
||||||
width={288}
|
width={192}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { GetStaticPaths } from "astro";
|
import type { GetStaticPaths } from "astro";
|
||||||
import { type CollectionEntry, getCollection } from "astro:content";
|
import { type CollectionEntry, getCollection } from "astro:content";
|
||||||
import { PUBLISH_DRAFTS } from "astro:env/server";
|
import { PUBLISH_DRAFTS } from "astro:env/server";
|
||||||
import BlogLayout from "../../layouts/BlogLayout.astro";
|
import BlogPostLayout from "../../layouts/BlogPostLayout.astro";
|
||||||
|
|
||||||
type Props = CollectionEntry<"blog">;
|
type Props = CollectionEntry<"blog">;
|
||||||
|
|
||||||
|
@ -24,6 +24,6 @@ const post = Astro.props;
|
||||||
const { Content } = await post.render();
|
const { Content } = await post.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<BlogLayout {...post.data}>
|
<BlogPostLayout {...post.data}>
|
||||||
<Content />
|
<Content />
|
||||||
</BlogLayout>
|
</BlogPostLayout>
|
||||||
|
|
|
@ -33,7 +33,7 @@ const games = await Promise.all(
|
||||||
data-tooltip
|
data-tooltip
|
||||||
>
|
>
|
||||||
{game.data.thumbnail ? (
|
{game.data.thumbnail ? (
|
||||||
<div class="flex aspect-[630/500] max-w-[288px] justify-center">
|
<div class="flex aspect-square max-w-[288px] justify-center">
|
||||||
<Image
|
<Image
|
||||||
loading={i < 10 ? "eager" : "lazy"}
|
loading={i < 10 ? "eager" : "lazy"}
|
||||||
class="u-photo m-auto"
|
class="u-photo m-auto"
|
||||||
|
|
|
@ -75,7 +75,7 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
|
||||||
date: post.data.pubDate,
|
date: post.data.pubDate,
|
||||||
fn: async () =>
|
fn: async () =>
|
||||||
({
|
({
|
||||||
type: "Game",
|
type: "Blog post",
|
||||||
thumbnail: post.data.thumbnail,
|
thumbnail: post.data.thumbnail,
|
||||||
href: `/blog/${post.slug}`,
|
href: `/blog/${post.slug}`,
|
||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
|
|
|
@ -26,6 +26,7 @@ const { badTag } = Astro.props;
|
||||||
<meta content="No." property="og:description" />
|
<meta content="No." property="og:description" />
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex" />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{badTag}"</h1>
|
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{badTag}"</h1>
|
||||||
|
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" />
|
||||||
<p class="my-4">No.</p>
|
<p class="my-4">No.</p>
|
||||||
</GalleryLayout>
|
</GalleryLayout>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Prose from "../../components/Prose.astro";
|
||||||
import { t, DEFAULT_LANG } from "../../i18n";
|
import { t, DEFAULT_LANG } from "../../i18n";
|
||||||
import { qualifyLocalURLsInMarkdown } from "../../utils/qualify_local_urls_in_markdown";
|
import { qualifyLocalURLsInMarkdown } from "../../utils/qualify_local_urls_in_markdown";
|
||||||
import UserComponent from "../../components/UserComponent.astro";
|
import UserComponent from "../../components/UserComponent.astro";
|
||||||
|
import { markdownToPlaintext } from "../../utils/markdown_to_plaintext";
|
||||||
|
|
||||||
type EntryWithPubDate<C extends CollectionKey> = CollectionEntry<C> & { data: { pubDate: Date } };
|
type EntryWithPubDate<C extends CollectionKey> = CollectionEntry<C> & { data: { pubDate: Date } };
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ type Props = {
|
||||||
related?: string[];
|
related?: string[];
|
||||||
stories: (EntryWithPubDate<"stories"> & { authors: CollectionEntry<"users">[] })[];
|
stories: (EntryWithPubDate<"stories"> & { authors: CollectionEntry<"users">[] })[];
|
||||||
games: (EntryWithPubDate<"games"> & { authors: CollectionEntry<"users">[] })[];
|
games: (EntryWithPubDate<"games"> & { authors: CollectionEntry<"users">[] })[];
|
||||||
|
blogPosts: (EntryWithPubDate<"blog"> & { authors: CollectionEntry<"users">[] })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
|
@ -25,24 +27,22 @@ type Params = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const [stories, games, series, tagCategories] = await Promise.all([
|
const [stories, games, blogPosts, series, tagCategories] = await Promise.all([
|
||||||
getCollection("stories"),
|
getCollection("stories"),
|
||||||
getCollection("games"),
|
getCollection("games"),
|
||||||
|
getCollection("blog"),
|
||||||
getCollection("series"),
|
getCollection("series"),
|
||||||
getCollection("tag-categories"),
|
getCollection("tag-categories"),
|
||||||
]);
|
]);
|
||||||
const seriesTags = new Set(series.map((s) => s.data.name));
|
const seriesTags = new Set(series.map((s) => s.data.name));
|
||||||
const tags = new Set<string>();
|
const tags = new Set<string>();
|
||||||
stories.forEach((story) => {
|
[stories, games, blogPosts].forEach((collection) =>
|
||||||
story.data.tags.forEach((tag) => {
|
collection.forEach((content) => {
|
||||||
tags.add(tag);
|
content.data.tags.forEach((tag) => {
|
||||||
});
|
tags.add(tag);
|
||||||
});
|
});
|
||||||
games.forEach((game) => {
|
}),
|
||||||
game.data.tags.forEach((tag) => {
|
);
|
||||||
tags.add(tag);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const tagDescriptions = tagCategories.reduce(
|
const tagDescriptions = tagCategories.reduce(
|
||||||
(acc, category) => {
|
(acc, category) => {
|
||||||
category.data.tags.forEach(({ name, description, related }) => {
|
category.data.tags.forEach(({ name, description, related }) => {
|
||||||
|
@ -100,6 +100,18 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
authors: await getEntries(game.data.authors),
|
authors: await getEntries(game.data.authors),
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
|
blogPosts: await Promise.all(
|
||||||
|
(
|
||||||
|
blogPosts.filter(
|
||||||
|
(post) => !post.data.isDraft && post.data.pubDate && post.data.tags.includes(tag),
|
||||||
|
) as EntryWithPubDate<"blog">[]
|
||||||
|
)
|
||||||
|
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||||
|
.map(async (post) => ({
|
||||||
|
...post,
|
||||||
|
authors: await getEntries(post.data.authors),
|
||||||
|
})),
|
||||||
|
),
|
||||||
} satisfies Props,
|
} satisfies Props,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
@ -116,12 +128,14 @@ const totalWorksWithTag = t(
|
||||||
props.tag,
|
props.tag,
|
||||||
props.stories.length,
|
props.stories.length,
|
||||||
props.games.length,
|
props.games.length,
|
||||||
|
props.blogPosts.length,
|
||||||
);
|
);
|
||||||
---
|
---
|
||||||
|
|
||||||
<GalleryLayout pageTitle={`Works tagged "${props.tag}"`}>
|
<GalleryLayout pageTitle={`Works tagged "${props.tag}"`}>
|
||||||
<meta slot="head" content={`Bad Manners || ${totalWorksWithTag || props.tag}`} property="og:description" />
|
<meta slot="head" content={`Bad Manners || ${totalWorksWithTag || props.tag}`} property="og:description" />
|
||||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{props.tag}"</h1>
|
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{props.tag}"</h1>
|
||||||
|
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" />
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
<Prose>
|
<Prose>
|
||||||
{description ? <Markdown of={description} /> : null}
|
{description ? <Markdown of={description} /> : null}
|
||||||
|
@ -148,6 +162,7 @@ const totalWorksWithTag = t(
|
||||||
class="u-url text-link hover:underline focus:underline"
|
class="u-url text-link hover:underline focus:underline"
|
||||||
href={`/stories/${story.slug}`}
|
href={`/stories/${story.slug}`}
|
||||||
title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
|
title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
|
||||||
|
data-tooltip
|
||||||
>
|
>
|
||||||
{story.data.thumbnail ? (
|
{story.data.thumbnail ? (
|
||||||
<div class="flex aspect-square max-w-[192px] justify-center">
|
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||||
|
@ -210,9 +225,10 @@ const totalWorksWithTag = t(
|
||||||
class="u-url text-link hover:underline focus:underline"
|
class="u-url text-link hover:underline focus:underline"
|
||||||
href={`/games/${game.slug}`}
|
href={`/games/${game.slug}`}
|
||||||
title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
|
title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
|
||||||
|
data-tooltip
|
||||||
>
|
>
|
||||||
{game.data.thumbnail ? (
|
{game.data.thumbnail ? (
|
||||||
<div class="flex aspect-[630/500] max-w-[192px] justify-center">
|
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||||
<Image
|
<Image
|
||||||
class="u-photo m-auto"
|
class="u-photo m-auto"
|
||||||
src={game.data.thumbnail}
|
src={game.data.thumbnail}
|
||||||
|
@ -255,4 +271,63 @@ const totalWorksWithTag = t(
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
props.blogPosts.length > 0 && (
|
||||||
|
<section class="my-2" aria-labelledby="content-blogPosts">
|
||||||
|
<h2 id="content-blogPosts" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||||
|
Blog posts
|
||||||
|
</h2>
|
||||||
|
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
||||||
|
{props.blogPosts.map((post) => (
|
||||||
|
<li class="h-entry break-inside-avoid" lang={post.data.lang}>
|
||||||
|
<a
|
||||||
|
class="u-url text-link hover:underline focus:underline"
|
||||||
|
href={`/blog/${post.slug}`}
|
||||||
|
title={markdownToPlaintext(post.data.description)}
|
||||||
|
data-tooltip
|
||||||
|
>
|
||||||
|
{post.data.thumbnail ? (
|
||||||
|
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||||
|
<Image
|
||||||
|
class="u-photo m-auto"
|
||||||
|
src={post.data.thumbnail}
|
||||||
|
alt={`Thumbnail for ${post.data.title}`}
|
||||||
|
width={192}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div class="max-w-[192px] text-sm">
|
||||||
|
<span class="p-name" aria-label="Title">
|
||||||
|
{post.data.title}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<time
|
||||||
|
class="dt-published italic"
|
||||||
|
datetime={post.data.pubDate.toISOString().slice(0, 10)}
|
||||||
|
aria-label="Publish date"
|
||||||
|
>
|
||||||
|
{post.data.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="sr-only select-none">
|
||||||
|
<p class="p-category" aria-label="Category">
|
||||||
|
Blog post
|
||||||
|
</p>
|
||||||
|
<p class="p-summary" aria-label="Summary">
|
||||||
|
{post.data.description}
|
||||||
|
</p>
|
||||||
|
<div aria-label="Authors">
|
||||||
|
<span>{post.authors.length == 1 ? "Author:" : "Authors:"}</span>
|
||||||
|
{post.authors.map((author) => (
|
||||||
|
<UserComponent rel="author" class="p-author" user={author} lang={post.data.lang} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
</GalleryLayout>
|
</GalleryLayout>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue