Add platforms to GameLayout

This commit is contained in:
Bad Manners 2024-04-12 18:15:03 -03:00
parent 877c02ccfc
commit 837433364d
16 changed files with 144 additions and 42 deletions

View file

@ -12,6 +12,7 @@ description: |
Some funny text.
# descriptionPlaintext: >
# Some funny text.
platforms: [web, windows, linux, macos, android, ios]
# mastodonPost:
# instance: meow.social
# user: BadManners

View file

@ -9,7 +9,7 @@ links:
furaffinity:
- https://www.furaffinity.net/user/NamelessUser
- Nameless_User
inkbunny: https://inkbunny.net/NamelessManners
inkbunny: https://inkbunny.net/NamelessUser
sofurry:
- https://nameless-user.sofurry.com/
- Nameless User

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "gallery-badmanners-xyz",
"version": "1.3.0",
"version": "1.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gallery-badmanners-xyz",
"version": "1.3.0",
"version": "1.3.1",
"dependencies": {
"@astrojs/check": "^0.5.10",
"@astrojs/rss": "^4.0.5",

View file

@ -1,7 +1,7 @@
{
"name": "gallery-badmanners-xyz",
"type": "module",
"version": "1.3.0",
"version": "1.3.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",

View file

@ -0,0 +1,36 @@
---
type Props = {
breakpoint: number;
};
const { breakpoint } = Astro.props;
---
<div id="slot-switch" data-breakpoint={breakpoint}></div>
<template id="slot-switch-big">
<slot />
</template>
<template id="slot-switch-small">
<slot />
</template>
<script>
const slotSwitch = document.querySelector("div#slot-switch") as HTMLDivElement;
const breakpointAttribute = slotSwitch.getAttribute("data-breakpoint");
if (breakpointAttribute && !isNaN(parseInt(breakpointAttribute))) {
const breakpoint = parseInt(breakpointAttribute);
const slotSwitchBig = document.querySelector("template#slot-switch-big") as HTMLTemplateElement;
const slotSwitchSmall = document.querySelector("template#slot-switch-small") as HTMLTemplateElement;
let displayBig: boolean | null = null;
function handleResize() {
const newDisplayBig = window.innerWidth >= breakpoint;
if (newDisplayBig !== displayBig) {
displayBig = newDisplayBig;
const template = displayBig ? slotSwitchBig : slotSwitchSmall;
slotSwitch.replaceChildren(template.content.cloneNode(true));
}
}
window.addEventListener("resize", handleResize);
handleResize();
}
</script>

View file

@ -22,6 +22,7 @@ const refineCopyrightedCharacters = (value: Record<string, any>) => !("" in valu
const lang = z.enum(["eng", "tok"]).default("eng");
const website = z.enum(WEBSITE_LIST);
const platform = z.enum(["web", "windows", "linux", "macos", "android", "ios"]);
const mastodonPost = z.object({
instance: z.string(),
user: z.string(),
@ -48,7 +49,7 @@ const storiesCollection = defineCollection({
authors: z
.union([reference("users"), z.array(reference("users"))])
.default("bad-manners")
.refine(refineAuthors, "authors cannot be empty"),
.refine(refineAuthors, `"authors" cannot be empty`),
descriptionPlaintext: z.string().optional(),
summary: z.string().optional(),
thumbnail: image().optional(),
@ -60,10 +61,7 @@ const storiesCollection = defineCollection({
copyrightedCharacters: z
.record(z.string(), reference("users"))
.default({})
.refine(
refineCopyrightedCharacters,
"copyrightedCharacters cannot have an empty catch-all key with other keys",
),
.refine(refineCopyrightedCharacters, `"copyrightedCharacters" cannot mix empty catch-all key with other keys`),
lang,
prev: reference("stories").nullish(),
next: reference("stories").nullish(),
@ -88,19 +86,17 @@ const gamesCollection = defineCollection({
authors: z
.union([reference("users"), z.array(reference("users"))])
.default("bad-manners")
.refine(refineAuthors, "authors cannot be empty"),
.refine(refineAuthors, `"authors" cannot be empty`),
descriptionPlaintext: z.string().optional(),
thumbnail: image().optional(),
thumbnailWidth: z.number().int().optional(),
thumbnailHeight: z.number().int().optional(),
series: reference("series").optional(),
platforms: z.array(platform).refine((platforms) => platforms.length > 0, `"platforms" cannot be empty`),
copyrightedCharacters: z
.record(z.string(), reference("users"))
.default({})
.refine(
refineCopyrightedCharacters,
"copyrightedCharacters cannot have an empty catch-all key with other keys",
),
.refine(refineCopyrightedCharacters, `"copyrightedCharacters" cannot mix empty catch-all key with other keys`),
lang,
relatedStories: z.array(reference("stories")).default([]),
relatedGames: z.array(reference("games")).default([]),
@ -125,7 +121,7 @@ const usersCollection = defineCollection({
.refine(
({ links, preferredLink }) => !preferredLink || preferredLink in links,
({ preferredLink }) => ({
message: `"${preferredLink}" not defined in links`,
message: `"${preferredLink}" not defined in "links"`,
path: ["preferredLink"],
}),
),
@ -136,7 +132,7 @@ const seriesCollection = defineCollection({
schema: z.object({
// Required
name: z.string(),
url: z.string().regex(localUrlRegex, "must be a local URL"),
url: z.string().regex(localUrlRegex, `"url" must be a local URL`),
}),
});
@ -149,7 +145,7 @@ const tagCategoriesCollection = defineCollection({
tags: z.array(
z.union([
z.string(),
z.record(lang, z.string()).refine((tag) => "eng" in tag, 'Object-formatted tag must have an "eng" key'),
z.record(lang, z.string()).refine((tag) => "eng" in tag, `object-formatted tag must have an "eng" key`),
]),
),
}),

View file

@ -26,6 +26,12 @@ descriptionPlaintext: >
An original soundtrack with 9 exclusive songs;
A challenging physics-based fishing minigame with scaling difficulty;
And a special cutscene...
platforms:
- web
- windows
- linux
- macos
- android
mastodonPost:
instance: meow.social
user: BadManners

View file

@ -91,6 +91,9 @@ export const UI_STRINGS: Record<string, TranslationRecord> = {
"story/requested_by": {
eng: (arg: string) => `Requested by ${arg}`,
},
"story/draft_warning": {
eng: "DRAFT VERSION DO NOT REDISTRIBUTE",
},
"characters/characters_are_copyrighted_by": {
eng: (owner: string, charactersList: string[]) =>
charactersList.length == 1
@ -100,6 +103,32 @@ export const UI_STRINGS: Record<string, TranslationRecord> = {
"characters/all_characters_are_copyrighted_by": {
eng: (owner: string) => `All characters are © ${owner}`,
},
"game/platforms": {
eng: (platforms: string[]) => {
const translatedPlatforms = platforms.map(
(platform) => (UI_STRINGS[`game/platform_${platform}`]?.eng as string | undefined) || platform,
);
return `A game for ${(UI_STRINGS["util/join_names"]!.eng as (arg: string[]) => string)(translatedPlatforms)}`;
},
},
"game/platform_web": {
eng: "web browsers",
},
"game/platform_windows": {
eng: "Windows",
},
"game/platform_linux": {
eng: "Linux",
},
"game/platform_macos": {
eng: "macOS",
},
"game/platform_android": {
eng: "Android",
},
"game/platform_ios": {
eng: "iOS",
},
};
export function t(lang: Lang, stringOrSource: string | TranslationRecord, ...args: any[]): string {

View file

@ -70,7 +70,10 @@ const logo = await getImage({ src: logoBM, width: 192 });
</button>
</div>
</div>
<main class="ml-0 max-w-6xl px-2 pb-12 pt-4 md:ml-60 md:px-4 print:pb-0" data-pagefind-body={enablePagefind ? "" : undefined}>
<main
class="ml-0 max-w-6xl px-2 pb-12 pt-4 md:ml-60 md:px-4 print:pb-0"
data-pagefind-body={enablePagefind ? "" : undefined}
>
<slot />
</main>
</div>

View file

@ -61,10 +61,18 @@ const thumbnail =
<meta property="og:title" content={props.title} data-pagefind-meta="title[content]" />
<meta property="og:description" content={props.contentWarning} />
<meta property="og:url" content={Astro.url} data-pagefind-meta="url[content]" />
{thumbnail ? <Fragment>
{
thumbnail ? (
<Fragment>
<meta content={thumbnail.src} property="og:image" data-pagefind-meta="image[content]" />
<meta property="og:image:alt" content={`Cover art for ${props.title}`} data-pagefind-meta="image_alt[content]" />
</Fragment> : null}
<meta
property="og:image:alt"
content={`Cover art for ${props.title}`}
data-pagefind-meta="image_alt[content]"
/>
</Fragment>
) : null
}
<meta name="theme-color" content="#7DD05A" data-react-helmet="true" />
</Fragment>
<div
@ -134,10 +142,13 @@ const thumbnail =
<Authors lang={props.lang}>
{authors.map((author) => <UserComponent lang={props.lang} user={author} />)}
</Authors>
<div id="platforms">
<p>{t(props.lang, "game/platforms", props.platforms)}</p>
</div>
{
props.isDraft ? (
<p id="draft-warning" class="py-2 text-center text-2xl font-semibold not-italic text-red-600">
DRAFT VERSION &ndash; DO NOT REDISTRIBUTE
{t(props.lang, "story/draft_warning")}
</p>
) : null
}
@ -174,7 +185,7 @@ const thumbnail =
id="draft-warning-bottom"
class="py-2 text-center font-serif text-2xl font-semibold not-italic text-red-600"
>
DRAFT VERSION &ndash; DO NOT REDISTRIBUTE
{t(props.lang, "story/draft_warning")}
</p>
) : (
<p
@ -195,7 +206,7 @@ const thumbnail =
}
<section id="description" class="px-2 font-serif" aria-describedby="title-description">
<h2 id="title-description" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
Description
{t(props.lang, "story/description")}
</h2>
<Prose>
<Markdown of={props.description} />
@ -208,7 +219,7 @@ const thumbnail =
><path
d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
></path></svg
><span>To top</span></a
><span>{t(props.lang, "story/to_top")}</span></a
>
</div>
<section id="tags" aria-describedby="title-tags" class="my-5">

View file

@ -71,10 +71,18 @@ const thumbnail =
<meta property="og:title" content={props.title} data-pagefind-meta="title[content]" />
<meta property="og:description" content={`Word count: ${props.wordCount}. ${props.contentWarning}`} />
<meta property="og:url" content={Astro.url} data-pagefind-meta="url[content]" />
{thumbnail ? <Fragment>
{
thumbnail ? (
<Fragment>
<meta content={thumbnail.src} property="og:image" data-pagefind-meta="image[content]" />
<meta property="og:image:alt" content={`Cover art for ${props.shortTitle || props.title}`} data-pagefind-meta="image_alt[content]" />
</Fragment> : null}
<meta
property="og:image:alt"
content={`Cover art for ${props.shortTitle || props.title}`}
data-pagefind-meta="image_alt[content]"
/>
</Fragment>
) : null
}
<meta name="theme-color" content="#7DD05A" data-react-helmet="true" />
</Fragment>
<div
@ -184,7 +192,7 @@ const thumbnail =
{
props.isDraft ? (
<p id="draft-warning" class="py-2 text-center text-2xl font-semibold not-italic text-red-600">
DRAFT VERSION &ndash; DO NOT REDISTRIBUTE
{t(props.lang, "story/draft_warning")}
</p>
) : null
}
@ -236,7 +244,7 @@ const thumbnail =
id="draft-warning-bottom"
class="py-2 text-center font-serif text-2xl font-semibold not-italic text-red-600"
>
DRAFT VERSION &ndash; DO NOT REDISTRIBUTE
{t(props.lang, "story/draft_warning")}
</p>
) : (
<p

View file

@ -83,7 +83,8 @@ export const GET: APIRoute = async ({ site }) => {
title: `New game! "${data.title}"`,
pubDate: toNoonUTCDate(data.pubDate),
link: `/games/${slug}`,
description: `${data.contentWarning} ${data.descriptionPlaintext || data.description}`
description:
`${t(data.lang, "game/platforms", data.platforms)}. ${data.contentWarning} ${data.descriptionPlaintext || data.description}`
.replaceAll(/[\n ]+/g, " ")
.trim(),
categories: ["game"],
@ -101,6 +102,7 @@ export const GET: APIRoute = async ({ site }) => {
);
}),
)}</p>` +
`<hr><p>${t(data.lang, "game/platforms", data.platforms)}</p>` +
`<hr><p><em>${data.contentWarning.trim()}</em></p>` +
`<hr>${tinyDecode(await marked(body))}` +
`<hr>${tinyDecode(await marked(data.description))}`,

View file

@ -1,11 +1,10 @@
---
import SearchComponent from "astro-pagefind/components/Search";
import GalleryLayout from "../layouts/GalleryLayout.astro";
---
<GalleryLayout pageTitle="Search">
<meta slot="head-description" property="og:description" content="Bad Manners || Search" />
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Search</h1>
<SearchComponent id="search" className="my-4 pagefind-ui" />
<SearchComponent id="search" className="pagefind-ui my-4" />
</GalleryLayout>

View file

@ -16,11 +16,21 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
---
<GalleryLayout pageTitle={series.data.name} enablePagefind={true}>
<meta slot="head-description" property="og:description" content="Bad Manners || The story of Quince, Nikili, and Suu."/>
<meta
slot="head-description"
property="og:description"
content="Bad Manners || The story of Quince, Nikili, and Suu."
/>
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">{series.data.name}</h1>
<p class="my-4">This is the main hub for the story of Quince, Nikili, and Suu, as well as all bonus content.</p>
<section class="my-2" aria-labelledby="main-chapters">
<h2 id="main-chapters" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100" data-pagefind-meta="type:series">Main chapters</h2>
<h2
id="main-chapters"
class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100"
data-pagefind-meta="type:series"
>
Main chapters
</h2>
<details
class="mx-3 mb-6 mt-1 rounded-lg border border-stone-400 bg-stone-300 dark:border-stone-500 dark:bg-stone-700"
>

View file

@ -3,7 +3,8 @@
@tailwind utilities;
@layer components {
.text-link, .pagefind-ui .pagefind-ui__result-link {
.text-link,
.pagefind-ui .pagefind-ui__result-link {
@apply text-stone-800 hover:text-bm-500 focus:text-bm-500 dark:text-zinc-300 dark:hover:text-bm-400 dark:focus:text-bm-400;
}