Add platforms to GameLayout
This commit is contained in:
parent
877c02ccfc
commit
837433364d
16 changed files with 144 additions and 42 deletions
|
@ -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
|
||||
|
|
|
@ -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
4
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
36
src/components/WidthSlotSwitcher.astro
Normal file
36
src/components/WidthSlotSwitcher.astro
Normal 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>
|
|
@ -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`),
|
||||
]),
|
||||
),
|
||||
}),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 – 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 – 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">
|
||||
|
|
|
@ -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 – 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 – DO NOT REDISTRIBUTE
|
||||
{t(props.lang, "story/draft_warning")}
|
||||
</p>
|
||||
) : (
|
||||
<p
|
||||
|
|
|
@ -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))}`,
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue